/* * Copyright (c) 2014, Ericsson AB. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this * list of conditions and the following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY * OF SUCH DAMAGE. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include <wels/codec_api.h> #include <wels/codec_app_def.h> #include <wels/codec_def.h> #include "gstopenh264dec.h" #include <gst/gst.h> #include <gst/video/video.h> #include <gst/video/gstvideodecoder.h> #include <string.h> /* for memcpy */ #define GST_OPENH264DEC_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ( \ (obj), GST_TYPE_OPENH264DEC, \ GstOpenh264DecPrivate)) GST_DEBUG_CATEGORY_STATIC (gst_openh264dec_debug_category); #define GST_CAT_DEFAULT gst_openh264dec_debug_category /* prototypes */ static void gst_openh264dec_set_property (GObject * object, guint property_id, const GValue * value, GParamSpec * pspec); static void gst_openh264dec_get_property (GObject * object, guint property_id, GValue * value, GParamSpec * pspec); static gboolean gst_openh264dec_start (GstVideoDecoder * decoder); static gboolean gst_openh264dec_stop (GstVideoDecoder * decoder); static gboolean gst_openh264dec_set_format (GstVideoDecoder * decoder, GstVideoCodecState * state); static gboolean gst_openh264dec_reset (GstVideoDecoder * decoder, gboolean hard); static GstFlowReturn gst_openh264dec_finish (GstVideoDecoder * decoder); static GstFlowReturn gst_openh264dec_handle_frame (GstVideoDecoder * decoder, GstVideoCodecFrame * frame); static gboolean gst_openh264dec_decide_allocation (GstVideoDecoder * decoder, GstQuery * query); enum { PROP_0, N_PROPERTIES }; struct _GstOpenh264DecPrivate { ISVCDecoder *decoder; GstVideoCodecState *input_state; guint width, height; }; /* pad templates */ static GstStaticPadTemplate gst_openh264dec_sink_template = GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS ("video/x-h264, stream-format=(string)byte-stream, alignment=(string)au,profile=(string){constrained-baseline,baseline}")); static GstStaticPadTemplate gst_openh264dec_src_template = GST_STATIC_PAD_TEMPLATE ("src", GST_PAD_SRC, GST_PAD_ALWAYS, GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("I420"))); /* class initialization */ G_DEFINE_TYPE_WITH_CODE (GstOpenh264Dec, gst_openh264dec, GST_TYPE_VIDEO_DECODER, GST_DEBUG_CATEGORY_INIT (gst_openh264dec_debug_category, "openh264dec", 0, "debug category for openh264dec element")); static void gst_openh264dec_class_init (GstOpenh264DecClass * klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GstVideoDecoderClass *video_decoder_class = GST_VIDEO_DECODER_CLASS (klass); g_type_class_add_private (klass, sizeof (GstOpenh264DecPrivate)); /* Setting up pads and setting metadata should be moved to base_class_init if you intend to subclass this class. */ gst_element_class_add_pad_template (GST_ELEMENT_CLASS (klass), gst_static_pad_template_get (&gst_openh264dec_sink_template)); gst_element_class_add_pad_template (GST_ELEMENT_CLASS (klass), gst_static_pad_template_get (&gst_openh264dec_src_template)); gst_element_class_set_static_metadata (GST_ELEMENT_CLASS (klass), "OpenH264 video decoder", "Decoder/Video", "OpenH264 video decoder", "Ericsson AB, http://www.ericsson.com"); gobject_class->set_property = gst_openh264dec_set_property; gobject_class->get_property = gst_openh264dec_get_property; video_decoder_class->start = GST_DEBUG_FUNCPTR (gst_openh264dec_start); video_decoder_class->stop = GST_DEBUG_FUNCPTR (gst_openh264dec_stop); video_decoder_class->set_format = GST_DEBUG_FUNCPTR (gst_openh264dec_set_format); video_decoder_class->reset = GST_DEBUG_FUNCPTR (gst_openh264dec_reset); video_decoder_class->finish = GST_DEBUG_FUNCPTR (gst_openh264dec_finish); video_decoder_class->handle_frame = GST_DEBUG_FUNCPTR (gst_openh264dec_handle_frame); video_decoder_class->decide_allocation = GST_DEBUG_FUNCPTR (gst_openh264dec_decide_allocation); } static void gst_openh264dec_init (GstOpenh264Dec * openh264dec) { openh264dec->priv = GST_OPENH264DEC_GET_PRIVATE (openh264dec); openh264dec->priv->decoder = NULL; gst_video_decoder_set_packetized (GST_VIDEO_DECODER (openh264dec), TRUE); gst_video_decoder_set_needs_format (GST_VIDEO_DECODER (openh264dec), TRUE); } void gst_openh264dec_set_property (GObject * object, guint property_id, const GValue * value, GParamSpec * pspec) { GstOpenh264Dec *openh264dec = GST_OPENH264DEC (object); GST_DEBUG_OBJECT (openh264dec, "set_property"); switch (property_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } void gst_openh264dec_get_property (GObject * object, guint property_id, GValue * value, GParamSpec * pspec) { GstOpenh264Dec *openh264dec = GST_OPENH264DEC (object); GST_DEBUG_OBJECT (openh264dec, "get_property"); switch (property_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static gboolean gst_openh264dec_start (GstVideoDecoder * decoder) { GstOpenh264Dec *openh264dec = GST_OPENH264DEC (decoder); gint ret; SDecodingParam dec_param = { 0 }; if (openh264dec->priv->decoder != NULL) { openh264dec->priv->decoder->Uninitialize (); WelsDestroyDecoder (openh264dec->priv->decoder); openh264dec->priv->decoder = NULL; } WelsCreateDecoder (&(openh264dec->priv->decoder)); dec_param.uiTargetDqLayer = 255; dec_param.eEcActiveIdc = ERROR_CON_FRAME_COPY; dec_param.eOutputColorFormat = videoFormatI420; dec_param.sVideoProperty.eVideoBsType = VIDEO_BITSTREAM_AVC; ret = openh264dec->priv->decoder->Initialize (&dec_param); GST_DEBUG_OBJECT (openh264dec, "openh264_dec_start called, openh264dec %sinitialized OK!", (ret != cmResultSuccess) ? "NOT " : ""); return (ret == cmResultSuccess); } static gboolean gst_openh264dec_stop (GstVideoDecoder * decoder) { GstOpenh264Dec *openh264dec = GST_OPENH264DEC (decoder); if (openh264dec->priv->decoder) { openh264dec->priv->decoder->Uninitialize (); WelsDestroyDecoder (openh264dec->priv->decoder); openh264dec->priv->decoder = NULL; } if (openh264dec->priv->input_state) { gst_video_codec_state_unref (openh264dec->priv->input_state); openh264dec->priv->input_state = NULL; } openh264dec->priv->width = openh264dec->priv->height = 0; return TRUE; } static gboolean gst_openh264dec_set_format (GstVideoDecoder * decoder, GstVideoCodecState * state) { GstOpenh264Dec *openh264dec = GST_OPENH264DEC (decoder); GST_DEBUG_OBJECT (openh264dec, "openh264_dec_set_format called, caps: %" GST_PTR_FORMAT, state->caps); if (openh264dec->priv->input_state) { gst_video_codec_state_unref (openh264dec->priv->input_state); openh264dec->priv->input_state = NULL; } openh264dec->priv->input_state = gst_video_codec_state_ref (state); return TRUE; } static gboolean gst_openh264dec_reset (GstVideoDecoder * decoder, gboolean hard) { GstOpenh264Dec *openh264dec = GST_OPENH264DEC (decoder); GST_DEBUG_OBJECT (openh264dec, "reset"); return TRUE; } static GstFlowReturn gst_openh264dec_handle_frame (GstVideoDecoder * decoder, GstVideoCodecFrame * frame) { GstOpenh264Dec *openh264dec = GST_OPENH264DEC (decoder); GstMapInfo map_info; GstVideoCodecState *state; SBufferInfo dst_buf_info; DECODING_STATE ret; guint8 *yuvdata[3]; GstFlowReturn flow_status; GstVideoFrame video_frame; guint actual_width, actual_height; guint i; guint8 *p; guint row_stride, component_width, component_height, src_width, row; if (frame) { if (!gst_buffer_map (frame->input_buffer, &map_info, GST_MAP_READ)) { GST_ERROR_OBJECT (openh264dec, "Cannot map input buffer!"); gst_video_codec_frame_unref (frame); return GST_FLOW_ERROR; } GST_LOG_OBJECT (openh264dec, "handle frame, %d", map_info.size > 4 ? map_info.data[4] & 0x1f : -1); memset (&dst_buf_info, 0, sizeof (SBufferInfo)); ret = openh264dec->priv->decoder->DecodeFrame2 (map_info.data, map_info.size, yuvdata, &dst_buf_info); if (ret == dsNoParamSets) { GST_DEBUG_OBJECT (openh264dec, "Requesting a key unit"); gst_pad_push_event (GST_VIDEO_DECODER_SINK_PAD (decoder), gst_video_event_new_upstream_force_key_unit (GST_CLOCK_TIME_NONE, FALSE, 0)); } if (ret != dsErrorFree && ret != dsNoParamSets) { GST_DEBUG_OBJECT (openh264dec, "Requesting a key unit"); gst_pad_push_event (GST_VIDEO_DECODER_SINK_PAD (decoder), gst_video_event_new_upstream_force_key_unit (GST_CLOCK_TIME_NONE, FALSE, 0)); GST_LOG_OBJECT (openh264dec, "error decoding nal, return code: %d", ret); } gst_buffer_unmap (frame->input_buffer, &map_info); gst_video_codec_frame_unref (frame); frame = NULL; } else { memset (&dst_buf_info, 0, sizeof (SBufferInfo)); ret = openh264dec->priv->decoder->DecodeFrame2 (NULL, 0, yuvdata, &dst_buf_info); if (ret != dsErrorFree) { gst_video_codec_frame_unref (frame); return GST_FLOW_EOS; } } /* FIXME: openh264 has no way for us to get a connection * between the input and output frames, we just have to * guess based on the input. Fortunately openh264 can * only do baseline profile. */ frame = gst_video_decoder_get_oldest_frame (decoder); if (!frame) { /* Can only happen in finish() */ return GST_FLOW_EOS; } /* No output available yet */ if (dst_buf_info.iBufferStatus != 1) { gst_video_codec_frame_unref (frame); return (frame ? GST_FLOW_OK : GST_FLOW_EOS); } actual_width = dst_buf_info.UsrData.sSystemBuffer.iWidth; actual_height = dst_buf_info.UsrData.sSystemBuffer.iHeight; if (!gst_pad_has_current_caps (GST_VIDEO_DECODER_SRC_PAD (openh264dec)) || actual_width != openh264dec->priv->width || actual_height != openh264dec->priv->height) { state = gst_video_decoder_set_output_state (decoder, GST_VIDEO_FORMAT_I420, actual_width, actual_height, openh264dec->priv->input_state); openh264dec->priv->width = actual_width; openh264dec->priv->height = actual_height; if (!gst_video_decoder_negotiate (decoder)) { GST_ERROR_OBJECT (openh264dec, "Failed to negotiate with downstream elements"); gst_video_codec_state_unref (state); gst_video_codec_frame_unref (frame); return GST_FLOW_NOT_NEGOTIATED; } } else { state = gst_video_decoder_get_output_state (decoder); } flow_status = gst_video_decoder_allocate_output_frame (decoder, frame); if (flow_status != GST_FLOW_OK) { gst_video_codec_state_unref (state); gst_video_codec_frame_unref (frame); return flow_status; } if (!gst_video_frame_map (&video_frame, &state->info, frame->output_buffer, GST_MAP_WRITE)) { GST_ERROR_OBJECT (openh264dec, "Cannot map output buffer!"); gst_video_codec_state_unref (state); gst_video_codec_frame_unref (frame); return GST_FLOW_ERROR; } for (i = 0; i < 3; i++) { p = GST_VIDEO_FRAME_COMP_DATA (&video_frame, i); row_stride = GST_VIDEO_FRAME_COMP_STRIDE (&video_frame, i); component_width = GST_VIDEO_FRAME_COMP_WIDTH (&video_frame, i); component_height = GST_VIDEO_FRAME_COMP_HEIGHT (&video_frame, i); src_width = i < 1 ? dst_buf_info.UsrData.sSystemBuffer. iStride[0] : dst_buf_info.UsrData.sSystemBuffer.iStride[1]; for (row = 0; row < component_height; row++) { memcpy (p, yuvdata[i], component_width); p += row_stride; yuvdata[i] += src_width; } } gst_video_codec_state_unref (state); gst_video_frame_unmap (&video_frame); return gst_video_decoder_finish_frame (decoder, frame); } static GstFlowReturn gst_openh264dec_finish (GstVideoDecoder * decoder) { GstOpenh264Dec *openh264dec = GST_OPENH264DEC (decoder); GST_DEBUG_OBJECT (openh264dec, "finish"); /* Decoder not negotiated yet */ if (openh264dec->priv->width == 0) return GST_FLOW_OK; /* Drain all pending frames */ while ((gst_openh264dec_handle_frame (decoder, NULL)) == GST_FLOW_OK); return GST_FLOW_OK; } static gboolean gst_openh264dec_decide_allocation (GstVideoDecoder * decoder, GstQuery * query) { GstVideoCodecState *state; GstBufferPool *pool; guint size, min, max; GstStructure *config; if (!GST_VIDEO_DECODER_CLASS (gst_openh264dec_parent_class)->decide_allocation (decoder, query)) return FALSE; state = gst_video_decoder_get_output_state (decoder); gst_query_parse_nth_allocation_pool (query, 0, &pool, &size, &min, &max); 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_query_set_nth_allocation_pool (query, 0, pool, size, min, max); gst_object_unref (pool); gst_video_codec_state_unref (state); return TRUE; }