}
s = gst_caps_get_structure (caps, 0);
- g_return_val_if_fail (s != NULL, FALSE);
if (gst_structure_has_name (s, "video/x-dirac")) {
GST_DEBUG_OBJECT (pad, "Creating Dirac stream");
gst_buffer_unref (pad_data->codec_data);
pad_data->codec_data = NULL;
}
+ if (pad_data->stream_id == mux->video_stream_id)
+ mux->video_stream_id = 0;
}
- if (pad_data->stream_id == mux->video_stream_id)
- mux->video_stream_id = 0;
GST_OBJECT_UNLOCK (pad);
gst_collect_pads_remove_pad (mux->collect, pad);
}
gst_segment_init (&base->segment, GST_FORMAT_UNDEFINED);
- base->last_seek_seqnum = (guint32) - 1;
+ base->last_seek_seqnum = GST_SEQNUM_INVALID;
base->mode = BASE_MODE_STREAMING;
base->seen_pat = FALSE;
G_OBJECT_CLASS (parent_class)->finalize (object);
}
+
/* returns NULL if no matching descriptor found *
* otherwise returns a descriptor that needs to *
* be freed */
if (base->segment.format == GST_FORMAT_TIME) {
base->packetizer->calculate_offset = FALSE;
base->packetizer->calculate_skew = TRUE;
+ /* Seek was handled upstream */
+ base->last_seek_seqnum = gst_event_get_seqnum (event);
} else {
base->packetizer->calculate_offset = TRUE;
base->packetizer->calculate_skew = FALSE;
GST_DEBUG ("Pulling data from %" G_GUINT64_FORMAT, base->seek_offset);
+ if (G_UNLIKELY (base->last_seek_seqnum == GST_SEQNUM_INVALID)) {
+ /* No configured seek, set a valid seqnum */
+ base->last_seek_seqnum = gst_util_seqnum_next ();
+ }
ret = gst_pad_pull_range (base->sinkpad, base->seek_offset,
100 * base->packetsize, &buf);
if (G_UNLIKELY (ret != GST_FLOW_OK))
/* Upstream segment */
GstSegment segment;
- /* Last received seek event seqnum (default -1) */
+ /* Last received seek event seqnum (default GST_SEQNUM_INVALID) */
guint last_seek_seqnum;
/* Whether to parse private section or not */
MpegTSPacketizerPacket *packet, GList **remaining);
/* Only valid if calculate_offset is TRUE */
-G_GNUC_INTERNAL guint mpegts_packetizer_get_seen_pcr (MpegTSPacketizer2 *packetizer);
-
G_GNUC_INTERNAL GstClockTime
mpegts_packetizer_offset_to_ts (MpegTSPacketizer2 * packetizer,
guint64 offset, guint16 pcr_pid);
#include "pesparse.h"
#include <gst/codecparsers/gsth264parser.h>
#include <gst/codecparsers/gstmpegvideoparser.h>
+#include <gst/video/video-color.h>
#include <math.h>
typedef struct _TSDemuxStream TSDemuxStream;
typedef struct _TSDemuxH264ParsingInfos TSDemuxH264ParsingInfos;
+typedef struct _TSDemuxJP2KParsingInfos TSDemuxJP2KParsingInfos;
/* Returns TRUE if a keyframe was found */
typedef gboolean (*GstTsDemuxKeyFrameScanFunction) (TSDemuxStream * stream,
SimpleBuffer framedata;
};
+struct _TSDemuxJP2KParsingInfos
+{
+ /* J2K parsing data */
+ gboolean interlace;
+};
struct _TSDemuxStream
{
MpegTSBaseStream stream;
GstTsDemuxKeyFrameScanFunction scan_function;
TSDemuxH264ParsingInfos h264infos;
+ TSDemuxJP2KParsingInfos jp2kInfos;
#ifdef TIZEN_FEATURE_AVOID_PAD_SWITCHING
/* For pad matching to avoid switching pads */
TSDemuxStream *matched_stream;
"video/x-cavs;" \
"video/x-wmv," \
"wmvversion = (int) 3, " \
- "format = (string) WVC1" \
- )
+ "format = (string) WVC1;" \
+ "image/x-jpc;" \
+)
#define AUDIO_CAPS \
GST_STATIC_CAPS ( \
gboolean res = FALSE;
gint64 val;
+ if (!demux->program) {
+ GST_DEBUG_OBJECT (demux, "No active program yet, can't provide duration");
+ return FALSE;
+ }
+
/* Get total size in bytes */
if (gst_pad_peer_query_duration (base->sinkpad, GST_FORMAT_BYTES, &val)) {
/* Convert it to duration */
"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");
/* The biggest offset */
guint64 offset = 0;
GList *tmp;
+ gboolean have_only_sparse = TRUE;
+
+ /* 0. Do we only have sparse stream */
+ for (tmp = demux->program->stream_list; tmp; tmp = tmp->next) {
+ TSDemuxStream *tmpstream = (TSDemuxStream *) tmp->data;
+
+ if (!tmpstream->sparse) {
+ have_only_sparse = FALSE;
+ break;
+ }
+ }
/* 1. Go over all streams */
for (tmp = demux->program->stream_list; tmp; tmp = tmp->next) {
TSDemuxStream *tmpstream = (TSDemuxStream *) tmp->data;
/* 1.1 check if at least one stream got a valid DTS */
- if ((tmpstream->raw_dts != -1 && tmpstream->dts != GST_CLOCK_TIME_NONE) ||
- (tmpstream->raw_pts != -1 && tmpstream->pts != GST_CLOCK_TIME_NONE)) {
- have_observation = TRUE;
- break;
+ if (have_only_sparse || !tmpstream->sparse) {
+ if ((tmpstream->raw_dts != -1 && tmpstream->dts != GST_CLOCK_TIME_NONE) ||
+ (tmpstream->raw_pts != -1 && tmpstream->pts != GST_CLOCK_TIME_NONE)) {
+ have_observation = TRUE;
+ break;
+ }
}
}
if (!demux->segment_event) {
demux->segment_event = gst_event_new_segment (&demux->segment);
- GST_EVENT_SEQNUM (demux->segment_event) = base->last_seek_seqnum;
+
+ if (base->last_seek_seqnum != GST_SEQNUM_INVALID)
+ gst_event_set_seqnum (demux->segment_event, base->last_seek_seqnum);
}
push_new_segment:
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;
+
+ guint data_location;
+ GstBuffer *retbuf = NULL;
+
+ 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);
+
+ /* Check for the location of the jp2k magic */
+ data_location =
+ gst_byte_reader_masked_scan_uint32 (&reader, 0xffffffff, 0xff4fff51, 0,
+ stream->current_size);
+ GST_DEBUG_OBJECT (stream->pad, "data location %d", data_location);
+ if (data_location == -1) {
+ GST_ERROR_OBJECT (stream->pad, "Stream does not contain jp2k magic header");
+ goto error;
+ }
+
+ /* 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 */
+ /* Some progressive streams might have a AUF[1] of value 0 present */
+ header_tag = gst_byte_reader_get_uint32_be_unchecked (&reader);
+ if (header_tag == 0 && !stream->jp2kInfos.interlace) {
+ AUF[1] = header_tag;
+ header_tag = gst_byte_reader_get_uint32_be_unchecked (&reader);
+ /* Bump up header size and recheck */
+ header_size += 4;
+ if (stream->current_size < header_size) {
+ GST_ERROR_OBJECT (stream->pad, "Not enough data for header");
+ goto error;
+ }
+ }
+ 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);
+
+ /* Check if we have enough data to create a valid buffer */
+ if ((stream->current_size - data_location) < (AUF[0] + AUF[1])) {
+ GST_ERROR ("Required size (%d) greater than remaining size in buffer (%d)",
+ AUF[0] + AUF[1], (stream->current_size - data_location));
+ goto error;
+ }
+
+ retbuf = gst_buffer_new_wrapped_full (0, stream->data, stream->current_size,
+ data_location, stream->current_size - data_location,
+ stream->data, g_free);
+ stream->data = NULL;
+ stream->current_size = 0;
+ return retbuf;
+
+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)
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);
+ if (!buffer) {
+ res = GST_FLOW_ERROR;
+ goto beach;
+ }
} else {
buffer = gst_buffer_new_wrapped (stream->data, stream->current_size);
}
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);
+ if (!buffer) {
+ res = GST_FLOW_ERROR;
+ goto beach;
+ }
} else {
buffer = gst_buffer_new_wrapped (stream->data, stream->current_size);
}
#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())
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) \
+ -I$(top_srcdir) \
$(GST_BASE_CFLAGS) $(GST_CFLAGS)
libgstmpegtsmux_la_LIBADD = $(top_builddir)/gst/mpegtsmux/tsmux/libtsmux.la \
$(GST_PLUGINS_BASE_LIBS) -lgstvideo-@GST_API_VERSION@ \
mpegtsmux.h \
mpegtsmux_aac.h \
mpegtsmux_ttxt.h \
- mpegtsmux_opus.h
+ mpegtsmux_opus.h \
+ mpegtsmux_jpeg2000.h
'mpegtsmux_aac.c',
'mpegtsmux_opus.c',
'mpegtsmux_ttxt.c',
+ 'mpegtsmux_jpeg2000.c',
'tsmux/tsmux.c',
'tsmux/tsmuxstream.c',
]
#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
+#define COLLECT_DATA_PAD(collect_data) (((GstCollectData *)(collect_data))->pad)
+
enum
{
PROP_0,
"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,"
"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",
mux->first = TRUE;
mux->last_flow_ret = GST_FLOW_OK;
mux->previous_pcr = -1;
+ mux->previous_offset = 0;
mux->pcr_rate_num = mux->pcr_rate_den = 1;
mux->last_ts = 0;
mux->is_delta = TRUE;
+ mux->is_header = FALSE;
mux->streamheader_sent = FALSE;
mux->pending_key_unit_ts = GST_CLOCK_TIME_NONE;
+ gst_event_replace (&mux->force_key_unit_event, NULL);
#if 0
mux->spn_count = 0;
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);
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) {
}
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);
}
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);
tsmux_set_pmt_interval (ts_data->prog, mux->pmt_interval);
g_hash_table_insert (mux->programs,
GINT_TO_POINTER (ts_data->prog_id), ts_data->prog);
+
+ /* Take the first stream of the program for the PCR */
+ GST_DEBUG_OBJECT (COLLECT_DATA_PAD (ts_data),
+ "Use stream (pid=%d) from pad as PCR for program (prog_id = %d)",
+ ts_data->pid, ts_data->prog_id);
+
+ tsmux_program_set_pcr_stream (ts_data->prog, ts_data->stream);
}
if (ts_data->stream == NULL) {
}
}
-
-#define COLLECT_DATA_PAD(collect_data) (((GstCollectData *)(collect_data))->pad)
-
static gboolean
mpegtsmux_sink_event (GstCollectPads * pads, GstCollectData * data,
GstEvent * event, gpointer user_data)
GST_TIME_ARGS (running_time), count);
gst_pad_push_event (mux->srcpad, event);
- /* output PAT */
- mux->tsmux->last_pat_ts = -1;
+ /* output PAT, SI tables */
+ tsmux_resend_pat (mux->tsmux);
+ tsmux_resend_si (mux->tsmux);
/* output PMT for each program */
for (cur = mux->tsmux->programs; cur; cur = cur->next) {
TsMuxProgram *program = (TsMuxProgram *) cur->data;
- program->last_pmt_ts = -1;
+ tsmux_resend_pmt (program);
}
- tsmux_program_set_pcr_stream (prog, NULL);
}
}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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__ */
}
/**
+ * tsmux_resend_pat:
+ * @mux: a #TsMux
+ *
+ * Resends the PAT before the next stream packet.
+ */
+void
+tsmux_resend_pat (TsMux * mux)
+{
+ g_return_if_fail (mux != NULL);
+
+ mux->last_pat_ts = G_MININT64;
+}
+
+/**
* tsmux_set_si_interval:
* @mux: a #TsMux
* @freq: a new SI table interval
}
/**
+ * tsmux_resend_si:
+ * @mux: a #TsMux
+ *
+ * Resends the SI tables before the next stream packet.
+ *
+ */
+void
+tsmux_resend_si (TsMux * mux)
+{
+ g_return_if_fail (mux != NULL);
+
+ mux->last_si_ts = G_MININT64;
+}
+
+/**
* tsmux_add_mpegts_si_section:
* @mux: a #TsMux
* @section: (transfer full): a #GstMpegtsSection to add
*
* Add a Service Information #GstMpegtsSection to the stream
*
- * Returns: #TRUE on success, #FALSE otherwise
+ * Returns: %TRUE on success, %FALSE otherwise
*/
gboolean
tsmux_add_mpegts_si_section (TsMux * mux, GstMpegtsSection * section)
}
/**
+ * tsmux_resend_pmt:
+ * @program: a #TsMuxProgram
+ *
+ * Resends the PMT before the next stream packet.
+ */
+void
+tsmux_resend_pmt (TsMuxProgram * program)
+{
+ g_return_if_fail (program != NULL);
+
+ program->last_pmt_ts = G_MININT64;
+}
+
+/**
* tsmux_program_add_stream:
* @program: a #TsMuxProgram
* @stream: a #TsMuxStream
gst_buffer_unmap (buf, &map);
+ GST_DEBUG ("Writing PES of size %d", (int) gst_buffer_get_size (buf));
res = tsmux_packet_out (mux, buf, cur_pcr);
/* Reset all dynamic flags */
void tsmux_set_alloc_func (TsMux *mux, TsMuxAllocFunc func, void *user_data);
void tsmux_set_pat_interval (TsMux *mux, guint interval);
guint tsmux_get_pat_interval (TsMux *mux);
+void tsmux_resend_pat (TsMux *mux);
guint16 tsmux_get_new_pid (TsMux *mux);
/* pid/program management */
void tsmux_program_free (TsMuxProgram *program);
void tsmux_set_pmt_interval (TsMuxProgram *program, guint interval);
guint tsmux_get_pmt_interval (TsMuxProgram *program);
+void tsmux_resend_pmt (TsMuxProgram *program);
/* SI table management */
void tsmux_set_si_interval (TsMux *mux, guint interval);
guint tsmux_get_si_interval (TsMux *mux);
+void tsmux_resend_si (TsMux *mux);
gboolean tsmux_add_mpegts_si_section (TsMux * mux, GstMpegtsSection * section);
/* stream management */
#include <string.h>
#include <gst/mpegts/mpegts.h>
+#include <gst/base/gstbytewriter.h>
#include "tsmuxcommon.h"
#include "tsmuxstream.h"
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:
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];
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,
/* optional fixed PES size for stream type */
guint16 pes_payload_size;
/* current PES payload size being written */
- guint16 cur_pes_payload_size;
+ guint32 cur_pes_payload_size;
/* ... of which already this much written */
- guint16 pes_bytes_written;
+ guint32 pes_bytes_written;
/* PTS/DTS to write if the flags in the packet info are set */
/* in MPEG PTS clock time */
/* 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 */
/* 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
#include "gstjpeg2000parse.h"
#include <gst/base/base.h>
+/* Not used at the moment
+static gboolean gst_jpeg2000_parse_is_cinema(guint16 rsiz) {
+ return ((rsiz >= GST_JPEG2000_PARSE_PROFILE_CINEMA_2K) && (rsiz <= GST_JPEG2000_PARSE_PROFILE_CINEMA_S4K));
+}
+static gboolean gst_jpeg2000_parse_is_storage(guint16 rsiz) {
+ return (rsiz == GST_JPEG2000_PARSE_PROFILE_CINEMA_LTS);
+}
+*/
+static gboolean
+gst_jpeg2000_parse_is_broadcast (guint16 rsiz)
+{
+ return ((rsiz >= GST_JPEG2000_PARSE_PROFILE_BC_SINGLE) &&
+ (rsiz <= ((GST_JPEG2000_PARSE_PROFILE_BC_MULTI_R) | (0x000b)))
+ && ((rsiz & (~GST_JPEG2000_PARSE_PROFILE_BC_MASK)) == 0));
+}
+
+static gboolean
+gst_jpeg2000_parse_is_imf (guint16 rsiz)
+{
+ return ((rsiz >= GST_JPEG2000_PARSE_PROFILE_IMF_2K)
+ && (rsiz <= ((GST_JPEG2000_PARSE_PROFILE_IMF_8K_R) | (0x009b))));
+}
+
+static gboolean
+gst_jpeg2000_parse_is_part_2 (guint16 rsiz)
+{
+ return (rsiz & GST_JPEG2000_PARSE_PROFILE_PART2);
+}
+
static void
" width = (int)[1, MAX], height = (int)[1, MAX],"
GST_JPEG2000_SAMPLING_LIST ","
GST_JPEG2000_COLORSPACE_LIST ","
+ " profile = (int)[0, 49151],"
" parsed = (boolean) true;"
"image/x-j2c,"
" width = (int)[1, MAX], height = (int)[1, MAX],"
GST_JPEG2000_SAMPLING_LIST ","
- GST_JPEG2000_COLORSPACE_LIST "," " parsed = (boolean) true")
+ GST_JPEG2000_COLORSPACE_LIST ","
+ " profile = (int)[0, 49151]," " parsed = (boolean) true")
);
static GstStaticPadTemplate sinktemplate =
GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK,
GST_PAD_ALWAYS,
- GST_STATIC_CAPS ("image/x-jpc,"
- GST_JPEG2000_SAMPLING_LIST ";"
- "image/x-jpc, "
- GST_JPEG2000_COLORSPACE_LIST ";"
- "image/x-j2c,"
- GST_JPEG2000_SAMPLING_LIST ";"
- "image/x-j2c, " GST_JPEG2000_COLORSPACE_LIST)
- );
+ GST_STATIC_CAPS ("image/jp2;image/x-jpc;image/x-j2c"));
#define parent_class gst_jpeg2000_parse_parent_class
G_DEFINE_TYPE (GstJPEG2000Parse, gst_jpeg2000_parse, GST_TYPE_BASE_PARSE);
}
}
- /* FIXME We could fail the negotiation immediatly if caps are empty */
if (caps && !gst_caps_is_empty (caps)) {
/* fixate to avoid ambiguity with lists when parsing */
caps = gst_caps_fixate (caps);
codec_format = format_from_media_type (gst_caps_get_structure (caps, 0));
}
- /* default */
- if (codec_format == GST_JPEG2000_PARSE_NO_CODEC)
- codec_format = GST_JPEG2000_PARSE_J2C;
-
GST_DEBUG_OBJECT (parse, "selected codec format %d", codec_format);
parse->codec_format = codec_format;
guint eoc_offset = 0;
GstCaps *current_caps = NULL;
GstStructure *current_caps_struct = NULL;
- const gchar *colorspace_string = NULL;
GstJPEG2000Colorspace colorspace = GST_JPEG2000_COLORSPACE_NONE;
guint x0, y0, x1, y1;
guint width = 0, height = 0;
guint8 dx[GST_JPEG2000_PARSE_MAX_SUPPORTED_COMPONENTS];
guint8 dy[GST_JPEG2000_PARSE_MAX_SUPPORTED_COMPONENTS];
guint16 numcomps;
+ guint16 capabilities = 0;
+ guint16 profile = 0;
+ gboolean validate_main_level = FALSE;
+ guint8 main_level = 0;
+ guint8 sub_level = 0;
guint16 compno;
GstJPEG2000Sampling parsed_sampling = GST_JPEG2000_SAMPLING_NONE;
const gchar *sink_sampling_string = NULL;
GstCaps *src_caps = NULL;
guint frame_size = 0;
gboolean is_j2c;
+ gboolean parsed_j2c_4cc = FALSE;
if (!gst_buffer_map (frame->buffer, &map, GST_MAP_READ)) {
GST_ERROR_OBJECT (jpeg2000parse, "Unable to map buffer");
return GST_FLOW_ERROR;
}
+ gst_byte_reader_init (&reader, map.data, map.size);
+ /* try to get from caps */
if (jpeg2000parse->codec_format == GST_JPEG2000_PARSE_NO_CODEC)
gst_jpeg2000_parse_negotiate (jpeg2000parse, NULL);
- is_j2c = jpeg2000parse->codec_format == GST_JPEG2000_PARSE_J2C;
+ /* if we can't get from caps, then try to parse */
+ if (jpeg2000parse->codec_format == GST_JPEG2000_PARSE_NO_CODEC) {
+ /* check for "jp2c" box */
+ /* both jp2 and j2c will be found with this scan, and both will be treated as j2c format */
+ j2c_box_id_offset = gst_byte_reader_masked_scan_uint32 (&reader, 0xffffffff,
+ GST_MAKE_FOURCC ('j', 'p', '2', 'c'), 0,
+ gst_byte_reader_get_remaining (&reader));
+ parsed_j2c_4cc = TRUE;
+ is_j2c = j2c_box_id_offset != -1;
+ jpeg2000parse->codec_format =
+ is_j2c ? GST_JPEG2000_PARSE_J2C : GST_JPEG2000_PARSE_JPC;
- gst_byte_reader_init (&reader, map.data, map.size);
- num_prefix_bytes = GST_JPEG2000_MARKER_SIZE;
+ } else {
+ /* for now, just treat JP2 as J2C */
+ if (jpeg2000parse->codec_format == GST_JPEG2000_PARSE_JP2) {
+ jpeg2000parse->codec_format = GST_JPEG2000_PARSE_J2C;
+ }
+ is_j2c = jpeg2000parse->codec_format == GST_JPEG2000_PARSE_J2C;
+ }
+ num_prefix_bytes = GST_JPEG2000_MARKER_SIZE;
if (is_j2c) {
num_prefix_bytes +=
GST_JPEG2000_JP2_SIZE_OF_BOX_LEN + GST_JPEG2000_JP2_SIZE_OF_BOX_ID;
- /* check for "jp2c" */
- j2c_box_id_offset = gst_byte_reader_masked_scan_uint32 (&reader, 0xffffffff,
- GST_MAKE_FOURCC ('j', 'p', '2', 'c'), 0,
- gst_byte_reader_get_remaining (&reader));
+ /* check for "jp2c" (may have already parsed j2c_box_id_offset if caps are empty) */
+ if (!parsed_j2c_4cc) {
+ j2c_box_id_offset =
+ gst_byte_reader_masked_scan_uint32 (&reader, 0xffffffff,
+ GST_MAKE_FOURCC ('j', 'p', '2', 'c'), 0,
+ gst_byte_reader_get_remaining (&reader));
+ }
if (j2c_box_id_offset == -1) {
GST_ELEMENT_ERROR (jpeg2000parse, STREAM, DECODE, NULL,
goto beach;
}
- /* 2 to skip marker size, and another 2 to skip rsiz field */
- if (!gst_byte_reader_skip (&reader, num_prefix_bytes + 2 + 2))
+ /* 2 to skip marker size */
+ if (!gst_byte_reader_skip (&reader, num_prefix_bytes + 2))
+ goto beach;
+
+ if (!gst_byte_reader_get_uint16_be (&reader, &capabilities))
goto beach;
+ profile = capabilities & GST_JPEG2000_PARSE_PROFILE_MASK;
+ if (!gst_jpeg2000_parse_is_part_2 (capabilities)) {
+ if ((profile > GST_JPEG2000_PARSE_PROFILE_CINEMA_LTS)
+ && !gst_jpeg2000_parse_is_broadcast (profile)
+ && !gst_jpeg2000_parse_is_imf (profile)) {
+ GST_ELEMENT_ERROR (jpeg2000parse, STREAM, DECODE, NULL,
+ ("Unrecognized JPEG 2000 profile %d", profile));
+ ret = GST_FLOW_ERROR;
+ goto beach;
+ }
+ if (gst_jpeg2000_parse_is_broadcast (profile)) {
+ main_level = capabilities & 0xF;
+ validate_main_level = TRUE;
+ } else if (gst_jpeg2000_parse_is_imf (profile)) {
+ main_level = capabilities & 0xF;
+ validate_main_level = TRUE;
+ sub_level = (capabilities >> 4) & 0xF;
+ if (sub_level > 9) {
+ GST_ELEMENT_ERROR (jpeg2000parse, STREAM, DECODE, NULL,
+ ("Sub level %d is invalid", sub_level));
+ ret = GST_FLOW_ERROR;
+ goto beach;
+ }
+ }
+ if (validate_main_level && main_level > 11) {
+ GST_ELEMENT_ERROR (jpeg2000parse, STREAM, DECODE, NULL,
+ ("Main level %d is invalid", main_level));
+ ret = GST_FLOW_ERROR;
+ goto beach;
+
+ }
+ }
+
+
if (!gst_byte_reader_get_uint32_be (&reader, &x1))
goto beach;
if (!gst_byte_reader_get_uint16_be (&reader, &numcomps))
goto beach;
- if (numcomps == 2 || numcomps > GST_JPEG2000_PARSE_MAX_SUPPORTED_COMPONENTS) {
+ if (numcomps == 0 || numcomps > GST_JPEG2000_PARSE_MAX_SUPPORTED_COMPONENTS) {
GST_ELEMENT_ERROR (jpeg2000parse, STREAM, DECODE, NULL,
("Unsupported number of components %d", numcomps));
ret = GST_FLOW_NOT_NEGOTIATED;
}
current_caps = gst_pad_get_current_caps (GST_BASE_PARSE_SINK_PAD (parse));
- if (!current_caps) {
- GST_ERROR_OBJECT (jpeg2000parse, "Unable to get current caps");
- ret = GST_FLOW_NOT_NEGOTIATED;
- goto beach;
- }
+ if (current_caps) {
+ const gchar *colorspace_string = NULL;
+ current_caps_struct = gst_caps_get_structure (current_caps, 0);
+ if (!current_caps_struct) {
+ GST_ERROR_OBJECT (jpeg2000parse,
+ "Unable to get structure of current caps struct");
+ ret = GST_FLOW_NOT_NEGOTIATED;
+ goto beach;
+ }
- current_caps_struct = gst_caps_get_structure (current_caps, 0);
- if (!current_caps_struct) {
- GST_ERROR_OBJECT (jpeg2000parse,
- "Unable to get structure of current caps struct");
- ret = GST_FLOW_NOT_NEGOTIATED;
- goto beach;
- }
+ colorspace_string = gst_structure_get_string
+ (current_caps_struct, "colorspace");
+ if (colorspace_string)
+ colorspace = gst_jpeg2000_colorspace_from_string (colorspace_string);
+ sink_sampling_string = gst_structure_get_string
+ (current_caps_struct, "sampling");
+ if (sink_sampling_string)
+ sink_sampling = gst_jpeg2000_sampling_from_string (sink_sampling_string);
- colorspace_string = gst_structure_get_string
- (current_caps_struct, "colorspace");
- if (colorspace_string)
- colorspace = gst_jpeg2000_colorspace_from_string (colorspace_string);
- sink_sampling_string = gst_structure_get_string
- (current_caps_struct, "sampling");
- if (sink_sampling_string)
- sink_sampling = gst_jpeg2000_sampling_from_string (sink_sampling_string);
+ } else {
+ /* guess color space based on number of components */
+ if (numcomps == 0 || numcomps > 4) {
+ GST_ERROR_OBJECT (jpeg2000parse,
+ "Unable to guess color space from number of components %d", numcomps);
+ ret = GST_FLOW_NOT_NEGOTIATED;
+ goto beach;
+ }
+ colorspace =
+ (numcomps >=
+ 3) ? GST_JPEG2000_COLORSPACE_RGB : GST_JPEG2000_COLORSPACE_GRAY;
+ if (numcomps == 4) {
+ GST_WARNING_OBJECT (jpeg2000parse, "No caps available: assuming RGBA");
+ } else if (numcomps == 3) {
+ GST_WARNING_OBJECT (jpeg2000parse, "No caps available: assuming RGB");
+ } else if (numcomps == 2) {
+ GST_WARNING_OBJECT (jpeg2000parse,
+ "No caps available: assuming grayscale with alpha");
+ }
+
+ }
for (compno = 0; compno < numcomps; ++compno) {
src_caps =
gst_caps_new_simple (media_type_from_codec_format
- (jpeg2000parse->codec_format), "width", G_TYPE_INT, width, "height",
- G_TYPE_INT, height, "colorspace", G_TYPE_STRING,
+ (jpeg2000parse->codec_format),
+ "width", G_TYPE_INT, width,
+ "height", G_TYPE_INT, height,
+ "colorspace", G_TYPE_STRING,
gst_jpeg2000_colorspace_to_string (colorspace), "sampling",
- G_TYPE_STRING, gst_jpeg2000_sampling_to_string (source_sampling), NULL);
+ G_TYPE_STRING, gst_jpeg2000_sampling_to_string (source_sampling),
+ "profile", G_TYPE_UINT, profile, NULL);
+
+ if (gst_jpeg2000_parse_is_broadcast (capabilities)
+ || gst_jpeg2000_parse_is_imf (capabilities)) {
+ gst_caps_set_simple (src_caps, "main-level", G_TYPE_UINT, main_level,
+ NULL);
+ if (gst_jpeg2000_parse_is_imf (capabilities)) {
+ gst_caps_set_simple (src_caps, "sub-level", G_TYPE_UINT, sub_level,
+ NULL);
+ }
+ }
- if (gst_structure_get_fraction (current_caps_struct, "framerate", &fr_num,
- &fr_denom)) {
- gst_caps_set_simple (src_caps, "framerate", GST_TYPE_FRACTION, fr_num,
- fr_denom, NULL);
- } else {
- GST_WARNING_OBJECT (jpeg2000parse, "No framerate set");
+ if (current_caps_struct) {
+ const gchar *caps_string = gst_structure_get_string
+ (current_caps_struct, "colorimetry");
+ if (caps_string) {
+ gst_caps_set_simple (src_caps, "colorimetry", G_TYPE_STRING,
+ caps_string, NULL);
+ }
+ caps_string = gst_structure_get_string
+ (current_caps_struct, "interlace-mode");
+ if (caps_string) {
+ gst_caps_set_simple (src_caps, "interlace-mode", G_TYPE_STRING,
+ caps_string, NULL);
+ }
+ caps_string = gst_structure_get_string
+ (current_caps_struct, "field-order");
+ if (caps_string) {
+ gst_caps_set_simple (src_caps, "field-order", G_TYPE_STRING,
+ caps_string, NULL);
+ }
+ caps_string = gst_structure_get_string
+ (current_caps_struct, "multiview-mode");
+ if (caps_string) {
+ gst_caps_set_simple (src_caps, "multiview-mode", G_TYPE_STRING,
+ caps_string, NULL);
+ }
+ caps_string = gst_structure_get_string
+ (current_caps_struct, "chroma-site");
+ if (caps_string) {
+ gst_caps_set_simple (src_caps, "chroma-site", G_TYPE_STRING,
+ caps_string, NULL);
+ }
+ if (gst_structure_get_fraction (current_caps_struct, "framerate", &fr_num,
+ &fr_denom)) {
+ gst_caps_set_simple (src_caps, "framerate", GST_TYPE_FRACTION, fr_num,
+ fr_denom, NULL);
+ } else {
+ GST_WARNING_OBJECT (jpeg2000parse, "No framerate set");
+ }
}
+
if (!gst_pad_set_caps (GST_BASE_PARSE_SRC_PAD (parse), src_caps)) {
GST_ERROR_OBJECT (jpeg2000parse, "Unable to set source caps");
ret = GST_FLOW_NOT_NEGOTIATED;
frame_size, eoc_frame_size);
}
frame_size = eoc_frame_size;
+ } else {
+ goto beach;
}
/* clean up and finish frame */
/* 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
typedef struct _GstJPEG2000Parse GstJPEG2000Parse;
typedef struct _GstJPEG2000ParseClass GstJPEG2000ParseClass;
-#define GST_JPEG2000_PARSE_MAX_SUPPORTED_COMPONENTS 4
+
+/**
+ * JPEG 2000 Profiles (stored in rsiz/capabilities field in code stream header)
+ * See Table A.10 from 15444-1 (updated in various AMDs)
+ *
+ * For broadcast profiles, the GST_JPEG2000_PARSE_PROFILE_BC_XXXX profile value must be combined with the target
+ * main level (3-0 LSBs, with value between 0 and 11).
+ * Example:
+ * capabilities GST_JPEG2000_PARSE_PROFILE_BC_MULTI | 0x0005 (in this case, main level equals 5)
+ *
+ * For IMF profiles, the GST_JPEG2000_PARSE_PROFILE_IMF_XXXX profile value must be combined with the target main level
+ * (3-0 LSBs, with value between 0 and 11), and target sub level (7-4 LSBs, with value between 0 and 9).
+ * Example:
+ * capabilities GST_JPEG2000_PARSE_PROFILE_IMF_2K | 0x0040 | 0x0005 (in this case, main level equals 5 and sub level equals 4)
+ *
+ *
+ * Broadcast main level (15444-1 AMD4,AMD8)
+ *
+ * Note: Mbit/s == 10^6 bits/s; Msamples/s == 10^6 samples/s
+ *
+ * Level 0: no max rate
+ * Level 1: 200 Mbits/s, 65 Msamples/s
+ * Level 2: 200 Mbits/s, 130 Msamples/s
+ * Level 3: 200 Mbits/s, 195 Msamples/s
+ * Level 4: 400 Mbits/s, 260 Msamples/s
+ * Level 5: 800Mbits/s, 520 Msamples/s
+ * Level >= 6: 2^(Level-6) * 1600 Mbits/s, 2^(Level-6) * 1200 Msamples/s
+ *
+ * Broadcast tiling
+ *
+ * Either single-tile or multi-tile. Multi-tile only permits
+ * 1 or 4 tiles per frame, where multiple tiles have identical
+ * sizes, and are configured in either 2x2 or 1x4 layout.
+ *
+ * */
+
+#define GST_JPEG2000_PARSE_PROFILE_NONE 0x0000 /** no profile - defined in 15444-1 */
+#define GST_JPEG2000_PARSE_PROFILE_0 0x0001 /** Profile 0 - defined in 15444-1,Table A.45 */
+#define GST_JPEG2000_PARSE_PROFILE_1 0x0002 /** Profile 1 - defined in 15444-1,Table A.45 */
+#define GST_JPEG2000_PARSE_PROFILE_CINEMA_2K 0x0003 /** 2K Cinema profile - defined in 15444-1 AMD1 */
+#define GST_JPEG2000_PARSE_PROFILE_CINEMA_4K 0x0004 /** 4K Cinema profile - defined in 15444-1 AMD1 */
+#define GST_JPEG2000_PARSE_PROFILE_CINEMA_S2K 0x0005 /** Scalable 2K Cinema profile - defined in 15444-1 AMD2 */
+#define GST_JPEG2000_PARSE_PROFILE_CINEMA_S4K 0x0006 /** Scalable 4K Cinema profile - defined in 15444-1 AMD2 */
+#define GST_JPEG2000_PARSE_PROFILE_CINEMA_LTS 0x0007/** Long Term Storage Cinema profile - defined in 15444-1 AMD2 */
+#define GST_JPEG2000_PARSE_PROFILE_BC_SINGLE 0x0100 /** Single Tile Broadcast profile - defined in 15444-1 AMD3 */
+#define GST_JPEG2000_PARSE_PROFILE_BC_MULTI 0x0200 /** Multi Tile Broadcast profile - defined in 15444-1 AMD3 */
+#define GST_JPEG2000_PARSE_PROFILE_BC_MULTI_R 0x0300 /** Multi Tile Reversible Broadcast profile - defined in 15444-1 AMD3 */
+#define GST_JPEG2000_PARSE_PROFILE_BC_MASK 0x0F0F /** Mask for broadcast profile, including main level */
+#define GST_JPEG2000_PARSE_PROFILE_IMF_2K 0x0400 /** 2K Single Tile Lossy IMF profile - defined in 15444-1 AMD 8 */
+#define GST_JPEG2000_PARSE_PROFILE_IMF_4K 0x0401 /** 4K Single Tile Lossy IMF profile - defined in 15444-1 AMD 8 */
+#define GST_JPEG2000_PARSE_PROFILE_IMF_8K 0x0402 /** 8K Single Tile Lossy IMF profile - defined in 15444-1 AMD 8 */
+#define GST_JPEG2000_PARSE_PROFILE_IMF_2K_R 0x0403 /** 2K Single/Multi Tile Reversible IMF profile - defined in 15444-1 AMD 8 */
+#define GST_JPEG2000_PARSE_PROFILE_IMF_4K_R 0x0800 /** 4K Single/Multi Tile Reversible IMF profile - defined in 15444-1 AMD 8 */
+#define GST_JPEG2000_PARSE_PROFILE_IMF_8K_R 0x0801 /** 8K Single/Multi Tile Reversible IMF profile - defined in 15444-1 AMD 8 */
+#define GST_JPEG2000_PARSE_PROFILE_MASK 0xBFFF /** Mask for profile bits */
+#define GST_JPEG2000_PARSE_PROFILE_PART2 0x8000 /** At least 1 extension defined in 15444-2 (Part-2) */
+
+#define GST_JPEG2000_PARSE_MAX_SUPPORTED_COMPONENTS 4
typedef enum
{