| // Copyright 2015 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "media/base/android/media_codec_decoder.h" |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/callback_helpers.h" |
| #include "base/logging.h" |
| #include "media/base/android/media_codec_bridge.h" |
| |
| namespace media { |
| |
| namespace { |
| |
| // Stop requesting new data in the kPrefetching state when the queue size |
| // reaches this limit. |
| const int kPrefetchLimit = 8; |
| |
| // Request new data in the kRunning state if the queue size is less than this. |
| const int kPlaybackLowLimit = 4; |
| |
| // Posting delay of the next frame processing, in milliseconds |
| const int kNextFrameDelay = 1; |
| |
| // Timeout for dequeuing an input buffer from MediaCodec in milliseconds. |
| const int kInputBufferTimeout = 20; |
| |
| // Timeout for dequeuing an output buffer from MediaCodec in milliseconds. |
| const int kOutputBufferTimeout = 20; |
| } |
| |
| MediaCodecDecoder::MediaCodecDecoder( |
| const scoped_refptr<base::SingleThreadTaskRunner>& media_task_runner, |
| const base::Closure& external_request_data_cb, |
| const base::Closure& starvation_cb, |
| const base::Closure& decoder_drained_cb, |
| const base::Closure& stop_done_cb, |
| const base::Closure& waiting_for_decryption_key_cb, |
| const base::Closure& error_cb, |
| const char* decoder_thread_name) |
| : media_task_runner_(media_task_runner), |
| decoder_thread_(decoder_thread_name), |
| needs_reconfigure_(false), |
| drain_decoder_(false), |
| always_reconfigure_for_tests_(false), |
| external_request_data_cb_(external_request_data_cb), |
| starvation_cb_(starvation_cb), |
| decoder_drained_cb_(decoder_drained_cb), |
| stop_done_cb_(stop_done_cb), |
| waiting_for_decryption_key_cb_(waiting_for_decryption_key_cb), |
| error_cb_(error_cb), |
| state_(kStopped), |
| is_prepared_(false), |
| eos_enqueued_(false), |
| missing_key_reported_(false), |
| completed_(false), |
| last_frame_posted_(false), |
| is_data_request_in_progress_(false), |
| is_incoming_data_invalid_(false), |
| #ifndef NDEBUG |
| verify_next_frame_is_key_(false), |
| #endif |
| weak_factory_(this) { |
| DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| |
| DVLOG(1) << "Decoder::Decoder() " << decoder_thread_name; |
| |
| internal_error_cb_ = |
| base::Bind(&MediaCodecDecoder::OnCodecError, weak_factory_.GetWeakPtr()); |
| internal_preroll_done_cb_ = |
| base::Bind(&MediaCodecDecoder::OnPrerollDone, weak_factory_.GetWeakPtr()); |
| request_data_cb_ = |
| base::Bind(&MediaCodecDecoder::RequestData, weak_factory_.GetWeakPtr()); |
| } |
| |
| MediaCodecDecoder::~MediaCodecDecoder() {} |
| |
| const char* MediaCodecDecoder::class_name() const { |
| return "Decoder"; |
| } |
| |
| void MediaCodecDecoder::Flush() { |
| DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| |
| DVLOG(1) << class_name() << "::" << __FUNCTION__; |
| |
| DCHECK_EQ(GetState(), kStopped); |
| |
| // Flush() is a part of the Seek request. Whenever we request a seek we need |
| // to invalidate the current data request. |
| if (is_data_request_in_progress_) |
| is_incoming_data_invalid_ = true; |
| |
| eos_enqueued_ = false; |
| missing_key_reported_ = false; |
| completed_ = false; |
| drain_decoder_ = false; |
| au_queue_.Flush(); |
| |
| // |is_prepared_| is set on the decoder thread, it shouldn't be running now. |
| DCHECK(!decoder_thread_.IsRunning()); |
| is_prepared_ = false; |
| |
| #ifndef NDEBUG |
| // We check and reset |verify_next_frame_is_key_| on Decoder thread. |
| // We have just DCHECKed that decoder thread is not running. |
| |
| // For video the first frame after flush must be key frame. |
| verify_next_frame_is_key_ = true; |
| #endif |
| |
| if (media_codec_bridge_) { |
| // MediaCodecBridge::Reset() performs MediaCodecBridge.flush() |
| MediaCodecStatus flush_status = media_codec_bridge_->Reset(); |
| if (flush_status != MEDIA_CODEC_OK) { |
| DVLOG(0) << class_name() << "::" << __FUNCTION__ |
| << "MediaCodecBridge::Reset() failed"; |
| media_task_runner_->PostTask(FROM_HERE, internal_error_cb_); |
| } |
| } |
| } |
| |
| void MediaCodecDecoder::ReleaseMediaCodec() { |
| DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| |
| DVLOG(1) << class_name() << "::" << __FUNCTION__; |
| |
| DCHECK(!decoder_thread_.IsRunning()); |
| |
| media_codec_bridge_.reset(); |
| |
| // |is_prepared_| is set on the decoder thread, it shouldn't be running now. |
| is_prepared_ = false; |
| } |
| |
| bool MediaCodecDecoder::IsPrefetchingOrPlaying() const { |
| DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| |
| // Whether decoder needs to be stopped. |
| base::AutoLock lock(state_lock_); |
| switch (state_) { |
| case kPrefetching: |
| case kPrefetched: |
| case kPrerolling: |
| case kPrerolled: |
| case kRunning: |
| return true; |
| case kStopped: |
| case kStopping: |
| case kInEmergencyStop: |
| case kError: |
| return false; |
| } |
| NOTREACHED(); |
| return false; |
| } |
| |
| bool MediaCodecDecoder::IsStopped() const { |
| DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| |
| return GetState() == kStopped; |
| } |
| |
| bool MediaCodecDecoder::IsCompleted() const { |
| DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| |
| return completed_; |
| } |
| |
| bool MediaCodecDecoder::NotCompletedAndNeedsPreroll() const { |
| DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| |
| return HasStream() && !completed_ && |
| (!is_prepared_ || preroll_timestamp_ != base::TimeDelta()); |
| } |
| |
| void MediaCodecDecoder::SetPrerollTimestamp(base::TimeDelta preroll_timestamp) { |
| DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| DVLOG(1) << class_name() << "::" << __FUNCTION__ << ": " << preroll_timestamp; |
| |
| preroll_timestamp_ = preroll_timestamp; |
| } |
| |
| void MediaCodecDecoder::SetNeedsReconfigure() { |
| DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| |
| DVLOG(1) << class_name() << "::" << __FUNCTION__; |
| |
| needs_reconfigure_ = true; |
| } |
| |
| void MediaCodecDecoder::Prefetch(const base::Closure& prefetch_done_cb) { |
| DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| |
| DVLOG(1) << class_name() << "::" << __FUNCTION__; |
| |
| DCHECK(GetState() == kStopped); |
| |
| prefetch_done_cb_ = prefetch_done_cb; |
| |
| SetState(kPrefetching); |
| PrefetchNextChunk(); |
| } |
| |
| MediaCodecDecoder::ConfigStatus MediaCodecDecoder::Configure( |
| jobject media_crypto) { |
| DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| |
| DVLOG(1) << class_name() << "::" << __FUNCTION__; |
| |
| if (GetState() == kError) { |
| DVLOG(0) << class_name() << "::" << __FUNCTION__ << ": wrong state kError"; |
| return kConfigFailure; |
| } |
| |
| if (needs_reconfigure_) { |
| DVLOG(1) << class_name() << "::" << __FUNCTION__ |
| << ": needs reconfigure, deleting MediaCodec"; |
| needs_reconfigure_ = false; |
| ReleaseMediaCodec(); |
| } |
| |
| if (media_codec_bridge_) { |
| DVLOG(1) << class_name() << "::" << __FUNCTION__ |
| << ": reconfiguration is not required, ignoring"; |
| return kConfigOk; |
| } |
| |
| // Read all |kConfigChanged| units preceding the data one. |
| AccessUnitQueue::Info au_info = au_queue_.GetInfo(); |
| while (au_info.configs) { |
| SetDemuxerConfigs(*au_info.configs); |
| au_queue_.Advance(); |
| au_info = au_queue_.GetInfo(); |
| } |
| |
| MediaCodecDecoder::ConfigStatus result = ConfigureInternal(media_crypto); |
| |
| #ifndef NDEBUG |
| // We check and reset |verify_next_frame_is_key_| on Decoder thread. |
| // This DCHECK ensures we won't need to lock this variable. |
| DCHECK(!decoder_thread_.IsRunning()); |
| |
| // For video the first frame after reconfiguration must be key frame. |
| if (result == kConfigOk) |
| verify_next_frame_is_key_ = true; |
| #endif |
| |
| return result; |
| } |
| |
| bool MediaCodecDecoder::Preroll(const base::Closure& preroll_done_cb) { |
| DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| |
| DVLOG(1) << class_name() << "::" << __FUNCTION__ |
| << " preroll_timestamp:" << preroll_timestamp_; |
| |
| DecoderState state = GetState(); |
| if (state != kPrefetched) { |
| DVLOG(0) << class_name() << "::" << __FUNCTION__ << ": wrong state " |
| << AsString(state) << ", ignoring"; |
| return false; |
| } |
| |
| if (!media_codec_bridge_) { |
| DVLOG(0) << class_name() << "::" << __FUNCTION__ |
| << ": not configured, ignoring"; |
| return false; |
| } |
| |
| DCHECK(!decoder_thread_.IsRunning()); |
| |
| preroll_done_cb_ = preroll_done_cb; |
| |
| // We only synchronize video stream. |
| DissociatePTSFromTime(); // associaton will happen after preroll is done. |
| |
| last_frame_posted_ = false; |
| |
| // Start the decoder thread |
| if (!decoder_thread_.Start()) { |
| DVLOG(0) << class_name() << "::" << __FUNCTION__ |
| << ": cannot start decoder thread"; |
| return false; |
| } |
| |
| SetState(kPrerolling); |
| |
| decoder_thread_.task_runner()->PostTask( |
| FROM_HERE, |
| base::Bind(&MediaCodecDecoder::ProcessNextFrame, base::Unretained(this))); |
| |
| return true; |
| } |
| |
| bool MediaCodecDecoder::Start(base::TimeDelta start_timestamp) { |
| DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| |
| DVLOG(1) << class_name() << "::" << __FUNCTION__ |
| << " start_timestamp:" << start_timestamp; |
| |
| DecoderState state = GetState(); |
| |
| if (state != kPrefetched && state != kPrerolled) { |
| DVLOG(0) << class_name() << "::" << __FUNCTION__ << ": wrong state " |
| << AsString(state) << ", ignoring"; |
| return false; |
| } |
| |
| if (!media_codec_bridge_) { |
| DVLOG(0) << class_name() << "::" << __FUNCTION__ |
| << ": not configured, ignoring"; |
| return false; |
| } |
| |
| // We only synchronize video stream. |
| AssociateCurrentTimeWithPTS(start_timestamp); |
| |
| DCHECK(preroll_timestamp_ == base::TimeDelta()); |
| |
| // Start the decoder thread |
| if (!decoder_thread_.IsRunning()) { |
| last_frame_posted_ = false; |
| if (!decoder_thread_.Start()) { |
| DVLOG(1) << class_name() << "::" << __FUNCTION__ |
| << ": cannot start decoder thread"; |
| return false; |
| } |
| } |
| |
| SetState(kRunning); |
| |
| decoder_thread_.task_runner()->PostTask( |
| FROM_HERE, |
| base::Bind(&MediaCodecDecoder::ProcessNextFrame, base::Unretained(this))); |
| |
| return true; |
| } |
| |
| void MediaCodecDecoder::SyncStop() { |
| DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| |
| DVLOG(1) << class_name() << "::" << __FUNCTION__; |
| |
| if (GetState() == kError) { |
| DVLOG(0) << class_name() << "::" << __FUNCTION__ |
| << ": wrong state kError, ignoring"; |
| return; |
| } |
| |
| DoEmergencyStop(); |
| |
| ReleaseDelayedBuffers(); |
| } |
| |
| void MediaCodecDecoder::RequestToStop() { |
| DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| |
| DVLOG(1) << class_name() << "::" << __FUNCTION__; |
| |
| DecoderState state = GetState(); |
| switch (state) { |
| case kError: |
| DVLOG(0) << class_name() << "::" << __FUNCTION__ |
| << ": wrong state kError, ignoring"; |
| break; |
| case kRunning: |
| SetState(kStopping); |
| break; |
| case kPrerolling: |
| case kPrerolled: |
| DCHECK(decoder_thread_.IsRunning()); |
| // Synchronous stop. |
| decoder_thread_.Stop(); |
| SetState(kStopped); |
| media_task_runner_->PostTask(FROM_HERE, stop_done_cb_); |
| break; |
| case kStopping: |
| case kStopped: |
| break; // ignore |
| case kPrefetching: |
| case kPrefetched: |
| // There is nothing to wait for, we can sent notification right away. |
| DCHECK(!decoder_thread_.IsRunning()); |
| SetState(kStopped); |
| media_task_runner_->PostTask(FROM_HERE, stop_done_cb_); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| void MediaCodecDecoder::OnLastFrameRendered(bool eos_encountered) { |
| DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| |
| DVLOG(1) << class_name() << "::" << __FUNCTION__ |
| << " eos_encountered:" << eos_encountered; |
| |
| decoder_thread_.Stop(); // synchronous |
| |
| SetState(kStopped); |
| completed_ = (eos_encountered && !drain_decoder_); |
| |
| missing_key_reported_ = false; |
| |
| // If the stream is completed during preroll we need to report it since |
| // another stream might be running and the player waits for two callbacks. |
| if (completed_ && !preroll_done_cb_.is_null()) { |
| preroll_timestamp_ = base::TimeDelta(); |
| media_task_runner_->PostTask(FROM_HERE, |
| base::ResetAndReturn(&preroll_done_cb_)); |
| } |
| |
| if (eos_encountered && drain_decoder_) { |
| drain_decoder_ = false; |
| eos_enqueued_ = false; |
| ReleaseMediaCodec(); |
| media_task_runner_->PostTask(FROM_HERE, decoder_drained_cb_); |
| } |
| |
| media_task_runner_->PostTask(FROM_HERE, stop_done_cb_); |
| } |
| |
| void MediaCodecDecoder::OnPrerollDone() { |
| DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| |
| DVLOG(1) << class_name() << "::" << __FUNCTION__ |
| << " state:" << AsString(GetState()); |
| |
| preroll_timestamp_ = base::TimeDelta(); |
| |
| // The state might be kStopping (?) |
| if (GetState() == kPrerolling) |
| SetState(kPrerolled); |
| |
| if (!preroll_done_cb_.is_null()) |
| base::ResetAndReturn(&preroll_done_cb_).Run(); |
| } |
| |
| void MediaCodecDecoder::OnDemuxerDataAvailable(const DemuxerData& data) { |
| DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| |
| // If |data| contains an aborted data, the last AU will have kAborted status. |
| bool aborted_data = |
| !data.access_units.empty() && |
| data.access_units.back().status == DemuxerStream::kAborted; |
| |
| #ifndef NDEBUG |
| const char* explain_if_skipped = |
| is_incoming_data_invalid_ ? " skipped as invalid" |
| : (aborted_data ? " skipped as aborted" : ""); |
| |
| for (const auto& unit : data.access_units) |
| DVLOG(2) << class_name() << "::" << __FUNCTION__ << explain_if_skipped |
| << " au: " << unit; |
| for (const auto& configs : data.demuxer_configs) |
| DVLOG(2) << class_name() << "::" << __FUNCTION__ << " configs: " << configs; |
| #endif |
| |
| if (!is_incoming_data_invalid_ && !aborted_data) |
| au_queue_.PushBack(data); |
| |
| is_incoming_data_invalid_ = false; |
| is_data_request_in_progress_ = false; |
| |
| // Do not request data if we got kAborted. There is no point to request the |
| // data after kAborted and before the OnDemuxerSeekDone. |
| if (GetState() == kPrefetching && !aborted_data) |
| PrefetchNextChunk(); |
| } |
| |
| bool MediaCodecDecoder::IsPrerollingForTests() const { |
| // UI task runner. |
| return GetState() == kPrerolling; |
| } |
| |
| void MediaCodecDecoder::SetAlwaysReconfigureForTests() { |
| // UI task runner. |
| always_reconfigure_for_tests_ = true; |
| } |
| |
| void MediaCodecDecoder::SetCodecCreatedCallbackForTests(base::Closure cb) { |
| // UI task runner. |
| codec_created_for_tests_cb_ = cb; |
| } |
| |
| int MediaCodecDecoder::NumDelayedRenderTasks() const { |
| return 0; |
| } |
| |
| void MediaCodecDecoder::DoEmergencyStop() { |
| DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| DVLOG(1) << class_name() << "::" << __FUNCTION__; |
| |
| // After this method returns, decoder thread will not be running. |
| |
| // Set [kInEmergencyStop| state to block already posted ProcessNextFrame(). |
| SetState(kInEmergencyStop); |
| |
| decoder_thread_.Stop(); // synchronous |
| |
| SetState(kStopped); |
| |
| missing_key_reported_ = false; |
| } |
| |
| void MediaCodecDecoder::CheckLastFrame(bool eos_encountered, |
| bool has_delayed_tasks) { |
| DCHECK(decoder_thread_.task_runner()->BelongsToCurrentThread()); |
| |
| bool last_frame_when_stopping = GetState() == kStopping && !has_delayed_tasks; |
| |
| if (last_frame_when_stopping || eos_encountered) { |
| media_task_runner_->PostTask( |
| FROM_HERE, base::Bind(&MediaCodecDecoder::OnLastFrameRendered, |
| weak_factory_.GetWeakPtr(), eos_encountered)); |
| last_frame_posted_ = true; |
| } |
| } |
| |
| void MediaCodecDecoder::OnCodecError() { |
| DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| |
| // Ignore codec errors from the moment surface is changed till the |
| // |media_codec_bridge_| is deleted. |
| if (needs_reconfigure_) { |
| DVLOG(1) << class_name() << "::" << __FUNCTION__ |
| << ": needs reconfigure, ignoring"; |
| return; |
| } |
| |
| SetState(kError); |
| error_cb_.Run(); |
| } |
| |
| void MediaCodecDecoder::RequestData() { |
| DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| |
| // Ensure one data request at a time. |
| if (!is_data_request_in_progress_) { |
| is_data_request_in_progress_ = true; |
| external_request_data_cb_.Run(); |
| } |
| } |
| |
| void MediaCodecDecoder::PrefetchNextChunk() { |
| DCHECK(media_task_runner_->BelongsToCurrentThread()); |
| |
| DVLOG(1) << class_name() << "::" << __FUNCTION__; |
| |
| AccessUnitQueue::Info au_info = au_queue_.GetInfo(); |
| |
| if (eos_enqueued_ || au_info.data_length >= kPrefetchLimit || |
| au_info.has_eos) { |
| // We are done prefetching |
| SetState(kPrefetched); |
| DVLOG(1) << class_name() << "::" << __FUNCTION__ << " posting PrefetchDone"; |
| media_task_runner_->PostTask(FROM_HERE, |
| base::ResetAndReturn(&prefetch_done_cb_)); |
| return; |
| } |
| |
| request_data_cb_.Run(); |
| } |
| |
| void MediaCodecDecoder::ProcessNextFrame() { |
| DCHECK(decoder_thread_.task_runner()->BelongsToCurrentThread()); |
| |
| DVLOG(2) << class_name() << "::" << __FUNCTION__; |
| |
| DecoderState state = GetState(); |
| |
| if (state != kPrerolling && state != kRunning && state != kStopping) { |
| DVLOG(1) << class_name() << "::" << __FUNCTION__ |
| << ": state: " << AsString(state) << " stopping frame processing"; |
| return; |
| } |
| |
| if (state == kStopping) { |
| if (NumDelayedRenderTasks() == 0 && !last_frame_posted_) { |
| media_task_runner_->PostTask( |
| FROM_HERE, base::Bind(&MediaCodecDecoder::OnLastFrameRendered, |
| weak_factory_.GetWeakPtr(), false)); |
| last_frame_posted_ = true; |
| } |
| |
| // We can stop processing, the |au_queue_| and MediaCodec queues can freeze. |
| // We only need to let finish the delayed rendering tasks. |
| DVLOG(1) << class_name() << "::" << __FUNCTION__ << " kStopping, returning"; |
| return; |
| } |
| |
| DCHECK(state == kPrerolling || state == kRunning); |
| |
| if (!EnqueueInputBuffer()) |
| return; |
| |
| if (!DepleteOutputBufferQueue()) |
| return; |
| |
| // We need a small delay if we want to stop this thread by |
| // decoder_thread_.Stop() reliably. |
| // The decoder thread message loop processes all pending |
| // (but not delayed) tasks before it can quit; without a delay |
| // the message loop might be forever processing the pendng tasks. |
| decoder_thread_.task_runner()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&MediaCodecDecoder::ProcessNextFrame, base::Unretained(this)), |
| base::TimeDelta::FromMilliseconds(kNextFrameDelay)); |
| } |
| |
| // Returns false if we should stop decoding process. Right now |
| // it happens if we got MediaCodec error or detected starvation. |
| bool MediaCodecDecoder::EnqueueInputBuffer() { |
| DCHECK(decoder_thread_.task_runner()->BelongsToCurrentThread()); |
| |
| DVLOG(2) << class_name() << "::" << __FUNCTION__; |
| |
| if (eos_enqueued_) { |
| DVLOG(1) << class_name() << "::" << __FUNCTION__ |
| << ": EOS enqueued, returning"; |
| return true; // Nothing to do |
| } |
| |
| if (missing_key_reported_) { |
| DVLOG(1) << class_name() << "::" << __FUNCTION__ |
| << ": NO KEY reported, returning"; |
| return true; // Nothing to do |
| } |
| |
| // Keep the number pending video frames low, ideally maintaining |
| // the same audio and video duration after stop request |
| if (NumDelayedRenderTasks() > 1) { |
| DVLOG(2) << class_name() << "::" << __FUNCTION__ << ": # delayed buffers (" |
| << NumDelayedRenderTasks() << ") exceeds 1, returning"; |
| return true; // Nothing to do |
| } |
| |
| // Get the next frame from the queue. As we go, request more data and |
| // consume |kConfigChanged| units. |
| |
| // |drain_decoder_| can be already set here if we could not dequeue the input |
| // buffer for it right away. |
| |
| AccessUnitQueue::Info au_info; |
| if (!drain_decoder_) { |
| au_info = AdvanceAccessUnitQueue(&drain_decoder_); |
| if (!au_info.length) { |
| // Report starvation and return, Start() will be called again later. |
| DVLOG(1) << class_name() << "::" << __FUNCTION__ |
| << ": starvation detected"; |
| media_task_runner_->PostTask(FROM_HERE, starvation_cb_); |
| return true; |
| } |
| |
| DCHECK(au_info.front_unit); |
| |
| #ifndef NDEBUG |
| if (verify_next_frame_is_key_) { |
| verify_next_frame_is_key_ = false; |
| VerifyUnitIsKeyFrame(au_info.front_unit); |
| } |
| #endif |
| } |
| |
| // Dequeue input buffer |
| |
| base::TimeDelta timeout = |
| base::TimeDelta::FromMilliseconds(kInputBufferTimeout); |
| int index = -1; |
| MediaCodecStatus status = |
| media_codec_bridge_->DequeueInputBuffer(timeout, &index); |
| |
| DVLOG(2) << class_name() << ":: DequeueInputBuffer index:" << index; |
| |
| switch (status) { |
| case MEDIA_CODEC_ERROR: |
| DVLOG(0) << class_name() << "::" << __FUNCTION__ |
| << ": MEDIA_CODEC_ERROR DequeueInputBuffer failed"; |
| media_task_runner_->PostTask(FROM_HERE, internal_error_cb_); |
| return false; |
| |
| case MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER: |
| DVLOG(2) |
| << class_name() << "::" << __FUNCTION__ |
| << ": DequeueInputBuffer returned MediaCodec.INFO_TRY_AGAIN_LATER."; |
| return true; |
| |
| default: |
| break; |
| } |
| |
| // We got the buffer |
| DCHECK_EQ(status, MEDIA_CODEC_OK); |
| DCHECK_GE(index, 0); |
| |
| const AccessUnit* unit = au_info.front_unit; |
| |
| if (drain_decoder_ || unit->is_end_of_stream) { |
| DVLOG(1) << class_name() << "::" << __FUNCTION__ << ": QueueEOS"; |
| media_codec_bridge_->QueueEOS(index); |
| eos_enqueued_ = true; |
| return true; |
| } |
| |
| DCHECK(unit); |
| DCHECK(!unit->data.empty()); |
| |
| if (unit->key_id.empty() || unit->iv.empty()) { |
| DVLOG(2) << class_name() << "::" << __FUNCTION__ |
| << ": QueueInputBuffer pts:" << unit->timestamp; |
| |
| status = media_codec_bridge_->QueueInputBuffer( |
| index, &unit->data[0], unit->data.size(), unit->timestamp); |
| } else { |
| DVLOG(2) << class_name() << "::" << __FUNCTION__ |
| << ": QueueSecureInputBuffer pts:" << unit->timestamp |
| << " key_id size:" << unit->key_id.size() |
| << " iv size:" << unit->iv.size() |
| << " subsamples size:" << unit->subsamples.size(); |
| |
| status = media_codec_bridge_->QueueSecureInputBuffer( |
| index, &unit->data[0], unit->data.size(), |
| reinterpret_cast<const uint8_t*>(&unit->key_id[0]), unit->key_id.size(), |
| reinterpret_cast<const uint8_t*>(&unit->iv[0]), unit->iv.size(), |
| unit->subsamples.empty() ? nullptr : &unit->subsamples[0], |
| unit->subsamples.size(), unit->timestamp); |
| } |
| |
| switch (status) { |
| case MEDIA_CODEC_OK: |
| break; |
| |
| case MEDIA_CODEC_ERROR: |
| DVLOG(0) << class_name() << "::" << __FUNCTION__ |
| << ": MEDIA_CODEC_ERROR: QueueInputBuffer failed"; |
| media_task_runner_->PostTask(FROM_HERE, internal_error_cb_); |
| return false; |
| |
| case MEDIA_CODEC_NO_KEY: |
| DVLOG(1) << class_name() << "::" << __FUNCTION__ |
| << ": MEDIA_CODEC_NO_KEY"; |
| media_task_runner_->PostTask(FROM_HERE, waiting_for_decryption_key_cb_); |
| |
| // In response to the |waiting_for_decryption_key_cb_| the player will |
| // request to stop decoder. We need to keep running to properly perform |
| // the stop, but prevent enqueuing the same frame over and over again so |
| // we won't generate more |waiting_for_decryption_key_cb_|. |
| missing_key_reported_ = true; |
| return true; |
| |
| default: |
| NOTREACHED() << class_name() << "::" << __FUNCTION__ |
| << ": unexpected error code " << status; |
| media_task_runner_->PostTask(FROM_HERE, internal_error_cb_); |
| return false; |
| } |
| |
| // Have successfully queued input buffer, go to next access unit. |
| au_queue_.Advance(); |
| return true; |
| } |
| |
| AccessUnitQueue::Info MediaCodecDecoder::AdvanceAccessUnitQueue( |
| bool* drain_decoder) { |
| DCHECK(decoder_thread_.task_runner()->BelongsToCurrentThread()); |
| DVLOG(2) << class_name() << "::" << __FUNCTION__; |
| |
| // Retrieve access units from the |au_queue_| in a loop until we either get |
| // a non-config front unit or until the queue is empty. |
| |
| DCHECK(drain_decoder != nullptr); |
| |
| AccessUnitQueue::Info au_info; |
| |
| do { |
| // Get current frame |
| au_info = au_queue_.GetInfo(); |
| |
| // Request the data from Demuxer |
| if (au_info.data_length <= kPlaybackLowLimit && !au_info.has_eos) |
| media_task_runner_->PostTask(FROM_HERE, request_data_cb_); |
| |
| if (!au_info.length) |
| break; // Starvation |
| |
| if (au_info.configs) { |
| DVLOG(1) << class_name() << "::" << __FUNCTION__ << ": received configs " |
| << (*au_info.configs); |
| |
| // Compare the new and current configs. |
| if (IsCodecReconfigureNeeded(*au_info.configs)) { |
| DVLOG(1) << class_name() << "::" << __FUNCTION__ |
| << ": reconfiguration and decoder drain required"; |
| *drain_decoder = true; |
| } |
| |
| // Replace the current configs. |
| SetDemuxerConfigs(*au_info.configs); |
| |
| // Move to the next frame |
| au_queue_.Advance(); |
| } |
| } while (au_info.configs); |
| |
| return au_info; |
| } |
| |
| // Returns false if there was MediaCodec error. |
| bool MediaCodecDecoder::DepleteOutputBufferQueue() { |
| DCHECK(decoder_thread_.task_runner()->BelongsToCurrentThread()); |
| |
| DVLOG(2) << class_name() << "::" << __FUNCTION__; |
| |
| int buffer_index = 0; |
| size_t offset = 0; |
| size_t size = 0; |
| base::TimeDelta pts; |
| MediaCodecStatus status; |
| bool eos_encountered = false; |
| |
| RenderMode render_mode; |
| |
| base::TimeDelta timeout = |
| base::TimeDelta::FromMilliseconds(kOutputBufferTimeout); |
| |
| // Extract all output buffers that are available. |
| // Usually there will be only one, but sometimes it is preceeded by |
| // MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED or MEDIA_CODEC_OUTPUT_FORMAT_CHANGED. |
| do { |
| status = media_codec_bridge_->DequeueOutputBuffer( |
| timeout, &buffer_index, &offset, &size, &pts, &eos_encountered, |
| nullptr); |
| |
| // Reset the timeout to 0 for the subsequent DequeueOutputBuffer() calls |
| // to quickly break the loop after we got all currently available buffers. |
| timeout = base::TimeDelta::FromMilliseconds(0); |
| |
| switch (status) { |
| case MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED: |
| // Output buffers are replaced in MediaCodecBridge, nothing to do. |
| DVLOG(2) << class_name() << "::" << __FUNCTION__ |
| << " MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED"; |
| break; |
| |
| case MEDIA_CODEC_OUTPUT_FORMAT_CHANGED: |
| DVLOG(2) << class_name() << "::" << __FUNCTION__ |
| << " MEDIA_CODEC_OUTPUT_FORMAT_CHANGED"; |
| OnOutputFormatChanged(); |
| break; |
| |
| case MEDIA_CODEC_OK: |
| // We got the decoded frame. |
| |
| is_prepared_ = true; |
| |
| if (pts < preroll_timestamp_) |
| render_mode = kRenderSkip; |
| else if (GetState() == kPrerolling) |
| render_mode = kRenderAfterPreroll; |
| else |
| render_mode = kRenderNow; |
| |
| Render(buffer_index, offset, size, render_mode, pts, eos_encountered); |
| |
| if (render_mode == kRenderAfterPreroll) { |
| DVLOG(1) << class_name() << "::" << __FUNCTION__ << " pts " << pts |
| << " >= preroll timestamp " << preroll_timestamp_ |
| << " preroll done, stopping frame processing"; |
| media_task_runner_->PostTask(FROM_HERE, internal_preroll_done_cb_); |
| return false; |
| } |
| break; |
| |
| case MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER: |
| // Nothing to do. |
| DVLOG(2) << class_name() << "::" << __FUNCTION__ |
| << " MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER"; |
| break; |
| |
| case MEDIA_CODEC_ERROR: |
| DVLOG(0) << class_name() << "::" << __FUNCTION__ |
| << ": MEDIA_CODEC_ERROR from DequeueOutputBuffer"; |
| media_task_runner_->PostTask(FROM_HERE, internal_error_cb_); |
| break; |
| |
| default: |
| NOTREACHED(); |
| break; |
| } |
| |
| } while (status != MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER && |
| status != MEDIA_CODEC_ERROR && !eos_encountered); |
| |
| if (eos_encountered) { |
| DVLOG(1) << class_name() << "::" << __FUNCTION__ |
| << " EOS dequeued, stopping frame processing"; |
| return false; |
| } |
| |
| if (status == MEDIA_CODEC_ERROR) { |
| DVLOG(0) << class_name() << "::" << __FUNCTION__ |
| << " MediaCodec error, stopping frame processing"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| MediaCodecDecoder::DecoderState MediaCodecDecoder::GetState() const { |
| base::AutoLock lock(state_lock_); |
| return state_; |
| } |
| |
| void MediaCodecDecoder::SetState(DecoderState state) { |
| DVLOG(1) << class_name() << "::" << __FUNCTION__ << " " << AsString(state); |
| |
| base::AutoLock lock(state_lock_); |
| state_ = state; |
| } |
| |
| #undef RETURN_STRING |
| #define RETURN_STRING(x) \ |
| case x: \ |
| return #x; |
| |
| const char* MediaCodecDecoder::AsString(RenderMode render_mode) { |
| switch (render_mode) { |
| RETURN_STRING(kRenderSkip); |
| RETURN_STRING(kRenderAfterPreroll); |
| RETURN_STRING(kRenderNow); |
| } |
| return nullptr; // crash early |
| } |
| |
| const char* MediaCodecDecoder::AsString(DecoderState state) { |
| switch (state) { |
| RETURN_STRING(kStopped); |
| RETURN_STRING(kPrefetching); |
| RETURN_STRING(kPrefetched); |
| RETURN_STRING(kPrerolling); |
| RETURN_STRING(kPrerolled); |
| RETURN_STRING(kRunning); |
| RETURN_STRING(kStopping); |
| RETURN_STRING(kInEmergencyStop); |
| RETURN_STRING(kError); |
| } |
| return nullptr; // crash early |
| } |
| |
| #undef RETURN_STRING |
| |
| } // namespace media |