diff options
Diffstat (limited to 'chromium/media/filters/pipeline_integration_test.cc')
-rw-r--r-- | chromium/media/filters/pipeline_integration_test.cc | 625 |
1 files changed, 492 insertions, 133 deletions
diff --git a/chromium/media/filters/pipeline_integration_test.cc b/chromium/media/filters/pipeline_integration_test.cc index bdf33f22418..f991dc39977 100644 --- a/chromium/media/filters/pipeline_integration_test.cc +++ b/chromium/media/filters/pipeline_integration_test.cc @@ -9,6 +9,7 @@ #include "base/memory/scoped_ptr.h" #include "base/strings/string_util.h" #include "build/build_config.h" +#include "media/base/cdm_promise.h" #include "media/base/decoder_buffer.h" #include "media/base/media_keys.h" #include "media/base/media_switches.h" @@ -17,8 +18,10 @@ #include "media/cdm/json_web_key.h" #include "media/filters/chunk_demuxer.h" +using testing::_; using testing::AnyNumber; using testing::AtMost; +using testing::SaveArg; namespace media { @@ -33,6 +36,7 @@ const char kVideoOnlyWebM[] = "video/webm; codecs=\"vp8\""; const char kMP4VideoType[] = "video/mp4"; const char kMP4AudioType[] = "audio/mp4"; #if defined(USE_PROPRIETARY_CODECS) +const char kADTS[] = "audio/aac"; const char kMP4[] = "video/mp4; codecs=\"avc1.4D4041,mp4a.40.2\""; const char kMP4Video[] = "video/mp4; codecs=\"avc1.4D4041\""; const char kMP4VideoAVC3[] = "video/mp4; codecs=\"avc3.64001f\""; @@ -57,19 +61,50 @@ const int kAppendWholeFile = -1; // Constants for the Media Source config change tests. const int kAppendTimeSec = 1; const int kAppendTimeMs = kAppendTimeSec * 1000; -const int k320WebMFileDurationMs = 2737; -const int k640WebMFileDurationMs = 2763; -const int kOpusEndTrimmingWebMFileDurationMs = 2771; -const int kVP9WebMFileDurationMs = 2735; -const int kVP8AWebMFileDurationMs = 2700; +const int k320WebMFileDurationMs = 2736; +const int k640WebMFileDurationMs = 2749; +const int kOpusEndTrimmingWebMFileDurationMs = 2741; +const int kVP9WebMFileDurationMs = 2736; +const int kVP8AWebMFileDurationMs = 2733; #if defined(USE_PROPRIETARY_CODECS) const int k640IsoFileDurationMs = 2737; const int k640IsoCencFileDurationMs = 2736; const int k1280IsoFileDurationMs = 2736; -const int k1280IsoAVC3FileDurationMs = 2735; +const int k1280IsoAVC3FileDurationMs = 2736; #endif // defined(USE_PROPRIETARY_CODECS) +// Return a timeline offset for bear-320x240-live.webm. +static base::Time kLiveTimelineOffset() { + // The file contians the following UTC timeline offset: + // 2012-11-10 12:34:56.789123456 + // Since base::Time only has a resolution of microseconds, + // construct a base::Time for 2012-11-10 12:34:56.789123. + base::Time::Exploded exploded_time; + exploded_time.year = 2012; + exploded_time.month = 11; + exploded_time.day_of_month = 10; + exploded_time.hour = 12; + exploded_time.minute = 34; + exploded_time.second = 56; + exploded_time.millisecond = 789; + base::Time timeline_offset = base::Time::FromUTCExploded(exploded_time); + + timeline_offset += base::TimeDelta::FromMicroseconds(123); + + return timeline_offset; +} + +// FFmpeg only supports time a resolution of seconds so this +// helper function truncates a base::Time to seconds resolution. +static base::Time TruncateToFFmpegTimeResolution(base::Time t) { + base::Time::Exploded exploded_time; + t.UTCExplode(&exploded_time); + exploded_time.millisecond = 0; + + return base::Time::FromUTCExploded(exploded_time); +} + // Note: Tests using this class only exercise the DecryptingDemuxerStream path. // They do not exercise the Decrypting{Audio|Video}Decoder path. class FakeEncryptedMedia { @@ -79,21 +114,19 @@ class FakeEncryptedMedia { public: virtual ~AppBase() {} - virtual void OnSessionCreated(uint32 session_id, - const std::string& web_session_id) = 0; - - virtual void OnSessionMessage(uint32 session_id, + virtual void OnSessionMessage(const std::string& web_session_id, const std::vector<uint8>& message, - const std::string& destination_url) = 0; + const GURL& destination_url) = 0; - virtual void OnSessionReady(uint32 session_id) = 0; + virtual void OnSessionReady(const std::string& web_session_id) = 0; - virtual void OnSessionClosed(uint32 session_id) = 0; + virtual void OnSessionClosed(const std::string& web_session_id) = 0; // Errors are not expected unless overridden. - virtual void OnSessionError(uint32 session_id, - MediaKeys::KeyError error_code, - int system_code) { + virtual void OnSessionError(const std::string& web_session_id, + const std::string& error_name, + uint32 system_code, + const std::string& error_message) { FAIL() << "Unexpected Key Error"; } @@ -103,15 +136,9 @@ class FakeEncryptedMedia { }; FakeEncryptedMedia(AppBase* app) - : decryptor_(base::Bind(&FakeEncryptedMedia::OnSessionCreated, - base::Unretained(this)), - base::Bind(&FakeEncryptedMedia::OnSessionMessage, - base::Unretained(this)), - base::Bind(&FakeEncryptedMedia::OnSessionReady, + : decryptor_(base::Bind(&FakeEncryptedMedia::OnSessionMessage, base::Unretained(this)), base::Bind(&FakeEncryptedMedia::OnSessionClosed, - base::Unretained(this)), - base::Bind(&FakeEncryptedMedia::OnSessionError, base::Unretained(this))), app_(app) {} @@ -120,28 +147,26 @@ class FakeEncryptedMedia { } // Callbacks for firing session events. Delegate to |app_|. - void OnSessionCreated(uint32 session_id, const std::string& web_session_id) { - app_->OnSessionCreated(session_id, web_session_id); - } - - void OnSessionMessage(uint32 session_id, + void OnSessionMessage(const std::string& web_session_id, const std::vector<uint8>& message, - const std::string& destination_url) { - app_->OnSessionMessage(session_id, message, destination_url); + const GURL& destination_url) { + app_->OnSessionMessage(web_session_id, message, destination_url); } - void OnSessionReady(uint32 session_id) { - app_->OnSessionReady(session_id); + void OnSessionReady(const std::string& web_session_id) { + app_->OnSessionReady(web_session_id); } - void OnSessionClosed(uint32 session_id) { - app_->OnSessionClosed(session_id); + void OnSessionClosed(const std::string& web_session_id) { + app_->OnSessionClosed(web_session_id); } - void OnSessionError(uint32 session_id, - MediaKeys::KeyError error_code, - int system_code) { - app_->OnSessionError(session_id, error_code, system_code); + void OnSessionError(const std::string& web_session_id, + const std::string& error_name, + uint32 system_code, + const std::string& error_message) { + app_->OnSessionError( + web_session_id, error_name, system_code, error_message); } void NeedKey(const std::string& type, @@ -154,44 +179,80 @@ class FakeEncryptedMedia { scoped_ptr<AppBase> app_; }; +enum PromiseResult { RESOLVED, REJECTED }; + // Provides |kSecretKey| in response to needkey. class KeyProvidingApp : public FakeEncryptedMedia::AppBase { public: - KeyProvidingApp() : current_session_id_(0) {} + KeyProvidingApp() {} - virtual void OnSessionCreated(uint32 session_id, - const std::string& web_session_id) OVERRIDE { - EXPECT_GT(session_id, 0u); - EXPECT_FALSE(web_session_id.empty()); + void OnResolveWithSession(PromiseResult expected, + const std::string& web_session_id) { + EXPECT_EQ(expected, RESOLVED); + EXPECT_GT(web_session_id.length(), 0ul); + current_session_id_ = web_session_id; + } + + void OnResolve(PromiseResult expected) { + EXPECT_EQ(expected, RESOLVED); + } + + void OnReject(PromiseResult expected, + media::MediaKeys::Exception exception_code, + uint32 system_code, + const std::string& error_message) { + EXPECT_EQ(expected, REJECTED); + } + + scoped_ptr<SimpleCdmPromise> CreatePromise(PromiseResult expected) { + scoped_ptr<media::SimpleCdmPromise> promise(new media::SimpleCdmPromise( + base::Bind( + &KeyProvidingApp::OnResolve, base::Unretained(this), expected), + base::Bind( + &KeyProvidingApp::OnReject, base::Unretained(this), expected))); + return promise.Pass(); + } + + scoped_ptr<NewSessionCdmPromise> CreateSessionPromise( + PromiseResult expected) { + scoped_ptr<media::NewSessionCdmPromise> promise( + new media::NewSessionCdmPromise( + base::Bind(&KeyProvidingApp::OnResolveWithSession, + base::Unretained(this), + expected), + base::Bind( + &KeyProvidingApp::OnReject, base::Unretained(this), expected))); + return promise.Pass(); } - virtual void OnSessionMessage(uint32 session_id, + virtual void OnSessionMessage(const std::string& web_session_id, const std::vector<uint8>& message, - const std::string& default_url) OVERRIDE { - EXPECT_GT(session_id, 0u); + const GURL& destination_url) OVERRIDE { + EXPECT_FALSE(web_session_id.empty()); EXPECT_FALSE(message.empty()); - - current_session_id_ = session_id; + EXPECT_EQ(current_session_id_, web_session_id); } - virtual void OnSessionReady(uint32 session_id) OVERRIDE { - EXPECT_GT(session_id, 0u); + virtual void OnSessionReady(const std::string& web_session_id) OVERRIDE { + EXPECT_EQ(current_session_id_, web_session_id); } - virtual void OnSessionClosed(uint32 session_id) OVERRIDE { - EXPECT_GT(session_id, 0u); + virtual void OnSessionClosed(const std::string& web_session_id) OVERRIDE { + EXPECT_EQ(current_session_id_, web_session_id); } virtual void NeedKey(const std::string& type, const std::vector<uint8>& init_data, AesDecryptor* decryptor) OVERRIDE { - if (current_session_id_ == 0u) { - EXPECT_TRUE( - decryptor->CreateSession(12, type, kInitData, arraysize(kInitData))); + if (current_session_id_.empty()) { + decryptor->CreateSession(type, + kInitData, + arraysize(kInitData), + MediaKeys::TEMPORARY_SESSION, + CreateSessionPromise(RESOLVED)); + EXPECT_FALSE(current_session_id_.empty()); } - EXPECT_EQ(current_session_id_, 12u); - // Clear Key really needs the key ID in |init_data|. For WebM, they are the // same, but this is not the case for ISO CENC. Therefore, provide the // correct key ID. @@ -207,36 +268,103 @@ class KeyProvidingApp : public FakeEncryptedMedia::AppBase { kSecretKey, arraysize(kSecretKey), key_id, key_id_length); decryptor->UpdateSession(current_session_id_, reinterpret_cast<const uint8*>(jwk.data()), - jwk.size()); + jwk.size(), + CreatePromise(RESOLVED)); } - uint32 current_session_id_; + std::string current_session_id_; }; -// Ignores needkey and does not perform a license request -class NoResponseApp : public FakeEncryptedMedia::AppBase { +class RotatingKeyProvidingApp : public KeyProvidingApp { public: - virtual void OnSessionCreated(uint32 session_id, - const std::string& web_session_id) OVERRIDE { - EXPECT_GT(session_id, 0u); - EXPECT_FALSE(web_session_id.empty()); + RotatingKeyProvidingApp() : num_distint_need_key_calls_(0) {} + virtual ~RotatingKeyProvidingApp() { + // Expect that NeedKey is fired multiple times with different |init_data|. + EXPECT_GT(num_distint_need_key_calls_, 1u); } - virtual void OnSessionMessage(uint32 session_id, + virtual void NeedKey(const std::string& type, + const std::vector<uint8>& init_data, + AesDecryptor* decryptor) OVERRIDE { + // Skip the request if the |init_data| has been seen. + if (init_data == prev_init_data_) + return; + prev_init_data_ = init_data; + ++num_distint_need_key_calls_; + + decryptor->CreateSession(type, + vector_as_array(&init_data), + init_data.size(), + MediaKeys::TEMPORARY_SESSION, + CreateSessionPromise(RESOLVED)); + + std::vector<uint8> key_id; + std::vector<uint8> key; + EXPECT_TRUE(GetKeyAndKeyId(init_data, &key, &key_id)); + + // Convert key into a JSON structure and then add it. + std::string jwk = GenerateJWKSet(vector_as_array(&key), + key.size(), + vector_as_array(&key_id), + key_id.size()); + decryptor->UpdateSession(current_session_id_, + reinterpret_cast<const uint8*>(jwk.data()), + jwk.size(), + CreatePromise(RESOLVED)); + } + + private: + bool GetKeyAndKeyId(std::vector<uint8> init_data, + std::vector<uint8>* key, + std::vector<uint8>* key_id) { + // For WebM, init_data is key_id; for ISO CENC, init_data should contain + // the key_id. We assume key_id is in the end of init_data here (that is + // only a reasonable assumption for WebM and clear key ISO CENC). + DCHECK_GE(init_data.size(), arraysize(kKeyId)); + std::vector<uint8> key_id_from_init_data( + init_data.end() - arraysize(kKeyId), init_data.end()); + + key->assign(kSecretKey, kSecretKey + arraysize(kSecretKey)); + key_id->assign(kKeyId, kKeyId + arraysize(kKeyId)); + + // The Key and KeyId for this testing key provider are created by left + // rotating kSecretKey and kKeyId. Note that this implementation is only + // intended for testing purpose. The actual key rotation algorithm can be + // much more complicated. + // Find out the rotating position from |key_id_from_init_data| and apply on + // |key|. + for (size_t pos = 0; pos < arraysize(kKeyId); ++pos) { + std::rotate(key_id->begin(), key_id->begin() + pos, key_id->end()); + if (*key_id == key_id_from_init_data) { + std::rotate(key->begin(), key->begin() + pos, key->end()); + return true; + } + } + return false; + } + + std::vector<uint8> prev_init_data_; + uint32 num_distint_need_key_calls_; +}; + +// Ignores needkey and does not perform a license request +class NoResponseApp : public FakeEncryptedMedia::AppBase { + public: + virtual void OnSessionMessage(const std::string& web_session_id, const std::vector<uint8>& message, - const std::string& default_url) OVERRIDE { - EXPECT_GT(session_id, 0u); + const GURL& default_url) OVERRIDE { + EXPECT_FALSE(web_session_id.empty()); EXPECT_FALSE(message.empty()); - FAIL() << "Unexpected KeyMessage"; + FAIL() << "Unexpected Message"; } - virtual void OnSessionReady(uint32 session_id) OVERRIDE { - EXPECT_GT(session_id, 0u); + virtual void OnSessionReady(const std::string& web_session_id) OVERRIDE { + EXPECT_FALSE(web_session_id.empty()); FAIL() << "Unexpected Ready"; } - virtual void OnSessionClosed(uint32 session_id) OVERRIDE { - EXPECT_GT(session_id, 0u); + virtual void OnSessionClosed(const std::string& web_session_id) OVERRIDE { + EXPECT_FALSE(web_session_id.empty()); FAIL() << "Unexpected Closed"; } @@ -250,18 +378,19 @@ class NoResponseApp : public FakeEncryptedMedia::AppBase { // Media Source API. class MockMediaSource { public: - MockMediaSource(const std::string& filename, const std::string& mimetype, + MockMediaSource(const std::string& filename, + const std::string& mimetype, int initial_append_size) : file_path_(GetTestDataFilePath(filename)), current_position_(0), initial_append_size_(initial_append_size), mimetype_(mimetype), chunk_demuxer_(new ChunkDemuxer( - base::Bind(&MockMediaSource::DemuxerOpened, - base::Unretained(this)), + base::Bind(&MockMediaSource::DemuxerOpened, base::Unretained(this)), base::Bind(&MockMediaSource::DemuxerNeedKey, base::Unretained(this)), - LogCB())), + LogCB(), + true)), owned_chunk_demuxer_(chunk_demuxer_) { file_data_ = ReadTestDataFile(filename); @@ -284,7 +413,9 @@ class MockMediaSource { void Seek(base::TimeDelta seek_time, int new_position, int seek_append_size) { chunk_demuxer_->StartWaitingForSeek(seek_time); - chunk_demuxer_->Abort(kSourceId); + chunk_demuxer_->Abort( + kSourceId, + base::TimeDelta(), kInfiniteDuration(), &last_timestamp_offset_); DCHECK_GE(new_position, 0); DCHECK_LT(new_position, file_data_->data_size()); @@ -297,16 +428,36 @@ class MockMediaSource { DCHECK(chunk_demuxer_); DCHECK_LT(current_position_, file_data_->data_size()); DCHECK_LE(current_position_ + size, file_data_->data_size()); + chunk_demuxer_->AppendData( - kSourceId, file_data_->data() + current_position_, size); + kSourceId, file_data_->data() + current_position_, size, + base::TimeDelta(), kInfiniteDuration(), &last_timestamp_offset_); current_position_ += size; } - void AppendAtTime(const base::TimeDelta& timestampOffset, - const uint8* pData, int size) { - CHECK(chunk_demuxer_->SetTimestampOffset(kSourceId, timestampOffset)); - chunk_demuxer_->AppendData(kSourceId, pData, size); - CHECK(chunk_demuxer_->SetTimestampOffset(kSourceId, base::TimeDelta())); + void AppendAtTime(base::TimeDelta timestamp_offset, + const uint8* pData, + int size) { + CHECK(!chunk_demuxer_->IsParsingMediaSegment(kSourceId)); + chunk_demuxer_->AppendData(kSourceId, pData, size, + base::TimeDelta(), kInfiniteDuration(), + ×tamp_offset); + last_timestamp_offset_ = timestamp_offset; + } + + void AppendAtTimeWithWindow(base::TimeDelta timestamp_offset, + base::TimeDelta append_window_start, + base::TimeDelta append_window_end, + const uint8* pData, + int size) { + CHECK(!chunk_demuxer_->IsParsingMediaSegment(kSourceId)); + chunk_demuxer_->AppendData(kSourceId, + pData, + size, + append_window_start, + append_window_end, + ×tamp_offset); + last_timestamp_offset_ = timestamp_offset; } void EndOfStream() { @@ -352,6 +503,7 @@ class MockMediaSource { } CHECK_EQ(chunk_demuxer_->AddId(kSourceId, type, codecs), ChunkDemuxer::kOk); + AppendData(initial_append_size_); } @@ -362,6 +514,10 @@ class MockMediaSource { need_key_cb_.Run(type, init_data); } + base::TimeDelta last_timestamp_offset() const { + return last_timestamp_offset_; + } + private: base::FilePath file_path_; scoped_refptr<DecoderBuffer> file_data_; @@ -371,6 +527,7 @@ class MockMediaSource { ChunkDemuxer* chunk_demuxer_; scoped_ptr<Demuxer> owned_chunk_demuxer_; Demuxer::NeedKeyCB need_key_cb_; + base::TimeDelta last_timestamp_offset_; }; class PipelineIntegrationTest @@ -378,16 +535,17 @@ class PipelineIntegrationTest public PipelineIntegrationTestBase { public: void StartPipelineWithMediaSource(MockMediaSource* source) { - EXPECT_CALL(*this, OnBufferingState(Pipeline::kHaveMetadata)) - .Times(AtMost(1)); - EXPECT_CALL(*this, OnBufferingState(Pipeline::kPrerollCompleted)) - .Times(AtMost(1)); + EXPECT_CALL(*this, OnMetadata(_)).Times(AtMost(1)) + .WillRepeatedly(SaveArg<0>(&metadata_)); + EXPECT_CALL(*this, OnPrerollCompleted()).Times(AtMost(1)); pipeline_->Start( CreateFilterCollection(source->GetDemuxer(), NULL), base::Bind(&PipelineIntegrationTest::OnEnded, base::Unretained(this)), base::Bind(&PipelineIntegrationTest::OnError, base::Unretained(this)), QuitOnStatusCB(PIPELINE_OK), - base::Bind(&PipelineIntegrationTest::OnBufferingState, + base::Bind(&PipelineIntegrationTest::OnMetadata, + base::Unretained(this)), + base::Bind(&PipelineIntegrationTest::OnPrerollCompleted, base::Unretained(this)), base::Closure()); @@ -402,17 +560,18 @@ class PipelineIntegrationTest void StartPipelineWithEncryptedMedia( MockMediaSource* source, FakeEncryptedMedia* encrypted_media) { - EXPECT_CALL(*this, OnBufferingState(Pipeline::kHaveMetadata)) - .Times(AtMost(1)); - EXPECT_CALL(*this, OnBufferingState(Pipeline::kPrerollCompleted)) - .Times(AtMost(1)); + EXPECT_CALL(*this, OnMetadata(_)).Times(AtMost(1)) + .WillRepeatedly(SaveArg<0>(&metadata_)); + EXPECT_CALL(*this, OnPrerollCompleted()).Times(AtMost(1)); pipeline_->Start( CreateFilterCollection(source->GetDemuxer(), encrypted_media->decryptor()), base::Bind(&PipelineIntegrationTest::OnEnded, base::Unretained(this)), base::Bind(&PipelineIntegrationTest::OnError, base::Unretained(this)), QuitOnStatusCB(PIPELINE_OK), - base::Bind(&PipelineIntegrationTest::OnBufferingState, + base::Bind(&PipelineIntegrationTest::OnMetadata, + base::Unretained(this)), + base::Bind(&PipelineIntegrationTest::OnPrerollCompleted, base::Unretained(this)), base::Closure()); @@ -462,6 +621,14 @@ TEST_F(PipelineIntegrationTest, BasicPlayback) { ASSERT_TRUE(WaitUntilOnEnded()); } +TEST_F(PipelineIntegrationTest, BasicPlaybackOpusOgg) { + ASSERT_TRUE(Start(GetTestDataFilePath("bear-opus.ogg"), PIPELINE_OK)); + + Play(); + + ASSERT_TRUE(WaitUntilOnEnded()); +} + TEST_F(PipelineIntegrationTest, BasicPlaybackHashed) { ASSERT_TRUE(Start( GetTestDataFilePath("bear-320x240.webm"), PIPELINE_OK, kHashed)); @@ -472,6 +639,24 @@ TEST_F(PipelineIntegrationTest, BasicPlaybackHashed) { EXPECT_EQ("f0be120a90a811506777c99a2cdf7cc1", GetVideoHash()); EXPECT_EQ("-3.59,-2.06,-0.43,2.15,0.77,-0.95,", GetAudioHash()); + EXPECT_TRUE(demuxer_->GetTimelineOffset().is_null()); +} + +TEST_F(PipelineIntegrationTest, BasicPlaybackLive) { + ASSERT_TRUE(Start( + GetTestDataFilePath("bear-320x240-live.webm"), PIPELINE_OK, kHashed)); + + Play(); + + ASSERT_TRUE(WaitUntilOnEnded()); + + EXPECT_EQ("f0be120a90a811506777c99a2cdf7cc1", GetVideoHash()); + EXPECT_EQ("-3.59,-2.06,-0.43,2.15,0.77,-0.95,", GetAudioHash()); + + // TODO: Fix FFmpeg code to return higher resolution time values so + // we don't have to truncate our expectations here. + EXPECT_EQ(TruncateToFFmpegTimeResolution(kLiveTimelineOffset()), + demuxer_->GetTimelineOffset()); } TEST_F(PipelineIntegrationTest, F32PlaybackHashed) { @@ -510,15 +695,34 @@ TEST_F(PipelineIntegrationTest, BasicPlayback_MediaSource) { Play(); ASSERT_TRUE(WaitUntilOnEnded()); + + EXPECT_TRUE(demuxer_->GetTimelineOffset().is_null()); source.Abort(); Stop(); } -// TODO(fgalligan): Enable after new vp9 files are landed. -// http://crbug.com/259116 -TEST_F(PipelineIntegrationTest, - DISABLED_BasicPlayback_MediaSource_VideoOnly_VP9_WebM) { - MockMediaSource source("bear-vp9.webm", kWebMVP9, 32393); +TEST_F(PipelineIntegrationTest, BasicPlayback_MediaSource_Live) { + MockMediaSource source("bear-320x240-live.webm", kWebM, 219221); + StartPipelineWithMediaSource(&source); + source.EndOfStream(); + + EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size()); + EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds()); + EXPECT_EQ(k320WebMFileDurationMs, + pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds()); + + Play(); + + ASSERT_TRUE(WaitUntilOnEnded()); + + EXPECT_EQ(kLiveTimelineOffset(), + demuxer_->GetTimelineOffset()); + source.Abort(); + Stop(); +} + +TEST_F(PipelineIntegrationTest, BasicPlayback_MediaSource_VP9_WebM) { + MockMediaSource source("bear-vp9.webm", kWebMVP9, 67504); StartPipelineWithMediaSource(&source); source.EndOfStream(); @@ -535,7 +739,6 @@ TEST_F(PipelineIntegrationTest, } TEST_F(PipelineIntegrationTest, BasicPlayback_MediaSource_VP8A_WebM) { - EXPECT_CALL(*this, OnSetOpaque(false)).Times(AnyNumber()); MockMediaSource source("bear-vp8a.webm", kVideoOnlyWebM, kAppendWholeFile); StartPipelineWithMediaSource(&source); source.EndOfStream(); @@ -553,7 +756,6 @@ TEST_F(PipelineIntegrationTest, BasicPlayback_MediaSource_VP8A_WebM) { } TEST_F(PipelineIntegrationTest, BasicPlayback_MediaSource_Opus_WebM) { - EXPECT_CALL(*this, OnSetOpaque(false)).Times(AnyNumber()); MockMediaSource source("bear-opus-end-trimming.webm", kOpusAudioOnlyWebM, kAppendWholeFile); StartPipelineWithMediaSource(&source); @@ -572,12 +774,10 @@ TEST_F(PipelineIntegrationTest, BasicPlayback_MediaSource_Opus_WebM) { // Flaky. http://crbug.com/304776 TEST_F(PipelineIntegrationTest, DISABLED_MediaSource_Opus_Seeking_WebM) { - EXPECT_CALL(*this, OnSetOpaque(false)).Times(AnyNumber()); MockMediaSource source("bear-opus-end-trimming.webm", kOpusAudioOnlyWebM, kAppendWholeFile); StartHashedPipelineWithMediaSource(&source); - EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size()); EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds()); EXPECT_EQ(kOpusEndTrimmingWebMFileDurationMs, @@ -711,16 +911,110 @@ TEST_F(PipelineIntegrationTest, } #if defined(USE_PROPRIETARY_CODECS) +TEST_F(PipelineIntegrationTest, MediaSource_ADTS) { + MockMediaSource source("sfx.adts", kADTS, kAppendWholeFile); + StartPipelineWithMediaSource(&source); + source.EndOfStream(); + + EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size()); + EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds()); + EXPECT_EQ(325, pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds()); + + Play(); + + EXPECT_TRUE(WaitUntilOnEnded()); +} + +TEST_F(PipelineIntegrationTest, MediaSource_ADTS_TimestampOffset) { + MockMediaSource source("sfx.adts", kADTS, kAppendWholeFile); + StartHashedPipelineWithMediaSource(&source); + EXPECT_EQ(325, source.last_timestamp_offset().InMilliseconds()); + + // Trim off multiple frames off the beginning of the segment which will cause + // the first decoded frame to be incorrect if preroll isn't implemented. + const base::TimeDelta adts_preroll_duration = + base::TimeDelta::FromSecondsD(2.5 * 1024 / 44100); + const base::TimeDelta append_time = + source.last_timestamp_offset() - adts_preroll_duration; + + scoped_refptr<DecoderBuffer> second_file = ReadTestDataFile("sfx.adts"); + source.AppendAtTimeWithWindow(append_time, + append_time + adts_preroll_duration, + kInfiniteDuration(), + second_file->data(), + second_file->data_size()); + source.EndOfStream(); + + EXPECT_EQ(592, source.last_timestamp_offset().InMilliseconds()); + EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size()); + EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds()); + EXPECT_EQ(592, pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds()); + + Play(); + + EXPECT_TRUE(WaitUntilOnEnded()); + + // Verify preroll is stripped. + EXPECT_EQ("-0.06,0.97,-0.90,-0.70,-0.53,-0.34,", GetAudioHash()); +} + +TEST_F(PipelineIntegrationTest, BasicPlaybackHashed_MP3) { + ASSERT_TRUE(Start(GetTestDataFilePath("sfx.mp3"), PIPELINE_OK, kHashed)); + + Play(); + + ASSERT_TRUE(WaitUntilOnEnded()); + + // Verify codec delay and preroll are stripped. + EXPECT_EQ("3.05,2.87,3.00,3.32,3.58,4.08,", GetAudioHash()); +} + TEST_F(PipelineIntegrationTest, MediaSource_MP3) { MockMediaSource source("sfx.mp3", kMP3, kAppendWholeFile); - StartPipelineWithMediaSource(&source); + StartHashedPipelineWithMediaSource(&source); source.EndOfStream(); + EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size()); + EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds()); + EXPECT_EQ(313, pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds()); + Play(); EXPECT_TRUE(WaitUntilOnEnded()); + + // Verify that codec delay was stripped. + EXPECT_EQ("1.01,2.71,4.18,4.32,3.04,1.12,", GetAudioHash()); } +TEST_F(PipelineIntegrationTest, MediaSource_MP3_TimestampOffset) { + MockMediaSource source("sfx.mp3", kMP3, kAppendWholeFile); + StartPipelineWithMediaSource(&source); + EXPECT_EQ(313, source.last_timestamp_offset().InMilliseconds()); + + // There are 576 silent frames at the start of this mp3. The second append + // should trim them off. + const base::TimeDelta mp3_preroll_duration = + base::TimeDelta::FromSecondsD(576.0 / 44100); + const base::TimeDelta append_time = + source.last_timestamp_offset() - mp3_preroll_duration; + + scoped_refptr<DecoderBuffer> second_file = ReadTestDataFile("sfx.mp3"); + source.AppendAtTimeWithWindow(append_time, + append_time + mp3_preroll_duration, + kInfiniteDuration(), + second_file->data(), + second_file->data_size()); + source.EndOfStream(); + + EXPECT_EQ(613, source.last_timestamp_offset().InMilliseconds()); + EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size()); + EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds()); + EXPECT_EQ(613, pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds()); + + Play(); + + EXPECT_TRUE(WaitUntilOnEnded()); +} TEST_F(PipelineIntegrationTest, MediaSource_MP3_Icecast) { MockMediaSource source("icy_sfx.mp3", kMP3, kAppendWholeFile); @@ -758,8 +1052,8 @@ TEST_F(PipelineIntegrationTest, MediaSource_ConfigChange_MP4) { TEST_F(PipelineIntegrationTest, MediaSource_ConfigChange_Encrypted_MP4_CENC_VideoOnly) { - MockMediaSource source("bear-640x360-v_frag-cenc.mp4", - kMP4Video, kAppendWholeFile); + MockMediaSource source("bear-640x360-v_frag-cenc.mp4", kMP4Video, + kAppendWholeFile); FakeEncryptedMedia encrypted_media(new KeyProvidingApp()); StartPipelineWithEncryptedMedia(&source, &encrypted_media); @@ -783,6 +1077,33 @@ TEST_F(PipelineIntegrationTest, Stop(); } +TEST_F(PipelineIntegrationTest, + MediaSource_ConfigChange_Encrypted_MP4_CENC_KeyRotation_VideoOnly) { + MockMediaSource source("bear-640x360-v_frag-cenc-key_rotation.mp4", kMP4Video, + kAppendWholeFile); + FakeEncryptedMedia encrypted_media(new RotatingKeyProvidingApp()); + StartPipelineWithEncryptedMedia(&source, &encrypted_media); + + scoped_refptr<DecoderBuffer> second_file = + ReadTestDataFile("bear-1280x720-v_frag-cenc-key_rotation.mp4"); + + source.AppendAtTime(base::TimeDelta::FromSeconds(kAppendTimeSec), + second_file->data(), second_file->data_size()); + + source.EndOfStream(); + + EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size()); + EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds()); + EXPECT_EQ(kAppendTimeMs + k1280IsoFileDurationMs, + pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds()); + + Play(); + + EXPECT_TRUE(WaitUntilOnEnded()); + source.Abort(); + Stop(); +} + // Config changes from clear to encrypted are not currently supported. // TODO(ddorwin): Figure out why this CHECKs in AppendAtTime(). TEST_F(PipelineIntegrationTest, @@ -818,8 +1139,8 @@ TEST_F(PipelineIntegrationTest, // Config changes from encrypted to clear are not currently supported. TEST_F(PipelineIntegrationTest, MediaSource_ConfigChange_EncryptedThenClear_MP4_CENC) { - MockMediaSource source("bear-640x360-v_frag-cenc.mp4", - kMP4Video, kAppendWholeFile); + MockMediaSource source("bear-640x360-v_frag-cenc.mp4", kMP4Video, + kAppendWholeFile); FakeEncryptedMedia encrypted_media(new KeyProvidingApp()); StartPipelineWithEncryptedMedia(&source, &encrypted_media); @@ -876,8 +1197,8 @@ TEST_F(PipelineIntegrationTest, EncryptedPlayback_WebM) { } TEST_F(PipelineIntegrationTest, EncryptedPlayback_ClearStart_WebM) { - MockMediaSource source("bear-320x240-av_enc-av_clear-1s.webm", - kWebM, kAppendWholeFile); + MockMediaSource source("bear-320x240-av_enc-av_clear-1s.webm", kWebM, + kAppendWholeFile); FakeEncryptedMedia encrypted_media(new KeyProvidingApp()); StartPipelineWithEncryptedMedia(&source, &encrypted_media); @@ -892,8 +1213,8 @@ TEST_F(PipelineIntegrationTest, EncryptedPlayback_ClearStart_WebM) { } TEST_F(PipelineIntegrationTest, EncryptedPlayback_NoEncryptedFrames_WebM) { - MockMediaSource source("bear-320x240-av_enc-av_clear-all.webm", - kWebM, kAppendWholeFile); + MockMediaSource source("bear-320x240-av_enc-av_clear-all.webm", kWebM, + kAppendWholeFile); FakeEncryptedMedia encrypted_media(new NoResponseApp()); StartPipelineWithEncryptedMedia(&source, &encrypted_media); @@ -909,8 +1230,8 @@ TEST_F(PipelineIntegrationTest, EncryptedPlayback_NoEncryptedFrames_WebM) { #if defined(USE_PROPRIETARY_CODECS) TEST_F(PipelineIntegrationTest, EncryptedPlayback_MP4_CENC_VideoOnly) { - MockMediaSource source("bear-1280x720-v_frag-cenc.mp4", - kMP4Video, kAppendWholeFile); + MockMediaSource source("bear-1280x720-v_frag-cenc.mp4", kMP4Video, + kAppendWholeFile); FakeEncryptedMedia encrypted_media(new KeyProvidingApp()); StartPipelineWithEncryptedMedia(&source, &encrypted_media); @@ -925,8 +1246,8 @@ TEST_F(PipelineIntegrationTest, EncryptedPlayback_MP4_CENC_VideoOnly) { } TEST_F(PipelineIntegrationTest, EncryptedPlayback_MP4_CENC_AudioOnly) { - MockMediaSource source("bear-1280x720-a_frag-cenc.mp4", - kMP4Audio, kAppendWholeFile); + MockMediaSource source("bear-1280x720-a_frag-cenc.mp4", kMP4Audio, + kAppendWholeFile); FakeEncryptedMedia encrypted_media(new KeyProvidingApp()); StartPipelineWithEncryptedMedia(&source, &encrypted_media); @@ -942,8 +1263,8 @@ TEST_F(PipelineIntegrationTest, EncryptedPlayback_MP4_CENC_AudioOnly) { TEST_F(PipelineIntegrationTest, EncryptedPlayback_NoEncryptedFrames_MP4_CENC_VideoOnly) { - MockMediaSource source("bear-1280x720-v_frag-cenc_clear-all.mp4", - kMP4Video, kAppendWholeFile); + MockMediaSource source("bear-1280x720-v_frag-cenc_clear-all.mp4", kMP4Video, + kAppendWholeFile); FakeEncryptedMedia encrypted_media(new NoResponseApp()); StartPipelineWithEncryptedMedia(&source, &encrypted_media); @@ -959,8 +1280,8 @@ TEST_F(PipelineIntegrationTest, TEST_F(PipelineIntegrationTest, EncryptedPlayback_NoEncryptedFrames_MP4_CENC_AudioOnly) { - MockMediaSource source("bear-1280x720-a_frag-cenc_clear-all.mp4", - kMP4Audio, kAppendWholeFile); + MockMediaSource source("bear-1280x720-a_frag-cenc_clear-all.mp4", kMP4Audio, + kAppendWholeFile); FakeEncryptedMedia encrypted_media(new NoResponseApp()); StartPipelineWithEncryptedMedia(&source, &encrypted_media); @@ -992,6 +1313,37 @@ TEST_F(PipelineIntegrationTest, BasicPlayback_MediaSource_VideoOnly_MP4_AVC3) { Stop(); } +TEST_F(PipelineIntegrationTest, EncryptedPlayback_MP4_CENC_KeyRotation_Video) { + MockMediaSource source("bear-1280x720-v_frag-cenc-key_rotation.mp4", + kMP4Video, kAppendWholeFile); + FakeEncryptedMedia encrypted_media(new RotatingKeyProvidingApp()); + StartPipelineWithEncryptedMedia(&source, &encrypted_media); + + source.EndOfStream(); + ASSERT_EQ(PIPELINE_OK, pipeline_status_); + + Play(); + + ASSERT_TRUE(WaitUntilOnEnded()); + source.Abort(); + Stop(); +} + +TEST_F(PipelineIntegrationTest, EncryptedPlayback_MP4_CENC_KeyRotation_Audio) { + MockMediaSource source("bear-1280x720-a_frag-cenc-key_rotation.mp4", + kMP4Audio, kAppendWholeFile); + FakeEncryptedMedia encrypted_media(new RotatingKeyProvidingApp()); + StartPipelineWithEncryptedMedia(&source, &encrypted_media); + + source.EndOfStream(); + ASSERT_EQ(PIPELINE_OK, pipeline_status_); + + Play(); + + ASSERT_TRUE(WaitUntilOnEnded()); + source.Abort(); + Stop(); +} #endif // TODO(acolwell): Fix flakiness http://crbug.com/117921 @@ -1051,7 +1403,7 @@ TEST_F(PipelineIntegrationTest, ChunkDemuxerAbortRead_AudioOnly) { TEST_F(PipelineIntegrationTest, ChunkDemuxerAbortRead_VideoOnly) { ASSERT_TRUE(TestSeekDuringRead("bear-320x240-video-only.webm", kVideoOnlyWebM, 32768, - base::TimeDelta::FromMilliseconds(200), + base::TimeDelta::FromMilliseconds(167), base::TimeDelta::FromMilliseconds(1668), 0x1C896, 65536)); } @@ -1060,18 +1412,12 @@ TEST_F(PipelineIntegrationTest, ChunkDemuxerAbortRead_VideoOnly) { TEST_F(PipelineIntegrationTest, BasicPlayback_AudioOnly_Opus_WebM) { ASSERT_TRUE(Start(GetTestDataFilePath("bear-opus-end-trimming.webm"), PIPELINE_OK)); - EXPECT_EQ(1u, pipeline_->GetBufferedTimeRanges().size()); - EXPECT_EQ(0, pipeline_->GetBufferedTimeRanges().start(0).InMilliseconds()); - EXPECT_EQ(kOpusEndTrimmingWebMFileDurationMs, - pipeline_->GetBufferedTimeRanges().end(0).InMilliseconds()); Play(); ASSERT_TRUE(WaitUntilOnEnded()); } // Verify that VP9 video in WebM containers can be played back. -// TODO(fgalligan): Enable after new vp9 files are landed. -// http://crbug.com/259116 -TEST_F(PipelineIntegrationTest, DISABLED_BasicPlayback_VideoOnly_VP9_WebM) { +TEST_F(PipelineIntegrationTest, BasicPlayback_VideoOnly_VP9_WebM) { ASSERT_TRUE(Start(GetTestDataFilePath("bear-vp9.webm"), PIPELINE_OK)); Play(); @@ -1080,9 +1426,7 @@ TEST_F(PipelineIntegrationTest, DISABLED_BasicPlayback_VideoOnly_VP9_WebM) { // Verify that VP9 video and Opus audio in the same WebM container can be played // back. -// TODO(fgalligan): Enable after new vp9 files are landed. -// http://crbug.com/259116 -TEST_F(PipelineIntegrationTest, DISABLED_BasicPlayback_VP9_Opus_WebM) { +TEST_F(PipelineIntegrationTest, BasicPlayback_VP9_Opus_WebM) { ASSERT_TRUE(Start(GetTestDataFilePath("bear-vp9-opus.webm"), PIPELINE_OK)); Play(); @@ -1091,7 +1435,6 @@ TEST_F(PipelineIntegrationTest, DISABLED_BasicPlayback_VP9_Opus_WebM) { // Verify that VP8 video with alpha channel can be played back. TEST_F(PipelineIntegrationTest, BasicPlayback_VP8A_WebM) { - EXPECT_CALL(*this, OnSetOpaque(false)).Times(AnyNumber()); ASSERT_TRUE(Start(GetTestDataFilePath("bear-vp8a.webm"), PIPELINE_OK)); Play(); @@ -1101,7 +1444,6 @@ TEST_F(PipelineIntegrationTest, BasicPlayback_VP8A_WebM) { // Verify that VP8A video with odd width/height can be played back. TEST_F(PipelineIntegrationTest, BasicPlayback_VP8A_Odd_WebM) { - EXPECT_CALL(*this, OnSetOpaque(false)).Times(AnyNumber()); ASSERT_TRUE(Start(GetTestDataFilePath("bear-vp8a-odd-dimensions.webm"), PIPELINE_OK)); Play(); @@ -1118,4 +1460,21 @@ TEST_F(PipelineIntegrationTest, ASSERT_TRUE(WaitUntilOnEnded()); } +// Verify that VP9 video with 4:4:4 subsampling can be played back. +TEST_F(PipelineIntegrationTest, P444_VP9_WebM) { + ASSERT_TRUE(Start(GetTestDataFilePath("bear-320x240-P444.webm"), + PIPELINE_OK)); + Play(); + ASSERT_TRUE(WaitUntilOnEnded()); + EXPECT_EQ(last_video_frame_format_, VideoFrame::YV24); +} + +// Verify that videos with an odd frame size playback successfully. +TEST_F(PipelineIntegrationTest, BasicPlayback_OddVideoSize) { + ASSERT_TRUE(Start(GetTestDataFilePath("butterfly-853x480.webm"), + PIPELINE_OK)); + Play(); + ASSERT_TRUE(WaitUntilOnEnded()); +} + } // namespace media |