mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-14 05:12:09 +00:00
96cf3d7063
gst_d3d12_result() will print message with ERROR level if failed. Use FAILED/SUCCEEDED macros instead, since not-supported feature is not a critical error Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/6955>
2478 lines
84 KiB
C++
2478 lines
84 KiB
C++
/* 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>
|
|
#include <algorithm>
|
|
|
|
/* *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 guint
|
|
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 (FAILED (hr) || !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 (FAILED (hr) || !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 (FAILED (hr) || !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 (SUCCEEDED (hr) && 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 (FAILED (hr)) {
|
|
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 (FAILED (hr) || !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 (FAILED (hr) || !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);
|
|
}
|