mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-23 18:21:04 +00:00
2361567bae
The format of the value adheres to RFC6064 and it is meant to be parsed and included in the SDP sent by gst-rtsp-server to its clients. Fixes https://bugzilla.gnome.org/show_bug.cgi?id=700748
988 lines
27 KiB
C
988 lines
27 KiB
C
/* GStreamer
|
|
* Copyright (C) 2008 Axis Communications <dev-gstreamer@axis.com>
|
|
* @author Bjorn Ostby <bjorn.ostby@axis.com>
|
|
* @author Peter Kjellerstedt <peter.kjellerstedt@axis.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*/
|
|
|
|
/**
|
|
* SECTION:element-rtpjpegpay
|
|
*
|
|
* Payload encode JPEG pictures into RTP packets according to RFC 2435.
|
|
* For detailed information see: http://www.rfc-editor.org/rfc/rfc2435.txt
|
|
*
|
|
* The payloader takes a JPEG picture, scans the header for quantization
|
|
* tables (if needed) and constructs the RTP packet header followed by
|
|
* the actual JPEG entropy scan.
|
|
*
|
|
* The payloader assumes that correct width and height is found in the caps.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include <gst/rtp/gstrtpbuffer.h>
|
|
|
|
#include "gstrtpjpegpay.h"
|
|
|
|
static GstStaticPadTemplate gst_rtp_jpeg_pay_sink_template =
|
|
GST_STATIC_PAD_TEMPLATE ("sink",
|
|
GST_PAD_SINK,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("image/jpeg; " "video/x-jpeg")
|
|
);
|
|
|
|
static GstStaticPadTemplate gst_rtp_jpeg_pay_src_template =
|
|
GST_STATIC_PAD_TEMPLATE ("src",
|
|
GST_PAD_SRC,
|
|
GST_PAD_ALWAYS,
|
|
GST_STATIC_CAPS ("application/x-rtp, "
|
|
" media = (string) \"video\", "
|
|
" payload = (int) 26 , "
|
|
" clock-rate = (int) 90000, "
|
|
" encoding-name = (string) \"JPEG\", "
|
|
" width = (int) [ 1, 65536 ], "
|
|
" height = (int) [ 1, 65536 ]")
|
|
);
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (rtpjpegpay_debug);
|
|
#define GST_CAT_DEFAULT (rtpjpegpay_debug)
|
|
|
|
/*
|
|
* QUANT_PREFIX_LEN:
|
|
*
|
|
* Prefix length in the header before the quantization tables:
|
|
* Two size bytes and one byte for precision
|
|
*/
|
|
#define QUANT_PREFIX_LEN 3
|
|
|
|
|
|
typedef enum _RtpJpegMarker RtpJpegMarker;
|
|
|
|
/*
|
|
* RtpJpegMarker:
|
|
* @JPEG_MARKER: Prefix for JPEG marker
|
|
* @JPEG_MARKER_SOI: Start of Image marker
|
|
* @JPEG_MARKER_JFIF: JFIF marker
|
|
* @JPEG_MARKER_CMT: Comment marker
|
|
* @JPEG_MARKER_DQT: Define Quantization Table marker
|
|
* @JPEG_MARKER_SOF: Start of Frame marker
|
|
* @JPEG_MARKER_DHT: Define Huffman Table marker
|
|
* @JPEG_MARKER_SOS: Start of Scan marker
|
|
* @JPEG_MARKER_EOI: End of Image marker
|
|
* @JPEG_MARKER_DRI: Define Restart Interval marker
|
|
* @JPEG_MARKER_H264: H264 marker
|
|
*
|
|
* Identifers for markers in JPEG header
|
|
*/
|
|
enum _RtpJpegMarker
|
|
{
|
|
JPEG_MARKER = 0xFF,
|
|
JPEG_MARKER_SOI = 0xD8,
|
|
JPEG_MARKER_JFIF = 0xE0,
|
|
JPEG_MARKER_CMT = 0xFE,
|
|
JPEG_MARKER_DQT = 0xDB,
|
|
JPEG_MARKER_SOF = 0xC0,
|
|
JPEG_MARKER_DHT = 0xC4,
|
|
JPEG_MARKER_SOS = 0xDA,
|
|
JPEG_MARKER_EOI = 0xD9,
|
|
JPEG_MARKER_DRI = 0xDD,
|
|
JPEG_MARKER_H264 = 0xE4
|
|
};
|
|
|
|
#define DEFAULT_JPEG_QUANT 255
|
|
|
|
#define DEFAULT_JPEG_QUALITY 255
|
|
#define DEFAULT_JPEG_TYPE 1
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_JPEG_QUALITY,
|
|
PROP_JPEG_TYPE,
|
|
PROP_LAST
|
|
};
|
|
|
|
enum
|
|
{
|
|
Q_TABLE_0 = 0,
|
|
Q_TABLE_1,
|
|
Q_TABLE_MAX /* only support for two tables at the moment */
|
|
};
|
|
|
|
typedef struct _RtpJpegHeader RtpJpegHeader;
|
|
|
|
/*
|
|
* RtpJpegHeader:
|
|
* @type_spec: type specific
|
|
* @offset: fragment offset
|
|
* @type: type field
|
|
* @q: quantization table for this frame
|
|
* @width: width of image in 8-pixel multiples
|
|
* @height: height of image in 8-pixel multiples
|
|
*
|
|
* 0 1 2 3
|
|
* 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
|
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
* | Type-specific | Fragment Offset |
|
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
* | Type | Q | Width | Height |
|
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
*/
|
|
struct _RtpJpegHeader
|
|
{
|
|
guint type_spec:8;
|
|
guint offset:24;
|
|
guint8 type;
|
|
guint8 q;
|
|
guint8 width;
|
|
guint8 height;
|
|
};
|
|
|
|
/*
|
|
* RtpQuantHeader
|
|
* @mbz: must be zero
|
|
* @precision: specify size of quantization tables
|
|
* @length: length of quantization data
|
|
*
|
|
* 0 1 2 3
|
|
* 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
|
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
* | MBZ | Precision | Length |
|
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
* | Quantization Table Data |
|
|
* | ... |
|
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
*/
|
|
typedef struct
|
|
{
|
|
guint8 mbz;
|
|
guint8 precision;
|
|
guint16 length;
|
|
} RtpQuantHeader;
|
|
|
|
typedef struct
|
|
{
|
|
guint8 size;
|
|
const guint8 *data;
|
|
} RtpQuantTable;
|
|
|
|
/*
|
|
* RtpRestartMarkerHeader:
|
|
* @restartInterval: number of MCUs that appear between restart markers
|
|
* @restartFirstLastCount: a combination of the first packet mark in the chunk
|
|
* last packet mark in the chunk and the position of the
|
|
* first restart interval in the current "chunk"
|
|
*
|
|
* 0 1 2 3
|
|
* 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
|
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
* | Restart Interval |F|L| Restart Count |
|
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
|
*
|
|
* The restart marker header is implemented according to the following
|
|
* methodology specified in section 3.1.7 of rfc2435.txt.
|
|
*
|
|
* "If the restart intervals in a frame are not guaranteed to be aligned
|
|
* with packet boundaries, the F (first) and L (last) bits MUST be set
|
|
* to 1 and the Restart Count MUST be set to 0x3FFF. This indicates
|
|
* that a receiver MUST reassemble the entire frame before decoding it."
|
|
*
|
|
*/
|
|
|
|
typedef struct
|
|
{
|
|
guint16 restart_interval;
|
|
guint16 restart_count;
|
|
} RtpRestartMarkerHeader;
|
|
|
|
typedef struct
|
|
{
|
|
guint8 id;
|
|
guint8 samp;
|
|
guint8 qt;
|
|
} CompInfo;
|
|
|
|
/* FIXME: restart marker header currently unsupported */
|
|
|
|
static void gst_rtp_jpeg_pay_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec);
|
|
|
|
static void gst_rtp_jpeg_pay_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec);
|
|
|
|
static gboolean gst_rtp_jpeg_pay_setcaps (GstRTPBasePayload * basepayload,
|
|
GstCaps * caps);
|
|
|
|
static GstFlowReturn gst_rtp_jpeg_pay_handle_buffer (GstRTPBasePayload * pad,
|
|
GstBuffer * buffer);
|
|
|
|
#define gst_rtp_jpeg_pay_parent_class parent_class
|
|
G_DEFINE_TYPE (GstRtpJPEGPay, gst_rtp_jpeg_pay, GST_TYPE_RTP_BASE_PAYLOAD);
|
|
|
|
static void
|
|
gst_rtp_jpeg_pay_class_init (GstRtpJPEGPayClass * klass)
|
|
{
|
|
GObjectClass *gobject_class;
|
|
GstElementClass *gstelement_class;
|
|
GstRTPBasePayloadClass *gstrtpbasepayload_class;
|
|
|
|
gobject_class = (GObjectClass *) klass;
|
|
gstelement_class = (GstElementClass *) klass;
|
|
gstrtpbasepayload_class = (GstRTPBasePayloadClass *) klass;
|
|
|
|
gobject_class->set_property = gst_rtp_jpeg_pay_set_property;
|
|
gobject_class->get_property = gst_rtp_jpeg_pay_get_property;
|
|
|
|
gst_element_class_add_pad_template (gstelement_class,
|
|
gst_static_pad_template_get (&gst_rtp_jpeg_pay_src_template));
|
|
gst_element_class_add_pad_template (gstelement_class,
|
|
gst_static_pad_template_get (&gst_rtp_jpeg_pay_sink_template));
|
|
|
|
gst_element_class_set_static_metadata (gstelement_class, "RTP JPEG payloader",
|
|
"Codec/Payloader/Network/RTP",
|
|
"Payload-encodes JPEG pictures into RTP packets (RFC 2435)",
|
|
"Axis Communications <dev-gstreamer@axis.com>");
|
|
|
|
gstrtpbasepayload_class->set_caps = gst_rtp_jpeg_pay_setcaps;
|
|
gstrtpbasepayload_class->handle_buffer = gst_rtp_jpeg_pay_handle_buffer;
|
|
|
|
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_JPEG_QUALITY,
|
|
g_param_spec_int ("quality", "Quality",
|
|
"Quality factor on JPEG data (unused)", 0, 255, 255,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_JPEG_TYPE,
|
|
g_param_spec_int ("type", "Type",
|
|
"Default JPEG Type, overwritten by SOF when present", 0, 255,
|
|
DEFAULT_JPEG_TYPE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
GST_DEBUG_CATEGORY_INIT (rtpjpegpay_debug, "rtpjpegpay", 0,
|
|
"Motion JPEG RTP Payloader");
|
|
}
|
|
|
|
static void
|
|
gst_rtp_jpeg_pay_init (GstRtpJPEGPay * pay)
|
|
{
|
|
pay->quality = DEFAULT_JPEG_QUALITY;
|
|
pay->quant = DEFAULT_JPEG_QUANT;
|
|
pay->type = DEFAULT_JPEG_TYPE;
|
|
pay->width = -1;
|
|
pay->height = -1;
|
|
}
|
|
|
|
static gboolean
|
|
gst_rtp_jpeg_pay_setcaps (GstRTPBasePayload * basepayload, GstCaps * caps)
|
|
{
|
|
GstStructure *caps_structure = gst_caps_get_structure (caps, 0);
|
|
GstRtpJPEGPay *pay;
|
|
gboolean res;
|
|
gint width = -1, height = -1;
|
|
gint num = 0, denom;
|
|
gchar *rate = NULL;
|
|
gchar *dim = NULL;
|
|
gchar *size;
|
|
|
|
pay = GST_RTP_JPEG_PAY (basepayload);
|
|
|
|
/* these properties are mandatory, but they might be adjusted by the SOF, if there
|
|
* is one. */
|
|
if (!gst_structure_get_int (caps_structure, "height", &height) || height <= 0) {
|
|
goto invalid_dimension;
|
|
}
|
|
|
|
if (!gst_structure_get_int (caps_structure, "width", &width) || width <= 0) {
|
|
goto invalid_dimension;
|
|
}
|
|
|
|
if (gst_structure_get_fraction (caps_structure, "framerate", &num, &denom) &&
|
|
(num < 0 || denom <= 0)) {
|
|
goto invalid_framerate;
|
|
}
|
|
|
|
if (height > 2040 || width > 2040) {
|
|
pay->height = 0;
|
|
pay->width = 0;
|
|
} else {
|
|
pay->height = GST_ROUND_UP_8 (height) / 8;
|
|
pay->width = GST_ROUND_UP_8 (width) / 8;
|
|
}
|
|
|
|
gst_rtp_base_payload_set_options (basepayload, "video", TRUE, "JPEG", 90000);
|
|
|
|
if (num > 0)
|
|
{
|
|
gdouble framerate;
|
|
gst_util_fraction_to_double (num, denom, &framerate);
|
|
rate = g_strdup_printf("%f", framerate);
|
|
}
|
|
|
|
size = g_strdup_printf("%d-%d", width, height);
|
|
|
|
if (pay->width == 0) {
|
|
GST_DEBUG_OBJECT (pay,
|
|
"width or height are greater than 2040, adding x-dimensions to caps");
|
|
dim = g_strdup_printf ("%d,%d", width, height);
|
|
}
|
|
|
|
if (rate != NULL && dim != NULL) {
|
|
res = gst_rtp_base_payload_set_outcaps (basepayload, "a-framerate",
|
|
G_TYPE_STRING, rate, "a-framesize", G_TYPE_STRING, size,
|
|
"x-dimensions", G_TYPE_STRING, dim, NULL);
|
|
} else if (rate != NULL && dim == NULL) {
|
|
res = gst_rtp_base_payload_set_outcaps (basepayload, "a-framerate",
|
|
G_TYPE_STRING, rate, "a-framesize", G_TYPE_STRING, size, NULL);
|
|
} else if (rate == NULL && dim != NULL) {
|
|
res = gst_rtp_base_payload_set_outcaps (basepayload, "x-dimensions",
|
|
G_TYPE_STRING, dim, "a-framesize", G_TYPE_STRING, size, NULL);
|
|
} else {
|
|
res = gst_rtp_base_payload_set_outcaps (basepayload, "a-framesize",
|
|
G_TYPE_STRING, size, NULL);
|
|
}
|
|
|
|
if (dim != NULL)
|
|
g_free (dim);
|
|
if (rate != NULL)
|
|
g_free (rate);
|
|
g_free (size);
|
|
|
|
return res;
|
|
|
|
/* ERRORS */
|
|
invalid_dimension:
|
|
{
|
|
GST_ERROR_OBJECT (pay, "Invalid width/height from caps");
|
|
return FALSE;
|
|
}
|
|
invalid_framerate:
|
|
{
|
|
GST_ERROR_OBJECT (pay, "Invalid framerate from caps");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static guint
|
|
gst_rtp_jpeg_pay_header_size (const guint8 * data, guint offset)
|
|
{
|
|
return data[offset] << 8 | data[offset + 1];
|
|
}
|
|
|
|
static guint
|
|
gst_rtp_jpeg_pay_read_quant_table (const guint8 * data, guint size,
|
|
guint offset, RtpQuantTable tables[])
|
|
{
|
|
guint quant_size, tab_size;
|
|
guint8 prec;
|
|
guint8 id;
|
|
|
|
if (offset + 2 > size)
|
|
goto too_small;
|
|
|
|
quant_size = gst_rtp_jpeg_pay_header_size (data, offset);
|
|
if (quant_size < 2)
|
|
goto small_quant_size;
|
|
|
|
/* clamp to available data */
|
|
if (offset + quant_size > size)
|
|
quant_size = size - offset;
|
|
|
|
offset += 2;
|
|
quant_size -= 2;
|
|
|
|
while (quant_size > 0) {
|
|
/* not enough to read the id */
|
|
if (offset + 1 > size)
|
|
break;
|
|
|
|
id = data[offset] & 0x0f;
|
|
if (id == 15)
|
|
/* invalid id received - corrupt data */
|
|
goto invalid_id;
|
|
|
|
prec = (data[offset] & 0xf0) >> 4;
|
|
if (prec)
|
|
tab_size = 128;
|
|
else
|
|
tab_size = 64;
|
|
|
|
/* there is not enough for the table */
|
|
if (quant_size < tab_size + 1)
|
|
goto no_table;
|
|
|
|
GST_LOG ("read quant table %d, tab_size %d, prec %02x", id, tab_size, prec);
|
|
|
|
tables[id].size = tab_size;
|
|
tables[id].data = &data[offset + 1];
|
|
|
|
tab_size += 1;
|
|
quant_size -= tab_size;
|
|
offset += tab_size;
|
|
}
|
|
done:
|
|
return offset + quant_size;
|
|
|
|
/* ERRORS */
|
|
too_small:
|
|
{
|
|
GST_WARNING ("not enough data");
|
|
return size;
|
|
}
|
|
small_quant_size:
|
|
{
|
|
GST_WARNING ("quant_size too small (%u < 2)", quant_size);
|
|
return size;
|
|
}
|
|
invalid_id:
|
|
{
|
|
GST_WARNING ("invalid id");
|
|
goto done;
|
|
}
|
|
no_table:
|
|
{
|
|
GST_WARNING ("not enough data for table (%u < %u)", quant_size,
|
|
tab_size + 1);
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_rtp_jpeg_pay_read_sof (GstRtpJPEGPay * pay, const guint8 * data,
|
|
guint size, guint * offset, CompInfo info[], RtpQuantTable tables[],
|
|
gulong tables_elements)
|
|
{
|
|
guint sof_size, off;
|
|
guint width, height, infolen;
|
|
CompInfo elem;
|
|
gint i, j;
|
|
|
|
off = *offset;
|
|
|
|
/* we need at least 17 bytes for the SOF */
|
|
if (off + 17 > size)
|
|
goto wrong_size;
|
|
|
|
sof_size = gst_rtp_jpeg_pay_header_size (data, off);
|
|
if (sof_size < 17)
|
|
goto wrong_length;
|
|
|
|
*offset += sof_size;
|
|
|
|
/* skip size */
|
|
off += 2;
|
|
|
|
/* precision should be 8 */
|
|
if (data[off++] != 8)
|
|
goto bad_precision;
|
|
|
|
/* read dimensions */
|
|
height = data[off] << 8 | data[off + 1];
|
|
width = data[off + 2] << 8 | data[off + 3];
|
|
off += 4;
|
|
|
|
GST_LOG_OBJECT (pay, "got dimensions %ux%u", height, width);
|
|
|
|
if (height == 0) {
|
|
goto invalid_dimension;
|
|
}
|
|
if (height > 2040) {
|
|
height = 0;
|
|
}
|
|
if (width == 0) {
|
|
goto invalid_dimension;
|
|
}
|
|
if (width > 2040) {
|
|
width = 0;
|
|
}
|
|
|
|
if (height == 0 || width == 0) {
|
|
pay->height = 0;
|
|
pay->width = 0;
|
|
} else {
|
|
pay->height = GST_ROUND_UP_8 (height) / 8;
|
|
pay->width = GST_ROUND_UP_8 (width) / 8;
|
|
}
|
|
|
|
/* we only support 3 components */
|
|
if (data[off++] != 3)
|
|
goto bad_components;
|
|
|
|
infolen = 0;
|
|
for (i = 0; i < 3; i++) {
|
|
elem.id = data[off++];
|
|
elem.samp = data[off++];
|
|
elem.qt = data[off++];
|
|
GST_LOG_OBJECT (pay, "got comp %d, samp %02x, qt %d", elem.id, elem.samp,
|
|
elem.qt);
|
|
/* insertion sort from the last element to the first */
|
|
for (j = infolen; j > 1; j--) {
|
|
if (G_LIKELY (info[j - 1].id < elem.id))
|
|
break;
|
|
info[j] = info[j - 1];
|
|
}
|
|
info[j] = elem;
|
|
infolen++;
|
|
}
|
|
|
|
/* see that the components are supported */
|
|
if (info[0].samp == 0x21)
|
|
pay->type = 0;
|
|
else if (info[0].samp == 0x22)
|
|
pay->type = 1;
|
|
else
|
|
goto invalid_comp;
|
|
|
|
if (!(info[1].samp == 0x11))
|
|
goto invalid_comp;
|
|
|
|
if (!(info[2].samp == 0x11))
|
|
goto invalid_comp;
|
|
|
|
/* the other components are free to use any quant table but they have to
|
|
* have the same table id */
|
|
if (info[1].qt != info[2].qt) {
|
|
/* Some MJPG (like the one from the Logitech C-920 camera) uses different
|
|
* quant tables for component 1 and 2 but both tables contain the exact
|
|
* same data, so we could consider them as being the same tables */
|
|
if (!(info[1].qt < tables_elements &&
|
|
info[2].qt < tables_elements &&
|
|
tables[info[1].qt].size > 0 &&
|
|
tables[info[1].qt].size == tables[info[2].qt].size &&
|
|
memcmp (tables[info[1].qt].data, tables[info[2].qt].data,
|
|
tables[info[1].qt].size) == 0))
|
|
goto invalid_comp;
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
/* ERRORS */
|
|
wrong_size:
|
|
{
|
|
GST_ELEMENT_ERROR (pay, STREAM, FORMAT,
|
|
("Wrong size %u (needed %u).", size, off + 17), (NULL));
|
|
return FALSE;
|
|
}
|
|
wrong_length:
|
|
{
|
|
GST_ELEMENT_ERROR (pay, STREAM, FORMAT,
|
|
("Wrong SOF length %u.", sof_size), (NULL));
|
|
return FALSE;
|
|
}
|
|
bad_precision:
|
|
{
|
|
GST_ELEMENT_ERROR (pay, STREAM, FORMAT,
|
|
("Wrong precision, expecting 8."), (NULL));
|
|
return FALSE;
|
|
}
|
|
invalid_dimension:
|
|
{
|
|
GST_ELEMENT_ERROR (pay, STREAM, FORMAT,
|
|
("Wrong dimension, size %ux%u", width, height), (NULL));
|
|
return FALSE;
|
|
}
|
|
bad_components:
|
|
{
|
|
GST_ELEMENT_ERROR (pay, STREAM, FORMAT,
|
|
("Wrong number of components"), (NULL));
|
|
return FALSE;
|
|
}
|
|
invalid_comp:
|
|
{
|
|
GST_ELEMENT_ERROR (pay, STREAM, FORMAT, ("Invalid component"), (NULL));
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
gst_rtp_jpeg_pay_read_dri (GstRtpJPEGPay * pay, const guint8 * data,
|
|
guint size, guint * offset, RtpRestartMarkerHeader * dri)
|
|
{
|
|
guint dri_size, off;
|
|
|
|
off = *offset;
|
|
|
|
/* we need at least 4 bytes for the DRI */
|
|
if (off + 4 > size)
|
|
goto wrong_size;
|
|
|
|
dri_size = gst_rtp_jpeg_pay_header_size (data, off);
|
|
if (dri_size < 4)
|
|
goto wrong_length;
|
|
|
|
*offset += dri_size;
|
|
off += 2;
|
|
|
|
dri->restart_interval = g_htons ((data[off] << 8) | (data[off + 1]));
|
|
dri->restart_count = g_htons (0xFFFF);
|
|
|
|
return dri->restart_interval > 0;
|
|
|
|
wrong_size:
|
|
{
|
|
GST_WARNING ("not enough data for DRI");
|
|
*offset = size;
|
|
return FALSE;
|
|
}
|
|
wrong_length:
|
|
{
|
|
GST_WARNING ("DRI size too small (%u)", dri_size);
|
|
*offset += dri_size;
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static RtpJpegMarker
|
|
gst_rtp_jpeg_pay_scan_marker (const guint8 * data, guint size, guint * offset)
|
|
{
|
|
while ((data[(*offset)++] != JPEG_MARKER) && ((*offset) < size));
|
|
|
|
if (G_UNLIKELY ((*offset) >= size)) {
|
|
GST_LOG ("found EOI marker");
|
|
return JPEG_MARKER_EOI;
|
|
} else {
|
|
guint8 marker;
|
|
|
|
marker = data[*offset];
|
|
GST_LOG ("found 0x%02x marker at offset %u", marker, *offset);
|
|
(*offset)++;
|
|
return marker;
|
|
}
|
|
}
|
|
|
|
static GstFlowReturn
|
|
gst_rtp_jpeg_pay_handle_buffer (GstRTPBasePayload * basepayload,
|
|
GstBuffer * buffer)
|
|
{
|
|
GstRtpJPEGPay *pay;
|
|
GstClockTime timestamp;
|
|
GstFlowReturn ret = GST_FLOW_ERROR;
|
|
RtpJpegHeader jpeg_header;
|
|
RtpQuantHeader quant_header;
|
|
RtpRestartMarkerHeader restart_marker_header;
|
|
RtpQuantTable tables[15] = { {0, NULL}, };
|
|
CompInfo info[3] = { {0,}, };
|
|
guint quant_data_size;
|
|
GstMapInfo map;
|
|
guint8 *data;
|
|
gsize size;
|
|
guint mtu;
|
|
guint bytes_left;
|
|
guint jpeg_header_size = 0;
|
|
guint offset;
|
|
gboolean frame_done;
|
|
gboolean sos_found, sof_found, dqt_found, dri_found;
|
|
gint i;
|
|
GstBufferList *list = NULL;
|
|
|
|
pay = GST_RTP_JPEG_PAY (basepayload);
|
|
mtu = GST_RTP_BASE_PAYLOAD_MTU (pay);
|
|
|
|
gst_buffer_map (buffer, &map, GST_MAP_READ);
|
|
data = map.data;
|
|
size = map.size;
|
|
timestamp = GST_BUFFER_TIMESTAMP (buffer);
|
|
offset = 0;
|
|
|
|
GST_LOG_OBJECT (pay, "got buffer size %" G_GSIZE_FORMAT
|
|
" , timestamp %" GST_TIME_FORMAT, size, GST_TIME_ARGS (timestamp));
|
|
|
|
/* parse the jpeg header for 'start of scan' and read quant tables if needed */
|
|
sos_found = FALSE;
|
|
dqt_found = FALSE;
|
|
sof_found = FALSE;
|
|
dri_found = FALSE;
|
|
|
|
while (!sos_found && (offset < size)) {
|
|
GST_LOG_OBJECT (pay, "checking from offset %u", offset);
|
|
switch (gst_rtp_jpeg_pay_scan_marker (data, size, &offset)) {
|
|
case JPEG_MARKER_JFIF:
|
|
case JPEG_MARKER_CMT:
|
|
case JPEG_MARKER_DHT:
|
|
case JPEG_MARKER_H264:
|
|
GST_LOG_OBJECT (pay, "skipping marker");
|
|
offset += gst_rtp_jpeg_pay_header_size (data, offset);
|
|
break;
|
|
case JPEG_MARKER_SOF:
|
|
if (!gst_rtp_jpeg_pay_read_sof (pay, data, size, &offset, info, tables,
|
|
G_N_ELEMENTS (tables)))
|
|
goto invalid_format;
|
|
sof_found = TRUE;
|
|
break;
|
|
case JPEG_MARKER_DQT:
|
|
GST_LOG ("DQT found");
|
|
offset = gst_rtp_jpeg_pay_read_quant_table (data, size, offset, tables);
|
|
dqt_found = TRUE;
|
|
break;
|
|
case JPEG_MARKER_SOS:
|
|
sos_found = TRUE;
|
|
GST_LOG_OBJECT (pay, "SOS found");
|
|
jpeg_header_size = offset + gst_rtp_jpeg_pay_header_size (data, offset);
|
|
break;
|
|
case JPEG_MARKER_EOI:
|
|
GST_WARNING_OBJECT (pay, "EOI reached before SOS!");
|
|
break;
|
|
case JPEG_MARKER_SOI:
|
|
GST_LOG_OBJECT (pay, "SOI found");
|
|
break;
|
|
case JPEG_MARKER_DRI:
|
|
GST_LOG_OBJECT (pay, "DRI found");
|
|
if (gst_rtp_jpeg_pay_read_dri (pay, data, size, &offset,
|
|
&restart_marker_header))
|
|
dri_found = TRUE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if (!dqt_found || !sof_found)
|
|
goto unsupported_jpeg;
|
|
|
|
/* by now we should either have negotiated the width/height or the SOF header
|
|
* should have filled us in */
|
|
if (pay->width < 0 || pay->height < 0) {
|
|
goto no_dimension;
|
|
}
|
|
|
|
GST_LOG_OBJECT (pay, "header size %u", jpeg_header_size);
|
|
|
|
size -= jpeg_header_size;
|
|
data += jpeg_header_size;
|
|
offset = 0;
|
|
|
|
if (dri_found)
|
|
pay->type += 64;
|
|
|
|
/* prepare stuff for the jpeg header */
|
|
jpeg_header.type_spec = 0;
|
|
jpeg_header.type = pay->type;
|
|
jpeg_header.q = pay->quant;
|
|
jpeg_header.width = pay->width;
|
|
jpeg_header.height = pay->height;
|
|
|
|
/* collect the quant headers sizes */
|
|
quant_header.mbz = 0;
|
|
quant_header.precision = 0;
|
|
quant_header.length = 0;
|
|
quant_data_size = 0;
|
|
|
|
if (pay->quant > 127) {
|
|
/* for the Y and U component, look up the quant table and its size. quant
|
|
* tables for U and V should be the same */
|
|
for (i = 0; i < 2; i++) {
|
|
guint qsize;
|
|
guint qt;
|
|
|
|
qt = info[i].qt;
|
|
if (qt >= G_N_ELEMENTS (tables))
|
|
goto invalid_quant;
|
|
|
|
qsize = tables[qt].size;
|
|
if (qsize == 0)
|
|
goto invalid_quant;
|
|
|
|
quant_header.precision |= (qsize == 64 ? 0 : (1 << i));
|
|
quant_data_size += qsize;
|
|
}
|
|
quant_header.length = g_htons (quant_data_size);
|
|
quant_data_size += sizeof (quant_header);
|
|
}
|
|
|
|
GST_LOG_OBJECT (pay, "quant_data size %u", quant_data_size);
|
|
|
|
list = gst_buffer_list_new ();
|
|
|
|
bytes_left = sizeof (jpeg_header) + quant_data_size + size;
|
|
|
|
if (dri_found)
|
|
bytes_left += sizeof (restart_marker_header);
|
|
|
|
frame_done = FALSE;
|
|
do {
|
|
GstBuffer *outbuf;
|
|
guint8 *payload;
|
|
guint payload_size = (bytes_left < mtu ? bytes_left : mtu);
|
|
guint header_size;
|
|
GstBuffer *paybuf;
|
|
GstRTPBuffer rtp = { NULL };
|
|
|
|
header_size = sizeof (jpeg_header) + quant_data_size;
|
|
if (dri_found)
|
|
header_size += sizeof (restart_marker_header);
|
|
|
|
outbuf = gst_rtp_buffer_new_allocate (header_size, 0, 0);
|
|
|
|
gst_rtp_buffer_map (outbuf, GST_MAP_WRITE, &rtp);
|
|
|
|
if (payload_size == bytes_left) {
|
|
GST_LOG_OBJECT (pay, "last packet of frame");
|
|
frame_done = TRUE;
|
|
gst_rtp_buffer_set_marker (&rtp, 1);
|
|
}
|
|
|
|
payload = gst_rtp_buffer_get_payload (&rtp);
|
|
|
|
/* update offset */
|
|
#if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
|
|
jpeg_header.offset = ((offset & 0x0000FF) << 16) |
|
|
((offset & 0xFF0000) >> 16) | (offset & 0x00FF00);
|
|
#else
|
|
jpeg_header.offset = offset;
|
|
#endif
|
|
memcpy (payload, &jpeg_header, sizeof (jpeg_header));
|
|
payload += sizeof (jpeg_header);
|
|
payload_size -= sizeof (jpeg_header);
|
|
|
|
if (dri_found) {
|
|
memcpy (payload, &restart_marker_header, sizeof (restart_marker_header));
|
|
payload += sizeof (restart_marker_header);
|
|
payload_size -= sizeof (restart_marker_header);
|
|
}
|
|
|
|
/* only send quant table with first packet */
|
|
if (G_UNLIKELY (quant_data_size > 0)) {
|
|
memcpy (payload, &quant_header, sizeof (quant_header));
|
|
payload += sizeof (quant_header);
|
|
|
|
/* copy the quant tables for luma and chrominance */
|
|
for (i = 0; i < 2; i++) {
|
|
guint qsize;
|
|
guint qt;
|
|
|
|
qt = info[i].qt;
|
|
qsize = tables[qt].size;
|
|
memcpy (payload, tables[qt].data, qsize);
|
|
|
|
GST_LOG_OBJECT (pay, "component %d using quant %d, size %d", i, qt,
|
|
qsize);
|
|
|
|
payload += qsize;
|
|
}
|
|
payload_size -= quant_data_size;
|
|
bytes_left -= quant_data_size;
|
|
quant_data_size = 0;
|
|
}
|
|
GST_LOG_OBJECT (pay, "sending payload size %d", payload_size);
|
|
gst_rtp_buffer_unmap (&rtp);
|
|
|
|
/* create a new buf to hold the payload */
|
|
paybuf = gst_buffer_copy_region (buffer, GST_BUFFER_COPY_MEMORY,
|
|
jpeg_header_size + offset, payload_size);
|
|
|
|
/* join memory parts */
|
|
outbuf = gst_buffer_append (outbuf, paybuf);
|
|
|
|
GST_BUFFER_TIMESTAMP (outbuf) = timestamp;
|
|
|
|
/* and add to list */
|
|
gst_buffer_list_insert (list, -1, outbuf);
|
|
|
|
bytes_left -= payload_size;
|
|
offset += payload_size;
|
|
data += payload_size;
|
|
}
|
|
while (!frame_done);
|
|
|
|
/* push the whole buffer list at once */
|
|
ret = gst_rtp_base_payload_push_list (basepayload, list);
|
|
|
|
gst_buffer_unmap (buffer, &map);
|
|
gst_buffer_unref (buffer);
|
|
|
|
return ret;
|
|
|
|
/* ERRORS */
|
|
unsupported_jpeg:
|
|
{
|
|
GST_ELEMENT_ERROR (pay, STREAM, FORMAT, ("Unsupported JPEG"), (NULL));
|
|
gst_buffer_unmap (buffer, &map);
|
|
gst_buffer_unref (buffer);
|
|
return GST_FLOW_NOT_SUPPORTED;
|
|
}
|
|
no_dimension:
|
|
{
|
|
GST_ELEMENT_ERROR (pay, STREAM, FORMAT, ("No size given"), (NULL));
|
|
gst_buffer_unmap (buffer, &map);
|
|
gst_buffer_unref (buffer);
|
|
return GST_FLOW_NOT_NEGOTIATED;
|
|
}
|
|
invalid_format:
|
|
{
|
|
/* error was posted */
|
|
gst_buffer_unmap (buffer, &map);
|
|
gst_buffer_unref (buffer);
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
invalid_quant:
|
|
{
|
|
GST_ELEMENT_ERROR (pay, STREAM, FORMAT, ("Invalid quant tables"), (NULL));
|
|
gst_buffer_unmap (buffer, &map);
|
|
gst_buffer_unref (buffer);
|
|
return GST_FLOW_ERROR;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_rtp_jpeg_pay_set_property (GObject * object, guint prop_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstRtpJPEGPay *rtpjpegpay;
|
|
|
|
rtpjpegpay = GST_RTP_JPEG_PAY (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_JPEG_QUALITY:
|
|
rtpjpegpay->quality = g_value_get_int (value);
|
|
GST_DEBUG_OBJECT (object, "quality = %d", rtpjpegpay->quality);
|
|
break;
|
|
case PROP_JPEG_TYPE:
|
|
rtpjpegpay->type = g_value_get_int (value);
|
|
GST_DEBUG_OBJECT (object, "type = %d", rtpjpegpay->type);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
gst_rtp_jpeg_pay_get_property (GObject * object, guint prop_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GstRtpJPEGPay *rtpjpegpay;
|
|
|
|
rtpjpegpay = GST_RTP_JPEG_PAY (object);
|
|
|
|
switch (prop_id) {
|
|
case PROP_JPEG_QUALITY:
|
|
g_value_set_int (value, rtpjpegpay->quality);
|
|
break;
|
|
case PROP_JPEG_TYPE:
|
|
g_value_set_int (value, rtpjpegpay->type);
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
gboolean
|
|
gst_rtp_jpeg_pay_plugin_init (GstPlugin * plugin)
|
|
{
|
|
return gst_element_register (plugin, "rtpjpegpay", GST_RANK_SECONDARY,
|
|
GST_TYPE_RTP_JPEG_PAY);
|
|
}
|