Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package gstreamer-plugins-base for
openSUSE:Factory checked in at 2026-11-09 16:03:09
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/gstreamer-plugins-base (Old)
and /work/SRC/openSUSE:Factory/.gstreamer-plugins-base.new.3759290 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "gstreamer-plugins-base"
Mon Nov 9 16:03:09 2026 rev:136 rq:1395965 version:1.28.9
Changes:
--------
---
/work/SRC/openSUSE:Factory/gstreamer-plugins-base/gstreamer-plugins-base.changes
2026-10-10 14:59:27.547179151 +0200
+++
/work/SRC/openSUSE:Factory/.gstreamer-plugins-base.new.3759290/gstreamer-plugins-base.changes
2026-11-09 16:03:16.991390774 +0100
@@ -1,0 +2,21 @@
+Fri Nov 6 20:04:14 UTC 2026 - Bjørn Lie <[email protected]>
+
+- Update to version 1.28.9:
+ * appsink: keep queued sticky events across a flush
+ * eagl and vulkan: Remove internal_view from the superview when finalizing
+ * decodebin3 aborts on giostreamsrc + parsebin with ID3-tagged MP3
+ * decodebin3: Crash in GstPad playing a MP3 file
+ * decodebin3: mq_slot_handle_stream_start: assertion failed: (collection)
+ * decodebin3: Null-deref crash in gst_decodebin_input_link_to_slot with
+ Strawberry player
+ * discoverer: Fix use-after-free when a stream reuses its parent
+ * parsebin: Handle lazy stream-id generation
+ * qsv,d3d11,d3d12: Build fixes on latest msys2 environment
+ * samiparse: Drop already parsed text from the HTML context buffer
+ * samiparse: Fix when encountering unclosed tags to prevent slowdown
+ * udmabuf: Allow disabling via env var
+ * video: fix undefined behaviour on empty unregistered user data meta
+ * video: vbi: reject pixel widths narrower than one conversion group
+ * videoconvertscale: Don't scale in the padding described by GstVideoMeta
+
+-------------------------------------------------------------------
Old:
----
gstreamer-1.28.8.tar.xz
New:
----
gstreamer-1.28.9.tar.xz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ gstreamer-plugins-base.spec ++++++
--- /var/tmp/diff_new_pack.gLVYP4/_old 2026-11-09 16:03:18.092436927 +0100
+++ /var/tmp/diff_new_pack.gLVYP4/_new 2026-11-09 16:03:18.094437011 +0100
@@ -20,7 +20,7 @@
%define gst_branch 1.0
Name: gstreamer-plugins-base
-Version: 1.28.8
+Version: 1.28.9
Release: 0
Summary: GStreamer Streaming-Media Framework Plug-Ins
License: GPL-2.0-or-later AND LGPL-2.1-or-later
++++++ _service ++++++
--- /var/tmp/diff_new_pack.gLVYP4/_old 2026-11-09 16:03:18.133438646 +0100
+++ /var/tmp/diff_new_pack.gLVYP4/_new 2026-11-09 16:03:18.137438814 +0100
@@ -4,7 +4,7 @@
<param name="scm">git</param>
<param
name="url">https://gitlab.freedesktop.org/gstreamer/gstreamer.git</param>
<param name="subdir">subprojects/gst-plugins-base</param>
- <param name="revision">1.28.8</param>
+ <param name="revision">1.28.9</param>
<param name="versionformat">@PARENT_TAG@+@TAG_OFFSET@</param>
<param name="versionrewrite-pattern">v?(.*)\+0</param>
<param name="versionrewrite-replacement">\1</param>
++++++ gstreamer-1.28.8.tar.xz -> gstreamer-1.28.9.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/gstreamer-1.28.8/gst/playback/gstdecodebin3.c
new/gstreamer-1.28.9/gst/playback/gstdecodebin3.c
--- old/gstreamer-1.28.8/gst/playback/gstdecodebin3.c 2026-10-08
17:20:32.000000000 +0200
+++ new/gstreamer-1.28.9/gst/playback/gstdecodebin3.c 2026-11-06
13:54:37.000000000 +0100
@@ -3378,7 +3378,13 @@
collection =
find_collection_for_stream (dbin, (gchar *) slot->active_stream_id);
- g_assert (collection);
+ if (G_UNLIKELY (collection == NULL)) {
+ GST_ELEMENT_ERROR (dbin, STREAM, FAILED,
+ ("Stream %s is not part of any known stream collection",
+ slot->active_stream_id),
+ ("pad %s:%s", GST_DEBUG_PAD_NAME (slot->src_pad)));
+ goto beach;
+ }
/* check if all streams are present for that collection. We do it now since
we
* might just have a single stream in the collection */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/gstreamer-1.28.8/gst/playback/gstparsebin.c
new/gstreamer-1.28.9/gst/playback/gstparsebin.c
--- old/gstreamer-1.28.8/gst/playback/gstparsebin.c 2026-10-08
17:20:32.000000000 +0200
+++ new/gstreamer-1.28.9/gst/playback/gstparsebin.c 2026-11-06
13:54:37.000000000 +0100
@@ -3148,9 +3148,17 @@
}
}
- if (chain->parsed) {
- complete = TRUE;
- goto out;
+ if (chain->parsed && chain->endpad) {
+ /* Plugging a parser is not enough to expose: the parser creates its
+ * stream-id lazily, on the first data it pushes, so a pad exposed here
+ * would publish a stream-collection built from an earlier stream-start
+ * and then emit a different one. The ghost mirrors its target's sticky
+ * events, so wait until the pad we are about to publish knows its own
+ * identity; the block probe on the target triggers exposure once it does.
*/
+ gchar *sid = gst_pad_get_stream_id (GST_PAD (chain->endpad));
+
+ complete = (sid != NULL);
+ g_free (sid);
}
out:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/gstreamer-1.28.8/gst/subparse/samiparse.c
new/gstreamer-1.28.9/gst/subparse/samiparse.c
--- old/gstreamer-1.28.8/gst/subparse/samiparse.c 2026-10-08
17:20:32.000000000 +0200
+++ new/gstreamer-1.28.9/gst/subparse/samiparse.c 2026-11-06
13:54:37.000000000 +0100
@@ -23,6 +23,11 @@
#include <string.h>
#include <stdlib.h>
+/* Maximum size kept for an unterminated HTML tag. A dangling '<' swallows
+ * input until the next '>' or '<'; this bounds how much is buffered and
+ * rescanned before the pending tag is dropped. */
+#define MAX_PENDING_TAG_SIZE 4096
+
#define ITALIC_TAG 'i'
#define SPAN_TAG 's'
#define RUBY_TAG 'r'
@@ -599,7 +604,25 @@
gchar *element = NULL;
/* find <blahblah> */
if (!strchr (next, '>')) {
- /* no tag end point. buffer will be process in next time */
+ /* no tag end point. drop what was already parsed, the pending tag
+ * stays buffered until the next text completes it. Truncate the
+ * buffer to a maximum size so that we don't do O(n^2) work
+ * rescanning an ever-growing buffer on subsequent calls. */
+ g_string_erase (ctxt->buf, 0, next - ctxt->buf->str);
+ if (ctxt->buf->len > MAX_PENDING_TAG_SIZE) {
+ /* Restart the pending tag at the next '<' inside the kept window:
+ * the buffer must keep starting with '<', otherwise the text left
+ * in front of it is emitted as caption content, and cutting a
+ * multibyte sequence in half would emit invalid UTF-8. Without a
+ * '<' in the window the pending tag is dropped, which ends the
+ * swallow window after at most MAX_PENDING_TAG_SIZE bytes. */
+ const gchar *p = memchr (ctxt->buf->str + ctxt->buf->len -
+ MAX_PENDING_TAG_SIZE, '<', MAX_PENDING_TAG_SIZE);
+ if (p)
+ g_string_erase (ctxt->buf, 0, p - ctxt->buf->str);
+ else
+ g_string_truncate (ctxt->buf, 0);
+ }
return TRUE;
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/gstreamer-1.28.8/gst/videoconvertscale/gstvideoconvertscale.c
new/gstreamer-1.28.9/gst/videoconvertscale/gstvideoconvertscale.c
--- old/gstreamer-1.28.8/gst/videoconvertscale/gstvideoconvertscale.c
2026-10-08 17:20:32.000000000 +0200
+++ new/gstreamer-1.28.9/gst/videoconvertscale/gstvideoconvertscale.c
2026-11-06 13:54:37.000000000 +0100
@@ -2007,6 +2007,7 @@
if (priv->converter_config_changed ||
!gst_video_info_is_equal (&priv->last_frame_vinfo, &in_frame->info)) {
GstStructure *options;
+ GstVideoInfo converter_in_info = in_frame->info;
if (priv->converter_config)
options = gst_structure_copy (priv->converter_config);
@@ -2022,10 +2023,18 @@
options =
gst_structure_copy (gst_video_converter_get_config (priv->convert));
+ if (!priv->converter_config) {
+ /* The meta may describe padded dimensions. Keep its layout, but use the
+ * visible dimensions from caps to avoid resampling the padding. */
+ converter_in_info.width = filter->in_info.width;
+ converter_in_info.height = filter->in_info.height;
+ }
+
gst_video_converter_free (priv->convert);
priv->convert =
gst_video_convert_scale_create_converter (GST_VIDEO_CONVERT_SCALE
- (filter), &in_frame->info, &filter->out_info, options);
+ (filter), &converter_in_info, &filter->out_info, options);
+ priv->last_frame_vinfo = in_frame->info;
priv->converter_config_changed = FALSE;
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/gstreamer-1.28.8/gst-libs/gst/allocators/gstudmabufallocator.c
new/gstreamer-1.28.9/gst-libs/gst/allocators/gstudmabufallocator.c
--- old/gstreamer-1.28.8/gst-libs/gst/allocators/gstudmabufallocator.c
2026-10-08 17:20:32.000000000 +0200
+++ new/gstreamer-1.28.9/gst-libs/gst/allocators/gstudmabufallocator.c
2026-11-06 13:54:37.000000000 +0100
@@ -29,6 +29,9 @@
* gst_allocator_alloc() method using `memfd_create()` and `UDMABUF_CREATE`.
* Platforms not supporting that (most non-Linux) will always return %NULL.
*
+ * The allocator can be disabled by setting the GST_DISABLE_UDMABUF environment
+ * variable.
+ *
* Since: 1.28
*/
@@ -204,12 +207,15 @@
alloc->mem_type = GST_ALLOCATOR_UDMABUF;
+ self->udmabuf_dev_fd = -1;
#if defined(HAVE_MEMFD_CREATE) && defined(HAVE_LINUX_UDMABUF_H)
- self->udmabuf_dev_fd = open ("/dev/udmabuf", O_RDONLY | O_CLOEXEC, 0);
- if (self->udmabuf_dev_fd == -1)
- GST_WARNING_OBJECT (self,
- "Udmabuf allocator not available, can't open /dev/udmabuf: %s",
- strerror (errno));
+ if (!g_getenv ("GST_DISABLE_UDMABUF")) {
+ self->udmabuf_dev_fd = open ("/dev/udmabuf", O_RDONLY | O_CLOEXEC, 0);
+ if (self->udmabuf_dev_fd == -1)
+ GST_WARNING_OBJECT (self,
+ "Udmabuf allocator not available, can't open /dev/udmabuf: %s",
+ strerror (errno));
+ }
#endif
/* Inherited from GstFdAllocator. Unset as we implement alloc(). */
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/gstreamer-1.28.8/gst-libs/gst/app/gstappsink.c
new/gstreamer-1.28.9/gst-libs/gst/app/gstappsink.c
--- old/gstreamer-1.28.8/gst-libs/gst/app/gstappsink.c 2026-10-08
17:20:32.000000000 +0200
+++ new/gstreamer-1.28.9/gst-libs/gst/app/gstappsink.c 2026-11-06
13:54:37.000000000 +0100
@@ -58,6 +58,11 @@
* is stopped or in the EOS state. You can check for the EOS state with the
* "eos" property or with the gst_app_sink_is_eos() method.
*
+ * Serialized events can be pulled along with the samples with
+ * gst_app_sink_pull_object(). Upstream doesn't send sticky events again after
+ * a flush, so the ones that were queued but not pulled yet are kept, as long
+ * as the pad still has them.
+ *
* The eos signal can also be used to be informed when the EOS state is reached
* to avoid polling.
*/
@@ -1006,8 +1011,31 @@
gst_sample_set_segment (priv->sample, NULL);
}
+typedef struct
+{
+ GQueue *sticky_events;
+ GQueue *requeue;
+} KeepStickyData;
+
+static gboolean
+gst_app_sink_keep_sticky_event (GstPad * pad, GstEvent ** event,
+ gpointer user_data)
+{
+ KeepStickyData *data = user_data;
+ GList *l = g_queue_find (data->sticky_events, *event);
+
+ if (l) {
+ g_queue_unlink (data->sticky_events, l);
+ g_queue_push_tail_link (data->requeue, l);
+ }
+
+ return TRUE;
+}
+
+/* With @sticky_events, the queued sticky events are moved there instead of
+ * being dropped, for the caller to keep the ones the pad still has. */
static void
-gst_app_sink_flush_unlocked (GstAppSink * appsink)
+gst_app_sink_flush_unlocked (GstAppSink * appsink, GQueue * sticky_events)
{
GstMiniObject *obj;
GstAppSinkPrivate *priv = appsink->priv;
@@ -1015,8 +1043,14 @@
GST_DEBUG_OBJECT (appsink, "flush stop appsink");
priv->is_eos = FALSE;
gst_buffer_replace (&priv->preroll_buffer, NULL);
- while ((obj = gst_vec_deque_pop_head (priv->queue)))
+ while ((obj = gst_vec_deque_pop_head (priv->queue))) {
+ if (sticky_events && gst_app_keep_on_flush (obj)) {
+ g_queue_push_tail (sticky_events, obj);
+ continue;
+ }
+
gst_mini_object_unref (obj);
+ }
gst_queue_status_info_reset (&priv->queue_status_info);
@@ -1057,7 +1091,7 @@
priv->flushing = TRUE;
priv->started = FALSE;
priv->wait_status = NOONE_WAITING;
- gst_app_sink_flush_unlocked (appsink);
+ gst_app_sink_flush_unlocked (appsink, NULL);
gst_buffer_replace (&priv->preroll_buffer, NULL);
gst_caps_replace (&priv->preroll_caps, NULL);
gst_caps_replace (&priv->last_caps, NULL);
@@ -1095,6 +1129,9 @@
{
GstAppSink *appsink = GST_APP_SINK_CAST (sink);
GstAppSinkPrivate *priv = appsink->priv;
+ GQueue sticky_events = G_QUEUE_INIT;
+ GQueue requeue = G_QUEUE_INIT;
+ KeepStickyData keep = { &sticky_events, &requeue };
GST_DEBUG_OBJECT (appsink, "%" GST_PTR_FORMAT, event);
@@ -1179,9 +1216,17 @@
case GST_EVENT_FLUSH_STOP:
g_mutex_lock (&priv->mutex);
GST_DEBUG_OBJECT (appsink, "received FLUSH_STOP");
- gst_app_sink_flush_unlocked (appsink);
+ gst_app_sink_flush_unlocked (appsink, &sticky_events);
g_mutex_unlock (&priv->mutex);
+ /* Upstream won't send the queued sticky events again. Keep the ones
+ * the pad still has, in its order: it already dropped the replaced
+ * ones and the ones the flush invalidates. */
+ gst_pad_sticky_events_foreach (GST_BASE_SINK_PAD (sink),
+ gst_app_sink_keep_sticky_event, &keep);
+ g_queue_clear_full (&sticky_events,
+ (GDestroyNotify) gst_mini_object_unref);
+
if (!priv->silent) {
g_object_notify (G_OBJECT (appsink), "dropped");
}
@@ -1195,6 +1240,7 @@
gboolean emit;
Callbacks *callbacks = NULL;
GstAppSinkSimpleCallbacks *simple_callbacks = NULL;
+ GstMiniObject *obj;
gboolean ret;
g_mutex_lock (&priv->mutex);
@@ -1209,6 +1255,13 @@
gst_vec_deque_push_tail (priv->queue, gst_event_ref (event));
gst_queue_status_info_push_event (&priv->queue_status_info);
+ /* Sticky events kept by a FLUSH_STOP go right after it */
+ while ((obj = g_queue_pop_head (&requeue))) {
+ GST_DEBUG_OBJECT (appsink, "keeping %" GST_PTR_FORMAT, obj);
+ gst_vec_deque_push_tail (priv->queue, obj);
+ gst_queue_status_info_push_event (&priv->queue_status_info);
+ }
+
if ((priv->wait_status & APP_WAITING))
g_cond_signal (&priv->cond);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/gstreamer-1.28.8/gst-libs/gst/app/gstappsrc.c
new/gstreamer-1.28.9/gst-libs/gst/app/gstappsrc.c
--- old/gstreamer-1.28.8/gst-libs/gst/app/gstappsrc.c 2026-10-08
17:20:32.000000000 +0200
+++ new/gstreamer-1.28.9/gst-libs/gst/app/gstappsrc.c 2026-11-06
13:54:37.000000000 +0100
@@ -841,17 +841,40 @@
gst_base_src_set_live (GST_BASE_SRC (appsrc), DEFAULT_PROP_IS_LIVE);
}
+/* Not stream-start and caps, as the segment would go out before them */
+static gboolean
+gst_app_src_keep_on_flush (GstMiniObject * obj)
+{
+ return gst_app_keep_on_flush (obj)
+ && GST_EVENT_TYPE (obj) != GST_EVENT_STREAM_START
+ && GST_EVENT_TYPE (obj) != GST_EVENT_CAPS;
+}
+
/* Must be called with priv->mutex */
static void
-gst_app_src_flush_queued (GstAppSrc * src, gboolean retain_last_caps)
+gst_app_src_flush_queued (GstAppSrc * src, gboolean retain_last_caps,
+ gboolean keep_sticky_events)
{
GstMiniObject *obj;
GstAppSrcPrivate *priv = src->priv;
GstCaps *requeue_caps = NULL;
+ GQueue sticky_events = G_QUEUE_INIT;
+
+ /* Nothing sends the queued sticky events again, keep them */
+ while ((obj = gst_vec_deque_pop_head (priv->delayed_events))) {
+ if (keep_sticky_events && gst_app_src_keep_on_flush (obj))
+ g_queue_push_tail (&sticky_events, obj);
+ else
+ gst_mini_object_unref (obj);
+ }
while (!gst_vec_deque_is_empty (priv->queue)) {
obj = gst_vec_deque_pop_head (priv->queue);
if (obj) {
+ if (keep_sticky_events && gst_app_src_keep_on_flush (obj)) {
+ g_queue_push_tail (&sticky_events, obj);
+ continue;
+ }
if (GST_IS_CAPS (obj) && retain_last_caps) {
gst_caps_replace (&requeue_caps, GST_CAPS_CAST (obj));
}
@@ -862,8 +885,9 @@
if (requeue_caps) {
gst_vec_deque_push_tail (priv->queue, requeue_caps);
}
+ while ((obj = g_queue_pop_head (&sticky_events)))
+ gst_vec_deque_push_tail (priv->queue, obj);
- gst_vec_deque_clear (priv->delayed_events);
priv->pushed_buffer = FALSE;
gst_queue_status_info_reset (&priv->queue_status_info);
@@ -896,7 +920,7 @@
callbacks = g_steal_pointer (&priv->callbacks);
if (priv->simple_callbacks)
simple_callbacks = g_steal_pointer (&priv->simple_callbacks);
- gst_app_src_flush_queued (appsrc, FALSE);
+ gst_app_src_flush_queued (appsrc, FALSE, FALSE);
g_mutex_unlock (&priv->mutex);
g_clear_pointer (&callbacks, callbacks_unref);
@@ -1124,7 +1148,7 @@
switch (GST_EVENT_TYPE (event)) {
case GST_EVENT_FLUSH_STOP:
g_mutex_lock (&priv->mutex);
- gst_app_src_flush_queued (appsrc, TRUE);
+ gst_app_src_flush_queued (appsrc, TRUE, TRUE);
priv->is_eos = FALSE;
g_mutex_unlock (&priv->mutex);
@@ -1223,7 +1247,7 @@
priv->flushing = TRUE;
priv->started = FALSE;
priv->posted_latency_msg = FALSE;
- gst_app_src_flush_queued (appsrc, TRUE);
+ gst_app_src_flush_queued (appsrc, TRUE, FALSE);
g_cond_broadcast (&priv->cond);
priv->in = priv->out = priv->dropped = 0;
g_mutex_unlock (&priv->mutex);
@@ -1382,7 +1406,7 @@
if (res) {
GST_DEBUG_OBJECT (appsrc, "flushing queue");
g_mutex_lock (&priv->mutex);
- gst_app_src_flush_queued (appsrc, TRUE);
+ gst_app_src_flush_queued (appsrc, TRUE, TRUE);
gst_segment_copy_into (segment, &priv->last_segment);
gst_segment_copy_into (segment, &priv->current_segment);
priv->pending_custom_segment = FALSE;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/gstreamer-1.28.8/gst-libs/gst/app/gstapputils.c
new/gstreamer-1.28.9/gst-libs/gst/app/gstapputils.c
--- old/gstreamer-1.28.8/gst-libs/gst/app/gstapputils.c 2026-10-08
17:20:32.000000000 +0200
+++ new/gstreamer-1.28.9/gst-libs/gst/app/gstapputils.c 2026-11-06
13:54:37.000000000 +0100
@@ -248,3 +248,14 @@
" buffers, %" GST_TIME_FORMAT, info->queued_bytes,
info->queued_buffers, GST_TIME_ARGS (info->queued_time));
}
+
+/* Whether @obj is a sticky event that a flush doesn't invalidate, as on a
+ * pad: nothing sends it again after the flush. */
+gboolean
+gst_app_keep_on_flush (GstMiniObject * obj)
+{
+ return GST_IS_EVENT (obj) && GST_EVENT_IS_STICKY (obj)
+ && GST_EVENT_TYPE (obj) != GST_EVENT_SEGMENT
+ && GST_EVENT_TYPE (obj) != GST_EVENT_EOS
+ && GST_EVENT_TYPE (obj) != GST_EVENT_STREAM_GROUP_DONE;
+}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/gstreamer-1.28.8/gst-libs/gst/app/gstapputils.h
new/gstreamer-1.28.9/gst-libs/gst/app/gstapputils.h
--- old/gstreamer-1.28.8/gst-libs/gst/app/gstapputils.h 2026-10-08
17:20:32.000000000 +0200
+++ new/gstreamer-1.28.9/gst-libs/gst/app/gstapputils.h 2026-11-06
13:54:37.000000000 +0100
@@ -44,4 +44,6 @@
GstSegment * current_segment, GstSegment * last_segment,
GstObject * log_context);
+gboolean gst_app_keep_on_flush (GstMiniObject * obj);
+
#endif
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/gstreamer-1.28.8/gst-libs/gst/gl/eagl/gstglwindow_eagl.m
new/gstreamer-1.28.9/gst-libs/gst/gl/eagl/gstglwindow_eagl.m
--- old/gstreamer-1.28.8/gst-libs/gst/gl/eagl/gstglwindow_eagl.m
2026-10-08 17:20:32.000000000 +0200
+++ new/gstreamer-1.28.9/gst-libs/gst/gl/eagl/gstglwindow_eagl.m
2026-11-06 13:54:37.000000000 +0100
@@ -115,6 +115,22 @@
{
GstGLWindowEagl *window = GST_GL_WINDOW_EAGL (object);
+ if (window->priv->internal_view) {
+ GstGLUIView *view = (__bridge GstGLUIView *) window->priv->internal_view;
+ /* UIKit teardown must run on the main thread. The block retains the view
+ * until removal, and its weak window reference makes callbacks safe. */
+ if ([NSThread isMainThread]) {
+ [view setGstWindow:NULL];
+ [view removeFromSuperview];
+ } else {
+ dispatch_async (dispatch_get_main_queue (), ^{
+ [view setGstWindow:NULL];
+ [view removeFromSuperview];
+ });
+ }
+ CFRelease (window->priv->internal_view);
+ window->priv->internal_view = NULL;
+ }
if (window->priv->layer)
CFRelease (window->priv->layer);
window->priv->layer = NULL;
@@ -362,7 +378,7 @@
}
@implementation GstGLUIView {
- GstGLWindowEagl * window_eagl;
+ GWeakRef window_eagl;
};
+(Class) layerClass
@@ -370,9 +386,22 @@
return [CAEAGLLayer class];
}
+-(instancetype) initWithFrame:(CGRect) frame
+{
+ self = [super initWithFrame:frame];
+ if (self)
+ g_weak_ref_init (&window_eagl, NULL);
+ return self;
+}
+
+-(void) dealloc
+{
+ g_weak_ref_clear (&window_eagl);
+}
+
-(void) setGstWindow:(GstGLWindowEagl *) window
{
- window_eagl = window;
+ g_weak_ref_set (&window_eagl, window);
}
-(void) didMoveToWindow
@@ -386,13 +415,14 @@
[super layoutSubviews];
[self updateScale];
- if (window_eagl) {
- GstGLWindow *window = GST_GL_WINDOW (window_eagl);
+ GstGLWindow *window = g_weak_ref_get (&window_eagl);
+ if (window) {
GstGLContext *context = gst_gl_window_get_context (window);
if (context) {
gst_gl_window_queue_resize (window);
gst_object_unref (context);
}
+ gst_object_unref (window);
}
}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/gstreamer-1.28.8/gst-libs/gst/gl/meson.build
new/gstreamer-1.28.9/gst-libs/gst/gl/meson.build
--- old/gstreamer-1.28.8/gst-libs/gst/gl/meson.build 2026-10-08
17:20:32.000000000 +0200
+++ new/gstreamer-1.28.9/gst-libs/gst/gl/meson.build 2026-11-06
13:54:37.000000000 +0100
@@ -822,8 +822,10 @@
windows_graphics_h = cc.has_header('windows.graphics.h')
windows_app_dep = cc.find_library('WindowsApp', required: false)
runtimeobject_lib = cc.find_library('runtimeobject', required: false)
+ windows_ui_xaml_h = cc.has_header('windows.ui.xaml.h')
+ windows_ui_xaml_media_dxinterop_h =
cc.has_header('windows.ui.xaml.media.dxinterop.h')
- if windows_graphics_h and windows_app_dep.found() and
runtimeobject_lib.found()
+ if windows_graphics_h and windows_ui_xaml_h and
windows_ui_xaml_media_dxinterop_h and windows_app_dep.found() and
runtimeobject_lib.found()
enabled_gl_winsys += 'winrt'
glconf.set('GST_GL_HAVE_WINDOW_WINRT', 1)
gl_priv_sources += [
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/gstreamer-1.28.8/gst-libs/gst/pbutils/gstdiscoverer.c
new/gstreamer-1.28.9/gst-libs/gst/pbutils/gstdiscoverer.c
--- old/gstreamer-1.28.8/gst-libs/gst/pbutils/gstdiscoverer.c 2026-10-08
17:20:32.000000000 +0200
+++ new/gstreamer-1.28.9/gst-libs/gst/pbutils/gstdiscoverer.c 2026-11-06
13:54:37.000000000 +0100
@@ -1234,6 +1234,7 @@
if (nval == NULL || GST_VALUE_HOLDS_STRUCTURE (nval)) {
GstStructure *st = find_stream_for_node (dc, topology);
+ GstDiscovererStreamInfo *parent_arg = parent;
gboolean add_to_list = TRUE;
if (st) {
@@ -1270,19 +1271,29 @@
/* We sometimes get an extra sub-stream from the parser. If this is
* the case, we just replace the parent caps with this stream's caps
* since they might contain more information */
+ GstDiscovererStreamInfo *sub;
+
gst_caps_replace (&parent->caps, caps);
- parse_stream_topology (dc, st, parent);
+ sub = parse_stream_topology (dc, st, parent);
+ if (sub)
+ gst_discoverer_stream_info_unref (sub);
add_to_list = FALSE;
} else if (child_is_raw_stream (parent->caps, caps)) {
/* This is the "raw" stream corresponding to the parent. This
* contains more information than the parent, tags etc. */
- parse_stream_topology (dc, st, parent);
+ GstDiscovererStreamInfo *sub;
+
+ sub = parse_stream_topology (dc, st, parent);
+ if (sub)
+ gst_discoverer_stream_info_unref (sub);
add_to_list = FALSE;
} else {
GstDiscovererStreamInfo *next = parse_stream_topology (dc, st, NULL);
- res->next = next;
- next->previous = res;
+ if (next) {
+ res->next = next;
+ next->previous = res;
+ }
}
gst_caps_unref (caps);
}
@@ -1292,8 +1303,13 @@
res->stream_number = dc->priv->current_info_stream_count++;
dc->priv->current_info->stream_list =
g_list_append (dc->priv->current_info->stream_list, res);
- } else {
+ } else if (res == parent_arg) {
+ /* collect_information() re-used the caller's parent object: drop the
+ * reference this call acquired on it and report that no standalone
+ * stream info was created. Unreffing a distinct res would drop the
+ * caller's own reference to the parent instead. */
gst_discoverer_stream_info_unref (res);
+ res = NULL;
}
} else if (GST_VALUE_HOLDS_LIST (nval)) {
@@ -1331,6 +1347,8 @@
GST_DEBUG_OBJECT (dc, "%d %" GST_PTR_FORMAT, i, subst);
substream = parse_stream_topology (dc, subst, NULL);
+ if (!substream)
+ continue;
substream->previous = res;
cont->streams =
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/gstreamer-1.28.8/gst-libs/gst/video/video-anc.c
new/gstreamer-1.28.9/gst-libs/gst/video/video-anc.c
--- old/gstreamer-1.28.8/gst-libs/gst/video/video-anc.c 2026-10-08
17:20:32.000000000 +0200
+++ new/gstreamer-1.28.9/gst-libs/gst/video/video-anc.c 2026-11-06
13:54:37.000000000 +0100
@@ -338,6 +338,7 @@
gst_video_vbi_parser_new (GstVideoFormat format, guint32 pixel_width)
{
GstVideoVBIParser *parser;
+ guint min_width;
g_return_val_if_fail (pixel_width > 0, NULL);
@@ -345,16 +346,27 @@
case GST_VIDEO_FORMAT_v210:
parser = g_new0 (GstVideoVBIParser, 1);
parser->bit16 = TRUE;
+ min_width = 6; /* v210 packs 6 pixels per 16-byte word */
break;
case GST_VIDEO_FORMAT_UYVY:
parser = g_new0 (GstVideoVBIParser, 1);
parser->bit16 = FALSE;
+ min_width = 4; /* UYVY lines convert 4 pixels at a time */
break;
default:
GST_WARNING ("Format not supported by GstVideoVBIParser");
return NULL;
}
+ /* Line conversion walks whole pixel groups and indexes the workspace
+ * relative to info.width; a line narrower than one group underflows the
+ * loop bounds. */
+ if (pixel_width < min_width) {
+ GST_WARNING ("Pixel width %u too small for this format", pixel_width);
+ g_free (parser);
+ return NULL;
+ }
+
gst_video_info_init (&parser->info);
if (!gst_video_info_set_format (&parser->info, format, pixel_width, 1)) {
GST_ERROR ("Could not create GstVideoInfo");
@@ -619,6 +631,7 @@
gst_video_vbi_encoder_new (GstVideoFormat format, guint32 pixel_width)
{
GstVideoVBIEncoder *encoder;
+ guint min_width;
g_return_val_if_fail (pixel_width > 0, NULL);
@@ -626,16 +639,27 @@
case GST_VIDEO_FORMAT_v210:
encoder = g_new0 (GstVideoVBIEncoder, 1);
encoder->bit16 = TRUE;
+ min_width = 6; /* v210 packs 6 pixels per 16-byte word */
break;
case GST_VIDEO_FORMAT_UYVY:
encoder = g_new0 (GstVideoVBIEncoder, 1);
encoder->bit16 = FALSE;
+ min_width = 4; /* UYVY lines convert 4 pixels at a time */
break;
default:
GST_WARNING ("Format not supported by GstVideoVBIEncoder");
return NULL;
}
+ /* Line conversion walks whole pixel groups and indexes the workspace
+ * relative to info.width; a line narrower than one group underflows the
+ * loop bounds. */
+ if (pixel_width < min_width) {
+ GST_WARNING ("Pixel width %u too small for this format", pixel_width);
+ g_free (encoder);
+ return NULL;
+ }
+
gst_video_info_init (&encoder->info);
if (!gst_video_info_set_format (&encoder->info, format, pixel_width, 1)) {
GST_ERROR ("Could not create GstVideoInfo");
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/gstreamer-1.28.8/gst-libs/gst/video/video-sei.c
new/gstreamer-1.28.9/gst-libs/gst/video/video-sei.c
--- old/gstreamer-1.28.8/gst-libs/gst/video/video-sei.c 2026-10-08
17:20:32.000000000 +0200
+++ new/gstreamer-1.28.9/gst-libs/gst/video/video-sei.c 2026-11-06
13:54:37.000000000 +0100
@@ -172,13 +172,18 @@
{
GstVideoSEIUserDataUnregisteredMeta *meta;
g_return_val_if_fail (GST_IS_BUFFER (buffer), NULL);
+ g_return_val_if_fail (uuid != NULL, NULL);
+ g_return_val_if_fail (data != NULL || size == 0, NULL);
meta = (GstVideoSEIUserDataUnregisteredMeta *) gst_buffer_add_meta (buffer,
GST_VIDEO_SEI_USER_DATA_UNREGISTERED_META_INFO, NULL);
g_assert (meta != NULL);
memcpy (meta->uuid, uuid, 16);
- meta->data = g_malloc (size);
- memcpy (meta->data, data, size);
+ meta->data = NULL;
+ if (size > 0) {
+ meta->data = g_malloc (size);
+ memcpy (meta->data, data, size);
+ }
meta->size = size;
return meta;
@@ -200,7 +205,13 @@
gst_video_sei_user_data_unregistered_parse_precision_time_stamp
(GstVideoSEIUserDataUnregisteredMeta * user_data, guint8 * status,
guint64 * precision_time_stamp) {
- guint8 *data = user_data->data;
+ guint8 *data;
+
+ g_return_val_if_fail (user_data != NULL, FALSE);
+ g_return_val_if_fail (status != NULL, FALSE);
+ g_return_val_if_fail (precision_time_stamp != NULL, FALSE);
+
+ data = user_data->data;
if (memcmp (user_data->uuid, &H264_MISP_MICROSECTIME, 16) != 0 &&
memcmp (user_data->uuid, &H265_MISP_MICROSECONDS, 16) != 0 &&
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/gstreamer-1.28.8/gst-plugins-base.doap
new/gstreamer-1.28.9/gst-plugins-base.doap
--- old/gstreamer-1.28.8/gst-plugins-base.doap 2026-10-08 17:20:32.000000000
+0200
+++ new/gstreamer-1.28.9/gst-plugins-base.doap 2026-11-06 13:54:37.000000000
+0100
@@ -36,6 +36,16 @@
<release>
<Version>
+ <revision>1.28.9</revision>
+ <branch>1.28</branch>
+ <name></name>
+ <created>2026-11-06</created>
+ <file-release
rdf:resource="https://gstreamer.freedesktop.org/src/gst-plugins-base/gst-plugins-base-1.28.9.tar.xz"
/>
+ </Version>
+ </release>
+
+ <release>
+ <Version>
<revision>1.28.8</revision>
<branch>1.28</branch>
<name></name>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/gstreamer-1.28.8/meson.build
new/gstreamer-1.28.9/meson.build
--- old/gstreamer-1.28.8/meson.build 2026-10-08 17:20:32.000000000 +0200
+++ new/gstreamer-1.28.9/meson.build 2026-11-06 13:54:37.000000000 +0100
@@ -1,5 +1,5 @@
project('gst-plugins-base', 'c',
- version : '1.28.8',
+ version : '1.28.9',
meson_version : '>= 1.4',
default_options : [ 'warning_level=1',
'buildtype=debugoptimized',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/gstreamer-1.28.8/scripts/dist-common-files.py
new/gstreamer-1.28.9/scripts/dist-common-files.py
--- old/gstreamer-1.28.8/scripts/dist-common-files.py 2026-10-08
17:20:32.000000000 +0200
+++ new/gstreamer-1.28.9/scripts/dist-common-files.py 2026-11-06
13:54:37.000000000 +0100
@@ -42,10 +42,14 @@
# Release notes (instead of NEWS) - could also write it out as NEWS.md
print('Copying release notes into dist staging directory ..')
- relnotes_src = os.path.join(source_root, '..', '..', 'release-notes',
major_minor, f'release-notes-{major_minor}.md')
+ relnotes_src = os.path.join(source_root, '..', '..', 'release-notes',
+ major_minor, f'release-notes-{major_minor}.md')
with open(relnotes_src, 'r') as f:
lines = f.readlines()
- if not f'### {project_version}\n' in lines:
+ if not f' - version: "{project_version}"\n' in lines:
+ sys.exit(
+ f'Update {relnotes_src} first, must contain a header entry for
{project_version}')
+ if not f'### {project_version} {{#{project_version}}}\n' in lines:
sys.exit(f'Update {relnotes_src} first, must contain a section for
{project_version}')
if not project_version.endswith('.0'):
found = False
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/gstreamer-1.28.8/tests/check/elements/appsink.c
new/gstreamer-1.28.9/tests/check/elements/appsink.c
--- old/gstreamer-1.28.8/tests/check/elements/appsink.c 2026-10-08
17:20:32.000000000 +0200
+++ new/gstreamer-1.28.9/tests/check/elements/appsink.c 2026-11-06
13:54:37.000000000 +0100
@@ -1087,6 +1087,125 @@
GST_END_TEST;
+static GstEvent *
+pull_event (GstAppSink * app_sink)
+{
+ GstMiniObject *obj = gst_app_sink_try_pull_object (app_sink, 0);
+
+ fail_unless (obj != NULL);
+ fail_unless (GST_IS_EVENT (obj), "expected an event, got %" GST_PTR_FORMAT,
+ obj);
+
+ return GST_EVENT_CAST (obj);
+}
+
+GST_START_TEST (test_sticky_events_kept_on_flush)
+{
+ GstElement *sink;
+ GstAppSink *app_sink;
+ GstTagList *tags;
+ GstEvent *event;
+ GstMiniObject *obj;
+ GstSegment segment;
+ gchar *title = NULL;
+ gint expected_buffer_number = 3;
+
+ sink = setup_appsink ();
+ app_sink = GST_APP_SINK (sink);
+
+ ASSERT_SET_STATE (sink, GST_STATE_PLAYING, GST_STATE_CHANGE_ASYNC);
+
+ /* Queue stream-start, caps, segment, two stream tags for the same slot, a
+ * global tag, two custom sticky events with different names, a custom
+ * non-sticky event, and a buffer, then flush. */
+ push_buffer_with_number (1);
+ tags = gst_tag_list_new (GST_TAG_TITLE, "first", NULL);
+ fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_tag (tags)));
+ tags = gst_tag_list_new (GST_TAG_TITLE, "second", NULL);
+ fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_tag (tags)));
+ tags = gst_tag_list_new (GST_TAG_TITLE, "global", NULL);
+ gst_tag_list_set_scope (tags, GST_TAG_SCOPE_GLOBAL);
+ fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_tag (tags)));
+ fail_unless (gst_pad_push_event (mysrcpad,
+ gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_STICKY,
+ gst_structure_new_empty ("sticky"))));
+ fail_unless (gst_pad_push_event (mysrcpad,
+ gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_STICKY,
+ gst_structure_new_empty ("other-sticky"))));
+ fail_unless (gst_pad_push_event (mysrcpad,
+ gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM,
+ gst_structure_new_empty ("not-sticky"))));
+ push_buffer_with_number (2);
+
+ fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_flush_start ()));
+ fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_flush_stop (TRUE)));
+ ASSERT_SET_STATE (sink, GST_STATE_PLAYING, GST_STATE_CHANGE_ASYNC);
+
+ /* Upstream won't send them again: after the FLUSH_STOP, the sticky events
+ * are still there, the latest one per slot, but not the old segment and
+ * buffers, nor the non-sticky event. Pull them before anything else is
+ * queued, so they must be accounted for to be pulled. */
+ event = pull_event (app_sink);
+ assert_equals_int (GST_EVENT_TYPE (event), GST_EVENT_FLUSH_STOP);
+ gst_event_unref (event);
+
+ event = pull_event (app_sink);
+ assert_equals_int (GST_EVENT_TYPE (event), GST_EVENT_STREAM_START);
+ gst_event_unref (event);
+
+ event = pull_event (app_sink);
+ assert_equals_int (GST_EVENT_TYPE (event), GST_EVENT_CAPS);
+ gst_event_unref (event);
+
+ event = pull_event (app_sink);
+ assert_equals_int (GST_EVENT_TYPE (event), GST_EVENT_TAG);
+ gst_event_parse_tag (event, &tags);
+ fail_unless (gst_tag_list_get_string (tags, GST_TAG_TITLE, &title));
+ assert_equals_string (title, "second");
+ g_free (title);
+ gst_event_unref (event);
+
+ event = pull_event (app_sink);
+ assert_equals_int (GST_EVENT_TYPE (event), GST_EVENT_TAG);
+ gst_event_parse_tag (event, &tags);
+ assert_equals_int (gst_tag_list_get_scope (tags), GST_TAG_SCOPE_GLOBAL);
+ gst_event_unref (event);
+
+ event = pull_event (app_sink);
+ assert_equals_int (GST_EVENT_TYPE (event),
+ GST_EVENT_CUSTOM_DOWNSTREAM_STICKY);
+ fail_unless (gst_event_has_name (event, "sticky"));
+ gst_event_unref (event);
+
+ event = pull_event (app_sink);
+ assert_equals_int (GST_EVENT_TYPE (event),
+ GST_EVENT_CUSTOM_DOWNSTREAM_STICKY);
+ fail_unless (gst_event_has_name (event, "other-sticky"));
+ gst_event_unref (event);
+
+ fail_unless (gst_app_sink_try_pull_object (app_sink, 0) == NULL);
+
+ gst_segment_init (&segment, GST_FORMAT_TIME);
+ fail_unless (gst_pad_push_event (mysrcpad, gst_event_new_segment
(&segment)));
+ push_buffer_with_number (3);
+
+ event = pull_event (app_sink);
+ assert_equals_int (GST_EVENT_TYPE (event), GST_EVENT_SEGMENT);
+ gst_event_unref (event);
+
+ obj = gst_app_sink_try_pull_object (app_sink, 0);
+ fail_unless (GST_IS_SAMPLE (obj));
+ gst_check_buffer_data (gst_sample_get_buffer (GST_SAMPLE_CAST (obj)),
+ &expected_buffer_number, sizeof (gint));
+ gst_mini_object_unref (obj);
+
+ fail_unless (gst_app_sink_try_pull_object (app_sink, 0) == NULL);
+
+ cleanup_appsink (sink);
+}
+
+GST_END_TEST;
+
static gboolean
propose_allocation_cb (GstAppSink * appsink, GstQuery * query,
gpointer callback_data)
@@ -1299,6 +1418,7 @@
tcase_add_test (tc_chain, test_event_paused);
tcase_add_test (tc_chain, test_reverse_stepping);
tcase_add_test (tc_chain, test_caps_before_flush_race_condition);
+ tcase_add_test (tc_chain, test_sticky_events_kept_on_flush);
tcase_add_test (tc_chain, test_query_allocation_callback);
tcase_add_test (tc_chain, test_query_allocation_signals);
tcase_add_test (tc_chain, test_eos_reset);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/gstreamer-1.28.8/tests/check/elements/appsrc.c
new/gstreamer-1.28.9/tests/check/elements/appsrc.c
--- old/gstreamer-1.28.8/tests/check/elements/appsrc.c 2026-10-08
17:20:32.000000000 +0200
+++ new/gstreamer-1.28.9/tests/check/elements/appsrc.c 2026-11-06
13:54:37.000000000 +0100
@@ -1557,6 +1557,145 @@
GST_END_TEST;
+GST_START_TEST (test_appsrc_sticky_events_kept_on_flush)
+{
+ GstHarness *h;
+ GstAppSrc *src;
+ GstCaps *caps;
+ GstEvent *event;
+ gboolean got_sticky = FALSE;
+
+ h = gst_harness_new ("appsrc");
+ src = GST_APP_SRC (h->element);
+ gst_harness_play (h);
+
+ /* Without caps, the events wait while appsrc goes EOS, then flush */
+ fail_unless (gst_element_send_event (h->element,
+ gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_STICKY,
+ gst_structure_new_empty ("sticky"))));
+ fail_unless (gst_element_send_event (h->element,
+ gst_event_new_stream_group_done (1)));
+ fail_unless (gst_element_send_event (h->element,
+ gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM,
+ gst_structure_new_empty ("not-sticky"))));
+ fail_unless_equals_int (gst_app_src_end_of_stream (src), GST_FLOW_OK);
+ while ((event = gst_harness_pull_event (h))) {
+ gboolean eos = GST_EVENT_TYPE (event) == GST_EVENT_EOS;
+
+ gst_event_unref (event);
+ if (eos)
+ break;
+ }
+ fail_unless (gst_element_send_event (h->element,
+ gst_event_new_flush_start ()));
+ fail_unless (gst_element_send_event (h->element,
+ gst_event_new_flush_stop (TRUE)));
+
+ caps = gst_caps_new_empty_simple ("foo/bar");
+ gst_app_src_set_caps (src, caps);
+ gst_caps_unref (caps);
+ fail_unless_equals_int (gst_app_src_push_buffer (src,
+ gst_buffer_new_and_alloc (1)), GST_FLOW_OK);
+ gst_buffer_unref (gst_harness_pull (h));
+
+ /* Nothing sends the sticky event again, it must be kept */
+ while ((event = gst_harness_try_pull_event (h))) {
+ if (gst_event_has_name (event, "sticky"))
+ got_sticky = TRUE;
+ fail_if (gst_event_has_name (event, "not-sticky"));
+ fail_if (GST_EVENT_TYPE (event) == GST_EVENT_STREAM_GROUP_DONE);
+ gst_event_unref (event);
+ }
+ fail_unless (got_sticky);
+
+ gst_harness_teardown (h);
+}
+
+GST_END_TEST;
+
+static GMutex buffer_blocked_mutex;
+static GCond buffer_blocked_cond;
+static gboolean buffer_blocked;
+
+static GstPadProbeReturn
+buffer_blocked_probe (GstPad * pad, GstPadProbeInfo * info, gpointer user_data)
+{
+ g_mutex_lock (&buffer_blocked_mutex);
+ buffer_blocked = TRUE;
+ g_cond_signal (&buffer_blocked_cond);
+ g_mutex_unlock (&buffer_blocked_mutex);
+
+ return GST_PAD_PROBE_OK;
+}
+
+GST_START_TEST (test_appsrc_queued_sticky_events_kept_on_flush)
+{
+ GstHarness *h;
+ GstAppSrc *src;
+ GstPad *srcpad;
+ GstCaps *caps, *current;
+ GstEvent *event;
+ gboolean flushed = FALSE, got_sticky = FALSE;
+ gulong probe;
+
+ h = gst_harness_new ("appsrc");
+ src = GST_APP_SRC (h->element);
+ srcpad = gst_element_get_static_pad (h->element, "src");
+ gst_harness_play (h);
+
+ /* Block the streaming thread on a buffer, so the caps and sticky event
+ * stay queued, then flush */
+ buffer_blocked = FALSE;
+ probe = gst_pad_add_probe (srcpad,
+ GST_PAD_PROBE_TYPE_BLOCK | GST_PAD_PROBE_TYPE_BUFFER,
+ buffer_blocked_probe, NULL, NULL);
+ caps = gst_caps_new_empty_simple ("foo/first");
+ gst_app_src_set_caps (src, caps);
+ gst_caps_unref (caps);
+ fail_unless_equals_int (gst_app_src_push_buffer (src,
+ gst_buffer_new_and_alloc (1)), GST_FLOW_OK);
+ g_mutex_lock (&buffer_blocked_mutex);
+ while (!buffer_blocked)
+ g_cond_wait (&buffer_blocked_cond, &buffer_blocked_mutex);
+ g_mutex_unlock (&buffer_blocked_mutex);
+
+ caps = gst_caps_new_empty_simple ("foo/second");
+ gst_app_src_set_caps (src, caps);
+ fail_unless (gst_element_send_event (h->element,
+ gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_STICKY,
+ gst_structure_new_empty ("sticky"))));
+ fail_unless (gst_element_send_event (h->element,
+ gst_event_new_flush_start ()));
+ gst_pad_remove_probe (srcpad, probe);
+ fail_unless (gst_element_send_event (h->element,
+ gst_event_new_flush_stop (TRUE)));
+
+ fail_unless_equals_int (gst_app_src_push_buffer (src,
+ gst_buffer_new_and_alloc (1)), GST_FLOW_OK);
+ gst_buffer_unref (gst_harness_pull (h));
+
+ /* The sticky event is kept, and goes after the new caps */
+ while ((event = gst_harness_try_pull_event (h))) {
+ if (GST_EVENT_TYPE (event) == GST_EVENT_FLUSH_STOP)
+ flushed = TRUE;
+ if (flushed && GST_EVENT_TYPE (event) == GST_EVENT_CAPS)
+ fail_if (got_sticky);
+ if (gst_event_has_name (event, "sticky"))
+ got_sticky = TRUE;
+ gst_event_unref (event);
+ }
+ fail_unless (got_sticky);
+ current = gst_pad_get_current_caps (h->sinkpad);
+ fail_unless (gst_caps_is_equal (current, caps));
+ gst_caps_unref (current);
+ gst_caps_unref (caps);
+
+ gst_object_unref (srcpad);
+ gst_harness_teardown (h);
+}
+
+GST_END_TEST;
+
enum ExpectedObj
{
EXPECTED_STREAM_START,
@@ -1765,6 +1904,8 @@
tcase_add_test (tc_chain, test_appsrc_send_event_before_buffer);
tcase_add_test (tc_chain, test_appsrc_send_event_before_sample);
tcase_add_test (tc_chain, test_appsrc_send_event_between_caps_buffer);
+ tcase_add_test (tc_chain, test_appsrc_sticky_events_kept_on_flush);
+ tcase_add_test (tc_chain, test_appsrc_queued_sticky_events_kept_on_flush);
if (RUNNING_ON_VALGRIND)
tcase_add_loop_test (tc_chain, test_appsrc_block_deadlock, 0, 5);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/gstreamer-1.28.8/tests/check/elements/subparse.c
new/gstreamer-1.28.9/tests/check/elements/subparse.c
--- old/gstreamer-1.28.8/tests/check/elements/subparse.c 2026-10-08
17:20:32.000000000 +0200
+++ new/gstreamer-1.28.9/tests/check/elements/subparse.c 2026-11-06
13:54:37.000000000 +0100
@@ -21,6 +21,10 @@
# include <config.h>
#endif
+#ifdef HAVE_VALGRIND
+# include <valgrind/valgrind.h>
+#endif
+
#include <gst/check/gstcheck.h>
#include <gst/check/gstharness.h>
@@ -970,6 +974,116 @@
GST_END_TEST;
+/* Per HTML 4.01 tag syntax (which SAMI Sync-block markup follows), a
+ * dangling '<' turns everything up to the next '>' into one tag: the
+ * following <SYNC> is swallowed as part of the garbage tag, not an
+ * error. The parse must complete and simply yield no caption. */
+GST_START_TEST (test_sami_unclosed_tags_swallow)
+{
+ GString *garbage = g_string_new ("<SAMI>\n<BODY>\n");
+ guint i;
+
+ for (i = 0; i < 200; i++)
+ g_string_append (garbage, "<unclosed tag without end\n");
+
+ g_string_append (garbage,
+ " <SYNC Start=5000>\n"
+ " <P Class=CC>\n"
+ " Swallowed caption.\n" "</BODY>\n" "</SAMI>\n");
+
+ setup_subparse ();
+ fail_unless_equals_int (gst_pad_push (mysrcpad,
+ buffer_from_static_string (garbage->str)), GST_FLOW_OK);
+ gst_pad_push_event (mysrcpad, gst_event_new_eos ());
+
+ fail_unless_equals_int (g_list_length (buffers), 0);
+
+ teardown_subparse ();
+ g_string_free (garbage, TRUE);
+}
+
+GST_END_TEST;
+
+/* The dangling '<' swallows at most the parser's pending-tag size cap:
+ * once the pending tag exceeds it and no '<' restarts it within the kept
+ * window, the pending tag is dropped and parsing resumes, so the caption
+ * after the garbage is still emitted. */
+GST_START_TEST (test_sami_unclosed_tags_recovery)
+{
+ GString *garbage = g_string_new ("<SAMI>\n<BODY>\n<x");
+ guint i;
+
+ for (i = 0; i < 5000; i++)
+ g_string_append_c (garbage, 'a');
+ g_string_append (garbage, "\n"
+ " <SYNC Start=5000>\n"
+ " <P Class=CC>\n"
+ " Recovered caption.\n" "</BODY>\n" "</SAMI>\n");
+
+ setup_subparse ();
+ fail_unless_equals_int (gst_pad_push (mysrcpad,
+ buffer_from_static_string (garbage->str)), GST_FLOW_OK);
+ gst_pad_push_event (mysrcpad, gst_event_new_eos ());
+
+ fail_unless_equals_int (g_list_length (buffers), 1);
+ fail_unless_equals_uint64 (GST_BUFFER_TIMESTAMP (buffers->data),
+ 5000 * GST_MSECOND);
+ {
+ GstMapInfo map;
+
+ gst_buffer_map (buffers->data, &map, GST_MAP_READ);
+ fail_unless_equals_int (map.size, strlen ("Recovered caption."));
+ fail_unless (!memcmp (map.data, "Recovered caption.", map.size));
+ gst_buffer_unmap (buffers->data, &map);
+ }
+
+ teardown_subparse ();
+ g_string_free (garbage, TRUE);
+}
+
+GST_END_TEST;
+
+/* Unclosed '<' lines must not make the HTML context buffer grow without
+ * bound nor cause quadratic rescanning: each call must do O(1) work.
+ * The header is sent once, then pure dangling-'<' lines in chunks, so the
+ * context buffer accumulates exactly like the fuzzing reproducer.
+ * Regression test for clusterfuzz-testcase-gst-subparse-5193624668405760. */
+GST_START_TEST (test_sami_unclosed_tags_perf)
+{
+ GString *chunk = g_string_new (NULL);
+ guint i;
+ gint64 start;
+
+ for (i = 0; i < 2000; i++)
+ g_string_append (chunk, "<unclosed\n");
+
+ setup_subparse ();
+ /* header once, so nothing later flushes the pending tag */
+ fail_unless_equals_int (gst_pad_push (mysrcpad,
+ buffer_from_static_string ("<SAMI>\n<BODY>\n")), GST_FLOW_OK);
+ start = g_get_monotonic_time ();
+ /* 500k dangling lines in ~20KB buffers */
+ for (i = 0; i < 250; i++) {
+ fail_unless_equals_int (gst_pad_push (mysrcpad,
+ buffer_from_static_string (chunk->str)), GST_FLOW_OK);
+ }
+ gst_pad_push_event (mysrcpad, gst_event_new_eos ());
+ /* no sync tag was ever reached */
+ fail_unless_equals_int (g_list_length (buffers), 0);
+#ifdef HAVE_VALGRIND
+ /* valgrind slows everything down enough that the wall-clock bound below
+ * is meaningless */
+ if (!RUNNING_ON_VALGRIND)
+#endif
+ fail_unless ((g_get_monotonic_time () - start) < 5 * G_USEC_PER_SEC,
+ "500k unclosed-tag lines took >5s (quadratic rescan / unbounded
buffer?)");
+
+ teardown_subparse ();
+ g_string_free (chunk, TRUE);
+}
+
+GST_END_TEST;
+
GST_START_TEST (test_sami_html_entities)
{
SubParseInputChunk sami_input[] = {
@@ -1065,6 +1179,28 @@
GST_END_TEST;
+GST_START_TEST (test_sami_split_tag)
+{
+ SubParseInputChunk sami_input[] = {
+ {"<SAMI>\n"
+ "<BODY>\n"
+ " <SYNC Start=1000>\n"
+ " <P Class=CC>\n" " This line splits a tag<b\n",
+ 1000 * GST_MSECOND, 2000 * GST_MSECOND,
+ "This line splits a tag\nin the middle."},
+ {"r> in the middle.\n"
+ " <SYNC Start=2000>\n"
+ " <P Class=CC>\n"
+ " Second line.\n" "</BODY>\n" "</SAMI>\n",
+ 2000 * GST_MSECOND, GST_CLOCK_TIME_NONE,
+ "Second line."}
+ };
+
+ do_test (sami_input, G_N_ELEMENTS (sami_input), "pango-markup");
+}
+
+GST_END_TEST;
+
GST_START_TEST (test_lrc)
{
SubParseInputChunk lrc_input[] = {
@@ -1142,10 +1278,14 @@
tcase_add_test (tc_chain, test_dks);
tcase_add_test (tc_chain, test_sami);
tcase_add_test (tc_chain, test_sami_xml_entities);
+ tcase_add_test (tc_chain, test_sami_unclosed_tags_swallow);
+ tcase_add_test (tc_chain, test_sami_unclosed_tags_recovery);
+ tcase_add_test (tc_chain, test_sami_unclosed_tags_perf);
tcase_add_test (tc_chain, test_sami_html_entities);
tcase_add_test (tc_chain, test_sami_bad_entities);
tcase_add_test (tc_chain, test_sami_comment);
tcase_add_test (tc_chain, test_sami_self_contained_tags);
+ tcase_add_test (tc_chain, test_sami_split_tag);
tcase_add_test (tc_chain, test_lrc);
tcase_add_test (tc_chain, test_raw_conversion);
return s;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/gstreamer-1.28.8/tests/check/elements/videoconvert.c
new/gstreamer-1.28.9/tests/check/elements/videoconvert.c
--- old/gstreamer-1.28.8/tests/check/elements/videoconvert.c 2026-10-08
17:20:32.000000000 +0200
+++ new/gstreamer-1.28.9/tests/check/elements/videoconvert.c 2026-11-06
13:54:37.000000000 +0100
@@ -256,6 +256,57 @@
GST_END_TEST;
+static GstBuffer *
+create_black_gray8_2x2_buffer (void)
+{
+ GstBuffer *buffer;
+ GstMapInfo map;
+ gsize offsets[GST_VIDEO_MAX_PLANES] = { 0, 0, 0, 0 };
+ gint strides[GST_VIDEO_MAX_PLANES] = { 2, 0, 0, 0 };
+
+ buffer = gst_buffer_new_allocate (NULL, 4, NULL);
+ fail_unless (gst_buffer_map (buffer, &map, GST_MAP_WRITE));
+ memset (map.data, 0, map.size);
+ gst_buffer_unmap (buffer, &map);
+
+ gst_buffer_add_video_meta_full (buffer, GST_VIDEO_FRAME_FLAG_NONE,
+ GST_VIDEO_FORMAT_GRAY8, 2, 2, 1, offsets, strides);
+
+ return buffer;
+}
+
+/* A GstVideoMeta may describe a buffer that is larger than the negotiated
+ * caps, for example a decoder output buffer carrying its coded size, or any
+ * buffer allocated with a GstVideoAlignment. Without a GstVideoCropMeta the
+ * picture is the top-left region of that buffer, so the padding must be
+ * cropped out and not scaled in. */
+GST_START_TEST (test_videometa_larger_than_caps)
+{
+ GstHarness *h;
+ GstBuffer *padded;
+ GstBuffer *exact;
+
+ h = gst_harness_new ("videoconvertscale");
+ gst_harness_set_src_caps_str (h,
+ "video/x-raw,format=GRAY8,width=2,height=2,framerate=30/1");
+ gst_harness_set_sink_caps_str (h,
+ "video/x-raw,format=RGB,width=2,height=2,framerate=30/1");
+
+ /* The top-left 2x2 region of this 4x4 buffer is black, the padding is not */
+ padded = gst_harness_push_and_pull (h, create_gray8_4x4_buffer ());
+ fail_unless (padded != NULL);
+ exact = gst_harness_push_and_pull (h, create_black_gray8_2x2_buffer ());
+ fail_unless (exact != NULL);
+
+ fail_unless (rgb_buffer_pixels_equal (padded, exact));
+
+ gst_buffer_unref (exact);
+ gst_buffer_unref (padded);
+ gst_harness_teardown (h);
+}
+
+GST_END_TEST;
+
/* Test that runtime converter configuration updates rebuild the converter */
GST_START_TEST (test_converter_config_update)
{
@@ -274,9 +325,11 @@
full_frame = gst_harness_push_and_pull (h, create_gray8_4x4_buffer ());
fail_unless (full_frame != NULL);
+ /* The default source rectangle is the top-left 2x2 region described by the
+ * caps, so configure the opposite corner to tell the two apart. */
config = gst_structure_new ("GstVideoConverter",
- GST_VIDEO_CONVERTER_OPT_SRC_X, G_TYPE_INT, 0,
- GST_VIDEO_CONVERTER_OPT_SRC_Y, G_TYPE_INT, 0,
+ GST_VIDEO_CONVERTER_OPT_SRC_X, G_TYPE_INT, 2,
+ GST_VIDEO_CONVERTER_OPT_SRC_Y, G_TYPE_INT, 2,
GST_VIDEO_CONVERTER_OPT_SRC_WIDTH, G_TYPE_INT, 2,
GST_VIDEO_CONVERTER_OPT_SRC_HEIGHT, G_TYPE_INT, 2, NULL);
g_object_set (h->element, "converter-config", config, NULL);
@@ -299,6 +352,129 @@
GST_END_TEST;
+
+/* A single top field of a 2x4 alternate-interlaced picture, in a buffer whose
+ * GstVideoMeta claims @meta_height rows. Everything past the first two rows
+ * of the field is padding, filled with @pad_value. */
+static GstBuffer *
+create_alternate_gray8_field (gint meta_height, guint8 pad_value)
+{
+ GstBuffer *buffer;
+ GstMapInfo map;
+ gsize offsets[GST_VIDEO_MAX_PLANES] = { 0, 0, 0, 0 };
+ gint strides[GST_VIDEO_MAX_PLANES] = { 2, 0, 0, 0 };
+
+ buffer = gst_buffer_new_allocate (NULL, 2 * meta_height, NULL);
+ fail_unless (gst_buffer_map (buffer, &map, GST_MAP_WRITE));
+ memset (map.data, 0, map.size);
+ if (map.size > 4)
+ memset (map.data + 4, pad_value, map.size - 4);
+ gst_buffer_unmap (buffer, &map);
+
+ gst_buffer_add_video_meta_full (buffer, GST_VIDEO_FRAME_FLAG_NONE,
+ GST_VIDEO_FORMAT_GRAY8, 2, meta_height, 1, offsets, strides);
+ GST_BUFFER_FLAG_SET (buffer, GST_VIDEO_BUFFER_FLAG_TOP_FIELD);
+
+ return buffer;
+}
+
+/* Same as test_videometa_larger_than_caps, but for split-field (alternate)
+ * interlacing, where the caps height is the height of the whole frame while
+ * each buffer only holds a single field. */
+GST_START_TEST (test_videometa_larger_than_caps_alternate)
+{
+ GstHarness *h;
+ GstBuffer *padded;
+ GstBuffer *exact;
+
+ h = gst_harness_new ("videoconvertscale");
+ gst_harness_set_src_caps_str (h,
+ "video/x-raw(format:Interlaced),format=GRAY8,width=2,height=4,"
+ "interlace-mode=alternate,framerate=30/1");
+ gst_harness_set_sink_caps_str (h,
+ "video/x-raw(format:Interlaced),format=RGB,width=2,height=4,"
+ "interlace-mode=alternate,framerate=30/1");
+
+ /* The caps describe 4 rows, so 2 rows per field. The meta describes 6 rows,
+ * so 3 rows per field, making the third row of the field padding. */
+ padded = gst_harness_push_and_pull (h, create_alternate_gray8_field (6,
255));
+ fail_unless (padded != NULL);
+ exact = gst_harness_push_and_pull (h, create_alternate_gray8_field (4, 0));
+ fail_unless (exact != NULL);
+
+ /* Each output buffer contains one 2x2 field; ignore row padding. */
+ fail_unless (rgb_buffer_pixels_equal (padded, exact));
+
+ gst_buffer_unref (exact);
+ gst_buffer_unref (padded);
+ gst_harness_teardown (h);
+}
+
+GST_END_TEST;
+
+static GstBuffer *
+create_nv12_10x8_buffer (gint meta_width, gint meta_height)
+{
+ GstVideoInfo info;
+ GstBuffer *buffer;
+ GstMapInfo map;
+ guint x, y;
+
+ fail_unless (gst_video_info_set_format (&info, GST_VIDEO_FORMAT_NV12,
+ meta_width, meta_height));
+ buffer = gst_buffer_new_allocate (NULL, info.size, NULL);
+ fail_unless (gst_buffer_map (buffer, &map, GST_MAP_WRITE));
+ memset (map.data, 255, map.size);
+ for (y = 0; y < 8; y++)
+ for (x = 0; x < 10; x++)
+ map.data[info.offset[0] + y * info.stride[0] + x] = x + y;
+ for (y = 0; y < 4; y++)
+ for (x = 0; x < 10; x++)
+ map.data[info.offset[1] + y * info.stride[1] + x] = x + y;
+ gst_buffer_unmap (buffer, &map);
+ gst_buffer_add_video_meta_full (buffer, GST_VIDEO_FRAME_FLAG_NONE,
+ GST_VIDEO_FORMAT_NV12, meta_width, meta_height, 2, info.offset,
+ info.stride);
+ return buffer;
+}
+
+/* Padded GstVideoMeta must not shift chroma during NV12 to I420 conversion. */
+GST_START_TEST (test_videometa_larger_than_caps_nv12)
+{
+ GstHarness *h;
+ GstBuffer *padded;
+ GstBuffer *exact;
+ GstVideoInfo info;
+ GstVideoFrame a, b;
+ guint plane, y;
+
+ h = gst_harness_new ("videoconvertscale");
+ gst_harness_set_src_caps_str (h,
+ "video/x-raw,format=NV12,width=10,height=8,framerate=30/1");
+ gst_harness_set_sink_caps_str (h,
+ "video/x-raw,format=I420,width=10,height=8,framerate=30/1");
+ padded = gst_harness_push_and_pull (h, create_nv12_10x8_buffer (16, 16));
+ exact = gst_harness_push_and_pull (h, create_nv12_10x8_buffer (10, 8));
+ fail_unless (padded != NULL && exact != NULL);
+ fail_unless (gst_video_info_set_format (&info, GST_VIDEO_FORMAT_I420, 10,
8));
+ fail_unless (gst_video_frame_map (&a, &info, padded, GST_MAP_READ));
+ fail_unless (gst_video_frame_map (&b, &info, exact, GST_MAP_READ));
+ for (plane = 0; plane < 3; plane++)
+ for (y = 0; y < (plane ? 4 : 8); y++)
+ fail_unless (memcmp ((guint8 *) GST_VIDEO_FRAME_PLANE_DATA (&a, plane) +
+ y * GST_VIDEO_FRAME_PLANE_STRIDE (&a, plane),
+ (guint8 *) GST_VIDEO_FRAME_PLANE_DATA (&b, plane) +
+ y * GST_VIDEO_FRAME_PLANE_STRIDE (&b, plane),
+ plane ? 5 : 10) == 0);
+ gst_video_frame_unmap (&b);
+ gst_video_frame_unmap (&a);
+ gst_buffer_unref (exact);
+ gst_buffer_unref (padded);
+ gst_harness_teardown (h);
+}
+
+GST_END_TEST;
+
static Suite *
videoconvert_suite (void)
{
@@ -310,6 +486,9 @@
tcase_add_test (tc_chain, test_template_formats);
tcase_add_test (tc_chain, test_negotiate_alternate);
tcase_add_test (tc_chain, test_videometa_layout_changes);
+ tcase_add_test (tc_chain, test_videometa_larger_than_caps);
+ tcase_add_test (tc_chain, test_videometa_larger_than_caps_alternate);
+ tcase_add_test (tc_chain, test_videometa_larger_than_caps_nv12);
tcase_add_test (tc_chain, test_converter_config_update);
return s;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/gstreamer-1.28.8/tests/check/libs/discoverer.c
new/gstreamer-1.28.9/tests/check/libs/discoverer.c
--- old/gstreamer-1.28.8/tests/check/libs/discoverer.c 2026-10-08
17:20:32.000000000 +0200
+++ new/gstreamer-1.28.9/tests/check/libs/discoverer.c 2026-11-06
13:54:37.000000000 +0100
@@ -29,7 +29,7 @@
#include <glib/gstdio.h>
#include <glib/gprintf.h>
-static gboolean have_theora, have_ogg;
+static gboolean have_theora, have_ogg, have_mp3_parser;
GST_START_TEST (test_disco_init)
{
@@ -106,6 +106,75 @@
GST_END_TEST;
+/* Minimal MP3 stream (tests/files/mp3-topology-uaf.bin, from OSS-Fuzz
+ * testcase 4764783547842560) whose stream topology contains a nested "next"
+ * node whose caps match its own parent. Parsing that node used to unref the
+ * parent stream info while a caller still held it, crashing when the caller
+ * wrote through the freed pointer. Push-based discovery (appsrc) as in the
+ * fuzzing harness. */
+static void
+push_data_on_source_setup (GstDiscoverer * dc, GstElement * source,
+ gpointer data)
+{
+ GBytes *bytes = data;
+ gsize size;
+ const guint8 *map;
+ GstBuffer *buf;
+ GstFlowReturn ret;
+
+ map = g_bytes_get_data (bytes, &size);
+ buf = gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY,
+ (gpointer) map, size, 0, size, NULL, NULL);
+ g_object_set (G_OBJECT (source), "size", (gint64) size, NULL);
+ g_signal_emit_by_name (G_OBJECT (source), "push-buffer", buf, &ret);
+ gst_buffer_unref (buf);
+}
+
+GST_START_TEST (test_disco_push_nested_same_caps)
+{
+ GError *err = NULL;
+ GstDiscoverer *dc;
+ GstDiscovererInfo *info;
+ gchar *path;
+ gpointer contents;
+ gsize size;
+ GBytes *bytes;
+
+ if (!have_mp3_parser) {
+ GST_INFO ("No mp3 parser, skipping");
+ return;
+ }
+
+ path = g_build_filename (GST_TEST_FILES_PATH, "mp3-topology-uaf.bin", NULL);
+
+ if (!g_file_get_contents (path, (gchar **) & contents, &size, &err)) {
+ g_free (path);
+ fail ("Could not read test file: %s", err->message);
+ }
+ g_free (path);
+ bytes = g_bytes_new_take (contents, size);
+
+ /* high timeout, in case we're running under valgrind */
+ dc = gst_discoverer_new (30 * GST_SECOND, &err);
+ fail_unless (dc != NULL);
+ fail_unless (err == NULL);
+
+ g_signal_connect (dc, "source-setup",
+ (GCallback) push_data_on_source_setup, bytes);
+
+ /* the point of the test is that discovery completes without crashing;
+ * the result itself depends on which plugins are available */
+ info = gst_discoverer_discover_uri (dc, "appsrc://", &err);
+ fail_unless (info != NULL);
+ g_clear_error (&err);
+
+ gst_discoverer_info_unref (info);
+ g_object_unref (dc);
+ g_bytes_unref (bytes);
+}
+
+GST_END_TEST;
+
GST_START_TEST (test_disco_sync)
{
GError *err = NULL;
@@ -394,6 +463,10 @@
"theoradec", GST_VERSION_MAJOR, GST_VERSION_MINOR, 0);
have_ogg = gst_registry_check_feature_version (gst_registry_get (),
"oggdemux", GST_VERSION_MAJOR, GST_VERSION_MINOR, 0);
+ /* the nested-topology reproducer only exercises the topology parser, so
+ * the mp3 parser alone is enough to trigger it */
+ have_mp3_parser = gst_registry_check_feature_version (gst_registry_get (),
+ "mpegaudioparse", GST_VERSION_MAJOR, GST_VERSION_MINOR, 0);
suite_add_tcase (s, tc_chain);
tcase_add_test (tc_chain, test_disco_init);
@@ -403,6 +476,7 @@
tcase_add_test (tc_chain, test_disco_sync_reuse_timeout);
tcase_add_test (tc_chain, test_disco_missing_plugins);
tcase_add_test (tc_chain, test_disco_serializing);
+ tcase_add_test (tc_chain, test_disco_push_nested_same_caps);
tcase_add_test (tc_chain, test_disco_async);
tcase_add_test (tc_chain, test_disco_async_custom_context);
tcase_add_test (tc_chain, test_disco_async_reuse);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/gstreamer-1.28.8/tests/check/libs/videoanc.c
new/gstreamer-1.28.9/tests/check/libs/videoanc.c
--- old/gstreamer-1.28.8/tests/check/libs/videoanc.c 2026-10-08
17:20:32.000000000 +0200
+++ new/gstreamer-1.28.9/tests/check/libs/videoanc.c 2026-11-06
13:54:37.000000000 +0100
@@ -339,6 +339,57 @@
GST_END_TEST;
+/* A line narrower than one conversion group underflows the loop bounds in the
+ * v210/UYVY line converters, corrupting the heap. The parser/encoder must
+ * reject such widths instead. */
+GST_START_TEST (reject_small_pixel_width)
+{
+ /* v210 packs 6 pixels per 16-byte word, UYVY converts 4 pixels at a time */
+ const guint v210_min_width = 6;
+ const guint uyvy_min_width = 4;
+ GstVideoVBIParser *parser;
+ GstVideoVBIEncoder *encoder;
+ guint8 line[64] = { 0, };
+ GstVideoAncillary vanc;
+ guint w;
+
+ /* Below the minimum: rejected, no object leaks back to the caller. */
+ for (w = 1; w < v210_min_width; w++) {
+ fail_if (gst_video_vbi_parser_new (GST_VIDEO_FORMAT_v210, w) != NULL);
+ fail_if (gst_video_vbi_encoder_new (GST_VIDEO_FORMAT_v210, w) != NULL);
+ }
+ for (w = 1; w < uyvy_min_width; w++) {
+ fail_if (gst_video_vbi_parser_new (GST_VIDEO_FORMAT_UYVY, w) != NULL);
+ fail_if (gst_video_vbi_encoder_new (GST_VIDEO_FORMAT_UYVY, w) != NULL);
+ }
+
+ /* At the minimum: accepted, and a full line converts without overrunning the
+ * workspace (empty line yields no ancillary data). */
+ parser = gst_video_vbi_parser_new (GST_VIDEO_FORMAT_v210, v210_min_width);
+ fail_unless (parser != NULL);
+ gst_video_vbi_parser_add_line (parser, line);
+ fail_unless_equals_int (gst_video_vbi_parser_get_ancillary (parser, &vanc),
+ GST_VIDEO_VBI_PARSER_RESULT_DONE);
+ gst_video_vbi_parser_free (parser);
+
+ parser = gst_video_vbi_parser_new (GST_VIDEO_FORMAT_UYVY, uyvy_min_width);
+ fail_unless (parser != NULL);
+ gst_video_vbi_parser_add_line (parser, line);
+ fail_unless_equals_int (gst_video_vbi_parser_get_ancillary (parser, &vanc),
+ GST_VIDEO_VBI_PARSER_RESULT_DONE);
+ gst_video_vbi_parser_free (parser);
+
+ encoder = gst_video_vbi_encoder_new (GST_VIDEO_FORMAT_v210, v210_min_width);
+ fail_unless (encoder != NULL);
+ gst_video_vbi_encoder_free (encoder);
+
+ encoder = gst_video_vbi_encoder_new (GST_VIDEO_FORMAT_UYVY, uyvy_min_width);
+ fail_unless (encoder != NULL);
+ gst_video_vbi_encoder_free (encoder);
+}
+
+GST_END_TEST;
+
static Suite *
gst_videoanc_suite (void)
{
@@ -353,6 +404,8 @@
tcase_add_test (tc, encode_8bit);
tcase_add_test (tc, encode_10bit);
+ tcase_add_test (tc, reject_small_pixel_width);
+
return s;
}
Binary files old/gstreamer-1.28.8/tests/files/mp3-topology-uaf.bin and
new/gstreamer-1.28.9/tests/files/mp3-topology-uaf.bin differ
++++++ gstreamer.obsinfo ++++++
--- /var/tmp/diff_new_pack.gLVYP4/_old 2026-11-09 16:03:18.686461828 +0100
+++ /var/tmp/diff_new_pack.gLVYP4/_new 2026-11-09 16:03:18.690461995 +0100
@@ -1,5 +1,5 @@
name: gstreamer
-version: 1.28.8
-mtime: 1791472832
-commit: 1b2a95a1b72787cda146b46df9605b168e5b683b
+version: 1.28.9
+mtime: 1793969677
+commit: 3ced45da096032bdac8e0922faa492a079c3180e