// Copyright 2016 The Chromium Authors
// 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 <memory>
#include "base/android/build_info.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "media/base/android/media_codec_bridge_impl.h"
#include "media/base/android/media_codec_util.h"
#include "media/base/async_destroy_video_decoder.h"
#include "media/base/decoder_buffer.h"
#include "media/base/media_log.h"
#include "media/base/media_switches.h"
#include "media/base/scoped_async_trace.h"
#include "media/base/status.h"
#include "media/base/supported_types.h"
#include "media/base/supported_video_decoder_config.h"
#include "media/base/video_aspect_ratio.h"
#include "media/base/video_codecs.h"
#include "media/base/video_decoder_config.h"
#include "media/base/video_frame.h"
#include "media/gpu/android/android_video_surface_chooser.h"
#include "media/gpu/android/codec_allocator.h"
#include "media/gpu/android/video_accelerator_util.h"
#include "media/media_buildflags.h"
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
#include "media/base/android/extract_sps_and_pps.h"
#endif
namespace media {
namespace {
void OutputBufferReleased(base::RepeatingClosure pump_cb, bool has_work) {
// The asynchronous API doesn't need pumping upon calls to ReleaseOutputBuffer
// unless we're draining or drained.
if (has_work) {
pump_cb.Run();
}
}
bool IsSurfaceControlEnabled(const gpu::GpuFeatureInfo& info) {
return info.status_values[gpu::GPU_FEATURE_TYPE_ANDROID_SURFACE_CONTROL] ==
gpu::kGpuFeatureStatusEnabled;
}
std::vector<SupportedVideoDecoderConfig> GetSupportedConfigsInternal(
DeviceInfo* device_info,
const bool allow_media_codec_software_decoder) {
std::vector<SupportedVideoDecoderConfig> supported_configs;
for (const auto& info : GetDecoderInfoCache()) {
const auto codec = VideoCodecProfileToVideoCodec(info.profile);
// Some DRM key system doesn't require a secure decoder to decrypt the
// stream (e.g. Widevine L3). When this function is called, we have no idea
// on which key system will be used. So we simply declare all the decoders
// to support encrypted content playback. If the playback does require a
// secure decoder, it will fail when creating MediaCodec.
constexpr bool kAllowEncrypted = true;
if ((codec == VideoCodec::kVP8 && device_info->IsVp8DecoderAvailable()) ||
(codec == VideoCodec::kVP9 && device_info->IsVp9DecoderAvailable()) ||
(codec == VideoCodec::kAV1 && device_info->IsAv1DecoderAvailable()) ||
(codec == VideoCodec::kH264 && IsBuiltInVideoCodec(codec))) {
// Don't allow OS software decoding for bundled software decoders unless
// the content is encrypted.
const bool can_use_builtin_software_decoder =
info.profile != VP9PROFILE_PROFILE2 &&
info.profile != VP9PROFILE_PROFILE3 &&
info.secure_codec_capability == SecureCodecCapability::kClear;
const bool is_os_software_decoder_allowed =
!can_use_builtin_software_decoder ||
allow_media_codec_software_decoder;
if (info.is_software_codec && !is_os_software_decoder_allowed) {
supported_configs.emplace_back(info.profile, info.profile,
info.coded_size_min, info.coded_size_max,
kAllowEncrypted,
/*require_encrypted=*/true);
continue;
}
// Require a minimum of 360p even for hardware decoding of VP8+VP9 and
// H.264 (if built-in support is available).
auto coded_size_min = info.coded_size_min;
if (!info.is_software_codec && can_use_builtin_software_decoder &&
(codec == VideoCodec::kVP8 || codec == VideoCodec::kVP9 ||
codec == VideoCodec::kH264)) {
coded_size_min.SetToMax(gfx::Size(360, 360));
}
supported_configs.emplace_back(
info.profile, info.profile, coded_size_min, info.coded_size_max,
kAllowEncrypted,
/*require_encrypted=*/info.secure_codec_capability ==
SecureCodecCapability::kEncrypted);
continue;
}
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
if (codec == VideoCodec::kH264
#if BUILDFLAG(ENABLE_PLATFORM_HEVC)
|| (codec == VideoCodec::kHEVC &&
base::FeatureList::IsEnabled(kPlatformHEVCDecoderSupport))
#endif // BUILDFLAG(ENABLE_PLATFORM_HEVC)
#if BUILDFLAG(ENABLE_PLATFORM_DOLBY_VISION)
|| codec == VideoCodec::kDolbyVision
#endif // BUILDFLAG(ENABLE_PLATFORM_DOLBY_VISION)
) {
supported_configs.emplace_back(
info.profile, info.profile, info.coded_size_min, info.coded_size_max,
kAllowEncrypted,
/*require_encrypted=*/info.secure_codec_capability ==
SecureCodecCapability::kEncrypted);
}
#endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
}
return supported_configs;
}
// Return the name of the decoder that will be used to create MediaCodec.
void SelectMediaCodec(const VideoDecoderConfig& config,
bool requires_secure_codec,
std::string* out_codec_name,
bool* out_is_software_codec) {
*out_is_software_codec = false;
*out_codec_name = "";
std::string software_decoder;
for (const auto& info : GetDecoderInfoCache()) {
VideoCodec codec = VideoCodecProfileToVideoCodec(info.profile);
if (config.codec() != codec) {
continue;
}
if (config.profile() != VIDEO_CODEC_PROFILE_UNKNOWN &&
config.profile() != info.profile) {
continue;
}
if (config.level() != kNoVideoCodecLevel &&
info.level != kNoVideoCodecLevel && config.level() > info.level) {
continue;
}
if (config.coded_size().width() < info.coded_size_min.width() ||
config.coded_size().height() < info.coded_size_min.height() ||
config.coded_size().width() > info.coded_size_max.width() ||
config.coded_size().height() > info.coded_size_max.height()) {
continue;
}
if (!requires_secure_codec &&
info.secure_codec_capability == SecureCodecCapability::kEncrypted) {
continue;
}
if (requires_secure_codec &&
info.secure_codec_capability == SecureCodecCapability::kClear) {
continue;
}
// Prioritize hardware decoder. Software decoder will be selected as a
// fallback option.
if (info.is_software_codec) {
if (software_decoder.empty()) {
software_decoder = info.name;
}
continue;
}
*out_is_software_codec = false;
*out_codec_name = info.name;
return;
}
// Allow software decoder if either:
// 1. the stream is encrypted.
// 2. No software decoder is bundled into Chromium.
if (!(config.is_encrypted()
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
|| config.codec() == VideoCodec::kH264
#if BUILDFLAG(ENABLE_PLATFORM_HEVC)
|| (config.codec() == VideoCodec::kHEVC &&
base::FeatureList::IsEnabled(kPlatformHEVCDecoderSupport))
#endif // BUILDFLAG(ENABLE_PLATFORM_HEVC)
#if BUILDFLAG(ENABLE_PLATFORM_DOLBY_VISION)
|| config.codec() == VideoCodec::kDolbyVision
#endif // BUILDFLAG(ENABLE_PLATFORM_DOLBY_VISION)
#endif // BUILDFLAG(USE_PROPRIETARY_CODECS)
)) {
DVLOG(2) << "Can't find proper video decoder from decoder info cache, "
"fallback to the default decoder selection path.";
return;
}
*out_is_software_codec = true;
*out_codec_name = software_decoder;
}
} // namespace
// When re-initializing the codec changes the resolution to be more than
// |kReallocateThreshold| times the old one, force a codec reallocation to
// update the hints that we provide to MediaCodec. crbug.com/989182 .
constexpr static float kReallocateThreshold = 3.9;
// static
PendingDecode PendingDecode::CreateEos() {
return {DecoderBuffer::CreateEOSBuffer(), base::DoNothing()};
}
PendingDecode::PendingDecode(scoped_refptr<DecoderBuffer> buffer,
VideoDecoder::DecodeCB decode_cb)
: buffer(std::move(buffer)), decode_cb(std::move(decode_cb)) {}
PendingDecode::PendingDecode(PendingDecode&& other) = default;
PendingDecode::~PendingDecode() = default;
// static
std::vector<SupportedVideoDecoderConfig>
MediaCodecVideoDecoder::GetSupportedConfigs() {
static const auto configs = GetSupportedConfigsInternal(
DeviceInfo::GetInstance(),
base::FeatureList::IsEnabled(media::kAllowMediaCodecSoftwareDecoder));
return configs;
}
MediaCodecVideoDecoder::MediaCodecVideoDecoder(
const gpu::GpuPreferences& gpu_preferences,
const gpu::GpuFeatureInfo& gpu_feature_info,
std::unique_ptr<MediaLog> media_log,
DeviceInfo* device_info,
CodecAllocator* codec_allocator,
std::unique_ptr<AndroidVideoSurfaceChooser> surface_chooser,
AndroidOverlayMojoFactoryCB overlay_factory_cb,
RequestOverlayInfoCB request_overlay_info_cb,
std::unique_ptr<VideoFrameFactory> video_frame_factory,
scoped_refptr<gpu::RefCountedLock> drdc_lock)
: gpu::RefCountedLockHelperDrDc(std::move(drdc_lock)),
media_log_(std::move(media_log)),
codec_allocator_(codec_allocator),
request_overlay_info_cb_(std::move(request_overlay_info_cb)),
is_surface_control_enabled_(IsSurfaceControlEnabled(gpu_feature_info)),
surface_chooser_helper_(
std::move(surface_chooser),
base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kForceVideoOverlays),
base::FeatureList::IsEnabled(media::kUseAndroidOverlayForSecureOnly),
is_surface_control_enabled_),
video_frame_factory_(std::move(video_frame_factory)),
overlay_factory_cb_(std::move(overlay_factory_cb)),
device_info_(device_info),
enable_threaded_texture_mailboxes_(
gpu_preferences.enable_threaded_texture_mailboxes),
allow_nonsecure_overlays_(
base::FeatureList::IsEnabled(media::kAllowNonSecureOverlays)) {
DVLOG(2) << __func__;
surface_chooser_helper_.chooser()->SetClientCallbacks(
base::BindRepeating(&MediaCodecVideoDecoder::OnSurfaceChosen,
weak_factory_.GetWeakPtr()),
base::BindRepeating(&MediaCodecVideoDecoder::OnSurfaceChosen,
weak_factory_.GetWeakPtr(), nullptr));
}
std::unique_ptr<VideoDecoder> MediaCodecVideoDecoder::Create(
const gpu::GpuPreferences& gpu_preferences,
const gpu::GpuFeatureInfo& gpu_feature_info,
std::unique_ptr<MediaLog> media_log,
DeviceInfo* device_info,
CodecAllocator* codec_allocator,
std::unique_ptr<AndroidVideoSurfaceChooser> surface_chooser,
AndroidOverlayMojoFactoryCB overlay_factory_cb,
RequestOverlayInfoCB request_overlay_info_cb,
std::unique_ptr<VideoFrameFactory> video_frame_factory,
scoped_refptr<gpu::RefCountedLock> drdc_lock) {
auto* decoder = new MediaCodecVideoDecoder(
gpu_preferences, gpu_feature_info, std::move(media_log), device_info,
codec_allocator, std::move(surface_chooser),
std::move(overlay_factory_cb), std::move(request_overlay_info_cb),
std::move(video_frame_factory), std::move(drdc_lock));
return std::make_unique<AsyncDestroyVideoDecoder<MediaCodecVideoDecoder>>(
base::WrapUnique(decoder));
}
MediaCodecVideoDecoder::~MediaCodecVideoDecoder() {
DVLOG(2) << __func__;
TRACE_EVENT0("media", "MediaCodecVideoDecoder::~MediaCodecVideoDecoder");
ReleaseCodec();
}
void MediaCodecVideoDecoder::DestroyAsync(
std::unique_ptr<MediaCodecVideoDecoder> decoder) {
DVLOG(1) << __func__;
TRACE_EVENT0("media", "MediaCodecVideoDecoder::Destroy");
DCHECK(decoder);
// This will be destroyed by a call to |DeleteSoon|
// in |OnCodecDrained|.
auto* self = decoder.release();
// Cancel pending callbacks.
//
// WARNING: This will lose the callback we've given to MediaCodecBridge for
// asynchronous notifications; so we must not leave this function with any
// work necessary from PumpCodec().
self->weak_factory_.InvalidateWeakPtrs();
if (self->media_crypto_context_) {
// Cancel previously registered callback (if any).
self->event_cb_registration_.reset();
self->media_crypto_context_->SetMediaCryptoReadyCB(base::NullCallback());
self->media_crypto_context_ = nullptr;
}
// Mojo callbacks require that they're run before destruction.
if (self->reset_cb_)
std::move(self->reset_cb_).Run();
// Cancel callbacks we no longer want.
self->codec_allocator_weak_factory_.InvalidateWeakPtrs();
self->CancelPendingDecodes(DecoderStatus::Codes::kAborted);
self->StartDrainingCodec(DrainType::kForDestroy);
// Per the WARNING above. Validate that no draining work remains.
DCHECK(!self->drain_type_.has_value());
}
void MediaCodecVideoDecoder::Initialize(const VideoDecoderConfig& config,
bool low_delay,
CdmContext* cdm_context,
InitCB init_cb,
const OutputCB& output_cb,
const WaitingCB& waiting_cb) {
DCHECK(output_cb);
DCHECK(waiting_cb);
const bool first_init = !decoder_config_.IsValidConfig();
DVLOG(1) << (first_init ? "Initializing" : "Reinitializing")
<< " MCVD with config: " << config.AsHumanReadableString()
<< ", cdm_context = " << cdm_context;
if (!config.IsValidConfig()) {
MEDIA_LOG(INFO, media_log_) << "Video configuration is not valid: "
<< config.AsHumanReadableString();
DVLOG(1) << "Invalid configuration.";
base::BindPostTaskToCurrentDefault(std::move(init_cb))
.Run(DecoderStatus::Codes::kUnsupportedConfig);
return;
}
// Tests override the DeviceInfo, so if an override is provided query the
// configs as they look under that DeviceInfo. If not, use the default method
// which is statically cached for faster Initialize().
//
// The tests also require the presence of software codecs.
const auto configs =
device_info_ == DeviceInfo::GetInstance()
? GetSupportedConfigs()
: GetSupportedConfigsInternal(
device_info_, /*allow_media_codec_software_decoder=*/true);
// If we don't have support support for a given codec, try to initialize
// anyways -- otherwise we're certain to fail playback.
if (!IsVideoDecoderConfigSupported(configs, config) &&
IsBuiltInVideoCodec(config.codec())) {
DVLOG(1) << "Unsupported configuration.";
MEDIA_LOG(INFO, media_log_) << "Video configuration is not valid: "
<< config.AsHumanReadableString();
base::BindPostTaskToCurrentDefault(std::move(init_cb))
.Run(DecoderStatus::Codes::kUnsupportedConfig);
return;
}
// Disallow codec changes when reinitializing.
if (!first_init && decoder_config_.codec() != config.codec()) {
DVLOG(1) << "Codec changed: cannot reinitialize";
MEDIA_LOG(INFO, media_log_) << "Cannot change codec during re-init: "
<< decoder_config_.AsHumanReadableString()
<< " -> " << config.AsHumanReadableString();
base::BindPostTaskToCurrentDefault(std::move(init_cb))
.Run(DecoderStatus::Codes::kCantChangeCodec);
return;
}
decoder_config_ = config;
surface_chooser_helper_.SetVideoRotation(
decoder_config_.video_transformation().rotation);
output_cb_ = output_cb;
waiting_cb_ = waiting_cb;
#if BUILDFLAG(USE_PROPRIETARY_CODECS)
if (config.codec() == VideoCodec::kH264)
ExtractSpsAndPps(config.extra_data(), &csd0_, &csd1_);
#endif
// We only support setting CDM at first initialization. Even if the initial
// config is clear, we'll still try to set CDM since we may switch to an
// encrypted config later.
const int width = decoder_config_.coded_size().width();
if (first_init && cdm_context && cdm_context->GetMediaCryptoContext()) {
DCHECK(media_crypto_.is_null());
last_width_ = width;
SetCdm(cdm_context, std::move(init_cb));
return;
}
if (config.is_encrypted() && media_crypto_.is_null()) {
DVLOG(1) << "No MediaCrypto to handle encrypted config";
MEDIA_LOG(INFO, media_log_) << "No MediaCrypto to handle encrypted config";
base::BindPostTaskToCurrentDefault(std::move(init_cb))
.Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
return;
}
// Do the rest of the initialization lazily on the first decode.
base::BindPostTaskToCurrentDefault(std::move(init_cb))
.Run(DecoderStatus::Codes::kOk);
// On re-init, reallocate the codec if the size has changed too much.
// Restrict this behavior to Q, where the behavior changed.
if (first_init) {
last_width_ = width;
} else if (width > last_width_ * kReallocateThreshold && device_info_ &&
device_info_->SdkVersion() > base::android::SDK_VERSION_P) {
// Reallocate the codec the next time we queue input, once there are no
// outstanding output buffers. Note that |deferred_flush_pending_| might
// already be set, which is fine. We're just upgrading the flush.
//
// If the codec IsDrained(), then we'll flush anyway. However, just to be
// sure, request a deferred flush.
deferred_flush_pending_ = true;
deferred_reallocation_pending_ = true;
// Since this will re-use the same surface, allow a retry to work around a
// race condition in the android framework.
should_retry_codec_allocation_ = true;
last_width_ = width;
} // else leave |last_width_| unmodified, since we're re-using the codec.
}
void MediaCodecVideoDecoder::SetCdm(CdmContext* cdm_context, InitCB init_cb) {
DVLOG(1) << __func__;
DCHECK(cdm_context) << "No CDM provided";
DCHECK(cdm_context->GetMediaCryptoContext());
media_crypto_context_ = cdm_context->GetMediaCryptoContext();
// CdmContext will always post the registered callback back to this thread.
event_cb_registration_ = cdm_context->RegisterEventCB(base::BindRepeating(
&MediaCodecVideoDecoder::OnCdmContextEvent, weak_factory_.GetWeakPtr()));
// The callback will be posted back to this thread via
// base::BindPostTaskToCurrentDefault.
media_crypto_context_->SetMediaCryptoReadyCB(
base::BindPostTaskToCurrentDefault(
base::BindOnce(&MediaCodecVideoDecoder::OnMediaCryptoReady,
weak_factory_.GetWeakPtr(), std::move(init_cb))));
}
void MediaCodecVideoDecoder::OnMediaCryptoReady(
InitCB init_cb,
JavaObjectPtr media_crypto,
bool requires_secure_video_codec) {
DVLOG(1) << __func__
<< ": requires_secure_video_codec = " << requires_secure_video_codec;
DCHECK(state_ == State::kInitializing);
DCHECK(media_crypto);
if (media_crypto->is_null()) {
media_crypto_context_->SetMediaCryptoReadyCB(base::NullCallback());
media_crypto_context_ = nullptr;
if (decoder_config_.is_encrypted()) {
LOG(ERROR) << "MediaCrypto is not available";
EnterTerminalState(State::kError, {DecoderStatus::Codes::kFailed,
"MediaCrypto is not available"});
std::move(init_cb).Run(DecoderStatus::Codes::kUnsupportedEncryptionMode);
return;
}
// MediaCrypto is not available, but the stream is clear. So we can still
// play the current stream. But if we switch to an encrypted stream playback
// will fail.
std::move(init_cb).Run(DecoderStatus::Codes::kOk);
return;
}
media_crypto_ = *media_crypto;
requires_secure_codec_ = requires_secure_video_codec;
// Request a secure surface in all cases. For L3, it's okay if we fall back
// to TextureOwner rather than fail composition. For L1, it's required.
surface_chooser_helper_.SetSecureSurfaceMode(
requires_secure_video_codec
? SurfaceChooserHelper::SecureSurfaceMode::kRequired
: SurfaceChooserHelper::SecureSurfaceMode::kRequested);
// Signal success, and create the codec lazily on the first decode.
std::move(init_cb).Run(DecoderStatus::Codes::kOk);
}
void MediaCodecVideoDecoder::OnCdmContextEvent(CdmContext::Event event) {
DVLOG(2) << __func__;
if (event != CdmContext::Event::kHasAdditionalUsableKey)
return;
waiting_for_key_ = false;
PumpCodec();
}
void MediaCodecVideoDecoder::StartLazyInit() {
DVLOG(2) << __func__;
TRACE_EVENT0("media", "MediaCodecVideoDecoder::StartLazyInit");
lazy_init_pending_ = false;
// Only ask for promotion hints if we can actually switch surfaces, since we
// wouldn't be able to do anything with them. Also, if threaded texture
// mailboxes are enabled, then we turn off overlays anyway.
const bool want_promotion_hints =
device_info_->IsSetOutputSurfaceSupported() &&
!enable_threaded_texture_mailboxes_;
VideoFrameFactory::OverlayMode overlay_mode =
VideoFrameFactory::OverlayMode::kDontRequestPromotionHints;
if (is_surface_control_enabled_) {
overlay_mode =
requires_secure_codec_
? VideoFrameFactory::OverlayMode::kSurfaceControlSecure
: VideoFrameFactory::OverlayMode::kSurfaceControlInsecure;
} else if (want_promotion_hints) {
overlay_mode = VideoFrameFactory::OverlayMode::kRequestPromotionHints;
}
// Regardless of whether we're using SurfaceControl or Dialog overlays, don't
// allow any overlays in A/B power testing mode, unless this requires a
// secure surface. Don't fail the playback for power testing.
if (!requires_secure_codec_ && !allow_nonsecure_overlays_)
overlay_mode = VideoFrameFactory::OverlayMode::kDontRequestPromotionHints;
video_frame_factory_->Initialize(
overlay_mode, base::BindRepeating(
&MediaCodecVideoDecoder::OnVideoFrameFactoryInitialized,
weak_factory_.GetWeakPtr()));
}
void MediaCodecVideoDecoder::OnVideoFrameFactoryInitialized(
scoped_refptr<gpu::TextureOwner> texture_owner) {
DVLOG(2) << __func__;
TRACE_EVENT0("media",
"MediaCodecVideoDecoder::OnVideoFrameFactoryInitialized");
if (!texture_owner) {
EnterTerminalState(State::kError, {DecoderStatus::Codes::kFailed,
"Could not allocated TextureOwner"});
return;
}
texture_owner_bundle_ =
new CodecSurfaceBundle(std::move(texture_owner), GetDrDcLock());
// This is for A/B power testing only. Turn off Dialog-based overlays in
// power testing mode, unless we need them for L1 content.
// See https://crbug.com/1081346 .
const bool allowed_for_experiment =
requires_secure_codec_ || allow_nonsecure_overlays_;
// Overlays are disabled when |enable_threaded_texture_mailboxes| is true
// (http://crbug.com/582170).
if (enable_threaded_texture_mailboxes_ ||
!device_info_->SupportsOverlaySurfaces() || !allowed_for_experiment) {
OnSurfaceChosen(nullptr);
return;
}
// Request OverlayInfo updates. Initialization continues on the first one.
bool restart_for_transitions = !device_info_->IsSetOutputSurfaceSupported();
std::move(request_overlay_info_cb_)
.Run(restart_for_transitions,
base::BindRepeating(&MediaCodecVideoDecoder::OnOverlayInfoChanged,
weak_factory_.GetWeakPtr()));
}
void MediaCodecVideoDecoder::OnOverlayInfoChanged(
const OverlayInfo& overlay_info) {
DVLOG(2) << __func__;
DCHECK(device_info_->SupportsOverlaySurfaces());
DCHECK(!enable_threaded_texture_mailboxes_);
if (InTerminalState())
return;
bool overlay_changed = !overlay_info_.RefersToSameOverlayAs(overlay_info);
overlay_info_ = overlay_info;
surface_chooser_helper_.SetIsFullscreen(overlay_info_.is_fullscreen);
surface_chooser_helper_.SetIsPersistentVideo(
overlay_info_.is_persistent_video);
surface_chooser_helper_.UpdateChooserState(
overlay_changed ? std::make_optional(CreateOverlayFactoryCb())
: std::nullopt);
}
void MediaCodecVideoDecoder::OnSurfaceChosen(
std::unique_ptr<AndroidOverlay> overlay) {
DVLOG(2) << __func__;
DCHECK(state_ == State::kInitializing ||
device_info_->IsSetOutputSurfaceSupported());
TRACE_EVENT1("media", "MediaCodecVideoDecoder::OnSurfaceChosen", "overlay",
overlay ? "yes" : "no");
if (overlay) {
overlay->AddSurfaceDestroyedCallback(
base::BindOnce(&MediaCodecVideoDecoder::OnSurfaceDestroyed,
weak_factory_.GetWeakPtr()));
target_surface_bundle_ = new CodecSurfaceBundle(std::move(overlay));
} else {
target_surface_bundle_ = texture_owner_bundle_;
}
// If we were waiting for our first surface during initialization, then
// proceed to create a codec.
if (state_ == State::kInitializing) {
state_ = State::kRunning;
CreateCodec();
}
}
void MediaCodecVideoDecoder::OnSurfaceDestroyed(AndroidOverlay* overlay) {
DVLOG(2) << __func__;
DCHECK_NE(state_, State::kInitializing);
TRACE_EVENT0("media", "MediaCodecVideoDecoder::OnSurfaceDestroyed");
// If SetOutputSurface() is not supported we only ever observe destruction of
// a single overlay so this must be the one we're using. In this case it's
// the responsibility of our consumer to destroy us for surface transitions.
// TODO(liberato): This might not be true for L1 / L3, since our caller has
// no idea that this has happened. We should unback the frames here. This
// might work now that we have CodecImageGroup -- verify this.
if (!device_info_->IsSetOutputSurfaceSupported()) {
EnterTerminalState(State::kSurfaceDestroyed,
{DecoderStatus::Codes::kFailed, "Surface destroyed"});
return;
}
// Reset the target bundle if it is the one being destroyed.
if (target_surface_bundle_ && target_surface_bundle_->overlay() == overlay)
target_surface_bundle_ = texture_owner_bundle_;
if (requires_secure_codec_ &&
target_surface_bundle_ == texture_owner_bundle_) {
// Assume that the target bundle is a SurfaceTexture, or insecure image
// reader, and reset the codec. We can't decode anything. We might want
// to verify that this isn't a secure image reader, but there's no
// combination that would create one that would also get here. Secure
// image readers are used with SurfaceControl only.
DVLOG(2) << "surface destroyed for secure codec, resetting MediaCodec.";
video_frame_factory_->SetSurfaceBundle(nullptr);
ReleaseCodec();
waiting_cb_.Run(WaitingReason::kSecureSurfaceLost);
return;
}
// Transition the codec away from the overlay if necessary. This must be
// complete before this function returns.
if (SurfaceTransitionPending())
TransitionToTargetSurface();
}
bool MediaCodecVideoDecoder::SurfaceTransitionPending() {
return codec_ && codec_->SurfaceBundle() != target_surface_bundle_;
}
void MediaCodecVideoDecoder::TransitionToTargetSurface() {
DVLOG(2) << __func__;
DCHECK(SurfaceTransitionPending());
DCHECK(device_info_->IsSetOutputSurfaceSupported());
if (!codec_->SetSurface(target_surface_bundle_)) {
video_frame_factory_->SetSurfaceBundle(nullptr);
EnterTerminalState(State::kError,
{DecoderStatus::Codes::kFailed,
"Could not switch codec output surface"});
return;
}
video_frame_factory_->SetSurfaceBundle(target_surface_bundle_);
CacheFrameInformation();
}
void MediaCodecVideoDecoder::CreateCodec() {
DCHECK(!codec_);
DCHECK(target_surface_bundle_);
DCHECK_EQ(state_, State::kRunning);
auto config = std::make_unique<VideoCodecConfig>();
if (requires_secure_codec_)
config->codec_type = CodecType::kSecure;
config->codec = decoder_config_.codec();
config->csd0 = csd0_;
config->csd1 = csd1_;
config->surface = target_surface_bundle_->GetJavaSurface();
config->media_crypto = media_crypto_;
config->initial_expected_coded_size = decoder_config_.coded_size();
config->container_color_space = decoder_config_.color_space_info();
config->hdr_metadata = decoder_config_.hdr_metadata();
SelectMediaCodec(decoder_config_, requires_secure_codec_, &config->name,
&is_software_codec_);
config->on_buffers_available_cb =
base::BindPostTaskToCurrentDefault(base::BindRepeating(
&MediaCodecVideoDecoder::PumpCodec, weak_factory_.GetWeakPtr()));
// Note that this might be the same surface bundle that we've been using, if
// we're reinitializing the codec without changing surfaces. That's fine.
video_frame_factory_->SetSurfaceBundle(target_surface_bundle_);
codec_allocator_->CreateMediaCodecAsync(
base::BindOnce(&MediaCodecVideoDecoder::OnCodecConfiguredInternal,
codec_allocator_weak_factory_.GetWeakPtr(),
codec_allocator_, target_surface_bundle_),
std::move(config));
}
// static
void MediaCodecVideoDecoder::OnCodecConfiguredInternal(
base::WeakPtr<MediaCodecVideoDecoder> weak_this,
CodecAllocator* codec_allocator,
scoped_refptr<CodecSurfaceBundle> surface_bundle,
std::unique_ptr<MediaCodecBridge> codec) {
if (!weak_this) {
if (codec) {
codec_allocator->ReleaseMediaCodec(
std::move(codec),
base::BindOnce(
&base::SequencedTaskRunner::ReleaseSoon<CodecSurfaceBundle>,
base::SequencedTaskRunner::GetCurrentDefault(), FROM_HERE,
std::move(surface_bundle)));
}
return;
}
weak_this->OnCodecConfigured(std::move(surface_bundle), std::move(codec));
}
void MediaCodecVideoDecoder::OnCodecConfigured(
scoped_refptr<CodecSurfaceBundle> surface_bundle,
std::unique_ptr<MediaCodecBridge> codec) {
DCHECK(!codec_);
DCHECK_EQ(state_, State::kRunning);
bool should_retry_codec_allocation = should_retry_codec_allocation_;
should_retry_codec_allocation_ = false;
// In rare cases, the framework can fail transiently when trying to re-use a
// surface. If we're in one of those cases, then retry codec allocation.
// This only happens on R and S, so skip it otherwise.
if (!codec && should_retry_codec_allocation &&
device_info_->SdkVersion() >= base::android::SDK_VERSION_R &&
device_info_->SdkVersion() <= 32 /* SDK_VERSION_S_V2 */
) {
// We might want to post this with a short delay, but there is already quite
// a lot of overhead in codec allocation.
CreateCodec();
return;
}
if (!codec) {
EnterTerminalState(State::kError, {DecoderStatus::Codes::kFailed,
"Unable to allocate codec"});
return;
}
const auto name = codec->GetName();
MEDIA_LOG(INFO, media_log_) << "Created MediaCodec " << name
<< ", is_software_codec=" << is_software_codec_;
// Since we can't get the coded size w/o rendering the frame, we try to guess
// in cases where we are unable to render the frame (resolution changes). If
// we can't guess, there will be a visible rendering glitch.
std::optional<gfx::Size> coded_size_alignment;
if (base::FeatureList::IsEnabled(kMediaCodecCodedSizeGuessing)) {
coded_size_alignment = MediaCodecUtil::LookupCodedSizeAlignment(name);
if (coded_size_alignment) {
MEDIA_LOG(INFO, media_log_) << "Using a coded size alignment of "
<< coded_size_alignment->ToString();
} else {
// TODO(crbug.com/40917948): If the known cases work well, we can try
// guessing generically since we get a glitch either way.
MEDIA_LOG(WARNING, media_log_)
<< "Unable to lookup coded size alignment for codec " << name;
}
}
max_input_size_ = codec->GetMaxInputSize();
codec_ = std::make_unique<CodecWrapper>(
CodecSurfacePair(std::move(codec), std::move(surface_bundle)),
base::BindRepeating(
&OutputBufferReleased,
base::BindPostTaskToCurrentDefault(base::BindRepeating(
&MediaCodecVideoDecoder::PumpCodec, weak_factory_.GetWeakPtr()))),
base::SequencedTaskRunner::GetCurrentDefault(),
decoder_config_.coded_size(),
decoder_config_.color_space_info().ToGfxColorSpace(),
coded_size_alignment);
// If the target surface changed while codec creation was in progress,
// transition to it immediately.
// Note: this can only happen if we support SetOutputSurface() because if we
// don't OnSurfaceDestroyed() cancels codec creations, and
// |surface_chooser_| doesn't change the target surface.
if (SurfaceTransitionPending())
TransitionToTargetSurface();
// Cache the frame information that goes with this codec.
CacheFrameInformation();
PumpCodec();
}
void MediaCodecVideoDecoder::Decode(scoped_refptr<DecoderBuffer> buffer,
DecodeCB decode_cb) {
DVLOG(3) << __func__ << ": " << buffer->AsHumanReadableString();
if (state_ == State::kError) {
std::move(decode_cb).Run(DecoderStatus::Codes::kFailed);
return;
}
if (!DecoderBuffer::DoSubsamplesMatch(*buffer)) {
std::move(decode_cb).Run(DecoderStatus::Codes::kFailed);
return;
}
pending_decodes_.emplace_back(std::move(buffer), std::move(decode_cb));
if (state_ == State::kInitializing) {
if (lazy_init_pending_)
StartLazyInit();
return;
}
PumpCodec();
}
void MediaCodecVideoDecoder::FlushCodec() {
DVLOG(2) << __func__;
// If a deferred flush was pending, then it isn't anymore.
deferred_flush_pending_ = false;
// Release and re-allocate the codec, if needed, for a resolution change.
// This also counts as a flush. Note that we could also stop / configure /
// start the codec, but there's a fair bit of complexity in that. Timing
// tests didn't show any big advantage. During a resolution change, the time
// between the next time we queue an input buffer and the next time we get an
// output buffer were:
//
// flush only: 0.04 s
// stop / configure / start: 0.026 s
// release / create: 0.03 s
//
// So, it seems that flushing the codec defers some work (buffer reallocation
// or similar) that ends up on the critical path. I didn't verify what
// happens when we're flushing without a resolution change, nor can I quite
// explain how anything can be done off the critical path when a flush is
// deferred to the first queued input.
if (deferred_reallocation_pending_) {
deferred_reallocation_pending_ = false;
ReleaseCodec();
// Re-initializing the codec with the same surface may need to retry.
should_retry_codec_allocation_ = !SurfaceTransitionPending();
CreateCodec();
}
if (!codec_ || codec_->IsFlushed())
return;
DVLOG(2) << "Flushing codec";
if (!codec_->Flush())
EnterTerminalState(State::kError,
{DecoderStatus::Codes::kFailed, "Codec flush failed"});
}
void MediaCodecVideoDecoder::PumpCodec() {
DVLOG(4) << __func__;
if (state_ != State::kRunning) {
return;
}
bool did_input = false, did_output = false;
do {
did_input = QueueInput();
did_output = DequeueOutput();
} while (did_input || did_output);
}
bool MediaCodecVideoDecoder::QueueInput() {
DVLOG(4) << __func__;
if (!codec_ || waiting_for_key_)
return false;
// If the codec is drained, flush it when there is a pending decode and no
// unreleased output buffers. This lets us avoid both unbacking frames when we
// flush, and flushing unnecessarily, like at EOS.
//
// Often, we'll elide the eos to drain the codec, but we want to pretend that
// we did. In this case, we should also flush.
if (codec_->IsDrained() || deferred_flush_pending_) {
if (!codec_->HasUnreleasedOutputBuffers() && !pending_decodes_.empty()) {
FlushCodec();
return true;
}
return false;
}
if (pending_decodes_.empty())
return false;
PendingDecode& pending_decode = pending_decodes_.front();
if (!pending_decode.buffer->end_of_stream() &&
pending_decode.buffer->is_key_frame() &&
pending_decode.buffer->size() > max_input_size_) {
// If we we're already using the provided resolution, try to guess something
// larger based on the actual input size.
if (decoder_config_.coded_size().width() == last_width_) {
// See MediaFormatBuilder::addInputSizeInfoToFormat() for details.
const size_t compression_ratio =
(decoder_config_.codec() == VideoCodec::kH264 ||
decoder_config_.codec() == VideoCodec::kVP8)
? 2
: 4;
const size_t max_pixels =
(pending_decode.buffer->size() * compression_ratio * 2) / 3;
if (max_pixels > 8294400) // 4K
decoder_config_.set_coded_size(gfx::Size(7680, 4320));
else if (max_pixels > 2088960) // 1080p
decoder_config_.set_coded_size(gfx::Size(3840, 2160));
else
decoder_config_.set_coded_size(gfx::Size(1920, 1080));
}
// Flush and reallocate on the next call to QueueInput() if we changed size;
// otherwise just try queuing the buffer and hoping for the best.
if (decoder_config_.coded_size().width() != last_width_) {
deferred_flush_pending_ = true;
deferred_reallocation_pending_ = true;
last_width_ = decoder_config_.coded_size().width();
return true;
}
}
auto status = codec_->QueueInputBuffer(*pending_decode.buffer);
DVLOG((status.code() == CodecWrapper::QueueStatus::Codes::kTryAgainLater ||
status.is_ok()
? 3
: 2))
<< "QueueInput(" << pending_decode.buffer->AsHumanReadableString()
<< ") status=" << MediaSerialize(status);
switch (status.code()) {
case CodecWrapper::QueueStatus::Codes::kOk:
break;
case CodecWrapper::QueueStatus::Codes::kTryAgainLater:
return false;
case CodecWrapper::QueueStatus::Codes::kNoKey:
// Retry when a key is added.
waiting_for_key_ = true;
waiting_cb_.Run(WaitingReason::kNoDecryptionKey);
return false;
case CodecWrapper::QueueStatus::Codes::kError:
EnterTerminalState(State::kError,
{DecoderStatus::Codes::kFailed,
"QueueInputBuffer failed", std::move(status)});
return false;
}
if (pending_decode.buffer->end_of_stream()) {
// The VideoDecoder interface requires that the EOS DecodeCB is called after
// all decodes before it are delivered, so we have to save it and call it
// when the EOS is dequeued.
DCHECK(!eos_decode_cb_);
eos_decode_cb_ = std::move(pending_decode.decode_cb);
} else {
std::move(pending_decode.decode_cb).Run(DecoderStatus::Codes::kOk);
}
pending_decodes_.pop_front();
return true;
}
bool MediaCodecVideoDecoder::DequeueOutput() {
DVLOG(4) << __func__;
if (!codec_ || codec_->IsDrained() || waiting_for_key_)
return false;
// If a surface transition is pending, wait for all outstanding buffers to be
// released before doing the transition. This is necessary because the
// VideoFrames corresponding to these buffers have metadata flags specific to
// the surface type, and changing the surface before they're rendered would
// invalidate them.
if (SurfaceTransitionPending()) {
if (!codec_->HasUnreleasedOutputBuffers()) {
TransitionToTargetSurface();
return true;
}
return false;
}
base::TimeDelta presentation_time;
bool eos = false;
std::unique_ptr<CodecOutputBuffer> output_buffer;
auto status =
codec_->DequeueOutputBuffer(&presentation_time, &eos, &output_buffer);
switch (status.code()) {
case CodecWrapper::DequeueStatus::Codes::kOk:
break;
case CodecWrapper::DequeueStatus::Codes::kTryAgainLater:
return false;
case CodecWrapper::DequeueStatus::Codes::kError:
DVLOG(1) << "DequeueOutputBuffer() error";
EnterTerminalState(State::kError,
{DecoderStatus::Codes::kFailed,
"DequeueOutputBuffer failed", std::move(status)});
return false;
}
DVLOG(3) << "DequeueOutputBuffer(): pts="
<< (eos ? "EOS"
: std::to_string(presentation_time.InMilliseconds()));
if (eos) {
if (eos_decode_cb_) {
// Schedule the EOS DecodeCB to run after all previous frames.
video_frame_factory_->RunAfterPendingVideoFrames(
base::BindOnce(&MediaCodecVideoDecoder::RunEosDecodeCb,
weak_factory_.GetWeakPtr(), reset_generation_));
}
if (drain_type_)
OnCodecDrained();
// We don't flush the drained codec immediately because it might be
// backing unrendered frames near EOS. It's flushed lazily in QueueInput().
return false;
}
// If we're draining for reset or destroy we can discard |output_buffer|
// without rendering it. This is also true if we elided the drain itself,
// and deferred a flush that would have happened when the drain completed.
if (drain_type_ || deferred_flush_pending_)
return true;
// If we're getting outputs larger than our configured size, we run the risk
// of exceeding MediaCodec's allowed input buffer size. Update the coded size
// as we go to ensure we can correctly reconfigure if needed later.
if (output_buffer->size().GetArea() > decoder_config_.coded_size().GetArea())
decoder_config_.set_coded_size(output_buffer->size());
gfx::Rect visible_rect(output_buffer->size());
std::unique_ptr<ScopedAsyncTrace> async_trace =
ScopedAsyncTrace::CreateIfEnabled(
"MediaCodecVideoDecoder::CreateVideoFrame");
// Make sure that we're notified when this is rendered. Otherwise, if we're
// waiting for all output buffers to drain so that we can swap the output
// surface, we might not realize that we may continue. If we're using
// SurfaceControl overlays, then this isn't needed; there is never a surface
// transition anyway.
if (!is_surface_control_enabled_) {
output_buffer->set_render_cb(
base::BindPostTaskToCurrentDefault(base::BindOnce(
&MediaCodecVideoDecoder::PumpCodec, weak_factory_.GetWeakPtr())));
}
video_frame_factory_->CreateVideoFrame(
std::move(output_buffer), presentation_time,
decoder_config_.aspect_ratio().GetNaturalSize(visible_rect),
CreatePromotionHintCB(),
base::BindOnce(&MediaCodecVideoDecoder::ForwardVideoFrame,
weak_factory_.GetWeakPtr(), reset_generation_,
std::move(async_trace), base::TimeTicks::Now()));
return true;
}
void MediaCodecVideoDecoder::RunEosDecodeCb(int reset_generation) {
// Both of the following conditions are necessary because:
// * In an error state, the reset generations will match but |eos_decode_cb_|
// will be aborted.
// * After a Reset(), the reset generations won't match, but we might already
// have a new |eos_decode_cb_| for the new generation.
if (reset_generation == reset_generation_ && eos_decode_cb_)
std::move(eos_decode_cb_).Run(DecoderStatus::Codes::kOk);
}
void MediaCodecVideoDecoder::ForwardVideoFrame(
int reset_generation,
std::unique_ptr<ScopedAsyncTrace> async_trace,
base::TimeTicks started_at,
scoped_refptr<VideoFrame> frame) {
DVLOG(3) << __func__ << " : "
<< (frame ? frame->AsHumanReadableString() : "null");
// No |frame| indicates an error creating it.
if (!frame) {
DLOG(ERROR) << __func__ << " |frame| is null";
EnterTerminalState(State::kError, {DecoderStatus::Codes::kFailed,
"Could not create VideoFrame"});
return;
}
// Attach the HDR metadata if the color space got this far and is still an HDR
// color space. Note that it might be converted to something else along the
// way, often sRGB. In that case, don't confuse things with HDR metadata.
if (frame->ColorSpace().IsHDR() && decoder_config_.hdr_metadata()) {
frame->set_hdr_metadata(decoder_config_.hdr_metadata());
}
if (media_crypto_context_) {
frame->metadata().protected_video = true;
if (requires_secure_codec_) {
frame->metadata().hw_protected = true;
}
}
if (reset_generation == reset_generation_) {
// TODO(liberato): We might actually have a SW decoder. Consider setting
// this to false if so, especially for higher bitrates.
frame->metadata().power_efficient = true;
output_cb_.Run(std::move(frame));
}
}
// Our Reset() provides a slightly stronger guarantee than VideoDecoder does.
// After |closure| runs:
// 1) no VideoFrames from before the Reset() will be output, and
// 2) no DecodeCBs (including EOS) from before the Reset() will be run.
void MediaCodecVideoDecoder::Reset(base::OnceClosure closure) {
DVLOG(2) << __func__;
DCHECK(!reset_cb_);
reset_generation_++;
reset_cb_ = std::move(closure);
CancelPendingDecodes(DecoderStatus::Codes::kAborted);
StartDrainingCodec(DrainType::kForReset);
}
void MediaCodecVideoDecoder::StartDrainingCodec(DrainType drain_type) {
DVLOG(2) << __func__;
TRACE_EVENT0("media", "MediaCodecVideoDecoder::StartDrainingCodec");
DCHECK(pending_decodes_.empty());
// It's okay if there's already a drain ongoing. We'll only enqueue an EOS if
// the codec isn't already draining.
drain_type_ = drain_type;
// We can safely invalidate outstanding buffers for both types of drain, and
// doing so can only make the drain complete quicker. Note that we do this
// even if we're eliding the drain, since we're either going to flush the
// codec or destroy it. While we're not required to do this, it might affect
// stability if we don't (https://crbug.com/869365). AVDA, in particular,
// dropped all pending codec output buffers when starting a reset (seek) or
// a destroy.
if (codec_)
codec_->DiscardOutputBuffers();
// If the codec isn't already drained or flushed, then we have to remember
// that we owe it a flush. We also have to remember not to deliver any
// output buffers that might still be in progress in the codec.
deferred_flush_pending_ =
codec_ && !codec_->IsDrained() && !codec_->IsFlushed();
OnCodecDrained();
}
void MediaCodecVideoDecoder::OnCodecDrained() {
DVLOG(2) << __func__;
TRACE_EVENT0("media", "MediaCodecVideoDecoder::OnCodecDrained");
DrainType drain_type = *drain_type_;
drain_type_.reset();
if (drain_type == DrainType::kForDestroy) {
// Post the delete in case the caller uses |this| after we return.
base::SequencedTaskRunner::GetCurrentDefault()->DeleteSoon(FROM_HERE, this);
return;
}
std::move(reset_cb_).Run();
// Flush the codec unless (a) it's already flushed, (b) it's drained and the
// flush will be handled automatically on the next decode, or (c) we've
// elided the eos and want to defer the flush.
if (codec_ && !codec_->IsFlushed() && !codec_->IsDrained() &&
!deferred_flush_pending_) {
FlushCodec();
}
}
void MediaCodecVideoDecoder::EnterTerminalState(State state,
DecoderStatus reason) {
// Any nested error inside `reason` not displayed as the error is propagated
// to the caller.
DVLOG(2) << __func__ << " " << static_cast<int>(state) << " "
<< reason.message();
state_ = state;
DCHECK(InTerminalState());
// Cancel pending codec creation.
codec_allocator_weak_factory_.InvalidateWeakPtrs();
ReleaseCodec();
target_surface_bundle_ = nullptr;
texture_owner_bundle_ = nullptr;
if (state == State::kError) {
CancelPendingDecodes(reason);
} else {
MEDIA_LOG(INFO, media_log_)
<< "Entering Terminal State: " << reason.message();
}
if (drain_type_) {
OnCodecDrained();
}
}
bool MediaCodecVideoDecoder::InTerminalState() {
return state_ == State::kSurfaceDestroyed || state_ == State::kError;
}
void MediaCodecVideoDecoder::CancelPendingDecodes(DecoderStatus status) {
for (auto& pending_decode : pending_decodes_)
std::move(pending_decode.decode_cb).Run(status);
pending_decodes_.clear();
if (eos_decode_cb_)
std::move(eos_decode_cb_).Run(status);
}
void MediaCodecVideoDecoder::ReleaseCodec() {
if (!codec_)
return;
auto pair = codec_->TakeCodecSurfacePair();
codec_ = nullptr;
codec_allocator_->ReleaseMediaCodec(
std::move(pair.first),
base::BindOnce(
&base::SequencedTaskRunner::ReleaseSoon<CodecSurfaceBundle>,
base::SequencedTaskRunner::GetCurrentDefault(), FROM_HERE,
std::move(pair.second)));
}
AndroidOverlayFactoryCB MediaCodecVideoDecoder::CreateOverlayFactoryCb() {
if (!overlay_factory_cb_ || !overlay_info_.HasValidRoutingToken())
return AndroidOverlayFactoryCB();
return base::BindRepeating(overlay_factory_cb_, *overlay_info_.routing_token);
}
VideoDecoderType MediaCodecVideoDecoder::GetDecoderType() const {
return VideoDecoderType::kMediaCodec;
}
bool MediaCodecVideoDecoder::NeedsBitstreamConversion() const {
return true;
}
bool MediaCodecVideoDecoder::CanReadWithoutStalling() const {
// We should always be able to get at least two outputs, one in the front
// buffer slot and one in the back buffer slot unless we're waiting for
// rendering to happen.
const auto buffer_count =
codec_ ? codec_->GetUnreleasedOutputBufferCount() : 0;
return !video_frame_factory_->IsStalled() && buffer_count < 2;
}
int MediaCodecVideoDecoder::GetMaxDecodeRequests() const {
// We indicate that we're done decoding a frame as soon as we submit it to
// MediaCodec so the number of parallel decode requests just sets the upper
// limit of the size of our pending decode queue.
return 2;
}
PromotionHintAggregator::NotifyPromotionHintCB
MediaCodecVideoDecoder::CreatePromotionHintCB() {
// Right now, we don't request promotion hints. This is only used by SOP.
// While we could simplify it a bit, this is the general form that we'll use
// when handling promotion hints.
// Note that this keeps only a wp to the surface bundle via |layout_cb|. It
// also continues to work even if |this| is destroyed; images might want to
// move an overlay around even after MCVD has been torn down. For example
// inline L1 content will fall into this case.
return base::BindPostTaskToCurrentDefault(base::BindRepeating(
[](base::WeakPtr<MediaCodecVideoDecoder> mcvd,
CodecSurfaceBundle::ScheduleLayoutCB layout_cb,
PromotionHintAggregator::Hint hint) {
// If we're promotable, and we have a surface bundle, then also
// position the overlay. We could do this even if the overlay is
// not promotable, but it wouldn't have any visible effect.
if (hint.is_promotable)
layout_cb.Run(hint.screen_rect);
// Notify MCVD about the promotion hint, so that it can decide if it
// wants to switch to / from an overlay.
if (mcvd)
mcvd->NotifyPromotionHint(hint);
},
weak_factory_.GetWeakPtr(),
codec_->SurfaceBundle()->GetScheduleLayoutCB()));
}
bool MediaCodecVideoDecoder::IsUsingOverlay() const {
return codec_ && codec_->SurfaceBundle() &&
codec_->SurfaceBundle()->overlay();
}
void MediaCodecVideoDecoder::NotifyPromotionHint(
PromotionHintAggregator::Hint hint) {
surface_chooser_helper_.NotifyPromotionHintAndUpdateChooser(hint,
IsUsingOverlay());
}
void MediaCodecVideoDecoder::CacheFrameInformation() {
cached_frame_information_ =
surface_chooser_helper_.ComputeFrameInformation(IsUsingOverlay());
}
} // namespace media