summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--media/filters/chunk_demuxer_unittest.cc14
-rw-r--r--media/filters/ffmpeg_video_decoder.cc85
-rw-r--r--media/filters/ffmpeg_video_decoder.h4
-rw-r--r--media/filters/pipeline_integration_test.cc113
-rw-r--r--media/filters/source_buffer_stream.cc45
-rw-r--r--media/filters/source_buffer_stream.h8
-rw-r--r--media/filters/source_buffer_stream_unittest.cc103
-rw-r--r--media/mp4/mp4_stream_parser.cc9
-rw-r--r--media/mp4/mp4_stream_parser_unittest.cc5
9 files changed, 281 insertions, 105 deletions
diff --git a/media/filters/chunk_demuxer_unittest.cc b/media/filters/chunk_demuxer_unittest.cc
index 2d04f51..c1cec58 100644
--- a/media/filters/chunk_demuxer_unittest.cc
+++ b/media/filters/chunk_demuxer_unittest.cc
@@ -2056,10 +2056,6 @@ TEST_F(ChunkDemuxerTest, TestConfigChange_Video) {
ASSERT_EQ(status, DemuxerStream::kConfigChanged);
EXPECT_EQ(last_timestamp.InMilliseconds(), 501);
- // 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());
@@ -2073,9 +2069,6 @@ TEST_F(ChunkDemuxerTest, TestConfigChange_Video) {
ASSERT_EQ(status, DemuxerStream::kConfigChanged);
EXPECT_EQ(last_timestamp.InMilliseconds(), 793);
- // 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()));
@@ -2110,10 +2103,6 @@ TEST_F(ChunkDemuxerTest, TestConfigChange_Audio) {
ASSERT_EQ(status, DemuxerStream::kConfigChanged);
EXPECT_EQ(last_timestamp.InMilliseconds(), 524);
- // 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());
@@ -2127,9 +2116,6 @@ TEST_F(ChunkDemuxerTest, TestConfigChange_Audio) {
ASSERT_EQ(status, DemuxerStream::kConfigChanged);
EXPECT_EQ(last_timestamp.InMilliseconds(), 759);
- // 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()));
diff --git a/media/filters/ffmpeg_video_decoder.cc b/media/filters/ffmpeg_video_decoder.cc
index 6c9387a..7bfd4e8 100644
--- a/media/filters/ffmpeg_video_decoder.cc
+++ b/media/filters/ffmpeg_video_decoder.cc
@@ -158,44 +158,13 @@ void FFmpegVideoDecoder::Initialize(const scoped_refptr<DemuxerStream>& stream,
demuxer_stream_ = stream;
statistics_cb_ = statistics_cb;
- const VideoDecoderConfig& config = stream->video_decoder_config();
-
- // TODO(scherkus): this check should go in Pipeline prior to creating
- // decoder objects.
- if (!config.IsValidConfig()) {
- DLOG(ERROR) << "Invalid video stream - " << config.AsHumanReadableString();
- status_cb.Run(PIPELINE_ERROR_DECODE);
- return;
- }
-
- // Initialize AVCodecContext structure.
- codec_context_ = avcodec_alloc_context3(NULL);
- VideoDecoderConfigToAVCodecContext(config, codec_context_);
-
- // Enable motion vector search (potentially slow), strong deblocking filter
- // for damaged macroblocks, and set our error detection sensitivity.
- codec_context_->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK;
- codec_context_->err_recognition = AV_EF_CAREFUL;
- codec_context_->thread_count = GetThreadCount(codec_context_->codec_id);
- codec_context_->opaque = this;
- codec_context_->flags |= CODEC_FLAG_EMU_EDGE;
- codec_context_->get_buffer = GetVideoBufferImpl;
- codec_context_->release_buffer = ReleaseVideoBufferImpl;
-
- AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id);
- if (!codec) {
- status_cb.Run(PIPELINE_ERROR_DECODE);
- return;
- }
-
- if (avcodec_open2(codec_context_, codec, NULL) < 0) {
+ if (!ConfigureDecoder()) {
status_cb.Run(PIPELINE_ERROR_DECODE);
return;
}
// Success!
state_ = kNormal;
- av_frame_ = avcodec_alloc_frame();
status_cb.Run(PIPELINE_OK);
}
@@ -320,10 +289,18 @@ void FFmpegVideoDecoder::DoDecryptOrDecodeBuffer(
return;
}
- if (status != DemuxerStream::kOk) {
- Status decoder_status =
- (status == DemuxerStream::kAborted) ? kOk : kDecodeError;
- base::ResetAndReturn(&read_cb_).Run(decoder_status, NULL);
+ if (status == DemuxerStream::kAborted) {
+ base::ResetAndReturn(&read_cb_).Run(kOk, NULL);
+ return;
+ }
+
+ if (status == DemuxerStream::kConfigChanged) {
+ if (!ConfigureDecoder()) {
+ base::ResetAndReturn(&read_cb_).Run(kDecodeError, NULL);
+ return;
+ }
+
+ ReadFromDemuxerStream();
return;
}
@@ -524,4 +501,40 @@ void FFmpegVideoDecoder::ReleaseFFmpegResources() {
}
}
+bool FFmpegVideoDecoder::ConfigureDecoder() {
+ const VideoDecoderConfig& config = demuxer_stream_->video_decoder_config();
+
+ if (!config.IsValidConfig()) {
+ DLOG(ERROR) << "Invalid video stream - " << config.AsHumanReadableString();
+ return false;
+ }
+
+ // Release existing decoder resources if necessary.
+ ReleaseFFmpegResources();
+
+ // Initialize AVCodecContext structure.
+ codec_context_ = avcodec_alloc_context3(NULL);
+ VideoDecoderConfigToAVCodecContext(config, codec_context_);
+
+ // Enable motion vector search (potentially slow), strong deblocking filter
+ // for damaged macroblocks, and set our error detection sensitivity.
+ codec_context_->error_concealment = FF_EC_GUESS_MVS | FF_EC_DEBLOCK;
+ codec_context_->err_recognition = AV_EF_CAREFUL;
+ codec_context_->thread_count = GetThreadCount(codec_context_->codec_id);
+ codec_context_->opaque = this;
+ codec_context_->flags |= CODEC_FLAG_EMU_EDGE;
+ codec_context_->get_buffer = GetVideoBufferImpl;
+ codec_context_->release_buffer = ReleaseVideoBufferImpl;
+
+ AVCodec* codec = avcodec_find_decoder(codec_context_->codec_id);
+ if (!codec)
+ return false;
+
+ if (avcodec_open2(codec_context_, codec, NULL) < 0)
+ return false;
+
+ av_frame_ = avcodec_alloc_frame();
+ return true;
+}
+
} // namespace media
diff --git a/media/filters/ffmpeg_video_decoder.h b/media/filters/ffmpeg_video_decoder.h
index a48e3b4..340864d 100644
--- a/media/filters/ffmpeg_video_decoder.h
+++ b/media/filters/ffmpeg_video_decoder.h
@@ -80,6 +80,10 @@ class MEDIA_EXPORT FFmpegVideoDecoder : public VideoDecoder {
bool Decode(const scoped_refptr<DecoderBuffer>& buffer,
scoped_refptr<VideoFrame>* video_frame);
+ // Handles (re-)initializing the decoder with a (new) config.
+ // Returns true if initialization was successful.
+ bool ConfigureDecoder();
+
// Releases resources associated with |codec_context_| and |av_frame_|
// and resets them to NULL.
void ReleaseFFmpegResources();
diff --git a/media/filters/pipeline_integration_test.cc b/media/filters/pipeline_integration_test.cc
index c5353f3..3f00b09 100644
--- a/media/filters/pipeline_integration_test.cc
+++ b/media/filters/pipeline_integration_test.cc
@@ -5,6 +5,7 @@
#include "media/filters/pipeline_integration_test_base.h"
#include "base/bind.h"
+#include "base/string_util.h"
#include "media/base/decoder_buffer.h"
#include "media/base/decryptor_client.h"
#include "media/base/test_data_util.h"
@@ -17,25 +18,34 @@ static const char kSourceId[] = "SourceId";
static const char kClearKeySystem[] = "org.w3.clearkey";
static const uint8 kInitData[] = { 0x69, 0x6e, 0x69, 0x74 };
+static const char kWebM[] = "video/webm; codecs=\"vp8,vorbis\"";
+static const char kAudioOnlyWebM[] = "video/webm; codecs=\"vorbis\"";
+static const char kVideoOnlyWebM[] = "video/webm; codecs=\"vp8\"";
+static const char kMP4[] = "video/mp4; codecs=\"avc1.4D4041,mp4a.40.2\"";
+
// Key used to encrypt video track in test file "bear-320x240-encrypted.webm".
static const uint8 kSecretKey[] = {
0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c
};
+static const int kAppendWholeFile = -1;
+
// 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,
- bool has_audio, bool has_video)
+ MockMediaSource(const std::string& filename, const std::string& mimetype,
+ int initial_append_size)
: url_(GetTestDataURL(filename)),
current_position_(0),
initial_append_size_(initial_append_size),
- has_audio_(has_audio),
- has_video_(has_video) {
+ mimetype_(mimetype) {
file_data_ = ReadTestDataFile(filename);
+ if (initial_append_size_ == kAppendWholeFile)
+ initial_append_size_ = file_data_->GetDataSize();
+
DCHECK_GT(initial_append_size_, 0);
DCHECK_LE(initial_append_size_, file_data_->GetDataSize());
}
@@ -67,6 +77,13 @@ class MockMediaSource : public ChunkDemuxerClient {
current_position_ += size;
}
+ void AppendAtTime(const base::TimeDelta& timestampOffset,
+ const uint8* pData, int size) {
+ CHECK(chunk_demuxer_->SetTimestampOffset(kSourceId, timestampOffset));
+ CHECK(chunk_demuxer_->AppendData(kSourceId, pData, size));
+ CHECK(chunk_demuxer_->SetTimestampOffset(kSourceId, base::TimeDelta()));
+ }
+
void EndOfStream() {
chunk_demuxer_->EndOfStream(PIPELINE_OK);
}
@@ -81,14 +98,15 @@ class MockMediaSource : public ChunkDemuxerClient {
virtual void DemuxerOpened(ChunkDemuxer* demuxer) {
chunk_demuxer_ = demuxer;
+ size_t semicolon = mimetype_.find(";");
+ std::string type = mimetype_.substr(0, semicolon);
+ size_t quote1 = mimetype_.find("\"");
+ size_t quote2 = mimetype_.find("\"", quote1 + 1);
+ std::string codecStr = mimetype_.substr(quote1 + 1, quote2 - quote1 - 1);
std::vector<std::string> codecs;
- if (has_audio_)
- codecs.push_back("vorbis");
+ Tokenize(codecStr, ",", &codecs);
- if (has_video_)
- codecs.push_back("vp8");
-
- chunk_demuxer_->AddId(kSourceId, "video/webm", codecs);
+ CHECK_EQ(chunk_demuxer_->AddId(kSourceId, type, codecs), ChunkDemuxer::kOk);
AppendData(initial_append_size_);
}
@@ -109,8 +127,7 @@ class MockMediaSource : public ChunkDemuxerClient {
scoped_refptr<DecoderBuffer> file_data_;
int current_position_;
int initial_append_size_;
- bool has_audio_;
- bool has_video_;
+ std::string mimetype_;
scoped_refptr<ChunkDemuxer> chunk_demuxer_;
DecryptorClient* decryptor_client_;
};
@@ -219,14 +236,13 @@ class PipelineIntegrationTest
// seek happens while there is a pending read on the ChunkDemuxer
// and no data is available.
bool TestSeekDuringRead(const std::string& filename,
+ const std::string& mimetype,
int initial_append_size,
base::TimeDelta start_seek_time,
base::TimeDelta seek_time,
int seek_file_position,
- int seek_append_size,
- bool has_audio,
- bool has_video) {
- MockMediaSource source(filename, initial_append_size, has_audio, has_video);
+ int seek_append_size) {
+ MockMediaSource source(filename, mimetype, initial_append_size);
StartPipelineWithMediaSource(&source);
if (pipeline_status_ != PIPELINE_OK)
@@ -269,7 +285,7 @@ TEST_F(PipelineIntegrationTest, BasicPlaybackHashed) {
}
TEST_F(PipelineIntegrationTest, BasicPlayback_MediaSource) {
- MockMediaSource source("bear-320x240.webm", 219229, true, true);
+ MockMediaSource source("bear-320x240.webm", kWebM, 219229);
StartPipelineWithMediaSource(&source);
source.EndOfStream();
ASSERT_EQ(pipeline_status_, PIPELINE_OK);
@@ -285,6 +301,57 @@ TEST_F(PipelineIntegrationTest, BasicPlayback_MediaSource) {
Stop();
}
+TEST_F(PipelineIntegrationTest, MediaSource_ConfigChange_WebM) {
+ MockMediaSource source("bear-320x240-16x9-aspect.webm", kWebM,
+ kAppendWholeFile);
+ StartPipelineWithMediaSource(&source);
+
+ scoped_refptr<DecoderBuffer> second_file =
+ ReadTestDataFile("bear-640x360.webm");
+
+ source.AppendAtTime(base::TimeDelta::FromSeconds(2),
+ second_file->GetData(), second_file->GetDataSize());
+
+ source.EndOfStream();
+ ASSERT_EQ(pipeline_status_, PIPELINE_OK);
+
+ EXPECT_EQ(pipeline_->GetBufferedTimeRanges().size(), 1u);
+ EXPECT_EQ(pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds(), 0);
+ EXPECT_EQ(pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds(), 4763);
+
+ Play();
+
+ ASSERT_TRUE(WaitUntilOnEnded());
+ source.Abort();
+ Stop();
+}
+
+#if defined(GOOGLE_CHROME_BUILD) || defined(USE_PROPRIETARY_CODECS)
+TEST_F(PipelineIntegrationTest, MediaSource_ConfigChange_MP4) {
+ MockMediaSource source("bear.640x360_dash.mp4", kMP4, kAppendWholeFile);
+ StartPipelineWithMediaSource(&source);
+
+ scoped_refptr<DecoderBuffer> second_file =
+ ReadTestDataFile("bear.1280x720_dash.mp4");
+
+ source.AppendAtTime(base::TimeDelta::FromSeconds(2),
+ second_file->GetData(), second_file->GetDataSize());
+
+ source.EndOfStream();
+ ASSERT_EQ(pipeline_status_, PIPELINE_OK);
+
+ EXPECT_EQ(pipeline_->GetBufferedTimeRanges().size(), 1u);
+ EXPECT_EQ(pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds(), 0);
+ EXPECT_EQ(pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds(), 4736);
+
+ Play();
+
+ ASSERT_TRUE(WaitUntilOnEnded());
+ source.Abort();
+ Stop();
+}
+#endif
+
TEST_F(PipelineIntegrationTest, BasicPlayback_16x9AspectRatio) {
ASSERT_TRUE(Start(GetTestDataURL("bear-320x240-16x9-aspect.webm"),
PIPELINE_OK));
@@ -293,7 +360,7 @@ TEST_F(PipelineIntegrationTest, BasicPlayback_16x9AspectRatio) {
}
TEST_F(PipelineIntegrationTest, EncryptedPlayback) {
- MockMediaSource source("bear-320x240-encrypted.webm", 220788, true, true);
+ MockMediaSource source("bear-320x240-encrypted.webm", kWebM, 220788);
FakeDecryptorClient encrypted_media;
StartPipelineWithEncryptedMedia(&source, &encrypted_media);
@@ -353,18 +420,20 @@ TEST_F(PipelineIntegrationTest, DISABLED_SeekWhilePlaying) {
// Verify audio decoder & renderer can handle aborted demuxer reads.
TEST_F(PipelineIntegrationTest, ChunkDemuxerAbortRead_AudioOnly) {
- ASSERT_TRUE(TestSeekDuringRead("bear-320x240-audio-only.webm", 8192,
+ ASSERT_TRUE(TestSeekDuringRead("bear-320x240-audio-only.webm", kAudioOnlyWebM,
+ 8192,
base::TimeDelta::FromMilliseconds(464),
base::TimeDelta::FromMilliseconds(617),
- 0x10CA, 19730, true, false));
+ 0x10CA, 19730));
}
// Verify video decoder & renderer can handle aborted demuxer reads.
TEST_F(PipelineIntegrationTest, ChunkDemuxerAbortRead_VideoOnly) {
- ASSERT_TRUE(TestSeekDuringRead("bear-320x240-video-only.webm", 32768,
+ ASSERT_TRUE(TestSeekDuringRead("bear-320x240-video-only.webm", kVideoOnlyWebM,
+ 32768,
base::TimeDelta::FromMilliseconds(200),
base::TimeDelta::FromMilliseconds(1668),
- 0x1C896, 65536, false, true));
+ 0x1C896, 65536));
}
} // namespace media
diff --git a/media/filters/source_buffer_stream.cc b/media/filters/source_buffer_stream.cc
index c768423..f66cdb9 100644
--- a/media/filters/source_buffer_stream.cc
+++ b/media/filters/source_buffer_stream.cc
@@ -270,8 +270,6 @@ namespace media {
SourceBufferStream::SourceBufferStream(const AudioDecoderConfig& audio_config)
: 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()),
@@ -281,16 +279,16 @@ SourceBufferStream::SourceBufferStream(const AudioDecoderConfig& audio_config)
new_media_segment_(false),
last_buffer_timestamp_(kNoTimestamp()),
max_interbuffer_distance_(kNoTimestamp()),
- memory_limit_(kDefaultAudioMemoryLimit) {
- audio_configs_[0] = new AudioDecoderConfig();
- audio_configs_[0]->CopyFrom(audio_config);
+ memory_limit_(kDefaultAudioMemoryLimit),
+ config_change_pending_(false) {
+ DCHECK(audio_config.IsValidConfig());
+ audio_configs_.push_back(new AudioDecoderConfig());
+ audio_configs_.back()->CopyFrom(audio_config);
}
SourceBufferStream::SourceBufferStream(const VideoDecoderConfig& video_config)
: 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()),
@@ -300,9 +298,11 @@ SourceBufferStream::SourceBufferStream(const VideoDecoderConfig& video_config)
new_media_segment_(false),
last_buffer_timestamp_(kNoTimestamp()),
max_interbuffer_distance_(kNoTimestamp()),
- memory_limit_(kDefaultVideoMemoryLimit) {
- video_configs_[0] = new VideoDecoderConfig();
- video_configs_[0]->CopyFrom(video_config);
+ memory_limit_(kDefaultVideoMemoryLimit),
+ config_change_pending_(false) {
+ DCHECK(video_config.IsValidConfig());
+ video_configs_.push_back(new VideoDecoderConfig());
+ video_configs_.back()->CopyFrom(video_config);
}
SourceBufferStream::~SourceBufferStream() {
@@ -736,6 +736,7 @@ void SourceBufferStream::Seek(base::TimeDelta timestamp) {
DCHECK(timestamp >= stream_start_time_);
SetSelectedRange(NULL);
track_buffer_.clear();
+ config_change_pending_ = false;
if (ShouldSeekToStartOfBuffered(timestamp)) {
SetSelectedRange(ranges_.front());
@@ -767,9 +768,13 @@ bool SourceBufferStream::IsSeekPending() const {
SourceBufferStream::Status SourceBufferStream::GetNextBuffer(
scoped_refptr<StreamParserBuffer>* out_buffer) {
+ CHECK(!config_change_pending_);
+
if (!track_buffer_.empty()) {
- if (track_buffer_.front()->GetConfigId() != current_config_index_)
+ if (track_buffer_.front()->GetConfigId() != current_config_index_) {
+ config_change_pending_ = true;
return kConfigChange;
+ }
*out_buffer = track_buffer_.front();
track_buffer_.pop_front();
@@ -779,8 +784,10 @@ SourceBufferStream::Status SourceBufferStream::GetNextBuffer(
if (!selected_range_ || !selected_range_->HasNextBuffer())
return kNeedBuffer;
- if (selected_range_->GetNextConfigId() != current_config_index_)
+ if (selected_range_->GetNextConfigId() != current_config_index_) {
+ config_change_pending_ = true;
return kConfigChange;
+ }
CHECK(selected_range_->GetNextBuffer(out_buffer));
return kSuccess;
@@ -852,12 +859,14 @@ bool SourceBufferStream::IsEndSelected() const {
}
const AudioDecoderConfig& SourceBufferStream::GetCurrentAudioDecoderConfig() {
- CompleteConfigChange();
+ if (config_change_pending_)
+ CompleteConfigChange();
return *audio_configs_[current_config_index_];
}
const VideoDecoderConfig& SourceBufferStream::GetCurrentVideoDecoderConfig() {
- CompleteConfigChange();
+ if (config_change_pending_)
+ CompleteConfigChange();
return *video_configs_[current_config_index_];
}
@@ -933,15 +942,15 @@ bool SourceBufferStream::UpdateVideoConfig(const VideoDecoderConfig& config) {
}
void SourceBufferStream::CompleteConfigChange() {
+ config_change_pending_ = false;
+
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();
+ if (selected_range_ && selected_range_->HasNextBuffer())
+ current_config_index_ = selected_range_->GetNextConfigId();
}
SourceBufferRange::SourceBufferRange(
diff --git a/media/filters/source_buffer_stream.h b/media/filters/source_buffer_stream.h
index e67a84d..0e08327 100644
--- a/media/filters/source_buffer_stream.h
+++ b/media/filters/source_buffer_stream.h
@@ -258,9 +258,15 @@ class MEDIA_EXPORT SourceBufferStream {
// Stores the largest distance between two adjacent buffers in this stream.
base::TimeDelta max_interbuffer_distance_;
-// The maximum amount of data in bytes the stream will keep in memory.
+ // The maximum amount of data in bytes the stream will keep in memory.
int memory_limit_;
+ // Indicates that a kConfigChanged status has been reported by GetNextBuffer()
+ // and GetCurrentXXXDecoderConfig() must be called to update the current
+ // config. GetNextBuffer() must not be called again until
+ // GetCurrentXXXDecoderConfig() has been called.
+ bool config_change_pending_;
+
DISALLOW_COPY_AND_ASSIGN(SourceBufferStream);
};
diff --git a/media/filters/source_buffer_stream_unittest.cc b/media/filters/source_buffer_stream_unittest.cc
index 4033869..1f46d0f 100644
--- a/media/filters/source_buffer_stream_unittest.cc
+++ b/media/filters/source_buffer_stream_unittest.cc
@@ -15,10 +15,14 @@ static const int kDefaultKeyframesPerSecond = 6;
static const uint8 kDataA = 0x11;
static const uint8 kDataB = 0x33;
static const int kDataSize = 1;
+static const gfx::Size kCodedSize(320, 240);
class SourceBufferStreamTest : public testing::Test {
protected:
SourceBufferStreamTest() {
+ config_.Initialize(kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN,
+ VideoFrame::YV12, kCodedSize, gfx::Rect(kCodedSize),
+ kCodedSize, NULL, 0, false);
stream_.reset(new SourceBufferStream(config_));
SetStreamInfo(kDefaultFramesPerSecond, kDefaultKeyframesPerSecond);
@@ -119,7 +123,10 @@ 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) == SourceBufferStream::kNeedBuffer)
+ SourceBufferStream::Status status = stream_->GetNextBuffer(&buffer);
+
+ EXPECT_NE(status, SourceBufferStream::kConfigChange);
+ if (status != SourceBufferStream::kSuccess)
break;
if (expect_keyframe && current_position == starting_position)
@@ -146,6 +153,13 @@ class SourceBufferStreamTest : public testing::Test {
EXPECT_EQ(stream_->GetNextBuffer(&buffer), SourceBufferStream::kNeedBuffer);
}
+ void CheckConfig(const VideoDecoderConfig& config) {
+ const VideoDecoderConfig& actual = stream_->GetCurrentVideoDecoderConfig();
+ EXPECT_TRUE(actual.Matches(config))
+ << "Expected: " << config.AsHumanReadableString()
+ << "\nActual: " << actual.AsHumanReadableString();
+ }
+
base::TimeDelta frame_duration() const { return frame_duration_; }
scoped_ptr<SourceBufferStream> stream_;
@@ -1822,6 +1836,93 @@ TEST_F(SourceBufferStreamTest, DISABLED_GarbageCollection_WaitingForKeyframe) {
CheckExpectedRanges("{ [15,15) [20,28) }");
}
+TEST_F(SourceBufferStreamTest, ConfigChange_Basic) {
+ gfx::Size kNewCodedSize(kCodedSize.width() * 2, kCodedSize.height() * 2);
+ VideoDecoderConfig new_config(
+ kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, VideoFrame::YV12, kNewCodedSize,
+ gfx::Rect(kNewCodedSize), kNewCodedSize, NULL, 0);
+ ASSERT_FALSE(new_config.Matches(config_));
+
+ Seek(0);
+ CheckConfig(config_);
+
+ // Append 5 buffers at positions 0 through 4
+ NewSegmentAppend(0, 5, &kDataA);
+
+ CheckConfig(config_);
+
+ // Signal a config change.
+ stream_->UpdateVideoConfig(new_config);
+
+ // Make sure updating the config doesn't change anything since new_config
+ // should not be associated with the buffer GetNextBuffer() will return.
+ CheckConfig(config_);
+
+ // Append 5 buffers at positions 5 through 9.
+ NewSegmentAppend(5, 5, &kDataB);
+
+ // Consume the buffers associated with the initial config.
+ scoped_refptr<StreamParserBuffer> buffer;
+ for (int i = 0; i < 5; i++) {
+ EXPECT_EQ(stream_->GetNextBuffer(&buffer), SourceBufferStream::kSuccess);
+ CheckConfig(config_);
+ }
+
+ // Verify the next attempt to get a buffer will signal that a config change
+ // has happened.
+ EXPECT_EQ(stream_->GetNextBuffer(&buffer), SourceBufferStream::kConfigChange);
+
+ // Verify that the new config is now returned.
+ CheckConfig(new_config);
+
+ // Consume the remaining buffers associated with the new config.
+ for (int i = 0; i < 5; i++) {
+ CheckConfig(new_config);
+ EXPECT_EQ(stream_->GetNextBuffer(&buffer), SourceBufferStream::kSuccess);
+ }
+}
+
+TEST_F(SourceBufferStreamTest, ConfigChange_Seek) {
+ scoped_refptr<StreamParserBuffer> buffer;
+ gfx::Size kNewCodedSize(kCodedSize.width() * 2, kCodedSize.height() * 2);
+ VideoDecoderConfig new_config(
+ kCodecVP8, VIDEO_CODEC_PROFILE_UNKNOWN, VideoFrame::YV12, kNewCodedSize,
+ gfx::Rect(kNewCodedSize), kNewCodedSize, NULL, 0);
+
+ Seek(0);
+ NewSegmentAppend(0, 5, &kDataA);
+ stream_->UpdateVideoConfig(new_config);
+ NewSegmentAppend(5, 5, &kDataB);
+
+ // Seek to the start of the buffers with the new config and make sure a
+ // config change is signalled.
+ CheckConfig(config_);
+ Seek(5);
+ CheckConfig(config_);
+ EXPECT_EQ(stream_->GetNextBuffer(&buffer), SourceBufferStream::kConfigChange);
+ CheckConfig(new_config);
+ CheckExpectedBuffers(5, 9, &kDataB);
+
+
+ // Seek to the start which has a different config. Don't fetch any buffers and
+ // seek back to buffers with the current config. Make sure a config change
+ // isn't signalled in this case.
+ CheckConfig(new_config);
+ Seek(0);
+ Seek(7);
+ CheckExpectedBuffers(5, 9, &kDataB);
+
+
+ // Seek to the start and make sure a config change is signalled.
+ CheckConfig(new_config);
+ Seek(0);
+ CheckConfig(new_config);
+ EXPECT_EQ(stream_->GetNextBuffer(&buffer), SourceBufferStream::kConfigChange);
+ CheckConfig(config_);
+ CheckExpectedBuffers(0, 4, &kDataA);
+}
+
+
// TODO(vrk): Add unit tests where keyframes are unaligned between streams.
// (crbug.com/133557)
diff --git a/media/mp4/mp4_stream_parser.cc b/media/mp4/mp4_stream_parser.cc
index 1aa8c70..b025b7a 100644
--- a/media/mp4/mp4_stream_parser.cc
+++ b/media/mp4/mp4_stream_parser.cc
@@ -237,14 +237,7 @@ bool MP4StreamParser::ParseMoov(BoxReader* reader) {
}
}
- // TODO(strobe): For now, we avoid sending new configs on a new
- // reinitialization segment, and instead simply embed the updated parameter
- // sets into the video stream. The conditional should be removed when
- // http://crbug.com/122913 is fixed. (We detect whether we've already sent
- // configs by looking at init_cb_ instead of config_cb_, because init_cb_
- // should only be fired once even after that bug is fixed.)
- if (!init_cb_.is_null())
- RCHECK(config_cb_.Run(audio_config, video_config));
+ RCHECK(config_cb_.Run(audio_config, video_config));
base::TimeDelta duration;
if (moov_->extends.header.fragment_duration > 0) {
diff --git a/media/mp4/mp4_stream_parser_unittest.cc b/media/mp4/mp4_stream_parser_unittest.cc
index 830f35b..20c32f1 100644
--- a/media/mp4/mp4_stream_parser_unittest.cc
+++ b/media/mp4/mp4_stream_parser_unittest.cc
@@ -62,11 +62,6 @@ class MP4StreamParserTest : public testing::Test {
const VideoDecoderConfig& vc) {
DVLOG(1) << "NewConfigF: audio=" << ac.IsValidConfig()
<< ", video=" << vc.IsValidConfig();
-
- // TODO(strobe): Until http://crbug.com/122913 is fixed, we want to make
- // sure that this callback isn't called more than once per stream. Remove
- // when that bug is fixed.
- EXPECT_FALSE(configs_received_);
configs_received_ = true;
return true;
}