// Copyright 2013 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. #include <string> #include "base/basictypes.h" #include "base/memory/scoped_ptr.h" #include "base/strings/stringprintf.h" #include "media/base/android/media_codec_bridge.h" #include "media/base/android/media_drm_bridge.h" #include "media/base/android/media_player_manager.h" #include "media/base/android/media_source_player.h" #include "media/base/decoder_buffer.h" #include "media/base/test_data_util.h" #include "testing/gmock/include/gmock/gmock.h" #include "ui/gl/android/surface_texture.h" namespace media { static const int kDefaultDurationInMs = 10000; // Mock of MediaPlayerManager for testing purpose class MockMediaPlayerManager : public MediaPlayerManager { public: MockMediaPlayerManager() : num_requests_(0), last_seek_request_id_(0) {} virtual ~MockMediaPlayerManager() {}; // MediaPlayerManager implementation. virtual void RequestMediaResources(int player_id) OVERRIDE {} virtual void ReleaseMediaResources(int player_id) OVERRIDE {} virtual MediaResourceGetter* GetMediaResourceGetter() OVERRIDE { return NULL; } virtual void OnTimeUpdate(int player_id, base::TimeDelta current_time) OVERRIDE {} virtual void OnMediaMetadataChanged( int player_id, base::TimeDelta duration, int width, int height, bool success) OVERRIDE {} virtual void OnPlaybackComplete(int player_id) OVERRIDE { if (message_loop_.is_running()) message_loop_.Quit(); } virtual void OnMediaInterrupted(int player_id) OVERRIDE {} virtual void OnBufferingUpdate(int player_id, int percentage) OVERRIDE {} virtual void OnSeekComplete(int player_id, base::TimeDelta current_time) OVERRIDE {} virtual void OnError(int player_id, int error) OVERRIDE {} virtual void OnVideoSizeChanged(int player_id, int width, int height) OVERRIDE {} virtual MediaPlayerAndroid* GetFullscreenPlayer() OVERRIDE { return NULL; } virtual MediaPlayerAndroid* GetPlayer(int player_id) OVERRIDE { return NULL; } virtual void DestroyAllMediaPlayers() OVERRIDE {} virtual void OnReadFromDemuxer(int player_id, media::DemuxerStream::Type type) OVERRIDE { num_requests_++; if (message_loop_.is_running()) message_loop_.Quit(); } virtual void OnMediaSeekRequest(int player_id, base::TimeDelta time_to_seek, unsigned seek_request_id) OVERRIDE { last_seek_request_id_ = seek_request_id; } virtual void OnMediaConfigRequest(int player_id) OVERRIDE {} virtual media::MediaDrmBridge* GetDrmBridge(int media_keys_id) OVERRIDE { return NULL; } virtual void OnProtectedSurfaceRequested(int player_id) OVERRIDE {} virtual void OnKeyAdded(int key_id, const std::string& session_id) OVERRIDE {} virtual void OnKeyError(int key_id, const std::string& session_id, media::MediaKeys::KeyError error_code, int system_code) OVERRIDE {} virtual void OnKeyMessage(int key_id, const std::string& session_id, const std::vector<uint8>& message, const std::string& destination_url) OVERRIDE {} int num_requests() const { return num_requests_; } unsigned last_seek_request_id() const { return last_seek_request_id_; } base::MessageLoop* message_loop() { return &message_loop_; } private: // The number of request this object sents for decoding data. int num_requests_; unsigned last_seek_request_id_; base::MessageLoop message_loop_; }; class MediaSourcePlayerTest : public testing::Test { public: MediaSourcePlayerTest() { manager_.reset(new MockMediaPlayerManager()); player_.reset(new MediaSourcePlayer(0, manager_.get())); } virtual ~MediaSourcePlayerTest() {} protected: // Get the decoder job from the MediaSourcePlayer. MediaDecoderJob* GetMediaDecoderJob(bool is_audio) { if (is_audio) { return reinterpret_cast<MediaDecoderJob*>( player_->audio_decoder_job_.get()); } return reinterpret_cast<MediaDecoderJob*>( player_->video_decoder_job_.get()); } // Starts an audio decoder job. void StartAudioDecoderJob() { DemuxerConfigs configs; configs.audio_codec = kCodecVorbis; configs.audio_channels = 2; configs.audio_sampling_rate = 44100; configs.is_audio_encrypted = false; configs.duration_ms = kDefaultDurationInMs; scoped_refptr<DecoderBuffer> buffer = ReadTestDataFile("vorbis-extradata"); configs.audio_extra_data = std::vector<uint8>( buffer->data(), buffer->data() + buffer->data_size()); Start(configs); } void StartVideoDecoderJob() { DemuxerConfigs configs; configs.video_codec = kCodecVP8; configs.video_size = gfx::Size(320, 240); configs.is_video_encrypted = false; configs.duration_ms = kDefaultDurationInMs; Start(configs); } // Starts decoding the data. void Start(const DemuxerConfigs& configs) { player_->DemuxerReady(configs); player_->Start(); } DemuxerData CreateReadFromDemuxerAckForAudio(int packet_id) { DemuxerData data; data.type = DemuxerStream::AUDIO; data.access_units.resize(1); data.access_units[0].status = DemuxerStream::kOk; scoped_refptr<DecoderBuffer> buffer = ReadTestDataFile( base::StringPrintf("vorbis-packet-%d", packet_id)); data.access_units[0].data = std::vector<uint8>( buffer->data(), buffer->data() + buffer->data_size()); // Vorbis needs 4 extra bytes padding on Android to decode properly. Check // NuMediaExtractor.cpp in Android source code. uint8 padding[4] = { 0xff , 0xff , 0xff , 0xff }; data.access_units[0].data.insert( data.access_units[0].data.end(), padding, padding + 4); return data; } DemuxerData CreateReadFromDemuxerAckForVideo() { DemuxerData data; data.type = DemuxerStream::VIDEO; data.access_units.resize(1); data.access_units[0].status = DemuxerStream::kOk; scoped_refptr<DecoderBuffer> buffer = ReadTestDataFile("vp8-I-frame-320x240"); data.access_units[0].data = std::vector<uint8>( buffer->data(), buffer->data() + buffer->data_size()); return data; } DemuxerData CreateEOSAck(bool is_audio) { DemuxerData data; data.type = is_audio ? DemuxerStream::AUDIO : DemuxerStream::VIDEO; data.access_units.resize(1); data.access_units[0].status = DemuxerStream::kOk; data.access_units[0].end_of_stream = true; return data; } base::TimeTicks StartTimeTicks() { return player_->start_time_ticks_; } bool IsTypeSupported(const std::vector<uint8>& scheme_uuid, const std::string& security_level, const std::string& container, const std::vector<std::string>& codecs) { return MediaSourcePlayer::IsTypeSupported( scheme_uuid, security_level, container, codecs); } protected: scoped_ptr<MockMediaPlayerManager> manager_; scoped_ptr<MediaSourcePlayer> player_; DISALLOW_COPY_AND_ASSIGN(MediaSourcePlayerTest); }; TEST_F(MediaSourcePlayerTest, StartAudioDecoderWithValidConfig) { if (!MediaCodecBridge::IsAvailable()) return; // Test audio decoder job will be created when codec is successfully started. StartAudioDecoderJob(); EXPECT_TRUE(NULL != GetMediaDecoderJob(true)); EXPECT_EQ(1, manager_->num_requests()); } TEST_F(MediaSourcePlayerTest, StartAudioDecoderWithInvalidConfig) { if (!MediaCodecBridge::IsAvailable()) return; // Test audio decoder job will not be created when failed to start the codec. DemuxerConfigs configs; configs.audio_codec = kCodecVorbis; configs.audio_channels = 2; configs.audio_sampling_rate = 44100; configs.is_audio_encrypted = false; configs.duration_ms = kDefaultDurationInMs; uint8 invalid_codec_data[] = { 0x00, 0xff, 0xff, 0xff, 0xff }; configs.audio_extra_data.insert(configs.audio_extra_data.begin(), invalid_codec_data, invalid_codec_data + 4); Start(configs); EXPECT_EQ(NULL, GetMediaDecoderJob(true)); EXPECT_EQ(0, manager_->num_requests()); } TEST_F(MediaSourcePlayerTest, StartVideoCodecWithValidSurface) { if (!MediaCodecBridge::IsAvailable()) return; // Test video decoder job will be created when surface is valid. scoped_refptr<gfx::SurfaceTexture> surface_texture( new gfx::SurfaceTexture(0)); gfx::ScopedJavaSurface surface(surface_texture.get()); StartVideoDecoderJob(); // Video decoder job will not be created until surface is available. EXPECT_EQ(NULL, GetMediaDecoderJob(false)); EXPECT_EQ(0, manager_->num_requests()); player_->SetVideoSurface(surface.Pass()); EXPECT_EQ(1u, manager_->last_seek_request_id()); player_->OnSeekRequestAck(manager_->last_seek_request_id()); // The decoder job should be ready now. EXPECT_TRUE(NULL != GetMediaDecoderJob(false)); EXPECT_EQ(1, manager_->num_requests()); } TEST_F(MediaSourcePlayerTest, StartVideoCodecWithInvalidSurface) { if (!MediaCodecBridge::IsAvailable()) return; // Test video decoder job will be created when surface is valid. scoped_refptr<gfx::SurfaceTexture> surface_texture( new gfx::SurfaceTexture(0)); gfx::ScopedJavaSurface surface(surface_texture.get()); StartVideoDecoderJob(); // Video decoder job will not be created until surface is available. EXPECT_EQ(NULL, GetMediaDecoderJob(false)); EXPECT_EQ(0, manager_->num_requests()); // Release the surface texture. surface_texture = NULL; player_->SetVideoSurface(surface.Pass()); EXPECT_EQ(1u, manager_->last_seek_request_id()); player_->OnSeekRequestAck(manager_->last_seek_request_id()); EXPECT_EQ(NULL, GetMediaDecoderJob(false)); EXPECT_EQ(0, manager_->num_requests()); } TEST_F(MediaSourcePlayerTest, ReadFromDemuxerAfterSeek) { if (!MediaCodecBridge::IsAvailable()) return; // Test decoder job will resend a ReadFromDemuxer request after seek. StartAudioDecoderJob(); EXPECT_TRUE(NULL != GetMediaDecoderJob(true)); EXPECT_EQ(1, manager_->num_requests()); // Initiate a seek player_->SeekTo(base::TimeDelta()); // Verify that the seek does not occur until the initial prefetch // completes. EXPECT_EQ(0u, manager_->last_seek_request_id()); // Simulate aborted read caused by the seek. This aborts the initial // prefetch. DemuxerData data; data.type = DemuxerStream::AUDIO; data.access_units.resize(1); data.access_units[0].status = DemuxerStream::kAborted; player_->ReadFromDemuxerAck(data); // Verify that the seek is requested now that the initial prefetch // has completed. EXPECT_EQ(1u, manager_->last_seek_request_id()); // Sending back the seek ACK, this should trigger the player to call // OnReadFromDemuxer() again. player_->OnSeekRequestAck(manager_->last_seek_request_id()); EXPECT_EQ(2, manager_->num_requests()); } TEST_F(MediaSourcePlayerTest, SetSurfaceWhileSeeking) { if (!MediaCodecBridge::IsAvailable()) return; // Test SetVideoSurface() will not cause an extra seek while the player is // waiting for a seek ACK. scoped_refptr<gfx::SurfaceTexture> surface_texture( new gfx::SurfaceTexture(0)); gfx::ScopedJavaSurface surface(surface_texture.get()); StartVideoDecoderJob(); // Player is still waiting for SetVideoSurface(), so no request is sent. EXPECT_EQ(0, manager_->num_requests()); player_->SeekTo(base::TimeDelta()); EXPECT_EQ(1u, manager_->last_seek_request_id()); player_->SetVideoSurface(surface.Pass()); EXPECT_TRUE(NULL == GetMediaDecoderJob(false)); EXPECT_EQ(1u, manager_->last_seek_request_id()); // Send the seek ack, player should start requesting data afterwards. player_->OnSeekRequestAck(manager_->last_seek_request_id()); EXPECT_TRUE(NULL != GetMediaDecoderJob(false)); EXPECT_EQ(1, manager_->num_requests()); } TEST_F(MediaSourcePlayerTest, StartAfterSeekFinish) { if (!MediaCodecBridge::IsAvailable()) return; // Test decoder job will not start until all pending seek event is handled. DemuxerConfigs configs; configs.audio_codec = kCodecVorbis; configs.audio_channels = 2; configs.audio_sampling_rate = 44100; configs.is_audio_encrypted = false; configs.duration_ms = kDefaultDurationInMs; player_->DemuxerReady(configs); EXPECT_EQ(NULL, GetMediaDecoderJob(true)); EXPECT_EQ(0, manager_->num_requests()); // Initiate a seek player_->SeekTo(base::TimeDelta()); EXPECT_EQ(1u, manager_->last_seek_request_id()); player_->Start(); EXPECT_EQ(NULL, GetMediaDecoderJob(true)); EXPECT_EQ(0, manager_->num_requests()); // Sending back the seek ACK. player_->OnSeekRequestAck(manager_->last_seek_request_id()); EXPECT_TRUE(NULL != GetMediaDecoderJob(true)); EXPECT_EQ(1, manager_->num_requests()); } TEST_F(MediaSourcePlayerTest, StartImmediatelyAfterPause) { if (!MediaCodecBridge::IsAvailable()) return; // Test that if the decoding job is not fully stopped after Pause(), // calling Start() will be a noop. StartAudioDecoderJob(); MediaDecoderJob* decoder_job = GetMediaDecoderJob(true); EXPECT_TRUE(NULL != decoder_job); EXPECT_EQ(1, manager_->num_requests()); EXPECT_FALSE(GetMediaDecoderJob(true)->is_decoding()); // Sending data to player. player_->ReadFromDemuxerAck(CreateReadFromDemuxerAckForAudio(0)); EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding()); // Decoder job will not immediately stop after Pause() since it is // running on another thread. player_->Pause(); EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding()); // Nothing happens when calling Start() again. player_->Start(); // Verify that Start() will not destroy and recreate the decoder job. EXPECT_EQ(decoder_job, GetMediaDecoderJob(true)); EXPECT_EQ(1, manager_->num_requests()); EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding()); manager_->message_loop()->Run(); // The decoder job should finish and a new request will be sent. EXPECT_EQ(2, manager_->num_requests()); EXPECT_FALSE(GetMediaDecoderJob(true)->is_decoding()); } TEST_F(MediaSourcePlayerTest, DecoderJobsCannotStartWithoutAudio) { if (!MediaCodecBridge::IsAvailable()) return; // Test that when Start() is called, video decoder jobs will wait for audio // decoder job before start decoding the data. DemuxerConfigs configs; configs.audio_codec = kCodecVorbis; configs.audio_channels = 2; configs.audio_sampling_rate = 44100; configs.is_audio_encrypted = false; scoped_refptr<DecoderBuffer> buffer = ReadTestDataFile("vorbis-extradata"); configs.audio_extra_data = std::vector<uint8>( buffer->data(), buffer->data() + buffer->data_size()); configs.video_codec = kCodecVP8; configs.video_size = gfx::Size(320, 240); configs.is_video_encrypted = false; configs.duration_ms = kDefaultDurationInMs; Start(configs); EXPECT_EQ(0, manager_->num_requests()); scoped_refptr<gfx::SurfaceTexture> surface_texture( new gfx::SurfaceTexture(0)); gfx::ScopedJavaSurface surface(surface_texture.get()); player_->SetVideoSurface(surface.Pass()); EXPECT_EQ(1u, manager_->last_seek_request_id()); player_->OnSeekRequestAck(manager_->last_seek_request_id()); MediaDecoderJob* audio_decoder_job = GetMediaDecoderJob(true); MediaDecoderJob* video_decoder_job = GetMediaDecoderJob(false); EXPECT_EQ(2, manager_->num_requests()); EXPECT_FALSE(audio_decoder_job->is_decoding()); EXPECT_FALSE(video_decoder_job->is_decoding()); // Sending audio data to player, audio decoder should not start. player_->ReadFromDemuxerAck(CreateReadFromDemuxerAckForVideo()); EXPECT_FALSE(video_decoder_job->is_decoding()); // Sending video data to player, both decoders should start now. player_->ReadFromDemuxerAck(CreateReadFromDemuxerAckForAudio(0)); EXPECT_TRUE(audio_decoder_job->is_decoding()); EXPECT_TRUE(video_decoder_job->is_decoding()); } TEST_F(MediaSourcePlayerTest, StartTimeTicksResetAfterDecoderUnderruns) { if (!MediaCodecBridge::IsAvailable()) return; // Test start time ticks will reset after decoder job underruns. StartAudioDecoderJob(); EXPECT_TRUE(NULL != GetMediaDecoderJob(true)); EXPECT_EQ(1, manager_->num_requests()); // For the first couple chunks, the decoder job may return // DECODE_FORMAT_CHANGED status instead of DECODE_SUCCEEDED status. Decode // more frames to guarantee that DECODE_SUCCEEDED will be returned. for (int i = 0; i < 4; ++i) { player_->ReadFromDemuxerAck(CreateReadFromDemuxerAckForAudio(i)); EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding()); manager_->message_loop()->Run(); } // The decoder job should finish and a new request will be sent. EXPECT_EQ(5, manager_->num_requests()); EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding()); base::TimeTicks previous = StartTimeTicks(); // Let the decoder timeout and execute the OnDecoderStarved() callback. base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding()); EXPECT_TRUE(StartTimeTicks() != base::TimeTicks()); manager_->message_loop()->RunUntilIdle(); // Send new data to the decoder so it can finish the currently // pending decode. player_->ReadFromDemuxerAck(CreateReadFromDemuxerAckForAudio(3)); while(GetMediaDecoderJob(true)->is_decoding()) manager_->message_loop()->RunUntilIdle(); // Verify the start time ticks is cleared at this point because the // player is prefetching. EXPECT_TRUE(StartTimeTicks() == base::TimeTicks()); // Send new data to the decoder so it can finish prefetching. This should // reset the start time ticks. player_->ReadFromDemuxerAck(CreateReadFromDemuxerAckForAudio(3)); EXPECT_TRUE(StartTimeTicks() != base::TimeTicks()); base::TimeTicks current = StartTimeTicks(); EXPECT_LE(100.0, (current - previous).InMillisecondsF()); } TEST_F(MediaSourcePlayerTest, NoRequestForDataAfterInputEOS) { if (!MediaCodecBridge::IsAvailable()) return; // Test MediaSourcePlayer will not request for new data after input EOS is // reached. scoped_refptr<gfx::SurfaceTexture> surface_texture( new gfx::SurfaceTexture(0)); gfx::ScopedJavaSurface surface(surface_texture.get()); player_->SetVideoSurface(surface.Pass()); StartVideoDecoderJob(); player_->OnSeekRequestAck(manager_->last_seek_request_id()); EXPECT_EQ(1, manager_->num_requests()); // Send the first input chunk. player_->ReadFromDemuxerAck(CreateReadFromDemuxerAckForVideo()); manager_->message_loop()->Run(); EXPECT_EQ(2, manager_->num_requests()); // Send EOS. player_->ReadFromDemuxerAck(CreateEOSAck(false)); manager_->message_loop()->Run(); // No more request for data should be made. EXPECT_EQ(2, manager_->num_requests()); } // Crashes. http://crbug.com/287817 TEST_F(MediaSourcePlayerTest, DISABLED_CanPlayType_Widevine) { if (!MediaCodecBridge::IsAvailable() || !MediaDrmBridge::IsAvailable()) return; uint8 kWidevineUUID[] = { 0xED, 0xEF, 0x8B, 0xA9, 0x79, 0xD6, 0x4A, 0xCE, 0xA3, 0xC8, 0x27, 0xDC, 0xD5, 0x1D, 0x21, 0xED }; std::vector<uint8> widevine_uuid(kWidevineUUID, kWidevineUUID + arraysize(kWidevineUUID)); std::vector<std::string> codec_avc(1, "avc1"); EXPECT_TRUE(IsTypeSupported(widevine_uuid, "L3", "video/mp4", codec_avc)); EXPECT_TRUE(IsTypeSupported(widevine_uuid, "L1", "video/mp4", codec_avc)); std::vector<std::string> codec_vp8(1, "vp8"); EXPECT_TRUE(IsTypeSupported(widevine_uuid, "L3", "video/webm", codec_vp8)); EXPECT_FALSE(IsTypeSupported(widevine_uuid, "L1", "video/webm", codec_vp8)); std::vector<std::string> codec_vorbis(1, "vorbis"); EXPECT_TRUE(IsTypeSupported(widevine_uuid, "L3", "video/webm", codec_vorbis)); EXPECT_FALSE( IsTypeSupported(widevine_uuid, "L1", "video/webm", codec_vorbis)); } TEST_F(MediaSourcePlayerTest, CanPlayType_InvalidUUID) { if (!MediaCodecBridge::IsAvailable() || !MediaDrmBridge::IsAvailable()) return; uint8 kInvalidUUID[] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF }; std::vector<uint8> invalid_uuid(kInvalidUUID, kInvalidUUID + arraysize(kInvalidUUID)); std::vector<std::string> codec_avc(1, "avc1"); EXPECT_FALSE(IsTypeSupported(invalid_uuid, "L3", "video/mp4", codec_avc)); EXPECT_FALSE(IsTypeSupported(invalid_uuid, "L1", "video/mp4", codec_avc)); } // TODO(xhwang): Are these IsTypeSupported tests device specific? // TODO(xhwang): Add more IsTypeSupported tests. } // namespace media