diff options
Diffstat (limited to 'media/base/android')
-rw-r--r-- | media/base/android/demuxer_android.h | 2 | ||||
-rw-r--r-- | media/base/android/media_decoder_job.cc | 163 | ||||
-rw-r--r-- | media/base/android/media_decoder_job.h | 86 | ||||
-rw-r--r-- | media/base/android/media_player_android.h | 2 | ||||
-rw-r--r-- | media/base/android/media_player_bridge.cc | 41 | ||||
-rw-r--r-- | media/base/android/media_player_bridge.h | 14 | ||||
-rw-r--r-- | media/base/android/media_source_player.cc | 107 | ||||
-rw-r--r-- | media/base/android/media_source_player.h | 27 | ||||
-rw-r--r-- | media/base/android/media_source_player_unittest.cc | 136 |
9 files changed, 385 insertions, 193 deletions
diff --git a/media/base/android/demuxer_android.h b/media/base/android/demuxer_android.h index 865dc9d33f..39dc30fe7e 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 535b8d114e..b7eef0cdfb 100644 --- a/media/base/android/media_decoder_job.cc +++ b/media/base/android/media_decoder_job.cc @@ -31,12 +31,15 @@ MediaDecoderJob::MediaDecoderJob( output_eos_encountered_(false), skip_eos_enqueue_(true), prerolling_(true), - weak_this_(this), 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) { + destroy_pending_(false), + is_requesting_demuxer_data_(false), + is_incoming_data_invalid_(false), + weak_factory_(this) { + InitializeReceivedData(); } MediaDecoderJob::~MediaDecoderJob() {} @@ -44,23 +47,38 @@ 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) { @@ -79,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()); @@ -89,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; } @@ -120,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()); @@ -208,16 +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() || - input_eos_encountered_; + // 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) { @@ -225,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), @@ -256,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))))); @@ -265,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__; @@ -363,7 +383,10 @@ void MediaDecoderJob::DecodeInternal( decoder_task_runner_->PostDelayedTask( FROM_HERE, base::Bind(&MediaDecoderJob::ReleaseOutputBuffer, - weak_this_.GetWeakPtr(), buffer_index, size, render_output, + weak_factory_.GetWeakPtr(), + buffer_index, + size, + render_output, base::Bind(callback, status, presentation_timestamp)), time_to_render); return; @@ -388,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()); @@ -411,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: @@ -428,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 286d27cf75..77caa9feec 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_; @@ -170,10 +203,6 @@ class MediaDecoderJob { // 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_; - // Callback used to request more data. base::Closure request_data_cb_; @@ -183,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. @@ -200,6 +237,17 @@ 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. + base::WeakPtrFactory<MediaDecoderJob> weak_factory_; + DISALLOW_IMPLICIT_CONSTRUCTORS(MediaDecoderJob); }; diff --git a/media/base/android/media_player_android.h b/media/base/android/media_player_android.h index c7e17447eb..2e5e3715cb 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 125c6bf999..b1bb3fec3c 100644 --- a/media/base/android/media_player_bridge.cc +++ b/media/base/android/media_player_bridge.cc @@ -34,7 +34,9 @@ MediaPlayerBridge::MediaPlayerBridge( MediaPlayerManager* manager, const RequestMediaResourcesCB& request_media_resources_cb, const ReleaseMediaResourcesCB& release_media_resources_cb) - : MediaPlayerAndroid(player_id, manager, request_media_resources_cb, + : MediaPlayerAndroid(player_id, + manager, + request_media_resources_cb, release_media_resources_cb), prepared_(false), pending_play_(false), @@ -47,10 +49,10 @@ MediaPlayerBridge::MediaPlayerBridge( can_pause_(true), can_seek_forward_(true), can_seek_backward_(true), - weak_this_(this), - listener_(base::MessageLoopProxy::current(), - weak_this_.GetWeakPtr()), - is_surface_in_use_(false) { + is_surface_in_use_(false), + weak_factory_(this) { + listener_.reset(new MediaPlayerListener(base::MessageLoopProxy::current(), + weak_factory_.GetWeakPtr())); } MediaPlayerBridge::~MediaPlayerBridge() { @@ -72,13 +74,17 @@ void MediaPlayerBridge::Initialize() { media::MediaResourceGetter* resource_getter = manager()->GetMediaResourceGetter(); if (url_.SchemeIsFileSystem() || url_.SchemeIs(kBlobScheme)) { - resource_getter->GetPlatformPathFromURL(url_, base::Bind( - &MediaPlayerBridge::ExtractMediaMetadata, weak_this_.GetWeakPtr())); + resource_getter->GetPlatformPathFromURL( + url_, + base::Bind(&MediaPlayerBridge::ExtractMediaMetadata, + weak_factory_.GetWeakPtr())); return; } - resource_getter->GetCookies(url_, first_party_for_cookies_, base::Bind( - &MediaPlayerBridge::OnCookiesRetrieved, weak_this_.GetWeakPtr())); + resource_getter->GetCookies(url_, + first_party_for_cookies_, + base::Bind(&MediaPlayerBridge::OnCookiesRetrieved, + weak_factory_.GetWeakPtr())); } void MediaPlayerBridge::CreateJavaMediaPlayerBridge() { @@ -110,7 +116,7 @@ void MediaPlayerBridge::SetMediaPlayerListener() { jobject j_context = base::android::GetApplicationContext(); DCHECK(j_context); - listener_.CreateMediaPlayerListener(j_context, j_media_player_bridge_.obj()); + listener_->CreateMediaPlayerListener(j_context, j_media_player_bridge_.obj()); } void MediaPlayerBridge::SetDuration(base::TimeDelta duration) { @@ -136,8 +142,9 @@ void MediaPlayerBridge::Prepare() { CreateJavaMediaPlayerBridge(); if (url_.SchemeIsFileSystem() || url_.SchemeIs(kBlobScheme)) { manager()->GetMediaResourceGetter()->GetPlatformPathFromURL( - url_, base::Bind(&MediaPlayerBridge::SetDataSource, - weak_this_.GetWeakPtr())); + url_, + base::Bind(&MediaPlayerBridge::SetDataSource, + weak_factory_.GetWeakPtr())); return; } @@ -201,9 +208,11 @@ void MediaPlayerBridge::OnCookiesRetrieved(const std::string& cookies) { void MediaPlayerBridge::ExtractMediaMetadata(const std::string& url) { manager()->GetMediaResourceGetter()->ExtractMediaMetadata( - url, cookies_, user_agent_, + url, + cookies_, + user_agent_, base::Bind(&MediaPlayerBridge::OnMediaMetadataExtracted, - weak_this_.GetWeakPtr())); + weak_factory_.GetWeakPtr())); } void MediaPlayerBridge::OnMediaMetadataExtracted( @@ -267,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; @@ -310,7 +319,7 @@ void MediaPlayerBridge::Release() { Java_MediaPlayerBridge_release(env, j_media_player_bridge_.obj()); j_media_player_bridge_.Reset(); release_media_resources_cb_.Run(player_id()); - listener_.ReleaseMediaPlayerListenerResources(); + listener_->ReleaseMediaPlayerListenerResources(); } void MediaPlayerBridge::SetVolume(double volume) { diff --git a/media/base/android/media_player_bridge.h b/media/base/android/media_player_bridge.h index 81e4511f89..b0620b659a 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; @@ -105,6 +105,8 @@ class MEDIA_EXPORT MediaPlayerBridge : public MediaPlayerAndroid { virtual base::android::ScopedJavaLocalRef<jobject> GetAllowedOperations(); private: + friend class MediaPlayerListener; + // Set the data source for the media player. void SetDataSource(const std::string& url); @@ -168,16 +170,16 @@ class MEDIA_EXPORT MediaPlayerBridge : public MediaPlayerAndroid { base::RepeatingTimer<MediaPlayerBridge> time_update_timer_; - // Weak pointer passed to |listener_| for callbacks. - base::WeakPtrFactory<MediaPlayerBridge> weak_this_; - // Listener object that listens to all the media player events. - MediaPlayerListener listener_; + scoped_ptr<MediaPlayerListener> listener_; // Whether player is currently using a surface. bool is_surface_in_use_; - friend class MediaPlayerListener; + // Weak pointer passed to |listener_| for callbacks. + // NOTE: Weak pointers must be invalidated before all other member variables. + base::WeakPtrFactory<MediaPlayerBridge> weak_factory_; + DISALLOW_COPY_AND_ASSIGN(MediaPlayerBridge); }; diff --git a/media/base/android/media_source_player.cc b/media/base/android/media_source_player.cc index 584d50b238..e52d0e49fc 100644 --- a/media/base/android/media_source_player.cc +++ b/media/base/android/media_source_player.cc @@ -37,7 +37,9 @@ MediaSourcePlayer::MediaSourcePlayer( const RequestMediaResourcesCB& request_media_resources_cb, const ReleaseMediaResourcesCB& release_media_resources_cb, scoped_ptr<DemuxerAndroid> demuxer) - : MediaPlayerAndroid(player_id, manager, request_media_resources_cb, + : MediaPlayerAndroid(player_id, + manager, + request_media_resources_cb, release_media_resources_cb), demuxer_(demuxer.Pass()), pending_event_(NO_EVENT_PENDING), @@ -59,9 +61,11 @@ MediaSourcePlayer::MediaSourcePlayer( pending_seek_(false), reconfig_audio_decoder_(false), reconfig_video_decoder_(false), - weak_this_(this), drm_bridge_(NULL), - is_waiting_for_key_(false) { + 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()); } @@ -106,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)); @@ -177,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)) { @@ -222,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() @@ -345,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) { @@ -388,7 +406,7 @@ void MediaSourcePlayer::SetDrmBridge(MediaDrmBridge* drm_bridge) { if (drm_bridge_->GetMediaCrypto().is_null()) { drm_bridge_->SetMediaCryptoReadyCB(base::Bind( - &MediaSourcePlayer::OnMediaCryptoReady, weak_this_.GetWeakPtr())); + &MediaSourcePlayer::OnMediaCryptoReady, weak_factory_.GetWeakPtr())); return; } @@ -397,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); @@ -419,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; @@ -450,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) { @@ -476,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; @@ -510,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()); @@ -523,8 +557,10 @@ void MediaSourcePlayer::ProcessPendingEvents() { return; SetPendingEvent(PREFETCH_DONE_EVENT_PENDING); - base::Closure barrier = BarrierClosure(count, base::Bind( - &MediaSourcePlayer::OnPrefetchDone, weak_this_.GetWeakPtr())); + base::Closure barrier = + BarrierClosure(count, + base::Bind(&MediaSourcePlayer::OnPrefetchDone, + weak_factory_.GetWeakPtr())); if (!AudioFinished()) audio_decoder_job_->Prefetch(barrier); @@ -545,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. @@ -646,9 +682,11 @@ void MediaSourcePlayer::DecodeMoreAudio() { DCHECK(!AudioFinished()); if (audio_decoder_job_->Decode( - start_time_ticks_, start_presentation_timestamp_, base::Bind( - &MediaSourcePlayer::MediaDecoderCallback, - weak_this_.GetWeakPtr(), true))) { + start_time_ticks_, + start_presentation_timestamp_, + base::Bind(&MediaSourcePlayer::MediaDecoderCallback, + weak_factory_.GetWeakPtr(), + true))) { TRACE_EVENT_ASYNC_BEGIN0("media", "MediaSourcePlayer::DecodeMoreAudio", audio_decoder_job_.get()); return; @@ -676,9 +714,11 @@ void MediaSourcePlayer::DecodeMoreVideo() { DCHECK(!VideoFinished()); if (video_decoder_job_->Decode( - start_time_ticks_, start_presentation_timestamp_, base::Bind( - &MediaSourcePlayer::MediaDecoderCallback, - weak_this_.GetWeakPtr(), false))) { + start_time_ticks_, + start_presentation_timestamp_, + base::Bind(&MediaSourcePlayer::MediaDecoderCallback, + weak_factory_.GetWeakPtr(), + false))) { TRACE_EVENT_ASYNC_BEGIN0("media", "MediaSourcePlayer::DecodeMoreVideo", video_decoder_job_.get()); return; @@ -747,7 +787,7 @@ bool MediaSourcePlayer::VideoFinished() { void MediaSourcePlayer::ConfigureAudioDecoderJob() { if (!HasAudio()) { - audio_decoder_job_.reset(); + ResetAudioDecoderJob(); return; } @@ -761,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(), @@ -777,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_|. @@ -784,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(); @@ -860,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. @@ -884,9 +936,8 @@ void MediaSourcePlayer::StartStarvationCallback( timeout = std::max(timeout, kMinStarvationTimeout); - decoder_starvation_callback_.Reset( - base::Bind(&MediaSourcePlayer::OnDecoderStarved, - weak_this_.GetWeakPtr())); + decoder_starvation_callback_.Reset(base::Bind( + &MediaSourcePlayer::OnDecoderStarved, weak_factory_.GetWeakPtr())); base::MessageLoop::current()->PostDelayedTask( FROM_HERE, decoder_starvation_callback_.callback(), timeout); } diff --git a/media/base/android/media_source_player.h b/media/base/android/media_source_player.h index b3af1be238..ed0483cd83 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,12 +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. @@ -87,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_|. @@ -102,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(); @@ -133,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 @@ -266,9 +269,6 @@ class MEDIA_EXPORT MediaSourcePlayer : public MediaPlayerAndroid, // Object to calculate the current audio timestamp for A/V sync. scoped_ptr<AudioTimestampHelper> audio_timestamp_helper_; - // Weak pointer passed to media decoder jobs for callbacks. - base::WeakPtrFactory<MediaSourcePlayer> weak_this_; - MediaDrmBridge* drm_bridge_; // No decryption key available to decrypt the encrypted buffer. In this case, @@ -282,7 +282,14 @@ class MEDIA_EXPORT MediaSourcePlayer : public MediaPlayerAndroid, // Whether |surface_| is currently used by the player. bool is_surface_in_use_; - friend class MediaSourcePlayerTest; + // 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_; + DISALLOW_COPY_AND_ASSIGN(MediaSourcePlayer); }; diff --git a/media/base/android/media_source_player_unittest.cc b/media/base/android/media_source_player_unittest.cc index 9cb04b1829..a3e69e5bd3 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()); |