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

Unified Diff: media/gpu/android/media_codec_video_decoder.cc

Issue 2549643002: media: Add MediaCodecVideoDecoder (as a copy of AVDA initially) (Closed)
Patch Set: update copyright Created 4 years 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
« no previous file with comments | « media/gpu/android/media_codec_video_decoder.h ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: media/gpu/android/media_codec_video_decoder.cc
diff --git a/media/gpu/android/media_codec_video_decoder.cc b/media/gpu/android/media_codec_video_decoder.cc
new file mode 100644
index 0000000000000000000000000000000000000000..0be3c3e498f244bb86d6ded8b478420fe2f3aa19
--- /dev/null
+++ b/media/gpu/android/media_codec_video_decoder.cc
@@ -0,0 +1,1508 @@
+// Copyright (c) 2013 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/gpu/android/media_codec_video_decoder.h"
+
+#include <stddef.h>
+
+#include <memory>
+
+#include "base/android/build_info.h"
+#include "base/auto_reset.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback_helpers.h"
+#include "base/command_line.h"
+#include "base/lazy_instance.h"
+#include "base/logging.h"
+#include "base/message_loop/message_loop.h"
+#include "base/metrics/histogram.h"
+#include "base/sys_info.h"
+#include "base/task_runner_util.h"
+#include "base/threading/thread.h"
+#include "base/threading/thread_checker.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/trace_event/trace_event.h"
+#include "gpu/command_buffer/service/gles2_cmd_decoder.h"
+#include "gpu/command_buffer/service/mailbox_manager.h"
+#include "gpu/ipc/service/gpu_channel.h"
+#include "media/base/android/media_codec_bridge.h"
+#include "media/base/android/media_codec_util.h"
+#include "media/base/bind_to_current_loop.h"
+#include "media/base/bitstream_buffer.h"
+#include "media/base/limits.h"
+#include "media/base/media.h"
+#include "media/base/timestamp_constants.h"
+#include "media/base/video_decoder_config.h"
+#include "media/gpu/avda_picture_buffer_manager.h"
+#include "media/gpu/shared_memory_region.h"
+#include "media/video/picture.h"
+#include "ui/gl/android/scoped_java_surface.h"
+#include "ui/gl/android/surface_texture.h"
+#include "ui/gl/gl_bindings.h"
+
+#if defined(ENABLE_MOJO_MEDIA_IN_GPU_PROCESS)
+#include "media/mojo/services/mojo_cdm_service.h"
+#endif
+
+#define NOTIFY_ERROR(error_code, error_message) \
+ do { \
+ DLOG(ERROR) << error_message; \
+ NotifyError(VideoDecodeAccelerator::error_code); \
+ } while (0)
+
+namespace media {
+
+namespace {
+
+enum { kNumPictureBuffers = limits::kMaxVideoFrames + 1 };
+
+// Max number of bitstreams notified to the client with
+// NotifyEndOfBitstreamBuffer() before getting output from the bitstream.
+enum { kMaxBitstreamsNotifiedInAdvance = 32 };
+
+// MediaCodec is only guaranteed to support baseline, but some devices may
+// support others. Advertise support for all H264 profiles and let the
+// MediaCodec fail when decoding if it's not actually supported. It's assumed
+// that consumers won't have software fallback for H264 on Android anyway.
+constexpr VideoCodecProfile kSupportedH264Profiles[] = {
+ H264PROFILE_BASELINE,
+ H264PROFILE_MAIN,
+ H264PROFILE_EXTENDED,
+ H264PROFILE_HIGH,
+ H264PROFILE_HIGH10PROFILE,
+ H264PROFILE_HIGH422PROFILE,
+ H264PROFILE_HIGH444PREDICTIVEPROFILE,
+ H264PROFILE_SCALABLEBASELINE,
+ H264PROFILE_SCALABLEHIGH,
+ H264PROFILE_STEREOHIGH,
+ H264PROFILE_MULTIVIEWHIGH};
+
+#if BUILDFLAG(ENABLE_HEVC_DEMUXING)
+constexpr VideoCodecProfile kSupportedHevcProfiles[] = {HEVCPROFILE_MAIN,
+ HEVCPROFILE_MAIN10};
+#endif
+
+// Because MediaCodec is thread-hostile (must be poked on a single thread) and
+// has no callback mechanism (b/11990118), we must drive it by polling for
+// complete frames (and available input buffers, when the codec is fully
+// saturated). This function defines the polling delay. The value used is an
+// arbitrary choice that trades off CPU utilization (spinning) against latency.
+// Mirrors android_video_encode_accelerator.cc:EncodePollDelay().
+//
+// An alternative to this polling scheme could be to dedicate a new thread
+// (instead of using the ChildThread) to run the MediaCodec, and make that
+// thread use the timeout-based flavor of MediaCodec's dequeue methods when it
+// believes the codec should complete "soon" (e.g. waiting for an input
+// buffer, or waiting for a picture when it knows enough complete input
+// pictures have been fed to saturate any internal buffering). This is
+// speculative and it's unclear that this would be a win (nor that there's a
+// reasonably device-agnostic way to fill in the "believes" above).
+constexpr base::TimeDelta DecodePollDelay =
+ base::TimeDelta::FromMilliseconds(10);
+
+constexpr base::TimeDelta NoWaitTimeOut = base::TimeDelta::FromMicroseconds(0);
+
+constexpr base::TimeDelta IdleTimerTimeOut = base::TimeDelta::FromSeconds(1);
+
+// On low end devices (< KitKat is always low-end due to buggy MediaCodec),
+// defer the surface creation until the codec is actually used if we know no
+// software fallback exists.
+bool ShouldDeferSurfaceCreation(int surface_id, VideoCodec codec) {
+ return surface_id == SurfaceManager::kNoSurfaceID && codec == kCodecH264 &&
+ AVDACodecAllocator::Instance()->IsAnyRegisteredAVDA() &&
+ (base::android::BuildInfo::GetInstance()->sdk_int() <= 18 ||
+ base::SysInfo::IsLowEndDevice());
+}
+
+} // namespace
+
+// MCVDManager manages shared resources for a number of MCVD instances.
+// Its responsibilities include:
+// - Starting and stopping a shared "construction" thread for instantiating and
+// releasing MediaCodecs.
+// - Detecting when a task has hung on the construction thread so MCVDs can
+// stop using it.
+// - Running a RepeatingTimer so that MCVDs can get a regular callback to
+// DoIOTask().
+// - Tracking the allocation of surfaces to MCVDs and delivering callbacks when
+// surfaces are released.
+class MCVDManager {
+ public:
+ // Request periodic callback of |mcvd|->DoIOTask(). Does nothing if the
+ // instance is already registered and the timer started. The first request
+ // will start the repeating timer on an interval of DecodePollDelay.
+ void StartTimer(MediaCodecVideoDecoder* mcvd) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ timer_mcvd_instances_.insert(mcvd);
+
+ // If the timer is running, StopTimer() might have been called earlier, if
+ // so remove the instance from the pending erasures.
+ if (timer_running_)
+ pending_erase_.erase(mcvd);
+
+ if (io_timer_.IsRunning())
+ return;
+ io_timer_.Start(FROM_HERE, DecodePollDelay, this, &MCVDManager::RunTimer);
+ }
+
+ // Stop callbacks to |mcvd|->DoIOTask(). Does nothing if the instance is not
+ // registered. If there are no instances left, the repeating timer will be
+ // stopped.
+ void StopTimer(MediaCodecVideoDecoder* mcvd) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // If the timer is running, defer erasures to avoid iterator invalidation.
+ if (timer_running_) {
+ pending_erase_.insert(mcvd);
+ return;
+ }
+
+ timer_mcvd_instances_.erase(mcvd);
+ if (timer_mcvd_instances_.empty())
+ io_timer_.Stop();
+ }
+
+ private:
+ friend struct base::DefaultLazyInstanceTraits<MCVDManager>;
+
+ MCVDManager() {}
+ ~MCVDManager() { NOTREACHED(); }
+
+ void RunTimer() {
+ {
+ // Call out to all MCVD instances, some of which may attempt to remove
+ // themselves from the list during this operation; those removals will be
+ // deferred until after all iterations are complete.
+ base::AutoReset<bool> scoper(&timer_running_, true);
+ for (auto* mcvd : timer_mcvd_instances_)
+ mcvd->DoIOTask(false);
+ }
+
+ // Take care of any deferred erasures.
+ for (auto* mcvd : pending_erase_)
+ StopTimer(mcvd);
+ pending_erase_.clear();
+
+ // TODO(dalecurtis): We may want to consider chunking this if task execution
+ // takes too long for the combined timer.
+ }
+
+ // All MCVD instances that would like us to poll DoIOTask.
+ std::set<MediaCodecVideoDecoder*> timer_mcvd_instances_;
+
+ // Since we can't delete while iterating when using a set, defer erasure until
+ // after iteration complete.
+ bool timer_running_ = false;
+ std::set<MediaCodecVideoDecoder*> pending_erase_;
+
+ // Repeating timer responsible for draining pending IO to the codecs.
+ base::RepeatingTimer io_timer_;
+
+ base::ThreadChecker thread_checker_;
+
+ DISALLOW_COPY_AND_ASSIGN(MCVDManager);
+};
+
+static base::LazyInstance<MCVDManager>::Leaky g_mcvd_manager =
+ LAZY_INSTANCE_INITIALIZER;
+
+MediaCodecVideoDecoder::BitstreamRecord::BitstreamRecord(
+ const BitstreamBuffer& bitstream_buffer)
+ : buffer(bitstream_buffer) {
+ if (buffer.id() != -1)
+ memory.reset(new SharedMemoryRegion(buffer, true));
+}
+
+MediaCodecVideoDecoder::BitstreamRecord::BitstreamRecord(
+ BitstreamRecord&& other)
+ : buffer(std::move(other.buffer)), memory(std::move(other.memory)) {}
+
+MediaCodecVideoDecoder::BitstreamRecord::~BitstreamRecord() {}
+
+MediaCodecVideoDecoder::MediaCodecVideoDecoder(
+ const MakeGLContextCurrentCallback& make_context_current_cb,
+ const GetGLES2DecoderCallback& get_gles2_decoder_cb)
+ : client_(NULL),
+ make_context_current_cb_(make_context_current_cb),
+ get_gles2_decoder_cb_(get_gles2_decoder_cb),
+ state_(NO_ERROR),
+ picturebuffers_requested_(false),
+ picture_buffer_manager_(get_gles2_decoder_cb),
+ drain_type_(DRAIN_TYPE_NONE),
+ media_drm_bridge_cdm_context_(nullptr),
+ cdm_registration_id_(0),
+ pending_input_buf_index_(-1),
+ deferred_initialization_pending_(false),
+ codec_needs_reset_(false),
+ defer_surface_creation_(false),
+ weak_this_factory_(this) {}
+
+MediaCodecVideoDecoder::~MediaCodecVideoDecoder() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ g_mcvd_manager.Get().StopTimer(this);
+ AVDACodecAllocator::Instance()->StopThread(this);
+
+#if defined(ENABLE_MOJO_MEDIA_IN_GPU_PROCESS)
+ if (!media_drm_bridge_cdm_context_)
+ return;
+
+ DCHECK(cdm_registration_id_);
+
+ // Cancel previously registered callback (if any).
+ media_drm_bridge_cdm_context_->SetMediaCryptoReadyCB(
+ MediaDrmBridgeCdmContext::MediaCryptoReadyCB());
+
+ media_drm_bridge_cdm_context_->UnregisterPlayer(cdm_registration_id_);
+#endif // defined(ENABLE_MOJO_MEDIA_IN_GPU_PROCESS)
+}
+
+bool MediaCodecVideoDecoder::Initialize(const Config& config, Client* client) {
+ DVLOG(1) << __FUNCTION__ << ": " << config.AsHumanReadableString();
+ TRACE_EVENT0("media", "MCVD::Initialize");
+ DCHECK(!media_codec_);
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (make_context_current_cb_.is_null() || get_gles2_decoder_cb_.is_null()) {
+ DLOG(ERROR) << "GL callbacks are required for this VDA";
+ return false;
+ }
+
+ if (config.output_mode != Config::OutputMode::ALLOCATE) {
+ DLOG(ERROR) << "Only ALLOCATE OutputMode is supported by this VDA";
+ return false;
+ }
+
+ DCHECK(client);
+ client_ = client;
+ config_ = config;
+ codec_config_ = new CodecConfig();
+ codec_config_->codec_ = VideoCodecProfileToVideoCodec(config.profile);
+ codec_config_->initial_expected_coded_size_ =
+ config.initial_expected_coded_size;
+
+ if (codec_config_->codec_ != kCodecVP8 &&
+ codec_config_->codec_ != kCodecVP9 &&
+#if BUILDFLAG(ENABLE_HEVC_DEMUXING)
+ codec_config_->codec_ != kCodecHEVC &&
+#endif
+ codec_config_->codec_ != kCodecH264) {
+ DLOG(ERROR) << "Unsupported profile: " << config.profile;
+ return false;
+ }
+
+ if (codec_config_->codec_ == kCodecH264) {
+ codec_config_->csd0_ = config.sps;
+ codec_config_->csd1_ = config.pps;
+ }
+
+ // Only use MediaCodec for VP8/9 if it's likely backed by hardware
+ // or if the stream is encrypted.
+ if (IsMediaCodecSoftwareDecodingForbidden() &&
+ VideoCodecBridge::IsKnownUnaccelerated(codec_config_->codec_,
+ MEDIA_CODEC_DECODER)) {
+ DVLOG(1) << "Initialization failed: "
+ << (codec_config_->codec_ == kCodecVP8 ? "vp8" : "vp9")
+ << " is not hardware accelerated";
+ return false;
+ }
+
+ auto gles_decoder = get_gles2_decoder_cb_.Run();
+ if (!gles_decoder) {
+ DLOG(ERROR) << "Failed to get gles2 decoder instance.";
+ return false;
+ }
+
+ // SetSurface() can't be called before Initialize(), so we pick up our first
+ // surface ID from the codec configuration.
+ DCHECK(!pending_surface_id_);
+
+ // If we're low on resources, we may decide to defer creation of the surface
+ // until the codec is actually used.
+ if (ShouldDeferSurfaceCreation(config_.surface_id, codec_config_->codec_)) {
+ DCHECK(!deferred_initialization_pending_);
+ // We should never be here if a SurfaceView is required.
+ DCHECK_EQ(config_.surface_id, SurfaceManager::kNoSurfaceID);
+ defer_surface_creation_ = true;
+ NotifyInitializationComplete(true);
+ return true;
+ }
+
+ // We signaled that we support deferred initialization, so see if the client
+ // does also.
+ deferred_initialization_pending_ = config.is_deferred_initialization_allowed;
+ if (config_.is_encrypted && !deferred_initialization_pending_) {
+ DLOG(ERROR) << "Deferred initialization must be used for encrypted streams";
+ return false;
+ }
+
+ if (AVDACodecAllocator::Instance()->AllocateSurface(this,
+ config_.surface_id)) {
+ // We now own the surface, so finish initialization.
+ return InitializePictureBufferManager();
+ }
+
+ // We have to wait for some other MCVD instance to free up the surface.
+ // OnSurfaceAvailable will be called when it's available.
+ return true;
+}
+
+void MediaCodecVideoDecoder::OnSurfaceAvailable(bool success) {
+ DCHECK(deferred_initialization_pending_);
+ DCHECK(!defer_surface_creation_);
+
+ if (!success || !InitializePictureBufferManager()) {
+ NotifyInitializationComplete(false);
+ deferred_initialization_pending_ = false;
+ }
+}
+
+bool MediaCodecVideoDecoder::InitializePictureBufferManager() {
+ if (!make_context_current_cb_.Run()) {
+ LOG(ERROR) << "Failed to make this decoder's GL context current.";
+ return false;
+ }
+
+ codec_config_->surface_ =
+ picture_buffer_manager_.Initialize(config_.surface_id);
+ if (codec_config_->surface_.IsEmpty())
+ return false;
+
+ if (!AVDACodecAllocator::Instance()->StartThread(this))
+ return false;
+
+ // If we are encrypted, then we aren't able to create the codec yet.
+ if (config_.is_encrypted) {
+ InitializeCdm();
+ return true;
+ }
+
+ if (deferred_initialization_pending_ || defer_surface_creation_) {
+ defer_surface_creation_ = false;
+ ConfigureMediaCodecAsynchronously();
+ return true;
+ }
+
+ // If the client doesn't support deferred initialization (WebRTC), then we
+ // should complete it now and return a meaningful result. Note that it would
+ // be nice if we didn't have to worry about starting codec configuration at
+ // all (::Initialize or the wrapper can do it), but then they have to remember
+ // not to start codec config if we have to wait for the cdm. It's somewhat
+ // clearer for us to handle both cases.
+ return ConfigureMediaCodecSynchronously();
+}
+
+void MediaCodecVideoDecoder::DoIOTask(bool start_timer) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ TRACE_EVENT0("media", "MCVD::DoIOTask");
+ if (state_ == ERROR || state_ == WAITING_FOR_CODEC ||
+ state_ == SURFACE_DESTROYED) {
+ return;
+ }
+
+ picture_buffer_manager_.MaybeRenderEarly();
+ bool did_work = false, did_input = false, did_output = false;
+ do {
+ did_input = QueueInput();
+ did_output = DequeueOutput();
+ if (did_input || did_output)
+ did_work = true;
+ } while (did_input || did_output);
+
+ ManageTimer(did_work || start_timer);
+}
+
+bool MediaCodecVideoDecoder::QueueInput() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ TRACE_EVENT0("media", "MCVD::QueueInput");
+ if (state_ == ERROR || state_ == WAITING_FOR_CODEC ||
+ state_ == WAITING_FOR_KEY) {
+ return false;
+ }
+ if (bitstreams_notified_in_advance_.size() > kMaxBitstreamsNotifiedInAdvance)
+ return false;
+ if (pending_bitstream_records_.empty())
+ return false;
+
+ 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 this buffer back to the pool of
+ // available input buffers. We have to reuse it in QueueSecureInputBuffer().
+ if (input_buf_index == -1) {
+ MediaCodecStatus status =
+ media_codec_->DequeueInputBuffer(NoWaitTimeOut, &input_buf_index);
+ switch (status) {
+ case MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER:
+ return false;
+ case MEDIA_CODEC_ERROR:
+ NOTIFY_ERROR(PLATFORM_FAILURE, "DequeueInputBuffer failed");
+ return false;
+ case MEDIA_CODEC_OK:
+ break;
+ default:
+ NOTREACHED();
+ return false;
+ }
+ }
+
+ DCHECK_NE(input_buf_index, -1);
+
+ BitstreamBuffer bitstream_buffer = pending_bitstream_records_.front().buffer;
+
+ if (bitstream_buffer.id() == -1) {
+ pending_bitstream_records_.pop();
+ TRACE_COUNTER1("media", "MCVD::PendingBitstreamBufferCount",
+ pending_bitstream_records_.size());
+
+ media_codec_->QueueEOS(input_buf_index);
+ return true;
+ }
+
+ std::unique_ptr<SharedMemoryRegion> shm;
+
+ if (pending_input_buf_index_ == -1) {
+ // When |pending_input_buf_index_| is not -1, the buffer is already dequeued
+ // from MediaCodec, filled with data and bitstream_buffer.handle() is
+ // closed.
+ shm = std::move(pending_bitstream_records_.front().memory);
+
+ if (!shm->Map()) {
+ NOTIFY_ERROR(UNREADABLE_INPUT, "SharedMemoryRegion::Map() failed");
+ return false;
+ }
+ }
+
+ const base::TimeDelta presentation_timestamp =
+ bitstream_buffer.presentation_timestamp();
+ DCHECK(presentation_timestamp != kNoTimestamp)
+ << "Bitstream buffers must have valid presentation timestamps";
+
+ // There may already be a bitstream buffer with this timestamp, e.g., VP9 alt
+ // ref frames, but it's OK to overwrite it because we only expect a single
+ // output frame to have that timestamp. MCVD clients only use the bitstream
+ // buffer id in the returned Pictures to map a bitstream buffer back to a
+ // timestamp on their side, so either one of the bitstream buffer ids will
+ // result in them finding the right timestamp.
+ bitstream_buffers_in_decoder_[presentation_timestamp] = bitstream_buffer.id();
+
+ // Notice that |memory| will be null if we repeatedly enqueue the same buffer,
+ // this happens after MEDIA_CODEC_NO_KEY.
+ const uint8_t* memory =
+ shm ? static_cast<const uint8_t*>(shm->memory()) : nullptr;
+ const std::string& key_id = bitstream_buffer.key_id();
+ const std::string& iv = bitstream_buffer.iv();
+ const std::vector<SubsampleEntry>& subsamples = bitstream_buffer.subsamples();
+
+ MediaCodecStatus status;
+ if (key_id.empty() || iv.empty()) {
+ status = media_codec_->QueueInputBuffer(input_buf_index, memory,
+ bitstream_buffer.size(),
+ presentation_timestamp);
+ } else {
+ status = media_codec_->QueueSecureInputBuffer(
+ input_buf_index, memory, bitstream_buffer.size(), key_id, iv,
+ subsamples, presentation_timestamp);
+ }
+
+ DVLOG(2) << __FUNCTION__
+ << ": Queue(Secure)InputBuffer: pts:" << presentation_timestamp
+ << " status:" << status;
+
+ if (status == 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: NO_KEY";
+ pending_input_buf_index_ = input_buf_index;
+ state_ = WAITING_FOR_KEY;
+ return false;
+ }
+
+ pending_input_buf_index_ = -1;
+ pending_bitstream_records_.pop();
+ TRACE_COUNTER1("media", "MCVD::PendingBitstreamBufferCount",
+ pending_bitstream_records_.size());
+ // We should call NotifyEndOfBitstreamBuffer(), when no more decoded output
+ // will be returned from the bitstream buffer. However, MediaCodec API is
+ // not enough to guarantee it.
+ // So, here, we calls NotifyEndOfBitstreamBuffer() in advance in order to
+ // keep getting more bitstreams from the client, and throttle them by using
+ // |bitstreams_notified_in_advance_|.
+ // TODO(dwkang): check if there is a way to remove this workaround.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&MediaCodecVideoDecoder::NotifyEndOfBitstreamBuffer,
+ weak_this_factory_.GetWeakPtr(), bitstream_buffer.id()));
+ bitstreams_notified_in_advance_.push_back(bitstream_buffer.id());
+
+ if (status != MEDIA_CODEC_OK) {
+ NOTIFY_ERROR(PLATFORM_FAILURE, "QueueInputBuffer failed:" << status);
+ return false;
+ }
+
+ return true;
+}
+
+bool MediaCodecVideoDecoder::DequeueOutput() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ TRACE_EVENT0("media", "MCVD::DequeueOutput");
+ if (state_ == ERROR || state_ == WAITING_FOR_CODEC)
+ return false;
+ // If we're draining for reset or destroy, then we don't need picture buffers
+ // since we won't send any decoded frames anyway. There might not be any,
+ // since the pipeline might not be sending them back and / or they don't
+ // exist anymore. From the pipeline's point of view, for Destroy at least,
+ // the VDA is already gone.
+ if (picturebuffers_requested_ && output_picture_buffers_.empty() &&
+ !IsDrainingForResetOrDestroy()) {
+ return false;
+ }
+ if (!output_picture_buffers_.empty() && free_picture_ids_.empty() &&
+ !IsDrainingForResetOrDestroy()) {
+ // Don't have any picture buffer to send. Need to wait.
+ return false;
+ }
+
+ // If we're waiting to switch surfaces pause output release until we have all
+ // picture buffers returned. This is so we can ensure the right flags are set
+ // on the picture buffers returned to the client.
+ if (pending_surface_id_) {
+ if (picture_buffer_manager_.HasUnrenderedPictures())
+ return false;
+ if (!UpdateSurface())
+ return false;
+ }
+
+ bool eos = false;
+ base::TimeDelta presentation_timestamp;
+ int32_t buf_index = 0;
+ do {
+ size_t offset = 0;
+ size_t size = 0;
+
+ TRACE_EVENT_BEGIN0("media", "MCVD::DequeueOutput");
+ MediaCodecStatus status = media_codec_->DequeueOutputBuffer(
+ NoWaitTimeOut, &buf_index, &offset, &size, &presentation_timestamp,
+ &eos, NULL);
+ TRACE_EVENT_END2("media", "MCVD::DequeueOutput", "status", status,
+ "presentation_timestamp (ms)",
+ presentation_timestamp.InMilliseconds());
+
+ switch (status) {
+ case MEDIA_CODEC_ERROR:
+ // Do not post an error if we are draining for reset and destroy.
+ // Instead, run the drain completion task.
+ if (IsDrainingForResetOrDestroy()) {
+ DVLOG(1) << __FUNCTION__ << ": error while codec draining";
+ state_ = ERROR;
+ OnDrainCompleted();
+ } else {
+ NOTIFY_ERROR(PLATFORM_FAILURE, "DequeueOutputBuffer failed.");
+ }
+ return false;
+
+ case MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER:
+ return false;
+
+ case MEDIA_CODEC_OUTPUT_FORMAT_CHANGED: {
+ // An OUTPUT_FORMAT_CHANGED is not reported after flush() if the frame
+ // size does not change. Therefore we have to keep track on the format
+ // even if draining, unless we are draining for destroy.
+ if (drain_type_ == DRAIN_FOR_DESTROY)
+ return true; // ignore
+
+ if (media_codec_->GetOutputSize(&size_) != MEDIA_CODEC_OK) {
+ NOTIFY_ERROR(PLATFORM_FAILURE, "GetOutputSize failed.");
+ return false;
+ }
+
+ DVLOG(3) << __FUNCTION__
+ << " OUTPUT_FORMAT_CHANGED, new size: " << size_.ToString();
+
+ // Don't request picture buffers if we already have some. This avoids
+ // having to dismiss the existing buffers which may actively reference
+ // decoded images. Breaking their connection to the decoded image will
+ // cause rendering of black frames. Instead, we let the existing
+ // PictureBuffers live on and we simply update their size the next time
+ // they're attached to an image of the new resolution. See the
+ // size update in |SendDecodedFrameToClient| and https://crbug/587994.
+ if (output_picture_buffers_.empty() && !picturebuffers_requested_) {
+ picturebuffers_requested_ = true;
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&MediaCodecVideoDecoder::RequestPictureBuffers,
+ weak_this_factory_.GetWeakPtr()));
+ return false;
+ }
+
+ return true;
+ }
+
+ case MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED:
+ break;
+
+ case MEDIA_CODEC_OK:
+ DCHECK_GE(buf_index, 0);
+ DVLOG(3) << __FUNCTION__ << ": pts:" << presentation_timestamp
+ << " buf_index:" << buf_index << " offset:" << offset
+ << " size:" << size << " eos:" << eos;
+ break;
+
+ default:
+ NOTREACHED();
+ break;
+ }
+ } while (buf_index < 0);
+
+ if (eos) {
+ OnDrainCompleted();
+ return false;
+ }
+
+ if (IsDrainingForResetOrDestroy()) {
+ media_codec_->ReleaseOutputBuffer(buf_index, false);
+ return true;
+ }
+
+ if (!picturebuffers_requested_) {
+ // In 0.01% of playbacks MediaCodec returns a frame before FORMAT_CHANGED.
+ // Occurs on JB and M. (See the Media.MCVD.MissingFormatChanged histogram.)
+ media_codec_->ReleaseOutputBuffer(buf_index, false);
+ NOTIFY_ERROR(PLATFORM_FAILURE, "Dequeued buffers before FORMAT_CHANGED.");
+ return false;
+ }
+
+ // Get the bitstream buffer id from the timestamp.
+ auto it = bitstream_buffers_in_decoder_.find(presentation_timestamp);
+
+ if (it != bitstream_buffers_in_decoder_.end()) {
+ const int32_t bitstream_buffer_id = it->second;
+ bitstream_buffers_in_decoder_.erase(bitstream_buffers_in_decoder_.begin(),
+ ++it);
+ SendDecodedFrameToClient(buf_index, bitstream_buffer_id);
+
+ // Removes ids former or equal than the id from decoder. Note that
+ // |bitstreams_notified_in_advance_| does not mean bitstream ids in decoder
+ // because of frame reordering issue. We just maintain this roughly and use
+ // it for throttling.
+ for (auto bitstream_it = bitstreams_notified_in_advance_.begin();
+ bitstream_it != bitstreams_notified_in_advance_.end();
+ ++bitstream_it) {
+ if (*bitstream_it == bitstream_buffer_id) {
+ bitstreams_notified_in_advance_.erase(
+ bitstreams_notified_in_advance_.begin(), ++bitstream_it);
+ break;
+ }
+ }
+ } else {
+ // Normally we assume that the decoder makes at most one output frame for
+ // each distinct input timestamp. However MediaCodecBridge uses timestamp
+ // correction and provides a non-decreasing timestamp sequence, which might
+ // result in timestamp duplicates. Discard the frame if we cannot get the
+ // corresponding buffer id.
+ DVLOG(3) << __FUNCTION__ << ": Releasing buffer with unexpected PTS: "
+ << presentation_timestamp;
+ media_codec_->ReleaseOutputBuffer(buf_index, false);
+ }
+
+ // We got a decoded frame, so try for another.
+ return true;
+}
+
+void MediaCodecVideoDecoder::SendDecodedFrameToClient(
+ int32_t codec_buffer_index,
+ int32_t bitstream_id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_NE(bitstream_id, -1);
+ DCHECK(!free_picture_ids_.empty());
+ TRACE_EVENT0("media", "MCVD::SendDecodedFrameToClient");
+
+ if (!make_context_current_cb_.Run()) {
+ NOTIFY_ERROR(PLATFORM_FAILURE, "Failed to make the GL context current.");
+ return;
+ }
+
+ int32_t picture_buffer_id = free_picture_ids_.front();
+ free_picture_ids_.pop();
+ TRACE_COUNTER1("media", "MCVD::FreePictureIds", free_picture_ids_.size());
+
+ const auto it = output_picture_buffers_.find(picture_buffer_id);
+ if (it == output_picture_buffers_.end()) {
+ NOTIFY_ERROR(PLATFORM_FAILURE,
+ "Can't find PictureBuffer id: " << picture_buffer_id);
+ return;
+ }
+
+ PictureBuffer& picture_buffer = it->second;
+ const bool size_changed = picture_buffer.size() != size_;
+ if (size_changed)
+ picture_buffer.set_size(size_);
+
+ const bool allow_overlay = picture_buffer_manager_.ArePicturesOverlayable();
+ UMA_HISTOGRAM_BOOLEAN("Media.AVDA.FrameSentAsOverlay", allow_overlay);
+ // TODO(hubbe): Insert the correct color space. http://crbug.com/647725
+ Picture picture(picture_buffer_id, bitstream_id, gfx::Rect(size_),
+ gfx::ColorSpace(), allow_overlay);
+ picture.set_size_changed(size_changed);
+
+ // Notify picture ready before calling UseCodecBufferForPictureBuffer() since
+ // that process may be slow and shouldn't delay delivery of the frame to the
+ // renderer. The picture is only used on the same thread as this method is
+ // called, so it is safe to do this.
+ NotifyPictureReady(picture);
+
+ // Connect the PictureBuffer to the decoded frame.
+ if (!picture_buffer_manager_.UseCodecBufferForPictureBuffer(
+ codec_buffer_index, picture_buffer, size_)) {
+ NOTIFY_ERROR(PLATFORM_FAILURE,
+ "Failed to attach the codec buffer to a picture buffer.");
+ }
+}
+
+void MediaCodecVideoDecoder::Decode(const BitstreamBuffer& bitstream_buffer) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (defer_surface_creation_ && !InitializePictureBufferManager()) {
+ NOTIFY_ERROR(PLATFORM_FAILURE,
+ "Failed deferred surface and MediaCodec initialization.");
+ return;
+ }
+
+ // If we previously deferred a codec restart, take care of it now. This can
+ // happen on older devices where configuration changes require a codec reset.
+ if (codec_needs_reset_) {
+ DCHECK_EQ(drain_type_, DRAIN_TYPE_NONE);
+ ResetCodecState();
+ }
+
+ if (bitstream_buffer.id() >= 0 && bitstream_buffer.size() > 0) {
+ DecodeBuffer(bitstream_buffer);
+ return;
+ }
+
+ if (base::SharedMemory::IsHandleValid(bitstream_buffer.handle()))
+ base::SharedMemory::CloseHandle(bitstream_buffer.handle());
+
+ if (bitstream_buffer.id() < 0) {
+ NOTIFY_ERROR(INVALID_ARGUMENT,
+ "Invalid bistream_buffer, id: " << bitstream_buffer.id());
+ } else {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&MediaCodecVideoDecoder::NotifyEndOfBitstreamBuffer,
+ weak_this_factory_.GetWeakPtr(), bitstream_buffer.id()));
+ }
+}
+
+void MediaCodecVideoDecoder::DecodeBuffer(
+ const BitstreamBuffer& bitstream_buffer) {
+ pending_bitstream_records_.push(BitstreamRecord(bitstream_buffer));
+ TRACE_COUNTER1("media", "MCVD::PendingBitstreamBufferCount",
+ pending_bitstream_records_.size());
+
+ DoIOTask(true);
+}
+
+void MediaCodecVideoDecoder::RequestPictureBuffers() {
+ if (client_) {
+ // Allocate a picture buffer that is the actual frame size. Note that it
+ // will be an external texture anyway, so it doesn't allocate an image of
+ // that size. It's important to get the coded size right, so that
+ // VideoLayerImpl doesn't try to scale the texture when building the quad
+ // for it.
+ client_->ProvidePictureBuffers(kNumPictureBuffers, PIXEL_FORMAT_UNKNOWN, 1,
+ size_,
+ AVDAPictureBufferManager::kTextureTarget);
+ }
+}
+
+void MediaCodecVideoDecoder::AssignPictureBuffers(
+ const std::vector<PictureBuffer>& buffers) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(output_picture_buffers_.empty());
+ DCHECK(free_picture_ids_.empty());
+
+ if (buffers.size() < kNumPictureBuffers) {
+ NOTIFY_ERROR(INVALID_ARGUMENT, "Not enough picture buffers assigned.");
+ return;
+ }
+
+ const bool have_context = make_context_current_cb_.Run();
+ LOG_IF(WARNING, !have_context)
+ << "Failed to make GL context current for Assign, continuing.";
+
+ for (size_t i = 0; i < buffers.size(); ++i) {
+ DCHECK(buffers[i].size() == size_);
+ int32_t id = buffers[i].id();
+ output_picture_buffers_.insert(std::make_pair(id, buffers[i]));
+ free_picture_ids_.push(id);
+
+ picture_buffer_manager_.AssignPictureBuffer(buffers[i], size_,
+ have_context);
+ }
+ TRACE_COUNTER1("media", "MCVD::FreePictureIds", free_picture_ids_.size());
+ DoIOTask(true);
+}
+
+void MediaCodecVideoDecoder::ReusePictureBuffer(int32_t picture_buffer_id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ free_picture_ids_.push(picture_buffer_id);
+ TRACE_COUNTER1("media", "MCVD::FreePictureIds", free_picture_ids_.size());
+
+ auto it = output_picture_buffers_.find(picture_buffer_id);
+ if (it == output_picture_buffers_.end()) {
+ NOTIFY_ERROR(PLATFORM_FAILURE, "Can't find PictureBuffer id "
+ << picture_buffer_id);
+ return;
+ }
+
+ picture_buffer_manager_.ReusePictureBuffer(it->second);
+ DoIOTask(true);
+}
+
+void MediaCodecVideoDecoder::Flush() {
+ DVLOG(1) << __FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (state_ == SURFACE_DESTROYED || defer_surface_creation_)
+ NotifyFlushDone();
+ else
+ StartCodecDrain(DRAIN_FOR_FLUSH);
+}
+
+void MediaCodecVideoDecoder::ConfigureMediaCodecAsynchronously() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ DCHECK_NE(state_, WAITING_FOR_CODEC);
+ state_ = WAITING_FOR_CODEC;
+
+ if (media_codec_) {
+ AVDACodecAllocator::Instance()->ReleaseMediaCodec(
+ std::move(media_codec_), codec_config_->task_type_, config_.surface_id);
+ picture_buffer_manager_.CodecChanged(nullptr);
+ }
+
+ codec_config_->task_type_ =
+ AVDACodecAllocator::Instance()->TaskTypeForAllocation();
+ if (codec_config_->task_type_ == TaskType::FAILED_CODEC) {
+ // If there is no free thread, then just fail.
+ OnCodecConfigured(nullptr);
+ return;
+ }
+
+ // If autodetection is disallowed, fall back to Chrome's software decoders
+ // instead of using the software decoders provided by MediaCodec.
+ if (codec_config_->task_type_ == TaskType::SW_CODEC &&
+ IsMediaCodecSoftwareDecodingForbidden()) {
+ OnCodecConfigured(nullptr);
+ return;
+ }
+
+ AVDACodecAllocator::Instance()->CreateMediaCodecAsync(
+ weak_this_factory_.GetWeakPtr(), codec_config_);
+}
+
+bool MediaCodecVideoDecoder::ConfigureMediaCodecSynchronously() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(!media_codec_);
+ DCHECK_NE(state_, WAITING_FOR_CODEC);
+ state_ = WAITING_FOR_CODEC;
+
+ codec_config_->task_type_ =
+ AVDACodecAllocator::Instance()->TaskTypeForAllocation();
+ if (codec_config_->task_type_ == TaskType::FAILED_CODEC) {
+ OnCodecConfigured(nullptr);
+ return false;
+ }
+
+ std::unique_ptr<VideoCodecBridge> media_codec =
+ AVDACodecAllocator::Instance()->CreateMediaCodecSync(codec_config_);
+ OnCodecConfigured(std::move(media_codec));
+ return !!media_codec_;
+}
+
+void MediaCodecVideoDecoder::OnCodecConfigured(
+ std::unique_ptr<VideoCodecBridge> media_codec) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(state_ == WAITING_FOR_CODEC || state_ == SURFACE_DESTROYED);
+
+ // If we are supposed to notify that initialization is complete, then do so
+ // now. Otherwise, this is a reconfiguration.
+ if (deferred_initialization_pending_) {
+ // Losing the output surface is not considered an error state, so notify
+ // success. The client will destroy this soon.
+ NotifyInitializationComplete(state_ == SURFACE_DESTROYED ? true
+ : !!media_codec);
+ deferred_initialization_pending_ = false;
+ }
+
+ // If |state_| changed to SURFACE_DESTROYED while we were configuring a codec,
+ // then the codec is already invalid so we return early and drop it.
+ if (state_ == SURFACE_DESTROYED)
+ return;
+
+ DCHECK(!media_codec_);
+ media_codec_ = std::move(media_codec);
+ picture_buffer_manager_.CodecChanged(media_codec_.get());
+ if (!media_codec_) {
+ NOTIFY_ERROR(PLATFORM_FAILURE, "Failed to create MediaCodec");
+ return;
+ }
+
+ state_ = NO_ERROR;
+
+ ManageTimer(true);
+}
+
+void MediaCodecVideoDecoder::StartCodecDrain(DrainType drain_type) {
+ DVLOG(2) << __FUNCTION__ << " drain_type:" << drain_type;
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // We assume that DRAIN_FOR_FLUSH and DRAIN_FOR_RESET cannot come while
+ // another drain request is present, but DRAIN_FOR_DESTROY can.
+ DCHECK_NE(drain_type, DRAIN_TYPE_NONE);
+ DCHECK(drain_type_ == DRAIN_TYPE_NONE || drain_type == DRAIN_FOR_DESTROY)
+ << "Unexpected StartCodecDrain() with drain type " << drain_type
+ << " while already draining with drain type " << drain_type_;
+
+ const bool enqueue_eos = drain_type_ == DRAIN_TYPE_NONE;
+ drain_type_ = drain_type;
+
+ if (enqueue_eos)
+ DecodeBuffer(BitstreamBuffer(-1, base::SharedMemoryHandle(), 0));
+}
+
+bool MediaCodecVideoDecoder::IsDrainingForResetOrDestroy() const {
+ return drain_type_ == DRAIN_FOR_RESET || drain_type_ == DRAIN_FOR_DESTROY;
+}
+
+void MediaCodecVideoDecoder::OnDrainCompleted() {
+ DVLOG(2) << __FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // If we were waiting for an EOS, clear the state and reset the MediaCodec
+ // as normal.
+ //
+ // Some Android platforms seem to send an EOS buffer even when we're not
+ // expecting it. In this case, destroy and reset the codec but don't notify
+ // flush done since it violates the state machine. http://crbug.com/585959.
+
+ switch (drain_type_) {
+ case DRAIN_TYPE_NONE:
+ // Unexpected EOS.
+ state_ = ERROR;
+ ResetCodecState();
+ break;
+ case DRAIN_FOR_FLUSH:
+ ResetCodecState();
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(&MediaCodecVideoDecoder::NotifyFlushDone,
+ weak_this_factory_.GetWeakPtr()));
+ break;
+ case DRAIN_FOR_RESET:
+ ResetCodecState();
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(&MediaCodecVideoDecoder::NotifyResetDone,
+ weak_this_factory_.GetWeakPtr()));
+ break;
+ case DRAIN_FOR_DESTROY:
+ ResetCodecState();
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(&MediaCodecVideoDecoder::ActualDestroy,
+ weak_this_factory_.GetWeakPtr()));
+ break;
+ }
+ drain_type_ = DRAIN_TYPE_NONE;
+}
+
+void MediaCodecVideoDecoder::ResetCodecState() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // If there is already a reset in flight, then that counts. This can really
+ // only happen if somebody calls Reset.
+ // If the surface is destroyed there's nothing to do.
+ if (state_ == WAITING_FOR_CODEC || state_ == SURFACE_DESTROYED)
+ return;
+
+ bitstream_buffers_in_decoder_.clear();
+
+ if (pending_input_buf_index_ != -1) {
+ // The data for that index exists in the input buffer, but corresponding
+ // shm block been deleted. Check that it is safe to flush the codec, i.e.
+ // |pending_bitstream_records_| is empty.
+ // TODO(timav): keep shm block for that buffer and remove this restriction.
+ DCHECK(pending_bitstream_records_.empty());
+ pending_input_buf_index_ = -1;
+ }
+
+ const bool did_codec_error_happen = state_ == ERROR;
+ state_ = NO_ERROR;
+
+ // Don't reset the codec here if there's no error and we're only flushing;
+ // instead defer until the next decode call; this prevents us from unbacking
+ // frames that might be out for display at end of stream.
+ codec_needs_reset_ = false;
+ if (drain_type_ == DRAIN_FOR_FLUSH && !did_codec_error_happen) {
+ codec_needs_reset_ = true;
+ return;
+ }
+
+ // Flush the codec if possible, or create a new one if not.
+ if (!did_codec_error_happen &&
+ !MediaCodecUtil::CodecNeedsFlushWorkaround(media_codec_.get())) {
+ DVLOG(3) << __FUNCTION__ << " Flushing MediaCodec.";
+ media_codec_->Flush();
+ // Since we just flushed all the output buffers, make sure that nothing is
+ // using them.
+ picture_buffer_manager_.CodecChanged(media_codec_.get());
+ } else {
+ DVLOG(3) << __FUNCTION__
+ << " Deleting the MediaCodec and creating a new one.";
+ g_mcvd_manager.Get().StopTimer(this);
+ ConfigureMediaCodecAsynchronously();
+ }
+}
+
+void MediaCodecVideoDecoder::Reset() {
+ DVLOG(1) << __FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
+ TRACE_EVENT0("media", "MCVD::Reset");
+
+ if (defer_surface_creation_) {
+ DCHECK(!media_codec_);
+ DCHECK(pending_bitstream_records_.empty());
+ DCHECK_EQ(state_, NO_ERROR);
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(&MediaCodecVideoDecoder::NotifyResetDone,
+ weak_this_factory_.GetWeakPtr()));
+ return;
+ }
+
+ while (!pending_bitstream_records_.empty()) {
+ int32_t bitstream_buffer_id =
+ pending_bitstream_records_.front().buffer.id();
+ pending_bitstream_records_.pop();
+
+ if (bitstream_buffer_id != -1) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::Bind(&MediaCodecVideoDecoder::NotifyEndOfBitstreamBuffer,
+ weak_this_factory_.GetWeakPtr(), bitstream_buffer_id));
+ }
+ }
+ TRACE_COUNTER1("media", "MCVD::PendingBitstreamBufferCount", 0);
+ bitstreams_notified_in_advance_.clear();
+
+ picture_buffer_manager_.ReleaseCodecBuffers(output_picture_buffers_);
+
+ // Some VP8 files require complete MediaCodec drain before we can call
+ // MediaCodec.flush() or MediaCodec.reset(). http://crbug.com/598963.
+ if (media_codec_ && codec_config_->codec_ == kCodecVP8 &&
+ !bitstream_buffers_in_decoder_.empty()) {
+ // Postpone ResetCodecState() after the drain.
+ StartCodecDrain(DRAIN_FOR_RESET);
+ } else {
+ ResetCodecState();
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::Bind(&MediaCodecVideoDecoder::NotifyResetDone,
+ weak_this_factory_.GetWeakPtr()));
+ }
+}
+
+void MediaCodecVideoDecoder::SetSurface(int32_t surface_id) {
+ DVLOG(1) << __func__;
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (surface_id == config_.surface_id) {
+ pending_surface_id_.reset();
+ return;
+ }
+
+ // Surface changes never take effect immediately, they will be handled during
+ // DequeOutput() once we get to a good switch point or immediately during an
+ // OnSurfaceDestroyed() call.
+ pending_surface_id_ = surface_id;
+}
+
+void MediaCodecVideoDecoder::Destroy() {
+ DVLOG(1) << __FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ picture_buffer_manager_.Destroy(output_picture_buffers_);
+
+ client_ = nullptr;
+
+ // Some VP8 files require a complete MediaCodec drain before we can call
+ // MediaCodec.flush() or MediaCodec.release(). http://crbug.com/598963. In
+ // that case, postpone ActualDestroy() until after the drain.
+ if (media_codec_ && codec_config_->codec_ == kCodecVP8) {
+ // Clear |pending_bitstream_records_|.
+ while (!pending_bitstream_records_.empty())
+ pending_bitstream_records_.pop();
+
+ StartCodecDrain(DRAIN_FOR_DESTROY);
+ } else {
+ ActualDestroy();
+ }
+}
+
+void MediaCodecVideoDecoder::ActualDestroy() {
+ DVLOG(1) << __FUNCTION__;
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // Note that async codec construction might still be in progress. In that
+ // case, the codec will be deleted when it completes once we invalidate all
+ // our weak refs.
+ weak_this_factory_.InvalidateWeakPtrs();
+ g_mcvd_manager.Get().StopTimer(this);
+ if (media_codec_) {
+ AVDACodecAllocator::Instance()->ReleaseMediaCodec(
+ std::move(media_codec_), codec_config_->task_type_, config_.surface_id);
+ }
+
+ // We no longer care about |surface_id|, in case we did before. It's okay
+ // if we have no surface and/or weren't the owner or a waiter.
+ AVDACodecAllocator::Instance()->DeallocateSurface(this, config_.surface_id);
+
+ delete this;
+}
+
+bool MediaCodecVideoDecoder::TryToSetupDecodeOnSeparateThread(
+ const base::WeakPtr<Client>& decode_client,
+ const scoped_refptr<base::SingleThreadTaskRunner>& decode_task_runner) {
+ return false;
+}
+
+void MediaCodecVideoDecoder::OnSurfaceDestroyed() {
+ DVLOG(1) << __func__;
+ TRACE_EVENT0("media", "MCVD::OnSurfaceDestroyed");
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ // If the API is available avoid having to restart the decoder in order to
+ // leave fullscreen. If we don't clear the surface immediately during this
+ // callback, the MediaCodec will throw an error as the surface is destroyed.
+ if (base::android::BuildInfo::GetInstance()->sdk_int() >= 23) {
+ // Since we can't wait for a transition, we must invalidate all outstanding
+ // picture buffers to avoid putting the GL system in a broken state.
+ picture_buffer_manager_.ReleaseCodecBuffers(output_picture_buffers_);
+
+ // Switch away from the surface being destroyed to a surface texture.
+ DCHECK_NE(config_.surface_id, SurfaceManager::kNoSurfaceID);
+
+ // The leaving fullscreen notification may come in before this point.
+ if (pending_surface_id_)
+ DCHECK_EQ(pending_surface_id_.value(), SurfaceManager::kNoSurfaceID);
+
+ pending_surface_id_ = SurfaceManager::kNoSurfaceID;
+ UpdateSurface();
+ return;
+ }
+
+ // If we're currently asynchronously configuring a codec, it will be destroyed
+ // when configuration completes and it notices that |state_| has changed to
+ // SURFACE_DESTROYED.
+ state_ = SURFACE_DESTROYED;
+ if (media_codec_) {
+ AVDACodecAllocator::Instance()->ReleaseMediaCodec(
+ std::move(media_codec_), codec_config_->task_type_, config_.surface_id);
+ picture_buffer_manager_.CodecChanged(nullptr);
+ }
+
+ // If we're draining, signal completion now because the drain can no longer
+ // proceed.
+ if (drain_type_ != DRAIN_TYPE_NONE)
+ OnDrainCompleted();
+}
+
+void MediaCodecVideoDecoder::InitializeCdm() {
+ DVLOG(2) << __FUNCTION__ << ": " << config_.cdm_id;
+
+#if !defined(ENABLE_MOJO_MEDIA_IN_GPU_PROCESS)
+ NOTIMPLEMENTED();
+ NotifyInitializationComplete(false);
+#else
+ // Store the CDM to hold a reference to it.
+ cdm_for_reference_holding_only_ =
+ MojoCdmService::LegacyGetCdm(config_.cdm_id);
+ DCHECK(cdm_for_reference_holding_only_);
+
+ // On Android platform the CdmContext must be a MediaDrmBridgeCdmContext.
+ media_drm_bridge_cdm_context_ = static_cast<MediaDrmBridgeCdmContext*>(
+ cdm_for_reference_holding_only_->GetCdmContext());
+ DCHECK(media_drm_bridge_cdm_context_);
+
+ // 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_ = media_drm_bridge_cdm_context_->RegisterPlayer(
+ BindToCurrentLoop(base::Bind(&MediaCodecVideoDecoder::OnKeyAdded,
+ weak_this_factory_.GetWeakPtr())),
+ base::Bind(&base::DoNothing));
+
+ // Deferred initialization will continue in OnMediaCryptoReady().
+ media_drm_bridge_cdm_context_->SetMediaCryptoReadyCB(
+ BindToCurrentLoop(base::Bind(&MediaCodecVideoDecoder::OnMediaCryptoReady,
+ weak_this_factory_.GetWeakPtr())));
+#endif // !defined(ENABLE_MOJO_MEDIA_IN_GPU_PROCESS)
+}
+
+void MediaCodecVideoDecoder::OnMediaCryptoReady(
+ MediaDrmBridgeCdmContext::JavaObjectPtr media_crypto,
+ bool needs_protected_surface) {
+ DVLOG(1) << __FUNCTION__;
+
+ if (!media_crypto) {
+ LOG(ERROR) << "MediaCrypto is not available, can't play encrypted stream.";
+ cdm_for_reference_holding_only_ = nullptr;
+ media_drm_bridge_cdm_context_ = nullptr;
+ NotifyInitializationComplete(false);
+ 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_);
+
+ codec_config_->media_crypto_ = std::move(media_crypto);
+ codec_config_->needs_protected_surface_ = needs_protected_surface;
+
+ // After receiving |media_crypto_| we can configure MediaCodec.
+ ConfigureMediaCodecAsynchronously();
+}
+
+void MediaCodecVideoDecoder::OnKeyAdded() {
+ DVLOG(1) << __FUNCTION__;
+
+ if (state_ == WAITING_FOR_KEY)
+ state_ = NO_ERROR;
+
+ DoIOTask(true);
+}
+
+void MediaCodecVideoDecoder::NotifyInitializationComplete(bool success) {
+ if (client_)
+ client_->NotifyInitializationComplete(success);
+}
+
+void MediaCodecVideoDecoder::NotifyPictureReady(const Picture& picture) {
+ if (client_)
+ client_->PictureReady(picture);
+}
+
+void MediaCodecVideoDecoder::NotifyEndOfBitstreamBuffer(int input_buffer_id) {
+ if (client_)
+ client_->NotifyEndOfBitstreamBuffer(input_buffer_id);
+}
+
+void MediaCodecVideoDecoder::NotifyFlushDone() {
+ if (client_)
+ client_->NotifyFlushDone();
+}
+
+void MediaCodecVideoDecoder::NotifyResetDone() {
+ if (client_)
+ client_->NotifyResetDone();
+}
+
+void MediaCodecVideoDecoder::NotifyError(Error error) {
+ state_ = ERROR;
+ if (client_)
+ client_->NotifyError(error);
+}
+
+void MediaCodecVideoDecoder::ManageTimer(bool did_work) {
+ bool should_be_running = true;
+
+ base::TimeTicks now = base::TimeTicks::Now();
+ if (!did_work && !most_recent_work_.is_null()) {
+ // Make sure that we have done work recently enough, else stop the timer.
+ if (now - most_recent_work_ > IdleTimerTimeOut) {
+ most_recent_work_ = base::TimeTicks();
+ should_be_running = false;
+ }
+ } else {
+ most_recent_work_ = now;
+ }
+
+ if (should_be_running)
+ g_mcvd_manager.Get().StartTimer(this);
+ else
+ g_mcvd_manager.Get().StopTimer(this);
+}
+
+// static
+VideoDecodeAccelerator::Capabilities MediaCodecVideoDecoder::GetCapabilities(
+ const gpu::GpuPreferences& gpu_preferences) {
+ Capabilities capabilities;
+ SupportedProfiles& profiles = capabilities.supported_profiles;
+
+ if (MediaCodecUtil::IsVp8DecoderAvailable()) {
+ SupportedProfile profile;
+ profile.profile = VP8PROFILE_ANY;
+ // Since there is little to no power benefit below 360p, don't advertise
+ // support for it. Let libvpx decode it, and save a MediaCodec instance.
+ // Note that we allow it anyway for encrypted content, since we push a
+ // separate profile for that.
+ profile.min_resolution.SetSize(480, 360);
+ profile.max_resolution.SetSize(3840, 2160);
+ // If we know MediaCodec will just create a software codec, prefer our
+ // internal software decoder instead. It's more up to date and secured
+ // within the renderer sandbox. However if the content is encrypted, we
+ // must use MediaCodec anyways since MediaDrm offers no way to decrypt
+ // the buffers and let us use our internal software decoders.
+ profile.encrypted_only =
+ VideoCodecBridge::IsKnownUnaccelerated(kCodecVP8, MEDIA_CODEC_DECODER);
+ profiles.push_back(profile);
+
+ // Always allow encrypted content, even at low resolutions.
+ profile.min_resolution.SetSize(0, 0);
+ profile.encrypted_only = true;
+ profiles.push_back(profile);
+ }
+
+ if (MediaCodecUtil::IsVp9DecoderAvailable()) {
+ const VideoCodecProfile profile_types[] = {
+ VP9PROFILE_PROFILE0, VP9PROFILE_PROFILE1, VP9PROFILE_PROFILE2,
+ VP9PROFILE_PROFILE3, VIDEO_CODEC_PROFILE_UNKNOWN};
+ const bool is_known_unaccelerated =
+ VideoCodecBridge::IsKnownUnaccelerated(kCodecVP9, MEDIA_CODEC_DECODER);
+ for (int i = 0; profile_types[i] != VIDEO_CODEC_PROFILE_UNKNOWN; i++) {
+ SupportedProfile profile;
+ // Limit to 360p, like we do for vp8. See above.
+ profile.min_resolution.SetSize(480, 360);
+ profile.max_resolution.SetSize(3840, 2160);
+ // If we know MediaCodec will just create a software codec, prefer our
+ // internal software decoder instead. It's more up to date and secured
+ // within the renderer sandbox. However if the content is encrypted, we
+ // must use MediaCodec anyways since MediaDrm offers no way to decrypt
+ // the buffers and let us use our internal software decoders.
+ profile.encrypted_only = is_known_unaccelerated;
+ profile.profile = profile_types[i];
+ profiles.push_back(profile);
+
+ // Always allow encrypted content.
+ profile.min_resolution.SetSize(0, 0);
+ profile.encrypted_only = true;
+ profiles.push_back(profile);
+ }
+ }
+
+ for (const auto& supported_profile : kSupportedH264Profiles) {
+ SupportedProfile profile;
+ profile.profile = supported_profile;
+ profile.min_resolution.SetSize(0, 0);
+ // Advertise support for 4k and let the MediaCodec fail when decoding if it
+ // doesn't support the resolution. It's assumed that consumers won't have
+ // software fallback for H264 on Android anyway.
+ profile.max_resolution.SetSize(3840, 2160);
+ profiles.push_back(profile);
+ }
+
+ capabilities.flags =
+ VideoDecodeAccelerator::Capabilities::SUPPORTS_DEFERRED_INITIALIZATION;
+ capabilities.flags |=
+ VideoDecodeAccelerator::Capabilities::NEEDS_ALL_PICTURE_BUFFERS_TO_DECODE;
+
+ // If we're using threaded texture mailboxes the COPY_REQUIRED flag must be
+ // set on the video frames (http://crbug.com/582170), and SurfaceView output
+ // is disabled (http://crbug.com/582170).
+ if (gpu_preferences.enable_threaded_texture_mailboxes) {
+ capabilities.flags |=
+ VideoDecodeAccelerator::Capabilities::REQUIRES_TEXTURE_COPY;
+ } else if (MediaCodecUtil::IsSurfaceViewOutputSupported()) {
+ capabilities.flags |=
+ VideoDecodeAccelerator::Capabilities::SUPPORTS_EXTERNAL_OUTPUT_SURFACE;
+ }
+
+#if BUILDFLAG(ENABLE_HEVC_DEMUXING)
+ for (const auto& supported_profile : kSupportedHevcProfiles) {
+ SupportedProfile profile;
+ profile.profile = supported_profile;
+ profile.min_resolution.SetSize(0, 0);
+ profile.max_resolution.SetSize(3840, 2160);
+ profiles.push_back(profile);
+ }
+#endif
+
+ return capabilities;
+}
+
+bool MediaCodecVideoDecoder::IsMediaCodecSoftwareDecodingForbidden() const {
+ // Prevent MediaCodec from using its internal software decoders when we have
+ // more secure and up to date versions in the renderer process.
+ return !config_.is_encrypted && (codec_config_->codec_ == kCodecVP8 ||
+ codec_config_->codec_ == kCodecVP9);
+}
+
+bool MediaCodecVideoDecoder::UpdateSurface() {
+ DCHECK(pending_surface_id_);
+ DCHECK_NE(config_.surface_id, pending_surface_id_.value());
+ DCHECK(config_.surface_id == SurfaceManager::kNoSurfaceID ||
+ pending_surface_id_.value() == SurfaceManager::kNoSurfaceID);
+
+ const int previous_surface_id = config_.surface_id;
+ const int new_surface_id = pending_surface_id_.value();
+ pending_surface_id_.reset();
+ bool success = true;
+
+ // TODO(watk): Fix this so we can wait for the new surface to be allocated.
+ if (!AVDACodecAllocator::Instance()->AllocateSurface(this, new_surface_id)) {
+ NOTIFY_ERROR(PLATFORM_FAILURE, "Failed to allocate the new surface");
+ success = false;
+ }
+
+ // Ensure the current context is active when switching surfaces; we may need
+ // to create a new texture.
+ if (success && !make_context_current_cb_.Run()) {
+ NOTIFY_ERROR(PLATFORM_FAILURE,
+ "Failed to make this decoder's GL context current when "
+ "switching surfaces.");
+ success = false;
+ }
+
+ if (success) {
+ codec_config_->surface_ =
+ picture_buffer_manager_.Initialize(new_surface_id);
+ if (codec_config_->surface_.IsEmpty()) {
+ NOTIFY_ERROR(PLATFORM_FAILURE, "Failed to switch surfaces.");
+ success = false;
+ }
+ }
+
+ if (success && media_codec_ &&
+ !media_codec_->SetSurface(codec_config_->surface_.j_surface().obj())) {
+ NOTIFY_ERROR(PLATFORM_FAILURE, "MediaCodec failed to switch surfaces.");
+ success = false;
+ }
+
+ if (success) {
+ config_.surface_id = new_surface_id;
+ } else {
+ // This might be called from OnSurfaceDestroyed(), so we have to release the
+ // MediaCodec if we failed to switch the surface.
+ if (media_codec_) {
+ AVDACodecAllocator::Instance()->ReleaseMediaCodec(
+ std::move(media_codec_), codec_config_->task_type_,
+ previous_surface_id);
+ picture_buffer_manager_.CodecChanged(nullptr);
+ }
+ AVDACodecAllocator::Instance()->DeallocateSurface(this, new_surface_id);
+ }
+
+ // Regardless of whether we succeeded, we no longer own the previous surface.
+ AVDACodecAllocator::Instance()->DeallocateSurface(this, previous_surface_id);
+
+ return success;
+}
+
+} // namespace media
« no previous file with comments | « media/gpu/android/media_codec_video_decoder.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698