gstreamer/subprojects/gst-plugins-bad/sys/va/gstvabasedec.c
Víctor Manuel Jáquez Leal 4c97ffeafb va: basedec: Always select first available format.
If the stream chroma doesn't match with any video format in the source
caps template (generated from va config surface formats) instead of
return unknown, return the first available format in the template,
assuming that the driver would be capable to do color conversions.

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/2404>
2022-05-11 15:03:54 +02:00

1018 lines
32 KiB
C

/* GStreamer
* Copyright (C) 2020 Igalia, S.L.
* Author: Víctor Jáquez <vjaquez@igalia.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the0
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "gstvabasedec.h"
#include <gst/va/gstva.h>
#include "gstvacaps.h"
#define GST_CAT_DEFAULT (base->debug_category)
#define GST_VA_BASE_DEC_GET_PARENT_CLASS(obj) (GST_VA_BASE_DEC_GET_CLASS(obj)->parent_decoder_class)
static void
gst_va_base_dec_get_property (GObject * object, guint prop_id,
GValue * value, GParamSpec * pspec)
{
GstVaBaseDec *self = GST_VA_BASE_DEC (object);
switch (prop_id) {
case GST_VA_DEC_PROP_DEVICE_PATH:{
if (!(self->display && GST_IS_VA_DISPLAY_DRM (self->display))) {
g_value_set_string (value, NULL);
return;
}
g_object_get_property (G_OBJECT (self->display), "path", value);
break;
}
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
}
}
static gboolean
gst_va_base_dec_open (GstVideoDecoder * decoder)
{
GstVaBaseDec *base = GST_VA_BASE_DEC (decoder);
GstVaBaseDecClass *klass = GST_VA_BASE_DEC_GET_CLASS (decoder);
gboolean ret = FALSE;
if (!gst_va_ensure_element_data (decoder, klass->render_device_path,
&base->display))
return FALSE;
g_object_notify (G_OBJECT (decoder), "device-path");
if (!g_atomic_pointer_get (&base->decoder)) {
GstVaDecoder *va_decoder;
va_decoder = gst_va_decoder_new (base->display, klass->codec);
if (va_decoder)
ret = TRUE;
gst_object_replace ((GstObject **) (&base->decoder),
(GstObject *) va_decoder);
gst_clear_object (&va_decoder);
} else {
ret = TRUE;
}
base->apply_video_crop = FALSE;
return ret;
}
gboolean
gst_va_base_dec_close (GstVideoDecoder * decoder)
{
GstVaBaseDec *base = GST_VA_BASE_DEC (decoder);
gst_clear_object (&base->decoder);
gst_clear_object (&base->display);
g_object_notify (G_OBJECT (decoder), "device-path");
return TRUE;
}
static gboolean
gst_va_base_dec_stop (GstVideoDecoder * decoder)
{
GstVaBaseDec *base = GST_VA_BASE_DEC (decoder);
if (!gst_va_decoder_close (base->decoder))
return FALSE;
if (base->output_state)
gst_video_codec_state_unref (base->output_state);
base->output_state = NULL;
if (base->other_pool)
gst_buffer_pool_set_active (base->other_pool, FALSE);
gst_clear_object (&base->other_pool);
g_clear_pointer (&base->convert, gst_video_converter_free);
return GST_VIDEO_DECODER_CLASS (GST_VA_BASE_DEC_GET_PARENT_CLASS
(decoder))->stop (decoder);
}
static GstCaps *
gst_va_base_dec_getcaps (GstVideoDecoder * decoder, GstCaps * filter)
{
GstCaps *caps = NULL, *tmp;
GstVaBaseDec *base = GST_VA_BASE_DEC (decoder);
GstVaDecoder *va_decoder = NULL;
gst_object_replace ((GstObject **) & va_decoder, (GstObject *) base->decoder);
if (va_decoder) {
caps = gst_va_decoder_get_sinkpad_caps (va_decoder);
gst_object_unref (va_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 (base, "Returning caps %" GST_PTR_FORMAT, caps);
} else {
caps = gst_video_decoder_proxy_getcaps (decoder, NULL, filter);
}
return caps;
}
static gboolean
_query_context (GstVaBaseDec * self, GstQuery * query)
{
GstVaDisplay *display = NULL;
gboolean ret;
gst_object_replace ((GstObject **) & display, (GstObject *) self->display);
ret = gst_va_handle_context_query (GST_ELEMENT_CAST (self), query, display);
gst_clear_object (&display);
return ret;
}
static gboolean
gst_va_base_dec_src_query (GstVideoDecoder * decoder, GstQuery * query)
{
GstVaBaseDec *base = GST_VA_BASE_DEC (decoder);
gboolean ret = FALSE;
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_CONTEXT:{
ret = _query_context (base, query);
break;
}
case GST_QUERY_CAPS:{
GstCaps *caps = NULL, *tmp, *filter = NULL;
GstVaDecoder *va_decoder = NULL;
gboolean fixed_caps;
gst_object_replace ((GstObject **) & va_decoder,
(GstObject *) base->decoder);
gst_query_parse_caps (query, &filter);
fixed_caps = GST_PAD_IS_FIXED_CAPS (GST_VIDEO_DECODER_SRC_PAD (decoder));
if (!fixed_caps && va_decoder)
caps = gst_va_decoder_get_srcpad_caps (va_decoder);
gst_clear_object (&va_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 (base, "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 (GST_VA_BASE_DEC_GET_PARENT_CLASS
(decoder))->src_query (decoder, query);
break;
}
return ret;
}
static gboolean
gst_va_base_dec_sink_query (GstVideoDecoder * decoder, GstQuery * query)
{
if (GST_QUERY_TYPE (query) == GST_QUERY_CONTEXT)
return _query_context (GST_VA_BASE_DEC (decoder), query);
return GST_VIDEO_DECODER_CLASS (GST_VA_BASE_DEC_GET_PARENT_CLASS
(decoder))->sink_query (decoder, query);
}
static GstAllocator *
_create_allocator (GstVaBaseDec * base, GstCaps * caps)
{
GstAllocator *allocator = NULL;
if (gst_caps_is_dmabuf (caps))
allocator = gst_va_dmabuf_allocator_new (base->display);
else {
GArray *surface_formats =
gst_va_decoder_get_surface_formats (base->decoder);
allocator = gst_va_allocator_new (base->display, surface_formats);
}
return allocator;
}
static void
_create_other_pool (GstVaBaseDec * base, GstAllocator * allocator,
GstAllocationParams * params, GstCaps * caps, guint size)
{
GstBufferPool *pool;
GstStructure *config;
gst_clear_object (&base->other_pool);
GST_DEBUG_OBJECT (base, "making new other pool for copy");
pool = gst_video_buffer_pool_new ();
config = gst_buffer_pool_get_config (pool);
gst_buffer_pool_config_set_params (config, caps, size, 0, 0);
gst_buffer_pool_config_set_allocator (config, allocator, params);
if (!gst_buffer_pool_set_config (pool, config)) {
GST_ERROR_OBJECT (base, "Couldn't configure other pool for copy.");
gst_clear_object (&pool);
}
base->other_pool = pool;
}
static gboolean
_need_video_crop (GstVaBaseDec * base)
{
if (base->need_valign &&
(base->valign.padding_left > 0 || base->valign.padding_top > 0))
return TRUE;
return FALSE;
}
/* This path for pool setting is a little complicated but not commonly
used. We deliberately separate it from the main path of pool setting. */
static gboolean
_decide_allocation_for_video_crop (GstVideoDecoder * decoder,
GstQuery * query, GstCaps * caps, const GstVideoInfo * info)
{
GstAllocator *allocator = NULL, *other_allocator = NULL;
GstAllocationParams other_params, params;
gboolean update_pool = FALSE, update_allocator = FALSE;
GstBufferPool *pool = NULL, *other_pool = NULL;
guint size = 0, min, max;
GstVaBaseDec *base = GST_VA_BASE_DEC (decoder);
gboolean ret = TRUE;
GstCaps *va_caps = NULL;
/* If others provide a valid allocator, just use it. */
if (gst_query_get_n_allocation_params (query) > 0) {
gst_query_parse_nth_allocation_param (query, 0, &other_allocator,
&other_params);
update_allocator = TRUE;
} else {
gst_allocation_params_init (&other_params);
}
/* If others provide a valid pool, just use it. */
if (gst_query_get_n_allocation_pools (query) > 0) {
gst_query_parse_nth_allocation_pool (query, 0, &other_pool, &size, &min,
&max);
min += base->min_buffers;
size = MAX (size, GST_VIDEO_INFO_SIZE (info));
update_pool = TRUE;
} else {
size = GST_VIDEO_INFO_SIZE (info);
min = base->min_buffers;
max = 0;
}
/* Ensure that the other pool is ready */
if (gst_caps_is_raw (caps)) {
if (GST_IS_VA_POOL (other_pool))
gst_clear_object (&other_pool);
if (!other_pool) {
if (other_allocator && (GST_IS_VA_DMABUF_ALLOCATOR (other_allocator)
|| GST_IS_VA_ALLOCATOR (other_allocator)))
gst_clear_object (&other_allocator);
_create_other_pool (base, other_allocator, &other_params, caps, size);
} else {
gst_object_replace ((GstObject **) & base->other_pool,
(GstObject *) other_pool);
}
} else {
GstStructure *other_config;
if (!GST_IS_VA_POOL (other_pool))
gst_clear_object (&other_pool);
if (!other_pool)
other_pool = gst_va_pool_new ();
if (other_allocator && !(GST_IS_VA_DMABUF_ALLOCATOR (other_allocator)
|| GST_IS_VA_ALLOCATOR (other_allocator)))
gst_clear_object (&other_allocator);
if (!other_allocator) {
other_allocator = _create_allocator (base, caps);
if (!other_allocator) {
ret = FALSE;
goto cleanup;
}
}
other_config = gst_buffer_pool_get_config (other_pool);
gst_buffer_pool_config_set_params (other_config, caps, size, min, max);
gst_buffer_pool_config_set_allocator (other_config, other_allocator,
&other_params);
/* Always support VideoMeta but no VideoCropMeta here. */
gst_buffer_pool_config_add_option (other_config,
GST_BUFFER_POOL_OPTION_VIDEO_META);
gst_buffer_pool_config_set_va_allocation_params (other_config, 0,
GST_VA_FEATURE_AUTO);
if (!gst_buffer_pool_set_config (other_pool, other_config)) {
ret = FALSE;
goto cleanup;
}
gst_object_replace ((GstObject **) & base->other_pool,
(GstObject *) other_pool);
}
/* Now setup the buffer pool for decoder */
pool = gst_va_pool_new ();
va_caps = gst_caps_copy (caps);
gst_caps_set_features_simple (va_caps,
gst_caps_features_from_string (GST_CAPS_FEATURE_MEMORY_VA));
if (!(allocator = _create_allocator (base, va_caps))) {
ret = FALSE;
goto cleanup;
}
gst_allocation_params_init (&params);
{
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, &params);
gst_buffer_pool_config_add_option (config,
GST_BUFFER_POOL_OPTION_VIDEO_META);
if (_need_video_crop (base))
gst_buffer_pool_config_set_va_alignment (config, &base->valign);
gst_buffer_pool_config_set_va_allocation_params (config,
VA_SURFACE_ATTRIB_USAGE_HINT_DECODER, GST_VA_FEATURE_AUTO);
if (!gst_buffer_pool_set_config (pool, config)) {
ret = FALSE;
goto cleanup;
}
}
if (update_allocator)
gst_query_set_nth_allocation_param (query, 0, allocator, &params);
else
gst_query_add_allocation_param (query, allocator, &params);
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_WARNING_OBJECT (base, "We need to copy the output buffer manually "
"because of the top/left alignment, which may have low performance. "
"The element which supports VideoCropMeta such as 'vapostproc' can "
"avoid this.");
base->copy_frames = TRUE;
base->apply_video_crop = TRUE;
cleanup:
if (ret != TRUE)
gst_clear_object (&base->other_pool);
gst_clear_object (&allocator);
gst_clear_object (&other_allocator);
gst_clear_object (&pool);
gst_clear_object (&other_pool);
gst_clear_caps (&va_caps);
return ret;
}
/* We only support system pool and va pool. For va pool, its allocator
* should be va allocator or dma allocator.
* If output caps is memory:VAMemory, the pool should be a va pool
* with va allocator.
* If output caps is memory:DMABuf, the pool should be a va pool
* with dma allocator.
* We may need the other_pool to copy the decoder picture to the
* output buffer. We need to do this copy when:
* 1). The output caps is raw(system mem), but the downstream does
* not support VideoMeta and the strides and offsets of the va pool
* are different from the system memory pool, which means that the
* gst_video_frame_map() can not map the buffer correctly. Then we
* need a va pool with va allocator as an the internal pool and create
* a system pool as the other_pool to copy frames to system mem and
* output it.
* 2). The decoder has crop_top/left value > 0(e.g. the conformance
* window in the H265). Which means that the real output picture
* locates in the middle of the decoded buffer. If the downstream can
* support VideoCropMeta, a VideoCropMeta is added to notify the
* real picture's coordinate and size. But if not, we need to copy
* it manually and the other_pool is needed. We always assume that
* decoded picture starts from top-left corner, and so there is no
* need to do this if crop_bottom/right value > 0.
*
* 1. if crop_top/left value > 0 and the downstream does not support the
* VideoCropMeta, we always have the other_pool to do the copy(The pool
* may be provided by the downstream element, or created by ourself if
* no suitable one found).
* 2. get allocator in query
* 2.1 if allocator is not ours and caps is raw, keep it for other_pool.
* 3. get pool in query
* 3.1 if pool is not va, downstream doesn't support video meta and
* caps are raw, keep it as other_pool.
* 3.2 if there's no pool in query and and caps is raw, create other_pool
* as GstVideoPool with the non-va from query and query's params.
* 4. create our allocator and pool if they aren't in query
* 5. add or update pool and allocator in query
* 6. set our custom pool configuration
*/
static gboolean
gst_va_base_dec_decide_allocation (GstVideoDecoder * decoder, GstQuery * query)
{
GstAllocator *allocator = NULL, *other_allocator = NULL;
GstAllocationParams other_params, params;
GstBufferPool *pool = NULL, *other_pool = NULL;
GstCaps *caps = NULL;
GstVideoInfo info;
GstVaBaseDec *base = GST_VA_BASE_DEC (decoder);
guint size = 0, min, max;
gboolean update_pool = FALSE, update_allocator = FALSE;
gboolean has_videometa, has_video_crop_meta;
gboolean ret = TRUE;
g_assert (base->min_buffers > 0);
gst_query_parse_allocation (query, &caps, NULL);
if (!(caps && gst_video_info_from_caps (&info, caps)))
goto wrong_caps;
has_videometa = gst_query_find_allocation_meta (query,
GST_VIDEO_META_API_TYPE, NULL);
has_video_crop_meta = has_videometa && gst_query_find_allocation_meta (query,
GST_VIDEO_CROP_META_API_TYPE, NULL);
/* 1. The output picture locates in the middle of the decoded buffer,
but the downstream element does not support VideoCropMeta, we
definitely need a copy.
2. Some codec such as H265, it does not clean the DPB when new SPS
comes. The new SPS may set the crop window to top-left corner and
so no video crop is needed here. But we may still have cached frames
in DPB which need a copy. */
if ((_need_video_crop (base) && !has_video_crop_meta) ||
base->apply_video_crop) {
return _decide_allocation_for_video_crop (decoder, query, caps, &info);
}
if (gst_query_get_n_allocation_params (query) > 0) {
gst_query_parse_nth_allocation_param (query, 0, &allocator, &other_params);
if (allocator && !(GST_IS_VA_DMABUF_ALLOCATOR (allocator)
|| GST_IS_VA_ALLOCATOR (allocator))) {
/* save the allocator for the other pool */
other_allocator = allocator;
allocator = NULL;
}
update_allocator = TRUE;
} else {
gst_allocation_params_init (&other_params);
}
gst_allocation_params_init (&params);
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)) {
GST_DEBUG_OBJECT (base,
"may need other pool for copy frames %" GST_PTR_FORMAT, pool);
other_pool = pool;
pool = NULL;
}
}
min += base->min_buffers;
size = MAX (size, GST_VIDEO_INFO_SIZE (&info));
update_pool = TRUE;
} else {
size = GST_VIDEO_INFO_SIZE (&info);
min = base->min_buffers;
max = 0;
}
if (!allocator) {
if (!(allocator = _create_allocator (base, caps))) {
ret = FALSE;
goto cleanup;
}
}
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, &params);
gst_buffer_pool_config_add_option (config,
GST_BUFFER_POOL_OPTION_VIDEO_META);
if (base->need_valign)
gst_buffer_pool_config_set_va_alignment (config, &base->valign);
gst_buffer_pool_config_set_va_allocation_params (config,
VA_SURFACE_ATTRIB_USAGE_HINT_DECODER, GST_VA_FEATURE_AUTO);
if (!gst_buffer_pool_set_config (pool, config)) {
ret = FALSE;
goto cleanup;
}
}
if (update_allocator)
gst_query_set_nth_allocation_param (query, 0, allocator, &params);
else
gst_query_add_allocation_param (query, allocator, &params);
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);
base->copy_frames = (!has_videometa && gst_va_pool_requires_video_meta (pool)
&& gst_caps_is_raw (caps));
if (base->copy_frames) {
if (other_pool) {
gst_object_replace ((GstObject **) & base->other_pool,
(GstObject *) other_pool);
} else {
_create_other_pool (base, other_allocator, &other_params, caps, size);
}
GST_DEBUG_OBJECT (base, "Use the other pool for copy %" GST_PTR_FORMAT,
base->other_pool);
} else {
gst_clear_object (&base->other_pool);
}
cleanup:
gst_clear_object (&allocator);
gst_clear_object (&other_allocator);
gst_clear_object (&pool);
gst_clear_object (&other_pool);
/* There's no need to chain decoder's method since all what is
* needed is done. */
return ret;
wrong_caps:
{
GST_WARNING_OBJECT (base, "No valid caps");
return FALSE;
}
}
static void
gst_va_base_dec_set_context (GstElement * element, GstContext * context)
{
GstVaDisplay *old_display, *new_display;
GstVaBaseDec *base = GST_VA_BASE_DEC (element);
GstVaBaseDecClass *klass = GST_VA_BASE_DEC_GET_CLASS (base);
gboolean ret;
old_display = base->display ? gst_object_ref (base->display) : NULL;
ret = gst_va_handle_set_context (element, context, klass->render_device_path,
&base->display);
new_display = base->display ? gst_object_ref (base->display) : NULL;
if (!ret
|| (old_display && new_display && old_display != new_display
&& base->decoder)) {
GST_ELEMENT_WARNING (base, RESOURCE, BUSY,
("Can't replace VA display while operating"), (NULL));
}
gst_clear_object (&old_display);
gst_clear_object (&new_display);
GST_ELEMENT_CLASS (GST_VA_BASE_DEC_GET_PARENT_CLASS
(element))->set_context (element, context);
}
void
gst_va_base_dec_init (GstVaBaseDec * base, GstDebugCategory * cat)
{
base->debug_category = cat;
}
void
gst_va_base_dec_class_init (GstVaBaseDecClass * klass, GstVaCodecs codec,
const gchar * render_device_path, GstCaps * sink_caps, GstCaps * src_caps,
GstCaps * doc_src_caps, GstCaps * doc_sink_caps)
{
GstPadTemplate *sink_pad_templ, *src_pad_templ;
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
GstVideoDecoderClass *decoder_class = GST_VIDEO_DECODER_CLASS (klass);
klass->parent_decoder_class = g_type_class_peek_parent (klass);
klass->codec = codec;
klass->render_device_path = g_strdup (render_device_path);
sink_pad_templ = gst_pad_template_new ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
sink_caps);
gst_element_class_add_pad_template (element_class, sink_pad_templ);
if (doc_sink_caps) {
gst_pad_template_set_documentation_caps (sink_pad_templ, doc_sink_caps);
gst_caps_unref (doc_sink_caps);
}
src_pad_templ = gst_pad_template_new ("src", GST_PAD_SRC, GST_PAD_ALWAYS,
src_caps);
gst_element_class_add_pad_template (element_class, src_pad_templ);
if (doc_src_caps) {
gst_pad_template_set_documentation_caps (src_pad_templ, doc_src_caps);
gst_caps_unref (doc_src_caps);
}
object_class->get_property = gst_va_base_dec_get_property;
element_class->set_context = GST_DEBUG_FUNCPTR (gst_va_base_dec_set_context);
decoder_class->open = GST_DEBUG_FUNCPTR (gst_va_base_dec_open);
decoder_class->close = GST_DEBUG_FUNCPTR (gst_va_base_dec_close);
decoder_class->stop = GST_DEBUG_FUNCPTR (gst_va_base_dec_stop);
decoder_class->getcaps = GST_DEBUG_FUNCPTR (gst_va_base_dec_getcaps);
decoder_class->src_query = GST_DEBUG_FUNCPTR (gst_va_base_dec_src_query);
decoder_class->sink_query = GST_DEBUG_FUNCPTR (gst_va_base_dec_sink_query);
decoder_class->decide_allocation =
GST_DEBUG_FUNCPTR (gst_va_base_dec_decide_allocation);
g_object_class_install_property (object_class, GST_VA_DEC_PROP_DEVICE_PATH,
g_param_spec_string ("device-path", "Device Path",
"DRM device path", NULL, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
}
/* XXX: if chroma has not an available format, the first format is
* returned, relying on an hypothetical internal CSC */
static GstVideoFormat
_find_video_format_from_chroma (const GValue * formats, guint chroma_type)
{
GstVideoFormat fmt;
guint i, num_values;
if (!formats)
return GST_VIDEO_FORMAT_UNKNOWN;
if (G_VALUE_HOLDS_STRING (formats)) {
return gst_video_format_from_string (g_value_get_string (formats));
} else if (GST_VALUE_HOLDS_LIST (formats)) {
GValue *val, *first_val = NULL;
num_values = gst_value_list_get_size (formats);
for (i = 0; i < num_values; i++) {
val = (GValue *) gst_value_list_get_value (formats, i);
if (!val)
continue;
if (!first_val)
first_val = val;
fmt = gst_video_format_from_string (g_value_get_string (val));
if (gst_va_chroma_from_video_format (fmt) == chroma_type)
return fmt;
}
if (first_val)
return gst_video_format_from_string (g_value_get_string (first_val));
}
return GST_VIDEO_FORMAT_UNKNOWN;
}
static GstVideoFormat
_caps_video_format_from_chroma (GstCaps * caps, GstCapsFeatures * features,
guint chroma_type)
{
guint i, num_structures;
GstCapsFeatures *feats;
GstStructure *structure;
const GValue *format;
num_structures = gst_caps_get_size (caps);
for (i = 0; i < num_structures; i++) {
feats = gst_caps_get_features (caps, i);
if (!gst_caps_features_is_equal (feats, features))
continue;
structure = gst_caps_get_structure (caps, i);
format = gst_structure_get_value (structure, "format");
return _find_video_format_from_chroma (format, chroma_type);
}
return GST_VIDEO_FORMAT_UNKNOWN;
}
static GstVideoFormat
_default_video_format_from_chroma (GstVaBaseDec * base,
GstCapsFeatures * features, guint chroma_type)
{
GstCaps *tmpl_caps;
GstVideoFormat ret = GST_VIDEO_FORMAT_UNKNOWN;
tmpl_caps = gst_pad_get_pad_template_caps (GST_VIDEO_DECODER_SRC_PAD (base));
ret = _caps_video_format_from_chroma (tmpl_caps, features, chroma_type);
gst_caps_unref (tmpl_caps);
return ret;
}
/* Check whether the downstream supports VideoMeta; if not, we need to
* fallback to the system memory. */
static gboolean
_downstream_has_video_meta (GstVaBaseDec * base, GstCaps * caps)
{
GstQuery *query;
gboolean ret = FALSE;
query = gst_query_new_allocation (caps, FALSE);
if (gst_pad_peer_query (GST_VIDEO_DECODER_SRC_PAD (base), query))
ret = gst_query_find_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL);
gst_query_unref (query);
return ret;
}
void
gst_va_base_dec_get_preferred_format_and_caps_features (GstVaBaseDec * base,
GstVideoFormat * format, GstCapsFeatures ** capsfeatures)
{
GstCaps *peer_caps, *preferred_caps = NULL;
GstCapsFeatures *features;
GstStructure *structure;
const GValue *v_format;
guint num_structures, i;
gboolean is_any;
g_return_if_fail (base);
/* verify if peer caps is any */
{
peer_caps =
gst_pad_peer_query_caps (GST_VIDEO_DECODER_SRC_PAD (base), NULL);
is_any = gst_caps_is_any (peer_caps);
gst_clear_caps (&peer_caps);
}
peer_caps = gst_pad_get_allowed_caps (GST_VIDEO_DECODER_SRC_PAD (base));
GST_DEBUG_OBJECT (base, "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, GST_CAPS_FEATURE_MEMORY_VA)) {
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)) {
if (capsfeatures)
*capsfeatures = NULL; /* system memory */
if (format) {
*format = _default_video_format_from_chroma (base,
GST_CAPS_FEATURES_MEMORY_SYSTEM_MEMORY, base->rt_format);
}
goto bail;
}
/* Use the first structure/feature is caps because is the
* "preferred" one */
if (capsfeatures) {
features = gst_caps_get_features (preferred_caps, 0);
if (features) {
*capsfeatures = gst_caps_features_copy (features);
if (is_any
&& !gst_caps_features_is_equal (features,
GST_CAPS_FEATURES_MEMORY_SYSTEM_MEMORY)
&& !_downstream_has_video_meta (base, preferred_caps)) {
GST_INFO_OBJECT (base, "Downstream reports ANY caps but without"
" VideoMeta support; fallback to system memory.");
gst_caps_features_free (*capsfeatures);
*capsfeatures = NULL;
}
} else {
*capsfeatures = NULL;
}
if (!*capsfeatures && format) {
*format = _default_video_format_from_chroma (base,
GST_CAPS_FEATURES_MEMORY_SYSTEM_MEMORY, base->rt_format);
goto bail;
}
}
if (!format)
goto bail;
structure = gst_caps_get_structure (preferred_caps, 0);
v_format = gst_structure_get_value (structure, "format");
*format = _find_video_format_from_chroma (v_format, base->rt_format);
bail:
gst_clear_caps (&preferred_caps);
}
static gboolean
_copy_buffer_and_apply_video_crop (GstVaBaseDec * base,
GstVideoFrame * src_frame, GstVideoFrame * dest_frame,
GstVideoCropMeta * video_crop)
{
GstVideoInfo dst_info = dest_frame->info;
dst_info.fps_n = src_frame->info.fps_n;
dst_info.fps_d = src_frame->info.fps_d;
if (base->convert) {
gboolean new_convert = FALSE;
gint x = 0, y = 0, width = 0, height = 0;
const GstStructure *config = gst_video_converter_get_config (base->convert);
if (!gst_structure_get_int (config, GST_VIDEO_CONVERTER_OPT_SRC_X, &x)
|| !gst_structure_get_int (config, GST_VIDEO_CONVERTER_OPT_SRC_Y, &y)
|| !gst_structure_get_int (config, GST_VIDEO_CONVERTER_OPT_SRC_WIDTH,
&width)
|| !gst_structure_get_int (config, GST_VIDEO_CONVERTER_OPT_SRC_HEIGHT,
&height))
new_convert = TRUE;
new_convert |= (video_crop->x != x);
new_convert |= (video_crop->y != y);
new_convert |= (video_crop->width != width);
new_convert |= (video_crop->height != height);
/* No need to check dest, it always has (0,0) -> (width, height) */
if (new_convert)
g_clear_pointer (&base->convert, gst_video_converter_free);
}
if (!base->convert) {
base->convert = gst_video_converter_new (&src_frame->info, &dst_info,
gst_structure_new ("options",
GST_VIDEO_CONVERTER_OPT_DITHER_METHOD,
GST_TYPE_VIDEO_DITHER_METHOD, GST_VIDEO_DITHER_NONE,
GST_VIDEO_CONVERTER_OPT_DITHER_QUANTIZATION,
G_TYPE_UINT, 0,
GST_VIDEO_CONVERTER_OPT_CHROMA_MODE,
GST_TYPE_VIDEO_CHROMA_MODE, GST_VIDEO_CHROMA_MODE_NONE,
GST_VIDEO_CONVERTER_OPT_MATRIX_MODE,
GST_TYPE_VIDEO_MATRIX_MODE, GST_VIDEO_MATRIX_MODE_NONE,
GST_VIDEO_CONVERTER_OPT_SRC_X, G_TYPE_INT, video_crop->x,
GST_VIDEO_CONVERTER_OPT_SRC_Y, G_TYPE_INT, video_crop->y,
GST_VIDEO_CONVERTER_OPT_SRC_WIDTH, G_TYPE_INT, video_crop->width,
GST_VIDEO_CONVERTER_OPT_SRC_HEIGHT, G_TYPE_INT, video_crop->height,
GST_VIDEO_CONVERTER_OPT_DEST_X, G_TYPE_INT, 0,
GST_VIDEO_CONVERTER_OPT_DEST_Y, G_TYPE_INT, 0,
GST_VIDEO_CONVERTER_OPT_DEST_WIDTH, G_TYPE_INT, video_crop->width,
GST_VIDEO_CONVERTER_OPT_DEST_HEIGHT, G_TYPE_INT, video_crop->height,
NULL));
if (!base->convert) {
GST_WARNING_OBJECT (base, "failed to create a video convert");
return FALSE;
}
}
gst_video_converter_frame (base->convert, src_frame, dest_frame);
return TRUE;
}
gboolean
gst_va_base_dec_copy_output_buffer (GstVaBaseDec * base,
GstVideoCodecFrame * codec_frame)
{
GstVideoFrame src_frame;
GstVideoFrame dest_frame;
GstVideoInfo dest_vinfo;
GstVideoInfo *src_vinfo;
GstBuffer *buffer = NULL;
GstVideoCropMeta *video_crop;
GstFlowReturn ret;
g_return_val_if_fail (base && base->output_state, FALSE);
if (!base->other_pool)
return FALSE;
if (!gst_buffer_pool_set_active (base->other_pool, TRUE))
return FALSE;
src_vinfo = &base->output_state->info;
gst_video_info_set_format (&dest_vinfo, GST_VIDEO_INFO_FORMAT (src_vinfo),
base->width, base->height);
ret = gst_buffer_pool_acquire_buffer (base->other_pool, &buffer, NULL);
if (ret != GST_FLOW_OK)
goto fail;
if (!gst_video_frame_map (&src_frame, src_vinfo, 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 (&src_frame);
goto fail;
}
video_crop = gst_buffer_get_video_crop_meta (codec_frame->output_buffer);
if (video_crop) {
if (!_copy_buffer_and_apply_video_crop (base,
&src_frame, &dest_frame, video_crop)) {
gst_video_frame_unmap (&src_frame);
gst_video_frame_unmap (&dest_frame);
GST_ERROR_OBJECT (base, "fail to apply the video crop.");
goto fail;
}
} else {
/* 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) = base->width;
GST_VIDEO_INFO_HEIGHT (&src_frame.info) = base->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:
if (buffer)
gst_buffer_unref (buffer);
GST_ERROR_OBJECT (base, "Failed copy output buffer.");
return FALSE;
}