diff --git a/media/gpu/mac/vt_video_encode_accelerator_mac.h b/media/gpu/mac/vt_video_encode_accelerator_mac.h index 606b15dc82a8a..3113eef0106dd 100644 --- a/media/gpu/mac/vt_video_encode_accelerator_mac.h +++ b/media/gpu/mac/vt_video_encode_accelerator_mac.h @@ -44,6 +44,8 @@ class MEDIA_GPU_EXPORT VTVideoEncodeAccelerator Client* client, std::unique_ptr<MediaLog> media_log = nullptr) override; void Encode(scoped_refptr<VideoFrame> frame, bool force_keyframe) override; + void Encode(scoped_refptr<VideoFrame> frame, + const VideoEncoder::EncodeOptions& options) override; void UseOutputBitstreamBuffer(BitstreamBuffer buffer) override; void RequestEncodingParametersChange( const Bitrate& bitrate, diff --git a/media/gpu/mac/vt_video_encode_accelerator_mac.mm b/media/gpu/mac/vt_video_encode_accelerator_mac.mm index 78a7ebdb226d8..78ac8a4a8dcc7 100644 --- a/media/gpu/mac/vt_video_encode_accelerator_mac.mm +++ b/media/gpu/mac/vt_video_encode_accelerator_mac.mm @@ -7,6 +7,7 @@ #import <Foundation/Foundation.h> #include <memory> +#include <optional> #include "base/apple/bridging.h" #include "base/apple/foundation_util.h" @@ -59,6 +60,7 @@ constexpr size_t kMaxFrameRateNumerator = 120; constexpr size_t kMaxFrameRateDenominator = 1; constexpr size_t kNumInputBuffers = 3; constexpr gfx::Size kDefaultSupportedResolution = gfx::Size(640, 480); +constexpr int kH26xMaxQp = 51; #if SOFTWARE_ENCODING_SUPPORTED // The IDs of the encoders that may be selected when we enable low latency via @@ -168,6 +170,18 @@ bool IsSVCSupported(VideoCodec codec) { return false; } +bool IsManualQpSupported(VideoCodec codec) { + // Querying `kVTCompressionPropertyKey_SupportsBaseFrameQP` is the + // way Apple recommends to test whether per frame QP is supported by a + // given encoder. Based on tests on Intel and Apple Silicon Macs, + // the property `kVTCompressionPropertyKey_SupportsBaseFrameQP` will + // only report `supported` for encoders created with + // `kVTVideoEncoderSpecification_EnableLowLatencyRateControl` set to + // `true`. Thus, we assume external mode is supported if SVC is + // supported. + return IsSVCSupported(codec); +} + static CFStringRef VideoCodecProfileToVTProfile(VideoCodecProfile profile) { switch (profile) { case H264PROFILE_BASELINE: @@ -395,6 +409,7 @@ VideoEncoderInfo GetVideoEncoderInfo( constexpr uint8_t kFullFramerate = 255; info.fps_allocation[0] = {kFullFramerate}; } + CHECK(info.reports_average_qp); return info; } @@ -403,10 +418,12 @@ VideoEncoderInfo GetVideoEncoderInfo( struct VTVideoEncodeAccelerator::InProgressFrameEncode { InProgressFrameEncode(scoped_refptr<VideoFrame> frame, - const gfx::ColorSpace& frame_cs) - : frame(frame), encoded_color_space(frame_cs) {} + const gfx::ColorSpace& frame_cs, + std::optional<int> frame_qp) + : frame(frame), encoded_color_space(frame_cs), qp(frame_qp) {} const scoped_refptr<VideoFrame> frame; const gfx::ColorSpace encoded_color_space; + const std::optional<int> qp; }; struct VTVideoEncodeAccelerator::EncodeOutput { @@ -418,7 +435,8 @@ struct VTVideoEncodeAccelerator::EncodeOutput { : info(info_flags), sample_buffer(sbuf, base::scoped_policy::RETAIN), capture_timestamp(frame_info.frame->timestamp()), - encoded_color_space(frame_info.encoded_color_space) {} + encoded_color_space(frame_info.encoded_color_space), + qp(frame_info.qp) {} EncodeOutput(const EncodeOutput&) = delete; EncodeOutput& operator=(const EncodeOutput&) = delete; @@ -427,6 +445,7 @@ struct VTVideoEncodeAccelerator::EncodeOutput { const base::apple::ScopedCFTypeRef<CMSampleBufferRef> sample_buffer; const base::TimeDelta capture_timestamp; const gfx::ColorSpace encoded_color_space; + const std::optional<int> qp; }; struct VTVideoEncodeAccelerator::BitstreamBufferRef { @@ -472,8 +491,9 @@ VTVideoEncodeAccelerator::GetSupportedProfiles() { supported_profile.max_framerate_denominator = kMaxFrameRateDenominator; // Advertise VBR here, even though the peak bitrate is never actually used. // See RequestEncodingParametersChange() for more details. - supported_profile.rate_control_modes = VideoEncodeAccelerator::kConstantMode | - VideoEncodeAccelerator::kVariableMode; + const SupportedRateControlMode always_supported_rate_control_modes = + VideoEncodeAccelerator::kConstantMode | + VideoEncodeAccelerator::kVariableMode; // L1T1 = no additional spatial and temporal layer = always supported. const std::vector<SVCScalabilityMode> always_supported_scalability_modes{ SVCScalabilityMode::kL1T1}; @@ -497,10 +517,16 @@ VTVideoEncodeAccelerator::GetSupportedProfiles() { supported_profile.min_resolution = min_resolution; supported_profile.is_software_codec = false; supported_profile.scalability_modes = always_supported_scalability_modes; + supported_profile.rate_control_modes = + always_supported_rate_control_modes; if (IsSVCSupported(codec)) { supported_profile.scalability_modes.push_back( SVCScalabilityMode::kL1T2); } + if (IsManualQpSupported(codec)) { + supported_profile.rate_control_modes |= + VideoEncodeAccelerator::kExternalMode; + } if (can_create_hardware_session[codec]) { supported_profiles.push_back(supported_profile); @@ -519,6 +545,8 @@ VTVideoEncodeAccelerator::GetSupportedProfiles() { // and if you set `no-preference`, VT will always emit an error. Thus, // we should just re-use min resolutions of HW encoder for SW encoder. supported_profile.scalability_modes = always_supported_scalability_modes; + supported_profile.rate_control_modes = + always_supported_rate_control_modes; supported_profile.is_software_codec = true; supported_profiles.push_back(supported_profile); @@ -573,6 +601,18 @@ bool VTVideoEncodeAccelerator::Initialize(const Config& config, return false; } + if (config.bitrate.mode() == Bitrate::Mode::kExternal) { + if (!IsManualQpSupported(codec_)) { + MEDIA_LOG(ERROR, media_log) << "External bitrate mode is not supported."; + return false; + } + if (!require_low_delay_) { + MEDIA_LOG(INFO, media_log) + << "Force enable low delay encoding for external bitrate mode."; + require_low_delay_ = true; + } + } + if (!ResetCompressionSession()) { MEDIA_LOG(ERROR, media_log) << "Failed creating compression session."; return false; @@ -595,6 +635,12 @@ bool VTVideoEncodeAccelerator::Initialize(const Config& config, void VTVideoEncodeAccelerator::Encode(scoped_refptr<VideoFrame> frame, bool force_keyframe) { + Encode(std::move(frame), VideoEncoder::EncodeOptions(force_keyframe)); +} + +void VTVideoEncodeAccelerator::Encode( + scoped_refptr<VideoFrame> frame, + const VideoEncoder::EncodeOptions& options) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(compression_session_); DCHECK(frame); @@ -639,10 +685,21 @@ void VTVideoEncodeAccelerator::Encode(scoped_refptr<VideoFrame> frame, } } - NSDictionary* frame_props = @{ - CFToNSPtrCast(kVTEncodeFrameOptionKey_ForceKeyFrame) : force_keyframe ? @YES - : @NO - }; + NSMutableDictionary* frame_props = [NSMutableDictionary dictionary]; + frame_props[CFToNSPtrCast(kVTEncodeFrameOptionKey_ForceKeyFrame)] = + options.key_frame ? @YES : @NO; + + std::optional<int> frame_qp; + if (@available(macOS LOW_LATENCY_AND_SVC_AVAILABLE_VER, *)) { + if (IsManualQpSupported(codec_) && + bitrate_.mode() == Bitrate::Mode::kExternal && + options.quantizer.has_value()) { + DCHECK(require_low_delay_); + frame_qp = std::clamp(options.quantizer.value(), 1, kH26xMaxQp); + frame_props[CFToNSPtrCast(kVTEncodeFrameOptionKey_BaseFrameQP)] = + @(frame_qp.value()); + } + } // VideoToolbox uses timestamps for rate control purposes, but we can't rely // on real frame timestamps to be consistent with configured frame rate. @@ -658,7 +715,8 @@ void VTVideoEncodeAccelerator::Encode(scoped_refptr<VideoFrame> frame, // Wrap information we'll need after the frame is encoded in a heap object. // We'll get the pointer back from the VideoToolbox completion callback. auto request = std::make_unique<InProgressFrameEncode>( - std::move(frame), encoder_color_space_.value_or(gfx::ColorSpace())); + std::move(frame), encoder_color_space_.value_or(gfx::ColorSpace()), + frame_qp); // We can pass the ownership of |request| to the encode callback if // successful. Otherwise let it fall out of scope. @@ -753,7 +811,13 @@ void VTVideoEncodeAccelerator::RequestEncodingParametersChange( {EncoderStatus::Codes::kSystemAPICallError, "Can't change frame rate"}); return; } - if (!session_property_setter.Set( + if (bitrate.mode() != bitrate_.mode()) { + NotifyErrorStatus({EncoderStatus::Codes::kEncoderUnsupportedConfig, + "Can't change bitrate mode after Initialize()"}); + return; + } + if (bitrate.mode() != Bitrate::Mode::kExternal && + !session_property_setter.Set( kVTCompressionPropertyKey_AverageBitRate, static_cast<int32_t>(bitrate.target_bps()))) { NotifyErrorStatus({EncoderStatus::Codes::kSystemAPICallError, @@ -933,6 +997,9 @@ void VTVideoEncodeAccelerator::ReturnBitstreamBuffer( } md.encoded_color_space = encode_output->encoded_color_space; + if (encode_output->qp.has_value()) { + md.qp = encode_output->qp.value(); + } client_->BitstreamBufferReady(buffer_ref->id, std::move(md)); MaybeRunFlushCallback();