gstreamer/gst/rtp/gstrtpjpegpay.c
Wim Taymans 0f1033c59f rtpjpegpay: correctly set the type header
Don't require width/height on the caps. Use the SOF header to find width/height
and fall back to the caps if there is no SOF. Also use the SOF info to find the
subsampling and quantization tables used. This allows us to set the right type
value in the JPEG rtp header.

Deprecate the quality property, it's unused now and it was used wrongly before.

Always send full quant tables for now until we have some code to detect default
ones.

Fixes #580880
2009-05-05 16:32:17 +02:00

670 lines
18 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., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/**
* SECTION: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"
/* elementfactory information */
static const GstElementDetails gst_rtp_jpeg_pay_details =
GST_ELEMENT_DETAILS ("RTP JPEG payloader",
"Codec/Payloader/Network",
"Payload-encodes JPEG pictures into RTP packets (RFC 2435)",
"Axis Communications <dev-gstreamer@axis.com>");
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\"")
);
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
*
* 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,
};
#define DEFAULT_JPEG_QUANT 255
#define DEFAULT_JPEG_QUALITY 255
#define DEFAULT_JPEG_TYPE 1
enum
{
PROP_0,
PROP_JPEG_QUALITY,
PROP_JPEG_TYPE
};
enum
{
Q_TABLE_0 = 0,
Q_TABLE_1,
Q_TABLE_MAX /* only support for two tables at the moment */
};
typedef struct _RtpJpegHeader RtpJpegHeader;
typedef struct _RtpQuantHeader RtpQuantHeader;
/*
* 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
{
#if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
guint type_spec:8;
guint offset:24;
#else
guint offset:24;
guint type_spec:8;
#endif
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 |
* | ... |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
struct _RtpQuantHeader
{
guint8 mbz;
guint8 precision;
guint16 length;
};
/* 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 (GstBaseRTPPayload * basepayload,
GstCaps * caps);
static GstFlowReturn gst_rtp_jpeg_pay_handle_buffer (GstBaseRTPPayload * pad,
GstBuffer * buffer);
GST_BOILERPLATE (GstRtpJPEGPay, gst_rtp_jpeg_pay, GstBaseRTPPayload,
GST_TYPE_BASE_RTP_PAYLOAD);
static void
gst_rtp_jpeg_pay_base_init (gpointer klass)
{
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&gst_rtp_jpeg_pay_src_template));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&gst_rtp_jpeg_pay_sink_template));
gst_element_class_set_details (element_class, &gst_rtp_jpeg_pay_details);
}
static void
gst_rtp_jpeg_pay_class_init (GstRtpJPEGPayClass * klass)
{
GObjectClass *gobject_class;
GstBaseRTPPayloadClass *gstbasertppayload_class;
gobject_class = (GObjectClass *) klass;
gstbasertppayload_class = (GstBaseRTPPayloadClass *) klass;
gobject_class->set_property = gst_rtp_jpeg_pay_set_property;
gobject_class->get_property = gst_rtp_jpeg_pay_get_property;
gstbasertppayload_class->set_caps = gst_rtp_jpeg_pay_setcaps;
gstbasertppayload_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_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));
GST_DEBUG_CATEGORY_INIT (rtpjpegpay_debug, "rtpjpegpay", 0,
"Motion JPEG RTP Payloader");
}
static void
gst_rtp_jpeg_pay_init (GstRtpJPEGPay * pay, GstRtpJPEGPayClass * klass)
{
pay->quality = DEFAULT_JPEG_QUALITY;
pay->quant = DEFAULT_JPEG_QUANT;
pay->type = DEFAULT_JPEG_TYPE;
}
static gboolean
gst_rtp_jpeg_pay_setcaps (GstBaseRTPPayload * basepayload, GstCaps * caps)
{
GstStructure *caps_structure = gst_caps_get_structure (caps, 0);
GstRtpJPEGPay *pay;
gint width = 0, height = 0;
pay = GST_RTP_JPEG_PAY (basepayload);
/* these properties are not mandatory, we can get them from the SOF, if there
* is one. */
if (gst_structure_get_int (caps_structure, "height", &height)) {
if (height <= 0 || height > 2040)
goto invalid_dimension;
}
pay->height = height / 8;
if (gst_structure_get_int (caps_structure, "width", &width)) {
if (width <= 0 || width > 2040)
goto invalid_dimension;
}
pay->width = width / 8;
gst_basertppayload_set_options (basepayload, "video", TRUE, "JPEG", 90000);
gst_basertppayload_set_outcaps (basepayload, NULL);
return TRUE;
/* ERRORS */
invalid_dimension:
{
GST_ERROR_OBJECT (pay, "Invalid width/height 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 offset,
const guint8 ** quantizer_table, RtpQuantHeader * qtable, guint8 index)
{
gint quant_size;
quant_size = gst_rtp_jpeg_pay_header_size (data, offset);
GST_LOG ("read quant table %d, size %d", index, quant_size);
qtable->precision |= (data[offset + 2] & 0x10) ? (1 << index) : 0x00;
/* ommit length and precision prefix from table */
quantizer_table[index] = &data[offset + QUANT_PREFIX_LEN];
quant_size -= QUANT_PREFIX_LEN;
qtable->length += quant_size;
return quant_size;
}
typedef struct
{
guint8 id;
guint8 samp;
guint8 qt;
} CompInfo;
static gboolean
gst_rtp_jpeg_pay_read_sof (GstRtpJPEGPay * pay, const guint8 * data,
guint * offset, RtpJpegHeader * header)
{
guint sof_size;
guint width, height, infolen;
CompInfo elem, info[3], *cptr;
gint i, j;
sof_size = gst_rtp_jpeg_pay_header_size (data, *offset);
if (sof_size < 17)
goto wrong_length;
/* skip size */
*offset += 2;
/* precision should be 8 */
if (data[(*offset)++] != 8)
goto bad_precision;
/* read dimensions */
height = data[*offset] << 8 | data[*offset + 1];
width = data[*offset + 2] << 8 | data[*offset + 3];
*offset += 4;
GST_LOG_OBJECT (pay, "got dimensions %ux%u", height, width);
if (height == 0 || height > 2040)
goto invalid_dimension;
if (width == 0 || width > 2040)
goto invalid_dimension;
pay->height = height / 8;
pay->width = width / 8;
header->width = pay->width;
header->height = pay->height;
/* we only support 3 components */
if (data[(*offset)++] != 3)
goto bad_components;
infolen = 0;
for (i = 0; i < 3; i++) {
elem.id = data[(*offset)++];
elem.samp = data[(*offset)++];
elem.qt = data[(*offset)++];
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 */
cptr = &info[0];
if (cptr->samp == 0x21 && cptr->qt == 0)
pay->type = 0;
else if (cptr->samp == 0x22 && cptr->qt == 0)
pay->type = 1;
else
goto invalid_comp;
header->type = pay->type;
cptr = &info[1];
if (!(cptr->samp == 0x11 && cptr->qt == 1))
goto invalid_comp;
cptr = &info[2];
if (!(cptr->samp == 0x11 && cptr->qt == 1))
goto invalid_comp;
return TRUE;
/* ERRORS */
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 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 %02x marker", marker);
return marker;
}
}
static GstFlowReturn
gst_rtp_jpeg_pay_handle_buffer (GstBaseRTPPayload * basepayload,
GstBuffer * buffer)
{
GstRtpJPEGPay *pay;
GstClockTime timestamp;
GstFlowReturn ret = GST_FLOW_ERROR;
RtpJpegHeader jpeg_header;
RtpQuantHeader quant_header;
const guint8 *jpeg_quantizer_table[Q_TABLE_MAX] = { NULL };
guint8 *data;
guint8 quant_table_index = 0;
guint size;
guint mtu;
guint bytes_left;
guint quant_data_size = sizeof (quant_header);
guint jpeg_header_size = 0;
guint offset = 0;
gboolean frame_done = FALSE;
gboolean sos_found = FALSE;
pay = GST_RTP_JPEG_PAY (basepayload);
mtu = GST_BASE_RTP_PAYLOAD_MTU (pay);
/* will be overwritten by SOF when present */
jpeg_header.type_spec = 0;
jpeg_header.offset = 0;
jpeg_header.type = pay->type;
jpeg_header.q = pay->quant;
jpeg_header.width = pay->width;
jpeg_header.height = pay->height;
size = GST_BUFFER_SIZE (buffer);
data = GST_BUFFER_DATA (buffer);
timestamp = GST_BUFFER_TIMESTAMP (buffer);
/* parse the jpeg header for 'start of scan' and read quant tables if needed */
while (!sos_found && (offset < size)) {
switch (gst_rtp_jpeg_pay_scan_marker (data, size, &offset)) {
case JPEG_MARKER_JFIF:
case JPEG_MARKER_CMT:
offset += gst_rtp_jpeg_pay_header_size (data, offset);
break;
case JPEG_MARKER_SOF:
offset += gst_rtp_jpeg_pay_read_sof (pay, data, &offset, &jpeg_header);
break;
case JPEG_MARKER_DHT:
{
guint len;
len = gst_rtp_jpeg_pay_header_size (data, offset);
offset += len;
break;
}
case JPEG_MARKER_DQT:
{
GST_LOG ("DQT found");
if ((jpeg_header.q >= 128) && (quant_table_index < Q_TABLE_MAX)) {
if (!quant_table_index) {
quant_header.length = 0;
quant_header.precision = 0;
quant_header.mbz = 0;
}
quant_data_size += gst_rtp_jpeg_pay_read_quant_table (data, offset,
jpeg_quantizer_table, &quant_header, quant_table_index);
quant_table_index++;
}
offset += gst_rtp_jpeg_pay_header_size (data, offset);
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;
default:
break;
}
}
if (jpeg_header.width == 0 || jpeg_header.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;
bytes_left = sizeof (jpeg_header) + quant_data_size + size;
while (!frame_done) {
GstBuffer *outbuf;
guint8 *payload;
guint payload_size = (bytes_left < mtu ? bytes_left : mtu);
outbuf = gst_rtp_buffer_new_allocate (payload_size, 0, 0);
GST_BUFFER_TIMESTAMP (outbuf) = timestamp;
if (payload_size == bytes_left) {
GST_LOG_OBJECT (pay, "last packet of frame");
frame_done = TRUE;
gst_rtp_buffer_set_marker (outbuf, 1);
}
payload = gst_rtp_buffer_get_payload (outbuf);
memcpy (payload, &jpeg_header, sizeof (jpeg_header));
payload += sizeof (jpeg_header);
payload_size -= sizeof (jpeg_header);
/* only send quant table with first packet */
if (G_UNLIKELY (quant_data_size > 0)) {
guint8 index;
const guint8 table_size = quant_header.length / quant_table_index;
quant_header.length = g_htons (quant_header.length);
memcpy (payload, &quant_header, sizeof (quant_header));
payload += sizeof (quant_header);
for (index = 0; index < quant_table_index; index++) {
GST_LOG_OBJECT (pay, "sending quant data %d, size %d", index,
table_size);
memcpy (payload, jpeg_quantizer_table[index], table_size);
payload += table_size;
}
payload_size -= quant_data_size;
bytes_left -= quant_data_size;
quant_data_size = 0;
}
GST_LOG_OBJECT (pay, "sending payload size %d", payload_size);
memcpy (payload, &data[offset], payload_size);
ret = gst_basertppayload_push (basepayload, outbuf);
if (ret != GST_FLOW_OK)
break;
bytes_left -= payload_size;
offset += payload_size;
#if (G_BYTE_ORDER == G_LITTLE_ENDIAN)
jpeg_header.offset = ((offset & 0x0000FF) << 16) |
((offset & 0xFF0000) >> 16) | (offset & 0x00FF00);
#else
jpeg_header.offset = offset;
#endif
}
gst_buffer_unref (buffer);
return ret;
/* ERRORS */
no_dimension:
{
GST_ELEMENT_ERROR (pay, STREAM, FORMAT, ("No size given"), (NULL));
return GST_FLOW_NOT_NEGOTIATED;
}
}
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_NONE,
GST_TYPE_RTP_JPEG_PAY);
}