| 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..c23163541edb10e90e1d04fe0592a8e3484034f7 100644
|
| --- a/media/filters/android/media_codec_audio_decoder.cc
|
| +++ b/media/filters/android/media_codec_audio_decoder.cc
|
| @@ -4,24 +4,80 @@
|
|
|
| #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;
|
| +
|
| +// Valid MediaCodec buffer indexes are zero or positive.
|
| +const int kInvalidBufferIndex = -1;
|
| +
|
| +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<AudioCodecBridge> audio_codec_bridge(
|
| + AudioCodecBridge::Create(config.codec()));
|
| + if (!audio_codec_bridge) {
|
| + DVLOG(0) << __FUNCTION__ << " failed: cannot create AudioCodecBridge";
|
| + return nullptr;
|
| + }
|
| +
|
| + const bool do_play = false; // Do not create AudioTrack object.
|
| + jobject media_crypto = nullptr;
|
| +
|
| + if (!audio_codec_bridge->ConfigureAndStart(config, do_play, media_crypto)) {
|
| + DVLOG(0) << __FUNCTION__ << " failed: cannot configure audio codec for "
|
| + << config.AsHumanReadableString();
|
| + return nullptr;
|
| + }
|
| +
|
| + return std::move(audio_codec_bridge);
|
| +}
|
| +
|
| +} // namespace (anonymous)
|
| +
|
| MediaCodecAudioDecoder::MediaCodecAudioDecoder(
|
| const scoped_refptr<base::SingleThreadTaskRunner>& task_runner)
|
| : task_runner_(task_runner),
|
| state_(STATE_UNINITIALIZED),
|
| - pending_input_buf_index_(-1),
|
| - weak_factory_(this) {
|
| + pending_input_buf_index_(kInvalidBufferIndex) {
|
| DVLOG(1) << __FUNCTION__;
|
| }
|
|
|
| MediaCodecAudioDecoder::~MediaCodecAudioDecoder() {
|
| DVLOG(1) << __FUNCTION__;
|
|
|
| - NOTIMPLEMENTED();
|
| + media_codec_.reset();
|
| +
|
| + ClearInputQueue(kAborted);
|
| }
|
|
|
| std::string MediaCodecAudioDecoder::GetDisplayName() const {
|
| @@ -33,47 +89,362 @@ 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);
|
| +
|
| + 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_ = CreateMediaCodec(config);
|
| + if (!media_codec_) {
|
| + bound_init_cb.Run(false);
|
| + return;
|
| + }
|
| +
|
| + config_ = config;
|
| + output_cb_ = BindToCurrentLoop(output_cb);
|
|
|
| - NOTIMPLEMENTED();
|
| + 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) {
|
| + // We get here if an error happens in DecodeOutput() or Reset().
|
| + DVLOG(2) << __FUNCTION__ << " " << buffer->AsHumanReadableString()
|
| + << ": Error state, returning decode error for all buffers";
|
| + ClearInputQueue(kDecodeError);
|
| + bound_decode_cb.Run(kDecodeError);
|
| + return;
|
| + }
|
| +
|
| + DVLOG(2) << __FUNCTION__ << " " << buffer->AsHumanReadableString();
|
| +
|
| + DCHECK_EQ(state_, STATE_READY) << " unexpected state " << AsString(state_);
|
| +
|
| + // AudioDecoder requires that "Only one decode may be in flight at any given
|
| + // time".
|
| + DCHECK(input_queue_.empty());
|
| +
|
| + 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();
|
| +
|
| + ClearInputQueue(kAborted);
|
| +
|
| + // Flush if we can, otherwise completely recreate and reconfigure the codec.
|
| + // Prior to JellyBean-MR2, flush() had several bugs (b/8125974, b/8347958) so
|
| + // we have to completely destroy and recreate the codec there.
|
| + bool success = false;
|
| + if (state_ != STATE_ERROR && state_ != STATE_DRAINED &&
|
| + 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();
|
| + media_codec_ = 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();
|
| - return true;
|
| + if (input_queue_.empty())
|
| + return false;
|
| +
|
| + if (state_ == STATE_WAITING_FOR_KEY || state_ == STATE_DRAINING ||
|
| + state_ == STATE_DRAINED)
|
| + return false;
|
| +
|
| + // DequeueInputBuffer() may set STATE_ERROR.
|
| + InputBufferInfo input_info = DequeueInputBuffer();
|
| +
|
| + if (input_info.buf_index == kInvalidBufferIndex) {
|
| + if (state_ == STATE_ERROR)
|
| + ClearInputQueue(kDecodeError);
|
| +
|
| + return false;
|
| + }
|
| +
|
| + // EnqueueInputBuffer() may set STATE_DRAINING, STATE_WAITING_FOR_KEY or
|
| + // STATE_ERROR.
|
| + bool did_work = EnqueueInputBuffer(input_info);
|
| +
|
| + switch (state_) {
|
| + case STATE_READY: {
|
| + const DecodeCB& decode_cb = input_queue_.front().second;
|
| + decode_cb.Run(kOk);
|
| + input_queue_.pop_front();
|
| + } break;
|
| +
|
| + case STATE_DRAINING:
|
| + // 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.
|
| + break;
|
| +
|
| + case STATE_WAITING_FOR_KEY:
|
| + // Keep trying to enqueue the same input buffer.
|
| + // The buffer is owned by us (not the MediaCodec) and is filled with data.
|
| + break;
|
| +
|
| + case STATE_ERROR:
|
| + ClearInputQueue(kDecodeError);
|
| + break;
|
| +
|
| + default:
|
| + NOTREACHED() << ": internal error, unexpected state " << AsString(state_);
|
| + SetState(STATE_ERROR);
|
| + ClearInputQueue(kDecodeError);
|
| + did_work = false;
|
| + break;
|
| + }
|
| +
|
| + return did_work;
|
| +}
|
| +
|
| +MediaCodecAudioDecoder::InputBufferInfo
|
| +MediaCodecAudioDecoder::DequeueInputBuffer() {
|
| + DVLOG(2) << __FUNCTION__;
|
| +
|
| + // 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 (pending_input_buf_index_ != kInvalidBufferIndex) {
|
| + InputBufferInfo result(pending_input_buf_index_, true);
|
| + pending_input_buf_index_ = kInvalidBufferIndex;
|
| + return result;
|
| + }
|
| +
|
| + int input_buf_index = kInvalidBufferIndex;
|
| +
|
| + media::MediaCodecStatus status =
|
| + media_codec_->DequeueInputBuffer(NoWaitTimeout(), &input_buf_index);
|
| + switch (status) {
|
| + case media::MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER:
|
| + break;
|
| +
|
| + case media::MEDIA_CODEC_ERROR:
|
| + DVLOG(1) << __FUNCTION__ << ": MEDIA_CODEC_ERROR from DequeInputBuffer";
|
| + SetState(STATE_ERROR);
|
| + break;
|
| +
|
| + case media::MEDIA_CODEC_OK:
|
| + break;
|
| +
|
| + default:
|
| + NOTREACHED() << "Unknown DequeueInputBuffer status " << status;
|
| + SetState(STATE_ERROR);
|
| + break;
|
| + }
|
| +
|
| + return InputBufferInfo(input_buf_index, false);
|
| +}
|
| +
|
| +bool MediaCodecAudioDecoder::EnqueueInputBuffer(
|
| + const InputBufferInfo& input_info) {
|
| + DVLOG(2) << __FUNCTION__ << ": index:" << input_info.buf_index;
|
| +
|
| + DCHECK_NE(input_info.buf_index, kInvalidBufferIndex);
|
| +
|
| + scoped_refptr<DecoderBuffer> decoder_buffer = input_queue_.front().first;
|
| +
|
| + if (decoder_buffer->end_of_stream()) {
|
| + media_codec_->QueueEOS(input_info.buf_index);
|
| +
|
| + SetState(STATE_DRAINING);
|
| + return true;
|
| + }
|
| +
|
| + media::MediaCodecStatus status = MEDIA_CODEC_OK;
|
| +
|
| + const DecryptConfig* decrypt_config = decoder_buffer->decrypt_config();
|
| + if (decrypt_config) {
|
| + // A pending buffer is already filled with data, no need to copy it again.
|
| + const uint8_t* memory =
|
| + input_info.is_pending ? decoder_buffer->data() : nullptr;
|
| +
|
| + DVLOG(2) << __FUNCTION__ << ": QueueSecureInputBuffer:"
|
| + << " index:" << input_info.buf_index
|
| + << " pts:" << decoder_buffer->timestamp()
|
| + << " size:" << decoder_buffer->data_size();
|
| +
|
| + status = media_codec_->QueueSecureInputBuffer(
|
| + input_info.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_info.buf_index
|
| + << " pts:" << decoder_buffer->timestamp()
|
| + << " size:" << decoder_buffer->data_size();
|
| +
|
| + status = media_codec_->QueueInputBuffer(
|
| + input_info.buf_index, decoder_buffer->data(),
|
| + decoder_buffer->data_size(), decoder_buffer->timestamp());
|
| + }
|
| +
|
| + bool did_work = false;
|
| + switch (status) {
|
| + case MEDIA_CODEC_ERROR:
|
| + DVLOG(0) << __FUNCTION__ << ": MEDIA_CODEC_ERROR from QueueInputBuffer";
|
| + SetState(STATE_ERROR);
|
| + break;
|
| +
|
| + case MEDIA_CODEC_NO_KEY:
|
| + DVLOG(1) << "QueueSecureInputBuffer failed: MEDIA_CODEC_NO_KEY";
|
| + pending_input_buf_index_ = input_info.buf_index;
|
| + SetState(STATE_WAITING_FOR_KEY);
|
| + break;
|
| +
|
| + case MEDIA_CODEC_OK:
|
| + did_work = true;
|
| + break;
|
| +
|
| + default:
|
| + NOTREACHED() << "Unknown Queue(Secure)InputBuffer status " << status;
|
| + SetState(STATE_ERROR);
|
| + break;
|
| + }
|
| + return did_work;
|
| +}
|
| +
|
| +void MediaCodecAudioDecoder::ClearInputQueue(Status decode_status) {
|
| + DVLOG(2) << __FUNCTION__;
|
| +
|
| + for (const auto& entry : input_queue_)
|
| + entry.second.Run(decode_status);
|
| +
|
| + input_queue_.clear();
|
| }
|
|
|
| bool MediaCodecAudioDecoder::DequeueOutput() {
|
| DVLOG(2) << __FUNCTION__;
|
|
|
| - NOTIMPLEMENTED();
|
| - return true;
|
| + MediaCodecStatus status;
|
| + OutputBufferInfo out;
|
| + bool did_work = 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";
|
| + did_work = true;
|
| + break;
|
| +
|
| + case MEDIA_CODEC_OUTPUT_FORMAT_CHANGED:
|
| + DVLOG(2) << __FUNCTION__ << " MEDIA_CODEC_OUTPUT_FORMAT_CHANGED";
|
| + OnOutputFormatChanged();
|
| + did_work = true;
|
| + break;
|
| +
|
| + case MEDIA_CODEC_OK:
|
| + // We got the decoded frame.
|
| + if (out.is_eos)
|
| + OnDecodedEos(out);
|
| + else
|
| + OnDecodedFrame(out);
|
| +
|
| + did_work = 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() << "Unknown DequeueOutputBuffer status " << status;
|
| + // Next Decode() will report the error to the pipeline.
|
| + SetState(STATE_ERROR);
|
| + break;
|
| + }
|
| + } while (status != MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER &&
|
| + status != MEDIA_CODEC_ERROR && !out.is_eos);
|
| +
|
| + return did_work;
|
| }
|
|
|
| -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 || idle_time_begin_ == base::TimeTicks()) {
|
| + idle_time_begin_ = now;
|
| + } else {
|
| + // Make sure that we have done work recently enough, else stop the timer.
|
| + if (now - idle_time_begin_ > IdleTimerTimeout())
|
| + should_be_running = false;
|
| + }
|
| +
|
| + 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 +453,76 @@ void MediaCodecAudioDecoder::SetState(State new_state) {
|
| state_ = new_state;
|
| }
|
|
|
| -scoped_ptr<MediaCodecBridge> MediaCodecAudioDecoder::ConfigureMediaCodec(
|
| - const AudioDecoderConfig& config) {
|
| - NOTIMPLEMENTED();
|
| - return nullptr;
|
| +void MediaCodecAudioDecoder::OnDecodedEos(const OutputBufferInfo& out) {
|
| + DVLOG(2) << __FUNCTION__ << " pts:" << out.pts;
|
| +
|
| + DCHECK_NE(out.buf_index, kInvalidBufferIndex);
|
| + DCHECK(media_codec_);
|
| +
|
| + DCHECK_EQ(state_, STATE_DRAINING);
|
| +
|
| + media_codec_->ReleaseOutputBuffer(out.buf_index, false);
|
| +
|
| + // Report the end of decoding for EOS DecoderBuffer.
|
| + DCHECK(!input_queue_.empty());
|
| + DCHECK(input_queue_.front().first->end_of_stream());
|
| +
|
| + const DecodeCB& decode_cb = input_queue_.front().second;
|
| + decode_cb.Run(kOk);
|
| +
|
| + // Remove the EOS buffer from the input queue.
|
| + input_queue_.pop_front();
|
| +
|
| + // Set state STATE_DRAINED after we have received EOS frame at the output.
|
| + // media_decoder_job.cc says: once output EOS has occurred, we should
|
| + // not be asked to decode again.
|
| + SetState(STATE_DRAINED);
|
| }
|
|
|
| void MediaCodecAudioDecoder::OnDecodedFrame(const OutputBufferInfo& out) {
|
| DVLOG(2) << __FUNCTION__ << " pts:" << out.pts;
|
|
|
| - NOTIMPLEMENTED();
|
| + DCHECK_NE(out.size, 0U);
|
| + DCHECK_NE(out.buf_index, kInvalidBufferIndex);
|
| + DCHECK(media_codec_);
|
| +
|
| + // 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.
|
| + CHECK_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
|
|
|