blob: 808aa761a55b551c7260eee8118833e2c03515fb [file] [log] [blame]
// 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