diff options
-rw-r--r-- | configure.ac | 2 | ||||
-rw-r--r-- | docs/plugins/Makefile.am | 1 | ||||
-rw-r--r-- | docs/plugins/gst-plugins-bad-plugins-docs.sgml | 2 | ||||
-rw-r--r-- | docs/plugins/gst-plugins-bad-plugins-sections.txt | 14 | ||||
-rw-r--r-- | gst/jpegformat/Makefile.am | 9 | ||||
-rw-r--r-- | gst/jpegformat/gstjpegparse.c | 807 | ||||
-rw-r--r-- | gst/jpegformat/gstjpegparse.h | 59 | ||||
-rw-r--r-- | tests/check/Makefile.am | 1 | ||||
-rw-r--r-- | tests/check/elements/jpegparse.c | 187 |
9 files changed, 1082 insertions, 0 deletions
diff --git a/configure.ac b/configure.ac index c9bf97151..142492a64 100644 --- a/configure.ac +++ b/configure.ac @@ -269,6 +269,7 @@ AG_GST_CHECK_PLUGIN(frei0r) AG_GST_CHECK_PLUGIN(h264parse) AG_GST_CHECK_PLUGIN(hdvparse) AG_GST_CHECK_PLUGIN(id3tag) +AG_GST_CHECK_PLUGIN(jpegformat) AG_GST_CHECK_PLUGIN(librfb) AG_GST_CHECK_PLUGIN(liveadder) AG_GST_CHECK_PLUGIN(mpegdemux) @@ -1657,6 +1658,7 @@ gst/frei0r/Makefile gst/h264parse/Makefile gst/hdvparse/Makefile gst/id3tag/Makefile +gst/jpegformat/Makefile gst/legacyresample/Makefile gst/librfb/Makefile gst/liveadder/Makefile diff --git a/docs/plugins/Makefile.am b/docs/plugins/Makefile.am index b688dbf08..32553aedd 100644 --- a/docs/plugins/Makefile.am +++ b/docs/plugins/Makefile.am @@ -145,6 +145,7 @@ EXTRA_HFILES = \ $(top_srcdir)/gst/festival/gstfestival.h \ $(top_srcdir)/gst/legacyresample/gstlegacyresample.h \ $(top_srcdir)/gst/liveadder/liveadder.h \ + $(top_srcdir)/gst/jpegformat/gstjpegparse.h \ $(top_srcdir)/gst/mxf/mxfdemux.h \ $(top_srcdir)/gst/mxf/mxfmux.h \ $(top_srcdir)/gst/nuvdemux/gstnuvdemux.h \ diff --git a/docs/plugins/gst-plugins-bad-plugins-docs.sgml b/docs/plugins/gst-plugins-bad-plugins-docs.sgml index 84f601b6d..de312d44a 100644 --- a/docs/plugins/gst-plugins-bad-plugins-docs.sgml +++ b/docs/plugins/gst-plugins-bad-plugins-docs.sgml @@ -50,6 +50,7 @@ <xi:include href="xml/element-ivorbisdec.xml" /> <xi:include href="xml/element-jackaudiosrc.xml" /> <xi:include href="xml/element-jackaudiosink.xml" /> + <xi:include href="xml/element-jpegparse.xml" /> <xi:include href="xml/element-kateenc.xml" /> <xi:include href="xml/element-katedec.xml" /> <xi:include href="xml/element-kateparse.xml" /> @@ -130,6 +131,7 @@ <xi:include href="xml/plugin-gsm.xml" /> <xi:include href="xml/plugin-h264parse.xml" /> <xi:include href="xml/plugin-jack.xml" /> + <xi:include href="xml/plugin-jpegformat.xml" /> <xi:include href="xml/plugin-kate.xml" /> <xi:include href="xml/plugin-ladspa.xml" /> <xi:include href="xml/plugin-liveadder.xml" /> diff --git a/docs/plugins/gst-plugins-bad-plugins-sections.txt b/docs/plugins/gst-plugins-bad-plugins-sections.txt index cd2d106c6..b60dba484 100644 --- a/docs/plugins/gst-plugins-bad-plugins-sections.txt +++ b/docs/plugins/gst-plugins-bad-plugins-sections.txt @@ -529,6 +529,20 @@ gst_jack_audio_sink_get_type </SECTION> <SECTION> +<FILE>element-jpegparse</FILE> +<TITLE>jpegparse</TITLE> +GstJpegParse +<SUBSECTION Starndard> +GstJpegParseClass +GST_JPEG_PARSE +GST_JPEG_PARSE_CLASS +GST_IS_JPEG_PARSE +GST_IS_JPEG_PARSE_CLASS +GST_TYPE_JPEG_PARSE +gst_jpeg_parse_get_type +</SECTION> + +<SECTION> <FILE>element-legacyresample</FILE> <TITLE>legacyresample</TITLE> GstLegacyresample diff --git a/gst/jpegformat/Makefile.am b/gst/jpegformat/Makefile.am new file mode 100644 index 000000000..1168ebbb7 --- /dev/null +++ b/gst/jpegformat/Makefile.am @@ -0,0 +1,9 @@ +plugin_LTLIBRARIES = libgstjpegformat.la + +libgstjpegformat_la_SOURCES = gstjpegparse.c +libgstjpegformat_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) +libgstjpegformat_la_LIBADD = $(GST_LIBS) $(GST_BASE_LIBS) +libgstjpegformat_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstjpegformat_la_LIBTOOLFLAGS = --tag=disable-static + +noinst_HEADERS = gstjpegparse.h diff --git a/gst/jpegformat/gstjpegparse.c b/gst/jpegformat/gstjpegparse.c new file mode 100644 index 000000000..b8eb060ea --- /dev/null +++ b/gst/jpegformat/gstjpegparse.c @@ -0,0 +1,807 @@ +/* GStreamer + * + * jpegparse: a parser for JPEG streams + * + * Copyright (C) <2009> Arnout Vandecappelle (Essensium/Mind) <arnout@mind.be> + * Víctor Manuel Jáquez Leal <vjaquez@igalia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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:element-jpegparse + * @short_description: JPEG parser + * + * Parses a JPEG stream into JPEG images. It looks for EOI boundaries to + * split a continuous stream into single-frame buffers. Also reads the + * image header searching for image properties such as width and height + * among others. + * <refsect2> + * <title>Example launch line</title> + * |[ + * gst-launch -v souphttpsrc location=... ! jpegparse ! matroskamux ! filesink location=... + * ]| + * The above pipeline fetches a motion JPEG stream from an IP camera over + * HTTP and stores it in a matroska file. + * </refsect2> + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gst/base/gstbytereader.h> + +#include "gstjpegparse.h" + +/* + * JPEG Markers + */ +#define SOF0 0xc0 +#define SOF1 0xc1 +#define SOF2 0xc2 +#define SOF3 0xc3 + +#define SOF5 0xc5 +#define SOF6 0xc6 +#define SOF7 0xc7 + +#define JPG 0xc8 +#define SOF9 0xc9 +#define SOF10 0xca +#define SOF11 0xcb +#define SOF13 0xcd +#define SOF14 0xce +#define SOF15 0xcf + +#define DHT 0xc4 + +#define DAC 0xcc + +#define RST0 0xd0 +#define RST1 0xd1 +#define RST2 0xd2 +#define RST3 0xd3 +#define RST4 0xd4 +#define RST5 0xd5 +#define RST6 0xd6 +#define RST7 0xd7 + +#define SOI 0xd8 +#define EOI 0xd9 +#define SOS 0xda +#define DQT 0xdb +#define DNL 0xdc +#define DRI 0xdd +#define DHP 0xde +#define EXP 0xdf + +#define APP0 0xe0 +#define APP1 0xe1 +#define APP15 0xef + +#define JPG0 0xf0 +#define JPG13 0xfd +#define COM 0xfe + +#define TEM 0x01 + +static const GstElementDetails gst_jpeg_parse_details = +GST_ELEMENT_DETAILS ("JPEG stream parser", + "Codec/Parser/Video", + "Parse JPEG images into single-frame buffers", + "Arnout Vandecappelle (Essensium/Mind) <arnout@mind.be>"); + +static GstStaticPadTemplate gst_jpeg_parse_src_pad_template = +GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("image/jpeg, " + "format = (fourcc) { I420, Y41B, UYVY, YV12 }, " + "width = (int) [ 0, MAX ]," + "height = (int) [ 0, MAX ], " + "interlaced = (boolean) { true, false }, " + "framerate = (fraction) [ 0/1, MAX ], " "parsed = (boolean) true") + ); + +static GstStaticPadTemplate gst_jpeg_parse_sink_pad_template = +GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("image/jpeg, parsed = (boolean) false") + ); + +GST_DEBUG_CATEGORY_STATIC (jpeg_parse_debug); +#define GST_CAT_DEFAULT jpeg_parse_debug + +#define GST_JPEG_PARSE_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GST_TYPE_JPEG_PARSE, GstJpegParsePrivate)) + +struct _GstJpegParsePrivate +{ + GstPad *srcpad; + + GstAdapter *adapter; + + /* negotiated state */ + gint caps_width, caps_height; + gint caps_framerate_numerator; + gint caps_framerate_denominator; + + /* a new segment arrived */ + gboolean new_segment; + + /* the parsed frame size */ + guint16 width, height; + + /* TRUE if the image is interlaced */ + gboolean interlaced; + + /* fourcc color space */ + guint32 fourcc; + + /* TRUE if the src caps sets a specific framerate */ + gboolean has_fps; + + /* the (expected) timestamp of the next frame */ + guint64 next_ts; + + /* duration of the current frame */ + guint64 duration; + + /* video state */ + gint framerate_numerator; + gint framerate_denominator; +}; + +static void gst_jpeg_parse_base_init (gpointer g_class); +static void gst_jpeg_parse_class_init (GstJpegParseClass * klass); +static void gst_jpeg_parse_dispose (GObject * object); + +static GstFlowReturn gst_jpeg_parse_chain (GstPad * pad, GstBuffer * buffer); +static gboolean gst_jpeg_parse_sink_setcaps (GstPad * pad, GstCaps * caps); +static gboolean gst_jpeg_parse_sink_event (GstPad * pad, GstEvent * event); +static GstStateChangeReturn gst_jpeg_parse_change_state (GstElement * element, + GstStateChange transition); + +#define _do_init(bla) \ + GST_DEBUG_CATEGORY_INIT (jpeg_parse_debug, "jpegparse", 0, "JPEG parser"); + +GST_BOILERPLATE_FULL (GstJpegParse, gst_jpeg_parse, GstElement, + GST_TYPE_ELEMENT, _do_init); + +static void +gst_jpeg_parse_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&gst_jpeg_parse_src_pad_template)); + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&gst_jpeg_parse_sink_pad_template)); + gst_element_class_set_details (element_class, &gst_jpeg_parse_details); +} + +static void +gst_jpeg_parse_class_init (GstJpegParseClass * klass) +{ + GstElementClass *gstelement_class; + GObjectClass *gobject_class; + + gstelement_class = (GstElementClass *) klass; + gobject_class = (GObjectClass *) klass; + + g_type_class_add_private (gobject_class, sizeof (GstJpegParsePrivate)); + gobject_class->dispose = gst_jpeg_parse_dispose; + + gstelement_class->change_state = + GST_DEBUG_FUNCPTR (gst_jpeg_parse_change_state); +} + +static void +gst_jpeg_parse_init (GstJpegParse * parse, GstJpegParseClass * g_class) +{ + GstPad *sinkpad; + + parse->priv = GST_JPEG_PARSE_GET_PRIVATE (parse); + + /* create the sink and src pads */ + sinkpad = gst_pad_new_from_static_template (&gst_jpeg_parse_sink_pad_template, + "sink"); + gst_pad_set_chain_function (sinkpad, + GST_DEBUG_FUNCPTR (gst_jpeg_parse_chain)); + gst_pad_set_event_function (sinkpad, + GST_DEBUG_FUNCPTR (gst_jpeg_parse_sink_event)); + gst_pad_set_setcaps_function (sinkpad, + GST_DEBUG_FUNCPTR (gst_jpeg_parse_sink_setcaps)); + gst_element_add_pad (GST_ELEMENT (parse), sinkpad); + + parse->priv->srcpad = + gst_pad_new_from_static_template (&gst_jpeg_parse_src_pad_template, + "src"); + gst_element_add_pad (GST_ELEMENT (parse), parse->priv->srcpad); + + parse->priv->next_ts = GST_CLOCK_TIME_NONE; + parse->priv->adapter = gst_adapter_new (); +} + +static void +gst_jpeg_parse_dispose (GObject * object) +{ + GstJpegParse *parse = GST_JPEG_PARSE (object); + + if (parse->priv->adapter != NULL) { + gst_adapter_clear (parse->priv->adapter); + gst_object_unref (parse->priv->adapter); + parse->priv->adapter = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + + +static gboolean +gst_jpeg_parse_sink_setcaps (GstPad * pad, GstCaps * caps) +{ + GstJpegParse *parse = GST_JPEG_PARSE (GST_OBJECT_PARENT (pad)); + GstStructure *s = gst_caps_get_structure (caps, 0); + const GValue *framerate; + + if ((framerate = gst_structure_get_value (s, "framerate")) != NULL) { + if (GST_VALUE_HOLDS_FRACTION (framerate)) { + parse->priv->framerate_numerator = + gst_value_get_fraction_numerator (framerate); + parse->priv->framerate_denominator = + gst_value_get_fraction_denominator (framerate); + parse->priv->has_fps = TRUE; + GST_DEBUG_OBJECT (parse, "got framerate of %d/%d", + parse->priv->framerate_numerator, parse->priv->framerate_denominator); + } + } + + return TRUE; +} + +/* Flush everything until the next JPEG header. The header is considered + * to be the a start marker (0xff 0xd8) followed by any other marker (0xff ...). + * Returns TRUE if the header was found, FALSE if more data is needed. */ +static gboolean +gst_jpeg_parse_skip_to_jpeg_header (GstJpegParse * parse) +{ + guint available, flush; + gboolean ret = TRUE; + + available = gst_adapter_available (parse->priv->adapter); + if (available < 4) + return FALSE; + flush = gst_adapter_masked_scan_uint32 (parse->priv->adapter, 0xffffff00, + 0xffd8ff00, 0, available); + if (flush == -1) { + flush = available - 3; /* Last 3 bytes + 1 more may match header. */ + ret = FALSE; + } + if (flush > 0) { + GST_LOG_OBJECT (parse, "Skipping %u bytes.", flush); + gst_adapter_flush (parse->priv->adapter, flush); + } + return ret; +} + +static inline gboolean +gst_jpeg_parse_parse_tag_has_entropy_segment (guint8 tag) +{ + if (tag == SOS || (tag >= RST0 && tag <= RST7)) + return TRUE; + return FALSE; +} + +/* Find the next marker, based on the marker at data. data[0] must be 0xff. + * Returns the offset of the next valid marker. Returns -1 if adapter doesn't + * have enough data. */ +static guint +gst_jpeg_parse_match_next_marker (const guint8 * data, guint size) +{ + guint marker_len; + guint8 tag; + + g_return_val_if_fail (data[0] == 0xff, -1); + g_return_val_if_fail (size >= 2, -1); + tag = data[1]; + + if (tag >= RST0 && tag <= EOI) + marker_len = 2; + else if (G_UNLIKELY (size < 4)) + return -1; + else + marker_len = GST_READ_UINT16_BE (data + 2) + 2; + /* Need marker_len for this marker, plus two for the next marker. */ + if (G_UNLIKELY (marker_len + 2 >= size)) + return -1; + if (G_UNLIKELY (gst_jpeg_parse_parse_tag_has_entropy_segment (tag))) { + while (!(data[marker_len] == 0xff && data[marker_len + 1] != 0x00)) { + if (G_UNLIKELY (marker_len + 2 >= size)) + return -1; + ++marker_len; + } + } + return marker_len; +} + +/* Returns the position beyond the end marker, -1 if insufficient data and -2 + if marker lengths are inconsistent. data must start with 0xff. */ +static guint +gst_jpeg_parse_find_end_marker (GstJpegParse * parse, const guint8 * data, + guint size) +{ + guint offset = 0; + + while (1) { + guint marker_len; + guint8 tag; + + if (offset + 1 >= size) + return -1; + + if (data[offset] != 0xff) + return -2; + + /* Skip over extra 0xff */ + while (G_UNLIKELY ((tag = data[offset + 1]) == 0xff)) { + ++offset; + if (G_UNLIKELY (offset + 1 >= size)) + return -1; + } + /* Check for EOI */ + if (G_UNLIKELY (tag == EOI)) { + GST_DEBUG_OBJECT (parse, "EOI at %u", offset); + return offset + 2; + } + /* Skip over this marker. */ + marker_len = gst_jpeg_parse_match_next_marker (data + offset, + size - offset); + if (G_UNLIKELY (marker_len == -1)) { + return -1; + } else { + GST_LOG_OBJECT (parse, "At offset %u: marker %02x, length %u", offset, + tag, marker_len); + offset += marker_len; + } + } +} + +/* scan until EOI, by interpreting marker + length */ +static guint +gst_jpeg_parse_get_image_length (GstJpegParse * parse) +{ + const guint8 *data; + guint size, offset, start = 2; + + size = gst_adapter_available (parse->priv->adapter); + if (size < 4) { + GST_DEBUG_OBJECT (parse, "Insufficient data for end marker."); + return 0; + } + data = gst_adapter_peek (parse->priv->adapter, size); + + g_return_val_if_fail (data[0] == 0xff && data[1] == SOI, 0); + + GST_DEBUG_OBJECT (parse, "Parsing jpeg image data (%u bytes)", size); + + /* skip start marker */ + offset = gst_jpeg_parse_find_end_marker (parse, data + 2, size - 2); + + if (offset == -1) { + GST_DEBUG_OBJECT (parse, "Insufficient data."); + return 0; + } else if (G_UNLIKELY (offset == -2)) { + GST_DEBUG_OBJECT (parse, "Lost sync, resyncing."); + /* FIXME does this make sense at all? This can only happen for broken + * images, and the most likely breakage is that it's truncated. In that + * case, however, we should be looking for a new start marker... */ + while (offset == -2 || offset == -1) { + start++; + while (start + 1 < size && data[start] != 0xff) + start++; + if (G_UNLIKELY (start + 1 >= size)) { + GST_DEBUG_OBJECT (parse, "Insufficient data while resyncing."); + return 0; + } + GST_LOG_OBJECT (parse, "Resyncing from offset %u.", start); + offset = gst_jpeg_parse_find_end_marker (parse, data + start, size - + start); + } + } + + return start + offset; +} + +static gboolean +gst_jpeg_parse_sof (GstJpegParse * parse, GstByteReader * reader) +{ + guint8 numcomps; /* Number of components in image + (1 for gray, 3 for YUV, etc.) */ + guint8 precision; /* precision (in bits) for the samples */ + guint8 compId[3]; /* unique value identifying each component */ + guint8 qtId[3]; /* quantization table ID to use for this comp */ + guint8 blockWidth[3]; /* Array[numComponents] giving the number of + blocks (horiz) in this component */ + guint8 blockHeight[3]; /* Same for the vertical part of this component */ + guint8 i, value; + gint temp; + + if (!gst_byte_reader_skip (reader, 2)) + return FALSE; + + /* Get sample precision */ + if (!gst_byte_reader_get_uint8 (reader, &precision)) + return FALSE; + + /* Get w and h */ + if (!gst_byte_reader_get_uint16_be (reader, &parse->priv->height)) + return FALSE; + if (!gst_byte_reader_get_uint16_be (reader, &parse->priv->width)) + return FALSE; + + /* Get number of components */ + if (!gst_byte_reader_get_uint8 (reader, &numcomps)) + return FALSE; + + if (numcomps > 3) + return FALSE; + + /* Get decimation and quantization table id for each component */ + for (i = 0; i < numcomps; i++) { + /* Get component ID number */ + if (!gst_byte_reader_get_uint8 (reader, &value)) + return FALSE; + compId[i] = value; + + /* Get decimation */ + if (!gst_byte_reader_get_uint8 (reader, &value)) + return FALSE; + blockWidth[i] = (value & 0xf0) >> 4; + blockHeight[i] = (value & 0x0f); + + /* Get quantization table id */ + if (!gst_byte_reader_get_uint8 (reader, &value)) + return FALSE; + qtId[i] = value; + } + + if (numcomps == 1) { + /* gray image - no fourcc */ + parse->priv->fourcc = 0; + } else if (numcomps == 3) { + temp = (blockWidth[0] * blockHeight[0]) / (blockWidth[1] * blockHeight[1]); + + if (temp == 4 && blockHeight[0] == 2) + parse->priv->fourcc = GST_MAKE_FOURCC ('I', '4', '2', '0'); + else if (temp == 4 && blockHeight[0] == 4) + parse->priv->fourcc = GST_MAKE_FOURCC ('Y', '4', '1', 'B'); + else if (temp == 2) + parse->priv->fourcc = GST_MAKE_FOURCC ('U', 'Y', 'V', 'Y'); + else if (temp == 1) + parse->priv->fourcc = GST_MAKE_FOURCC ('Y', 'V', '1', '2'); + else + parse->priv->fourcc = 0; + } else { + return FALSE; + } + + return TRUE; +} + +static gboolean +gst_jpeg_parse_read_header (GstJpegParse * parse, GstBuffer * buffer) +{ + GstByteReader reader = GST_BYTE_READER_INIT_FROM_BUFFER (buffer); + guint8 marker; + guint16 comsize; + gboolean foundSOF = FALSE; + + if (!gst_byte_reader_peek_uint8 (&reader, &marker)) + goto error; + + while (marker == 0xff) { + if (!gst_byte_reader_skip (&reader, 1)) + goto error; + + if (!gst_byte_reader_get_uint8 (&reader, &marker)) + goto error; + + GST_INFO_OBJECT (parse, "marker = %x", marker); + + switch (marker) { + case SOS: /* start of scan (begins compressed data) */ + return foundSOF; + + case SOI: + break; + + case DRI: + if (!gst_byte_reader_skip (&reader, 4)) /* fixed size */ + goto error; + break; + + case APP0: + case APP1: + case APP15: + case COM: + case DHT: + case DQT: + /* Ignore these codes */ + if (!gst_byte_reader_get_uint16_be (&reader, &comsize)) + goto error; + if (!gst_byte_reader_skip (&reader, comsize - 2)) + goto error; + GST_LOG_OBJECT (parse, "comment skiping %u bytes", comsize - 2); + break; + + case SOF2: + parse->priv->interlaced = TRUE; + + case SOF0: + /* Flush length field */ + foundSOF = TRUE; + if (!gst_jpeg_parse_sof (parse, &reader)) + goto error; + + return TRUE; + + default: + /* Not SOF or SOI. Must not be a JPEG file (or file pointer + * is placed wrong). In either case, it's an error. */ + return FALSE; + } + + if (!gst_byte_reader_peek_uint8 (&reader, &marker)) + goto error; + } + + return foundSOF; + +error: + GST_WARNING_OBJECT (parse, "Error parsing image header"); + return FALSE; +} + +static gboolean +gst_jpeg_parse_set_new_caps (GstJpegParse * parse, gboolean header_ok) +{ + GstCaps *caps; + gboolean res; + + caps = gst_caps_new_simple ("image/jpeg", + "parsed", G_TYPE_BOOLEAN, TRUE, NULL); + + if (header_ok == TRUE) { + gst_caps_set_simple (caps, + "format", GST_TYPE_FOURCC, parse->priv->fourcc, + "interlaced", G_TYPE_BOOLEAN, parse->priv->interlaced, + "width", G_TYPE_INT, parse->priv->width, + "height", G_TYPE_INT, parse->priv->height, NULL); + } + + if (parse->priv->has_fps == TRUE) { + /* we have a framerate */ + gst_caps_set_simple (caps, "framerate", GST_TYPE_FRACTION, + parse->priv->framerate_numerator, + parse->priv->framerate_denominator, NULL); + + if (!GST_CLOCK_TIME_IS_VALID (parse->priv->duration) + && parse->priv->framerate_numerator != 0) { + parse->priv->duration = gst_util_uint64_scale_int (GST_SECOND, + parse->priv->framerate_numerator, parse->priv->framerate_denominator); + } + } else { + /* unknown duration */ + parse->priv->duration = GST_CLOCK_TIME_NONE; + } + + GST_DEBUG_OBJECT (parse, "setting downstream caps to %" GST_PTR_FORMAT, caps); + res = gst_pad_set_caps (parse->priv->srcpad, caps); + gst_caps_unref (caps); + + return res; + +} + +static GstFlowReturn +gst_jpeg_parse_push_buffer (GstJpegParse * parse, guint len) +{ + GstBuffer *outbuf; + GstFlowReturn ret = GST_FLOW_OK; + gboolean header_ok; + + outbuf = gst_adapter_take_buffer (parse->priv->adapter, len); + if (outbuf == NULL) { + GST_ELEMENT_ERROR (parse, STREAM, DECODE, + ("Failed to take buffer of size %u", len), + ("Failed to take buffer of size %u", len)); + return GST_FLOW_ERROR; + } + + header_ok = gst_jpeg_parse_read_header (parse, outbuf); + + if (parse->priv->new_segment == TRUE + || parse->priv->width != parse->priv->caps_width + || parse->priv->height != parse->priv->caps_height + || parse->priv->framerate_numerator != + parse->priv->caps_framerate_numerator + || parse->priv->framerate_denominator != + parse->priv->caps_framerate_denominator) { + if (!gst_jpeg_parse_set_new_caps (parse, header_ok)) { + GST_ELEMENT_ERROR (parse, CORE, NEGOTIATION, + ("Can't set caps to the src pad"), ("Can't set caps to the src pad")); + return GST_FLOW_ERROR; + } + + parse->priv->new_segment = FALSE; + parse->priv->caps_width = parse->priv->width; + parse->priv->caps_height = parse->priv->height; + parse->priv->caps_framerate_numerator = parse->priv->framerate_numerator; + parse->priv->caps_framerate_denominator = + parse->priv->framerate_denominator; + } + + GST_BUFFER_TIMESTAMP (outbuf) = parse->priv->next_ts; + + if (parse->priv->has_fps && GST_CLOCK_TIME_IS_VALID (parse->priv->next_ts) + && GST_CLOCK_TIME_IS_VALID (parse->priv->duration)) { + parse->priv->next_ts += parse->priv->duration; + } else { + parse->priv->duration = GST_CLOCK_TIME_NONE; + parse->priv->next_ts = GST_CLOCK_TIME_NONE; + } + + GST_BUFFER_DURATION (outbuf) = parse->priv->duration; + + gst_buffer_set_caps (outbuf, GST_PAD_CAPS (parse->priv->srcpad)); + + GST_LOG_OBJECT (parse, "pushing buffer (ts=%" GST_TIME_FORMAT ", len=%u)", + GST_TIME_ARGS (GST_BUFFER_TIMESTAMP (outbuf)), len); + + ret = gst_pad_push (parse->priv->srcpad, outbuf); + + return ret; +} + +static GstFlowReturn +gst_jpeg_parse_chain (GstPad * pad, GstBuffer * buf) +{ + GstJpegParse *parse; + guint len; + GstClockTime timestamp, duration; + GstFlowReturn ret = GST_FLOW_OK; + + parse = GST_JPEG_PARSE (GST_PAD_PARENT (pad)); + + timestamp = GST_BUFFER_TIMESTAMP (buf); + duration = GST_BUFFER_DURATION (buf); + + gst_adapter_push (parse->priv->adapter, buf); + + while (ret == GST_FLOW_OK && gst_jpeg_parse_skip_to_jpeg_header (parse)) { + if (G_UNLIKELY (!GST_CLOCK_TIME_IS_VALID (parse->priv->next_ts))) + parse->priv->next_ts = timestamp; + + parse->priv->duration = duration; + + len = gst_jpeg_parse_get_image_length (parse); + if (len == 0) + return GST_FLOW_OK; + + ret = gst_jpeg_parse_push_buffer (parse, len); + } + + GST_DEBUG_OBJECT (parse, "No further start marker found."); + return ret; +} + +static gboolean +gst_jpeg_parse_sink_event (GstPad * pad, GstEvent * event) +{ + GstJpegParse *parse; + gboolean res = TRUE; + + parse = GST_JPEG_PARSE (gst_pad_get_parent (pad)); + + GST_DEBUG_OBJECT (parse, "event : %s", GST_EVENT_TYPE_NAME (event)); + + switch (GST_EVENT_TYPE (event)) { + case GST_EVENT_EOS:{ + /* Push the remaining data, even though it's incomplete */ + guint available = gst_adapter_available (parse->priv->adapter); + if (available > 0) + gst_jpeg_parse_push_buffer (parse, available); + gst_pad_push_event (parse->priv->srcpad, event); + break; + } + case GST_EVENT_NEWSEGMENT: + /* Discard any data in the adapter. There should have been an EOS before + * to flush it. */ + gst_adapter_clear (parse->priv->adapter); + gst_pad_push_event (parse->priv->srcpad, event); + parse->priv->new_segment = TRUE; + break; + default: + res = gst_pad_event_default (pad, event); + break; + } + + gst_object_unref (parse); + return res; +} + +static GstStateChangeReturn +gst_jpeg_parse_change_state (GstElement * element, GstStateChange transition) +{ + GstStateChangeReturn ret; + GstJpegParse *parse; + + parse = GST_JPEG_PARSE (element); + + switch (transition) { + case GST_STATE_CHANGE_READY_TO_PAUSED: + parse->priv->has_fps = FALSE; + + parse->priv->interlaced = FALSE; + parse->priv->width = parse->priv->height = 0; + parse->priv->framerate_numerator = 0; + parse->priv->framerate_denominator = 1; + + parse->priv->caps_framerate_numerator = + parse->priv->caps_framerate_denominator = 0; + parse->priv->caps_width = parse->priv->caps_height = -1; + + parse->priv->new_segment = FALSE; + + parse->priv->next_ts = GST_CLOCK_TIME_NONE; + default: + break; + } + + ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); + if (ret != GST_STATE_CHANGE_SUCCESS) + return ret; + + switch (transition) { + case GST_STATE_CHANGE_PAUSED_TO_READY: + gst_adapter_clear (parse->priv->adapter); + break; + default: + break; + } + + return ret; +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + + if (!gst_element_register (plugin, "jpegparse", GST_RANK_PRIMARY + 1, + GST_TYPE_JPEG_PARSE)) + return FALSE; + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "jpegformat", + "JPEG interchange format plugin", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/gst/jpegformat/gstjpegparse.h b/gst/jpegformat/gstjpegparse.h new file mode 100644 index 000000000..3d8c45e19 --- /dev/null +++ b/gst/jpegformat/gstjpegparse.h @@ -0,0 +1,59 @@ +/* GStreamer + * + * jpegparse: a parser for JPEG streams + * + * Copyright (C) <2009> Arnout Vandecappelle (Essensium/Mind) <arnout@mind.be> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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_JPEG_PARSE_H__ +#define __GST_JPEG_PARSE_H__ + +#include <gst/gst.h> +#include <gst/base/gstadapter.h> + +G_BEGIN_DECLS + +#define GST_TYPE_JPEG_PARSE \ + (gst_jpeg_parse_get_type()) +#define GST_JPEG_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_JPEG_PARSE,GstJpegParse)) +#define GST_JPEG_PARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_JPEG_PARSE,GstJpegParseClass)) +#define GST_IS_JPEG_PARSE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_JPEG_PARSE)) +#define GST_IS_JPEG_PARSE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_JPEG_PARSE)) + +typedef struct _GstJpegParse GstJpegParse; +typedef struct _GstJpegParsePrivate GstJpegParsePrivate; +typedef struct _GstJpegParseClass GstJpegParseClass; + +struct _GstJpegParse { + GstElement element; + GstJpegParsePrivate *priv; +}; + +struct _GstJpegParseClass { + GstElementClass parent_class; +}; + +GType gst_jpeg_parse_get_type (void); + +G_END_DECLS + +#endif /* __GST_JPEG_PARSE_H__ */ diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am index 900bbc089..70b1e5c70 100644 --- a/tests/check/Makefile.am +++ b/tests/check/Makefile.am @@ -113,6 +113,7 @@ check_PROGRAMS = \ elements/asfmux \ elements/capssetter \ elements/legacyresample \ + elements/jpegparse \ elements/qtmux \ elements/selector \ elements/shapewipe \ diff --git a/tests/check/elements/jpegparse.c b/tests/check/elements/jpegparse.c new file mode 100644 index 000000000..8c93c3bb6 --- /dev/null +++ b/tests/check/elements/jpegparse.c @@ -0,0 +1,187 @@ +/* GStreamer + * + * unit test for jpegparse + * + * Copyright (C) <2009> Arnout Vandecappelle (Essensium/Mind) <arnout@mind.be> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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. + */ + +#include <unistd.h> + +#include <gst/check/gstcheck.h> + +/* This test doesn't use actual JPEG data, but some fake data that we know + will trigger certain paths in jpegparse. */ + +guint8 test_data_garbage[] = { 0x00, 0x01, 0xff, 0x32, 0x00, 0xff }; +guint8 test_data_short_frame[] = { 0xff, 0xd8, 0xff, 0xd9 }; + +guint8 test_data_normal_frame[] = { 0xff, 0xd8, 0xff, 0x12, 0x00, 0x03, 0x33, + 0xff, 0xd9 +}; + +guint8 test_data_entropy[] = { 0xff, 0xd8, 0xff, 0xda, 0x00, 0x04, 0x22, 0x33, + 0x44, 0xff, 0x00, 0x55, 0xff, 0x04, 0x00, 0x04, 0x22, 0x33, 0xff, 0xd9 +}; +guint8 test_data_ff[] = { 0xff, 0xff }; + +guint8 test_data_extra_ff[] = { 0xff, 0xd8, 0xff, 0xff, 0xff, 0x12, 0x00, 0x03, + 0x33, 0xff, 0xff, 0xff, 0xd9 +}; + +static GList * +_make_buffers_in (GList * buffer_in, guint8 * test_data, gsize test_data_size) +{ + GstBuffer *buffer; + gsize i; + + for (i = 0; i < test_data_size; i++) { + buffer = gst_buffer_new (); + gst_buffer_set_data (buffer, test_data + i, 1); + gst_buffer_set_caps (buffer, gst_caps_new_simple ("image/jpeg", "parsed", + G_TYPE_BOOLEAN, FALSE, NULL)); + buffer_in = g_list_append (buffer_in, buffer); + } + return buffer_in; +} + +#define make_buffers_in(buffer_in, test_data) \ + _make_buffers_in(buffer_in, test_data, sizeof(test_data)) + +static GList * +_make_buffers_out (GList * buffer_out, guint8 * test_data, gsize test_data_size) +{ + GstBuffer *buffer; + + buffer = gst_buffer_new (); + gst_buffer_set_data (buffer, test_data, test_data_size); + gst_buffer_set_caps (buffer, gst_caps_new_simple ("image/jpeg", "parsed", + G_TYPE_BOOLEAN, TRUE, NULL)); + buffer_out = g_list_append (buffer_out, buffer); + return buffer_out; +} + +#define make_buffers_out(buffer_out, test_data) \ + _make_buffers_out(buffer_out, test_data, sizeof(test_data)) + +GST_START_TEST (test_parse_single_byte) +{ + GList *buffer_in = NULL, *buffer_out = NULL; + + /* Push the data byte by byte, injecting some garbage. */ + buffer_in = make_buffers_in (buffer_in, test_data_garbage); + buffer_in = make_buffers_in (buffer_in, test_data_short_frame); + buffer_in = make_buffers_in (buffer_in, test_data_garbage); + buffer_in = make_buffers_in (buffer_in, test_data_normal_frame); + buffer_in = make_buffers_in (buffer_in, test_data_ff); + buffer_in = make_buffers_in (buffer_in, test_data_entropy); + buffer_in = make_buffers_in (buffer_in, test_data_extra_ff); + + buffer_out = make_buffers_out (buffer_out, test_data_short_frame); + buffer_out = make_buffers_out (buffer_out, test_data_normal_frame); + buffer_out = make_buffers_out (buffer_out, test_data_entropy); + buffer_out = make_buffers_out (buffer_out, test_data_extra_ff); + gst_check_element_push_buffer_list ("jpegparse", buffer_in, buffer_out, + GST_FLOW_OK); +} + +GST_END_TEST; + + + +GST_START_TEST (test_parse_all_in_one_buf) +{ + GList *buffer_in = NULL, *buffer_out = NULL; + GstBuffer *buffer = NULL; + gsize total_size = 0; + gsize offset = 0; + + /* Push the data in a single buffer, injecting some garbage. */ + total_size += sizeof (test_data_garbage); + total_size += sizeof (test_data_short_frame); + total_size += sizeof (test_data_garbage); + total_size += sizeof (test_data_normal_frame); + total_size += sizeof (test_data_ff); + total_size += sizeof (test_data_entropy); + total_size += sizeof (test_data_extra_ff); + buffer = gst_buffer_new_and_alloc (total_size); + memcpy (GST_BUFFER_DATA (buffer) + offset, test_data_garbage, + sizeof (test_data_garbage)); + offset += sizeof (test_data_garbage); + memcpy (GST_BUFFER_DATA (buffer) + offset, test_data_short_frame, + sizeof (test_data_short_frame)); + offset += sizeof (test_data_short_frame); + memcpy (GST_BUFFER_DATA (buffer) + offset, test_data_garbage, + sizeof (test_data_garbage)); + offset += sizeof (test_data_garbage); + memcpy (GST_BUFFER_DATA (buffer) + offset, test_data_normal_frame, + sizeof (test_data_normal_frame)); + offset += sizeof (test_data_normal_frame); + memcpy (GST_BUFFER_DATA (buffer) + offset, test_data_ff, + sizeof (test_data_ff)); + offset += sizeof (test_data_ff); + memcpy (GST_BUFFER_DATA (buffer) + offset, test_data_entropy, + sizeof (test_data_entropy)); + offset += sizeof (test_data_entropy); + memcpy (GST_BUFFER_DATA (buffer) + offset, test_data_extra_ff, + sizeof (test_data_extra_ff)); + offset += sizeof (test_data_extra_ff); + + gst_buffer_set_caps (buffer, gst_caps_new_simple ("image/jpeg", "parsed", + G_TYPE_BOOLEAN, FALSE, NULL)); + GST_LOG ("Pushing single buffer of %u bytes.", total_size); + buffer_in = g_list_append (buffer_in, buffer); + + buffer_out = make_buffers_out (buffer_out, test_data_short_frame); + buffer_out = make_buffers_out (buffer_out, test_data_normal_frame); + buffer_out = make_buffers_out (buffer_out, test_data_entropy); + buffer_out = make_buffers_out (buffer_out, test_data_extra_ff); + gst_check_element_push_buffer_list ("jpegparse", buffer_in, buffer_out, + GST_FLOW_OK); +} + +GST_END_TEST; + +Suite * +jpegparse_suite (void) +{ + Suite *s = suite_create ("jpegparse"); + TCase *tc_chain = tcase_create ("jpegparse"); + + suite_add_tcase (s, tc_chain); + tcase_add_test (tc_chain, test_parse_single_byte); + tcase_add_test (tc_chain, test_parse_all_in_one_buf); + + return s; +} + +int +main (int argc, char **argv) +{ + int nf; + + Suite *s = jpegparse_suite (); + SRunner *sr = srunner_create (s); + + gst_check_init (&argc, &argv); + + srunner_run_all (sr, CK_NORMAL); + nf = srunner_ntests_failed (sr); + srunner_free (sr); + + return nf; +} |