diff options
author | qinmin@chromium.org <qinmin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-20 21:45:01 +0000 |
---|---|---|
committer | qinmin@chromium.org <qinmin@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-03-20 21:45:01 +0000 |
commit | fda22cc637871c06f9ff19fe8b63c43e46511bce (patch) | |
tree | b154c297949be223856ea469f29ee5f72b73fbba | |
parent | cecc8346b4c3bc90aa2091452bf96dd142271a05 (diff) | |
download | chromium_src-fda22cc637871c06f9ff19fe8b63c43e46511bce.zip chromium_src-fda22cc637871c06f9ff19fe8b63c43e46511bce.tar.gz chromium_src-fda22cc637871c06f9ff19fe8b63c43e46511bce.tar.bz2 |
Reducing the IPC latency for MSE video decoding
For MSE, we decode the data in the browser process. When all the data is decoded, we started to request data from the render process.
So the decoder is idle during the IPC round trip + data copy time
This may introduce some issues as this delay may impact smooth video playbacks.
This change tries to solve the above problem:
1. Divide data into 2 chunks in the browser process
2. When decoding 1 chunk, check if the chunk is reaching its end.
If so, request data for the current chunk, and start decoding the next chunk.
3. When new data arrives, store them in the next chunk (since the current one might still be being decoded)
This change also:
1. reduced the buffered data size at the browser side
2. fixes crbug.com/306314 if there is pending data requests across release()/start()
3. fixed all the unit tests behaviors. The new approach should be well tested by all the unit tests we have.
BUG=306314
Review URL: https://codereview.chromium.org/196133020
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@258419 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | content/renderer/media/android/media_source_delegate.cc | 4 | ||||
-rw-r--r-- | media/base/android/demuxer_android.h | 2 | ||||
-rw-r--r-- | media/base/android/media_decoder_job.cc | 156 | ||||
-rw-r--r-- | media/base/android/media_decoder_job.h | 77 | ||||
-rw-r--r-- | media/base/android/media_player_android.h | 2 | ||||
-rw-r--r-- | media/base/android/media_player_bridge.cc | 2 | ||||
-rw-r--r-- | media/base/android/media_player_bridge.h | 2 | ||||
-rw-r--r-- | media/base/android/media_source_player.cc | 70 | ||||
-rw-r--r-- | media/base/android/media_source_player.h | 17 | ||||
-rw-r--r-- | media/base/android/media_source_player_unittest.cc | 136 |
10 files changed, 319 insertions, 149 deletions
diff --git a/content/renderer/media/android/media_source_delegate.cc b/content/renderer/media/android/media_source_delegate.cc index 88addce..563240e 100644 --- a/content/renderer/media/android/media_source_delegate.cc +++ b/content/renderer/media/android/media_source_delegate.cc @@ -31,8 +31,8 @@ using blink::WebString; namespace { // The size of the access unit to transfer in an IPC in case of MediaSource. -// 16: approximately 250ms of content in 60 fps movies. -const size_t kAccessUnitSizeForMediaSource = 16; +// 4: approximately 64ms of content in 60 fps movies. +const size_t kAccessUnitSizeForMediaSource = 4; const uint8 kVorbisPadding[] = { 0xff, 0xff, 0xff, 0xff }; diff --git a/media/base/android/demuxer_android.h b/media/base/android/demuxer_android.h index 865dc9d..39dc30f 100644 --- a/media/base/android/demuxer_android.h +++ b/media/base/android/demuxer_android.h @@ -62,7 +62,7 @@ class MEDIA_EXPORT DemuxerAndroidClient { // For regular demuxer seeks, |actual_browser_seek_time| is kNoTimestamp() and // should be ignored by browser player. virtual void OnDemuxerSeekDone( - const base::TimeDelta& actual_browser_seek_time) = 0; + base::TimeDelta actual_browser_seek_time) = 0; // Called whenever the demuxer has detected a duration change. virtual void OnDemuxerDurationChanged(base::TimeDelta duration) = 0; diff --git a/media/base/android/media_decoder_job.cc b/media/base/android/media_decoder_job.cc index 6d01cbf..b7eef0c 100644 --- a/media/base/android/media_decoder_job.cc +++ b/media/base/android/media_decoder_job.cc @@ -32,34 +32,53 @@ MediaDecoderJob::MediaDecoderJob( skip_eos_enqueue_(true), prerolling_(true), request_data_cb_(request_data_cb), - access_unit_index_(0), + current_demuxer_data_index_(0), input_buf_index_(-1), stop_decode_pending_(false), destroy_pending_(false), - weak_factory_(this) {} + is_requesting_demuxer_data_(false), + is_incoming_data_invalid_(false), + weak_factory_(this) { + InitializeReceivedData(); +} MediaDecoderJob::~MediaDecoderJob() {} void MediaDecoderJob::OnDataReceived(const DemuxerData& data) { DVLOG(1) << __FUNCTION__ << ": " << data.access_units.size() << " units"; DCHECK(ui_task_runner_->BelongsToCurrentThread()); - DCHECK(!on_data_received_cb_.is_null()); + DCHECK(NoAccessUnitsRemainingInChunk(false)); TRACE_EVENT_ASYNC_END2( "media", "MediaDecoderJob::RequestData", this, "Data type", data.type == media::DemuxerStream::AUDIO ? "AUDIO" : "VIDEO", "Units read", data.access_units.size()); - base::Closure done_cb = base::ResetAndReturn(&on_data_received_cb_); + if (is_incoming_data_invalid_) { + is_incoming_data_invalid_ = false; + + // If there is a pending callback, need to request the data again to get + // valid data. + if (!on_data_received_cb_.is_null()) + request_data_cb_.Run(); + else + is_requesting_demuxer_data_ = false; + return; + } + + size_t next_demuxer_data_index = inactive_demuxer_data_index(); + received_data_[next_demuxer_data_index] = data; + access_unit_index_[next_demuxer_data_index] = 0; + is_requesting_demuxer_data_ = false; + base::Closure done_cb = base::ResetAndReturn(&on_data_received_cb_); if (stop_decode_pending_) { OnDecodeCompleted(MEDIA_CODEC_STOPPED, kNoTimestamp(), 0); return; } - access_unit_index_ = 0; - received_data_ = data; - done_cb.Run(); + if (!done_cb.is_null()) + done_cb.Run(); } void MediaDecoderJob::Prefetch(const base::Closure& prefetch_cb) { @@ -78,8 +97,8 @@ void MediaDecoderJob::Prefetch(const base::Closure& prefetch_cb) { } bool MediaDecoderJob::Decode( - const base::TimeTicks& start_time_ticks, - const base::TimeDelta& start_presentation_timestamp, + base::TimeTicks start_time_ticks, + base::TimeDelta start_presentation_timestamp, const DecoderCallback& callback) { DCHECK(decode_cb_.is_null()); DCHECK(on_data_received_cb_.is_null()); @@ -88,23 +107,21 @@ bool MediaDecoderJob::Decode( decode_cb_ = callback; if (!HasData()) { - RequestData(base::Bind(&MediaDecoderJob::DecodeNextAccessUnit, + RequestData(base::Bind(&MediaDecoderJob::DecodeCurrentAccessUnit, base::Unretained(this), start_time_ticks, start_presentation_timestamp)); return true; } - if (DemuxerStream::kConfigChanged == - received_data_.access_units[access_unit_index_].status) { + if (DemuxerStream::kConfigChanged == CurrentAccessUnit().status) { // Clear received data because we need to handle a config change. decode_cb_.Reset(); - received_data_ = DemuxerData(); - access_unit_index_ = 0; + ClearData(); return false; } - DecodeNextAccessUnit(start_time_ticks, start_presentation_timestamp); + DecodeCurrentAccessUnit(start_time_ticks, start_presentation_timestamp); return true; } @@ -119,14 +136,10 @@ void MediaDecoderJob::Flush() { // Do nothing, flush when the next Decode() happens. needs_flush_ = true; - received_data_ = DemuxerData(); - input_eos_encountered_ = false; - access_unit_index_ = 0; - on_data_received_cb_.Reset(); + ClearData(); } -void MediaDecoderJob::BeginPrerolling( - const base::TimeDelta& preroll_timestamp) { +void MediaDecoderJob::BeginPrerolling(base::TimeDelta preroll_timestamp) { DVLOG(1) << __FUNCTION__ << "(" << preroll_timestamp.InSecondsF() << ")"; DCHECK(ui_task_runner_->BelongsToCurrentThread()); DCHECK(!is_decoding()); @@ -207,15 +220,12 @@ MediaCodecStatus MediaDecoderJob::QueueInputBuffer(const AccessUnit& unit) { bool MediaDecoderJob::HasData() const { DCHECK(ui_task_runner_->BelongsToCurrentThread()); - // When |input_eos_encountered_| is set, |access_units| must not be empty and - // |access_unit_index_| must be pointing to an EOS unit. We'll reuse this - // unit to flush the decoder until we hit output EOS. - DCHECK(!input_eos_encountered_ || - (received_data_.access_units.size() > 0 && - access_unit_index_ < received_data_.access_units.size())) - << " (access_units.size(): " << received_data_.access_units.size() - << ", access_unit_index_: " << access_unit_index_ << ")"; - return access_unit_index_ < received_data_.access_units.size(); + // When |input_eos_encountered_| is set, |access_unit_index_| and + // |current_demuxer_data_index_| must be pointing to an EOS unit. + // We'll reuse this unit to flush the decoder until we hit output EOS. + DCHECK(!input_eos_encountered_ || !NoAccessUnitsRemainingInChunk(true)); + return !NoAccessUnitsRemainingInChunk(true) || + !NoAccessUnitsRemainingInChunk(false); } void MediaDecoderJob::RequestData(const base::Closure& done_cb) { @@ -223,26 +233,38 @@ void MediaDecoderJob::RequestData(const base::Closure& done_cb) { DCHECK(ui_task_runner_->BelongsToCurrentThread()); DCHECK(on_data_received_cb_.is_null()); DCHECK(!input_eos_encountered_); + DCHECK(NoAccessUnitsRemainingInChunk(false)); TRACE_EVENT_ASYNC_BEGIN0("media", "MediaDecoderJob::RequestData", this); - received_data_ = DemuxerData(); - access_unit_index_ = 0; on_data_received_cb_ = done_cb; + // If we are already expecting new data, just set the callback and do + // nothing. + if (is_requesting_demuxer_data_) + return; + + // The new incoming data will be stored as the next demuxer data chunk, since + // the decoder might still be decoding the current one. + size_t next_demuxer_data_index = inactive_demuxer_data_index(); + received_data_[next_demuxer_data_index] = DemuxerData(); + access_unit_index_[next_demuxer_data_index] = 0; + is_requesting_demuxer_data_ = true; + request_data_cb_.Run(); } -void MediaDecoderJob::DecodeNextAccessUnit( - const base::TimeTicks& start_time_ticks, - const base::TimeDelta& start_presentation_timestamp) { +void MediaDecoderJob::DecodeCurrentAccessUnit( + base::TimeTicks start_time_ticks, + base::TimeDelta start_presentation_timestamp) { DCHECK(ui_task_runner_->BelongsToCurrentThread()); DCHECK(!decode_cb_.is_null()); + RequestCurrentChunkIfEmpty(); + const AccessUnit& access_unit = CurrentAccessUnit(); // If the first access unit is a config change, request the player to dequeue // the input buffer again so that it can request config data. - if (received_data_.access_units[access_unit_index_].status == - DemuxerStream::kConfigChanged) { + if (access_unit.status == DemuxerStream::kConfigChanged) { ui_task_runner_->PostTask(FROM_HERE, base::Bind(&MediaDecoderJob::OnDecodeCompleted, base::Unretained(this), @@ -254,7 +276,7 @@ void MediaDecoderJob::DecodeNextAccessUnit( decoder_task_runner_->PostTask(FROM_HERE, base::Bind( &MediaDecoderJob::DecodeInternal, base::Unretained(this), - received_data_.access_units[access_unit_index_], + access_unit, start_time_ticks, start_presentation_timestamp, needs_flush_, media::BindToCurrentLoop(base::Bind( &MediaDecoderJob::OnDecodeCompleted, base::Unretained(this))))); @@ -263,8 +285,8 @@ void MediaDecoderJob::DecodeNextAccessUnit( void MediaDecoderJob::DecodeInternal( const AccessUnit& unit, - const base::TimeTicks& start_time_ticks, - const base::TimeDelta& start_presentation_timestamp, + base::TimeTicks start_time_ticks, + base::TimeDelta start_presentation_timestamp, bool needs_flush, const MediaDecoderJob::DecoderCallback& callback) { DVLOG(1) << __FUNCTION__; @@ -389,7 +411,7 @@ void MediaDecoderJob::DecodeInternal( } void MediaDecoderJob::OnDecodeCompleted( - MediaCodecStatus status, const base::TimeDelta& presentation_timestamp, + MediaCodecStatus status, base::TimeDelta presentation_timestamp, size_t audio_output_bytes) { DCHECK(ui_task_runner_->BelongsToCurrentThread()); @@ -412,7 +434,7 @@ void MediaDecoderJob::OnDecodeCompleted( case MEDIA_CODEC_OUTPUT_FORMAT_CHANGED: case MEDIA_CODEC_OUTPUT_END_OF_STREAM: if (!input_eos_encountered_) - access_unit_index_++; + access_unit_index_[current_demuxer_data_index_]++; break; case MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER: @@ -429,4 +451,54 @@ void MediaDecoderJob::OnDecodeCompleted( audio_output_bytes); } +const AccessUnit& MediaDecoderJob::CurrentAccessUnit() const { + DCHECK(ui_task_runner_->BelongsToCurrentThread()); + DCHECK(HasData()); + int index = NoAccessUnitsRemainingInChunk(true) ? + inactive_demuxer_data_index() : current_demuxer_data_index_; + return received_data_[index].access_units[access_unit_index_[index]]; +} + +bool MediaDecoderJob::NoAccessUnitsRemainingInChunk( + bool is_active_chunk) const { + DCHECK(ui_task_runner_->BelongsToCurrentThread()); + size_t index = is_active_chunk ? current_demuxer_data_index_ : + inactive_demuxer_data_index(); + return received_data_[index].access_units.size() <= access_unit_index_[index]; +} + +void MediaDecoderJob::ClearData() { + DCHECK(ui_task_runner_->BelongsToCurrentThread()); + current_demuxer_data_index_ = 0; + InitializeReceivedData(); + on_data_received_cb_.Reset(); + if (is_requesting_demuxer_data_) + is_incoming_data_invalid_ = true; + input_eos_encountered_ = false; +} + +void MediaDecoderJob::RequestCurrentChunkIfEmpty() { + DCHECK(ui_task_runner_->BelongsToCurrentThread()); + DCHECK(HasData()); + if (!NoAccessUnitsRemainingInChunk(true)) + return; + + // Requests new data if the the last access unit of the next chunk is not EOS. + current_demuxer_data_index_ = inactive_demuxer_data_index(); + const AccessUnit last_access_unit = + received_data_[current_demuxer_data_index_].access_units.back(); + if (!last_access_unit.end_of_stream && + last_access_unit.status != DemuxerStream::kConfigChanged && + last_access_unit.status != DemuxerStream::kAborted) { + RequestData(base::Closure()); + } +} + +void MediaDecoderJob::InitializeReceivedData() { + for (size_t i = 0; i < 2; ++i) { + received_data_[i] = DemuxerData(); + access_unit_index_[i] = 0; + } +} + } // namespace media diff --git a/media/base/android/media_decoder_job.h b/media/base/android/media_decoder_job.h index 9c5949a..77caa9f 100644 --- a/media/base/android/media_decoder_job.h +++ b/media/base/android/media_decoder_job.h @@ -19,6 +19,9 @@ namespace media { // Class for managing all the decoding tasks. Each decoding task will be posted // onto the same thread. The thread will be stopped once Stop() is called. +// Data is stored in 2 chunks. When new data arrives, it is always stored in +// an inactive chunk. And when the current active chunk becomes empty, a new +// data request will be sent to the renderer. class MediaDecoderJob { public: struct Deleter { @@ -30,7 +33,7 @@ class MediaDecoderJob { // 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&, + typedef base::Callback<void(MediaCodecStatus, base::TimeDelta, size_t)> DecoderCallback; // Callback when a decoder job finishes releasing the output buffer. // Args: audio output bytes, must be 0 for video. @@ -52,8 +55,8 @@ class MediaDecoderJob { // called when the decode operation is complete. // Returns false if a config change is needed. |callback| is ignored // and will not be called. - bool Decode(const base::TimeTicks& start_time_ticks, - const base::TimeDelta& start_presentation_timestamp, + bool Decode(base::TimeTicks start_time_ticks, + base::TimeDelta start_presentation_timestamp, const DecoderCallback& callback); // Called to stop the last Decode() early. @@ -70,12 +73,16 @@ class MediaDecoderJob { void Flush(); // Enter prerolling state. The job must not currently be decoding. - void BeginPrerolling(const base::TimeDelta& preroll_timestamp); + void BeginPrerolling(base::TimeDelta preroll_timestamp); bool prerolling() const { return prerolling_; } bool is_decoding() const { return !decode_cb_.is_null(); } + bool is_requesting_demuxer_data() const { + return is_requesting_demuxer_data_; + } + protected: MediaDecoderJob( const scoped_refptr<base::SingleThreadTaskRunner>& decoder_task_runner, @@ -95,6 +102,8 @@ class MediaDecoderJob { virtual bool ComputeTimeToRender() const = 0; private: + friend class MediaSourcePlayerTest; + // Causes this instance to be deleted on the thread it is bound to. void Release(); @@ -107,10 +116,10 @@ class MediaDecoderJob { // |done_cb| is called when more data is available in |received_data_|. void RequestData(const base::Closure& done_cb); - // Posts a task to start decoding the next access unit in |received_data_|. - void DecodeNextAccessUnit( - const base::TimeTicks& start_time_ticks, - const base::TimeDelta& start_presentation_timestamp); + // Posts a task to start decoding the current access unit in |received_data_|. + void DecodeCurrentAccessUnit( + base::TimeTicks start_time_ticks, + base::TimeDelta start_presentation_timestamp); // Helper function to decoder data on |thread_|. |unit| contains all the data // to be decoded. |start_time_ticks| and |start_presentation_timestamp| @@ -119,8 +128,8 @@ class MediaDecoderJob { // frame should be rendered. If |needs_flush| is true, codec needs to be // flushed at the beginning of this call. void DecodeInternal(const AccessUnit& unit, - const base::TimeTicks& start_time_ticks, - const base::TimeDelta& start_presentation_timestamp, + base::TimeTicks start_time_ticks, + base::TimeDelta start_presentation_timestamp, bool needs_flush, const DecoderCallback& callback); @@ -128,9 +137,33 @@ class MediaDecoderJob { // 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, + base::TimeDelta presentation_timestamp, size_t audio_output_bytes); + // Helper function to get the current access unit that is being decoded. + const AccessUnit& CurrentAccessUnit() const; + + // Check whether a chunk has no remaining access units to decode. If + // |is_active_chunk| is true, this function returns whether decoder has + // consumed all data in |received_data_[current_demuxer_data_index_]|. + // Otherwise, it returns whether decoder has consumed all data in the inactive + // chunk. + bool NoAccessUnitsRemainingInChunk(bool is_active_chunk) const; + + // Clearn all the received data. + void ClearData(); + + // Request new data for the current chunk if it runs out of data. + void RequestCurrentChunkIfEmpty(); + + // Initialize |received_data_| and |access_unit_index_|. + void InitializeReceivedData(); + + // Return the index to |received_data_| that is not currently being decoded. + size_t inactive_demuxer_data_index() const { + return 1 - current_demuxer_data_index_; + } + // The UI message loop where callbacks should be dispatched. scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_; @@ -179,11 +212,19 @@ class MediaDecoderJob { // Callback to run when the current Decode() operation completes. DecoderCallback decode_cb_; - // The current access unit being processed. - size_t access_unit_index_; - // Data received over IPC from last RequestData() operation. - DemuxerData received_data_; + // We keep 2 chunks at the same time to reduce the IPC latency between chunks. + // If data inside the current chunk are all decoded, we will request a new + // chunk from the demuxer and swap the current chunk with the other one. + // New data will always be stored in the other chunk since the current + // one may be still in use. + DemuxerData received_data_[2]; + + // Index to the current data chunk that is being decoded. + size_t current_demuxer_data_index_; + + // Index to the access unit inside each data chunk that is being decoded. + size_t access_unit_index_[2]; // The index of input buffer that can be used by QueueInputBuffer(). // If the index is uninitialized or invalid, it must be -1. @@ -196,6 +237,12 @@ class MediaDecoderJob { // while there is a decode in progress. bool destroy_pending_; + // Indicates whether the decoder is in the middle of requesting new data. + bool is_requesting_demuxer_data_; + + // Indicates whether the incoming data should be ignored. + bool is_incoming_data_invalid_; + // Weak pointer passed to media decoder jobs for callbacks. It is bounded to // the decoder thread. // NOTE: Weak pointers must be invalidated before all other member variables. diff --git a/media/base/android/media_player_android.h b/media/base/android/media_player_android.h index c7e1744..2e5e371 100644 --- a/media/base/android/media_player_android.h +++ b/media/base/android/media_player_android.h @@ -52,7 +52,7 @@ class MEDIA_EXPORT MediaPlayerAndroid { // Seek to a particular position, based on renderer signaling actual seek // with MediaPlayerHostMsg_Seek. If eventual success, OnSeekComplete() will be // called. - virtual void SeekTo(const base::TimeDelta& timestamp) = 0; + virtual void SeekTo(base::TimeDelta timestamp) = 0; // Release the player resources. virtual void Release() = 0; diff --git a/media/base/android/media_player_bridge.cc b/media/base/android/media_player_bridge.cc index 3cf4543..b1bb3fe 100644 --- a/media/base/android/media_player_bridge.cc +++ b/media/base/android/media_player_bridge.cc @@ -276,7 +276,7 @@ int MediaPlayerBridge::GetVideoHeight() { env, j_media_player_bridge_.obj()); } -void MediaPlayerBridge::SeekTo(const base::TimeDelta& timestamp) { +void MediaPlayerBridge::SeekTo(base::TimeDelta timestamp) { // Record the time to seek when OnMediaPrepared() is called. pending_seek_ = timestamp; diff --git a/media/base/android/media_player_bridge.h b/media/base/android/media_player_bridge.h index 1e3d128..b0620b6 100644 --- a/media/base/android/media_player_bridge.h +++ b/media/base/android/media_player_bridge.h @@ -59,7 +59,7 @@ class MEDIA_EXPORT MediaPlayerBridge : public MediaPlayerAndroid { virtual void SetVideoSurface(gfx::ScopedJavaSurface surface) OVERRIDE; virtual void Start() OVERRIDE; virtual void Pause(bool is_media_related_action ALLOW_UNUSED) OVERRIDE; - virtual void SeekTo(const base::TimeDelta& timestamp) OVERRIDE; + virtual void SeekTo(base::TimeDelta timestamp) OVERRIDE; virtual void Release() OVERRIDE; virtual void SetVolume(double volume) OVERRIDE; virtual int GetVideoWidth() OVERRIDE; diff --git a/media/base/android/media_source_player.cc b/media/base/android/media_source_player.cc index 99a619e..e52d0e4 100644 --- a/media/base/android/media_source_player.cc +++ b/media/base/android/media_source_player.cc @@ -63,6 +63,8 @@ MediaSourcePlayer::MediaSourcePlayer( reconfig_video_decoder_(false), drm_bridge_(NULL), is_waiting_for_key_(false), + has_pending_audio_data_request_(false), + has_pending_video_data_request_(false), weak_factory_(this) { demuxer_->Initialize(this); clock_.SetMaxTime(base::TimeDelta()); @@ -108,7 +110,7 @@ void MediaSourcePlayer::SetVideoSurface(gfx::ScopedJavaSurface surface) { } void MediaSourcePlayer::ScheduleSeekEventAndStopDecoding( - const base::TimeDelta& seek_time) { + base::TimeDelta seek_time) { DVLOG(1) << __FUNCTION__ << "(" << seek_time.InSecondsF() << ")"; DCHECK(!IsEventPending(SEEK_EVENT_PENDING)); @@ -179,7 +181,7 @@ int MediaSourcePlayer::GetVideoHeight() { return height_; } -void MediaSourcePlayer::SeekTo(const base::TimeDelta& timestamp) { +void MediaSourcePlayer::SeekTo(base::TimeDelta timestamp) { DVLOG(1) << __FUNCTION__ << "(" << timestamp.InSecondsF() << ")"; if (IsEventPending(SEEK_EVENT_PENDING)) { @@ -224,7 +226,7 @@ void MediaSourcePlayer::Release() { // Clear all the pending events except seeks and config changes. pending_event_ &= (SEEK_EVENT_PENDING | CONFIG_CHANGE_EVENT_PENDING); is_surface_in_use_ = false; - audio_decoder_job_.reset(); + ResetAudioDecoderJob(); ResetVideoDecoderJob(); // Prevent job re-creation attempts in OnDemuxerConfigsAvailable() @@ -347,6 +349,20 @@ void MediaSourcePlayer::OnDemuxerConfigsAvailable( void MediaSourcePlayer::OnDemuxerDataAvailable(const DemuxerData& data) { DVLOG(1) << __FUNCTION__ << "(" << data.type << ")"; DCHECK_LT(0u, data.access_units.size()); + + if (has_pending_audio_data_request_ && data.type == DemuxerStream::AUDIO) { + has_pending_audio_data_request_ = false; + ProcessPendingEvents(); + return; + } + + if (has_pending_video_data_request_ && data.type == DemuxerStream::VIDEO) { + next_video_data_is_iframe_ = false; + has_pending_video_data_request_ = false; + ProcessPendingEvents(); + return; + } + if (data.type == DemuxerStream::AUDIO && audio_decoder_job_) { audio_decoder_job_->OnDataReceived(data); } else if (data.type == DemuxerStream::VIDEO) { @@ -399,7 +415,7 @@ void MediaSourcePlayer::SetDrmBridge(MediaDrmBridge* drm_bridge) { } void MediaSourcePlayer::OnDemuxerSeekDone( - const base::TimeDelta& actual_browser_seek_time) { + base::TimeDelta actual_browser_seek_time) { DVLOG(1) << __FUNCTION__; ClearPendingEvent(SEEK_EVENT_PENDING); @@ -421,14 +437,17 @@ void MediaSourcePlayer::OnDemuxerSeekDone( // player clock to the actual seek target. if (doing_browser_seek_) { DCHECK(actual_browser_seek_time != kNoTimestamp()); + base::TimeDelta seek_time = actual_browser_seek_time; // A browser seek must not jump into the past. Ideally, it seeks to the // requested time, but it might jump into the future. - DCHECK(actual_browser_seek_time >= GetCurrentTime()); + DCHECK(seek_time >= GetCurrentTime()); DVLOG(1) << __FUNCTION__ << " : setting clock to actual browser seek time: " - << actual_browser_seek_time.InSecondsF(); - clock_.SetTime(actual_browser_seek_time, actual_browser_seek_time); + << seek_time.InSecondsF(); + clock_.SetTime(seek_time, seek_time); if (audio_timestamp_helper_) - audio_timestamp_helper_->SetBaseTimestamp(actual_browser_seek_time); + audio_timestamp_helper_->SetBaseTimestamp(seek_time); + } else { + DCHECK(actual_browser_seek_time == kNoTimestamp()); } reached_audio_eos_ = false; @@ -452,7 +471,7 @@ void MediaSourcePlayer::OnDemuxerSeekDone( } void MediaSourcePlayer::UpdateTimestamps( - const base::TimeDelta& presentation_timestamp, size_t audio_output_bytes) { + base::TimeDelta presentation_timestamp, size_t audio_output_bytes) { base::TimeDelta new_max_time = presentation_timestamp; if (audio_output_bytes > 0) { @@ -478,6 +497,11 @@ void MediaSourcePlayer::ProcessPendingEvents() { return; } + if (has_pending_audio_data_request_ || has_pending_video_data_request_) { + DVLOG(1) << __FUNCTION__ << " : has pending data request."; + return; + } + if (IsEventPending(PREFETCH_DONE_EVENT_PENDING)) { DVLOG(1) << __FUNCTION__ << " : PREFETCH_DONE still pending."; return; @@ -512,6 +536,14 @@ void MediaSourcePlayer::ProcessPendingEvents() { if (IsEventPending(PREFETCH_REQUEST_EVENT_PENDING)) { DVLOG(1) << __FUNCTION__ << " : Handling PREFETCH_REQUEST_EVENT."; + // If one of the decoder is not initialized, cancel this event as it will be + // called later when Start() is called again. + if ((HasVideo() && !video_decoder_job_) || + (HasAudio() && !audio_decoder_job_)) { + ClearPendingEvent(PREFETCH_REQUEST_EVENT_PENDING); + return; + } + DCHECK(audio_decoder_job_ || AudioFinished()); DCHECK(video_decoder_job_ || VideoFinished()); @@ -549,7 +581,7 @@ void MediaSourcePlayer::ProcessPendingEvents() { void MediaSourcePlayer::MediaDecoderCallback( bool is_audio, MediaCodecStatus status, - const base::TimeDelta& presentation_timestamp, size_t audio_output_bytes) { + base::TimeDelta presentation_timestamp, size_t audio_output_bytes) { DVLOG(1) << __FUNCTION__ << ": " << is_audio << ", " << status; // TODO(xhwang): Drop IntToString() when http://crbug.com/303899 is fixed. @@ -755,7 +787,7 @@ bool MediaSourcePlayer::VideoFinished() { void MediaSourcePlayer::ConfigureAudioDecoderJob() { if (!HasAudio()) { - audio_decoder_job_.reset(); + ResetAudioDecoderJob(); return; } @@ -769,8 +801,8 @@ void MediaSourcePlayer::ConfigureAudioDecoderJob() { DCHECK(!audio_decoder_job_ || !audio_decoder_job_->is_decoding()); + ResetAudioDecoderJob(); DVLOG(1) << __FUNCTION__ << " : creating new audio decoder job"; - audio_decoder_job_.reset(AudioDecoderJob::Create( audio_codec_, sampling_rate_, num_channels_, &audio_extra_data_[0], audio_extra_data_.size(), media_crypto.obj(), @@ -785,6 +817,10 @@ void MediaSourcePlayer::ConfigureAudioDecoderJob() { } void MediaSourcePlayer::ResetVideoDecoderJob() { + if (video_decoder_job_) { + has_pending_video_data_request_ = + video_decoder_job_->is_requesting_demuxer_data(); + } video_decoder_job_.reset(); // Any eventual video decoder job re-creation will use the current |surface_|. @@ -792,6 +828,14 @@ void MediaSourcePlayer::ResetVideoDecoderJob() { ClearPendingEvent(SURFACE_CHANGE_EVENT_PENDING); } +void MediaSourcePlayer::ResetAudioDecoderJob() { + if (audio_decoder_job_) { + has_pending_audio_data_request_ = + audio_decoder_job_->is_requesting_demuxer_data(); + } + audio_decoder_job_.reset(); +} + void MediaSourcePlayer::ConfigureVideoDecoderJob() { if (!HasVideo() || surface_.IsEmpty()) { ResetVideoDecoderJob(); @@ -868,7 +912,7 @@ void MediaSourcePlayer::OnDecoderStarved() { } void MediaSourcePlayer::StartStarvationCallback( - const base::TimeDelta& presentation_timestamp) { + base::TimeDelta presentation_timestamp) { // 20ms was chosen because it is the typical size of a compressed audio frame. // Anything smaller than this would likely cause unnecessary cycling in and // out of the prefetch state. diff --git a/media/base/android/media_source_player.h b/media/base/android/media_source_player.h index 168126f..ed0483c 100644 --- a/media/base/android/media_source_player.h +++ b/media/base/android/media_source_player.h @@ -50,7 +50,7 @@ class MEDIA_EXPORT MediaSourcePlayer : public MediaPlayerAndroid, virtual void SetVideoSurface(gfx::ScopedJavaSurface surface) OVERRIDE; virtual void Start() OVERRIDE; virtual void Pause(bool is_media_related_action ALLOW_UNUSED) OVERRIDE; - virtual void SeekTo(const base::TimeDelta& timestamp) OVERRIDE; + virtual void SeekTo(base::TimeDelta timestamp) OVERRIDE; virtual void Release() OVERRIDE; virtual void SetVolume(double volume) OVERRIDE; virtual int GetVideoWidth() OVERRIDE; @@ -70,14 +70,14 @@ class MEDIA_EXPORT MediaSourcePlayer : public MediaPlayerAndroid, virtual void OnDemuxerConfigsAvailable(const DemuxerConfigs& params) OVERRIDE; virtual void OnDemuxerDataAvailable(const DemuxerData& params) OVERRIDE; virtual void OnDemuxerSeekDone( - const base::TimeDelta& actual_browser_seek_time) OVERRIDE; + base::TimeDelta actual_browser_seek_time) OVERRIDE; virtual void OnDemuxerDurationChanged(base::TimeDelta duration) OVERRIDE; private: friend class MediaSourcePlayerTest; // Update the current timestamp. - void UpdateTimestamps(const base::TimeDelta& presentation_timestamp, + void UpdateTimestamps(base::TimeDelta presentation_timestamp, size_t audio_output_bytes); // Helper function for starting media playback. @@ -89,7 +89,7 @@ class MEDIA_EXPORT MediaSourcePlayer : public MediaPlayerAndroid, // Called when the decoder finishes its task. void MediaDecoderCallback( bool is_audio, MediaCodecStatus status, - const base::TimeDelta& presentation_timestamp, + base::TimeDelta presentation_timestamp, size_t audio_output_bytes); // Gets MediaCrypto object from |drm_bridge_|. @@ -104,6 +104,7 @@ class MEDIA_EXPORT MediaSourcePlayer : public MediaPlayerAndroid, // Helper method to clear any pending |SURFACE_CHANGE_EVENT_PENDING| // and reset |video_decoder_job_| to null. void ResetVideoDecoderJob(); + void ResetAudioDecoderJob(); // Helper methods to configure the decoder jobs. void ConfigureVideoDecoderJob(); @@ -135,13 +136,13 @@ class MEDIA_EXPORT MediaSourcePlayer : public MediaPlayerAndroid, // |presentation_timestamp| - The presentation timestamp used for starvation // timeout computations. It represents the timestamp of the last piece of // decoded data. - void StartStarvationCallback(const base::TimeDelta& presentation_timestamp); + void StartStarvationCallback(base::TimeDelta presentation_timestamp); // Schedules a seek event in |pending_events_| and calls StopDecode() on all // the MediaDecoderJobs. Sets clock to |seek_time|, and resets // |pending_seek_|. There must not already be a seek event in // |pending_events_|. - void ScheduleSeekEventAndStopDecoding(const base::TimeDelta& seek_time); + void ScheduleSeekEventAndStopDecoding(base::TimeDelta seek_time); // Schedules a browser seek event. We must not currently be processing any // seek. Note that there is possibility that browser seek of renderer demuxer @@ -281,6 +282,10 @@ class MEDIA_EXPORT MediaSourcePlayer : public MediaPlayerAndroid, // Whether |surface_| is currently used by the player. bool is_surface_in_use_; + // Whether there are pending data requests by the decoder. + bool has_pending_audio_data_request_; + bool has_pending_video_data_request_; + // Weak pointer passed to media decoder jobs for callbacks. // NOTE: Weak pointers must be invalidated before all other member variables. base::WeakPtrFactory<MediaSourcePlayer> weak_factory_; diff --git a/media/base/android/media_source_player_unittest.cc b/media/base/android/media_source_player_unittest.cc index 9cb04b1..a3e69e5b 100644 --- a/media/base/android/media_source_player_unittest.cc +++ b/media/base/android/media_source_player_unittest.cc @@ -408,10 +408,12 @@ class MediaSourcePlayerTest : public testing::Test { StartAudioDecoderJob(true); EXPECT_FALSE(GetMediaDecoderJob(true)->is_decoding()); player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForAudio(0)); + EXPECT_EQ(2, demuxer_->num_data_requests()); EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding()); player_.SeekTo(seek_time); EXPECT_EQ(0.0, GetPrerollTimestamp().InMillisecondsF()); EXPECT_EQ(0, demuxer_->num_seek_requests()); + player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForAudio(0)); } // Seek, including simulated receipt of |kAborted| read between SeekTo() and @@ -474,7 +476,7 @@ class MediaSourcePlayerTest : public testing::Test { EXPECT_TRUE(GetMediaDecoderJob(is_audio)->is_decoding()); EXPECT_EQ(target_timestamp, player_.GetCurrentTime()); current_timestamp += 30; - message_loop_.Run(); + WaitForDecodeDone(is_audio, !is_audio); } EXPECT_LE(target_timestamp, player_.GetCurrentTime()); } @@ -500,7 +502,8 @@ class MediaSourcePlayerTest : public testing::Test { // browser seek results once decode completes and surface change processing // begins. void BrowserSeekPlayer(bool trigger_with_release_start) { - int expected_num_data_requests = demuxer_->num_data_requests() + 1; + int expected_num_data_requests = demuxer_->num_data_requests() + + (trigger_with_release_start ? 1 : 2); int expected_num_seek_requests = demuxer_->num_seek_requests(); int expected_num_browser_seek_requests = demuxer_->num_browser_seek_requests(); @@ -512,7 +515,8 @@ class MediaSourcePlayerTest : public testing::Test { if (trigger_with_release_start) { ReleasePlayer(); - // Simulate demuxer's response to the video data request. + // Simulate demuxer's response to the video data request. The data will be + // discarded. player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForVideo()); EXPECT_FALSE(GetMediaDecoderJob(false)); EXPECT_FALSE(player_.IsPlaying()); @@ -520,6 +524,7 @@ class MediaSourcePlayerTest : public testing::Test { CreateNextTextureAndSetVideoSurface(); StartVideoDecoderJob(false); + EXPECT_FALSE(GetMediaDecoderJob(false)); } else { // Simulate demuxer's response to the video data request. player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForVideo()); @@ -537,8 +542,8 @@ class MediaSourcePlayerTest : public testing::Test { // Wait for the decoder job to finish decoding and be reset pending the // browser seek. - while (GetMediaDecoderJob(false)) - message_loop_.RunUntilIdle(); + WaitForVideoDecodeDone(); + player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForVideo()); } // Only one browser seek should have been initiated, and no further data @@ -579,7 +584,7 @@ class MediaSourcePlayerTest : public testing::Test { else player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForVideo()); - message_loop_.Run(); + WaitForDecodeDone(is_audio, !is_audio); // We should have completed the prefetch phase at this point. expected_num_data_requests++; @@ -620,10 +625,11 @@ class MediaSourcePlayerTest : public testing::Test { // assumed to exist for any stream whose decode completion is awaited. void WaitForDecodeDone(bool wait_for_audio, bool wait_for_video) { DCHECK(wait_for_audio || wait_for_video); - while ((wait_for_audio && GetMediaDecoderJob(true) && + GetMediaDecoderJob(true)->HasData() && GetMediaDecoderJob(true)->is_decoding()) || (wait_for_video && GetMediaDecoderJob(false) && + GetMediaDecoderJob(false)->HasData() && GetMediaDecoderJob(false)->is_decoding())) { message_loop_.RunUntilIdle(); } @@ -694,8 +700,7 @@ class MediaSourcePlayerTest : public testing::Test { // media types configured. Since prefetching may be in progress, we cannot // reliably expect Run() to complete until we have sent demuxer data for all // configured media types, above. - for (int i = 0; i < (have_audio ? 1 : 0) + (have_video ? 1 : 0); i++) - message_loop_.Run(); + WaitForDecodeDone(have_audio, have_video); // Simulate seek while decoding EOS or non-EOS for the appropriate // stream(s). @@ -878,20 +883,20 @@ TEST_F(MediaSourcePlayerTest, ChangeMultipleSurfaceWhileDecoding) { // Wait for the decoder job to finish decoding and be reset pending a browser // seek. - while (GetMediaDecoderJob(false)) - message_loop_.RunUntilIdle(); + WaitForVideoDecodeDone(); + player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForVideo()); // Only one browser seek should have been initiated. No further data request // should have been processed on |message_loop_| before surface change event // became pending, above. EXPECT_EQ(1, demuxer_->num_browser_seek_requests()); - EXPECT_EQ(1, demuxer_->num_data_requests()); + EXPECT_EQ(2, demuxer_->num_data_requests()); // Simulate browser seek is done and confirm player requests more data for new // video decoder job. player_.OnDemuxerSeekDone(player_.GetCurrentTime()); EXPECT_TRUE(GetMediaDecoderJob(false)); - EXPECT_EQ(2, demuxer_->num_data_requests()); + EXPECT_EQ(3, demuxer_->num_data_requests()); EXPECT_EQ(1, demuxer_->num_seek_requests()); } @@ -909,7 +914,6 @@ TEST_F(MediaSourcePlayerTest, SetEmptySurfaceAndStarveWhileDecoding) { // While the decoder is decoding, pass an empty surface. gfx::ScopedJavaSurface empty_surface; player_.SetVideoSurface(empty_surface.Pass()); - // Let the player starve. However, it should not issue any new data request in // this case. TriggerPlayerStarvation(); @@ -920,7 +924,8 @@ TEST_F(MediaSourcePlayerTest, SetEmptySurfaceAndStarveWhileDecoding) { // No further seek or data requests should have been received since the // surface is empty. EXPECT_EQ(0, demuxer_->num_browser_seek_requests()); - EXPECT_EQ(1, demuxer_->num_data_requests()); + EXPECT_EQ(2, demuxer_->num_data_requests()); + player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForVideo()); // Playback resumes once a non-empty surface is passed. CreateNextTextureAndSetVideoSurface(); @@ -938,10 +943,13 @@ TEST_F(MediaSourcePlayerTest, ReleaseVideoDecoderResourcesWhileDecoding) { ReleasePlayer(); // The resources will be immediately released since the decoder is idle. EXPECT_EQ(1, manager_.num_resources_released()); + player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForVideo()); // Recreate the video decoder. CreateNextTextureAndSetVideoSurface(); player_.Start(); + EXPECT_EQ(1, demuxer_->num_browser_seek_requests()); + player_.OnDemuxerSeekDone(base::TimeDelta()); EXPECT_EQ(2, manager_.num_resources_requested()); player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForVideo()); ReleasePlayer(); @@ -1018,6 +1026,7 @@ TEST_F(MediaSourcePlayerTest, StartImmediatelyAfterPause) { // Sending data to player. player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForAudio(0)); EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding()); + EXPECT_EQ(2, demuxer_->num_data_requests()); // Decoder job will not immediately stop after Pause() since it is // running on another thread. @@ -1028,12 +1037,12 @@ TEST_F(MediaSourcePlayerTest, StartImmediatelyAfterPause) { player_.Start(); // Verify that Start() will not destroy and recreate the decoder job. EXPECT_EQ(decoder_job, GetMediaDecoderJob(true)); - EXPECT_EQ(1, demuxer_->num_data_requests()); - EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding()); - message_loop_.Run(); - // The decoder job should finish and a new request will be sent. + + while (GetMediaDecoderJob(true)->is_decoding()) + message_loop_.RunUntilIdle(); + // The decoder job should finish and wait for data. EXPECT_EQ(2, demuxer_->num_data_requests()); - EXPECT_FALSE(GetMediaDecoderJob(true)->is_decoding()); + EXPECT_TRUE(GetMediaDecoderJob(true)->is_requesting_demuxer_data()); } TEST_F(MediaSourcePlayerTest, DecoderJobsCannotStartWithoutAudio) { @@ -1074,7 +1083,8 @@ TEST_F(MediaSourcePlayerTest, StartTimeTicksResetAfterDecoderUnderruns) { for (int i = 0; i < 4; ++i) { player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForAudio(i)); EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding()); - message_loop_.Run(); + // Decode data until decoder started requesting new data again. + WaitForAudioDecodeDone(); } // The decoder job should finish and a new request will be sent. @@ -1082,15 +1092,8 @@ TEST_F(MediaSourcePlayerTest, StartTimeTicksResetAfterDecoderUnderruns) { EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding()); base::TimeTicks previous = StartTimeTicks(); - // Let the decoder timeout and execute the OnDecoderStarved() callback. - base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); - - EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding()); - EXPECT_TRUE(StartTimeTicks() != base::TimeTicks()); - message_loop_.RunUntilIdle(); - - // Send new data to the decoder so it can finish the currently - // pending decode. + // Let the decoder starve. + TriggerPlayerStarvation(); player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForAudio(3)); WaitForAudioDecodeDone(); @@ -1104,7 +1107,7 @@ TEST_F(MediaSourcePlayerTest, StartTimeTicksResetAfterDecoderUnderruns) { EXPECT_TRUE(StartTimeTicks() != base::TimeTicks()); base::TimeTicks current = StartTimeTicks(); - EXPECT_LE(100.0, (current - previous).InMillisecondsF()); + EXPECT_LE(0, (current - previous).InMillisecondsF()); } TEST_F(MediaSourcePlayerTest, V_SecondAccessUnitIsEOSAndResumePlayAfterSeek) { @@ -1116,7 +1119,7 @@ TEST_F(MediaSourcePlayerTest, V_SecondAccessUnitIsEOSAndResumePlayAfterSeek) { // Send the first input chunk. player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForVideo()); - message_loop_.Run(); + WaitForVideoDecodeDone(); VerifyPlaybackCompletesOnEOSDecode(true, false); VerifyCompletedPlaybackResumesOnSeekPlusStart(false, true); @@ -1230,9 +1233,7 @@ TEST_F(MediaSourcePlayerTest, AV_NoPrefetchForFinishedVideoOnAudioStarvation) { // Wait until video EOS is processed and more data (assumed to be audio) is // requested. - while (demuxer_->num_data_requests() < 3) - message_loop_.RunUntilIdle(); - WaitForVideoDecodeDone(); + WaitForAudioVideoDecodeDone(); EXPECT_EQ(3, demuxer_->num_data_requests()); // Simulate decoder underrun to trigger prefetch while still decoding audio. @@ -1245,7 +1246,6 @@ TEST_F(MediaSourcePlayerTest, AV_NoPrefetchForFinishedVideoOnAudioStarvation) { // starvation was triggered. WaitForAudioDecodeDone(); EXPECT_EQ(4, demuxer_->num_data_requests()); - player_.OnDemuxerDataAvailable(CreateEOSAck(true)); // Audio EOS EXPECT_FALSE(GetMediaDecoderJob(false)->is_decoding()); EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding()); @@ -1263,7 +1263,7 @@ TEST_F(MediaSourcePlayerTest, V_StarvationDuringEOSDecode) { CreateNextTextureAndSetVideoSurface(); StartVideoDecoderJob(true); player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForVideo()); - message_loop_.Run(); + WaitForVideoDecodeDone(); // Simulate decoder underrun to trigger prefetch while decoding EOS. player_.OnDemuxerDataAvailable(CreateEOSAck(false)); // Video EOS @@ -1279,7 +1279,7 @@ TEST_F(MediaSourcePlayerTest, A_StarvationDuringEOSDecode) { // starvation occurs during EOS decode. StartAudioDecoderJob(true); player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForAudio(0)); - message_loop_.Run(); + WaitForAudioDecodeDone(); // Simulate decoder underrun to trigger prefetch while decoding EOS. player_.OnDemuxerDataAvailable(CreateEOSAck(true)); // Audio EOS @@ -1389,21 +1389,20 @@ TEST_F(MediaSourcePlayerTest, BrowserSeek_RegularSeekPendsBrowserSeekDone) { EXPECT_FALSE(GetMediaDecoderJob(false)); EXPECT_EQ(2, demuxer_->num_seek_requests()); EXPECT_EQ(1, demuxer_->num_browser_seek_requests()); - EXPECT_EQ(1, demuxer_->num_data_requests()); // Simulate regular seek is done and confirm player requests more data for // new video decoder job. player_.OnDemuxerSeekDone(kNoTimestamp()); EXPECT_TRUE(GetMediaDecoderJob(false)); - EXPECT_EQ(2, demuxer_->num_data_requests()); + EXPECT_EQ(3, demuxer_->num_data_requests()); EXPECT_EQ(2, demuxer_->num_seek_requests()); } -TEST_F(MediaSourcePlayerTest, NoSeekForInitialReleaseAndStart) { +TEST_F(MediaSourcePlayerTest, BrowserSeek_InitialReleaseAndStart) { SKIP_TEST_IF_MEDIA_CODEC_BRIDGE_IS_NOT_AVAILABLE(); - // Test that no seek is requested if player Release() + Start() occurs prior - // to receiving any data. + // Test that browser seek is requested if player Release() + Start() occurs + // prior to receiving any data. CreateNextTextureAndSetVideoSurface(); StartVideoDecoderJob(true); ReleasePlayer(); @@ -1413,12 +1412,15 @@ TEST_F(MediaSourcePlayerTest, NoSeekForInitialReleaseAndStart) { player_.Start(); - // TODO(wolenetz/qinmin): Multiple in-flight data requests for same stream - // should be prevented. See http://crbug.com/306314. - EXPECT_EQ(2, demuxer_->num_data_requests()); - EXPECT_TRUE(GetMediaDecoderJob(false)); + // The new player won't be created until the pending data request is + // processed. + EXPECT_EQ(1, demuxer_->num_data_requests()); + EXPECT_FALSE(GetMediaDecoderJob(false)); - EXPECT_EQ(0, demuxer_->num_seek_requests()); + // A browser seek should be requested. + player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForVideo()); + EXPECT_EQ(1, demuxer_->num_browser_seek_requests()); + EXPECT_EQ(1, demuxer_->num_data_requests()); } TEST_F(MediaSourcePlayerTest, BrowserSeek_MidStreamReleaseAndStart) { @@ -1427,7 +1429,6 @@ TEST_F(MediaSourcePlayerTest, BrowserSeek_MidStreamReleaseAndStart) { // Test that one browser seek is requested if player Release() + Start(), with // video data received between Release() and Start(). BrowserSeekPlayer(true); - EXPECT_EQ(1, demuxer_->num_data_requests()); // Simulate browser seek is done and confirm player requests more data. player_.OnDemuxerSeekDone(base::TimeDelta()); @@ -1474,7 +1475,7 @@ TEST_F(MediaSourcePlayerTest, SeekingAfterCompletingPrerollRestartsPreroll) { for (int i = 0; i < 4; ++i) { player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForAudio(i)); EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding()); - message_loop_.Run(); + WaitForAudioDecodeDone(); } EXPECT_LT(0.0, player_.GetCurrentTime().InMillisecondsF()); EXPECT_FALSE(IsPrerolling(true)); @@ -1492,7 +1493,7 @@ TEST_F(MediaSourcePlayerTest, SeekingAfterCompletingPrerollRestartsPreroll) { 500 + 30 * (i - 1)); player_.OnDemuxerDataAvailable(data); EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding()); - message_loop_.Run(); + WaitForAudioDecodeDone(); } EXPECT_LT(500.0, player_.GetCurrentTime().InMillisecondsF()); EXPECT_FALSE(IsPrerolling(true)); @@ -1536,13 +1537,13 @@ TEST_F(MediaSourcePlayerTest, PrerollContinuesAcrossReleaseAndStart) { // verification and prevents multiple in-flight data requests. ReleasePlayer(); player_.OnDemuxerDataAvailable(data); - message_loop_.RunUntilIdle(); + WaitForAudioDecodeDone(); EXPECT_FALSE(GetMediaDecoderJob(true)); StartAudioDecoderJob(true); } else { player_.OnDemuxerDataAvailable(data); EXPECT_TRUE(GetMediaDecoderJob(true)->is_decoding()); - message_loop_.Run(); + WaitForAudioDecodeDone(); } EXPECT_TRUE(IsPrerolling(true)); } @@ -1670,7 +1671,7 @@ TEST_F(MediaSourcePlayerTest, BrowserSeek_PrerollAfterBrowserSeek) { EXPECT_TRUE(GetMediaDecoderJob(false)); EXPECT_EQ(100.0, player_.GetCurrentTime().InMillisecondsF()); EXPECT_EQ(100.0, GetPrerollTimestamp().InMillisecondsF()); - EXPECT_EQ(2, demuxer_->num_data_requests()); + EXPECT_EQ(3, demuxer_->num_data_requests()); PrerollDecoderToTime( false, base::TimeDelta(), base::TimeDelta::FromMilliseconds(100)); @@ -1781,14 +1782,16 @@ TEST_F(MediaSourcePlayerTest, CreateNextTextureAndSetVideoSurface(); TriggerPlayerStarvation(); WaitForVideoDecodeDone(); + EXPECT_EQ(0, demuxer_->num_browser_seek_requests()); // Surface change should trigger a seek. + player_.OnDemuxerDataAvailable(data); EXPECT_EQ(1, demuxer_->num_browser_seek_requests()); player_.OnDemuxerSeekDone(base::TimeDelta()); EXPECT_TRUE(GetMediaDecoderJob(false)); // A new data request should be sent. - EXPECT_EQ(2, demuxer_->num_data_requests()); + EXPECT_EQ(3, demuxer_->num_data_requests()); } TEST_F(MediaSourcePlayerTest, ReleaseWithOnPrefetchDoneAlreadyPosted) { @@ -1804,7 +1807,7 @@ TEST_F(MediaSourcePlayerTest, ReleaseWithOnPrefetchDoneAlreadyPosted) { // Escape the original prefetch by decoding a single access unit. player_.OnDemuxerDataAvailable(CreateReadFromDemuxerAckForAudio(0)); - message_loop_.Run(); + WaitForAudioDecodeDone(); // Prime the job with a few more access units, so that a later prefetch, // triggered by starvation to simulate decoder underrun, can trivially @@ -1823,8 +1826,7 @@ TEST_F(MediaSourcePlayerTest, ReleaseWithOnPrefetchDoneAlreadyPosted) { // occurs and should execute after the Release(). OnNextTestDecodeCallbackPostTaskToReleasePlayer(); - while (GetMediaDecoderJob(true)) - message_loop_.RunUntilIdle(); + WaitForAudioDecodeDone(); EXPECT_TRUE(decoder_callback_hook_executed_); EXPECT_EQ(2, demuxer_->num_data_requests()); @@ -1850,7 +1852,7 @@ TEST_F(MediaSourcePlayerTest, SeekToThenReleaseThenDemuxerSeekAndDone) { EXPECT_FALSE(player_.IsPlaying()); // Player should begin prefetch and resume preroll upon Start(). - EXPECT_EQ(1, demuxer_->num_data_requests()); + EXPECT_EQ(2, demuxer_->num_data_requests()); StartAudioDecoderJob(true); EXPECT_TRUE(IsPrerolling(true)); EXPECT_EQ(100.0, GetPrerollTimestamp().InMillisecondsF()); @@ -1874,14 +1876,14 @@ TEST_F(MediaSourcePlayerTest, SeekToThenReleaseThenDemuxerSeekThenStart) { // Player should not prefetch upon Start() nor create the decoder job, due to // awaiting DemuxerSeekDone. - EXPECT_EQ(1, demuxer_->num_data_requests()); + EXPECT_EQ(2, demuxer_->num_data_requests()); StartAudioDecoderJob(false); player_.OnDemuxerSeekDone(kNoTimestamp()); EXPECT_TRUE(GetMediaDecoderJob(true)); EXPECT_TRUE(IsPrerolling(true)); EXPECT_EQ(100.0, GetPrerollTimestamp().InMillisecondsF()); - EXPECT_EQ(2, demuxer_->num_data_requests()); + EXPECT_EQ(3, demuxer_->num_data_requests()); // No further seek should have been requested since Release(), above. EXPECT_EQ(1, demuxer_->num_seek_requests()); @@ -1905,7 +1907,7 @@ TEST_F(MediaSourcePlayerTest, SeekToThenDemuxerSeekThenReleaseThenSeekDone) { EXPECT_EQ(100.0, GetPrerollTimestamp().InMillisecondsF()); // Player should begin prefetch and resume preroll upon Start(). - EXPECT_EQ(1, demuxer_->num_data_requests()); + EXPECT_EQ(2, demuxer_->num_data_requests()); StartAudioDecoderJob(true); EXPECT_TRUE(IsPrerolling(true)); EXPECT_EQ(100.0, GetPrerollTimestamp().InMillisecondsF()); @@ -1927,14 +1929,14 @@ TEST_F(MediaSourcePlayerTest, SeekToThenReleaseThenStart) { EXPECT_EQ(1, demuxer_->num_seek_requests()); ReleasePlayer(); - EXPECT_EQ(1, demuxer_->num_data_requests()); + EXPECT_EQ(2, demuxer_->num_data_requests()); StartAudioDecoderJob(false); player_.OnDemuxerSeekDone(kNoTimestamp()); EXPECT_TRUE(GetMediaDecoderJob(true)); EXPECT_TRUE(IsPrerolling(true)); EXPECT_EQ(100.0, GetPrerollTimestamp().InMillisecondsF()); - EXPECT_EQ(2, demuxer_->num_data_requests()); + EXPECT_EQ(3, demuxer_->num_data_requests()); // No further seek should have been requested since before Release(), above. EXPECT_EQ(1, demuxer_->num_seek_requests()); @@ -2004,7 +2006,7 @@ TEST_F(MediaSourcePlayerTest, BrowserSeek_ThenReleaseThenDemuxerSeekDone) { EXPECT_EQ(expected_preroll_timestamp, GetPrerollTimestamp()); // Player should begin prefetch and resume preroll upon Start(). - EXPECT_EQ(1, demuxer_->num_data_requests()); + EXPECT_EQ(2, demuxer_->num_data_requests()); CreateNextTextureAndSetVideoSurface(); StartVideoDecoderJob(true); EXPECT_TRUE(IsPrerolling(false)); @@ -2027,7 +2029,7 @@ TEST_F(MediaSourcePlayerTest, BrowserSeek_ThenReleaseThenStart) { base::TimeDelta expected_preroll_timestamp = player_.GetCurrentTime(); ReleasePlayer(); - EXPECT_EQ(1, demuxer_->num_data_requests()); + EXPECT_EQ(2, demuxer_->num_data_requests()); CreateNextTextureAndSetVideoSurface(); StartVideoDecoderJob(false); @@ -2036,7 +2038,7 @@ TEST_F(MediaSourcePlayerTest, BrowserSeek_ThenReleaseThenStart) { EXPECT_TRUE(IsPrerolling(false)); EXPECT_EQ(expected_preroll_timestamp, GetPrerollTimestamp()); EXPECT_EQ(expected_preroll_timestamp, player_.GetCurrentTime()); - EXPECT_EQ(2, demuxer_->num_data_requests()); + EXPECT_EQ(3, demuxer_->num_data_requests()); // No further seek should have been requested since BrowserSeekPlayer(). EXPECT_EQ(1, demuxer_->num_seek_requests()); |