From 2e8afcf51aa038e1ff7877cb054b25f78d4ff99b Mon Sep 17 00:00:00 2001 From: Edward Hervey Date: Mon, 15 Jul 2024 16:10:10 +0200 Subject: [PATCH] mpegts: Add support for JPEG-XS Part-of: --- girs/GstMpegts-1.0.gir | 151 +++++++++++++++ .../docs/plugins/gst_plugins_cache.json | 4 +- .../gst-libs/gst/mpegts/gstmpegtsdescriptor.c | 180 +++++++++++++++++- .../gst-libs/gst/mpegts/gstmpegtsdescriptor.h | 52 +++++ .../gst-libs/gst/mpegts/gstmpegtssection.h | 8 + .../gst-plugins-bad/gst/mpegtsdemux/tsdemux.c | 96 ++++++++++ .../gst/mpegtsmux/gstbasetsmux.c | 160 ++++++++++++++++ .../gst/mpegtsmux/gstbasetsmuxjpegxs.c | 106 +++++++++++ .../gst/mpegtsmux/gstbasetsmuxjpegxs.h | 49 +++++ .../gst/mpegtsmux/gstmpegtsmux.c | 3 +- .../gst-plugins-bad/gst/mpegtsmux/meson.build | 1 + .../gst/mpegtsmux/tsmux/tsmuxstream.c | 13 ++ .../gst/mpegtsmux/tsmux/tsmuxstream.h | 6 +- 13 files changed, 822 insertions(+), 7 deletions(-) create mode 100644 subprojects/gst-plugins-bad/gst/mpegtsmux/gstbasetsmuxjpegxs.c create mode 100644 subprojects/gst-plugins-bad/gst/mpegtsmux/gstbasetsmuxjpegxs.h diff --git a/girs/GstMpegts-1.0.gir b/girs/GstMpegts-1.0.gir index c34b5f0d65..dfea53a786 100644 --- a/girs/GstMpegts-1.0.gir +++ b/girs/GstMpegts-1.0.gir @@ -1311,6 +1311,20 @@ two bytes are the @tag and @length. + + Copy the given descriptor. + + + A copy of @desc. + + + + + A #GstMpegtsDescriptor: + + + + Frees @desc @@ -2047,6 +2061,24 @@ ISO 639-1 language code from the returned ISO 639-2 one. + + Parses the JPEG-XS descriptor information from @descriptor: + + + TRUE if the information could be parsed, else FALSE. + + + + + A #GstMpegtsDescriptor + + + + A parsed #GstMpegtsJpegXsDescriptor + + + + Extracts the logical channels from @descriptor. @@ -2310,6 +2342,20 @@ a single language + + Create a new #GstMpegtsDescriptor based on the information in @jpegxs + + + The #GstMpegtsDescriptor + + + + + A #GstMpegtsJpegXsDescriptor + + + + @@ -2799,6 +2845,94 @@ Consult the relevant specifications for more details. + + JPEG-XS descriptor + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -4746,6 +4880,9 @@ profiles defined in Annex A for service-compatible stereoscopic 3D servicesRec. ITU-T H.265 | ISO/IEC 23008-2 video stream or an HEVC temporal video sub-bitstream + + JPEG-XS stream type + IPMP stream @@ -5091,6 +5228,20 @@ a single language + + Create a new #GstMpegtsDescriptor based on the information in @jpegxs + + + The #GstMpegtsDescriptor + + + + + A #GstMpegtsJpegXsDescriptor + + + + diff --git a/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json b/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json index bcf5cf2967..44c8fedc0b 100644 --- a/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json +++ b/subprojects/gst-plugins-bad/docs/plugins/gst_plugins_cache.json @@ -220605,7 +220605,7 @@ "presence": "sometimes" }, "video_%%01x_%%05x": { - "caps": "video/mpeg:\n mpegversion: { (int)1, (int)2, (int)4 }\n systemstream: false\nvideo/x-h264:\n stream-format: byte-stream\nvideo/x-h265:\n stream-format: byte-stream\nvideo/x-dirac:\nvideo/x-cavs:\nvideo/x-wmv:\n wmvversion: 3\n format: WVC1\nimage/x-jpc:\n", + "caps": "video/mpeg:\n mpegversion: { (int)1, (int)2, (int)4 }\n systemstream: false\nvideo/x-h264:\n stream-format: byte-stream\nvideo/x-h265:\n stream-format: byte-stream\nvideo/x-dirac:\nvideo/x-cavs:\nvideo/x-wmv:\n wmvversion: 3\n format: WVC1\nimage/x-jpc:\nimage/x-jxsc:\n", "direction": "src", "presence": "sometimes" } @@ -220863,7 +220863,7 @@ "long-name": "MPEG Transport Stream Muxer", "pad-templates": { "sink_%%d": { - "caps": "video/mpeg:\n parsed: true\n mpegversion: { (int)1, (int)2, (int)4 }\n systemstream: false\nvideo/x-dirac:\nimage/x-jpc:\n alignment: frame\nvideo/x-h264:\n stream-format: byte-stream\n alignment: { (string)au, (string)nal }\nvideo/x-h265:\n stream-format: byte-stream\n alignment: { (string)au, (string)nal }\naudio/mpeg:\n parsed: true\n mpegversion: 1\naudio/mpeg:\n framed: true\n mpegversion: { (int)2, (int)4 }\n stream-format: { (string)adts, (string)raw }\naudio/x-lpcm:\n width: { (int)16, (int)20, (int)24 }\n rate: { (int)48000, (int)96000 }\n channels: [ 1, 8 ]\n dynamic_range: [ 0, 255 ]\n emphasis: { (boolean)false, (boolean)true }\n mute: { (boolean)false, (boolean)true }\naudio/x-ac3:\n framed: true\naudio/x-dts:\n framed: true\naudio/x-opus:\n channels: [ 1, 255 ]\nsubpicture/x-dvb:\napplication/x-teletext:\nmeta/x-klv:\n parsed: true\nmeta/x-id3:\n parsed: true\nimage/x-jpc:\n alignment: frame\n profile: [ 0, 49151 ]\n", + "caps": "video/mpeg:\n parsed: true\n mpegversion: { (int)1, (int)2, (int)4 }\n systemstream: false\nvideo/x-dirac:\nimage/x-jpc:\n alignment: frame\nvideo/x-h264:\n stream-format: byte-stream\n alignment: { (string)au, (string)nal }\nvideo/x-h265:\n stream-format: byte-stream\n alignment: { (string)au, (string)nal }\naudio/mpeg:\n parsed: true\n mpegversion: 1\naudio/mpeg:\n framed: true\n mpegversion: { (int)2, (int)4 }\n stream-format: { (string)adts, (string)raw }\naudio/x-lpcm:\n width: { (int)16, (int)20, (int)24 }\n rate: { (int)48000, (int)96000 }\n channels: [ 1, 8 ]\n dynamic_range: [ 0, 255 ]\n emphasis: { (boolean)false, (boolean)true }\n mute: { (boolean)false, (boolean)true }\naudio/x-ac3:\n framed: true\naudio/x-dts:\n framed: true\naudio/x-opus:\n channels: [ 1, 255 ]\nsubpicture/x-dvb:\napplication/x-teletext:\nmeta/x-klv:\n parsed: true\nmeta/x-id3:\n parsed: true\nimage/x-jpc:\n alignment: frame\n profile: [ 0, 49151 ]\nimage/x-jxsc:\n alignment: frame\n sampling: { (string)YCbCr-4:2:2, (string)YCbCr-4:4:4 }\n", "direction": "sink", "presence": "request", "type": "GstBaseTsMuxPad" diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mpegts/gstmpegtsdescriptor.c b/subprojects/gst-plugins-bad/gst-libs/gst/mpegts/gstmpegtsdescriptor.c index 8b6bcf2c8d..a976fa41e7 100644 --- a/subprojects/gst-plugins-bad/gst-libs/gst/mpegts/gstmpegtsdescriptor.c +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mpegts/gstmpegtsdescriptor.c @@ -696,8 +696,18 @@ _new_descriptor_with_extension (guint8 tag, guint8 tag_extension, guint8 length) return descriptor; } -static GstMpegtsDescriptor * -_copy_descriptor (GstMpegtsDescriptor * desc) +/** + * gst_mpegts_descriptor_copy: + * @desc: (transfer none): A #GstMpegtsDescriptor: + * + * Copy the given descriptor. + * + * Returns: (transfer full): A copy of @desc. + * + * Since: 1.26 + */ +GstMpegtsDescriptor * +gst_mpegts_descriptor_copy (GstMpegtsDescriptor * desc) { GstMpegtsDescriptor *copy; @@ -721,7 +731,7 @@ gst_mpegts_descriptor_free (GstMpegtsDescriptor * desc) } G_DEFINE_BOXED_TYPE (GstMpegtsDescriptor, gst_mpegts_descriptor, - (GBoxedCopyFunc) _copy_descriptor, + (GBoxedCopyFunc) gst_mpegts_descriptor_copy, (GBoxedFreeFunc) gst_mpegts_descriptor_free); /** @@ -1544,3 +1554,167 @@ gst_mpegts_descriptor_from_metadata_pointer (const return descriptor; } + +DEFINE_STATIC_COPY_FUNCTION (GstMpegtsJpegXsDescriptor, + gst_mpegts_jpeg_xs_descriptor); +DEFINE_STATIC_FREE_FUNCTION (GstMpegtsJpegXsDescriptor, + gst_mpegts_jpeg_xs_descriptor); + +G_DEFINE_BOXED_TYPE (GstMpegtsJpegXsDescriptor, gst_mpegts_jpeg_xs_descriptor, + (GBoxedCopyFunc) _gst_mpegts_jpeg_xs_descriptor_copy, + (GFreeFunc) _gst_mpegts_jpeg_xs_descriptor_free); + +/** + * gst_mpegts_descriptor_parse_jpeg_xs: + * @descriptor: A #GstMpegtsDescriptor + * @res: (out): A parsed #GstMpegtsJpegXsDescriptor + * + * Parses the JPEG-XS descriptor information from @descriptor: + * + * Returns: TRUE if the information could be parsed, else FALSE. + * + * Since: 1.26 + */ + +gboolean +gst_mpegts_descriptor_parse_jpeg_xs (const GstMpegtsDescriptor * descriptor, + GstMpegtsJpegXsDescriptor * res) +{ + GstByteReader br; + guint8 flags; + g_return_val_if_fail (descriptor != NULL && res != NULL, FALSE); + + /* The smallest jpegxs descriptor doesn't contain the MDM, but is an H.222.0 extension (so additional one byte) */ + __common_desc_ext_checks (descriptor, GST_MTS_DESC_EXT_JXS_VIDEO, 32, FALSE); + + /* Skip tag/length/extension/tag/length */ + gst_byte_reader_init (&br, descriptor->data + 5, descriptor->length - 3); + memset (res, 0, sizeof (*res)); + + /* First part can be scanned out with unchecked reader */ + res->descriptor_version = gst_byte_reader_get_uint8_unchecked (&br); + if (res->descriptor_version != 0) { + GST_WARNING ("Unsupported JPEG-XS descriptor version (%d != 0)", + res->descriptor_version); + return FALSE; + } + res->horizontal_size = gst_byte_reader_get_uint16_be_unchecked (&br); + res->vertical_size = gst_byte_reader_get_uint16_be_unchecked (&br); + res->brat = gst_byte_reader_get_uint32_be_unchecked (&br); + res->frat = gst_byte_reader_get_uint32_be_unchecked (&br); + res->schar = gst_byte_reader_get_uint16_be_unchecked (&br); + res->Ppih = gst_byte_reader_get_uint16_be_unchecked (&br); + res->Plev = gst_byte_reader_get_uint16_be_unchecked (&br); + res->max_buffer_size = gst_byte_reader_get_uint32_be_unchecked (&br); + res->buffer_model_type = gst_byte_reader_get_uint8_unchecked (&br); + res->colour_primaries = gst_byte_reader_get_uint8_unchecked (&br); + res->transfer_characteristics = gst_byte_reader_get_uint8_unchecked (&br); + res->matrix_coefficients = gst_byte_reader_get_uint8_unchecked (&br); + + res->video_full_range_flag = + (gst_byte_reader_get_uint8_unchecked (&br) & 0x80) == 0x80; + flags = gst_byte_reader_get_uint8_unchecked (&br); + res->still_mode = flags >> 7; + if ((flags & 0x40) == 0x40) { + if (gst_byte_reader_get_remaining (&br) < 28) { + GST_ERROR ("MDM present on JPEG-XS descriptor but not enough bytes"); + return FALSE; + } + res->X_c0 = gst_byte_reader_get_uint16_be_unchecked (&br); + res->Y_c0 = gst_byte_reader_get_uint16_be_unchecked (&br); + res->X_c1 = gst_byte_reader_get_uint16_be_unchecked (&br); + res->Y_c1 = gst_byte_reader_get_uint16_be_unchecked (&br); + res->X_c2 = gst_byte_reader_get_uint16_be_unchecked (&br); + res->Y_c2 = gst_byte_reader_get_uint16_be_unchecked (&br); + res->X_wp = gst_byte_reader_get_uint16_be_unchecked (&br); + res->Y_wp = gst_byte_reader_get_uint16_be_unchecked (&br); + res->L_max = gst_byte_reader_get_uint32_be_unchecked (&br); + res->L_min = gst_byte_reader_get_uint32_be_unchecked (&br); + res->MaxCLL = gst_byte_reader_get_uint16_be_unchecked (&br); + res->MaxFALL = gst_byte_reader_get_uint16_be_unchecked (&br); + } + + return TRUE; +} + +/** + * gst_mpegts_descriptor_from_jpeg_xs: + * @jpegxs: A #GstMpegtsJpegXsDescriptor + * + * Create a new #GstMpegtsDescriptor based on the information in @jpegxs + * + * Returns: (transfer full): The #GstMpegtsDescriptor + * + * Since: 1.26 + */ +GstMpegtsDescriptor * +gst_mpegts_descriptor_from_jpeg_xs (const GstMpegtsJpegXsDescriptor * jpegxs) +{ + gsize desc_size = 30; + GstByteWriter writer; + guint8 *desc_data; + GstMpegtsDescriptor *descriptor; + + /* Extension descriptor + * tag/length are take care of by gst_mpegts_descriptor_from_custom + * The size of the "internal" descriptor (in the extension) is 1 (for the extension_descriptor_tag) and 29 for JXS_video_descriptor + */ + + gst_byte_writer_init_with_size (&writer, desc_size, FALSE); + + /* extension tag */ + gst_byte_writer_put_uint8 (&writer, GST_MTS_DESC_EXT_JXS_VIDEO); + /* tag/length again */ + gst_byte_writer_put_uint8 (&writer, GST_MTS_DESC_EXT_JXS_VIDEO); + /* Size is 27 (29 minus 2 initial bytes for tag/length */ + gst_byte_writer_put_uint8 (&writer, 27); + /* descriptor version: 0 */ + gst_byte_writer_put_uint8 (&writer, 0); + /* horizontal/vertical size */ + gst_byte_writer_put_uint16_be (&writer, jpegxs->horizontal_size); + gst_byte_writer_put_uint16_be (&writer, jpegxs->vertical_size); + /* brat/frat */ + gst_byte_writer_put_uint32_be (&writer, jpegxs->brat); + gst_byte_writer_put_uint32_be (&writer, jpegxs->frat); + + /* schar, Ppih, Plev */ + gst_byte_writer_put_uint16_be (&writer, jpegxs->schar); + gst_byte_writer_put_uint16_be (&writer, jpegxs->Ppih); + gst_byte_writer_put_uint16_be (&writer, jpegxs->Plev); + + gst_byte_writer_put_uint32_be (&writer, jpegxs->max_buffer_size); + + /* Buffer model type */ + gst_byte_writer_put_uint8 (&writer, jpegxs->buffer_model_type); + + /* color_primaries */ + gst_byte_writer_put_uint8 (&writer, jpegxs->colour_primaries); + + /* transfer_characteristics */ + gst_byte_writer_put_uint8 (&writer, jpegxs->transfer_characteristics); + + /* matrix_coefficients */ + gst_byte_writer_put_uint8 (&writer, jpegxs->matrix_coefficients); + + /* video_full_range_flag */ + gst_byte_writer_put_uint8 (&writer, + jpegxs->video_full_range_flag ? 1 << 7 : 0); + + /* still_mode_flag : off + * mdm_flag : off */ + gst_byte_writer_put_uint8 (&writer, jpegxs->still_mode ? 1 : 0); + + if (jpegxs->mdm_flag) { + GST_ERROR ("Mastering Display Metadata not supported yet !"); + } + + desc_size = gst_byte_writer_get_size (&writer); + desc_data = gst_byte_writer_reset_and_get_data (&writer); + + descriptor = + gst_mpegts_descriptor_from_custom (GST_MTS_DESC_EXTENSION, desc_data, + desc_size); + g_free (desc_data); + + return descriptor; +} diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mpegts/gstmpegtsdescriptor.h b/subprojects/gst-plugins-bad/gst-libs/gst/mpegts/gstmpegtsdescriptor.h index 400189e691..4981a6168f 100644 --- a/subprojects/gst-plugins-bad/gst-libs/gst/mpegts/gstmpegtsdescriptor.h +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mpegts/gstmpegtsdescriptor.h @@ -226,6 +226,9 @@ struct _GstMpegtsDescriptor GST_MPEGTS_API void gst_mpegts_descriptor_free (GstMpegtsDescriptor *desc); +GST_MPEGTS_API +GstMpegtsDescriptor * gst_mpegts_descriptor_copy (GstMpegtsDescriptor *desc); + GST_MPEGTS_API GPtrArray *gst_mpegts_parse_descriptors (guint8 * buffer, gsize buf_len); @@ -663,6 +666,55 @@ GType gst_mpegts_metadata_pointer_descriptor_get_type(void); GST_MPEGTS_API GstMpegtsDescriptor *gst_mpegts_descriptor_from_metadata_pointer(const GstMpegtsMetadataPointerDescriptor *metadata_pointer_descriptor); +/* JPEG-XS descriptor */ + +/** + * GstMpegtsJpegXsDescriptor: + * + * JPEG-XS descriptor + * + * Since: 1.26 + */ + +typedef struct _GstMpegtsJpegXsDescriptor { + guint8 descriptor_version; + guint16 horizontal_size, vertical_size; + guint32 brat, frat; + guint16 schar, Ppih, Plev; + guint32 max_buffer_size; + guint8 buffer_model_type; + guint8 colour_primaries; + guint8 transfer_characteristics; + guint8 matrix_coefficients; + gboolean video_full_range_flag; + gboolean still_mode; + gboolean mdm_flag; + guint16 X_c0, Y_c0, X_c1, Y_c1, X_c2, Y_c2; + guint16 X_wp, Y_wp; + guint32 L_max, L_min; + guint16 MaxCLL, MaxFALL; +} GstMpegtsJpegXsDescriptor; + +/** + * GST_TYPE_MPEGTS_JPEG_XS_DESCRIPTOR: + * + * Since: 1.26 + */ +#define GST_TYPE_MPEGTS_JPEG_XS_DESCRIPTOR \ + (gst_mpegts_jpeg_xs_descriptor_get_type()) + +GST_MPEGTS_API +GType gst_mpegts_jpeg_xs_descriptor_get_type(void); + +GST_MPEGTS_API +gboolean +gst_mpegts_descriptor_parse_jpeg_xs(const GstMpegtsDescriptor *descriptor, + GstMpegtsJpegXsDescriptor *res); + +GST_MPEGTS_API +GstMpegtsDescriptor * +gst_mpegts_descriptor_from_jpeg_xs(const GstMpegtsJpegXsDescriptor *jpegxs); + G_END_DECLS #endif /* GST_MPEGTS_DESCRIPTOR_H */ diff --git a/subprojects/gst-plugins-bad/gst-libs/gst/mpegts/gstmpegtssection.h b/subprojects/gst-plugins-bad/gst-libs/gst/mpegts/gstmpegtssection.h index b40c84f13b..23884426a8 100644 --- a/subprojects/gst-plugins-bad/gst-libs/gst/mpegts/gstmpegtssection.h +++ b/subprojects/gst-plugins-bad/gst-libs/gst/mpegts/gstmpegtssection.h @@ -399,6 +399,14 @@ typedef enum { GST_MPEGTS_STREAM_TYPE_VIDEO_H264_STEREO_ADDITIONAL_VIEW = 0x23, GST_MPEGTS_STREAM_TYPE_VIDEO_HEVC = 0x24, /* 0x24 - 0x7e : Rec. ITU-T H.222.0 | ISO/IEC 13818-1 Reserved */ + /** + * GST_MPEGTS_STREAM_TYPE_VIDEO_JPEG_XS: + * + * JPEG-XS stream type + * + * Since: 1.26 + */ + GST_MPEGTS_STREAM_TYPE_VIDEO_JPEG_XS = 0x32, GST_MPEGTS_STREAM_TYPE_IPMP_STREAM = 0x7f, /* 0x80 - 0xff : User Private (or defined in other specs) */ diff --git a/subprojects/gst-plugins-bad/gst/mpegtsdemux/tsdemux.c b/subprojects/gst-plugins-bad/gst/mpegtsdemux/tsdemux.c index a3d5effb49..5367ba331a 100644 --- a/subprojects/gst-plugins-bad/gst/mpegtsdemux/tsdemux.c +++ b/subprojects/gst-plugins-bad/gst/mpegtsdemux/tsdemux.c @@ -243,6 +243,7 @@ struct _TSDemuxStream "wmvversion = (int) 3, " \ "format = (string) WVC1;" \ "image/x-jpc;" \ + "image/x-jxsc;" \ ) #define AUDIO_CAPS \ @@ -1848,6 +1849,53 @@ create_pad_for_stream (MpegTSBase * base, MpegTSBaseStream * bstream, "colorspace", G_TYPE_STRING, colorspace, NULL); } break; + case GST_MPEGTS_STREAM_TYPE_VIDEO_JPEG_XS: + { + GstMpegtsJpegXsDescriptor jpegxs; + desc = + mpegts_get_descriptor_from_stream_with_extension (bstream, + GST_MTS_DESC_EXTENSION, GST_MTS_DESC_EXT_JXS_VIDEO); + if (!desc) { + GST_WARNING_OBJECT (demux, + "image/x-jxsc stream does not have mandatory descriptor"); + break; + } + if (!gst_mpegts_descriptor_parse_jpeg_xs (desc, &jpegxs)) { + GST_WARNING_OBJECT (demux, "Invalid JPEG XS descriptor"); + break; + } + if (jpegxs.frat >> 30) { + GST_WARNING_OBJECT (demux, "Interlaced JPEG-XS not supported yet"); + break; + } + if ((jpegxs.schar >> 15) == 0) { + GST_WARNING_OBJECT (demux, "JPEG-XS sampling properties are required"); + break; + } + is_video = TRUE; + caps = + gst_caps_from_string + ("image/x-jxsc, alignment=(string)frame, interlace-mode=(string)progressive"); + + /* interlace-mode, sampling, depth */ + gst_caps_set_simple (caps, "width", G_TYPE_INT, jpegxs.horizontal_size, + "height", G_TYPE_INT, jpegxs.vertical_size, "depth", G_TYPE_INT, + (jpegxs.schar >> 4) & 0xf, NULL); + switch (jpegxs.schar & 0xf) { + case 0: + gst_caps_set_simple (caps, "sampling", G_TYPE_STRING, "YCbCr-4:2:2", + NULL); + break; + case 1: + gst_caps_set_simple (caps, "sampling", G_TYPE_STRING, "YCbCr-4:4:4", + NULL); + break; + default: + GST_WARNING_OBJECT (demux, "Unsupported JPEG-XS sampling format"); + break; + } + break; + } case ST_VIDEO_DIRAC: if (bstream->registration_id == 0x64726163) { GST_LOG_OBJECT (demux, "dirac"); @@ -3119,6 +3167,50 @@ error: } } +static GstBuffer * +parse_jpegxs_access_unit (TSDemuxStream * stream) +{ + GstByteReader br; + guint32 header_tag; + guint32 header_size; + GstBuffer *retbuf; + + if (stream->current_size < 30) { + GST_ERROR_OBJECT (stream->pad, "Not enough data for header"); + goto error; + } + + gst_byte_reader_init (&br, stream->data, stream->current_size); + + /* Should start with `jxes` box header */ + header_size = gst_byte_reader_get_uint32_be_unchecked (&br); + header_tag = gst_byte_reader_get_uint32_be_unchecked (&br); + if (header_size != 30 || header_tag != 0x6a786573) { + GST_ERROR_OBJECT (stream->pad, + "Invalid 'jxes' header (size:%u, tag:%" GST_FOURCC_FORMAT ")", + header_size, GST_FOURCC_ARGS (header_tag)); + return NULL; + } + + /* FIXME : Parse/extract timecode */ + + /* Ignore the rest of that box */ + retbuf = + gst_buffer_new_wrapped_full (0, stream->data, stream->current_size, + header_size, stream->current_size - header_size, stream->data, g_free); + stream->data = NULL; + stream->current_size = 0; + return retbuf; + +error: + GST_ERROR ("Failed to parse JPEG-XS access unit"); + g_free (stream->data); + stream->data = NULL; + stream->current_size = 0; + return NULL; + +} + /* interlaced mode is disabled at the moment */ /*#define TSDEMUX_JP2K_SUPPORT_INTERLACE */ static GstBuffer * @@ -3472,6 +3564,8 @@ gst_ts_demux_push_pending_data (GstTSDemux * demux, TSDemuxStream * stream, } else if (bs->stream_type == GST_MPEGTS_STREAM_TYPE_METADATA_PES_PACKETS && bs->registration_id == DRF_ID_KLVA) { buffer_list = parse_pes_metadata_frame (stream); + } else if (bs->stream_type == GST_MPEGTS_STREAM_TYPE_VIDEO_JPEG_XS) { + buffer = parse_jpegxs_access_unit (stream); } else { buffer = gst_buffer_new_wrapped (stream->data, stream->current_size); } @@ -3522,6 +3616,8 @@ gst_ts_demux_push_pending_data (GstTSDemux * demux, TSDemuxStream * stream, } else if (bs->stream_type == GST_MPEGTS_STREAM_TYPE_METADATA_PES_PACKETS && bs->registration_id == DRF_ID_KLVA) { buffer_list = parse_pes_metadata_frame (stream); + } else if (bs->stream_type == GST_MPEGTS_STREAM_TYPE_VIDEO_JPEG_XS) { + buffer = parse_jpegxs_access_unit (stream); } else { buffer = gst_buffer_new_wrapped (stream->data, stream->current_size); } diff --git a/subprojects/gst-plugins-bad/gst/mpegtsmux/gstbasetsmux.c b/subprojects/gst-plugins-bad/gst/mpegtsmux/gstbasetsmux.c index ba93947e90..0985882fe8 100644 --- a/subprojects/gst-plugins-bad/gst/mpegtsmux/gstbasetsmux.c +++ b/subprojects/gst-plugins-bad/gst/mpegtsmux/gstbasetsmux.c @@ -85,6 +85,7 @@ #include "gstbasetsmuxttxt.h" #include "gstbasetsmuxopus.h" #include "gstbasetsmuxjpeg2000.h" +#include "gstbasetsmuxjpegxs.h" GST_DEBUG_CATEGORY (gst_base_ts_mux_debug); #define GST_CAT_DEFAULT gst_base_ts_mux_debug @@ -440,6 +441,140 @@ release_buffer_cb (guint8 * data, void *user_data) stream_data_free ((StreamData *) user_data); } +static GstMpegtsJpegXsDescriptor * +gst_base_ts_mux_jpegxs_descriptor (GstBaseTsMux * mux, + GstBaseTsMuxPad * ts_pad, GstCaps * caps) +{ + GstStructure *s = gst_caps_get_structure (caps, 0); + gint codestream_length, depth; + GstMpegtsJpegXsDescriptor *jpegxs_descriptor; + GstVideoInfo video_info; + const gchar *sampling; + + if (!gst_video_info_from_caps (&video_info, caps)) + return NULL; + + /* Get (and calculate) all fields from the caps information */ + sampling = gst_structure_get_string (s, "sampling"); + if (!gst_structure_get_int (s, "codestream-length", &codestream_length) + || !sampling || !gst_structure_get_int (s, "depth", &depth) || !depth) { + GST_ERROR_OBJECT (ts_pad, + "JPEG-XS caps doesn't contain all required fields"); + return NULL; + } + + jpegxs_descriptor = g_new0 (GstMpegtsJpegXsDescriptor, 1); + + jpegxs_descriptor->horizontal_size = GST_VIDEO_INFO_WIDTH (&video_info); + jpegxs_descriptor->vertical_size = GST_VIDEO_INFO_HEIGHT (&video_info); + + { + /* FIXME : Cap according to limit defined by profile/level */ + guint32 brat = G_MAXUINT32; + if (GST_VIDEO_INFO_FPS_N (&video_info) > 0 + && GST_VIDEO_INFO_FPS_D (&video_info) > 0) { + // 125000 = * 8 / 1000000 (convert to bits, divide to Mbps) + guint64 v = + gst_util_uint64_scale_ceil (GST_VIDEO_INFO_FPS_N (&video_info), + codestream_length, + GST_VIDEO_INFO_FPS_D (&video_info) * 125000); + if (v < G_MAXUINT32) + brat = v & 0xffffffff; + } + jpegxs_descriptor->brat = brat; + } + + { + guint32 frat = 0; + gint fps_n = GST_VIDEO_INFO_FPS_N (&video_info); + gint fps_d = GST_VIDEO_INFO_FPS_D (&video_info); + gint denom_value = 1; + + /* Only framerate divisible by 1 or 1.001 are allowed */ + if (fps_d == 1001) { + fps_n /= 1000; + denom_value = 2; + } else if (fps_d != 1) { + GST_ERROR_OBJECT (ts_pad, "framerate %d/%d is not allowed for JPEG-XS", + fps_n, fps_d); + goto free_return; + } + if (fps_n > G_MAXUINT16) { + GST_ERROR_OBJECT (ts_pad, "framerate %d/%d exceeds limits for JPEG-XS", + fps_n, fps_d); + goto free_return; + } + + if (GST_VIDEO_INFO_IS_INTERLACED (&video_info)) { + if (GST_VIDEO_INFO_FIELD_ORDER (&video_info) == + GST_VIDEO_FIELD_ORDER_TOP_FIELD_FIRST) { + frat |= 1 << 30; + } else if (GST_VIDEO_INFO_FIELD_ORDER (&video_info) == + GST_VIDEO_FIELD_ORDER_BOTTOM_FIELD_FIRST) { + frat |= 1 << 31; + } else { + GST_ERROR_OBJECT (ts_pad, "Unknown interlace mode"); + goto free_return; + } + } + + frat |= denom_value << 24; + frat |= fps_n; + + jpegxs_descriptor->frat = frat; + } + + { + guint16 schar = (depth & 0xf) << 4; + /* FIXME : Support all other variants */ + if (!g_strcmp0 (sampling, "YCbCr-4:2:2")) { + schar |= 0; + } else if (!g_strcmp0 (sampling, "YCbCr-4:4:4")) { + schar |= 1; + } else { + GST_ERROR_OBJECT (ts_pad, "Unsupported sampling %s", sampling); + goto free_return; + } + + /* schar is valid */ + schar |= 1 << 15; + + jpegxs_descriptor->schar = schar; + } + + /* FIXME : Handle profile/level/sublevel once provided by caps. For now we are unrestricted */ + jpegxs_descriptor->Ppih = 0; + jpegxs_descriptor->Plev = 0; + + /* FIXME : Calculate max_buffer_size based on profile/level if specified */ + jpegxs_descriptor->max_buffer_size = jpegxs_descriptor->brat / 160; + + /* Hardcoded buffer_model_type of 2 accordingly to H.222.0 specification */ + jpegxs_descriptor->buffer_model_type = 2; + + jpegxs_descriptor->colour_primaries = + gst_video_color_primaries_to_iso (video_info.colorimetry.primaries); + jpegxs_descriptor->transfer_characteristics = + gst_video_transfer_function_to_iso (video_info.colorimetry.transfer); + jpegxs_descriptor->matrix_coefficients = + gst_video_color_matrix_to_iso (video_info.colorimetry.matrix); + jpegxs_descriptor->video_full_range_flag = + video_info.colorimetry.range == GST_VIDEO_COLOR_RANGE_0_255; + + /* We don't accept still pictures */ + jpegxs_descriptor->still_mode = FALSE; + + /* FIXME : Add support for Mastering Display Metadata parsing */ + + return jpegxs_descriptor; + +free_return: + { + g_free (jpegxs_descriptor); + return NULL; + } +} + /* Must be called with mux->lock held */ static GstFlowReturn gst_base_ts_mux_create_or_update_stream (GstBaseTsMux * mux, @@ -459,6 +594,7 @@ gst_base_ts_mux_create_or_update_stream (GstBaseTsMux * mux, const gchar *stream_format = NULL; const char *interlace_mode = NULL; gchar *pmt_name; + GstMpegtsDescriptor *pmt_descriptor = NULL; GST_DEBUG_OBJECT (ts_pad, "%s stream with PID 0x%04x for caps %" GST_PTR_FORMAT, @@ -676,6 +812,22 @@ gst_base_ts_mux_create_or_update_stream (GstBaseTsMux * mux, st = TSMUX_ST_PS_KLV; } else if (strcmp (mt, "meta/x-id3") == 0) { st = TSMUX_ST_PS_ID3; + } else if (strcmp (mt, "image/x-jxsc") == 0) { + /* FIXME: Get actual values from caps */ + GstMpegtsJpegXsDescriptor *jpegxs_descriptor = + gst_base_ts_mux_jpegxs_descriptor (mux, ts_pad, caps); + if (!jpegxs_descriptor) + goto not_negotiated; + + pmt_descriptor = gst_mpegts_descriptor_from_jpeg_xs (jpegxs_descriptor); + if (!pmt_descriptor) { + g_free (jpegxs_descriptor); + goto not_negotiated; + } + st = TSMUX_ST_VIDEO_JPEG_XS; + ts_pad->prepare_func = gst_base_ts_mux_prepare_jpegxs; + ts_pad->prepare_data = jpegxs_descriptor; + ts_pad->free_func = gst_base_ts_mux_free_jpegxs; } else if (strcmp (mt, "image/x-jpc") == 0) { /* * See this document for more details on standard: @@ -802,6 +954,10 @@ gst_base_ts_mux_create_or_update_stream (GstBaseTsMux * mux, goto error; } + if (pmt_descriptor) { + ts_pad->stream->pmt_descriptor = pmt_descriptor; + } + pmt_name = g_strdup_printf ("PMT_%d", ts_pad->pid); if (mux->prog_map && gst_structure_has_field (mux->prog_map, pmt_name)) { gst_structure_get_int (mux->prog_map, pmt_name, &ts_pad->stream->pmt_index); @@ -842,9 +998,13 @@ gst_base_ts_mux_create_or_update_stream (GstBaseTsMux * mux, /* ERRORS */ not_negotiated: + if (pmt_descriptor) + gst_mpegts_descriptor_free (pmt_descriptor); return GST_FLOW_NOT_NEGOTIATED; error: + if (pmt_descriptor) + gst_mpegts_descriptor_free (pmt_descriptor); return GST_FLOW_ERROR; } diff --git a/subprojects/gst-plugins-bad/gst/mpegtsmux/gstbasetsmuxjpegxs.c b/subprojects/gst-plugins-bad/gst/mpegtsmux/gstbasetsmuxjpegxs.c new file mode 100644 index 0000000000..786f47907f --- /dev/null +++ b/subprojects/gst-plugins-bad/gst/mpegtsmux/gstbasetsmuxjpegxs.c @@ -0,0 +1,106 @@ +/* JPEG-XS support for MPEG-TS + * + * Copyright (C) <2024> Centricular ltd + * @author Edward Hervey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include "gstbasetsmuxjpegxs.h" +#include +#include +#include + +#define GST_CAT_DEFAULT gst_base_ts_mux_debug + +GstBuffer * +gst_base_ts_mux_prepare_jpegxs (GstBuffer * buf, GstBaseTsMuxPad * pad, + GstBaseTsMux * mux) +{ + GstMpegtsJpegXsDescriptor *private_data = pad->prepare_data; + GstByteWriter wr; + GstBuffer *out_buf = NULL; + gsize header_size = 30; + guint8 *jxes_header = NULL; + GstClockTime seconds = buf->pts / GST_SECOND; + GstClockTime minutes = seconds / 60; + GstClockTime hours = minutes / 60; + + /* FIXME : Instead of constantly allocating/freeing a new header, we should: + * * Generate this header once + * * Update it only for the new tcod + */ + + seconds = seconds % 60; + minutes = minutes % 60; + hours = hours % 24; + + /* Box is fixed size */ + gst_byte_writer_init_with_size (&wr, header_size, FALSE); + + gst_byte_writer_put_uint32_be (&wr, header_size); + /* Elementary stream header box 'jxes' == 0x6a786573 */ + gst_byte_writer_put_uint32_be (&wr, 0x6a786573); + + /* brat, frat are 32 bytes */ + gst_byte_writer_put_uint32_be (&wr, private_data->brat); + gst_byte_writer_put_uint32_be (&wr, private_data->frat); + + /* schar, Ppih, Plev */ + gst_byte_writer_put_uint16_be (&wr, private_data->schar); + gst_byte_writer_put_uint16_be (&wr, private_data->Ppih); + gst_byte_writer_put_uint16_be (&wr, private_data->Plev); + + gst_byte_writer_put_uint8 (&wr, private_data->colour_primaries); + gst_byte_writer_put_uint8 (&wr, private_data->transfer_characteristics); + gst_byte_writer_put_uint8 (&wr, private_data->matrix_coefficients); + + gst_byte_writer_put_uint8 (&wr, private_data->video_full_range_flag << 7); + + /* put HHMMSSFF */ + gst_byte_writer_put_uint8 (&wr, (guint8) hours); + gst_byte_writer_put_uint8 (&wr, (guint8) minutes); + gst_byte_writer_put_uint8 (&wr, (guint8) seconds); + gst_byte_writer_put_uint8 (&wr, 0x0); + + /* Put jxes header in buffer */ + header_size = gst_byte_writer_get_size (&wr); + jxes_header = gst_byte_writer_reset_and_get_data (&wr); + out_buf = gst_buffer_new_wrapped (jxes_header, header_size); + + /* Copy complete frame */ + gst_buffer_copy_into (out_buf, buf, + GST_BUFFER_COPY_METADATA | GST_BUFFER_COPY_TIMESTAMPS | + GST_BUFFER_COPY_MEMORY, 0, -1); + GST_DEBUG_OBJECT (mux, "Prepared JPEGXS PES of size %d", + (int) gst_buffer_get_size (out_buf)); + + return out_buf; +} + +void +gst_base_ts_mux_free_jpegxs (gpointer prepare_data) +{ + /* Free prepare data memory object */ + g_free (prepare_data); +} diff --git a/subprojects/gst-plugins-bad/gst/mpegtsmux/gstbasetsmuxjpegxs.h b/subprojects/gst-plugins-bad/gst/mpegtsmux/gstbasetsmuxjpegxs.h new file mode 100644 index 0000000000..039ff5828c --- /dev/null +++ b/subprojects/gst-plugins-bad/gst/mpegtsmux/gstbasetsmuxjpegxs.h @@ -0,0 +1,49 @@ +/* Support for JPEG-XS + * + * Copyright (C) <2024> Centricular ltd + * @author Edward Hervey + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#ifndef __BASETSMUX_JPEGXS_H__ +#define __BASETSMUX_JPEGXS_H__ + +#include "glib.h" +#include "gstbasetsmux.h" + +typedef struct jpegxs_private_data +{ + guint32 brat; + guint32 frat; + guint16 schar; + guint16 Ppih; + guint16 Plev; + guint8 color_primaries; + guint8 transfer_characteristics; + guint8 matrix_coefficients; + gboolean video_full_range_flag; + guint32 tcod; +} jpegxs_private_data; + +GstBuffer *gst_base_ts_mux_prepare_jpegxs (GstBuffer * buf, GstBaseTsMuxPad * pad, + GstBaseTsMux * mux); + +void gst_base_ts_mux_free_jpegxs (gpointer prepare_data); + +#endif /* __BASETSMUX_JPEGXS_H__ */ diff --git a/subprojects/gst-plugins-bad/gst/mpegtsmux/gstmpegtsmux.c b/subprojects/gst-plugins-bad/gst/mpegtsmux/gstmpegtsmux.c index 744f34213b..199565e6ec 100644 --- a/subprojects/gst-plugins-bad/gst/mpegtsmux/gstmpegtsmux.c +++ b/subprojects/gst-plugins-bad/gst/mpegtsmux/gstmpegtsmux.c @@ -126,7 +126,8 @@ static GstStaticPadTemplate gst_mpeg_ts_mux_sink_factory = "channels = (int) [1, 255];" "subpicture/x-dvb; application/x-teletext; meta/x-klv, parsed=true;" "meta/x-id3, parsed=true;" - "image/x-jpc, alignment = (string) frame, profile = (int)[0, 49151];")); + "image/x-jpc, alignment = (string) frame, profile = (int)[0, 49151];" + "image/x-jxsc, alignment = (string) frame, sampling = { YCbCr-4:2:2, YCbCr-4:4:4 };")); static GstStaticPadTemplate gst_mpeg_ts_mux_src_factory = GST_STATIC_PAD_TEMPLATE ("src", diff --git a/subprojects/gst-plugins-bad/gst/mpegtsmux/meson.build b/subprojects/gst-plugins-bad/gst/mpegtsmux/meson.build index b80fc2f90c..e0d873d9a3 100644 --- a/subprojects/gst-plugins-bad/gst/mpegtsmux/meson.build +++ b/subprojects/gst-plugins-bad/gst/mpegtsmux/meson.build @@ -5,6 +5,7 @@ tsmux_sources = [ 'gstbasetsmuxopus.c', 'gstbasetsmuxttxt.c', 'gstbasetsmuxjpeg2000.c', + 'gstbasetsmuxjpegxs.c', 'gstmpegtsmux.c', 'gstatscmux.c', 'tsmux/tsmux.c', diff --git a/subprojects/gst-plugins-bad/gst/mpegtsmux/tsmux/tsmuxstream.c b/subprojects/gst-plugins-bad/gst/mpegtsmux/tsmux/tsmuxstream.c index e4350c1bd6..a96ba24909 100644 --- a/subprojects/gst-plugins-bad/gst/mpegtsmux/tsmux/tsmuxstream.c +++ b/subprojects/gst-plugins-bad/gst/mpegtsmux/tsmux/tsmuxstream.c @@ -153,6 +153,11 @@ tsmux_stream_new (guint16 pid, guint stream_type, guint stream_number) stream->pi.flags |= TSMUX_PACKET_FLAG_PES_FULL_HEADER; stream->gst_stream_type = GST_STREAM_TYPE_VIDEO; break; + case TSMUX_ST_VIDEO_JPEG_XS: + stream->id = 0xBD; /* Private Stream 1 */ + stream->pi.flags |= TSMUX_PACKET_FLAG_PES_FULL_HEADER; + stream->gst_stream_type = GST_STREAM_TYPE_VIDEO; + break; case TSMUX_ST_AUDIO_AAC: case TSMUX_ST_AUDIO_MPEG1: case TSMUX_ST_AUDIO_MPEG2: @@ -296,6 +301,8 @@ tsmux_stream_free (TsMuxStream * stream) } g_list_free (stream->buffers); + if (stream->pmt_descriptor) + gst_mpegts_descriptor_free (stream->pmt_descriptor); g_free (stream); } @@ -902,6 +909,12 @@ tsmux_stream_default_get_es_descrs (TsMuxStream * stream, g_ptr_array_add (pmt_stream->descriptors, descriptor); } break; + case TSMUX_ST_VIDEO_JPEG_XS: + { + g_ptr_array_add (pmt_stream->descriptors, + gst_mpegts_descriptor_copy (stream->pmt_descriptor)); + } + break; case TSMUX_ST_PS_AUDIO_AC3: { /* This is only called for DVB, ATSC ignores this case in favour of its diff --git a/subprojects/gst-plugins-bad/gst/mpegtsmux/tsmux/tsmuxstream.h b/subprojects/gst-plugins-bad/gst/mpegtsmux/tsmux/tsmuxstream.h index 91353ebafc..b0449a7f15 100644 --- a/subprojects/gst-plugins-bad/gst/mpegtsmux/tsmux/tsmuxstream.h +++ b/subprojects/gst-plugins-bad/gst/mpegtsmux/tsmux/tsmuxstream.h @@ -121,7 +121,8 @@ enum TsMuxStreamType { TSMUX_ST_PES_METADATA = 0x15, TSMUX_ST_VIDEO_H264 = 0x1b, TSMUX_ST_VIDEO_HEVC = 0x24, - TSMUX_ST_VIDEO_JP2K = 0x21, + TSMUX_ST_VIDEO_JP2K = 0x21, + TSMUX_ST_VIDEO_JPEG_XS = 0x32, /* private stream types */ TSMUX_ST_PS_AUDIO_AC3 = 0x81, @@ -227,6 +228,9 @@ struct TsMuxStream { guint16 profile_and_level; gboolean interlace_mode; guint8 color_spec; + + /* PMT descriptor for the stream */ + GstMpegtsDescriptor *pmt_descriptor; }; /* stream management */