/* GStreamer * Copyright (C) 2024 Seungha Yang * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstd3d12h264enc.h" #include "gstd3d12encoder.h" #include "gstd3d12dpbstorage.h" #include "gstd3d12pluginutils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* *INDENT-OFF* */ using namespace Microsoft::WRL; /* *INDENT-ON* */ GST_DEBUG_CATEGORY_STATIC (gst_d3d12_h264_enc_debug); #define GST_CAT_DEFAULT gst_d3d12_h264_enc_debug enum { PROP_0, PROP_RATE_CONTROL_SUPPORT, PROP_SLICE_MODE_SUPPORT, PROP_AUD, PROP_GOP_SIZE, PROP_REF_FRAMES, PROP_FRAME_ANALYSIS, PROP_RATE_CONTROL, PROP_BITRATE, PROP_MAX_BITRATE, PROP_QVBR_QUALITY, PROP_QP_INIT, PROP_QP_MIN, PROP_QP_MAX, PROP_QP_I, PROP_QP_P, PROP_QP_B, PROP_SLICE_MODE, PROP_SLICE_PARTITION, PROP_CC_INSERT, }; #define DEFAULT_AUD TRUE #define DEFAULT_FRAME_ANALYSIS FALSE #define DEFAULT_GOP_SIZE 60 #define DEFAULT_RATE_CONTROL D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR #define DEFAULT_BITRATE 2000 #define DEFAULT_MAX_BITRATE 4000 #define DEFAULT_QVBR_QUALITY 23 #define DEFAULT_QP 0 #define DEFAULT_CQP 23 #define DEFAULT_REF_FRAMES 0 #define DEFAULT_SLICE_MODE D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_FULL_FRAME #define DEFAULT_SLICE_PARTITION 0 #define DEFAULT_CC_INSERT GST_D3D12_ENCODER_SEI_INSERT struct GstD3D12H264EncClassData { gint64 luid; guint device_id; guint vendor_id; gchar *description; GstCaps *sink_caps; GstCaps *src_caps; guint rc_support; guint slice_mode_support; }; /* *INDENT-OFF* */ class GstD3D12H264EncGop { public: void Init (guint gop_length) { /* TODO: pic_order_cnt_type == 0 for b-frame */ gop_struct_.pic_order_cnt_type = 2; if (gop_length == 1) gop_struct_.PPicturePeriod = 0; else gop_struct_.PPicturePeriod = 1; /* 0 means infinite */ if (gop_length == 0) { gop_struct_.GOPLength = 0; gop_struct_.log2_max_frame_num_minus4 = 12; } else { /* count bits */ guint val = gop_length; guint num_bits = 0; while (val) { num_bits++; val >>= 1; } if (num_bits < 4) gop_struct_.log2_max_frame_num_minus4 = 0; else if (num_bits > 16) gop_struct_.log2_max_frame_num_minus4 = 12; else gop_struct_.log2_max_frame_num_minus4 = num_bits - 4; gop_struct_.GOPLength = gop_length; } MaxFrameNum_ = 1 << (gop_struct_.log2_max_frame_num_minus4 + 4); if (gop_struct_.pic_order_cnt_type == 2) { /* unused */ gop_struct_.log2_max_pic_order_cnt_lsb_minus4 = 0; MaxPicOrderCnt_ = MaxFrameNum_ * 2; } else { guint log2_max_pic_order_cnt = gop_struct_.log2_max_frame_num_minus4 + 5; log2_max_pic_order_cnt = MIN (16, log2_max_pic_order_cnt); gop_struct_.log2_max_pic_order_cnt_lsb_minus4 = log2_max_pic_order_cnt - 4; MaxPicOrderCnt_ = 1 << log2_max_pic_order_cnt; } gop_start_ = true; frame_num_ = 0; encode_order_ = 0; } D3D12_VIDEO_ENCODER_SEQUENCE_GOP_STRUCTURE_H264 GetGopStruct () { return gop_struct_; } void FillPicCtrl (D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264 & pic_ctrl) { if (gop_start_) { pic_ctrl.FrameType = D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_IDR_FRAME; pic_ctrl.idr_pic_id = idr_pic_id_; pic_ctrl.FrameDecodingOrderNumber = 0; pic_ctrl.PictureOrderCountNumber = 0; pic_ctrl.TemporalLayerIndex = 0; idr_pic_id_++; gop_start_ = false; } else { pic_ctrl.FrameType = D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_P_FRAME; pic_ctrl.idr_pic_id = idr_pic_id_; pic_ctrl.FrameDecodingOrderNumber = frame_num_; pic_ctrl.PictureOrderCountNumber = ((guint) frame_num_) * 2; pic_ctrl.TemporalLayerIndex = 0; } /* And increase frame num */ /* FIXME: frame_num = (frame_num_of_pref_ref_pic + 1) % ... */ frame_num_ = (frame_num_ + 1) % MaxFrameNum_; encode_order_++; if (gop_struct_.GOPLength != 0 && encode_order_ >= gop_struct_.GOPLength) { frame_num_ = 0; encode_order_ = 0; gop_start_ = true; } } void ForceKeyUnit () { frame_num_ = 0; encode_order_ = 0; gop_start_ = true; } private: D3D12_VIDEO_ENCODER_SEQUENCE_GOP_STRUCTURE_H264 gop_struct_ = { }; guint16 frame_num_ = 0; guint16 idr_pic_id_ = 0; guint32 MaxFrameNum_ = 16; guint32 MaxPicOrderCnt_ = 16; guint64 encode_order_ = 0; bool gop_start_ = false; }; class GstD3D12H264EncDpb { public: GstD3D12H264EncDpb (GstD3D12Device * device, UINT width, UINT height, UINT max_dpb_size, bool array_of_textures) { max_dpb_size_ = max_dpb_size; if (max_dpb_size_ > 0) { storage_ = gst_d3d12_dpb_storage_new (device, max_dpb_size + 1, array_of_textures, DXGI_FORMAT_NV12, width, height, D3D12_RESOURCE_FLAG_VIDEO_ENCODE_REFERENCE_ONLY | D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE); } } ~GstD3D12H264EncDpb () { gst_clear_object (&storage_); } bool IsValid () { if (max_dpb_size_ > 0 && !storage_) return false; return true; } bool StartFrame (bool is_reference, D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264 * ctrl_data, D3D12_VIDEO_ENCODER_RECONSTRUCTED_PICTURE * recon_pic, D3D12_VIDEO_ENCODE_REFERENCE_FRAMES * ref_frames, UINT64 display_order) { ctrl_data_ = *ctrl_data; cur_display_order_ = display_order; cur_frame_is_ref_ = is_reference; recon_pic_.pReconstructedPicture = nullptr; recon_pic_.ReconstructedPictureSubresource = 0; if (max_dpb_size_ > 0 && ctrl_data_.FrameType == D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_IDR_FRAME) { ref_pic_desc_.clear(); ref_pic_display_order_.clear (); gst_d3d12_dpb_storage_clear_dpb (storage_); } if (is_reference) { g_assert (max_dpb_size_ > 0); if (!gst_d3d12_dpb_storage_acquire_frame (storage_, &recon_pic_)) return false; } *recon_pic = recon_pic_; switch (ctrl_data_.FrameType) { case D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_P_FRAME: case D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_B_FRAME: g_assert (max_dpb_size_ > 0); gst_d3d12_dpb_storage_get_reference_frames (storage_, ref_frames); break; default: ref_frames->NumTexture2Ds = 0; ref_frames->ppTexture2Ds = nullptr; ref_frames->pSubresources = nullptr; break; } list0_.clear (); list1_.clear (); bool build_l0 = (ctrl_data_.FrameType == D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_P_FRAME) || (ctrl_data_.FrameType == D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_B_FRAME); bool build_l1 = ctrl_data_.FrameType == D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_B_FRAME; if (build_l0) { for (UINT i = 0; i < (UINT) ref_pic_display_order_.size (); i++) { if (ref_pic_display_order_[i] < display_order) list0_.push_back (i); } } if (build_l1) { for (UINT i = 0; i < (UINT) ref_pic_display_order_.size (); i++) { if (ref_pic_display_order_[i] > display_order) list1_.push_back (i); } } ctrl_data->List0ReferenceFramesCount = list0_.size (); ctrl_data->pList0ReferenceFrames = list0_.empty () ? nullptr : list0_.data (); ctrl_data->List1ReferenceFramesCount = list1_.size (); ctrl_data->pList1ReferenceFrames = list1_.empty () ? nullptr : list1_.data (); ctrl_data->ReferenceFramesReconPictureDescriptorsCount = ref_pic_desc_.size (); ctrl_data->pReferenceFramesReconPictureDescriptors = ref_pic_desc_.empty () ? nullptr : ref_pic_desc_.data (); return true; } void EndFrame () { if (!cur_frame_is_ref_ || max_dpb_size_ == 0) return; if (gst_d3d12_dpb_storage_get_dpb_size (storage_) == max_dpb_size_) { gst_d3d12_dpb_storage_remove_oldest_frame (storage_); ref_pic_display_order_.pop_back (); ref_pic_desc_.pop_back (); } gst_d3d12_dpb_storage_add_frame (storage_, &recon_pic_); D3D12_VIDEO_ENCODER_REFERENCE_PICTURE_DESCRIPTOR_H264 desc = { }; desc.ReconstructedPictureResourceIndex = 0; desc.IsLongTermReference = FALSE; desc.PictureOrderCountNumber = ctrl_data_.PictureOrderCountNumber; desc.FrameDecodingOrderNumber = ctrl_data_.FrameDecodingOrderNumber; desc.TemporalLayerIndex = 0; ref_pic_display_order_.insert (ref_pic_display_order_.begin (), cur_display_order_); ref_pic_desc_.insert (ref_pic_desc_.begin(), desc); for (UINT i = 1; i < ref_pic_desc_.size (); i++) ref_pic_desc_[i].ReconstructedPictureResourceIndex = i; } private: std::vector ref_pic_desc_; std::vector ref_pic_display_order_; D3D12_VIDEO_ENCODER_RECONSTRUCTED_PICTURE recon_pic_ = { }; D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264 ctrl_data_ = { }; std::vector list0_; std::vector list1_; UINT max_dpb_size_ = 0; UINT64 cur_display_order_ = 0; bool cur_frame_is_ref_ = false; GstD3D12DpbStorage *storage_ = nullptr; }; struct GstD3D12H264SPS { void Clear () { memset (&sps, 0, sizeof (GstH264SPS)); bytes.clear (); } GstH264SPS sps; std::vector bytes; }; struct GstD3D12H264PPS { void Clear () { memset (&pps, 0, sizeof (GstH264PPS)); bytes.clear (); } GstH264PPS pps; std::vector bytes; }; struct GstD3D12H264EncPrivate { GstD3D12H264EncPrivate () { cc_sei = g_array_new (FALSE, FALSE, sizeof (GstH264SEIMessage)); g_array_set_clear_func (cc_sei, (GDestroyNotify) gst_h264_sei_clear); } ~GstD3D12H264EncPrivate () { g_array_unref (cc_sei); } GstVideoInfo info; GstD3D12H264SPS sps; std::vector pps; GstH264Profile selected_profile; GstD3D12H264EncGop gop; std::unique_ptr dpb; guint last_pps_id = 0; guint64 display_order = 0; GArray *cc_sei; std::mutex prop_lock; GstD3D12EncoderConfig encoder_config = { }; D3D12_VIDEO_ENCODER_PROFILE_H264 profile_h264 = D3D12_VIDEO_ENCODER_PROFILE_H264_MAIN; D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264 config_h264 = { }; D3D12_VIDEO_ENCODER_LEVELS_H264 level_h264; D3D12_VIDEO_ENCODER_PICTURE_CONTROL_SUBREGIONS_LAYOUT_DATA_SLICES layout_slices = { }; D3D12_VIDEO_ENCODER_SEQUENCE_GOP_STRUCTURE_H264 gop_struct_h264 = { }; D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264 pic_control_h264 = { }; D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE selected_rc_mode = D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_ABSOLUTE_QP_MAP; D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE selected_slice_mode = DEFAULT_SLICE_MODE; guint selected_ref_frames = 0; D3D12_VIDEO_ENCODER_CODEC_PICTURE_CONTROL_SUPPORT_H264 pic_ctrl_support = { }; /* properties */ gboolean aud = DEFAULT_AUD; /* gop struct related */ guint gop_size = DEFAULT_GOP_SIZE; guint ref_frames = DEFAULT_REF_FRAMES; gboolean gop_updated = FALSE; /* rate control config */ D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE rc_mode = DEFAULT_RATE_CONTROL; gboolean frame_analysis = DEFAULT_FRAME_ANALYSIS; gboolean rc_flag_updated = FALSE; guint bitrate = DEFAULT_BITRATE; guint max_bitrate = DEFAULT_MAX_BITRATE; guint qvbr_quality = DEFAULT_QVBR_QUALITY; guint qp_init = DEFAULT_QP; guint qp_min = DEFAULT_QP; guint qp_max = DEFAULT_QP; guint qp_i = DEFAULT_CQP; guint qp_p = DEFAULT_CQP; guint qp_b = DEFAULT_CQP; gboolean rc_updated = FALSE; /* slice mode */ D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE slice_mode = DEFAULT_SLICE_MODE; guint slice_partition = DEFAULT_SLICE_PARTITION; gboolean slice_updated = FALSE; GstD3D12EncoderSeiInsertMode cc_insert = DEFAULT_CC_INSERT; }; /* *INDENT-ON* */ struct GstD3D12H264Enc { GstD3D12Encoder parent; GstD3D12H264EncPrivate *priv; }; struct GstD3D12H264EncClass { GstD3D12EncoderClass parent_class; GstD3D12H264EncClassData *cdata; }; static inline GstD3D12H264Enc * GST_D3D12_H264_ENC (gpointer ptr) { return (GstD3D12H264Enc *) ptr; } static inline GstD3D12H264EncClass * GST_D3D12_H264_ENC_GET_CLASS (gpointer ptr) { return G_TYPE_INSTANCE_GET_CLASS (ptr, G_TYPE_FROM_INSTANCE (ptr), GstD3D12H264EncClass); } static void gst_d3d12_h264_enc_finalize (GObject * object); static void gst_d3d12_h264_enc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_d3d12_h264_enc_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static gboolean gst_d3d12_h264_enc_start (GstVideoEncoder * encoder); static gboolean gst_d3d12_h264_enc_stop (GstVideoEncoder * encoder); static gboolean gst_d3d12_h264_enc_transform_meta (GstVideoEncoder * encoder, GstVideoCodecFrame * frame, GstMeta * meta); static gboolean gst_d3d12_h264_enc_new_sequence (GstD3D12Encoder * encoder, ID3D12VideoDevice * video_device, GstVideoCodecState * state, GstD3D12EncoderConfig * config); static gboolean gst_d3d12_h264_enc_start_frame (GstD3D12Encoder * encoder, ID3D12VideoDevice * video_device, GstVideoCodecFrame * frame, D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_DESC * seq_ctrl, D3D12_VIDEO_ENCODER_PICTURE_CONTROL_DESC * picture_ctrl, D3D12_VIDEO_ENCODER_RECONSTRUCTED_PICTURE * recon_pic, GstD3D12EncoderConfig * config, gboolean * need_new_session); static gboolean gst_d3d12_h264_enc_end_frame (GstD3D12Encoder * encoder); static GstElementClass *parent_class = nullptr; static void gst_d3d12_h264_enc_class_init (GstD3D12H264EncClass * klass, gpointer data) { auto object_class = G_OBJECT_CLASS (klass); auto element_class = GST_ELEMENT_CLASS (klass); auto encoder_class = GST_VIDEO_ENCODER_CLASS (klass); auto d3d12enc_class = GST_D3D12_ENCODER_CLASS (klass); auto cdata = (GstD3D12H264EncClassData *) data; GstPadTemplate *pad_templ; auto read_only_params = (GParamFlags) (GST_PARAM_DOC_SHOW_DEFAULT | G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); auto rw_params = (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); parent_class = (GstElementClass *) g_type_class_peek_parent (klass); klass->cdata = cdata; object_class->finalize = gst_d3d12_h264_enc_finalize; object_class->set_property = gst_d3d12_h264_enc_set_property; object_class->get_property = gst_d3d12_h264_enc_get_property; g_object_class_install_property (object_class, PROP_RATE_CONTROL_SUPPORT, g_param_spec_flags ("rate-control-support", "Rate Control Support", "Supported rate control modes", GST_TYPE_D3D12_ENCODER_RATE_CONTROL_SUPPORT, 0, read_only_params)); g_object_class_install_property (object_class, PROP_SLICE_MODE_SUPPORT, g_param_spec_flags ("slice-mode-support", "Slice Mode Support", "Supported slice partition modes", GST_TYPE_D3D12_ENCODER_SUBREGION_LAYOUT_SUPPORT, 1, read_only_params)); g_object_class_install_property (object_class, PROP_AUD, g_param_spec_boolean ("aud", "AUD", "Use AU delimiter", DEFAULT_AUD, rw_params)); g_object_class_install_property (object_class, PROP_GOP_SIZE, g_param_spec_uint ("gop-size", "GOP Size", "Size of GOP (0 = infinite)", 0, G_MAXUINT32, DEFAULT_GOP_SIZE, rw_params)); g_object_class_install_property (object_class, PROP_REF_FRAMES, g_param_spec_uint ("ref-frames", "Ref frames", "Preferred number of reference frames. Actual number of reference " "frames can be limited depending on hardware (0 = unspecified)", 0, 16, DEFAULT_REF_FRAMES, rw_params)); g_object_class_install_property (object_class, PROP_FRAME_ANALYSIS, g_param_spec_boolean ("frame-analysis", "Frame Analysis", "Enable 2 pass encoding if supported by hardware", DEFAULT_FRAME_ANALYSIS, rw_params)); g_object_class_install_property (object_class, PROP_RATE_CONTROL, g_param_spec_enum ("rate-control", "Rate Control", "Rate Control Method", GST_TYPE_D3D12_ENCODER_RATE_CONTROL, DEFAULT_RATE_CONTROL, rw_params)); g_object_class_install_property (object_class, PROP_BITRATE, g_param_spec_uint ("bitrate", "Bitrate", "Target bitrate in kbits/second. " "Used for \"cbr\", \"vbr\", and \"qvbr\" rate control", 0, G_MAXUINT, DEFAULT_BITRATE, rw_params)); g_object_class_install_property (object_class, PROP_MAX_BITRATE, g_param_spec_uint ("max-bitrate", "Max Bitrate", "Peak bitrate in kbits/second. " "Used for \"vbr\", and \"qvbr\" rate control", 0, G_MAXUINT, DEFAULT_MAX_BITRATE, rw_params)); g_object_class_install_property (object_class, PROP_QVBR_QUALITY, g_param_spec_uint ("qvbr-quality", "QVBR Quality", "Constant quality target value for \"qvbr\" rate control", 0, 51, DEFAULT_QVBR_QUALITY, rw_params)); g_object_class_install_property (object_class, PROP_QP_INIT, g_param_spec_uint ("qp-init", "QP Init", "Initial QP value. " "Used for \"cbr\", \"vbr\", and \"qvbr\" rate control", 0, 51, DEFAULT_QP, rw_params)); g_object_class_install_property (object_class, PROP_QP_MIN, g_param_spec_uint ("qp-min", "QP Min", "Minimum QP value for \"cbr\", \"vbr\", and \"qvbr\" rate control. " "To enable min/max QP setting, \"qp-max >= qp-min > 0\" " "condition should be satisfied", 0, 51, DEFAULT_QP, rw_params)); g_object_class_install_property (object_class, PROP_QP_MAX, g_param_spec_uint ("qp-max", "QP Max", "Maximum QP value for \"cbr\", \"vbr\", and \"qvbr\" rate control. " "To enable min/max QP setting, \"qp-max >= qp-min > 0\" " "condition should be satisfied", 0, 51, DEFAULT_QP, rw_params)); g_object_class_install_property (object_class, PROP_QP_I, g_param_spec_uint ("qp-i", "QP I", "Constant QP value for I frames. Used for \"cqp\" rate control", 1, 51, DEFAULT_CQP, rw_params)); g_object_class_install_property (object_class, PROP_QP_P, g_param_spec_uint ("qp-p", "QP P", "Constant QP value for P frames. Used for \"cqp\" rate control", 1, 51, DEFAULT_CQP, rw_params)); g_object_class_install_property (object_class, PROP_QP_I, g_param_spec_uint ("qp-b", "QP B", "Constant QP value for B frames. Used for \"cqp\" rate control", 1, 51, DEFAULT_CQP, rw_params)); g_object_class_install_property (object_class, PROP_SLICE_MODE, g_param_spec_enum ("slice-mode", "Slice Mode", "Slice partiton mode", GST_TYPE_D3D12_ENCODER_SUBREGION_LAYOUT, DEFAULT_SLICE_MODE, rw_params)); g_object_class_install_property (object_class, PROP_SLICE_PARTITION, g_param_spec_uint ("slice-partition", "Slice partition", "Slice partition threshold interpreted depending on \"slice-mode\". " "If set zero, full frame encoding will be selected without " "partitioning regardless of requested \"slice-mode\"", 0, G_MAXUINT, DEFAULT_SLICE_PARTITION, rw_params)); g_object_class_install_property (object_class, PROP_CC_INSERT, g_param_spec_enum ("cc-insert", "Closed Caption Insert", "Closed Caption insert mode", GST_TYPE_D3D12_ENCODER_SEI_INSERT_MODE, DEFAULT_CC_INSERT, rw_params)); std::string long_name = "Direct3D12 H.264 " + std::string (cdata->description) + " Encoder"; gst_element_class_set_metadata (element_class, long_name.c_str (), "Codec/Encoder/Video/Hardware", "Direct3D12 H.264 Video Encoder", "Seungha Yang "); pad_templ = gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, cdata->sink_caps); gst_element_class_add_pad_template (element_class, pad_templ); pad_templ = gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS, cdata->src_caps); gst_element_class_add_pad_template (element_class, pad_templ); encoder_class->start = GST_DEBUG_FUNCPTR (gst_d3d12_h264_enc_start); encoder_class->stop = GST_DEBUG_FUNCPTR (gst_d3d12_h264_enc_stop); encoder_class->transform_meta = GST_DEBUG_FUNCPTR (gst_d3d12_h264_enc_transform_meta); d3d12enc_class->adapter_luid = cdata->luid; d3d12enc_class->device_id = cdata->device_id; d3d12enc_class->vendor_id = cdata->vendor_id; d3d12enc_class->new_sequence = GST_DEBUG_FUNCPTR (gst_d3d12_h264_enc_new_sequence); d3d12enc_class->start_frame = GST_DEBUG_FUNCPTR (gst_d3d12_h264_enc_start_frame); d3d12enc_class->end_frame = GST_DEBUG_FUNCPTR (gst_d3d12_h264_enc_end_frame); } static void gst_d3d12_h264_enc_init (GstD3D12H264Enc * self) { self->priv = new GstD3D12H264EncPrivate (); } static void gst_d3d12_h264_enc_finalize (GObject * object) { auto self = GST_D3D12_H264_ENC (object); delete self->priv; G_OBJECT_CLASS (parent_class)->finalize (object); } static void gst_d3d12_h264_enc_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { auto self = GST_D3D12_H264_ENC (object); auto priv = self->priv; std::lock_guard < std::mutex > lk (priv->prop_lock); switch (prop_id) { case PROP_AUD: priv->aud = g_value_get_boolean (value); break; case PROP_GOP_SIZE: { auto gop_size = g_value_get_uint (value); if (gop_size != priv->gop_size) { priv->gop_size = gop_size; priv->gop_updated = TRUE; } break; } case PROP_REF_FRAMES: { auto ref_frames = g_value_get_uint (value); if (ref_frames != priv->ref_frames) { priv->ref_frames = ref_frames; priv->gop_updated = TRUE; } break; } case PROP_FRAME_ANALYSIS: { auto frame_analysis = g_value_get_boolean (value); if (frame_analysis != priv->frame_analysis) { priv->frame_analysis = frame_analysis; priv->rc_updated = TRUE; } break; } case PROP_RATE_CONTROL: { auto mode = (D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE) g_value_get_enum (value); if (mode != priv->rc_mode) { priv->rc_mode = mode; priv->rc_updated = TRUE; } break; } case PROP_BITRATE: { auto bitrate = g_value_get_uint (value); if (bitrate == 0) bitrate = DEFAULT_BITRATE; if (bitrate != priv->bitrate) { priv->bitrate = bitrate; if (priv->selected_rc_mode != D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CQP) priv->rc_updated = TRUE; } break; } case PROP_MAX_BITRATE: { auto max_bitrate = g_value_get_uint (value); if (max_bitrate == 0) max_bitrate = DEFAULT_MAX_BITRATE; if (max_bitrate != priv->max_bitrate) { priv->max_bitrate = max_bitrate; switch (priv->selected_rc_mode) { case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR: case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR: priv->rc_updated = TRUE; break; default: break; } } break; } case PROP_QVBR_QUALITY: { auto qvbr_quality = g_value_get_uint (value); if (qvbr_quality != priv->qvbr_quality) { priv->qvbr_quality = qvbr_quality; if (priv->selected_rc_mode == D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR) { priv->rc_updated = TRUE; } } break; } case PROP_QP_INIT: { auto qp_init = g_value_get_uint (value); if (qp_init != priv->qp_init) { priv->qp_init = qp_init; switch (priv->selected_rc_mode) { case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CBR: case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR: case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR: priv->rc_updated = TRUE; break; default: break; } } break; } case PROP_QP_MIN: { auto qp_min = g_value_get_uint (value); if (qp_min != priv->qp_min) { priv->qp_min = qp_min; switch (priv->selected_rc_mode) { case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CBR: case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR: case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR: priv->rc_updated = TRUE; break; default: break; } } break; } case PROP_QP_MAX: { auto qp_max = g_value_get_uint (value); if (qp_max != priv->qp_max) { priv->qp_max = qp_max; switch (priv->selected_rc_mode) { case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CBR: case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR: case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR: priv->rc_updated = TRUE; break; default: break; } } break; } case PROP_QP_I: { auto qp_i = g_value_get_uint (value); if (qp_i != priv->qp_i) { priv->qp_i = qp_i; if (priv->selected_rc_mode == D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CQP) priv->rc_updated = TRUE; } break; } case PROP_QP_P: { auto qp_p = g_value_get_uint (value); if (qp_p != priv->qp_p) { priv->qp_p = qp_p; if (priv->selected_rc_mode == D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CQP) priv->rc_updated = TRUE; } break; } case PROP_QP_B: { auto qp_b = g_value_get_uint (value); if (qp_b != priv->qp_b) { priv->qp_b = qp_b; if (priv->selected_rc_mode == D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CQP) priv->rc_updated = TRUE; } break; } case PROP_SLICE_MODE: { auto slice_mode = (D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE) g_value_get_enum (value); if (slice_mode != priv->slice_mode) { priv->slice_mode = slice_mode; if (priv->selected_slice_mode != slice_mode) priv->slice_updated = TRUE; } break; } case PROP_SLICE_PARTITION: { auto slice_partition = g_value_get_uint (value); if (slice_partition != priv->slice_partition) { priv->slice_partition = slice_partition; if (priv->selected_slice_mode != D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_FULL_FRAME) { priv->slice_updated = TRUE; } } break; } case PROP_CC_INSERT: priv->cc_insert = (GstD3D12EncoderSeiInsertMode) g_value_get_enum (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_d3d12_h264_enc_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { auto self = GST_D3D12_H264_ENC (object); auto priv = self->priv; auto klass = GST_D3D12_H264_ENC_GET_CLASS (self); const auto cdata = klass->cdata; std::lock_guard < std::mutex > lk (priv->prop_lock); switch (prop_id) { case PROP_RATE_CONTROL_SUPPORT: g_value_set_flags (value, cdata->rc_support); break; case PROP_SLICE_MODE_SUPPORT: g_value_set_flags (value, cdata->slice_mode_support); break; case PROP_AUD: g_value_set_boolean (value, priv->aud); break; case PROP_GOP_SIZE: g_value_set_uint (value, priv->gop_size); break; case PROP_REF_FRAMES: g_value_set_uint (value, priv->ref_frames); break; case PROP_FRAME_ANALYSIS: g_value_set_boolean (value, priv->frame_analysis); break; case PROP_RATE_CONTROL: g_value_set_enum (value, priv->rc_mode); break; case PROP_BITRATE: g_value_set_uint (value, priv->bitrate); break; case PROP_MAX_BITRATE: g_value_set_uint (value, priv->max_bitrate); break; case PROP_QVBR_QUALITY: g_value_set_uint (value, priv->qvbr_quality); break; case PROP_QP_INIT: g_value_set_uint (value, priv->qp_init); break; case PROP_QP_MIN: g_value_set_uint (value, priv->qp_min); break; case PROP_QP_MAX: g_value_set_uint (value, priv->qp_max); break; case PROP_QP_I: g_value_set_uint (value, priv->qp_i); break; case PROP_QP_P: g_value_set_uint (value, priv->qp_p); break; case PROP_QP_B: g_value_set_uint (value, priv->qp_p); break; case PROP_SLICE_MODE: g_value_set_enum (value, priv->slice_mode); break; case PROP_SLICE_PARTITION: g_value_set_uint (value, priv->slice_partition); break; case PROP_CC_INSERT: g_value_set_enum (value, priv->cc_insert); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static gboolean gst_d3d12_h264_enc_start (GstVideoEncoder * encoder) { auto self = GST_D3D12_H264_ENC (encoder); auto priv = self->priv; GST_DEBUG_OBJECT (self, "Start"); priv->display_order = 0; return GST_VIDEO_ENCODER_CLASS (parent_class)->start (encoder); } static gboolean gst_d3d12_h264_enc_stop (GstVideoEncoder * encoder) { auto self = GST_D3D12_H264_ENC (encoder); auto priv = self->priv; GST_DEBUG_OBJECT (self, "Stop"); priv->dpb = nullptr; return GST_VIDEO_ENCODER_CLASS (parent_class)->stop (encoder); } static gboolean gst_d3d12_h264_enc_transform_meta (GstVideoEncoder * encoder, GstVideoCodecFrame * frame, GstMeta * meta) { auto self = GST_D3D12_H264_ENC (encoder); auto priv = self->priv; if (meta->info->api == GST_VIDEO_CAPTION_META_API_TYPE) { std::lock_guard < std::mutex > lk (priv->prop_lock); if (priv->cc_insert == GST_D3D12_ENCODER_SEI_INSERT_AND_DROP) { auto cc_meta = (GstVideoCaptionMeta *) meta; if (cc_meta->caption_type == GST_VIDEO_CAPTION_TYPE_CEA708_RAW) return FALSE; } } return GST_VIDEO_ENCODER_CLASS (parent_class)->transform_meta (encoder, frame, meta); } static gboolean gst_d3d12_h264_enc_build_sps (GstD3D12H264Enc * self, const GstVideoInfo * info, const D3D12_VIDEO_ENCODER_PICTURE_RESOLUTION_DESC * resolution, guint num_ref) { auto priv = self->priv; guint8 sps_buf[4096] = { 0, }; /* *INDENT-OFF* */ static const std::unordered_map level_map = { {D3D12_VIDEO_ENCODER_LEVELS_H264_1, GST_H264_LEVEL_L1}, {D3D12_VIDEO_ENCODER_LEVELS_H264_1b, GST_H264_LEVEL_L1B}, {D3D12_VIDEO_ENCODER_LEVELS_H264_11, GST_H264_LEVEL_L1_1}, {D3D12_VIDEO_ENCODER_LEVELS_H264_12, GST_H264_LEVEL_L1_2}, {D3D12_VIDEO_ENCODER_LEVELS_H264_13, GST_H264_LEVEL_L1_3}, {D3D12_VIDEO_ENCODER_LEVELS_H264_2, GST_H264_LEVEL_L2}, {D3D12_VIDEO_ENCODER_LEVELS_H264_21, GST_H264_LEVEL_L2_1}, {D3D12_VIDEO_ENCODER_LEVELS_H264_22, GST_H264_LEVEL_L2_2}, {D3D12_VIDEO_ENCODER_LEVELS_H264_3, GST_H264_LEVEL_L3}, {D3D12_VIDEO_ENCODER_LEVELS_H264_31, GST_H264_LEVEL_L3_1}, {D3D12_VIDEO_ENCODER_LEVELS_H264_32, GST_H264_LEVEL_L3_2}, {D3D12_VIDEO_ENCODER_LEVELS_H264_4, GST_H264_LEVEL_L4}, {D3D12_VIDEO_ENCODER_LEVELS_H264_41, GST_H264_LEVEL_L4_1}, {D3D12_VIDEO_ENCODER_LEVELS_H264_42, GST_H264_LEVEL_L4_2}, {D3D12_VIDEO_ENCODER_LEVELS_H264_5, GST_H264_LEVEL_L5}, {D3D12_VIDEO_ENCODER_LEVELS_H264_51, GST_H264_LEVEL_L5_1}, {D3D12_VIDEO_ENCODER_LEVELS_H264_52, GST_H264_LEVEL_L5_2}, {D3D12_VIDEO_ENCODER_LEVELS_H264_6, GST_H264_LEVEL_L6}, {D3D12_VIDEO_ENCODER_LEVELS_H264_61, GST_H264_LEVEL_L6_1}, {D3D12_VIDEO_ENCODER_LEVELS_H264_62, GST_H264_LEVEL_L6_2}, }; static const std::array, 17> par_map {{ {0, 0}, {1, 1}, {12, 11}, {10, 11}, {16, 11}, {40, 33}, {24, 11}, {20, 11}, {32, 11}, {80, 33}, {18, 11}, {15, 11}, {64, 33}, {160, 99}, {4, 3}, {3, 2}, {2, 1} }}; /* *INDENT-ON* */ priv->sps.Clear (); auto & sps = priv->sps.sps; sps.id = 0; sps.profile_idc = priv->selected_profile; switch (sps.profile_idc) { case GST_H264_PROFILE_BASELINE: sps.constraint_set0_flag = 1; sps.constraint_set1_flag = 1; break; case GST_H264_PROFILE_MAIN: sps.constraint_set1_flag = 1; break; default: break; } if (priv->level_h264 == D3D12_VIDEO_ENCODER_LEVELS_H264_1b) sps.constraint_set3_flag = 1; sps.level_idc = level_map.at (priv->level_h264); /* d3d12 supports only 4:2:0 encoding */ sps.chroma_format_idc = 1; sps.separate_colour_plane_flag = 0; /* add high-10 profile support */ sps.bit_depth_luma_minus8 = 0; sps.bit_depth_chroma_minus8 = 0; sps.qpprime_y_zero_transform_bypass_flag = 0; sps.scaling_matrix_present_flag = 0; sps.log2_max_frame_num_minus4 = priv->gop_struct_h264.log2_max_frame_num_minus4; sps.pic_order_cnt_type = priv->gop_struct_h264.pic_order_cnt_type; sps.log2_max_pic_order_cnt_lsb_minus4 = priv->gop_struct_h264.log2_max_pic_order_cnt_lsb_minus4; sps.num_ref_frames = num_ref; sps.gaps_in_frame_num_value_allowed_flag = 0; sps.pic_width_in_mbs_minus1 = (resolution->Width / 16) - 1; sps.pic_height_in_map_units_minus1 = (resolution->Height / 16) - 1; sps.frame_mbs_only_flag = 1; if (sps.profile_idc != GST_H264_PROFILE_BASELINE) sps.direct_8x8_inference_flag = 1; if (resolution->Width != (UINT) info->width || resolution->Height != (UINT) info->height) { sps.frame_cropping_flag = 1; sps.frame_crop_left_offset = 0; sps.frame_crop_right_offset = (resolution->Width - info->width) / 2; sps.frame_crop_top_offset = 0; sps.frame_crop_bottom_offset = (resolution->Height - info->height) / 2; } sps.vui_parameters_present_flag = 1; auto & vui = sps.vui_parameters; const auto colorimetry = &info->colorimetry; if (info->par_n > 0 && info->par_d > 0) { const auto it = std::find_if (par_map.begin (), par_map.end (),[&](const auto & par) { return par.first == info->par_n && par.second == info->par_d; } ); if (it != par_map.end ()) { vui.aspect_ratio_info_present_flag = 1; vui.aspect_ratio_idc = (guint8) std::distance (par_map.begin (), it); } else if (info->par_n <= G_MAXUINT16 && info->par_d <= G_MAXUINT16) { vui.aspect_ratio_info_present_flag = 1; vui.aspect_ratio_idc = 0xff; vui.sar_width = info->par_n; vui.sar_height = info->par_d; } } vui.video_signal_type_present_flag = 1; /* Unspecified */ vui.video_format = 5; vui.video_full_range_flag = colorimetry->range == GST_VIDEO_COLOR_RANGE_0_255 ? 1 : 0; vui.colour_description_present_flag = 1; vui.colour_primaries = gst_video_color_primaries_to_iso (colorimetry->primaries); vui.transfer_characteristics = gst_video_transfer_function_to_iso (colorimetry->transfer); vui.matrix_coefficients = gst_video_color_matrix_to_iso (colorimetry->matrix); if (info->fps_n > 0 && info->fps_d > 0) { vui.timing_info_present_flag = 1; vui.num_units_in_tick = info->fps_d; vui.time_scale = 2 * (guint) info->fps_n; } /* TODO: pic_struct_present_flag = 1 for picture timeing SEI */ guint nal_size = G_N_ELEMENTS (sps_buf); GstH264BitWriterResult write_ret = gst_h264_bit_writer_sps (&sps, TRUE, sps_buf, &nal_size); if (write_ret != GST_H264_BIT_WRITER_OK) { GST_ERROR_OBJECT (self, "Couldn't build SPS"); return FALSE; } priv->sps.bytes.resize (G_N_ELEMENTS (sps_buf)); guint written_size = priv->sps.bytes.size (); write_ret = gst_h264_bit_writer_convert_to_nal (4, FALSE, TRUE, FALSE, sps_buf, nal_size * 8, priv->sps.bytes.data (), &written_size); if (write_ret != GST_H264_BIT_WRITER_OK) { GST_ERROR_OBJECT (self, "Couldn't build SPS bytes"); return FALSE; } priv->sps.bytes.resize (written_size); return TRUE; } static gboolean gst_d3d12_h264_enc_build_pps (GstD3D12H264Enc * self, guint num_ref) { auto priv = self->priv; /* Driver does not seem to use num_ref_idx_active_override_flag. * Needs multiple PPS to signal ref pics */ /* TODO: make more PPS for L1 ref pics */ guint num_pps = MAX (1, num_ref); priv->pps.resize (num_pps); for (size_t i = 0; i < priv->pps.size (); i++) { guint8 pps_buf[1024] = { 0, }; auto & d3d12_pps = priv->pps[i]; d3d12_pps.Clear (); auto & pps = d3d12_pps.pps; pps.id = i; pps.sequence = &priv->sps.sps; if ((priv->config_h264.ConfigurationFlags & D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264_FLAG_ENABLE_CABAC_ENCODING) != 0) { pps.entropy_coding_mode_flag = 1; } else { pps.entropy_coding_mode_flag = 0; } pps.pic_order_present_flag = 0; pps.num_slice_groups_minus1 = 0; pps.num_ref_idx_l0_active_minus1 = i; pps.num_ref_idx_l1_active_minus1 = 0; pps.weighted_pred_flag = 0; pps.weighted_bipred_idc = 0; pps.pic_init_qp_minus26 = 0; pps.pic_init_qs_minus26 = 0; pps.chroma_qp_index_offset = 0; pps.deblocking_filter_control_present_flag = 1; if ((priv->config_h264.ConfigurationFlags & D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264_FLAG_USE_CONSTRAINED_INTRAPREDICTION) != 0) { pps.constrained_intra_pred_flag = 1; } else { pps.constrained_intra_pred_flag = 0; } pps.redundant_pic_cnt_present_flag = 0; if ((priv->config_h264.ConfigurationFlags & D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264_FLAG_USE_ADAPTIVE_8x8_TRANSFORM) != 0) { /* double check. This is not allowed for baseline and main profiles */ pps.transform_8x8_mode_flag = 1; } else { pps.transform_8x8_mode_flag = 0; } pps.pic_scaling_matrix_present_flag = 0; pps.second_chroma_qp_index_offset = 0; guint nal_size = G_N_ELEMENTS (pps_buf); d3d12_pps.bytes.resize (nal_size); GstH264BitWriterResult write_ret = gst_h264_bit_writer_pps (&pps, TRUE, pps_buf, &nal_size); if (write_ret != GST_H264_BIT_WRITER_OK) { GST_ERROR_OBJECT (self, "Couldn't build PPS"); return FALSE; } guint written_size = d3d12_pps.bytes.size (); write_ret = gst_h264_bit_writer_convert_to_nal (4, FALSE, TRUE, FALSE, pps_buf, nal_size * 8, d3d12_pps.bytes.data (), &written_size); if (write_ret != GST_H264_BIT_WRITER_OK) { GST_ERROR_OBJECT (self, "Couldn't build PPS bytes"); return FALSE; } d3d12_pps.bytes.resize (written_size); } return TRUE; } static gboolean gst_d3d12_h264_enc_get_max_ref_frames (GstD3D12H264Enc * self) { auto priv = self->priv; const auto & pic_ctrl_support = priv->pic_ctrl_support; guint max_ref_frames = MIN (pic_ctrl_support.MaxL0ReferencesForP, pic_ctrl_support.MaxDPBCapacity); guint ref_frames = priv->ref_frames; if (max_ref_frames == 0) { GST_INFO_OBJECT (self, "Hardware does not support inter prediction, forcing all-intra"); ref_frames = 0; } else if (priv->gop_size == 1) { GST_INFO_OBJECT (self, "User requested all-intra coding"); ref_frames = 0; } else { /* TODO: at least 2 ref frames if B frame is enabled */ if (ref_frames != 0) ref_frames = MIN (ref_frames, max_ref_frames); else ref_frames = 1; } return ref_frames; } static gboolean gst_d3d12_h264_enc_update_gop (GstD3D12H264Enc * self, ID3D12VideoDevice * video_device, GstD3D12EncoderConfig * config, D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAGS * seq_flags) { auto priv = self->priv; if (seq_flags && !priv->gop_updated) return TRUE; auto ref_frames = gst_d3d12_h264_enc_get_max_ref_frames (self); auto gop_size = priv->gop_size; if (ref_frames == 0) gop_size = 1; priv->last_pps_id = 0; auto prev_gop_struct = priv->gop.GetGopStruct (); auto prev_ref_frames = priv->selected_ref_frames; priv->selected_ref_frames = ref_frames; priv->gop.Init (gop_size); priv->gop_struct_h264 = priv->gop.GetGopStruct (); if (seq_flags) { if (prev_ref_frames != ref_frames || memcmp (&prev_gop_struct, &priv->gop_struct_h264, sizeof (priv->gop_struct_h264)) != 0) { GST_DEBUG_OBJECT (self, "Gop struct updated"); *seq_flags |= D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_GOP_SEQUENCE_CHANGE; } } GST_DEBUG_OBJECT (self, "Configured GOP struct, GOPLength: %u, PPicturePeriod: %u, " "pic_order_cnt_type: %d, log2_max_frame_num_minus4: %d, " "log2_max_pic_order_cnt_lsb_minus4: %d", priv->gop_struct_h264.GOPLength, priv->gop_struct_h264.PPicturePeriod, priv->gop_struct_h264.pic_order_cnt_type, priv->gop_struct_h264.log2_max_frame_num_minus4, priv->gop_struct_h264.log2_max_pic_order_cnt_lsb_minus4); priv->gop_updated = FALSE; return TRUE; } /* called with prop_lock taken */ static gboolean gst_d3d12_h264_enc_update_rate_control (GstD3D12H264Enc * self, ID3D12VideoDevice * video_device, GstD3D12EncoderConfig * config, D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAGS * seq_flags) { auto priv = self->priv; const D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE rc_modes[] = { D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR, D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR, D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CBR, D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CQP, }; if (seq_flags && !priv->rc_updated) return TRUE; GstD3D12EncoderConfig prev_config = *config; config->rate_control.Flags = D3D12_VIDEO_ENCODER_RATE_CONTROL_FLAG_NONE; UINT64 bitrate = priv->bitrate; if (bitrate == 0) bitrate = DEFAULT_BITRATE; UINT64 max_bitrate = priv->max_bitrate; if (max_bitrate < bitrate) { if (bitrate >= G_MAXUINT64 / 2) max_bitrate = bitrate; else max_bitrate = bitrate * 2; } /* Property uses kbps, and API uses bps */ bitrate *= 1000; max_bitrate *= 1000; /* Fill every rate control struct and select later */ config->cqp.ConstantQP_FullIntracodedFrame = priv->qp_i; config->cqp.ConstantQP_InterPredictedFrame_PrevRefOnly = priv->qp_p; config->cqp.ConstantQP_InterPredictedFrame_BiDirectionalRef = priv->qp_b; config->cbr.InitialQP = priv->qp_init; config->cbr.MinQP = priv->qp_min; config->cbr.MaxQP = priv->qp_max; config->cbr.TargetBitRate = bitrate; config->vbr.InitialQP = priv->qp_init; config->vbr.MinQP = priv->qp_min; config->vbr.MaxQP = priv->qp_max; config->vbr.TargetAvgBitRate = bitrate; config->vbr.PeakBitRate = max_bitrate; config->qvbr.InitialQP = priv->qp_init; config->qvbr.MinQP = priv->qp_min; config->qvbr.MaxQP = priv->qp_max; config->qvbr.TargetAvgBitRate = bitrate; config->qvbr.PeakBitRate = max_bitrate; config->qvbr.ConstantQualityTarget = priv->qvbr_quality; D3D12_FEATURE_DATA_VIDEO_ENCODER_RATE_CONTROL_MODE feature_data = { }; feature_data.Codec = D3D12_VIDEO_ENCODER_CODEC_H264; feature_data.RateControlMode = priv->rc_mode; auto hr = video_device->CheckFeatureSupport (D3D12_FEATURE_VIDEO_ENCODER_RATE_CONTROL_MODE, &feature_data, sizeof (feature_data)); if (SUCCEEDED (hr) && feature_data.IsSupported) { priv->selected_rc_mode = priv->rc_mode; } else { GST_INFO_OBJECT (self, "Requested rate control mode is not supported"); for (guint i = 0; i < G_N_ELEMENTS (rc_modes); i++) { feature_data.RateControlMode = rc_modes[i]; hr = video_device->CheckFeatureSupport (D3D12_FEATURE_VIDEO_ENCODER_RATE_CONTROL_MODE, &feature_data, sizeof (feature_data)); if (SUCCEEDED (hr) && feature_data.IsSupported) { priv->selected_rc_mode = rc_modes[i]; break; } else { feature_data.IsSupported = FALSE; } } if (!feature_data.IsSupported) { GST_ERROR_OBJECT (self, "Couldn't find support rate control mode"); return FALSE; } } GST_INFO_OBJECT (self, "Requested rate control mode %d, selected %d", priv->rc_mode, priv->selected_rc_mode); config->rate_control.Mode = priv->selected_rc_mode; switch (priv->selected_rc_mode) { case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CQP: config->rate_control.ConfigParams.DataSize = sizeof (config->cqp); config->rate_control.ConfigParams.pConfiguration_CQP = &config->cqp; break; case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CBR: config->rate_control.ConfigParams.DataSize = sizeof (config->cbr); config->rate_control.ConfigParams.pConfiguration_CBR = &config->cbr; break; case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR: config->rate_control.ConfigParams.DataSize = sizeof (config->vbr); config->rate_control.ConfigParams.pConfiguration_VBR = &config->vbr; break; case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR: config->rate_control.ConfigParams.DataSize = sizeof (config->qvbr); config->rate_control.ConfigParams.pConfiguration_QVBR = &config->qvbr; break; default: g_assert_not_reached (); return FALSE; } if (seq_flags) { if (prev_config.rate_control.Mode != config->rate_control.Mode) { GST_DEBUG_OBJECT (self, "Rate control mode changed %d -> %d", prev_config.rate_control.Mode, config->rate_control.Mode); *seq_flags |= D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_RATE_CONTROL_CHANGE; } else { void *prev, *cur; switch (config->rate_control.Mode) { case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CQP: prev = &prev_config.cqp; cur = &config->cqp; break; case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CBR: prev = &prev_config.cbr; cur = &config->cbr; break; case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR: prev = &prev_config.vbr; cur = &config->vbr; break; case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR: prev = &prev_config.qvbr; cur = &config->cbr; break; default: g_assert_not_reached (); return FALSE; } if (memcmp (prev, cur, config->rate_control.ConfigParams.DataSize) != 0) { GST_DEBUG_OBJECT (self, "Rate control params updated"); *seq_flags |= D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_RATE_CONTROL_CHANGE; } } } priv->rc_updated = FALSE; return TRUE; } static gboolean gst_d3d12_h264_enc_update_slice (GstD3D12H264Enc * self, ID3D12VideoDevice * video_device, GstD3D12EncoderConfig * config, D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAGS * seq_flags, D3D12_VIDEO_ENCODER_SUPPORT_FLAGS * support_flags) { auto priv = self->priv; if (seq_flags && !priv->slice_updated) return TRUE; auto encoder = GST_D3D12_ENCODER (self); auto prev_mode = priv->selected_slice_mode; auto prev_slice = priv->layout_slices; priv->selected_slice_mode = D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_FULL_FRAME; priv->layout_slices.NumberOfSlicesPerFrame = 1; config->max_subregions = 1; D3D12_FEATURE_DATA_VIDEO_ENCODER_SUPPORT support = { }; D3D12_FEATURE_DATA_VIDEO_ENCODER_RESOLUTION_SUPPORT_LIMITS limits = { }; D3D12_VIDEO_ENCODER_PROFILE_H264 suggested_profile = priv->profile_h264; D3D12_VIDEO_ENCODER_LEVELS_H264 suggested_level; support.Codec = D3D12_VIDEO_ENCODER_CODEC_H264; support.InputFormat = DXGI_FORMAT_NV12; support.CodecConfiguration = config->codec_config; support.CodecGopSequence = config->gop_struct; support.RateControl = config->rate_control; /* TODO: add intra-refresh support */ support.IntraRefresh = D3D12_VIDEO_ENCODER_INTRA_REFRESH_MODE_NONE; support.ResolutionsListCount = 1; support.pResolutionList = &config->resolution; support.MaxReferenceFramesInDPB = priv->selected_ref_frames; support.pResolutionDependentSupport = &limits; support.SuggestedProfile.DataSize = sizeof (suggested_profile); support.SuggestedProfile.pH264Profile = &suggested_profile; support.SuggestedLevel.DataSize = sizeof (suggested_level); support.SuggestedLevel.pH264LevelSetting = &suggested_level; HRESULT hr; if (priv->slice_mode != D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_FULL_FRAME && priv->slice_partition > 0) { /* TODO: fallback to other mode if possible */ D3D12_FEATURE_DATA_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE feature_layout = { }; feature_layout.Codec = D3D12_VIDEO_ENCODER_CODEC_H264; feature_layout.Profile = config->profile_desc; feature_layout.Level = config->level; feature_layout.SubregionMode = priv->slice_mode; hr = video_device->CheckFeatureSupport (D3D12_FEATURE_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE, &feature_layout, sizeof (feature_layout)); if (!gst_d3d12_result (hr, encoder->device) || !feature_layout.IsSupported) { GST_WARNING_OBJECT (self, "Requested slice mode is not supported"); } else { support.SubregionFrameEncoding = priv->slice_mode; hr = video_device->CheckFeatureSupport (D3D12_FEATURE_VIDEO_ENCODER_SUPPORT, &support, sizeof (support)); if (gst_d3d12_result (hr, encoder->device) && CHECK_SUPPORT_FLAG (support.SupportFlags, GENERAL_SUPPORT_OK) && support.ValidationFlags == D3D12_VIDEO_ENCODER_VALIDATION_FLAG_NONE && limits.MaxSubregionsNumber > 1 && limits.SubregionBlockPixelsSize > 0) { switch (priv->slice_mode) { case D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_BYTES_PER_SUBREGION: priv->selected_slice_mode = priv->slice_mode; /* Don't know how many slices would be generated */ config->max_subregions = limits.MaxSubregionsNumber; *support_flags = support.SupportFlags; break; case D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_SQUARE_UNITS_PER_SUBREGION_ROW_UNALIGNED: { auto total_mbs = (config->resolution.Width / limits.SubregionBlockPixelsSize) * (config->resolution.Height / limits.SubregionBlockPixelsSize); if (priv->slice_partition >= total_mbs) { GST_DEBUG_OBJECT (self, "Requested MBs per slice exceeds total MBs per frame"); } else { priv->selected_slice_mode = priv->slice_mode; auto min_mbs_per_slice = (guint) std::ceil ((float) total_mbs / limits.MaxSubregionsNumber); if (min_mbs_per_slice > priv->slice_partition) { GST_WARNING_OBJECT (self, "Too small number of MBs per slice"); priv->layout_slices.NumberOfCodingUnitsPerSlice = min_mbs_per_slice; config->max_subregions = limits.MaxSubregionsNumber; } else { priv->layout_slices.NumberOfCodingUnitsPerSlice = priv->slice_partition; config->max_subregions = std::ceil ((float) total_mbs / priv->slice_partition); } *support_flags = support.SupportFlags; } break; } case D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_UNIFORM_PARTITIONING_ROWS_PER_SUBREGION: { auto total_rows = config->resolution.Height / limits.SubregionBlockPixelsSize; if (priv->slice_partition >= total_rows) { GST_DEBUG_OBJECT (self, "Requested rows per slice exceeds total rows per frame"); } else { priv->selected_slice_mode = priv->slice_mode; auto min_rows_per_slice = (guint) std::ceil ((float) total_rows / limits.MaxSubregionsNumber); if (min_rows_per_slice > priv->slice_partition) { GST_WARNING_OBJECT (self, "Too small number of rows per slice"); priv->layout_slices.NumberOfRowsPerSlice = min_rows_per_slice; config->max_subregions = limits.MaxSubregionsNumber; } else { priv->layout_slices.NumberOfRowsPerSlice = priv->slice_partition; config->max_subregions = (guint) std::ceil ((float) total_rows / priv->slice_partition); } *support_flags = support.SupportFlags; } break; } case D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_UNIFORM_PARTITIONING_SUBREGIONS_PER_FRAME: { if (priv->slice_partition > 1) { priv->selected_slice_mode = priv->slice_mode; if (priv->slice_partition > limits.MaxSubregionsNumber) { GST_WARNING_OBJECT (self, "Too many slices per frame"); priv->layout_slices.NumberOfSlicesPerFrame = limits.MaxSubregionsNumber; config->max_subregions = limits.MaxSubregionsNumber; } else { priv->layout_slices.NumberOfSlicesPerFrame = priv->slice_partition; config->max_subregions = priv->slice_partition; } *support_flags = support.SupportFlags; } break; } default: break; } } } } if (seq_flags && (prev_mode != priv->selected_slice_mode || prev_slice.NumberOfSlicesPerFrame != priv->layout_slices.NumberOfSlicesPerFrame)) { GST_DEBUG_OBJECT (self, "Slice mode updated"); *seq_flags |= D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_SUBREGION_LAYOUT_CHANGE; } priv->slice_updated = FALSE; return TRUE; } static gboolean gst_d3d12_h264_enc_reconfigure (GstD3D12H264Enc * self, ID3D12VideoDevice * video_device, GstD3D12EncoderConfig * config, D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAGS * seq_flags) { auto encoder = GST_D3D12_ENCODER (self); auto priv = self->priv; auto prev_config = *config; if (!gst_d3d12_h264_enc_update_gop (self, video_device, config, seq_flags)) return FALSE; if (!gst_d3d12_h264_enc_update_rate_control (self, video_device, config, seq_flags)) { return FALSE; } D3D12_FEATURE_DATA_VIDEO_ENCODER_SUPPORT support = { }; D3D12_FEATURE_DATA_VIDEO_ENCODER_RESOLUTION_SUPPORT_LIMITS limits = { }; D3D12_VIDEO_ENCODER_PROFILE_H264 suggested_profile = priv->profile_h264; support.Codec = D3D12_VIDEO_ENCODER_CODEC_H264; support.InputFormat = DXGI_FORMAT_NV12; support.CodecConfiguration = config->codec_config; support.CodecGopSequence = config->gop_struct; support.RateControl = config->rate_control; /* TODO: add intra-refresh support */ support.IntraRefresh = D3D12_VIDEO_ENCODER_INTRA_REFRESH_MODE_NONE; support.SubregionFrameEncoding = priv->selected_slice_mode; support.ResolutionsListCount = 1; support.pResolutionList = &config->resolution; support.MaxReferenceFramesInDPB = priv->selected_ref_frames; support.pResolutionDependentSupport = &limits; support.SuggestedProfile.DataSize = sizeof (suggested_profile); support.SuggestedProfile.pH264Profile = &suggested_profile; support.SuggestedLevel = config->level; auto hr = video_device->CheckFeatureSupport (D3D12_FEATURE_VIDEO_ENCODER_SUPPORT, &support, sizeof (support)); /* This is our minimum/simplest configuration * TODO: negotiate again depending on validation flags */ if (!gst_d3d12_result (hr, encoder->device) || !CHECK_SUPPORT_FLAG (support.SupportFlags, GENERAL_SUPPORT_OK) || (support.ValidationFlags != D3D12_VIDEO_ENCODER_VALIDATION_FLAG_NONE)) { GST_ERROR_OBJECT (self, "Couldn't query encoder support"); return FALSE; } /* Update rate control flags based on support flags */ if (priv->frame_analysis) { if (CHECK_SUPPORT_FLAG (support.SupportFlags, RATE_CONTROL_FRAME_ANALYSIS_AVAILABLE)) { GST_INFO_OBJECT (self, "Frame analysis is enabled as requested"); config->rate_control.Flags |= D3D12_VIDEO_ENCODER_RATE_CONTROL_FLAG_ENABLE_FRAME_ANALYSIS; } else { GST_INFO_OBJECT (self, "Frame analysis is not supported"); } } if (priv->qp_init > 0) { switch (priv->selected_rc_mode) { case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CBR: case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR: case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR: if (CHECK_SUPPORT_FLAG (support.SupportFlags, RATE_CONTROL_INITIAL_QP_AVAILABLE)) { GST_INFO_OBJECT (self, "Initial QP %d is enabled as requested", priv->qp_init); config->rate_control.Flags |= D3D12_VIDEO_ENCODER_RATE_CONTROL_FLAG_ENABLE_INITIAL_QP; } else { GST_INFO_OBJECT (self, "Initial QP is not supported"); } break; default: break; } } if (priv->qp_max >= priv->qp_min && priv->qp_min > 0) { switch (priv->selected_rc_mode) { case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CBR: case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR: case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR: if (CHECK_SUPPORT_FLAG (support.SupportFlags, RATE_CONTROL_ADJUSTABLE_QP_RANGE_AVAILABLE)) { GST_INFO_OBJECT (self, "QP range [%d, %d] is enabled as requested", priv->qp_min, priv->qp_max); config->rate_control.Flags |= D3D12_VIDEO_ENCODER_RATE_CONTROL_FLAG_ENABLE_QP_RANGE; } else { GST_INFO_OBJECT (self, "QP range is not supported"); } break; default: break; } } if (seq_flags) { if (prev_config.rate_control.Flags != config->rate_control.Flags) { GST_DEBUG_OBJECT (self, "Rate control flag updated"); *seq_flags |= D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_RATE_CONTROL_CHANGE; } } if (!gst_d3d12_h264_enc_update_slice (self, video_device, config, seq_flags, &support.SupportFlags)) { return FALSE; } config->support_flags = support.SupportFlags; if (!seq_flags || (*seq_flags & D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_GOP_SEQUENCE_CHANGE) != 0) { priv->gop.ForceKeyUnit (); gst_d3d12_h264_enc_build_sps (self, &priv->info, &config->resolution, priv->selected_ref_frames); gst_d3d12_h264_enc_build_pps (self, priv->selected_ref_frames); bool array_of_textures = !CHECK_SUPPORT_FLAG (config->support_flags, RECONSTRUCTED_FRAMES_REQUIRE_TEXTURE_ARRAYS); auto dpb = std::make_unique < GstD3D12H264EncDpb > (encoder->device, config->resolution.Width, config->resolution.Height, priv->selected_ref_frames, array_of_textures); if (!dpb->IsValid ()) { GST_ERROR_OBJECT (self, "Couldn't create dpb"); return FALSE; } GST_DEBUG_OBJECT (self, "New DPB configured"); priv->dpb = nullptr; priv->dpb = std::move (dpb); } return TRUE; } static gboolean gst_d3d12_h264_enc_new_sequence (GstD3D12Encoder * encoder, ID3D12VideoDevice * video_device, GstVideoCodecState * state, GstD3D12EncoderConfig * config) { auto self = GST_D3D12_H264_ENC (encoder); auto priv = self->priv; auto info = &state->info; std::lock_guard < std::mutex > lk (priv->prop_lock); priv->dpb = nullptr; priv->info = state->info; config->profile_desc.DataSize = sizeof (D3D12_VIDEO_ENCODER_PROFILE_H264); config->profile_desc.pH264Profile = &priv->profile_h264; config->codec_config.DataSize = sizeof (D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264); config->codec_config.pH264Config = &priv->config_h264; config->level.DataSize = sizeof (D3D12_VIDEO_ENCODER_LEVELS_H264); config->level.pH264LevelSetting = &priv->level_h264; config->layout.DataSize = sizeof (D3D12_VIDEO_ENCODER_PICTURE_CONTROL_SUBREGIONS_LAYOUT_DATA_SLICES); config->layout.pSlicesPartition_H264 = &priv->layout_slices; config->gop_struct.DataSize = sizeof (D3D12_VIDEO_ENCODER_SEQUENCE_GOP_STRUCTURE_H264); config->gop_struct.pH264GroupOfPictures = &priv->gop_struct_h264; config->resolution.Width = GST_ROUND_UP_16 (info->width); config->resolution.Height = GST_ROUND_UP_16 (info->height); priv->selected_profile = GST_H264_PROFILE_MAIN; priv->profile_h264 = D3D12_VIDEO_ENCODER_PROFILE_H264_MAIN; auto allowed_caps = gst_pad_get_allowed_caps (GST_VIDEO_ENCODER_SRC_PAD (encoder)); if (allowed_caps && !gst_caps_is_any (allowed_caps)) { allowed_caps = gst_caps_fixate (gst_caps_make_writable (allowed_caps)); auto s = gst_caps_get_structure (allowed_caps, 0); auto profile_str = gst_structure_get_string (s, "profile"); if (g_strcmp0 (profile_str, "high") == 0) { priv->selected_profile = GST_H264_PROFILE_HIGH; priv->profile_h264 = D3D12_VIDEO_ENCODER_PROFILE_H264_HIGH; } else if (g_strcmp0 (profile_str, "constrained-baseline") == 0) { priv->selected_profile = GST_H264_PROFILE_BASELINE; priv->profile_h264 = D3D12_VIDEO_ENCODER_PROFILE_H264_MAIN; } else { priv->selected_profile = GST_H264_PROFILE_MAIN; priv->profile_h264 = D3D12_VIDEO_ENCODER_PROFILE_H264_MAIN; } } gst_clear_caps (&allowed_caps); const gchar *profile_str = nullptr; switch (priv->selected_profile) { case GST_H264_PROFILE_BASELINE: profile_str = "constrained-baseline"; break; case GST_H264_PROFILE_MAIN: profile_str = "main"; break; case GST_H264_PROFILE_HIGH: profile_str = "high"; break; default: g_assert_not_reached (); break; } auto caps = gst_caps_new_simple ("video/x-h264", "alignment", G_TYPE_STRING, "au", "profile", G_TYPE_STRING, profile_str, "stream-format", G_TYPE_STRING, "byte-stream", nullptr); auto output_state = gst_video_encoder_set_output_state (GST_VIDEO_ENCODER (self), caps, state); gst_video_codec_state_unref (output_state); D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC_PICTURE_CONTROL_SUPPORT feature_pic_ctrl = { }; feature_pic_ctrl.Codec = D3D12_VIDEO_ENCODER_CODEC_H264; feature_pic_ctrl.Profile.DataSize = sizeof (priv->profile_h264); feature_pic_ctrl.Profile.pH264Profile = &priv->profile_h264; feature_pic_ctrl.PictureSupport.DataSize = sizeof (priv->pic_ctrl_support); feature_pic_ctrl.PictureSupport.pH264Support = &priv->pic_ctrl_support; auto hr = video_device->CheckFeatureSupport (D3D12_FEATURE_VIDEO_ENCODER_CODEC_PICTURE_CONTROL_SUPPORT, &feature_pic_ctrl, sizeof (feature_pic_ctrl)); if (!gst_d3d12_result (hr, encoder->device) || !feature_pic_ctrl.IsSupported) { GST_ERROR_OBJECT (self, "Couldn't query picture control support"); return FALSE; } if (info->fps_n > 0 && info->fps_d > 0) { config->rate_control.TargetFrameRate.Numerator = info->fps_n; config->rate_control.TargetFrameRate.Denominator = info->fps_d; } else { config->rate_control.TargetFrameRate.Numerator = 30; config->rate_control.TargetFrameRate.Denominator = 1; } priv->config_h264.ConfigurationFlags = D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264_FLAG_NONE; priv->config_h264.DirectModeConfig = D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264_DIRECT_MODES_DISABLED; priv->config_h264.DisableDeblockingFilterConfig = D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264_SLICES_DEBLOCKING_MODE_0_ALL_LUMA_CHROMA_SLICE_BLOCK_EDGES_ALWAYS_FILTERED; if (priv->selected_profile != GST_H264_PROFILE_BASELINE) { priv->config_h264.ConfigurationFlags |= D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264_FLAG_ENABLE_CABAC_ENCODING; } return gst_d3d12_h264_enc_reconfigure (self, video_device, config, nullptr); } static gboolean gst_d3d12_h264_enc_foreach_caption_meta (GstBuffer * buffer, GstMeta ** meta, GArray * cc_sei) { if ((*meta)->info->api != GST_VIDEO_CAPTION_META_API_TYPE) return TRUE; auto cc_meta = (GstVideoCaptionMeta *) (*meta); if (cc_meta->caption_type != GST_VIDEO_CAPTION_TYPE_CEA708_RAW) return TRUE; GstH264SEIMessage sei = { }; sei.payloadType = GST_H264_SEI_REGISTERED_USER_DATA; auto rud = &sei.payload.registered_user_data; rud->country_code = 181; rud->size = cc_meta->size + 10; auto data = (guint8 *) g_malloc (rud->size); data[0] = 0; /* 16-bits itu_t_t35_provider_code */ data[1] = 49; data[2] = 'G'; /* 32-bits ATSC_user_identifier */ data[3] = 'A'; data[4] = '9'; data[5] = '4'; data[6] = 3; /* 8-bits ATSC1_data_user_data_type_code */ /* 8-bits: * 1 bit process_em_data_flag (0) * 1 bit process_cc_data_flag (1) * 1 bit additional_data_flag (0) * 5-bits cc_count */ data[7] = ((cc_meta->size / 3) & 0x1f) | 0x40; data[8] = 255; /* 8 bits em_data, unused */ memcpy (data + 9, cc_meta->data, cc_meta->size); data[cc_meta->size + 9] = 255; /* 8 marker bits */ rud->data = data; g_array_append_val (cc_sei, sei); return TRUE; } static gboolean gst_d3d12_h264_enc_start_frame (GstD3D12Encoder * encoder, ID3D12VideoDevice * video_device, GstVideoCodecFrame * frame, D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_DESC * seq_ctrl, D3D12_VIDEO_ENCODER_PICTURE_CONTROL_DESC * picture_ctrl, D3D12_VIDEO_ENCODER_RECONSTRUCTED_PICTURE * recon_pic, GstD3D12EncoderConfig * config, gboolean * need_new_session) { auto self = GST_D3D12_H264_ENC (encoder); auto priv = self->priv; static guint8 aud_data[] = { 0x00, 0x00, 0x00, 0x01, 0x09, 0xf0 }; *need_new_session = FALSE; std::lock_guard < std::mutex > lk (priv->prop_lock); seq_ctrl->Flags = D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_NONE; /* Reset GOP struct on force-keyunit */ if (GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME (frame)) { GST_DEBUG_OBJECT (self, "Force keyframe requested"); priv->gop.ForceKeyUnit (); } auto prev_level = priv->level_h264; if (!gst_d3d12_h264_enc_reconfigure (self, video_device, config, &seq_ctrl->Flags)) { GST_ERROR_OBJECT (self, "Reconfigure failed"); return FALSE; } if (seq_ctrl->Flags != D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_NONE) { *need_new_session = gst_d3d12_encoder_check_needs_new_session (config->support_flags, seq_ctrl->Flags); } if (priv->level_h264 != prev_level) *need_new_session = TRUE; if (*need_new_session) { seq_ctrl->Flags = D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_NONE; GST_DEBUG_OBJECT (self, "Needs new session, forcing IDR"); priv->gop.ForceKeyUnit (); } priv->gop.FillPicCtrl (priv->pic_control_h264); if (priv->pic_control_h264.FrameType == D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_IDR_FRAME) { GST_LOG_OBJECT (self, "Sync point at frame %" G_GUINT64_FORMAT, priv->display_order); GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (frame); } seq_ctrl->IntraRefreshConfig.Mode = D3D12_VIDEO_ENCODER_INTRA_REFRESH_MODE_NONE; seq_ctrl->IntraRefreshConfig.IntraRefreshDuration = 0; seq_ctrl->RateControl = config->rate_control; seq_ctrl->PictureTargetResolution = config->resolution; seq_ctrl->SelectedLayoutMode = priv->selected_slice_mode; seq_ctrl->FrameSubregionsLayoutData = config->layout; seq_ctrl->CodecGopSequence = config->gop_struct; picture_ctrl->IntraRefreshFrameIndex = 0; /* TODO: b frame can be non-reference picture */ picture_ctrl->Flags = priv->selected_ref_frames > 0 ? D3D12_VIDEO_ENCODER_PICTURE_CONTROL_FLAG_USED_AS_REFERENCE_PICTURE : D3D12_VIDEO_ENCODER_PICTURE_CONTROL_FLAG_NONE; picture_ctrl->PictureControlCodecData.DataSize = sizeof (priv->pic_control_h264); picture_ctrl->PictureControlCodecData.pH264PicData = &priv->pic_control_h264; if (!priv->dpb->StartFrame (picture_ctrl->Flags == D3D12_VIDEO_ENCODER_PICTURE_CONTROL_FLAG_USED_AS_REFERENCE_PICTURE, &priv->pic_control_h264, recon_pic, &picture_ctrl->ReferenceFrames, priv->display_order)) { GST_ERROR_OBJECT (self, "Start frame failed"); return FALSE; } priv->display_order++; priv->pic_control_h264.Flags = D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264_FLAG_NONE; /* FIXME: count L1 too */ priv->pic_control_h264.pic_parameter_set_id = priv->pic_control_h264.List0ReferenceFramesCount > 1 ? priv->pic_control_h264.List0ReferenceFramesCount - 1 : 0; priv->pic_control_h264.adaptive_ref_pic_marking_mode_flag = 0; priv->pic_control_h264.RefPicMarkingOperationsCommandsCount = 0; priv->pic_control_h264.pRefPicMarkingOperationsCommands = nullptr; priv->pic_control_h264.List0RefPicModificationsCount = 0; priv->pic_control_h264.pList0RefPicModifications = nullptr; priv->pic_control_h264.List1RefPicModificationsCount = 0; priv->pic_control_h264.pList1RefPicModifications = nullptr; priv->pic_control_h264.QPMapValuesCount = 0; priv->pic_control_h264.pRateControlQPMap = nullptr; if (priv->pic_control_h264.FrameType == D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_IDR_FRAME) { auto buf_size = priv->sps.bytes.size () + priv->pps[0].bytes.size (); if (priv->aud) buf_size += sizeof (aud_data); auto output_buf = gst_buffer_new_and_alloc (buf_size); GstMapInfo map_info; gst_buffer_map (output_buf, &map_info, GST_MAP_WRITE); auto data = (guint8 *) map_info.data; if (priv->aud) { memcpy (data, aud_data, sizeof (aud_data)); data += sizeof (aud_data); } memcpy (data, priv->sps.bytes.data (), priv->sps.bytes.size ()); data += priv->sps.bytes.size (); memcpy (data, priv->pps[0].bytes.data (), priv->pps[0].bytes.size ()); gst_buffer_unmap (output_buf, &map_info); frame->output_buffer = output_buf; priv->last_pps_id = 0; } else if (priv->pic_control_h264.pic_parameter_set_id != priv->last_pps_id) { const auto & cur_pps = priv->pps[priv->pic_control_h264.pic_parameter_set_id]; auto buf_size = cur_pps.bytes.size (); if (priv->aud) buf_size += sizeof (aud_data); auto output_buf = gst_buffer_new_and_alloc (buf_size); GstMapInfo map_info; gst_buffer_map (output_buf, &map_info, GST_MAP_WRITE); auto data = (guint8 *) map_info.data; if (priv->aud) { memcpy (data, aud_data, sizeof (aud_data)); data += sizeof (aud_data); } memcpy (data, cur_pps.bytes.data (), cur_pps.bytes.size ()); gst_buffer_unmap (output_buf, &map_info); frame->output_buffer = output_buf; priv->last_pps_id = priv->pic_control_h264.pic_parameter_set_id; } else if (priv->aud) { auto buf_size = sizeof (aud_data); auto output_buf = gst_buffer_new_and_alloc (buf_size); GstMapInfo map_info; gst_buffer_map (output_buf, &map_info, GST_MAP_WRITE); memcpy (map_info.data, aud_data, sizeof (aud_data)); gst_buffer_unmap (output_buf, &map_info); frame->output_buffer = output_buf; } if (priv->cc_insert != GST_D3D12_ENCODER_SEI_DISABLED) { g_array_set_size (priv->cc_sei, 0); gst_buffer_foreach_meta (frame->input_buffer, (GstBufferForeachMetaFunc) gst_d3d12_h264_enc_foreach_caption_meta, priv->cc_sei); if (priv->cc_sei->len > 0) { auto mem = gst_h264_create_sei_memory (4, priv->cc_sei); if (mem) { GST_TRACE_OBJECT (self, "Inserting CC SEI"); if (!frame->output_buffer) frame->output_buffer = gst_buffer_new (); gst_buffer_append_memory (frame->output_buffer, mem); } } } return TRUE; } static gboolean gst_d3d12_h264_enc_end_frame (GstD3D12Encoder * encoder) { auto self = GST_D3D12_H264_ENC (encoder); auto priv = self->priv; priv->dpb->EndFrame (); return TRUE; } void gst_d3d12_h264_enc_register (GstPlugin * plugin, GstD3D12Device * device, ID3D12VideoDevice * video_device, guint rank) { HRESULT hr; std::vector < std::string > profiles; GST_DEBUG_CATEGORY_INIT (gst_d3d12_h264_enc_debug, "d3d12h264enc", 0, "d3d12h264enc"); D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC feature_codec = { }; feature_codec.Codec = D3D12_VIDEO_ENCODER_CODEC_H264; hr = video_device->CheckFeatureSupport (D3D12_FEATURE_VIDEO_ENCODER_CODEC, &feature_codec, sizeof (feature_codec)); if (!gst_d3d12_result (hr, device) || !feature_codec.IsSupported) { GST_INFO_OBJECT (device, "Device does not support H.264 encoding"); return; } D3D12_FEATURE_DATA_VIDEO_ENCODER_PROFILE_LEVEL feature_profile_level = { }; D3D12_VIDEO_ENCODER_PROFILE_H264 profile_h264 = D3D12_VIDEO_ENCODER_PROFILE_H264_MAIN; D3D12_VIDEO_ENCODER_LEVELS_H264 level_h264_min; D3D12_VIDEO_ENCODER_LEVELS_H264 level_h264_max; feature_profile_level.Codec = D3D12_VIDEO_ENCODER_CODEC_H264; feature_profile_level.Profile.DataSize = sizeof (profile_h264); feature_profile_level.Profile.pH264Profile = &profile_h264; feature_profile_level.MinSupportedLevel.DataSize = sizeof (level_h264_min); feature_profile_level.MinSupportedLevel.pH264LevelSetting = &level_h264_min; feature_profile_level.MaxSupportedLevel.DataSize = sizeof (level_h264_max); feature_profile_level.MaxSupportedLevel.pH264LevelSetting = &level_h264_max; D3D12_FEATURE_DATA_VIDEO_ENCODER_INPUT_FORMAT feature_input_format = { }; feature_input_format.Codec = D3D12_VIDEO_ENCODER_CODEC_H264; feature_input_format.Profile = feature_profile_level.Profile; hr = video_device->CheckFeatureSupport (D3D12_FEATURE_VIDEO_ENCODER_PROFILE_LEVEL, &feature_profile_level, sizeof (feature_profile_level)); if (!gst_d3d12_result (hr, device) || !feature_profile_level.IsSupported) { GST_WARNING_OBJECT (device, "Main profile is not supported"); return; } feature_input_format.Format = DXGI_FORMAT_NV12; hr = video_device->CheckFeatureSupport (D3D12_FEATURE_VIDEO_ENCODER_INPUT_FORMAT, &feature_input_format, sizeof (feature_input_format)); if (!gst_d3d12_result (hr, device) || !feature_input_format.IsSupported) { GST_WARNING_OBJECT (device, "NV12 format is not supported"); return; } profiles.push_back ("constrained-baseline"); profiles.push_back ("main"); GST_INFO_OBJECT (device, "Main profile is supported, level [%d, %d]", level_h264_min, level_h264_max); profile_h264 = D3D12_VIDEO_ENCODER_PROFILE_H264_HIGH; hr = video_device->CheckFeatureSupport (D3D12_FEATURE_VIDEO_ENCODER_PROFILE_LEVEL, &feature_profile_level, sizeof (feature_profile_level)); if (gst_d3d12_result (hr, device) && feature_profile_level.IsSupported) { profiles.push_back ("high"); GST_INFO_OBJECT (device, "High profile is supported, level [%d, %d]", level_h264_min, level_h264_max); } if (profiles.empty ()) { GST_WARNING_OBJECT (device, "Couldn't find supported profile"); return; } D3D12_FEATURE_DATA_VIDEO_ENCODER_OUTPUT_RESOLUTION_RATIOS_COUNT ratios_count = { }; ratios_count.Codec = D3D12_VIDEO_ENCODER_CODEC_H264; hr = video_device->CheckFeatureSupport (D3D12_FEATURE_VIDEO_ENCODER_OUTPUT_RESOLUTION_RATIOS_COUNT, &ratios_count, sizeof (ratios_count)); if (!gst_d3d12_result (hr, device)) { GST_WARNING_OBJECT (device, "Couldn't query output resolution ratios count"); return; } std::vector < D3D12_VIDEO_ENCODER_PICTURE_RESOLUTION_RATIO_DESC > ratios; D3D12_FEATURE_DATA_VIDEO_ENCODER_OUTPUT_RESOLUTION feature_resolution = { }; feature_resolution.Codec = D3D12_VIDEO_ENCODER_CODEC_H264; feature_resolution.ResolutionRatiosCount = ratios_count.ResolutionRatiosCount; if (ratios_count.ResolutionRatiosCount > 0) { ratios.resize (ratios_count.ResolutionRatiosCount); feature_resolution.pResolutionRatios = &ratios[0]; } hr = video_device->CheckFeatureSupport (D3D12_FEATURE_VIDEO_ENCODER_OUTPUT_RESOLUTION, &feature_resolution, sizeof (feature_resolution)); if (!gst_d3d12_result (hr, device) || !feature_resolution.IsSupported) { GST_WARNING_OBJECT (device, "Couldn't query output resolution"); return; } GST_INFO_OBJECT (device, "Device supported resolution %ux%u - %ux%u, align requirement %u, %u", feature_resolution.MinResolutionSupported.Width, feature_resolution.MinResolutionSupported.Height, feature_resolution.MaxResolutionSupported.Width, feature_resolution.MaxResolutionSupported.Height, feature_resolution.ResolutionWidthMultipleRequirement, feature_resolution.ResolutionHeightMultipleRequirement); guint rc_support = 0; D3D12_FEATURE_DATA_VIDEO_ENCODER_RATE_CONTROL_MODE feature_rate_control = { }; feature_rate_control.Codec = D3D12_VIDEO_ENCODER_CODEC_H264; feature_rate_control.RateControlMode = D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CQP; hr = video_device->CheckFeatureSupport (D3D12_FEATURE_VIDEO_ENCODER_RATE_CONTROL_MODE, &feature_rate_control, sizeof (feature_rate_control)); if (SUCCEEDED (hr) && feature_rate_control.IsSupported) { GST_INFO_OBJECT (device, "CQP suported"); rc_support |= (1 << D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CQP); } feature_rate_control.RateControlMode = D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CBR; hr = video_device->CheckFeatureSupport (D3D12_FEATURE_VIDEO_ENCODER_RATE_CONTROL_MODE, &feature_rate_control, sizeof (feature_rate_control)); if (SUCCEEDED (hr) && feature_rate_control.IsSupported) { GST_INFO_OBJECT (device, "CBR suported"); rc_support |= (1 << D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CBR); } feature_rate_control.RateControlMode = D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR; hr = video_device->CheckFeatureSupport (D3D12_FEATURE_VIDEO_ENCODER_RATE_CONTROL_MODE, &feature_rate_control, sizeof (feature_rate_control)); if (SUCCEEDED (hr) && feature_rate_control.IsSupported) { GST_INFO_OBJECT (device, "VBR suported"); rc_support |= (1 << D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR); } feature_rate_control.RateControlMode = D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR; hr = video_device->CheckFeatureSupport (D3D12_FEATURE_VIDEO_ENCODER_RATE_CONTROL_MODE, &feature_rate_control, sizeof (feature_rate_control)); if (SUCCEEDED (hr) && feature_rate_control.IsSupported) { GST_INFO_OBJECT (device, "VBR suported"); rc_support |= (1 << D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR); } if (!rc_support) { GST_WARNING_OBJECT (device, "Couldn't find supported rate control mode"); return; } profile_h264 = D3D12_VIDEO_ENCODER_PROFILE_H264_MAIN; D3D12_FEATURE_DATA_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE feature_layout = { }; feature_layout.Codec = D3D12_VIDEO_ENCODER_CODEC_H264; feature_layout.Profile.DataSize = sizeof (profile_h264); feature_layout.Profile.pH264Profile = &profile_h264; feature_layout.Level.DataSize = sizeof (D3D12_VIDEO_ENCODER_LEVELS_H264); D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE layout_modes[] = { D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_FULL_FRAME, D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_BYTES_PER_SUBREGION, D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_SQUARE_UNITS_PER_SUBREGION_ROW_UNALIGNED, D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_UNIFORM_PARTITIONING_ROWS_PER_SUBREGION, D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_UNIFORM_PARTITIONING_SUBREGIONS_PER_FRAME, }; guint slice_mode_support = 0; for (guint i = 0; i < G_N_ELEMENTS (layout_modes); i++) { feature_layout.SubregionMode = layout_modes[i]; for (guint level = (guint) level_h264_min; level <= (guint) level_h264_max; level++) { auto level_h264 = (D3D12_VIDEO_ENCODER_LEVELS_H264) level; feature_layout.Level.pH264LevelSetting = &level_h264; hr = video_device->CheckFeatureSupport (D3D12_FEATURE_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE, &feature_layout, sizeof (feature_layout)); if (SUCCEEDED (hr) && feature_layout.IsSupported) { slice_mode_support |= (1 << layout_modes[i]); break; } } } if (!slice_mode_support) { GST_WARNING_OBJECT (device, "No supported subregion layout"); return; } if (slice_mode_support & (1 << D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_FULL_FRAME) == 0) { GST_WARNING_OBJECT (device, "Full frame encoding is not supported"); return; } auto subregions = g_flags_to_string (GST_TYPE_D3D12_ENCODER_SUBREGION_LAYOUT_SUPPORT, slice_mode_support); GST_INFO_OBJECT (device, "Supported subregion modes: \"%s\"", subregions); g_free (subregions); D3D12_VIDEO_ENCODER_CODEC_PICTURE_CONTROL_SUPPORT_H264 picture_ctrl_h264; D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC_PICTURE_CONTROL_SUPPORT feature_pic_ctrl = { }; feature_pic_ctrl.Codec = D3D12_VIDEO_ENCODER_CODEC_H264; feature_pic_ctrl.Profile.DataSize = sizeof (profile_h264); feature_pic_ctrl.Profile.pH264Profile = &profile_h264; feature_pic_ctrl.PictureSupport.DataSize = sizeof (picture_ctrl_h264); feature_pic_ctrl.PictureSupport.pH264Support = &picture_ctrl_h264; hr = video_device->CheckFeatureSupport (D3D12_FEATURE_VIDEO_ENCODER_CODEC_PICTURE_CONTROL_SUPPORT, &feature_pic_ctrl, sizeof (feature_pic_ctrl)); if (!gst_d3d12_result (hr, device) || !feature_pic_ctrl.IsSupported) { GST_WARNING_OBJECT (device, "Couldn't query picture control support"); return; } GST_INFO_OBJECT (device, "MaxL0ReferencesForP: %u, MaxL0ReferencesForB: %u, " "MaxL1ReferencesForB: %u, MaxLongTermReferences: %u, MaxDPBCapacity %u", picture_ctrl_h264.MaxL0ReferencesForP, picture_ctrl_h264.MaxL0ReferencesForB, picture_ctrl_h264.MaxL1ReferencesForB, picture_ctrl_h264.MaxLongTermReferences, picture_ctrl_h264.MaxDPBCapacity); std::string resolution_str = "width = (int) [" + std::to_string (feature_resolution.MinResolutionSupported.Width) + ", " + std::to_string (feature_resolution.MaxResolutionSupported.Width) + "], height = (int) [" + std::to_string (feature_resolution.MinResolutionSupported.Height) + ", " + std::to_string (feature_resolution.MaxResolutionSupported.Height) + " ]"; std::string sink_caps_str = "video/x-raw, format = (string) NV12, " + resolution_str + ", interlace-mode = (string) progressive"; std::string src_caps_str = "video/x-h264, " + resolution_str + ", stream-format = (string) byte-stream, alignment = (string) au, "; if (profiles.size () == 1) { src_caps_str += "profile = (string) " + profiles[0]; } else { src_caps_str += "profile = (string) { "; std::reverse (profiles.begin (), profiles.end ()); for (size_t i = 0; i < profiles.size (); i++) { if (i != 0) src_caps_str += ", "; src_caps_str += profiles[i]; } src_caps_str += " }"; } auto sysmem_caps = gst_caps_from_string (sink_caps_str.c_str ()); auto sink_caps = gst_caps_copy (sysmem_caps); gst_caps_set_features_simple (sink_caps, gst_caps_features_new (GST_CAPS_FEATURE_MEMORY_D3D12_MEMORY, nullptr)); gst_caps_append (sink_caps, sysmem_caps); auto src_caps = gst_caps_from_string (src_caps_str.c_str ()); GST_MINI_OBJECT_FLAG_SET (sink_caps, GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED); GST_MINI_OBJECT_FLAG_SET (src_caps, GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED); GstD3D12H264EncClassData *cdata = new GstD3D12H264EncClassData (); g_object_get (device, "adapter-luid", &cdata->luid, "device-id", &cdata->device_id, "vendor-id", &cdata->vendor_id, "description", &cdata->description, nullptr); cdata->sink_caps = sink_caps; cdata->src_caps = src_caps; cdata->rc_support = rc_support; cdata->slice_mode_support = slice_mode_support; GType type; gchar *type_name; gchar *feature_name; guint index = 0; GTypeInfo type_info = { sizeof (GstD3D12H264EncClass), nullptr, nullptr, (GClassInitFunc) gst_d3d12_h264_enc_class_init, nullptr, nullptr, sizeof (GstD3D12H264Enc), 0, (GInstanceInitFunc) gst_d3d12_h264_enc_init, }; type_info.class_data = cdata; type_name = g_strdup ("GstD3D12H264Enc"); feature_name = g_strdup ("d3d12h264enc"); while (g_type_from_name (type_name)) { index++; g_free (type_name); g_free (feature_name); type_name = g_strdup_printf ("GstD3D12H264Device%dEnc", index); feature_name = g_strdup_printf ("d3d12h264device%denc", index); } type = g_type_register_static (GST_TYPE_D3D12_ENCODER, type_name, &type_info, (GTypeFlags) 0); if (rank > 0 && index != 0) rank--; if (index != 0) gst_element_type_set_skip_documentation (type); if (!gst_element_register (plugin, feature_name, rank, type)) GST_WARNING ("Failed to register plugin '%s'", type_name); g_free (type_name); g_free (feature_name); }