diff --git a/meson_options.txt b/meson_options.txt index 00aac86dcb..94cfa28541 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -151,6 +151,7 @@ option('tinyalsa', type : 'feature', value : 'auto', description : 'TinyALSA plu option('transcode', type : 'feature', value : 'auto', description : 'Transcode plugin') option('ttml', type : 'feature', value : 'auto', description : 'TTML subtitle parser and renderer plugin') option('uvch264', type : 'feature', value : 'auto', description : 'UVC compliant H.264 camera source plugin') +option('va', type : 'feature', value : 'auto', description: 'VA-API new plugin') option('voaacenc', type : 'feature', value : 'auto', description : 'AAC audio encoder plugin') option('voamrwbenc', type : 'feature', value : 'auto', description : 'AMR-WB audio encoder plugin') option('vulkan', type : 'feature', value : 'auto', description : 'Vulkan video sink plugin') diff --git a/sys/meson.build b/sys/meson.build index bb0791b06e..cf39e57707 100644 --- a/sys/meson.build +++ b/sys/meson.build @@ -21,8 +21,8 @@ subdir('shm') subdir('tinyalsa') subdir('uvch264') subdir('v4l2codecs') +subdir('va') subdir('wasapi') subdir('wasapi2') subdir('winks') subdir('winscreencap') - diff --git a/sys/va/gstvaallocator.c b/sys/va/gstvaallocator.c new file mode 100644 index 0000000000..3207a0715f --- /dev/null +++ b/sys/va/gstvaallocator.c @@ -0,0 +1,970 @@ +/* GStreamer + * Copyright (C) 2020 Igalia, S.L. + * Author: Víctor Jáquez + * + * 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 "gstvaallocator.h" + +#include +#include +#include + +#include "gstvacaps.h" +#include "gstvavideoformat.h" + +#define GST_CAT_DEFAULT gst_va_memory_debug +GST_DEBUG_CATEGORY_STATIC (gst_va_memory_debug); + +struct _GstVaDmabufAllocator +{ + GstDmaBufAllocator parent; + + /* queue for disposable surfaces */ + GstAtomicQueue *queue; + GstVaDisplay *display; + + GstMemoryMapFunction parent_map; +}; + +static void _init_debug_category (void); + +#define gst_va_dmabuf_allocator_parent_class dmabuf_parent_class +G_DEFINE_TYPE_WITH_CODE (GstVaDmabufAllocator, gst_va_dmabuf_allocator, + GST_TYPE_DMABUF_ALLOCATOR, _init_debug_category ()); + +typedef struct _GstVaBufferSurface GstVaBufferSurface; +struct _GstVaBufferSurface +{ + GstVideoInfo info; + VASurfaceID surface; + volatile gint ref_count; +}; + +static void +_init_debug_category (void) +{ +#ifndef GST_DISABLE_GST_DEBUG + static volatile gsize _init = 0; + + if (g_once_init_enter (&_init)) { + GST_DEBUG_CATEGORY_INIT (gst_va_memory_debug, "vamemory", 0, "VA memory"); + g_once_init_leave (&_init, 1); + } +#endif +} + +static gboolean +_destroy_surfaces (GstVaDisplay * display, VASurfaceID * surfaces, + gint num_surfaces) +{ + VADisplay dpy = gst_va_display_get_va_dpy (display); + VAStatus status; + + g_return_val_if_fail (num_surfaces > 0, FALSE); + + gst_va_display_lock (display); + status = vaDestroySurfaces (dpy, surfaces, num_surfaces); + gst_va_display_unlock (display); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR ("vaDestroySurfaces: %s", vaErrorStr (status)); + return FALSE; + } + + return TRUE; + +} + +static gboolean +_create_surfaces (GstVaDisplay * display, guint rt_format, guint fourcc, + guint width, guint height, gint usage_hint, VASurfaceID * surfaces, + guint num_surfaces) +{ + VADisplay dpy = gst_va_display_get_va_dpy (display); + /* *INDENT-OFF* */ + VASurfaceAttrib attrs[] = { + { + .type = VASurfaceAttribUsageHint, + .flags = VA_SURFACE_ATTRIB_SETTABLE, + .value.type = VAGenericValueTypeInteger, + .value.value.i = usage_hint, + }, + { + .type = VASurfaceAttribPixelFormat, + .flags = VA_SURFACE_ATTRIB_SETTABLE, + .value.type = VAGenericValueTypeInteger, + .value.value.i = fourcc, + }, + { + .type = VASurfaceAttribMemoryType, + .flags = VA_SURFACE_ATTRIB_SETTABLE, + .value.type = VAGenericValueTypeInteger, + .value.value.i = VA_SURFACE_ATTRIB_MEM_TYPE_VA, + }, + }; + /* *INDENT-ON* */ + VAStatus status; + + g_return_val_if_fail (num_surfaces > 0, FALSE); + + gst_va_display_lock (display); + status = vaCreateSurfaces (dpy, rt_format, width, height, surfaces, + num_surfaces, attrs, G_N_ELEMENTS (attrs)); + gst_va_display_unlock (display); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR ("vaCreateSurfaces: %s", vaErrorStr (status)); + return FALSE; + } + + return TRUE; +} + +static gboolean +_export_surface_to_dmabuf (GstVaDisplay * display, VASurfaceID surface, + guint32 flags, VADRMPRIMESurfaceDescriptor * desc) +{ + VADisplay dpy = gst_va_display_get_va_dpy (display); + VAStatus status; + + gst_va_display_lock (display); + status = vaExportSurfaceHandle (dpy, surface, + VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2, flags, desc); + gst_va_display_unlock (display); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR ("vaExportSurfaceHandle: %s", vaErrorStr (status)); + return FALSE; + } + + return TRUE; +} + +static gboolean +_destroy_image (GstVaDisplay * display, VAImageID image_id) +{ + VADisplay dpy = gst_va_display_get_va_dpy (display); + VAStatus status; + + gst_va_display_lock (display); + status = vaDestroyImage (dpy, image_id); + gst_va_display_unlock (display); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR ("vaDestroyImage: %s", vaErrorStr (status)); + return FALSE; + } + return TRUE; +} + +static gboolean +_get_derive_image (GstVaDisplay * display, VASurfaceID surface, VAImage * image) +{ + VADisplay dpy = gst_va_display_get_va_dpy (display); + VAStatus status; + + gst_va_display_lock (display); + status = vaDeriveImage (dpy, surface, image); + gst_va_display_unlock (display); + if (status != VA_STATUS_SUCCESS) { + GST_WARNING ("vaDeriveImage: %s", vaErrorStr (status)); + return FALSE; + } + + return TRUE; +} + +static gboolean +_create_image (GstVaDisplay * display, GstVideoFormat format, gint width, + gint height, VAImage * image) +{ + VADisplay dpy = gst_va_display_get_va_dpy (display); + const VAImageFormat *va_format; + VAStatus status; + + va_format = gst_va_image_format_from_video_format (format); + if (!va_format) + return FALSE; + + gst_va_display_lock (display); + status = + vaCreateImage (dpy, (VAImageFormat *) va_format, width, height, image); + gst_va_display_unlock (display); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR ("vaCreateImage: %s", vaErrorStr (status)); + return FALSE; + } + return TRUE; +} + +static gboolean +_get_image (GstVaDisplay * display, VASurfaceID surface, VAImage * image) +{ + VADisplay dpy = gst_va_display_get_va_dpy (display); + VAStatus status; + + gst_va_display_lock (display); + status = vaGetImage (dpy, surface, 0, 0, image->width, image->height, + image->image_id); + gst_va_display_unlock (display); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR ("vaGetImage: %s", vaErrorStr (status)); + return FALSE; + } + + return TRUE; +} + +static gboolean +_sync_surface (GstVaDisplay * display, VASurfaceID surface) +{ + VADisplay dpy = gst_va_display_get_va_dpy (display); + VAStatus status; + + gst_va_display_lock (display); + status = vaSyncSurface (dpy, surface); + gst_va_display_unlock (display); + if (status != VA_STATUS_SUCCESS) { + GST_WARNING ("vaSyncSurface: %s", vaErrorStr (status)); + return FALSE; + } + return TRUE; +} + +static gboolean +_map_buffer (GstVaDisplay * display, VABufferID buffer, gpointer * data) +{ + VADisplay dpy = gst_va_display_get_va_dpy (display); + VAStatus status; + + gst_va_display_lock (display); + status = vaMapBuffer (dpy, buffer, data); + gst_va_display_unlock (display); + if (status != VA_STATUS_SUCCESS) { + GST_WARNING ("vaMapBuffer: %s", vaErrorStr (status)); + return FALSE; + } + return TRUE; +} + +static gboolean +_unmap_buffer (GstVaDisplay * display, VABufferID buffer) +{ + VADisplay dpy = gst_va_display_get_va_dpy (display); + VAStatus status; + + gst_va_display_lock (display); + status = vaUnmapBuffer (dpy, buffer); + gst_va_display_unlock (display); + if (status != VA_STATUS_SUCCESS) { + GST_WARNING ("vaUnmapBuffer: %s", vaErrorStr (status)); + return FALSE; + } + return TRUE; +} + +static gboolean +_put_image (GstVaDisplay * display, VASurfaceID surface, VAImage * image) +{ + VADisplay dpy = gst_va_display_get_va_dpy (display); + VAStatus status; + + if (!_sync_surface (display, surface)) + return FALSE; + + gst_va_display_lock (display); + status = vaPutImage (dpy, surface, image->image_id, 0, 0, image->width, + image->height, 0, 0, image->width, image->height); + gst_va_display_unlock (display); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR ("vaPutImage: %s", vaErrorStr (status)); + return FALSE; + } + return TRUE; +} + +static GQuark +gst_va_buffer_surface_quark (void) +{ + static gsize surface_quark = 0; + + if (g_once_init_enter (&surface_quark)) { + GQuark quark = g_quark_from_string ("GstVaBufferSurface"); + g_once_init_leave (&surface_quark, quark); + } + + return surface_quark; +} + +static GQuark +gst_va_drm_mod_quark (void) +{ + static gsize drm_mod_quark = 0; + + if (g_once_init_enter (&drm_mod_quark)) { + GQuark quark = g_quark_from_string ("DRMModifier"); + g_once_init_leave (&drm_mod_quark, quark); + } + + return drm_mod_quark; +} + +static gpointer +gst_va_dmabuf_mem_map (GstMemory * gmem, gsize maxsize, GstMapFlags flags) +{ + GstVaDmabufAllocator *self = GST_VA_DMABUF_ALLOCATOR (gmem->allocator); + VASurfaceID surface = gst_va_memory_get_surface (gmem, NULL); + + _sync_surface (self->display, surface); + + /* @TODO: if mapping with flag GST_MAP_VASURFACE return the + * VA_SURFACE_ID. + * if mapping and drm_modifers are not lineal, use vaDeriveImage */ +#ifndef GST_DISABLE_GST_DEBUG + { + guint64 *drm_mod; + + drm_mod = gst_mini_object_get_qdata (GST_MINI_OBJECT (gmem), + gst_va_drm_mod_quark ()); + GST_TRACE_OBJECT (self, "DRM modifiers: %#lx", *drm_mod); + } +#endif + + return self->parent_map (gmem, maxsize, flags); +} + +static void +gst_va_dmabuf_allocator_dispose (GObject * object) +{ + GstVaDmabufAllocator *self = GST_VA_DMABUF_ALLOCATOR (object); + + gst_clear_object (&self->display); + gst_atomic_queue_unref (self->queue); + + G_OBJECT_CLASS (dmabuf_parent_class)->dispose (object); +} + +static void +gst_va_dmabuf_allocator_free (GstAllocator * allocator, GstMemory * mem) +{ + GstVaDmabufAllocator *self = GST_VA_DMABUF_ALLOCATOR (allocator); + GstVaBufferSurface *buf; + + /* first close the dmabuf fd */ + GST_ALLOCATOR_CLASS (dmabuf_parent_class)->free (allocator, mem); + + while ((buf = gst_atomic_queue_pop (self->queue))) { + GST_LOG_OBJECT (self, "Destroying surface %#x", buf->surface); + _destroy_surfaces (self->display, &buf->surface, 1); + g_slice_free (GstVaBufferSurface, buf); + } +} + +static void +gst_va_dmabuf_allocator_class_init (GstVaDmabufAllocatorClass * klass) +{ + GstAllocatorClass *allocator_class = GST_ALLOCATOR_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gst_va_dmabuf_allocator_dispose; + allocator_class->free = gst_va_dmabuf_allocator_free; +} + +static void +gst_va_dmabuf_allocator_init (GstVaDmabufAllocator * self) +{ + self->queue = gst_atomic_queue_new (2); + + self->parent_map = GST_ALLOCATOR (self)->mem_map; + GST_ALLOCATOR (self)->mem_map = gst_va_dmabuf_mem_map; +} + +GstAllocator * +gst_va_dmabuf_allocator_new (GstVaDisplay * display) +{ + GstVaDmabufAllocator *self; + + g_return_val_if_fail (GST_IS_VA_DISPLAY (display), NULL); + + self = g_object_new (GST_TYPE_VA_DMABUF_ALLOCATOR, NULL); + self->display = gst_object_ref (display); + gst_object_ref_sink (self); + + return GST_ALLOCATOR (self); +} + +static GstVaBufferSurface * +_create_buffer_surface (GstVaDmabufAllocator * self, VASurfaceID surface, + GstVideoFormat format, gint width, gint height) +{ + GstVaBufferSurface *buf = g_slice_new (GstVaBufferSurface); + + g_atomic_int_set (&buf->ref_count, 0); + buf->surface = surface; + gst_video_info_set_format (&buf->info, format, width, height); + + return buf; +} + +static inline goffset +_get_fd_size (gint fd) +{ + return lseek (fd, 0, SEEK_END); +} + +static gboolean +gst_va_memory_dispose (GstMiniObject * mini_object) +{ + GstMemory *mem = GST_MEMORY_CAST (mini_object); + GstVaDmabufAllocator *self = GST_VA_DMABUF_ALLOCATOR (mem->allocator); + GstVaBufferSurface *buf; + + buf = gst_mini_object_get_qdata (mini_object, gst_va_buffer_surface_quark ()); + if (buf && g_atomic_int_dec_and_test (&buf->ref_count)) + gst_atomic_queue_push (self->queue, buf); + + return TRUE; +} + +gboolean +gst_va_dmabuf_setup_buffer (GstAllocator * allocator, GstBuffer * buffer, + GstVaAllocationParams * params) +{ + GstVaBufferSurface *buf; + GstVaDmabufAllocator *self = GST_VA_DMABUF_ALLOCATOR (allocator); + GstVideoFormat format; + VADRMPRIMESurfaceDescriptor desc = { 0, }; + VASurfaceID surface; + guint32 i, fourcc, rt_format, export_flags; + + g_return_val_if_fail (GST_IS_VA_DMABUF_ALLOCATOR (allocator), FALSE); + g_return_val_if_fail (params, FALSE); + + format = GST_VIDEO_INFO_FORMAT (¶ms->info); + fourcc = gst_va_fourcc_from_video_format (format); + rt_format = gst_va_chroma_from_video_format (format); + if (fourcc == 0 || rt_format == 0) { + GST_ERROR_OBJECT (allocator, "Unsupported format: %s", + gst_video_format_to_string (GST_VIDEO_INFO_FORMAT (¶ms->info))); + return FALSE; + } + + if (!_create_surfaces (self->display, rt_format, fourcc, + GST_VIDEO_INFO_WIDTH (¶ms->info), + GST_VIDEO_INFO_HEIGHT (¶ms->info), params->usage_hint, &surface, + 1)) + return FALSE; + + /* Each layer will contain exactly one plane. For example, an NV12 + * surface will be exported as two layers */ + export_flags = VA_EXPORT_SURFACE_SEPARATE_LAYERS + | VA_EXPORT_SURFACE_READ_WRITE; + if (!_export_surface_to_dmabuf (self->display, surface, export_flags, &desc)) + goto failed; + + g_assert (GST_VIDEO_INFO_N_PLANES (¶ms->info) == desc.num_layers); + + if (fourcc != desc.fourcc) { + GST_ERROR ("Unsupported fourcc: %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (desc.fourcc)); + goto failed; + } + + buf = _create_buffer_surface (self, surface, format, desc.width, desc.height); + + for (i = 0; i < desc.num_objects; i++) { + gint fd = desc.objects[i].fd; + gsize size = desc.objects[i].size > 0 ? + desc.objects[i].size : _get_fd_size (fd); + GstMemory *mem = gst_dmabuf_allocator_alloc (allocator, fd, size); + guint64 *drm_mod = g_new (guint64, 1); + + gst_buffer_append_memory (buffer, mem); + + GST_MINI_OBJECT (mem)->dispose = gst_va_memory_dispose; + + g_atomic_int_add (&buf->ref_count, 1); + gst_mini_object_set_qdata (GST_MINI_OBJECT (mem), + gst_va_buffer_surface_quark (), buf, NULL); + + *drm_mod = desc.objects[i].drm_format_modifier; + gst_mini_object_set_qdata (GST_MINI_OBJECT (mem), gst_va_drm_mod_quark (), + drm_mod, g_free); + } + + for (i = 0; i < desc.num_layers; i++) { + g_assert (desc.layers[i].num_planes == 1); + GST_VIDEO_INFO_PLANE_OFFSET (&buf->info, i) = desc.layers[i].offset[0]; + GST_VIDEO_INFO_PLANE_STRIDE (&buf->info, i) = desc.layers[i].pitch[0]; + } + + GST_VIDEO_INFO_SIZE (&buf->info) = gst_buffer_get_size (buffer); + GST_LOG_OBJECT (self, "Created surface %#x [%dx%d] size %" G_GSIZE_FORMAT, + buf->surface, GST_VIDEO_INFO_WIDTH (&buf->info), + GST_VIDEO_INFO_HEIGHT (&buf->info), GST_VIDEO_INFO_SIZE (&buf->info)); + + params->info = buf->info; + + return TRUE; + +failed: + { + _destroy_surfaces (self->display, &surface, 1); + return FALSE; + } +} + +gboolean +gst_va_dmabuf_try (GstAllocator * allocator, GstVaAllocationParams * params) +{ + GstBuffer *buffer = gst_buffer_new (); + GstMapInfo map_info; + gboolean ret; + + ret = gst_va_dmabuf_setup_buffer (allocator, buffer, params); + if (ret) { + /* XXX: radeonsi for kadaveri cannot map dmabufs to user space */ + if (!gst_buffer_map (buffer, &map_info, GST_MAP_READWRITE)) { + GST_WARNING_OBJECT (allocator, + "DMABuf backend cannot map frames to user space."); + } + gst_buffer_unmap (buffer, &map_info); + } + gst_buffer_unref (buffer); + + return ret; +} + +/*===================== GstVaAllocator / GstVaMemory =========================*/ + +struct _GstVaAllocator +{ + GstAllocator parent; + + GstVaDisplay *display; + gboolean use_derived; +}; + +typedef struct _GstVaMemory GstVaMemory; +struct _GstVaMemory +{ + GstMemory parent; + + GstVideoInfo info; + VASurfaceID surface; + GstVideoFormat surface_format; + VAImage image; + gpointer mapped_data; + + GstMapFlags prev_mapflags; + volatile gint map_count; + + gboolean is_derived; + gboolean is_dirty; + GMutex lock; +}; + +G_DEFINE_TYPE_WITH_CODE (GstVaAllocator, gst_va_allocator, GST_TYPE_ALLOCATOR, + _init_debug_category ()); + +static void +gst_va_allocator_dispose (GObject * object) +{ + GstVaAllocator *self = GST_VA_ALLOCATOR (object); + + gst_clear_object (&self->display); + + G_OBJECT_CLASS (gst_va_allocator_parent_class)->dispose (object); +} + +static void +_va_free (GstAllocator * allocator, GstMemory * mem) +{ + GstVaAllocator *self = GST_VA_ALLOCATOR (allocator); + GstVaMemory *va_mem = (GstVaMemory *) mem; + + GST_LOG_OBJECT (self, "Destroying surface %#x", va_mem->surface); + + _destroy_surfaces (self->display, &va_mem->surface, 1); + g_mutex_clear (&va_mem->lock); + + g_slice_free (GstVaMemory, va_mem); +} + +static void +gst_va_allocator_class_init (GstVaAllocatorClass * klass) +{ + GstAllocatorClass *allocator_class = GST_ALLOCATOR_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gst_va_allocator_dispose; + allocator_class->free = _va_free; +} + +static inline void +_clean_mem (GstVaMemory * mem) +{ + memset (&mem->image, 0, sizeof (mem->image)); + mem->image.image_id = VA_INVALID_ID; + mem->image.buf = VA_INVALID_ID; + + mem->is_derived = TRUE; + mem->is_dirty = FALSE; + mem->prev_mapflags = 0; + mem->mapped_data = NULL; +} + +static void +_reset_mem (GstVaMemory * mem, GstAllocator * allocator, gsize size) +{ + _clean_mem (mem); + g_atomic_int_set (&mem->map_count, 0); + g_mutex_init (&mem->lock); + + gst_memory_init (GST_MEMORY_CAST (mem), GST_MEMORY_FLAG_NO_SHARE, allocator, + NULL, size, 0 /* align */ , 0 /* offset */ , size); +} + +static inline gboolean +_ensure_image (GstVaDisplay * display, VASurfaceID surface, GstVideoInfo * info, + VAImage * image, gboolean * derived) +{ + gint i; + gboolean try_derived; + + if (image->image_id != VA_INVALID_ID) + return TRUE; + + if (!_sync_surface (display, surface)) + return FALSE; + + try_derived = (derived) ? *derived : FALSE; + + if (try_derived && _get_derive_image (display, surface, image)) + goto bail; + if (!_create_image (display, GST_VIDEO_INFO_FORMAT (info), + GST_VIDEO_INFO_WIDTH (info), GST_VIDEO_INFO_HEIGHT (info), image)) + return FALSE; + + if (derived) + *derived = FALSE; + +bail: + for (i = 0; i < image->num_planes; i++) { + GST_VIDEO_INFO_PLANE_OFFSET (info, i) = image->offsets[i]; + GST_VIDEO_INFO_PLANE_STRIDE (info, i) = image->pitches[i]; + } + + GST_VIDEO_INFO_SIZE (info) = image->data_size; + + return TRUE; +} + +static gpointer +_va_map_unlocked (GstVaMemory * mem, GstMapFlags flags) +{ + GstAllocator *allocator = GST_MEMORY_CAST (mem)->allocator; + GstVaAllocator *va_allocator; + GstVaDisplay *display; + + g_return_val_if_fail (mem->surface != VA_INVALID_ID, NULL); + g_return_val_if_fail (GST_IS_VA_ALLOCATOR (allocator), NULL); + + if (g_atomic_int_get (&mem->map_count) > 0) { + if (mem->prev_mapflags != flags || !mem->mapped_data) + return NULL; + else + goto success; + } + + va_allocator = GST_VA_ALLOCATOR (allocator); + display = va_allocator->display; + + if (flags & GST_MAP_WRITE) { + mem->is_dirty = TRUE; + mem->is_derived = FALSE; + } else { /* GST_MAP_READ only */ + mem->is_dirty = FALSE; + mem->is_derived = va_allocator->use_derived && + (GST_VIDEO_INFO_FORMAT (&mem->info) == mem->surface_format); + } + + if (!_ensure_image (display, mem->surface, &mem->info, &mem->image, + &mem->is_derived)) + return NULL; + + va_allocator->use_derived = mem->is_derived; + + if (!mem->is_derived) { + if (!_get_image (display, mem->surface, &mem->image)) + goto fail; + } + + if (!_map_buffer (display, mem->image.buf, &mem->mapped_data)) + goto fail; + +success: + { + mem->prev_mapflags = flags; + g_atomic_int_add (&mem->map_count, 1); + return mem->mapped_data; + } + +fail: + { + _destroy_image (display, mem->image.image_id); + _clean_mem (mem); + return NULL; + } +} + +static gpointer +_va_map (GstVaMemory * mem, gsize maxsize, GstMapFlags flags) +{ + gpointer data; + + g_mutex_lock (&mem->lock); + data = _va_map_unlocked (mem, flags); + g_mutex_unlock (&mem->lock); + + return data; +} + +static gboolean +_va_unmap_unlocked (GstVaMemory * mem) +{ + GstAllocator *allocator = GST_MEMORY_CAST (mem)->allocator; + GstVaDisplay *display; + gboolean ret = TRUE; + + if (!g_atomic_int_dec_and_test (&mem->map_count)) + return TRUE; + + display = GST_VA_ALLOCATOR (allocator)->display; + + if (mem->image.image_id != VA_INVALID_ID) { + if (mem->is_dirty && !mem->is_derived) { + ret = _put_image (display, mem->surface, &mem->image); + mem->is_dirty = FALSE; + } + /* XXX(victor): if is derived and is dirty, create another surface + * an replace it in mem */ + } + + ret &= _unmap_buffer (display, mem->image.buf); + ret &= _destroy_image (display, mem->image.image_id); + + _clean_mem (mem); + + return ret; +} + +static gboolean +_va_unmap (GstVaMemory * mem) +{ + gboolean ret; + + g_mutex_lock (&mem->lock); + ret = _va_unmap_unlocked (mem); + g_mutex_unlock (&mem->lock); + + return ret; +} + +/* XXX(victor): shallow copy -- only the surface */ +static GstMemory * +_va_copy_unlocked (GstVaMemory * mem) +{ + GstVaMemory *ret; + gsize size; + + ret = g_slice_new (GstVaMemory); + + size = GST_VIDEO_INFO_SIZE (&mem->info); + + ret->info = mem->info; + ret->surface = mem->surface; + + _reset_mem (ret, GST_MEMORY_CAST (mem)->allocator, size); + + return GST_MEMORY_CAST (ret); +} + +static GstMemory * +_va_copy (GstVaMemory * mem, gssize offset, gssize size) +{ + GstMemory *ret; + + g_mutex_lock (&mem->lock); + ret = _va_copy_unlocked (mem); + g_mutex_unlock (&mem->lock); + + return ret; +} + +static GstMemory * +_va_share (GstMemory * mem, gssize offset, gssize size) +{ + /* VA surfaces are opaque structures, which cannot be shared */ + return NULL; +} + +static gboolean +_va_is_span (GstMemory * mem1, GstMemory * mem2, gsize * offset) +{ + /* VA surfaces are opaque structures, which might live in other + * memory. It is impossible to know, so far, if they can mergable. */ + return FALSE; +} + +static void +gst_va_allocator_init (GstVaAllocator * self) +{ + GstAllocator *allocator = GST_ALLOCATOR (self); + + allocator->mem_type = GST_ALLOCATOR_VASURFACE; + allocator->mem_map = (GstMemoryMapFunction) _va_map; + allocator->mem_unmap = (GstMemoryUnmapFunction) _va_unmap; + allocator->mem_copy = (GstMemoryCopyFunction) _va_copy; + allocator->mem_share = _va_share; + allocator->mem_is_span = _va_is_span; + + self->use_derived = TRUE; + + GST_OBJECT_FLAG_SET (self, GST_ALLOCATOR_FLAG_CUSTOM_ALLOC); +} + +GstMemory * +gst_va_allocator_alloc (GstAllocator * allocator, + GstVaAllocationParams * params) +{ + GstVaAllocator *self; + GstVaMemory *mem; + GstVideoFormat format; + VAImage image = { 0, }; + VASurfaceID surface; + guint32 fourcc, rt_format; + + g_return_val_if_fail (GST_IS_VA_ALLOCATOR (allocator), NULL); + + self = GST_VA_ALLOCATOR (allocator); + + format = GST_VIDEO_INFO_FORMAT (¶ms->info); + if (gst_va_video_format_is_extra (format)) + format = GST_VIDEO_FORMAT_NV12; + + fourcc = gst_va_fourcc_from_video_format (format); + rt_format = gst_va_chroma_from_video_format (format); + if (fourcc == 0 || rt_format == 0) { + GST_ERROR_OBJECT (allocator, "Unsupported format: %s", + gst_video_format_to_string (format)); + return NULL; + } + + if (!_create_surfaces (self->display, rt_format, fourcc, + GST_VIDEO_INFO_WIDTH (¶ms->info), + GST_VIDEO_INFO_HEIGHT (¶ms->info), params->usage_hint, &surface, + 1)) + return NULL; + + image.image_id = VA_INVALID_ID; + if (!_ensure_image (self->display, surface, ¶ms->info, &image, NULL)) + return NULL; + _destroy_image (self->display, image.image_id); + + mem = g_slice_new (GstVaMemory); + + mem->surface = surface; + mem->surface_format = format; + mem->info = params->info; + + _reset_mem (mem, allocator, GST_VIDEO_INFO_SIZE (¶ms->info)); + + GST_LOG_OBJECT (self, "Created surface %#x [%dx%d]", mem->surface, + GST_VIDEO_INFO_WIDTH (&mem->info), GST_VIDEO_INFO_HEIGHT (&mem->info)); + + return GST_MEMORY_CAST (mem); +} + +GstAllocator * +gst_va_allocator_new (GstVaDisplay * display) +{ + GstVaAllocator *self; + + g_return_val_if_fail (GST_IS_VA_DISPLAY (display), NULL); + + self = g_object_new (GST_TYPE_VA_ALLOCATOR, NULL); + self->display = gst_object_ref (display); + gst_object_ref_sink (self); + + return GST_ALLOCATOR (self); +} + +gboolean +gst_va_allocator_try (GstAllocator * allocator, GstVaAllocationParams * params) +{ + GstMemory *mem; + + mem = gst_va_allocator_alloc (allocator, params); + if (!mem) + return FALSE; + gst_memory_unref (mem); + return TRUE; +} + +/*============ Utilities =====================================================*/ + +VASurfaceID +gst_va_memory_get_surface (GstMemory * mem, GstVideoInfo * info) +{ + VASurfaceID surface = VA_INVALID_ID; + + if (!mem->allocator) + return VA_INVALID_ID; + + if (GST_IS_VA_DMABUF_ALLOCATOR (mem->allocator)) { + GstVaBufferSurface *buf; + + buf = gst_mini_object_get_qdata (GST_MINI_OBJECT (mem), + gst_va_buffer_surface_quark ()); + if (buf) { + if (info) + *info = buf->info; + surface = buf->surface; + } + } else if (GST_IS_VA_ALLOCATOR (mem->allocator)) { + GstVaMemory *va_mem = (GstVaMemory *) mem; + surface = va_mem->surface; + if (info) + *info = va_mem->info; + } + + return surface; +} + +VASurfaceID +gst_va_buffer_get_surface (GstBuffer * buffer, GstVideoInfo * info) +{ + GstMemory *mem; + + mem = gst_buffer_peek_memory (buffer, 0); + if (!mem) + return VA_INVALID_ID; + + return gst_va_memory_get_surface (mem, info); +} diff --git a/sys/va/gstvaallocator.h b/sys/va/gstvaallocator.h new file mode 100644 index 0000000000..2b37bc4cb2 --- /dev/null +++ b/sys/va/gstvaallocator.h @@ -0,0 +1,64 @@ +/* GStreamer + * Copyright (C) 2020 Igalia, S.L. + * Author: Víctor Jáquez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include + +#include "gstvadisplay.h" + +G_BEGIN_DECLS + +typedef struct _GstVaAllocationParams GstVaAllocationParams; +struct _GstVaAllocationParams +{ + GstVideoInfo info; + guint32 usage_hint; +}; + +#define GST_TYPE_VA_DMABUF_ALLOCATOR (gst_va_dmabuf_allocator_get_type()) +G_DECLARE_FINAL_TYPE (GstVaDmabufAllocator, gst_va_dmabuf_allocator, GST, + VA_DMABUF_ALLOCATOR, GstDmaBufAllocator); + +GstAllocator * gst_va_dmabuf_allocator_new (GstVaDisplay * display); +gboolean gst_va_dmabuf_setup_buffer (GstAllocator * allocator, + GstBuffer * buffer, + GstVaAllocationParams * params); +gboolean gst_va_dmabuf_try (GstAllocator * allocator, + GstVaAllocationParams * params); + +#define GST_TYPE_VA_ALLOCATOR (gst_va_allocator_get_type()) +G_DECLARE_FINAL_TYPE (GstVaAllocator, gst_va_allocator, GST, VA_ALLOCATOR, GstAllocator); + +#define GST_ALLOCATOR_VASURFACE "VAMemory" + +GstAllocator * gst_va_allocator_new (GstVaDisplay * display); +GstMemory * gst_va_allocator_alloc (GstAllocator * allocator, + GstVaAllocationParams * params); +gboolean gst_va_allocator_try (GstAllocator * allocator, + GstVaAllocationParams * params); + +VASurfaceID gst_va_memory_get_surface (GstMemory * mem, + GstVideoInfo * info); +VASurfaceID gst_va_buffer_get_surface (GstBuffer * buffer, + GstVideoInfo * info); + +G_END_DECLS diff --git a/sys/va/gstvacaps.c b/sys/va/gstvacaps.c new file mode 100644 index 0000000000..415aa97379 --- /dev/null +++ b/sys/va/gstvacaps.c @@ -0,0 +1,470 @@ +/* GStreamer + * Copyright (C) 2020 Igalia, S.L. + * Author: Víctor Jáquez + * + * 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 "gstvacaps.h" + +#include + +#include "gstvadisplay.h" +#include "gstvaprofile.h" +#include "gstvavideoformat.h" + +GST_DEBUG_CATEGORY_EXTERN (gst_va_display_debug); +#define GST_CAT_DEFAULT gst_va_display_debug + +static const guint va_rt_format_list[] = { +#define R(name) G_PASTE (VA_RT_FORMAT_, name) + R (YUV420), + R (YUV422), + R (YUV444), + R (YUV411), + R (YUV400), + R (YUV420_10), + R (YUV422_10), + R (YUV444_10), + R (YUV420_12), + R (YUV422_12), + R (YUV444_12), + R (YUV420_10BPP), + R (RGB16), + R (RGB32), + R (RGBP), + R (RGB32_10), + R (RGB32_10BPP), + R (PROTECTED), +#undef R +}; + +VASurfaceAttrib * +gst_va_get_surface_attribs (GstVaDisplay * display, VAConfigID config, + guint * attrib_count) +{ + VADisplay dpy; + VASurfaceAttrib *attribs; + VAStatus status; + + dpy = gst_va_display_get_va_dpy (display); + + gst_va_display_lock (display); + status = vaQuerySurfaceAttributes (dpy, config, NULL, attrib_count); + gst_va_display_unlock (display); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR_OBJECT (display, "vaQuerySurfaceAttributes: %s", + vaErrorStr (status)); + return NULL; + } + + attribs = g_new (VASurfaceAttrib, *attrib_count); + + gst_va_display_lock (display); + status = vaQuerySurfaceAttributes (dpy, config, attribs, attrib_count); + gst_va_display_unlock (display); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR_OBJECT (display, "vaQuerySurfaceAttributes: %s", + vaErrorStr (status)); + goto bail; + } + + return attribs; + +bail: + g_free (attribs); + return NULL; +} + +static gboolean +_array_has_format (GArray * formats, GstVideoFormat format) +{ + GstVideoFormat fmt; + guint i; + + for (i = 0; i < formats->len; i++) { + fmt = g_array_index (formats, GstVideoFormat, i); + if (fmt == format) + return TRUE; + } + return FALSE; +} + +static gboolean +_gst_caps_set_format_array (GstCaps * caps, GArray * formats) +{ + GstVideoFormat fmt; + GValue v_formats = G_VALUE_INIT; + const gchar *format; + guint i; + + if (formats->len == 1) { + fmt = g_array_index (formats, GstVideoFormat, 0); + if (fmt == GST_VIDEO_FORMAT_UNKNOWN) + return FALSE; + format = gst_video_format_to_string (fmt); + if (!format) + return FALSE; + + g_value_init (&v_formats, G_TYPE_STRING); + g_value_set_string (&v_formats, format); + } else if (formats->len > 1) { + + gst_value_list_init (&v_formats, formats->len); + + for (i = 0; i < formats->len; i++) { + GValue item = G_VALUE_INIT; + + fmt = g_array_index (formats, GstVideoFormat, i); + if (fmt == GST_VIDEO_FORMAT_UNKNOWN) + continue; + format = gst_video_format_to_string (fmt); + if (!format) + continue; + + g_value_init (&item, G_TYPE_STRING); + g_value_set_string (&item, format); + gst_value_list_append_value (&v_formats, &item); + g_value_unset (&item); + } + } else { + return FALSE; + } + + gst_caps_set_value (caps, "format", &v_formats); + g_value_unset (&v_formats); + + return TRUE; +} + +/* extra formats to add to raw caps bacause *in theory* all drivers + * could create images from surface's native format (NV12) to + * these. */ +static const GstVideoFormat extra_formats[] = { + GST_VIDEO_FORMAT_I420, GST_VIDEO_FORMAT_YV12 +}; + +gboolean +gst_va_video_format_is_extra (guint format) +{ + guint i; + + for (i = 0; i < G_N_ELEMENTS (extra_formats); i++) { + if (extra_formats[i] == format) + return TRUE; + } + return FALSE; +} + +GstCaps * +gst_va_create_raw_caps_from_config (GstVaDisplay * display, VAConfigID config) +{ + GArray *formats; + GstCaps *caps, *base_caps, *feature_caps; + GstCapsFeatures *features; + GstVideoFormat format; + VASurfaceAttrib *attribs; + guint i, attrib_count, mem_type = 0; + gint min_width = 1, max_width = G_MAXINT; + gint min_height = 1, max_height = G_MAXINT; + + attribs = gst_va_get_surface_attribs (display, config, &attrib_count); + if (!attribs) + return NULL; + formats = g_array_new (FALSE, FALSE, sizeof (GstVideoFormat)); + + for (i = 0; i < attrib_count; i++) { + if (attribs[i].value.type != VAGenericValueTypeInteger) + continue; + switch (attribs[i].type) { + case VASurfaceAttribPixelFormat: + format = gst_va_video_format_from_va_fourcc (attribs[i].value.value.i); + if (format != GST_VIDEO_FORMAT_UNKNOWN) + g_array_append_val (formats, format); + break; + case VASurfaceAttribMinWidth: + min_width = MAX (min_width, attribs[i].value.value.i); + break; + case VASurfaceAttribMaxWidth: + max_width = attribs[i].value.value.i; + break; + case VASurfaceAttribMinHeight: + min_height = MAX (min_height, attribs[i].value.value.i); + break; + case VASurfaceAttribMaxHeight: + max_height = attribs[i].value.value.i; + break; + case VASurfaceAttribMemoryType: + mem_type = attribs[i].value.value.i; + break; + default: + break; + } + } + + base_caps = gst_caps_new_simple ("video/x-raw", "width", GST_TYPE_INT_RANGE, + min_width, max_width, "height", GST_TYPE_INT_RANGE, min_height, + max_height, NULL); + + _gst_caps_set_format_array (base_caps, formats); + + caps = gst_caps_new_empty (); + + if (mem_type & VA_SURFACE_ATTRIB_MEM_TYPE_VA) { + feature_caps = gst_caps_copy (base_caps); + features = gst_caps_features_from_string ("memory:VAMemory"); + gst_caps_set_features_simple (feature_caps, features); + caps = gst_caps_merge (caps, feature_caps); + } + if (mem_type & VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME + || mem_type & VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2) { + feature_caps = gst_caps_copy (base_caps); + features = gst_caps_features_from_string ("memory:DMABuf"); + gst_caps_set_features_simple (feature_caps, features); + caps = gst_caps_merge (caps, feature_caps); + } + /* raw caps */ + { + feature_caps = + gst_caps_new_simple ("video/x-raw", "width", GST_TYPE_INT_RANGE, + min_width, max_width, "height", GST_TYPE_INT_RANGE, min_height, + max_height, NULL); + + if (_array_has_format (formats, GST_VIDEO_FORMAT_NV12)) { + for (i = 0; i < G_N_ELEMENTS (extra_formats); i++) + g_array_append_val (formats, extra_formats[i]); + } + + if (_gst_caps_set_format_array (feature_caps, formats)) + caps = gst_caps_merge (caps, feature_caps); + else + gst_clear_caps (&feature_caps); + } + + gst_caps_unref (base_caps); + g_array_unref (formats); + g_free (attribs); + + return caps; +} + +static GstCaps * +gst_va_create_raw_caps (GstVaDisplay * display, VAProfile profile, + VAEntrypoint entrypoint, guint rt_format) +{ + GstCaps *caps; + VAConfigAttrib attrib = { + .type = VAConfigAttribRTFormat, + .value = rt_format, + }; + VAConfigID config; + VADisplay dpy; + VAStatus status; + + dpy = gst_va_display_get_va_dpy (display); + + gst_va_display_lock (display); + status = vaCreateConfig (dpy, profile, entrypoint, &attrib, 1, &config); + gst_va_display_unlock (display); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR_OBJECT (display, "vaCreateConfig: %s", vaErrorStr (status)); + return NULL; + } + + caps = gst_va_create_raw_caps_from_config (display, config); + + gst_va_display_lock (display); + status = vaDestroyConfig (dpy, config); + gst_va_display_unlock (display); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR_OBJECT (display, "vaDestroyConfig: %s", vaErrorStr (status)); + return NULL; + } + + return caps; +} + +static GstCaps * +gst_va_create_coded_caps (GstVaDisplay * display, VAProfile profile, + VAEntrypoint entrypoint, guint32 * rt_formats_ptr) +{ + GstCaps *caps; + VAConfigAttrib attribs[] = { + {.type = VAConfigAttribMaxPictureWidth,}, + {.type = VAConfigAttribMaxPictureHeight,}, + {.type = VAConfigAttribRTFormat,}, + }; + VADisplay dpy; + VAStatus status; + guint32 value, rt_formats = 0; + gint i, max_width = -1, max_height = -1; + + dpy = gst_va_display_get_va_dpy (display); + + gst_va_display_lock (display); + status = vaGetConfigAttributes (dpy, profile, entrypoint, attribs, + G_N_ELEMENTS (attribs)); + gst_va_display_unlock (display); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR_OBJECT (display, "vaGetConfigAttributes: %s", + vaErrorStr (status)); + return NULL; + } + + for (i = 0; i < G_N_ELEMENTS (attribs); i++) { + value = attribs[i].value; + if (value == VA_ATTRIB_NOT_SUPPORTED) + continue; + switch (attribs[i].type) { + case VAConfigAttribMaxPictureHeight: + if (value <= G_MAXINT) + max_height = value; + break; + case VAConfigAttribMaxPictureWidth: + if (value <= G_MAXINT) + max_width = value; + break; + case VAConfigAttribRTFormat: + rt_formats = value; + break; + default: + break; + } + } + + if (rt_formats_ptr) + *rt_formats_ptr = rt_formats; + + caps = gst_va_profile_caps (profile); + if (!caps) + return NULL; + + if (max_width == -1 || max_height == -1) + return caps; + + gst_caps_set_simple (caps, "width", GST_TYPE_INT_RANGE, 1, max_width, + "height", GST_TYPE_INT_RANGE, 1, max_height, NULL); + + return caps; +} + +gboolean +gst_va_caps_from_profiles (GstVaDisplay * display, GArray * profiles, + VAEntrypoint entrypoint, GstCaps ** codedcaps_ptr, GstCaps ** rawcaps_ptr) +{ + GstCaps *codedcaps, *rawcaps; + VAProfile profile; + gboolean ret; + gint i, j, k; + guint32 rt_formats; + gint min_width = 1, max_width = G_MAXINT; + gint min_height = 1, max_height = G_MAXINT; + + g_return_val_if_fail (GST_IS_VA_DISPLAY (display), FALSE); + g_return_val_if_fail (profiles, FALSE); + + codedcaps = gst_caps_new_empty (); + rawcaps = gst_caps_new_empty (); + + for (i = 0; i < profiles->len; i++) { + GstCaps *profile_codedcaps; + + profile = g_array_index (profiles, VAProfile, i); + profile_codedcaps = gst_va_create_coded_caps (display, profile, entrypoint, + &rt_formats); + if (!profile_codedcaps) + continue; + + for (j = 0; rt_formats && j < G_N_ELEMENTS (va_rt_format_list); j++) { + if (rt_formats & va_rt_format_list[j]) { + GstCaps *profile_rawcaps = gst_va_create_raw_caps (display, profile, + entrypoint, va_rt_format_list[j]); + + if (!profile_rawcaps) + continue; + + /* fetch width and height ranges */ + { + guint num_structures = gst_caps_get_size (profile_rawcaps); + + for (k = 0; k < num_structures; k++) { + GstStructure *st = gst_caps_get_structure (profile_rawcaps, k); + if (!st) + continue; + if (gst_structure_has_field (st, "width") + && gst_structure_has_field (st, "height")) { + const GValue *w = gst_structure_get_value (st, "width"); + const GValue *h = gst_structure_get_value (st, "height"); + + min_width = MAX (min_width, gst_value_get_int_range_min (w)); + max_width = MIN (max_width, gst_value_get_int_range_max (w)); + min_height = MAX (min_height, gst_value_get_int_range_min (h)); + max_height = MIN (max_height, gst_value_get_int_range_max (h)); + } + } + } + + rawcaps = gst_caps_merge (rawcaps, profile_rawcaps); + } + } + + /* check frame size range was specified otherwise use the one used + * by the rawcaps */ + { + guint num_structures = gst_caps_get_size (profile_codedcaps); + + for (k = 0; k < num_structures; k++) { + GstStructure *st = gst_caps_get_structure (profile_codedcaps, k); + if (!st) + continue; + if (!gst_structure_has_field (st, "width")) + gst_structure_set (st, "width", GST_TYPE_INT_RANGE, min_width, + max_width, NULL); + if (!gst_structure_has_field (st, "height")) + gst_structure_set (st, "height", GST_TYPE_INT_RANGE, min_height, + max_height, NULL); + } + } + + codedcaps = gst_caps_merge (codedcaps, profile_codedcaps); + } + + if (gst_caps_is_empty (rawcaps)) + gst_caps_replace (&rawcaps, NULL); + if (gst_caps_is_empty (codedcaps)) + gst_caps_replace (&codedcaps, NULL); + + if ((ret = codedcaps && rawcaps)) { + rawcaps = gst_caps_simplify (rawcaps); + codedcaps = gst_caps_simplify (codedcaps); + + if (rawcaps_ptr) + *rawcaps_ptr = gst_caps_ref (rawcaps); + if (codedcaps_ptr) + *codedcaps_ptr = gst_caps_ref (codedcaps); + } + + if (codedcaps) + gst_caps_unref (codedcaps); + if (rawcaps) + gst_caps_unref (rawcaps); + + return ret; +} diff --git a/sys/va/gstvacaps.h b/sys/va/gstvacaps.h new file mode 100644 index 0000000000..daf4416b67 --- /dev/null +++ b/sys/va/gstvacaps.h @@ -0,0 +1,43 @@ +/* GStreamer + * Copyright (C) 2020 Igalia, S.L. + * Author: Víctor Jáquez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include "gstvadisplay.h" + +G_BEGIN_DECLS + +gboolean gst_va_caps_from_profiles (GstVaDisplay * display, + GArray * profiles, + VAEntrypoint entrypoint, + GstCaps ** codedcaps, + GstCaps ** rawcaps); + +VASurfaceAttrib * gst_va_get_surface_attribs (GstVaDisplay * display, + VAConfigID config, + guint * attrib_count); + +GstCaps * gst_va_create_raw_caps_from_config (GstVaDisplay * display, + VAConfigID config); + +gboolean gst_va_video_format_is_extra (guint format); + +G_END_DECLS + diff --git a/sys/va/gstvadecoder.c b/sys/va/gstvadecoder.c new file mode 100644 index 0000000000..1401260708 --- /dev/null +++ b/sys/va/gstvadecoder.c @@ -0,0 +1,656 @@ +/* GStreamer + * Copyright (C) 2020 Igalia, S.L. + * Author: Víctor Jáquez + * + * 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 "gstvadecoder.h" + +#include "gstvacaps.h" +#include "gstvadisplay_wrapped.h" + +struct _GstVaDecoder +{ + GstObject parent; + + GArray *available_profiles; + GstCaps *srcpad_caps; + GstCaps *sinkpad_caps; + GstVaDisplay *display; + VAConfigID config; + VAContextID context; + VAProfile profile; + guint rt_format; + gint coded_width; + gint coded_height; +}; + +GST_DEBUG_CATEGORY_STATIC (gst_va_decoder_debug); +#define GST_CAT_DEFAULT gst_va_decoder_debug + +#define gst_va_decoder_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstVaDecoder, gst_va_decoder, GST_TYPE_OBJECT, + GST_DEBUG_CATEGORY_INIT (gst_va_decoder_debug, "vadecoder", 0, + "VA Decoder")); + +enum +{ + PROP_DISPLAY = 1, + PROP_PROFILE, + PROP_WIDTH, + PROP_HEIGHT, + PROP_CHROMA, + N_PROPERTIES +}; + +static GParamSpec *g_properties[N_PROPERTIES]; + +static void +gst_va_decoder_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstVaDecoder *self = GST_VA_DECODER (object); + + switch (prop_id) { + case PROP_DISPLAY:{ + g_assert (!self->display); + self->display = g_value_dup_object (value); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_va_decoder_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstVaDecoder *self = GST_VA_DECODER (object); + + switch (prop_id) { + case PROP_DISPLAY: + g_value_set_object (value, self->display); + break; + case PROP_PROFILE: + g_value_set_int (value, self->profile); + break; + case PROP_CHROMA: + g_value_set_uint (value, self->rt_format); + break; + case PROP_WIDTH: + g_value_set_int (value, self->coded_width); + break; + case PROP_HEIGHT: + g_value_set_int (value, self->coded_height); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_va_decoder_dispose (GObject * object) +{ + GstVaDecoder *self = GST_VA_DECODER (object); + + gst_va_decoder_close (self); + + g_clear_pointer (&self->available_profiles, g_array_unref); + gst_clear_object (&self->display); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_va_decoder_class_init (GstVaDecoderClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->set_property = gst_va_decoder_set_property; + gobject_class->get_property = gst_va_decoder_get_property; + gobject_class->dispose = gst_va_decoder_dispose; + + g_properties[PROP_DISPLAY] = + g_param_spec_object ("display", "GstVaDisplay", "GstVADisplay object", + GST_TYPE_VA_DISPLAY, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_properties[PROP_PROFILE] = + g_param_spec_int ("va-profile", "VAProfile", "VA Profile", + VAProfileNone, 50, VAProfileNone, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_properties[PROP_CHROMA] = + g_param_spec_uint ("va-rt-format", "VARTFormat", "VA RT Fromat or chroma", + VA_RT_FORMAT_YUV420, VA_RT_FORMAT_PROTECTED, VA_RT_FORMAT_YUV420, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_properties[PROP_WIDTH] = + g_param_spec_int ("coded-width", "coded-picture-width", + "coded picture width", 0, G_MAXINT, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_properties[PROP_HEIGHT] = + g_param_spec_int ("coded-height", "coded-picture-height", + "coded picture height", 0, G_MAXINT, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPERTIES, g_properties); +} + +static void +gst_va_decoder_init (GstVaDecoder * self) +{ + self->profile = VAProfileNone; + self->config = VA_INVALID_ID; + self->context = VA_INVALID_ID; + self->rt_format = VA_RT_FORMAT_YUV420; + self->coded_width = 0; + self->coded_height = 0; +} + +static gboolean +gst_va_decoder_initialize (GstVaDecoder * self, guint32 codec) +{ + if (self->available_profiles) + return FALSE; + + self->available_profiles = gst_va_display_get_profiles (self->display, codec, + VAEntrypointVLD); + + return (self->available_profiles != NULL); +} + +GstVaDecoder * +gst_va_decoder_new (GstVaDisplay * display, guint32 codec) +{ + GstVaDecoder *self; + + g_return_val_if_fail (GST_IS_VA_DISPLAY (display), NULL); + + self = g_object_new (GST_TYPE_VA_DECODER, "display", display, NULL); + if (!gst_va_decoder_initialize (self, codec)) + gst_clear_object (&self); + + return self; +} + +gboolean +gst_va_decoder_is_open (GstVaDecoder * self) +{ + gboolean ret; + + g_return_val_if_fail (GST_IS_VA_DECODER (self), FALSE); + + GST_OBJECT_LOCK (self); + ret = (self->config != VA_INVALID_ID && self->profile != VAProfileNone); + GST_OBJECT_UNLOCK (self); + return ret; +} + +gboolean +gst_va_decoder_open (GstVaDecoder * self, VAProfile profile, guint rt_format) +{ + VAConfigAttrib attrib = { + .type = VAConfigAttribRTFormat, + .value = rt_format, + }; + VADisplay dpy; + VAStatus status; + + g_return_val_if_fail (GST_IS_VA_DECODER (self), FALSE); + + if (self->config != VA_INVALID_ID) + return TRUE; + + if (!gst_va_decoder_has_profile (self, profile)) { + GST_ERROR_OBJECT (self, "Unsupported profile: %d", profile); + return FALSE; + } + + dpy = gst_va_display_get_va_dpy (self->display); + gst_va_display_lock (self->display); + status = vaCreateConfig (dpy, profile, VAEntrypointVLD, &attrib, 1, + &self->config); + gst_va_display_unlock (self->display); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR_OBJECT (self, "vaCreateConfig: %s", vaErrorStr (status)); + return FALSE; + } + + GST_OBJECT_LOCK (self); + self->profile = profile; + self->rt_format = rt_format; + GST_OBJECT_UNLOCK (self); + + /* now we should return now only this profile's caps */ + gst_caps_replace (&self->srcpad_caps, NULL); + + return TRUE; +} + +gboolean +gst_va_decoder_close (GstVaDecoder * self) +{ + VADisplay dpy; + VAStatus status; + + g_return_val_if_fail (GST_IS_VA_DECODER (self), FALSE); + + if (!gst_va_decoder_is_open (self)) + return TRUE; + + dpy = gst_va_display_get_va_dpy (self->display); + + if (self->context != VA_INVALID_ID) { + gst_va_display_lock (self->display); + status = vaDestroyContext (dpy, self->context); + gst_va_display_unlock (self->display); + if (status != VA_STATUS_SUCCESS) + GST_ERROR_OBJECT (self, "vaDestroyContext: %s", vaErrorStr (status)); + } + + gst_va_display_lock (self->display); + status = vaDestroyConfig (dpy, self->config); + gst_va_display_unlock (self->display); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR_OBJECT (self, "vaDestroyConfig: %s", vaErrorStr (status)); + return FALSE; + } + + GST_OBJECT_LOCK (self); + gst_va_decoder_init (self); + GST_OBJECT_UNLOCK (self); + + gst_caps_replace (&self->srcpad_caps, NULL); + gst_caps_replace (&self->sinkpad_caps, NULL); + + return TRUE; +} + +gboolean +gst_va_decoder_set_format (GstVaDecoder * self, gint coded_width, + gint coded_height, GArray * surfaces) +{ + VADisplay dpy; + VAStatus status; + VASurfaceID *render_targets = NULL; + gint num_render_targets = 0; + + g_return_val_if_fail (GST_IS_VA_DECODER (self), FALSE); + + if (self->context != VA_INVALID_ID) { + GST_WARNING_OBJECT (self, "decoder already has a format"); + return TRUE; + } + + if (!gst_va_decoder_is_open (self)) { + GST_ERROR_OBJECT (self, "decoder has not been opened yet"); + return FALSE; + } + + if (surfaces) { + num_render_targets = surfaces->len; + render_targets = (VASurfaceID *) surfaces->data; + } + + dpy = gst_va_display_get_va_dpy (self->display); + + gst_va_display_lock (self->display); + status = vaCreateContext (dpy, self->config, coded_width, coded_height, + VA_PROGRESSIVE, render_targets, num_render_targets, &self->context); + gst_va_display_unlock (self->display); + + if (status != VA_STATUS_SUCCESS) { + GST_ERROR_OBJECT (self, "vaDestroyConfig: %s", vaErrorStr (status)); + return FALSE; + } + + GST_OBJECT_LOCK (self); + self->coded_width = coded_width; + self->coded_height = coded_height; + GST_OBJECT_UNLOCK (self); + + return TRUE; +} + +static gboolean +_get_codec_caps (GstVaDecoder * self) +{ + GstCaps *sinkpad_caps = NULL, *srcpad_caps = NULL; + + if (!gst_va_decoder_is_open (self) + && GST_IS_VA_DISPLAY_WRAPPED (self->display)) { + if (gst_va_caps_from_profiles (self->display, self->available_profiles, + VAEntrypointVLD, &sinkpad_caps, &srcpad_caps)) { + gst_caps_replace (&self->sinkpad_caps, sinkpad_caps); + gst_caps_replace (&self->srcpad_caps, srcpad_caps); + gst_caps_unref (srcpad_caps); + gst_caps_unref (sinkpad_caps); + + return TRUE; + } + } + + return FALSE; +} + +GstCaps * +gst_va_decoder_get_srcpad_caps (GstVaDecoder * self) +{ + GstCaps *srcpad_caps = NULL; + + g_return_val_if_fail (GST_IS_VA_DECODER (self), FALSE); + + if (self->srcpad_caps) + return gst_caps_ref (self->srcpad_caps); + + if (_get_codec_caps (self)) + return gst_caps_ref (self->srcpad_caps); + + if (gst_va_decoder_is_open (self)) { + srcpad_caps = gst_va_create_raw_caps_from_config (self->display, + self->config); + gst_caps_replace (&self->srcpad_caps, srcpad_caps); + gst_caps_unref (srcpad_caps); + + return gst_caps_ref (self->srcpad_caps); + } + + return NULL; +} + +GstCaps * +gst_va_decoder_get_sinkpad_caps (GstVaDecoder * self) +{ + g_return_val_if_fail (GST_IS_VA_DECODER (self), FALSE); + + if (self->sinkpad_caps) + return gst_caps_ref (self->sinkpad_caps); + + if (_get_codec_caps (self)) + return gst_caps_ref (self->sinkpad_caps); + + return NULL; +} + +gboolean +gst_va_decoder_has_profile (GstVaDecoder * self, VAProfile profile) +{ + gint i; + + g_return_val_if_fail (GST_IS_VA_DECODER (self), FALSE); + + if (profile == VAProfileNone) + return FALSE; + + for (i = 0; i < self->available_profiles->len; i++) { + if (g_array_index (self->available_profiles, VAProfile, i) == profile) + return TRUE; + } + + return FALSE; +} + +gint +gst_va_decoder_get_mem_types (GstVaDecoder * self) +{ + VASurfaceAttrib *attribs; + guint i, attrib_count; + gint ret = 0; + + g_return_val_if_fail (GST_IS_VA_DECODER (self), 0); + + if (!gst_va_decoder_is_open (self)) + return 0; + + attribs = gst_va_get_surface_attribs (self->display, self->config, + &attrib_count); + if (!attribs) + return 0; + + for (i = 0; i < attrib_count; i++) { + if (attribs[i].value.type != VAGenericValueTypeInteger) + continue; + switch (attribs[i].type) { + case VASurfaceAttribMemoryType: + ret = attribs[i].value.value.i; + break; + default: + break; + } + } + + g_free (attribs); + + return ret; +} + +gboolean +gst_va_decoder_add_param_buffer (GstVaDecoder * self, GstVaDecodePicture * pic, + gint type, gpointer data, gsize size) +{ + VABufferID buffer; + VADisplay dpy; + VAStatus status; + + g_return_val_if_fail (GST_IS_VA_DECODER (self), FALSE); + g_return_val_if_fail (self->context != VA_INVALID_ID, FALSE); + g_return_val_if_fail (pic && data && size > 0, FALSE); + g_return_val_if_fail (pic->buffers->len + 1 <= 16, FALSE); + + dpy = gst_va_display_get_va_dpy (self->display); + gst_va_display_lock (self->display); + status = vaCreateBuffer (dpy, self->context, type, size, 1, data, &buffer); + gst_va_display_unlock (self->display); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR_OBJECT (self, "vaCreateBuffer: %s", vaErrorStr (status)); + return FALSE; + } + + g_array_append_val (pic->buffers, buffer); + return TRUE; +} + +gboolean +gst_va_decoder_add_slice_buffer (GstVaDecoder * self, GstVaDecodePicture * pic, + gpointer params_data, gsize params_size, gpointer slice_data, + gsize slice_size) +{ + VABufferID params_buffer, slice_buffer; + VADisplay dpy; + VAStatus status; + + g_return_val_if_fail (GST_IS_VA_DECODER (self), FALSE); + g_return_val_if_fail (self->context != VA_INVALID_ID, FALSE); + g_return_val_if_fail (pic && slice_data && slice_size > 0 + && params_data && params_size > 0, FALSE); + + dpy = gst_va_display_get_va_dpy (self->display); + gst_va_display_lock (self->display); + status = vaCreateBuffer (dpy, self->context, VASliceParameterBufferType, + params_size, 1, params_data, ¶ms_buffer); + gst_va_display_unlock (self->display); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR_OBJECT (self, "vaCreateBuffer: %s", vaErrorStr (status)); + return FALSE; + } + + gst_va_display_lock (self->display); + status = vaCreateBuffer (dpy, self->context, VASliceDataBufferType, + slice_size, 1, slice_data, &slice_buffer); + gst_va_display_unlock (self->display); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR_OBJECT (self, "vaCreateBuffer: %s", vaErrorStr (status)); + return FALSE; + } + + g_array_append_val (pic->slices, params_buffer); + g_array_append_val (pic->slices, slice_buffer); + + return TRUE; +} + +gboolean +gst_va_decoder_decode (GstVaDecoder * self, GstVaDecodePicture * pic) +{ + VADisplay dpy; + VAStatus status; + gboolean ret = FALSE; + + g_return_val_if_fail (GST_IS_VA_DECODER (self), FALSE); + g_return_val_if_fail (self->context != VA_INVALID_ID, FALSE); + g_return_val_if_fail (pic && pic->surface != VA_INVALID_ID, FALSE); + + GST_TRACE_OBJECT (self, "Decode to surface %#x", pic->surface); + + dpy = gst_va_display_get_va_dpy (self->display); + + gst_va_display_lock (self->display); + status = vaBeginPicture (dpy, self->context, pic->surface); + gst_va_display_unlock (self->display); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR_OBJECT (self, "vaBeginPicture: %s", vaErrorStr (status)); + goto fail_end_pic; + } + + gst_va_display_lock (self->display); + status = vaRenderPicture (dpy, self->context, + (VABufferID *) pic->buffers->data, pic->buffers->len); + gst_va_display_unlock (self->display); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR_OBJECT (self, "vaRenderPicture: %s", vaErrorStr (status)); + goto fail_end_pic; + } + + gst_va_display_lock (self->display); + status = vaRenderPicture (dpy, self->context, + (VABufferID *) pic->slices->data, pic->slices->len); + gst_va_display_unlock (self->display); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR_OBJECT (self, "vaRenderPicture: %s", vaErrorStr (status)); + goto fail_end_pic; + } + + gst_va_display_lock (self->display); + status = vaEndPicture (dpy, self->context); + gst_va_display_unlock (self->display); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR_OBJECT (self, "vaEndPicture: %s", vaErrorStr (status)); + goto bail; + } + + ret = TRUE; + +bail: + gst_va_decoder_destroy_buffers (self, pic); + + return ret; + +fail_end_pic: + { + gst_va_display_lock (self->display); + status = vaEndPicture (dpy, self->context); + gst_va_display_unlock (self->display); + if (status != VA_STATUS_SUCCESS) + GST_ERROR_OBJECT (self, "vaEndPicture: %s", vaErrorStr (status)); + goto bail; + } +} + +gboolean +gst_va_decoder_destroy_buffers (GstVaDecoder * self, GstVaDecodePicture * pic) +{ + VABufferID buffer; + VADisplay dpy; + VAStatus status; + guint i; + gboolean ret = TRUE; + + g_return_val_if_fail (GST_IS_VA_DECODER (self), FALSE); + g_return_val_if_fail (pic && pic->surface != VA_INVALID_ID, FALSE); + + GST_TRACE_OBJECT (self, "Destroy buffers of surface %#x", pic->surface); + + dpy = gst_va_display_get_va_dpy (self->display); + + for (i = 0; i < pic->buffers->len; i++) { + buffer = g_array_index (pic->buffers, VABufferID, i); + gst_va_display_lock (self->display); + status = vaDestroyBuffer (dpy, buffer); + gst_va_display_unlock (self->display); + if (status != VA_STATUS_SUCCESS) { + ret = FALSE; + GST_WARNING ("Failed to destroy parameter buffer: %s", + vaErrorStr (status)); + } + } + + for (i = 0; i < pic->slices->len; i++) { + buffer = g_array_index (pic->slices, VABufferID, i); + gst_va_display_lock (self->display); + status = vaDestroyBuffer (dpy, buffer); + gst_va_display_unlock (self->display); + if (status != VA_STATUS_SUCCESS) { + ret = FALSE; + GST_WARNING ("Failed to destroy slice buffer: %s", vaErrorStr (status)); + } + } + + pic->buffers = g_array_set_size (pic->buffers, 0); + pic->slices = g_array_set_size (pic->slices, 0); + + return ret; +} + + +GstVaDecodePicture * +gst_va_decoder_new_decode_picture (GstVaDecoder * self, VASurfaceID surface) +{ + GstVaDecodePicture *pic; + + g_return_val_if_fail (GST_IS_VA_DECODER (self), NULL); + g_return_val_if_fail (surface != VA_INVALID_ID, NULL); + + pic = g_slice_new (GstVaDecodePicture); + pic->surface = surface; + pic->buffers = g_array_sized_new (FALSE, FALSE, sizeof (VABufferID), 16); + pic->slices = g_array_sized_new (FALSE, FALSE, sizeof (VABufferID), 64); + + return pic; +} + +void +gst_va_decode_picture_free (GstVaDecodePicture * pic) +{ + g_return_if_fail (pic); + + if (pic->buffers->len > 0 || pic->slices->len > 0) + GST_WARNING ("VABufferID are leaked"); + + g_clear_pointer (&pic->buffers, g_array_unref); + g_clear_pointer (&pic->slices, g_array_unref); + + g_slice_free (GstVaDecodePicture, pic); +} diff --git a/sys/va/gstvadecoder.h b/sys/va/gstvadecoder.h new file mode 100644 index 0000000000..4d685ee868 --- /dev/null +++ b/sys/va/gstvadecoder.h @@ -0,0 +1,75 @@ +/* GStreamer + * Copyright (C) 2020 Igalia, S.L. + * Author: Víctor Jáquez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include "gstvadisplay.h" + +G_BEGIN_DECLS + +typedef struct _GstVaDecodePicture GstVaDecodePicture; +struct _GstVaDecodePicture +{ + GArray *buffers; + GArray *slices; + VASurfaceID surface; +}; + +#define GST_TYPE_VA_DECODER (gst_va_decoder_get_type()) +G_DECLARE_FINAL_TYPE (GstVaDecoder, gst_va_decoder, GST, VA_DECODER, GstObject) + +GstVaDecoder * gst_va_decoder_new (GstVaDisplay * display, + guint32 codec); +gboolean gst_va_decoder_open (GstVaDecoder * self, + VAProfile profile, + guint rt_format); +gboolean gst_va_decoder_close (GstVaDecoder * self); +gboolean gst_va_decoder_is_open (GstVaDecoder * self); +gboolean gst_va_decoder_set_format (GstVaDecoder * self, + gint coded_width, + gint coded_height, + GArray * surfaces); +GstCaps * gst_va_decoder_get_srcpad_caps (GstVaDecoder * self); +GstCaps * gst_va_decoder_get_sinkpad_caps (GstVaDecoder * self); +gboolean gst_va_decoder_has_profile (GstVaDecoder * self, + VAProfile profile); +gint gst_va_decoder_get_mem_types (GstVaDecoder * self); + +gboolean gst_va_decoder_add_param_buffer (GstVaDecoder * self, + GstVaDecodePicture * pic, + gint type, + gpointer data, + gsize size); +gboolean gst_va_decoder_add_slice_buffer (GstVaDecoder * self, + GstVaDecodePicture * pic, + gpointer params_data, + gsize params_size, + gpointer slice_data, + gsize slice_size); +gboolean gst_va_decoder_decode (GstVaDecoder * self, + GstVaDecodePicture * pic); +gboolean gst_va_decoder_destroy_buffers (GstVaDecoder * self, + GstVaDecodePicture * pic); + +GstVaDecodePicture * gst_va_decoder_new_decode_picture (GstVaDecoder * self, + VASurfaceID surface); +void gst_va_decode_picture_free (GstVaDecodePicture * pic); + +G_END_DECLS diff --git a/sys/va/gstvadevice.c b/sys/va/gstvadevice.c new file mode 100644 index 0000000000..d666285a5b --- /dev/null +++ b/sys/va/gstvadevice.c @@ -0,0 +1,97 @@ +/* GStreamer + * Copyright (C) 2020 Igalia, S.L. + * Author: Víctor Jáquez + * + * 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 "gstvadevice.h" + +#include +#include "gstvadisplay_drm.h" + +#define GST_CAT_DEFAULT gstva_debug +GST_DEBUG_CATEGORY_EXTERN (gstva_debug); + +GST_DEFINE_MINI_OBJECT_TYPE (GstVaDevice, gst_va_device); + +static void +gst_va_device_free (GstVaDevice * device) +{ + if (device->display) + gst_object_unref (device->display); + g_free (device->render_device_path); + g_free (device); +} + +static GstVaDevice * +gst_va_device_new (GstVaDisplay * display, const gchar * render_device_path) +{ + GstVaDevice *device = g_new0 (GstVaDevice, 1); + + gst_mini_object_init (GST_MINI_OBJECT_CAST (device), 0, GST_TYPE_VA_DEVICE, + NULL, NULL, (GstMiniObjectFreeFunction) gst_va_device_free); + + /* take ownership */ + device->display = display; + device->render_device_path = g_strdup (render_device_path); + + return device; +} + +GList * +gst_va_device_find_devices (void) +{ + GUdevClient *client; + GList *udev_devices, *dev; + GQueue devices = G_QUEUE_INIT; + + client = g_udev_client_new (NULL); + udev_devices = g_udev_client_query_by_subsystem (client, "drm"); + + for (dev = udev_devices; dev; dev = g_list_next (dev)) { + GstVaDisplay *dpy; + GUdevDevice *udev = (GUdevDevice *) dev->data; + const gchar *path = g_udev_device_get_device_file (udev); + const gchar *name = g_udev_device_get_name (udev); + + if (!path || !g_str_has_prefix (name, "renderD")) { + GST_LOG ("Ignoring %s in %s", name, path); + continue; + } + + if (!(dpy = gst_va_display_drm_new_from_path (path))) + continue; + + GST_INFO ("Found VA-API device: %s", path); + g_queue_push_tail (&devices, gst_va_device_new (dpy, path)); + } + + g_list_free_full (udev_devices, g_object_unref); + g_object_unref (client); + + return devices.head; +} + +void +gst_va_device_list_free (GList * devices) +{ + g_list_free_full (devices, (GDestroyNotify) gst_mini_object_unref); +} diff --git a/sys/va/gstvadevice.h b/sys/va/gstvadevice.h new file mode 100644 index 0000000000..f5bc7df3a2 --- /dev/null +++ b/sys/va/gstvadevice.h @@ -0,0 +1,45 @@ +/* GStreamer + * Copyright (C) 2020 Igalia, S.L. + * Author: Víctor Jáquez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#include "gstvadisplay.h" + +#define GST_TYPE_VA_DEVICE (gst_va_device_get_type()) +#define GST_IS_VA_DEVICE(obj) (GST_IS_MINI_OBJECT_TYPE((obj), GST_TYPE_VA_DEVICE)) +#define GST_VA_DEVICE(obj) ((GstVaDevice *)(obj)) + +typedef struct +{ + GstMiniObject mini_object; + + GstVaDisplay *display; + gchar *render_device_path; +} GstVaDevice; + +GType gst_va_device_get_type (void); +GList * gst_va_device_find_devices (void); +void gst_va_device_list_free (GList * devices); + +G_END_DECLS diff --git a/sys/va/gstvadisplay.c b/sys/va/gstvadisplay.c new file mode 100644 index 0000000000..0b03da7e34 --- /dev/null +++ b/sys/va/gstvadisplay.c @@ -0,0 +1,369 @@ +/* GStreamer + * Copyright (C) 2020 Igalia, S.L. + * Author: Víctor Jáquez + * + * 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 "gstvadisplay.h" +#include "gstvaprofile.h" + +GST_DEBUG_CATEGORY (gst_va_display_debug); +#define GST_CAT_DEFAULT gst_va_display_debug + +typedef struct _GstVaDisplayPrivate GstVaDisplayPrivate; +struct _GstVaDisplayPrivate +{ + GRecMutex lock; + VADisplay display; + + gboolean foreign; + gboolean init; +}; + +#define gst_va_display_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstVaDisplay, gst_va_display, GST_TYPE_OBJECT, + G_ADD_PRIVATE (GstVaDisplay); + GST_DEBUG_CATEGORY_INIT (gst_va_display_debug, "vadisplay", 0, + "VA Display")); +enum +{ + PROP_VA_DISPLAY = 1, + N_PROPERTIES +}; + +static GParamSpec *g_properties[N_PROPERTIES]; + +#define GET_PRIV(obj) gst_va_display_get_instance_private (GST_VA_DISPLAY (obj)) + +static gboolean +_gst_va_display_driver_filter (VADisplay display) +{ + const char *vendor = vaQueryVendorString (display); + + GST_INFO ("VA-API driver vendor: %s", vendor); + + /* XXX(victor): driver quirks & driver whitelist */ + + return TRUE; +} + +static void +gst_va_display_set_display (GstVaDisplay * self, gpointer display) +{ + GstVaDisplayPrivate *priv = GET_PRIV (self); + + if (!display) + return; + + if (vaDisplayIsValid (display) == 0) { + GST_WARNING_OBJECT (self, + "User's VA display is invalid. An internal one will be tried."); + return; + } + + if (!_gst_va_display_driver_filter (display)) + return; + + priv->display = display; + priv->foreign = TRUE; + + /* assume driver is already initialized */ + priv->init = TRUE; +} + +static void +gst_va_display_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstVaDisplay *self = GST_VA_DISPLAY (object); + + switch (prop_id) { + case PROP_VA_DISPLAY:{ + gpointer display = g_value_get_pointer (value); + gst_va_display_set_display (self, display); + break; + } + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_va_display_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstVaDisplayPrivate *priv = GET_PRIV (object); + + switch (prop_id) { + case PROP_VA_DISPLAY: + g_value_set_pointer (value, priv->display); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_va_display_constructed (GObject * object) +{ + GstVaDisplay *self = GST_VA_DISPLAY (object); + GstVaDisplayPrivate *priv = GET_PRIV (object); + GstVaDisplayClass *klass = GST_VA_DISPLAY_GET_CLASS (object); + + if (!priv->display && klass->create_va_display) + priv->display = klass->create_va_display (self); + + G_OBJECT_CLASS (parent_class)->constructed (object); +} + +static void +gst_va_display_dispose (GObject * object) +{ + GstVaDisplayPrivate *priv = GET_PRIV (object); + + if (priv->display && !priv->foreign) + vaTerminate (priv->display); + priv->display = NULL; + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_va_display_finalize (GObject * object) +{ + GstVaDisplayPrivate *priv = GET_PRIV (object); + + g_rec_mutex_clear (&priv->lock); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_va_display_class_init (GstVaDisplayClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->set_property = gst_va_display_set_property; + gobject_class->get_property = gst_va_display_get_property; + gobject_class->constructed = gst_va_display_constructed; + gobject_class->dispose = gst_va_display_dispose; + gobject_class->finalize = gst_va_display_finalize; + + g_properties[PROP_VA_DISPLAY] = + g_param_spec_pointer ("va-display", "VADisplay", "VA Display handler", + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPERTIES, g_properties); +} + +static void +gst_va_display_init (GstVaDisplay * self) +{ + GstVaDisplayPrivate *priv = GET_PRIV (self); + + g_rec_mutex_init (&priv->lock); +} + +void +gst_va_display_lock (GstVaDisplay * self) +{ + GstVaDisplayPrivate *priv; + + g_return_if_fail (GST_IS_VA_DISPLAY (self)); + + priv = GET_PRIV (self); + + g_rec_mutex_lock (&priv->lock); +} + +void +gst_va_display_unlock (GstVaDisplay * self) +{ + GstVaDisplayPrivate *priv; + + g_return_if_fail (GST_IS_VA_DISPLAY (self)); + + priv = GET_PRIV (self); + + g_rec_mutex_unlock (&priv->lock); +} + +#ifndef GST_DISABLE_GST_DEBUG +static gchar * +_strip_msg (const char *message) +{ + gchar *msg = g_strdup (message); + if (!msg) + return NULL; + return g_strstrip (msg); +} + +static void +_va_warning (gpointer object, const char *message) +{ + GstVaDisplay *self = GST_VA_DISPLAY (object); + gchar *msg; + + if ((msg = _strip_msg (message))) { + GST_WARNING_OBJECT (self, "VA error: %s", msg); + g_free (msg); + } +} + +static void +_va_info (gpointer object, const char *message) +{ + GstVaDisplay *self = GST_VA_DISPLAY (object); + gchar *msg; + + if ((msg = _strip_msg (message))) { + GST_INFO_OBJECT (self, "VA info: %s", msg); + g_free (msg); + } +} +#endif + +/** + * gst_va_display_initialize: + * @self: a #GstVaDisplay + * + * If the display is set by the user (foreign) it is assumed that the + * driver is already initialized, thus this function is noop. + * + * If the display is opened internally, this function will initialize + * the driver and it will set driver's message callbacks. + * + * NOTE: this function is supposed to be private, only used by + * GstVaDisplay descendants. + * + * Returns: %TRUE if the VA driver can be initialized; %FALSE + * otherwise + **/ +gboolean +gst_va_display_initialize (GstVaDisplay * self) +{ + GstVaDisplayPrivate *priv; + VAStatus status; + int major_version = -1, minor_version = -1; + + g_return_val_if_fail (GST_IS_VA_DISPLAY (self), FALSE); + + priv = GET_PRIV (self); + + if (priv->init) + return TRUE; + + if (!priv->display) + return FALSE; + +#ifndef GST_DISABLE_GST_DEBUG + vaSetErrorCallback (priv->display, _va_warning, self); + vaSetInfoCallback (priv->display, _va_info, self); +#endif + + status = vaInitialize (priv->display, &major_version, &minor_version); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR_OBJECT (self, "vaInitialize: %s", vaErrorStr (status)); + return FALSE; + } + + GST_INFO_OBJECT (self, "VA-API version %d.%d", major_version, minor_version); + + priv->init = TRUE; + + if (!_gst_va_display_driver_filter (priv->display)) + return FALSE; + + return TRUE; +} + +VADisplay +gst_va_display_get_va_dpy (GstVaDisplay * self) +{ + VADisplay dpy = 0; + + g_return_val_if_fail (GST_IS_VA_DISPLAY (self), 0); + + g_object_get (self, "va-display", &dpy, NULL); + return dpy; +} + +GArray * +gst_va_display_get_profiles (GstVaDisplay * self, guint32 codec, + VAEntrypoint entrypoint) +{ + GArray *ret = NULL; + VADisplay dpy; + VAEntrypoint *entrypoints; + VAProfile *profiles; + VAStatus status; + gint i, j, num_entrypoints = 0, num_profiles = 0; + + g_return_val_if_fail (GST_IS_VA_DISPLAY (self), NULL); + + dpy = gst_va_display_get_va_dpy (self); + + gst_va_display_lock (self); + num_profiles = vaMaxNumProfiles (dpy); + num_entrypoints = vaMaxNumEntrypoints (dpy); + gst_va_display_unlock (self); + + profiles = g_new (VAProfile, num_profiles); + entrypoints = g_new (VAEntrypoint, num_entrypoints); + + gst_va_display_lock (self); + status = vaQueryConfigProfiles (dpy, profiles, &num_profiles); + gst_va_display_unlock (self); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR ("vaQueryConfigProfile: %s", vaErrorStr (status)); + goto bail; + } + + for (i = 0; i < num_profiles; i++) { + if (codec != gst_va_profile_codec (profiles[i])) + continue; + + gst_va_display_lock (self); + status = vaQueryConfigEntrypoints (dpy, profiles[i], entrypoints, + &num_entrypoints); + gst_va_display_unlock (self); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR ("vaQueryConfigEntrypoints: %s", vaErrorStr (status)); + goto bail; + } + + for (j = 0; j < num_entrypoints; j++) { + if (entrypoints[j] == entrypoint) { + if (!ret) + ret = g_array_new (FALSE, FALSE, sizeof (VAProfile)); + g_array_append_val (ret, profiles[i]); + break; + } + } + } + +bail: + g_free (entrypoints); + g_free (profiles); + return ret; +} diff --git a/sys/va/gstvadisplay.h b/sys/va/gstvadisplay.h new file mode 100644 index 0000000000..87bd39f1bc --- /dev/null +++ b/sys/va/gstvadisplay.h @@ -0,0 +1,48 @@ +/* GStreamer + * Copyright (C) 2020 Igalia, S.L. + * Author: Víctor Jáquez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_VA_DISPLAY (gst_va_display_get_type()) +G_DECLARE_DERIVABLE_TYPE (GstVaDisplay, gst_va_display, GST, VA_DISPLAY, GstObject) + +struct _GstVaDisplayClass +{ + /*< private > */ + GstObjectClass parent_class; + + /*< public > */ + gpointer (*create_va_display) (GstVaDisplay * self); +}; + +void gst_va_display_lock (GstVaDisplay * self); +void gst_va_display_unlock (GstVaDisplay * self); +gboolean gst_va_display_initialize (GstVaDisplay * self); +VADisplay gst_va_display_get_va_dpy (GstVaDisplay * self); +GArray * gst_va_display_get_profiles (GstVaDisplay * self, + guint32 codec, + VAEntrypoint entrypoint); + +G_END_DECLS diff --git a/sys/va/gstvadisplay_drm.c b/sys/va/gstvadisplay_drm.c new file mode 100644 index 0000000000..93625ea436 --- /dev/null +++ b/sys/va/gstvadisplay_drm.c @@ -0,0 +1,188 @@ +/* GStreamer + * Copyright (C) 2020 Igalia, S.L. + * Author: Víctor Jáquez + * + * 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 "gstvadisplay_drm.h" +#include +#include +#include +#include +#include + +#if HAVE_LIBDRM +#include +#endif + +struct _GstVaDisplayDrm +{ + GstVaDisplay parent; + + gchar *path; + gint fd; +}; + +GST_DEBUG_CATEGORY_EXTERN (gst_va_display_debug); +#define GST_CAT_DEFAULT gst_va_display_debug + +#define gst_va_display_drm_parent_class parent_class +G_DEFINE_TYPE (GstVaDisplayDrm, gst_va_display_drm, GST_TYPE_VA_DISPLAY); + +enum +{ + PROP_PATH = 1, + N_PROPERTIES +}; + +static GParamSpec *g_properties[N_PROPERTIES]; + +#define MAX_DEVICES 8 + +static void +gst_va_display_drm_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstVaDisplayDrm *self = GST_VA_DISPLAY_DRM (object); + + switch (prop_id) { + case PROP_PATH: + self->path = g_value_dup_string (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_va_display_drm_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstVaDisplayDrm *self = GST_VA_DISPLAY_DRM (object); + + switch (prop_id) { + case PROP_PATH: + g_value_set_string (value, self->path); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gst_va_display_drm_finalize (GObject * object) +{ + GstVaDisplayDrm *self = GST_VA_DISPLAY_DRM (object); + + g_free (self->path); + if (self->fd > -1) + close (self->fd); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static gpointer +gst_va_display_drm_create_va_display (GstVaDisplay * display) +{ + int fd, saved_errno = 0; + GstVaDisplayDrm *self = GST_VA_DISPLAY_DRM (display); + + fd = open (self->path, O_RDWR); + saved_errno = errno; + if (fd < 0) { + GST_WARNING_OBJECT (self, "Failed to open %s: %s", self->path, + g_strerror (saved_errno)); + close (fd); + return 0; + } +#if HAVE_LIBDRM + { + drmVersion *version; + + version = drmGetVersion (fd); + if (!version) { + GST_ERROR_OBJECT (self, "Device %s is not a DRM render node", self->path); + return 0; + } + GST_INFO_OBJECT (self, "DRM render node with kernel driver %s", + version->name); + drmFreeVersion (version); + } +#endif + + self->fd = fd; + return vaGetDisplayDRM (self->fd); +} + +static void +gst_va_display_drm_class_init (GstVaDisplayDrmClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstVaDisplayClass *vadisplay_class = GST_VA_DISPLAY_CLASS (klass); + + gobject_class->set_property = gst_va_display_drm_set_property; + gobject_class->get_property = gst_va_display_drm_get_property; + gobject_class->finalize = gst_va_display_drm_finalize; + + vadisplay_class->create_va_display = gst_va_display_drm_create_va_display; + + g_properties[PROP_PATH] = + g_param_spec_string ("path", "render-path", "The path of DRM device", + "/dev/dri/renderD128", + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY); + + g_object_class_install_properties (gobject_class, N_PROPERTIES, g_properties); +} + +static void +gst_va_display_drm_init (GstVaDisplayDrm * self) +{ + self->fd = -1; +} + +/** + * gst_va_display_drm_new_from_path: + * @path: the path to the DRM device + * + * Creates a new #GstVaDisplay from a DRM device . It will try to open + * and operate the device in @path. + * + * Returns: a newly allocated #GstVaDisplay if the specified DRM + * render device could be opened and initialized; otherwise %NULL + * is returned. + **/ +GstVaDisplay * +gst_va_display_drm_new_from_path (const gchar * path) +{ + GstVaDisplay *dpy; + + g_return_val_if_fail (path, NULL); + + dpy = g_object_new (GST_TYPE_VA_DISPLAY_DRM, "path", path, NULL); + if (!gst_va_display_initialize (dpy)) { + gst_object_unref (dpy); + return NULL; + } + + return gst_object_ref_sink (dpy); +} diff --git a/sys/va/gstvadisplay_drm.h b/sys/va/gstvadisplay_drm.h new file mode 100644 index 0000000000..1b3121a258 --- /dev/null +++ b/sys/va/gstvadisplay_drm.h @@ -0,0 +1,33 @@ +/* GStreamer + * Copyright (C) 2020 Igalia, S.L. + * Author: Víctor Jáquez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include "gstvadisplay.h" + +G_BEGIN_DECLS + +#define GST_TYPE_VA_DISPLAY_DRM (gst_va_display_drm_get_type()) +G_DECLARE_FINAL_TYPE (GstVaDisplayDrm, gst_va_display_drm, GST, VA_DISPLAY_DRM, + GstVaDisplay) + +GstVaDisplay * gst_va_display_drm_new_from_path (const gchar * path); + +G_END_DECLS diff --git a/sys/va/gstvadisplay_wrapped.c b/sys/va/gstvadisplay_wrapped.c new file mode 100644 index 0000000000..0bc17dd61d --- /dev/null +++ b/sys/va/gstvadisplay_wrapped.c @@ -0,0 +1,69 @@ +/* GStreamer + * Copyright (C) 2020 Igalia, S.L. + * Author: Víctor Jáquez + * + * 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 "gstvadisplay_wrapped.h" + +struct _GstVaDisplayWrapped +{ + GstVaDisplay parent; +}; + +#define gst_va_display_wrapped_parent_class parent_class +G_DEFINE_TYPE (GstVaDisplayWrapped, gst_va_display_wrapped, + GST_TYPE_VA_DISPLAY); + +static void +gst_va_display_wrapped_class_init (GstVaDisplayWrappedClass * klass) +{ +} + +static void +gst_va_display_wrapped_init (GstVaDisplayWrapped * self) +{ +} + +/** + * gst_va_display_wrapped_new: + * @handle: a #VADisplay to wrap + * + * Creates a #GstVaDisplay wrapping an already created and initialized + * VADisplay. + * + * Returns: a new #GstVaDisplay if @handle is valid, Otherwise %NULL. + **/ +GstVaDisplay * +gst_va_display_wrapped_new (guintptr handle) +{ + GstVaDisplay *dpy; + + g_return_val_if_fail (handle, NULL); + + dpy = g_object_new (GST_TYPE_VA_DISPLAY_WRAPPED, "display", handle, NULL); + if (!gst_va_display_initialize (dpy)) { + gst_object_unref (dpy); + return NULL; + } + + return gst_object_ref_sink (dpy); +} diff --git a/sys/va/gstvadisplay_wrapped.h b/sys/va/gstvadisplay_wrapped.h new file mode 100644 index 0000000000..752ecb5031 --- /dev/null +++ b/sys/va/gstvadisplay_wrapped.h @@ -0,0 +1,33 @@ +/* GStreamer + * Copyright (C) 2020 Igalia, S.L. + * Author: Víctor Jáquez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include "gstvadisplay.h" + +G_BEGIN_DECLS + +#define GST_TYPE_VA_DISPLAY_WRAPPED gst_va_display_wrapped_get_type() +G_DECLARE_FINAL_TYPE (GstVaDisplayWrapped, gst_va_display_wrapped, GST, + VA_DISPLAY_WRAPPED, GstVaDisplay) + +GstVaDisplay * gst_va_display_wrapped_new (guintptr handle); + +G_END_DECLS diff --git a/sys/va/gstvah264dec.c b/sys/va/gstvah264dec.c new file mode 100644 index 0000000000..0e9dd13de3 --- /dev/null +++ b/sys/va/gstvah264dec.c @@ -0,0 +1,1416 @@ +/* GStreamer + * Copyright (C) 2020 Igalia, S.L. + * Author: Víctor Jáquez + * + * 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 the0 + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "gstvah264dec.h" + +#include + +#include "gstvaallocator.h" +#include "gstvacaps.h" +#include "gstvadecoder.h" +#include "gstvadevice.h" +#include "gstvadisplay_drm.h" +#include "gstvapool.h" +#include "gstvaprofile.h" +#include "gstvavideoformat.h" + +GST_DEBUG_CATEGORY_STATIC (gst_va_h264dec_debug); +#define GST_CAT_DEFAULT gst_va_h264dec_debug + +#define GST_VA_H264_DEC(obj) ((GstVaH264Dec *) obj) +#define GST_VA_H264_DEC_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), G_TYPE_FROM_INSTANCE (obj), GstVaH264DecClass)) +#define GST_VA_H264_DEC_CLASS(klass) ((GstVaH264DecClass *) klass) + +typedef struct _GstVaH264Dec GstVaH264Dec; +typedef struct _GstVaH264DecClass GstVaH264DecClass; + +struct _GstVaH264DecClass +{ + GstH264DecoderClass parent_class; + + gchar *render_device_path; +}; + +struct _GstVaH264Dec +{ + GstH264Decoder parent; + + GstVaDecoder *decoder; + + GstBufferPool *other_pool; + + GstFlowReturn last_ret; + GstVideoCodecState *output_state; + + VAProfile profile; + gint display_width; + gint display_height; + gint coded_width; + gint coded_height; + guint rt_format; + gint dpb_size; + + gboolean need_negotiation; + gboolean need_cropping; + gboolean has_videometa; + gboolean copy_frames; +}; + +static GstElementClass *parent_class = NULL; + +struct CData +{ + gchar *render_device_path; + gchar *description; + GstCaps *sink_caps; + GstCaps *src_caps; +}; + +static gboolean +gst_va_h264_dec_end_picture (GstH264Decoder * decoder, GstH264Picture * picture) +{ + GstVaH264Dec *self = GST_VA_H264_DEC (decoder); + GstVaDecodePicture *va_pic; + + GST_LOG_OBJECT (self, "end picture %p, (poc %d)", + picture, picture->pic_order_cnt); + + va_pic = gst_h264_picture_get_user_data (picture); + + return gst_va_decoder_decode (self->decoder, va_pic); +} + +static gboolean +_copy_output_buffer (GstVaH264Dec * self, GstVideoCodecFrame * codec_frame) +{ + GstVideoFrame src_frame; + GstVideoFrame dest_frame; + GstVideoInfo dest_vinfo; + GstBuffer *buffer; + GstFlowReturn ret; + + if (!self->other_pool) + return FALSE; + + if (!gst_buffer_pool_set_active (self->other_pool, TRUE)) + return FALSE; + + gst_video_info_set_format (&dest_vinfo, + GST_VIDEO_INFO_FORMAT (&self->output_state->info), self->display_width, + self->display_height); + + ret = gst_buffer_pool_acquire_buffer (self->other_pool, &buffer, NULL); + if (ret != GST_FLOW_OK) + goto fail; + + if (!gst_video_frame_map (&src_frame, &self->output_state->info, + codec_frame->output_buffer, GST_MAP_READ)) + goto fail; + + if (!gst_video_frame_map (&dest_frame, &dest_vinfo, buffer, GST_MAP_WRITE)) { + gst_video_frame_unmap (&dest_frame); + goto fail; + } + + /* gst_video_frame_copy can crop this, but does not know, so let + * make it think it's all right */ + GST_VIDEO_INFO_WIDTH (&src_frame.info) = self->display_width; + GST_VIDEO_INFO_HEIGHT (&src_frame.info) = self->display_height; + + if (!gst_video_frame_copy (&dest_frame, &src_frame)) { + gst_video_frame_unmap (&src_frame); + gst_video_frame_unmap (&dest_frame); + goto fail; + } + + gst_video_frame_unmap (&src_frame); + gst_video_frame_unmap (&dest_frame); + gst_buffer_replace (&codec_frame->output_buffer, buffer); + gst_buffer_unref (buffer); + + return TRUE; + +fail: + GST_ERROR_OBJECT (self, "Failed copy output buffer."); + return FALSE; +} + + +static GstFlowReturn +gst_va_h264_dec_output_picture (GstH264Decoder * decoder, + GstH264Picture * picture) +{ + GstVaH264Dec *self = GST_VA_H264_DEC (decoder); + GstVideoCodecFrame *frame = NULL; + + GST_LOG_OBJECT (self, + "Outputting picture %p (poc %d)", picture, picture->pic_order_cnt); + + frame = gst_video_decoder_get_frame (GST_VIDEO_DECODER (self), + picture->system_frame_number); + + if (self->last_ret != GST_FLOW_OK) { + gst_video_decoder_drop_frame (GST_VIDEO_DECODER (self), frame); + return self->last_ret; + } + + if (self->copy_frames) + _copy_output_buffer (self, frame); + + GST_BUFFER_PTS (frame->output_buffer) = GST_BUFFER_PTS (frame->input_buffer); + GST_BUFFER_DTS (frame->output_buffer) = GST_CLOCK_TIME_NONE; + GST_BUFFER_DURATION (frame->output_buffer) = + GST_BUFFER_DURATION (frame->input_buffer); + + GST_LOG_OBJECT (self, "Finish frame %" GST_TIME_FORMAT, + GST_TIME_ARGS (GST_BUFFER_PTS (frame->output_buffer))); + + return gst_video_decoder_finish_frame (GST_VIDEO_DECODER (self), frame); +} + +static void +_init_vaapi_pic (VAPictureH264 * va_picture) +{ + va_picture->picture_id = VA_INVALID_ID; + va_picture->frame_idx = 0; + va_picture->flags = VA_PICTURE_H264_INVALID; + va_picture->TopFieldOrderCnt = 0; + va_picture->BottomFieldOrderCnt = 0; +} + +static void +_fill_vaapi_pic (VAPictureH264 * va_picture, GstH264Picture * picture) +{ + GstVaDecodePicture *va_pic; + + va_pic = gst_h264_picture_get_user_data (picture); + + if (!va_pic) { + _init_vaapi_pic (va_picture); + return; + } + + va_picture->picture_id = va_pic->surface; + va_picture->flags = 0; + + if (picture->ref && picture->long_term) { + va_picture->flags |= VA_PICTURE_H264_LONG_TERM_REFERENCE; + va_picture->frame_idx = picture->long_term_frame_idx; + } else { + if (picture->ref) + va_picture->flags |= VA_PICTURE_H264_SHORT_TERM_REFERENCE; + va_picture->frame_idx = picture->frame_num; + } + + switch (picture->field) { + case GST_H264_PICTURE_FIELD_FRAME: + va_picture->TopFieldOrderCnt = picture->top_field_order_cnt; + va_picture->BottomFieldOrderCnt = picture->bottom_field_order_cnt; + break; + case GST_H264_PICTURE_FIELD_TOP_FIELD: + va_picture->flags |= VA_PICTURE_H264_TOP_FIELD; + va_picture->TopFieldOrderCnt = picture->top_field_order_cnt; + va_picture->BottomFieldOrderCnt = 0; + break; + case GST_H264_PICTURE_FIELD_BOTTOM_FIELD: + va_picture->flags |= VA_PICTURE_H264_BOTTOM_FIELD; + va_picture->TopFieldOrderCnt = 0; + va_picture->BottomFieldOrderCnt = picture->bottom_field_order_cnt; + break; + default: + va_picture->TopFieldOrderCnt = 0; + va_picture->BottomFieldOrderCnt = 0; + break; + } +} + +/* fill the VA API reference picture lists from the GstCodec reference + * picture list */ +static void +_fill_ref_pic_list (VAPictureH264 va_reflist[32], GArray * reflist) +{ + guint i; + + for (i = 0; i < reflist->len; i++) { + GstH264Picture *picture = g_array_index (reflist, GstH264Picture *, i); + _fill_vaapi_pic (&va_reflist[i], picture); + } + + for (; i < 32; i++) + _init_vaapi_pic (&va_reflist[i]); +} + +static void +_fill_pred_weight_table (GstH264SliceHdr * header, + VASliceParameterBufferH264 * slice_param) +{ + GstH264PPS *pps; + GstH264SPS *sps; + guint num_weight_tables = 0; + gint i, j; + + pps = header->pps; + sps = pps->sequence; + + if (pps->weighted_pred_flag + && (GST_H264_IS_P_SLICE (header) || GST_H264_IS_SP_SLICE (header))) + num_weight_tables = 1; + else if (pps->weighted_bipred_idc == 1 && GST_H264_IS_B_SLICE (header)) + num_weight_tables = 2; + + if (num_weight_tables == 0) + return; + + slice_param->luma_log2_weight_denom = + header->pred_weight_table.luma_log2_weight_denom; + slice_param->chroma_log2_weight_denom = + header->pred_weight_table.chroma_log2_weight_denom; + + /* VA API also wants the inferred (default) values, not only what is + * available in the bitstream (7.4.3.2). */ + + slice_param->luma_weight_l0_flag = 1; + for (i = 0; i <= slice_param->num_ref_idx_l0_active_minus1; i++) { + slice_param->luma_weight_l0[i] = + header->pred_weight_table.luma_weight_l0[i]; + slice_param->luma_offset_l0[i] = + header->pred_weight_table.luma_offset_l0[i]; + } + + slice_param->chroma_weight_l0_flag = sps->chroma_array_type != 0; + if (slice_param->chroma_weight_l0_flag) { + for (i = 0; i <= slice_param->num_ref_idx_l0_active_minus1; i++) { + for (j = 0; j < 2; j++) { + slice_param->chroma_weight_l0[i][j] = + header->pred_weight_table.chroma_weight_l0[i][j]; + slice_param->chroma_offset_l0[i][j] = + header->pred_weight_table.chroma_offset_l0[i][j]; + } + } + } + + if (num_weight_tables == 1) + return; + + slice_param->luma_weight_l1_flag = 1; + for (i = 0; i <= slice_param->num_ref_idx_l1_active_minus1; i++) { + slice_param->luma_weight_l1[i] = + header->pred_weight_table.luma_weight_l1[i]; + slice_param->luma_offset_l1[i] = + header->pred_weight_table.luma_offset_l1[i]; + } + + slice_param->chroma_weight_l1_flag = sps->chroma_array_type != 0; + if (slice_param->chroma_weight_l1_flag) { + for (i = 0; i <= slice_param->num_ref_idx_l1_active_minus1; i++) { + for (j = 0; j < 2; j++) { + slice_param->chroma_weight_l1[i][j] = + header->pred_weight_table.chroma_weight_l1[i][j]; + slice_param->chroma_offset_l1[i][j] = + header->pred_weight_table.chroma_offset_l1[i][j]; + } + } + } +} + +static inline guint +_get_slice_data_bit_offset (GstH264SliceHdr * header, guint nal_header_bytes) +{ + guint epb_count; + + epb_count = header->n_emulation_prevention_bytes; + return 8 * nal_header_bytes + header->header_size - epb_count * 8; +} + +static gboolean +gst_va_h264_dec_decode_slice (GstH264Decoder * decoder, + GstH264Picture * picture, GstH264Slice * slice, GArray * ref_pic_list0, + GArray * ref_pic_list1) +{ + GstH264SliceHdr *header = &slice->header; + GstH264NalUnit *nalu = &slice->nalu; + GstVaDecodePicture *va_pic; + GstVaH264Dec *self = GST_VA_H264_DEC (decoder); + VASliceParameterBufferH264 slice_param; + gboolean ret; + + GST_TRACE_OBJECT (self, "-"); + + /* *INDENT-OFF* */ + slice_param = (VASliceParameterBufferH264) { + .slice_data_size = nalu->size, + .slice_data_offset = 0, + .slice_data_flag = VA_SLICE_DATA_FLAG_ALL, + .slice_data_bit_offset = + _get_slice_data_bit_offset (header, nalu->header_bytes), + .first_mb_in_slice = header->first_mb_in_slice, + .slice_type = header->type % 5, + .direct_spatial_mv_pred_flag = header->direct_spatial_mv_pred_flag, + .cabac_init_idc = header->cabac_init_idc, + .slice_qp_delta = header->slice_qp_delta, + .disable_deblocking_filter_idc = header->disable_deblocking_filter_idc, + .slice_alpha_c0_offset_div2 = header->slice_alpha_c0_offset_div2, + .slice_beta_offset_div2 = header->slice_beta_offset_div2, + .num_ref_idx_l0_active_minus1 = header->num_ref_idx_l0_active_minus1, + .num_ref_idx_l1_active_minus1 = header->num_ref_idx_l1_active_minus1, + }; + /* *INDENT-ON* */ + + _fill_ref_pic_list (slice_param.RefPicList0, ref_pic_list0); + _fill_ref_pic_list (slice_param.RefPicList1, ref_pic_list1); + + _fill_pred_weight_table (header, &slice_param); + + va_pic = gst_h264_picture_get_user_data (picture); + + ret = gst_va_decoder_add_slice_buffer (self->decoder, va_pic, &slice_param, + sizeof (slice_param), slice->nalu.data + slice->nalu.offset, + slice->nalu.size); + if (!ret) { + gst_va_decoder_destroy_buffers (self->decoder, va_pic); + return FALSE; + } + + return TRUE; +} + +static gboolean +gst_va_h264_dec_start_picture (GstH264Decoder * decoder, + GstH264Picture * picture, GstH264Slice * slice, GstH264Dpb * dpb) +{ + GstH264PPS *pps; + GstH264SPS *sps; + GstVaH264Dec *self = GST_VA_H264_DEC (decoder); + GstVaDecodePicture *va_pic; + VAIQMatrixBufferH264 iq_matrix = { 0, }; + VAPictureParameterBufferH264 pic_param; + guint i, n; + + GST_TRACE_OBJECT (self, "-"); + + va_pic = gst_h264_picture_get_user_data (picture); + + pps = slice->header.pps; + sps = pps->sequence; + + /* *INDENT-OFF* */ + pic_param = (VAPictureParameterBufferH264) { + /* .CurrPic */ + /* .ReferenceFrames */ + .picture_width_in_mbs_minus1 = sps->pic_width_in_mbs_minus1, + .picture_height_in_mbs_minus1 = + sps->pic_height_in_map_units_minus1 << !sps->frame_mbs_only_flag, + .bit_depth_luma_minus8 = sps->bit_depth_luma_minus8, + .bit_depth_chroma_minus8 = sps->bit_depth_chroma_minus8, + .num_ref_frames = sps->num_ref_frames, + .seq_fields.bits = { + .chroma_format_idc = sps->chroma_format_idc, + .residual_colour_transform_flag = sps->separate_colour_plane_flag, + .gaps_in_frame_num_value_allowed_flag = + sps->gaps_in_frame_num_value_allowed_flag, + .frame_mbs_only_flag = sps->frame_mbs_only_flag, + .mb_adaptive_frame_field_flag = sps->mb_adaptive_frame_field_flag, + .direct_8x8_inference_flag = sps->direct_8x8_inference_flag, + .MinLumaBiPredSize8x8 = sps->level_idc >= 31, /* A.3.3.2 */ + .log2_max_frame_num_minus4 = sps->log2_max_frame_num_minus4, + .pic_order_cnt_type = sps->pic_order_cnt_type, + .log2_max_pic_order_cnt_lsb_minus4 = sps->log2_max_pic_order_cnt_lsb_minus4, + .delta_pic_order_always_zero_flag = sps->delta_pic_order_always_zero_flag, + }, + .pic_init_qp_minus26 = pps->pic_init_qp_minus26, + .pic_init_qs_minus26 = pps->pic_init_qs_minus26, + .chroma_qp_index_offset = pps->chroma_qp_index_offset, + .second_chroma_qp_index_offset = pps->second_chroma_qp_index_offset, + .pic_fields.bits = { + .entropy_coding_mode_flag = pps->entropy_coding_mode_flag, + .weighted_pred_flag = pps->weighted_pred_flag, + .weighted_bipred_idc = pps->weighted_bipred_idc, + .transform_8x8_mode_flag = pps->transform_8x8_mode_flag, + .field_pic_flag = slice->header.field_pic_flag, + .constrained_intra_pred_flag = pps->constrained_intra_pred_flag, + .pic_order_present_flag = pps->pic_order_present_flag, + .deblocking_filter_control_present_flag = + pps->deblocking_filter_control_present_flag, + .redundant_pic_cnt_present_flag = pps->redundant_pic_cnt_present_flag, + .reference_pic_flag = picture->nal_ref_idc != 0, + }, + .frame_num = slice->header.frame_num, + }; + /* *INDENT-ON* */ + + _fill_vaapi_pic (&pic_param.CurrPic, picture); + + /* reference frames */ + { + GArray *ref_list = g_array_sized_new (FALSE, FALSE, + sizeof (GstH264Picture *), 16); + g_array_set_clear_func (ref_list, (GDestroyNotify) gst_h264_picture_clear); + + gst_h264_dpb_get_pictures_short_term_ref (dpb, ref_list); + for (i = 0; i < 16 && i < ref_list->len; i++) { + GstH264Picture *pic = g_array_index (ref_list, GstH264Picture *, i); + _fill_vaapi_pic (&pic_param.ReferenceFrames[i], pic); + } + g_array_set_size (ref_list, 0); + + gst_h264_dpb_get_pictures_long_term_ref (dpb, ref_list); + for (; i < 16 && i < ref_list->len; i++) { + GstH264Picture *pic = g_array_index (ref_list, GstH264Picture *, i); + _fill_vaapi_pic (&pic_param.ReferenceFrames[i], pic); + } + g_array_unref (ref_list); + + for (; i < 16; i++) + _init_vaapi_pic (&pic_param.ReferenceFrames[i]); + } + + if (!gst_va_decoder_add_param_buffer (self->decoder, va_pic, + VAPictureParameterBufferType, &pic_param, sizeof (pic_param))) + goto fail; + + /* there are always 6 4x4 scaling lists */ + for (i = 0; i < 6; i++) { + gst_h264_quant_matrix_4x4_get_raster_from_zigzag (iq_matrix.ScalingList4x4 + [i], pps->scaling_lists_4x4[i]); + } + + /* We need the first 2 entries (Y intra and Y inter for YCbCr 4:2:2 and + * less, and the full 6 entries for 4:4:4, see Table 7-2 of the spec for + * more details */ + n = (pps->sequence->chroma_format_idc == 3) ? 6 : 2; + for (i = 0; i < n; i++) { + gst_h264_quant_matrix_8x8_get_raster_from_zigzag (iq_matrix.ScalingList8x8 + [i], pps->scaling_lists_8x8[i]); + } + + if (!gst_va_decoder_add_param_buffer (self->decoder, va_pic, + VAIQMatrixBufferType, &iq_matrix, sizeof (iq_matrix))) + goto fail; + + return TRUE; + +fail: + { + gst_va_decoder_destroy_buffers (self->decoder, va_pic); + return FALSE; + } +} + +static gboolean +gst_va_h264_dec_new_picture (GstH264Decoder * decoder, GstH264Picture * picture) +{ + GstVaH264Dec *self = GST_VA_H264_DEC (decoder); + GstVaDecodePicture *pic; + GstVideoCodecFrame *frame; + GstVideoDecoder *vdec = GST_VIDEO_DECODER (decoder); + VASurfaceID surface; + + frame = gst_video_decoder_get_frame (vdec, picture->system_frame_number); + if (!frame) + return FALSE; /* something failed before */ + self->last_ret = gst_video_decoder_allocate_output_frame (vdec, frame); + if (self->last_ret != GST_FLOW_OK) + goto error; + + surface = gst_va_buffer_get_surface (frame->output_buffer, NULL); + + gst_video_codec_frame_unref (frame); + + pic = gst_va_decoder_new_decode_picture (self->decoder, surface); + gst_h264_picture_set_user_data (picture, pic, + (GDestroyNotify) gst_va_decode_picture_free); + + GST_LOG_OBJECT (self, "New va decode picture %p - %#x", pic, pic->surface); + + return TRUE; + +error: + { + gst_video_codec_frame_unref (frame); + return FALSE; + } +} + +static inline guint +_get_num_views (const GstH264SPS * sps) +{ + return 1 + (sps->extension_type == GST_H264_NAL_EXTENSION_MVC ? + sps->extension.mvc.num_views_minus1 : 0); +} + +static guint +_get_rtformat (GstVaH264Dec * self, guint8 bit_depth_luma, + guint8 chroma_format_idc) +{ + switch (bit_depth_luma) { + case 10: + if (chroma_format_idc == 3) + return VA_RT_FORMAT_YUV444_10; + if (chroma_format_idc == 2) + return VA_RT_FORMAT_YUV422_10; + else + return VA_RT_FORMAT_YUV420_10; + break; + case 8: + if (chroma_format_idc == 3) + return VA_RT_FORMAT_YUV444; + if (chroma_format_idc == 2) + return VA_RT_FORMAT_YUV422; + else + return VA_RT_FORMAT_YUV420; + break; + default: + GST_ERROR_OBJECT (self, "Unsupported chroma format: %d " + "(with depth luma: %d)", chroma_format_idc, bit_depth_luma); + return 0; + } +} + +/* *INDENT-OFF* */ +static const struct +{ + GstH264Profile profile_idc; + VAProfile va_profile; +} profile_map[] = { +#define P(idc, va) { G_PASTE (GST_H264_PROFILE_, idc), G_PASTE (VAProfileH264, va) } + /* P (BASELINE, ), */ + P (MAIN, Main), + /* P (EXTENDED, ), */ + P (HIGH, High), + /* P (HIGH10, ), */ + /* P (HIGH_422, ), */ + /* P (HIGH_444, ), */ + P (MULTIVIEW_HIGH, MultiviewHigh), + P (STEREO_HIGH, StereoHigh), + /* P (SCALABLE_BASELINE, ), */ + /* P (SCALABLE_HIGH, ), */ +#undef P +}; +/* *INDENT-ON* */ + +static VAProfile +_get_profile (GstVaH264Dec * self, const GstH264SPS * sps, gint max_dpb_size) +{ + VAProfile profiles[4]; + gint i = 0, j; + + for (j = 0; j < G_N_ELEMENTS (profile_map); j++) { + if (profile_map[j].profile_idc == sps->profile_idc) { + profiles[i++] = profile_map[j].va_profile; + break; + } + } + + switch (sps->profile_idc) { + case GST_H264_PROFILE_BASELINE: + if (sps->constraint_set1_flag) { /* A.2.2 (main profile) */ + profiles[i++] = VAProfileH264ConstrainedBaseline; + profiles[i++] = VAProfileH264Main; + } + break; + case GST_H264_PROFILE_EXTENDED: + if (sps->constraint_set1_flag) { /* A.2.2 (main profile) */ + profiles[i++] = VAProfileH264Main; + } + break; + case GST_H264_PROFILE_MULTIVIEW_HIGH: + if (_get_num_views (sps) == 2) { + profiles[i++] = VAProfileH264StereoHigh; + } + if (max_dpb_size <= 16 /* && i965 driver */ ) { + profiles[i++] = VAProfileH264MultiviewHigh; + } + default: + break; + } + + for (j = 0; j < i && j < G_N_ELEMENTS (profiles); j++) { + if (gst_va_decoder_has_profile (self->decoder, profiles[j])) + return profiles[j]; + } + + GST_ERROR_OBJECT (self, "Unsupported profile: %d", sps->profile_idc); + + return VAProfileNone; +} + +static gboolean +_format_changed (GstVaH264Dec * self, VAProfile new_profile, guint new_rtformat, + gint new_width, gint new_height) +{ + VAProfile profile = VAProfileNone; + guint rt_format = VA_RT_FORMAT_YUV420; + gint width = 0, height = 0; + + g_object_get (self->decoder, "va-profile", &profile, "va-rt-format", + &rt_format, "coded-width", &width, "coded-height", &height, NULL); + + /* @TODO: Check if current buffers are large enough, and reuse + * them */ + return !(profile == new_profile && rt_format == new_rtformat + && width == new_width && height == new_height); +} + +static gboolean +gst_va_h264_dec_new_sequence (GstH264Decoder * decoder, const GstH264SPS * sps, + gint max_dpb_size) +{ + GstVaH264Dec *self = GST_VA_H264_DEC (decoder); + VAProfile profile; + gint display_width; + gint display_height; + guint rt_format; + gboolean negotiation_needed = FALSE; + + if (self->dpb_size < max_dpb_size) + self->dpb_size = max_dpb_size; + + if (sps->frame_cropping_flag) { + display_width = sps->crop_rect_width; + display_height = sps->crop_rect_height; + } else { + display_width = sps->width; + display_height = sps->height; + } + + profile = _get_profile (self, sps, max_dpb_size); + if (profile == VAProfileNone) + return FALSE; + + rt_format = _get_rtformat (self, sps->bit_depth_luma_minus8 + 8, + sps->chroma_format_idc); + if (rt_format == 0) + return FALSE; + + if (_format_changed (self, profile, rt_format, sps->width, sps->height)) { + self->profile = profile; + self->rt_format = rt_format; + self->coded_width = sps->width; + self->coded_height = sps->height; + + negotiation_needed = TRUE; + GST_INFO_OBJECT (self, "Format changed to %s [%x] (%dx%d)", + gst_va_profile_name (profile), rt_format, self->coded_width, + self->coded_height); + } + + if (self->display_width != display_width + || self->display_height != display_height) { + self->display_width = display_width; + self->display_height = display_height; + + negotiation_needed = TRUE; + GST_INFO_OBJECT (self, "Resolution changed to %dx%d", self->display_width, + self->display_height); + } + + self->need_cropping = self->display_width < self->coded_width + || self->display_height < self->coded_height; + + if (negotiation_needed) { + self->need_negotiation = TRUE; + if (!gst_video_decoder_negotiate (GST_VIDEO_DECODER (self))) { + GST_ERROR_OBJECT (self, "Failed to negotiate with downstream"); + return FALSE; + } + } + + /* @TODO: check if it is required to copy surfaces to downstream */ + + return TRUE; +} + +static gboolean +gst_va_h264_dec_open (GstVideoDecoder * decoder) +{ + GstVaDisplay *display; + GstVaH264Dec *self = GST_VA_H264_DEC (decoder); + GstVaH264DecClass *klass = GST_VA_H264_DEC_GET_CLASS (decoder); + + /* @XXX(victor): query pipeline's context */ + display = gst_va_display_drm_new_from_path (klass->render_device_path); + if (!display) + return FALSE; + + if (!self->decoder) + self->decoder = gst_va_decoder_new (display, H264); + + gst_object_unref (display); + + return (self->decoder != NULL); +} + +static gboolean +gst_va_h264_dec_close (GstVideoDecoder * decoder) +{ + GstVaH264Dec *self = GST_VA_H264_DEC (decoder); + + gst_clear_object (&self->decoder); + + return TRUE; +} + +static GstCaps * +_complete_sink_caps (GstCaps * sinkcaps) +{ + GstCaps *caps = gst_caps_copy (sinkcaps); + GValue val = G_VALUE_INIT; + const gchar *streamformat[] = { "avc", "avc3", "byte-stream" }; + gint i; + + g_value_init (&val, G_TYPE_STRING); + g_value_set_string (&val, "au"); + gst_caps_set_value (caps, "alignment", &val); + g_value_unset (&val); + + gst_value_list_init (&val, G_N_ELEMENTS (streamformat)); + for (i = 0; i < G_N_ELEMENTS (streamformat); i++) { + GValue v = G_VALUE_INIT; + + g_value_init (&v, G_TYPE_STRING); + g_value_set_string (&v, streamformat[i]); + gst_value_list_append_value (&val, &v); + g_value_unset (&v); + } + gst_caps_set_value (caps, "stream-format", &val); + g_value_unset (&val); + + return caps; +} + +static GstCaps * +gst_va_h264_dec_sink_getcaps (GstVideoDecoder * decoder, GstCaps * filter) +{ + GstCaps *sinkcaps, *caps = NULL, *tmp; + GstVaH264Dec *self = GST_VA_H264_DEC (decoder); + + if (self->decoder) + caps = gst_va_decoder_get_sinkpad_caps (self->decoder); + + if (caps) { + sinkcaps = _complete_sink_caps (caps); + gst_caps_unref (caps); + if (filter) { + tmp = gst_caps_intersect_full (filter, sinkcaps, + GST_CAPS_INTERSECT_FIRST); + gst_caps_unref (sinkcaps); + caps = tmp; + } else { + caps = sinkcaps; + } + GST_LOG_OBJECT (self, "Returning caps %" GST_PTR_FORMAT, caps); + } else if (!caps) { + caps = gst_video_decoder_proxy_getcaps (decoder, NULL, filter); + } + + return caps; +} + +static gboolean +gst_va_h264_dec_src_query (GstVideoDecoder * decoder, GstQuery * query) +{ + GstVaH264Dec *self = GST_VA_H264_DEC (decoder); + gboolean ret = FALSE; + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_CAPS:{ + GstCaps *caps = NULL, *tmp, *filter = NULL; + + gst_query_parse_caps (query, &filter); + if (self->decoder) + caps = gst_va_decoder_get_srcpad_caps (self->decoder); + if (caps) { + if (filter) { + tmp = + gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST); + gst_caps_unref (caps); + caps = tmp; + } + + GST_LOG_OBJECT (self, "Returning caps %" GST_PTR_FORMAT, caps); + gst_query_set_caps_result (query, caps); + gst_caps_unref (caps); + ret = TRUE; + break; + } + /* else jump to default */ + } + default: + ret = GST_VIDEO_DECODER_CLASS (parent_class)->src_query (decoder, query); + break; + } + + return ret; +} + +static gboolean +gst_va_h264_dec_stop (GstVideoDecoder * decoder) +{ + GstVaH264Dec *self = GST_VA_H264_DEC (decoder); + + if (!gst_va_decoder_close (self->decoder)) + return FALSE; + + if (self->output_state) + gst_video_codec_state_unref (self->output_state); + self->output_state = NULL; + + if (self->other_pool) + gst_buffer_pool_set_active (self->other_pool, FALSE); + gst_clear_object (&self->other_pool); + + return GST_VIDEO_DECODER_CLASS (parent_class)->stop (decoder); +} + +static GstVideoFormat +_default_video_format_from_chroma (guint chroma_type) +{ + switch (chroma_type) { + case VA_RT_FORMAT_YUV420: + case VA_RT_FORMAT_YUV422: + case VA_RT_FORMAT_YUV444: + return GST_VIDEO_FORMAT_NV12; + case VA_RT_FORMAT_YUV420_10: + case VA_RT_FORMAT_YUV422_10: + case VA_RT_FORMAT_YUV444_10: + return GST_VIDEO_FORMAT_P010_10LE; + default: + return GST_VIDEO_FORMAT_UNKNOWN; + } +} + +static void +_get_preferred_format_and_caps_features (GstVaH264Dec * self, + GstVideoFormat * format, GstCapsFeatures ** capsfeatures) +{ + GstCaps *peer_caps, *preferred_caps = NULL; + GstCapsFeatures *features; + GstStructure *structure; + const GValue *v_format; + guint num_structures, i; + + peer_caps = gst_pad_get_allowed_caps (GST_VIDEO_DECODER_SRC_PAD (self)); + GST_DEBUG_OBJECT (self, "Allowed caps %" GST_PTR_FORMAT, peer_caps); + + /* prefer memory:VASurface over other caps features */ + num_structures = gst_caps_get_size (peer_caps); + for (i = 0; i < num_structures; i++) { + features = gst_caps_get_features (peer_caps, i); + structure = gst_caps_get_structure (peer_caps, i); + + if (gst_caps_features_is_any (features)) + continue; + + if (gst_caps_features_contains (features, "memory:VAMemory")) { + preferred_caps = gst_caps_new_full (gst_structure_copy (structure), NULL); + gst_caps_set_features_simple (preferred_caps, + gst_caps_features_copy (features)); + break; + } + } + + if (!preferred_caps) + preferred_caps = peer_caps; + else + gst_clear_caps (&peer_caps); + + if (gst_caps_is_empty (preferred_caps) + || gst_caps_is_any (preferred_caps)) { + /* if any or not linked yet then system memory and nv12 */ + if (capsfeatures) + *capsfeatures = NULL; + if (format) + *format = _default_video_format_from_chroma (self->rt_format); + goto bail; + } + + features = gst_caps_get_features (preferred_caps, 0); + if (features && capsfeatures) + *capsfeatures = gst_caps_features_copy (features); + + if (!format) + goto bail; + + structure = gst_caps_get_structure (preferred_caps, 0); + v_format = gst_structure_get_value (structure, "format"); + if (!v_format) + *format = _default_video_format_from_chroma (self->rt_format); + else if (G_VALUE_HOLDS_STRING (v_format)) + *format = gst_video_format_from_string (g_value_get_string (v_format)); + else if (GST_VALUE_HOLDS_LIST (v_format)) { + guint num_values = gst_value_list_get_size (v_format); + for (i = 0; i < num_values; i++) { + GstVideoFormat fmt; + const GValue *v_fmt = gst_value_list_get_value (v_format, i); + if (!v_fmt) + continue; + fmt = gst_video_format_from_string (g_value_get_string (v_fmt)); + if (gst_va_chroma_from_video_format (fmt) == self->rt_format) { + *format = fmt; + break; + } + } + if (i == num_values) + *format = _default_video_format_from_chroma (self->rt_format); + } + +bail: + gst_clear_caps (&preferred_caps); +} + +static gboolean +gst_va_h264_dec_negotiate (GstVideoDecoder * decoder) +{ + GstVaH264Dec *self = GST_VA_H264_DEC (decoder); + GstVideoFormat format = GST_VIDEO_FORMAT_UNKNOWN; + GstCapsFeatures *capsfeatures = NULL; + GstH264Decoder *h264dec = GST_H264_DECODER (decoder); + + /* Ignore downstream renegotiation request. */ + if (!self->need_negotiation) + return TRUE; + + self->need_negotiation = FALSE; + + if (gst_va_decoder_is_open (self->decoder) + && !gst_va_decoder_close (self->decoder)) + return FALSE; + + if (!gst_va_decoder_open (self->decoder, self->profile, self->rt_format)) + return FALSE; + + if (!gst_va_decoder_set_format (self->decoder, self->coded_width, + self->coded_height, NULL)) + return FALSE; + + if (self->output_state) + gst_video_codec_state_unref (self->output_state); + + _get_preferred_format_and_caps_features (self, &format, &capsfeatures); + + self->output_state = + gst_video_decoder_set_output_state (decoder, format, + self->display_width, self->display_height, h264dec->input_state); + + self->output_state->caps = gst_video_info_to_caps (&self->output_state->info); + if (capsfeatures) + gst_caps_set_features_simple (self->output_state->caps, capsfeatures); + + GST_INFO_OBJECT (self, "Negotiated caps %" GST_PTR_FORMAT, + self->output_state->caps); + + return GST_VIDEO_DECODER_CLASS (parent_class)->negotiate (decoder); +} + +static inline gboolean +_caps_is_dmabuf (GstVaH264Dec * self, GstCaps * caps) +{ + GstCapsFeatures *features; + + features = gst_caps_get_features (caps, 0); + return gst_caps_features_contains (features, GST_CAPS_FEATURE_MEMORY_DMABUF) + && (gst_va_decoder_get_mem_types (self->decoder) + & VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME); +} + +static inline void +_shall_copy_frames (GstVaH264Dec * self, GstVideoInfo * info) +{ + GstVideoInfo ref_info; + guint i; + + self->copy_frames = FALSE; + + if (self->has_videometa) + return; + + gst_video_info_set_format (&ref_info, GST_VIDEO_INFO_FORMAT (info), + self->display_width, self->display_height); + + for (i = 0; i < GST_VIDEO_INFO_N_PLANES (info); i++) { + if (info->stride[i] != ref_info.stride[i] || + info->offset[i] != ref_info.offset[i]) { + GST_WARNING_OBJECT (self, + "GstVideoMeta support required, copying frames."); + self->copy_frames = TRUE; + break; + } + } +} + +static gboolean +_try_allocator (GstVaH264Dec * self, GstAllocator * allocator, GstCaps * caps, + guint * size) +{ + GstVaAllocationParams params = { + .usage_hint = VA_SURFACE_ATTRIB_USAGE_HINT_DECODER, + }; + + if (!gst_video_info_from_caps (¶ms.info, caps)) + return FALSE; + if (self->need_cropping) { + GST_VIDEO_INFO_WIDTH (¶ms.info) = self->coded_width; + GST_VIDEO_INFO_HEIGHT (¶ms.info) = self->coded_height; + } + + if (GST_IS_VA_DMABUF_ALLOCATOR (allocator)) { + if (!gst_va_dmabuf_try (allocator, ¶ms)) + return FALSE; + } else if (GST_IS_VA_ALLOCATOR (allocator)) { + if (!gst_va_allocator_try (allocator, ¶ms)) + return FALSE; + _shall_copy_frames (self, ¶ms.info); + } else { + return FALSE; + } + + if (size) + *size = GST_VIDEO_INFO_SIZE (¶ms.info); + + return TRUE; +} + +static GstAllocator * +_create_allocator (GstVaH264Dec * self, GstCaps * caps, guint * size) +{ + GstAllocator *allocator = NULL; + GstVaDisplay *display = NULL; + + g_object_get (self->decoder, "display", &display, NULL); + + if (_caps_is_dmabuf (self, caps)) + allocator = gst_va_dmabuf_allocator_new (display); + else + allocator = gst_va_allocator_new (display); + + gst_object_unref (display); + + if (!_try_allocator (self, allocator, caps, size)) + gst_clear_object (&allocator); + + return allocator; +} + +/* 1. get allocator in query + * 1.1 if allocator is not ours and downstream doesn't handle + * videometa, keep it for other_pool + * 2. get pool in query + * 2.1 if pool is not va, keep it as other_pool if downstream + * doesn't handle videometa or (it doesn't handle alignment and + * the stream needs cropping) + * 2.2 if there's no pool in query and downstream doesn't handle + * videometa, create other_pool as GstVideoPool with the non-va + * from query and query's params + * 3. create our allocator and pool if they aren't in query + * 4. add or update pool and allocator in query + * 5. set our custom pool configuration + */ +static gboolean +gst_va_h264_dec_decide_allocation (GstVideoDecoder * decoder, GstQuery * query) +{ + GstAllocator *allocator = NULL, *other_allocator = NULL; + GstAllocationParams other_params, params; + GstBufferPool *pool = NULL; + GstCaps *caps = NULL; + GstStructure *config; + GstVideoInfo info; + GstVaH264Dec *self = GST_VA_H264_DEC (decoder); + guint size, min, max; + gboolean update_pool = FALSE, update_allocator = FALSE, has_videoalignment; + + gst_query_parse_allocation (query, &caps, NULL); + + if (!(caps && gst_video_info_from_caps (&info, caps))) + goto wrong_caps; + + self->has_videometa = gst_query_find_allocation_meta (query, + GST_VIDEO_META_API_TYPE, NULL); + + if (gst_query_get_n_allocation_params (query) > 0) { + gst_query_parse_nth_allocation_param (query, 0, &allocator, &other_params); + if (allocator && !self->has_videometa + && !GST_IS_VA_DMABUF_ALLOCATOR (allocator)) { + /* save the allocator for the other pool */ + if ((other_allocator = allocator)) + allocator = NULL; + } + update_allocator = TRUE; + } else { + gst_allocation_params_init (&other_params); + } + + gst_allocation_params_init (¶ms); + + if (gst_query_get_n_allocation_pools (query) > 0) { + gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max); + if (pool) { + if (!GST_IS_VA_POOL (pool)) { + has_videoalignment = gst_buffer_pool_has_option (pool, + GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT); + if (!self->has_videometa || (!has_videoalignment + && self->need_cropping)) { + gst_object_replace ((GstObject **) & self->other_pool, + (GstObject *) pool); + gst_object_unref (pool); /* decrease previous increase */ + } + gst_clear_object (&pool); + } + } + + min = MAX (16 + 4, min); /* max num pic references + scratch surfaces */ + size = MAX (size, GST_VIDEO_INFO_SIZE (&info)); + + update_pool = TRUE; + } else { + size = GST_VIDEO_INFO_SIZE (&info); + + if (!self->has_videometa) { + GST_DEBUG_OBJECT (self, "making new other pool for copy"); + self->other_pool = gst_video_buffer_pool_new (); + config = gst_buffer_pool_get_config (self->other_pool); + gst_buffer_pool_config_set_params (config, caps, size, 0, 0); + gst_buffer_pool_config_set_allocator (config, other_allocator, + &other_params); + if (!gst_buffer_pool_set_config (self->other_pool, config)) { + GST_ERROR_OBJECT (self, "couldn't configure other pool for copy"); + gst_clear_object (&self->other_pool); + } + } else { + gst_clear_object (&other_allocator); + } + + min = 16 + 4; /* max num pic references + scratch surfaces */ + max = 0; + } + + if (!allocator) { + if (!(allocator = _create_allocator (self, caps, &size))) + return FALSE; + } + + if (!pool) + pool = gst_va_pool_new (); + + { + GstStructure *config = gst_buffer_pool_get_config (pool); + + gst_buffer_pool_config_set_params (config, caps, size, min, max); + gst_buffer_pool_config_set_allocator (config, allocator, ¶ms); + gst_buffer_pool_config_add_option (config, + GST_BUFFER_POOL_OPTION_VIDEO_META); + + if (self->need_cropping) { + GstVideoAlignment video_align = { + .padding_bottom = self->coded_height - self->display_height, + .padding_left = self->coded_width - self->display_width, + }; + gst_buffer_pool_config_add_option (config, + GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT); + gst_buffer_pool_config_set_video_alignment (config, &video_align); + } + + gst_buffer_pool_config_set_va_allocation_params (config, + VA_SURFACE_ATTRIB_USAGE_HINT_DECODER); + + if (!gst_buffer_pool_set_config (pool, config)) + return FALSE; + } + + if (update_allocator) + gst_query_set_nth_allocation_param (query, 0, allocator, ¶ms); + else + gst_query_add_allocation_param (query, allocator, ¶ms); + + if (update_pool) + gst_query_set_nth_allocation_pool (query, 0, pool, size, min, max); + else + gst_query_add_allocation_pool (query, pool, size, min, max); + + gst_object_unref (allocator); + gst_object_unref (pool); + + return GST_VIDEO_DECODER_CLASS (parent_class)->decide_allocation (decoder, + query); + +wrong_caps: + { + GST_WARNING_OBJECT (self, "No valid caps"); + return FALSE; + } +} + +static void +gst_va_h264_dec_dispose (GObject * object) +{ + gst_va_h264_dec_close (GST_VIDEO_DECODER (object)); + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_va_h264_dec_class_init (gpointer g_class, gpointer class_data) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (g_class); + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + GstH264DecoderClass *h264decoder_class = GST_H264_DECODER_CLASS (g_class); + GstVaH264DecClass *klass = GST_VA_H264_DEC_CLASS (g_class); + GstVideoDecoderClass *decoder_class = GST_VIDEO_DECODER_CLASS (g_class); + struct CData *cdata = class_data; + gchar *long_name; + + parent_class = g_type_class_peek_parent (g_class); + + klass->render_device_path = cdata->render_device_path; + + if (cdata->description) { + long_name = g_strdup_printf ("VA-API H.264 Decoder in %s", + cdata->description); + } else { + long_name = g_strdup ("VA-API H.264 Decoder"); + } + + gst_element_class_set_metadata (element_class, long_name, + "Codec/Decoder/Video/Hardware", + "VA-API based H.264 video decoder", + "Víctor Jáquez "); + + gst_element_class_add_pad_template (element_class, + gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, + cdata->sink_caps)); + gst_element_class_add_pad_template (element_class, + gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS, + cdata->src_caps)); + + gobject_class->dispose = gst_va_h264_dec_dispose; + + decoder_class->open = GST_DEBUG_FUNCPTR (gst_va_h264_dec_open); + decoder_class->close = GST_DEBUG_FUNCPTR (gst_va_h264_dec_close); + decoder_class->stop = GST_DEBUG_FUNCPTR (gst_va_h264_dec_stop); + decoder_class->getcaps = GST_DEBUG_FUNCPTR (gst_va_h264_dec_sink_getcaps); + decoder_class->src_query = GST_DEBUG_FUNCPTR (gst_va_h264_dec_src_query); + decoder_class->negotiate = GST_DEBUG_FUNCPTR (gst_va_h264_dec_negotiate); + decoder_class->decide_allocation = + GST_DEBUG_FUNCPTR (gst_va_h264_dec_decide_allocation); + + h264decoder_class->new_sequence = + GST_DEBUG_FUNCPTR (gst_va_h264_dec_new_sequence); + h264decoder_class->decode_slice = + GST_DEBUG_FUNCPTR (gst_va_h264_dec_decode_slice); + + h264decoder_class->new_picture = + GST_DEBUG_FUNCPTR (gst_va_h264_dec_new_picture); + h264decoder_class->output_picture = + GST_DEBUG_FUNCPTR (gst_va_h264_dec_output_picture); + h264decoder_class->start_picture = + GST_DEBUG_FUNCPTR (gst_va_h264_dec_start_picture); + h264decoder_class->end_picture = + GST_DEBUG_FUNCPTR (gst_va_h264_dec_end_picture); + + g_free (long_name); + g_free (cdata->description); + gst_caps_unref (cdata->src_caps); + gst_caps_unref (cdata->sink_caps); + g_free (cdata); +} + +static void +gst_va_h264_dec_init (GTypeInstance * instance, gpointer g_class) +{ + gst_h264_decoder_set_process_ref_pic_lists (GST_H264_DECODER (instance), + TRUE); +} + +static gpointer +_register_debug_category (gpointer data) +{ + GST_DEBUG_CATEGORY_INIT (gst_va_h264dec_debug, "vah264dec", 0, + "VA h264 decoder"); + + return NULL; +} + +gboolean +gst_va_h264_dec_register (GstPlugin * plugin, GstVaDevice * device, + GstCaps * sink_caps, GstCaps * src_caps, guint rank) +{ + static GOnce debug_once = G_ONCE_INIT; + GType type; + GTypeInfo type_info = { + .class_size = sizeof (GstVaH264DecClass), + .class_init = gst_va_h264_dec_class_init, + .instance_size = sizeof (GstVaH264Dec), + .instance_init = gst_va_h264_dec_init, + }; + struct CData *cdata; + gboolean ret; + gchar *type_name, *feature_name; + + g_return_val_if_fail (GST_IS_PLUGIN (plugin), FALSE); + g_return_val_if_fail (GST_IS_VA_DEVICE (device), FALSE); + g_return_val_if_fail (GST_IS_CAPS (sink_caps), FALSE); + g_return_val_if_fail (GST_IS_CAPS (src_caps), FALSE); + + cdata = g_new (struct CData, 1); + cdata->description = NULL; + cdata->render_device_path = g_strdup (device->render_device_path); + cdata->sink_caps = _complete_sink_caps (sink_caps); + cdata->src_caps = gst_caps_ref (src_caps); + + /* class data will be leaked if the element never gets instantiated */ + GST_MINI_OBJECT_FLAG_SET (cdata->sink_caps, + GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED); + GST_MINI_OBJECT_FLAG_SET (src_caps, GST_MINI_OBJECT_FLAG_MAY_BE_LEAKED); + + type_info.class_data = cdata; + + type_name = g_strdup ("GstVaH264Dec"); + feature_name = g_strdup ("vah264dec"); + + /* The first decoder to be registered should use a constant name, + * like vah264dec, for any additional decoders, we create unique + * names, using inserting the render device name. */ + if (g_type_from_name (type_name)) { + gchar *basename = g_path_get_basename (device->render_device_path); + g_free (type_name); + g_free (feature_name); + type_name = g_strdup_printf ("GstVa%sH264Dec", basename); + feature_name = g_strdup_printf ("va%sh264dec", basename); + cdata->description = basename; + + /* lower rank for non-first device */ + if (rank > 0) + rank--; + } + + g_once (&debug_once, _register_debug_category, NULL); + + type = g_type_register_static (GST_TYPE_H264_DECODER, + type_name, &type_info, 0); + + ret = gst_element_register (plugin, feature_name, rank, type); + + g_free (type_name); + g_free (feature_name); + + return ret; +} diff --git a/sys/va/gstvah264dec.h b/sys/va/gstvah264dec.h new file mode 100644 index 0000000000..78f5ca7773 --- /dev/null +++ b/sys/va/gstvah264dec.h @@ -0,0 +1,35 @@ +/* GStreamer + * Copyright (C) 2020 Igalia, S.L. + * Author: Víctor Jáquez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include + +#include "gstvadevice.h" + +G_BEGIN_DECLS + +gboolean gst_va_h264_dec_register (GstPlugin * plugin, + GstVaDevice * device, + GstCaps * sink_caps, + GstCaps * src_caps, + guint rank); + +G_END_DECLS diff --git a/sys/va/gstvapool.c b/sys/va/gstvapool.c new file mode 100644 index 0000000000..1b0bae82ad --- /dev/null +++ b/sys/va/gstvapool.c @@ -0,0 +1,283 @@ +/* GStreamer + * Copyright (C) 2020 Igalia, S.L. + * Author: Víctor Jáquez + * + * 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 "gstvapool.h" + +#include "gstvaallocator.h" + +GST_DEBUG_CATEGORY_STATIC (gst_va_pool_debug); +#define GST_CAT_DEFAULT gst_va_pool_debug + +struct _GstVaPool +{ + GstBufferPool parent; + + GstVideoInfo alloc_info; + GstVideoInfo caps_info; + GstAllocator *allocator; + guint32 usage_hint; + gboolean add_videometa; + gboolean need_alignment; + GstVideoAlignment video_align; +}; + +#define gst_va_pool_parent_class parent_class +G_DEFINE_TYPE_WITH_CODE (GstVaPool, gst_va_pool, GST_TYPE_BUFFER_POOL, + GST_DEBUG_CATEGORY_INIT (gst_va_pool_debug, "vapool", 0, "VA Pool")); + +static const gchar ** +gst_va_pool_get_options (GstBufferPool * pool) +{ + static const gchar *options[] = { GST_BUFFER_POOL_OPTION_VIDEO_META, + GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT, NULL + }; + return options; +} + +static inline gboolean +gst_buffer_pool_config_get_va_allocation_params (GstStructure * config, + guint32 * usage_hint) +{ + if (!gst_structure_get (config, "usage-hint", G_TYPE_UINT, usage_hint, NULL)) + *usage_hint = VA_SURFACE_ATTRIB_USAGE_HINT_GENERIC; + + return TRUE; +} + +static gboolean +gst_va_pool_set_config (GstBufferPool * pool, GstStructure * config) +{ + GstAllocator *allocator; + GstCaps *caps; + GstVaPool *vpool = GST_VA_POOL (pool); + GstVideoAlignment video_align = { 0, }; + GstVideoInfo caps_info, alloc_info; + gint width, height; + guint size, min_buffers, max_buffers; + guint32 usage_hint; + + if (!gst_buffer_pool_config_get_params (config, &caps, &size, &min_buffers, + &max_buffers)) + goto wrong_config; + + if (!caps) + goto no_caps; + + if (!gst_video_info_from_caps (&caps_info, caps)) + goto wrong_caps; + + if (size < GST_VIDEO_INFO_SIZE (&caps_info)) + goto wrong_size; + + if (!gst_buffer_pool_config_get_allocator (config, &allocator, NULL)) + goto wrong_config; + + if (!(allocator && (GST_IS_VA_DMABUF_ALLOCATOR (allocator) + || GST_IS_VA_ALLOCATOR (allocator)))) + goto wrong_config; + + if (!gst_buffer_pool_config_get_va_allocation_params (config, &usage_hint)) + goto wrong_config; + + width = GST_VIDEO_INFO_WIDTH (&caps_info); + height = GST_VIDEO_INFO_HEIGHT (&caps_info); + + GST_LOG_OBJECT (vpool, "%dx%d - %u | caps %" GST_PTR_FORMAT, width, height, + size, caps); + + if (vpool->allocator) + gst_object_unref (vpool->allocator); + if ((vpool->allocator = allocator)) + gst_object_ref (allocator); + + /* enable metadata based on config of the pool */ + vpool->add_videometa = gst_buffer_pool_config_has_option (config, + GST_BUFFER_POOL_OPTION_VIDEO_META); + + /* parse extra alignment info */ + vpool->need_alignment = gst_buffer_pool_config_has_option (config, + GST_BUFFER_POOL_OPTION_VIDEO_ALIGNMENT); + + if (vpool->need_alignment && vpool->add_videometa) { + gst_buffer_pool_config_get_video_alignment (config, &video_align); + + width += video_align.padding_left + video_align.padding_right; + height += video_align.padding_bottom + video_align.padding_top; + + /* apply the alignment to the info */ + if (!gst_video_info_align (&caps_info, &video_align)) + goto failed_to_align; + + gst_buffer_pool_config_set_video_alignment (config, &video_align); + } + + GST_VIDEO_INFO_SIZE (&caps_info) = + MAX (size, GST_VIDEO_INFO_SIZE (&caps_info)); + + alloc_info = caps_info; + GST_VIDEO_INFO_WIDTH (&alloc_info) = width; + GST_VIDEO_INFO_HEIGHT (&alloc_info) = height; + + vpool->caps_info = caps_info; + vpool->alloc_info = alloc_info; + vpool->usage_hint = usage_hint; + vpool->video_align = video_align; + + gst_buffer_pool_config_set_params (config, caps, + GST_VIDEO_INFO_SIZE (&caps_info), min_buffers, max_buffers); + + return GST_BUFFER_POOL_CLASS (parent_class)->set_config (pool, config); + + /* ERRORS */ +wrong_config: + { + GST_WARNING_OBJECT (vpool, "invalid config"); + return FALSE; + } +no_caps: + { + GST_WARNING_OBJECT (vpool, "no caps in config"); + return FALSE; + } +wrong_caps: + { + GST_WARNING_OBJECT (vpool, + "failed getting geometry from caps %" GST_PTR_FORMAT, caps); + return FALSE; + } +wrong_size: + { + GST_WARNING_OBJECT (vpool, + "Provided size is to small for the caps: %u < %" G_GSIZE_FORMAT, size, + GST_VIDEO_INFO_SIZE (&caps_info)); + return FALSE; + } +failed_to_align: + { + GST_WARNING_OBJECT (pool, "Failed to align"); + return FALSE; + } +} + +static GstFlowReturn +gst_va_pool_alloc (GstBufferPool * pool, GstBuffer ** buffer, + GstBufferPoolAcquireParams * params) +{ + GstBuffer *buf; + GstVideoMeta *vmeta; + GstVaPool *vpool = GST_VA_POOL (pool); + GstVaAllocationParams alloc_params = { + .info = vpool->alloc_info, + .usage_hint = vpool->usage_hint, + }; + + buf = gst_buffer_new (); + + if (GST_IS_VA_DMABUF_ALLOCATOR (vpool->allocator)) { + if (!gst_va_dmabuf_setup_buffer (vpool->allocator, buf, &alloc_params)) + goto no_memory; + } else if (GST_IS_VA_ALLOCATOR (vpool->allocator)) { + GstMemory *mem = gst_va_allocator_alloc (vpool->allocator, &alloc_params); + if (!mem) + goto no_memory; + gst_buffer_append_memory (buf, mem); + } else + goto no_memory; + + if (vpool->add_videometa) { + /* GstVaAllocator may update offset/stride given the physical + * memory */ + vmeta = gst_buffer_add_video_meta_full (buf, GST_VIDEO_FRAME_FLAG_NONE, + GST_VIDEO_INFO_FORMAT (&vpool->caps_info), + GST_VIDEO_INFO_WIDTH (&vpool->caps_info), + GST_VIDEO_INFO_HEIGHT (&vpool->caps_info), + GST_VIDEO_INFO_N_PLANES (&vpool->caps_info), + alloc_params.info.offset, alloc_params.info.stride); + + if (vpool->need_alignment) + gst_video_meta_set_alignment (vmeta, vpool->video_align); + } + + *buffer = buf; + + return GST_FLOW_OK; + + /* ERROR */ +no_memory: + { + gst_buffer_unref (buf); + GST_WARNING_OBJECT (vpool, "can't create memory"); + return GST_FLOW_ERROR; + } +} + +static void +gst_va_pool_dispose (GObject * object) +{ + GstVaPool *pool = GST_VA_POOL (object); + + GST_LOG_OBJECT (pool, "finalize video buffer pool %p", pool); + + gst_clear_object (&pool->allocator); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_va_pool_class_init (GstVaPoolClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstBufferPoolClass *gstbufferpool_class = GST_BUFFER_POOL_CLASS (klass); + + gobject_class->dispose = gst_va_pool_dispose; + + gstbufferpool_class->get_options = gst_va_pool_get_options; + gstbufferpool_class->set_config = gst_va_pool_set_config; + gstbufferpool_class->alloc_buffer = gst_va_pool_alloc; +} + +static void +gst_va_pool_init (GstVaPool * self) +{ +} + +GstBufferPool * +gst_va_pool_new (void) +{ + GstVaPool *pool; + + pool = g_object_new (GST_TYPE_VA_POOL, NULL); + gst_object_ref_sink (pool); + + GST_LOG_OBJECT (pool, "new va video buffer pool %p", pool); + + return GST_BUFFER_POOL_CAST (pool); +} + +void +gst_buffer_pool_config_set_va_allocation_params (GstStructure * config, + guint usage_hint) +{ + gst_structure_set (config, "usage-hint", G_TYPE_UINT, usage_hint, NULL); +} diff --git a/sys/va/gstvapool.h b/sys/va/gstvapool.h new file mode 100644 index 0000000000..38e73fe72e --- /dev/null +++ b/sys/va/gstvapool.h @@ -0,0 +1,33 @@ +/* GStreamer + * Copyright (C) 2020 Igalia, S.L. + * Author: Víctor Jáquez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_VA_POOL (gst_va_pool_get_type()) +G_DECLARE_FINAL_TYPE (GstVaPool, gst_va_pool, GST, VA_POOL, GstBufferPool) + +GstBufferPool * gst_va_pool_new (void); +void gst_buffer_pool_config_set_va_allocation_params (GstStructure * config, + guint usage_hint); +G_END_DECLS diff --git a/sys/va/gstvaprofile.c b/sys/va/gstvaprofile.c new file mode 100644 index 0000000000..eed61a4933 --- /dev/null +++ b/sys/va/gstvaprofile.c @@ -0,0 +1,147 @@ +/* GStreamer + * Copyright (C) 2020 Igalia, S.L. + * Author: Víctor Jáquez + * + * 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 "gstvaprofile.h" + +/* *INDENT-OFF* */ +static const struct ProfileMap +{ + VAProfile profile; + GstVaCodecs codec; + const gchar *name; + const gchar *media_type; + const gchar *caps_str; +} profile_map[] = { +#define P(codec, name, media_type, caps_str) { \ + G_PASTE (G_PASTE (VAProfile, codec), name), codec, \ + G_STRINGIFY (G_PASTE (G_PASTE (VAProfile, codec), name)), \ + media_type, caps_str } + P (MPEG2, Simple, "video/mpeg", + "mpegversion = (int) 2, profile = (string) simple"), + P (MPEG2, Main, "video/mpeg", + "mpegversion = (int) 2, profile = (string) main"), + P (MPEG4, Simple, "video/mpeg", + "mpegversion = (int) 4, profile = (string) simple"), + P (MPEG4, AdvancedSimple, "video/mpeg", + "mpegversion = (int) 4, profile = (string) advanced-simple"), + P (MPEG4, Main, "video/mpeg", + "mpegversion = (int) 4, profile = (string) main, "), + P (H264, Main, "video/x-h264", "profile = (string) { main, baseline }"), + P (H264, High, "video/x-h264", + "profile = (string) { progressive-high, constrained-high, high }"), + P (VC1, Simple, "video/x-wmv", + "wmvversion = (int) 3, profile = (string) simple"), + P (VC1, Main, "video/x-wmv", + "wmvversion = (int) 3, profile = (string) main"), + P (VC1, Advanced, "video/x-wmv", + "wmvversion = (int) 3, format = (string) WVC1, " + "profile = (string) advanced"), + P (H263, Baseline, "video/x-h263", + "variant = (string) itu, h263version = (string) h263, " + "profile = (string) baseline"), + P (JPEG, Baseline, "image/jpeg", NULL), + P (H264, ConstrainedBaseline, "video/x-h264", + "profile = (string) { baseline, constrained-baseline }"), + P (VP8, Version0_3, "video/x-vp8", NULL), + /* Unsupported profiles by current GstH264Decoder */ + /* P (H264, MultiviewHigh, "video/x-h264", */ + /* "profile = (string) { multiview-high, stereo-high }"), */ + /* P (H264, StereoHigh, "video/x-h264", */ + /* "profile = (string) { multiview-high, stereo-high }"), */ + P (HEVC, Main, "video/x-h265", "profile = (string) main"), + P (HEVC, Main10, "video/x-h265", "profile = (string) main-10"), + P (VP9, Profile0, "video/x-vp9", "profile = (string) profile0"), + P (VP9, Profile1, "video/x-vp9", "profile = (string) profile1"), + P (VP9, Profile2, "video/x-vp9", "profile = (string) profile2"), + P (VP9, Profile3, "video/x-vp9", "profile = (string) profile3"), + P (HEVC, Main12, "video/x-h265", "profile = (string) main-12"), + P (HEVC, Main422_10, "video/x-h265", "profile = (string) main-422-10"), + P (HEVC, Main422_12, "video/x-h265", "profile = (string) main-422-12"), + P (HEVC, Main444, "video/x-h265", "profile = (string) main-444"), + P (HEVC, Main444_10, "video/x-h265", "profile = (string) main-444-10"), + P (HEVC, Main444_12, "video/x-h265", "profile = (string) main-444-12"), + P (HEVC, SccMain, "video/x-h265", "profile = (string) screen-extended-main"), + P (HEVC, SccMain10, "video/x-h265", + "profile = (string) screen-extended-main-10"), + P (HEVC, SccMain444, "video/x-h265", + "profile = (string) screen-extended-main-444"), +#if VA_CHECK_VERSION(1,7,0) + P (AV1, Profile0, "video/x-av1", NULL), + P (AV1, Profile1, "video/x-av1", NULL), +#endif +#if VA_CHECK_VERSION(1, 8, 0) + P (HEVC, SccMain444_10, "video/x-h265", + "profile = (string) screen-extended-main-444-10"), +#endif +#undef P +}; +/* *INDENT-ON* */ + +static const struct ProfileMap * +get_profile_map (VAProfile profile) +{ + int i; + + for (i = 0; i < G_N_ELEMENTS (profile_map); i++) { + if (profile_map[i].profile == profile) + return &profile_map[i]; + } + + return NULL; +} + +guint32 +gst_va_profile_codec (VAProfile profile) +{ + const struct ProfileMap *map = get_profile_map (profile); + return map ? map->codec : GST_MAKE_FOURCC ('N', 'O', 'N', 'E'); +} + +const gchar * +gst_va_profile_name (VAProfile profile) +{ + const struct ProfileMap *map = get_profile_map (profile); + return map ? map->name : NULL; +} + +GstCaps * +gst_va_profile_caps (VAProfile profile) +{ + const struct ProfileMap *map = get_profile_map (profile); + GstCaps *caps; + gchar *caps_str; + + if (!map) + return NULL; + + if (map->caps_str) + caps_str = g_strdup_printf ("%s, %s", map->media_type, map->caps_str); + else + caps_str = g_strdup (map->media_type); + + caps = gst_caps_from_string (caps_str); + g_free (caps_str); + + return caps; +} diff --git a/sys/va/gstvaprofile.h b/sys/va/gstvaprofile.h new file mode 100644 index 0000000000..3780876393 --- /dev/null +++ b/sys/va/gstvaprofile.h @@ -0,0 +1,46 @@ +/* GStreamer + * Copyright (C) 2020 Igalia, S.L. + * Author: Víctor Jáquez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +typedef enum +{ + AV1 = GST_MAKE_FOURCC ('A', 'V', '0', '1'), + H263 = GST_MAKE_FOURCC ('H', '2', '6', '3'), + H264 = GST_MAKE_FOURCC ('H', '2', '6', '4'), + HEVC = GST_MAKE_FOURCC ('H', '2', '6', '5'), + JPEG = GST_MAKE_FOURCC ('J', 'P', 'E', 'G'), + MPEG2 = GST_MAKE_FOURCC ('M', 'P', 'E', 'G'), + MPEG4 = GST_MAKE_FOURCC ('M', 'P', 'G', '4'), + VC1 = GST_MAKE_FOURCC ('W', 'M', 'V', '3'), + VP8 = GST_MAKE_FOURCC ('V', 'P', '8', '0'), + VP9 = GST_MAKE_FOURCC ('V', 'P', '9', '0'), +} GstVaCodecs; + +guint32 gst_va_profile_codec (VAProfile profile); +GstCaps * gst_va_profile_caps (VAProfile profile); +const gchar * gst_va_profile_name (VAProfile profile); + +G_END_DECLS diff --git a/sys/va/gstvavideoformat.c b/sys/va/gstvavideoformat.c new file mode 100644 index 0000000000..5dbccf6a9b --- /dev/null +++ b/sys/va/gstvavideoformat.c @@ -0,0 +1,158 @@ +/* GStreamer + * Copyright (C) 2020 Igalia, S.L. + * Author: Víctor Jáquez + * + * 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 "gstvavideoformat.h" + +#define VA_NSB_FIRST 0 /* No Significant Bit */ + +/* XXX(victor): add RGB fourcc runtime checkups for screwed drivers */ +/* *INDENT-OFF* */ +static const struct FormatMap +{ + GstVideoFormat format; + guint va_rtformat; + VAImageFormat va_format; +} format_map[] = { +#define F(format, fourcc, rtformat, order, bpp, depth, r, g, b, a) { \ + G_PASTE (GST_VIDEO_FORMAT_, format), \ + G_PASTE (VA_RT_FORMAT_, rtformat), \ + { VA_FOURCC fourcc, G_PASTE (G_PASTE (VA_, order), _FIRST), \ + bpp, depth, r, g, b, a } } +#define G(format, fourcc, rtformat, order, bpp) \ + F (format, fourcc, rtformat, order, bpp, 0, 0, 0 ,0, 0) + G (NV12, ('N', 'V', '1', '2'), YUV420, NSB, 12), + G (NV21, ('N', 'V', '2', '1'), YUV420, NSB, 21), + G (VUYA, ('A', 'Y', 'U', 'V'), YUV444, LSB, 32), + F (RGBA, ('R', 'G', 'B', 'A'), RGB32, LSB, 32, 32, 0x000000ff, + 0x0000ff00, 0x00ff0000, 0xff000000), + /* F (????, RGBX), */ + F (BGRA, ('B', 'G', 'R', 'A'), RGB32, LSB, 32, 32, 0x00ff0000, + 0x0000ff00, 0x000000ff, 0xff000000), + F (ARGB, ('A', 'R', 'G', 'B'), RGB32, LSB, 32, 32, 0x0000ff00, + 0x00ff0000, 0xff000000, 0x000000ff), + /* F (????, XRGB), */ + F (ABGR, ('A', 'B', 'G', 'R'), RGB32, LSB, 32, 32, 0xff000000, + 0x00ff0000, 0x0000ff00, 0x000000ff), + /* F (????, XBGR), */ + G (UYVY, ('U', 'Y', 'V', 'Y'), YUV422, NSB, 16), + G (YUY2, ('Y', 'U', 'Y', '2'), YUV422, NSB, 16), + G (AYUV, ('A', 'Y', 'U', 'V'), YUV444, LSB, 32), + /* F (????, NV11), */ + G (YV12, ('Y', 'V', '1', '2'), YUV420, NSB, 12), + /* F (????, P208), */ + G (I420, ('I', '4', '2', '0'), YUV420, NSB, 12), + /* F (????, YV24), */ + /* F (????, YV32), */ + /* F (????, Y800), */ + /* F (????, IMC3), */ + /* F (????, 411P), */ + /* F (????, 411R), */ + /* F (????, 422H), */ + /* F (????, 422V), */ + /* F (????, 444P), */ + /* F (????, RGBP), */ + /* F (????, BGRP), */ + /* F (????, RGB565), */ + /* F (????, BGR565), */ + G (Y210, ('Y', '2', '1', '0'), YUV422_10, NSB, 32), + /* F (????, Y216), */ + G (Y410, ('Y', '4', '1', '0'), YUV444_10, NSB, 32), + /* F (????, Y416), */ + /* F (????, YV16), */ + G (P010_10LE, ('P', '0', '1', '0'), YUV420_10, NSB, 24), + /* F (P016_LE, P016, ????), */ + /* F (????, I010), */ + /* F (????, IYUV), */ + /* F (????, A2R10G10B10), */ + /* F (????, A2B10G10R10), */ + /* F (????, X2R10G10B10), */ + /* F (????, X2B10G10R10), */ + G (GRAY8, ('Y', '8', '0', '0'), YUV400, NSB, 8), + /* F (????, Y16), */ + /* G (VYUY, VYUY, YUV422), */ + /* G (YVYU, YVYU, YUV422), */ + /* F (ARGB64, ARGB64, ????), */ + /* F (????, ABGR64), */ +#undef F +#undef G +}; +/* *INDENT-ON* */ + +static const struct FormatMap * +get_format_map_from_va_fourcc (guint va_fourcc) +{ + int i; + + for (i = 0; i < G_N_ELEMENTS (format_map); i++) { + if (format_map[i].va_format.fourcc == va_fourcc) + return &format_map[i]; + } + + return NULL; +} + +static const struct FormatMap * +get_format_map_from_video_format (GstVideoFormat format) +{ + int i; + + for (i = 0; i < G_N_ELEMENTS (format_map); i++) { + if (format_map[i].format == format) + return &format_map[i]; + } + + return NULL; +} + +GstVideoFormat +gst_va_video_format_from_va_fourcc (guint va_fourcc) +{ + const struct FormatMap *map = get_format_map_from_va_fourcc (va_fourcc); + + return map ? map->format : GST_VIDEO_FORMAT_UNKNOWN; +} + +guint +gst_va_fourcc_from_video_format (GstVideoFormat format) +{ + const struct FormatMap *map = get_format_map_from_video_format (format); + + return map ? map->va_format.fourcc : 0; +} + +guint +gst_va_chroma_from_video_format (GstVideoFormat format) +{ + const struct FormatMap *map = get_format_map_from_video_format (format); + + return map ? map->va_rtformat : 0; +} + +const VAImageFormat * +gst_va_image_format_from_video_format (GstVideoFormat format) +{ + const struct FormatMap *map = get_format_map_from_video_format (format); + + return map ? &map->va_format : NULL; +} diff --git a/sys/va/gstvavideoformat.h b/sys/va/gstvavideoformat.h new file mode 100644 index 0000000000..c73aa7685f --- /dev/null +++ b/sys/va/gstvavideoformat.h @@ -0,0 +1,33 @@ +/* GStreamer + * Copyright (C) 2020 Igalia, S.L. + * Author: Víctor Jáquez + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +GstVideoFormat gst_va_video_format_from_va_fourcc (guint fourcc); +guint gst_va_fourcc_from_video_format (GstVideoFormat format); +guint gst_va_chroma_from_video_format (GstVideoFormat format); +const VAImageFormat * gst_va_image_format_from_video_format (GstVideoFormat format); + +G_END_DECLS diff --git a/sys/va/meson.build b/sys/va/meson.build new file mode 100644 index 0000000000..ba19076566 --- /dev/null +++ b/sys/va/meson.build @@ -0,0 +1,54 @@ +va_sources = [ + 'plugin.c', + 'gstvaallocator.c', + 'gstvacaps.c', + 'gstvadecoder.c', + 'gstvadisplay.c', + 'gstvadisplay_drm.c', + 'gstvadisplay_wrapped.c', + 'gstvadevice.c', + 'gstvah264dec.c', + 'gstvapool.c', + 'gstvaprofile.c', + 'gstvavideoformat.c', +] + +va_option = get_option('va') +if va_option.disabled() + subdir_done() +endif + +libva_req = ['>= 1.6'] + +libva_dep = dependency('libva', version: libva_req, required: va_option) +libva_drm_dep = dependency('libva-drm', version: libva_req, required: va_option) +libgudev_dep = dependency('gudev-1.0', required: va_option) +libdrm_dep = dependency('libdrm', required: false, + fallback: ['libdrm', 'ext_libdrm']) + +have_va = libva_dep.found() and libva_drm_dep.found() +if not (have_va and libgudev_dep.found()) + if va_option.enabled() + error('The va plugin was enabled explicity, but required dependencies were not found.') + endif + subdir_done() +endif + +cdata.set10('HAVE_LIBDRM', libdrm_dep.found()) + +driverdir = libva_dep.get_pkgconfig_variable('driverdir') +if driverdir == '' + driverdir = join_paths(get_option('prefix'), get_option('libdir'), 'dri') +endif +gstva_cargs = ['-DLIBVA_DRIVERS_PATH="' + driverdir + '"'] + +gstva = library('gstva', + va_sources, + c_args : gst_plugins_bad_args + extra_c_args + gstva_cargs + ['-std=c99'], + include_directories : [configinc], + dependencies : [gstbase_dep, gstvideo_dep, gstcodecs_dep, libva_dep, gstallocators_dep, libva_drm_dep, libgudev_dep, libdrm_dep] + extra_dep, + install : true, + install_dir : plugins_install_dir, +) +pkgconfig.generate(gstva, install_dir : plugins_pkgconfig_install_dir) +plugins += [gstva] diff --git a/sys/va/plugin.c b/sys/va/plugin.c new file mode 100644 index 0000000000..7efbff0157 --- /dev/null +++ b/sys/va/plugin.c @@ -0,0 +1,246 @@ +/* GStreamer + * Copyright (C) 2020 Igalia, S.L. + * Author: Víctor Jáquez + * + * 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 "gstvacaps.h" +#include "gstvadevice.h" +#include "gstvah264dec.h" +#include "gstvaprofile.h" + +#define GST_CAT_DEFAULT gstva_debug +GST_DEBUG_CATEGORY (gstva_debug); + +static void +plugin_add_dependencies (GstPlugin * plugin) +{ + const gchar *env_vars[] = { "LIBVA_DRIVER_NAME", NULL }; + const gchar *kernel_paths[] = { "/dev/dri", NULL }; + const gchar *kernel_names[] = { "renderD", NULL }; + + /* features get updated upon changes in /dev/dri/renderD* */ + gst_plugin_add_dependency (plugin, NULL, kernel_paths, kernel_names, + GST_PLUGIN_DEPENDENCY_FLAG_FILE_NAME_IS_PREFIX); + + /* features get updated upon changes on LIBVA_DRIVER_NAME envvar */ + gst_plugin_add_dependency (plugin, env_vars, NULL, NULL, + GST_PLUGIN_DEPENDENCY_FLAG_NONE); + + /* features get updated upon changes in default VA drivers + * directory */ + gst_plugin_add_dependency_simple (plugin, "LIBVA_DRIVERS_PATH", + LIBVA_DRIVERS_PATH, "_drv_video.so", + GST_PLUGIN_DEPENDENCY_FLAG_FILE_NAME_IS_SUFFIX | + GST_PLUGIN_DEPENDENCY_FLAG_PATHS_ARE_DEFAULT_ONLY); +} + +static void +plugin_register_decoders (GstPlugin * plugin, GstVaDevice * device, + GHashTable * decoders) +{ + GHashTableIter iter; + gpointer key, value; + + g_hash_table_iter_init (&iter, decoders); + while (g_hash_table_iter_next (&iter, &key, &value)) { + guint32 codec = *((gint64 *) key); + GArray *profiles = (GArray *) value; + GstCaps *sinkcaps = NULL, *srccaps = NULL; + + if (!profiles || profiles->len == 0) + continue; + + if (!gst_va_caps_from_profiles (device->display, profiles, VAEntrypointVLD, + &sinkcaps, &srccaps)) + continue; + + GST_LOG ("%d decoder codec: %" GST_FOURCC_FORMAT, profiles->len, + GST_FOURCC_ARGS (codec)); + GST_LOG ("sink caps: %" GST_PTR_FORMAT, sinkcaps); + GST_LOG ("src caps: %" GST_PTR_FORMAT, srccaps); + + switch (codec) { + case H264: + if (!gst_va_h264_dec_register (plugin, device, sinkcaps, srccaps, + GST_RANK_NONE)) { + GST_WARNING ("Failed to register H264 decoder: %s", + device->render_device_path); + } + break; + default: + GST_DEBUG ("No decoder implementation for %" GST_FOURCC_FORMAT, + GST_FOURCC_ARGS (codec)); + break; + } + + gst_caps_unref (srccaps); + gst_caps_unref (sinkcaps); + } +} + +static void +plugin_register_encoders (GstPlugin * plugin, GstVaDevice * device, + GHashTable * encoders, VAEntrypoint entrypoint) +{ + GHashTableIter iter; + gpointer key, value; + const gchar *str; + + if (entrypoint == VAEntrypointEncSliceLP) + str = "low power "; + else + str = ""; + + g_hash_table_iter_init (&iter, encoders); + while (g_hash_table_iter_next (&iter, &key, &value)) { + guint32 codec = *((gint64 *) key); + GArray *profiles = (GArray *) value; + GstCaps *sinkcaps = NULL, *srccaps = NULL; + + if (!profiles || profiles->len == 0) + continue; + + if (!gst_va_caps_from_profiles (device->display, profiles, entrypoint, + &srccaps, &sinkcaps)) + continue; + + GST_LOG ("%d encoder %scodec: %" GST_FOURCC_FORMAT, profiles->len, str, + GST_FOURCC_ARGS (codec)); + GST_LOG ("sink caps: %" GST_PTR_FORMAT, sinkcaps); + GST_LOG ("src caps: %" GST_PTR_FORMAT, srccaps); + + gst_caps_unref (srccaps); + gst_caps_unref (sinkcaps); + } +} + +static inline void +_insert_profile_in_table (GHashTable * table, VAProfile profile) +{ + gint64 codec = gst_va_profile_codec (profile); + GArray *profiles; + + if (codec == GST_MAKE_FOURCC ('N', 'O', 'N', 'E')) + return; + + profiles = g_hash_table_lookup (table, &codec); + if (!profiles) { + gint64 *codec_ptr = g_new (gint64, 1); + + *codec_ptr = codec; + profiles = g_array_new (FALSE, FALSE, sizeof (VAProfile)); + g_hash_table_insert (table, codec_ptr, profiles); + } + g_array_append_val (profiles, profile); +} + +static gboolean +plugin_register_elements (GstPlugin * plugin, GstVaDevice * device) +{ + VADisplay dpy = gst_va_display_get_va_dpy (device->display); + VAEntrypoint *entrypoints = g_new (VAEntrypoint, vaMaxNumEntrypoints (dpy)); + VAProfile *profiles = g_new (VAProfile, vaMaxNumProfiles (dpy)); + VAStatus status; + GHashTable *decoders, *encoders, *encoderslp, *encodersimg; + gint i, j, num_entrypoints = 0, num_profiles = 0; + gboolean ret = FALSE; + + decoders = g_hash_table_new_full (g_int64_hash, g_int64_equal, + (GDestroyNotify) g_free, (GDestroyNotify) g_array_unref); + encoders = g_hash_table_new_full (g_int64_hash, g_int64_equal, + (GDestroyNotify) g_free, (GDestroyNotify) g_array_unref); + encoderslp = g_hash_table_new_full (g_int64_hash, g_int64_equal, + (GDestroyNotify) g_free, (GDestroyNotify) g_array_unref); + encodersimg = g_hash_table_new_full (g_int64_hash, g_int64_equal, + (GDestroyNotify) g_free, (GDestroyNotify) g_array_unref); + + status = vaQueryConfigProfiles (dpy, profiles, &num_profiles); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR ("vaQueryConfigProfile: %s", vaErrorStr (status)); + goto bail; + } + + for (i = 0; i < num_profiles; i++) { + status = vaQueryConfigEntrypoints (dpy, profiles[i], entrypoints, + &num_entrypoints); + if (status != VA_STATUS_SUCCESS) { + GST_ERROR ("vaQueryConfigEntrypoints: %s", vaErrorStr (status)); + goto bail; + } + + for (j = 0; j < num_entrypoints; j++) { + if (entrypoints[j] == VAEntrypointVLD) + _insert_profile_in_table (decoders, profiles[i]); + else if (entrypoints[j] == VAEntrypointEncSlice) + _insert_profile_in_table (encoders, profiles[i]); + else if (entrypoints[j] == VAEntrypointEncSliceLP) + _insert_profile_in_table (encoderslp, profiles[i]); + else if (entrypoints[j] == VAEntrypointEncPicture) + _insert_profile_in_table (encodersimg, profiles[i]); + } + } + + plugin_register_decoders (plugin, device, decoders); + plugin_register_encoders (plugin, device, encoders, VAEntrypointEncSlice); + plugin_register_encoders (plugin, device, encoderslp, VAEntrypointEncSliceLP); + plugin_register_encoders (plugin, device, encodersimg, + VAEntrypointEncPicture); + + ret = TRUE; + +bail: + g_hash_table_unref (encodersimg); + g_hash_table_unref (encoderslp); + g_hash_table_unref (encoders); + g_hash_table_unref (decoders); + g_free (entrypoints); + g_free (profiles); + + return ret; +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + GList *devices, *dev; + gboolean ret = TRUE; + + GST_DEBUG_CATEGORY_INIT (gstva_debug, "va", 0, "VA general debug"); + + plugin_add_dependencies (plugin); + + devices = gst_va_device_find_devices (); + for (dev = devices; dev; dev = g_list_next (dev)) { + if (!plugin_register_elements (plugin, dev->data)) { + ret = FALSE; + break; + } + } + gst_va_device_list_free (devices); + + return ret; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + va, "VA-API codecs plugin", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)