diff options
author | Mathieu Duponchelle <mathieu@centricular.com> | 2021-08-10 00:53:57 +0200 |
---|---|---|
committer | GStreamer Marge Bot <gitlab-merge-bot@gstreamer-foundation.org> | 2021-08-20 00:16:43 +0000 |
commit | 9bd8d608d5bae27ec5ff09e733f76ca32b17420c (patch) | |
tree | 006c24cb55c6c4e1d4b5139f055f9977ae8c4843 | |
parent | cb75eda13b20b5633546e40e7d8fcc0d479ee901 (diff) | |
download | gstreamer-plugins-good-9bd8d608d5bae27ec5ff09e733f76ca32b17420c.tar.gz |
matroska-mux: support H264 avc3 / H265 hev1
The matroska codec specs is unfortunately vague on the subject,
stating for H264:
AVC/H.264 stored as described in [@!ISO.14496-15]
and for H265:
HEVC/H.265 stored as described in [@!ISO.14496-15]
This spec however specifies multiple stream formats, our
implementation has opted for interpreting this as avc1 / hvc1,
both of which disallow in-band SPS.
Most decoders however will support in-band SPS / PPS, and
this commit gives the option to explicitly mux in avc3 / hev1,
which allows changing stream parameters on the fly, that is
useful for smart encoding for example.
When either of these stream formats are picked as the input,
changes in codec_data / tier / level / profile do not cause
renegotiation failure, a warning is logged however as it isn't
clear how compliant such a stream is.
The stream-format field is correctly ordered in the template
caps to avoid selecting potentially non-compliant options on
automatic negotiation.
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-good/-/merge_requests/1047>
-rw-r--r-- | docs/gst_plugins_cache.json | 10 | ||||
-rw-r--r-- | gst/matroska/matroska-mux.c | 77 |
2 files changed, 79 insertions, 8 deletions
diff --git a/docs/gst_plugins_cache.json b/docs/gst_plugins_cache.json index 0d7eeb229..2e098f78f 100644 --- a/docs/gst_plugins_cache.json +++ b/docs/gst_plugins_cache.json @@ -7537,7 +7537,7 @@ "type": "GstQTMuxPad" }, "video_%%u": { - "caps": "video/x-h263:\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/mpeg:\n mpegversion: 4\n systemstream: false\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-divx:\n divxversion: 5\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-h264:\n stream-format: avc\n alignment: au\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\n", + "caps": "video/x-h263:\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/mpeg:\n mpegversion: 4\n systemstream: false\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-divx:\n divxversion: 5\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-h264:\n stream-format: { (string)avc, (string)avc3 }\n alignment: au\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\n", "direction": "sink", "presence": "request", "type": "GstQTMuxPad" @@ -7592,7 +7592,7 @@ "type": "GstAggregatorPad" }, "video_%%u": { - "caps": "video/mpeg:\n mpegversion: 4\n systemstream: false\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-divx:\n divxversion: 5\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-h264:\n stream-format: avc\n alignment: au\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\n", + "caps": "video/mpeg:\n mpegversion: 4\n systemstream: false\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-divx:\n divxversion: 5\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-h264:\n stream-format: { (string)avc, (string)avc3 }\n alignment: au\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\n", "direction": "sink", "presence": "request", "type": "GstQTMuxPad" @@ -7708,7 +7708,7 @@ "type": "GstQTMuxPad" }, "video_%%u": { - "caps": "video/mpeg:\n mpegversion: 4\n systemstream: false\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-divx:\n divxversion: 5\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-h264:\n stream-format: avc\n alignment: au\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-h265:\n stream-format: { (string)hvc1, (string)hev1 }\n alignment: au\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-mp4-part:\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-av1:\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\n", + "caps": "video/mpeg:\n mpegversion: 4\n systemstream: false\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-divx:\n divxversion: 5\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-h264:\n stream-format: { (string)avc, (string)avc3 }\n alignment: au\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-h265:\n stream-format: { (string)hvc1, (string)hev1 }\n alignment: au\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-mp4-part:\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-av1:\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\n", "direction": "sink", "presence": "request", "type": "GstQTMuxPad" @@ -7881,7 +7881,7 @@ "type": "GstQTMuxPad" }, "video_%%u": { - "caps": "video/x-raw:\n format: { RGB, UYVY, v210 }\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/mpeg:\n mpegversion: 4\n systemstream: false\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-divx:\n divxversion: 5\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-prores:\n variant: { (string)standard, (string)lt, (string)hq, (string)proxy, (string)4444, (string)4444xq }\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-cineform:\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-h263:\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-h264:\n stream-format: avc\n alignment: au\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-h265:\n stream-format: { (string)hvc1, (string)hev1 }\n alignment: au\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-svq:\n svqversion: 3\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-dv:\n systemstream: false\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nimage/jpeg:\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nimage/png:\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-vp8:\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-vp9:\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-dirac:\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-qt-part:\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-av1:\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\n", + "caps": "video/x-raw:\n format: { RGB, UYVY, v210 }\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/mpeg:\n mpegversion: 4\n systemstream: false\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-divx:\n divxversion: 5\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-prores:\n variant: { (string)standard, (string)lt, (string)hq, (string)proxy, (string)4444, (string)4444xq }\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-cineform:\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-h263:\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-h264:\n stream-format: { (string)avc, (string)avc3 }\n alignment: au\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-h265:\n stream-format: { (string)hvc1, (string)hev1 }\n alignment: au\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-svq:\n svqversion: 3\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-dv:\n systemstream: false\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nimage/jpeg:\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nimage/png:\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-vp8:\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-vp9:\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-dirac:\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-qt-part:\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-av1:\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\n", "direction": "sink", "presence": "request", "type": "GstQTMuxPad" @@ -9114,7 +9114,7 @@ "presence": "request" }, "video_%%u": { - "caps": "video/mpeg:\n mpegversion: { (int)1, (int)2, (int)4 }\n systemstream: false\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-h264:\n stream-format: avc\n alignment: au\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-h265:\n stream-format: hvc1\n alignment: au\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-divx:\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-huffyuv:\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-dv:\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-h263:\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-msmpeg:\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nimage/jpeg:\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-theora:\nvideo/x-dirac:\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-pn-realvideo:\n rmversion: [ 1, 4 ]\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-vp8:\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-vp9:\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-raw:\n format: { YUY2, I420, YV12, UYVY, AYUV, GRAY8, BGR, RGB }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-prores:\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-wmv:\n wmvversion: [ 1, 3 ]\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-av1:\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n", + "caps": "video/mpeg:\n mpegversion: { (int)1, (int)2, (int)4 }\n systemstream: false\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-h264:\n stream-format: { (string)avc, (string)avc3 }\n alignment: au\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-h265:\n stream-format: { (string)hvc1, (string)hev1 }\n alignment: au\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-divx:\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-huffyuv:\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-dv:\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-h263:\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-msmpeg:\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nimage/jpeg:\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-theora:\nvideo/x-dirac:\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-pn-realvideo:\n rmversion: [ 1, 4 ]\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-vp8:\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-vp9:\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-raw:\n format: { YUY2, I420, YV12, UYVY, AYUV, GRAY8, BGR, RGB }\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-prores:\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-wmv:\n wmvversion: [ 1, 3 ]\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\nvideo/x-av1:\n width: [ 1, 2147483647 ]\n height: [ 1, 2147483647 ]\n", "direction": "sink", "presence": "request" } diff --git a/gst/matroska/matroska-mux.c b/gst/matroska/matroska-mux.c index 7dd8901ff..28970a6f1 100644 --- a/gst/matroska/matroska-mux.c +++ b/gst/matroska/matroska-mux.c @@ -114,9 +114,9 @@ static GstStaticPadTemplate videosink_templ = "mpegversion = (int) { 1, 2, 4 }, " "systemstream = (boolean) false, " COMMON_VIDEO_CAPS "; " - "video/x-h264, stream-format=avc, alignment=au, " + "video/x-h264, stream-format = (string) { avc, avc3 }, alignment=au, " COMMON_VIDEO_CAPS "; " - "video/x-h265, stream-format=hvc1, alignment=au, " + "video/x-h265, stream-format = (string) { hvc1, hev1 }, alignment=au, " COMMON_VIDEO_CAPS "; " "video/x-divx, " COMMON_VIDEO_CAPS "; " @@ -961,6 +961,63 @@ gst_matroska_mux_set_codec_id (GstMatroskaTrackContext * context, context->codec_id = g_strdup (id); } +static gboolean +check_field (GQuark field_id, const GValue * value, gpointer user_data) +{ + GstStructure *structure = (GstStructure *) user_data; + const gchar *name = gst_structure_get_name (structure); + + if ((g_strcmp0 (name, "video/x-h264") == 0 && + !g_strcmp0 (gst_structure_get_string (structure, "stream-format"), + "avc3")) || (g_strcmp0 (name, "video/x-h265") == 0 + && !g_strcmp0 (gst_structure_get_string (structure, "stream-format"), + "hev1")) + ) { + /* While in theory, matroska only supports avc1 / hvc1, and doesn't support codec_data + * changes, in practice most decoders will use in-band SPS / PPS (avc3 / hev1), if the + * input stream is avc3 / hev1 we let the new codec_data slide to support "smart" encoding. + * + * We don't warn here as we already warned elsewhere. + */ + if (field_id == g_quark_from_static_string ("codec_data")) { + return FALSE; + } else if (field_id == g_quark_from_static_string ("tier")) { + return FALSE; + } else if (field_id == g_quark_from_static_string ("profile")) { + return FALSE; + } else if (field_id == g_quark_from_static_string ("level")) { + return FALSE; + } + } + + return TRUE; +} + +static gboolean +check_new_caps (GstCaps * old_caps, GstCaps * new_caps) +{ + GstStructure *old_s, *new_s; + gboolean ret; + + old_caps = gst_caps_copy (old_caps); + new_caps = gst_caps_copy (new_caps); + + new_s = gst_caps_get_structure (new_caps, 0); + old_s = gst_caps_get_structure (old_caps, 0); + + gst_structure_filter_and_map_in_place (new_s, + (GstStructureFilterMapFunc) check_field, new_s); + gst_structure_filter_and_map_in_place (old_s, + (GstStructureFilterMapFunc) check_field, old_s); + + ret = gst_caps_is_subset (new_caps, old_caps); + + gst_caps_unref (new_caps); + gst_caps_unref (old_caps); + + return ret; +} + /** * gst_matroska_mux_video_pad_setcaps: * @pad: Pad which got the caps. @@ -991,7 +1048,7 @@ gst_matroska_mux_video_pad_setcaps (GstPad * pad, GstCaps * caps) if ((old_caps = gst_pad_get_current_caps (pad))) { if (mux->state >= GST_MATROSKA_MUX_STATE_HEADER - && !gst_caps_is_subset (caps, old_caps)) { + && !check_new_caps (old_caps, caps)) { GST_ELEMENT_ERROR (mux, STREAM, MUX, (NULL), ("Caps changes are not supported by Matroska\nCurrent: `%" GST_PTR_FORMAT "`\nNew: `%" GST_PTR_FORMAT "`", old_caps, caps)); @@ -1231,6 +1288,13 @@ skip_details: gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_VIDEO_MPEG4_AVC); gst_matroska_mux_free_codec_priv (context); + + if (!g_strcmp0 (gst_structure_get_string (structure, "stream-format"), + "avc3")) { + GST_WARNING_OBJECT (mux, + "avc3 is not officially supported, only use this format for smart encoding"); + } + /* Create avcC header */ if (codec_buf != NULL) { context->codec_priv_size = gst_buffer_get_size (codec_buf); @@ -1241,6 +1305,13 @@ skip_details: gst_matroska_mux_set_codec_id (context, GST_MATROSKA_CODEC_ID_VIDEO_MPEGH_HEVC); gst_matroska_mux_free_codec_priv (context); + + if (!g_strcmp0 (gst_structure_get_string (structure, "stream-format"), + "hev1")) { + GST_WARNING_OBJECT (mux, + "hev1 is not officially supported, only use this format for smart encoding"); + } + /* Create hvcC header */ if (codec_buf != NULL) { context->codec_priv_size = gst_buffer_get_size (codec_buf); |