mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-20 23:36:38 +00:00
3a7e4635bb
Add VP9 encoder with 4:2:0 8 bits and 4:4:4 8/10 bits formats support Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/1408>
535 lines
15 KiB
C++
535 lines
15 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 "gstqsvallocator.h"
|
|
|
|
GST_DEBUG_CATEGORY_EXTERN (gst_qsv_allocator_debug);
|
|
#define GST_CAT_DEFAULT gst_qsv_allocator_debug
|
|
|
|
/* Both d3d11 and va use (GST_MAP_FLAG_LAST << 1) value
|
|
* for GPU access */
|
|
#define GST_MAP_QSV (GST_MAP_FLAG_LAST << 1)
|
|
|
|
struct _GstQsvFrame
|
|
{
|
|
GstMiniObject parent;
|
|
|
|
GstQsvAllocator *allocator;
|
|
|
|
GMutex lock;
|
|
|
|
guint map_count;
|
|
GstBuffer *buffer;
|
|
GstVideoInfo info;
|
|
GstVideoFrame frame;
|
|
GstQsvMemoryType mem_type;
|
|
|
|
/* For direct GPU access */
|
|
GstMapInfo map_info;
|
|
};
|
|
|
|
GST_DEFINE_MINI_OBJECT_TYPE (GstQsvFrame, gst_qsv_frame);
|
|
|
|
static void
|
|
_gst_qsv_frame_free (GstQsvFrame * frame)
|
|
{
|
|
g_mutex_clear (&frame->lock);
|
|
gst_clear_buffer (&frame->buffer);
|
|
gst_clear_object (&frame->allocator);
|
|
g_free (frame);
|
|
}
|
|
|
|
static GstQsvFrame *
|
|
gst_qsv_frame_new (void)
|
|
{
|
|
GstQsvFrame *self;
|
|
|
|
self = g_new0 (GstQsvFrame, 1);
|
|
g_mutex_init (&self->lock);
|
|
|
|
gst_mini_object_init (GST_MINI_OBJECT_CAST (self), 0,
|
|
GST_TYPE_QSV_FRAME, nullptr, nullptr,
|
|
(GstMiniObjectFreeFunction) _gst_qsv_frame_free);
|
|
|
|
return self;
|
|
}
|
|
|
|
GstBuffer *
|
|
gst_qsv_frame_peek_buffer (GstQsvFrame * frame)
|
|
{
|
|
g_return_val_if_fail (GST_IS_QSV_FRAME (frame), nullptr);
|
|
|
|
return frame->buffer;
|
|
}
|
|
|
|
struct _GstQsvAllocatorPrivate
|
|
{
|
|
GstAtomicQueue *queue;
|
|
|
|
mfxFrameAllocator allocator;
|
|
};
|
|
|
|
#define gst_qsv_allocator_parent_class parent_class
|
|
G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GstQsvAllocator,
|
|
gst_qsv_allocator, GST_TYPE_OBJECT);
|
|
|
|
static void gst_qsv_allocator_finalize (GObject * object);
|
|
static mfxStatus gst_qsv_allocator_alloc (mfxHDL pthis,
|
|
mfxFrameAllocRequest * request, mfxFrameAllocResponse * response);
|
|
static mfxStatus gst_qsv_allocator_lock (mfxHDL pthis, mfxMemId mid,
|
|
mfxFrameData * ptr);
|
|
static mfxStatus gst_qsv_allocator_unlock (mfxHDL pthis, mfxMemId mid,
|
|
mfxFrameData * ptr);
|
|
static mfxStatus gst_qsv_allocator_get_hdl (mfxHDL pthis, mfxMemId mid,
|
|
mfxHDL * handle);
|
|
static mfxStatus gst_qsv_allocator_free (mfxHDL pthis,
|
|
mfxFrameAllocResponse * response);
|
|
|
|
static void
|
|
gst_qsv_allocator_class_init (GstQsvAllocatorClass * klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->finalize = gst_qsv_allocator_finalize;
|
|
}
|
|
|
|
static void
|
|
gst_qsv_allocator_init (GstQsvAllocator * self)
|
|
{
|
|
GstQsvAllocatorPrivate *priv;
|
|
|
|
priv = self->priv = (GstQsvAllocatorPrivate *)
|
|
gst_qsv_allocator_get_instance_private (self);
|
|
|
|
priv->queue = gst_atomic_queue_new (16);
|
|
|
|
priv->allocator.pthis = self;
|
|
priv->allocator.Alloc = gst_qsv_allocator_alloc;
|
|
priv->allocator.Lock = gst_qsv_allocator_lock;
|
|
priv->allocator.Unlock = gst_qsv_allocator_unlock;
|
|
priv->allocator.GetHDL = gst_qsv_allocator_get_hdl;
|
|
priv->allocator.Free = gst_qsv_allocator_free;
|
|
}
|
|
|
|
static void
|
|
gst_qsv_allocator_finalize (GObject * object)
|
|
{
|
|
GstQsvAllocator *self = GST_QSV_ALLOCATOR (object);
|
|
GstQsvAllocatorPrivate *priv = self->priv;
|
|
GstQsvFrame *frame;
|
|
|
|
GST_DEBUG_OBJECT (object, "finalize");
|
|
|
|
while ((frame = (GstQsvFrame *) gst_atomic_queue_pop (priv->queue)))
|
|
gst_qsv_frame_unref (frame);
|
|
|
|
gst_atomic_queue_unref (priv->queue);
|
|
|
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
|
}
|
|
|
|
static mfxStatus
|
|
gst_qsv_allocator_alloc_default (GstQsvAllocator * self,
|
|
mfxFrameAllocRequest * request, mfxFrameAllocResponse * response)
|
|
{
|
|
GstQsvFrame **mids = nullptr;
|
|
GstVideoInfo info;
|
|
GstVideoFormat format = GST_VIDEO_FORMAT_UNKNOWN;
|
|
|
|
GST_TRACE_OBJECT (self, "Alloc");
|
|
|
|
/* Something unexpected and went wrong */
|
|
if ((request->Type & MFX_MEMTYPE_SYSTEM_MEMORY) == 0) {
|
|
GST_ERROR_OBJECT (self,
|
|
"MFX is requesting system memory, type 0x%x", request->Type);
|
|
return MFX_ERR_UNSUPPORTED;
|
|
}
|
|
|
|
switch (request->Info.FourCC) {
|
|
case MFX_FOURCC_NV12:
|
|
format = GST_VIDEO_FORMAT_NV12;
|
|
break;
|
|
case MFX_FOURCC_P010:
|
|
format = GST_VIDEO_FORMAT_P010_10LE;
|
|
break;
|
|
case MFX_FOURCC_AYUV:
|
|
format = GST_VIDEO_FORMAT_VUYA;
|
|
break;
|
|
case MFX_FOURCC_Y410:
|
|
format = GST_VIDEO_FORMAT_Y410;
|
|
break;
|
|
default:
|
|
/* TODO: add more formats */
|
|
break;
|
|
}
|
|
|
|
if (format == GST_VIDEO_FORMAT_UNKNOWN) {
|
|
GST_ERROR_OBJECT (self, "Unknown MFX format fourcc %" GST_FOURCC_FORMAT,
|
|
GST_FOURCC_ARGS (request->Info.FourCC));
|
|
|
|
return MFX_ERR_UNSUPPORTED;
|
|
}
|
|
|
|
mids = g_new0 (GstQsvFrame *, request->NumFrameSuggested);
|
|
response->NumFrameActual = request->NumFrameSuggested;
|
|
|
|
gst_video_info_set_format (&info,
|
|
format, request->Info.Width, request->Info.Height);
|
|
for (guint i = 0; i < request->NumFrameSuggested; i++) {
|
|
GstBuffer *buffer;
|
|
|
|
buffer = gst_buffer_new_and_alloc (info.size);
|
|
mids[i] = gst_qsv_allocator_acquire_frame (self,
|
|
GST_QSV_SYSTEM_MEMORY, &info, buffer, nullptr);
|
|
gst_buffer_unref (buffer);
|
|
}
|
|
|
|
response->mids = (mfxMemId *) mids;
|
|
|
|
return MFX_ERR_NONE;
|
|
}
|
|
|
|
static mfxStatus
|
|
gst_qsv_allocator_alloc (mfxHDL pthis,
|
|
mfxFrameAllocRequest * request, mfxFrameAllocResponse * response)
|
|
{
|
|
GstQsvAllocator *self = GST_QSV_ALLOCATOR (pthis);
|
|
GstQsvAllocatorClass *klass;
|
|
|
|
if ((request->Type & MFX_MEMTYPE_SYSTEM_MEMORY) != 0)
|
|
return gst_qsv_allocator_alloc_default (self, request, response);
|
|
|
|
klass = GST_QSV_ALLOCATOR_GET_CLASS (self);
|
|
|
|
g_assert (klass->alloc);
|
|
|
|
return klass->alloc (self, request, response);
|
|
}
|
|
|
|
static mfxStatus
|
|
gst_qsv_allocator_lock (mfxHDL pthis, mfxMemId mid, mfxFrameData * ptr)
|
|
{
|
|
GstQsvAllocator *self = GST_QSV_ALLOCATOR (pthis);
|
|
GstQsvFrame *frame = (GstQsvFrame *) mid;
|
|
guint stride;
|
|
|
|
GST_TRACE_OBJECT (self, "Lock mfxMemId %p", mid);
|
|
|
|
g_mutex_lock (&frame->lock);
|
|
if (frame->map_count == 0) {
|
|
gst_video_frame_map (&frame->frame, &frame->info, frame->buffer,
|
|
GST_MAP_READ);
|
|
}
|
|
|
|
frame->map_count++;
|
|
stride = GST_VIDEO_FRAME_PLANE_STRIDE (&frame->frame, 0);
|
|
|
|
/* FIXME: check and handle other formats */
|
|
switch (GST_VIDEO_INFO_FORMAT (&frame->info)) {
|
|
case GST_VIDEO_FORMAT_NV12:
|
|
case GST_VIDEO_FORMAT_P010_10LE:
|
|
ptr->Pitch = (mfxU16) stride;
|
|
ptr->Y = (mfxU8 *) GST_VIDEO_FRAME_PLANE_DATA (&frame->frame, 0);
|
|
ptr->UV = (mfxU8 *) GST_VIDEO_FRAME_PLANE_DATA (&frame->frame, 1);
|
|
break;
|
|
case GST_VIDEO_FORMAT_VUYA:
|
|
ptr->PitchHigh = (mfxU16) (stride / (1 << 16));
|
|
ptr->PitchLow = (mfxU16) (stride % (1 << 16));
|
|
ptr->V = (mfxU8 *) GST_VIDEO_FRAME_PLANE_DATA (&frame->frame, 0);
|
|
ptr->U = ptr->V + 1;
|
|
ptr->Y = ptr->V + 2;
|
|
ptr->A = ptr->V + 3;
|
|
break;
|
|
case GST_VIDEO_FORMAT_Y410:
|
|
ptr->PitchHigh = (mfxU16) (stride / (1 << 16));
|
|
ptr->PitchLow = (mfxU16) (stride % (1 << 16));
|
|
ptr->Y410 = (mfxY410 *) GST_VIDEO_FRAME_PLANE_DATA (&frame->frame, 0);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
g_mutex_unlock (&frame->lock);
|
|
|
|
return MFX_ERR_NONE;
|
|
}
|
|
|
|
static mfxStatus
|
|
gst_qsv_allocator_unlock (mfxHDL pthis, mfxMemId mid, mfxFrameData * ptr)
|
|
{
|
|
GstQsvAllocator *self = GST_QSV_ALLOCATOR (pthis);
|
|
GstQsvFrame *frame = (GstQsvFrame *) mid;
|
|
|
|
GST_TRACE_OBJECT (self, "Unlock mfxMemId %p", mid);
|
|
|
|
g_mutex_lock (&frame->lock);
|
|
|
|
if (frame->map_count > 0) {
|
|
frame->map_count--;
|
|
|
|
if (frame->map_count == 0)
|
|
gst_video_frame_unmap (&frame->frame);
|
|
} else {
|
|
GST_WARNING_OBJECT (self, "Unlock request for non-locked memory");
|
|
}
|
|
|
|
g_mutex_unlock (&frame->lock);
|
|
|
|
return MFX_ERR_NONE;
|
|
}
|
|
|
|
static mfxStatus
|
|
gst_qsv_allocator_get_hdl (mfxHDL pthis, mfxMemId mid, mfxHDL * handle)
|
|
{
|
|
GstQsvAllocator *self = GST_QSV_ALLOCATOR (pthis);
|
|
GstQsvFrame *frame = GST_QSV_FRAME_CAST (mid);
|
|
|
|
if (frame->mem_type != GST_QSV_VIDEO_MEMORY) {
|
|
GST_ERROR_OBJECT (self, "Unexpected call");
|
|
|
|
return MFX_ERR_UNSUPPORTED;
|
|
}
|
|
|
|
if (!frame->map_info.data) {
|
|
GST_ERROR_OBJECT (self, "No mapped data");
|
|
return MFX_ERR_UNSUPPORTED;
|
|
}
|
|
|
|
GST_TRACE_OBJECT (self, "Get handle for mfxMemId %p", mid);
|
|
|
|
#ifdef G_OS_WIN32
|
|
mfxHDLPair *pair = (mfxHDLPair *) handle;
|
|
pair->first = (mfxHDL) frame->map_info.data;
|
|
|
|
/* GstD3D11 will fill user_data[0] with subresource index */
|
|
pair->second = (mfxHDL) frame->map_info.user_data[0];
|
|
#else
|
|
*handle = (mfxHDL) frame->map_info.data;
|
|
#endif
|
|
|
|
return MFX_ERR_NONE;
|
|
}
|
|
|
|
static mfxStatus
|
|
gst_qsv_allocator_free (mfxHDL pthis, mfxFrameAllocResponse * response)
|
|
{
|
|
GstQsvFrame **frames = (GstQsvFrame **) response->mids;
|
|
|
|
for (guint i = 0; i < response->NumFrameActual; i++)
|
|
gst_clear_qsv_frame (&frames[i]);
|
|
|
|
g_clear_pointer (&response->mids, g_free);
|
|
|
|
return MFX_ERR_NONE;
|
|
}
|
|
|
|
static void
|
|
gst_qsv_frame_release (GstQsvFrame * frame)
|
|
{
|
|
GstQsvAllocator *allocator = frame->allocator;
|
|
|
|
g_mutex_lock (&frame->lock);
|
|
if (frame->map_count > 0) {
|
|
GST_WARNING_OBJECT (allocator, "Releasing mapped frame %p", frame);
|
|
gst_video_frame_unmap (&frame->frame);
|
|
}
|
|
frame->map_count = 0;
|
|
g_mutex_unlock (&frame->lock);
|
|
|
|
if (frame->mem_type == GST_QSV_VIDEO_MEMORY && frame->map_info.data)
|
|
gst_buffer_unmap (frame->buffer, &frame->map_info);
|
|
|
|
memset (&frame->map_info, 0, sizeof (GstMapInfo));
|
|
|
|
gst_clear_buffer (&frame->buffer);
|
|
GST_MINI_OBJECT_CAST (frame)->dispose = nullptr;
|
|
frame->allocator = nullptr;
|
|
|
|
GST_TRACE_OBJECT (allocator, "Moving frame %p back to pool", frame);
|
|
|
|
gst_atomic_queue_push (allocator->priv->queue, frame);
|
|
gst_object_unref (allocator);
|
|
}
|
|
|
|
static gboolean
|
|
gst_qsv_frame_dispose (GstQsvFrame * frame)
|
|
{
|
|
g_assert (frame->allocator);
|
|
|
|
gst_qsv_frame_ref (frame);
|
|
gst_qsv_frame_release (frame);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static GstBuffer *
|
|
gst_qsv_allocator_upload_default (GstQsvAllocator * allocator,
|
|
const GstVideoInfo * info, GstBuffer * buffer, GstBufferPool * pool)
|
|
{
|
|
GstBuffer *dst_buf;
|
|
GstFlowReturn flow_ret;
|
|
GstVideoFrame src_frame, dst_frame;
|
|
|
|
flow_ret = gst_buffer_pool_acquire_buffer (pool, &dst_buf, nullptr);
|
|
if (flow_ret != GST_FLOW_OK) {
|
|
GST_WARNING ("Failed to acquire buffer from pool, return %s",
|
|
gst_flow_get_name (flow_ret));
|
|
return nullptr;
|
|
}
|
|
|
|
gst_video_frame_map (&src_frame, info, buffer, GST_MAP_READ);
|
|
gst_video_frame_map (&dst_frame, info, dst_buf, GST_MAP_WRITE);
|
|
|
|
if (GST_VIDEO_FRAME_WIDTH (&src_frame) == GST_VIDEO_FRAME_WIDTH (&dst_frame)
|
|
&& GST_VIDEO_FRAME_HEIGHT (&src_frame) ==
|
|
GST_VIDEO_FRAME_HEIGHT (&dst_frame)) {
|
|
gst_video_frame_unmap (&src_frame);
|
|
gst_video_frame_unmap (&dst_frame);
|
|
|
|
gst_buffer_unref (dst_buf);
|
|
return gst_buffer_ref (buffer);
|
|
}
|
|
|
|
for (guint i = 0; i < GST_VIDEO_FRAME_N_PLANES (&src_frame); i++) {
|
|
guint src_width_in_bytes, src_height;
|
|
guint dst_width_in_bytes, dst_height;
|
|
guint width_in_bytes, height;
|
|
guint src_stride, dst_stride;
|
|
guint8 *src_data, *dst_data;
|
|
|
|
src_width_in_bytes = GST_VIDEO_FRAME_COMP_WIDTH (&src_frame, i) *
|
|
GST_VIDEO_FRAME_COMP_PSTRIDE (&src_frame, i);
|
|
src_height = GST_VIDEO_FRAME_COMP_HEIGHT (&src_frame, i);
|
|
src_stride = GST_VIDEO_FRAME_COMP_STRIDE (&src_frame, i);
|
|
|
|
dst_width_in_bytes = GST_VIDEO_FRAME_COMP_WIDTH (&dst_frame, i) *
|
|
GST_VIDEO_FRAME_COMP_PSTRIDE (&dst_frame, i);
|
|
dst_height = GST_VIDEO_FRAME_COMP_HEIGHT (&dst_frame, i);
|
|
dst_stride = GST_VIDEO_FRAME_COMP_STRIDE (&dst_frame, i);
|
|
|
|
width_in_bytes = MIN (src_width_in_bytes, dst_width_in_bytes);
|
|
height = MIN (src_height, dst_height);
|
|
|
|
src_data = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (&src_frame, i);
|
|
dst_data = (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (&dst_frame, i);
|
|
|
|
for (guint j = 0; j < height; j++) {
|
|
memcpy (dst_data, src_data, width_in_bytes);
|
|
dst_data += dst_stride;
|
|
src_data += src_stride;
|
|
}
|
|
}
|
|
|
|
gst_video_frame_unmap (&dst_frame);
|
|
gst_video_frame_unmap (&src_frame);
|
|
|
|
return dst_buf;
|
|
}
|
|
|
|
/**
|
|
* gst_qsv_allocator_acquire_frame:
|
|
* @allocator: a #GstQsvAllocator
|
|
* @mem_type: a memory type
|
|
* @info: a #GstVideoInfo
|
|
* @buffer: (transfer none): a #GstBuffer
|
|
* @pool: (nullable): a #GstBufferPool
|
|
*
|
|
* Uploads @buffer to video memory if required, and wraps GstBuffer using
|
|
* #GstQsvFrame object so that QSV API can access native memory handle
|
|
* via mfxFrameAllocator interface.
|
|
*
|
|
* Returns: a #GstQsvFrame object
|
|
*/
|
|
GstQsvFrame *
|
|
gst_qsv_allocator_acquire_frame (GstQsvAllocator * allocator,
|
|
GstQsvMemoryType mem_type, const GstVideoInfo * info, GstBuffer * buffer,
|
|
GstBufferPool * pool)
|
|
{
|
|
GstQsvAllocatorPrivate *priv;
|
|
GstQsvFrame *frame;
|
|
|
|
g_return_val_if_fail (GST_IS_QSV_ALLOCATOR (allocator), nullptr);
|
|
|
|
priv = allocator->priv;
|
|
frame = (GstQsvFrame *) gst_atomic_queue_pop (priv->queue);
|
|
|
|
if (!frame)
|
|
frame = gst_qsv_frame_new ();
|
|
|
|
frame->mem_type = mem_type;
|
|
frame->allocator = (GstQsvAllocator *) gst_object_ref (allocator);
|
|
GST_MINI_OBJECT_CAST (frame)->dispose =
|
|
(GstMiniObjectDisposeFunction) gst_qsv_frame_dispose;
|
|
|
|
if (!pool) {
|
|
frame->buffer = gst_buffer_ref (buffer);
|
|
frame->info = *info;
|
|
} else {
|
|
GstBuffer *upload_buf;
|
|
|
|
if (mem_type == GST_QSV_SYSTEM_MEMORY) {
|
|
upload_buf = gst_qsv_allocator_upload_default (allocator, info, buffer,
|
|
pool);
|
|
} else {
|
|
GstQsvAllocatorClass *klass;
|
|
|
|
klass = GST_QSV_ALLOCATOR_GET_CLASS (allocator);
|
|
g_assert (klass->upload);
|
|
|
|
upload_buf = klass->upload (allocator, info, buffer, pool);
|
|
}
|
|
|
|
if (!upload_buf) {
|
|
GST_WARNING_OBJECT (allocator, "Failed to upload buffer");
|
|
gst_qsv_frame_unref (frame);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
frame->buffer = upload_buf;
|
|
frame->info = *info;
|
|
}
|
|
|
|
if (mem_type == GST_QSV_VIDEO_MEMORY) {
|
|
/* TODO: we need to know context whether this memory is for
|
|
* output (e.g., decoder or vpp), but we have only encoder
|
|
* implementation at the moment, so GST_MAP_READ should be fine */
|
|
if (!gst_buffer_map (frame->buffer, &frame->map_info,
|
|
(GstMapFlags) (GST_MAP_READ | GST_MAP_QSV))) {
|
|
GST_ERROR_OBJECT (allocator, "Failed to map video buffer");
|
|
gst_qsv_frame_unref (frame);
|
|
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
return frame;
|
|
}
|
|
|
|
mfxFrameAllocator *
|
|
gst_qsv_allocator_get_allocator_handle (GstQsvAllocator * allocator)
|
|
{
|
|
g_return_val_if_fail (GST_IS_QSV_ALLOCATOR (allocator), nullptr);
|
|
|
|
return &allocator->priv->allocator;
|
|
}
|