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 */