From: lekma Date: Sun, 21 Aug 2016 07:32:18 +0000 (+0200) Subject: [wip]: codec profiles + transcode + vaapi X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=014bb9505e1d7eb6e37e10931dd89ab3b1a0dc01;p=thirdparty%2Ftvheadend.git [wip]: codec profiles + transcode + vaapi --- diff --git a/Makefile b/Makefile index b3ce2db3a..c2e780610 100644 --- 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) diff --git a/Makefile.ffmpeg b/Makefile.ffmpeg index b605ce885..d060656c2 100644 --- a/Makefile.ffmpeg +++ b/Makefile.ffmpeg @@ -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 $@ diff --git a/Makefile.webui b/Makefile.webui index eb7fbbbc8..ff8a1745e 100644 --- a/Makefile.webui +++ b/Makefile.webui @@ -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" diff --git a/configure b/configure index 926907bc7..a61ff4eb2 100755 --- 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 diff --git a/src/api.c b/src/api.c index 50d857d1b..66d5d8b98 100644 --- 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(); diff --git a/src/api.h b/src/api.h index 6636540cd..80f2b5d28 100644 --- 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 index 000000000..d19fca477 --- /dev/null +++ b/src/api/api_codec.c @@ -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 . + */ + + +#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); +} diff --git a/src/dvr/dvr_autorec.c b/src/dvr/dvr_autorec.c index b0af7be07..e3667a950 100644 --- a/src/dvr/dvr_autorec.c +++ b/src/dvr/dvr_autorec.c @@ -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 && diff --git a/src/dvr/dvr_config.c b/src/dvr/dvr_config.c index c7b8d94a4..1b5c98b3c 100644 --- a/src/dvr/dvr_config.c +++ b/src/dvr/dvr_config.c @@ -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); } diff --git a/src/dvr/dvr_db.c b/src/dvr/dvr_db.c index 828ca1a4a..38984e1e8 100644 --- a/src/dvr/dvr_db.c +++ b/src/dvr/dvr_db.c @@ -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) { diff --git a/src/dvr/dvr_rec.c b/src/dvr/dvr_rec.c index 9ebd3d7d6..bc2e66d3d 100644 --- a/src/dvr/dvr_rec.c +++ b/src/dvr/dvr_rec.c @@ -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 ?: "", si->si_network ?: "", @@ -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); } diff --git a/src/dvr/dvr_timerec.c b/src/dvr/dvr_timerec.c index af93fb51f..70d239698 100644 --- a/src/dvr/dvr_timerec.c +++ b/src/dvr/dvr_timerec.c @@ -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 && diff --git a/src/esfilter.h b/src/esfilter.h index 0d516fad3..7c7cf22f2 100644 --- a/src/esfilter.h +++ b/src/esfilter.h @@ -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) diff --git a/src/libav.c b/src/libav.c index 042b16f49..961660b7d 100644 --- a/src/libav.c +++ b/src/libav.c @@ -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 diff --git a/src/main.c b/src/main.c index 2456d14c2..93039634b 100644 --- a/src/main.c +++ b/src/main.c @@ -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, >imers, gti_link) @@ -686,7 +687,7 @@ mainloop(void) #endif while((gti = LIST_FIRST(>imers)) != 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(); diff --git a/src/muxer/muxer_libav.c b/src/muxer/muxer_libav.c index da6b6cff6..683ddfae6 100644 --- a/src/muxer/muxer_libav.c +++ b/src/muxer/muxer_libav.c @@ -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: diff --git a/src/muxer/muxer_mkv.c b/src/muxer/muxer_mkv.c index 755dbad1d..aa58a224e 100644 --- a/src/muxer/muxer_mkv.c +++ b/src/muxer/muxer_mkv.c @@ -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); diff --git a/src/parsers/bitstream.h b/src/parsers/bitstream.h index 4e4d4b960..f1bcf16df 100644 --- a/src/parsers/bitstream.h +++ b/src/parsers/bitstream.h @@ -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_ */ diff --git a/src/plumbing/globalheaders.c b/src/plumbing/globalheaders.c index 28cf0ff7f..cc7f7319f 100644 --- a/src/plumbing/globalheaders.c +++ b/src/plumbing/globalheaders.c @@ -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 index f2fa90890..000000000 --- a/src/plumbing/transcoding.c +++ /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 . - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 index 3fc42b0e0..000000000 --- a/src/plumbing/transcoding.h +++ /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 . - */ -#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); diff --git a/src/plumbing/tsfix.c b/src/plumbing/tsfix.c index f01f3b5a4..33e2bdf1b 100644 --- a/src/plumbing/tsfix.c +++ b/src/plumbing/tsfix.c @@ -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; diff --git a/src/profile.c b/src/profile.c index 612e64d56..abc53c53d 100644 --- a/src/profile.c +++ b/src/profile.c @@ -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)) { diff --git a/src/prop.c b/src/prop.c index fb33d128a..654cd0284 100644 --- a/src/prop.c +++ b/src/prop.c @@ -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) diff --git a/src/prop.h b/src/prop.h index ae9ea2a80..8404ad297 100644 --- a/src/prop.h +++ b/src/prop.h @@ -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 ); diff --git a/src/streaming.c b/src/streaming.c index a53cee746..38a0f1aea 100644 --- a/src/streaming.c +++ b/src/streaming.c @@ -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 index 000000000..b62c2da2b --- /dev/null +++ b/src/transcoding/codec.h @@ -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 . + */ + + +#ifndef TVH_TRANSCODING_CODEC_H__ +#define TVH_TRANSCODING_CODEC_H__ + + +#include "tvheadend.h" +#include "idnode.h" +#include "streaming.h" +#include "libav.h" + +#include + + +#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 index 000000000..4bd2908c8 --- /dev/null +++ b/src/transcoding/codec/codec.c @@ -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 . + */ + + +#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 : ""); + 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) : ""; +} + + +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 index 000000000..380e2f2be --- /dev/null +++ b/src/transcoding/codec/codecs/aac.c @@ -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 . + */ + + +#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 index 000000000..80b698073 --- /dev/null +++ b/src/transcoding/codec/codecs/libs/libfdk_aac.c @@ -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 . + */ + + +#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 index 000000000..5a5862caa --- /dev/null +++ b/src/transcoding/codec/codecs/libs/libopus.c @@ -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 . + */ + + +#include "transcoding/codec/internals.h" + +#include + + +/* 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 index 000000000..8511cabf7 --- /dev/null +++ b/src/transcoding/codec/codecs/libs/libtheora.c @@ -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 . + */ + + +#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 index 000000000..41b9dc3a8 --- /dev/null +++ b/src/transcoding/codec/codecs/libs/libvorbis.c @@ -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 . + */ + + +#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 index 000000000..cfc5ee367 --- /dev/null +++ b/src/transcoding/codec/codecs/libs/libvpx.c @@ -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 . + */ + + +#include "transcoding/codec/internals.h" + +#include + + +/* 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 index 000000000..4dade839d --- /dev/null +++ b/src/transcoding/codec/codecs/libs/libx26x.c @@ -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 . + */ + + +#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 + +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 + + +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 index 000000000..6cf4ecb0b --- /dev/null +++ b/src/transcoding/codec/codecs/libs/omx.c @@ -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 . + */ + + +#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 index 000000000..446e04fd9 --- /dev/null +++ b/src/transcoding/codec/codecs/libs/vaapi.c @@ -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 . + */ + + +#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 index 000000000..84952a525 --- /dev/null +++ b/src/transcoding/codec/codecs/mp2.c @@ -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 . + */ + + +#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 index 000000000..d78f8d426 --- /dev/null +++ b/src/transcoding/codec/codecs/mpeg2video.c @@ -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 . + */ + + +#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 index 000000000..89df6b2d5 --- /dev/null +++ b/src/transcoding/codec/codecs/vorbis.c @@ -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 . + */ + + +#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 index 000000000..3d3106778 --- /dev/null +++ b/src/transcoding/codec/internals.h @@ -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 . + */ + + +#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 index 000000000..11c3e8f81 --- /dev/null +++ b/src/transcoding/codec/module.c @@ -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 . + */ + + +#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 index 000000000..26047595c --- /dev/null +++ b/src/transcoding/codec/profile.c @@ -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 . + */ + + +#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 : ""); + } +} + + +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 index 000000000..667ed6feb --- /dev/null +++ b/src/transcoding/codec/profile_audio_class.c @@ -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 . + */ + + +#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 index 000000000..8326c0cf5 --- /dev/null +++ b/src/transcoding/codec/profile_class.c @@ -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 . + */ + + +#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) : ""; + 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 index 000000000..8c6f877cc --- /dev/null +++ b/src/transcoding/codec/profile_video_class.c @@ -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 . + */ + + +#include "internals.h" + +#include + + +/* 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 index 000000000..af47f187c --- /dev/null +++ b/src/transcoding/memutils.c @@ -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 . + */ + + +#include "memutils.h" + +#include "memoryinfo.h" + +#include + + +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 index 000000000..ad608fef6 --- /dev/null +++ b/src/transcoding/memutils.h @@ -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 . + */ + + +#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 index 000000000..c842ebb9c --- /dev/null +++ b/src/transcoding/transcode.h @@ -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 . + */ + + +#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 index 000000000..c4ccdcf67 --- /dev/null +++ b/src/transcoding/transcode/audio.c @@ -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 . + */ + + +#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 index 000000000..d18fd5ed0 --- /dev/null +++ b/src/transcoding/transcode/context.c @@ -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 . + */ + + +#include "internals.h" + +#include + + +#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 : ""); + 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 index 000000000..57d2be638 --- /dev/null +++ b/src/transcoding/transcode/helpers.c @@ -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 . + */ + + +#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 index 000000000..75195dde9 --- /dev/null +++ b/src/transcoding/transcode/hwaccels/hwaccels.c @@ -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 . + */ + +#include "hwaccels.h" + +#if ENABLE_VAAPI +#include "vaapi.h" +#endif + +#include + + +/* 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 index 000000000..aaa3733a6 --- /dev/null +++ b/src/transcoding/transcode/hwaccels/hwaccels.h @@ -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 . + */ + + +#ifndef TVH_TRANSCODING_TRANSCODE_HWACCELS_H__ +#define TVH_TRANSCODING_TRANSCODE_HWACCELS_H__ + + +#include "tvheadend.h" + +#include + + +/* 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 index 000000000..89321eb7d --- /dev/null +++ b/src/transcoding/transcode/hwaccels/vaapi.c @@ -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 . + */ + + +#include "vaapi.h" + +#include +#include +#include +#include + +#include + + +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 index 000000000..af3944887 --- /dev/null +++ b/src/transcoding/transcode/hwaccels/vaapi.h @@ -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 . + */ + + +#ifndef TVH_TRANSCODING_TRANSCODE_HWACCELS_VAAPI_H__ +#define TVH_TRANSCODING_TRANSCODE_HWACCELS_VAAPI_H__ + + +#include "tvheadend.h" + +#include + + +/* 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 index 000000000..41f5842f0 --- /dev/null +++ b/src/transcoding/transcode/internals.h @@ -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 . + */ + + +#ifndef TVH_TRANSCODING_TRANSCODE_INTERNALS_H__ +#define TVH_TRANSCODING_TRANSCODE_INTERNALS_H__ + + +#include "log.h" + +#include "transcoding/codec.h" +#include "transcoding/memutils.h" + +#include +#include +#include +#include +#include + + +#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 index 000000000..31e8974ed --- /dev/null +++ b/src/transcoding/transcode/log.h @@ -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 . + */ + + +#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 : "", \ + ((self)->oavctx) ? (self)->oavctx->codec->name : "", \ + ##__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 index 000000000..e0669790f --- /dev/null +++ b/src/transcoding/transcode/module.c @@ -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 . + */ + + +#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 index 000000000..7afcdbe6f --- /dev/null +++ b/src/transcoding/transcode/stream.c @@ -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 . + */ + + +#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 index 000000000..e720eb013 --- /dev/null +++ b/src/transcoding/transcode/transcoder.c @@ -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 . + */ + + +#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 index 000000000..4245feb76 --- /dev/null +++ b/src/transcoding/transcode/video.c @@ -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 . + */ + + +#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, +}; diff --git a/src/tvheadend.h b/src/tvheadend.h index dcaf6b24a..673dd1ea4 100644 --- a/src/tvheadend.h +++ b/src/tvheadend.h @@ -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 */ diff --git a/src/tvhlog.c b/src/tvhlog.c index f483bbff2..f21f67593 100644 --- a/src/tvhlog.c +++ b/src/tvhlog.c @@ -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; diff --git a/src/tvhlog.h b/src/tvhlog.h index 53c2c9852..39774c6ec 100644 --- a/src/tvhlog.h +++ b/src/tvhlog.h @@ -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 index 000000000..dff82db6c --- /dev/null +++ b/src/webui/static/app/codec.js @@ -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 + }); + } +}; diff --git a/src/webui/static/app/esfilter.js b/src/webui/static/app/esfilter.js index 7113b746c..d14551ad8 100644 --- a/src/webui/static/app/esfilter.js +++ b/src/webui/static/app/esfilter.js @@ -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, { diff --git a/src/webui/static/app/ext.css b/src/webui/static/app/ext.css index 8749e8e97..56eee7968 100644 --- a/src/webui/static/app/ext.css +++ b/src/webui/static/app/ext.css @@ -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; @@ -624,6 +624,14 @@ 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; @@ -1040,9 +1048,9 @@ 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 * @@ -1054,7 +1062,7 @@ * 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 */ @@ -1135,10 +1143,10 @@ * @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 { @@ -1155,7 +1163,7 @@ } .ux-lovcombo-icon-unchecked { - background: transparent + background: transparent url(../extjs/resources/images/default/menu/unchecked.gif); } diff --git a/src/webui/static/app/idnode.js b/src/webui/static/app/idnode.js index 178c85326..3cd86c1b5 100644 --- a/src/webui/static/app/idnode.js +++ b/src/webui/static/app/idnode.js @@ -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 index 000000000..b0ef7cec3 --- /dev/null +++ b/src/webui/static/app/profile.js @@ -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 + }); +}; diff --git a/src/webui/static/app/tvheadend.js b/src/webui/static/app/tvheadend.js index e86678ccf..55db1143f 100644 --- a/src/webui/static/app/tvheadend.js +++ b/src/webui/static/app/tvheadend.js @@ -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 += '
'; } - + var bodyid = Ext.id(); var text = '
'; 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!') + '
' + @@ -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); }, diff --git a/src/webui/static/extjs/examples/ux/Spinner.js b/src/webui/static/extjs/examples/ux/Spinner.js deleted file mode 120000 index 10c08be5c..000000000 --- a/src/webui/static/extjs/examples/ux/Spinner.js +++ /dev/null @@ -1 +0,0 @@ -../../../../../../vendor/ext-3.4/examples/ux/Spinner.js \ No newline at end of file diff --git a/src/webui/static/extjs/examples/ux/Spinner.js b/src/webui/static/extjs/examples/ux/Spinner.js new file mode 100644 index 000000000..2d54442ac --- /dev/null +++ b/src/webui/static/extjs/examples/ux/Spinner.js @@ -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; diff --git a/src/webui/static/extjs/examples/ux/SpinnerField.js b/src/webui/static/extjs/examples/ux/SpinnerField.js deleted file mode 120000 index 2aff7165e..000000000 --- a/src/webui/static/extjs/examples/ux/SpinnerField.js +++ /dev/null @@ -1 +0,0 @@ -../../../../../../vendor/ext-3.4/examples/ux/SpinnerField.js \ No newline at end of file diff --git a/src/webui/static/extjs/examples/ux/SpinnerField.js b/src/webui/static/extjs/examples/ux/SpinnerField.js new file mode 100644 index 000000000..d6f7615ce --- /dev/null +++ b/src/webui/static/extjs/examples/ux/SpinnerField.js @@ -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 index 000000000..eaec3df38 --- /dev/null +++ b/src/webui/static/icons/plugin_disabled.png @@ -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 index 8a3699a1d..000000000 --- a/support/patches/libmfx.linux.path.diff +++ /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); - diff --git a/support/patches/libx265.pic.diff b/support/patches/libx265.pic.diff index 7aaec3874..e0df0fb85 100644 --- a/support/patches/libx265.pic.diff +++ b/support/patches/libx265.pic.diff @@ -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") diff --git a/support/patches/libx265.pie.diff b/support/patches/libx265.pie.diff index 0f4a93012..570b84305 100644 --- a/support/patches/libx265.pie.diff +++ b/support/patches/libx265.pie.diff @@ -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)