subtitleoverlay: Handle video sink pad CAPS query earlier

The internal elements are only created when caps on both video and subtitle pads
are known.

Prior to that, a GST_QUERY_CAPS on a video sink pad would just return ANY
instead of giving a hint of what downstream can actually handle and
prefers. This could result in upstream elements (such as decoders) deciding on
chosing (in the best cases) a non-optimal caps or (in the worst case) caps that
couldn't be handled by the elements downstream of subtitleoverlay.

In order to fix that, we assume that all subtitle "elements" handle the subtitle
overlay composition feature/meta and handle `GST_QUERY_CAPS` ourselves if the
internal elements aren't present yet.

Fixes #3176

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5834>
This commit is contained in:
Edward Hervey 2023-12-19 10:23:12 +01:00 committed by Edward Hervey
parent b13ea85140
commit 87bb3a8e2e

View file

@ -55,6 +55,10 @@ GST_DEBUG_CATEGORY_STATIC (subtitle_overlay_debug);
#define IS_VIDEO_CHAIN_IGNORE_ERROR(flow) \
G_UNLIKELY (flow == GST_FLOW_ERROR)
/* We have colorspace conversions so we can accept any known video formats */
#define SUBTITLE_OVERLAY_CAPS GST_VIDEO_CAPS_MAKE (GST_VIDEO_FORMATS_ALL)
static GstStaticCaps sw_template_caps = GST_STATIC_CAPS (SUBTITLE_OVERLAY_CAPS);
static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
@ -1796,6 +1800,186 @@ done:
return ret;
}
/**
* gst_subtitle_overlay_add_feature_and_intersect:
*
* Creates a new #GstCaps containing the (given caps +
* given caps feature) + (given caps intersected by the
* given filter).
*
* Returns: the new #GstCaps
*/
static GstCaps *
gst_subtitle_overlay_add_feature_and_intersect (GstCaps * caps,
const gchar * feature, GstCaps * filter)
{
int i, caps_size;
GstCaps *new_caps;
new_caps = gst_caps_copy (caps);
caps_size = gst_caps_get_size (new_caps);
for (i = 0; i < caps_size; i++) {
GstCapsFeatures *features = gst_caps_get_features (new_caps, i);
if (!gst_caps_features_is_any (features)) {
gst_caps_features_add (features, feature);
}
}
gst_caps_append (new_caps, gst_caps_intersect_full (caps,
filter, GST_CAPS_INTERSECT_FIRST));
return new_caps;
}
/**
* gst_subtitle_overlay_intersect_by_feature:
*
* Creates a new #GstCaps based on the following filtering rule.
*
* For each individual caps contained in given caps, if the
* caps uses the given caps feature, keep a version of the caps
* with the feature and an another one without. Otherwise, intersect
* the caps with the given filter.
*
* Returns: the new #GstCaps
*/
static GstCaps *
gst_subtitle_overlay_intersect_by_feature (GstCaps * caps,
const gchar * feature, GstCaps * filter)
{
int i, caps_size;
GstCaps *new_caps;
new_caps = gst_caps_new_empty ();
caps_size = gst_caps_get_size (caps);
for (i = 0; i < caps_size; i++) {
GstStructure *caps_structure = gst_caps_get_structure (caps, i);
GstCapsFeatures *caps_features =
gst_caps_features_copy (gst_caps_get_features (caps, i));
GstCaps *filtered_caps;
GstCaps *simple_caps =
gst_caps_new_full (gst_structure_copy (caps_structure), NULL);
gst_caps_set_features (simple_caps, 0, caps_features);
if (gst_caps_features_contains (caps_features, feature)) {
gst_caps_append (new_caps, gst_caps_copy (simple_caps));
gst_caps_features_remove (caps_features, feature);
filtered_caps = gst_caps_ref (simple_caps);
} else {
filtered_caps = gst_caps_intersect_full (simple_caps, filter,
GST_CAPS_INTERSECT_FIRST);
}
gst_caps_unref (simple_caps);
gst_caps_append (new_caps, filtered_caps);
}
return new_caps;
}
static GstCaps *
gst_subtitle_overlay_get_videosink_caps (GstSubtitleOverlay * overlay,
GstPad * pad, GstCaps * filter)
{
GstPad *srcpad = overlay->srcpad;
GstCaps *peer_caps = NULL, *caps = NULL, *overlay_filter = NULL;
if (filter) {
/* filter caps + composition feature + filter caps
* filtered by the software caps. */
GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
overlay_filter = gst_subtitle_overlay_add_feature_and_intersect (filter,
GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
gst_caps_unref (sw_caps);
GST_DEBUG_OBJECT (overlay, "overlay filter %" GST_PTR_FORMAT,
overlay_filter);
}
peer_caps = gst_pad_peer_query_caps (srcpad, overlay_filter);
if (overlay_filter)
gst_caps_unref (overlay_filter);
if (peer_caps) {
GST_DEBUG_OBJECT (pad, "peer caps %" GST_PTR_FORMAT, peer_caps);
if (gst_caps_is_any (peer_caps)) {
/* if peer returns ANY caps, return filtered src pad template caps */
caps = gst_caps_copy (gst_pad_get_pad_template_caps (srcpad));
} else {
/* duplicate caps which contains the composition into one version with
* the meta and one without. Filter the other caps by the software caps */
GstCaps *sw_caps = gst_static_caps_get (&sw_template_caps);
caps = gst_subtitle_overlay_intersect_by_feature (peer_caps,
GST_CAPS_FEATURE_META_GST_VIDEO_OVERLAY_COMPOSITION, sw_caps);
gst_caps_unref (sw_caps);
}
gst_caps_unref (peer_caps);
} else {
/* no peer, our padtemplate is enough then */
caps = gst_pad_get_pad_template_caps (pad);
}
if (filter) {
GstCaps *intersection = gst_caps_intersect_full (filter, caps,
GST_CAPS_INTERSECT_FIRST);
gst_caps_unref (caps);
caps = intersection;
}
GST_DEBUG_OBJECT (overlay, "returning %" GST_PTR_FORMAT, caps);
return caps;
}
static gboolean
gst_subtitle_overlay_video_sink_query (GstPad * pad, GstObject * parent,
GstQuery * query)
{
GstSubtitleOverlay *overlay = (GstSubtitleOverlay *) parent;
gboolean res = FALSE;
GST_DEBUG_OBJECT (pad, "query %" GST_PTR_FORMAT, query);
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_CAPS:
{
GstPad *target = gst_ghost_pad_get_target (GST_GHOST_PAD_CAST (pad));
if (target) {
/* If we have a peer, we let the default handler take care of it */
res = gst_pad_query_default (pad, parent, query);
gst_object_unref (target);
} else {
GstCaps *filter, *caps;
/* We haven't instantiated the internal elements yet, we handle it ourselves */
gst_query_parse_caps (query, &filter);
caps = gst_subtitle_overlay_get_videosink_caps (overlay, pad, filter);
gst_query_set_caps_result (query, caps);
gst_caps_unref (caps);
res = TRUE;
}
break;
}
default:
res = gst_pad_query_default (pad, parent, query);
break;
}
return res;
}
static GstFlowReturn
gst_subtitle_overlay_video_sink_chain (GstPad * pad, GstObject * parent,
GstBuffer * buffer)
@ -2023,6 +2207,8 @@ gst_subtitle_overlay_subtitle_sink_query (GstPad * pad, GstObject * parent,
{
gboolean ret;
GST_DEBUG_OBJECT (pad, "got query %" GST_PTR_FORMAT, query);
switch (GST_QUERY_TYPE (query)) {
case GST_QUERY_ACCEPT_CAPS:
{
@ -2078,6 +2264,8 @@ gst_subtitle_overlay_init (GstSubtitleOverlay * self)
gst_object_unref (templ);
gst_pad_set_event_function (self->video_sinkpad,
GST_DEBUG_FUNCPTR (gst_subtitle_overlay_video_sink_event));
gst_pad_set_query_function (self->video_sinkpad,
GST_DEBUG_FUNCPTR (gst_subtitle_overlay_video_sink_query));
gst_pad_set_chain_function (self->video_sinkpad,
GST_DEBUG_FUNCPTR (gst_subtitle_overlay_video_sink_chain));