| /* GStreamer |
| * Copyright (C) <2013> Sreerenj Balachandran <sreerenj.balachandran@intel.com> |
| * 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 <string.h> |
| |
| #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_sink_event (GstVideoDecoder * bdec, |
| GstEvent * event); |
| |
| 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 <sreerenj.balachandrn@intel.com>"); |
| |
| 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; |
| vdec_class->sink_event = gst_webp_dec_sink_event; |
| |
| 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; |
| gst_video_decoder_set_use_default_pad_acceptcaps (GST_VIDEO_DECODER_CAST |
| (dec), TRUE); |
| GST_PAD_SET_ACCEPT_TEMPLATE (GST_VIDEO_DECODER_SINK_PAD (dec)); |
| } |
| |
| 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; |
| |
| 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); |
| |
| 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 gboolean |
| gst_webp_dec_sink_event (GstVideoDecoder * bdec, GstEvent * event) |
| { |
| const GstSegment *segment; |
| |
| if (GST_EVENT_TYPE (event) != GST_EVENT_SEGMENT) |
| goto done; |
| |
| gst_event_parse_segment (event, &segment); |
| |
| if (segment->format == GST_FORMAT_TIME) |
| gst_video_decoder_set_packetized (bdec, TRUE); |
| else |
| gst_video_decoder_set_packetized (bdec, FALSE); |
| |
| done: |
| return GST_VIDEO_DECODER_CLASS (parent_class)->sink_event (bdec, event); |
| } |
| |
| 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); |
| } |