1 /* GStreamer
2 * Copyright (C) 2009 Wim Taymans <wim.taymans@gmail.com>
3 *
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.
8 *
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.
13 *
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.
18 */
19
20 /**
21 * SECTION:element-rtpj2kpay
22 *
23 * Payload encode JPEG 2000 images into RTP packets according to RFC 5371
24 * and RFC 5372.
25 * For detailed information see: https://datatracker.ietf.org/doc/rfc5371/
26 * and https://datatracker.ietf.org/doc/rfc5372/
27 *
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.
32 *
33 *
34 */
35
36 #ifdef HAVE_CONFIG_H
37 # include "config.h"
38 #endif
39
40 #include <string.h>
41 #include <gst/rtp/gstrtpbuffer.h>
42 #include <gst/video/video.h>
43 #include "gstrtpj2kcommon.h"
44 #include "gstrtpj2kpay.h"
45 #include "gstrtputils.h"
46
47 static GstStaticPadTemplate gst_rtp_j2k_pay_sink_template =
48 GST_STATIC_PAD_TEMPLATE ("sink",
49 GST_PAD_SINK,
50 GST_PAD_ALWAYS,
51 GST_STATIC_CAPS ("image/x-jpc, " GST_RTP_J2K_SAMPLING_LIST)
52 );
53
54
55 static GstStaticPadTemplate gst_rtp_j2k_pay_src_template =
56 GST_STATIC_PAD_TEMPLATE ("src",
57 GST_PAD_SRC,
58 GST_PAD_ALWAYS,
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\"")
64 );
65
66 GST_DEBUG_CATEGORY_STATIC (rtpj2kpay_debug);
67 #define GST_CAT_DEFAULT (rtpj2kpay_debug)
68
69
70 enum
71 {
72 PROP_0,
73 PROP_LAST
74 };
75
76 typedef struct
77 {
78 guint tp:2;
79 guint MHF:2;
80 guint mh_id:3;
81 guint T:1;
82 guint priority:8;
83 guint tile:16;
84 guint offset:24;
85 } RtpJ2KHeader;
86
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);
91
92 static gboolean gst_rtp_j2k_pay_setcaps (GstRTPBasePayload * basepayload,
93 GstCaps * caps);
94
95 static GstFlowReturn gst_rtp_j2k_pay_handle_buffer (GstRTPBasePayload * pad,
96 GstBuffer * buffer);
97
98 #define gst_rtp_j2k_pay_parent_class parent_class
99 G_DEFINE_TYPE (GstRtpJ2KPay, gst_rtp_j2k_pay, GST_TYPE_RTP_BASE_PAYLOAD);
100
101 static void
gst_rtp_j2k_pay_class_init(GstRtpJ2KPayClass * klass)102 gst_rtp_j2k_pay_class_init (GstRtpJ2KPayClass * klass)
103 {
104 GObjectClass *gobject_class;
105 GstElementClass *gstelement_class;
106 GstRTPBasePayloadClass *gstrtpbasepayload_class;
107
108 gobject_class = (GObjectClass *) klass;
109 gstelement_class = (GstElementClass *) klass;
110 gstrtpbasepayload_class = (GstRTPBasePayloadClass *) klass;
111
112 gobject_class->set_property = gst_rtp_j2k_pay_set_property;
113 gobject_class->get_property = gst_rtp_j2k_pay_get_property;
114
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);
119
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>");
124
125 gstrtpbasepayload_class->set_caps = gst_rtp_j2k_pay_setcaps;
126 gstrtpbasepayload_class->handle_buffer = gst_rtp_j2k_pay_handle_buffer;
127
128 GST_DEBUG_CATEGORY_INIT (rtpj2kpay_debug, "rtpj2kpay", 0,
129 "JPEG 2000 RTP Payloader");
130 }
131
132 static void
gst_rtp_j2k_pay_init(GstRtpJ2KPay * pay)133 gst_rtp_j2k_pay_init (GstRtpJ2KPay * pay)
134 {
135 }
136
137 static gboolean
gst_rtp_j2k_pay_setcaps(GstRTPBasePayload * basepayload,GstCaps * caps)138 gst_rtp_j2k_pay_setcaps (GstRTPBasePayload * basepayload, GstCaps * caps)
139 {
140 GstStructure *caps_structure = gst_caps_get_structure (caps, 0);
141 gboolean res;
142 gint width = 0, height = 0;
143 const gchar *sampling = NULL;
144
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);
148
149
150 /* sampling is a required field */
151 sampling = gst_structure_get_string (caps_structure, "sampling");
152
153 gst_rtp_base_payload_set_options (basepayload, "video", TRUE, "JPEG2000",
154 90000);
155
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);
160 else
161 res =
162 gst_rtp_base_payload_set_outcaps (basepayload, "sampling",
163 G_TYPE_STRING, sampling, NULL);
164 return res;
165 }
166
167
168 static guint
gst_rtp_j2k_pay_header_size(const guint8 * data,guint offset)169 gst_rtp_j2k_pay_header_size (const guint8 * data, guint offset)
170 {
171 return data[offset] << 8 | data[offset + 1];
172 }
173
174
175 static GstRtpJ2KMarker
gst_rtp_j2k_pay_scan_marker(const guint8 * data,guint size,guint * offset)176 gst_rtp_j2k_pay_scan_marker (const guint8 * data, guint size, guint * offset)
177 {
178 while ((data[(*offset)++] != GST_J2K_MARKER) && ((*offset) < size));
179
180 if (G_UNLIKELY ((*offset) >= size)) {
181 return GST_J2K_MARKER_EOC;
182 } else {
183 guint8 marker = data[(*offset)++];
184 return (GstRtpJ2KMarker) marker;
185 }
186 }
187
188 typedef struct
189 {
190 RtpJ2KHeader header;
191 gboolean multi_tile;
192 gboolean bitstream;
193 guint next_sot;
194 gboolean force_packet;
195 } RtpJ2KState;
196
197
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.
202 */
203
204 static guint
find_pu_end(GstRtpJ2KPay * pay,const guint8 * data,guint size,guint offset,RtpJ2KState * state)205 find_pu_end (GstRtpJ2KPay * pay, const guint8 * data, guint size,
206 guint offset, RtpJ2KState * state)
207 {
208 gboolean cut_sop = FALSE;
209 GstRtpJ2KMarker marker;
210
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);
215
216 if (state->bitstream) {
217 /* parsing bitstream, only look for SOP */
218 switch (marker) {
219 case GST_J2K_MARKER_SOP:
220 GST_LOG_OBJECT (pay, "found SOP at %u", offset);
221 if (cut_sop)
222 return offset - 2;
223 cut_sop = TRUE;
224 break;
225 case GST_J2K_MARKER_EPH:
226 /* just skip over EPH */
227 GST_LOG_OBJECT (pay, "found EPH at %u", offset);
228 break;
229 default:
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;
237 else
238 return state->next_sot;
239 }
240 break;
241 }
242 } else {
243 switch (marker) {
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;
249 break;
250 case GST_J2K_MARKER_SOT:
251 {
252 guint len, Psot, tile;
253
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;
260 return offset - 2;
261 }
262
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);
266 if (len < 8)
267 return size;
268 if (offset + len >= size)
269 return size;
270
271 /* Isot */
272 tile = GST_READ_UINT16_BE (&data[offset + 2]);
273
274 if (!state->multi_tile) {
275 /* we have detected multiple tiles in this rtp packet : tile bit is now invalid */
276 if (state->header.T == 0 && state->header.tile != tile) {
277 state->header.T = 1;
278 state->multi_tile = TRUE;
279 } else {
280 state->header.T = 0;
281 }
282 }
283 state->header.tile = tile;
284
285 /* Note: Tile parts from multiple tiles in single RTP packet
286 will make T invalid.
287 This cannot happen in our case since we always
288 send tile headers in their own RTP packets, so we cannot mix
289 tile parts in a single RTP packet */
290
291 /* Psot: offset of next tile. If it's 0, next tile goes all the way
292 to the end of the data */
293 Psot = GST_READ_UINT32_BE (&data[offset + 4]);
294 if (Psot == 0)
295 state->next_sot = size;
296 else
297 state->next_sot = offset - 2 + Psot;
298
299 offset += len;
300 GST_LOG_OBJECT (pay, "Isot %u, Psot %u, next %u", state->header.tile,
301 Psot, state->next_sot);
302 break;
303 }
304 case GST_J2K_MARKER_SOD:
305 GST_LOG_OBJECT (pay, "found SOD at %u", offset);
306 /* go to bitstream parsing */
307 state->bitstream = TRUE;
308 /* cut at the next SOP or else include all data */
309 cut_sop = TRUE;
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;
313 break;
314 case GST_J2K_MARKER_EOC:
315 GST_LOG_OBJECT (pay, "found EOC at %u", offset);
316 return offset;
317 default:
318 {
319 guint len = gst_rtp_j2k_pay_header_size (data, offset);
320 GST_LOG_OBJECT (pay, "skip 0x%02x len %u", marker, len);
321 offset += len;
322 break;
323 }
324 }
325 }
326 }
327 GST_DEBUG_OBJECT (pay, "reached end of data");
328 return size;
329 }
330
331 static GstFlowReturn
gst_rtp_j2k_pay_handle_buffer(GstRTPBasePayload * basepayload,GstBuffer * buffer)332 gst_rtp_j2k_pay_handle_buffer (GstRTPBasePayload * basepayload,
333 GstBuffer * buffer)
334 {
335 GstRtpJ2KPay *pay;
336 GstClockTime timestamp;
337 GstFlowReturn ret = GST_FLOW_ERROR;
338 RtpJ2KState state;
339 GstBufferList *list = NULL;
340 GstMapInfo map;
341 guint mtu, max_size;
342 guint offset;
343 guint end, pos;
344
345 pay = GST_RTP_J2K_PAY (basepayload);
346 mtu = GST_RTP_BASE_PAYLOAD_MTU (pay);
347
348 gst_buffer_map (buffer, &map, GST_MAP_READ);
349 timestamp = GST_BUFFER_PTS (buffer);
350 offset = pos = end = 0;
351
352 GST_LOG_OBJECT (pay,
353 "got buffer size %" G_GSIZE_FORMAT ", timestamp %" GST_TIME_FORMAT,
354 map.size, GST_TIME_ARGS (timestamp));
355
356 /* do some header defaults first */
357 state.header.tp = 0; /* only progressive scan */
358 state.header.MHF = 0; /* no header */
359 state.header.mh_id = 0; /* always 0 for now */
360 state.header.T = 1; /* invalid tile, because we always begin with the main header */
361 state.header.priority = 255; /* always 255 for now */
362 state.header.tile = 0xffff; /* no tile number */
363 state.header.offset = 0; /* offset of 0 */
364 state.multi_tile = FALSE;
365 state.bitstream = FALSE;
366 state.next_sot = 0;
367 state.force_packet = FALSE;
368
369 /* get max packet length */
370 max_size =
371 gst_rtp_buffer_calc_payload_len (mtu - GST_RTP_J2K_HEADER_SIZE, 0, 0);
372
373 list = gst_buffer_list_new_sized ((mtu / max_size) + 1);
374
375 do {
376 GstBuffer *outbuf;
377 guint8 *header;
378 guint payload_size;
379 guint pu_size;
380 GstRTPBuffer rtp = { NULL };
381
382 /* try to pack as much as we can */
383 do {
384 /* see how much we have scanned already */
385 pu_size = end - offset;
386 GST_DEBUG_OBJECT (pay, "scanned pu size %u", pu_size);
387
388 /* we need to make a new packet */
389 if (state.force_packet) {
390 GST_DEBUG_OBJECT (pay, "need to force a new packet");
391 state.force_packet = FALSE;
392 pos = end;
393 break;
394 }
395
396 /* else see if we have enough */
397 if (pu_size > max_size) {
398 if (pos != offset)
399 /* the packet became too large, use previous scanpos */
400 pu_size = pos - offset;
401 else
402 /* the already scanned data was already too big, make sure we start
403 * scanning from the last searched position */
404 pos = end;
405
406 GST_DEBUG_OBJECT (pay, "max size exceeded pu_size %u", pu_size);
407 break;
408 }
409
410 pos = end;
411
412 /* exit when finished */
413 if (pos == map.size)
414 break;
415
416 /* scan next packetization unit and fill in the header */
417 end = find_pu_end (pay, map.data, map.size, pos, &state);
418 } while (TRUE);
419
420 while (pu_size > 0) {
421 guint packet_size, data_size;
422 GstBuffer *paybuf;
423
424 /* calculate the packet size */
425 packet_size =
426 gst_rtp_buffer_calc_packet_len (pu_size + GST_RTP_J2K_HEADER_SIZE, 0,
427 0);
428
429 if (packet_size > mtu) {
430 GST_DEBUG_OBJECT (pay, "needed packet size %u clamped to MTU %u",
431 packet_size, mtu);
432 packet_size = mtu;
433 } else {
434 GST_DEBUG_OBJECT (pay, "needed packet size %u fits in MTU %u",
435 packet_size, mtu);
436 }
437
438 /* get total payload size and data size */
439 payload_size = gst_rtp_buffer_calc_payload_len (packet_size, 0, 0);
440 data_size = payload_size - GST_RTP_J2K_HEADER_SIZE;
441
442 /* make buffer for header */
443 outbuf = gst_rtp_buffer_new_allocate (GST_RTP_J2K_HEADER_SIZE, 0, 0);
444
445 GST_BUFFER_PTS (outbuf) = timestamp;
446
447 gst_rtp_buffer_map (outbuf, GST_MAP_WRITE, &rtp);
448
449 /* get pointer to header */
450 header = gst_rtp_buffer_get_payload (&rtp);
451
452 pu_size -= data_size;
453
454 /* reached the end of a packetization unit */
455 if (pu_size == 0 && end >= map.size) {
456 gst_rtp_buffer_set_marker (&rtp, TRUE);
457 }
458 /* If we were processing a header, see if all fits in one RTP packet
459 or if we have to fragment it */
460 if (state.header.MHF) {
461 switch (state.header.MHF) {
462 case 3:
463 if (pu_size > 0)
464 state.header.MHF = 1;
465 break;
466 case 1:
467 if (pu_size == 0)
468 state.header.MHF = 2;
469 break;
470 default:
471 break;
472 }
473 }
474
475 /*
476 * RtpJ2KHeader:
477 * @tp: type (0 progressive, 1 odd field, 2 even field)
478 * @MHF: Main Header Flag
479 * @mh_id: Main Header Identification
480 * @T: Tile field invalidation flag
481 * @priority: priority
482 * @tile number: the tile number of the payload
483 * @reserved: set to 0
484 * @fragment offset: the byte offset of the current payload
485 *
486 * 0 1 2 3
487 * 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
488 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
489 * |tp |MHF|mh_id|T| priority | tile number |
490 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
491 * |reserved | fragment offset |
492 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
493 */
494 header[0] = (state.header.tp << 6) | (state.header.MHF << 4) |
495 (state.header.mh_id << 1) | state.header.T;
496 header[1] = state.header.priority;
497 header[2] = state.header.tile >> 8;
498 header[3] = state.header.tile & 0xff;
499 header[4] = 0;
500 header[5] = state.header.offset >> 16;
501 header[6] = (state.header.offset >> 8) & 0xff;
502 header[7] = state.header.offset & 0xff;
503
504 gst_rtp_buffer_unmap (&rtp);
505
506 /* make subbuffer of j2k data */
507 paybuf = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_ALL,
508 offset, data_size);
509 gst_rtp_copy_video_meta (basepayload, outbuf, paybuf);
510 outbuf = gst_buffer_append (outbuf, paybuf);
511
512 gst_buffer_list_add (list, outbuf);
513
514 /* reset multi_tile */
515 state.multi_tile = FALSE;
516
517
518 /* set MHF to zero if there is no more main header to process */
519 if (state.header.MHF & 2)
520 state.header.MHF = 0;
521
522 /* tile is valid, if there is no more header to process */
523 if (!state.header.MHF)
524 state.header.T = 0;
525
526
527 offset += data_size;
528 state.header.offset = offset;
529 }
530 offset = pos;
531 } while (offset < map.size);
532
533 gst_buffer_unmap (buffer, &map);
534 gst_buffer_unref (buffer);
535
536 /* push the whole buffer list at once */
537 ret = gst_rtp_base_payload_push_list (basepayload, list);
538
539 return ret;
540 }
541
542 static void
gst_rtp_j2k_pay_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)543 gst_rtp_j2k_pay_set_property (GObject * object, guint prop_id,
544 const GValue * value, GParamSpec * pspec)
545 {
546 switch (prop_id) {
547 default:
548 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
549 break;
550 }
551 }
552
553 static void
gst_rtp_j2k_pay_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)554 gst_rtp_j2k_pay_get_property (GObject * object, guint prop_id,
555 GValue * value, GParamSpec * pspec)
556 {
557 switch (prop_id) {
558 default:
559 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
560 break;
561 }
562 }
563
564 gboolean
gst_rtp_j2k_pay_plugin_init(GstPlugin * plugin)565 gst_rtp_j2k_pay_plugin_init (GstPlugin * plugin)
566 {
567 return gst_element_register (plugin, "rtpj2kpay", GST_RANK_SECONDARY,
568 GST_TYPE_RTP_J2K_PAY);
569 }
570