OLD | NEW |
(Empty) | |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "base/bind.h" |
| 6 #include "base/logging.h" |
| 7 #include "base/strings/stringprintf.h" |
| 8 #include "base/timer/timer.h" |
| 9 #include "media/base/android/demuxer_android.h" |
| 10 #include "media/base/android/media_codec_bridge.h" |
| 11 #include "media/base/android/media_codec_player.h" |
| 12 #include "media/base/android/media_player_manager.h" |
| 13 #include "media/base/decoder_buffer.h" |
| 14 #include "media/base/test_data_util.h" |
| 15 #include "testing/gtest/include/gtest/gtest.h" |
| 16 |
| 17 namespace media { |
| 18 |
| 19 // Helper macro to skip the test if MediaCodecBridge isn't available. |
| 20 #define SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE() \ |
| 21 do { \ |
| 22 if (!MediaCodecBridge::IsAvailable()) { \ |
| 23 VLOG(0) << "Could not run test - not supported on device."; \ |
| 24 return; \ |
| 25 } \ |
| 26 } while (0) |
| 27 |
| 28 |
| 29 #define RUN_ON_MEDIA_THREAD(CLASS, METHOD, ...) \ |
| 30 do { \ |
| 31 if (!GetMediaTaskRunner()->BelongsToCurrentThread()) { \ |
| 32 GetMediaTaskRunner()->PostTask( \ |
| 33 FROM_HERE, \ |
| 34 base::Bind(&CLASS :: METHOD, base::Unretained(this), ##__VA_ARGS__));\ |
| 35 return; \ |
| 36 } \ |
| 37 } while(0) |
| 38 |
| 39 namespace { |
| 40 const base::TimeDelta kDefaultTimeout = base::TimeDelta::FromMilliseconds(200); |
| 41 } |
| 42 |
| 43 // Mock of MediaPlayerManager for testing purpose. |
| 44 |
| 45 class MockMediaPlayerManager : public MediaPlayerManager { |
| 46 public: |
| 47 MockMediaPlayerManager() |
| 48 : playback_completed_(false), |
| 49 weak_ptr_factory_(this) {} |
| 50 ~MockMediaPlayerManager() override {} |
| 51 |
| 52 MediaResourceGetter* GetMediaResourceGetter() override { return nullptr; } |
| 53 MediaUrlInterceptor* GetMediaUrlInterceptor() override { return nullptr; } |
| 54 void OnTimeUpdate(int player_id, |
| 55 base::TimeDelta current_timestamp, |
| 56 base::TimeTicks current_time_ticks) override {} |
| 57 void OnMediaMetadataChanged( |
| 58 int player_id, |
| 59 base::TimeDelta duration, |
| 60 int width, |
| 61 int height, |
| 62 bool success) override { |
| 63 media_metadata_.duration = duration; |
| 64 media_metadata_.width = width; |
| 65 media_metadata_.height = height; |
| 66 media_metadata_.modified = true; |
| 67 } |
| 68 |
| 69 void OnPlaybackComplete(int player_id) override { |
| 70 playback_completed_ = true; |
| 71 } |
| 72 void OnMediaInterrupted(int player_id) override {} |
| 73 void OnBufferingUpdate(int player_id, int percentage) override {} |
| 74 void OnSeekComplete( |
| 75 int player_id, |
| 76 const base::TimeDelta& current_time) override {} |
| 77 void OnError(int player_id, int error) override {} |
| 78 void OnVideoSizeChanged(int player_id, int width, int height) override {} |
| 79 void OnAudibleStateChanged(int player_id, bool is_audible_now) override {} |
| 80 void OnWaitingForDecryptionKey(int player_id) override {} |
| 81 MediaPlayerAndroid* GetFullscreenPlayer() override { return nullptr; } |
| 82 MediaPlayerAndroid* GetPlayer(int player_id) override { return nullptr; } |
| 83 void RequestFullScreen(int player_id) override {} |
| 84 |
| 85 void OnMediaResourcesRequested(int player_id) {} |
| 86 |
| 87 base::WeakPtr<MockMediaPlayerManager> GetWeakPtr() { |
| 88 return weak_ptr_factory_.GetWeakPtr(); |
| 89 } |
| 90 |
| 91 // Conditions to wait for. |
| 92 bool IsMetadataChanged() const { return media_metadata_.modified; } |
| 93 bool IsPlaybackCompleted() const { return playback_completed_; } |
| 94 |
| 95 struct MediaMetadata { |
| 96 base::TimeDelta duration; |
| 97 int width; |
| 98 int height; |
| 99 bool modified; |
| 100 MediaMetadata() : width(0), height(0), modified(false) {} |
| 101 }; |
| 102 MediaMetadata media_metadata_; |
| 103 |
| 104 private: |
| 105 bool playback_completed_; |
| 106 |
| 107 base::WeakPtrFactory<MockMediaPlayerManager> weak_ptr_factory_; |
| 108 |
| 109 DISALLOW_COPY_AND_ASSIGN(MockMediaPlayerManager); |
| 110 }; |
| 111 |
| 112 // ChunkFactory defines how the data will be generated. |
| 113 |
| 114 struct ChunkFactory { |
| 115 virtual ~ChunkFactory() {} |
| 116 // Returns true if data is created. |
| 117 virtual bool CreateChunk(DemuxerStream::Type stream_type, |
| 118 DemuxerData* chunk, base::TimeDelta* delay) = 0; |
| 119 }; |
| 120 |
| 121 class AudioFactory : public ChunkFactory { |
| 122 public: |
| 123 AudioFactory(const base::TimeDelta& duration, |
| 124 const base::TimeDelta& frame_period); |
| 125 |
| 126 bool CreateChunk(DemuxerStream::Type stream_type, |
| 127 DemuxerData* chunk, base::TimeDelta* delay) override; |
| 128 |
| 129 private: |
| 130 base::TimeDelta duration_; |
| 131 base::TimeDelta frame_period_; |
| 132 std::vector<uint8> packet_[4]; |
| 133 base::TimeDelta current_pts_; |
| 134 }; |
| 135 |
| 136 AudioFactory::AudioFactory(const base::TimeDelta& duration, |
| 137 const base::TimeDelta& frame_period) |
| 138 : duration_(duration), |
| 139 frame_period_(frame_period) { |
| 140 // Load packets |
| 141 for (int i = 0; i < 4; ++i) { |
| 142 scoped_refptr<DecoderBuffer> buffer = |
| 143 ReadTestDataFile(base::StringPrintf("vorbis-packet-%d", i)); |
| 144 packet_[i] = std::vector<uint8>( |
| 145 buffer->data(), buffer->data() + buffer->data_size()); |
| 146 } |
| 147 } |
| 148 |
| 149 bool AudioFactory::CreateChunk(DemuxerStream::Type stream_type, |
| 150 DemuxerData* chunk, base::TimeDelta* delay) { |
| 151 if (stream_type != DemuxerStream::AUDIO) |
| 152 return false; |
| 153 |
| 154 DCHECK(chunk); |
| 155 DCHECK(delay); |
| 156 |
| 157 *delay = base::TimeDelta(); |
| 158 |
| 159 chunk->type = stream_type; |
| 160 for (int i = 0; i < 4; ++i) { |
| 161 chunk->access_units.push_back(AccessUnit()); |
| 162 AccessUnit& unit = chunk->access_units.back(); |
| 163 unit.status = DemuxerStream::kOk; |
| 164 |
| 165 unit.timestamp = current_pts_; |
| 166 current_pts_ += frame_period_; |
| 167 |
| 168 if (unit.timestamp > duration_) { |
| 169 unit.is_end_of_stream = true; |
| 170 break; // EOS units have no data |
| 171 } |
| 172 |
| 173 unit.data = packet_[i]; |
| 174 // Vorbis needs 4 extra bytes padding on Android to decode properly. Check |
| 175 // NuMediaExtractor.cpp in Android source code. |
| 176 uint8 padding[4] = { 0xff , 0xff , 0xff , 0xff }; |
| 177 unit.data.insert(unit.data.end(), padding, padding + 4); |
| 178 } |
| 179 return true; |
| 180 } |
| 181 |
| 182 // Mock of DemuxerAndroid for testing purpose. |
| 183 |
| 184 class MockDemuxerAndroid : public DemuxerAndroid { |
| 185 public: |
| 186 MockDemuxerAndroid() : client_(nullptr) {} |
| 187 ~MockDemuxerAndroid() override {} |
| 188 |
| 189 // DemuxerAndroid implementation |
| 190 void Initialize(DemuxerAndroidClient* client) override; |
| 191 void RequestDemuxerData(DemuxerStream::Type type) override; |
| 192 void RequestDemuxerSeek(const base::TimeDelta& time_to_seek, |
| 193 bool is_browser_seek) override {} |
| 194 |
| 195 // Post DemuxerConfigs to the client (i.e. the player) on correct thread. |
| 196 void PostConfigs(const DemuxerConfigs& configs); |
| 197 |
| 198 // Sets the data rule that governs the process of packet generation. |
| 199 void SetChunkFactory(scoped_ptr<ChunkFactory> factory) { |
| 200 chunk_factory_ = factory.Pass(); |
| 201 } |
| 202 |
| 203 // Conditions to wait for. |
| 204 bool IsInitialized() const { return client_; } |
| 205 bool HasPendingConfigs() const { return pending_configs_; } |
| 206 |
| 207 private: |
| 208 DemuxerAndroidClient* client_; |
| 209 scoped_ptr<DemuxerConfigs> pending_configs_; |
| 210 scoped_ptr<ChunkFactory> chunk_factory_; |
| 211 |
| 212 DISALLOW_COPY_AND_ASSIGN(MockDemuxerAndroid); |
| 213 }; |
| 214 |
| 215 void MockDemuxerAndroid::Initialize(DemuxerAndroidClient* client) { |
| 216 DVLOG(1) << "MockDemuxerAndroid::" << __FUNCTION__; |
| 217 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); |
| 218 |
| 219 client_ = client; |
| 220 if (pending_configs_) |
| 221 client_->OnDemuxerConfigsAvailable(*pending_configs_); |
| 222 } |
| 223 |
| 224 void MockDemuxerAndroid::RequestDemuxerData(DemuxerStream::Type type) { |
| 225 DCHECK(chunk_factory_); |
| 226 DCHECK(client_); |
| 227 |
| 228 DemuxerData chunk; |
| 229 base::TimeDelta delay; |
| 230 if (chunk_factory_->CreateChunk(type, &chunk, &delay)) { |
| 231 // Post to Media thread. |
| 232 GetMediaTaskRunner()->PostDelayedTask( |
| 233 FROM_HERE, |
| 234 base::Bind(&DemuxerAndroidClient::OnDemuxerDataAvailable, |
| 235 base::Unretained(client_), chunk), |
| 236 delay); |
| 237 } |
| 238 } |
| 239 |
| 240 void MockDemuxerAndroid::PostConfigs(const DemuxerConfigs& configs) { |
| 241 DVLOG(1) << "MockDemuxerAndroid::" << __FUNCTION__; |
| 242 RUN_ON_MEDIA_THREAD(MockDemuxerAndroid, PostConfigs, configs); |
| 243 |
| 244 DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); |
| 245 |
| 246 if (client_) |
| 247 client_->OnDemuxerConfigsAvailable(configs); |
| 248 else |
| 249 pending_configs_ = scoped_ptr<DemuxerConfigs>(new DemuxerConfigs(configs)); |
| 250 } |
| 251 |
| 252 // The test fixture for MediaCodecPlayer |
| 253 |
| 254 class MediaCodecPlayerTest : public testing::Test { |
| 255 public: |
| 256 MediaCodecPlayerTest(); |
| 257 ~MediaCodecPlayerTest() override; |
| 258 |
| 259 protected: |
| 260 typedef base::Callback<bool ()> Predicate; |
| 261 |
| 262 void CreatePlayer(); |
| 263 |
| 264 // Waits for condition to become true or for timeout to expire. |
| 265 // Returns true if the condition becomes true. |
| 266 bool WaitForCondition(const Predicate& condition, |
| 267 const base::TimeDelta& timeout = kDefaultTimeout); |
| 268 |
| 269 DemuxerConfigs CreateAudioConfigs( |
| 270 AudioCodec audio_codec, const base::TimeDelta& duration); |
| 271 DemuxerConfigs CreateVideoConfigs(const base::TimeDelta& duration); |
| 272 DemuxerConfigs CreateAudioVideoConfigs(const base::TimeDelta& duration); |
| 273 |
| 274 base::MessageLoop message_loop_; |
| 275 MockMediaPlayerManager manager_; |
| 276 MockDemuxerAndroid* demuxer_; // owned by player_ |
| 277 MediaCodecPlayer* player_; // raw pointer due to DeleteOnCorrectThread() |
| 278 |
| 279 private: |
| 280 bool is_timeout_expired() const { return is_timeout_expired_; } |
| 281 void SetTimeoutExpired(bool value) { |
| 282 is_timeout_expired_ = value; |
| 283 } |
| 284 |
| 285 bool is_timeout_expired_; |
| 286 |
| 287 DISALLOW_COPY_AND_ASSIGN(MediaCodecPlayerTest); |
| 288 }; |
| 289 |
| 290 MediaCodecPlayerTest::MediaCodecPlayerTest() |
| 291 : demuxer_(new MockDemuxerAndroid()), |
| 292 player_(nullptr) { |
| 293 } |
| 294 |
| 295 void MediaCodecPlayerTest::CreatePlayer() { |
| 296 DCHECK(demuxer_); |
| 297 player_ = new MediaCodecPlayer( |
| 298 0, // player_id |
| 299 manager_.GetWeakPtr(), |
| 300 base::Bind(&MockMediaPlayerManager::OnMediaResourcesRequested, |
| 301 base::Unretained(&manager_)), |
| 302 scoped_ptr<MockDemuxerAndroid>(demuxer_), |
| 303 GURL()); |
| 304 |
| 305 DCHECK(player_); |
| 306 } |
| 307 |
| 308 MediaCodecPlayerTest::~MediaCodecPlayerTest() { |
| 309 if (player_) |
| 310 player_->DeleteOnCorrectThread(); |
| 311 } |
| 312 |
| 313 bool MediaCodecPlayerTest::WaitForCondition(const Predicate& condition, |
| 314 const base::TimeDelta& timeout) { |
| 315 // Let the message_loop_ process events. |
| 316 // We start the timer and RunUntilIdle() until it signals. |
| 317 |
| 318 SetTimeoutExpired(false); |
| 319 |
| 320 base::Timer timer(false, false); |
| 321 timer.Start(FROM_HERE, timeout, |
| 322 base::Bind(&MediaCodecPlayerTest::SetTimeoutExpired, |
| 323 base::Unretained(this), true)); |
| 324 |
| 325 do { |
| 326 if (condition.Run()) { |
| 327 timer.Stop(); |
| 328 return true; |
| 329 } |
| 330 message_loop_.RunUntilIdle(); |
| 331 } while (!is_timeout_expired()); |
| 332 |
| 333 DCHECK(!timer.IsRunning()); |
| 334 return false; |
| 335 } |
| 336 |
| 337 DemuxerConfigs MediaCodecPlayerTest::CreateAudioConfigs( |
| 338 AudioCodec audio_codec, const base::TimeDelta& duration) { |
| 339 |
| 340 DemuxerConfigs configs; |
| 341 configs.audio_codec = audio_codec; |
| 342 configs.audio_channels = 2; |
| 343 configs.is_audio_encrypted = false; |
| 344 configs.duration = duration; |
| 345 |
| 346 // Other codecs are not yet supported by this helper. |
| 347 EXPECT_TRUE(audio_codec == kCodecAAC || audio_codec == kCodecVorbis); |
| 348 |
| 349 switch (audio_codec) { |
| 350 case kCodecVorbis: |
| 351 { |
| 352 configs.audio_sampling_rate = 44100; |
| 353 scoped_refptr<DecoderBuffer> buffer = ReadTestDataFile( |
| 354 "vorbis-extradata"); |
| 355 configs.audio_extra_data = std::vector<uint8>( |
| 356 buffer->data(), |
| 357 buffer->data() + buffer->data_size()); |
| 358 } |
| 359 break; |
| 360 |
| 361 case kCodecAAC: |
| 362 { |
| 363 configs.audio_sampling_rate = 48000; |
| 364 uint8 aac_extra_data[] = { 0x13, 0x10 }; |
| 365 configs.audio_extra_data = std::vector<uint8>( |
| 366 aac_extra_data, |
| 367 aac_extra_data + 2); |
| 368 } |
| 369 break; |
| 370 |
| 371 default: |
| 372 NOTREACHED(); |
| 373 break; |
| 374 } |
| 375 |
| 376 return configs; |
| 377 } |
| 378 |
| 379 DemuxerConfigs MediaCodecPlayerTest::CreateVideoConfigs( |
| 380 const base::TimeDelta& duration) { |
| 381 DemuxerConfigs configs; |
| 382 configs.video_codec = kCodecVP8; |
| 383 configs.video_size = gfx::Size(320, 240); |
| 384 configs.is_video_encrypted = false; |
| 385 return configs; |
| 386 } |
| 387 |
| 388 DemuxerConfigs MediaCodecPlayerTest::CreateAudioVideoConfigs( |
| 389 const base::TimeDelta& duration) { |
| 390 DemuxerConfigs configs = CreateAudioConfigs(kCodecVorbis, duration); |
| 391 configs.video_codec = kCodecVP8; |
| 392 configs.video_size = gfx::Size(320, 240); |
| 393 configs.is_video_encrypted = false; |
| 394 return configs; |
| 395 } |
| 396 |
| 397 TEST_F(MediaCodecPlayerTest, SetAudioConfigsBeforePlayerCreation) { |
| 398 // Post configuration when there is no player yet. |
| 399 EXPECT_EQ(nullptr, player_); |
| 400 |
| 401 base::TimeDelta duration = base::TimeDelta::FromSeconds(10); |
| 402 demuxer_->PostConfigs(CreateAudioConfigs(kCodecVorbis, duration)); |
| 403 |
| 404 // Wait until the configuration gets to the media thread. |
| 405 WaitForCondition(base::Bind(&MockDemuxerAndroid::HasPendingConfigs, |
| 406 base::Unretained(demuxer_))); |
| 407 |
| 408 // Then create the player. |
| 409 CreatePlayer(); |
| 410 |
| 411 // Configuration should propagate through the player and to the manager. |
| 412 WaitForCondition(base::Bind(&MockMediaPlayerManager::IsMetadataChanged, |
| 413 base::Unretained(&manager_))); |
| 414 |
| 415 EXPECT_EQ(duration, manager_.media_metadata_.duration); |
| 416 EXPECT_EQ(0, manager_.media_metadata_.width); |
| 417 EXPECT_EQ(0, manager_.media_metadata_.height); |
| 418 } |
| 419 |
| 420 TEST_F(MediaCodecPlayerTest, SetAudioConfigsAfterPlayerCreation) { |
| 421 CreatePlayer(); |
| 422 |
| 423 // Wait till the player is initialized on media thread. |
| 424 WaitForCondition(base::Bind(&MockDemuxerAndroid::IsInitialized, |
| 425 base::Unretained(demuxer_))); |
| 426 |
| 427 // Post configuration after the player has been initialized. |
| 428 base::TimeDelta duration = base::TimeDelta::FromSeconds(10); |
| 429 demuxer_->PostConfigs(CreateAudioConfigs(kCodecVorbis, duration)); |
| 430 |
| 431 // Configuration should propagate through the player and to the manager. |
| 432 WaitForCondition(base::Bind(&MockMediaPlayerManager::IsMetadataChanged, |
| 433 base::Unretained(&manager_))); |
| 434 |
| 435 EXPECT_EQ(duration, manager_.media_metadata_.duration); |
| 436 EXPECT_EQ(0, manager_.media_metadata_.width); |
| 437 EXPECT_EQ(0, manager_.media_metadata_.height); |
| 438 } |
| 439 |
| 440 TEST_F(MediaCodecPlayerTest, SetAudioVideoConfigsAfterPlayerCreation) { |
| 441 CreatePlayer(); |
| 442 |
| 443 // Wait till the player is initialized on media thread. |
| 444 WaitForCondition(base::Bind(&MockDemuxerAndroid::IsInitialized, |
| 445 base::Unretained(demuxer_))); |
| 446 |
| 447 // Post configuration after the player has been initialized. |
| 448 base::TimeDelta duration = base::TimeDelta::FromSeconds(10); |
| 449 demuxer_->PostConfigs(CreateAudioVideoConfigs(duration)); |
| 450 |
| 451 // Configuration should propagate through the player and to the manager. |
| 452 WaitForCondition(base::Bind(&MockMediaPlayerManager::IsMetadataChanged, |
| 453 base::Unretained(&manager_))); |
| 454 |
| 455 EXPECT_EQ(duration, manager_.media_metadata_.duration); |
| 456 EXPECT_EQ(320, manager_.media_metadata_.width); |
| 457 EXPECT_EQ(240, manager_.media_metadata_.height); |
| 458 } |
| 459 |
| 460 TEST_F(MediaCodecPlayerTest, PlayAudioTillCompletion) { |
| 461 SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| 462 |
| 463 base::TimeDelta duration = base::TimeDelta::FromMilliseconds(1000); |
| 464 base::TimeDelta frame_period = base::TimeDelta::FromMilliseconds(20); |
| 465 demuxer_->SetChunkFactory(scoped_ptr<ChunkFactory>( |
| 466 new AudioFactory(duration, frame_period))); |
| 467 |
| 468 CreatePlayer(); |
| 469 |
| 470 // Wait till the player is initialized on media thread. |
| 471 WaitForCondition(base::Bind(&MockDemuxerAndroid::IsInitialized, |
| 472 base::Unretained(demuxer_))); |
| 473 |
| 474 // Post configuration after the player has been initialized. |
| 475 demuxer_->PostConfigs(CreateAudioConfigs(kCodecVorbis, duration)); |
| 476 |
| 477 EXPECT_FALSE(manager_.IsPlaybackCompleted()); |
| 478 |
| 479 player_->Start(); |
| 480 |
| 481 bool playback_completed = |
| 482 WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackCompleted, |
| 483 base::Unretained(&manager_)), |
| 484 base::TimeDelta::FromMilliseconds(1100)); |
| 485 EXPECT_TRUE(playback_completed); |
| 486 } |
| 487 |
| 488 } // namespace media |
OLD | NEW |