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.
This commit is contained in:
Wim Taymans 2010-12-02 17:53:42 +01:00
parent 29363d6068
commit 005e27fa79

View file

@ -72,6 +72,8 @@ typedef enum
J2K_MARKER = 0xFF, J2K_MARKER = 0xFF,
J2K_MARKER_SOC = 0x4F, J2K_MARKER_SOC = 0x4F,
J2K_MARKER_SOT = 0x90, J2K_MARKER_SOT = 0x90,
J2K_MARKER_SOP = 0x91,
J2K_MARKER_SOD = 0x93,
J2K_MARKER_EOC = 0xD9 J2K_MARKER_EOC = 0xD9
} RtpJ2KMarker; } RtpJ2KMarker;
@ -226,66 +228,128 @@ gst_rtp_j2k_pay_scan_marker (const guint8 * data, guint size, guint * offset)
while ((data[(*offset)++] != J2K_MARKER) && ((*offset) < size)); while ((data[(*offset)++] != J2K_MARKER) && ((*offset) < size));
if (G_UNLIKELY ((*offset) >= size)) { if (G_UNLIKELY ((*offset) >= size)) {
GST_LOG ("end of data, return EOC");
return J2K_MARKER_EOC; return J2K_MARKER_EOC;
} else { } else {
guint8 marker = data[(*offset)++]; guint8 marker = data[(*offset)++];
GST_LOG ("found %02x marker", marker);
return marker; return marker;
} }
} }
typedef struct
{
RtpJ2KHeader header;
gboolean bitstream;
guint n_tiles;
guint next_sot;
gboolean force_packet;
} RtpJ2KState;
static guint static guint
find_pu_end (GstRtpJ2KPay * pay, const guint8 * data, guint size, 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' */ /* parse the j2k header for 'start of codestream' */
while (offset < size) {
GST_LOG_OBJECT (pay, "checking from offset %u", offset); GST_LOG_OBJECT (pay, "checking from offset %u", offset);
switch (gst_rtp_j2k_pay_scan_marker (data, size, &offset)) { while (offset < size) {
marker = gst_rtp_j2k_pay_scan_marker (data, size, &offset);
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: case J2K_MARKER_SOC:
GST_DEBUG_OBJECT (pay, "found SOC at %u", offset); GST_LOG_OBJECT (pay, "found SOC at %u", offset);
header->MHF = 1; state->header.MHF = 1;
break; break;
case J2K_MARKER_SOT: case J2K_MARKER_SOT:
{ {
guint len, Psot; guint len, Psot;
GST_DEBUG_OBJECT (pay, "found SOT at %u", offset); GST_LOG_OBJECT (pay, "found SOT at %u", offset);
/* we found SOT but also had a header first */ /* we found SOT but also had a header first */
if (header->MHF) if (state->header.MHF) {
state->force_packet = TRUE;
return offset - 2; return offset - 2;
}
/* parse SOT but do some sanity checks first */ /* parse SOT but do some sanity checks first */
len = gst_rtp_j2k_pay_header_size (data, offset); len = gst_rtp_j2k_pay_header_size (data, offset);
GST_DEBUG_OBJECT (pay, "SOT length %u", len); GST_LOG_OBJECT (pay, "SOT length %u", len);
if (len < 8) if (len < 8)
return size; return size;
if (offset + len >= size) if (offset + len >= size)
return size; return size;
/* we have a valid tile number now, keep it in the header */ if (state->n_tiles == 0)
header->T = 0; /* first tile, T is valid */
header->tile = GST_READ_UINT16_BE (&data[offset + 2]); 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 /* get offset of next tile, if it's 0, it goes all the way to the end of
* the data */ * the data */
Psot = GST_READ_UINT32_BE (&data[offset + 4]); Psot = GST_READ_UINT32_BE (&data[offset + 4]);
if (Psot == 0) if (Psot == 0)
offset = size; state->next_sot = size;
else else
offset += Psot; state->next_sot = offset - 2 + Psot;
GST_DEBUG_OBJECT (pay, "Isot %u, Psot %u", header->tile, Psot);
offset += len;
GST_LOG_OBJECT (pay, "Isot %u, Psot %u, next %u", state->header.tile,
Psot, state->next_sot);
break; 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: case J2K_MARKER_EOC:
GST_DEBUG_OBJECT (pay, "found EOC"); GST_LOG_OBJECT (pay, "found EOC at %u", offset);
return offset; return offset;
default: default:
offset += gst_rtp_j2k_pay_header_size (data, offset); {
guint len = gst_rtp_j2k_pay_header_size (data, offset);
GST_LOG_OBJECT (pay, "skip 0x%02x len %u", marker, len);
offset += len;
break; break;
} }
} }
}
}
GST_DEBUG_OBJECT (pay, "reached end of data"); GST_DEBUG_OBJECT (pay, "reached end of data");
return size; return size;
} }
@ -297,13 +361,14 @@ gst_rtp_j2k_pay_handle_buffer (GstBaseRTPPayload * basepayload,
GstRtpJ2KPay *pay; GstRtpJ2KPay *pay;
GstClockTime timestamp; GstClockTime timestamp;
GstFlowReturn ret = GST_FLOW_ERROR; GstFlowReturn ret = GST_FLOW_ERROR;
RtpJ2KHeader j2k_header; RtpJ2KState state;
GstBufferList *list = NULL; GstBufferList *list = NULL;
GstBufferListIterator *it = NULL; GstBufferListIterator *it = NULL;
guint8 *data; guint8 *data;
guint size; guint size;
guint mtu; guint mtu, max_size;
guint offset; guint offset;
guint end, pos;
pay = GST_RTP_J2K_PAY (basepayload); pay = GST_RTP_J2K_PAY (basepayload);
mtu = GST_BASE_RTP_PAYLOAD_MTU (pay); mtu = GST_BASE_RTP_PAYLOAD_MTU (pay);
@ -311,47 +376,87 @@ gst_rtp_j2k_pay_handle_buffer (GstBaseRTPPayload * basepayload,
size = GST_BUFFER_SIZE (buffer); size = GST_BUFFER_SIZE (buffer);
data = GST_BUFFER_DATA (buffer); data = GST_BUFFER_DATA (buffer);
timestamp = GST_BUFFER_TIMESTAMP (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_LOG_OBJECT (pay, "got buffer size %u, timestamp %" GST_TIME_FORMAT, size,
GST_TIME_ARGS (timestamp)); GST_TIME_ARGS (timestamp));
/* do some header defaults first */ /* do some header defaults first */
j2k_header.tp = 0; /* only progressive scan */ state.header.tp = 0; /* only progressive scan */
j2k_header.MHF = 0; /* no header */ state.header.MHF = 0; /* no header */
j2k_header.mh_id = 0; /* always 0 for now */ state.header.mh_id = 0; /* always 0 for now */
j2k_header.T = 1; /* invalid tile */ state.header.T = 1; /* invalid tile */
j2k_header.priority = 255; /* always 255 for now */ state.header.priority = 255; /* always 255 for now */
j2k_header.tile = 0; /* no tile number */ state.header.tile = 0; /* no tile number */
j2k_header.reserved = 0; state.header.reserved = 0;
state.bitstream = FALSE;
state.n_tiles = 0;
state.next_sot = 0;
state.force_packet = FALSE;
if (pay->buffer_list) { if (pay->buffer_list) {
list = gst_buffer_list_new (); list = gst_buffer_list_new ();
it = gst_buffer_list_iterate (list); it = gst_buffer_list_iterate (list);
} }
/* get max packet length */
max_size = gst_rtp_buffer_calc_payload_len (mtu - HEADER_SIZE, 0, 0);
do { do {
GstBuffer *outbuf; GstBuffer *outbuf;
guint8 *header; guint8 *header;
guint pu_size, end; guint payload_size;
guint pu_size;
/* scan next packetization unit and fill in the header */ /* try to pack as much as we can */
end = find_pu_end (pay, data, size, offset, &j2k_header); do {
/* see how much we have scanned already */
pu_size = end - offset; 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) { while (pu_size > 0) {
guint packet_size, payload_size, data_size; guint packet_size, data_size;
/* calculate the packet size */ /* calculate the packet size */
packet_size = packet_size =
gst_rtp_buffer_calc_packet_len (pu_size + HEADER_SIZE, 0, 0); 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 */ /* get total payload size and data size */
payload_size = gst_rtp_buffer_calc_payload_len (packet_size, 0, 0); payload_size = gst_rtp_buffer_calc_payload_len (packet_size, 0, 0);
data_size = payload_size - HEADER_SIZE; data_size = payload_size - HEADER_SIZE;
@ -371,13 +476,13 @@ gst_rtp_j2k_pay_handle_buffer (GstBaseRTPPayload * basepayload,
pu_size -= data_size; pu_size -= data_size;
if (pu_size == 0) { if (pu_size == 0) {
/* reached the end of a packetization unit */ /* 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 were doing a header, see if all fit in one packet or if
* we had to fragment it */ * we had to fragment it */
if (offset == 0) if (offset == 0)
j2k_header.MHF = 3; state.header.MHF = 3;
else else
j2k_header.MHF = 2; state.header.MHF = 2;
} }
if (end >= size) if (end >= size)
gst_rtp_buffer_set_marker (outbuf, TRUE); 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 */ /* copy the header and push the packet */
#if (G_BYTE_ORDER == G_LITTLE_ENDIAN) #if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
j2k_header.offset = ((offset & 0x0000FF) << 16) | state.header.offset = ((offset & 0x0000FF) << 16) |
((offset & 0xFF0000) >> 16) | (offset & 0x00FF00); ((offset & 0xFF0000) >> 16) | (offset & 0x00FF00);
#else #else
j2k_header.offset = offset; state.header.offset = offset;
#endif #endif
memcpy (header, &j2k_header, HEADER_SIZE); memcpy (header, &state.header, HEADER_SIZE);
if (pay->buffer_list) { if (pay->buffer_list) {
GstBuffer *paybuf; GstBuffer *paybuf;
@ -414,13 +519,13 @@ gst_rtp_j2k_pay_handle_buffer (GstBaseRTPPayload * basepayload,
} }
/* reset header for next round */ /* reset header for next round */
j2k_header.MHF = 0; state.header.MHF = 0;
j2k_header.T = 1; state.header.T = 1;
j2k_header.tile = 0; state.header.tile = 0;
offset += data_size; offset += data_size;
} }
offset = end; offset = pos;
} while (offset < size); } while (offset < size);
done: done: