Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(14)

Unified Diff: media/filters/android_audio_decoder.cc

Issue 1651673002: Add MediaCodecAudioDecoder implementation (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: media/filters/android_audio_decoder.cc
diff --git a/media/filters/android_audio_decoder.cc b/media/filters/android_audio_decoder.cc
new file mode 100644
index 0000000000000000000000000000000000000000..6c46aef11e68da6154167c641818acc270948d2e
--- /dev/null
+++ b/media/filters/android_audio_decoder.cc
@@ -0,0 +1,555 @@
+// Copyright 2016 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/filters/android_audio_decoder.h"
+
+#include "base/android/build_info.h"
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/logging.h"
+#include "base/thread_task_runner_handle.h"
+
+#include "media/base/android/sdk_media_codec_bridge.h"
+#include "media/base/audio_buffer.h"
+#include "media/base/bind_to_current_loop.h"
+
+namespace media {
+
+// Android MediaCodec can only output 16bit PCM audio.
+const int kBytesPerOutputSample = 2;
+
+inline const base::TimeDelta DecodePollDelay() {
+ return base::TimeDelta::FromMilliseconds(10);
+}
+
+inline const base::TimeDelta NoWaitTimeOut() {
+ return base::TimeDelta::FromMicroseconds(0);
+}
+
+inline const base::TimeDelta IdleTimerTimeOut() {
+ return base::TimeDelta::FromSeconds(1);
+}
+
+AndroidAudioDecoder::AndroidAudioDecoder()
+ : task_runner_(base::ThreadTaskRunnerHandle::Get()),
+ state_(kStateUninitialized),
+ channel_count_(0),
+ bytes_per_frame_(0),
+ cdm_registration_id_(0),
+ pending_input_buf_index_(-1),
+ weak_factory_(this) {
+ DVLOG(1) << __FUNCTION__;
+}
+
+AndroidAudioDecoder::~AndroidAudioDecoder() {
+ DVLOG(1) << __FUNCTION__;
+
+ if (!set_cdm_ready_cb_.is_null())
+ base::ResetAndReturn(&set_cdm_ready_cb_).Run(CdmReadyCB());
+ if (!cdm_attached_cb_.is_null())
+ base::ResetAndReturn(&cdm_attached_cb_).Run(false);
+ if (!init_cb_.is_null())
+ base::ResetAndReturn(&init_cb_).Run(false);
+
+ media_codec_.reset();
+
+ // Report kAborted status for pending EOS and all frames in the input queue.
+ if (!eos_decode_cb_.is_null())
+ base::ResetAndReturn(&eos_decode_cb_).Run(kAborted);
+
+ for (const auto& entry : input_queue_) {
+ entry.second.Run(kAborted);
+ }
+}
+
+std::string AndroidAudioDecoder::GetDisplayName() const {
+ return "AndroidAudioDecoder";
+}
+
+void AndroidAudioDecoder::Initialize(const AudioDecoderConfig& config,
+ const SetCdmReadyCB& set_cdm_ready_cb,
+ const InitCB& init_cb,
+ const OutputCB& output_cb) {
+ DVLOG(1) << __FUNCTION__ << ": " << config.AsHumanReadableString();
+
+ InitCB bound_init_cb = BindToCurrentLoop(init_cb);
+
+ // Keep this consistent with AudioCodecBridge.
+ const bool is_codec_supported = config.codec() == kCodecVorbis ||
+ config.codec() == kCodecAAC ||
+ config.codec() == kCodecOpus;
+ if (!is_codec_supported) {
+ DVLOG(1) << "Unsuported codec " << GetCodecName(config.codec());
+ bound_init_cb.Run(false);
+ return;
+ }
+
+ config_ = config;
+ init_cb_ = bound_init_cb;
+ output_cb_ = BindToCurrentLoop(output_cb);
+
+ // The following derived parameters are frequently used.
+ channel_count_ = ChannelLayoutToChannelCount(config_.channel_layout());
+ bytes_per_frame_ = kBytesPerOutputSample * channel_count_;
+
+ if (config.is_encrypted()) {
+ // Delay configuration until we get MediaCrypto object.
+ set_cdm_ready_cb_ = set_cdm_ready_cb;
+ set_cdm_ready_cb_.Run(BindToCurrentLoop(
+ base::Bind(&AndroidAudioDecoder::SetCdm, weak_factory_.GetWeakPtr())));
+ SetState(kStateWaitingForCDM);
+ return;
+ }
+
+ const bool success = ConfigureMediaCodec();
+
+ SetState(success ? kStateReady : kStateUninitialized);
+ base::ResetAndReturn(&init_cb_).Run(success);
+}
+
+void AndroidAudioDecoder::Decode(const scoped_refptr<DecoderBuffer>& buffer,
+ const DecodeCB& decode_cb) {
+ DecodeCB bound_decode_cb = BindToCurrentLoop(decode_cb);
+
+ if (state_ == kStateError) {
+ DVLOG(2) << __FUNCTION__ << " " << buffer->AsShortString()
+ << ": Error state, dropping buffer";
+ bound_decode_cb.Run(kDecodeError);
+ return;
+ }
+
+ DVLOG(2) << __FUNCTION__ << " " << buffer->AsShortString();
+
+ input_queue_.push_back(std::make_pair(buffer, bound_decode_cb));
+
+ DoIOTask();
+}
+
+void AndroidAudioDecoder::Reset(const base::Closure& closure) {
+ DVLOG(1) << __FUNCTION__;
+
+ io_timer_.Stop();
+
+ if (!eos_decode_cb_.is_null())
+ base::ResetAndReturn(&eos_decode_cb_).Run(kAborted);
+
+ // Report kAborted status for all frames in the input queue.
+ for (const auto& entry : input_queue_) {
+ entry.second.Run(kAborted);
+ }
+ input_queue_.clear();
+
+ // Flush if we can, otherwise completely recreate and reconfigure the codec.
+ // Prior to JB-MR2, flush() had several bugs (b/8125974, b/8347958).
+ bool success = false;
+ if (state_ != kStateError && state_ != kStateDrained &&
+ base::android::BuildInfo::GetInstance()->sdk_int() < 18) {
+ // media_codec_->Reset() calls MediaCodec.flush().
+ success = (media_codec_->Reset() == MEDIA_CODEC_OK);
+ }
+
+ if (!success) {
+ media_codec_.reset();
+ success = ConfigureMediaCodec();
+ }
+
+ SetState(success ? kStateReady : kStateError);
+
+ task_runner_->PostTask(FROM_HERE, closure);
+}
+
+void AndroidAudioDecoder::SetCdm(CdmContext* cdm_context,
+ const CdmAttachedCB& cdm_attached_cb) {
+ DVLOG(1) << __FUNCTION__;
+
+ DCHECK(!init_cb_.is_null());
+ DCHECK(!set_cdm_ready_cb_.is_null());
+ set_cdm_ready_cb_.Reset();
+
+ if (!cdm_context || cdm_context->GetCdmId() == CdmContext::kInvalidCdmId) {
+ DVLOG(1) << __FUNCTION__ << ": CDM ID not available.";
+ cdm_attached_cb.Run(false);
+ base::ResetAndReturn(&init_cb_).Run(false);
+ SetState(kStateUninitialized);
+ return;
+ }
+
+ if (cdm_) {
+ NOTREACHED() << "We do not support resetting CDM.";
+ cdm_attached_cb.Run(false);
+ base::ResetAndReturn(&init_cb_).Run(false);
+ SetState(kStateUninitialized);
+ return;
+ }
+
+// TODO(timav): get |cdm_| from cdm_context->GetCdmId().
+
+// #define CDM_IN_ANDROID_AUDIO_DECODE
+#if !defined(CDM_IN_ANDROID_AUDIO_DECODER)
+ NOTIMPLEMENTED();
+ cdm_attached_cb.Run(false);
+ base::ResetAndReturn(&init_cb_).Run(false);
+ SetState(kStateUninitialized);
+#else
+ // cdm_ = media::MojoCdmService::GetCdm(cdm_id);
+ // DCHECK(cdm_);
+
+ // On Android platform the MediaKeys will be its subclass MediaDrmBridge.
+ MediaDrmBridge* drm_bridge = static_cast<MediaDrmBridge*>(cdm_.get());
+
+ // Register CDM callbacks. The callbacks registered will be posted back to
+ // this thread via BindToCurrentLoop.
+
+ // Since |this| holds a reference to the |cdm_|, by the time the CDM is
+ // destructed, UnregisterPlayer() must have been called and |this| has been
+ // destructed as well. So the |cdm_unset_cb| will never have a chance to be
+ // called.
+ // TODO(xhwang): Remove |cdm_unset_cb| after it's not used on all platforms.
+ cdm_registration_id_ = drm_bridge->RegisterPlayer(
+ BindToCurrentLoop(base::Bind(&AndroidAudioDecoder::OnKeyAdded,
+ weak_factory_.GetWeakPtr())),
+ base::Bind(&base::DoNothing));
+
+ drm_bridge->SetMediaCryptoReadyCB(BindToCurrentLoop(base::Bind(
+ &AndroidAudioDecoder::OnMediaCryptoReady, weak_factory_.GetWeakPtr())));
+
+ // Postpone cdm_attached_cb.Run() call till CreateMediaCodec() which we can do
+ // after OnMediaCryptoReady().
+ cdm_attached_cb_ = cdm_attached_cb;
+ SetState(kStateWaitingForCrypto);
+#endif
+}
+
+void AndroidAudioDecoder::OnMediaCryptoReady(
+ media::MediaDrmBridge::JavaObjectPtr media_crypto) {
+ DVLOG(1) << __FUNCTION__;
+
+ if (!media_crypto) {
+ LOG(ERROR) << "MediaCrypto is not available, can't play encrypted stream.";
+ base::ResetAndReturn(&cdm_attached_cb_).Run(false);
+ base::ResetAndReturn(&init_cb_).Run(false);
+ SetState(kStateUninitialized);
+ return;
+ }
+
+ DCHECK(!media_crypto->is_null());
+
+ // We assume this is a part of the initialization process, thus MediaCodec
+ // is not created yet.
+ DCHECK(!media_codec_);
+
+ media_crypto_ = std::move(media_crypto);
+
+ // After receiving |media_crypto_| we can configure MediaCodec.
+ const bool success = ConfigureMediaCodec();
+
+ SetState(success ? kStateReady : kStateUninitialized);
+
+ base::ResetAndReturn(&cdm_attached_cb_).Run(success);
+ base::ResetAndReturn(&init_cb_).Run(success);
+}
+
+void AndroidAudioDecoder::OnKeyAdded() {
+ DVLOG(1) << __FUNCTION__;
+
+ if (state_ == kStateWaitingForKey)
+ SetState(kStateReady);
+
+ DoIOTask();
+}
+
+void AndroidAudioDecoder::DoIOTask() {
+ if (state_ == kStateError)
+ return;
+
+ const bool did_input = QueueInput();
+ const bool did_output = DequeueOutput();
+
+ ManageTimer(did_input || did_output);
+}
+
+bool AndroidAudioDecoder::QueueInput() {
+ DVLOG(2) << __FUNCTION__;
+
+ if (input_queue_.empty())
+ return false;
+
+ if (state_ == kStateWaitingForKey)
+ return false;
+
+ if (state_ == kStateDraining)
+ return false;
+
+ scoped_refptr<DecoderBuffer> decoder_buffer = input_queue_.front().first;
+ const DecodeCB& decode_cb = input_queue_.front().second;
+
+ int input_buf_index = pending_input_buf_index_;
+
+ // Do not dequeue a new input buffer if we failed with MEDIA_CODEC_NO_KEY.
+ // That status does not return the input buffer back to the pool of
+ // available input buffers. We have to reuse it in QueueSecureInputBuffer().
+ if (input_buf_index == -1) {
+ media::MediaCodecStatus status =
+ media_codec_->DequeueInputBuffer(NoWaitTimeOut(), &input_buf_index);
+ switch (status) {
+ case media::MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER:
+ return false;
+ case media::MEDIA_CODEC_ERROR:
+ DVLOG(1) << __FUNCTION__ << ": MEDIA_CODEC_ERROR";
+ SetState(kStateError);
+
+ // Report an error to the pipeline.
+ decode_cb.Run(kDecodeError);
+ return false;
+ case media::MEDIA_CODEC_OK:
+ break;
+ default:
+ NOTREACHED() << "Unknown DequeueInputBuffer status " << status;
+ return false;
+ }
+ }
+
+ DCHECK_NE(input_buf_index, -1);
+
+ if (decoder_buffer->end_of_stream()) {
+ media_codec_->QueueEOS(input_buf_index);
+
+ // After queueing EOS we need to flush decoder, i.e. receive this EOS at
+ // the output, and then call corresponding decoder_cb.
+ eos_decode_cb_ = decode_cb;
+ input_queue_.pop_front();
+ SetState(kStateDraining);
+ return true;
+ }
+
+ // Make sure our DecoderBuffer is not EOS, otherwise we can't call any method.
+ DCHECK(!decoder_buffer->end_of_stream());
+
+ media::MediaCodecStatus status;
+ const DecryptConfig* decrypt_config = decoder_buffer->decrypt_config();
+ if (decrypt_config) {
+ // If pending_input_buf_index_ != -1 the input buffer is already filled up,
+ // no need to copy it again.
+ const uint8_t* memory =
+ (pending_input_buf_index_ == -1) ? decoder_buffer->data() : nullptr;
+
+ status = media_codec_->QueueSecureInputBuffer(
+ input_buf_index, memory, decoder_buffer->data_size(),
+ decrypt_config->key_id(), decrypt_config->iv(),
+ decrypt_config->subsamples(), decoder_buffer->timestamp());
+
+ DVLOG(2) << __FUNCTION__
+ << ": QueueInputBuffer: pts:" << decoder_buffer->timestamp()
+ << " status:" << status;
+ } else {
+ status = media_codec_->QueueInputBuffer(
+ input_buf_index, decoder_buffer->data(), decoder_buffer->data_size(),
+ decoder_buffer->timestamp());
+ DVLOG(2) << __FUNCTION__
+ << ": QueueSecureInputBuffer: pts:" << decoder_buffer->timestamp()
+ << " status:" << status;
+ }
+
+ if (status == media::MEDIA_CODEC_NO_KEY) {
+ // Keep trying to enqueue the same input buffer.
+ // The buffer is owned by us (not the MediaCodec) and is filled with data.
+ DVLOG(1) << "QueueSecureInputBuffer failed: MEDIA_CODEC_NO_KEY";
+ pending_input_buf_index_ = input_buf_index;
+ SetState(kStateWaitingForKey);
+ return false;
+ }
+
+ pending_input_buf_index_ = -1;
+
+ // Although audio_decoder.h says "Once the buffer is decoded the decoder calls
+ // |decode_cb|", we call |decode_cb| when the buffer is accepted by
+ // MediaCodec, not when it is completely decoded. It seems consistent to what
+ // other decoders do.
+ decode_cb.Run(kOk);
+ input_queue_.pop_front();
+ return true;
+}
+
+bool AndroidAudioDecoder::DequeueOutput() {
liberato (no reviews please) 2016/02/01 15:18:06 DequeueOutput looks like it's an improvement over
+ DVLOG(2) << __FUNCTION__;
+
+ DCHECK(media_codec_);
+
+ MediaCodecStatus status;
+ OutputBufferInfo out;
liberato (no reviews please) 2016/02/01 15:18:06 OutputBufferInfo: good idea for avda.
+ bool work_done = false;
+ do {
+ status = media_codec_->DequeueOutputBuffer(NoWaitTimeOut(), &out.buf_index,
+ &out.offset, &out.size, &out.pts,
+ &out.is_eos, &out.is_key_frame);
+
+ switch (status) {
+ case MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED:
+ // Output buffers are replaced in MediaCodecBridge, nothing to do.
+ DVLOG(2) << __FUNCTION__ << " MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED";
+ work_done = true;
+ break;
+
+ case MEDIA_CODEC_OUTPUT_FORMAT_CHANGED:
+ DVLOG(2) << __FUNCTION__ << " MEDIA_CODEC_OUTPUT_FORMAT_CHANGED";
+ OnOutputFormatChanged();
+ work_done = true;
+ break;
+
+ case MEDIA_CODEC_OK:
+ // We got the decoded frame.
+ if (out.is_eos) {
+ media_codec_->ReleaseOutputBuffer(out.buf_index, false);
+
+ DCHECK_EQ(state_, kStateDraining);
+ DCHECK(!eos_decode_cb_.is_null());
+
+ // Report the end of decoding for EOS DecoderBuffer.
+ base::ResetAndReturn(&eos_decode_cb_).Run(kOk);
+
+ // media_decoder_job.cc says: once output EOS has occurred, we should
+ // not be asked to decode again.
+ // Have a separate state for this case.
+ SetState(kStateDrained);
+ } else {
+ // Process the real decoded frame.
+ OnDecodedFrame(&out);
+ }
+
+ work_done = true;
+ break;
+
+ case MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER:
+ // Nothing to do.
+ DVLOG(2) << __FUNCTION__ << " MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER";
+ break;
+
+ case MEDIA_CODEC_ERROR:
+ DVLOG(0) << __FUNCTION__
+ << ": MEDIA_CODEC_ERROR from DequeueOutputBuffer";
+
+ // Next Decode() will report the error to the pipeline.
+ SetState(kStateError);
+ break;
+
+ default:
+ NOTREACHED();
+ break;
+ }
+ } while (status != MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER &&
+ status != MEDIA_CODEC_ERROR && !out.is_eos);
+
+ return work_done;
+}
+
+void AndroidAudioDecoder::ManageTimer(bool did_work) {
+ bool should_be_running = true;
+
+ base::TimeTicks now = base::TimeTicks::Now();
+ if (!did_work) {
+ // Make sure that we have done work recently enough, else stop the timer.
+ if (now - most_recent_work_ > IdleTimerTimeOut())
+ should_be_running = false;
+ } else {
+ most_recent_work_ = now;
+ }
+
+ if (should_be_running && !io_timer_.IsRunning()) {
+ io_timer_.Start(FROM_HERE, DecodePollDelay(), this,
+ &AndroidAudioDecoder::DoIOTask);
+ } else if (!should_be_running && io_timer_.IsRunning()) {
+ io_timer_.Stop();
+ }
+}
+
+void AndroidAudioDecoder::SetState(State new_state) {
liberato (no reviews please) 2016/02/01 15:18:06 this is an improvement over avda.
+ DVLOG(1) << __FUNCTION__ << ": " << AsString(state_) << "->"
+ << AsString(new_state);
+ state_ = new_state;
+}
+
+bool AndroidAudioDecoder::ConfigureMediaCodec() {
+ DVLOG(1) << __FUNCTION__;
+
+ media_codec_.reset(AudioCodecBridge::Create(config_.codec()));
+ if (!media_codec_) {
+ DVLOG(0) << __FUNCTION__ << " failed: cannot create AudioCodecBridge";
+ return false;
+ }
+
+ AudioCodecBridge* audio_codec_bridge =
+ static_cast<AudioCodecBridge*>(media_codec_.get());
+ DCHECK(audio_codec_bridge);
+
+ jobject media_crypto = media_crypto_ ? media_crypto_->obj() : nullptr;
+
+ const bool play_audio = false; // Do not create AudioTrack object.
+ if (!audio_codec_bridge->ConfigureAndStart(
+ config_.codec(), config_.samples_per_second(), channel_count_,
+ &config_.extra_data()[0], config_.extra_data().size(),
+ config_.codec_delay(), config_.seek_preroll().InMicroseconds() * 1000,
+ play_audio, media_crypto)) {
+ DVLOG(0) << __FUNCTION__ << " failed: cannot start audio codec";
+ media_codec_.reset();
+ return false;
+ }
+
+ return true;
+}
+
+void AndroidAudioDecoder::OnDecodedFrame(const OutputBufferInfo* out) {
+ DCHECK(out);
+ DCHECK_NE(out->size, 0U);
+ DCHECK(media_codec_);
+
+ DVLOG(2) << __FUNCTION__ << " pts:" << out->pts;
+
+ // Create AudioOutput buffer based on configuration.
+ const size_t frame_count = out->size / bytes_per_frame_;
+
+ scoped_refptr<AudioBuffer> audio_buffer = AudioBuffer::CreateBuffer(
+ kSampleFormatS16, config_.channel_layout(), channel_count_,
+ config_.samples_per_second(), frame_count);
+
+ // Copy data into AudioBuffer.
+ media_codec_->CopyFromOutputBuffer(out->buf_index, out->offset,
+ audio_buffer->interleaved_data(),
+ audio_buffer->interleaved_data_size());
+
+ // Release MediaCodec output buffer.
+ media_codec_->ReleaseOutputBuffer(out->buf_index, false);
+
+ // Call the output_cb_.
+ output_cb_.Run(audio_buffer);
+}
+
+void AndroidAudioDecoder::OnOutputFormatChanged() {
+ DVLOG(0) << __FUNCTION__ << ": not implemented, going to error state";
+
+ SetState(kStateError);
+}
+
+#undef RETURN_STRING
+#define RETURN_STRING(x) \
+ case x: \
+ return #x;
+
+// static
+const char* AndroidAudioDecoder::AsString(State state) {
+ switch (state) {
+ RETURN_STRING(kStateUninitialized);
+ RETURN_STRING(kStateWaitingForCDM);
+ RETURN_STRING(kStateWaitingForCrypto);
+ RETURN_STRING(kStateReady);
+ RETURN_STRING(kStateWaitingForKey);
+ RETURN_STRING(kStateDraining);
+ RETURN_STRING(kStateDrained);
+ RETURN_STRING(kStateError);
+ }
+ return nullptr; // crash early
+}
+
+#undef RETURN_STRING
+
+} // namespace media

Powered by Google App Engine
This is Rietveld 408576698