Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | |
|
watk
2015/06/05 01:00:33
s/2013/2015
Tima Vaisburd
2015/06/05 04:17:47
Done.
| |
| 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 kDefaultDelay = 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 delay to expire. | |
| 265 // Returns true if the condition becomes true. | |
| 266 bool WaitForCondition(const Predicate& condition, | |
| 267 const base::TimeDelta& delay = kDefaultDelay); | |
| 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_delay_expired() const { return is_delay_expired_; } | |
| 281 void SetDelayExpired(bool value) { | |
| 282 is_delay_expired_ = value; | |
| 283 } | |
| 284 | |
| 285 bool is_delay_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& delay) { | |
| 315 // Let the message_loop_ process events. | |
| 316 // We post delayed task and RunUntilIdle() until it signals. | |
| 317 | |
| 318 SetDelayExpired(false); | |
| 319 | |
| 320 base::Timer timer(false, false); | |
| 321 timer.Start(FROM_HERE, delay, | |
| 322 base::Bind(&MediaCodecPlayerTest::SetDelayExpired, | |
| 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_delay_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 |