/* * 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 "gstopenh264elements.h" #include "gstopenh264dec.h" #include #define OPENH264_VERSION_CHECK(maj,min) ((OPENH264_MAJOR > (maj)) || (OPENH264_MAJOR == (maj) && OPENH264_MINOR >= (min))) #include #include #include #include /* for memcpy */ #if OPENH264_VERSION_CHECK (1,9) #define HAVE_OPENH264_MAIN_PROFILE 1 #else #define HAVE_OPENH264_MAIN_PROFILE 0 #endif GST_DEBUG_CATEGORY_STATIC (gst_openh264dec_debug_category); #define GST_CAT_DEFAULT gst_openh264dec_debug_category /* prototypes */ 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); static gboolean openh264dec_element_init (GstPlugin * plugin); #if HAVE_OPENH264_MAIN_PROFILE #define SUPPORTED_PROFILE_STR "profile=(string){ constrained-baseline, baseline, main, high, constrained-high, progressive-high }" #else #define SUPPORTED_PROFILE_STR "profile=(string){ constrained-baseline, baseline }" #endif /* 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, " SUPPORTED_PROFILE_STR )); 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 (GstOpenh264Dec, gst_openh264dec, GST_TYPE_VIDEO_DECODER); GST_ELEMENT_REGISTER_DEFINE_CUSTOM (openh264dec, openh264dec_element_init); static void gst_openh264dec_class_init (GstOpenh264DecClass * klass) { GstVideoDecoderClass *video_decoder_class = GST_VIDEO_DECODER_CLASS (klass); gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS (klass), &gst_openh264dec_sink_template); gst_element_class_add_static_pad_template (GST_ELEMENT_CLASS (klass), &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"); 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->decoder = NULL; gst_video_decoder_set_packetized (GST_VIDEO_DECODER (openh264dec), TRUE); gst_video_decoder_set_needs_format (GST_VIDEO_DECODER (openh264dec), TRUE); } #ifndef GST_DISABLE_GST_DEBUG static void openh264_trace_cb (void *ctx, int level, const char *string) { GObject *o = G_OBJECT (ctx); GstDebugLevel lvl = GST_LEVEL_WARNING; if (level >= WELS_LOG_DETAIL) lvl = GST_LEVEL_LOG; else if (level >= WELS_LOG_DEBUG) lvl = GST_LEVEL_DEBUG; else if (level >= WELS_LOG_INFO) lvl = GST_LEVEL_INFO; else if (level >= WELS_LOG_WARNING) lvl = GST_LEVEL_WARNING; else if (level >= WELS_LOG_ERROR) lvl = GST_LEVEL_ERROR; gst_debug_log (GST_CAT_DEFAULT, lvl, "", "", 0, o, "%s", string); } #endif static gboolean gst_openh264dec_start (GstVideoDecoder * decoder) { GstOpenh264Dec *openh264dec = GST_OPENH264DEC (decoder); gint ret; SDecodingParam dec_param = { 0 }; if (openh264dec->decoder != NULL) { openh264dec->decoder->Uninitialize (); WelsDestroyDecoder (openh264dec->decoder); openh264dec->decoder = NULL; } if (WelsCreateDecoder (&(openh264dec->decoder)) != 0) { GST_ELEMENT_ERROR (openh264dec, LIBRARY, INIT, (NULL), ("Failed to create OpenH264 decoder.")); return FALSE; } #ifndef GST_DISABLE_GST_DEBUG { int log_level = WELS_LOG_WARNING; WelsTraceCallback log_cb = openh264_trace_cb; openh264dec->decoder->SetOption (DECODER_OPTION_TRACE_LEVEL, &log_level); openh264dec->decoder->SetOption (DECODER_OPTION_TRACE_CALLBACK, (void *) &log_cb); openh264dec->decoder->SetOption (DECODER_OPTION_TRACE_CALLBACK_CONTEXT, (void *) &decoder); } #endif dec_param.uiTargetDqLayer = 255; dec_param.eEcActiveIdc = ERROR_CON_FRAME_COPY; #if OPENH264_MAJOR == 1 && OPENH264_MINOR < 6 dec_param.eOutputColorFormat = videoFormatI420; #endif dec_param.sVideoProperty.eVideoBsType = VIDEO_BITSTREAM_AVC; ret = openh264dec->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->decoder) { openh264dec->decoder->Uninitialize (); WelsDestroyDecoder (openh264dec->decoder); openh264dec->decoder = NULL; } if (openh264dec->input_state) { gst_video_codec_state_unref (openh264dec->input_state); openh264dec->input_state = NULL; } openh264dec->width = openh264dec->height = 0; return TRUE; } static gboolean gst_openh264dec_set_format (GstVideoDecoder * decoder, GstVideoCodecState * state) { GstOpenh264Dec *openh264dec = GST_OPENH264DEC (decoder); GST_DEBUG_OBJECT (openh264dec, "input caps: %" GST_PTR_FORMAT, state->caps); if (openh264dec->input_state) { gst_video_codec_state_unref (openh264dec->input_state); openh264dec->input_state = NULL; } openh264dec->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 == NULL) { #if OPENH264_VERSION_CHECK (1,9) /* Called with no videoframe for EOS logic. Drain out */ int end_of_stream = 1; memset (&dst_buf_info, 0, sizeof (SBufferInfo)); openh264dec->decoder->SetOption (DECODER_OPTION_END_OF_STREAM, &end_of_stream); ret = openh264dec->decoder->FlushFrame (yuvdata, &dst_buf_info); if (ret != dsErrorFree || dst_buf_info.iBufferStatus != 1) { GST_DEBUG_OBJECT (decoder, "No more frames to retrieve at EOS"); return GST_FLOW_EOS; } #else return GST_FLOW_EOS; #endif } else { 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, 1st NAL type %d", map_info.size > 4 ? map_info.data[4] & 0x1f : -1); memset (&dst_buf_info, 0, sizeof (SBufferInfo)); /* Use the unsigned long long OpenH264 timestamp to store the system_frame_number * to track the original frame through any OpenH264 reordering */ dst_buf_info.uiInBsTimeStamp = frame->system_frame_number; GST_LOG_OBJECT (decoder, "Submitting frame with PTS %" GST_TIME_FORMAT " and frame ref %" G_GUINT64_FORMAT, GST_TIME_ARGS (frame->pts), (guint64) frame->system_frame_number); ret = openh264dec->decoder->DecodeFrameNoDelay (map_info.data, map_info.size, yuvdata, &dst_buf_info); gst_buffer_unmap (frame->input_buffer, &map_info); if (ret != dsErrorFree) { /* Request a key unit from upstream */ GST_DEBUG_OBJECT (openh264dec, "Requesting a key unit"); gst_video_decoder_request_sync_point (decoder, frame, (GstVideoDecoderRequestSyncPointFlags) 0); GST_LOG_OBJECT (openh264dec, "error decoding nal, return code: %d", ret); gst_video_codec_frame_unref (frame); /* Get back the frame that was reported as errored */ frame = gst_video_decoder_get_frame (decoder, dst_buf_info.uiOutYuvTimeStamp); if (frame) { GST_LOG_OBJECT (decoder, "Dropping errored frame ref %" G_GUINT64_FORMAT, (guint64) dst_buf_info.uiOutYuvTimeStamp); return gst_video_decoder_drop_frame (decoder, frame); } return GST_FLOW_OK; } gst_video_codec_frame_unref (frame); frame = NULL; /* No output available yet */ if (dst_buf_info.iBufferStatus != 1) { GST_LOG_OBJECT (decoder, "No buffer decoded yet"); return GST_FLOW_OK; } } GST_LOG_OBJECT (decoder, "Got back frame with frame ref %" G_GUINT64_FORMAT, (guint64) dst_buf_info.uiOutYuvTimeStamp); /* OpenH264 lets us pass an int reference through * so we can retrieve the input frame now */ frame = gst_video_decoder_get_frame (decoder, dst_buf_info.uiOutYuvTimeStamp); if (!frame) { /* Where did our frame go? This is a reference tracking error. */ GST_WARNING_OBJECT (decoder, "Failed to look up frame ref %" G_GUINT64_FORMAT, (guint64) dst_buf_info.uiOutYuvTimeStamp); return GST_FLOW_OK; } 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->width || actual_height != openh264dec->height) { state = gst_video_decoder_set_output_state (decoder, GST_VIDEO_FORMAT_I420, actual_width, actual_height, openh264dec->input_state); openh264dec->width = actual_width; openh264dec->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->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; } static gboolean openh264dec_element_init (GstPlugin * plugin) { GST_DEBUG_CATEGORY_INIT (gst_openh264dec_debug_category, "openh264dec", 0, "debug category for openh264dec element"); return gst_element_register (plugin, "openh264dec", GST_RANK_MARGINAL, GST_TYPE_OPENH264DEC); }