gstreamer/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12h264enc.cpp

2478 lines
84 KiB
C++
Raw Normal View History

/* GStreamer
* Copyright (C) 2024 Seungha Yang <seungha@centricular.com>
*
* 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 <gst/base/gstqueuearray.h>
#include <gst/codecparsers/gsth264parser.h>
#include <gst/codecparsers/gsth264bitwriter.h>
#include <directx/d3dx12.h>
#include <wrl.h>
#include <string.h>
#include <cmath>
#include <vector>
#include <array>
#include <string>
#include <mutex>
#include <condition_variable>
#include <unordered_map>
#include <memory>
/* *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<D3D12_VIDEO_ENCODER_REFERENCE_PICTURE_DESCRIPTOR_H264> ref_pic_desc_;
std::vector<UINT64> ref_pic_display_order_;
D3D12_VIDEO_ENCODER_RECONSTRUCTED_PICTURE recon_pic_ = { };
D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264 ctrl_data_ = { };
std::vector<UINT> list0_;
std::vector<UINT> 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<guint8> bytes;
};
struct GstD3D12H264PPS
{
void Clear ()
{
memset (&pps, 0, sizeof (GstH264PPS));
bytes.clear ();
}
GstH264PPS pps;
std::vector<guint8> 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<GstD3D12H264PPS> pps;
GstH264Profile selected_profile;
GstD3D12H264EncGop gop;
std::unique_ptr<GstD3D12H264EncDpb> 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 <seungha@centricular.com>");
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<D3D12_VIDEO_ENCODER_LEVELS_H264, guint8>
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<std::pair<gint, gint>, 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);
}