[vlc-devel] [PATCH]: HEVC/H.265 decoder using libde265

Rémi Denis-Courmont remi at remlab.net
Wed May 7 17:12:48 CEST 2014


    Hello,

contrib and module should be separate patches.

Also...

diff --git a/THANKS b/THANKS
index 95bf02d..9300318 100644
--- a/THANKS
+++ b/THANKS
@@ -29,6 +29,7 @@ libbluray - Copyright (c) 2009-2013 VideoLAN and 
authors - LGPLv2.1 or later
  libcaca - Copyright (c) 2004 Sam Hocevar - WTFPL / LGPLv2.1 or later /
     GPLv2 or later / ISC
  libdca - Copyright (c) 2004-2007 VideoLAN and authors - GPLv2 or later
+libde265 - Copyright (c) 2013-2014 struktur AG - LGPLv3 or later
  libdvbpsi - Copyright (c) 2001-2013 VideoLAN and authors - LGPLv2.1 or 
later
  libdvdcss - Copyright (c) 2001-2012 VideoLAN and authors - GPLv2 or 
later
  libdvdread - LGPLv2.1 or later
diff --git a/configure.ac b/configure.ac
index 2f28385..0589a58 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2712,6 +2712,11 @@ dnl
  PKG_ENABLE_MODULES_VLC([SCHROEDINGER], [], [schroedinger-1.0 >= 
1.0.10], [dirac decoder and encoder using schroedinger], [auto])

  dnl
+dnl  libde265 decoder plugin (for hevc/h.265 video)
+dnl
+PKG_ENABLE_MODULES_VLC([DE265], [], [libde265 >= 0.6], [hevc/h.265 
decoder using libde265], [auto])
+
+dnl
  dnl  PNG decoder module
  dnl
  AC_ARG_ENABLE(png,
diff --git a/contrib/src/libde265/rules.mak 
b/contrib/src/libde265/rules.mak
new file mode 100644
index 0000000..4c6ac24
--- /dev/null
+++ b/contrib/src/libde265/rules.mak
@@ -0,0 +1,21 @@
+# libde265
+
+LIBDE265_VERSION := 0.6
+LIBDE265_URL := 
https://github.com/strukturag/libde265/releases/download/v$(LIBDE265_VERSION)/libde265-$(LIBDE265_VERSION).tar.gz
+
+$(TARBALLS)/libde265-$(LIBDE265_VERSION).tar.gz:
+	$(call download,$(LIBDE265_URL))
+
+.sum-libde265: libde265-$(LIBDE265_VERSION).tar.gz
+	$(warning $@ not implemented)

WTH is that not implemented?

+	touch $@
+
+libde265: libde265-$(LIBDE265_VERSION).tar.gz .sum-libde265
+	$(UNPACK)
+	$(UPDATE_AUTOCONFIG)
+	$(MOVE)
+
+.libde265: libde265
+	cd $< && $(HOSTVARS) CFLAGS="$(CFLAGS) -O3" ./configure $(HOSTCONF) 
--disable-dec265 --disable-sherlock265
+	cd $< && $(MAKE) install
+	touch $@
diff --git a/extras/package/ios/build.sh b/extras/package/ios/build.sh
index c3aec25..eae01b5 100755
--- a/extras/package/ios/build.sh
+++ b/extras/package/ios/build.sh
@@ -224,6 +224,7 @@ echo "EXTRA_CFLAGS += ${EXTRA_CFLAGS}" >> 
config.mak
  echo "EXTRA_LDFLAGS += ${EXTRA_LDFLAGS}" >> config.mak
  make fetch
  make
+make .libde265

I'm not familiar with iOS, but this is dubious.

  spopd

  info "Bootstraping vlc"
diff --git a/extras/package/macosx/build.sh 
b/extras/package/macosx/build.sh
index abac1e5..92ce147 100755
--- a/extras/package/macosx/build.sh
+++ b/extras/package/macosx/build.sh
@@ -116,6 +116,7 @@ mkdir -p build && cd build
  ../bootstrap --build=$TRIPLET --host=$TRIPLET > $out
  if [ ! -e "../$TRIPLET" ]; then
      make prebuilt > $out
+    make .libde265 > $out

Ditto.

  fi
  spopd

diff --git a/modules/codec/Makefile.am b/modules/codec/Makefile.am
index 14500c6..31386bd 100644
--- a/modules/codec/Makefile.am
+++ b/modules/codec/Makefile.am
@@ -94,6 +94,13 @@ libschroedinger_plugin_la_LIBADD = 
$(LIBS_schroedinger)
  EXTRA_LTLIBRARIES += libschroedinger_plugin.la
  codec_LTLIBRARIES += $(LTLIBschroedinger)

+libde265_plugin_la_SOURCES = codec/libde265dec.c
+libde265_plugin_la_CFLAGS = $(AM_CFLAGS) $(CFLAGS_de265)
+libde265_plugin_la_LDFLAGS = $(AM_LDFLAGS) -rpath '$(codecdir)'
+libde265_plugin_la_LIBADD = $(LIBS_de265)
+EXTRA_LTLIBRARIES += libde265_plugin.la
+codec_LTLIBRARIES += $(LTLIBde265)
+

  ### Image codecs ###

diff --git a/modules/codec/libde265dec.c b/modules/codec/libde265dec.c
new file mode 100644
index 0000000..5bfad8c
--- /dev/null
+++ b/modules/codec/libde265dec.c
@@ -0,0 +1,392 @@
+/*****************************************************************************
+ * libde265dec.c: libde265 decoder (HEVC/H.265) module
+ 
*****************************************************************************
+ * Copyright (C) 2014 struktur AG
+ *
+ * Authors: Joachim Bauch <bauch at struktur.de>
+ *
+ * This program is free software; you can redistribute it and/or 
modify it
+ * under the terms of the GNU Lesser General Public License as 
published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public 
License
+ * along with this program; if not, write to the Free Software 
Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ 
*****************************************************************************/
+
+/*****************************************************************************
+ * Preamble
+ 
*****************************************************************************/
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_codec.h>
+
+#include <libde265/de265.h>
+
+// Default size of length headers for packetized streams.
+// Should always come from the "extra" data.
+#define DEFAULT_LENGTH_SIZE     4
+
+// Maximum number of threads to use
+#define MAX_THREAD_COUNT        32
+
+// Drop all frames if late frames were available for more than 5 
seconds
+#define LATE_FRAMES_DROP_ALWAYS_AGE 5
+
+// Tell decoder to skip decoding if more than 4 late frames
+#define LATE_FRAMES_DROP_DECODER    4
+
+// Don't pass data to decoder if more than 12 late frames
+#define LATE_FRAMES_DROP_HARD       12
+
+/****************************************************************************
+ * Local prototypes
+ 
****************************************************************************/
+static int Open(vlc_object_t *);
+static void Close(vlc_object_t *);
+
+/*****************************************************************************
+ * Module descriptor
+ 
*****************************************************************************/
+
+vlc_module_begin ()
+    set_shortname("libde265dec")

Do we assume this is never localized? Not everybody uses the Latin 
alphabet.

+    set_description(N_("HEVC/H.265 video decoder using libde265"))
+    set_capability("decoder", 200)
+    set_callbacks(Open, Close)
+    set_category(CAT_INPUT)
+    set_subcategory(SUBCAT_INPUT_VCODEC)
+vlc_module_end ()
+
+/*****************************************************************************
+ * decoder_sys_t: libde265 decoder descriptor
+ 
*****************************************************************************/
+struct decoder_sys_t
+{
+    de265_decoder_context *ctx;
+
+    bool check_extra;
+    bool packetized;
+    int length_size;
+    int late_frames;
+    mtime_t late_frames_start;
+};
+
+/****************************************************************************
+ * Decode: the whole thing
+ 
****************************************************************************/
+static picture_t *Decode(decoder_t *dec, block_t **pp_block)
+{
+    decoder_sys_t *sys = dec->p_sys;
+    de265_decoder_context *ctx = sys->ctx;
+    bool drawpicture;
+    bool prerolling;
+    de265_error err;
+    int can_decode_more;
+    const struct de265_image *image;
+
+    block_t *block = *pp_block;
+    if (!block)
+        return NULL;

I think pp_block can be NULL to trigger flushing nowadays, but I am not 
sure.

+
+    if (block->i_flags & 
(BLOCK_FLAG_DISCONTINUITY|BLOCK_FLAG_CORRUPTED)) {
+        sys->late_frames = 0;
+        if (block->i_flags & BLOCK_FLAG_DISCONTINUITY) {
+            de265_reset(ctx);
+        }
+        goto error;
+    }
+
+    if (sys->check_extra) {
+        int extra_length = dec->fmt_in.i_extra;
+        sys->check_extra = false;
+        if (extra_length > 0) {
+            unsigned char *extra = (unsigned char *) 
dec->fmt_in.p_extra;
+            if (extra_length > 3 && extra != NULL && (extra[0] || 
extra[1] || extra[2] > 1)) {
+                sys->packetized = true;
+                if (extra_length > 21) {
+                    sys->length_size = (extra[21] & 3) + 1;
+                }
+                msg_Dbg(dec, "Assuming packetized data (%d bytes 
length)", sys->length_size);
+            } else {
+                sys->packetized = false;
+                msg_Dbg(dec, "Assuming non-packetized data");
+                err = de265_push_data(ctx, extra, extra_length, 0, 
NULL);
+                if (!de265_isOK(err)) {
+                    msg_Err(dec, "Failed to push extra data: %s (%d)", 
de265_get_error_text(err), err);
+                    goto error;
+                }
+            }
+#if LIBDE265_NUMERIC_VERSION >= 0x00070000
+            de265_push_end_of_NAL(ctx);
+#endif
+            do {
+                err = de265_decode(ctx, &can_decode_more);
+                switch (err) {
+                case DE265_OK:
+                    break;
+
+                case DE265_ERROR_IMAGE_BUFFER_FULL:
+                case DE265_ERROR_WAITING_FOR_INPUT_DATA:
+                    // not really an error
+                    can_decode_more = 0;
+                    break;
+
+                default:
+                    if (!de265_isOK(err)) {
+                        msg_Err(dec, "Failed to decode extra data: %s 
(%d)", de265_get_error_text(err), err);
+                        goto error;
+                    }
+                }
+            } while (can_decode_more);
+        }
+    }
+
+    if ((prerolling = (block->i_flags & BLOCK_FLAG_PREROLL))) {
+        sys->late_frames = 0;
+        drawpicture = false;
+    } else {
+        drawpicture = true;
+    }
+
+    if (!dec->b_pace_control && (sys->late_frames > 0) &&
+        (mdate() - sys->late_frames_start > 
INT64_C(LATE_FRAMES_DROP_ALWAYS_AGE*1000000))) {

Use CLOCK_FREQ.

+        sys->late_frames--;
+        msg_Err(dec, "more than %d seconds of late video -> "
+                "dropping frame (computer too slow ?)", 
LATE_FRAMES_DROP_ALWAYS_AGE);
+        goto error;
+    }
+
+    if (!dec->b_pace_control &&
+        (sys->late_frames > LATE_FRAMES_DROP_DECODER)) {
+        drawpicture = false;
+        if (sys->late_frames < LATE_FRAMES_DROP_HARD) {
+            // we could tell the decoder to skip frame, this will be
+            // available in a later version of libde265.
+            // for now, pass to decoder...
+        } else {
+            // picture too late, won't decode, but break picture until
+            // a new keyframe is available
+            sys->late_frames--; /* needed else it will never be 
decrease */
+            msg_Warn(dec, "More than %d late frames, dropping frame", 
LATE_FRAMES_DROP_DECODER);
+            goto error;
+        }
+    }
+
+    uint8_t *p_buffer = block->p_buffer;
+    size_t i_buffer = block->i_buffer;
+    if (i_buffer > 0) {
+        if (sys->packetized) {
+            while (i_buffer >= (size_t) sys->length_size) {
+                int i;
+                uint32_t length = 0;
+                for (i=0; i<sys->length_size; i++) {
+                    length = (length << 8) | p_buffer[i];
+                }
+
+                p_buffer += sys->length_size;
+                i_buffer -= sys->length_size;
+                if (length > i_buffer) {
+                    msg_Err(dec, "Buffer underrun while pushing data 
(%d > %ld)", length, i_buffer);
+                    goto error;
+                }
+
+                err = de265_push_NAL(ctx, p_buffer, length, 
block->i_pts, NULL);
+                if (!de265_isOK(err)) {
+                    msg_Err(dec, "Failed to push data: %s (%d)", 
de265_get_error_text(err), err);
+                    goto error;
+                }
+
+                p_buffer += length;
+                i_buffer -= length;
+            }
+        } else {
+            err = de265_push_data(ctx, p_buffer, i_buffer, 
block->i_pts, NULL);
+            if (!de265_isOK(err)) {
+                msg_Err(dec, "Failed to push data: %s (%d)", 
de265_get_error_text(err), err);
+                goto error;
+            }
+        }
+    } else {
+        err = de265_flush_data(ctx);
+        if (!de265_isOK(err)) {
+            msg_Err(dec, "Failed to flush data: %s (%d)", 
de265_get_error_text(err), err);
+            goto error;
+        }
+    }
+    block_Release(block);
+    *pp_block = NULL;
+
+    mtime_t pts;
+    // decode (and skip) all available images (e.g. when prerolling
+    // after a seek)
+    do {
+        // decode data until we get an image or no more data is
+        // available for decoding
+        do {
+            err = de265_decode(ctx, &can_decode_more);
+            switch (err) {
+            case DE265_OK:
+                break;
+
+            case DE265_ERROR_IMAGE_BUFFER_FULL:
+            case DE265_ERROR_WAITING_FOR_INPUT_DATA:
+                // not really an error
+                can_decode_more = 0;
+                break;
+
+            default:
+                if (!de265_isOK(err)) {
+                    msg_Err(dec, "Failed to decode frame: %s (%d)", 
de265_get_error_text(err), err);
+                    return NULL;
+                }
+            }
+
+            image = de265_get_next_picture(ctx);
+        } while (image == NULL && can_decode_more);
+        if (!image) {
+            return NULL;
+        }
+
+        if (de265_get_chroma_format(image) != de265_chroma_420) {
+            msg_Err(dec, "Unsupported output colorspace %d", 
de265_get_chroma_format(image));
+            return NULL;
+        }
+
+        pts = de265_get_image_PTS(image);
+
+        mtime_t display_date = 0;
+        if (!prerolling) {
+            display_date = decoder_GetDisplayDate(dec, pts);
+        }
+
+        if (display_date > 0 && display_date <= mdate()) {
+            sys->late_frames++;
+            if (sys->late_frames == 1) {
+                sys->late_frames_start = mdate();
+            }
+        } else {
+            sys->late_frames = 0;
+        }
+    } while (!drawpicture);
+
+    video_format_t *v = &dec->fmt_out.video;
+    int width = de265_get_image_width(image, 0);
+    int height = de265_get_image_height(image, 0);
+
+    if (width != (int) v->i_width || height != (int) v->i_height) {
+        v->i_width = width;
+        v->i_height = height;
+    }
+    if (width != (int) v->i_visible_width || height != (int) 
v->i_visible_height) {
+        v->i_visible_width = width;
+        v->i_visible_height = height;
+    }
+
+    picture_t *pic = decoder_NewPicture(dec);
+    if (!pic)
+        return NULL;
+
+    for (int plane = 0; plane < pic->i_planes; plane++ ) {
+        int src_stride;
+        const uint8_t *src = de265_get_image_plane(image, plane, 
&src_stride);
+        int dst_stride = pic->p[plane].i_pitch;
+        uint8_t *dst = pic->p[plane].p_pixels;
+
+        int size = __MIN( src_stride, dst_stride );
+        for( int line = 0; line < pic->p[plane].i_visible_lines; 
line++ ) {
+            memcpy( dst, src, size );

Ouch... can't this library decode directly to the picture buffers?

This is especially bad with a precedence higher than libavcvodec's...

+            src += src_stride;
+            dst += dst_stride;
+        }
+    }
+
+    pic->b_progressive = true; /* codec does not support interlacing 
*/
+    pic->date = pts;
+
+    return pic;
+
+error:
+    if (*pp_block != NULL) {

Tautology?

+        block_Release(*pp_block);
+    }
+    *pp_block = NULL;
+    return NULL;
+}
+
+/*****************************************************************************
+ * Open: probe the decoder
+ 
*****************************************************************************/
+static int Open(vlc_object_t *p_this)
+{
+    decoder_t *dec = (decoder_t *)p_this;
+
+    if (dec->fmt_in.i_codec != VLC_CODEC_HEVC) {
+        return VLC_EGENERIC;
+    }
+
+    decoder_sys_t *sys = malloc(sizeof(*sys));
+    if (!sys)
+        return VLC_ENOMEM;
+    dec->p_sys = sys;
+
+    msg_Dbg(p_this, "using libde265 version %s", de265_get_version());
+    if ((sys->ctx = de265_new_decoder()) == NULL) {
+        msg_Err(p_this, "Failed to initialize decoder\n");
+        free(sys);
+        return VLC_EGENERIC;
+    }
+
+    // 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%.
+    int threads = __MIN(vlc_GetCPUCount() * 2, MAX_THREAD_COUNT);
+    if (threads > 1) {

Seems like a tautology.

+        de265_error err;
+        err = de265_start_worker_threads(sys->ctx, threads);
+        if (!de265_isOK(err)) {
+            // don't report to caller, decoding will work anyway...
+            msg_Err(dec, "Failed to start worker threads: %s (%d)", 
de265_get_error_text(err), err);
+        } else {
+            msg_Dbg(p_this, "started %d worker threads", threads);
+        }
+    }
+
+    dec->pf_decode_video = Decode;
+
+    dec->fmt_out.i_cat = VIDEO_ES;
+    dec->fmt_out.video.i_width = dec->fmt_in.video.i_width;
+    dec->fmt_out.video.i_height = dec->fmt_in.video.i_height;
+    dec->fmt_out.i_codec = VLC_CODEC_I420;
+    dec->b_need_packetized = true;
+
+    sys->check_extra = true;
+    sys->length_size = DEFAULT_LENGTH_SIZE;
+    sys->packetized = dec->fmt_in.b_packetized;
+    sys->late_frames = 0;
+
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * Close: decoder destruction
+ 
*****************************************************************************/
+static void Close(vlc_object_t *p_this)
+{
+    decoder_t *dec = (decoder_t *)p_this;
+    decoder_sys_t *sys = dec->p_sys;
+
+    de265_free_decoder(sys->ctx);
+
+    free(sys);
+}
-- 
1.7.9.5

-- 
Rémi Denis-Courmont



More information about the vlc-devel mailing list