gstreamer/ext/libpng/gstpngdec.c
Vineeth T M 63e0b29291 pngdec: change parse logic
Right now in parse logic the signature is checked every time the parse function
is called, and the whole data is the scanned each and every time, even though the
data is scanned in the previous instance. Changing the logic such that, we skip
the bytes which are already scanned in the previous instances of parse. This
helps in avoiding multiple scan of already scanned data/signature.

https://bugzilla.gnome.org/show_bug.cgi?id=737708
2014-11-04 10:55:32 +00:00

643 lines
18 KiB
C

/* GStreamer
* Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
* Copyright (C) 2012 Collabora Ltd.
* Author : Edward Hervey <edward@collabora.com>
* Copyright (C) 2013 Collabora Ltd.
* Author : Sebastian Dröge <sebastian.droege@collabora.co.uk>
* Olivier Crete <olivier.crete@collabora.com>
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
/**
* SECTION:element-pngdec
*
* Decodes png images. If there is no framerate set on sink caps, it sends EOS
* after the first picture.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "gstpngdec.h"
#include <stdlib.h>
#include <string.h>
#include <gst/base/gstbytereader.h>
#include <gst/video/video.h>
#include <gst/video/gstvideometa.h>
#include <gst/video/gstvideopool.h>
#include <gst/gst-i18n-plugin.h>
GST_DEBUG_CATEGORY_STATIC (pngdec_debug);
#define GST_CAT_DEFAULT pngdec_debug
static gboolean gst_pngdec_libpng_init (GstPngDec * pngdec);
static GstFlowReturn gst_pngdec_caps_create_and_set (GstPngDec * pngdec);
static gboolean gst_pngdec_start (GstVideoDecoder * decoder);
static gboolean gst_pngdec_stop (GstVideoDecoder * decoder);
static gboolean gst_pngdec_flush (GstVideoDecoder * decoder);
static gboolean gst_pngdec_set_format (GstVideoDecoder * Decoder,
GstVideoCodecState * state);
static GstFlowReturn gst_pngdec_parse (GstVideoDecoder * decoder,
GstVideoCodecFrame * frame, GstAdapter * adapter, gboolean at_eos);
static GstFlowReturn gst_pngdec_handle_frame (GstVideoDecoder * decoder,
GstVideoCodecFrame * frame);
static gboolean gst_pngdec_decide_allocation (GstVideoDecoder * decoder,
GstQuery * query);
#define parent_class gst_pngdec_parent_class
G_DEFINE_TYPE (GstPngDec, gst_pngdec, GST_TYPE_VIDEO_DECODER);
static GstStaticPadTemplate gst_pngdec_src_pad_template =
GST_STATIC_PAD_TEMPLATE ("src",
GST_PAD_SRC,
GST_PAD_ALWAYS,
GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE
("{ RGBA, RGB, ARGB64, GRAY8, GRAY16_BE }"))
);
static GstStaticPadTemplate gst_pngdec_sink_pad_template =
GST_STATIC_PAD_TEMPLATE ("sink",
GST_PAD_SINK,
GST_PAD_ALWAYS,
GST_STATIC_CAPS ("image/png")
);
static void
gst_pngdec_class_init (GstPngDecClass * klass)
{
GstElementClass *element_class = (GstElementClass *) klass;
GstVideoDecoderClass *vdec_class = (GstVideoDecoderClass *) klass;
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&gst_pngdec_src_pad_template));
gst_element_class_add_pad_template (element_class,
gst_static_pad_template_get (&gst_pngdec_sink_pad_template));
gst_element_class_set_static_metadata (element_class, "PNG image decoder",
"Codec/Decoder/Image",
"Decode a png video frame to a raw image",
"Wim Taymans <wim@fluendo.com>");
vdec_class->start = gst_pngdec_start;
vdec_class->stop = gst_pngdec_stop;
vdec_class->flush = gst_pngdec_flush;
vdec_class->set_format = gst_pngdec_set_format;
vdec_class->parse = gst_pngdec_parse;
vdec_class->handle_frame = gst_pngdec_handle_frame;
vdec_class->decide_allocation = gst_pngdec_decide_allocation;
GST_DEBUG_CATEGORY_INIT (pngdec_debug, "pngdec", 0, "PNG image decoder");
}
static void
gst_pngdec_init (GstPngDec * pngdec)
{
pngdec->png = NULL;
pngdec->info = NULL;
pngdec->endinfo = NULL;
pngdec->color_type = -1;
pngdec->image_ready = FALSE;
pngdec->read_data = 0;
}
static void
user_error_fn (png_structp png_ptr, png_const_charp error_msg)
{
GST_ERROR ("%s", error_msg);
}
static void
user_warning_fn (png_structp png_ptr, png_const_charp warning_msg)
{
GST_WARNING ("%s", warning_msg);
}
static void
user_info_callback (png_structp png_ptr, png_infop info)
{
GstPngDec *pngdec = NULL;
GstFlowReturn ret;
GST_LOG ("info ready");
pngdec = GST_PNGDEC (png_get_io_ptr (png_ptr));
/* Generate the caps and configure */
ret = gst_pngdec_caps_create_and_set (pngdec);
if (ret != GST_FLOW_OK) {
goto beach;
}
/* Allocate output buffer */
ret =
gst_video_decoder_allocate_output_frame (GST_VIDEO_DECODER (pngdec),
pngdec->current_frame);
if (G_UNLIKELY (ret != GST_FLOW_OK))
GST_DEBUG_OBJECT (pngdec, "failed to acquire buffer");
beach:
pngdec->ret = ret;
}
static gboolean
gst_pngdec_set_format (GstVideoDecoder * decoder, GstVideoCodecState * state)
{
GstPngDec *pngdec = (GstPngDec *) decoder;
if (pngdec->input_state)
gst_video_codec_state_unref (pngdec->input_state);
pngdec->input_state = gst_video_codec_state_ref (state);
if (decoder->input_segment.format == GST_FORMAT_TIME)
gst_video_decoder_set_packetized (decoder, TRUE);
else
gst_video_decoder_set_packetized (decoder, FALSE);
/* We'll set format later on */
return TRUE;
}
static void
user_endrow_callback (png_structp png_ptr, png_bytep new_row,
png_uint_32 row_num, int pass)
{
GstPngDec *pngdec = NULL;
pngdec = GST_PNGDEC (png_get_io_ptr (png_ptr));
/* If buffer_out doesn't exist, it means buffer_alloc failed, which
* will already have set the return code */
if (new_row && GST_IS_BUFFER (pngdec->current_frame->output_buffer)) {
GstVideoFrame frame;
GstBuffer *buffer = pngdec->current_frame->output_buffer;
size_t offset;
guint8 *data;
if (!gst_video_frame_map (&frame, &pngdec->output_state->info, buffer,
GST_MAP_WRITE)) {
pngdec->ret = GST_FLOW_ERROR;
return;
}
data = GST_VIDEO_FRAME_COMP_DATA (&frame, 0);
offset = row_num * GST_VIDEO_FRAME_COMP_STRIDE (&frame, 0);
GST_LOG ("got row %u at pass %d, copying in buffer %p at offset %"
G_GSIZE_FORMAT, (guint) row_num, pass,
pngdec->current_frame->output_buffer, offset);
png_progressive_combine_row (pngdec->png, data + offset, new_row);
gst_video_frame_unmap (&frame);
pngdec->ret = GST_FLOW_OK;
} else
pngdec->ret = GST_FLOW_OK;
}
static void
user_end_callback (png_structp png_ptr, png_infop info)
{
GstPngDec *pngdec = NULL;
pngdec = GST_PNGDEC (png_get_io_ptr (png_ptr));
GST_LOG_OBJECT (pngdec, "and we are done reading this image");
if (!pngdec->current_frame->output_buffer)
return;
gst_buffer_unmap (pngdec->current_frame->input_buffer,
&pngdec->current_frame_map);
pngdec->ret =
gst_video_decoder_finish_frame (GST_VIDEO_DECODER (pngdec),
pngdec->current_frame);
pngdec->image_ready = TRUE;
}
static GstFlowReturn
gst_pngdec_caps_create_and_set (GstPngDec * pngdec)
{
GstFlowReturn ret = GST_FLOW_OK;
gint bpc = 0, color_type;
png_uint_32 width, height;
GstVideoFormat format = GST_VIDEO_FORMAT_UNKNOWN;
g_return_val_if_fail (GST_IS_PNGDEC (pngdec), GST_FLOW_ERROR);
/* Get bits per channel */
bpc = png_get_bit_depth (pngdec->png, pngdec->info);
/* Get Color type */
color_type = png_get_color_type (pngdec->png, pngdec->info);
/* Add alpha channel if 16-bit depth, but not for GRAY images */
if ((bpc > 8) && (color_type != PNG_COLOR_TYPE_GRAY)) {
png_set_add_alpha (pngdec->png, 0xffff, PNG_FILLER_BEFORE);
png_set_swap (pngdec->png);
}
#if 0
/* We used to have this HACK to reverse the outgoing bytes, but the problem
* that originally required the hack seems to have been in videoconvert's
* RGBA descriptions. It doesn't seem needed now that's fixed, but might
* still be needed on big-endian systems, I'm not sure. J.S. 6/7/2007 */
if (color_type == PNG_COLOR_TYPE_RGB_ALPHA)
png_set_bgr (pngdec->png);
#endif
/* Gray scale with alpha channel converted to RGB */
if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
GST_LOG_OBJECT (pngdec,
"converting grayscale png with alpha channel to RGB");
png_set_gray_to_rgb (pngdec->png);
}
/* Gray scale converted to upscaled to 8 bits */
if ((color_type == PNG_COLOR_TYPE_GRAY_ALPHA) ||
(color_type == PNG_COLOR_TYPE_GRAY)) {
if (bpc < 8) { /* Convert to 8 bits */
GST_LOG_OBJECT (pngdec, "converting grayscale image to 8 bits");
#if PNG_LIBPNG_VER < 10400
png_set_gray_1_2_4_to_8 (pngdec->png);
#else
png_set_expand_gray_1_2_4_to_8 (pngdec->png);
#endif
}
}
/* Palette converted to RGB */
if (color_type == PNG_COLOR_TYPE_PALETTE) {
GST_LOG_OBJECT (pngdec, "converting palette png to RGB");
png_set_palette_to_rgb (pngdec->png);
}
png_set_interlace_handling (pngdec->png);
/* Update the info structure */
png_read_update_info (pngdec->png, pngdec->info);
/* Get IHDR header again after transformation settings */
png_get_IHDR (pngdec->png, pngdec->info, &width, &height,
&bpc, &pngdec->color_type, NULL, NULL, NULL);
GST_LOG_OBJECT (pngdec, "this is a %dx%d PNG image", (gint) width,
(gint) height);
switch (pngdec->color_type) {
case PNG_COLOR_TYPE_RGB:
GST_LOG_OBJECT (pngdec, "we have no alpha channel, depth is 24 bits");
if (bpc == 8)
format = GST_VIDEO_FORMAT_RGB;
break;
case PNG_COLOR_TYPE_RGB_ALPHA:
GST_LOG_OBJECT (pngdec,
"we have an alpha channel, depth is 32 or 64 bits");
if (bpc == 8)
format = GST_VIDEO_FORMAT_RGBA;
else if (bpc == 16)
format = GST_VIDEO_FORMAT_ARGB64;
break;
case PNG_COLOR_TYPE_GRAY:
GST_LOG_OBJECT (pngdec,
"We have an gray image, depth is 8 or 16 (be) bits");
if (bpc == 8)
format = GST_VIDEO_FORMAT_GRAY8;
else if (bpc == 16)
format = GST_VIDEO_FORMAT_GRAY16_BE;
break;
default:
break;
}
if (format == GST_VIDEO_FORMAT_UNKNOWN) {
GST_ELEMENT_ERROR (pngdec, STREAM, NOT_IMPLEMENTED, (NULL),
("pngdec does not support this color type"));
ret = GST_FLOW_NOT_SUPPORTED;
goto beach;
}
/* Check if output state changed */
if (pngdec->output_state) {
GstVideoInfo *info = &pngdec->output_state->info;
if (width == GST_VIDEO_INFO_WIDTH (info) &&
height == GST_VIDEO_INFO_HEIGHT (info) &&
GST_VIDEO_INFO_FORMAT (info) == format) {
goto beach;
}
gst_video_codec_state_unref (pngdec->output_state);
}
pngdec->output_state =
gst_video_decoder_set_output_state (GST_VIDEO_DECODER (pngdec), format,
width, height, pngdec->input_state);
gst_video_decoder_negotiate (GST_VIDEO_DECODER (pngdec));
GST_DEBUG ("Final %d %d", GST_VIDEO_INFO_WIDTH (&pngdec->output_state->info),
GST_VIDEO_INFO_HEIGHT (&pngdec->output_state->info));
beach:
return ret;
}
static GstFlowReturn
gst_pngdec_handle_frame (GstVideoDecoder * decoder, GstVideoCodecFrame * frame)
{
GstPngDec *pngdec = (GstPngDec *) decoder;
GstFlowReturn ret = GST_FLOW_OK;
GST_LOG_OBJECT (pngdec, "Got buffer, size=%u",
(guint) gst_buffer_get_size (frame->input_buffer));
/* Let libpng come back here on error */
if (setjmp (png_jmpbuf (pngdec->png))) {
GST_WARNING_OBJECT (pngdec, "error during decoding");
ret = GST_FLOW_ERROR;
goto beach;
}
pngdec->current_frame = frame;
/* Progressive loading of the PNG image */
if (!gst_buffer_map (frame->input_buffer, &pngdec->current_frame_map,
GST_MAP_READ)) {
GST_WARNING_OBJECT (pngdec, "Failed to map input buffer");
ret = GST_FLOW_ERROR;
goto beach;
}
png_process_data (pngdec->png, pngdec->info,
pngdec->current_frame_map.data, pngdec->current_frame_map.size);
if (pngdec->image_ready) {
/* Reset ourselves for the next frame */
gst_pngdec_flush (decoder);
GST_LOG_OBJECT (pngdec, "setting up callbacks for next frame");
png_set_progressive_read_fn (pngdec->png, pngdec,
user_info_callback, user_endrow_callback, user_end_callback);
pngdec->image_ready = FALSE;
} else {
/* An error happened and we have to unmap */
gst_buffer_unmap (pngdec->current_frame->input_buffer,
&pngdec->current_frame_map);
}
ret = pngdec->ret;
beach:
return ret;
}
/* Based on pngparse */
#define PNG_SIGNATURE G_GUINT64_CONSTANT (0x89504E470D0A1A0A)
static GstFlowReturn
gst_pngdec_parse (GstVideoDecoder * decoder, GstVideoCodecFrame * frame,
GstAdapter * adapter, gboolean at_eos)
{
gsize toadd = 0;
GstByteReader reader;
gconstpointer data;
guint64 signature;
gsize size;
GstPngDec *pngdec = (GstPngDec *) decoder;
GST_VIDEO_CODEC_FRAME_SET_SYNC_POINT (frame);
/* FIXME : The overhead of using scan_uint32 is massive */
size = gst_adapter_available (adapter);
GST_DEBUG ("Parsing PNG image data (%" G_GSIZE_FORMAT " bytes)", size);
if (size < 8)
goto need_more_data;
data = gst_adapter_map (adapter, size);
gst_byte_reader_init (&reader, data, size);
if (pngdec->read_data == 0) {
if (!gst_byte_reader_peek_uint64_be (&reader, &signature))
goto need_more_data;
if (signature != PNG_SIGNATURE) {
for (;;) {
guint offset;
offset = gst_byte_reader_masked_scan_uint32 (&reader, 0xffffffff,
0x89504E47, 0, gst_byte_reader_get_remaining (&reader));
if (offset == -1) {
gst_adapter_flush (adapter,
gst_byte_reader_get_remaining (&reader) - 4);
goto need_more_data;
}
if (!gst_byte_reader_skip (&reader, offset))
goto need_more_data;
if (!gst_byte_reader_peek_uint64_be (&reader, &signature))
goto need_more_data;
if (signature == PNG_SIGNATURE) {
/* We're skipping, go out, we'll be back */
gst_adapter_flush (adapter, gst_byte_reader_get_pos (&reader));
goto need_more_data;
}
if (!gst_byte_reader_skip (&reader, 4))
goto need_more_data;
}
}
pngdec->read_data = 8;
}
if (!gst_byte_reader_skip (&reader, pngdec->read_data))
goto need_more_data;
for (;;) {
guint32 length;
guint32 code;
if (!gst_byte_reader_get_uint32_be (&reader, &length))
goto need_more_data;
if (!gst_byte_reader_get_uint32_le (&reader, &code))
goto need_more_data;
if (!gst_byte_reader_skip (&reader, length + 4))
goto need_more_data;
if (code == GST_MAKE_FOURCC ('I', 'E', 'N', 'D')) {
/* Have complete frame */
toadd = gst_byte_reader_get_pos (&reader);
GST_DEBUG_OBJECT (decoder, "Have complete frame of size %" G_GSIZE_FORMAT,
toadd);
pngdec->read_data = 0;
goto have_full_frame;
} else
pngdec->read_data += length + 12;
}
g_assert_not_reached ();
return GST_FLOW_ERROR;
need_more_data:
return GST_VIDEO_DECODER_FLOW_NEED_DATA;
have_full_frame:
if (toadd)
gst_video_decoder_add_to_frame (decoder, toadd);
return gst_video_decoder_have_frame (decoder);
}
static gboolean
gst_pngdec_decide_allocation (GstVideoDecoder * bdec, GstQuery * query)
{
GstBufferPool *pool = NULL;
GstStructure *config;
if (!GST_VIDEO_DECODER_CLASS (parent_class)->decide_allocation (bdec, query))
return FALSE;
if (gst_query_get_n_allocation_pools (query) > 0)
gst_query_parse_nth_allocation_pool (query, 0, &pool, NULL, NULL, NULL);
if (pool == NULL)
return FALSE;
config = gst_buffer_pool_get_config (pool);
if (gst_query_find_allocation_meta (query, GST_VIDEO_META_API_TYPE, NULL)) {
gst_buffer_pool_config_add_option (config,
GST_BUFFER_POOL_OPTION_VIDEO_META);
}
gst_buffer_pool_set_config (pool, config);
gst_object_unref (pool);
return TRUE;
}
static gboolean
gst_pngdec_libpng_init (GstPngDec * pngdec)
{
g_return_val_if_fail (GST_IS_PNGDEC (pngdec), FALSE);
GST_LOG ("init libpng structures");
/* initialize png struct stuff */
pngdec->png = png_create_read_struct (PNG_LIBPNG_VER_STRING,
(png_voidp) NULL, user_error_fn, user_warning_fn);
if (pngdec->png == NULL)
goto init_failed;
pngdec->info = png_create_info_struct (pngdec->png);
if (pngdec->info == NULL)
goto info_failed;
pngdec->endinfo = png_create_info_struct (pngdec->png);
if (pngdec->endinfo == NULL)
goto endinfo_failed;
png_set_progressive_read_fn (pngdec->png, pngdec,
user_info_callback, user_endrow_callback, user_end_callback);
return TRUE;
/* ERRORS */
init_failed:
{
GST_ELEMENT_ERROR (pngdec, LIBRARY, INIT, (NULL),
("Failed to initialize png structure"));
return FALSE;
}
info_failed:
{
GST_ELEMENT_ERROR (pngdec, LIBRARY, INIT, (NULL),
("Failed to initialize info structure"));
return FALSE;
}
endinfo_failed:
{
GST_ELEMENT_ERROR (pngdec, LIBRARY, INIT, (NULL),
("Failed to initialize endinfo structure"));
return FALSE;
}
}
static void
gst_pngdec_libpng_clear (GstPngDec * pngdec)
{
png_infopp info = NULL, endinfo = NULL;
GST_LOG ("cleaning up libpng structures");
if (pngdec->info) {
info = &pngdec->info;
}
if (pngdec->endinfo) {
endinfo = &pngdec->endinfo;
}
if (pngdec->png) {
png_destroy_read_struct (&(pngdec->png), info, endinfo);
pngdec->png = NULL;
pngdec->info = NULL;
pngdec->endinfo = NULL;
}
pngdec->color_type = -1;
pngdec->read_data = 0;
}
static gboolean
gst_pngdec_start (GstVideoDecoder * decoder)
{
GstPngDec *pngdec = (GstPngDec *) decoder;
gst_video_decoder_set_packetized (GST_VIDEO_DECODER (pngdec), FALSE);
gst_pngdec_libpng_init (pngdec);
return TRUE;
}
static gboolean
gst_pngdec_stop (GstVideoDecoder * decoder)
{
GstPngDec *pngdec = (GstPngDec *) decoder;
gst_pngdec_libpng_clear (pngdec);
if (pngdec->input_state) {
gst_video_codec_state_unref (pngdec->input_state);
pngdec->input_state = NULL;
}
if (pngdec->output_state) {
gst_video_codec_state_unref (pngdec->output_state);
pngdec->output_state = NULL;
}
return TRUE;
}
/* Clean up the libpng structures */
static gboolean
gst_pngdec_flush (GstVideoDecoder * decoder)
{
gst_pngdec_libpng_clear ((GstPngDec *) decoder);
gst_pngdec_libpng_init ((GstPngDec *) decoder);
return TRUE;
}