From e2bd8809bfd2134b53d542fdf1a6f9f1f3474ad8 Mon Sep 17 00:00:00 2001 From: Carlos Bentzen Date: Fri, 7 Feb 2025 18:53:11 +0100 Subject: [PATCH] qtmux: add support for VVC/H.266 video Part-of: --- .../docs/gst_plugins_cache.json | 4 +-- .../gst-plugins-good/gst/isomp4/atoms.c | 29 +++++++++++++++++++ .../gst-plugins-good/gst/isomp4/atoms.h | 1 + .../gst-plugins-good/gst/isomp4/gstqtmux.c | 25 +++++++++++++++- .../gst-plugins-good/gst/isomp4/gstqtmuxmap.c | 13 +++++++-- 5 files changed, 66 insertions(+), 6 deletions(-) diff --git a/subprojects/gst-plugins-good/docs/gst_plugins_cache.json b/subprojects/gst-plugins-good/docs/gst_plugins_cache.json index 52d5cd388c..aa2238ff45 100644 --- a/subprojects/gst-plugins-good/docs/gst_plugins_cache.json +++ b/subprojects/gst-plugins-good/docs/gst_plugins_cache.json @@ -8460,7 +8460,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: { (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 stream-format: obu-stream\n alignment: tu\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-vp9:\n profile: { (string)0, (string)1, (string)2, (string)3 }\n chroma-format: { (string)4:2:0, (string)4:2:2, (string)4:4:4 }\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-h266:\n stream-format: { (string)vvc1, (string)vvi1 }\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 stream-format: obu-stream\n alignment: tu\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\nvideo/x-vp9:\n profile: { (string)0, (string)1, (string)2, (string)3 }\n chroma-format: { (string)4:2:0, (string)4:2:2, (string)4:4:4 }\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\n", "direction": "sink", "presence": "request", "type": "GstQTMuxPad" @@ -8633,7 +8633,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: { (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 profile: { (string)0, (string)1, (string)2, (string)3 }\n chroma-format: { (string)4:2:0, (string)4:2:2, (string)4:4:4 }\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 stream-format: obu-stream\n alignment: tu\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-h266:\n stream-format: { (string)vvc1, (string)vvi1 }\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 profile: { (string)0, (string)1, (string)2, (string)3 }\n chroma-format: { (string)4:2:0, (string)4:2:2, (string)4:4:4 }\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 stream-format: obu-stream\n alignment: tu\n width: [ 16, 2147483647 ]\n height: [ 16, 2147483647 ]\n", "direction": "sink", "presence": "request", "type": "GstQTMuxPad" diff --git a/subprojects/gst-plugins-good/gst/isomp4/atoms.c b/subprojects/gst-plugins-good/gst/isomp4/atoms.c index 115ab78234..f46876f1cf 100644 --- a/subprojects/gst-plugins-good/gst/isomp4/atoms.c +++ b/subprojects/gst-plugins-good/gst/isomp4/atoms.c @@ -5811,3 +5811,32 @@ build_vpcC_extension (guint8 profile, guint8 level, guint8 bit_depth, return build_atom_info_wrapper ((Atom *) atom_data, atom_data_copy_data, atom_data_free); } + +AtomInfo * +build_vvcC_extension (guint8 version, guint32 flags, + const GstBuffer * codec_data) +{ + AtomData *atom_data; + GstByteWriter bw; + gboolean hdl = TRUE; + guint8 *data_block; + guint data_block_len; + + gst_byte_writer_init (&bw); + hdl &= gst_byte_writer_put_uint8 (&bw, version); + hdl &= gst_byte_writer_put_uint24_be (&bw, flags & 0xFFFFFF); + hdl &= gst_byte_writer_put_buffer (&bw, (GstBuffer *) codec_data, 0, -1); + + if (!hdl) { + GST_WARNING ("error creating header"); + return NULL; + } + + data_block_len = gst_byte_writer_get_size (&bw); + data_block = gst_byte_writer_reset_and_get_data (&bw); + atom_data = atom_data_new_from_data (FOURCC_vvcC, data_block, data_block_len); + g_free (data_block); + + return build_atom_info_wrapper ((Atom *) atom_data, atom_data_copy_data, + atom_data_free); +} diff --git a/subprojects/gst-plugins-good/gst/isomp4/atoms.h b/subprojects/gst-plugins-good/gst/isomp4/atoms.h index 5a36de954b..7fa5382241 100644 --- a/subprojects/gst-plugins-good/gst/isomp4/atoms.h +++ b/subprojects/gst-plugins-good/gst/isomp4/atoms.h @@ -1122,6 +1122,7 @@ AtomInfo * build_vpcC_extension (guint8 profile, guint8 level, guint8 b guint8 chroma_subsampling, gboolean video_full_range, guint8 colour_primaries, guint8 transfer_characteristics, guint8 matrix_coefficients); +AtomInfo * build_vvcC_extension (guint8 version, guint32 flags, const GstBuffer* codec_data); /* diff --git a/subprojects/gst-plugins-good/gst/isomp4/gstqtmux.c b/subprojects/gst-plugins-good/gst/isomp4/gstqtmux.c index a13e7e3de1..acdd8a9e1e 100644 --- a/subprojects/gst-plugins-good/gst/isomp4/gstqtmux.c +++ b/subprojects/gst-plugins-good/gst/isomp4/gstqtmux.c @@ -5820,7 +5820,8 @@ check_field (const GstIdStr * fieldname, const GValue * value, } if (g_strcmp0 (name, "video/x-h264") == 0 || - g_strcmp0 (name, "video/x-h265") == 0) { + g_strcmp0 (name, "video/x-h265") == 0 || + g_strcmp0 (name, "video/x-h266") == 0) { /* We support muxing multiple codec_data structures, and the new SPS * will contain updated tier / level / profiles, which means we do * not need to fail renegotiation when those change. @@ -6468,6 +6469,28 @@ gst_qt_mux_video_sink_set_caps (GstQTMuxPad * qtpad, GstCaps * caps) if (ext_atom != NULL) ext_atom_list = g_list_prepend (ext_atom_list, ext_atom); + } else if (strcmp (mimetype, "video/x-h266") == 0) { + const gchar *format; + + if (!codec_data) { + GST_WARNING_OBJECT (qtmux, "no codec_data in h266 caps"); + goto refuse_caps; + } + + format = gst_structure_get_string (structure, "stream-format"); + if (strcmp (format, "vvc1") == 0) + entry.fourcc = FOURCC_vvc1; + else if (strcmp (format, "vvi1") == 0) + entry.fourcc = FOURCC_vvi1; + + ext_atom = build_btrt_extension (0, qtpad->avg_bitrate, qtpad->max_bitrate); + if (ext_atom != NULL) + ext_atom_list = g_list_prepend (ext_atom_list, ext_atom); + + ext_atom = build_vvcC_extension (0, 0, codec_data); + if (ext_atom != NULL) + ext_atom_list = g_list_prepend (ext_atom_list, ext_atom); + } else if (strcmp (mimetype, "video/x-svq") == 0) { gint version = 0; const GstBuffer *seqh = NULL; diff --git a/subprojects/gst-plugins-good/gst/isomp4/gstqtmuxmap.c b/subprojects/gst-plugins-good/gst/isomp4/gstqtmuxmap.c index 88bc6da2ba..4abe6ad535 100644 --- a/subprojects/gst-plugins-good/gst/isomp4/gstqtmuxmap.c +++ b/subprojects/gst-plugins-good/gst/isomp4/gstqtmuxmap.c @@ -70,6 +70,12 @@ "alignment = (string) au, " \ COMMON_VIDEO_CAPS +#define H266_CAPS \ + "video/x-h266, " \ + "stream-format = (string) { vvc1, vvi1 }, " \ + "alignment = (string) au, " \ + COMMON_VIDEO_CAPS + #define MPEG4V_CAPS \ "video/mpeg, " \ "mpegversion = (int) 4, "\ @@ -197,6 +203,7 @@ GstQTMuxFormatProp gst_qt_mux_format_list[] = { H263_CAPS "; " H264_CAPS "; " H265_CAPS "; " + H266_CAPS "; " SVQ_CAPS "; " "video/x-dv, " "systemstream = (boolean) false, " @@ -229,9 +236,9 @@ GstQTMuxFormatProp gst_qt_mux_format_list[] = { "MP4", "GstMP4Mux", GST_STATIC_CAPS ("video/quicktime, variant = (string) iso"), - GST_STATIC_CAPS (MPEG4V_CAPS "; " H264_CAPS ";" H265_CAPS ";" - "video/x-mp4-part," COMMON_VIDEO_CAPS "; " - "video/x-av1, " "stream-format = (string) \"obu-stream\", " + GST_STATIC_CAPS (MPEG4V_CAPS "; " H264_CAPS ";" H265_CAPS ";" H266_CAPS + ";" "video/x-mp4-part," COMMON_VIDEO_CAPS "; " "video/x-av1, " + "stream-format = (string) \"obu-stream\", " "alignment = (string) \"tu\", " COMMON_VIDEO_CAPS "; " VP9_CAPS "; "), GST_STATIC_CAPS (MP123_CAPS "; " AAC_CAPS " ; " AC3_CAPS " ; " ALAC_CAPS