/* 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. */ /** * plugin-va: * * Since: 1.18 */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gstvaav1dec.h" #include "gstvacaps.h" #include "gstvacompositor.h" #include "gstvadeinterlace.h" #include "gstvadevice.h" #include "gstvafilter.h" #include "gstvah264dec.h" #include "gstvah264enc.h" #include "gstvah265dec.h" #include "gstvampeg2dec.h" #include "gstvaprofile.h" #include "gstvavp8dec.h" #include "gstvavp9dec.h" #include "gstvavpp.h" #define GST_CAT_DEFAULT gstva_debug GST_DEBUG_CATEGORY (gstva_debug); /* big bad mutex to exclusive access to shared stream buffers, such as * DMABuf after a tee */ GRecMutex GST_VA_SHARED_LOCK = { 0, }; 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; case HEVC: if (!gst_va_h265_dec_register (plugin, device, sinkcaps, srccaps, GST_RANK_NONE)) { GST_WARNING ("Failed to register H265 decoder: %s", device->render_device_path); } break; case VP8: if (!gst_va_vp8_dec_register (plugin, device, sinkcaps, srccaps, GST_RANK_NONE)) { GST_WARNING ("Failed to register VP8 decoder: %s", device->render_device_path); } break; case VP9: if (!gst_va_vp9_dec_register (plugin, device, sinkcaps, srccaps, GST_RANK_NONE)) { GST_WARNING ("Failed to register VP9 decoder: %s", device->render_device_path); } break; case MPEG2: if (!gst_va_mpeg2_dec_register (plugin, device, sinkcaps, srccaps, GST_RANK_NONE)) { GST_WARNING ("Failed to register Mpeg2 decoder: %s", device->render_device_path); } break; #if VA_CHECK_VERSION(1, 8, 0) case AV1: if (!gst_va_av1_dec_register (plugin, device, sinkcaps, srccaps, GST_RANK_NONE)) { GST_WARNING ("Failed to register AV1 decoder: %s", device->render_device_path); } break; #endif 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; 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, (entrypoint == VAEntrypointEncSliceLP) ? "low power " : "", 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_enc_register (plugin, device, sinkcaps, srccaps, GST_RANK_NONE, entrypoint)) { GST_WARNING ("Failed to register H264 decoder: %s", device->render_device_path); } break; default: GST_DEBUG ("No encoder implementation for %" GST_FOURCC_FORMAT, GST_FOURCC_ARGS (codec)); break; } gst_caps_unref (srccaps); gst_caps_unref (sinkcaps); } } static void plugin_register_vpp (GstPlugin * plugin, GstVaDevice * device) { GstVaFilter *filter; gboolean has_colorbalance, has_deinterlace, has_compose; has_colorbalance = FALSE; has_deinterlace = FALSE; has_compose = FALSE; filter = gst_va_filter_new (device->display); if (gst_va_filter_open (filter)) { has_colorbalance = gst_va_filter_has_filter (filter, VAProcFilterColorBalance); has_deinterlace = gst_va_filter_has_filter (filter, VAProcFilterDeinterlacing); has_compose = gst_va_filter_has_compose (filter); } else { GST_WARNING ("Failed open VA filter"); gst_object_unref (filter); return; } gst_object_unref (filter); if (!gst_va_vpp_register (plugin, device, has_colorbalance, GST_RANK_NONE)) GST_WARNING ("Failed to register postproc: %s", device->render_device_path); if (has_deinterlace) { if (!gst_va_deinterlace_register (plugin, device, GST_RANK_NONE)) { GST_WARNING ("Failed to register deinterlace: %s", device->render_device_path); } } if (has_compose) { if (!gst_va_compositor_register (plugin, device, GST_RANK_NONE)) { GST_WARNING ("Failed to register compositor: %s", device->render_device_path); } } } 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 has_vpp = FALSE, 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]); else if (entrypoints[j] == VAEntrypointVideoProc) has_vpp = TRUE; } } 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); if (has_vpp) plugin_register_vpp (plugin, device); 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)