From 005e27fa79246869e0af373d223e85ee34c335cf Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Thu, 2 Dec 2010 17:53:42 +0100 Subject: [PATCH] j2kpay: use SOP markers to split bitstream When parsing the bitstream, look for SOP markers because we are allowed to split packets on those marker boundaries. Rework the parsing code a little so that we can pack multiple Packetization units in one RTP packet. --- gst/rtp/gstrtpj2kpay.c | 251 +++++++++++++++++++++++++++++------------ 1 file changed, 178 insertions(+), 73 deletions(-) diff --git a/gst/rtp/gstrtpj2kpay.c b/gst/rtp/gstrtpj2kpay.c index 8485e69629..de4462e45b 100644 --- a/gst/rtp/gstrtpj2kpay.c +++ b/gst/rtp/gstrtpj2kpay.c @@ -72,6 +72,8 @@ typedef enum J2K_MARKER = 0xFF, J2K_MARKER_SOC = 0x4F, J2K_MARKER_SOT = 0x90, + J2K_MARKER_SOP = 0x91, + J2K_MARKER_SOD = 0x93, J2K_MARKER_EOC = 0xD9 } RtpJ2KMarker; @@ -226,64 +228,126 @@ gst_rtp_j2k_pay_scan_marker (const guint8 * data, guint size, guint * offset) while ((data[(*offset)++] != J2K_MARKER) && ((*offset) < size)); if (G_UNLIKELY ((*offset) >= size)) { - GST_LOG ("end of data, return EOC"); return J2K_MARKER_EOC; } else { guint8 marker = data[(*offset)++]; - GST_LOG ("found %02x marker", marker); return marker; } } +typedef struct +{ + RtpJ2KHeader header; + gboolean bitstream; + guint n_tiles; + guint next_sot; + gboolean force_packet; +} RtpJ2KState; + static guint find_pu_end (GstRtpJ2KPay * pay, const guint8 * data, guint size, - guint offset, RtpJ2KHeader * header) + guint offset, RtpJ2KState * state) { + gboolean cut_sop = FALSE; + RtpJ2KMarker marker; + /* parse the j2k header for 'start of codestream' */ + GST_LOG_OBJECT (pay, "checking from offset %u", offset); while (offset < size) { - GST_LOG_OBJECT (pay, "checking from offset %u", offset); - switch (gst_rtp_j2k_pay_scan_marker (data, size, &offset)) { - case J2K_MARKER_SOC: - GST_DEBUG_OBJECT (pay, "found SOC at %u", offset); - header->MHF = 1; - break; - case J2K_MARKER_SOT: - { - guint len, Psot; + marker = gst_rtp_j2k_pay_scan_marker (data, size, &offset); - GST_DEBUG_OBJECT (pay, "found SOT at %u", offset); - /* we found SOT but also had a header first */ - if (header->MHF) - return offset - 2; - - /* parse SOT but do some sanity checks first */ - len = gst_rtp_j2k_pay_header_size (data, offset); - GST_DEBUG_OBJECT (pay, "SOT length %u", len); - if (len < 8) - return size; - if (offset + len >= size) - return size; - - /* we have a valid tile number now, keep it in the header */ - header->T = 0; - header->tile = GST_READ_UINT16_BE (&data[offset + 2]); - - /* get offset of next tile, if it's 0, it goes all the way to the end of - * the data */ - Psot = GST_READ_UINT32_BE (&data[offset + 4]); - if (Psot == 0) - offset = size; - else - offset += Psot; - GST_DEBUG_OBJECT (pay, "Isot %u, Psot %u", header->tile, Psot); - break; + if (state->bitstream) { + /* parsing bitstream, only look for SOP */ + switch (marker) { + case J2K_MARKER_SOP: + GST_LOG_OBJECT (pay, "found SOP at %u", offset); + if (cut_sop) + return offset - 2; + cut_sop = TRUE; + break; + default: + if (offset >= state->next_sot) { + GST_LOG_OBJECT (pay, "reached next SOT at %u", offset); + state->bitstream = FALSE; + state->force_packet = TRUE; + if (marker == J2K_MARKER_EOC) + /* include EOC */ + return state->next_sot + 2; + else + return state->next_sot; + } + break; + } + } else { + switch (marker) { + case J2K_MARKER_SOC: + GST_LOG_OBJECT (pay, "found SOC at %u", offset); + state->header.MHF = 1; + break; + case J2K_MARKER_SOT: + { + guint len, Psot; + + GST_LOG_OBJECT (pay, "found SOT at %u", offset); + /* we found SOT but also had a header first */ + if (state->header.MHF) { + state->force_packet = TRUE; + return offset - 2; + } + + /* parse SOT but do some sanity checks first */ + len = gst_rtp_j2k_pay_header_size (data, offset); + GST_LOG_OBJECT (pay, "SOT length %u", len); + if (len < 8) + return size; + 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++; + + /* get offset of next tile, if it's 0, it 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; + else + state->next_sot = offset - 2 + Psot; + + offset += len; + GST_LOG_OBJECT (pay, "Isot %u, Psot %u, next %u", state->header.tile, + Psot, state->next_sot); + break; + } + case 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 */ + cut_sop = TRUE; + /* force a new packet when we see SOP, this can be optional but the + * spec recommends packing headers separately */ + state->force_packet = TRUE; + break; + case J2K_MARKER_EOC: + GST_LOG_OBJECT (pay, "found EOC at %u", offset); + return offset; + default: + { + guint len = gst_rtp_j2k_pay_header_size (data, offset); + GST_LOG_OBJECT (pay, "skip 0x%02x len %u", marker, len); + offset += len; + break; + } } - case J2K_MARKER_EOC: - GST_DEBUG_OBJECT (pay, "found EOC"); - return offset; - default: - offset += gst_rtp_j2k_pay_header_size (data, offset); - break; } } GST_DEBUG_OBJECT (pay, "reached end of data"); @@ -297,13 +361,14 @@ gst_rtp_j2k_pay_handle_buffer (GstBaseRTPPayload * basepayload, GstRtpJ2KPay *pay; GstClockTime timestamp; GstFlowReturn ret = GST_FLOW_ERROR; - RtpJ2KHeader j2k_header; + RtpJ2KState state; GstBufferList *list = NULL; GstBufferListIterator *it = NULL; guint8 *data; guint size; - guint mtu; + guint mtu, max_size; guint offset; + guint end, pos; pay = GST_RTP_J2K_PAY (basepayload); mtu = GST_BASE_RTP_PAYLOAD_MTU (pay); @@ -311,47 +376,87 @@ gst_rtp_j2k_pay_handle_buffer (GstBaseRTPPayload * basepayload, size = GST_BUFFER_SIZE (buffer); data = GST_BUFFER_DATA (buffer); timestamp = GST_BUFFER_TIMESTAMP (buffer); - offset = 0; + offset = pos = end = 0; GST_LOG_OBJECT (pay, "got buffer size %u, timestamp %" GST_TIME_FORMAT, size, GST_TIME_ARGS (timestamp)); /* do some header defaults first */ - j2k_header.tp = 0; /* only progressive scan */ - j2k_header.MHF = 0; /* no header */ - j2k_header.mh_id = 0; /* always 0 for now */ - j2k_header.T = 1; /* invalid tile */ - j2k_header.priority = 255; /* always 255 for now */ - j2k_header.tile = 0; /* no tile number */ - j2k_header.reserved = 0; + 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.priority = 255; /* always 255 for now */ + state.header.tile = 0; /* no tile number */ + state.header.reserved = 0; + state.bitstream = FALSE; + state.n_tiles = 0; + state.next_sot = 0; + state.force_packet = FALSE; if (pay->buffer_list) { list = gst_buffer_list_new (); it = gst_buffer_list_iterate (list); } + /* get max packet length */ + max_size = gst_rtp_buffer_calc_payload_len (mtu - HEADER_SIZE, 0, 0); + do { GstBuffer *outbuf; guint8 *header; - guint pu_size, end; + guint payload_size; + guint pu_size; - /* scan next packetization unit and fill in the header */ - end = find_pu_end (pay, data, size, offset, &j2k_header); - pu_size = end - offset; + /* try to pack as much as we can */ + do { + /* see how much we have scanned already */ + pu_size = end - offset; + GST_DEBUG_OBJECT (pay, "scanned pu size %u", pu_size); - GST_DEBUG_OBJECT (pay, "pu of size %u", pu_size); + /* we need to make a new packet */ + if (state.force_packet) { + GST_DEBUG_OBJECT (pay, "need to force a new packet"); + state.force_packet = FALSE; + pos = end; + break; + } + + /* else see if we have enough */ + if (pu_size > max_size) { + if (pos != offset) + /* the packet became too large, use previous scanpos */ + pu_size = pos - offset; + else + /* the already scanned data was already too big, make sure we start + * scanning from the last searched position */ + pos = end; + + GST_DEBUG_OBJECT (pay, "max size exceeded pu_size %u", pu_size); + break; + } + + pos = end; + /* scan next packetization unit and fill in the header */ + end = find_pu_end (pay, data, size, pos, &state); + } while (TRUE); while (pu_size > 0) { - guint packet_size, payload_size, data_size; + guint packet_size, data_size; /* calculate the packet size */ packet_size = gst_rtp_buffer_calc_packet_len (pu_size + HEADER_SIZE, 0, 0); - GST_DEBUG_OBJECT (pay, "needed packet size %u", packet_size); + if (packet_size > mtu) { + GST_DEBUG_OBJECT (pay, "needed packet size %u clamped to MTU %u", + packet_size, mtu); + packet_size = mtu; + } else { + GST_DEBUG_OBJECT (pay, "needed packet size %u fits in MTU %u", + packet_size, mtu); + } - /* make sure it fits the MTU */ - packet_size = (packet_size < mtu ? packet_size : mtu); /* get total payload size and data size */ payload_size = gst_rtp_buffer_calc_payload_len (packet_size, 0, 0); data_size = payload_size - HEADER_SIZE; @@ -371,13 +476,13 @@ gst_rtp_j2k_pay_handle_buffer (GstBaseRTPPayload * basepayload, pu_size -= data_size; if (pu_size == 0) { /* reached the end of a packetization unit */ - if (j2k_header.MHF) { + 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) - j2k_header.MHF = 3; + state.header.MHF = 3; else - j2k_header.MHF = 2; + state.header.MHF = 2; } if (end >= size) gst_rtp_buffer_set_marker (outbuf, TRUE); @@ -385,12 +490,12 @@ gst_rtp_j2k_pay_handle_buffer (GstBaseRTPPayload * basepayload, /* copy the header and push the packet */ #if (G_BYTE_ORDER == G_LITTLE_ENDIAN) - j2k_header.offset = ((offset & 0x0000FF) << 16) | + state.header.offset = ((offset & 0x0000FF) << 16) | ((offset & 0xFF0000) >> 16) | (offset & 0x00FF00); #else - j2k_header.offset = offset; + state.header.offset = offset; #endif - memcpy (header, &j2k_header, HEADER_SIZE); + memcpy (header, &state.header, HEADER_SIZE); if (pay->buffer_list) { GstBuffer *paybuf; @@ -414,13 +519,13 @@ gst_rtp_j2k_pay_handle_buffer (GstBaseRTPPayload * basepayload, } /* reset header for next round */ - j2k_header.MHF = 0; - j2k_header.T = 1; - j2k_header.tile = 0; + state.header.MHF = 0; + state.header.T = 1; + state.header.tile = 0; offset += data_size; } - offset = end; + offset = pos; } while (offset < size); done: