tsmux/tsdemux: Add support for JPEG2000
authorAaron Boxer <boxerab@gmail.com>
Wed, 19 Jul 2017 14:14:21 +0000 (10:14 -0400)
committerSebastian Dröge <sebastian@centricular.com>
Fri, 21 Jul 2017 06:33:31 +0000 (09:33 +0300)
Based on patches by Milos Seleceni.

https://bugzilla.gnome.org/show_bug.cgi?id=753323

gst/mpegtsdemux/tsdemux.c
gst/mpegtsdemux/tsdemux.h
gst/mpegtsmux/Makefile.am
gst/mpegtsmux/mpegtsmux.c
gst/mpegtsmux/mpegtsmux_jpeg2000.c [new file with mode: 0644]
gst/mpegtsmux/mpegtsmux_jpeg2000.h [new file with mode: 0644]
gst/mpegtsmux/tsmux/tsmux.c
gst/mpegtsmux/tsmux/tsmuxstream.c
gst/mpegtsmux/tsmux/tsmuxstream.h
gst/videoparsers/gstjpeg2000parse.c
gst/videoparsers/gstjpeg2000parse.h

index 20357a8..a570a1c 100644 (file)
@@ -48,6 +48,7 @@
 #include "pesparse.h"
 #include <gst/codecparsers/gsth264parser.h>
 #include <gst/codecparsers/gstmpegvideoparser.h>
+#include <gst/video/video-color.h>
 
 #include <math.h>
 
@@ -112,6 +113,7 @@ typedef struct
 typedef struct _TSDemuxStream TSDemuxStream;
 
 typedef struct _TSDemuxH264ParsingInfos TSDemuxH264ParsingInfos;
+typedef struct _TSDemuxJP2KParsingInfos TSDemuxJP2KParsingInfos;
 
 /* Returns TRUE if a keyframe was found */
 typedef gboolean (*GstTsDemuxKeyFrameScanFunction) (TSDemuxStream * stream,
@@ -133,6 +135,11 @@ struct _TSDemuxH264ParsingInfos
   SimpleBuffer framedata;
 };
 
+struct _TSDemuxJP2KParsingInfos
+{
+  /* J2K parsing data */
+  gboolean interlace;
+};
 struct _TSDemuxStream
 {
   MpegTSBaseStream stream;
@@ -200,6 +207,7 @@ struct _TSDemuxStream
 
   GstTsDemuxKeyFrameScanFunction scan_function;
   TSDemuxH264ParsingInfos h264infos;
+  TSDemuxJP2KParsingInfos jp2kInfos;
 };
 
 #define VIDEO_CAPS \
@@ -215,8 +223,9 @@ struct _TSDemuxStream
     "video/x-cavs;" \
     "video/x-wmv," \
       "wmvversion = (int) 3, " \
-      "format = (string) WVC1" \
-  )
+      "format = (string) WVC1;" \
+      "image/x-jpc;" \
+)
 
 #define AUDIO_CAPS \
   GST_STATIC_CAPS ( \
@@ -1449,6 +1458,83 @@ create_pad_for_stream (MpegTSBase * base, MpegTSBaseStream * bstream,
           "stream-format", G_TYPE_STRING, "byte-stream",
           "alignment", G_TYPE_STRING, "nal", NULL);
       break;
+    case GST_MPEGTS_STREAM_TYPE_VIDEO_JP2K:
+      is_video = TRUE;
+      desc =
+          mpegts_get_descriptor_from_stream (bstream, GST_MTS_DESC_J2K_VIDEO);
+      if (desc == NULL) {
+        caps = gst_caps_new_empty_simple ("image/x-jpc");
+        break;
+      } else {
+        GstByteReader br;
+        guint16 DEN_frame_rate = 0;
+        guint16 NUM_frame_rate = 0;
+        guint8 color_specification = 0;
+        guint8 remaining_8b = 0;
+        gboolean interlaced_video = 0;
+        const gchar *interlace_mode = NULL;
+        const gchar *colorspace = NULL;
+        const gchar *colorimetry_mode = NULL;
+        guint16 profile_and_level G_GNUC_UNUSED;
+        guint32 horizontal_size G_GNUC_UNUSED;
+        guint32 vertical_size G_GNUC_UNUSED;
+        guint32 max_bit_rate G_GNUC_UNUSED;
+        guint32 max_buffer_size G_GNUC_UNUSED;
+        const guint desc_min_length = 24;
+
+        if (desc->length < desc_min_length) {
+          GST_ERROR
+              ("GST_MPEGTS_STREAM_TYPE_VIDEO_JP2K: descriptor length %d too short",
+              desc->length);
+          return NULL;
+        }
+
+        /* Skip the descriptor tag and length */
+        gst_byte_reader_init (&br, desc->data + 2, desc->length);
+
+        profile_and_level = gst_byte_reader_get_uint16_be_unchecked (&br);
+        horizontal_size = gst_byte_reader_get_uint32_be_unchecked (&br);
+        vertical_size = gst_byte_reader_get_uint32_be_unchecked (&br);
+        max_bit_rate = gst_byte_reader_get_uint32_be_unchecked (&br);
+        max_buffer_size = gst_byte_reader_get_uint32_be_unchecked (&br);
+        DEN_frame_rate = gst_byte_reader_get_uint16_be_unchecked (&br);
+        NUM_frame_rate = gst_byte_reader_get_uint16_be_unchecked (&br);
+        color_specification = gst_byte_reader_get_uint8_unchecked (&br);
+        remaining_8b = gst_byte_reader_get_uint8_unchecked (&br);
+        interlaced_video = remaining_8b & 0x40;
+        /* we don't support demuxing interlaced at the moment */
+        if (interlaced_video) {
+          GST_ERROR
+              ("GST_MPEGTS_STREAM_TYPE_VIDEO_JP2K: interlaced video not supported");
+          return NULL;
+        } else {
+          interlace_mode = "progressive";
+          stream->jp2kInfos.interlace = FALSE;
+        }
+        switch (color_specification) {
+          case GST_MPEGTSDEMUX_JPEG2000_COLORSPEC_SRGB:
+            colorspace = "sRGB";
+            colorimetry_mode = GST_VIDEO_COLORIMETRY_SRGB;
+            break;
+          case GST_MPEGTSDEMUX_JPEG2000_COLORSPEC_REC601:
+            colorspace = "sYUV";
+            colorimetry_mode = GST_VIDEO_COLORIMETRY_BT601;
+            break;
+          case GST_MPEGTSDEMUX_JPEG2000_COLORSPEC_REC709:
+          case GST_MPEGTSDEMUX_JPEG2000_COLORSPEC_CIELUV:
+            colorspace = "sYUV";
+            colorimetry_mode = GST_VIDEO_COLORIMETRY_BT709;
+            break;
+          default:
+            break;
+        }
+        caps = gst_caps_new_simple ("image/x-jpc",
+            "framerate", GST_TYPE_FRACTION, NUM_frame_rate, DEN_frame_rate,
+            "interlace-mode", G_TYPE_STRING, interlace_mode,
+            "colorimetry", G_TYPE_STRING, colorimetry_mode,
+            "colorspace", G_TYPE_STRING, colorspace, NULL);
+      }
+      break;
     case ST_VIDEO_DIRAC:
       if (bstream->registration_id == 0x64726163) {
         GST_LOG ("dirac");
@@ -2553,11 +2639,150 @@ error:
     g_free (stream->data);
     stream->data = NULL;
     stream->current_size = 0;
-    gst_buffer_list_unref (buffer_list);
+    if (buffer_list)
+      gst_buffer_list_unref (buffer_list);
     return NULL;
   }
 }
 
+/* interlaced mode is disabled at the moment */
+/*#define TSDEMUX_JP2K_SUPPORT_INTERLACE */
+static GstBuffer *
+parse_jp2k_access_unit (TSDemuxStream * stream)
+{
+  GstByteReader reader;
+  /* header tag */
+  guint32 header_tag;
+  /* Framerate box */
+  guint16 den G_GNUC_UNUSED;
+  guint16 num G_GNUC_UNUSED;
+  /* Maximum bitrate box */
+  guint32 MaxBr G_GNUC_UNUSED;
+  guint32 AUF[2] = { 0, 0 };
+#ifdef TSDEMUX_JP2K_SUPPORT_INTERLACE
+  /* Field Coding Box */
+  guint8 Fic G_GNUC_UNUSED = 1;
+  guint8 Fio G_GNUC_UNUSED = 0;
+  /* header size equals 38 for non-interlaced, and 48 for interlaced */
+  guint header_size = stream->jp2kInfos.interlace ? 48 : 38;
+#else
+  /* header size equals 38 for non-interlaced, and 48 for interlaced */
+  guint header_size = 38;
+#endif
+  /* Time Code box */
+  guint32 HHMMSSFF G_GNUC_UNUSED;
+  /* Broadcast color box */
+  guint8 CollC G_GNUC_UNUSED;
+  guint8 b G_GNUC_UNUSED;
+
+  guint8 *packet_data = NULL;
+  guint packet_size;
+  guint remaining = 0;
+
+  if (stream->current_size < header_size) {
+    GST_ERROR_OBJECT (stream->pad, "Not enough data for header");
+    goto error;
+  }
+
+  gst_byte_reader_init (&reader, stream->data, stream->current_size);
+
+  /* Elementary stream header box 'elsm' == 0x656c736d */
+  header_tag = gst_byte_reader_get_uint32_be_unchecked (&reader);
+  if (header_tag != 0x656c736d) {
+    GST_ERROR_OBJECT (stream->pad, "Expected ELSM box but found box %x instead",
+        header_tag);
+    goto error;
+  }
+  /* Frame rate box 'frat' == 0x66726174 */
+  header_tag = gst_byte_reader_get_uint32_be_unchecked (&reader);
+  if (header_tag != 0x66726174) {
+    GST_ERROR_OBJECT (stream->pad,
+        "Expected frame rate box, but found box %x instead", header_tag);
+    goto error;
+
+  }
+  den = gst_byte_reader_get_uint16_be_unchecked (&reader);
+  num = gst_byte_reader_get_uint16_be_unchecked (&reader);
+  /* Maximum bit rate box 'brat' == 0x62726174 */
+  header_tag = gst_byte_reader_get_uint32_be_unchecked (&reader);
+  if (header_tag != 0x62726174) {
+    GST_ERROR_OBJECT (stream->pad, "Expected brat box but read box %x instead",
+        header_tag);
+    goto error;
+
+  }
+  MaxBr = gst_byte_reader_get_uint32_be_unchecked (&reader);
+  AUF[0] = gst_byte_reader_get_uint32_be_unchecked (&reader);
+  if (stream->jp2kInfos.interlace) {
+#ifdef TSDEMUX_JP2K_SUPPORT_INTERLACE
+    AUF[1] = gst_byte_reader_get_uint32_be_unchecked (&reader);
+    /*  Field Coding Box 'fiel' == 0x6669656c */
+    header_tag = gst_byte_reader_get_uint32_be_unchecked (&reader);
+    if (header_tag != 0x6669656c) {
+      GST_ERROR_OBJECT (stream->pad,
+          "Expected Field Coding box but found box %x instead", header_tag);
+      goto error;
+    }
+    Fic = gst_byte_reader_get_uint8_unchecked (&reader);
+    Fio = gst_byte_reader_get_uint8_unchecked (&reader);
+#else
+    GST_ERROR_OBJECT (stream->pad, "interlaced mode not supported");
+    goto error;
+#endif
+  }
+  /* Time Code Box 'tcod' == 0x74636f64 */
+  header_tag = gst_byte_reader_get_uint32_be_unchecked (&reader);
+  if (header_tag != 0x74636f64) {
+    GST_ERROR_OBJECT (stream->pad,
+        "Expected Time code box but found %d box instead", header_tag);
+    goto error;
+  }
+  HHMMSSFF = gst_byte_reader_get_uint32_be_unchecked (&reader);
+  /* Broadcast Color Box 'bcol' == 0x6263686c */
+  header_tag = gst_byte_reader_get_uint32_be_unchecked (&reader);
+  if (header_tag != 0x62636f6c) {
+    GST_ERROR_OBJECT (stream->pad,
+        "Expected Broadcast color box but found %x box instead", header_tag);
+    goto error;
+  }
+  CollC = gst_byte_reader_get_uint8_unchecked (&reader);
+  b = gst_byte_reader_get_uint8_unchecked (&reader);
+  remaining = gst_byte_reader_get_remaining (&reader);
+  packet_size = remaining;
+
+  GST_DEBUG_OBJECT (stream->pad,
+      "Size of first codestream in TS stream: %d; bytes remaining: %d", AUF[0],
+      remaining);
+
+  if (!gst_byte_reader_dup_data (&reader, packet_size, &packet_data)) {
+    GST_ERROR ("Required size %d > %d than remaining size in buffer", AUF[0],
+        packet_size);
+    goto error;
+  }
+#ifdef TSDEMUX_JP2K_SUPPORT_INTERLACE
+  if (stream->jp2kInfos.interlace) {
+    remaining = gst_byte_reader_get_remaining (&reader);
+    if (!gst_byte_reader_dup_data (&reader, AUF[1], &packet_data)) {
+      GST_ERROR ("Required size %d > %d than remaining size in buffer", AUF[1],
+          remaining);
+      goto error;
+    }
+
+  }
+#endif
+  g_free (stream->data);
+  stream->data = NULL;
+  stream->current_size = 0;
+  return gst_buffer_new_wrapped (packet_data, packet_size);
+
+error:
+  GST_ERROR ("Failed to parse JP2K access unit");
+  g_free (stream->data);
+  stream->data = NULL;
+  stream->current_size = 0;
+  return NULL;
+}
+
 static GstFlowReturn
 gst_ts_demux_push_pending_data (GstTSDemux * demux, TSDemuxStream * stream,
     MpegTSBaseProgram * target_program)
@@ -2615,6 +2840,8 @@ gst_ts_demux_push_pending_data (GstTSDemux * demux, TSDemuxStream * stream,
           gst_buffer_list_unref (buffer_list);
           buffer_list = NULL;
         }
+      } else if (bs->stream_type == GST_MPEGTS_STREAM_TYPE_VIDEO_JP2K) {
+        buffer = parse_jp2k_access_unit (stream);
       } else {
         buffer = gst_buffer_new_wrapped (stream->data, stream->current_size);
       }
@@ -2649,6 +2876,8 @@ gst_ts_demux_push_pending_data (GstTSDemux * demux, TSDemuxStream * stream,
         gst_buffer_list_unref (buffer_list);
         buffer_list = NULL;
       }
+    } else if (bs->stream_type == GST_MPEGTS_STREAM_TYPE_VIDEO_JP2K) {
+      buffer = parse_jp2k_access_unit (stream);
     } else {
       buffer = gst_buffer_new_wrapped (stream->data, stream->current_size);
     }
index 32cbde2..394aefd 100644 (file)
 #include "mpegtsbase.h"
 #include "mpegtspacketizer.h"
 
+/* color specifications for JPEG 2000 stream over MPEG TS */
+typedef enum
+{
+  GST_MPEGTSDEMUX_JPEG2000_COLORSPEC_UNKNOWN,
+  GST_MPEGTSDEMUX_JPEG2000_COLORSPEC_SRGB,
+  GST_MPEGTSDEMUX_JPEG2000_COLORSPEC_REC601,
+  GST_MPEGTSDEMUX_JPEG2000_COLORSPEC_REC709,
+  GST_MPEGTSDEMUX_JPEG2000_COLORSPEC_CIELUV,
+  GST_MPEGTSDEMUX_JPEG2000_COLORSPEC_CIEXYZ,
+  GST_MPEGTSDEMUX_JPEG2000_COLORSPEC_REC2020,
+  GST_MPEGTSDEMUX_JPEG2000_COLORSPEC_SMPTE2084
+} GstMpegTsDemuxJpeg2000ColorSpec;
+
+
 G_BEGIN_DECLS
 #define GST_TYPE_TS_DEMUX \
   (gst_ts_demux_get_type())
index 18d58d1..27c31c2 100644 (file)
@@ -6,7 +6,8 @@ libgstmpegtsmux_la_SOURCES = \
        mpegtsmux.c \
        mpegtsmux_aac.c \
        mpegtsmux_ttxt.c \
-       mpegtsmux_opus.c
+       mpegtsmux_opus.c \
+       mpegtsmux_jpeg2000.c
 
 libgstmpegtsmux_la_CFLAGS = $(GST_PLUGINS_BAD_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) \
                            $(GST_BASE_CFLAGS) $(GST_CFLAGS)
@@ -21,4 +22,5 @@ noinst_HEADERS = \
        mpegtsmux.h \
        mpegtsmux_aac.h \
        mpegtsmux_ttxt.h \
-       mpegtsmux_opus.h
+       mpegtsmux_opus.h \
+       mpegtsmux_jpeg2000.h
index 67c2b72..98e97c3 100644 (file)
@@ -98,6 +98,9 @@
 #include "mpegtsmux_aac.h"
 #include "mpegtsmux_ttxt.h"
 #include "mpegtsmux_opus.h"
+#include "mpegtsmux_jpeg2000.h"
+#include <gst/videoparsers/gstjpeg2000parse.h>
+#include <gst/video/video-color.h>
 
 GST_DEBUG_CATEGORY (mpegtsmux_debug);
 #define GST_CAT_DEFAULT mpegtsmux_debug
@@ -125,6 +128,7 @@ static GstStaticPadTemplate mpegtsmux_sink_factory =
         "mpegversion = (int) { 1, 2, 4 }, "
         "systemstream = (boolean) false; "
         "video/x-dirac;"
+        "image/x-jpc;"
         "video/x-h264,stream-format=(string)byte-stream,"
         "alignment=(string){au, nal}; "
         "video/x-h265,stream-format=(string)byte-stream,"
@@ -149,7 +153,8 @@ static GstStaticPadTemplate mpegtsmux_sink_factory =
         "audio/x-opus, "
         "channels = (int) [1, 8], "
         "channel-mapping-family = (int) {0, 1};"
-        "subpicture/x-dvb; application/x-teletext; meta/x-klv, parsed=true"));
+        "subpicture/x-dvb; application/x-teletext; meta/x-klv, parsed=true;"
+        "image/x-jpc, profile = (int)[0, 49151];"));
 
 static GstStaticPadTemplate mpegtsmux_src_factory =
 GST_STATIC_PAD_TEMPLATE ("src",
@@ -588,6 +593,11 @@ mpegtsmux_create_stream (MpegTsMux * mux, MpegTsPadData * ts_data)
   const GValue *value = NULL;
   GstBuffer *codec_data = NULL;
   guint8 opus_channel_config_code = 0;
+  guint16 profile = 0;
+  guint8 main_level = 0;
+  guint32 max_rate = 0;
+  guint8 color_spec = 0;
+  j2k_private_data *private_data = NULL;
 
   pad = ts_data->collect.pad;
   caps = gst_pad_get_current_caps (pad);
@@ -739,6 +749,94 @@ mpegtsmux_create_stream (MpegTsMux * mux, MpegTsPadData * ts_data)
     ts_data->prepare_func = mpegtsmux_prepare_opus;
   } else if (strcmp (mt, "meta/x-klv") == 0) {
     st = TSMUX_ST_PS_KLV;
+  } else if (strcmp (mt, "image/x-jpc") == 0) {
+    /*
+     * See this document for more details on standard:
+     *
+     * https://www.itu.int/rec/T-REC-H.222.0-201206-S/en
+     *  Annex S describes J2K details
+     *  Page 104 of this document describes J2k video descriptor
+     */
+
+    const GValue *vProfile = gst_structure_get_value (s, "profile");
+    const GValue *vMainlevel = gst_structure_get_value (s, "main-level");
+    const GValue *vFramerate = gst_structure_get_value (s, "framerate");
+    const GValue *vColorimetry = gst_structure_get_value (s, "colorimetry");
+    private_data = g_new0 (j2k_private_data, 1);
+    profile = g_value_get_uint (vProfile);
+    if (profile != GST_JPEG2000_PARSE_PROFILE_BC_SINGLE) {
+      /* for now, we will relax the condition that the profile must equal GST_JPEG2000_PARSE_PROFILE_BC_SINGLE */
+      /*GST_ERROR_OBJECT (pad, "Invalid JPEG 2000 profile %d", profile);
+         goto not_negotiated; */
+    }
+    /* for now, we will relax the condition that the main level must be present */
+    if (vMainlevel) {
+      main_level = g_value_get_uint (vMainlevel);
+      if (main_level > 11) {
+        GST_ERROR_OBJECT (pad, "Invalid main level %d", main_level);
+        goto not_negotiated;
+      }
+      if (main_level >= 6) {
+        max_rate = 2 ^ (main_level - 6) * 1600 * 1000000;
+      } else {
+        switch (main_level) {
+          case 0:
+          case 1:
+          case 2:
+          case 3:
+            max_rate = 200 * 1000000;
+            break;
+          case 4:
+            max_rate = 400 * 1000000;
+            break;
+          case 5:
+            max_rate = 800 * 1000000;
+            break;
+          default:
+            break;
+        }
+      }
+    } else {
+      /*GST_ERROR_OBJECT (pad, "Missing main level");
+         goto not_negotiated; */
+    }
+    /* We always mux video in J2K-over-MPEG-TS non-interlaced mode */
+    private_data->interlace = FALSE;
+    private_data->den = 0;
+    private_data->num = 0;
+    private_data->max_bitrate = max_rate;
+    private_data->color_spec = 1;
+    /* these two fields are not used, since we always mux as non-interlaced */
+    private_data->Fic = 1;
+    private_data->Fio = 0;
+
+    /* Get Framerate */
+    if (vFramerate != NULL) {
+      /* Data for ELSM header */
+      private_data->num = gst_value_get_fraction_numerator (vFramerate);
+      private_data->den = gst_value_get_fraction_denominator (vFramerate);
+    }
+    /* Get Colorimetry */
+    if (vColorimetry) {
+      const char *colorimetry = g_value_get_string (vColorimetry);
+      color_spec = GST_MPEGTS_JPEG2000_COLORSPEC_SRGB;  /* RGB as default */
+      if (g_str_equal (colorimetry, GST_VIDEO_COLORIMETRY_BT601)) {
+        color_spec = GST_MPEGTS_JPEG2000_COLORSPEC_REC601;
+      } else {
+        if (g_str_equal (colorimetry, GST_VIDEO_COLORIMETRY_BT709)
+            || g_str_equal (colorimetry, GST_VIDEO_COLORIMETRY_SMPTE240M)) {
+          color_spec = GST_MPEGTS_JPEG2000_COLORSPEC_REC709;
+        }
+      }
+      private_data->color_spec = color_spec;
+    } else {
+      GST_ERROR_OBJECT (pad, "Colorimetry not present in caps");
+      goto not_negotiated;
+    }
+    st = TSMUX_ST_VIDEO_JP2K;
+    ts_data->prepare_func = mpegtsmux_prepare_jpeg2000;
+    ts_data->prepare_data = private_data;
+    ts_data->free_func = mpegtsmux_free_jpeg2000;
   }
 
   if (st != TSMUX_ST_RESERVED) {
@@ -749,10 +847,29 @@ mpegtsmux_create_stream (MpegTsMux * mux, MpegTsPadData * ts_data)
   }
 
   if (ts_data->stream != NULL) {
+    const char *interlace_mode = gst_structure_get_string (s, "interlace-mode");
     gst_structure_get_int (s, "rate", &ts_data->stream->audio_sampling);
     gst_structure_get_int (s, "channels", &ts_data->stream->audio_channels);
     gst_structure_get_int (s, "bitrate", &ts_data->stream->audio_bitrate);
 
+    /* frame rate */
+    gst_structure_get_fraction (s, "framerate", &ts_data->stream->num,
+        &ts_data->stream->den);
+
+    /* Interlace mode */
+    ts_data->stream->interlace_mode = FALSE;
+    if (interlace_mode) {
+      ts_data->stream->interlace_mode =
+          g_str_equal (interlace_mode, "interleaved");
+    }
+    /* Width and Height */
+    gst_structure_get_int (s, "width", &ts_data->stream->horizontal_size);
+    gst_structure_get_int (s, "height", &ts_data->stream->vertical_size);
+
+    ts_data->stream->color_spec = color_spec;
+    ts_data->stream->max_bitrate = max_rate;
+    ts_data->stream->profile_and_level = profile | main_level;
+
     ts_data->stream->opus_channel_config_code = opus_channel_config_code;
 
     tsmux_stream_set_buffer_release_func (ts_data->stream, release_buffer_cb);
@@ -784,13 +901,12 @@ mpegtsmux_create_stream (MpegTsMux * mux, MpegTsPadData * ts_data)
   }
   GST_OBJECT_UNLOCK (mux);
 #endif
-
   gst_caps_unref (caps);
   return ret;
-
   /* ERRORS */
 not_negotiated:
   {
+    g_free (private_data);
     GST_DEBUG_OBJECT (pad, "Sink pad caps were not set before pushing");
     if (caps)
       gst_caps_unref (caps);
diff --git a/gst/mpegtsmux/mpegtsmux_jpeg2000.c b/gst/mpegtsmux/mpegtsmux_jpeg2000.c
new file mode 100644 (file)
index 0000000..2e8db8c
--- /dev/null
@@ -0,0 +1,134 @@
+/* GStreamer JPEG 2000 Parser
+ * 
+ * Copyright (C) <2016> Milos Seleceni
+ *  @author Milos Seleceni <milos.seleceni@comprimato.com>
+ *
+ * Copyright (C) <2016-2017> Grok Image Compression Inc.
+ *  @author Aaron Boxer <boxerab@gmail.com>
+ *
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include "mpegtsmux_jpeg2000.h"
+#include <string.h>
+#include <gst/audio/audio.h>
+#include <gst/base/gstbytewriter.h>
+#include <gst/gst.h>
+
+#define GST_CAT_DEFAULT mpegtsmux_debug
+
+GstBuffer *
+mpegtsmux_prepare_jpeg2000 (GstBuffer * buf, MpegTsPadData * data,
+    MpegTsMux * mux)
+{
+  j2k_private_data *private_data = data->prepare_data;
+  GstByteWriter wr;
+  GstBuffer *out_buf = NULL;
+  guint8 *elsm_header = NULL;
+  const guint header_size = private_data->interlace ? 48 : 38;
+  GstClockTime seconds = buf->pts / GST_SECOND;
+  GstClockTime minutes = seconds / 60;
+  GstClockTime hours = minutes / 60;
+
+  /* interlaced not supported */
+  if (private_data->interlace) {
+    GST_ERROR_OBJECT (mux, "Interlaced not supported");
+    return NULL;
+  }
+
+  seconds = seconds % 60;
+  minutes = minutes % 60;
+  hours = hours % 24;
+
+  /* ??? Hack for missing frame number index in buffer offset */
+  /* guint8 frame_number = private_data->frame_number % 60; */
+  gst_byte_writer_init_with_size (&wr, header_size, FALSE);
+
+  /* Elementary stream header box 'elsm' == 0x656c736d */
+  gst_byte_writer_put_uint32_be (&wr, 0x656c736d);
+  /* Framerate box 'frat' == 0x66726174 */
+  gst_byte_writer_put_uint32_be (&wr, 0x66726174);
+  /* put framerate denominator */
+  gst_byte_writer_put_uint16_be (&wr, private_data->den);
+  /* put framerate numerator */
+  gst_byte_writer_put_uint16_be (&wr, private_data->num);
+  /* Maximum bitrate box 'brat' == 0x62726174 */
+  gst_byte_writer_put_uint32_be (&wr, 0x62726174);
+  /* put Maximum bitrate */
+  gst_byte_writer_put_uint32_be (&wr, private_data->max_bitrate);
+  /* put size of first codestream */
+  /* private_data->AUF[0] */
+  gst_byte_writer_put_uint32_be (&wr, gst_buffer_get_size (buf));
+
+  /* ToDo: the if block below is never called, because we do not support muxing J2K-over-mpeg-TS interlaced data
+   * If we ever do, then the code below will need to tested and perhaps modified
+   */
+  if (private_data->interlace) {
+    /* put size of second codestream */
+    gst_byte_writer_put_uint32_be (&wr, gst_buffer_get_size (buf));
+    /* Time Code Box 'fiel' == 0x6669656c */
+    gst_byte_writer_put_uint32_be (&wr, 0x6669656c);
+    /* put Fic */
+    gst_byte_writer_put_uint8 (&wr, private_data->Fic);
+    /* put Fio */
+    gst_byte_writer_put_uint8 (&wr, private_data->Fio);
+  }
+
+  /* Time Code Box 'tcod' == 0x74636f64 */
+  gst_byte_writer_put_uint32_be (&wr, 0x74636f64);
+
+  /* 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);
+  /* ??? Hack for missing frame number index in buffer offset */
+  /* private_data->frame_number++; */
+
+  /* Broadcast Color Box 'bcol' == 0x62636f6c */
+  gst_byte_writer_put_uint32_be (&wr, 0x62636f6c);
+  /* put color spec */
+  gst_byte_writer_put_uint8 (&wr, private_data->color_spec);
+  /* put reserved 8-bit */
+  gst_byte_writer_put_uint8 (&wr, 0xff);
+  /* Allocate ELSM header size only; gst_buffer_copy_into will add gst_buffer_get_size (buf) bytes to out_buf */
+  out_buf = gst_buffer_new_and_alloc (header_size);
+
+  /* Copy ELSM header */
+  elsm_header = gst_byte_writer_reset_and_get_data (&wr);
+  gst_buffer_fill (out_buf, 0, elsm_header, header_size);
+  g_free (elsm_header);
+  /* 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 J2K PES of size %d",
+      (int) gst_buffer_get_size (out_buf));
+
+  return out_buf;
+}
+
+void
+mpegtsmux_free_jpeg2000 (gpointer prepare_data)
+{
+  /*  Free prepare data memory object */
+  g_free (prepare_data);
+}
diff --git a/gst/mpegtsmux/mpegtsmux_jpeg2000.h b/gst/mpegtsmux/mpegtsmux_jpeg2000.h
new file mode 100644 (file)
index 0000000..37ed433
--- /dev/null
@@ -0,0 +1,63 @@
+/* GStreamer JPEG 2000 Parser
+ * 
+ * Copyright (C) <2016> Milos Seleceni
+ *  @author Milos Seleceni <milos.seleceni@comprimato.com>
+ *
+ * Copyright (C) <2016-2017> Grok Image Compression Inc.
+ *  @author Aaron Boxer <boxerab@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef __MPEGTSMUX_JPEG2000_H__
+#define __MPEGTSMUX_JPEG2000_H__
+
+#include "mpegtsmux.h"
+
+/* color specifications for JPEG 2000 stream over MPEG TS */
+typedef enum
+{
+  GST_MPEGTS_JPEG2000_COLORSPEC_UNKNOWN,
+  GST_MPEGTS_JPEG2000_COLORSPEC_SRGB,
+  GST_MPEGTS_JPEG2000_COLORSPEC_REC601,
+  GST_MPEGTS_JPEG2000_COLORSPEC_REC709,
+  GST_MPEGTS_JPEG2000_COLORSPEC_CIELUV,
+  GST_MPEGTS_JPEG2000_COLORSPEC_CIEXYZ,
+  GST_MPEGTS_JPEG2000_COLORSPEC_REC2020,
+  GST_MPEGTS_JPEG2000_COLORSPEC_SMPTE2084
+} GstMpegTsJpeg2000ColorSpec;
+
+
+typedef struct j2k_private_data
+{
+  gboolean interlace;
+  guint16 den;
+  guint16 num;
+  /* Maximum bitrate box */
+  guint32 max_bitrate;
+  /* Field Coding Box */
+  guint8 Fic;
+  guint8 Fio;
+  /* Broadcast color box */
+  guint8 color_spec;
+} j2k_private_data;
+
+GstBuffer *mpegtsmux_prepare_jpeg2000 (GstBuffer * buf, MpegTsPadData * data,
+    MpegTsMux * mux);
+
+void mpegtsmux_free_jpeg2000 (gpointer prepare_data);
+
+#endif /* __MPEGTSMUX_JPEG2000_H__ */
index ee28704..24ae7e2 100644 (file)
@@ -1104,6 +1104,8 @@ tsmux_write_stream_packet (TsMux * mux, TsMuxStream * stream)
 
   gst_buffer_unmap (buf, &map);
 
+  GST_DEBUG_OBJECT (mux, "Writing PES of size %d",
+      (int) gst_buffer_get_size (buf));
   res = tsmux_packet_out (mux, buf, cur_pcr);
 
   /* Reset all dynamic flags */
index f719d8f..4cbd217 100644 (file)
@@ -84,6 +84,7 @@
 #include <string.h>
 
 #include <gst/mpegts/mpegts.h>
+#include <gst/base/gstbytewriter.h>
 
 #include "tsmuxcommon.h"
 #include "tsmuxstream.h"
@@ -144,6 +145,11 @@ tsmux_stream_new (guint16 pid, TsMuxStreamType stream_type)
       stream->pi.flags |= TSMUX_PACKET_FLAG_PES_FULL_HEADER;
       stream->is_video_stream = TRUE;
       break;
+    case TSMUX_ST_VIDEO_JP2K:
+      stream->id = 0xBD;
+      stream->pi.flags |= TSMUX_PACKET_FLAG_PES_FULL_HEADER;
+      stream->is_video_stream = TRUE;
+      break;
     case TSMUX_ST_AUDIO_AAC:
     case TSMUX_ST_AUDIO_MPEG1:
     case TSMUX_ST_AUDIO_MPEG2:
@@ -768,6 +774,75 @@ tsmux_stream_get_es_descrs (TsMuxStream * stream,
       descriptor = gst_mpegts_descriptor_from_registration ("drac", NULL, 0);
       g_ptr_array_add (pmt_stream->descriptors, descriptor);
       break;
+    case TSMUX_ST_VIDEO_JP2K:
+    {
+      /* J2K video descriptor
+       * descriptor_tag             8 uimsbf
+       * descriptor_length          8 uimsbf
+       * profile_and_level         16 uimsbf
+       * horizontal_size           32 uimsbf
+       * vertical_size             32 uimsbf
+       * max_bit_rate              32 uimsbf
+       * max_buffer_size           32 uimsbf
+       * DEN_frame_rate            16 uimsbf
+       * NUM_frame_rate            16 uimsbf
+       * color_specification        8 bslbf
+       * still_mode                 1 bslbf
+       * interlace_video            1 bslbf
+       * reserved                   6 bslbf
+       * private_data_byte          8 bslbf
+       */
+      gint8 still_interlace_reserved = 0x00;
+      int wr_size = 0;
+      guint8 *add_info = NULL;
+      guint8 level = stream->profile_and_level & 0xF;
+      guint32 max_buffer_size = 0;
+      GstByteWriter writer;
+      gst_byte_writer_init_with_size (&writer, 32, FALSE);
+
+      switch (level) {
+        case 1:
+        case 2:
+        case 3:
+          max_buffer_size = 1250000;
+          break;
+        case 4:
+          max_buffer_size = 2500000;
+          break;
+        case 5:
+          max_buffer_size = 5000000;
+          break;
+        case 6:
+          max_buffer_size = 10000000;
+          break;
+        default:
+          break;
+      }
+
+      gst_byte_writer_put_uint16_be (&writer, stream->profile_and_level);
+      gst_byte_writer_put_uint32_be (&writer, stream->horizontal_size);
+      gst_byte_writer_put_uint32_be (&writer, stream->vertical_size);
+      gst_byte_writer_put_uint32_be (&writer, max_buffer_size);
+      gst_byte_writer_put_uint32_be (&writer, stream->max_bitrate);
+      gst_byte_writer_put_uint16_be (&writer, stream->den);
+      gst_byte_writer_put_uint16_be (&writer, stream->num);
+      gst_byte_writer_put_uint8 (&writer, stream->color_spec);
+
+      if (stream->interlace_mode)
+        still_interlace_reserved |= 0x40;
+
+      gst_byte_writer_put_uint8 (&writer, still_interlace_reserved);
+      gst_byte_writer_put_uint8 (&writer, 0x00);        /* private data byte */
+
+      wr_size = gst_byte_writer_get_size (&writer);
+      add_info = gst_byte_writer_reset_and_get_data (&writer);
+
+      descriptor =
+          gst_mpegts_descriptor_from_custom (GST_MTS_DESC_J2K_VIDEO, add_info,
+          wr_size);
+      g_ptr_array_add (pmt_stream->descriptors, descriptor);
+    }
+      break;
     case TSMUX_ST_PS_AUDIO_AC3:
     {
       guint8 add_info[6];
index 2766718..8befbe5 100644 (file)
@@ -134,6 +134,7 @@ enum TsMuxStreamType {
   TSMUX_ST_VIDEO_MPEG4                = 0x10,
   TSMUX_ST_VIDEO_H264                 = 0x1b,
   TSMUX_ST_VIDEO_HEVC                 = 0x24,
+  TSMUX_ST_VIDEO_JP2K = 0x21,
 
   /* private stream types */
   TSMUX_ST_PS_AUDIO_AC3               = 0x81,
@@ -216,6 +217,16 @@ struct TsMuxStream {
   /* Opus */
   gboolean is_opus;
   guint8 opus_channel_config_code;
+  /* Jpeg2000 */
+  gint32 horizontal_size;
+  gint32 vertical_size;
+  gint32 den;
+  gint32 num;
+  /* Maximum bitrate box */
+  guint32 max_bitrate;
+  guint16 profile_and_level;
+  gboolean interlace_mode;
+  guint8 color_spec;
 };
 
 /* stream management */
index f348877..d55f503 100644 (file)
@@ -1,5 +1,5 @@
 /* GStreamer JPEG 2000 Parser
- * Copyright (C) <2016> Grok Image Compession Inc.
+ * Copyright (C) <2016-2017> Grok Image Compression Inc.
  *  @author Aaron Boxer <boxerab@gmail.com>
  *
  * This library is free software; you can redistribute it and/or
index 6000f34..05c03d1 100644 (file)
@@ -1,5 +1,5 @@
 /* GStreamer JPEG 2000 Parser
- * Copyright (C) <2016> Grok Image Compression Inc.
+ * Copyright (C) <2016-2017> Grok Image Compression Inc.
  *  @author Aaron Boxer <boxerab@gmail.com>
  *
  * This library is free software; you can redistribute it and/or