]> git.ipfire.org Git - thirdparty/tvheadend.git/commitdiff
[wip]: codec profiles + transcode + vaapi
authorlekma <lekmalek@gmail.com>
Sun, 21 Aug 2016 07:32:18 +0000 (09:32 +0200)
committerJaroslav Kysela <perex@perex.cz>
Mon, 28 Aug 2017 13:32:17 +0000 (15:32 +0200)
77 files changed:
Makefile
Makefile.ffmpeg
Makefile.webui
configure
src/api.c
src/api.h
src/api/api_codec.c [new file with mode: 0644]
src/dvr/dvr_autorec.c
src/dvr/dvr_config.c
src/dvr/dvr_db.c
src/dvr/dvr_rec.c
src/dvr/dvr_timerec.c
src/esfilter.h
src/libav.c
src/main.c
src/muxer/muxer_libav.c
src/muxer/muxer_mkv.c
src/parsers/bitstream.h
src/plumbing/globalheaders.c
src/plumbing/transcoding.c [deleted file]
src/plumbing/transcoding.h [deleted file]
src/plumbing/tsfix.c
src/profile.c
src/prop.c
src/prop.h
src/streaming.c
src/transcoding/codec.h [new file with mode: 0644]
src/transcoding/codec/codec.c [new file with mode: 0644]
src/transcoding/codec/codecs/aac.c [new file with mode: 0644]
src/transcoding/codec/codecs/libs/libfdk_aac.c [new file with mode: 0644]
src/transcoding/codec/codecs/libs/libopus.c [new file with mode: 0644]
src/transcoding/codec/codecs/libs/libtheora.c [new file with mode: 0644]
src/transcoding/codec/codecs/libs/libvorbis.c [new file with mode: 0644]
src/transcoding/codec/codecs/libs/libvpx.c [new file with mode: 0644]
src/transcoding/codec/codecs/libs/libx26x.c [new file with mode: 0644]
src/transcoding/codec/codecs/libs/omx.c [new file with mode: 0644]
src/transcoding/codec/codecs/libs/vaapi.c [new file with mode: 0644]
src/transcoding/codec/codecs/mp2.c [new file with mode: 0644]
src/transcoding/codec/codecs/mpeg2video.c [new file with mode: 0644]
src/transcoding/codec/codecs/vorbis.c [new file with mode: 0644]
src/transcoding/codec/internals.h [new file with mode: 0644]
src/transcoding/codec/module.c [new file with mode: 0644]
src/transcoding/codec/profile.c [new file with mode: 0644]
src/transcoding/codec/profile_audio_class.c [new file with mode: 0644]
src/transcoding/codec/profile_class.c [new file with mode: 0644]
src/transcoding/codec/profile_video_class.c [new file with mode: 0644]
src/transcoding/memutils.c [new file with mode: 0644]
src/transcoding/memutils.h [new file with mode: 0644]
src/transcoding/transcode.h [new file with mode: 0644]
src/transcoding/transcode/audio.c [new file with mode: 0644]
src/transcoding/transcode/context.c [new file with mode: 0644]
src/transcoding/transcode/helpers.c [new file with mode: 0644]
src/transcoding/transcode/hwaccels/hwaccels.c [new file with mode: 0644]
src/transcoding/transcode/hwaccels/hwaccels.h [new file with mode: 0644]
src/transcoding/transcode/hwaccels/vaapi.c [new file with mode: 0644]
src/transcoding/transcode/hwaccels/vaapi.h [new file with mode: 0644]
src/transcoding/transcode/internals.h [new file with mode: 0644]
src/transcoding/transcode/log.h [new file with mode: 0644]
src/transcoding/transcode/module.c [new file with mode: 0644]
src/transcoding/transcode/stream.c [new file with mode: 0644]
src/transcoding/transcode/transcoder.c [new file with mode: 0644]
src/transcoding/transcode/video.c [new file with mode: 0644]
src/tvheadend.h
src/tvhlog.c
src/tvhlog.h
src/webui/static/app/codec.js [new file with mode: 0644]
src/webui/static/app/esfilter.js
src/webui/static/app/ext.css
src/webui/static/app/idnode.js
src/webui/static/app/profile.js [new file with mode: 0644]
src/webui/static/app/tvheadend.js
src/webui/static/extjs/examples/ux/Spinner.js [changed from symlink to file mode: 0644]
src/webui/static/extjs/examples/ux/SpinnerField.js [changed from symlink to file mode: 0644]
src/webui/static/icons/plugin_disabled.png [new symlink]
support/patches/libmfx.linux.path.diff [deleted file]
support/patches/libx265.pic.diff
support/patches/libx265.pie.diff

index b3ce2db3a02003913f90b7deba2c0f4da848be23..c2e7806106e04a5e71814d530425f8766f21d6df 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -133,8 +133,8 @@ ifeq ($(CONFIG_LIBFDKAAC_STATIC),yes)
 FFMPEG_DEPS += libfdk-aac
 endif
 
-ifeq ($(CONFIG_LIBMFX_STATIC),yes)
-FFMPEG_DEPS += libmfx
+ifeq ($(CONFIG_LIBOPUS_STATIC),yes)
+FFMPEG_DEPS += libopus
 endif
 
 LDFLAGS += $(foreach lib,$(FFMPEG_LIBS),$(FFMPEG_LIBDIR)/$(lib).a)
@@ -293,6 +293,7 @@ SRCS-2 = \
        src/api/api_access.c \
        src/api/api_dvr.c \
        src/api/api_caclient.c \
+       src/api/api_codec.c \
        src/api/api_profile.c \
        src/api/api_bouquet.c \
        src/api/api_language.c \
@@ -474,6 +475,43 @@ SRCS-BONJOUR = \
 SRCS-$(CONFIG_BONJOUR) = $(SRCS-BONJOUR)
 I18N-C += $(SRCS-BONJOUR)
 
+# codecs
+SRCS-CODECS = $(wildcard src/transcoding/codec/*.c)
+SRCS-CODECS += $(wildcard src/transcoding/codec/codecs/*.c)
+ifneq (,$(filter yes,$(CONFIG_LIBX264) $(CONFIG_LIBX265)))
+LIBS-CODECS += libx26x
+endif
+ifeq ($(CONFIG_LIBVPX),yes)
+LIBS-CODECS += libvpx
+endif
+ifeq ($(CONFIG_LIBTHEORA),yes)
+LIBS-CODECS += libtheora
+endif
+ifeq ($(CONFIG_LIBVORBIS),yes)
+LIBS-CODECS += libvorbis
+endif
+ifeq ($(CONFIG_LIBFDKAAC),yes)
+LIBS-CODECS += libfdk_aac
+endif
+ifeq ($(CONFIG_LIBOPUS),yes)
+LIBS-CODECS += libopus
+endif
+ifeq ($(CONFIG_VAAPI),yes)
+LIBS-CODECS += vaapi
+endif
+ifeq ($(CONFIG_OMX),yes)
+LIBS-CODECS += omx
+endif
+SRCS-CODECS += $(foreach lib,$(LIBS-CODECS),src/transcoding/codec/codecs/libs/$(lib).c)
+
+#hwaccels
+ifeq ($(CONFIG_HWACCELS),yes)
+SRCS-HWACCELS += src/transcoding/transcode/hwaccels/hwaccels.c
+ifeq ($(CONFIG_VAAPI),yes)
+SRCS-HWACCELS += src/transcoding/transcode/hwaccels/vaapi.c
+endif
+endif
+
 # libav
 DEPS-LIBAV = \
        src/main.c \
@@ -481,8 +519,11 @@ DEPS-LIBAV = \
 SRCS-LIBAV = \
        src/libav.c \
        src/input/mpegts/iptv/iptv_libav.c \
-       src/muxer/muxer_libav.c \
-       src/plumbing/transcoding.c
+       src/muxer/muxer_libav.c
+SRCS-LIBAV += $(wildcard src/transcoding/*.c)
+SRCS-LIBAV += $(wildcard src/transcoding/transcode/*.c)
+SRCS-LIBAV += $(SRCS-HWACCELS)
+SRCS-LIBAV += $(SRCS-CODECS)
 SRCS-$(CONFIG_LIBAV) += $(SRCS-LIBAV)
 I18N-C += $(SRCS-LIBAV)
 
index b605ce8857a507c37e76fc44417314c2789d7252..d060656c2c18bad1702fcaef92351b3d3cbd1009 100644 (file)
@@ -36,12 +36,13 @@ EXTLIBS        =
 COMPONENTS     = avutil avcodec avformat swscale avresample swresample avfilter
 PROTOCOLS      = file http https hls mmsh mmst rtmp rtmpe rtmps rtmpt rtmpte rtmpts \
                  ffrtmpcrypt ffrtmphttp rtp srtp tcp udp udplite unix
-DECODERS       = mpeg2video mp2 aac vorbis ac3 eac3 aac_latm h264 hevc
+DECODERS       = mpeg2video mp2 aac vorbis ac3 eac3 aac_latm opus h264 hevc theora
 ENCODERS       = mpeg2video mp2 aac vorbis
 MUXERS         = mpegts dvd matroska mp4
 DEMUXERS       = mpegts mpeg matroska mp4 hls flv live_flv
 BSFS           = h264_mp4toannexb hevc_mp4toannexb
-FILTERS        = yadif scale null aresample anull
+FILTERS        = yadif format hwupload hwdownload scale null aresample anull
+HWACCELS       =
 
 YASM           = yasm-1.3.0
 YASM_TB        = $(YASM).tar.gz
@@ -88,19 +89,16 @@ LIBFDKAAC_TB   = $(LIBFDKAAC).tar.gz
 LIBFDKAAC_URL  = https://freefr.dl.sourceforge.net/project/opencore-amr/fdk-aac/$(LIBFDKAAC_TB)
 LIBFDKAAC_SHA1 = 9215d19bdd911954fd5bc879c707ab571716ba0d
 
-LIBMFX_HASH    = e6fc25c974839dcbcf483edee01f51142ddc74f3
-LIBMFX         = mfx_dispatch-$(LIBMFX_HASH)
-LIBMFX_TB      = $(LIBMFX_HASH).tar.gz
-LIBMFX_URL     = https://github.com/lu-zero/mfx_dispatch/archive/$(LIBMFX_TB)
-LIBMFX_SHA1    = 33dec0b054d842e8380eb31b97afe02d07d5c6fc
-LIBMFX_DIFFS   =
+LIBOPUS        = opus-1.1.3
+LIBOPUS_TB     = $(LIBOPUS).tar.gz
+LIBOPUS_URL    = http://downloads.xiph.org/releases/opus/$(LIBOPUS_TB)
+LIBOPUS_SHA1   = 7cb1bef20975afbf14a8a43308aed9cb41629f37
 
 FFMPEG         = ffmpeg-3.3.3
 FFMPEG_TB      = $(FFMPEG).tar.bz2
 FFMPEG_URL     = http://ffmpeg.org/releases/$(FFMPEG_TB)
 FFMPEG_SHA1    = 23bce5ccc4aeae23e1893d6cea7b1bd57b2591cb
 
-
 # ##############################################################################
 # Library Config
 # ##############################################################################
@@ -157,6 +155,7 @@ ELIBS     := -L$(EPREFIX)/lib -ldl
 CONFIGURE := FFMPEG_PREFIX=$(EPREFIX) \
              PKG_CONFIG=$(ROOTDIR)/support/pkg-config.ffmpeg \
              ./configure --prefix=/ffmpeg --enable-static --disable-shared
+CONFIGURE_PI := CC="$(COMPILER) $(CFLAGS_PI)" $(CONFIGURE)
 
 # ##############################################################################
 # YASM
@@ -169,7 +168,7 @@ $(LIB_ROOT)/$(YASM)/.tvh_download:
 
 $(LIB_ROOT)/$(YASM)/.tvh_build: \
                $(LIB_ROOT)/$(YASM)/.tvh_download
-       cd $(LIB_ROOT)/$(YASM) && $(CONFIGURE) \
+       cd $(LIB_ROOT)/$(YASM) && $(CONFIGURE_PI) \
                --libdir=/$(EPREFIX0)/lib
        DESTDIR=$(EBUILDIR) \
                $(MAKE) -C $(LIB_ROOT)/$(YASM) install
@@ -210,7 +209,7 @@ $(LIB_ROOT)/$(LIBX264)/.tvh_build: \
                $(LIB_ROOT)/$(LIBX264)/.tvh_download
        cd $(LIB_ROOT)/$(LIBX264) && $(CONFIGURE) \
                --extra-asflags="-DPIC" \
-                --extra-cflags="$(CFLAGS_PI)" \
+               --extra-cflags="$(CFLAGS_PI)" \
                --disable-cli \
                --disable-swscale \
                --disable-lavf \
@@ -312,7 +311,7 @@ $(LIB_ROOT)/$(LIBVPX)/.tvh_build: \
                --disable-examples \
                --disable-docs \
                --disable-unit-tests \
-                $(LIBVPX_TARGET)
+               $(LIBVPX_TARGET)
        DIST_DIR=$(EPREFIX) \
                $(MAKE) -C $(LIB_ROOT)/$(LIBVPX) install
        @touch $@
@@ -345,7 +344,7 @@ $(LIB_ROOT)/$(LIBOGG)/.tvh_build: \
                $(LIB_ROOT)/$(YASM)/.tvh_build \
                $(LIB_ROOT)/$(LIBOGG)/.tvh_download
        cd $(LIB_ROOT)/$(LIBOGG) && \
-               CFLAGS="$(CFLAGS_PI)" $(CONFIGURE) \
+               $(CONFIGURE_PI) \
                --libdir=/$(EPREFIX0)/lib
        DESTDIR=$(EBUILDIR) \
                $(MAKE) -C $(LIB_ROOT)/$(LIBOGG) install
@@ -384,7 +383,7 @@ $(LIB_ROOT)/$(LIBTHEORA)/.tvh_build: \
                $(LIB_ROOT)/$(LIBOGG)/.tvh_build \
                $(LIB_ROOT)/$(LIBTHEORA)/.tvh_download
        cd $(LIB_ROOT)/$(LIBTHEORA) && \
-               CFLAGS="$(CFLAGS_PI)" $(CONFIGURE) \
+               $(CONFIGURE_PI) \
                --libdir=/$(EPREFIX0)/lib \
                --with-ogg=$(EPREFIX) \
                --disable-examples \
@@ -432,7 +431,7 @@ $(LIB_ROOT)/$(LIBVORBIS)/.tvh_build: \
                $(LIB_ROOT)/$(LIBOGG)/.tvh_build \
                $(LIB_ROOT)/$(LIBVORBIS)/.tvh_download
        cd $(LIB_ROOT)/$(LIBVORBIS) && \
-               CFLAGS="$(CFLAGS_PI)" $(CONFIGURE) \
+               $(CONFIGURE_PI) \
                --libdir=/$(EPREFIX0)/lib \
                --with-ogg=$(EPREFIX)
        DESTDIR=$(EBUILDIR) \
@@ -473,8 +472,7 @@ $(LIB_ROOT)/$(LIBFDKAAC)/.tvh_download:
 
 $(LIB_ROOT)/$(LIBFDKAAC)/.tvh_build: \
                $(LIB_ROOT)/$(LIBFDKAAC)/.tvh_download
-       cd $(LIB_ROOT)/$(LIBFDKAAC) && \
-               CXXFLAGS="$(CFLAGS_PI) -std=c++98" CFLAGS="$(CFLAGS_PI)" $(CONFIGURE) \
+       cd $(LIB_ROOT)/$(LIBFDKAAC) && CXXFLAGS="-std=c++98" $(CONFIGURE_PI) \
                --libdir=/$(EPREFIX0)/lib
        DESTDIR=$(EBUILDIR) \
                $(MAKE) -C $(LIB_ROOT)/$(LIBFDKAAC) install
@@ -494,16 +492,47 @@ endif
 
 
 # ##############################################################################
-# VAAPI
+# LIBOPUS
 #
 
-ifeq (yes,$(CONFIG_VAAPI))
+ifeq (yes,$(CONFIG_LIBOPUS))
 
-EXTLIBS += vaapi
-ENCODERS += h264_vaapi hevc_vaapi
+EXTLIBS  += libopus
+ENCODERS += libopus
+
+endif
+
+
+ifeq (yes,$(CONFIG_LIBOPUS_STATIC))
+
+$(LIB_ROOT)/$(LIBOPUS)/.tvh_download:
+       $(call DOWNLOAD,$(LIBOPUS_URL),$(LIB_ROOT)/$(LIBOPUS_TB),$(LIBOPUS_SHA1))
+       $(call UNTAR,$(LIBOPUS_TB),z)
+       @touch $@
+
+$(LIB_ROOT)/$(LIBOPUS)/.tvh_build: \
+               $(LIB_ROOT)/$(YASM)/.tvh_build \
+               $(LIB_ROOT)/$(LIBOPUS)/.tvh_download
+       cd $(LIB_ROOT)/$(LIBOPUS) && $(CONFIGURE_PI) \
+               --disable-doc \
+               --disable-extra-programs
+       DESTDIR=$(EBUILDIR) \
+               $(MAKE) -C $(LIB_ROOT)/$(LIBOPUS) install
+       @touch $@
+
+else
+
+$(LIB_ROOT)/$(LIBOPUS)/.tvh_download:
+       @mkdir -p $(LIB_ROOT)/$(LIBOPUS)
+       @touch $@
+
+$(LIB_ROOT)/$(LIBOPUS)/.tvh_build: \
+       $(LIB_ROOT)/$(LIBOPUS)/.tvh_download
+       @touch $@
 
 endif
 
+
 # ##############################################################################
 # NVENC
 #
@@ -515,46 +544,35 @@ ENCODERS += nvenc_h264 nvenc_hevc
 
 endif
 
+
 # ##############################################################################
-# LIBMFX
+# VAAPI
 #
 
-ifeq (yes,$(CONFIG_QSV))
+ifeq (yes,$(CONFIG_VAAPI))
 
-EXTLIBS  += libmfx
-DECODERS += mpeg2_qsv h264_qsv hevc_qsv
-ENCODERS += mpeg2_qsv h264_qsv hevc_qsv
+EXTLIBS  += vaapi
+ENCODERS += h264_vaapi hevc_vaapi
+HWACCELS += mpeg2_vaapi h264_vaapi hevc_vaapi
+FILTERS  += scale_vaapi
 
 endif
 
 
-ifeq (yes,$(CONFIG_LIBMFX_STATIC))
+# ##############################################################################
+# OMX
+#
 
-$(LIB_ROOT)/$(LIBMFX)/.tvh_download:
-       $(call DOWNLOAD,$(LIBMFX_URL),$(LIB_ROOT)/$(LIBMFX_TB),$(LIBMFX_SHA1))
-       $(call UNTAR,$(LIBMFX_TB),z)
-       $(call PATCH,$(LIBMFX),$(LIBMFX_DIFFS))
-       @touch $@
+ifeq (yes,$(CONFIG_OMX))
 
-$(LIB_ROOT)/$(LIBMFX)/.tvh_build: \
-               $(LIB_ROOT)/$(LIBMFX)/.tvh_download
-       cd $(LIB_ROOT)/$(LIBMFX) && autoreconf -i && \
-               CXXFLAGS="$(CFLAGS_PI)" CFLAGS="$(CFLAGS_PI)" $(CONFIGURE) \
-               --with-libva_x11 \
-               --with-libva_drm
-       DESTDIR=$(EBUILDIR) \
-               $(MAKE) -C $(LIB_ROOT)/$(LIBMFX) install
-       @touch $@
+EXTLIBS  += omx
+ENCODERS += h264_omx
 
-else
+endif
 
-$(LIB_ROOT)/$(LIBMFX)/.tvh_download:
-       @mkdir -p $(LIB_ROOT)/$(LIBMFX)
-       @touch $@
+ifeq (yes,$(CONFIG_OMX_RPI))
 
-$(LIB_ROOT)/$(LIBMFX)/.tvh_build: \
-       $(LIB_ROOT)/$(LIBMFX)/.tvh_download
-       @touch $@
+EXTLIBS  += omx_rpi
 
 endif
 
@@ -595,7 +613,7 @@ $(LIB_ROOT)/$(FFMPEG)/.tvh_build: \
                $(LIB_ROOT)/$(LIBTHEORA)/.tvh_build \
                $(LIB_ROOT)/$(LIBVORBIS)/.tvh_build \
                $(LIB_ROOT)/$(LIBFDKAAC)/.tvh_build \
-               $(LIB_ROOT)/$(LIBMFX)/.tvh_build \
+               $(LIB_ROOT)/$(LIBOPUS)/.tvh_build \
                $(LIB_ROOT)/$(FFMPEG)/.tvh_download
        cd $(LIB_ROOT)/$(FFMPEG) && $(CONFIGURE) \
                --disable-all \
@@ -611,7 +629,8 @@ $(LIB_ROOT)/$(FFMPEG)/.tvh_build: \
                $(foreach demuxer,$(DEMUXERS),--enable-demuxer=$(demuxer)) \
                $(foreach muxer,$(MUXERS),--enable-muxer=$(muxer)) \
                $(foreach bsf,$(BSFS),--enable-bsf=$(bsf)) \
-               $(foreach filter,$(FILTERS),--enable-filter=$(filter))
+               $(foreach filter,$(FILTERS),--enable-filter=$(filter)) \
+               $(foreach hwaccel,$(HWACCELS),--enable-hwaccel=$(hwaccel))
        DESTDIR=$(EBUILDIR) \
                $(MAKE) -C $(LIB_ROOT)/$(FFMPEG) install
        @touch $@
index eb7fbbbc897b7d2271fd65074a542e6798471c1a..ff8a1745e9405eb1e14c8c9fd5704079459c0d14 100644 (file)
@@ -131,6 +131,10 @@ endif
 
 JAVASCRIPT += $(ROOTPATH)/app/tvadapters.js
 JAVASCRIPT += $(ROOTPATH)/app/idnode.js
+JAVASCRIPT += $(ROOTPATH)/app/profile.js
+ifeq ($(CONFIG_LIBAV), yes)
+JAVASCRIPT += $(ROOTPATH)/app/codec.js
+endif
 JAVASCRIPT += $(ROOTPATH)/app/esfilter.js
 ifeq ($(CONFIG_MPEGTS), yes)
 JAVASCRIPT += $(ROOTPATH)/app/mpegts.js
@@ -278,7 +282,7 @@ compile-std: $(WEBDIR)/$(ROOTPATH)/tvh.js.gz $(WEBDIR)/$(ROOTPATH)/tvh.blue.css.
              $(WEBDIR)/$(ROOTPATH)/tvh-tv.js.gz $(WEBDIR)/$(ROOTPATH)/tvh-tv.css.gz \
              $(WEBDIR)/extjs-std.c $(WEBDIR)/extjs-tv-std.c $(JSI-FILES)
        @echo "WEBUI std finished"
-       
+
 .PHONY: compile-debug
 compile-debug: $(WEBDIR)/extjs-debug.c $(WEBDIR)/extjs-tv-debug.c
        @echo "WEBUI debug finished"
index 926907bc78e64f25fb060ec384221527fec3dd77..a61ff4eb2a8941d657c110f7bcc414000ed150d1 100755 (executable)
--- a/configure
+++ b/configure
@@ -48,11 +48,13 @@ OPTIONS=(
   "libtheora_static:yes"
   "libvorbis:yes"
   "libvorbis_static:yes"
-  "libfdkaac:yes"
+  "libfdkaac:no"
   "libfdkaac_static:yes"
-  "nvenc:auto"
-  "qsv:no"
-  "libmfx_static:yes"
+  "libopus:yes"
+  "libopus_static:yes"
+  "nvenc:no"
+  "vaapi:no"
+  "omx:no"
   "inotify:auto"
   "epoll:auto"
   "pcre:auto"
@@ -522,28 +524,19 @@ if enabled ffmpeg_static; then
     disable libfdkaac_static
   fi
 
-  # nvenc
-  if enabled_or_auto nvenc; then
-    if check_cc_header nvEncodeAPI; then
-      enable nvenc
-    elif enabled nvenc; then
-      die "NVENC library (https://developer.nvidia.com/nvidia-video-codec-sdk) not found"
+  # libopus
+  if enabled libopus; then
+    if disabled libopus_static; then
+      check_pkg opus ">=1.1" || die "opus package not found"
     fi
+  else
+    disable libopus_static
   fi
 
-  # qsv
-  if enabled qsv; then
-    if disabled libmfx_static; then
-      check_pkg libmfx ">=1.16" || die "libmfx package not found"
-    else
-      check_bin autoreconf       || die "autoreconf not found"
-      check_bin libtool          || die "libtool not found"
-      check_bin libtoolize       || die "libtoolize not found"
-      check_cc_lib stdc++ stdcpp || die "libstdc++ not found"
-    fi
-    enable vaapi
-  else
-    disable libmfx_static
+  # nvenc
+  if enabled nvenc; then
+    check_cc_header nvEncodeAPI || \
+      die "NVENC library (https://developer.nvidia.com/nvidia-video-codec-sdk) not found"
   fi
 
   # vaapi
@@ -552,6 +545,13 @@ if enabled ffmpeg_static; then
       die "vaapi (Video Acceleration (VA) API for Linux) not found"
     check_pkg libva-x11 ">=0.38.0" || die "libva-x11 not found"
     check_pkg libva-drm ">=0.38.0" || die "libva-drm not found"
+    enable hwaccels
+  fi
+
+  # omx
+  if enabled omx; then
+    check_cc_header OMX_Core || die "OpenMAX IL not found"
+    check_cc_header "/opt/vc/include/bcm_host.h" omx_rpi
   fi
 
 else
@@ -559,13 +559,13 @@ else
   if enabled_or_auto libav; then
     has_libav=true
 
-    check_pkg libavfilter   ">=6.31.100"  || has_libav=false
-    check_pkg libswresample ">=2.0.101"   || has_libav=false
+    check_pkg libavfilter   ">=6.47.100"  || has_libav=false
+    check_pkg libswresample ">=2.1.100"   || has_libav=false
     check_pkg libavresample ">=3.0.0"     || has_libav=false
-    check_pkg libswscale    ">=4.0.100"   || has_libav=false
-    check_pkg libavformat   ">=57.25.100" || has_libav=false
-    check_pkg libavcodec    ">=57.24.102" || has_libav=false
-    check_pkg libavutil     ">=55.17.103" || has_libav=false
+    check_pkg libswscale    ">=4.1.100"   || has_libav=false
+    check_pkg libavformat   ">=57.41.100" || has_libav=false
+    check_pkg libavcodec    ">=57.48.101" || has_libav=false
+    check_pkg libavutil     ">=55.28.100" || has_libav=false
 
     if $has_libav; then
       enable libav
index 50d857d1b4fdfaa0e6953b6f255924c83cae1ea2..66d5d8b988a8f1824f3f73a9f5c1dc59f2c611e4 100644 (file)
--- a/src/api.c
+++ b/src/api.c
@@ -136,6 +136,7 @@ void api_init ( void )
   api_access_init();
   api_dvr_init();
   api_caclient_init();
+  api_codec_init();
   api_profile_init();
   api_language_init();
   api_satip_server_init();
index 6636540cd7a463ec94a00edcb37391d0539fe5c3..80f2b5d286314edd6176efa859a2584653022d74 100644 (file)
--- a/src/api.h
+++ b/src/api.h
@@ -76,6 +76,7 @@ void api_intlconv_init      ( void );
 void api_access_init        ( void );
 void api_dvr_init           ( void );
 void api_caclient_init      ( void );
+void api_codec_init         ( void );
 void api_profile_init       ( void );
 void api_language_init      ( void );
 void api_satip_server_init  ( void );
diff --git a/src/api/api_codec.c b/src/api/api_codec.c
new file mode 100644 (file)
index 0000000..d19fca4
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ *  tvheadend - API access to Codec Profiles
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "tvheadend.h"
+#include "access.h"
+#include "api.h"
+
+#include "transcoding/codec.h"
+
+
+static int
+api_codec_list(access_t *perm, void *opaque, const char *op,
+               htsmsg_t *args, htsmsg_t **resp)
+{
+    htsmsg_t *list = NULL, *map = NULL;
+    TVHCodec *tvh_codec = NULL;
+    int err = ENOMEM;
+
+    if ((list = htsmsg_create_list())) {
+        pthread_mutex_lock(&global_lock);
+        SLIST_FOREACH(tvh_codec, &tvh_codecs, link) {
+            if (!(map = idclass_serialize(tvh_codec_get_class(tvh_codec),
+                                          perm->aa_lang_ui))) {
+                htsmsg_destroy(list);
+                list = NULL;
+                break;
+            }
+            htsmsg_add_str(map, "name", tvh_codec_get_name(tvh_codec));
+            htsmsg_add_str(map, "title", tvh_codec_get_title(tvh_codec));
+            htsmsg_add_msg(list, NULL, map);
+        }
+        pthread_mutex_unlock(&global_lock);
+        if (list) {
+            if ((*resp = htsmsg_create_map())) {
+                htsmsg_add_msg(*resp, "entries", list);
+                err = 0;
+            }
+            else {
+                htsmsg_destroy(list);
+                list = NULL;
+            }
+        }
+    }
+    return err;
+}
+
+
+static int
+api_codec_profile_list(access_t *perm, void *opaque, const char *op,
+                       htsmsg_t *args, htsmsg_t **resp)
+{
+    htsmsg_t *list = NULL, *map = NULL;
+    TVHCodecProfile *tvh_profile = NULL;
+    char buf[UUID_HEX_SIZE];
+    int err = ENOMEM;
+
+    if ((list = htsmsg_create_list())) {
+        pthread_mutex_lock(&global_lock);
+        LIST_FOREACH(tvh_profile, &tvh_codec_profiles, link) {
+            if (!(map = htsmsg_create_map())) {
+                htsmsg_destroy(list);
+                list = NULL;
+                break;
+            }
+            htsmsg_add_str(map, "uuid",
+                           idnode_uuid_as_str((idnode_t *)tvh_profile, buf));
+            htsmsg_add_str(map, "title",
+                           tvh_codec_profile_get_title(tvh_profile));
+            htsmsg_add_str(map, "status",
+                           tvh_codec_profile_get_status(tvh_profile));
+            htsmsg_add_msg(list, NULL, map);
+        }
+        pthread_mutex_unlock(&global_lock);
+        if (list) {
+            if ((*resp = htsmsg_create_map())) {
+                htsmsg_add_msg(*resp, "entries", list);
+                err = 0;
+            }
+            else {
+                htsmsg_destroy(list);
+                list = NULL;
+            }
+        }
+    }
+    return err;
+}
+
+
+static int
+api_codec_profile_create(access_t *perm, void *opaque, const char *op,
+                         htsmsg_t *args, htsmsg_t **resp)
+{
+    const char *codec_name = NULL;
+    htsmsg_t *conf = NULL;
+    int err = EINVAL;
+
+    if ((codec_name = htsmsg_get_str(args, "class")) &&
+        (conf = htsmsg_get_map(args, "conf"))) {
+        htsmsg_set_str(conf, "codec_name", codec_name);
+        pthread_mutex_lock(&global_lock);
+        err = (err = tvh_codec_profile_create(conf, NULL, 1)) ? -err : 0;
+        pthread_mutex_unlock(&global_lock);
+    }
+    return err;
+}
+
+
+void
+api_codec_init(void)
+{
+    static api_hook_t ah[] = {
+        {"codec/list", ACCESS_ADMIN, api_codec_list, NULL},
+        {"codec_profile/list", ACCESS_ANONYMOUS, api_codec_profile_list, NULL},
+        {"codec_profile/create", ACCESS_ADMIN, api_codec_profile_create, NULL},
+        {"codec_profile/class", ACCESS_ADMIN, api_idnode_class,
+         (void*)&codec_profile_class},
+        {NULL},
+    };
+
+    api_register_all(ah);
+}
index b0af7be07b6a4fbce57002a67d61dcdd0e3da1c6..e3667a9507fac546b664fe5267f1800355a586a5 100644 (file)
@@ -992,7 +992,7 @@ dvr_autorec_entry_class_btype_list ( void *o, const char *lang )
 }
 
 static uint32_t
-dvr_autorec_entry_class_owner_opts(void *o)
+dvr_autorec_entry_class_owner_opts(void *o, uint32_t opts)
 {
   dvr_autorec_entry_t *dae = (dvr_autorec_entry_t *)o;
   if (dae && dae->dae_id.in_access &&
index c7b8d94a4b9786a5d8eb0e44c18fbd60f7a8b7d4..1b5c98b3cbb523dd843fc55fc966004cc0fe0870 100644 (file)
@@ -609,7 +609,7 @@ dvr_config_class_enabled_set(void *o, const void *v)
 }
 
 static uint32_t
-dvr_config_class_enabled_opts(void *o)
+dvr_config_class_enabled_opts(void *o, uint32_t opts)
 {
   dvr_config_t *cfg = (dvr_config_t *)o;
   if (cfg && dvr_config_is_default(cfg) && dvr_config_is_valid(cfg))
@@ -764,7 +764,7 @@ dvr_config_class_retention_list ( void *o, const char *lang )
 static htsmsg_t *
 dvr_config_class_extra_list(void *o, const char *lang)
 {
-  return dvr_entry_class_duration_list(o, 
+  return dvr_entry_class_duration_list(o,
            tvh_gettext_lang(lang, N_("Not set (none or channel configuration)")),
            4*60, 1, lang);
 }
index 828ca1a4a4198390e715667afd1748dbab7edfd7..38984e1e80108346430ed0547ab0cd7549debd0e 100644 (file)
@@ -614,7 +614,7 @@ dvr_entry_status(dvr_entry_t *de)
   switch(de->de_sched_state) {
   case DVR_SCHEDULED:
     return N_("Scheduled for recording");
-    
+
   case DVR_RECORDING:
 
     switch(de->de_rec_state) {
@@ -842,7 +842,7 @@ dvr_entry_fuzzy_match(dvr_entry_t *de, epg_broadcast_t *e, uint16_t eid, int64_t
   /* Outside of window */
   if ((int64_t)llabs(e->start - de->de_start) > time_window)
     return 0;
-  
+
   /* Title match (or contains?) */
   if (strcasecmp(title1, title2))
     return 0;
@@ -1387,7 +1387,7 @@ static dvr_entry_t *_dvr_duplicate_event(dvr_entry_t *de)
       // if titles are not defined or do not match, don't dedup
       if (lang_str_compare(de->de_title, de2->de_title))
         continue;
-      
+
       if (match(de, de2, &aux)) {
         free(aux);
         return de2;
@@ -1419,7 +1419,7 @@ static dvr_entry_t *_dvr_duplicate_event(dvr_entry_t *de)
       if (record != DVR_AUTOREC_LRECORD_DIFFERENT_TITLE &&
           lang_str_compare(de->de_title, de2->de_title))
         continue;
-      
+
       if (match(de, de2, &aux)) {
         free(aux);
         return de2;
@@ -1530,7 +1530,7 @@ dvr_entry_destroy(dvr_entry_t *de, int delconf)
     hts_settings_remove("dvr/log/%s", idnode_uuid_as_str(&de->de_id, ubuf));
 
   htsp_dvr_entry_delete(de);
-  
+
 #if ENABLE_INOTIFY
   dvr_inotify_del(de);
 #endif
@@ -1794,7 +1794,7 @@ static dvr_entry_t *_dvr_entry_update
     dvr_entry_set_timer(de);
   }
 
-  /* Title */ 
+  /* Title */
   if (e && e->episode && e->episode->title) {
     save |= lang_str_set2(&de->de_title, e->episode->title) ? DVR_UPDATED_TITLE : 0;
   } else if (title) {
@@ -1874,7 +1874,7 @@ dosave:
 /**
  *
  */
-dvr_entry_t * 
+dvr_entry_t *
 dvr_entry_update
   ( dvr_entry_t *de, int enabled,
     const char *dvr_config_uuid, channel_t *ch,
@@ -1893,7 +1893,7 @@ dvr_entry_update
 /**
  * Used to notify the DVR that an event has been replaced in the EPG
  */
-void 
+void
 dvr_event_replaced(epg_broadcast_t *e, epg_broadcast_t *new_e)
 {
   dvr_entry_t *de, *de_next;
@@ -2216,7 +2216,7 @@ dvr_entry_find_by_id(int id)
   LIST_FOREACH(de, &dvrentries, de_global_link)
     if(idnode_get_short_uuid(&de->de_id) == id)
       break;
-  return de;  
+  return de;
 }
 
 
@@ -2357,7 +2357,7 @@ dvr_entry_class_start_set(void *o, const void *v)
 }
 
 static uint32_t
-dvr_entry_class_start_opts(void *o)
+dvr_entry_class_start_opts(void *o, uint32_t opts)
 {
   dvr_entry_t *de = (dvr_entry_t *)o;
   if (de && !dvr_entry_is_editable(de))
@@ -2366,7 +2366,7 @@ dvr_entry_class_start_opts(void *o)
 }
 
 static uint32_t
-dvr_entry_class_config_name_opts(void *o)
+dvr_entry_class_config_name_opts(void *o, uint32_t opts)
 {
   dvr_entry_t *de = (dvr_entry_t *)o;
   if (de && !dvr_entry_is_editable(de))
@@ -2375,7 +2375,7 @@ dvr_entry_class_config_name_opts(void *o)
 }
 
 static uint32_t
-dvr_entry_class_owner_opts(void *o)
+dvr_entry_class_owner_opts(void *o, uint32_t opts)
 {
   dvr_entry_t *de = (dvr_entry_t *)o;
   if (de && de->de_id.in_access &&
@@ -2385,7 +2385,7 @@ dvr_entry_class_owner_opts(void *o)
 }
 
 static uint32_t
-dvr_entry_class_start_extra_opts(void *o)
+dvr_entry_class_start_extra_opts(void *o, uint32_t opts)
 {
   dvr_entry_t *de = (dvr_entry_t *)o;
   if (de && !dvr_entry_is_editable(de))
@@ -3042,7 +3042,7 @@ dvr_entry_class_extra_list(void *o, const char *lang)
   const char *msg = N_("Not set (use channel or DVR configuration)");
   return dvr_entry_class_duration_list(o, tvh_gettext_lang(lang, msg), 4*60, 1, lang);
 }
-                                        
+
 static htsmsg_t *
 dvr_entry_class_content_type_list(void *o, const char *lang)
 {
index 9ebd3d7d6bb15d5b746ec29d1f1ec48de7cffc3b..bc2e66d3d3d08e8ab2f35dd7212660af666514fb 100644 (file)
@@ -242,7 +242,7 @@ cleanup_filename(dvr_config_t *cfg, char *s, int dosubs)
     else if (cfg->dvr_whitespace_in_title &&
              (*s == ' ' || *s == '\t') &&
              dosubs)
-      *s = '-';        
+      *s = '-';
 
     else if (cfg->dvr_clean_title &&
              ((*s < 32) || (*s > 122) ||
@@ -990,7 +990,7 @@ dvr_rec_start(dvr_entry_t *de, const streaming_start_t *ss)
         "adapter: \"%s\", "
         "network: \"%s\", mux: \"%s\", provider: \"%s\", "
         "service: \"%s\"",
-               
+
         dvr_get_filename(de) ?: lang_str_get(de->de_title, NULL),
         si->si_adapter  ?: "<N/A>",
         si->si_network  ?: "<N/A>",
@@ -1024,7 +1024,7 @@ dvr_rec_start(dvr_entry_t *de, const streaming_start_t *ss)
       htsmsg_add_u32(e, "audio_type", ssc->ssc_audio_type);
       if(ssc->ssc_audio_version)
         htsmsg_add_u32(e, "audio_version", ssc->ssc_audio_version);
-      if(ssc->ssc_sri)
+      if(ssc->ssc_sri < 16)
        snprintf(sr, sizeof(sr), "%d", sri_to_rate(ssc->ssc_sri));
       else
        strcpy(sr, "?");
@@ -1493,7 +1493,7 @@ fin:
 
     case SMT_SERVICE_STATUS:
       if (sm->sm_code & TSS_PACKETS) {
-       
+
       } else if (sm->sm_code & TSS_ERRORS) {
 
        int code = SM_CODE_UNDEFINED_ERROR;
@@ -1592,7 +1592,7 @@ dvr_spawn_cmd(dvr_entry_t *de, const char *cmd, const char *filename, int pre)
   args = htsstr_argsplit(buf1);
   if(args[0])
     spawnv(args[0], (void *)args, NULL, 1, 1);
-    
+
   htsstr_argsplit_free(args);
 }
 
index af93fb51fc0cf13c51523889c67a81e6e81d86f3..70d23969816402bffecfb9e21c18036d5fb8d061 100644 (file)
@@ -509,7 +509,7 @@ dvr_timerec_entry_class_weekdays_rend(void *o, const char *lang)
 }
 
 static uint32_t
-dvr_timerec_entry_class_owner_opts(void *o)
+dvr_timerec_entry_class_owner_opts(void *o, uint32_t opts)
 {
   dvr_timerec_entry_t *dte = (dvr_timerec_entry_t *)o;
   if (dte && dte->dte_id.in_access &&
index 0d516fad33b18f96fa0803e32941c097ffef31e5..7c7cf22f2303a5e6a40035e6a1d2524725f195f2 100644 (file)
@@ -46,11 +46,12 @@ extern const idclass_t esfilter_class_other;
 
 #define ESF_MASK_VIDEO \
   (SCT_MASK(SCT_MPEG2VIDEO) | SCT_MASK(SCT_H264) | SCT_MASK(SCT_VP8) | \
-   SCT_MASK(SCT_HEVC) | SCT_MASK(SCT_VP9))
+   SCT_MASK(SCT_HEVC) | SCT_MASK(SCT_VP9) | SCT_MASK(SCT_THEORA))
 
 #define ESF_MASK_AUDIO \
   (SCT_MASK(SCT_MPEG2AUDIO) | SCT_MASK(SCT_AC3) | SCT_MASK(SCT_AAC) | \
-   SCT_MASK(SCT_EAC3) | SCT_MASK(SCT_MP4A) | SCT_MASK(SCT_VORBIS))
+   SCT_MASK(SCT_EAC3) | SCT_MASK(SCT_MP4A) | SCT_MASK(SCT_VORBIS) | \
+   SCT_MASK(SCT_OPUS))
 
 #define ESF_MASK_TELETEXT \
   SCT_MASK(SCT_TELETEXT)
index 042b16f4949fe7587d9b0b6c91233c379feb2d34..961660b7def466ecb74f22270115cf5be1301f46 100644 (file)
@@ -1,4 +1,4 @@
-#include "plumbing/transcoding.h"
+#include "transcoding/transcode.h"
 #include "libav.h"
 
 /**
@@ -11,8 +11,8 @@ libav_log_callback(void *ptr, int level, const char *fmt, va_list vl)
     char *nl;
     char *l;
 
-    if ((level == AV_LOG_DEBUG) && !(tvhlog_options & TVHLOG_OPT_LIBAV))
-      return;
+    //if ((level == AV_LOG_DEBUG) && !(tvhlog_options & TVHLOG_OPT_LIBAV))
+    //  return;
 
     memset(message, 0, sizeof(message));
     vsnprintf(message, sizeof(message), fmt, vl);
@@ -84,6 +84,9 @@ streaming_component_type2codec_id(streaming_component_type_t type)
   case SCT_HEVC:
     codec_id = AV_CODEC_ID_HEVC;
     break;
+  case SCT_THEORA:
+    codec_id = AV_CODEC_ID_THEORA;
+    break;
   case SCT_AC3:
     codec_id = AV_CODEC_ID_AC3;
     break;
@@ -100,6 +103,9 @@ streaming_component_type2codec_id(streaming_component_type_t type)
   case SCT_VORBIS:
     codec_id = AV_CODEC_ID_VORBIS;
     break;
+  case SCT_OPUS:
+    codec_id = AV_CODEC_ID_OPUS;
+    break;
   case SCT_DVBSUB:
     codec_id = AV_CODEC_ID_DVB_SUBTITLE;
     break;
@@ -124,7 +130,7 @@ streaming_component_type2codec_id(streaming_component_type_t type)
 streaming_component_type_t
 codec_id2streaming_component_type(enum AVCodecID id)
 {
-  streaming_component_type_t type = SCT_NONE;
+  streaming_component_type_t type = SCT_UNKNOWN;
 
   switch(id) {
   case AV_CODEC_ID_H264:
@@ -142,6 +148,9 @@ codec_id2streaming_component_type(enum AVCodecID id)
   case AV_CODEC_ID_HEVC:
     type = SCT_HEVC;
     break;
+  case AV_CODEC_ID_THEORA:
+    type = SCT_THEORA;
+    break;
   case AV_CODEC_ID_AC3:
     type = SCT_AC3;
     break;
@@ -157,6 +166,9 @@ codec_id2streaming_component_type(enum AVCodecID id)
   case AV_CODEC_ID_VORBIS:
     type = SCT_VORBIS;
     break;
+  case AV_CODEC_ID_OPUS:
+    type = SCT_OPUS;
+    break;
   case AV_CODEC_ID_DVB_SUBTITLE:
     type = SCT_DVBSUB;
     break;
@@ -179,8 +191,8 @@ codec_id2streaming_component_type(enum AVCodecID id)
 
 
 /**
- * 
- */ 
+ *
+ */
 int
 libav_is_encoder(AVCodec *codec)
 {
@@ -192,8 +204,8 @@ libav_is_encoder(AVCodec *codec)
 }
 
 /**
- * 
- */ 
+ *
+ */
 void
 libav_set_loglevel(void)
 {
@@ -216,11 +228,15 @@ libav_init(void)
   av_register_all();
   avformat_network_init();
   avfilter_register_all();
-  transcoding_init();
+  transcode_init();
 }
 
+/**
+ *
+ */
 void
 libav_done(void)
 {
+  transcode_done();
   avformat_network_deinit();
 }
\ No newline at end of file
index 2456d14c205e3b676b9e67ddcb641d7ee654e4dc..93039634b5e9bebf3297df8efc1a62dee0f39131 100644 (file)
@@ -68,6 +68,7 @@
 #include "intlconv.h"
 #include "dbus.h"
 #include "libav.h"
+#include "transcoding/codec.h"
 #include "profile.h"
 #include "bouquet.h"
 #include "tvhtime.h"
@@ -98,7 +99,7 @@ typedef struct {
   enum {
     OPT_STR,
     OPT_INT,
-    OPT_BOOL, 
+    OPT_BOOL,
     OPT_STR_LIST,
   }          type;
   void       *param;
@@ -526,7 +527,7 @@ show_usage
       free(desc);
     }
   }
-  printf("%s", 
+  printf("%s",
          _("\n"
            "For more information please visit the Tvheadend website:\n"
            "https://tvheadend.org\n"));
@@ -615,7 +616,7 @@ mtimer_thread(void *aux)
     next = now + sec2mono(3600);
 
     while((mti = LIST_FIRST(&mtimers)) != NULL) {
-      
+
       if (mti->mti_expire > now) {
         next = mti->mti_expire;
         break;
@@ -648,7 +649,7 @@ mtimer_thread(void *aux)
     tvh_cond_timedwait(&mtimer_cond, &global_lock, next);
     pthread_mutex_unlock(&global_lock);
   }
-  
+
   return NULL;
 }
 
@@ -678,7 +679,7 @@ mainloop(void)
 
     // TODO: there is a risk that if timers re-insert themselves to
     //       the top of the list with a 0 offset we could loop indefinitely
-    
+
 #if 0
     tvhdebug(LS_GTIMER, "now %"PRItime_t, ts.tv_sec);
     LIST_FOREACH(gti, &gtimers, gti_link)
@@ -686,7 +687,7 @@ mainloop(void)
 #endif
 
     while((gti = LIST_FIRST(&gtimers)) != NULL) {
-      
+
       if (gti->gti_expire > now) {
         ts.tv_sec = gti->gti_expire;
         break;
@@ -1033,12 +1034,12 @@ main(int argc, char **argv)
   }
   if (opt_log_debug)
     log_debug  = opt_log_debug;
-    
+
   tvhlog_init(log_level, log_options, opt_logpath);
   tvhlog_set_debug(log_debug);
   tvhlog_set_trace(log_trace);
   tvhinfo(LS_MAIN, "Log started");
+
   signal(SIGPIPE, handle_sigpipe); // will be redundant later
   signal(SIGILL, handle_sigill);   // see handler..
 
@@ -1144,7 +1145,7 @@ main(int argc, char **argv)
     tvhlog_options &= ~TVHLOG_OPT_STDERR;
   if (!isatty(2))
     tvhlog_options &= ~TVHLOG_OPT_DECORATE;
-  
+
   /* Initialise clock */
   pthread_mutex_lock(&global_lock);
   __mdispatch_clock = getmonoclock();
@@ -1199,6 +1200,7 @@ main(int argc, char **argv)
   tvhftrace(LS_MAIN, fsmonitor_init);
   tvhftrace(LS_MAIN, libav_init);
   tvhftrace(LS_MAIN, tvhtime_init);
+  tvhftrace(LS_MAIN, codec_init);
   tvhftrace(LS_MAIN, profile_init);
   tvhftrace(LS_MAIN, imagecache_init);
   tvhftrace(LS_MAIN, http_client_init, opt_user_agent);
@@ -1330,6 +1332,8 @@ main(int argc, char **argv)
   tvhftrace(LS_MAIN, lang_str_done);
   tvhftrace(LS_MAIN, esfilter_done);
   tvhftrace(LS_MAIN, profile_done);
+  tvhftrace(LS_MAIN, codec_done);
+  tvhftrace(LS_MAIN, libav_done);
   tvhftrace(LS_MAIN, intlconv_done);
   tvhftrace(LS_MAIN, urlparse_done);
   tvhftrace(LS_MAIN, streaming_done);
@@ -1346,8 +1350,6 @@ main(int argc, char **argv)
   if(opt_fork)
     unlink(opt_pidpath);
 
-  libav_done();
-    
   /* OpenSSL - welcome to the "cleanup" hell */
   ENGINE_cleanup();
   RAND_cleanup();
index da6b6cff655dc7374a7282e4c3cb14dfe2f1992d..683ddfae6b9960ba9c9917c4f76383690273a0d4 100644 (file)
@@ -200,7 +200,9 @@ lav_muxer_support_stream(muxer_container_type_t mc,
   case MC_WEBM:
   case MC_AVWEBM:
     ret |= type == SCT_VP8;
+    ret |= type == SCT_VP9;
     ret |= type == SCT_VORBIS;
+    ret |= type == SCT_OPUS;
     break;
 
   case MC_MPEGTS:
index 755dbad1d86ebeafc03db4013b20e66f9fb0583e..aa58a224e046dd0a9eae46f6c012935b9bc13448 100644 (file)
@@ -42,6 +42,7 @@
 #include "parsers/parser_hevc.h"
 #include "muxer_mkv.h"
 
+
 extern int dvr_iov_max;
 
 TAILQ_HEAD(mk_cue_queue, mk_cue);
@@ -170,35 +171,48 @@ mk_build_ebmlheader(mk_muxer_t *mk)
 
 
 /**
- *
+ * lifted from avpriv_split_xiph_headers() in libavcodec/xiph.c
  */
 static int
-mk_split_vorbis_headers(uint8_t *extradata, int extradata_size,
-                       uint8_t *header_start[3],  int header_len[3])
+mk_split_xiph_headers(uint8_t *extradata, int extradata_size,
+                      int first_header_size, uint8_t *header_start[3],
+                      int header_len[3])
 {
-  int i;
-  if (extradata_size >= 3 && extradata_size < INT_MAX - 0x1ff && extradata[0] == 2) {
-    int overall_len = 3;
-    extradata++;
-    for (i=0; i<2; i++, extradata++) {
-      header_len[i] = 0;
-      for (; overall_len < extradata_size && *extradata==0xff; extradata++) {
-       header_len[i] += 0xff;
-       overall_len   += 0xff + 1;
-      }
-      header_len[i] += *extradata;
-      overall_len   += *extradata;
-      if (overall_len > extradata_size)
-       return -1;
+    int i;
+
+    if (extradata_size >= 6 && RB16(extradata) == first_header_size) {
+        int overall_len = 6;
+        for (i=0; i<3; i++) {
+            header_len[i] = RB16(extradata);
+            extradata += 2;
+            header_start[i] = extradata;
+            extradata += header_len[i];
+            if (overall_len > extradata_size - header_len[i])
+                return -1;
+            overall_len += header_len[i];
+        }
+    } else if (extradata_size >= 3 && extradata_size < INT_MAX - 0x1ff && extradata[0] == 2) {
+        int overall_len = 3;
+        extradata++;
+        for (i=0; i<2; i++, extradata++) {
+            header_len[i] = 0;
+            for (; overall_len < extradata_size && *extradata==0xff; extradata++) {
+                header_len[i] += 0xff;
+                overall_len   += 0xff + 1;
+            }
+            header_len[i] += *extradata;
+            overall_len   += *extradata;
+            if (overall_len > extradata_size)
+                return -1;
+        }
+        header_len[2] = extradata_size - overall_len;
+        header_start[0] = extradata;
+        header_start[1] = header_start[0] + header_len[0];
+        header_start[2] = header_start[1] + header_len[1];
+    } else {
+        return -1;
     }
-    header_len[2] = extradata_size - overall_len;
-    header_start[0] = extradata;
-    header_start[1] = header_start[0] + header_len[0];
-    header_start[2] = header_start[1] + header_len[1];
-  } else {
-    return -1;
-  }
-  return 0;
+    return 0;
 }
 
 
@@ -303,6 +317,12 @@ mk_build_tracks(mk_muxer_t *mk, streaming_start_t *ss)
       mk->cluster_maxsize = 10000000;
       break;
 
+    case SCT_THEORA:
+      tracktype = 1;
+      codec_id = "V_THEORA";
+      mk->cluster_maxsize = 5242880;
+      break;
+
     case SCT_MPEG2AUDIO:
       tracktype = 2;
       codec_id  = "A_MPEG/L2";
@@ -334,6 +354,11 @@ mk_build_tracks(mk_muxer_t *mk, streaming_start_t *ss)
       codec_id = "A_VORBIS";
       break;
 
+    case SCT_OPUS:
+      tracktype = 2;
+      codec_id = "A_OPUS";
+      break;
+
     case SCT_DVBSUB:
       if (mk->dvbsub_skip)
         goto disable;
@@ -374,6 +399,7 @@ disable:
     case SCT_MPEG2VIDEO:
     case SCT_MP4A:
     case SCT_AAC:
+    case SCT_OPUS:
       if(ssc->ssc_gh) {
         sbuf_t hdr;
         sbuf_init(&hdr);
@@ -392,29 +418,27 @@ disable:
       }
       break;
 
+    case SCT_THEORA:
     case SCT_VORBIS:
       if(ssc->ssc_gh) {
-       htsbuf_queue_t *cp;
-       uint8_t *header_start[3];
-       int header_len[3];
-       int j;
-       if(mk_split_vorbis_headers(pktbuf_ptr(ssc->ssc_gh),
-                                  pktbuf_len(ssc->ssc_gh),
-                                  header_start,
-                                  header_len) < 0)
-         break;
-
-       cp = htsbuf_queue_alloc(0);
-
-       ebml_append_xiph_size(cp, 2);
-
-       for (j = 0; j < 2; j++)
-         ebml_append_xiph_size(cp, header_len[j]);
-
-       for (j = 0; j < 3; j++)
-         htsbuf_append(cp, header_start[j], header_len[j]);
-
-       ebml_append_master(t, 0x63a2, cp);
+        htsbuf_queue_t *cp;
+        uint8_t *header_start[3];
+        int header_len[3];
+        int j;
+        int first_header_size = ssc->ssc_type == SCT_VORBIS ? 30 : 42;
+
+        if(mk_split_xiph_headers(pktbuf_ptr(ssc->ssc_gh), pktbuf_len(ssc->ssc_gh),
+                                 first_header_size, header_start, header_len)) {
+          tvherror(LS_MKV, "failed to split xiph headers");
+          break;
+        }
+        cp = htsbuf_queue_alloc(0);
+        ebml_append_xiph_size(cp, 2);
+        for (j = 0; j < 2; j++)
+          ebml_append_xiph_size(cp, header_len[j]);
+        for (j = 0; j < 3; j++)
+          htsbuf_append(cp, header_start[j], header_len[j]);
+        ebml_append_master(t, 0x63a2, cp);
       }
       break;
 
@@ -437,15 +461,16 @@ disable:
       ebml_append_uint(vi, 0xb0, ssc->ssc_width);
       ebml_append_uint(vi, 0xba, ssc->ssc_height);
 
-      if(mk->webm && ssc->ssc_aspect_num && ssc->ssc_aspect_den) {
-       // DAR is not supported by webm
-       ebml_append_uint(vi, 0x54b2, 1);
-       ebml_append_uint(vi, 0x54b0, (ssc->ssc_height * ssc->ssc_aspect_num) / ssc->ssc_aspect_den);
-       ebml_append_uint(vi, 0x54ba, ssc->ssc_height);
-      } else if(ssc->ssc_aspect_num && ssc->ssc_aspect_den) {
-       ebml_append_uint(vi, 0x54b2, 3); // Display width/height is in DAR
-       ebml_append_uint(vi, 0x54b0, ssc->ssc_aspect_num);
-       ebml_append_uint(vi, 0x54ba, ssc->ssc_aspect_den);
+      if (ssc->ssc_aspect_num && ssc->ssc_aspect_den) {
+        if (mk->webm) {
+          ebml_append_uint(vi, 0x54b0, (ssc->ssc_height * ssc->ssc_aspect_num) / ssc->ssc_aspect_den);
+          ebml_append_uint(vi, 0x54ba, ssc->ssc_height);
+          ebml_append_uint(vi, 0x54b2, 0); // DisplayUnit: pixels because DAR is not supported by webm
+        } else {
+          ebml_append_uint(vi, 0x54b0, ssc->ssc_aspect_num);
+          ebml_append_uint(vi, 0x54ba, ssc->ssc_aspect_den);
+          ebml_append_uint(vi, 0x54b2, 3); // DisplayUnit: DAR
+        }
       }
 
       ebml_append_master(t, 0xe0, vi);
index 4e4d4b960285eeda2000a675f773dcf876e1e16c..f1bcf16dfa6df331cd5c7fd0354b65f191093664 100644 (file)
@@ -69,4 +69,6 @@ RB24(const uint8_t *d)
   return (d[0] << 16) | (d[1] << 8) | d[2];
 }
 
+#define RB16(x) ((((const uint8_t*)(x))[0] << 8) | ((const uint8_t*)(x))[1])
+
 #endif /* BITSTREAM_H_ */
index 28cf0ff7faa49401297221519f6719541f4a7768..cc7f7319f3f729a8b2ff1f0df1fd9c543b286463 100644 (file)
@@ -48,7 +48,8 @@ gh_require_meta(int type)
          type == SCT_MPEG2VIDEO ||
          type == SCT_MP4A ||
          type == SCT_AAC ||
-         type == SCT_VORBIS;
+         type == SCT_VORBIS ||
+         type == SCT_THEORA;
 }
 
 /**
@@ -84,7 +85,7 @@ apply_header(streaming_start_component_t *ssc, th_pkt_t *pkt)
   if(ssc->ssc_frameduration == 0 && pkt->pkt_duration != 0)
     ssc->ssc_frameduration = pkt->pkt_duration;
 
-  if(SCT_ISAUDIO(ssc->ssc_type) && !ssc->ssc_channels && !ssc->ssc_sri) {
+  if(SCT_ISAUDIO(ssc->ssc_type) && !ssc->ssc_channels) {
     ssc->ssc_channels = pkt->a.pkt_channels;
     ssc->ssc_sri      = pkt->a.pkt_sri;
     ssc->ssc_ext_sri  = pkt->a.pkt_ext_sri;
@@ -140,9 +141,9 @@ header_complete(streaming_start_component_t *ssc, int not_so_picky)
       return 0;
   }
 
-  if(is_audio && (ssc->ssc_sri == 0 || ssc->ssc_channels == 0))
+  if(is_audio && !ssc->ssc_channels)
     return 0;
-  
+
   if(ssc->ssc_gh == NULL && gh_require_meta(ssc->ssc_type))
     return 0;
 
@@ -287,7 +288,7 @@ gh_hold(globalheaders_t *gh, streaming_message_t *sm)
   switch(sm->sm_type) {
   case SMT_PACKET:
     pkt = sm->sm_data;
-    ssc = streaming_start_component_find_by_index(gh->gh_ss, 
+    ssc = streaming_start_component_find_by_index(gh->gh_ss,
                                                  pkt->pkt_componentindex);
     if (ssc == NULL) {
       tvherror(LS_GLOBALHEADERS, "Unable to find component %d", pkt->pkt_componentindex);
@@ -313,10 +314,10 @@ gh_hold(globalheaders_t *gh, streaming_message_t *sm)
       break;
 
     // Send our modified start
-    sm = streaming_msg_create_data(SMT_START, 
+    sm = streaming_msg_create_data(SMT_START,
                                   streaming_start_copy(gh->gh_ss));
     streaming_target_deliver2(gh->gh_output, sm);
-   
+
     // Send all pending packets
     while((pkt = pktref_get_first(&gh->gh_holdq)) != NULL) {
       if (pkt->pkt_payload) {
@@ -371,7 +372,7 @@ gh_pass(globalheaders_t *gh, streaming_message_t *sm)
     /* restart */
     gh_start(gh, sm);
     break;
-    
+
   case SMT_STOP:
     gh->gh_passthru = 0;
     gh_flush(gh);
diff --git a/src/plumbing/transcoding.c b/src/plumbing/transcoding.c
deleted file mode 100644 (file)
index f2fa908..0000000
+++ /dev/null
@@ -1,2210 +0,0 @@
-/**
- *  Transcoding
- *  Copyright (C) 2013 John Törnblom
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 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 General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <unistd.h>
-#include <libavformat/avformat.h>
-#include <libavcodec/avcodec.h>
-#include <libavfilter/avfiltergraph.h>
-#include <libavfilter/buffersink.h>
-#include <libavfilter/buffersrc.h>
-#include <libavutil/opt.h>
-#include <libavresample/avresample.h>
-#include <libavutil/opt.h>
-#include <libavutil/audio_fifo.h>
-#include <libavutil/dict.h>
-
-#if LIBAVUTIL_VERSION_MICRO >= 100 /* FFMPEG */
-#define USING_FFMPEG 1
-#endif
-
-#include "tvheadend.h"
-#include "settings.h"
-#include "streaming.h"
-#include "service.h"
-#include "packet.h"
-#include "transcoding.h"
-#include "libav.h"
-#include "parsers/bitstream.h"
-#include "parsers/parser_avc.h"
-
-LIST_HEAD(transcoder_stream_list, transcoder_stream);
-
-struct transcoder;
-
-typedef struct transcoder_stream {
-  int                           ts_index;
-  streaming_component_type_t    ts_type;
-  streaming_target_t           *ts_target;
-  LIST_ENTRY(transcoder_stream) ts_link;
-  int                           ts_first;
-
-  pktbuf_t                     *ts_input_gh;
-
-  void (*ts_handle_pkt) (struct transcoder *, struct transcoder_stream *, th_pkt_t *);
-  void (*ts_destroy)    (struct transcoder *, struct transcoder_stream *);
-} transcoder_stream_t;
-
-
-typedef struct audio_stream {
-  transcoder_stream_t;
-
-  AVCodecContext *aud_ictx;
-  AVCodec        *aud_icodec;
-
-  AVCodecContext *aud_octx;
-  AVCodec        *aud_ocodec;
-
-  uint64_t        aud_dec_pts;
-  uint64_t        aud_enc_pts;
-
-  int8_t          aud_channels;
-  int32_t         aud_bitrate;
-
-  AVAudioResampleContext *resample_context;
-  AVAudioFifo     *fifo;
-  int             resample;
-  int             resample_is_open;
-
-  enum AVSampleFormat last_sample_fmt;
-  int             last_sample_rate;
-  uint64_t        last_channel_layout;
-
-} audio_stream_t;
-
-
-typedef struct video_stream {
-  transcoder_stream_t;
-
-  AVCodecContext            *vid_ictx;
-  AVCodec                   *vid_icodec;
-
-  AVCodecContext            *vid_octx;
-  AVCodec                   *vid_ocodec;
-
-  AVFrame                   *vid_dec_frame;
-  AVFrame                   *vid_enc_frame;
-
-  AVFilterGraph             *flt_graph;
-  AVFilterContext           *flt_bufsrcctx;
-  AVFilterContext           *flt_bufsinkctx;
-
-  int16_t                    vid_width;
-  int16_t                    vid_height;
-
-  int                        vid_first_sent;
-  int                        vid_first_encoded;
-  th_pkt_t                  *vid_first_pkt;
-} video_stream_t;
-
-
-typedef struct subtitle_stream {
-  transcoder_stream_t;
-
-  AVCodecContext            *sub_ictx;
-  AVCodec                   *sub_icodec;
-
-  AVCodecContext            *sub_octx;
-  AVCodec                   *sub_ocodec;
-} subtitle_stream_t;
-
-
-
-typedef struct transcoder {
-  streaming_target_t  t_input;  // must be first
-  streaming_target_t *t_output;
-
-  uint32_t            t_id;
-
-  transcoder_props_t            t_props;
-  struct transcoder_stream_list t_stream_list;
-} transcoder_t;
-
-
-
-#define WORKING_ENCODER(x) \
-  ((x) == AV_CODEC_ID_H264 || (x) == AV_CODEC_ID_MPEG2VIDEO || \
-   (x) == AV_CODEC_ID_VP8  || /* (x) == AV_CODEC_ID_VP9 || */ \
-   (x) == AV_CODEC_ID_HEVC || (x) == AV_CODEC_ID_AAC || \
-   (x) == AV_CODEC_ID_MP2  || (x) == AV_CODEC_ID_VORBIS)
-
-/**
- *
- */
-static inline int
-shortid(transcoder_t *t)
-{
-  return t->t_id & 0xffff;
-}
-
-static inline void
-transcoder_stream_invalidate(transcoder_stream_t *ts)
-{
-  ts->ts_index = 0;
-}
-
-static AVCodecContext *
-avcodec_alloc_context3_tvh(const AVCodec *codec)
-{
-  AVCodecContext *ctx = avcodec_alloc_context3(codec);
-  if (ctx) {
-    ctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
-  }
-  return ctx;
-}
-
-static char *const
-get_error_text(const int error)
-{
-  static char error_buffer[255];
-  av_strerror(error, error_buffer, sizeof(error_buffer));
-  return error_buffer;
-}
-
-static int
-transcode_opt_set_int(transcoder_t *t, transcoder_stream_t *ts,
-                      void *ctx, const char *opt,
-                      int64_t val, int abort)
-{
-  int opt_error;
-  if ((opt_error = av_opt_set_int(ctx, opt, val, 0)) != 0) {
-    tvherror(LS_TRANSCODE, "%04X: Could not set option %s (error '%s')",
-             shortid(t), opt, get_error_text(opt_error));
-    if (abort)
-      transcoder_stream_invalidate(ts);
-    return -1;
-  }
-  return 0;
-}
-
-static void
-av_dict_set_int__(AVDictionary **opts, const char *key, int64_t val, int flags)
-{
-  char buf[32];
-  snprintf(buf, sizeof(buf), "%"PRId64, val);
-  av_dict_set(opts, key, buf, flags);
-}
-
-/**
- * get best effort sample rate
- */
-static int
-transcode_get_sample_rate(int rate, AVCodec *codec)
-{
-  /* if codec only supports certain rates, check if rate is available */
-  if (codec->supported_samplerates) {
-    /* Find if we have a matching sample_rate */
-    int acount = 0;
-    int rate_alt = 0;
-    while (codec->supported_samplerates[acount] > 0) {
-      if (codec->supported_samplerates[acount] == rate) {
-        /* original rate supported by codec */
-        return rate;
-      }
-
-      /* check for highest available rate smaller that the original rate */
-      if (codec->supported_samplerates[acount] > rate_alt &&
-          codec->supported_samplerates[acount] < rate) {
-        rate_alt = codec->supported_samplerates[acount];
-      }
-      acount++;
-    }
-
-    return rate_alt;
-  }
-
-
-  return rate;
-}
-
-/**
- * get best effort sample format
- */
-static enum AVSampleFormat
-transcode_get_sample_fmt(enum AVSampleFormat fmt, AVCodec *codec)
-{
-  /* if codec only supports certain formats, check if selected format is available */
-  if (codec->sample_fmts) {
-    /* Find if we have a matching sample_fmt */
-    int acount = 0;
-    while (codec->sample_fmts[acount] > AV_SAMPLE_FMT_NONE) {
-      if (codec->sample_fmts[acount] == fmt) {
-        /* original format supported by codec */
-        return fmt;
-      }
-      acount++;
-    }
-
-    /* use first supported sample format */
-    if (acount > 0) {
-      return codec->sample_fmts[0];
-    } else {
-      return AV_SAMPLE_FMT_NONE;
-    }
-  }
-
-  return fmt;
-}
-
-/**
- * get best effort channel layout
- */
-static uint64_t
-transcode_get_channel_layout(int *channels, AVCodec *codec)
-{
-  uint64_t channel_layout = AV_CH_LAYOUT_STEREO;
-
-  /* use channel layout based on input field */
-  switch (*channels) {
-  case 1: channel_layout = AV_CH_LAYOUT_MONO;     break;
-  case 2: channel_layout = AV_CH_LAYOUT_STEREO;   break;
-  case 3: channel_layout = AV_CH_LAYOUT_SURROUND; break;
-  case 4: channel_layout = AV_CH_LAYOUT_QUAD;     break;
-  case 5: channel_layout = AV_CH_LAYOUT_5POINT0;  break;
-  case 6: channel_layout = AV_CH_LAYOUT_5POINT1;  break;
-  case 7: channel_layout = AV_CH_LAYOUT_6POINT1;  break;
-  case 8: channel_layout = AV_CH_LAYOUT_7POINT1;  break;
-  }
-
-  /* if codec only supports certain layouts, check if selected layout is available */
-  if (codec->channel_layouts) {
-    int acount = 0;
-    uint64_t channel_layout_def = av_get_default_channel_layout(*channels);
-    uint64_t channel_layout_alt = 0;
-
-    while (codec->channel_layouts[acount] > 0) {
-      if (codec->channel_layouts[acount] == channel_layout) {
-        /* original layout supported by codec */
-        return channel_layout;
-      }
-
-      /* check for best matching layout with same or less number of channels */
-      if (av_get_channel_layout_nb_channels(codec->channel_layouts[acount]) <= *channels) {
-        if (av_get_channel_layout_nb_channels(codec->channel_layouts[acount]) >
-            av_get_channel_layout_nb_channels(channel_layout_alt)) {
-          /* prefer layout with more channels */
-          channel_layout_alt = codec->channel_layouts[acount];
-        } else if (av_get_channel_layout_nb_channels(codec->channel_layouts[acount]) ==
-                   av_get_channel_layout_nb_channels(channel_layout_alt) &&
-                   codec->channel_layouts[acount] == channel_layout_def) {
-          /* prefer default layout for number of channels over alternative layout */
-          channel_layout_alt = channel_layout_def;
-        }
-      }
-
-      acount++;
-    }
-
-    if (channel_layout_alt) {
-      channel_layout = channel_layout_alt;
-      *channels = av_get_channel_layout_nb_channels(channel_layout_alt);
-    } else {
-      channel_layout = 0;
-      *channels = 0;
-    }
-  }
-
-  return channel_layout;
-}
-
-/**
- *
- */
-static AVCodec *
-transcoder_get_decoder(transcoder_t *t, streaming_component_type_t ty)
-{
-  enum AVCodecID codec_id;
-  AVCodec *codec;
-
-  /* the MP4A and AAC packet format is same, reduce to one type */
-  if (ty == SCT_MP4A)
-    ty = SCT_AAC;
-
-  codec_id = streaming_component_type2codec_id(ty);
-  if (codec_id == AV_CODEC_ID_NONE) {
-    tvherror(LS_TRANSCODE, "%04X: Unsupported input codec %s",
-            shortid(t), streaming_component_type2txt(ty));
-    return NULL;
-  }
-
-  codec = avcodec_find_decoder(codec_id);
-  if (!codec) {
-    tvherror(LS_TRANSCODE, "%04X: Unable to find %s decoder",
-            shortid(t), streaming_component_type2txt(ty));
-    return NULL;
-  }
-
-  tvhtrace(LS_TRANSCODE, "%04X: Using decoder %s", shortid(t), codec->name);
-
-  return codec;
-}
-
-
-/**
- *
- */
-static AVCodec *
-transcoder_get_encoder(transcoder_t *t, const char *codec_name)
-{
-  AVCodec *codec;
-
-  codec = avcodec_find_encoder_by_name(codec_name);
-  if (!codec) {
-    tvherror(LS_TRANSCODE, "%04X: Unable to find %s encoder",
-             shortid(t), codec_name);
-    return NULL;
-  }
-  tvhtrace(LS_TRANSCODE, "%04X: Using encoder %s", shortid(t), codec->name);
-
-  return codec;
-}
-
-
-/**
- *
- */
-static void
-transcoder_stream_packet(transcoder_t *t, transcoder_stream_t *ts, th_pkt_t *pkt)
-{
-  streaming_message_t *sm;
-
-  tvhtrace(LS_TRANSCODE, "%04X: deliver copy (pts = %" PRIu64 ")",
-           shortid(t), pkt->pkt_pts);
-  sm = streaming_msg_create_pkt(pkt);
-  streaming_target_deliver2(ts->ts_target, sm);
-  pkt_ref_dec(pkt);
-}
-
-
-/**
- *
- */
-static void
-transcoder_stream_subtitle(transcoder_t *t, transcoder_stream_t *ts, th_pkt_t *pkt)
-{
-  //streaming_message_t *sm;
-  AVCodec *icodec;
-  AVCodecContext *ictx;
-  AVPacket packet;
-  AVSubtitle sub;
-  int length, got_subtitle;
-
-  subtitle_stream_t *ss = (subtitle_stream_t*)ts;
-
-  ictx = ss->sub_ictx;
-  //octx = ss->sub_octx;
-
-  icodec = ss->sub_icodec;
-  //ocodec = ss->sub_ocodec;
-
-
-  if (!avcodec_is_open(ictx)) {
-    if (avcodec_open2(ictx, icodec, NULL) < 0) {
-      tvherror(LS_TRANSCODE, "%04X: Unable to open %s decoder",
-               shortid(t), icodec->name);
-      transcoder_stream_invalidate(ts);
-      return;
-    }
-  }
-
-  av_init_packet(&packet);
-  packet.data     = pktbuf_ptr(pkt->pkt_payload);
-  packet.size     = pktbuf_len(pkt->pkt_payload);
-  packet.pts      = pkt->pkt_pts;
-  packet.dts      = pkt->pkt_dts;
-  packet.duration = pkt->pkt_duration;
-
-  memset(&sub, 0, sizeof(sub));
-
-  length = avcodec_decode_subtitle2(ictx,  &sub, &got_subtitle, &packet);
-  if (length <= 0) {
-    if (length == AVERROR_INVALIDDATA) goto cleanup;
-    tvherror(LS_TRANSCODE, "%04X: Unable to decode subtitle (%d, %s)",
-             shortid(t), length, get_error_text(length));
-    goto cleanup;
-  }
-
-  if (!got_subtitle)
-    goto cleanup;
-
-  //TODO: encoding
-
- cleanup:
-  av_free_packet(&packet);
-  avsubtitle_free(&sub);
-}
-
-static void
-create_adts_header(pktbuf_t *pb, int sri, int channels)
-{
-   bitstream_t bs;
-
-   /* 7 bytes of ADTS header */
-   init_wbits(&bs, pktbuf_ptr(pb), 56);
-
-   put_bits(&bs, 0xfff, 12); // Sync marker
-   put_bits(&bs, 0, 1);      // ID 0 = MPEG 4, 1 = MPEG 2
-   put_bits(&bs, 0, 2);      // Layer
-   put_bits(&bs, 1, 1);      // Protection absent
-   put_bits(&bs, 1, 2);      // AOT, 1 = AAC LC
-   put_bits(&bs, sri, 4);
-   put_bits(&bs, 1, 1);      // Private bit
-   put_bits(&bs, channels, 3);
-   put_bits(&bs, 1, 1);      // Original
-   put_bits(&bs, 1, 1);      // Copy
-
-   put_bits(&bs, 1, 1);      // Copyright identification bit
-   put_bits(&bs, 1, 1);      // Copyright identification start
-   put_bits(&bs, pktbuf_len(pb), 13);
-   put_bits(&bs, 0x7ff, 11); // Buffer fullness
-   put_bits(&bs, 0, 2);      // RDB in frame
-}
-
-/**
- *
- */
-static void
-transcoder_stream_audio(transcoder_t *t, transcoder_stream_t *ts, th_pkt_t *pkt)
-{
-  AVCodec *icodec, *ocodec;
-  AVCodecContext *ictx, *octx;
-  AVPacket packet;
-  int length;
-  streaming_message_t *sm;
-  th_pkt_t *n;
-  audio_stream_t *as = (audio_stream_t*)ts;
-  int got_frame, got_packet_ptr;
-  AVFrame *frame = av_frame_alloc();
-  char layout_buf[100];
-  uint8_t *d;
-
-  ictx = as->aud_ictx;
-  octx = as->aud_octx;
-
-  icodec = as->aud_icodec;
-  ocodec = as->aud_ocodec;
-
-  av_init_packet(&packet);
-
-  if (!avcodec_is_open(ictx)) {
-    if (icodec->id == AV_CODEC_ID_AAC || icodec->id == AV_CODEC_ID_VORBIS) {
-      d = pktbuf_ptr(pkt->pkt_payload);
-      if (icodec->id == AV_CODEC_ID_AAC && d && pktbuf_len(pkt->pkt_payload) > 2 &&
-          d[0] == 0xff && (d[1] & 0xf0) == 0xf0) {
-        /* DTS packets have all info */
-      } else if (ts->ts_input_gh) {
-        ictx->extradata_size = pktbuf_len(ts->ts_input_gh);
-        ictx->extradata = av_malloc(ictx->extradata_size);
-        memcpy(ictx->extradata,
-               pktbuf_ptr(ts->ts_input_gh), pktbuf_len(ts->ts_input_gh));
-        tvhtrace(LS_TRANSCODE, "%04X: copy meta data for %s (len %zd)",
-                 shortid(t), icodec->id == AV_CODEC_ID_AAC ? "AAC" : "VORBIS",
-                 pktbuf_len(ts->ts_input_gh));
-      } else {
-        tvherror(LS_TRANSCODE, "%04X: missing meta data for %s",
-                 shortid(t), icodec->id == AV_CODEC_ID_AAC ? "AAC" : "VORBIS");
-      }
-    }
-
-    if (avcodec_open2(ictx, icodec, NULL) < 0) {
-      tvherror(LS_TRANSCODE, "%04X: Unable to open %s decoder",
-               shortid(t), icodec->name);
-      transcoder_stream_invalidate(ts);
-      goto cleanup;
-    }
-
-    as->aud_dec_pts = pkt->pkt_pts;
-  }
-
-  if (pkt->pkt_pts > as->aud_dec_pts) {
-    tvhwarn(LS_TRANSCODE, "%04X: Detected framedrop in audio", shortid(t));
-    as->aud_enc_pts += (pkt->pkt_pts - as->aud_dec_pts);
-    as->aud_dec_pts += (pkt->pkt_pts - as->aud_dec_pts);
-  }
-
-  packet.data     = pktbuf_ptr(pkt->pkt_payload);
-  packet.size     = pktbuf_len(pkt->pkt_payload);
-  packet.pts      = pkt->pkt_pts;
-  packet.dts      = pkt->pkt_dts;
-  packet.duration = pkt->pkt_duration;
-
-  length = avcodec_decode_audio4(ictx, frame, &got_frame, &packet);
-  av_free_packet(&packet);
-
-  tvhtrace(LS_TRANSCODE, "%04X: audio decode: consumed=%d size=%zu, got=%d, pts=%" PRIi64,
-           shortid(t), length, pktbuf_len(pkt->pkt_payload), got_frame, pkt->pkt_pts);
-
-  if (length < 0) {
-    if (length == AVERROR_INVALIDDATA) goto cleanup;
-    tvherror(LS_TRANSCODE, "%04X: Unable to decode audio (%d, %s)",
-             shortid(t), length, get_error_text(length));
-    transcoder_stream_invalidate(ts);
-    goto cleanup;
-  }
-
-  if (!got_frame) {
-    tvhtrace(LS_TRANSCODE, "%04X: Did not have a full frame in the packet", shortid(t));
-    goto cleanup;
-  }
-
-  if (length != pktbuf_len(pkt->pkt_payload))
-    tvhwarn(LS_TRANSCODE,
-            "%04X: undecoded data (in=%zu, consumed=%d)",
-            shortid(t), pktbuf_len(pkt->pkt_payload), length);
-
-  if (!avcodec_is_open(octx)) {
-    as->aud_enc_pts       = pkt->pkt_pts;
-    octx->sample_rate     = transcode_get_sample_rate(ictx->sample_rate, ocodec);
-    octx->sample_fmt      = transcode_get_sample_fmt(ictx->sample_fmt, ocodec);
-    octx->time_base       = ictx->time_base;
-    octx->channels        = as->aud_channels ? as->aud_channels : ictx->channels;
-    octx->channel_layout  = transcode_get_channel_layout(&octx->channels, ocodec);
-    octx->bit_rate        = as->aud_bitrate  ? as->aud_bitrate  : 0;
-    octx->flags          |= CODEC_FLAG_GLOBAL_HEADER;
-
-    if (!octx->sample_rate) {
-      tvherror(LS_TRANSCODE, "%04X: audio encoder has no suitable sample rate!", shortid(t));
-      transcoder_stream_invalidate(ts);
-      goto cleanup;
-    } else {
-      tvhdebug(LS_TRANSCODE, "%04X: using audio sample rate %d",
-                          shortid(t), octx->sample_rate);
-    }
-
-    if (octx->sample_fmt == AV_SAMPLE_FMT_NONE) {
-      tvherror(LS_TRANSCODE, "%04X: audio encoder has no suitable sample format!", shortid(t));
-      transcoder_stream_invalidate(ts);
-      goto cleanup;
-    } else {
-      tvhdebug(LS_TRANSCODE, "%04X: using audio sample format %s",
-                          shortid(t), av_get_sample_fmt_name(octx->sample_fmt));
-    }
-
-    if (!octx->channel_layout) {
-      tvherror(LS_TRANSCODE, "%04X: audio encoder has no suitable channel layout!", shortid(t));
-      transcoder_stream_invalidate(ts);
-      goto cleanup;
-    } else {
-      av_get_channel_layout_string(layout_buf, sizeof (layout_buf), octx->channels, octx->channel_layout);
-      tvhdebug(LS_TRANSCODE, "%04X: using audio channel layout %s",
-                          shortid(t), layout_buf);
-    }
-
-    // Set flags and quality settings, if no bitrate was specified.
-    // The MPEG2 encoder only supports encoding with fixed bitrate.
-    // All AAC encoders should support encoding with fixed bitrate,
-    // but some don't support encoding with global_quality (vbr).
-    // All vorbis encoders support encoding with global_quality (vbr),
-    // but the built in vorbis encoder doesn't support fixed bitrate.
-    switch (ts->ts_type) {
-    case SCT_MPEG2AUDIO:
-      // use 96 kbit per channel as default
-      if (octx->bit_rate == 0) {
-        octx->bit_rate = octx->channels * 96000;
-      }
-      break;
-
-    case SCT_AAC:
-      octx->flags |= CODEC_FLAG_BITEXACT;
-      // use 64 kbit per channel as default
-      if (octx->bit_rate == 0) {
-        octx->bit_rate = octx->channels * 64000;
-      }
-      break;
-
-    case SCT_VORBIS:
-      // use vbr with quality setting as default
-      // and also use a user specified bitrate < 16 kbit as quality setting
-      if (octx->bit_rate == 0) {
-        octx->flags |= CODEC_FLAG_QSCALE;
-        octx->global_quality = 4 * FF_QP2LAMBDA;
-      } else if (t->t_props.tp_abitrate < 16) {
-        octx->flags |= CODEC_FLAG_QSCALE;
-        octx->global_quality = t->t_props.tp_abitrate * FF_QP2LAMBDA;
-        octx->bit_rate = 0;
-      }
-      break;
-
-    default:
-      break;
-    }
-
-    if (avcodec_open2(octx, ocodec, NULL) < 0) {
-      tvherror(LS_TRANSCODE, "%04X: Unable to open %s encoder",
-               shortid(t), ocodec->name);
-      transcoder_stream_invalidate(ts);
-      goto cleanup;
-    }
-
-    as->fifo = av_audio_fifo_alloc(octx->sample_fmt, octx->channels, 1);
-    if (!as->fifo) {
-      tvherror(LS_TRANSCODE, "%04X: Could not allocate fifo", shortid(t));
-      transcoder_stream_invalidate(ts);
-      goto cleanup;
-    }
-
-    as->resample_context    = NULL;
-
-    as->last_sample_rate    = ictx->sample_rate;
-    as->last_sample_fmt     = ictx->sample_fmt;
-    as->last_channel_layout = ictx->channel_layout;
-  }
-
-  /* check for changed input format and close resampler, if changed */
-  if (as->last_sample_rate    != ictx->sample_rate    ||
-      as->last_sample_fmt     != ictx->sample_fmt     ||
-      as->last_channel_layout != ictx->channel_layout) {
-    tvhdebug(LS_TRANSCODE, "%04X: audio input format changed", shortid(t));
-
-    as->last_sample_rate    = ictx->sample_rate;
-    as->last_sample_fmt     = ictx->sample_fmt;
-    as->last_channel_layout = ictx->channel_layout;
-
-    if (as->resample_context) {
-      tvhdebug(LS_TRANSCODE, "%04X: stopping audio resampling", shortid(t));
-      avresample_free(&as->resample_context);
-      as->resample_context = NULL;
-      as->resample_is_open = 0;
-    }
-  }
-
-  as->resample = (ictx->channel_layout != octx->channel_layout) ||
-                 (ictx->sample_fmt     != octx->sample_fmt)     ||
-                 (ictx->sample_rate    != octx->sample_rate);
-
-  if (as->resample) {
-    if (!as->resample_context) {
-      if (!(as->resample_context = avresample_alloc_context())) {
-        tvherror(LS_TRANSCODE, "%04X: Could not allocate resample context", shortid(t));
-        transcoder_stream_invalidate(ts);
-        goto cleanup;
-      }
-
-      // resample audio
-      tvhdebug(LS_TRANSCODE, "%04X: starting audio resampling", shortid(t));
-
-      av_get_channel_layout_string(layout_buf, sizeof (layout_buf), ictx->channels, ictx->channel_layout);
-      tvhdebug(LS_TRANSCODE, "%04X: IN : channel_layout=%s, rate=%d, fmt=%s, bitrate=%"PRId64,
-               shortid(t), layout_buf, ictx->sample_rate,
-               av_get_sample_fmt_name(ictx->sample_fmt), (int64_t)ictx->bit_rate);
-
-      av_get_channel_layout_string(layout_buf, sizeof (layout_buf), octx->channels, octx->channel_layout);
-      tvhdebug(LS_TRANSCODE, "%04X: OUT: channel_layout=%s, rate=%d, fmt=%s, bitrate=%"PRId64,
-               shortid(t), layout_buf, octx->sample_rate,
-               av_get_sample_fmt_name(octx->sample_fmt), (int64_t)octx->bit_rate);
-
-      if (transcode_opt_set_int(t, ts, as->resample_context,
-                                "in_channel_layout", ictx->channel_layout, 1))
-        goto cleanup;
-      if (transcode_opt_set_int(t, ts, as->resample_context,
-                                "out_channel_layout", octx->channel_layout, 1))
-        goto cleanup;
-      if (transcode_opt_set_int(t, ts, as->resample_context,
-                                "in_sample_rate", ictx->sample_rate, 1))
-        goto cleanup;
-      if (transcode_opt_set_int(t, ts, as->resample_context,
-                                "out_sample_rate", octx->sample_rate, 1))
-        goto cleanup;
-      if (transcode_opt_set_int(t, ts, as->resample_context,
-                                "in_sample_fmt", ictx->sample_fmt, 1))
-        goto cleanup;
-      if (transcode_opt_set_int(t, ts, as->resample_context,
-                                "out_sample_fmt", octx->sample_fmt, 1))
-        goto cleanup;
-      if (avresample_open(as->resample_context) < 0) {
-        tvherror(LS_TRANSCODE, "%04X: Error avresample_open", shortid(t));
-        transcoder_stream_invalidate(ts);
-        goto cleanup;
-      }
-      as->resample_is_open = 1;
-    }
-
-    uint8_t **output = alloca(octx->channels * sizeof(uint8_t *));
-
-    if (av_samples_alloc(output, NULL, octx->channels, frame->nb_samples, octx->sample_fmt, 1) < 0) {
-      tvherror(LS_TRANSCODE, "%04X: av_resamples_alloc failed", shortid(t));
-      transcoder_stream_invalidate(ts);
-      goto scleanup;
-    }
-
-    length = avresample_convert(as->resample_context, NULL, 0, frame->nb_samples,
-                                frame->extended_data, 0, frame->nb_samples);
-    tvhtrace(LS_TRANSCODE, "%04X: avresample_convert: %d", shortid(t), length);
-    while (avresample_available(as->resample_context) > 0) {
-      length = avresample_read(as->resample_context, output, frame->nb_samples);
-
-      if (length > 0) {
-        if (av_audio_fifo_realloc(as->fifo, av_audio_fifo_size(as->fifo) + length) < 0) {
-          tvherror(LS_TRANSCODE, "%04X: Could not reallocate FIFO", shortid(t));
-          transcoder_stream_invalidate(ts);
-          goto scleanup;
-        }
-
-        if (av_audio_fifo_write(as->fifo, (void **)output, length) < length) {
-          tvherror(LS_TRANSCODE, "%04X: Could not write to FIFO", shortid(t));
-          goto scleanup;
-        }
-      }
-      continue;
-
-scleanup:
-      transcoder_stream_invalidate(ts);
-      av_freep(&output[0]);
-      goto cleanup;
-    }
-
-    av_freep(&output[0]);
-
-/*  Need to find out where we are going to do this. Normally at the end.
-    int delay_samples = avresample_get_delay(as->resample_context);
-    if (delay_samples) {
-      tvhdebug(LS_TRANSCODE, "%d samples in resamples delay buffer.", delay_samples);
-      goto cleanup;
-    }
-*/
-
-  } else {
-
-    if (av_audio_fifo_realloc(as->fifo, av_audio_fifo_size(as->fifo) + frame->nb_samples) < 0) {
-      tvherror(LS_TRANSCODE, "%04X: Could not reallocate FIFO", shortid(t));
-      transcoder_stream_invalidate(ts);
-      goto cleanup;
-    }
-
-    if (av_audio_fifo_write(as->fifo, (void **)frame->extended_data, frame->nb_samples) < frame->nb_samples) {
-      tvherror(LS_TRANSCODE, "%04X: Could not write to FIFO", shortid(t));
-      transcoder_stream_invalidate(ts);
-      goto cleanup;
-    }
-
-  }
-
-  as->aud_dec_pts += pkt->pkt_duration;
-
-  while (av_audio_fifo_size(as->fifo) >= octx->frame_size) {
-    tvhtrace(LS_TRANSCODE, "%04X: audio loop: fifo=%d, frame=%d",
-             shortid(t), av_audio_fifo_size(as->fifo), octx->frame_size);
-
-    av_frame_free(&frame);
-    frame = av_frame_alloc();
-    frame->nb_samples = octx->frame_size;
-    frame->format = octx->sample_fmt;
-#if USING_FFMPEG
-    frame->channels = octx->channels;
-#endif
-    frame->channel_layout = octx->channel_layout;
-    frame->sample_rate = octx->sample_rate;
-    if (av_frame_get_buffer(frame, 0) < 0) {
-      tvherror(LS_TRANSCODE, "%04X: Could not allocate output frame samples", shortid(t));
-      transcoder_stream_invalidate(ts);
-      goto cleanup;
-    }
-
-    if ((length = av_audio_fifo_read(as->fifo, (void **)frame->data, octx->frame_size)) != octx->frame_size) {
-      tvherror(LS_TRANSCODE, "%04X: Could not read data from FIFO", shortid(t));
-      transcoder_stream_invalidate(ts);
-      goto cleanup;
-    }
-
-    tvhtrace(LS_TRANSCODE, "%04X: pre-encode: linesize=%d, samples=%d, pts=%" PRIi64,
-             shortid(t), frame->linesize[0], length, as->aud_enc_pts);
-
-    frame->pts = as->aud_enc_pts;
-    as->aud_enc_pts += (octx->frame_size * 90000) / octx->sample_rate;
-
-    av_init_packet(&packet);
-    packet.data = NULL;
-    packet.size = 0;
-    length = avcodec_encode_audio2(octx, &packet, frame, &got_packet_ptr);
-    tvhtrace(LS_TRANSCODE, "%04X: encoded: packet=%d, ret=%d, got=%d, pts=%" PRIi64,
-             shortid(t), packet.size, length, got_packet_ptr, packet.pts);
-
-    if ((length < 0) || (got_packet_ptr < -1)) {
-
-      tvherror(LS_TRANSCODE, "%04X: Unable to encode audio (%d:%d)",
-               shortid(t), length, got_packet_ptr);
-      transcoder_stream_invalidate(ts);
-      goto cleanup;
-
-    } else if (got_packet_ptr && packet.pts >= 0) {
-
-      int extra_size = 0;
-
-      if (ts->ts_type == SCT_AAC) {
-        /* only if ADTS header is missing, create it */
-        if (packet.size < 2 || packet.data[0] != 0xff || (packet.data[1] & 0xf0) != 0xf0)
-          extra_size = 7;
-      }
-
-      n = pkt_alloc(ts->ts_type, NULL, packet.size + extra_size, packet.pts, packet.pts, packet.pts);
-      memcpy(pktbuf_ptr(n->pkt_payload) + extra_size, packet.data, packet.size);
-
-      n->pkt_componentindex = ts->ts_index;
-      n->a.pkt_channels     = octx->channels;
-      n->a.pkt_sri          = rate_to_sri(octx->sample_rate);
-      n->pkt_duration       = packet.duration;
-
-      if (extra_size && ts->ts_type == SCT_AAC)
-        create_adts_header(n->pkt_payload, n->a.pkt_sri, octx->channels);
-
-      if (octx->extradata_size)
-        n->pkt_meta = pktbuf_alloc(octx->extradata, octx->extradata_size);
-
-      tvhtrace(LS_TRANSCODE, "%04X: deliver audio (pts = %" PRIi64 ", delay = %i)",
-               shortid(t), n->pkt_pts, octx->delay);
-      sm = streaming_msg_create_pkt(n);
-      streaming_target_deliver2(ts->ts_target, sm);
-      pkt_ref_dec(n);
-    }
-
-    av_free_packet(&packet);
-  }
-
- cleanup:
-
-  av_frame_free(&frame);
-  av_free_packet(&packet);
-
-  pkt_ref_dec(pkt);
-}
-
-/**
- * Parse MPEG2 header, simplifier version (we know what ffmpeg/libav generates
- */
-static void
-extract_mpeg2_global_data(th_pkt_t *n, uint8_t *data, int len)
-{
-/*
-From: http://en.wikipedia.org/wiki/Elementary_stream
-Field Name     # of bits       Description
-start code                             32      0x000001B3
-Horizontal Size                                12
-Vertical Size                          12
-Aspect ratio                           4
-Frame rate code                                4
-Bit rate                               18      Actual bit rate = bit rate * 400, rounded upwards. Use 0x3FFFF for variable bit rate.
-Marker bit                             1       Always 1.
-VBV buf size                           10      Size of video buffer verifier = 16*1024*vbv buf size
-constrained parameters flag            1
-load intra quantizer matrix            1       If bit set then intra quantizer matrix follows, otherwise use default values.
-intra quantizer matrix                 0 or 64*8
-load non intra quantizer matrix        1       If bit set then non intra quantizer matrix follows.
-non intra quantizer matrix             0 or 64*8
-
-Minimal of 12 bytes.
-*/
-  int hs = 12;
-
-  if (len >= hs && RB32(data) == 0x000001b3) {  // SEQ_START_CODE
-
-    // load intra quantizer matrix
-    if (data[hs-1] & 0x02) {
-      if (hs + 64 < len) return;
-      hs += 64;
-    }
-
-    // load non intra quantizer matrix
-    if (data[hs-1] & 0x01) {
-      if (hs + 64 < len) return;
-      hs += 64;
-    }
-
-    // See if we have the first EXT_START_CODE. Normally 10 bytes
-    // https://git.libav.org/?p=libav.git;a=blob;f=libavcodec/mpeg12enc.c;h=3376f1075f4b7582a8e4556e98deddab3e049dab;hb=HEAD#l272
-    if (hs + 10 <= len && RB32(data + hs) == 0x000001b5) // EXT_START_CODE
-      hs += 10;
-
-    // See if we have the second EXT_START_CODE. Normally 12 bytes
-    // https://git.libav.org/?p=libav.git;a=blob;f=libavcodec/mpeg12enc.c;h=3376f1075f4b7582a8e4556e98deddab3e049dab;hb=HEAD#l291
-    // ffmpeg libs might have this block missing
-    if (hs + 12 <= len && RB32(data + hs) == 0x000001b5) // EXT_START_CODE
-      hs += 12;
-
-    // See if we have the second GOP_START_CODE. Normally 31 bits == 4 bytes
-    // https://git.libav.org/?p=libav.git;a=blob;f=libavcodec/mpeg12enc.c;h=3376f1075f4b7582a8e4556e98deddab3e049dab;hb=HEAD#l304
-    if (hs + 4 <= len && RB32(data + hs) == 0x000001b8) // GOP_START_CODE
-      hs += 4;
-
-    n->pkt_meta = pktbuf_alloc(data, hs);
-  }
-}
-
-/**
- *
- */
-static void
-send_video_packet(transcoder_t *t, transcoder_stream_t *ts, th_pkt_t *pkt,
-                  AVPacket *epkt, AVCodecContext *octx)
-{
-  video_stream_t *vs = (video_stream_t*)ts;
-  streaming_message_t *sm;
-  th_pkt_t *n;
-
-  if (epkt->size <= 0) {
-    if (epkt->size) {
-      tvherror(LS_TRANSCODE, "%04X: Unable to encode video (%d)", shortid(t), epkt->size);
-      transcoder_stream_invalidate(ts);
-    }
-
-    return;
-  }
-
-  if (!octx->coded_frame)
-    return;
-
-  if ((ts->ts_type == SCT_H264 || ts->ts_type == SCT_HEVC) &&
-      octx->extradata_size &&
-      (ts->ts_first || octx->coded_frame->pict_type == AV_PICTURE_TYPE_I)) {
-    n = pkt_alloc(ts->ts_type, NULL, octx->extradata_size + epkt->size, epkt->pts, epkt->dts, epkt->dts);
-    memcpy(pktbuf_ptr(n->pkt_payload), octx->extradata, octx->extradata_size);
-    memcpy(pktbuf_ptr(n->pkt_payload) + octx->extradata_size, epkt->data, epkt->size);
-    ts->ts_first = 0;
-  } else {
-    n = pkt_alloc(ts->ts_type, epkt->data, epkt->size, epkt->pts, epkt->dts, epkt->dts);
-  }
-
-  switch (octx->coded_frame->pict_type) {
-  case AV_PICTURE_TYPE_I:
-    n->v.pkt_frametype = PKT_I_FRAME;
-    break;
-
-  case AV_PICTURE_TYPE_P:
-    n->v.pkt_frametype = PKT_P_FRAME;
-    break;
-
-  case AV_PICTURE_TYPE_B:
-    n->v.pkt_frametype = PKT_B_FRAME;
-    break;
-
-  default:
-    break;
-  }
-
-  n->pkt_duration       = pkt->pkt_duration;
-  n->pkt_commercial     = pkt->pkt_commercial;
-  n->pkt_componentindex = pkt->pkt_componentindex;
-  n->v.pkt_field        = pkt->v.pkt_field;
-  n->v.pkt_aspect_num   = pkt->v.pkt_aspect_num;
-  n->v.pkt_aspect_den   = pkt->v.pkt_aspect_den;
-
-  if(octx->coded_frame && octx->coded_frame->pts != AV_NOPTS_VALUE) {
-    if(n->pkt_dts != PTS_UNSET)
-      n->pkt_dts -= n->pkt_pts;
-
-    n->pkt_pts = octx->coded_frame->pts;
-
-    if(n->pkt_dts != PTS_UNSET)
-      n->pkt_dts += n->pkt_pts;
-  }
-
-  if (octx->extradata_size) {
-    n->pkt_meta = pktbuf_alloc(octx->extradata, octx->extradata_size);
-  } else {
-    if (octx->codec_id == AV_CODEC_ID_MPEG2VIDEO)
-      extract_mpeg2_global_data(n, epkt->data, epkt->size);
-  }
-
-  tvhtrace(LS_TRANSCODE, "%04X: deliver video (dts = %" PRIu64 ", pts = %" PRIu64 ")", shortid(t), n->pkt_dts, n->pkt_pts);
-
-  if (!vs->vid_first_encoded) {
-    vs->vid_first_pkt = n;
-    vs->vid_first_encoded = 1;
-    return;
-  }
-  if (vs->vid_first_pkt) {
-    if (vs->vid_first_pkt->pkt_dts < n->pkt_dts) {
-      sm = streaming_msg_create_pkt(vs->vid_first_pkt);
-      streaming_target_deliver2(ts->ts_target, sm);
-    } else {
-      tvhtrace(LS_TRANSCODE, "%04X: video skip first packet", shortid(t));
-    }
-    pkt_ref_dec(vs->vid_first_pkt);
-    vs->vid_first_pkt = NULL;
-  }
-
-  sm = streaming_msg_create_pkt(n);
-  streaming_target_deliver2(ts->ts_target, sm);
-  pkt_ref_dec(n);
-
-}
-
-/* create a simple deinterlacer-scaler video filter chain */
-static int
-create_video_filter(video_stream_t *vs, transcoder_t *t,
-                    AVCodecContext *ictx, AVCodecContext *octx)
-{
-  AVFilterInOut *flt_inputs, *flt_outputs;
-  AVFilter *flt_bufsrc, *flt_bufsink;
-  enum AVPixelFormat pix_fmts[] = { 0, AV_PIX_FMT_NONE };
-  char opt[128];
-  int err;
-
-  err = 1;
-  flt_inputs = flt_outputs = NULL;
-  flt_bufsrc = flt_bufsink = NULL;
-
-  if (vs->flt_graph)
-    avfilter_graph_free(&vs->flt_graph);
-
-  vs->flt_graph = avfilter_graph_alloc();
-  if (!vs->flt_graph)
-    return err;
-
-  flt_inputs = avfilter_inout_alloc();
-  if (!flt_inputs)
-    goto out_err;
-
-  flt_outputs = avfilter_inout_alloc();
-  if (!flt_outputs)
-    goto out_err;
-
-  flt_bufsrc = avfilter_get_by_name("buffer");
-  flt_bufsink = avfilter_get_by_name("buffersink");
-  if (!flt_bufsrc || !flt_bufsink) {
-    tvherror(LS_TRANSCODE, "%04X: libav default buffers unknown", shortid(t));
-    goto out_err;
-  }
-
-  memset(opt, 0, sizeof(opt));
-  snprintf(opt, sizeof(opt), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
-           ictx->width,
-           ictx->height,
-           ictx->pix_fmt,
-           ictx->time_base.num,
-           ictx->time_base.den,
-           ictx->sample_aspect_ratio.num,
-           ictx->sample_aspect_ratio.den);
-
-  err = avfilter_graph_create_filter(&vs->flt_bufsrcctx, flt_bufsrc, "in",
-                                     opt, NULL, vs->flt_graph);
-  if (err < 0) {
-    tvherror(LS_TRANSCODE, "%04X: fltchain IN init error", shortid(t));
-    goto out_err;
-  }
-
-  err = avfilter_graph_create_filter(&vs->flt_bufsinkctx, flt_bufsink,
-                                     "out", NULL, NULL, vs->flt_graph);
-  if (err < 0) {
-    tvherror(LS_TRANSCODE, "%04X: fltchain OUT init error", shortid(t));
-    goto out_err;
-  }
-
-  pix_fmts[0] = octx->pix_fmt;
-  err = av_opt_set_int_list(vs->flt_bufsinkctx, "pix_fmts", pix_fmts,
-                            AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
-  if (err < 0) {
-    tvherror(LS_TRANSCODE, "%08X: fltchain cannot set output pixfmt",
-             shortid(t));
-    goto out_err;
-  }
-
-  flt_outputs->name = av_strdup("in");
-  flt_outputs->filter_ctx = vs->flt_bufsrcctx;
-  flt_outputs->pad_idx = 0;
-  flt_outputs->next = NULL;
-  flt_inputs->name = av_strdup("out");
-  flt_inputs->filter_ctx = vs->flt_bufsinkctx;
-  flt_inputs->pad_idx = 0;
-  flt_inputs->next = NULL;
-
-  /* add filters: yadif to deinterlace and a scaler */
-  memset(opt, 0, sizeof(opt));
-  snprintf(opt, sizeof(opt), "yadif,scale=%dx%d",
-           octx->width,
-           octx->height);
-  err = avfilter_graph_parse_ptr(vs->flt_graph,
-                                 opt,
-                                 &flt_inputs,
-                                 &flt_outputs,
-                                 NULL);
-  if (err < 0) {
-    tvherror(LS_TRANSCODE, "%04X: failed to init filter chain", shortid(t));
-    goto out_err;
-  }
-
-  err = avfilter_graph_config(vs->flt_graph, NULL);
-  if (err < 0) {
-    tvherror(LS_TRANSCODE, "%04X: failed to config filter chain", shortid(t));
-    goto out_err;
-  }
-
-  avfilter_inout_free(&flt_inputs);
-  avfilter_inout_free(&flt_outputs);
-
-  return 0;  /* all OK */
-
-out_err:
-  if (flt_inputs)
-    avfilter_inout_free(&flt_inputs);
-  if (flt_outputs)
-    avfilter_inout_free(&flt_outputs);
-  if (vs->flt_graph) {
-    avfilter_graph_free(&vs->flt_graph);
-    vs->flt_graph = NULL;
-  }
-
-  return err;
-}
-
-/**
- *
- */
-static void
-transcoder_stream_video(transcoder_t *t, transcoder_stream_t *ts, th_pkt_t *pkt)
-{
-  AVCodec *icodec, *ocodec;
-  AVCodecContext *ictx, *octx;
-  AVDictionary *opts;
-  AVPacket packet, packet2;
-  int length, ret, got_picture, got_output, got_ref;
-  video_stream_t *vs = (video_stream_t*)ts;
-  streaming_message_t *sm;
-  th_pkt_t *pkt2;
-  static int max_bitrate = INT_MAX / ((3000*10)/8);
-
-  av_init_packet(&packet);
-  av_init_packet(&packet2);
-  packet2.data = NULL;
-  packet2.size = 0;
-
-  ictx = vs->vid_ictx;
-  octx = vs->vid_octx;
-
-  icodec = vs->vid_icodec;
-  ocodec = vs->vid_ocodec;
-
-  opts = NULL;
-
-  got_ref = 0;
-
-  if (!avcodec_is_open(ictx)) {
-    if (icodec->id == AV_CODEC_ID_H264) {
-      if (ts->ts_input_gh) {
-        ictx->extradata_size = pktbuf_len(ts->ts_input_gh);
-        ictx->extradata = av_malloc(ictx->extradata_size);
-        memcpy(ictx->extradata,
-               pktbuf_ptr(ts->ts_input_gh), pktbuf_len(ts->ts_input_gh));
-        tvhtrace(LS_TRANSCODE, "%04X: copy meta data for H264 (len %zd)",
-                 shortid(t), pktbuf_len(ts->ts_input_gh));
-      }
-    }
-
-    if (avcodec_open2(ictx, icodec, NULL) < 0) {
-      tvherror(LS_TRANSCODE, "%04X: Unable to open %s decoder", shortid(t), icodec->name);
-      transcoder_stream_invalidate(ts);
-      goto cleanup;
-    }
-  }
-
-  if (!vs->vid_first_sent) {
-    /* notify global headers that we're live */
-    /* the video packets might be delayed */
-    pkt2 = pkt_alloc(ts->ts_type, NULL, 0, pkt->pkt_pts, pkt->pkt_dts, pkt->pkt_dts);
-    pkt2->pkt_componentindex = pkt->pkt_componentindex;
-    sm = streaming_msg_create_pkt(pkt2);
-    streaming_target_deliver2(ts->ts_target, sm);
-    pkt_ref_dec(pkt2);
-    vs->vid_first_sent = 1;
-  }
-
-  packet.data     = pktbuf_ptr(pkt->pkt_payload);
-  packet.size     = pktbuf_len(pkt->pkt_payload);
-  packet.pts      = pkt->pkt_pts;
-  packet.dts      = pkt->pkt_dts;
-  packet.duration = pkt->pkt_duration;
-
-  vs->vid_enc_frame->pts = packet.pts;
-  vs->vid_enc_frame->pkt_dts = packet.dts;
-  vs->vid_enc_frame->pkt_pts = packet.pts;
-
-  vs->vid_dec_frame->pts = packet.pts;
-  vs->vid_dec_frame->pkt_dts = packet.dts;
-  vs->vid_dec_frame->pkt_pts = packet.pts;
-
-  ictx->reordered_opaque = packet.pts;
-
-  length = avcodec_decode_video2(ictx, vs->vid_dec_frame, &got_picture, &packet);
-  if (length <= 0) {
-    if (length == AVERROR_INVALIDDATA) goto cleanup;
-    tvherror(LS_TRANSCODE, "%04X: Unable to decode video (%d, %s)",
-             shortid(t), length, get_error_text(length));
-    goto cleanup;
-  }
-
-  if (!got_picture)
-    goto cleanup;
-
-  got_ref = 1;
-
-  octx->sample_aspect_ratio.num = ictx->sample_aspect_ratio.num;
-  octx->sample_aspect_ratio.den = ictx->sample_aspect_ratio.den;
-
-  vs->vid_enc_frame->sample_aspect_ratio.num = vs->vid_dec_frame->sample_aspect_ratio.num;
-  vs->vid_enc_frame->sample_aspect_ratio.den = vs->vid_dec_frame->sample_aspect_ratio.den;
-
-  if(!avcodec_is_open(octx)) {
-    // Common settings
-    octx->width           = vs->vid_width  ? vs->vid_width  : ictx->width;
-    octx->height          = vs->vid_height ? vs->vid_height : ictx->height;
-
-    // Encoder uses "time_base" for bitrate calculation, but "time_base" from decoder
-    // will be deprecated in the future, therefore calculate "time_base" from "framerate" if available.
-    octx->ticks_per_frame = ictx->ticks_per_frame;
-    if (ictx->framerate.num == 0) {
-      ictx->framerate.num = 30;
-      ictx->framerate.den = 1;
-    }
-    if (ictx->time_base.num == 0) {
-      ictx->time_base.num = ictx->framerate.den;
-      ictx->time_base.den = ictx->framerate.num;
-    }
-    octx->framerate = ictx->framerate;
-#if LIBAVCODEC_VERSION_MICRO >= 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(56, 13, 100) // ffmpeg 2.5
-    octx->time_base       = av_inv_q(av_mul_q(ictx->framerate, av_make_q(ictx->ticks_per_frame, 1)));
-#else
-    octx->time_base       = ictx->time_base;
-#endif
-
-    // set default gop size to 1 second
-    octx->gop_size        = ceil(av_q2d(av_inv_q(av_div_q(octx->time_base, (AVRational){1, octx->ticks_per_frame}))));
-
-    switch (ts->ts_type) {
-    case SCT_MPEG2VIDEO:
-       if (!strcmp(ocodec->name, "nvenc") || !strcmp(ocodec->name, "mpeg2_qsv"))
-          octx->pix_fmt    = AV_PIX_FMT_NV12;
-      else
-          octx->pix_fmt    = AV_PIX_FMT_YUV420P;
-
-      octx->flags         |= CODEC_FLAG_GLOBAL_HEADER;
-
-      if (t->t_props.tp_vbitrate < 64) {
-        // encode with specified quality and optimize for low latency
-        // valid values for quality are 2-31, smaller means better quality, use 5 as default
-        octx->flags          |= CODEC_FLAG_QSCALE;
-        octx->global_quality  = FF_QP2LAMBDA *
-            (t->t_props.tp_vbitrate == 0 ? 5 : MINMAX(t->t_props.tp_vbitrate, 2, 31));
-      } else {
-        // encode with specified bitrate and optimize for high compression
-        octx->bit_rate        = t->t_props.tp_vbitrate * 1000;
-        octx->rc_max_rate     = ceil(octx->bit_rate * 1.25);
-        octx->rc_buffer_size  = octx->rc_max_rate * 3;
-        // use gop size of 5 seconds
-        octx->gop_size       *= 5;
-        // activate b-frames
-        octx->max_b_frames    = 3;
-      }
-
-      break;
-
-    case SCT_VP8:
-      octx->pix_fmt        = AV_PIX_FMT_YUV420P;
-
-      // setting quality to realtime will use as much CPU for transcoding as possible,
-      // while still encoding in realtime
-      av_dict_set(&opts, "quality", "realtime", 0);
-
-      if (t->t_props.tp_vbitrate < 64) {
-        // encode with specified quality and optimize for low latency
-        // valid values for quality are 1-63, smaller means better quality, use 15 as default
-        av_dict_set_int__(&opts,      "crf", t->t_props.tp_vbitrate == 0 ? 15 : t->t_props.tp_vbitrate, 0);
-        // bitrate setting is still required, as it's used as max rate in CQ mode
-        // and set to a very low value by default
-        octx->bit_rate        = 25000000;
-      } else {
-        // encode with specified bitrate and optimize for high compression
-        octx->bit_rate        = t->t_props.tp_vbitrate * 1000;
-        octx->rc_buffer_size  = octx->bit_rate * 3;
-        // use gop size of 5 seconds
-        octx->gop_size       *= 5;
-      }
-
-      break;
-
-    case SCT_H264:
-       if (!strcmp(ocodec->name, "nvenc") || !strcmp(ocodec->name, "h264_qsv"))
-          octx->pix_fmt    = AV_PIX_FMT_NV12;
-      else
-          octx->pix_fmt    = AV_PIX_FMT_YUV420P;
-
-      octx->flags         |= CODEC_FLAG_GLOBAL_HEADER;
-
-      // Default = "medium". We gain more encoding speed compared to the loss of quality when lowering it _slightly_.
-      // select preset according to system performance and codec type
-      av_dict_set(&opts, "preset",  t->t_props.tp_vcodec_preset, 0);
-      tvhinfo(LS_TRANSCODE, "%04X: Using preset %s", shortid(t), t->t_props.tp_vcodec_preset);
-
-      // All modern devices should support "high" profile
-      av_dict_set(&opts, "profile", "high", 0);
-
-      if (t->t_props.tp_vbitrate < 64) {
-        // encode with specified quality and optimize for low latency
-        // valid values for quality are 1-51, smaller means better quality, use 15 as default
-        av_dict_set_int__(&opts,      "crf", t->t_props.tp_vbitrate == 0 ? 15 : MIN(51, t->t_props.tp_vbitrate), 0);
-        // tune "zerolatency" removes as much encoder latency as possible
-        av_dict_set(&opts,      "tune", "zerolatency", 0);
-      } else {
-        // encode with specified bitrate and optimize for high compression
-        octx->bit_rate        = t->t_props.tp_vbitrate * 1000;
-        octx->rc_max_rate     = ceil(octx->bit_rate * 1.25);
-        octx->rc_buffer_size  = octx->rc_max_rate * 3;
-        // force-cfr=1 is needed for correct bitrate calculation (tune "zerolatency" also sets this)
-        av_dict_set(&opts,      "x264opts", "force-cfr=1", 0);
-        // use gop size of 5 seconds
-        octx->gop_size       *= 5;
-      }
-
-      break;
-
-    case SCT_HEVC:
-      octx->pix_fmt        = AV_PIX_FMT_YUV420P;
-      octx->flags         |= CODEC_FLAG_GLOBAL_HEADER;
-
-      // on all hardware ultrafast (or maybe superfast) should be safe
-      // select preset according to system performance
-      av_dict_set(&opts, "preset",  t->t_props.tp_vcodec_preset, 0);
-      tvhinfo(LS_TRANSCODE, "%04X: Using preset %s", shortid(t), t->t_props.tp_vcodec_preset);
-
-      // disables encoder features which tend to be bottlenecks for the decoder/player
-      av_dict_set(&opts, "tune",   "fastdecode", 0);
-
-      if (t->t_props.tp_vbitrate < 64) {
-        // encode with specified quality
-        // valid values for crf are 1-51, smaller means better quality
-        // use 18 as default
-        av_dict_set_int__(&opts, "crf", t->t_props.tp_vbitrate == 0 ? 18 : MIN(51, t->t_props.tp_vbitrate), 0);
-
-        // the following is equivalent to tune=zerolatency for presets: ultra/superfast
-        av_dict_set(&opts, "x265-params", "bframes=0",        0);
-        av_dict_set(&opts, "x265-params", ":rc-lookahead=0",  AV_DICT_APPEND);
-        av_dict_set(&opts, "x265-params", ":scenecut=0",      AV_DICT_APPEND);
-        av_dict_set(&opts, "x265-params", ":frame-threads=1", AV_DICT_APPEND);
-      } else {
-        int bitrate, maxrate, bufsize;
-        bitrate = (t->t_props.tp_vbitrate > max_bitrate) ? max_bitrate : t->t_props.tp_vbitrate;
-        maxrate = ceil(bitrate * 1.25);
-        bufsize = maxrate * 3;
-
-        tvhdebug(LS_TRANSCODE, "tuning HEVC encoder for ABR rate control, "
-                 "bitrate: %dkbps, vbv-bufsize: %dkbits, vbv-maxrate: %dkbps",
-                 bitrate, bufsize, maxrate);
-
-        // this is the same as setting --bitrate=bitrate
-        octx->bit_rate = bitrate * 1000;
-
-        av_dict_set(&opts,       "x265-params", "vbv-bufsize=",  0);
-        av_dict_set_int__(&opts, "x265-params", bufsize,         AV_DICT_APPEND);
-        av_dict_set(&opts,       "x265-params", ":vbv-maxrate=", AV_DICT_APPEND);
-        av_dict_set_int__(&opts, "x265-params", maxrate,         AV_DICT_APPEND);
-        av_dict_set(&opts,       "x265-params", ":strict-cbr=1", AV_DICT_APPEND);
-      }
-      // reduce key frame interface for live streaming
-      av_dict_set(&opts, "x265-params", ":keyint=49:min-keyint=15", AV_DICT_APPEND);
-
-      break;
-
-    default:
-      break;
-    }
-
-    if (avcodec_open2(octx, ocodec, &opts) < 0) {
-      tvherror(LS_TRANSCODE, "%04X: Unable to open %s encoder",
-               shortid(t), ocodec->name);
-      transcoder_stream_invalidate(ts);
-      goto cleanup;
-    }
-
-    if (create_video_filter(vs, t, ictx, octx)) {
-      tvherror(LS_TRANSCODE, "%04X: Video filter creation failed",
-               shortid(t));
-      transcoder_stream_invalidate(ts);
-      goto cleanup;
-    }
-  }
-
-  /* push decoded frame into filter chain */
-  if (av_buffersrc_add_frame(vs->flt_bufsrcctx, vs->vid_dec_frame) < 0) {
-    tvherror(LS_TRANSCODE, "%04X: filter input error", shortid(t));
-    transcoder_stream_invalidate(ts);
-    goto cleanup;
-  }
-
-  /* and pull out a filtered frame */
-  got_output = 0;
-  while (1) {
-       ret = av_buffersink_get_frame(vs->flt_bufsinkctx, vs->vid_enc_frame);
-       if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
-               break;
-       if (ret < 0) {
-               tvherror(LS_TRANSCODE, "%04X: filter output error", shortid(t));
-               transcoder_stream_invalidate(ts);
-               goto cleanup;
-       }
-
-       vs->vid_enc_frame->format  = octx->pix_fmt;
-       vs->vid_enc_frame->width   = octx->width;
-       vs->vid_enc_frame->height  = octx->height;
-
-       vs->vid_enc_frame->pkt_pts = vs->vid_dec_frame->pkt_pts;
-       vs->vid_enc_frame->pkt_dts = vs->vid_dec_frame->pkt_dts;
-
-       if (vs->vid_dec_frame->reordered_opaque != AV_NOPTS_VALUE)
-               vs->vid_enc_frame->pts = vs->vid_dec_frame->reordered_opaque;
-
-       else if (ictx->coded_frame && ictx->coded_frame->pts != AV_NOPTS_VALUE)
-               vs->vid_enc_frame->pts = vs->vid_dec_frame->pts;
-
-       ret = avcodec_encode_video2(octx, &packet2, vs->vid_enc_frame, &got_output);
-       if (ret < 0) {
-               tvherror(LS_TRANSCODE, "%04X: Error encoding frame", shortid(t));
-               transcoder_stream_invalidate(ts);
-               goto cleanup;
-       }
-       av_frame_unref(vs->vid_enc_frame);
-  }
-
-  if (got_output)
-    send_video_packet(t, ts, pkt, &packet2, octx);
-
- cleanup:
-  if (got_ref)
-    av_frame_unref(vs->vid_dec_frame);
-
-  av_free_packet(&packet2);
-
-  av_free_packet(&packet);
-
-  if(opts)
-    av_dict_free(&opts);
-
-  pkt_ref_dec(pkt);
-}
-
-
-/**
- *
- */
-static void
-transcoder_packet(transcoder_t *t, th_pkt_t *pkt)
-{
-  transcoder_stream_t *ts;
-  streaming_message_t *sm;
-
-  LIST_FOREACH(ts, &t->t_stream_list, ts_link) {
-    if (pkt->pkt_componentindex == ts->ts_index) {
-      if (pkt->pkt_payload) {
-        ts->ts_handle_pkt(t, ts, pkt);
-      } else {
-        sm = streaming_msg_create_pkt(pkt);
-        streaming_target_deliver2(ts->ts_target, sm);
-        pkt_ref_dec(pkt);
-      }
-      return;
-    }
-  }
-  pkt_ref_dec(pkt);
-}
-
-
-/**
- *
- */
-static void
-transcoder_destroy_stream(transcoder_t *t, transcoder_stream_t *ts)
-{
-  if (ts->ts_input_gh)
-    pktbuf_ref_dec(ts->ts_input_gh);
-  free(ts);
-}
-
-
-/**
- *
- */
-static int
-transcoder_init_stream(transcoder_t *t, streaming_start_component_t *ssc)
-{
-  transcoder_stream_t *ts = calloc(1, sizeof(transcoder_stream_t));
-
-  ts->ts_index      = ssc->ssc_index;
-  ts->ts_type       = ssc->ssc_type;
-  ts->ts_target     = t->t_output;
-  ts->ts_handle_pkt = transcoder_stream_packet;
-  ts->ts_destroy    = transcoder_destroy_stream;
-
-  LIST_INSERT_HEAD(&t->t_stream_list, ts, ts_link);
-
-  if(ssc->ssc_gh) {
-    pktbuf_ref_inc(ssc->ssc_gh);
-    ts->ts_input_gh = ssc->ssc_gh;
-    pktbuf_ref_inc(ssc->ssc_gh);
-  }
-
-  tvhinfo(LS_TRANSCODE, "%04X: %d:%s ==> Passthrough",
-         shortid(t), ssc->ssc_index,
-         streaming_component_type2txt(ssc->ssc_type));
-
-  return 1;
-}
-
-
-/**
- *
- */
-static void
-transcoder_destroy_subtitle(transcoder_t *t, transcoder_stream_t *ts)
-{
-  subtitle_stream_t *ss = (subtitle_stream_t*)ts;
-
-  if(ss->sub_ictx) {
-    av_freep(&ss->sub_ictx->extradata);
-    ss->sub_ictx->extradata_size = 0;
-    avcodec_close(ss->sub_ictx);
-    av_free(ss->sub_ictx);
-  }
-
-  if(ss->sub_octx) {
-    avcodec_close(ss->sub_octx);
-    av_free(ss->sub_octx);
-  }
-
-  transcoder_destroy_stream(t, ts);
-}
-
-
-/**
- *
- */
-static int
-transcoder_init_subtitle(transcoder_t *t, streaming_start_component_t *ssc)
-{
-  subtitle_stream_t *ss;
-  AVCodec *icodec, *ocodec;
-  transcoder_props_t *tp = &t->t_props;
-  int sct;
-
-  if (tp->tp_scodec[0] == '\0')
-    return 0;
-
-  else if (!strcmp(tp->tp_scodec, "copy"))
-    return transcoder_init_stream(t, ssc);
-
-  else if (!(icodec = transcoder_get_decoder(t, ssc->ssc_type)))
-    return transcoder_init_stream(t, ssc);
-
-  else if (!(ocodec = transcoder_get_encoder(t, tp->tp_scodec)))
-    return transcoder_init_stream(t, ssc);
-
-  sct = codec_id2streaming_component_type(ocodec->id);
-
-  if (sct == ssc->ssc_type)
-    return transcoder_init_stream(t, ssc);
-
-  ss = calloc(1, sizeof(subtitle_stream_t));
-
-  ss->ts_index      = ssc->ssc_index;
-  ss->ts_type       = sct;
-  ss->ts_target     = t->t_output;
-  ss->ts_handle_pkt = transcoder_stream_subtitle;
-  ss->ts_destroy    = transcoder_destroy_subtitle;
-  if (ssc->ssc_gh) {
-    ss->ts_input_gh = ssc->ssc_gh;
-    pktbuf_ref_inc(ssc->ssc_gh);
-  }
-
-  ss->sub_icodec = icodec;
-  ss->sub_ocodec = ocodec;
-
-  ss->sub_ictx = avcodec_alloc_context3_tvh(icodec);
-  ss->sub_octx = avcodec_alloc_context3_tvh(ocodec);
-
-  LIST_INSERT_HEAD(&t->t_stream_list, (transcoder_stream_t*)ss, ts_link);
-
-  tvhinfo(LS_TRANSCODE, "%04X: %d:%s ==> %s (%s)",
-         shortid(t), ssc->ssc_index,
-         streaming_component_type2txt(ssc->ssc_type),
-         streaming_component_type2txt(ss->ts_type),
-         ocodec->name);
-
-  ssc->ssc_type = sct;
-  ssc->ssc_gh = NULL;
-
-  return 1;
-}
-
-
-/**
- *
- */
-static void
-transcoder_destroy_audio(transcoder_t *t, transcoder_stream_t *ts)
-{
-  audio_stream_t *as = (audio_stream_t*)ts;
-
-  if(as->aud_ictx) {
-    av_freep(&as->aud_ictx->extradata);
-    as->aud_ictx->extradata_size = 0;
-    avcodec_close(as->aud_ictx);
-    av_free(as->aud_ictx);
-  }
-
-  if(as->aud_octx) {
-    avcodec_close(as->aud_octx);
-    av_free(as->aud_octx);
-  }
-
-  if ((as->resample_context) && as->resample_is_open )
-      avresample_close(as->resample_context);
-  avresample_free(&as->resample_context);
-
-  av_audio_fifo_free(as->fifo);
-
-  transcoder_destroy_stream(t, ts);
-}
-
-
-/**
- *
- */
-static int
-transcoder_init_audio(transcoder_t *t, streaming_start_component_t *ssc)
-{
-  audio_stream_t *as;
-  transcoder_stream_t *ts;
-  AVCodec *icodec, *ocodec;
-  transcoder_props_t *tp = &t->t_props;
-  int sct;
-
-  if (tp->tp_acodec[0] == '\0')
-    return 0;
-
-  else if (!strcmp(tp->tp_acodec, "copy"))
-    return transcoder_init_stream(t, ssc);
-
-  else if (!(icodec = transcoder_get_decoder(t, ssc->ssc_type)))
-    return transcoder_init_stream(t, ssc);
-
-  else if (!(ocodec = transcoder_get_encoder(t, tp->tp_acodec)))
-    return transcoder_init_stream(t, ssc);
-
-  LIST_FOREACH(ts, &t->t_stream_list, ts_link)
-    if (SCT_ISAUDIO(ts->ts_type))
-       return 0;
-
-  sct = codec_id2streaming_component_type(ocodec->id);
-
-  // Don't transcode to identical output codec unless the streaming profile specifies a bitrate limiter.
-  if (sct == ssc->ssc_type && t->t_props.tp_abitrate < 16) {
-    return transcoder_init_stream(t, ssc);
-  }
-
-  as = calloc(1, sizeof(audio_stream_t));
-
-  as->ts_index      = ssc->ssc_index;
-  as->ts_type       = sct;
-  as->ts_target     = t->t_output;
-  as->ts_handle_pkt = transcoder_stream_audio;
-  as->ts_destroy    = transcoder_destroy_audio;
-  if (ssc->ssc_gh) {
-    as->ts_input_gh = ssc->ssc_gh;
-    pktbuf_ref_inc(ssc->ssc_gh);
-  }
-
-  as->aud_icodec = icodec;
-  as->aud_ocodec = ocodec;
-
-  as->aud_ictx = avcodec_alloc_context3_tvh(icodec);
-  as->aud_octx = avcodec_alloc_context3_tvh(ocodec);
-
-  LIST_INSERT_HEAD(&t->t_stream_list, (transcoder_stream_t*)as, ts_link);
-
-  tvhinfo(LS_TRANSCODE, "%04X: %d:%s ==> %s (%s)",
-         shortid(t), ssc->ssc_index,
-         streaming_component_type2txt(ssc->ssc_type),
-         streaming_component_type2txt(as->ts_type),
-         ocodec->name);
-
-  ssc->ssc_type     = sct;
-  ssc->ssc_gh       = NULL;
-
-  if(tp->tp_channels > 0)
-    as->aud_channels = tp->tp_channels;
-  if(tp->tp_abitrate > 0)
-    as->aud_bitrate = tp->tp_abitrate * 1000;
-
-  as->resample_context = NULL;
-  as->fifo = NULL;
-  as->resample = 0;
-
-  return 1;
-}
-
-
-/**
- *
- */
-static void
-transcoder_destroy_video(transcoder_t *t, transcoder_stream_t *ts)
-{
-  video_stream_t *vs = (video_stream_t*)ts;
-
-  if(vs->vid_ictx) {
-    av_freep(&vs->vid_ictx->extradata);
-    vs->vid_ictx->extradata_size = 0;
-    avcodec_close(vs->vid_ictx);
-    av_free(vs->vid_ictx);
-  }
-
-  if(vs->vid_octx) {
-    avcodec_close(vs->vid_octx);
-    av_free(vs->vid_octx);
-  }
-
-  if(vs->vid_dec_frame)
-    av_free(vs->vid_dec_frame);
-
-  if(vs->vid_enc_frame)
-    av_free(vs->vid_enc_frame);
-
-  if (vs->vid_first_pkt)
-    pkt_ref_dec(vs->vid_first_pkt);
-
-  if (vs->flt_graph) {
-    avfilter_graph_free(&vs->flt_graph);
-    vs->flt_graph = NULL;
-  }
-
-  transcoder_destroy_stream(t, ts);
-}
-
-
-/**
- *
- */
-static int
-transcoder_init_video(transcoder_t *t, streaming_start_component_t *ssc)
-{
-  video_stream_t *vs;
-  AVCodec *icodec, *ocodec;
-  transcoder_props_t *tp = &t->t_props;
-  int sct;
-  char *str, *token, *saveptr, codec_list[sizeof(tp->tp_src_vcodec)];
-  int codec_match=0;
-
-  strncpy(codec_list, tp->tp_src_vcodec, sizeof(tp->tp_src_vcodec)-1);
-
-  tvhtrace(LS_TRANSCODE, "src_vcodec=\"%s\" ssc_type=%d (%s)\n",
-                 tp->tp_src_vcodec,
-                 ssc->ssc_type,
-                 streaming_component_type2txt(ssc->ssc_type));
-
-  if (codec_list[0] != '\0') {
-    for (str=codec_list; ; str = NULL) {
-      token = strtok_r(str," ,|;" , &saveptr);
-      if (token == NULL)
-        break; //no match found, use profile settings
-      if(!strcasecmp(token, streaming_component_type2txt(ssc->ssc_type))) { //match found
-       codec_match=1;
-       break;
-      }
-    }
-    if (!codec_match)
-      return transcoder_init_stream(t, ssc); //copy codec
-  }
-
-
-  if (tp->tp_vcodec[0] == '\0')
-    return 0;
-
-  else if (!strcmp(tp->tp_vcodec, "copy"))
-    return transcoder_init_stream(t, ssc);
-
-  else if (!(icodec = transcoder_get_decoder(t, ssc->ssc_type)))
-    return transcoder_init_stream(t, ssc);
-
-  else if (!(ocodec = transcoder_get_encoder(t, tp->tp_vcodec)))
-    return transcoder_init_stream(t, ssc);
-
-  sct = codec_id2streaming_component_type(ocodec->id);
-
-  vs = calloc(1, sizeof(video_stream_t));
-
-  vs->ts_index      = ssc->ssc_index;
-  vs->ts_type       = sct;
-  vs->ts_target     = t->t_output;
-  vs->ts_handle_pkt = transcoder_stream_video;
-  vs->ts_destroy    = transcoder_destroy_video;
-  if (ssc->ssc_gh) {
-    vs->ts_input_gh = ssc->ssc_gh;
-    pktbuf_ref_inc(ssc->ssc_gh);
-  }
-
-  vs->vid_icodec = icodec;
-  vs->vid_ocodec = ocodec;
-
-  vs->vid_ictx = avcodec_alloc_context3_tvh(icodec);
-  vs->vid_octx = avcodec_alloc_context3_tvh(ocodec);
-
-  if (t->t_props.tp_nrprocessors)
-    vs->vid_octx->thread_count = t->t_props.tp_nrprocessors;
-
-  vs->vid_dec_frame = av_frame_alloc();
-  vs->vid_enc_frame = av_frame_alloc();
-
-  av_frame_unref(vs->vid_dec_frame);
-  av_frame_unref(vs->vid_enc_frame);
-
-  vs->flt_graph = NULL;                /* allocated in packet processor */
-
-  LIST_INSERT_HEAD(&t->t_stream_list, (transcoder_stream_t*)vs, ts_link);
-
-
-  if(tp->tp_resolution > 0) {
-    vs->vid_height = MIN(tp->tp_resolution, ssc->ssc_height);
-    vs->vid_height += vs->vid_height & 1; /* Must be even */
-
-    double aspect = (double)ssc->ssc_width / ssc->ssc_height;
-    vs->vid_width = vs->vid_height * aspect;
-    vs->vid_width += vs->vid_width & 1;   /* Must be even */
-  } else {
-    vs->vid_height = ssc->ssc_height;
-    vs->vid_width  = ssc->ssc_width;
-  }
-
-  tvhinfo(LS_TRANSCODE, "%04X: %d:%s %dx%d ==> %s %dx%d (%s)",
-          shortid(t),
-          ssc->ssc_index,
-          streaming_component_type2txt(ssc->ssc_type),
-          ssc->ssc_width,
-          ssc->ssc_height,
-          streaming_component_type2txt(vs->ts_type),
-          vs->vid_width,
-          vs->vid_height,
-          ocodec->name);
-
-  ssc->ssc_type   = sct;
-  ssc->ssc_width  = vs->vid_width;
-  ssc->ssc_height = vs->vid_height;
-  ssc->ssc_gh     = NULL;
-
-  return 1;
-}
-
-
-/**
- * Figure out how many streams we will use.
- */
-static int
-transcoder_calc_stream_count(transcoder_t *t, streaming_start_t *ss) {
-  int i = 0;
-  int video = 0;
-  int audio = 0;
-  int subtitle = 0;
-  streaming_start_component_t *ssc = NULL;
-
-  for (i = 0; i < ss->ss_num_components; i++) {
-    ssc = &ss->ss_components[i];
-
-    if (ssc->ssc_disabled)
-      continue;
-
-    if (SCT_ISVIDEO(ssc->ssc_type)) {
-      if (t->t_props.tp_vcodec[0] == '\0')
-       video = 0;
-      else if (!strcmp(t->t_props.tp_vcodec, "copy"))
-       video++;
-      else
-       video = 1;
-
-    } else if (SCT_ISAUDIO(ssc->ssc_type)) {
-      if (t->t_props.tp_acodec[0] == '\0')
-       audio = 0;
-      else if (!strcmp(t->t_props.tp_acodec, "copy"))
-       audio++;
-      else
-       audio = 1;
-
-    } else if (SCT_ISSUBTITLE(ssc->ssc_type)) {
-      if (t->t_props.tp_scodec[0] == '\0')
-       subtitle = 0;
-      else if (!strcmp(t->t_props.tp_scodec, "copy"))
-       subtitle++;
-      else
-       subtitle = 1;
-    }
-  }
-
-  tvhtrace(LS_TRANSCODE, "%04X: transcoder_calc_stream_count=%d (video=%d, audio=%d, subtitle=%d)",
-           shortid(t), (video + audio + subtitle), video, audio, subtitle);
-
-
-  return (video + audio + subtitle);
-}
-
-
-/**
- *
- */
-static streaming_start_t *
-transcoder_start(transcoder_t *t, streaming_start_t *src)
-{
-  int i, j, n, rc;
-  streaming_start_t *ss;
-  transcoder_props_t *tp = &t->t_props;
-  char* requested_lang;
-
-  n = transcoder_calc_stream_count(t, src);
-  ss = calloc(1, (sizeof(streaming_start_t) +
-                 sizeof(streaming_start_component_t) * n));
-
-  ss->ss_refcount       = 1;
-  ss->ss_num_components = n;
-  ss->ss_pcr_pid        = src->ss_pcr_pid;
-  ss->ss_pmt_pid        = src->ss_pmt_pid;
-  service_source_info_copy(&ss->ss_si, &src->ss_si);
-
-  requested_lang = tp->tp_language;
-
-  if (requested_lang[0] != '\0')
-  {
-      for (i = 0; i < src->ss_num_components; i++) {
-        streaming_start_component_t *ssc_src = &src->ss_components[i];
-        if (SCT_ISAUDIO(ssc_src->ssc_type) && !strcmp(tp->tp_language, ssc_src->ssc_lang))
-          break;
-      }
-
-      if (i == src->ss_num_components)
-      {
-        tvhinfo(LS_TRANSCODE, "Could not find requestd lang [%s] in stream, using first one", tp->tp_language);
-        requested_lang[0] = '\0';
-      }
-  }
-
-  for (i = j = 0; i < src->ss_num_components && j < n; i++) {
-    streaming_start_component_t *ssc_src = &src->ss_components[i];
-    streaming_start_component_t *ssc = &ss->ss_components[j];
-
-    if (ssc_src->ssc_disabled)
-      continue;
-
-    *ssc = *ssc_src;
-
-    if (SCT_ISVIDEO(ssc->ssc_type))
-      rc = transcoder_init_video(t, ssc);
-    else if (SCT_ISAUDIO(ssc->ssc_type) && (requested_lang[0] == '\0' || !strcmp(requested_lang, ssc->ssc_lang)))
-      rc = transcoder_init_audio(t, ssc);
-    else if (SCT_ISSUBTITLE(ssc->ssc_type))
-      rc = transcoder_init_subtitle(t, ssc);
-    else
-      rc = 0;
-
-    if(!rc)
-      tvhinfo(LS_TRANSCODE, "%04X: %d:%s ==> Filtered",
-             shortid(t), ssc->ssc_index,
-             streaming_component_type2txt(ssc->ssc_type));
-    else
-      j++;
-  }
-
-  return ss;
-}
-
-
-/**
- *
- */
-static void
-transcoder_stop(transcoder_t *t)
-{
-  transcoder_stream_t *ts;
-
-  while ((ts = LIST_FIRST(&t->t_stream_list))) {
-    LIST_REMOVE(ts, ts_link);
-
-    if (ts->ts_destroy)
-      ts->ts_destroy(t, ts);
-  }
-}
-
-
-/**
- *
- */
-static void
-transcoder_input(void *opaque, streaming_message_t *sm)
-{
-  transcoder_t *t = opaque;
-  streaming_start_t *ss;
-
-  switch (sm->sm_type) {
-  case SMT_PACKET:
-    transcoder_packet(t, sm->sm_data);
-    sm->sm_data = NULL;
-    streaming_msg_free(sm);
-    break;
-
-  case SMT_START:
-    transcoder_stop(t);
-    ss = transcoder_start(t, sm->sm_data);
-    streaming_start_unref(sm->sm_data);
-    sm->sm_data = ss;
-
-    streaming_target_deliver2(t->t_output, sm);
-    break;
-
-  case SMT_STOP:
-    transcoder_stop(t);
-    /* Fallthrough */
-
-  case SMT_GRACE:
-  case SMT_SPEED:
-  case SMT_SKIP:
-  case SMT_TIMESHIFT_STATUS:
-  case SMT_EXIT:
-  case SMT_SERVICE_STATUS:
-  case SMT_SIGNAL_STATUS:
-  case SMT_DESCRAMBLE_INFO:
-  case SMT_NOSTART:
-  case SMT_NOSTART_WARN:
-  case SMT_MPEGTS:
-    streaming_target_deliver2(t->t_output, sm);
-    break;
-  }
-}
-
-static htsmsg_t *
-transcoder_input_info(void *opaque, htsmsg_t *list)
-{
-  transcoder_t *t = opaque;
-  streaming_target_t *st = t->t_output;
-  htsmsg_add_str(list, NULL, "transcoder input");
-  return st->st_ops.st_info(st->st_opaque, list);;
-}
-
-static streaming_ops_t transcoder_input_ops = {
-  .st_cb   = transcoder_input,
-  .st_info = transcoder_input_info
-};
-
-
-
-/**
- *
- */
-streaming_target_t *
-transcoder_create(streaming_target_t *output)
-{
-  static uint32_t transcoder_id = 0;
-  transcoder_t *t = calloc(1, sizeof(transcoder_t));
-
-  t->t_id = ++transcoder_id;
-  if (!t->t_id) t->t_id = ++transcoder_id;
-  t->t_output = output;
-
-  streaming_target_init(&t->t_input, &transcoder_input_ops, t, 0);
-
-  return &t->t_input;
-}
-
-
-/**
- *
- */
-void
-transcoder_set_properties(streaming_target_t *st,
-                         transcoder_props_t *props)
-{
-  transcoder_t *t = (transcoder_t *)st;
-  transcoder_props_t *tp = &t->t_props;
-
-  strncpy(tp->tp_vcodec, props->tp_vcodec, sizeof(tp->tp_vcodec)-1);
-  strncpy(tp->tp_vcodec_preset, props->tp_vcodec_preset, sizeof(tp->tp_vcodec_preset)-1);
-  strncpy(tp->tp_acodec, props->tp_acodec, sizeof(tp->tp_acodec)-1);
-  strncpy(tp->tp_scodec, props->tp_scodec, sizeof(tp->tp_scodec)-1);
-  tp->tp_channels   = props->tp_channels;
-  tp->tp_vbitrate   = props->tp_vbitrate;
-  tp->tp_abitrate   = props->tp_abitrate;
-  tp->tp_resolution = props->tp_resolution;
-
-  memcpy(tp->tp_language, props->tp_language, 4);
-
-  strncpy(tp->tp_src_vcodec, props->tp_src_vcodec, sizeof(tp->tp_src_vcodec)-1);
-}
-
-
-/**
- *
- */
-void
-transcoder_destroy(streaming_target_t *st)
-{
-  transcoder_t *t = (transcoder_t *)st;
-
-  transcoder_stop(t);
-  free(t);
-}
-
-
-/**
- *
- */
-htsmsg_t *
-transcoder_get_capabilities(int experimental)
-{
-  AVCodec *p = NULL;
-  streaming_component_type_t sct;
-  htsmsg_t *array = htsmsg_create_list(), *m;
-  char buf[128];
-
-  while ((p = av_codec_next(p))) {
-
-    if (!libav_is_encoder(p))
-      continue;
-
-    if (!WORKING_ENCODER(p->id))
-      continue;
-
-    if (((p->capabilities & CODEC_CAP_EXPERIMENTAL) && !experimental) ||
-        (p->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)) {
-      continue;
-    }
-
-    sct = codec_id2streaming_component_type(p->id);
-    if (sct == SCT_NONE || sct == SCT_UNKNOWN)
-      continue;
-
-    m = htsmsg_create_map();
-    htsmsg_add_s32(m, "type", sct);
-    htsmsg_add_u32(m, "id", p->id);
-    htsmsg_add_str(m, "name", p->name);
-    snprintf(buf, sizeof(buf), "%s%s",
-             p->long_name ?: "",
-             (p->capabilities & CODEC_CAP_EXPERIMENTAL) ?
-               " (Experimental)" : "");
-    if (buf[0] != '\0')
-      htsmsg_add_str(m, "long_name", buf);
-    htsmsg_add_msg(array, NULL, m);
-  }
-  return array;
-}
-
-
-/*
- *
- */
-void transcoding_init(void)
-{
-}
diff --git a/src/plumbing/transcoding.h b/src/plumbing/transcoding.h
deleted file mode 100644 (file)
index 3fc42b0..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/**
- *  Transcoding
- *  Copyright (C) 2013 John Törnblom
- *
- *  This program is free software: you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation, either version 3 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 General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
- */
-#pragma once
-
-#include "tvheadend.h"
-#include "htsmsg.h"
-
-typedef struct transcoder_prop {
-  char     tp_vcodec[32];
-  char     tp_vcodec_preset[32];
-  char     tp_acodec[32];
-  char     tp_scodec[32];
-
-  int8_t   tp_channels;
-  int32_t  tp_vbitrate;
-  int32_t  tp_abitrate;
-  char     tp_language[4];
-  int32_t  tp_resolution;
-
-  long     tp_nrprocessors;
-  char     tp_src_vcodec[128];
-} transcoder_props_t;
-
-extern uint32_t transcoding_enabled;
-
-streaming_target_t *transcoder_create (streaming_target_t *output);
-void                transcoder_destroy(streaming_target_t *tr);
-
-htsmsg_t *transcoder_get_capabilities(int experimental);
-void transcoder_set_properties  (streaming_target_t *tr,
-                                transcoder_props_t *prop);
-
-
-void transcoding_init(void);
index f01f3b5a40285e478084c9aaf42c79a8115d641e..33e2bdf1bf14fa2d9903091a57eaac2a5d2bce7f 100644 (file)
@@ -379,9 +379,7 @@ recover_pts(tsfix_t *tf, tfstream_t *tfs, th_pkt_t *pkt)
   int total;
 
   pktref_enqueue(&tf->tf_ptsq, pkt);
-
   while((pkt = pktref_get_first(&tf->tf_ptsq)) != NULL) {
-    
     tfs = tfs_find(tf, pkt);
 
     switch(tfs->tfs_type) {
@@ -473,7 +471,7 @@ tsfix_input_packet(tsfix_t *tf, streaming_message_t *sm)
   tfstream_t *tfs = tfs_find(tf, pkt), *tfs2;
   streaming_msg_free(sm);
   int64_t diff, diff2, threshold;
-  
+
   if(tfs == NULL || mclk() < tf->tf_start_time) {
     pkt_ref_dec(pkt);
     return;
index 612e64d56404b5fb48dfe7a75a85ad727821401c..abc53c53dcc0bb1294cc31239c180535c18c3e01 100644 (file)
@@ -25,7 +25,8 @@
 #include "plumbing/globalheaders.h"
 #if ENABLE_LIBAV
 #include "lang_codes.h"
-#include "plumbing/transcoding.h"
+#include "transcoding/transcode.h"
+#include "transcoding/codec.h"
 #endif
 #if ENABLE_TIMESHIFT
 #include "timeshift.h"
@@ -184,7 +185,7 @@ profile_class_delete(idnode_t *self)
 }
 
 static uint32_t
-profile_class_enabled_opts(void *o)
+profile_class_enabled_opts(void *o, uint32_t opts)
 {
   profile_t *pro = o;
   uint32_t r = 0;
@@ -232,7 +233,7 @@ profile_class_default_set(void *o, const void *v)
 }
 
 static uint32_t
-profile_class_name_opts(void *o)
+profile_class_name_opts(void *o, uint32_t opts)
 {
   profile_t *pro = o;
   uint32_t r = 0;
@@ -2002,38 +2003,15 @@ profile_libav_mp4_builder(void)
  *  Transcoding + packet-like muxers
  */
 
-static int profile_transcode_experimental_codecs = 1;
-
 typedef struct profile_transcode {
   profile_t;
-  int      pro_mc;
-  uint32_t pro_resolution;
-  uint32_t pro_channels;
-  uint32_t pro_vbitrate;
-  uint32_t pro_abitrate;
-  char    *pro_language;
-  char    *pro_vcodec;
-  char    *pro_vcodec_preset;
-  char    *pro_acodec;
-  char    *pro_scodec;
-  char    *pro_src_vcodec;
+  int   pro_mc;
+  char *pro_vcodec;
+  char *pro_acodec;
+  char *pro_scodec;
 } profile_transcode_t;
 
 
-static htsmsg_t *
-profile_class_src_vcodec_list ( void *o, const char *lang )
-{
-  static const struct strtab_str tab[] = {
-    { N_("Any"),               "" },
-    { "MPEG2VIDEO",            "MPEG2VIDEO" },
-    { "H264",                  "H264" },
-    { "VP8",                   "VP8" },
-    { "HEVC",                  "HEVC" },
-    { "VP9",                   "VP9" },
-  };
-  return strtab2htsmsg_str(tab, 1, lang);
-}
-
 static htsmsg_t *
 profile_class_mc_list ( void *o, const char *lang )
 {
@@ -2051,151 +2029,73 @@ profile_class_mc_list ( void *o, const char *lang )
   return strtab2htsmsg(tab, 1, lang);
 }
 
-static htsmsg_t *
-profile_class_channels_list ( void *o, const char *lang )
+static int
+htsmsg_add_entry(htsmsg_t *list, const char *key, const char *val)
 {
-  static const struct strtab tab[] = {
-    { N_("Copy layout"),                   0 },
-    { N_("Mono"),                          1 },
-    { N_("Stereo"),                        2 },
-    { N_("Surround (2 front, rear mono)"), 3 },
-    { N_("Quad (4.0)"),                    4 },
-    { N_("5.0"),                           5 },
-    { N_("5.1"),                           6 },
-    { N_("6.1"),                           7 },
-    { N_("7.1"),                           8 }
-  };
-  return strtab2htsmsg(tab, 1, lang);
+  htsmsg_t *map = NULL;
+
+  if ((map = htsmsg_create_map())) {
+    htsmsg_add_str(map, "key", key);
+    htsmsg_add_str(map, "val", val);
+    htsmsg_add_msg(list, NULL, map);
+    return 0;
+  }
+  return -1;
 }
 
 static htsmsg_t *
-profile_class_language_list(void *o, const char *lang)
+profile_class_codec_profile_make_list(const char *lang)
 {
-  htsmsg_t *l = htsmsg_create_list();
-  const lang_code_t *lc = lang_codes;
-  char buf[128];
+  htsmsg_t *list = NULL;
 
-  while (lc->code2b) {
-    htsmsg_t *e;
-    if (!strcmp(lc->code2b, "und")) {
-      e = htsmsg_create_key_val("", tvh_gettext_lang(lang, N_("Use original")));
-    } else {
-      snprintf(buf, sizeof(buf), "%s (%s)", lc->desc, lc->code2b);
-      buf[sizeof(buf)-1] = '\0';
-      e = htsmsg_create_key_val(lc->code2b, buf);
+  if ((list = htsmsg_create_list())) {
+    if (htsmsg_add_entry(list, "", tvh_gettext_lang(lang, N_("Disabled"))) ||
+      htsmsg_add_entry(list, "copy", tvh_gettext_lang(lang, N_("Copy")))) {
+      htsmsg_destroy(list);
+      list = NULL;
     }
-    htsmsg_add_msg(l, NULL, e);
-    lc++;
   }
-  return l;
-}
-
-static inline int
-profile_class_check_sct(htsmsg_t *c, int sct)
-{
-  htsmsg_field_t *f;
-  int64_t x;
-  HTSMSG_FOREACH(f, c)
-    if (!htsmsg_field_get_s64(f, &x))
-      if (x == sct)
-        return 1;
-  return 0;
+  return list;
 }
 
 static htsmsg_t *
-profile_class_codec_list(int (*check)(int sct), const char *lang)
-{
-  htsmsg_t *l = htsmsg_create_list(), *e, *c, *m;
-  htsmsg_field_t *f;
-  const char *s, *s2;
-  char buf[128];
-  int sct;
-
-  e = htsmsg_create_key_val("", tvh_gettext_lang(lang, N_("Do not use")));
-  htsmsg_add_msg(l, NULL, e);
-  e = htsmsg_create_key_val("copy", tvh_gettext_lang(lang, N_("Copy codec type")));
-  htsmsg_add_msg(l, NULL, e);
-  c = transcoder_get_capabilities(profile_transcode_experimental_codecs);
-  HTSMSG_FOREACH(f, c) {
-    if (!(m = htsmsg_field_get_map(f)))
-      continue;
-    if (htsmsg_get_s32(m, "type", &sct))
-      continue;
-    if (!check(sct))
-      continue;
-    if (!(s = htsmsg_get_str(m, "name")))
-      continue;
-    s2 = htsmsg_get_str(m, "long_name");
-    if (s2)
-      snprintf(buf, sizeof(buf), "%s: %s", s, s2);
-    else
-      snprintf(buf, sizeof(buf), "%s", s);
-    e = htsmsg_create_key_val(s, buf);
-    htsmsg_add_msg(l, NULL, e);
+profile_class_codec_profiles_list(enum AVMediaType media_type, const char *lang)
+{
+  htsmsg_t *list = NULL, *profiles = NULL, *map = NULL;
+  htsmsg_field_t *field;
+
+  if ((list = profile_class_codec_profile_make_list(lang)) &&
+      (profiles = codec_get_profiles_list(media_type))) {
+    HTSMSG_FOREACH(field, profiles) {
+      if (!(map = htsmsg_detach_submsg(field))) {
+        htsmsg_destroy(list);
+        list = NULL;
+        break;
+      }
+      htsmsg_add_msg(list, NULL, map);
+    }
+    htsmsg_destroy(profiles);
+    profiles = NULL;
   }
-  htsmsg_destroy(c);
-  return l;
-}
-
-static int
-profile_class_vcodec_sct_check(int sct)
-{
-  return SCT_ISVIDEO(sct);
-}
-
-static htsmsg_t *
-profile_class_vcodec_list(void *o, const char *lang)
-{
-  return profile_class_codec_list(profile_class_vcodec_sct_check, lang);
+  return list;
 }
 
 static htsmsg_t *
-profile_class_vcodec_preset_list(void *o, const char *lang)
-{
-  static const struct strtab_str tab[] = {
-    {N_("ultrafast: h264 / h265")                    , "ultrafast" },
-    {N_("superfast: h264 / h265")                    , "superfast" },
-    {N_("veryfast: h264 / h265 / qsv(h264)")         , "veryfast"  },
-    {N_("faster: h264 / h265 / qsv(h264)")           , "faster"    },
-    {N_("fast: h264 / h265 / qsv(h264 / h265)")      , "fast"      },
-    {N_("medium: h264 / h265 / qsv(h264 / h265)")    , "medium"    },
-    {N_("slow: h264 / h265 / qsv(h264 / h265)")      , "slow"      },
-    {N_("slower: h264 / h265 / qsv(h264)")           , "slower"    },
-    {N_("veryslow: h264 / h265 / qsv(h264)")         , "veryslow"  },
-    {N_("placebo: h264 / h265")                      , "placebo"   },
-    {N_("hq: nvenc(h264 / h265)")                    , "hq"        },
-    {N_("hp: nvenc(h264 / h265)")                    , "hp"        },
-    {N_("bd: nvenc(h264 / h265)")                    , "bd"        },
-    {N_("ll: nvenc(h264 / h265)")                    , "ll"        },
-    {N_("llhq: nvenc(h264 / h265)")                  , "llhq"      },
-    {N_("llhp: nvenc(h264 / h265)")                  , "llhp"      },
-    {N_("default: nvenc(h264 / h265)")               , "default"   }
-  };
-  return strtab2htsmsg_str(tab, 1, lang);
-}
-
-static int
-profile_class_acodec_sct_check(int sct)
+profile_class_pro_vcodec_list(void *o, const char *lang)
 {
-  return SCT_ISAUDIO(sct);
+  return profile_class_codec_profiles_list(AVMEDIA_TYPE_VIDEO, lang);
 }
 
 static htsmsg_t *
-profile_class_acodec_list(void *o, const char *lang)
+profile_class_pro_acodec_list(void *o, const char *lang)
 {
-  return profile_class_codec_list(profile_class_acodec_sct_check, lang);
-}
-
-static int
-profile_class_scodec_sct_check(int sct)
-{
-  return SCT_ISSUBTITLE(sct);
+  return profile_class_codec_profiles_list(AVMEDIA_TYPE_AUDIO, lang);
 }
 
 static htsmsg_t *
-profile_class_scodec_list(void *o, const char *lang)
+profile_class_pro_scodec_list(void *o, const char *lang)
 {
-  return profile_class_codec_list(profile_class_scodec_sct_check, lang);
+  return profile_class_codec_profiles_list(AVMEDIA_TYPE_SUBTITLE, lang);
 }
 
 const idclass_t profile_transcode_class =
@@ -2225,120 +2125,33 @@ const idclass_t profile_transcode_class =
       .list     = profile_class_mc_list,
       .group    = 1
     },
-    {
-      .type     = PT_U32,
-      .id       = "resolution",
-      .name     = N_("Resolution (height)"),
-      .desc     = N_("Vertical resolution (height) of the output video "
-                     "stream. Horizontal resolution is adjusted "
-                     "automatically to preserve aspect ratio. When set "
-                     "to 0, the input resolution is used."),
-      .off      = offsetof(profile_transcode_t, pro_resolution),
-      .def.u32  = 384,
-      .group    = 2
-    },
-    {
-      .type     = PT_U32,
-      .id       = "channels",
-      .name     = N_("Channels"),
-      .desc     = N_("Audio channel layout."),
-      .off      = offsetof(profile_transcode_t, pro_channels),
-      .def.u32  = 2,
-      .list     = profile_class_channels_list,
-      .opts     = PO_ADVANCED,
-      .group    = 2
-    },
-    {
-      .type     = PT_STR,
-      .id       = "language",
-      .name     = N_("Language"),
-      .desc     = N_("Preferred audio language."),
-      .off      = offsetof(profile_transcode_t, pro_language),
-      .list     = profile_class_language_list,
-      .opts     = PO_ADVANCED,
-      .group    = 2
-    },
-    {
-      .type     = PT_STR,
-      .id       = "src_vcodec",
-      .name     = N_("Source video codec"),
-      .desc     = N_("Transcode video only if source video codec matches. "
-                     "\"Any\" ignores source video codec checking and "
-                     "always transcodes. If no codec match is found, "
-                     "transcoding is done using the \"copy\" codec. "
-                     "if a match is found, transcode with the "
-                     "parameters in this profile. Separate codec names "
-                     "with comma."),
-      .off      = offsetof(profile_transcode_t, pro_src_vcodec),
-      .def.i    = SCT_UNKNOWN,
-      .list     = profile_class_src_vcodec_list,
-      .opts     = PO_ADVANCED,
-      .group    = 2
-    },
     {
       .type     = PT_STR,
-      .id       = "vcodec",
-      .name     = N_("Video codec"),
-      .desc     = N_("Video codec to use for the transcode. "
-                     "\"Do not use\" will disable video output."),
+      .id       = "pro_vcodec",
+      .name     = N_("Video codec profile"),
+      .desc     = N_("Select video codec profile to use for transcoding."),
       .off      = offsetof(profile_transcode_t, pro_vcodec),
-      .def.s    = "libx264",
-      .list     = profile_class_vcodec_list,
-      .opts     = PO_ADVANCED,
-      .group    = 2
-    },
-    {
-      .type     = PT_STR,
-      .id       = "vcodec_preset",
-      .name     = N_("Video codec preset"),
-      .desc     = N_("Video codec preset to use for transcoding."),
-      .off      = offsetof(profile_transcode_t, pro_vcodec_preset),
-      .def.s    = "faster",
-      .list     = profile_class_vcodec_preset_list,
-      .opts     = PO_ADVANCED,
-      .group    = 2
-    },
-    {
-      .type     = PT_U32,
-      .id       = "vbitrate",
-      .name     = N_("Video bitrate (kb/s) (0=auto)"),
-      .desc     = N_("Bitrate to use for the transcode. See Help for "
-                     "details."),
-      .off      = offsetof(profile_transcode_t, pro_vbitrate),
+      .list     = profile_class_pro_vcodec_list,
       .opts     = PO_ADVANCED,
-      .def.u32  = 0,
       .group    = 2
     },
     {
       .type     = PT_STR,
-      .id       = "acodec",
-      .name     = N_("Audio codec"),
-      .desc     = N_("Audio codec to use for the transcode. \"Do not "
-                     "use\" will disable audio output."),
+      .id       = "pro_acodec",
+      .name     = N_("Audio codec profile"),
+      .desc     = N_("Select audio codec profile to use for transcoding."),
       .off      = offsetof(profile_transcode_t, pro_acodec),
-      .def.s    = "libvorbis",
-      .list     = profile_class_acodec_list,
+      .list     = profile_class_pro_acodec_list,
       .opts     = PO_ADVANCED,
       .group    = 2
     },
-    {
-      .type     = PT_U32,
-      .id       = "abitrate",
-      .name     = N_("Audio bitrate (kb/s) (0=auto)"),
-      .desc     = N_("Audio bitrate to use for transcoding."),
-      .off      = offsetof(profile_transcode_t, pro_abitrate),
-      .opts     = PO_ADVANCED,
-      .def.u32  = 0,
-      .group    = 2
-    },
     {
       .type     = PT_STR,
-      .id       = "scodec",
-      .name     = N_("Subtitle codec"),
-      .desc     = N_("Select subtitle codec to use for transcoding."),
+      .id       = "pro_scodec",
+      .name     = N_("Subtitle codec profile"),
+      .desc     = N_("Select subtitle codec profile to use for transcoding."),
       .off      = offsetof(profile_transcode_t, pro_scodec),
-      .def.s    = "",
-      .list     = profile_class_scodec_list,
+      .list     = profile_class_pro_scodec_list,
       .opts     = PO_ADVANCED,
       .group    = 2
     },
@@ -2346,25 +2159,6 @@ const idclass_t profile_transcode_class =
   }
 };
 
-static int
-profile_transcode_resolution(profile_transcode_t *pro)
-{
-  return pro->pro_resolution == 0 ? 0 :
-         (pro->pro_resolution >= 240 ? pro->pro_resolution : 240);
-}
-
-static int
-profile_transcode_vbitrate(profile_transcode_t *pro)
-{
-  return pro->pro_vbitrate;
-}
-
-static int
-profile_transcode_abitrate(profile_transcode_t *pro)
-{
-  return pro->pro_abitrate;
-}
-
 static int
 profile_transcode_can_share(profile_chain_t *prch,
                             profile_chain_t *joiner)
@@ -2381,20 +2175,10 @@ profile_transcode_can_share(profile_chain_t *prch,
    */
   if (strcmp(pro1->pro_vcodec ?: "", pro2->pro_vcodec ?: ""))
     return 0;
-  if (strcmp(pro1->pro_vcodec_preset ?: "", pro2->pro_vcodec_preset ?: ""))
-    return 0;
   if (strcmp(pro1->pro_acodec ?: "", pro2->pro_acodec ?: ""))
     return 0;
   if (strcmp(pro1->pro_scodec ?: "", pro2->pro_scodec ?: ""))
     return 0;
-  if (profile_transcode_resolution(pro1) != profile_transcode_resolution(pro2))
-    return 0;
-  if (profile_transcode_vbitrate(pro1) != profile_transcode_vbitrate(pro2))
-    return 0;
-  if (profile_transcode_abitrate(pro1) != profile_transcode_abitrate(pro2))
-    return 0;
-  if (strcmp(pro1->pro_language ?: "", pro2->pro_language ?: ""))
-    return 0;
   return 1;
 }
 
@@ -2405,30 +2189,17 @@ profile_transcode_work(profile_chain_t *prch,
 {
   profile_sharer_t *prsh;
   profile_transcode_t *pro = (profile_transcode_t *)prch->prch_pro;
-  transcoder_props_t props;
+  const char *profiles[AVMEDIA_TYPE_NB] = { NULL };
 
   prsh = profile_sharer_find(prch);
   if (!prsh)
     goto fail;
 
-  memset(&props, 0, sizeof(props));
-  strncpy(props.tp_vcodec, pro->pro_vcodec ?: "", sizeof(props.tp_vcodec)-1);
-  strncpy(props.tp_vcodec_preset, pro->pro_vcodec_preset ?: "", sizeof(props.tp_vcodec_preset)-1);
-  strncpy(props.tp_acodec, pro->pro_acodec ?: "", sizeof(props.tp_acodec)-1);
-  strncpy(props.tp_scodec, pro->pro_scodec ?: "", sizeof(props.tp_scodec)-1);
-  props.tp_resolution = profile_transcode_resolution(pro);
-  props.tp_channels   = pro->pro_channels;
-  props.tp_vbitrate   = profile_transcode_vbitrate(pro);
-  props.tp_abitrate   = profile_transcode_abitrate(pro);
-  strncpy(props.tp_language, pro->pro_language ?: "", 3);
-
-  if (!pro->pro_src_vcodec) {
-     strcpy(props.tp_src_vcodec, "");
-  } else if(!strncasecmp("Any",pro->pro_src_vcodec,3)) {
-     strcpy(props.tp_src_vcodec, "");
-  } else {
-     strncpy(props.tp_src_vcodec, pro->pro_src_vcodec ?: "", sizeof(props.tp_src_vcodec)-1);
-  }
+  prch->prch_can_share = profile_transcode_can_share;
+
+  profiles[AVMEDIA_TYPE_VIDEO] = pro->pro_vcodec ?: "";
+  profiles[AVMEDIA_TYPE_AUDIO] = pro->pro_acodec ?: "";
+  profiles[AVMEDIA_TYPE_SUBTITLE] = pro->pro_scodec ?: "";
 
   dst = prch->prch_gh = globalheaders_create(dst);
 
@@ -2440,10 +2211,9 @@ profile_transcode_work(profile_chain_t *prch,
     goto fail;
   if (!prsh->prsh_transcoder) {
     assert(!prsh->prsh_tsfix);
-    dst = prsh->prsh_transcoder = transcoder_create(&prsh->prsh_input);
+    dst = prsh->prsh_transcoder = transcoder_create(&prsh->prsh_input, profiles);
     if (!dst)
       goto fail;
-    transcoder_set_properties(dst, &props);
     prsh->prsh_tsfix = tsfix_create(dst);
   }
   prch->prch_share = prsh->prsh_tsfix;
@@ -2530,10 +2300,8 @@ profile_transcode_free(profile_t *_pro)
 {
   profile_transcode_t *pro = (profile_transcode_t *)_pro;
   free(pro->pro_vcodec);
-  free(pro->pro_vcodec_preset);
   free(pro->pro_acodec);
   free(pro->pro_scodec);
-  free(pro->pro_src_vcodec);
 }
 
 static profile_t *
@@ -2574,8 +2342,6 @@ profile_init(void)
   profile_register(&profile_libav_mpegts_class, profile_libav_mpegts_builder);
   profile_register(&profile_libav_matroska_class, profile_libav_matroska_builder);
   profile_register(&profile_libav_mp4_class, profile_libav_mp4_builder);
-  profile_transcode_experimental_codecs =
-    getenv("TVHEADEND_LIBAV_NO_EXPERIMENTAL_CODECS") ? 0 : 1;
   profile_register(&profile_transcode_class, profile_transcode_builder);
 #endif
 
@@ -2641,24 +2407,8 @@ profile_init(void)
     htsmsg_destroy(conf);
   }
 
-  name = "audio";
-  pro = profile_find_by_name2(name, NULL, 1);
-  if (pro == NULL || strcmp(profile_get_name(pro), name)) {
-    htsmsg_t *conf;
-
-    conf = htsmsg_create_map();
-    htsmsg_add_str (conf, "class", "profile-audio");
-    htsmsg_add_bool(conf, "enabled", 1);
-    htsmsg_add_str (conf, "name", name);
-    htsmsg_add_str (conf, "comment", _("Audio-only stream"));
-    htsmsg_add_s32 (conf, "priority", PROFILE_SPRIO_NORMAL);
-    htsmsg_add_bool(conf, "shield", 1);
-    (void)profile_create(NULL, conf, 1);
-    htsmsg_destroy(conf);
-  }
-
-#if ENABLE_LIBAV
-
+//#if ENABLE_LIBAV
+#if 0
   name = "webtv-vp8-vorbis-webm";
   pro = profile_find_by_name2(name, NULL, 1);
   if (pro == NULL || strcmp(profile_get_name(pro), name)) {
index fb33d128ad8da81d1dc5cc967bf4d93fd5f0e3fa..654cd0284abafdeca987f6081d0ef22ff02113d0 100644 (file)
@@ -100,7 +100,7 @@ prop_write_values
     if (!f) continue;
 
     /* Ignore */
-    opts = p->get_opts ? p->get_opts(obj) : p->opts;
+    opts = p->get_opts ? p->get_opts(obj, p->opts) : p->opts;
     if(opts & optmask) continue;
 
     /* Sanity check */
@@ -280,7 +280,7 @@ prop_read_value
   char buf[24];
 
   /* Ignore */
-  u32 = p->get_opts ? p->get_opts(obj) : p->opts;
+  u32 = p->get_opts ? p->get_opts(obj, p->opts) : p->opts;
   if (u32 & optmask) return;
   if (p->type == PT_NONE) return;
 
@@ -493,7 +493,7 @@ prop_serialize_value
   }
 
   /* Options */
-  opts = pl->get_opts ? pl->get_opts(obj) : pl->opts;
+  opts = pl->get_opts ? pl->get_opts(obj, pl->opts) : pl->opts;
   if (opts & PO_RDONLY)
     htsmsg_add_bool(m, "rdonly", 1);
   if (opts & PO_NOSAVE)
index ae9ea2a805490c63783693e3c386f594b93c678e..8404ad297cd9cd03d497350e38017d55dbd43dae 100644 (file)
@@ -115,7 +115,7 @@ typedef struct property {
   } def;
 
   /* Extended options */
-  uint32_t    (*get_opts) (void *ptr);
+  uint32_t    (*get_opts) (void *ptr, uint32_t opts);
 
   /* Documentation callback */
   char       *(*doc) ( const struct property *prop, const char *lang );
index a53cee7469f483913ba1099b6a9304bca8c2feef..38a0f1aea51f5ed1aaf910df373999102817139c 100644 (file)
@@ -69,7 +69,7 @@ streaming_message_data_size(streaming_message_t *sm)
 /**
  *
  */
-static void 
+static void
 streaming_queue_deliver(void *opauqe, streaming_message_t *sm)
 {
   streaming_queue_t *sq = opauqe;
@@ -431,7 +431,7 @@ streaming_code2txt(int code)
   switch(code) {
   case SM_CODE_OK:
     return N_("OK");
-    
+
   case SM_CODE_SOURCE_RECONFIGURED:
     return N_("Source reconfigured");
   case SM_CODE_BAD_SOURCE:
@@ -498,11 +498,11 @@ streaming_start_t *
 streaming_start_copy(const streaming_start_t *src)
 {
   int i;
-  size_t siz = sizeof(streaming_start_t) + 
+  size_t siz = sizeof(streaming_start_t) +
     sizeof(streaming_start_component_t) * src->ss_num_components;
-  
+
   streaming_start_t *dst = malloc(siz);
-  
+
   memcpy(dst, src, siz);
   service_source_info_copy(&dst->ss_si, &src->ss_si);
 
@@ -557,6 +557,8 @@ static struct strtab streamtypetab[] = {
   { "HEVC",       SCT_HEVC },
   { "VP9",        SCT_VP9 },
   { "HBBTV",      SCT_HBBTV },
+  { "THEORA",     SCT_THEORA },
+  { "OPUS",       SCT_OPUS },
 };
 
 /**
diff --git a/src/transcoding/codec.h b/src/transcoding/codec.h
new file mode 100644 (file)
index 0000000..b62c2da
--- /dev/null
@@ -0,0 +1,159 @@
+/*
+ *  tvheadend - Codec Profiles
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef TVH_TRANSCODING_CODEC_H__
+#define TVH_TRANSCODING_CODEC_H__
+
+
+#include "tvheadend.h"
+#include "idnode.h"
+#include "streaming.h"
+#include "libav.h"
+
+#include <libavcodec/avcodec.h>
+
+
+#define tvh_ssc_t streaming_start_component_t
+#define tvh_sct_t streaming_component_type_t
+
+
+struct tvh_codec_t;
+typedef struct tvh_codec_t TVHCodec;
+
+struct tvh_codec_profile_t;
+typedef struct tvh_codec_profile_t TVHCodecProfile;
+
+
+/* codec_profile_class ====================================================== */
+
+typedef int (*codec_profile_setup_meth) (TVHCodecProfile *, tvh_ssc_t *);
+typedef int (*codec_profile_is_copy_meth) (TVHCodecProfile *, tvh_ssc_t *);
+typedef int (*codec_profile_open_meth) (TVHCodecProfile *, AVDictionary **);
+
+typedef struct {
+    idclass_t idclass;
+    codec_profile_setup_meth setup;
+    codec_profile_is_copy_meth is_copy;
+    codec_profile_open_meth open;
+} codec_profile_class_t;
+
+
+/* TVHCodec ================================================================= */
+
+typedef struct tvh_codec_t {
+    const char *name;
+    size_t size;
+    const codec_profile_class_t *idclass;
+    AVCodec *codec;
+    const AVProfile *profiles;
+    SLIST_ENTRY(tvh_codec_t) link;
+} TVHCodec;
+
+SLIST_HEAD(TVHCodecs, tvh_codec_t);
+extern struct TVHCodecs tvh_codecs;
+
+const idclass_t *
+tvh_codec_get_class(TVHCodec *self);
+
+const char *
+tvh_codec_get_name(TVHCodec *self);
+
+const char *
+tvh_codec_get_title(TVHCodec *self);
+
+
+/* TVHCodecProfile ========================================================== */
+
+extern const codec_profile_class_t codec_profile_class;
+
+typedef struct tvh_codec_profile_t {
+    idnode_t idnode;
+    TVHCodec *codec;
+    const char *name;
+    const char *description;
+    const char *codec_name;
+    double bit_rate;
+    double qscale;
+    int profile;
+    LIST_ENTRY(tvh_codec_profile_t) link;
+} TVHCodecProfile;
+
+LIST_HEAD(TVHCodecProfiles, tvh_codec_profile_t);
+extern struct TVHCodecProfiles tvh_codec_profiles;
+
+int
+tvh_codec_profile_create(htsmsg_t *conf, const char *uuid, int save);
+
+const char *
+tvh_codec_profile_get_status(TVHCodecProfile *self);
+
+const char *
+tvh_codec_profile_get_name(TVHCodecProfile *self);
+
+const char *
+tvh_codec_profile_get_title(TVHCodecProfile *self);
+
+AVCodec *
+tvh_codec_profile_get_avcodec(TVHCodecProfile *self);
+
+
+/* transcode api */
+int
+tvh_codec_profile_is_copy(TVHCodecProfile *self, tvh_ssc_t *ssc); // XXX: not too sure...
+
+int
+tvh_codec_profile_open(TVHCodecProfile *self, AVDictionary **opts);
+
+
+/* video */
+int
+tvh_codec_profile_video_get_hwaccel(TVHCodecProfile *self);
+
+const enum AVPixelFormat *
+tvh_codec_profile_video_get_pix_fmts(TVHCodecProfile *self);
+
+
+/* audio */
+const enum AVSampleFormat *
+tvh_codec_profile_audio_get_sample_fmts(TVHCodecProfile *self);
+
+const int *
+tvh_codec_profile_audio_get_sample_rates(TVHCodecProfile *self);
+
+const uint64_t *
+tvh_codec_profile_audio_get_channel_layouts(TVHCodecProfile *self);
+
+
+/* module level ============================================================= */
+
+TVHCodecProfile *
+codec_find_profile(const char *name);
+
+htsmsg_t *
+codec_get_profiles_list(enum AVMediaType media_type);
+
+void
+codec_init(void);
+
+void
+codec_done(void);
+
+
+#endif // TVH_TRANSCODING_CODEC_H__
diff --git a/src/transcoding/codec/codec.c b/src/transcoding/codec/codec.c
new file mode 100644 (file)
index 0000000..4bd2908
--- /dev/null
@@ -0,0 +1,293 @@
+/*
+ *  tvheadend - Codec Profiles
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "internals.h"
+
+
+struct TVHCodecs tvh_codecs;
+
+
+/* encoders ================================================================= */
+
+extern TVHCodec tvh_codec_mpeg2video;
+extern TVHCodec tvh_codec_mp2;
+extern TVHCodec tvh_codec_aac;
+extern TVHCodec tvh_codec_vorbis;
+
+#if ENABLE_LIBX264
+extern TVHCodec tvh_codec_libx264;
+#endif
+#if ENABLE_LIBX265
+extern TVHCodec tvh_codec_libx265;
+#endif
+
+#if ENABLE_LIBVPX
+extern TVHCodec tvh_codec_libvpx_vp8;
+extern TVHCodec tvh_codec_libvpx_vp9;
+#endif
+
+#if ENABLE_LIBTHEORA
+extern TVHCodec tvh_codec_libtheora;
+#endif
+#if ENABLE_LIBVORBIS
+extern TVHCodec tvh_codec_libvorbis;
+#endif
+
+#if ENABLE_LIBFDKAAC
+extern TVHCodec tvh_codec_libfdk_aac;
+#endif
+
+#if ENABLE_LIBOPUS
+extern TVHCodec tvh_codec_libopus;
+#endif
+
+#if ENABLE_VAAPI
+extern TVHCodec tvh_codec_vaapi_h264;
+extern TVHCodec tvh_codec_vaapi_hevc;
+#endif
+
+#if ENABLE_OMX
+extern TVHCodec tvh_codec_omx_h264;
+#endif
+
+
+/* AVCodec ================================================================== */
+
+static enum AVMediaType
+codec_get_type(AVCodec *self)
+{
+    return self->type;
+}
+
+
+static const char *
+codec_get_type_string(AVCodec *self)
+{
+    return av_get_media_type_string(self->type);
+}
+
+
+const char *
+codec_get_title(AVCodec *self)
+{
+    static char codec_title[TVH_TITLE_LEN];
+
+    memset(codec_title, 0, sizeof(codec_title));
+    if (
+        str_snprintf(codec_title, sizeof(codec_title),
+            self->long_name ? "%s: %s%s" : "%s%s%s",
+            self->name, self->long_name ? self->long_name : "",
+            (self->capabilities & CODEC_CAP_EXPERIMENTAL) ? " (Experimental)" : "")
+       ) {
+        return NULL;
+    }
+    return codec_title;
+}
+
+
+/* TVHCodec ================================================================= */
+
+static void
+tvh_codec_video_init(TVHVideoCodec *self, AVCodec *codec)
+{
+    if (!self->pix_fmts) {
+        self->pix_fmts = codec->pix_fmts;
+    }
+}
+
+
+static void
+tvh_codec_audio_init(TVHAudioCodec *self, AVCodec *codec)
+{
+    if (!self->sample_fmts) {
+        self->sample_fmts = codec->sample_fmts;
+    }
+    if (!self->sample_rates) {
+        self->sample_rates = codec->supported_samplerates;
+    }
+    if (!self->channel_layouts) {
+        self->channel_layouts = codec->channel_layouts;
+    }
+}
+
+
+static void
+tvh_codec_init(TVHCodec *self, AVCodec *codec)
+{
+    if (!self->profiles) {
+        self->profiles = codec->profiles;
+    }
+    switch (codec->type) {
+        case AVMEDIA_TYPE_VIDEO:
+            tvh_codec_video_init((TVHVideoCodec *)self, codec);
+            break;
+        case AVMEDIA_TYPE_AUDIO:
+            tvh_codec_audio_init((TVHAudioCodec *)self, codec);
+            break;
+        default:
+            break;
+    }
+}
+
+
+static void
+tvh_codec_register(TVHCodec *self)
+{
+    static const size_t min_size = sizeof(TVHCodecProfile);
+    AVCodec *codec = NULL;
+
+    if (!self->name || self->name[0] == '\0' ||
+        self->size < min_size || !self->idclass) {
+        tvherror(LS_CODEC, "incomplete/wrong definition for '%s' codec",
+                 self->name ? self->name : "<missing name>");
+        return;
+    }
+
+    if ((codec = avcodec_find_encoder_by_name(self->name)) &&
+        !(codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)) {
+        tvh_codec_init(self, codec);
+        self->codec = codec; // enabled
+    }
+    idclass_register((idclass_t *)self->idclass);
+    SLIST_INSERT_HEAD(&tvh_codecs, self, link);
+    tvhinfo(LS_CODEC, "'%s' encoder registered", self->name);
+}
+
+
+/* exposed */
+
+const idclass_t *
+tvh_codec_get_class(TVHCodec *self)
+{
+    return (idclass_t *)self->idclass;
+}
+
+
+const char *
+tvh_codec_get_name(TVHCodec *self)
+{
+    return self->name;
+}
+
+
+const char *
+tvh_codec_get_title(TVHCodec *self)
+{
+    return self->codec ? codec_get_title(self->codec) : self->name;
+}
+
+
+enum AVMediaType
+tvh_codec_get_type(TVHCodec *self)
+{
+    return self->codec ? codec_get_type(self->codec) : AVMEDIA_TYPE_UNKNOWN;
+}
+
+
+const char *
+tvh_codec_get_type_string(TVHCodec *self)
+{
+    return self->codec ? codec_get_type_string(self->codec) : "<unknown>";
+}
+
+
+AVCodec *
+tvh_codec_get_codec(TVHCodec *self)
+{
+    return self->codec;
+}
+
+
+int
+tvh_codec_is_enabled(TVHCodec *self)
+{
+    return self->codec ? 1 : 0;
+}
+
+
+TVHCodec *
+tvh_codec_find(const char *name)
+{
+    TVHCodec *codec = NULL;
+
+    SLIST_FOREACH(codec, &tvh_codecs, link) {
+        if (!strcmp(codec->name, name)) {
+            return codec;
+        }
+    }
+    return NULL;
+}
+
+
+void
+tvh_codecs_register()
+{
+    SLIST_INIT(&tvh_codecs);
+    tvh_codec_register(&tvh_codec_mpeg2video);
+    tvh_codec_register(&tvh_codec_mp2);
+    tvh_codec_register(&tvh_codec_aac);
+    tvh_codec_register(&tvh_codec_vorbis);
+
+#if ENABLE_LIBX264
+    tvh_codec_register(&tvh_codec_libx264);
+#endif
+#if ENABLE_LIBX265
+    tvh_codec_register(&tvh_codec_libx265);
+#endif
+
+#if ENABLE_LIBVPX
+    tvh_codec_register(&tvh_codec_libvpx_vp8);
+    tvh_codec_register(&tvh_codec_libvpx_vp9);
+#endif
+
+#if ENABLE_LIBTHEORA
+    tvh_codec_register(&tvh_codec_libtheora);
+#endif
+#if ENABLE_LIBVORBIS
+    tvh_codec_register(&tvh_codec_libvorbis);
+#endif
+
+#if ENABLE_LIBFDKAAC
+    tvh_codec_register(&tvh_codec_libfdk_aac);
+#endif
+
+#if ENABLE_LIBOPUS
+    tvh_codec_register(&tvh_codec_libopus);
+#endif
+
+#if ENABLE_VAAPI
+    tvh_codec_register(&tvh_codec_vaapi_h264);
+    tvh_codec_register(&tvh_codec_vaapi_hevc);
+#endif
+
+#if ENABLE_OMX
+    tvh_codec_register(&tvh_codec_omx_h264);
+#endif
+}
+
+
+void
+tvh_codecs_forget()
+{
+    tvhinfo(LS_CODEC, "forgetting codecs");
+    while (!SLIST_EMPTY(&tvh_codecs)) {
+        SLIST_REMOVE_HEAD(&tvh_codecs, link);
+    }
+}
diff --git a/src/transcoding/codec/codecs/aac.c b/src/transcoding/codec/codecs/aac.c
new file mode 100644 (file)
index 0000000..380e2f2
--- /dev/null
@@ -0,0 +1,131 @@
+/*
+ *  tvheadend - Codec Profiles
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/codec/internals.h"
+
+
+/* aac ====================================================================== */
+
+static const AVProfile aac_profiles[] = {
+    { FF_PROFILE_AAC_MAIN, "Main" },
+    { FF_PROFILE_AAC_LOW,  "LC" },
+    { FF_PROFILE_AAC_LTP,  "LTP" },
+    { FF_PROFILE_UNKNOWN },
+};
+
+// see aac_chan_configs in ffmpeg-3.0.2/libavcodec/aacenctab.h
+static const uint64_t aac_channel_layouts[] = {
+    AV_CH_LAYOUT_MONO,
+    AV_CH_LAYOUT_STEREO,
+    AV_CH_LAYOUT_SURROUND,
+    AV_CH_LAYOUT_4POINT0,
+    AV_CH_LAYOUT_5POINT0_BACK,
+    AV_CH_LAYOUT_5POINT1_BACK,
+    AV_CH_LAYOUT_7POINT1_WIDE_BACK,
+    0
+};
+
+
+typedef struct {
+    TVHAudioCodecProfile;
+    const char *coder;
+} tvh_codec_profile_aac_t;
+
+
+static int
+tvh_codec_profile_aac_open(tvh_codec_profile_aac_t *self, AVDictionary **opts)
+{
+    // bit_rate or global_quality
+    if (self->bit_rate) {
+        AV_DICT_SET_BIT_RATE(opts, self->bit_rate);
+    }
+    else {
+        AV_DICT_SET_GLOBAL_QUALITY(opts, self->qscale, 1);
+    }
+    AV_DICT_SET(opts, "aac_coder", self->coder, 0);
+    return 0;
+}
+
+
+static htsmsg_t *
+codec_profile_aac_class_coder_list(void *obj, const char *lang)
+{
+    static const struct strtab_str tab[] = {
+        {N_("anmr: ANMR method (Not currently recommended)"), "anmr"},
+        {N_("twoloop: Two loop searching method"),            "twoloop"},
+        {N_("fast: Constant quantizer (Not recommended)"),    "fast"}
+    };
+    return strtab2htsmsg_str(tab, 1, lang);
+}
+
+
+static const codec_profile_class_t codec_profile_aac_class = {
+    {
+        .ic_super      = (idclass_t *)&codec_profile_audio_class,
+        .ic_class      = "codec_profile_aac",
+        .ic_caption    = N_("aac"),
+        .ic_properties = (const property_t[]){
+            {
+                .type     = PT_DBL,
+                .id       = "bit_rate",
+                .name     = N_("Bitrate (kb/s) (0=auto)"),
+                .desc     = N_("Constant bitrate (CBR) mode."),
+                .group    = 3,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(TVHCodecProfile, bit_rate),
+                .def.d    = 0,
+            },
+            {
+                .type     = PT_DBL,
+                .id       = "qscale",
+                .name     = N_("Quality (0=auto)"),
+                .desc     = N_("Variable bitrate (VBR) mode [0-2]."),
+                .group    = 3,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(TVHCodecProfile, qscale),
+                .intextra = INTEXTRA_RANGE(0, 2, 1),
+                .def.d    = 0,
+            },
+            {
+                .type     = PT_STR,
+                .id       = "coder",
+                .name     = N_("Coding algorithm"),
+                .desc     = N_("Coding algorithm."),
+                .group    = 5,
+                .opts     = PO_EXPERT,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(tvh_codec_profile_aac_t, coder),
+                .list     = codec_profile_aac_class_coder_list,
+                .def.s    = "twoloop",
+            },
+            {}
+        }
+    },
+    .open = (codec_profile_open_meth)tvh_codec_profile_aac_open,
+};
+
+
+TVHAudioCodec tvh_codec_aac = {
+    .name            = "aac",
+    .size            = sizeof(tvh_codec_profile_aac_t),
+    .idclass         = &codec_profile_aac_class,
+    .profiles        = aac_profiles,
+    .channel_layouts = aac_channel_layouts,
+};
diff --git a/src/transcoding/codec/codecs/libs/libfdk_aac.c b/src/transcoding/codec/codecs/libs/libfdk_aac.c
new file mode 100644 (file)
index 0000000..80b6980
--- /dev/null
@@ -0,0 +1,139 @@
+/*
+ *  tvheadend - Codec Profiles
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/codec/internals.h"
+
+
+/* libfdk_aac =============================================================== */
+
+typedef struct {
+    TVHAudioCodecProfile;
+    int vbr;
+    int afterburner;
+    int eld_sbr;
+    int signaling;
+} tvh_codec_profile_libfdk_aac_t;
+
+
+static int
+tvh_codec_profile_libfdk_aac_open(tvh_codec_profile_libfdk_aac_t *self,
+                                  AVDictionary **opts)
+{
+    AV_DICT_SET_FLAGS_GLOBAL_HEADER(opts);
+    // bit_rate or vbr
+    if (self->bit_rate) {
+        AV_DICT_SET_BIT_RATE(opts, self->bit_rate);
+    }
+    else {
+        AV_DICT_SET_INT(opts, "vbr", self->vbr ? self->vbr : 3, 0);
+    }
+    AV_DICT_SET_INT(opts, "afterburner", self->afterburner, 0);
+    AV_DICT_SET_INT(opts, "eld_sbr", self->eld_sbr, 0);
+    AV_DICT_SET_INT(opts, "signaling", self->signaling, 0);
+    return 0;
+}
+
+
+static htsmsg_t *
+codec_profile_libfdk_aac_class_signaling_list(void *obj, const char *lang)
+{
+    static const struct strtab tab[] = {
+        {N_("default"),                                               -1},
+        {N_("implicit: Implicit backwards compatible signaling"),      0},
+        {N_("explicit_sbr: Explicit SBR, implicit PS signaling"),      1},
+        {N_("explicit_hierarchical: Explicit hierarchical signaling"), 2}
+    };
+    return strtab2htsmsg(tab, 1, lang);
+}
+
+
+static const codec_profile_class_t codec_profile_libfdk_aac_class = {
+    {
+        .ic_super      = (idclass_t *)&codec_profile_audio_class,
+        .ic_class      = "codec_profile_libfdk_aac",
+        .ic_caption    = N_("libfdk_aac"),
+        .ic_properties = (const property_t[]){
+            {
+                .type     = PT_DBL,
+                .id       = "bit_rate",
+                .name     = N_("Bitrate (kb/s) (0=auto)"),
+                .desc     = N_("Constant bitrate (CBR) mode."),
+                .group    = 3,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(TVHCodecProfile, bit_rate),
+                .def.d    = 0,
+            },
+            {
+                .type     = PT_INT,
+                .id       = "vbr",
+                .name     = N_("Quality (0=auto)"),
+                .desc     = N_("Variable bitrate (VBR) mode [0-5]."),
+                .group    = 3,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(tvh_codec_profile_libfdk_aac_t, vbr),
+                .intextra = INTEXTRA_RANGE(0, 5, 1),
+                .def.i    = 0,
+            },
+            {
+                .type     = PT_BOOL,
+                .id       = "afterburner",
+                .name     = N_("Afterburner (improved quality)"),
+                .desc     = N_("Afterburner (improved quality)."),
+                .group    = 5,
+                .opts     = PO_EXPERT,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(tvh_codec_profile_libfdk_aac_t, afterburner),
+                .def.i    = 1,
+            },
+            {
+                .type     = PT_BOOL,
+                .id       = "eld_sbr",
+                .name     = N_("Enable SBR for ELD"),
+                .desc     = N_("Enable SBR for ELD."),
+                .group    = 5,
+                .opts     = PO_EXPERT | PO_HIDDEN,
+                .get_opts = codec_profile_class_profile_get_opts,
+                .off      = offsetof(tvh_codec_profile_libfdk_aac_t, eld_sbr),
+                .def.i    = 0,
+            },
+            {
+                .type     = PT_INT,
+                .id       = "signaling",
+                .name     = N_("Signaling"),
+                .desc     = N_("SBR/PS signaling style."),
+                .group    = 5,
+                .opts     = PO_EXPERT | PO_HIDDEN,
+                .get_opts = codec_profile_class_profile_get_opts,
+                .off      = offsetof(tvh_codec_profile_libfdk_aac_t, signaling),
+                .list     = codec_profile_libfdk_aac_class_signaling_list,
+                .def.i    = -1,
+            },
+            {}
+        }
+    },
+    .open = (codec_profile_open_meth)tvh_codec_profile_libfdk_aac_open,
+};
+
+
+TVHAudioCodec tvh_codec_libfdk_aac = {
+    .name    = "libfdk_aac",
+    .size    = sizeof(tvh_codec_profile_libfdk_aac_t),
+    .idclass = &codec_profile_libfdk_aac_class,
+};
diff --git a/src/transcoding/codec/codecs/libs/libopus.c b/src/transcoding/codec/codecs/libs/libopus.c
new file mode 100644 (file)
index 0000000..5a5862c
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+ *  tvheadend - Codec Profiles
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/codec/internals.h"
+
+#include <opus/opus_defines.h>
+
+
+/* libopus ================================================================== */
+
+typedef struct {
+    TVHAudioCodecProfile;
+    int vbr;
+    int application;
+    int complexity;
+} tvh_codec_profile_libopus_t;
+
+
+static int
+tvh_codec_profile_libopus_open(tvh_codec_profile_libopus_t *self,
+                               AVDictionary **opts)
+{
+    AV_DICT_SET_BIT_RATE(opts, self->bit_rate);
+    AV_DICT_SET_INT(opts, "vbr", self->vbr, 0);
+    AV_DICT_SET_INT(opts, "application", self->application, 0);
+    AV_DICT_SET_INT(opts, "compression_level", self->complexity, 0);
+    return 0;
+}
+
+
+static htsmsg_t *
+codec_profile_libopus_class_vbr_list(void *obj, const char *lang)
+{
+    static const struct strtab tab[] = {
+        {N_("off: Use constant bit rate"),       0},
+        {N_("on: Use variable bit rate"),        1},
+        {N_("constrained: Use constrained VBR"), 2}
+    };
+    return strtab2htsmsg(tab, 1, lang);
+}
+
+
+static htsmsg_t *
+codec_profile_libopus_class_application_list(void *obj, const char *lang)
+{
+    static const struct strtab tab[] = {
+        {N_("voip: Favor improved speech intelligibility"),       OPUS_APPLICATION_VOIP},
+        {N_("audio: Favor faithfulness to the input"),            OPUS_APPLICATION_AUDIO},
+        {N_("lowdelay: Restrict to only the lowest delay modes"), OPUS_APPLICATION_RESTRICTED_LOWDELAY}
+    };
+    return strtab2htsmsg(tab, 1, lang);
+}
+
+
+static const codec_profile_class_t codec_profile_libopus_class = {
+    {
+        .ic_super      = (idclass_t *)&codec_profile_audio_class,
+        .ic_class      = "codec_profile_libopus",
+        .ic_caption    = N_("libopus"),
+        .ic_properties = (const property_t[]){
+            {
+                .type     = PT_DBL,
+                .id       = "bit_rate",
+                .name     = N_("Bitrate (kb/s) (0=auto)"),
+                .desc     = N_("Constant bitrate (CBR) mode."),
+                .group    = 3,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(TVHCodecProfile, bit_rate),
+                .def.d    = 0,
+            },
+            {
+                .type     = PT_INT,
+                .id       = "vbr",
+                .name     = N_("Bitrate mode"),
+                .desc     = N_("Bitrate mode."),
+                .group    = 3,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(tvh_codec_profile_libopus_t, vbr),
+                .list     = codec_profile_libopus_class_vbr_list,
+                .def.i    = 0,
+            },
+            {
+                .type     = PT_INT,
+                .id       = "application",
+                .name     = N_("Application"),
+                .desc     = N_("Intended application type."),
+                .group    = 5,
+                .opts     = PO_EXPERT,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(tvh_codec_profile_libopus_t, application),
+                .list     = codec_profile_libopus_class_application_list,
+                .def.i    = OPUS_APPLICATION_AUDIO,
+            },
+            {
+                .type     = PT_INT,
+                .id       = "complexity",
+                .name     = N_("Encoding algorithm complexity"),
+                .desc     = N_("0 gives the fastest encodes but lower quality, "
+                               "while 10 gives the highest quality but slowest "
+                               "encoding [0-10]."),
+                .group    = 5,
+                .opts     = PO_EXPERT,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(tvh_codec_profile_libopus_t, complexity),
+                .intextra = INTEXTRA_RANGE(0, 10, 1),
+                .def.i    = 10,
+            },
+            {}
+        }
+    },
+    .open = (codec_profile_open_meth)tvh_codec_profile_libopus_open,
+};
+
+
+TVHAudioCodec tvh_codec_libopus = {
+    .name    = "libopus",
+    .size    = sizeof(tvh_codec_profile_libopus_t),
+    .idclass = &codec_profile_libopus_class,
+};
diff --git a/src/transcoding/codec/codecs/libs/libtheora.c b/src/transcoding/codec/codecs/libs/libtheora.c
new file mode 100644 (file)
index 0000000..8511cab
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ *  tvheadend - Codec Profiles
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/codec/internals.h"
+
+
+/* libtheora ================================================================ */
+
+static int
+tvh_codec_profile_libtheora_open(TVHCodecProfile *self, AVDictionary **opts)
+{
+    // bit_rate or global_quality
+    if (self->bit_rate) {
+        AV_DICT_SET_BIT_RATE(opts, self->bit_rate);
+    }
+    else {
+        AV_DICT_SET_GLOBAL_QUALITY(opts, self->qscale, 6);
+    }
+    return 0;
+}
+
+
+static const codec_profile_class_t codec_profile_libtheora_class = {
+    {
+        .ic_super      = (idclass_t *)&codec_profile_video_class,
+        .ic_class      = "codec_profile_libtheora",
+        .ic_caption    = N_("libtheora"),
+        .ic_properties = (const property_t[]){
+            {
+                .type     = PT_DBL,
+                .id       = "bit_rate",
+                .name     = N_("Bitrate (kb/s) (0=auto)"),
+                .desc     = N_("Constant bitrate (CBR) mode."),
+                .group    = 3,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(TVHCodecProfile, bit_rate),
+                .def.d    = 0,
+            },
+            {
+                .type     = PT_DBL,
+                .id       = "qscale",
+                .name     = N_("Quality (0=auto)"),
+                .desc     = N_("Variable bitrate (VBR) mode [0-10]."),
+                .group    = 3,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(TVHCodecProfile, qscale),
+                .intextra = INTEXTRA_RANGE(0, 10, 1),
+                .def.d    = 0,
+            },
+            {}
+        }
+    },
+    .open = tvh_codec_profile_libtheora_open,
+};
+
+
+TVHVideoCodec tvh_codec_libtheora = {
+    .name    = "libtheora",
+    .size    = sizeof(TVHVideoCodecProfile),
+    .idclass = &codec_profile_libtheora_class,
+};
diff --git a/src/transcoding/codec/codecs/libs/libvorbis.c b/src/transcoding/codec/codecs/libs/libvorbis.c
new file mode 100644 (file)
index 0000000..41b9dc3
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ *  tvheadend - Codec Profiles
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/codec/internals.h"
+
+
+/* libvorbis ================================================================ */
+
+// see libvorbis_setup() in ffmpeg-3.0.2/libavcodec/libvorbisenc.c
+static const uint64_t libvorbis_channel_layouts[] = {
+    AV_CH_LAYOUT_MONO,
+    AV_CH_LAYOUT_STEREO,
+    AV_CH_LAYOUT_SURROUND,
+    AV_CH_LAYOUT_2_2,
+    AV_CH_LAYOUT_QUAD,
+    AV_CH_LAYOUT_5POINT0,
+    AV_CH_LAYOUT_5POINT0_BACK,
+    AV_CH_LAYOUT_5POINT1,
+    AV_CH_LAYOUT_5POINT1_BACK,
+    AV_CH_LAYOUT_6POINT1,
+    AV_CH_LAYOUT_7POINT1,
+    //AV_CH_LAYOUT_HEXADECAGONAL,
+    //AV_CH_LAYOUT_STEREO_DOWNMIX,
+    0
+};
+
+
+static int
+tvh_codec_profile_libvorbis_open(TVHCodecProfile *self, AVDictionary **opts)
+{
+    // bit_rate or global_quality
+    if (self->bit_rate) {
+        AV_DICT_SET_BIT_RATE(opts, self->bit_rate);
+    }
+    else {
+        AV_DICT_SET_GLOBAL_QUALITY(opts, self->qscale, 5);
+    }
+    return 0;
+}
+
+
+static const codec_profile_class_t codec_profile_libvorbis_class = {
+    {
+        .ic_super      = (idclass_t *)&codec_profile_audio_class,
+        .ic_class      = "codec_profile_libvorbis",
+        .ic_caption    = N_("libvorbis"),
+        .ic_properties = (const property_t[]){
+            {
+                .type     = PT_DBL,
+                .id       = "bit_rate",
+                .name     = N_("Bitrate (kb/s) (0=auto)"),
+                .desc     = N_("Average bitrate (ABR) mode."),
+                .group    = 3,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(TVHCodecProfile, bit_rate),
+                .def.d    = 0,
+            },
+            {
+                .type     = PT_DBL,
+                .id       = "qscale",
+                .name     = N_("Quality (0=auto)"),
+                .desc     = N_("Variable bitrate (VBR) mode [0-10]."),
+                .group    = 3,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(TVHCodecProfile, qscale),
+                .intextra = INTEXTRA_RANGE(0, 10, 1),
+                .def.d    = 0,
+            },
+            {}
+        }
+    },
+    .open = tvh_codec_profile_libvorbis_open,
+};
+
+
+TVHAudioCodec tvh_codec_libvorbis = {
+    .name            = "libvorbis",
+    .size            = sizeof(TVHAudioCodecProfile),
+    .idclass         = &codec_profile_libvorbis_class,
+    .channel_layouts = libvorbis_channel_layouts,
+};
diff --git a/src/transcoding/codec/codecs/libs/libvpx.c b/src/transcoding/codec/codecs/libs/libvpx.c
new file mode 100644 (file)
index 0000000..cfc5ee3
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ *  tvheadend - Codec Profiles
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/codec/internals.h"
+
+#include <vpx/vp8cx.h>
+
+
+/* libvpx =================================================================== */
+
+typedef struct {
+    TVHVideoCodecProfile;
+    int deadline;
+    int cpu_used;
+    int tune;
+} tvh_codec_profile_libvpx_t;
+
+
+static int
+tvh_codec_profile_libvpx_open(tvh_codec_profile_libvpx_t *self,
+                              AVDictionary **opts)
+{
+    AV_DICT_SET_TVH_REQUIRE_META(opts, 0);
+    AV_DICT_SET_BIT_RATE(opts, self->bit_rate ? self->bit_rate : 2560);
+    if (self->crf) {
+        AV_DICT_SET_CRF(opts, self->crf, 10);
+    }
+    AV_DICT_SET_INT(opts, "deadline", self->deadline, 0);
+    AV_DICT_SET_INT(opts, "cpu-used", self->cpu_used, 0);
+    AV_DICT_SET_INT(opts, "tune", self->tune, 0);
+    AV_DICT_SET_INT(opts, "threads", 0, 0);
+    return 0;
+}
+
+
+static htsmsg_t *
+codec_profile_libvpx_class_deadline_list(void *obj, const char *lang)
+{
+    static const struct strtab tab[] = {
+        {N_("best"),     VPX_DL_BEST_QUALITY},
+        {N_("good"),     VPX_DL_GOOD_QUALITY},
+        {N_("realtime"), VPX_DL_REALTIME}
+    };
+    return strtab2htsmsg(tab, 1, lang);
+}
+
+
+static htsmsg_t *
+codec_profile_libvpx_class_tune_list(void *obj, const char *lang)
+{
+    static const struct strtab tab[] = {
+        {N_("psnr"), VP8_TUNE_PSNR},
+        {N_("ssim"), VP8_TUNE_SSIM}
+    };
+    return strtab2htsmsg(tab, 1, lang);
+}
+
+
+static const codec_profile_class_t codec_profile_libvpx_class = {
+    {
+        .ic_super      = (idclass_t *)&codec_profile_video_class,
+        .ic_class      = "codec_profile_libvpx",
+        .ic_caption    = N_("libvpx"),
+        .ic_properties = (const property_t[]){
+            {
+                .type     = PT_DBL,
+                .id       = "bit_rate",
+                .name     = N_("Bitrate (kb/s) (0=auto)"),
+                .desc     = N_("Constant bitrate (CBR) mode."),
+                .group    = 3,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(TVHCodecProfile, bit_rate),
+                .def.d    = 0,
+            },
+            {
+                .type     = PT_INT,
+                .id       = "crf",
+                .name     = N_("Constant Rate Factor (0=auto)"),
+                .desc     = N_("Select the quality for constant quality mode [0-63]."),
+                .group    = 3,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(TVHVideoCodecProfile, crf),
+                .intextra = INTEXTRA_RANGE(0, 63, 1),
+                .def.i    = 0,
+            },
+            {
+                .type     = PT_INT,
+                .id       = "deadline",
+                .name     = N_("Quality"),
+                .desc     = N_("Time to spend encoding, in microseconds."),
+                .group    = 5,
+                .opts     = PO_EXPERT,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(tvh_codec_profile_libvpx_t, deadline),
+                .list     = codec_profile_libvpx_class_deadline_list,
+                .def.i    = VPX_DL_REALTIME,
+            },
+            {
+                .type     = PT_INT,
+                .id       = "cpu-used",
+                .name     = N_("Speed"),
+                .desc     = N_("Quality/Speed ratio modifier."),
+                .group    = 5,
+                .opts     = PO_EXPERT,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(tvh_codec_profile_libvpx_t, cpu_used),
+                .intextra = INTEXTRA_RANGE(0, 15, 1),
+                .def.i    = 8,
+            },
+            {
+                .type     = PT_INT,
+                .id       = "tune",
+                .name     = N_("Tune"),
+                .desc     = N_("Tune the encoding to a specific scenario."),
+                .group    = 5,
+                .opts     = PO_EXPERT,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(tvh_codec_profile_libvpx_t, tune),
+                .list     = codec_profile_libvpx_class_tune_list,
+                .def.i    = VP8_TUNE_PSNR,
+            },
+            {}
+        }
+    },
+    .open = (codec_profile_open_meth)tvh_codec_profile_libvpx_open,
+};
+
+
+/* libvpx_vp8 =============================================================== */
+
+TVHVideoCodec tvh_codec_libvpx_vp8 = {
+    .name     = "libvpx",
+    .size     = sizeof(tvh_codec_profile_libvpx_t),
+    .idclass  = &codec_profile_libvpx_class,
+};
+
+
+/* libvpx_vp9 =============================================================== */
+
+TVHVideoCodec tvh_codec_libvpx_vp9 = {
+    .name    = "libvpx-vp9",
+    .size    = sizeof(tvh_codec_profile_libvpx_t),
+    .idclass = &codec_profile_libvpx_class,
+};
diff --git a/src/transcoding/codec/codecs/libs/libx26x.c b/src/transcoding/codec/codecs/libs/libx26x.c
new file mode 100644 (file)
index 0000000..4dade83
--- /dev/null
@@ -0,0 +1,301 @@
+/*
+ *  tvheadend - Codec Profiles
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/codec/internals.h"
+
+
+/* utils ==================================================================== */
+
+static htsmsg_t *
+strlist2htsmsg(const char * const *values)
+{
+    htsmsg_t *list = NULL, *map = NULL;
+    int i;
+    const char *value = NULL;
+
+    if ((list = htsmsg_create_list())) {
+        for (i = 0; (value = values[i]); i++) {
+            if (!(map = htsmsg_create_map())) {
+                htsmsg_destroy(list);
+                list = NULL;
+                break;
+            }
+            ADD_STR_VAL(list, map, value);
+        }
+    }
+    return list;
+}
+
+
+/* libx26x ================================================================== */
+
+typedef struct {
+    TVHVideoCodecProfile;
+    const char *preset;
+    const char *tune;
+    const char *params;
+} tvh_codec_profile_libx26x_t;
+
+
+static int
+tvh_codec_profile_libx26x_open(tvh_codec_profile_libx26x_t *self,
+                               AVDictionary **opts)
+{
+    AV_DICT_SET(opts, "preset", self->preset, 0);
+    AV_DICT_SET(opts, "tune", self->tune, 0);
+    return 0;
+}
+
+
+static const codec_profile_class_t codec_profile_libx26x_class = {
+    {
+        .ic_super      = (idclass_t *)&codec_profile_video_class,
+        .ic_class      = "codec_profile_libx26x",
+        .ic_caption    = N_("libx26x"),
+        .ic_properties = (const property_t[]){
+            {
+                .type     = PT_DBL,
+                .id       = "bit_rate",
+                .name     = N_("Bitrate (kb/s) (0=auto)"),
+                .desc     = N_("Average bitrate (ABR) mode."),
+                .group    = 3,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(TVHCodecProfile, bit_rate),
+                .def.d    = 0,
+            },
+            {
+                .type     = PT_INT,
+                .id       = "crf",
+                .name     = N_("Constant Rate Factor (0=auto)"),
+                .desc     = N_("Select the quality for constant quality mode [0-51]."),
+                .group    = 3,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(TVHVideoCodecProfile, crf),
+                .intextra = INTEXTRA_RANGE(0, 51, 1),
+                .def.i    = 0,
+            },
+            {}
+        }
+    },
+    .open = (codec_profile_open_meth)tvh_codec_profile_libx26x_open,
+};
+
+
+/* libx264 ================================================================== */
+
+#if ENABLE_LIBX264
+
+#include <x264.h>
+
+static const AVProfile libx264_profiles[] = {
+    { FF_PROFILE_H264_BASELINE, "Baseline" },
+    { FF_PROFILE_H264_MAIN,     "Main" },
+    { FF_PROFILE_H264_HIGH,     "High" },
+    { FF_PROFILE_H264_HIGH_10,  "High 10" },
+    { FF_PROFILE_H264_HIGH_422, "High 4:2:2" },
+    { FF_PROFILE_H264_HIGH_444, "High 4:4:4" },
+    { FF_PROFILE_UNKNOWN },
+};
+
+
+static int
+tvh_codec_profile_libx264_open(tvh_codec_profile_libx26x_t *self,
+                               AVDictionary **opts)
+{
+    // bit_rate or crf
+    if (self->bit_rate) {
+        AV_DICT_SET_BIT_RATE(opts, self->bit_rate);
+    }
+    else {
+        AV_DICT_SET_CRF(opts, self->crf, 15);
+    }
+    if (self->params && strlen(self->params)) {
+        AV_DICT_SET(opts, "x264-params", self->params, 0);
+    }
+    return 0;
+}
+
+
+static htsmsg_t *
+codec_profile_libx264_class_preset_list(void *obj, const char *lang)
+{
+    return strlist2htsmsg(x264_preset_names);
+}
+
+
+static htsmsg_t *
+codec_profile_libx264_class_tune_list(void *obj, const char *lang)
+{
+    return strlist2htsmsg(x264_tune_names);
+}
+
+
+static const codec_profile_class_t codec_profile_libx264_class = {
+    {
+        .ic_super      = (idclass_t *)&codec_profile_libx26x_class,
+        .ic_class      = "codec_profile_libx264",
+        .ic_caption    = N_("libx264"),
+        .ic_properties = (const property_t[]){
+            {
+                .type     = PT_STR,
+                .id       = "preset",
+                .name     = N_("Preset"),
+                .desc     = N_("Set the encoding preset (cf. x264 --fullhelp)."),
+                .group    = 5,
+                .opts     = PO_EXPERT,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(tvh_codec_profile_libx26x_t, preset),
+                .list     = codec_profile_libx264_class_preset_list,
+                .def.s    = "faster",
+            },
+            {
+                .type     = PT_STR,
+                .id       = "tune",
+                .name     = N_("Tune"),
+                .desc     = N_("Tune the encoding params (cf. x264 --fullhelp)."),
+                .group    = 5,
+                .opts     = PO_EXPERT,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(tvh_codec_profile_libx26x_t, tune),
+                .list     = codec_profile_libx264_class_tune_list,
+                .def.s    = "zerolatency",
+            },
+            {
+                .type     = PT_STR,
+                .id       = "params",
+                .name     = N_("Parameters"),
+                .desc     = N_("Override the configuration using a ':' separated "
+                               "list of key=value parameters."),
+                .group    = 5,
+                .opts     = PO_EXPERT,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(tvh_codec_profile_libx26x_t, params),
+            },
+            {}
+        }
+    },
+    .open = (codec_profile_open_meth)tvh_codec_profile_libx264_open,
+};
+
+
+TVHVideoCodec tvh_codec_libx264 = {
+    .name     = "libx264",
+    .size     = sizeof(tvh_codec_profile_libx26x_t),
+    .idclass  = &codec_profile_libx264_class,
+    .profiles = libx264_profiles,
+};
+
+#endif
+
+
+/* libx265 ================================================================== */
+
+#if ENABLE_LIBX265
+
+#include <x265.h>
+
+
+static int
+tvh_codec_profile_libx265_open(tvh_codec_profile_libx26x_t *self,
+                               AVDictionary **opts)
+{
+    // bit_rate or crf
+    if (self->bit_rate) {
+        AV_DICT_SET_BIT_RATE(opts, self->bit_rate);
+    }
+    else {
+        AV_DICT_SET_CRF(opts, self->crf, 18);
+    }
+    if (self->params && strlen(self->params)) {
+        AV_DICT_SET(opts, "x265-params", self->params, 0);
+    }
+    return 0;
+}
+
+
+static htsmsg_t *
+codec_profile_libx265_class_preset_list(void *obj, const char *lang)
+{
+    return strlist2htsmsg(x265_preset_names);
+}
+
+
+static htsmsg_t *
+codec_profile_libx265_class_tune_list(void *obj, const char *lang)
+{
+    return strlist2htsmsg(x265_tune_names);
+}
+
+
+static const codec_profile_class_t codec_profile_libx265_class = {
+    {
+        .ic_super      = (idclass_t *)&codec_profile_libx26x_class,
+        .ic_class      = "codec_profile_libx265",
+        .ic_caption    = N_("libx265"),
+        .ic_properties = (const property_t[]){
+            {
+                .type     = PT_STR,
+                .id       = "preset",
+                .name     = N_("Preset"),
+                .desc     = N_("set the x265 preset."),
+                .group    = 5,
+                .opts     = PO_EXPERT,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(tvh_codec_profile_libx26x_t, preset),
+                .list     = codec_profile_libx265_class_preset_list,
+                .def.s    = "superfast",
+            },
+            {
+                .type     = PT_STR,
+                .id       = "tune",
+                .name     = N_("Tune"),
+                .desc     = N_("set the x265 tune parameter."),
+                .group    = 5,
+                .opts     = PO_EXPERT,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(tvh_codec_profile_libx26x_t, tune),
+                .list     = codec_profile_libx265_class_tune_list,
+                .def.s    = "fastdecode",
+            },
+            {
+                .type     = PT_STR,
+                .id       = "params",
+                .name     = N_("Parameters"),
+                .desc     = N_("Override the configuration using a ':' separated "
+                               "list of key=value parameters."),
+                .group    = 5,
+                .opts     = PO_EXPERT,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(tvh_codec_profile_libx26x_t, params),
+            },
+            {}
+        }
+    },
+    .open = (codec_profile_open_meth)tvh_codec_profile_libx265_open,
+};
+
+
+TVHVideoCodec tvh_codec_libx265 = {
+    .name    = "libx265",
+    .size    = sizeof(tvh_codec_profile_libx26x_t),
+    .idclass = &codec_profile_libx265_class,
+};
+
+#endif
diff --git a/src/transcoding/codec/codecs/libs/omx.c b/src/transcoding/codec/codecs/libs/omx.c
new file mode 100644 (file)
index 0000000..6cf4ecb
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ *  tvheadend - Codec Profiles
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/codec/internals.h"
+
+
+/* omx ====================================================================== */
+
+typedef struct {
+    TVHVideoCodecProfile;
+    const char *libname;
+    const char *libprefix;
+    int zerocopy;
+} tvh_codec_profile_omx_t;
+
+
+static int
+tvh_codec_profile_omx_open(tvh_codec_profile_omx_t *self, AVDictionary **opts)
+{
+    if (self->libname && strlen(self->libname)) {
+        AV_DICT_SET(opts, "omx_libname", self->libname, 0);
+    }
+    if (self->libprefix && strlen(self->libprefix)) {
+        AV_DICT_SET(opts, "omx_libprefix", self->libprefix, 0);
+    }
+    AV_DICT_SET_INT(opts, "zerocopy", self->zerocopy, 0);
+    return 0;
+}
+
+
+static const codec_profile_class_t codec_profile_omx_class = {
+    {
+        .ic_super   = (idclass_t *)&codec_profile_video_class,
+        .ic_class   = "codec_profile_omx",
+        .ic_caption = N_("omx_h264"),
+        .ic_properties = (const property_t[]){
+            {
+                .type     = PT_STR,
+                .id       = "omx_libname",
+                .name     = N_("Library name"),
+                .desc     = N_("OpenMAX library name."),
+                .group    = 5,
+                .opts     = PO_EXPERT,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(tvh_codec_profile_omx_t, libname),
+            },
+            {
+                .type     = PT_STR,
+                .id       = "omx_libprefix",
+                .name     = N_("Library prefix"),
+                .desc     = N_("OpenMAX library prefix."),
+                .group    = 5,
+                .opts     = PO_EXPERT,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(tvh_codec_profile_omx_t, libprefix),
+            },
+            {
+                .type     = PT_BOOL,
+                .id       = "zerocopy",
+                .name     = N_("Zerocopy"),
+                .desc     = N_("Try to avoid copying input frames if possible."),
+                .group    = 5,
+                .opts     = PO_EXPERT,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(tvh_codec_profile_omx_t, zerocopy),
+                .def.i    = 0,
+            },
+            {}
+        }
+    },
+    .open = (codec_profile_open_meth)tvh_codec_profile_omx_open,
+};
+
+
+/* h264_omx ================================================================= */
+
+TVHVideoCodec tvh_codec_omx_h264 = {
+    .name    = "h264_omx",
+    .size    = sizeof(tvh_codec_profile_omx_t),
+    .idclass = &codec_profile_omx_class,
+};
diff --git a/src/transcoding/codec/codecs/libs/vaapi.c b/src/transcoding/codec/codecs/libs/vaapi.c
new file mode 100644 (file)
index 0000000..446e04f
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ *  tvheadend - Codec Profiles
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/codec/internals.h"
+
+
+#define AV_DICT_SET_QP(d, v, a) \
+    AV_DICT_SET_INT((d), "qp", (v) ? (v) : (a), AV_DICT_DONT_OVERWRITE)
+
+
+/* vaapi ==================================================================== */
+
+typedef struct {
+    TVHVideoCodecProfile;
+    int qp;
+    int quality;
+} tvh_codec_profile_vaapi_t;
+
+
+static int
+tvh_codec_profile_vaapi_open(tvh_codec_profile_vaapi_t *self,
+                             AVDictionary **opts)
+{
+    // pix_fmt
+    AV_DICT_SET_PIX_FMT(opts, self->pix_fmt, AV_PIX_FMT_VAAPI);
+    return 0;
+}
+
+
+static const codec_profile_class_t codec_profile_vaapi_class = {
+    {
+        .ic_super      = (idclass_t *)&codec_profile_video_class,
+        .ic_class      = "codec_profile_vaapi",
+        .ic_caption    = N_("vaapi"),
+        .ic_properties = (const property_t[]){
+            {
+                .type     = PT_DBL,
+                .id       = "bit_rate",
+                .name     = N_("Bitrate (kb/s) (0=auto)"),
+                .desc     = N_("Target bitrate."),
+                .group    = 3,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(TVHCodecProfile, bit_rate),
+                .def.d    = 0,
+            },
+            {
+                .type     = PT_INT,
+                .id       = "qp",
+                .name     = N_("Constant QP (0=auto)"),
+                .group    = 3,
+                .desc     = N_("Fixed QP of P frames [0-52]."),
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(tvh_codec_profile_vaapi_t, qp),
+                .intextra = INTEXTRA_RANGE(0, 52, 1),
+                .def.i    = 0,
+            },
+            {}
+        }
+    },
+    .open = (codec_profile_open_meth)tvh_codec_profile_vaapi_open,
+};
+
+
+/* h264_vaapi =============================================================== */
+
+static const AVProfile vaapi_h264_profiles[] = {
+    { FF_PROFILE_H264_BASELINE,             "Baseline" },
+    { FF_PROFILE_H264_CONSTRAINED_BASELINE, "Constrained Baseline" },
+    { FF_PROFILE_H264_MAIN,                 "Main" },
+    { FF_PROFILE_H264_HIGH,                 "High" },
+    { FF_PROFILE_UNKNOWN },
+};
+
+static int
+tvh_codec_profile_vaapi_h264_open(tvh_codec_profile_vaapi_t *self,
+                                  AVDictionary **opts)
+{
+    // bit_rate or qp
+    if (self->bit_rate) {
+        AV_DICT_SET_BIT_RATE(opts, self->bit_rate);
+    }
+    else {
+        AV_DICT_SET_QP(opts, self->qp, 20);
+    }
+    AV_DICT_SET_INT(opts, "quality", self->quality, 0);
+    return 0;
+}
+
+
+static const codec_profile_class_t codec_profile_vaapi_h264_class = {
+    {
+        .ic_super      = (idclass_t *)&codec_profile_vaapi_class,
+        .ic_class      = "codec_profile_vaapi_h264",
+        .ic_caption    = N_("vaapi_h264"),
+        .ic_properties = (const property_t[]){
+            {
+                .type     = PT_INT,
+                .id       = "quality",
+                .name     = N_("Quality (0=auto)"),
+                .desc     = N_("Set encode quality (trades off against speed, "
+                               "higher is faster) [0-8]."),
+                .group    = 5,
+                .opts     = PO_EXPERT,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(tvh_codec_profile_vaapi_t, quality),
+                .intextra = INTEXTRA_RANGE(0, 8, 1),
+                .def.i    = 0,
+            },
+            {}
+        }
+    },
+    .open = (codec_profile_open_meth)tvh_codec_profile_vaapi_h264_open,
+};
+
+
+TVHVideoCodec tvh_codec_vaapi_h264 = {
+    .name     = "h264_vaapi",
+    .size     = sizeof(tvh_codec_profile_vaapi_t),
+    .idclass  = &codec_profile_vaapi_h264_class,
+    .profiles = vaapi_h264_profiles,
+};
+
+
+/* hevc_vaapi =============================================================== */
+
+static const AVProfile vaapi_hevc_profiles[] = {
+    { FF_PROFILE_HEVC_MAIN, "Main" },
+    { FF_PROFILE_UNKNOWN },
+};
+
+static int
+tvh_codec_profile_vaapi_hevc_open(tvh_codec_profile_vaapi_t *self,
+                                  AVDictionary **opts)
+{
+    // bit_rate or qp
+    if (self->bit_rate) {
+        AV_DICT_SET_BIT_RATE(opts, self->bit_rate);
+    }
+    else {
+        AV_DICT_SET_QP(opts, self->qp, 25);
+    }
+    return 0;
+}
+
+
+static const codec_profile_class_t codec_profile_vaapi_hevc_class = {
+    {
+        .ic_super      = (idclass_t *)&codec_profile_vaapi_class,
+        .ic_class      = "codec_profile_vaapi_hevc",
+        .ic_caption    = N_("vaapi_hevc")
+    },
+    .open = (codec_profile_open_meth)tvh_codec_profile_vaapi_hevc_open,
+};
+
+
+TVHVideoCodec tvh_codec_vaapi_hevc = {
+    .name     = "hevc_vaapi",
+    .size     = sizeof(tvh_codec_profile_vaapi_t),
+    .idclass  = &codec_profile_vaapi_hevc_class,
+    .profiles = vaapi_hevc_profiles,
+};
diff --git a/src/transcoding/codec/codecs/mp2.c b/src/transcoding/codec/codecs/mp2.c
new file mode 100644 (file)
index 0000000..84952a5
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ *  tvheadend - Codec Profiles
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/codec/internals.h"
+
+
+/* mp2 ====================================================================== */
+
+static int
+tvh_codec_profile_mp2_open(TVHCodecProfile *self, AVDictionary **opts)
+{
+    AV_DICT_SET_TVH_REQUIRE_META(opts, 0);
+    // bit_rate
+    if (self->bit_rate) {
+        AV_DICT_SET_BIT_RATE(opts, self->bit_rate);
+    }
+    return 0;
+}
+
+
+static htsmsg_t *
+codec_profile_mp2_class_bit_rate_list(void *obj, const char *lang)
+{
+    // This is a place holder.
+    // The real list is set in src/webui/static/app/codec.js
+    return htsmsg_create_list();
+}
+
+
+static const codec_profile_class_t codec_profile_mp2_class = {
+    {
+        .ic_super      = (idclass_t *)&codec_profile_audio_class,
+        .ic_class      = "codec_profile_mp2",
+        .ic_caption    = N_("mp2"),
+        .ic_properties = (const property_t[]){
+            {
+                .type     = PT_DBL,
+                .id       = "bit_rate",
+                .name     = N_("Bitrate (kb/s)"),
+                .desc     = N_("Constant bitrate (CBR) mode."),
+                .group    = 3,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(TVHCodecProfile, bit_rate),
+                .list     = codec_profile_mp2_class_bit_rate_list,
+                .def.d    = 0,
+            },
+            {}
+        }
+    },
+    .open = tvh_codec_profile_mp2_open,
+};
+
+
+TVHAudioCodec tvh_codec_mp2 = {
+    .name    = "mp2",
+    .size    = sizeof(TVHAudioCodecProfile),
+    .idclass = &codec_profile_mp2_class,
+};
diff --git a/src/transcoding/codec/codecs/mpeg2video.c b/src/transcoding/codec/codecs/mpeg2video.c
new file mode 100644 (file)
index 0000000..d78f8d4
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ *  tvheadend - Codec Profiles
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/codec/internals.h"
+
+
+/* mpeg2video =============================================================== */
+
+static int
+tvh_codec_profile_mpeg2video_open(TVHCodecProfile *self, AVDictionary **opts)
+{
+    // bit_rate or global_quality
+    if (self->bit_rate) {
+        AV_DICT_SET_BIT_RATE(opts, self->bit_rate);
+    }
+    else {
+        AV_DICT_SET_GLOBAL_QUALITY(opts, self->qscale, 5);
+    }
+    return 0;
+}
+
+
+static const codec_profile_class_t codec_profile_mpeg2video_class = {
+    {
+        .ic_super      = (idclass_t *)&codec_profile_video_class,
+        .ic_class      = "codec_profile_mpeg2video",
+        .ic_caption    = N_("mpeg2video"),
+        .ic_properties = (const property_t[]){
+            {
+                .type     = PT_DBL,
+                .id       = "bit_rate",
+                .name     = N_("Bitrate (kb/s) (0=auto)"),
+                .desc     = N_("Constant bitrate (CBR) mode."),
+                .group    = 3,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(TVHCodecProfile, bit_rate),
+                .def.d    = 0,
+            },
+            {
+                .type     = PT_DBL,
+                .id       = "qscale",
+                .name     = N_("Quality (0=auto)"),
+                .desc     = N_("Variable bitrate (VBR) mode [0-31]."),
+                .group    = 3,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(TVHCodecProfile, qscale),
+                .intextra = INTEXTRA_RANGE(0, 31, 1),
+                .def.d    = 0,
+            },
+            {}
+        }
+    },
+    .open = tvh_codec_profile_mpeg2video_open,
+};
+
+
+TVHVideoCodec tvh_codec_mpeg2video = {
+    .name    = "mpeg2video",
+    .size    = sizeof(TVHVideoCodecProfile),
+    .idclass = &codec_profile_mpeg2video_class,
+};
diff --git a/src/transcoding/codec/codecs/vorbis.c b/src/transcoding/codec/codecs/vorbis.c
new file mode 100644 (file)
index 0000000..89df6b2
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ *  tvheadend - Codec Profiles
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/codec/internals.h"
+
+
+/* vorbis =================================================================== */
+
+static int
+tvh_codec_profile_vorbis_open(TVHCodecProfile *self, AVDictionary **opts)
+{
+    AV_DICT_SET_GLOBAL_QUALITY(opts, self->qscale, 5);
+    return 0;
+}
+
+
+// see vorbis_encode_init() in ffmpeg-3.0.2/libavcodec/vorbisenc.c
+static const uint64_t vorbis_channel_layouts[] = {
+    AV_CH_LAYOUT_STEREO,
+    0
+};
+
+
+static const codec_profile_class_t codec_profile_vorbis_class = {
+    {
+        .ic_super      = (idclass_t *)&codec_profile_audio_class,
+        .ic_class      = "codec_profile_vorbis",
+        .ic_caption    = N_("vorbis"),
+        .ic_properties = (const property_t[]){
+            {
+                .type     = PT_DBL,
+                .id       = "qscale",
+                .name     = N_("Quality (0=auto)"),
+                .desc     = N_("Variable bitrate (VBR) mode [0-10]."),
+                .group    = 3,
+                .get_opts = codec_profile_class_get_opts,
+                .off      = offsetof(TVHCodecProfile, qscale),
+                .intextra = INTEXTRA_RANGE(0, 10, 1),
+                .def.d    = 0,
+            },
+            {}
+        }
+    },
+    .open = tvh_codec_profile_vorbis_open,
+};
+
+
+TVHAudioCodec tvh_codec_vorbis = {
+    .name            = "vorbis",
+    .size            = sizeof(TVHAudioCodecProfile),
+    .idclass         = &codec_profile_vorbis_class,
+    .channel_layouts = vorbis_channel_layouts,
+};
diff --git a/src/transcoding/codec/internals.h b/src/transcoding/codec/internals.h
new file mode 100644 (file)
index 0000000..3d31067
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+ *  tvheadend - Codec Profiles
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef TVH_TRANSCODING_CODEC_INTERNALS_H__
+#define TVH_TRANSCODING_CODEC_INTERNALS_H__
+
+
+#include "transcoding/codec.h"
+#include "transcoding/memutils.h"
+
+
+#define AUTO_STR "auto"
+
+#define ADD_ENTRY(l, m, kt, k, vt, v) \
+    do { \
+        htsmsg_add_##kt((m), "key", (k)); \
+        htsmsg_add_##vt((m), "val", (v)); \
+        htsmsg_add_msg((l), NULL, (m)); \
+    } while (0)
+
+#define ADD_VAL(l, m, t, v) ADD_ENTRY(l, m, t, v, t, v)
+
+#define ADD_STR_VAL(l, m, v) ADD_VAL(l, m, str, v)
+#define ADD_S32_VAL(l, m, v) ADD_VAL(l, m, s32, v)
+#define ADD_U32_VAL(l, m, v) ADD_VAL(l, m, u32, v)
+#define ADD_S64_VAL(l, m, v) ADD_VAL(l, m, s64, v)
+
+
+#define _tvh_codec_get_opts(c, a, o, T) \
+    ((c) ? tvh_codec_base_get_opts((c), (o), ((((T *)(c))->a) != NULL)) : (o))
+
+#define tvh_codec_get_opts(c, a, o) \
+    _tvh_codec_get_opts(c, a, o, TVHCodec)
+
+#define tvh_codec_video_get_opts(c, a, o) \
+    _tvh_codec_get_opts(c, a, o, TVHVideoCodec)
+
+#define tvh_codec_audio_get_opts(c, a, o) \
+    _tvh_codec_get_opts(c, a, o, TVHAudioCodec)
+
+
+#define _tvh_codec_get_list(c, a, T, n) \
+    (((c) && tvh_codec_is_enabled((c))) ? tvh_codec##n##get_list_##a(((T *)(c))) : NULL)
+
+#define tvh_codec_get_list(c, a) \
+    _tvh_codec_get_list(c, a, TVHCodec, _)
+
+#define tvh_codec_video_get_list(c, a) \
+    _tvh_codec_get_list(c, a, TVHVideoCodec, _video_)
+
+#define tvh_codec_audio_get_list(c, a) \
+    _tvh_codec_get_list(c, a, TVHAudioCodec, _audio_)
+
+
+#define _tvh_codec_getattr(c, a, t, T) \
+    (((c) && tvh_codec_is_enabled((c)) && tvh_codec_get_type((c)) == (t)) ? (((T *)(c))->a) : NULL)
+
+#define tvh_codec_video_getattr(c, a) \
+    _tvh_codec_getattr(c, a, AVMEDIA_TYPE_VIDEO, TVHVideoCodec)
+
+#define tvh_codec_audio_getattr(c, a) \
+    _tvh_codec_getattr(c, a, AVMEDIA_TYPE_AUDIO, TVHAudioCodec)
+
+
+
+#define AV_DICT_SET(d, k, v, f) \
+    do { \
+        if (av_dict_set((d), (k), (v), (f)) < 0) { \
+            return -1; \
+        } \
+    } while (0)
+
+#define AV_DICT_SET_INT(d, k, v, f) \
+    do { \
+        if (av_dict_set_int((d), (k), (v), (f)) < 0) { \
+            return -1; \
+        } \
+    } while (0)
+
+#define AV_DICT_SET_TVH_REQUIRE_META(d, v) \
+    AV_DICT_SET_INT((d), "tvh_require_meta", (v), AV_DICT_DONT_OVERWRITE)
+
+#define AV_DICT_SET_FLAGS(d, v) \
+    AV_DICT_SET((d), "flags", (v), AV_DICT_APPEND)
+
+#define AV_DICT_SET_FLAGS_GLOBAL_HEADER(d) \
+    AV_DICT_SET_FLAGS((d), "+global_header")
+
+#define AV_DICT_SET_BIT_RATE(d, v) \
+    AV_DICT_SET_INT((d), "b", (v) * 1000, AV_DICT_DONT_OVERWRITE)
+
+#define AV_DICT_SET_GLOBAL_QUALITY(d, v, a) \
+    do { \
+        AV_DICT_SET_FLAGS((d), "+qscale"); \
+        AV_DICT_SET_INT((d), "global_quality", ((v) ? (v) : (a)) * FF_QP2LAMBDA, \
+                        AV_DICT_DONT_OVERWRITE); \
+    } while (0)
+
+#define AV_DICT_SET_CRF(d, v, a) \
+    AV_DICT_SET_INT((d), "crf", (v) ? (v) : (a), AV_DICT_DONT_OVERWRITE)
+
+#define AV_DICT_SET_PIX_FMT(d, v, a) \
+    AV_DICT_SET_INT((d), "pix_fmt", ((v) != AV_PIX_FMT_NONE) ? (v) : (a), \
+                    AV_DICT_DONT_OVERWRITE)
+
+
+/* codec_profile_class ====================================================== */
+
+uint32_t
+codec_profile_class_get_opts(void *obj, uint32_t opts);
+
+uint32_t
+codec_profile_class_profile_get_opts(void *obj, uint32_t opts);
+
+
+/* AVCodec ================================================================== */
+
+const char *
+codec_get_title(AVCodec *self);
+
+
+/* TVHCodec ================================================================= */
+
+enum AVMediaType
+tvh_codec_get_type(TVHCodec *self);
+
+const char *
+tvh_codec_get_type_string(TVHCodec *self);
+
+AVCodec *
+tvh_codec_get_codec(TVHCodec *self);
+
+int
+tvh_codec_is_enabled(TVHCodec *self);
+
+TVHCodec *
+tvh_codec_find(const char *name);
+
+void
+tvh_codecs_register(void);
+
+void
+tvh_codecs_forget(void);
+
+
+/* codec_profile_class */
+
+uint32_t
+tvh_codec_base_get_opts(TVHCodec *self, uint32_t opts, int visible);
+
+htsmsg_t *
+tvh_codec_get_list_profiles(TVHCodec *self);
+
+/* codec_profile_video_class */
+
+typedef struct tvh_codec_video_t {
+    TVHCodec;
+    const enum AVPixelFormat *pix_fmts;
+} TVHVideoCodec;
+
+
+/* codec_profile_audio_class */
+
+typedef struct tvh_codec_audio_t {
+    TVHCodec;
+    const enum AVSampleFormat *sample_fmts;
+    const int *sample_rates;
+    const uint64_t *channel_layouts;
+} TVHAudioCodec;
+
+
+/* TVHCodecProfile ========================================================== */
+
+void
+tvh_codec_profile_remove(TVHCodecProfile *self, int delete);
+
+TVHCodec *
+tvh_codec_profile_get_codec(TVHCodecProfile *self);
+
+TVHCodecProfile *
+tvh_codec_profile_find(const char *name);
+
+void
+tvh_codec_profiles_load(void);
+
+void
+tvh_codec_profiles_remove(void);
+
+
+/* video */
+
+extern const codec_profile_class_t codec_profile_video_class;
+
+typedef struct tvh_codec_profile_video_t {
+    TVHCodecProfile;
+    int deinterlace;
+    int height;
+    int hwaccel;
+    int pix_fmt;
+    int crf;
+    AVRational size;
+} TVHVideoCodecProfile;
+
+
+/* audio */
+
+extern const codec_profile_class_t codec_profile_audio_class;
+
+typedef struct tvh_codec_profile_audio_t {
+    TVHCodecProfile;
+    const char *language;
+    int sample_fmt;
+    int sample_rate;
+    int64_t channel_layout;
+} TVHAudioCodecProfile;
+
+
+#endif // TVH_TRANSCODING_CODEC_INTERNALS_H__
diff --git a/src/transcoding/codec/module.c b/src/transcoding/codec/module.c
new file mode 100644 (file)
index 0000000..11c3e8f
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ *  tvheadend - Codec Profiles
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "internals.h"
+
+
+/* module level ============================================================= */
+
+TVHCodecProfile *
+codec_find_profile(const char *name)
+{
+    return name ? tvh_codec_profile_find(name) : NULL;
+}
+
+
+htsmsg_t *
+codec_get_profiles_list(enum AVMediaType media_type)
+{
+    htsmsg_t *list = NULL, *map = NULL;
+    TVHCodecProfile *profile = NULL;
+    TVHCodec *codec = NULL;
+
+
+    if ((list = htsmsg_create_list())) {
+        LIST_FOREACH(profile, &tvh_codec_profiles, link) {
+            if ((codec = tvh_codec_profile_get_codec(profile)) &&
+                tvh_codec_get_type(codec) == media_type) {
+                if (!(map = htsmsg_create_map())) {
+                    htsmsg_destroy(list);
+                    list = NULL;
+                    break;
+                }
+                ADD_ENTRY(list, map,
+                          str, tvh_codec_profile_get_name(profile),
+                          str, tvh_codec_profile_get_title(profile));
+            }
+        }
+    }
+    return list;
+}
+
+
+void
+codec_init(void)
+{
+    // codecs
+    tvh_codecs_register();
+    // codec profiles
+    tvh_codec_profiles_load();
+}
+
+
+void
+codec_done(void)
+{
+    pthread_mutex_lock(&global_lock);
+
+    // codec profiles
+    tvh_codec_profiles_remove();
+    // codecs
+    tvh_codecs_forget();
+
+    pthread_mutex_unlock(&global_lock);
+}
diff --git a/src/transcoding/codec/profile.c b/src/transcoding/codec/profile.c
new file mode 100644 (file)
index 0000000..2604759
--- /dev/null
@@ -0,0 +1,338 @@
+/*
+ *  tvheadend - Codec Profiles
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "internals.h"
+
+#include "settings.h"
+
+
+struct TVHCodecProfiles tvh_codec_profiles;
+
+
+/* TVHCodecProfile ========================================================== */
+
+static TVHCodecProfile *
+tvh_codec_profile_alloc(TVHCodec *codec)
+{
+    TVHCodecProfile *self = NULL;
+
+    if ((self = calloc(1, codec->size))) {
+        self->codec = codec;
+    }
+    return self;
+}
+
+
+static void
+tvh_codec_profile_free(TVHCodecProfile *self)
+{
+    if (self) {
+        self->codec = NULL;
+        free(self);
+        self = NULL;
+    }
+}
+
+
+static void
+tvh_codec_profile_load(htsmsg_field_t *config)
+{
+    htsmsg_t *conf = NULL;
+    const char *name = NULL;
+
+    if ((conf = htsmsg_field_get_map(config)) &&
+        tvh_codec_profile_create(conf, config->hmf_name, 0)) {
+        tvherror(LS_CODEC, "unable to load codec profile: '%s'",
+                 (name = htsmsg_get_str(conf, "name")) ? name : "<unknown>");
+    }
+}
+
+
+static int
+tvh_codec_profile_setup(TVHCodecProfile *self, tvh_ssc_t *ssc)
+{
+    const idclass_t *idclass = (&self->idnode)->in_class;
+    const codec_profile_class_t *codec_profile_class = NULL;
+    int ret = 0;
+
+    while (idclass) {
+        codec_profile_class = (codec_profile_class_t *)idclass;
+        if (codec_profile_class->setup &&
+            (ret = codec_profile_class->setup(self, ssc))) {
+            break;
+        }
+        idclass = idclass->ic_super;
+    }
+    return ret;
+}
+
+
+/* exposed */
+
+int
+tvh_codec_profile_create(htsmsg_t *conf, const char *uuid, int save)
+{
+    const char *profile_name = NULL, *codec_name = NULL;
+    TVHCodec *codec = NULL;
+    TVHCodecProfile *profile = NULL;
+
+    lock_assert(&global_lock);
+
+    if ((!(profile_name = htsmsg_get_str(conf, "name")) ||
+         profile_name[0] == '\0' || !strcmp(profile_name, "copy") ||
+         strlen(profile_name) >= TVH_NAME_LEN) ||
+        (!(codec_name = htsmsg_get_str(conf, "codec_name")) ||
+         codec_name[0] == '\0')) {
+        tvherror(LS_CODEC, "missing/empty/wrong 'name' or 'codec_name'");
+        return EINVAL;
+    }
+    if ((profile = tvh_codec_profile_find(profile_name))) {
+        tvherror(LS_CODEC, "codec profile '%s' already exists", profile_name);
+        return EEXIST;
+    }
+    if (!(codec = tvh_codec_find(codec_name))) {
+        tvherror(LS_CODEC, "codec '%s' not found", codec_name);
+        return ENOENT;
+    }
+    if (!(profile = tvh_codec_profile_alloc(codec))) {
+        tvherror(LS_CODEC, "failed to allocate TVHCodecProfile");
+        return ENOMEM;
+    }
+    if (idnode_insert(&profile->idnode, uuid, (idclass_t *)codec->idclass, 0)) {
+        tvh_codec_profile_free(profile);
+        tvherror(LS_CODEC, "failed to insert idnode");
+        return EINVAL;
+    }
+    idnode_load(&profile->idnode, conf);
+    LIST_INSERT_HEAD(&tvh_codec_profiles, profile, link);
+    if (save) {
+        idnode_changed(&profile->idnode);
+    }
+    tvhinfo(LS_CODEC, "'%s' codec profile created", profile_name);
+    return 0;
+}
+
+
+const char *
+tvh_codec_profile_get_status(TVHCodecProfile *self)
+{
+    TVHCodec *codec = tvh_codec_profile_get_codec(self);
+
+    if (codec && tvh_codec_is_enabled(codec)) {
+        return "codecEnabled";
+    }
+    return "codecDisabled";
+}
+
+
+const char *
+tvh_codec_profile_get_name(TVHCodecProfile *self)
+{
+    return self->name;
+}
+
+
+const char *
+tvh_codec_profile_get_title(TVHCodecProfile *self)
+{
+    static char profile_title[TVH_TITLE_LEN];
+
+    memset(profile_title, 0, sizeof(profile_title));
+    if (
+        str_snprintf(profile_title, sizeof(profile_title),
+            (self->description && strcmp(self->description, "")) ? "%s (%s)" : "%s%s",
+            self->name, self->description ? self->description : "")
+       ) {
+        return NULL;
+    }
+    return profile_title;
+}
+
+
+AVCodec *
+tvh_codec_profile_get_avcodec(TVHCodecProfile *self)
+{
+    TVHCodec *codec = tvh_codec_profile_get_codec(self);
+    return codec ? tvh_codec_get_codec(codec) : NULL;
+}
+
+
+/* transcode api */
+int
+tvh_codec_profile_is_copy(TVHCodecProfile *self, tvh_ssc_t *ssc)
+{
+    const idclass_t *idclass = NULL;
+    const codec_profile_class_t *codec_profile_class = NULL;
+    AVCodec *avcodec = NULL;
+    tvh_sct_t out_type = SCT_UNKNOWN;
+
+
+    if (tvh_codec_profile_setup(self, ssc)) {
+        return -1;
+    }
+    if (!(avcodec = tvh_codec_profile_get_avcodec(self))) {
+        tvherror(LS_CODEC, "profile '%s' is disabled", self->name);
+        return -1;
+    }
+    if ((out_type = codec_id2streaming_component_type(avcodec->id)) == SCT_UNKNOWN) {
+        tvherror(LS_CODEC, "unknown type for profile '%s'", self->name);
+        return -1;
+    }
+    if (out_type == ssc->ssc_type) {
+        idclass = (&self->idnode)->in_class;
+        while (idclass) {
+            codec_profile_class = (codec_profile_class_t *)idclass;
+            if (codec_profile_class->is_copy &&
+                !codec_profile_class->is_copy(self, ssc)) {
+                return 0;
+            }
+            idclass = idclass->ic_super;
+        }
+        return 1;
+    }
+    return 0;
+}
+
+
+int
+tvh_codec_profile_open(TVHCodecProfile *self, AVDictionary **opts)
+{
+    const idclass_t *idclass = (&self->idnode)->in_class;
+    const codec_profile_class_t *codec_profile_class = NULL;
+    int ret = 0;
+
+    while (idclass) {
+        codec_profile_class = (codec_profile_class_t *)idclass;
+        if (codec_profile_class->open &&
+            (ret = codec_profile_class->open(self, opts))) {
+            break;
+        }
+        idclass = idclass->ic_super;
+    }
+    return ret;
+}
+
+
+/* video */
+int
+tvh_codec_profile_video_get_hwaccel(TVHCodecProfile *self)
+{
+    TVHCodec *codec = tvh_codec_profile_get_codec(self);
+    if (codec && tvh_codec_is_enabled(codec) &&
+        tvh_codec_get_type(codec) == AVMEDIA_TYPE_VIDEO) {
+        return ((TVHVideoCodecProfile *)self)->hwaccel;
+    }
+    return -1;
+}
+
+const enum AVPixelFormat *
+tvh_codec_profile_video_get_pix_fmts(TVHCodecProfile *self)
+{
+    TVHCodec *codec = tvh_codec_profile_get_codec(self);
+    return tvh_codec_video_getattr(codec, pix_fmts);
+}
+
+
+/* audio */
+const enum AVSampleFormat *
+tvh_codec_profile_audio_get_sample_fmts(TVHCodecProfile *self)
+{
+    TVHCodec *codec = tvh_codec_profile_get_codec(self);
+    return tvh_codec_audio_getattr(codec, sample_fmts);
+}
+
+const int *
+tvh_codec_profile_audio_get_sample_rates(TVHCodecProfile *self)
+{
+    TVHCodec *codec = tvh_codec_profile_get_codec(self);
+    return tvh_codec_audio_getattr(codec, sample_rates);
+}
+
+const uint64_t *
+tvh_codec_profile_audio_get_channel_layouts(TVHCodecProfile *self)
+{
+    TVHCodec *codec = tvh_codec_profile_get_codec(self);
+    return tvh_codec_audio_getattr(codec, channel_layouts);
+}
+
+
+/* internal api */
+void
+tvh_codec_profile_remove(TVHCodecProfile *self, int delete)
+{
+    static char uuid[UUID_HEX_SIZE];
+
+    memset(uuid, 0, sizeof(uuid));
+    idnode_save_check(&self->idnode, delete);
+    if (delete) {
+        hts_settings_remove("codec/%s", idnode_uuid_as_str(&self->idnode, uuid));
+    }
+    LIST_REMOVE(self, link);
+    idnode_unlink(&self->idnode);
+    tvh_codec_profile_free(self);
+}
+
+
+TVHCodec *
+tvh_codec_profile_get_codec(TVHCodecProfile *self)
+{
+    return self ? self->codec : NULL;
+}
+
+
+TVHCodecProfile *
+tvh_codec_profile_find(const char *name)
+{
+    TVHCodecProfile *profile = NULL;
+
+    LIST_FOREACH(profile, &tvh_codec_profiles, link) {
+        if (!strcmp(profile->name, name)) {
+            return profile;
+        }
+    }
+    return NULL;
+}
+
+
+void
+tvh_codec_profiles_load()
+{
+    htsmsg_t *settings = NULL;
+    htsmsg_field_t *config = NULL;
+
+    LIST_INIT(&tvh_codec_profiles);
+    if ((settings = hts_settings_load("codec"))) {
+        HTSMSG_FOREACH(config, settings) {
+            tvh_codec_profile_load(config);
+        }
+        htsmsg_destroy(settings);
+        settings = NULL;
+    }
+}
+
+
+void
+tvh_codec_profiles_remove()
+{
+    tvhinfo(LS_CODEC, "removing codec profiles");
+    while (!LIST_EMPTY(&tvh_codec_profiles)) {
+        tvh_codec_profile_remove(LIST_FIRST(&tvh_codec_profiles), 0);
+    }
+}
diff --git a/src/transcoding/codec/profile_audio_class.c b/src/transcoding/codec/profile_audio_class.c
new file mode 100644 (file)
index 0000000..667ed6f
--- /dev/null
@@ -0,0 +1,309 @@
+/*
+ *  tvheadend - Codec Profiles
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "internals.h"
+
+#include "lang_codes.h"
+
+
+/* TVHCodec ================================================================= */
+
+static htsmsg_t *
+tvh_codec_audio_get_list_sample_fmts(TVHAudioCodec *self)
+{
+    htsmsg_t *list = NULL, *map = NULL;
+    const enum AVSampleFormat *sample_fmts = self->sample_fmts;
+    enum AVSampleFormat f = AV_SAMPLE_FMT_NONE;
+    const char *f_str = NULL;
+    int i;
+
+    if (sample_fmts && (list = htsmsg_create_list())) {
+        if (!(map = htsmsg_create_map())) {
+            htsmsg_destroy(list);
+            list = NULL;
+        }
+        else {
+            ADD_ENTRY(list, map, s32, f, str, AUTO_STR);
+            for (i = 0; (f = sample_fmts[i]) != AV_SAMPLE_FMT_NONE; i++) {
+                if (!(f_str = av_get_sample_fmt_name(f)) ||
+                    !(map = htsmsg_create_map())) {
+                    htsmsg_destroy(list);
+                    list = NULL;
+                    break;
+                }
+                ADD_ENTRY(list, map, s32, f, str, f_str);
+            }
+        }
+    }
+    return list;
+}
+
+
+static htsmsg_t *
+tvh_codec_audio_get_list_sample_rates(TVHAudioCodec *self)
+{
+    htsmsg_t *list = NULL, *map = NULL;
+    const int *sample_rates = self->sample_rates;
+    int r = 0, i;
+
+   if (sample_rates && (list = htsmsg_create_list())) {
+        if (!(map = htsmsg_create_map())) {
+            htsmsg_destroy(list);
+            list = NULL;
+        }
+        else {
+            ADD_ENTRY(list, map, s32, r, str, AUTO_STR);
+            for (i = 0; (r = sample_rates[i]); i++) {
+                if (!(map = htsmsg_create_map())) {
+                    htsmsg_destroy(list);
+                    list = NULL;
+                    break;
+                }
+                ADD_S32_VAL(list, map, r);
+            }
+        }
+    }
+    return list;
+}
+
+
+static htsmsg_t *
+tvh_codec_audio_get_list_channel_layouts(TVHAudioCodec *self)
+{
+    htsmsg_t *list = NULL, *map = NULL;
+    const uint64_t *channel_layouts = self->channel_layouts;
+    uint64_t l = 0;
+    static char l_buf[16];
+    int i;
+
+    if (channel_layouts && (list = htsmsg_create_list())) {
+        if (!(map = htsmsg_create_map())) {
+            htsmsg_destroy(list);
+            list = NULL;
+        }
+        else {
+            ADD_ENTRY(list, map, s64, l, str, AUTO_STR);
+            for (i = 0; (l = channel_layouts[i]); i++) {
+                if (l < INT64_MAX) {
+                    if (!(map = htsmsg_create_map())) {
+                        htsmsg_destroy(list);
+                        list = NULL;
+                        break;
+                    }
+                    memset(l_buf, 0, sizeof(l_buf));
+                    av_get_channel_layout_string(l_buf, sizeof(l_buf), 0, l);
+                    ADD_ENTRY(list, map, s64, l, str, l_buf);
+                }
+            }
+        }
+    }
+    return list;
+}
+
+
+/* TVHCodecProfile ========================================================== */
+
+static int
+tvh_codec_profile_audio_is_copy(TVHAudioCodecProfile *self, tvh_ssc_t *ssc)
+{
+    // TODO: fix me
+    // assuming default channel_layout (AV_CH_LAYOUT_STEREO)
+    // and sample_rate (48kHz) for input
+    int ssc_channels = ssc->ssc_channels ? ssc->ssc_channels : 2;
+    int ssc_sr = ssc->ssc_sri ? sri_to_rate(ssc->ssc_sri) : 48000;
+    if ((self->channel_layout &&
+         ssc_channels != av_get_channel_layout_nb_channels(self->channel_layout)) ||
+        (self->sample_rate && ssc_sr != self->sample_rate)) {
+        return 0;
+    }
+    return 1;
+}
+
+
+static int
+tvh_codec_profile_audio_open(TVHAudioCodecProfile *self, AVDictionary **opts)
+{
+    if (self->sample_fmt != AV_SAMPLE_FMT_NONE) {
+        AV_DICT_SET_INT(opts, "sample_fmt", self->sample_fmt,
+                        AV_DICT_DONT_OVERWRITE);
+    }
+    if (self->sample_rate) {
+        AV_DICT_SET_INT(opts, "sample_rate", self->sample_rate,
+                        AV_DICT_DONT_OVERWRITE);
+    }
+    if (self->channel_layout) {
+        AV_DICT_SET_INT(opts, "channel_layout", self->channel_layout,
+                        AV_DICT_DONT_OVERWRITE);
+    }
+    return 0;
+}
+
+
+/* codec_profile_audio_class ================================================ */
+
+/* codec_profile_audio_class.language */
+
+static htsmsg_t *
+codec_profile_audio_class_language_list(void *obj, const char *lang)
+{
+    // TODO: rewrite
+    htsmsg_t *list = htsmsg_create_list();
+    lang_code_t *lc = (lang_code_t *)lang_codes;
+    static char lc_buf[128];
+
+    while (lc->code2b) {
+        htsmsg_t *map = htsmsg_create_map();
+        if (!strcmp(lc->code2b, "und")) {
+            htsmsg_add_str(map, "key", "");
+            htsmsg_add_str(map, "val", tvh_gettext_lang(lang, N_("Use original")));
+        }
+        else {
+            memset(lc_buf, 0, sizeof(lc_buf));
+            if (!str_snprintf(lc_buf, sizeof(lc_buf), "%s (%s)", lc->desc, lc->code2b)) {
+                htsmsg_add_str(map, "key", lc->code2b);
+                htsmsg_add_str(map, "val", lc_buf);
+            }
+            else {
+                htsmsg_destroy(list);
+                list = NULL;
+                break;
+            }
+        }
+        htsmsg_add_msg(list, NULL, map);
+        lc++;
+    }
+    return list;
+}
+
+
+/* codec_profile_audio_class.sample_fmt */
+
+static uint32_t
+codec_profile_audio_class_sample_fmt_get_opts(void *obj, uint32_t opts)
+{
+    TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+    return tvh_codec_audio_get_opts(codec, sample_fmts, opts);
+}
+
+
+static htsmsg_t *
+codec_profile_audio_class_sample_fmt_list(void *obj, const char *lang)
+{
+    TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+    return tvh_codec_audio_get_list(codec, sample_fmts);
+}
+
+
+/* codec_profile_audio_class.sample_rate */
+
+static uint32_t
+codec_profile_audio_class_sample_rate_get_opts(void *obj, uint32_t opts)
+{
+    TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+    return tvh_codec_audio_get_opts(codec, sample_rates, opts);
+}
+
+
+static htsmsg_t *
+codec_profile_audio_class_sample_rate_list(void *obj, const char *lang)
+{
+    TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+    return tvh_codec_audio_get_list(codec, sample_rates);
+}
+
+
+/* codec_profile_audio_class.channel_layout */
+
+static uint32_t
+codec_profile_audio_class_channel_layout_get_opts(void *obj, uint32_t opts)
+{
+    TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+    return tvh_codec_audio_get_opts(codec, channel_layouts, opts);
+}
+
+
+static htsmsg_t *
+codec_profile_audio_class_channel_layout_list(void *obj, const char *lang)
+{
+    TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+    return tvh_codec_audio_get_list(codec, channel_layouts);
+}
+
+
+/* codec_profile_audio_class */
+
+const codec_profile_class_t codec_profile_audio_class = {
+    {
+        .ic_super      = (idclass_t *)&codec_profile_class,
+        .ic_class      = "codec_profile_audio",
+        .ic_caption    = N_("audio"),
+        .ic_properties = (const property_t[]) {
+            {
+                .type     = PT_STR,
+                .id       = "language",
+                .name     = N_("Language"),
+                .desc     = N_("Preferred audio language."),
+                .group    = 2,
+                .off      = offsetof(TVHAudioCodecProfile, language),
+                .list     = codec_profile_audio_class_language_list,
+                .def.s    = "",
+            },
+            {
+                .type     = PT_INT,
+                .id       = "sample_fmt",
+                .name     = N_("Sample format"),
+                .desc     = N_("Audio sample format."),
+                .group    = 4,
+                .opts     = PO_ADVANCED | PO_HIDDEN,
+                .get_opts = codec_profile_audio_class_sample_fmt_get_opts,
+                .off      = offsetof(TVHAudioCodecProfile, sample_fmt),
+                .list     = codec_profile_audio_class_sample_fmt_list,
+                .def.i    = AV_SAMPLE_FMT_NONE,
+            },
+            {
+                .type     = PT_INT,
+                .id       = "sample_rate",
+                .name     = N_("Sample rate"),
+                .desc     = N_("Samples per second."),
+                .group    = 4,
+                .opts     = PO_ADVANCED | PO_HIDDEN,
+                .get_opts = codec_profile_audio_class_sample_rate_get_opts,
+                .off      = offsetof(TVHAudioCodecProfile, sample_rate),
+                .list     = codec_profile_audio_class_sample_rate_list,
+                .def.i    = 0,
+            },
+            {
+                .type     = PT_S64,
+                .id       = "channel_layout",
+                .name     = N_("Channel layout"),
+                .desc     = N_("Audio channel layout."),
+                .group    = 4,
+                .opts     = PO_ADVANCED | PO_HIDDEN,
+                .get_opts = codec_profile_audio_class_channel_layout_get_opts,
+                .off      = offsetof(TVHAudioCodecProfile, channel_layout),
+                .list     = codec_profile_audio_class_channel_layout_list,
+                .def.s64  = 0,
+            },
+            {}
+        }
+    },
+    .is_copy = (codec_profile_is_copy_meth)tvh_codec_profile_audio_is_copy,
+    .open    = (codec_profile_open_meth)tvh_codec_profile_audio_open,
+};
diff --git a/src/transcoding/codec/profile_class.c b/src/transcoding/codec/profile_class.c
new file mode 100644 (file)
index 0000000..8326c0c
--- /dev/null
@@ -0,0 +1,292 @@
+/*
+ *  tvheadend - Codec Profiles
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "internals.h"
+
+#include "access.h"
+
+
+/* TVHCodec ================================================================= */
+
+uint32_t
+tvh_codec_base_get_opts(TVHCodec *self, uint32_t opts, int visible)
+{
+    if (!tvh_codec_is_enabled(self)) {
+        opts |= PO_RDONLY;
+    }
+    else if (visible) {
+        opts &= ~(PO_HIDDEN);
+    }
+    return opts;
+}
+
+
+htsmsg_t *
+tvh_codec_get_list_profiles(TVHCodec *self)
+{
+    htsmsg_t *list = NULL, *map = NULL;
+    const AVProfile *profiles = self->profiles;
+    AVProfile *p = NULL;
+
+    if (profiles && (list = htsmsg_create_list())) {
+        if (!(map = htsmsg_create_map())) {
+            htsmsg_destroy(list);
+            list = NULL;
+        }
+        else {
+            ADD_ENTRY(list, map, s32, FF_PROFILE_UNKNOWN, str, AUTO_STR);
+            for (p = (AVProfile *)profiles; p->profile != FF_PROFILE_UNKNOWN; p++) {
+                if (!(map = htsmsg_create_map())) {
+                    htsmsg_destroy(list);
+                    list = NULL;
+                    break;
+                }
+                ADD_ENTRY(list, map, s32, p->profile, str, p->name);
+            }
+        }
+    }
+    return list;
+}
+
+
+/* TVHCodecProfile ========================================================== */
+
+static int
+tvh_codec_profile_base_is_copy(TVHCodecProfile *self, tvh_ssc_t *ssc)
+{
+    return (!(self->bit_rate || self->qscale));
+}
+
+
+static int
+tvh_codec_profile_base_open(TVHCodecProfile *self, AVDictionary **opts)
+{
+    AV_DICT_SET_TVH_REQUIRE_META(opts, 1);
+    // profile
+    if (self->profile != FF_PROFILE_UNKNOWN) {
+        AV_DICT_SET_INT(opts, "profile", self->profile, 0);
+    }
+    return 0;
+}
+
+
+/* codec_profile_class ====================================================== */
+
+static htsmsg_t *
+codec_profile_class_save(idnode_t *idnode, char *filename, size_t fsize)
+{
+    htsmsg_t *map = NULL;
+    static char uuid[UUID_HEX_SIZE];
+
+    memset(uuid, 0, sizeof(uuid));
+    if (!str_snprintf(filename, fsize, "codec/%s", idnode_uuid_as_str(idnode, uuid)) &&
+        (map = htsmsg_create_map())) {
+        idnode_save(idnode, map);
+    }
+    return map;
+}
+
+
+static void
+codec_profile_class_delete(idnode_t *idnode)
+{
+    tvh_codec_profile_remove((TVHCodecProfile *)idnode, 1);
+}
+
+
+/* codec_profile_class.name */
+
+static int
+codec_profile_class_name_set(void *obj, const void *val)
+{
+    TVHCodecProfile *self = (TVHCodecProfile *)obj, *other = NULL;
+    const char *name = (const char *)val;
+
+    if (self) {
+        if (name && name[0] != '\0' && strcmp(name, "copy") &&
+            strlen(name) < TVH_NAME_LEN &&
+            strcmp(name, self->name ? self->name : "")) {
+            if ((other = tvh_codec_profile_find(name)) && other != self) {
+                tvherror(LS_CODEC, "profile '%s' already exists", name);
+            }
+            else {
+                if (self->name) {
+                    free((void *)self->name);
+                    self->name = NULL;
+                }
+                self->name = strdup(name);
+                return 1;
+            }
+        }
+    }
+    return 0;
+}
+
+
+/* codec_profile_class.type */
+
+static const void *
+codec_profile_class_type_get(void *obj)
+{
+    static const char *type;
+
+    TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+    type = codec ? tvh_codec_get_type_string(codec) : "<unknown>";
+    return &type;
+}
+
+
+/* codec_profile_class.enabled */
+
+static const void *
+codec_profile_class_enabled_get(void *obj)
+{
+    static int enabled;
+
+    TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+    enabled = codec ? tvh_codec_is_enabled(codec) : 0;
+    return &enabled;
+}
+
+
+/* codec_profile_class.profile */
+
+static htsmsg_t *
+codec_profile_class_profile_list(void *obj, const char *lang)
+{
+    TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+    return tvh_codec_get_list(codec, profiles);
+}
+
+
+/* codec_profile_class */
+
+const codec_profile_class_t codec_profile_class = {
+    {
+        .ic_class    = "codec_profile",
+        .ic_caption  = N_("Codec Profile"),
+        .ic_event    = "codec_profile",
+        .ic_perm_def = ACCESS_ADMIN,
+        .ic_save     = codec_profile_class_save,
+        .ic_delete   = codec_profile_class_delete,
+        .ic_groups   = (const property_group_t[]) {
+            {
+                .name   = N_("Identification"),
+                .number = 1,
+            },
+            {
+                .name   = N_("Profile Settings"),
+                .number = 2,
+            },
+            {
+                .name   = N_("Codec Settings"),
+                .number = 3,
+            },
+            {
+                .name   = N_("Advanced Settings"),
+                .number = 4,
+            },
+            {
+                .name   = N_("Expert Settings"),
+                .number = 5,
+            },
+            {}
+        },
+        .ic_properties = (const property_t[]) {
+            {
+                .type     = PT_STR,
+                .id       = "name",
+                .name     = N_("Name"),
+                .desc     = N_("Name."),
+                .group    = 1,
+                .off      = offsetof(TVHCodecProfile, name),
+                .set      = codec_profile_class_name_set,
+            },
+            {
+                .type     = PT_STR,
+                .id       = "description",
+                .name     = N_("Description"),
+                .desc     = N_("Profile description."),
+                .group    = 1,
+                .off      = offsetof(TVHCodecProfile, description),
+            },
+            {
+                .type     = PT_STR,
+                .id       = "codec_name",
+                .name     = N_("Codec"),
+                .desc     = N_("Codec name."),
+                .group    = 1,
+                .opts     = PO_RDONLY,
+                .off      = offsetof(TVHCodecProfile, codec_name),
+            },
+            {
+                .type     = PT_STR,
+                .id       = "type",
+                .name     = N_("Type"),
+                .desc     = N_("Codec type."),
+                .group    = 1,
+                .opts     = PO_RDONLY | PO_NOSAVE,
+                .get      = codec_profile_class_type_get,
+            },
+            {
+                .type     = PT_BOOL,
+                .id       = "enabled",
+                .name     = N_("Enabled"),
+                .desc     = N_("Codec status."),
+                .group    = 1,
+                .opts     = PO_RDONLY | PO_NOSAVE,
+                .get      = codec_profile_class_enabled_get,
+            },
+            {
+                .type     = PT_INT,
+                .id       = "profile",
+                .name     = N_("Profile"),
+                .desc     = N_("Profile."),
+                .group    = 4,
+                .opts     = PO_ADVANCED | PO_HIDDEN,
+                .get_opts = codec_profile_class_profile_get_opts,
+                .off      = offsetof(TVHCodecProfile, profile),
+                .list     = codec_profile_class_profile_list,
+                .def.i    = FF_PROFILE_UNKNOWN,
+            },
+            {}
+        }
+    },
+    .is_copy = tvh_codec_profile_base_is_copy,
+    .open    = tvh_codec_profile_base_open,
+};
+
+
+/* exposed */
+
+uint32_t
+codec_profile_class_get_opts(void *obj, uint32_t opts)
+{
+    TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+    return codec ? tvh_codec_base_get_opts(codec, opts, 0) : opts;
+}
+
+
+uint32_t
+codec_profile_class_profile_get_opts(void *obj, uint32_t opts)
+{
+    TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+    return tvh_codec_get_opts(codec, profiles, opts);
+}
diff --git a/src/transcoding/codec/profile_video_class.c b/src/transcoding/codec/profile_video_class.c
new file mode 100644 (file)
index 0000000..8c6f877
--- /dev/null
@@ -0,0 +1,175 @@
+/*
+ *  tvheadend - Codec Profiles
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "internals.h"
+
+#include <libavutil/pixdesc.h>
+
+
+/* TVHCodec ================================================================= */
+
+static htsmsg_t *
+tvh_codec_video_get_list_pix_fmts(TVHVideoCodec *self)
+{
+    htsmsg_t *list = NULL, *map = NULL;
+    const enum AVPixelFormat *pix_fmts = self->pix_fmts;
+    enum AVPixelFormat f = AV_PIX_FMT_NONE;
+    const char *f_str = NULL;
+    int i;
+
+    if (pix_fmts && (list = htsmsg_create_list())) {
+        if (!(map = htsmsg_create_map())) {
+            htsmsg_destroy(list);
+            list = NULL;
+        }
+        else {
+            ADD_ENTRY(list, map, s32, f, str, AUTO_STR);
+            for (i = 0; (f = pix_fmts[i]) != AV_PIX_FMT_NONE; i++) {
+                if (!(f_str = av_get_pix_fmt_name(f)) ||
+                    !(map = htsmsg_create_map())) {
+                    htsmsg_destroy(list);
+                    list = NULL;
+                    break;
+                }
+                ADD_ENTRY(list, map, s32, f, str, f_str);
+            }
+        }
+    }
+    return list;
+}
+
+
+/* TVHCodecProfile ========================================================== */
+
+static int
+tvh_codec_profile_video_setup(TVHVideoCodecProfile *self, tvh_ssc_t *ssc)
+{
+    self->size.den = ssc->ssc_height;
+    self->size.num = ssc->ssc_width;
+    if (self->height) {
+        self->size.den = self->height;
+        self->size.den += self->size.den & 1;
+        self->size.num = self->size.den * ((double)ssc->ssc_width / ssc->ssc_height);
+        self->size.num += self->size.num & 1;
+    }
+    return 0;
+}
+
+
+static int
+tvh_codec_profile_video_is_copy(TVHVideoCodecProfile *self, tvh_ssc_t *ssc)
+{
+    return (!self->deinterlace && (self->size.den == ssc->ssc_height));
+}
+
+
+static int
+tvh_codec_profile_video_open(TVHVideoCodecProfile *self, AVDictionary **opts)
+{
+    AV_DICT_SET_INT(opts, "tvh_filter_deint", self->deinterlace, 0);
+    // video_size
+    AV_DICT_SET_INT(opts, "width", self->size.num, 0);
+    AV_DICT_SET_INT(opts, "height", self->size.den, 0);
+    // crf
+    if (self->crf) {
+        AV_DICT_SET_INT(opts, "crf", self->crf, AV_DICT_DONT_OVERWRITE);
+    }
+    // pix_fmt
+    AV_DICT_SET_PIX_FMT(opts, self->pix_fmt, AV_PIX_FMT_YUV420P);
+    return 0;
+}
+
+
+/* codec_profile_video_class ================================================ */
+
+/* codec_profile_video_class.pix_fmt */
+
+static uint32_t
+codec_profile_video_class_pix_fmt_get_opts(void *obj, uint32_t opts)
+{
+    TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+    return tvh_codec_video_get_opts(codec, pix_fmts, opts);
+}
+
+
+static htsmsg_t *
+codec_profile_video_class_pix_fmt_list(void *obj, const char *lang)
+{
+    TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+    return tvh_codec_video_get_list(codec, pix_fmts);
+}
+
+
+/* codec_profile_video_class */
+
+const codec_profile_class_t codec_profile_video_class = {
+    {
+        .ic_super      = (idclass_t *)&codec_profile_class,
+        .ic_class      = "codec_profile_video",
+        .ic_caption    = N_("video"),
+        .ic_properties = (const property_t[]) {
+            {
+                .type     = PT_BOOL,
+                .id       = "deinterlace",
+                .name     = N_("Deinterlace"),
+                .desc     = N_("Deinterlace."),
+                .group    = 2,
+                .off      = offsetof(TVHVideoCodecProfile, deinterlace),
+                .def.i    = 1,
+            },
+            {
+                .type     = PT_INT,
+                .id       = "height",
+                .name     = N_("Height (pixels) (0=no scaling)"),
+                .desc     = N_("Height of the output video stream. Horizontal resolution "
+                               "is adjusted automatically to preserve aspect ratio. "
+                               "When set to 0, the input resolution is used."),
+                .group    = 2,
+                .off      = offsetof(TVHVideoCodecProfile, height),
+                .def.i    = 0,
+            },
+            {
+                .type     = PT_BOOL,
+                .id       = "hwaccel",
+                .name     = N_("Hardware acceleration"),
+                .desc     = N_("Use hardware acceleration for decoding if available."),
+                .group    = 2,
+                .off      = offsetof(TVHVideoCodecProfile, hwaccel),
+                .def.i    = 0,
+            },
+            {
+                .type     = PT_INT,
+                .id       = "pix_fmt",
+                .name     = N_("Pixel format"),
+                .desc     = N_("Video pixel format."),
+                .group    = 4,
+                .opts     = PO_ADVANCED | PO_HIDDEN,
+                .get_opts = codec_profile_video_class_pix_fmt_get_opts,
+                .off      = offsetof(TVHVideoCodecProfile, pix_fmt),
+                .list     = codec_profile_video_class_pix_fmt_list,
+                .def.i    = AV_PIX_FMT_NONE,
+            },
+            {}
+        }
+    },
+    .setup       = (codec_profile_is_copy_meth)tvh_codec_profile_video_setup,
+    .is_copy     = (codec_profile_is_copy_meth)tvh_codec_profile_video_is_copy,
+    .open        = (codec_profile_open_meth)tvh_codec_profile_video_open,
+};
diff --git a/src/transcoding/memutils.c b/src/transcoding/memutils.c
new file mode 100644 (file)
index 0000000..af47f18
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+ *  tvheadend - Transcoding
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "memutils.h"
+
+#include "memoryinfo.h"
+
+#include <libavutil/mem.h>
+
+
+static char *
+str_add(const char *sep, const char *str)
+{
+    size_t sep_len = strlen(sep), str_len = strlen(str);
+    char *result = NULL;
+
+    if ((result = calloc(1, sep_len + str_len + 1))) {
+        strncpy(result, sep, sep_len);
+        strncat(result, str, str_len);
+    }
+    return result;
+}
+
+
+static char *
+str_vjoin(const char *separator, va_list ap)
+{
+    const char *sep = separator ? separator : "", *va_str = NULL;
+    char *str = NULL, *tmp_result = NULL, *result = NULL;
+    size_t str_len = 0, result_size = 1;
+
+    if ((result = calloc(1, result_size))) {
+        while ((va_str = va_arg(ap, const char *))) {
+            if (strlen(va_str)) {
+                if ((str = str_add(strlen(result) ? sep : "", va_str))) {
+                    if ((str_len = strlen(str))) {
+                        result_size += str_len;
+                        if ((tmp_result = realloc(result, result_size))) {
+                            result = tmp_result;
+                            strncat(result, str, str_len);
+                        }
+                        else {
+                            str_clear(result);
+                        }
+                    }
+                    str_clear(str);
+                }
+                else {
+                    str_clear(result);
+                }
+                if (!result) {
+                    break;
+                }
+            }
+        }
+    }
+    return result;
+}
+
+
+char *
+str_join(const char *separator, ...)
+{
+    va_list ap;
+    va_start(ap, separator);
+    char *result = str_vjoin(separator, ap);
+    va_end(ap);
+    return result;
+}
+
+
+int
+str_snprintf(char *str, size_t size, const char *format, ...)
+{
+    va_list ap;
+    va_start(ap, format);
+    int ret = vsnprintf(str, size, format, ap);
+    va_end(ap);
+    return (ret < 0 || ret >= size) ? -1 : 0;
+}
+
+
+/* meant to replace streaming_msg_create_pkt in streaming.h
+   IMPORTANT: takes ownership of pkt */
+streaming_message_t *
+msg_create(th_pkt_t *pkt)
+{
+    static const size_t msg_size = sizeof(streaming_message_t);
+    streaming_message_t *msg = NULL;
+
+    if ((msg = calloc(1, msg_size))) {
+        msg->sm_type = SMT_PACKET;
+#if ENABLE_TIMESHIFT
+        msg->sm_time = 0;
+#endif
+        msg->sm_data = pkt; // takes ownership
+    }
+    else {
+        TVHPKT_DECREF(pkt);
+    }
+    return msg;
+}
+
+
+/* _IMPORTANT!_: need to check for pb->pb_size and pb->pb_data
+   _BEFORE_ calling pktbuf_copy_data */
+uint8_t *
+pktbuf_copy_data(pktbuf_t *pb)
+{
+    uint8_t *data = av_mallocz(pb->pb_size);
+    if (data) {
+        memcpy(data, pb->pb_data, pb->pb_size);
+    }
+    return data;
+}
+
+
+/* meant to replace pktbuf_alloc in packet.h */
+pktbuf_t *
+pktbuf_create(const uint8_t *data, size_t size)
+{
+    static const size_t pktbuf_size = sizeof(pktbuf_t);
+    pktbuf_t *pktbuf = NULL;
+    uint8_t *buffer = NULL;
+
+    if (size) {
+        if (!(buffer = calloc(1, size))) {
+            return NULL;
+        }
+        else if (data) {
+            memcpy(buffer, data, size);
+        }
+    }
+    if ((pktbuf = calloc(1, pktbuf_size))) {
+        pktbuf->pb_refcount = 1;
+        pktbuf->pb_err = 0;
+        pktbuf->pb_data = buffer;
+        pktbuf->pb_size = size;
+        memoryinfo_alloc(&pktbuf_memoryinfo, pktbuf_size + pktbuf->pb_size);
+    }
+    else if (buffer) {
+        free(buffer);
+        buffer = NULL;
+    }
+    return pktbuf;
+}
+
+
+/* meant to replace pkt_alloc in packet.h */
+th_pkt_t *
+pkt_create(const uint8_t *data, size_t size, int64_t pts, int64_t dts)
+{
+    static const size_t pkt_size = sizeof(th_pkt_t);
+    th_pkt_t *pkt = NULL;
+    pktbuf_t *payload = NULL;
+
+    if (size && !(payload = pktbuf_create(data, size))) {
+        return NULL;
+    }
+    if ((pkt = calloc(1, pkt_size))) {
+        pkt->pkt_refcount = 1;
+        pkt->pkt_pts = pts;
+        pkt->pkt_dts = dts;
+        pkt->pkt_payload = payload;
+        memoryinfo_alloc(&pkt_memoryinfo, pkt_size);
+    }
+    else if (payload) {
+        pktbuf_ref_dec(payload);
+        payload = NULL;
+    }
+    return pkt;
+}
diff --git a/src/transcoding/memutils.h b/src/transcoding/memutils.h
new file mode 100644 (file)
index 0000000..ad608fe
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+ *  tvheadend - Transcoding
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef TVH_TRANSCODING_MEMUTILS_H__
+#define TVH_TRANSCODING_MEMUTILS_H__
+
+
+#include "tvheadend.h"
+#include "streaming.h"
+
+
+#define TVH_INPUT_BUFFER_MAX_SIZE (INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE)
+
+
+#define TVHPKT_INCREF pkt_ref_inc
+
+#define TVHPKT_DECREF pkt_ref_dec
+
+#define TVHPKT_CLEAR(ptr) \
+    do { \
+        th_pkt_t *_tmp = (ptr); \
+        if (_tmp != NULL) { \
+            (ptr) = NULL; \
+            TVHPKT_DECREF(_tmp); \
+        } \
+    } while (0)
+
+#define TVHPKT_SET(ptr, pkt) \
+    do { \
+        TVHPKT_INCREF((pkt)); \
+        TVHPKT_CLEAR((ptr)); \
+        (ptr) = (pkt); \
+    } while (0)
+
+
+#define str_clear(str) \
+    do { \
+        if ((str) != NULL) { \
+            free((str)); \
+            (str) = NULL; \
+        } \
+    } while (0)
+
+char *
+str_join(const char *separator, ...);
+
+int
+str_snprintf(char *str, size_t size, const char *format, ...);
+
+
+streaming_message_t *
+msg_create(th_pkt_t *pkt);
+
+/* _IMPORTANT!_: need to check for pb->pb_size and pb->pb_data
+   _BEFORE_ calling pktbuf_copy_data */
+uint8_t *
+pktbuf_copy_data(pktbuf_t *pb);
+
+pktbuf_t *
+pktbuf_create(const uint8_t *data, size_t size);
+
+th_pkt_t *
+pkt_create(const uint8_t *data, size_t size, int64_t pts, int64_t dts);
+
+
+#endif // TVH_TRANSCODING_MEMUTILS_H__
diff --git a/src/transcoding/transcode.h b/src/transcoding/transcode.h
new file mode 100644 (file)
index 0000000..c842ebb
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ *  tvheadend - Transcoding
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef TVH_TRANSCODING_TRANSCODE_H__
+#define TVH_TRANSCODING_TRANSCODE_H__
+
+
+#include "tvheadend.h"
+
+
+/* TVHTranscoder ============================================================ */
+
+streaming_target_t *
+transcoder_create(streaming_target_t *output, const char **profiles);
+
+void
+transcoder_destroy(streaming_target_t *st);
+
+
+/* module level ============================================================= */
+
+void
+transcode_init(void);
+
+void
+transcode_done(void);
+
+
+#endif // TVH_TRANSCODING_TRANSCODE_H__
diff --git a/src/transcoding/transcode/audio.c b/src/transcoding/transcode/audio.c
new file mode 100644 (file)
index 0000000..c4ccdcf
--- /dev/null
@@ -0,0 +1,245 @@
+/*
+ *  tvheadend - Transcoding
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "internals.h"
+
+
+static enum AVSampleFormat
+_audio_context_sample_fmt(TVHContext *self, AVDictionary **opts)
+{
+    const enum AVSampleFormat *sample_fmts =
+        tvh_codec_profile_audio_get_sample_fmts(self->profile);
+    enum AVSampleFormat ifmt = self->iavctx->sample_fmt;
+    enum AVSampleFormat ofmt = AV_SAMPLE_FMT_NONE;
+    int i;
+
+    if (!tvh_context_get_int_opt(opts, "sample_fmt", &ofmt) &&
+        ofmt == AV_SAMPLE_FMT_NONE) {
+        if (sample_fmts) {
+            for (i = 0; (ofmt = sample_fmts[i]) != AV_SAMPLE_FMT_NONE; i++) {
+                if (ofmt == ifmt) {
+                    break;
+                }
+            }
+            ofmt = (ofmt != AV_SAMPLE_FMT_NONE) ? ofmt : sample_fmts[0];
+        }
+        else {
+            ofmt = ifmt;
+        }
+    }
+    return ofmt;
+}
+
+
+static int
+_audio_context_sample_rate(TVHContext *self, AVDictionary **opts)
+{
+    const int *sample_rates =
+        tvh_codec_profile_audio_get_sample_rates(self->profile);
+    int irate = self->iavctx->sample_rate, altrate = 0, orate = 0, i;
+
+    if (!tvh_context_get_int_opt(opts, "sample_rate", &orate) &&
+        !orate && sample_rates) {
+        for (i = 0; (orate = sample_rates[i]); i++) {
+            if (orate == irate) {
+                break;
+            }
+            if (orate < irate) {
+                altrate = orate;
+            }
+        }
+    }
+    return orate ? orate : (altrate ? altrate : 48000);
+}
+
+
+static uint64_t
+_audio_context_channel_layout(TVHContext *self, AVDictionary **opts)
+{
+    const uint64_t *channel_layouts =
+        tvh_codec_profile_audio_get_channel_layouts(self->profile);
+    uint64_t ilayout = self->iavctx->channel_layout;
+    uint64_t altlayout = 0, olayout = 0;
+    int tmp = 0, ichannels = 0, i;
+
+
+    if (!tvh_context_get_int_opt(opts, "channel_layout", &tmp) &&
+        !(olayout = tmp) && channel_layouts) {
+        ichannels = av_get_channel_layout_nb_channels(ilayout);
+        for (i = 0; (olayout = channel_layouts[i]); i++) {
+            if (olayout == ilayout) {
+                break;
+            }
+            if (av_get_channel_layout_nb_channels(olayout) <= ichannels) {
+                altlayout = olayout;
+            }
+        }
+    }
+    return olayout ? olayout : (altlayout ? altlayout : AV_CH_LAYOUT_STEREO);
+}
+
+
+static int
+tvh_audio_context_open_encoder(TVHContext *self, AVDictionary **opts)
+{
+    // XXX: is this a safe assumption?
+    if (!self->iavctx->time_base.den) {
+        self->iavctx->time_base = av_make_q(1, 90000);
+    }
+    // sample_fmt
+    self->oavctx->sample_fmt = _audio_context_sample_fmt(self, opts);
+    if (self->oavctx->sample_fmt == AV_SAMPLE_FMT_NONE) {
+        tvh_context_log(self, LOG_ERR,
+                        "audio encoder has no suitable sample format");
+        return -1;
+    }
+    // sample_rate
+    self->oavctx->sample_rate = _audio_context_sample_rate(self, opts);
+    if (!self->oavctx->sample_rate) {
+        tvh_context_log(self, LOG_ERR,
+                        "audio encoder has no suitable sample rate");
+        return -1;
+    }
+    self->oavctx->time_base = av_make_q(1, self->oavctx->sample_rate);
+    self->sri = rate_to_sri(self->oavctx->sample_rate);
+    // channel_layout
+    self->oavctx->channel_layout = _audio_context_channel_layout(self, opts);
+    if (!self->oavctx->channel_layout) {
+        tvh_context_log(self, LOG_ERR,
+                        "audio encoder has no suitable channel layout");
+        return -1;
+    }
+    self->oavctx->channels =
+        av_get_channel_layout_nb_channels(self->oavctx->channel_layout);
+    return 0;
+}
+
+
+static int
+tvh_audio_context_open_filters(TVHContext *self, AVDictionary **opts)
+{
+    static char source_args[128];
+    static char filters[16];
+    int resample = (self->iavctx->sample_rate != self->oavctx->sample_rate);
+
+    // source args
+    memset(source_args, 0, sizeof(source_args));
+    if (str_snprintf(source_args, sizeof(source_args),
+            "time_base=%d/%d:sample_rate=%d:sample_fmt=%d:channel_layout=0x%"PRIx64,
+            self->iavctx->time_base.num,
+            self->iavctx->time_base.den,
+            self->iavctx->sample_rate,
+            self->iavctx->sample_fmt,
+            self->iavctx->channel_layout)) {
+        return -1;
+    }
+
+    // context filters
+    memset(filters, 0, sizeof(filters));
+    if (str_snprintf(filters, sizeof(filters), "%s",
+                     (resample) ? "aresample" : "anull")) {
+        return -1;
+    }
+
+    int ret = tvh_context_open_filters(self,
+        "abuffer", source_args,                           // source
+        filters,                                          // filters
+        "abuffersink",                                    // sink
+        "channel_layouts", &self->oavctx->channel_layout, // sink option: channel_layout
+        sizeof(self->oavctx->channel_layout),
+        "sample_fmts", &self->oavctx->sample_fmt,         // sink option: sample_fmt
+        sizeof(self->oavctx->sample_fmt),
+        "sample_rates", &self->oavctx->sample_rate,       // sink option: sample_rate
+        sizeof(self->oavctx->sample_rate),
+        NULL);                                            // _IMPORTANT!_
+    if (!ret) {
+        av_buffersink_set_frame_size(self->oavfltctx, self->oavctx->frame_size);
+    }
+    return ret;
+}
+
+
+static int
+tvh_audio_context_open(TVHContext *self, TVHOpenPhase phase, AVDictionary **opts)
+{
+    switch (phase) {
+        case OPEN_ENCODER:
+            return tvh_audio_context_open_encoder(self, opts);
+        case OPEN_ENCODER_POST:
+            self->delta = av_rescale_q(self->oavctx->frame_size,
+                                       self->oavctx->time_base,
+                                       self->iavctx->time_base);
+            return tvh_audio_context_open_filters(self, opts);
+        default:
+            break;
+    }
+    return 0;
+}
+
+
+static int
+tvh_audio_context_decode(TVHContext *self, AVPacket *avpkt)
+{
+    int64_t prev_pts = self->pts;
+
+    if (prev_pts != (self->pts = avpkt->pts - self->duration)) {
+        tvh_context_log(self, LOG_WARNING, "Detected framedrop in audio");
+    }
+    self->duration += avpkt->duration;
+    return 0;
+}
+
+
+static int
+tvh_audio_context_encode(TVHContext *self, AVFrame *avframe)
+{
+    avframe->nb_samples = self->oavctx->frame_size;
+    avframe->pts = self->pts;
+    self->pts += self->delta;
+    self->duration -= self->delta;
+    return 0;
+}
+
+
+static int
+tvh_audio_context_ship(TVHContext *self, AVPacket *avpkt)
+{
+    return (avpkt->pts >= 0);
+}
+
+
+static int
+tvh_audio_context_wrap(TVHContext *self, AVPacket *avpkt, th_pkt_t *pkt)
+{
+    pkt->pkt_duration   = avpkt->duration;
+    pkt->a.pkt_channels = self->oavctx->channels;
+    pkt->a.pkt_sri      = self->sri;
+    return 0;
+}
+
+
+TVHContextType TVHAudioContext = {
+    .media_type = AVMEDIA_TYPE_AUDIO,
+    .open       = tvh_audio_context_open,
+    .decode     = tvh_audio_context_decode,
+    .encode     = tvh_audio_context_encode,
+    .ship       = tvh_audio_context_ship,
+    .wrap       = tvh_audio_context_wrap,
+};
diff --git a/src/transcoding/transcode/context.c b/src/transcoding/transcode/context.c
new file mode 100644 (file)
index 0000000..d18fd5e
--- /dev/null
@@ -0,0 +1,732 @@
+/*
+ *  tvheadend - Transcoding
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "internals.h"
+
+#include <libavutil/opt.h>
+
+
+#define TVH_BUFFERSRC_FLAGS AV_BUFFERSRC_FLAG_PUSH | AV_BUFFERSRC_FLAG_KEEP_REF
+#define TVH_BUFFERSINK_FLAGS AV_BUFFERSINK_FLAG_NO_REQUEST
+
+
+/* TVHContextType =========================================================== */
+
+extern TVHContextType TVHVideoContext;
+extern TVHContextType TVHAudioContext;
+
+
+SLIST_HEAD(TVHContextTypes, tvh_context_type_t);
+static struct TVHContextTypes tvh_context_types;
+
+
+static void
+tvh_context_type_register(TVHContextType *self)
+{
+    if (self->media_type <= AVMEDIA_TYPE_UNKNOWN ||
+        self->media_type >= AVMEDIA_TYPE_NB ||
+        !self->ship) {
+        tvherror(LS_TRANSCODE, "incomplete/wrong definition for context type");
+        return;
+    }
+    SLIST_INSERT_HEAD(&tvh_context_types, self, link);
+    tvhinfo(LS_TRANSCODE, "'%s' context type registered",
+            av_get_media_type_string(self->media_type));
+}
+
+
+static TVHContextType *
+tvh_context_type_find(enum AVMediaType media_type)
+{
+    TVHContextType *type = NULL;
+
+    SLIST_FOREACH(type, &tvh_context_types, link) {
+        if (type->media_type == media_type) {
+            return type;
+        }
+    }
+    return NULL;
+}
+
+
+void
+tvh_context_types_register()
+{
+    SLIST_INIT(&tvh_context_types);
+    tvh_context_type_register(&TVHVideoContext);
+    tvh_context_type_register(&TVHAudioContext);
+}
+
+
+void
+tvh_context_types_forget()
+{
+    tvhinfo(LS_TRANSCODE, "forgetting context types");
+    while (!SLIST_EMPTY(&tvh_context_types)) {
+        SLIST_REMOVE_HEAD(&tvh_context_types, link);
+    }
+}
+
+
+/* TVHContext =============================================================== */
+
+// wrappers
+
+static void
+_context_print_opts(TVHContext *self, AVDictionary *opts)
+{
+    char *buffer = NULL;
+
+    if (opts) {
+        av_dict_get_string(opts, &buffer, '=', ',');
+        tvh_context_log(self, LOG_DEBUG, "opts: %s", buffer);
+        av_freep(&buffer);
+    }
+}
+
+
+static int
+_context_filters_apply_sink_options(TVHContext *self, va_list ap)
+{
+    const char *opt_name = NULL;
+    const uint8_t *opt_val = NULL;
+    int opt_size = 0;
+    int ret = -1;
+
+    while ((opt_name = va_arg(ap, const char *))) {
+        opt_val = va_arg(ap, const uint8_t *);
+        opt_size = va_arg(ap, int);
+        if ((ret = av_opt_set_bin(self->oavfltctx, opt_name, opt_val, opt_size,
+                                  AV_OPT_SEARCH_CHILDREN))) {
+            tvh_context_log(self, LOG_ERR, "filters: failed to set option: '%s'",
+                            opt_name);
+            break;
+        }
+    }
+    return ret;
+}
+
+
+static int
+_context_open(TVHContext *self, TVHOpenPhase phase, AVDictionary **opts)
+{
+    return (self->type->open) ? self->type->open(self, phase, opts) : 0;
+}
+
+
+static int
+_context_decode(TVHContext *self, AVPacket *avpkt)
+{
+    return (self->type->decode) ? self->type->decode(self, avpkt) : 0;
+}
+
+
+static int
+_context_encode(TVHContext *self, AVFrame *avframe)
+{
+    return (self->type->encode) ? self->type->encode(self, avframe) : 0;
+    /*int ret = -1;
+
+    if (!(ret = (self->type->encode) ? self->type->encode(self, avframe) : 0) &&
+        (self->helper && self->helper->encode)) {
+        ret = self->helper->encode(self, avframe);
+    }
+    return ret;*/
+}
+
+
+static int
+_context_meta(TVHContext *self, AVPacket *avpkt, th_pkt_t *pkt)
+{
+    if (self->require_meta) {
+        if (self->helper && self->helper->meta) {
+            self->require_meta = self->helper->meta(self, avpkt, pkt);
+        }
+        else if (self->oavctx->extradata_size) {
+            pkt->pkt_meta = pktbuf_create(self->oavctx->extradata,
+                                          self->oavctx->extradata_size);
+            self->require_meta = (pkt->pkt_meta) ? 0 : -1;
+        }
+    }
+    return (self->require_meta < 0) ? -1 : 0;
+}
+
+
+static int
+_context_wrap(TVHContext *self, AVPacket *avpkt, th_pkt_t *pkt)
+{
+    return (self->type->wrap) ? self->type->wrap(self, avpkt, pkt) : 0;
+}
+
+
+// creation
+
+static AVCodecContext *
+tvh_context_alloc_avctx(AVCodec *avcodec)
+{
+    AVCodecContext *avctx = NULL;
+
+    if ((avctx = avcodec_alloc_context3(avcodec))) {
+        avctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
+        avctx->refcounted_frames = 1;
+    }
+    return avctx;
+}
+
+
+static int
+tvh_context_setup(TVHContext *self, AVCodec *iavcodec, AVCodec *oavcodec)
+{
+    enum AVMediaType media_type = iavcodec->type;
+    const char *media_type_name = av_get_media_type_string(media_type);
+
+    if (!(self->type = tvh_context_type_find(media_type))) {
+        tvh_stream_log(self->stream, LOG_ERR,
+                       "failed to find context type for '%s' media type",
+                       media_type_name ? media_type_name : "<unknown>");
+        return -1;
+    }
+    if (!(self->iavctx = tvh_context_alloc_avctx(iavcodec)) ||
+        !(self->oavctx = tvh_context_alloc_avctx(oavcodec)) ||
+        !(self->iavframe = av_frame_alloc()) ||
+        !(self->oavframe = av_frame_alloc())) {
+        tvh_stream_log(self->stream, LOG_ERR,
+                       "failed to allocate AVCodecContext/AVFrame");
+        return -1;
+    }
+    return 0;
+}
+
+
+// open
+
+static int
+tvh_context_open(TVHContext *self, TVHOpenPhase phase)
+{
+    AVCodecContext *avctx = NULL;
+    TVHContextHelper *helper = NULL;
+    AVDictionary *opts = NULL;
+    int ret = 0;
+
+    switch (phase) {
+        case OPEN_DECODER:
+            avctx = self->iavctx;
+            helper = tvh_decoder_helper_find(avctx->codec);
+            break;
+        case OPEN_ENCODER:
+            avctx = self->oavctx;
+            helper = self->helper = tvh_encoder_helper_find(avctx->codec);
+            ret = tvh_codec_profile_open(self->profile, &opts);
+            break;
+        default:
+            tvh_context_log(self, LOG_ERR, "invalid open phase");
+            ret = AVERROR(EINVAL);
+            break;
+    }
+    if (!ret) {
+        _context_print_opts(self, opts);
+        ret = tvh_context_get_int_opt(&opts, "tvh_require_meta",
+                                      &self->require_meta);
+        if (!ret && !(ret = _context_open(self, phase, &opts)) && // pre open
+            !(ret = (helper && helper->open) ? helper->open(self, &opts) : 0) && // pre open
+            !(ret = avcodec_open2(avctx, NULL, &opts))) { // open
+            ret = _context_open(self, phase + 1, &opts); // post open
+        }
+        _context_print_opts(self, opts);
+    }
+    if (opts) {
+        av_dict_free(&opts);
+    }
+    return ret;
+}
+
+
+// shipping
+
+static th_pkt_t *
+tvh_context_pack(TVHContext *self, AVPacket *avpkt)
+{
+    th_pkt_t *pkt = NULL;
+
+    if (self->helper && self->helper->pack) {
+        pkt = self->helper->pack(self, avpkt);
+    }
+    else {
+        pkt = pkt_create(avpkt->data, avpkt->size, avpkt->pts, avpkt->dts);
+    }
+    if (!pkt) {
+        tvh_context_log(self, LOG_ERR, "failed to create packet");
+    }
+    else if (_context_meta(self, avpkt, pkt) ||
+             _context_wrap(self, avpkt, pkt)) {
+        TVHPKT_CLEAR(pkt);
+    }
+    return pkt;
+}
+
+
+static int
+tvh_context_ship(TVHContext *self, AVPacket *avpkt)
+{
+    th_pkt_t *pkt = NULL;
+    int ret = -1;
+
+    if (((ret = self->type->ship(self, avpkt)) > 0) &&
+        (pkt = tvh_context_pack(self, avpkt))) {
+        ret = tvh_context_deliver(self, pkt);
+    }
+    if ((ret = (ret != 0) ? -1 : ret)) {
+        av_packet_unref(avpkt);
+    }
+    return ret;
+}
+
+
+// encoding
+
+static int
+tvh_context_receive_packet(TVHContext *self)
+{
+    AVPacket avpkt;
+    int ret = -1;
+
+    av_init_packet(&avpkt);
+    while ((ret = avcodec_receive_packet(self->oavctx, &avpkt)) != AVERROR(EAGAIN)) {
+        if (ret || (ret = tvh_context_ship(self, &avpkt))) {
+            break;
+        }
+    }
+    return (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) ? 0 : ret;
+}
+
+
+static int
+tvh_context_encode_frame(TVHContext *self, AVFrame *avframe)
+{
+    int ret = -1;
+
+    if (!(ret = avframe ? _context_encode(self, avframe) : 0)) {
+        while ((ret = avcodec_send_frame(self->oavctx, avframe)) == AVERROR(EAGAIN)) {
+            if ((ret = tvh_context_receive_packet(self))) {
+                break;
+            }
+        }
+    }
+    if (!ret) {
+        ret = tvh_context_receive_packet(self);
+    }
+    if ((ret = (ret == AVERROR_EOF) ? 0 : ret)) {
+        av_frame_unref(avframe);
+    }
+    return ret;
+}
+
+
+static int
+tvh_context_pull_frame(TVHContext *self, AVFrame *avframe)
+{
+    int ret = -1;
+
+    av_frame_unref(avframe);
+    ret = av_buffersink_get_frame_flags(self->oavfltctx, avframe, TVH_BUFFERSINK_FLAGS);
+    return (ret > 0) ? 0 : ret;
+}
+
+
+static int
+tvh_context_push_frame(TVHContext *self, AVFrame *avframe)
+{
+    int ret = -1;
+
+    ret = av_buffersrc_add_frame_flags(self->iavfltctx, avframe, TVH_BUFFERSRC_FLAGS);
+    return (ret > 0) ? 0 : ret;
+}
+
+
+static int
+tvh_context_encode(TVHContext *self, AVFrame *avframe)
+{
+    int ret = 0;
+
+    if (!avcodec_is_open(self->oavctx)) {
+        ret = tvh_context_open(self, OPEN_ENCODER);
+    }
+    if (!ret && !(ret = tvh_context_push_frame(self, avframe))) {
+        while ((ret = tvh_context_pull_frame(self, self->oavframe)) != AVERROR(EAGAIN)) {
+            if (ret || (ret = tvh_context_encode_frame(self, self->oavframe))) {
+                break;
+            }
+        }
+    }
+    if ((ret = (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) ? 0 : ret)) {
+        av_frame_unref(avframe);
+    }
+    return ret;
+}
+
+
+// decoding
+
+static int
+tvh_context_receive_frame(TVHContext *self, AVFrame *avframe)
+{
+    int ret = -1;
+
+    while ((ret = avcodec_receive_frame(self->iavctx, avframe)) != AVERROR(EAGAIN)) {
+        if (ret || (ret = tvh_context_encode(self, avframe))) {
+            break;
+        }
+    }
+    return (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) ? 0 : ret;
+}
+
+
+static int
+tvh_context_decode_packet(TVHContext *self, const AVPacket *avpkt)
+{
+    int ret = -1;
+
+    while ((ret = avcodec_send_packet(self->iavctx, avpkt)) == AVERROR(EAGAIN)) {
+        if ((ret = tvh_context_receive_frame(self, self->iavframe))) {
+            break;
+        }
+    }
+    if (!ret) {
+        ret = tvh_context_receive_frame(self, self->iavframe);
+    }
+    return (ret == AVERROR_EOF) ? 0 : ret;
+}
+
+
+static int
+tvh_context_decode(TVHContext *self, AVPacket *avpkt)
+{
+    int ret = 0;
+
+    if (!avcodec_is_open(self->iavctx)) {
+        ret = tvh_context_open(self, OPEN_DECODER);
+    }
+    if (!ret && !(ret = _context_decode(self, avpkt))) {
+        ret = tvh_context_decode_packet(self, avpkt);
+    }
+    return (ret == AVERROR(EAGAIN) || ret == AVERROR_INVALIDDATA) ? 0 : ret;
+}
+
+
+static void
+tvh_context_flush(TVHContext *self)
+{
+    tvh_context_decode_packet(self, NULL);
+    tvh_context_encode_frame(self, NULL);
+}
+
+
+/* exposed */
+
+int
+tvh_context_get_int_opt(AVDictionary **opts, const char *key, int *value)
+{
+    AVDictionaryEntry *entry = NULL;
+
+    if (*opts &&
+        (entry = av_dict_get(*opts, key, NULL, AV_DICT_MATCH_CASE))) {
+        *value = atoi(entry->value);
+        return (av_dict_set(opts, key, NULL, 0) < 0) ? -1 : 0;
+    }
+    return 0;
+}
+
+
+int
+tvh_context_get_str_opt(AVDictionary **opts, const char *key, char **value)
+{
+    AVDictionaryEntry *entry = NULL;
+
+    if (*opts &&
+        (entry = av_dict_get(*opts, key, NULL, AV_DICT_MATCH_CASE))) {
+        *value = strdup(entry->value);
+        return (av_dict_set(opts, key, NULL, 0) < 0) ? -1 : 0;
+    }
+    return 0;
+}
+
+
+void
+tvh_context_close(TVHContext *self, int flush)
+{
+    if (flush) {
+        tvh_context_flush(self);
+    }
+    if (self->type->close) {
+        self->type->close(self);
+    }
+    if (self->oavctx && avcodec_is_open(self->oavctx)) {
+        avcodec_close(self->oavctx);
+    }
+    if (self->iavctx && avcodec_is_open(self->iavctx)) {
+        avcodec_close(self->iavctx);
+    }
+}
+
+
+int
+tvh_context_open_filters(TVHContext *self,
+                         const char *source_name, const char *source_args,
+                         const char *filters, const char *sink_name, ...)
+{
+    AVFilter *iavflt = NULL, *oavflt = NULL;
+    AVFilterInOut *iavfltio = NULL, *oavfltio = NULL;
+    AVBufferSrcParameters *par = NULL;
+    int i, ret = -1;
+
+    tvh_context_log(self, LOG_DEBUG, "filters: source args: '%s'", source_args);
+    tvh_context_log(self, LOG_DEBUG, "filters: filters: '%s'", filters);
+
+    if (!(self->avfltgraph = avfilter_graph_alloc()) ||
+        !(iavfltio = avfilter_inout_alloc()) ||
+        !(oavfltio = avfilter_inout_alloc()) ||
+        !(par = av_buffersrc_parameters_alloc())) {
+        ret = AVERROR(ENOMEM);
+        goto finish;
+    }
+
+    // source
+    if (!(iavflt = avfilter_get_by_name(source_name))) {
+        tvh_context_log(self, LOG_ERR, "filters: source filter'%s' not found",
+                        source_name);
+        ret = AVERROR_FILTER_NOT_FOUND;
+        goto finish;
+    }
+    ret = avfilter_graph_create_filter(&self->iavfltctx, iavflt, "in",
+                                       source_args, NULL, self->avfltgraph);
+    if (ret < 0) {
+        tvh_context_log(self, LOG_ERR, "filters: failed to create 'in' filter");
+        goto finish;
+    }
+
+    // additional buffersrc params
+    if (!strcmp("buffer", source_name) && self->iavctx->hw_frames_ctx) { // hmm...
+        memset(par, 0, sizeof(AVBufferSrcParameters));
+        par->format = AV_PIX_FMT_NONE;
+        par->hw_frames_ctx = self->iavctx->hw_frames_ctx;
+        ret = av_buffersrc_parameters_set(self->iavfltctx, par);
+        if (ret < 0) {
+            tvh_context_log(self, LOG_ERR,
+                            "filters: failed to set additional parameters");
+            goto finish;
+        }
+    }
+
+    // sink
+    if (!(oavflt = avfilter_get_by_name(sink_name))) {
+        tvh_context_log(self, LOG_ERR, "filters: sink filter '%s' not found",
+                        sink_name);
+        ret = AVERROR_FILTER_NOT_FOUND;
+        goto finish;
+    }
+    ret = avfilter_graph_create_filter(&self->oavfltctx, oavflt, "out",
+                                       NULL, NULL, self->avfltgraph);
+    if (ret < 0) {
+        tvh_context_log(self, LOG_ERR, "filters: failed to create 'out' filter");
+        goto finish;
+    }
+
+    // sink options
+    va_list ap;
+    va_start(ap, sink_name);
+    ret = _context_filters_apply_sink_options(self, ap);
+    va_end(ap);
+    if (ret) {
+        goto finish;
+    }
+
+    // Endpoints for the filter graph.
+    iavfltio->name       = av_strdup("out");
+    iavfltio->filter_ctx = self->oavfltctx;
+    iavfltio->pad_idx    = 0;
+    iavfltio->next       = NULL;
+
+    oavfltio->name       = av_strdup("in");
+    oavfltio->filter_ctx = self->iavfltctx;
+    oavfltio->pad_idx    = 0;
+    oavfltio->next       = NULL;
+
+    if (!iavfltio->name || !oavfltio->name) {
+        ret = AVERROR(ENOMEM);
+        goto finish;
+    }
+
+    // filters
+    ret = avfilter_graph_parse_ptr(self->avfltgraph, filters,
+                                   &iavfltio, &oavfltio, NULL);
+    if (ret < 0) {
+        tvh_context_log(self, LOG_ERR, "filters: failed to add filters: '%s'",
+                        filters);
+        goto finish;
+    }
+
+    // additional filtergraph params
+    if (!strcmp("buffer", source_name) && self->oavctx->opaque) { // hmm...
+        AVBufferRef *hw_device_ctx = self->oavctx->opaque;
+        for (i = 0; i < self->avfltgraph->nb_filters; i++) {
+            if (!(self->avfltgraph->filters[i]->hw_device_ctx =
+                      av_buffer_ref(hw_device_ctx))) {
+                ret = -1;
+                goto finish;
+            }
+        }
+    }
+
+    avfilter_graph_set_auto_convert(self->avfltgraph,
+                                    AVFILTER_AUTO_CONVERT_NONE);
+
+    if ((ret = avfilter_graph_config(self->avfltgraph, NULL)) < 0) {
+        tvh_context_log(self, LOG_ERR, "filters: failed to config filter graph");
+    }
+
+finish:
+    if (par) {
+        av_freep(&par);
+    }
+    if (iavfltio) {
+        avfilter_inout_free(&iavfltio);
+    }
+    if (oavfltio) {
+        avfilter_inout_free(&oavfltio);
+    }
+    return (ret > 0) ? 0 : ret;
+}
+
+
+int
+tvh_context_deliver(TVHContext *self, th_pkt_t *pkt)
+{
+    return tvh_stream_deliver(self->stream, pkt);
+}
+
+
+int
+tvh_context_handle(TVHContext *self, th_pkt_t *pkt)
+{
+    int ret = 0;
+    uint8_t *data = NULL;
+    size_t size = 0;
+    AVPacket avpkt;
+
+    if ((size = pktbuf_len(pkt->pkt_payload)) && pktbuf_ptr(pkt->pkt_payload)) {
+        if (size >= TVH_INPUT_BUFFER_MAX_SIZE) {
+            tvh_context_log(self, LOG_ERR, "packet payload too big");
+            ret = AVERROR(EOVERFLOW);
+        }
+        else if (!(data = pktbuf_copy_data(pkt->pkt_payload))) {
+            tvh_context_log(self, LOG_ERR, "failed to copy packet payload");
+            ret = AVERROR(ENOMEM);
+        }
+        else {
+            av_init_packet(&avpkt);
+            if ((ret = av_packet_from_data(&avpkt, data, size))) { // takes ownership of data
+                tvh_context_log(self, LOG_ERR,
+                                "failed to allocate AVPacket buffer");
+                av_freep(data);
+            }
+            else {
+                if (!self->input_gh && pkt->pkt_meta) {
+                    pktbuf_ref_inc(pkt->pkt_meta);
+                    self->input_gh = pkt->pkt_meta;
+                }
+                avpkt.pts = pkt->pkt_pts;
+                avpkt.dts = pkt->pkt_dts;
+                avpkt.duration = pkt->pkt_duration;
+                TVHPKT_SET(self->src_pkt, pkt);
+                ret = tvh_context_decode(self, &avpkt);
+                av_packet_unref(&avpkt); // will free data
+            }
+        }
+    }
+    return ret;
+}
+
+
+TVHContext *
+tvh_context_create(TVHStream *stream, TVHCodecProfile *profile,
+                   AVCodec *iavcodec, AVCodec *oavcodec, pktbuf_t *input_gh)
+{
+    TVHContext *self = NULL;
+
+    if (!(self = calloc(1, sizeof(TVHContext)))) {
+        tvh_stream_log(stream, LOG_ERR, "failed to allocate context");
+        return NULL;
+    }
+    self->stream = stream;
+    self->profile = profile;
+    if (tvh_context_setup(self, iavcodec, oavcodec)) {
+        tvh_context_destroy(self);
+        return NULL;
+    }
+    if (input_gh) {
+        pktbuf_ref_inc(input_gh);
+        self->input_gh = input_gh;
+    }
+    return self;
+}
+
+
+void
+tvh_context_destroy(TVHContext *self)
+{
+    if (self) {
+        tvh_context_close(self, 0);
+        TVHPKT_CLEAR(self->src_pkt);
+        if (self->avfltgraph) {
+            avfilter_graph_free(&self->avfltgraph); // frees filter contexts
+            self->oavfltctx = NULL;
+            self->iavfltctx = NULL;
+            self->avfltgraph = NULL;
+        }
+        if (self->helper) {
+            self->helper = NULL;
+        }
+        if (self->input_gh) {
+            pktbuf_ref_dec(self->input_gh);
+            self->input_gh = NULL;
+        }
+        if (self->oavframe) {
+            av_frame_free(&self->oavframe);
+            self->oavframe = NULL;
+        }
+        if (self->iavframe) {
+            av_frame_free(&self->iavframe);
+            self->iavframe = NULL;
+        }
+        if (self->oavctx) {
+            avcodec_free_context(&self->oavctx); // frees extradata
+            self->oavctx = NULL;
+        }
+        if (self->iavctx) {
+            avcodec_free_context(&self->iavctx);
+            self->iavctx = NULL;
+        }
+        self->type = NULL;
+        self->profile = NULL;
+        self->stream = NULL;
+        free(self);
+        self = NULL;
+    }
+}
diff --git a/src/transcoding/transcode/helpers.c b/src/transcoding/transcode/helpers.c
new file mode 100644 (file)
index 0000000..57d2be6
--- /dev/null
@@ -0,0 +1,482 @@
+/*
+ *  tvheadend - Transcoding
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "internals.h"
+
+#include "parsers/bitstream.h"
+#include "parsers/parser_avc.h"
+#include "parsers/parser_h264.h"
+#include "parsers/parser_hevc.h"
+
+
+SLIST_HEAD(TVHContextHelpers, tvh_context_helper_t);
+static struct TVHContextHelpers tvh_decoder_helpers;
+static struct TVHContextHelpers tvh_encoder_helpers;
+
+
+#define tvh_context_helper_register(list, helper, kind) \
+    do { \
+        if ((helper)->type <= AVMEDIA_TYPE_UNKNOWN || \
+            (helper)->type >= AVMEDIA_TYPE_NB) { \
+            tvherror(LS_TRANSCODE, \
+                     "incomplete/wrong definition for " #helper " helper"); \
+            return; \
+        } \
+        SLIST_INSERT_HEAD((list), (helper), link); \
+        tvhinfo(LS_TRANSCODE, "'" #helper "' %s helper registered", (kind)); \
+    } while (0)
+
+#define tvh_decoder_helper_register(helper) \
+    do { \
+        if (!(helper)->open) { \
+            tvherror(LS_TRANSCODE, \
+                     "incomplete/wrong definition for " #helper " helper"); \
+            return; \
+        } \
+        tvh_context_helper_register(&tvh_decoder_helpers, helper, "decoder"); \
+    } while (0)
+
+#define tvh_encoder_helper_register(helper) \
+    do { \
+        if (!((helper)->open || (helper)->pack || (helper)->meta)) { \
+            tvherror(LS_TRANSCODE, \
+                     "incomplete/wrong definition for " #helper " helper"); \
+            return; \
+        } \
+        tvh_context_helper_register(&tvh_encoder_helpers, helper, "encoder"); \
+    } while (0)
+
+
+static TVHContextHelper *
+tvh_context_helper_find(struct TVHContextHelpers *list, const AVCodec *codec)
+{
+    TVHContextHelper *helper = NULL;
+
+    SLIST_FOREACH(helper, list, link) {
+        if (helper->type == codec->type && helper->id == codec->id) {
+            tvhdebug(LS_TRANSCODE, "found helper for: %s", codec->name);
+            return helper;
+        }
+    }
+    return NULL;
+}
+
+
+/* decoders ================================================================= */
+
+/* shared by H264, AAC and VORBIS */
+static int
+tvh_extradata_open(TVHContext *self, AVDictionary **opts)
+{
+    size_t extradata_size = 0;
+
+    if (!(extradata_size = pktbuf_len(self->input_gh))) {
+        return AVERROR(EAGAIN);
+    }
+    if (extradata_size >= TVH_INPUT_BUFFER_MAX_SIZE) {
+        tvh_context_log(self, LOG_ERR, "extradata too big");
+        return AVERROR(EOVERFLOW);
+    }
+    if (self->iavctx->extradata) {
+        tvh_context_log(self, LOG_ERR, "extradata already set!");
+        return AVERROR(EALREADY);
+    }
+    if (!(self->iavctx->extradata =
+              av_mallocz(extradata_size + AV_INPUT_BUFFER_PADDING_SIZE))) {
+        tvh_context_log(self, LOG_ERR, "failed to alloc extradata");
+        return AVERROR(ENOMEM);
+    }
+    memcpy(self->iavctx->extradata, pktbuf_ptr(self->input_gh), extradata_size);
+    self->iavctx->extradata_size = extradata_size;
+    return 0;
+}
+
+
+/* H264 */
+static TVHContextHelper TVHH264Decoder = {
+    .type = AVMEDIA_TYPE_VIDEO,
+    .id   = AV_CODEC_ID_H264,
+    .open = tvh_extradata_open,
+};
+
+/* THEORA */
+static TVHContextHelper TVHTHEORADecoder = {
+    .type = AVMEDIA_TYPE_VIDEO,
+    .id   = AV_CODEC_ID_THEORA,
+    .open = tvh_extradata_open,
+};
+
+
+/* AAC */
+static TVHContextHelper TVHAACDecoder = {
+    .type = AVMEDIA_TYPE_AUDIO,
+    .id   = AV_CODEC_ID_AAC,
+    .open = tvh_extradata_open,
+};
+
+
+/* VORBIS */
+static TVHContextHelper TVHVORBISDecoder = {
+    .type = AVMEDIA_TYPE_AUDIO,
+    .id   = AV_CODEC_ID_VORBIS,
+    .open = tvh_extradata_open,
+};
+
+
+/* OPUS */
+static TVHContextHelper TVHOPUSDecoder = {
+    .type = AVMEDIA_TYPE_AUDIO,
+    .id   = AV_CODEC_ID_OPUS,
+    .open = tvh_extradata_open,
+};
+
+
+static void
+tvh_decoder_helpers_register()
+{
+    SLIST_INIT(&tvh_decoder_helpers);
+    /* video */
+    tvh_decoder_helper_register(&TVHH264Decoder);
+    tvh_decoder_helper_register(&TVHTHEORADecoder);
+    /* audio */
+    tvh_decoder_helper_register(&TVHAACDecoder);
+    tvh_decoder_helper_register(&TVHVORBISDecoder);
+    tvh_decoder_helper_register(&TVHOPUSDecoder);
+}
+
+
+/* encoders ================================================================= */
+
+/* MPEG2VIDEO */
+static int
+tvh_mpeg2video_open(TVHContext *self, AVDictionary **opts)
+{
+    self->oavctx->gop_size = MIN(600, self->oavctx->gop_size);
+    self->oavctx->max_b_frames = MIN(16, self->oavctx->max_b_frames);
+    return 0;
+}
+
+static int
+tvh_mpeg2video_meta(TVHContext *self, AVPacket *avpkt, th_pkt_t *pkt)
+{
+    const uint8_t *data = avpkt->data;
+    size_t size = 12; // header size
+
+    if ((avpkt->flags & AV_PKT_FLAG_KEY)) {
+        // sequence header
+        if (avpkt->size < size || RB32(data) != 0x000001b3) { // SEQ_START_CODE
+            tvh_context_log(self, LOG_ERR,
+                "MPEG2 header: missing sequence header");
+            return -1;
+        }
+        // load intra quantiser matrix
+        if (data[size-1] & 0x02) {
+            if (avpkt->size < size + 64) {
+                tvh_context_log(self, LOG_ERR,
+                    "MPEG2 header: missing load intra quantiser matrix");
+                return -1;
+            }
+            size += 64;
+        }
+        // load non-intra quantiser matrix
+        if (data[size-1] & 0x01) {
+            if (avpkt->size < size + 64) {
+                tvh_context_log(self, LOG_ERR,
+                    "MPEG2 header: missing load non-intra quantiser matrix");
+                return -1;
+            }
+            size += 64;
+        }
+        // sequence extension
+        if (avpkt->size < size + 10 || RB32(data + size) != 0x000001b5) { // EXT_START_CODE
+            tvh_context_log(self, LOG_ERR,
+                "MPEG2 header: missing sequence extension");
+            return -1;
+        }
+        size += 10;
+        // sequence display extension
+        if (RB32(data + size) == 0x000001b5) { // EXT_START_CODE
+            if (avpkt->size < size + 12) {
+                tvh_context_log(self, LOG_ERR,
+                    "MPEG2 header: missing sequence display extension");
+                return -1;
+            }
+            size += 12;
+        }
+        // group of pictures
+        if (avpkt->size < size + 8 || RB32(data + size) != 0x000001b8) { // GOP_START_CODE
+            tvh_context_log(self, LOG_ERR,
+                "MPEG2 header: missing group of pictures");
+            return -1;
+        }
+        size += 8;
+        if (!(pkt->pkt_meta = pktbuf_create(data, size))) {
+            return -1;
+        }
+        return 0;
+    }
+    return 1;
+}
+
+static TVHContextHelper TVHMPEG2VIDEOEncoder = {
+    .type   = AVMEDIA_TYPE_VIDEO,
+    .id     = AV_CODEC_ID_MPEG2VIDEO,
+    .open   = tvh_mpeg2video_open,
+    .meta   = tvh_mpeg2video_meta,
+};
+
+
+/* H264 */
+static int
+tvh_h264_meta(TVHContext *self, AVPacket *avpkt, th_pkt_t *pkt)
+{
+    if ((avpkt->flags & AV_PKT_FLAG_KEY) && (avpkt->size > 6) &&
+        (RB32(avpkt->data) == 0x00000001 || RB24(avpkt->data) == 0x000001)) {
+        const uint8_t *p = avpkt->data;
+        const uint8_t *end = p + avpkt->size;
+        const uint8_t *nal_start, *nal_end;
+        int size = 0;
+
+        nal_start = avc_find_startcode(p, end);
+        for (;;) {
+            while (nal_start < end && !*(nal_start++));
+            if (nal_start == end) {
+                break;
+            }
+            nal_end = avc_find_startcode(nal_start, end);
+            int nal_len = nal_end - nal_start;
+            uint8_t nal_type = (nal_start[0] & 0x1f);
+            if ((nal_type == H264_NAL_SPS &&
+                 nal_len >= 4 && nal_len <= UINT16_MAX) ||
+                (nal_type == H264_NAL_PPS &&
+                 nal_len <= UINT16_MAX)) {
+                size = nal_end - p;
+                nal_start = nal_end;
+                continue;
+            }
+            break;
+        }
+        if (size) {
+            if (!(pkt->pkt_meta = pktbuf_create(avpkt->data, size))) {
+                return -1;
+            }
+            return 0;
+        }
+    }
+    return 1;
+}
+
+static TVHContextHelper TVHH264Encoder = {
+    .type = AVMEDIA_TYPE_VIDEO,
+    .id   = AV_CODEC_ID_H264,
+    .meta = tvh_h264_meta,
+};
+
+
+/* HEVC */
+static int
+tvh_hevc_meta(TVHContext *self, AVPacket *avpkt, th_pkt_t *pkt)
+{
+
+    if ((avpkt->flags & AV_PKT_FLAG_KEY) && (avpkt->size >= 6) &&
+        (*avpkt->data != 1) &&
+        (RB24(avpkt->data) == 1 || RB32(avpkt->data) == 1)) {
+        const uint8_t *p = avpkt->data;
+        const uint8_t *end = p + avpkt->size;
+        const uint8_t *nal_start, *nal_end;
+        int size = 0;
+
+        nal_start = avc_find_startcode(p, end);
+        for (;;) {
+            while (nal_start < end && !*(nal_start++));
+            if (nal_start == end) {
+                break;
+            }
+            nal_end = avc_find_startcode(nal_start, end);
+            uint8_t nal_type = ((nal_start[0] >> 1) & 0x3f);
+            switch (nal_type) {
+                case HEVC_NAL_VPS:
+                case HEVC_NAL_SPS:
+                case HEVC_NAL_PPS:
+                case HEVC_NAL_SEI_PREFIX:
+                case HEVC_NAL_SEI_SUFFIX:
+                    size = nal_end - p;
+                    nal_start = nal_end;
+                    continue;
+                default:
+                    break;
+            }
+            break;
+        }
+        if (size) {
+            if (!(pkt->pkt_meta = pktbuf_create(avpkt->data, size))) {
+                return -1;
+            }
+            return 0;
+        }
+    }
+    return 1;
+}
+
+static TVHContextHelper TVHHEVCEncoder = {
+    .type = AVMEDIA_TYPE_VIDEO,
+    .id   = AV_CODEC_ID_HEVC,
+    .meta = tvh_hevc_meta,
+};
+
+
+/* VPX */
+static int
+tvh_libvpx_open(TVHContext *self, AVDictionary **opts)
+{
+    self->oavctx->gop_size = MIN(128, self->oavctx->gop_size);
+    return 0;
+}
+
+static TVHContextHelper TVHVP8Encoder = {
+    .type   = AVMEDIA_TYPE_VIDEO,
+    .id     = AV_CODEC_ID_VP8,
+    .open   = tvh_libvpx_open,
+};
+
+static TVHContextHelper TVHVP9Encoder = {
+    .type   = AVMEDIA_TYPE_VIDEO,
+    .id     = AV_CODEC_ID_VP9,
+    .open   = tvh_libvpx_open,
+};
+
+
+/* AAC */
+static void
+tvh_aac_pack_adts_header(TVHContext *self, pktbuf_t *pb)
+{
+    // XXX: this really should happen in the muxer
+    int chan_conf = (self->oavctx->channels == 8) ? 7 : self->oavctx->channels;
+    bitstream_t bs;
+
+    // https://wiki.multimedia.cx/index.php?title=ADTS
+    init_wbits(&bs, pktbuf_ptr(pb), 56);     // 7 bytes
+    put_bits(&bs, 0xfff, 12);                /* syncword */
+    put_bits(&bs, 0, 1);                     /* version */
+    put_bits(&bs, 0, 2);                     /* layer */
+    put_bits(&bs, 1, 1);                     /* protection absent */
+    put_bits(&bs, self->oavctx->profile, 2); /* profile (aot-1) */
+    put_bits(&bs, self->sri, 4);             /* sample rate index */
+    put_bits(&bs, 0, 1);                     /* private bit */
+    put_bits(&bs, chan_conf, 3);             /* channel configuration */
+    put_bits(&bs, 0, 1);                     /* originality */
+    put_bits(&bs, 0, 1);                     /* home */
+    put_bits(&bs, 0, 1);                     /* copyrighted id bit */
+    put_bits(&bs, 0, 1);                     /* copyright id start */
+    put_bits(&bs, pktbuf_len(pb), 13);       /* frame length */
+    put_bits(&bs, 0x7ff, 11);                /* buffer fullness */
+    put_bits(&bs, 0, 2);                     /* rdbs in frame (-1)*/
+}
+
+static th_pkt_t *
+tvh_aac_pack(TVHContext *self, AVPacket *avpkt)
+{
+    static const size_t header_size = 7, max_size = ((1 << 13) - 1);
+    size_t pkt_size = 0;
+    th_pkt_t *pkt = NULL;
+
+    // afaict: we write an adts header if there isn't one already, why would
+    // there be one in the first place?.
+    // originally there was a check for avpkt->size < 2, I don't get it.
+    // max aac frame size = 768 bytes per channel, max writable size 13 bits
+    if (avpkt->size > (768 * self->oavctx->channels)) {
+        tvh_context_log(self, LOG_WARNING,
+            "packet size (%d) > aac max frame size (%d for %d channels)",
+            avpkt->size, (768 * self->oavctx->channels),
+            self->oavctx->channels);
+    }
+    if ((pkt_size = avpkt->size + header_size) > max_size) {
+        tvh_context_log(self, LOG_ERR, "aac frame data too big");
+    }
+    else if (avpkt->data[0] != 0xff || (avpkt->data[1] & 0xf0) != 0xf0) {
+        if ((pkt = pkt_create(NULL, pkt_size, avpkt->pts, avpkt->dts))) {
+            tvh_aac_pack_adts_header(self, pkt->pkt_payload);
+            memcpy(pktbuf_ptr(pkt->pkt_payload) + header_size,
+                   avpkt->data, avpkt->size);
+        }
+    }
+    else {
+        pkt = pkt_create(avpkt->data, avpkt->size, avpkt->pts, avpkt->dts);
+    }
+    return pkt;
+}
+
+static TVHContextHelper TVHAACEncoder = {
+    .type = AVMEDIA_TYPE_AUDIO,
+    .id   = AV_CODEC_ID_AAC,
+    .pack = tvh_aac_pack,
+};
+
+
+static void
+tvh_encoder_helpers_register()
+{
+    SLIST_INIT(&tvh_encoder_helpers);
+    /* video */
+    tvh_encoder_helper_register(&TVHMPEG2VIDEOEncoder);
+    tvh_encoder_helper_register(&TVHH264Encoder);
+    tvh_encoder_helper_register(&TVHHEVCEncoder);
+    tvh_encoder_helper_register(&TVHVP8Encoder);
+    tvh_encoder_helper_register(&TVHVP9Encoder);
+    /* audio */
+    tvh_encoder_helper_register(&TVHAACEncoder);
+}
+
+
+/* public =================================================================== */
+
+TVHContextHelper *
+tvh_decoder_helper_find(const AVCodec *codec)
+{
+    return tvh_context_helper_find(&tvh_decoder_helpers, codec);
+}
+
+
+TVHContextHelper *
+tvh_encoder_helper_find(const AVCodec *codec)
+{
+    return tvh_context_helper_find(&tvh_encoder_helpers, codec);
+}
+
+
+void
+tvh_context_helpers_register()
+{
+    tvh_decoder_helpers_register();
+    tvh_encoder_helpers_register();
+}
+
+
+void
+tvh_context_helpers_forget()
+{
+    tvhinfo(LS_TRANSCODE, "forgetting context helpers");
+    while (!SLIST_EMPTY(&tvh_encoder_helpers)) {
+        SLIST_REMOVE_HEAD(&tvh_encoder_helpers, link);
+    }
+    while (!SLIST_EMPTY(&tvh_decoder_helpers)) {
+        SLIST_REMOVE_HEAD(&tvh_decoder_helpers, link);
+    }
+}
diff --git a/src/transcoding/transcode/hwaccels/hwaccels.c b/src/transcoding/transcode/hwaccels/hwaccels.c
new file mode 100644 (file)
index 0000000..75195dd
--- /dev/null
@@ -0,0 +1,154 @@
+/*
+ *  tvheadend - Transcoding
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hwaccels.h"
+
+#if ENABLE_VAAPI
+#include "vaapi.h"
+#endif
+
+#include <libavutil/pixdesc.h>
+
+
+/* decoding ================================================================= */
+
+/* lifted from libavcodec/utils.c */
+static AVHWAccel *
+find_hwaccel(enum AVCodecID codec_id, enum AVPixelFormat pix_fmt)
+{
+    AVHWAccel *hwaccel = NULL;
+
+    while ((hwaccel = av_hwaccel_next(hwaccel))) {
+        if (hwaccel->id == codec_id && hwaccel->pix_fmt == pix_fmt) {
+            return hwaccel;
+        }
+    }
+    return NULL;
+}
+
+
+static int
+hwaccels_decode_setup_context(AVCodecContext *avctx,
+                              const enum AVPixelFormat pix_fmt)
+{
+    AVHWAccel *hwa = NULL;
+
+    if (!(hwa = find_hwaccel(avctx->codec_id, pix_fmt))) {
+        tvherror(LS_TRANSCODE, "no AVHWAccel for the pixel format");
+        return AVERROR(ENOENT);
+    }
+    switch (pix_fmt) {
+#if ENABLE_VAAPI
+        case AV_PIX_FMT_VAAPI:
+            return vaapi_decode_setup_context(avctx);
+#endif
+        default:
+            break;
+    }
+    return -1;
+}
+
+
+enum AVPixelFormat
+hwaccels_decode_get_format(AVCodecContext *avctx,
+                           const enum AVPixelFormat *pix_fmts)
+{
+    enum AVPixelFormat pix_fmt = AV_PIX_FMT_NONE;
+    const AVPixFmtDescriptor *desc;
+    int i;
+
+    for (i = 0; pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
+        pix_fmt = pix_fmts[i];
+        if ((desc = av_pix_fmt_desc_get(pix_fmt))) {
+            tvhdebug(LS_TRANSCODE, "trying pix_fmt: %s", desc->name);
+            if ((desc->flags & AV_PIX_FMT_FLAG_HWACCEL) &&
+                !hwaccels_decode_setup_context(avctx, pix_fmt)) {
+                break;
+            }
+        }
+    }
+    return pix_fmt;
+}
+
+
+void
+hwaccels_decode_close_context(AVCodecContext *avctx)
+{
+    if (avctx->hwaccel_context) {
+        switch (avctx->pix_fmt) {
+#if ENABLE_VAAPI
+            case AV_PIX_FMT_VAAPI:
+                vaapi_decode_close_context(avctx);
+                break;
+#endif
+            default:
+                break;
+        }
+        avctx->hwaccel_context = NULL;
+    }
+}
+
+
+/* encoding ================================================================= */
+
+int
+hwaccels_encode_setup_context(AVCodecContext *avctx)
+{
+    switch (avctx->pix_fmt) {
+#if ENABLE_VAAPI
+        case AV_PIX_FMT_VAAPI:
+            return vaapi_encode_setup_context(avctx);
+#endif
+        default:
+            break;
+    }
+    return 0;
+}
+
+
+void
+hwaccels_encode_close_context(AVCodecContext *avctx)
+{
+    switch (avctx->pix_fmt) {
+#if ENABLE_VAAPI
+        case AV_PIX_FMT_VAAPI:
+            vaapi_encode_close_context(avctx);
+            break;
+#endif
+        default:
+            break;
+    }
+}
+
+
+/* module =================================================================== */
+
+void
+hwaccels_init(void)
+{
+}
+
+
+void
+hwaccels_done(void)
+{
+#if ENABLE_VAAPI
+    vaapi_done();
+#endif
+}
diff --git a/src/transcoding/transcode/hwaccels/hwaccels.h b/src/transcoding/transcode/hwaccels/hwaccels.h
new file mode 100644 (file)
index 0000000..aaa3733
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ *  tvheadend - Transcoding
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef TVH_TRANSCODING_TRANSCODE_HWACCELS_H__
+#define TVH_TRANSCODING_TRANSCODE_HWACCELS_H__
+
+
+#include "tvheadend.h"
+
+#include <libavcodec/avcodec.h>
+
+
+/* decoding ================================================================= */
+
+enum AVPixelFormat
+hwaccels_decode_get_format(AVCodecContext *avctx,
+                           const enum AVPixelFormat *pix_fmts);
+
+void
+hwaccels_decode_close_context(AVCodecContext *avctx);
+
+
+/* encoding ================================================================= */
+
+int
+hwaccels_encode_setup_context(AVCodecContext *avctx);
+
+void
+hwaccels_encode_close_context(AVCodecContext *avctx);
+
+
+/* module =================================================================== */
+
+void
+hwaccels_init(void);
+
+void
+hwaccels_done(void);
+
+
+#endif // TVH_TRANSCODING_TRANSCODE_HWACCELS_H__
diff --git a/src/transcoding/transcode/hwaccels/vaapi.c b/src/transcoding/transcode/hwaccels/vaapi.c
new file mode 100644 (file)
index 0000000..89321eb
--- /dev/null
@@ -0,0 +1,544 @@
+/*
+ *  tvheadend - Transcoding
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "vaapi.h"
+
+#include <libavcodec/vaapi.h>
+#include <libavutil/hwcontext.h>
+#include <libavutil/hwcontext_vaapi.h>
+#include <libavutil/pixdesc.h>
+
+#include <va/va.h>
+
+
+static AVBufferRef *hw_device_ref = NULL;
+
+
+typedef struct tvh_vaapi_context_t {
+    struct vaapi_context;
+    VAEntrypoint entrypoint;
+    enum AVPixelFormat io_format;
+    enum AVPixelFormat sw_format;
+    int width;
+    int height;
+    AVBufferRef *hw_device_ref;
+    AVBufferRef *hw_frames_ref;
+} TVHVAContext;
+
+
+static int
+tvhva_init()
+{
+    static const char *renderD_fmt = "/dev/dri/renderD%d";
+    static const char *card_fmt = "/dev/dri/card%d";
+    int i, dev_num;
+    char device[32];
+
+    //search for valid graphics device
+    for (i = 0; i < 6; i++) {
+        memset(device, 0, sizeof(device));
+        if (i < 3) {
+            dev_num = i + 128;
+            snprintf(device, sizeof(device), renderD_fmt, dev_num);
+        }
+        else {
+            dev_num = i - 3;
+            snprintf(device, sizeof(device), card_fmt, dev_num);
+        }
+        tvhdebug(LS_VAAPI, "trying device: %s", device);
+        if (av_hwdevice_ctx_create(&hw_device_ref, AV_HWDEVICE_TYPE_VAAPI,
+                                   device, NULL, 0)) {
+            tvhdebug(LS_VAAPI,
+                     "failed to create a context for device: %s", device);
+            continue;
+        }
+        else {
+            tvhinfo(LS_VAAPI,
+                    "succesful context creation for device: %s", device);
+            return 0;
+        }
+    }
+    tvherror(LS_VAAPI, "failed to find suitable VAAPI device");
+    return -1;
+}
+
+
+static void
+tvhva_done()
+{
+    if (hw_device_ref) {
+        av_buffer_unref(&hw_device_ref);
+        hw_device_ref = NULL;
+    }
+}
+
+
+/* TVHVAContext ============================================================= */
+
+static void
+tvhva_context_destroy(TVHVAContext *self)
+{
+    if (self) {
+        if (self->context_id != VA_INVALID_ID) {
+            vaDestroyContext(self->display, self->context_id);
+            self->context_id = VA_INVALID_ID;
+        }
+        if (self->config_id != VA_INVALID_ID) {
+            vaDestroyConfig(self->display, self->config_id);
+            self->config_id = VA_INVALID_ID;
+        }
+        self->display = NULL;
+        if (self->hw_frames_ref) {
+            av_buffer_unref(&self->hw_frames_ref);
+            self->hw_frames_ref = NULL;
+        }
+        if (self->hw_device_ref) {
+            av_buffer_unref(&self->hw_device_ref);
+            self->hw_device_ref = NULL;
+        }
+        free(self);
+        self = NULL;
+    }
+}
+
+
+static VADisplay *
+tvhva_context_display(TVHVAContext *self)
+{
+    AVHWDeviceContext *hw_device_ctx = NULL;
+
+    if (!hw_device_ref && tvhva_init()) {
+        return NULL;
+    }
+    if (!(self->hw_device_ref = av_buffer_ref(hw_device_ref))) {
+        return NULL;
+    }
+    hw_device_ctx = (AVHWDeviceContext*)self->hw_device_ref->data;
+    return ((AVVAAPIDeviceContext *)hw_device_ctx->hwctx)->display;
+}
+
+
+static VAProfile
+tvhva_context_profile(TVHVAContext *self, AVCodecContext *avctx)
+{
+    VAStatus va_res = VA_STATUS_ERROR_UNKNOWN;
+    VAProfile profile = VAProfileNone, check = VAProfileNone, *profiles = NULL;
+    int i, j, profiles_max, profiles_len;
+
+    switch (avctx->codec->id) {
+        case AV_CODEC_ID_MPEG2VIDEO:
+            switch (avctx->profile) {
+                case FF_PROFILE_UNKNOWN:
+                case FF_PROFILE_MPEG2_MAIN:
+                    check = VAProfileMPEG2Main;
+                    break;
+                case FF_PROFILE_MPEG2_SIMPLE:
+                    check = VAProfileMPEG2Simple;
+                    break;
+                default:
+                    break;
+            }
+            break;
+        case AV_CODEC_ID_H264:
+            switch (avctx->profile) {
+                case FF_PROFILE_UNKNOWN:
+                case FF_PROFILE_H264_HIGH:
+                    check = VAProfileH264High;
+                    break;
+                case FF_PROFILE_H264_BASELINE:
+                    check = VAProfileH264Baseline;
+                    break;
+                case FF_PROFILE_H264_CONSTRAINED_BASELINE:
+                    check = VAProfileH264ConstrainedBaseline;
+                    break;
+                case FF_PROFILE_H264_MAIN:
+                    check = VAProfileH264Main;
+                    break;
+                default:
+                    break;
+            }
+            break;
+        case AV_CODEC_ID_HEVC:
+            switch (avctx->profile) {
+                case FF_PROFILE_UNKNOWN:
+                case FF_PROFILE_HEVC_MAIN:
+                    check = VAProfileHEVCMain;
+                    break;
+                case FF_PROFILE_HEVC_MAIN_10:
+                    check = VAProfileHEVCMain10;
+                    break;
+                default:
+                    break;
+            }
+            break;
+        default:
+            break;
+    }
+
+    if (check != VAProfileNone &&
+        (profiles_max = vaMaxNumProfiles(self->display)) > 0 &&
+        (profiles = calloc(profiles_max, sizeof(VAProfile)))) {
+        for (j = 0; j < profiles_max; j++) {
+            profiles[j] = VAProfileNone;
+        }
+        va_res = vaQueryConfigProfiles(self->display,
+                                       profiles, &profiles_len);
+        if (va_res == VA_STATUS_SUCCESS) {
+            for (i = 0; i < profiles_len; i++) {
+                if (profiles[i] == check) {
+                    profile = check;
+                    break;
+                }
+            }
+        }
+        free(profiles);
+    }
+
+    return profile;
+}
+
+
+static int
+tvhva_context_check_profile(TVHVAContext *self, VAProfile profile)
+{
+    VAStatus va_res = VA_STATUS_ERROR_UNKNOWN;
+    VAEntrypoint *entrypoints = NULL;
+    int res = -1, i, entrypoints_max, entrypoints_len;
+
+    if ((entrypoints_max = vaMaxNumEntrypoints(self->display)) > 0 &&
+        (entrypoints = calloc(entrypoints_max, sizeof(VAEntrypoint)))) {
+        va_res = vaQueryConfigEntrypoints(self->display, profile,
+                                          entrypoints, &entrypoints_len);
+        if (va_res == VA_STATUS_SUCCESS) {
+            for (i = 0; i < entrypoints_len; i++) {
+                if (entrypoints[i] == self->entrypoint) {
+                    res = 0;
+                    break;
+                }
+            }
+        }
+        free(entrypoints);
+    }
+    return res;
+}
+
+
+static unsigned int
+tvhva_get_format(enum AVPixelFormat pix_fmt)
+{
+    switch (pix_fmt) {
+        //case AV_PIX_FMT_YUV420P: // the cake is a lie
+        case AV_PIX_FMT_NV12:
+            return VA_RT_FORMAT_YUV420;
+        case AV_PIX_FMT_YUV422P:
+        case AV_PIX_FMT_UYVY422:
+        case AV_PIX_FMT_YUYV422:
+            return VA_RT_FORMAT_YUV422;
+        case AV_PIX_FMT_GRAY8:
+            return VA_RT_FORMAT_YUV400;
+        default:
+            return 0;
+    }
+}
+
+
+static int
+tvhva_context_config(TVHVAContext *self, VAProfile profile, unsigned int format)
+{
+    VAStatus va_res = VA_STATUS_ERROR_UNKNOWN;
+    VAConfigAttrib attrib = { VAConfigAttribRTFormat, VA_ATTRIB_NOT_SUPPORTED };
+
+    // vaCreateConfig
+    va_res = vaGetConfigAttributes(self->display, profile, self->entrypoint,
+                                   &attrib, 1);
+    if (va_res != VA_STATUS_SUCCESS) {
+        tvherror(LS_VAAPI, "vaGetConfigAttributes: %s", vaErrorStr(va_res));
+        return -1;
+    }
+    if (attrib.value == VA_ATTRIB_NOT_SUPPORTED || !(attrib.value & format)) {
+        tvherror(LS_VAAPI, "unsupported VA_RT_FORMAT");
+        return -1;
+    }
+    attrib.value = format;
+    va_res = vaCreateConfig(self->display, profile, self->entrypoint,
+                            &attrib, 1, &self->config_id);
+    if (va_res != VA_STATUS_SUCCESS) {
+        tvherror(LS_VAAPI, "vaCreateConfig: %s", vaErrorStr(va_res));
+        return -1;
+    }
+    return 0;
+}
+
+
+static int
+tvhva_context_check_constraints(TVHVAContext *self)
+{
+    AVVAAPIHWConfig *va_config = NULL;
+    AVHWFramesConstraints *hw_constraints = NULL;
+    enum AVPixelFormat sw_format = AV_PIX_FMT_NONE;
+    const AVPixFmtDescriptor *sw_desc = NULL, *io_desc = NULL;
+    int i, ret = 0;
+
+    if (!(va_config = av_hwdevice_hwconfig_alloc(self->hw_device_ref))) {
+        tvherror(LS_VAAPI, "failed to allocate hwconfig");
+        return AVERROR(ENOMEM);
+    }
+    va_config->config_id = self->config_id;
+
+    hw_constraints =
+        av_hwdevice_get_hwframe_constraints(self->hw_device_ref, va_config);
+    if (!hw_constraints) {
+        tvherror(LS_VAAPI, "failed to get constraints");
+        av_freep(&va_config);
+        return -1;
+    }
+
+    if (self->io_format != AV_PIX_FMT_NONE) {
+        for (i = 0; hw_constraints->valid_sw_formats[i] != AV_PIX_FMT_NONE; i++) {
+            sw_format = hw_constraints->valid_sw_formats[i];
+            if (sw_format == self->io_format) {
+                self->sw_format = sw_format;
+                break;
+            }
+        }
+        if (self->sw_format == AV_PIX_FMT_NONE &&
+            (io_desc = av_pix_fmt_desc_get(self->io_format))) {
+            for (i = 0; hw_constraints->valid_sw_formats[i] != AV_PIX_FMT_NONE; i++) {
+                sw_format = hw_constraints->valid_sw_formats[i];
+                if ((sw_desc = av_pix_fmt_desc_get(sw_format)) &&
+                    sw_desc->nb_components == io_desc->nb_components &&
+                    sw_desc->log2_chroma_w == io_desc->log2_chroma_w &&
+                    sw_desc->log2_chroma_h == io_desc->log2_chroma_h) {
+                    self->sw_format = sw_format;
+                    break;
+                }
+            }
+        }
+    }
+    if (self->sw_format == AV_PIX_FMT_NONE) {
+        tvherror(LS_VAAPI, "VAAPI hardware does not support pixel format: %s",
+                 av_get_pix_fmt_name(self->io_format));
+        ret = AVERROR(EINVAL);
+        goto end;
+    }
+
+    // Ensure the picture size is supported by the hardware.
+    if (self->width < hw_constraints->min_width ||
+        self->height < hw_constraints->min_height ||
+        self->width > hw_constraints->max_width ||
+        self->height > hw_constraints->max_height) {
+        tvherror(LS_VAAPI, "VAAPI hardware does not support image "
+                 "size %dx%d (constraints: width %d-%d height %d-%d).",
+                 self->width, self->height,
+                 hw_constraints->min_width, hw_constraints->max_width,
+                 hw_constraints->min_height, hw_constraints->max_height);
+        ret = AVERROR(EINVAL);
+    }
+
+end:
+    av_hwframe_constraints_free(&hw_constraints);
+    av_freep(&va_config);
+    return ret;
+}
+
+
+static int
+tvhva_context_setup(TVHVAContext *self, AVCodecContext *avctx)
+{
+    VAProfile profile = VAProfileNone;
+    unsigned int format = 0;
+    VAStatus va_res = VA_STATUS_ERROR_UNKNOWN;
+    AVHWFramesContext *hw_frames_ctx = NULL;
+    AVVAAPIFramesContext *va_frames = NULL;
+
+    if (!(self->display = tvhva_context_display(self))) {
+        return -1;
+    }
+    if ((profile = tvhva_context_profile(self, avctx)) == VAProfileNone ||
+        tvhva_context_check_profile(self, profile)) {
+        tvherror(LS_VAAPI, "unsupported codec: %s and/or profile: %s",
+                 avctx->codec->name,
+                 av_get_profile_name(avctx->codec, avctx->profile));
+        return -1;
+    }
+    if (!(format = tvhva_get_format(self->io_format))) {
+        tvherror(LS_VAAPI, "unsupported pixel format: %s",
+                 av_get_pix_fmt_name(self->io_format));
+        return -1;
+    }
+
+    if (tvhva_context_config(self, profile, format) ||
+        tvhva_context_check_constraints(self)) {
+        return -1;
+    }
+
+    if (!(self->hw_frames_ref = av_hwframe_ctx_alloc(self->hw_device_ref))) {
+        tvherror(LS_VAAPI, "failed to create VAAPI frame context.");
+        return AVERROR(ENOMEM);
+    }
+    hw_frames_ctx = (AVHWFramesContext*)self->hw_frames_ref->data;
+    hw_frames_ctx->format = AV_PIX_FMT_VAAPI;
+    hw_frames_ctx->sw_format = self->sw_format;
+    hw_frames_ctx->width = self->width;
+    hw_frames_ctx->height = self->height;
+    hw_frames_ctx->initial_pool_size = 32;
+
+    if (av_hwframe_ctx_init(self->hw_frames_ref) < 0) {
+        tvherror(LS_VAAPI, "failed to initialise VAAPI frame context");
+        return -1;
+    }
+
+    // vaCreateContext
+    if (self->entrypoint == VAEntrypointVLD) { // decode only
+        va_frames = hw_frames_ctx->hwctx;
+        va_res = vaCreateContext(self->display, self->config_id,
+                                 self->width, self->height,
+                                 VA_PROGRESSIVE,
+                                 va_frames->surface_ids, va_frames->nb_surfaces,
+                                 &self->context_id);
+        if (va_res != VA_STATUS_SUCCESS) {
+            tvherror(LS_VAAPI, "vaCreateContext: %s", vaErrorStr(va_res));
+            return -1;
+        }
+    }
+
+    if (!(avctx->hw_frames_ctx = av_buffer_ref(self->hw_frames_ref))) {
+        return AVERROR(ENOMEM);
+    }
+
+    avctx->sw_pix_fmt = self->sw_format;
+    avctx->thread_count = 1;
+
+    return 0;
+}
+
+
+static TVHVAContext *
+tvhva_context_create(AVCodecContext *avctx, VAEntrypoint entrypoint)
+{
+    TVHVAContext *self = NULL;
+
+    if (!(self = calloc(1, sizeof(TVHVAContext)))) {
+        tvherror(LS_VAAPI, "failed to allocate vaapi context");
+        return NULL;
+    }
+    self->display = NULL;
+    self->config_id = VA_INVALID_ID;
+    self->context_id = VA_INVALID_ID;
+    self->entrypoint = entrypoint;
+    if (avctx->sw_pix_fmt == AV_PIX_FMT_YUV420P) {
+        self->io_format = AV_PIX_FMT_NV12;
+    }
+    else {
+        self->io_format = avctx->sw_pix_fmt;
+    }
+    self->sw_format = AV_PIX_FMT_NONE;
+    self->width = avctx->coded_width;
+    self->height = avctx->coded_height;
+    self->hw_device_ref = NULL;
+    self->hw_frames_ref = NULL;
+    if (tvhva_context_setup(self, avctx)) {
+        tvhva_context_destroy(self);
+        return NULL;
+    }
+    return self;
+}
+
+
+/* decoding ================================================================= */
+
+static int
+vaapi_get_buffer2(AVCodecContext *avctx, AVFrame *avframe, int flags)
+{
+    if (!(avctx->codec->capabilities & AV_CODEC_CAP_DR1)) {
+        return avcodec_default_get_buffer2(avctx, avframe, flags);
+    }
+    return av_hwframe_get_buffer(avctx->hw_frames_ctx, avframe, 0);
+}
+
+
+int
+vaapi_decode_setup_context(AVCodecContext *avctx)
+{
+    if (!(avctx->hwaccel_context =
+          tvhva_context_create(avctx, VAEntrypointVLD))) {
+        return -1;
+    }
+
+    avctx->get_buffer2 = vaapi_get_buffer2;
+    avctx->thread_safe_callbacks = 0;
+
+    return 0;
+}
+
+
+void
+vaapi_decode_close_context(AVCodecContext *avctx)
+{
+    /*if (avctx->hw_frames_ctx) {
+        av_buffer_unref(&avctx->hw_frames_ctx);
+        avctx->hw_frames_ctx = NULL;
+    }*/
+    tvhva_context_destroy(avctx->hwaccel_context);
+}
+
+
+/* encoding ================================================================= */
+
+int
+vaapi_encode_setup_context(AVCodecContext *avctx)
+{
+    TVHVAContext *hwaccel_context = NULL;
+
+    if (!(hwaccel_context =
+          tvhva_context_create(avctx, VAEntrypointEncSlice))) {
+        return -1;
+    }
+    if (!(avctx->opaque = av_buffer_ref(hwaccel_context->hw_device_ref))) {
+        tvhva_context_destroy(hwaccel_context);
+        return AVERROR(ENOMEM);
+    }
+    tvhva_context_destroy(hwaccel_context);
+    return 0;
+}
+
+
+void
+vaapi_encode_close_context(AVCodecContext *avctx)
+{
+    /*if (avctx->hw_frames_ctx) {
+        av_buffer_unref(&avctx->hw_frames_ctx);
+        avctx->hw_frames_ctx = NULL;
+    }*/
+    if (avctx->opaque) {
+        AVBufferRef *hw_device_ctx = avctx->opaque;
+        av_buffer_unref(&hw_device_ctx);
+        avctx->opaque = NULL;
+    }
+}
+
+
+/* module =================================================================== */
+
+void
+vaapi_done()
+{
+    tvhva_done();
+}
diff --git a/src/transcoding/transcode/hwaccels/vaapi.h b/src/transcoding/transcode/hwaccels/vaapi.h
new file mode 100644 (file)
index 0000000..af39448
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ *  tvheadend - Transcoding
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef TVH_TRANSCODING_TRANSCODE_HWACCELS_VAAPI_H__
+#define TVH_TRANSCODING_TRANSCODE_HWACCELS_VAAPI_H__
+
+
+#include "tvheadend.h"
+
+#include <libavcodec/avcodec.h>
+
+
+/* decoding ================================================================= */
+
+int
+vaapi_decode_setup_context(AVCodecContext *avctx);
+
+void
+vaapi_decode_close_context(AVCodecContext *avctx);
+
+
+/* encoding ================================================================= */
+
+int
+vaapi_encode_setup_context(AVCodecContext *avctx);
+
+void
+vaapi_encode_close_context(AVCodecContext *avctx);
+
+
+/* module =================================================================== */
+
+void
+vaapi_done(void);
+
+
+#endif // TVH_TRANSCODING_TRANSCODE_HWACCELS_VAAPI_H__
diff --git a/src/transcoding/transcode/internals.h b/src/transcoding/transcode/internals.h
new file mode 100644 (file)
index 0000000..41f5842
--- /dev/null
@@ -0,0 +1,228 @@
+/*
+ *  tvheadend - Transcoding
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef TVH_TRANSCODING_TRANSCODE_INTERNALS_H__
+#define TVH_TRANSCODING_TRANSCODE_INTERNALS_H__
+
+
+#include "log.h"
+
+#include "transcoding/codec.h"
+#include "transcoding/memutils.h"
+
+#include <libavcodec/avcodec.h>
+#include <libavfilter/avfilter.h>
+#include <libavfilter/buffersrc.h>
+#include <libavfilter/buffersink.h>
+#include <libavutil/pixdesc.h>
+
+
+#define tvh_st_t streaming_target_t
+#define tvh_ss_t streaming_start_t
+#define tvh_sm_t streaming_message_t
+
+
+extern TVHCodecProfile *tvh_codec_profile_copy;
+
+
+struct tvh_transcoder_t;
+typedef struct tvh_transcoder_t TVHTranscoder;
+
+struct tvh_stream_t;
+typedef struct tvh_stream_t TVHStream;
+
+struct tvh_context_type_t;
+typedef struct tvh_context_type_t TVHContextType;
+
+struct tvh_context_t;
+typedef struct tvh_context_t TVHContext;
+
+struct tvh_context_helper_t;
+typedef struct tvh_context_helper_t TVHContextHelper;
+
+// post phase _MUST_ be == phase + 1
+typedef enum {
+    OPEN_DECODER,
+    OPEN_DECODER_POST,
+    OPEN_ENCODER,
+    OPEN_ENCODER_POST
+} TVHOpenPhase;
+
+
+/* TVHTranscoder ============================================================ */
+
+SLIST_HEAD(TVHStreams, tvh_stream_t);
+
+typedef struct tvh_transcoder_t {
+    tvh_st_t input;
+    struct TVHStreams streams;
+    uint32_t id;
+    tvh_st_t *output;
+    TVHCodecProfile *profiles[AVMEDIA_TYPE_NB];
+} TVHTranscoder;
+
+int
+tvh_transcoder_deliver(TVHTranscoder *self, th_pkt_t *pkt);
+
+TVHTranscoder *
+tvh_transcoder_create(tvh_st_t *output, const char **profiles);
+
+void
+tvh_transcoder_destroy(TVHTranscoder *self);
+
+
+/* TVHStream ================================================================ */
+
+typedef struct tvh_stream_t {
+    TVHTranscoder *transcoder;
+    int id;
+    int index;
+    tvh_sct_t type;
+    TVHContext *context;
+    SLIST_ENTRY(tvh_stream_t) link;
+} TVHStream;
+
+void
+tvh_stream_stop(TVHStream *self, int flush);
+
+int
+tvh_stream_handle(TVHStream *self, th_pkt_t *pkt);
+
+int
+tvh_stream_deliver(TVHStream *self, th_pkt_t *pkt);
+
+TVHStream *
+tvh_stream_create(TVHTranscoder *transcoder, TVHCodecProfile *profile,
+                  tvh_ssc_t *ssc);
+
+void
+tvh_stream_destroy(TVHStream *self);
+
+
+/* TVHContextType =========================================================== */
+
+typedef int (*tvh_context_open_meth)(TVHContext *, TVHOpenPhase, AVDictionary **);
+typedef int (*tvh_context_decode_meth)(TVHContext *, AVPacket *);
+typedef int (*tvh_context_encode_meth)(TVHContext *, AVFrame *);
+typedef int (*tvh_context_ship_meth)(TVHContext *, AVPacket *);
+typedef int (*tvh_context_wrap_meth)(TVHContext *, AVPacket *, th_pkt_t *);
+typedef void (*tvh_context_close_meth)(TVHContext *);
+
+typedef struct tvh_context_type_t {
+    enum AVMediaType media_type;
+    tvh_context_open_meth open;
+    tvh_context_decode_meth decode;
+    tvh_context_encode_meth encode;
+    tvh_context_ship_meth ship;
+    tvh_context_wrap_meth wrap;
+    tvh_context_close_meth close;
+    SLIST_ENTRY(tvh_context_type_t) link;
+} TVHContextType;
+
+void
+tvh_context_types_register(void);
+
+void
+tvh_context_types_forget(void);
+
+
+/* TVHContext =============================================================== */
+
+typedef struct tvh_context_t {
+    TVHStream *stream;
+    TVHCodecProfile *profile;
+    TVHContextType *type;
+    AVCodecContext *iavctx;
+    AVCodecContext *oavctx;
+    AVFrame *iavframe;
+    AVFrame *oavframe;
+    pktbuf_t *input_gh;
+    TVHContextHelper *helper; // encoder helper
+    AVFilterGraph *avfltgraph;
+    AVFilterContext *iavfltctx;
+    AVFilterContext *oavfltctx;
+    th_pkt_t *src_pkt;
+    int require_meta;
+    // only for audio
+    int64_t duration;
+    int64_t delta;
+    int64_t pts;
+    uint8_t sri;
+} TVHContext;
+
+int
+tvh_context_get_int_opt(AVDictionary **opts, const char *key, int *value);
+
+int
+tvh_context_get_str_opt(AVDictionary **opts, const char *key, char **value);
+
+void
+tvh_context_close(TVHContext *self, int flush);
+
+/* __VA_ARGS__ = NULL terminated list of sink options
+   sink option = (const char *name, const uint8_t *value, int size) */
+int
+tvh_context_open_filters(TVHContext *self,
+                         const char *source_name, const char *source_args,
+                         const char *filters, const char *sink_name, ...);
+
+int
+tvh_context_handle(TVHContext *self, th_pkt_t *pkt);
+
+int
+tvh_context_deliver(TVHContext *self, th_pkt_t *pkt);
+
+TVHContext *
+tvh_context_create(TVHStream *stream, TVHCodecProfile *profile,
+                   AVCodec *iavcodec, AVCodec *oavcodec, pktbuf_t *input_gh);
+
+void
+tvh_context_destroy(TVHContext *self);
+
+
+/* TVHContextHelper ========================================================= */
+
+typedef int (*tvh_context_helper_open_meth)(TVHContext *, AVDictionary **);
+typedef th_pkt_t *(*tvh_context_helper_pack_meth)(TVHContext *, AVPacket *);
+typedef int (*tvh_context_helper_meta_meth)(TVHContext *, AVPacket *, th_pkt_t *);
+
+typedef struct tvh_context_helper_t {
+    enum AVMediaType type;
+    enum AVCodecID id;
+    tvh_context_helper_open_meth open;
+    tvh_context_helper_pack_meth pack;
+    tvh_context_helper_meta_meth meta;
+    SLIST_ENTRY(tvh_context_helper_t) link;
+} TVHContextHelper;
+
+TVHContextHelper *
+tvh_decoder_helper_find(const AVCodec *avcodec);
+
+TVHContextHelper *
+tvh_encoder_helper_find(const AVCodec *avcodec);
+
+void
+tvh_context_helpers_register(void);
+
+void
+tvh_context_helpers_forget(void);
+
+
+#endif // TVH_TRANSCODING_TRANSCODE_INTERNALS_H__
diff --git a/src/transcoding/transcode/log.h b/src/transcoding/transcode/log.h
new file mode 100644 (file)
index 0000000..31e8974
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ *  tvheadend - Transcoding
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef TVH_TRANSCODING_TRANSCODE_LOG_H__
+#define TVH_TRANSCODING_TRANSCODE_LOG_H__
+
+
+#define tvh_transcoder_log(self, level, fmt, ...) \
+    do { \
+        tvhlog((level), LS_TRANSCODE, "%04X: " fmt, \
+            (((self)->id) & 0xffff), \
+            ##__VA_ARGS__); \
+    } while (0)
+
+#define _stream_log(level, fmt, transcoder, id, type, ...) \
+    do { \
+        tvh_transcoder_log((transcoder), (level), "%02d:%s: " fmt, \
+            (id), \
+            streaming_component_type2txt((type)), \
+            ##__VA_ARGS__); \
+    } while (0)
+
+#define tvh_ssc_log(ssc, level, fmt, transcoder, ...) \
+    do { \
+        _stream_log((level), fmt, \
+            (transcoder), \
+            (ssc)->ssc_index, \
+            (ssc)->ssc_type, \
+            ##__VA_ARGS__); \
+    } while (0)
+
+#define tvh_stream_log(self, level, fmt, ...) \
+    do { \
+        _stream_log((level), fmt, \
+            (self)->transcoder, \
+            (self)->id, \
+            (self)->type, \
+            ##__VA_ARGS__); \
+    } while (0)
+
+#define tvh_context_log(self, level, fmt, ...) \
+    do { \
+        tvh_stream_log((self)->stream, (level), "[%s => %s]: " fmt, \
+            ((self)->iavctx) ? (self)->iavctx->codec->name : "<unknown>", \
+            ((self)->oavctx) ? (self)->oavctx->codec->name : "<unknown>", \
+            ##__VA_ARGS__); \
+    } while (0)
+
+
+#endif // TVH_TRANSCODING_TRANSCODE_LOG_H__
diff --git a/src/transcoding/transcode/module.c b/src/transcoding/transcode/module.c
new file mode 100644 (file)
index 0000000..e066979
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ *  tvheadend - Transcoding
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/transcode.h"
+#include "internals.h"
+
+#if ENABLE_HWACCELS
+#include "hwaccels/hwaccels.h"
+#endif
+
+
+/* TVHTranscoder ============================================================ */
+
+streaming_target_t *
+transcoder_create(streaming_target_t *output, const char **profiles)
+{
+    return (streaming_target_t *)tvh_transcoder_create(output, profiles);
+}
+
+
+void
+transcoder_destroy(streaming_target_t *st)
+{
+    tvh_transcoder_destroy((TVHTranscoder *)st);
+}
+
+
+/* module level ============================================================= */
+
+void
+transcode_init()
+{
+    tvh_context_types_register();
+    tvh_context_helpers_register();
+#if ENABLE_HWACCELS
+    hwaccels_init();
+#endif
+}
+
+void
+transcode_done()
+{
+#if ENABLE_HWACCELS
+    hwaccels_done();
+#endif
+    tvh_context_helpers_forget();
+    tvh_context_types_forget();
+}
diff --git a/src/transcoding/transcode/stream.c b/src/transcoding/transcode/stream.c
new file mode 100644 (file)
index 0000000..7afcdbe
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+ *  tvheadend - Transcoding
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "internals.h"
+
+
+/* TVHStream ================================================================ */
+
+static int
+tvh_stream_is_copy(TVHCodecProfile *profile, tvh_ssc_t *ssc)
+{
+    if (profile == tvh_codec_profile_copy) {
+        return 1;
+    }
+    return tvh_codec_profile_is_copy(profile, ssc);
+}
+
+
+static int
+tvh_stream_setup(TVHStream *self, TVHCodecProfile *profile, tvh_ssc_t *ssc)
+{
+    enum AVCodecID icodec_id = streaming_component_type2codec_id(ssc->ssc_type);
+    AVCodec *icodec = NULL, *ocodec = NULL;
+
+    if (icodec_id == AV_CODEC_ID_NONE) {
+        tvh_stream_log(self, LOG_ERR, "unknown decoder id for '%s'",
+                       streaming_component_type2txt(ssc->ssc_type));
+        return -1;
+    }
+    if (!(icodec = avcodec_find_decoder(icodec_id))) {
+        tvh_stream_log(self, LOG_ERR, "failed to find decoder for '%s'",
+                       streaming_component_type2txt(ssc->ssc_type));
+        return -1;
+    }
+    if (!(ocodec = tvh_codec_profile_get_avcodec(profile))) {
+        tvh_stream_log(self, LOG_ERR, "profile '%s' is disabled",
+                       tvh_codec_profile_get_name(profile));
+        return -1;
+    }
+    if (icodec->type == AVMEDIA_TYPE_UNKNOWN ||
+        icodec->type != ocodec->type) { // is this even possible?
+        tvh_stream_log(self, LOG_ERR, "unknown or mismatch media type");
+        return -1;
+    }
+
+    if (!(self->context = tvh_context_create(self, profile,
+                                             icodec, ocodec, ssc->ssc_gh))) {
+        return -1;
+    }
+    self->type = ssc->ssc_type = codec_id2streaming_component_type(ocodec->id);
+    ssc->ssc_gh = NULL;
+    return 0;
+}
+
+
+/* exposed */
+
+void
+tvh_stream_stop(TVHStream *self, int flush)
+{
+    if (self->index >= 0) {
+        if (self->context) {
+            tvh_context_close(self->context, flush);
+        }
+        self->index = -1;
+    }
+}
+
+
+int
+tvh_stream_handle(TVHStream *self, th_pkt_t *pkt)
+{
+    if (pkt->pkt_payload && self->context) {
+        return (tvh_context_handle(self->context, pkt) < 0) ? -1 : 0;
+    }
+    TVHPKT_INCREF(pkt);
+    return tvh_transcoder_deliver(self->transcoder, pkt);
+}
+
+
+int
+tvh_stream_deliver(TVHStream *self, th_pkt_t *pkt)
+{
+    pkt->pkt_componentindex = self->index;
+    return tvh_transcoder_deliver(self->transcoder, pkt);
+}
+
+
+TVHStream *
+tvh_stream_create(TVHTranscoder *transcoder, TVHCodecProfile *profile,
+                  tvh_ssc_t *ssc)
+{
+    TVHStream *self = NULL;
+    int is_copy = -1;
+
+    if (!(self = calloc(1, sizeof(TVHStream)))) {
+        tvh_ssc_log(ssc, LOG_ERR, "failed to allocate stream", transcoder);
+        return NULL;
+    }
+    self->transcoder = transcoder;
+    self->id = self->index = ssc->ssc_index;
+    self->type = ssc->ssc_type;
+    if ((is_copy = tvh_stream_is_copy(profile, ssc)) > 0) {
+        if (ssc->ssc_gh) {
+            pktbuf_ref_inc(ssc->ssc_gh);
+        }
+        tvh_stream_log(self, LOG_INFO, "==> Passthrough");
+    }
+    else if (is_copy < 0 || tvh_stream_setup(self, profile, ssc)) {
+        tvh_stream_destroy(self);
+        return NULL;
+    }
+    return self;
+}
+
+
+void
+tvh_stream_destroy(TVHStream *self)
+{
+    if (self) {
+        tvh_stream_stop(self, 0);
+        if (self->context) {
+            tvh_context_destroy(self->context);
+            self->context = NULL;
+        }
+        self->transcoder = NULL;
+        free(self);
+        self = NULL;
+    }
+}
diff --git a/src/transcoding/transcode/transcoder.c b/src/transcoding/transcode/transcoder.c
new file mode 100644 (file)
index 0000000..e720eb0
--- /dev/null
@@ -0,0 +1,278 @@
+/*
+ *  tvheadend - Transcoding
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "internals.h"
+
+
+static TVHCodecProfile _codec_profile_copy = { .name = "copy" };
+TVHCodecProfile *tvh_codec_profile_copy = &_codec_profile_copy;
+
+
+/* utils ==================================================================== */
+
+static enum AVMediaType
+ssc_get_media_type(tvh_ssc_t *ssc)
+{
+    if (SCT_ISVIDEO(ssc->ssc_type)) {
+        return AVMEDIA_TYPE_VIDEO;
+    }
+    if (SCT_ISAUDIO(ssc->ssc_type)) {
+        return AVMEDIA_TYPE_AUDIO;
+    }
+    if (SCT_ISSUBTITLE(ssc->ssc_type)) {
+        return AVMEDIA_TYPE_SUBTITLE;
+    }
+    return AVMEDIA_TYPE_UNKNOWN;
+}
+
+
+static TVHCodecProfile *
+ssc_is_stream(tvh_ssc_t *ssc, TVHCodecProfile **profiles)
+{
+    enum AVMediaType media_type = AVMEDIA_TYPE_UNKNOWN;
+
+    if (!ssc->ssc_disabled &&
+        (media_type = ssc_get_media_type(ssc)) != AVMEDIA_TYPE_UNKNOWN) {
+        return profiles[media_type];
+    }
+    return NULL;
+}
+
+
+static int
+stream_count(tvh_ss_t *ss, TVHCodecProfile **profiles)
+{
+    int i, count = 0;
+    tvh_ssc_t *ssc = NULL;
+
+    for (i = 0; i < ss->ss_num_components; i++) {
+        if ((ssc = &ss->ss_components[i]) && ssc_is_stream(ssc, profiles)) {
+            count++;
+        }
+    }
+    return count;
+}
+
+
+static TVHCodecProfile *
+_find_profile(const char *name)
+{
+    if (!strcmp(name, "copy")) {
+        return tvh_codec_profile_copy;
+    }
+    return codec_find_profile(name);
+}
+
+
+/* TVHTranscoder ============================================================ */
+
+static void
+tvh_transcoder_handle(TVHTranscoder *self, th_pkt_t *pkt)
+{
+    TVHStream *stream = NULL;
+
+    SLIST_FOREACH(stream, &self->streams, link) {
+        if (pkt->pkt_componentindex == stream->index) {
+            if (tvh_stream_handle(stream, pkt)) {
+                tvh_stream_stop(stream, 0);
+            }
+            break;
+        }
+    }
+}
+
+
+static tvh_ss_t *
+tvh_transcoder_start(TVHTranscoder *self, tvh_ss_t *ss_src)
+{
+    // TODO: rewrite, include language selection
+    tvh_ss_t *ss = NULL;
+    tvh_ssc_t *ssc_src = NULL, *ssc = NULL;
+    TVHCodecProfile *profile = NULL;
+    TVHStream *stream = NULL;
+    int i, j, count = stream_count(ss_src, self->profiles);
+
+    ss = calloc(1, (sizeof(tvh_ss_t) + (sizeof(tvh_ssc_t) * count)));
+    if (ss) {
+        ss->ss_refcount = 1;
+        ss->ss_pcr_pid = ss_src->ss_pcr_pid;
+        ss->ss_pmt_pid = ss_src->ss_pmt_pid;
+        service_source_info_copy(&ss->ss_si, &ss_src->ss_si);
+        ss->ss_num_components = count;
+        for (i = j = 0; i < ss_src->ss_num_components && j < count; i++) {
+            ssc_src = &ss_src->ss_components[i];
+            ssc = &ss->ss_components[j];
+            if (ssc) {
+                if ((profile = ssc_is_stream(ssc_src, self->profiles))) {
+                    *ssc = *ssc_src;
+                    j++;
+                    if ((stream = tvh_stream_create(self, profile, ssc))) {
+                        SLIST_INSERT_HEAD(&self->streams, stream, link);
+                    }
+                    else {
+                        tvh_ssc_log(ssc, LOG_ERR, "==> Filtered out", self);
+                    }
+                }
+                else {
+                    tvh_ssc_log(ssc, LOG_INFO, "==> Filtered out", self);
+                }
+            }
+        }
+    }
+    return ss;
+}
+
+
+static void
+tvh_transcoder_stop(TVHTranscoder *self, int flush)
+{
+    TVHStream *stream = NULL;
+
+    SLIST_FOREACH(stream, &self->streams, link) {
+        tvh_stream_stop(stream, flush);
+    }
+}
+
+
+static void
+tvh_transcoder_stream(void *opaque, tvh_sm_t *msg)
+{
+    TVHTranscoder *self = opaque;
+    tvh_ss_t *ss = NULL;
+
+    switch (msg->sm_type) {
+        case SMT_PACKET:
+            if(msg->sm_data) {
+                tvh_transcoder_handle(self, msg->sm_data);
+                TVHPKT_CLEAR(msg->sm_data);
+            }
+            streaming_msg_free(msg);
+            break;
+        case SMT_START:
+            if (msg->sm_data) {
+                ss = tvh_transcoder_start(self, msg->sm_data);
+                streaming_start_unref(msg->sm_data);
+                msg->sm_data = ss;
+            }
+            streaming_target_deliver2(self->output, msg);
+            break;
+        case SMT_STOP:
+            tvh_transcoder_stop(self, 1);
+            /* !!! FALLTHROUGH !!! */
+        default:
+            streaming_target_deliver2(self->output, msg);
+            break;
+    }
+}
+
+
+static int
+tvh_transcoder_setup(TVHTranscoder *self, const char **profiles)
+{
+    const char *profile = NULL;
+    int i;
+
+    for (i = 0; i < AVMEDIA_TYPE_NB; i++) {
+        if ((profile = profiles[i]) && strlen(profile)) {
+            if (!(self->profiles[i] = _find_profile(profile))) {
+                tvh_transcoder_log(self, LOG_ERR,
+                                   "failed to find codec profile: '%s'", profile);
+                return -1;
+            }
+        }
+    }
+    return 0;
+}
+
+
+static htsmsg_t *
+tvh_transcoder_info(void *opaque, htsmsg_t *list)
+{
+  TVHTranscoder *self = opaque;
+  streaming_target_t *st = self->output;
+  htsmsg_add_str(list, NULL, "transcoder input");
+  return st->st_ops.st_info(st->st_opaque, list);
+}
+
+
+/* exposed */
+
+int
+tvh_transcoder_deliver(TVHTranscoder *self, th_pkt_t *pkt)
+{
+    tvh_sm_t *msg = NULL;
+
+    if (!(msg = msg_create(pkt))) { // takes ownership of pkt
+        tvh_transcoder_log(self, LOG_ERR, "failed to create message");
+        return -1;
+    }
+    streaming_target_deliver2(self->output, msg);
+    return 0;
+}
+
+
+static streaming_ops_t tvh_transcoder_ops = {
+  .st_cb   = tvh_transcoder_stream,
+  .st_info = tvh_transcoder_info
+};
+
+
+TVHTranscoder *
+tvh_transcoder_create(tvh_st_t *output, const char **profiles)
+{
+    static uint32_t id = 0;
+    TVHTranscoder *self = NULL;
+
+    if (!(self = calloc(1, sizeof(TVHTranscoder)))) {
+        tvherror(LS_TRANSCODE, "failed to allocate transcoder");
+        return NULL;
+    }
+    SLIST_INIT(&self->streams);
+    self->id = ++id;
+    if (!self->id) {
+        self->id = ++id;
+    }
+    self->output = output;
+    if (tvh_transcoder_setup(self, profiles)) {
+        tvh_transcoder_destroy(self);
+        return NULL;
+    }
+    streaming_target_init(&self->input, &tvh_transcoder_ops, self, 0);
+    return self;
+}
+
+
+void
+tvh_transcoder_destroy(TVHTranscoder *self)
+{
+    TVHStream *stream = NULL;
+
+    if (self) {
+        tvh_transcoder_stop(self, 0);
+        self->output = NULL;
+        while (!SLIST_EMPTY(&self->streams)) {
+            stream = SLIST_FIRST(&self->streams);
+            SLIST_REMOVE_HEAD(&self->streams, link);
+            tvh_stream_destroy(stream);
+        }
+        free(self);
+        self = NULL;
+    }
+}
diff --git a/src/transcoding/transcode/video.c b/src/transcoding/transcode/video.c
new file mode 100644 (file)
index 0000000..4245feb
--- /dev/null
@@ -0,0 +1,296 @@
+/*
+ *  tvheadend - Transcoding
+ *
+ *  Copyright (C) 2016 Tvheadend
+ *
+ *  This program is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "internals.h"
+
+#if ENABLE_HWACCELS
+#include "hwaccels/hwaccels.h"
+#endif
+
+
+static int
+_video_filters_hw_pix_fmt(enum AVPixelFormat pix_fmt)
+{
+    const AVPixFmtDescriptor *desc;
+
+    if ((desc = av_pix_fmt_desc_get(pix_fmt)) &&
+        (desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) {
+        return 1;
+    }
+    return 0;
+}
+
+
+static int
+_video_filters_get_filters(TVHContext *self, AVDictionary **opts, char **filters)
+{
+    static char download[48];
+    static char deint[8];
+    static char scale[24];
+    static char upload[48];
+    int ihw = _video_filters_hw_pix_fmt(self->iavctx->pix_fmt);
+    int ohw = _video_filters_hw_pix_fmt(self->oavctx->pix_fmt);
+    int filter_scale = (self->iavctx->height != self->oavctx->height);
+    int filter_deint = 0, filter_download = 0, filter_upload = 0;
+
+    if (tvh_context_get_int_opt(opts, "tvh_filter_deint", &filter_deint)) {
+        return -1;
+    }
+    filter_download = (ihw && (!ohw || filter_scale || filter_deint)) ? 1 : 0;
+    filter_upload = ((filter_download || !ihw) && ohw) ? 1 : 0;
+
+    memset(download, 0, sizeof(download));
+    if (filter_download &&
+        str_snprintf(download, sizeof(download), "hwdownload,format=pix_fmts=%s",
+                     av_get_pix_fmt_name(self->iavctx->sw_pix_fmt))) {
+        return -1;
+    }
+
+    memset(deint, 0, sizeof(deint));
+    if (filter_deint && str_snprintf(deint, sizeof(deint), "yadif")) {
+        return -1;
+    }
+
+    memset(scale, 0, sizeof(scale));
+    if (filter_scale &&
+        str_snprintf(scale, sizeof(scale), "scale=w=-2:h=%d",
+                     self->oavctx->height)) {
+        return -1;
+    }
+
+    memset(upload, 0, sizeof(upload));
+    if (filter_upload &&
+        str_snprintf(upload, sizeof(upload), "format=pix_fmts=%s|%s,hwupload",
+                     av_get_pix_fmt_name(self->oavctx->sw_pix_fmt),
+                     av_get_pix_fmt_name(self->oavctx->pix_fmt))) {
+        return -1;
+    }
+
+    if (!(*filters = str_join(",", download, deint, scale, upload, NULL))) {
+        return -1;
+    }
+
+    return 0;
+}
+
+
+static int
+tvh_video_context_open_decoder(TVHContext *self, AVDictionary **opts)
+{
+#if ENABLE_HWACCELS
+    int hwaccel = -1;
+    if ((hwaccel = tvh_codec_profile_video_get_hwaccel(self->profile)) < 0) {
+        return -1;
+    }
+    if (hwaccel) {
+        self->iavctx->get_format = hwaccels_decode_get_format;
+    }
+#endif
+    return 0;
+}
+
+
+static int
+tvh_video_context_notify_gh(TVHContext *self)
+{
+    /* notify global headers that we're live */
+    /* the video packets might be delayed */
+    th_pkt_t *pkt = NULL;
+
+    pkt = pkt_create(NULL, 0, self->src_pkt->pkt_pts, self->src_pkt->pkt_dts);
+    if (pkt) {
+        return tvh_context_deliver(self, pkt);
+    }
+    return -1;
+}
+
+
+static int
+tvh_video_context_open_encoder(TVHContext *self, AVDictionary **opts)
+{
+    AVRational ticks_per_frame;
+
+    if (tvh_context_get_int_opt(opts, "pix_fmt", &self->oavctx->pix_fmt) ||
+        tvh_context_get_int_opt(opts, "width", &self->oavctx->width) ||
+        tvh_context_get_int_opt(opts, "height", &self->oavctx->height)) {
+        return -1;
+    }
+
+#if ENABLE_HWACCELS
+    self->oavctx->coded_width = self->oavctx->width;
+    self->oavctx->coded_height = self->oavctx->height;
+    if (hwaccels_encode_setup_context(self->oavctx)) {
+        return -1;
+    }
+#endif
+
+    // XXX: is this a safe assumption?
+    if (!self->iavctx->framerate.num) {
+        self->iavctx->framerate = av_make_q(30, 1);
+    }
+    self->oavctx->framerate = self->iavctx->framerate;
+    self->oavctx->ticks_per_frame = self->iavctx->ticks_per_frame;
+    ticks_per_frame = av_make_q(self->oavctx->ticks_per_frame, 1);
+    self->oavctx->time_base = av_inv_q(av_mul_q(
+        self->oavctx->framerate, ticks_per_frame));
+    self->oavctx->gop_size = ceil(av_q2d(av_inv_q(av_mul_q(
+        self->oavctx->time_base, ticks_per_frame))));
+    self->oavctx->gop_size *= 3;
+    self->oavctx->max_b_frames = 3;
+    return 0;
+}
+
+
+static int
+tvh_video_context_open_filters(TVHContext *self, AVDictionary **opts)
+{
+    static char source_args[128];
+    char *filters = NULL;
+
+    // source args
+    memset(source_args, 0, sizeof(source_args));
+    if (str_snprintf(source_args, sizeof(source_args),
+            "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
+            self->iavctx->width,
+            self->iavctx->height,
+            self->iavctx->pix_fmt,
+            self->iavctx->time_base.num,
+            self->iavctx->time_base.den,
+            self->iavctx->sample_aspect_ratio.num,
+            self->iavctx->sample_aspect_ratio.den)) {
+        return -1;
+    }
+
+    // filters
+    if (_video_filters_get_filters(self, opts, &filters)) {
+        return -1;
+    }
+
+    int ret = tvh_context_open_filters(self,
+        "buffer", source_args,              // source
+        strlen(filters) ? filters : "null", // filters
+        "buffersink",                       // sink
+        "pix_fmts", &self->oavctx->pix_fmt, // sink option: pix_fmt
+        sizeof(self->oavctx->pix_fmt),
+        NULL);                              // _IMPORTANT!_
+    str_clear(filters);
+    return ret;
+}
+
+
+static int
+tvh_video_context_open(TVHContext *self, TVHOpenPhase phase, AVDictionary **opts)
+{
+    switch (phase) {
+        case OPEN_DECODER:
+            return tvh_video_context_open_decoder(self, opts);
+        case OPEN_DECODER_POST:
+            return tvh_video_context_notify_gh(self);
+        case OPEN_ENCODER:
+            return tvh_video_context_open_encoder(self, opts);
+        case OPEN_ENCODER_POST:
+            return tvh_video_context_open_filters(self, opts);
+        default:
+            break;
+    }
+    return 0;
+}
+
+
+static int
+tvh_video_context_encode(TVHContext *self, AVFrame *avframe)
+{
+    avframe->pts = av_frame_get_best_effort_timestamp(avframe);
+    /*tvhinfo(LS_TRANSCODE, "encode avframe: pts: %"PRId64 ", pkt_pts: %"PRId64 ", pkt_dts: %"PRId64,
+            avframe->pts, avframe->pkt_pts, avframe->pkt_dts);*/
+    return 0;
+}
+
+
+static int
+tvh_video_context_ship(TVHContext *self, AVPacket *avpkt)
+{
+    /*tvhinfo(LS_TRANSCODE, "ship avpkt: pts: %"PRId64 ", dts: %"PRId64,
+            avpkt->pts, avpkt->dts);*/
+    if (avpkt->size < 0 || avpkt->pts < avpkt->dts) {
+        tvh_context_log(self, LOG_ERR, "encode failed");
+        return -1;
+    }
+    return avpkt->size;
+}
+
+
+static int
+tvh_video_context_wrap(TVHContext *self, AVPacket *avpkt, th_pkt_t *pkt)
+{
+    uint8_t *qsdata = NULL;
+    int qsdata_size = 0;
+    enum AVPictureType pict_type = AV_PICTURE_TYPE_NONE;
+
+    qsdata = av_packet_get_side_data(avpkt, AV_PKT_DATA_QUALITY_STATS,
+                                     &qsdata_size);
+    if (qsdata && qsdata_size >= 5) {
+        pict_type = qsdata[4];
+    }
+#if FF_API_CODED_FRAME
+    else if (self->oavctx->coded_frame) {
+        pict_type = self->oavctx->coded_frame->pict_type;
+    }
+#endif
+    switch (pict_type) {
+        case AV_PICTURE_TYPE_I:
+            pkt->v.pkt_frametype = PKT_I_FRAME;
+            break;
+        case AV_PICTURE_TYPE_P:
+            pkt->v.pkt_frametype = PKT_P_FRAME;
+            break;
+        case AV_PICTURE_TYPE_B:
+            pkt->v.pkt_frametype = PKT_B_FRAME;
+            break;
+        default:
+            break;
+    }
+    pkt->pkt_duration   = avpkt->duration;
+    pkt->pkt_commercial = self->src_pkt->pkt_commercial;
+    pkt->v.pkt_field      = self->src_pkt->v.pkt_field;
+    pkt->v.pkt_aspect_num = self->src_pkt->v.pkt_aspect_num;
+    pkt->v.pkt_aspect_den = self->src_pkt->v.pkt_aspect_den;
+    return 0;
+}
+
+
+static void
+tvh_video_context_close(TVHContext *self)
+{
+#if ENABLE_HWACCELS
+    hwaccels_encode_close_context(self->oavctx);
+    hwaccels_decode_close_context(self->iavctx);
+#endif
+}
+
+
+TVHContextType TVHVideoContext = {
+    .media_type = AVMEDIA_TYPE_VIDEO,
+    .open       = tvh_video_context_open,
+    .encode     = tvh_video_context_encode,
+    .ship       = tvh_video_context_ship,
+    .wrap       = tvh_video_context_wrap,
+    .close      = tvh_video_context_close,
+};
index dcaf6b24aa3f5b3b3f4e217bc92b72a19fe6d712..673dd1ea492bc8549739874160729f4dd1889f54 100644 (file)
@@ -303,17 +303,21 @@ typedef enum {
   SCT_HEVC,
   SCT_VP9,
   SCT_HBBTV,
-  SCT_LAST = SCT_HBBTV
+  SCT_THEORA,
+  SCT_OPUS,
+  SCT_LAST = SCT_OPUS
 } streaming_component_type_t;
 
 #define SCT_MASK(t) (1 << (t))
 
-#define SCT_ISVIDEO(t) ((t) == SCT_MPEG2VIDEO || (t) == SCT_H264 ||    \
-                       (t) == SCT_VP8 || (t) == SCT_HEVC || (t) == SCT_VP9)
+#define SCT_ISVIDEO(t) ((t) == SCT_MPEG2VIDEO || (t) == SCT_H264 || \
+                       (t) == SCT_VP8 || (t) == SCT_HEVC || \
+                       (t) == SCT_VP9 || (t) == SCT_THEORA)
 
 #define SCT_ISAUDIO(t) ((t) == SCT_MPEG2AUDIO || (t) == SCT_AC3 || \
-                        (t) == SCT_AAC  || (t) == SCT_MP4A ||     \
-                       (t) == SCT_EAC3 || (t) == SCT_VORBIS)
+                       (t) == SCT_AAC  || (t) == SCT_MP4A || \
+                       (t) == SCT_EAC3 || (t) == SCT_VORBIS || \
+                       (t) == SCT_OPUS)
 
 #define SCT_ISAV(t) (SCT_ISVIDEO(t) || SCT_ISAUDIO(t))
 
@@ -423,7 +427,7 @@ typedef enum {
   /**
    * Packet with data.
    *
-   * sm_data points to a th_pkt. th_pkt will be unref'ed when 
+   * sm_data points to a th_pkt. th_pkt will be unref'ed when
    * the message is destroyed
    */
   SMT_PACKET,
@@ -471,7 +475,7 @@ typedef enum {
    *
    * End of streaming. If sm_code is 0 this was a result to an
    * unsubscription. Otherwise the reason was external and the
-   * subscription scheduler will attempt to start a new streaming 
+   * subscription scheduler will attempt to start a new streaming
    * session.
    */
   SMT_STOP,
@@ -616,7 +620,7 @@ typedef struct streaming_target {
  *
  */
 typedef struct streaming_queue {
-  
+
   streaming_target_t sq_st;
 
   pthread_mutex_t sq_mutex;    /* Protects sp_queue */
@@ -624,7 +628,7 @@ typedef struct streaming_queue {
 
   size_t          sq_maxsize;  /* Max queue size (bytes) */
   size_t          sq_size;     /* Actual queue size (bytes) - only data */
-  
+
   struct streaming_message_queue sq_queue;
 
 } streaming_queue_t;
@@ -855,7 +859,7 @@ void sha1_calc(uint8_t *dst, const uint8_t *d1, size_t d1_len, const uint8_t *d2
 uint32_t gcdU32(uint32_t a, uint32_t b);
 static inline int32_t deltaI32(int32_t a, int32_t b) { return (a > b) ? (a - b) : (b - a); }
 static inline uint32_t deltaU32(uint32_t a, uint32_t b) { return (a > b) ? (a - b) : (b - a); }
-  
+
 #define SKEL_DECLARE(name, type) type *name;
 #define SKEL_ALLOC(name) do { if (!name) name = calloc(1, sizeof(*name)); } while (0)
 #define SKEL_USED(name) do { name = NULL; } while (0)
@@ -889,4 +893,8 @@ void tvh_qsort_r(void *base, size_t nmemb, size_t size, int (*compar)(const void
 #define PRItime_t       "ld"
 #endif
 
+/* transcoding */
+#define TVH_NAME_LEN 32
+#define TVH_TITLE_LEN 256
+
 #endif /* TVHEADEND_H */
index f483bbff2bfa8072c5301e4bbe34eafae557551d..f21f67593708cf2b3c9833a05a0d90c653d79869 100644 (file)
@@ -118,7 +118,7 @@ tvhlog_subsys_t tvhlog_subsystems[] = {
   [LS_PCR]           = { "pcr",           N_("PCR Clocks") },
   [LS_PARSER]        = { "parser",        N_("MPEG-TS Parser") },
   [LS_TS]            = { "TS",            N_("Transport Stream") },
-  [LS_GLOBALHEADERS] = { "globalheaders", N_("Global Headers") }, 
+  [LS_GLOBALHEADERS] = { "globalheaders", N_("Global Headers") },
   [LS_TSFIX]         = { "tsfix",         N_("Time Stamp Fix") },
   [LS_HEVC]          = { "hevc",          N_("HEVC - H.265") },
   [LS_MUXER]         = { "muxer",         N_("Muxer") },
@@ -159,7 +159,7 @@ tvhlog_subsys_t tvhlog_subsystems[] = {
   [LS_EN50494]       = { "en50494",       N_("Unicable (EN50494)") },
   [LS_SATIP]         = { "satip",         N_("SAT>IP Client") },
   [LS_SATIPS]        = { "satips",        N_("SAT>IP Server") },
-  [LS_TVHDHOMERUN]   = { "tvhdhomerun",   N_("TVHDHomeRun Client") }, 
+  [LS_TVHDHOMERUN]   = { "tvhdhomerun",   N_("TVHDHomeRun Client") },
   [LS_PSIP]          = { "psip",          N_("ATSC PSIP EPG") },
   [LS_OPENTV]        = { "opentv",        N_("OpenTV EPG") },
   [LS_PYEPG]         = { "pyepg",         N_("PyEPG Import") },
@@ -169,6 +169,8 @@ tvhlog_subsys_t tvhlog_subsystems[] = {
   [LS_SCANFILE]      = { "scanfile",      N_("Scanfile") },
   [LS_TSFILE]        = { "tsfile",        N_("MPEG-TS File") },
   [LS_TSDEBUG]       = { "tsdebug",       N_("MPEG-TS Input Debug") },
+  [LS_CODEC]         = { "codec",         N_("Codec") },
+  [LS_VAAPI]         = { "vaapi",         N_("VA-API") },
 };
 
 static void
@@ -293,7 +295,7 @@ tvhlog_process
       const char *ltxt = logtxtmeta[msg->severity][0];
       const char *sgr  = logtxtmeta[msg->severity][1];
       const char *sgroff;
-    
+
       if (options & TVHLOG_OPT_DECORATE)
         sgroff = "\033[0m";
       else {
@@ -314,7 +316,7 @@ tvhlog_process
         fprintf(*fp, "%s [%7s]:%s\n", t, ltxt, msg->msg);
     }
   }
-  
+
   free(msg->msg);
   free(msg);
 }
@@ -355,7 +357,7 @@ tvhlog_thread ( void *p )
         path = NULL;
       }
     }
-    options  = tvhlog_options; 
+    options  = tvhlog_options;
     pthread_mutex_unlock(&tvhlog_mutex);
     tvhlog_process(msg, options, &fp, path);
     pthread_mutex_lock(&tvhlog_mutex);
@@ -519,7 +521,7 @@ tvhlog_backtrace_printf(const char *fmt, ...)
 /*
  * Initialise
  */
-void 
+void
 tvhlog_init ( int level, int options, const char *path )
 {
   tvhlog_level   = level;
index 53c2c985211d2f83050daf1f4eafef64c2d34f75..39774c6ec2a28d723c6c051b2ff0b98ad2007347 100644 (file)
@@ -51,7 +51,7 @@ extern pthread_mutex_t  tvhlog_mutex;
 extern tvhlog_subsys_t  tvhlog_subsystems[];
 
 /* Initialise */
-void tvhlog_init       ( int level, int options, const char *path ); 
+void tvhlog_init       ( int level, int options, const char *path );
 void tvhlog_start      ( void );
 void tvhlog_end        ( void );
 void tvhlog_set_debug  ( const char *subsys );
@@ -193,6 +193,8 @@ enum {
   LS_SCANFILE,
   LS_TSFILE,
   LS_TSDEBUG,
+  LS_CODEC,
+  LS_VAAPI,
   LS_LAST     /* keep this last */
 };
 
diff --git a/src/webui/static/app/codec.js b/src/webui/static/app/codec.js
new file mode 100644 (file)
index 0000000..dff82db
--- /dev/null
@@ -0,0 +1,291 @@
+/*
+ * Codec Profiles
+ */
+
+
+function genericCBRvsVBR(form) {
+    function updateBitrate(cbr_f, vbr_f) {
+        if (vbr_f.getValue() > 0) {
+            cbr_f.setValue(0);
+            cbr_f.setReadOnly(true);
+        }
+        else {
+            cbr_f.setReadOnly(false);
+        }
+    }
+
+    var cbr_field = form.findField('bit_rate');
+    var vbr_field = form.findField('qp') || form.findField('crf') || form.findField('qscale');
+    vbr_field.spinner.editable = false;
+    updateBitrate(cbr_field, vbr_field);
+
+    vbr_field.on('spin', function(spinner) {
+        updateBitrate(cbr_field, spinner.field);
+    });
+}
+
+
+var codec_profile_forms = {
+    'default': function(form) {
+        var name_field = form.findField('name');
+        name_field.maxLength = 31; // TVH_NAME_LEN -1
+    },
+
+    'codec_profile_mpeg2video': genericCBRvsVBR,
+
+    'codec_profile_mp2': function(form) {
+        function makeData(list) {
+            var data = [{'key': list[0],  'val': 'auto'}];
+            for (var i = 1; i < list.length; i++) {
+                var val = list[i];
+                data.push({'key': val, 'val': val.toString()});
+            }
+            return data;
+        }
+
+        // see avpriv_mpa_bitrate_tab and avpriv_mpa_freq_tab
+        // in ffmpeg-3.0.2/libavcodec/mpegaudiodata.c
+        var hi_freqs = [0, 44100, 48000, 32000];
+        var hi_rates = makeData([0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384]);
+        var lo_rates = makeData([0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160]);
+
+        function updateBitrate(samplerate_f, bitrate_f) {
+            var bitrates = hi_rates;
+            if (hi_freqs.indexOf(samplerate_f.getValue()) === -1) {
+                bitrates = lo_rates;
+            }
+            bitrate_f.store.loadData(bitrates);
+        }
+
+        var samplerate_field = form.findField('sample_rate');
+        samplerate_field.forceSelection = true;
+        var bitrate_field = form.findField('bit_rate');
+        bitrate_field.forceSelection = true;
+        updateBitrate(samplerate_field, bitrate_field);
+
+        samplerate_field.on('select', function(combo, record, index) {
+            updateBitrate(combo, bitrate_field);
+            if (bitrate_field.store.data.keys.indexOf(bitrate_field.getValue()) === -1) {
+                bitrate_field.setValue(0);
+            }
+        });
+    },
+
+    'codec_profile_aac': function(form) {
+        /*
+            name                           | value | nb. channels
+            -----------------------------------------------------
+            AV_CH_LAYOUT_MONO              | 4     | 1
+            AV_CH_LAYOUT_STEREO            | 3     | 2
+            AV_CH_LAYOUT_SURROUND          | 7     | 3
+            AV_CH_LAYOUT_4POINT0           | 263   | 4
+            AV_CH_LAYOUT_5POINT0_BACK      | 55    | 5
+            AV_CH_LAYOUT_5POINT1_BACK      | 63    | 6
+            AV_CH_LAYOUT_7POINT1_WIDE_BACK | 255   | 8
+        */
+        var channels = [0, 4, 3, 7, 263, 55, 63, -1, 255]
+
+        function updateBitrate(bitrate_f, quality_f, samplerate_f, layout_f) {
+            if (quality_f.getValue() > 0) {
+                bitrate_f.setValue(0);
+                bitrate_f.setReadOnly(true);
+            }
+            else {
+                var samplerate = samplerate_f.getValue() || 48000;
+                var layout = layout_f.getValue() || 3; // AV_CH_LAYOUT_STEREO
+                var max_bitrate = (6 * samplerate * channels.indexOf(layout)) / 1000;
+                bitrate_f.setMaxValue(max_bitrate);
+                if (bitrate_f.getValue() > max_bitrate) {
+                    bitrate_f.setValue(max_bitrate);
+                }
+                bitrate_f.setReadOnly(false);
+            }
+        }
+
+        var bitrate_field = form.findField('bit_rate');
+        var quality_field = form.findField('qscale');
+        quality_field.minValue = 0;
+        quality_field.maxValue = 2.0;
+        quality_field.spinner.incrementValue = 0.1;
+        quality_field.spinner.editable = false;
+        var samplerate_field = form.findField('sample_rate');
+        samplerate_field.forceSelection = true;
+        var layout_field = form.findField('channel_layout');
+        layout_field.forceSelection = true;
+        updateBitrate(bitrate_field, quality_field, samplerate_field, layout_field);
+
+        quality_field.on('spin', function(spinner) {
+            updateBitrate(bitrate_field, spinner.field, samplerate_field, layout_field);
+        });
+
+        samplerate_field.on('select', function(combo, record, index) {
+            updateBitrate(bitrate_field, quality_field, combo, layout_field);
+        });
+
+        layout_field.on('select', function(combo, record, index) {
+            updateBitrate(bitrate_field, quality_field, samplerate_field, combo);
+        });
+    },
+
+    'codec_profile_libx264': genericCBRvsVBR,
+
+    'codec_profile_libx265': genericCBRvsVBR,
+
+    'codec_profile_libvpx': function(form) {
+        function updateSpeed(quality_f, speed_f) {
+            if (quality_f.getValue() == 1) { // VPX_DL_REALTIME
+                speed_f.setMaxValue(15);
+            }
+            else {
+                speed_f.setMaxValue(5);
+            }
+        }
+
+        genericCBRvsVBR(form);
+        var quality_field = form.findField('deadline');
+        var speed_field = form.findField('cpu-used');
+        updateSpeed(quality_field, speed_field);
+
+        quality_field.on('select', function(combo, record, index) {
+            updateSpeed(combo, speed_field);
+            if (speed_field.getValue() > speed_field.maxValue) {
+                speed_field.setValue(speed_field.maxValue);
+            }
+        });
+    },
+
+    'codec_profile_libtheora': genericCBRvsVBR,
+
+    'codec_profile_libvorbis': genericCBRvsVBR,
+
+    'codec_profile_libfdk_aac': function(form) {
+        // TODO: max bitrate
+        // max vbr = 5 for FF_PROFILE_UNKNOWN(-99) and FF_PROFILE_AAC_LOW(1)
+        // max vbr = 3 for FF_PROFILE_AAC_HE(4) and FF_PROFILE_AAC_HE_V2(28)
+        // vbr disabled for FF_PROFILE_AAC_LD(22) and FF_PROFILE_AAC_ELD(38)
+        var max_vbr = [null, null, null, [4, 28], null, [-99, 1]];
+        function updateVBR(profile_f, cbr_f, vbr_f) {
+            var profile = profile_f.getValue();
+            for (var i = 0; i < max_vbr.length; i++) {
+                var profiles = max_vbr[i];
+                if (profiles && profiles.indexOf(profile) !== -1) {
+                    vbr_f.setMaxValue(i);
+                    if (vbr_f.getValue() > i) {
+                        vbr_f.setValue(i);
+                    }
+                    vbr_f.spinner.setReadOnly(false);
+                    if (vbr_f.getValue() > 0) {
+                        cbr_f.setValue(0);
+                        cbr_f.setReadOnly(true);
+                    }
+                    return;
+                }
+            }
+            vbr_f.setValue(0);
+            vbr_f.spinner.setReadOnly(true);
+            cbr_f.setReadOnly(false);
+        }
+
+        function updateSignaling(profile_f, eld_sbr_f, signaling_f) {
+            if (profile_f.getValue() === 38) { // FF_PROFILE_AAC_ELD
+                eld_sbr_f.setReadOnly(false);
+                signaling_f.setReadOnly(false);
+            }
+            else {
+                eld_sbr_f.setValue(0);
+                eld_sbr_f.setReadOnly(true);
+                signaling_f.setValue(-1);
+                signaling_f.setReadOnly(true);
+            }
+        }
+
+        function updateBitrate(cbr_f, vbr_f) {
+            if (vbr_f.getValue() > 0) {
+                cbr_f.setValue(0);
+                cbr_f.setReadOnly(true);
+            }
+            else {
+                cbr_f.setReadOnly(false);
+            }
+        }
+
+        var eld_sbr_field = form.findField('eld_sbr');
+        var signaling_field = form.findField('signaling');
+        var cbr_field = form.findField('bit_rate');
+        var vbr_field = form.findField('vbr');
+        vbr_field.spinner.editable = false;
+        var profile_field = form.findField('profile');
+        updateVBR(profile_field, cbr_field, vbr_field);
+        updateSignaling(profile_field, eld_sbr_field, signaling_field);
+        updateBitrate(cbr_field, vbr_field);
+
+        vbr_field.on('spin', function(spinner) {
+            updateBitrate(cbr_field, spinner.field);
+        });
+
+        profile_field.on('select', function(combo, record, index) {
+            updateVBR(combo, cbr_field, vbr_field);
+            updateSignaling(combo, eld_sbr_field, signaling_field);
+        });
+    },
+
+    'codec_profile_vaapi_h264': genericCBRvsVBR,
+
+    'codec_profile_vaapi_hevc': genericCBRvsVBR
+};
+
+
+tvheadend.codec_tab = function(panel)
+{
+    if (tvheadend.capabilities.indexOf('libav') !== -1) {
+        var actions = new Ext.ux.grid.RowActions({
+            id: 'status',
+            header: '',
+            width: 45,
+            actions: [ { iconIndex: 'status' } ],
+            destroy: function() {}
+        });
+
+        if (!tvheadend.codec_list) {
+            tvheadend.codec_list = new Ext.data.JsonStore({
+                url: 'api/codec/list',
+                root: 'entries',
+                fields: ['name', 'title'],
+                id: 'name',
+                autoLoad: true
+            });
+        }
+
+        tvheadend.idnode_form_grid(panel, {
+            url: 'api/codec_profile',
+            clazz: 'codec_profile',
+            comet: 'codec_profile',
+            titleS: _('Codec Profile'),
+            titleP: _('Codec Profiles'),
+            titleC: _('Codec Profile Name'),
+            iconCls: 'option',
+            key: 'uuid',
+            val: 'title',
+            fields: ['uuid', 'title', 'status'],
+            list: {url: 'api/codec_profile/list', params: {}},
+            lcol: [actions],
+            plugins: [actions],
+            add: {
+                url: 'api/codec_profile',
+                titleS: _('Codec Profile'),
+                select: {
+                    label: _('Codec'),
+                    store: tvheadend.codec_list,
+                    fullRecord: true,
+                    displayField: 'title',
+                    valueField: 'name',
+                    formField: 'codec_name',
+                    list: '-type,enabled'
+                },
+                create: {}
+            },
+            del: true,
+            forms: codec_profile_forms
+        });
+    }
+};
index 7113b746c60b6e73d38c606e241dd8b19c998ae0..d14551ad8f4abf9122508358e429f5e6c9423cf6 100644 (file)
@@ -1,46 +1,24 @@
 /*
- * Stream Profiles, Elementary Stream Filters
+ * Elementary Stream Filters
  */
 
 tvheadend.esfilter_tab = function(panel)
 {
-    if (!tvheadend.profile_builders) {
-        tvheadend.profile_builders = new Ext.data.JsonStore({
-            url: 'api/profile/builders',
-            root: 'entries',
-            fields: ['class', 'caption', 'order', 'groups', 'params'],
-            id: 'class',
-            autoLoad: true
+    if (tvheadend.uilevel_match('expert', tvheadend.uilevel)) {
+        var tab = new Ext.TabPanel({
+            activeTab: 0,
+            autoScroll: true,
+            title: _('Stream Filters'),
+            iconCls: 'otherFilters',
+            items: []
         });
+        tvheadend.esfilter_tabs(tab);
+        panel.add(tab);
     }
+};
 
-    var list = '-class';
-
-    tvheadend.idnode_form_grid(panel, {
-        url: 'api/profile',
-        clazz: 'profile',
-        comet: 'profile',
-        titleS: _('Stream Profile'),
-        titleP: _('Stream Profiles'),
-        titleC: _('Stream Profile Name'),
-        iconCls: 'film',
-        edit: { params: { list: list } },
-        add: {
-            url: 'api/profile',
-            titleS: _('Stream Profile'),
-            select: {
-                label: _('Type'),
-                store: tvheadend.profile_builders,
-                fullRecord: true,
-                displayField: 'caption',
-                valueField: 'class',
-                list: list
-            },
-            create: { }
-        },
-        del: true
-    });
-
+tvheadend.esfilter_tabs = function(panel)
+{
     var eslist = '-class,index';
 
     tvheadend.idnode_grid(panel, {
index 8749e8e97f2648ebfc6adaec378ba5d93fb6ce45..56eee796886cdf9c978012593daceb6640f29c6a 100644 (file)
@@ -2,9 +2,9 @@
  * Ext JS Library 2.1
  * Copyright(c) 2006-2008, Ext JS, LLC.
  * licensing@extjs.com
- * 
+ *
  * http://extjs.com/license
- */ 
+ */
 #header {
     font-family: tahoma,arial;
     background-color: #507AAA;
     background-image: url(../icons/exclamation.png) !important;
 }
 
+.codecDisabled {
+    background-image: url(../icons/plugin_disabled.png) !important;
+}
+
+.codecEnabled {
+    background-image: url(../icons/plugin.png) !important;
+}
+
 .x-linked {
     display: inline-block;
     background-image: url(../icons/linked.gif) !important;
     height: auto;
 }
 
-/** vim: ts=4:sw=4:nu:fdc=4:nospell 
+/** vim: ts=4:sw=4:nu:fdc=4:nospell
  *
- * Ext.ux.grid.RowActions.css 
+ * Ext.ux.grid.RowActions.css
  *
  * Style sheets for Grid RowActions Plugin
  *
  * the Open Source LGPL 3.0 license.  Commercial use is permitted to the extent
  * that the code/component(s) do NOT become part of another Open Source or Commercially
  * licensed development library or toolkit without explicit permission.
- * 
+ *
  * License details: http://www.gnu.org/licenses/lgpl.html
  */
 
  * @version   $Id: Ext.ux.form.LovCombo.css 189 2008-04-16 21:01:06Z jozo $
  *
  * @license Ext.ux.form.LovCombo.css is licensed under the terms of the Open Source
- * LGPL 3.0 license. Commercial use is permitted to the extent that the 
+ * LGPL 3.0 license. Commercial use is permitted to the extent that the
  * code/component(s) do NOT become part of another Open Source or Commercially
  * licensed development library or toolkit without explicit permission.
- * 
+ *
  * License details: http://www.gnu.org/licenses/lgpl.html
  */
 .ux-lovcombo-icon {
 }
 
 .ux-lovcombo-icon-unchecked {
-    background: transparent 
+    background: transparent
         url(../extjs/resources/images/default/menu/unchecked.gif);
 }
 
index 178c85326572a317717b9af3c031220adf72994d..3cd86c1b5af39c9539f82f6c2b1d25d546022303 100644 (file)
@@ -884,6 +884,7 @@ tvheadend.idnode_editor_form = function(uilevel, d, meta, panel, conf)
     var df = [];
     var groups = null;
     var width = 0;
+    var hiddenFields = [];
 
     /* Fields */
     for (var i = 0; i < d.length; i++) {
@@ -919,7 +920,9 @@ tvheadend.idnode_editor_form = function(uilevel, d, meta, panel, conf)
                 ]
             });
         }
-        if (p.group && meta && meta.groups) {
+        if (p.hidden) {
+            hiddenFields.push(f);
+        } else if (p.group && meta && meta.groups) {
             f.tvh_uilevel = p.expert ? 'expert' : (p.advanced ? 'advanced' : 'basic');
             if (!groups)
                 groups = {};
@@ -1046,7 +1049,25 @@ tvheadend.idnode_editor_form = function(uilevel, d, meta, panel, conf)
         if (rf.length)
             panel.add(newFieldSet({ title: _("Read-only Info"), items: rf, collapsed: 'true'}));
     }
+
+    if (hiddenFields.length) {
+        var f = newFieldSet({ items: hiddenFields });
+        f.setVisible(false);
+        panel.add(f);
+    }
+
+    // form customization (if any) before layout()
+    if (conf.forms) {
+        if ('default' in conf.forms) {
+            conf.forms['default'](panel.getForm());
+        }
+        if (meta['class'] in conf.forms) {
+            conf.forms[meta['class']](panel.getForm());
+        }
+    }
+
     panel.doLayout();
+
     if (width)
         panel.fixedWidth = width + 50;
     if (conf.uuids) {
@@ -1085,7 +1106,8 @@ tvheadend.idnode_editor = function(_uilevel, item, conf)
         var c = {
             showpwd: conf.showpwd,
             uuids: conf.uuids,
-            labelWidth: conf.labelWidth || 200
+            labelWidth: conf.labelWidth || 200,
+            forms: conf.forms
         };
 
         tvheadend.idnode_editor_form(uilevel, item.props || item.params, item.meta, panel, c);
@@ -1362,10 +1384,14 @@ tvheadend.idnode_create = function(conf, onlyDefault, cloneValues)
                         pclass = r.get(conf.select.valueField);
                         win.setTitle(String.format(_('Add {0}'), s.lastSelectionText));
                         panel.remove(s);
-                        tvheadend.idnode_editor_form(uilevel, d, r.json, panel, { create: true, showpwd: true });
+                        tvheadend.idnode_editor_form(uilevel, d, r.json, panel, { create: true, showpwd: true, forms: conf.forms });
                         abuttons.save.setVisible(true);
                         abuttons.apply.setVisible(true);
                         win.setOriginSize(true);
+                        if (conf.select.formField) {
+                            values = values || {};
+                            values[conf.select.formField] = r.data[conf.select.valueField];
+                        }
                         if (values)
                             panel.getForm().setValues(values);
                     }
@@ -2289,6 +2315,9 @@ tvheadend.idnode_form_grid = function(panel, conf)
                 text: _('Add'),
                 disabled: false,
                 handler: function() {
+                    if (!conf.add.forms && conf.forms) {
+                        conf.add['forms'] = conf['forms'];
+                    }
                     tvheadend.idnode_create(conf.add, true);
                 }
             });
@@ -2474,7 +2503,8 @@ tvheadend.idnode_form_grid = function(panel, conf)
                         noButtons: true,
                         width: 730,
                         noautoWidth: true,
-                        showpwd: conf.showpwd
+                        showpwd: conf.showpwd,
+                        forms: conf.forms
                     });
                     abuttons.save.setDisabled(false);
                     abuttons.undo.setDisabled(false);
diff --git a/src/webui/static/app/profile.js b/src/webui/static/app/profile.js
new file mode 100644 (file)
index 0000000..b0ef7ce
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Stream Profiles
+ */
+
+tvheadend.profile_tab = function(panel)
+{
+    if (!tvheadend.profile_builders) {
+        tvheadend.profile_builders = new Ext.data.JsonStore({
+            url: 'api/profile/builders',
+            root: 'entries',
+            fields: ['class', 'caption', 'order', 'groups', 'params'],
+            id: 'class',
+            autoLoad: true
+        });
+    }
+
+    var list = '-class';
+
+    tvheadend.idnode_form_grid(panel, {
+        url: 'api/profile',
+        clazz: 'profile',
+        comet: 'profile',
+        titleS: _('Stream Profile'),
+        titleP: _('Stream Profiles'),
+        titleC: _('Stream Profile Name'),
+        iconCls: 'film',
+        edit: { params: { list: list } },
+        add: {
+            url: 'api/profile',
+            titleS: _('Stream Profile'),
+            select: {
+                label: _('Type'),
+                store: tvheadend.profile_builders,
+                fullRecord: true,
+                displayField: 'caption',
+                valueField: 'class',
+                list: list
+            },
+            create: { }
+        },
+        del: true
+    });
+};
index e86678ccffe8fea9d3df548a6dc88cb4b919da52..55db1143f47f145bc0c9543c137bad2fa54d658b 100644 (file)
@@ -116,7 +116,7 @@ tvheadend.mdhelp = function(pagename) {
             tvheadend.doc_win.close();
             tvheadend.doc_win = null;
         }
-        
+
         if (title)
             title = title[title.length-1];
 
@@ -131,7 +131,7 @@ tvheadend.mdhelp = function(pagename) {
             if (history)
                 history += '<hr/>';
         }
-        
+
         var bodyid = Ext.id();
         var text = '<div id="' + bodyid + '">';
         if (tvheadend.docs_toc || history)
@@ -233,7 +233,7 @@ tvheadend.mdhelp = function(pagename) {
         tvheadend.doc_win = win;
     }
 
-    var helpfailuremsg = function() { 
+    var helpfailuremsg = function() {
         Ext.MessageBox.show({
             title:_('Error'),
             msg: _('There was a problem displaying the Help!') + '<br>' + 
@@ -658,7 +658,7 @@ function diskspaceUpdate(o) {
 }
 
 /**
- * This function creates top level tabs based on access so users without 
+ * This function creates top level tabs based on access so users without
  * access to subsystems won't see them.
  *
  * Obviosuly, access is verified in the server too.
@@ -672,10 +672,10 @@ function accessUpdate(o) {
 
     if (o.uilevel)
         tvheadend.uilevel = o.uilevel;
-        
+
     if (o.theme)
         tvheadend.theme = o.theme;
-        
+
     tvheadend.quicktips = o.quicktips ? true : false;
 
     if (o.uilevel_nochange)
@@ -725,7 +725,7 @@ function accessUpdate(o) {
         tvheadend.baseconf(general);
         tvheadend.imgcacheconf(general);
         tvheadend.satipsrvconf(general);
-        
+
         cp.add(general);
 
         /* Users */
@@ -741,7 +741,7 @@ function accessUpdate(o) {
         tvheadend.acleditor(users);
         tvheadend.passwdeditor(users);
         tvheadend.ipblockeditor(users);
-        
+
         cp.add(users);
 
         /* DVB inputs, networks, muxes, services */
@@ -790,6 +790,8 @@ function accessUpdate(o) {
             iconCls: 'film_edit',
             items: []
         });
+        tvheadend.profile_tab(stream);
+        tvheadend.codec_tab(stream);
         tvheadend.esfilter_tab(stream);
 
         cp.add(stream);
@@ -805,7 +807,7 @@ function accessUpdate(o) {
         });
         tvheadend.dvr_settings(tsdvr);
         if (tvheadend.capabilities.indexOf('timeshift') !== -1)
-          tvheadend.timeshift(tsdvr);
+            tvheadend.timeshift(tsdvr);
 
         cp.add(tsdvr);
 
@@ -885,7 +887,7 @@ tvheadend.log = function(msg, style) {
  *
  */
 tvheadend.RootTabExtraComponent = Ext.extend(Ext.Component, {
-   
+
     onRender1: function(tab, before) {
         if (!this.componentTpl) {
             var tt = new Ext.Template(
@@ -904,7 +906,7 @@ tvheadend.RootTabExtraComponent = Ext.extend(Ext.Component, {
 });
 
 tvheadend.RootTabExtraClickComponent = Ext.extend(Ext.Component, {
-   
+
     onRender1: function(tab, before, click_cb) {
         if (!this.componentTpl) {
             var tt = new Ext.Template(
@@ -959,7 +961,7 @@ tvheadend.RootTabPanel = Ext.extend(Ext.TabPanel, {
                 }
             }
         }
-        
+
         if (this.extra.time)
             window.setInterval(this.setTime, 1000);
     },
deleted file mode 120000 (symlink)
index 10c08be5c42ce95ec0aa64878925593cb3285aa0..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../../../../../../vendor/ext-3.4/examples/ux/Spinner.js
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..2d54442acda417fa84cb07add8d6d4f138800635
--- /dev/null
@@ -0,0 +1,480 @@
+/*
+This file is part of Ext JS 3.4
+
+Copyright (c) 2011-2013 Sencha Inc
+
+Contact:  http://www.sencha.com/contact
+
+GNU General Public License Usage
+This file may be used under the terms of the GNU General Public License version 3.0 as
+published by the Free Software Foundation and appearing in the file LICENSE included in the
+packaging of this file.
+
+Please review the following information to ensure the GNU General Public License version 3.0
+requirements will be met: http://www.gnu.org/copyleft/gpl.html.
+
+If you are unsure which license is appropriate for your use, please contact the sales department
+at http://www.sencha.com/contact.
+
+Build date: 2013-04-03 15:07:25
+*/
+/**
+ * @class Ext.ux.Spinner
+ * @extends Ext.util.Observable
+ * Creates a Spinner control utilized by Ext.ux.form.SpinnerField
+ */
+Ext.ux.Spinner = Ext.extend(Ext.util.Observable, {
+    incrementValue: 1,
+    alternateIncrementValue: 5,
+    triggerClass: 'x-form-spinner-trigger',
+    splitterClass: 'x-form-spinner-splitter',
+    alternateKey: Ext.EventObject.shiftKey,
+    defaultValue: 0,
+    accelerate: false,
+    readOnly: false,
+    editable: true,
+
+    constructor: function(config){
+        Ext.ux.Spinner.superclass.constructor.call(this, config);
+        Ext.apply(this, config);
+        this.mimicing = false;
+    },
+
+    init: function(field){
+        this.field = field;
+
+        field.afterMethod('onRender', this.doRender, this);
+        field.afterMethod('onEnable', this.doEnable, this);
+        field.afterMethod('onDisable', this.doDisable, this);
+        field.afterMethod('afterRender', this.doAfterRender, this);
+        field.afterMethod('onResize', this.doResize, this);
+        field.afterMethod('onFocus', this.doFocus, this);
+        field.beforeMethod('onDestroy', this.doDestroy, this);
+    },
+
+    doRender: function(ct, position){
+        var el = this.el = this.field.getEl();
+        var f = this.field;
+
+        if (!f.wrap) {
+            f.wrap = this.wrap = el.wrap({
+                cls: "x-form-field-wrap"
+            });
+        }
+        else {
+            this.wrap = f.wrap.addClass('x-form-field-wrap');
+        }
+
+        this.trigger = this.wrap.createChild({
+            tag: "img",
+            src: Ext.BLANK_IMAGE_URL,
+            cls: "x-form-trigger " + this.triggerClass
+        });
+
+        if (!f.width) {
+            this.wrap.setWidth(el.getWidth() + this.trigger.getWidth());
+        }
+
+        this.splitter = this.wrap.createChild({
+            tag: 'div',
+            cls: this.splitterClass,
+            style: 'width:13px; height:2px;'
+        });
+        this.splitter.setRight((Ext.isIE) ? 1 : 2).setTop(10).show();
+
+        this.proxy = this.trigger.createProxy('', this.splitter, true);
+        this.proxy.addClass("x-form-spinner-proxy");
+        this.proxy.setStyle('left', '0px');
+        this.proxy.setSize(14, 1);
+        this.proxy.hide();
+        this.dd = new Ext.dd.DDProxy(this.splitter.dom.id, "SpinnerDrag", {
+            dragElId: this.proxy.id
+        });
+
+        this.initTrigger();
+        this.initSpinner();
+    },
+
+    doAfterRender: function(){
+        var y;
+        if (Ext.isIE && this.el.getY() != (y = this.trigger.getY())) {
+            this.el.position();
+            this.el.setY(y);
+        }
+        this.updateEditState();
+    },
+
+    doEnable: function(){
+        if (this.wrap) {
+            this.disabled = false;
+            this.wrap.removeClass(this.field.disabledClass);
+        }
+    },
+
+    doDisable: function(){
+        if (this.wrap) {
+               this.disabled = true;
+            this.wrap.addClass(this.field.disabledClass);
+            this.el.removeClass(this.field.disabledClass);
+        }
+    },
+
+    doResize: function(w, h){
+        if (typeof w == 'number') {
+            this.el.setWidth(w - this.trigger.getWidth());
+        }
+        this.wrap.setWidth(this.el.getWidth() + this.trigger.getWidth());
+    },
+
+    doFocus: function(){
+        if (!this.mimicing) {
+            this.wrap.addClass('x-trigger-wrap-focus');
+            this.mimicing = true;
+            Ext.get(Ext.isIE ? document.body : document).on("mousedown", this.mimicBlur, this, {
+                delay: 10
+            });
+            this.el.on('keydown', this.checkTab, this);
+        }
+    },
+
+    // private
+    checkTab: function(e){
+        if (e.getKey() == e.TAB) {
+            this.triggerBlur();
+        }
+    },
+
+    // private
+    mimicBlur: function(e){
+        if (!this.wrap.contains(e.target) && this.field.validateBlur(e)) {
+            this.triggerBlur();
+        }
+    },
+
+    // private
+    triggerBlur: function(){
+        this.mimicing = false;
+        Ext.get(Ext.isIE ? document.body : document).un("mousedown", this.mimicBlur, this);
+        this.el.un("keydown", this.checkTab, this);
+        this.field.beforeBlur();
+        this.wrap.removeClass('x-trigger-wrap-focus');
+        this.field.onBlur.call(this.field);
+    },
+
+    initTrigger: function(){
+        this.trigger.addClassOnOver('x-form-trigger-over');
+        this.trigger.addClassOnClick('x-form-trigger-click');
+    },
+
+    initSpinner: function(){
+        this.field.addEvents({
+            'spin': true,
+            'spinup': true,
+            'spindown': true
+        });
+
+        this.keyNav = new Ext.KeyNav(this.el, {
+            "up": function(e){
+                e.preventDefault();
+                this.onSpinUp();
+            },
+
+            "down": function(e){
+                e.preventDefault();
+                this.onSpinDown();
+            },
+
+            "pageUp": function(e){
+                e.preventDefault();
+                this.onSpinUpAlternate();
+            },
+
+            "pageDown": function(e){
+                e.preventDefault();
+                this.onSpinDownAlternate();
+            },
+
+            scope: this
+        });
+
+        this.repeater = new Ext.util.ClickRepeater(this.trigger, {
+            accelerate: this.accelerate
+        });
+        this.field.mon(this.repeater, "click", this.onTriggerClick, this, {
+            preventDefault: true
+        });
+
+        this.field.mon(this.trigger, {
+            mouseover: this.onMouseOver,
+            mouseout: this.onMouseOut,
+            mousemove: this.onMouseMove,
+            mousedown: this.onMouseDown,
+            mouseup: this.onMouseUp,
+            scope: this,
+            preventDefault: true
+        });
+
+        this.field.mon(this.wrap, "mousewheel", this.handleMouseWheel, this);
+
+        this.dd.setXConstraint(0, 0, 10)
+        this.dd.setYConstraint(1500, 1500, 10);
+        this.dd.endDrag = this.endDrag.createDelegate(this);
+        this.dd.startDrag = this.startDrag.createDelegate(this);
+        this.dd.onDrag = this.onDrag.createDelegate(this);
+    },
+
+    onMouseOver: function(){
+        if (this.disabled) {
+            return;
+        }
+        var middle = this.getMiddle();
+        this.tmpHoverClass = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-overup' : 'x-form-spinner-overdown';
+        this.trigger.addClass(this.tmpHoverClass);
+    },
+
+    //private
+    onMouseOut: function(){
+        this.trigger.removeClass(this.tmpHoverClass);
+    },
+
+    //private
+    onMouseMove: function(){
+        if (this.disabled) {
+            return;
+        }
+        var middle = this.getMiddle();
+        if (((Ext.EventObject.getPageY() > middle) && this.tmpHoverClass == "x-form-spinner-overup") ||
+        ((Ext.EventObject.getPageY() < middle) && this.tmpHoverClass == "x-form-spinner-overdown")) {
+        }
+    },
+
+    //private
+    onMouseDown: function(){
+        if (this.disabled) {
+            return;
+        }
+        var middle = this.getMiddle();
+        this.tmpClickClass = (Ext.EventObject.getPageY() < middle) ? 'x-form-spinner-clickup' : 'x-form-spinner-clickdown';
+        this.trigger.addClass(this.tmpClickClass);
+    },
+
+    //private
+    onMouseUp: function(){
+        this.trigger.removeClass(this.tmpClickClass);
+    },
+
+    //private
+    onTriggerClick: function(){
+        if (this.disabled || this.readOnly) {
+            return;
+        }
+        var middle = this.getMiddle();
+        var ud = (Ext.EventObject.getPageY() < middle) ? 'Up' : 'Down';
+        this['onSpin' + ud]();
+    },
+
+    //private
+    getMiddle: function(){
+        var t = this.trigger.getTop();
+        var h = this.trigger.getHeight();
+        var middle = t + (h / 2);
+        return middle;
+    },
+
+    //private
+    //checks if control is allowed to spin
+    isSpinnable: function(){
+        if (this.disabled || this.readOnly) {
+            Ext.EventObject.preventDefault(); //prevent scrolling when disabled/readonly
+            return false;
+        }
+        return true;
+    },
+
+    updateEditState: function(){
+        if (this.field.rendered) {
+            this.field.setReadOnly(this.readOnly || (!this.editable));
+        }
+    },
+
+    setEditable: function(editable){
+        if(editable != this.editable){
+            this.editable = editable;
+            this.updateEditState();
+        }
+    },
+
+    setReadOnly: function(readOnly){
+        if(readOnly != this.readOnly){
+            this.readOnly = readOnly;
+            this.updateEditState();
+        }
+    },
+
+    handleMouseWheel: function(e){
+        //disable scrolling when not focused
+        if (this.wrap.hasClass('x-trigger-wrap-focus') == false) {
+            return;
+        }
+
+        var delta = e.getWheelDelta();
+        if (delta > 0) {
+            this.onSpinUp();
+            e.stopEvent();
+        }
+        else
+            if (delta < 0) {
+                this.onSpinDown();
+                e.stopEvent();
+            }
+    },
+
+    //private
+    startDrag: function(){
+        this.proxy.show();
+        this._previousY = Ext.fly(this.dd.getDragEl()).getTop();
+    },
+
+    //private
+    endDrag: function(){
+        this.proxy.hide();
+    },
+
+    //private
+    onDrag: function(){
+        if (this.disabled) {
+            return;
+        }
+        var y = Ext.fly(this.dd.getDragEl()).getTop();
+        var ud = '';
+
+        if (this._previousY > y) {
+            ud = 'Up';
+        } //up
+        if (this._previousY < y) {
+            ud = 'Down';
+        } //down
+        if (ud != '') {
+            this['onSpin' + ud]();
+        }
+
+        this._previousY = y;
+    },
+
+    //private
+    onSpinUp: function(){
+        if (this.isSpinnable() == false) {
+            return;
+        }
+        if (Ext.EventObject.shiftKey == true) {
+            this.onSpinUpAlternate();
+            return;
+        }
+        else {
+            this.spin(false, false);
+        }
+        this.field.fireEvent("spin", this);
+        this.field.fireEvent("spinup", this);
+    },
+
+    //private
+    onSpinDown: function(){
+        if (this.isSpinnable() == false) {
+            return;
+        }
+        if (Ext.EventObject.shiftKey == true) {
+            this.onSpinDownAlternate();
+            return;
+        }
+        else {
+            this.spin(true, false);
+        }
+        this.field.fireEvent("spin", this);
+        this.field.fireEvent("spindown", this);
+    },
+
+    //private
+    onSpinUpAlternate: function(){
+        if (this.isSpinnable() == false) {
+            return;
+        }
+        this.spin(false, true);
+        this.field.fireEvent("spin", this);
+        this.field.fireEvent("spinup", this);
+    },
+
+    //private
+    onSpinDownAlternate: function(){
+        if (this.isSpinnable() == false) {
+            return;
+        }
+        this.spin(true, true);
+        this.field.fireEvent("spin", this);
+        this.field.fireEvent("spindown", this);
+    },
+
+    spin: function(down, alternate){
+        var v = parseFloat(this.field.getValue());
+        var incr = (alternate == true) ? this.alternateIncrementValue : this.incrementValue;
+        (down == true) ? v -= incr : v += incr;
+
+        v = (isNaN(v)) ? this.defaultValue : v;
+        v = this.fixBoundries(v);
+        this.field.setRawValue(v);
+    },
+
+    fixBoundries: function(value){
+        var v = value;
+
+        if (this.field.minValue != undefined && v < this.field.minValue) {
+            v = this.field.minValue;
+        }
+        if (this.field.maxValue != undefined && v > this.field.maxValue) {
+            v = this.field.maxValue;
+        }
+
+        return this.fixPrecision(v);
+    },
+
+    // private
+    fixPrecision: function(value){
+        var nan = isNaN(value);
+        if (!this.field.allowDecimals || this.field.decimalPrecision == -1 || nan || !value) {
+            return nan ? '' : value;
+        }
+        return parseFloat(parseFloat(value).toFixed(this.field.decimalPrecision));
+    },
+
+    doDestroy: function(){
+        if (this.trigger) {
+            this.trigger.remove();
+        }
+        if (this.wrap) {
+            this.wrap.remove();
+            delete this.field.wrap;
+        }
+
+        if (this.splitter) {
+            this.splitter.remove();
+        }
+
+        if (this.dd) {
+            this.dd.unreg();
+            this.dd = null;
+        }
+
+        if (this.proxy) {
+            this.proxy.remove();
+        }
+
+        if (this.repeater) {
+            this.repeater.purgeListeners();
+        }
+        if (this.mimicing){
+            Ext.get(Ext.isIE ? document.body : document).un("mousedown", this.mimicBlur, this);
+        }
+    }
+});
+
+//backwards compat
+Ext.form.Spinner = Ext.ux.Spinner;
deleted file mode 120000 (symlink)
index 2aff7165eb6158f2304a0f4f2b1579299e7cbac8..0000000000000000000000000000000000000000
+++ /dev/null
@@ -1 +0,0 @@
-../../../../../../vendor/ext-3.4/examples/ux/SpinnerField.js
\ No newline at end of file
new file mode 100644 (file)
index 0000000000000000000000000000000000000000..d6f7615cebf345cbb0305db9495d42258dfe2194
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+This file is part of Ext JS 3.4
+
+Copyright (c) 2011-2013 Sencha Inc
+
+Contact:  http://www.sencha.com/contact
+
+GNU General Public License Usage
+This file may be used under the terms of the GNU General Public License version 3.0 as
+published by the Free Software Foundation and appearing in the file LICENSE included in the
+packaging of this file.
+
+Please review the following information to ensure the GNU General Public License version 3.0
+requirements will be met: http://www.gnu.org/copyleft/gpl.html.
+
+If you are unsure which license is appropriate for your use, please contact the sales department
+at http://www.sencha.com/contact.
+
+Build date: 2013-04-03 15:07:25
+*/
+Ext.ns('Ext.ux.form');
+
+/**
+ * @class Ext.ux.form.SpinnerField
+ * @extends Ext.form.NumberField
+ * Creates a field utilizing Ext.ux.Spinner
+ * @xtype spinnerfield
+ */
+Ext.ux.form.SpinnerField = Ext.extend(Ext.form.NumberField, {
+    actionMode: 'wrap',
+    deferHeight: true,
+    autoSize: Ext.emptyFn,
+    onBlur: Ext.emptyFn,
+    adjustSize: Ext.BoxComponent.prototype.adjustSize,
+
+       constructor: function(config) {
+               var spinnerConfig = Ext.copyTo({}, config, 'incrementValue,alternateIncrementValue,accelerate,defaultValue,triggerClass,splitterClass,readOnly,editable');
+
+               var spl = this.spinner = new Ext.ux.Spinner(spinnerConfig);
+
+               var plugins = config.plugins
+                       ? (Ext.isArray(config.plugins)
+                               ? config.plugins.push(spl)
+                               : [config.plugins, spl])
+                       : spl;
+
+               Ext.ux.form.SpinnerField.superclass.constructor.call(this, Ext.apply(config, {plugins: plugins}));
+       },
+
+    // private
+    getResizeEl: function(){
+        return this.wrap;
+    },
+
+    // private
+    getPositionEl: function(){
+        return this.wrap;
+    },
+
+    // private
+    alignErrorIcon: function(){
+        if (this.wrap) {
+            this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);
+        }
+    },
+
+    validateBlur: function(){
+        return true;
+    }
+});
+
+Ext.reg('spinnerfield', Ext.ux.form.SpinnerField);
+
+//backwards compat
+Ext.form.SpinnerField = Ext.ux.form.SpinnerField;
diff --git a/src/webui/static/icons/plugin_disabled.png b/src/webui/static/icons/plugin_disabled.png
new file mode 120000 (symlink)
index 0000000..eaec3df
--- /dev/null
@@ -0,0 +1 @@
+../../../../vendor/famfamsilk/plugin_disabled.png
\ No newline at end of file
diff --git a/support/patches/libmfx.linux.path.diff b/support/patches/libmfx.linux.path.diff
deleted file mode 100644 (file)
index 8a3699a..0000000
+++ /dev/null
@@ -1,12 +0,0 @@
-diff -urN ../mfx_dispatch.orig/src/mfx_library_iterator_linux.cpp ./src/mfx_library_iterator_linux.cpp
---- ../mfx_dispatch.orig/src/mfx_library_iterator_linux.cpp    2016-02-06 12:48:45.070749257 +0100
-+++ ./src/mfx_library_iterator_linux.cpp       2016-02-06 12:50:40.830752490 +0100
-@@ -333,7 +333,7 @@
-     m_implType = implType;
-     snprintf(m_path, sizeof(m_path)/sizeof(m_path[0]),
--             "%s/%s/%04x/%04x", mfx_storage_opt, mfx_folder, m_vendorID, m_deviceID);
-+             "%s/%s", mfx_storage_opt, mfx_folder);
-     m_libs_num = mfx_list_libraries(m_path, (MFX_LIB_HARDWARE == implType), &m_libs);
index 7aaec3874d0919ac792c1e75cdc6e289ff3c2dd6..e0df0fb855009334c38b5cecee88a68a3b2f8b8c 100644 (file)
@@ -1,6 +1,7 @@
---- source/CMakeLists.txt.old  2016-05-05 16:49:17.373993257 +0200
-+++ source/CMakeLists.txt      2016-05-05 16:50:25.259801461 +0200
-@@ -100,11 +100,11 @@
+diff -urN ../x265_2.0.orig/source/CMakeLists.txt ./source/CMakeLists.txt
+--- ../x265_2.0.orig/source/CMakeLists.txt     2016-07-13 15:53:26.000000000 +0200
++++ ./source/CMakeLists.txt    2016-07-30 08:57:07.632539944 +0200
+@@ -107,11 +107,7 @@
      endif(NO_ATOMICS)
  endif(UNIX)
  
@@ -9,11 +10,7 @@
 -else()
 -    option(ENABLE_PIC "Enable Position Independent Code" OFF)
 -endif(X64 AND NOT WIN32)
-+#if(X64 AND NOT WIN32)
 +option(ENABLE_PIC "Enable Position Independent Code" ON)
-+#else()
-+#    option(ENABLE_PIC "Enable Position Independent Code" OFF)
-+#endif(X64 AND NOT WIN32)
  
  # Compiler detection
  if(CMAKE_GENERATOR STREQUAL "Xcode")
index 0f4a93012a7d60f4f3a7c7481d34c8330780d690..570b84305d532bbe01daf2648920795e663437ec 100644 (file)
@@ -1,33 +1,26 @@
---- source/CMakeLists.txt.old  2016-05-03 14:34:37.168396127 +0200
-+++ source/CMakeLists.txt      2016-05-03 14:35:35.551372285 +0200
-@@ -100,7 +100,7 @@
-     endif(NO_ATOMICS)
- endif(UNIX)
--if(X64 AND NOT WIN32)
-+if(X64NONONO AND NOT WIN32)
-     option(ENABLE_PIC "Enable Position Independent Code" ON)
- else()
-     option(ENABLE_PIC "Enable Position Independent Code" OFF)
-@@ -166,6 +166,8 @@
+diff -urN ../x265_2.0.pic/source/CMakeLists.txt ./source/CMakeLists.txt
+--- ../x265_2.0.pic/source/CMakeLists.txt      2016-07-30 08:57:07.632539944 +0200
++++ ./source/CMakeLists.txt    2016-08-08 12:12:39.539380667 +0200
+@@ -168,7 +168,7 @@
+     add_definitions(-D__STDC_LIMIT_MACROS=1)
      add_definitions(-std=gnu++98)
      if(ENABLE_PIC)
-          add_definitions(-fPIC)
-+    else()
+-         add_definitions(-fPIC)
 +         add_definitions(-fPIE)
      endif(ENABLE_PIC)
      if(NATIVE_BUILD)
          if(INTEL_CXX)
---- source/cmake/CMakeASM_YASMInformation.cmake.old    2016-05-03 15:52:36.572122457 +0200
-+++ source/cmake/CMakeASM_YASMInformation.cmake        2016-05-03 15:53:18.939438179 +0200
-@@ -3,9 +3,7 @@
- if(X64)
-     list(APPEND ASM_FLAGS -DARCH_X86_64=1)
--    if(ENABLE_PIC)
--        list(APPEND ASM_FLAGS -DPIC)
--    endif()
-+    list(APPEND ASM_FLAGS -DPIC)
-     if(APPLE)
-         set(ARGS -f macho64 -m amd64 -DPREFIX)
-     elseif(UNIX AND NOT CYGWIN)
+@@ -183,11 +183,11 @@
+         endif()
+     endif()
+     if(ARM AND CROSS_COMPILE_ARM)
+-        set(ARM_ARGS -march=armv6 -mfloat-abi=soft -mfpu=vfp -marm -fPIC)
++        set(ARM_ARGS -march=armv6 -mfloat-abi=soft -mfpu=vfp -marm -fPIE)
+     elseif(ARM)
+         find_package(Neon)
+         if(CPU_HAS_NEON)
+-            set(ARM_ARGS -mcpu=native -mfloat-abi=hard -mfpu=neon -marm -fPIC)
++            set(ARM_ARGS -mcpu=native -mfloat-abi=hard -mfpu=neon -marm -fPIE)
+             add_definitions(-DHAVE_NEON)
+         else()
+             set(ARM_ARGS -mcpu=native -mfloat-abi=hard -mfpu=vfp -marm)