8ce27056264d20d4ee1254218d02e08ea1b696cf
[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 #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")
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 enum
70 {
71   PROP_0,
72   PROP_LAST
73 };
74
75 typedef struct
76 {
77   guint tp:2;
78   guint MHF:2;
79   guint mh_id:3;
80   guint T:1;
81   guint priority:8;
82   guint tile:16;
83   guint offset:24;
84 } RtpJ2KHeader;
85
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);
90
91 static gboolean gst_rtp_j2k_pay_setcaps (GstRTPBasePayload * basepayload,
92     GstCaps * caps);
93
94 static GstFlowReturn gst_rtp_j2k_pay_handle_buffer (GstRTPBasePayload * pad,
95     GstBuffer * buffer);
96
97 #define gst_rtp_j2k_pay_parent_class parent_class
98 G_DEFINE_TYPE (GstRtpJ2KPay, gst_rtp_j2k_pay, GST_TYPE_RTP_BASE_PAYLOAD);
99
100 static void
101 gst_rtp_j2k_pay_class_init (GstRtpJ2KPayClass * klass)
102 {
103   GObjectClass *gobject_class;
104   GstElementClass *gstelement_class;
105   GstRTPBasePayloadClass *gstrtpbasepayload_class;
106
107   gobject_class = (GObjectClass *) klass;
108   gstelement_class = (GstElementClass *) klass;
109   gstrtpbasepayload_class = (GstRTPBasePayloadClass *) klass;
110
111   gobject_class->set_property = gst_rtp_j2k_pay_set_property;
112   gobject_class->get_property = gst_rtp_j2k_pay_get_property;
113
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);
118
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>");
123
124   gstrtpbasepayload_class->set_caps = gst_rtp_j2k_pay_setcaps;
125   gstrtpbasepayload_class->handle_buffer = gst_rtp_j2k_pay_handle_buffer;
126
127   GST_DEBUG_CATEGORY_INIT (rtpj2kpay_debug, "rtpj2kpay", 0,
128       "JPEG 2000 RTP Payloader");
129 }
130
131 static void
132 gst_rtp_j2k_pay_init (GstRtpJ2KPay * pay)
133 {
134 }
135
136 static gboolean
137 gst_rtp_j2k_pay_setcaps (GstRTPBasePayload * basepayload, GstCaps * caps)
138 {
139   GstStructure *caps_structure = gst_caps_get_structure (caps, 0);
140   GstRtpJ2KPay *pay;
141   gint width = 0, height = 0;
142   gboolean res;
143
144   pay = GST_RTP_J2K_PAY (basepayload);
145
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;
149   }
150   if (gst_structure_get_int (caps_structure, "width", &width)) {
151     pay->width = width;
152   }
153
154   gst_rtp_base_payload_set_options (basepayload, "video", TRUE, "JPEG2000",
155       90000);
156   res = gst_rtp_base_payload_set_outcaps (basepayload, NULL);
157
158   return res;
159 }
160
161
162 static guint
163 gst_rtp_j2k_pay_header_size (const guint8 * data, guint offset)
164 {
165   return data[offset] << 8 | data[offset + 1];
166 }
167
168
169 static GstRtpJ2KMarker
170 gst_rtp_j2k_pay_scan_marker (const guint8 * data, guint size, guint * offset)
171 {
172   while ((data[(*offset)++] != GST_J2K_MARKER) && ((*offset) < size));
173
174   if (G_UNLIKELY ((*offset) >= size)) {
175     return GST_J2K_MARKER_EOC;
176   } else {
177     guint8 marker = data[(*offset)++];
178     return (GstRtpJ2KMarker) marker;
179   }
180 }
181
182 typedef struct
183 {
184   RtpJ2KHeader header;
185   gboolean bitstream;
186   guint n_tiles;
187   guint next_sot;
188   gboolean force_packet;
189 } RtpJ2KState;
190
191 static guint
192 find_pu_end (GstRtpJ2KPay * pay, const guint8 * data, guint size,
193     guint offset, RtpJ2KState * state)
194 {
195   gboolean cut_sop = FALSE;
196   GstRtpJ2KMarker marker;
197
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);
202
203     if (state->bitstream) {
204       /* parsing bitstream, only look for SOP */
205       switch (marker) {
206         case GST_J2K_MARKER_SOP:
207           GST_LOG_OBJECT (pay, "found SOP at %u", offset);
208           if (cut_sop)
209             return offset - 2;
210           cut_sop = TRUE;
211           break;
212         case GST_J2K_MARKER_EPH:
213           /* just skip over EPH */
214           GST_LOG_OBJECT (pay, "found EPH at %u", offset);
215           break;
216         default:
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;
224             else
225               return state->next_sot;
226           }
227           break;
228       }
229     } else {
230       switch (marker) {
231         case GST_J2K_MARKER_SOC:
232           GST_LOG_OBJECT (pay, "found SOC at %u", offset);
233           state->header.MHF = 1;
234           break;
235         case GST_J2K_MARKER_SOT:
236         {
237           guint len, Psot;
238
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;
243             return offset - 2;
244           }
245
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);
249           if (len < 8)
250             return size;
251           if (offset + len >= size)
252             return size;
253
254           if (state->n_tiles == 0)
255             /* first tile, T is valid */
256             state->header.T = 0;
257           else
258             /* more tiles, T becomes invalid */
259             state->header.T = 1;
260           state->header.tile = GST_READ_UINT16_BE (&data[offset + 2]);
261           state->n_tiles++;
262
263           /* get offset of next tile, if it's 0, it goes all the way to the end of
264            * the data */
265           Psot = GST_READ_UINT32_BE (&data[offset + 4]);
266           if (Psot == 0)
267             state->next_sot = size;
268           else
269             state->next_sot = offset - 2 + Psot;
270
271           offset += len;
272           GST_LOG_OBJECT (pay, "Isot %u, Psot %u, next %u", state->header.tile,
273               Psot, state->next_sot);
274           break;
275         }
276         case GST_J2K_MARKER_SOD:
277           GST_LOG_OBJECT (pay, "found SOD at %u", offset);
278           /* can't have more tiles now */
279           state->n_tiles = 0;
280           /* go to bitstream parsing */
281           state->bitstream = TRUE;
282           /* cut at the next SOP or else include all data */
283           cut_sop = TRUE;
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;
287           break;
288         case GST_J2K_MARKER_EOC:
289           GST_LOG_OBJECT (pay, "found EOC at %u", offset);
290           return offset;
291         default:
292         {
293           guint len = gst_rtp_j2k_pay_header_size (data, offset);
294           GST_LOG_OBJECT (pay, "skip 0x%02x len %u", marker, len);
295           offset += len;
296           break;
297         }
298       }
299     }
300   }
301   GST_DEBUG_OBJECT (pay, "reached end of data");
302   return size;
303 }
304
305 static GstFlowReturn
306 gst_rtp_j2k_pay_handle_buffer (GstRTPBasePayload * basepayload,
307     GstBuffer * buffer)
308 {
309   GstRtpJ2KPay *pay;
310   GstClockTime timestamp;
311   GstFlowReturn ret = GST_FLOW_ERROR;
312   RtpJ2KState state;
313   GstBufferList *list = NULL;
314   GstMapInfo map;
315   guint mtu, max_size;
316   guint offset;
317   guint end, pos;
318
319   pay = GST_RTP_J2K_PAY (basepayload);
320   mtu = GST_RTP_BASE_PAYLOAD_MTU (pay);
321
322   gst_buffer_map (buffer, &map, GST_MAP_READ);
323   timestamp = GST_BUFFER_PTS (buffer);
324   offset = pos = end = 0;
325
326   GST_LOG_OBJECT (pay,
327       "got buffer size %" G_GSIZE_FORMAT ", timestamp %" GST_TIME_FORMAT,
328       map.size, GST_TIME_ARGS (timestamp));
329
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;
339   state.n_tiles = 0;
340   state.next_sot = 0;
341   state.force_packet = FALSE;
342
343   /* get max packet length */
344   max_size =
345       gst_rtp_buffer_calc_payload_len (mtu - GST_RTP_J2K_HEADER_SIZE, 0, 0);
346
347   list = gst_buffer_list_new_sized ((mtu / max_size) + 1);
348
349   do {
350     GstBuffer *outbuf;
351     guint8 *header;
352     guint payload_size;
353     guint pu_size;
354     GstRTPBuffer rtp = { NULL };
355
356     /* try to pack as much as we can */
357     do {
358       /* see how much we have scanned already */
359       pu_size = end - offset;
360       GST_DEBUG_OBJECT (pay, "scanned pu size %u", pu_size);
361
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;
366         pos = end;
367         break;
368       }
369
370       /* else see if we have enough */
371       if (pu_size > max_size) {
372         if (pos != offset)
373           /* the packet became too large, use previous scanpos */
374           pu_size = pos - offset;
375         else
376           /* the already scanned data was already too big, make sure we start
377            * scanning from the last searched position */
378           pos = end;
379
380         GST_DEBUG_OBJECT (pay, "max size exceeded pu_size %u", pu_size);
381         break;
382       }
383
384       pos = end;
385
386       /* exit when finished */
387       if (pos == map.size)
388         break;
389
390       /* scan next packetization unit and fill in the header */
391       end = find_pu_end (pay, map.data, map.size, pos, &state);
392     } while (TRUE);
393
394     while (pu_size > 0) {
395       guint packet_size, data_size;
396       GstBuffer *paybuf;
397
398       /* calculate the packet size */
399       packet_size =
400           gst_rtp_buffer_calc_packet_len (pu_size + GST_RTP_J2K_HEADER_SIZE, 0,
401           0);
402
403       if (packet_size > mtu) {
404         GST_DEBUG_OBJECT (pay, "needed packet size %u clamped to MTU %u",
405             packet_size, mtu);
406         packet_size = mtu;
407       } else {
408         GST_DEBUG_OBJECT (pay, "needed packet size %u fits in MTU %u",
409             packet_size, mtu);
410       }
411
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;
415
416       /* make buffer for header */
417       outbuf = gst_rtp_buffer_new_allocate (GST_RTP_J2K_HEADER_SIZE, 0, 0);
418
419       GST_BUFFER_PTS (outbuf) = timestamp;
420
421       gst_rtp_buffer_map (outbuf, GST_MAP_WRITE, &rtp);
422
423       /* get pointer to header */
424       header = gst_rtp_buffer_get_payload (&rtp);
425
426       pu_size -= data_size;
427       if (pu_size == 0) {
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 */
432           if (offset == 0)
433             state.header.MHF = 3;
434           else
435             state.header.MHF = 2;
436         }
437         if (end >= map.size)
438           gst_rtp_buffer_set_marker (&rtp, TRUE);
439       }
440
441       /*
442        * RtpJ2KHeader:
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
451        *
452        *  0                   1                   2                   3
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        * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
459        */
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;
465       header[4] = 0;
466       header[5] = state.header.offset >> 16;
467       header[6] = (state.header.offset >> 8) & 0xff;
468       header[7] = state.header.offset & 0xff;
469
470       gst_rtp_buffer_unmap (&rtp);
471
472       /* make subbuffer of j2k data */
473       paybuf = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_ALL,
474           offset, data_size);
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);
478
479       gst_buffer_list_add (list, outbuf);
480
481       /* reset header for next round */
482       state.header.MHF = 0;
483       state.header.T = 1;
484       state.header.tile = 0;
485
486       offset += data_size;
487       state.header.offset = offset;
488     }
489     offset = pos;
490   } while (offset < map.size);
491
492   gst_buffer_unref (buffer);
493
494   /* push the whole buffer list at once */
495   ret = gst_rtp_base_payload_push_list (basepayload, list);
496
497   return ret;
498 }
499
500 static void
501 gst_rtp_j2k_pay_set_property (GObject * object, guint prop_id,
502     const GValue * value, GParamSpec * pspec)
503 {
504   switch (prop_id) {
505     default:
506       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
507       break;
508   }
509 }
510
511 static void
512 gst_rtp_j2k_pay_get_property (GObject * object, guint prop_id,
513     GValue * value, GParamSpec * pspec)
514 {
515   switch (prop_id) {
516     default:
517       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
518       break;
519   }
520 }
521
522 gboolean
523 gst_rtp_j2k_pay_plugin_init (GstPlugin * plugin)
524 {
525   return gst_element_register (plugin, "rtpj2kpay", GST_RANK_SECONDARY,
526       GST_TYPE_RTP_J2K_PAY);
527 }