summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--configure.ac2
-rw-r--r--docs/plugins/Makefile.am1
-rw-r--r--docs/plugins/gst-plugins-bad-plugins-docs.sgml2
-rw-r--r--docs/plugins/gst-plugins-bad-plugins-sections.txt14
-rw-r--r--gst/jpegformat/Makefile.am9
-rw-r--r--gst/jpegformat/gstjpegparse.c807
-rw-r--r--gst/jpegformat/gstjpegparse.h59
-rw-r--r--tests/check/Makefile.am1
-rw-r--r--tests/check/elements/jpegparse.c187
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;
+}