2 * Copyright (C) 2009 Wim Taymans <wim.taymans@gmail.com>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
21 * SECTION:element-rtpj2kpay
23 * Payload encode JPEG 2000 pictures into RTP packets according to RFC 5371.
24 * For detailed information see: http://www.rfc-editor.org/rfc/rfc5371.txt
26 * The payloader takes a JPEG 2000 picture, scans the header for packetization
27 * units and constructs the RTP packet header followed by the actual JPEG 2000
36 #include <gst/rtp/gstrtpbuffer.h>
38 #include "gstrtpj2kpay.h"
40 static GstStaticPadTemplate gst_rtp_j2k_pay_sink_template =
41 GST_STATIC_PAD_TEMPLATE ("sink",
44 GST_STATIC_CAPS ("image/x-jpc")
47 static GstStaticPadTemplate gst_rtp_j2k_pay_src_template =
48 GST_STATIC_PAD_TEMPLATE ("src",
51 GST_STATIC_CAPS ("application/x-rtp, "
52 " media = (string) \"video\", "
53 " payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
54 " clock-rate = (int) 90000, "
55 " encoding-name = (string) \"JPEG2000\"")
58 GST_DEBUG_CATEGORY_STATIC (rtpj2kpay_debug);
59 #define GST_CAT_DEFAULT (rtpj2kpay_debug)
63 * @J2K_MARKER: Prefix for JPEG 2000 marker
64 * @J2K_MARKER_SOC: Start of Codestream
65 * @J2K_MARKER_SOT: Start of tile
66 * @J2K_MARKER_EOC: End of Codestream
68 * Identifers for markers in JPEG 2000 codestreams
73 J2K_MARKER_SOC = 0x4F,
74 J2K_MARKER_SOT = 0x90,
75 J2K_MARKER_SOP = 0x91,
76 J2K_MARKER_EPH = 0x92,
77 J2K_MARKER_SOD = 0x93,
81 #define DEFAULT_BUFFER_LIST TRUE
101 #define HEADER_SIZE 8
103 static void gst_rtp_j2k_pay_set_property (GObject * object, guint prop_id,
104 const GValue * value, GParamSpec * pspec);
105 static void gst_rtp_j2k_pay_get_property (GObject * object, guint prop_id,
106 GValue * value, GParamSpec * pspec);
108 static gboolean gst_rtp_j2k_pay_setcaps (GstBaseRTPPayload * basepayload,
111 static GstFlowReturn gst_rtp_j2k_pay_handle_buffer (GstBaseRTPPayload * pad,
114 GST_BOILERPLATE (GstRtpJ2KPay, gst_rtp_j2k_pay, GstBaseRTPPayload,
115 GST_TYPE_BASE_RTP_PAYLOAD);
118 gst_rtp_j2k_pay_base_init (gpointer klass)
120 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
122 gst_element_class_add_static_pad_template (element_class,
123 &gst_rtp_j2k_pay_src_template);
124 gst_element_class_add_static_pad_template (element_class,
125 &gst_rtp_j2k_pay_sink_template);
127 gst_element_class_set_details_simple (element_class,
128 "RTP JPEG 2000 payloader", "Codec/Payloader/Network/RTP",
129 "Payload-encodes JPEG 2000 pictures into RTP packets (RFC 5371)",
130 "Wim Taymans <wim.taymans@gmail.com>");
134 gst_rtp_j2k_pay_class_init (GstRtpJ2KPayClass * klass)
136 GObjectClass *gobject_class;
137 GstBaseRTPPayloadClass *gstbasertppayload_class;
139 gobject_class = (GObjectClass *) klass;
140 gstbasertppayload_class = (GstBaseRTPPayloadClass *) klass;
142 gobject_class->set_property = gst_rtp_j2k_pay_set_property;
143 gobject_class->get_property = gst_rtp_j2k_pay_get_property;
145 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BUFFER_LIST,
146 g_param_spec_boolean ("buffer-list", "Buffer List",
148 DEFAULT_BUFFER_LIST, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
150 gstbasertppayload_class->set_caps = gst_rtp_j2k_pay_setcaps;
151 gstbasertppayload_class->handle_buffer = gst_rtp_j2k_pay_handle_buffer;
153 GST_DEBUG_CATEGORY_INIT (rtpj2kpay_debug, "rtpj2kpay", 0,
154 "JPEG 2000 RTP Payloader");
158 gst_rtp_j2k_pay_init (GstRtpJ2KPay * pay, GstRtpJ2KPayClass * klass)
160 pay->buffer_list = DEFAULT_BUFFER_LIST;
164 gst_rtp_j2k_pay_setcaps (GstBaseRTPPayload * basepayload, GstCaps * caps)
166 GstStructure *caps_structure = gst_caps_get_structure (caps, 0);
168 gint width = 0, height = 0;
171 pay = GST_RTP_J2K_PAY (basepayload);
173 /* these properties are not mandatory, we can get them from the stream */
174 if (gst_structure_get_int (caps_structure, "height", &height)) {
175 pay->height = height;
177 if (gst_structure_get_int (caps_structure, "width", &width)) {
181 gst_basertppayload_set_options (basepayload, "video", TRUE, "JPEG2000",
183 res = gst_basertppayload_set_outcaps (basepayload, NULL);
190 gst_rtp_j2k_pay_header_size (const guint8 * data, guint offset)
192 return data[offset] << 8 | data[offset + 1];
196 gst_rtp_j2k_pay_scan_marker (const guint8 * data, guint size, guint * offset)
198 while ((data[(*offset)++] != J2K_MARKER) && ((*offset) < size));
200 if (G_UNLIKELY ((*offset) >= size)) {
201 return J2K_MARKER_EOC;
203 guint8 marker = data[(*offset)++];
214 gboolean force_packet;
218 find_pu_end (GstRtpJ2KPay * pay, const guint8 * data, guint size,
219 guint offset, RtpJ2KState * state)
221 gboolean cut_sop = FALSE;
224 /* parse the j2k header for 'start of codestream' */
225 GST_LOG_OBJECT (pay, "checking from offset %u", offset);
226 while (offset < size) {
227 marker = gst_rtp_j2k_pay_scan_marker (data, size, &offset);
229 if (state->bitstream) {
230 /* parsing bitstream, only look for SOP */
233 GST_LOG_OBJECT (pay, "found SOP at %u", offset);
239 /* just skip over EPH */
240 GST_LOG_OBJECT (pay, "found EPH at %u", offset);
243 if (offset >= state->next_sot) {
244 GST_LOG_OBJECT (pay, "reached next SOT at %u", offset);
245 state->bitstream = FALSE;
246 state->force_packet = TRUE;
247 if (marker == J2K_MARKER_EOC && state->next_sot + 2 <= size)
248 /* include EOC but never go past the max size */
249 return state->next_sot + 2;
251 return state->next_sot;
258 GST_LOG_OBJECT (pay, "found SOC at %u", offset);
259 state->header.MHF = 1;
265 GST_LOG_OBJECT (pay, "found SOT at %u", offset);
266 /* we found SOT but also had a header first */
267 if (state->header.MHF) {
268 state->force_packet = TRUE;
272 /* parse SOT but do some sanity checks first */
273 len = gst_rtp_j2k_pay_header_size (data, offset);
274 GST_LOG_OBJECT (pay, "SOT length %u", len);
277 if (offset + len >= size)
280 if (state->n_tiles == 0)
281 /* first tile, T is valid */
284 /* more tiles, T becomes invalid */
286 state->header.tile = GST_READ_UINT16_BE (&data[offset + 2]);
289 /* get offset of next tile, if it's 0, it goes all the way to the end of
291 Psot = GST_READ_UINT32_BE (&data[offset + 4]);
293 state->next_sot = size;
295 state->next_sot = offset - 2 + Psot;
298 GST_LOG_OBJECT (pay, "Isot %u, Psot %u, next %u", state->header.tile,
299 Psot, state->next_sot);
303 GST_LOG_OBJECT (pay, "found SOD at %u", offset);
304 /* can't have more tiles now */
306 /* go to bitstream parsing */
307 state->bitstream = TRUE;
308 /* cut at the next SOP or else include all data */
310 /* force a new packet when we see SOP, this can be optional but the
311 * spec recommends packing headers separately */
312 state->force_packet = TRUE;
315 GST_LOG_OBJECT (pay, "found EOC at %u", offset);
319 guint len = gst_rtp_j2k_pay_header_size (data, offset);
320 GST_LOG_OBJECT (pay, "skip 0x%02x len %u", marker, len);
327 GST_DEBUG_OBJECT (pay, "reached end of data");
332 gst_rtp_j2k_pay_handle_buffer (GstBaseRTPPayload * basepayload,
336 GstClockTime timestamp;
337 GstFlowReturn ret = GST_FLOW_ERROR;
339 GstBufferList *list = NULL;
340 GstBufferListIterator *it = NULL;
347 pay = GST_RTP_J2K_PAY (basepayload);
348 mtu = GST_BASE_RTP_PAYLOAD_MTU (pay);
350 size = GST_BUFFER_SIZE (buffer);
351 data = GST_BUFFER_DATA (buffer);
352 timestamp = GST_BUFFER_TIMESTAMP (buffer);
353 offset = pos = end = 0;
355 GST_LOG_OBJECT (pay, "got buffer size %u, timestamp %" GST_TIME_FORMAT, size,
356 GST_TIME_ARGS (timestamp));
358 /* do some header defaults first */
359 state.header.tp = 0; /* only progressive scan */
360 state.header.MHF = 0; /* no header */
361 state.header.mh_id = 0; /* always 0 for now */
362 state.header.T = 1; /* invalid tile */
363 state.header.priority = 255; /* always 255 for now */
364 state.header.tile = 0; /* no tile number */
365 state.header.offset = 0; /* offset of 0 */
366 state.bitstream = FALSE;
369 state.force_packet = FALSE;
371 if (pay->buffer_list) {
372 list = gst_buffer_list_new ();
373 it = gst_buffer_list_iterate (list);
376 /* get max packet length */
377 max_size = gst_rtp_buffer_calc_payload_len (mtu - HEADER_SIZE, 0, 0);
385 /* try to pack as much as we can */
387 /* see how much we have scanned already */
388 pu_size = end - offset;
389 GST_DEBUG_OBJECT (pay, "scanned pu size %u", pu_size);
391 /* we need to make a new packet */
392 if (state.force_packet) {
393 GST_DEBUG_OBJECT (pay, "need to force a new packet");
394 state.force_packet = FALSE;
399 /* else see if we have enough */
400 if (pu_size > max_size) {
402 /* the packet became too large, use previous scanpos */
403 pu_size = pos - offset;
405 /* the already scanned data was already too big, make sure we start
406 * scanning from the last searched position */
409 GST_DEBUG_OBJECT (pay, "max size exceeded pu_size %u", pu_size);
415 /* exit when finished */
419 /* scan next packetization unit and fill in the header */
420 end = find_pu_end (pay, data, size, pos, &state);
423 while (pu_size > 0) {
424 guint packet_size, data_size;
426 /* calculate the packet size */
428 gst_rtp_buffer_calc_packet_len (pu_size + HEADER_SIZE, 0, 0);
430 if (packet_size > mtu) {
431 GST_DEBUG_OBJECT (pay, "needed packet size %u clamped to MTU %u",
435 GST_DEBUG_OBJECT (pay, "needed packet size %u fits in MTU %u",
439 /* get total payload size and data size */
440 payload_size = gst_rtp_buffer_calc_payload_len (packet_size, 0, 0);
441 data_size = payload_size - HEADER_SIZE;
443 if (pay->buffer_list) {
444 /* make buffer for header */
445 outbuf = gst_rtp_buffer_new_allocate (HEADER_SIZE, 0, 0);
447 /* make buffer for header and data */
448 outbuf = gst_rtp_buffer_new_allocate (payload_size, 0, 0);
450 GST_BUFFER_TIMESTAMP (outbuf) = timestamp;
452 /* get pointer to header */
453 header = gst_rtp_buffer_get_payload (outbuf);
455 pu_size -= data_size;
457 /* reached the end of a packetization unit */
458 if (state.header.MHF) {
459 /* we were doing a header, see if all fit in one packet or if
460 * we had to fragment it */
462 state.header.MHF = 3;
464 state.header.MHF = 2;
467 gst_rtp_buffer_set_marker (outbuf, TRUE);
472 * @tp: type (0 progressive, 1 odd field, 2 even field)
473 * @MHF: Main Header Flag
474 * @mh_id: Main Header Identification
475 * @T: Tile field invalidation flag
476 * @priority: priority
477 * @tile number: the tile number of the payload
478 * @reserved: set to 0
479 * @fragment offset: the byte offset of the current payload
482 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
483 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
484 * |tp |MHF|mh_id|T| priority | tile number |
485 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
486 * |reserved | fragment offset |
487 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
489 header[0] = (state.header.tp << 6) | (state.header.MHF << 4) |
490 (state.header.mh_id << 1) | state.header.T;
491 header[1] = state.header.priority;
492 header[2] = state.header.tile >> 8;
493 header[3] = state.header.tile & 0xff;
495 header[5] = state.header.offset >> 16;
496 header[6] = (state.header.offset >> 8) & 0xff;
497 header[7] = state.header.offset & 0xff;
499 if (pay->buffer_list) {
502 /* make subbuffer of j2k data */
503 paybuf = gst_buffer_create_sub (buffer, offset, data_size);
505 /* create a new group to hold the header and the payload */
506 gst_buffer_list_iterator_add_group (it);
508 /* add both buffers to the buffer list */
509 gst_buffer_list_iterator_add (it, outbuf);
510 gst_buffer_list_iterator_add (it, paybuf);
513 memcpy (header + HEADER_SIZE, &data[offset], data_size);
515 ret = gst_basertppayload_push (basepayload, outbuf);
516 if (ret != GST_FLOW_OK)
520 /* reset header for next round */
521 state.header.MHF = 0;
523 state.header.tile = 0;
528 } while (offset < size);
531 gst_buffer_unref (buffer);
533 if (pay->buffer_list) {
534 /* free iterator and push the whole buffer list at once */
535 gst_buffer_list_iterator_free (it);
536 ret = gst_basertppayload_push_list (basepayload, list);
543 gst_rtp_j2k_pay_set_property (GObject * object, guint prop_id,
544 const GValue * value, GParamSpec * pspec)
546 GstRtpJ2KPay *rtpj2kpay;
548 rtpj2kpay = GST_RTP_J2K_PAY (object);
551 case PROP_BUFFER_LIST:
552 rtpj2kpay->buffer_list = g_value_get_boolean (value);
555 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
561 gst_rtp_j2k_pay_get_property (GObject * object, guint prop_id,
562 GValue * value, GParamSpec * pspec)
564 GstRtpJ2KPay *rtpj2kpay;
566 rtpj2kpay = GST_RTP_J2K_PAY (object);
569 case PROP_BUFFER_LIST:
570 g_value_set_boolean (value, rtpj2kpay->buffer_list);
573 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
579 gst_rtp_j2k_pay_plugin_init (GstPlugin * plugin)
581 return gst_element_register (plugin, "rtpj2kpay", GST_RANK_SECONDARY,
582 GST_TYPE_RTP_J2K_PAY);