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

Unified Diff: media/base/android/media_codec_loop.cc

Issue 2016213003: Separate MediaCodecLoop from MediaCodecAudioDecoder (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: cl feedback. Created 4 years, 6 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/base/android/media_codec_loop.cc
diff --git a/media/base/android/media_codec_loop.cc b/media/base/android/media_codec_loop.cc
new file mode 100644
index 0000000000000000000000000000000000000000..f6dc35ae3edcaf1e835bec947b192a1299320a11
--- /dev/null
+++ b/media/base/android/media_codec_loop.cc
@@ -0,0 +1,398 @@
+// 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/base/android/media_codec_loop.h"
+
+#include "base/android/build_info.h"
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/logging.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "media/base/android/sdk_media_codec_bridge.h"
+#include "media/base/audio_buffer.h"
+#include "media/base/audio_timestamp_helper.h"
+#include "media/base/bind_to_current_loop.h"
+#include "media/base/timestamp_constants.h"
+
+namespace media {
+
+constexpr base::TimeDelta kDecodePollDelay =
+ base::TimeDelta::FromMilliseconds(10);
+constexpr base::TimeDelta kNoWaitTimeout = base::TimeDelta::FromMicroseconds(0);
+constexpr base::TimeDelta kIdleTimerTimeout = base::TimeDelta::FromSeconds(1);
+
+MediaCodecLoop::InputData::InputData()
+ : memory(nullptr), length(0), is_eos(false), is_encrypted(false) {}
+
+MediaCodecLoop::InputData::InputData(const InputData& other)
+ : memory(other.memory),
+ length(other.length),
+ key_id(other.key_id),
+ iv(other.iv),
+ subsamples(other.subsamples),
+ presentation_time(other.presentation_time),
+ completion_cb(other.completion_cb),
+ is_eos(other.is_eos),
+ is_encrypted(other.is_encrypted) {}
+
+MediaCodecLoop::InputData::~InputData() {}
+
+MediaCodecLoop::MediaCodecLoop(Client* client,
+ std::unique_ptr<MediaCodecBridge>&& media_codec)
+ : state_(STATE_READY),
+ client_(client),
+ media_codec_(std::move(media_codec)),
+ pending_input_buf_index_(kInvalidBufferIndex),
+ weak_factory_(this) {
+ DVLOG(1) << __FUNCTION__;
DaleCurtis 2016/06/13 23:16:13 I typically drop these from production code, but u
liberato (no reviews please) 2016/06/14 17:26:44 Done.
+ DCHECK(media_codec_);
+}
+
+MediaCodecLoop::~MediaCodecLoop() {
+ DVLOG(1) << __FUNCTION__;
+}
+
+void MediaCodecLoop::OnKeyAdded() {
+ DVLOG(1) << __FUNCTION__;
+
+ if (state_ == STATE_WAITING_FOR_KEY)
+ SetState(STATE_READY);
+
+ DoIOTask();
+}
+
+bool MediaCodecLoop::TryFlush() {
+ DVLOG(1) << __FUNCTION__;
+
+ // We do not clear the input queue here. It depends on the caller.
+ // For decoder reset, then it is appropriate. Otherwise, the requests might
+ // simply be sent to us later, such as on a format change.
+
+ // 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;
DaleCurtis 2016/06/13 23:16:13 Reorder this so that we early return instead of us
liberato (no reviews please) 2016/06/14 17:26:44 Done, also renamed 'flush' pending rebase onto wat
+ if (state_ != STATE_ERROR && state_ != STATE_DRAINED &&
+ base::android::BuildInfo::GetInstance()->sdk_int() >= 18) {
+ io_timer_.Stop();
+
+ // media_codec_->Reset() calls MediaCodec.flush().
+ success = media_codec_->Reset() == MEDIA_CODEC_OK;
+
+ // Transition to the error state if the flush failed.
+ SetState(success ? STATE_READY : STATE_ERROR);
+ }
+
+ return success;
+}
+
+void MediaCodecLoop::DoIOTask() {
+ if (state_ == STATE_ERROR)
+ return;
+
+ bool did_work = false, did_input = false, did_output = false;
+ do {
+ did_input = ProcessOneInputBuffer();
+ did_output = ProcessOneOutputBuffer();
+ if (did_input || did_output)
+ did_work = true;
+ } while (did_input || did_output);
+
+ // TODO(liberato): add "start_timer" for AVDA.
+ ManageTimer(did_work);
+}
+
+bool MediaCodecLoop::ProcessOneInputBuffer() {
+ DVLOG(2) << __FUNCTION__;
+
+ // We can only queue a buffer if there is input from the client, or if we
+ // tried previously but had to wait for a key. In the latter case, MediaCodec
+ // already has the data.
+ if (pending_input_buf_index_ == kInvalidBufferIndex &&
DaleCurtis 2016/06/13 23:16:13 A single pending_input_buf_index_ isn't going to w
liberato (no reviews please) 2016/06/14 17:26:44 this is only if we're waiting for a key, which lim
+ !client_->IsAnyInputPending()) {
+ return false;
+ }
+
+ if (state_ == STATE_WAITING_FOR_KEY || state_ == STATE_DRAINING ||
+ state_ == STATE_DRAINED) {
DaleCurtis 2016/06/13 23:16:13 STATE_ERROR ?
liberato (no reviews please) 2016/06/14 17:26:44 Done.
+ return false;
+ }
+
+ // DequeueInputBuffer() may set STATE_ERROR.
+ InputBuffer input_buffer = DequeueInputBuffer();
DaleCurtis 2016/06/13 23:16:13 Instead of return by copy should this take a Input
liberato (no reviews please) 2016/06/14 17:26:44 this sounded like a good idea, but i un-did it aft
+
+ if (input_buffer.index == kInvalidBufferIndex)
+ return false;
+
+ // EnqueueInputBuffer() may set STATE_DRAINING, STATE_WAITING_FOR_KEY or
+ // STATE_ERROR.
+ EnqueueInputBuffer(input_buffer);
+ return state_ == STATE_READY;
+}
+
+MediaCodecLoop::InputBuffer MediaCodecLoop::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) {
+ InputBuffer result(pending_input_buf_index_, true);
+ pending_input_buf_index_ = kInvalidBufferIndex;
+ return result;
+ }
+
+ int input_buf_index = kInvalidBufferIndex;
+
+ media::MediaCodecStatus status =
+ media_codec_->DequeueInputBuffer(kNoWaitTimeout, &input_buf_index);
+ switch (status) {
+ case media::MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER:
+ DVLOG(2) << __FUNCTION__ << ": MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER";
+ break;
+
+ case media::MEDIA_CODEC_ERROR:
+ DVLOG(1) << __FUNCTION__ << ": MEDIA_CODEC_ERROR from DequeInputBuffer";
DaleCurtis 2016/06/13 23:16:13 DLOG(ERROR) ?
liberato (no reviews please) 2016/06/14 17:26:44 Done.
+ SetState(STATE_ERROR);
+ break;
+
+ case media::MEDIA_CODEC_OK:
+ break;
+
+ default:
+ NOTREACHED() << "Unknown DequeueInputBuffer status " << status;
+ SetState(STATE_ERROR);
+ break;
+ }
+
+ return InputBuffer(input_buf_index, false);
+}
+
+void MediaCodecLoop::EnqueueInputBuffer(const InputBuffer& input_buffer) {
+ DVLOG(2) << __FUNCTION__ << ": index:" << input_buffer.index;
+
+ DCHECK_NE(input_buffer.index, kInvalidBufferIndex);
+
+ InputData input_data;
+
DaleCurtis 2016/06/13 23:16:13 Remove line so it's clear input_data goes with con
liberato (no reviews please) 2016/06/14 17:26:45 Done.
+ if (input_buffer.is_pending) {
+ // A pending buffer is already filled with data, no need to copy it again.
+ DVLOG(2) << __FUNCTION__ << ": QueueSecureInputBuffer (pending):"
+ << " index:" << input_buffer.index
+ << " pts:" << pending_input_buf_data_.presentation_time
+ << " size:" << pending_input_buf_data_.length;
+
+ input_data = pending_input_buf_data_;
+ } else {
+ input_data = client_->ProvideInputData();
+ }
+
+ // Process this buffer.
DaleCurtis 2016/06/13 23:16:13 Not really useful.
liberato (no reviews please) 2016/06/14 17:26:44 Done.
+
+ if (input_data.is_eos) {
+ media_codec_->QueueEOS(input_buffer.index);
+ SetState(STATE_DRAINING);
+
+ // For EOS, the completion callback is called when the EOS arrives at the
+ // output queue.
+ pending_eos_completion_cb_ = input_data.completion_cb;
+ return;
+ }
+
+ media::MediaCodecStatus status = MEDIA_CODEC_OK;
+
+ if (input_data.is_encrypted) {
+ // Note that input_data might not have a valid memory ptr if this is a
+ // re-send of a buffer that was sent before decryption keys arrived.
+
+ DVLOG(2) << __FUNCTION__ << ": QueueSecureInputBuffer:"
+ << " index:" << input_buffer.index
+ << " pts:" << input_data.presentation_time
+ << " size:" << input_data.length;
+
+ status = media_codec_->QueueSecureInputBuffer(
+ input_buffer.index, input_data.memory, input_data.length,
+ input_data.key_id, input_data.iv, input_data.subsamples,
+ input_data.presentation_time);
+
+ } else {
+ DVLOG(2) << __FUNCTION__ << ": QueueInputBuffer:"
+ << " index:" << input_buffer.index
+ << " pts:" << input_data.presentation_time
+ << " size:" << input_data.length;
+
+ status = media_codec_->QueueInputBuffer(
+ input_buffer.index, input_data.memory, input_data.length,
+ input_data.presentation_time);
+ }
+
+ switch (status) {
+ case MEDIA_CODEC_ERROR:
+ DVLOG(0) << __FUNCTION__ << ": MEDIA_CODEC_ERROR from QueueInputBuffer";
DaleCurtis 2016/06/13 23:16:13 DLOG(ERROR)
liberato (no reviews please) 2016/06/14 17:26:44 Done.
+ input_data.completion_cb.Run(DecodeStatus::DECODE_ERROR);
+ // Transition to the error state after running the completion cb, to keep
+ // it in order if the client chooses to flush its queue.
+ SetState(STATE_ERROR);
+ break;
+
+ case MEDIA_CODEC_NO_KEY:
+ DVLOG(1) << "QueueSecureInputBuffer failed: MEDIA_CODEC_NO_KEY";
+ // Do not call the completion cb here. It will be called when we retry
+ // after getting the key.
+ pending_input_buf_index_ = input_buffer.index;
+ pending_input_buf_data_ = input_data;
+ // MediaCodec has a copy of the data already. When we call again, be sure
+ // to send in nullptr for the source. Note that the client doesn't
+ // guarantee that the pointer will remain valid after we return anyway.
+ pending_input_buf_data_.memory = nullptr;
+ SetState(STATE_WAITING_FOR_KEY);
+ break;
+
+ case MEDIA_CODEC_OK:
+ input_data.completion_cb.Run(DecodeStatus::OK);
+ break;
+
+ default:
+ NOTREACHED() << "Unknown Queue(Secure)InputBuffer status " << status;
+ input_data.completion_cb.Run(DecodeStatus::DECODE_ERROR);
+ SetState(STATE_ERROR);
+ break;
+ }
+}
+
+bool MediaCodecLoop::ProcessOneOutputBuffer() {
+ DVLOG(2) << __FUNCTION__;
+
+ MediaCodecStatus status;
+ OutputBuffer out;
+ bool did_work = false;
+
+ // TODO(liberato): When merging AVDA, we will also have to ask the client if
+ // it can accept another output buffer.
+
+ status = media_codec_->DequeueOutputBuffer(kNoWaitTimeout, &out.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";
+ if (!client_->OnOutputFormatChanged())
+ SetState(STATE_ERROR);
+ did_work = (state_ != STATE_ERROR);
+ break;
+
+ case MEDIA_CODEC_OK:
+ // We got the decoded frame.
+ if (out.is_eos) {
+ // 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.
+ DCHECK_EQ(state_, STATE_DRAINING);
+ SetState(STATE_DRAINED);
+
+ DCHECK_NE(out.index, kInvalidBufferIndex);
+ DCHECK(media_codec_);
+
+ media_codec_->ReleaseOutputBuffer(out.index, false);
+
+ // Run the EOS completion callback now, since we deferred it until
+ // the EOS was completely processed.
+ pending_eos_completion_cb_.Run(DecodeStatus::OK);
+ pending_eos_completion_cb_ = DecodeCB();
+
+ client_->OnDecodedEos(out);
+ } else {
+ if (!client_->OnDecodedFrame(out))
+ SetState(STATE_ERROR);
+ }
+
+ 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__
DaleCurtis 2016/06/13 23:16:13 DLOG(ERROR)
liberato (no reviews please) 2016/06/14 17:26:44 Done.
+ << ": 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;
+ }
+
+ return did_work;
+}
+
+void MediaCodecLoop::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_ > kIdleTimerTimeout)
+ should_be_running = false;
+ }
+
+ if (should_be_running && !io_timer_.IsRunning()) {
+ io_timer_.Start(FROM_HERE, kDecodePollDelay, this,
+ &MediaCodecLoop::DoIOTask);
+ } else if (!should_be_running && io_timer_.IsRunning()) {
+ io_timer_.Stop();
+ }
+}
+
+void MediaCodecLoop::SetState(State new_state) {
+ DVLOG(1) << __FUNCTION__ << ": " << AsString(state_) << "->"
+ << AsString(new_state);
+ if (state_ != new_state && state_ == STATE_ERROR)
+ client_->OnCodecLoopError();
+ state_ = new_state;
+}
+
+MediaCodecBridge* MediaCodecLoop::GetCodec() const {
+ return media_codec_.get();
+}
+
+#if defined(RETURN_STRING)
DaleCurtis 2016/06/13 23:16:13 Hmm, actually, is this even necessary? I.e. won't
liberato (no reviews please) 2016/06/14 17:26:44 yes.
+#error Unexpected macro collision.
+#endif
+
+// static
+const char* MediaCodecLoop::AsString(State state) {
+#define RETURN_STRING(x) \
+ case x: \
+ return #x;
+
+ switch (state) {
+ RETURN_STRING(STATE_READY);
+ RETURN_STRING(STATE_WAITING_FOR_KEY);
+ RETURN_STRING(STATE_DRAINING);
+ RETURN_STRING(STATE_DRAINED);
+ RETURN_STRING(STATE_ERROR);
+ }
+#undef RETURN_STRING
+
+ NOTREACHED() << "Unknown state " << state;
+ return nullptr;
+}
+
+} // namespace media

Powered by Google App Engine
This is Rietveld 408576698