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

Unified Diff: media/filters/android/media_codec_audio_decoder.cc

Issue 1651673002: Add MediaCodecAudioDecoder implementation (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Modified a comment Created 4 years, 10 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/media_codec_audio_decoder.cc
diff --git a/media/filters/android/media_codec_audio_decoder.cc b/media/filters/android/media_codec_audio_decoder.cc
index 0f4d35296fd9902ee0d0d991e463c53f9fd96ac8..4b9fd2da8de9b1d378ed1547ce84a36d20c54576 100644
--- a/media/filters/android/media_codec_audio_decoder.cc
+++ b/media/filters/android/media_codec_audio_decoder.cc
@@ -4,11 +4,69 @@
#include "media/filters/android/media_codec_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 {
+namespace {
+
+// 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);
+}
+
+inline int GetChannelCount(const AudioDecoderConfig& config) {
+ return ChannelLayoutToChannelCount(config.channel_layout());
+}
+
+scoped_ptr<MediaCodecBridge> CreateMediaCodec(
+ const AudioDecoderConfig& config) {
+ DVLOG(1) << __FUNCTION__ << ": config:" << config.AsHumanReadableString();
+
+ scoped_ptr<MediaCodecBridge> media_codec(
xhwang 2016/02/12 10:15:05 You can have "scoped_ptr<AudioCodecBridge> audio_c
Tima Vaisburd 2016/02/13 01:31:24 How to return scoped_ptr<MediaCodecBridge> on l.65
xhwang 2016/02/13 07:43:13 Can you try? return std::move(media_codec);
Tima Vaisburd 2016/02/15 23:25:42 That works!
+ AudioCodecBridge::Create(config.codec()));
+ if (!media_codec) {
+ DVLOG(0) << __FUNCTION__ << " failed: cannot create AudioCodecBridge";
+ return nullptr;
+ }
+
+ AudioCodecBridge* audio_codec_bridge =
+ static_cast<AudioCodecBridge*>(media_codec.get());
+ DCHECK(audio_codec_bridge);
+
+ const bool play_audio = false; // Do not create AudioTrack object.
+ jobject media_crypto = nullptr;
+
+ if (!audio_codec_bridge->ConfigureAndStart(config, play_audio,
+ media_crypto)) {
+ DVLOG(0) << __FUNCTION__ << " failed: cannot configure audio codec for "
+ << config.AsHumanReadableString();
+ return nullptr;
+ }
+
+ return media_codec;
+}
+
+} // namespace (anonymous)
+
MediaCodecAudioDecoder::MediaCodecAudioDecoder(
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner)
: task_runner_(task_runner),
@@ -21,7 +79,12 @@ MediaCodecAudioDecoder::MediaCodecAudioDecoder(
MediaCodecAudioDecoder::~MediaCodecAudioDecoder() {
DVLOG(1) << __FUNCTION__;
- NOTIMPLEMENTED();
+ media_codec_.reset();
xhwang 2016/02/12 10:15:05 Is this needed? Can we just let the scoped_ptr dto
Tima Vaisburd 2016/02/13 01:31:24 As far as I understand this is not needed right no
xhwang 2016/02/13 07:43:13 Acknowledged.
+
+ // Report kAborted status for all frames in the input queue.
+ // That includes pending EOS.
+ for (const auto& entry : input_queue_)
+ entry.second.Run(kAborted);
}
std::string MediaCodecAudioDecoder::GetDisplayName() const {
@@ -33,47 +96,322 @@ void MediaCodecAudioDecoder::Initialize(const AudioDecoderConfig& config,
const InitCB& init_cb,
const OutputCB& output_cb) {
DVLOG(1) << __FUNCTION__ << ": " << config.AsHumanReadableString();
+ DCHECK_EQ(state_, STATE_UNINITIALIZED);
+
+ InitCB bound_init_cb = BindToCurrentLoop(init_cb);
- NOTIMPLEMENTED();
+ if (config.is_encrypted()) {
+ DVLOG(1) << "Encrypted streams are not supported by " << GetDisplayName();
+ bound_init_cb.Run(false);
+ return;
+ }
+
+ // We can support only the codecs that AudioCodecBridge can decode.
+ 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;
+ }
+
+ media_codec_ = std::move(CreateMediaCodec(config));
xhwang 2016/02/12 10:15:05 Will it work if you remove std::move?
Tima Vaisburd 2016/02/13 01:31:24 Yes! Unnamed temporary?
xhwang 2016/02/13 07:43:13 rvalue: https://code.google.com/p/chromium/codesea
Tima Vaisburd 2016/02/15 23:25:42 Thank you for the link.
+ if (!media_codec_) {
+ bound_init_cb.Run(false);
+ return;
+ }
+
+ config_ = config;
+ output_cb_ = BindToCurrentLoop(output_cb);
+
+ SetState(STATE_READY);
+ bound_init_cb.Run(true);
}
void MediaCodecAudioDecoder::Decode(const scoped_refptr<DecoderBuffer>& buffer,
const DecodeCB& decode_cb) {
- NOTIMPLEMENTED();
+ DecodeCB bound_decode_cb = BindToCurrentLoop(decode_cb);
+
+ if (state_ == STATE_ERROR) {
+ DVLOG(2) << __FUNCTION__ << " " << buffer->AsHumanReadableString()
+ << ": Error state, dropping buffer";
+ bound_decode_cb.Run(kDecodeError);
+ return;
+ }
+
+ DVLOG(2) << __FUNCTION__ << " " << buffer->AsHumanReadableString();
+
+ DCHECK_EQ(state_, STATE_READY) << " unexpected state " << AsString(state_);
+
+ input_queue_.push_back(std::make_pair(buffer, bound_decode_cb));
+
+ DoIOTask();
}
void MediaCodecAudioDecoder::Reset(const base::Closure& closure) {
DVLOG(1) << __FUNCTION__;
- NOTIMPLEMENTED();
+ io_timer_.Stop();
+
+ // 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_ != STATE_ERROR && state_ != STATE_DRAINED &&
+ base::android::BuildInfo::GetInstance()->sdk_int() < 18) {
xhwang 2016/02/12 10:15:05 Why we only Reset/Flush when SDK < 18?
Tima Vaisburd 2016/02/13 01:31:24 Bug! Thank you, should be SDK >= 18. I updated the
+ // media_codec_->Reset() calls MediaCodec.flush().
+ success = (media_codec_->Reset() == MEDIA_CODEC_OK);
+ }
+
+ if (!success) {
+ media_codec_.reset();
+ media_codec_ = std::move(CreateMediaCodec(config_));
+ success = !!media_codec_;
+ }
+
+ SetState(success ? STATE_READY : STATE_ERROR);
+
+ task_runner_->PostTask(FROM_HERE, closure);
}
void MediaCodecAudioDecoder::OnKeyAdded() {
DVLOG(1) << __FUNCTION__;
- NOTIMPLEMENTED();
+ if (state_ == STATE_WAITING_FOR_KEY)
+ SetState(STATE_READY);
+
+ DoIOTask();
}
void MediaCodecAudioDecoder::DoIOTask() {
- NOTIMPLEMENTED();
+ if (state_ == STATE_ERROR)
+ return;
+
+ const bool did_input = QueueInput();
+ const bool did_output = DequeueOutput();
+
+ ManageTimer(did_input || did_output);
}
bool MediaCodecAudioDecoder::QueueInput() {
DVLOG(2) << __FUNCTION__;
- NOTIMPLEMENTED();
+ if (input_queue_.empty())
+ return false;
+
+ if (state_ == STATE_WAITING_FOR_KEY)
+ return false;
+
+ if (state_ == STATE_DRAINING)
+ 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 from DequeInputBuffer";
+ SetState(STATE_ERROR);
+
+ // Report an error to the pipeline.
+ decode_cb.Run(kDecodeError);
xhwang 2016/02/12 10:15:04 Do you want to clear |input_queue_| and fire all p
Tima Vaisburd 2016/02/13 01:31:24 I created DequeueInputBuffer() and EnqueueInputBuf
xhwang 2016/02/13 07:43:13 In Reset() you'll clear |input_queue_| anyways, ri
Tima Vaisburd 2016/02/15 23:25:42 Agreed. Created a new method ClearInputQueue() and
+ return false;
+ case media::MEDIA_CODEC_OK:
+ break;
+ default:
+ NOTREACHED() << "Unknown DequeueInputBuffer status " << status;
+ return false;
+ }
+ }
+
+ DCHECK_NE(input_buf_index, -1);
xhwang 2016/02/12 10:15:04 QueueInput() is too long. Can you wrap line 217-24
Tima Vaisburd 2016/02/13 01:31:24 See my comment above.
+
xhwang 2016/02/12 10:15:05 See above. Move line 214 here. You don't need lin
Tima Vaisburd 2016/02/13 01:31:24 See my comment above.
+ 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.
+ // Keep the EOS buffer in the input queue.
+ SetState(STATE_DRAINING);
+ return true;
+ }
+
+ // Make sure our DecoderBuffer is not EOS, otherwise we can't call any method.
+ DCHECK(!decoder_buffer->end_of_stream());
xhwang 2016/02/12 10:15:05 Not needed given line 245 and 252?
Tima Vaisburd 2016/02/13 01:31:24 Removed.
+
+ media::MediaCodecStatus status = MEDIA_CODEC_OK;
+ 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;
+
+ DVLOG(2) << __FUNCTION__ << ": QueueSecureInputBuffer:"
+ << " index:" << input_buf_index
+ << " pts:" << decoder_buffer->timestamp()
+ << " size:" << decoder_buffer->data_size();
+
+ 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());
+
+ } else {
+ DVLOG(2) << __FUNCTION__ << ": QueueInputBuffer:"
+ << " index:" << input_buf_index
+ << " pts:" << decoder_buffer->timestamp()
+ << " size:" << decoder_buffer->data_size();
+
+ status = media_codec_->QueueInputBuffer(
+ input_buf_index, decoder_buffer->data(), decoder_buffer->data_size(),
+ decoder_buffer->timestamp());
+ }
+
+ pending_input_buf_index_ = -1;
+
+ switch (status) {
+ case MEDIA_CODEC_ERROR:
+ DVLOG(0) << __FUNCTION__ << ": MEDIA_CODEC_ERROR from QueueInputBuffer";
+ SetState(STATE_ERROR);
+
+ // Report an error to the pipeline.
+ decode_cb.Run(kDecodeError);
+ return false;
+
+ case 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(STATE_WAITING_FOR_KEY);
+ return false;
+
+ case MEDIA_CODEC_OK:
+ break;
+
+ default:
+ NOTREACHED() << "Unknown Queue(Secure)InputBuffer status " << status;
+ break;
xhwang 2016/02/12 10:15:05 Should we go to STATE_ERROR and bail out?
Tima Vaisburd 2016/02/13 01:31:24 Done.
+ }
xhwang 2016/02/12 10:15:05 ditto, wrap this into a QueueInputBuffer() method?
Tima Vaisburd 2016/02/13 01:31:24 Done.
+
+ // 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
xhwang 2016/02/12 10:15:05 The comment actually means that once the buffer is
Tima Vaisburd 2016/02/13 01:31:24 Done.
+ // 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 MediaCodecAudioDecoder::DequeueOutput() {
DVLOG(2) << __FUNCTION__;
- NOTIMPLEMENTED();
- return true;
+ DCHECK(media_codec_);
xhwang 2016/02/12 10:15:04 Not needed. If this fires we'll just crash on line
Tima Vaisburd 2016/02/13 01:31:24 Done. I've always wanted to ask though: there are
+
+ MediaCodecStatus status;
+ OutputBufferInfo out;
+ bool work_done = false;
xhwang 2016/02/12 10:15:05 use |did_work| to be consistent?
Tima Vaisburd 2016/02/13 01:31:24 Done.
+ 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_, STATE_DRAINING);
+
+ // Report the end of decoding for EOS DecoderBuffer.
+ const DecodeCB& decode_cb = input_queue_.front().second;
+ decode_cb.Run(kOk);
+
+ // Remove the EOS buffer from the input queue.
+ input_queue_.pop_front();
+
xhwang 2016/02/12 10:15:05 input_queue_ should be empty now?
Tima Vaisburd 2016/02/13 01:31:24 For this audio decoder probably yes, but in genera
+ // 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(STATE_DRAINED);
xhwang 2016/02/12 10:15:05 The switch/case block is too long. Wrap this block
Tima Vaisburd 2016/02/13 01:31:24 Yes, I called OnDecodedEos() and renamed OnDecoder
+ } 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(STATE_ERROR);
+ break;
+
+ default:
+ NOTREACHED();
+ break;
+ }
+ } while (status != MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER &&
+ status != MEDIA_CODEC_ERROR && !out.is_eos);
+
+ return work_done;
}
-void MediaCodecAudioDecoder::ManageTimer(bool start) {
- NOTIMPLEMENTED();
+void MediaCodecAudioDecoder::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 - idle_time_begin_ > IdleTimerTimeOut())
xhwang 2016/02/12 10:15:05 The initial value of idle_time_begin_ would be 0.
Tima Vaisburd 2016/02/13 01:31:24 I think you mean if (!did_work && idle_time_begi
+ should_be_running = false;
+ } else {
+ idle_time_begin_ = now;
+ }
+
+ if (should_be_running && !io_timer_.IsRunning()) {
+ io_timer_.Start(FROM_HERE, DecodePollDelay(), this,
+ &MediaCodecAudioDecoder::DoIOTask);
+ } else if (!should_be_running && io_timer_.IsRunning()) {
+ io_timer_.Stop();
+ }
}
void MediaCodecAudioDecoder::SetState(State new_state) {
@@ -82,22 +420,50 @@ void MediaCodecAudioDecoder::SetState(State new_state) {
state_ = new_state;
}
-scoped_ptr<MediaCodecBridge> MediaCodecAudioDecoder::ConfigureMediaCodec(
- const AudioDecoderConfig& config) {
- NOTIMPLEMENTED();
- return nullptr;
-}
-
void MediaCodecAudioDecoder::OnDecodedFrame(const OutputBufferInfo& out) {
+ DCHECK_NE(out.size, 0U);
+ DCHECK_GE(out.buf_index, 0); // |out->buf_index| must be valid
+ DCHECK(media_codec_);
xhwang 2016/02/12 10:15:05 nit: usually we DVLOG first, then DCHECK.
Tima Vaisburd 2016/02/13 01:31:24 My bad! Done.
+
DVLOG(2) << __FUNCTION__ << " pts:" << out.pts;
- NOTIMPLEMENTED();
+ // Create AudioOutput buffer based on configuration.
+ const int channel_count = GetChannelCount(config_);
+ const int bytes_per_frame = kBytesPerOutputSample * channel_count;
+ 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);
+
+ // Set timestamp.
+ audio_buffer->set_timestamp(out.pts);
+
+ // Copy data into AudioBuffer.
+ DCHECK_LE(out.size, audio_buffer->data_size());
+
+ media_codec_->CopyFromOutputBuffer(out.buf_index, out.offset,
+ audio_buffer->channel_data()[0],
+ audio_buffer->data_size());
+
+ // Release MediaCodec output buffer.
+ media_codec_->ReleaseOutputBuffer(out.buf_index, false);
+
+ // Call the |output_cb_|.
+ output_cb_.Run(audio_buffer);
}
void MediaCodecAudioDecoder::OnOutputFormatChanged() {
DVLOG(2) << __FUNCTION__;
- NOTIMPLEMENTED();
+ // We do not support the change of sampling rate on the fly
+ int new_sampling_rate = media_codec_->GetOutputSamplingRate();
+ if (new_sampling_rate != config_.samples_per_second()) {
+ DVLOG(0) << "Sampling rate change is not supported by" << GetDisplayName()
+ << " (detected change " << config_.samples_per_second() << "->"
+ << new_sampling_rate << ")";
+ SetState(STATE_ERROR);
+ }
}
#undef RETURN_STRING

Powered by Google App Engine
This is Rietveld 408576698