| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "media/filters/ffmpeg_demuxer.h" | 5 #include "media/filters/ffmpeg_demuxer.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <string> | 8 #include <string> |
| 9 | 9 |
| 10 #include "base/bind.h" | 10 #include "base/bind.h" |
| 11 #include "base/callback.h" | 11 #include "base/callback.h" |
| 12 #include "base/callback_helpers.h" |
| 12 #include "base/command_line.h" | 13 #include "base/command_line.h" |
| 13 #include "base/memory/scoped_ptr.h" | 14 #include "base/memory/scoped_ptr.h" |
| 14 #include "base/message_loop.h" | 15 #include "base/message_loop.h" |
| 15 #include "base/stl_util.h" | 16 #include "base/stl_util.h" |
| 16 #include "base/string_util.h" | 17 #include "base/string_util.h" |
| 17 #include "base/task_runner_util.h" | 18 #include "base/task_runner_util.h" |
| 18 #include "base/time.h" | 19 #include "base/time.h" |
| 19 #include "media/base/audio_decoder_config.h" | 20 #include "media/base/audio_decoder_config.h" |
| 20 #include "media/base/bind_to_loop.h" | 21 #include "media/base/bind_to_loop.h" |
| 21 #include "media/base/decoder_buffer.h" | 22 #include "media/base/decoder_buffer.h" |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 93 stream_->time_base, packet->duration)); | 94 stream_->time_base, packet->duration)); |
| 94 if (buffer->GetTimestamp() != kNoTimestamp() && | 95 if (buffer->GetTimestamp() != kNoTimestamp() && |
| 95 last_packet_timestamp_ != kNoTimestamp() && | 96 last_packet_timestamp_ != kNoTimestamp() && |
| 96 last_packet_timestamp_ < buffer->GetTimestamp()) { | 97 last_packet_timestamp_ < buffer->GetTimestamp()) { |
| 97 buffered_ranges_.Add(last_packet_timestamp_, buffer->GetTimestamp()); | 98 buffered_ranges_.Add(last_packet_timestamp_, buffer->GetTimestamp()); |
| 98 demuxer_->NotifyBufferingChanged(); | 99 demuxer_->NotifyBufferingChanged(); |
| 99 } | 100 } |
| 100 last_packet_timestamp_ = buffer->GetTimestamp(); | 101 last_packet_timestamp_ = buffer->GetTimestamp(); |
| 101 | 102 |
| 102 buffer_queue_.Push(buffer); | 103 buffer_queue_.Push(buffer); |
| 103 SatisfyPendingReads(); | 104 SatisfyPendingRead(); |
| 104 } | 105 } |
| 105 | 106 |
| 106 void FFmpegDemuxerStream::SetEndOfStream() { | 107 void FFmpegDemuxerStream::SetEndOfStream() { |
| 107 DCHECK(message_loop_->BelongsToCurrentThread()); | 108 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 108 end_of_stream_ = true; | 109 end_of_stream_ = true; |
| 109 SatisfyPendingReads(); | 110 SatisfyPendingRead(); |
| 110 } | 111 } |
| 111 | 112 |
| 112 void FFmpegDemuxerStream::FlushBuffers() { | 113 void FFmpegDemuxerStream::FlushBuffers() { |
| 113 DCHECK(message_loop_->BelongsToCurrentThread()); | 114 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 114 DCHECK(read_queue_.empty()) << "Read requests should be empty"; | 115 DCHECK(read_cb_.is_null()) << "There should be no pending read"; |
| 115 buffer_queue_.Clear(); | 116 buffer_queue_.Clear(); |
| 116 end_of_stream_ = false; | 117 end_of_stream_ = false; |
| 117 last_packet_timestamp_ = kNoTimestamp(); | 118 last_packet_timestamp_ = kNoTimestamp(); |
| 118 } | 119 } |
| 119 | 120 |
| 120 void FFmpegDemuxerStream::Stop() { | 121 void FFmpegDemuxerStream::Stop() { |
| 121 DCHECK(message_loop_->BelongsToCurrentThread()); | 122 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 122 buffer_queue_.Clear(); | 123 buffer_queue_.Clear(); |
| 123 for (ReadQueue::iterator it = read_queue_.begin(); | 124 if (!read_cb_.is_null()) { |
| 124 it != read_queue_.end(); ++it) { | 125 base::ResetAndReturn(&read_cb_).Run( |
| 125 it->Run(DemuxerStream::kOk, DecoderBuffer::CreateEOSBuffer()); | 126 DemuxerStream::kOk, DecoderBuffer::CreateEOSBuffer()); |
| 126 } | 127 } |
| 127 read_queue_.clear(); | |
| 128 stopped_ = true; | 128 stopped_ = true; |
| 129 end_of_stream_ = true; | 129 end_of_stream_ = true; |
| 130 } | 130 } |
| 131 | 131 |
| 132 base::TimeDelta FFmpegDemuxerStream::duration() { | 132 base::TimeDelta FFmpegDemuxerStream::duration() { |
| 133 return duration_; | 133 return duration_; |
| 134 } | 134 } |
| 135 | 135 |
| 136 DemuxerStream::Type FFmpegDemuxerStream::type() { | 136 DemuxerStream::Type FFmpegDemuxerStream::type() { |
| 137 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 137 return type_; | 138 return type_; |
| 138 } | 139 } |
| 139 | 140 |
| 140 void FFmpegDemuxerStream::Read(const ReadCB& read_cb) { | 141 void FFmpegDemuxerStream::Read(const ReadCB& read_cb) { |
| 141 if (!message_loop_->BelongsToCurrentThread()) { | 142 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 142 message_loop_->PostTask(FROM_HERE, base::Bind( | 143 CHECK(read_cb_.is_null()) << "Overlapping reads are not supported"; |
| 143 &FFmpegDemuxerStream::Read, this, read_cb)); | 144 read_cb_ = BindToCurrentLoop(read_cb); |
| 144 return; | |
| 145 } | |
| 146 | 145 |
| 147 // Don't accept any additional reads if we've been told to stop. | 146 // Don't accept any additional reads if we've been told to stop. |
| 148 // The |demuxer_| may have been destroyed in the pipeline thread. | 147 // The |demuxer_| may have been destroyed in the pipeline thread. |
| 149 // | 148 // |
| 150 // TODO(scherkus): it would be cleaner to reply with an error message. | 149 // TODO(scherkus): it would be cleaner to reply with an error message. |
| 151 if (stopped_) { | 150 if (stopped_) { |
| 152 read_cb.Run(DemuxerStream::kOk, DecoderBuffer::CreateEOSBuffer()); | 151 base::ResetAndReturn(&read_cb_).Run( |
| 152 DemuxerStream::kOk, DecoderBuffer::CreateEOSBuffer()); |
| 153 return; | 153 return; |
| 154 } | 154 } |
| 155 | 155 |
| 156 read_queue_.push_back(read_cb); | 156 SatisfyPendingRead(); |
| 157 SatisfyPendingReads(); | |
| 158 } | 157 } |
| 159 | 158 |
| 160 void FFmpegDemuxerStream::EnableBitstreamConverter() { | 159 void FFmpegDemuxerStream::EnableBitstreamConverter() { |
| 161 if (!message_loop_->BelongsToCurrentThread()) { | 160 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 162 message_loop_->PostTask(FROM_HERE, base::Bind( | |
| 163 &FFmpegDemuxerStream::EnableBitstreamConverter, this)); | |
| 164 return; | |
| 165 } | |
| 166 CHECK(bitstream_converter_.get()); | 161 CHECK(bitstream_converter_.get()); |
| 167 bitstream_converter_enabled_ = true; | 162 bitstream_converter_enabled_ = true; |
| 168 } | 163 } |
| 169 | 164 |
| 170 const AudioDecoderConfig& FFmpegDemuxerStream::audio_decoder_config() { | 165 const AudioDecoderConfig& FFmpegDemuxerStream::audio_decoder_config() { |
| 166 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 171 CHECK_EQ(type_, AUDIO); | 167 CHECK_EQ(type_, AUDIO); |
| 172 return audio_config_; | 168 return audio_config_; |
| 173 } | 169 } |
| 174 | 170 |
| 175 const VideoDecoderConfig& FFmpegDemuxerStream::video_decoder_config() { | 171 const VideoDecoderConfig& FFmpegDemuxerStream::video_decoder_config() { |
| 172 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 176 CHECK_EQ(type_, VIDEO); | 173 CHECK_EQ(type_, VIDEO); |
| 177 return video_config_; | 174 return video_config_; |
| 178 } | 175 } |
| 179 | 176 |
| 180 FFmpegDemuxerStream::~FFmpegDemuxerStream() { | 177 FFmpegDemuxerStream::~FFmpegDemuxerStream() { |
| 181 DCHECK(stopped_); | 178 DCHECK(stopped_); |
| 182 DCHECK(read_queue_.empty()); | 179 DCHECK(read_cb_.is_null()); |
| 183 DCHECK(buffer_queue_.IsEmpty()); | 180 DCHECK(buffer_queue_.IsEmpty()); |
| 184 } | 181 } |
| 185 | 182 |
| 186 base::TimeDelta FFmpegDemuxerStream::GetElapsedTime() const { | 183 base::TimeDelta FFmpegDemuxerStream::GetElapsedTime() const { |
| 187 return ConvertStreamTimestamp(stream_->time_base, stream_->cur_dts); | 184 return ConvertStreamTimestamp(stream_->time_base, stream_->cur_dts); |
| 188 } | 185 } |
| 189 | 186 |
| 190 Ranges<base::TimeDelta> FFmpegDemuxerStream::GetBufferedRanges() const { | 187 Ranges<base::TimeDelta> FFmpegDemuxerStream::GetBufferedRanges() const { |
| 191 return buffered_ranges_; | 188 return buffered_ranges_; |
| 192 } | 189 } |
| 193 | 190 |
| 194 void FFmpegDemuxerStream::SatisfyPendingReads() { | 191 void FFmpegDemuxerStream::SatisfyPendingRead() { |
| 195 DCHECK(message_loop_->BelongsToCurrentThread()); | 192 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 196 while (!read_queue_.empty()) { | 193 if (!read_cb_.is_null()) { |
| 197 scoped_refptr<DecoderBuffer> buffer; | |
| 198 | |
| 199 if (!buffer_queue_.IsEmpty()) { | 194 if (!buffer_queue_.IsEmpty()) { |
| 200 buffer = buffer_queue_.Pop(); | 195 base::ResetAndReturn(&read_cb_).Run( |
| 196 DemuxerStream::kOk, buffer_queue_.Pop()); |
| 201 } else if (end_of_stream_) { | 197 } else if (end_of_stream_) { |
| 202 buffer = DecoderBuffer::CreateEOSBuffer(); | 198 base::ResetAndReturn(&read_cb_).Run( |
| 203 } else { | 199 DemuxerStream::kOk, DecoderBuffer::CreateEOSBuffer()); |
| 204 break; | |
| 205 } | 200 } |
| 206 | |
| 207 // Send buffer back on a new execution stack to avoid recursing. | |
| 208 ReadCB read_cb = read_queue_.front(); | |
| 209 read_queue_.pop_front(); | |
| 210 message_loop_->PostTask(FROM_HERE, base::Bind( | |
| 211 read_cb, DemuxerStream::kOk, buffer)); | |
| 212 } | 201 } |
| 213 | 202 |
| 214 // Have capacity? Ask for more! | 203 // Have capacity? Ask for more! |
| 215 if (HasAvailableCapacity() && !end_of_stream_) { | 204 if (HasAvailableCapacity() && !end_of_stream_) { |
| 216 demuxer_->NotifyCapacityAvailable(); | 205 demuxer_->NotifyCapacityAvailable(); |
| 217 } | 206 } |
| 218 } | 207 } |
| 219 | 208 |
| 220 bool FFmpegDemuxerStream::HasAvailableCapacity() { | 209 bool FFmpegDemuxerStream::HasAvailableCapacity() { |
| 221 // Try to have one second's worth of encoded data per stream. | 210 // Try to have one second's worth of encoded data per stream. |
| (...skipping 28 matching lines...) Expand all Loading... |
| 250 duration_known_(false), | 239 duration_known_(false), |
| 251 url_protocol_(data_source, BindToLoop(message_loop_, base::Bind( | 240 url_protocol_(data_source, BindToLoop(message_loop_, base::Bind( |
| 252 &FFmpegDemuxer::OnDataSourceError, base::Unretained(this)))) { | 241 &FFmpegDemuxer::OnDataSourceError, base::Unretained(this)))) { |
| 253 DCHECK(message_loop_); | 242 DCHECK(message_loop_); |
| 254 DCHECK(data_source_); | 243 DCHECK(data_source_); |
| 255 } | 244 } |
| 256 | 245 |
| 257 FFmpegDemuxer::~FFmpegDemuxer() {} | 246 FFmpegDemuxer::~FFmpegDemuxer() {} |
| 258 | 247 |
| 259 void FFmpegDemuxer::Stop(const base::Closure& callback) { | 248 void FFmpegDemuxer::Stop(const base::Closure& callback) { |
| 260 // Post a task to notify the streams to stop as well. | 249 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 261 message_loop_->PostTask(FROM_HERE, | 250 url_protocol_.Abort(); |
| 262 base::Bind(&FFmpegDemuxer::StopTask, this, callback)); | 251 data_source_->Stop(BindToCurrentLoop(base::Bind( |
| 252 &FFmpegDemuxer::OnDataSourceStopped, this, BindToCurrentLoop(callback)))); |
| 263 } | 253 } |
| 264 | 254 |
| 265 void FFmpegDemuxer::Seek(base::TimeDelta time, const PipelineStatusCB& cb) { | 255 void FFmpegDemuxer::Seek(base::TimeDelta time, const PipelineStatusCB& cb) { |
| 266 message_loop_->PostTask(FROM_HERE, | 256 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 267 base::Bind(&FFmpegDemuxer::SeekTask, this, time, cb)); | 257 CHECK(!pending_seek_); |
| 258 |
| 259 // TODO(scherkus): Inspect |pending_read_| and cancel IO via |blocking_url_|, |
| 260 // otherwise we can end up waiting for a pre-seek read to complete even though |
| 261 // we know we're going to drop it on the floor. |
| 262 |
| 263 // Always seek to a timestamp less than or equal to the desired timestamp. |
| 264 int flags = AVSEEK_FLAG_BACKWARD; |
| 265 |
| 266 // Passing -1 as our stream index lets FFmpeg pick a default stream. FFmpeg |
| 267 // will attempt to use the lowest-index video stream, if present, followed by |
| 268 // the lowest-index audio stream. |
| 269 pending_seek_ = true; |
| 270 base::PostTaskAndReplyWithResult( |
| 271 blocking_thread_.message_loop_proxy(), FROM_HERE, |
| 272 base::Bind(&av_seek_frame, glue_->format_context(), -1, |
| 273 time.InMicroseconds(), flags), |
| 274 base::Bind(&FFmpegDemuxer::OnSeekFrameDone, this, cb)); |
| 268 } | 275 } |
| 269 | 276 |
| 270 void FFmpegDemuxer::SetPlaybackRate(float playback_rate) { | 277 void FFmpegDemuxer::SetPlaybackRate(float playback_rate) { |
| 271 DCHECK(data_source_.get()); | 278 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 272 data_source_->SetPlaybackRate(playback_rate); | 279 data_source_->SetPlaybackRate(playback_rate); |
| 273 } | 280 } |
| 274 | 281 |
| 275 void FFmpegDemuxer::OnAudioRendererDisabled() { | 282 void FFmpegDemuxer::OnAudioRendererDisabled() { |
| 276 message_loop_->PostTask(FROM_HERE, base::Bind( | 283 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 277 &FFmpegDemuxer::DisableAudioStreamTask, this)); | 284 audio_disabled_ = true; |
| 285 StreamVector::iterator iter; |
| 286 for (iter = streams_.begin(); iter != streams_.end(); ++iter) { |
| 287 if (*iter && (*iter)->type() == DemuxerStream::AUDIO) { |
| 288 (*iter)->Stop(); |
| 289 } |
| 290 } |
| 278 } | 291 } |
| 279 | 292 |
| 280 void FFmpegDemuxer::Initialize(DemuxerHost* host, | 293 void FFmpegDemuxer::Initialize(DemuxerHost* host, |
| 281 const PipelineStatusCB& status_cb) { | 294 const PipelineStatusCB& status_cb) { |
| 282 message_loop_->PostTask(FROM_HERE, base::Bind( | 295 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 283 &FFmpegDemuxer::InitializeTask, this, host, status_cb)); | 296 host_ = host; |
| 297 |
| 298 // TODO(scherkus): DataSource should have a host by this point, |
| 299 // see http://crbug.com/122071 |
| 300 data_source_->set_host(host); |
| 301 |
| 302 glue_.reset(new FFmpegGlue(&url_protocol_)); |
| 303 AVFormatContext* format_context = glue_->format_context(); |
| 304 |
| 305 // Disable ID3v1 tag reading to avoid costly seeks to end of file for data we |
| 306 // don't use. FFmpeg will only read ID3v1 tags if no other metadata is |
| 307 // available, so add a metadata entry to ensure some is always present. |
| 308 av_dict_set(&format_context->metadata, "skip_id3v1_tags", "", 0); |
| 309 |
| 310 // Open the AVFormatContext using our glue layer. |
| 311 CHECK(blocking_thread_.Start()); |
| 312 base::PostTaskAndReplyWithResult( |
| 313 blocking_thread_.message_loop_proxy(), FROM_HERE, |
| 314 base::Bind(&FFmpegGlue::OpenContext, base::Unretained(glue_.get())), |
| 315 base::Bind(&FFmpegDemuxer::OnOpenContextDone, this, status_cb)); |
| 284 } | 316 } |
| 285 | 317 |
| 286 scoped_refptr<DemuxerStream> FFmpegDemuxer::GetStream( | 318 scoped_refptr<DemuxerStream> FFmpegDemuxer::GetStream( |
| 287 DemuxerStream::Type type) { | 319 DemuxerStream::Type type) { |
| 320 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 288 return GetFFmpegStream(type); | 321 return GetFFmpegStream(type); |
| 289 } | 322 } |
| 290 | 323 |
| 291 scoped_refptr<FFmpegDemuxerStream> FFmpegDemuxer::GetFFmpegStream( | 324 scoped_refptr<FFmpegDemuxerStream> FFmpegDemuxer::GetFFmpegStream( |
| 292 DemuxerStream::Type type) const { | 325 DemuxerStream::Type type) const { |
| 293 StreamVector::const_iterator iter; | 326 StreamVector::const_iterator iter; |
| 294 for (iter = streams_.begin(); iter != streams_.end(); ++iter) { | 327 for (iter = streams_.begin(); iter != streams_.end(); ++iter) { |
| 295 if (*iter && (*iter)->type() == type) { | 328 if (*iter && (*iter)->type() == type) { |
| 296 return *iter; | 329 return *iter; |
| 297 } | 330 } |
| 298 } | 331 } |
| 299 return NULL; | 332 return NULL; |
| 300 } | 333 } |
| 301 | 334 |
| 302 base::TimeDelta FFmpegDemuxer::GetStartTime() const { | 335 base::TimeDelta FFmpegDemuxer::GetStartTime() const { |
| 336 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 303 return start_time_; | 337 return start_time_; |
| 304 } | 338 } |
| 305 | 339 |
| 306 // Helper for calculating the bitrate of the media based on information stored | 340 // Helper for calculating the bitrate of the media based on information stored |
| 307 // in |format_context| or failing that the size and duration of the media. | 341 // in |format_context| or failing that the size and duration of the media. |
| 308 // | 342 // |
| 309 // Returns 0 if a bitrate could not be determined. | 343 // Returns 0 if a bitrate could not be determined. |
| 310 static int CalculateBitrate( | 344 static int CalculateBitrate( |
| 311 AVFormatContext* format_context, | 345 AVFormatContext* format_context, |
| 312 const base::TimeDelta& duration, | 346 const base::TimeDelta& duration, |
| (...skipping 19 matching lines...) Expand all Loading... |
| 332 return 0; | 366 return 0; |
| 333 } | 367 } |
| 334 | 368 |
| 335 // Do math in floating point as we'd overflow an int64 if the filesize was | 369 // Do math in floating point as we'd overflow an int64 if the filesize was |
| 336 // larger than ~1073GB. | 370 // larger than ~1073GB. |
| 337 double bytes = filesize_in_bytes; | 371 double bytes = filesize_in_bytes; |
| 338 double duration_us = duration.InMicroseconds(); | 372 double duration_us = duration.InMicroseconds(); |
| 339 return bytes * 8000000.0 / duration_us; | 373 return bytes * 8000000.0 / duration_us; |
| 340 } | 374 } |
| 341 | 375 |
| 342 void FFmpegDemuxer::InitializeTask(DemuxerHost* host, | |
| 343 const PipelineStatusCB& status_cb) { | |
| 344 DCHECK(message_loop_->BelongsToCurrentThread()); | |
| 345 host_ = host; | |
| 346 | |
| 347 // TODO(scherkus): DataSource should have a host by this point, | |
| 348 // see http://crbug.com/122071 | |
| 349 data_source_->set_host(host); | |
| 350 | |
| 351 glue_.reset(new FFmpegGlue(&url_protocol_)); | |
| 352 AVFormatContext* format_context = glue_->format_context(); | |
| 353 | |
| 354 // Disable ID3v1 tag reading to avoid costly seeks to end of file for data we | |
| 355 // don't use. FFmpeg will only read ID3v1 tags if no other metadata is | |
| 356 // available, so add a metadata entry to ensure some is always present. | |
| 357 av_dict_set(&format_context->metadata, "skip_id3v1_tags", "", 0); | |
| 358 | |
| 359 // Open the AVFormatContext using our glue layer. | |
| 360 CHECK(blocking_thread_.Start()); | |
| 361 base::PostTaskAndReplyWithResult( | |
| 362 blocking_thread_.message_loop_proxy(), FROM_HERE, | |
| 363 base::Bind(&FFmpegGlue::OpenContext, base::Unretained(glue_.get())), | |
| 364 base::Bind(&FFmpegDemuxer::OnOpenContextDone, this, status_cb)); | |
| 365 } | |
| 366 | |
| 367 void FFmpegDemuxer::OnOpenContextDone(const PipelineStatusCB& status_cb, | 376 void FFmpegDemuxer::OnOpenContextDone(const PipelineStatusCB& status_cb, |
| 368 bool result) { | 377 bool result) { |
| 369 DCHECK(message_loop_->BelongsToCurrentThread()); | 378 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 370 if (!blocking_thread_.IsRunning()) { | 379 if (!blocking_thread_.IsRunning()) { |
| 371 status_cb.Run(PIPELINE_ERROR_ABORT); | 380 status_cb.Run(PIPELINE_ERROR_ABORT); |
| 372 return; | 381 return; |
| 373 } | 382 } |
| 374 | 383 |
| 375 if (!result) { | 384 if (!result) { |
| 376 status_cb.Run(DEMUXER_ERROR_COULD_NOT_OPEN); | 385 status_cb.Run(DEMUXER_ERROR_COULD_NOT_OPEN); |
| (...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 471 | 480 |
| 472 int64 filesize_in_bytes = 0; | 481 int64 filesize_in_bytes = 0; |
| 473 url_protocol_.GetSize(&filesize_in_bytes); | 482 url_protocol_.GetSize(&filesize_in_bytes); |
| 474 bitrate_ = CalculateBitrate(format_context, max_duration, filesize_in_bytes); | 483 bitrate_ = CalculateBitrate(format_context, max_duration, filesize_in_bytes); |
| 475 if (bitrate_ > 0) | 484 if (bitrate_ > 0) |
| 476 data_source_->SetBitrate(bitrate_); | 485 data_source_->SetBitrate(bitrate_); |
| 477 | 486 |
| 478 status_cb.Run(PIPELINE_OK); | 487 status_cb.Run(PIPELINE_OK); |
| 479 } | 488 } |
| 480 | 489 |
| 481 void FFmpegDemuxer::SeekTask(base::TimeDelta time, const PipelineStatusCB& cb) { | |
| 482 DCHECK(message_loop_->BelongsToCurrentThread()); | |
| 483 CHECK(!pending_seek_); | |
| 484 | |
| 485 // TODO(scherkus): Inspect |pending_read_| and cancel IO via |blocking_url_|, | |
| 486 // otherwise we can end up waiting for a pre-seek read to complete even though | |
| 487 // we know we're going to drop it on the floor. | |
| 488 | |
| 489 // Always seek to a timestamp less than or equal to the desired timestamp. | |
| 490 int flags = AVSEEK_FLAG_BACKWARD; | |
| 491 | |
| 492 // Passing -1 as our stream index lets FFmpeg pick a default stream. FFmpeg | |
| 493 // will attempt to use the lowest-index video stream, if present, followed by | |
| 494 // the lowest-index audio stream. | |
| 495 pending_seek_ = true; | |
| 496 base::PostTaskAndReplyWithResult( | |
| 497 blocking_thread_.message_loop_proxy(), FROM_HERE, | |
| 498 base::Bind(&av_seek_frame, glue_->format_context(), -1, | |
| 499 time.InMicroseconds(), flags), | |
| 500 base::Bind(&FFmpegDemuxer::OnSeekFrameDone, this, cb)); | |
| 501 } | |
| 502 | |
| 503 void FFmpegDemuxer::OnSeekFrameDone(const PipelineStatusCB& cb, int result) { | 490 void FFmpegDemuxer::OnSeekFrameDone(const PipelineStatusCB& cb, int result) { |
| 504 DCHECK(message_loop_->BelongsToCurrentThread()); | 491 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 505 CHECK(pending_seek_); | 492 CHECK(pending_seek_); |
| 506 pending_seek_ = false; | 493 pending_seek_ = false; |
| 507 | 494 |
| 508 if (!blocking_thread_.IsRunning()) { | 495 if (!blocking_thread_.IsRunning()) { |
| 509 cb.Run(PIPELINE_ERROR_ABORT); | 496 cb.Run(PIPELINE_ERROR_ABORT); |
| 510 return; | 497 return; |
| 511 } | 498 } |
| 512 | 499 |
| 513 if (result < 0) { | 500 if (result < 0) { |
| 514 // Use VLOG(1) instead of NOTIMPLEMENTED() to prevent the message being | 501 // Use VLOG(1) instead of NOTIMPLEMENTED() to prevent the message being |
| 515 // captured from stdout and contaminates testing. | 502 // captured from stdout and contaminates testing. |
| 516 // TODO(scherkus): Implement this properly and signal error (BUG=23447). | 503 // TODO(scherkus): Implement this properly and signal error (BUG=23447). |
| 517 VLOG(1) << "Not implemented"; | 504 VLOG(1) << "Not implemented"; |
| 518 } | 505 } |
| 519 | 506 |
| 520 // Tell streams to flush buffers due to seeking. | 507 // Tell streams to flush buffers due to seeking. |
| 521 StreamVector::iterator iter; | 508 StreamVector::iterator iter; |
| 522 for (iter = streams_.begin(); iter != streams_.end(); ++iter) { | 509 for (iter = streams_.begin(); iter != streams_.end(); ++iter) { |
| 523 if (*iter) | 510 if (*iter) |
| 524 (*iter)->FlushBuffers(); | 511 (*iter)->FlushBuffers(); |
| 525 } | 512 } |
| 526 | 513 |
| 527 // Resume demuxing until capacity. | 514 // Resume reading until capacity. |
| 528 DemuxTask(); | 515 ReadFrameIfNeeded(); |
| 529 | 516 |
| 530 // Notify we're finished seeking. | 517 // Notify we're finished seeking. |
| 531 cb.Run(PIPELINE_OK); | 518 cb.Run(PIPELINE_OK); |
| 532 } | 519 } |
| 533 | 520 |
| 534 void FFmpegDemuxer::DemuxTask() { | 521 void FFmpegDemuxer::ReadFrameIfNeeded() { |
| 535 DCHECK(message_loop_->BelongsToCurrentThread()); | 522 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 536 | 523 |
| 537 // Make sure we have work to do before demuxing. | 524 // Make sure we have work to do before reading. |
| 538 if (!blocking_thread_.IsRunning() || !StreamsHaveAvailableCapacity() || | 525 if (!blocking_thread_.IsRunning() || !StreamsHaveAvailableCapacity() || |
| 539 pending_read_ || pending_seek_) { | 526 pending_read_ || pending_seek_) { |
| 540 return; | 527 return; |
| 541 } | 528 } |
| 542 | 529 |
| 543 // Allocate and read an AVPacket from the media. Save |packet_ptr| since | 530 // Allocate and read an AVPacket from the media. Save |packet_ptr| since |
| 544 // evaluation order of packet.get() and base::Passed(&packet) is | 531 // evaluation order of packet.get() and base::Passed(&packet) is |
| 545 // undefined. | 532 // undefined. |
| 546 ScopedAVPacket packet(new AVPacket()); | 533 ScopedAVPacket packet(new AVPacket()); |
| 547 AVPacket* packet_ptr = packet.get(); | 534 AVPacket* packet_ptr = packet.get(); |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 593 // Defend against ffmpeg giving us a bad stream index. | 580 // Defend against ffmpeg giving us a bad stream index. |
| 594 if (packet->stream_index >= 0 && | 581 if (packet->stream_index >= 0 && |
| 595 packet->stream_index < static_cast<int>(streams_.size()) && | 582 packet->stream_index < static_cast<int>(streams_.size()) && |
| 596 streams_[packet->stream_index] && | 583 streams_[packet->stream_index] && |
| 597 (!audio_disabled_ || | 584 (!audio_disabled_ || |
| 598 streams_[packet->stream_index]->type() != DemuxerStream::AUDIO)) { | 585 streams_[packet->stream_index]->type() != DemuxerStream::AUDIO)) { |
| 599 FFmpegDemuxerStream* demuxer_stream = streams_[packet->stream_index]; | 586 FFmpegDemuxerStream* demuxer_stream = streams_[packet->stream_index]; |
| 600 demuxer_stream->EnqueuePacket(packet.Pass()); | 587 demuxer_stream->EnqueuePacket(packet.Pass()); |
| 601 } | 588 } |
| 602 | 589 |
| 603 // Keep demuxing until we've reached capacity. | 590 // Keep reading until we've reached capacity. |
| 604 DemuxTask(); | 591 ReadFrameIfNeeded(); |
| 605 } | |
| 606 | |
| 607 void FFmpegDemuxer::StopTask(const base::Closure& callback) { | |
| 608 DCHECK(message_loop_->BelongsToCurrentThread()); | |
| 609 url_protocol_.Abort(); | |
| 610 data_source_->Stop(BindToLoop(message_loop_, base::Bind( | |
| 611 &FFmpegDemuxer::OnDataSourceStopped, this, callback))); | |
| 612 } | 592 } |
| 613 | 593 |
| 614 void FFmpegDemuxer::OnDataSourceStopped(const base::Closure& callback) { | 594 void FFmpegDemuxer::OnDataSourceStopped(const base::Closure& callback) { |
| 615 // This will block until all tasks complete. Note that after this returns it's | 595 // This will block until all tasks complete. Note that after this returns it's |
| 616 // possible for reply tasks (e.g., OnReadFrameDone()) to be queued on this | 596 // possible for reply tasks (e.g., OnReadFrameDone()) to be queued on this |
| 617 // thread. Each of the reply task methods must check whether we've stopped the | 597 // thread. Each of the reply task methods must check whether we've stopped the |
| 618 // thread and drop their results on the floor. | 598 // thread and drop their results on the floor. |
| 619 DCHECK(message_loop_->BelongsToCurrentThread()); | 599 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 620 blocking_thread_.Stop(); | 600 blocking_thread_.Stop(); |
| 621 | 601 |
| 622 StreamVector::iterator iter; | 602 StreamVector::iterator iter; |
| 623 for (iter = streams_.begin(); iter != streams_.end(); ++iter) { | 603 for (iter = streams_.begin(); iter != streams_.end(); ++iter) { |
| 624 if (*iter) | 604 if (*iter) |
| 625 (*iter)->Stop(); | 605 (*iter)->Stop(); |
| 626 } | 606 } |
| 627 | 607 |
| 628 callback.Run(); | 608 callback.Run(); |
| 629 } | 609 } |
| 630 | 610 |
| 631 void FFmpegDemuxer::DisableAudioStreamTask() { | |
| 632 DCHECK(message_loop_->BelongsToCurrentThread()); | |
| 633 audio_disabled_ = true; | |
| 634 StreamVector::iterator iter; | |
| 635 for (iter = streams_.begin(); iter != streams_.end(); ++iter) { | |
| 636 if (*iter && (*iter)->type() == DemuxerStream::AUDIO) { | |
| 637 (*iter)->Stop(); | |
| 638 } | |
| 639 } | |
| 640 } | |
| 641 | |
| 642 bool FFmpegDemuxer::StreamsHaveAvailableCapacity() { | 611 bool FFmpegDemuxer::StreamsHaveAvailableCapacity() { |
| 643 DCHECK(message_loop_->BelongsToCurrentThread()); | 612 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 644 StreamVector::iterator iter; | 613 StreamVector::iterator iter; |
| 645 for (iter = streams_.begin(); iter != streams_.end(); ++iter) { | 614 for (iter = streams_.begin(); iter != streams_.end(); ++iter) { |
| 646 if (*iter && (*iter)->HasAvailableCapacity()) { | 615 if (*iter && (*iter)->HasAvailableCapacity()) { |
| 647 return true; | 616 return true; |
| 648 } | 617 } |
| 649 } | 618 } |
| 650 return false; | 619 return false; |
| 651 } | 620 } |
| 652 | 621 |
| 653 void FFmpegDemuxer::StreamHasEnded() { | 622 void FFmpegDemuxer::StreamHasEnded() { |
| 654 DCHECK(message_loop_->BelongsToCurrentThread()); | 623 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 655 StreamVector::iterator iter; | 624 StreamVector::iterator iter; |
| 656 for (iter = streams_.begin(); iter != streams_.end(); ++iter) { | 625 for (iter = streams_.begin(); iter != streams_.end(); ++iter) { |
| 657 if (!*iter || | 626 if (!*iter || |
| 658 (audio_disabled_ && (*iter)->type() == DemuxerStream::AUDIO)) { | 627 (audio_disabled_ && (*iter)->type() == DemuxerStream::AUDIO)) { |
| 659 continue; | 628 continue; |
| 660 } | 629 } |
| 661 (*iter)->SetEndOfStream(); | 630 (*iter)->SetEndOfStream(); |
| 662 } | 631 } |
| 663 } | 632 } |
| 664 | 633 |
| 665 void FFmpegDemuxer::NotifyCapacityAvailable() { | 634 void FFmpegDemuxer::NotifyCapacityAvailable() { |
| 666 DCHECK(message_loop_->BelongsToCurrentThread()); | 635 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 667 DemuxTask(); | 636 ReadFrameIfNeeded(); |
| 668 } | 637 } |
| 669 | 638 |
| 670 void FFmpegDemuxer::NotifyBufferingChanged() { | 639 void FFmpegDemuxer::NotifyBufferingChanged() { |
| 671 DCHECK(message_loop_->BelongsToCurrentThread()); | 640 DCHECK(message_loop_->BelongsToCurrentThread()); |
| 672 Ranges<base::TimeDelta> buffered; | 641 Ranges<base::TimeDelta> buffered; |
| 673 scoped_refptr<FFmpegDemuxerStream> audio = | 642 scoped_refptr<FFmpegDemuxerStream> audio = |
| 674 audio_disabled_ ? NULL : GetFFmpegStream(DemuxerStream::AUDIO); | 643 audio_disabled_ ? NULL : GetFFmpegStream(DemuxerStream::AUDIO); |
| 675 scoped_refptr<FFmpegDemuxerStream> video = | 644 scoped_refptr<FFmpegDemuxerStream> video = |
| 676 GetFFmpegStream(DemuxerStream::VIDEO); | 645 GetFFmpegStream(DemuxerStream::VIDEO); |
| 677 if (audio && video) { | 646 if (audio && video) { |
| 678 buffered = audio->GetBufferedRanges().IntersectionWith( | 647 buffered = audio->GetBufferedRanges().IntersectionWith( |
| 679 video->GetBufferedRanges()); | 648 video->GetBufferedRanges()); |
| 680 } else if (audio) { | 649 } else if (audio) { |
| 681 buffered = audio->GetBufferedRanges(); | 650 buffered = audio->GetBufferedRanges(); |
| 682 } else if (video) { | 651 } else if (video) { |
| 683 buffered = video->GetBufferedRanges(); | 652 buffered = video->GetBufferedRanges(); |
| 684 } | 653 } |
| 685 for (size_t i = 0; i < buffered.size(); ++i) | 654 for (size_t i = 0; i < buffered.size(); ++i) |
| 686 host_->AddBufferedTimeRange(buffered.start(i), buffered.end(i)); | 655 host_->AddBufferedTimeRange(buffered.start(i), buffered.end(i)); |
| 687 } | 656 } |
| 688 | 657 |
| 689 void FFmpegDemuxer::OnDataSourceError() { | 658 void FFmpegDemuxer::OnDataSourceError() { |
| 690 host_->OnDemuxerError(PIPELINE_ERROR_READ); | 659 host_->OnDemuxerError(PIPELINE_ERROR_READ); |
| 691 } | 660 } |
| 692 | 661 |
| 693 } // namespace media | 662 } // namespace media |
| OLD | NEW |