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., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
21 * SECTION:element-rtpj2kpay
23 * Payload encode JPEG 2000 images into RTP packets according to RFC 5371
25 * For detailed information see: https://datatracker.ietf.org/doc/rfc5371/
26 * and https://datatracker.ietf.org/doc/rfc5372/
28 * The payloader takes a JPEG 2000 image, scans it for "packetization
29 * units" and constructs the RTP packet header followed by the JPEG 2000
30 * codestream. A "packetization unit" is defined as either a JPEG 2000 main header,
31 * a JPEG 2000 tile-part header, or a JPEG 2000 packet.
41 #include <gst/rtp/gstrtpbuffer.h>
42 #include <gst/video/video.h>
43 #include "gstrtpj2kcommon.h"
44 #include "gstrtpj2kpay.h"
45 #include "gstrtputils.h"
47 static GstStaticPadTemplate gst_rtp_j2k_pay_sink_template =
48 GST_STATIC_PAD_TEMPLATE ("sink",
51 GST_STATIC_CAPS ("image/x-jpc, " GST_RTP_J2K_SAMPLING_LIST)
55 static GstStaticPadTemplate gst_rtp_j2k_pay_src_template =
56 GST_STATIC_PAD_TEMPLATE ("src",
59 GST_STATIC_CAPS ("application/x-rtp, "
60 " media = (string) \"video\", "
61 " payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
62 " clock-rate = (int) 90000, "
63 GST_RTP_J2K_SAMPLING_LIST "," " encoding-name = (string) \"JPEG2000\"")
66 GST_DEBUG_CATEGORY_STATIC (rtpj2kpay_debug);
67 #define GST_CAT_DEFAULT (rtpj2kpay_debug)
87 static void gst_rtp_j2k_pay_set_property (GObject * object, guint prop_id,
88 const GValue * value, GParamSpec * pspec);
89 static void gst_rtp_j2k_pay_get_property (GObject * object, guint prop_id,
90 GValue * value, GParamSpec * pspec);
92 static gboolean gst_rtp_j2k_pay_setcaps (GstRTPBasePayload * basepayload,
95 static GstFlowReturn gst_rtp_j2k_pay_handle_buffer (GstRTPBasePayload * pad,
98 #define gst_rtp_j2k_pay_parent_class parent_class
99 G_DEFINE_TYPE (GstRtpJ2KPay, gst_rtp_j2k_pay, GST_TYPE_RTP_BASE_PAYLOAD);
102 gst_rtp_j2k_pay_class_init (GstRtpJ2KPayClass * klass)
104 GObjectClass *gobject_class;
105 GstElementClass *gstelement_class;
106 GstRTPBasePayloadClass *gstrtpbasepayload_class;
108 gobject_class = (GObjectClass *) klass;
109 gstelement_class = (GstElementClass *) klass;
110 gstrtpbasepayload_class = (GstRTPBasePayloadClass *) klass;
112 gobject_class->set_property = gst_rtp_j2k_pay_set_property;
113 gobject_class->get_property = gst_rtp_j2k_pay_get_property;
115 gst_element_class_add_static_pad_template (gstelement_class,
116 &gst_rtp_j2k_pay_src_template);
117 gst_element_class_add_static_pad_template (gstelement_class,
118 &gst_rtp_j2k_pay_sink_template);
120 gst_element_class_set_static_metadata (gstelement_class,
121 "RTP JPEG 2000 payloader", "Codec/Payloader/Network/RTP",
122 "Payload-encodes JPEG 2000 pictures into RTP packets (RFC 5371)",
123 "Wim Taymans <wim.taymans@gmail.com>");
125 gstrtpbasepayload_class->set_caps = gst_rtp_j2k_pay_setcaps;
126 gstrtpbasepayload_class->handle_buffer = gst_rtp_j2k_pay_handle_buffer;
128 GST_DEBUG_CATEGORY_INIT (rtpj2kpay_debug, "rtpj2kpay", 0,
129 "JPEG 2000 RTP Payloader");
133 gst_rtp_j2k_pay_init (GstRtpJ2KPay * pay)
138 gst_rtp_j2k_pay_setcaps (GstRTPBasePayload * basepayload, GstCaps * caps)
140 GstStructure *caps_structure = gst_caps_get_structure (caps, 0);
142 gint width = 0, height = 0;
143 const gchar *sampling = NULL;
145 gboolean has_width = gst_structure_get_int (caps_structure, "width", &width);
146 gboolean has_height =
147 gst_structure_get_int (caps_structure, "height", &height);
150 /* sampling is a required field */
151 sampling = gst_structure_get_string (caps_structure, "sampling");
153 gst_rtp_base_payload_set_options (basepayload, "video", TRUE, "JPEG2000",
156 if (has_width && has_height)
157 res = gst_rtp_base_payload_set_outcaps (basepayload,
158 "sampling", G_TYPE_STRING, sampling, "width", G_TYPE_INT, width,
159 "height", G_TYPE_INT, height, NULL);
162 gst_rtp_base_payload_set_outcaps (basepayload, "sampling",
163 G_TYPE_STRING, sampling, NULL);
169 gst_rtp_j2k_pay_header_size (const guint8 * data, guint offset)
171 return data[offset] << 8 | data[offset + 1];
175 static GstRtpJ2KMarker
176 gst_rtp_j2k_pay_scan_marker (const guint8 * data, guint size, guint * offset)
178 while ((data[(*offset)++] != GST_J2K_MARKER) && ((*offset) < size));
180 if (G_UNLIKELY ((*offset) >= size)) {
181 return GST_J2K_MARKER_EOC;
183 guint8 marker = data[(*offset)++];
184 return (GstRtpJ2KMarker) marker;
191 gboolean multi_tile_part;
194 gboolean force_packet;
198 /* Note: The standard recommends that headers be put in their own RTP packets, so we follow
199 * this recommendation in the code. Also, this method groups together all J2K packets
200 * for a tile part and treats this group as a packetization unit. According to the RFC,
201 * only an individual J2K packet is considered a packetization unit.
205 find_pu_end (GstRtpJ2KPay * pay, const guint8 * data, guint size,
206 guint offset, RtpJ2KState * state)
208 gboolean cut_sop = FALSE;
209 GstRtpJ2KMarker marker;
211 /* parse the j2k header for 'start of codestream' */
212 GST_LOG_OBJECT (pay, "checking from offset %u", offset);
213 while (offset < size) {
214 marker = gst_rtp_j2k_pay_scan_marker (data, size, &offset);
216 if (state->bitstream) {
217 /* parsing bitstream, only look for SOP */
219 case GST_J2K_MARKER_SOP:
220 GST_LOG_OBJECT (pay, "found SOP at %u", offset);
225 case GST_J2K_MARKER_EPH:
226 /* just skip over EPH */
227 GST_LOG_OBJECT (pay, "found EPH at %u", offset);
230 if (offset >= state->next_sot) {
231 GST_LOG_OBJECT (pay, "reached next SOT at %u", offset);
232 state->bitstream = FALSE;
233 state->force_packet = TRUE;
234 if (marker == GST_J2K_MARKER_EOC && state->next_sot + 2 <= size)
235 /* include EOC but never go past the max size */
236 return state->next_sot + 2;
238 return state->next_sot;
244 case GST_J2K_MARKER_SOC:
245 GST_LOG_OBJECT (pay, "found SOC at %u", offset);
246 /* start off by assuming that we will fit the entire header
247 into the RTP payload */
248 state->header.MHF = 3;
250 case GST_J2K_MARKER_SOT:
252 guint len, Psot, tile;
254 GST_LOG_OBJECT (pay, "found SOT at %u", offset);
255 /* SOT for first tile part in code stream:
256 force close of current RTP packet, so that it
257 only contains main header */
258 if (state->header.MHF) {
259 state->force_packet = TRUE;
263 /* parse SOT but do some sanity checks first */
264 len = gst_rtp_j2k_pay_header_size (data, offset);
265 GST_LOG_OBJECT (pay, "SOT length %u", len);
268 if (offset + len >= size)
272 tile = GST_READ_UINT16_BE (&data[offset + 2]);
274 if (!state->multi_tile_part) {
276 /* tile is marked as valid */
279 /* we have detected multiple tile parts in this rtp packet : tile bit is now invalid */
280 if (state->header.tile != tile) {
282 state->multi_tile_part = TRUE;
285 state->header.tile = tile;
287 /* Note: Tile parts from multiple tiles in single RTP packet
289 This cannot happen in our case since we always
290 send tile headers in their own RTP packets, so we cannot mix
291 tile parts in a single RTP packet */
293 /* Psot: offset of next tile. If it's 0, next tile goes all the way
294 to the end of the data */
295 Psot = GST_READ_UINT32_BE (&data[offset + 4]);
297 state->next_sot = size;
299 state->next_sot = offset - 2 + Psot;
302 GST_LOG_OBJECT (pay, "Isot %u, Psot %u, next %u", state->header.tile,
303 Psot, state->next_sot);
306 case GST_J2K_MARKER_SOD:
307 GST_LOG_OBJECT (pay, "found SOD at %u", offset);
308 /* go to bitstream parsing */
309 state->bitstream = TRUE;
310 /* cut at the next SOP or else include all data */
312 /* force a new packet when we see SOP, this can be optional but the
313 * spec recommends packing headers separately */
314 state->force_packet = TRUE;
316 case GST_J2K_MARKER_EOC:
317 GST_LOG_OBJECT (pay, "found EOC at %u", offset);
321 guint len = gst_rtp_j2k_pay_header_size (data, offset);
322 GST_LOG_OBJECT (pay, "skip 0x%02x len %u", marker, len);
329 GST_DEBUG_OBJECT (pay, "reached end of data");
334 gst_rtp_j2k_pay_handle_buffer (GstRTPBasePayload * basepayload,
338 GstClockTime timestamp;
339 GstFlowReturn ret = GST_FLOW_ERROR;
341 GstBufferList *list = NULL;
347 pay = GST_RTP_J2K_PAY (basepayload);
348 mtu = GST_RTP_BASE_PAYLOAD_MTU (pay);
350 gst_buffer_map (buffer, &map, GST_MAP_READ);
351 timestamp = GST_BUFFER_PTS (buffer);
352 offset = pos = end = 0;
355 "got buffer size %" G_GSIZE_FORMAT ", timestamp %" GST_TIME_FORMAT,
356 map.size, 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, because we always begin with the main header */
363 state.header.priority = 255; /* always 255 for now */
364 state.header.tile = -1; /* no tile number */
365 state.header.offset = 0; /* offset of 0 */
366 state.multi_tile_part = FALSE;
367 state.bitstream = FALSE;
369 state.force_packet = FALSE;
371 /* get max packet length */
373 gst_rtp_buffer_calc_payload_len (mtu - GST_RTP_J2K_HEADER_SIZE, 0, 0);
375 list = gst_buffer_list_new_sized ((mtu / max_size) + 1);
382 GstRTPBuffer rtp = { NULL };
384 /* try to pack as much as we can */
386 /* see how much we have scanned already */
387 pu_size = end - offset;
388 GST_DEBUG_OBJECT (pay, "scanned pu size %u", pu_size);
390 /* we need to make a new packet */
391 if (state.force_packet) {
392 GST_DEBUG_OBJECT (pay, "need to force a new packet");
393 state.force_packet = FALSE;
398 /* else see if we have enough */
399 if (pu_size > max_size) {
401 /* the packet became too large, use previous scanpos */
402 pu_size = pos - offset;
404 /* the already scanned data was already too big, make sure we start
405 * scanning from the last searched position */
408 GST_DEBUG_OBJECT (pay, "max size exceeded pu_size %u", pu_size);
414 /* exit when finished */
418 /* scan next packetization unit and fill in the header */
419 end = find_pu_end (pay, map.data, map.size, pos, &state);
422 while (pu_size > 0) {
423 guint packet_size, data_size;
426 /* calculate the packet size */
428 gst_rtp_buffer_calc_packet_len (pu_size + GST_RTP_J2K_HEADER_SIZE, 0,
431 if (packet_size > mtu) {
432 GST_DEBUG_OBJECT (pay, "needed packet size %u clamped to MTU %u",
436 GST_DEBUG_OBJECT (pay, "needed packet size %u fits in MTU %u",
440 /* get total payload size and data size */
441 payload_size = gst_rtp_buffer_calc_payload_len (packet_size, 0, 0);
442 data_size = payload_size - GST_RTP_J2K_HEADER_SIZE;
444 /* make buffer for header */
445 outbuf = gst_rtp_buffer_new_allocate (GST_RTP_J2K_HEADER_SIZE, 0, 0);
447 GST_BUFFER_PTS (outbuf) = timestamp;
449 gst_rtp_buffer_map (outbuf, GST_MAP_WRITE, &rtp);
451 /* get pointer to header */
452 header = gst_rtp_buffer_get_payload (&rtp);
454 pu_size -= data_size;
456 /* reached the end of a packetization unit */
457 if (pu_size == 0 && end >= map.size) {
458 gst_rtp_buffer_set_marker (&rtp, TRUE);
460 /* If we were processing a header, see if all fits in one RTP packet
461 or if we have to fragment it */
462 if (state.header.MHF) {
463 switch (state.header.MHF) {
466 state.header.MHF = 1;
470 state.header.MHF = 2;
479 * @tp: type (0 progressive, 1 odd field, 2 even field)
480 * @MHF: Main Header Flag
481 * @mh_id: Main Header Identification
482 * @T: Tile field invalidation flag
483 * @priority: priority
484 * @tile number: the tile number of the payload
485 * @reserved: set to 0
486 * @fragment offset: the byte offset of the current payload
489 * 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
490 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
491 * |tp |MHF|mh_id|T| priority | tile number |
492 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
493 * |reserved | fragment offset |
494 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
496 header[0] = (state.header.tp << 6) | (state.header.MHF << 4) |
497 (state.header.mh_id << 1) | state.header.T;
498 header[1] = state.header.priority;
499 header[2] = state.header.tile >> 8;
500 header[3] = state.header.tile & 0xff;
502 header[5] = state.header.offset >> 16;
503 header[6] = (state.header.offset >> 8) & 0xff;
504 header[7] = state.header.offset & 0xff;
506 gst_rtp_buffer_unmap (&rtp);
508 /* make subbuffer of j2k data */
509 paybuf = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_ALL,
511 gst_rtp_copy_meta (GST_ELEMENT_CAST (basepayload), outbuf, paybuf,
512 g_quark_from_static_string (GST_META_TAG_VIDEO_STR));
513 outbuf = gst_buffer_append (outbuf, paybuf);
515 gst_buffer_list_add (list, outbuf);
517 /* reset multi_tile */
518 state.multi_tile_part = FALSE;
521 /* set MHF to zero if there is no more main header to process */
522 if (state.header.MHF & 2)
523 state.header.MHF = 0;
525 /* tile is valid, if there is no more header to process */
526 if (!state.header.MHF)
531 state.header.offset = offset;
534 } while (offset < map.size);
536 gst_buffer_unmap (buffer, &map);
537 gst_buffer_unref (buffer);
539 /* push the whole buffer list at once */
540 ret = gst_rtp_base_payload_push_list (basepayload, list);
546 gst_rtp_j2k_pay_set_property (GObject * object, guint prop_id,
547 const GValue * value, GParamSpec * pspec)
551 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
557 gst_rtp_j2k_pay_get_property (GObject * object, guint prop_id,
558 GValue * value, GParamSpec * pspec)
562 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
568 gst_rtp_j2k_pay_plugin_init (GstPlugin * plugin)
570 return gst_element_register (plugin, "rtpj2kpay", GST_RANK_SECONDARY,
571 GST_TYPE_RTP_J2K_PAY);