diff --git a/ChangeLog b/ChangeLog index 7d7b839c39..32d9b1d056 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,19 @@ +2008-11-14 Wim Taymans + + Patch by: Bjorn Ostby + + * gst/rtp/Makefile.am: + * gst/rtp/gstrtp.c: (plugin_init): + * gst/rtp/gstrtpjpegpay.c: (gst_rtp_jpeg_pay_base_init), + (gst_rtp_jpeg_pay_class_init), (gst_rtp_jpeg_pay_init), + (gst_rtp_jpeg_pay_setcaps), (gst_rtp_jpeg_pay_header_size), + (gst_rtp_jpeg_pay_read_quant_table), + (gst_rtp_jpeg_pay_scan_marker), (gst_rtp_jpeg_pay_handle_buffer), + (gst_rtp_jpeg_pay_set_property), (gst_rtp_jpeg_pay_get_property), + (gst_rtp_jpeg_pay_plugin_init): + * gst/rtp/gstrtpjpegpay.h: + Add JPEG payloader. Fixes #560756. + 2008-11-13 Wim Taymans Patch by: Fabricio Godoy diff --git a/gst/rtp/Makefile.am b/gst/rtp/Makefile.am index 6c4c52da63..d677930ba4 100644 --- a/gst/rtp/Makefile.am +++ b/gst/rtp/Makefile.am @@ -32,6 +32,7 @@ libgstrtp_la_SOURCES = \ gstrtph263pay.c \ gstrtph264depay.c \ gstrtph264pay.c \ + gstrtpjpegpay.c \ gstrtpL16depay.c \ gstrtpL16pay.c \ gstasteriskh263.c \ @@ -101,6 +102,7 @@ noinst_HEADERS = \ gstrtph263pay.h \ gstrtph264depay.h \ gstrtph264pay.h \ + gstrtpjpegpay.h \ gstrtpmp1sdepay.h \ gstrtpmp2tdepay.h \ gstrtpmp2tpay.h \ diff --git a/gst/rtp/gstrtp.c b/gst/rtp/gstrtp.c index 3ada28dabd..b7e000a0af 100644 --- a/gst/rtp/gstrtp.c +++ b/gst/rtp/gstrtp.c @@ -49,6 +49,7 @@ #include "gstrtph263pay.h" #include "gstrtph264depay.h" #include "gstrtph264pay.h" +#include "gstrtpjpegpay.h" #include "gstrtpL16depay.h" #include "gstrtpL16pay.h" #include "gstasteriskh263.h" @@ -158,6 +159,9 @@ plugin_init (GstPlugin * plugin) if (!gst_rtp_h264_pay_plugin_init (plugin)) return FALSE; + if (!gst_rtp_jpeg_pay_plugin_init (plugin)) + return FALSE; + if (!gst_rtp_L16_pay_plugin_init (plugin)) return FALSE; diff --git a/gst/rtp/gstrtpjpegpay.c b/gst/rtp/gstrtpjpegpay.c new file mode 100644 index 0000000000..d59eebec93 --- /dev/null +++ b/gst/rtp/gstrtpjpegpay.c @@ -0,0 +1,514 @@ +/* GStreamer + * Copyright (C) 2008 Axis Communications + * @author Bjorn Ostby + * @author Peter Kjellerstedt + * + * 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 + * @short_description: RTP payloader for JPEG pictures + * + * 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 +#include + +#include "gstrtpjpegpay.h" + +/* elementfactory information */ +static const GstElementDetails gst_rtp_jpeg_pay_details = +GST_ELEMENT_DETAILS ("RTP packet payloader", + "Codec/Payloader/Network", + "Payload-encodes JPEG pictures into a RTP packet", + "Axis Communications "); + +static GstStaticPadTemplate gst_rtp_jpeg_pay_sink_template = + GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("image/jpeg, " + " height = (int) [0, 2040], " + " width = (int) [0, 2040]; " + "video/x-jpeg, " + " height = (int) [0, 2040], " " width = (int) [0, 2040]") + ); + +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 + +/** + * DEFAULT_JPEG_QUALITY: + * + */ +#define DEFAULT_JPEG_QUALITY 0 + +/** + * DEFAULT_JPEG_TYPE: + * + */ +#define DEFAULT_JPEG_TYPE 0 + +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, +}; + +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", + 0, 255, DEFAULT_JPEG_QUALITY, G_PARAM_READWRITE)); + + g_object_class_install_property (G_OBJECT_CLASS (klass), PROP_JPEG_TYPE, + g_param_spec_int ("type", "Type", "JPEG Type", + 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 * rtpjpegpay, GstRtpJPEGPayClass * klass) +{ + rtpjpegpay->quality = DEFAULT_JPEG_QUALITY; + rtpjpegpay->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 *rtpjpegpay; + + rtpjpegpay = GST_RTP_JPEG_PAY (basepayload); + + if (!gst_structure_get_int (caps_structure, "height", &rtpjpegpay->height)) { + goto caps_fail; + } + + if (!gst_structure_get_int (caps_structure, "width", &rtpjpegpay->width)) { + goto caps_fail; + } + + rtpjpegpay->height /= 8; + rtpjpegpay->width /= 8; + + gst_basertppayload_set_options (basepayload, "video", TRUE, "JPEG", 90000); + gst_basertppayload_set_outcaps (basepayload, NULL); + + return TRUE; + +caps_fail: + GST_ERROR_OBJECT (rtpjpegpay, "Failed to get 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 = gst_rtp_jpeg_pay_header_size (data, offset); + + 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; +} + +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)) { + return JPEG_MARKER_EOI; + } else { + return data[(*offset)++]; + } +} + +static GstFlowReturn +gst_rtp_jpeg_pay_handle_buffer (GstBaseRTPPayload * basepayload, + GstBuffer * buffer) +{ + GstRtpJPEGPay *rtpjpegpay; + 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; + + rtpjpegpay = GST_RTP_JPEG_PAY (basepayload); + mtu = GST_BASE_RTP_PAYLOAD_MTU (rtpjpegpay); + + jpeg_header.type_spec = 0; + jpeg_header.offset = 0; + jpeg_header.type = rtpjpegpay->type; + jpeg_header.q = rtpjpegpay->quality; + jpeg_header.width = rtpjpegpay->width; + jpeg_header.height = rtpjpegpay->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: + case JPEG_MARKER_SOF: + case JPEG_MARKER_DHT: + offset += gst_rtp_jpeg_pay_header_size (data, offset); + break; + case JPEG_MARKER_DQT:{ + 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; + jpeg_header_size = offset + gst_rtp_jpeg_pay_header_size (data, offset); + break; + case JPEG_MARKER_EOI: + GST_WARNING ("EOI reached before SOS!"); + break; + case JPEG_MARKER_SOI: + default: + break; + } + } + + 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) { + 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++) { + 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; + } + + 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; +} + +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); +} diff --git a/gst/rtp/gstrtpjpegpay.h b/gst/rtp/gstrtpjpegpay.h new file mode 100644 index 0000000000..5af6176c02 --- /dev/null +++ b/gst/rtp/gstrtpjpegpay.h @@ -0,0 +1,58 @@ +/* GStreamer + * Copyright (C) 2008 Axis Communications + * + * 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. + */ + +#ifndef __GST_RTP_JPEG_PAY_H__ +#define __GST_RTP_JPEG_PAY_H__ + +#include +#include + +G_BEGIN_DECLS +#define GST_TYPE_RTP_JPEG_PAY \ + (gst_rtp_jpeg_pay_get_type()) +#define GST_RTP_JPEG_PAY(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_RTP_JPEG_PAY,GstRtpJPEGPay)) +#define GST_RTP_JPEG_PAY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_RTP_JPEG_PAY,GstRtpJPEGPayClass)) +#define GST_IS_RTP_JPEG_PAY(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_RTP_JPEG_PAY)) +#define GST_IS_RTP_JPEG_PAY_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_RTP_JPEG_PAY)) +typedef struct _GstRtpJPEGPay GstRtpJPEGPay; +typedef struct _GstRtpJPEGPayClass GstRtpJPEGPayClass; + +struct _GstRtpJPEGPay +{ + GstBaseRTPPayload payload; + + gint height; + guint8 quality; + guint8 type; + gint width; +}; + +struct _GstRtpJPEGPayClass +{ + GstBaseRTPPayloadClass parent_class; +}; + +gboolean gst_rtp_jpeg_pay_plugin_init (GstPlugin * plugin); + +G_END_DECLS +#endif /* __GST_RTP_JPEG_PAY_H__ */