videodecoder: implement caps query

Refactor the encoder's caps query proxying function to a common place
and use it in the videodecoder to proxy downstream restrictions.

The new function is private to the gstvideo lib.

https://bugzilla.gnome.org/show_bug.cgi?id=741263
This commit is contained in:
Thiago Santos 2014-12-08 16:33:33 -03:00
parent f78a3d9ef2
commit f24075887f
6 changed files with 365 additions and 83 deletions

View file

@ -42,6 +42,7 @@ libgstvideo_@GST_API_VERSION@_la_SOURCES = \
gstvideodecoder.c \ gstvideodecoder.c \
gstvideoencoder.c \ gstvideoencoder.c \
gstvideoutils.c \ gstvideoutils.c \
gstvideoutilsprivate.c \
video-resampler.c \ video-resampler.c \
video-blend.c \ video-blend.c \
video-overlay-composition.c video-overlay-composition.c
@ -78,6 +79,7 @@ libgstvideo_@GST_API_VERSION@include_HEADERS = \
video-overlay-composition.h video-overlay-composition.h
nodist_libgstvideo_@GST_API_VERSION@include_HEADERS = $(built_headers) nodist_libgstvideo_@GST_API_VERSION@include_HEADERS = $(built_headers)
noinst_HEADERS = gstvideoutilsprivate.h
libgstvideo_@GST_API_VERSION@_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) \ libgstvideo_@GST_API_VERSION@_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) \
$(ORC_CFLAGS) $(ORC_CFLAGS)

View file

@ -295,6 +295,7 @@
#include "gstvideodecoder.h" #include "gstvideodecoder.h"
#include "gstvideoutils.h" #include "gstvideoutils.h"
#include "gstvideoutilsprivate.h"
#include <gst/video/video.h> #include <gst/video/video.h>
#include <gst/video/video-event.h> #include <gst/video/video-event.h>
@ -1613,6 +1614,19 @@ gst_video_decoder_sink_query_default (GstVideoDecoder * decoder,
res = klass->propose_allocation (decoder, query); res = klass->propose_allocation (decoder, query);
break; break;
} }
case GST_QUERY_CAPS:{
GstCaps *filter;
GstCaps *result;
gst_query_parse_caps (query, &filter);
result = __gst_video_element_proxy_getcaps (GST_ELEMENT_CAST (decoder),
GST_VIDEO_DECODER_SINK_PAD (decoder),
GST_VIDEO_DECODER_SRC_PAD (decoder), NULL, filter);
gst_query_set_caps_result (query, result);
gst_caps_unref (result);
res = TRUE;
break;
}
default: default:
res = gst_pad_query_default (pad, GST_OBJECT (decoder), query); res = gst_pad_query_default (pad, GST_OBJECT (decoder), query);
break; break;

View file

@ -117,6 +117,7 @@
#include <gst/video/video.h> #include <gst/video/video.h>
#include "gstvideoencoder.h" #include "gstvideoencoder.h"
#include "gstvideoutils.h" #include "gstvideoutils.h"
#include "gstvideoutilsprivate.h"
#include <gst/video/gstvideometa.h> #include <gst/video/gstvideometa.h>
#include <gst/video/gstvideopool.h> #include <gst/video/gstvideopool.h>
@ -679,69 +680,9 @@ GstCaps *
gst_video_encoder_proxy_getcaps (GstVideoEncoder * encoder, GstCaps * caps, gst_video_encoder_proxy_getcaps (GstVideoEncoder * encoder, GstCaps * caps,
GstCaps * filter) GstCaps * filter)
{ {
GstCaps *templ_caps; return __gst_video_element_proxy_getcaps (GST_ELEMENT_CAST (encoder),
GstCaps *allowed; GST_VIDEO_ENCODER_SINK_PAD (encoder),
GstCaps *fcaps, *filter_caps; GST_VIDEO_ENCODER_SRC_PAD (encoder), caps, filter);
gint i, j;
/* Allow downstream to specify width/height/framerate/PAR constraints
* and forward them upstream for video converters to handle
*/
templ_caps =
caps ? gst_caps_ref (caps) :
gst_pad_get_pad_template_caps (encoder->sinkpad);
allowed = gst_pad_get_allowed_caps (encoder->srcpad);
if (!allowed || gst_caps_is_empty (allowed) || gst_caps_is_any (allowed)) {
fcaps = templ_caps;
goto done;
}
GST_LOG_OBJECT (encoder, "template caps %" GST_PTR_FORMAT, templ_caps);
GST_LOG_OBJECT (encoder, "allowed caps %" GST_PTR_FORMAT, allowed);
filter_caps = gst_caps_new_empty ();
for (i = 0; i < gst_caps_get_size (templ_caps); i++) {
GQuark q_name =
gst_structure_get_name_id (gst_caps_get_structure (templ_caps, i));
for (j = 0; j < gst_caps_get_size (allowed); j++) {
const GstStructure *allowed_s = gst_caps_get_structure (allowed, j);
const GValue *val;
GstStructure *s;
s = gst_structure_new_id_empty (q_name);
if ((val = gst_structure_get_value (allowed_s, "width")))
gst_structure_set_value (s, "width", val);
if ((val = gst_structure_get_value (allowed_s, "height")))
gst_structure_set_value (s, "height", val);
if ((val = gst_structure_get_value (allowed_s, "framerate")))
gst_structure_set_value (s, "framerate", val);
if ((val = gst_structure_get_value (allowed_s, "pixel-aspect-ratio")))
gst_structure_set_value (s, "pixel-aspect-ratio", val);
filter_caps = gst_caps_merge_structure (filter_caps, s);
}
}
fcaps = gst_caps_intersect (filter_caps, templ_caps);
gst_caps_unref (filter_caps);
gst_caps_unref (templ_caps);
if (filter) {
GST_LOG_OBJECT (encoder, "intersecting with %" GST_PTR_FORMAT, filter);
filter_caps = gst_caps_intersect (fcaps, filter);
gst_caps_unref (fcaps);
fcaps = filter_caps;
}
done:
gst_caps_replace (&allowed, NULL);
GST_LOG_OBJECT (encoder, "proxy caps %" GST_PTR_FORMAT, fcaps);
return fcaps;
} }
static GstCaps * static GstCaps *

View file

@ -0,0 +1,109 @@
/* GStreamer
* Copyright (C) 2008 David Schleef <ds@schleef.org>
* Copyright (C) 2012 Collabora Ltd.
* Author : Edward Hervey <edward@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <gst/video/video.h>
#include "gstvideoutilsprivate.h"
/**
* __gst_video_element_proxy_getcaps:
* @element: a #GstElement
* @sinkpad: the element's sink #GstPad
* @srcpad: the element's source #GstPad
* @initial_caps: initial caps
* @filter: filter caps
*
* Returns caps that express @initial_caps (or sink template caps if
* @initial_caps == NULL) restricted to resolution/format/...
* combinations supported by downstream elements (e.g. muxers).
*
* Returns: a #GstCaps owned by caller
*/
GstCaps *
__gst_video_element_proxy_getcaps (GstElement * element, GstPad * sinkpad,
GstPad * srcpad, GstCaps * initial_caps, GstCaps * filter)
{
GstCaps *templ_caps;
GstCaps *allowed;
GstCaps *fcaps, *filter_caps;
gint i, j;
/* Allow downstream to specify width/height/framerate/PAR constraints
* and forward them upstream for video converters to handle
*/
templ_caps = initial_caps ? gst_caps_ref (initial_caps) :
gst_pad_get_pad_template_caps (sinkpad);
allowed = gst_pad_get_allowed_caps (srcpad);
if (!allowed || gst_caps_is_empty (allowed) || gst_caps_is_any (allowed)) {
fcaps = templ_caps;
goto done;
}
GST_LOG_OBJECT (element, "template caps %" GST_PTR_FORMAT, templ_caps);
GST_LOG_OBJECT (element, "allowed caps %" GST_PTR_FORMAT, allowed);
filter_caps = gst_caps_new_empty ();
for (i = 0; i < gst_caps_get_size (templ_caps); i++) {
GQuark q_name =
gst_structure_get_name_id (gst_caps_get_structure (templ_caps, i));
for (j = 0; j < gst_caps_get_size (allowed); j++) {
const GstStructure *allowed_s = gst_caps_get_structure (allowed, j);
const GValue *val;
GstStructure *s;
s = gst_structure_new_id_empty (q_name);
if ((val = gst_structure_get_value (allowed_s, "width")))
gst_structure_set_value (s, "width", val);
if ((val = gst_structure_get_value (allowed_s, "height")))
gst_structure_set_value (s, "height", val);
if ((val = gst_structure_get_value (allowed_s, "framerate")))
gst_structure_set_value (s, "framerate", val);
if ((val = gst_structure_get_value (allowed_s, "pixel-aspect-ratio")))
gst_structure_set_value (s, "pixel-aspect-ratio", val);
filter_caps = gst_caps_merge_structure (filter_caps, s);
}
}
fcaps = gst_caps_intersect (filter_caps, templ_caps);
gst_caps_unref (filter_caps);
gst_caps_unref (templ_caps);
if (filter) {
GST_LOG_OBJECT (element, "intersecting with %" GST_PTR_FORMAT, filter);
filter_caps = gst_caps_intersect (fcaps, filter);
gst_caps_unref (fcaps);
fcaps = filter_caps;
}
done:
gst_caps_replace (&allowed, NULL);
GST_LOG_OBJECT (element, "proxy caps %" GST_PTR_FORMAT, fcaps);
return fcaps;
}

View file

@ -0,0 +1,40 @@
/* GStreamer
* Copyright (C) 2008 David Schleef <ds@schleef.org>
* Copyright (C) 2012 Collabora Ltd.
* Author : Edward Hervey <edward@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef __GST_VIDEO_H__
#include <gst/video/video.h>
#endif
#ifndef _GST_VIDEO_UTILS_PRIVATE_H_
#define _GST_VIDEO_UTILS_PRIVATE_H_
#include <gst/gst.h>
G_BEGIN_DECLS
/* Element utility functions */
GstCaps *__gst_video_element_proxy_getcaps (GstElement * element, GstPad * sinkpad,
GstPad * srcpad, GstCaps * initial_caps,
GstCaps * filter);
G_END_DECLS
#endif

View file

@ -27,6 +27,38 @@
#include <gst/video/video.h> #include <gst/video/video.h>
#include <gst/app/app.h> #include <gst/app/app.h>
static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("video/x-raw")
);
#define RESTRICTED_CAPS_WIDTH 800
#define RESTRICTED_CAPS_HEIGHT 600
#define RESTRICTED_CAPS_FPS_N 30
#define RESTRICTED_CAPS_FPS_D 1
static GstStaticPadTemplate sinktemplate_restricted =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("video/x-raw, width=(int)800, height=(int)600,"
" framerate=(fraction)30/1")
);
static GstStaticPadTemplate sinktemplate_with_range =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("video/x-raw, width=(int)[1,800], height=(int)[1,600],"
" framerate=(fraction)[1/1, 30/1]")
);
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("video/x-test-custom")
);
static GstPad *mysrcpad, *mysinkpad; static GstPad *mysrcpad, *mysinkpad;
static GstElement *dec; static GstElement *dec;
static GList *events = NULL; static GList *events = NULL;
@ -181,22 +213,17 @@ _mysinkpad_event (GstPad * pad, GstObject * parent, GstEvent * event)
} }
static void static void
setup_videodecodertester (void) setup_videodecodertester (GstStaticPadTemplate * sinktmpl,
GstStaticPadTemplate * srctmpl)
{ {
static GstStaticPadTemplate sinktemplate = GST_STATIC_PAD_TEMPLATE ("sink", if (sinktmpl == NULL)
GST_PAD_SINK, sinktmpl = &sinktemplate;
GST_PAD_ALWAYS, if (srctmpl == NULL)
GST_STATIC_CAPS ("video/x-raw") srctmpl = &srctemplate;
);
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("video/x-test-custom")
);
dec = g_object_new (GST_VIDEO_DECODER_TESTER_TYPE, NULL); dec = g_object_new (GST_VIDEO_DECODER_TESTER_TYPE, NULL);
mysrcpad = gst_check_setup_src_pad (dec, &srctemplate); mysrcpad = gst_check_setup_src_pad (dec, srctmpl);
mysinkpad = gst_check_setup_sink_pad (dec, &sinktemplate); mysinkpad = gst_check_setup_sink_pad (dec, sinktmpl);
gst_pad_set_event_function (mysinkpad, _mysinkpad_event); gst_pad_set_event_function (mysinkpad, _mysinkpad_event);
} }
@ -259,7 +286,7 @@ GST_START_TEST (videodecoder_playback)
guint64 i; guint64 i;
GList *iter; GList *iter;
setup_videodecodertester (); setup_videodecodertester (NULL, NULL);
gst_pad_set_active (mysrcpad, TRUE); gst_pad_set_active (mysrcpad, TRUE);
gst_element_set_state (dec, GST_STATE_PLAYING); gst_element_set_state (dec, GST_STATE_PLAYING);
@ -321,7 +348,7 @@ GST_START_TEST (videodecoder_playback_with_events)
GList *iter; GList *iter;
GList *events_iter; GList *events_iter;
setup_videodecodertester (); setup_videodecodertester (NULL, NULL);
gst_pad_set_active (mysrcpad, TRUE); gst_pad_set_active (mysrcpad, TRUE);
gst_element_set_state (dec, GST_STATE_PLAYING); gst_element_set_state (dec, GST_STATE_PLAYING);
@ -429,7 +456,7 @@ GST_START_TEST (videodecoder_flush_events)
guint64 i; guint64 i;
GList *events_iter; GList *events_iter;
setup_videodecodertester (); setup_videodecodertester (NULL, NULL);
gst_pad_set_active (mysrcpad, TRUE); gst_pad_set_active (mysrcpad, TRUE);
gst_element_set_state (dec, GST_STATE_PLAYING); gst_element_set_state (dec, GST_STATE_PLAYING);
@ -533,7 +560,7 @@ GST_START_TEST (videodecoder_playback_first_frames_not_decoded)
GstBuffer *buffer; GstBuffer *buffer;
guint64 i = 0; guint64 i = 0;
setup_videodecodertester (); setup_videodecodertester (NULL, NULL);
gst_pad_set_active (mysrcpad, TRUE); gst_pad_set_active (mysrcpad, TRUE);
gst_element_set_state (dec, GST_STATE_PLAYING); gst_element_set_state (dec, GST_STATE_PLAYING);
@ -592,7 +619,7 @@ GST_START_TEST (videodecoder_buffer_after_segment)
GstClockTime pos; GstClockTime pos;
GList *iter; GList *iter;
setup_videodecodertester (); setup_videodecodertester (NULL, NULL);
gst_pad_set_active (mysrcpad, TRUE); gst_pad_set_active (mysrcpad, TRUE);
gst_element_set_state (dec, GST_STATE_PLAYING); gst_element_set_state (dec, GST_STATE_PLAYING);
@ -661,7 +688,7 @@ GST_START_TEST (videodecoder_backwards_playback)
guint64 i; guint64 i;
GList *iter; GList *iter;
setup_videodecodertester (); setup_videodecodertester (NULL, NULL);
gst_pad_set_active (mysrcpad, TRUE); gst_pad_set_active (mysrcpad, TRUE);
gst_element_set_state (dec, GST_STATE_PLAYING); gst_element_set_state (dec, GST_STATE_PLAYING);
@ -744,7 +771,7 @@ GST_START_TEST (videodecoder_backwards_buffer_after_segment)
guint64 i; guint64 i;
GstClockTime pos; GstClockTime pos;
setup_videodecodertester (); setup_videodecodertester (NULL, NULL);
gst_pad_set_active (mysrcpad, TRUE); gst_pad_set_active (mysrcpad, TRUE);
gst_element_set_state (dec, GST_STATE_PLAYING); gst_element_set_state (dec, GST_STATE_PLAYING);
@ -817,6 +844,151 @@ GST_START_TEST (videodecoder_backwards_buffer_after_segment)
GST_END_TEST; GST_END_TEST;
GST_START_TEST (videodecoder_query_caps_with_fixed_caps_peer)
{
GstCaps *caps;
GstCaps *filter;
GstStructure *structure;
gint width, height, fps_n, fps_d;
setup_videodecodertester (&sinktemplate_restricted, NULL);
gst_pad_set_active (mysrcpad, TRUE);
gst_element_set_state (dec, GST_STATE_PLAYING);
gst_pad_set_active (mysinkpad, TRUE);
caps = gst_pad_peer_query_caps (mysrcpad, NULL);
fail_unless (caps != NULL);
structure = gst_caps_get_structure (caps, 0);
fail_unless (gst_structure_get_int (structure, "width", &width));
fail_unless (gst_structure_get_int (structure, "height", &height));
fail_unless (gst_structure_get_fraction (structure, "framerate", &fps_n,
&fps_d));
/* match our restricted caps values */
fail_unless (width == RESTRICTED_CAPS_WIDTH);
fail_unless (height == RESTRICTED_CAPS_HEIGHT);
fail_unless (fps_n == RESTRICTED_CAPS_FPS_N);
fail_unless (fps_d == RESTRICTED_CAPS_FPS_D);
gst_caps_unref (caps);
filter = gst_caps_new_simple ("video/x-custom-test", "width", G_TYPE_INT,
1000, "height", G_TYPE_INT, 1000, "framerate", GST_TYPE_FRACTION,
1000, 1, NULL);
caps = gst_pad_peer_query_caps (mysrcpad, filter);
fail_unless (caps != NULL);
fail_unless (gst_caps_is_empty (caps));
gst_caps_unref (caps);
gst_caps_unref (filter);
cleanup_videodecodertest ();
}
GST_END_TEST;
static void
_get_int_range (GstStructure * s, const gchar * field, gint * min_v,
gint * max_v)
{
const GValue *value;
value = gst_structure_get_value (s, field);
fail_unless (value != NULL);
fail_unless (GST_VALUE_HOLDS_INT_RANGE (value));
*min_v = gst_value_get_int_range_min (value);
*max_v = gst_value_get_int_range_max (value);
}
static void
_get_fraction_range (GstStructure * s, const gchar * field, gint * fps_n_min,
gint * fps_d_min, gint * fps_n_max, gint * fps_d_max)
{
const GValue *value;
const GValue *min_v, *max_v;
value = gst_structure_get_value (s, field);
fail_unless (value != NULL);
fail_unless (GST_VALUE_HOLDS_FRACTION_RANGE (value));
min_v = gst_value_get_fraction_range_min (value);
fail_unless (GST_VALUE_HOLDS_FRACTION (min_v));
*fps_n_min = gst_value_get_fraction_numerator (min_v);
*fps_d_min = gst_value_get_fraction_denominator (min_v);
max_v = gst_value_get_fraction_range_max (value);
fail_unless (GST_VALUE_HOLDS_FRACTION (max_v));
*fps_n_max = gst_value_get_fraction_numerator (max_v);
*fps_d_max = gst_value_get_fraction_denominator (max_v);
}
GST_START_TEST (videodecoder_query_caps_with_range_caps_peer)
{
GstCaps *caps;
GstCaps *filter;
GstStructure *structure;
gint width, height, fps_n, fps_d;
gint width_min, height_min, fps_n_min, fps_d_min;
gint width_max, height_max, fps_n_max, fps_d_max;
setup_videodecodertester (&sinktemplate_with_range, NULL);
gst_pad_set_active (mysrcpad, TRUE);
gst_element_set_state (dec, GST_STATE_PLAYING);
gst_pad_set_active (mysinkpad, TRUE);
caps = gst_pad_peer_query_caps (mysrcpad, NULL);
fail_unless (caps != NULL);
structure = gst_caps_get_structure (caps, 0);
_get_int_range (structure, "width", &width_min, &width_max);
_get_int_range (structure, "height", &height_min, &height_max);
_get_fraction_range (structure, "framerate", &fps_n_min, &fps_d_min,
&fps_n_max, &fps_d_max);
fail_unless (width_min == 1);
fail_unless (width_max == RESTRICTED_CAPS_WIDTH);
fail_unless (height_min == 1);
fail_unless (height_max == RESTRICTED_CAPS_HEIGHT);
fail_unless (fps_n_min == 1);
fail_unless (fps_d_min == 1);
fail_unless (fps_n_max == RESTRICTED_CAPS_FPS_N);
fail_unless (fps_d_max == RESTRICTED_CAPS_FPS_D);
gst_caps_unref (caps);
/* query with a fixed filter */
filter = gst_caps_new_simple ("video/x-test-custom", "width", G_TYPE_INT,
RESTRICTED_CAPS_WIDTH, "height", G_TYPE_INT, RESTRICTED_CAPS_HEIGHT,
"framerate", GST_TYPE_FRACTION, RESTRICTED_CAPS_FPS_N,
RESTRICTED_CAPS_FPS_D, NULL);
caps = gst_pad_peer_query_caps (mysrcpad, filter);
fail_unless (caps != NULL);
structure = gst_caps_get_structure (caps, 0);
fail_unless (gst_structure_get_int (structure, "width", &width));
fail_unless (gst_structure_get_int (structure, "height", &height));
fail_unless (gst_structure_get_fraction (structure, "framerate", &fps_n,
&fps_d));
fail_unless (width == RESTRICTED_CAPS_WIDTH);
fail_unless (height == RESTRICTED_CAPS_HEIGHT);
fail_unless (fps_n == RESTRICTED_CAPS_FPS_N);
fail_unless (fps_d == RESTRICTED_CAPS_FPS_D);
gst_caps_unref (caps);
gst_caps_unref (filter);
/* query with a fixed filter that will lead to empty result */
filter = gst_caps_new_simple ("video/x-test-custom", "width", G_TYPE_INT,
1000, "height", G_TYPE_INT, 1000, "framerate", GST_TYPE_FRACTION,
1000, 1, NULL);
caps = gst_pad_peer_query_caps (mysrcpad, filter);
fail_unless (caps != NULL);
fail_unless (gst_caps_is_empty (caps));
gst_caps_unref (caps);
gst_caps_unref (filter);
cleanup_videodecodertest ();
}
GST_END_TEST;
static Suite * static Suite *
gst_videodecoder_suite (void) gst_videodecoder_suite (void)
{ {
@ -824,6 +996,10 @@ gst_videodecoder_suite (void)
TCase *tc = tcase_create ("general"); TCase *tc = tcase_create ("general");
suite_add_tcase (s, tc); suite_add_tcase (s, tc);
tcase_add_test (tc, videodecoder_query_caps_with_fixed_caps_peer);
tcase_add_test (tc, videodecoder_query_caps_with_range_caps_peer);
tcase_add_test (tc, videodecoder_playback); tcase_add_test (tc, videodecoder_playback);
tcase_add_test (tc, videodecoder_playback_with_events); tcase_add_test (tc, videodecoder_playback_with_events);
tcase_add_test (tc, videodecoder_playback_first_frames_not_decoded); tcase_add_test (tc, videodecoder_playback_first_frames_not_decoded);