summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--media/base/demuxer_stream.h6
-rw-r--r--media/base/filters.h9
-rw-r--r--media/filters/audio_renderer_base.cc72
-rw-r--r--media/filters/audio_renderer_base.h6
-rw-r--r--media/filters/audio_renderer_base_unittest.cc64
-rw-r--r--media/filters/chunk_demuxer.cc219
-rw-r--r--media/filters/ffmpeg_audio_decoder.cc6
-rw-r--r--media/filters/ffmpeg_audio_decoder_unittest.cc16
-rw-r--r--media/filters/ffmpeg_video_decoder.cc5
-rw-r--r--media/filters/ffmpeg_video_decoder_unittest.cc12
-rw-r--r--media/filters/gpu_video_decoder.cc10
-rw-r--r--media/filters/pipeline_integration_test.cc140
-rw-r--r--media/filters/video_renderer_base.cc14
-rw-r--r--media/filters/video_renderer_base_unittest.cc99
-rw-r--r--media/webm/webm_cluster_parser.cc8
15 files changed, 557 insertions, 129 deletions
diff --git a/media/base/demuxer_stream.h b/media/base/demuxer_stream.h
index fb0739b..d793d49 100644
--- a/media/base/demuxer_stream.h
+++ b/media/base/demuxer_stream.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// 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.
@@ -27,7 +27,9 @@ class MEDIA_EXPORT DemuxerStream
// Request a buffer to returned via the provided callback.
//
- // Buffers will be non-NULL yet may be end of stream buffers.
+ // Non-NULL buffer pointers will contain media data or signal the end of the
+ // stream. A NULL pointer indicates an aborted Read(). This can happen if the
+ // DemuxerStream gets flushed and doesn't have any more data to return.
typedef base::Callback<void(const scoped_refptr<Buffer>&)> ReadCallback;
virtual void Read(const ReadCallback& read_callback) = 0;
diff --git a/media/base/filters.h b/media/base/filters.h
index 8f186d6..cf8bcea 100644
--- a/media/base/filters.h
+++ b/media/base/filters.h
@@ -135,7 +135,9 @@ class MEDIA_EXPORT VideoDecoder : public Filter {
// Implementations guarantee that the callback will not be called from within
// this method.
//
- // Frames will be non-NULL yet may be end of stream frames.
+ // Non-NULL frames contain decoded video data or may indicate the end of
+ // the stream. NULL video frames indicate an aborted read. This can happen if
+ // the DemuxerStream gets flushed and doesn't have any more data to return.
typedef base::Callback<void(scoped_refptr<VideoFrame>)> ReadCB;
virtual void Read(const ReadCB& callback) = 0;
@@ -183,7 +185,10 @@ class MEDIA_EXPORT AudioDecoder : public Filter {
// Implementations guarantee that the callback will not be called from within
// this method.
//
- // Sample buffers will be non-NULL yet may be end of stream buffers.
+ // Non-NULL sample buffer pointers will contain decoded audio data or may
+ // indicate the end of the stream. A NULL buffer pointer indicates an aborted
+ // Read(). This can happen if the DemuxerStream gets flushed and doesn't have
+ // any more data to return.
typedef base::Callback<void(scoped_refptr<Buffer>)> ReadCB;
virtual void Read(const ReadCB& callback) = 0;
diff --git a/media/filters/audio_renderer_base.cc b/media/filters/audio_renderer_base.cc
index b80536a..23c17a9 100644
--- a/media/filters/audio_renderer_base.cc
+++ b/media/filters/audio_renderer_base.cc
@@ -67,6 +67,7 @@ void AudioRendererBase::Seek(base::TimeDelta time, const FilterStatusCB& cb) {
base::AutoLock auto_lock(lock_);
DCHECK_EQ(kPaused, state_);
DCHECK(!pending_read_) << "Pending read must complete before seeking";
+ DCHECK(pause_callback_.is_null());
DCHECK(seek_cb_.is_null());
state_ = kSeeking;
seek_cb_ = cb;
@@ -144,46 +145,46 @@ void AudioRendererBase::DecodedAudioReady(scoped_refptr<Buffer> buffer) {
CHECK(pending_read_);
pending_read_ = false;
- // TODO(scherkus): this happens due to a race, primarily because Stop() is a
- // synchronous call when it should be asynchronous and accept a callback.
- // Refer to http://crbug.com/16059
- if (state_ == kStopped) {
- return;
- }
-
- // Don't enqueue an end-of-stream buffer because it has no data, otherwise
- // discard decoded audio data until we reach our desired seek timestamp.
- if (buffer->IsEndOfStream()) {
+ if (buffer && buffer->IsEndOfStream()) {
recieved_end_of_stream_ = true;
- // Transition to kPlaying if we are currently handling an underflow since no
- // more data will be arriving.
+ // Transition to kPlaying if we are currently handling an underflow since
+ // no more data will be arriving.
if (state_ == kUnderflow || state_ == kRebuffering)
state_ = kPlaying;
- } else if (state_ == kSeeking && !buffer->IsEndOfStream() &&
- (buffer->GetTimestamp() + buffer->GetDuration()) <
- seek_timestamp_) {
- ScheduleRead_Locked();
- } else {
- // Note: Calling this may schedule more reads.
- algorithm_->EnqueueBuffer(buffer);
}
- // Check for our preroll complete condition.
- if (state_ == kSeeking) {
- DCHECK(!seek_cb_.is_null());
- if (algorithm_->IsQueueFull() || recieved_end_of_stream_) {
- // Transition into paused whether we have data in |algorithm_| or not.
- // FillBuffer() will play silence if there's nothing to fill.
+ switch (state_) {
+ case kUninitialized:
+ NOTREACHED();
+ return;
+ case kPaused:
+ if (buffer && !buffer->IsEndOfStream())
+ algorithm_->EnqueueBuffer(buffer);
+ DCHECK(!pending_read_);
+ ResetAndRunCB(&pause_callback_);
+ return;
+ case kSeeking:
+ if (IsBeforeSeekTime(buffer)) {
+ ScheduleRead_Locked();
+ return;
+ }
+ if (buffer && !buffer->IsEndOfStream()) {
+ algorithm_->EnqueueBuffer(buffer);
+ if (!algorithm_->IsQueueFull())
+ return;
+ }
state_ = kPaused;
ResetAndRunCB(&seek_cb_, PIPELINE_OK);
- }
- } else if (state_ == kPaused && !pending_read_) {
- // No more pending read! We're now officially "paused".
- if (!pause_callback_.is_null()) {
- pause_callback_.Run();
- pause_callback_.Reset();
- }
+ return;
+ case kPlaying:
+ case kUnderflow:
+ case kRebuffering:
+ if (buffer && !buffer->IsEndOfStream())
+ algorithm_->EnqueueBuffer(buffer);
+ return;
+ case kStopped:
+ return;
}
}
@@ -278,7 +279,7 @@ void AudioRendererBase::SignalEndOfStream() {
void AudioRendererBase::ScheduleRead_Locked() {
lock_.AssertAcquired();
- if (pending_read_)
+ if (pending_read_ || state_ == kPaused)
return;
pending_read_ = true;
decoder_->Read(read_cb_);
@@ -294,4 +295,9 @@ float AudioRendererBase::GetPlaybackRate() {
return algorithm_->playback_rate();
}
+bool AudioRendererBase::IsBeforeSeekTime(const scoped_refptr<Buffer>& buffer) {
+ return (state_ == kSeeking) && buffer && !buffer->IsEndOfStream() &&
+ (buffer->GetTimestamp() + buffer->GetDuration()) < seek_timestamp_;
+}
+
} // namespace media
diff --git a/media/filters/audio_renderer_base.h b/media/filters/audio_renderer_base.h
index f6218c7..4326901 100644
--- a/media/filters/audio_renderer_base.h
+++ b/media/filters/audio_renderer_base.h
@@ -113,6 +113,11 @@ class MEDIA_EXPORT AudioRendererBase : public AudioRenderer {
// Safe to call from any thread.
void ScheduleRead_Locked();
+ // Returns true if the data in the buffer is all before
+ // |seek_timestamp_|. This can only return true while
+ // in the kSeeking state.
+ bool IsBeforeSeekTime(const scoped_refptr<Buffer>& buffer);
+
// Audio decoder.
scoped_refptr<AudioDecoder> decoder_;
@@ -128,7 +133,6 @@ class MEDIA_EXPORT AudioRendererBase : public AudioRenderer {
kSeeking,
kPlaying,
kStopped,
- kError,
kUnderflow,
kRebuffering,
};
diff --git a/media/filters/audio_renderer_base_unittest.cc b/media/filters/audio_renderer_base_unittest.cc
index e07d524..976743c 100644
--- a/media/filters/audio_renderer_base_unittest.cc
+++ b/media/filters/audio_renderer_base_unittest.cc
@@ -16,6 +16,8 @@ using ::testing::_;
using ::testing::AnyNumber;
using ::testing::Invoke;
using ::testing::Return;
+using ::testing::ReturnPointee;
+using ::testing::SaveArg;
using ::testing::StrictMock;
namespace media {
@@ -111,6 +113,18 @@ class AudioRendererBaseTest : public ::testing::Test {
renderer_->SetPlaybackRate(1.0f);
}
+ void Seek(base::TimeDelta seek_time) {
+ next_timestamp_ = seek_time;
+
+ // Seek to trigger prerolling.
+ EXPECT_CALL(*decoder_, Read(_));
+ renderer_->Seek(seek_time, NewSeekCB());
+
+ // Fill entire buffer to complete prerolling.
+ EXPECT_CALL(*this, OnSeekComplete(PIPELINE_OK));
+ DeliverRemainingAudio();
+ }
+
// Delivers |size| bytes with value kPlayingAudio to |renderer_|.
//
// There must be a pending read callback.
@@ -120,11 +134,22 @@ class AudioRendererBaseTest : public ::testing::Test {
buffer->SetDataSize(size);
memset(buffer->GetWritableData(), kPlayingAudio, buffer->GetDataSize());
+ buffer->SetTimestamp(next_timestamp_);
+ int64 bps = decoder_->bits_per_channel() * decoder_->samples_per_second();
+ buffer->SetDuration(base::TimeDelta::FromMilliseconds(8000 * size / bps));
+ next_timestamp_ += buffer->GetDuration();
+
AudioDecoder::ReadCB read_cb;
std::swap(read_cb, read_cb_);
read_cb.Run(buffer);
}
+ void AbortPendingRead() {
+ AudioDecoder::ReadCB read_cb;
+ std::swap(read_cb, read_cb_);
+ read_cb.Run(NULL);
+ }
+
// Delivers an end of stream buffer to |renderer_|.
//
// There must be a pending read callback.
@@ -182,6 +207,7 @@ class AudioRendererBaseTest : public ::testing::Test {
scoped_refptr<MockAudioDecoder> decoder_;
StrictMock<MockFilterHost> host_;
AudioDecoder::ReadCB read_cb_;
+ base::TimeDelta next_timestamp_;
private:
void SaveReadCallback(const AudioDecoder::ReadCB& callback) {
@@ -326,6 +352,9 @@ TEST_F(AudioRendererBaseTest, Underflow_EndOfStream) {
EXPECT_CALL(*renderer_, OnRenderEndOfStream())
.WillOnce(Invoke(renderer_.get(), &AudioRendererBase::SignalEndOfStream));
EXPECT_CALL(host_, NotifyEnded());
+
+ EXPECT_CALL(host_, GetTime()).WillOnce(Return(base::TimeDelta()));
+ EXPECT_CALL(host_, SetTime(_));
EXPECT_FALSE(ConsumeBufferedData(kDataSize, &muted));
EXPECT_FALSE(muted);
}
@@ -359,4 +388,39 @@ TEST_F(AudioRendererBaseTest, Underflow_ResumeFromCallback) {
EXPECT_FALSE(muted);
}
+TEST_F(AudioRendererBaseTest, AbortPendingRead_Preroll) {
+ Initialize();
+
+ // Seek to trigger prerolling.
+ EXPECT_CALL(*decoder_, Read(_));
+ renderer_->Seek(base::TimeDelta(), NewSeekCB());
+
+ // Simulate the decoder aborting the pending read.
+ EXPECT_CALL(*this, OnSeekComplete(PIPELINE_OK));
+ AbortPendingRead();
+
+ // Seek to trigger another preroll and verify it completes
+ // normally.
+ Seek(base::TimeDelta::FromSeconds(1));
+
+ ASSERT_TRUE(read_cb_.is_null());
+}
+
+TEST_F(AudioRendererBaseTest, AbortPendingRead_Pause) {
+ Initialize();
+
+ Preroll();
+ Play();
+
+ // Partially drain internal buffer so we get a pending read.
+ EXPECT_CALL(*decoder_, Read(_));
+ EXPECT_TRUE(ConsumeBufferedData(bytes_buffered() / 2, NULL));
+
+ renderer_->Pause(NewExpectedClosure());
+
+ AbortPendingRead();
+
+ Seek(base::TimeDelta::FromSeconds(1));
+}
+
} // namespace media
diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc
index 0c27733..95cd9e7 100644
--- a/media/filters/chunk_demuxer.cc
+++ b/media/filters/chunk_demuxer.cc
@@ -24,12 +24,14 @@ class ChunkDemuxerStream : public DemuxerStream {
public:
typedef std::deque<scoped_refptr<Buffer> > BufferQueue;
typedef std::deque<ReadCallback> ReadCBQueue;
+ typedef std::deque<base::Closure> ClosureQueue;
explicit ChunkDemuxerStream(const AudioDecoderConfig& audio_config);
explicit ChunkDemuxerStream(const VideoDecoderConfig& video_config);
virtual ~ChunkDemuxerStream();
void Flush();
+ void Seek(base::TimeDelta time);
// Checks if it is ok to add the |buffers| to the stream.
bool CanAddBuffers(const BufferQueue& buffers) const;
@@ -40,22 +42,41 @@ class ChunkDemuxerStream : public DemuxerStream {
bool GetLastBufferTimestamp(base::TimeDelta* timestamp) const;
// DemuxerStream methods.
- virtual void Read(const ReadCallback& read_callback);
- virtual Type type();
- virtual void EnableBitstreamConverter();
- virtual const AudioDecoderConfig& audio_decoder_config();
- virtual const VideoDecoderConfig& video_decoder_config();
+ virtual void Read(const ReadCallback& read_callback) OVERRIDE;
+ virtual Type type() OVERRIDE;
+ virtual void EnableBitstreamConverter() OVERRIDE;
+ virtual const AudioDecoderConfig& audio_decoder_config() OVERRIDE;
+ virtual const VideoDecoderConfig& video_decoder_config() OVERRIDE;
private:
+ enum State {
+ RETURNING_DATA_FOR_READS,
+ WAITING_FOR_SEEK,
+ RECEIVED_EOS_WHILE_WAITING_FOR_SEEK, // EOS = End of stream.
+ RECEIVED_EOS,
+ RETURNING_EOS_FOR_READS,
+ SHUTDOWN,
+ };
+
+ // Assigns |state_| to |state|
+ void ChangeState_Locked(State state);
+
+ // Adds the callback to |read_cbs_| so it can be called later when we
+ // have data.
+ void DeferRead_Locked(const ReadCallback& read_cb);
+
+ // Creates closures that bind ReadCallbacks in |read_cbs_| to data in
+ // |buffers_| and pops the callbacks & buffers from the respecive queues.
+ void CreateReadDoneClosures_Locked(ClosureQueue* closures);
+
Type type_;
AudioDecoderConfig audio_config_;
VideoDecoderConfig video_config_;
mutable base::Lock lock_;
+ State state_;
ReadCBQueue read_cbs_;
BufferQueue buffers_;
- bool shutdown_called_;
- bool received_end_of_stream_;
// Keeps track of the timestamp of the last buffer we have
// added to |buffers_|. This is used to enforce buffers with strictly
@@ -67,8 +88,7 @@ class ChunkDemuxerStream : public DemuxerStream {
ChunkDemuxerStream::ChunkDemuxerStream(const AudioDecoderConfig& audio_config)
: type_(AUDIO),
- shutdown_called_(false),
- received_end_of_stream_(false),
+ state_(RETURNING_DATA_FOR_READS),
last_buffer_timestamp_(kNoTimestamp()) {
audio_config_.CopyFrom(audio_config);
}
@@ -76,8 +96,7 @@ ChunkDemuxerStream::ChunkDemuxerStream(const AudioDecoderConfig& audio_config)
ChunkDemuxerStream::ChunkDemuxerStream(const VideoDecoderConfig& video_config)
: type_(VIDEO),
- shutdown_called_(false),
- received_end_of_stream_(false),
+ state_(RETURNING_DATA_FOR_READS),
last_buffer_timestamp_(kNoTimestamp()) {
video_config_.CopyFrom(video_config);
}
@@ -86,10 +105,34 @@ ChunkDemuxerStream::~ChunkDemuxerStream() {}
void ChunkDemuxerStream::Flush() {
DVLOG(1) << "Flush()";
+ ReadCBQueue read_cbs;
+ {
+ base::AutoLock auto_lock(lock_);
+ buffers_.clear();
+ ChangeState_Locked(WAITING_FOR_SEEK);
+ last_buffer_timestamp_ = kNoTimestamp();
+
+ std::swap(read_cbs_, read_cbs);
+ }
+
+ for (ReadCBQueue::iterator it = read_cbs.begin(); it != read_cbs.end(); ++it)
+ it->Run(scoped_refptr<Buffer>());
+}
+
+void ChunkDemuxerStream::Seek(base::TimeDelta time) {
base::AutoLock auto_lock(lock_);
- buffers_.clear();
- received_end_of_stream_ = false;
- last_buffer_timestamp_ = kNoTimestamp();
+
+ DCHECK(read_cbs_.empty());
+
+ if (state_ == WAITING_FOR_SEEK) {
+ ChangeState_Locked(RETURNING_DATA_FOR_READS);
+ return;
+ }
+
+ if (state_ == RECEIVED_EOS_WHILE_WAITING_FOR_SEEK) {
+ ChangeState_Locked(RECEIVED_EOS);
+ return;
+ }
}
bool ChunkDemuxerStream::CanAddBuffers(const BufferQueue& buffers) const {
@@ -109,7 +152,7 @@ void ChunkDemuxerStream::AddBuffers(const BufferQueue& buffers) {
if (buffers.empty())
return;
- std::deque<base::Closure> callbacks;
+ ClosureQueue closures;
{
base::AutoLock auto_lock(lock_);
@@ -117,17 +160,15 @@ void ChunkDemuxerStream::AddBuffers(const BufferQueue& buffers) {
itr != buffers.end(); itr++) {
// Make sure we aren't trying to add a buffer after we have received and
// "end of stream" buffer.
- DCHECK(!received_end_of_stream_);
+ DCHECK_NE(state_, RECEIVED_EOS_WHILE_WAITING_FOR_SEEK);
+ DCHECK_NE(state_, RECEIVED_EOS);
+ DCHECK_NE(state_, RETURNING_EOS_FOR_READS);
if ((*itr)->IsEndOfStream()) {
- received_end_of_stream_ = true;
-
- // Push enough EOS buffers to satisfy outstanding Read() requests.
- if (read_cbs_.size() > buffers_.size()) {
- size_t pending_read_without_data = read_cbs_.size() - buffers_.size();
- for (size_t i = 0; i <= pending_read_without_data; ++i) {
- buffers_.push_back(*itr);
- }
+ if (state_ == WAITING_FOR_SEEK) {
+ ChangeState_Locked(RECEIVED_EOS_WHILE_WAITING_FOR_SEEK);
+ } else {
+ ChangeState_Locked(RECEIVED_EOS);
}
} else {
base::TimeDelta current_ts = (*itr)->GetTimestamp();
@@ -141,38 +182,27 @@ void ChunkDemuxerStream::AddBuffers(const BufferQueue& buffers) {
}
}
- while (!buffers_.empty() && !read_cbs_.empty()) {
- callbacks.push_back(base::Bind(read_cbs_.front(), buffers_.front()));
- buffers_.pop_front();
- read_cbs_.pop_front();
- }
+ CreateReadDoneClosures_Locked(&closures);
}
- while (!callbacks.empty()) {
- callbacks.front().Run();
- callbacks.pop_front();
- }
+ for (ClosureQueue::iterator it = closures.begin(); it != closures.end(); ++it)
+ it->Run();
}
void ChunkDemuxerStream::Shutdown() {
- std::deque<ReadCallback> callbacks;
+ ReadCBQueue read_cbs;
{
base::AutoLock auto_lock(lock_);
- shutdown_called_ = true;
+ ChangeState_Locked(SHUTDOWN);
- // Collect all the pending Read() callbacks.
- while (!read_cbs_.empty()) {
- callbacks.push_back(read_cbs_.front());
- read_cbs_.pop_front();
- }
+ std::swap(read_cbs_, read_cbs);
+ buffers_.clear();
}
// Pass end of stream buffers to all callbacks to signal that no more data
// will be sent.
- while (!callbacks.empty()) {
- callbacks.front().Run(CreateEOSBuffer());
- callbacks.pop_front();
- }
+ for (ReadCBQueue::iterator it = read_cbs.begin(); it != read_cbs.end(); ++it)
+ it->Run(CreateEOSBuffer());
}
bool ChunkDemuxerStream::GetLastBufferTimestamp(
@@ -206,33 +236,46 @@ void ChunkDemuxerStream::Read(const ReadCallback& read_callback) {
{
base::AutoLock auto_lock(lock_);
- if (shutdown_called_ || (received_end_of_stream_ && buffers_.empty())) {
- buffer = CreateEOSBuffer();
- } else {
- if (buffers_.empty()) {
- // Wrap & store |read_callback| so that it will
- // get called on the current MessageLoop.
- read_cbs_.push_back(base::Bind(&RunOnMessageLoop,
- read_callback,
- MessageLoop::current()));
- return;
- }
+ switch(state_) {
+ case RETURNING_DATA_FOR_READS:
+ // If we don't have any buffers ready or already have
+ // pending reads, then defer this read.
+ if (buffers_.empty() || !read_cbs_.empty()) {
+ DeferRead_Locked(read_callback);
+ return;
+ }
- if (!read_cbs_.empty()) {
- // Wrap & store |read_callback| so that it will
- // get called on the current MessageLoop.
- read_cbs_.push_back(base::Bind(&RunOnMessageLoop,
- read_callback,
- MessageLoop::current()));
- return;
- }
+ buffer = buffers_.front();
+ buffers_.pop_front();
+ break;
+
+ case WAITING_FOR_SEEK:
+ case RECEIVED_EOS_WHILE_WAITING_FOR_SEEK:
+ // Null buffers should be returned in this state since we are waiting
+ // for a seek. Any buffers in |buffers_| should NOT be returned because
+ // they are associated with the seek.
+ DCHECK(read_cbs_.empty());
+ break;
+ case RECEIVED_EOS:
+ DCHECK(read_cbs_.empty());
+
+ if (buffers_.empty()) {
+ ChangeState_Locked(RETURNING_EOS_FOR_READS);
+ buffer = CreateEOSBuffer();
+ } else {
+ buffer = buffers_.front();
+ buffers_.pop_front();
+ }
+ break;
- buffer = buffers_.front();
- buffers_.pop_front();
+ case RETURNING_EOS_FOR_READS:
+ case SHUTDOWN:
+ DCHECK(buffers_.empty());
+ DCHECK(read_cbs_.empty());
+ buffer = CreateEOSBuffer();
}
}
- DCHECK(buffer.get());
read_callback.Run(buffer);
}
@@ -250,6 +293,44 @@ const VideoDecoderConfig& ChunkDemuxerStream::video_decoder_config() {
return video_config_;
}
+void ChunkDemuxerStream::ChangeState_Locked(State state) {
+ lock_.AssertAcquired();
+ state_ = state;
+}
+
+void ChunkDemuxerStream::DeferRead_Locked(const ReadCallback& read_cb) {
+ lock_.AssertAcquired();
+ // Wrap & store |read_callback| so that it will
+ // get called on the current MessageLoop.
+ read_cbs_.push_back(base::Bind(&RunOnMessageLoop, read_cb,
+ MessageLoop::current()));
+}
+
+void ChunkDemuxerStream::CreateReadDoneClosures_Locked(ClosureQueue* closures) {
+ lock_.AssertAcquired();
+
+ if (state_ != RETURNING_DATA_FOR_READS && state_ != RECEIVED_EOS)
+ return;
+
+ while (!buffers_.empty() && !read_cbs_.empty()) {
+ closures->push_back(base::Bind(read_cbs_.front(), buffers_.front()));
+ buffers_.pop_front();
+ read_cbs_.pop_front();
+ }
+
+ if (state_ != RECEIVED_EOS || !buffers_.empty() || read_cbs_.empty())
+ return;
+
+ // Push enough EOS buffers to satisfy outstanding Read() requests.
+ scoped_refptr<Buffer> end_of_stream_buffer = CreateEOSBuffer();
+ while (!read_cbs_.empty()) {
+ closures->push_back(base::Bind(read_cbs_.front(), end_of_stream_buffer));
+ read_cbs_.pop_front();
+ }
+
+ ChangeState_Locked(RETURNING_EOS_FOR_READS);
+}
+
ChunkDemuxer::ChunkDemuxer(ChunkDemuxerClient* client)
: state_(WAITING_FOR_INIT),
client_(client),
@@ -306,6 +387,12 @@ void ChunkDemuxer::Seek(base::TimeDelta time, const PipelineStatusCB& cb) {
base::AutoLock auto_lock(lock_);
if (state_ == INITIALIZED || state_ == ENDED) {
+ if (audio_)
+ audio_->Seek(time);
+
+ if (video_)
+ video_->Seek(time);
+
if (seek_waits_for_data_) {
DVLOG(1) << "Seek() : waiting for more data to arrive.";
seek_cb_ = cb;
diff --git a/media/filters/ffmpeg_audio_decoder.cc b/media/filters/ffmpeg_audio_decoder.cc
index 71b32e5..a3ad564 100644
--- a/media/filters/ffmpeg_audio_decoder.cc
+++ b/media/filters/ffmpeg_audio_decoder.cc
@@ -169,6 +169,12 @@ void FFmpegAudioDecoder::DoDecodeBuffer(const scoped_refptr<Buffer>& input) {
DCHECK_EQ(MessageLoop::current(), message_loop_);
DCHECK(!read_cb_.is_null());
+ if (!input) {
+ // DemuxeStream::Read() was aborted so we abort the decoder's pending read.
+ DeliverSamples(NULL);
+ return;
+ }
+
// FFmpeg tends to seek Ogg audio streams in the middle of nowhere, giving us
// a whole bunch of AV_NOPTS_VALUE packets. Discard them until we find
// something valid. Refer to http://crbug.com/49709
diff --git a/media/filters/ffmpeg_audio_decoder_unittest.cc b/media/filters/ffmpeg_audio_decoder_unittest.cc
index 12e2ce6..0efe91e 100644
--- a/media/filters/ffmpeg_audio_decoder_unittest.cc
+++ b/media/filters/ffmpeg_audio_decoder_unittest.cc
@@ -180,4 +180,20 @@ TEST_F(FFmpegAudioDecoderTest, ProduceAudioSamples) {
Stop();
}
+TEST_F(FFmpegAudioDecoderTest, ReadAbort) {
+ Initialize();
+
+ encoded_audio_.clear();
+ encoded_audio_.push_back(NULL);
+
+ EXPECT_CALL(*demuxer_, Read(_))
+ .WillOnce(InvokeReadPacket(this));
+ Read();
+
+ EXPECT_EQ(decoded_audio_.size(), 1u);
+ EXPECT_TRUE(decoded_audio_[0].get() == NULL);
+
+ Stop();
+}
+
} // namespace media
diff --git a/media/filters/ffmpeg_video_decoder.cc b/media/filters/ffmpeg_video_decoder.cc
index 048cbe5..7ca0d1d 100644
--- a/media/filters/ffmpeg_video_decoder.cc
+++ b/media/filters/ffmpeg_video_decoder.cc
@@ -221,6 +221,11 @@ void FFmpegVideoDecoder::DoDecodeBuffer(const scoped_refptr<Buffer>& buffer) {
DCHECK_NE(state_, kDecodeFinished);
DCHECK(!read_cb_.is_null());
+ if (!buffer) {
+ DeliverFrame(NULL);
+ return;
+ }
+
// During decode, because reads are issued asynchronously, it is possible to
// receive multiple end of stream buffers since each read is acked. When the
// first end of stream buffer is read, FFmpeg may still have frames queued
diff --git a/media/filters/ffmpeg_video_decoder_unittest.cc b/media/filters/ffmpeg_video_decoder_unittest.cc
index 2f285c3..83a754e 100644
--- a/media/filters/ffmpeg_video_decoder_unittest.cc
+++ b/media/filters/ffmpeg_video_decoder_unittest.cc
@@ -438,4 +438,16 @@ TEST_F(FFmpegVideoDecoderTest, Stop_EndOfStream) {
Stop();
}
+// Test aborted read on the demuxer stream.
+TEST_F(FFmpegVideoDecoderTest, AbortPendingRead) {
+ Initialize();
+
+ EXPECT_CALL(*demuxer_, Read(_))
+ .WillOnce(ReturnBuffer(scoped_refptr<Buffer>()));
+
+ scoped_refptr<VideoFrame> video_frame;
+ Read(&video_frame);
+ EXPECT_FALSE(video_frame);
+}
+
} // namespace media
diff --git a/media/filters/gpu_video_decoder.cc b/media/filters/gpu_video_decoder.cc
index 193ccd8..c839d04 100644
--- a/media/filters/gpu_video_decoder.cc
+++ b/media/filters/gpu_video_decoder.cc
@@ -224,6 +224,16 @@ void GpuVideoDecoder::RequestBufferDecode(const scoped_refptr<Buffer>& buffer) {
}
demuxer_read_in_progress_ = false;
+ if (!buffer) {
+ if (pending_read_cb_.is_null())
+ return;
+
+ gvd_loop_proxy_->PostTask(FROM_HERE, base::Bind(
+ pending_read_cb_, scoped_refptr<VideoFrame>()));
+ pending_read_cb_.Reset();
+ return;
+ }
+
if (!vda_) {
EnqueueFrameAndTriggerFrameDelivery(VideoFrame::CreateEmptyFrame());
return;
diff --git a/media/filters/pipeline_integration_test.cc b/media/filters/pipeline_integration_test.cc
index 37d92c1..dbdbbc8 100644
--- a/media/filters/pipeline_integration_test.cc
+++ b/media/filters/pipeline_integration_test.cc
@@ -9,6 +9,9 @@
#include "media/base/message_loop_factory_impl.h"
#include "media/base/pipeline.h"
#include "media/base/test_data_util.h"
+#include "media/filters/chunk_demuxer.h"
+#include "media/filters/chunk_demuxer_client.h"
+#include "media/filters/chunk_demuxer_factory.h"
#include "media/filters/ffmpeg_audio_decoder.h"
#include "media/filters/ffmpeg_demuxer_factory.h"
#include "media/filters/ffmpeg_video_decoder.h"
@@ -22,6 +25,71 @@ using ::testing::AnyNumber;
namespace media {
+// Helper class that emulates calls made on the ChunkDemuxer by the
+// Media Source API.
+class MockMediaSource : public ChunkDemuxerClient {
+ public:
+ MockMediaSource(const std::string& filename, int initial_append_size)
+ : url_(GetTestDataURL(filename)),
+ current_position_(0),
+ initial_append_size_(initial_append_size) {
+ ReadTestDataFile(filename, &file_data_, &file_data_size_);
+
+ DCHECK_GT(initial_append_size_, 0);
+ DCHECK_LE(initial_append_size_, file_data_size_);
+ }
+
+ virtual ~MockMediaSource() {}
+
+ const std::string& url() { return url_; }
+
+ void Seek(int new_position, int seek_append_size) {
+ chunk_demuxer_->FlushData();
+
+ DCHECK_GE(new_position, 0);
+ DCHECK_LT(new_position, file_data_size_);
+ current_position_ = new_position;
+
+ AppendData(seek_append_size);
+ }
+
+ void AppendData(int size) {
+ DCHECK(chunk_demuxer_.get());
+ DCHECK_LT(current_position_, file_data_size_);
+ DCHECK_LE(current_position_ + size, file_data_size_);
+ chunk_demuxer_->AppendData(file_data_.get() + current_position_, size);
+ current_position_ += size;
+ }
+
+ void EndOfStream() {
+ chunk_demuxer_->EndOfStream(PIPELINE_OK);
+ }
+
+ void Abort() {
+ if (!chunk_demuxer_.get())
+ return;
+ chunk_demuxer_->Shutdown();
+ }
+
+ // ChunkDemuxerClient methods.
+ virtual void DemuxerOpened(ChunkDemuxer* demuxer) {
+ chunk_demuxer_ = demuxer;
+ AppendData(initial_append_size_);
+ }
+
+ virtual void DemuxerClosed() {
+ chunk_demuxer_ = NULL;
+ }
+
+ private:
+ std::string url_;
+ scoped_array<uint8> file_data_;
+ int file_data_size_;
+ int current_position_;
+ int initial_append_size_;
+ scoped_refptr<ChunkDemuxer> chunk_demuxer_;
+};
+
// Integration tests for Pipeline. Real demuxers, real decoders, and
// base renderer implementations are used to verify pipeline functionality. The
// renderers used in these tests rely heavily on the AudioRendererBase &
@@ -38,7 +106,7 @@ class PipelineIntegrationTest : public testing::Test {
pipeline_(new Pipeline(&message_loop_, new MediaLog())),
ended_(false) {
EXPECT_CALL(*this, OnVideoRendererPaint()).Times(AnyNumber());
- EXPECT_CALL(*this, OnSetOpaque(true));
+ EXPECT_CALL(*this, OnSetOpaque(true)).Times(AnyNumber());
}
virtual ~PipelineIntegrationTest() {
@@ -50,7 +118,7 @@ class PipelineIntegrationTest : public testing::Test {
void OnStatusCallback(PipelineStatus expected_status,
PipelineStatus status) {
- DCHECK_EQ(status, expected_status);
+ EXPECT_EQ(status, expected_status);
message_loop_.PostTask(FROM_HERE, MessageLoop::QuitClosure());
}
@@ -138,11 +206,20 @@ class PipelineIntegrationTest : public testing::Test {
scoped_ptr<FilterCollection> CreateFilterCollection(const std::string& url) {
scoped_refptr<FileDataSource> data_source = new FileDataSource();
CHECK_EQ(PIPELINE_OK, data_source->Initialize(url));
-
- scoped_ptr<FilterCollection> collection(
- new FilterCollection());
- collection->SetDemuxerFactory(scoped_ptr<DemuxerFactory>(
+ return CreateFilterCollection(scoped_ptr<DemuxerFactory>(
new FFmpegDemuxerFactory(data_source, &message_loop_)));
+ }
+
+ scoped_ptr<FilterCollection> CreateFilterCollection(
+ ChunkDemuxerClient* client) {
+ return CreateFilterCollection(scoped_ptr<DemuxerFactory>(
+ new ChunkDemuxerFactory(client)));
+ }
+
+ scoped_ptr<FilterCollection> CreateFilterCollection(
+ scoped_ptr<DemuxerFactory> demuxer_factory) {
+ scoped_ptr<FilterCollection> collection(new FilterCollection());
+ collection->SetDemuxerFactory(demuxer_factory.Pass());
collection->AddAudioDecoder(new FFmpegAudioDecoder(
message_loop_factory_->GetMessageLoop("AudioDecoderThread")));
collection->AddVideoDecoder(new FFmpegVideoDecoder(
@@ -156,6 +233,38 @@ class PipelineIntegrationTest : public testing::Test {
return collection.Pass();
}
+ // Verifies that seeking works properly for ChunkDemuxer when the
+ // seek happens while there is a pending read on the ChunkDemuxer
+ // and no data is available.
+ void TestSeekDuringRead(const std::string& filename,
+ int initial_append_size,
+ base::TimeDelta start_seek_time,
+ base::TimeDelta seek_time,
+ int seek_file_position,
+ int seek_append_size) {
+ MockMediaSource source(filename, initial_append_size);
+
+ pipeline_->Start(CreateFilterCollection(&source), source.url(),
+ base::Bind(&PipelineIntegrationTest::OnEnded,
+ base::Unretained(this)),
+ base::Bind(&PipelineIntegrationTest::OnError,
+ base::Unretained(this)),
+ NetworkEventCB(),
+ QuitOnStatusCB(PIPELINE_OK));
+ message_loop_.Run();
+
+ Play();
+ WaitUntilCurrentTimeIsAfter(start_seek_time);
+
+ source.Seek(seek_file_position, seek_append_size);
+ Seek(seek_time);
+
+ source.EndOfStream();
+
+ source.Abort();
+ Stop();
+ }
+
protected:
MessageLoop message_loop_;
scoped_ptr<MessageLoopFactory> message_loop_factory_;
@@ -199,7 +308,8 @@ TEST_F(PipelineIntegrationTest, SeekWhilePaused) {
WaitUntilOnEnded();
}
-TEST_F(PipelineIntegrationTest, SeekWhilePlaying) {
+// TODO(acolwell): Fix flakiness http://crbug.com/109875
+TEST_F(PipelineIntegrationTest, FLAKY_SeekWhilePlaying) {
Start(GetTestDataURL("bear-320x240.webm"), PIPELINE_OK);
base::TimeDelta duration(pipeline_->GetMediaDuration());
@@ -218,4 +328,20 @@ TEST_F(PipelineIntegrationTest, SeekWhilePlaying) {
WaitUntilOnEnded();
}
+// Verify audio decoder & renderer can handle aborted demuxer reads.
+TEST_F(PipelineIntegrationTest, ChunkDemuxerAbortRead_AudioOnly) {
+ TestSeekDuringRead("bear-320x240-audio-only.webm", 8192,
+ base::TimeDelta::FromMilliseconds(477),
+ base::TimeDelta::FromMilliseconds(617),
+ 0x10CA, 19730);
+}
+
+// Verify video decoder & renderer can handle aborted demuxer reads.
+TEST_F(PipelineIntegrationTest, ChunkDemuxerAbortRead_VideoOnly) {
+ TestSeekDuringRead("bear-320x240-video-only.webm", 32768,
+ base::TimeDelta::FromMilliseconds(200),
+ base::TimeDelta::FromMilliseconds(1668),
+ 0x1C896, 65536);
+}
+
} // namespace media
diff --git a/media/filters/video_renderer_base.cc b/media/filters/video_renderer_base.cc
index df7350c..69a6dac 100644
--- a/media/filters/video_renderer_base.cc
+++ b/media/filters/video_renderer_base.cc
@@ -336,8 +336,6 @@ void VideoRendererBase::PutCurrentFrame(scoped_refptr<VideoFrame> frame) {
}
void VideoRendererBase::FrameReady(scoped_refptr<VideoFrame> frame) {
- DCHECK(frame);
-
base::AutoLock auto_lock(lock_);
DCHECK_NE(state_, kUninitialized);
DCHECK_NE(state_, kStopped);
@@ -352,6 +350,18 @@ void VideoRendererBase::FrameReady(scoped_refptr<VideoFrame> frame) {
return;
}
+ if (!frame) {
+ if (state_ != kSeeking)
+ return;
+
+ // Abort seek early for a NULL frame because we won't get more frames.
+ // A new seek will be requested after this one completes so there is no
+ // point trying to collect more frames.
+ state_ = kPrerolled;
+ ResetAndRunCB(&seek_cb_, PIPELINE_OK);
+ return;
+ }
+
// Discard frames until we reach our desired seek timestamp.
if (state_ == kSeeking && !frame->IsEndOfStream() &&
(frame->GetTimestamp() + frame->GetDuration()) <= seek_timestamp_) {
diff --git a/media/filters/video_renderer_base_unittest.cc b/media/filters/video_renderer_base_unittest.cc
index 88a1b1a..7ff3f41 100644
--- a/media/filters/video_renderer_base_unittest.cc
+++ b/media/filters/video_renderer_base_unittest.cc
@@ -48,10 +48,11 @@ class VideoRendererBaseTest : public ::testing::Test {
event_(false, false),
timeout_(base::TimeDelta::FromMilliseconds(
TestTimeouts::action_timeout_ms())),
- seeking_(false) {
+ seeking_(false),
+ paint_cv_(&lock_),
+ paint_was_called_(false) {
renderer_ = new VideoRendererBase(
- base::Bind(&VideoRendererBaseTest::PaintCBWasCalled,
- base::Unretained(this)),
+ base::Bind(&VideoRendererBaseTest::Paint, base::Unretained(this)),
base::Bind(&VideoRendererBaseTest::SetOpaqueCBWasCalled,
base::Unretained(this)));
renderer_->set_host(&host_);
@@ -74,8 +75,6 @@ class VideoRendererBaseTest : public ::testing::Test {
}
}
- MOCK_METHOD0(PaintCBWasCalled, void());
-
MOCK_CONST_METHOD1(SetOpaqueCBWasCalled, void(bool));
void Initialize() {
@@ -178,6 +177,18 @@ class VideoRendererBaseTest : public ::testing::Test {
}
}
+ void AbortRead() {
+ // Lock+swap to avoid re-entrancy issues.
+ VideoDecoder::ReadCB read_cb;
+ {
+ base::AutoLock l(lock_);
+ CHECK(!read_cb_.is_null()) << "Can't deliver a frame without a callback";
+ std::swap(read_cb, read_cb_);
+ }
+
+ read_cb.Run(NULL);
+ }
+
void ExpectCurrentFrame(bool present) {
scoped_refptr<VideoFrame> frame;
renderer_->GetCurrentFrame(&frame);
@@ -220,10 +231,12 @@ class VideoRendererBaseTest : public ::testing::Test {
void RenderFrame(int64 timestamp) {
base::AutoLock l(lock_);
time_ = base::TimeDelta::FromMicroseconds(timestamp);
+ paint_was_called_ = false;
if (read_cb_.is_null()) {
cv_.TimedWait(timeout_);
CHECK(!read_cb_.is_null()) << "Timed out waiting for read to occur.";
}
+ WaitForPaint_Locked();
}
// Advances clock to |timestamp| (which should be the timestamp of the last
@@ -241,6 +254,11 @@ class VideoRendererBaseTest : public ::testing::Test {
base::WaitableEvent* event() { return &event_; }
const base::TimeDelta& timeout() { return timeout_; }
+ void VerifyNotSeeking() {
+ base::AutoLock l(lock_);
+ ASSERT_FALSE(seeking_);
+ }
+
protected:
StatisticsCallback NewStatisticsCallback() {
return base::Bind(&MockStatisticsCallback::OnStatistics,
@@ -280,13 +298,12 @@ class VideoRendererBaseTest : public ::testing::Test {
}
void FinishSeeking(int64 timestamp) {
- EXPECT_CALL(*this, PaintCBWasCalled());
- EXPECT_TRUE(seeking_);
-
// Satisfy the read requests. The callback must be executed in order
// to exit the loop since VideoRendererBase can read a few extra frames
// after |timestamp| in order to preroll.
base::AutoLock l(lock_);
+ EXPECT_TRUE(seeking_);
+ paint_was_called_ = false;
int i = 0;
while (seeking_) {
if (!read_cb_.is_null()) {
@@ -310,6 +327,21 @@ class VideoRendererBaseTest : public ::testing::Test {
}
EXPECT_TRUE(read_cb_.is_null());
+ WaitForPaint_Locked();
+ }
+
+ void Paint() {
+ base::AutoLock l(lock_);
+ paint_was_called_ = true;
+ paint_cv_.Signal();
+ }
+
+ void WaitForPaint_Locked() {
+ lock_.AssertAcquired();
+ if (paint_was_called_)
+ return;
+ paint_cv_.TimedWait(timeout_);
+ EXPECT_TRUE(paint_was_called_);
}
base::Lock lock_;
@@ -322,6 +354,10 @@ class VideoRendererBaseTest : public ::testing::Test {
VideoDecoder::ReadCB read_cb_;
base::TimeDelta time_;
+ // Used in conjunction with |lock_| to wait for Paint() calls.
+ base::ConditionVariable paint_cv_;
+ bool paint_was_called_;
+
DISALLOW_COPY_AND_ASSIGN(VideoRendererBaseTest);
};
@@ -342,13 +378,8 @@ TEST_F(VideoRendererBaseTest, EndOfStream) {
Play();
// Finish rendering up to the next-to-last frame.
- //
- // Put the gmock expectation here to avoid racing with the rendering thread.
- EXPECT_CALL(*this, PaintCBWasCalled())
- .Times(limits::kMaxVideoFrames - 1);
- for (int i = 1; i < limits::kMaxVideoFrames; ++i) {
+ for (int i = 1; i < limits::kMaxVideoFrames; ++i)
RenderFrame(kFrameDuration * i);
- }
// Finish rendering the last frame, we should NOT get a new frame but instead
// get notified of end of stream.
@@ -475,4 +506,44 @@ TEST_F(VideoRendererBaseTest, Shutdown_DuringPaint) {
Stop();
}
+TEST_F(VideoRendererBaseTest, AbortPendingRead_Playing) {
+ Initialize();
+ Play();
+
+ // Render a frame to trigger a Read().
+ RenderFrame(kFrameDuration);
+
+ AbortRead();
+
+ Pause();
+ Flush();
+ Seek(kFrameDuration * 6);
+ ExpectCurrentTimestamp(kFrameDuration * 6);
+ Shutdown();
+}
+
+TEST_F(VideoRendererBaseTest, AbortPendingRead_Flush) {
+ Initialize();
+ Play();
+
+ // Render a frame to trigger a Read().
+ RenderFrame(kFrameDuration);
+
+ Pause();
+ renderer_->Flush(NewWaitableClosure());
+ AbortRead();
+ WaitForClosure();
+ Shutdown();
+}
+
+TEST_F(VideoRendererBaseTest, AbortPendingRead_Seek) {
+ Initialize();
+ Pause();
+ Flush();
+ StartSeeking(kFrameDuration * 6);
+ AbortRead();
+ VerifyNotSeeking();
+ Shutdown();
+}
+
} // namespace media
diff --git a/media/webm/webm_cluster_parser.cc b/media/webm/webm_cluster_parser.cc
index 8151ec6..92f1dc7 100644
--- a/media/webm/webm_cluster_parser.cc
+++ b/media/webm/webm_cluster_parser.cc
@@ -6,13 +6,17 @@
#include "base/logging.h"
#include "media/base/data_buffer.h"
+#include "media/ffmpeg/ffmpeg_common.h"
#include "media/webm/webm_constants.h"
namespace media {
static Buffer* CreateBuffer(const uint8* data, size_t size) {
- scoped_array<uint8> buf(new uint8[size]);
+ // Why FF_INPUT_BUFFER_PADDING_SIZE? FFmpeg assumes all input buffers are
+ // padded with this value.
+ scoped_array<uint8> buf(new uint8[size + FF_INPUT_BUFFER_PADDING_SIZE]);
memcpy(buf.get(), data, size);
+ memset(buf.get() + size, 0, FF_INPUT_BUFFER_PADDING_SIZE);
return new DataBuffer(buf.Pass(), size);
}
@@ -129,7 +133,7 @@ bool WebMClusterParser::OnSimpleBlock(int track_num, int timecode,
if (!queue->empty() &&
buffer->GetTimestamp() == queue->back()->GetTimestamp()) {
DVLOG(1) << "Got SimpleBlock timecode is not strictly monotonically "
- << "increasing for track " << track_num;
+ << "increasing for track " << track_num;
return false;
}