// 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& 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& 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), error_cb_(error_cb), state_(kStopped), is_prepared_(false), eos_enqueued_(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; 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; } base::android::ScopedJavaLocalRef MediaCodecDecoder::GetMediaCrypto() { base::android::ScopedJavaLocalRef media_crypto; // TODO(timav): implement DRM. // drm_bridge_ is not implemented // if (drm_bridge_) // media_crypto = drm_bridge_->GetMediaCrypto(); return media_crypto; } 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() { 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(); #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()); // http://crbug.com/526755 DVLOG(0) << class_name() << "::" << __FUNCTION__ << " eos_encountered:" << eos_encountered; decoder_thread_.Stop(); // synchronous SetState(kStopped); completed_ = (eos_encountered && !drain_decoder_); // 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()) { // http://crbug.com/526755 DVLOG(0) << class_name() << "::" << __FUNCTION__ << ": completed, calling preroll_done_cb_"; 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()); // http://crbug.com/526755 DVLOG(0) << 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; } // http://crbug.com/526755 void MediaCodecDecoder::SetVerboseForTests(bool value) { // UI task runner. verbose_ = value; } 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); } 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) { if (verbose_) { DVLOG(0) << class_name() << "::" << __FUNCTION__ << " last_frame_when_stopping:" << last_frame_when_stopping << " eos_encountered:" << eos_encountered << ", posting MediaCodecDecoder::OnLastFrameRendered"; } 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__ << ": not running"; return; } if (state == kStopping) { if (NumDelayedRenderTasks() == 0 && !last_frame_posted_) { // http://crbug.com/526755 DVLOG(0) << class_name() << "::" << __FUNCTION__ << ": kStopping, no delayed tasks, posting OnLastFrameRendered"; 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 } // 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); DVLOG(2) << class_name() << "::" << __FUNCTION__ << ": QueueInputBuffer pts:" << unit->timestamp; status = media_codec_bridge_->QueueInputBuffer( index, &unit->data[0], unit->data.size(), unit->timestamp); if (status == MEDIA_CODEC_ERROR) { DVLOG(0) << class_name() << "::" << __FUNCTION__ << ": MEDIA_CODEC_ERROR: QueueInputBuffer failed"; 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. 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. 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