diff options
author | wolenetz@chromium.org <wolenetz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-24 02:07:00 +0000 |
---|---|---|
committer | wolenetz@chromium.org <wolenetz@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-10-24 02:07:00 +0000 |
commit | 85eb95d47c73880b935aa1bc50ef4cb552ad4578 (patch) | |
tree | dbb89d401a523bf26b68aaa959dca12ad3a1734e /media | |
parent | ff77bfd1cce9d8a3be10901c4e1e1f81d62ab9d6 (diff) | |
download | chromium_src-85eb95d47c73880b935aa1bc50ef4cb552ad4578.zip chromium_src-85eb95d47c73880b935aa1bc50ef4cb552ad4578.tar.gz chromium_src-85eb95d47c73880b935aa1bc50ef4cb552ad4578.tar.bz2 |
Restore preroll state when decoder job created
Adds resumption of MediaDecoderJob preroll when MediaSourcePlayer
re-creates the job(s), and adds related MediaSourcePlayerTests.
Includes test hooks to verify job's prerolling status, also to be used by eventual fix for bug 309184.
R=qinmin@chromium.org,acolwell@chromium.org
BUG=309122, 309184
TEST=All MediaSourcePlayerTests pass on Android with MediaCodecBridge available.
Review URL: https://codereview.chromium.org/33543004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@230600 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'media')
-rw-r--r-- | media/base/android/media_decoder_job.cc | 33 | ||||
-rw-r--r-- | media/base/android/media_decoder_job.h | 22 | ||||
-rw-r--r-- | media/base/android/media_source_player.cc | 10 | ||||
-rw-r--r-- | media/base/android/media_source_player.h | 4 | ||||
-rw-r--r-- | media/base/android/media_source_player_unittest.cc | 328 |
5 files changed, 325 insertions, 72 deletions
diff --git a/media/base/android/media_decoder_job.cc b/media/base/android/media_decoder_job.cc index 59db63a..5ea284e 100644 --- a/media/base/android/media_decoder_job.cc +++ b/media/base/android/media_decoder_job.cc @@ -28,6 +28,7 @@ MediaDecoderJob::MediaDecoderJob( media_codec_bridge_(media_codec_bridge), needs_flush_(false), input_eos_encountered_(false), + prerolling_(true), weak_this_(this), request_data_cb_(request_data_cb), access_unit_index_(0), @@ -121,6 +122,16 @@ void MediaDecoderJob::Flush() { on_data_received_cb_.Reset(); } +void MediaDecoderJob::BeginPrerolling( + const base::TimeDelta& preroll_timestamp) { + DVLOG(1) << __FUNCTION__ << "(" << preroll_timestamp.InSecondsF() << ")"; + DCHECK(ui_loop_->BelongsToCurrentThread()); + DCHECK(!is_decoding()); + + preroll_timestamp_ = preroll_timestamp; + prerolling_ = true; +} + void MediaDecoderJob::Release() { DCHECK(ui_loop_->BelongsToCurrentThread()); @@ -282,11 +293,9 @@ void MediaDecoderJob::DecodeInternal( &output_eos_encountered); if (status != MEDIA_CODEC_OK) { - if (status == MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED) { - if (media_codec_bridge_->GetOutputBuffers()) - status = MEDIA_CODEC_OK; - else - status = MEDIA_CODEC_ERROR; + if (status == MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED && + !media_codec_bridge_->GetOutputBuffers()) { + status = MEDIA_CODEC_ERROR; } callback.Run(status, kNoTimestamp(), 0); return; @@ -299,9 +308,12 @@ void MediaDecoderJob::DecodeInternal( status = MEDIA_CODEC_INPUT_END_OF_STREAM; // Check whether we need to render the output. - // TODO(qinmin): comparing |unit.timestamp| with |preroll_timestamp_| is not - // accurate due to data reordering. Need to use the |presentation_timestamp| - // for video, and use |size| to calculate the timestamp for audio. + // TODO(qinmin): comparing most recently queued input's |unit.timestamp| with + // |preroll_timestamp_| is not accurate due to data reordering and possible + // input queueing without immediate dequeue when |input_status| != + // |MEDIA_CODEC_OK|. Need to use the |presentation_timestamp| for video, and + // use |size| to calculate the timestamp for audio. See + // http://crbug.com/310823 and b/11356652. bool render_output = unit.timestamp >= preroll_timestamp_ && (status != MEDIA_CODEC_OUTPUT_END_OF_STREAM || size != 0u); base::TimeDelta time_to_render; @@ -350,6 +362,11 @@ void MediaDecoderJob::OnDecodeCompleted( } DCHECK(!decode_cb_.is_null()); + + // If output was queued for rendering, then we have completed prerolling. + if (presentation_timestamp != kNoTimestamp()) + prerolling_ = false; + switch (status) { case MEDIA_CODEC_OK: case MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER: diff --git a/media/base/android/media_decoder_job.h b/media/base/android/media_decoder_job.h index ea3956e..e9530b39 100644 --- a/media/base/android/media_decoder_job.h +++ b/media/base/android/media_decoder_job.h @@ -27,6 +27,9 @@ class MediaDecoderJob { // Callback when a decoder job finishes its work. Args: whether decode // finished successfully, presentation time, audio output bytes. + // If the presentation time is equal to kNoTimestamp(), the decoder job + // skipped rendering of the decoded output and the callback target should + // update its clock to avoid introducing extra delays to the next frame. typedef base::Callback<void(MediaCodecStatus, const base::TimeDelta&, size_t)> DecoderCallback; // Callback when a decoder job finishes releasing the output buffer. @@ -66,9 +69,10 @@ class MediaDecoderJob { // Flush the decoder. void Flush(); - void set_preroll_timestamp(const base::TimeDelta& preroll_timestamp) { - preroll_timestamp_ = preroll_timestamp; - } + // Enter prerolling state. The job must not currently be decoding. + void BeginPrerolling(const base::TimeDelta& preroll_timestamp); + + bool prerolling() const { return prerolling_; } bool is_decoding() const { return !decode_cb_.is_null(); } @@ -120,9 +124,8 @@ class MediaDecoderJob { const DecoderCallback& callback); // Called on the UI thread to indicate that one decode cycle has completed. - // If the |presentation_timestamp| is equal to kNoTimestamp(), - // the caller should ignore the output of this frame and update its clock to - // avoid introducing extra delays to the next frame. + // Completes any pending job destruction or any pending decode stop. If + // destruction was not pending, passes its arguments to |decode_cb_|. void OnDecodeCompleted(MediaCodecStatus status, const base::TimeDelta& presentation_timestamp, size_t audio_output_bytes); @@ -149,6 +152,13 @@ class MediaDecoderJob { // is not very accurate. base::TimeDelta preroll_timestamp_; + // Indicates prerolling state. If true, this job has not yet decoded output + // that it will render, since the most recent of job construction or + // BeginPrerolling(). If false, |preroll_timestamp_| has been reached. + // TODO(qinmin): Comparing access unit's timestamp with |preroll_timestamp_| + // is not very accurate. + bool prerolling_; + // Weak pointer passed to media decoder jobs for callbacks. It is bounded to // the decoder thread. base::WeakPtrFactory<MediaDecoderJob> weak_this_; diff --git a/media/base/android/media_source_player.cc b/media/base/android/media_source_player.cc index 016f8b5..508cd44 100644 --- a/media/base/android/media_source_player.cc +++ b/media/base/android/media_source_player.cc @@ -394,10 +394,11 @@ void MediaSourcePlayer::OnDemuxerSeekDone() { // to preroll media decoder jobs. Currently |start_presentation_timestamp_| // is calculated from decoder output, while preroll relies on the access // unit's timestamp. There are some differences between the two. + preroll_timestamp_ = current_time; if (audio_decoder_job_) - audio_decoder_job_->set_preroll_timestamp(current_time); + audio_decoder_job_->BeginPrerolling(preroll_timestamp_); if (video_decoder_job_) - video_decoder_job_->set_preroll_timestamp(current_time); + video_decoder_job_->BeginPrerolling(preroll_timestamp_); ProcessPendingEvents(); } @@ -658,6 +659,7 @@ void MediaSourcePlayer::ConfigureAudioDecoderJob() { if (audio_decoder_job_) { SetVolumeInternal(); + audio_decoder_job_->BeginPrerolling(preroll_timestamp_); reconfig_audio_decoder_ = false; } } @@ -692,8 +694,10 @@ void MediaSourcePlayer::ConfigureVideoDecoderJob() { base::Bind(&DemuxerAndroid::RequestDemuxerData, base::Unretained(demuxer_.get()), DemuxerStream::VIDEO))); - if (video_decoder_job_) + if (video_decoder_job_) { + video_decoder_job_->BeginPrerolling(preroll_timestamp_); reconfig_video_decoder_ = false; + } // Inform the fullscreen view the player is ready. // TODO(qinmin): refactor MediaPlayerBridge so that we have a better way diff --git a/media/base/android/media_source_player.h b/media/base/android/media_source_player.h index 61215b4..652ab76 100644 --- a/media/base/android/media_source_player.h +++ b/media/base/android/media_source_player.h @@ -207,6 +207,10 @@ class MEDIA_EXPORT MediaSourcePlayer : public MediaPlayerAndroid, bool reconfig_audio_decoder_; bool reconfig_video_decoder_; + // Track the most recent preroll target. Decoder re-creation needs this to + // resume any in-progress preroll. + base::TimeDelta preroll_timestamp_; + // A cancelable task that is posted when the audio decoder starts requesting // new data. This callback runs if no data arrives before the timeout period // elapses. diff --git a/media/base/android/media_source_player_unittest.cc b/media/base/android/media_source_player_unittest.cc index 8826848..7d20ef4 100644 --- a/media/base/android/media_source_player_unittest.cc +++ b/media/base/android/media_source_player_unittest.cc @@ -92,11 +92,14 @@ class MockDemuxerAndroid : public DemuxerAndroid { explicit MockDemuxerAndroid(base::MessageLoop* message_loop) : message_loop_(message_loop), num_data_requests_(0), - num_seek_requests_(0) {} + num_seek_requests_(0), + num_config_requests_(0) {} virtual ~MockDemuxerAndroid() {} virtual void Initialize(DemuxerAndroidClient* client) OVERRIDE {} - virtual void RequestDemuxerConfigs() OVERRIDE {} + virtual void RequestDemuxerConfigs() OVERRIDE { + num_config_requests_++; + } virtual void RequestDemuxerData(DemuxerStream::Type type) OVERRIDE { num_data_requests_++; if (message_loop_->is_running()) @@ -109,6 +112,7 @@ class MockDemuxerAndroid : public DemuxerAndroid { int num_data_requests() const { return num_data_requests_; } int num_seek_requests() const { return num_seek_requests_; } + int num_config_requests() const { return num_config_requests_; } private: base::MessageLoop* message_loop_; @@ -119,6 +123,9 @@ class MockDemuxerAndroid : public DemuxerAndroid { // The number of seek requests this object has seen. int num_seek_requests_; + // The number of demuxer config requests this object has seen. + int num_config_requests_; + DISALLOW_COPY_AND_ASSIGN(MockDemuxerAndroid); }; @@ -141,6 +148,17 @@ class MediaSourcePlayerTest : public testing::Test { player_.video_decoder_job_.get()); } + // Get the per-job prerolling status from the MediaSourcePlayer's job matching + // |is_audio|. Caller must guard against NPE if the player's job is NULL. + bool IsPrerolling(bool is_audio) { + return GetMediaDecoderJob(is_audio)->prerolling(); + } + + // Get the preroll timestamp from the MediaSourcePlayer. + base::TimeDelta GetPrerollTimestamp() { + return player_.preroll_timestamp_; + } + // Starts an audio decoder job. void StartAudioDecoderJob() { DemuxerConfigs configs; @@ -201,13 +219,56 @@ class MediaSourcePlayerTest : public testing::Test { } DemuxerData CreateEOSAck(bool is_audio) { - DemuxerData data; - data.type = is_audio ? DemuxerStream::AUDIO : DemuxerStream::VIDEO; - data.access_units.resize(1); - data.access_units[0].status = DemuxerStream::kOk; - data.access_units[0].end_of_stream = true; - return data; - } + DemuxerData data; + data.type = is_audio ? DemuxerStream::AUDIO : DemuxerStream::VIDEO; + data.access_units.resize(1); + data.access_units[0].status = DemuxerStream::kOk; + data.access_units[0].end_of_stream = true; + return data; + } + + DemuxerData CreateAbortedAck(bool is_audio) { + DemuxerData data; + data.type = is_audio ? DemuxerStream::AUDIO : DemuxerStream::VIDEO; + data.access_units.resize(1); + data.access_units[0].status = DemuxerStream::kAborted; + return data; + } + + // Seek, including simulated receipt of |kAborted| read between SeekTo() + // and OnDemuxerSeekDone(). Use this helper method only when the player + // already has created the decoder job. + void SeekPlayer(bool is_audio, const base::TimeDelta& seek_time) { + EXPECT_TRUE(GetMediaDecoderJob(is_audio)); + + int original_num_seeks = demuxer_->num_seek_requests(); + int original_num_data_requests = demuxer_->num_data_requests(); + + // Initiate a seek. Skip the round-trip of requesting seek from renderer. + // Instead behave as if the renderer has asked us to seek. + player_.SeekTo(seek_time); + + // Verify that the seek does not occur until previously outstanding data + // request is satisfied. + EXPECT_EQ(original_num_seeks, demuxer_->num_seek_requests()); + + // Simulate seeking causes the demuxer to abort the outstanding read caused + // by the seek. + player_.OnDemuxerDataAvailable(CreateAbortedAck(is_audio)); + + // Verify that the seek is requested now that the outstanding read is + // completed by aborted access unit. + EXPECT_EQ(original_num_seeks + 1, demuxer_->num_seek_requests()); + + // Send back the seek done notification. This should trigger the player to + // call OnReadFromDemuxer() again. + EXPECT_EQ(original_num_data_requests, demuxer_->num_data_requests()); + player_.OnDemuxerSeekDone(); + EXPECT_EQ(original_num_data_requests + 1, demuxer_->num_data_requests()); + + // No other seek should have been requested. + EXPECT_EQ(original_num_seeks + 1, demuxer_->num_seek_requests()); + } base::TimeTicks StartTimeTicks() { return player_.start_time_ticks_; @@ -318,34 +379,8 @@ TEST_F(MediaSourcePlayerTest, ReadFromDemuxerAfterSeek) { StartAudioDecoderJob(); EXPECT_TRUE(GetMediaDecoderJob(true)); EXPECT_EQ(1, demuxer_->num_data_requests()); - - // Initiate a seek. Skip the round-trip of requesting seek from renderer. - // Instead behave as if the renderer has asked us to seek. - player_.SeekTo(base::TimeDelta()); - - // Verify that the seek does not occur until the initial prefetch - // completes. - EXPECT_EQ(0, demuxer_->num_seek_requests()); - - // Simulate aborted read caused by the seek. This aborts the initial - // prefetch. - DemuxerData data; - data.type = DemuxerStream::AUDIO; - data.access_units.resize(1); - data.access_units[0].status = DemuxerStream::kAborted; - player_.OnDemuxerDataAvailable(data); - - // Verify that the seek is requested now that the initial prefetch - // has completed. - EXPECT_EQ(1, demuxer_->num_seek_requests()); - - // Sending back the seek done notification. This should trigger the player to - // call OnReadFromDemuxer() again. - player_.OnDemuxerSeekDone(); + SeekPlayer(true, base::TimeDelta()); EXPECT_EQ(2, demuxer_->num_data_requests()); - - // Reconfirm exactly 1 seek request has been made of demuxer. - EXPECT_EQ(1, demuxer_->num_seek_requests()); } TEST_F(MediaSourcePlayerTest, SetSurfaceWhileSeeking) { @@ -403,7 +438,7 @@ TEST_F(MediaSourcePlayerTest, ChangeMultipleSurfaceWhileDecoding) { player_.SetVideoSurface(surface.Pass()); // Wait for the decoder job to finish decoding. - while(GetMediaDecoderJob(false)->is_decoding()) + while (GetMediaDecoderJob(false)->is_decoding()) message_loop_.RunUntilIdle(); // A seek should be initiated to request Iframe. EXPECT_EQ(1, demuxer_->num_seek_requests()); @@ -548,7 +583,7 @@ TEST_F(MediaSourcePlayerTest, StartTimeTicksResetAfterDecoderUnderruns) { // Send new data to the decoder so it can finish the currently // pending decode. player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForAudio(3)); - while(GetMediaDecoderJob(true)->is_decoding()) + while (GetMediaDecoderJob(true)->is_decoding()) message_loop_.RunUntilIdle(); // Verify the start time ticks is cleared at this point because the @@ -629,14 +664,11 @@ TEST_F(MediaSourcePlayerTest, NoRequestForDataAfterAbort) { EXPECT_EQ(1, demuxer_->num_data_requests()); // Send an aborted access unit. - DemuxerData data; - data.type = DemuxerStream::AUDIO; - data.access_units.resize(1); - data.access_units[0].status = DemuxerStream::kAborted; - player_.OnDemuxerDataAvailable(data); + player_.OnDemuxerDataAvailable(CreateAbortedAck(true)); + EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding()); // Wait for the decoder job to finish decoding. - while(GetMediaDecoderJob(true)->is_decoding()) + while (GetMediaDecoderJob(true)->is_decoding()) message_loop_.RunUntilIdle(); // No request will be sent for new data. @@ -661,35 +693,220 @@ TEST_F(MediaSourcePlayerTest, DemuxerDataArrivesAfterRelease) { EXPECT_EQ(1, demuxer_->num_data_requests()); } -TEST_F(MediaSourcePlayerTest, PrerollAfterSeek) { - if (!MediaCodecBridge::IsAvailable()) { - LOG(INFO) << "Could not run test - not supported on device."; - return; +TEST_F(MediaSourcePlayerTest, PrerollAudioAfterSeek) { + SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); + + // Test decoder job will preroll the media to the seek position. + StartAudioDecoderJob(); + EXPECT_TRUE(GetMediaDecoderJob(true)); + EXPECT_EQ(1, demuxer_->num_data_requests()); + + SeekPlayer(true, base::TimeDelta::FromMilliseconds(100)); + EXPECT_TRUE(IsPrerolling(true)); + EXPECT_EQ(100.0, GetPrerollTimestamp().InMillisecondsF()); + + // Send some data before the seek position. + for (int i = 1; i < 4; ++i) { + player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForAudio(i)); + EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding()); + message_loop_.Run(); } + EXPECT_EQ(100.0, player_.GetCurrentTime().InMillisecondsF()); + EXPECT_TRUE(IsPrerolling(true)); + + // Send data after the seek position. + DemuxerData data = CreateReadFromDemuxerAckForAudio(3); + data.access_units[0].timestamp = base::TimeDelta::FromMilliseconds(100); + player_.OnDemuxerDataAvailable(data); + message_loop_.Run(); + EXPECT_LT(100.0, player_.GetCurrentTime().InMillisecondsF()); + EXPECT_FALSE(IsPrerolling(true)); +} + +TEST_F(MediaSourcePlayerTest, PrerollVideoAfterSeek) { + SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); // Test decoder job will preroll the media to the seek position. + CreateAndSetVideoSurface(); + StartVideoDecoderJob(); + EXPECT_TRUE(GetMediaDecoderJob(false)); + EXPECT_EQ(1, demuxer_->num_data_requests()); + + SeekPlayer(false, base::TimeDelta::FromMilliseconds(100)); + EXPECT_TRUE(IsPrerolling(false)); + EXPECT_EQ(100.0, GetPrerollTimestamp().InMillisecondsF()); + + // Send some data before the seek position. + DemuxerData data; + for (int i = 1; i < 4; ++i) { + data = CreateReadFromDemuxerAckForVideo(); + data.access_units[0].timestamp = base::TimeDelta::FromMilliseconds(i * 30); + player_.OnDemuxerDataAvailable(data); + EXPECT_TRUE(GetMediaDecoderJob(false)->is_decoding()); + message_loop_.Run(); + } + EXPECT_EQ(100.0, player_.GetCurrentTime().InMillisecondsF()); + EXPECT_TRUE(IsPrerolling(false)); + + // Send data at the seek position. + data = CreateReadFromDemuxerAckForVideo(); + data.access_units[0].timestamp = base::TimeDelta::FromMilliseconds(100); + player_.OnDemuxerDataAvailable(data); + message_loop_.Run(); + + // TODO(wolenetz/qinmin): Player's maintenance of current time for video-only + // streams depends on decoder output, which may be initially inaccurate, and + // encoded video test data may also need updating. Verify at least that AU + // timestamp-based preroll logic has determined video preroll has completed. + EXPECT_FALSE(IsPrerolling(false)); +} + +TEST_F(MediaSourcePlayerTest, SeekingAfterCompletingPrerollRestartsPreroll) { + SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); + + // Test decoder job will begin prerolling upon seek, when it was not + // prerolling prior to the seek. + StartAudioDecoderJob(); + MediaDecoderJob* decoder_job = GetMediaDecoderJob(true); + EXPECT_TRUE(decoder_job); + EXPECT_EQ(1, demuxer_->num_data_requests()); + EXPECT_TRUE(IsPrerolling(true)); + + // Complete the initial preroll by feeding data to the decoder. + for (int i = 0; i < 4; ++i) { + player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForAudio(i)); + EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding()); + message_loop_.Run(); + } + EXPECT_LT(0.0, player_.GetCurrentTime().InMillisecondsF()); + EXPECT_FALSE(IsPrerolling(true)); + + SeekPlayer(true, base::TimeDelta::FromMilliseconds(500)); + + // Prerolling should have begun again. + EXPECT_TRUE(IsPrerolling(true)); + EXPECT_EQ(500.0, GetPrerollTimestamp().InMillisecondsF()); + + // Send data at and after the seek position. Prerolling should complete. + for (int i = 0; i < 4; ++i) { + DemuxerData data = CreateReadFromDemuxerAckForAudio(i); + data.access_units[0].timestamp = base::TimeDelta::FromMilliseconds( + 500 + 30 * (i - 1)); + player_.OnDemuxerDataAvailable(data); + EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding()); + message_loop_.Run(); + } + EXPECT_LT(500.0, player_.GetCurrentTime().InMillisecondsF()); + EXPECT_FALSE(IsPrerolling(true)); + + // Throughout this test, we should have not re-created the decoder job, so + // IsPrerolling() transition from false to true was not due to constructor + // initialization. It was due to BeginPrerolling(). + EXPECT_EQ(decoder_job, GetMediaDecoderJob(true)); +} + +TEST_F(MediaSourcePlayerTest, PrerollContinuesAcrossReleaseAndStart) { + SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); + + // Test decoder job will resume media prerolling if interrupted by Release() + // and Start(). + StartAudioDecoderJob(); + EXPECT_TRUE(GetMediaDecoderJob(true)); + EXPECT_EQ(1, demuxer_->num_data_requests()); + + SeekPlayer(true, base::TimeDelta::FromMilliseconds(100)); + EXPECT_TRUE(IsPrerolling(true)); + EXPECT_EQ(100.0, GetPrerollTimestamp().InMillisecondsF()); + + // Send some data before the seek position. + // Test uses 'large' number of iterations because decoder job may not get + // MEDIA_CODEC_OK output status until after a few dequeue output attempts. + // This allows decoder status to stabilize prior to AU timestamp reaching + // the preroll target. + DemuxerData data; + for (int i = 0; i < 10; ++i) { + data = CreateReadFromDemuxerAckForAudio(3); + data.access_units[0].timestamp = base::TimeDelta::FromMilliseconds(i * 10); + if (i == 1) { + // While still prerolling, Release() and Start() the player. + // TODO(qinmin): Simulation of multiple in-flight data requests (one from + // before Release(), one from after Start()) is not included here, and + // neither is any data enqueued for later decode if it arrives after + // Release() and before Start(). See http://crbug.com/306314. Assumption + // for this test, to prevent flakiness until the bug is fixed, is the + // first request's data arrives before Start(). Though that data is not + // seen by decoder, this assumption allows preroll continuation + // verification and prevents multiple in-flight data requests. + player_.Release(); + player_.OnDemuxerDataAvailable(data); + message_loop_.RunUntilIdle(); + EXPECT_EQ(NULL, GetMediaDecoderJob(true)); + StartAudioDecoderJob(); + EXPECT_TRUE(GetMediaDecoderJob(true)); + } else { + player_.OnDemuxerDataAvailable(data); + EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding()); + message_loop_.Run(); + } + EXPECT_TRUE(IsPrerolling(true)); + } + EXPECT_EQ(100.0, player_.GetCurrentTime().InMillisecondsF()); + EXPECT_TRUE(IsPrerolling(true)); + + // Send data after the seek position. + data = CreateReadFromDemuxerAckForAudio(3); + data.access_units[0].timestamp = base::TimeDelta::FromMilliseconds(100); + player_.OnDemuxerDataAvailable(data); + message_loop_.Run(); + EXPECT_LT(100.0, player_.GetCurrentTime().InMillisecondsF()); + EXPECT_FALSE(IsPrerolling(true)); +} + +TEST_F(MediaSourcePlayerTest, PrerollContinuesAcrossConfigChange) { + SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); + + // Test decoder job will resume media prerolling if interrupted by + // |kConfigChanged| and OnDemuxerConfigsAvailable(). StartAudioDecoderJob(); EXPECT_TRUE(GetMediaDecoderJob(true)); EXPECT_EQ(1, demuxer_->num_data_requests()); - // Initiate a seek to 100 ms. This will abort the intial prefetch. - player_.SeekTo(base::TimeDelta::FromMilliseconds(100)); + SeekPlayer(true, base::TimeDelta::FromMilliseconds(100)); + EXPECT_TRUE(IsPrerolling(true)); + EXPECT_EQ(100.0, GetPrerollTimestamp().InMillisecondsF()); + + // In response to data request, simulate that demuxer signals config change by + // sending an AU with |kConfigChanged|. Player should prepare to reconfigure + // the audio decoder job, and should request new demuxer configs. DemuxerData data; data.type = DemuxerStream::AUDIO; data.access_units.resize(1); - data.access_units[0].status = DemuxerStream::kAborted; + data.access_units[0].status = DemuxerStream::kConfigChanged; + EXPECT_EQ(0, demuxer_->num_config_requests()); player_.OnDemuxerDataAvailable(data); - EXPECT_EQ(1, demuxer_->num_seek_requests()); - player_.OnDemuxerSeekDone(); - EXPECT_EQ(2, demuxer_->num_data_requests()); + EXPECT_EQ(1, demuxer_->num_config_requests()); + + // Simulate arrival of new configs. + DemuxerConfigs configs; + configs.audio_codec = kCodecVorbis; + configs.audio_channels = 2; + configs.audio_sampling_rate = 44100; + configs.is_audio_encrypted = false; + configs.duration_ms = kDefaultDurationInMs; + scoped_refptr<DecoderBuffer> buffer = ReadTestDataFile("vorbis-extradata"); + configs.audio_extra_data = std::vector<uint8>( + buffer->data(), + buffer->data() + buffer->data_size()); + player_.OnDemuxerConfigsAvailable(configs); - // Send some data with before the seek position. + // Send some data before the seek position. for (int i = 1; i < 4; ++i) { player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForAudio(i)); EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding()); message_loop_.Run(); } EXPECT_EQ(100.0, player_.GetCurrentTime().InMillisecondsF()); + EXPECT_TRUE(IsPrerolling(true)); // Send data after the seek position. data = CreateReadFromDemuxerAckForAudio(3); @@ -697,6 +914,7 @@ TEST_F(MediaSourcePlayerTest, PrerollAfterSeek) { player_.OnDemuxerDataAvailable(data); message_loop_.Run(); EXPECT_LT(100.0, player_.GetCurrentTime().InMillisecondsF()); + EXPECT_FALSE(IsPrerolling(true)); } // TODO(xhwang): Enable this test when the test devices are updated. |