| // Copyright 2015 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 <stdint.h> |
| |
| #include "base/bind.h" |
| #include "base/logging.h" |
| #include "base/timer/timer.h" |
| #include "media/base/android/demuxer_android.h" |
| #include "media/base/android/media_codec_player.h" |
| #include "media/base/android/media_codec_util.h" |
| #include "media/base/android/media_player_manager.h" |
| #include "media/base/android/media_task_runner.h" |
| #include "media/base/android/sdk_media_codec_bridge.h" |
| #include "media/base/android/test_data_factory.h" |
| #include "media/base/android/test_statistics.h" |
| #include "media/base/timestamp_constants.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/gl/android/surface_texture.h" |
| |
| namespace media { |
| |
| #define RUN_ON_MEDIA_THREAD(CLASS, METHOD, ...) \ |
| do { \ |
| if (!GetMediaTaskRunner()->BelongsToCurrentThread()) { \ |
| GetMediaTaskRunner()->PostTask( \ |
| FROM_HERE, \ |
| base::Bind(&CLASS::METHOD, base::Unretained(this), ##__VA_ARGS__)); \ |
| return; \ |
| } \ |
| } while (0) |
| |
| namespace { |
| |
| const base::TimeDelta kDefaultTimeout = base::TimeDelta::FromMilliseconds(200); |
| const base::TimeDelta kAudioFramePeriod = |
| base::TimeDelta::FromSecondsD(1024.0 / 44100); // 1024 samples @ 44100 Hz |
| const base::TimeDelta kVideoFramePeriod = base::TimeDelta::FromMilliseconds(20); |
| |
| enum Flags { |
| kAlwaysReconfigAudio = 0x1, |
| kAlwaysReconfigVideo = 0x2, |
| }; |
| |
| // The predicate that always returns false, used for WaitForDelay implementation |
| bool AlwaysFalse() { |
| return false; |
| } |
| |
| // The method used to compare two time values of type T in expectations. |
| // Type T requires that a difference of type base::TimeDelta is defined. |
| template <typename T> |
| bool AlmostEqual(T a, T b, double tolerance_ms) { |
| return (a - b).magnitude().InMilliseconds() <= tolerance_ms; |
| } |
| |
| // A helper function to calculate the expected number of frames. |
| int GetFrameCount(base::TimeDelta duration, |
| base::TimeDelta frame_period, |
| int num_reconfigs) { |
| // A chunk has 4 access units. The last unit timestamp must exceed the |
| // duration. Last chunk has 3 regular access units and one stand-alone EOS |
| // unit that we do not count. |
| |
| // Number of time intervals to exceed duration. |
| int num_intervals = duration / frame_period + 1.0; |
| |
| // To cover these intervals we need one extra unit at the beginning and a one |
| // for each reconfiguration. |
| int num_units = num_intervals + 1 + num_reconfigs; |
| |
| // Number of 4-unit chunks that hold these units: |
| int num_chunks = (num_units + 3) / 4; |
| |
| // Altogether these chunks hold 4*num_chunks units, but we do not count |
| // reconfiguration units and last EOS as frames. |
| return 4 * num_chunks - 1 - num_reconfigs; |
| } |
| |
| // Mock of MediaPlayerManager for testing purpose. |
| |
| class MockMediaPlayerManager : public MediaPlayerManager { |
| public: |
| MockMediaPlayerManager() |
| : playback_allowed_(true), |
| playback_completed_(false), |
| num_seeks_completed_(0), |
| num_audio_codecs_created_(0), |
| num_video_codecs_created_(0), |
| weak_ptr_factory_(this) {} |
| ~MockMediaPlayerManager() override {} |
| |
| MediaResourceGetter* GetMediaResourceGetter() override { return nullptr; } |
| MediaUrlInterceptor* GetMediaUrlInterceptor() override { return nullptr; } |
| |
| // Regular time update callback, reports current playback time to |
| // MediaPlayerManager. |
| void OnTimeUpdate(int player_id, |
| base::TimeDelta current_timestamp, |
| base::TimeTicks current_time_ticks) override { |
| pts_stat_.AddValue(current_timestamp); |
| } |
| |
| void OnMediaMetadataChanged(int player_id, |
| base::TimeDelta duration, |
| int width, |
| int height, |
| bool success) override { |
| media_metadata_.duration = duration; |
| media_metadata_.width = width; |
| media_metadata_.height = height; |
| media_metadata_.modified = true; |
| } |
| |
| void OnPlaybackComplete(int player_id) override { |
| playback_completed_ = true; |
| } |
| |
| void OnMediaInterrupted(int player_id) override {} |
| void OnBufferingUpdate(int player_id, int percentage) override {} |
| void OnSeekComplete(int player_id, |
| const base::TimeDelta& current_time) override { |
| ++num_seeks_completed_; |
| } |
| void OnError(int player_id, int error) override {} |
| void OnVideoSizeChanged(int player_id, int width, int height) override {} |
| void OnWaitingForDecryptionKey(int player_id) override {} |
| MediaPlayerAndroid* GetFullscreenPlayer() override { return nullptr; } |
| MediaPlayerAndroid* GetPlayer(int player_id) override { return nullptr; } |
| bool RequestPlay(int player_id, base::TimeDelta duration) override { |
| return playback_allowed_; |
| } |
| |
| void OnMediaResourcesRequested(int player_id) {} |
| |
| // Time update callback that reports the internal progress of the stream. |
| // Implementation dependent, used for testing only. |
| void OnDecodersTimeUpdate(DemuxerStream::Type stream_type, |
| base::TimeDelta now_playing, |
| base::TimeDelta last_buffered) { |
| render_stat_[stream_type].AddValue( |
| PTSTime(now_playing, base::TimeTicks::Now())); |
| } |
| |
| // Notification called on MediaCodec creation. |
| // Implementation dependent, used for testing only. |
| void OnMediaCodecCreated(DemuxerStream::Type stream_type) { |
| if (stream_type == DemuxerStream::AUDIO) |
| ++num_audio_codecs_created_; |
| else if (stream_type == DemuxerStream::VIDEO) |
| ++num_video_codecs_created_; |
| } |
| |
| // First frame information |
| base::TimeDelta FirstFramePTS(DemuxerStream::Type stream_type) const { |
| return render_stat_[stream_type].min().pts; |
| } |
| base::TimeTicks FirstFrameTime(DemuxerStream::Type stream_type) const { |
| return render_stat_[stream_type].min().time; |
| } |
| |
| base::WeakPtr<MockMediaPlayerManager> GetWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| void SetPlaybackAllowed(bool value) { playback_allowed_ = value; } |
| |
| // Conditions to wait for. |
| bool IsMetadataChanged() const { return media_metadata_.modified; } |
| bool IsPlaybackCompleted() const { return playback_completed_; } |
| bool IsPlaybackStarted() const { return pts_stat_.num_values() > 0; } |
| bool IsPlaybackBeyondPosition(const base::TimeDelta& pts) const { |
| return pts_stat_.max() > pts; |
| } |
| bool IsSeekCompleted() const { return num_seeks_completed_ > 0; } |
| bool HasFirstFrame(DemuxerStream::Type stream_type) const { |
| return render_stat_[stream_type].num_values() != 0; |
| } |
| |
| int num_audio_codecs_created() const { return num_audio_codecs_created_; } |
| int num_video_codecs_created() const { return num_video_codecs_created_; } |
| |
| struct MediaMetadata { |
| base::TimeDelta duration; |
| int width; |
| int height; |
| bool modified; |
| MediaMetadata() : width(0), height(0), modified(false) {} |
| }; |
| MediaMetadata media_metadata_; |
| |
| struct PTSTime { |
| base::TimeDelta pts; |
| base::TimeTicks time; |
| |
| PTSTime() : pts(), time() {} |
| PTSTime(base::TimeDelta p, base::TimeTicks t) : pts(p), time(t) {} |
| bool is_null() const { return time.is_null(); } |
| bool operator<(const PTSTime& rhs) const { return time < rhs.time; } |
| }; |
| Minimax<PTSTime> render_stat_[DemuxerStream::NUM_TYPES]; |
| |
| Minimax<base::TimeDelta> pts_stat_; |
| |
| private: |
| bool playback_allowed_; |
| bool playback_completed_; |
| int num_seeks_completed_; |
| int num_audio_codecs_created_; |
| int num_video_codecs_created_; |
| |
| base::WeakPtrFactory<MockMediaPlayerManager> weak_ptr_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MockMediaPlayerManager); |
| }; |
| |
| // Helper method that creates demuxer configuration. |
| |
| DemuxerConfigs CreateAudioVideoConfigs(const base::TimeDelta& duration, |
| const gfx::Size& video_size) { |
| DemuxerConfigs configs = |
| TestDataFactory::CreateAudioConfigs(kCodecAAC, duration); |
| configs.video_codec = kCodecVP8; |
| configs.video_size = video_size; |
| configs.is_video_encrypted = false; |
| return configs; |
| } |
| |
| DemuxerConfigs CreateAudioVideoConfigs(const TestDataFactory* audio, |
| const TestDataFactory* video) { |
| DemuxerConfigs result = audio->GetConfigs(); |
| DemuxerConfigs vconf = video->GetConfigs(); |
| |
| result.video_codec = vconf.video_codec; |
| result.video_size = vconf.video_size; |
| result.is_video_encrypted = vconf.is_video_encrypted; |
| result.duration = std::max(result.duration, vconf.duration); |
| return result; |
| } |
| |
| // AudioFactory creates data chunks that simulate audio stream from demuxer. |
| |
| class AudioFactory : public TestDataFactory { |
| public: |
| AudioFactory(base::TimeDelta duration) |
| : TestDataFactory("aac-44100-packet-%d", duration, kAudioFramePeriod) {} |
| |
| DemuxerConfigs GetConfigs() const override { |
| return TestDataFactory::CreateAudioConfigs(kCodecAAC, duration_); |
| } |
| |
| protected: |
| void ModifyChunk(DemuxerData* chunk) override { |
| DCHECK(chunk); |
| for (AccessUnit& unit : chunk->access_units) { |
| if (!unit.data.empty()) |
| unit.is_key_frame = true; |
| } |
| } |
| }; |
| |
| // VideoFactory creates a video stream from demuxer. |
| |
| class VideoFactory : public TestDataFactory { |
| public: |
| VideoFactory(base::TimeDelta duration) |
| : TestDataFactory("h264-320x180-frame-%d", duration, kVideoFramePeriod), |
| key_frame_requested_(true) {} |
| |
| DemuxerConfigs GetConfigs() const override { |
| return TestDataFactory::CreateVideoConfigs(kCodecH264, duration_, |
| gfx::Size(320, 180)); |
| } |
| |
| void RequestKeyFrame() { key_frame_requested_ = true; } |
| |
| protected: |
| void ModifyChunk(DemuxerData* chunk) override { |
| // The frames are taken from High profile and some are B-frames. |
| // The first 4 frames appear in the file in the following order: |
| // |
| // Frames: I P B P |
| // Decoding order: 0 1 2 3 |
| // Presentation order: 0 2 1 4(3) |
| // |
| // I keep the last PTS to be 3 for simplicity. |
| |
| // If the chunk contains EOS, it should not break the presentation order. |
| // For instance, the following chunk is ok: |
| // |
| // Frames: I P B EOS |
| // Decoding order: 0 1 2 - |
| // Presentation order: 0 2 1 - |
| // |
| // while this might cause decoder to block: |
| // |
| // Frames: I P EOS |
| // Decoding order: 0 1 - |
| // Presentation order: 0 2 - <------- might wait for the B frame forever |
| // |
| // With current base class implementation that always has EOS at the 4th |
| // place we are covered (http://crbug.com/526755) |
| |
| DCHECK(chunk); |
| DCHECK(chunk->access_units.size() == 4); |
| |
| // Swap pts for second and third frames. |
| base::TimeDelta tmp = chunk->access_units[1].timestamp; |
| chunk->access_units[1].timestamp = chunk->access_units[2].timestamp; |
| chunk->access_units[2].timestamp = tmp; |
| |
| // Make first frame a key frame. |
| if (key_frame_requested_) { |
| chunk->access_units[0].is_key_frame = true; |
| key_frame_requested_ = false; |
| } |
| } |
| |
| private: |
| bool key_frame_requested_; |
| }; |
| |
| // Mock of DemuxerAndroid for testing purpose. |
| |
| class MockDemuxerAndroid : public DemuxerAndroid { |
| public: |
| MockDemuxerAndroid(base::MessageLoop* ui_message_loop); |
| ~MockDemuxerAndroid() override; |
| |
| // DemuxerAndroid implementation |
| void Initialize(DemuxerAndroidClient* client) override; |
| void RequestDemuxerData(DemuxerStream::Type type) override; |
| void RequestDemuxerSeek(const base::TimeDelta& seek_request, |
| bool is_browser_seek) override; |
| |
| // Helper methods that enable using a weak pointer when posting to the player. |
| void OnDemuxerDataAvailable(const DemuxerData& chunk); |
| void OnDemuxerSeekDone(base::TimeDelta reported_seek_time); |
| |
| // Sets the callback that is fired when demuxer is deleted (deletion |
| // happens on the Media thread). |
| void SetDemuxerDeletedCallback(base::Closure cb) { demuxer_deleted_cb_ = cb; } |
| |
| // Sets the audio data factory. |
| void SetAudioFactory(scoped_ptr<AudioFactory> factory) { |
| audio_factory_ = factory.Pass(); |
| } |
| |
| // Sets the video data factory. |
| void SetVideoFactory(scoped_ptr<VideoFactory> factory) { |
| video_factory_ = factory.Pass(); |
| } |
| |
| // Accessors for data factories. |
| AudioFactory* audio_factory() const { return audio_factory_.get(); } |
| VideoFactory* video_factory() const { return video_factory_.get(); } |
| |
| // Set the preroll interval after seek for audio stream. |
| void SetAudioPrerollInterval(base::TimeDelta value) { |
| audio_preroll_interval_ = value; |
| } |
| |
| // Set the preroll interval after seek for video stream. |
| void SetVideoPrerollInterval(base::TimeDelta value) { |
| video_preroll_interval_ = value; |
| } |
| |
| // Sets the delay in OnDemuxerSeekDone response. |
| void SetSeekDoneDelay(base::TimeDelta delay) { seek_done_delay_ = delay; } |
| |
| // Post DemuxerConfigs to the client (i.e. the player) on correct thread. |
| void PostConfigs(const DemuxerConfigs& configs); |
| |
| // Post DemuxerConfigs derived from data factories that has been set. |
| void PostInternalConfigs(); |
| |
| // Conditions to wait for. |
| bool IsInitialized() const { return client_; } |
| bool HasPendingConfigs() const { return pending_configs_; } |
| bool ReceivedSeekRequest() const { return num_seeks_ > 0; } |
| bool ReceivedBrowserSeekRequest() const { return num_browser_seeks_ > 0; } |
| |
| private: |
| base::MessageLoop* ui_message_loop_; |
| DemuxerAndroidClient* client_; |
| |
| scoped_ptr<DemuxerConfigs> pending_configs_; |
| scoped_ptr<AudioFactory> audio_factory_; |
| scoped_ptr<VideoFactory> video_factory_; |
| |
| base::TimeDelta audio_preroll_interval_; |
| base::TimeDelta video_preroll_interval_; |
| base::TimeDelta seek_done_delay_; |
| |
| int num_seeks_; |
| int num_browser_seeks_; |
| |
| base::Closure demuxer_deleted_cb_; |
| |
| // NOTE: WeakPtrFactory must be the last data member to be destroyed first. |
| base::WeakPtrFactory<MockDemuxerAndroid> weak_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MockDemuxerAndroid); |
| }; |
| |
| MockDemuxerAndroid::MockDemuxerAndroid(base::MessageLoop* ui_message_loop) |
| : ui_message_loop_(ui_message_loop), |
| client_(nullptr), |
| num_seeks_(0), |
| num_browser_seeks_(0), |
| weak_factory_(this) {} |
| |
| MockDemuxerAndroid::~MockDemuxerAndroid() { |
| DVLOG(1) << "MockDemuxerAndroid::" << __FUNCTION__; |
| DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); |
| |
| if (!demuxer_deleted_cb_.is_null()) |
| ui_message_loop_->PostTask(FROM_HERE, demuxer_deleted_cb_); |
| } |
| |
| void MockDemuxerAndroid::Initialize(DemuxerAndroidClient* client) { |
| DVLOG(1) << "MockDemuxerAndroid::" << __FUNCTION__; |
| DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); |
| |
| client_ = client; |
| if (pending_configs_) |
| client_->OnDemuxerConfigsAvailable(*pending_configs_); |
| } |
| |
| void MockDemuxerAndroid::RequestDemuxerData(DemuxerStream::Type type) { |
| DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); |
| |
| DemuxerData chunk; |
| base::TimeDelta delay; |
| |
| bool created = false; |
| if (type == DemuxerStream::AUDIO && audio_factory_) |
| created = audio_factory_->CreateChunk(&chunk, &delay); |
| else if (type == DemuxerStream::VIDEO && video_factory_) |
| created = video_factory_->CreateChunk(&chunk, &delay); |
| |
| if (!created) |
| return; |
| |
| // Request key frame after |kConfigChanged| |
| if (type == DemuxerStream::VIDEO && !chunk.demuxer_configs.empty()) |
| video_factory_->RequestKeyFrame(); |
| |
| chunk.type = type; |
| |
| // Post to the Media thread. Use the weak pointer to prevent the data arrival |
| // after the player has been deleted. |
| GetMediaTaskRunner()->PostDelayedTask( |
| FROM_HERE, base::Bind(&MockDemuxerAndroid::OnDemuxerDataAvailable, |
| weak_factory_.GetWeakPtr(), chunk), |
| delay); |
| } |
| |
| void MockDemuxerAndroid::RequestDemuxerSeek(const base::TimeDelta& seek_request, |
| bool is_browser_seek) { |
| // Tell data factories to start next chunk with the new timestamp. |
| if (audio_factory_) { |
| base::TimeDelta time_to_seek = |
| std::max(base::TimeDelta(), seek_request - audio_preroll_interval_); |
| audio_factory_->SeekTo(time_to_seek); |
| } |
| if (video_factory_) { |
| base::TimeDelta time_to_seek = |
| std::max(base::TimeDelta(), seek_request - video_preroll_interval_); |
| video_factory_->SeekTo(time_to_seek); |
| video_factory_->RequestKeyFrame(); |
| } |
| |
| ++num_seeks_; |
| if (is_browser_seek) |
| ++num_browser_seeks_; |
| |
| // Post OnDemuxerSeekDone() to the player. |
| DCHECK(client_); |
| base::TimeDelta reported_seek_time = |
| is_browser_seek ? seek_request : kNoTimestamp(); |
| GetMediaTaskRunner()->PostDelayedTask( |
| FROM_HERE, base::Bind(&MockDemuxerAndroid::OnDemuxerSeekDone, |
| weak_factory_.GetWeakPtr(), reported_seek_time), |
| seek_done_delay_); |
| } |
| |
| void MockDemuxerAndroid::OnDemuxerDataAvailable(const DemuxerData& chunk) { |
| DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); |
| DCHECK(client_); |
| client_->OnDemuxerDataAvailable(chunk); |
| } |
| |
| void MockDemuxerAndroid::OnDemuxerSeekDone(base::TimeDelta reported_seek_time) { |
| DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); |
| DCHECK(client_); |
| client_->OnDemuxerSeekDone(reported_seek_time); |
| } |
| |
| void MockDemuxerAndroid::PostConfigs(const DemuxerConfigs& configs) { |
| RUN_ON_MEDIA_THREAD(MockDemuxerAndroid, PostConfigs, configs); |
| |
| DVLOG(1) << "MockDemuxerAndroid::" << __FUNCTION__; |
| |
| DCHECK(GetMediaTaskRunner()->BelongsToCurrentThread()); |
| |
| if (client_) |
| client_->OnDemuxerConfigsAvailable(configs); |
| else |
| pending_configs_ = scoped_ptr<DemuxerConfigs>(new DemuxerConfigs(configs)); |
| } |
| |
| void MockDemuxerAndroid::PostInternalConfigs() { |
| ASSERT_TRUE(audio_factory_ || video_factory_); |
| |
| if (audio_factory_ && video_factory_) { |
| PostConfigs( |
| CreateAudioVideoConfigs(audio_factory_.get(), video_factory_.get())); |
| } else if (audio_factory_) { |
| PostConfigs(audio_factory_->GetConfigs()); |
| } else if (video_factory_) { |
| PostConfigs(video_factory_->GetConfigs()); |
| } |
| } |
| |
| } // namespace (anonymous) |
| |
| // The test fixture for MediaCodecPlayer |
| |
| class MediaCodecPlayerTest : public testing::Test { |
| public: |
| MediaCodecPlayerTest(); |
| |
| // Conditions to wait for. |
| bool IsPaused() const { return !(player_ && player_->IsPlaying()); } |
| |
| protected: |
| typedef base::Callback<bool()> Predicate; |
| |
| void TearDown() override; |
| |
| void CreatePlayer(); |
| void SetVideoSurface(); |
| void SetVideoSurfaceB(); |
| void RemoveVideoSurface(); |
| |
| // Waits for condition to become true or for timeout to expire. |
| // Returns true if the condition becomes true. |
| bool WaitForCondition(const Predicate& condition, |
| const base::TimeDelta& timeout = kDefaultTimeout); |
| |
| // Waits for timeout to expire. |
| void WaitForDelay(const base::TimeDelta& timeout); |
| |
| // Waits till playback position as determined by maximal reported pts |
| // reaches the given value or for timeout to expire. Returns true if the |
| // playback has passed the given position. |
| bool WaitForPlaybackBeyondPosition( |
| const base::TimeDelta& pts, |
| const base::TimeDelta& timeout = kDefaultTimeout); |
| |
| // Helper method that starts video only stream. Waits till it actually |
| // started. |
| bool StartVideoPlayback(base::TimeDelta duration, const char* test_name); |
| |
| // Helper method that starts audio and video streams. |
| bool StartAVPlayback(scoped_ptr<AudioFactory> audio_factory, |
| scoped_ptr<VideoFactory> video_factory, |
| uint32_t flags, |
| const char* test_name); |
| |
| // Helper method that starts audio and video streams with preroll. |
| // The preroll is achieved by setting significant video preroll interval |
| // so video will have to catch up with audio. To make room for this interval |
| // the Start() command is preceded by SeekTo(). |
| bool StartAVSeekAndPreroll(scoped_ptr<AudioFactory> audio_factory, |
| scoped_ptr<VideoFactory> video_factory, |
| base::TimeDelta seek_position, |
| uint32_t flags, |
| const char* test_name); |
| |
| // Callback sent when demuxer is being deleted. |
| void OnDemuxerDeleted() { demuxer_ = nullptr; } |
| |
| bool IsDemuxerDeleted() const { return !demuxer_; } |
| |
| base::MessageLoop message_loop_; |
| MockMediaPlayerManager manager_; |
| MockDemuxerAndroid* demuxer_; // owned by player_ |
| scoped_refptr<gfx::SurfaceTexture> surface_texture_a_; |
| scoped_refptr<gfx::SurfaceTexture> surface_texture_b_; |
| MediaCodecPlayer* player_; // raw pointer due to DeleteOnCorrectThread() |
| |
| private: |
| bool is_timeout_expired() const { return is_timeout_expired_; } |
| void SetTimeoutExpired(bool value) { is_timeout_expired_ = value; } |
| |
| bool is_timeout_expired_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MediaCodecPlayerTest); |
| }; |
| |
| MediaCodecPlayerTest::MediaCodecPlayerTest() |
| : demuxer_(new MockDemuxerAndroid(&message_loop_)), |
| player_(nullptr), |
| is_timeout_expired_(false) {} |
| |
| void MediaCodecPlayerTest::TearDown() { |
| DVLOG(1) << __FUNCTION__; |
| |
| // Wait till the player is destroyed on the Media thread. |
| |
| if (player_) { |
| // The player deletes the demuxer on the Media thread. The demuxer's |
| // destructor sends a notification to the UI thread. When this notification |
| // arrives we can conclude that player started destroying its member |
| // variables. By that time the media codecs should have been released. |
| |
| DCHECK(demuxer_); |
| demuxer_->SetDemuxerDeletedCallback(base::Bind( |
| &MediaCodecPlayerTest::OnDemuxerDeleted, base::Unretained(this))); |
| |
| player_->DeleteOnCorrectThread(); |
| |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MediaCodecPlayerTest::IsDemuxerDeleted, |
| base::Unretained(this)), |
| base::TimeDelta::FromMilliseconds(500))); |
| |
| player_ = nullptr; |
| } |
| } |
| |
| void MediaCodecPlayerTest::CreatePlayer() { |
| DCHECK(demuxer_); |
| player_ = new MediaCodecPlayer( |
| 0, // player_id |
| manager_.GetWeakPtr(), |
| base::Bind(&MockMediaPlayerManager::OnMediaResourcesRequested, |
| base::Unretained(&manager_)), |
| scoped_ptr<MockDemuxerAndroid>(demuxer_), GURL()); |
| |
| DCHECK(player_); |
| } |
| |
| void MediaCodecPlayerTest::SetVideoSurface() { |
| surface_texture_a_ = gfx::SurfaceTexture::Create(0); |
| gfx::ScopedJavaSurface surface(surface_texture_a_.get()); |
| |
| ASSERT_NE(nullptr, player_); |
| player_->SetVideoSurface(surface.Pass()); |
| } |
| |
| void MediaCodecPlayerTest::SetVideoSurfaceB() { |
| surface_texture_b_ = gfx::SurfaceTexture::Create(1); |
| gfx::ScopedJavaSurface surface(surface_texture_b_.get()); |
| |
| ASSERT_NE(nullptr, player_); |
| player_->SetVideoSurface(surface.Pass()); |
| } |
| |
| void MediaCodecPlayerTest::RemoveVideoSurface() { |
| player_->SetVideoSurface(gfx::ScopedJavaSurface()); |
| surface_texture_a_ = NULL; |
| } |
| |
| bool MediaCodecPlayerTest::WaitForCondition(const Predicate& condition, |
| const base::TimeDelta& timeout) { |
| // Let the message_loop_ process events. |
| // We start the timer and RunUntilIdle() until it signals. |
| |
| SetTimeoutExpired(false); |
| |
| base::Timer timer(false, false); |
| timer.Start(FROM_HERE, timeout, |
| base::Bind(&MediaCodecPlayerTest::SetTimeoutExpired, |
| base::Unretained(this), true)); |
| |
| do { |
| if (condition.Run()) { |
| timer.Stop(); |
| return true; |
| } |
| message_loop_.RunUntilIdle(); |
| } while (!is_timeout_expired()); |
| |
| DCHECK(!timer.IsRunning()); |
| return false; |
| } |
| |
| void MediaCodecPlayerTest::WaitForDelay(const base::TimeDelta& timeout) { |
| WaitForCondition(base::Bind(&AlwaysFalse), timeout); |
| } |
| |
| bool MediaCodecPlayerTest::WaitForPlaybackBeyondPosition( |
| const base::TimeDelta& pts, |
| const base::TimeDelta& timeout) { |
| return WaitForCondition( |
| base::Bind(&MockMediaPlayerManager::IsPlaybackBeyondPosition, |
| base::Unretained(&manager_), pts), |
| timeout); |
| } |
| |
| bool MediaCodecPlayerTest::StartVideoPlayback(base::TimeDelta duration, |
| const char* test_name) { |
| const base::TimeDelta start_timeout = base::TimeDelta::FromMilliseconds(800); |
| |
| demuxer_->SetVideoFactory( |
| scoped_ptr<VideoFactory>(new VideoFactory(duration))); |
| |
| CreatePlayer(); |
| |
| // Wait till the player is initialized on media thread. |
| EXPECT_TRUE(WaitForCondition(base::Bind(&MockDemuxerAndroid::IsInitialized, |
| base::Unretained(demuxer_)))); |
| |
| if (!demuxer_->IsInitialized()) { |
| DVLOG(0) << test_name << ": demuxer is not initialized"; |
| return false; |
| } |
| |
| SetVideoSurface(); |
| |
| // Post configuration after the player has been initialized. |
| demuxer_->PostInternalConfigs(); |
| |
| // Start the player. |
| EXPECT_FALSE(manager_.IsPlaybackStarted()); |
| player_->Start(); |
| |
| // Wait for playback to start. |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackStarted, |
| base::Unretained(&manager_)), |
| start_timeout)); |
| |
| if (!manager_.IsPlaybackStarted()) { |
| DVLOG(0) << test_name << ": playback did not start"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool MediaCodecPlayerTest::StartAVPlayback( |
| scoped_ptr<AudioFactory> audio_factory, |
| scoped_ptr<VideoFactory> video_factory, |
| uint32_t flags, |
| const char* test_name) { |
| demuxer_->SetAudioFactory(audio_factory.Pass()); |
| demuxer_->SetVideoFactory(video_factory.Pass()); |
| |
| CreatePlayer(); |
| SetVideoSurface(); |
| |
| // Wait till the player is initialized on media thread. |
| EXPECT_TRUE(WaitForCondition(base::Bind(&MockDemuxerAndroid::IsInitialized, |
| base::Unretained(demuxer_)))); |
| |
| if (!demuxer_->IsInitialized()) { |
| DVLOG(0) << test_name << ": demuxer is not initialized"; |
| return false; |
| } |
| |
| // Ask decoders to always reconfigure after the player has been initialized. |
| if (flags & kAlwaysReconfigAudio) |
| player_->SetAlwaysReconfigureForTests(DemuxerStream::AUDIO); |
| if (flags & kAlwaysReconfigVideo) |
| player_->SetAlwaysReconfigureForTests(DemuxerStream::VIDEO); |
| |
| // Set a testing callback to receive PTS from decoders. |
| player_->SetDecodersTimeCallbackForTests( |
| base::Bind(&MockMediaPlayerManager::OnDecodersTimeUpdate, |
| base::Unretained(&manager_))); |
| |
| // Set a testing callback to receive MediaCodec creation events from decoders. |
| player_->SetCodecCreatedCallbackForTests( |
| base::Bind(&MockMediaPlayerManager::OnMediaCodecCreated, |
| base::Unretained(&manager_))); |
| |
| // Post configuration after the player has been initialized. |
| demuxer_->PostInternalConfigs(); |
| |
| // Start and wait for playback. |
| player_->Start(); |
| |
| // Wait till we start to play. |
| base::TimeDelta start_timeout = base::TimeDelta::FromMilliseconds(2000); |
| |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackStarted, |
| base::Unretained(&manager_)), |
| start_timeout)); |
| |
| if (!manager_.IsPlaybackStarted()) { |
| DVLOG(0) << test_name << ": playback did not start"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool MediaCodecPlayerTest::StartAVSeekAndPreroll( |
| scoped_ptr<AudioFactory> audio_factory, |
| scoped_ptr<VideoFactory> video_factory, |
| base::TimeDelta seek_position, |
| uint32_t flags, |
| const char* test_name) { |
| // Initialize A/V playback |
| |
| demuxer_->SetAudioFactory(audio_factory.Pass()); |
| demuxer_->SetVideoFactory(video_factory.Pass()); |
| |
| CreatePlayer(); |
| SetVideoSurface(); |
| |
| // Wait till the player is initialized on media thread. |
| EXPECT_TRUE(WaitForCondition(base::Bind(&MockDemuxerAndroid::IsInitialized, |
| base::Unretained(demuxer_)))); |
| |
| if (!demuxer_->IsInitialized()) { |
| DVLOG(0) << test_name << ": demuxer is not initialized"; |
| return false; |
| } |
| |
| // Ask decoders to always reconfigure after the player has been initialized. |
| if (flags & kAlwaysReconfigAudio) |
| player_->SetAlwaysReconfigureForTests(DemuxerStream::AUDIO); |
| if (flags & kAlwaysReconfigVideo) |
| player_->SetAlwaysReconfigureForTests(DemuxerStream::VIDEO); |
| |
| // Set a testing callback to receive PTS from decoders. |
| player_->SetDecodersTimeCallbackForTests( |
| base::Bind(&MockMediaPlayerManager::OnDecodersTimeUpdate, |
| base::Unretained(&manager_))); |
| |
| // Set a testing callback to receive MediaCodec creation events from decoders. |
| player_->SetCodecCreatedCallbackForTests( |
| base::Bind(&MockMediaPlayerManager::OnMediaCodecCreated, |
| base::Unretained(&manager_))); |
| |
| // Post configuration after the player has been initialized. |
| demuxer_->PostInternalConfigs(); |
| |
| // Issue SeekTo(). |
| player_->SeekTo(seek_position); |
| |
| // Start the playback. |
| player_->Start(); |
| |
| // Wait till preroll starts. |
| base::TimeDelta start_timeout = base::TimeDelta::FromMilliseconds(2000); |
| EXPECT_TRUE(WaitForCondition( |
| base::Bind(&MediaCodecPlayer::IsPrerollingForTests, |
| base::Unretained(player_), DemuxerStream::VIDEO), |
| start_timeout)); |
| |
| if (!player_->IsPrerollingForTests(DemuxerStream::VIDEO)) { |
| DVLOG(0) << test_name << ": preroll did not happen for video"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| TEST_F(MediaCodecPlayerTest, SetAudioConfigsBeforePlayerCreation) { |
| // Post configuration when there is no player yet. |
| EXPECT_EQ(nullptr, player_); |
| |
| base::TimeDelta duration = base::TimeDelta::FromSeconds(10); |
| |
| demuxer_->PostConfigs( |
| TestDataFactory::CreateAudioConfigs(kCodecAAC, duration)); |
| |
| // Wait until the configuration gets to the media thread. |
| EXPECT_TRUE(WaitForCondition(base::Bind( |
| &MockDemuxerAndroid::HasPendingConfigs, base::Unretained(demuxer_)))); |
| |
| // Then create the player. |
| CreatePlayer(); |
| |
| // Configuration should propagate through the player and to the manager. |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsMetadataChanged, |
| base::Unretained(&manager_)))); |
| |
| EXPECT_EQ(duration, manager_.media_metadata_.duration); |
| EXPECT_EQ(0, manager_.media_metadata_.width); |
| EXPECT_EQ(0, manager_.media_metadata_.height); |
| } |
| |
| TEST_F(MediaCodecPlayerTest, SetAudioConfigsAfterPlayerCreation) { |
| CreatePlayer(); |
| |
| // Wait till the player is initialized on media thread. |
| EXPECT_TRUE(WaitForCondition(base::Bind(&MockDemuxerAndroid::IsInitialized, |
| base::Unretained(demuxer_)))); |
| |
| // Post configuration after the player has been initialized. |
| base::TimeDelta duration = base::TimeDelta::FromSeconds(10); |
| demuxer_->PostConfigs( |
| TestDataFactory::CreateAudioConfigs(kCodecAAC, duration)); |
| |
| // Configuration should propagate through the player and to the manager. |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsMetadataChanged, |
| base::Unretained(&manager_)))); |
| |
| EXPECT_EQ(duration, manager_.media_metadata_.duration); |
| EXPECT_EQ(0, manager_.media_metadata_.width); |
| EXPECT_EQ(0, manager_.media_metadata_.height); |
| } |
| |
| TEST_F(MediaCodecPlayerTest, SetAudioVideoConfigsAfterPlayerCreation) { |
| CreatePlayer(); |
| |
| // Wait till the player is initialized on media thread. |
| EXPECT_TRUE(WaitForCondition(base::Bind(&MockDemuxerAndroid::IsInitialized, |
| base::Unretained(demuxer_)))); |
| |
| // Post configuration after the player has been initialized. |
| base::TimeDelta duration = base::TimeDelta::FromSeconds(10); |
| demuxer_->PostConfigs(CreateAudioVideoConfigs(duration, gfx::Size(320, 240))); |
| |
| // Configuration should propagate through the player and to the manager. |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsMetadataChanged, |
| base::Unretained(&manager_)))); |
| |
| EXPECT_EQ(duration, manager_.media_metadata_.duration); |
| EXPECT_EQ(320, manager_.media_metadata_.width); |
| EXPECT_EQ(240, manager_.media_metadata_.height); |
| } |
| |
| TEST_F(MediaCodecPlayerTest, AudioPlayTillCompletion) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| base::TimeDelta duration = base::TimeDelta::FromMilliseconds(1000); |
| base::TimeDelta timeout = base::TimeDelta::FromMilliseconds(2000); |
| |
| demuxer_->SetAudioFactory( |
| scoped_ptr<AudioFactory>(new AudioFactory(duration))); |
| |
| CreatePlayer(); |
| |
| // Wait till the player is initialized on media thread. |
| EXPECT_TRUE(WaitForCondition(base::Bind(&MockDemuxerAndroid::IsInitialized, |
| base::Unretained(demuxer_)))); |
| |
| // Post configuration after the player has been initialized. |
| demuxer_->PostInternalConfigs(); |
| |
| EXPECT_FALSE(manager_.IsPlaybackCompleted()); |
| |
| player_->Start(); |
| |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackCompleted, |
| base::Unretained(&manager_)), |
| timeout)); |
| |
| // Current timestamp reflects "now playing" time. It might come with delay |
| // relative to the frame's PTS. Allow for 100 ms delay here. |
| base::TimeDelta audio_pts_delay = base::TimeDelta::FromMilliseconds(100); |
| EXPECT_LT(duration - audio_pts_delay, manager_.pts_stat_.max()); |
| } |
| |
| TEST_F(MediaCodecPlayerTest, AudioNoPermission) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| base::TimeDelta duration = base::TimeDelta::FromMilliseconds(1000); |
| base::TimeDelta start_timeout = base::TimeDelta::FromMilliseconds(800); |
| |
| manager_.SetPlaybackAllowed(false); |
| |
| demuxer_->SetAudioFactory( |
| scoped_ptr<AudioFactory>(new AudioFactory(duration))); |
| |
| CreatePlayer(); |
| |
| // Wait till the player is initialized on media thread. |
| EXPECT_TRUE(WaitForCondition(base::Bind(&MockDemuxerAndroid::IsInitialized, |
| base::Unretained(demuxer_)))); |
| |
| // Post configuration after the player has been initialized. |
| demuxer_->PostInternalConfigs(); |
| |
| EXPECT_FALSE(manager_.IsPlaybackCompleted()); |
| |
| player_->Start(); |
| |
| // Playback should not start. |
| EXPECT_FALSE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackStarted, |
| base::Unretained(&manager_)), |
| start_timeout)); |
| } |
| |
| TEST_F(MediaCodecPlayerTest, VideoPlayTillCompletion) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| base::TimeDelta duration = base::TimeDelta::FromMilliseconds(500); |
| base::TimeDelta timeout = base::TimeDelta::FromMilliseconds(2000); |
| |
| ASSERT_TRUE(StartVideoPlayback(duration, "VideoPlayTillCompletion")); |
| |
| // Wait till completion. |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackCompleted, |
| base::Unretained(&manager_)), |
| timeout)); |
| |
| EXPECT_LE(duration, manager_.pts_stat_.max()); |
| } |
| |
| TEST_F(MediaCodecPlayerTest, VideoNoPermission) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| base::TimeDelta duration = base::TimeDelta::FromMilliseconds(500); |
| const base::TimeDelta start_timeout = base::TimeDelta::FromMilliseconds(800); |
| |
| manager_.SetPlaybackAllowed(false); |
| |
| demuxer_->SetVideoFactory( |
| scoped_ptr<VideoFactory>(new VideoFactory(duration))); |
| |
| CreatePlayer(); |
| |
| // Wait till the player is initialized on media thread. |
| EXPECT_TRUE(WaitForCondition(base::Bind(&MockDemuxerAndroid::IsInitialized, |
| base::Unretained(demuxer_)))); |
| |
| SetVideoSurface(); |
| |
| // Post configuration after the player has been initialized. |
| demuxer_->PostInternalConfigs(); |
| |
| // Start the player. |
| EXPECT_FALSE(manager_.IsPlaybackStarted()); |
| player_->Start(); |
| |
| // Playback should not start. |
| EXPECT_FALSE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackStarted, |
| base::Unretained(&manager_)), |
| start_timeout)); |
| } |
| |
| // http://crbug.com/518900 |
| TEST_F(MediaCodecPlayerTest, AudioSeekAfterStop) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| // Play for 300 ms, then Pause, then Seek to beginning. The playback should |
| // start from the beginning. |
| |
| base::TimeDelta duration = base::TimeDelta::FromMilliseconds(2000); |
| |
| demuxer_->SetAudioFactory( |
| scoped_ptr<AudioFactory>(new AudioFactory(duration))); |
| |
| CreatePlayer(); |
| |
| // Post configuration. |
| demuxer_->PostInternalConfigs(); |
| |
| // Start the player. |
| player_->Start(); |
| |
| // Wait for playback to start. |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackStarted, |
| base::Unretained(&manager_)))); |
| |
| // Wait for 300 ms and stop. The 300 ms interval takes into account potential |
| // audio delay: audio takes time reconfiguring after the first several packets |
| // get written to the audio track. |
| WaitForDelay(base::TimeDelta::FromMilliseconds(300)); |
| |
| player_->Pause(true); |
| |
| // Make sure we played at least 100 ms. |
| EXPECT_LT(base::TimeDelta::FromMilliseconds(100), manager_.pts_stat_.max()); |
| |
| // Wait till the Pause is completed. |
| EXPECT_TRUE(WaitForCondition( |
| base::Bind(&MediaCodecPlayerTest::IsPaused, base::Unretained(this)))); |
| |
| // Clear statistics. |
| manager_.pts_stat_.Clear(); |
| |
| // Now we can seek to the beginning and start the playback. |
| player_->SeekTo(base::TimeDelta()); |
| |
| player_->Start(); |
| |
| // Wait for playback to start. |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackStarted, |
| base::Unretained(&manager_)))); |
| |
| // Make sure we started from the beginninig |
| EXPECT_GT(base::TimeDelta::FromMilliseconds(40), manager_.pts_stat_.min()); |
| |
| // The player should have reported the seek completion to the manager. |
| EXPECT_TRUE(WaitForCondition(base::Bind( |
| &MockMediaPlayerManager::IsSeekCompleted, base::Unretained(&manager_)))); |
| } |
| |
| TEST_F(MediaCodecPlayerTest, AudioSeekThenPlay) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| // Issue Seek command immediately followed by Start. The playback should |
| // start at the seek position. |
| |
| base::TimeDelta duration = base::TimeDelta::FromMilliseconds(2000); |
| base::TimeDelta seek_position = base::TimeDelta::FromMilliseconds(500); |
| |
| demuxer_->SetAudioFactory( |
| scoped_ptr<AudioFactory>(new AudioFactory(duration))); |
| |
| CreatePlayer(); |
| |
| // Post configuration. |
| demuxer_->PostInternalConfigs(); |
| |
| // Seek and immediately start. |
| player_->SeekTo(seek_position); |
| player_->Start(); |
| |
| // Wait for playback to start. |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackStarted, |
| base::Unretained(&manager_)))); |
| |
| // The playback should start at |seek_position| |
| EXPECT_TRUE(AlmostEqual(seek_position, manager_.pts_stat_.min(), 25)); |
| |
| // The player should have reported the seek completion to the manager. |
| EXPECT_TRUE(WaitForCondition(base::Bind( |
| &MockMediaPlayerManager::IsSeekCompleted, base::Unretained(&manager_)))); |
| } |
| |
| TEST_F(MediaCodecPlayerTest, AudioSeekThenPlayThenConfig) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| // Issue Seek command immediately followed by Start but without prior demuxer |
| // configuration. Start should wait for configuration. After it has been |
| // posted the playback should start at the seek position. |
| |
| base::TimeDelta duration = base::TimeDelta::FromMilliseconds(2000); |
| base::TimeDelta seek_position = base::TimeDelta::FromMilliseconds(500); |
| |
| demuxer_->SetAudioFactory( |
| scoped_ptr<AudioFactory>(new AudioFactory(duration))); |
| |
| CreatePlayer(); |
| |
| // Seek and immediately start. |
| player_->SeekTo(seek_position); |
| player_->Start(); |
| |
| // Make sure the player is waiting. |
| WaitForDelay(base::TimeDelta::FromMilliseconds(200)); |
| EXPECT_FALSE(player_->IsPlaying()); |
| |
| // Post configuration. |
| demuxer_->PostInternalConfigs(); |
| |
| // Wait for playback to start. |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackStarted, |
| base::Unretained(&manager_)))); |
| |
| // The playback should start at |seek_position| |
| EXPECT_TRUE(AlmostEqual(seek_position, manager_.pts_stat_.min(), 25)); |
| |
| // The player should have reported the seek completion to the manager. |
| EXPECT_TRUE(WaitForCondition(base::Bind( |
| &MockMediaPlayerManager::IsSeekCompleted, base::Unretained(&manager_)))); |
| } |
| |
| // http://crbug.com/518900 |
| TEST_F(MediaCodecPlayerTest, AudioSeekWhilePlaying) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| // Play for 300 ms, then issue several Seek commands in the row. |
| // The playback should continue at the last seek position. |
| |
| // To test this condition without analyzing the reported time details |
| // and without introducing dependency on implementation I make a long (10s) |
| // duration and test that the playback resumes after big time jump (5s) in a |
| // short period of time (200 ms). |
| base::TimeDelta duration = base::TimeDelta::FromSeconds(10); |
| |
| demuxer_->SetAudioFactory( |
| scoped_ptr<AudioFactory>(new AudioFactory(duration))); |
| |
| CreatePlayer(); |
| |
| // Post configuration. |
| demuxer_->PostInternalConfigs(); |
| |
| // Start the player. |
| player_->Start(); |
| |
| // Wait for playback to start. |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackStarted, |
| base::Unretained(&manager_)))); |
| |
| // Wait for 300 ms. |
| WaitForDelay(base::TimeDelta::FromMilliseconds(300)); |
| |
| // Make sure we played at least 100 ms. |
| EXPECT_LT(base::TimeDelta::FromMilliseconds(100), manager_.pts_stat_.max()); |
| |
| // Seek forward several times. |
| player_->SeekTo(base::TimeDelta::FromSeconds(3)); |
| player_->SeekTo(base::TimeDelta::FromSeconds(4)); |
| player_->SeekTo(base::TimeDelta::FromSeconds(5)); |
| |
| // Make sure that we reached the last timestamp within default timeout, |
| // i.e. 200 ms. |
| EXPECT_TRUE(WaitForPlaybackBeyondPosition(base::TimeDelta::FromSeconds(5))); |
| EXPECT_TRUE(player_->IsPlaying()); |
| |
| // The player should have reported the seek completion to the manager. |
| EXPECT_TRUE(WaitForCondition(base::Bind( |
| &MockMediaPlayerManager::IsSeekCompleted, base::Unretained(&manager_)))); |
| } |
| |
| TEST_F(MediaCodecPlayerTest, VideoReplaceSurface) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| base::TimeDelta duration = base::TimeDelta::FromMilliseconds(1000); |
| base::TimeDelta timeout = base::TimeDelta::FromMilliseconds(1500); |
| |
| ASSERT_TRUE(StartVideoPlayback(duration, "VideoReplaceSurface")); |
| |
| // Wait for some time and check statistics. |
| WaitForDelay(base::TimeDelta::FromMilliseconds(200)); |
| |
| // Make sure we played at least 100 ms. |
| EXPECT_LT(base::TimeDelta::FromMilliseconds(100), manager_.pts_stat_.max()); |
| |
| // Set new video surface without removing the old one. |
| SetVideoSurfaceB(); |
| |
| // We should receive a browser seek request. |
| EXPECT_TRUE(WaitForCondition( |
| base::Bind(&MockDemuxerAndroid::ReceivedBrowserSeekRequest, |
| base::Unretained(demuxer_)))); |
| |
| // Playback should continue with a new surface. Wait till completion. |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackCompleted, |
| base::Unretained(&manager_)), |
| timeout)); |
| EXPECT_LE(duration, manager_.pts_stat_.max()); |
| } |
| |
| TEST_F(MediaCodecPlayerTest, VideoRemoveAndSetSurface) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| base::TimeDelta duration = base::TimeDelta::FromMilliseconds(1000); |
| |
| ASSERT_TRUE(StartVideoPlayback(duration, "VideoRemoveAndSetSurface")); |
| |
| // Wait for some time and check statistics. |
| WaitForDelay(base::TimeDelta::FromMilliseconds(200)); |
| |
| // Make sure we played at least 100 ms. |
| EXPECT_LT(base::TimeDelta::FromMilliseconds(100), manager_.pts_stat_.max()); |
| |
| // Remove video surface. |
| RemoveVideoSurface(); |
| |
| // We should be stuck waiting for the new surface. |
| WaitForDelay(base::TimeDelta::FromMilliseconds(200)); |
| EXPECT_FALSE(player_->IsPlaying()); |
| |
| // Save last PTS and clear statistics. |
| base::TimeDelta max_pts_before_removal = manager_.pts_stat_.max(); |
| manager_.pts_stat_.Clear(); |
| |
| // After clearing statistics we are ready to wait for IsPlaybackStarted again. |
| EXPECT_FALSE(manager_.IsPlaybackStarted()); |
| |
| // Extra RemoveVideoSurface() should not change anything. |
| RemoveVideoSurface(); |
| |
| // Set another video surface. |
| SetVideoSurfaceB(); |
| |
| // We should receive a browser seek request. |
| EXPECT_TRUE(WaitForCondition( |
| base::Bind(&MockDemuxerAndroid::ReceivedBrowserSeekRequest, |
| base::Unretained(demuxer_)))); |
| |
| // Playback should continue with a new surface. Wait till it starts again. |
| base::TimeDelta reconfigure_timeout = base::TimeDelta::FromMilliseconds(800); |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackStarted, |
| base::Unretained(&manager_)), |
| reconfigure_timeout)); |
| |
| // Timestamps should not go back. |
| EXPECT_LE(max_pts_before_removal, manager_.pts_stat_.max()); |
| } |
| |
| // http://crbug.com/518900 |
| TEST_F(MediaCodecPlayerTest, VideoReleaseAndStart) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| base::TimeDelta duration = base::TimeDelta::FromMilliseconds(1000); |
| |
| ASSERT_TRUE(StartVideoPlayback(duration, "VideoReleaseAndStart")); |
| |
| // Wait for some time and check statistics. |
| WaitForDelay(base::TimeDelta::FromMilliseconds(200)); |
| |
| // Make sure we played at least 100 ms. |
| EXPECT_LT(base::TimeDelta::FromMilliseconds(100), manager_.pts_stat_.max()); |
| |
| // When the user presses Tasks button Chrome calls Pause() and Release(). |
| player_->Pause(true); |
| player_->Release(); |
| |
| // Make sure we are not playing any more. |
| WaitForDelay(base::TimeDelta::FromMilliseconds(200)); |
| EXPECT_FALSE(player_->IsPlaying()); |
| |
| // Save last PTS and clear statistics. |
| base::TimeDelta max_pts_before_backgrounding = manager_.pts_stat_.max(); |
| manager_.pts_stat_.Clear(); |
| |
| // After clearing statistics we are ready to wait for IsPlaybackStarted again. |
| EXPECT_FALSE(manager_.IsPlaybackStarted()); |
| |
| // Restart. |
| SetVideoSurface(); |
| player_->Start(); |
| |
| // We should receive a browser seek request. |
| EXPECT_TRUE(WaitForCondition( |
| base::Bind(&MockDemuxerAndroid::ReceivedBrowserSeekRequest, |
| base::Unretained(demuxer_)))); |
| |
| // Wait for playback to start again. |
| base::TimeDelta reconfigure_timeout = base::TimeDelta::FromMilliseconds(800); |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackStarted, |
| base::Unretained(&manager_)), |
| reconfigure_timeout)); |
| |
| // Timestamps should not go back. |
| EXPECT_LE(max_pts_before_backgrounding, manager_.pts_stat_.max()); |
| } |
| |
| TEST_F(MediaCodecPlayerTest, VideoSeekAndRelease) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| base::TimeDelta duration = base::TimeDelta::FromMilliseconds(2000); |
| base::TimeDelta seek_position = base::TimeDelta::FromMilliseconds(1000); |
| |
| ASSERT_TRUE(StartVideoPlayback(duration, "VideoSeekAndRelease")); |
| |
| // Wait for some time and check statistics. |
| WaitForDelay(base::TimeDelta::FromMilliseconds(200)); |
| |
| // Make sure we played at least 100 ms. |
| EXPECT_LT(base::TimeDelta::FromMilliseconds(100), manager_.pts_stat_.max()); |
| |
| // Issue SeekTo() immediately followed by Release(). |
| player_->SeekTo(seek_position); |
| player_->Release(); |
| |
| // Make sure we are not playing any more. |
| WaitForDelay(base::TimeDelta::FromMilliseconds(400)); |
| EXPECT_FALSE(player_->IsPlaying()); |
| |
| // The Release() should not cancel the SeekTo() and we should have received |
| // the seek request by this time. |
| EXPECT_TRUE(demuxer_->ReceivedSeekRequest()); |
| |
| // The player should have reported the seek completion to the manager. |
| EXPECT_TRUE(WaitForCondition(base::Bind( |
| &MockMediaPlayerManager::IsSeekCompleted, base::Unretained(&manager_)))); |
| |
| // Clear statistics. |
| manager_.pts_stat_.Clear(); |
| |
| // After clearing statistics we are ready to wait for IsPlaybackStarted again. |
| EXPECT_FALSE(manager_.IsPlaybackStarted()); |
| |
| // Restart. |
| SetVideoSurface(); |
| player_->Start(); |
| |
| // Wait for playback to start again. |
| base::TimeDelta reconfigure_timeout = base::TimeDelta::FromMilliseconds(800); |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackStarted, |
| base::Unretained(&manager_)), |
| reconfigure_timeout)); |
| |
| // Timestamps should start at the new seek position |
| EXPECT_LE(seek_position, manager_.pts_stat_.min()); |
| } |
| |
| TEST_F(MediaCodecPlayerTest, VideoReleaseWhileWaitingForSeek) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| base::TimeDelta duration = base::TimeDelta::FromMilliseconds(2000); |
| base::TimeDelta seek_position = base::TimeDelta::FromMilliseconds(1000); |
| |
| ASSERT_TRUE(StartVideoPlayback(duration, "VideoReleaseWhileWaitingForSeek")); |
| |
| // Wait for some time and check statistics. |
| WaitForDelay(base::TimeDelta::FromMilliseconds(200)); |
| |
| // Make sure we played at least 100 ms. |
| EXPECT_LT(base::TimeDelta::FromMilliseconds(100), manager_.pts_stat_.max()); |
| |
| // Set artificial delay in the OnDemuxerSeekDone response so we can |
| // issue commands while the player is in the STATE_WAITING_FOR_SEEK. |
| demuxer_->SetSeekDoneDelay(base::TimeDelta::FromMilliseconds(100)); |
| |
| // Issue SeekTo(). |
| player_->SeekTo(seek_position); |
| |
| // Wait for the seek request to demuxer. |
| EXPECT_TRUE(WaitForCondition(base::Bind( |
| &MockDemuxerAndroid::ReceivedSeekRequest, base::Unretained(demuxer_)))); |
| |
| // The player is supposed to be in STATE_WAITING_FOR_SEEK. Issue Release(). |
| player_->Release(); |
| |
| // Make sure we are not playing any more. |
| WaitForDelay(base::TimeDelta::FromMilliseconds(400)); |
| EXPECT_FALSE(player_->IsPlaying()); |
| |
| // Clear statistics. |
| manager_.pts_stat_.Clear(); |
| |
| // After clearing statistics we are ready to wait for IsPlaybackStarted again. |
| EXPECT_FALSE(manager_.IsPlaybackStarted()); |
| |
| // Restart. |
| SetVideoSurface(); |
| player_->Start(); |
| |
| // Wait for playback to start again. |
| base::TimeDelta reconfigure_timeout = base::TimeDelta::FromMilliseconds(1000); |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackStarted, |
| base::Unretained(&manager_)), |
| reconfigure_timeout)); |
| |
| // Timestamps should start at the new seek position |
| EXPECT_LE(seek_position, manager_.pts_stat_.min()); |
| |
| // The player should have reported the seek completion to the manager. |
| EXPECT_TRUE(WaitForCondition(base::Bind( |
| &MockMediaPlayerManager::IsSeekCompleted, base::Unretained(&manager_)))); |
| } |
| |
| TEST_F(MediaCodecPlayerTest, VideoPrerollAfterSeek) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| // A simple test for preroll for video stream only. After the seek is done |
| // the data factory generates the frames with pts before the seek time, and |
| // they should not be rendered. We deduce which frame is rendered by looking |
| // at the reported time progress. |
| |
| base::TimeDelta duration = base::TimeDelta::FromMilliseconds(600); |
| base::TimeDelta seek_position = base::TimeDelta::FromMilliseconds(500); |
| base::TimeDelta start_timeout = base::TimeDelta::FromMilliseconds(800); |
| |
| // Tell demuxer to make the first frame 100ms earlier than the seek request. |
| demuxer_->SetVideoPrerollInterval(base::TimeDelta::FromMilliseconds(100)); |
| |
| demuxer_->SetVideoFactory( |
| scoped_ptr<VideoFactory>(new VideoFactory(duration))); |
| |
| CreatePlayer(); |
| SetVideoSurface(); |
| |
| // Wait till the player is initialized on media thread. |
| EXPECT_TRUE(WaitForCondition(base::Bind(&MockDemuxerAndroid::IsInitialized, |
| base::Unretained(demuxer_)))); |
| if (!demuxer_->IsInitialized()) { |
| DVLOG(0) << "VideoPrerollAfterSeek: demuxer is not initialized"; |
| return; |
| } |
| |
| // Post configuration after the player has been initialized. |
| demuxer_->PostInternalConfigs(); |
| |
| // Issue SeekTo(). |
| player_->SeekTo(seek_position); |
| |
| // Start the playback and make sure it is started. |
| player_->Start(); |
| |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackStarted, |
| base::Unretained(&manager_)), |
| start_timeout)); |
| |
| // Wait for completion. |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackCompleted, |
| base::Unretained(&manager_)))); |
| |
| // The first pts should be equal than seek position even if video frames |
| // started 100 ms eralier than the seek request. |
| EXPECT_EQ(seek_position, manager_.pts_stat_.min()); |
| |
| EXPECT_EQ(6, manager_.pts_stat_.num_values()); |
| } |
| |
| TEST_F(MediaCodecPlayerTest, AVPrerollAudioWaitsForVideo) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| // Test that during prerolling neither audio nor video plays and that both |
| // resume simultaneously after preroll is finished. In other words, test |
| // that preroll works. |
| // We put the video into the long preroll and intercept the time when first |
| // rendering happens in each stream. The moment of rendering is approximated |
| // with a decoder PTS that is delivered by a test-only callback. |
| |
| base::TimeDelta duration = base::TimeDelta::FromMilliseconds(2000); |
| |
| // Set significant preroll interval. 500 ms means 25 frames, at 10 ms |
| // per frame it would take 250 ms to preroll. |
| base::TimeDelta seek_position = base::TimeDelta::FromMilliseconds(1000); |
| base::TimeDelta preroll_intvl = base::TimeDelta::FromMilliseconds(500); |
| base::TimeDelta preroll_timeout = base::TimeDelta::FromMilliseconds(1000); |
| |
| scoped_ptr<AudioFactory> audio_factory(new AudioFactory(duration)); |
| scoped_ptr<VideoFactory> video_factory(new VideoFactory(duration)); |
| |
| demuxer_->SetVideoPrerollInterval(preroll_intvl); |
| |
| ASSERT_TRUE(StartAVSeekAndPreroll(audio_factory.Pass(), video_factory.Pass(), |
| seek_position, 0, |
| "AVPrerollAudioWaitsForVideo")); |
| |
| // Wait till preroll finishes and the real playback starts. |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackStarted, |
| base::Unretained(&manager_)), |
| preroll_timeout)); |
| |
| // Ensure that the first audio and video pts are close to each other and are |
| // reported at the close moments in time. |
| |
| EXPECT_TRUE(manager_.HasFirstFrame(DemuxerStream::AUDIO)); |
| EXPECT_TRUE(WaitForCondition( |
| base::Bind(&MockMediaPlayerManager::HasFirstFrame, |
| base::Unretained(&manager_), DemuxerStream::VIDEO))); |
| |
| EXPECT_TRUE(AlmostEqual(manager_.FirstFramePTS(DemuxerStream::AUDIO), |
| manager_.FirstFramePTS(DemuxerStream::VIDEO), 25)); |
| |
| EXPECT_TRUE(AlmostEqual(manager_.FirstFrameTime(DemuxerStream::AUDIO), |
| manager_.FirstFrameTime(DemuxerStream::VIDEO), 50)); |
| |
| // The playback should start at |seek_position| |
| EXPECT_TRUE(AlmostEqual(seek_position, manager_.pts_stat_.min(), 25)); |
| } |
| |
| TEST_F(MediaCodecPlayerTest, AVPrerollReleaseAndRestart) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| // Test that player will resume prerolling if prerolling is interrupted by |
| // Release() and Start(). |
| |
| base::TimeDelta duration = base::TimeDelta::FromMilliseconds(2000); |
| |
| // Set significant preroll interval. 500 ms means 25 frames, at 10 ms |
| // per frame it would take 250 ms to preroll. |
| base::TimeDelta seek_position = base::TimeDelta::FromMilliseconds(1000); |
| base::TimeDelta preroll_intvl = base::TimeDelta::FromMilliseconds(500); |
| |
| base::TimeDelta start_timeout = base::TimeDelta::FromMilliseconds(800); |
| base::TimeDelta preroll_timeout = base::TimeDelta::FromMilliseconds(1000); |
| |
| scoped_ptr<AudioFactory> audio_factory(new AudioFactory(duration)); |
| scoped_ptr<VideoFactory> video_factory(new VideoFactory(duration)); |
| |
| demuxer_->SetVideoPrerollInterval(preroll_intvl); |
| |
| ASSERT_TRUE(StartAVSeekAndPreroll(audio_factory.Pass(), video_factory.Pass(), |
| seek_position, 0, |
| "AVPrerollReleaseAndRestart")); |
| |
| // Issue Release(). |
| player_->Release(); |
| |
| // Make sure we have not been playing. |
| WaitForDelay(base::TimeDelta::FromMilliseconds(400)); |
| |
| EXPECT_FALSE(manager_.HasFirstFrame(DemuxerStream::AUDIO)); |
| EXPECT_FALSE(manager_.HasFirstFrame(DemuxerStream::VIDEO)); |
| |
| EXPECT_FALSE(player_->IsPrerollingForTests(DemuxerStream::AUDIO)); |
| EXPECT_FALSE(player_->IsPrerollingForTests(DemuxerStream::VIDEO)); |
| EXPECT_EQ(0, manager_.pts_stat_.num_values()); |
| |
| // Restart. Release() removed the video surface, we need to set it again. |
| SetVideoSurface(); |
| player_->Start(); |
| |
| // The playback should pass through prerolling phase. |
| EXPECT_TRUE(WaitForCondition( |
| base::Bind(&MediaCodecPlayer::IsPrerollingForTests, |
| base::Unretained(player_), DemuxerStream::VIDEO), |
| start_timeout)); |
| |
| // Wait till preroll finishes and the real playback starts. |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackStarted, |
| base::Unretained(&manager_)), |
| preroll_timeout)); |
| |
| // Ensure that the first audio and video pts are close to each other and are |
| // reported at the close moments in time. |
| |
| EXPECT_TRUE(manager_.HasFirstFrame(DemuxerStream::AUDIO)); |
| EXPECT_TRUE(WaitForCondition( |
| base::Bind(&MockMediaPlayerManager::HasFirstFrame, |
| base::Unretained(&manager_), DemuxerStream::VIDEO))); |
| |
| // Release() might disacrd first audio frame. |
| EXPECT_TRUE(AlmostEqual(manager_.FirstFramePTS(DemuxerStream::AUDIO), |
| manager_.FirstFramePTS(DemuxerStream::VIDEO), 50)); |
| |
| EXPECT_TRUE(AlmostEqual(manager_.FirstFrameTime(DemuxerStream::AUDIO), |
| manager_.FirstFrameTime(DemuxerStream::VIDEO), 50)); |
| |
| // The playback should start at |seek_position|, but Release() might discard |
| // the first audio frame. |
| EXPECT_TRUE(AlmostEqual(seek_position, manager_.pts_stat_.min(), 50)); |
| } |
| |
| TEST_F(MediaCodecPlayerTest, AVPrerollStopAndRestart) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| // Test that if Pause() happens during the preroll phase, |
| // we continue to do preroll after restart. |
| |
| base::TimeDelta duration = base::TimeDelta::FromMilliseconds(1200); |
| |
| // Set significant preroll interval. 500 ms means 25 frames, at 10 ms |
| // per frame it would take 250 ms to preroll. |
| base::TimeDelta seek_position = base::TimeDelta::FromMilliseconds(1000); |
| base::TimeDelta preroll_intvl = base::TimeDelta::FromMilliseconds(500); |
| |
| base::TimeDelta start_timeout = base::TimeDelta::FromMilliseconds(800); |
| base::TimeDelta preroll_timeout = base::TimeDelta::FromMilliseconds(1000); |
| |
| scoped_ptr<AudioFactory> audio_factory(new AudioFactory(duration)); |
| scoped_ptr<VideoFactory> video_factory(new VideoFactory(duration)); |
| |
| demuxer_->SetVideoPrerollInterval(preroll_intvl); |
| |
| ASSERT_TRUE(StartAVSeekAndPreroll(audio_factory.Pass(), video_factory.Pass(), |
| seek_position, 0, |
| "AVPrerollStopAndRestart")); |
| |
| // Video stream should be prerolling. Request to stop. |
| EXPECT_FALSE(IsPaused()); |
| player_->Pause(true); |
| |
| EXPECT_TRUE(WaitForCondition( |
| base::Bind(&MediaCodecPlayerTest::IsPaused, base::Unretained(this)))); |
| |
| // Test that we have not been playing. |
| EXPECT_FALSE(manager_.HasFirstFrame(DemuxerStream::AUDIO)); |
| EXPECT_FALSE(manager_.HasFirstFrame(DemuxerStream::VIDEO)); |
| |
| EXPECT_FALSE(player_->IsPrerollingForTests(DemuxerStream::AUDIO)); |
| EXPECT_FALSE(player_->IsPrerollingForTests(DemuxerStream::VIDEO)); |
| EXPECT_EQ(0, manager_.pts_stat_.num_values()); |
| |
| // Restart. |
| player_->Start(); |
| |
| // There should be preroll after the start. |
| EXPECT_TRUE(WaitForCondition( |
| base::Bind(&MediaCodecPlayer::IsPrerollingForTests, |
| base::Unretained(player_), DemuxerStream::VIDEO), |
| start_timeout)); |
| |
| // Wait for a short period of time, so that preroll is still ongoing, |
| // and pause again. |
| WaitForDelay(base::TimeDelta::FromMilliseconds(100)); |
| |
| EXPECT_FALSE(IsPaused()); |
| player_->Pause(true); |
| |
| EXPECT_TRUE(WaitForCondition( |
| base::Bind(&MediaCodecPlayerTest::IsPaused, base::Unretained(this)))); |
| |
| // Check that we still haven't started rendering. |
| EXPECT_FALSE(manager_.HasFirstFrame(DemuxerStream::AUDIO)); |
| EXPECT_FALSE(manager_.HasFirstFrame(DemuxerStream::VIDEO)); |
| |
| EXPECT_FALSE(player_->IsPrerollingForTests(DemuxerStream::AUDIO)); |
| EXPECT_FALSE(player_->IsPrerollingForTests(DemuxerStream::VIDEO)); |
| EXPECT_EQ(0, manager_.pts_stat_.num_values()); |
| |
| // Restart again. |
| player_->Start(); |
| |
| // Wait till we start to play. |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackStarted, |
| base::Unretained(&manager_)), |
| preroll_timeout)); |
| |
| // Check that we did prerolling, i.e. audio did wait for video. |
| EXPECT_TRUE(manager_.HasFirstFrame(DemuxerStream::AUDIO)); |
| EXPECT_TRUE(WaitForCondition( |
| base::Bind(&MockMediaPlayerManager::HasFirstFrame, |
| base::Unretained(&manager_), DemuxerStream::VIDEO))); |
| |
| EXPECT_TRUE(AlmostEqual(manager_.FirstFramePTS(DemuxerStream::AUDIO), |
| manager_.FirstFramePTS(DemuxerStream::VIDEO), 25)); |
| |
| EXPECT_TRUE(AlmostEqual(manager_.FirstFrameTime(DemuxerStream::AUDIO), |
| manager_.FirstFrameTime(DemuxerStream::VIDEO), 50)); |
| |
| // The playback should start at |seek_position| |
| EXPECT_TRUE(AlmostEqual(seek_position, manager_.pts_stat_.min(), 25)); |
| } |
| |
| TEST_F(MediaCodecPlayerTest, AVPrerollVideoEndsWhilePrerolling) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| // Test that when one stream ends in the preroll phase and another is not |
| // the preroll finishes and playback continues after it. |
| |
| // http://crbug.com/526755 |
| // TODO(timav): remove these logs after verifying that the bug is fixed. |
| DVLOG(0) << "AVPrerollVideoEndsWhilePrerolling: begin"; |
| |
| base::TimeDelta audio_duration = base::TimeDelta::FromMilliseconds(1100); |
| base::TimeDelta video_duration = base::TimeDelta::FromMilliseconds(900); |
| base::TimeDelta seek_position = base::TimeDelta::FromMilliseconds(1000); |
| base::TimeDelta video_preroll_intvl = base::TimeDelta::FromMilliseconds(200); |
| |
| base::TimeDelta start_timeout = base::TimeDelta::FromMilliseconds(800); |
| base::TimeDelta preroll_timeout = base::TimeDelta::FromMilliseconds(400); |
| |
| demuxer_->SetVideoPrerollInterval(video_preroll_intvl); |
| |
| demuxer_->SetAudioFactory( |
| scoped_ptr<AudioFactory>(new AudioFactory(audio_duration))); |
| demuxer_->SetVideoFactory( |
| scoped_ptr<VideoFactory>(new VideoFactory(video_duration))); |
| |
| CreatePlayer(); |
| SetVideoSurface(); |
| |
| // Set special testing callback to receive PTS from decoders. |
| player_->SetDecodersTimeCallbackForTests( |
| base::Bind(&MockMediaPlayerManager::OnDecodersTimeUpdate, |
| base::Unretained(&manager_))); |
| |
| // Wait till the player is initialized on media thread. |
| EXPECT_TRUE(WaitForCondition(base::Bind(&MockDemuxerAndroid::IsInitialized, |
| base::Unretained(demuxer_)))); |
| |
| if (!demuxer_->IsInitialized()) { |
| DVLOG(0) << "AVPrerollVideoEndsWhilePrerolling: demuxer is not initialized"; |
| return; |
| } |
| |
| // Post configuration after the player has been initialized. |
| demuxer_->PostInternalConfigs(); |
| |
| // Issue SeekTo(). |
| player_->SeekTo(seek_position); |
| |
| // Start the playback. |
| player_->Start(); |
| |
| // The video decoder should start prerolling. |
| // Wait till preroll starts. |
| EXPECT_TRUE(WaitForCondition( |
| base::Bind(&MediaCodecPlayer::IsPrerollingForTests, |
| base::Unretained(player_), DemuxerStream::VIDEO), |
| start_timeout)); |
| |
| // Wait for playback to start. |
| bool playback_started = |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackStarted, |
| base::Unretained(&manager_)), |
| preroll_timeout); |
| |
| // http://crbug.com/526755 |
| if (!playback_started) { |
| DVLOG(0) << "AVPrerollVideoEndsWhilePrerolling: playback did not start for " |
| << preroll_timeout; |
| } |
| ASSERT_TRUE(playback_started); |
| |
| EXPECT_TRUE(manager_.HasFirstFrame(DemuxerStream::AUDIO)); |
| |
| // Play till completion. |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackCompleted, |
| base::Unretained(&manager_)))); |
| |
| // There should not be any video frames. |
| EXPECT_FALSE(manager_.HasFirstFrame(DemuxerStream::VIDEO)); |
| |
| // http://crbug.com/526755 |
| DVLOG(0) << "AVPrerollVideoEndsWhilePrerolling: end"; |
| } |
| |
| TEST_F(MediaCodecPlayerTest, VideoConfigChangeWhilePlaying) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| // Test that video only playback continues after video config change. |
| |
| // Initialize video playback |
| base::TimeDelta duration = base::TimeDelta::FromMilliseconds(1200); |
| base::TimeDelta config_change_position = |
| base::TimeDelta::FromMilliseconds(1000); |
| |
| base::TimeDelta start_timeout = base::TimeDelta::FromMilliseconds(2000); |
| base::TimeDelta completion_timeout = base::TimeDelta::FromMilliseconds(3000); |
| |
| demuxer_->SetVideoFactory( |
| scoped_ptr<VideoFactory>(new VideoFactory(duration))); |
| |
| demuxer_->video_factory()->RequestConfigChange(config_change_position); |
| |
| CreatePlayer(); |
| SetVideoSurface(); |
| |
| // Wait till the player is initialized on media thread. |
| EXPECT_TRUE(WaitForCondition(base::Bind(&MockDemuxerAndroid::IsInitialized, |
| base::Unretained(demuxer_)))); |
| |
| if (!demuxer_->IsInitialized()) { |
| DVLOG(0) << "VideoConfigChangeWhilePlaying: demuxer is not initialized"; |
| return; |
| } |
| |
| // Ask decoders to always reconfigure after the player has been initialized. |
| player_->SetAlwaysReconfigureForTests(DemuxerStream::VIDEO); |
| |
| // Set a testing callback to receive PTS from decoders. |
| player_->SetDecodersTimeCallbackForTests( |
| base::Bind(&MockMediaPlayerManager::OnDecodersTimeUpdate, |
| base::Unretained(&manager_))); |
| |
| // Set a testing callback to receive MediaCodec creation events from decoders. |
| player_->SetCodecCreatedCallbackForTests( |
| base::Bind(&MockMediaPlayerManager::OnMediaCodecCreated, |
| base::Unretained(&manager_))); |
| |
| // Post configuration after the player has been initialized. |
| demuxer_->PostInternalConfigs(); |
| |
| // Start and wait for playback. |
| player_->Start(); |
| |
| // Wait till we start to play. |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackStarted, |
| base::Unretained(&manager_)), |
| start_timeout)); |
| |
| // Wait till completion |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackCompleted, |
| base::Unretained(&manager_)), |
| completion_timeout)); |
| |
| // The video codec should be recreated upon config changes. |
| EXPECT_EQ(2, manager_.num_video_codecs_created()); |
| |
| // Check that we did not miss video frames |
| int expected_video_frames = GetFrameCount(duration, kVideoFramePeriod, 1); |
| EXPECT_EQ(expected_video_frames, |
| manager_.render_stat_[DemuxerStream::VIDEO].num_values()); |
| } |
| |
| TEST_F(MediaCodecPlayerTest, AVVideoConfigChangeWhilePlaying) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| // Test that A/V playback continues after video config change. |
| |
| // Initialize A/V playback |
| base::TimeDelta duration = base::TimeDelta::FromMilliseconds(1200); |
| base::TimeDelta config_change_position = |
| base::TimeDelta::FromMilliseconds(1000); |
| |
| base::TimeDelta completion_timeout = base::TimeDelta::FromMilliseconds(3000); |
| |
| scoped_ptr<AudioFactory> audio_factory(new AudioFactory(duration)); |
| scoped_ptr<VideoFactory> video_factory(new VideoFactory(duration)); |
| |
| video_factory->RequestConfigChange(config_change_position); |
| |
| ASSERT_TRUE(StartAVPlayback(audio_factory.Pass(), video_factory.Pass(), |
| kAlwaysReconfigVideo, |
| "AVVideoConfigChangeWhilePlaying")); |
| |
| // Wait till completion |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackCompleted, |
| base::Unretained(&manager_)), |
| completion_timeout)); |
| |
| // The audio codec should be kept. |
| EXPECT_EQ(1, manager_.num_audio_codecs_created()); |
| |
| // The video codec should be recreated upon config changes. |
| EXPECT_EQ(2, manager_.num_video_codecs_created()); |
| |
| // Check that we did not miss video frames |
| int expected_video_frames = GetFrameCount(duration, kVideoFramePeriod, 1); |
| EXPECT_EQ(expected_video_frames, |
| manager_.render_stat_[DemuxerStream::VIDEO].num_values()); |
| |
| // Check that we did not miss audio frames. We expect one postponed frames |
| // that are not reported. |
| // For Nexus 4 KitKat the AAC decoder seems to swallow the first frame |
| // but reports the last pts twice, maybe it just shifts the reported PTS. |
| int expected_audio_frames = GetFrameCount(duration, kAudioFramePeriod, 0) - 1; |
| EXPECT_EQ(expected_audio_frames, |
| manager_.render_stat_[DemuxerStream::AUDIO].num_values()); |
| } |
| |
| TEST_F(MediaCodecPlayerTest, AVAudioConfigChangeWhilePlaying) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| // Test that A/V playback continues after audio config change. |
| |
| base::TimeDelta duration = base::TimeDelta::FromMilliseconds(1200); |
| base::TimeDelta config_change_position = |
| base::TimeDelta::FromMilliseconds(1000); |
| |
| base::TimeDelta completion_timeout = base::TimeDelta::FromMilliseconds(3000); |
| |
| scoped_ptr<AudioFactory> audio_factory(new AudioFactory(duration)); |
| scoped_ptr<VideoFactory> video_factory(new VideoFactory(duration)); |
| |
| audio_factory->RequestConfigChange(config_change_position); |
| |
| ASSERT_TRUE(StartAVPlayback(audio_factory.Pass(), video_factory.Pass(), |
| kAlwaysReconfigAudio, |
| "AVAudioConfigChangeWhilePlaying")); |
| |
| // Wait till completion |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackCompleted, |
| base::Unretained(&manager_)), |
| completion_timeout)); |
| |
| // The audio codec should be recreated upon config changes. |
| EXPECT_EQ(2, manager_.num_audio_codecs_created()); |
| |
| // The video codec should be kept. |
| EXPECT_EQ(1, manager_.num_video_codecs_created()); |
| |
| // Check that we did not miss video frames. |
| int expected_video_frames = GetFrameCount(duration, kVideoFramePeriod, 0); |
| EXPECT_EQ(expected_video_frames, |
| manager_.render_stat_[DemuxerStream::VIDEO].num_values()); |
| |
| // Check that we did not miss audio frames. We expect two postponed frames |
| // that are not reported. |
| int expected_audio_frames = GetFrameCount(duration, kAudioFramePeriod, 1) - 2; |
| EXPECT_EQ(expected_audio_frames, |
| manager_.render_stat_[DemuxerStream::AUDIO].num_values()); |
| } |
| |
| TEST_F(MediaCodecPlayerTest, AVSimultaneousConfigChange_1) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| // Test that the playback continues if audio and video config changes happen |
| // at the same time. |
| |
| base::TimeDelta duration = base::TimeDelta::FromMilliseconds(1200); |
| base::TimeDelta config_change_audio = base::TimeDelta::FromMilliseconds(1000); |
| base::TimeDelta config_change_video = base::TimeDelta::FromMilliseconds(1000); |
| |
| base::TimeDelta completion_timeout = base::TimeDelta::FromMilliseconds(3000); |
| |
| scoped_ptr<AudioFactory> audio_factory(new AudioFactory(duration)); |
| scoped_ptr<VideoFactory> video_factory(new VideoFactory(duration)); |
| |
| audio_factory->RequestConfigChange(config_change_audio); |
| video_factory->RequestConfigChange(config_change_video); |
| |
| ASSERT_TRUE(StartAVPlayback(audio_factory.Pass(), video_factory.Pass(), |
| kAlwaysReconfigAudio | kAlwaysReconfigVideo, |
| "AVSimultaneousConfigChange_1")); |
| |
| // Wait till completion |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackCompleted, |
| base::Unretained(&manager_)), |
| completion_timeout)); |
| |
| // The audio codec should be recreated upon config changes. |
| EXPECT_EQ(2, manager_.num_audio_codecs_created()); |
| |
| // The video codec should be recreated upon config changes. |
| EXPECT_EQ(2, manager_.num_video_codecs_created()); |
| |
| // Check that we did not miss video frames. |
| int expected_video_frames = GetFrameCount(duration, kVideoFramePeriod, 1); |
| EXPECT_EQ(expected_video_frames, |
| manager_.render_stat_[DemuxerStream::VIDEO].num_values()); |
| |
| // Check that we did not miss audio frames. We expect two postponed frames |
| // that are not reported. |
| int expected_audio_frames = GetFrameCount(duration, kAudioFramePeriod, 1) - 2; |
| EXPECT_EQ(expected_audio_frames, |
| manager_.render_stat_[DemuxerStream::AUDIO].num_values()); |
| } |
| |
| TEST_F(MediaCodecPlayerTest, AVSimultaneousConfigChange_2) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| // Test that the playback continues if audio and video config changes happen |
| // at the same time. Move audio change moment slightly to make it drained |
| // after video. |
| |
| base::TimeDelta duration = base::TimeDelta::FromMilliseconds(1200); |
| base::TimeDelta config_change_audio = base::TimeDelta::FromMilliseconds(1020); |
| base::TimeDelta config_change_video = base::TimeDelta::FromMilliseconds(1000); |
| |
| base::TimeDelta completion_timeout = base::TimeDelta::FromMilliseconds(3000); |
| |
| scoped_ptr<AudioFactory> audio_factory(new AudioFactory(duration)); |
| scoped_ptr<VideoFactory> video_factory(new VideoFactory(duration)); |
| |
| audio_factory->RequestConfigChange(config_change_audio); |
| video_factory->RequestConfigChange(config_change_video); |
| |
| ASSERT_TRUE(StartAVPlayback(audio_factory.Pass(), video_factory.Pass(), |
| kAlwaysReconfigAudio | kAlwaysReconfigVideo, |
| "AVSimultaneousConfigChange_2")); |
| |
| // Wait till completion |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackCompleted, |
| base::Unretained(&manager_)), |
| completion_timeout)); |
| |
| // The audio codec should be recreated upon config changes. |
| EXPECT_EQ(2, manager_.num_audio_codecs_created()); |
| |
| // The video codec should be recreated upon config changes. |
| EXPECT_EQ(2, manager_.num_video_codecs_created()); |
| |
| // Check that we did not miss video frames. |
| int expected_video_frames = GetFrameCount(duration, kVideoFramePeriod, 1); |
| EXPECT_EQ(expected_video_frames, |
| manager_.render_stat_[DemuxerStream::VIDEO].num_values()); |
| |
| // Check that we did not miss audio frames. We expect two postponed frames |
| // that are not reported. |
| int expected_audio_frames = GetFrameCount(duration, kAudioFramePeriod, 1) - 2; |
| EXPECT_EQ(expected_audio_frames, |
| manager_.render_stat_[DemuxerStream::AUDIO].num_values()); |
| } |
| |
| TEST_F(MediaCodecPlayerTest, AVAudioEndsAcrossVideoConfigChange) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| // Test that audio can end while video config change processing. |
| |
| base::TimeDelta audio_duration = base::TimeDelta::FromMilliseconds(1000); |
| base::TimeDelta video_duration = base::TimeDelta::FromMilliseconds(1200); |
| base::TimeDelta config_change_video = base::TimeDelta::FromMilliseconds(1000); |
| |
| base::TimeDelta completion_timeout = base::TimeDelta::FromMilliseconds(3000); |
| |
| scoped_ptr<AudioFactory> audio_factory(new AudioFactory(audio_duration)); |
| scoped_ptr<VideoFactory> video_factory(new VideoFactory(video_duration)); |
| |
| video_factory->RequestConfigChange(config_change_video); |
| |
| ASSERT_TRUE(StartAVPlayback(audio_factory.Pass(), video_factory.Pass(), |
| kAlwaysReconfigVideo, |
| "AVAudioEndsAcrossVideoConfigChange")); |
| |
| // Wait till completion |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackCompleted, |
| base::Unretained(&manager_)), |
| completion_timeout)); |
| |
| // The audio codec should not be recreated. |
| EXPECT_EQ(1, manager_.num_audio_codecs_created()); |
| |
| // The video codec should be recreated upon config changes. |
| EXPECT_EQ(2, manager_.num_video_codecs_created()); |
| |
| // Check that we did not miss video frames. |
| int expected_video_frames = |
| GetFrameCount(video_duration, kVideoFramePeriod, 1); |
| EXPECT_EQ(expected_video_frames, |
| manager_.render_stat_[DemuxerStream::VIDEO].num_values()); |
| |
| // Check the last video frame timestamp. The maximum render pts may differ |
| // from |video_duration| because of the testing artefact: if the last video |
| // chunk is incomplete if will have different last pts due to B-frames |
| // rearrangements. |
| EXPECT_LE(video_duration, |
| manager_.render_stat_[DemuxerStream::VIDEO].max().pts); |
| |
| // Check that the playback time reported by the player goes past |
| // the audio time and corresponds to video after the audio ended. |
| EXPECT_EQ(video_duration, manager_.pts_stat_.max()); |
| } |
| |
| TEST_F(MediaCodecPlayerTest, AVVideoEndsAcrossAudioConfigChange) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| // Test that video can end while audio config change processing. |
| base::TimeDelta audio_duration = base::TimeDelta::FromMilliseconds(1200); |
| base::TimeDelta video_duration = base::TimeDelta::FromMilliseconds(1000); |
| base::TimeDelta config_change_audio = base::TimeDelta::FromMilliseconds(1000); |
| |
| base::TimeDelta completion_timeout = base::TimeDelta::FromMilliseconds(3000); |
| |
| scoped_ptr<AudioFactory> audio_factory(new AudioFactory(audio_duration)); |
| scoped_ptr<VideoFactory> video_factory(new VideoFactory(video_duration)); |
| |
| audio_factory->RequestConfigChange(config_change_audio); |
| |
| ASSERT_TRUE(StartAVPlayback(audio_factory.Pass(), video_factory.Pass(), |
| kAlwaysReconfigAudio, |
| "AVVideoEndsAcrossAudioConfigChange")); |
| |
| // Wait till completion |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackCompleted, |
| base::Unretained(&manager_)), |
| completion_timeout)); |
| |
| // The audio codec should be recreated upon config changes. |
| EXPECT_EQ(2, manager_.num_audio_codecs_created()); |
| |
| // The video codec should not be recreated. |
| EXPECT_EQ(1, manager_.num_video_codecs_created()); |
| |
| // Check that we did not miss audio frames. We expect two postponed frames |
| // that are not reported. |
| int expected_audio_frames = |
| GetFrameCount(audio_duration, kAudioFramePeriod, 1) - 2; |
| EXPECT_EQ(expected_audio_frames, |
| manager_.render_stat_[DemuxerStream::AUDIO].num_values()); |
| } |
| |
| TEST_F(MediaCodecPlayerTest, AVPrerollAcrossVideoConfigChange) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| // Test that preroll continues if interrupted by video config change. |
| |
| base::TimeDelta duration = base::TimeDelta::FromMilliseconds(1200); |
| base::TimeDelta seek_position = base::TimeDelta::FromMilliseconds(1000); |
| base::TimeDelta config_change_position = |
| base::TimeDelta::FromMilliseconds(800); |
| base::TimeDelta video_preroll_intvl = base::TimeDelta::FromMilliseconds(500); |
| base::TimeDelta preroll_timeout = base::TimeDelta::FromMilliseconds(3000); |
| |
| demuxer_->SetVideoPrerollInterval(video_preroll_intvl); |
| |
| scoped_ptr<AudioFactory> audio_factory(new AudioFactory(duration)); |
| |
| scoped_ptr<VideoFactory> video_factory(new VideoFactory(duration)); |
| video_factory->RequestConfigChange(config_change_position); |
| |
| ASSERT_TRUE(StartAVSeekAndPreroll(audio_factory.Pass(), video_factory.Pass(), |
| seek_position, kAlwaysReconfigVideo, |
| "AVPrerollAcrossVideoConfigChange")); |
| |
| // Wait till preroll finishes and the real playback starts. |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackStarted, |
| base::Unretained(&manager_)), |
| preroll_timeout)); |
| |
| // The presense of config change should not affect preroll behavior: |
| |
| // Ensure that the first audio and video pts are close to each other and are |
| // reported at the close moments in time. |
| |
| EXPECT_TRUE(manager_.HasFirstFrame(DemuxerStream::AUDIO)); |
| EXPECT_TRUE(WaitForCondition( |
| base::Bind(&MockMediaPlayerManager::HasFirstFrame, |
| base::Unretained(&manager_), DemuxerStream::VIDEO))); |
| |
| EXPECT_TRUE(AlmostEqual(manager_.FirstFramePTS(DemuxerStream::AUDIO), |
| manager_.FirstFramePTS(DemuxerStream::VIDEO), 25)); |
| |
| EXPECT_TRUE(AlmostEqual(manager_.FirstFrameTime(DemuxerStream::AUDIO), |
| manager_.FirstFrameTime(DemuxerStream::VIDEO), 50)); |
| |
| // The playback should start at |seek_position| |
| EXPECT_TRUE(AlmostEqual(seek_position, manager_.pts_stat_.min(), 25)); |
| } |
| |
| TEST_F(MediaCodecPlayerTest, AVPrerollAcrossAudioConfigChange) { |
| SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); |
| |
| // Test that preroll continues if interrupted by video config change. |
| |
| base::TimeDelta duration = base::TimeDelta::FromMilliseconds(1200); |
| base::TimeDelta seek_position = base::TimeDelta::FromMilliseconds(1000); |
| base::TimeDelta config_change_position = |
| base::TimeDelta::FromMilliseconds(800); |
| base::TimeDelta audio_preroll_intvl = base::TimeDelta::FromMilliseconds(400); |
| base::TimeDelta preroll_timeout = base::TimeDelta::FromMilliseconds(3000); |
| |
| demuxer_->SetAudioPrerollInterval(audio_preroll_intvl); |
| |
| scoped_ptr<AudioFactory> audio_factory(new AudioFactory(duration)); |
| audio_factory->RequestConfigChange(config_change_position); |
| |
| scoped_ptr<VideoFactory> video_factory(new VideoFactory(duration)); |
| |
| ASSERT_TRUE(StartAVSeekAndPreroll(audio_factory.Pass(), video_factory.Pass(), |
| seek_position, kAlwaysReconfigAudio, |
| "AVPrerollAcrossAudioConfigChange")); |
| |
| // Wait till preroll finishes and the real playback starts. |
| EXPECT_TRUE( |
| WaitForCondition(base::Bind(&MockMediaPlayerManager::IsPlaybackStarted, |
| base::Unretained(&manager_)), |
| preroll_timeout)); |
| |
| // The presense of config change should not affect preroll behavior: |
| |
| // Ensure that the first audio and video pts are close to each other and are |
| // reported at the close moments in time. |
| |
| EXPECT_TRUE(manager_.HasFirstFrame(DemuxerStream::AUDIO)); |
| EXPECT_TRUE(WaitForCondition( |
| base::Bind(&MockMediaPlayerManager::HasFirstFrame, |
| base::Unretained(&manager_), DemuxerStream::VIDEO))); |
| |
| // Wait for some more video |
| WaitForDelay(base::TimeDelta::FromMilliseconds(100)); |
| |
| EXPECT_TRUE(AlmostEqual(manager_.FirstFramePTS(DemuxerStream::AUDIO), |
| manager_.FirstFramePTS(DemuxerStream::VIDEO), 25)); |
| |
| // Because for video preroll the first frame after preroll renders during the |
| // preroll stage (and not after the preroll is done) we cannot guarantee the |
| // proper video timimg in this test. |
| // TODO(timav): maybe we should not call the testing callback for |
| // kRenderAfterPreroll for video (for audio we already do not call). |
| // EXPECT_TRUE(AlmostEqual(manager_.FirstFrameTime(DemuxerStream::AUDIO), |
| // manager_.FirstFrameTime(DemuxerStream::VIDEO), 50)); |
| |
| // The playback should start at |seek_position| |
| EXPECT_TRUE(AlmostEqual(seek_position, manager_.pts_stat_.min(), 25)); |
| } |
| |
| } // namespace media |