mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-30 13:41:48 +00:00
4a22cc8fb3
VA drivers allocate surfaces given their properties, so there's no need to provide a buffer size to the VA pool. Though, the buffer size is provided by the driver, or the canonical size is used for single planed surfaces. This patch removes the need to provide a size for the function gst_va_pool_new_with_config() and adds a helper method to retrieve the surface size, gst_va_pool_get_buffer_size(). Also change the callers accordingly. Changes for custom VA pool creation will be addressed in the following commits. Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5805>
1642 lines
48 KiB
C++
1642 lines
48 KiB
C++
/* GStreamer
|
|
* Copyright (C) 2021 Seungha Yang <seungha@centricular.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "gstqsvencoder.h"
|
|
#include <mfxvideo++.h>
|
|
#include <string.h>
|
|
#include <string>
|
|
|
|
#ifdef G_OS_WIN32
|
|
#include "gstqsvallocator_d3d11.h"
|
|
|
|
#include <wrl.h>
|
|
|
|
/* *INDENT-OFF* */
|
|
using namespace Microsoft::WRL;
|
|
/* *INDENT-ON* */
|
|
#else
|
|
#include "gstqsvallocator_va.h"
|
|
#endif /* G_OS_WIN32 */
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (gst_qsv_encoder_debug);
|
|
#define GST_CAT_DEFAULT gst_qsv_encoder_debug
|
|
|
|
/**
|
|
* GstQsvCodingOption:
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
GType
|
|
gst_qsv_coding_option_get_type (void)
|
|
{
|
|
static GType coding_opt_type = 0;
|
|
static const GEnumValue coding_opts[] = {
|
|
/**
|
|
* GstQsvCodingOption::unknown:
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
{MFX_CODINGOPTION_UNKNOWN, "Unknown", "unknown"},
|
|
|
|
/**
|
|
* GstQsvCodingOption::on:
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
{MFX_CODINGOPTION_ON, "On", "on"},
|
|
|
|
/**
|
|
* GstQsvCodingOption::off:
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
{MFX_CODINGOPTION_OFF, "Off", "off"},
|
|
{0, nullptr, nullptr}
|
|
};
|
|
|
|
GST_QSV_CALL_ONCE_BEGIN {
|
|
coding_opt_type = g_enum_register_static ("GstQsvCodingOption",
|
|
coding_opts);
|
|
} GST_QSV_CALL_ONCE_END;
|
|
|
|
return coding_opt_type;
|
|
}
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_ADAPTER_LUID,
|
|
PROP_DEVICE_PATH,
|
|
PROP_TARGET_USAGE,
|
|
PROP_LOW_LATENCY,
|
|
};
|
|
|
|
#define DEFAULT_TARGET_USAGE MFX_TARGETUSAGE_BALANCED
|
|
#define DEFAULT_LOW_LATENCY FALSE
|
|
|
|
typedef struct _GstQsvEncoderSurface
|
|
{
|
|
mfxFrameSurface1 surface;
|
|
mfxEncodeCtrl encode_control;
|
|
|
|
/* array of mfxPayload (e.g., SEI data) associated with this surface */
|
|
GPtrArray *payload;
|
|
|
|
/* holds ownership */
|
|
GstQsvFrame *qsv_frame;
|
|
} GstQsvEncoderSurface;
|
|
|
|
typedef struct _GstQsvEncoderTask
|
|
{
|
|
mfxSyncPoint sync_point;
|
|
mfxBitstream bitstream;
|
|
} GstQsvEncoderTask;
|
|
|
|
struct _GstQsvEncoderPrivate
|
|
{
|
|
GstObject *device;
|
|
|
|
GstVideoCodecState *input_state;
|
|
GstQsvAllocator *allocator;
|
|
|
|
/* API specific alignment requirement (multiple of 16 or 32) */
|
|
GstVideoInfo aligned_info;
|
|
|
|
mfxSession session;
|
|
mfxVideoParam video_param;
|
|
|
|
/* List of mfxExtBuffer configured by subclass, subclass will hold
|
|
* allocated memory for each mfxExtBuffer */
|
|
GPtrArray *extra_params;
|
|
|
|
MFXVideoENCODE *encoder;
|
|
GstQsvMemoryType mem_type;
|
|
|
|
/* Internal buffer pool used to allocate fallback buffer when input buffer
|
|
* is not compatible with expected format/type/resolution etc */
|
|
GstBufferPool *internal_pool;
|
|
|
|
/* Array of GstQsvEncoderSurface, holding ownership */
|
|
GArray *surface_pool;
|
|
guint next_surface_index;
|
|
|
|
/* Array of GstQsvEncoderTask, holding ownership */
|
|
GArray *task_pool;
|
|
|
|
GQueue free_tasks;
|
|
GQueue pending_tasks;
|
|
|
|
/* Properties */
|
|
guint target_usage;
|
|
gboolean low_latency;
|
|
};
|
|
|
|
/**
|
|
* GstQsvEncoder:
|
|
*
|
|
* Base class for Intel Quick Sync video encoders
|
|
*
|
|
* Since: 1.22
|
|
*/
|
|
#define gst_qsv_encoder_parent_class parent_class
|
|
G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GstQsvEncoder, gst_qsv_encoder,
|
|
GST_TYPE_VIDEO_ENCODER, G_ADD_PRIVATE (GstQsvEncoder);
|
|
GST_DEBUG_CATEGORY_INIT (gst_qsv_encoder_debug,
|
|
"qsvencoder", 0, "qsvencoder"));
|
|
|
|
static void gst_qsv_encoder_dispose (GObject * object);
|
|
static void gst_qsv_encoder_finalize (GObject * object);
|
|
static void gst_qsv_encoder_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec);
|
|
static void gst_qsv_encoder_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec);
|
|
|
|
static void gst_qsv_encoder_set_context (GstElement * element,
|
|
GstContext * context);
|
|
|
|
static gboolean gst_qsv_encoder_open (GstVideoEncoder * encoder);
|
|
static gboolean gst_qsv_encoder_stop (GstVideoEncoder * encoder);
|
|
static gboolean gst_qsv_encoder_close (GstVideoEncoder * encoder);
|
|
static gboolean gst_qsv_encoder_set_format (GstVideoEncoder * encoder,
|
|
GstVideoCodecState * state);
|
|
static GstFlowReturn gst_qsv_encoder_handle_frame (GstVideoEncoder * encoder,
|
|
GstVideoCodecFrame * frame);
|
|
static GstFlowReturn gst_qsv_encoder_finish (GstVideoEncoder * encoder);
|
|
static gboolean gst_qsv_encoder_flush (GstVideoEncoder * encoder);
|
|
static gboolean gst_qsv_encoder_sink_query (GstVideoEncoder * encoder,
|
|
GstQuery * query);
|
|
static gboolean gst_qsv_encoder_src_query (GstVideoEncoder * encoder,
|
|
GstQuery * query);
|
|
static gboolean gst_qsv_encoder_propose_allocation (GstVideoEncoder * encoder,
|
|
GstQuery * query);
|
|
|
|
static void gst_qsv_encoder_surface_clear (GstQsvEncoderSurface * task);
|
|
static void gst_qsv_encoder_task_clear (GstQsvEncoderTask * task);
|
|
|
|
static void
|
|
gst_qsv_encoder_class_init (GstQsvEncoderClass * klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
|
|
GstVideoEncoderClass *videoenc_class = GST_VIDEO_ENCODER_CLASS (klass);
|
|
GParamFlags param_flags = (GParamFlags) (GST_PARAM_DOC_SHOW_DEFAULT |
|
|
GST_PARAM_CONDITIONALLY_AVAILABLE | G_PARAM_READABLE |
|
|
G_PARAM_STATIC_STRINGS);
|
|
|
|
object_class->dispose = gst_qsv_encoder_dispose;
|
|
object_class->finalize = gst_qsv_encoder_finalize;
|
|
object_class->set_property = gst_qsv_encoder_set_property;
|
|
object_class->get_property = gst_qsv_encoder_get_property;
|
|
|
|
#ifdef G_OS_WIN32
|
|
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, param_flags));
|
|
#else
|
|
g_object_class_install_property (object_class, PROP_DEVICE_PATH,
|
|
g_param_spec_string ("device-path", "Device Path",
|
|
"DRM device path", nullptr, param_flags));
|
|
#endif
|
|
|
|
g_object_class_install_property (object_class, PROP_TARGET_USAGE,
|
|
g_param_spec_uint ("target-usage", "Target Usage",
|
|
"1: Best quality, 4: Balanced, 7: Best speed",
|
|
1, 7, DEFAULT_TARGET_USAGE,
|
|
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
|
|
|
g_object_class_install_property (object_class, PROP_LOW_LATENCY,
|
|
g_param_spec_boolean ("low-latency", "Low Latency",
|
|
"Enables low-latency encoding", DEFAULT_LOW_LATENCY,
|
|
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
|
|
|
element_class->set_context = GST_DEBUG_FUNCPTR (gst_qsv_encoder_set_context);
|
|
|
|
videoenc_class->open = GST_DEBUG_FUNCPTR (gst_qsv_encoder_open);
|
|
videoenc_class->stop = GST_DEBUG_FUNCPTR (gst_qsv_encoder_stop);
|
|
videoenc_class->close = GST_DEBUG_FUNCPTR (gst_qsv_encoder_close);
|
|
videoenc_class->set_format = GST_DEBUG_FUNCPTR (gst_qsv_encoder_set_format);
|
|
videoenc_class->handle_frame =
|
|
GST_DEBUG_FUNCPTR (gst_qsv_encoder_handle_frame);
|
|
videoenc_class->finish = GST_DEBUG_FUNCPTR (gst_qsv_encoder_finish);
|
|
videoenc_class->flush = GST_DEBUG_FUNCPTR (gst_qsv_encoder_flush);
|
|
videoenc_class->sink_query = GST_DEBUG_FUNCPTR (gst_qsv_encoder_sink_query);
|
|
videoenc_class->src_query = GST_DEBUG_FUNCPTR (gst_qsv_encoder_src_query);
|
|
videoenc_class->propose_allocation =
|
|
GST_DEBUG_FUNCPTR (gst_qsv_encoder_propose_allocation);
|
|
|
|
gst_type_mark_as_plugin_api (GST_TYPE_QSV_ENCODER, (GstPluginAPIFlags) 0);
|
|
gst_type_mark_as_plugin_api (GST_TYPE_QSV_CODING_OPTION,
|
|
(GstPluginAPIFlags) 0);
|
|
}
|
|
|
|
static void
|
|
gst_qsv_encoder_init (GstQsvEncoder * self)
|
|
{
|
|
GstQsvEncoderPrivate *priv;
|
|
|
|
priv = self->priv =
|
|
(GstQsvEncoderPrivate *) gst_qsv_encoder_get_instance_private (self);
|
|
|
|
priv->extra_params = g_ptr_array_sized_new (8);
|
|
|
|
priv->surface_pool = g_array_new (FALSE, TRUE, sizeof (GstQsvEncoderSurface));
|
|
g_array_set_clear_func (priv->surface_pool,
|
|
(GDestroyNotify) gst_qsv_encoder_surface_clear);
|
|
|
|
priv->task_pool = g_array_new (FALSE, TRUE, sizeof (GstQsvEncoderTask));
|
|
g_array_set_clear_func (priv->task_pool,
|
|
(GDestroyNotify) gst_qsv_encoder_task_clear);
|
|
|
|
g_queue_init (&priv->free_tasks);
|
|
g_queue_init (&priv->pending_tasks);
|
|
|
|
priv->target_usage = DEFAULT_TARGET_USAGE;
|
|
priv->low_latency = DEFAULT_LOW_LATENCY;
|
|
}
|
|
|
|
static void
|
|
gst_qsv_encoder_dispose (GObject * object)
|
|
{
|
|
GstQsvEncoder *self = GST_QSV_ENCODER (object);
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
|
|
gst_clear_object (&priv->device);
|
|
|
|
G_OBJECT_CLASS (parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
gst_qsv_encoder_finalize (GObject * object)
|
|
{
|
|
GstQsvEncoder *self = GST_QSV_ENCODER (object);
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
|
|
g_ptr_array_unref (priv->extra_params);
|
|
g_array_unref (priv->task_pool);
|
|
g_array_unref (priv->surface_pool);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
gst_qsv_encoder_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstQsvEncoder *self = GST_QSV_ENCODER (object);
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
|
|
switch (prop_id) {
|
|
case PROP_TARGET_USAGE:
|
|
priv->target_usage = g_value_get_uint (value);
|
|
break;
|
|
case PROP_LOW_LATENCY:
|
|
priv->low_latency = g_value_get_boolean (value);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_qsv_encoder_get_property (GObject * object, guint prop_id, GValue * value,
|
|
GParamSpec * pspec)
|
|
{
|
|
GstQsvEncoder *self = GST_QSV_ENCODER (object);
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
GstQsvEncoderClass *klass = GST_QSV_ENCODER_GET_CLASS (self);
|
|
|
|
switch (prop_id) {
|
|
case PROP_ADAPTER_LUID:
|
|
g_value_set_int64 (value, klass->adapter_luid);
|
|
break;
|
|
case PROP_DEVICE_PATH:
|
|
g_value_set_string (value, klass->display_path);
|
|
break;
|
|
case PROP_TARGET_USAGE:
|
|
g_value_set_uint (value, priv->target_usage);
|
|
break;
|
|
case PROP_LOW_LATENCY:
|
|
g_value_set_boolean (value, priv->low_latency);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_qsv_encoder_set_context (GstElement * element, GstContext * context)
|
|
{
|
|
GstQsvEncoder *self = GST_QSV_ENCODER (element);
|
|
GstQsvEncoderClass *klass = GST_QSV_ENCODER_GET_CLASS (element);
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
|
|
#ifdef G_OS_WIN32
|
|
gst_d3d11_handle_set_context_for_adapter_luid (element,
|
|
context, klass->adapter_luid, (GstD3D11Device **) & priv->device);
|
|
#else
|
|
gst_va_handle_set_context (element, context, klass->display_path,
|
|
(GstVaDisplay **) & priv->device);
|
|
#endif
|
|
|
|
GST_ELEMENT_CLASS (parent_class)->set_context (element, context);
|
|
}
|
|
|
|
#ifdef G_OS_WIN32
|
|
static gboolean
|
|
gst_qsv_encoder_open_platform_device (GstQsvEncoder * self)
|
|
{
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
GstQsvEncoderClass *klass = GST_QSV_ENCODER_GET_CLASS (self);
|
|
ComPtr < ID3D10Multithread > multi_thread;
|
|
HRESULT hr;
|
|
ID3D11Device *device_handle;
|
|
mfxStatus status;
|
|
GstD3D11Device *device;
|
|
|
|
if (!gst_d3d11_ensure_element_data_for_adapter_luid (GST_ELEMENT (self),
|
|
klass->adapter_luid, (GstD3D11Device **) & priv->device)) {
|
|
GST_ERROR_OBJECT (self, "d3d11 device is unavailable");
|
|
return FALSE;
|
|
}
|
|
|
|
device = GST_D3D11_DEVICE_CAST (priv->device);
|
|
priv->allocator = gst_qsv_d3d11_allocator_new (device);
|
|
|
|
/* For D3D11 device handle to be used by QSV, multithread protection layer
|
|
* must be enabled before the MFXVideoCORE_SetHandle() call.
|
|
*
|
|
* TODO: Need to check performance impact by this mutithread protection layer,
|
|
* since it may have a negative impact on overall pipeline performance.
|
|
* If so, we should create encoding session dedicated d3d11 device and
|
|
* make use of shared resource */
|
|
device_handle = gst_d3d11_device_get_device_handle (device);
|
|
hr = device_handle->QueryInterface (IID_PPV_ARGS (&multi_thread));
|
|
if (!gst_d3d11_result (hr, device)) {
|
|
GST_ERROR_OBJECT (self, "ID3D10Multithread interface is unavailable");
|
|
return FALSE;
|
|
}
|
|
|
|
multi_thread->SetMultithreadProtected (TRUE);
|
|
status = MFXVideoCORE_SetHandle (priv->session, MFX_HANDLE_D3D11_DEVICE,
|
|
device_handle);
|
|
if (status != MFX_ERR_NONE) {
|
|
GST_ERROR_OBJECT (self, "Failed to set d3d11 device handle");
|
|
return FALSE;
|
|
}
|
|
|
|
/* NOTE: We never use this mfxFrameAllocator to allocate memory from our side,
|
|
* but required for QSV because:
|
|
* 1) QSV may request memory allocation for encoder's internal usage,
|
|
* MFX_FOURCC_P8 for example
|
|
* 2) Our mfxFrameAllocator provides bridge layer for
|
|
* gst_video_frame_{map,unmap} and mfxFrameAllocator::{Lock,Unlock},
|
|
* including mfxFrameAllocator::GetHDL.
|
|
* 3) GstQsvAllocator provides GstQsvFrame pool, and therefore allocated
|
|
* GstQsvFrame struct can be re-used without per-frame malloc/free
|
|
*/
|
|
status = MFXVideoCORE_SetFrameAllocator (priv->session,
|
|
gst_qsv_allocator_get_allocator_handle (priv->allocator));
|
|
if (status != MFX_ERR_NONE) {
|
|
GST_ERROR_OBJECT (self, "Failed to set frame allocator %d", status);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
#else
|
|
static gboolean
|
|
gst_qsv_encoder_open_platform_device (GstQsvEncoder * self)
|
|
{
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
GstQsvEncoderClass *klass = GST_QSV_ENCODER_GET_CLASS (self);
|
|
mfxStatus status;
|
|
GstVaDisplay *display;
|
|
|
|
if (!gst_va_ensure_element_data (GST_ELEMENT (self), klass->display_path,
|
|
(GstVaDisplay **) & priv->device)) {
|
|
GST_ERROR_OBJECT (self, "VA display is unavailable");
|
|
return FALSE;
|
|
}
|
|
|
|
display = GST_VA_DISPLAY (priv->device);
|
|
|
|
priv->allocator = gst_qsv_va_allocator_new (display);
|
|
|
|
status = MFXVideoCORE_SetHandle (priv->session, MFX_HANDLE_VA_DISPLAY,
|
|
gst_va_display_get_va_dpy (display));
|
|
if (status != MFX_ERR_NONE) {
|
|
GST_ERROR_OBJECT (self, "Failed to set VA display handle");
|
|
return FALSE;
|
|
}
|
|
|
|
status = MFXVideoCORE_SetFrameAllocator (priv->session,
|
|
gst_qsv_allocator_get_allocator_handle (priv->allocator));
|
|
if (status != MFX_ERR_NONE) {
|
|
GST_ERROR_OBJECT (self, "Failed to set frame allocator %d", status);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
static gboolean
|
|
gst_qsv_encoder_open (GstVideoEncoder * encoder)
|
|
{
|
|
GstQsvEncoder *self = GST_QSV_ENCODER (encoder);
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
GstQsvEncoderClass *klass = GST_QSV_ENCODER_GET_CLASS (self);
|
|
mfxStatus status;
|
|
|
|
status = MFXCreateSession (gst_qsv_get_loader (), klass->impl_index,
|
|
&priv->session);
|
|
if (status != MFX_ERR_NONE) {
|
|
GST_ERROR_OBJECT (self, "Failed to create session");
|
|
return FALSE;
|
|
}
|
|
|
|
if (!gst_qsv_encoder_open_platform_device (self)) {
|
|
g_clear_pointer (&priv->session, MFXClose);
|
|
gst_clear_object (&priv->allocator);
|
|
gst_clear_object (&priv->device);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_qsv_encoder_reset (GstQsvEncoder * self)
|
|
{
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
|
|
if (priv->encoder) {
|
|
delete priv->encoder;
|
|
priv->encoder = nullptr;
|
|
}
|
|
|
|
if (priv->internal_pool) {
|
|
gst_buffer_pool_set_active (priv->internal_pool, FALSE);
|
|
gst_clear_object (&priv->internal_pool);
|
|
}
|
|
|
|
g_array_set_size (priv->surface_pool, 0);
|
|
g_array_set_size (priv->task_pool, 0);
|
|
g_queue_clear (&priv->free_tasks);
|
|
g_queue_clear (&priv->pending_tasks);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_qsv_encoder_stop (GstVideoEncoder * encoder)
|
|
{
|
|
GstQsvEncoder *self = GST_QSV_ENCODER (encoder);
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
|
|
gst_qsv_encoder_reset (self);
|
|
g_clear_pointer (&priv->input_state, gst_video_codec_state_unref);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_qsv_encoder_close (GstVideoEncoder * encoder)
|
|
{
|
|
GstQsvEncoder *self = GST_QSV_ENCODER (encoder);
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
|
|
g_clear_pointer (&priv->session, MFXClose);
|
|
gst_clear_object (&priv->allocator);
|
|
gst_clear_object (&priv->device);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
gst_qsv_encoder_payload_clear (mfxPayload * payload)
|
|
{
|
|
if (!payload)
|
|
return;
|
|
|
|
g_free (payload->Data);
|
|
g_free (payload);
|
|
}
|
|
|
|
static void
|
|
gst_qsv_encoder_surface_reset (GstQsvEncoderSurface * surface)
|
|
{
|
|
if (!surface)
|
|
return;
|
|
|
|
gst_clear_qsv_frame (&surface->qsv_frame);
|
|
g_ptr_array_set_size (surface->payload, 0);
|
|
memset (&surface->encode_control, 0, sizeof (mfxEncodeCtrl));
|
|
}
|
|
|
|
static void
|
|
gst_qsv_encoder_surface_clear (GstQsvEncoderSurface * surface)
|
|
{
|
|
if (!surface)
|
|
return;
|
|
|
|
gst_qsv_encoder_surface_reset (surface);
|
|
g_clear_pointer (&surface->payload, g_ptr_array_unref);
|
|
memset (&surface->surface, 0, sizeof (mfxFrameSurface1));
|
|
}
|
|
|
|
static void
|
|
gst_qsv_encoder_task_reset (GstQsvEncoder * self, GstQsvEncoderTask * task)
|
|
{
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
|
|
if (!task)
|
|
return;
|
|
|
|
task->sync_point = nullptr;
|
|
task->bitstream.DataLength = 0;
|
|
g_queue_push_head (&priv->free_tasks, task);
|
|
}
|
|
|
|
static void
|
|
gst_qsv_encoder_task_clear (GstQsvEncoderTask * task)
|
|
{
|
|
if (!task)
|
|
return;
|
|
|
|
g_clear_pointer (&task->bitstream.Data, g_free);
|
|
memset (&task->bitstream, 0, sizeof (mfxBitstream));
|
|
}
|
|
|
|
static GstQsvEncoderSurface *
|
|
gst_qsv_encoder_get_next_surface (GstQsvEncoder * self)
|
|
{
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
GstQsvEncoderSurface *surface = nullptr;
|
|
|
|
for (guint i = priv->next_surface_index; i < priv->surface_pool->len; i++) {
|
|
GstQsvEncoderSurface *iter =
|
|
&g_array_index (priv->surface_pool, GstQsvEncoderSurface, i);
|
|
|
|
/* This means surface is still being used by QSV */
|
|
if (iter->surface.Data.Locked > 0)
|
|
continue;
|
|
|
|
surface = iter;
|
|
priv->next_surface_index = i;
|
|
goto out;
|
|
}
|
|
|
|
for (guint i = 0; i < priv->next_surface_index; i++) {
|
|
GstQsvEncoderSurface *iter =
|
|
&g_array_index (priv->surface_pool, GstQsvEncoderSurface, i);
|
|
|
|
/* This means surface is still being used by QSV */
|
|
if (iter->surface.Data.Locked > 0)
|
|
continue;
|
|
|
|
surface = iter;
|
|
priv->next_surface_index = i;
|
|
goto out;
|
|
}
|
|
|
|
/* Magic number to avoid too large pool size */
|
|
if (priv->surface_pool->len > 64) {
|
|
GST_ERROR_OBJECT (self,
|
|
"No availble surface but pool size is too large already");
|
|
return nullptr;
|
|
}
|
|
|
|
/* Something went wrong, increase surface pool size */
|
|
GST_INFO_OBJECT (self, "No useable surfaces, increasing pool size to %d",
|
|
priv->surface_pool->len + 1);
|
|
|
|
g_array_set_size (priv->surface_pool, priv->surface_pool->len + 1);
|
|
surface = &g_array_index (priv->surface_pool, GstQsvEncoderSurface,
|
|
priv->surface_pool->len - 1);
|
|
|
|
memset (surface, 0, sizeof (GstQsvEncoderSurface));
|
|
surface->surface.Info =
|
|
g_array_index (priv->surface_pool, GstQsvEncoderSurface, 0).surface.Info;
|
|
surface->payload = g_ptr_array_new_with_free_func ((GDestroyNotify)
|
|
gst_qsv_encoder_payload_clear);
|
|
|
|
out:
|
|
priv->next_surface_index++;
|
|
priv->next_surface_index %= priv->surface_pool->len;
|
|
|
|
gst_qsv_encoder_surface_reset (surface);
|
|
return surface;
|
|
}
|
|
|
|
static mfxStatus
|
|
gst_qsv_encoder_encode_frame (GstQsvEncoder * self,
|
|
GstQsvEncoderSurface * surface, GstQsvEncoderTask * task, mfxU64 timestamp)
|
|
{
|
|
mfxFrameSurface1 *s;
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
mfxStatus status;
|
|
guint retry_count = 0;
|
|
/* magic number */
|
|
const guint retry_threshold = 100;
|
|
mfxEncodeCtrl *encode_ctrl;
|
|
|
|
if (surface) {
|
|
s = &surface->surface;
|
|
s->Data.MemId = (mfxMemId) surface->qsv_frame;
|
|
s->Data.TimeStamp = timestamp;
|
|
encode_ctrl = &surface->encode_control;
|
|
} else {
|
|
/* draining */
|
|
s = nullptr;
|
|
encode_ctrl = nullptr;
|
|
}
|
|
|
|
do {
|
|
status = priv->encoder->EncodeFrameAsync (encode_ctrl,
|
|
s, &task->bitstream, &task->sync_point);
|
|
|
|
/* XXX: probably we should try to drain pending tasks if any in this case
|
|
* as documented? */
|
|
if (status == MFX_WRN_DEVICE_BUSY && retry_count < retry_threshold) {
|
|
GST_INFO_OBJECT (self, "GPU is busy, retry count (%d/%d)",
|
|
retry_count, retry_threshold);
|
|
retry_count++;
|
|
|
|
/* Magic number 10ms */
|
|
g_usleep (10000);
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
} while (TRUE);
|
|
|
|
return status;
|
|
}
|
|
|
|
static GstVideoCodecFrame *
|
|
gst_qsv_encoder_find_output_frame (GstQsvEncoder * self, GstClockTime pts)
|
|
{
|
|
GList *frames, *iter;
|
|
GstVideoCodecFrame *ret = nullptr;
|
|
GstVideoCodecFrame *closest = nullptr;
|
|
guint64 min_pts_abs_diff = 0;
|
|
|
|
/* give up, just returns the oldest frame */
|
|
if (!GST_CLOCK_TIME_IS_VALID (pts))
|
|
return gst_video_encoder_get_oldest_frame (GST_VIDEO_ENCODER (self));
|
|
|
|
frames = gst_video_encoder_get_frames (GST_VIDEO_ENCODER (self));
|
|
|
|
for (iter = frames; iter; iter = g_list_next (iter)) {
|
|
GstVideoCodecFrame *frame = (GstVideoCodecFrame *) iter->data;
|
|
guint64 abs_diff;
|
|
|
|
if (!GST_CLOCK_TIME_IS_VALID (frame->pts))
|
|
continue;
|
|
|
|
if (pts == frame->pts) {
|
|
ret = frame;
|
|
break;
|
|
}
|
|
|
|
if (pts >= frame->pts)
|
|
abs_diff = pts - frame->pts;
|
|
else
|
|
abs_diff = frame->pts - pts;
|
|
|
|
if (!closest || abs_diff < min_pts_abs_diff) {
|
|
closest = frame;
|
|
min_pts_abs_diff = abs_diff;
|
|
}
|
|
}
|
|
|
|
if (!ret && closest)
|
|
ret = closest;
|
|
|
|
if (ret) {
|
|
gst_video_codec_frame_ref (ret);
|
|
} else {
|
|
ret = gst_video_encoder_get_oldest_frame (GST_VIDEO_ENCODER (self));
|
|
}
|
|
|
|
if (frames)
|
|
g_list_free_full (frames, (GDestroyNotify) gst_video_codec_frame_unref);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_qsv_encoder_finish_frame (GstQsvEncoder * self, GstQsvEncoderTask * task,
|
|
gboolean discard)
|
|
{
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
GstQsvEncoderClass *klass = GST_QSV_ENCODER_GET_CLASS (self);
|
|
mfxStatus status;
|
|
mfxBitstream *bs;
|
|
GstVideoCodecFrame *frame;
|
|
GstClockTime qsv_pts = GST_CLOCK_TIME_NONE;
|
|
GstClockTime qsv_dts = GST_CLOCK_TIME_NONE;
|
|
GstBuffer *buffer;
|
|
gboolean keyframe = FALSE;
|
|
guint retry_count = 0;
|
|
/* magic number */
|
|
const guint retry_threshold = 100;
|
|
|
|
status = MFX_ERR_NONE;
|
|
do {
|
|
/* magic number 100 ms */
|
|
status = MFXVideoCORE_SyncOperation (priv->session, task->sync_point, 100);
|
|
|
|
/* Retry up to 10 sec (100 ms x 100 times), that should be enough time for
|
|
* encoding a frame using hardware */
|
|
if (status == MFX_WRN_IN_EXECUTION && retry_count < retry_threshold) {
|
|
GST_DEBUG_OBJECT (self,
|
|
"Operation is still in execution, retry count (%d/%d)",
|
|
retry_count, retry_threshold);
|
|
retry_count++;
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
} while (TRUE);
|
|
|
|
if (discard) {
|
|
gst_qsv_encoder_task_reset (self, task);
|
|
return GST_FLOW_OK;
|
|
}
|
|
|
|
if (status != MFX_ERR_NONE && status != MFX_ERR_NONE_PARTIAL_OUTPUT) {
|
|
gst_qsv_encoder_task_reset (self, task);
|
|
|
|
if (status == MFX_ERR_ABORTED) {
|
|
GST_INFO_OBJECT (self, "Operation was aborted");
|
|
return GST_FLOW_FLUSHING;
|
|
}
|
|
|
|
GST_WARNING_OBJECT (self, "SyncOperation returned %d (%s)",
|
|
QSV_STATUS_ARGS (status));
|
|
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
bs = &task->bitstream;
|
|
qsv_pts = gst_qsv_timestamp_to_gst (bs->TimeStamp);
|
|
|
|
/* SDK runtime seems to report zero DTS for all fraems in case of VP9.
|
|
* It sounds SDK bug, but we can workaround it safely because VP9 B-frame is
|
|
* not supported in this implementation.
|
|
*
|
|
* Also we perfer our nanoseconds timestamp instead of QSV's timescale.
|
|
* So let' ignore QSV's timescale for non-{h264,h265} cases.
|
|
*
|
|
* TODO: We may need to use DTS for MPEG2 (not implemented yet)
|
|
*/
|
|
if (klass->codec_id == MFX_CODEC_AVC || klass->codec_id == MFX_CODEC_HEVC)
|
|
qsv_dts = gst_qsv_timestamp_to_gst ((mfxU64) bs->DecodeTimeStamp);
|
|
|
|
if ((bs->FrameType & MFX_FRAMETYPE_IDR) != 0)
|
|
keyframe = TRUE;
|
|
|
|
if (klass->create_output_buffer) {
|
|
buffer = klass->create_output_buffer (self, bs);
|
|
} else {
|
|
buffer = gst_buffer_new_memdup (bs->Data + bs->DataOffset, bs->DataLength);
|
|
}
|
|
gst_qsv_encoder_task_reset (self, task);
|
|
|
|
if (!buffer) {
|
|
GST_ERROR_OBJECT (self, "No output buffer");
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
|
|
frame = gst_qsv_encoder_find_output_frame (self, qsv_pts);
|
|
if (frame) {
|
|
if (GST_CLOCK_TIME_IS_VALID (qsv_dts)) {
|
|
frame->pts = qsv_pts;
|
|
frame->dts = qsv_dts;
|
|
} else {
|
|
frame->dts = frame->pts;
|
|
}
|
|
|
|
frame->output_buffer = buffer;
|
|
|
|
if (keyframe)
|
|
GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (frame);
|
|
|
|
return gst_video_encoder_finish_frame (GST_VIDEO_ENCODER (self), frame);
|
|
}
|
|
|
|
/* Empty available frame, something went wrong but we can just push this
|
|
* buffer */
|
|
GST_WARNING_OBJECT (self, "Failed to find corresponding frame");
|
|
GST_BUFFER_PTS (buffer) = qsv_pts;
|
|
if (GST_CLOCK_TIME_IS_VALID (qsv_dts))
|
|
GST_BUFFER_DTS (buffer) = qsv_dts;
|
|
else
|
|
GST_BUFFER_DTS (buffer) = qsv_pts;
|
|
|
|
if (!keyframe)
|
|
GST_BUFFER_FLAG_SET (buffer, GST_BUFFER_FLAG_DELTA_UNIT);
|
|
|
|
return gst_pad_push (GST_VIDEO_ENCODER_SRC_PAD (self), buffer);
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_qsv_encoder_drain (GstQsvEncoder * self, gboolean discard)
|
|
{
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
mfxStatus status = MFX_ERR_NONE;
|
|
GstFlowReturn ret = GST_FLOW_OK;
|
|
GstQsvEncoderTask *task;
|
|
|
|
if (!priv->session || !priv->encoder)
|
|
return GST_FLOW_OK;
|
|
|
|
GST_DEBUG_OBJECT (self, "Drain");
|
|
|
|
/* Drain pending tasks first if any */
|
|
while (g_queue_get_length (&priv->pending_tasks) > 0) {
|
|
task = (GstQsvEncoderTask *) g_queue_pop_tail (&priv->pending_tasks);
|
|
ret = gst_qsv_encoder_finish_frame (self, task, discard);
|
|
}
|
|
|
|
while (status == MFX_ERR_NONE) {
|
|
task = (GstQsvEncoderTask *) g_queue_pop_tail (&priv->free_tasks);
|
|
status = gst_qsv_encoder_encode_frame (self,
|
|
nullptr, task, MFX_TIMESTAMP_UNKNOWN);
|
|
|
|
/* once it's fully drained, then driver will return more data */
|
|
if (status == MFX_ERR_NONE && task->sync_point) {
|
|
ret = gst_qsv_encoder_finish_frame (self, task, discard);
|
|
continue;
|
|
}
|
|
|
|
if (status != MFX_ERR_MORE_DATA)
|
|
GST_WARNING_OBJECT (self, "Unexpected status return %d (%s)",
|
|
QSV_STATUS_ARGS (status));
|
|
|
|
g_queue_push_head (&priv->free_tasks, task);
|
|
}
|
|
|
|
/* Release GstQsvFrame objects */
|
|
for (guint i = 0; i < priv->surface_pool->len; i++) {
|
|
GstQsvEncoderSurface *iter =
|
|
&g_array_index (priv->surface_pool, GstQsvEncoderSurface, i);
|
|
|
|
if (iter->surface.Data.Locked > 0) {
|
|
GST_WARNING_OBJECT (self,
|
|
"Encoder was drained but QSV is holding surface %d", i);
|
|
continue;
|
|
}
|
|
|
|
gst_qsv_encoder_surface_reset (iter);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#ifdef G_OS_WIN32
|
|
static gboolean
|
|
gst_qsv_encoder_prepare_d3d11_pool (GstQsvEncoder * self,
|
|
GstCaps * caps, GstVideoInfo * aligned_info)
|
|
{
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
GstStructure *config;
|
|
GstD3D11AllocationParams *params;
|
|
GstD3D11Device *device = GST_D3D11_DEVICE_CAST (priv->device);
|
|
guint bind_flags = 0;
|
|
GstD3D11Format device_format;
|
|
|
|
gst_d3d11_device_get_format (device, GST_VIDEO_INFO_FORMAT (aligned_info),
|
|
&device_format);
|
|
if ((device_format.format_support[0] & D3D11_FORMAT_SUPPORT_RENDER_TARGET) ==
|
|
D3D11_FORMAT_SUPPORT_RENDER_TARGET) {
|
|
/* XXX: workaround for greenish artifacts
|
|
* https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/1238
|
|
* bind to render target so that d3d11 memory allocator can clear texture
|
|
* with black color */
|
|
bind_flags = D3D11_BIND_RENDER_TARGET;
|
|
}
|
|
|
|
priv->internal_pool = gst_d3d11_buffer_pool_new (device);
|
|
config = gst_buffer_pool_get_config (priv->internal_pool);
|
|
params = gst_d3d11_allocation_params_new (device, aligned_info,
|
|
GST_D3D11_ALLOCATION_FLAG_DEFAULT, bind_flags,
|
|
D3D11_RESOURCE_MISC_SHARED);
|
|
|
|
gst_buffer_pool_config_set_d3d11_allocation_params (config, params);
|
|
gst_d3d11_allocation_params_free (params);
|
|
gst_buffer_pool_config_set_params (config, caps,
|
|
GST_VIDEO_INFO_SIZE (aligned_info), 0, 0);
|
|
gst_buffer_pool_set_config (priv->internal_pool, config);
|
|
gst_buffer_pool_set_active (priv->internal_pool, TRUE);
|
|
|
|
return TRUE;
|
|
}
|
|
#else
|
|
static gboolean
|
|
gst_qsv_encoder_prepare_va_pool (GstQsvEncoder * self,
|
|
GstCaps * caps, GstVideoInfo * aligned_info)
|
|
{
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
GstAllocator *allocator;
|
|
GstStructure *config;
|
|
GArray *formats;
|
|
GstAllocationParams params;
|
|
GstVaDisplay *display = GST_VA_DISPLAY (priv->device);
|
|
|
|
formats = g_array_new (FALSE, FALSE, sizeof (GstVideoFormat));
|
|
g_array_append_val (formats, GST_VIDEO_INFO_FORMAT (aligned_info));
|
|
|
|
allocator = gst_va_allocator_new (display, formats);
|
|
if (!allocator) {
|
|
GST_ERROR_OBJECT (self, "Failed to create allocator");
|
|
return FALSE;
|
|
}
|
|
|
|
gst_allocation_params_init (¶ms);
|
|
|
|
priv->internal_pool = gst_va_pool_new_with_config (caps, 0, 0,
|
|
VA_SURFACE_ATTRIB_USAGE_HINT_GENERIC, GST_VA_FEATURE_AUTO,
|
|
allocator, ¶ms);
|
|
gst_object_unref (allocator);
|
|
|
|
|
|
if (!priv->internal_pool) {
|
|
GST_ERROR_OBJECT (self, "Failed to create va pool");
|
|
return FALSE;
|
|
}
|
|
|
|
config = gst_buffer_pool_get_config (priv->internal_pool);
|
|
gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META);
|
|
gst_buffer_pool_config_set_params (config, caps,
|
|
GST_VIDEO_INFO_SIZE (aligned_info), 0, 0);
|
|
gst_buffer_pool_set_config (priv->internal_pool, config);
|
|
gst_buffer_pool_set_active (priv->internal_pool, TRUE);
|
|
|
|
return TRUE;
|
|
}
|
|
#endif
|
|
|
|
/* Prepare internal pool, which is used to allocate fallback buffer
|
|
* when upstream buffer is not directly accessible by QSV */
|
|
static gboolean
|
|
gst_qsv_encoder_prepare_pool (GstQsvEncoder * self, GstCaps * caps,
|
|
GstVideoInfo * aligned_info)
|
|
{
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
gboolean ret = FALSE;
|
|
GstCaps *aligned_caps;
|
|
|
|
if (priv->internal_pool) {
|
|
gst_buffer_pool_set_active (priv->internal_pool, FALSE);
|
|
gst_clear_object (&priv->internal_pool);
|
|
}
|
|
|
|
aligned_caps = gst_video_info_to_caps (aligned_info);
|
|
|
|
#ifdef G_OS_WIN32
|
|
ret = gst_qsv_encoder_prepare_d3d11_pool (self, aligned_caps, aligned_info);
|
|
#else
|
|
ret = gst_qsv_encoder_prepare_va_pool (self, aligned_caps, aligned_info);
|
|
#endif
|
|
|
|
gst_caps_unref (aligned_caps);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static gboolean
|
|
gst_qsv_encoder_init_encode_session (GstQsvEncoder * self)
|
|
{
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
GstQsvEncoderClass *klass = GST_QSV_ENCODER_GET_CLASS (self);
|
|
GstVideoInfo *info = &priv->input_state->info;
|
|
GstCaps *caps = priv->input_state->caps;
|
|
mfxVideoParam param;
|
|
mfxFrameInfo *frame_info;
|
|
mfxFrameAllocRequest alloc_request;
|
|
mfxStatus status;
|
|
MFXVideoENCODE *encoder_handle = nullptr;
|
|
guint bitstream_size;
|
|
gboolean ret;
|
|
guint64 min_delay_frames, max_delay_frames;
|
|
GstClockTime min_latency, max_latency;
|
|
|
|
gst_qsv_encoder_drain (self, FALSE);
|
|
gst_qsv_encoder_reset (self);
|
|
|
|
encoder_handle = new MFXVideoENCODE (priv->session);
|
|
|
|
memset (¶m, 0, sizeof (mfxVideoParam));
|
|
|
|
g_ptr_array_set_size (priv->extra_params, 0);
|
|
g_assert (klass->set_format);
|
|
if (!klass->set_format (self, priv->input_state, ¶m, priv->extra_params)) {
|
|
GST_ERROR_OBJECT (self, "Subclass failed to set format");
|
|
goto error;
|
|
}
|
|
|
|
/* LowPower mode supports smaller set of features, don't enable it for now */
|
|
param.mfx.LowPower = MFX_CODINGOPTION_OFF;
|
|
if (priv->low_latency)
|
|
param.AsyncDepth = 1;
|
|
else
|
|
param.AsyncDepth = 4;
|
|
|
|
param.mfx.TargetUsage = priv->target_usage;
|
|
|
|
frame_info = ¶m.mfx.FrameInfo;
|
|
|
|
gst_video_info_set_interlaced_format (&priv->aligned_info,
|
|
GST_VIDEO_INFO_FORMAT (info), GST_VIDEO_INFO_INTERLACE_MODE (info),
|
|
frame_info->Width, frame_info->Height);
|
|
|
|
/* Always video memory, even when upstream is non-hardware element */
|
|
priv->mem_type = GST_QSV_VIDEO_MEMORY | GST_QSV_ENCODER_IN_MEMORY;
|
|
param.IOPattern = MFX_IOPATTERN_IN_VIDEO_MEMORY;
|
|
if (!gst_qsv_encoder_prepare_pool (self, caps, &priv->aligned_info)) {
|
|
GST_ERROR_OBJECT (self, "Failed to prepare pool");
|
|
goto error;
|
|
}
|
|
|
|
status = encoder_handle->Query (¶m, ¶m);
|
|
/* If device is unhappy with LowPower = OFF, try again with unknown */
|
|
if (status < MFX_ERR_NONE) {
|
|
GST_INFO_OBJECT (self, "LowPower - OFF returned %d (%s)",
|
|
QSV_STATUS_ARGS (status));
|
|
param.mfx.LowPower = MFX_CODINGOPTION_UNKNOWN;
|
|
status = encoder_handle->Query (¶m, ¶m);
|
|
}
|
|
QSV_CHECK_STATUS (self, status, MFXVideoENCODE::Query);
|
|
|
|
status = encoder_handle->QueryIOSurf (¶m, &alloc_request);
|
|
QSV_CHECK_STATUS (self, status, MFXVideoENCODE::QueryIOSurf);
|
|
|
|
status = encoder_handle->Init (¶m);
|
|
QSV_CHECK_STATUS (self, status, MFXVideoENCODE::Init);
|
|
|
|
status = encoder_handle->GetVideoParam (¶m);
|
|
QSV_CHECK_STATUS (self, status, MFXVideoENCODE::GetVideoParam);
|
|
|
|
GST_DEBUG_OBJECT (self, "NumFrameSuggested: %d, AsyncDepth %d",
|
|
alloc_request.NumFrameSuggested, param.AsyncDepth);
|
|
|
|
g_assert (klass->set_output_state);
|
|
ret = klass->set_output_state (self, priv->input_state, priv->session);
|
|
if (!ret) {
|
|
GST_ERROR_OBJECT (self, "Subclass failed to set output state");
|
|
goto error;
|
|
}
|
|
|
|
/* Prepare surface pool with size NumFrameSuggested, then if it's not
|
|
* sufficient while encoding, we can increse the pool size dynamically
|
|
* if needed */
|
|
g_array_set_size (priv->surface_pool, alloc_request.NumFrameSuggested);
|
|
for (guint i = 0; i < priv->surface_pool->len; i++) {
|
|
GstQsvEncoderSurface *surface = &g_array_index (priv->surface_pool,
|
|
GstQsvEncoderSurface, i);
|
|
|
|
surface->surface.Info = param.mfx.FrameInfo;
|
|
surface->payload = g_ptr_array_new_with_free_func ((GDestroyNotify)
|
|
gst_qsv_encoder_payload_clear);
|
|
}
|
|
priv->next_surface_index = 0;
|
|
|
|
g_array_set_size (priv->task_pool, param.AsyncDepth);
|
|
if (klass->codec_id == MFX_CODEC_JPEG) {
|
|
gdouble factor = 4.0;
|
|
|
|
/* jpeg zero returns buffer size */
|
|
switch (GST_VIDEO_INFO_FORMAT (info)) {
|
|
case GST_VIDEO_FORMAT_NV12:
|
|
factor = 1.5;
|
|
break;
|
|
case GST_VIDEO_FORMAT_YUY2:
|
|
factor = 2.0;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
bitstream_size = (guint)
|
|
(factor * GST_VIDEO_INFO_WIDTH (info) * GST_VIDEO_INFO_HEIGHT (info));
|
|
} else {
|
|
bitstream_size =
|
|
(guint) param.mfx.BufferSizeInKB * param.mfx.BRCParamMultiplier * 1024;
|
|
}
|
|
|
|
for (guint i = 0; i < priv->task_pool->len; i++) {
|
|
GstQsvEncoderTask *task = &g_array_index (priv->task_pool,
|
|
GstQsvEncoderTask, i);
|
|
|
|
task->bitstream.Data = (mfxU8 *) g_malloc (bitstream_size);
|
|
task->bitstream.MaxLength = bitstream_size;
|
|
|
|
g_queue_push_head (&priv->free_tasks, task);
|
|
}
|
|
|
|
min_delay_frames = priv->task_pool->len;
|
|
/* takes the number of bframes into account */
|
|
if (param.mfx.GopRefDist > 1)
|
|
min_delay_frames += (param.mfx.GopRefDist - 1);
|
|
max_delay_frames = priv->surface_pool->len + priv->task_pool->len;
|
|
|
|
min_latency = gst_util_uint64_scale (min_delay_frames * GST_SECOND,
|
|
param.mfx.FrameInfo.FrameRateExtD, param.mfx.FrameInfo.FrameRateExtN);
|
|
max_latency = gst_util_uint64_scale (max_delay_frames * GST_SECOND,
|
|
param.mfx.FrameInfo.FrameRateExtD, param.mfx.FrameInfo.FrameRateExtN);
|
|
gst_video_encoder_set_latency (GST_VIDEO_ENCODER (self),
|
|
min_latency, max_latency);
|
|
|
|
priv->video_param = param;
|
|
priv->encoder = encoder_handle;
|
|
|
|
return TRUE;
|
|
|
|
error:
|
|
if (encoder_handle)
|
|
delete encoder_handle;
|
|
|
|
gst_qsv_encoder_reset (self);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_qsv_encoder_reset_encode_session (GstQsvEncoder * self)
|
|
{
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
GPtrArray *extra_params = priv->extra_params;
|
|
mfxStatus status;
|
|
mfxExtEncoderResetOption reset_opt;
|
|
|
|
if (!priv->encoder) {
|
|
GST_WARNING_OBJECT (self, "Encoder was not configured");
|
|
return gst_qsv_encoder_init_encode_session (self);
|
|
}
|
|
|
|
reset_opt.Header.BufferId = MFX_EXTBUFF_ENCODER_RESET_OPTION;
|
|
reset_opt.Header.BufferSz = sizeof (mfxExtEncoderResetOption);
|
|
reset_opt.StartNewSequence = MFX_CODINGOPTION_OFF;
|
|
|
|
gst_qsv_encoder_drain (self, FALSE);
|
|
|
|
g_ptr_array_add (extra_params, &reset_opt);
|
|
priv->video_param.ExtParam = (mfxExtBuffer **) extra_params->pdata;
|
|
priv->video_param.NumExtParam = extra_params->len;
|
|
|
|
status = priv->encoder->Reset (&priv->video_param);
|
|
g_ptr_array_remove_index (extra_params, extra_params->len - 1);
|
|
priv->video_param.NumExtParam = extra_params->len;
|
|
|
|
if (status != MFX_ERR_NONE) {
|
|
GST_WARNING_OBJECT (self, "MFXVideoENCODE_Reset returned %d (%s)",
|
|
QSV_STATUS_ARGS (status));
|
|
return gst_qsv_encoder_init_encode_session (self);
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (self, "Encode session reset done");
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_qsv_encoder_set_format (GstVideoEncoder * encoder,
|
|
GstVideoCodecState * state)
|
|
{
|
|
GstQsvEncoder *self = GST_QSV_ENCODER (encoder);
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
|
|
g_clear_pointer (&priv->input_state, gst_video_codec_state_unref);
|
|
priv->input_state = gst_video_codec_state_ref (state);
|
|
|
|
return gst_qsv_encoder_init_encode_session (self);
|
|
}
|
|
|
|
static mfxU16
|
|
gst_qsv_encoder_get_pic_struct (GstQsvEncoder * self,
|
|
GstVideoCodecFrame * frame)
|
|
{
|
|
GstQsvEncoderClass *klass = GST_QSV_ENCODER_GET_CLASS (self);
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
GstVideoInfo *info = &priv->input_state->info;
|
|
|
|
if (klass->codec_id != MFX_CODEC_AVC)
|
|
return MFX_PICSTRUCT_PROGRESSIVE;
|
|
|
|
if (!GST_VIDEO_INFO_IS_INTERLACED (info))
|
|
return MFX_PICSTRUCT_PROGRESSIVE;
|
|
|
|
if (GST_VIDEO_INFO_INTERLACE_MODE (info) == GST_VIDEO_INTERLACE_MODE_MIXED) {
|
|
if (!GST_BUFFER_FLAG_IS_SET (frame->input_buffer,
|
|
GST_VIDEO_BUFFER_FLAG_INTERLACED)) {
|
|
return MFX_PICSTRUCT_PROGRESSIVE;
|
|
}
|
|
|
|
if (GST_BUFFER_FLAG_IS_SET (frame->input_buffer, GST_VIDEO_BUFFER_FLAG_TFF))
|
|
return MFX_PICSTRUCT_FIELD_TFF;
|
|
|
|
return MFX_PICSTRUCT_FIELD_BFF;
|
|
}
|
|
|
|
switch (GST_VIDEO_INFO_FIELD_ORDER (info)) {
|
|
case GST_VIDEO_FIELD_ORDER_TOP_FIELD_FIRST:
|
|
return MFX_PICSTRUCT_FIELD_TFF;
|
|
break;
|
|
case GST_VIDEO_FIELD_ORDER_BOTTOM_FIELD_FIRST:
|
|
return MFX_PICSTRUCT_FIELD_BFF;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (GST_BUFFER_FLAG_IS_SET (frame->input_buffer, GST_VIDEO_BUFFER_FLAG_TFF))
|
|
return MFX_PICSTRUCT_FIELD_TFF;
|
|
|
|
return MFX_PICSTRUCT_FIELD_BFF;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_qsv_encoder_handle_frame (GstVideoEncoder * encoder,
|
|
GstVideoCodecFrame * frame)
|
|
{
|
|
GstQsvEncoder *self = GST_QSV_ENCODER (encoder);
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
GstQsvEncoderClass *klass = GST_QSV_ENCODER_GET_CLASS (self);
|
|
GstFlowReturn ret = GST_FLOW_ERROR;
|
|
GstQsvEncoderSurface *surface;
|
|
GstQsvEncoderTask *task;
|
|
mfxU64 timestamp;
|
|
mfxStatus status;
|
|
|
|
if (klass->check_reconfigure && priv->encoder) {
|
|
GstQsvEncoderReconfigure reconfigure;
|
|
|
|
reconfigure = klass->check_reconfigure (self, priv->session,
|
|
&priv->video_param, priv->extra_params);
|
|
|
|
switch (reconfigure) {
|
|
case GST_QSV_ENCODER_RECONFIGURE_BITRATE:
|
|
if (!gst_qsv_encoder_reset_encode_session (self)) {
|
|
GST_ERROR_OBJECT (self, "Failed to reset session");
|
|
gst_video_encoder_finish_frame (encoder, frame);
|
|
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
break;
|
|
case GST_QSV_ENCODER_RECONFIGURE_FULL:
|
|
if (!gst_qsv_encoder_init_encode_session (self)) {
|
|
GST_ERROR_OBJECT (self, "Failed to init session");
|
|
gst_video_encoder_finish_frame (encoder, frame);
|
|
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!priv->encoder) {
|
|
GST_ERROR_OBJECT (self, "Encoder object was not configured");
|
|
gst_video_encoder_finish_frame (encoder, frame);
|
|
|
|
return GST_FLOW_NOT_NEGOTIATED;
|
|
}
|
|
|
|
surface = gst_qsv_encoder_get_next_surface (self);
|
|
if (!surface) {
|
|
GST_ERROR_OBJECT (self, "No available surface");
|
|
goto out;
|
|
}
|
|
|
|
task = (GstQsvEncoderTask *) g_queue_pop_tail (&priv->free_tasks);
|
|
g_assert (task);
|
|
|
|
surface->qsv_frame =
|
|
gst_qsv_allocator_acquire_frame (priv->allocator, priv->mem_type,
|
|
&priv->input_state->info, gst_buffer_ref (frame->input_buffer),
|
|
priv->internal_pool);
|
|
|
|
if (!surface->qsv_frame) {
|
|
GST_ERROR_OBJECT (self, "Failed to wrap buffer with qsv frame");
|
|
gst_qsv_encoder_task_reset (self, task);
|
|
goto out;
|
|
}
|
|
|
|
surface->surface.Info.PicStruct =
|
|
gst_qsv_encoder_get_pic_struct (self, frame);
|
|
|
|
if (GST_VIDEO_CODEC_FRAME_IS_FORCE_KEYFRAME (frame)) {
|
|
surface->encode_control.FrameType =
|
|
MFX_FRAMETYPE_IDR | MFX_FRAMETYPE_I | MFX_FRAMETYPE_REF;
|
|
} else {
|
|
surface->encode_control.FrameType = MFX_FRAMETYPE_UNKNOWN;
|
|
}
|
|
|
|
if (klass->attach_payload) {
|
|
klass->attach_payload (self, frame, surface->payload);
|
|
if (surface->payload->len > 0) {
|
|
surface->encode_control.NumPayload = surface->payload->len;
|
|
surface->encode_control.Payload = (mfxPayload **) surface->payload->pdata;
|
|
}
|
|
}
|
|
|
|
timestamp = gst_qsv_timestamp_from_gst (frame->pts);
|
|
status = gst_qsv_encoder_encode_frame (self, surface, task, timestamp);
|
|
if (status != MFX_ERR_NONE && status != MFX_ERR_MORE_DATA) {
|
|
GST_ERROR_OBJECT (self, "Failed to encode frame, ret %d (%s)",
|
|
QSV_STATUS_ARGS (status));
|
|
gst_qsv_encoder_task_reset (self, task);
|
|
goto out;
|
|
}
|
|
|
|
if (status == MFX_ERR_NONE && task->sync_point) {
|
|
g_queue_push_head (&priv->pending_tasks, task);
|
|
} else {
|
|
gst_qsv_encoder_task_reset (self, task);
|
|
}
|
|
|
|
ret = GST_FLOW_OK;
|
|
/* Do not sync immediately, but record tasks which have output buffer here
|
|
* to improve throughput.
|
|
* In this way, hardware may be able to run encoding job from its background
|
|
* threads (if any). We will do sync only when there's no more free task item
|
|
*/
|
|
while (g_queue_get_length (&priv->pending_tasks) >= priv->task_pool->len) {
|
|
GstQsvEncoderTask *task =
|
|
(GstQsvEncoderTask *) g_queue_pop_tail (&priv->pending_tasks);
|
|
ret = gst_qsv_encoder_finish_frame (self, task, FALSE);
|
|
}
|
|
|
|
out:
|
|
gst_video_codec_frame_unref (frame);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_qsv_encoder_finish (GstVideoEncoder * encoder)
|
|
{
|
|
GstQsvEncoder *self = GST_QSV_ENCODER (encoder);
|
|
|
|
return gst_qsv_encoder_drain (self, FALSE);
|
|
}
|
|
|
|
static gboolean
|
|
gst_qsv_encoder_flush (GstVideoEncoder * encoder)
|
|
{
|
|
GstQsvEncoder *self = GST_QSV_ENCODER (encoder);
|
|
|
|
gst_qsv_encoder_drain (self, TRUE);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
gst_qsv_encoder_handle_context_query (GstQsvEncoder * self, GstQuery * query)
|
|
{
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
|
|
#ifdef G_OS_WIN32
|
|
return gst_d3d11_handle_context_query (GST_ELEMENT (self), query,
|
|
(GstD3D11Device *) priv->device);
|
|
#else
|
|
return gst_va_handle_context_query (GST_ELEMENT (self), query,
|
|
(GstVaDisplay *) priv->device);
|
|
#endif
|
|
}
|
|
|
|
static gboolean
|
|
gst_qsv_encoder_sink_query (GstVideoEncoder * encoder, GstQuery * query)
|
|
{
|
|
GstQsvEncoder *self = GST_QSV_ENCODER (encoder);
|
|
|
|
switch (GST_QUERY_TYPE (query)) {
|
|
case GST_QUERY_CONTEXT:
|
|
if (gst_qsv_encoder_handle_context_query (self, query))
|
|
return TRUE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return GST_VIDEO_ENCODER_CLASS (parent_class)->sink_query (encoder, query);
|
|
}
|
|
|
|
static gboolean
|
|
gst_qsv_encoder_src_query (GstVideoEncoder * encoder, GstQuery * query)
|
|
{
|
|
GstQsvEncoder *self = GST_QSV_ENCODER (encoder);
|
|
|
|
switch (GST_QUERY_TYPE (query)) {
|
|
case GST_QUERY_CONTEXT:
|
|
if (gst_qsv_encoder_handle_context_query (self, query))
|
|
return TRUE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return GST_VIDEO_ENCODER_CLASS (parent_class)->src_query (encoder, query);
|
|
}
|
|
|
|
#ifdef G_OS_WIN32
|
|
static gboolean
|
|
gst_qsv_encoder_propose_allocation (GstVideoEncoder * encoder, GstQuery * query)
|
|
{
|
|
GstQsvEncoder *self = GST_QSV_ENCODER (encoder);
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
GstD3D11Device *device = GST_D3D11_DEVICE (priv->device);
|
|
GstVideoInfo info;
|
|
GstBufferPool *pool;
|
|
GstCaps *caps;
|
|
guint size;
|
|
GstStructure *config;
|
|
GstCapsFeatures *features;
|
|
gboolean is_d3d11 = FALSE;
|
|
|
|
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;
|
|
}
|
|
|
|
features = gst_caps_get_features (caps, 0);
|
|
if (features && gst_caps_features_contains (features,
|
|
GST_CAPS_FEATURE_MEMORY_D3D11_MEMORY)) {
|
|
GST_DEBUG_OBJECT (self, "upstream support d3d11 memory");
|
|
pool = gst_d3d11_buffer_pool_new (device);
|
|
is_d3d11 = TRUE;
|
|
} else {
|
|
pool = gst_video_buffer_pool_new ();
|
|
}
|
|
|
|
config = gst_buffer_pool_get_config (pool);
|
|
gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META);
|
|
|
|
if (is_d3d11) {
|
|
GstD3D11AllocationParams *d3d11_params;
|
|
GstVideoAlignment align;
|
|
|
|
/* d3d11 buffer pool doesn't support generic video alignment
|
|
* because memory layout of CPU accessible staging texture is uncontrollable.
|
|
* Do D3D11 specific handling */
|
|
gst_video_alignment_reset (&align);
|
|
|
|
align.padding_right = GST_VIDEO_INFO_WIDTH (&priv->aligned_info) -
|
|
GST_VIDEO_INFO_WIDTH (&info);
|
|
align.padding_bottom = GST_VIDEO_INFO_HEIGHT (&priv->aligned_info) -
|
|
GST_VIDEO_INFO_HEIGHT (&info);
|
|
|
|
d3d11_params = gst_d3d11_allocation_params_new (device, &info,
|
|
GST_D3D11_ALLOCATION_FLAG_DEFAULT, 0, 0);
|
|
|
|
gst_d3d11_allocation_params_alignment (d3d11_params, &align);
|
|
gst_buffer_pool_config_set_d3d11_allocation_params (config, d3d11_params);
|
|
gst_d3d11_allocation_params_free (d3d11_params);
|
|
} else {
|
|
gst_buffer_pool_config_add_option (config,
|
|
GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT);
|
|
}
|
|
|
|
size = GST_VIDEO_INFO_SIZE (&info);
|
|
gst_buffer_pool_config_set_params (config,
|
|
caps, size, priv->surface_pool->len, 0);
|
|
|
|
if (!gst_buffer_pool_set_config (pool, config)) {
|
|
GST_WARNING_OBJECT (self, "Failed to set pool config");
|
|
gst_object_unref (pool);
|
|
return FALSE;
|
|
}
|
|
|
|
/* d3d11 buffer pool will update actual CPU accessible buffer size based on
|
|
* allocated staging texture per gst_buffer_pool_set_config() call,
|
|
* need query again to get the size */
|
|
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, priv->surface_pool->len, 0);
|
|
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, nullptr);
|
|
gst_object_unref (pool);
|
|
|
|
return TRUE;
|
|
}
|
|
#else
|
|
static gboolean
|
|
gst_qsv_encoder_propose_allocation (GstVideoEncoder * encoder, GstQuery * query)
|
|
{
|
|
GstQsvEncoder *self = GST_QSV_ENCODER (encoder);
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
GstVideoInfo info;
|
|
GstAllocator *allocator = nullptr;
|
|
GstBufferPool *pool;
|
|
GstCaps *caps;
|
|
guint size;
|
|
GstStructure *config;
|
|
GstVideoAlignment align;
|
|
GstAllocationParams params;
|
|
GArray *formats;
|
|
|
|
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;
|
|
}
|
|
|
|
gst_allocation_params_init (¶ms);
|
|
|
|
formats = g_array_new (FALSE, FALSE, sizeof (GstVideoFormat));
|
|
g_array_append_val (formats, GST_VIDEO_INFO_FORMAT (&info));
|
|
|
|
allocator = gst_va_allocator_new (GST_VA_DISPLAY (priv->device), formats);
|
|
if (!allocator) {
|
|
GST_ERROR_OBJECT (self, "Failed to create allocator");
|
|
return FALSE;
|
|
}
|
|
|
|
pool = gst_va_pool_new_with_config (caps, priv->surface_pool->len, 0,
|
|
VA_SURFACE_ATTRIB_USAGE_HINT_GENERIC, GST_VA_FEATURE_AUTO,
|
|
allocator, ¶ms);
|
|
|
|
if (!pool) {
|
|
GST_ERROR_OBJECT (self, "Failed to create va pool");
|
|
gst_object_unref (allocator);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
gst_video_alignment_reset (&align);
|
|
align.padding_right = GST_VIDEO_INFO_WIDTH (&priv->aligned_info) -
|
|
GST_VIDEO_INFO_WIDTH (&info);
|
|
align.padding_bottom = GST_VIDEO_INFO_HEIGHT (&priv->aligned_info) -
|
|
GST_VIDEO_INFO_HEIGHT (&info);
|
|
|
|
config = gst_buffer_pool_get_config (pool);
|
|
gst_buffer_pool_config_add_option (config, GST_BUFFER_POOL_OPTION_VIDEO_META);
|
|
gst_buffer_pool_config_add_option (config,
|
|
GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT);
|
|
gst_buffer_pool_config_set_video_alignment (config, &align);
|
|
|
|
gst_buffer_pool_config_set_params (config,
|
|
caps, GST_VIDEO_INFO_SIZE (&info), priv->surface_pool->len, 0);
|
|
|
|
if (!gst_buffer_pool_set_config (pool, config)) {
|
|
GST_ERROR_OBJECT (self, "Failed to set pool config");
|
|
gst_clear_object (&allocator);
|
|
gst_object_unref (pool);
|
|
return FALSE;
|
|
}
|
|
|
|
if (allocator)
|
|
gst_query_add_allocation_param (query, allocator, ¶ms);
|
|
|
|
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, priv->surface_pool->len, 0);
|
|
gst_query_add_allocation_meta (query, GST_VIDEO_META_API_TYPE, nullptr);
|
|
|
|
gst_clear_object (&allocator);
|
|
gst_object_unref (pool);
|
|
|
|
return TRUE;
|
|
}
|
|
#endif
|