• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer
2  * Copyright (C) 2008 Axis Communications <dev-gstreamer@axis.com>
3  * @author Bjorn Ostby <bjorn.ostby@axis.com>
4  * @author Peter Kjellerstedt <peter.kjellerstedt@axis.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 
22 /**
23  * SECTION:element-rtpjpegpay
24  * @title: rtpjpegpay
25  *
26  * Payload encode JPEG pictures into RTP packets according to RFC 2435.
27  * For detailed information see: http://www.rfc-editor.org/rfc/rfc2435.txt
28  *
29  * The payloader takes a JPEG picture, scans the header for quantization
30  * tables (if needed) and constructs the RTP packet header followed by
31  * the actual JPEG entropy scan.
32  *
33  * The payloader assumes that correct width and height is found in the caps.
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 "gstrtpelements.h"
45 #include "gstrtpjpegpay.h"
46 #include "gstrtputils.h"
47 #include "gstbuffermemory.h"
48 
49 static GstStaticPadTemplate gst_rtp_jpeg_pay_sink_template =
50     GST_STATIC_PAD_TEMPLATE ("sink",
51     GST_PAD_SINK,
52     GST_PAD_ALWAYS,
53     GST_STATIC_CAPS ("image/jpeg; " "video/x-jpeg")
54     );
55 
56 static GstStaticPadTemplate gst_rtp_jpeg_pay_src_template =
57     GST_STATIC_PAD_TEMPLATE ("src",
58     GST_PAD_SRC,
59     GST_PAD_ALWAYS,
60     GST_STATIC_CAPS ("application/x-rtp, "
61         "  media = (string) \"video\", "
62         "  payload = (int) " GST_RTP_PAYLOAD_JPEG_STRING ", "
63         "  clock-rate = (int) 90000,   "
64         "  encoding-name = (string) \"JPEG\", "
65         "  width = (int) [ 1, 65536 ], " "  height = (int) [ 1, 65536 ]; "
66         " application/x-rtp, "
67         "  media = (string) \"video\", "
68         "  payload = (int) " GST_RTP_PAYLOAD_DYNAMIC_STRING ", "
69         "  clock-rate = (int) 90000,   "
70         "  encoding-name = (string) \"JPEG\", "
71         "  width = (int) [ 1, 65536 ], " "  height = (int) [ 1, 65536 ]")
72     );
73 
74 GST_DEBUG_CATEGORY_STATIC (rtpjpegpay_debug);
75 #define GST_CAT_DEFAULT (rtpjpegpay_debug)
76 
77 /*
78  * QUANT_PREFIX_LEN:
79  *
80  * Prefix length in the header before the quantization tables:
81  * Two size bytes and one byte for precision
82  */
83 #define QUANT_PREFIX_LEN     3
84 
85 
86 typedef enum _RtpJpegMarker RtpJpegMarker;
87 
88 /*
89  * RtpJpegMarker:
90  * @JPEG_MARKER: Prefix for JPEG marker
91  * @JPEG_MARKER_SOI: Start of Image marker
92  * @JPEG_MARKER_JFIF: JFIF marker
93  * @JPEG_MARKER_CMT: Comment marker
94  * @JPEG_MARKER_DQT: Define Quantization Table marker
95  * @JPEG_MARKER_SOF: Start of Frame marker
96  * @JPEG_MARKER_DHT: Define Huffman Table marker
97  * @JPEG_MARKER_SOS: Start of Scan marker
98  * @JPEG_MARKER_EOI: End of Image marker
99  * @JPEG_MARKER_DRI: Define Restart Interval marker
100  * @JPEG_MARKER_H264: H264 marker
101  *
102  * Identifiers for markers in JPEG header
103  */
104 enum _RtpJpegMarker
105 {
106   JPEG_MARKER = 0xFF,
107   JPEG_MARKER_SOI = 0xD8,
108   JPEG_MARKER_JFIF = 0xE0,
109   JPEG_MARKER_CMT = 0xFE,
110   JPEG_MARKER_DQT = 0xDB,
111   JPEG_MARKER_SOF = 0xC0,
112   JPEG_MARKER_DHT = 0xC4,
113   JPEG_MARKER_JPG = 0xC8,
114   JPEG_MARKER_SOS = 0xDA,
115   JPEG_MARKER_EOI = 0xD9,
116   JPEG_MARKER_DRI = 0xDD,
117   JPEG_MARKER_APP0 = 0xE0,
118   JPEG_MARKER_H264 = 0xE4,      /* APP4 */
119   JPEG_MARKER_APP15 = 0xEF,
120   JPEG_MARKER_JPG0 = 0xF0,
121   JPEG_MARKER_JPG13 = 0xFD
122 };
123 
124 #define DEFAULT_JPEG_QUANT    255
125 
126 #define DEFAULT_JPEG_QUALITY  255
127 #define DEFAULT_JPEG_TYPE     1
128 
129 enum
130 {
131   PROP_0,
132   PROP_JPEG_QUALITY,
133   PROP_JPEG_TYPE
134 };
135 
136 enum
137 {
138   Q_TABLE_0 = 0,
139   Q_TABLE_1,
140   Q_TABLE_MAX                   /* only support for two tables at the moment */
141 };
142 
143 typedef struct _RtpJpegHeader RtpJpegHeader;
144 
145 /*
146  * RtpJpegHeader:
147  * @type_spec: type specific
148  * @offset: fragment offset
149  * @type: type field
150  * @q: quantization table for this frame
151  * @width: width of image in 8-pixel multiples
152  * @height: height of image in 8-pixel multiples
153  *
154  * 0                   1                   2                   3
155  * 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
156  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
157  * | Type-specific |              Fragment Offset                  |
158  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
159  * |      Type     |       Q       |     Width     |     Height    |
160  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
161  */
162 struct _RtpJpegHeader
163 {
164   guint type_spec:8;
165   guint offset:24;
166   guint8 type;
167   guint8 q;
168   guint8 width;
169   guint8 height;
170 };
171 
172 /*
173  * RtpQuantHeader
174  * @mbz: must be zero
175  * @precision: specify size of quantization tables
176  * @length: length of quantization data
177  *
178  * 0                   1                   2                   3
179  * 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
180  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
181  * |      MBZ      |   Precision   |             Length            |
182  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
183  * |                    Quantization Table Data                    |
184  * |                              ...                              |
185  * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
186  */
187 typedef struct
188 {
189   guint8 mbz;
190   guint8 precision;
191   guint16 length;
192 } RtpQuantHeader;
193 
194 typedef struct
195 {
196   guint8 size;
197   const guint8 *data;
198 } RtpQuantTable;
199 
200 /*
201  * RtpRestartMarkerHeader:
202  * @restartInterval: number of MCUs that appear between restart markers
203  * @restartFirstLastCount: a combination of the first packet mark in the chunk
204  *                         last packet mark in the chunk and the position of the
205  *                         first restart interval in the current "chunk"
206  *
207  *    0                   1                   2                   3
208  *   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
209  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
210  *  |       Restart Interval        |F|L|       Restart Count       |
211  *  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
212  *
213  *  The restart marker header is implemented according to the following
214  *  methodology specified in section 3.1.7 of rfc2435.txt.
215  *
216  *  "If the restart intervals in a frame are not guaranteed to be aligned
217  *  with packet boundaries, the F (first) and L (last) bits MUST be set
218  *  to 1 and the Restart Count MUST be set to 0x3FFF.  This indicates
219  *  that a receiver MUST reassemble the entire frame before decoding it."
220  *
221  */
222 
223 typedef struct
224 {
225   guint16 restart_interval;
226   guint16 restart_count;
227 } RtpRestartMarkerHeader;
228 
229 typedef struct
230 {
231   guint8 id;
232   guint8 samp;
233   guint8 qt;
234 } CompInfo;
235 
236 /* FIXME: restart marker header currently unsupported */
237 
238 static void gst_rtp_jpeg_pay_set_property (GObject * object, guint prop_id,
239     const GValue * value, GParamSpec * pspec);
240 
241 static void gst_rtp_jpeg_pay_get_property (GObject * object, guint prop_id,
242     GValue * value, GParamSpec * pspec);
243 
244 static gboolean gst_rtp_jpeg_pay_setcaps (GstRTPBasePayload * basepayload,
245     GstCaps * caps);
246 
247 static GstFlowReturn gst_rtp_jpeg_pay_handle_buffer (GstRTPBasePayload * pad,
248     GstBuffer * buffer);
249 
250 #define gst_rtp_jpeg_pay_parent_class parent_class
251 G_DEFINE_TYPE (GstRtpJPEGPay, gst_rtp_jpeg_pay, GST_TYPE_RTP_BASE_PAYLOAD);
252 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (rtpjpegpay, "rtpjpegpay",
253     GST_RANK_SECONDARY, GST_TYPE_RTP_JPEG_PAY, rtp_element_init (plugin));
254 
255 static void
gst_rtp_jpeg_pay_class_init(GstRtpJPEGPayClass * klass)256 gst_rtp_jpeg_pay_class_init (GstRtpJPEGPayClass * klass)
257 {
258   GObjectClass *gobject_class;
259   GstElementClass *gstelement_class;
260   GstRTPBasePayloadClass *gstrtpbasepayload_class;
261 
262   gobject_class = (GObjectClass *) klass;
263   gstelement_class = (GstElementClass *) klass;
264   gstrtpbasepayload_class = (GstRTPBasePayloadClass *) klass;
265 
266   gobject_class->set_property = gst_rtp_jpeg_pay_set_property;
267   gobject_class->get_property = gst_rtp_jpeg_pay_get_property;
268 
269   gst_element_class_add_static_pad_template (gstelement_class,
270       &gst_rtp_jpeg_pay_src_template);
271   gst_element_class_add_static_pad_template (gstelement_class,
272       &gst_rtp_jpeg_pay_sink_template);
273 
274   gst_element_class_set_static_metadata (gstelement_class, "RTP JPEG payloader",
275       "Codec/Payloader/Network/RTP",
276       "Payload-encodes JPEG pictures into RTP packets (RFC 2435)",
277       "Axis Communications <dev-gstreamer@axis.com>");
278 
279   gstrtpbasepayload_class->set_caps = gst_rtp_jpeg_pay_setcaps;
280   gstrtpbasepayload_class->handle_buffer = gst_rtp_jpeg_pay_handle_buffer;
281 
282   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_JPEG_QUALITY,
283       g_param_spec_int ("quality", "Quality",
284           "Quality factor on JPEG data (unused)", 0, 255, 255,
285           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
286 
287   g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_JPEG_TYPE,
288       g_param_spec_int ("type", "Type",
289           "Default JPEG Type, overwritten by SOF when present", 0, 255,
290           DEFAULT_JPEG_TYPE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
291 
292   GST_DEBUG_CATEGORY_INIT (rtpjpegpay_debug, "rtpjpegpay", 0,
293       "Motion JPEG RTP Payloader");
294 }
295 
296 static void
gst_rtp_jpeg_pay_init(GstRtpJPEGPay * pay)297 gst_rtp_jpeg_pay_init (GstRtpJPEGPay * pay)
298 {
299   pay->quality = DEFAULT_JPEG_QUALITY;
300   pay->quant = DEFAULT_JPEG_QUANT;
301   pay->type = DEFAULT_JPEG_TYPE;
302   pay->width = -1;
303   pay->height = -1;
304 
305   GST_RTP_BASE_PAYLOAD_PT (pay) = GST_RTP_PAYLOAD_JPEG;
306 }
307 
308 static gboolean
gst_rtp_jpeg_pay_setcaps(GstRTPBasePayload * basepayload,GstCaps * caps)309 gst_rtp_jpeg_pay_setcaps (GstRTPBasePayload * basepayload, GstCaps * caps)
310 {
311   GstStructure *caps_structure = gst_caps_get_structure (caps, 0);
312   GstRtpJPEGPay *pay;
313   gboolean res;
314   gint width = -1, height = -1;
315   gint num = 0, denom;
316   gchar *rate = NULL;
317   gchar *dim = NULL;
318 
319   pay = GST_RTP_JPEG_PAY (basepayload);
320 
321   /* these properties are mandatory, but they might be adjusted by the SOF, if there
322    * is one. */
323   if (!gst_structure_get_int (caps_structure, "height", &height) || height <= 0) {
324     goto invalid_dimension;
325   }
326 
327   if (!gst_structure_get_int (caps_structure, "width", &width) || width <= 0) {
328     goto invalid_dimension;
329   }
330 
331   if (gst_structure_get_fraction (caps_structure, "framerate", &num, &denom) &&
332       (num < 0 || denom <= 0)) {
333     goto invalid_framerate;
334   }
335 
336   if (height > 2040 || width > 2040) {
337     pay->height = 0;
338     pay->width = 0;
339   } else {
340     pay->height = GST_ROUND_UP_8 (height) / 8;
341     pay->width = GST_ROUND_UP_8 (width) / 8;
342   }
343 
344   gst_rtp_base_payload_set_options (basepayload, "video",
345       basepayload->pt != GST_RTP_PAYLOAD_JPEG, "JPEG", 90000);
346 
347   if (num > 0) {
348     gdouble framerate;
349     gst_util_fraction_to_double (num, denom, &framerate);
350     rate = g_strdup_printf ("%f", framerate);
351   }
352 
353   if (pay->width == 0) {
354     GST_DEBUG_OBJECT (pay,
355         "width or height are greater than 2040, adding x-dimensions to caps");
356     dim = g_strdup_printf ("%d,%d", width, height);
357   }
358 
359   if (rate != NULL && dim != NULL) {
360     res = gst_rtp_base_payload_set_outcaps (basepayload, "a-framerate",
361         G_TYPE_STRING, rate, "x-dimensions", G_TYPE_STRING, dim, NULL);
362   } else if (rate != NULL && dim == NULL) {
363     res = gst_rtp_base_payload_set_outcaps (basepayload, "a-framerate",
364         G_TYPE_STRING, rate, NULL);
365   } else if (rate == NULL && dim != NULL) {
366     res = gst_rtp_base_payload_set_outcaps (basepayload, "x-dimensions",
367         G_TYPE_STRING, dim, NULL);
368   } else {
369     res = gst_rtp_base_payload_set_outcaps (basepayload, NULL);
370   }
371 
372   g_free (dim);
373   g_free (rate);
374 
375   return res;
376 
377   /* ERRORS */
378 invalid_dimension:
379   {
380     GST_ERROR_OBJECT (pay, "Invalid width/height from caps");
381     return FALSE;
382   }
383 invalid_framerate:
384   {
385     GST_ERROR_OBJECT (pay, "Invalid framerate from caps");
386     return FALSE;
387   }
388 }
389 
390 /*
391  * get uint16 value from current position in mapped memory.
392  * the memory offset will be increased with 2.
393  */
394 static guint
parse_mem_inc_offset_guint16(GstBufferMemoryMap * memory)395 parse_mem_inc_offset_guint16 (GstBufferMemoryMap * memory)
396 {
397   guint data;
398 
399   g_return_val_if_fail (memory->total_size > (memory->offset + 1), 0);
400 
401   data = ((guint) * memory->data) << 8;
402   gst_buffer_memory_advance_bytes (memory, 1);
403   data = data | (*memory->data);
404   gst_buffer_memory_advance_bytes (memory, 1);
405 
406   return data;
407 }
408 
409 /*
410  * get uint8 value from current position in mapped memory.
411  * the memory offset will be increased with 1.
412  */
413 static guint
parse_mem_inc_offset_guint8(GstBufferMemoryMap * memory)414 parse_mem_inc_offset_guint8 (GstBufferMemoryMap * memory)
415 {
416   guint data;
417 
418   g_return_val_if_fail (memory->total_size > memory->offset, 0);
419 
420   data = (*memory->data);
421   gst_buffer_memory_advance_bytes (memory, 1);
422 
423   return data;
424 }
425 
426 static void
gst_rtp_jpeg_pay_read_quant_table(GstBufferMemoryMap * memory,RtpQuantTable tables[])427 gst_rtp_jpeg_pay_read_quant_table (GstBufferMemoryMap * memory,
428     RtpQuantTable tables[])
429 {
430   guint quant_size, tab_size;
431   guint8 prec;
432   guint8 id;
433 
434   if (memory->total_size <= (memory->offset + 1))
435     goto too_small;
436 
437   quant_size = parse_mem_inc_offset_guint16 (memory);
438   if (quant_size < 2)
439     goto small_quant_size;
440 
441   /* clamp to available data */
442   if (memory->offset + quant_size > memory->total_size)
443     quant_size = memory->total_size - memory->offset;
444 
445   quant_size -= 2;
446 
447   while (quant_size > 0) {
448     guint8 data;
449     /* not enough to read the id */
450     if (memory->offset + 1 > memory->total_size)
451       break;
452 
453     data = parse_mem_inc_offset_guint8 (memory);
454     id = data & 0x0f;
455     if (id == 15)
456       /* invalid id received - corrupt data */
457       goto invalid_id;
458 
459     prec = (data & 0xf0) >> 4;
460     if (prec)
461       tab_size = 128;
462     else
463       tab_size = 64;
464 
465     /* there is not enough for the table */
466     if (quant_size < tab_size + 1)
467       goto no_table;
468 
469     GST_LOG ("read quant table %d, tab_size %d, prec %02x", id, tab_size, prec);
470 
471     tables[id].size = tab_size;
472     tables[id].data = memory->data;
473 
474     quant_size -= (tab_size + 1);
475     if (!gst_buffer_memory_advance_bytes (memory, tab_size)) {
476       goto too_small;
477     }
478   }
479 done:
480   return;
481 
482   /* ERRORS */
483 too_small:
484   {
485     GST_WARNING ("not enough data");
486     return;
487   }
488 small_quant_size:
489   {
490     GST_WARNING ("quant_size too small (%u < 2)", quant_size);
491     return;
492   }
493 invalid_id:
494   {
495     GST_WARNING ("invalid id");
496     goto done;
497   }
498 no_table:
499   {
500     GST_WARNING ("not enough data for table (%u < %u)", quant_size,
501         tab_size + 1);
502     goto done;
503   }
504 }
505 
506 static gboolean
gst_rtp_jpeg_pay_read_sof(GstRtpJPEGPay * pay,GstBufferMemoryMap * memory,CompInfo info[],RtpQuantTable tables[],gulong tables_elements)507 gst_rtp_jpeg_pay_read_sof (GstRtpJPEGPay * pay, GstBufferMemoryMap * memory,
508     CompInfo info[], RtpQuantTable tables[], gulong tables_elements)
509 {
510   guint sof_size, off;
511   guint width, height, infolen;
512   CompInfo elem;
513   gint i, j;
514 
515   off = memory->offset;
516 
517   /* we need at least 17 bytes for the SOF */
518   if (off + 17 > memory->total_size)
519     goto wrong_size;
520 
521   sof_size = parse_mem_inc_offset_guint16 (memory);
522   if (sof_size < 17)
523     goto wrong_length;
524 
525   /* precision should be 8 */
526   if (parse_mem_inc_offset_guint8 (memory) != 8)
527     goto bad_precision;
528 
529   /* read dimensions */
530   height = parse_mem_inc_offset_guint16 (memory);
531   width = parse_mem_inc_offset_guint16 (memory);
532 
533   GST_LOG_OBJECT (pay, "got dimensions %ux%u", height, width);
534 
535   if (height == 0) {
536     goto invalid_dimension;
537   }
538   if (height > 2040) {
539     height = 0;
540   }
541   if (width == 0) {
542     goto invalid_dimension;
543   }
544   if (width > 2040) {
545     width = 0;
546   }
547 
548   if (height == 0 || width == 0) {
549     pay->height = 0;
550     pay->width = 0;
551   } else {
552     pay->height = GST_ROUND_UP_8 (height) / 8;
553     pay->width = GST_ROUND_UP_8 (width) / 8;
554   }
555 
556   /* we only support 3 components */
557   if (parse_mem_inc_offset_guint8 (memory) != 3)
558     goto bad_components;
559 
560   infolen = 0;
561   for (i = 0; i < 3; i++) {
562     elem.id = parse_mem_inc_offset_guint8 (memory);
563     elem.samp = parse_mem_inc_offset_guint8 (memory);
564     elem.qt = parse_mem_inc_offset_guint8 (memory);
565     GST_LOG_OBJECT (pay, "got comp %d, samp %02x, qt %d", elem.id, elem.samp,
566         elem.qt);
567     /* insertion sort from the last element to the first */
568     for (j = infolen; j > 1; j--) {
569       if (G_LIKELY (info[j - 1].id < elem.id))
570         break;
571       info[j] = info[j - 1];
572     }
573     info[j] = elem;
574     infolen++;
575   }
576 
577   /* see that the components are supported */
578   if (info[0].samp == 0x21)
579     pay->type = 0;
580   else if (info[0].samp == 0x22)
581     pay->type = 1;
582   else
583     goto invalid_comp;
584 
585   if (!(info[1].samp == 0x11))
586     goto invalid_comp;
587 
588   if (!(info[2].samp == 0x11))
589     goto invalid_comp;
590 
591   return TRUE;
592 
593   /* ERRORS */
594 wrong_size:
595   {
596     GST_ELEMENT_WARNING (pay, STREAM, FORMAT,
597         ("Wrong size %u (needed %u).", (guint) memory->total_size, off + 17),
598         (NULL));
599     return FALSE;
600   }
601 wrong_length:
602   {
603     GST_ELEMENT_WARNING (pay, STREAM, FORMAT,
604         ("Wrong SOF length %u.", sof_size), (NULL));
605     return FALSE;
606   }
607 bad_precision:
608   {
609     GST_ELEMENT_WARNING (pay, STREAM, FORMAT,
610         ("Wrong precision, expecting 8."), (NULL));
611     return FALSE;
612   }
613 invalid_dimension:
614   {
615     GST_ELEMENT_WARNING (pay, STREAM, FORMAT,
616         ("Wrong dimension, size %ux%u", width, height), (NULL));
617     return FALSE;
618   }
619 bad_components:
620   {
621     GST_ELEMENT_WARNING (pay, STREAM, FORMAT,
622         ("Wrong number of components"), (NULL));
623     return FALSE;
624   }
625 invalid_comp:
626   {
627     GST_ELEMENT_WARNING (pay, STREAM, FORMAT, ("Invalid component"), (NULL));
628     return FALSE;
629   }
630 }
631 
632 static gboolean
gst_rtp_jpeg_pay_read_dri(GstRtpJPEGPay * pay,GstBufferMemoryMap * memory,RtpRestartMarkerHeader * dri)633 gst_rtp_jpeg_pay_read_dri (GstRtpJPEGPay * pay, GstBufferMemoryMap * memory,
634     RtpRestartMarkerHeader * dri)
635 {
636   guint dri_size, restart_interval;
637 
638   /* we need at least 4 bytes for the DRI */
639   if (memory->offset + 4 > memory->total_size)
640     goto wrong_size;
641 
642   dri_size = parse_mem_inc_offset_guint16 (memory);
643   if (dri_size < 4)
644     goto wrong_length;
645 
646   restart_interval = parse_mem_inc_offset_guint16 (memory);
647   dri->restart_interval = g_htons (restart_interval);
648   dri->restart_count = g_htons (0xFFFF);
649   if (!gst_buffer_memory_advance_bytes (memory, dri_size - 4)) {
650     goto wrong_size;
651   }
652 
653   return dri->restart_interval > 0;
654 
655 wrong_size:
656   {
657     GST_WARNING ("not enough data for DRI");
658     return FALSE;
659   }
660 wrong_length:
661   {
662     GST_WARNING ("DRI size too small (%u)", dri_size);
663     /* offset got incremented by two when dri_size was parsed. */
664     if (dri_size > 2)
665       gst_buffer_memory_advance_bytes (memory, dri_size - 2);
666     return FALSE;
667   }
668 }
669 
670 static void
gst_rtp_jpeg_pay_skipping_marker(GstBufferMemoryMap * memory)671 gst_rtp_jpeg_pay_skipping_marker (GstBufferMemoryMap * memory)
672 {
673   guint skip;
674 
675   if (G_UNLIKELY (((memory->offset + 1) >= memory->total_size))) {
676     goto wrong_size;
677   }
678   skip = parse_mem_inc_offset_guint16 (memory);
679 
680   if (G_UNLIKELY (((skip - 2 + memory->offset) > memory->total_size))) {
681     goto wrong_size;
682   }
683   if (skip > 2) {
684     gst_buffer_memory_advance_bytes (memory, skip - 2);
685   }
686   return;
687 
688 wrong_size:
689   {
690     GST_WARNING ("not enough data");
691   }
692 }
693 
694 static RtpJpegMarker
gst_rtp_jpeg_pay_scan_marker(GstBufferMemoryMap * memory)695 gst_rtp_jpeg_pay_scan_marker (GstBufferMemoryMap * memory)
696 {
697   guint8 marker = parse_mem_inc_offset_guint8 (memory);
698 
699   while (marker != JPEG_MARKER && ((memory->offset) < memory->total_size)) {
700     marker = parse_mem_inc_offset_guint8 (memory);
701   }
702 
703   if (G_UNLIKELY ((memory->offset) >= memory->total_size)) {
704     GST_LOG ("found EOI marker");
705     return JPEG_MARKER_EOI;
706   } else {
707     marker = parse_mem_inc_offset_guint8 (memory);
708     return marker;
709   }
710 }
711 
712 #define RTP_HEADER_LEN 12
713 
714 static GstFlowReturn
gst_rtp_jpeg_pay_handle_buffer(GstRTPBasePayload * basepayload,GstBuffer * buffer)715 gst_rtp_jpeg_pay_handle_buffer (GstRTPBasePayload * basepayload,
716     GstBuffer * buffer)
717 {
718   GstRtpJPEGPay *pay;
719   GstClockTime timestamp;
720   GstFlowReturn ret = GST_FLOW_ERROR;
721   RtpJpegHeader jpeg_header;
722   RtpQuantHeader quant_header;
723   RtpRestartMarkerHeader restart_marker_header;
724   RtpQuantTable tables[15] = { {0, NULL}, };
725   CompInfo info[3] = { {0,}, };
726   guint quant_data_size;
727   guint mtu, max_payload_size;
728   guint bytes_left;
729   guint jpeg_header_size = 0;
730   guint offset;
731   gboolean frame_done;
732   gboolean sos_found, sof_found, dqt_found, dri_found;
733   gint i;
734   GstBufferList *list = NULL;
735   gboolean discont;
736   GstBufferMemoryMap memory;
737 
738   pay = GST_RTP_JPEG_PAY (basepayload);
739   mtu = GST_RTP_BASE_PAYLOAD_MTU (pay);
740 
741   gst_buffer_memory_map (buffer, &memory);
742 
743   timestamp = GST_BUFFER_PTS (buffer);
744   discont = GST_BUFFER_IS_DISCONT (buffer);
745 
746   GST_LOG_OBJECT (pay, "got buffer size %" G_GSIZE_FORMAT
747       " , timestamp %" GST_TIME_FORMAT, memory.total_size,
748       GST_TIME_ARGS (timestamp));
749 
750   /* parse the jpeg header for 'start of scan' and read quant tables if needed */
751   sos_found = FALSE;
752   dqt_found = FALSE;
753   sof_found = FALSE;
754   dri_found = FALSE;
755 
756   while (!sos_found && (memory.offset < memory.total_size)) {
757     gint marker;
758 
759     GST_LOG_OBJECT (pay, "checking from offset %u", memory.offset);
760     marker = gst_rtp_jpeg_pay_scan_marker (&memory);
761     switch (marker) {
762       case JPEG_MARKER_JFIF:
763       case JPEG_MARKER_CMT:
764       case JPEG_MARKER_DHT:
765       case JPEG_MARKER_H264:
766         GST_LOG_OBJECT (pay, "skipping marker");
767         gst_rtp_jpeg_pay_skipping_marker (&memory);
768         break;
769       case JPEG_MARKER_SOF:
770         if (!gst_rtp_jpeg_pay_read_sof (pay, &memory, info, tables,
771                 G_N_ELEMENTS (tables)))
772           goto invalid_format;
773         sof_found = TRUE;
774         break;
775       case JPEG_MARKER_DQT:
776         GST_LOG ("DQT found");
777         gst_rtp_jpeg_pay_read_quant_table (&memory, tables);
778         dqt_found = TRUE;
779         break;
780       case JPEG_MARKER_SOS:
781         sos_found = TRUE;
782         GST_LOG_OBJECT (pay, "SOS found");
783         jpeg_header_size = memory.offset;
784         /* Do not re-combine into single statement with previous line! */
785         jpeg_header_size += parse_mem_inc_offset_guint16 (&memory);
786         break;
787       case JPEG_MARKER_EOI:
788         GST_WARNING_OBJECT (pay, "EOI reached before SOS!");
789         break;
790       case JPEG_MARKER_SOI:
791         GST_LOG_OBJECT (pay, "SOI found");
792         break;
793       case JPEG_MARKER_DRI:
794         GST_LOG_OBJECT (pay, "DRI found");
795         if (gst_rtp_jpeg_pay_read_dri (pay, &memory, &restart_marker_header))
796           dri_found = TRUE;
797         break;
798       default:
799         if (marker == JPEG_MARKER_JPG ||
800             (marker >= JPEG_MARKER_JPG0 && marker <= JPEG_MARKER_JPG13) ||
801             (marker >= JPEG_MARKER_APP0 && marker <= JPEG_MARKER_APP15)) {
802           GST_LOG_OBJECT (pay, "skipping marker");
803           gst_rtp_jpeg_pay_skipping_marker (&memory);
804         } else {
805           /* no need to do anything, gst_rtp_jpeg_pay_scan_marker will go on */
806           GST_FIXME_OBJECT (pay, "unhandled marker 0x%02x", marker);
807         }
808         break;
809     }
810   }
811   if (!dqt_found || !sof_found)
812     goto unsupported_jpeg;
813 
814   /* by now we should either have negotiated the width/height or the SOF header
815    * should have filled us in */
816   if (pay->width < 0 || pay->height < 0) {
817     goto no_dimension;
818   }
819 
820   GST_LOG_OBJECT (pay, "header size %u", jpeg_header_size);
821 
822   offset = 0;
823 
824   if (dri_found)
825     pay->type += 64;
826 
827   /* prepare stuff for the jpeg header */
828   jpeg_header.type_spec = 0;
829   jpeg_header.type = pay->type;
830   jpeg_header.q = pay->quant;
831   jpeg_header.width = pay->width;
832   jpeg_header.height = pay->height;
833   /* collect the quant headers sizes */
834   quant_header.mbz = 0;
835   quant_header.precision = 0;
836   quant_header.length = 0;
837   quant_data_size = 0;
838 
839   if (pay->quant > 127) {
840     /* for the Y and U component, look up the quant table and its size. quant
841      * tables for U and V should be the same */
842     for (i = 0; i < 2; i++) {
843       guint qsize;
844       guint qt;
845 
846       qt = info[i].qt;
847       if (qt >= G_N_ELEMENTS (tables))
848         goto invalid_quant;
849 
850       qsize = tables[qt].size;
851       if (qsize == 0)
852         goto invalid_quant;
853 
854       quant_header.precision |= (qsize == 64 ? 0 : (1 << i));
855       quant_data_size += qsize;
856     }
857     quant_header.length = g_htons (quant_data_size);
858     quant_data_size += sizeof (quant_header);
859   }
860 
861   GST_LOG_OBJECT (pay, "quant_data size %u", quant_data_size);
862 
863   bytes_left =
864       sizeof (jpeg_header) + quant_data_size + memory.total_size -
865       jpeg_header_size;
866 
867   if (dri_found)
868     bytes_left += sizeof (restart_marker_header);
869 
870   max_payload_size = mtu - (RTP_HEADER_LEN + sizeof (jpeg_header));
871   list = gst_buffer_list_new_sized ((bytes_left / max_payload_size) + 1);
872 
873   frame_done = FALSE;
874   do {
875     GstBuffer *outbuf;
876     guint8 *payload;
877     guint payload_size;
878     guint header_size;
879     GstBuffer *paybuf;
880     GstRTPBuffer rtp = { NULL };
881     guint rtp_header_size = gst_rtp_buffer_calc_header_len (0);
882 
883     /* The available room is the packet MTU, minus the RTP header length. */
884     payload_size =
885         (bytes_left < (mtu - rtp_header_size) ? bytes_left :
886         (mtu - rtp_header_size));
887 
888     header_size = sizeof (jpeg_header) + quant_data_size;
889     if (dri_found)
890       header_size += sizeof (restart_marker_header);
891 
892     outbuf =
893         gst_rtp_base_payload_allocate_output_buffer (basepayload, header_size,
894         0, 0);
895 
896     gst_rtp_buffer_map (outbuf, GST_MAP_WRITE, &rtp);
897 
898     if (payload_size == bytes_left) {
899       GST_LOG_OBJECT (pay, "last packet of frame");
900       frame_done = TRUE;
901       gst_rtp_buffer_set_marker (&rtp, 1);
902     }
903 
904     payload = gst_rtp_buffer_get_payload (&rtp);
905 
906     /* update offset */
907 #if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
908     jpeg_header.offset = ((offset & 0x0000FF) << 16) |
909         ((offset & 0xFF0000) >> 16) | (offset & 0x00FF00);
910 #else
911     jpeg_header.offset = offset;
912 #endif
913     memcpy (payload, &jpeg_header, sizeof (jpeg_header));
914     payload += sizeof (jpeg_header);
915     payload_size -= sizeof (jpeg_header);
916 
917     if (dri_found) {
918       memcpy (payload, &restart_marker_header, sizeof (restart_marker_header));
919       payload += sizeof (restart_marker_header);
920       payload_size -= sizeof (restart_marker_header);
921     }
922 
923     /* only send quant table with first packet */
924     if (G_UNLIKELY (quant_data_size > 0)) {
925       memcpy (payload, &quant_header, sizeof (quant_header));
926       payload += sizeof (quant_header);
927 
928       /* copy the quant tables for luma and chrominance */
929       for (i = 0; i < 2; i++) {
930         guint qsize;
931         guint qt;
932 
933         qt = info[i].qt;
934         qsize = tables[qt].size;
935         memcpy (payload, tables[qt].data, qsize);
936 
937         GST_LOG_OBJECT (pay, "component %d using quant %d, size %d", i, qt,
938             qsize);
939 
940         payload += qsize;
941       }
942       payload_size -= quant_data_size;
943       bytes_left -= quant_data_size;
944       quant_data_size = 0;
945     }
946     GST_LOG_OBJECT (pay, "sending payload size %d", payload_size);
947     gst_rtp_buffer_unmap (&rtp);
948 
949     /* create a new buf to hold the payload */
950     paybuf = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_ALL,
951         jpeg_header_size + offset, payload_size);
952 
953     /* join memory parts */
954     gst_rtp_copy_video_meta (pay, outbuf, paybuf);
955     outbuf = gst_buffer_append (outbuf, paybuf);
956 
957     GST_BUFFER_PTS (outbuf) = timestamp;
958 
959     if (discont) {
960       GST_BUFFER_FLAG_SET (outbuf, GST_BUFFER_FLAG_DISCONT);
961       /* Only the first outputted buffer has the DISCONT flag */
962       discont = FALSE;
963     }
964 
965     /* and add to list */
966     gst_buffer_list_insert (list, -1, outbuf);
967 
968     bytes_left -= payload_size;
969     offset += payload_size;
970   }
971   while (!frame_done);
972   /* push the whole buffer list at once */
973   ret = gst_rtp_base_payload_push_list (basepayload, list);
974 
975   gst_buffer_memory_unmap (&memory);
976   gst_buffer_unref (buffer);
977 
978   return ret;
979 
980   /* ERRORS */
981 unsupported_jpeg:
982   {
983     GST_ELEMENT_WARNING (pay, STREAM, FORMAT, ("Unsupported JPEG"), (NULL));
984     gst_buffer_memory_unmap (&memory);
985     gst_buffer_unref (buffer);
986     return GST_FLOW_OK;
987   }
988 no_dimension:
989   {
990     GST_ELEMENT_WARNING (pay, STREAM, FORMAT, ("No size given"), (NULL));
991     gst_buffer_memory_unmap (&memory);
992     gst_buffer_unref (buffer);
993     return GST_FLOW_OK;
994   }
995 invalid_format:
996   {
997     /* error was posted */
998     gst_buffer_memory_unmap (&memory);
999     gst_buffer_unref (buffer);
1000     return GST_FLOW_OK;
1001   }
1002 invalid_quant:
1003   {
1004     GST_ELEMENT_WARNING (pay, STREAM, FORMAT, ("Invalid quant tables"), (NULL));
1005     gst_buffer_memory_unmap (&memory);
1006     gst_buffer_unref (buffer);
1007     return GST_FLOW_OK;
1008   }
1009 }
1010 
1011 static void
gst_rtp_jpeg_pay_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1012 gst_rtp_jpeg_pay_set_property (GObject * object, guint prop_id,
1013     const GValue * value, GParamSpec * pspec)
1014 {
1015   GstRtpJPEGPay *rtpjpegpay;
1016 
1017   rtpjpegpay = GST_RTP_JPEG_PAY (object);
1018 
1019   switch (prop_id) {
1020     case PROP_JPEG_QUALITY:
1021       rtpjpegpay->quality = g_value_get_int (value);
1022       GST_DEBUG_OBJECT (object, "quality = %d", rtpjpegpay->quality);
1023       break;
1024     case PROP_JPEG_TYPE:
1025       rtpjpegpay->type = g_value_get_int (value);
1026       GST_DEBUG_OBJECT (object, "type = %d", rtpjpegpay->type);
1027       break;
1028     default:
1029       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1030       break;
1031   }
1032 }
1033 
1034 static void
gst_rtp_jpeg_pay_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1035 gst_rtp_jpeg_pay_get_property (GObject * object, guint prop_id,
1036     GValue * value, GParamSpec * pspec)
1037 {
1038   GstRtpJPEGPay *rtpjpegpay;
1039 
1040   rtpjpegpay = GST_RTP_JPEG_PAY (object);
1041 
1042   switch (prop_id) {
1043     case PROP_JPEG_QUALITY:
1044       g_value_set_int (value, rtpjpegpay->quality);
1045       break;
1046     case PROP_JPEG_TYPE:
1047       g_value_set_int (value, rtpjpegpay->type);
1048       break;
1049     default:
1050       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1051       break;
1052   }
1053 }
1054