#include "media/gpu/mac/video_toolbox_video_decoder.h"
#include <CoreMedia/CoreMedia.h>
#include <VideoToolbox/VideoToolbox.h>
#include <memory>
#include <utility>
#include "base/apple/scoped_cftyperef.h"
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/scoped_policy.h"
#include "base/task/bind_post_task.h"
#include "media/base/decoder_status.h"
#include "media/base/media_log.h"
#include "media/base/media_switches.h"
#include "media/base/supported_types.h"
#include "media/base/video_frame.h"
#include "media/gpu/accelerated_video_decoder.h"
#include "media/gpu/av1_decoder.h"
#include "media/gpu/h264_decoder.h"
#include "media/gpu/mac/video_toolbox_av1_accelerator.h"
#include "media/gpu/mac/video_toolbox_decompression_metadata.h"
#include "media/gpu/mac/video_toolbox_h264_accelerator.h"
#include "media/gpu/mac/video_toolbox_vp9_accelerator.h"
#include "media/gpu/vp9_decoder.h"
#include "ui/gfx/geometry/size.h"
#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
#include "media/gpu/h265_decoder.h"
#include "media/gpu/mac/video_toolbox_h265_accelerator.h"
#endif
namespace media {
namespace {
bool SupportsH264() {
return VTIsHardwareDecodeSupported(kCMVideoCodecType_H264);
}
bool InitializeVP9() {
#if BUILDFLAG(IS_MAC)
VTRegisterSupplementalVideoDecoderIfAvailable(kCMVideoCodecType_VP9);
return VTIsHardwareDecodeSupported(kCMVideoCodecType_VP9);
#else
return false;
#endif
}
bool SupportsVP9() {
static const bool initialized = InitializeVP9();
return initialized;
}
bool SupportsAV1() {
return VTIsHardwareDecodeSupported(kCMVideoCodecType_AV1);
}
#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
bool SupportsHEVC() {
return base::FeatureList::IsEnabled(media::kPlatformHEVCDecoderSupport);
}
#endif
}
VideoToolboxVideoDecoder::VideoToolboxVideoDecoder(
scoped_refptr<base::SequencedTaskRunner> task_runner,
std::unique_ptr<MediaLog> media_log,
const gpu::GpuDriverBugWorkarounds& gpu_workarounds,
scoped_refptr<base::SequencedTaskRunner> gpu_task_runner,
GetCommandBufferStubCB get_stub_cb)
: task_runner_(std::move(task_runner)),
media_log_(std::move(media_log)),
gpu_workarounds_(gpu_workarounds),
gpu_task_runner_(std::move(gpu_task_runner)),
get_stub_cb_(std::move(get_stub_cb)),
video_toolbox_(
task_runner_,
media_log_->Clone(),
base::BindRepeating(&VideoToolboxVideoDecoder::OnVideoToolboxOutput,
base::Unretained(this)),
base::BindRepeating(&VideoToolboxVideoDecoder::OnVideoToolboxError,
base::Unretained(this))),
output_queue_(task_runner_) {
DVLOG(1) << __func__;
}
VideoToolboxVideoDecoder::~VideoToolboxVideoDecoder() {
DVLOG(1) << __func__;
}
bool VideoToolboxVideoDecoder::NeedsBitstreamConversion() const {
DVLOG(4) << __func__;
return true;
}
int VideoToolboxVideoDecoder::GetMaxDecodeRequests() const {
DVLOG(4) << __func__;
return 4;
}
VideoDecoderType VideoToolboxVideoDecoder::GetDecoderType() const {
DVLOG(4) << __func__;
return VideoDecoderType::kVideoToolbox;
}
void VideoToolboxVideoDecoder::Initialize(const VideoDecoderConfig& config,
bool low_delay,
CdmContext* cdm_context,
InitCB init_cb,
const OutputCB& output_cb,
const WaitingCB& waiting_cb) {
DVLOG(1) << __func__;
DCHECK(decode_cbs_.empty());
DCHECK(config.IsValidConfig());
if (has_error_) {
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(std::move(init_cb), DecoderStatus::Codes::kFailed));
return;
}
bool profile_supported = false;
for (const auto& supported_config :
GetSupportedVideoDecoderConfigs(gpu_workarounds_)) {
if (supported_config.profile_min <= config.profile() &&
config.profile() <= supported_config.profile_max) {
profile_supported = true;
break;
}
}
if (!profile_supported && IsDecoderBuiltInVideoCodec(config.codec())) {
task_runner_->PostTask(
FROM_HERE, base::BindOnce(std::move(init_cb),
DecoderStatus::Codes::kUnsupportedProfile));
NotifyError(DecoderStatus::Codes::kUnsupportedProfile);
return;
}
if (config.is_encrypted()) {
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(std::move(init_cb),
DecoderStatus::Codes::kUnsupportedEncryptionMode));
NotifyError(DecoderStatus::Codes::kUnsupportedEncryptionMode);
return;
}
if (accelerator_) {
ResetInternal(DecoderStatus::Codes::kAborted);
}
auto accelerator_decode_cb = base::BindRepeating(
&VideoToolboxVideoDecoder::OnAcceleratorDecode, base::Unretained(this));
auto accelerator_output_cb = base::BindRepeating(
&VideoToolboxVideoDecoder::OnAcceleratorOutput, base::Unretained(this));
switch (VideoCodecProfileToVideoCodec(config.profile())) {
case VideoCodec::kH264:
accelerator_ = std::make_unique<H264Decoder>(
std::make_unique<VideoToolboxH264Accelerator>(
media_log_->Clone(), std::move(accelerator_decode_cb),
std::move(accelerator_output_cb)),
config.profile(), config.color_space_info());
break;
case VideoCodec::kVP9:
accelerator_ = std::make_unique<VP9Decoder>(
std::make_unique<VideoToolboxVP9Accelerator>(
media_log_->Clone(), config.hdr_metadata(),
std::move(accelerator_decode_cb),
std::move(accelerator_output_cb)),
config.profile(), config.color_space_info());
break;
case VideoCodec::kAV1:
accelerator_ = std::make_unique<AV1Decoder>(
std::make_unique<VideoToolboxAV1Accelerator>(
media_log_->Clone(), config.hdr_metadata(),
std::move(accelerator_decode_cb),
std::move(accelerator_output_cb)),
config.profile(), config.color_space_info());
break;
#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
case VideoCodec::kHEVC:
accelerator_ = std::make_unique<H265Decoder>(
std::make_unique<VideoToolboxH265Accelerator>(
media_log_->Clone(), std::move(accelerator_decode_cb),
std::move(accelerator_output_cb)),
config.profile(), config.color_space_info());
break;
#endif
default:
task_runner_->PostTask(
FROM_HERE, base::BindOnce(std::move(init_cb),
DecoderStatus::Codes::kUnsupportedCodec));
NotifyError(DecoderStatus::Codes::kUnsupportedCodec);
return;
}
config_ = config;
output_queue_.SetOutputCB(output_cb);
task_runner_->PostTask(
FROM_HERE, base::BindOnce(std::move(init_cb), DecoderStatus::Codes::kOk));
}
void VideoToolboxVideoDecoder::Decode(scoped_refptr<DecoderBuffer> buffer,
DecodeCB decode_cb) {
DVLOG(3) << __func__ << " buffer=" << buffer->AsHumanReadableString();
if (has_error_) {
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(std::move(decode_cb), DecoderStatus::Codes::kFailed));
return;
}
if (buffer->end_of_stream()) {
if (!accelerator_->Flush()) {
task_runner_->PostTask(
FROM_HERE, base::BindOnce(std::move(decode_cb),
DecoderStatus::Codes::kMalformedBitstream));
NotifyError(DecoderStatus::Codes::kMalformedBitstream);
return;
}
output_queue_.Flush(std::move(decode_cb));
return;
}
decode_cbs_.push(std::move(decode_cb));
accelerator_->SetStream(-1, buffer);
while (true) {
active_decode_ = buffer;
AcceleratedVideoDecoder::DecodeResult result = accelerator_->Decode();
active_decode_.reset();
switch (result) {
case AcceleratedVideoDecoder::kDecodeError:
case AcceleratedVideoDecoder::kRanOutOfSurfaces:
case AcceleratedVideoDecoder::kTryAgain:
NotifyError(DecoderStatus::Codes::kMalformedBitstream);
return;
case AcceleratedVideoDecoder::kConfigChange:
continue;
case AcceleratedVideoDecoder::kRanOutOfStreamData:
ReleaseDecodeCallbacks();
return;
}
}
}
void VideoToolboxVideoDecoder::Reset(base::OnceClosure reset_cb) {
DVLOG(1) << __func__;
if (has_error_) {
task_runner_->PostTask(FROM_HERE, std::move(reset_cb));
return;
}
ResetInternal(DecoderStatus::Codes::kAborted);
task_runner_->PostTask(FROM_HERE, std::move(reset_cb));
}
void VideoToolboxVideoDecoder::NotifyError(DecoderStatus status) {
DVLOG(1) << __func__;
if (has_error_) {
return;
}
has_error_ = true;
ResetInternal(status);
}
void VideoToolboxVideoDecoder::ResetInternal(DecoderStatus status) {
DVLOG(4) << __func__;
while (!decode_cbs_.empty()) {
task_runner_->PostTask(
FROM_HERE, base::BindOnce(std::move(decode_cbs_.front()), status));
decode_cbs_.pop();
}
if (accelerator_) {
accelerator_->Reset();
}
video_toolbox_.Reset();
output_queue_.Reset(status);
converter_weak_this_factory_.InvalidateWeakPtrs();
num_conversions_ = 0;
}
void VideoToolboxVideoDecoder::ReleaseDecodeCallbacks() {
DVLOG(4) << __func__;
DCHECK(!has_error_);
while (decode_cbs_.size() > video_toolbox_.NumDecodes() + num_conversions_) {
task_runner_->PostTask(FROM_HERE,
base::BindOnce(std::move(decode_cbs_.front()),
DecoderStatus::Codes::kOk));
decode_cbs_.pop();
}
}
void VideoToolboxVideoDecoder::OnAcceleratorDecode(
base::apple::ScopedCFTypeRef<CMSampleBufferRef> sample,
VideoToolboxDecompressionSessionMetadata session_metadata,
scoped_refptr<CodecPicture> picture) {
DVLOG(4) << __func__
<< " pts=" << active_decode_->timestamp().InMilliseconds();
DCHECK(active_decode_);
auto metadata = std::make_unique<VideoToolboxDecodeMetadata>();
metadata->picture = std::move(picture);
metadata->timestamp = active_decode_->timestamp();
metadata->duration = active_decode_->duration();
metadata->aspect_ratio = config_.aspect_ratio();
metadata->color_space = accelerator_->GetVideoColorSpace().ToGfxColorSpace();
if (!metadata->color_space.IsValid()) {
metadata->color_space = config_.color_space_info().ToGfxColorSpace();
}
metadata->hdr_metadata = accelerator_->GetHDRMetadata();
if (!metadata->hdr_metadata) {
metadata->hdr_metadata = config_.hdr_metadata();
}
metadata->session_metadata = session_metadata;
video_toolbox_.Decode(std::move(sample), std::move(metadata));
}
void VideoToolboxVideoDecoder::OnAcceleratorOutput(
scoped_refptr<CodecPicture> picture) {
DVLOG(3) << __func__;
output_queue_.SchedulePicture(std::move(picture));
}
void VideoToolboxVideoDecoder::OnVideoToolboxOutput(
base::apple::ScopedCFTypeRef<CVImageBufferRef> image,
std::unique_ptr<VideoToolboxDecodeMetadata> metadata) {
DVLOG(4) << __func__ << " pts=" << metadata->timestamp.InMilliseconds();
if (has_error_) {
return;
}
if (!image) {
ReleaseDecodeCallbacks();
return;
}
if (!converter_) {
converter_ = base::MakeRefCounted<VideoToolboxFrameConverter>(
gpu_task_runner_, media_log_->Clone(), std::move(get_stub_cb_));
}
gpu_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&VideoToolboxFrameConverter::Convert, converter_, std::move(image),
std::move(metadata),
base::BindPostTask(
task_runner_,
base::BindOnce(&VideoToolboxVideoDecoder::OnConverterOutput,
converter_weak_this_factory_.GetWeakPtr()))));
++num_conversions_;
}
void VideoToolboxVideoDecoder::OnVideoToolboxError(DecoderStatus status) {
DVLOG(1) << __func__;
NotifyError(std::move(status));
}
void VideoToolboxVideoDecoder::OnConverterOutput(
scoped_refptr<VideoFrame> frame,
std::unique_ptr<VideoToolboxDecodeMetadata> metadata) {
DVLOG(4) << __func__ << " pts=" << metadata->timestamp.InMilliseconds();
if (has_error_) {
return;
}
if (!frame) {
NotifyError(DecoderStatus::Codes::kFailedToGetVideoFrame);
return;
}
CHECK_GT(num_conversions_, 0u);
--num_conversions_;
ReleaseDecodeCallbacks();
output_queue_.FulfillPicture(std::move(metadata->picture), std::move(frame));
}
std::vector<SupportedVideoDecoderConfig>
VideoToolboxVideoDecoder::GetSupportedVideoDecoderConfigs(
const gpu::GpuDriverBugWorkarounds& gpu_workarounds) {
std::vector<SupportedVideoDecoderConfig> supported;
#ifdef ARCH_CPU_X86_FAMILY
constexpr gfx::Size kMinSize(16, 16);
#else
constexpr gfx::Size kMinSize(64, 64);
#endif
constexpr gfx::Size kLegacyMaxSize(4096, 4096);
constexpr gfx::Size kModernMaxSize(8192, 8192);
if (!gpu_workarounds.disable_accelerated_h264_decode && SupportsH264()) {
supported.emplace_back(
H264PROFILE_BASELINE,
H264PROFILE_HIGH,
kMinSize,
kLegacyMaxSize,
false,
false);
}
if (!gpu_workarounds.disable_accelerated_vp9_decode && SupportsVP9()) {
supported.emplace_back(
VP9PROFILE_PROFILE0,
VP9PROFILE_PROFILE0,
kMinSize,
kLegacyMaxSize,
false,
false);
if (!gpu_workarounds.disable_accelerated_vp9_profile2_decode) {
supported.emplace_back(
VP9PROFILE_PROFILE2,
VP9PROFILE_PROFILE2,
kMinSize,
kLegacyMaxSize,
false,
false);
}
}
if (!gpu_workarounds.disable_accelerated_av1_decode && SupportsAV1()) {
supported.emplace_back(
AV1PROFILE_PROFILE_MAIN,
AV1PROFILE_PROFILE_MAIN,
kMinSize,
kModernMaxSize,
false,
false);
}
#if BUILDFLAG(ENABLE_HEVC_PARSER_AND_HW_DECODER)
if (!gpu_workarounds.disable_accelerated_hevc_decode && SupportsHEVC()) {
constexpr gfx::Size kHevcMinSize(16, 16);
supported.emplace_back(
HEVCPROFILE_MIN,
HEVCPROFILE_MAX,
kHevcMinSize,
kModernMaxSize,
false,
false);
supported.emplace_back(
HEVCPROFILE_REXT,
HEVCPROFILE_REXT,
kHevcMinSize,
kModernMaxSize,
false,
false);
}
#endif
return supported;
}
}