/**
* SECTION:element-rtpj2kpay
+ * @title: rtpj2kpay
*
* Payload encode JPEG 2000 images into RTP packets according to RFC 5371
* and RFC 5372.
* codestream. A "packetization unit" is defined as either a JPEG 2000 main header,
* a JPEG 2000 tile-part header, or a JPEG 2000 packet.
*
- *
*/
#ifdef HAVE_CONFIG_H
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
- GST_STATIC_CAPS ("image/x-jpc")
+ GST_STATIC_CAPS ("image/x-jpc, " GST_RTP_J2K_SAMPLING_LIST)
);
+
static GstStaticPadTemplate gst_rtp_j2k_pay_src_template =
GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
" media = (string) \"video\", "
" payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
" clock-rate = (int) 90000, "
- " encoding-name = (string) \"JPEG2000\"")
+ GST_RTP_J2K_SAMPLING_LIST "," " encoding-name = (string) \"JPEG2000\"")
);
GST_DEBUG_CATEGORY_STATIC (rtpj2kpay_debug);
gst_rtp_j2k_pay_setcaps (GstRTPBasePayload * basepayload, GstCaps * caps)
{
GstStructure *caps_structure = gst_caps_get_structure (caps, 0);
- GstRtpJ2KPay *pay;
- gint width = 0, height = 0;
gboolean res;
+ gint width = 0, height = 0;
+ const gchar *sampling = NULL;
- pay = GST_RTP_J2K_PAY (basepayload);
+ gboolean has_width = gst_structure_get_int (caps_structure, "width", &width);
+ gboolean has_height =
+ gst_structure_get_int (caps_structure, "height", &height);
- /* these properties are not mandatory, we can get them from the stream */
- if (gst_structure_get_int (caps_structure, "height", &height)) {
- pay->height = height;
- }
- if (gst_structure_get_int (caps_structure, "width", &width)) {
- pay->width = width;
- }
+
+ /* sampling is a required field */
+ sampling = gst_structure_get_string (caps_structure, "sampling");
gst_rtp_base_payload_set_options (basepayload, "video", TRUE, "JPEG2000",
90000);
- res = gst_rtp_base_payload_set_outcaps (basepayload, NULL);
+ if (has_width && has_height)
+ res = gst_rtp_base_payload_set_outcaps (basepayload,
+ "sampling", G_TYPE_STRING, sampling, "width", G_TYPE_INT, width,
+ "height", G_TYPE_INT, height, NULL);
+ else
+ res =
+ gst_rtp_base_payload_set_outcaps (basepayload, "sampling",
+ G_TYPE_STRING, sampling, NULL);
return res;
}
typedef struct
{
RtpJ2KHeader header;
+ gboolean multi_tile;
gboolean bitstream;
- guint n_tiles;
guint next_sot;
gboolean force_packet;
} RtpJ2KState;
+
+/* Note: The standard recommends that headers be put in their own RTP packets, so we follow
+ * this recommendation in the code. Also, this method groups together all J2K packets
+ * for a tile part and treats this group as a packetization unit. According to the RFC,
+ * only an individual J2K packet is considered a packetization unit.
+ */
+
static guint
find_pu_end (GstRtpJ2KPay * pay, const guint8 * data, guint size,
guint offset, RtpJ2KState * state)
switch (marker) {
case GST_J2K_MARKER_SOC:
GST_LOG_OBJECT (pay, "found SOC at %u", offset);
- state->header.MHF = 1;
+ /* start off by assuming that we will fit the entire header
+ into the RTP payload */
+ state->header.MHF = 3;
break;
case GST_J2K_MARKER_SOT:
{
- guint len, Psot;
+ guint len, Psot, tile;
GST_LOG_OBJECT (pay, "found SOT at %u", offset);
- /* we found SOT but also had a header first */
+ /* SOT for first tile part in code stream:
+ force close of current RTP packet, so that it
+ only contains main header */
if (state->header.MHF) {
state->force_packet = TRUE;
return offset - 2;
if (offset + len >= size)
return size;
- if (state->n_tiles == 0)
- /* first tile, T is valid */
- state->header.T = 0;
- else
- /* more tiles, T becomes invalid */
- state->header.T = 1;
- state->header.tile = GST_READ_UINT16_BE (&data[offset + 2]);
- state->n_tiles++;
+ /* Isot */
+ tile = GST_READ_UINT16_BE (&data[offset + 2]);
+
+ if (!state->multi_tile) {
+ /* we have detected multiple tiles in this rtp packet : tile bit is now invalid */
+ if (state->header.T == 0 && state->header.tile != tile) {
+ state->header.T = 1;
+ state->multi_tile = TRUE;
+ } else {
+ state->header.T = 0;
+ }
+ }
+ state->header.tile = tile;
- /* get offset of next tile, if it's 0, it goes all the way to the end of
- * the data */
+ /* Note: Tile parts from multiple tiles in single RTP packet
+ will make T invalid.
+ This cannot happen in our case since we always
+ send tile headers in their own RTP packets, so we cannot mix
+ tile parts in a single RTP packet */
+
+ /* Psot: offset of next tile. If it's 0, next tile goes all the way
+ to the end of the data */
Psot = GST_READ_UINT32_BE (&data[offset + 4]);
if (Psot == 0)
state->next_sot = size;
}
case GST_J2K_MARKER_SOD:
GST_LOG_OBJECT (pay, "found SOD at %u", offset);
- /* can't have more tiles now */
- state->n_tiles = 0;
/* go to bitstream parsing */
state->bitstream = TRUE;
/* cut at the next SOP or else include all data */
state.header.tp = 0; /* only progressive scan */
state.header.MHF = 0; /* no header */
state.header.mh_id = 0; /* always 0 for now */
- state.header.T = 1; /* invalid tile */
+ state.header.T = 1; /* invalid tile, because we always begin with the main header */
state.header.priority = 255; /* always 255 for now */
- state.header.tile = 0; /* no tile number */
+ state.header.tile = 0xffff; /* no tile number */
state.header.offset = 0; /* offset of 0 */
+ state.multi_tile = FALSE;
state.bitstream = FALSE;
- state.n_tiles = 0;
state.next_sot = 0;
state.force_packet = FALSE;
header = gst_rtp_buffer_get_payload (&rtp);
pu_size -= data_size;
- if (pu_size == 0) {
- /* reached the end of a packetization unit */
- if (state.header.MHF) {
- /* we were doing a header, see if all fit in one packet or if
- * we had to fragment it */
- if (offset == 0)
- state.header.MHF = 3;
- else
- state.header.MHF = 2;
+
+ /* reached the end of a packetization unit */
+ if (pu_size == 0 && end >= map.size) {
+ gst_rtp_buffer_set_marker (&rtp, TRUE);
+ }
+ /* If we were processing a header, see if all fits in one RTP packet
+ or if we have to fragment it */
+ if (state.header.MHF) {
+ switch (state.header.MHF) {
+ case 3:
+ if (pu_size > 0)
+ state.header.MHF = 1;
+ break;
+ case 1:
+ if (pu_size == 0)
+ state.header.MHF = 2;
+ break;
+ default:
+ break;
}
- if (end >= map.size)
- gst_rtp_buffer_set_marker (&rtp, TRUE);
}
/*
/* make subbuffer of j2k data */
paybuf = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_ALL,
offset, data_size);
- gst_rtp_copy_meta (GST_ELEMENT_CAST (basepayload), outbuf, paybuf,
- g_quark_from_static_string (GST_META_TAG_VIDEO_STR));
+ gst_rtp_copy_video_meta (basepayload, outbuf, paybuf);
outbuf = gst_buffer_append (outbuf, paybuf);
gst_buffer_list_add (list, outbuf);
- /* reset header for next round */
- state.header.MHF = 0;
- state.header.T = 1;
- state.header.tile = 0;
+ /* reset multi_tile */
+ state.multi_tile = FALSE;
+
+
+ /* set MHF to zero if there is no more main header to process */
+ if (state.header.MHF & 2)
+ state.header.MHF = 0;
+
+ /* tile is valid, if there is no more header to process */
+ if (!state.header.MHF)
+ state.header.T = 0;
+
offset += data_size;
state.header.offset = offset;
offset = pos;
} while (offset < map.size);
+ gst_buffer_unmap (buffer, &map);
gst_buffer_unref (buffer);
/* push the whole buffer list at once */