rtpj2k: update documentation
[platform/upstream/gst-plugins-good.git] / gst / rtp / gstrtpj2kpay.c
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
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")
52     );
53
54 static GstStaticPadTemplate gst_rtp_j2k_pay_src_template =
55 GST_STATIC_PAD_TEMPLATE ("src",
56     GST_PAD_SRC,
57     GST_PAD_ALWAYS,
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\"")
63     );
64
65 GST_DEBUG_CATEGORY_STATIC (rtpj2kpay_debug);
66 #define GST_CAT_DEFAULT (rtpj2kpay_debug)
67
68 /*
69  * RtpJ2KMarker:
70  * @J2K_MARKER: Prefix for JPEG 2000 marker
71  * @J2K_MARKER_SOC: Start of Codestream
72  * @J2K_MARKER_SOT: Start of tile
73  * @J2K_MARKER_EOC: End of Codestream
74  *
75  * Identifers for markers in JPEG 2000 codestreams
76  */
77 typedef enum
78 {
79   J2K_MARKER = 0xFF,
80   J2K_MARKER_SOC = 0x4F,
81   J2K_MARKER_SOT = 0x90,
82   J2K_MARKER_SOP = 0x91,
83   J2K_MARKER_EPH = 0x92,
84   J2K_MARKER_SOD = 0x93,
85   J2K_MARKER_EOC = 0xD9
86 } RtpJ2KMarker;
87
88 enum
89 {
90   PROP_0,
91   PROP_LAST
92 };
93
94 typedef struct
95 {
96   guint tp:2;
97   guint MHF:2;
98   guint mh_id:3;
99   guint T:1;
100   guint priority:8;
101   guint tile:16;
102   guint offset:24;
103 } RtpJ2KHeader;
104
105 #define HEADER_SIZE 8
106
107 static void gst_rtp_j2k_pay_set_property (GObject * object, guint prop_id,
108     const GValue * value, GParamSpec * pspec);
109 static void gst_rtp_j2k_pay_get_property (GObject * object, guint prop_id,
110     GValue * value, GParamSpec * pspec);
111
112 static gboolean gst_rtp_j2k_pay_setcaps (GstRTPBasePayload * basepayload,
113     GstCaps * caps);
114
115 static GstFlowReturn gst_rtp_j2k_pay_handle_buffer (GstRTPBasePayload * pad,
116     GstBuffer * buffer);
117
118 #define gst_rtp_j2k_pay_parent_class parent_class
119 G_DEFINE_TYPE (GstRtpJ2KPay, gst_rtp_j2k_pay, GST_TYPE_RTP_BASE_PAYLOAD);
120
121 static void
122 gst_rtp_j2k_pay_class_init (GstRtpJ2KPayClass * klass)
123 {
124   GObjectClass *gobject_class;
125   GstElementClass *gstelement_class;
126   GstRTPBasePayloadClass *gstrtpbasepayload_class;
127
128   gobject_class = (GObjectClass *) klass;
129   gstelement_class = (GstElementClass *) klass;
130   gstrtpbasepayload_class = (GstRTPBasePayloadClass *) klass;
131
132   gobject_class->set_property = gst_rtp_j2k_pay_set_property;
133   gobject_class->get_property = gst_rtp_j2k_pay_get_property;
134
135   gst_element_class_add_static_pad_template (gstelement_class,
136       &gst_rtp_j2k_pay_src_template);
137   gst_element_class_add_static_pad_template (gstelement_class,
138       &gst_rtp_j2k_pay_sink_template);
139
140   gst_element_class_set_static_metadata (gstelement_class,
141       "RTP JPEG 2000 payloader", "Codec/Payloader/Network/RTP",
142       "Payload-encodes JPEG 2000 pictures into RTP packets (RFC 5371)",
143       "Wim Taymans <wim.taymans@gmail.com>");
144
145   gstrtpbasepayload_class->set_caps = gst_rtp_j2k_pay_setcaps;
146   gstrtpbasepayload_class->handle_buffer = gst_rtp_j2k_pay_handle_buffer;
147
148   GST_DEBUG_CATEGORY_INIT (rtpj2kpay_debug, "rtpj2kpay", 0,
149       "JPEG 2000 RTP Payloader");
150 }
151
152 static void
153 gst_rtp_j2k_pay_init (GstRtpJ2KPay * pay)
154 {
155 }
156
157 static gboolean
158 gst_rtp_j2k_pay_setcaps (GstRTPBasePayload * basepayload, GstCaps * caps)
159 {
160   GstStructure *caps_structure = gst_caps_get_structure (caps, 0);
161   GstRtpJ2KPay *pay;
162   gint width = 0, height = 0;
163   gboolean res;
164
165   pay = GST_RTP_J2K_PAY (basepayload);
166
167   /* these properties are not mandatory, we can get them from the stream */
168   if (gst_structure_get_int (caps_structure, "height", &height)) {
169     pay->height = height;
170   }
171   if (gst_structure_get_int (caps_structure, "width", &width)) {
172     pay->width = width;
173   }
174
175   gst_rtp_base_payload_set_options (basepayload, "video", TRUE, "JPEG2000",
176       90000);
177   res = gst_rtp_base_payload_set_outcaps (basepayload, NULL);
178
179   return res;
180 }
181
182
183 static guint
184 gst_rtp_j2k_pay_header_size (const guint8 * data, guint offset)
185 {
186   return data[offset] << 8 | data[offset + 1];
187 }
188
189 static RtpJ2KMarker
190 gst_rtp_j2k_pay_scan_marker (const guint8 * data, guint size, guint * offset)
191 {
192   while ((data[(*offset)++] != J2K_MARKER) && ((*offset) < size));
193
194   if (G_UNLIKELY ((*offset) >= size)) {
195     return J2K_MARKER_EOC;
196   } else {
197     guint8 marker = data[(*offset)++];
198     return marker;
199   }
200 }
201
202 typedef struct
203 {
204   RtpJ2KHeader header;
205   gboolean bitstream;
206   guint n_tiles;
207   guint next_sot;
208   gboolean force_packet;
209 } RtpJ2KState;
210
211 static guint
212 find_pu_end (GstRtpJ2KPay * pay, const guint8 * data, guint size,
213     guint offset, RtpJ2KState * state)
214 {
215   gboolean cut_sop = FALSE;
216   RtpJ2KMarker marker;
217
218   /* parse the j2k header for 'start of codestream' */
219   GST_LOG_OBJECT (pay, "checking from offset %u", offset);
220   while (offset < size) {
221     marker = gst_rtp_j2k_pay_scan_marker (data, size, &offset);
222
223     if (state->bitstream) {
224       /* parsing bitstream, only look for SOP */
225       switch (marker) {
226         case J2K_MARKER_SOP:
227           GST_LOG_OBJECT (pay, "found SOP at %u", offset);
228           if (cut_sop)
229             return offset - 2;
230           cut_sop = TRUE;
231           break;
232         case J2K_MARKER_EPH:
233           /* just skip over EPH */
234           GST_LOG_OBJECT (pay, "found EPH at %u", offset);
235           break;
236         default:
237           if (offset >= state->next_sot) {
238             GST_LOG_OBJECT (pay, "reached next SOT at %u", offset);
239             state->bitstream = FALSE;
240             state->force_packet = TRUE;
241             if (marker == J2K_MARKER_EOC && state->next_sot + 2 <= size)
242               /* include EOC but never go past the max size */
243               return state->next_sot + 2;
244             else
245               return state->next_sot;
246           }
247           break;
248       }
249     } else {
250       switch (marker) {
251         case J2K_MARKER_SOC:
252           GST_LOG_OBJECT (pay, "found SOC at %u", offset);
253           state->header.MHF = 1;
254           break;
255         case J2K_MARKER_SOT:
256         {
257           guint len, Psot;
258
259           GST_LOG_OBJECT (pay, "found SOT at %u", offset);
260           /* we found SOT but also had a header first */
261           if (state->header.MHF) {
262             state->force_packet = TRUE;
263             return offset - 2;
264           }
265
266           /* parse SOT but do some sanity checks first */
267           len = gst_rtp_j2k_pay_header_size (data, offset);
268           GST_LOG_OBJECT (pay, "SOT length %u", len);
269           if (len < 8)
270             return size;
271           if (offset + len >= size)
272             return size;
273
274           if (state->n_tiles == 0)
275             /* first tile, T is valid */
276             state->header.T = 0;
277           else
278             /* more tiles, T becomes invalid */
279             state->header.T = 1;
280           state->header.tile = GST_READ_UINT16_BE (&data[offset + 2]);
281           state->n_tiles++;
282
283           /* get offset of next tile, if it's 0, it goes all the way to the end of
284            * the data */
285           Psot = GST_READ_UINT32_BE (&data[offset + 4]);
286           if (Psot == 0)
287             state->next_sot = size;
288           else
289             state->next_sot = offset - 2 + Psot;
290
291           offset += len;
292           GST_LOG_OBJECT (pay, "Isot %u, Psot %u, next %u", state->header.tile,
293               Psot, state->next_sot);
294           break;
295         }
296         case J2K_MARKER_SOD:
297           GST_LOG_OBJECT (pay, "found SOD at %u", offset);
298           /* can't have more tiles now */
299           state->n_tiles = 0;
300           /* go to bitstream parsing */
301           state->bitstream = TRUE;
302           /* cut at the next SOP or else include all data */
303           cut_sop = TRUE;
304           /* force a new packet when we see SOP, this can be optional but the
305            * spec recommends packing headers separately */
306           state->force_packet = TRUE;
307           break;
308         case J2K_MARKER_EOC:
309           GST_LOG_OBJECT (pay, "found EOC at %u", offset);
310           return offset;
311         default:
312         {
313           guint len = gst_rtp_j2k_pay_header_size (data, offset);
314           GST_LOG_OBJECT (pay, "skip 0x%02x len %u", marker, len);
315           offset += len;
316           break;
317         }
318       }
319     }
320   }
321   GST_DEBUG_OBJECT (pay, "reached end of data");
322   return size;
323 }
324
325 static GstFlowReturn
326 gst_rtp_j2k_pay_handle_buffer (GstRTPBasePayload * basepayload,
327     GstBuffer * buffer)
328 {
329   GstRtpJ2KPay *pay;
330   GstClockTime timestamp;
331   GstFlowReturn ret = GST_FLOW_ERROR;
332   RtpJ2KState state;
333   GstBufferList *list = NULL;
334   GstMapInfo map;
335   guint mtu, max_size;
336   guint offset;
337   guint end, pos;
338
339   pay = GST_RTP_J2K_PAY (basepayload);
340   mtu = GST_RTP_BASE_PAYLOAD_MTU (pay);
341
342   gst_buffer_map (buffer, &map, GST_MAP_READ);
343   timestamp = GST_BUFFER_PTS (buffer);
344   offset = pos = end = 0;
345
346   GST_LOG_OBJECT (pay,
347       "got buffer size %" G_GSIZE_FORMAT ", timestamp %" GST_TIME_FORMAT,
348       map.size, GST_TIME_ARGS (timestamp));
349
350   /* do some header defaults first */
351   state.header.tp = 0;          /* only progressive scan */
352   state.header.MHF = 0;         /* no header */
353   state.header.mh_id = 0;       /* always 0 for now */
354   state.header.T = 1;           /* invalid tile */
355   state.header.priority = 255;  /* always 255 for now */
356   state.header.tile = 0;        /* no tile number */
357   state.header.offset = 0;      /* offset of 0 */
358   state.bitstream = FALSE;
359   state.n_tiles = 0;
360   state.next_sot = 0;
361   state.force_packet = FALSE;
362
363   /* get max packet length */
364   max_size = gst_rtp_buffer_calc_payload_len (mtu - HEADER_SIZE, 0, 0);
365
366   list = gst_buffer_list_new_sized ((mtu / max_size) + 1);
367
368   do {
369     GstBuffer *outbuf;
370     guint8 *header;
371     guint payload_size;
372     guint pu_size;
373     GstRTPBuffer rtp = { NULL };
374
375     /* try to pack as much as we can */
376     do {
377       /* see how much we have scanned already */
378       pu_size = end - offset;
379       GST_DEBUG_OBJECT (pay, "scanned pu size %u", pu_size);
380
381       /* we need to make a new packet */
382       if (state.force_packet) {
383         GST_DEBUG_OBJECT (pay, "need to force a new packet");
384         state.force_packet = FALSE;
385         pos = end;
386         break;
387       }
388
389       /* else see if we have enough */
390       if (pu_size > max_size) {
391         if (pos != offset)
392           /* the packet became too large, use previous scanpos */
393           pu_size = pos - offset;
394         else
395           /* the already scanned data was already too big, make sure we start
396            * scanning from the last searched position */
397           pos = end;
398
399         GST_DEBUG_OBJECT (pay, "max size exceeded pu_size %u", pu_size);
400         break;
401       }
402
403       pos = end;
404
405       /* exit when finished */
406       if (pos == map.size)
407         break;
408
409       /* scan next packetization unit and fill in the header */
410       end = find_pu_end (pay, map.data, map.size, pos, &state);
411     } while (TRUE);
412
413     while (pu_size > 0) {
414       guint packet_size, data_size;
415       GstBuffer *paybuf;
416
417       /* calculate the packet size */
418       packet_size =
419           gst_rtp_buffer_calc_packet_len (pu_size + HEADER_SIZE, 0, 0);
420
421       if (packet_size > mtu) {
422         GST_DEBUG_OBJECT (pay, "needed packet size %u clamped to MTU %u",
423             packet_size, mtu);
424         packet_size = mtu;
425       } else {
426         GST_DEBUG_OBJECT (pay, "needed packet size %u fits in MTU %u",
427             packet_size, mtu);
428       }
429
430       /* get total payload size and data size */
431       payload_size = gst_rtp_buffer_calc_payload_len (packet_size, 0, 0);
432       data_size = payload_size - HEADER_SIZE;
433
434       /* make buffer for header */
435       outbuf = gst_rtp_buffer_new_allocate (HEADER_SIZE, 0, 0);
436
437       GST_BUFFER_PTS (outbuf) = timestamp;
438
439       gst_rtp_buffer_map (outbuf, GST_MAP_WRITE, &rtp);
440
441       /* get pointer to header */
442       header = gst_rtp_buffer_get_payload (&rtp);
443
444       pu_size -= data_size;
445       if (pu_size == 0) {
446         /* reached the end of a packetization unit */
447         if (state.header.MHF) {
448           /* we were doing a header, see if all fit in one packet or if
449            * we had to fragment it */
450           if (offset == 0)
451             state.header.MHF = 3;
452           else
453             state.header.MHF = 2;
454         }
455         if (end >= map.size)
456           gst_rtp_buffer_set_marker (&rtp, TRUE);
457       }
458
459       /*
460        * RtpJ2KHeader:
461        * @tp: type (0 progressive, 1 odd field, 2 even field)
462        * @MHF: Main Header Flag
463        * @mh_id: Main Header Identification
464        * @T: Tile field invalidation flag
465        * @priority: priority
466        * @tile number: the tile number of the payload
467        * @reserved: set to 0
468        * @fragment offset: the byte offset of the current payload
469        *
470        *  0                   1                   2                   3
471        *  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
472        * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
473        * |tp |MHF|mh_id|T|     priority  |           tile number         |
474        * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
475        * |reserved       |             fragment offset                   |
476        * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
477        */
478       header[0] = (state.header.tp << 6) | (state.header.MHF << 4) |
479           (state.header.mh_id << 1) | state.header.T;
480       header[1] = state.header.priority;
481       header[2] = state.header.tile >> 8;
482       header[3] = state.header.tile & 0xff;
483       header[4] = 0;
484       header[5] = state.header.offset >> 16;
485       header[6] = (state.header.offset >> 8) & 0xff;
486       header[7] = state.header.offset & 0xff;
487
488       gst_rtp_buffer_unmap (&rtp);
489
490       /* make subbuffer of j2k data */
491       paybuf = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_ALL,
492           offset, data_size);
493       gst_rtp_copy_meta (GST_ELEMENT_CAST (basepayload), outbuf, paybuf,
494           g_quark_from_static_string (GST_META_TAG_VIDEO_STR));
495       outbuf = gst_buffer_append (outbuf, paybuf);
496
497       gst_buffer_list_add (list, outbuf);
498
499       /* reset header for next round */
500       state.header.MHF = 0;
501       state.header.T = 1;
502       state.header.tile = 0;
503
504       offset += data_size;
505       state.header.offset = offset;
506     }
507     offset = pos;
508   } while (offset < map.size);
509
510   gst_buffer_unref (buffer);
511
512   /* push the whole buffer list at once */
513   ret = gst_rtp_base_payload_push_list (basepayload, list);
514
515   return ret;
516 }
517
518 static void
519 gst_rtp_j2k_pay_set_property (GObject * object, guint prop_id,
520     const GValue * value, GParamSpec * pspec)
521 {
522   switch (prop_id) {
523     default:
524       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
525       break;
526   }
527 }
528
529 static void
530 gst_rtp_j2k_pay_get_property (GObject * object, guint prop_id,
531     GValue * value, GParamSpec * pspec)
532 {
533   switch (prop_id) {
534     default:
535       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
536       break;
537   }
538 }
539
540 gboolean
541 gst_rtp_j2k_pay_plugin_init (GstPlugin * plugin)
542 {
543   return gst_element_register (plugin, "rtpj2kpay", GST_RANK_SECONDARY,
544       GST_TYPE_RTP_J2K_PAY);
545 }