diff options
author | acolwell@chromium.org <acolwell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-07-26 16:38:03 +0000 |
---|---|---|
committer | acolwell@chromium.org <acolwell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-07-26 16:38:03 +0000 |
commit | a93866519172cc8993583d9116ce16eec01d532b (patch) | |
tree | e617d768783948cabd3e0363cfa6bfe45a6b4ee0 | |
parent | 2b2d9393845b8bb60f843d14ab0b59c4bc3949db (diff) | |
download | chromium_src-a93866519172cc8993583d9116ce16eec01d532b.zip chromium_src-a93866519172cc8993583d9116ce16eec01d532b.tar.gz chromium_src-a93866519172cc8993583d9116ce16eec01d532b.tar.bz2 |
Add config change handling to SourceBufferStream & ChunkDemuxer
BUG=122913
TEST=None
Review URL: https://chromiumcodereview.appspot.com/10696182
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@148563 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | media/base/audio_decoder_config.cc | 10 | ||||
-rw-r--r-- | media/base/audio_decoder_config.h | 4 | ||||
-rw-r--r-- | media/base/byte_queue.h | 5 | ||||
-rw-r--r-- | media/base/stream_parser_buffer.cc | 11 | ||||
-rw-r--r-- | media/base/stream_parser_buffer.h | 9 | ||||
-rw-r--r-- | media/base/video_decoder_config.cc | 12 | ||||
-rw-r--r-- | media/base/video_decoder_config.h | 4 | ||||
-rw-r--r-- | media/filters/chunk_demuxer.cc | 66 | ||||
-rw-r--r-- | media/filters/chunk_demuxer_unittest.cc | 248 | ||||
-rw-r--r-- | media/filters/source_buffer_stream.cc | 151 | ||||
-rw-r--r-- | media/filters/source_buffer_stream.h | 61 | ||||
-rw-r--r-- | media/filters/source_buffer_stream_unittest.cc | 10 | ||||
-rw-r--r-- | media/test/data/bear-320x240-manifest.js | 17 | ||||
-rw-r--r-- | media/test/data/bear-640x360-manifest.js | 16 | ||||
-rw-r--r-- | media/webm/webm_info_parser.h | 5 | ||||
-rw-r--r-- | media/webm/webm_stream_parser.cc | 9 |
16 files changed, 558 insertions, 80 deletions
diff --git a/media/base/audio_decoder_config.cc b/media/base/audio_decoder_config.cc index 5d15e7f..1f259f3 100644 --- a/media/base/audio_decoder_config.cc +++ b/media/base/audio_decoder_config.cc @@ -81,6 +81,16 @@ bool AudioDecoderConfig::IsValidConfig() const { samples_per_second_ <= limits::kMaxSampleRate; } +bool AudioDecoderConfig::Matches(const AudioDecoderConfig& config) const { + return ((codec() == config.codec()) && + (bits_per_channel() == config.bits_per_channel()) && + (channel_layout() == config.channel_layout()) && + (samples_per_second() == config.samples_per_second()) && + (extra_data_size() == config.extra_data_size()) && + (!extra_data() || !memcmp(extra_data(), config.extra_data(), + extra_data_size()))); +} + void AudioDecoderConfig::CopyFrom(const AudioDecoderConfig& audio_config) { Initialize(audio_config.codec(), audio_config.bits_per_channel(), diff --git a/media/base/audio_decoder_config.h b/media/base/audio_decoder_config.h index 6e58c5b..e2309d5 100644 --- a/media/base/audio_decoder_config.h +++ b/media/base/audio_decoder_config.h @@ -65,6 +65,10 @@ class MEDIA_EXPORT AudioDecoderConfig { // otherwise. bool IsValidConfig() const; + // Returns true if all fields in |config| match this config. + // Note: The contents of |extra_data_| are compared not the raw pointers. + bool Matches(const AudioDecoderConfig& config) const; + AudioCodec codec() const; int bits_per_channel() const; ChannelLayout channel_layout() const; diff --git a/media/base/byte_queue.h b/media/base/byte_queue.h index d3ef605..7619472 100644 --- a/media/base/byte_queue.h +++ b/media/base/byte_queue.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. @@ -7,6 +7,7 @@ #include "base/basictypes.h" #include "base/memory/scoped_ptr.h" +#include "media/base/media_export.h" namespace media { @@ -15,7 +16,7 @@ namespace media { // Pop(). The contents of the queue can be observed via the Peek() method. // This class manages the underlying storage of the queue and tries to minimize // the number of buffer copies when data is appended and removed. -class ByteQueue { +class MEDIA_EXPORT ByteQueue { public: ByteQueue(); ~ByteQueue(); diff --git a/media/base/stream_parser_buffer.cc b/media/base/stream_parser_buffer.cc index 8501372..04f9513 100644 --- a/media/base/stream_parser_buffer.cc +++ b/media/base/stream_parser_buffer.cc @@ -32,11 +32,20 @@ StreamParserBuffer::StreamParserBuffer(const uint8* data, int data_size, bool is_keyframe) : DecoderBuffer(data, data_size), is_keyframe_(is_keyframe), - decode_timestamp_(kNoTimestamp()) { + decode_timestamp_(kNoTimestamp()), + config_id_(kInvalidConfigId) { SetDuration(kNoTimestamp()); } StreamParserBuffer::~StreamParserBuffer() { } +int StreamParserBuffer::GetConfigId() const { + return config_id_; +} + +void StreamParserBuffer::SetConfigId(int config_id) { + config_id_ = config_id; +} + } // namespace media diff --git a/media/base/stream_parser_buffer.h b/media/base/stream_parser_buffer.h index da7f64f..90e4ca9 100644 --- a/media/base/stream_parser_buffer.h +++ b/media/base/stream_parser_buffer.h @@ -12,6 +12,9 @@ namespace media { class MEDIA_EXPORT StreamParserBuffer : public DecoderBuffer { public: + // Value used to signal an invalid decoder config ID. + enum { kInvalidConfigId = -1 }; + static scoped_refptr<StreamParserBuffer> CreateEOSBuffer(); static scoped_refptr<StreamParserBuffer> CopyFrom( const uint8* data, int data_size, bool is_keyframe); @@ -22,12 +25,18 @@ class MEDIA_EXPORT StreamParserBuffer : public DecoderBuffer { base::TimeDelta GetDecodeTimestamp() const; void SetDecodeTimestamp(const base::TimeDelta& timestamp); + // Gets/sets the ID of the decoder config associated with this + // buffer. + int GetConfigId() const; + void SetConfigId(int config_id); + private: StreamParserBuffer(const uint8* data, int data_size, bool is_keyframe); virtual ~StreamParserBuffer(); bool is_keyframe_; base::TimeDelta decode_timestamp_; + int config_id_; DISALLOW_COPY_AND_ASSIGN(StreamParserBuffer); }; diff --git a/media/base/video_decoder_config.cc b/media/base/video_decoder_config.cc index 1a1783d..1e00e34 100644 --- a/media/base/video_decoder_config.cc +++ b/media/base/video_decoder_config.cc @@ -149,6 +149,18 @@ bool VideoDecoderConfig::IsValidConfig() const { format_, natural_size_.width(), natural_size_.height()); } +bool VideoDecoderConfig::Matches(const VideoDecoderConfig& config) const { + return ((codec() == config.codec()) && + (format() == config.format()) && + (profile() == config.profile()) && + (coded_size() == config.coded_size()) && + (visible_rect() == config.visible_rect()) && + (natural_size() == config.natural_size()) && + (extra_data_size() == config.extra_data_size()) && + (!extra_data() || !memcmp(extra_data(), config.extra_data(), + extra_data_size()))); +} + std::string VideoDecoderConfig::AsHumanReadableString() const { std::ostringstream s; s << "codec: " << codec() diff --git a/media/base/video_decoder_config.h b/media/base/video_decoder_config.h index e095ba9..894eb24 100644 --- a/media/base/video_decoder_config.h +++ b/media/base/video_decoder_config.h @@ -96,6 +96,10 @@ class MEDIA_EXPORT VideoDecoderConfig { // otherwise. bool IsValidConfig() const; + // Returns true if all fields in |config| match this config. + // Note: The contents of |extra_data_| are compared not the raw pointers. + bool Matches(const VideoDecoderConfig& config) const; + // Returns a human-readable string describing |*this|. For debugging & test // output only. std::string AsHumanReadableString() const; diff --git a/media/filters/chunk_demuxer.cc b/media/filters/chunk_demuxer.cc index c548467..be4fa60 100644 --- a/media/filters/chunk_demuxer.cc +++ b/media/filters/chunk_demuxer.cc @@ -326,46 +326,13 @@ Ranges<TimeDelta> ChunkDemuxerStream::GetBufferedRanges() { bool ChunkDemuxerStream::UpdateAudioConfig(const AudioDecoderConfig& config) { DCHECK(config.IsValidConfig()); DCHECK_EQ(type_, AUDIO); - - const AudioDecoderConfig& current_config = - stream_->GetCurrentAudioDecoderConfig(); - - bool success = (current_config.codec() == config.codec()) && - (current_config.bits_per_channel() == config.bits_per_channel()) && - (current_config.channel_layout() == config.channel_layout()) && - (current_config.samples_per_second() == config.samples_per_second()) && - (current_config.extra_data_size() == config.extra_data_size()) && - (!current_config.extra_data() || - !memcmp(current_config.extra_data(), config.extra_data(), - current_config.extra_data_size())); - - if (!success) - DVLOG(1) << "UpdateAudioConfig() : Failed to update audio config."; - - return success; + return stream_->UpdateAudioConfig(config); } bool ChunkDemuxerStream::UpdateVideoConfig(const VideoDecoderConfig& config) { DCHECK(config.IsValidConfig()); DCHECK_EQ(type_, VIDEO); - const VideoDecoderConfig& current_config = - stream_->GetCurrentVideoDecoderConfig(); - - bool success = (current_config.codec() == config.codec()) && - (current_config.format() == config.format()) && - (current_config.profile() == config.profile()) && - (current_config.coded_size() == config.coded_size()) && - (current_config.visible_rect() == config.visible_rect()) && - (current_config.natural_size() == config.natural_size()) && - (current_config.extra_data_size() == config.extra_data_size()) && - (!current_config.extra_data() || - !memcmp(current_config.extra_data(), config.extra_data(), - current_config.extra_data_size())); - - if (!success) - DVLOG(1) << "UpdateVideoConfig() : Failed to update video config."; - - return success; + return stream_->UpdateVideoConfig(config); } void ChunkDemuxerStream::EndOfStream() { @@ -448,6 +415,9 @@ const VideoDecoderConfig& ChunkDemuxerStream::video_decoder_config() { void ChunkDemuxerStream::ChangeState_Locked(State state) { lock_.AssertAcquired(); + DVLOG(1) << "ChunkDemuxerStream::ChangeState_Locked() : " + << "type " << type_ + << " - " << state_ << " -> " << state; state_ = state; } @@ -485,17 +455,23 @@ bool ChunkDemuxerStream::GetNextBuffer_Locked( switch (state_) { case RETURNING_DATA_FOR_READS: - if (stream_->GetNextBuffer(buffer)) { - *status = DemuxerStream::kOk; - return true; - } - - if (end_of_stream_) { - *status = DemuxerStream::kOk; - *buffer = StreamParserBuffer::CreateEOSBuffer(); - return true; + switch (stream_->GetNextBuffer(buffer)) { + case SourceBufferStream::kSuccess: + *status = DemuxerStream::kOk; + return true; + case SourceBufferStream::kNeedBuffer: + if (end_of_stream_) { + *status = DemuxerStream::kOk; + *buffer = StreamParserBuffer::CreateEOSBuffer(); + return true; + } + return false; + case SourceBufferStream::kConfigChange: + *status = kConfigChanged; + *buffer = NULL; + return true; } - return false; + break; case WAITING_FOR_SEEK: // Null buffers should be returned in this state since we are waiting // for a seek. Any buffers in the SourceBuffer should NOT be returned diff --git a/media/filters/chunk_demuxer_unittest.cc b/media/filters/chunk_demuxer_unittest.cc index c364bc3..fda9bb7 100644 --- a/media/filters/chunk_demuxer_unittest.cc +++ b/media/filters/chunk_demuxer_unittest.cc @@ -356,6 +356,65 @@ class ChunkDemuxerTest : public testing::Test { return AppendInitSegment(true, true, false); } + // Initializes the demuxer with data from 2 files with different + // decoder configurations. This is used to test the decoder config change + // logic. + // + // bear-320x240.webm VideoDecoderConfig returns 320x240 for its natural_size() + // bear-640x360.webm VideoDecoderConfig returns 640x360 for its natural_size() + // The resulting video stream returns data from each file for the following + // time ranges. + // bear-320x240.webm : [0-501) [801-2737) + // bear-640x360.webm : [501-801) + // + // bear-320x240.webm AudioDecoderConfig returns 3863 for its extra_data_size() + // bear-640x360.webm AudioDecoderConfig returns 3935 for its extra_data_size() + // The resulting audio stream returns data from each file for the following + // time ranges. + // bear-320x240.webm : [0-464) [779-2737) + // bear-640x360.webm : [477-756) + bool InitDemuxerWithConfigChangeData() { + scoped_refptr<DecoderBuffer> bear1 = ReadTestDataFile("bear-320x240.webm"); + scoped_refptr<DecoderBuffer> bear2 = ReadTestDataFile("bear-640x360.webm"); + + EXPECT_CALL(*client_, DemuxerOpened(_)); + demuxer_->Initialize( + &host_, CreateInitDoneCB(base::TimeDelta::FromMilliseconds(2744), + PIPELINE_OK)); + + if (AddId(kSourceId, true, true) != ChunkDemuxer::kOk) + return false; + + // Append the whole bear1 file. + if (!AppendData(bear1->GetData(), bear1->GetDataSize())) + return false; + CheckExpectedRanges(kSourceId, "{ [0,2737) }"); + + // Append initialization segment for bear2. + // Note: Offsets here and below are derived from + // media/test/data/bear-640x360-manifest.js and + // media/test/data/bear-320x240-manifest.js which were + // generated from media/test/data/bear-640x360.webm and + // media/test/data/bear-320x240.webm respectively. + if (!AppendData(bear2->GetData(), 4459)) + return false; + + // Append a media segment that goes from [0.477000, 0.988000). + if (!AppendData(bear2->GetData() + 55173, 19021)) + return false; + CheckExpectedRanges(kSourceId, "{ [0,1002) [1201,2737) }"); + + // Append initialization segment for bear1 & fill gap with [770-1197) + // segment. + if (!AppendData(bear1->GetData(), 4370) || + !AppendData(bear1->GetData() + 72737, 28183)) { + return false; + } + CheckExpectedRanges(kSourceId, "{ [0,2737) }"); + + return demuxer_->EndOfStream(PIPELINE_OK); + } + void ShutdownDemuxer() { if (demuxer_) { EXPECT_CALL(*client_, DemuxerClosed()); @@ -531,6 +590,12 @@ class ChunkDemuxerTest : public testing::Test { base::Unretained(this))); } + void ExpectConfigChanged(DemuxerStream* stream) { + EXPECT_CALL(*this, ReadDone(DemuxerStream::kConfigChanged, _)); + stream->Read(base::Bind(&ChunkDemuxerTest::ReadDone, + base::Unretained(this))); + } + MOCK_METHOD1(Checkpoint, void(int id)); struct BufferTimestamps { @@ -1816,4 +1881,187 @@ TEST_F(ChunkDemuxerTest, TestEndOfStreamDuringSeek) { end_of_stream_helper.CheckIfReadDonesWereCalled(true); } +static void ConfigChangeReadDone(DemuxerStream::Status* status_out, + scoped_refptr<DecoderBuffer>* buffer_out, + DemuxerStream::Status status, + const scoped_refptr<DecoderBuffer>& buffer) { + *status_out = status; + *buffer_out = buffer; +} + +static void ReadUntilNotOkOrEndOfStream( + const scoped_refptr<DemuxerStream>& stream, + DemuxerStream::Status* status, + base::TimeDelta* last_timestamp) { + scoped_refptr<DecoderBuffer> buffer; + + *last_timestamp = kNoTimestamp(); + do { + stream->Read(base::Bind(&ConfigChangeReadDone, status, &buffer)); + if (*status == DemuxerStream::kOk && !buffer->IsEndOfStream()) + *last_timestamp = buffer->GetTimestamp(); + + } while (*status == DemuxerStream::kOk && !buffer->IsEndOfStream()); +} + +TEST_F(ChunkDemuxerTest, TestConfigChange_Video) { + InSequence s; + + ASSERT_TRUE(InitDemuxerWithConfigChangeData()); + + scoped_refptr<DemuxerStream> stream = + demuxer_->GetStream(DemuxerStream::VIDEO); + DemuxerStream::Status status; + base::TimeDelta last_timestamp; + + // Fetch initial video config and verify it matches what we expect. + const VideoDecoderConfig& video_config_1 = stream->video_decoder_config(); + ASSERT_TRUE(video_config_1.IsValidConfig()); + EXPECT_EQ(video_config_1.natural_size().width(), 320); + EXPECT_EQ(video_config_1.natural_size().height(), 240); + + ExpectRead(stream, 0); + + ReadUntilNotOkOrEndOfStream(stream, &status, &last_timestamp); + + ASSERT_EQ(status, DemuxerStream::kConfigChanged); + EXPECT_EQ(last_timestamp.InMilliseconds(), 467); + + // Verify that another read will result in kConfigChanged being returned + // again. + ExpectConfigChanged(stream); + + // Fetch the new decoder config. + const VideoDecoderConfig& video_config_2 = stream->video_decoder_config(); + ASSERT_TRUE(video_config_2.IsValidConfig()); + EXPECT_EQ(video_config_2.natural_size().width(), 640); + EXPECT_EQ(video_config_2.natural_size().height(), 360); + + ExpectRead(stream, 501); + + // Read until the next config change. + ReadUntilNotOkOrEndOfStream(stream, &status, &last_timestamp); + ASSERT_EQ(status, DemuxerStream::kConfigChanged); + EXPECT_EQ(last_timestamp.InMilliseconds(), 767); + + // Verify we get another ConfigChanged status. + ExpectConfigChanged(stream); + + // Get the new config and verify that it matches the first one. + ASSERT_TRUE(video_config_1.Matches(stream->video_decoder_config())); + + ExpectRead(stream, 801); + + // Read until the end of the stream just to make sure there aren't any other + // config changes. + ReadUntilNotOkOrEndOfStream(stream, &status, &last_timestamp); + ASSERT_EQ(status, DemuxerStream::kOk); +} + +TEST_F(ChunkDemuxerTest, TestConfigChange_Audio) { + InSequence s; + + ASSERT_TRUE(InitDemuxerWithConfigChangeData()); + + scoped_refptr<DemuxerStream> stream = + demuxer_->GetStream(DemuxerStream::AUDIO); + DemuxerStream::Status status; + base::TimeDelta last_timestamp; + + // Fetch initial audio config and verify it matches what we expect. + const AudioDecoderConfig& audio_config_1 = stream->audio_decoder_config(); + ASSERT_TRUE(audio_config_1.IsValidConfig()); + EXPECT_EQ(audio_config_1.samples_per_second(), 44100); + EXPECT_EQ(audio_config_1.extra_data_size(), 3863u); + + ExpectRead(stream, 0); + + ReadUntilNotOkOrEndOfStream(stream, &status, &last_timestamp); + + ASSERT_EQ(status, DemuxerStream::kConfigChanged); + EXPECT_EQ(last_timestamp.InMilliseconds(), 464); + + // Verify that another read will result in kConfigChanged being returned + // again. + ExpectConfigChanged(stream); + + // Fetch the new decoder config. + const AudioDecoderConfig& audio_config_2 = stream->audio_decoder_config(); + ASSERT_TRUE(audio_config_2.IsValidConfig()); + EXPECT_EQ(audio_config_2.samples_per_second(), 44100); + EXPECT_EQ(audio_config_2.extra_data_size(), 3935u); + + ExpectRead(stream, 477); + + // Read until the next config change. + ReadUntilNotOkOrEndOfStream(stream, &status, &last_timestamp); + ASSERT_EQ(status, DemuxerStream::kConfigChanged); + EXPECT_EQ(last_timestamp.InMilliseconds(), 756); + + // Verify we get another ConfigChanged status. + ExpectConfigChanged(stream); + + // Get the new config and verify that it matches the first one. + ASSERT_TRUE(audio_config_1.Matches(stream->audio_decoder_config())); + + ExpectRead(stream, 779); + + // Read until the end of the stream just to make sure there aren't any other + // config changes. + ReadUntilNotOkOrEndOfStream(stream, &status, &last_timestamp); + ASSERT_EQ(status, DemuxerStream::kOk); +} + +TEST_F(ChunkDemuxerTest, TestConfigChange_Seek) { + InSequence s; + + ASSERT_TRUE(InitDemuxerWithConfigChangeData()); + + scoped_refptr<DemuxerStream> stream = + demuxer_->GetStream(DemuxerStream::VIDEO); + + // Fetch initial video config and verify it matches what we expect. + const VideoDecoderConfig& video_config_1 = stream->video_decoder_config(); + ASSERT_TRUE(video_config_1.IsValidConfig()); + EXPECT_EQ(video_config_1.natural_size().width(), 320); + EXPECT_EQ(video_config_1.natural_size().height(), 240); + + ExpectRead(stream, 0); + + // Seek to a location with a different config. + demuxer_->Seek(base::TimeDelta::FromMilliseconds(501), + NewExpectedStatusCB(PIPELINE_OK)); + + // Verify that the config change is signalled. + ExpectConfigChanged(stream); + + // Fetch the new decoder config and verify it is what we expect. + const VideoDecoderConfig& video_config_2 = stream->video_decoder_config(); + ASSERT_TRUE(video_config_2.IsValidConfig()); + EXPECT_EQ(video_config_2.natural_size().width(), 640); + EXPECT_EQ(video_config_2.natural_size().height(), 360); + + // Verify that Read() will return a buffer now. + ExpectRead(stream, 501); + + // Seek back to the beginning and verify we get another config change. + demuxer_->Seek(base::TimeDelta::FromMilliseconds(0), + NewExpectedStatusCB(PIPELINE_OK)); + ExpectConfigChanged(stream); + ASSERT_TRUE(video_config_1.Matches(stream->video_decoder_config())); + ExpectRead(stream, 0); + + // Seek to a location that requires a config change and then + // seek to a new location that has the same configuration as + // the start of the file without a Read() in the middle. + demuxer_->Seek(base::TimeDelta::FromMilliseconds(501), + NewExpectedStatusCB(PIPELINE_OK)); + demuxer_->Seek(base::TimeDelta::FromMilliseconds(801), + NewExpectedStatusCB(PIPELINE_OK)); + + // Verify that no config change is signalled. + ExpectRead(stream, 801); + ASSERT_TRUE(video_config_1.Matches(stream->video_decoder_config())); +} + } // namespace media diff --git a/media/filters/source_buffer_stream.cc b/media/filters/source_buffer_stream.cc index 015fc6c..2ffbb6a 100644 --- a/media/filters/source_buffer_stream.cc +++ b/media/filters/source_buffer_stream.cc @@ -9,6 +9,7 @@ #include "base/bind.h" #include "base/logging.h" +#include "base/stl_util.h" namespace media { @@ -93,6 +94,10 @@ class SourceBufferRange { bool GetNextBuffer(scoped_refptr<StreamParserBuffer>* out_buffer); bool HasNextBuffer() const; + // Returns the config ID for the buffer that will be returned by + // GetNextBuffer(). + int GetNextConfigId() const; + // Returns true if the range knows the position of the next buffer it should // return, i.e. it has been Seek()ed. This does not necessarily mean that it // has the next buffer yet. @@ -229,7 +234,11 @@ static int kDefaultBufferDurationInMs = 125; namespace media { SourceBufferStream::SourceBufferStream(const AudioDecoderConfig& audio_config) - : stream_start_time_(kNoTimestamp()), + : current_config_index_(0), + append_config_index_(0), + audio_configs_(1), + video_configs_(0), + stream_start_time_(kNoTimestamp()), seek_pending_(false), seek_buffer_timestamp_(kNoTimestamp()), selected_range_(NULL), @@ -238,11 +247,16 @@ SourceBufferStream::SourceBufferStream(const AudioDecoderConfig& audio_config) new_media_segment_(false), last_buffer_timestamp_(kNoTimestamp()), max_interbuffer_distance_(kNoTimestamp()) { - audio_config_.CopyFrom(audio_config); + audio_configs_[0] = new AudioDecoderConfig(); + audio_configs_[0]->CopyFrom(audio_config); } SourceBufferStream::SourceBufferStream(const VideoDecoderConfig& video_config) - : stream_start_time_(kNoTimestamp()), + : current_config_index_(0), + append_config_index_(0), + audio_configs_(0), + video_configs_(1), + stream_start_time_(kNoTimestamp()), seek_pending_(false), seek_buffer_timestamp_(kNoTimestamp()), selected_range_(NULL), @@ -251,7 +265,8 @@ SourceBufferStream::SourceBufferStream(const VideoDecoderConfig& video_config) new_media_segment_(false), last_buffer_timestamp_(kNoTimestamp()), max_interbuffer_distance_(kNoTimestamp()) { - video_config_.CopyFrom(video_config); + video_configs_[0] = new VideoDecoderConfig(); + video_configs_[0]->CopyFrom(video_config); } SourceBufferStream::~SourceBufferStream() { @@ -259,6 +274,9 @@ SourceBufferStream::~SourceBufferStream() { delete ranges_.front(); ranges_.pop_front(); } + + STLDeleteElements(&audio_configs_); + STLDeleteElements(&video_configs_); } void SourceBufferStream::OnNewMediaSegment( @@ -305,6 +323,7 @@ bool SourceBufferStream::Append( } UpdateMaxInterbufferDistance(buffers); + SetConfigIds(buffers); // Save a snapshot of stream state before range modifications are made. base::TimeDelta next_buffer_timestamp = GetNextBufferTimestamp(); @@ -423,6 +442,13 @@ void SourceBufferStream::UpdateMaxInterbufferDistance( } } +void SourceBufferStream::SetConfigIds(const BufferQueue& buffers) { + for (BufferQueue::const_iterator itr = buffers.begin(); + itr != buffers.end(); ++itr) { + (*itr)->SetConfigId(append_config_index_); + } +} + void SourceBufferStream::InsertIntoExistingRange( const RangeList::iterator& range_for_new_buffers_itr, const BufferQueue& new_buffers, @@ -653,15 +679,25 @@ bool SourceBufferStream::IsSeekPending() const { return seek_pending_; } -bool SourceBufferStream::GetNextBuffer( +SourceBufferStream::Status SourceBufferStream::GetNextBuffer( scoped_refptr<StreamParserBuffer>* out_buffer) { if (!track_buffer_.empty()) { + if (track_buffer_.front()->GetConfigId() != current_config_index_) + return kConfigChange; + *out_buffer = track_buffer_.front(); track_buffer_.pop_front(); - return true; + return kSuccess; } - return selected_range_ && selected_range_->GetNextBuffer(out_buffer); + if (!selected_range_ || !selected_range_->HasNextBuffer()) + return kNeedBuffer; + + if (selected_range_->GetNextConfigId() != current_config_index_) + return kConfigChange; + + CHECK(selected_range_->GetNextBuffer(out_buffer)); + return kSuccess; } base::TimeDelta SourceBufferStream::GetNextBufferTimestamp() { @@ -729,12 +765,99 @@ bool SourceBufferStream::IsEndSelected() const { return ranges_.empty() || selected_range_ == ranges_.back(); } +const AudioDecoderConfig& SourceBufferStream::GetCurrentAudioDecoderConfig() { + CompleteConfigChange(); + return *audio_configs_[current_config_index_]; +} + +const VideoDecoderConfig& SourceBufferStream::GetCurrentVideoDecoderConfig() { + CompleteConfigChange(); + return *video_configs_[current_config_index_]; +} + base::TimeDelta SourceBufferStream::GetMaxInterbufferDistance() const { if (max_interbuffer_distance_ == kNoTimestamp()) return base::TimeDelta::FromMilliseconds(kDefaultBufferDurationInMs); return max_interbuffer_distance_; } +bool SourceBufferStream::UpdateAudioConfig(const AudioDecoderConfig& config) { + DCHECK(!audio_configs_.empty()); + DCHECK(video_configs_.empty()); + + if (audio_configs_[0]->codec() != config.codec()) { + DVLOG(1) << "UpdateAudioConfig() : Codec changes not allowed."; + return false; + } + + if (audio_configs_[0]->samples_per_second() != config.samples_per_second()) { + DVLOG(1) << "UpdateAudioConfig() : Sample rate changes not allowed."; + return false; + } + + if (audio_configs_[0]->channel_layout() != config.channel_layout()) { + DVLOG(1) << "UpdateAudioConfig() : Channel layout changes not allowed."; + return false; + } + + if (audio_configs_[0]->bits_per_channel() != config.bits_per_channel()) { + DVLOG(1) << "UpdateAudioConfig() : Bits per channel changes not allowed."; + return false; + } + + // Check to see if the new config matches an existing one. + for (size_t i = 0; i < audio_configs_.size(); ++i) { + if (config.Matches(*audio_configs_[i])) { + append_config_index_ = i; + return true; + } + } + + // No matches found so let's add this one to the list. + append_config_index_ = audio_configs_.size(); + audio_configs_.resize(audio_configs_.size() + 1); + audio_configs_[append_config_index_] = new AudioDecoderConfig(); + audio_configs_[append_config_index_]->CopyFrom(config); + return true; +} + +bool SourceBufferStream::UpdateVideoConfig(const VideoDecoderConfig& config) { + DCHECK(!video_configs_.empty()); + DCHECK(audio_configs_.empty()); + + if (video_configs_[0]->codec() != config.codec()) { + DVLOG(1) << "UpdateVideoConfig() : Codec changes not allowed."; + return false; + } + + // Check to see if the new config matches an existing one. + for (size_t i = 0; i < video_configs_.size(); ++i) { + if (config.Matches(*video_configs_[i])) { + append_config_index_ = i; + return true; + } + } + + // No matches found so let's add this one to the list. + append_config_index_ = video_configs_.size(); + video_configs_.resize(video_configs_.size() + 1); + video_configs_[append_config_index_] = new VideoDecoderConfig(); + video_configs_[append_config_index_]->CopyFrom(config); + return true; +} + +void SourceBufferStream::CompleteConfigChange() { + if (!track_buffer_.empty()) { + current_config_index_ = track_buffer_.front()->GetConfigId(); + return; + } + + if (!selected_range_ || !selected_range_->HasNextBuffer()) + return; + + current_config_index_ = selected_range_->GetNextConfigId(); +} + SourceBufferRange::SourceBufferRange( const BufferQueue& new_buffers, base::TimeDelta media_segment_start_time, const InterbufferDistanceCB& interbuffer_distance_cb) @@ -925,12 +1048,9 @@ bool SourceBufferRange::TruncateAt( bool SourceBufferRange::GetNextBuffer( scoped_refptr<StreamParserBuffer>* out_buffer) { - if (waiting_for_keyframe_ || - next_buffer_index_ >= static_cast<int>(buffers_.size())) { + if (!HasNextBuffer()) return false; - } - DCHECK_GE(next_buffer_index_, 0); *out_buffer = buffers_.at(next_buffer_index_); next_buffer_index_++; return true; @@ -938,9 +1058,16 @@ bool SourceBufferRange::GetNextBuffer( bool SourceBufferRange::HasNextBuffer() const { return next_buffer_index_ >= 0 && - next_buffer_index_ < static_cast<int>(buffers_.size()); + next_buffer_index_ < static_cast<int>(buffers_.size()) && + !waiting_for_keyframe_; } +int SourceBufferRange::GetNextConfigId() const { + DCHECK(HasNextBuffer()); + return buffers_.at(next_buffer_index_)->GetConfigId(); +} + + base::TimeDelta SourceBufferRange::GetNextTimestamp() const { DCHECK(!buffers_.empty()); DCHECK(HasNextBufferPosition()); diff --git a/media/filters/source_buffer_stream.h b/media/filters/source_buffer_stream.h index 3784ccd..1dba5bd 100644 --- a/media/filters/source_buffer_stream.h +++ b/media/filters/source_buffer_stream.h @@ -13,6 +13,7 @@ #include <deque> #include <list> #include <utility> +#include <vector> #include "base/memory/ref_counted.h" #include "media/base/audio_decoder_config.h" @@ -30,6 +31,17 @@ class MEDIA_EXPORT SourceBufferStream { public: typedef std::deque<scoped_refptr<StreamParserBuffer> > BufferQueue; + // Status returned by GetNextBuffer(). + // kSuccess: Indicates that the next buffer was returned. + // kNeedBuffer: Indicates that we need more data before a buffer can be + // returned. + // kConfigChange: Indicates that the next buffer requires a config change. + enum Status { + kSuccess, + kNeedBuffer, + kConfigChange, + }; + explicit SourceBufferStream(const AudioDecoderConfig& audio_config); explicit SourceBufferStream(const VideoDecoderConfig& video_config); @@ -63,9 +75,10 @@ class MEDIA_EXPORT SourceBufferStream { // Seek() has not been called yet. // |out_buffer|'s timestamp may be earlier than the |timestamp| passed to // the last Seek() call. - // Returns true if |out_buffer| is filled with a valid buffer, false if - // there is not enough data buffered to fulfill the request. - bool GetNextBuffer(scoped_refptr<StreamParserBuffer>* out_buffer); + // Returns kSuccess if |out_buffer| is filled with a valid buffer, kNeedBuffer + // if there is not enough data buffered to fulfill the request, and + // kConfigChange if the next buffer requires a config change. + Status GetNextBuffer(scoped_refptr<StreamParserBuffer>* out_buffer); // Returns a list of the buffered time ranges. Ranges<base::TimeDelta> GetBufferedTime() const; @@ -73,12 +86,16 @@ class MEDIA_EXPORT SourceBufferStream { // Returns true if we don't have any ranges or the last range is selected. bool IsEndSelected() const; - const AudioDecoderConfig& GetCurrentAudioDecoderConfig() { - return audio_config_; - } - const VideoDecoderConfig& GetCurrentVideoDecoderConfig() { - return video_config_; - } + const AudioDecoderConfig& GetCurrentAudioDecoderConfig(); + const VideoDecoderConfig& GetCurrentVideoDecoderConfig(); + + // Notifies this object that the audio config has changed and buffers in + // future Append() calls should be associated with this new config. + bool UpdateAudioConfig(const AudioDecoderConfig& config); + + // Notifies this object that the video config has changed and buffers in + // future Append() calls should be associated with this new config. + bool UpdateVideoConfig(const VideoDecoderConfig& config); // Returns the largest distance between two adjacent buffers in this stream, // or an estimate if no two adjacent buffers have been appended to the stream @@ -172,11 +189,33 @@ class MEDIA_EXPORT SourceBufferStream { // Measures the distances between buffer timestamps and tracks the max. void UpdateMaxInterbufferDistance(const BufferQueue& buffers); + // Sets the config ID for each buffer to |append_config_index_|. + void SetConfigIds(const BufferQueue& buffers); + + // Called to complete a config change. Updates |current_config_index_| to + // match the index of the next buffer. Calling this method causes + // GetNextBuffer() to stop returning kConfigChange and start returning + // kSuccess. + void CompleteConfigChange(); + // List of disjoint buffered ranges, ordered by start time. RangeList ranges_; - AudioDecoderConfig audio_config_; - VideoDecoderConfig video_config_; + // Indicates which decoder config is being used by the decoder. + // GetNextBuffer() is only allows to return buffers that have a + // config ID that matches this index. If there is a mismatch then + // it must signal that a config change is needed. + int current_config_index_; + + // Indicates which decoder config to associate with new buffers + // being appended. Each new buffer appended has its config ID set + // to the value of this field. + int append_config_index_; + + // Holds the audio/video configs for this stream. |current_config_index_| + // and |append_config_index_| represent indexes into one of these vectors. + std::vector<AudioDecoderConfig*> audio_configs_; + std::vector<VideoDecoderConfig*> video_configs_; // The starting time of the stream. base::TimeDelta stream_start_time_; diff --git a/media/filters/source_buffer_stream_unittest.cc b/media/filters/source_buffer_stream_unittest.cc index 310af01..35161c1 100644 --- a/media/filters/source_buffer_stream_unittest.cc +++ b/media/filters/source_buffer_stream_unittest.cc @@ -115,7 +115,7 @@ class SourceBufferStreamTest : public testing::Test { int current_position = starting_position; for (; current_position <= ending_position; current_position++) { scoped_refptr<StreamParserBuffer> buffer; - if (!stream_->GetNextBuffer(&buffer)) + if (stream_->GetNextBuffer(&buffer) == SourceBufferStream::kNeedBuffer) break; if (expect_keyframe && current_position == starting_position) @@ -139,7 +139,7 @@ class SourceBufferStreamTest : public testing::Test { void CheckNoNextBuffer() { scoped_refptr<StreamParserBuffer> buffer; - EXPECT_FALSE(stream_->GetNextBuffer(&buffer)); + EXPECT_EQ(stream_->GetNextBuffer(&buffer), SourceBufferStream::kNeedBuffer); } base::TimeDelta frame_duration() const { return frame_duration_; } @@ -1163,7 +1163,7 @@ TEST_F(SourceBufferStreamTest, Seek_StartOfSegment) { scoped_refptr<StreamParserBuffer> buffer; // GetNextBuffer() should return the next buffer at position (5 + |bump|). - EXPECT_TRUE(stream_->GetNextBuffer(&buffer)); + EXPECT_EQ(stream_->GetNextBuffer(&buffer), SourceBufferStream::kSuccess); EXPECT_EQ(buffer->GetDecodeTimestamp(), 5 * frame_duration() + bump); // Check rest of buffers. @@ -1177,7 +1177,7 @@ TEST_F(SourceBufferStreamTest, Seek_StartOfSegment) { NewSegmentAppend_OffsetFirstBuffer(15, 5, bump); // GetNextBuffer() should return the next buffer at position (15 + |bump|). - EXPECT_TRUE(stream_->GetNextBuffer(&buffer)); + EXPECT_EQ(stream_->GetNextBuffer(&buffer), SourceBufferStream::kSuccess); EXPECT_EQ(buffer->GetDecodeTimestamp(), 15 * frame_duration() + bump); // Check rest of buffers. @@ -1509,7 +1509,7 @@ TEST_F(SourceBufferStreamTest, PresentationTimestampIndependence) { // Check for IBB...BBP pattern. for (int i = 0; i < 20; i++) { scoped_refptr<StreamParserBuffer> buffer; - ASSERT_TRUE(stream_->GetNextBuffer(&buffer)); + ASSERT_EQ(stream_->GetNextBuffer(&buffer), SourceBufferStream::kSuccess); if (buffer->IsKeyframe()) { EXPECT_EQ(buffer->GetTimestamp(), buffer->GetDecodeTimestamp()); diff --git a/media/test/data/bear-320x240-manifest.js b/media/test/data/bear-320x240-manifest.js new file mode 100644 index 0000000..9e6602a --- /dev/null +++ b/media/test/data/bear-320x240-manifest.js @@ -0,0 +1,17 @@ +// 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. +{ + duration: 2.744000, + type: 'video/webm; codecs="vp8, vorbis"', + init: { offset: 0, size: 4370}, + media: [ + { offset: 4370, size: 40778, timecode: 0.000000 }, + { offset: 45148, size: 27589, timecode: 0.396000 }, + { offset: 72737, size: 28183, timecode: 0.779000 }, + { offset: 100920, size: 31600, timecode: 1.197000 }, + { offset: 132520, size: 33922, timecode: 1.589000 }, + { offset: 166442, size: 30587, timecode: 1.987000 }, + { offset: 197029, size: 22079, timecode: 2.400000 }, + ] +} diff --git a/media/test/data/bear-640x360-manifest.js b/media/test/data/bear-640x360-manifest.js new file mode 100644 index 0000000..9f34488 --- /dev/null +++ b/media/test/data/bear-640x360-manifest.js @@ -0,0 +1,16 @@ +// 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. +{ + duration: 2.740000, + type: 'video/webm; codecs="vp8, vorbis"', + init: { offset: 0, size: 4459}, + media: [ + { offset: 4459, size: 50714, timecode: 0.000000 }, + { offset: 55173, size: 19021, timecode: 0.477000 }, + { offset: 74194, size: 19810, timecode: 0.988000 }, + { offset: 94004, size: 21706, timecode: 1.496000 }, + { offset: 115710, size: 20249, timecode: 1.990000 }, + { offset: 135959, size: 9946, timecode: 2.489000 }, + ] +} diff --git a/media/webm/webm_info_parser.h b/media/webm/webm_info_parser.h index 2cc7879..ab5de43 100644 --- a/media/webm/webm_info_parser.h +++ b/media/webm/webm_info_parser.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. @@ -6,12 +6,13 @@ #define MEDIA_WEBM_WEBM_INFO_PARSER_H_ #include "base/compiler_specific.h" +#include "media/base/media_export.h" #include "media/webm/webm_parser.h" namespace media { // Parser for WebM Info element. -class WebMInfoParser : public WebMParserClient { +class MEDIA_EXPORT WebMInfoParser : public WebMParserClient { public: WebMInfoParser(); virtual ~WebMInfoParser(); diff --git a/media/webm/webm_stream_parser.cc b/media/webm/webm_stream_parser.cc index d141bfd..e9c8233 100644 --- a/media/webm/webm_stream_parser.cc +++ b/media/webm/webm_stream_parser.cc @@ -235,7 +235,8 @@ bool WebMStreamParser::Parse(const uint8* buf, int size) { int cur_size = 0; byte_queue_.Peek(&cur, &cur_size); - do { + while (cur_size > 0) { + State oldState = state_; switch (state_) { case kParsingHeaders: result = ParseInfoAndTracks(cur, cur_size); @@ -255,10 +256,14 @@ bool WebMStreamParser::Parse(const uint8* buf, int size) { return false; } + if (state_ == oldState && result == 0) + break; + + DCHECK_GE(result, 0); cur += result; cur_size -= result; bytes_parsed += result; - } while (result > 0 && cur_size > 0); + } byte_queue_.Pop(bytes_parsed); return true; |