diff options
-rw-r--r-- | media/base/decoder_buffer_queue.cc | 78 | ||||
-rw-r--r-- | media/base/decoder_buffer_queue.h | 70 | ||||
-rw-r--r-- | media/base/decoder_buffer_queue_unittest.cc | 137 | ||||
-rw-r--r-- | media/filters/ffmpeg_demuxer.cc | 140 | ||||
-rw-r--r-- | media/filters/ffmpeg_demuxer.h | 50 | ||||
-rw-r--r-- | media/media.gyp | 3 |
6 files changed, 401 insertions, 77 deletions
diff --git a/media/base/decoder_buffer_queue.cc b/media/base/decoder_buffer_queue.cc new file mode 100644 index 0000000..ad91c37 --- /dev/null +++ b/media/base/decoder_buffer_queue.cc @@ -0,0 +1,78 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/base/decoder_buffer_queue.h" + +#include "base/logging.h" +#include "media/base/decoder_buffer.h" + +namespace media { + +DecoderBufferQueue::DecoderBufferQueue() + : earliest_valid_timestamp_(kNoTimestamp()) { +} + +DecoderBufferQueue::~DecoderBufferQueue() {} + +void DecoderBufferQueue::Push(const scoped_refptr<DecoderBuffer>& buffer) { + CHECK(!buffer->IsEndOfStream()); + + queue_.push_back(buffer); + + // TODO(scherkus): FFmpeg returns some packets with no timestamp after + // seeking. Fix and turn this into CHECK(). See http://crbug.com/162192 + if (buffer->GetTimestamp() == kNoTimestamp()) { + DVLOG(1) << "Buffer has no timestamp"; + return; + } + + if (earliest_valid_timestamp_ == kNoTimestamp()) { + earliest_valid_timestamp_ = buffer->GetTimestamp(); + } + + if (buffer->GetTimestamp() < earliest_valid_timestamp_) { + DVLOG(1) + << "Out of order timestamps: " + << buffer->GetTimestamp().InMicroseconds() + << " vs. " + << earliest_valid_timestamp_.InMicroseconds(); + return; + } + + earliest_valid_timestamp_ = buffer->GetTimestamp(); + in_order_queue_.push_back(buffer); +} + +scoped_refptr<DecoderBuffer> DecoderBufferQueue::Pop() { + scoped_refptr<DecoderBuffer> buffer = queue_.front(); + queue_.pop_front(); + + if (!in_order_queue_.empty() && + in_order_queue_.front() == buffer) { + in_order_queue_.pop_front(); + } + + return buffer; +} + +void DecoderBufferQueue::Clear() { + queue_.clear(); + in_order_queue_.clear(); + earliest_valid_timestamp_ = kNoTimestamp(); +} + +bool DecoderBufferQueue::IsEmpty() { + return queue_.empty(); +} + +base::TimeDelta DecoderBufferQueue::Duration() { + if (in_order_queue_.size() < 2) + return base::TimeDelta(); + + base::TimeDelta start = in_order_queue_.front()->GetTimestamp(); + base::TimeDelta end = in_order_queue_.back()->GetTimestamp(); + return end - start; +} + +} // namespace media diff --git a/media/base/decoder_buffer_queue.h b/media/base/decoder_buffer_queue.h new file mode 100644 index 0000000..f75046c --- /dev/null +++ b/media/base/decoder_buffer_queue.h @@ -0,0 +1,70 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MEDIA_BASE_DECODER_BUFFER_QUEUE_H_ +#define MEDIA_BASE_DECODER_BUFFER_QUEUE_H_ + +#include <deque> + +#include "base/memory/ref_counted.h" +#include "base/time.h" +#include "media/base/media_export.h" + +namespace media { + +class DecoderBuffer; + +// Maintains a queue of DecoderBuffers in increasing timestamp order. +// +// Individual buffer durations are ignored when calculating the duration of the +// queue i.e., the queue must have at least 2 in-order buffers to calculate +// duration. +// +// Not thread safe: access must be externally synchronized. +class MEDIA_EXPORT DecoderBufferQueue { + public: + DecoderBufferQueue(); + ~DecoderBufferQueue(); + + // Push |buffer| to the end of the queue. If |buffer| is queued out of order + // it will be excluded from duration calculations. + // + // It is invalid to push an end-of-stream |buffer|. + void Push(const scoped_refptr<DecoderBuffer>& buffer); + + // Pops a DecoderBuffer from the front of the queue. + // + // It is invalid to call Pop() on an empty queue. + scoped_refptr<DecoderBuffer> Pop(); + + // Removes all queued buffers. + void Clear(); + + // Returns true if this queue is empty. + bool IsEmpty(); + + // Returns the duration of encoded data stored in this queue as measured by + // the timestamps of the earliest and latest buffers, ignoring out of order + // buffers. + // + // Returns zero if the queue is empty. + base::TimeDelta Duration(); + + private: + typedef std::deque<scoped_refptr<DecoderBuffer> > Queue; + Queue queue_; + + // A subset of |queue_| that contains buffers that are in strictly + // increasing timestamp order. Used to calculate Duration() while ignoring + // out-of-order buffers. + Queue in_order_queue_; + + base::TimeDelta earliest_valid_timestamp_; + + DISALLOW_COPY_AND_ASSIGN(DecoderBufferQueue); +}; + +} // namespace media + +#endif // MEDIA_BASE_DECODER_BUFFER_QUEUE_H_ diff --git a/media/base/decoder_buffer_queue_unittest.cc b/media/base/decoder_buffer_queue_unittest.cc new file mode 100644 index 0000000..02cd541 --- /dev/null +++ b/media/base/decoder_buffer_queue_unittest.cc @@ -0,0 +1,137 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "media/base/decoder_buffer.h" +#include "media/base/decoder_buffer_queue.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace media { + +static base::TimeDelta ToTimeDelta(int seconds) { + if (seconds < 0) + return kNoTimestamp(); + return base::TimeDelta::FromSeconds(seconds); +} + +// Helper to create buffers with specified timestamp in seconds. +// +// Negative numbers will be converted to kNoTimestamp(); +static scoped_refptr<DecoderBuffer> CreateBuffer(int timestamp) { + scoped_refptr<DecoderBuffer> buffer = new DecoderBuffer(0); + buffer->SetTimestamp(ToTimeDelta(timestamp)); + buffer->SetDuration(ToTimeDelta(0)); + return buffer; +} + +TEST(DecoderBufferQueueTest, IsEmpty) { + DecoderBufferQueue queue; + EXPECT_TRUE(queue.IsEmpty()); + + queue.Push(CreateBuffer(0)); + EXPECT_FALSE(queue.IsEmpty()); +} + +TEST(DecoderBufferQueueTest, Clear) { + DecoderBufferQueue queue; + queue.Push(CreateBuffer(0)); + queue.Push(CreateBuffer(1)); + EXPECT_FALSE(queue.IsEmpty()); + EXPECT_EQ(1, queue.Duration().InSeconds()); + + queue.Clear(); + EXPECT_TRUE(queue.IsEmpty()); + EXPECT_EQ(0, queue.Duration().InSeconds()); +} + +TEST(DecoderBufferQueueTest, Duration) { + DecoderBufferQueue queue; + EXPECT_EQ(0, queue.Duration().InSeconds()); + + queue.Push(CreateBuffer(0)); + EXPECT_EQ(0, queue.Duration().InSeconds()); + + queue.Push(CreateBuffer(1)); + EXPECT_EQ(1, queue.Duration().InSeconds()); + + queue.Push(CreateBuffer(2)); + EXPECT_EQ(2, queue.Duration().InSeconds()); + + queue.Push(CreateBuffer(4)); + EXPECT_EQ(4, queue.Duration().InSeconds()); + + queue.Pop(); + EXPECT_EQ(3, queue.Duration().InSeconds()); + + queue.Pop(); + EXPECT_EQ(2, queue.Duration().InSeconds()); + + queue.Pop(); + EXPECT_EQ(0, queue.Duration().InSeconds()); + + queue.Pop(); + EXPECT_EQ(0, queue.Duration().InSeconds()); +} + +TEST(DecoderBufferQueueTest, Duration_OutOfOrder) { + DecoderBufferQueue queue; + queue.Push(CreateBuffer(10)); + queue.Push(CreateBuffer(12)); + EXPECT_EQ(2, queue.Duration().InSeconds()); + + // Out of order: duration shouldn't change. + queue.Push(CreateBuffer(8)); + EXPECT_EQ(2, queue.Duration().InSeconds()); + + // Removing first buffer should leave the second buffer as the only buffer + // included in the duration calculation. + queue.Pop(); + EXPECT_EQ(0, queue.Duration().InSeconds()); + + // Removing second buffer leaves the out-of-order buffer. It shouldn't be + // included in duration calculations. + queue.Pop(); + EXPECT_EQ(0, queue.Duration().InSeconds()); + + // Push a still-too-early buffer. It shouldn't be included in duration + // calculations. + queue.Push(CreateBuffer(11)); + EXPECT_EQ(0, queue.Duration().InSeconds()); + + // Push a buffer that's after the earliest valid time. It's a singular valid + // buffer so duration is still zero. + queue.Push(CreateBuffer(14)); + EXPECT_EQ(0, queue.Duration().InSeconds()); + + // Push a second valid buffer. We should now have a duration. + queue.Push(CreateBuffer(17)); + EXPECT_EQ(3, queue.Duration().InSeconds()); +} + +TEST(DecoderBufferQueueTest, Duration_NoTimestamp) { + // Buffers with no timestamp don't affect duration. + DecoderBufferQueue queue; + queue.Push(CreateBuffer(0)); + queue.Push(CreateBuffer(4)); + EXPECT_EQ(4, queue.Duration().InSeconds()); + + queue.Push(CreateBuffer(-1)); + EXPECT_EQ(4, queue.Duration().InSeconds()); + + queue.Push(CreateBuffer(6)); + EXPECT_EQ(6, queue.Duration().InSeconds()); + + queue.Pop(); + EXPECT_EQ(2, queue.Duration().InSeconds()); + + queue.Pop(); + EXPECT_EQ(0, queue.Duration().InSeconds()); + + queue.Pop(); + EXPECT_EQ(0, queue.Duration().InSeconds()); + + queue.Pop(); + EXPECT_EQ(0, queue.Duration().InSeconds()); +} + +} // namespace media diff --git a/media/filters/ffmpeg_demuxer.cc b/media/filters/ffmpeg_demuxer.cc index 64f8401..326b881 100644 --- a/media/filters/ffmpeg_demuxer.cc +++ b/media/filters/ffmpeg_demuxer.cc @@ -39,6 +39,7 @@ FFmpegDemuxerStream::FFmpegDemuxerStream( stream_(stream), type_(UNKNOWN), stopped_(false), + end_of_stream_(false), last_packet_timestamp_(kNoTimestamp()), bitstream_converter_enabled_(false) { DCHECK(demuxer_); @@ -67,68 +68,65 @@ FFmpegDemuxerStream::FFmpegDemuxerStream( } } -bool FFmpegDemuxerStream::HasPendingReads() { - DCHECK(message_loop_->BelongsToCurrentThread()); - DCHECK(!stopped_ || read_queue_.empty()) - << "Read queue should have been emptied if demuxing stream is stopped"; - return !read_queue_.empty(); -} - void FFmpegDemuxerStream::EnqueuePacket(ScopedAVPacket packet) { DCHECK(message_loop_->BelongsToCurrentThread()); - if (stopped_) { + if (stopped_ || end_of_stream_) { NOTREACHED() << "Attempted to enqueue packet on a stopped stream"; return; } - scoped_refptr<DecoderBuffer> buffer; - if (!packet.get()) { - buffer = DecoderBuffer::CreateEOSBuffer(); - } else { - // Convert the packet if there is a bitstream filter. - if (packet->data && bitstream_converter_enabled_ && - !bitstream_converter_->ConvertPacket(packet.get())) { - LOG(ERROR) << "Format converstion failed."; - } - - // If a packet is returned by FFmpeg's av_parser_parse2() the packet will - // reference inner memory of FFmpeg. As such we should transfer the packet - // into memory we control. - buffer = DecoderBuffer::CopyFrom(packet->data, packet->size); - buffer->SetTimestamp(ConvertStreamTimestamp( - stream_->time_base, packet->pts)); - buffer->SetDuration(ConvertStreamTimestamp( - stream_->time_base, packet->duration)); - if (buffer->GetTimestamp() != kNoTimestamp() && - last_packet_timestamp_ != kNoTimestamp() && - last_packet_timestamp_ < buffer->GetTimestamp()) { - buffered_ranges_.Add(last_packet_timestamp_, buffer->GetTimestamp()); - demuxer_->NotifyBufferingChanged(); - } - last_packet_timestamp_ = buffer->GetTimestamp(); + // Convert the packet if there is a bitstream filter. + if (packet->data && bitstream_converter_enabled_ && + !bitstream_converter_->ConvertPacket(packet.get())) { + LOG(ERROR) << "Format conversion failed."; } - buffer_queue_.push_back(buffer); + // If a packet is returned by FFmpeg's av_parser_parse2() the packet will + // reference inner memory of FFmpeg. As such we should transfer the packet + // into memory we control. + scoped_refptr<DecoderBuffer> buffer; + buffer = DecoderBuffer::CopyFrom(packet->data, packet->size); + buffer->SetTimestamp(ConvertStreamTimestamp( + stream_->time_base, packet->pts)); + buffer->SetDuration(ConvertStreamTimestamp( + stream_->time_base, packet->duration)); + if (buffer->GetTimestamp() != kNoTimestamp() && + last_packet_timestamp_ != kNoTimestamp() && + last_packet_timestamp_ < buffer->GetTimestamp()) { + buffered_ranges_.Add(last_packet_timestamp_, buffer->GetTimestamp()); + demuxer_->NotifyBufferingChanged(); + } + last_packet_timestamp_ = buffer->GetTimestamp(); + + buffer_queue_.Push(buffer); + SatisfyPendingReads(); +} + +void FFmpegDemuxerStream::SetEndOfStream() { + DCHECK(message_loop_->BelongsToCurrentThread()); + end_of_stream_ = true; SatisfyPendingReads(); } void FFmpegDemuxerStream::FlushBuffers() { DCHECK(message_loop_->BelongsToCurrentThread()); DCHECK(read_queue_.empty()) << "Read requests should be empty"; - buffer_queue_.clear(); + buffer_queue_.Clear(); + end_of_stream_ = false; last_packet_timestamp_ = kNoTimestamp(); } void FFmpegDemuxerStream::Stop() { DCHECK(message_loop_->BelongsToCurrentThread()); - buffer_queue_.clear(); + buffer_queue_.Clear(); for (ReadQueue::iterator it = read_queue_.begin(); it != read_queue_.end(); ++it) { it->Run(DemuxerStream::kOk, DecoderBuffer::CreateEOSBuffer()); } read_queue_.clear(); stopped_ = true; + end_of_stream_ = true; } base::TimeDelta FFmpegDemuxerStream::duration() { @@ -182,7 +180,7 @@ const VideoDecoderConfig& FFmpegDemuxerStream::video_decoder_config() { FFmpegDemuxerStream::~FFmpegDemuxerStream() { DCHECK(stopped_); DCHECK(read_queue_.empty()); - DCHECK(buffer_queue_.empty()); + DCHECK(buffer_queue_.IsEmpty()); } base::TimeDelta FFmpegDemuxerStream::GetElapsedTime() const { @@ -195,23 +193,36 @@ Ranges<base::TimeDelta> FFmpegDemuxerStream::GetBufferedRanges() const { void FFmpegDemuxerStream::SatisfyPendingReads() { DCHECK(message_loop_->BelongsToCurrentThread()); - while (!read_queue_.empty() && !buffer_queue_.empty()) { + while (!read_queue_.empty()) { + scoped_refptr<DecoderBuffer> buffer; + + if (!buffer_queue_.IsEmpty()) { + buffer = buffer_queue_.Pop(); + } else if (end_of_stream_) { + buffer = DecoderBuffer::CreateEOSBuffer(); + } else { + break; + } + + // Send buffer back on a new execution stack to avoid recursing. ReadCB read_cb = read_queue_.front(); read_queue_.pop_front(); - - // Send earliest buffer back on a new execution stack to avoid recursing. - scoped_refptr<DecoderBuffer> buffer = buffer_queue_.front(); - buffer_queue_.pop_front(); message_loop_->PostTask(FROM_HERE, base::Bind( read_cb, DemuxerStream::kOk, buffer)); } - // No buffers but pending reads? Ask for more! - if (!read_queue_.empty() && buffer_queue_.empty()) { - demuxer_->NotifyHasPendingRead(); + // Have capacity? Ask for more! + if (HasAvailableCapacity() && !end_of_stream_) { + demuxer_->NotifyCapacityAvailable(); } } +bool FFmpegDemuxerStream::HasAvailableCapacity() { + // Try to have one second's worth of encoded data per stream. + const base::TimeDelta kCapacity = base::TimeDelta::FromSeconds(1); + return buffer_queue_.IsEmpty() || buffer_queue_.Duration() < kCapacity; +} + // static base::TimeDelta FFmpegDemuxerStream::ConvertStreamTimestamp( const AVRational& time_base, int64 timestamp) { @@ -230,6 +241,8 @@ FFmpegDemuxer::FFmpegDemuxer( : host_(NULL), message_loop_(message_loop), blocking_thread_("FFmpegDemuxer"), + pending_read_(false), + pending_seek_(false), data_source_(data_source), bitrate_(0), start_time_(kNoTimestamp()), @@ -467,6 +480,11 @@ void FFmpegDemuxer::OnFindStreamInfoDone(const PipelineStatusCB& status_cb, 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; @@ -474,6 +492,7 @@ void FFmpegDemuxer::SeekTask(base::TimeDelta time, const PipelineStatusCB& cb) { // 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, @@ -483,6 +502,9 @@ void FFmpegDemuxer::SeekTask(base::TimeDelta time, const PipelineStatusCB& cb) { void FFmpegDemuxer::OnSeekFrameDone(const PipelineStatusCB& cb, int result) { DCHECK(message_loop_->BelongsToCurrentThread()); + CHECK(pending_seek_); + pending_seek_ = false; + if (!blocking_thread_.IsRunning()) { cb.Run(PIPELINE_ERROR_ABORT); return; @@ -502,6 +524,9 @@ void FFmpegDemuxer::OnSeekFrameDone(const PipelineStatusCB& cb, int result) { (*iter)->FlushBuffers(); } + // Resume demuxing until capacity. + DemuxTask(); + // Notify we're finished seeking. cb.Run(PIPELINE_OK); } @@ -510,7 +535,8 @@ void FFmpegDemuxer::DemuxTask() { DCHECK(message_loop_->BelongsToCurrentThread()); // Make sure we have work to do before demuxing. - if (!StreamsHavePendingReads()) { + if (!blocking_thread_.IsRunning() || !StreamsHaveAvailableCapacity() || + pending_read_ || pending_seek_) { return; } @@ -520,6 +546,7 @@ void FFmpegDemuxer::DemuxTask() { ScopedAVPacket packet(new AVPacket()); AVPacket* packet_ptr = packet.get(); + pending_read_ = true; base::PostTaskAndReplyWithResult( blocking_thread_.message_loop_proxy(), FROM_HERE, base::Bind(&av_read_frame, glue_->format_context(), packet_ptr), @@ -528,7 +555,10 @@ void FFmpegDemuxer::DemuxTask() { void FFmpegDemuxer::OnReadFrameDone(ScopedAVPacket packet, int result) { DCHECK(message_loop_->BelongsToCurrentThread()); - if (!blocking_thread_.IsRunning()) { + DCHECK(pending_read_); + pending_read_ = false; + + if (!blocking_thread_.IsRunning() || pending_seek_) { return; } @@ -570,12 +600,8 @@ void FFmpegDemuxer::OnReadFrameDone(ScopedAVPacket packet, int result) { demuxer_stream->EnqueuePacket(packet.Pass()); } - // Create a loop by posting another task. This allows seek and message loop - // quit tasks to get processed. - if (StreamsHavePendingReads()) { - message_loop_->PostTask(FROM_HERE, base::Bind( - &FFmpegDemuxer::DemuxTask, this)); - } + // Keep demuxing until we've reached capacity. + DemuxTask(); } void FFmpegDemuxer::StopTask(const base::Closure& callback) { @@ -613,11 +639,11 @@ void FFmpegDemuxer::DisableAudioStreamTask() { } } -bool FFmpegDemuxer::StreamsHavePendingReads() { +bool FFmpegDemuxer::StreamsHaveAvailableCapacity() { DCHECK(message_loop_->BelongsToCurrentThread()); StreamVector::iterator iter; for (iter = streams_.begin(); iter != streams_.end(); ++iter) { - if (*iter && (*iter)->HasPendingReads()) { + if (*iter && (*iter)->HasAvailableCapacity()) { return true; } } @@ -632,11 +658,11 @@ void FFmpegDemuxer::StreamHasEnded() { (audio_disabled_ && (*iter)->type() == DemuxerStream::AUDIO)) { continue; } - (*iter)->EnqueuePacket(ScopedAVPacket()); + (*iter)->SetEndOfStream(); } } -void FFmpegDemuxer::NotifyHasPendingRead() { +void FFmpegDemuxer::NotifyCapacityAvailable() { DCHECK(message_loop_->BelongsToCurrentThread()); DemuxTask(); } diff --git a/media/filters/ffmpeg_demuxer.h b/media/filters/ffmpeg_demuxer.h index 5f8566f..889cc3b 100644 --- a/media/filters/ffmpeg_demuxer.h +++ b/media/filters/ffmpeg_demuxer.h @@ -30,6 +30,7 @@ #include "base/threading/thread.h" #include "media/base/audio_decoder_config.h" #include "media/base/decoder_buffer.h" +#include "media/base/decoder_buffer_queue.h" #include "media/base/demuxer.h" #include "media/base/pipeline.h" #include "media/base/video_decoder_config.h" @@ -55,14 +56,15 @@ class FFmpegDemuxerStream : public DemuxerStream { // inside |stream|. Both parameters must outlive |this|. FFmpegDemuxerStream(FFmpegDemuxer* demuxer, AVStream* stream); - // Returns true is this stream has pending reads, false otherwise. - bool HasPendingReads(); - - // Enqueues the given AVPacket. If |packet| is NULL an end of stream packet - // is enqueued. + // Enqueues the given AVPacket. It is invalid to queue a |packet| after + // SetEndOfStream() has been called. void EnqueuePacket(ScopedAVPacket packet); - // Signals to empty the buffer queue and mark next packet as discontinuous. + // Enters the end of stream state. After delivering remaining queued buffers + // only end of stream buffers will be delivered. + void SetEndOfStream(); + + // Drops queued buffers and clears end of stream state. void FlushBuffers(); // Empties the queues and ignores any additional calls to Read(). @@ -85,6 +87,9 @@ class FFmpegDemuxerStream : public DemuxerStream { // Used to determine stream duration when it's not known ahead of time. base::TimeDelta GetElapsedTime() const; + // Returns true if this stream has capacity for additional data. + bool HasAvailableCapacity(); + protected: virtual ~FFmpegDemuxerStream(); @@ -107,11 +112,11 @@ class FFmpegDemuxerStream : public DemuxerStream { Type type_; base::TimeDelta duration_; bool stopped_; + bool end_of_stream_; base::TimeDelta last_packet_timestamp_; Ranges<base::TimeDelta> buffered_ranges_; - typedef std::deque<scoped_refptr<DecoderBuffer> > BufferQueue; - BufferQueue buffer_queue_; + DecoderBufferQueue buffer_queue_; typedef std::deque<ReadCB> ReadQueue; ReadQueue read_queue_; @@ -138,9 +143,9 @@ class MEDIA_EXPORT FFmpegDemuxer : public Demuxer { DemuxerStream::Type type) OVERRIDE; virtual base::TimeDelta GetStartTime() const OVERRIDE; - // Allow FFmpegDemuxerStream to notify us when it requires more data or has - // updated information about what buffered data is available. - void NotifyHasPendingRead(); + // Allow FFmpegDemuxerStream to notify us when there is updated information + // about capacity and what buffered data is available. + void NotifyCapacityAvailable(); void NotifyBufferingChanged(); private: @@ -169,16 +174,11 @@ class MEDIA_EXPORT FFmpegDemuxer : public Demuxer { // Carries out disabling the audio stream on the demuxer thread. void DisableAudioStreamTask(); - // Returns true if any of the streams have pending reads. Since we lazily - // post a DemuxTask() for every read, we use this method to quickly terminate - // the tasks if there is no work to do. - // - // Must be called on the demuxer thread. - bool StreamsHavePendingReads(); + // Returns true iff any stream has additional capacity. Note that streams can + // go over capacity depending on how the file is muxed. + bool StreamsHaveAvailableCapacity(); - // Signal all FFmpegDemuxerStream that the stream has ended. - // - // Must be called on the demuxer thread. + // Signal all FFmpegDemuxerStreams that the stream has ended. void StreamHasEnded(); // Called by |url_protocol_| whenever |data_source_| returns a read error. @@ -196,6 +196,16 @@ class MEDIA_EXPORT FFmpegDemuxer : public Demuxer { // Thread on which all blocking FFmpeg operations are executed. base::Thread blocking_thread_; + // Tracks if there's an outstanding av_read_frame() operation. + // + // TODO(scherkus): Allow more than one read in flight for higher read + // throughput using demuxer_bench to verify improvements. + bool pending_read_; + + // Tracks if there's an outstanding av_seek_frame() operation. Used to discard + // results of pre-seek av_read_frame() operations. + bool pending_seek_; + // |streams_| mirrors the AVStream array in |format_context_|. It contains // FFmpegDemuxerStreams encapsluating AVStream objects at the same index. // diff --git a/media/media.gyp b/media/media.gyp index 552dfee..0bb6e00 100644 --- a/media/media.gyp +++ b/media/media.gyp @@ -202,6 +202,8 @@ 'base/data_source.h', 'base/decoder_buffer.cc', 'base/decoder_buffer.h', + 'base/decoder_buffer_queue.cc', + 'base/decoder_buffer_queue.h', 'base/decryptor.cc', 'base/decryptor.h', 'base/decryptor_client.h', @@ -684,6 +686,7 @@ 'base/clock_unittest.cc', 'base/data_buffer_unittest.cc', 'base/decoder_buffer_unittest.cc', + 'base/decoder_buffer_queue_unittest.cc', 'base/djb2_unittest.cc', 'base/filter_collection_unittest.cc', 'base/gmock_callback_support_unittest.cc', |