/* * gstvaapiuploader.c - VA-API video upload helper * * Copyright (C) 2010-2011 Splitted-Desktop Systems * Author: Gwenole Beauchesne * Copyright (C) 2011-2013 Intel Corporation * Author: Gwenole Beauchesne * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA */ #include "gst/vaapi/sysdeps.h" #include #include #include #include #include #include "gstvaapiuploader.h" #include "gstvaapipluginutil.h" #include "gstvaapivideobuffer.h" #define GST_HELPER_NAME "vaapiupload" #define GST_HELPER_DESC "VA-API video uploader" GST_DEBUG_CATEGORY_STATIC(gst_debug_vaapi_uploader); #define GST_CAT_DEFAULT gst_debug_vaapi_uploader G_DEFINE_TYPE(GstVaapiUploader, gst_vaapi_uploader, G_TYPE_OBJECT) #define GST_VAAPI_UPLOADER_CAST(obj) \ ((GstVaapiUploader *)(obj)) #define GST_VAAPI_UPLOADER_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE((obj), \ GST_VAAPI_TYPE_UPLOADER, \ GstVaapiUploaderPrivate)) struct _GstVaapiUploaderPrivate { GstVaapiDisplay *display; GstCaps *allowed_caps; GstVaapiVideoPool *images; GstVideoInfo image_info; GstVaapiVideoPool *surfaces; GstVideoInfo surface_info; guint direct_rendering; }; enum { PROP_0, PROP_DISPLAY, }; static void gst_vaapi_uploader_destroy(GstVaapiUploader *uploader) { GstVaapiUploaderPrivate * const priv = uploader->priv; gst_caps_replace(&priv->allowed_caps, NULL); gst_vaapi_video_pool_replace(&priv->images, NULL); gst_vaapi_video_pool_replace(&priv->surfaces, NULL); gst_vaapi_display_replace(&priv->display, NULL); } static gboolean ensure_display(GstVaapiUploader *uploader, GstVaapiDisplay *display) { GstVaapiUploaderPrivate * const priv = uploader->priv; gst_vaapi_display_replace(&priv->display, display); return TRUE; } static gboolean ensure_image(GstVaapiImage *image) { guint i, num_planes, width, height; /* Make the image fully dirty */ if (!gst_vaapi_image_map(image)) return FALSE; gst_vaapi_image_get_size(image, &width, &height); num_planes = gst_vaapi_image_get_plane_count(image); for (i = 0; i < num_planes; i++) { guchar * const plane = gst_vaapi_image_get_plane(image, i); if (plane) memset(plane, 0, gst_vaapi_image_get_pitch(image, i)); } if (!gst_vaapi_image_unmap(image)) gst_vaapi_image_unmap(image); return TRUE; } static gboolean ensure_allowed_caps(GstVaapiUploader *uploader) { GstVaapiUploaderPrivate * const priv = uploader->priv; GstVaapiSurface *surface = NULL; GArray *formats = NULL, *out_formats = NULL; GstCaps *out_caps; guint i; gboolean success = FALSE; enum { WIDTH = 64, HEIGHT = 64 }; if (priv->allowed_caps) return TRUE; formats = gst_vaapi_display_get_image_formats(priv->display); if (!formats) goto cleanup; out_formats = g_array_sized_new(FALSE, FALSE, sizeof(GstVideoFormat), formats->len); if (!out_formats) goto cleanup; surface = gst_vaapi_surface_new(priv->display, GST_VAAPI_CHROMA_TYPE_YUV420, WIDTH, HEIGHT); if (!surface) goto cleanup; for (i = 0; i < formats->len; i++) { const GstVideoFormat format = g_array_index(formats, GstVideoFormat, i); GstVaapiImage *image; if (format == GST_VIDEO_FORMAT_UNKNOWN) continue; image = gst_vaapi_image_new(priv->display, format, WIDTH, HEIGHT); if (!image) continue; if (ensure_image(image) && gst_vaapi_surface_put_image(surface, image)) g_array_append_val(out_formats, format); gst_vaapi_object_unref(image); } out_caps = gst_vaapi_video_format_new_template_caps_from_list(out_formats); if (!out_caps) goto cleanup; gst_caps_replace(&priv->allowed_caps, out_caps); gst_caps_unref(out_caps); success = TRUE; cleanup: if (out_formats) g_array_unref(out_formats); if (formats) g_array_unref(formats); if (surface) gst_vaapi_object_unref(surface); return success; } static gboolean ensure_image_pool(GstVaapiUploader *uploader, GstCaps *caps, gboolean *caps_changed_ptr) { GstVaapiUploaderPrivate * const priv = uploader->priv; GstVaapiVideoPool *pool; GstVideoInfo vi; GstVideoFormat format; guint width, height; if (!gst_video_info_from_caps(&vi, caps)) return FALSE; format = GST_VIDEO_INFO_FORMAT(&vi); width = GST_VIDEO_INFO_WIDTH(&vi); height = GST_VIDEO_INFO_HEIGHT(&vi); *caps_changed_ptr = format != GST_VIDEO_INFO_FORMAT(&priv->image_info) || width != GST_VIDEO_INFO_WIDTH(&priv->image_info) || height != GST_VIDEO_INFO_HEIGHT(&priv->image_info); if (!*caps_changed_ptr) return TRUE; pool = gst_vaapi_image_pool_new(priv->display, &vi); if (!pool) return FALSE; gst_video_info_set_format(&priv->image_info, format, width, height); gst_vaapi_video_pool_replace(&priv->images, pool); gst_vaapi_video_pool_unref(pool); return TRUE; } static gboolean ensure_surface_pool(GstVaapiUploader *uploader, GstCaps *caps, gboolean *caps_changed_ptr) { GstVaapiUploaderPrivate * const priv = uploader->priv; GstVaapiVideoPool *pool; GstVideoInfo vi; GstVideoFormat format; guint width, height; if (!gst_video_info_from_caps(&vi, caps)) return FALSE; format = GST_VIDEO_INFO_FORMAT(&vi); width = GST_VIDEO_INFO_WIDTH(&vi); height = GST_VIDEO_INFO_HEIGHT(&vi); *caps_changed_ptr = format != GST_VIDEO_INFO_FORMAT(&priv->surface_info) || width != GST_VIDEO_INFO_WIDTH(&priv->surface_info) || height != GST_VIDEO_INFO_HEIGHT(&priv->surface_info); if (!*caps_changed_ptr) return TRUE; /* Always try to downsample source buffers to YUV 4:2:0 format as this saves memory bandwidth for further rendering */ /* XXX: this also means that visual quality is not preserved */ if (format != GST_VIDEO_FORMAT_ENCODED) { const GstVaapiChromaType chroma_type = gst_vaapi_video_format_get_chroma_type(format); if (chroma_type != GST_VAAPI_CHROMA_TYPE_YUV420) { const GstVideoFormat image_format = GST_VIDEO_INFO_FORMAT(&priv->image_info); GST_INFO("use implicit conversion of %s buffers to NV12 surfaces", gst_video_format_to_string(image_format)); gst_video_info_set_format(&vi, GST_VIDEO_FORMAT_NV12, width, height); } } pool = gst_vaapi_surface_pool_new(priv->display, &vi); if (!pool) return FALSE; gst_video_info_set_format(&priv->surface_info, format, width, height); gst_vaapi_video_pool_replace(&priv->surfaces, pool); gst_vaapi_video_pool_unref(pool); return TRUE; } static void gst_vaapi_uploader_finalize(GObject *object) { gst_vaapi_uploader_destroy(GST_VAAPI_UPLOADER_CAST(object)); G_OBJECT_CLASS(gst_vaapi_uploader_parent_class)->finalize(object); } static void gst_vaapi_uploader_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { GstVaapiUploader * const uploader = GST_VAAPI_UPLOADER_CAST(object); switch (prop_id) { case PROP_DISPLAY: ensure_display(uploader, g_value_get_pointer(value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void gst_vaapi_uploader_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { GstVaapiUploader * const uploader = GST_VAAPI_UPLOADER_CAST(object); switch (prop_id) { case PROP_DISPLAY: g_value_set_pointer(value, uploader->priv->display); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void gst_vaapi_uploader_class_init(GstVaapiUploaderClass *klass) { GObjectClass * const object_class = G_OBJECT_CLASS(klass); GST_DEBUG_CATEGORY_INIT(gst_debug_vaapi_uploader, GST_HELPER_NAME, 0, GST_HELPER_DESC); g_type_class_add_private(klass, sizeof(GstVaapiUploaderPrivate)); object_class->finalize = gst_vaapi_uploader_finalize; object_class->set_property = gst_vaapi_uploader_set_property; object_class->get_property = gst_vaapi_uploader_get_property; g_object_class_install_property( object_class, PROP_DISPLAY, g_param_spec_pointer( "display", "Display", "The GstVaapiDisplay this object is bound to", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); } static void gst_vaapi_uploader_init(GstVaapiUploader *uploader) { GstVaapiUploaderPrivate *priv; priv = GST_VAAPI_UPLOADER_GET_PRIVATE(uploader); uploader->priv = priv; gst_video_info_init(&priv->image_info); gst_video_info_init(&priv->surface_info); } GstVaapiUploader * gst_vaapi_uploader_new(GstVaapiDisplay *display) { return g_object_new(GST_VAAPI_TYPE_UPLOADER, "display", display, NULL); } gboolean gst_vaapi_uploader_ensure_display( GstVaapiUploader *uploader, GstVaapiDisplay *display ) { g_return_val_if_fail(GST_VAAPI_IS_UPLOADER(uploader), FALSE); g_return_val_if_fail(display != NULL, FALSE); return ensure_display(uploader,display); } gboolean gst_vaapi_uploader_ensure_caps( GstVaapiUploader *uploader, GstCaps *src_caps, GstCaps *out_caps ) { GstVaapiUploaderPrivate *priv; GstVaapiImage *image; gboolean image_caps_changed, surface_caps_changed; g_return_val_if_fail(GST_VAAPI_IS_UPLOADER(uploader), FALSE); g_return_val_if_fail(src_caps != NULL, FALSE); if (!out_caps) out_caps = src_caps; if (!ensure_image_pool(uploader, src_caps, &image_caps_changed)) return FALSE; if (!ensure_surface_pool(uploader, out_caps, &surface_caps_changed)) return FALSE; if (!image_caps_changed && !surface_caps_changed) return TRUE; priv = uploader->priv; priv->direct_rendering = 0; /* Check if we can alias source and output buffers (same data_size) */ image = gst_vaapi_video_pool_get_object(priv->images); if (image) { if ((gst_vaapi_image_get_format(image) == GST_VIDEO_INFO_FORMAT(&priv->image_info)) && gst_vaapi_image_is_linear(image) && (gst_vaapi_image_get_data_size(image) == GST_VIDEO_INFO_SIZE(&priv->image_info))) priv->direct_rendering = 1; gst_vaapi_video_pool_put_object(priv->images, image); } GST_INFO("direct-rendering: level %u", priv->direct_rendering); return TRUE; } gboolean gst_vaapi_uploader_process( GstVaapiUploader *uploader, GstBuffer *src_buffer, GstBuffer *out_buffer ) { GstVaapiVideoMeta *src_meta, *out_meta; GstVaapiSurface *surface; GstVaapiImage *image; g_return_val_if_fail(GST_VAAPI_IS_UPLOADER(uploader), FALSE); out_meta = gst_buffer_get_vaapi_video_meta(out_buffer); if (!out_meta) { GST_WARNING("expected an output video buffer"); return FALSE; } surface = gst_vaapi_video_meta_get_surface(out_meta); g_return_val_if_fail(surface != NULL, FALSE); src_meta = gst_buffer_get_vaapi_video_meta(src_buffer); if (src_meta) { /* GstVaapiVideoBuffer with mapped VA image */ image = gst_vaapi_video_meta_get_image(src_meta); if (!image || !gst_vaapi_image_unmap(image)) return FALSE; } else { /* Regular GstBuffer that needs to be uploaded to a VA image */ image = gst_vaapi_video_meta_get_image(out_meta); if (!image) { image = gst_vaapi_video_pool_get_object(uploader->priv->images); if (!image) return FALSE; gst_vaapi_video_meta_set_image(out_meta, image); } if (!gst_vaapi_image_update_from_buffer(image, src_buffer, NULL)) return FALSE; } g_return_val_if_fail(image != NULL, FALSE); if (!gst_vaapi_surface_put_image(surface, image)) { GST_WARNING("failed to upload YUV buffer to VA surface"); return FALSE; } /* Map again for next uploads */ if (!gst_vaapi_image_map(image)) return FALSE; return TRUE; } GstCaps * gst_vaapi_uploader_get_caps(GstVaapiUploader *uploader) { g_return_val_if_fail(GST_VAAPI_IS_UPLOADER(uploader), NULL); if (!ensure_allowed_caps(uploader)) return NULL; return uploader->priv->allowed_caps; } GstBuffer * gst_vaapi_uploader_get_buffer(GstVaapiUploader *uploader) { GstVaapiUploaderPrivate *priv; GstVaapiImage *image; GstVaapiVideoMeta *meta; GstVaapiSurfaceProxy *proxy; GstBuffer *buffer; g_return_val_if_fail(GST_VAAPI_IS_UPLOADER(uploader), NULL); priv = uploader->priv; buffer = gst_vaapi_video_buffer_new_from_pool(priv->images); if (!buffer) { GST_WARNING("failed to allocate video buffer"); goto error; } proxy = gst_vaapi_surface_proxy_new_from_pool( GST_VAAPI_SURFACE_POOL(priv->surfaces)); if (!proxy) { GST_WARNING("failed to allocate VA surface"); goto error; } meta = gst_buffer_get_vaapi_video_meta(buffer); gst_vaapi_video_meta_set_surface_proxy(meta, proxy); gst_vaapi_surface_proxy_unref(proxy); image = gst_vaapi_video_meta_get_image(meta); if (!gst_vaapi_image_map(image)) { GST_WARNING("failed to map VA image"); goto error; } #if !GST_CHECK_VERSION(1,0,0) GST_BUFFER_DATA(buffer) = gst_vaapi_image_get_plane(image, 0); GST_BUFFER_SIZE(buffer) = gst_vaapi_image_get_data_size(image); #endif return buffer; error: gst_buffer_unref(buffer); return buffer; } gboolean gst_vaapi_uploader_has_direct_rendering(GstVaapiUploader *uploader) { g_return_val_if_fail(GST_VAAPI_IS_UPLOADER(uploader), FALSE); return uploader->priv->direct_rendering; }