diff --git a/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json b/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json index 8b2719dea7..1afefbf251 100644 --- a/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json +++ b/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json @@ -11614,6 +11614,290 @@ }, "rank": "none" }, + "d3d12h264enc": { + "author": "Seungha Yang ", + "description": "Direct3D12 H.264 Video Encoder", + "hierarchy": [ + "GstD3D12H264Enc", + "GstD3D12Encoder", + "GstVideoEncoder", + "GstElement", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "interfaces": [ + "GstPreset" + ], + "klass": "Codec/Encoder/Video/Hardware", + "pad-templates": { + "sink": { + "caps": "video/x-raw(memory:D3D12Memory):\n format: NV12\n width: [ 16, 4096 ]\n height: [ 16, 4096 ]\n interlace-mode: progressive\nvideo/x-raw:\n format: NV12\n width: [ 16, 4096 ]\n height: [ 16, 4096 ]\n interlace-mode: progressive\n", + "direction": "sink", + "presence": "always" + }, + "src": { + "caps": "video/x-h264:\n width: [ 16, 4096 ]\n height: [ 16, 4096 ]\n stream-format: byte-stream\n alignment: au\n profile: { (string)high, (string)main, (string)constrained-baseline }\n", + "direction": "src", + "presence": "always" + } + }, + "properties": { + "aud": { + "blurb": "Use AU delimiter", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "true", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": true + }, + "bitrate": { + "blurb": "Target bitrate in kbits/second. Used for \"cbr\", \"vbr\", and \"qvbr\" rate control", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "2000", + "max": "-1", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + }, + "cc-insert": { + "blurb": "Closed Caption insert mode", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "insert (0)", + "mutable": "null", + "readable": true, + "type": "GstD3D12EncoderSeiInsertMode", + "writable": true + }, + "frame-analysis": { + "blurb": "Enable 2 pass encoding if supported by hardware", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "false", + "mutable": "null", + "readable": true, + "type": "gboolean", + "writable": true + }, + "gop-size": { + "blurb": "Size of GOP (0 = infinite)", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "60", + "max": "-1", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + }, + "max-bitrate": { + "blurb": "Peak bitrate in kbits/second. Used for \"vbr\", and \"qvbr\" rate control", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "4000", + "max": "-1", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + }, + "qp-b": { + "blurb": "Constant QP value for B frames. Used for \"cqp\" rate control", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "23", + "max": "51", + "min": "1", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + }, + "qp-i": { + "blurb": "Constant QP value for I frames. Used for \"cqp\" rate control", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "23", + "max": "51", + "min": "1", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + }, + "qp-init": { + "blurb": "Initial QP value. Used for \"cbr\", \"vbr\", and \"qvbr\" rate control", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "51", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + }, + "qp-max": { + "blurb": "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", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "51", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + }, + "qp-min": { + "blurb": "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", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "51", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + }, + "qp-p": { + "blurb": "Constant QP value for P frames. Used for \"cqp\" rate control", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "23", + "max": "51", + "min": "1", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + }, + "qvbr-quality": { + "blurb": "Constant quality target value for \"qvbr\" rate control", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "23", + "max": "51", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + }, + "rate-control": { + "blurb": "Rate Control Method", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "vbr (3)", + "mutable": "null", + "readable": true, + "type": "GstD3D12EncoderRateControl", + "writable": true + }, + "rate-control-support": { + "blurb": "Supported rate control modes", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "(none)", + "mutable": "null", + "readable": true, + "type": "GstD3D12EncoderRateControlSupport", + "writable": false + }, + "ref-frames": { + "blurb": "Preferred number of reference frames. Actual number of reference frames can be limited depending on hardware (0 = unspecified)", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "16", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + }, + "slice-mode": { + "blurb": "Slice partiton mode", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "full (0)", + "mutable": "null", + "readable": true, + "type": "GstD3D12EncoderSubregionLayout", + "writable": true + }, + "slice-mode-support": { + "blurb": "Supported slice partition modes", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "full", + "mutable": "null", + "readable": true, + "type": "GstD3D12EncoderSubregionLayoutSupport", + "writable": false + }, + "slice-partition": { + "blurb": "Slice partition threshold interpreted depending on \"slice-mode\". If set zero, full frame encoding will be selected without partitioning regardless of requested \"slice-mode\"", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "-1", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": true + } + }, + "rank": "none" + }, "d3d12h265dec": { "author": "Seungha Yang ", "description": "Direct3D12/DXVA based H.265 video decoder", @@ -12415,6 +12699,194 @@ } ] }, + "GstD3D12Encoder": { + "hierarchy": [ + "GstD3D12Encoder", + "GstVideoEncoder", + "GstElement", + "GstObject", + "GInitiallyUnowned", + "GObject" + ], + "interfaces": [ + "GstPreset" + ], + "kind": "object", + "properties": { + "adapter-luid": { + "blurb": "DXGI Adapter LUID (Locally Unique Identifier) of created device", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "9223372036854775807", + "min": "-9223372036854775808", + "mutable": "null", + "readable": true, + "type": "gint64", + "writable": false + }, + "device-id": { + "blurb": "DXGI Device ID", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "-1", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": false + }, + "vendor-id": { + "blurb": "DXGI Vendor ID", + "conditionally-available": false, + "construct": false, + "construct-only": false, + "controllable": false, + "default": "0", + "max": "-1", + "min": "0", + "mutable": "null", + "readable": true, + "type": "guint", + "writable": false + } + } + }, + "GstD3D12EncoderRateControl": { + "kind": "enum", + "values": [ + { + "desc": "Constant QP", + "name": "cqp", + "value": "1" + }, + { + "desc": "Constant bitrate", + "name": "cbr", + "value": "2" + }, + { + "desc": "Variable bitrate", + "name": "vbr", + "value": "3" + }, + { + "desc": "Constant quality variable bitrate", + "name": "qvbr", + "value": "4" + } + ] + }, + "GstD3D12EncoderRateControlSupport": { + "kind": "flags", + "values": [ + { + "desc": "Constant QP", + "name": "cqp", + "value": "0x00000002" + }, + { + "desc": "Constant bitrate", + "name": "cbr", + "value": "0x00000004" + }, + { + "desc": "Variable bitrate", + "name": "vbr", + "value": "0x00000008" + }, + { + "desc": "Constant quality variable bitrate", + "name": "qvbr", + "value": "0x00000010" + } + ] + }, + "GstD3D12EncoderSeiInsertMode": { + "kind": "enum", + "values": [ + { + "desc": "Insert", + "name": "insert", + "value": "0" + }, + { + "desc": "Insert and drop", + "name": "insert-and-drop", + "value": "1" + }, + { + "desc": "Disabled", + "name": "disabled", + "value": "2" + } + ] + }, + "GstD3D12EncoderSubregionLayout": { + "kind": "enum", + "values": [ + { + "desc": "Full frame without partitioning", + "name": "full", + "value": "0" + }, + { + "desc": "Bytes per subregion", + "name": "bytes", + "value": "1" + }, + { + "desc": "Coding units (e.g., macroblock) per subregion", + "name": "coding-units", + "value": "2" + }, + { + "desc": "Uniform rows per subregion", + "name": "rows", + "value": "3" + }, + { + "desc": "Uniform subregions per frame", + "name": "subregions", + "value": "4" + } + ] + }, + "GstD3D12EncoderSubregionLayoutSupport": { + "kind": "flags", + "values": [ + { + "desc": "Full frame without partitioning", + "name": "full", + "value": "0x00000001" + }, + { + "desc": "Bytes per subregion", + "name": "bytes", + "value": "0x00000002" + }, + { + "desc": "Coding units (e.g., macroblock) per subregion", + "name": "coding-units", + "value": "0x00000004" + }, + { + "desc": "Uniform rows (in coding-unit) per subregion", + "name": "rows", + "value": "0x00000008" + }, + { + "desc": "Uniform subregions per frame", + "name": "subregions", + "value": "0x00000010" + } + ] + }, "GstD3D12MSAAMode": { "kind": "enum", "values": [ diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12-private.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12-private.h index faea28b479..9ff1cb2ee4 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12-private.h +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12-private.h @@ -188,3 +188,7 @@ static const GstD3D12Format g_gst_d3d12_default_format_map[] = { #undef MAKE_FORMAT_MAP_RGBP #define GST_D3D12_N_FORMATS G_N_ELEMENTS(g_gst_d3d12_default_format_map) + +void gst_d3d12_device_clear_yuv_texture (GstD3D12Device * device, + GstMemory * mem); + diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12device.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12device.cpp index 681cb8c9e7..2e4c01506c 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12device.cpp +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12device.cpp @@ -24,6 +24,7 @@ #include "gstd3d12.h" #include "gstd3d12-private.h" #include "gstd3d11on12.h" +#include #include #include #include @@ -143,6 +144,8 @@ struct _GstD3D12DevicePrivate GstD3D12CommandListPool *copy_cl_pool = nullptr; GstD3D12CommandAllocatorPool *copy_ca_pool = nullptr; + guint rtv_inc_size; + guint adapter_index = 0; guint device_id = 0; guint vendor_id = 0; @@ -649,6 +652,9 @@ gst_d3d12_device_new_internal (const GstD3D12DeviceConstructData * data) if (!priv->copy_ca_pool) goto error; + priv->rtv_inc_size = + device->GetDescriptorHandleIncrementSize (D3D12_DESCRIPTOR_HEAP_TYPE_RTV); + return self; error: @@ -1107,3 +1113,75 @@ gst_d3d12_device_d3d12_debug (GstD3D12Device * device, const gchar * file, info_queue->ClearStoredMessages (); } + +void +gst_d3d12_device_clear_yuv_texture (GstD3D12Device * device, GstMemory * mem) +{ + auto priv = device->priv; + auto dmem = GST_D3D12_MEMORY_CAST (mem); + ComPtr < ID3D12DescriptorHeap > heap; + + auto resource = gst_d3d12_memory_get_resource_handle (dmem); + auto desc = resource->GetDesc (); + + if (desc.Format != DXGI_FORMAT_NV12 && desc.Format != DXGI_FORMAT_P010 && + desc.Format != DXGI_FORMAT_P016) { + return; + } + + gst_d3d12_memory_get_render_target_view_heap (dmem, &heap); + if (!heap) + return; + + GstD3D12CommandAllocator *gst_ca = nullptr; + gst_d3d12_command_allocator_pool_acquire (priv->direct_ca_pool, &gst_ca); + if (!gst_ca) + return; + + ComPtr < ID3D12CommandAllocator > ca; + gst_d3d12_command_allocator_get_handle (gst_ca, &ca); + + GstD3D12CommandList *gst_cl = nullptr; + gst_d3d12_command_list_pool_acquire (priv->direct_cl_pool, + ca.Get (), &gst_cl); + if (!gst_cl) { + gst_d3d12_command_allocator_unref (gst_ca); + return; + } + + ComPtr < ID3D12CommandList > cl_base; + ComPtr < ID3D12GraphicsCommandList > cl; + + gst_d3d12_command_list_get_handle (gst_cl, &cl_base); + cl_base.As (&cl); + + auto rtv_handle = + CD3DX12_CPU_DESCRIPTOR_HANDLE (heap->GetCPUDescriptorHandleForHeapStart + (), + priv->rtv_inc_size); + + const FLOAT clear_color[4] = { 0.5f, 0.5f, 0.5f, 1.0f }; + cl->ClearRenderTargetView (rtv_handle, clear_color, 0, nullptr); + + auto hr = cl->Close (); + if (!gst_d3d12_result (hr, device)) { + gst_clear_d3d12_command_list (&gst_cl); + gst_clear_d3d12_command_allocator (&gst_ca); + return; + } + + ID3D12CommandList *cmd_list[] = { cl.Get () }; + guint64 fence_val = 0; + hr = gst_d3d12_command_queue_execute_command_lists (priv->direct_queue, + 1, cmd_list, &fence_val); + auto ret = gst_d3d12_result (hr, device); + gst_d3d12_command_list_unref (gst_cl); + + if (ret) { + gst_d3d12_command_queue_set_notify (priv->direct_queue, fence_val, + gst_ca, (GDestroyNotify) gst_d3d12_command_allocator_unref); + dmem->fence_value = fence_val; + } else { + gst_d3d12_command_allocator_unref (gst_ca); + } +} diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12dpbstorage.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12dpbstorage.cpp new file mode 100644 index 0000000000..3fec034dd8 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12dpbstorage.cpp @@ -0,0 +1,333 @@ +/* GStreamer + * Copyright (C) 2023 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstd3d12dpbstorage.h" +#include +#include +#include + +GST_DEBUG_CATEGORY_STATIC (gst_d3d12_dpb_storage_debug); +#define GST_CAT_DEFAULT gst_d3d12_dpb_storage_debug + +/* *INDENT-OFF* */ +using namespace Microsoft::WRL; + +struct OwnedTexture +{ + ComPtr texture; + guint subresource = 0; + gboolean is_free = TRUE; +}; + +struct GstD3D12DpbStoragePrivate +{ + gboolean array_of_textures; + DXGI_FORMAT format; + guint width; + guint height; + D3D12_RESOURCE_FLAGS resource_flags; + std::vector dpb; + std::vector dpb_subresource; + std::vector pool; + ComPtr base_texture; +}; +/* *INDENT-ON* */ + +struct _GstD3D12DpbStorage +{ + GstObject parent; + + GstD3D12Device *device; + GstD3D12DpbStoragePrivate *priv; +}; + +static void gst_d3d12_dpb_storage_finalize (GObject * object); + +#define gst_d3d12_dpb_storage_parent_class parent_class +G_DEFINE_TYPE (GstD3D12DpbStorage, gst_d3d12_dpb_storage, GST_TYPE_OBJECT); + +static void +gst_d3d12_dpb_storage_class_init (GstD3D12DpbStorageClass * klass) +{ + auto object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gst_d3d12_dpb_storage_finalize; + + GST_DEBUG_CATEGORY_INIT (gst_d3d12_dpb_storage_debug, "d3d12dpbstorage", 0, + "d3d12dpbstorage"); +} + +static void +gst_d3d12_dpb_storage_init (GstD3D12DpbStorage * self) +{ + self->priv = new GstD3D12DpbStoragePrivate (); +} + +static void +gst_d3d12_dpb_storage_finalize (GObject * object) +{ + auto self = GST_D3D12_DPB_STORAGE (object); + + delete self->priv; + gst_clear_object (&self->device); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static HRESULT +allocate_texture (ID3D12Device * device, DXGI_FORMAT format, guint width, + guint height, D3D12_RESOURCE_FLAGS resource_flags, guint array_size, + ID3D12Resource ** texture) +{ + D3D12_HEAP_PROPERTIES prop = + CD3DX12_HEAP_PROPERTIES (D3D12_HEAP_TYPE_DEFAULT); + D3D12_RESOURCE_DESC desc = CD3DX12_RESOURCE_DESC::Tex2D (format, + width, height, array_size, 1, 1, 0, resource_flags); + + return device->CreateCommittedResource (&prop, D3D12_HEAP_FLAG_NONE, + &desc, D3D12_RESOURCE_STATE_COMMON, nullptr, IID_PPV_ARGS (texture)); +} + +gboolean +gst_d3d12_dpb_storage_acquire_frame (GstD3D12DpbStorage * storage, + D3D12_VIDEO_ENCODER_RECONSTRUCTED_PICTURE * frame) +{ + g_return_val_if_fail (GST_IS_D3D12_DPB_STORAGE (storage), FALSE); + g_return_val_if_fail (frame, FALSE); + + auto priv = storage->priv; + + for (size_t i = 0; i < priv->pool.size (); i++) { + auto & it = priv->pool[i]; + if (it.is_free) { + frame->pReconstructedPicture = it.texture.Get (); + frame->ReconstructedPictureSubresource = it.subresource; + it.is_free = FALSE; + return TRUE; + } + } + + if (!priv->array_of_textures) { + GST_ERROR_OBJECT (storage, "No available free texture"); + frame->pReconstructedPicture = nullptr; + frame->ReconstructedPictureSubresource = 0; + return FALSE; + } + + auto device = gst_d3d12_device_get_device_handle (storage->device); + + OwnedTexture new_texture; + new_texture.is_free = FALSE; + HRESULT hr = allocate_texture (device, priv->format, priv->width, + priv->height, priv->resource_flags, 1, &new_texture.texture); + + if (!gst_d3d12_result (hr, storage->device)) { + GST_ERROR_OBJECT (storage, "Couldn't allocate texture"); + frame->pReconstructedPicture = nullptr; + frame->ReconstructedPictureSubresource = 0; + return FALSE; + } + + frame->pReconstructedPicture = new_texture.texture.Get (); + frame->ReconstructedPictureSubresource = 0; + + priv->pool.push_back (new_texture); + + return TRUE; +} + +gboolean +gst_d3d12_dpb_storage_add_frame (GstD3D12DpbStorage * storage, + D3D12_VIDEO_ENCODER_RECONSTRUCTED_PICTURE * frame) +{ + g_return_val_if_fail (GST_IS_D3D12_DPB_STORAGE (storage), FALSE); + g_return_val_if_fail (frame, FALSE); + g_return_val_if_fail (frame->pReconstructedPicture, FALSE); + + auto priv = storage->priv; + + priv->dpb.insert (priv->dpb.begin (), frame->pReconstructedPicture); + priv->dpb_subresource.insert (priv->dpb_subresource.begin (), + frame->ReconstructedPictureSubresource); + + return TRUE; +} + +gboolean +gst_d3d12_dpb_storage_get_reference_frames (GstD3D12DpbStorage * storage, + D3D12_VIDEO_ENCODE_REFERENCE_FRAMES * ref_frames) +{ + g_return_val_if_fail (GST_IS_D3D12_DPB_STORAGE (storage), FALSE); + g_return_val_if_fail (ref_frames, FALSE); + + auto priv = storage->priv; + + ref_frames->NumTexture2Ds = priv->dpb.size (); + ref_frames->ppTexture2Ds = priv->dpb.data (); + if (priv->array_of_textures) + ref_frames->pSubresources = nullptr; + else + ref_frames->pSubresources = priv->dpb_subresource.data (); + + return TRUE; +} + +static void +gst_d3d12_dpb_storage_release_frame (GstD3D12DpbStorage * self, + ID3D12Resource * texture, guint subresource) +{ + auto priv = self->priv; + + if (priv->array_of_textures) { + for (size_t i = 0; i < priv->pool.size (); i++) { + auto & it = priv->pool[i]; + if (texture == it.texture.Get () && it.subresource == subresource) { + it.is_free = TRUE; + return; + } + } + + g_assert_not_reached (); + } else { + g_return_if_fail (subresource < priv->pool.size ()); + + priv->pool[subresource].is_free = TRUE; + } +} + +gboolean +gst_d3d12_dpb_storage_remove_oldest_frame (GstD3D12DpbStorage * storage) +{ + g_return_val_if_fail (GST_IS_D3D12_DPB_STORAGE (storage), FALSE); + + auto priv = storage->priv; + + if (priv->dpb.empty ()) { + GST_WARNING_OBJECT (storage, "DPB is empty now"); + return FALSE; + } + + gst_d3d12_dpb_storage_release_frame (storage, + priv->dpb.back (), priv->dpb_subresource.back ()); + + priv->dpb.pop_back (); + priv->dpb_subresource.pop_back (); + + return TRUE; +} + +void +gst_d3d12_dpb_storage_clear_dpb (GstD3D12DpbStorage * storage) +{ + g_return_if_fail (GST_IS_D3D12_DPB_STORAGE (storage)); + + auto priv = storage->priv; + + g_assert (priv->dpb.size () == priv->dpb_subresource.size ()); + + for (size_t i = 0; i < priv->dpb.size (); i++) { + gst_d3d12_dpb_storage_release_frame (storage, + priv->dpb[i], priv->dpb_subresource[i]); + } + + priv->dpb.clear (); + priv->dpb_subresource.clear (); +} + +guint +gst_d3d12_dpb_storage_get_dpb_size (GstD3D12DpbStorage * storage) +{ + g_return_val_if_fail (GST_IS_D3D12_DPB_STORAGE (storage), 0); + + auto priv = storage->priv; + + return priv->dpb.size (); +} + +guint +gst_d3d12_dpb_storage_get_pool_size (GstD3D12DpbStorage * storage) +{ + g_return_val_if_fail (GST_IS_D3D12_DPB_STORAGE (storage), 0); + + auto priv = storage->priv; + + return priv->pool.size (); +} + +GstD3D12DpbStorage * +gst_d3d12_dpb_storage_new (GstD3D12Device * device, guint dpb_size, + gboolean use_array_of_textures, DXGI_FORMAT format, guint width, + guint height, D3D12_RESOURCE_FLAGS resource_flags) +{ + g_return_val_if_fail (GST_IS_D3D12_DEVICE (device), nullptr); + + auto self = (GstD3D12DpbStorage *) + g_object_new (GST_TYPE_D3D12_DPB_STORAGE, nullptr); + gst_object_ref_sink (self); + + self->device = (GstD3D12Device *) gst_object_ref (device); + auto priv = self->priv; + auto device_handle = gst_d3d12_device_get_device_handle (device); + HRESULT hr; + + if (use_array_of_textures) { + for (guint i = 0; i < dpb_size; i++) { + OwnedTexture texture; + + hr = allocate_texture (device_handle, + format, width, height, resource_flags, 1, &texture.texture); + if (!gst_d3d12_result (hr, device)) { + GST_ERROR_OBJECT (self, "Couldn't allocate initial texture"); + gst_object_unref (self); + return nullptr; + } + + priv->pool.push_back (texture); + } + } else { + hr = allocate_texture (device_handle, + format, width, height, resource_flags, dpb_size, &priv->base_texture); + + if (!gst_d3d12_result (hr, device)) { + GST_ERROR_OBJECT (self, "Couldn't allocate initial texture"); + gst_object_unref (self); + return nullptr; + } + + priv->pool.resize (dpb_size); + + for (guint i = 0; i < dpb_size; i++) { + priv->pool[i].texture = priv->base_texture; + priv->pool[i].subresource = i; + } + } + + priv->width = width; + priv->height = height; + priv->resource_flags = resource_flags; + priv->array_of_textures = use_array_of_textures; + priv->dpb.reserve (dpb_size); + priv->dpb_subresource.reserve (dpb_size); + + return self; +} diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12dpbstorage.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12dpbstorage.h new file mode 100644 index 0000000000..08e64b0f75 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12dpbstorage.h @@ -0,0 +1,57 @@ +/* GStreamer + * Copyright (C) 2023 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include +#include "gstd3d12.h" + +G_BEGIN_DECLS + +#define GST_TYPE_D3D12_DPB_STORAGE (gst_d3d12_dpb_storage_get_type()) +G_DECLARE_FINAL_TYPE (GstD3D12DpbStorage, gst_d3d12_dpb_storage, + GST, D3D12_DPB_STORAGE, GstObject); + +GstD3D12DpbStorage * gst_d3d12_dpb_storage_new (GstD3D12Device * device, + guint dpb_size, + gboolean use_array_of_textures, + DXGI_FORMAT format, + guint width, + guint height, + D3D12_RESOURCE_FLAGS resource_flags); + +gboolean gst_d3d12_dpb_storage_acquire_frame (GstD3D12DpbStorage * storage, + D3D12_VIDEO_ENCODER_RECONSTRUCTED_PICTURE * frame); + +gboolean gst_d3d12_dpb_storage_add_frame (GstD3D12DpbStorage * storage, + D3D12_VIDEO_ENCODER_RECONSTRUCTED_PICTURE * frame); + +gboolean gst_d3d12_dpb_storage_get_reference_frames (GstD3D12DpbStorage * storage, + D3D12_VIDEO_ENCODE_REFERENCE_FRAMES * ref_frames); + +gboolean gst_d3d12_dpb_storage_remove_oldest_frame (GstD3D12DpbStorage * storage); + +void gst_d3d12_dpb_storage_clear_dpb (GstD3D12DpbStorage * storage); + +guint gst_d3d12_dpb_storage_get_dpb_size (GstD3D12DpbStorage * storage); + +guint gst_d3d12_dpb_storage_get_pool_size (GstD3D12DpbStorage * storage); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12encoder.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12encoder.cpp new file mode 100644 index 0000000000..fdaec99512 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12encoder.cpp @@ -0,0 +1,1646 @@ +/* GStreamer + * Copyright (C) 2024 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstd3d12encoder.h" +#include "gstd3d12encoderbufferpool.h" +#include "gstd3d12pluginutils.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* *INDENT-OFF* */ +using namespace Microsoft::WRL; +/* *INDENT-ON* */ + +GST_DEBUG_CATEGORY_STATIC (gst_d3d12_encoder_debug); +#define GST_CAT_DEFAULT gst_d3d12_encoder_debug + +enum +{ + PROP_0, + PROP_ADAPTER_LUID, + PROP_DEVICE_ID, + PROP_VENDOR_ID, +}; + +#define ASYNC_DEPTH 4 + +struct EncoderOutputData +{ + GstVideoCodecFrame *frame = nullptr; + GstD3D12EncoderBuffer *buffer = nullptr; + guint64 fence_val = 0; +}; + +/* *INDENT-OFF* */ +struct EncoderSessionData +{ + EncoderSessionData () + { + output_queue = gst_queue_array_new_for_struct (sizeof (EncoderOutputData), + 16); + } + + ~EncoderSessionData () + { + gst_clear_object (&encoder_pool); + gst_queue_array_free (output_queue); + } + + ComPtr encoder; + ComPtr heap; + + std::mutex queue_lock; + std::condition_variable queue_cond; + GstQueueArray *output_queue; + + GstD3D12EncoderBufferPool *encoder_pool = nullptr; + GstBufferPool *upload_pool = nullptr; +}; + +struct EncoderCmdData +{ + EncoderCmdData () + { + event_handle = CreateEventEx (nullptr, nullptr, 0, EVENT_ALL_ACCESS); + } + + ~EncoderCmdData () + { + if (queue) + gst_d3d12_command_queue_fence_wait (queue, G_MAXUINT64, event_handle); + + CloseHandle (event_handle); + gst_clear_object (&ca_pool); + gst_clear_object (&queue); + } + + ComPtr video_device; + ComPtr cl; + GstD3D12CommandQueue *queue = nullptr; + GstD3D12CommandAllocatorPool *ca_pool = nullptr; + HANDLE event_handle; + guint64 fence_val = 0; +}; + +struct GstD3D12EncoderPrivate +{ + GstD3D12EncoderPrivate () + { + fence_data_pool = gst_d3d12_fence_data_pool_new (); + } + + ~GstD3D12EncoderPrivate () + { + gst_clear_object (&fence_data_pool); + g_clear_pointer (&input_state, gst_video_codec_state_unref); + } + + GstD3D12EncoderConfig config = { }; + D3D12_VIDEO_ENCODER_DESC encoder_desc = { }; + D3D12_VIDEO_ENCODER_HEAP_DESC heap_desc = { }; + D3D12_FEATURE_DATA_VIDEO_ENCODER_RESOURCE_REQUIREMENTS resource_req = { }; + + GstVideoCodecState *input_state = nullptr; + + std::vector subregions; + + std::unique_ptr cmd; + std::unique_ptr session; + GThread *output_thread = nullptr; + GstD3D12FenceDataPool *fence_data_pool; + std::atomic last_flow = { GST_FLOW_OK }; + std::atomic flushing = { false }; + bool array_of_textures = false; + D3D12_FEATURE_DATA_FORMAT_INFO format_info = { }; +}; +/* *INDENT-ON* */ + +static void gst_d3d12_encoder_finalize (GObject * object); +static void gst_d3d12_encoder_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_d3d12_encoder_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static void gst_d3d12_encoder_set_context (GstElement * element, + GstContext * context); +static gboolean gst_d3d12_encoder_open (GstVideoEncoder * encoder); +static gboolean gst_d3d12_encoder_start (GstVideoEncoder * encoder); +static gboolean gst_d3d12_encoder_stop (GstVideoEncoder * encoder); +static gboolean gst_d3d12_encoder_close (GstVideoEncoder * encoder); +static gboolean gst_d3d12_encoder_sink_query (GstVideoEncoder * encoder, + GstQuery * query); +static gboolean gst_d3d12_encoder_src_query (GstVideoEncoder * encoder, + GstQuery * query); +static gboolean gst_d3d12_encoder_propose_allocation (GstVideoEncoder * encoder, + GstQuery * query); +static gboolean gst_d3d12_encoder_set_format (GstVideoEncoder * encoder, + GstVideoCodecState * state); +static GstFlowReturn gst_d3d12_encoder_handle_frame (GstVideoEncoder * encoder, + GstVideoCodecFrame * frame); +static GstFlowReturn gst_d3d12_encoder_finish (GstVideoEncoder * encoder); +static gboolean gst_d3d12_encoder_flush (GstVideoEncoder * encoder); + +#define gst_d3d12_encoder_parent_class parent_class +G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstD3D12Encoder, + gst_d3d12_encoder, GST_TYPE_VIDEO_ENCODER, + GST_DEBUG_CATEGORY_INIT (gst_d3d12_encoder_debug, + "d3d12encoder", 0, "d3d12encoder")); + +static void +gst_d3d12_encoder_class_init (GstD3D12EncoderClass * klass) +{ + auto object_class = G_OBJECT_CLASS (klass); + auto element_class = GST_ELEMENT_CLASS (klass); + auto encoder_class = GST_VIDEO_ENCODER_CLASS (klass); + auto read_only_params = (GParamFlags) (GST_PARAM_DOC_SHOW_DEFAULT | + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + object_class->finalize = gst_d3d12_encoder_finalize; + object_class->get_property = gst_d3d12_encoder_get_property; + + g_object_class_install_property (object_class, PROP_ADAPTER_LUID, + g_param_spec_int64 ("adapter-luid", "Adapter LUID", + "DXGI Adapter LUID (Locally Unique Identifier) of created device", + G_MININT64, G_MAXINT64, 0, read_only_params)); + g_object_class_install_property (object_class, PROP_DEVICE_ID, + g_param_spec_uint ("device-id", "Device Id", + "DXGI Device ID", 0, G_MAXUINT32, 0, read_only_params)); + g_object_class_install_property (object_class, PROP_VENDOR_ID, + g_param_spec_uint ("vendor-id", "Vendor Id", + "DXGI Vendor ID", 0, G_MAXUINT32, 0, read_only_params)); + + element_class->set_context = + GST_DEBUG_FUNCPTR (gst_d3d12_encoder_set_context); + + encoder_class->open = GST_DEBUG_FUNCPTR (gst_d3d12_encoder_open); + encoder_class->start = GST_DEBUG_FUNCPTR (gst_d3d12_encoder_start); + encoder_class->stop = GST_DEBUG_FUNCPTR (gst_d3d12_encoder_stop); + encoder_class->close = GST_DEBUG_FUNCPTR (gst_d3d12_encoder_close); + encoder_class->sink_query = GST_DEBUG_FUNCPTR (gst_d3d12_encoder_sink_query); + encoder_class->src_query = GST_DEBUG_FUNCPTR (gst_d3d12_encoder_src_query); + encoder_class->propose_allocation = + GST_DEBUG_FUNCPTR (gst_d3d12_encoder_propose_allocation); + encoder_class->set_format = GST_DEBUG_FUNCPTR (gst_d3d12_encoder_set_format); + encoder_class->handle_frame = + GST_DEBUG_FUNCPTR (gst_d3d12_encoder_handle_frame); + encoder_class->finish = GST_DEBUG_FUNCPTR (gst_d3d12_encoder_finish); + encoder_class->flush = GST_DEBUG_FUNCPTR (gst_d3d12_encoder_flush); + + gst_type_mark_as_plugin_api (GST_TYPE_D3D12_ENCODER, (GstPluginAPIFlags) 0); + gst_type_mark_as_plugin_api (GST_TYPE_D3D12_ENCODER_RATE_CONTROL, + (GstPluginAPIFlags) 0); + gst_type_mark_as_plugin_api (GST_TYPE_D3D12_ENCODER_RATE_CONTROL_SUPPORT, + (GstPluginAPIFlags) 0); + gst_type_mark_as_plugin_api (GST_TYPE_D3D12_ENCODER_SUBREGION_LAYOUT, + (GstPluginAPIFlags) 0); + gst_type_mark_as_plugin_api (GST_TYPE_D3D12_ENCODER_SUBREGION_LAYOUT_SUPPORT, + (GstPluginAPIFlags) 0); + gst_type_mark_as_plugin_api (GST_TYPE_D3D12_ENCODER_SEI_INSERT_MODE, + (GstPluginAPIFlags) 0); +} + +static void +gst_d3d12_encoder_init (GstD3D12Encoder * self) +{ + self->priv = new GstD3D12EncoderPrivate (); +} + +static void +gst_d3d12_encoder_finalize (GObject * object) +{ + auto self = GST_D3D12_ENCODER (object); + + delete self->priv; + gst_clear_object (&self->device); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_d3d12_encoder_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + auto self = GST_D3D12_ENCODER (object); + auto klass = GST_D3D12_ENCODER_GET_CLASS (self); + + switch (prop_id) { + case PROP_ADAPTER_LUID: + g_value_set_int64 (value, klass->adapter_luid); + break; + case PROP_DEVICE_ID: + g_value_set_uint (value, klass->device_id); + break; + case PROP_VENDOR_ID: + g_value_set_uint (value, klass->vendor_id); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_d3d12_encoder_set_context (GstElement * element, GstContext * context) +{ + auto self = GST_D3D12_ENCODER (element); + auto klass = GST_D3D12_ENCODER_GET_CLASS (self); + + gst_d3d12_handle_set_context_for_adapter_luid (element, context, + klass->adapter_luid, &self->device); + + GST_ELEMENT_CLASS (parent_class)->set_context (element, context); +} + +static gboolean +gst_d3d12_encoder_open (GstVideoEncoder * encoder) +{ + auto self = GST_D3D12_ENCODER (encoder); + auto priv = self->priv; + auto klass = GST_D3D12_ENCODER_GET_CLASS (self); + HRESULT hr; + ComPtr < ID3D12VideoDevice3 > video_device; + + if (!gst_d3d12_ensure_element_data_for_adapter_luid (GST_ELEMENT_CAST (self), + klass->adapter_luid, &self->device)) { + GST_ERROR_OBJECT (self, "Couldn't get device"); + return FALSE; + } + + auto device = gst_d3d12_device_get_device_handle (self->device); + hr = device->QueryInterface (IID_PPV_ARGS (&video_device)); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "ID3D12VideoDevice3 interface is unavailable"); + return FALSE; + } + + D3D12_COMMAND_QUEUE_DESC queue_desc = { }; + queue_desc.Type = D3D12_COMMAND_LIST_TYPE_VIDEO_ENCODE; + + auto cmd = std::make_unique < EncoderCmdData > (); + cmd->queue = gst_d3d12_command_queue_new (self->device, + &queue_desc, ASYNC_DEPTH); + if (!cmd->queue) { + GST_ERROR_OBJECT (self, "Couldn't create command queue"); + return FALSE; + } + + cmd->ca_pool = gst_d3d12_command_allocator_pool_new (self->device, + D3D12_COMMAND_LIST_TYPE_VIDEO_ENCODE); + cmd->video_device = video_device; + + priv->cmd = std::move (cmd); + + return TRUE; +} + +static gboolean +gst_d3d12_encoder_start (GstVideoEncoder * encoder) +{ + auto self = GST_D3D12_ENCODER (encoder); + auto priv = self->priv; + + GST_DEBUG_OBJECT (self, "Start"); + + if (!priv->cmd) { + GST_ERROR_OBJECT (self, "Command data is not configured"); + return FALSE; + } + + priv->last_flow = GST_FLOW_OK; + + return TRUE; +} + +static void +gst_d3d12_encoder_drain (GstD3D12Encoder * self, gboolean locked) +{ + auto priv = self->priv; + + GST_DEBUG_OBJECT (self, "Draining"); + + /* Unlock stream-lock so that output thread can push pending data + * from output thread */ + if (locked) + GST_VIDEO_ENCODER_STREAM_UNLOCK (self); + + if (priv->cmd) { + GST_DEBUG_OBJECT (self, "Waiting for command finish %" G_GUINT64_FORMAT, + priv->cmd->fence_val); + gst_d3d12_command_queue_fence_wait (priv->cmd->queue, priv->cmd->fence_val, + priv->cmd->event_handle); + } + + if (priv->session && priv->output_thread) { + GST_DEBUG_OBJECT (self, "Sending empty task"); + auto empty_data = EncoderOutputData (); + std::lock_guard < std::mutex > lk (priv->session->queue_lock); + gst_queue_array_push_tail_struct (priv->session->output_queue, &empty_data); + priv->session->queue_cond.notify_one (); + } + + g_clear_pointer (&priv->output_thread, g_thread_join); + + if (locked) + GST_VIDEO_ENCODER_STREAM_LOCK (self); + + GST_DEBUG_OBJECT (self, "Drained"); +} + +static gboolean +gst_d3d12_encoder_stop (GstVideoEncoder * encoder) +{ + auto self = GST_D3D12_ENCODER (encoder); + auto priv = self->priv; + + GST_DEBUG_OBJECT (self, "Stop"); + + gst_d3d12_encoder_drain (self, FALSE); + priv->session = nullptr; + + return TRUE; +} + +static gboolean +gst_d3d12_encoder_close (GstVideoEncoder * encoder) +{ + auto self = GST_D3D12_ENCODER (encoder); + auto priv = self->priv; + + GST_DEBUG_OBJECT (self, "Close"); + + gst_d3d12_encoder_drain (self, FALSE); + priv->session = nullptr; + priv->cmd = nullptr; + + gst_clear_object (&self->device); + + return TRUE; +} + +static gboolean +gst_d3d12_encoder_handle_query (GstD3D12Encoder * self, GstQuery * query) +{ + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_CONTEXT: + return gst_d3d12_handle_context_query (GST_ELEMENT (self), + query, self->device); + default: + break; + } + + return FALSE; +} + +static gboolean +gst_d3d12_encoder_sink_query (GstVideoEncoder * encoder, GstQuery * query) +{ + auto self = GST_D3D12_ENCODER (encoder); + + if (gst_d3d12_encoder_handle_query (self, query)) + return TRUE; + + return GST_VIDEO_ENCODER_CLASS (parent_class)->sink_query (encoder, query); +} + +static gboolean +gst_d3d12_encoder_src_query (GstVideoEncoder * encoder, GstQuery * query) +{ + auto self = GST_D3D12_ENCODER (encoder); + + if (gst_d3d12_encoder_handle_query (self, query)) + return TRUE; + + return GST_VIDEO_ENCODER_CLASS (parent_class)->src_query (encoder, query); +} + +static GstBufferPool * +gst_d3d12_encoder_create_upload_pool (GstD3D12Encoder * self) +{ + auto priv = self->priv; + GstVideoInfo info; + + gst_video_info_set_format (&info, GST_VIDEO_FORMAT_NV12, + priv->config.resolution.Width, priv->config.resolution.Height); + auto caps = gst_video_info_to_caps (&info); + auto pool = gst_d3d12_buffer_pool_new (self->device); + auto config = gst_buffer_pool_get_config (pool); + + auto params = gst_d3d12_allocation_params_new (self->device, &info, + GST_D3D12_ALLOCATION_FLAG_DEFAULT, + D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS | + D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET); + gst_buffer_pool_config_set_d3d12_allocation_params (config, params); + gst_d3d12_allocation_params_free (params); + gst_buffer_pool_config_set_params (config, caps, info.size, 0, 0); + gst_caps_unref (caps); + + if (!gst_buffer_pool_set_config (pool, config)) { + GST_ERROR_OBJECT (self, "Set config failed"); + return nullptr; + } + + if (!gst_buffer_pool_set_active (pool, TRUE)) { + GST_ERROR_OBJECT (self, "Set active failed"); + return nullptr; + } + + return pool; +} + +static gboolean +gst_d3d12_encoder_propose_allocation (GstVideoEncoder * encoder, + GstQuery * query) +{ + auto self = GST_D3D12_ENCODER (encoder); + auto priv = self->priv; + GstCaps *caps; + GstVideoInfo info; + + gst_query_parse_allocation (query, &caps, nullptr); + if (!caps) { + GST_WARNING_OBJECT (self, "null caps in query"); + return FALSE; + } + + if (!gst_video_info_from_caps (&info, caps)) { + GST_WARNING_OBJECT (self, "Failed to convert caps into info"); + return FALSE; + } + + auto features = gst_caps_get_features (caps, 0); + gboolean is_d3d12 = FALSE; + GstBufferPool *pool; + if (gst_caps_features_contains (features, + GST_CAPS_FEATURE_MEMORY_D3D12_MEMORY)) { + GST_DEBUG_OBJECT (self, "Upstream supports d3d12 memory"); + pool = gst_d3d12_buffer_pool_new (self->device); + is_d3d12 = TRUE; + } else { + pool = gst_video_buffer_pool_new (); + } + + if (!pool) { + GST_ERROR_OBJECT (self, "Couldn't create buffer pool"); + return FALSE; + } + + auto config = gst_buffer_pool_get_config (pool); + gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META); + + if (is_d3d12) { + GstVideoAlignment align; + + gst_video_alignment_reset (&align); + align.padding_right = priv->config.resolution.Width - info.width; + align.padding_bottom = priv->config.resolution.Height - info.height; + + auto params = gst_d3d12_allocation_params_new (self->device, &info, + GST_D3D12_ALLOCATION_FLAG_DEFAULT, + D3D12_RESOURCE_FLAG_ALLOW_SIMULTANEOUS_ACCESS | + D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET); + gst_d3d12_allocation_params_alignment (params, &align); + gst_buffer_pool_config_set_d3d12_allocation_params (config, params); + gst_d3d12_allocation_params_free (params); + } else { + gst_buffer_pool_config_add_option (config, + GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT); + } + + guint size = GST_VIDEO_INFO_SIZE (&info); + gst_buffer_pool_config_set_params (config, caps, size, ASYNC_DEPTH, 0); + + if (!gst_buffer_pool_set_config (pool, config)) { + GST_ERROR_OBJECT (self, "Failed to set pool config"); + gst_object_unref (pool); + return FALSE; + } + + config = gst_buffer_pool_get_config (pool); + gst_buffer_pool_config_get_params (config, nullptr, &size, nullptr, nullptr); + gst_structure_free (config); + + gst_query_add_allocation_pool (query, pool, size, ASYNC_DEPTH, 0); + gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, nullptr); + gst_object_unref (pool); + + return TRUE; +} + +static gboolean +gst_d3d12_encoder_set_format (GstVideoEncoder * encoder, + GstVideoCodecState * state) +{ + auto self = GST_D3D12_ENCODER (encoder); + auto priv = self->priv; + auto klass = GST_D3D12_ENCODER_GET_CLASS (self); + + gst_d3d12_encoder_drain (self, TRUE); + priv->session = nullptr; + + g_clear_pointer (&priv->input_state, gst_video_codec_state_unref); + priv->input_state = gst_video_codec_state_ref (state); + + priv->last_flow = GST_FLOW_OK; + priv->config = { }; + + GST_DEBUG_OBJECT (self, "Set format with caps %" GST_PTR_FORMAT, state->caps); + + if (!klass->new_sequence (self, priv->cmd->video_device.Get (), state, + &priv->config)) { + GST_ERROR_OBJECT (self, "Couldn't accept new sequence"); + return FALSE; + } + + g_assert (priv->config.max_subregions > 0); + + auto & config = priv->config; + auto flags = config.support_flags; + + GST_DEBUG_OBJECT (self, "Encoder caps, " + "rate-control-reconfig: %d, resolution-reconfig: %d, " + "vbv-size: %d, frame-analysis: %d, texture-arrays: %d, delta-qp: %d, " + "subregion-reconfig: %d, qp-range: %d, initial-qp: %d, " + "max-frame-size: %d, gop-reconfigure: %d, me-precision-limit: %d", + CHECK_SUPPORT_FLAG (flags, RATE_CONTROL_RECONFIGURATION_AVAILABLE), + CHECK_SUPPORT_FLAG (flags, RESOLUTION_RECONFIGURATION_AVAILABLE), + CHECK_SUPPORT_FLAG (flags, RATE_CONTROL_VBV_SIZE_CONFIG_AVAILABLE), + CHECK_SUPPORT_FLAG (flags, RATE_CONTROL_FRAME_ANALYSIS_AVAILABLE), + CHECK_SUPPORT_FLAG (flags, RECONSTRUCTED_FRAMES_REQUIRE_TEXTURE_ARRAYS), + CHECK_SUPPORT_FLAG (flags, RATE_CONTROL_DELTA_QP_AVAILABLE), + CHECK_SUPPORT_FLAG (flags, SUBREGION_LAYOUT_RECONFIGURATION_AVAILABLE), + CHECK_SUPPORT_FLAG (flags, RATE_CONTROL_ADJUSTABLE_QP_RANGE_AVAILABLE), + CHECK_SUPPORT_FLAG (flags, RATE_CONTROL_INITIAL_QP_AVAILABLE), + CHECK_SUPPORT_FLAG (flags, RATE_CONTROL_MAX_FRAME_SIZE_AVAILABLE), + CHECK_SUPPORT_FLAG (flags, SEQUENCE_GOP_RECONFIGURATION_AVAILABLE), + CHECK_SUPPORT_FLAG (flags, + MOTION_ESTIMATION_PRECISION_MODE_LIMIT_AVAILABLE)); + + auto video_device = priv->cmd->video_device; + + auto & resource_req = priv->resource_req; + resource_req.Codec = klass->codec; + resource_req.Profile = config.profile_desc; + resource_req.InputFormat = DXGI_FORMAT_NV12; + resource_req.PictureTargetResolution = config.resolution; + auto hr = video_device->CheckFeatureSupport + (D3D12_FEATURE_VIDEO_ENCODER_RESOURCE_REQUIREMENTS, + &resource_req, sizeof (resource_req)); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't query resource requirement"); + return FALSE; + } + + if (CHECK_SUPPORT_FLAG (flags, RECONSTRUCTED_FRAMES_REQUIRE_TEXTURE_ARRAYS)) { + GST_DEBUG_OBJECT (self, "Device requires texture array"); + priv->array_of_textures = false; + } else { + GST_DEBUG_OBJECT (self, "Device supports array of textures"); + priv->array_of_textures = true; + } + + auto device = gst_d3d12_device_get_device_handle (self->device); + priv->format_info.Format = DXGI_FORMAT_NV12; + hr = device->CheckFeatureSupport (D3D12_FEATURE_FORMAT_INFO, + &priv->format_info, sizeof (priv->format_info)); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't query format info"); + return FALSE; + } + + auto session = std::make_unique < EncoderSessionData > (); + + auto & desc = priv->encoder_desc; + desc = { }; + desc.EncodeCodec = klass->codec; + desc.EncodeProfile = config.profile_desc; + desc.InputFormat = priv->format_info.Format; + desc.CodecConfiguration = config.codec_config; + desc.MaxMotionEstimationPrecision = + D3D12_VIDEO_ENCODER_MOTION_ESTIMATION_PRECISION_MODE_MAXIMUM; + + hr = video_device->CreateVideoEncoder (&desc, + IID_PPV_ARGS (&session->encoder)); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't create encoder"); + return FALSE; + } + + auto & heap_desc = priv->heap_desc; + heap_desc = { }; + heap_desc.EncodeCodec = klass->codec; + heap_desc.EncodeProfile = config.profile_desc; + heap_desc.EncodeLevel = config.level; + heap_desc.ResolutionsListCount = 1; + heap_desc.pResolutionList = &config.resolution; + hr = video_device->CreateVideoEncoderHeap (&heap_desc, + IID_PPV_ARGS (&session->heap)); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't create encoder heap"); + return FALSE; + } + + guint resolved_metadata_size; + resolved_metadata_size = sizeof (D3D12_VIDEO_ENCODER_OUTPUT_METADATA) + + sizeof (D3D12_VIDEO_ENCODER_FRAME_SUBREGION_METADATA) * + config.max_subregions; + if (resource_req.EncoderMetadataBufferAccessAlignment > 1) { + resolved_metadata_size = + ((resolved_metadata_size + + resource_req.EncoderMetadataBufferAccessAlignment - 1) / + resource_req.EncoderMetadataBufferAccessAlignment) * + resource_req.EncoderMetadataBufferAccessAlignment; + } + + /* TODO: calculate bitstream buffer size */ + guint bitstream_size = 1024 * 1024 * 8; + session->encoder_pool = gst_d3d12_encoder_buffer_pool_new (self->device, + resource_req.MaxEncoderOutputMetadataBufferSize, + resolved_metadata_size, bitstream_size, ASYNC_DEPTH); + + session->upload_pool = gst_d3d12_encoder_create_upload_pool (self); + if (!session->upload_pool) + return FALSE; + + priv->session = std::move (session); + + return TRUE; +} + +static GstBuffer * +gst_d3d12_encoder_upload_frame (GstD3D12Encoder * self, GstBuffer * buffer) +{ + auto priv = self->priv; + const auto info = &priv->input_state->info; + GstBuffer *upload = nullptr; + gboolean d3d12_copy = FALSE; + + auto mem = gst_buffer_peek_memory (buffer, 0); + if (gst_is_d3d12_memory (mem)) { + auto dmem = GST_D3D12_MEMORY_CAST (mem); + if (dmem->device == self->device) { + GstMapInfo map_info; + if (!gst_memory_map (mem, &map_info, + (GstMapFlags) (GST_MAP_READ | GST_MAP_D3D12))) { + GST_ERROR_OBJECT (self, "Couldn't map memory"); + return nullptr; + } + + gst_memory_unmap (mem, &map_info); + + auto resource = gst_d3d12_memory_get_resource_handle (dmem); + auto desc = resource->GetDesc (); + if (desc.Width >= (UINT64) priv->config.resolution.Width && + desc.Height >= priv->config.resolution.Height) { + return gst_buffer_ref (buffer); + } + + d3d12_copy = TRUE; + } + } + + gst_buffer_pool_acquire_buffer (priv->session->upload_pool, &upload, nullptr); + if (!upload) { + GST_ERROR_OBJECT (self, "Couldn't acquire buffer"); + return nullptr; + } + + if (d3d12_copy) { + std::vector < GstD3D12CopyTextureRegionArgs > copy_args; + auto dmem = GST_D3D12_MEMORY_CAST (mem); + auto src_resource = gst_d3d12_memory_get_resource_handle (dmem); + dmem = (GstD3D12Memory *) gst_buffer_peek_memory (upload, 0); + auto dst_resource = gst_d3d12_memory_get_resource_handle (dmem); + D3D12_BOX src_box[2]; + + auto desc = src_resource->GetDesc (); + + UINT width = MIN ((UINT) desc.Width, priv->config.resolution.Width); + UINT height = MIN ((UINT) desc.Height, priv->config.resolution.Height); + + for (guint i = 0; i < 2; i++) { + GstD3D12CopyTextureRegionArgs args; + memset (&args, 0, sizeof (args)); + + args.src = CD3DX12_TEXTURE_COPY_LOCATION (src_resource, i); + args.dst = CD3DX12_TEXTURE_COPY_LOCATION (dst_resource, i); + + src_box[i].left = 0; + src_box[i].top = 0; + src_box[i].front = 0; + src_box[i].back = 1; + + if (i == 0) { + src_box[i].right = width; + src_box[i].bottom = height; + } else { + src_box[i].right = width / 2; + src_box[i].bottom = height / 2; + } + + args.src_box = &src_box[i]; + copy_args.push_back (args); + } + + guint64 fence_val = 0; + gst_d3d12_device_copy_texture_region (self->device, copy_args.size (), + copy_args.data (), D3D12_COMMAND_LIST_TYPE_DIRECT, &fence_val); + gst_d3d12_buffer_after_write (upload, fence_val); + } else { + GstVideoFrame src_frame, dst_frame; + if (!gst_video_frame_map (&src_frame, info, buffer, GST_MAP_READ)) { + GST_ERROR_OBJECT (self, "Couldn't map frame"); + gst_buffer_unref (upload); + return nullptr; + } + + if (!gst_video_frame_map (&dst_frame, info, upload, GST_MAP_WRITE)) { + GST_ERROR_OBJECT (self, "Couldn't map frame"); + gst_video_frame_unmap (&src_frame); + gst_buffer_unref (upload); + return nullptr; + } + + for (guint i = 0; i < GST_VIDEO_FRAME_N_PLANES (&src_frame); i++) { + auto src_width_in_bytes = GST_VIDEO_FRAME_COMP_WIDTH (&src_frame, i) * + GST_VIDEO_FRAME_COMP_PSTRIDE (&src_frame, i); + auto src_height = GST_VIDEO_FRAME_COMP_HEIGHT (&src_frame, i); + auto src_stride = GST_VIDEO_FRAME_PLANE_STRIDE (&src_frame, i); + + auto dst_width_in_bytes = GST_VIDEO_FRAME_COMP_WIDTH (&dst_frame, i) * + GST_VIDEO_FRAME_COMP_PSTRIDE (&dst_frame, i); + auto dst_height = GST_VIDEO_FRAME_COMP_HEIGHT (&dst_frame, i); + auto dst_stride = GST_VIDEO_FRAME_PLANE_STRIDE (&dst_frame, i); + + auto width_in_bytes = MIN (src_width_in_bytes, dst_width_in_bytes); + auto height = MIN (src_height, dst_height); + auto src_data = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (&src_frame, i); + auto dst_data = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (&dst_frame, i); + + for (guint j = 0; j < height; j++) { + memcpy (dst_data, src_data, width_in_bytes); + dst_data += dst_stride; + src_data += src_stride; + } + } + + gst_video_frame_unmap (&dst_frame); + gst_video_frame_unmap (&src_frame); + + GstMapInfo map_info; + mem = gst_buffer_peek_memory (upload, 0); + if (!gst_memory_map (mem, &map_info, + (GstMapFlags) (GST_MAP_READ | GST_MAP_D3D12))) { + GST_ERROR_OBJECT (self, "Couldn't map memory"); + gst_buffer_unref (upload); + return nullptr; + } + + gst_memory_unmap (mem, &map_info); + } + + return upload; +} + +static void +gst_d3d12_encoder_build_command (GstD3D12Encoder * self, + const D3D12_VIDEO_ENCODER_ENCODEFRAME_INPUT_ARGUMENTS * in_args, + const D3D12_VIDEO_ENCODER_ENCODEFRAME_OUTPUT_ARGUMENTS * out_args, + const D3D12_VIDEO_ENCODER_RESOLVE_METADATA_INPUT_ARGUMENTS * meta_in_args, + const D3D12_VIDEO_ENCODER_RESOLVE_METADATA_OUTPUT_ARGUMENTS * meta_out_args, + GstD3D12FenceData * fence_data, + ID3D12VideoEncodeCommandList2 * command_list) +{ + auto priv = self->priv; + const auto & format_info = priv->format_info; + auto array_of_textures = priv->array_of_textures; + + std::vector < D3D12_RESOURCE_BARRIER > pre_enc_barrier; + std::vector < D3D12_RESOURCE_BARRIER > post_enc_barrier; + + pre_enc_barrier. + push_back (CD3DX12_RESOURCE_BARRIER::Transition (in_args->pInputFrame, + D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_VIDEO_ENCODE_READ)); + post_enc_barrier. + push_back (CD3DX12_RESOURCE_BARRIER::Transition (in_args->pInputFrame, + D3D12_RESOURCE_STATE_VIDEO_ENCODE_READ, D3D12_RESOURCE_STATE_COMMON)); + + /* Reference picture barriers */ + if (in_args->PictureControlDesc.ReferenceFrames.NumTexture2Ds > 0) { + if (array_of_textures) { + for (UINT i = 0; + i < in_args->PictureControlDesc.ReferenceFrames.NumTexture2Ds; i++) { + auto ref_pic = + in_args->PictureControlDesc.ReferenceFrames.ppTexture2Ds[i]; + ref_pic->AddRef (); + gst_d3d12_fence_data_add_notify_com (fence_data, ref_pic); + pre_enc_barrier.push_back (CD3DX12_RESOURCE_BARRIER:: + Transition (ref_pic, D3D12_RESOURCE_STATE_COMMON, + D3D12_RESOURCE_STATE_VIDEO_ENCODE_READ)); + post_enc_barrier.push_back (CD3DX12_RESOURCE_BARRIER:: + Transition (ref_pic, D3D12_RESOURCE_STATE_VIDEO_ENCODE_READ, + D3D12_RESOURCE_STATE_COMMON)); + } + } else { + auto ref_pic = + in_args->PictureControlDesc.ReferenceFrames.ppTexture2Ds[0]; + ref_pic->AddRef (); + gst_d3d12_fence_data_add_notify_com (fence_data, ref_pic); + auto ref_pic_desc = ref_pic->GetDesc (); + + for (UINT i = 0; + i < in_args->PictureControlDesc.ReferenceFrames.NumTexture2Ds; i++) { + UINT mip_slice, plane_slice, array_slice; + D3D12DecomposeSubresource (in_args->PictureControlDesc. + ReferenceFrames.pSubresources[i], ref_pic_desc.MipLevels, + ref_pic_desc.DepthOrArraySize, mip_slice, array_slice, plane_slice); + + for (UINT plane = 0; plane < format_info.PlaneCount; plane++) { + UINT subresource = D3D12CalcSubresource (mip_slice, array_slice, + plane, ref_pic_desc.MipLevels, + ref_pic_desc.DepthOrArraySize); + + pre_enc_barrier.push_back (CD3DX12_RESOURCE_BARRIER:: + Transition (ref_pic, D3D12_RESOURCE_STATE_COMMON, + D3D12_RESOURCE_STATE_VIDEO_ENCODE_READ, subresource)); + post_enc_barrier.push_back (CD3DX12_RESOURCE_BARRIER:: + Transition (ref_pic, D3D12_RESOURCE_STATE_VIDEO_ENCODE_READ, + D3D12_RESOURCE_STATE_COMMON, subresource)); + } + } + } + } + + /* Reconstructed picture barries */ + if (out_args->ReconstructedPicture.pReconstructedPicture) { + out_args->ReconstructedPicture.pReconstructedPicture->AddRef (); + gst_d3d12_fence_data_add_notify_com (fence_data, + out_args->ReconstructedPicture.pReconstructedPicture); + + if (array_of_textures) { + pre_enc_barrier.push_back (CD3DX12_RESOURCE_BARRIER:: + Transition (out_args->ReconstructedPicture.pReconstructedPicture, + D3D12_RESOURCE_STATE_COMMON, + D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE)); + + post_enc_barrier.push_back (CD3DX12_RESOURCE_BARRIER:: + Transition (out_args->ReconstructedPicture.pReconstructedPicture, + D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE, + D3D12_RESOURCE_STATE_COMMON)); + } else { + auto recon_pic_desc = + out_args->ReconstructedPicture.pReconstructedPicture->GetDesc (); + UINT mip_slice, plane_slice, array_slice; + + D3D12DecomposeSubresource (out_args-> + ReconstructedPicture.ReconstructedPictureSubresource, + recon_pic_desc.MipLevels, recon_pic_desc.DepthOrArraySize, mip_slice, + array_slice, plane_slice); + + for (UINT plane = 0; plane < format_info.PlaneCount; plane++) { + UINT subresource = D3D12CalcSubresource (mip_slice, array_slice, + plane, recon_pic_desc.MipLevels, + recon_pic_desc.DepthOrArraySize); + + pre_enc_barrier.push_back (CD3DX12_RESOURCE_BARRIER:: + Transition (out_args->ReconstructedPicture.pReconstructedPicture, + D3D12_RESOURCE_STATE_COMMON, + D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE, subresource)); + + post_enc_barrier.push_back (CD3DX12_RESOURCE_BARRIER:: + Transition (out_args->ReconstructedPicture.pReconstructedPicture, + D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE, + D3D12_RESOURCE_STATE_COMMON, subresource)); + } + } + } + + pre_enc_barrier. + push_back (CD3DX12_RESOURCE_BARRIER::Transition (out_args->Bitstream. + pBuffer, D3D12_RESOURCE_STATE_COMMON, + D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE)); + pre_enc_barrier. + push_back (CD3DX12_RESOURCE_BARRIER:: + Transition (out_args->EncoderOutputMetadata.pBuffer, + D3D12_RESOURCE_STATE_COMMON, + D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE)); + + command_list->ResourceBarrier (pre_enc_barrier.size (), + pre_enc_barrier.data ()); + + auto encoder = priv->session->encoder; + auto heap = priv->session->heap; + + command_list->EncodeFrame (encoder.Get (), heap.Get (), in_args, out_args); + gst_d3d12_fence_data_add_notify_com (fence_data, encoder.Detach ()); + gst_d3d12_fence_data_add_notify_com (fence_data, heap.Detach ()); + + post_enc_barrier. + push_back (CD3DX12_RESOURCE_BARRIER::Transition (out_args->Bitstream. + pBuffer, D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE, + D3D12_RESOURCE_STATE_COMMON)); + post_enc_barrier. + push_back (CD3DX12_RESOURCE_BARRIER:: + Transition (out_args->EncoderOutputMetadata.pBuffer, + D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE, + D3D12_RESOURCE_STATE_VIDEO_ENCODE_READ)); + post_enc_barrier.push_back (CD3DX12_RESOURCE_BARRIER:: + Transition (meta_out_args->ResolvedLayoutMetadata.pBuffer, + D3D12_RESOURCE_STATE_COMMON, + D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE)); + + command_list->ResourceBarrier (post_enc_barrier.size (), + post_enc_barrier.data ()); + + command_list->ResolveEncoderOutputMetadata (meta_in_args, meta_out_args); + + std::vector < D3D12_RESOURCE_BARRIER > post_resolve_barrier; + post_resolve_barrier.push_back (CD3DX12_RESOURCE_BARRIER:: + Transition (out_args->EncoderOutputMetadata.pBuffer, + D3D12_RESOURCE_STATE_VIDEO_ENCODE_READ, D3D12_RESOURCE_STATE_COMMON)); + post_resolve_barrier.push_back (CD3DX12_RESOURCE_BARRIER:: + Transition (meta_out_args->ResolvedLayoutMetadata.pBuffer, + D3D12_RESOURCE_STATE_VIDEO_ENCODE_WRITE, + D3D12_RESOURCE_STATE_COMMON)); + + command_list->ResourceBarrier (post_resolve_barrier.size (), + post_resolve_barrier.data ()); +} + +static gboolean +gst_d3d12_encoder_resolve_bitstream (GstD3D12Encoder * self, + ID3D12Resource * resolved_metadata, ID3D12Resource * bitstream, + GstBuffer * output) +{ + auto priv = self->priv; + guint8 *map_data; + HRESULT hr; + + g_assert (output); + + CD3DX12_RANGE zero_range (0, 0); + + hr = resolved_metadata->Map (0, nullptr, (void **) &map_data); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't map metadata"); + return FALSE; + } + + D3D12_VIDEO_ENCODER_OUTPUT_METADATA output_meta = + *((D3D12_VIDEO_ENCODER_OUTPUT_METADATA *) map_data); + GST_TRACE_OBJECT (self, + "EncodeErrorFlags: %" G_GUINT64_FORMAT ", " + "EncodeStats.AverageQP: %" G_GUINT64_FORMAT ", " + "EncodeStats.IntraCodingUnitsCount: %" G_GUINT64_FORMAT ", " + "EncodeStats.InterCodingUnitsCount: %" G_GUINT64_FORMAT ", " + "EncodeStats.SkipCodingUnitsCount: %" G_GUINT64_FORMAT ", " + "EncodeStats.AverageMotionEstimationXDirection: %" G_GUINT64_FORMAT ", " + "EncodeStats.AverageMotionEstimationYDirection: %" G_GUINT64_FORMAT ", " + "EncodedBitstreamWrittenBytesCount: %" G_GUINT64_FORMAT ", " + "WrittenSubregionsCount: %" G_GUINT64_FORMAT, + output_meta.EncodeErrorFlags, + output_meta.EncodeStats.AverageQP, + output_meta.EncodeStats.IntraCodingUnitsCount, + output_meta.EncodeStats.InterCodingUnitsCount, + output_meta.EncodeStats.SkipCodingUnitsCount, + output_meta.EncodeStats.AverageMotionEstimationXDirection, + output_meta.EncodeStats.AverageMotionEstimationYDirection, + output_meta.EncodedBitstreamWrittenBytesCount, + output_meta.WrittenSubregionsCount); + + if (output_meta.WrittenSubregionsCount == 0 || + output_meta.EncodedBitstreamWrittenBytesCount == 0) { + GST_ERROR_OBJECT (self, "No written data"); + resolved_metadata->Unmap (0, &zero_range); + + return FALSE; + } + + if (output_meta.EncodeErrorFlags != + D3D12_VIDEO_ENCODER_ENCODE_ERROR_FLAG_NO_ERROR) { + if ((output_meta.EncodeErrorFlags & + D3D12_VIDEO_ENCODER_ENCODE_ERROR_FLAG_CODEC_PICTURE_CONTROL_NOT_SUPPORTED) + != 0) { + GST_ERROR_OBJECT (self, "Picture control not supported"); + } + + if ((output_meta.EncodeErrorFlags & + D3D12_VIDEO_ENCODER_ENCODE_ERROR_FLAG_SUBREGION_LAYOUT_CONFIGURATION_NOT_SUPPORTED) + != 0) { + GST_ERROR_OBJECT (self, "Subregion layout not supported"); + } + + if ((output_meta.EncodeErrorFlags & + D3D12_VIDEO_ENCODER_ENCODE_ERROR_FLAG_INVALID_REFERENCE_PICTURES) != + 0) { + GST_ERROR_OBJECT (self, "Invalid reference picture"); + } + + if ((output_meta.EncodeErrorFlags & + D3D12_VIDEO_ENCODER_ENCODE_ERROR_FLAG_RECONFIGURATION_REQUEST_NOT_SUPPORTED) + != 0) { + GST_ERROR_OBJECT (self, "Reconfigure not supported"); + } + + if ((output_meta.EncodeErrorFlags & + D3D12_VIDEO_ENCODER_ENCODE_ERROR_FLAG_INVALID_METADATA_BUFFER_SOURCE) + != 0) { + GST_ERROR_OBJECT (self, "Invalid metadata buffer source"); + } + resolved_metadata->Unmap (0, &zero_range); + + return FALSE; + } + + map_data += sizeof (D3D12_VIDEO_ENCODER_OUTPUT_METADATA); + priv->subregions.clear (); + UINT64 total_subregion_size = 0; + for (guint i = 0; i < output_meta.WrittenSubregionsCount; i++) { + auto subregion = + *((D3D12_VIDEO_ENCODER_FRAME_SUBREGION_METADATA *) map_data); + + GST_TRACE_OBJECT (self, "Subregion %d, bSize: %" G_GUINT64_FORMAT ", " + "bStartOffset: %" G_GUINT64_FORMAT ", bHeaderSize: %" G_GUINT64_FORMAT, + i, subregion.bSize, subregion.bStartOffset, subregion.bHeaderSize); + + subregion.bStartOffset += total_subregion_size; + + priv->subregions.push_back (subregion); + total_subregion_size += subregion.bSize; + map_data += sizeof (D3D12_VIDEO_ENCODER_FRAME_SUBREGION_METADATA); + } + + resolved_metadata->Unmap (0, &zero_range); + + hr = bitstream->Map (0, nullptr, (void **) &map_data); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't map bitstream"); + return FALSE; + } + + auto mem = gst_allocator_alloc (nullptr, total_subregion_size, nullptr); + GstMapInfo map_info; + + gst_memory_map (mem, &map_info, GST_MAP_WRITE); + auto data = (guint8 *) map_info.data; + for (size_t i = 0; i < priv->subregions.size (); i++) { + const auto & subregion = priv->subregions[i]; + memcpy (data, map_data + subregion.bStartOffset, subregion.bSize); + data += subregion.bSize; + } + gst_memory_unmap (mem, &map_info); + bitstream->Unmap (0, &zero_range); + + gst_buffer_append_memory (output, mem); + + return TRUE; +} + +static gpointer +gst_d3d12_encoder_output_loop (GstD3D12Encoder * self) +{ + auto priv = self->priv; + auto encoder = GST_VIDEO_ENCODER (self); + + GST_DEBUG_OBJECT (self, "Entering output thread"); + + HANDLE event_handle = CreateEventEx (nullptr, nullptr, 0, EVENT_ALL_ACCESS); + + while (true) { + EncoderOutputData output_data; + { + GST_LOG_OBJECT (self, "Waiting for output data"); + std::unique_lock < std::mutex > lk (priv->session->queue_lock); + while (gst_queue_array_is_empty (priv->session->output_queue)) + priv->session->queue_cond.wait (lk); + + output_data = *((EncoderOutputData *) + gst_queue_array_pop_head_struct (priv->session->output_queue)); + } + + if (!output_data.frame) { + GST_DEBUG_OBJECT (self, "Got terminate data"); + break; + } + + GST_LOG_OBJECT (self, "Processing output %" G_GUINT64_FORMAT, + output_data.fence_val); + + gst_d3d12_command_queue_fence_wait (priv->cmd->queue, output_data.fence_val, + event_handle); + + if (priv->flushing) { + GST_DEBUG_OBJECT (self, "We are flushing"); + gst_d3d12_encoder_buffer_unref (output_data.buffer); + gst_clear_buffer (&output_data.frame->output_buffer); + gst_video_encoder_finish_frame (encoder, output_data.frame); + } else if (priv->last_flow == GST_FLOW_OK) { + ComPtr < ID3D12Resource > resolved_metadata; + ComPtr < ID3D12Resource > bitstream; + + gst_d3d12_encoder_buffer_get_resolved_metadata (output_data.buffer, + &resolved_metadata); + gst_d3d12_encoder_buffer_get_bitstream (output_data.buffer, &bitstream); + + auto frame = output_data.frame; + if (!frame->output_buffer) + frame->output_buffer = gst_buffer_new (); + + auto resolve_ret = gst_d3d12_encoder_resolve_bitstream (self, + resolved_metadata.Get (), + bitstream.Get (), frame->output_buffer); + gst_d3d12_encoder_buffer_unref (output_data.buffer); + + if (!resolve_ret) { + GST_ERROR_OBJECT (self, "Couldn't resolve bitstream buffer"); + priv->last_flow = GST_FLOW_ERROR; + gst_clear_buffer (&frame->output_buffer); + gst_video_encoder_finish_frame (encoder, frame); + } else { + /* TODO: calculate dts in case of bframe is used */ + frame->dts = frame->pts; + + priv->last_flow = gst_video_encoder_finish_frame (encoder, frame); + if (priv->last_flow != GST_FLOW_OK) { + GST_WARNING_OBJECT (self, "Last flow was %s", + gst_flow_get_name (priv->last_flow)); + } + } + } else { + GST_DEBUG_OBJECT (self, "Dropping framem last flow return was %s", + gst_flow_get_name (priv->last_flow)); + gst_d3d12_encoder_buffer_unref (output_data.buffer); + gst_clear_buffer (&output_data.frame->output_buffer); + gst_video_encoder_finish_frame (encoder, output_data.frame); + } + } + + GST_DEBUG_OBJECT (self, "Leaving output thread"); + + CloseHandle (event_handle); + + return nullptr; +} + +static GstFlowReturn +gst_d3d12_encoder_handle_frame (GstVideoEncoder * encoder, + GstVideoCodecFrame * frame) +{ + auto self = GST_D3D12_ENCODER (encoder); + auto priv = self->priv; + auto & config = priv->config; + auto klass = GST_D3D12_ENCODER_GET_CLASS (self); + + if (!priv->session) { + GST_ERROR_OBJECT (self, "Encoding session is not configured"); + return GST_FLOW_ERROR; + } + + if (!priv->session->encoder || !priv->session->heap) { + GST_ERROR_OBJECT (self, "Previous reconfigure failed"); + return GST_FLOW_ERROR; + } + + if (priv->last_flow != GST_FLOW_OK) { + GST_WARNING_OBJECT (self, "Last flow was %s", + gst_flow_get_name (priv->last_flow)); + gst_video_encoder_finish_frame (encoder, frame); + return priv->last_flow; + } + + auto upload = gst_d3d12_encoder_upload_frame (self, frame->input_buffer); + if (!upload) { + gst_video_encoder_finish_frame (encoder, frame); + return GST_FLOW_ERROR; + } + + GstD3D12FenceData *fence_data; + gst_d3d12_fence_data_pool_acquire (priv->fence_data_pool, &fence_data); + gst_d3d12_fence_data_add_notify_mini_object (fence_data, upload); + + GstD3D12CommandAllocator *gst_ca; + if (!gst_d3d12_command_allocator_pool_acquire (priv->cmd->ca_pool, &gst_ca)) { + GST_ERROR_OBJECT (self, "Couldn't acquire command allocator"); + gst_d3d12_fence_data_unref (fence_data); + gst_video_encoder_finish_frame (encoder, frame); + return GST_FLOW_ERROR; + } + + gst_d3d12_fence_data_add_notify_mini_object (fence_data, gst_ca); + + ComPtr < ID3D12CommandAllocator > ca; + gst_d3d12_command_allocator_get_handle (gst_ca, &ca); + auto hr = ca->Reset (); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't reset command allocator"); + gst_d3d12_fence_data_unref (fence_data); + gst_video_encoder_finish_frame (encoder, frame); + return GST_FLOW_ERROR; + } + + if (!priv->cmd->cl) { + auto device = gst_d3d12_device_get_device_handle (self->device); + hr = device->CreateCommandList (0, D3D12_COMMAND_LIST_TYPE_VIDEO_ENCODE, + ca.Get (), nullptr, IID_PPV_ARGS (&priv->cmd->cl)); + } else { + hr = priv->cmd->cl->Reset (ca.Get ()); + } + + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't reset command list"); + gst_d3d12_fence_data_unref (fence_data); + gst_video_encoder_finish_frame (encoder, frame); + return GST_FLOW_ERROR; + } + + auto video_device = priv->cmd->video_device.Get (); + D3D12_VIDEO_ENCODER_ENCODEFRAME_INPUT_ARGUMENTS in_args = { }; + D3D12_VIDEO_ENCODER_ENCODEFRAME_OUTPUT_ARGUMENTS out_args = { }; + D3D12_VIDEO_ENCODER_RESOLVE_METADATA_INPUT_ARGUMENTS meta_in_args = { }; + D3D12_VIDEO_ENCODER_RESOLVE_METADATA_OUTPUT_ARGUMENTS meta_out_args = { }; + auto prev_max_subregions = config.max_subregions; + gboolean need_new_session = FALSE; + if (!klass->start_frame (self, video_device, frame, + &in_args.SequenceControlDesc, + &in_args.PictureControlDesc, &out_args.ReconstructedPicture, &config, + &need_new_session)) { + GST_ERROR_OBJECT (self, "Start frame failed"); + gst_d3d12_fence_data_unref (fence_data); + gst_video_encoder_finish_frame (encoder, frame); + return GST_FLOW_ERROR; + } + + if (need_new_session) { + GST_DEBUG_OBJECT (self, "Need new encoding session"); + in_args.SequenceControlDesc.Flags = + D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_NONE; + + priv->session->encoder = nullptr; + priv->session->heap = nullptr; + + auto & desc = priv->encoder_desc; + desc.EncodeCodec = klass->codec; + desc.EncodeProfile = config.profile_desc; + desc.InputFormat = priv->format_info.Format; + desc.CodecConfiguration = config.codec_config; + desc.MaxMotionEstimationPrecision = + D3D12_VIDEO_ENCODER_MOTION_ESTIMATION_PRECISION_MODE_MAXIMUM; + + hr = video_device->CreateVideoEncoder (&desc, + IID_PPV_ARGS (&priv->session->encoder)); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't create encoder"); + gst_d3d12_fence_data_unref (fence_data); + gst_video_encoder_finish_frame (encoder, frame); + return GST_FLOW_ERROR; + } + + auto & heap_desc = priv->heap_desc; + heap_desc.EncodeCodec = klass->codec; + heap_desc.EncodeProfile = config.profile_desc; + heap_desc.EncodeLevel = config.level; + heap_desc.ResolutionsListCount = 1; + heap_desc.pResolutionList = &config.resolution; + hr = video_device->CreateVideoEncoderHeap (&heap_desc, + IID_PPV_ARGS (&priv->session->heap)); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't create encoder heap"); + gst_d3d12_fence_data_unref (fence_data); + gst_video_encoder_finish_frame (encoder, frame); + return GST_FLOW_ERROR; + } + } + + if (prev_max_subregions != config.max_subregions) { + gst_clear_object (&priv->session->encoder_pool); + const auto & resource_req = priv->resource_req; + + GST_DEBUG_OBJECT (self, "Subregion count changed %d -> %d", + prev_max_subregions, config.max_subregions); + + guint resolved_metadata_size; + resolved_metadata_size = sizeof (D3D12_VIDEO_ENCODER_OUTPUT_METADATA) + + sizeof (D3D12_VIDEO_ENCODER_FRAME_SUBREGION_METADATA) * + config.max_subregions; + if (priv->resource_req.EncoderMetadataBufferAccessAlignment > 1) { + resolved_metadata_size = + ((resolved_metadata_size + + resource_req.EncoderMetadataBufferAccessAlignment - 1) / + resource_req.EncoderMetadataBufferAccessAlignment) * + resource_req.EncoderMetadataBufferAccessAlignment; + } + + /* TODO: calculate bitstream buffer size */ + guint bitstream_size = 1024 * 1024 * 8; + priv->session->encoder_pool = + gst_d3d12_encoder_buffer_pool_new (self->device, + resource_req.MaxEncoderOutputMetadataBufferSize, + resolved_metadata_size, bitstream_size, ASYNC_DEPTH); + } + + GstD3D12EncoderBuffer *encoder_buf; + GST_VIDEO_ENCODER_STREAM_UNLOCK (self); + if (!gst_d3d12_encoder_buffer_pool_acquire (priv->session->encoder_pool, + &encoder_buf)) { + GST_ERROR_OBJECT (self, "Couldn't acquire bitstream buffer"); + GST_VIDEO_ENCODER_STREAM_LOCK (self); + klass->end_frame (self); + gst_clear_buffer (&frame->output_buffer); + gst_d3d12_fence_data_unref (fence_data); + gst_video_encoder_finish_frame (encoder, frame); + return GST_FLOW_ERROR; + } + GST_VIDEO_ENCODER_STREAM_LOCK (self); + + gst_d3d12_fence_data_add_notify_mini_object (fence_data, encoder_buf); + + ComPtr < ID3D12Resource > metadata; + ComPtr < ID3D12Resource > resolved_metadata; + ComPtr < ID3D12Resource > bitstream; + gst_d3d12_encoder_buffer_get_metadata (encoder_buf, &metadata); + gst_d3d12_encoder_buffer_get_resolved_metadata (encoder_buf, + &resolved_metadata); + gst_d3d12_encoder_buffer_get_bitstream (encoder_buf, &bitstream); + + auto mem = (GstD3D12Memory *) gst_buffer_peek_memory (upload, 0); + auto resource = gst_d3d12_memory_get_resource_handle (mem); + + in_args.pInputFrame = resource; + in_args.InputFrameSubresource = 0; + in_args.CurrentFrameBitstreamMetadataSize = 0; + + out_args.Bitstream.pBuffer = bitstream.Get (); + out_args.Bitstream.FrameStartOffset = 0; + out_args.EncoderOutputMetadata.pBuffer = metadata.Get (); + out_args.EncoderOutputMetadata.Offset = 0; + + meta_in_args.EncoderCodec = klass->codec; + meta_in_args.EncoderProfile = config.profile_desc; + meta_in_args.EncoderInputFormat = DXGI_FORMAT_NV12; + meta_in_args.EncodedPictureEffectiveResolution = config.resolution; + meta_in_args.HWLayoutMetadata.pBuffer = metadata.Get (); + meta_in_args.HWLayoutMetadata.Offset = 0; + + meta_out_args.ResolvedLayoutMetadata.pBuffer = resolved_metadata.Get (); + meta_out_args.ResolvedLayoutMetadata.Offset = 0; + + auto cl = priv->cmd->cl; + gst_d3d12_encoder_build_command (self, &in_args, &out_args, &meta_in_args, + &meta_out_args, fence_data, cl.Get ()); + hr = cl->Close (); + + klass->end_frame (self); + + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't close command list"); + gst_d3d12_fence_data_unref (fence_data); + gst_clear_buffer (&frame->output_buffer); + gst_video_encoder_finish_frame (encoder, frame); + return GST_FLOW_ERROR; + } + + auto completed = gst_d3d12_device_get_completed_value (self->device, + D3D12_COMMAND_LIST_TYPE_DIRECT); + if (completed < mem->fence_value) { + auto queue = gst_d3d12_device_get_command_queue (self->device, + D3D12_COMMAND_LIST_TYPE_DIRECT); + ComPtr < ID3D12Fence > fence; + gst_d3d12_command_queue_get_fence (queue, &fence); + gst_d3d12_command_queue_execute_wait (priv->cmd->queue, fence.Get (), + mem->fence_value); + } + + ID3D12CommandList *cmd_list[] = { cl.Get () }; + hr = gst_d3d12_command_queue_execute_command_lists (priv->cmd->queue, + 1, cmd_list, &priv->cmd->fence_val); + if (!gst_d3d12_result (hr, self->device)) { + GST_ERROR_OBJECT (self, "Couldn't execute command list"); + gst_d3d12_fence_data_unref (fence_data); + gst_clear_buffer (&frame->output_buffer); + gst_video_encoder_finish_frame (encoder, frame); + return GST_FLOW_ERROR; + } + + gst_d3d12_command_queue_set_notify (priv->cmd->queue, priv->cmd->fence_val, + fence_data, (GDestroyNotify) gst_d3d12_fence_data_unref); + + auto output_data = EncoderOutputData (); + output_data.frame = frame; + output_data.buffer = gst_d3d12_encoder_buffer_ref (encoder_buf); + output_data.fence_val = priv->cmd->fence_val; + + GST_LOG_OBJECT (self, "Enqueue data %" G_GUINT64_FORMAT, + priv->cmd->fence_val); + + GST_VIDEO_ENCODER_STREAM_UNLOCK (self); + { + std::lock_guard < std::mutex > lk (priv->session->queue_lock); + gst_queue_array_push_tail_struct (priv->session->output_queue, + &output_data); + priv->session->queue_cond.notify_one (); + } + GST_VIDEO_ENCODER_STREAM_LOCK (self); + + if (!priv->output_thread) { + GST_DEBUG_OBJECT (self, "Spawning output thread"); + priv->output_thread = g_thread_new ("GstD3D12H264EncLoop", + (GThreadFunc) gst_d3d12_encoder_output_loop, self); + } + + return priv->last_flow; +} + +static GstFlowReturn +gst_d3d12_encoder_finish (GstVideoEncoder * encoder) +{ + auto self = GST_D3D12_ENCODER (encoder); + + GST_DEBUG_OBJECT (self, "Finish"); + + gst_d3d12_encoder_drain (self, TRUE); + + return GST_FLOW_OK; +} + +static gboolean +gst_d3d12_encoder_flush (GstVideoEncoder * encoder) +{ + auto self = GST_D3D12_ENCODER (encoder); + auto priv = self->priv; + + GST_DEBUG_OBJECT (self, "Finish"); + + priv->flushing = true; + gst_d3d12_encoder_drain (self, TRUE); + priv->flushing = false; + priv->last_flow = GST_FLOW_OK; + + return GST_FLOW_OK; +} + +GType +gst_d3d12_encoder_rate_control_get_type (void) +{ + static GType type = 0; + static const GEnumValue methods[] = { + {D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CQP, "Constant QP", "cqp"}, + {D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CBR, "Constant bitrate", "cbr"}, + {D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR, "Variable bitrate", "vbr"}, + {D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR, + "Constant quality variable bitrate", "qvbr"}, + {0, nullptr, nullptr}, + }; + + GST_D3D12_CALL_ONCE_BEGIN { + type = g_enum_register_static ("GstD3D12EncoderRateControl", methods); + } GST_D3D12_CALL_ONCE_END; + + return type; +} + +GType +gst_d3d12_encoder_rate_control_support_get_type (void) +{ + static GType type = 0; + static const GFlagsValue methods[] = { + {(1 << D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CQP), "Constant QP", "cqp"}, + {(1 << D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CBR), "Constant bitrate", + "cbr"}, + {(1 << D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR), "Variable bitrate", + "vbr"}, + {(1 << D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR), + "Constant quality variable bitrate", "qvbr"}, + {0, nullptr, nullptr}, + }; + + GST_D3D12_CALL_ONCE_BEGIN { + type = g_flags_register_static ("GstD3D12EncoderRateControlSupport", + methods); + } GST_D3D12_CALL_ONCE_END; + + return type; +} + +GType +gst_d3d12_encoder_subregion_layout_get_type (void) +{ + static GType type = 0; + static const GEnumValue methods[] = { + {D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_FULL_FRAME, + "Full frame without partitioning", "full"}, + {D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_BYTES_PER_SUBREGION, + "Bytes per subregion", "bytes"}, + {D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_SQUARE_UNITS_PER_SUBREGION_ROW_UNALIGNED, + "Coding units (e.g., macroblock) per subregion", "coding-units"}, + {D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_UNIFORM_PARTITIONING_ROWS_PER_SUBREGION, + "Uniform rows per subregion", "rows"}, + {D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_UNIFORM_PARTITIONING_SUBREGIONS_PER_FRAME, + "Uniform subregions per frame", "subregions"}, + {0, nullptr, nullptr}, + }; + + GST_D3D12_CALL_ONCE_BEGIN { + type = g_enum_register_static ("GstD3D12EncoderSubregionLayout", methods); + } GST_D3D12_CALL_ONCE_END; + + return type; +} + +GType +gst_d3d12_encoder_subregion_layout_support_get_type (void) +{ + static GType type = 0; + static const GFlagsValue methods[] = { + {(1 << D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_FULL_FRAME), + "Full frame without partitioning", "full"}, + {(1 << D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_BYTES_PER_SUBREGION), + "Bytes per subregion", "bytes"}, + {(1 << D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_SQUARE_UNITS_PER_SUBREGION_ROW_UNALIGNED), + "Coding units (e.g., macroblock) per subregion", "coding-units"}, + {(1 << D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_UNIFORM_PARTITIONING_ROWS_PER_SUBREGION), + "Uniform rows (in coding-unit) per subregion", "rows"}, + {(1 << D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_UNIFORM_PARTITIONING_SUBREGIONS_PER_FRAME), + "Uniform subregions per frame", "subregions"}, + {0, nullptr, nullptr}, + }; + + GST_D3D12_CALL_ONCE_BEGIN { + type = g_flags_register_static ("GstD3D12EncoderSubregionLayoutSupport", + methods); + } GST_D3D12_CALL_ONCE_END; + + return type; +} + +GType +gst_d3d12_encoder_sei_insert_mode_get_type (void) +{ + static GType type = 0; + static const GEnumValue modes[] = { + {GST_D3D12_ENCODER_SEI_INSERT, "Insert", "insert"}, + {GST_D3D12_ENCODER_SEI_INSERT_AND_DROP, "Insert and drop", + "insert-and-drop"}, + {GST_D3D12_ENCODER_SEI_DISABLED, "Disabled", "disabled"}, + {0, nullptr, nullptr} + }; + + GST_D3D12_CALL_ONCE_BEGIN { + type = g_enum_register_static ("GstD3D12EncoderSeiInsertMode", modes); + } GST_D3D12_CALL_ONCE_END; + + return type; +} + +gboolean +gst_d3d12_encoder_check_needs_new_session (D3D12_VIDEO_ENCODER_SUPPORT_FLAGS + support_flags, D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAGS seq_flags) +{ + bool rc_updated = (seq_flags & + D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_RATE_CONTROL_CHANGE) != 0; + bool can_rc_update = CHECK_SUPPORT_FLAG (support_flags, + RATE_CONTROL_RECONFIGURATION_AVAILABLE); + if (rc_updated && !can_rc_update) + return TRUE; + + bool layout_updated = (seq_flags & + D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_SUBREGION_LAYOUT_CHANGE) != 0; + bool can_layout_update = CHECK_SUPPORT_FLAG (support_flags, + SUBREGION_LAYOUT_RECONFIGURATION_AVAILABLE); + if (layout_updated && !can_layout_update) + return TRUE; + + bool gop_updated = (seq_flags & + D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_GOP_SEQUENCE_CHANGE) != 0; + bool can_gop_update = CHECK_SUPPORT_FLAG (support_flags, + SEQUENCE_GOP_RECONFIGURATION_AVAILABLE); + if (gop_updated && !can_gop_update) + return TRUE; + + return FALSE; +} diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12encoder.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12encoder.h new file mode 100644 index 0000000000..2f08005f77 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12encoder.h @@ -0,0 +1,119 @@ +/* GStreamer + * Copyright (C) 2024 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include +#include "gstd3d12.h" + +G_BEGIN_DECLS + +#define GST_TYPE_D3D12_ENCODER (gst_d3d12_encoder_get_type()) +#define GST_D3D12_ENCODER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_D3D12_ENCODER,GstD3D12Encoder)) +#define GST_D3D12_ENCODER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_D3D12_ENCODER,GstD3D12EncoderClass)) +#define GST_D3D12_ENCODER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj),GST_TYPE_D3D12_ENCODER,GstD3D12EncoderClass)) +#define GST_IS_D3D12_ENCODER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_D3D12_ENCODER)) +#define GST_IS_D3D12_ENCODER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_D3D12_ENCODER)) + +struct GstD3D12EncoderPrivate; + +struct GstD3D12EncoderConfig +{ + D3D12_VIDEO_ENCODER_PROFILE_DESC profile_desc; + D3D12_VIDEO_ENCODER_LEVEL_SETTING level; + D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION codec_config; + D3D12_VIDEO_ENCODER_PICTURE_CONTROL_SUBREGIONS_LAYOUT_DATA layout; + D3D12_VIDEO_ENCODER_SEQUENCE_GOP_STRUCTURE gop_struct; + D3D12_VIDEO_ENCODER_PICTURE_RESOLUTION_DESC resolution; + D3D12_VIDEO_ENCODER_SUPPORT_FLAGS support_flags; + D3D12_VIDEO_ENCODER_RATE_CONTROL_CQP cqp;; + D3D12_VIDEO_ENCODER_RATE_CONTROL_CBR cbr; + D3D12_VIDEO_ENCODER_RATE_CONTROL_VBR vbr; + D3D12_VIDEO_ENCODER_RATE_CONTROL_QVBR qvbr; + D3D12_VIDEO_ENCODER_RATE_CONTROL rate_control; + guint max_subregions; +}; + +enum GstD3D12EncoderSeiInsertMode +{ + GST_D3D12_ENCODER_SEI_INSERT, + GST_D3D12_ENCODER_SEI_INSERT_AND_DROP, + GST_D3D12_ENCODER_SEI_DISABLED, +}; + +#define GST_TYPE_D3D12_ENCODER_RATE_CONTROL (gst_d3d12_encoder_rate_control_get_type ()) +GType gst_d3d12_encoder_rate_control_get_type (void); + +#define GST_TYPE_D3D12_ENCODER_RATE_CONTROL_SUPPORT (gst_d3d12_encoder_rate_control_support_get_type ()) +GType gst_d3d12_encoder_rate_control_support_get_type (void); + +#define GST_TYPE_D3D12_ENCODER_SUBREGION_LAYOUT (gst_d3d12_encoder_subregion_layout_get_type()) +GType gst_d3d12_encoder_subregion_layout_get_type (void); + +#define GST_TYPE_D3D12_ENCODER_SUBREGION_LAYOUT_SUPPORT (gst_d3d12_encoder_subregion_layout_support_get_type ()) +GType gst_d3d12_encoder_subregion_layout_support_get_type (void); + +#define GST_TYPE_D3D12_ENCODER_SEI_INSERT_MODE (gst_d3d12_encoder_sei_insert_mode_get_type ()) +GType gst_d3d12_encoder_sei_insert_mode_get_type (void); + +struct GstD3D12Encoder +{ + GstVideoEncoder parent; + + GstD3D12Device *device; + + GstD3D12EncoderPrivate *priv; +}; + +struct GstD3D12EncoderClass +{ + GstVideoEncoderClass parent_class; + + D3D12_VIDEO_ENCODER_CODEC codec; + gint64 adapter_luid; + guint device_id; + guint vendor_id; + + gboolean (*new_sequence) (GstD3D12Encoder * encoder, + ID3D12VideoDevice * video_device, + GstVideoCodecState * state, + GstD3D12EncoderConfig * config); + + gboolean (*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); + + gboolean (*end_frame) (GstD3D12Encoder * encoder); +}; + +GType gst_d3d12_encoder_get_type (void); + +gboolean gst_d3d12_encoder_check_needs_new_session (D3D12_VIDEO_ENCODER_SUPPORT_FLAGS support_flags, + D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAGS seq_flags); + +#define CHECK_SUPPORT_FLAG(flags, f) \ + ((flags & D3D12_VIDEO_ENCODER_SUPPORT_FLAG_ ##f) != 0) + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12encoderbufferpool.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12encoderbufferpool.cpp new file mode 100644 index 0000000000..b1cc7661bd --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12encoderbufferpool.cpp @@ -0,0 +1,326 @@ +/* GStreamer + * Copyright (C) 2024 Seungha Yang + * + * This library is free software; you cln 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 "gstd3d12encoderbufferpool.h" +#include +#include +#include +#include +#include + +GST_DEBUG_CATEGORY_STATIC (gst_d3d12_encoder_buffer_pool_debug); +#define GST_CAT_DEFAULT gst_d3d12_encoder_buffer_pool_debug + +/* *INDENT-OFF* */ +using namespace Microsoft::WRL; + +struct _GstD3D12EncoderBuffer : public GstMiniObject +{ + GstD3D12EncoderBufferPool *pool = nullptr; + ComPtr metadata; + ComPtr resolved_metadata; + ComPtr bitstream; +}; + +struct GstD3D12EncoderBufferPoolPrivate +{ + ~GstD3D12EncoderBufferPoolPrivate () + { + while (!buffer_pool.empty ()) { + auto buf = buffer_pool.front (); + buffer_pool.pop (); + gst_mini_object_unref (buf); + } + } + + ComPtr device; + + std::mutex lock; + std::condition_variable cond; + std::queuebuffer_pool; + + guint metadata_size; + guint resolved_metadata_size; + guint bitstream_size; + guint pool_size; +}; +/* *INDENT-ON* */ + +struct _GstD3D12EncoderBufferPool +{ + GstObject parent; + + GstD3D12EncoderBufferPoolPrivate *priv; +}; + +GST_DEFINE_MINI_OBJECT_TYPE (GstD3D12EncoderBuffer, gst_d3d12_encoder_buffer); + +static void gst_d3d12_encoder_buffer_pool_finalize (GObject * object); + +#define gst_d3d12_encoder_buffer_pool_parent_class parent_class +G_DEFINE_TYPE (GstD3D12EncoderBufferPool, + gst_d3d12_encoder_buffer_pool, GST_TYPE_OBJECT); + +static void +gst_d3d12_encoder_buffer_pool_class_init (GstD3D12EncoderBufferPoolClass * + klass) +{ + auto object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gst_d3d12_encoder_buffer_pool_finalize; + + GST_DEBUG_CATEGORY_INIT (gst_d3d12_encoder_buffer_pool_debug, + "d3d12encoderbufferpool", 0, "d3d12encoderbufferpool"); +} + +static void +gst_d3d12_encoder_buffer_pool_init (GstD3D12EncoderBufferPool * self) +{ + self->priv = new GstD3D12EncoderBufferPoolPrivate (); +} + +static void +gst_d3d12_encoder_buffer_pool_finalize (GObject * object) +{ + auto self = GST_D3D12_ENCODER_BUFFER_POOL (object); + + GST_DEBUG_OBJECT (self, "Finalize"); + + delete self->priv; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_d3d12_encoder_buffer_free (GstD3D12EncoderBuffer * buffer) +{ + delete buffer; +} + +static GstD3D12EncoderBuffer * +gst_d3d12_encoder_buffer_pool_alloc (GstD3D12EncoderBufferPool * self) +{ + auto priv = self->priv; + D3D12_HEAP_PROPERTIES prop = + CD3DX12_HEAP_PROPERTIES (D3D12_HEAP_TYPE_DEFAULT); + D3D12_RESOURCE_DESC desc = + CD3DX12_RESOURCE_DESC::Buffer (priv->metadata_size); + + ComPtr < ID3D12Resource > metadata; + auto hr = priv->device->CreateCommittedResource (&prop, + D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COMMON, + nullptr, IID_PPV_ARGS (&metadata)); + if (FAILED (hr)) { + GST_ERROR_OBJECT (self, "Couldn't metadata buffer, hr: 0x%x", (guint) hr); + return nullptr; + } + + prop = CD3DX12_HEAP_PROPERTIES (D3D12_CPU_PAGE_PROPERTY_WRITE_BACK, + D3D12_MEMORY_POOL_L0); + desc = CD3DX12_RESOURCE_DESC::Buffer (priv->resolved_metadata_size); + ComPtr < ID3D12Resource > resolved_metadata; + hr = priv->device->CreateCommittedResource (&prop, + D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COMMON, + nullptr, IID_PPV_ARGS (&resolved_metadata)); + if (FAILED (hr)) { + GST_ERROR_OBJECT (self, "Couldn't metadata buffer, hr: 0x%x", (guint) hr); + return nullptr; + } + + desc = CD3DX12_RESOURCE_DESC::Buffer (priv->bitstream_size); + ComPtr < ID3D12Resource > bitstream; + hr = priv->device->CreateCommittedResource (&prop, + D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COMMON, + nullptr, IID_PPV_ARGS (&bitstream)); + if (FAILED (hr)) { + GST_ERROR_OBJECT (self, "Couldn't metadata buffer, hr: 0x%x", (guint) hr); + return nullptr; + } + + auto new_buf = new GstD3D12EncoderBuffer (); + + gst_mini_object_init (new_buf, 0, gst_d3d12_encoder_buffer_get_type (), + nullptr, nullptr, + (GstMiniObjectFreeFunction) gst_d3d12_encoder_buffer_free); + new_buf->metadata = metadata; + new_buf->resolved_metadata = resolved_metadata; + new_buf->bitstream = bitstream; + + return new_buf; +} + +GstD3D12EncoderBufferPool * +gst_d3d12_encoder_buffer_pool_new (GstD3D12Device * device, + guint metadata_size, guint resolved_metadata_size, guint bitstream_size, + guint pool_size) +{ + g_return_val_if_fail (GST_IS_D3D12_DEVICE (device), nullptr); + + auto self = (GstD3D12EncoderBufferPool *) + g_object_new (GST_TYPE_D3D12_ENCODER_BUFFER_POOL, nullptr); + gst_object_ref_sink (self); + + auto priv = self->priv; + priv->device = gst_d3d12_device_get_device_handle (device);; + priv->metadata_size = metadata_size; + priv->resolved_metadata_size = resolved_metadata_size; + priv->bitstream_size = bitstream_size; + priv->pool_size = pool_size; + + for (guint i = 0; i < pool_size; i++) { + auto new_buf = gst_d3d12_encoder_buffer_pool_alloc (self); + if (!new_buf) { + gst_object_unref (self); + return nullptr; + } + + priv->buffer_pool.push (new_buf); + } + + return self; +} + +static void +gst_d3d12_encoder_buffer_pool_release (GstD3D12EncoderBufferPool * pool, + GstD3D12EncoderBuffer * buffer) +{ + auto priv = pool->priv; + { + std::lock_guard < std::mutex > lk (priv->lock); + buffer->dispose = nullptr; + buffer->pool = nullptr; + priv->buffer_pool.push (buffer); + priv->cond.notify_one (); + } + + gst_object_unref (pool); +} + +static gboolean +gst_d3d12_encoder_buffer_dispose (GstD3D12EncoderBuffer * buffer) +{ + if (!buffer->pool) + return TRUE; + + gst_mini_object_ref (buffer); + gst_d3d12_encoder_buffer_pool_release (buffer->pool, buffer); + + return FALSE; +} + +gboolean +gst_d3d12_encoder_buffer_pool_acquire (GstD3D12EncoderBufferPool * pool, + GstD3D12EncoderBuffer ** buffer) +{ + g_return_val_if_fail (GST_IS_D3D12_ENCODER_BUFFER_POOL (pool), FALSE); + g_return_val_if_fail (buffer, FALSE); + + *buffer = nullptr; + + auto priv = pool->priv; + GstD3D12EncoderBuffer *new_buf = nullptr; + + { + std::unique_lock < std::mutex > lk (priv->lock); + if (priv->pool_size > 0) { + while (priv->buffer_pool.empty ()) + priv->cond.wait (lk); + } + + if (!priv->buffer_pool.empty ()) { + new_buf = priv->buffer_pool.front (); + priv->buffer_pool.pop (); + } + } + + if (!new_buf) + new_buf = gst_d3d12_encoder_buffer_pool_alloc (pool); + + if (!new_buf) + return FALSE; + + new_buf->pool = (GstD3D12EncoderBufferPool *) gst_object_ref (pool); + new_buf->dispose = + (GstMiniObjectDisposeFunction) gst_d3d12_encoder_buffer_dispose; + + *buffer = new_buf; + + return TRUE; +} + +GstD3D12EncoderBuffer * +gst_d3d12_encoder_buffer_ref (GstD3D12EncoderBuffer * buffer) +{ + return (GstD3D12EncoderBuffer *) gst_mini_object_ref (buffer); +} + +void +gst_d3d12_encoder_buffer_unref (GstD3D12EncoderBuffer * buffer) +{ + gst_mini_object_unref (buffer); +} + +void +gst_clear_d3d12_encoder_buffer (GstD3D12EncoderBuffer ** buffer) +{ + gst_clear_mini_object (buffer); +} + +gboolean +gst_d3d12_encoder_buffer_get_metadata (GstD3D12EncoderBuffer * buffer, + ID3D12Resource ** metadata) +{ + g_return_val_if_fail (buffer, FALSE); + g_return_val_if_fail (metadata, FALSE); + + *metadata = buffer->metadata.Get (); + (*metadata)->AddRef (); + + return TRUE; +} + +gboolean +gst_d3d12_encoder_buffer_get_resolved_metadata (GstD3D12EncoderBuffer * buffer, + ID3D12Resource ** resolved_metadata) +{ + g_return_val_if_fail (buffer, FALSE); + g_return_val_if_fail (resolved_metadata, FALSE); + + *resolved_metadata = buffer->resolved_metadata.Get (); + (*resolved_metadata)->AddRef (); + + return TRUE; +} + +gboolean +gst_d3d12_encoder_buffer_get_bitstream (GstD3D12EncoderBuffer * buffer, + ID3D12Resource ** bitstream) +{ + g_return_val_if_fail (buffer, FALSE); + g_return_val_if_fail (bitstream, FALSE); + + *bitstream = buffer->bitstream.Get (); + (*bitstream)->AddRef (); + + return TRUE; +} diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12encoderbufferpool.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12encoderbufferpool.h new file mode 100644 index 0000000000..455de27350 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12encoderbufferpool.h @@ -0,0 +1,59 @@ +/* GStreamer + * Copyright (C) 2024 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include +#include "gstd3d12.h" + +G_BEGIN_DECLS + +#define GST_TYPE_D3D12_ENCODER_BUFFER_POOL (gst_d3d12_encoder_buffer_pool_get_type()) +G_DECLARE_FINAL_TYPE (GstD3D12EncoderBufferPool, + gst_d3d12_encoder_buffer_pool, GST, D3D12_ENCODER_BUFFER_POOL, GstObject); + +typedef struct _GstD3D12EncoderBuffer GstD3D12EncoderBuffer; + +GstD3D12EncoderBufferPool * gst_d3d12_encoder_buffer_pool_new (GstD3D12Device * device, + guint metadata_size, + guint resolved_metadata_size, + guint bitstream_size, + guint pool_size); + +gboolean gst_d3d12_encoder_buffer_pool_acquire (GstD3D12EncoderBufferPool * pool, + GstD3D12EncoderBuffer ** buffer); + +GstD3D12EncoderBuffer * gst_d3d12_encoder_buffer_ref (GstD3D12EncoderBuffer * buffer); + +void gst_d3d12_encoder_buffer_unref (GstD3D12EncoderBuffer * buffer); + +void gst_clear_d3d12_encoder_buffer (GstD3D12EncoderBuffer ** buffer); + +gboolean gst_d3d12_encoder_buffer_get_metadata (GstD3D12EncoderBuffer * buffer, + ID3D12Resource ** metadata); + +gboolean gst_d3d12_encoder_buffer_get_resolved_metadata (GstD3D12EncoderBuffer * buffer, + ID3D12Resource ** resolved_metadata); + +gboolean gst_d3d12_encoder_buffer_get_bitstream (GstD3D12EncoderBuffer * buffer, + ID3D12Resource ** bitstream); + +G_END_DECLS + diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12h264enc.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12h264enc.cpp new file mode 100644 index 0000000000..89c9b46b26 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12h264enc.cpp @@ -0,0 +1,2482 @@ +/* GStreamer + * Copyright (C) 2024 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstd3d12h264enc.h" +#include "gstd3d12encoder.h" +#include "gstd3d12dpbstorage.h" +#include "gstd3d12pluginutils.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* *INDENT-OFF* */ +using namespace Microsoft::WRL; +/* *INDENT-ON* */ + +GST_DEBUG_CATEGORY_STATIC (gst_d3d12_h264_enc_debug); +#define GST_CAT_DEFAULT gst_d3d12_h264_enc_debug + +enum +{ + PROP_0, + PROP_RATE_CONTROL_SUPPORT, + PROP_SLICE_MODE_SUPPORT, + PROP_AUD, + PROP_GOP_SIZE, + PROP_REF_FRAMES, + PROP_FRAME_ANALYSIS, + PROP_RATE_CONTROL, + PROP_BITRATE, + PROP_MAX_BITRATE, + PROP_QVBR_QUALITY, + PROP_QP_INIT, + PROP_QP_MIN, + PROP_QP_MAX, + PROP_QP_I, + PROP_QP_P, + PROP_QP_B, + PROP_SLICE_MODE, + PROP_SLICE_PARTITION, + PROP_CC_INSERT, +}; + +#define DEFAULT_AUD TRUE +#define DEFAULT_FRAME_ANALYSIS FALSE +#define DEFAULT_GOP_SIZE 60 +#define DEFAULT_RATE_CONTROL D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR +#define DEFAULT_BITRATE 2000 +#define DEFAULT_MAX_BITRATE 4000 +#define DEFAULT_QVBR_QUALITY 23 +#define DEFAULT_QP 0 +#define DEFAULT_CQP 23 +#define DEFAULT_REF_FRAMES 0 +#define DEFAULT_SLICE_MODE D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_FULL_FRAME +#define DEFAULT_SLICE_PARTITION 0 +#define DEFAULT_CC_INSERT GST_D3D12_ENCODER_SEI_INSERT + +struct GstD3D12H264EncClassData +{ + gint64 luid; + guint device_id; + guint vendor_id; + gchar *description; + GstCaps *sink_caps; + GstCaps *src_caps; + guint rc_support; + guint slice_mode_support; + D3D12_VIDEO_ENCODER_CODEC_PICTURE_CONTROL_SUPPORT_H264 picture_ctrl; +}; + +/* *INDENT-OFF* */ +class GstD3D12H264EncGop +{ +public: + void Init (guint gop_length) + { + /* TODO: pic_order_cnt_type == 0 for b-frame */ + gop_struct_.pic_order_cnt_type = 2; + if (gop_length == 1) + gop_struct_.PPicturePeriod = 0; + else + gop_struct_.PPicturePeriod = 1; + + /* 0 means infinite */ + if (gop_length == 0) { + gop_struct_.GOPLength = 0; + gop_struct_.log2_max_frame_num_minus4 = 12; + } else { + /* count bits */ + guint val = gop_length; + guint num_bits = 0; + while (val) { + num_bits++; + val >>= 1; + } + + if (num_bits < 4) + gop_struct_.log2_max_frame_num_minus4 = 0; + else if (num_bits > 16) + gop_struct_.log2_max_frame_num_minus4 = 12; + else + gop_struct_.log2_max_frame_num_minus4 = num_bits - 4; + + gop_struct_.GOPLength = gop_length; + } + + MaxFrameNum_ = 1 << (gop_struct_.log2_max_frame_num_minus4 + 4); + + if (gop_struct_.pic_order_cnt_type == 2) { + /* unused */ + gop_struct_.log2_max_pic_order_cnt_lsb_minus4 = 0; + MaxPicOrderCnt_ = MaxFrameNum_ * 2; + } else { + guint log2_max_pic_order_cnt = gop_struct_.log2_max_frame_num_minus4 + 5; + log2_max_pic_order_cnt = MIN (16, log2_max_pic_order_cnt); + gop_struct_.log2_max_pic_order_cnt_lsb_minus4 = log2_max_pic_order_cnt - 4; + + MaxPicOrderCnt_ = 1 << log2_max_pic_order_cnt; + } + + gop_start_ = true; + frame_num_ = 0; + encode_order_ = 0; + } + + D3D12_VIDEO_ENCODER_SEQUENCE_GOP_STRUCTURE_H264 + GetGopStruct () + { + return gop_struct_; + } + + void + FillPicCtrl (D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264 & pic_ctrl) + { + if (gop_start_) { + pic_ctrl.FrameType = D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_IDR_FRAME; + pic_ctrl.idr_pic_id = idr_pic_id_; + pic_ctrl.FrameDecodingOrderNumber = 0; + pic_ctrl.PictureOrderCountNumber = 0; + pic_ctrl.TemporalLayerIndex = 0; + idr_pic_id_++; + gop_start_ = false; + } else { + pic_ctrl.FrameType = D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_P_FRAME; + pic_ctrl.idr_pic_id = idr_pic_id_; + pic_ctrl.FrameDecodingOrderNumber = frame_num_; + pic_ctrl.PictureOrderCountNumber = ((guint) frame_num_) * 2; + pic_ctrl.TemporalLayerIndex = 0; + } + + /* And increase frame num */ + /* FIXME: frame_num = (frame_num_of_pref_ref_pic + 1) % ... */ + frame_num_ = (frame_num_ + 1) % MaxFrameNum_; + encode_order_++; + if (gop_struct_.GOPLength != 0 && encode_order_ >= gop_struct_.GOPLength) { + frame_num_ = 0; + encode_order_ = 0; + gop_start_ = true; + } + } + + void ForceKeyUnit () + { + frame_num_ = 0; + encode_order_ = 0; + gop_start_ = true; + } + +private: + D3D12_VIDEO_ENCODER_SEQUENCE_GOP_STRUCTURE_H264 gop_struct_ = { }; + guint16 frame_num_ = 0; + guint16 idr_pic_id_ = 0; + guint32 MaxFrameNum_ = 16; + guint32 MaxPicOrderCnt_ = 16; + guint64 encode_order_ = 0; + bool gop_start_ = false; +}; + +class GstD3D12H264EncDpb +{ +public: + GstD3D12H264EncDpb (GstD3D12Device * device, UINT width, UINT height, + UINT max_dpb_size, bool array_of_textures) + { + max_dpb_size_ = max_dpb_size; + if (max_dpb_size_ > 0) { + storage_ = gst_d3d12_dpb_storage_new (device, max_dpb_size + 1, + array_of_textures, DXGI_FORMAT_NV12, width, height, + D3D12_RESOURCE_FLAG_VIDEO_ENCODE_REFERENCE_ONLY | + D3D12_RESOURCE_FLAG_DENY_SHADER_RESOURCE); + } + } + + ~GstD3D12H264EncDpb () + { + gst_clear_object (&storage_); + } + + bool IsValid () + { + if (max_dpb_size_ > 0 && !storage_) + return false; + + return true; + } + + bool StartFrame (bool is_reference, + D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264 * ctrl_data, + D3D12_VIDEO_ENCODER_RECONSTRUCTED_PICTURE * recon_pic, + D3D12_VIDEO_ENCODE_REFERENCE_FRAMES * ref_frames, + UINT64 display_order) + { + ctrl_data_ = *ctrl_data; + cur_display_order_ = display_order; + cur_frame_is_ref_ = is_reference; + + recon_pic_.pReconstructedPicture = nullptr; + recon_pic_.ReconstructedPictureSubresource = 0; + + if (max_dpb_size_ > 0 && + ctrl_data_.FrameType == D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_IDR_FRAME) { + ref_pic_desc_.clear(); + ref_pic_display_order_.clear (); + gst_d3d12_dpb_storage_clear_dpb (storage_); + } + + if (is_reference) { + g_assert (max_dpb_size_ > 0); + if (!gst_d3d12_dpb_storage_acquire_frame (storage_, &recon_pic_)) + return false; + } + + *recon_pic = recon_pic_; + + switch (ctrl_data_.FrameType) { + case D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_P_FRAME: + case D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_B_FRAME: + g_assert (max_dpb_size_ > 0); + gst_d3d12_dpb_storage_get_reference_frames (storage_, + ref_frames); + break; + default: + ref_frames->NumTexture2Ds = 0; + ref_frames->ppTexture2Ds = nullptr; + ref_frames->pSubresources = nullptr; + break; + } + + list0_.clear (); + list1_.clear (); + + bool build_l0 = (ctrl_data_.FrameType == + D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_P_FRAME) || + (ctrl_data_.FrameType == + D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_B_FRAME); + bool build_l1 = ctrl_data_.FrameType == + D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_B_FRAME; + + if (build_l0) { + for (UINT i = 0; i < (UINT) ref_pic_display_order_.size (); i++) { + if (ref_pic_display_order_[i] < display_order) + list0_.push_back (i); + } + } + + if (build_l1) { + for (UINT i = 0; i < (UINT) ref_pic_display_order_.size (); i++) { + if (ref_pic_display_order_[i] > display_order) + list1_.push_back (i); + } + } + + ctrl_data->List0ReferenceFramesCount = list0_.size (); + ctrl_data->pList0ReferenceFrames = + list0_.empty () ? nullptr : list0_.data (); + + ctrl_data->List1ReferenceFramesCount = list1_.size (); + ctrl_data->pList1ReferenceFrames = + list1_.empty () ? nullptr : list1_.data (); + + ctrl_data->ReferenceFramesReconPictureDescriptorsCount = + ref_pic_desc_.size (); + ctrl_data->pReferenceFramesReconPictureDescriptors = + ref_pic_desc_.empty () ? nullptr : ref_pic_desc_.data (); + + return true; + } + + void EndFrame () + { + if (!cur_frame_is_ref_ || max_dpb_size_ == 0) + return; + + if (gst_d3d12_dpb_storage_get_dpb_size (storage_) == max_dpb_size_) { + gst_d3d12_dpb_storage_remove_oldest_frame (storage_); + ref_pic_display_order_.pop_back (); + ref_pic_desc_.pop_back (); + } + + gst_d3d12_dpb_storage_add_frame (storage_, &recon_pic_); + + D3D12_VIDEO_ENCODER_REFERENCE_PICTURE_DESCRIPTOR_H264 desc = { }; + desc.ReconstructedPictureResourceIndex = 0; + desc.IsLongTermReference = FALSE; + desc.PictureOrderCountNumber = ctrl_data_.PictureOrderCountNumber; + desc.FrameDecodingOrderNumber = ctrl_data_.FrameDecodingOrderNumber; + desc.TemporalLayerIndex = 0; + + ref_pic_display_order_.insert (ref_pic_display_order_.begin (), + cur_display_order_); + ref_pic_desc_.insert (ref_pic_desc_.begin(), desc); + for (UINT i = 1; i < ref_pic_desc_.size (); i++) + ref_pic_desc_[i].ReconstructedPictureResourceIndex = i; + } + +private: + std::vector ref_pic_desc_; + std::vector ref_pic_display_order_; + D3D12_VIDEO_ENCODER_RECONSTRUCTED_PICTURE recon_pic_ = { }; + D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264 ctrl_data_ = { }; + std::vector list0_; + std::vector list1_; + UINT max_dpb_size_ = 0; + UINT64 cur_display_order_ = 0; + bool cur_frame_is_ref_ = false; + GstD3D12DpbStorage *storage_ = nullptr; +}; + +struct GstD3D12H264SPS +{ + void Clear () + { + memset (&sps, 0, sizeof (GstH264SPS)); + bytes.clear (); + } + + GstH264SPS sps; + std::vector bytes; +}; + +struct GstD3D12H264PPS +{ + void Clear () + { + memset (&pps, 0, sizeof (GstH264PPS)); + bytes.clear (); + } + + GstH264PPS pps; + std::vector bytes; +}; + +struct GstD3D12H264EncPrivate +{ + GstD3D12H264EncPrivate () + { + cc_sei = g_array_new (FALSE, FALSE, sizeof (GstH264SEIMessage)); + g_array_set_clear_func (cc_sei, (GDestroyNotify) gst_h264_sei_clear); + } + + ~GstD3D12H264EncPrivate () + { + g_array_unref (cc_sei); + } + + GstVideoInfo info; + GstD3D12H264SPS sps; + std::vector pps; + GstH264Profile selected_profile; + GstD3D12H264EncGop gop; + std::unique_ptr dpb; + guint last_pps_id = 0; + guint64 display_order = 0; + GArray *cc_sei; + + std::mutex prop_lock; + + GstD3D12EncoderConfig encoder_config = { }; + + D3D12_VIDEO_ENCODER_PROFILE_H264 profile_h264 = + D3D12_VIDEO_ENCODER_PROFILE_H264_MAIN; + D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264 config_h264 = { }; + D3D12_VIDEO_ENCODER_LEVELS_H264 level_h264; + D3D12_VIDEO_ENCODER_PICTURE_CONTROL_SUBREGIONS_LAYOUT_DATA_SLICES + layout_slices = { }; + D3D12_VIDEO_ENCODER_SEQUENCE_GOP_STRUCTURE_H264 gop_struct_h264 = { }; + D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264 pic_control_h264 = { }; + + D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE selected_rc_mode = + D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_ABSOLUTE_QP_MAP; + D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE selected_slice_mode = + DEFAULT_SLICE_MODE; + guint selected_ref_frames = 0; + D3D12_VIDEO_ENCODER_CODEC_PICTURE_CONTROL_SUPPORT_H264 pic_ctrl_support = { }; + + /* properties */ + gboolean aud = DEFAULT_AUD; + + /* gop struct related */ + guint gop_size = DEFAULT_GOP_SIZE; + guint ref_frames = DEFAULT_REF_FRAMES; + gboolean gop_updated = FALSE; + + /* rate control config */ + D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE rc_mode = DEFAULT_RATE_CONTROL; + gboolean frame_analysis = DEFAULT_FRAME_ANALYSIS; + gboolean rc_flag_updated = FALSE; + guint bitrate = DEFAULT_BITRATE; + guint max_bitrate = DEFAULT_MAX_BITRATE; + guint qvbr_quality = DEFAULT_QVBR_QUALITY; + guint qp_init = DEFAULT_QP; + guint qp_min = DEFAULT_QP; + guint qp_max = DEFAULT_QP; + guint qp_i = DEFAULT_CQP; + guint qp_p = DEFAULT_CQP; + guint qp_b = DEFAULT_CQP; + gboolean rc_updated = FALSE; + + /* slice mode */ + D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE slice_mode = + DEFAULT_SLICE_MODE; + guint slice_partition = DEFAULT_SLICE_PARTITION; + gboolean slice_updated = FALSE; + + GstD3D12EncoderSeiInsertMode cc_insert = DEFAULT_CC_INSERT; +}; +/* *INDENT-ON* */ + +struct GstD3D12H264Enc +{ + GstD3D12Encoder parent; + + GstD3D12H264EncPrivate *priv; +}; + +struct GstD3D12H264EncClass +{ + GstD3D12EncoderClass parent_class; + + GstD3D12H264EncClassData *cdata; +}; + +static inline GstD3D12H264Enc * +GST_D3D12_H264_ENC (gpointer ptr) +{ + return (GstD3D12H264Enc *) ptr; +} + +static inline GstD3D12H264EncClass * +GST_D3D12_H264_ENC_GET_CLASS (gpointer ptr) +{ + return G_TYPE_INSTANCE_GET_CLASS (ptr, G_TYPE_FROM_INSTANCE (ptr), + GstD3D12H264EncClass); +} + +static void gst_d3d12_h264_enc_finalize (GObject * object); +static void gst_d3d12_h264_enc_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_d3d12_h264_enc_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static gboolean gst_d3d12_h264_enc_start (GstVideoEncoder * encoder); +static gboolean gst_d3d12_h264_enc_stop (GstVideoEncoder * encoder); +static gboolean gst_d3d12_h264_enc_transform_meta (GstVideoEncoder * encoder, + GstVideoCodecFrame * frame, GstMeta * meta); +static gboolean gst_d3d12_h264_enc_new_sequence (GstD3D12Encoder * encoder, + ID3D12VideoDevice * video_device, GstVideoCodecState * state, + GstD3D12EncoderConfig * config); +static gboolean gst_d3d12_h264_enc_start_frame (GstD3D12Encoder * encoder, + ID3D12VideoDevice * video_device, GstVideoCodecFrame * frame, + D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_DESC * seq_ctrl, + D3D12_VIDEO_ENCODER_PICTURE_CONTROL_DESC * picture_ctrl, + D3D12_VIDEO_ENCODER_RECONSTRUCTED_PICTURE * recon_pic, + GstD3D12EncoderConfig * config, gboolean * need_new_session); +static gboolean gst_d3d12_h264_enc_end_frame (GstD3D12Encoder * encoder); + +static GstElementClass *parent_class = nullptr; + +static void +gst_d3d12_h264_enc_class_init (GstD3D12H264EncClass * klass, gpointer data) +{ + auto object_class = G_OBJECT_CLASS (klass); + auto element_class = GST_ELEMENT_CLASS (klass); + auto encoder_class = GST_VIDEO_ENCODER_CLASS (klass); + auto d3d12enc_class = GST_D3D12_ENCODER_CLASS (klass); + auto cdata = (GstD3D12H264EncClassData *) data; + GstPadTemplate *pad_templ; + auto read_only_params = (GParamFlags) (GST_PARAM_DOC_SHOW_DEFAULT | + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + auto rw_params = (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); + + parent_class = (GstElementClass *) g_type_class_peek_parent (klass); + klass->cdata = cdata; + + object_class->finalize = gst_d3d12_h264_enc_finalize; + object_class->set_property = gst_d3d12_h264_enc_set_property; + object_class->get_property = gst_d3d12_h264_enc_get_property; + + g_object_class_install_property (object_class, PROP_RATE_CONTROL_SUPPORT, + g_param_spec_flags ("rate-control-support", "Rate Control Support", + "Supported rate control modes", + GST_TYPE_D3D12_ENCODER_RATE_CONTROL_SUPPORT, 0, read_only_params)); + + g_object_class_install_property (object_class, PROP_SLICE_MODE_SUPPORT, + g_param_spec_flags ("slice-mode-support", "Slice Mode Support", + "Supported slice partition modes", + GST_TYPE_D3D12_ENCODER_SUBREGION_LAYOUT_SUPPORT, 1, + read_only_params)); + + g_object_class_install_property (object_class, PROP_AUD, + g_param_spec_boolean ("aud", "AUD", "Use AU delimiter", DEFAULT_AUD, + rw_params)); + + g_object_class_install_property (object_class, PROP_GOP_SIZE, + g_param_spec_uint ("gop-size", "GOP Size", "Size of GOP (0 = infinite)", + 0, G_MAXUINT32, DEFAULT_GOP_SIZE, rw_params)); + + g_object_class_install_property (object_class, PROP_REF_FRAMES, + g_param_spec_uint ("ref-frames", "Ref frames", + "Preferred number of reference frames. Actual number of reference " + "frames can be limited depending on hardware (0 = unspecified)", + 0, 16, DEFAULT_REF_FRAMES, rw_params)); + + g_object_class_install_property (object_class, PROP_FRAME_ANALYSIS, + g_param_spec_boolean ("frame-analysis", "Frame Analysis", + "Enable 2 pass encoding if supported by hardware", + DEFAULT_FRAME_ANALYSIS, rw_params)); + + g_object_class_install_property (object_class, PROP_RATE_CONTROL, + g_param_spec_enum ("rate-control", "Rate Control", + "Rate Control Method", GST_TYPE_D3D12_ENCODER_RATE_CONTROL, + DEFAULT_RATE_CONTROL, rw_params)); + + g_object_class_install_property (object_class, PROP_BITRATE, + g_param_spec_uint ("bitrate", "Bitrate", + "Target bitrate in kbits/second. " + "Used for \"cbr\", \"vbr\", and \"qvbr\" rate control", + 0, G_MAXUINT, DEFAULT_BITRATE, rw_params)); + + g_object_class_install_property (object_class, PROP_MAX_BITRATE, + g_param_spec_uint ("max-bitrate", "Max Bitrate", + "Peak bitrate in kbits/second. " + "Used for \"vbr\", and \"qvbr\" rate control", + 0, G_MAXUINT, DEFAULT_MAX_BITRATE, rw_params)); + + g_object_class_install_property (object_class, PROP_QVBR_QUALITY, + g_param_spec_uint ("qvbr-quality", "QVBR Quality", + "Constant quality target value for \"qvbr\" rate control", + 0, 51, DEFAULT_QVBR_QUALITY, rw_params)); + + g_object_class_install_property (object_class, PROP_QP_INIT, + g_param_spec_uint ("qp-init", "QP Init", + "Initial QP value. " + "Used for \"cbr\", \"vbr\", and \"qvbr\" rate control", + 0, 51, DEFAULT_QP, rw_params)); + + g_object_class_install_property (object_class, PROP_QP_MIN, + g_param_spec_uint ("qp-min", "QP Min", + "Minimum QP value for \"cbr\", \"vbr\", and \"qvbr\" rate control. " + "To enable min/max QP setting, \"qp-max >= qp-min > 0\" " + "condition should be satisfied", 0, 51, DEFAULT_QP, rw_params)); + + g_object_class_install_property (object_class, PROP_QP_MAX, + g_param_spec_uint ("qp-max", "QP Max", + "Maximum QP value for \"cbr\", \"vbr\", and \"qvbr\" rate control. " + "To enable min/max QP setting, \"qp-max >= qp-min > 0\" " + "condition should be satisfied", 0, 51, DEFAULT_QP, rw_params)); + + g_object_class_install_property (object_class, PROP_QP_I, + g_param_spec_uint ("qp-i", "QP I", + "Constant QP value for I frames. Used for \"cqp\" rate control", + 1, 51, DEFAULT_CQP, rw_params)); + + g_object_class_install_property (object_class, PROP_QP_P, + g_param_spec_uint ("qp-p", "QP P", + "Constant QP value for P frames. Used for \"cqp\" rate control", + 1, 51, DEFAULT_CQP, rw_params)); + + g_object_class_install_property (object_class, PROP_QP_I, + g_param_spec_uint ("qp-b", "QP B", + "Constant QP value for B frames. Used for \"cqp\" rate control", + 1, 51, DEFAULT_CQP, rw_params)); + + g_object_class_install_property (object_class, PROP_SLICE_MODE, + g_param_spec_enum ("slice-mode", "Slice Mode", + "Slice partiton mode", GST_TYPE_D3D12_ENCODER_SUBREGION_LAYOUT, + DEFAULT_SLICE_MODE, rw_params)); + + g_object_class_install_property (object_class, PROP_SLICE_PARTITION, + g_param_spec_uint ("slice-partition", "Slice partition", + "Slice partition threshold interpreted depending on \"slice-mode\". " + "If set zero, full frame encoding will be selected without " + "partitioning regardless of requested \"slice-mode\"", + 0, G_MAXUINT, DEFAULT_SLICE_PARTITION, rw_params)); + + g_object_class_install_property (object_class, PROP_CC_INSERT, + g_param_spec_enum ("cc-insert", "Closed Caption Insert", + "Closed Caption insert mode", GST_TYPE_D3D12_ENCODER_SEI_INSERT_MODE, + DEFAULT_CC_INSERT, rw_params)); + + std::string long_name = "Direct3D12 H.264 " + std::string (cdata->description) + + " Encoder"; + gst_element_class_set_metadata (element_class, long_name.c_str (), + "Codec/Encoder/Video/Hardware", "Direct3D12 H.264 Video Encoder", + "Seungha Yang "); + + pad_templ = gst_pad_template_new ("sink", + GST_PAD_SINK, GST_PAD_ALWAYS, cdata->sink_caps); + gst_element_class_add_pad_template (element_class, pad_templ); + + pad_templ = gst_pad_template_new ("src", + GST_PAD_SRC, GST_PAD_ALWAYS, cdata->src_caps); + gst_element_class_add_pad_template (element_class, pad_templ); + + encoder_class->start = GST_DEBUG_FUNCPTR (gst_d3d12_h264_enc_start); + encoder_class->stop = GST_DEBUG_FUNCPTR (gst_d3d12_h264_enc_stop); + encoder_class->transform_meta = + GST_DEBUG_FUNCPTR (gst_d3d12_h264_enc_transform_meta); + + d3d12enc_class->adapter_luid = cdata->luid; + d3d12enc_class->device_id = cdata->device_id; + d3d12enc_class->vendor_id = cdata->vendor_id; + d3d12enc_class->new_sequence = + GST_DEBUG_FUNCPTR (gst_d3d12_h264_enc_new_sequence); + d3d12enc_class->start_frame = + GST_DEBUG_FUNCPTR (gst_d3d12_h264_enc_start_frame); + d3d12enc_class->end_frame = GST_DEBUG_FUNCPTR (gst_d3d12_h264_enc_end_frame); +} + +static void +gst_d3d12_h264_enc_init (GstD3D12H264Enc * self) +{ + self->priv = new GstD3D12H264EncPrivate (); +} + +static void +gst_d3d12_h264_enc_finalize (GObject * object) +{ + auto self = GST_D3D12_H264_ENC (object); + + delete self->priv; + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_d3d12_h264_enc_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + auto self = GST_D3D12_H264_ENC (object); + auto priv = self->priv; + + std::lock_guard < std::mutex > lk (priv->prop_lock); + switch (prop_id) { + case PROP_AUD: + priv->aud = g_value_get_boolean (value); + break; + case PROP_GOP_SIZE: + { + auto gop_size = g_value_get_uint (value); + if (gop_size != priv->gop_size) { + priv->gop_size = gop_size; + priv->gop_updated = TRUE; + } + break; + } + case PROP_REF_FRAMES: + { + auto ref_frames = g_value_get_uint (value); + if (ref_frames != priv->ref_frames) { + priv->ref_frames = ref_frames; + priv->gop_updated = TRUE; + } + break; + } + case PROP_FRAME_ANALYSIS: + { + auto frame_analysis = g_value_get_boolean (value); + if (frame_analysis != priv->frame_analysis) { + priv->frame_analysis = frame_analysis; + priv->rc_updated = TRUE; + } + break; + } + case PROP_RATE_CONTROL: + { + auto mode = (D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE) + g_value_get_enum (value); + if (mode != priv->rc_mode) { + priv->rc_mode = mode; + priv->rc_updated = TRUE; + } + break; + } + case PROP_BITRATE: + { + auto bitrate = g_value_get_uint (value); + if (bitrate == 0) + bitrate = DEFAULT_BITRATE; + + if (bitrate != priv->bitrate) { + priv->bitrate = bitrate; + if (priv->selected_rc_mode != D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CQP) + priv->rc_updated = TRUE; + } + break; + } + case PROP_MAX_BITRATE: + { + auto max_bitrate = g_value_get_uint (value); + if (max_bitrate == 0) + max_bitrate = DEFAULT_MAX_BITRATE; + + if (max_bitrate != priv->max_bitrate) { + priv->max_bitrate = max_bitrate; + switch (priv->selected_rc_mode) { + case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR: + case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR: + priv->rc_updated = TRUE; + break; + default: + break; + } + } + break; + } + case PROP_QVBR_QUALITY: + { + auto qvbr_quality = g_value_get_uint (value); + if (qvbr_quality != priv->qvbr_quality) { + priv->qvbr_quality = qvbr_quality; + if (priv->selected_rc_mode == + D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR) { + priv->rc_updated = TRUE; + } + } + break; + } + case PROP_QP_INIT: + { + auto qp_init = g_value_get_uint (value); + if (qp_init != priv->qp_init) { + priv->qp_init = qp_init; + switch (priv->selected_rc_mode) { + case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CBR: + case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR: + case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR: + priv->rc_updated = TRUE; + break; + default: + break; + } + } + break; + } + case PROP_QP_MIN: + { + auto qp_min = g_value_get_uint (value); + if (qp_min != priv->qp_min) { + priv->qp_min = qp_min; + switch (priv->selected_rc_mode) { + case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CBR: + case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR: + case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR: + priv->rc_updated = TRUE; + break; + default: + break; + } + } + break; + } + case PROP_QP_MAX: + { + auto qp_max = g_value_get_uint (value); + if (qp_max != priv->qp_max) { + priv->qp_max = qp_max; + switch (priv->selected_rc_mode) { + case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CBR: + case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR: + case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR: + priv->rc_updated = TRUE; + break; + default: + break; + } + } + break; + } + case PROP_QP_I: + { + auto qp_i = g_value_get_uint (value); + if (qp_i != priv->qp_i) { + priv->qp_i = qp_i; + if (priv->selected_rc_mode == D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CQP) + priv->rc_updated = TRUE; + } + break; + } + case PROP_QP_P: + { + auto qp_p = g_value_get_uint (value); + if (qp_p != priv->qp_p) { + priv->qp_p = qp_p; + if (priv->selected_rc_mode == D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CQP) + priv->rc_updated = TRUE; + } + break; + } + case PROP_QP_B: + { + auto qp_b = g_value_get_uint (value); + if (qp_b != priv->qp_b) { + priv->qp_b = qp_b; + if (priv->selected_rc_mode == D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CQP) + priv->rc_updated = TRUE; + } + break; + } + case PROP_SLICE_MODE: + { + auto slice_mode = (D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE) + g_value_get_enum (value); + if (slice_mode != priv->slice_mode) { + priv->slice_mode = slice_mode; + if (priv->selected_slice_mode != slice_mode) + priv->slice_updated = TRUE; + } + break; + } + case PROP_SLICE_PARTITION: + { + auto slice_partition = g_value_get_uint (value); + if (slice_partition != priv->slice_partition) { + priv->slice_partition = slice_partition; + if (priv->selected_slice_mode != + D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_FULL_FRAME) { + priv->slice_updated = TRUE; + } + } + break; + } + case PROP_CC_INSERT: + priv->cc_insert = (GstD3D12EncoderSeiInsertMode) g_value_get_enum (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_d3d12_h264_enc_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + auto self = GST_D3D12_H264_ENC (object); + auto priv = self->priv; + auto klass = GST_D3D12_H264_ENC_GET_CLASS (self); + const auto cdata = klass->cdata; + + std::lock_guard < std::mutex > lk (priv->prop_lock); + switch (prop_id) { + case PROP_RATE_CONTROL_SUPPORT: + g_value_set_flags (value, cdata->rc_support); + break; + case PROP_SLICE_MODE_SUPPORT: + g_value_set_flags (value, cdata->slice_mode_support); + break; + case PROP_AUD: + g_value_set_boolean (value, priv->aud); + break; + case PROP_GOP_SIZE: + g_value_set_uint (value, priv->gop_size); + break; + case PROP_REF_FRAMES: + g_value_set_uint (value, priv->ref_frames); + break; + case PROP_FRAME_ANALYSIS: + g_value_set_boolean (value, priv->frame_analysis); + break; + case PROP_RATE_CONTROL: + g_value_set_enum (value, priv->rc_mode); + break; + case PROP_BITRATE: + g_value_set_uint (value, priv->bitrate); + break; + case PROP_MAX_BITRATE: + g_value_set_uint (value, priv->max_bitrate); + break; + case PROP_QVBR_QUALITY: + g_value_set_uint (value, priv->qvbr_quality); + break; + case PROP_QP_INIT: + g_value_set_uint (value, priv->qp_init); + break; + case PROP_QP_MIN: + g_value_set_uint (value, priv->qp_min); + break; + case PROP_QP_MAX: + g_value_set_uint (value, priv->qp_max); + break; + case PROP_QP_I: + g_value_set_uint (value, priv->qp_i); + break; + case PROP_QP_P: + g_value_set_uint (value, priv->qp_p); + break; + case PROP_QP_B: + g_value_set_uint (value, priv->qp_p); + break; + case PROP_SLICE_MODE: + g_value_set_enum (value, priv->slice_mode); + break; + case PROP_SLICE_PARTITION: + g_value_set_uint (value, priv->slice_partition); + break; + case PROP_CC_INSERT: + g_value_set_enum (value, priv->cc_insert); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static gboolean +gst_d3d12_h264_enc_start (GstVideoEncoder * encoder) +{ + auto self = GST_D3D12_H264_ENC (encoder); + auto priv = self->priv; + + GST_DEBUG_OBJECT (self, "Start"); + + priv->display_order = 0; + + return GST_VIDEO_ENCODER_CLASS (parent_class)->start (encoder); +} + +static gboolean +gst_d3d12_h264_enc_stop (GstVideoEncoder * encoder) +{ + auto self = GST_D3D12_H264_ENC (encoder); + auto priv = self->priv; + + GST_DEBUG_OBJECT (self, "Stop"); + + priv->dpb = nullptr; + + return GST_VIDEO_ENCODER_CLASS (parent_class)->stop (encoder); +} + +static gboolean +gst_d3d12_h264_enc_transform_meta (GstVideoEncoder * encoder, + GstVideoCodecFrame * frame, GstMeta * meta) +{ + auto self = GST_D3D12_H264_ENC (encoder); + auto priv = self->priv; + + if (meta->info->api == GST_VIDEO_CAPTION_META_API_TYPE) { + std::lock_guard < std::mutex > lk (priv->prop_lock); + if (priv->cc_insert == GST_D3D12_ENCODER_SEI_INSERT_AND_DROP) { + auto cc_meta = (GstVideoCaptionMeta *) meta; + if (cc_meta->caption_type == GST_VIDEO_CAPTION_TYPE_CEA708_RAW) + return FALSE; + } + } + + return GST_VIDEO_ENCODER_CLASS (parent_class)->transform_meta (encoder, + frame, meta); +} + +static gboolean +gst_d3d12_h264_enc_build_sps (GstD3D12H264Enc * self, const GstVideoInfo * info, + const D3D12_VIDEO_ENCODER_PICTURE_RESOLUTION_DESC * resolution, + guint num_ref) +{ + auto priv = self->priv; + guint8 sps_buf[4096] = { 0, }; + /* *INDENT-OFF* */ + static const std::unordered_map + level_map = { + {D3D12_VIDEO_ENCODER_LEVELS_H264_1, GST_H264_LEVEL_L1}, + {D3D12_VIDEO_ENCODER_LEVELS_H264_1b, GST_H264_LEVEL_L1B}, + {D3D12_VIDEO_ENCODER_LEVELS_H264_11, GST_H264_LEVEL_L1_1}, + {D3D12_VIDEO_ENCODER_LEVELS_H264_12, GST_H264_LEVEL_L1_2}, + {D3D12_VIDEO_ENCODER_LEVELS_H264_13, GST_H264_LEVEL_L1_3}, + {D3D12_VIDEO_ENCODER_LEVELS_H264_2, GST_H264_LEVEL_L2}, + {D3D12_VIDEO_ENCODER_LEVELS_H264_21, GST_H264_LEVEL_L2_1}, + {D3D12_VIDEO_ENCODER_LEVELS_H264_22, GST_H264_LEVEL_L2_2}, + {D3D12_VIDEO_ENCODER_LEVELS_H264_3, GST_H264_LEVEL_L3}, + {D3D12_VIDEO_ENCODER_LEVELS_H264_31, GST_H264_LEVEL_L3_1}, + {D3D12_VIDEO_ENCODER_LEVELS_H264_32, GST_H264_LEVEL_L3_2}, + {D3D12_VIDEO_ENCODER_LEVELS_H264_4, GST_H264_LEVEL_L4}, + {D3D12_VIDEO_ENCODER_LEVELS_H264_41, GST_H264_LEVEL_L4_1}, + {D3D12_VIDEO_ENCODER_LEVELS_H264_42, GST_H264_LEVEL_L4_2}, + {D3D12_VIDEO_ENCODER_LEVELS_H264_5, GST_H264_LEVEL_L5}, + {D3D12_VIDEO_ENCODER_LEVELS_H264_51, GST_H264_LEVEL_L5_1}, + {D3D12_VIDEO_ENCODER_LEVELS_H264_52, GST_H264_LEVEL_L5_2}, + {D3D12_VIDEO_ENCODER_LEVELS_H264_6, GST_H264_LEVEL_L6}, + {D3D12_VIDEO_ENCODER_LEVELS_H264_61, GST_H264_LEVEL_L6_1}, + {D3D12_VIDEO_ENCODER_LEVELS_H264_62, GST_H264_LEVEL_L6_2}, + }; + static const std::array, 17> par_map {{ + {0, 0}, + {1, 1}, + {12, 11}, + {10, 11}, + {16, 11}, + {40, 33}, + {24, 11}, + {20, 11}, + {32, 11}, + {80, 33}, + {18, 11}, + {15, 11}, + {64, 33}, + {160, 99}, + {4, 3}, + {3, 2}, + {2, 1} + }}; + /* *INDENT-ON* */ + + priv->sps.Clear (); + auto & sps = priv->sps.sps; + sps.id = 0; + sps.profile_idc = priv->selected_profile; + switch (sps.profile_idc) { + case GST_H264_PROFILE_BASELINE: + sps.constraint_set0_flag = 1; + sps.constraint_set1_flag = 1; + break; + case GST_H264_PROFILE_MAIN: + sps.constraint_set1_flag = 1; + break; + default: + break; + } + + if (priv->level_h264 == D3D12_VIDEO_ENCODER_LEVELS_H264_1b) + sps.constraint_set3_flag = 1; + + sps.level_idc = level_map.at (priv->level_h264); + /* d3d12 supports only 4:2:0 encoding */ + sps.chroma_format_idc = 1; + sps.separate_colour_plane_flag = 0; + /* add high-10 profile support */ + sps.bit_depth_luma_minus8 = 0; + sps.bit_depth_chroma_minus8 = 0; + sps.qpprime_y_zero_transform_bypass_flag = 0; + sps.scaling_matrix_present_flag = 0; + sps.log2_max_frame_num_minus4 = + priv->gop_struct_h264.log2_max_frame_num_minus4; + sps.pic_order_cnt_type = priv->gop_struct_h264.pic_order_cnt_type; + sps.log2_max_pic_order_cnt_lsb_minus4 = + priv->gop_struct_h264.log2_max_pic_order_cnt_lsb_minus4; + sps.num_ref_frames = num_ref; + sps.gaps_in_frame_num_value_allowed_flag = 0; + sps.pic_width_in_mbs_minus1 = (resolution->Width / 16) - 1; + sps.pic_height_in_map_units_minus1 = (resolution->Height / 16) - 1; + sps.frame_mbs_only_flag = 1; + + if (sps.profile_idc != GST_H264_PROFILE_BASELINE) + sps.direct_8x8_inference_flag = 1; + + if (resolution->Width != (UINT) info->width || + resolution->Height != (UINT) info->height) { + sps.frame_cropping_flag = 1; + sps.frame_crop_left_offset = 0; + sps.frame_crop_right_offset = (resolution->Width - info->width) / 2; + sps.frame_crop_top_offset = 0; + sps.frame_crop_bottom_offset = (resolution->Height - info->height) / 2; + } + + sps.vui_parameters_present_flag = 1; + auto & vui = sps.vui_parameters; + const auto colorimetry = &info->colorimetry; + + if (info->par_n > 0 && info->par_d > 0) { + const auto it = std::find_if (par_map.begin (), + par_map.end (),[&](const auto & par) { + return par.first == info->par_n && par.second == info->par_d; + } + ); + + if (it != par_map.end ()) { + vui.aspect_ratio_info_present_flag = 1; + vui.aspect_ratio_idc = (guint8) std::distance (par_map.begin (), it); + } else if (info->par_n <= G_MAXUINT16 && info->par_d <= G_MAXUINT16) { + vui.aspect_ratio_info_present_flag = 1; + vui.aspect_ratio_idc = 0xff; + vui.sar_width = info->par_n; + vui.sar_height = info->par_d; + } + } + + vui.video_signal_type_present_flag = 1; + /* Unspecified */ + vui.video_format = 5; + vui.video_full_range_flag = + colorimetry->range == GST_VIDEO_COLOR_RANGE_0_255 ? 1 : 0; + vui.colour_description_present_flag = 1; + vui.colour_primaries = + gst_video_color_primaries_to_iso (colorimetry->primaries); + vui.transfer_characteristics = + gst_video_transfer_function_to_iso (colorimetry->transfer); + vui.matrix_coefficients = gst_video_color_matrix_to_iso (colorimetry->matrix); + if (info->fps_n > 0 && info->fps_d > 0) { + vui.timing_info_present_flag = 1; + vui.num_units_in_tick = info->fps_d; + vui.time_scale = 2 * (guint) info->fps_n; + } + + /* TODO: pic_struct_present_flag = 1 for picture timeing SEI */ + + guint nal_size = G_N_ELEMENTS (sps_buf); + GstH264BitWriterResult write_ret = + gst_h264_bit_writer_sps (&sps, TRUE, sps_buf, &nal_size); + if (write_ret != GST_H264_BIT_WRITER_OK) { + GST_ERROR_OBJECT (self, "Couldn't build SPS"); + return FALSE; + } + + priv->sps.bytes.resize (G_N_ELEMENTS (sps_buf)); + guint written_size = priv->sps.bytes.size (); + write_ret = gst_h264_bit_writer_convert_to_nal (4, FALSE, TRUE, FALSE, + sps_buf, nal_size * 8, priv->sps.bytes.data (), &written_size); + if (write_ret != GST_H264_BIT_WRITER_OK) { + GST_ERROR_OBJECT (self, "Couldn't build SPS bytes"); + return FALSE; + } + priv->sps.bytes.resize (written_size); + + return TRUE; +} + +static gboolean +gst_d3d12_h264_enc_build_pps (GstD3D12H264Enc * self, guint num_ref) +{ + auto priv = self->priv; + + /* Driver does not seem to use num_ref_idx_active_override_flag. + * Needs multiple PPS to signal ref pics */ + /* TODO: make more PPS for L1 ref pics */ + guint num_pps = MAX (1, num_ref); + priv->pps.resize (num_pps); + for (size_t i = 0; i < priv->pps.size (); i++) { + guint8 pps_buf[1024] = { 0, }; + auto & d3d12_pps = priv->pps[i]; + d3d12_pps.Clear (); + + auto & pps = d3d12_pps.pps; + + pps.id = i; + pps.sequence = &priv->sps.sps; + if ((priv->config_h264.ConfigurationFlags & + D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264_FLAG_ENABLE_CABAC_ENCODING) + != 0) { + pps.entropy_coding_mode_flag = 1; + } else { + pps.entropy_coding_mode_flag = 0; + } + + pps.pic_order_present_flag = 0; + pps.num_slice_groups_minus1 = 0; + pps.num_ref_idx_l0_active_minus1 = i; + pps.num_ref_idx_l1_active_minus1 = 0; + pps.weighted_pred_flag = 0; + pps.weighted_bipred_idc = 0; + pps.pic_init_qp_minus26 = 0; + pps.pic_init_qs_minus26 = 0; + pps.chroma_qp_index_offset = 0; + pps.deblocking_filter_control_present_flag = 1; + if ((priv->config_h264.ConfigurationFlags & + D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264_FLAG_USE_CONSTRAINED_INTRAPREDICTION) + != 0) { + pps.constrained_intra_pred_flag = 1; + } else { + pps.constrained_intra_pred_flag = 0; + } + pps.redundant_pic_cnt_present_flag = 0; + if ((priv->config_h264.ConfigurationFlags & + D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264_FLAG_USE_ADAPTIVE_8x8_TRANSFORM) + != 0) { + /* double check. This is not allowed for baseline and main profiles */ + pps.transform_8x8_mode_flag = 1; + } else { + pps.transform_8x8_mode_flag = 0; + } + + pps.pic_scaling_matrix_present_flag = 0; + pps.second_chroma_qp_index_offset = 0; + + guint nal_size = G_N_ELEMENTS (pps_buf); + d3d12_pps.bytes.resize (nal_size); + GstH264BitWriterResult write_ret = + gst_h264_bit_writer_pps (&pps, TRUE, pps_buf, &nal_size); + if (write_ret != GST_H264_BIT_WRITER_OK) { + GST_ERROR_OBJECT (self, "Couldn't build PPS"); + return FALSE; + } + + guint written_size = d3d12_pps.bytes.size (); + write_ret = gst_h264_bit_writer_convert_to_nal (4, FALSE, TRUE, FALSE, + pps_buf, nal_size * 8, d3d12_pps.bytes.data (), &written_size); + if (write_ret != GST_H264_BIT_WRITER_OK) { + GST_ERROR_OBJECT (self, "Couldn't build PPS bytes"); + return FALSE; + } + + d3d12_pps.bytes.resize (written_size); + } + + return TRUE; +} + +static gboolean +gst_d3d12_h264_enc_get_max_ref_frames (GstD3D12H264Enc * self) +{ + auto priv = self->priv; + const auto & pic_ctrl_support = priv->pic_ctrl_support; + + guint max_ref_frames = MIN (pic_ctrl_support.MaxL0ReferencesForP, + pic_ctrl_support.MaxDPBCapacity); + guint ref_frames = priv->ref_frames; + + if (max_ref_frames == 0) { + GST_INFO_OBJECT (self, + "Hardware does not support inter prediction, forcing all-intra"); + ref_frames = 0; + } else if (priv->gop_size == 1) { + GST_INFO_OBJECT (self, "User requested all-intra coding"); + ref_frames = 0; + } else { + /* TODO: at least 2 ref frames if B frame is enabled */ + if (ref_frames != 0) + ref_frames = MIN (ref_frames, max_ref_frames); + else + ref_frames = 1; + } + + return ref_frames; +} + +static gboolean +gst_d3d12_h264_enc_update_gop (GstD3D12H264Enc * self, + ID3D12VideoDevice * video_device, GstD3D12EncoderConfig * config, + D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAGS * seq_flags) +{ + auto priv = self->priv; + + if (seq_flags && !priv->gop_updated) + return TRUE; + + auto ref_frames = gst_d3d12_h264_enc_get_max_ref_frames (self); + auto gop_size = priv->gop_size; + if (ref_frames == 0) + gop_size = 1; + + priv->last_pps_id = 0; + + auto prev_gop_struct = priv->gop.GetGopStruct (); + auto prev_ref_frames = priv->selected_ref_frames; + + priv->selected_ref_frames = ref_frames; + priv->gop.Init (gop_size); + priv->gop_struct_h264 = priv->gop.GetGopStruct (); + + if (seq_flags) { + if (prev_ref_frames != ref_frames || + memcmp (&prev_gop_struct, &priv->gop_struct_h264, + sizeof (priv->gop_struct_h264)) != 0) { + GST_DEBUG_OBJECT (self, "Gop struct updated"); + *seq_flags |= + D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_GOP_SEQUENCE_CHANGE; + } + } + + GST_DEBUG_OBJECT (self, + "Configured GOP struct, GOPLength: %u, PPicturePeriod: %u, " + "pic_order_cnt_type: %d, log2_max_frame_num_minus4: %d, " + "log2_max_pic_order_cnt_lsb_minus4: %d", priv->gop_struct_h264.GOPLength, + priv->gop_struct_h264.PPicturePeriod, + priv->gop_struct_h264.pic_order_cnt_type, + priv->gop_struct_h264.log2_max_frame_num_minus4, + priv->gop_struct_h264.log2_max_pic_order_cnt_lsb_minus4); + + priv->gop_updated = FALSE; + + return TRUE; +} + +/* called with prop_lock taken */ +static gboolean +gst_d3d12_h264_enc_update_rate_control (GstD3D12H264Enc * self, + ID3D12VideoDevice * video_device, GstD3D12EncoderConfig * config, + D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAGS * seq_flags) +{ + auto priv = self->priv; + const D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE rc_modes[] = { + D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR, + D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR, + D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CBR, + D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CQP, + }; + + if (seq_flags && !priv->rc_updated) + return TRUE; + + GstD3D12EncoderConfig prev_config = *config; + + config->rate_control.Flags = D3D12_VIDEO_ENCODER_RATE_CONTROL_FLAG_NONE; + UINT64 bitrate = priv->bitrate; + if (bitrate == 0) + bitrate = DEFAULT_BITRATE; + + UINT64 max_bitrate = priv->max_bitrate; + if (max_bitrate < bitrate) { + if (bitrate >= G_MAXUINT64 / 2) + max_bitrate = bitrate; + else + max_bitrate = bitrate * 2; + } + + /* Property uses kbps, and API uses bps */ + bitrate *= 1000; + max_bitrate *= 1000; + + /* Fill every rate control struct and select later */ + config->cqp.ConstantQP_FullIntracodedFrame = priv->qp_i; + config->cqp.ConstantQP_InterPredictedFrame_PrevRefOnly = priv->qp_p; + config->cqp.ConstantQP_InterPredictedFrame_BiDirectionalRef = priv->qp_b; + + config->cbr.InitialQP = priv->qp_init; + config->cbr.MinQP = priv->qp_min; + config->cbr.MaxQP = priv->qp_max; + config->cbr.TargetBitRate = bitrate; + + config->vbr.InitialQP = priv->qp_init; + config->vbr.MinQP = priv->qp_min; + config->vbr.MaxQP = priv->qp_max; + config->vbr.TargetAvgBitRate = bitrate; + config->vbr.PeakBitRate = max_bitrate; + + config->qvbr.InitialQP = priv->qp_init; + config->qvbr.MinQP = priv->qp_min; + config->qvbr.MaxQP = priv->qp_max; + config->qvbr.TargetAvgBitRate = bitrate; + config->qvbr.PeakBitRate = max_bitrate; + config->qvbr.ConstantQualityTarget = priv->qvbr_quality; + + D3D12_FEATURE_DATA_VIDEO_ENCODER_RATE_CONTROL_MODE feature_data = { }; + feature_data.Codec = D3D12_VIDEO_ENCODER_CODEC_H264; + feature_data.RateControlMode = priv->rc_mode; + + auto hr = video_device->CheckFeatureSupport + (D3D12_FEATURE_VIDEO_ENCODER_RATE_CONTROL_MODE, + &feature_data, sizeof (feature_data)); + if (SUCCEEDED (hr) && feature_data.IsSupported) { + priv->selected_rc_mode = priv->rc_mode; + } else { + GST_INFO_OBJECT (self, "Requested rate control mode is not supported"); + + for (guint i = 0; i < G_N_ELEMENTS (rc_modes); i++) { + feature_data.RateControlMode = rc_modes[i]; + hr = video_device->CheckFeatureSupport + (D3D12_FEATURE_VIDEO_ENCODER_RATE_CONTROL_MODE, &feature_data, + sizeof (feature_data)); + if (SUCCEEDED (hr) && feature_data.IsSupported) { + priv->selected_rc_mode = rc_modes[i]; + break; + } else { + feature_data.IsSupported = FALSE; + } + } + + if (!feature_data.IsSupported) { + GST_ERROR_OBJECT (self, "Couldn't find support rate control mode"); + return FALSE; + } + } + + GST_INFO_OBJECT (self, "Requested rate control mode %d, selected %d", + priv->rc_mode, priv->selected_rc_mode); + + config->rate_control.Mode = priv->selected_rc_mode; + switch (priv->selected_rc_mode) { + case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CQP: + config->rate_control.ConfigParams.DataSize = sizeof (config->cqp); + config->rate_control.ConfigParams.pConfiguration_CQP = &config->cqp; + break; + case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CBR: + config->rate_control.ConfigParams.DataSize = sizeof (config->cbr); + config->rate_control.ConfigParams.pConfiguration_CBR = &config->cbr; + break; + case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR: + config->rate_control.ConfigParams.DataSize = sizeof (config->vbr); + config->rate_control.ConfigParams.pConfiguration_VBR = &config->vbr; + break; + case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR: + config->rate_control.ConfigParams.DataSize = sizeof (config->qvbr); + config->rate_control.ConfigParams.pConfiguration_QVBR = &config->qvbr; + break; + default: + g_assert_not_reached (); + return FALSE; + } + + if (seq_flags) { + if (prev_config.rate_control.Mode != config->rate_control.Mode) { + GST_DEBUG_OBJECT (self, "Rate control mode changed %d -> %d", + prev_config.rate_control.Mode, config->rate_control.Mode); + *seq_flags |= + D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_RATE_CONTROL_CHANGE; + } else { + void *prev, *cur; + switch (config->rate_control.Mode) { + case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CQP: + prev = &prev_config.cqp; + cur = &config->cqp; + break; + case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CBR: + prev = &prev_config.cbr; + cur = &config->cbr; + break; + case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR: + prev = &prev_config.vbr; + cur = &config->vbr; + break; + case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR: + prev = &prev_config.qvbr; + cur = &config->cbr; + break; + default: + g_assert_not_reached (); + return FALSE; + } + + if (memcmp (prev, cur, config->rate_control.ConfigParams.DataSize) != 0) { + GST_DEBUG_OBJECT (self, "Rate control params updated"); + *seq_flags |= + D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_RATE_CONTROL_CHANGE; + } + } + } + + priv->rc_updated = FALSE; + + return TRUE; +} + +static gboolean +gst_d3d12_h264_enc_update_slice (GstD3D12H264Enc * self, + ID3D12VideoDevice * video_device, GstD3D12EncoderConfig * config, + D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAGS * seq_flags, + D3D12_VIDEO_ENCODER_SUPPORT_FLAGS * support_flags) +{ + auto priv = self->priv; + + if (seq_flags && !priv->slice_updated) + return TRUE; + + auto encoder = GST_D3D12_ENCODER (self); + auto prev_mode = priv->selected_slice_mode; + auto prev_slice = priv->layout_slices; + + priv->selected_slice_mode = + D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_FULL_FRAME; + priv->layout_slices.NumberOfSlicesPerFrame = 1; + config->max_subregions = 1; + + D3D12_FEATURE_DATA_VIDEO_ENCODER_SUPPORT support = { }; + D3D12_FEATURE_DATA_VIDEO_ENCODER_RESOLUTION_SUPPORT_LIMITS limits = { }; + D3D12_VIDEO_ENCODER_PROFILE_H264 suggested_profile = priv->profile_h264; + D3D12_VIDEO_ENCODER_LEVELS_H264 suggested_level; + + support.Codec = D3D12_VIDEO_ENCODER_CODEC_H264; + support.InputFormat = DXGI_FORMAT_NV12; + support.CodecConfiguration = config->codec_config; + support.CodecGopSequence = config->gop_struct; + support.RateControl = config->rate_control; + /* TODO: add intra-refresh support */ + support.IntraRefresh = D3D12_VIDEO_ENCODER_INTRA_REFRESH_MODE_NONE; + support.ResolutionsListCount = 1; + support.pResolutionList = &config->resolution; + support.MaxReferenceFramesInDPB = priv->selected_ref_frames; + support.pResolutionDependentSupport = &limits; + support.SuggestedProfile.DataSize = sizeof (suggested_profile); + support.SuggestedProfile.pH264Profile = &suggested_profile; + support.SuggestedLevel.DataSize = sizeof (suggested_level); + support.SuggestedLevel.pH264LevelSetting = &suggested_level; + + HRESULT hr; + if (priv->slice_mode != + D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_FULL_FRAME + && priv->slice_partition > 0) { + /* TODO: fallback to other mode if possible */ + D3D12_FEATURE_DATA_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE + feature_layout = { }; + feature_layout.Codec = D3D12_VIDEO_ENCODER_CODEC_H264; + feature_layout.Profile = config->profile_desc; + feature_layout.Level = config->level; + feature_layout.SubregionMode = priv->slice_mode; + hr = video_device->CheckFeatureSupport + (D3D12_FEATURE_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE, + &feature_layout, sizeof (feature_layout)); + if (!gst_d3d12_result (hr, encoder->device) || !feature_layout.IsSupported) { + GST_WARNING_OBJECT (self, "Requested slice mode is not supported"); + } else { + support.SubregionFrameEncoding = priv->slice_mode; + hr = video_device->CheckFeatureSupport + (D3D12_FEATURE_VIDEO_ENCODER_SUPPORT, &support, sizeof (support)); + if (gst_d3d12_result (hr, encoder->device) + && CHECK_SUPPORT_FLAG (support.SupportFlags, GENERAL_SUPPORT_OK) + && support.ValidationFlags == D3D12_VIDEO_ENCODER_VALIDATION_FLAG_NONE + && limits.MaxSubregionsNumber > 1 + && limits.SubregionBlockPixelsSize > 0) { + switch (priv->slice_mode) { + case D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_BYTES_PER_SUBREGION: + priv->selected_slice_mode = + priv->slice_mode; + /* Don't know how many slices would be generated */ + config->max_subregions = limits.MaxSubregionsNumber; + *support_flags = support.SupportFlags; + break; + case D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_SQUARE_UNITS_PER_SUBREGION_ROW_UNALIGNED: + { + auto total_mbs = + (config->resolution.Width / limits.SubregionBlockPixelsSize) * + (config->resolution.Height / limits.SubregionBlockPixelsSize); + if (priv->slice_partition >= total_mbs) { + GST_DEBUG_OBJECT (self, + "Requested MBs per slice exceeds total MBs per frame"); + } else { + priv->selected_slice_mode = priv->slice_mode; + + auto min_mbs_per_slice = (guint) std::ceil ((float) total_mbs + / limits.MaxSubregionsNumber); + + if (min_mbs_per_slice > priv->slice_partition) { + GST_WARNING_OBJECT (self, "Too small number of MBs per slice"); + priv->layout_slices.NumberOfCodingUnitsPerSlice = + min_mbs_per_slice; + config->max_subregions = limits.MaxSubregionsNumber; + } else { + priv->layout_slices.NumberOfCodingUnitsPerSlice = + priv->slice_partition; + config->max_subregions = std::ceil ((float) total_mbs + / priv->slice_partition); + } + + *support_flags = support.SupportFlags; + } + break; + } + case D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_UNIFORM_PARTITIONING_ROWS_PER_SUBREGION: + { + auto total_rows = config->resolution.Height / + limits.SubregionBlockPixelsSize; + if (priv->slice_partition >= total_rows) { + GST_DEBUG_OBJECT (self, + "Requested rows per slice exceeds total rows per frame"); + } else { + priv->selected_slice_mode = priv->slice_mode; + + auto min_rows_per_slice = (guint) std::ceil ((float) total_rows + / limits.MaxSubregionsNumber); + + if (min_rows_per_slice > priv->slice_partition) { + GST_WARNING_OBJECT (self, "Too small number of rows per slice"); + priv->layout_slices.NumberOfRowsPerSlice = min_rows_per_slice; + config->max_subregions = limits.MaxSubregionsNumber; + } else { + priv->layout_slices.NumberOfRowsPerSlice = + priv->slice_partition; + config->max_subregions = (guint) std::ceil ((float) total_rows + / priv->slice_partition); + } + + *support_flags = support.SupportFlags; + } + break; + } + case D3D12_VIDEO_ENCODER_FRAME_SUBREGION_LAYOUT_MODE_UNIFORM_PARTITIONING_SUBREGIONS_PER_FRAME: + { + if (priv->slice_partition > 1) { + priv->selected_slice_mode = priv->slice_mode; + + if (priv->slice_partition > limits.MaxSubregionsNumber) { + GST_WARNING_OBJECT (self, "Too many slices per frame"); + priv->layout_slices.NumberOfSlicesPerFrame = + limits.MaxSubregionsNumber; + config->max_subregions = limits.MaxSubregionsNumber; + } else { + priv->layout_slices.NumberOfSlicesPerFrame = + priv->slice_partition; + config->max_subregions = priv->slice_partition; + } + + *support_flags = support.SupportFlags; + } + break; + } + default: + break; + } + } + } + } + + if (seq_flags && (prev_mode != priv->selected_slice_mode || + prev_slice.NumberOfSlicesPerFrame != + priv->layout_slices.NumberOfSlicesPerFrame)) { + GST_DEBUG_OBJECT (self, "Slice mode updated"); + *seq_flags |= + D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_SUBREGION_LAYOUT_CHANGE; + } + + priv->slice_updated = FALSE; + + return TRUE; +} + +static gboolean +gst_d3d12_h264_enc_reconfigure (GstD3D12H264Enc * self, + ID3D12VideoDevice * video_device, GstD3D12EncoderConfig * config, + D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAGS * seq_flags) +{ + auto encoder = GST_D3D12_ENCODER (self); + auto priv = self->priv; + auto prev_config = *config; + + if (!gst_d3d12_h264_enc_update_gop (self, video_device, config, seq_flags)) + return FALSE; + + if (!gst_d3d12_h264_enc_update_rate_control (self, + video_device, config, seq_flags)) { + return FALSE; + } + + D3D12_FEATURE_DATA_VIDEO_ENCODER_SUPPORT support = { }; + D3D12_FEATURE_DATA_VIDEO_ENCODER_RESOLUTION_SUPPORT_LIMITS limits = { }; + D3D12_VIDEO_ENCODER_PROFILE_H264 suggested_profile = priv->profile_h264; + + support.Codec = D3D12_VIDEO_ENCODER_CODEC_H264; + support.InputFormat = DXGI_FORMAT_NV12; + support.CodecConfiguration = config->codec_config; + support.CodecGopSequence = config->gop_struct; + support.RateControl = config->rate_control; + /* TODO: add intra-refresh support */ + support.IntraRefresh = D3D12_VIDEO_ENCODER_INTRA_REFRESH_MODE_NONE; + support.SubregionFrameEncoding = priv->selected_slice_mode; + support.ResolutionsListCount = 1; + support.pResolutionList = &config->resolution; + support.MaxReferenceFramesInDPB = priv->selected_ref_frames; + support.pResolutionDependentSupport = &limits; + support.SuggestedProfile.DataSize = sizeof (suggested_profile); + support.SuggestedProfile.pH264Profile = &suggested_profile; + support.SuggestedLevel = config->level; + + auto hr = video_device->CheckFeatureSupport + (D3D12_FEATURE_VIDEO_ENCODER_SUPPORT, &support, sizeof (support)); + + /* This is our minimum/simplest configuration + * TODO: negotiate again depending on validation flags */ + if (!gst_d3d12_result (hr, encoder->device) || + !CHECK_SUPPORT_FLAG (support.SupportFlags, GENERAL_SUPPORT_OK) || + (support.ValidationFlags != D3D12_VIDEO_ENCODER_VALIDATION_FLAG_NONE)) { + GST_ERROR_OBJECT (self, "Couldn't query encoder support"); + return FALSE; + } + + /* Update rate control flags based on support flags */ + if (priv->frame_analysis) { + if (CHECK_SUPPORT_FLAG (support.SupportFlags, + RATE_CONTROL_FRAME_ANALYSIS_AVAILABLE)) { + GST_INFO_OBJECT (self, "Frame analysis is enabled as requested"); + config->rate_control.Flags |= + D3D12_VIDEO_ENCODER_RATE_CONTROL_FLAG_ENABLE_FRAME_ANALYSIS; + } else { + GST_INFO_OBJECT (self, "Frame analysis is not supported"); + } + } + + if (priv->qp_init > 0) { + switch (priv->selected_rc_mode) { + case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CBR: + case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR: + case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR: + if (CHECK_SUPPORT_FLAG (support.SupportFlags, + RATE_CONTROL_INITIAL_QP_AVAILABLE)) { + GST_INFO_OBJECT (self, "Initial QP %d is enabled as requested", + priv->qp_init); + config->rate_control.Flags |= + D3D12_VIDEO_ENCODER_RATE_CONTROL_FLAG_ENABLE_INITIAL_QP; + } else { + GST_INFO_OBJECT (self, "Initial QP is not supported"); + } + break; + default: + break; + } + } + + if (priv->qp_max >= priv->qp_min && priv->qp_min > 0) { + switch (priv->selected_rc_mode) { + case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_CBR: + case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_VBR: + case D3D12_VIDEO_ENCODER_RATE_CONTROL_MODE_QVBR: + if (CHECK_SUPPORT_FLAG (support.SupportFlags, + RATE_CONTROL_ADJUSTABLE_QP_RANGE_AVAILABLE)) { + GST_INFO_OBJECT (self, "QP range [%d, %d] is enabled as requested", + priv->qp_min, priv->qp_max); + config->rate_control.Flags |= + D3D12_VIDEO_ENCODER_RATE_CONTROL_FLAG_ENABLE_QP_RANGE; + } else { + GST_INFO_OBJECT (self, "QP range is not supported"); + } + break; + default: + break; + } + } + + if (seq_flags) { + if (prev_config.rate_control.Flags != config->rate_control.Flags) { + GST_DEBUG_OBJECT (self, "Rate control flag updated"); + *seq_flags |= + D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_RATE_CONTROL_CHANGE; + } + } + + if (!gst_d3d12_h264_enc_update_slice (self, video_device, config, + seq_flags, &support.SupportFlags)) { + return FALSE; + } + + config->support_flags = support.SupportFlags; + + if (!seq_flags || + (*seq_flags & + D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_GOP_SEQUENCE_CHANGE) != 0) { + priv->gop.ForceKeyUnit (); + gst_d3d12_h264_enc_build_sps (self, &priv->info, &config->resolution, + priv->selected_ref_frames); + gst_d3d12_h264_enc_build_pps (self, priv->selected_ref_frames); + + bool array_of_textures = !CHECK_SUPPORT_FLAG (config->support_flags, + RECONSTRUCTED_FRAMES_REQUIRE_TEXTURE_ARRAYS); + auto dpb = std::make_unique < GstD3D12H264EncDpb > (encoder->device, + config->resolution.Width, config->resolution.Height, + priv->selected_ref_frames, + array_of_textures); + if (!dpb->IsValid ()) { + GST_ERROR_OBJECT (self, "Couldn't create dpb"); + return FALSE; + } + + GST_DEBUG_OBJECT (self, "New DPB configured"); + + priv->dpb = nullptr; + priv->dpb = std::move (dpb); + } + + return TRUE; +} + +static gboolean +gst_d3d12_h264_enc_new_sequence (GstD3D12Encoder * encoder, + ID3D12VideoDevice * video_device, GstVideoCodecState * state, + GstD3D12EncoderConfig * config) +{ + auto self = GST_D3D12_H264_ENC (encoder); + auto priv = self->priv; + auto info = &state->info; + + std::lock_guard < std::mutex > lk (priv->prop_lock); + + priv->dpb = nullptr; + priv->info = state->info; + + config->profile_desc.DataSize = sizeof (D3D12_VIDEO_ENCODER_PROFILE_H264); + config->profile_desc.pH264Profile = &priv->profile_h264; + + config->codec_config.DataSize = + sizeof (D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264); + config->codec_config.pH264Config = &priv->config_h264; + + config->level.DataSize = sizeof (D3D12_VIDEO_ENCODER_LEVELS_H264); + config->level.pH264LevelSetting = &priv->level_h264; + + config->layout.DataSize = + sizeof + (D3D12_VIDEO_ENCODER_PICTURE_CONTROL_SUBREGIONS_LAYOUT_DATA_SLICES); + config->layout.pSlicesPartition_H264 = &priv->layout_slices; + + config->gop_struct.DataSize = + sizeof (D3D12_VIDEO_ENCODER_SEQUENCE_GOP_STRUCTURE_H264); + config->gop_struct.pH264GroupOfPictures = &priv->gop_struct_h264; + + config->resolution.Width = GST_ROUND_UP_16 (info->width); + config->resolution.Height = GST_ROUND_UP_16 (info->height); + + priv->selected_profile = GST_H264_PROFILE_MAIN; + priv->profile_h264 = D3D12_VIDEO_ENCODER_PROFILE_H264_MAIN; + + auto allowed_caps = + gst_pad_get_allowed_caps (GST_VIDEO_ENCODER_SRC_PAD (encoder)); + if (allowed_caps && !gst_caps_is_any (allowed_caps)) { + allowed_caps = gst_caps_fixate (gst_caps_make_writable (allowed_caps)); + auto s = gst_caps_get_structure (allowed_caps, 0); + auto profile_str = gst_structure_get_string (s, "profile"); + if (g_strcmp0 (profile_str, "high") == 0) { + priv->selected_profile = GST_H264_PROFILE_HIGH; + priv->profile_h264 = D3D12_VIDEO_ENCODER_PROFILE_H264_HIGH; + } else if (g_strcmp0 (profile_str, "constrained-baseline") == 0) { + priv->selected_profile = GST_H264_PROFILE_BASELINE; + priv->profile_h264 = D3D12_VIDEO_ENCODER_PROFILE_H264_MAIN; + } else { + priv->selected_profile = GST_H264_PROFILE_MAIN; + priv->profile_h264 = D3D12_VIDEO_ENCODER_PROFILE_H264_MAIN; + } + } + + gst_clear_caps (&allowed_caps); + + const gchar *profile_str = nullptr; + switch (priv->selected_profile) { + case GST_H264_PROFILE_BASELINE: + profile_str = "constrained-baseline"; + break; + case GST_H264_PROFILE_MAIN: + profile_str = "main"; + break; + case GST_H264_PROFILE_HIGH: + profile_str = "high"; + break; + default: + g_assert_not_reached (); + break; + } + + auto caps = gst_caps_new_simple ("video/x-h264", + "alignment", G_TYPE_STRING, "au", "profile", G_TYPE_STRING, profile_str, + "stream-format", G_TYPE_STRING, "byte-stream", nullptr); + auto output_state = + gst_video_encoder_set_output_state (GST_VIDEO_ENCODER (self), caps, + state); + gst_video_codec_state_unref (output_state); + + D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC_PICTURE_CONTROL_SUPPORT + feature_pic_ctrl = { }; + feature_pic_ctrl.Codec = D3D12_VIDEO_ENCODER_CODEC_H264; + feature_pic_ctrl.Profile.DataSize = sizeof (priv->profile_h264); + feature_pic_ctrl.Profile.pH264Profile = &priv->profile_h264; + feature_pic_ctrl.PictureSupport.DataSize = sizeof (priv->pic_ctrl_support); + feature_pic_ctrl.PictureSupport.pH264Support = &priv->pic_ctrl_support; + auto hr = video_device->CheckFeatureSupport + (D3D12_FEATURE_VIDEO_ENCODER_CODEC_PICTURE_CONTROL_SUPPORT, + &feature_pic_ctrl, sizeof (feature_pic_ctrl)); + if (!gst_d3d12_result (hr, encoder->device) || !feature_pic_ctrl.IsSupported) { + GST_ERROR_OBJECT (self, "Couldn't query picture control support"); + return FALSE; + } + + if (info->fps_n > 0 && info->fps_d > 0) { + config->rate_control.TargetFrameRate.Numerator = info->fps_n; + config->rate_control.TargetFrameRate.Denominator = info->fps_d; + } else { + config->rate_control.TargetFrameRate.Numerator = 30; + config->rate_control.TargetFrameRate.Denominator = 1; + } + + priv->config_h264.ConfigurationFlags = + D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264_FLAG_NONE; + priv->config_h264.DirectModeConfig = + D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264_DIRECT_MODES_DISABLED; + priv->config_h264.DisableDeblockingFilterConfig = + D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264_SLICES_DEBLOCKING_MODE_0_ALL_LUMA_CHROMA_SLICE_BLOCK_EDGES_ALWAYS_FILTERED; + + if (priv->selected_profile != GST_H264_PROFILE_BASELINE) { + priv->config_h264.ConfigurationFlags |= + D3D12_VIDEO_ENCODER_CODEC_CONFIGURATION_H264_FLAG_ENABLE_CABAC_ENCODING; + } + + return gst_d3d12_h264_enc_reconfigure (self, video_device, config, nullptr); +} + +static gboolean +gst_d3d12_h264_enc_foreach_caption_meta (GstBuffer * buffer, GstMeta ** meta, + GArray * cc_sei) +{ + if ((*meta)->info->api != GST_VIDEO_CAPTION_META_API_TYPE) + return TRUE; + + auto cc_meta = (GstVideoCaptionMeta *) (*meta); + if (cc_meta->caption_type != GST_VIDEO_CAPTION_TYPE_CEA708_RAW) + return TRUE; + + GstH264SEIMessage sei = { }; + sei.payloadType = GST_H264_SEI_REGISTERED_USER_DATA; + auto rud = &sei.payload.registered_user_data; + + rud->country_code = 181; + rud->size = cc_meta->size + 10; + + auto data = (guint8 *) g_malloc (rud->size); + data[0] = 0; /* 16-bits itu_t_t35_provider_code */ + data[1] = 49; + data[2] = 'G'; /* 32-bits ATSC_user_identifier */ + data[3] = 'A'; + data[4] = '9'; + data[5] = '4'; + data[6] = 3; /* 8-bits ATSC1_data_user_data_type_code */ + /* 8-bits: + * 1 bit process_em_data_flag (0) + * 1 bit process_cc_data_flag (1) + * 1 bit additional_data_flag (0) + * 5-bits cc_count + */ + data[7] = ((cc_meta->size / 3) & 0x1f) | 0x40; + data[8] = 255; /* 8 bits em_data, unused */ + memcpy (data + 9, cc_meta->data, cc_meta->size); + data[cc_meta->size + 9] = 255; /* 8 marker bits */ + + rud->data = data; + + g_array_append_val (cc_sei, sei); + + return TRUE; +} + +static gboolean +gst_d3d12_h264_enc_start_frame (GstD3D12Encoder * encoder, + ID3D12VideoDevice * video_device, GstVideoCodecFrame * frame, + D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_DESC * seq_ctrl, + D3D12_VIDEO_ENCODER_PICTURE_CONTROL_DESC * picture_ctrl, + D3D12_VIDEO_ENCODER_RECONSTRUCTED_PICTURE * recon_pic, + GstD3D12EncoderConfig * config, gboolean * need_new_session) +{ + auto self = GST_D3D12_H264_ENC (encoder); + auto priv = self->priv; + static guint8 aud_data[] = { + 0x00, 0x00, 0x00, 0x01, 0x09, 0xf0 + }; + + *need_new_session = FALSE; + + std::lock_guard < std::mutex > lk (priv->prop_lock); + seq_ctrl->Flags = D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_NONE; + + /* Reset GOP struct on force-keyunit */ + if (GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME (frame)) { + GST_DEBUG_OBJECT (self, "Force keyframe requested"); + priv->gop.ForceKeyUnit (); + } + + auto prev_level = priv->level_h264; + if (!gst_d3d12_h264_enc_reconfigure (self, video_device, config, + &seq_ctrl->Flags)) { + GST_ERROR_OBJECT (self, "Reconfigure failed"); + return FALSE; + } + + if (seq_ctrl->Flags != D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_NONE) { + *need_new_session = + gst_d3d12_encoder_check_needs_new_session (config->support_flags, + seq_ctrl->Flags); + } + + if (priv->level_h264 != prev_level) + *need_new_session = TRUE; + + if (*need_new_session) { + seq_ctrl->Flags = D3D12_VIDEO_ENCODER_SEQUENCE_CONTROL_FLAG_NONE; + + GST_DEBUG_OBJECT (self, "Needs new session, forcing IDR"); + priv->gop.ForceKeyUnit (); + } + + priv->gop.FillPicCtrl (priv->pic_control_h264); + + if (priv->pic_control_h264.FrameType == + D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_IDR_FRAME) { + GST_LOG_OBJECT (self, "Sync point at frame %" G_GUINT64_FORMAT, + priv->display_order); + GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (frame); + } + + seq_ctrl->IntraRefreshConfig.Mode = + D3D12_VIDEO_ENCODER_INTRA_REFRESH_MODE_NONE; + seq_ctrl->IntraRefreshConfig.IntraRefreshDuration = 0; + seq_ctrl->RateControl = config->rate_control; + seq_ctrl->PictureTargetResolution = config->resolution; + seq_ctrl->SelectedLayoutMode = priv->selected_slice_mode; + seq_ctrl->FrameSubregionsLayoutData = config->layout; + seq_ctrl->CodecGopSequence = config->gop_struct; + + picture_ctrl->IntraRefreshFrameIndex = 0; + /* TODO: b frame can be non-reference picture */ + picture_ctrl->Flags = priv->selected_ref_frames > 0 ? + D3D12_VIDEO_ENCODER_PICTURE_CONTROL_FLAG_USED_AS_REFERENCE_PICTURE : + D3D12_VIDEO_ENCODER_PICTURE_CONTROL_FLAG_NONE; + picture_ctrl->PictureControlCodecData.DataSize = + sizeof (priv->pic_control_h264); + picture_ctrl->PictureControlCodecData.pH264PicData = &priv->pic_control_h264; + + if (!priv->dpb->StartFrame (picture_ctrl->Flags == + D3D12_VIDEO_ENCODER_PICTURE_CONTROL_FLAG_USED_AS_REFERENCE_PICTURE, + &priv->pic_control_h264, recon_pic, &picture_ctrl->ReferenceFrames, + priv->display_order)) { + GST_ERROR_OBJECT (self, "Start frame failed"); + return FALSE; + } + + priv->display_order++; + + priv->pic_control_h264.Flags = + D3D12_VIDEO_ENCODER_PICTURE_CONTROL_CODEC_DATA_H264_FLAG_NONE; + /* FIXME: count L1 too */ + priv->pic_control_h264.pic_parameter_set_id = + priv->pic_control_h264.List0ReferenceFramesCount > 1 ? + priv->pic_control_h264.List0ReferenceFramesCount - 1 : 0; + priv->pic_control_h264.adaptive_ref_pic_marking_mode_flag = 0; + priv->pic_control_h264.RefPicMarkingOperationsCommandsCount = 0; + priv->pic_control_h264.pRefPicMarkingOperationsCommands = nullptr; + priv->pic_control_h264.List0RefPicModificationsCount = 0; + priv->pic_control_h264.pList0RefPicModifications = nullptr; + priv->pic_control_h264.List1RefPicModificationsCount = 0; + priv->pic_control_h264.pList1RefPicModifications = nullptr; + priv->pic_control_h264.QPMapValuesCount = 0; + priv->pic_control_h264.pRateControlQPMap = nullptr; + + if (priv->pic_control_h264.FrameType == + D3D12_VIDEO_ENCODER_FRAME_TYPE_H264_IDR_FRAME) { + auto buf_size = priv->sps.bytes.size () + priv->pps[0].bytes.size (); + if (priv->aud) + buf_size += sizeof (aud_data); + + auto output_buf = gst_buffer_new_and_alloc (buf_size); + GstMapInfo map_info; + gst_buffer_map (output_buf, &map_info, GST_MAP_WRITE); + auto data = (guint8 *) map_info.data; + + if (priv->aud) { + memcpy (data, aud_data, sizeof (aud_data)); + data += sizeof (aud_data); + } + + memcpy (data, priv->sps.bytes.data (), priv->sps.bytes.size ()); + data += priv->sps.bytes.size (); + + memcpy (data, priv->pps[0].bytes.data (), priv->pps[0].bytes.size ()); + gst_buffer_unmap (output_buf, &map_info); + frame->output_buffer = output_buf; + + priv->last_pps_id = 0; + } else if (priv->pic_control_h264.pic_parameter_set_id != priv->last_pps_id) { + const auto & cur_pps = + priv->pps[priv->pic_control_h264.pic_parameter_set_id]; + auto buf_size = cur_pps.bytes.size (); + + if (priv->aud) + buf_size += sizeof (aud_data); + + auto output_buf = gst_buffer_new_and_alloc (buf_size); + GstMapInfo map_info; + gst_buffer_map (output_buf, &map_info, GST_MAP_WRITE); + auto data = (guint8 *) map_info.data; + + if (priv->aud) { + memcpy (data, aud_data, sizeof (aud_data)); + data += sizeof (aud_data); + } + + memcpy (data, cur_pps.bytes.data (), cur_pps.bytes.size ()); + gst_buffer_unmap (output_buf, &map_info); + frame->output_buffer = output_buf; + + priv->last_pps_id = priv->pic_control_h264.pic_parameter_set_id; + } else if (priv->aud) { + auto buf_size = sizeof (aud_data); + auto output_buf = gst_buffer_new_and_alloc (buf_size); + GstMapInfo map_info; + gst_buffer_map (output_buf, &map_info, GST_MAP_WRITE); + memcpy (map_info.data, aud_data, sizeof (aud_data)); + gst_buffer_unmap (output_buf, &map_info); + frame->output_buffer = output_buf; + } + + if (priv->cc_insert != GST_D3D12_ENCODER_SEI_DISABLED) { + g_array_set_size (priv->cc_sei, 0); + gst_buffer_foreach_meta (frame->input_buffer, + (GstBufferForeachMetaFunc) gst_d3d12_h264_enc_foreach_caption_meta, + priv->cc_sei); + if (priv->cc_sei->len > 0) { + auto mem = gst_h264_create_sei_memory (4, priv->cc_sei); + if (mem) { + GST_TRACE_OBJECT (self, "Inserting CC SEI"); + + if (!frame->output_buffer) + frame->output_buffer = gst_buffer_new (); + + gst_buffer_append_memory (frame->output_buffer, mem); + } + } + } + + return TRUE; +} + +static gboolean +gst_d3d12_h264_enc_end_frame (GstD3D12Encoder * encoder) +{ + auto self = GST_D3D12_H264_ENC (encoder); + auto priv = self->priv; + + priv->dpb->EndFrame (); + + return TRUE; +} + +void +gst_d3d12_h264_enc_register (GstPlugin * plugin, GstD3D12Device * device, + ID3D12VideoDevice * video_device, guint rank) +{ + HRESULT hr; + std::vector < std::string > profiles; + + GST_DEBUG_CATEGORY_INIT (gst_d3d12_h264_enc_debug, + "d3d12h264enc", 0, "d3d12h264enc"); + + D3D12_FEATURE_DATA_VIDEO_ENCODER_CODEC feature_codec = { }; + feature_codec.Codec = D3D12_VIDEO_ENCODER_CODEC_H264; + hr = video_device->CheckFeatureSupport (D3D12_FEATURE_VIDEO_ENCODER_CODEC, + &feature_codec, sizeof (feature_codec)); + + if (!gst_d3d12_result (hr, device) || !feature_codec.IsSupported) { + GST_INFO_OBJECT (device, "Device does not support H.264 encoding"); + return; + } + + D3D12_FEATURE_DATA_VIDEO_ENCODER_PROFILE_LEVEL feature_profile_level = { }; + D3D12_VIDEO_ENCODER_PROFILE_H264 profile_h264 = + D3D12_VIDEO_ENCODER_PROFILE_H264_MAIN; + D3D12_VIDEO_ENCODER_LEVELS_H264 level_h264_min; + D3D12_VIDEO_ENCODER_LEVELS_H264 level_h264_max; + + feature_profile_level.Codec = D3D12_VIDEO_ENCODER_CODEC_H264; + feature_profile_level.Profile.DataSize = sizeof (profile_h264); + feature_profile_level.Profile.pH264Profile = &profile_h264; + feature_profile_level.MinSupportedLevel.DataSize = sizeof (level_h264_min); + feature_profile_level.MinSupportedLevel.pH264LevelSetting = &level_h264_min; + feature_profile_level.MaxSupportedLevel.DataSize = sizeof (level_h264_max); + feature_profile_level.MaxSupportedLevel.pH264LevelSetting = &level_h264_max; + + D3D12_FEATURE_DATA_VIDEO_ENCODER_INPUT_FORMAT feature_input_format = { }; + feature_input_format.Codec = D3D12_VIDEO_ENCODER_CODEC_H264; + feature_input_format.Profile = feature_profile_level.Profile; + + hr = video_device->CheckFeatureSupport + (D3D12_FEATURE_VIDEO_ENCODER_PROFILE_LEVEL, &feature_profile_level, + sizeof (feature_profile_level)); + if (!gst_d3d12_result (hr, device) || !feature_profile_level.IsSupported) { + GST_WARNING_OBJECT (device, "Main profile is not supported"); + return; + } + + feature_input_format.Format = DXGI_FORMAT_NV12; + hr = video_device->CheckFeatureSupport + (D3D12_FEATURE_VIDEO_ENCODER_INPUT_FORMAT, &feature_input_format, + sizeof (feature_input_format)); + if (!gst_d3d12_result (hr, device) || !feature_input_format.IsSupported) { + GST_WARNING_OBJECT (device, "NV12 format is not supported"); + return; + } + + profiles.push_back ("constrained-baseline"); + profiles.push_back ("main"); + GST_INFO_OBJECT (device, "Main profile is supported, level [%d, %d]", + level_h264_min, level_h264_max); + + profile_h264 = D3D12_VIDEO_ENCODER_PROFILE_H264_HIGH; + if (gst_d3d12_result (hr, device) && feature_profile_level.IsSupported) { + 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) { + 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; + cdata->picture_ctrl = picture_ctrl_h264; + + 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); +} diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12h264enc.h b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12h264enc.h new file mode 100644 index 0000000000..16e91c4c42 --- /dev/null +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12h264enc.h @@ -0,0 +1,33 @@ +/* GStreamer + * Copyright (C) 2024 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include +#include "gstd3d12.h" + +G_BEGIN_DECLS + +void gst_d3d12_h264_enc_register (GstPlugin * plugin, + GstD3D12Device * device, + ID3D12VideoDevice * video_device, + guint rank); + +G_END_DECLS diff --git a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12memory.cpp b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12memory.cpp index 469e0316dd..22d79cca64 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12memory.cpp +++ b/subprojects/gst-plugins-bad/sys/d3d12/gstd3d12memory.cpp @@ -23,6 +23,7 @@ #include "gstd3d12.h" #include "gstd3d12memory-private.h" +#include "gstd3d12-private.h" #include #include #include @@ -774,7 +775,20 @@ gst_d3d12_allocator_alloc_internal (GstD3D12Allocator * self, return nullptr; } - return gst_d3d12_allocator_alloc_wrapped (self, device, resource.Get (), 0); + auto mem = + gst_d3d12_allocator_alloc_wrapped (self, device, resource.Get (), 0); + if (!mem) + return nullptr; + + /* Initialize YUV texture with black color */ + if (desc->Dimension == D3D12_RESOURCE_DIMENSION_TEXTURE2D && + (desc->Flags & D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET) != 0 && + (heap_flags & D3D12_HEAP_FLAG_CREATE_NOT_ZEROED) == 0 && + desc->DepthOrArraySize == 1) { + gst_d3d12_device_clear_yuv_texture (device, mem); + } + + return mem; } GstMemory * diff --git a/subprojects/gst-plugins-bad/sys/d3d12/meson.build b/subprojects/gst-plugins-bad/sys/d3d12/meson.build index b5f654da1f..9bcf659d8b 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/meson.build +++ b/subprojects/gst-plugins-bad/sys/d3d12/meson.build @@ -14,10 +14,14 @@ d3d12_sources = [ 'gstd3d12descriptorpool.cpp', 'gstd3d12device.cpp', 'gstd3d12download.cpp', + 'gstd3d12dpbstorage.cpp', 'gstd3d12dxgicapture.cpp', + 'gstd3d12encoder.cpp', + 'gstd3d12encoderbufferpool.cpp', 'gstd3d12fencedatapool.cpp', 'gstd3d12format.cpp', 'gstd3d12h264dec.cpp', + 'gstd3d12h264enc.cpp', 'gstd3d12h265dec.cpp', 'gstd3d12memory.cpp', 'gstd3d12overlaycompositor.cpp', diff --git a/subprojects/gst-plugins-bad/sys/d3d12/plugin.cpp b/subprojects/gst-plugins-bad/sys/d3d12/plugin.cpp index 27a48d9427..9da2c3d5d7 100644 --- a/subprojects/gst-plugins-bad/sys/d3d12/plugin.cpp +++ b/subprojects/gst-plugins-bad/sys/d3d12/plugin.cpp @@ -38,6 +38,7 @@ #include "gstd3d12screencapturedevice.h" #include "gstd3d12screencapturesrc.h" #include "gstd3d12h264dec.h" +#include "gstd3d12h264enc.h" #include "gstd3d12h265dec.h" #include "gstd3d12vp9dec.h" #include "gstd3d12av1dec.h" @@ -106,6 +107,9 @@ plugin_init (GstPlugin * plugin) gst_d3d12_av1_dec_register (plugin, device, video_device.Get (), GST_RANK_NONE); + gst_d3d12_h264_enc_register (plugin, device, video_device.Get (), + GST_RANK_NONE); + gst_object_unref (device); } diff --git a/subprojects/gst-plugins-bad/tests/examples/d3d12/d3d12enc-dynamic-reconfigure.c b/subprojects/gst-plugins-bad/tests/examples/d3d12/d3d12enc-dynamic-reconfigure.c new file mode 100644 index 0000000000..bc800328e7 --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/examples/d3d12/d3d12enc-dynamic-reconfigure.c @@ -0,0 +1,508 @@ +/* GStreamer + * Copyright (C) 2024 Seungha Yang + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include "../key-handler.h" + +static gchar *rc_modes[] = { + "cqp", "cbr", "vbr", "qvbr" +}; + +static gchar *slice_modes[] = { + "full", "subregions" +}; + +static GMainLoop *loop = NULL; +static gint width = 640; +static gint height = 480; +static guint bitrate = 1000; +static guint max_bitrate = 2000; +static guint rc_index = 0; +static guint qp_i = 24; +static guint qp_p = 24; +static guint qp_b = 24; +static guint max_qp = 51; +static guint gop_size = 30; +static guint ref_frames = 0; +static guint slice_mode_index = 0; +static guint num_slices = 2; + +#define BITRATE_STEP 100 + +G_LOCK_DEFINE_STATIC (input_lock); + +typedef struct +{ + GstElement *pipeline; + GstElement *capsfilter; + GstElement *encoder; + gulong probe_id; + + gint prev_width; + gint prev_height; +} TestCallbackData; + +static void +print_keyboard_help (void) +{ + /* *INDENT-OFF* */ + static struct + { + const gchar *key_desc; + const gchar *key_help; + } key_controls[] = { + { + "q", "Quit"}, { + "right arrow", "Increase Width"}, { + "left arrow", "Decrease Width"}, { + "up arrow", "Increase Height"}, { + "down arrow", "Decrease Height"}, { + "f", "Sends force keyunit event"}, { + "]", "Increase bitrate by 100 kbps"}, { + "[", "Decrease bitrate by 100 kbps"}, { + "}", "Increase max-bitrate by 100 kbps"}, { + "{", "Decrease max-bitrate by 100 kbps"}, { + "r", "Toggle rate-control mode"}, { + "<", "Decrease GOP size"}, { + ">", "Increase GOP size"}, { + "+", "Incrase ref-frames"}, { + "-", "Decrase ref-frames"}, { + "I", "Increase QP-I"}, { + "i", "Decrease QP-I"}, { + "P", "Increase QP-P"}, { + "p", "Decrease QP-P"}, { + "m", "Toggle slice mode"}, { + "S", "Incrase number of slices"}, { + "s", "Decrease number of slices"}, { + "k", "show keyboard shortcuts"} + }; + /* *INDENT-ON* */ + + guint i, chars_to_pad, desc_len, max_desc_len = 0; + + gst_print ("\n\n%s\n\n", "Keyboard controls:"); + + for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) { + desc_len = g_utf8_strlen (key_controls[i].key_desc, -1); + max_desc_len = MAX (max_desc_len, desc_len); + } + ++max_desc_len; + + for (i = 0; i < G_N_ELEMENTS (key_controls); ++i) { + chars_to_pad = max_desc_len - g_utf8_strlen (key_controls[i].key_desc, -1); + gst_print ("\t%s", key_controls[i].key_desc); + gst_print ("%-*s: ", chars_to_pad, ""); + gst_print ("%s\n", key_controls[i].key_help); + } + gst_print ("\n"); +} + +static void +keyboard_cb (gchar input, gboolean is_ascii, gpointer user_data) +{ + TestCallbackData *data = (TestCallbackData *) user_data; + + G_LOCK (input_lock); + + if (!is_ascii) { + switch (input) { + case KB_ARROW_UP: + height += 2; + gst_println ("Increase height to %d", height); + break; + case KB_ARROW_DOWN: + height -= 2; + height = MAX (height, 16); + gst_println ("Decrease height to %d", height); + break; + case KB_ARROW_LEFT: + width -= 2; + width = MAX (width, 16); + gst_println ("Decrease width to %d", width); + break; + case KB_ARROW_RIGHT: + width += 2; + gst_println ("Increase width to %d", width); + break; + default: + break; + } + } else { + switch (input) { + case 'k': + case 'K': + print_keyboard_help (); + break; + case 'q': + case 'Q': + gst_element_send_event (data->pipeline, gst_event_new_eos ()); + g_main_loop_quit (loop); + break; + case 'f': + { + GstEvent *event = + gst_video_event_new_upstream_force_key_unit (GST_CLOCK_TIME_NONE, + TRUE, 0); + gst_println ("Sending force keyunit event"); + gst_element_send_event (data->encoder, event); + break; + } + case ']': + if (bitrate < G_MAXUINT64 - BITRATE_STEP) { + bitrate += BITRATE_STEP; + max_bitrate = MAX (max_bitrate, bitrate); + gst_println ("Increase bitrate to %" G_GUINT64_FORMAT, bitrate); + g_object_set (data->encoder, "bitrate", bitrate, "max-bitrate", + max_bitrate, NULL); + } + break; + case '[': + if (bitrate > 2 * BITRATE_STEP) { + bitrate -= BITRATE_STEP; + gst_println ("Decrease bitrate to %" G_GUINT64_FORMAT, bitrate); + g_object_set (data->encoder, "bitrate", bitrate, NULL); + } + break; + case '}': + if (max_bitrate < G_MAXUINT64 - BITRATE_STEP) { + max_bitrate += BITRATE_STEP; + gst_println ("Increase max bitrate to %" G_GUINT64_FORMAT, + max_bitrate); + g_object_set (data->encoder, "max-bitrate", max_bitrate, NULL); + } + break; + case '{': + if (max_bitrate > 2 * BITRATE_STEP) { + max_bitrate -= BITRATE_STEP; + bitrate = MAX (bitrate, max_bitrate); + gst_println ("Decrease max bitrate to %" G_GUINT64_FORMAT, + max_bitrate); + g_object_set (data->encoder, "bitrate", bitrate, "max-bitrate", + max_bitrate, NULL); + } + break; + case 'r': + rc_index++; + rc_index %= G_N_ELEMENTS (rc_modes); + gst_println ("Change rate control mode to %s", rc_modes[rc_index]); + gst_util_set_object_arg (G_OBJECT (data->encoder), "rate-control", + rc_modes[rc_index]); + break; + case '<': + gop_size--; + gst_println ("Updating GOP size to %u", gop_size); + g_object_set (data->encoder, "gop-size", gop_size, NULL); + break; + case '>': + gop_size++; + gst_println ("Updating GOP size to %u", gop_size); + g_object_set (data->encoder, "gop-size", gop_size, NULL); + break; + case '+': + ref_frames++; + ref_frames %= 17; + gst_println ("Updating ref frames to %u", ref_frames); + g_object_set (data->encoder, "ref-frames", ref_frames, NULL); + break; + case '-': + ref_frames--; + ref_frames %= 17; + gst_println ("Updating ref frames to %u", ref_frames); + g_object_set (data->encoder, "ref-frames", ref_frames, NULL); + break; + case 'I': + qp_i++; + qp_i %= 52; + if (qp_i == 0) + qp_i++; + gst_println ("Updating QP-I to %d", qp_i); + g_object_set (data->encoder, "qp-i", qp_i, NULL); + break; + case 'i': + qp_i--; + qp_i %= 52; + if (qp_i == 0) + qp_i = 51; + gst_println ("Updating QP-I to %d", qp_i); + g_object_set (data->encoder, "qp-i", qp_i, NULL); + break; + case 'P': + qp_p++; + qp_p %= 52; + if (qp_p == 0) + qp_p++; + gst_println ("Updating QP-P to %d", qp_p); + g_object_set (data->encoder, "qp-p", qp_p, NULL); + break; + case 'p': + qp_p--; + qp_p %= 52; + if (qp_p == 0) + qp_p = 51; + gst_println ("Updating QP-P to %d", qp_i); + g_object_set (data->encoder, "qp-p", qp_i, NULL); + break; + case 'm': + slice_mode_index++; + slice_mode_index %= G_N_ELEMENTS (slice_modes); + gst_println ("Updating slice mode to %s", + slice_modes[slice_mode_index]); + gst_util_set_object_arg (G_OBJECT (data->encoder), "slice-mode", + slice_modes[slice_mode_index]); + break; + case 'S': + num_slices++; + gst_println ("Updating slice partition to %u", num_slices); + g_object_set (data->encoder, "slice-partition", num_slices, NULL); + break; + case 's': + num_slices--; + gst_println ("Updating slice partition to %u", num_slices); + g_object_set (data->encoder, "slice-partition", num_slices, NULL); + break; + default: + break; + } + } + + G_UNLOCK (input_lock); +} + +static gboolean +bus_msg (GstBus * bus, GstMessage * msg, gpointer user_data) +{ + switch (GST_MESSAGE_TYPE (msg)) { + case GST_MESSAGE_ERROR:{ + GError *err; + gchar *dbg; + + gst_message_parse_error (msg, &err, &dbg); + gst_printerrln ("ERROR %s", err->message); + if (dbg != NULL) + gst_printerrln ("ERROR debug information: %s", dbg); + g_clear_error (&err); + g_free (dbg); + + g_main_loop_quit (loop); + break; + } + default: + break; + } + + return TRUE; +} + +static gboolean +check_encoder_available (const gchar * encoder_name) +{ + gboolean ret = TRUE; + GstElement *elem; + + elem = gst_element_factory_make (encoder_name, NULL); + if (!elem) { + gst_printerrln ("%s is not available", encoder_name); + return FALSE; + } + + if (gst_element_set_state (elem, + GST_STATE_PAUSED) != GST_STATE_CHANGE_SUCCESS) { + gst_printerrln ("cannot open device"); + ret = FALSE; + } + + gst_element_set_state (elem, GST_STATE_NULL); + gst_object_unref (elem); + + return ret; +} + +static GstPadProbeReturn +resolution_change_probe (GstPad * pad, GstPadProbeInfo * info, + gpointer user_data) +{ + GstPadProbeReturn ret = GST_PAD_PROBE_OK; + TestCallbackData *data = (TestCallbackData *) user_data; + + G_LOCK (input_lock); + + if (GST_IS_BUFFER (GST_PAD_PROBE_INFO_DATA (info))) { + GstBuffer *buffer = GST_PAD_PROBE_INFO_BUFFER (info); + GstPad *peer = gst_pad_get_peer (pad); + GstFlowReturn flow_ret = GST_FLOW_OK; + + ret = GST_PAD_PROBE_HANDLED; + + if (peer) { + flow_ret = gst_pad_chain (peer, buffer); + + if (flow_ret != GST_FLOW_OK) { + gst_pad_remove_probe (pad, data->probe_id); + data->probe_id = 0; + } else { + if (data->prev_width != width || data->prev_height != height) { + GstCaps *caps = NULL; + gint next_width, next_height; + + next_width = width; + next_height = height; + + g_object_get (data->capsfilter, "caps", &caps, NULL); + caps = gst_caps_make_writable (caps); + gst_caps_set_simple (caps, + "width", G_TYPE_INT, next_width, "height", G_TYPE_INT, + next_height, NULL); + g_object_set (data->capsfilter, "caps", caps, NULL); + gst_caps_unref (caps); + + data->prev_width = next_width; + data->prev_height = next_height; + } + } + } + } + + G_UNLOCK (input_lock); + + return ret; +} + +gint +main (gint argc, gchar ** argv) +{ + GstElement *pipeline; + GstElement *src, *capsfilter, *enc, *enc_queue, *dec, *parser, *queue, *sink; + GstStateChangeReturn sret; + GError *error = NULL; + GOptionContext *option_ctx; + GstCaps *caps; + TestCallbackData data = { 0, }; + GstPad *pad; + gchar *encoder_name = NULL; + /* *INDENT-OFF* */ + GOptionEntry options[] = { + {"encoder", 0, 0, G_OPTION_ARG_STRING, &encoder_name, + "Video encoder element to test, default: d3d12h264enc"}, + {NULL} + }; + /* *INDENT-ON* */ + +#define MAKE_ELEMENT_AND_ADD(elem, name) G_STMT_START { \ + GstElement *_elem = gst_element_factory_make (name, NULL); \ + if (!_elem) { \ + gst_printerrln ("%s is not available", name); \ + exit (1); \ + } \ + gst_println ("Adding element %s", name); \ + elem = _elem; \ + gst_bin_add (GST_BIN (pipeline), elem); \ +} G_STMT_END + + option_ctx = + g_option_context_new ("d3d12 video encoder dynamic reconfigure example"); + g_option_context_add_main_entries (option_ctx, options, NULL); + g_option_context_set_help_enabled (option_ctx, TRUE); + if (!g_option_context_parse (option_ctx, &argc, &argv, &error)) { + gst_printerrln ("option parsing failed: %s\n", error->message); + g_clear_error (&error); + exit (1); + } + + g_option_context_free (option_ctx); + gst_init (NULL, NULL); + + if (!encoder_name) + encoder_name = g_strdup ("d3d12h264enc"); + + if (!check_encoder_available (encoder_name)) { + gst_printerrln ("Cannot load %s plugin", encoder_name); + exit (1); + } + + /* prepare the pipeline */ + loop = g_main_loop_new (NULL, FALSE); + + pipeline = gst_pipeline_new (NULL); + + MAKE_ELEMENT_AND_ADD (src, "videotestsrc"); + g_object_set (src, "pattern", 1, "is-live", TRUE, NULL); + + MAKE_ELEMENT_AND_ADD (capsfilter, "capsfilter"); + MAKE_ELEMENT_AND_ADD (enc, encoder_name); + + g_object_set (enc, "bitrate", bitrate, "max-bitrate", max_bitrate, + "qp-i", qp_i, "qp-p", qp_p, "gop-size", 30, NULL); + + gst_util_set_object_arg (G_OBJECT (enc), "rate-control", rc_modes[rc_index]); + + MAKE_ELEMENT_AND_ADD (enc_queue, "queue"); + MAKE_ELEMENT_AND_ADD (parser, "h264parse"); + MAKE_ELEMENT_AND_ADD (dec, "d3d12h264dec"); + MAKE_ELEMENT_AND_ADD (queue, "queue"); + MAKE_ELEMENT_AND_ADD (sink, "d3d12videosink"); + + if (!gst_element_link_many (src, capsfilter, enc, enc_queue, + parser, dec, queue, sink, NULL)) { + gst_printerrln ("Failed to link element"); + exit (1); + } + + caps = gst_caps_new_simple ("video/x-raw", "width", G_TYPE_INT, + width, "height", G_TYPE_INT, height, NULL); + g_object_set (capsfilter, "caps", caps, NULL); + gst_caps_unref (caps); + + data.pipeline = pipeline; + data.capsfilter = capsfilter; + data.encoder = enc; + + pad = gst_element_get_static_pad (capsfilter, "src"); + data.probe_id = gst_pad_add_probe (pad, GST_PAD_PROBE_TYPE_BUFFER, + (GstPadProbeCallback) resolution_change_probe, &data, NULL); + gst_object_unref (pad); + data.prev_width = width; + data.prev_height = height; + + gst_bus_add_watch (GST_ELEMENT_BUS (pipeline), bus_msg, &data); + + /* run the pipeline */ + sret = gst_element_set_state (pipeline, GST_STATE_PLAYING); + if (sret == GST_STATE_CHANGE_FAILURE) { + gst_printerrln ("Pipeline doesn't want to playing\n"); + } else { + set_key_handler ((KeyInputCallback) keyboard_cb, &data); + g_main_loop_run (loop); + unset_key_handler (); + } + + gst_element_set_state (pipeline, GST_STATE_NULL); + gst_bus_remove_watch (GST_ELEMENT_BUS (pipeline)); + + gst_object_unref (pipeline); + g_main_loop_unref (loop); + g_free (encoder_name); + + return 0; +} diff --git a/subprojects/gst-plugins-bad/tests/examples/d3d12/meson.build b/subprojects/gst-plugins-bad/tests/examples/d3d12/meson.build new file mode 100644 index 0000000000..949e92548d --- /dev/null +++ b/subprojects/gst-plugins-bad/tests/examples/d3d12/meson.build @@ -0,0 +1,10 @@ +if host_system != 'windows' + subdir_done() +endif + +executable('d3d12enc-dynamic-reconfigure', + ['d3d12enc-dynamic-reconfigure.c', '../key-handler.c'], + include_directories : [configinc], + dependencies: [gst_dep, gstbase_dep, gstvideo_dep], + c_args : gst_plugins_bad_args, + install: false) diff --git a/subprojects/gst-plugins-bad/tests/examples/meson.build b/subprojects/gst-plugins-bad/tests/examples/meson.build index d6a1e1ade9..aec685bf65 100644 --- a/subprojects/gst-plugins-bad/tests/examples/meson.build +++ b/subprojects/gst-plugins-bad/tests/examples/meson.build @@ -4,6 +4,7 @@ subdir('camerabin2') subdir('codecparsers') subdir('codecs') subdir('d3d11') +subdir('d3d12') subdir('directfb') subdir('gtk') subdir('ipcpipeline')