diff --git a/configure.ac b/configure.ac index 56ede7d015..3afa85040a 100644 --- a/configure.ac +++ b/configure.ac @@ -2196,6 +2196,14 @@ AG_GST_CHECK_FEATURE(LV2, [lv2], lv2, [ AC_SUBST(SLV2_LIBS) ]) +dnl *** libde265 *** +translit(dnm, m, l) AM_CONDITIONAL(USE_LIBDE265, true) +AG_GST_CHECK_FEATURE(LIBDE265, [libde265 HEVC/H.265 decoder], libde265, [ + PKG_CHECK_MODULES(LIBDE265, libde265 >= 0.9, HAVE_LIBDE265="yes", HAVE_LIBDE265="no") + AC_SUBST(LIBDE265_CFLAGS) + AC_SUBST(LIBDE265_LIBS) +]) + dnl *** libmms *** translit(dnm, m, l) AM_CONDITIONAL(USE_LIBMMS, true) AG_GST_CHECK_FEATURE(LIBMMS, [mms protocol library], libmms, [ @@ -3053,6 +3061,7 @@ AM_CONDITIONAL(USE_KATE, false) AM_CONDITIONAL(USE_TIGER, false) AM_CONDITIONAL(USE_LADSPA, false) AM_CONDITIONAL(USE_LV2, false) +AM_CONDITIONAL(USE_LIBDE265, false) AM_CONDITIONAL(USE_LIBMMS, false) AM_CONDITIONAL(USE_LINSYS, false) AM_CONDITIONAL(USE_MODPLUG, false) @@ -3352,6 +3361,7 @@ ext/hls/Makefile ext/kate/Makefile ext/ladspa/Makefile ext/lv2/Makefile +ext/libde265/Makefile ext/libmms/Makefile ext/libvisual/Makefile ext/Makefile diff --git a/ext/Makefile.am b/ext/Makefile.am index f39e1ee70e..37eece025b 100644 --- a/ext/Makefile.am +++ b/ext/Makefile.am @@ -142,6 +142,12 @@ else LV2_DIR = endif +if USE_LIBDE265 +LIBDE265_DIR = libde265 +else +LIBDE265_DIR = +endif + # if USE_LIBFAME # LIBFAME_DIR=libfame # else @@ -427,6 +433,7 @@ SUBDIRS=\ $(KATE_DIR) \ $(LADSPA_DIR) \ $(LV2_DIR) \ + $(LIBDE265_DIR) \ $(LIBFAME_DIR) \ $(LIBMMS_DIR) \ $(LIBVISUAL_DIR) \ @@ -487,6 +494,7 @@ DIST_SUBDIRS = \ hls \ ladspa \ kate \ + libde265 \ libmms \ libvisual \ lv2 \ diff --git a/ext/libde265/Makefile.am b/ext/libde265/Makefile.am new file mode 100644 index 0000000000..e90096c0fb --- /dev/null +++ b/ext/libde265/Makefile.am @@ -0,0 +1,15 @@ +plugin_LTLIBRARIES = libgstlibde265.la + +libgstlibde265_la_SOURCES = \ + gstlibde265.c \ + libde265-dec.c + +libgstlibde265_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS) \ + $(LIBDE265_CFLAGS) +libgstlibde265_la_LIBADD = $(GST_PLUGINS_BASE_LIBS) -lgstvideo-$(GST_API_VERSION) $(GST_BASE_LIBS) $(GST_LIBS) \ + $(LIBDE265_LIBS) +libgstlibde265_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstlibde265_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS) + +noinst_HEADERS = \ + libde265-dec.h diff --git a/ext/libde265/gstlibde265.c b/ext/libde265/gstlibde265.c new file mode 100644 index 0000000000..2ee7ffe511 --- /dev/null +++ b/ext/libde265/gstlibde265.c @@ -0,0 +1,41 @@ +/* + * GStreamer HEVC/H.265 video codec. + * + * Copyright (c) 2014 struktur AG, Joachim Bauch + * + * 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 "libde265-dec.h" + +static gboolean +plugin_init (GstPlugin * plugin) +{ + gboolean ret; + + ret = gst_libde265_dec_plugin_init (plugin); + return ret; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + gstlibde265, + "HEVC/H.265 decoder using libde265", + plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/ext/libde265/libde265-dec.c b/ext/libde265/libde265-dec.c new file mode 100644 index 0000000000..456b819cee --- /dev/null +++ b/ext/libde265/libde265-dec.c @@ -0,0 +1,895 @@ +/* + * GStreamer HEVC/H.265 video codec. + * + * Copyright (c) 2014 struktur AG, Joachim Bauch + * + * 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. + */ + +/** + * SECTION:element-libde265dec + * + * Decodes HEVC/H.265 video. + * + * + * Example launch line + * |[ + * gst-launch-1.0 filesrc location=bitstream.hevc ! 'video/x-hevc,stream-format=byte-stream,framerate=25/1' ! libde265dec ! autovideosink + * ]| The above pipeline decodes the HEVC/H.265 bitstream and renders it to the screen. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "libde265-dec.h" + +/* use two decoder threads if no information about + * available CPU cores can be retrieved */ +#define DEFAULT_THREAD_COUNT 2 + +#define parent_class gst_libde265_dec_parent_class +G_DEFINE_TYPE (GstLibde265Dec, gst_libde265_dec, GST_TYPE_VIDEO_DECODER); + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS + ("video/x-h265, stream-format=(string) { hvc1, hev1, byte-stream }, " + "alignment=(string) { au, nal }") + ); + +static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE ("I420")) + ); + +enum +{ + PROP_0, + PROP_MAX_THREADS, + PROP_LAST +}; + +#define DEFAULT_FORMAT GST_TYPE_LIBDE265_FORMAT_PACKETIZED +#define DEFAULT_MAX_THREADS 0 + +static void gst_libde265_dec_finalize (GObject * object); + +static void gst_libde265_dec_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_libde265_dec_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); + +static gboolean gst_libde265_dec_start (GstVideoDecoder * decoder); +static gboolean gst_libde265_dec_stop (GstVideoDecoder * decoder); +static gboolean gst_libde265_dec_set_format (GstVideoDecoder * decoder, + GstVideoCodecState * state); +static gboolean gst_libde265_dec_flush (GstVideoDecoder * decoder); +static GstFlowReturn gst_libde265_dec_finish (GstVideoDecoder * decoder); +static GstFlowReturn _gst_libde265_return_image (GstVideoDecoder * decoder, + GstVideoCodecFrame * frame, const struct de265_image *img); +static GstFlowReturn gst_libde265_dec_handle_frame (GstVideoDecoder * decoder, + GstVideoCodecFrame * frame); +static GstFlowReturn _gst_libde265_image_available (GstVideoDecoder * decoder, + int width, int height); + +static void +gst_libde265_dec_class_init (GstLibde265DecClass * klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GstVideoDecoderClass *decoder_class = GST_VIDEO_DECODER_CLASS (klass); + GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass); + + gobject_class->finalize = gst_libde265_dec_finalize; + gobject_class->set_property = gst_libde265_dec_set_property; + gobject_class->get_property = gst_libde265_dec_get_property; + + g_object_class_install_property (gobject_class, PROP_MAX_THREADS, + g_param_spec_int ("max-threads", "Maximum decode threads", + "Maximum number of worker threads to spawn. (0 = auto)", + 0, G_MAXINT, DEFAULT_MAX_THREADS, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + decoder_class->start = GST_DEBUG_FUNCPTR (gst_libde265_dec_start); + decoder_class->stop = GST_DEBUG_FUNCPTR (gst_libde265_dec_stop); + decoder_class->set_format = GST_DEBUG_FUNCPTR (gst_libde265_dec_set_format); + decoder_class->flush = GST_DEBUG_FUNCPTR (gst_libde265_dec_flush); + decoder_class->finish = GST_DEBUG_FUNCPTR (gst_libde265_dec_finish); + decoder_class->handle_frame = + GST_DEBUG_FUNCPTR (gst_libde265_dec_handle_frame); + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&sink_template)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&src_template)); + + gst_element_class_set_static_metadata (gstelement_class, + "HEVC/H.265 parser", + "Codec/Parser/Converter/Video", + "Decodes HEVC/H.265 video streams using libde265", + "struktur AG "); +} + +static inline void +_gst_libde265_dec_reset_decoder (GstLibde265Dec * dec) +{ + dec->ctx = NULL; + dec->buffer_full = 0; + dec->codec_data = NULL; + dec->codec_data_size = 0; + dec->input_state = NULL; + dec->output_state = NULL; +} + +static void +gst_libde265_dec_init (GstLibde265Dec * dec) +{ + dec->format = DEFAULT_FORMAT; + dec->max_threads = DEFAULT_MAX_THREADS; + dec->length_size = 4; + _gst_libde265_dec_reset_decoder (dec); + gst_video_decoder_set_packetized (GST_VIDEO_DECODER (dec), TRUE); +} + +static inline void +_gst_libde265_dec_free_decoder (GstLibde265Dec * dec) +{ + if (dec->ctx != NULL) { + de265_free_decoder (dec->ctx); + } + free (dec->codec_data); + if (dec->input_state != NULL) { + gst_video_codec_state_unref (dec->input_state); + } + if (dec->output_state != NULL) { + gst_video_codec_state_unref (dec->output_state); + } + _gst_libde265_dec_reset_decoder (dec); +} + +static void +gst_libde265_dec_finalize (GObject * object) +{ + GstLibde265Dec *dec = GST_LIBDE265_DEC (object); + + _gst_libde265_dec_free_decoder (dec); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_libde265_dec_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstLibde265Dec *dec = GST_LIBDE265_DEC (object); + + switch (prop_id) { + case PROP_MAX_THREADS: + dec->max_threads = g_value_get_int (value); + if (dec->max_threads) { + GST_DEBUG_OBJECT (dec, "Max. threads set to %d", dec->max_threads); + } else { + GST_DEBUG_OBJECT (dec, "Max. threads set to auto"); + } + break; + default: + break; + } +} + +static void +gst_libde265_dec_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstLibde265Dec *dec = GST_LIBDE265_DEC (object); + + switch (prop_id) { + case PROP_MAX_THREADS: + g_value_set_int (value, dec->max_threads); + break; + default: + break; + } +} + +struct GstLibde265FrameRef +{ + GstVideoDecoder *decoder; + GstVideoCodecFrame *frame; + GstVideoFrame vframe; + GstBuffer *buffer; + gboolean mapped; +}; + +static void +gst_libde265_dec_release_frame_ref (struct GstLibde265FrameRef *ref) +{ + if (ref->mapped) { + gst_video_frame_unmap (&ref->vframe); + } + gst_video_codec_frame_unref (ref->frame); + gst_buffer_replace (&ref->buffer, NULL); + g_free (ref); +} + +static int +gst_libde265_dec_get_buffer (de265_decoder_context * ctx, + struct de265_image_spec *spec, struct de265_image *img, void *userdata) +{ + GstVideoDecoder *base = (GstVideoDecoder *) userdata; + GstLibde265Dec *dec = GST_LIBDE265_DEC (base); + GstVideoCodecFrame *frame = NULL; + int i; + int width = spec->width; + int height = spec->height; + GstFlowReturn ret; + struct GstLibde265FrameRef *ref; + GstVideoInfo *info; + int frame_number; + + frame_number = (uintptr_t) de265_get_image_user_data (img) - 1; + if (G_UNLIKELY (frame_number == -1)) { + /* should not happen... */ + GST_WARNING_OBJECT (base, "Frame has no number assigned!"); + goto fallback; + } + + frame = gst_video_decoder_get_frame (base, frame_number); + if (G_UNLIKELY (frame == NULL)) { + /* should not happen... */ + GST_WARNING_OBJECT (base, "Couldn't get codec frame!"); + goto fallback; + } + + if (width % spec->alignment) { + width += spec->alignment - (width % spec->alignment); + } + if (width != spec->visible_width || height != spec->visible_height) { + /* clipping not supported for now */ + goto fallback; + } + + ret = _gst_libde265_image_available (base, width, height); + if (G_UNLIKELY (ret != GST_FLOW_OK)) { + GST_ERROR_OBJECT (dec, "Failed to notify about available image"); + goto fallback; + } + + ret = + gst_video_decoder_allocate_output_frame (GST_VIDEO_DECODER (dec), frame); + if (G_UNLIKELY (ret != GST_FLOW_OK)) { + GST_ERROR_OBJECT (dec, "Failed to allocate output buffer"); + goto fallback; + } + + ref = (struct GstLibde265FrameRef *) g_malloc0 (sizeof (*ref)); + g_assert (ref != NULL); + ref->decoder = base; + ref->frame = frame; + + gst_buffer_replace (&ref->buffer, frame->output_buffer); + gst_buffer_replace (&frame->output_buffer, NULL); + + info = &dec->output_state->info; + if (!gst_video_frame_map (&ref->vframe, info, ref->buffer, GST_MAP_READWRITE)) { + GST_ERROR_OBJECT (dec, "Failed to map frame output buffer"); + goto error; + } + + ref->mapped = TRUE; + if (GST_VIDEO_FRAME_PLANE_STRIDE (&ref->vframe, + 0) < width * GST_VIDEO_FRAME_COMP_PSTRIDE (&ref->vframe, 0)) { + GST_DEBUG_OBJECT (dec, "plane 0: pitch too small (%d/%d*%d)", + GST_VIDEO_FRAME_PLANE_STRIDE (&ref->vframe, 0), width, + GST_VIDEO_FRAME_COMP_PSTRIDE (&ref->vframe, 0)); + goto error; + } + + if (GST_VIDEO_FRAME_COMP_HEIGHT (&ref->vframe, 0) < height) { + GST_DEBUG_OBJECT (dec, "plane 0: lines too few (%d/%d)", + GST_VIDEO_FRAME_COMP_HEIGHT (&ref->vframe, 0), height); + goto error; + } + + for (i = 0; i < 3; i++) { + uint8_t *data; + int stride = GST_VIDEO_FRAME_PLANE_STRIDE (&ref->vframe, i); + if (stride % spec->alignment) { + GST_DEBUG_OBJECT (dec, "plane %d: pitch not aligned (%d%%%d)", + i, stride, spec->alignment); + goto error; + } + + data = GST_VIDEO_FRAME_PLANE_DATA (&ref->vframe, i); + if ((uintptr_t) (data) % spec->alignment) { + GST_DEBUG_OBJECT (dec, "plane %d not aligned", i); + goto error; + } + + de265_set_image_plane (img, i, data, stride, ref); + } + return 1; + +error: + gst_libde265_dec_release_frame_ref (ref); + frame = NULL; + +fallback: + if (frame != NULL) { + gst_video_codec_frame_unref (frame); + } + return de265_get_default_image_allocation_functions ()->get_buffer (ctx, + spec, img, userdata); +} + +static void +gst_libde265_dec_release_buffer (de265_decoder_context * ctx, + struct de265_image *img, void *userdata) +{ + GstVideoDecoder *base = (GstVideoDecoder *) userdata; + struct GstLibde265FrameRef *ref = + (struct GstLibde265FrameRef *) de265_get_image_plane_user_data (img, 0); + if (ref == NULL) { + de265_get_default_image_allocation_functions ()->release_buffer (ctx, img, + userdata); + return; + } + gst_libde265_dec_release_frame_ref (ref); + (void) base; /* unused */ +} + +static gboolean +gst_libde265_dec_start (GstVideoDecoder * decoder) +{ + GstLibde265Dec *dec = GST_LIBDE265_DEC (decoder); + int threads = dec->max_threads; + struct de265_image_allocation allocation; + + _gst_libde265_dec_free_decoder (dec); + dec->ctx = de265_new_decoder (); + if (dec->ctx == NULL) { + return FALSE; + } + if (threads == 0) { +#if defined(_SC_NPROC_ONLN) + threads = sysconf (_SC_NPROC_ONLN); +#elif defined(_SC_NPROCESSORS_ONLN) + threads = sysconf (_SC_NPROCESSORS_ONLN); +#else +#warning "Don't know how to get number of CPU cores, will use the default thread count" + threads = DEFAULT_THREAD_COUNT; +#endif + if (threads <= 0) { + threads = DEFAULT_THREAD_COUNT; + } + /* NOTE: We start more threads than cores for now, as some threads + * might get blocked while waiting for dependent data. Having more + * threads increases decoding speed by about 10% */ + threads *= 2; + } + if (threads > 1) { + if (threads > 32) { + /* TODO: this limit should come from the libde265 headers */ + threads = 32; + } + de265_start_worker_threads (dec->ctx, threads); + } + GST_INFO_OBJECT (dec, "Using libde265 %s with %d worker threads", + de265_get_version (), threads); + + allocation.get_buffer = gst_libde265_dec_get_buffer; + allocation.release_buffer = gst_libde265_dec_release_buffer; + de265_set_image_allocation_functions (dec->ctx, &allocation, decoder); + /* NOTE: we explicitly disable hash checks for now */ + de265_set_parameter_bool (dec->ctx, DE265_DECODER_PARAM_BOOL_SEI_CHECK_HASH, + 0); + return TRUE; +} + +static gboolean +gst_libde265_dec_stop (GstVideoDecoder * decoder) +{ + GstLibde265Dec *dec = GST_LIBDE265_DEC (decoder); + + _gst_libde265_dec_free_decoder (dec); + + return TRUE; +} + +static gboolean +gst_libde265_dec_flush (GstVideoDecoder * decoder) +{ + GstLibde265Dec *dec = GST_LIBDE265_DEC (decoder); + + de265_reset (dec->ctx); + dec->buffer_full = 0; + if (dec->codec_data != NULL + && dec->format == GST_TYPE_LIBDE265_FORMAT_BYTESTREAM) { + int more; + de265_error err = + de265_push_data (dec->ctx, dec->codec_data, dec->codec_data_size, 0, + NULL); + if (!de265_isOK (err)) { + GST_ELEMENT_ERROR (decoder, STREAM, DECODE, + ("Failed to push codec data: %s (code=%d)", + de265_get_error_text (err), err), (NULL)); + return FALSE; + } + de265_push_end_of_NAL (dec->ctx); + do { + err = de265_decode (dec->ctx, &more); + switch (err) { + case DE265_OK: + break; + + case DE265_ERROR_IMAGE_BUFFER_FULL: + case DE265_ERROR_WAITING_FOR_INPUT_DATA: + /* not really an error */ + more = 0; + break; + + default: + if (!de265_isOK (err)) { + GST_ELEMENT_ERROR (decoder, STREAM, DECODE, + ("Failed to decode codec data: %s (code=%d)", + de265_get_error_text (err), err), (NULL)); + return FALSE; + } + } + } while (more); + } + + return TRUE; +} + +static GstFlowReturn +gst_libde265_dec_finish (GstVideoDecoder * decoder) +{ + GstLibde265Dec *dec = GST_LIBDE265_DEC (decoder); + de265_error err; + const struct de265_image *img; + int more; + GstFlowReturn result; + + err = de265_flush_data (dec->ctx); + if (!de265_isOK (err)) { + GST_ELEMENT_ERROR (decoder, STREAM, DECODE, + ("Failed to flush decoder: %s (code=%d)", + de265_get_error_text (err), err), (NULL)); + return GST_FLOW_ERROR; + } + + do { + err = de265_decode (dec->ctx, &more); + switch (err) { + case DE265_OK: + case DE265_ERROR_IMAGE_BUFFER_FULL: + img = de265_get_next_picture (dec->ctx); + if (img != NULL) { + result = _gst_libde265_return_image (decoder, NULL, img); + if (result != GST_FLOW_OK) { + return result; + } + } + break; + + case DE265_ERROR_WAITING_FOR_INPUT_DATA: + /* not really an error */ + more = 0; + break; + + default: + if (!de265_isOK (err)) { + GST_ELEMENT_ERROR (decoder, STREAM, DECODE, + ("Failed to decode codec data: %s (code=%d)", + de265_get_error_text (err), err), (NULL)); + return FALSE; + } + } + } while (more); + + return GST_FLOW_OK; +} + +static GstFlowReturn +_gst_libde265_image_available (GstVideoDecoder * decoder, int width, int height) +{ + GstLibde265Dec *dec = GST_LIBDE265_DEC (decoder); + + if (G_UNLIKELY (dec->output_state == NULL + || width != dec->output_state->info.width + || height != dec->output_state->info.height)) { + GstVideoCodecState *state = + gst_video_decoder_set_output_state (decoder, GST_VIDEO_FORMAT_I420, + width, height, dec->input_state); + if (state == NULL) { + GST_ERROR_OBJECT (dec, "Failed to set output state"); + return GST_FLOW_ERROR; + } + if (!gst_video_decoder_negotiate (decoder)) { + GST_ERROR_OBJECT (dec, "Failed to negotiate format"); + return GST_FLOW_ERROR; + } + if (dec->output_state != NULL) { + gst_video_codec_state_unref (dec->output_state); + } + dec->output_state = state; + GST_DEBUG_OBJECT (dec, "Frame dimensions are %d x %d", width, height); + } + + return GST_FLOW_OK; +} + +static gboolean +gst_libde265_dec_set_format (GstVideoDecoder * decoder, + GstVideoCodecState * state) +{ + GstLibde265Dec *dec = GST_LIBDE265_DEC (decoder); + + if (dec->input_state != NULL) { + gst_video_codec_state_unref (dec->input_state); + } + dec->input_state = state; + if (state != NULL) { + gst_video_codec_state_ref (state); + } + if (state != NULL && state->caps != NULL) { + GstStructure *str; + const GValue *value; + str = gst_caps_get_structure (state->caps, 0); + if ((value = gst_structure_get_value (str, "codec_data"))) { + GstMapInfo info; + guint8 *data; + gsize size; + GstBuffer *buf; + de265_error err; + int more; + + buf = gst_value_get_buffer (value); + if (!gst_buffer_map (buf, &info, GST_MAP_READ)) { + GST_ELEMENT_ERROR (decoder, STREAM, DECODE, + ("Failed to map codec data"), (NULL)); + return FALSE; + } + data = info.data; + size = info.size; + free (dec->codec_data); + dec->codec_data = malloc (size); + g_assert (dec->codec_data != NULL); + dec->codec_data_size = size; + memcpy (dec->codec_data, data, size); + if (size > 3 && (data[0] || data[1] || data[2] > 1)) { + /* encoded in "hvcC" format (assume version 0) */ + dec->format = GST_TYPE_LIBDE265_FORMAT_PACKETIZED; + if (size > 22) { + int i; + int num_param_sets; + int pos; + if (data[0] != 0) { + GST_ELEMENT_WARNING (decoder, STREAM, + DECODE, ("Unsupported extra data version %d, decoding may fail", + data[0]), (NULL)); + } + dec->length_size = (data[21] & 3) + 1; + num_param_sets = data[22]; + pos = 23; + for (i = 0; i < num_param_sets; i++) { + int j; + int nal_count; + if (pos + 3 > size) { + GST_ELEMENT_ERROR (decoder, STREAM, DECODE, + ("Buffer underrun in extra header (%d >= %ld)", pos + 3, + size), (NULL)); + return FALSE; + } + /* ignore flags + NAL type (1 byte) */ + nal_count = data[pos + 1] << 8 | data[pos + 2]; + pos += 3; + for (j = 0; j < nal_count; j++) { + int nal_size; + if (pos + 2 > size) { + GST_ELEMENT_ERROR (decoder, STREAM, DECODE, + ("Buffer underrun in extra nal header (%d >= %ld)", pos + 2, + size), (NULL)); + return FALSE; + } + nal_size = data[pos] << 8 | data[pos + 1]; + if (pos + 2 + nal_size > size) { + GST_ELEMENT_ERROR (decoder, STREAM, DECODE, + ("Buffer underrun in extra nal (%d >= %ld)", + pos + 2 + nal_size, size), (NULL)); + return FALSE; + } + err = + de265_push_NAL (dec->ctx, data + pos + 2, nal_size, 0, NULL); + if (!de265_isOK (err)) { + GST_ELEMENT_ERROR (decoder, STREAM, DECODE, + ("Failed to push data: %s (%d)", de265_get_error_text (err), + err), (NULL)); + return FALSE; + } + pos += 2 + nal_size; + } + } + } + GST_DEBUG ("Assuming packetized data (%d bytes length)", + dec->length_size); + } else { + dec->format = GST_TYPE_LIBDE265_FORMAT_BYTESTREAM; + GST_DEBUG_OBJECT (dec, "Assuming non-packetized data"); + err = de265_push_data (dec->ctx, data, size, 0, NULL); + if (!de265_isOK (err)) { + gst_buffer_unmap (buf, &info); + GST_ELEMENT_ERROR (decoder, STREAM, DECODE, + ("Failed to push codec data: %s (code=%d)", + de265_get_error_text (err), err), (NULL)); + return FALSE; + } + } + gst_buffer_unmap (buf, &info); + de265_push_end_of_NAL (dec->ctx); + do { + err = de265_decode (dec->ctx, &more); + switch (err) { + case DE265_OK: + break; + + case DE265_ERROR_IMAGE_BUFFER_FULL: + case DE265_ERROR_WAITING_FOR_INPUT_DATA: + /* not really an error */ + more = 0; + break; + + default: + if (!de265_isOK (err)) { + GST_ELEMENT_ERROR (decoder, STREAM, DECODE, + ("Failed to decode codec data: %s (code=%d)", + de265_get_error_text (err), err), (NULL)); + return FALSE; + } + } + } while (more); + } else if ((value = gst_structure_get_value (str, "stream-format"))) { + const gchar *str = g_value_get_string (value); + if (strcmp (str, "byte-stream") == 0) { + dec->format = GST_TYPE_LIBDE265_FORMAT_BYTESTREAM; + GST_DEBUG_OBJECT (dec, "Assuming raw byte-stream"); + } + } + } + + return TRUE; +} + +static GstFlowReturn +_gst_libde265_return_image (GstVideoDecoder * decoder, + GstVideoCodecFrame * frame, const struct de265_image *img) +{ + GstLibde265Dec *dec = GST_LIBDE265_DEC (decoder); + struct GstLibde265FrameRef *ref; + GstFlowReturn result; + GstVideoFrame outframe; + GstVideoCodecFrame *out_frame; + int frame_number; + + ref = (struct GstLibde265FrameRef *) de265_get_image_plane_user_data (img, 0); + if (ref != NULL) { + /* decoder is using direct rendering */ + out_frame = gst_video_codec_frame_ref (ref->frame); + if (frame != NULL) { + gst_video_codec_frame_unref (frame); + } + gst_buffer_replace (&out_frame->output_buffer, ref->buffer); + gst_buffer_replace (&ref->buffer, NULL); + return gst_video_decoder_finish_frame (decoder, out_frame); + } + + result = + _gst_libde265_image_available (decoder, de265_get_image_width (img, 0), + de265_get_image_height (img, 0)); + if (result != GST_FLOW_OK) { + GST_ERROR_OBJECT (dec, "Failed to notify about available image"); + return result; + } + + frame_number = (uintptr_t) de265_get_image_user_data (img) - 1; + if (frame_number != -1) { + out_frame = gst_video_decoder_get_frame (decoder, frame_number); + } else { + out_frame = NULL; + } + if (frame != NULL) { + gst_video_codec_frame_unref (frame); + } + + if (out_frame == NULL) { + GST_ERROR_OBJECT (dec, "No frame available to return"); + return GST_FLOW_ERROR; + } + + result = gst_video_decoder_allocate_output_frame (decoder, out_frame); + if (result != GST_FLOW_OK) { + GST_ERROR_OBJECT (dec, "Failed to allocate output frame"); + return result; + } + + g_assert (dec->output_state != NULL); + if (!gst_video_frame_map (&outframe, &dec->output_state->info, + out_frame->output_buffer, GST_MAP_WRITE)) { + GST_ERROR_OBJECT (dec, "Failed to map output buffer"); + return GST_FLOW_ERROR; + } + + for (int plane = 0; plane < 3; plane++) { + int width = de265_get_image_width (img, plane); + int height = de265_get_image_height (img, plane); + int srcstride = width; + int dststride = GST_VIDEO_FRAME_COMP_STRIDE (&outframe, plane); + const uint8_t *src = de265_get_image_plane (img, plane, &srcstride); + uint8_t *dest = GST_VIDEO_FRAME_COMP_DATA (&outframe, plane); + if (srcstride == width && dststride == width) { + memcpy (dest, src, height * width); + } else { + while (height--) { + memcpy (dest, src, width); + src += srcstride; + dest += dststride; + } + } + } + gst_video_frame_unmap (&outframe); + return gst_video_decoder_finish_frame (decoder, out_frame); +} + +static GstFlowReturn +gst_libde265_dec_handle_frame (GstVideoDecoder * decoder, + GstVideoCodecFrame * frame) +{ + GstLibde265Dec *dec = GST_LIBDE265_DEC (decoder); + uint8_t *frame_data; + uint8_t *end_data; + const struct de265_image *img; + de265_error ret = DE265_OK; + int more = 0; + GstClockTime pts; + gsize size; + GstMapInfo info; + + pts = frame->pts; + if (pts == GST_CLOCK_TIME_NONE) { + pts = frame->dts; + } + + if (!gst_buffer_map (frame->input_buffer, &info, GST_MAP_READ)) { + GST_ERROR_OBJECT (dec, "Failed to map input buffer"); + return GST_FLOW_ERROR; + } + + frame_data = info.data; + size = info.size; + end_data = frame_data + size; + + if (size > 0) { + if (dec->format == GST_TYPE_LIBDE265_FORMAT_PACKETIZED) { + /* stream contains length fields and NALs */ + uint8_t *start_data = frame_data; + while (start_data + dec->length_size <= end_data) { + int nal_size = 0; + int i; + for (i = 0; i < dec->length_size; i++) { + nal_size = (nal_size << 8) | start_data[i]; + } + if (start_data + dec->length_size + nal_size > end_data) { + GST_ELEMENT_ERROR (decoder, STREAM, DECODE, + ("Overflow in input data, check stream format"), (NULL)); + goto error_input; + } + ret = + de265_push_NAL (dec->ctx, start_data + dec->length_size, nal_size, + (de265_PTS) pts, + (void *) (uintptr_t) (frame->system_frame_number + 1)); + if (ret != DE265_OK) { + GST_ELEMENT_ERROR (decoder, STREAM, DECODE, + ("Error while pushing data: %s (code=%d)", + de265_get_error_text (ret), ret), (NULL)); + goto error_input; + } + start_data += dec->length_size + nal_size; + } + } else { + ret = + de265_push_data (dec->ctx, frame_data, size, (de265_PTS) pts, + (void *) (uintptr_t) (frame->system_frame_number + 1)); + if (ret != DE265_OK) { + GST_ELEMENT_ERROR (decoder, STREAM, DECODE, + ("Error while pushing data: %s (code=%d)", + de265_get_error_text (ret), ret), (NULL)); + goto error_input; + } + } + } else { + ret = de265_flush_data (dec->ctx); + if (ret != DE265_OK) { + GST_ELEMENT_ERROR (decoder, STREAM, DECODE, + ("Error while flushing data: %s (code=%d)", + de265_get_error_text (ret), ret), (NULL)); + goto error_input; + } + } + gst_buffer_unmap (frame->input_buffer, &info); + + /* decode as much as possible */ + do { + ret = de265_decode (dec->ctx, &more); + } while (more && ret == DE265_OK); + + switch (ret) { + case DE265_OK: + case DE265_ERROR_WAITING_FOR_INPUT_DATA: + break; + + case DE265_ERROR_IMAGE_BUFFER_FULL: + dec->buffer_full = 1; + if ((img = de265_peek_next_picture (dec->ctx)) == NULL) { + return GST_FLOW_OK; + } + break; + + default: + GST_ELEMENT_ERROR (decoder, STREAM, DECODE, + ("Error while decoding: %s (code=%d)", de265_get_error_text (ret), + ret), (NULL)); + return GST_FLOW_ERROR; + } + + while ((ret = de265_get_warning (dec->ctx)) != DE265_OK) { + GST_ELEMENT_WARNING (decoder, STREAM, DECODE, + ("%s (code=%d)", de265_get_error_text (ret), ret), (NULL)); + } + + img = de265_get_next_picture (dec->ctx); + if (img == NULL) { + /* need more data */ + return GST_FLOW_OK; + } + + return _gst_libde265_return_image (decoder, frame, img); + +error_input: + gst_buffer_unmap (frame->input_buffer, &info); + return GST_FLOW_ERROR; +} + +gboolean +gst_libde265_dec_plugin_init (GstPlugin * plugin) +{ + /* create an elementfactory for the libde265 decoder element */ + if (!gst_element_register (plugin, "libde265dec", + GST_RANK_PRIMARY, GST_TYPE_LIBDE265_DEC)) + return FALSE; + + return TRUE; +} diff --git a/ext/libde265/libde265-dec.h b/ext/libde265/libde265-dec.h new file mode 100644 index 0000000000..03b76bd2c6 --- /dev/null +++ b/ext/libde265/libde265-dec.h @@ -0,0 +1,71 @@ +/* + * GStreamer HEVC/H.265 video codec. + * + * Copyright (c) 2014 struktur AG, Joachim Bauch + * + * 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. + */ + +#ifndef __GST_LIBDE265_DEC_H__ +#define __GST_LIBDE265_DEC_H__ + +#include +#include + +#include + +G_BEGIN_DECLS +#define GST_TYPE_LIBDE265_DEC \ + (gst_libde265_dec_get_type()) +#define GST_LIBDE265_DEC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_LIBDE265_DEC,GstLibde265Dec)) +#define GST_LIBDE265_DEC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_LIBDE265_DEC,GstLibde265DecClass)) + +typedef enum +{ + GST_TYPE_LIBDE265_FORMAT_PACKETIZED, + GST_TYPE_LIBDE265_FORMAT_BYTESTREAM +} GstLibde265DecFormat; + +typedef struct _GstLibde265Dec +{ + GstVideoDecoder parent; + + /* private */ + de265_decoder_context *ctx; + GstLibde265DecFormat format; + int length_size; + int max_threads; + int buffer_full; + void *codec_data; + int codec_data_size; + GstVideoCodecState *input_state; + GstVideoCodecState *output_state; +} GstLibde265Dec; + +typedef struct _GstLibde265DecClass +{ + GstVideoDecoderClass parent; +} GstLibde265DecClass; + +GType gst_libde265_dec_get_type (void); + +G_END_DECLS + +gboolean gst_libde265_dec_plugin_init (GstPlugin * plugin); + +#endif /* __GST_LIBDE265_DEC_H__ */ diff --git a/gst-plugins-bad.spec.in b/gst-plugins-bad.spec.in index c011bec23a..a5c4700a22 100644 --- a/gst-plugins-bad.spec.in +++ b/gst-plugins-bad.spec.in @@ -31,6 +31,7 @@ BuildRequires: libass-devel %ifnarch s390 s390x BuildRequires: libdc1394-devel %endif +BuildRequires: libde265-devel BuildRequires: libdvdnav-devel BuildRequires: libexif-devel BuildRequires: libiptcdata-devel @@ -266,6 +267,7 @@ make ERROR_CFLAGS='' ERROR_CXXFLAGS='' # %{_libdir}/gstreamer-%{majorminor}/libgstgsm.so %{_libdir}/gstreamer-%{majorminor}/libgstkate.so %{_libdir}/gstreamer-%{majorminor}/libgstladspa.so +%{_libdir}/gstreamer-%{majorminor}/libgstlibde265.so %{_libdir}/gstreamer-%{majorminor}/libgstmodplug.so %{_libdir}/gstreamer-%{majorminor}/libgstofa.so %{_libdir}/gstreamer-%{majorminor}/libgstresindvd.so