mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-19 08:11:16 +00:00
886cfecd36
... and add more encoding options. QSV API supports dynamic bitrate change without IDR insertion. That's more efficient way of runtime encoding option update than starting from new sequence with IDR per bitrate option change. Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/2039>
1489 lines
44 KiB
C++
1489 lines
44 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 <gst/d3d11/gstd3d11.h>
|
|
#include "gstqsvallocator_d3d11.h"
|
|
|
|
#include <wrl.h>
|
|
|
|
/* *INDENT-OFF* */
|
|
using namespace Microsoft::WRL;
|
|
/* *INDENT-ON* */
|
|
#else
|
|
#include <gst/va/gstvadisplay_drm.h>
|
|
#include "gstqsvallocator_va.h"
|
|
#endif /* G_OS_WIN32 */
|
|
|
|
GST_DEBUG_CATEGORY_EXTERN (gst_qsv_encoder_debug);
|
|
#define GST_CAT_DEFAULT gst_qsv_encoder_debug
|
|
|
|
GType
|
|
gst_qsv_coding_option_get_type (void)
|
|
{
|
|
static GType coding_opt_type = 0;
|
|
static const GEnumValue coding_opts[] = {
|
|
{MFX_CODINGOPTION_UNKNOWN, "Unknown", "unknown"},
|
|
{MFX_CODINGOPTION_ON, "On", "on"},
|
|
{MFX_CODINGOPTION_OFF, "Off", "off"},
|
|
{0, nullptr, nullptr}
|
|
};
|
|
|
|
if (g_once_init_enter (&coding_opt_type)) {
|
|
GType type = g_enum_register_static ("GstQsvCodingOption",
|
|
coding_opts);
|
|
g_once_init_leave (&coding_opt_type, type);
|
|
}
|
|
|
|
return coding_opt_type;
|
|
}
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
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;
|
|
};
|
|
|
|
#define gst_qsv_encoder_parent_class parent_class
|
|
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GstQsvEncoder, gst_qsv_encoder,
|
|
GST_TYPE_VIDEO_ENCODER);
|
|
|
|
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);
|
|
|
|
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;
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
|
|
switch (prop_id) {
|
|
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)
|
|
{
|
|
#ifdef G_OS_WIN32
|
|
GstQsvEncoder *self = GST_QSV_ENCODER (element);
|
|
GstQsvEncoderClass *klass = GST_QSV_ENCODER_GET_CLASS (element);
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
|
|
gst_d3d11_handle_set_context_for_adapter_luid (element,
|
|
context, klass->adapter_luid, (GstD3D11Device **) & 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;
|
|
|
|
/* GstVADisplay context sharing is not public yet (VA plugin internal) */
|
|
if (!priv->device) {
|
|
display = gst_va_display_drm_new_from_path (klass->display_path);
|
|
if (!display) {
|
|
GST_ERROR_OBJECT (self, "VA display is unavailable");
|
|
return FALSE;
|
|
}
|
|
|
|
priv->device = GST_OBJECT (display);
|
|
} else {
|
|
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 pts = GST_CLOCK_TIME_NONE;
|
|
GstClockTime 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;
|
|
pts = gst_qsv_timestamp_to_gst (bs->TimeStamp);
|
|
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, pts);
|
|
if (frame) {
|
|
frame->pts = pts;
|
|
frame->dts = dts;
|
|
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) = pts;
|
|
GST_BUFFER_DTS (buffer) = dts;
|
|
|
|
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);
|
|
|
|
GST_DEBUG_OBJECT (self, "Use d3d11 memory pool");
|
|
|
|
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,
|
|
(GstD3D11AllocationFlags) 0, 0);
|
|
|
|
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;
|
|
}
|
|
#endif
|
|
|
|
static gboolean
|
|
gst_qsv_encoder_prepare_system_pool (GstQsvEncoder * self,
|
|
GstCaps * caps, GstVideoInfo * aligned_info)
|
|
{
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
GstStructure *config;
|
|
|
|
GST_DEBUG_OBJECT (self, "Use system memory pool");
|
|
|
|
priv->internal_pool = gst_video_buffer_pool_new ();
|
|
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;
|
|
}
|
|
|
|
/* 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, mfxU16 * io_pattern)
|
|
{
|
|
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);
|
|
|
|
/* TODO: Add Linux video memory (VA/DMABuf) support */
|
|
#ifdef G_OS_WIN32
|
|
priv->mem_type = GST_QSV_VIDEO_MEMORY | GST_QSV_ENCODER_IN_MEMORY;
|
|
*io_pattern = MFX_IOPATTERN_IN_VIDEO_MEMORY;
|
|
|
|
ret = gst_qsv_encoder_prepare_d3d11_pool (self, aligned_caps, aligned_info);
|
|
#endif
|
|
|
|
if (!ret) {
|
|
priv->mem_type = GST_QSV_SYSTEM_MEMORY | GST_QSV_ENCODER_IN_MEMORY;
|
|
*io_pattern = MFX_IOPATTERN_IN_SYSTEM_MEMORY;
|
|
|
|
ret = gst_qsv_encoder_prepare_system_pool (self,
|
|
aligned_caps, aligned_info);
|
|
}
|
|
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);
|
|
|
|
if (!gst_qsv_encoder_prepare_pool (self, caps, &priv->aligned_info,
|
|
¶m.IOPattern)) {
|
|
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);
|
|
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;
|
|
max_delay_frames = priv->surface_pool->len + min_delay_frames;
|
|
|
|
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)
|
|
{
|
|
#ifdef G_OS_WIN32
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
|
|
return gst_d3d11_handle_context_query (GST_ELEMENT (self), query,
|
|
(GstD3D11Device *) priv->device);
|
|
#endif
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
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_d3d11_staging_buffer_pool_new (device);
|
|
}
|
|
|
|
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,
|
|
(GstD3D11AllocationFlags) 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);
|
|
}
|
|
|
|
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
|
|
/* TODO: Add support VA/DMABuf */
|
|
static gboolean
|
|
gst_qsv_encoder_propose_allocation (GstVideoEncoder * encoder, GstQuery * query)
|
|
{
|
|
GstQsvEncoder *self = GST_QSV_ENCODER (encoder);
|
|
GstQsvEncoderPrivate *priv = self->priv;
|
|
GstVideoInfo info;
|
|
GstBufferPool *pool;
|
|
GstCaps *caps;
|
|
guint size;
|
|
GstStructure *config;
|
|
GstVideoAlignment align;
|
|
|
|
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;
|
|
}
|
|
|
|
pool = gst_video_buffer_pool_new ();
|
|
|
|
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_video_info_align (&info, &align);
|
|
gst_buffer_pool_config_set_video_alignment (config, &align);
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
#endif
|