diff options
-rw-r--r-- | media/filters/chunk_demuxer_unittest.cc | 14 | ||||
-rw-r--r-- | media/filters/ffmpeg_video_decoder.cc | 85 | ||||
-rw-r--r-- | media/filters/ffmpeg_video_decoder.h | 4 | ||||
-rw-r--r-- | media/filters/pipeline_integration_test.cc | 113 | ||||
-rw-r--r-- | media/filters/source_buffer_stream.cc | 45 | ||||
-rw-r--r-- | media/filters/source_buffer_stream.h | 8 | ||||
-rw-r--r-- | media/filters/source_buffer_stream_unittest.cc | 103 | ||||
-rw-r--r-- | media/mp4/mp4_stream_parser.cc | 9 | ||||
-rw-r--r-- | media/mp4/mp4_stream_parser_unittest.cc | 5 |
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; } |