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>
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.
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.
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., 59 Temple Place - Suite 330,
19 * Boston, MA 02111-1307, USA.
23 * SECTION:element-rtpjpegpay
25 * Payload encode JPEG pictures into RTP packets according to RFC 2435.
26 * For detailed information see: http://www.rfc-editor.org/rfc/rfc2435.txt
28 * The payloader takes a JPEG picture, scans the header for quantization
29 * tables (if needed) and constructs the RTP packet header followed by
30 * the actual JPEG entropy scan.
32 * The payloader assumes that correct width and height is found in the caps.
40 #include <gst/rtp/gstrtpbuffer.h>
42 #include "gstrtpjpegpay.h"
44 static GstStaticPadTemplate gst_rtp_jpeg_pay_sink_template =
45 GST_STATIC_PAD_TEMPLATE ("sink",
48 GST_STATIC_CAPS ("image/jpeg; " "video/x-jpeg")
51 static GstStaticPadTemplate gst_rtp_jpeg_pay_src_template =
52 GST_STATIC_PAD_TEMPLATE ("src",
55 GST_STATIC_CAPS ("application/x-rtp, "
56 " media = (string) \"video\", "
57 " payload = (int) 26 , "
58 " clock-rate = (int) 90000, " " encoding-name = (string) \"JPEG\"")
61 GST_DEBUG_CATEGORY_STATIC (rtpjpegpay_debug);
62 #define GST_CAT_DEFAULT (rtpjpegpay_debug)
67 * Prefix length in the header before the quantization tables:
68 * Two size bytes and one byte for precision
70 #define QUANT_PREFIX_LEN 3
73 * DEFAULT_BUFFER_LIST:
76 #define DEFAULT_BUFFER_LIST FALSE
78 typedef enum _RtpJpegMarker RtpJpegMarker;
82 * @JPEG_MARKER: Prefix for JPEG marker
83 * @JPEG_MARKER_SOI: Start of Image marker
84 * @JPEG_MARKER_JFIF: JFIF marker
85 * @JPEG_MARKER_CMT: Comment marker
86 * @JPEG_MARKER_DQT: Define Quantization Table marker
87 * @JPEG_MARKER_SOF: Start of Frame marker
88 * @JPEG_MARKER_DHT: Define Huffman Table marker
89 * @JPEG_MARKER_SOS: Start of Scan marker
90 * @JPEG_MARKER_EOI: End of Image marker
92 * Identifers for markers in JPEG header
97 JPEG_MARKER_SOI = 0xD8,
98 JPEG_MARKER_JFIF = 0xE0,
99 JPEG_MARKER_CMT = 0xFE,
100 JPEG_MARKER_DQT = 0xDB,
101 JPEG_MARKER_SOF = 0xC0,
102 JPEG_MARKER_DHT = 0xC4,
103 JPEG_MARKER_SOS = 0xDA,
104 JPEG_MARKER_EOI = 0xD9,
107 #define DEFAULT_JPEG_QUANT 255
109 #define DEFAULT_JPEG_QUALITY 255
110 #define DEFAULT_JPEG_TYPE 1
125 Q_TABLE_MAX /* only support for two tables at the moment */
128 typedef struct _RtpJpegHeader RtpJpegHeader;
132 * @type_spec: type specific
133 * @offset: fragment offset
135 * @q: quantization table for this frame
136 * @width: width of image in 8-pixel multiples
137 * @height: height of image in 8-pixel multiples
140 * 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
141 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
142 * | Type-specific | Fragment Offset |
143 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
144 * | Type | Q | Width | Height |
145 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
147 struct _RtpJpegHeader
160 * @precision: specify size of quantization tables
161 * @length: length of quantization data
164 * 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
165 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
166 * | MBZ | Precision | Length |
167 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
168 * | Quantization Table Data |
170 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
192 /* FIXME: restart marker header currently unsupported */
194 static void gst_rtp_jpeg_pay_set_property (GObject * object, guint prop_id,
195 const GValue * value, GParamSpec * pspec);
197 static void gst_rtp_jpeg_pay_get_property (GObject * object, guint prop_id,
198 GValue * value, GParamSpec * pspec);
200 static gboolean gst_rtp_jpeg_pay_setcaps (GstBaseRTPPayload * basepayload,
203 static GstFlowReturn gst_rtp_jpeg_pay_handle_buffer (GstBaseRTPPayload * pad,
206 GST_BOILERPLATE (GstRtpJPEGPay, gst_rtp_jpeg_pay, GstBaseRTPPayload,
207 GST_TYPE_BASE_RTP_PAYLOAD);
210 gst_rtp_jpeg_pay_base_init (gpointer klass)
212 GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
214 gst_element_class_add_pad_template (element_class,
215 gst_static_pad_template_get (&gst_rtp_jpeg_pay_src_template));
216 gst_element_class_add_pad_template (element_class,
217 gst_static_pad_template_get (&gst_rtp_jpeg_pay_sink_template));
219 gst_element_class_set_details_simple (element_class, "RTP JPEG payloader",
220 "Codec/Payloader/Network",
221 "Payload-encodes JPEG pictures into RTP packets (RFC 2435)",
222 "Axis Communications <dev-gstreamer@axis.com>");
226 gst_rtp_jpeg_pay_class_init (GstRtpJPEGPayClass * klass)
228 GObjectClass *gobject_class;
229 GstBaseRTPPayloadClass *gstbasertppayload_class;
231 gobject_class = (GObjectClass *) klass;
232 gstbasertppayload_class = (GstBaseRTPPayloadClass *) klass;
234 gobject_class->set_property = gst_rtp_jpeg_pay_set_property;
235 gobject_class->get_property = gst_rtp_jpeg_pay_get_property;
237 gstbasertppayload_class->set_caps = gst_rtp_jpeg_pay_setcaps;
238 gstbasertppayload_class->handle_buffer = gst_rtp_jpeg_pay_handle_buffer;
240 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_JPEG_QUALITY,
241 g_param_spec_int ("quality", "Quality",
242 "Quality factor on JPEG data (unused)", 0, 255, 255,
245 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_JPEG_TYPE,
246 g_param_spec_int ("type", "Type",
247 "Default JPEG Type, overwritten by SOF when present", 0, 255,
248 DEFAULT_JPEG_TYPE, G_PARAM_READWRITE));
250 g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_BUFFER_LIST,
251 g_param_spec_boolean ("buffer-list", "Buffer List",
253 DEFAULT_BUFFER_LIST, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
255 GST_DEBUG_CATEGORY_INIT (rtpjpegpay_debug, "rtpjpegpay", 0,
256 "Motion JPEG RTP Payloader");
260 gst_rtp_jpeg_pay_init (GstRtpJPEGPay * pay, GstRtpJPEGPayClass * klass)
262 pay->quality = DEFAULT_JPEG_QUALITY;
263 pay->quant = DEFAULT_JPEG_QUANT;
264 pay->type = DEFAULT_JPEG_TYPE;
265 pay->buffer_list = DEFAULT_BUFFER_LIST;
269 gst_rtp_jpeg_pay_setcaps (GstBaseRTPPayload * basepayload, GstCaps * caps)
271 GstStructure *caps_structure = gst_caps_get_structure (caps, 0);
274 gint width = 0, height = 0;
276 pay = GST_RTP_JPEG_PAY (basepayload);
278 /* these properties are not mandatory, we can get them from the SOF, if there
280 if (gst_structure_get_int (caps_structure, "height", &height)) {
281 if (height <= 0 || height > 2040)
282 goto invalid_dimension;
284 pay->height = height / 8;
286 if (gst_structure_get_int (caps_structure, "width", &width)) {
287 if (width <= 0 || width > 2040)
288 goto invalid_dimension;
290 pay->width = width / 8;
292 gst_basertppayload_set_options (basepayload, "video", TRUE, "JPEG", 90000);
293 res = gst_basertppayload_set_outcaps (basepayload, NULL);
300 GST_ERROR_OBJECT (pay, "Invalid width/height from caps");
306 gst_rtp_jpeg_pay_header_size (const guint8 * data, guint offset)
308 return data[offset] << 8 | data[offset + 1];
312 gst_rtp_jpeg_pay_read_quant_table (const guint8 * data, guint size,
313 guint offset, RtpQuantTable tables[])
315 guint quant_size, tab_size;
319 if (offset + 2 > size)
322 quant_size = gst_rtp_jpeg_pay_header_size (data, offset);
326 /* clamp to available data */
327 if (offset + quant_size > size)
328 quant_size = size - offset;
333 while (quant_size > 0) {
334 /* not enough to read the id */
335 if (offset + 1 > size)
338 id = data[offset] & 0xf;
340 /* invalid id received - corrupt data */
343 prec = (data[offset] & 0xf0) >> 4;
349 /* there is not enough for the table */
350 if (quant_size < tab_size + 1)
353 GST_LOG ("read quant table %d, tab_size %d, prec %02x", id, tab_size, prec);
355 tables[id].size = tab_size;
356 tables[id].data = &data[offset + 1];
359 quant_size -= tab_size;
362 return offset + quant_size;
366 gst_rtp_jpeg_pay_read_sof (GstRtpJPEGPay * pay, const guint8 * data,
367 guint size, guint * offset, CompInfo info[])
370 guint width, height, infolen;
376 /* we need at least 17 bytes for the SOF */
380 sof_size = gst_rtp_jpeg_pay_header_size (data, off);
389 /* precision should be 8 */
390 if (data[off++] != 8)
393 /* read dimensions */
394 height = data[off] << 8 | data[off + 1];
395 width = data[off + 2] << 8 | data[off + 3];
398 GST_LOG_OBJECT (pay, "got dimensions %ux%u", height, width);
400 if (height == 0 || height > 2040)
401 goto invalid_dimension;
402 if (width == 0 || width > 2040)
403 goto invalid_dimension;
405 pay->height = height / 8;
406 pay->width = width / 8;
408 /* we only support 3 components */
409 if (data[off++] != 3)
413 for (i = 0; i < 3; i++) {
414 elem.id = data[off++];
415 elem.samp = data[off++];
416 elem.qt = data[off++];
417 GST_LOG_OBJECT (pay, "got comp %d, samp %02x, qt %d", elem.id, elem.samp,
419 /* insertion sort from the last element to the first */
420 for (j = infolen; j > 1; j--) {
421 if (G_LIKELY (info[j - 1].id < elem.id))
423 info[j] = info[j - 1];
429 /* see that the components are supported */
430 if (info[0].samp == 0x21)
432 else if (info[0].samp == 0x22)
437 /* the other components are free to use any quant table but they have to
438 * have the same table id */
439 if (!(info[1].samp == 0x11))
442 if (!(info[2].samp == 0x11))
445 if (info[1].qt != info[2].qt)
453 GST_ELEMENT_ERROR (pay, STREAM, FORMAT,
454 ("Wrong SOF length %u.", sof_size), (NULL));
459 GST_ELEMENT_ERROR (pay, STREAM, FORMAT,
460 ("Wrong precision, expecting 8."), (NULL));
465 GST_ELEMENT_ERROR (pay, STREAM, FORMAT,
466 ("Wrong dimension, size %ux%u", width, height), (NULL));
471 GST_ELEMENT_ERROR (pay, STREAM, FORMAT,
472 ("Wrong number of components"), (NULL));
477 GST_ELEMENT_ERROR (pay, STREAM, FORMAT, ("Invalid component"), (NULL));
483 gst_rtp_jpeg_pay_scan_marker (const guint8 * data, guint size, guint * offset)
485 while ((data[(*offset)++] != JPEG_MARKER) && ((*offset) < size));
487 if (G_UNLIKELY ((*offset) >= size)) {
488 GST_LOG ("found EOI marker");
489 return JPEG_MARKER_EOI;
493 marker = data[*offset];
494 GST_LOG ("found %02x marker at offset %u", marker, *offset);
501 gst_rtp_jpeg_pay_handle_buffer (GstBaseRTPPayload * basepayload,
505 GstClockTime timestamp;
506 GstFlowReturn ret = GST_FLOW_ERROR;
507 RtpJpegHeader jpeg_header;
508 RtpQuantHeader quant_header;
509 RtpQuantTable tables[15] = { {0, NULL}, };
510 CompInfo info[3] = { {0,}, };
511 guint quant_data_size;
516 guint jpeg_header_size = 0;
519 gboolean sos_found, sof_found, dqt_found;
521 GstBufferList *list = NULL;
522 GstBufferListIterator *it = NULL;
524 pay = GST_RTP_JPEG_PAY (basepayload);
525 mtu = GST_BASE_RTP_PAYLOAD_MTU (pay);
527 size = GST_BUFFER_SIZE (buffer);
528 data = GST_BUFFER_DATA (buffer);
529 timestamp = GST_BUFFER_TIMESTAMP (buffer);
532 GST_LOG_OBJECT (pay, "got buffer size %u, timestamp %" GST_TIME_FORMAT, size,
533 GST_TIME_ARGS (timestamp));
535 /* parse the jpeg header for 'start of scan' and read quant tables if needed */
540 while (!sos_found && (offset < size)) {
541 GST_LOG_OBJECT (pay, "checking from offset %u", offset);
542 switch (gst_rtp_jpeg_pay_scan_marker (data, size, &offset)) {
543 case JPEG_MARKER_JFIF:
544 case JPEG_MARKER_CMT:
545 case JPEG_MARKER_DHT:
546 GST_LOG_OBJECT (pay, "skipping marker");
547 offset += gst_rtp_jpeg_pay_header_size (data, offset);
549 case JPEG_MARKER_SOF:
550 if (!gst_rtp_jpeg_pay_read_sof (pay, data, size, &offset, info))
554 case JPEG_MARKER_DQT:
555 GST_LOG ("DQT found");
556 offset = gst_rtp_jpeg_pay_read_quant_table (data, size, offset, tables);
559 case JPEG_MARKER_SOS:
561 GST_LOG_OBJECT (pay, "SOS found");
562 jpeg_header_size = offset + gst_rtp_jpeg_pay_header_size (data, offset);
564 case JPEG_MARKER_EOI:
565 GST_WARNING_OBJECT (pay, "EOI reached before SOS!");
567 case JPEG_MARKER_SOI:
568 GST_LOG_OBJECT (pay, "SOI found");
574 if (!dqt_found || !sof_found)
575 goto unsupported_jpeg;
577 /* by now we should either have negotiated the width/height or the SOF header
578 * should have filled us in */
579 if (pay->width == 0 || pay->height == 0)
582 GST_LOG_OBJECT (pay, "header size %u", jpeg_header_size);
584 size -= jpeg_header_size;
585 data += jpeg_header_size;
588 /* prepare stuff for the jpeg header */
589 jpeg_header.type_spec = 0;
590 jpeg_header.type = pay->type;
591 jpeg_header.q = pay->quant;
592 jpeg_header.width = pay->width;
593 jpeg_header.height = pay->height;
595 /* collect the quant headers sizes */
596 quant_header.mbz = 0;
597 quant_header.precision = 0;
598 quant_header.length = 0;
601 if (pay->quant > 127) {
602 /* for the Y and U component, look up the quant table and its size. quant
603 * tables for U and V should be the same */
604 for (i = 0; i < 2; i++) {
612 qsize = tables[qt].size;
616 quant_header.precision |= (qsize == 64 ? 0 : (1 << i));
617 quant_data_size += qsize;
619 quant_header.length = g_htons (quant_data_size);
620 quant_data_size += sizeof (quant_header);
623 GST_LOG_OBJECT (pay, "quant_data size %u", quant_data_size);
625 if (pay->buffer_list) {
626 list = gst_buffer_list_new ();
627 it = gst_buffer_list_iterate (list);
630 bytes_left = sizeof (jpeg_header) + quant_data_size + size;
636 guint payload_size = (bytes_left < mtu ? bytes_left : mtu);
638 if (pay->buffer_list) {
639 outbuf = gst_rtp_buffer_new_allocate (sizeof (jpeg_header) +
640 quant_data_size, 0, 0);
642 outbuf = gst_rtp_buffer_new_allocate (payload_size, 0, 0);
644 GST_BUFFER_TIMESTAMP (outbuf) = timestamp;
646 if (payload_size == bytes_left) {
647 GST_LOG_OBJECT (pay, "last packet of frame");
649 gst_rtp_buffer_set_marker (outbuf, 1);
652 payload = gst_rtp_buffer_get_payload (outbuf);
655 #if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
656 jpeg_header.offset = ((offset & 0x0000FF) << 16) |
657 ((offset & 0xFF0000) >> 16) | (offset & 0x00FF00);
659 jpeg_header.offset = offset;
661 memcpy (payload, &jpeg_header, sizeof (jpeg_header));
662 payload += sizeof (jpeg_header);
663 payload_size -= sizeof (jpeg_header);
665 /* only send quant table with first packet */
666 if (G_UNLIKELY (quant_data_size > 0)) {
667 memcpy (payload, &quant_header, sizeof (quant_header));
668 payload += sizeof (quant_header);
670 /* copy the quant tables for luma and chrominance */
671 for (i = 0; i < 2; i++) {
676 qsize = tables[qt].size;
677 memcpy (payload, tables[qt].data, qsize);
679 GST_LOG_OBJECT (pay, "component %d using quant %d, size %d", i, qt,
684 payload_size -= quant_data_size;
685 bytes_left -= quant_data_size;
688 GST_LOG_OBJECT (pay, "sending payload size %d", payload_size);
690 if (pay->buffer_list) {
693 /* create a new buf to hold the payload */
694 paybuf = gst_buffer_create_sub (buffer, jpeg_header_size + offset,
697 /* create a new group to hold the rtp header and the payload */
698 gst_buffer_list_iterator_add_group (it);
699 gst_buffer_list_iterator_add (it, outbuf);
700 gst_buffer_list_iterator_add (it, paybuf);
702 memcpy (payload, data, payload_size);
703 ret = gst_basertppayload_push (basepayload, outbuf);
704 if (ret != GST_FLOW_OK)
708 bytes_left -= payload_size;
709 offset += payload_size;
710 data += payload_size;
714 if (pay->buffer_list) {
715 gst_buffer_list_iterator_free (it);
716 /* push the whole buffer list at once */
717 ret = gst_basertppayload_push_list (basepayload, list);
720 gst_buffer_unref (buffer);
727 GST_ELEMENT_ERROR (pay, STREAM, FORMAT, ("Unsupported JPEG"), (NULL));
728 gst_buffer_unref (buffer);
729 return GST_FLOW_NOT_SUPPORTED;
733 GST_ELEMENT_ERROR (pay, STREAM, FORMAT, ("No size given"), (NULL));
734 gst_buffer_unref (buffer);
735 return GST_FLOW_NOT_NEGOTIATED;
739 /* error was posted */
740 gst_buffer_unref (buffer);
741 return GST_FLOW_ERROR;
745 GST_ELEMENT_ERROR (pay, STREAM, FORMAT, ("Invalid quant tables"), (NULL));
746 gst_buffer_unref (buffer);
747 return GST_FLOW_ERROR;
752 gst_rtp_jpeg_pay_set_property (GObject * object, guint prop_id,
753 const GValue * value, GParamSpec * pspec)
755 GstRtpJPEGPay *rtpjpegpay;
757 rtpjpegpay = GST_RTP_JPEG_PAY (object);
760 case PROP_JPEG_QUALITY:
761 rtpjpegpay->quality = g_value_get_int (value);
762 GST_DEBUG_OBJECT (object, "quality = %d", rtpjpegpay->quality);
765 rtpjpegpay->type = g_value_get_int (value);
766 GST_DEBUG_OBJECT (object, "type = %d", rtpjpegpay->type);
768 case PROP_BUFFER_LIST:
769 rtpjpegpay->buffer_list = g_value_get_boolean (value);
770 GST_DEBUG_OBJECT (object, "buffer_list = %d", rtpjpegpay->buffer_list);
773 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
779 gst_rtp_jpeg_pay_get_property (GObject * object, guint prop_id,
780 GValue * value, GParamSpec * pspec)
782 GstRtpJPEGPay *rtpjpegpay;
784 rtpjpegpay = GST_RTP_JPEG_PAY (object);
787 case PROP_JPEG_QUALITY:
788 g_value_set_int (value, rtpjpegpay->quality);
791 g_value_set_int (value, rtpjpegpay->type);
793 case PROP_BUFFER_LIST:
794 g_value_set_boolean (value, rtpjpegpay->buffer_list);
797 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
803 gst_rtp_jpeg_pay_plugin_init (GstPlugin * plugin)
805 return gst_element_register (plugin, "rtpjpegpay", GST_RANK_NONE,
806 GST_TYPE_RTP_JPEG_PAY);