summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoracolwell@chromium.org <acolwell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-26 16:38:03 +0000
committeracolwell@chromium.org <acolwell@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-26 16:38:03 +0000
commita93866519172cc8993583d9116ce16eec01d532b (patch)
treee617d768783948cabd3e0363cfa6bfe45a6b4ee0
parent2b2d9393845b8bb60f843d14ab0b59c4bc3949db (diff)
downloadchromium_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.cc10
-rw-r--r--media/base/audio_decoder_config.h4
-rw-r--r--media/base/byte_queue.h5
-rw-r--r--media/base/stream_parser_buffer.cc11
-rw-r--r--media/base/stream_parser_buffer.h9
-rw-r--r--media/base/video_decoder_config.cc12
-rw-r--r--media/base/video_decoder_config.h4
-rw-r--r--media/filters/chunk_demuxer.cc66
-rw-r--r--media/filters/chunk_demuxer_unittest.cc248
-rw-r--r--media/filters/source_buffer_stream.cc151
-rw-r--r--media/filters/source_buffer_stream.h61
-rw-r--r--media/filters/source_buffer_stream_unittest.cc10
-rw-r--r--media/test/data/bear-320x240-manifest.js17
-rw-r--r--media/test/data/bear-640x360-manifest.js16
-rw-r--r--media/webm/webm_info_parser.h5
-rw-r--r--media/webm/webm_stream_parser.cc9
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;