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")
54 static GstStaticPadTemplate gst_rtp_j2k_pay_src_template =
55 GST_STATIC_PAD_TEMPLATE ("src",
58 GST_STATIC_CAPS ("application/x-rtp, "
59 " media = (string) \"video\", "
60 " payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
61 " clock-rate = (int) 90000, "
62 " encoding-name = (string) \"JPEG2000\"")
65 GST_DEBUG_CATEGORY_STATIC (rtpj2kpay_debug);
66 #define GST_CAT_DEFAULT (rtpj2kpay_debug)
86 static void gst_rtp_j2k_pay_set_property (GObject * object, guint prop_id,
87 const GValue * value, GParamSpec * pspec);
88 static void gst_rtp_j2k_pay_get_property (GObject * object, guint prop_id,
89 GValue * value, GParamSpec * pspec);
91 static gboolean gst_rtp_j2k_pay_setcaps (GstRTPBasePayload * basepayload,
94 static GstFlowReturn gst_rtp_j2k_pay_handle_buffer (GstRTPBasePayload * pad,
97 #define gst_rtp_j2k_pay_parent_class parent_class
98 G_DEFINE_TYPE (GstRtpJ2KPay, gst_rtp_j2k_pay, GST_TYPE_RTP_BASE_PAYLOAD);
101 gst_rtp_j2k_pay_class_init (GstRtpJ2KPayClass * klass)
103 GObjectClass *gobject_class;
104 GstElementClass *gstelement_class;
105 GstRTPBasePayloadClass *gstrtpbasepayload_class;
107 gobject_class = (GObjectClass *) klass;
108 gstelement_class = (GstElementClass *) klass;
109 gstrtpbasepayload_class = (GstRTPBasePayloadClass *) klass;
111 gobject_class->set_property = gst_rtp_j2k_pay_set_property;
112 gobject_class->get_property = gst_rtp_j2k_pay_get_property;
114 gst_element_class_add_static_pad_template (gstelement_class,
115 &gst_rtp_j2k_pay_src_template);
116 gst_element_class_add_static_pad_template (gstelement_class,
117 &gst_rtp_j2k_pay_sink_template);
119 gst_element_class_set_static_metadata (gstelement_class,
120 "RTP JPEG 2000 payloader", "Codec/Payloader/Network/RTP",
121 "Payload-encodes JPEG 2000 pictures into RTP packets (RFC 5371)",
122 "Wim Taymans <wim.taymans@gmail.com>");
124 gstrtpbasepayload_class->set_caps = gst_rtp_j2k_pay_setcaps;
125 gstrtpbasepayload_class->handle_buffer = gst_rtp_j2k_pay_handle_buffer;
127 GST_DEBUG_CATEGORY_INIT (rtpj2kpay_debug, "rtpj2kpay", 0,
128 "JPEG 2000 RTP Payloader");
132 gst_rtp_j2k_pay_init (GstRtpJ2KPay * pay)
137 gst_rtp_j2k_pay_setcaps (GstRTPBasePayload * basepayload, GstCaps * caps)
139 GstStructure *caps_structure = gst_caps_get_structure (caps, 0);
141 gint width = 0, height = 0;
144 pay = GST_RTP_J2K_PAY (basepayload);
146 /* these properties are not mandatory, we can get them from the stream */
147 if (gst_structure_get_int (caps_structure, "height", &height)) {
148 pay->height = height;
150 if (gst_structure_get_int (caps_structure, "width", &width)) {
154 gst_rtp_base_payload_set_options (basepayload, "video", TRUE, "JPEG2000",
156 res = gst_rtp_base_payload_set_outcaps (basepayload, NULL);
163 gst_rtp_j2k_pay_header_size (const guint8 * data, guint offset)
165 return data[offset] << 8 | data[offset + 1];
169 static GstRtpJ2KMarker
170 gst_rtp_j2k_pay_scan_marker (const guint8 * data, guint size, guint * offset)
172 while ((data[(*offset)++] != GST_J2K_MARKER) && ((*offset) < size));
174 if (G_UNLIKELY ((*offset) >= size)) {
175 return GST_J2K_MARKER_EOC;
177 guint8 marker = data[(*offset)++];
178 return (GstRtpJ2KMarker) marker;
188 gboolean force_packet;
192 find_pu_end (GstRtpJ2KPay * pay, const guint8 * data, guint size,
193 guint offset, RtpJ2KState * state)
195 gboolean cut_sop = FALSE;
196 GstRtpJ2KMarker marker;
198 /* parse the j2k header for 'start of codestream' */
199 GST_LOG_OBJECT (pay, "checking from offset %u", offset);
200 while (offset < size) {
201 marker = gst_rtp_j2k_pay_scan_marker (data, size, &offset);
203 if (state->bitstream) {
204 /* parsing bitstream, only look for SOP */
206 case GST_J2K_MARKER_SOP:
207 GST_LOG_OBJECT (pay, "found SOP at %u", offset);
212 case GST_J2K_MARKER_EPH:
213 /* just skip over EPH */
214 GST_LOG_OBJECT (pay, "found EPH at %u", offset);
217 if (offset >= state->next_sot) {
218 GST_LOG_OBJECT (pay, "reached next SOT at %u", offset);
219 state->bitstream = FALSE;
220 state->force_packet = TRUE;
221 if (marker == GST_J2K_MARKER_EOC && state->next_sot + 2 <= size)
222 /* include EOC but never go past the max size */
223 return state->next_sot + 2;
225 return state->next_sot;
231 case GST_J2K_MARKER_SOC:
232 GST_LOG_OBJECT (pay, "found SOC at %u", offset);
233 state->header.MHF = 1;
235 case GST_J2K_MARKER_SOT:
239 GST_LOG_OBJECT (pay, "found SOT at %u", offset);
240 /* we found SOT but also had a header first */
241 if (state->header.MHF) {
242 state->force_packet = TRUE;
246 /* parse SOT but do some sanity checks first */
247 len = gst_rtp_j2k_pay_header_size (data, offset);
248 GST_LOG_OBJECT (pay, "SOT length %u", len);
251 if (offset + len >= size)
254 if (state->n_tiles == 0)
255 /* first tile, T is valid */
258 /* more tiles, T becomes invalid */
260 state->header.tile = GST_READ_UINT16_BE (&data[offset + 2]);
263 /* get offset of next tile, if it's 0, it goes all the way to the end of
265 Psot = GST_READ_UINT32_BE (&data[offset + 4]);
267 state->next_sot = size;
269 state->next_sot = offset - 2 + Psot;
272 GST_LOG_OBJECT (pay, "Isot %u, Psot %u, next %u", state->header.tile,
273 Psot, state->next_sot);
276 case GST_J2K_MARKER_SOD:
277 GST_LOG_OBJECT (pay, "found SOD at %u", offset);
278 /* can't have more tiles now */
280 /* go to bitstream parsing */
281 state->bitstream = TRUE;
282 /* cut at the next SOP or else include all data */
284 /* force a new packet when we see SOP, this can be optional but the
285 * spec recommends packing headers separately */
286 state->force_packet = TRUE;
288 case GST_J2K_MARKER_EOC:
289 GST_LOG_OBJECT (pay, "found EOC at %u", offset);
293 guint len = gst_rtp_j2k_pay_header_size (data, offset);
294 GST_LOG_OBJECT (pay, "skip 0x%02x len %u", marker, len);
301 GST_DEBUG_OBJECT (pay, "reached end of data");
306 gst_rtp_j2k_pay_handle_buffer (GstRTPBasePayload * basepayload,
310 GstClockTime timestamp;
311 GstFlowReturn ret = GST_FLOW_ERROR;
313 GstBufferList *list = NULL;
319 pay = GST_RTP_J2K_PAY (basepayload);
320 mtu = GST_RTP_BASE_PAYLOAD_MTU (pay);
322 gst_buffer_map (buffer, &map, GST_MAP_READ);
323 timestamp = GST_BUFFER_PTS (buffer);
324 offset = pos = end = 0;
327 "got buffer size %" G_GSIZE_FORMAT ", timestamp %" GST_TIME_FORMAT,
328 map.size, GST_TIME_ARGS (timestamp));
330 /* do some header defaults first */
331 state.header.tp = 0; /* only progressive scan */
332 state.header.MHF = 0; /* no header */
333 state.header.mh_id = 0; /* always 0 for now */
334 state.header.T = 1; /* invalid tile */
335 state.header.priority = 255; /* always 255 for now */
336 state.header.tile = 0; /* no tile number */
337 state.header.offset = 0; /* offset of 0 */
338 state.bitstream = FALSE;
341 state.force_packet = FALSE;
343 /* get max packet length */
345 gst_rtp_buffer_calc_payload_len (mtu - GST_RTP_J2K_HEADER_SIZE, 0, 0);
347 list = gst_buffer_list_new_sized ((mtu / max_size) + 1);
354 GstRTPBuffer rtp = { NULL };
356 /* try to pack as much as we can */
358 /* see how much we have scanned already */
359 pu_size = end - offset;
360 GST_DEBUG_OBJECT (pay, "scanned pu size %u", pu_size);
362 /* we need to make a new packet */
363 if (state.force_packet) {
364 GST_DEBUG_OBJECT (pay, "need to force a new packet");
365 state.force_packet = FALSE;
370 /* else see if we have enough */
371 if (pu_size > max_size) {
373 /* the packet became too large, use previous scanpos */
374 pu_size = pos - offset;
376 /* the already scanned data was already too big, make sure we start
377 * scanning from the last searched position */
380 GST_DEBUG_OBJECT (pay, "max size exceeded pu_size %u", pu_size);
386 /* exit when finished */
390 /* scan next packetization unit and fill in the header */
391 end = find_pu_end (pay, map.data, map.size, pos, &state);
394 while (pu_size > 0) {
395 guint packet_size, data_size;
398 /* calculate the packet size */
400 gst_rtp_buffer_calc_packet_len (pu_size + GST_RTP_J2K_HEADER_SIZE, 0,
403 if (packet_size > mtu) {
404 GST_DEBUG_OBJECT (pay, "needed packet size %u clamped to MTU %u",
408 GST_DEBUG_OBJECT (pay, "needed packet size %u fits in MTU %u",
412 /* get total payload size and data size */
413 payload_size = gst_rtp_buffer_calc_payload_len (packet_size, 0, 0);
414 data_size = payload_size - GST_RTP_J2K_HEADER_SIZE;
416 /* make buffer for header */
417 outbuf = gst_rtp_buffer_new_allocate (GST_RTP_J2K_HEADER_SIZE, 0, 0);
419 GST_BUFFER_PTS (outbuf) = timestamp;
421 gst_rtp_buffer_map (outbuf, GST_MAP_WRITE, &rtp);
423 /* get pointer to header */
424 header = gst_rtp_buffer_get_payload (&rtp);
426 pu_size -= data_size;
428 /* reached the end of a packetization unit */
429 if (state.header.MHF) {
430 /* we were doing a header, see if all fit in one packet or if
431 * we had to fragment it */
433 state.header.MHF = 3;
435 state.header.MHF = 2;
438 gst_rtp_buffer_set_marker (&rtp, TRUE);
443 * @tp: type (0 progressive, 1 odd field, 2 even field)
444 * @MHF: Main Header Flag
445 * @mh_id: Main Header Identification
446 * @T: Tile field invalidation flag
447 * @priority: priority
448 * @tile number: the tile number of the payload
449 * @reserved: set to 0
450 * @fragment offset: the byte offset of the current payload
453 * 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
454 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
455 * |tp |MHF|mh_id|T| priority | tile number |
456 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
457 * |reserved | fragment offset |
458 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
460 header[0] = (state.header.tp << 6) | (state.header.MHF << 4) |
461 (state.header.mh_id << 1) | state.header.T;
462 header[1] = state.header.priority;
463 header[2] = state.header.tile >> 8;
464 header[3] = state.header.tile & 0xff;
466 header[5] = state.header.offset >> 16;
467 header[6] = (state.header.offset >> 8) & 0xff;
468 header[7] = state.header.offset & 0xff;
470 gst_rtp_buffer_unmap (&rtp);
472 /* make subbuffer of j2k data */
473 paybuf = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_ALL,
475 gst_rtp_copy_meta (GST_ELEMENT_CAST (basepayload), outbuf, paybuf,
476 g_quark_from_static_string (GST_META_TAG_VIDEO_STR));
477 outbuf = gst_buffer_append (outbuf, paybuf);
479 gst_buffer_list_add (list, outbuf);
481 /* reset header for next round */
482 state.header.MHF = 0;
484 state.header.tile = 0;
487 state.header.offset = offset;
490 } while (offset < map.size);
492 gst_buffer_unref (buffer);
494 /* push the whole buffer list at once */
495 ret = gst_rtp_base_payload_push_list (basepayload, list);
501 gst_rtp_j2k_pay_set_property (GObject * object, guint prop_id,
502 const GValue * value, GParamSpec * pspec)
506 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
512 gst_rtp_j2k_pay_get_property (GObject * object, guint prop_id,
513 GValue * value, GParamSpec * pspec)
517 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
523 gst_rtp_j2k_pay_plugin_init (GstPlugin * plugin)
525 return gst_element_register (plugin, "rtpj2kpay", GST_RANK_SECONDARY,
526 GST_TYPE_RTP_J2K_PAY);