| Index: media/filters/ffmpeg_demuxer.cc
|
| diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc
|
| index 326b8818998e8e79d6a4937e144e59a1e6a668ef..a53e7c19bf2ced14724d7f57a158d8e40e1f5fcc 100644
|
| --- a/media/filters/ffmpeg_demuxer.cc
|
| +++ b/media/filters/ffmpeg_demuxer.cc
|
| @@ -9,6 +9,7 @@
|
|
|
| #include "base/bind.h"
|
| #include "base/callback.h"
|
| +#include "base/callback_helpers.h"
|
| #include "base/command_line.h"
|
| #include "base/memory/scoped_ptr.h"
|
| #include "base/message_loop.h"
|
| @@ -100,18 +101,18 @@ void FFmpegDemuxerStream::EnqueuePacket(ScopedAVPacket packet) {
|
| last_packet_timestamp_ = buffer->GetTimestamp();
|
|
|
| buffer_queue_.Push(buffer);
|
| - SatisfyPendingReads();
|
| + SatisfyPendingRead();
|
| }
|
|
|
| void FFmpegDemuxerStream::SetEndOfStream() {
|
| DCHECK(message_loop_->BelongsToCurrentThread());
|
| end_of_stream_ = true;
|
| - SatisfyPendingReads();
|
| + SatisfyPendingRead();
|
| }
|
|
|
| void FFmpegDemuxerStream::FlushBuffers() {
|
| DCHECK(message_loop_->BelongsToCurrentThread());
|
| - DCHECK(read_queue_.empty()) << "Read requests should be empty";
|
| + DCHECK(read_cb_.is_null()) << "There should be no pending read";
|
| buffer_queue_.Clear();
|
| end_of_stream_ = false;
|
| last_packet_timestamp_ = kNoTimestamp();
|
| @@ -120,11 +121,10 @@ void FFmpegDemuxerStream::FlushBuffers() {
|
| void FFmpegDemuxerStream::Stop() {
|
| DCHECK(message_loop_->BelongsToCurrentThread());
|
| buffer_queue_.Clear();
|
| - for (ReadQueue::iterator it = read_queue_.begin();
|
| - it != read_queue_.end(); ++it) {
|
| - it->Run(DemuxerStream::kOk, DecoderBuffer::CreateEOSBuffer());
|
| + if (!read_cb_.is_null()) {
|
| + base::ResetAndReturn(&read_cb_).Run(
|
| + DemuxerStream::kOk, DecoderBuffer::CreateEOSBuffer());
|
| }
|
| - read_queue_.clear();
|
| stopped_ = true;
|
| end_of_stream_ = true;
|
| }
|
| @@ -134,52 +134,49 @@ base::TimeDelta FFmpegDemuxerStream::duration() {
|
| }
|
|
|
| DemuxerStream::Type FFmpegDemuxerStream::type() {
|
| + DCHECK(message_loop_->BelongsToCurrentThread());
|
| return type_;
|
| }
|
|
|
| void FFmpegDemuxerStream::Read(const ReadCB& read_cb) {
|
| - if (!message_loop_->BelongsToCurrentThread()) {
|
| - message_loop_->PostTask(FROM_HERE, base::Bind(
|
| - &FFmpegDemuxerStream::Read, this, read_cb));
|
| - return;
|
| - }
|
| + DCHECK(message_loop_->BelongsToCurrentThread());
|
| + CHECK(read_cb_.is_null()) << "Overlapping reads are not supported";
|
| + read_cb_ = BindToCurrentLoop(read_cb);
|
|
|
| // Don't accept any additional reads if we've been told to stop.
|
| // The |demuxer_| may have been destroyed in the pipeline thread.
|
| //
|
| // TODO(scherkus): it would be cleaner to reply with an error message.
|
| if (stopped_) {
|
| - read_cb.Run(DemuxerStream::kOk, DecoderBuffer::CreateEOSBuffer());
|
| + base::ResetAndReturn(&read_cb_).Run(
|
| + DemuxerStream::kOk, DecoderBuffer::CreateEOSBuffer());
|
| return;
|
| }
|
|
|
| - read_queue_.push_back(read_cb);
|
| - SatisfyPendingReads();
|
| + SatisfyPendingRead();
|
| }
|
|
|
| void FFmpegDemuxerStream::EnableBitstreamConverter() {
|
| - if (!message_loop_->BelongsToCurrentThread()) {
|
| - message_loop_->PostTask(FROM_HERE, base::Bind(
|
| - &FFmpegDemuxerStream::EnableBitstreamConverter, this));
|
| - return;
|
| - }
|
| + DCHECK(message_loop_->BelongsToCurrentThread());
|
| CHECK(bitstream_converter_.get());
|
| bitstream_converter_enabled_ = true;
|
| }
|
|
|
| const AudioDecoderConfig& FFmpegDemuxerStream::audio_decoder_config() {
|
| + DCHECK(message_loop_->BelongsToCurrentThread());
|
| CHECK_EQ(type_, AUDIO);
|
| return audio_config_;
|
| }
|
|
|
| const VideoDecoderConfig& FFmpegDemuxerStream::video_decoder_config() {
|
| + DCHECK(message_loop_->BelongsToCurrentThread());
|
| CHECK_EQ(type_, VIDEO);
|
| return video_config_;
|
| }
|
|
|
| FFmpegDemuxerStream::~FFmpegDemuxerStream() {
|
| DCHECK(stopped_);
|
| - DCHECK(read_queue_.empty());
|
| + DCHECK(read_cb_.is_null());
|
| DCHECK(buffer_queue_.IsEmpty());
|
| }
|
|
|
| @@ -191,24 +188,16 @@ Ranges<base::TimeDelta> FFmpegDemuxerStream::GetBufferedRanges() const {
|
| return buffered_ranges_;
|
| }
|
|
|
| -void FFmpegDemuxerStream::SatisfyPendingReads() {
|
| +void FFmpegDemuxerStream::SatisfyPendingRead() {
|
| DCHECK(message_loop_->BelongsToCurrentThread());
|
| - while (!read_queue_.empty()) {
|
| - scoped_refptr<DecoderBuffer> buffer;
|
| -
|
| + if (!read_cb_.is_null()) {
|
| if (!buffer_queue_.IsEmpty()) {
|
| - buffer = buffer_queue_.Pop();
|
| + base::ResetAndReturn(&read_cb_).Run(
|
| + DemuxerStream::kOk, buffer_queue_.Pop());
|
| } else if (end_of_stream_) {
|
| - buffer = DecoderBuffer::CreateEOSBuffer();
|
| - } else {
|
| - break;
|
| + base::ResetAndReturn(&read_cb_).Run(
|
| + DemuxerStream::kOk, DecoderBuffer::CreateEOSBuffer());
|
| }
|
| -
|
| - // Send buffer back on a new execution stack to avoid recursing.
|
| - ReadCB read_cb = read_queue_.front();
|
| - read_queue_.pop_front();
|
| - message_loop_->PostTask(FROM_HERE, base::Bind(
|
| - read_cb, DemuxerStream::kOk, buffer));
|
| }
|
|
|
| // Have capacity? Ask for more!
|
| @@ -257,34 +246,78 @@ FFmpegDemuxer::FFmpegDemuxer(
|
| FFmpegDemuxer::~FFmpegDemuxer() {}
|
|
|
| void FFmpegDemuxer::Stop(const base::Closure& callback) {
|
| - // Post a task to notify the streams to stop as well.
|
| - message_loop_->PostTask(FROM_HERE,
|
| - base::Bind(&FFmpegDemuxer::StopTask, this, callback));
|
| + DCHECK(message_loop_->BelongsToCurrentThread());
|
| + url_protocol_.Abort();
|
| + data_source_->Stop(BindToCurrentLoop(base::Bind(
|
| + &FFmpegDemuxer::OnDataSourceStopped, this, BindToCurrentLoop(callback))));
|
| }
|
|
|
| void FFmpegDemuxer::Seek(base::TimeDelta time, const PipelineStatusCB& cb) {
|
| - message_loop_->PostTask(FROM_HERE,
|
| - base::Bind(&FFmpegDemuxer::SeekTask, this, time, cb));
|
| + DCHECK(message_loop_->BelongsToCurrentThread());
|
| + CHECK(!pending_seek_);
|
| +
|
| + // TODO(scherkus): Inspect |pending_read_| and cancel IO via |blocking_url_|,
|
| + // otherwise we can end up waiting for a pre-seek read to complete even though
|
| + // we know we're going to drop it on the floor.
|
| +
|
| + // Always seek to a timestamp less than or equal to the desired timestamp.
|
| + int flags = AVSEEK_FLAG_BACKWARD;
|
| +
|
| + // Passing -1 as our stream index lets FFmpeg pick a default stream. FFmpeg
|
| + // will attempt to use the lowest-index video stream, if present, followed by
|
| + // the lowest-index audio stream.
|
| + pending_seek_ = true;
|
| + base::PostTaskAndReplyWithResult(
|
| + blocking_thread_.message_loop_proxy(), FROM_HERE,
|
| + base::Bind(&av_seek_frame, glue_->format_context(), -1,
|
| + time.InMicroseconds(), flags),
|
| + base::Bind(&FFmpegDemuxer::OnSeekFrameDone, this, cb));
|
| }
|
|
|
| void FFmpegDemuxer::SetPlaybackRate(float playback_rate) {
|
| - DCHECK(data_source_.get());
|
| + DCHECK(message_loop_->BelongsToCurrentThread());
|
| data_source_->SetPlaybackRate(playback_rate);
|
| }
|
|
|
| void FFmpegDemuxer::OnAudioRendererDisabled() {
|
| - message_loop_->PostTask(FROM_HERE, base::Bind(
|
| - &FFmpegDemuxer::DisableAudioStreamTask, this));
|
| + DCHECK(message_loop_->BelongsToCurrentThread());
|
| + audio_disabled_ = true;
|
| + StreamVector::iterator iter;
|
| + for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
|
| + if (*iter && (*iter)->type() == DemuxerStream::AUDIO) {
|
| + (*iter)->Stop();
|
| + }
|
| + }
|
| }
|
|
|
| void FFmpegDemuxer::Initialize(DemuxerHost* host,
|
| const PipelineStatusCB& status_cb) {
|
| - message_loop_->PostTask(FROM_HERE, base::Bind(
|
| - &FFmpegDemuxer::InitializeTask, this, host, status_cb));
|
| + DCHECK(message_loop_->BelongsToCurrentThread());
|
| + host_ = host;
|
| +
|
| + // TODO(scherkus): DataSource should have a host by this point,
|
| + // see http://crbug.com/122071
|
| + data_source_->set_host(host);
|
| +
|
| + glue_.reset(new FFmpegGlue(&url_protocol_));
|
| + AVFormatContext* format_context = glue_->format_context();
|
| +
|
| + // Disable ID3v1 tag reading to avoid costly seeks to end of file for data we
|
| + // don't use. FFmpeg will only read ID3v1 tags if no other metadata is
|
| + // available, so add a metadata entry to ensure some is always present.
|
| + av_dict_set(&format_context->metadata, "skip_id3v1_tags", "", 0);
|
| +
|
| + // Open the AVFormatContext using our glue layer.
|
| + CHECK(blocking_thread_.Start());
|
| + base::PostTaskAndReplyWithResult(
|
| + blocking_thread_.message_loop_proxy(), FROM_HERE,
|
| + base::Bind(&FFmpegGlue::OpenContext, base::Unretained(glue_.get())),
|
| + base::Bind(&FFmpegDemuxer::OnOpenContextDone, this, status_cb));
|
| }
|
|
|
| scoped_refptr<DemuxerStream> FFmpegDemuxer::GetStream(
|
| DemuxerStream::Type type) {
|
| + DCHECK(message_loop_->BelongsToCurrentThread());
|
| return GetFFmpegStream(type);
|
| }
|
|
|
| @@ -300,6 +333,7 @@ scoped_refptr<FFmpegDemuxerStream> FFmpegDemuxer::GetFFmpegStream(
|
| }
|
|
|
| base::TimeDelta FFmpegDemuxer::GetStartTime() const {
|
| + DCHECK(message_loop_->BelongsToCurrentThread());
|
| return start_time_;
|
| }
|
|
|
| @@ -339,31 +373,6 @@ static int CalculateBitrate(
|
| return bytes * 8000000.0 / duration_us;
|
| }
|
|
|
| -void FFmpegDemuxer::InitializeTask(DemuxerHost* host,
|
| - const PipelineStatusCB& status_cb) {
|
| - DCHECK(message_loop_->BelongsToCurrentThread());
|
| - host_ = host;
|
| -
|
| - // TODO(scherkus): DataSource should have a host by this point,
|
| - // see http://crbug.com/122071
|
| - data_source_->set_host(host);
|
| -
|
| - glue_.reset(new FFmpegGlue(&url_protocol_));
|
| - AVFormatContext* format_context = glue_->format_context();
|
| -
|
| - // Disable ID3v1 tag reading to avoid costly seeks to end of file for data we
|
| - // don't use. FFmpeg will only read ID3v1 tags if no other metadata is
|
| - // available, so add a metadata entry to ensure some is always present.
|
| - av_dict_set(&format_context->metadata, "skip_id3v1_tags", "", 0);
|
| -
|
| - // Open the AVFormatContext using our glue layer.
|
| - CHECK(blocking_thread_.Start());
|
| - base::PostTaskAndReplyWithResult(
|
| - blocking_thread_.message_loop_proxy(), FROM_HERE,
|
| - base::Bind(&FFmpegGlue::OpenContext, base::Unretained(glue_.get())),
|
| - base::Bind(&FFmpegDemuxer::OnOpenContextDone, this, status_cb));
|
| -}
|
| -
|
| void FFmpegDemuxer::OnOpenContextDone(const PipelineStatusCB& status_cb,
|
| bool result) {
|
| DCHECK(message_loop_->BelongsToCurrentThread());
|
| @@ -478,28 +487,6 @@ void FFmpegDemuxer::OnFindStreamInfoDone(const PipelineStatusCB& status_cb,
|
| status_cb.Run(PIPELINE_OK);
|
| }
|
|
|
| -void FFmpegDemuxer::SeekTask(base::TimeDelta time, const PipelineStatusCB& cb) {
|
| - DCHECK(message_loop_->BelongsToCurrentThread());
|
| - CHECK(!pending_seek_);
|
| -
|
| - // TODO(scherkus): Inspect |pending_read_| and cancel IO via |blocking_url_|,
|
| - // otherwise we can end up waiting for a pre-seek read to complete even though
|
| - // we know we're going to drop it on the floor.
|
| -
|
| - // Always seek to a timestamp less than or equal to the desired timestamp.
|
| - int flags = AVSEEK_FLAG_BACKWARD;
|
| -
|
| - // Passing -1 as our stream index lets FFmpeg pick a default stream. FFmpeg
|
| - // will attempt to use the lowest-index video stream, if present, followed by
|
| - // the lowest-index audio stream.
|
| - pending_seek_ = true;
|
| - base::PostTaskAndReplyWithResult(
|
| - blocking_thread_.message_loop_proxy(), FROM_HERE,
|
| - base::Bind(&av_seek_frame, glue_->format_context(), -1,
|
| - time.InMicroseconds(), flags),
|
| - base::Bind(&FFmpegDemuxer::OnSeekFrameDone, this, cb));
|
| -}
|
| -
|
| void FFmpegDemuxer::OnSeekFrameDone(const PipelineStatusCB& cb, int result) {
|
| DCHECK(message_loop_->BelongsToCurrentThread());
|
| CHECK(pending_seek_);
|
| @@ -524,17 +511,17 @@ void FFmpegDemuxer::OnSeekFrameDone(const PipelineStatusCB& cb, int result) {
|
| (*iter)->FlushBuffers();
|
| }
|
|
|
| - // Resume demuxing until capacity.
|
| - DemuxTask();
|
| + // Resume reading until capacity.
|
| + ReadFrameIfNeeded();
|
|
|
| // Notify we're finished seeking.
|
| cb.Run(PIPELINE_OK);
|
| }
|
|
|
| -void FFmpegDemuxer::DemuxTask() {
|
| +void FFmpegDemuxer::ReadFrameIfNeeded() {
|
| DCHECK(message_loop_->BelongsToCurrentThread());
|
|
|
| - // Make sure we have work to do before demuxing.
|
| + // Make sure we have work to do before reading.
|
| if (!blocking_thread_.IsRunning() || !StreamsHaveAvailableCapacity() ||
|
| pending_read_ || pending_seek_) {
|
| return;
|
| @@ -600,15 +587,8 @@ void FFmpegDemuxer::OnReadFrameDone(ScopedAVPacket packet, int result) {
|
| demuxer_stream->EnqueuePacket(packet.Pass());
|
| }
|
|
|
| - // Keep demuxing until we've reached capacity.
|
| - DemuxTask();
|
| -}
|
| -
|
| -void FFmpegDemuxer::StopTask(const base::Closure& callback) {
|
| - DCHECK(message_loop_->BelongsToCurrentThread());
|
| - url_protocol_.Abort();
|
| - data_source_->Stop(BindToLoop(message_loop_, base::Bind(
|
| - &FFmpegDemuxer::OnDataSourceStopped, this, callback)));
|
| + // Keep reading until we've reached capacity.
|
| + ReadFrameIfNeeded();
|
| }
|
|
|
| void FFmpegDemuxer::OnDataSourceStopped(const base::Closure& callback) {
|
| @@ -628,17 +608,6 @@ void FFmpegDemuxer::OnDataSourceStopped(const base::Closure& callback) {
|
| callback.Run();
|
| }
|
|
|
| -void FFmpegDemuxer::DisableAudioStreamTask() {
|
| - DCHECK(message_loop_->BelongsToCurrentThread());
|
| - audio_disabled_ = true;
|
| - StreamVector::iterator iter;
|
| - for (iter = streams_.begin(); iter != streams_.end(); ++iter) {
|
| - if (*iter && (*iter)->type() == DemuxerStream::AUDIO) {
|
| - (*iter)->Stop();
|
| - }
|
| - }
|
| -}
|
| -
|
| bool FFmpegDemuxer::StreamsHaveAvailableCapacity() {
|
| DCHECK(message_loop_->BelongsToCurrentThread());
|
| StreamVector::iterator iter;
|
| @@ -664,7 +633,7 @@ void FFmpegDemuxer::StreamHasEnded() {
|
|
|
| void FFmpegDemuxer::NotifyCapacityAvailable() {
|
| DCHECK(message_loop_->BelongsToCurrentThread());
|
| - DemuxTask();
|
| + ReadFrameIfNeeded();
|
| }
|
|
|
| void FFmpegDemuxer::NotifyBufferingChanged() {
|
|
|