From 2ad501aa518b2358cd6df90f98888f3598123e65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 8 Sep 2011 14:46:23 +0200 Subject: [PATCH 01/43] decodebin: Update design documentation about how Parser/Converter are handled --- docs/design/design-decodebin.txt | 34 +++++++------------------------- 1 file changed, 7 insertions(+), 27 deletions(-) diff --git a/docs/design/design-decodebin.txt b/docs/design/design-decodebin.txt index 07cd3d4ac8..957014f21b 100644 --- a/docs/design/design-decodebin.txt +++ b/docs/design/design-decodebin.txt @@ -264,31 +264,11 @@ caps (which would contain the relevant capabilities/restrictions such as supported profiles, resolutions, etc.), after the usual "autoplug-*" signal filtering/sorting of course. -This could be done in multiple ways, e.g. +This is done by plugging a capsfilter element right after the parser, and +constructing set of filter caps from the list of available decoders (one +appends at the end just the name(s) of the caps structures from the parser +pad template caps to function as an 'ANY other' caps equivalent). This let +the parser negotiate to a supported stream format in the same way as with +the static pipeline mentioned above, but of course incur some overhead +through the additional capsfilter element. - - plug a capsfilter element right after the parser, and construct - a set of filter caps from the list of available decoders (one - could append at the end just the name(s) of the caps structures - from the parser pad template caps to function as an 'ANY other' - caps equivalent). This would let the parser negotiate to a - supported stream format in the same way as with the static - pipeline mentioned above, but of course incur some overhead - through the additional capsfilter element. - - - one could add a filter-caps equivalent property to the parsers - (and/or GstBaseParse class) (e.g. "prefered-caps" or so). - - - one could add some kind of "fixate-caps" or "fixate-format" - signal to such parsers - -Alternatively, one could simply make all decoders incorporate parsers, so -that always all formats are supported. This is problematic for other reasons -though (e.g. we would not be able to detect the profile in all cases then -before plugging a decoder, which would make it hard to just play the audio -part of a stream and not the video if a suitable decoder was missing, for -example). - -Additional considerations: the same problem exists with sinks that support -non-raw formats. Consider, for example, an audio sink that accepts DTS audio, -but only the 14-bit variant, not the 16-bit variant (or only native endiannes). -Ideally dcaparse would convert into the required stream format here. From 25939e02183e9a9420d02c3f2c3ddcd3fe83cd65 Mon Sep 17 00:00:00 2001 From: Nicolas Dufresne Date: Wed, 31 Aug 2011 20:51:17 -0400 Subject: [PATCH 02/43] subparse: Improve subrip type check regex This patch prevents timestamp like "1 1:00:00", which would have been seen as hour 101 by our parser, and allow single digit hour, minute and seconds as it's already supported by the parser, and also by other implementation like in mplayer. This fixes bug 657872. https://bugzilla.gnome.org/show_bug.cgi?id=657872 --- gst/subparse/gstsubparse.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gst/subparse/gstsubparse.c b/gst/subparse/gstsubparse.c index 8d495f01b0..240ef587e5 100644 --- a/gst/subparse/gstsubparse.c +++ b/gst/subparse/gstsubparse.c @@ -1246,9 +1246,9 @@ gst_sub_parse_data_format_autodetect_regex_once (GstSubParseRegex regtype) } break; case GST_SUB_PARSE_REGEX_SUBRIP: - result = (gpointer) g_regex_new ("^([ 0-9]){0,3}[0-9]\\s*(\x0d)?\x0a" - "[ 0-9][0-9]:[ 0-9][0-9]:[ 0-9][0-9][,.][ 0-9]{0,2}[0-9]" - " +--> +([ 0-9])?[0-9]:[ 0-9][0-9]:[ 0-9][0-9][,.][ 0-9]{0,2}[0-9]", + result = (gpointer) g_regex_new ("^ {0,3}[ 0-9]{1,4}\\s*(\x0d)?\x0a" + " ?[0-9]{1,2}: ?[0-9]{1,2}: ?[0-9]{1,2}[,.] {0,2}[0-9]{1,3}" + " +--> +[0-9]{1,2}: ?[0-9]{1,2}: ?[0-9]{1,2}[,.] {0,2}[0-9]{1,2}", G_REGEX_RAW | G_REGEX_OPTIMIZE, 0, &gerr); if (result == NULL) { g_warning ("Compilation of subrip regex failed: %s", gerr->message); From 4095551b3182453a435aecd4c4e605dd27180f1b Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Fri, 9 Sep 2011 12:07:44 +0100 Subject: [PATCH 03/43] typefind: recognize Asylum modules Note that there is already a AMF detection for a different magic, I'm not sure if that's a different format with the same initials or not. AMF is used for a few different formats (including video), so... This fixes playbin2 playing Asylum modules. https://bugzilla.gnome.org/show_bug.cgi?id=658514 --- gst/typefind/gsttypefindfunctions.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/gst/typefind/gsttypefindfunctions.c b/gst/typefind/gsttypefindfunctions.c index 5d3d95cbef..62d398466c 100644 --- a/gst/typefind/gsttypefindfunctions.c +++ b/gst/typefind/gsttypefindfunctions.c @@ -2888,6 +2888,13 @@ mod_type_find (GstTypeFind * tf, gpointer unused) return; } } + /* AMF */ + if ((data = gst_type_find_peek (tf, 0, 19)) != NULL) { + if (memcmp (data, "ASYLUM Music Format", 19) == 0) { + gst_type_find_suggest (tf, GST_TYPE_FIND_MAXIMUM, MOD_CAPS); + return; + } + } } /*** application/x-shockwave-flash ***/ From 0f38f86182dfcac86d7920d880e2631e024d8b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 9 Sep 2011 13:07:57 +0100 Subject: [PATCH 04/43] colorbalance: add some guards to interface methods https://bugzilla.gnome.org/show_bug.cgi?id=658584 --- gst-libs/gst/interfaces/colorbalance.c | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/gst-libs/gst/interfaces/colorbalance.c b/gst-libs/gst/interfaces/colorbalance.c index 60268acae3..0bf52a915d 100644 --- a/gst-libs/gst/interfaces/colorbalance.c +++ b/gst-libs/gst/interfaces/colorbalance.c @@ -124,7 +124,11 @@ gst_color_balance_class_init (GstColorBalanceClass * klass) const GList * gst_color_balance_list_channels (GstColorBalance * balance) { - GstColorBalanceClass *klass = GST_COLOR_BALANCE_GET_CLASS (balance); + GstColorBalanceClass *klass; + + g_return_val_if_fail (GST_IS_COLOR_BALANCE (balance), NULL); + + klass = GST_COLOR_BALANCE_GET_CLASS (balance); if (klass->list_channels) { return klass->list_channels (balance); @@ -175,7 +179,11 @@ gint gst_color_balance_get_value (GstColorBalance * balance, GstColorBalanceChannel * channel) { - GstColorBalanceClass *klass = GST_COLOR_BALANCE_GET_CLASS (balance); + GstColorBalanceClass *klass; + + g_return_val_if_fail (GST_IS_COLOR_BALANCE (balance), 0); + + klass = GST_COLOR_BALANCE_GET_CLASS (balance); if (klass->get_value) { return klass->get_value (balance, channel); @@ -197,7 +205,12 @@ gst_color_balance_get_value (GstColorBalance * balance, GstColorBalanceType gst_color_balance_get_balance_type (GstColorBalance * balance) { - GstColorBalanceClass *klass = GST_COLOR_BALANCE_GET_CLASS (balance); + GstColorBalanceClass *klass; + + g_return_val_if_fail (GST_IS_COLOR_BALANCE (balance), + GST_COLOR_BALANCE_SOFTWARE); + + klass = GST_COLOR_BALANCE_GET_CLASS (balance); return klass->balance_type; } @@ -217,6 +230,9 @@ void gst_color_balance_value_changed (GstColorBalance * balance, GstColorBalanceChannel * channel, gint value) { + + g_return_if_fail (GST_IS_COLOR_BALANCE (balance)); + g_signal_emit (G_OBJECT (balance), gst_color_balance_signals[VALUE_CHANGED], 0, channel, value); From 76ed3fb04d55839176a7b546d876efbc946f4419 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 9 Sep 2011 13:10:13 +0100 Subject: [PATCH 05/43] docs: fix some typos in the decodebin design document --- docs/design/design-decodebin.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/design/design-decodebin.txt b/docs/design/design-decodebin.txt index 957014f21b..9d74f71152 100644 --- a/docs/design/design-decodebin.txt +++ b/docs/design/design-decodebin.txt @@ -255,11 +255,11 @@ for baseline profile, and software fallback for main/high profile; or a DSP codec only supporting certain resolutions, with a software fallback for unusual resolutions). So if decodebin just plugged the most highest-ranking decoder, that decoder might not be be able to handle the actual stream later -on, which would yield in an error (this is a data flow error then which would +on, which would yield an error (this is a data flow error then which would be hard to intercept and avoid in decodebin). In other words, we can't solve this issue by plugging a decoder right away with the parser. -So decodebin need to communicate to the parser the set of available decoder +So decodebin needs to communicate to the parser the set of available decoder caps (which would contain the relevant capabilities/restrictions such as supported profiles, resolutions, etc.), after the usual "autoplug-*" signal filtering/sorting of course. From 55182ed8419f7ad8aefec49ae797daeeb7f96824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Sat, 10 Sep 2011 18:30:55 +0100 Subject: [PATCH 06/43] baseaudiosrc: don't try to fixate "width" field for alaw/mulaw Fixes warning when trying to fixate e.g. pulsesrc ! audio/x-alaw ! fakesink. --- gst-libs/gst/audio/gstbaseaudiosrc.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/gst-libs/gst/audio/gstbaseaudiosrc.c b/gst-libs/gst/audio/gstbaseaudiosrc.c index 7c7ec935b0..7718747c5d 100644 --- a/gst-libs/gst/audio/gstbaseaudiosrc.c +++ b/gst-libs/gst/audio/gstbaseaudiosrc.c @@ -534,14 +534,17 @@ gst_base_audio_src_fixate (GstBaseSrc * bsrc, GstCaps * caps) /* fields for all formats */ gst_structure_fixate_field_nearest_int (s, "rate", 44100); gst_structure_fixate_field_nearest_int (s, "channels", 2); - gst_structure_fixate_field_nearest_int (s, "width", 16); - /* fields for int */ - if (gst_structure_has_field (s, "depth")) { - gst_structure_get_int (s, "width", &width); - /* round width to nearest multiple of 8 for the depth */ - depth = GST_ROUND_UP_8 (width); - gst_structure_fixate_field_nearest_int (s, "depth", depth); + /* fields for int and/or float, but maybe not others like alaw/mulaw */ + if (gst_structure_has_field (s, "width")) { + gst_structure_fixate_field_nearest_int (s, "width", 16); + + if (gst_structure_has_field (s, "depth")) { + gst_structure_get_int (s, "width", &width); + /* round width to nearest multiple of 8 for the depth */ + depth = GST_ROUND_UP_8 (width); + gst_structure_fixate_field_nearest_int (s, "depth", depth); + } } if (gst_structure_has_field (s, "signed")) gst_structure_fixate_field_boolean (s, "signed", TRUE); @@ -561,8 +564,7 @@ gst_base_audio_src_setcaps (GstBaseSrc * bsrc, GstCaps * caps) spec->latency_time = src->latency_time; GST_OBJECT_LOCK (src); - if (!gst_ring_buffer_parse_caps (spec, caps)) - { + if (!gst_ring_buffer_parse_caps (spec, caps)) { GST_OBJECT_UNLOCK (src); goto parse_error; } From d223a6dd59aec16c9aa6226cee747c38e678026b Mon Sep 17 00:00:00 2001 From: Thomas Vander Stichele Date: Sun, 11 Sep 2011 14:22:59 -0400 Subject: [PATCH 07/43] theoraenc: Fix descriptions of properties --- ext/theora/gsttheoraenc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/theora/gsttheoraenc.c b/ext/theora/gsttheoraenc.c index 7610e9bc47..cae79db44f 100644 --- a/ext/theora/gsttheoraenc.c +++ b/ext/theora/gsttheoraenc.c @@ -388,17 +388,17 @@ gst_theora_enc_class_init (GstTheoraEncClass * klass) THEORA_DEF_VP3_COMPATIBLE, (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_DROP_FRAMES, - g_param_spec_boolean ("drop-frames", "VP3 Compatible", + g_param_spec_boolean ("drop-frames", "Drop Frames", "Allow or disallow frame dropping", THEORA_DEF_DROP_FRAMES, (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_CAP_OVERFLOW, - g_param_spec_boolean ("cap-overflow", "VP3 Compatible", + g_param_spec_boolean ("cap-overflow", "Cap Overflow", "Enable capping of bit reservoir overflows", THEORA_DEF_CAP_OVERFLOW, (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_CAP_UNDERFLOW, - g_param_spec_boolean ("cap-underflow", "VP3 Compatible", + g_param_spec_boolean ("cap-underflow", "Cap Underflow", "Enable capping of bit reservoir underflows", THEORA_DEF_CAP_UNDERFLOW, (GParamFlags) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); From 454c554b113b6be1b3d8af55b1d79e0da85266a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Mon, 12 Sep 2011 19:53:51 +0100 Subject: [PATCH 08/43] docs: minor addition to GST_TAG_ID3V2_HEADER_SIZE docs --- gst-libs/gst/tag/tag.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gst-libs/gst/tag/tag.h b/gst-libs/gst/tag/tag.h index ba8f87849d..057378c2e6 100644 --- a/gst-libs/gst/tag/tag.h +++ b/gst-libs/gst/tag/tag.h @@ -445,7 +445,8 @@ GType gst_tag_image_type_get_type (void); /** * GST_TAG_ID3V2_HEADER_SIZE: * - * ID3V2 header size considered minimum input for some functions. + * ID3V2 header size considered minimum input for some functions such as + * gst_tag_list_from_id3v2_tag() and gst_tag_get_id3v2_tag_size() for example. * * Since: 0.10.36 */ From 14a79628a732d040402c09990e43ea80c14a681c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Mon, 12 Sep 2011 15:10:37 +0100 Subject: [PATCH 09/43] playbin2: try to catch malformed URIs Only log in debug log for now, since the check is a bit half-hearted, its purpose is mostly to make sure people use gst_filename_to_uri() or g_filename_to_uri(). https://bugzilla.gnome.org/show_bug.cgi?id=654673 --- gst/playback/gstplaybin2.c | 39 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/gst/playback/gstplaybin2.c b/gst/playback/gstplaybin2.c index 20e24c25b2..b057bbe5ad 100644 --- a/gst/playback/gstplaybin2.c +++ b/gst/playback/gstplaybin2.c @@ -1272,6 +1272,34 @@ gst_play_bin_finalize (GObject * object) G_OBJECT_CLASS (parent_class)->finalize (object); } +static gboolean +gst_playbin_uri_is_valid (GstPlayBin * playbin, const gchar * uri) +{ + const gchar *c; + + GST_LOG_OBJECT (playbin, "checking uri '%s'", uri); + + /* this just checks the protocol */ + if (!gst_uri_is_valid (uri)) + return FALSE; + + for (c = uri; *c != '\0'; ++c) { + if (*c >= 128 || !g_ascii_isprint (*c)) + goto invalid; + if (*c == ' ') + goto invalid; + } + + return TRUE; + +invalid: + { + GST_WARNING_OBJECT (playbin, "uri '%s' not valid, character #%u", + uri, (guint) ((guintptr) c - (guintptr) uri)); + return FALSE; + } +} + static void gst_play_bin_set_uri (GstPlayBin * playbin, const gchar * uri) { @@ -1282,6 +1310,17 @@ gst_play_bin_set_uri (GstPlayBin * playbin, const gchar * uri) return; } + if (!gst_playbin_uri_is_valid (playbin, uri)) { + if (g_str_has_prefix (uri, "file:")) { + GST_ERROR_OBJECT (playbin, "malformed file URI '%s' - make sure to " + "escape spaces and non-ASCII characters properly and specify an " + "absolute path. Use gst_filename_to_uri() to convert filenames " + "to URIs", uri); + } else { + GST_ERROR_OBJECT (playbin, "malformed URI '%s'", uri); + } + } + GST_PLAY_BIN_LOCK (playbin); group = playbin->next_group; From 596c75b541c30e0db233f16e3894b65b0ad47712 Mon Sep 17 00:00:00 2001 From: Josep Torra Date: Mon, 12 Sep 2011 15:46:46 +0200 Subject: [PATCH 10/43] subtitleoverlay: gracefully handle non raw video streams Implement handling of non raw video streams by avoiding colorspace elements and autoplugging a compatible renderer if available. Fallback to passthrough if no compatible renderer is found. --- gst/playback/gstsubtitleoverlay.c | 207 ++++++++++++++++++++++-------- 1 file changed, 155 insertions(+), 52 deletions(-) diff --git a/gst/playback/gstsubtitleoverlay.c b/gst/playback/gstsubtitleoverlay.c index 66b65657e7..155203be59 100644 --- a/gst/playback/gstsubtitleoverlay.c +++ b/gst/playback/gstsubtitleoverlay.c @@ -178,6 +178,42 @@ static const gchar *_sub_pad_names[] = { "subpicture", "subpicture_sink", "subtitle_sink", "subtitle" }; +static inline gboolean +_is_raw_video (GstStructure * s) +{ + const gchar *name; + + name = gst_structure_get_name (s); + + if (g_str_has_prefix (name, "video/x-raw-")) + return TRUE; + return FALSE; +} + +static gboolean +_is_raw_video_pad (GstPad * pad) +{ + GstPad *peer = gst_pad_get_peer (pad); + GstCaps *caps; + gboolean raw = TRUE; + + if (peer) { + caps = gst_pad_get_negotiated_caps (peer); + if (!caps) { + caps = gst_pad_get_caps_reffed (peer); + } + gst_object_unref (peer); + } else { + caps = gst_pad_get_caps_reffed (pad); + } + + raw = _is_raw_video (gst_caps_get_structure (caps, 0)); + + gst_caps_unref (caps); + + return raw; +} + static GstCaps * _get_sub_caps (GstElementFactory * factory) { @@ -1067,6 +1103,7 @@ _pad_blocked_cb (GstPad * pad, gboolean blocked, gpointer user_data) } else { const gchar *name = gst_plugin_feature_get_name (GST_PLUGIN_FEATURE_CAST (factory)); + gboolean is_raw_video = _is_raw_video_pad (self->video_sinkpad); if (strcmp (name, "textoverlay") == 0) { /* Set some textoverlay specific properties */ @@ -1086,66 +1123,123 @@ _pad_blocked_cb (GstPad * pad, gboolean blocked, gpointer user_data) g_object_set (self->renderer, "font-desc", self->font_desc, NULL); } - /* First link everything internally */ - if (G_UNLIKELY (!_create_element (self, &self->post_colorspace, - COLORSPACE, NULL, "post-colorspace", FALSE))) { - continue; - } + if (is_raw_video) { + /* First check that renderer also supports raw video */ + sink = _get_video_pad (element); + if (G_UNLIKELY (!sink)) { + GST_WARNING_OBJECT (self, "Can't get video sink from renderer"); + continue; + } - src = gst_element_get_static_pad (element, "src"); - if (G_UNLIKELY (!src)) { - GST_WARNING_OBJECT (self, "Can't get src pad from renderer"); - continue; - } + if (G_UNLIKELY (!_is_raw_video_pad (sink))) { + GST_DEBUG_OBJECT (self, "Renderer doesn't support raw video"); + gst_object_unref (sink); + continue; + } + gst_object_unref (sink); - sink = gst_element_get_static_pad (self->post_colorspace, "sink"); - if (G_UNLIKELY (!sink)) { - GST_WARNING_OBJECT (self, "Can't get sink pad from " COLORSPACE); - gst_object_unref (src); - continue; - } + /* First link everything internally */ + if (G_UNLIKELY (!_create_element (self, &self->post_colorspace, + COLORSPACE, NULL, "post-colorspace", FALSE))) { + continue; + } + src = gst_element_get_static_pad (element, "src"); + if (G_UNLIKELY (!src)) { + GST_WARNING_OBJECT (self, "Can't get src pad from renderer"); + continue; + } - if (G_UNLIKELY (gst_pad_link (src, sink) != GST_PAD_LINK_OK)) { - GST_WARNING_OBJECT (self, "Can't link renderer with " COLORSPACE); + sink = gst_element_get_static_pad (self->post_colorspace, "sink"); + if (G_UNLIKELY (!sink)) { + GST_WARNING_OBJECT (self, "Can't get sink pad from " COLORSPACE); + gst_object_unref (src); + continue; + } + + if (G_UNLIKELY (gst_pad_link (src, sink) != GST_PAD_LINK_OK)) { + GST_WARNING_OBJECT (self, "Can't link renderer with " COLORSPACE); + gst_object_unref (src); + gst_object_unref (sink); + continue; + } gst_object_unref (src); gst_object_unref (sink); - continue; - } - gst_object_unref (src); - gst_object_unref (sink); - if (G_UNLIKELY (!_create_element (self, &self->pre_colorspace, - COLORSPACE, NULL, "pre-colorspace", FALSE))) { - continue; - } + if (G_UNLIKELY (!_create_element (self, &self->pre_colorspace, + COLORSPACE, NULL, "pre-colorspace", FALSE))) { + continue; + } - sink = _get_video_pad (element); - if (G_UNLIKELY (!sink)) { - GST_WARNING_OBJECT (self, "Can't get video sink from renderer"); - continue; - } + sink = _get_video_pad (element); + if (G_UNLIKELY (!sink)) { + GST_WARNING_OBJECT (self, "Can't get video sink from renderer"); + continue; + } - src = gst_element_get_static_pad (self->pre_colorspace, "src"); - if (G_UNLIKELY (!src)) { - GST_WARNING_OBJECT (self, "Can't get srcpad from " COLORSPACE); - gst_object_unref (sink); - continue; - } + src = gst_element_get_static_pad (self->pre_colorspace, "src"); + if (G_UNLIKELY (!src)) { + GST_WARNING_OBJECT (self, "Can't get srcpad from " COLORSPACE); + gst_object_unref (sink); + continue; + } - if (G_UNLIKELY (gst_pad_link (src, sink) != GST_PAD_LINK_OK)) { - GST_WARNING_OBJECT (self, "Can't link " COLORSPACE " to renderer"); + if (G_UNLIKELY (gst_pad_link (src, sink) != GST_PAD_LINK_OK)) { + GST_WARNING_OBJECT (self, "Can't link " COLORSPACE " to renderer"); + gst_object_unref (src); + gst_object_unref (sink); + continue; + } gst_object_unref (src); gst_object_unref (sink); - continue; - } - gst_object_unref (src); - gst_object_unref (sink); - /* Set src ghostpad target */ - src = gst_element_get_static_pad (self->post_colorspace, "src"); - if (G_UNLIKELY (!src)) { - GST_WARNING_OBJECT (self, "Can't get src pad from " COLORSPACE); - continue; + /* Set src ghostpad target */ + src = gst_element_get_static_pad (self->post_colorspace, "src"); + if (G_UNLIKELY (!src)) { + GST_WARNING_OBJECT (self, "Can't get src pad from " COLORSPACE); + continue; + } + } else { /* No raw video pad */ + GstCaps *allowed_caps, *video_caps = NULL; + GstPad *video_peer; + gboolean can_intersect = FALSE; + + video_peer = gst_pad_get_peer (self->video_sinkpad); + if (video_peer) { + video_caps = gst_pad_get_negotiated_caps (video_peer); + if (!video_caps) { + video_caps = gst_pad_get_caps_reffed (video_peer); + } + gst_object_unref (video_peer); + } + + sink = _get_video_pad (element); + if (G_UNLIKELY (!sink)) { + GST_WARNING_OBJECT (self, "Can't get video sink from renderer"); + continue; + } + allowed_caps = gst_pad_get_caps_reffed (sink); + gst_object_unref (sink); + + if (allowed_caps && video_caps) + can_intersect = gst_caps_can_intersect (allowed_caps, video_caps); + + if (allowed_caps) + gst_caps_unref (allowed_caps); + + if (video_caps) + gst_caps_unref (video_caps); + + if (G_UNLIKELY (!can_intersect)) { + GST_WARNING_OBJECT (self, "Renderer with custom caps is not " + "compatible with video stream"); + continue; + } + + src = gst_element_get_static_pad (element, "src"); + if (G_UNLIKELY (!src)) { + GST_WARNING_OBJECT (self, "Can't get src pad from renderer"); + continue; + } } if (G_UNLIKELY (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST @@ -1203,10 +1297,19 @@ _pad_blocked_cb (GstPad * pad, gboolean blocked, gpointer user_data) } /* Set the sink ghostpad targets */ - sink = gst_element_get_static_pad (self->pre_colorspace, "sink"); - if (G_UNLIKELY (!sink)) { - GST_WARNING_OBJECT (self, "Can't get sink pad from " COLORSPACE); - continue; + if (self->pre_colorspace) { + sink = gst_element_get_static_pad (self->pre_colorspace, "sink"); + if (G_UNLIKELY (!sink)) { + GST_WARNING_OBJECT (self, "Can't get sink pad from " COLORSPACE); + continue; + } + } else { + sink = _get_video_pad (element); + if (G_UNLIKELY (!sink)) { + GST_WARNING_OBJECT (self, "Can't get sink pad from %" GST_PTR_FORMAT, + element); + continue; + } } if (G_UNLIKELY (!gst_ghost_pad_set_target (GST_GHOST_PAD_CAST From 8f8ad316ce303e39689beeb23f335d3b5c359283 Mon Sep 17 00:00:00 2001 From: Josep Torra Date: Mon, 12 Sep 2011 15:48:59 +0200 Subject: [PATCH 11/43] Revert "playsink: only add text overlay if vido sink also accepts raw caps" This reverts commit a22faad18a73a27a2a0c903748c1a355df4d8c13. Instead of disabling subtitles completelly when video stream have custom caps, just let the sutbtileoverlay cope with them as now it's able to. --- gst/playback/gstplaysink.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gst/playback/gstplaysink.c b/gst/playback/gstplaysink.c index 78ef84f545..32d0ae79c1 100644 --- a/gst/playback/gstplaysink.c +++ b/gst/playback/gstplaysink.c @@ -2114,8 +2114,7 @@ gst_play_sink_reconfigure (GstPlaySink * playsink) GST_OBJECT_UNLOCK (playsink); /* figure out which components we need */ - if (flags & GST_PLAY_FLAG_TEXT && playsink->video_pad_raw - && playsink->text_pad) { + if (flags & GST_PLAY_FLAG_TEXT && playsink->text_pad) { /* we have subtitles and we are requested to show it */ need_text = TRUE; } From 6704b37fc3c1039ab771223ef0d45c2345268ad8 Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Sat, 13 Aug 2011 14:14:19 +0100 Subject: [PATCH 12/43] oggdemux: do not propagate discontinuities in sparse streams The first packet of a sparse stream may arrive after an initial delay in the stream. If ogg_stream_packetout reports a discontinuity in a sparse stream, do not propagate it to other streams in the chain unnecessarily. https://bugzilla.gnome.org/show_bug.cgi?id=621897 --- ext/ogg/gstoggdemux.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ext/ogg/gstoggdemux.c b/ext/ogg/gstoggdemux.c index a1f845aecc..ac20a46230 100644 --- a/ext/ogg/gstoggdemux.c +++ b/ext/ogg/gstoggdemux.c @@ -101,6 +101,7 @@ static gboolean gst_ogg_demux_collect_chain_info (GstOggDemux * ogg, GstOggChain * chain); static gboolean gst_ogg_demux_activate_chain (GstOggDemux * ogg, GstOggChain * chain, GstEvent * event); +static void gst_ogg_pad_mark_discont (GstOggPad * pad); static void gst_ogg_chain_mark_discont (GstOggChain * chain); static gboolean gst_ogg_demux_perform_seek (GstOggDemux * ogg, @@ -959,7 +960,11 @@ gst_ogg_pad_stream_out (GstOggPad * pad, gint npackets) break; case -1: GST_LOG_OBJECT (ogg, "packetout discont"); - gst_ogg_chain_mark_discont (pad->chain); + if (!pad->map.is_sparse) { + gst_ogg_chain_mark_discont (pad->chain); + } else { + gst_ogg_pad_mark_discont (pad); + } break; case 1: GST_LOG_OBJECT (ogg, "packetout gave packet of size %ld", packet.bytes); From b0b4e286c8cde2e79a959a444a2c68e99c3f29c6 Mon Sep 17 00:00:00 2001 From: Josep Torra Date: Thu, 15 Sep 2011 09:23:54 +0200 Subject: [PATCH 13/43] playbin2: autoplug sink if stream is incompatible to the configured one Fixes regression since 0.10.33 where sinks that can cope with non raw caps or custom caps are not autoplugged if there's a sink configured with the properties video-sink and audio-sink which cannot handle the stream. This change checks for compatibility on the configured one and use it if success. Otherwhise it tries with the found factories. --- gst/playback/gstplaybin2.c | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/gst/playback/gstplaybin2.c b/gst/playback/gstplaybin2.c index b057bbe5ad..22f43e1022 100644 --- a/gst/playback/gstplaybin2.c +++ b/gst/playback/gstplaybin2.c @@ -3050,17 +3050,6 @@ autoplug_factories_cb (GstElement * decodebin, GstPad * pad, GstElementFactory *factory = GST_ELEMENT_FACTORY_CAST (tmp->data); GValue val = { 0, }; - if (group->audio_sink && gst_element_factory_list_is_type (factory, - GST_ELEMENT_FACTORY_TYPE_SINK | - GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) { - continue; - } - if (group->video_sink && gst_element_factory_list_is_type (factory, - GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO - | GST_ELEMENT_FACTORY_TYPE_MEDIA_IMAGE)) { - continue; - } - g_value_init (&val, G_TYPE_OBJECT); g_value_set_object (&val, factory); g_value_array_append (result, &val); @@ -3272,8 +3261,6 @@ autoplug_select_cb (GstElement * decodebin, GstPad * pad, "Existing sink '%s' does not accept caps: %" GST_PTR_FORMAT, GST_ELEMENT_NAME (sink), caps); gst_object_unref (sink); - GST_SOURCE_GROUP_UNLOCK (group); - return GST_AUTOPLUG_SELECT_SKIP; } } GST_DEBUG_OBJECT (playbin, "we have no pending sink, try to create one"); From bd52f00796649c5c4097d9b5f50fdea227182299 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Thu, 15 Sep 2011 14:27:35 +0200 Subject: [PATCH 14/43] Revert "playbin2: autoplug sink if stream is incompatible to the configured one" This reverts commit b0b4e286c8cde2e79a959a444a2c68e99c3f29c6. We agreed that the previous (pre-.35) behaviour is broken and a bug and the current behaviour is correct, deterministic and allows the application to handle stuff properly while the old behaviour can't be handled properly by applications and just worked in some applications by luck. The solution to the problem that was solved by relying on the old, broken behaviour would be, to make decodebin2/playbin2 more aware of decoders and improve the autoplugging of decoders by considering the caps supported by the sink instead of just using something with the highest rank. See bug #656923. --- gst/playback/gstplaybin2.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/gst/playback/gstplaybin2.c b/gst/playback/gstplaybin2.c index 22f43e1022..b057bbe5ad 100644 --- a/gst/playback/gstplaybin2.c +++ b/gst/playback/gstplaybin2.c @@ -3050,6 +3050,17 @@ autoplug_factories_cb (GstElement * decodebin, GstPad * pad, GstElementFactory *factory = GST_ELEMENT_FACTORY_CAST (tmp->data); GValue val = { 0, }; + if (group->audio_sink && gst_element_factory_list_is_type (factory, + GST_ELEMENT_FACTORY_TYPE_SINK | + GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO)) { + continue; + } + if (group->video_sink && gst_element_factory_list_is_type (factory, + GST_ELEMENT_FACTORY_TYPE_SINK | GST_ELEMENT_FACTORY_TYPE_MEDIA_VIDEO + | GST_ELEMENT_FACTORY_TYPE_MEDIA_IMAGE)) { + continue; + } + g_value_init (&val, G_TYPE_OBJECT); g_value_set_object (&val, factory); g_value_array_append (result, &val); @@ -3261,6 +3272,8 @@ autoplug_select_cb (GstElement * decodebin, GstPad * pad, "Existing sink '%s' does not accept caps: %" GST_PTR_FORMAT, GST_ELEMENT_NAME (sink), caps); gst_object_unref (sink); + GST_SOURCE_GROUP_UNLOCK (group); + return GST_AUTOPLUG_SELECT_SKIP; } } GST_DEBUG_OBJECT (playbin, "we have no pending sink, try to create one"); From b678f6cf69a97625c4e9145ffae41c9fd78d5f2f Mon Sep 17 00:00:00 2001 From: Stefan Sauer Date: Thu, 15 Sep 2011 16:47:26 +0200 Subject: [PATCH 15/43] adder: don't access the event after pushing Fixes valgrind warnings. --- gst/adder/gstadder.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gst/adder/gstadder.c b/gst/adder/gstadder.c index 7d06239ab8..5ba7c7d61d 100644 --- a/gst/adder/gstadder.c +++ b/gst/adder/gstadder.c @@ -1186,8 +1186,7 @@ gst_adder_collected (GstCollectPads * pads, gpointer user_data) if (event) { if (!gst_pad_push_event (adder->srcpad, event)) { - GST_WARNING_OBJECT (adder->srcpad, "Sending event %p (%s) failed.", - event, GST_EVENT_TYPE_NAME (event)); + GST_WARNING_OBJECT (adder->srcpad, "Sending event failed"); } } else { GST_WARNING_OBJECT (adder->srcpad, "Creating new segment event for " From a330ff0721299889e74632751c5a4f592be90f6d Mon Sep 17 00:00:00 2001 From: Alessandro Decina Date: Thu, 15 Sep 2011 22:04:56 +0200 Subject: [PATCH 16/43] playbin2: fix compiler warning Remove a check for gchar >= 128 --- gst/playback/gstplaybin2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gst/playback/gstplaybin2.c b/gst/playback/gstplaybin2.c index b057bbe5ad..41b3cd443d 100644 --- a/gst/playback/gstplaybin2.c +++ b/gst/playback/gstplaybin2.c @@ -1284,7 +1284,7 @@ gst_playbin_uri_is_valid (GstPlayBin * playbin, const gchar * uri) return FALSE; for (c = uri; *c != '\0'; ++c) { - if (*c >= 128 || !g_ascii_isprint (*c)) + if (!g_ascii_isprint (*c)) goto invalid; if (*c == ' ') goto invalid; From 0173afa38cc6f8a2cb810f99a24ddb1d5d5f4868 Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Sat, 13 Aug 2011 14:18:56 +0100 Subject: [PATCH 17/43] oggdemux: implement push mode seeking This patch implements seeking in push mode (eg, over the net) in Ogg, using the double bisection method. As a side effect, it also fixes duration determination of network streams, by seeking to the end to check the actual duration. Known issues: - Getting an EOS while seeking stops the streaming task, I can't find a way to prevent this (eg, by issuing a seek in the event handler). - Seeking twice in a VERY short succession with playbin2 fails for streams with subtitles, we end up pushing in a dataqueue which is flushing. Rare in normal use AFAICT. - Seeking is slow on slow links - byte ranges guesses could be made better, decreasing the number of required requests - If no granule position is found in the last 64 KB of a stream, duration will be left unknown (should be pretty rare) https://bugzilla.gnome.org/show_bug.cgi?id=621897 --- ext/ogg/gstoggdemux.c | 886 +++++++++++++++++++++++++++++++++++++++--- ext/ogg/gstoggdemux.h | 27 ++ 2 files changed, 865 insertions(+), 48 deletions(-) diff --git a/ext/ogg/gstoggdemux.c b/ext/ogg/gstoggdemux.c index ac20a46230..16f559e83e 100644 --- a/ext/ogg/gstoggdemux.c +++ b/ext/ogg/gstoggdemux.c @@ -47,11 +47,30 @@ #define CHUNKSIZE (8500) /* this is out of vorbisfile */ +/* we hope we get a granpos within this many bytes off the end */ +#define DURATION_CHUNK_OFFSET (64*1024) + +/* stop duration checks within this much of EOS */ +#define EOS_AVOIDANCE_THRESHOLD 8192 + #define GST_FLOW_LIMIT GST_FLOW_CUSTOM_ERROR +#define GST_FLOW_SKIP_PUSH GST_FLOW_CUSTOM_SUCCESS_1 #define GST_CHAIN_LOCK(ogg) g_mutex_lock((ogg)->chain_lock) #define GST_CHAIN_UNLOCK(ogg) g_mutex_unlock((ogg)->chain_lock) +#define GST_PUSH_LOCK(ogg) \ + do { \ + GST_TRACE_OBJECT(ogg, "Push lock"); \ + g_mutex_lock((ogg)->push_lock); \ + } while(0) + +#define GST_PUSH_UNLOCK(ogg) \ + do { \ + GST_TRACE_OBJECT(ogg, "Push unlock"); \ + g_mutex_unlock((ogg)->push_lock); \ + } while(0) + GST_DEBUG_CATEGORY (gst_ogg_demux_debug); GST_DEBUG_CATEGORY (gst_ogg_demux_setup_debug); #define GST_CAT_DEFAULT gst_ogg_demux_debug @@ -125,6 +144,11 @@ static void gst_ogg_demux_sync_streams (GstOggDemux * ogg); GstCaps *gst_ogg_demux_set_header_on_caps (GstOggDemux * ogg, GstCaps * caps, GList * headers); +static gboolean gst_ogg_demux_send_event (GstOggDemux * ogg, GstEvent * event); +static gboolean gst_ogg_demux_perform_seek_push (GstOggDemux * ogg, + GstEvent * event); +static gboolean gst_ogg_demux_check_duration_push (GstOggDemux * ogg, + GstSeekFlags flags, GstEvent * event); GType gst_ogg_pad_get_type (void); G_DEFINE_TYPE (GstOggPad, gst_ogg_pad, GST_TYPE_PAD); @@ -304,8 +328,7 @@ gst_ogg_pad_src_query (GstPad * pad, GstQuery * query) GstOggPad *pad = g_array_index (ogg->current_chain->streams, GstOggPad *, i); - seekable |= (pad->map.index != NULL && pad->map.n_index != 0); - + seekable = TRUE; if (pad->map.index != NULL && pad->map.n_index != 0) { GstOggIndex *idx; GstClockTime idx_time; @@ -318,6 +341,8 @@ gst_ogg_pad_src_query (GstPad * pad, GstQuery * query) stop = idx_time; else stop = MAX (idx_time, stop); + } else { + stop = -1; /* we've no clue, sadly, without seeking */ } } } @@ -455,6 +480,22 @@ gst_ogg_demux_chain_peer (GstOggPad * pad, ogg_packet * packet, cret = GST_FLOW_OK; + GST_PUSH_LOCK (ogg); + if (!ogg->pullmode && ogg->push_state == PUSH_PLAYING + && ogg->push_time_length == GST_CLOCK_TIME_NONE + && !ogg->push_disable_seeking) { + if (!ogg->building_chain) { + /* we got all headers, now try to get duration */ + if (!gst_ogg_demux_check_duration_push (ogg, GST_SEEK_FLAG_FLUSH, NULL)) { + GST_PUSH_UNLOCK (ogg); + return GST_FLOW_OK; + } + } + GST_PUSH_UNLOCK (ogg); + return GST_FLOW_OK; + } + GST_PUSH_UNLOCK (ogg); + GST_DEBUG_OBJECT (ogg, "%p streaming to peer serial %08x", pad, pad->map.serialno); @@ -704,6 +745,36 @@ gst_ogg_demux_collect_start_time (GstOggDemux * ogg, GstOggChain * chain) return start_time; } +static GstClockTime +gst_ogg_demux_collect_sync_time (GstOggDemux * ogg, GstOggChain * chain) +{ + gint i; + GstClockTime sync_time = GST_CLOCK_TIME_NONE; + + if (!chain) { + GST_WARNING_OBJECT (ogg, "No chain!"); + return GST_CLOCK_TIME_NONE; + } + + for (i = 0; i < chain->streams->len; i++) { + GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); + + if (pad->map.is_sparse) + continue; + + if (pad->push_sync_time == GST_CLOCK_TIME_NONE) { + sync_time = GST_CLOCK_TIME_NONE; + break; + } else { + if (sync_time == GST_CLOCK_TIME_NONE) + sync_time = pad->push_sync_time; + else + sync_time = MAX (sync_time, pad->push_sync_time); + } + } + return sync_time; +} + /* submit a packet to the oggpad, this function will run the * typefind code for the pad if this is the first packet for this * stream @@ -838,7 +909,10 @@ gst_ogg_pad_submit_packet (GstOggPad * pad, ogg_packet * packet) pad->start_time = gst_ogg_stream_granule_to_time (&pad->map, start_granule); - GST_DEBUG ("start time %" G_GINT64_FORMAT, pad->start_time); + GST_DEBUG_OBJECT (ogg, + "start time %" GST_TIME_FORMAT " (%" GST_TIME_FORMAT ") for %s", + GST_TIME_ARGS (pad->start_time), GST_TIME_ARGS (pad->start_time), + gst_ogg_stream_get_media_type (&pad->map)); } else { packet->granulepos = gst_ogg_stream_granule_to_granulepos (&pad->map, pad->map.accumulated_granule, pad->keyframe_granule); @@ -887,8 +961,25 @@ gst_ogg_pad_submit_packet (GstOggPad * pad, ogg_packet * packet) segment_time = chain->begin_time; /* create the newsegment event we are going to send out */ - event = gst_event_new_new_segment (FALSE, ogg->segment.rate, - GST_FORMAT_TIME, start_time, chain->segment_stop, segment_time); + GST_PUSH_LOCK (ogg); + if (!ogg->pullmode && ogg->push_state == PUSH_LINEAR2) { + /* if we are fast forwarding to the actual seek target, + ensure previous frames are clipped */ + GST_DEBUG_OBJECT (ogg, + "Resynced, starting segment at %" GST_TIME_FORMAT + ", start_time %" GST_TIME_FORMAT, + GST_TIME_ARGS (ogg->push_seek_time_original_target), + GST_TIME_ARGS (start_time)); + event = + gst_event_new_new_segment (FALSE, ogg->push_seek_rate, + GST_FORMAT_TIME, ogg->push_seek_time_original_target, -1, + ogg->push_seek_time_original_target); + ogg->push_state = PUSH_PLAYING; + } else { + event = gst_event_new_new_segment (FALSE, ogg->segment.rate, + GST_FORMAT_TIME, start_time, chain->segment_stop, segment_time); + } + GST_PUSH_UNLOCK (ogg); ogg->resync = FALSE; } @@ -1001,6 +1092,424 @@ could_not_submit: } } +static void +gst_ogg_demux_setup_bisection_bounds (GstOggDemux * ogg) +{ + if (ogg->push_last_seek_time >= ogg->push_seek_time_target) { + GST_DEBUG_OBJECT (ogg, "We overshot by %" GST_TIME_FORMAT, + GST_TIME_ARGS (ogg->push_last_seek_time - ogg->push_seek_time_target)); + ogg->push_offset1 = ogg->push_last_seek_offset; + ogg->push_time1 = ogg->push_last_seek_time; + } else { + GST_DEBUG_OBJECT (ogg, "We undershot by %" GST_TIME_FORMAT, + GST_TIME_ARGS (ogg->push_seek_time_target - ogg->push_last_seek_time)); + ogg->push_offset0 = ogg->push_last_seek_offset; + ogg->push_time0 = ogg->push_last_seek_time; + } +} + +static gint64 +gst_ogg_demux_estimate_bisection_target (GstOggDemux * ogg) +{ + gint64 best; + gint64 segment_bitrate; + + /* we might not know the length of the stream in time, + so push_time1 might not be set */ + GST_DEBUG_OBJECT (ogg, + "push time 1: %" GST_TIME_FORMAT ", dbytes %" G_GINT64_FORMAT, + GST_TIME_ARGS (ogg->push_time1), ogg->push_offset1 - ogg->push_offset0); + if (ogg->push_time1 == GST_CLOCK_TIME_NONE) { + GST_DEBUG_OBJECT (ogg, + "New segment to consider: bytes %" G_GINT64_FORMAT " %" G_GINT64_FORMAT + ", time %" GST_TIME_FORMAT " (open ended)", ogg->push_offset0, + ogg->push_offset1, GST_TIME_ARGS (ogg->push_time0)); + if (ogg->push_last_seek_time == ogg->push_start_time) { + /* if we're at start and don't know the end time, we can't estimate + bitrate, so get the nominal declared bitrate as a failsafe, or some + random constant which will be discarded after we made a (probably + dire) first guess */ + segment_bitrate = (ogg->bitrate > 0 ? ogg->bitrate : 1000); + } else { + segment_bitrate = + gst_util_uint64_scale (ogg->push_last_seek_offset - 0, + 8 * GST_SECOND, ogg->push_last_seek_time - ogg->push_start_time); + } + best = + ogg->push_offset0 + + gst_util_uint64_scale (ogg->push_seek_time_target - ogg->push_time0, + segment_bitrate, 8 * GST_SECOND); + } else { + GST_DEBUG_OBJECT (ogg, + "New segment to consider: bytes %" G_GINT64_FORMAT " %" G_GINT64_FORMAT + ", time %" GST_TIME_FORMAT " %" GST_TIME_FORMAT, ogg->push_offset0, + ogg->push_offset1, GST_TIME_ARGS (ogg->push_time0), + GST_TIME_ARGS (ogg->push_time1)); + if (ogg->push_time0 == ogg->push_time1) { + best = ogg->push_offset0; + } else { + segment_bitrate = + gst_util_uint64_scale (ogg->push_offset1 - ogg->push_offset0, + 8 * GST_SECOND, ogg->push_time1 - ogg->push_time0); + GST_DEBUG_OBJECT (ogg, + "Local bitrate on the %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT + " segment: %" G_GINT64_FORMAT, GST_TIME_ARGS (ogg->push_time0), + GST_TIME_ARGS (ogg->push_time1)); + best = + ogg->push_offset0 + + gst_util_uint64_scale (ogg->push_seek_time_target - ogg->push_time0, + segment_bitrate, 8 * GST_SECOND); + } + } + + /* offset by typical page size */ + best -= CHUNKSIZE; + if (best < ogg->push_offset0) + best = ogg->push_offset0; + if (best < 0) + best = 0; + + return best; +} + +static void +gst_ogg_demux_record_keyframe_time (GstOggDemux * ogg, GstOggPad * pad, + ogg_int64_t granpos) +{ + gint64 kf_granule; + GstClockTime kf_time; + + kf_granule = gst_ogg_stream_granulepos_to_key_granule (&pad->map, granpos); + kf_time = gst_ogg_stream_granule_to_time (&pad->map, kf_granule); + + pad->push_kf_time = kf_time; +} + +/* returns the earliest keyframe time for all non sparse pads in the chain, + * if known, and GST_CLOCK_TIME_NONE if not */ +static GstClockTime +gst_ogg_demux_get_earliest_keyframe_time (GstOggDemux * ogg) +{ + GstClockTime t = GST_CLOCK_TIME_NONE; + GstOggChain *chain = ogg->building_chain; + int i; + + if (!chain) { + GST_WARNING_OBJECT (ogg, "No chain!"); + return GST_CLOCK_TIME_NONE; + } + for (i = 0; i < chain->streams->len; i++) { + GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); + + if (pad->map.is_sparse) + continue; + if (pad->push_kf_time == GST_CLOCK_TIME_NONE) + return GST_CLOCK_TIME_NONE; + if (t == GST_CLOCK_TIME_NONE || pad->push_kf_time < t) + t = pad->push_kf_time; + } + + return t; +} + +/* MUST be called with the push lock locked, and will unlock it + regardless of return value. */ +static GstFlowReturn +gst_ogg_demux_seek_back_after_push_duration_check_unlock (GstOggDemux * ogg) +{ + GstEvent *event; + + /* Get the delayed event, if any */ + event = ogg->push_mode_seek_delayed_event; + ogg->push_mode_seek_delayed_event = NULL; + + ogg->push_state = PUSH_PLAYING; + + GST_PUSH_UNLOCK (ogg); + + if (event) { + /* If there is one, perform it */ + gst_ogg_demux_perform_seek_push (ogg, event); + } else { + /* If there wasn't, seek back at start to start normal playback */ + GST_INFO_OBJECT (ogg, "Seeking back to 0 after duration check"); + event = gst_event_new_seek (1.0, GST_FORMAT_BYTES, + GST_SEEK_FLAG_ACCURATE | GST_SEEK_FLAG_FLUSH, + GST_SEEK_TYPE_SET, 1, GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE); + if (!gst_pad_push_event (ogg->sinkpad, event)) { + GST_WARNING_OBJECT (ogg, "Failed seeking back to start"); + return GST_FLOW_ERROR; + } + } + + return GST_FLOW_OK; +} + +static gboolean +gst_ogg_pad_handle_push_mode_state (GstOggPad * pad, ogg_page * page) +{ + GstOggDemux *ogg = pad->ogg; + ogg_int64_t granpos = ogg_page_granulepos (page); + + GST_PUSH_LOCK (ogg); + if (granpos >= 0) { + if (ogg->push_start_time == GST_CLOCK_TIME_NONE) { + ogg->push_start_time = + gst_ogg_stream_get_start_time_for_granulepos (&pad->map, granpos); + GST_DEBUG_OBJECT (ogg, "Stream start time: %" GST_TIME_FORMAT, + GST_TIME_ARGS (ogg->push_start_time)); + } + ogg->push_time_offset = + gst_ogg_stream_get_end_time_for_granulepos (&pad->map, granpos); + if (ogg->push_time_offset > 0) { + GST_DEBUG_OBJECT (ogg, "Bitrate since start: %" G_GUINT64_FORMAT, + gst_util_uint64_scale (ogg->push_byte_offset, 8 * GST_SECOND, + ogg->push_time_offset)); + } + + if (ogg->push_state == PUSH_DURATION) { + GstClockTime t = + gst_ogg_stream_get_end_time_for_granulepos (&pad->map, granpos); + + if (ogg->total_time == GST_CLOCK_TIME_NONE || t > ogg->total_time) { + GST_DEBUG_OBJECT (ogg, "New total time: %" GST_TIME_FORMAT, + GST_TIME_ARGS (t)); + ogg->total_time = t; + ogg->push_time_length = t; + } + + /* If we were determining the duration of the stream, we're now done, + and can get back to sending the original event we delayed. + We stop a bit before the end of the stream, as if we get a EOS + event and there is a queue2 upstream (such as when using playbin2), + it will pause the task *after* we come back from the EOS handler, + so we cannot prevent the pausing by issuing a seek. */ + if (ogg->push_byte_offset + EOS_AVOIDANCE_THRESHOLD >= + ogg->push_byte_length) { + GstMessage *message; + GstFlowReturn res; + + /* tell the pipeline we've just found out the duration */ + GST_INFO_OBJECT (ogg, "New duration found: %" GST_TIME_FORMAT, + GST_TIME_ARGS (ogg->total_time)); + message = + gst_message_new_duration (GST_OBJECT (ogg), GST_FORMAT_TIME, + ogg->total_time); + gst_element_post_message (GST_ELEMENT (ogg), message); + + GST_DEBUG_OBJECT (ogg, + "We're close enough to the end, and we're scared " + "to get too close, seeking back to start"); + + res = gst_ogg_demux_seek_back_after_push_duration_check_unlock (ogg); + if (res != GST_FLOW_OK) + return res; + return GST_FLOW_SKIP_PUSH; + } else { + GST_PUSH_UNLOCK (ogg); + } + return GST_FLOW_SKIP_PUSH; + } + } + + /* if we're seeking, look at time, and decide what to do */ + if (ogg->push_state != PUSH_PLAYING && ogg->push_state != PUSH_LINEAR2) { + GstClockTime t; + gint64 best = -1; + GstEvent *sevent; + int res; + gboolean close_enough; + + /* ignore -1 granpos when seeking, we want to sync on a real granpos */ + if (granpos < 0) { + GST_PUSH_UNLOCK (ogg); + if (ogg_stream_pagein (&pad->map.stream, page) != 0) + goto choked; + return GST_FLOW_SKIP_PUSH; + } + + t = gst_ogg_stream_get_end_time_for_granulepos (&pad->map, granpos); + + if (ogg->push_state == PUSH_BISECT1 || ogg->push_state == PUSH_BISECT2) { + GstClockTime sync_time; + + if (pad->push_sync_time == GST_CLOCK_TIME_NONE) + pad->push_sync_time = t; + GST_DEBUG_OBJECT (ogg, "Got timestamp %" GST_TIME_FORMAT " for %s", + GST_TIME_ARGS (t), gst_ogg_stream_get_media_type (&pad->map)); + sync_time = gst_ogg_demux_collect_sync_time (ogg, ogg->building_chain); + if (sync_time == GST_CLOCK_TIME_NONE) { + GST_PUSH_UNLOCK (ogg); + GST_DEBUG_OBJECT (ogg, + "Not enough timing info collected for sync, waiting for more"); + if (ogg_stream_pagein (&pad->map.stream, page) != 0) + goto choked; + return GST_FLOW_SKIP_PUSH; + } + ogg->push_last_seek_time = sync_time; + + GST_DEBUG_OBJECT (ogg, + "Bisection just seeked at %" G_GINT64_FORMAT ", time %" + GST_TIME_FORMAT ", target was %" GST_TIME_FORMAT, + ogg->push_last_seek_offset, + GST_TIME_ARGS (ogg->push_last_seek_time), + GST_TIME_ARGS (ogg->push_seek_time_target)); + + if (ogg->push_time1 != GST_CLOCK_TIME_NONE) { + GST_DEBUG_OBJECT (ogg, + "Interval was %" G_GINT64_FORMAT " - %" G_GINT64_FORMAT " (%" + G_GINT64_FORMAT "), time %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT + " (%" GST_TIME_FORMAT ")", ogg->push_offset0, ogg->push_offset1, + ogg->push_offset1 - ogg->push_offset0, + GST_TIME_ARGS (ogg->push_time0), GST_TIME_ARGS (ogg->push_time1), + GST_TIME_ARGS (ogg->push_time1 - ogg->push_time0)); + } else { + GST_DEBUG_OBJECT (ogg, + "Interval was %" G_GINT64_FORMAT " - %" G_GINT64_FORMAT " (%" + G_GINT64_FORMAT "), time %" GST_TIME_FORMAT " - unknown", + ogg->push_offset0, ogg->push_offset1, + ogg->push_offset1 - ogg->push_offset0, + GST_TIME_ARGS (ogg->push_time0)); + } + + gst_ogg_demux_setup_bisection_bounds (ogg); + + best = gst_ogg_demux_estimate_bisection_target (ogg); + + if (ogg->push_seek_time_target == 0) { + GST_DEBUG_OBJECT (ogg, "Seeking to 0, deemed close enough"); + close_enough = (ogg->push_last_seek_time == 0); + } else { + /* TODO: make this dependent on framerate ? */ + GstClockTime threshold = GST_SECOND / 2; + + /* We want to be within half a second before the target */ + if (threshold > ogg->push_seek_time_target) + threshold = ogg->push_seek_time_target; + close_enough = ogg->push_last_seek_time < ogg->push_seek_time_target + && ogg->push_last_seek_time >= + ogg->push_seek_time_target - threshold; + GST_DEBUG_OBJECT (ogg, + "testing if we're close enough: %" GST_TIME_FORMAT " <= %" + GST_TIME_FORMAT " < %" GST_TIME_FORMAT " ? %s", + GST_TIME_ARGS (ogg->push_seek_time_target - threshold), + GST_TIME_ARGS (ogg->push_last_seek_time), + GST_TIME_ARGS (ogg->push_seek_time_target), + close_enough ? "Yes" : "No"); + } + + if (close_enough || best == ogg->push_last_seek_offset) { + if (ogg->push_state == PUSH_BISECT1) { + /* we now know the time segment we'll have to search for + the second bisection */ + ogg->push_time0 = ogg->push_start_time; + ogg->push_offset0 = 0; + + GST_DEBUG_OBJECT (ogg, + "Seek to %" GST_TIME_FORMAT + " (%lx) done, now gathering pages for all non-sparse streams", + GST_TIME_ARGS (ogg->push_seek_time_target), (long) granpos); + ogg->push_state = PUSH_LINEAR1; + } else { + /* If we're asked for an accurate seek, we'll go forward till + we get to the original seek target time, else we'll just drop + here at the keyframe */ + if (ogg->push_seek_flags & GST_SEEK_FLAG_ACCURATE) { + GST_INFO_OBJECT (ogg, + "Seek to keyframe at %" GST_TIME_FORMAT " done (we're at %" + GST_TIME_FORMAT "), skipping to original target (%" + GST_TIME_FORMAT ")", + GST_TIME_ARGS (ogg->push_seek_time_target), + GST_TIME_ARGS (sync_time), + GST_TIME_ARGS (ogg->push_seek_time_original_target)); + ogg->push_state = PUSH_LINEAR2; + } else { + GST_DEBUG_OBJECT (ogg, "Seek to keyframe done, playing"); + + /* we're synced to the seek target, so flush stream and stuff + any queued pages into the stream so we start decoding there */ + ogg->push_state = PUSH_PLAYING; + } + GST_INFO_OBJECT (ogg, "Bisection needed %d + %d steps", + ogg->push_bisection_steps[0], ogg->push_bisection_steps[1]); + } + } + } else if (ogg->push_state == PUSH_LINEAR1) { + if (pad->push_kf_time == GST_CLOCK_TIME_NONE) { + GstClockTime earliest_keyframe_time; + + gst_ogg_demux_record_keyframe_time (ogg, pad, granpos); + GST_DEBUG_OBJECT (ogg, + "Previous keyframe for %s stream at %" GST_TIME_FORMAT, + gst_ogg_stream_get_media_type (&pad->map), + GST_TIME_ARGS (pad->push_kf_time)); + earliest_keyframe_time = gst_ogg_demux_get_earliest_keyframe_time (ogg); + if (earliest_keyframe_time != GST_CLOCK_TIME_NONE) { + GST_DEBUG_OBJECT (ogg, + "All non sparse streams now have a previous keyframe time," + "bisecting again to %" GST_TIME_FORMAT, + GST_TIME_ARGS (earliest_keyframe_time)); + ogg->push_seek_time_target = earliest_keyframe_time; + + ogg->push_state = PUSH_BISECT2; + best = gst_ogg_demux_estimate_bisection_target (ogg); + } + } + } + + if (ogg->push_state == PUSH_BISECT1 || ogg->push_state == PUSH_BISECT2) { + gint i; + + ogg_sync_reset (&ogg->sync); + for (i = 0; i < ogg->building_chain->streams->len; i++) { + GstOggPad *pad = + g_array_index (ogg->building_chain->streams, GstOggPad *, i); + + pad->push_sync_time = GST_CLOCK_TIME_NONE; + ogg_stream_reset (&pad->map.stream); + } + + GST_DEBUG_OBJECT (ogg, + "seeking to %" G_GINT64_FORMAT " - %" G_GINT64_FORMAT, best, + (gint64) - 1); + /* do seek */ + g_assert (best != -1); + ogg->push_bisection_steps[ogg->push_state == PUSH_BISECT2 ? 1 : 0]++; + sevent = + gst_event_new_seek (ogg->push_seek_rate, GST_FORMAT_BYTES, + ogg->push_seek_flags, GST_SEEK_TYPE_SET, best, + GST_SEEK_TYPE_NONE, -1); + + GST_PUSH_UNLOCK (ogg); + res = gst_pad_push_event (ogg->sinkpad, sevent); + if (!res) { + /* We failed to send the seek event, notify the pipeline */ + GST_ELEMENT_ERROR (ogg, RESOURCE, SEEK, (NULL), ("Failed to seek")); + return GST_FLOW_ERROR; + } + return GST_FLOW_SKIP_PUSH; + } + + if (ogg->push_state != PUSH_PLAYING) { + GST_PUSH_UNLOCK (ogg); + return GST_FLOW_SKIP_PUSH; + } + } + GST_PUSH_UNLOCK (ogg); + + return GST_FLOW_OK; + +choked: + { + GST_WARNING_OBJECT (ogg, + "ogg stream choked on page (serial %08x), " + "resetting stream", pad->map.serialno); + gst_ogg_pad_reset (pad); + /* we continue to recover */ + return GST_FLOW_SKIP_PUSH; + } +} + /* submit a page to an oggpad, this function will then submit all * the packets in the page. */ @@ -1013,7 +1522,7 @@ gst_ogg_pad_submit_page (GstOggPad * pad, ogg_page * page) ogg = pad->ogg; - /* for negative rates we read pages backwards and must therefore be carefull + /* for negative rates we read pages backwards and must therefore be careful * with continued pages */ if (ogg->segment.rate < 0.0) { gint npackets; @@ -1039,6 +1548,15 @@ gst_ogg_pad_submit_page (GstOggPad * pad, ogg_page * page) } } + /* keep track of time in push mode */ + if (!ogg->pullmode) { + result = gst_ogg_pad_handle_push_mode_state (pad, page); + if (result == GST_FLOW_SKIP_PUSH) + return GST_FLOW_OK; + if (result != GST_FLOW_OK) + return result; + } + if (ogg_stream_pagein (&pad->map.stream, page) != 0) goto choked; @@ -1275,7 +1793,6 @@ static gboolean gst_ogg_demux_sink_activate_push (GstPad * sinkpad, gboolean active); static GstStateChangeReturn gst_ogg_demux_change_state (GstElement * element, GstStateChange transition); -static gboolean gst_ogg_demux_send_event (GstOggDemux * ogg, GstEvent * event); static void gst_ogg_print (GstOggDemux * demux); @@ -1327,6 +1844,7 @@ gst_ogg_demux_init (GstOggDemux * ogg, GstOggDemuxClass * g_class) gst_element_add_pad (GST_ELEMENT (ogg), ogg->sinkpad); ogg->chain_lock = g_mutex_new (); + ogg->push_lock = g_mutex_new (); ogg->chains = g_array_new (FALSE, TRUE, sizeof (GstOggChain *)); ogg->newsegment = NULL; @@ -1341,6 +1859,7 @@ gst_ogg_demux_finalize (GObject * object) g_array_free (ogg->chains, TRUE); g_mutex_free (ogg->chain_lock); + g_mutex_free (ogg->push_lock); ogg_sync_clear (&ogg->sync); if (ogg->newsegment) @@ -1366,6 +1885,7 @@ gst_ogg_demux_reset_streams (GstOggDemux * ogg) stream->map.accumulated_granule = 0; } ogg->building_chain = chain; + GST_DEBUG_OBJECT (ogg, "Resetting current chain"); ogg->current_chain = NULL; ogg->resync = TRUE; } @@ -1386,16 +1906,57 @@ gst_ogg_demux_sink_event (GstPad * pad, GstEvent * event) GST_DEBUG_OBJECT (ogg, "got a flush stop event"); ogg_sync_reset (&ogg->sync); res = gst_ogg_demux_send_event (ogg, event); - gst_ogg_demux_reset_streams (ogg); + if (ogg->pullmode || ogg->push_state != PUSH_DURATION) { + /* it's starting to feel reaaaally dirty :( + if we're on a spliced seek to get duration, don't reset streams, + we'll need them for the delayed seek */ + gst_ogg_demux_reset_streams (ogg); + } break; case GST_EVENT_NEWSEGMENT: GST_DEBUG_OBJECT (ogg, "got a new segment event"); + { + gboolean update; + GstFormat format; + gdouble rate, arate; + gint64 start, stop, time; + + gst_event_parse_new_segment_full (event, &update, &rate, &arate, + &format, &start, &stop, &time); + if (format == GST_FORMAT_BYTES) { + if (!ogg->pullmode) { + GST_PUSH_LOCK (ogg); + ogg->push_byte_offset = start; + ogg->push_last_seek_offset = start; + GST_PUSH_UNLOCK (ogg); + } + } + } gst_event_unref (event); res = TRUE; break; case GST_EVENT_EOS: { GST_DEBUG_OBJECT (ogg, "got an EOS event"); +#if 0 + /* This would be what is needed (recover from EOS by going on to + the next step (issue the delayed seek)), but it does not work + if there is a queue2 upstream - see more details comment in + gst_ogg_pad_submit_page. + If I could find a way to bypass queue2 behavior, this should + be enabled. */ + GST_PUSH_LOCK (ogg); + if (ogg->push_state == PUSH_DURATION) { + GST_DEBUG_OBJECT (ogg, "Got EOS while determining length"); + res = gst_ogg_demux_seek_back_after_push_duration_check_unlock (ogg); + if (res != GST_FLOW_OK) { + GST_DEBUG_OBJECT (ogg, "Error seeking back after duration check: %d", + res); + } + break; + } + GST_PUSH_UNLOCK (ogg); +#endif res = gst_ogg_demux_send_event (ogg, event); if (ogg->current_chain == NULL) { GST_ELEMENT_ERROR (ogg, STREAM, DEMUX, (NULL), @@ -1436,6 +1997,12 @@ gst_ogg_demux_submit_buffer (GstOggDemux * ogg, GstBuffer * buffer) if (G_UNLIKELY (ogg_sync_wrote (&ogg->sync, size) < 0)) goto write_failed; + if (!ogg->pullmode) { + GST_PUSH_LOCK (ogg); + ogg->push_byte_offset += size; + GST_PUSH_UNLOCK (ogg); + } + done: gst_buffer_unref (buffer); @@ -1706,11 +2273,9 @@ gst_ogg_demux_deactivate_current_chain (GstOggDemux * ogg) pad->added = FALSE; } - /* if we cannot seek back to the chain, we can destroy the chain - * completely */ - if (!ogg->pullmode) { - gst_ogg_chain_free (chain); - } + /* With push mode seeking implemented, we can now seek back to the chain, + so we do not destroy it */ + GST_DEBUG_OBJECT (ogg, "Resetting current chain"); ogg->current_chain = NULL; return TRUE; @@ -1757,6 +2322,23 @@ gst_ogg_demux_set_header_on_caps (GstOggDemux * ogg, GstCaps * caps, return caps; } +static void +gst_ogg_demux_push_queued_buffers (GstOggDemux * ogg, GstOggPad * pad) +{ + GList *walk; + + /* push queued packets */ + for (walk = pad->map.queued; walk; walk = g_list_next (walk)) { + ogg_packet *p = walk->data; + + gst_ogg_demux_chain_peer (pad, p, TRUE); + _ogg_packet_free (p); + } + /* and free the queued buffers */ + g_list_free (pad->map.queued); + pad->map.queued = NULL; +} + static gboolean gst_ogg_demux_activate_chain (GstOggDemux * ogg, GstOggChain * chain, GstEvent * event) @@ -1769,6 +2351,11 @@ gst_ogg_demux_activate_chain (GstOggDemux * ogg, GstOggChain * chain, if (chain == ogg->current_chain) { if (event) gst_event_unref (event); + + for (i = 0; i < chain->streams->len; i++) { + GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); + gst_ogg_demux_push_queued_buffers (ogg, pad); + } return TRUE; } @@ -1809,6 +2396,7 @@ gst_ogg_demux_activate_chain (GstOggDemux * ogg, GstOggChain * chain, /* after adding the new pads, remove the old pads */ gst_ogg_demux_deactivate_current_chain (ogg); + GST_DEBUG_OBJECT (ogg, "Setting current chain to %p", chain); ogg->current_chain = chain; /* we are finished now */ @@ -1853,16 +2441,7 @@ gst_ogg_demux_activate_chain (GstOggDemux * ogg, GstOggChain * chain, } GST_DEBUG_OBJECT (ogg, "pushing queued buffers"); - /* push queued packets */ - for (walk = pad->map.queued; walk; walk = g_list_next (walk)) { - ogg_packet *p = walk->data; - - gst_ogg_demux_chain_peer (pad, p, TRUE); - _ogg_packet_free (p); - } - /* and free the queued buffers */ - g_list_free (pad->map.queued); - pad->map.queued = NULL; + gst_ogg_demux_push_queued_buffers (ogg, pad); } return TRUE; } @@ -2486,6 +3065,85 @@ no_chain: } } +static gboolean +gst_ogg_demux_get_duration_push (GstOggDemux * ogg, int flags) +{ + /* In push mode, we get to the end of the stream to get the duration */ + gint64 position; + GstEvent *sevent; + gboolean res; + + /* A full Ogg page can be almost 64 KB. There's no guarantee that there'll be a + granpos there, but it's fairly likely */ + position = + ogg->push_byte_length - DURATION_CHUNK_OFFSET - EOS_AVOIDANCE_THRESHOLD; + if (position < 0) + position = 0; + + GST_DEBUG_OBJECT (ogg, + "Getting duration, seeking near the end, to %" G_GINT64_FORMAT, position); + ogg->push_state = PUSH_DURATION; + /* do not read the last byte */ + sevent = gst_event_new_seek (1.0, GST_FORMAT_BYTES, flags, GST_SEEK_TYPE_SET, + position, GST_SEEK_TYPE_SET, ogg->push_byte_length - 1); + res = gst_pad_push_event (ogg->sinkpad, sevent); + if (res) { + GST_DEBUG_OBJECT (ogg, "Seek succesful"); + return TRUE; + } else { + GST_INFO_OBJECT (ogg, "Seek failed, duration will stay unknown"); + ogg->push_state = PUSH_PLAYING; + return FALSE; + } +} + +static gboolean +gst_ogg_demux_check_duration_push (GstOggDemux * ogg, GstSeekFlags flags, + GstEvent * event) +{ + if (ogg->push_byte_length < 0) { + GstPad *peer; + + GST_DEBUG_OBJECT (ogg, "Trying to find byte/time length"); + if ((peer = gst_pad_get_peer (ogg->sinkpad)) != NULL) { + GstFormat format = GST_FORMAT_BYTES; + gint64 length; + int res; + + res = gst_pad_query_duration (peer, &format, &length); + if (res && length > 0) { + ogg->push_byte_length = length; + GST_DEBUG_OBJECT (ogg, + "File byte length %" G_GINT64_FORMAT, ogg->push_byte_length); + } + format = GST_FORMAT_TIME; + res = gst_pad_query_duration (peer, &format, &length); + gst_object_unref (peer); + if (res && length >= 0) { + ogg->push_time_length = length; + GST_DEBUG_OBJECT (ogg, "File time length %" GST_TIME_FORMAT, + GST_TIME_ARGS (ogg->push_time_length)); + } else if (!ogg->push_disable_seeking) { + gboolean res; + + res = gst_ogg_demux_get_duration_push (ogg, flags); + if (res) { + GST_DEBUG_OBJECT (ogg, + "File time length unknown, trying to determine"); + ogg->push_mode_seek_delayed_event = NULL; + if (event) { + GST_DEBUG_OBJECT (ogg, + "Let me intercept this innocent looking seek request"); + ogg->push_mode_seek_delayed_event = gst_event_copy (event); + } + return FALSE; + } + } + } + } + return TRUE; +} + static gboolean gst_ogg_demux_perform_seek_push (GstOggDemux * ogg, GstEvent * event) { @@ -2499,6 +3157,9 @@ gst_ogg_demux_perform_seek_push (GstOggDemux * ogg, GstEvent * event) GstEvent *sevent; GstOggChain *chain; gint64 best, best_time; + gint i; + + GST_DEBUG_OBJECT (ogg, "Push mode seek request received"); gst_event_parse_seek (event, &rate, &format, &flags, &start_type, &start, &stop_type, &stop); @@ -2508,39 +3169,129 @@ gst_ogg_demux_perform_seek_push (GstOggDemux * ogg, GstEvent * event) goto error; } + if (start_type != GST_SEEK_TYPE_SET) { + GST_DEBUG_OBJECT (ogg, "can only seek to a SET target"); + goto error; + } + + if (!(flags & GST_SEEK_FLAG_FLUSH)) { + GST_DEBUG_OBJECT (ogg, "can only do flushing seeks"); + goto error; + } + + GST_DEBUG_OBJECT (ogg, "Push mode seek request: %" GST_TIME_FORMAT, + GST_TIME_ARGS (start)); + chain = ogg->current_chain; - if (!chain) + if (!chain) { + GST_WARNING_OBJECT (ogg, "No chain to seek on"); + goto error; + } + + /* start accessing push_* members */ + GST_PUSH_LOCK (ogg); + + /* not if we disabled seeking (chained streams) */ + if (ogg->push_disable_seeking) { + GST_DEBUG_OBJECT (ogg, "Seeking disabled"); + goto error_locked; + } + + /* not when we're trying to work out duration */ + if (ogg->push_state == PUSH_DURATION) { + GST_DEBUG_OBJECT (ogg, "Busy working out duration, try again later"); + goto error_locked; + } + + /* actually, not if we're doing any seeking already */ + if (ogg->push_state != PUSH_PLAYING) { + GST_DEBUG_OBJECT (ogg, "Already doing some seeking, try again later"); + goto error_locked; + } + + /* on the first seek, get length if we can */ + if (!gst_ogg_demux_check_duration_push (ogg, flags, event)) { + GST_PUSH_UNLOCK (ogg); return FALSE; + } if (do_index_search (ogg, chain, 0, -1, 0, -1, start, &best, &best_time)) { /* the index gave some result */ GST_DEBUG_OBJECT (ogg, "found offset %" G_GINT64_FORMAT " with time %" G_GUINT64_FORMAT, best, best_time); - start = best; - } else if ((bitrate = ogg->bitrate) > 0) { - /* try with bitrate convert the seek positions to bytes */ - if (start_type != GST_SEEK_TYPE_NONE) { - start = gst_util_uint64_scale (start, bitrate, 8 * GST_SECOND); - } - if (stop_type != GST_SEEK_TYPE_NONE) { - stop = gst_util_uint64_scale (stop, bitrate, 8 * GST_SECOND); - } } else { - /* we don't know */ - res = FALSE; + if (ogg->push_time_length > 0) { + /* if we know the time length, we know the full segment bitrate */ + GST_DEBUG_OBJECT (ogg, "Using real file bitrate"); + bitrate = + gst_util_uint64_scale (ogg->push_byte_length, 8 * GST_SECOND, + ogg->push_time_length); + } else if (ogg->push_time_offset > 0) { + /* get a first approximation using known bitrate to the current position */ + GST_DEBUG_OBJECT (ogg, "Using file bitrate so far"); + bitrate = + gst_util_uint64_scale (ogg->push_byte_offset, 8 * GST_SECOND, + ogg->push_time_offset); + } else if (ogg->bitrate > 0) { + /* nominal bitrate is better than nothing, even if it lies often */ + GST_DEBUG_OBJECT (ogg, "Using nominal bitrate"); + bitrate = ogg->bitrate; + } else { + /* meh */ + GST_DEBUG_OBJECT (ogg, + "At stream start, and no nominal bitrate, using some random magic " + "number to seed"); + /* the bisection, once started, should give us a better approximation */ + bitrate = 1000; + } + best = gst_util_uint64_scale (start, bitrate, 8 * GST_SECOND); } - if (res) { - GST_DEBUG_OBJECT (ogg, - "seeking to %" G_GINT64_FORMAT " - %" G_GINT64_FORMAT, start, stop); - /* do seek */ - sevent = gst_event_new_seek (rate, GST_FORMAT_BYTES, flags, - start_type, start, stop_type, stop); + /* offset by typical page length, and ensure our best guess is within + reasonable bounds */ + best -= CHUNKSIZE; + if (best < 0) + best = 0; + if (ogg->push_byte_length > 0 && best >= ogg->push_byte_length) + best = ogg->push_byte_length - 1; - res = gst_pad_push_event (ogg->sinkpad, sevent); + /* set up bisection search */ + ogg->push_offset0 = 0; + ogg->push_offset1 = ogg->push_byte_length - 1; + ogg->push_time0 = ogg->push_start_time; + ogg->push_time1 = ogg->push_time_length; + ogg->push_seek_time_target = start; + ogg->push_seek_time_original_target = start; + ogg->push_state = PUSH_BISECT1; + + /* reset pad push mode seeking state */ + for (i = 0; i < chain->streams->len; i++) { + GstOggPad *pad = g_array_index (chain->streams, GstOggPad *, i); + pad->push_kf_time = GST_CLOCK_TIME_NONE; + pad->push_sync_time = GST_CLOCK_TIME_NONE; } + GST_DEBUG_OBJECT (ogg, + "Setting up bisection search for %" G_GINT64_FORMAT " - %" G_GINT64_FORMAT + " (time %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT ")", ogg->push_offset0, + ogg->push_offset1, GST_TIME_ARGS (ogg->push_time0), + GST_TIME_ARGS (ogg->push_time1)); + GST_DEBUG_OBJECT (ogg, + "Target time is %" GST_TIME_FORMAT ", best first guess is %" + G_GINT64_FORMAT, GST_TIME_ARGS (ogg->push_seek_time_target), best); + + ogg->push_seek_rate = rate; + ogg->push_seek_flags = flags; + ogg->push_mode_seek_delayed_event = NULL; + ogg->push_bisection_steps[0] = 1; + ogg->push_bisection_steps[1] = 0; + sevent = gst_event_new_seek (rate, GST_FORMAT_BYTES, flags, + start_type, best, GST_SEEK_TYPE_NONE, -1); + + GST_PUSH_UNLOCK (ogg); + res = gst_pad_push_event (ogg->sinkpad, sevent); + return res; /* ERRORS */ @@ -2549,6 +3300,10 @@ error: GST_DEBUG_OBJECT (ogg, "seek failed"); return FALSE; } + +error_locked: + GST_PUSH_UNLOCK (ogg); + goto error; } static gboolean @@ -3142,11 +3897,31 @@ gst_ogg_demux_handle_page (GstOggDemux * ogg, ogg_page * page) if (pad) { result = gst_ogg_pad_submit_page (pad, page); } else { - /* no pad. This means an ogg page without bos has been seen for this - * serialno. we just ignore it but post a warning... */ - GST_ELEMENT_WARNING (ogg, STREAM, DECODE, - (NULL), ("unknown ogg pad for serial %08x detected", serialno)); - return GST_FLOW_OK; + GST_PUSH_LOCK (ogg); + if (!ogg->pullmode && !ogg->push_disable_seeking) { + /* no pad while probing for duration, we must have a chained stream, + and we don't support them, so back off */ + GST_INFO_OBJECT (ogg, "We seem to have a chained stream, we won't seek"); + if (ogg->push_state == PUSH_DURATION) { + GstFlowReturn res; + + res = gst_ogg_demux_seek_back_after_push_duration_check_unlock (ogg); + if (res != GST_FLOW_OK) + return res; + } + + /* only once we seeked back */ + GST_PUSH_LOCK (ogg); + ogg->push_disable_seeking = TRUE; + } else { + GST_PUSH_UNLOCK (ogg); + /* no pad. This means an ogg page without bos has been seen for this + * serialno. we just ignore it but post a warning... */ + GST_ELEMENT_WARNING (ogg, STREAM, DECODE, + (NULL), ("unknown ogg pad for serial %08x detected", serialno)); + return GST_FLOW_OK; + } + GST_PUSH_UNLOCK (ogg); } return result; @@ -3171,8 +3946,11 @@ gst_ogg_demux_chain (GstPad * pad, GstBuffer * buffer) ogg = GST_OGG_DEMUX (GST_OBJECT_PARENT (pad)); - GST_DEBUG_OBJECT (ogg, "chain"); + GST_DEBUG_OBJECT (ogg, "enter"); result = gst_ogg_demux_submit_buffer (ogg, buffer); + if (result < 0) { + GST_DEBUG_OBJECT (ogg, "gst_ogg_demux_submit_buffer returned %d", result); + } while (result == GST_FLOW_OK) { ogg_page page; @@ -3186,11 +3964,15 @@ gst_ogg_demux_chain (GstPad * pad, GstBuffer * buffer) GST_DEBUG_OBJECT (ogg, "discont in page found, continuing"); } else { result = gst_ogg_demux_handle_page (ogg, &page); + if (result < 0) { + GST_DEBUG_OBJECT (ogg, "gst_ogg_demux_handle_page returned %d", result); + } } } if (ret == 0 || result == GST_FLOW_OK) { gst_ogg_demux_sync_streams (ogg); } + GST_DEBUG_OBJECT (ogg, "leave with %d", result); return result; } @@ -3608,6 +4390,14 @@ gst_ogg_demux_change_state (GstElement * element, GstStateChange transition) ogg->bitrate = 0; ogg->segment_running = FALSE; ogg->total_time = -1; + GST_PUSH_LOCK (ogg); + ogg->push_byte_offset = 0; + ogg->push_byte_length = -1; + ogg->push_time_length = GST_CLOCK_TIME_NONE; + ogg->push_time_offset = GST_CLOCK_TIME_NONE; + ogg->push_disable_seeking = FALSE; + ogg->push_state = PUSH_PLAYING; + GST_PUSH_UNLOCK (ogg); gst_segment_init (&ogg->segment, GST_FORMAT_TIME); break; case GST_STATE_CHANGE_PAUSED_TO_PLAYING: diff --git a/ext/ogg/gstoggdemux.h b/ext/ogg/gstoggdemux.h index 031627baff..712dc8f05c 100644 --- a/ext/ogg/gstoggdemux.h +++ b/ext/ogg/gstoggdemux.h @@ -113,6 +113,10 @@ struct _GstOggPad gboolean is_eos; gboolean added; + + /* push mode seeking */ + GstClockTime push_kf_time; + GstClockTime push_sync_time; }; struct _GstOggPadClass @@ -162,6 +166,29 @@ struct _GstOggDemux gint64 basetime; gint64 prestime; + /* push mode seeking support */ + GMutex *push_lock; /* we need the lock to protect the push mode variables */ + gint64 push_byte_offset; /* where were are at in the stream, in bytes */ + gint64 push_byte_length; /* length in bytes of the stream, -1 if unknown */ + GstClockTime push_time_length; /* length in time of the stream */ + GstClockTime push_start_time; /* start time of the stream */ + GstClockTime push_time_offset; /* where were are at in the stream, in time */ + enum { PUSH_PLAYING, PUSH_DURATION, PUSH_BISECT1, PUSH_LINEAR1, PUSH_BISECT2, PUSH_LINEAR2 } push_state; + + GstClockTime push_seek_time_original_target; + GstClockTime push_seek_time_target; + gint64 push_last_seek_offset; + GstClockTime push_last_seek_time; + gint64 push_offset0, push_offset1; /* bisection search offset bounds */ + GstClockTime push_time0, push_time1; /* bisection search time bounds */ + + double push_seek_rate; + GstSeekFlags push_seek_flags; + GstEvent *push_mode_seek_delayed_event; + gboolean push_disable_seeking; + + gint push_bisection_steps[2]; + /* ogg stuff */ ogg_sync_state sync; }; From 89fc5b4bd8714d4d666d5cee8139495712389e20 Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Wed, 14 Sep 2011 12:23:19 +0100 Subject: [PATCH 18/43] oggdemux: fix wedge when seeking twice quickly in push mode This could happen when testing with navseek, and pressing right and left at roughly the same time. The current chain is temporarily moved away, and this caused the flush events not to be sent to the source pads, which would cause the data queues downstream to reject incoming data after the seek, and shut down, wedging the pipeline. Now, I can't really decide whether this is a nasty steaming hack or a good fix, but it certainly does fix the issue, and does not seem to break anything else so far. https://bugzilla.gnome.org/show_bug.cgi?id=621897 --- ext/ogg/gstoggdemux.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ext/ogg/gstoggdemux.c b/ext/ogg/gstoggdemux.c index 16f559e83e..ade86450ed 100644 --- a/ext/ogg/gstoggdemux.c +++ b/ext/ogg/gstoggdemux.c @@ -3982,6 +3982,9 @@ gst_ogg_demux_send_event (GstOggDemux * ogg, GstEvent * event) GstOggChain *chain = ogg->current_chain; gboolean res = TRUE; + if (!chain) + chain = ogg->building_chain; + if (chain) { gint i; @@ -3992,6 +3995,8 @@ gst_ogg_demux_send_event (GstOggDemux * ogg, GstEvent * event) GST_DEBUG_OBJECT (pad, "Pushing event %" GST_PTR_FORMAT, event); res &= gst_pad_push_event (GST_PAD (pad), event); } + } else { + GST_WARNING_OBJECT (ogg, "No chain to forward event to"); } gst_event_unref (event); From 049e2756325c318468c393f4e562b91769731da9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 16 Sep 2011 20:11:56 +0100 Subject: [PATCH 19/43] oggdemux: minor printf format fix --- ext/ogg/gstoggdemux.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/ogg/gstoggdemux.c b/ext/ogg/gstoggdemux.c index ade86450ed..74cc026d53 100644 --- a/ext/ogg/gstoggdemux.c +++ b/ext/ogg/gstoggdemux.c @@ -1154,7 +1154,7 @@ gst_ogg_demux_estimate_bisection_target (GstOggDemux * ogg) GST_DEBUG_OBJECT (ogg, "Local bitrate on the %" GST_TIME_FORMAT " - %" GST_TIME_FORMAT " segment: %" G_GINT64_FORMAT, GST_TIME_ARGS (ogg->push_time0), - GST_TIME_ARGS (ogg->push_time1)); + GST_TIME_ARGS (ogg->push_time1), segment_bitrate); best = ogg->push_offset0 + gst_util_uint64_scale (ogg->push_seek_time_target - ogg->push_time0, From 15d8082a55965f7804dcbbb3d5b8a1023d86db61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Fri, 16 Sep 2011 20:14:39 +0100 Subject: [PATCH 20/43] oggdemux: remove superfluous check in newsegment event handler If we get a newsegment event from upstream, we can be quite sure we're not operating pull-based. --- ext/ogg/gstoggdemux.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ext/ogg/gstoggdemux.c b/ext/ogg/gstoggdemux.c index 74cc026d53..58a1a7c08a 100644 --- a/ext/ogg/gstoggdemux.c +++ b/ext/ogg/gstoggdemux.c @@ -1924,12 +1924,13 @@ gst_ogg_demux_sink_event (GstPad * pad, GstEvent * event) gst_event_parse_new_segment_full (event, &update, &rate, &arate, &format, &start, &stop, &time); if (format == GST_FORMAT_BYTES) { - if (!ogg->pullmode) { - GST_PUSH_LOCK (ogg); - ogg->push_byte_offset = start; - ogg->push_last_seek_offset = start; - GST_PUSH_UNLOCK (ogg); - } + GST_PUSH_LOCK (ogg); + ogg->push_byte_offset = start; + ogg->push_last_seek_offset = start; + GST_PUSH_UNLOCK (ogg); + } else { + GST_WARNING_OBJECT (ogg, "unexpected segment format: %s", + gst_format_get_name (format)); } } gst_event_unref (event); From d094913a612753e5811035c9f2b3c8a4039ec808 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 19 Sep 2011 09:34:08 +0200 Subject: [PATCH 21/43] textoverlay: Protect against accessing the NULL parent of the pads during shutdown Fixes bug #658901. --- ext/pango/gsttextoverlay.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/ext/pango/gsttextoverlay.c b/ext/pango/gsttextoverlay.c index e3e3b5607e..e740ba6f40 100644 --- a/ext/pango/gsttextoverlay.c +++ b/ext/pango/gsttextoverlay.c @@ -766,6 +766,8 @@ gst_text_overlay_setcaps_txt (GstPad * pad, GstCaps * caps) GstStructure *structure; overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); + if (G_UNLIKELY (!overlay)) + return FALSE; structure = gst_caps_get_structure (caps, 0); overlay->have_pango_markup = @@ -792,6 +794,8 @@ gst_text_overlay_setcaps (GstPad * pad, GstCaps * caps) g_return_val_if_fail (gst_caps_is_fixed (caps), FALSE); overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); + if (G_UNLIKELY (!overlay)) + return FALSE; overlay->width = 0; overlay->height = 0; @@ -1037,6 +1041,8 @@ gst_text_overlay_src_query (GstPad * pad, GstQuery * query) GstTextOverlay *overlay = NULL; overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); + if (G_UNLIKELY (!overlay)) + return FALSE; ret = gst_pad_peer_query (overlay->video_sinkpad, query); @@ -1052,6 +1058,10 @@ gst_text_overlay_src_event (GstPad * pad, GstEvent * event) GstTextOverlay *overlay = NULL; overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); + if (G_UNLIKELY (!overlay)) { + gst_event_unref (event); + return FALSE; + } switch (GST_EVENT_TYPE (event)) { case GST_EVENT_SEEK:{ @@ -1114,6 +1124,8 @@ gst_text_overlay_getcaps (GstPad * pad) GstCaps *caps; overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); + if (G_UNLIKELY (!overlay)) + return gst_caps_copy (gst_pad_get_pad_template_caps (pad)); if (pad == overlay->srcpad) otherpad = overlay->video_sinkpad; @@ -2125,6 +2137,8 @@ gst_text_overlay_text_pad_link (GstPad * pad, GstPad * peer) GstTextOverlay *overlay; overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); + if (G_UNLIKELY (!overlay)) + return GST_PAD_LINK_REFUSED; GST_DEBUG_OBJECT (overlay, "Text pad linked"); @@ -2157,6 +2171,10 @@ gst_text_overlay_text_event (GstPad * pad, GstEvent * event) GstTextOverlay *overlay = NULL; overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); + if (G_UNLIKELY (!overlay)) { + gst_event_unref (event); + return FALSE; + } GST_LOG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event)); @@ -2242,6 +2260,10 @@ gst_text_overlay_video_event (GstPad * pad, GstEvent * event) GstTextOverlay *overlay = NULL; overlay = GST_TEXT_OVERLAY (gst_pad_get_parent (pad)); + if (G_UNLIKELY (!overlay)) { + gst_event_unref (event); + return FALSE; + } GST_DEBUG_OBJECT (pad, "received event %s", GST_EVENT_TYPE_NAME (event)); @@ -2314,6 +2336,9 @@ gst_text_overlay_video_bufferalloc (GstPad * pad, guint64 offset, guint size, GstFlowReturn ret = GST_FLOW_WRONG_STATE; GstPad *allocpad; + if (G_UNLIKELY (!overlay)) + return GST_FLOW_WRONG_STATE; + GST_OBJECT_LOCK (overlay); allocpad = overlay->srcpad ? gst_object_ref (overlay->srcpad) : NULL; GST_OBJECT_UNLOCK (overlay); From b1bb1e9b26a9d4a1dd907063fdfb12e55c38e69c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 19 Sep 2011 11:24:47 +0200 Subject: [PATCH 22/43] subtitleoverlay: Implement support for switching between raw and non-raw video streams --- gst/playback/gstsubtitleoverlay.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/gst/playback/gstsubtitleoverlay.c b/gst/playback/gstsubtitleoverlay.c index 155203be59..d198d732d9 100644 --- a/gst/playback/gstsubtitleoverlay.c +++ b/gst/playback/gstsubtitleoverlay.c @@ -1747,18 +1747,36 @@ static gboolean gst_subtitle_overlay_video_sink_setcaps (GstPad * pad, GstCaps * caps) { GstSubtitleOverlay *self = GST_SUBTITLE_OVERLAY (gst_pad_get_parent (pad)); + GstPad *target; gboolean ret = TRUE; gint fps_n, fps_d; GST_DEBUG_OBJECT (pad, "Setting caps: %" GST_PTR_FORMAT, caps); + target = + gst_ghost_pad_get_target (GST_GHOST_PAD_CAST (self->subtitle_sinkpad)); + + GST_SUBTITLE_OVERLAY_LOCK (self); + + if (!target || !gst_pad_accept_caps (target, caps)) { + GST_DEBUG_OBJECT (pad, "Target did not accept caps -- reconfiguring"); + + gst_pad_set_blocked_async_full (self->subtitle_block_pad, TRUE, + _pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + + gst_pad_set_blocked_async_full (self->video_block_pad, TRUE, + _pad_blocked_cb, gst_object_ref (self), + (GDestroyNotify) gst_object_unref); + } + if (!gst_video_parse_caps_framerate (caps, &fps_n, &fps_d)) { GST_ERROR_OBJECT (pad, "Failed to parse framerate from caps"); ret = FALSE; + GST_SUBTITLE_OVERLAY_UNLOCK (self); goto out; } - GST_SUBTITLE_OVERLAY_LOCK (self); if (self->fps_n != fps_n || self->fps_d != fps_d) { GST_DEBUG_OBJECT (self, "New video fps: %d/%d", fps_n, fps_d); self->fps_n = fps_n; @@ -1770,6 +1788,8 @@ gst_subtitle_overlay_video_sink_setcaps (GstPad * pad, GstCaps * caps) ret = gst_ghost_pad_setcaps_default (pad, caps); out: + if (target) + gst_object_unref (target); gst_object_unref (self); return ret; } From e574f58e7126b4b2c2fe1efabeba20e33865b787 Mon Sep 17 00:00:00 2001 From: Mark Nauwelaerts Date: Mon, 1 Aug 2011 07:54:02 +0200 Subject: [PATCH 23/43] rtspdefs: add RTCP-Interval header --- gst-libs/gst/rtsp/gstrtspdefs.c | 3 +++ gst-libs/gst/rtsp/gstrtspdefs.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/gst-libs/gst/rtsp/gstrtspdefs.c b/gst-libs/gst/rtsp/gstrtspdefs.c index 9935c670b2..ee74b9830c 100644 --- a/gst-libs/gst/rtsp/gstrtspdefs.c +++ b/gst-libs/gst/rtsp/gstrtspdefs.c @@ -177,6 +177,9 @@ static struct rtsp_header rtsp_headers[] = { {"X-Server-IP-Address", FALSE}, {"X-Sessioncookie", FALSE}, + /* Since 0.10.36 */ + {"RTCP-Interval", FALSE}, + {NULL, FALSE} }; diff --git a/gst-libs/gst/rtsp/gstrtspdefs.h b/gst-libs/gst/rtsp/gstrtspdefs.h index d67babd1c0..3727bf29a6 100644 --- a/gst-libs/gst/rtsp/gstrtspdefs.h +++ b/gst-libs/gst/rtsp/gstrtspdefs.h @@ -333,6 +333,9 @@ typedef enum { GST_RTSP_HDR_X_SERVER_IP_ADDRESS, /* X-Server-IP-Address */ GST_RTSP_HDR_X_SESSIONCOOKIE, /* X-Sessioncookie */ + /* Since 0.10.36 */ + GST_RTSP_HDR_RTCP_INTERVAL, /* RTCP-Interval */ + GST_RTSP_HDR_LAST } GstRTSPHeaderField; From 936e87cdcfe766c4ebf9186f722de1278dae950c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 19 Sep 2011 11:53:02 +0200 Subject: [PATCH 24/43] decodebin2: Fix non-prerolling pipelines and not-linked errors if a parser is available but no decoder Fixes bug #658846. --- gst/playback/gstdecodebin2.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gst/playback/gstdecodebin2.c b/gst/playback/gstdecodebin2.c index 17f19f8da3..a1da6c5f08 100644 --- a/gst/playback/gstdecodebin2.c +++ b/gst/playback/gstdecodebin2.c @@ -1379,6 +1379,7 @@ analyze_new_pad (GstDecodeBin * dbin, GstElement * src, GstPad * pad, GstElementFactory *factory; const gchar *classification; gboolean is_parser_converter = FALSE; + gboolean res; GST_DEBUG_OBJECT (dbin, "Pad %s:%s caps:%" GST_PTR_FORMAT, GST_DEBUG_PAD_NAME (pad), caps); @@ -1605,7 +1606,7 @@ analyze_new_pad (GstDecodeBin * dbin, GstElement * src, GstPad * pad, /* 1.h else continue autoplugging something from the list. */ GST_LOG_OBJECT (pad, "Let's continue discovery on this pad"); - connect_pad (dbin, src, dpad, pad, caps, factories, chain); + res = connect_pad (dbin, src, dpad, pad, caps, factories, chain); /* Need to unref the capsfilter srcpad here if * we inserted a capsfilter */ @@ -1615,6 +1616,9 @@ analyze_new_pad (GstDecodeBin * dbin, GstElement * src, GstPad * pad, gst_object_unref (dpad); g_value_array_free (factories); + if (!res) + goto unknown_type; + return; expose_pad: From 9d2bcb08fea8ce3ca56479779d573dfcd4fbe00c Mon Sep 17 00:00:00 2001 From: Edward Hervey Date: Thu, 28 Jul 2011 16:44:33 +0000 Subject: [PATCH 25/43] decodebin2: Rewrite EOS-handling code This is now really threadsafe and improves switching between different groups. --- gst/playback/gstdecodebin2.c | 208 ++++++++++++++++++++++------------- 1 file changed, 132 insertions(+), 76 deletions(-) diff --git a/gst/playback/gstdecodebin2.c b/gst/playback/gstdecodebin2.c index a1da6c5f08..741c45dcf6 100644 --- a/gst/playback/gstdecodebin2.c +++ b/gst/playback/gstdecodebin2.c @@ -433,7 +433,6 @@ static void gst_decode_group_free (GstDecodeGroup * group); static GstDecodeGroup *gst_decode_group_new (GstDecodeBin * dbin, GstDecodeChain * chain); static gboolean gst_decode_chain_is_complete (GstDecodeChain * chain); -static gboolean gst_decode_chain_handle_eos (GstDecodeChain * chain); static gboolean gst_decode_chain_expose (GstDecodeChain * chain, GList ** endpads, gboolean * missing_plugin); static gboolean gst_decode_chain_is_drained (GstDecodeChain * chain); @@ -3040,95 +3039,152 @@ out: return complete; } +static gboolean +drain_and_switch_chains (GstDecodeChain * chain, GstDecodePad * drainpad, + gboolean * last_group, gboolean * drained, gboolean * switched); +/* drain_and_switch_chains/groups: + * + * CALL WITH CHAIN LOCK (or group parent) TAKEN ! + * + * Goes down the chains/groups until it finds the chain + * to which the drainpad belongs. + * + * It marks that pad/chain as drained and then will figure + * out which group to switch to or not. + * + * last_chain will be set to TRUE if the group to which the + * pad belongs is the last one. + * + * drained will be set to TRUE if the chain/group is drained. + * + * Returns: TRUE if the chain contained the target pad */ +static gboolean +drain_and_switch_group (GstDecodeGroup * group, GstDecodePad * drainpad, + gboolean * last_group, gboolean * drained, gboolean * switched) +{ + gboolean handled = FALSE; + gboolean alldrained = TRUE; + GList *tmp; + + GST_DEBUG ("Checking group %p (target pad %s:%s)", + group, GST_DEBUG_PAD_NAME (drainpad)); + + /* Definitely can't be in drained groups */ + if (G_UNLIKELY (group->drained)) { + goto beach; + } + + /* Figure out if all our chains are drained with the + * new information */ + for (tmp = group->children; tmp; tmp = tmp->next) { + GstDecodeChain *chain = (GstDecodeChain *) tmp->data; + gboolean subdrained; + + handled |= + drain_and_switch_chains (chain, drainpad, last_group, &subdrained, + switched); + if (!subdrained) + alldrained = FALSE; + } + +beach: + GST_DEBUG ("group %p (last_group:%d, drained:%d, switched:%d, handled:%d)", + group, *last_group, alldrained, *switched, handled); + *drained = alldrained; + return handled; +} + +static gboolean +drain_and_switch_chains (GstDecodeChain * chain, GstDecodePad * drainpad, + gboolean * last_group, gboolean * drained, gboolean * switched) +{ + gboolean handled = FALSE; + GstDecodeBin *dbin = chain->dbin; + + GST_DEBUG ("Checking chain %p (target pad %s:%s)", + chain, GST_DEBUG_PAD_NAME (drainpad)); + + CHAIN_MUTEX_LOCK (chain); + + if (chain->endpad) { + /* Check if we're reached the target endchain */ + if (chain == drainpad->chain) { + GST_DEBUG ("Found the target chain"); + drainpad->drained = TRUE; + handled = TRUE; + } + + *drained = chain->endpad->drained; + goto beach; + } + + /* We known there are groups to switch to */ + if (chain->next_groups) + *last_group = FALSE; + + /* Check the active group */ + if (chain->active_group) { + gboolean subdrained = FALSE; + handled = drain_and_switch_group (chain->active_group, drainpad, + last_group, &subdrained, switched); + + /* The group is drained, see if we can switch to another */ + if (handled && subdrained && !*switched) { + if (chain->next_groups) { + /* Switch to next group */ + GST_DEBUG_OBJECT (dbin, "Hiding current group %p", chain->active_group); + gst_decode_group_hide (chain->active_group); + chain->old_groups = + g_list_prepend (chain->old_groups, chain->active_group); + GST_DEBUG_OBJECT (dbin, "Switching to next group %p", + chain->next_groups->data); + chain->active_group = chain->next_groups->data; + chain->next_groups = + g_list_delete_link (chain->next_groups, chain->next_groups); + *switched = TRUE; + *drained = FALSE; + } else { + GST_DEBUG ("Group %p was the last in chain %p", chain->active_group, + chain); + *drained = TRUE; + /* We're drained ! */ + } + } + } + +beach: + CHAIN_MUTEX_UNLOCK (chain); + + GST_DEBUG ("Chain %p (handled:%d, last_group:%d, drained:%d, switched:%d)", + chain, handled, *last_group, *drained, *switched); + + return handled; +} + /* check if the group is drained, meaning all pads have seen an EOS * event. */ static gboolean gst_decode_pad_handle_eos (GstDecodePad * pad) { + gboolean last_group = TRUE; + gboolean switched = FALSE; + gboolean drained = FALSE; GstDecodeChain *chain = pad->chain; + GstDecodeBin *dbin = chain->dbin; - GST_LOG_OBJECT (pad->dbin, "chain : %p, pad %p", chain, pad); - pad->drained = TRUE; - return gst_decode_chain_handle_eos (chain); -} + GST_LOG_OBJECT (dbin, "pad %p", pad); + drain_and_switch_chains (dbin->decode_chain, pad, &last_group, &drained, + &switched); -/* gst_decode_chain_handle_eos: - * - * Checks if there are next groups in any parent chain - * to which we can switch or if everything is drained. - * - * If there are groups to switch to, hide the current active - * one and expose the new one. - * - * If a group isn't completely drained (i.e. we received EOS - * only on one of the streams) this function will return FALSE - * to indicate the EOS on the given chain should be dropped - * to avoid it from going downstream. - * - * MT-safe, don't call with chain lock! - */ -static gboolean -gst_decode_chain_handle_eos (GstDecodeChain * eos_chain) -{ - GstDecodeBin *dbin = eos_chain->dbin; - GstDecodeGroup *group; - GstDecodeChain *chain = eos_chain; - gboolean drained; - gboolean forward_eos = TRUE; - - g_return_val_if_fail (eos_chain->endpad, TRUE); - - CHAIN_MUTEX_LOCK (chain); - while ((group = chain->parent)) { - CHAIN_MUTEX_UNLOCK (chain); - chain = group->parent; - CHAIN_MUTEX_LOCK (chain); - - if (gst_decode_group_is_drained (group)) { - continue; - } - break; - } - - drained = chain->active_group ? - gst_decode_group_is_drained (chain->active_group) : TRUE; - - /* Now either group == NULL and chain == dbin->decode_chain - * or chain is the lowest chain that has a non-drained group */ - if (chain->active_group && drained && chain->next_groups) { - /* There's an active group which is drained and we have another - * one to switch to. */ - GST_DEBUG_OBJECT (dbin, "Hiding current group %p", chain->active_group); - gst_decode_group_hide (chain->active_group); - chain->old_groups = g_list_prepend (chain->old_groups, chain->active_group); - GST_DEBUG_OBJECT (dbin, "Switching to next group %p", - chain->next_groups->data); - chain->active_group = chain->next_groups->data; - chain->next_groups = - g_list_delete_link (chain->next_groups, chain->next_groups); - CHAIN_MUTEX_UNLOCK (chain); + if (switched) { + /* If we resulted in a group switch, expose what's needed */ EXPOSE_LOCK (dbin); if (gst_decode_chain_is_complete (dbin->decode_chain)) gst_decode_bin_expose (dbin); EXPOSE_UNLOCK (dbin); - } else if (!chain->active_group || drained) { - /* The group is drained and there isn't a future one */ - g_assert (chain == dbin->decode_chain); - CHAIN_MUTEX_UNLOCK (chain); - - GST_LOG_OBJECT (dbin, "all groups drained, fire signal"); - g_signal_emit (G_OBJECT (dbin), gst_decode_bin_signals[SIGNAL_DRAINED], 0, - NULL); - } else { - CHAIN_MUTEX_UNLOCK (chain); - GST_DEBUG_OBJECT (dbin, - "Current active group in chain %p is not drained yet", chain); - /* Instruct caller to drop EOS event if we have future groups */ - if (chain->next_groups) - forward_eos = FALSE; } - return forward_eos; + return last_group; } /* gst_decode_group_is_drained: From 27058c1bc69ae1fd6413f85e29da845d31691116 Mon Sep 17 00:00:00 2001 From: Youness Alaoui Date: Thu, 18 Aug 2011 15:13:23 +0000 Subject: [PATCH 26/43] decodebin2: Initialize variable correctly If subdrained isn't initialized to FALSE then a chain might think that its group is drained when in fact it's not and this can cause a switch too early or even cause a deadlock. --- gst/playback/gstdecodebin2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gst/playback/gstdecodebin2.c b/gst/playback/gstdecodebin2.c index 741c45dcf6..158d6e6f22 100644 --- a/gst/playback/gstdecodebin2.c +++ b/gst/playback/gstdecodebin2.c @@ -3078,7 +3078,7 @@ drain_and_switch_group (GstDecodeGroup * group, GstDecodePad * drainpad, * new information */ for (tmp = group->children; tmp; tmp = tmp->next) { GstDecodeChain *chain = (GstDecodeChain *) tmp->data; - gboolean subdrained; + gboolean subdrained = FALSE; handled |= drain_and_switch_chains (chain, drainpad, last_group, &subdrained, From 01d37532b69d8434f96647b5ca08a6d1447c7841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Mon, 19 Sep 2011 14:02:00 +0200 Subject: [PATCH 27/43] subtitleoverlay: Get the target of the video sinkpad, not the target sinkpad in the video setcaps handler --- gst/playback/gstsubtitleoverlay.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gst/playback/gstsubtitleoverlay.c b/gst/playback/gstsubtitleoverlay.c index d198d732d9..f43d945909 100644 --- a/gst/playback/gstsubtitleoverlay.c +++ b/gst/playback/gstsubtitleoverlay.c @@ -1753,8 +1753,7 @@ gst_subtitle_overlay_video_sink_setcaps (GstPad * pad, GstCaps * caps) GST_DEBUG_OBJECT (pad, "Setting caps: %" GST_PTR_FORMAT, caps); - target = - gst_ghost_pad_get_target (GST_GHOST_PAD_CAST (self->subtitle_sinkpad)); + target = gst_ghost_pad_get_target (GST_GHOST_PAD_CAST (self->video_sinkpad)); GST_SUBTITLE_OVERLAY_LOCK (self); From 043ee22e25689014c0cd3f9327506d4b959995d4 Mon Sep 17 00:00:00 2001 From: Age Bosma Date: Mon, 19 Sep 2011 14:16:19 +0200 Subject: [PATCH 28/43] discoverer: Don't use gtk-doc /* < ... > */ style comments for signals The /*< ... >*/ style is only used for public|protected|private, signal comments use /* signals */. This prevents the some code parsers/binding generators to be confused by the comment. --- gst-libs/gst/pbutils/gstdiscoverer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gst-libs/gst/pbutils/gstdiscoverer.h b/gst-libs/gst/pbutils/gstdiscoverer.h index 61236fd1d5..450b7665fc 100644 --- a/gst-libs/gst/pbutils/gstdiscoverer.h +++ b/gst-libs/gst/pbutils/gstdiscoverer.h @@ -258,7 +258,7 @@ struct _GstDiscoverer { struct _GstDiscovererClass { GObjectClass parentclass; - /*< signals >*/ + /* signals */ void (*finished) (GstDiscoverer *discoverer); void (*starting) (GstDiscoverer *discoverer); void (*discovered) (GstDiscoverer *discoverer, From c956c5fd008fdc7ff6ebcdbfe79d1d0332ae0374 Mon Sep 17 00:00:00 2001 From: Vincent Penquerc'h Date: Thu, 13 Jan 2011 15:35:30 +0000 Subject: [PATCH 29/43] oggstream: only use information from skeleton if we have nothing better The codec setup headers are a lot more likely to have correct information, especially as it's easy to remux a skeleton in a file where streams don't have the same parameters (I've even seen a file with two skeletons). Still, this is useful in the case we have a codec we can't decode, so we can at least (theoretically) convert granpos to time, so we discard this information if the codec setup has already provided it. This fixes playback on (at lesat) the original archive.org encoding of "The Night of the Living Dead" (now replaced by another encoding). https://bugzilla.gnome.org/show_bug.cgi?id=612443 --- ext/ogg/gstoggdemux.c | 4 ++++ ext/ogg/gstoggstream.c | 18 ++++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/ext/ogg/gstoggdemux.c b/ext/ogg/gstoggdemux.c index 58a1a7c08a..c883d12733 100644 --- a/ext/ogg/gstoggdemux.c +++ b/ext/ogg/gstoggdemux.c @@ -189,6 +189,10 @@ gst_ogg_pad_init (GstOggPad * pad) pad->continued = NULL; pad->map.headers = NULL; pad->map.queued = NULL; + + pad->map.granulerate_n = 0; + pad->map.granulerate_d = 0; + pad->map.granuleshift = -1; } static void diff --git a/ext/ogg/gstoggstream.c b/ext/ogg/gstoggstream.c index c58daeab2b..a26791d261 100644 --- a/ext/ogg/gstoggstream.c +++ b/ext/ogg/gstoggstream.c @@ -1160,13 +1160,23 @@ gst_ogg_map_add_fisbone (GstOggStream * pad, GstOggStream * skel_pad, pad->have_fisbone = TRUE; - /* we just overwrite whatever was set before by the format-specific setup */ - pad->granulerate_n = GST_READ_UINT64_LE (data); - pad->granulerate_d = GST_READ_UINT64_LE (data + 8); + /* We don't overwrite whatever was set before by the format-specific + setup: skeleton contains wrong information sometimes, and the codec + headers are authoritative. + So we only gather information that was not already filled out by + the mapper setup. This should hopefully allow handling unknown + streams a bit better, while not trashing correct setup from bad + skeleton data. */ + if (pad->granulerate_n == 0 || pad->granulerate_d == 0) { + pad->granulerate_n = GST_READ_UINT64_LE (data); + pad->granulerate_d = GST_READ_UINT64_LE (data + 8); + } + if (pad->granuleshift < 0) { + pad->granuleshift = GST_READ_UINT8 (data + 28); + } start_granule = GST_READ_UINT64_LE (data + 16); pad->preroll = GST_READ_UINT32_LE (data + 24); - pad->granuleshift = GST_READ_UINT8 (data + 28); start_time = granulepos_to_granule_default (pad, start_granule); From 7e1e169b1bd9dd842b46689e9c22a36520cb6104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 20 Sep 2011 13:45:55 +0200 Subject: [PATCH 30/43] decodebin2: Fix unit test by strictly implementing parser behaviour instead of relying on basetransform --- tests/check/elements/decodebin2.c | 238 ++++++++++++++---------------- 1 file changed, 111 insertions(+), 127 deletions(-) diff --git a/tests/check/elements/decodebin2.c b/tests/check/elements/decodebin2.c index f015ef2cb4..784c0e554b 100644 --- a/tests/check/elements/decodebin2.c +++ b/tests/check/elements/decodebin2.c @@ -27,7 +27,6 @@ #include #include -#include #include static const gchar dummytext[] = @@ -370,15 +369,15 @@ static GType gst_fake_h264_decoder_get_type (void); #undef parent_class #define parent_class fake_h264_parser_parent_class typedef struct _GstFakeH264Parser GstFakeH264Parser; -typedef GstBaseTransformClass GstFakeH264ParserClass; +typedef GstElementClass GstFakeH264ParserClass; struct _GstFakeH264Parser { - GstBaseTransform parent; + GstElement parent; }; -GST_BOILERPLATE (GstFakeH264Parser, gst_fake_h264_parser, GstBaseTransform, - GST_TYPE_BASE_TRANSFORM); +GST_BOILERPLATE (GstFakeH264Parser, gst_fake_h264_parser, GstElement, + GST_TYPE_ELEMENT); static void gst_fake_h264_parser_base_init (gpointer klass) @@ -400,89 +399,87 @@ gst_fake_h264_parser_base_init (gpointer klass) "FakeH264Parser", "Codec/Parser/Converter/Video", "yep", "me"); } -static GstFlowReturn -gst_fake_h264_parser_transform (GstBaseTransform * trans, GstBuffer * inbuf, - GstBuffer * outbuf) -{ - return GST_FLOW_OK; -} - -static GstCaps * -gst_fake_h264_parser_transform_caps (GstBaseTransform * trans, - GstPadDirection direction, GstCaps * caps) -{ - if (direction == GST_PAD_SRC) - return gst_caps_from_string ("video/x-h264"); - else - return gst_caps_from_string ("video/x-h264, " - "stream-format=(string) { avc, byte-stream }"); -} - -static gboolean -gst_fake_h264_parser_get_unit_size (GstBaseTransform * trans, GstCaps * caps, - guint * size) -{ - *size = 1; - return TRUE; -} - -static gboolean -gst_fake_h264_parser_set_caps (GstBaseTransform * trans, GstCaps * incaps, - GstCaps * outcaps) -{ - GstStructure *s; - const gchar *stream_format; - - s = gst_caps_get_structure (incaps, 0); - fail_unless (gst_structure_has_name (s, "video/x-h264")); - - s = gst_caps_get_structure (outcaps, 0); - fail_unless (gst_structure_has_name (s, "video/x-h264")); - stream_format = gst_structure_get_string (s, "stream-format"); - fail_unless_equals_string ("byte-stream", stream_format); - - return TRUE; -} - -static GstFlowReturn -gst_fake_h264_parser_prepare_output_buffer (GstBaseTransform * trans, - GstBuffer * inbuf, gint size, GstCaps * caps, GstBuffer ** outbuf) -{ - *outbuf = gst_buffer_ref (inbuf); - return GST_FLOW_OK; -} - static void gst_fake_h264_parser_class_init (GstFakeH264ParserClass * klass) { - GstBaseTransformClass *basetrans_class = (GstBaseTransformClass *) klass; +} - basetrans_class->transform = gst_fake_h264_parser_transform; - basetrans_class->transform_caps = gst_fake_h264_parser_transform_caps; - basetrans_class->get_unit_size = gst_fake_h264_parser_get_unit_size; - basetrans_class->set_caps = gst_fake_h264_parser_set_caps; - basetrans_class->prepare_output_buffer = - gst_fake_h264_parser_prepare_output_buffer; +static gboolean +gst_fake_h264_parser_sink_setcaps (GstPad * pad, GstCaps * caps) +{ + GstElement *self = GST_ELEMENT (gst_pad_get_parent (pad)); + GstPad *otherpad = gst_element_get_static_pad (self, "src"); + GstCaps *accepted_caps; + GstStructure *s; + const gchar *stream_format; + + accepted_caps = gst_pad_get_allowed_caps (otherpad); + accepted_caps = gst_caps_make_writable (accepted_caps); + gst_caps_truncate (accepted_caps); + + s = gst_caps_get_structure (accepted_caps, 0); + stream_format = gst_structure_get_string (s, "stream-format"); + if (!stream_format) + gst_structure_set (s, "stream-format", G_TYPE_STRING, "avc", NULL); + + gst_pad_set_caps (otherpad, accepted_caps); + gst_caps_unref (accepted_caps); + + gst_object_unref (otherpad); + gst_object_unref (self); + + return TRUE; +} + +static GstFlowReturn +gst_fake_h264_parser_sink_chain (GstPad * pad, GstBuffer * buf) +{ + GstElement *self = GST_ELEMENT (gst_pad_get_parent (pad)); + GstPad *otherpad = gst_element_get_static_pad (self, "src"); + GstFlowReturn ret = GST_FLOW_OK; + + buf = gst_buffer_make_metadata_writable (buf); + gst_buffer_set_caps (buf, GST_PAD_CAPS (otherpad)); + + ret = gst_pad_push (otherpad, buf); + + gst_object_unref (otherpad); + gst_object_unref (self); + + return ret; } static void gst_fake_h264_parser_init (GstFakeH264Parser * self, GstFakeH264ParserClass * klass) { + GstPad *pad; + + pad = + gst_pad_new_from_template (gst_element_class_get_pad_template + (GST_ELEMENT_GET_CLASS (self), "sink"), "sink"); + gst_pad_set_setcaps_function (pad, gst_fake_h264_parser_sink_setcaps); + gst_pad_set_chain_function (pad, gst_fake_h264_parser_sink_chain); + gst_element_add_pad (GST_ELEMENT (self), pad); + + pad = + gst_pad_new_from_template (gst_element_class_get_pad_template + (GST_ELEMENT_GET_CLASS (self), "src"), "src"); + gst_element_add_pad (GST_ELEMENT (self), pad); } #undef parent_class #define parent_class fake_h264_decoder_parent_class typedef struct _GstFakeH264Decoder GstFakeH264Decoder; -typedef GstBaseTransformClass GstFakeH264DecoderClass; +typedef GstElementClass GstFakeH264DecoderClass; struct _GstFakeH264Decoder { - GstBaseTransform parent; + GstElement parent; }; -GST_BOILERPLATE (GstFakeH264Decoder, gst_fake_h264_decoder, GstBaseTransform, - GST_TYPE_BASE_TRANSFORM); +GST_BOILERPLATE (GstFakeH264Decoder, gst_fake_h264_decoder, GstElement, + GST_TYPE_ELEMENT); static void gst_fake_h264_decoder_base_init (gpointer klass) @@ -503,75 +500,62 @@ gst_fake_h264_decoder_base_init (gpointer klass) "FakeH264Decoder", "Codec/Decoder/Video", "yep", "me"); } -static GstFlowReturn -gst_fake_h264_decoder_transform (GstBaseTransform * trans, GstBuffer * inbuf, - GstBuffer * outbuf) -{ - return GST_FLOW_OK; -} - -static GstCaps * -gst_fake_h264_decoder_transform_caps (GstBaseTransform * trans, - GstPadDirection direction, GstCaps * caps) -{ - if (direction == GST_PAD_SRC) - return gst_caps_from_string ("video/x-h264, " - "stream-format=(string) byte-stream"); - else - return gst_caps_from_string ("video/x-raw-yuv"); -} - -static gboolean -gst_fake_h264_decoder_get_unit_size (GstBaseTransform * trans, GstCaps * caps, - guint * size) -{ - *size = 1; - return TRUE; -} - -static gboolean -gst_fake_h264_decoder_set_caps (GstBaseTransform * trans, GstCaps * incaps, - GstCaps * outcaps) -{ - GstStructure *s; - const gchar *stream_format; - - s = gst_caps_get_structure (incaps, 0); - fail_unless (gst_structure_has_name (s, "video/x-h264")); - stream_format = gst_structure_get_string (s, "stream-format"); - fail_unless_equals_string ("byte-stream", stream_format); - - s = gst_caps_get_structure (outcaps, 0); - fail_unless (gst_structure_has_name (s, "video/x-raw-yuv")); - - return TRUE; -} - -static GstFlowReturn -gst_fake_h264_decoder_prepare_output_buffer (GstBaseTransform * trans, - GstBuffer * inbuf, gint size, GstCaps * caps, GstBuffer ** outbuf) -{ - *outbuf = gst_buffer_ref (inbuf); - return GST_FLOW_OK; -} - static void gst_fake_h264_decoder_class_init (GstFakeH264DecoderClass * klass) { - GstBaseTransformClass *basetrans_class = (GstBaseTransformClass *) klass; +} - basetrans_class->transform = gst_fake_h264_decoder_transform; - basetrans_class->transform_caps = gst_fake_h264_decoder_transform_caps; - basetrans_class->get_unit_size = gst_fake_h264_decoder_get_unit_size; - basetrans_class->set_caps = gst_fake_h264_decoder_set_caps; - basetrans_class->prepare_output_buffer = - gst_fake_h264_decoder_prepare_output_buffer; +static gboolean +gst_fake_h264_decoder_sink_setcaps (GstPad * pad, GstCaps * caps) +{ + GstElement *self = GST_ELEMENT (gst_pad_get_parent (pad)); + GstPad *otherpad = gst_element_get_static_pad (self, "src"); + + caps = gst_caps_new_simple ("video/x-raw-yuv", NULL); + gst_pad_set_caps (otherpad, caps); + gst_caps_unref (caps); + + gst_object_unref (otherpad); + gst_object_unref (self); + + return TRUE; +} + +static GstFlowReturn +gst_fake_h264_decoder_sink_chain (GstPad * pad, GstBuffer * buf) +{ + GstElement *self = GST_ELEMENT (gst_pad_get_parent (pad)); + GstPad *otherpad = gst_element_get_static_pad (self, "src"); + GstFlowReturn ret = GST_FLOW_OK; + + buf = gst_buffer_make_metadata_writable (buf); + gst_buffer_set_caps (buf, GST_PAD_CAPS (otherpad)); + + ret = gst_pad_push (otherpad, buf); + + gst_object_unref (otherpad); + gst_object_unref (self); + + return ret; } static void gst_fake_h264_decoder_init (GstFakeH264Decoder * self, GstFakeH264DecoderClass * klass) { + GstPad *pad; + + pad = + gst_pad_new_from_template (gst_element_class_get_pad_template + (GST_ELEMENT_GET_CLASS (self), "sink"), "sink"); + gst_pad_set_setcaps_function (pad, gst_fake_h264_decoder_sink_setcaps); + gst_pad_set_chain_function (pad, gst_fake_h264_decoder_sink_chain); + gst_element_add_pad (GST_ELEMENT (self), pad); + + pad = + gst_pad_new_from_template (gst_element_class_get_pad_template + (GST_ELEMENT_GET_CLASS (self), "src"), "src"); + gst_element_add_pad (GST_ELEMENT (self), pad); } static void From 8e114c427aebadec0c5f11834110ad884805a7ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 20 Sep 2011 14:03:47 +0200 Subject: [PATCH 31/43] decodebin2: Only call autoplug-continue with fixed caps With unfixed caps we can't reliably decide if the final caps are going to be "raw" (e.g. supported by a sink) or not. We will get here again later when the caps are fixed. --- gst/playback/gstdecodebin2.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/gst/playback/gstdecodebin2.c b/gst/playback/gstdecodebin2.c index 158d6e6f22..bf88ed8148 100644 --- a/gst/playback/gstdecodebin2.c +++ b/gst/playback/gstdecodebin2.c @@ -1423,10 +1423,17 @@ analyze_new_pad (GstDecodeBin * dbin, GstElement * src, GstPad * pad, dpad = gst_decode_pad_new (dbin, pad, chain); /* 1. Emit 'autoplug-continue' the result will tell us if this pads needs - * further autoplugging. */ - g_signal_emit (G_OBJECT (dbin), - gst_decode_bin_signals[SIGNAL_AUTOPLUG_CONTINUE], 0, dpad, caps, - &apcontinue); + * further autoplugging. Only do this for fixed caps, for unfixed caps + * we will later come here again from the notify::caps handler. The + * problem with unfixed caps is that we can reliably tell if the output + * is e.g. accepted by a sink because only parts of the possible final + * caps might be accepted by the sink. */ + if (gst_caps_is_fixed (caps)) + g_signal_emit (G_OBJECT (dbin), + gst_decode_bin_signals[SIGNAL_AUTOPLUG_CONTINUE], 0, dpad, caps, + &apcontinue); + else + apcontinue = TRUE; /* 1.a if autoplug-continue is FALSE or caps is a raw format, goto pad_is_final */ if ((!apcontinue) || are_final_caps (dbin, caps)) From 61c6fcb2cd84bba6177fd15294d03f5624b302e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= Date: Tue, 20 Sep 2011 14:04:45 +0200 Subject: [PATCH 32/43] decodebin2: Only check if this is a discarded type if we have fixed caps For unfixed caps we will get here again later when the caps are fixed. --- gst/playback/gstdecodebin2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gst/playback/gstdecodebin2.c b/gst/playback/gstdecodebin2.c index bf88ed8148..eb14dc2482 100644 --- a/gst/playback/gstdecodebin2.c +++ b/gst/playback/gstdecodebin2.c @@ -1494,7 +1494,7 @@ analyze_new_pad (GstDecodeBin * dbin, GstElement * src, GstPad * pad, /* At this point we have a potential decoder, but we might not need it * if it doesn't match the output caps */ - if (!dbin->expose_allstreams) { + if (!dbin->expose_allstreams && gst_caps_is_fixed (caps)) { guint i; const GList *tmps; gboolean dontuse = FALSE; From f57bbc585d371a95c114e9257469f65311a6b895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim-Philipp=20M=C3=BCller?= Date: Tue, 20 Sep 2011 13:35:55 +0100 Subject: [PATCH 33/43] videorate: don't unref event we don't own http://bugzilla.gnome.org/show_bug.cgi?id=659562 --- gst/videorate/gstvideorate.c | 1 - 1 file changed, 1 deletion(-) diff --git a/gst/videorate/gstvideorate.c b/gst/videorate/gstvideorate.c index 307ebc2e62..67aefc42a9 100644 --- a/gst/videorate/gstvideorate.c +++ b/gst/videorate/gstvideorate.c @@ -623,7 +623,6 @@ format_error: { GST_WARNING_OBJECT (videorate, "Got segment but doesn't have GST_FORMAT_TIME value"); - gst_event_unref (event); return FALSE; } } From ec7ca80c9e5faec62982ce560716d29067209c01 Mon Sep 17 00:00:00 2001 From: Sjoerd Simons Date: Thu, 1 Sep 2011 16:47:49 +0100 Subject: [PATCH 34/43] videorate: Add more strict caps negotiation When in drop-only mode we can never provide a framerate that is higher then the input, so let the caps negotiation reflect this. --- gst/videorate/gstvideorate.c | 100 ++++++++++++++++++++++++++++++++--- 1 file changed, 94 insertions(+), 6 deletions(-) diff --git a/gst/videorate/gstvideorate.c b/gst/videorate/gstvideorate.c index 67aefc42a9..b9b20cffd8 100644 --- a/gst/videorate/gstvideorate.c +++ b/gst/videorate/gstvideorate.c @@ -256,25 +256,113 @@ gst_video_rate_class_init (GstVideoRateClass * klass) G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); } +static void +gst_value_fraction_get_extremes (const GValue * v, + gint * min_num, gint * min_denom, gint * max_num, gint * max_denom) +{ + if (GST_VALUE_HOLDS_FRACTION (v)) { + *min_num = *max_num = gst_value_get_fraction_numerator (v); + *min_denom = *max_denom = gst_value_get_fraction_denominator (v); + } else if (GST_VALUE_HOLDS_FRACTION_RANGE (v)) { + const GValue *min, *max; + + min = gst_value_get_fraction_range_min (v); + *min_num = gst_value_get_fraction_numerator (min); + *min_denom = gst_value_get_fraction_denominator (min); + + max = gst_value_get_fraction_range_max (v); + *max_num = gst_value_get_fraction_numerator (max); + *max_denom = gst_value_get_fraction_denominator (max); + } else if (GST_VALUE_HOLDS_LIST (v)) { + gint min_n = G_MAXINT, min_d = 1, max_n = 0, max_d = 1; + int i, n; + + *min_num = G_MAXINT; + *min_denom = 1; + *max_num = 0; + *max_denom = 1; + + n = gst_value_list_get_size (v); + + g_assert (n > 0); + + for (i = 0; i < n; i++) { + const GValue *t = gst_value_list_get_value (v, i); + + gst_value_fraction_get_extremes (t, &min_n, &min_d, &max_n, &max_d); + if (gst_util_fraction_compare (min_n, min_d, *min_num, *min_denom) < 0) { + *min_num = min_n; + *min_denom = min_d; + } + + if (gst_util_fraction_compare (max_n, max_d, *max_num, *max_denom) > 0) { + *max_num = max_n; + *max_denom = max_d; + } + } + } else { + g_warning ("Unknown type for framerate"); + *min_num = 0; + *min_denom = 1; + *max_num = G_MAXINT; + *max_denom = 1; + } +} + static GstCaps * gst_video_rate_transform_caps (GstBaseTransform * trans, GstPadDirection direction, GstCaps * caps) { + GstVideoRate *videorate = GST_VIDEO_RATE (trans); GstCaps *ret; - GstStructure *s; + GstStructure *s, *s2; + GstStructure *s3 = NULL; /* Should always be called with simple caps */ g_return_val_if_fail (GST_CAPS_IS_SIMPLE (caps), NULL); ret = gst_caps_copy (caps); - s = gst_structure_copy (gst_caps_get_structure (caps, 0)); + s = gst_caps_get_structure (ret, 0); + s2 = gst_structure_copy (s); - /* set the framerate as a range */ - gst_structure_set (s, "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, - G_MAXINT, 1, NULL); + if (videorate->drop_only) { + gint min_num = 0, min_denom = 1; + gint max_num = G_MAXINT, max_denom = 1; - gst_caps_append_structure (ret, s); + if (gst_structure_has_field (s, "framerate")) { + const GValue *v; + v = gst_structure_get_value (s, "framerate"); + + gst_value_fraction_get_extremes (v, &min_num, &min_denom, + &max_num, &max_denom); + } + + if (direction == GST_PAD_SRC) { + /* We can accept anything as long as it's at least the minimal framerate + * the the sink needs */ + gst_structure_set (s2, "framerate", GST_TYPE_FRACTION_RANGE, + min_num, min_denom, G_MAXINT, 1, NULL); + + /* Also allow unknown framerate, if it isn't already */ + if (min_num != 0 || min_denom != 1) { + s3 = gst_structure_copy (s); + gst_structure_set (s3, "framerate", GST_TYPE_FRACTION, 0, 1, NULL); + } + } else { + /* We can provide everything upto the maximum framerate at the src */ + gst_structure_set (s2, "framerate", GST_TYPE_FRACTION_RANGE, + 0, 1, max_num, max_denom, NULL); + } + } else { + /* set the framerate as a range */ + gst_structure_set (s2, "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, + G_MAXINT, 1, NULL); + } + + gst_caps_merge_structure (ret, s2); + if (s3 != NULL) + gst_caps_merge_structure (ret, s3); return ret; } From ee3dfd447143ef5eda291fc4d611296ec10b9f13 Mon Sep 17 00:00:00 2001 From: Sjoerd Simons Date: Thu, 1 Sep 2011 17:05:23 +0100 Subject: [PATCH 35/43] videorate: Add test for caps negotiation --- tests/check/elements/videorate.c | 174 +++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) diff --git a/tests/check/elements/videorate.c b/tests/check/elements/videorate.c index 69eb3c6f56..9e3c592aec 100644 --- a/tests/check/elements/videorate.c +++ b/tests/check/elements/videorate.c @@ -774,6 +774,178 @@ GST_START_TEST (test_selected_caps) GST_END_TEST; + +/* Caps negotiation tests */ +typedef struct +{ + const gchar *caps; + gboolean drop_only; + /* Result of the videomaxrate caps after transforming */ + const gchar *expected_sink_caps; + const gchar *expected_src_caps; +} TestInfo; + +static TestInfo caps_negotiation_tests[] = { + { + .caps = "video/x-raw-yuv", + .drop_only = FALSE, + .expected_sink_caps = "video/x-raw-yuv", + .expected_src_caps = "video/x-raw-yuv"}, + { + .caps = "video/x-raw-yuv", + .drop_only = TRUE, + .expected_sink_caps = "video/x-raw-yuv", + .expected_src_caps = "video/x-raw-yuv"}, + { + .caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", + .drop_only = FALSE, + .expected_sink_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", + .expected_src_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]"}, + { + .caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", + .drop_only = TRUE, + .expected_sink_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", + .expected_src_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]"}, + { + .caps = "video/x-raw-yuv, framerate=15/1", + .drop_only = FALSE, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction)15/1;" + "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", + .expected_src_caps = + "video/x-raw-yuv, framerate=(fraction)15/1;" + "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]"}, + { + .caps = "video/x-raw-yuv, framerate=15/1", + .drop_only = TRUE, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction)15/1;" + "video/x-raw-yuv, framerate=(fraction)[15/1, MAX];" + "video/x-raw-yuv, framerate=(fraction)0/1", + .expected_src_caps = + "video/x-raw-yuv, framerate=(fraction)15/1;" + "video/x-raw-yuv, framerate=(fraction)[0/1, 15/1]"}, + { + .caps = "video/x-raw-yuv, framerate=[15/1, 30/1]", + .drop_only = FALSE, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction)[15/1, 30/1];" + "video/x-raw-yuv, framerate=(fraction)[0/1, MAX];", + .expected_src_caps = + "video/x-raw-yuv, framerate=(fraction)[15/1, 30/1];" + "video/x-raw-yuv, framerate=(fraction)[0/1, MAX];"}, + { + .caps = "video/x-raw-yuv, framerate=[15/1, 30/1]", + .drop_only = TRUE, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction)[15/1, 30/1];" + "video/x-raw-yuv, framerate=(fraction)[15/1, MAX];" + "video/x-raw-yuv, framerate=(fraction)0/1", + .expected_src_caps = + "video/x-raw-yuv, framerate=(fraction)[15/1, 30/1];" + "video/x-raw-yuv, framerate=(fraction)[0/1, 30/1]"}, + { + .caps = "video/x-raw-yuv, framerate={15/1, 30/1}", + .drop_only = FALSE, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction){15/1, 30/1};" + "video/x-raw-yuv, framerate=(fraction)[0/1, MAX];", + .expected_src_caps = + "video/x-raw-yuv, framerate=(fraction){15/1, 30/1};" + "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]"}, + { + .caps = "video/x-raw-yuv, framerate={15/1, 30/1}", + .drop_only = TRUE, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction){15/1, 30/1};" + "video/x-raw-yuv, framerate=(fraction)[15/1, MAX];" + "video/x-raw-yuv, framerate=(fraction)0/1", + .expected_src_caps = + "video/x-raw-yuv, framerate=(fraction){15/1, 30/1};" + "video/x-raw-yuv, framerate=(fraction)[0/1, 30/1];"}, +}; + +static GstCaps * +_getcaps_function (GstPad * pad) +{ + GstCaps *caps = g_object_get_data (G_OBJECT (pad), "caps"); + + fail_unless (caps != NULL); + + return gst_caps_copy (caps); +} + +static void +check_caps_identical (GstCaps * a, GstCaps * b) +{ + int i; + + if (gst_caps_get_size (a) != gst_caps_get_size (b)) + goto fail; + + for (i = 0; i < gst_caps_get_size (a); i++) { + GstStructure *sa, *sb; + + sa = gst_caps_get_structure (a, i); + sb = gst_caps_get_structure (b, i); + + if (!gst_structure_is_equal (sa, sb)) + goto fail; + } + + return; + +fail: + fail ("caps (%s) is not equal to caps (%s)", + gst_caps_to_string (a), gst_caps_to_string (b)); +} + +static void +check_peer_caps (GstPad * pad, const char *expected) +{ + GstCaps *caps; + GstCaps *expected_caps; + + caps = gst_pad_peer_get_caps (pad); + fail_unless (caps != NULL); + + expected_caps = gst_caps_from_string (expected); + fail_unless (expected_caps != NULL); + + check_caps_identical (caps, expected_caps); + + gst_caps_unref (caps); + gst_caps_unref (expected_caps); +} + +GST_START_TEST (test_caps_negotiation) +{ + GstElement *videorate; + GstCaps *caps; + TestInfo *test = &caps_negotiation_tests[__i__]; + + videorate = setup_videorate_full (&srctemplate, &sinktemplate); + + caps = gst_caps_from_string (test->caps); + g_object_set_data_full (G_OBJECT (mysrcpad), "caps", + gst_caps_ref (caps), (GDestroyNotify) gst_caps_unref); + g_object_set_data_full (G_OBJECT (mysinkpad), "caps", + gst_caps_ref (caps), (GDestroyNotify) gst_caps_unref); + gst_caps_unref (caps); + + g_object_set (videorate, "drop-only", test->drop_only, NULL); + + gst_pad_set_getcaps_function (mysrcpad, _getcaps_function); + gst_pad_set_getcaps_function (mysinkpad, _getcaps_function); + + check_peer_caps (mysrcpad, test->expected_sink_caps); + check_peer_caps (mysinkpad, test->expected_src_caps); + + gst_object_unref (videorate); +} + +GST_END_TEST; + static Suite * videorate_suite (void) { @@ -790,6 +962,8 @@ videorate_suite (void) tcase_add_test (tc_chain, test_non_ok_flow); tcase_add_test (tc_chain, test_upstream_caps_nego); tcase_add_test (tc_chain, test_selected_caps); + tcase_add_loop_test (tc_chain, test_caps_negotiation, + 0, G_N_ELEMENTS (caps_negotiation_tests)); return s; } From b9b5b133fd911d048295d959e0bb7fda72347469 Mon Sep 17 00:00:00 2001 From: Sjoerd Simons Date: Mon, 19 Sep 2011 18:26:04 +0100 Subject: [PATCH 36/43] videorate: Add a max-rate property In various use-case you want to dynamically change the framerate (e.g. live streams where the available network bandwidth changes). Doing this via capsfilters in the pipeline tends to be very cumbersome and racy, using this property instead makes it very painless. --- gst/videorate/gstvideorate.c | 115 ++++++++++++++++++++++++++++++++--- gst/videorate/gstvideorate.h | 2 + 2 files changed, 110 insertions(+), 7 deletions(-) diff --git a/gst/videorate/gstvideorate.c b/gst/videorate/gstvideorate.c index b9b20cffd8..667dac87ed 100644 --- a/gst/videorate/gstvideorate.c +++ b/gst/videorate/gstvideorate.c @@ -88,6 +88,7 @@ enum #define DEFAULT_SKIP_TO_FIRST FALSE #define DEFAULT_DROP_ONLY FALSE #define DEFAULT_AVERAGE_PERIOD 0 +#define DEFAULT_MAX_RATE G_MAXINT enum { @@ -100,7 +101,8 @@ enum ARG_NEW_PREF, ARG_SKIP_TO_FIRST, ARG_DROP_ONLY, - ARG_AVERAGE_PERIOD + ARG_AVERAGE_PERIOD, + ARG_MAX_RATE /* FILL ME */ }; @@ -254,6 +256,20 @@ gst_video_rate_class_init (GstVideoRateClass * klass) "Period over which to average the framerate (in ns) (0 = disabled)", 0, G_MAXINT64, DEFAULT_AVERAGE_PERIOD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + /** + * GstVideoRate:max-rate: + * + * maximum framerate to pass through + * + * Since: 0.10.36 + */ + g_object_class_install_property (object_class, ARG_MAX_RATE, + g_param_spec_int ("max-rate", "maximum framerate", + "Maximum framerate allowed to pass through " + "(in frames per second, implies drop-only)", + 1, G_MAXINT, DEFAULT_MAX_RATE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS)); } static void @@ -309,6 +325,52 @@ gst_value_fraction_get_extremes (const GValue * v, } } +/* Clamp the framerate in a caps structure to be a smaller range then + * [1...max_rate], otherwise return false */ +static gboolean +gst_video_max_rate_clamp_structure (GstStructure * s, gint maxrate, + gint * min_num, gint * min_denom, gint * max_num, gint * max_denom) +{ + gboolean ret = FALSE; + + if (!gst_structure_has_field (s, "framerate")) { + /* No framerate field implies any framerate, clamping would result in + * [1..max_rate] so not a real subset */ + goto out; + } else { + const GValue *v; + GValue intersection = { 0, }; + GValue clamp = { 0, }; + gint tmp_num, tmp_denom; + + g_value_init (&clamp, GST_TYPE_FRACTION_RANGE); + gst_value_set_fraction_range_full (&clamp, 0, 1, maxrate, 1); + + v = gst_structure_get_value (s, "framerate"); + ret = gst_value_intersect (&intersection, v, &clamp); + g_value_unset (&clamp); + + if (!ret) + goto out; + + gst_value_fraction_get_extremes (&intersection, + min_num, min_denom, max_num, max_denom); + + gst_value_fraction_get_extremes (v, + &tmp_num, &tmp_denom, max_num, max_denom); + + if (gst_util_fraction_compare (*max_num, *max_denom, maxrate, 1) > 0) { + *max_num = maxrate; + *max_denom = 1; + } + + gst_structure_take_value (s, "framerate", &intersection); + } + +out: + return ret; +} + static GstCaps * gst_video_rate_transform_caps (GstBaseTransform * trans, GstPadDirection direction, GstCaps * caps) @@ -317,6 +379,7 @@ gst_video_rate_transform_caps (GstBaseTransform * trans, GstCaps *ret; GstStructure *s, *s2; GstStructure *s3 = NULL; + int maxrate = g_atomic_int_get (&videorate->max_rate); /* Should always be called with simple caps */ g_return_val_if_fail (GST_CAPS_IS_SIMPLE (caps), NULL); @@ -330,12 +393,26 @@ gst_video_rate_transform_caps (GstBaseTransform * trans, gint min_num = 0, min_denom = 1; gint max_num = G_MAXINT, max_denom = 1; - if (gst_structure_has_field (s, "framerate")) { - const GValue *v; - v = gst_structure_get_value (s, "framerate"); + /* Clamp the caps to our maximum rate as the first caps if possible */ + if (!gst_video_max_rate_clamp_structure (s, maxrate, + &min_num, &min_denom, &max_num, &max_denom)) { + min_num = 0; + min_denom = 1; + max_num = maxrate; + max_denom = 1; - gst_value_fraction_get_extremes (v, &min_num, &min_denom, - &max_num, &max_denom); + /* clamp wouldn't be a real subset of 1..maxrate, in this case the sink + * caps should become [1..maxrate], [1..maxint] and the src caps just + * [1..maxrate]. In case there was a caps incompatibility things will + * explode later as appropriate :) + * + * In case [X..maxrate] == [X..maxint], skip as we'll set it later + */ + if (direction == GST_PAD_SRC && maxrate != G_MAXINT) + gst_structure_set (s, "framerate", GST_TYPE_FRACTION_RANGE, + min_num, min_denom, maxrate, 1, NULL); + else + gst_caps_remove_structure (ret, 0); } if (direction == GST_PAD_SRC) { @@ -349,11 +426,21 @@ gst_video_rate_transform_caps (GstBaseTransform * trans, s3 = gst_structure_copy (s); gst_structure_set (s3, "framerate", GST_TYPE_FRACTION, 0, 1, NULL); } - } else { + } else if (max_num != 0 || max_denom != 1) { /* We can provide everything upto the maximum framerate at the src */ gst_structure_set (s2, "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, max_num, max_denom, NULL); } + } else if (direction == GST_PAD_SINK) { + gint min_num = 0, min_denom = 1; + gint max_num = G_MAXINT, max_denom = 1; + + if (!gst_video_max_rate_clamp_structure (s, maxrate, + &min_num, &min_denom, &max_num, &max_denom)) + gst_caps_remove_structure (ret, 0); + + gst_structure_set (s2, "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, + maxrate, 1, NULL); } else { /* set the framerate as a range */ gst_structure_set (s2, "framerate", GST_TYPE_FRACTION_RANGE, 0, 1, @@ -475,6 +562,7 @@ gst_video_rate_init (GstVideoRate * videorate, GstVideoRateClass * klass) videorate->drop_only = DEFAULT_DROP_ONLY; videorate->average_period = DEFAULT_AVERAGE_PERIOD; videorate->average_period_set = DEFAULT_AVERAGE_PERIOD; + videorate->max_rate = DEFAULT_MAX_RATE; videorate->from_rate_numerator = 0; videorate->from_rate_denominator = 0; @@ -1079,15 +1167,25 @@ gst_video_rate_set_property (GObject * object, break; case ARG_DROP_ONLY: videorate->drop_only = g_value_get_boolean (value); + goto reconfigure; break; case ARG_AVERAGE_PERIOD: videorate->average_period_set = g_value_get_uint64 (value); break; + case ARG_MAX_RATE: + g_atomic_int_set (&videorate->max_rate, g_value_get_int (value)); + goto reconfigure; + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } GST_OBJECT_UNLOCK (videorate); + return; + +reconfigure: + GST_OBJECT_UNLOCK (videorate); + gst_base_transform_reconfigure (GST_BASE_TRANSFORM (videorate)); } static void @@ -1125,6 +1223,9 @@ gst_video_rate_get_property (GObject * object, case ARG_AVERAGE_PERIOD: g_value_set_uint64 (value, videorate->average_period_set); break; + case ARG_MAX_RATE: + g_value_set_int (value, g_atomic_int_get (&videorate->max_rate)); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; diff --git a/gst/videorate/gstvideorate.h b/gst/videorate/gstvideorate.h index a8e1de1db5..998d6daa9a 100644 --- a/gst/videorate/gstvideorate.h +++ b/gst/videorate/gstvideorate.h @@ -76,6 +76,8 @@ struct _GstVideoRate gboolean skip_to_first; gboolean drop_only; guint64 average_period_set; + + volatile int max_rate; }; struct _GstVideoRateClass From 8c9e6346b492ac10a7a588cf6acad2951b6c9b9e Mon Sep 17 00:00:00 2001 From: Sjoerd Simons Date: Mon, 19 Sep 2011 18:31:07 +0100 Subject: [PATCH 37/43] videorate: Print which caps didn't match up --- tests/check/elements/videorate.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/check/elements/videorate.c b/tests/check/elements/videorate.c index 9e3c592aec..07f2fb7bc7 100644 --- a/tests/check/elements/videorate.c +++ b/tests/check/elements/videorate.c @@ -876,7 +876,7 @@ _getcaps_function (GstPad * pad) } static void -check_caps_identical (GstCaps * a, GstCaps * b) +check_caps_identical (GstCaps * a, GstCaps * b, const char *name) { int i; @@ -896,12 +896,12 @@ check_caps_identical (GstCaps * a, GstCaps * b) return; fail: - fail ("caps (%s) is not equal to caps (%s)", - gst_caps_to_string (a), gst_caps_to_string (b)); + fail ("%s caps (%s) is not equal to caps (%s)", + name, gst_caps_to_string (a), gst_caps_to_string (b)); } static void -check_peer_caps (GstPad * pad, const char *expected) +check_peer_caps (GstPad * pad, const char *expected, const char *name) { GstCaps *caps; GstCaps *expected_caps; @@ -912,7 +912,7 @@ check_peer_caps (GstPad * pad, const char *expected) expected_caps = gst_caps_from_string (expected); fail_unless (expected_caps != NULL); - check_caps_identical (caps, expected_caps); + check_caps_identical (caps, expected_caps, name); gst_caps_unref (caps); gst_caps_unref (expected_caps); @@ -938,8 +938,8 @@ GST_START_TEST (test_caps_negotiation) gst_pad_set_getcaps_function (mysrcpad, _getcaps_function); gst_pad_set_getcaps_function (mysinkpad, _getcaps_function); - check_peer_caps (mysrcpad, test->expected_sink_caps); - check_peer_caps (mysinkpad, test->expected_src_caps); + check_peer_caps (mysrcpad, test->expected_sink_caps, "sink"); + check_peer_caps (mysinkpad, test->expected_src_caps, "src"); gst_object_unref (videorate); } From 8ab394773339e1975a1f0ff688cd266899df2248 Mon Sep 17 00:00:00 2001 From: Sjoerd Simons Date: Mon, 19 Sep 2011 18:32:26 +0100 Subject: [PATCH 38/43] videorate: Add tests for the max-rate case --- tests/check/elements/videorate.c | 104 ++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 3 deletions(-) diff --git a/tests/check/elements/videorate.c b/tests/check/elements/videorate.c index 07f2fb7bc7..db6793ca14 100644 --- a/tests/check/elements/videorate.c +++ b/tests/check/elements/videorate.c @@ -780,6 +780,7 @@ typedef struct { const gchar *caps; gboolean drop_only; + int max_rate; /* Result of the videomaxrate caps after transforming */ const gchar *expected_sink_caps; const gchar *expected_src_caps; @@ -790,22 +791,52 @@ static TestInfo caps_negotiation_tests[] = { .caps = "video/x-raw-yuv", .drop_only = FALSE, .expected_sink_caps = "video/x-raw-yuv", - .expected_src_caps = "video/x-raw-yuv"}, + .expected_src_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]"}, + { + .caps = "video/x-raw-yuv", + .drop_only = FALSE, + .max_rate = 15, + .expected_sink_caps = "video/x-raw-yuv", + .expected_src_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, 15]"}, { .caps = "video/x-raw-yuv", .drop_only = TRUE, - .expected_sink_caps = "video/x-raw-yuv", - .expected_src_caps = "video/x-raw-yuv"}, + .expected_sink_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", + .expected_src_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]"}, + { + .caps = "video/x-raw-yuv", + .drop_only = TRUE, + .max_rate = 15, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction)[0/1, 15];" + "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", + .expected_src_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, 15]"}, + + { .caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", .drop_only = FALSE, .expected_sink_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", .expected_src_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]"}, + { + .caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", + .drop_only = FALSE, + .max_rate = 15, + .expected_sink_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", + .expected_src_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, 15]"}, { .caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", .drop_only = TRUE, .expected_sink_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", .expected_src_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]"}, + { + .caps = "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", + .drop_only = TRUE, + .max_rate = 15, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction)[0/1, 15];" + "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", + .expected_src_caps = "video/x-raw-yuv, framerate=(fraction)[0/1, 15]"}, { .caps = "video/x-raw-yuv, framerate=15/1", .drop_only = FALSE, @@ -815,6 +846,16 @@ static TestInfo caps_negotiation_tests[] = { .expected_src_caps = "video/x-raw-yuv, framerate=(fraction)15/1;" "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]"}, + { + .caps = "video/x-raw-yuv, framerate=15/1", + .drop_only = FALSE, + .max_rate = 20, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction)15/1;" + "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]", + .expected_src_caps = + "video/x-raw-yuv, framerate=(fraction)15/1;" + "video/x-raw-yuv, framerate=(fraction)[0/1, 20/1]"}, { .caps = "video/x-raw-yuv, framerate=15/1", .drop_only = TRUE, @@ -825,6 +866,17 @@ static TestInfo caps_negotiation_tests[] = { .expected_src_caps = "video/x-raw-yuv, framerate=(fraction)15/1;" "video/x-raw-yuv, framerate=(fraction)[0/1, 15/1]"}, + { + .caps = "video/x-raw-yuv, framerate=15/1", + .drop_only = TRUE, + .max_rate = 20, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction)15/1;" + "video/x-raw-yuv, framerate=(fraction)[15/1, MAX];" + "video/x-raw-yuv, framerate=(fraction)0/1", + .expected_src_caps = + "video/x-raw-yuv, framerate=(fraction)15/1;" + "video/x-raw-yuv, framerate=(fraction)[0/1, 15/1];"}, { .caps = "video/x-raw-yuv, framerate=[15/1, 30/1]", .drop_only = FALSE, @@ -834,6 +886,16 @@ static TestInfo caps_negotiation_tests[] = { .expected_src_caps = "video/x-raw-yuv, framerate=(fraction)[15/1, 30/1];" "video/x-raw-yuv, framerate=(fraction)[0/1, MAX];"}, + { + .caps = "video/x-raw-yuv, framerate=[15/1, 30/1]", + .drop_only = FALSE, + .max_rate = 20, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction)[15/1, 30/1];" + "video/x-raw-yuv, framerate=(fraction)[0/1, MAX];", + .expected_src_caps = + "video/x-raw-yuv, framerate=(fraction)[15/1, 20/1];" + "video/x-raw-yuv, framerate=(fraction)[0/1, 20/1];"}, { .caps = "video/x-raw-yuv, framerate=[15/1, 30/1]", .drop_only = TRUE, @@ -844,6 +906,18 @@ static TestInfo caps_negotiation_tests[] = { .expected_src_caps = "video/x-raw-yuv, framerate=(fraction)[15/1, 30/1];" "video/x-raw-yuv, framerate=(fraction)[0/1, 30/1]"}, + { + .caps = "video/x-raw-yuv, framerate=[15/1, 30/1]", + .drop_only = TRUE, + .max_rate = 20, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction)[15/1, 20/1];" + "video/x-raw-yuv, framerate=(fraction)[15/1, 30/1];" + "video/x-raw-yuv, framerate=(fraction)[15/1, MAX];" + "video/x-raw-yuv, framerate=(fraction)0/1", + .expected_src_caps = + "video/x-raw-yuv, framerate=(fraction)[15/1, 20/1];" + "video/x-raw-yuv, framerate=(fraction)[0/1, 20/1]"}, { .caps = "video/x-raw-yuv, framerate={15/1, 30/1}", .drop_only = FALSE, @@ -853,6 +927,16 @@ static TestInfo caps_negotiation_tests[] = { .expected_src_caps = "video/x-raw-yuv, framerate=(fraction){15/1, 30/1};" "video/x-raw-yuv, framerate=(fraction)[0/1, MAX]"}, + { + .caps = "video/x-raw-yuv, framerate={15/1, 30/1}", + .drop_only = FALSE, + .max_rate = 20, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction){15/1, 30/1};" + "video/x-raw-yuv, framerate=(fraction)[0/1, MAX];", + .expected_src_caps = + "video/x-raw-yuv, framerate=(fraction)15/1;" + "video/x-raw-yuv, framerate=(fraction)[0/1, 20/1];"}, { .caps = "video/x-raw-yuv, framerate={15/1, 30/1}", .drop_only = TRUE, @@ -863,6 +947,18 @@ static TestInfo caps_negotiation_tests[] = { .expected_src_caps = "video/x-raw-yuv, framerate=(fraction){15/1, 30/1};" "video/x-raw-yuv, framerate=(fraction)[0/1, 30/1];"}, + { + .caps = "video/x-raw-yuv, framerate={15/1, 30/1}", + .drop_only = TRUE, + .max_rate = 20, + .expected_sink_caps = + "video/x-raw-yuv, framerate=(fraction)15/1;" + "video/x-raw-yuv, framerate=(fraction){15/1, 30/1};" + "video/x-raw-yuv, framerate=(fraction)[15/1, MAX];" + "video/x-raw-yuv, framerate=(fraction)0/1", + .expected_src_caps = + "video/x-raw-yuv, framerate=(fraction)15/1;" + "video/x-raw-yuv, framerate=(fraction)[0/1, 20/1]"}, }; static GstCaps * @@ -934,6 +1030,8 @@ GST_START_TEST (test_caps_negotiation) gst_caps_unref (caps); g_object_set (videorate, "drop-only", test->drop_only, NULL); + if (test->max_rate != 0) + g_object_set (videorate, "max-rate", test->max_rate, NULL); gst_pad_set_getcaps_function (mysrcpad, _getcaps_function); gst_pad_set_getcaps_function (mysinkpad, _getcaps_function); From b44978befe3c192acc6a1d7966b0bf76e2ea56d1 Mon Sep 17 00:00:00 2001 From: Mark Nauwelaerts Date: Thu, 22 Sep 2011 15:34:41 +0200 Subject: [PATCH 39/43] audiodecoder: fix documentation typo --- gst-libs/gst/audio/gstaudiodecoder.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gst-libs/gst/audio/gstaudiodecoder.c b/gst-libs/gst/audio/gstaudiodecoder.c index 9bc20655d0..21c75bcd6e 100644 --- a/gst-libs/gst/audio/gstaudiodecoder.c +++ b/gst-libs/gst/audio/gstaudiodecoder.c @@ -54,7 +54,7 @@ * * As of configuration stage, and throughout processing, GstAudioDecoder * provides various (context) parameters, e.g. describing the format of - * output audio data (valid when output caps have been caps) or current parsing state. + * output audio data (valid when output caps have been set) or current parsing state. * Conversely, subclass can and should configure context to inform * base class of its expectation w.r.t. buffer handling. * From 7fa7de9221b2b76dda68fd4bd5ab097e78afa555 Mon Sep 17 00:00:00 2001 From: Mark Nauwelaerts Date: Thu, 22 Sep 2011 15:36:22 +0200 Subject: [PATCH 40/43] audio: some more accessor macros for GstAudioInfo --- gst-libs/gst/audio/audio.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/gst-libs/gst/audio/audio.h b/gst-libs/gst/audio/audio.h index eba7a344ca..a050e515fe 100644 --- a/gst-libs/gst/audio/audio.h +++ b/gst-libs/gst/audio/audio.h @@ -256,6 +256,14 @@ struct _GstAudioInfo { #define GST_AUDIO_INFO_DEPTH(i) (GST_AUDIO_FORMAT_INFO_DEPTH((i)->finfo)) #define GST_AUDIO_INFO_BPS(info) (GST_AUDIO_INFO_DEPTH(info) >> 3) +#define GST_AUDIO_INFO_IS_INTEGER(i) (GST_AUDIO_FORMAT_INFO_IS_INTEGER((i)->finfo)) +#define GST_AUDIO_INFO_IS_FLOAT(i) (GST_AUDIO_FORMAT_INFO_IS_FLOAT((i)->finfo)) +#define GST_AUDIO_INFO_IS_SIGNED(i) (GST_AUDIO_FORMAT_INFO_IS_SIGNED((i)->finfo)) + +#define GST_AUDIO_INFO_ENDIANNESS(i) (GST_AUDIO_FORMAT_INFO_ENDIANNES((i)->finfo)) +#define GST_AUDIO_INFO_IS_LITTLE_ENDIAN(i) (GST_AUDIO_FORMAT_INFO_IS_LITTLE_ENDIAN((i)->finfo)) +#define GST_AUDIO_INFO_IS_BIG_ENDIAN(i) (GST_AUDIO_FORMAT_INFO_IS_BIG_ENDIAN((i)->finfo)) + #define GST_AUDIO_INFO_FLAGS(info) ((info)->flags) #define GST_AUDIO_INFO_HAS_DEFAULT_POSITIONS(info) ((info)->flags & GST_AUDIO_FLAG_DEFAULT_POSITIONS) From b420dd54eab6ebb077630e830267f25c2d7d59f5 Mon Sep 17 00:00:00 2001 From: Mark Nauwelaerts Date: Thu, 22 Sep 2011 15:37:07 +0200 Subject: [PATCH 41/43] audioencoder: prevent crashing when comparing to a freshly inited GstAudioInfo --- gst-libs/gst/audio/gstaudioencoder.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/gst-libs/gst/audio/gstaudioencoder.c b/gst-libs/gst/audio/gstaudioencoder.c index 619ec5a7ef..80f5cd07d5 100644 --- a/gst-libs/gst/audio/gstaudioencoder.c +++ b/gst-libs/gst/audio/gstaudioencoder.c @@ -933,6 +933,8 @@ audio_info_is_equal (GstAudioInfo * from, GstAudioInfo * to) { if (from == to) return TRUE; + if (from->finfo == NULL || to->finfo == NULL) + return FALSE; if (GST_AUDIO_INFO_FORMAT (from) != GST_AUDIO_INFO_FORMAT (to)) return FALSE; if (GST_AUDIO_INFO_RATE (from) != GST_AUDIO_INFO_RATE (to)) From 2a362a95f72caebc1bd0f49e1891b7a470d4af0b Mon Sep 17 00:00:00 2001 From: Mark Nauwelaerts Date: Thu, 22 Sep 2011 15:38:22 +0200 Subject: [PATCH 42/43] audioencoder: changed is verily the opposite of equal --- gst-libs/gst/audio/gstaudioencoder.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gst-libs/gst/audio/gstaudioencoder.c b/gst-libs/gst/audio/gstaudioencoder.c index 80f5cd07d5..17dceee555 100644 --- a/gst-libs/gst/audio/gstaudioencoder.c +++ b/gst-libs/gst/audio/gstaudioencoder.c @@ -983,7 +983,7 @@ gst_audio_encoder_sink_setcaps (GstPad * pad, GstCaps * caps) if (!gst_audio_info_from_caps (state, caps)) goto refuse_caps; - changed = audio_info_is_equal (state, old_state); + changed = !audio_info_is_equal (state, old_state); gst_audio_info_free (old_state); if (changed) { From 001b4a0072fae1f695698660e2d7642c37ea665b Mon Sep 17 00:00:00 2001 From: Mark Nauwelaerts Date: Thu, 22 Sep 2011 15:38:51 +0200 Subject: [PATCH 43/43] audioencoder: proxy some more optional downstream caps fields to upstream --- gst-libs/gst/audio/gstaudioencoder.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/gst-libs/gst/audio/gstaudioencoder.c b/gst-libs/gst/audio/gstaudioencoder.c index 17dceee555..edd9263c88 100644 --- a/gst-libs/gst/audio/gstaudioencoder.c +++ b/gst-libs/gst/audio/gstaudioencoder.c @@ -1085,6 +1085,18 @@ gst_audio_encoder_proxy_getcaps (GstAudioEncoder * enc, GstCaps * caps) gst_structure_set_value (s, "rate", val); if ((val = gst_structure_get_value (allowed_s, "channels"))) gst_structure_set_value (s, "channels", val); + /* following might also make sense for some encoded formats, + * e.g. wavpack */ + if ((val = gst_structure_get_value (allowed_s, "width"))) + gst_structure_set_value (s, "width", val); + if ((val = gst_structure_get_value (allowed_s, "depth"))) + gst_structure_set_value (s, "depth", val); + if ((val = gst_structure_get_value (allowed_s, "endianness"))) + gst_structure_set_value (s, "endianness", val); + if ((val = gst_structure_get_value (allowed_s, "signed"))) + gst_structure_set_value (s, "signed", val); + if ((val = gst_structure_get_value (allowed_s, "channel-positions"))) + gst_structure_set_value (s, "channel-positions", val); gst_caps_merge_structure (filter_caps, s); }