gstreamer/gst/jpegformat/gstjpegparse.c
Arun Raghavan 8ca66f336e jpegformat: Push tags after setting srcpad caps
This patch defers emission of tag events till caps are set on the source
pad of jpegparse, so that these tags can be seen downstream.

https://bugzilla.gnome.org/show_bug.cgi?id=627211
2010-11-19 14:57:59 -03:00

980 lines
30 KiB
C

/* 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. Jpegparse can also extract metadata (e.g. xmp).
*
* <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>
*/
/* FIXME: output plain JFIF APP marker only. This provides best code reuse.
* JPEG decoders would not need to handle this part anymore. Also when remuxing
* (... ! jpegparse ! ... ! jifmux ! ...) metadata consolidation would be
* easier.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <string.h>
#include <gst/base/gstbytereader.h>
#include <gst/tag/tag.h>
#include "gstjpegparse.h"
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
struct _GstJpegParsePrivate
{
GstPad *srcpad;
GstAdapter *adapter;
guint last_offset;
guint last_entropy_len;
gboolean last_resync;
/* 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;
/* tags */
GstTagList *tags;
};
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 GstCaps *gst_jpeg_parse_src_getcaps (GstPad * pad);
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_simple (element_class,
"JPEG stream parser",
"Video/Parser",
"Parse JPEG images into single-frame buffers",
"Arnout Vandecappelle (Essensium/Mind) <arnout@mind.be>");
}
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 = G_TYPE_INSTANCE_GET_PRIVATE (parse, GST_TYPE_JPEG_PARSE,
GstJpegParsePrivate);
/* 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_pad_set_getcaps_function (parse->priv->srcpad,
GST_DEBUG_FUNCPTR (gst_jpeg_parse_src_getcaps));
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;
}
static GstCaps *
gst_jpeg_parse_src_getcaps (GstPad * pad)
{
GstCaps *result;
if ((result = GST_PAD_CAPS (pad))) {
result = gst_caps_ref (result);
GST_DEBUG_OBJECT (pad, "using pad caps %" GST_PTR_FORMAT, result);
} else {
result = gst_caps_ref (GST_PAD_TEMPLATE_CAPS (GST_PAD_PAD_TEMPLATE (pad)));
GST_DEBUG_OBJECT (pad, "using pad template caps %" GST_PTR_FORMAT, result);
}
return result;
}
/*
* gst_jpeg_parse_skip_to_jpeg_header:
* @parse: the parser
*
* Flush everything until the next JPEG header. The header is considered
* to be the a start marker SOI (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;
}
/* returns image length in bytes if parsed
* successfully, otherwise 0 if not enough data */
static guint
gst_jpeg_parse_get_image_length (GstJpegParse * parse)
{
guint size;
gboolean resync;
GstAdapter *adapter = parse->priv->adapter;
gint offset, noffset;
size = gst_adapter_available (adapter);
/* we expect at least 4 bytes, first of which start marker */
if (gst_adapter_masked_scan_uint32 (adapter, 0xffff0000, 0xffd80000, 0, 4))
return 0;
GST_DEBUG ("Parsing jpeg image data (%u bytes)", size);
GST_DEBUG ("Parse state: offset=%d, resync=%d, entropy len=%d",
parse->priv->last_offset, parse->priv->last_resync,
parse->priv->last_entropy_len);
/* offset is 2 less than actual offset;
* - adapter needs at least 4 bytes for scanning,
* - start and end marker ensure at least that much
*/
/* resume from state offset */
offset = parse->priv->last_offset;
while (1) {
guint frame_len;
guint32 value;
noffset =
gst_adapter_masked_scan_uint32_peek (adapter, 0x0000ff00, 0x0000ff00,
offset, size - offset, &value);
/* lost sync if 0xff marker not where expected */
if ((resync = (noffset != offset))) {
GST_DEBUG ("Lost sync at 0x%08x, resyncing", offset + 2);
}
/* may have marker, but could have been resyncng */
resync = resync || parse->priv->last_resync;
/* Skip over extra 0xff */
while ((noffset >= 0) && ((value & 0xff) == 0xff)) {
noffset++;
noffset =
gst_adapter_masked_scan_uint32_peek (adapter, 0x0000ff00, 0x0000ff00,
noffset, size - noffset, &value);
}
/* enough bytes left for marker? (we need 0xNN after the 0xff) */
if (noffset < 0) {
GST_DEBUG ("at end of input and no EOI marker found, need more data");
goto need_more_data;
}
/* now lock on the marker we found */
offset = noffset;
value = value & 0xff;
if (value == 0xd9) {
GST_DEBUG ("0x%08x: EOI marker", offset + 2);
/* clear parse state */
parse->priv->last_resync = FALSE;
parse->priv->last_offset = 0;
return (offset + 4);
}
if (value >= 0xd0 && value <= 0xd7)
frame_len = 0;
else {
/* peek tag and subsequent length */
if (offset + 2 + 4 > size)
goto need_more_data;
else
gst_adapter_masked_scan_uint32_peek (adapter, 0x0, 0x0, offset + 2, 4,
&frame_len);
frame_len = frame_len & 0xffff;
}
GST_DEBUG ("0x%08x: tag %02x, frame_len=%u", offset + 2, value, frame_len);
/* the frame length includes the 2 bytes for the length; here we want at
* least 2 more bytes at the end for an end marker */
if (offset + 2 + 2 + frame_len + 2 > size) {
goto need_more_data;
}
if (gst_jpeg_parse_parse_tag_has_entropy_segment (value)) {
guint eseglen = parse->priv->last_entropy_len;
GST_DEBUG ("0x%08x: finding entropy segment length", offset + 2);
noffset = offset + 2 + frame_len + eseglen;
while (1) {
noffset = gst_adapter_masked_scan_uint32_peek (adapter, 0x0000ff00,
0x0000ff00, noffset, size - noffset, &value);
if (noffset < 0) {
/* need more data */
parse->priv->last_entropy_len = size - offset - 4 - frame_len - 2;
goto need_more_data;
}
if ((value & 0xff) != 0x00) {
eseglen = noffset - offset - frame_len - 2;
break;
}
noffset++;
}
parse->priv->last_entropy_len = 0;
frame_len += eseglen;
GST_DEBUG ("entropy segment length=%u => frame_len=%u", eseglen,
frame_len);
}
if (resync) {
/* check if we will still be in sync if we interpret
* this as a sync point and skip this frame */
noffset = offset + frame_len + 2;
noffset = gst_adapter_masked_scan_uint32 (adapter, 0x0000ff00, 0x0000ff00,
noffset, 4);
if (noffset < 0) {
/* ignore and continue resyncing until we hit the end
* of our data or find a sync point that looks okay */
continue;
}
GST_DEBUG ("found sync at 0x%x", offset + 2);
}
offset += frame_len + 2;
}
/* EXITS */
need_more_data:
{
parse->priv->last_offset = offset;
parse->priv->last_resync = resync;
return 0;
}
}
static inline gboolean
gst_jpeg_parse_sof (GstJpegParse * parse, GstByteReader * reader)
{
guint8 numcomps = 0; /* 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 = 0;
gint temp;
/* flush length field */
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;
}
GST_DEBUG_OBJECT (parse, "Header parsed");
return TRUE;
}
static inline gboolean
gst_jpeg_parse_skip_marker (GstJpegParse * parse,
GstByteReader * reader, guint8 marker)
{
guint16 size;
if (!gst_byte_reader_get_uint16_be (reader, &size))
return FALSE;
if (!gst_byte_reader_skip (reader, size - 2))
return FALSE;
GST_LOG_OBJECT (parse, "unhandled marker %x skiping %u bytes", marker, size);
return TRUE;
}
static gboolean
gst_jpeg_parse_read_header (GstJpegParse * parse, GstBuffer * buffer)
{
GstByteReader reader = GST_BYTE_READER_INIT_FROM_BUFFER (buffer);
guint8 marker = 0;
guint16 size = 0;
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_DEBUG_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 COM:{ /* read comment and post as tag */
const guint8 *comment = NULL;
if (!gst_byte_reader_get_uint16_be (&reader, &size))
goto error;
if (!gst_byte_reader_get_data (&reader, size - 2, &comment))
goto error;
if (!parse->priv->tags)
parse->priv->tags = gst_tag_list_new ();
gst_tag_list_add (parse->priv->tags, GST_TAG_MERGE_REPLACE,
GST_TAG_COMMENT, comment, NULL);
break;
}
case APP1:{
const gchar *id_str;
if (!gst_byte_reader_get_uint16_be (&reader, &size))
goto error;
if (!gst_byte_reader_get_string_utf8 (&reader, &id_str))
goto error;
if (!strcmp (id_str, "Exif")) {
const guint8 *exif_data = NULL;
guint exif_size = size - 2 - 6; /* 6 bytes for "Exif\0\0 id" */
GstTagList *tags;
GstBuffer *buf;
/* skip padding */
gst_byte_reader_skip (&reader, 1);
/* handle exif metadata */
if (!gst_byte_reader_get_data (&reader, exif_size, &exif_data))
goto error;
buf = gst_buffer_new ();
GST_BUFFER_DATA (buf) = (guint8 *) exif_data;
GST_BUFFER_SIZE (buf) = exif_size;
tags = gst_tag_list_from_exif_buffer_with_tiff_header (buf);
gst_buffer_unref (buf);
if (tags) {
GST_INFO_OBJECT (parse, "got exif metadata");
if (!parse->priv->tags)
parse->priv->tags = gst_tag_list_new ();
gst_tag_list_insert (parse->priv->tags, tags,
GST_TAG_MERGE_REPLACE);
gst_tag_list_free (tags);
}
GST_LOG_OBJECT (parse, "parsed marker %x: '%s' %u bytes",
marker, id_str, size - 2);
} else if (!strcmp (id_str, "http://ns.adobe.com/xap/1.0/")) {
const guint8 *xmp_data = NULL;
guint xmp_size = size - 2 - 29; /* 29 bytes for the id */
GstTagList *tags;
GstBuffer *buf;
/* handle xmp metadata */
if (!gst_byte_reader_get_data (&reader, xmp_size, &xmp_data))
goto error;
buf = gst_buffer_new ();
GST_BUFFER_DATA (buf) = (guint8 *) xmp_data;
GST_BUFFER_SIZE (buf) = xmp_size;
tags = gst_tag_list_from_xmp_buffer (buf);
gst_buffer_unref (buf);
if (tags) {
GST_INFO_OBJECT (parse, "got xmp metadata");
if (!parse->priv->tags)
parse->priv->tags = gst_tag_list_new ();
gst_tag_list_insert (parse->priv->tags, tags,
GST_TAG_MERGE_REPLACE);
gst_tag_list_free (tags);
}
GST_LOG_OBJECT (parse, "parsed marker %x: '%s' %u bytes",
marker, id_str, size - 2);
} else {
if (!gst_byte_reader_skip (&reader, size - 3 - strlen (id_str)))
goto error;
GST_LOG_OBJECT (parse, "unhandled marker %x: '%s' skiping %u bytes",
marker, id_str, size - 2);
}
break;
}
case APP0:
case APP2:
case APP13:
case APP14:
case APP15:{
const gchar *id_str;
if (!gst_byte_reader_get_uint16_be (&reader, &size))
goto error;
if (!gst_byte_reader_get_string_utf8 (&reader, &id_str))
goto error;
if (!gst_byte_reader_skip (&reader, size - 3 - strlen (id_str)))
goto error;
GST_LOG_OBJECT (parse, "unhandled marker %x: '%s' skiping %u bytes",
marker, id_str, size - 2);
break;
}
case DHT:
case DQT:
/* Ignore these codes */
if (!gst_jpeg_parse_skip_marker (parse, &reader, marker))
goto error;
break;
case SOF2:
parse->priv->interlaced = TRUE;
/* fall through */
case SOF0:
foundSOF = TRUE;
/* parse Start Of Frame */
if (!gst_jpeg_parse_sof (parse, &reader))
goto error;
return TRUE;
default:
if (marker == JPG || (marker >= JPG0 && marker <= JPG13)) {
/* we'd like to remove them from the buffer */
#if 1
guint pos = gst_byte_reader_get_pos (&reader);
guint8 *data = GST_BUFFER_DATA (buffer);
if (!gst_byte_reader_peek_uint16_be (&reader, &size))
goto error;
if (gst_byte_reader_get_remaining (&reader) < size)
goto error;
GST_LOG_OBJECT (parse, "unhandled marker %x removing %u bytes",
marker, size);
memmove (&data[pos], &data[pos + size],
GST_BUFFER_SIZE (buffer) - (pos + size));
GST_BUFFER_SIZE (buffer) -= size;
if (!gst_byte_reader_set_pos (&reader, pos - size))
goto error;
#else
if (!gst_jpeg_parse_skip_marker (parse, &reader, marker))
goto error;
#endif
} else {
GST_WARNING_OBJECT (parse, "unhandled marker %x, leaving", marker);
/* 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 (need more than %u bytes available)",
gst_byte_reader_get_remaining (&reader));
return FALSE;
}
static gboolean
gst_jpeg_parse_set_new_caps (GstJpegParse * parse, gboolean header_ok)
{
GstCaps *caps;
gboolean res;
GST_DEBUG_OBJECT (parse, "setting caps on srcpad (hdr_ok=%d, have_fps=%d)",
header_ok, parse->priv->has_fps);
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_caps_set_simple (caps, "framerate", GST_TYPE_FRACTION, 1, 1, NULL);
}
GST_DEBUG_OBJECT (parse,
"setting downstream caps on %s:%s to %" GST_PTR_FORMAT,
GST_DEBUG_PAD_NAME (parse->priv->srcpad), 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;
/* reset the offset (only when we flushed) */
parse->priv->last_offset = 0;
parse->priv->last_entropy_len = 0;
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;
}
if (parse->priv->tags) {
GST_DEBUG_OBJECT (parse, "Pushing tags");
gst_element_found_tags_for_pad (GST_ELEMENT_CAST (parse),
parse->priv->srcpad, parse->priv->tags);
parse->priv->tags = NULL;
}
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;
parse->priv->tags = NULL;
}
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;
/* check if we already have a EOI */
len = gst_jpeg_parse_get_image_length (parse);
if (len == 0)
return GST_FLOW_OK;
GST_LOG_OBJECT (parse, "parsed image of size %d", len);
/* now we have enough in the adapter to process a full jpeg image */
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_FLUSH_STOP:
parse->priv->next_ts = GST_CLOCK_TIME_NONE;
parse->priv->last_offset = 0;
parse->priv->last_entropy_len = 0;
parse->priv->last_resync = FALSE;
gst_adapter_clear (parse->priv->adapter);
break;
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);
res = 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);
res = gst_pad_push_event (parse->priv->srcpad, event);
parse->priv->new_segment = TRUE;
break;
case GST_EVENT_TAG:{
GstTagList *taglist = NULL;
gst_event_parse_tag (event, &taglist);
if (!parse->priv->new_segment)
res = gst_pad_event_default (pad, event);
else {
/* Hold on to the tags till the srcpad caps are definitely set */
if (!parse->priv->tags)
parse->priv->tags = gst_tag_list_new ();
gst_tag_list_insert (parse->priv->tags, taglist, GST_TAG_MERGE_REPLACE);
}
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;
parse->priv->last_offset = 0;
parse->priv->last_entropy_len = 0;
parse->priv->last_resync = FALSE;
parse->priv->tags = NULL;
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);
if (parse->priv->tags)
gst_tag_list_free (parse->priv->tags);
break;
default:
break;
}
return ret;
}