/* GStreamer * Copyright (C) <2013> Sreerenj Balachandran * Copyright (C) <2013> Intel Corporation * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "gstwebpdec.h" #define MIN_WIDTH 1 #define MAX_WIDTH 16383 #define MIN_HEIGHT 1 #define MAX_HEIGHT 16383 enum { PROP_0, PROP_BYPASS_FILTERING, PROP_NO_FANCY_UPSAMPLING, PROP_USE_THREADS }; static GstStaticPadTemplate gst_webp_dec_sink_pad_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("image/webp") ); /*Fixme: Add YUV support */ static GstStaticPadTemplate gst_webp_dec_src_pad_template = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("{ RGB, RGBA, BGR, BGRA, ARGB, RGB16}")) ); GST_DEBUG_CATEGORY_STATIC (webp_dec_debug); #define GST_CAT_DEFAULT webp_dec_debug static void gst_webp_dec_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_webp_dec_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); static gboolean gst_webp_dec_start (GstVideoDecoder * bdec); static gboolean gst_webp_dec_stop (GstVideoDecoder * bdec); static gboolean gst_webp_dec_set_format (GstVideoDecoder * dec, GstVideoCodecState * state); static GstFlowReturn gst_webp_dec_parse (GstVideoDecoder * bdec, GstVideoCodecFrame * frame, GstAdapter * adapter, gboolean at_eos); static GstFlowReturn gst_webp_dec_handle_frame (GstVideoDecoder * bdec, GstVideoCodecFrame * frame); static gboolean gst_webp_dec_decide_allocation (GstVideoDecoder * bdec, GstQuery * query); static gboolean gst_webp_dec_reset_frame (GstWebPDec * webpdec); #define gst_webp_dec_parent_class parent_class G_DEFINE_TYPE (GstWebPDec, gst_webp_dec, GST_TYPE_VIDEO_DECODER); static void gst_webp_dec_class_init (GstWebPDecClass * klass) { GObjectClass *gobject_class; GstElementClass *element_class; GstVideoDecoderClass *vdec_class; gobject_class = (GObjectClass *) klass; element_class = (GstElementClass *) klass; vdec_class = (GstVideoDecoderClass *) klass; parent_class = g_type_class_peek_parent (klass); gobject_class->set_property = gst_webp_dec_set_property; gobject_class->get_property = gst_webp_dec_get_property; gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&gst_webp_dec_src_pad_template)); gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&gst_webp_dec_sink_pad_template)); gst_element_class_set_static_metadata (element_class, "WebP image decoder", "Codec/Decoder/Image", "Decode images from WebP format", "Sreerenj Balachandran "); g_object_class_install_property (gobject_class, PROP_BYPASS_FILTERING, g_param_spec_boolean ("bypass-filtering", "Bypass Filtering", "When enabled, skip the in-loop filtering", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_NO_FANCY_UPSAMPLING, g_param_spec_boolean ("no-fancy-upsampling", "No Fancy Upsampling", "When enabled, use faster pointwise upsampler", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property (gobject_class, PROP_USE_THREADS, g_param_spec_boolean ("use-threads", "Use Threads", "When enabled, use multi-threaded decoding", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); vdec_class->start = gst_webp_dec_start; vdec_class->stop = gst_webp_dec_stop; vdec_class->parse = gst_webp_dec_parse; vdec_class->set_format = gst_webp_dec_set_format; vdec_class->handle_frame = gst_webp_dec_handle_frame; vdec_class->decide_allocation = gst_webp_dec_decide_allocation; GST_DEBUG_CATEGORY_INIT (webp_dec_debug, "webpdec", 0, "WebP decoder"); } static void gst_webp_dec_init (GstWebPDec * dec) { GST_DEBUG ("Initialize the webp decoder"); memset (&dec->config, 0, sizeof (dec->config)); dec->saw_header = FALSE; dec->bypass_filtering = FALSE; dec->no_fancy_upsampling = FALSE; dec->use_threads = FALSE; } static gboolean gst_webp_dec_reset_frame (GstWebPDec * webpdec) { GST_DEBUG ("Reset the current frame properties"); webpdec->saw_header = FALSE; if (!WebPInitDecoderConfig (&webpdec->config)) { GST_WARNING_OBJECT (webpdec, "Failed to configure the WebP image decoding libraray"); return FALSE; } return TRUE; } static void gst_webp_dec_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstWebPDec *dec; dec = GST_WEBP_DEC (object); switch (prop_id) { case PROP_BYPASS_FILTERING: dec->bypass_filtering = g_value_get_boolean (value); break; case PROP_NO_FANCY_UPSAMPLING: dec->no_fancy_upsampling = g_value_get_boolean (value); break; case PROP_USE_THREADS: dec->use_threads = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void gst_webp_dec_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstWebPDec *dec; dec = GST_WEBP_DEC (object); switch (prop_id) { case PROP_BYPASS_FILTERING: g_value_set_boolean (value, dec->bypass_filtering); break; case PROP_NO_FANCY_UPSAMPLING: g_value_set_boolean (value, dec->no_fancy_upsampling); break; case PROP_USE_THREADS: g_value_set_boolean (value, dec->use_threads); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static gboolean gst_webp_dec_start (GstVideoDecoder * decoder) { GstWebPDec *webpdec = (GstWebPDec *) decoder; gst_video_decoder_set_packetized (GST_VIDEO_DECODER (webpdec), FALSE); return gst_webp_dec_reset_frame (webpdec); } static gboolean gst_webp_dec_stop (GstVideoDecoder * bdec) { GstWebPDec *webpdec = (GstWebPDec *) bdec; if (webpdec->input_state) { gst_video_codec_state_unref (webpdec->input_state); webpdec->input_state = NULL; } if (webpdec->output_state) { gst_video_codec_state_unref (webpdec->output_state); webpdec->output_state = NULL; } return TRUE; } static gboolean gst_webp_dec_set_format (GstVideoDecoder * decoder, GstVideoCodecState * state) { GstWebPDec *webpdec = (GstWebPDec *) decoder; if (webpdec->input_state) gst_video_codec_state_unref (webpdec->input_state); webpdec->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); return TRUE; } static gboolean gst_webp_dec_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 GstFlowReturn gst_webp_dec_parse (GstVideoDecoder * decoder, GstVideoCodecFrame * frame, GstAdapter * adapter, gboolean at_eos) { gsize toadd = 0; gsize size; gconstpointer data; GstByteReader reader; GstWebPDec *webpdec = (GstWebPDec *) decoder; size = gst_adapter_available (adapter); GST_DEBUG_OBJECT (decoder, "parsing webp image data (%" G_GSIZE_FORMAT " bytes)", size); if (at_eos) { GST_DEBUG ("Flushing all data out"); toadd = size; /* If we have leftover data, throw it away */ if (!webpdec->saw_header) goto drop_frame; goto have_full_frame; } if (!webpdec->saw_header) { guint32 code; if (size < 12) goto need_more_data; data = gst_adapter_map (adapter, size); gst_byte_reader_init (&reader, data, size); if (!gst_byte_reader_get_uint32_le (&reader, &code)) goto error; if (code == GST_MAKE_FOURCC ('R', 'I', 'F', 'F')) { if (!gst_byte_reader_get_uint32_le (&reader, &webpdec->frame_size)) goto error; if (!gst_byte_reader_get_uint32_le (&reader, &code)) goto error; if (code == GST_MAKE_FOURCC ('W', 'E', 'B', 'P')) webpdec->saw_header = TRUE; } } if (!webpdec->saw_header) goto error; if (size >= (webpdec->frame_size + 8)) { toadd = webpdec->frame_size + 8; webpdec->saw_header = FALSE; goto have_full_frame; } 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); drop_frame: gst_adapter_flush (adapter, size); return GST_FLOW_OK; error: return GST_FLOW_ERROR; } static GstFlowReturn gst_webp_dec_update_src_caps (GstWebPDec * dec, GstMapInfo * map_info) { WebPBitstreamFeatures features; GstVideoFormat format = GST_VIDEO_FORMAT_UNKNOWN; if (WebPGetFeatures (map_info->data, map_info->size, &features) != VP8_STATUS_OK) { GST_ERROR_OBJECT (dec, "Failed to execute WebPGetFeatures"); return GST_FLOW_ERROR; } if (features.width < MIN_WIDTH || features.width > MAX_WIDTH || features.height < MIN_HEIGHT || features.height > MAX_HEIGHT) { GST_ERROR_OBJECT (dec, "Dimensions of the frame is unspported by libwebp"); return GST_FLOW_ERROR; } /* TODO: Add support for other formats */ if (features.has_alpha) { format = GST_VIDEO_FORMAT_ARGB; dec->colorspace = MODE_ARGB; } else { format = GST_VIDEO_FORMAT_RGB; dec->colorspace = MODE_RGB; } /* Check if output state changed */ if (dec->output_state) { GstVideoInfo *info = &dec->output_state->info; if (features.width == GST_VIDEO_INFO_WIDTH (info) && features.height == GST_VIDEO_INFO_HEIGHT (info) && GST_VIDEO_INFO_FORMAT (info) == format) { goto beach; } gst_video_codec_state_unref (dec->output_state); } dec->output_state = gst_video_decoder_set_output_state (GST_VIDEO_DECODER (dec), format, features.width, features.height, dec->input_state); if (!gst_video_decoder_negotiate (GST_VIDEO_DECODER (dec))) return GST_FLOW_NOT_NEGOTIATED; beach: return GST_FLOW_OK; } static GstFlowReturn gst_webp_dec_handle_frame (GstVideoDecoder * decoder, GstVideoCodecFrame * frame) { GstWebPDec *webpdec = (GstWebPDec *) decoder; GstMapInfo map_info; GstFlowReturn ret = GST_FLOW_OK; GstVideoFrame vframe; gst_buffer_map (frame->input_buffer, &map_info, GST_MAP_READ); ret = gst_webp_dec_update_src_caps (webpdec, &map_info); if (ret != GST_FLOW_OK) { gst_buffer_unmap (frame->input_buffer, &map_info); gst_video_codec_frame_unref (frame); goto done; } ret = gst_video_decoder_allocate_output_frame (decoder, frame); if (G_UNLIKELY (ret != GST_FLOW_OK)) { GST_ERROR_OBJECT (decoder, "failed to allocate output frame"); ret = GST_FLOW_ERROR; gst_buffer_unmap (frame->input_buffer, &map_info); gst_video_codec_frame_unref (frame); goto done; } if (!gst_video_frame_map (&vframe, &webpdec->output_state->info, frame->output_buffer, GST_MAP_READWRITE)) { GST_ERROR_OBJECT (decoder, "Failed to map output videoframe"); ret = GST_FLOW_ERROR; gst_buffer_unmap (frame->input_buffer, &map_info); gst_video_codec_frame_unref (frame); goto done; } /* configure output buffer parameteres */ webpdec->config.options.bypass_filtering = webpdec->bypass_filtering; webpdec->config.options.no_fancy_upsampling = webpdec->no_fancy_upsampling; webpdec->config.options.use_threads = webpdec->use_threads; webpdec->config.output.colorspace = webpdec->colorspace; webpdec->config.output.u.RGBA.rgba = (uint8_t *) vframe.map[0].data; webpdec->config.output.u.RGBA.stride = GST_VIDEO_FRAME_COMP_STRIDE (&vframe, 0); webpdec->config.output.u.RGBA.size = GST_VIDEO_FRAME_SIZE (&vframe); webpdec->config.output.is_external_memory = 1; if (WebPDecode (map_info.data, map_info.size, &webpdec->config) != VP8_STATUS_OK) { GST_ERROR_OBJECT (decoder, "Failed to decode the webp frame"); ret = GST_FLOW_ERROR; gst_video_frame_unmap (&vframe); gst_buffer_unmap (frame->input_buffer, &map_info); gst_video_codec_frame_unref (frame); goto done; } gst_video_frame_unmap (&vframe); gst_buffer_unmap (frame->input_buffer, &map_info); ret = gst_video_decoder_finish_frame (decoder, frame); if (!gst_webp_dec_reset_frame (webpdec)) { ret = GST_FLOW_ERROR; goto done; } done: return ret; } gboolean gst_webp_dec_register (GstPlugin * plugin) { return gst_element_register (plugin, "webpdec", GST_RANK_PRIMARY, GST_TYPE_WEBP_DEC); }