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