mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-23 16:50:47 +00:00
1469 lines
46 KiB
C
1469 lines
46 KiB
C
/*
|
|
* GStreamer
|
|
* Copyright (C) 2024 Igalia, S.L.
|
|
*
|
|
* 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 "gstvkencoder-private.h"
|
|
|
|
#include "gstvkvideo-private.h"
|
|
|
|
extern const VkExtensionProperties vk_codec_extensions[3];
|
|
extern const uint32_t _vk_codec_supported_extensions[4];
|
|
|
|
typedef struct _GstVulkanEncoderPrivate GstVulkanEncoderPrivate;
|
|
|
|
struct _GstVulkanEncoderPrivate
|
|
{
|
|
GstVulkanHandle *session_params;
|
|
|
|
GstCaps *profile_caps;
|
|
|
|
GstVulkanEncoderCallbacks callbacks;
|
|
gpointer callbacks_user_data;
|
|
GDestroyNotify callbacks_notify;
|
|
|
|
GstVulkanOperation *exec;
|
|
|
|
GstVulkanVideoSession session;
|
|
GstVulkanVideoCapabilities caps;
|
|
VkVideoFormatPropertiesKHR format;
|
|
|
|
GstVulkanVideoProfile profile;
|
|
|
|
gboolean vk_loaded;
|
|
GstVulkanVideoFunctions vk;
|
|
|
|
GstVulkanEncoderPicture *slots[32];
|
|
|
|
guint32 quality;
|
|
VkVideoEncodeRateControlModeFlagBitsKHR rc_mode;
|
|
|
|
gboolean started;
|
|
gboolean session_reset;
|
|
|
|
gboolean layered_dpb;
|
|
GstBufferPool *dpb_pool;
|
|
GstBuffer *layered_buffer;
|
|
GstVulkanImageView *layered_view;
|
|
};
|
|
|
|
/**
|
|
* SECTION:vkencoder
|
|
* @title: GstVulkanEncoder
|
|
* @short_description: Generic Vulkan Video Encoder
|
|
*/
|
|
|
|
#define GST_CAT_DEFAULT gst_vulkan_encoder_debug
|
|
GST_DEBUG_CATEGORY (GST_CAT_DEFAULT);
|
|
|
|
#define gst_vulkan_encoder_parent_class parent_class
|
|
G_DEFINE_TYPE_WITH_CODE (GstVulkanEncoder, gst_vulkan_encoder,
|
|
GST_TYPE_OBJECT, G_ADD_PRIVATE (GstVulkanEncoder)
|
|
GST_DEBUG_CATEGORY_INIT (gst_vulkan_encoder_debug,
|
|
"vulkanencoder", 0, "Vulkan device encoder"));
|
|
|
|
const uint32_t _vk_codec_supported_extensions[] = {
|
|
[GST_VK_VIDEO_EXTENSION_ENCODE_H264] = VK_MAKE_VIDEO_STD_VERSION (0, 9, 11),
|
|
[GST_VK_VIDEO_EXTENSION_ENCODE_H265] = VK_MAKE_VIDEO_STD_VERSION (0, 9, 12),
|
|
};
|
|
|
|
static gboolean
|
|
_populate_function_table (GstVulkanEncoder * self)
|
|
{
|
|
GstVulkanEncoderPrivate *priv =
|
|
gst_vulkan_encoder_get_instance_private (self);
|
|
GstVulkanInstance *instance;
|
|
|
|
if (priv->vk_loaded)
|
|
return TRUE;
|
|
|
|
instance = gst_vulkan_device_get_instance (self->queue->device);
|
|
if (!instance) {
|
|
GST_ERROR_OBJECT (self, "Failed to get instance from the device");
|
|
return FALSE;
|
|
}
|
|
|
|
priv->vk_loaded = gst_vulkan_video_get_vk_functions (instance, &priv->vk);
|
|
gst_object_unref (instance);
|
|
return priv->vk_loaded;
|
|
}
|
|
|
|
static void
|
|
gst_vulkan_encoder_finalize (GObject * object)
|
|
{
|
|
GstVulkanEncoder *self = GST_VULKAN_ENCODER (object);
|
|
GstVulkanEncoderPrivate *priv =
|
|
gst_vulkan_encoder_get_instance_private (self);
|
|
|
|
if (priv->callbacks_user_data && priv->callbacks_notify) {
|
|
priv->callbacks_notify (priv->callbacks_user_data);
|
|
priv->callbacks_user_data = NULL;
|
|
priv->callbacks_notify = NULL;
|
|
}
|
|
|
|
gst_clear_object (&self->queue);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gst_vulkan_encoder_init (GstVulkanEncoder * self)
|
|
{
|
|
GstVulkanEncoderPrivate *priv;
|
|
|
|
priv = gst_vulkan_encoder_get_instance_private (self);
|
|
|
|
priv->rc_mode = VK_VIDEO_ENCODE_RATE_CONTROL_MODE_DISABLED_BIT_KHR;
|
|
}
|
|
|
|
static void
|
|
gst_vulkan_encoder_class_init (GstVulkanEncoderClass * klass)
|
|
{
|
|
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
|
|
|
gobject_class->finalize = gst_vulkan_encoder_finalize;
|
|
}
|
|
|
|
static VkFormat
|
|
gst_vulkan_video_encoder_get_format (GstVulkanEncoder * self,
|
|
VkImageUsageFlagBits imageUsage, GError ** error)
|
|
{
|
|
VkResult res;
|
|
VkVideoFormatPropertiesKHR *fmts = NULL;
|
|
guint i, n_fmts;
|
|
VkPhysicalDevice gpu =
|
|
gst_vulkan_device_get_physical_device (self->queue->device);
|
|
GstVulkanEncoderPrivate *priv =
|
|
gst_vulkan_encoder_get_instance_private (self);
|
|
GstVideoFormat format = GST_VIDEO_FORMAT_UNKNOWN;
|
|
VkFormat vk_format = VK_FORMAT_UNDEFINED;
|
|
VkVideoProfileListInfoKHR profile_list = {
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_PROFILE_LIST_INFO_KHR,
|
|
.profileCount = 1,
|
|
.pProfiles = &priv->profile.profile,
|
|
};
|
|
VkPhysicalDeviceVideoFormatInfoKHR fmt_info = {
|
|
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VIDEO_FORMAT_INFO_KHR,
|
|
.pNext = &profile_list,
|
|
.imageUsage = imageUsage,
|
|
};
|
|
|
|
res = priv->vk.GetPhysicalDeviceVideoFormatProperties (gpu, &fmt_info,
|
|
&n_fmts, NULL);
|
|
if (gst_vulkan_error_to_g_error (res, error,
|
|
"vkGetPhysicalDeviceVideoFormatPropertiesKHR") != VK_SUCCESS)
|
|
goto beach;
|
|
|
|
if (n_fmts == 0) {
|
|
g_set_error (error, GST_VULKAN_ERROR, VK_ERROR_INITIALIZATION_FAILED,
|
|
"Profile doesn't have an output format");
|
|
return vk_format;
|
|
}
|
|
|
|
fmts = g_new0 (VkVideoFormatPropertiesKHR, n_fmts);
|
|
for (i = 0; i < n_fmts; i++)
|
|
fmts[i].sType = VK_STRUCTURE_TYPE_VIDEO_FORMAT_PROPERTIES_KHR;
|
|
|
|
res = priv->vk.GetPhysicalDeviceVideoFormatProperties (gpu, &fmt_info,
|
|
&n_fmts, fmts);
|
|
if (gst_vulkan_error_to_g_error (res, error,
|
|
"vkGetPhysicalDeviceVideoFormatPropertiesKHR") != VK_SUCCESS) {
|
|
goto beach;
|
|
}
|
|
|
|
if (n_fmts == 0) {
|
|
g_set_error (error, GST_VULKAN_ERROR, VK_ERROR_INITIALIZATION_FAILED,
|
|
"Profile doesn't have an output format");
|
|
goto beach;
|
|
}
|
|
|
|
/* find the best output format */
|
|
for (i = 0; i < n_fmts; i++) {
|
|
format = gst_vulkan_format_to_video_format (fmts[i].format);
|
|
if (format == GST_VIDEO_FORMAT_UNKNOWN) {
|
|
GST_WARNING_OBJECT (self, "Unknown Vulkan format %i", fmts[i].format);
|
|
continue;
|
|
} else {
|
|
vk_format = fmts[i].format;
|
|
priv->format = fmts[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (vk_format == VK_FORMAT_UNDEFINED) {
|
|
g_set_error (error, GST_VULKAN_ERROR, VK_ERROR_INITIALIZATION_FAILED,
|
|
"No valid output format found");
|
|
}
|
|
|
|
beach:
|
|
g_clear_pointer (&fmts, g_free);
|
|
return vk_format;
|
|
}
|
|
|
|
static void
|
|
gst_vulkan_handle_free_video_session_parameters (GstVulkanHandle * handle,
|
|
gpointer data)
|
|
{
|
|
PFN_vkDestroyVideoSessionParametersKHR vkDestroyVideoSessionParameters;
|
|
|
|
g_return_if_fail (handle != NULL);
|
|
g_return_if_fail (handle->handle != VK_NULL_HANDLE);
|
|
g_return_if_fail (handle->type ==
|
|
GST_VULKAN_HANDLE_TYPE_VIDEO_SESSION_PARAMETERS);
|
|
g_return_if_fail (handle->user_data);
|
|
|
|
vkDestroyVideoSessionParameters = handle->user_data;
|
|
vkDestroyVideoSessionParameters (handle->device->device,
|
|
(VkVideoSessionKHR) handle->handle, NULL);
|
|
}
|
|
|
|
static GstVulkanHandle *
|
|
gst_vulkan_encoder_new_video_session_parameters (GstVulkanEncoder * self,
|
|
GstVulkanEncoderParameters * params, GError ** error)
|
|
{
|
|
GstVulkanEncoderPrivate *priv;
|
|
VkVideoSessionParametersCreateInfoKHR session_params_info;
|
|
VkVideoEncodeQualityLevelInfoKHR quality_info;
|
|
VkResult res;
|
|
VkVideoSessionParametersKHR session_params;
|
|
|
|
g_return_val_if_fail (GST_IS_VULKAN_ENCODER (self), NULL);
|
|
g_return_val_if_fail (params != NULL, NULL);
|
|
|
|
priv = gst_vulkan_encoder_get_instance_private (self);
|
|
|
|
if (!priv->session.session)
|
|
return NULL;
|
|
|
|
/* *INDENT-OFF* */
|
|
quality_info = (VkVideoEncodeQualityLevelInfoKHR) {
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_QUALITY_LEVEL_INFO_KHR,
|
|
.pNext = params,
|
|
.qualityLevel = priv->quality,
|
|
};
|
|
session_params_info = (VkVideoSessionParametersCreateInfoKHR) {
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_SESSION_PARAMETERS_CREATE_INFO_KHR,
|
|
.pNext = &quality_info,
|
|
.videoSession = priv->session.session->handle,
|
|
};
|
|
/* *INDENT-ON* */
|
|
|
|
res = priv->vk.CreateVideoSessionParameters (self->queue->device->device,
|
|
&session_params_info, NULL, &session_params);
|
|
if (gst_vulkan_error_to_g_error (res, error,
|
|
"vkCreateVideoSessionParametersKHR") != VK_SUCCESS)
|
|
return NULL;
|
|
|
|
return gst_vulkan_handle_new_wrapped (self->queue->device,
|
|
GST_VULKAN_HANDLE_TYPE_VIDEO_SESSION_PARAMETERS,
|
|
(GstVulkanHandleTypedef) session_params,
|
|
gst_vulkan_handle_free_video_session_parameters,
|
|
priv->vk.DestroyVideoSessionParameters);
|
|
}
|
|
|
|
/**
|
|
* gst_vulkan_encode_picture_init:
|
|
* @pic: the #GstVulkanEncoderPicture to initialize
|
|
* @self: the #GstVulkanEncoder with the pool's configuration.
|
|
* @in_buffer: (transfer none): the input #GstBuffer.
|
|
* @size: size of the output buffer
|
|
*
|
|
* Initialize @pic structure.
|
|
*
|
|
* Returns: %TRUE if @pic was initialized correctly; otherwise %FALSE
|
|
*/
|
|
gboolean
|
|
gst_vulkan_encoder_picture_init (GstVulkanEncoderPicture * pic,
|
|
GstVulkanEncoder * self, GstBuffer * in_buffer, gsize size)
|
|
{
|
|
GstVulkanEncoderPrivate *priv;
|
|
gsize size_aligned;
|
|
|
|
g_return_val_if_fail (pic != NULL, FALSE);
|
|
g_return_val_if_fail (GST_IS_VULKAN_ENCODER (self), FALSE);
|
|
g_return_val_if_fail (GST_IS_BUFFER (in_buffer), FALSE);
|
|
|
|
priv = gst_vulkan_encoder_get_instance_private (self);
|
|
|
|
size_aligned = GST_ROUND_UP_N (size,
|
|
priv->caps.caps.minBitstreamBufferSizeAlignment);
|
|
|
|
if (priv->layered_dpb) {
|
|
g_assert (priv->layered_buffer);
|
|
pic->dpb_buffer = gst_buffer_ref (priv->layered_buffer);
|
|
} else {
|
|
GstFlowReturn ret;
|
|
|
|
g_assert (GST_IS_BUFFER_POOL (priv->dpb_pool));
|
|
ret =
|
|
gst_buffer_pool_acquire_buffer (priv->dpb_pool, &pic->dpb_buffer, NULL);
|
|
if (ret != GST_FLOW_OK)
|
|
return FALSE;
|
|
}
|
|
pic->in_buffer = gst_buffer_ref (in_buffer);
|
|
pic->out_buffer =
|
|
gst_vulkan_video_codec_buffer_new (self->queue->device, &priv->profile,
|
|
VK_BUFFER_USAGE_VIDEO_ENCODE_DST_BIT_KHR, size_aligned);
|
|
if (!pic->out_buffer) {
|
|
gst_clear_buffer (&pic->dpb_buffer);
|
|
return FALSE;
|
|
}
|
|
pic->offset = 0;
|
|
|
|
pic->img_view = gst_vulkan_video_image_create_view (pic->in_buffer,
|
|
priv->layered_dpb, TRUE, NULL);
|
|
|
|
if (priv->layered_dpb) {
|
|
pic->dpb_view = gst_vulkan_image_view_ref (priv->layered_view);
|
|
} else {
|
|
pic->dpb_view = gst_vulkan_video_image_create_view (pic->dpb_buffer,
|
|
priv->layered_dpb, FALSE, NULL);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gst_vulkan_encoder_picture_clear:
|
|
* @pic: the #GstVulkanEncoderPicture to free.
|
|
* @self: the #GstVulkanEncoder instance.
|
|
*
|
|
* Release data of @pic.
|
|
*/
|
|
void
|
|
gst_vulkan_encoder_picture_clear (GstVulkanEncoderPicture * pic,
|
|
GstVulkanEncoder * self)
|
|
{
|
|
GstVulkanEncoderPrivate *priv;
|
|
|
|
g_return_if_fail (GST_IS_VULKAN_ENCODER (self));
|
|
g_return_if_fail (pic != NULL);
|
|
|
|
priv = gst_vulkan_encoder_get_instance_private (self);
|
|
|
|
if (pic->dpb_slot.slotIndex > 0) {
|
|
priv->slots[pic->dpb_slot.slotIndex] = NULL;
|
|
pic->dpb_slot.slotIndex = -1;
|
|
}
|
|
|
|
gst_clear_buffer (&pic->in_buffer);
|
|
gst_clear_buffer (&pic->dpb_buffer);
|
|
gst_clear_buffer (&pic->out_buffer);
|
|
|
|
gst_vulkan_image_view_unref (pic->img_view);
|
|
pic->img_view = NULL;
|
|
|
|
gst_vulkan_image_view_unref (pic->dpb_view);
|
|
pic->dpb_view = NULL;
|
|
}
|
|
|
|
/**
|
|
* gst_vulkan_encoder_is_started:
|
|
* @self: a #GstVulkanEncoder
|
|
*
|
|
* Returns: whether gst_vulkan_encoder_start() was called correctly previously.
|
|
*/
|
|
gboolean
|
|
gst_vulkan_encoder_is_started (GstVulkanEncoder * self)
|
|
{
|
|
GstVulkanEncoderPrivate *priv;
|
|
|
|
g_return_val_if_fail (GST_IS_VULKAN_ENCODER (self), FALSE);
|
|
|
|
priv = gst_vulkan_encoder_get_instance_private (self);
|
|
return priv->started;
|
|
}
|
|
|
|
|
|
/**
|
|
* gst_vulkan_encoder_caps:
|
|
* @self: a #GstVulkanEncoder
|
|
* @caps: (out): a #GstVulkanVideoCapabilities
|
|
*
|
|
* Get the #GstVulkanVideoCapabilities of the encoder if available
|
|
*
|
|
* Returns: whether the encoder has vulkan encoder caps.
|
|
*
|
|
*/
|
|
gboolean
|
|
gst_vulkan_encoder_caps (GstVulkanEncoder * self,
|
|
GstVulkanVideoCapabilities * caps)
|
|
{
|
|
GstVulkanEncoderPrivate *priv;
|
|
|
|
g_return_val_if_fail (GST_IS_VULKAN_ENCODER (self), FALSE);
|
|
|
|
priv = gst_vulkan_encoder_get_instance_private (self);
|
|
|
|
if (!priv->started)
|
|
return FALSE;
|
|
|
|
if (caps) {
|
|
*caps = priv->caps;
|
|
caps->caps.pNext = &caps->encoder.caps;
|
|
caps->encoder.caps.pNext = &caps->encoder.codec;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gst_vulkan_encoder_profile_caps:
|
|
* @self: a #GstVulkanEncoder
|
|
*
|
|
* Get the #GstCaps according to the encoder video profile
|
|
*
|
|
* Returns: (transfer full): #GstCaps of the profile defined at gst_vulkan_encoder_start()
|
|
*
|
|
*/
|
|
GstCaps *
|
|
gst_vulkan_encoder_profile_caps (GstVulkanEncoder * self)
|
|
{
|
|
GstVulkanEncoderPrivate *priv;
|
|
|
|
g_return_val_if_fail (GST_IS_VULKAN_ENCODER (self), NULL);
|
|
|
|
priv = gst_vulkan_encoder_get_instance_private (self);
|
|
|
|
if (!priv->started)
|
|
return NULL;
|
|
|
|
return gst_caps_ref (priv->profile_caps);
|
|
}
|
|
|
|
/**
|
|
* gst_vulkan_encoder_quality_level:
|
|
* @self: a #GstVulkanEncoder
|
|
*
|
|
* Get the current encoding quality level.
|
|
*
|
|
* Returns: whether the encoder has started, it will return the quality level;
|
|
* otherwise it will return -1
|
|
*/
|
|
gint32
|
|
gst_vulkan_encoder_quality_level (GstVulkanEncoder * self)
|
|
{
|
|
GstVulkanEncoderPrivate *priv;
|
|
|
|
g_return_val_if_fail (GST_IS_VULKAN_ENCODER (self), -1);
|
|
|
|
priv = gst_vulkan_encoder_get_instance_private (self);
|
|
|
|
if (!priv->started)
|
|
return -1;
|
|
|
|
return priv->quality;
|
|
}
|
|
|
|
/**
|
|
* gst_vulkan_encoder_stop:
|
|
* @self: a #GstVulkanEncoder
|
|
*
|
|
* Stop the encoder.
|
|
*
|
|
* Returns: whether the encoder stopped correctly.
|
|
*
|
|
*/
|
|
gboolean
|
|
gst_vulkan_encoder_stop (GstVulkanEncoder * self)
|
|
{
|
|
GstVulkanEncoderPrivate *priv;
|
|
|
|
g_return_val_if_fail (GST_IS_VULKAN_ENCODER (self), FALSE);
|
|
|
|
priv = gst_vulkan_encoder_get_instance_private (self);
|
|
if (!priv->started)
|
|
return TRUE;
|
|
|
|
gst_vulkan_video_session_destroy (&priv->session);
|
|
|
|
gst_clear_caps (&priv->profile_caps);
|
|
|
|
gst_clear_vulkan_handle (&priv->session_params);
|
|
|
|
if (priv->layered_view)
|
|
gst_vulkan_image_view_unref (priv->layered_view);
|
|
gst_clear_buffer (&priv->layered_buffer);
|
|
gst_clear_object (&priv->dpb_pool);
|
|
|
|
gst_clear_object (&priv->exec);
|
|
|
|
priv->started = FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
#ifndef GST_DISABLE_GST_DEBUG
|
|
static const char *
|
|
_rate_control_mode_to_str (VkVideoEncodeRateControlModeFlagBitsKHR rc_mode)
|
|
{
|
|
const struct
|
|
{
|
|
VkVideoEncodeRateControlModeFlagBitsKHR mode;
|
|
const char *str;
|
|
} _RateControlMap[] = {
|
|
{VK_VIDEO_ENCODE_RATE_CONTROL_MODE_DEFAULT_KHR, "DEFAULT"},
|
|
#define F(mode) { G_PASTE(G_PASTE(VK_VIDEO_ENCODE_RATE_CONTROL_MODE_, mode), _BIT_KHR), G_STRINGIFY(mode) }
|
|
F (DISABLED),
|
|
F (CBR),
|
|
F (VBR),
|
|
#undef F
|
|
};
|
|
|
|
for (int i = 0; i <= G_N_ELEMENTS (_RateControlMap); i++) {
|
|
if (rc_mode == _RateControlMap[i].mode)
|
|
return _RateControlMap[i].str;
|
|
}
|
|
return "UNKNOWN";
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
_rate_control_mode_validate (GstVulkanEncoder * self,
|
|
VkVideoEncodeRateControlModeFlagBitsKHR rc_mode)
|
|
{
|
|
GstVulkanEncoderPrivate *priv =
|
|
gst_vulkan_encoder_get_instance_private (self);
|
|
|
|
if (rc_mode > VK_VIDEO_ENCODE_RATE_CONTROL_MODE_DEFAULT_KHR
|
|
&& !(priv->caps.encoder.caps.rateControlModes & rc_mode)) {
|
|
rc_mode = VK_VIDEO_ENCODE_RATE_CONTROL_MODE_DEFAULT_KHR;
|
|
for (int i = VK_VIDEO_ENCODE_RATE_CONTROL_MODE_DISABLED_BIT_KHR;
|
|
i <= VK_VIDEO_ENCODE_RATE_CONTROL_MODE_VBR_BIT_KHR; i++) {
|
|
if ((priv->caps.encoder.caps.rateControlModes) & i) {
|
|
GST_DEBUG_OBJECT (self, "rate control mode is forced to: %s",
|
|
_rate_control_mode_to_str (i));
|
|
rc_mode = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gst_vulkan_encoder_start:
|
|
* @self: a #GstVulkanEncoder
|
|
* @profile: a #GstVulkanVideoProfile
|
|
* @codec_quality_props: codec specific quality structure to fetch
|
|
* @error: (out) : an error result in case of failure or %NULL
|
|
*
|
|
* Start the encoding session according to a valid Vulkan profile
|
|
*
|
|
* Returns: whether the encoder started correctly.
|
|
*
|
|
*/
|
|
gboolean
|
|
gst_vulkan_encoder_start (GstVulkanEncoder * self,
|
|
GstVulkanVideoProfile * profile,
|
|
GstVulkanEncoderQualityProperties * codec_quality_props, GError ** error)
|
|
{
|
|
GstVulkanEncoderPrivate *priv;
|
|
VkResult res;
|
|
VkVideoSessionCreateInfoKHR session_create;
|
|
VkPhysicalDevice gpu;
|
|
VkFormat pic_format = VK_FORMAT_UNDEFINED;
|
|
int codec_idx;
|
|
GstVulkanCommandPool *cmd_pool;
|
|
VkQueryPoolVideoEncodeFeedbackCreateInfoKHR query_create;
|
|
VkPhysicalDeviceVideoEncodeQualityLevelInfoKHR quality_info;
|
|
VkVideoEncodeQualityLevelPropertiesKHR quality_props;
|
|
GError *query_err = NULL;
|
|
|
|
g_return_val_if_fail (GST_IS_VULKAN_ENCODER (self), FALSE);
|
|
g_return_val_if_fail (profile != NULL, FALSE);
|
|
g_return_val_if_fail (codec_quality_props != NULL, FALSE);
|
|
|
|
priv = gst_vulkan_encoder_get_instance_private (self);
|
|
|
|
if (priv->started)
|
|
return TRUE;
|
|
|
|
if (!_populate_function_table (self)) {
|
|
g_set_error (error, GST_VULKAN_ERROR, VK_ERROR_INITIALIZATION_FAILED,
|
|
"Couldn't load Vulkan Video functions");
|
|
return FALSE;
|
|
}
|
|
|
|
switch (self->codec) {
|
|
case VK_VIDEO_CODEC_OPERATION_ENCODE_H264_BIT_KHR:
|
|
if (!gst_vulkan_video_profile_is_valid (profile, self->codec)) {
|
|
g_set_error (error, GST_VULKAN_ERROR, VK_ERROR_INITIALIZATION_FAILED,
|
|
"Invalid profile");
|
|
return FALSE;
|
|
}
|
|
priv->caps.encoder.codec.h264 = (VkVideoEncodeH264CapabilitiesKHR) {
|
|
/* *INDENT-OFF* */
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_CAPABILITIES_KHR,
|
|
/* *INDENT-ON* */
|
|
};
|
|
codec_idx = GST_VK_VIDEO_EXTENSION_ENCODE_H264;
|
|
break;
|
|
case VK_VIDEO_CODEC_OPERATION_ENCODE_H265_BIT_KHR:
|
|
if (!gst_vulkan_video_profile_is_valid (profile, self->codec)) {
|
|
g_set_error (error, GST_VULKAN_ERROR, VK_ERROR_INITIALIZATION_FAILED,
|
|
"Invalid profile");
|
|
return FALSE;
|
|
}
|
|
priv->caps.encoder.codec.h265 = (VkVideoEncodeH265CapabilitiesKHR) {
|
|
/* *INDENT-OFF* */
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H265_CAPABILITIES_KHR,
|
|
/* *INDENT-ON* */
|
|
};
|
|
codec_idx = GST_VK_VIDEO_EXTENSION_ENCODE_H265;
|
|
break;
|
|
default:
|
|
g_set_error (error, GST_VULKAN_ERROR, VK_ERROR_INITIALIZATION_FAILED,
|
|
"Invalid codec");
|
|
return FALSE;
|
|
}
|
|
|
|
priv->profile = *profile;
|
|
|
|
/* ensure the chain up of structure */
|
|
priv->profile.usage.encode.pNext = &priv->profile.codec;
|
|
priv->profile.profile.pNext = &priv->profile.usage.encode;
|
|
|
|
/* *INDENT-OFF* */
|
|
priv->caps.encoder.caps = (VkVideoEncodeCapabilitiesKHR) {
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_CAPABILITIES_KHR,
|
|
.pNext = &priv->caps.encoder.codec,
|
|
};
|
|
priv->caps.caps = (VkVideoCapabilitiesKHR) {
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_CAPABILITIES_KHR,
|
|
.pNext = &priv->caps.encoder.caps,
|
|
};
|
|
/* *INDENT-ON* */
|
|
|
|
gpu = gst_vulkan_device_get_physical_device (self->queue->device);
|
|
res = priv->vk.GetPhysicalDeviceVideoCapabilities (gpu,
|
|
&priv->profile.profile, &priv->caps.caps);
|
|
if (gst_vulkan_error_to_g_error (res, error,
|
|
"vkGetPhysicalDeviceVideoCapabilitiesKHR") != VK_SUCCESS)
|
|
return FALSE;
|
|
|
|
if (_vk_codec_extensions[codec_idx].specVersion <
|
|
_vk_codec_supported_extensions[codec_idx]) {
|
|
g_set_error (error, GST_VULKAN_ERROR, VK_ERROR_INITIALIZATION_FAILED,
|
|
"STD version headers [%i.%i.%i] not supported, need at least [%i.%i.%i], check your SDK path.",
|
|
VK_CODEC_VERSION (_vk_codec_extensions[codec_idx].specVersion),
|
|
VK_CODEC_VERSION (_vk_codec_supported_extensions[codec_idx]));
|
|
return FALSE;
|
|
}
|
|
|
|
if (_vk_codec_extensions[codec_idx].specVersion <
|
|
priv->caps.caps.stdHeaderVersion.specVersion) {
|
|
g_set_error (error, GST_VULKAN_ERROR, VK_ERROR_INITIALIZATION_FAILED,
|
|
"The driver needs a newer version [%i.%i.%i] of the current headers %d.%d.%d, please update the code to support this driver.",
|
|
VK_CODEC_VERSION (priv->caps.caps.stdHeaderVersion.specVersion),
|
|
VK_CODEC_VERSION (_vk_codec_extensions[codec_idx].specVersion));
|
|
return FALSE;
|
|
}
|
|
|
|
/* Get output format */
|
|
pic_format = gst_vulkan_video_encoder_get_format (self,
|
|
VK_IMAGE_USAGE_VIDEO_ENCODE_SRC_BIT_KHR |
|
|
VK_IMAGE_USAGE_VIDEO_ENCODE_DPB_BIT_KHR, error);
|
|
if (pic_format == VK_FORMAT_UNDEFINED)
|
|
return FALSE;
|
|
|
|
cmd_pool = gst_vulkan_queue_create_command_pool (self->queue, error);
|
|
if (!cmd_pool)
|
|
return FALSE;
|
|
priv->exec = gst_vulkan_operation_new (cmd_pool);
|
|
gst_object_unref (cmd_pool);
|
|
|
|
/* we don't want overridden parameters in queries */
|
|
/* *INDENT-OFF* */
|
|
query_create = (VkQueryPoolVideoEncodeFeedbackCreateInfoKHR) {
|
|
.sType = VK_STRUCTURE_TYPE_QUERY_POOL_VIDEO_ENCODE_FEEDBACK_CREATE_INFO_KHR,
|
|
.pNext = &profile->profile,
|
|
.encodeFeedbackFlags = priv->caps.encoder.caps.supportedEncodeFeedbackFlags &
|
|
(~VK_VIDEO_ENCODE_FEEDBACK_BITSTREAM_HAS_OVERRIDES_BIT_KHR),
|
|
};
|
|
/* *INDENT-ON* */
|
|
|
|
if (!gst_vulkan_operation_enable_query (priv->exec,
|
|
VK_QUERY_TYPE_VIDEO_ENCODE_FEEDBACK_KHR, 1, &query_create,
|
|
&query_err)) {
|
|
if (query_err->code != VK_ERROR_FEATURE_NOT_PRESENT) {
|
|
g_propagate_error (error, query_err);
|
|
goto failed;
|
|
}
|
|
g_clear_error (&query_err);
|
|
}
|
|
|
|
priv->profile_caps = gst_vulkan_video_profile_to_caps (&priv->profile);
|
|
|
|
GST_LOG_OBJECT (self, "Encoder capabilities for %" GST_PTR_FORMAT ":\n"
|
|
" Codec header version: %i.%i.%i (driver), %i.%i.%i (compiled)\n"
|
|
" Width from %i to %i\n"
|
|
" Height from %i to %i\n"
|
|
" Width granularity: %i\n"
|
|
" Height granularity: %i\n"
|
|
" Bitstream offset alignment: %" G_GUINT64_FORMAT "\n"
|
|
" Bitstream size alignment: %" G_GUINT64_FORMAT "\n"
|
|
" Maximum reference slots: %u\n"
|
|
" Maximum active references: %u\n"
|
|
" encode maximum bitrate: %" G_GUINT64_FORMAT "\n"
|
|
" encode quality levels: %i\n"
|
|
" encode image width granularity: %i\n"
|
|
" encode image height granularity: %i\n"
|
|
" encode pool feedback bitstream:%s%s%s%s\n"
|
|
" encode rate-control modes:%s%s\n"
|
|
" Capability flags:%s%s%s\n",
|
|
priv->profile_caps,
|
|
VK_CODEC_VERSION (priv->caps.caps.stdHeaderVersion.specVersion),
|
|
VK_CODEC_VERSION (_vk_codec_extensions[codec_idx].specVersion),
|
|
priv->caps.caps.minCodedExtent.width,
|
|
priv->caps.caps.maxCodedExtent.width,
|
|
priv->caps.caps.minCodedExtent.height,
|
|
priv->caps.caps.maxCodedExtent.height,
|
|
priv->caps.caps.pictureAccessGranularity.width,
|
|
priv->caps.caps.pictureAccessGranularity.height,
|
|
priv->caps.caps.minBitstreamBufferOffsetAlignment,
|
|
priv->caps.caps.minBitstreamBufferSizeAlignment,
|
|
priv->caps.caps.maxDpbSlots,
|
|
priv->caps.caps.maxActiveReferencePictures,
|
|
priv->caps.encoder.caps.maxBitrate,
|
|
priv->caps.encoder.caps.maxQualityLevels,
|
|
priv->caps.encoder.caps.encodeInputPictureGranularity.width,
|
|
priv->caps.encoder.caps.encodeInputPictureGranularity.height,
|
|
priv->caps.encoder.caps.supportedEncodeFeedbackFlags ? "" : " none",
|
|
priv->caps.encoder.caps.supportedEncodeFeedbackFlags &
|
|
VK_VIDEO_ENCODE_FEEDBACK_BITSTREAM_BUFFER_OFFSET_BIT_KHR ?
|
|
" buffer_offset" : "",
|
|
priv->caps.encoder.caps.supportedEncodeFeedbackFlags &
|
|
VK_VIDEO_ENCODE_FEEDBACK_BITSTREAM_BYTES_WRITTEN_BIT_KHR ?
|
|
" bytes_written" : "",
|
|
priv->caps.encoder.caps.supportedEncodeFeedbackFlags &
|
|
VK_VIDEO_ENCODE_FEEDBACK_BITSTREAM_HAS_OVERRIDES_BIT_KHR ?
|
|
" has_overrides" : "",
|
|
priv->caps.encoder.caps.rateControlModes &
|
|
VK_VIDEO_ENCODE_RATE_CONTROL_MODE_CBR_BIT_KHR ? " cbr" : "",
|
|
priv->caps.encoder.caps.rateControlModes &
|
|
VK_VIDEO_ENCODE_RATE_CONTROL_MODE_VBR_BIT_KHR ? " vbr" : "",
|
|
priv->caps.caps.flags ? "" : " none",
|
|
priv->caps.caps.flags &
|
|
VK_VIDEO_CAPABILITY_PROTECTED_CONTENT_BIT_KHR ? " protected" : "",
|
|
priv->caps.caps.flags &
|
|
VK_VIDEO_CAPABILITY_SEPARATE_REFERENCE_IMAGES_BIT_KHR ?
|
|
" separate_references" : "");
|
|
|
|
priv->layered_dpb =
|
|
!(priv->caps.
|
|
caps.flags & VK_VIDEO_CAPABILITY_SEPARATE_REFERENCE_IMAGES_BIT_KHR);
|
|
|
|
if (codec_quality_props->quality_level >= 0) {
|
|
priv->quality = MIN (codec_quality_props->quality_level,
|
|
priv->caps.encoder.caps.maxQualityLevels - 1);
|
|
} else {
|
|
priv->quality = priv->caps.encoder.caps.maxQualityLevels / 2;
|
|
}
|
|
|
|
/* *INDENT-OFF* */
|
|
quality_info = (VkPhysicalDeviceVideoEncodeQualityLevelInfoKHR) {
|
|
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VIDEO_ENCODE_QUALITY_LEVEL_INFO_KHR,
|
|
.pVideoProfile = &profile->profile,
|
|
.qualityLevel = priv->quality,
|
|
};
|
|
quality_props = (VkVideoEncodeQualityLevelPropertiesKHR) {
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_QUALITY_LEVEL_PROPERTIES_KHR,
|
|
.pNext = &codec_quality_props->codec,
|
|
};
|
|
/* *INDENT-ON* */
|
|
|
|
res = priv->vk.GetPhysicalDeviceVideoEncodeQualityLevelProperties (gpu,
|
|
&quality_info, &quality_props);
|
|
if (gst_vulkan_error_to_g_error (res, error,
|
|
"vketPhysicalDeviceVideoEncodeQualityLevelPropertiesKHR")
|
|
!= VK_SUCCESS)
|
|
goto failed;
|
|
|
|
/* *INDENT-OFF* */
|
|
session_create = (VkVideoSessionCreateInfoKHR) {
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_SESSION_CREATE_INFO_KHR,
|
|
.queueFamilyIndex = self->queue->family,
|
|
.pVideoProfile = &profile->profile,
|
|
.pictureFormat = pic_format,
|
|
.maxCodedExtent = priv->caps.caps.maxCodedExtent,
|
|
.referencePictureFormat = pic_format,
|
|
.maxDpbSlots = priv->caps.caps.maxDpbSlots,
|
|
.maxActiveReferencePictures = priv->caps.caps.maxActiveReferencePictures,
|
|
.pStdHeaderVersion = &_vk_codec_extensions[codec_idx],
|
|
};
|
|
/* *INDENT-ON* */
|
|
|
|
if (!gst_vulkan_video_session_create (&priv->session, self->queue->device,
|
|
&priv->vk, &session_create, error))
|
|
goto failed;
|
|
|
|
/* check rate control mode if it was set before start */
|
|
_rate_control_mode_validate (self, priv->rc_mode);
|
|
|
|
priv->session_reset = TRUE;
|
|
priv->started = TRUE;
|
|
|
|
return TRUE;
|
|
|
|
failed:
|
|
gst_clear_object (&priv->exec);
|
|
gst_clear_caps (&priv->profile_caps);
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* gst_vulkan_encoder_update_video_session_parameters:
|
|
* @self: a #GstVulkanEncoder
|
|
* @params: a #GstVulkanEncoderParameters
|
|
* @error: (out) (optional): an error result in case of failure
|
|
*
|
|
* Set the sessions parameters to be used by the encoder
|
|
*
|
|
* Returns: whether the encoder updated the session parameters correctly.
|
|
*
|
|
*/
|
|
gboolean
|
|
gst_vulkan_encoder_update_video_session_parameters (GstVulkanEncoder * self,
|
|
GstVulkanEncoderParameters * params, GError ** error)
|
|
{
|
|
GstVulkanEncoderPrivate *priv;
|
|
GstVulkanHandle *handle;
|
|
|
|
g_return_val_if_fail (GST_IS_VULKAN_ENCODER (self), FALSE);
|
|
g_return_val_if_fail (params != NULL, FALSE);
|
|
|
|
handle =
|
|
gst_vulkan_encoder_new_video_session_parameters (self, params, error);
|
|
if (!handle)
|
|
return FALSE;
|
|
|
|
priv = gst_vulkan_encoder_get_instance_private (self);
|
|
|
|
gst_clear_vulkan_handle (&priv->session_params);
|
|
priv->session_params = handle;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gst_vulkan_encoder_video_session_parameters_overrides:
|
|
* @self: a #GstVulkanEncoder
|
|
* @params: a #GstVulkanEncoderParametersOverrides
|
|
* @feedback: (out) (optional): #GstVulkanEncoderParametersFeedback or %NULL
|
|
* @data_size: (out) (optional): the allocated size of @data
|
|
* @data: (out) (optional): location to store the requested overrides, use
|
|
* g_free() to free after use
|
|
*
|
|
* 42.15.1. Codec-Specific Semantics
|
|
*
|
|
* Implementations supporting video encode operations for any particular video
|
|
* codec operation often support only a subset of the available encoding tools
|
|
* defined by the corresponding video compression standards.
|
|
*
|
|
* … this specification allows implementations to override the value of any of
|
|
* the codec-specific parameters,
|
|
*
|
|
* Returns: whether the encoder has bew sessions parameters.
|
|
*
|
|
*/
|
|
gboolean
|
|
gst_vulkan_encoder_video_session_parameters_overrides (GstVulkanEncoder * self,
|
|
GstVulkanEncoderParametersOverrides * params,
|
|
GstVulkanEncoderParametersFeedback * feedback, gsize * data_size,
|
|
gpointer * data, GError ** error)
|
|
{
|
|
VkVideoEncodeSessionParametersGetInfoKHR video_params_info;
|
|
VkVideoEncodeSessionParametersFeedbackInfoKHR feedback_info;
|
|
VkResult res;
|
|
GstVulkanEncoderPrivate *priv;
|
|
gsize size;
|
|
gpointer param_data;
|
|
gboolean write;
|
|
|
|
g_return_val_if_fail (GST_IS_VULKAN_ENCODER (self), FALSE);
|
|
g_return_val_if_fail (params != NULL && feedback != NULL, FALSE);
|
|
|
|
priv = gst_vulkan_encoder_get_instance_private (self);
|
|
if (!priv->started)
|
|
return FALSE;
|
|
|
|
switch (self->codec) {
|
|
case VK_VIDEO_CODEC_OPERATION_ENCODE_H264_BIT_KHR:
|
|
if (params->h264.sType !=
|
|
VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_SESSION_PARAMETERS_GET_INFO_KHR) {
|
|
gst_vulkan_error_to_g_error (GST_VULKAN_ERROR, error,
|
|
"Invalid parameter for H.264");
|
|
return FALSE;
|
|
}
|
|
write = params->h264.writeStdPPS || params->h264.writeStdSPS;
|
|
if (feedback) {
|
|
feedback->h264.sType =
|
|
VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_SESSION_PARAMETERS_FEEDBACK_INFO_KHR;
|
|
}
|
|
break;
|
|
case VK_VIDEO_CODEC_OPERATION_ENCODE_H265_BIT_KHR:
|
|
if (params->h265.sType !=
|
|
VK_STRUCTURE_TYPE_VIDEO_ENCODE_H265_SESSION_PARAMETERS_GET_INFO_KHR) {
|
|
gst_vulkan_error_to_g_error (GST_VULKAN_ERROR, error,
|
|
"Invalid parameter for H.265");
|
|
return FALSE;
|
|
}
|
|
write = params->h265.writeStdPPS || params->h265.writeStdSPS
|
|
|| params->h265.writeStdVPS;
|
|
if (feedback) {
|
|
feedback->h265.sType =
|
|
VK_STRUCTURE_TYPE_VIDEO_ENCODE_H265_SESSION_PARAMETERS_FEEDBACK_INFO_KHR;
|
|
}
|
|
break;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
/* *INDENT-OFF* */
|
|
video_params_info = (VkVideoEncodeSessionParametersGetInfoKHR) {
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_SESSION_PARAMETERS_GET_INFO_KHR,
|
|
.pNext = params,
|
|
.videoSessionParameters = priv->session_params->handle,
|
|
};
|
|
|
|
feedback_info = (VkVideoEncodeSessionParametersFeedbackInfoKHR) {
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_SESSION_PARAMETERS_FEEDBACK_INFO_KHR,
|
|
.pNext = feedback,
|
|
};
|
|
/* *INDENT-ON* */
|
|
|
|
res = priv->vk.GetEncodedVideoSessionParameters (self->queue->device->device,
|
|
&video_params_info, &feedback_info, &size, NULL);
|
|
if (gst_vulkan_error_to_g_error (res, error,
|
|
"vGetEncodedVideoSessionParametersKHR") != VK_SUCCESS)
|
|
return FALSE;
|
|
|
|
/* FIXME: forcing because a bug in NVIDIA driver */
|
|
feedback_info.hasOverrides = 1;
|
|
if (!feedback_info.hasOverrides || !data || !write)
|
|
return TRUE;
|
|
|
|
GST_DEBUG_OBJECT (self, "allocating for bitstream parameters %"
|
|
G_GSIZE_FORMAT, size);
|
|
param_data = g_malloc (size);
|
|
|
|
res = priv->vk.GetEncodedVideoSessionParameters (self->queue->device->device,
|
|
&video_params_info, &feedback_info, &size, param_data);
|
|
if (gst_vulkan_error_to_g_error (res, error,
|
|
"vGetEncodedVideoSessionParametersKHR") != VK_SUCCESS)
|
|
return FALSE;
|
|
|
|
if (data_size)
|
|
*data_size = size;
|
|
*data = param_data;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* gst_vulkan_encoder_create_dpb_pool:
|
|
* @self: a #GstVulkanEncoder
|
|
* @caps: the #GstCaps of the DPB pool
|
|
*
|
|
* Instantiates an internal Vulkan image pool for driver encoders whose output
|
|
* buffers can be used as DPB buffers.
|
|
*
|
|
* Returns: whether the pool was created.
|
|
*/
|
|
gboolean
|
|
gst_vulkan_encoder_create_dpb_pool (GstVulkanEncoder * self, GstCaps * caps)
|
|
{
|
|
GstVulkanEncoderPrivate *priv;
|
|
GstCaps *profile_caps;
|
|
GstStructure *config;
|
|
guint min_buffers, max_buffers;
|
|
GstFlowReturn ret;
|
|
|
|
g_return_val_if_fail (GST_IS_VULKAN_ENCODER (self), FALSE);
|
|
g_return_val_if_fail (GST_IS_CAPS (caps), FALSE);
|
|
|
|
priv = gst_vulkan_encoder_get_instance_private (self);
|
|
|
|
if (!priv->started)
|
|
return FALSE;
|
|
|
|
if ((!priv->layered_dpb && priv->dpb_pool)
|
|
|| (priv->layered_dpb && priv->layered_buffer))
|
|
return TRUE;
|
|
|
|
if (priv->layered_dpb) {
|
|
min_buffers = max_buffers = 1;
|
|
} else {
|
|
min_buffers = priv->caps.caps.maxDpbSlots;
|
|
max_buffers = 0;
|
|
}
|
|
|
|
priv->dpb_pool = gst_vulkan_image_buffer_pool_new (self->queue->device);
|
|
|
|
config = gst_buffer_pool_get_config (priv->dpb_pool);
|
|
gst_buffer_pool_config_set_params (config, caps, 1024, min_buffers,
|
|
max_buffers);
|
|
gst_vulkan_image_buffer_pool_config_set_allocation_params (config,
|
|
VK_IMAGE_USAGE_VIDEO_ENCODE_DPB_BIT_KHR,
|
|
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, VK_IMAGE_LAYOUT_VIDEO_ENCODE_DPB_KHR,
|
|
VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT);
|
|
|
|
if (priv->layered_dpb) {
|
|
gst_structure_set (config, "num-layers", G_TYPE_UINT,
|
|
priv->caps.caps.maxDpbSlots, NULL);
|
|
}
|
|
profile_caps = gst_vulkan_encoder_profile_caps (self);
|
|
gst_vulkan_image_buffer_pool_config_set_encode_caps (config, profile_caps);
|
|
gst_caps_unref (profile_caps);
|
|
|
|
if (!gst_buffer_pool_set_config (priv->dpb_pool, config))
|
|
goto bail;
|
|
if (!gst_buffer_pool_set_active (priv->dpb_pool, TRUE))
|
|
goto bail;
|
|
|
|
if (priv->layered_dpb) {
|
|
ret = gst_buffer_pool_acquire_buffer (priv->dpb_pool, &priv->layered_buffer,
|
|
NULL);
|
|
if (ret != GST_FLOW_OK)
|
|
goto bail;
|
|
|
|
priv->layered_view =
|
|
gst_vulkan_video_image_create_view (priv->layered_buffer,
|
|
priv->layered_dpb, FALSE, NULL);
|
|
|
|
gst_clear_object (&priv->dpb_pool);
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
bail:
|
|
gst_clear_object (&priv->dpb_pool);
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
_setup_rate_control (GstVulkanEncoder * self, GstVulkanEncoderPicture * pic,
|
|
GstVideoInfo * info, VkVideoEncodeRateControlInfoKHR * rc_info,
|
|
VkVideoEncodeRateControlLayerInfoKHR * rc_layer)
|
|
{
|
|
GstVulkanEncoderPrivate *priv;
|
|
|
|
priv = gst_vulkan_encoder_get_instance_private (self);
|
|
|
|
g_assert (priv->callbacks.setup_rc_pic);
|
|
|
|
/* *INDENT-OFF* */
|
|
*rc_info = (VkVideoEncodeRateControlInfoKHR) {
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_RATE_CONTROL_INFO_KHR,
|
|
.rateControlMode = priv->rc_mode,
|
|
};
|
|
/* *INDENT-ON* */
|
|
|
|
if (priv->rc_mode > VK_VIDEO_ENCODE_RATE_CONTROL_MODE_DISABLED_BIT_KHR) {
|
|
/* *INDENT-OFF* */
|
|
*rc_layer = (VkVideoEncodeRateControlLayerInfoKHR) {
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_RATE_CONTROL_LAYER_INFO_KHR,
|
|
.averageBitrate = 0, /* to be filled in callback */
|
|
.maxBitrate = 0, /* to be filled in callback */
|
|
.frameRateNumerator = GST_VIDEO_INFO_FPS_N (info),
|
|
.frameRateDenominator = GST_VIDEO_INFO_FPS_D (info),
|
|
};
|
|
/* *INDENT-ON* */
|
|
|
|
rc_info->layerCount++;
|
|
rc_info->pLayers = rc_layer;
|
|
}
|
|
|
|
priv->callbacks.setup_rc_pic (pic, rc_info, rc_layer,
|
|
priv->callbacks_user_data);
|
|
}
|
|
|
|
/**
|
|
* gst_vulkan_encoder_encode:
|
|
* @self: a #GstVulkanEncoder
|
|
* @info: the #GstVideoInfo of the @pic to process
|
|
* @pic: a #GstVulkanEncoderPicture
|
|
* @nb_refs: number of @ref_pics
|
|
* @ref_pics: an array of #GstVulkanEncoderPicture
|
|
*
|
|
* Encode a picture according to its reference pictures.
|
|
*
|
|
* Returns: whether the encode process completed successfully.
|
|
*
|
|
*/
|
|
gboolean
|
|
gst_vulkan_encoder_encode (GstVulkanEncoder * self, GstVideoInfo * info,
|
|
GstVulkanEncoderPicture * pic, guint nb_refs,
|
|
GstVulkanEncoderPicture ** ref_pics)
|
|
{
|
|
GstVulkanEncoderPrivate *priv;
|
|
GError *err = NULL;
|
|
gboolean ret = TRUE;
|
|
GstMemory *mem;
|
|
int i, slot_index = -1;
|
|
GstVulkanEncodeQueryResult *encode_res;
|
|
VkVideoCodingControlInfoKHR coding_ctrl;
|
|
VkVideoBeginCodingInfoKHR begin_coding;
|
|
VkVideoEncodeInfoKHR encode_info;
|
|
VkVideoEndCodingInfoKHR end_coding;
|
|
VkVideoReferenceSlotInfoKHR ref_slots[37];
|
|
GstVulkanCommandBuffer *cmd_buf;
|
|
GArray *barriers;
|
|
VkVideoEncodeQualityLevelInfoKHR quality_info;
|
|
VkVideoEncodeRateControlLayerInfoKHR rc_layer;
|
|
VkVideoEncodeRateControlInfoKHR rc_info;
|
|
|
|
g_return_val_if_fail (GST_IS_VULKAN_ENCODER (self), FALSE);
|
|
g_return_val_if_fail (info != NULL && pic != NULL, FALSE);
|
|
|
|
priv = gst_vulkan_encoder_get_instance_private (self);
|
|
|
|
/* initialize the vulkan operation */
|
|
if (!gst_vulkan_operation_begin (priv->exec, &err))
|
|
goto bail;
|
|
|
|
_setup_rate_control (self, pic, info, &rc_info, &rc_layer);
|
|
|
|
/* *INDENT-OFF* */
|
|
quality_info = (VkVideoEncodeQualityLevelInfoKHR) {
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_QUALITY_LEVEL_INFO_KHR,
|
|
.pNext = &rc_info,
|
|
.qualityLevel = priv->quality,
|
|
};
|
|
coding_ctrl = (VkVideoCodingControlInfoKHR) {
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_CODING_CONTROL_INFO_KHR,
|
|
.pNext = &quality_info,
|
|
.flags = VK_VIDEO_CODING_CONTROL_ENCODE_QUALITY_LEVEL_BIT_KHR
|
|
| VK_VIDEO_CODING_CONTROL_ENCODE_RATE_CONTROL_BIT_KHR
|
|
| VK_VIDEO_CODING_CONTROL_RESET_BIT_KHR,
|
|
};
|
|
/* *INDENT-ON* */
|
|
|
|
g_assert (pic->dpb_buffer && pic->dpb_view);
|
|
g_assert (pic->in_buffer && pic->img_view);
|
|
g_assert (pic->out_buffer);
|
|
|
|
/* Attribute a free slot index to the picture to be used later as a reference.
|
|
* The picture is kept until it remains useful to the encoding process.*/
|
|
for (i = 0; i < priv->caps.caps.maxDpbSlots; i++) {
|
|
if (!priv->slots[i]) {
|
|
priv->slots[i] = pic;
|
|
slot_index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Set the ref slots according to the pic refs to bound the video
|
|
session encoding. It should contain all the references + 1 to book
|
|
a new slotIndex (-1) for the current picture. */
|
|
/* *INDENT-OFF* */
|
|
pic->dpb = (VkVideoPictureResourceInfoKHR) {
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_PICTURE_RESOURCE_INFO_KHR,
|
|
.pNext = NULL,
|
|
.codedOffset = { 0, 0 },
|
|
.codedExtent = {
|
|
.width = GST_VIDEO_INFO_WIDTH (info),
|
|
.height = GST_VIDEO_INFO_HEIGHT (info),
|
|
},
|
|
.baseArrayLayer = 0,
|
|
.imageViewBinding = pic->dpb_view->view,
|
|
};
|
|
pic->dpb_slot = (VkVideoReferenceSlotInfoKHR) {
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_REFERENCE_SLOT_INFO_KHR,
|
|
.pNext = NULL, /* to fill in callback */
|
|
.slotIndex = slot_index,
|
|
.pPictureResource = &pic->dpb,
|
|
};
|
|
/* *INDENT-ON* */
|
|
|
|
for (i = 0; i < nb_refs; i++)
|
|
ref_slots[i] = ref_pics[i]->dpb_slot;
|
|
|
|
ref_slots[nb_refs] = pic->dpb_slot;
|
|
ref_slots[nb_refs].slotIndex = -1;
|
|
|
|
/* Setup the begin coding structure using the reference slots */
|
|
/* *INDENT-OFF* */
|
|
begin_coding = (VkVideoBeginCodingInfoKHR) {
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_BEGIN_CODING_INFO_KHR,
|
|
.pNext = !priv->session_reset ? &rc_info : NULL,
|
|
.videoSession = priv->session.session->handle,
|
|
.videoSessionParameters = priv->session_params->handle,
|
|
.referenceSlotCount = nb_refs + 1,
|
|
.pReferenceSlots = ref_slots,
|
|
};
|
|
/* *INDENT-ON* */
|
|
|
|
cmd_buf = priv->exec->cmd_buf;
|
|
priv->vk.CmdBeginVideoCoding (cmd_buf->cmd, &begin_coding);
|
|
|
|
/* 42.9. Video Coding Control
|
|
To apply dynamic controls to the currently bound video session object such as
|
|
quality information. This should be done when requesting a new coding contol ie
|
|
first attempt of encoding.
|
|
*/
|
|
if (priv->session_reset) {
|
|
priv->vk.CmdControlVideoCoding (cmd_buf->cmd, &coding_ctrl);
|
|
priv->session_reset = FALSE;
|
|
}
|
|
|
|
/* Peek the output memory to be used by VkVideoEncodeInfoKHR.dstBuffer */
|
|
mem = gst_buffer_peek_memory (pic->out_buffer, 0);
|
|
|
|
/* Setup the encode info */
|
|
/* *INDENT-OFF* */
|
|
encode_info = (VkVideoEncodeInfoKHR) {
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_INFO_KHR,
|
|
.pNext = NULL, /* to fill in callback */
|
|
.flags = 0x0,
|
|
.dstBuffer = ((GstVulkanBufferMemory *) mem)->buffer,
|
|
.dstBufferOffset = pic->offset,
|
|
.dstBufferRange = gst_memory_get_sizes (mem, NULL, NULL),
|
|
.srcPictureResource = (VkVideoPictureResourceInfoKHR) {
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_PICTURE_RESOURCE_INFO_KHR,
|
|
.pNext = NULL,
|
|
.codedOffset = { 0, 0 },
|
|
.codedExtent = {
|
|
.width = GST_VIDEO_INFO_WIDTH (info),
|
|
.height = GST_VIDEO_INFO_HEIGHT (info),
|
|
},
|
|
.baseArrayLayer = 0,
|
|
.imageViewBinding = pic->img_view->view,
|
|
},
|
|
.pSetupReferenceSlot = &pic->dpb_slot,
|
|
.referenceSlotCount = nb_refs,
|
|
.pReferenceSlots = ref_slots,
|
|
.precedingExternallyEncodedBytes = 0,
|
|
};
|
|
/* *INDENT-ON* */
|
|
|
|
encode_info.dstBufferRange -= encode_info.dstBufferOffset;
|
|
encode_info.dstBufferRange = GST_ROUND_DOWN_N (encode_info.dstBufferRange,
|
|
priv->caps.caps.minBitstreamBufferSizeAlignment);
|
|
|
|
g_assert (priv->callbacks.setup_codec_pic);
|
|
priv->callbacks.setup_codec_pic (pic, &encode_info,
|
|
priv->callbacks_user_data);
|
|
|
|
gst_vulkan_operation_add_dependency_frame (priv->exec, pic->in_buffer,
|
|
VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
|
VK_PIPELINE_STAGE_2_VIDEO_ENCODE_BIT_KHR);
|
|
gst_vulkan_operation_add_frame_barrier (priv->exec, pic->in_buffer,
|
|
VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
|
VK_PIPELINE_STAGE_2_VIDEO_ENCODE_BIT_KHR,
|
|
VK_ACCESS_2_VIDEO_ENCODE_READ_BIT_KHR,
|
|
VK_IMAGE_LAYOUT_VIDEO_ENCODE_SRC_KHR, NULL);
|
|
|
|
gst_vulkan_operation_add_dependency_frame (priv->exec, pic->dpb_buffer,
|
|
VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
|
VK_PIPELINE_STAGE_2_VIDEO_ENCODE_BIT_KHR);
|
|
gst_vulkan_operation_add_frame_barrier (priv->exec, pic->dpb_buffer,
|
|
VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
|
|
VK_PIPELINE_STAGE_2_VIDEO_ENCODE_BIT_KHR,
|
|
VK_ACCESS_2_VIDEO_ENCODE_READ_BIT_KHR,
|
|
VK_IMAGE_LAYOUT_VIDEO_ENCODE_DPB_KHR, NULL);
|
|
|
|
barriers = gst_vulkan_operation_retrieve_image_barriers (priv->exec);
|
|
|
|
/* *INDENT-OFF* */
|
|
vkCmdPipelineBarrier2 (cmd_buf->cmd, &(VkDependencyInfo) {
|
|
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
|
|
.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT,
|
|
.pImageMemoryBarriers = (VkImageMemoryBarrier2 *) barriers->data,
|
|
.imageMemoryBarrierCount = barriers->len,
|
|
}
|
|
);
|
|
/* *INDENT-ON* */
|
|
g_array_unref (barriers);
|
|
|
|
gst_vulkan_operation_begin_query (priv->exec, 0);
|
|
priv->vk.CmdEncodeVideo (cmd_buf->cmd, &encode_info);
|
|
gst_vulkan_operation_end_query (priv->exec, 0);
|
|
|
|
/* *INDENT-OFF* */
|
|
end_coding = (VkVideoEndCodingInfoKHR) {
|
|
.sType = VK_STRUCTURE_TYPE_VIDEO_END_CODING_INFO_KHR,
|
|
};
|
|
/* *INDENT-ON* */
|
|
|
|
/* 41.5 4. vkCmdEndVideoCodingKHR signals the end of the recording of the
|
|
* Vulkan Video Context, as established by vkCmdBeginVideoCodingKHR. */
|
|
priv->vk.CmdEndVideoCoding (cmd_buf->cmd, &end_coding);
|
|
|
|
if (!gst_vulkan_operation_end (priv->exec, &err)) {
|
|
GST_ERROR_OBJECT (self, "The operation did not complete properly");
|
|
goto bail;
|
|
}
|
|
/* Wait the operation to complete or we might have a failing query */
|
|
gst_vulkan_operation_wait (priv->exec);
|
|
|
|
gst_vulkan_operation_get_query (priv->exec, (gpointer *) & encode_res, &err);
|
|
if (encode_res->status == VK_QUERY_RESULT_STATUS_COMPLETE_KHR) {
|
|
GST_INFO_OBJECT (self, "The frame %p has been encoded with size %"
|
|
G_GUINT64_FORMAT, pic, encode_res->data_size + pic->offset);
|
|
gst_buffer_set_size (pic->out_buffer, encode_res->data_size + pic->offset);
|
|
} else {
|
|
GST_ERROR_OBJECT (self,
|
|
"The operation did not complete properly, query status = %d",
|
|
encode_res->status);
|
|
goto bail;
|
|
}
|
|
|
|
return ret;
|
|
bail:
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gst_vulkan_create_encoder_from_queue:
|
|
* @queue: a #GstVulkanQueue
|
|
* @codec: (type guint): the VkVideoCodecOperationFlagBitsKHR to encode
|
|
*
|
|
* Creates a #GstVulkanEncoder object if @codec encoding is supported by @queue
|
|
*
|
|
* Returns: (transfer full) (nullable): the #GstVulkanEncoder object
|
|
*
|
|
*/
|
|
GstVulkanEncoder *
|
|
gst_vulkan_encoder_create_from_queue (GstVulkanQueue * queue, guint codec)
|
|
{
|
|
GstVulkanPhysicalDevice *device;
|
|
GstVulkanEncoder *encoder;
|
|
guint flags, expected_flag, supported_video_ops;
|
|
const char *extension;
|
|
|
|
g_return_val_if_fail (GST_IS_VULKAN_QUEUE (queue), NULL);
|
|
|
|
device = queue->device->physical_device;
|
|
expected_flag = VK_QUEUE_VIDEO_ENCODE_BIT_KHR;
|
|
flags = device->queue_family_props[queue->family].queueFlags;
|
|
supported_video_ops = device->queue_family_ops[queue->family].video;
|
|
|
|
if (device->properties.apiVersion < VK_MAKE_VERSION (1, 3, 275)) {
|
|
GST_WARNING_OBJECT (queue,
|
|
"API version %d.%d.%d doesn't support video encode extensions",
|
|
VK_VERSION_MAJOR (device->properties.apiVersion),
|
|
VK_VERSION_MINOR (device->properties.apiVersion),
|
|
VK_VERSION_PATCH (device->properties.apiVersion));
|
|
return NULL;
|
|
}
|
|
|
|
switch (codec) {
|
|
case VK_VIDEO_CODEC_OPERATION_ENCODE_H264_BIT_KHR:
|
|
extension = VK_KHR_VIDEO_ENCODE_H264_EXTENSION_NAME;
|
|
break;
|
|
case VK_VIDEO_CODEC_OPERATION_ENCODE_H265_BIT_KHR:
|
|
extension = VK_KHR_VIDEO_ENCODE_H265_EXTENSION_NAME;
|
|
break;
|
|
default:
|
|
GST_WARNING_OBJECT (queue, "Unsupported codec");
|
|
return NULL;
|
|
}
|
|
|
|
if ((flags & expected_flag) != expected_flag) {
|
|
GST_WARNING_OBJECT (queue, "Queue doesn't support encoding");
|
|
return NULL;
|
|
}
|
|
if ((supported_video_ops & codec) != codec) {
|
|
GST_WARNING_OBJECT (queue, "Queue doesn't support codec encoding");
|
|
return NULL;
|
|
}
|
|
|
|
if (!(gst_vulkan_device_is_extension_enabled (queue->device,
|
|
VK_KHR_VIDEO_QUEUE_EXTENSION_NAME)
|
|
&& gst_vulkan_device_is_extension_enabled (queue->device,
|
|
VK_KHR_VIDEO_ENCODE_QUEUE_EXTENSION_NAME)
|
|
&& gst_vulkan_device_is_extension_enabled (queue->device, extension)))
|
|
return NULL;
|
|
|
|
encoder = g_object_new (GST_TYPE_VULKAN_ENCODER, NULL);
|
|
gst_object_ref_sink (encoder);
|
|
encoder->queue = gst_object_ref (queue);
|
|
encoder->codec = codec;
|
|
|
|
return encoder;
|
|
}
|
|
|
|
void
|
|
gst_vulkan_encoder_set_callbacks (GstVulkanEncoder * self,
|
|
GstVulkanEncoderCallbacks * callbacks, gpointer user_data,
|
|
GDestroyNotify notify)
|
|
{
|
|
GstVulkanEncoderPrivate *priv;
|
|
|
|
g_return_if_fail (GST_IS_VULKAN_ENCODER (self) && callbacks);
|
|
|
|
priv = gst_vulkan_encoder_get_instance_private (self);
|
|
|
|
priv->callbacks = *callbacks;
|
|
if (priv->callbacks_user_data && priv->callbacks_notify)
|
|
priv->callbacks_notify (priv->callbacks_user_data);
|
|
priv->callbacks_user_data = user_data;
|
|
priv->callbacks_notify = notify;
|
|
}
|
|
|
|
void
|
|
gst_vulkan_encoder_set_rc_mode (GstVulkanEncoder * self,
|
|
VkVideoEncodeRateControlModeFlagBitsKHR rc_mode)
|
|
{
|
|
GstVulkanEncoderPrivate *priv;
|
|
|
|
g_return_if_fail (GST_IS_VULKAN_ENCODER (self));
|
|
|
|
priv = gst_vulkan_encoder_get_instance_private (self);
|
|
|
|
if (priv->rc_mode == rc_mode)
|
|
return;
|
|
|
|
if (priv->started)
|
|
_rate_control_mode_validate (self, rc_mode);
|
|
|
|
priv->session_reset = TRUE;
|
|
priv->rc_mode = rc_mode;
|
|
}
|
|
|
|
GType
|
|
gst_vulkan_encoder_rate_control_mode_get_type (void)
|
|
{
|
|
static GType type = 0;
|
|
|
|
if (type == 0) {
|
|
/* *INDENT-OFF* */
|
|
static const GEnumValue values[] = {
|
|
{ VK_VIDEO_ENCODE_RATE_CONTROL_MODE_DEFAULT_KHR,
|
|
"Driver's default", "default" },
|
|
{ VK_VIDEO_ENCODE_RATE_CONTROL_MODE_DISABLED_BIT_KHR,
|
|
"Constant quantizer", "cqp" },
|
|
{ VK_VIDEO_ENCODE_RATE_CONTROL_MODE_CBR_BIT_KHR,
|
|
"Constant bitrate", "cbr" },
|
|
{ VK_VIDEO_ENCODE_RATE_CONTROL_MODE_VBR_BIT_KHR,
|
|
"Variable bitrate", "vbr" },
|
|
{ 0, }
|
|
};
|
|
/* *INDENT-ON* */
|
|
|
|
type = g_enum_register_static ("GstVulkanEncoderRateControlMode", values);
|
|
}
|
|
return type;
|
|
}
|