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)
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 \
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 \
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)
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
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
# ##############################################################################
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
$(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
$(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 \
--disable-examples \
--disable-docs \
--disable-unit-tests \
- $(LIBVPX_TARGET)
+ $(LIBVPX_TARGET)
DIST_DIR=$(EPREFIX) \
$(MAKE) -C $(LIB_ROOT)/$(LIBVPX) install
@touch $@
$(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
$(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 \
$(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) \
$(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
# ##############################################################################
-# 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
#
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
$(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 \
$(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 $@
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
$(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"
"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"
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
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
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
api_access_init();
api_dvr_init();
api_caclient_init();
+ api_codec_init();
api_profile_init();
api_language_init();
api_satip_server_init();
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 );
--- /dev/null
+/*
+ * tvheadend - API access to Codec Profiles
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "tvheadend.h"
+#include "access.h"
+#include "api.h"
+
+#include "transcoding/codec.h"
+
+
+static int
+api_codec_list(access_t *perm, void *opaque, const char *op,
+ htsmsg_t *args, htsmsg_t **resp)
+{
+ htsmsg_t *list = NULL, *map = NULL;
+ TVHCodec *tvh_codec = NULL;
+ int err = ENOMEM;
+
+ if ((list = htsmsg_create_list())) {
+ pthread_mutex_lock(&global_lock);
+ SLIST_FOREACH(tvh_codec, &tvh_codecs, link) {
+ if (!(map = idclass_serialize(tvh_codec_get_class(tvh_codec),
+ perm->aa_lang_ui))) {
+ htsmsg_destroy(list);
+ list = NULL;
+ break;
+ }
+ htsmsg_add_str(map, "name", tvh_codec_get_name(tvh_codec));
+ htsmsg_add_str(map, "title", tvh_codec_get_title(tvh_codec));
+ htsmsg_add_msg(list, NULL, map);
+ }
+ pthread_mutex_unlock(&global_lock);
+ if (list) {
+ if ((*resp = htsmsg_create_map())) {
+ htsmsg_add_msg(*resp, "entries", list);
+ err = 0;
+ }
+ else {
+ htsmsg_destroy(list);
+ list = NULL;
+ }
+ }
+ }
+ return err;
+}
+
+
+static int
+api_codec_profile_list(access_t *perm, void *opaque, const char *op,
+ htsmsg_t *args, htsmsg_t **resp)
+{
+ htsmsg_t *list = NULL, *map = NULL;
+ TVHCodecProfile *tvh_profile = NULL;
+ char buf[UUID_HEX_SIZE];
+ int err = ENOMEM;
+
+ if ((list = htsmsg_create_list())) {
+ pthread_mutex_lock(&global_lock);
+ LIST_FOREACH(tvh_profile, &tvh_codec_profiles, link) {
+ if (!(map = htsmsg_create_map())) {
+ htsmsg_destroy(list);
+ list = NULL;
+ break;
+ }
+ htsmsg_add_str(map, "uuid",
+ idnode_uuid_as_str((idnode_t *)tvh_profile, buf));
+ htsmsg_add_str(map, "title",
+ tvh_codec_profile_get_title(tvh_profile));
+ htsmsg_add_str(map, "status",
+ tvh_codec_profile_get_status(tvh_profile));
+ htsmsg_add_msg(list, NULL, map);
+ }
+ pthread_mutex_unlock(&global_lock);
+ if (list) {
+ if ((*resp = htsmsg_create_map())) {
+ htsmsg_add_msg(*resp, "entries", list);
+ err = 0;
+ }
+ else {
+ htsmsg_destroy(list);
+ list = NULL;
+ }
+ }
+ }
+ return err;
+}
+
+
+static int
+api_codec_profile_create(access_t *perm, void *opaque, const char *op,
+ htsmsg_t *args, htsmsg_t **resp)
+{
+ const char *codec_name = NULL;
+ htsmsg_t *conf = NULL;
+ int err = EINVAL;
+
+ if ((codec_name = htsmsg_get_str(args, "class")) &&
+ (conf = htsmsg_get_map(args, "conf"))) {
+ htsmsg_set_str(conf, "codec_name", codec_name);
+ pthread_mutex_lock(&global_lock);
+ err = (err = tvh_codec_profile_create(conf, NULL, 1)) ? -err : 0;
+ pthread_mutex_unlock(&global_lock);
+ }
+ return err;
+}
+
+
+void
+api_codec_init(void)
+{
+ static api_hook_t ah[] = {
+ {"codec/list", ACCESS_ADMIN, api_codec_list, NULL},
+ {"codec_profile/list", ACCESS_ANONYMOUS, api_codec_profile_list, NULL},
+ {"codec_profile/create", ACCESS_ADMIN, api_codec_profile_create, NULL},
+ {"codec_profile/class", ACCESS_ADMIN, api_idnode_class,
+ (void*)&codec_profile_class},
+ {NULL},
+ };
+
+ api_register_all(ah);
+}
}
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 &&
}
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))
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);
}
switch(de->de_sched_state) {
case DVR_SCHEDULED:
return N_("Scheduled for recording");
-
+
case DVR_RECORDING:
switch(de->de_rec_state) {
/* 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;
// 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;
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;
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
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) {
/**
*
*/
-dvr_entry_t *
+dvr_entry_t *
dvr_entry_update
( dvr_entry_t *de, int enabled,
const char *dvr_config_uuid, channel_t *ch,
/**
* 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;
LIST_FOREACH(de, &dvrentries, de_global_link)
if(idnode_get_short_uuid(&de->de_id) == id)
break;
- return de;
+ return de;
}
}
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))
}
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))
}
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 &&
}
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))
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)
{
else if (cfg->dvr_whitespace_in_title &&
(*s == ' ' || *s == '\t') &&
dosubs)
- *s = '-';
+ *s = '-';
else if (cfg->dvr_clean_title &&
((*s < 32) || (*s > 122) ||
"adapter: \"%s\", "
"network: \"%s\", mux: \"%s\", provider: \"%s\", "
"service: \"%s\"",
-
+
dvr_get_filename(de) ?: lang_str_get(de->de_title, NULL),
si->si_adapter ?: "<N/A>",
si->si_network ?: "<N/A>",
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, "?");
case SMT_SERVICE_STATUS:
if (sm->sm_code & TSS_PACKETS) {
-
+
} else if (sm->sm_code & TSS_ERRORS) {
int code = SM_CODE_UNDEFINED_ERROR;
args = htsstr_argsplit(buf1);
if(args[0])
spawnv(args[0], (void *)args, NULL, 1, 1);
-
+
htsstr_argsplit_free(args);
}
}
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 &&
#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)
-#include "plumbing/transcoding.h"
+#include "transcoding/transcode.h"
#include "libav.h"
/**
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);
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;
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;
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:
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;
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;
/**
- *
- */
+ *
+ */
int
libav_is_encoder(AVCodec *codec)
{
}
/**
- *
- */
+ *
+ */
void
libav_set_loglevel(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
#include "intlconv.h"
#include "dbus.h"
#include "libav.h"
+#include "transcoding/codec.h"
#include "profile.h"
#include "bouquet.h"
#include "tvhtime.h"
enum {
OPT_STR,
OPT_INT,
- OPT_BOOL,
+ OPT_BOOL,
OPT_STR_LIST,
} type;
void *param;
free(desc);
}
}
- printf("%s",
+ printf("%s",
_("\n"
"For more information please visit the Tvheadend website:\n"
"https://tvheadend.org\n"));
next = now + sec2mono(3600);
while((mti = LIST_FIRST(&mtimers)) != NULL) {
-
+
if (mti->mti_expire > now) {
next = mti->mti_expire;
break;
tvh_cond_timedwait(&mtimer_cond, &global_lock, next);
pthread_mutex_unlock(&global_lock);
}
-
+
return NULL;
}
// 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)
#endif
while((gti = LIST_FIRST(>imers)) != NULL) {
-
+
if (gti->gti_expire > now) {
ts.tv_sec = gti->gti_expire;
break;
}
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..
tvhlog_options &= ~TVHLOG_OPT_STDERR;
if (!isatty(2))
tvhlog_options &= ~TVHLOG_OPT_DECORATE;
-
+
/* Initialise clock */
pthread_mutex_lock(&global_lock);
__mdispatch_clock = getmonoclock();
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);
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);
if(opt_fork)
unlink(opt_pidpath);
- libav_done();
-
/* OpenSSL - welcome to the "cleanup" hell */
ENGINE_cleanup();
RAND_cleanup();
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:
#include "parsers/parser_hevc.h"
#include "muxer_mkv.h"
+
extern int dvr_iov_max;
TAILQ_HEAD(mk_cue_queue, mk_cue);
/**
- *
+ * 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;
}
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";
codec_id = "A_VORBIS";
break;
+ case SCT_OPUS:
+ tracktype = 2;
+ codec_id = "A_OPUS";
+ break;
+
case SCT_DVBSUB:
if (mk->dvbsub_skip)
goto disable;
case SCT_MPEG2VIDEO:
case SCT_MP4A:
case SCT_AAC:
+ case SCT_OPUS:
if(ssc->ssc_gh) {
sbuf_t hdr;
sbuf_init(&hdr);
}
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;
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);
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_ */
type == SCT_MPEG2VIDEO ||
type == SCT_MP4A ||
type == SCT_AAC ||
- type == SCT_VORBIS;
+ type == SCT_VORBIS ||
+ type == SCT_THEORA;
}
/**
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;
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;
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);
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) {
/* restart */
gh_start(gh, sm);
break;
-
+
case SMT_STOP:
gh->gh_passthru = 0;
gh_flush(gh);
+++ /dev/null
-/**
- * Transcoding
- * Copyright (C) 2013 John Törnblom
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include <unistd.h>
-#include <libavformat/avformat.h>
-#include <libavcodec/avcodec.h>
-#include <libavfilter/avfiltergraph.h>
-#include <libavfilter/buffersink.h>
-#include <libavfilter/buffersrc.h>
-#include <libavutil/opt.h>
-#include <libavresample/avresample.h>
-#include <libavutil/opt.h>
-#include <libavutil/audio_fifo.h>
-#include <libavutil/dict.h>
-
-#if LIBAVUTIL_VERSION_MICRO >= 100 /* FFMPEG */
-#define USING_FFMPEG 1
-#endif
-
-#include "tvheadend.h"
-#include "settings.h"
-#include "streaming.h"
-#include "service.h"
-#include "packet.h"
-#include "transcoding.h"
-#include "libav.h"
-#include "parsers/bitstream.h"
-#include "parsers/parser_avc.h"
-
-LIST_HEAD(transcoder_stream_list, transcoder_stream);
-
-struct transcoder;
-
-typedef struct transcoder_stream {
- int ts_index;
- streaming_component_type_t ts_type;
- streaming_target_t *ts_target;
- LIST_ENTRY(transcoder_stream) ts_link;
- int ts_first;
-
- pktbuf_t *ts_input_gh;
-
- void (*ts_handle_pkt) (struct transcoder *, struct transcoder_stream *, th_pkt_t *);
- void (*ts_destroy) (struct transcoder *, struct transcoder_stream *);
-} transcoder_stream_t;
-
-
-typedef struct audio_stream {
- transcoder_stream_t;
-
- AVCodecContext *aud_ictx;
- AVCodec *aud_icodec;
-
- AVCodecContext *aud_octx;
- AVCodec *aud_ocodec;
-
- uint64_t aud_dec_pts;
- uint64_t aud_enc_pts;
-
- int8_t aud_channels;
- int32_t aud_bitrate;
-
- AVAudioResampleContext *resample_context;
- AVAudioFifo *fifo;
- int resample;
- int resample_is_open;
-
- enum AVSampleFormat last_sample_fmt;
- int last_sample_rate;
- uint64_t last_channel_layout;
-
-} audio_stream_t;
-
-
-typedef struct video_stream {
- transcoder_stream_t;
-
- AVCodecContext *vid_ictx;
- AVCodec *vid_icodec;
-
- AVCodecContext *vid_octx;
- AVCodec *vid_ocodec;
-
- AVFrame *vid_dec_frame;
- AVFrame *vid_enc_frame;
-
- AVFilterGraph *flt_graph;
- AVFilterContext *flt_bufsrcctx;
- AVFilterContext *flt_bufsinkctx;
-
- int16_t vid_width;
- int16_t vid_height;
-
- int vid_first_sent;
- int vid_first_encoded;
- th_pkt_t *vid_first_pkt;
-} video_stream_t;
-
-
-typedef struct subtitle_stream {
- transcoder_stream_t;
-
- AVCodecContext *sub_ictx;
- AVCodec *sub_icodec;
-
- AVCodecContext *sub_octx;
- AVCodec *sub_ocodec;
-} subtitle_stream_t;
-
-
-
-typedef struct transcoder {
- streaming_target_t t_input; // must be first
- streaming_target_t *t_output;
-
- uint32_t t_id;
-
- transcoder_props_t t_props;
- struct transcoder_stream_list t_stream_list;
-} transcoder_t;
-
-
-
-#define WORKING_ENCODER(x) \
- ((x) == AV_CODEC_ID_H264 || (x) == AV_CODEC_ID_MPEG2VIDEO || \
- (x) == AV_CODEC_ID_VP8 || /* (x) == AV_CODEC_ID_VP9 || */ \
- (x) == AV_CODEC_ID_HEVC || (x) == AV_CODEC_ID_AAC || \
- (x) == AV_CODEC_ID_MP2 || (x) == AV_CODEC_ID_VORBIS)
-
-/**
- *
- */
-static inline int
-shortid(transcoder_t *t)
-{
- return t->t_id & 0xffff;
-}
-
-static inline void
-transcoder_stream_invalidate(transcoder_stream_t *ts)
-{
- ts->ts_index = 0;
-}
-
-static AVCodecContext *
-avcodec_alloc_context3_tvh(const AVCodec *codec)
-{
- AVCodecContext *ctx = avcodec_alloc_context3(codec);
- if (ctx) {
- ctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
- }
- return ctx;
-}
-
-static char *const
-get_error_text(const int error)
-{
- static char error_buffer[255];
- av_strerror(error, error_buffer, sizeof(error_buffer));
- return error_buffer;
-}
-
-static int
-transcode_opt_set_int(transcoder_t *t, transcoder_stream_t *ts,
- void *ctx, const char *opt,
- int64_t val, int abort)
-{
- int opt_error;
- if ((opt_error = av_opt_set_int(ctx, opt, val, 0)) != 0) {
- tvherror(LS_TRANSCODE, "%04X: Could not set option %s (error '%s')",
- shortid(t), opt, get_error_text(opt_error));
- if (abort)
- transcoder_stream_invalidate(ts);
- return -1;
- }
- return 0;
-}
-
-static void
-av_dict_set_int__(AVDictionary **opts, const char *key, int64_t val, int flags)
-{
- char buf[32];
- snprintf(buf, sizeof(buf), "%"PRId64, val);
- av_dict_set(opts, key, buf, flags);
-}
-
-/**
- * get best effort sample rate
- */
-static int
-transcode_get_sample_rate(int rate, AVCodec *codec)
-{
- /* if codec only supports certain rates, check if rate is available */
- if (codec->supported_samplerates) {
- /* Find if we have a matching sample_rate */
- int acount = 0;
- int rate_alt = 0;
- while (codec->supported_samplerates[acount] > 0) {
- if (codec->supported_samplerates[acount] == rate) {
- /* original rate supported by codec */
- return rate;
- }
-
- /* check for highest available rate smaller that the original rate */
- if (codec->supported_samplerates[acount] > rate_alt &&
- codec->supported_samplerates[acount] < rate) {
- rate_alt = codec->supported_samplerates[acount];
- }
- acount++;
- }
-
- return rate_alt;
- }
-
-
- return rate;
-}
-
-/**
- * get best effort sample format
- */
-static enum AVSampleFormat
-transcode_get_sample_fmt(enum AVSampleFormat fmt, AVCodec *codec)
-{
- /* if codec only supports certain formats, check if selected format is available */
- if (codec->sample_fmts) {
- /* Find if we have a matching sample_fmt */
- int acount = 0;
- while (codec->sample_fmts[acount] > AV_SAMPLE_FMT_NONE) {
- if (codec->sample_fmts[acount] == fmt) {
- /* original format supported by codec */
- return fmt;
- }
- acount++;
- }
-
- /* use first supported sample format */
- if (acount > 0) {
- return codec->sample_fmts[0];
- } else {
- return AV_SAMPLE_FMT_NONE;
- }
- }
-
- return fmt;
-}
-
-/**
- * get best effort channel layout
- */
-static uint64_t
-transcode_get_channel_layout(int *channels, AVCodec *codec)
-{
- uint64_t channel_layout = AV_CH_LAYOUT_STEREO;
-
- /* use channel layout based on input field */
- switch (*channels) {
- case 1: channel_layout = AV_CH_LAYOUT_MONO; break;
- case 2: channel_layout = AV_CH_LAYOUT_STEREO; break;
- case 3: channel_layout = AV_CH_LAYOUT_SURROUND; break;
- case 4: channel_layout = AV_CH_LAYOUT_QUAD; break;
- case 5: channel_layout = AV_CH_LAYOUT_5POINT0; break;
- case 6: channel_layout = AV_CH_LAYOUT_5POINT1; break;
- case 7: channel_layout = AV_CH_LAYOUT_6POINT1; break;
- case 8: channel_layout = AV_CH_LAYOUT_7POINT1; break;
- }
-
- /* if codec only supports certain layouts, check if selected layout is available */
- if (codec->channel_layouts) {
- int acount = 0;
- uint64_t channel_layout_def = av_get_default_channel_layout(*channels);
- uint64_t channel_layout_alt = 0;
-
- while (codec->channel_layouts[acount] > 0) {
- if (codec->channel_layouts[acount] == channel_layout) {
- /* original layout supported by codec */
- return channel_layout;
- }
-
- /* check for best matching layout with same or less number of channels */
- if (av_get_channel_layout_nb_channels(codec->channel_layouts[acount]) <= *channels) {
- if (av_get_channel_layout_nb_channels(codec->channel_layouts[acount]) >
- av_get_channel_layout_nb_channels(channel_layout_alt)) {
- /* prefer layout with more channels */
- channel_layout_alt = codec->channel_layouts[acount];
- } else if (av_get_channel_layout_nb_channels(codec->channel_layouts[acount]) ==
- av_get_channel_layout_nb_channels(channel_layout_alt) &&
- codec->channel_layouts[acount] == channel_layout_def) {
- /* prefer default layout for number of channels over alternative layout */
- channel_layout_alt = channel_layout_def;
- }
- }
-
- acount++;
- }
-
- if (channel_layout_alt) {
- channel_layout = channel_layout_alt;
- *channels = av_get_channel_layout_nb_channels(channel_layout_alt);
- } else {
- channel_layout = 0;
- *channels = 0;
- }
- }
-
- return channel_layout;
-}
-
-/**
- *
- */
-static AVCodec *
-transcoder_get_decoder(transcoder_t *t, streaming_component_type_t ty)
-{
- enum AVCodecID codec_id;
- AVCodec *codec;
-
- /* the MP4A and AAC packet format is same, reduce to one type */
- if (ty == SCT_MP4A)
- ty = SCT_AAC;
-
- codec_id = streaming_component_type2codec_id(ty);
- if (codec_id == AV_CODEC_ID_NONE) {
- tvherror(LS_TRANSCODE, "%04X: Unsupported input codec %s",
- shortid(t), streaming_component_type2txt(ty));
- return NULL;
- }
-
- codec = avcodec_find_decoder(codec_id);
- if (!codec) {
- tvherror(LS_TRANSCODE, "%04X: Unable to find %s decoder",
- shortid(t), streaming_component_type2txt(ty));
- return NULL;
- }
-
- tvhtrace(LS_TRANSCODE, "%04X: Using decoder %s", shortid(t), codec->name);
-
- return codec;
-}
-
-
-/**
- *
- */
-static AVCodec *
-transcoder_get_encoder(transcoder_t *t, const char *codec_name)
-{
- AVCodec *codec;
-
- codec = avcodec_find_encoder_by_name(codec_name);
- if (!codec) {
- tvherror(LS_TRANSCODE, "%04X: Unable to find %s encoder",
- shortid(t), codec_name);
- return NULL;
- }
- tvhtrace(LS_TRANSCODE, "%04X: Using encoder %s", shortid(t), codec->name);
-
- return codec;
-}
-
-
-/**
- *
- */
-static void
-transcoder_stream_packet(transcoder_t *t, transcoder_stream_t *ts, th_pkt_t *pkt)
-{
- streaming_message_t *sm;
-
- tvhtrace(LS_TRANSCODE, "%04X: deliver copy (pts = %" PRIu64 ")",
- shortid(t), pkt->pkt_pts);
- sm = streaming_msg_create_pkt(pkt);
- streaming_target_deliver2(ts->ts_target, sm);
- pkt_ref_dec(pkt);
-}
-
-
-/**
- *
- */
-static void
-transcoder_stream_subtitle(transcoder_t *t, transcoder_stream_t *ts, th_pkt_t *pkt)
-{
- //streaming_message_t *sm;
- AVCodec *icodec;
- AVCodecContext *ictx;
- AVPacket packet;
- AVSubtitle sub;
- int length, got_subtitle;
-
- subtitle_stream_t *ss = (subtitle_stream_t*)ts;
-
- ictx = ss->sub_ictx;
- //octx = ss->sub_octx;
-
- icodec = ss->sub_icodec;
- //ocodec = ss->sub_ocodec;
-
-
- if (!avcodec_is_open(ictx)) {
- if (avcodec_open2(ictx, icodec, NULL) < 0) {
- tvherror(LS_TRANSCODE, "%04X: Unable to open %s decoder",
- shortid(t), icodec->name);
- transcoder_stream_invalidate(ts);
- return;
- }
- }
-
- av_init_packet(&packet);
- packet.data = pktbuf_ptr(pkt->pkt_payload);
- packet.size = pktbuf_len(pkt->pkt_payload);
- packet.pts = pkt->pkt_pts;
- packet.dts = pkt->pkt_dts;
- packet.duration = pkt->pkt_duration;
-
- memset(&sub, 0, sizeof(sub));
-
- length = avcodec_decode_subtitle2(ictx, &sub, &got_subtitle, &packet);
- if (length <= 0) {
- if (length == AVERROR_INVALIDDATA) goto cleanup;
- tvherror(LS_TRANSCODE, "%04X: Unable to decode subtitle (%d, %s)",
- shortid(t), length, get_error_text(length));
- goto cleanup;
- }
-
- if (!got_subtitle)
- goto cleanup;
-
- //TODO: encoding
-
- cleanup:
- av_free_packet(&packet);
- avsubtitle_free(&sub);
-}
-
-static void
-create_adts_header(pktbuf_t *pb, int sri, int channels)
-{
- bitstream_t bs;
-
- /* 7 bytes of ADTS header */
- init_wbits(&bs, pktbuf_ptr(pb), 56);
-
- put_bits(&bs, 0xfff, 12); // Sync marker
- put_bits(&bs, 0, 1); // ID 0 = MPEG 4, 1 = MPEG 2
- put_bits(&bs, 0, 2); // Layer
- put_bits(&bs, 1, 1); // Protection absent
- put_bits(&bs, 1, 2); // AOT, 1 = AAC LC
- put_bits(&bs, sri, 4);
- put_bits(&bs, 1, 1); // Private bit
- put_bits(&bs, channels, 3);
- put_bits(&bs, 1, 1); // Original
- put_bits(&bs, 1, 1); // Copy
-
- put_bits(&bs, 1, 1); // Copyright identification bit
- put_bits(&bs, 1, 1); // Copyright identification start
- put_bits(&bs, pktbuf_len(pb), 13);
- put_bits(&bs, 0x7ff, 11); // Buffer fullness
- put_bits(&bs, 0, 2); // RDB in frame
-}
-
-/**
- *
- */
-static void
-transcoder_stream_audio(transcoder_t *t, transcoder_stream_t *ts, th_pkt_t *pkt)
-{
- AVCodec *icodec, *ocodec;
- AVCodecContext *ictx, *octx;
- AVPacket packet;
- int length;
- streaming_message_t *sm;
- th_pkt_t *n;
- audio_stream_t *as = (audio_stream_t*)ts;
- int got_frame, got_packet_ptr;
- AVFrame *frame = av_frame_alloc();
- char layout_buf[100];
- uint8_t *d;
-
- ictx = as->aud_ictx;
- octx = as->aud_octx;
-
- icodec = as->aud_icodec;
- ocodec = as->aud_ocodec;
-
- av_init_packet(&packet);
-
- if (!avcodec_is_open(ictx)) {
- if (icodec->id == AV_CODEC_ID_AAC || icodec->id == AV_CODEC_ID_VORBIS) {
- d = pktbuf_ptr(pkt->pkt_payload);
- if (icodec->id == AV_CODEC_ID_AAC && d && pktbuf_len(pkt->pkt_payload) > 2 &&
- d[0] == 0xff && (d[1] & 0xf0) == 0xf0) {
- /* DTS packets have all info */
- } else if (ts->ts_input_gh) {
- ictx->extradata_size = pktbuf_len(ts->ts_input_gh);
- ictx->extradata = av_malloc(ictx->extradata_size);
- memcpy(ictx->extradata,
- pktbuf_ptr(ts->ts_input_gh), pktbuf_len(ts->ts_input_gh));
- tvhtrace(LS_TRANSCODE, "%04X: copy meta data for %s (len %zd)",
- shortid(t), icodec->id == AV_CODEC_ID_AAC ? "AAC" : "VORBIS",
- pktbuf_len(ts->ts_input_gh));
- } else {
- tvherror(LS_TRANSCODE, "%04X: missing meta data for %s",
- shortid(t), icodec->id == AV_CODEC_ID_AAC ? "AAC" : "VORBIS");
- }
- }
-
- if (avcodec_open2(ictx, icodec, NULL) < 0) {
- tvherror(LS_TRANSCODE, "%04X: Unable to open %s decoder",
- shortid(t), icodec->name);
- transcoder_stream_invalidate(ts);
- goto cleanup;
- }
-
- as->aud_dec_pts = pkt->pkt_pts;
- }
-
- if (pkt->pkt_pts > as->aud_dec_pts) {
- tvhwarn(LS_TRANSCODE, "%04X: Detected framedrop in audio", shortid(t));
- as->aud_enc_pts += (pkt->pkt_pts - as->aud_dec_pts);
- as->aud_dec_pts += (pkt->pkt_pts - as->aud_dec_pts);
- }
-
- packet.data = pktbuf_ptr(pkt->pkt_payload);
- packet.size = pktbuf_len(pkt->pkt_payload);
- packet.pts = pkt->pkt_pts;
- packet.dts = pkt->pkt_dts;
- packet.duration = pkt->pkt_duration;
-
- length = avcodec_decode_audio4(ictx, frame, &got_frame, &packet);
- av_free_packet(&packet);
-
- tvhtrace(LS_TRANSCODE, "%04X: audio decode: consumed=%d size=%zu, got=%d, pts=%" PRIi64,
- shortid(t), length, pktbuf_len(pkt->pkt_payload), got_frame, pkt->pkt_pts);
-
- if (length < 0) {
- if (length == AVERROR_INVALIDDATA) goto cleanup;
- tvherror(LS_TRANSCODE, "%04X: Unable to decode audio (%d, %s)",
- shortid(t), length, get_error_text(length));
- transcoder_stream_invalidate(ts);
- goto cleanup;
- }
-
- if (!got_frame) {
- tvhtrace(LS_TRANSCODE, "%04X: Did not have a full frame in the packet", shortid(t));
- goto cleanup;
- }
-
- if (length != pktbuf_len(pkt->pkt_payload))
- tvhwarn(LS_TRANSCODE,
- "%04X: undecoded data (in=%zu, consumed=%d)",
- shortid(t), pktbuf_len(pkt->pkt_payload), length);
-
- if (!avcodec_is_open(octx)) {
- as->aud_enc_pts = pkt->pkt_pts;
- octx->sample_rate = transcode_get_sample_rate(ictx->sample_rate, ocodec);
- octx->sample_fmt = transcode_get_sample_fmt(ictx->sample_fmt, ocodec);
- octx->time_base = ictx->time_base;
- octx->channels = as->aud_channels ? as->aud_channels : ictx->channels;
- octx->channel_layout = transcode_get_channel_layout(&octx->channels, ocodec);
- octx->bit_rate = as->aud_bitrate ? as->aud_bitrate : 0;
- octx->flags |= CODEC_FLAG_GLOBAL_HEADER;
-
- if (!octx->sample_rate) {
- tvherror(LS_TRANSCODE, "%04X: audio encoder has no suitable sample rate!", shortid(t));
- transcoder_stream_invalidate(ts);
- goto cleanup;
- } else {
- tvhdebug(LS_TRANSCODE, "%04X: using audio sample rate %d",
- shortid(t), octx->sample_rate);
- }
-
- if (octx->sample_fmt == AV_SAMPLE_FMT_NONE) {
- tvherror(LS_TRANSCODE, "%04X: audio encoder has no suitable sample format!", shortid(t));
- transcoder_stream_invalidate(ts);
- goto cleanup;
- } else {
- tvhdebug(LS_TRANSCODE, "%04X: using audio sample format %s",
- shortid(t), av_get_sample_fmt_name(octx->sample_fmt));
- }
-
- if (!octx->channel_layout) {
- tvherror(LS_TRANSCODE, "%04X: audio encoder has no suitable channel layout!", shortid(t));
- transcoder_stream_invalidate(ts);
- goto cleanup;
- } else {
- av_get_channel_layout_string(layout_buf, sizeof (layout_buf), octx->channels, octx->channel_layout);
- tvhdebug(LS_TRANSCODE, "%04X: using audio channel layout %s",
- shortid(t), layout_buf);
- }
-
- // Set flags and quality settings, if no bitrate was specified.
- // The MPEG2 encoder only supports encoding with fixed bitrate.
- // All AAC encoders should support encoding with fixed bitrate,
- // but some don't support encoding with global_quality (vbr).
- // All vorbis encoders support encoding with global_quality (vbr),
- // but the built in vorbis encoder doesn't support fixed bitrate.
- switch (ts->ts_type) {
- case SCT_MPEG2AUDIO:
- // use 96 kbit per channel as default
- if (octx->bit_rate == 0) {
- octx->bit_rate = octx->channels * 96000;
- }
- break;
-
- case SCT_AAC:
- octx->flags |= CODEC_FLAG_BITEXACT;
- // use 64 kbit per channel as default
- if (octx->bit_rate == 0) {
- octx->bit_rate = octx->channels * 64000;
- }
- break;
-
- case SCT_VORBIS:
- // use vbr with quality setting as default
- // and also use a user specified bitrate < 16 kbit as quality setting
- if (octx->bit_rate == 0) {
- octx->flags |= CODEC_FLAG_QSCALE;
- octx->global_quality = 4 * FF_QP2LAMBDA;
- } else if (t->t_props.tp_abitrate < 16) {
- octx->flags |= CODEC_FLAG_QSCALE;
- octx->global_quality = t->t_props.tp_abitrate * FF_QP2LAMBDA;
- octx->bit_rate = 0;
- }
- break;
-
- default:
- break;
- }
-
- if (avcodec_open2(octx, ocodec, NULL) < 0) {
- tvherror(LS_TRANSCODE, "%04X: Unable to open %s encoder",
- shortid(t), ocodec->name);
- transcoder_stream_invalidate(ts);
- goto cleanup;
- }
-
- as->fifo = av_audio_fifo_alloc(octx->sample_fmt, octx->channels, 1);
- if (!as->fifo) {
- tvherror(LS_TRANSCODE, "%04X: Could not allocate fifo", shortid(t));
- transcoder_stream_invalidate(ts);
- goto cleanup;
- }
-
- as->resample_context = NULL;
-
- as->last_sample_rate = ictx->sample_rate;
- as->last_sample_fmt = ictx->sample_fmt;
- as->last_channel_layout = ictx->channel_layout;
- }
-
- /* check for changed input format and close resampler, if changed */
- if (as->last_sample_rate != ictx->sample_rate ||
- as->last_sample_fmt != ictx->sample_fmt ||
- as->last_channel_layout != ictx->channel_layout) {
- tvhdebug(LS_TRANSCODE, "%04X: audio input format changed", shortid(t));
-
- as->last_sample_rate = ictx->sample_rate;
- as->last_sample_fmt = ictx->sample_fmt;
- as->last_channel_layout = ictx->channel_layout;
-
- if (as->resample_context) {
- tvhdebug(LS_TRANSCODE, "%04X: stopping audio resampling", shortid(t));
- avresample_free(&as->resample_context);
- as->resample_context = NULL;
- as->resample_is_open = 0;
- }
- }
-
- as->resample = (ictx->channel_layout != octx->channel_layout) ||
- (ictx->sample_fmt != octx->sample_fmt) ||
- (ictx->sample_rate != octx->sample_rate);
-
- if (as->resample) {
- if (!as->resample_context) {
- if (!(as->resample_context = avresample_alloc_context())) {
- tvherror(LS_TRANSCODE, "%04X: Could not allocate resample context", shortid(t));
- transcoder_stream_invalidate(ts);
- goto cleanup;
- }
-
- // resample audio
- tvhdebug(LS_TRANSCODE, "%04X: starting audio resampling", shortid(t));
-
- av_get_channel_layout_string(layout_buf, sizeof (layout_buf), ictx->channels, ictx->channel_layout);
- tvhdebug(LS_TRANSCODE, "%04X: IN : channel_layout=%s, rate=%d, fmt=%s, bitrate=%"PRId64,
- shortid(t), layout_buf, ictx->sample_rate,
- av_get_sample_fmt_name(ictx->sample_fmt), (int64_t)ictx->bit_rate);
-
- av_get_channel_layout_string(layout_buf, sizeof (layout_buf), octx->channels, octx->channel_layout);
- tvhdebug(LS_TRANSCODE, "%04X: OUT: channel_layout=%s, rate=%d, fmt=%s, bitrate=%"PRId64,
- shortid(t), layout_buf, octx->sample_rate,
- av_get_sample_fmt_name(octx->sample_fmt), (int64_t)octx->bit_rate);
-
- if (transcode_opt_set_int(t, ts, as->resample_context,
- "in_channel_layout", ictx->channel_layout, 1))
- goto cleanup;
- if (transcode_opt_set_int(t, ts, as->resample_context,
- "out_channel_layout", octx->channel_layout, 1))
- goto cleanup;
- if (transcode_opt_set_int(t, ts, as->resample_context,
- "in_sample_rate", ictx->sample_rate, 1))
- goto cleanup;
- if (transcode_opt_set_int(t, ts, as->resample_context,
- "out_sample_rate", octx->sample_rate, 1))
- goto cleanup;
- if (transcode_opt_set_int(t, ts, as->resample_context,
- "in_sample_fmt", ictx->sample_fmt, 1))
- goto cleanup;
- if (transcode_opt_set_int(t, ts, as->resample_context,
- "out_sample_fmt", octx->sample_fmt, 1))
- goto cleanup;
- if (avresample_open(as->resample_context) < 0) {
- tvherror(LS_TRANSCODE, "%04X: Error avresample_open", shortid(t));
- transcoder_stream_invalidate(ts);
- goto cleanup;
- }
- as->resample_is_open = 1;
- }
-
- uint8_t **output = alloca(octx->channels * sizeof(uint8_t *));
-
- if (av_samples_alloc(output, NULL, octx->channels, frame->nb_samples, octx->sample_fmt, 1) < 0) {
- tvherror(LS_TRANSCODE, "%04X: av_resamples_alloc failed", shortid(t));
- transcoder_stream_invalidate(ts);
- goto scleanup;
- }
-
- length = avresample_convert(as->resample_context, NULL, 0, frame->nb_samples,
- frame->extended_data, 0, frame->nb_samples);
- tvhtrace(LS_TRANSCODE, "%04X: avresample_convert: %d", shortid(t), length);
- while (avresample_available(as->resample_context) > 0) {
- length = avresample_read(as->resample_context, output, frame->nb_samples);
-
- if (length > 0) {
- if (av_audio_fifo_realloc(as->fifo, av_audio_fifo_size(as->fifo) + length) < 0) {
- tvherror(LS_TRANSCODE, "%04X: Could not reallocate FIFO", shortid(t));
- transcoder_stream_invalidate(ts);
- goto scleanup;
- }
-
- if (av_audio_fifo_write(as->fifo, (void **)output, length) < length) {
- tvherror(LS_TRANSCODE, "%04X: Could not write to FIFO", shortid(t));
- goto scleanup;
- }
- }
- continue;
-
-scleanup:
- transcoder_stream_invalidate(ts);
- av_freep(&output[0]);
- goto cleanup;
- }
-
- av_freep(&output[0]);
-
-/* Need to find out where we are going to do this. Normally at the end.
- int delay_samples = avresample_get_delay(as->resample_context);
- if (delay_samples) {
- tvhdebug(LS_TRANSCODE, "%d samples in resamples delay buffer.", delay_samples);
- goto cleanup;
- }
-*/
-
- } else {
-
- if (av_audio_fifo_realloc(as->fifo, av_audio_fifo_size(as->fifo) + frame->nb_samples) < 0) {
- tvherror(LS_TRANSCODE, "%04X: Could not reallocate FIFO", shortid(t));
- transcoder_stream_invalidate(ts);
- goto cleanup;
- }
-
- if (av_audio_fifo_write(as->fifo, (void **)frame->extended_data, frame->nb_samples) < frame->nb_samples) {
- tvherror(LS_TRANSCODE, "%04X: Could not write to FIFO", shortid(t));
- transcoder_stream_invalidate(ts);
- goto cleanup;
- }
-
- }
-
- as->aud_dec_pts += pkt->pkt_duration;
-
- while (av_audio_fifo_size(as->fifo) >= octx->frame_size) {
- tvhtrace(LS_TRANSCODE, "%04X: audio loop: fifo=%d, frame=%d",
- shortid(t), av_audio_fifo_size(as->fifo), octx->frame_size);
-
- av_frame_free(&frame);
- frame = av_frame_alloc();
- frame->nb_samples = octx->frame_size;
- frame->format = octx->sample_fmt;
-#if USING_FFMPEG
- frame->channels = octx->channels;
-#endif
- frame->channel_layout = octx->channel_layout;
- frame->sample_rate = octx->sample_rate;
- if (av_frame_get_buffer(frame, 0) < 0) {
- tvherror(LS_TRANSCODE, "%04X: Could not allocate output frame samples", shortid(t));
- transcoder_stream_invalidate(ts);
- goto cleanup;
- }
-
- if ((length = av_audio_fifo_read(as->fifo, (void **)frame->data, octx->frame_size)) != octx->frame_size) {
- tvherror(LS_TRANSCODE, "%04X: Could not read data from FIFO", shortid(t));
- transcoder_stream_invalidate(ts);
- goto cleanup;
- }
-
- tvhtrace(LS_TRANSCODE, "%04X: pre-encode: linesize=%d, samples=%d, pts=%" PRIi64,
- shortid(t), frame->linesize[0], length, as->aud_enc_pts);
-
- frame->pts = as->aud_enc_pts;
- as->aud_enc_pts += (octx->frame_size * 90000) / octx->sample_rate;
-
- av_init_packet(&packet);
- packet.data = NULL;
- packet.size = 0;
- length = avcodec_encode_audio2(octx, &packet, frame, &got_packet_ptr);
- tvhtrace(LS_TRANSCODE, "%04X: encoded: packet=%d, ret=%d, got=%d, pts=%" PRIi64,
- shortid(t), packet.size, length, got_packet_ptr, packet.pts);
-
- if ((length < 0) || (got_packet_ptr < -1)) {
-
- tvherror(LS_TRANSCODE, "%04X: Unable to encode audio (%d:%d)",
- shortid(t), length, got_packet_ptr);
- transcoder_stream_invalidate(ts);
- goto cleanup;
-
- } else if (got_packet_ptr && packet.pts >= 0) {
-
- int extra_size = 0;
-
- if (ts->ts_type == SCT_AAC) {
- /* only if ADTS header is missing, create it */
- if (packet.size < 2 || packet.data[0] != 0xff || (packet.data[1] & 0xf0) != 0xf0)
- extra_size = 7;
- }
-
- n = pkt_alloc(ts->ts_type, NULL, packet.size + extra_size, packet.pts, packet.pts, packet.pts);
- memcpy(pktbuf_ptr(n->pkt_payload) + extra_size, packet.data, packet.size);
-
- n->pkt_componentindex = ts->ts_index;
- n->a.pkt_channels = octx->channels;
- n->a.pkt_sri = rate_to_sri(octx->sample_rate);
- n->pkt_duration = packet.duration;
-
- if (extra_size && ts->ts_type == SCT_AAC)
- create_adts_header(n->pkt_payload, n->a.pkt_sri, octx->channels);
-
- if (octx->extradata_size)
- n->pkt_meta = pktbuf_alloc(octx->extradata, octx->extradata_size);
-
- tvhtrace(LS_TRANSCODE, "%04X: deliver audio (pts = %" PRIi64 ", delay = %i)",
- shortid(t), n->pkt_pts, octx->delay);
- sm = streaming_msg_create_pkt(n);
- streaming_target_deliver2(ts->ts_target, sm);
- pkt_ref_dec(n);
- }
-
- av_free_packet(&packet);
- }
-
- cleanup:
-
- av_frame_free(&frame);
- av_free_packet(&packet);
-
- pkt_ref_dec(pkt);
-}
-
-/**
- * Parse MPEG2 header, simplifier version (we know what ffmpeg/libav generates
- */
-static void
-extract_mpeg2_global_data(th_pkt_t *n, uint8_t *data, int len)
-{
-/*
-From: http://en.wikipedia.org/wiki/Elementary_stream
-Field Name # of bits Description
-start code 32 0x000001B3
-Horizontal Size 12
-Vertical Size 12
-Aspect ratio 4
-Frame rate code 4
-Bit rate 18 Actual bit rate = bit rate * 400, rounded upwards. Use 0x3FFFF for variable bit rate.
-Marker bit 1 Always 1.
-VBV buf size 10 Size of video buffer verifier = 16*1024*vbv buf size
-constrained parameters flag 1
-load intra quantizer matrix 1 If bit set then intra quantizer matrix follows, otherwise use default values.
-intra quantizer matrix 0 or 64*8
-load non intra quantizer matrix 1 If bit set then non intra quantizer matrix follows.
-non intra quantizer matrix 0 or 64*8
-
-Minimal of 12 bytes.
-*/
- int hs = 12;
-
- if (len >= hs && RB32(data) == 0x000001b3) { // SEQ_START_CODE
-
- // load intra quantizer matrix
- if (data[hs-1] & 0x02) {
- if (hs + 64 < len) return;
- hs += 64;
- }
-
- // load non intra quantizer matrix
- if (data[hs-1] & 0x01) {
- if (hs + 64 < len) return;
- hs += 64;
- }
-
- // See if we have the first EXT_START_CODE. Normally 10 bytes
- // https://git.libav.org/?p=libav.git;a=blob;f=libavcodec/mpeg12enc.c;h=3376f1075f4b7582a8e4556e98deddab3e049dab;hb=HEAD#l272
- if (hs + 10 <= len && RB32(data + hs) == 0x000001b5) // EXT_START_CODE
- hs += 10;
-
- // See if we have the second EXT_START_CODE. Normally 12 bytes
- // https://git.libav.org/?p=libav.git;a=blob;f=libavcodec/mpeg12enc.c;h=3376f1075f4b7582a8e4556e98deddab3e049dab;hb=HEAD#l291
- // ffmpeg libs might have this block missing
- if (hs + 12 <= len && RB32(data + hs) == 0x000001b5) // EXT_START_CODE
- hs += 12;
-
- // See if we have the second GOP_START_CODE. Normally 31 bits == 4 bytes
- // https://git.libav.org/?p=libav.git;a=blob;f=libavcodec/mpeg12enc.c;h=3376f1075f4b7582a8e4556e98deddab3e049dab;hb=HEAD#l304
- if (hs + 4 <= len && RB32(data + hs) == 0x000001b8) // GOP_START_CODE
- hs += 4;
-
- n->pkt_meta = pktbuf_alloc(data, hs);
- }
-}
-
-/**
- *
- */
-static void
-send_video_packet(transcoder_t *t, transcoder_stream_t *ts, th_pkt_t *pkt,
- AVPacket *epkt, AVCodecContext *octx)
-{
- video_stream_t *vs = (video_stream_t*)ts;
- streaming_message_t *sm;
- th_pkt_t *n;
-
- if (epkt->size <= 0) {
- if (epkt->size) {
- tvherror(LS_TRANSCODE, "%04X: Unable to encode video (%d)", shortid(t), epkt->size);
- transcoder_stream_invalidate(ts);
- }
-
- return;
- }
-
- if (!octx->coded_frame)
- return;
-
- if ((ts->ts_type == SCT_H264 || ts->ts_type == SCT_HEVC) &&
- octx->extradata_size &&
- (ts->ts_first || octx->coded_frame->pict_type == AV_PICTURE_TYPE_I)) {
- n = pkt_alloc(ts->ts_type, NULL, octx->extradata_size + epkt->size, epkt->pts, epkt->dts, epkt->dts);
- memcpy(pktbuf_ptr(n->pkt_payload), octx->extradata, octx->extradata_size);
- memcpy(pktbuf_ptr(n->pkt_payload) + octx->extradata_size, epkt->data, epkt->size);
- ts->ts_first = 0;
- } else {
- n = pkt_alloc(ts->ts_type, epkt->data, epkt->size, epkt->pts, epkt->dts, epkt->dts);
- }
-
- switch (octx->coded_frame->pict_type) {
- case AV_PICTURE_TYPE_I:
- n->v.pkt_frametype = PKT_I_FRAME;
- break;
-
- case AV_PICTURE_TYPE_P:
- n->v.pkt_frametype = PKT_P_FRAME;
- break;
-
- case AV_PICTURE_TYPE_B:
- n->v.pkt_frametype = PKT_B_FRAME;
- break;
-
- default:
- break;
- }
-
- n->pkt_duration = pkt->pkt_duration;
- n->pkt_commercial = pkt->pkt_commercial;
- n->pkt_componentindex = pkt->pkt_componentindex;
- n->v.pkt_field = pkt->v.pkt_field;
- n->v.pkt_aspect_num = pkt->v.pkt_aspect_num;
- n->v.pkt_aspect_den = pkt->v.pkt_aspect_den;
-
- if(octx->coded_frame && octx->coded_frame->pts != AV_NOPTS_VALUE) {
- if(n->pkt_dts != PTS_UNSET)
- n->pkt_dts -= n->pkt_pts;
-
- n->pkt_pts = octx->coded_frame->pts;
-
- if(n->pkt_dts != PTS_UNSET)
- n->pkt_dts += n->pkt_pts;
- }
-
- if (octx->extradata_size) {
- n->pkt_meta = pktbuf_alloc(octx->extradata, octx->extradata_size);
- } else {
- if (octx->codec_id == AV_CODEC_ID_MPEG2VIDEO)
- extract_mpeg2_global_data(n, epkt->data, epkt->size);
- }
-
- tvhtrace(LS_TRANSCODE, "%04X: deliver video (dts = %" PRIu64 ", pts = %" PRIu64 ")", shortid(t), n->pkt_dts, n->pkt_pts);
-
- if (!vs->vid_first_encoded) {
- vs->vid_first_pkt = n;
- vs->vid_first_encoded = 1;
- return;
- }
- if (vs->vid_first_pkt) {
- if (vs->vid_first_pkt->pkt_dts < n->pkt_dts) {
- sm = streaming_msg_create_pkt(vs->vid_first_pkt);
- streaming_target_deliver2(ts->ts_target, sm);
- } else {
- tvhtrace(LS_TRANSCODE, "%04X: video skip first packet", shortid(t));
- }
- pkt_ref_dec(vs->vid_first_pkt);
- vs->vid_first_pkt = NULL;
- }
-
- sm = streaming_msg_create_pkt(n);
- streaming_target_deliver2(ts->ts_target, sm);
- pkt_ref_dec(n);
-
-}
-
-/* create a simple deinterlacer-scaler video filter chain */
-static int
-create_video_filter(video_stream_t *vs, transcoder_t *t,
- AVCodecContext *ictx, AVCodecContext *octx)
-{
- AVFilterInOut *flt_inputs, *flt_outputs;
- AVFilter *flt_bufsrc, *flt_bufsink;
- enum AVPixelFormat pix_fmts[] = { 0, AV_PIX_FMT_NONE };
- char opt[128];
- int err;
-
- err = 1;
- flt_inputs = flt_outputs = NULL;
- flt_bufsrc = flt_bufsink = NULL;
-
- if (vs->flt_graph)
- avfilter_graph_free(&vs->flt_graph);
-
- vs->flt_graph = avfilter_graph_alloc();
- if (!vs->flt_graph)
- return err;
-
- flt_inputs = avfilter_inout_alloc();
- if (!flt_inputs)
- goto out_err;
-
- flt_outputs = avfilter_inout_alloc();
- if (!flt_outputs)
- goto out_err;
-
- flt_bufsrc = avfilter_get_by_name("buffer");
- flt_bufsink = avfilter_get_by_name("buffersink");
- if (!flt_bufsrc || !flt_bufsink) {
- tvherror(LS_TRANSCODE, "%04X: libav default buffers unknown", shortid(t));
- goto out_err;
- }
-
- memset(opt, 0, sizeof(opt));
- snprintf(opt, sizeof(opt), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
- ictx->width,
- ictx->height,
- ictx->pix_fmt,
- ictx->time_base.num,
- ictx->time_base.den,
- ictx->sample_aspect_ratio.num,
- ictx->sample_aspect_ratio.den);
-
- err = avfilter_graph_create_filter(&vs->flt_bufsrcctx, flt_bufsrc, "in",
- opt, NULL, vs->flt_graph);
- if (err < 0) {
- tvherror(LS_TRANSCODE, "%04X: fltchain IN init error", shortid(t));
- goto out_err;
- }
-
- err = avfilter_graph_create_filter(&vs->flt_bufsinkctx, flt_bufsink,
- "out", NULL, NULL, vs->flt_graph);
- if (err < 0) {
- tvherror(LS_TRANSCODE, "%04X: fltchain OUT init error", shortid(t));
- goto out_err;
- }
-
- pix_fmts[0] = octx->pix_fmt;
- err = av_opt_set_int_list(vs->flt_bufsinkctx, "pix_fmts", pix_fmts,
- AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
- if (err < 0) {
- tvherror(LS_TRANSCODE, "%08X: fltchain cannot set output pixfmt",
- shortid(t));
- goto out_err;
- }
-
- flt_outputs->name = av_strdup("in");
- flt_outputs->filter_ctx = vs->flt_bufsrcctx;
- flt_outputs->pad_idx = 0;
- flt_outputs->next = NULL;
- flt_inputs->name = av_strdup("out");
- flt_inputs->filter_ctx = vs->flt_bufsinkctx;
- flt_inputs->pad_idx = 0;
- flt_inputs->next = NULL;
-
- /* add filters: yadif to deinterlace and a scaler */
- memset(opt, 0, sizeof(opt));
- snprintf(opt, sizeof(opt), "yadif,scale=%dx%d",
- octx->width,
- octx->height);
- err = avfilter_graph_parse_ptr(vs->flt_graph,
- opt,
- &flt_inputs,
- &flt_outputs,
- NULL);
- if (err < 0) {
- tvherror(LS_TRANSCODE, "%04X: failed to init filter chain", shortid(t));
- goto out_err;
- }
-
- err = avfilter_graph_config(vs->flt_graph, NULL);
- if (err < 0) {
- tvherror(LS_TRANSCODE, "%04X: failed to config filter chain", shortid(t));
- goto out_err;
- }
-
- avfilter_inout_free(&flt_inputs);
- avfilter_inout_free(&flt_outputs);
-
- return 0; /* all OK */
-
-out_err:
- if (flt_inputs)
- avfilter_inout_free(&flt_inputs);
- if (flt_outputs)
- avfilter_inout_free(&flt_outputs);
- if (vs->flt_graph) {
- avfilter_graph_free(&vs->flt_graph);
- vs->flt_graph = NULL;
- }
-
- return err;
-}
-
-/**
- *
- */
-static void
-transcoder_stream_video(transcoder_t *t, transcoder_stream_t *ts, th_pkt_t *pkt)
-{
- AVCodec *icodec, *ocodec;
- AVCodecContext *ictx, *octx;
- AVDictionary *opts;
- AVPacket packet, packet2;
- int length, ret, got_picture, got_output, got_ref;
- video_stream_t *vs = (video_stream_t*)ts;
- streaming_message_t *sm;
- th_pkt_t *pkt2;
- static int max_bitrate = INT_MAX / ((3000*10)/8);
-
- av_init_packet(&packet);
- av_init_packet(&packet2);
- packet2.data = NULL;
- packet2.size = 0;
-
- ictx = vs->vid_ictx;
- octx = vs->vid_octx;
-
- icodec = vs->vid_icodec;
- ocodec = vs->vid_ocodec;
-
- opts = NULL;
-
- got_ref = 0;
-
- if (!avcodec_is_open(ictx)) {
- if (icodec->id == AV_CODEC_ID_H264) {
- if (ts->ts_input_gh) {
- ictx->extradata_size = pktbuf_len(ts->ts_input_gh);
- ictx->extradata = av_malloc(ictx->extradata_size);
- memcpy(ictx->extradata,
- pktbuf_ptr(ts->ts_input_gh), pktbuf_len(ts->ts_input_gh));
- tvhtrace(LS_TRANSCODE, "%04X: copy meta data for H264 (len %zd)",
- shortid(t), pktbuf_len(ts->ts_input_gh));
- }
- }
-
- if (avcodec_open2(ictx, icodec, NULL) < 0) {
- tvherror(LS_TRANSCODE, "%04X: Unable to open %s decoder", shortid(t), icodec->name);
- transcoder_stream_invalidate(ts);
- goto cleanup;
- }
- }
-
- if (!vs->vid_first_sent) {
- /* notify global headers that we're live */
- /* the video packets might be delayed */
- pkt2 = pkt_alloc(ts->ts_type, NULL, 0, pkt->pkt_pts, pkt->pkt_dts, pkt->pkt_dts);
- pkt2->pkt_componentindex = pkt->pkt_componentindex;
- sm = streaming_msg_create_pkt(pkt2);
- streaming_target_deliver2(ts->ts_target, sm);
- pkt_ref_dec(pkt2);
- vs->vid_first_sent = 1;
- }
-
- packet.data = pktbuf_ptr(pkt->pkt_payload);
- packet.size = pktbuf_len(pkt->pkt_payload);
- packet.pts = pkt->pkt_pts;
- packet.dts = pkt->pkt_dts;
- packet.duration = pkt->pkt_duration;
-
- vs->vid_enc_frame->pts = packet.pts;
- vs->vid_enc_frame->pkt_dts = packet.dts;
- vs->vid_enc_frame->pkt_pts = packet.pts;
-
- vs->vid_dec_frame->pts = packet.pts;
- vs->vid_dec_frame->pkt_dts = packet.dts;
- vs->vid_dec_frame->pkt_pts = packet.pts;
-
- ictx->reordered_opaque = packet.pts;
-
- length = avcodec_decode_video2(ictx, vs->vid_dec_frame, &got_picture, &packet);
- if (length <= 0) {
- if (length == AVERROR_INVALIDDATA) goto cleanup;
- tvherror(LS_TRANSCODE, "%04X: Unable to decode video (%d, %s)",
- shortid(t), length, get_error_text(length));
- goto cleanup;
- }
-
- if (!got_picture)
- goto cleanup;
-
- got_ref = 1;
-
- octx->sample_aspect_ratio.num = ictx->sample_aspect_ratio.num;
- octx->sample_aspect_ratio.den = ictx->sample_aspect_ratio.den;
-
- vs->vid_enc_frame->sample_aspect_ratio.num = vs->vid_dec_frame->sample_aspect_ratio.num;
- vs->vid_enc_frame->sample_aspect_ratio.den = vs->vid_dec_frame->sample_aspect_ratio.den;
-
- if(!avcodec_is_open(octx)) {
- // Common settings
- octx->width = vs->vid_width ? vs->vid_width : ictx->width;
- octx->height = vs->vid_height ? vs->vid_height : ictx->height;
-
- // Encoder uses "time_base" for bitrate calculation, but "time_base" from decoder
- // will be deprecated in the future, therefore calculate "time_base" from "framerate" if available.
- octx->ticks_per_frame = ictx->ticks_per_frame;
- if (ictx->framerate.num == 0) {
- ictx->framerate.num = 30;
- ictx->framerate.den = 1;
- }
- if (ictx->time_base.num == 0) {
- ictx->time_base.num = ictx->framerate.den;
- ictx->time_base.den = ictx->framerate.num;
- }
- octx->framerate = ictx->framerate;
-#if LIBAVCODEC_VERSION_MICRO >= 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(56, 13, 100) // ffmpeg 2.5
- octx->time_base = av_inv_q(av_mul_q(ictx->framerate, av_make_q(ictx->ticks_per_frame, 1)));
-#else
- octx->time_base = ictx->time_base;
-#endif
-
- // set default gop size to 1 second
- octx->gop_size = ceil(av_q2d(av_inv_q(av_div_q(octx->time_base, (AVRational){1, octx->ticks_per_frame}))));
-
- switch (ts->ts_type) {
- case SCT_MPEG2VIDEO:
- if (!strcmp(ocodec->name, "nvenc") || !strcmp(ocodec->name, "mpeg2_qsv"))
- octx->pix_fmt = AV_PIX_FMT_NV12;
- else
- octx->pix_fmt = AV_PIX_FMT_YUV420P;
-
- octx->flags |= CODEC_FLAG_GLOBAL_HEADER;
-
- if (t->t_props.tp_vbitrate < 64) {
- // encode with specified quality and optimize for low latency
- // valid values for quality are 2-31, smaller means better quality, use 5 as default
- octx->flags |= CODEC_FLAG_QSCALE;
- octx->global_quality = FF_QP2LAMBDA *
- (t->t_props.tp_vbitrate == 0 ? 5 : MINMAX(t->t_props.tp_vbitrate, 2, 31));
- } else {
- // encode with specified bitrate and optimize for high compression
- octx->bit_rate = t->t_props.tp_vbitrate * 1000;
- octx->rc_max_rate = ceil(octx->bit_rate * 1.25);
- octx->rc_buffer_size = octx->rc_max_rate * 3;
- // use gop size of 5 seconds
- octx->gop_size *= 5;
- // activate b-frames
- octx->max_b_frames = 3;
- }
-
- break;
-
- case SCT_VP8:
- octx->pix_fmt = AV_PIX_FMT_YUV420P;
-
- // setting quality to realtime will use as much CPU for transcoding as possible,
- // while still encoding in realtime
- av_dict_set(&opts, "quality", "realtime", 0);
-
- if (t->t_props.tp_vbitrate < 64) {
- // encode with specified quality and optimize for low latency
- // valid values for quality are 1-63, smaller means better quality, use 15 as default
- av_dict_set_int__(&opts, "crf", t->t_props.tp_vbitrate == 0 ? 15 : t->t_props.tp_vbitrate, 0);
- // bitrate setting is still required, as it's used as max rate in CQ mode
- // and set to a very low value by default
- octx->bit_rate = 25000000;
- } else {
- // encode with specified bitrate and optimize for high compression
- octx->bit_rate = t->t_props.tp_vbitrate * 1000;
- octx->rc_buffer_size = octx->bit_rate * 3;
- // use gop size of 5 seconds
- octx->gop_size *= 5;
- }
-
- break;
-
- case SCT_H264:
- if (!strcmp(ocodec->name, "nvenc") || !strcmp(ocodec->name, "h264_qsv"))
- octx->pix_fmt = AV_PIX_FMT_NV12;
- else
- octx->pix_fmt = AV_PIX_FMT_YUV420P;
-
- octx->flags |= CODEC_FLAG_GLOBAL_HEADER;
-
- // Default = "medium". We gain more encoding speed compared to the loss of quality when lowering it _slightly_.
- // select preset according to system performance and codec type
- av_dict_set(&opts, "preset", t->t_props.tp_vcodec_preset, 0);
- tvhinfo(LS_TRANSCODE, "%04X: Using preset %s", shortid(t), t->t_props.tp_vcodec_preset);
-
- // All modern devices should support "high" profile
- av_dict_set(&opts, "profile", "high", 0);
-
- if (t->t_props.tp_vbitrate < 64) {
- // encode with specified quality and optimize for low latency
- // valid values for quality are 1-51, smaller means better quality, use 15 as default
- av_dict_set_int__(&opts, "crf", t->t_props.tp_vbitrate == 0 ? 15 : MIN(51, t->t_props.tp_vbitrate), 0);
- // tune "zerolatency" removes as much encoder latency as possible
- av_dict_set(&opts, "tune", "zerolatency", 0);
- } else {
- // encode with specified bitrate and optimize for high compression
- octx->bit_rate = t->t_props.tp_vbitrate * 1000;
- octx->rc_max_rate = ceil(octx->bit_rate * 1.25);
- octx->rc_buffer_size = octx->rc_max_rate * 3;
- // force-cfr=1 is needed for correct bitrate calculation (tune "zerolatency" also sets this)
- av_dict_set(&opts, "x264opts", "force-cfr=1", 0);
- // use gop size of 5 seconds
- octx->gop_size *= 5;
- }
-
- break;
-
- case SCT_HEVC:
- octx->pix_fmt = AV_PIX_FMT_YUV420P;
- octx->flags |= CODEC_FLAG_GLOBAL_HEADER;
-
- // on all hardware ultrafast (or maybe superfast) should be safe
- // select preset according to system performance
- av_dict_set(&opts, "preset", t->t_props.tp_vcodec_preset, 0);
- tvhinfo(LS_TRANSCODE, "%04X: Using preset %s", shortid(t), t->t_props.tp_vcodec_preset);
-
- // disables encoder features which tend to be bottlenecks for the decoder/player
- av_dict_set(&opts, "tune", "fastdecode", 0);
-
- if (t->t_props.tp_vbitrate < 64) {
- // encode with specified quality
- // valid values for crf are 1-51, smaller means better quality
- // use 18 as default
- av_dict_set_int__(&opts, "crf", t->t_props.tp_vbitrate == 0 ? 18 : MIN(51, t->t_props.tp_vbitrate), 0);
-
- // the following is equivalent to tune=zerolatency for presets: ultra/superfast
- av_dict_set(&opts, "x265-params", "bframes=0", 0);
- av_dict_set(&opts, "x265-params", ":rc-lookahead=0", AV_DICT_APPEND);
- av_dict_set(&opts, "x265-params", ":scenecut=0", AV_DICT_APPEND);
- av_dict_set(&opts, "x265-params", ":frame-threads=1", AV_DICT_APPEND);
- } else {
- int bitrate, maxrate, bufsize;
- bitrate = (t->t_props.tp_vbitrate > max_bitrate) ? max_bitrate : t->t_props.tp_vbitrate;
- maxrate = ceil(bitrate * 1.25);
- bufsize = maxrate * 3;
-
- tvhdebug(LS_TRANSCODE, "tuning HEVC encoder for ABR rate control, "
- "bitrate: %dkbps, vbv-bufsize: %dkbits, vbv-maxrate: %dkbps",
- bitrate, bufsize, maxrate);
-
- // this is the same as setting --bitrate=bitrate
- octx->bit_rate = bitrate * 1000;
-
- av_dict_set(&opts, "x265-params", "vbv-bufsize=", 0);
- av_dict_set_int__(&opts, "x265-params", bufsize, AV_DICT_APPEND);
- av_dict_set(&opts, "x265-params", ":vbv-maxrate=", AV_DICT_APPEND);
- av_dict_set_int__(&opts, "x265-params", maxrate, AV_DICT_APPEND);
- av_dict_set(&opts, "x265-params", ":strict-cbr=1", AV_DICT_APPEND);
- }
- // reduce key frame interface for live streaming
- av_dict_set(&opts, "x265-params", ":keyint=49:min-keyint=15", AV_DICT_APPEND);
-
- break;
-
- default:
- break;
- }
-
- if (avcodec_open2(octx, ocodec, &opts) < 0) {
- tvherror(LS_TRANSCODE, "%04X: Unable to open %s encoder",
- shortid(t), ocodec->name);
- transcoder_stream_invalidate(ts);
- goto cleanup;
- }
-
- if (create_video_filter(vs, t, ictx, octx)) {
- tvherror(LS_TRANSCODE, "%04X: Video filter creation failed",
- shortid(t));
- transcoder_stream_invalidate(ts);
- goto cleanup;
- }
- }
-
- /* push decoded frame into filter chain */
- if (av_buffersrc_add_frame(vs->flt_bufsrcctx, vs->vid_dec_frame) < 0) {
- tvherror(LS_TRANSCODE, "%04X: filter input error", shortid(t));
- transcoder_stream_invalidate(ts);
- goto cleanup;
- }
-
- /* and pull out a filtered frame */
- got_output = 0;
- while (1) {
- ret = av_buffersink_get_frame(vs->flt_bufsinkctx, vs->vid_enc_frame);
- if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
- break;
- if (ret < 0) {
- tvherror(LS_TRANSCODE, "%04X: filter output error", shortid(t));
- transcoder_stream_invalidate(ts);
- goto cleanup;
- }
-
- vs->vid_enc_frame->format = octx->pix_fmt;
- vs->vid_enc_frame->width = octx->width;
- vs->vid_enc_frame->height = octx->height;
-
- vs->vid_enc_frame->pkt_pts = vs->vid_dec_frame->pkt_pts;
- vs->vid_enc_frame->pkt_dts = vs->vid_dec_frame->pkt_dts;
-
- if (vs->vid_dec_frame->reordered_opaque != AV_NOPTS_VALUE)
- vs->vid_enc_frame->pts = vs->vid_dec_frame->reordered_opaque;
-
- else if (ictx->coded_frame && ictx->coded_frame->pts != AV_NOPTS_VALUE)
- vs->vid_enc_frame->pts = vs->vid_dec_frame->pts;
-
- ret = avcodec_encode_video2(octx, &packet2, vs->vid_enc_frame, &got_output);
- if (ret < 0) {
- tvherror(LS_TRANSCODE, "%04X: Error encoding frame", shortid(t));
- transcoder_stream_invalidate(ts);
- goto cleanup;
- }
- av_frame_unref(vs->vid_enc_frame);
- }
-
- if (got_output)
- send_video_packet(t, ts, pkt, &packet2, octx);
-
- cleanup:
- if (got_ref)
- av_frame_unref(vs->vid_dec_frame);
-
- av_free_packet(&packet2);
-
- av_free_packet(&packet);
-
- if(opts)
- av_dict_free(&opts);
-
- pkt_ref_dec(pkt);
-}
-
-
-/**
- *
- */
-static void
-transcoder_packet(transcoder_t *t, th_pkt_t *pkt)
-{
- transcoder_stream_t *ts;
- streaming_message_t *sm;
-
- LIST_FOREACH(ts, &t->t_stream_list, ts_link) {
- if (pkt->pkt_componentindex == ts->ts_index) {
- if (pkt->pkt_payload) {
- ts->ts_handle_pkt(t, ts, pkt);
- } else {
- sm = streaming_msg_create_pkt(pkt);
- streaming_target_deliver2(ts->ts_target, sm);
- pkt_ref_dec(pkt);
- }
- return;
- }
- }
- pkt_ref_dec(pkt);
-}
-
-
-/**
- *
- */
-static void
-transcoder_destroy_stream(transcoder_t *t, transcoder_stream_t *ts)
-{
- if (ts->ts_input_gh)
- pktbuf_ref_dec(ts->ts_input_gh);
- free(ts);
-}
-
-
-/**
- *
- */
-static int
-transcoder_init_stream(transcoder_t *t, streaming_start_component_t *ssc)
-{
- transcoder_stream_t *ts = calloc(1, sizeof(transcoder_stream_t));
-
- ts->ts_index = ssc->ssc_index;
- ts->ts_type = ssc->ssc_type;
- ts->ts_target = t->t_output;
- ts->ts_handle_pkt = transcoder_stream_packet;
- ts->ts_destroy = transcoder_destroy_stream;
-
- LIST_INSERT_HEAD(&t->t_stream_list, ts, ts_link);
-
- if(ssc->ssc_gh) {
- pktbuf_ref_inc(ssc->ssc_gh);
- ts->ts_input_gh = ssc->ssc_gh;
- pktbuf_ref_inc(ssc->ssc_gh);
- }
-
- tvhinfo(LS_TRANSCODE, "%04X: %d:%s ==> Passthrough",
- shortid(t), ssc->ssc_index,
- streaming_component_type2txt(ssc->ssc_type));
-
- return 1;
-}
-
-
-/**
- *
- */
-static void
-transcoder_destroy_subtitle(transcoder_t *t, transcoder_stream_t *ts)
-{
- subtitle_stream_t *ss = (subtitle_stream_t*)ts;
-
- if(ss->sub_ictx) {
- av_freep(&ss->sub_ictx->extradata);
- ss->sub_ictx->extradata_size = 0;
- avcodec_close(ss->sub_ictx);
- av_free(ss->sub_ictx);
- }
-
- if(ss->sub_octx) {
- avcodec_close(ss->sub_octx);
- av_free(ss->sub_octx);
- }
-
- transcoder_destroy_stream(t, ts);
-}
-
-
-/**
- *
- */
-static int
-transcoder_init_subtitle(transcoder_t *t, streaming_start_component_t *ssc)
-{
- subtitle_stream_t *ss;
- AVCodec *icodec, *ocodec;
- transcoder_props_t *tp = &t->t_props;
- int sct;
-
- if (tp->tp_scodec[0] == '\0')
- return 0;
-
- else if (!strcmp(tp->tp_scodec, "copy"))
- return transcoder_init_stream(t, ssc);
-
- else if (!(icodec = transcoder_get_decoder(t, ssc->ssc_type)))
- return transcoder_init_stream(t, ssc);
-
- else if (!(ocodec = transcoder_get_encoder(t, tp->tp_scodec)))
- return transcoder_init_stream(t, ssc);
-
- sct = codec_id2streaming_component_type(ocodec->id);
-
- if (sct == ssc->ssc_type)
- return transcoder_init_stream(t, ssc);
-
- ss = calloc(1, sizeof(subtitle_stream_t));
-
- ss->ts_index = ssc->ssc_index;
- ss->ts_type = sct;
- ss->ts_target = t->t_output;
- ss->ts_handle_pkt = transcoder_stream_subtitle;
- ss->ts_destroy = transcoder_destroy_subtitle;
- if (ssc->ssc_gh) {
- ss->ts_input_gh = ssc->ssc_gh;
- pktbuf_ref_inc(ssc->ssc_gh);
- }
-
- ss->sub_icodec = icodec;
- ss->sub_ocodec = ocodec;
-
- ss->sub_ictx = avcodec_alloc_context3_tvh(icodec);
- ss->sub_octx = avcodec_alloc_context3_tvh(ocodec);
-
- LIST_INSERT_HEAD(&t->t_stream_list, (transcoder_stream_t*)ss, ts_link);
-
- tvhinfo(LS_TRANSCODE, "%04X: %d:%s ==> %s (%s)",
- shortid(t), ssc->ssc_index,
- streaming_component_type2txt(ssc->ssc_type),
- streaming_component_type2txt(ss->ts_type),
- ocodec->name);
-
- ssc->ssc_type = sct;
- ssc->ssc_gh = NULL;
-
- return 1;
-}
-
-
-/**
- *
- */
-static void
-transcoder_destroy_audio(transcoder_t *t, transcoder_stream_t *ts)
-{
- audio_stream_t *as = (audio_stream_t*)ts;
-
- if(as->aud_ictx) {
- av_freep(&as->aud_ictx->extradata);
- as->aud_ictx->extradata_size = 0;
- avcodec_close(as->aud_ictx);
- av_free(as->aud_ictx);
- }
-
- if(as->aud_octx) {
- avcodec_close(as->aud_octx);
- av_free(as->aud_octx);
- }
-
- if ((as->resample_context) && as->resample_is_open )
- avresample_close(as->resample_context);
- avresample_free(&as->resample_context);
-
- av_audio_fifo_free(as->fifo);
-
- transcoder_destroy_stream(t, ts);
-}
-
-
-/**
- *
- */
-static int
-transcoder_init_audio(transcoder_t *t, streaming_start_component_t *ssc)
-{
- audio_stream_t *as;
- transcoder_stream_t *ts;
- AVCodec *icodec, *ocodec;
- transcoder_props_t *tp = &t->t_props;
- int sct;
-
- if (tp->tp_acodec[0] == '\0')
- return 0;
-
- else if (!strcmp(tp->tp_acodec, "copy"))
- return transcoder_init_stream(t, ssc);
-
- else if (!(icodec = transcoder_get_decoder(t, ssc->ssc_type)))
- return transcoder_init_stream(t, ssc);
-
- else if (!(ocodec = transcoder_get_encoder(t, tp->tp_acodec)))
- return transcoder_init_stream(t, ssc);
-
- LIST_FOREACH(ts, &t->t_stream_list, ts_link)
- if (SCT_ISAUDIO(ts->ts_type))
- return 0;
-
- sct = codec_id2streaming_component_type(ocodec->id);
-
- // Don't transcode to identical output codec unless the streaming profile specifies a bitrate limiter.
- if (sct == ssc->ssc_type && t->t_props.tp_abitrate < 16) {
- return transcoder_init_stream(t, ssc);
- }
-
- as = calloc(1, sizeof(audio_stream_t));
-
- as->ts_index = ssc->ssc_index;
- as->ts_type = sct;
- as->ts_target = t->t_output;
- as->ts_handle_pkt = transcoder_stream_audio;
- as->ts_destroy = transcoder_destroy_audio;
- if (ssc->ssc_gh) {
- as->ts_input_gh = ssc->ssc_gh;
- pktbuf_ref_inc(ssc->ssc_gh);
- }
-
- as->aud_icodec = icodec;
- as->aud_ocodec = ocodec;
-
- as->aud_ictx = avcodec_alloc_context3_tvh(icodec);
- as->aud_octx = avcodec_alloc_context3_tvh(ocodec);
-
- LIST_INSERT_HEAD(&t->t_stream_list, (transcoder_stream_t*)as, ts_link);
-
- tvhinfo(LS_TRANSCODE, "%04X: %d:%s ==> %s (%s)",
- shortid(t), ssc->ssc_index,
- streaming_component_type2txt(ssc->ssc_type),
- streaming_component_type2txt(as->ts_type),
- ocodec->name);
-
- ssc->ssc_type = sct;
- ssc->ssc_gh = NULL;
-
- if(tp->tp_channels > 0)
- as->aud_channels = tp->tp_channels;
- if(tp->tp_abitrate > 0)
- as->aud_bitrate = tp->tp_abitrate * 1000;
-
- as->resample_context = NULL;
- as->fifo = NULL;
- as->resample = 0;
-
- return 1;
-}
-
-
-/**
- *
- */
-static void
-transcoder_destroy_video(transcoder_t *t, transcoder_stream_t *ts)
-{
- video_stream_t *vs = (video_stream_t*)ts;
-
- if(vs->vid_ictx) {
- av_freep(&vs->vid_ictx->extradata);
- vs->vid_ictx->extradata_size = 0;
- avcodec_close(vs->vid_ictx);
- av_free(vs->vid_ictx);
- }
-
- if(vs->vid_octx) {
- avcodec_close(vs->vid_octx);
- av_free(vs->vid_octx);
- }
-
- if(vs->vid_dec_frame)
- av_free(vs->vid_dec_frame);
-
- if(vs->vid_enc_frame)
- av_free(vs->vid_enc_frame);
-
- if (vs->vid_first_pkt)
- pkt_ref_dec(vs->vid_first_pkt);
-
- if (vs->flt_graph) {
- avfilter_graph_free(&vs->flt_graph);
- vs->flt_graph = NULL;
- }
-
- transcoder_destroy_stream(t, ts);
-}
-
-
-/**
- *
- */
-static int
-transcoder_init_video(transcoder_t *t, streaming_start_component_t *ssc)
-{
- video_stream_t *vs;
- AVCodec *icodec, *ocodec;
- transcoder_props_t *tp = &t->t_props;
- int sct;
- char *str, *token, *saveptr, codec_list[sizeof(tp->tp_src_vcodec)];
- int codec_match=0;
-
- strncpy(codec_list, tp->tp_src_vcodec, sizeof(tp->tp_src_vcodec)-1);
-
- tvhtrace(LS_TRANSCODE, "src_vcodec=\"%s\" ssc_type=%d (%s)\n",
- tp->tp_src_vcodec,
- ssc->ssc_type,
- streaming_component_type2txt(ssc->ssc_type));
-
- if (codec_list[0] != '\0') {
- for (str=codec_list; ; str = NULL) {
- token = strtok_r(str," ,|;" , &saveptr);
- if (token == NULL)
- break; //no match found, use profile settings
- if(!strcasecmp(token, streaming_component_type2txt(ssc->ssc_type))) { //match found
- codec_match=1;
- break;
- }
- }
- if (!codec_match)
- return transcoder_init_stream(t, ssc); //copy codec
- }
-
-
- if (tp->tp_vcodec[0] == '\0')
- return 0;
-
- else if (!strcmp(tp->tp_vcodec, "copy"))
- return transcoder_init_stream(t, ssc);
-
- else if (!(icodec = transcoder_get_decoder(t, ssc->ssc_type)))
- return transcoder_init_stream(t, ssc);
-
- else if (!(ocodec = transcoder_get_encoder(t, tp->tp_vcodec)))
- return transcoder_init_stream(t, ssc);
-
- sct = codec_id2streaming_component_type(ocodec->id);
-
- vs = calloc(1, sizeof(video_stream_t));
-
- vs->ts_index = ssc->ssc_index;
- vs->ts_type = sct;
- vs->ts_target = t->t_output;
- vs->ts_handle_pkt = transcoder_stream_video;
- vs->ts_destroy = transcoder_destroy_video;
- if (ssc->ssc_gh) {
- vs->ts_input_gh = ssc->ssc_gh;
- pktbuf_ref_inc(ssc->ssc_gh);
- }
-
- vs->vid_icodec = icodec;
- vs->vid_ocodec = ocodec;
-
- vs->vid_ictx = avcodec_alloc_context3_tvh(icodec);
- vs->vid_octx = avcodec_alloc_context3_tvh(ocodec);
-
- if (t->t_props.tp_nrprocessors)
- vs->vid_octx->thread_count = t->t_props.tp_nrprocessors;
-
- vs->vid_dec_frame = av_frame_alloc();
- vs->vid_enc_frame = av_frame_alloc();
-
- av_frame_unref(vs->vid_dec_frame);
- av_frame_unref(vs->vid_enc_frame);
-
- vs->flt_graph = NULL; /* allocated in packet processor */
-
- LIST_INSERT_HEAD(&t->t_stream_list, (transcoder_stream_t*)vs, ts_link);
-
-
- if(tp->tp_resolution > 0) {
- vs->vid_height = MIN(tp->tp_resolution, ssc->ssc_height);
- vs->vid_height += vs->vid_height & 1; /* Must be even */
-
- double aspect = (double)ssc->ssc_width / ssc->ssc_height;
- vs->vid_width = vs->vid_height * aspect;
- vs->vid_width += vs->vid_width & 1; /* Must be even */
- } else {
- vs->vid_height = ssc->ssc_height;
- vs->vid_width = ssc->ssc_width;
- }
-
- tvhinfo(LS_TRANSCODE, "%04X: %d:%s %dx%d ==> %s %dx%d (%s)",
- shortid(t),
- ssc->ssc_index,
- streaming_component_type2txt(ssc->ssc_type),
- ssc->ssc_width,
- ssc->ssc_height,
- streaming_component_type2txt(vs->ts_type),
- vs->vid_width,
- vs->vid_height,
- ocodec->name);
-
- ssc->ssc_type = sct;
- ssc->ssc_width = vs->vid_width;
- ssc->ssc_height = vs->vid_height;
- ssc->ssc_gh = NULL;
-
- return 1;
-}
-
-
-/**
- * Figure out how many streams we will use.
- */
-static int
-transcoder_calc_stream_count(transcoder_t *t, streaming_start_t *ss) {
- int i = 0;
- int video = 0;
- int audio = 0;
- int subtitle = 0;
- streaming_start_component_t *ssc = NULL;
-
- for (i = 0; i < ss->ss_num_components; i++) {
- ssc = &ss->ss_components[i];
-
- if (ssc->ssc_disabled)
- continue;
-
- if (SCT_ISVIDEO(ssc->ssc_type)) {
- if (t->t_props.tp_vcodec[0] == '\0')
- video = 0;
- else if (!strcmp(t->t_props.tp_vcodec, "copy"))
- video++;
- else
- video = 1;
-
- } else if (SCT_ISAUDIO(ssc->ssc_type)) {
- if (t->t_props.tp_acodec[0] == '\0')
- audio = 0;
- else if (!strcmp(t->t_props.tp_acodec, "copy"))
- audio++;
- else
- audio = 1;
-
- } else if (SCT_ISSUBTITLE(ssc->ssc_type)) {
- if (t->t_props.tp_scodec[0] == '\0')
- subtitle = 0;
- else if (!strcmp(t->t_props.tp_scodec, "copy"))
- subtitle++;
- else
- subtitle = 1;
- }
- }
-
- tvhtrace(LS_TRANSCODE, "%04X: transcoder_calc_stream_count=%d (video=%d, audio=%d, subtitle=%d)",
- shortid(t), (video + audio + subtitle), video, audio, subtitle);
-
-
- return (video + audio + subtitle);
-}
-
-
-/**
- *
- */
-static streaming_start_t *
-transcoder_start(transcoder_t *t, streaming_start_t *src)
-{
- int i, j, n, rc;
- streaming_start_t *ss;
- transcoder_props_t *tp = &t->t_props;
- char* requested_lang;
-
- n = transcoder_calc_stream_count(t, src);
- ss = calloc(1, (sizeof(streaming_start_t) +
- sizeof(streaming_start_component_t) * n));
-
- ss->ss_refcount = 1;
- ss->ss_num_components = n;
- ss->ss_pcr_pid = src->ss_pcr_pid;
- ss->ss_pmt_pid = src->ss_pmt_pid;
- service_source_info_copy(&ss->ss_si, &src->ss_si);
-
- requested_lang = tp->tp_language;
-
- if (requested_lang[0] != '\0')
- {
- for (i = 0; i < src->ss_num_components; i++) {
- streaming_start_component_t *ssc_src = &src->ss_components[i];
- if (SCT_ISAUDIO(ssc_src->ssc_type) && !strcmp(tp->tp_language, ssc_src->ssc_lang))
- break;
- }
-
- if (i == src->ss_num_components)
- {
- tvhinfo(LS_TRANSCODE, "Could not find requestd lang [%s] in stream, using first one", tp->tp_language);
- requested_lang[0] = '\0';
- }
- }
-
- for (i = j = 0; i < src->ss_num_components && j < n; i++) {
- streaming_start_component_t *ssc_src = &src->ss_components[i];
- streaming_start_component_t *ssc = &ss->ss_components[j];
-
- if (ssc_src->ssc_disabled)
- continue;
-
- *ssc = *ssc_src;
-
- if (SCT_ISVIDEO(ssc->ssc_type))
- rc = transcoder_init_video(t, ssc);
- else if (SCT_ISAUDIO(ssc->ssc_type) && (requested_lang[0] == '\0' || !strcmp(requested_lang, ssc->ssc_lang)))
- rc = transcoder_init_audio(t, ssc);
- else if (SCT_ISSUBTITLE(ssc->ssc_type))
- rc = transcoder_init_subtitle(t, ssc);
- else
- rc = 0;
-
- if(!rc)
- tvhinfo(LS_TRANSCODE, "%04X: %d:%s ==> Filtered",
- shortid(t), ssc->ssc_index,
- streaming_component_type2txt(ssc->ssc_type));
- else
- j++;
- }
-
- return ss;
-}
-
-
-/**
- *
- */
-static void
-transcoder_stop(transcoder_t *t)
-{
- transcoder_stream_t *ts;
-
- while ((ts = LIST_FIRST(&t->t_stream_list))) {
- LIST_REMOVE(ts, ts_link);
-
- if (ts->ts_destroy)
- ts->ts_destroy(t, ts);
- }
-}
-
-
-/**
- *
- */
-static void
-transcoder_input(void *opaque, streaming_message_t *sm)
-{
- transcoder_t *t = opaque;
- streaming_start_t *ss;
-
- switch (sm->sm_type) {
- case SMT_PACKET:
- transcoder_packet(t, sm->sm_data);
- sm->sm_data = NULL;
- streaming_msg_free(sm);
- break;
-
- case SMT_START:
- transcoder_stop(t);
- ss = transcoder_start(t, sm->sm_data);
- streaming_start_unref(sm->sm_data);
- sm->sm_data = ss;
-
- streaming_target_deliver2(t->t_output, sm);
- break;
-
- case SMT_STOP:
- transcoder_stop(t);
- /* Fallthrough */
-
- case SMT_GRACE:
- case SMT_SPEED:
- case SMT_SKIP:
- case SMT_TIMESHIFT_STATUS:
- case SMT_EXIT:
- case SMT_SERVICE_STATUS:
- case SMT_SIGNAL_STATUS:
- case SMT_DESCRAMBLE_INFO:
- case SMT_NOSTART:
- case SMT_NOSTART_WARN:
- case SMT_MPEGTS:
- streaming_target_deliver2(t->t_output, sm);
- break;
- }
-}
-
-static htsmsg_t *
-transcoder_input_info(void *opaque, htsmsg_t *list)
-{
- transcoder_t *t = opaque;
- streaming_target_t *st = t->t_output;
- htsmsg_add_str(list, NULL, "transcoder input");
- return st->st_ops.st_info(st->st_opaque, list);;
-}
-
-static streaming_ops_t transcoder_input_ops = {
- .st_cb = transcoder_input,
- .st_info = transcoder_input_info
-};
-
-
-
-/**
- *
- */
-streaming_target_t *
-transcoder_create(streaming_target_t *output)
-{
- static uint32_t transcoder_id = 0;
- transcoder_t *t = calloc(1, sizeof(transcoder_t));
-
- t->t_id = ++transcoder_id;
- if (!t->t_id) t->t_id = ++transcoder_id;
- t->t_output = output;
-
- streaming_target_init(&t->t_input, &transcoder_input_ops, t, 0);
-
- return &t->t_input;
-}
-
-
-/**
- *
- */
-void
-transcoder_set_properties(streaming_target_t *st,
- transcoder_props_t *props)
-{
- transcoder_t *t = (transcoder_t *)st;
- transcoder_props_t *tp = &t->t_props;
-
- strncpy(tp->tp_vcodec, props->tp_vcodec, sizeof(tp->tp_vcodec)-1);
- strncpy(tp->tp_vcodec_preset, props->tp_vcodec_preset, sizeof(tp->tp_vcodec_preset)-1);
- strncpy(tp->tp_acodec, props->tp_acodec, sizeof(tp->tp_acodec)-1);
- strncpy(tp->tp_scodec, props->tp_scodec, sizeof(tp->tp_scodec)-1);
- tp->tp_channels = props->tp_channels;
- tp->tp_vbitrate = props->tp_vbitrate;
- tp->tp_abitrate = props->tp_abitrate;
- tp->tp_resolution = props->tp_resolution;
-
- memcpy(tp->tp_language, props->tp_language, 4);
-
- strncpy(tp->tp_src_vcodec, props->tp_src_vcodec, sizeof(tp->tp_src_vcodec)-1);
-}
-
-
-/**
- *
- */
-void
-transcoder_destroy(streaming_target_t *st)
-{
- transcoder_t *t = (transcoder_t *)st;
-
- transcoder_stop(t);
- free(t);
-}
-
-
-/**
- *
- */
-htsmsg_t *
-transcoder_get_capabilities(int experimental)
-{
- AVCodec *p = NULL;
- streaming_component_type_t sct;
- htsmsg_t *array = htsmsg_create_list(), *m;
- char buf[128];
-
- while ((p = av_codec_next(p))) {
-
- if (!libav_is_encoder(p))
- continue;
-
- if (!WORKING_ENCODER(p->id))
- continue;
-
- if (((p->capabilities & CODEC_CAP_EXPERIMENTAL) && !experimental) ||
- (p->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)) {
- continue;
- }
-
- sct = codec_id2streaming_component_type(p->id);
- if (sct == SCT_NONE || sct == SCT_UNKNOWN)
- continue;
-
- m = htsmsg_create_map();
- htsmsg_add_s32(m, "type", sct);
- htsmsg_add_u32(m, "id", p->id);
- htsmsg_add_str(m, "name", p->name);
- snprintf(buf, sizeof(buf), "%s%s",
- p->long_name ?: "",
- (p->capabilities & CODEC_CAP_EXPERIMENTAL) ?
- " (Experimental)" : "");
- if (buf[0] != '\0')
- htsmsg_add_str(m, "long_name", buf);
- htsmsg_add_msg(array, NULL, m);
- }
- return array;
-}
-
-
-/*
- *
- */
-void transcoding_init(void)
-{
-}
+++ /dev/null
-/**
- * Transcoding
- * Copyright (C) 2013 John Törnblom
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#pragma once
-
-#include "tvheadend.h"
-#include "htsmsg.h"
-
-typedef struct transcoder_prop {
- char tp_vcodec[32];
- char tp_vcodec_preset[32];
- char tp_acodec[32];
- char tp_scodec[32];
-
- int8_t tp_channels;
- int32_t tp_vbitrate;
- int32_t tp_abitrate;
- char tp_language[4];
- int32_t tp_resolution;
-
- long tp_nrprocessors;
- char tp_src_vcodec[128];
-} transcoder_props_t;
-
-extern uint32_t transcoding_enabled;
-
-streaming_target_t *transcoder_create (streaming_target_t *output);
-void transcoder_destroy(streaming_target_t *tr);
-
-htsmsg_t *transcoder_get_capabilities(int experimental);
-void transcoder_set_properties (streaming_target_t *tr,
- transcoder_props_t *prop);
-
-
-void transcoding_init(void);
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) {
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;
#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"
}
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;
}
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;
* 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 )
{
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 =
.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
},
}
};
-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)
*/
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;
}
{
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);
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;
{
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 *
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
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)) {
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 */
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;
}
/* 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)
} 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 );
/**
*
*/
-static void
+static void
streaming_queue_deliver(void *opauqe, streaming_message_t *sm)
{
streaming_queue_t *sq = opauqe;
switch(code) {
case SM_CODE_OK:
return N_("OK");
-
+
case SM_CODE_SOURCE_RECONFIGURED:
return N_("Source reconfigured");
case SM_CODE_BAD_SOURCE:
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);
{ "HEVC", SCT_HEVC },
{ "VP9", SCT_VP9 },
{ "HBBTV", SCT_HBBTV },
+ { "THEORA", SCT_THEORA },
+ { "OPUS", SCT_OPUS },
};
/**
--- /dev/null
+/*
+ * tvheadend - Codec Profiles
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef TVH_TRANSCODING_CODEC_H__
+#define TVH_TRANSCODING_CODEC_H__
+
+
+#include "tvheadend.h"
+#include "idnode.h"
+#include "streaming.h"
+#include "libav.h"
+
+#include <libavcodec/avcodec.h>
+
+
+#define tvh_ssc_t streaming_start_component_t
+#define tvh_sct_t streaming_component_type_t
+
+
+struct tvh_codec_t;
+typedef struct tvh_codec_t TVHCodec;
+
+struct tvh_codec_profile_t;
+typedef struct tvh_codec_profile_t TVHCodecProfile;
+
+
+/* codec_profile_class ====================================================== */
+
+typedef int (*codec_profile_setup_meth) (TVHCodecProfile *, tvh_ssc_t *);
+typedef int (*codec_profile_is_copy_meth) (TVHCodecProfile *, tvh_ssc_t *);
+typedef int (*codec_profile_open_meth) (TVHCodecProfile *, AVDictionary **);
+
+typedef struct {
+ idclass_t idclass;
+ codec_profile_setup_meth setup;
+ codec_profile_is_copy_meth is_copy;
+ codec_profile_open_meth open;
+} codec_profile_class_t;
+
+
+/* TVHCodec ================================================================= */
+
+typedef struct tvh_codec_t {
+ const char *name;
+ size_t size;
+ const codec_profile_class_t *idclass;
+ AVCodec *codec;
+ const AVProfile *profiles;
+ SLIST_ENTRY(tvh_codec_t) link;
+} TVHCodec;
+
+SLIST_HEAD(TVHCodecs, tvh_codec_t);
+extern struct TVHCodecs tvh_codecs;
+
+const idclass_t *
+tvh_codec_get_class(TVHCodec *self);
+
+const char *
+tvh_codec_get_name(TVHCodec *self);
+
+const char *
+tvh_codec_get_title(TVHCodec *self);
+
+
+/* TVHCodecProfile ========================================================== */
+
+extern const codec_profile_class_t codec_profile_class;
+
+typedef struct tvh_codec_profile_t {
+ idnode_t idnode;
+ TVHCodec *codec;
+ const char *name;
+ const char *description;
+ const char *codec_name;
+ double bit_rate;
+ double qscale;
+ int profile;
+ LIST_ENTRY(tvh_codec_profile_t) link;
+} TVHCodecProfile;
+
+LIST_HEAD(TVHCodecProfiles, tvh_codec_profile_t);
+extern struct TVHCodecProfiles tvh_codec_profiles;
+
+int
+tvh_codec_profile_create(htsmsg_t *conf, const char *uuid, int save);
+
+const char *
+tvh_codec_profile_get_status(TVHCodecProfile *self);
+
+const char *
+tvh_codec_profile_get_name(TVHCodecProfile *self);
+
+const char *
+tvh_codec_profile_get_title(TVHCodecProfile *self);
+
+AVCodec *
+tvh_codec_profile_get_avcodec(TVHCodecProfile *self);
+
+
+/* transcode api */
+int
+tvh_codec_profile_is_copy(TVHCodecProfile *self, tvh_ssc_t *ssc); // XXX: not too sure...
+
+int
+tvh_codec_profile_open(TVHCodecProfile *self, AVDictionary **opts);
+
+
+/* video */
+int
+tvh_codec_profile_video_get_hwaccel(TVHCodecProfile *self);
+
+const enum AVPixelFormat *
+tvh_codec_profile_video_get_pix_fmts(TVHCodecProfile *self);
+
+
+/* audio */
+const enum AVSampleFormat *
+tvh_codec_profile_audio_get_sample_fmts(TVHCodecProfile *self);
+
+const int *
+tvh_codec_profile_audio_get_sample_rates(TVHCodecProfile *self);
+
+const uint64_t *
+tvh_codec_profile_audio_get_channel_layouts(TVHCodecProfile *self);
+
+
+/* module level ============================================================= */
+
+TVHCodecProfile *
+codec_find_profile(const char *name);
+
+htsmsg_t *
+codec_get_profiles_list(enum AVMediaType media_type);
+
+void
+codec_init(void);
+
+void
+codec_done(void);
+
+
+#endif // TVH_TRANSCODING_CODEC_H__
--- /dev/null
+/*
+ * tvheadend - Codec Profiles
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "internals.h"
+
+
+struct TVHCodecs tvh_codecs;
+
+
+/* encoders ================================================================= */
+
+extern TVHCodec tvh_codec_mpeg2video;
+extern TVHCodec tvh_codec_mp2;
+extern TVHCodec tvh_codec_aac;
+extern TVHCodec tvh_codec_vorbis;
+
+#if ENABLE_LIBX264
+extern TVHCodec tvh_codec_libx264;
+#endif
+#if ENABLE_LIBX265
+extern TVHCodec tvh_codec_libx265;
+#endif
+
+#if ENABLE_LIBVPX
+extern TVHCodec tvh_codec_libvpx_vp8;
+extern TVHCodec tvh_codec_libvpx_vp9;
+#endif
+
+#if ENABLE_LIBTHEORA
+extern TVHCodec tvh_codec_libtheora;
+#endif
+#if ENABLE_LIBVORBIS
+extern TVHCodec tvh_codec_libvorbis;
+#endif
+
+#if ENABLE_LIBFDKAAC
+extern TVHCodec tvh_codec_libfdk_aac;
+#endif
+
+#if ENABLE_LIBOPUS
+extern TVHCodec tvh_codec_libopus;
+#endif
+
+#if ENABLE_VAAPI
+extern TVHCodec tvh_codec_vaapi_h264;
+extern TVHCodec tvh_codec_vaapi_hevc;
+#endif
+
+#if ENABLE_OMX
+extern TVHCodec tvh_codec_omx_h264;
+#endif
+
+
+/* AVCodec ================================================================== */
+
+static enum AVMediaType
+codec_get_type(AVCodec *self)
+{
+ return self->type;
+}
+
+
+static const char *
+codec_get_type_string(AVCodec *self)
+{
+ return av_get_media_type_string(self->type);
+}
+
+
+const char *
+codec_get_title(AVCodec *self)
+{
+ static char codec_title[TVH_TITLE_LEN];
+
+ memset(codec_title, 0, sizeof(codec_title));
+ if (
+ str_snprintf(codec_title, sizeof(codec_title),
+ self->long_name ? "%s: %s%s" : "%s%s%s",
+ self->name, self->long_name ? self->long_name : "",
+ (self->capabilities & CODEC_CAP_EXPERIMENTAL) ? " (Experimental)" : "")
+ ) {
+ return NULL;
+ }
+ return codec_title;
+}
+
+
+/* TVHCodec ================================================================= */
+
+static void
+tvh_codec_video_init(TVHVideoCodec *self, AVCodec *codec)
+{
+ if (!self->pix_fmts) {
+ self->pix_fmts = codec->pix_fmts;
+ }
+}
+
+
+static void
+tvh_codec_audio_init(TVHAudioCodec *self, AVCodec *codec)
+{
+ if (!self->sample_fmts) {
+ self->sample_fmts = codec->sample_fmts;
+ }
+ if (!self->sample_rates) {
+ self->sample_rates = codec->supported_samplerates;
+ }
+ if (!self->channel_layouts) {
+ self->channel_layouts = codec->channel_layouts;
+ }
+}
+
+
+static void
+tvh_codec_init(TVHCodec *self, AVCodec *codec)
+{
+ if (!self->profiles) {
+ self->profiles = codec->profiles;
+ }
+ switch (codec->type) {
+ case AVMEDIA_TYPE_VIDEO:
+ tvh_codec_video_init((TVHVideoCodec *)self, codec);
+ break;
+ case AVMEDIA_TYPE_AUDIO:
+ tvh_codec_audio_init((TVHAudioCodec *)self, codec);
+ break;
+ default:
+ break;
+ }
+}
+
+
+static void
+tvh_codec_register(TVHCodec *self)
+{
+ static const size_t min_size = sizeof(TVHCodecProfile);
+ AVCodec *codec = NULL;
+
+ if (!self->name || self->name[0] == '\0' ||
+ self->size < min_size || !self->idclass) {
+ tvherror(LS_CODEC, "incomplete/wrong definition for '%s' codec",
+ self->name ? self->name : "<missing name>");
+ return;
+ }
+
+ if ((codec = avcodec_find_encoder_by_name(self->name)) &&
+ !(codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)) {
+ tvh_codec_init(self, codec);
+ self->codec = codec; // enabled
+ }
+ idclass_register((idclass_t *)self->idclass);
+ SLIST_INSERT_HEAD(&tvh_codecs, self, link);
+ tvhinfo(LS_CODEC, "'%s' encoder registered", self->name);
+}
+
+
+/* exposed */
+
+const idclass_t *
+tvh_codec_get_class(TVHCodec *self)
+{
+ return (idclass_t *)self->idclass;
+}
+
+
+const char *
+tvh_codec_get_name(TVHCodec *self)
+{
+ return self->name;
+}
+
+
+const char *
+tvh_codec_get_title(TVHCodec *self)
+{
+ return self->codec ? codec_get_title(self->codec) : self->name;
+}
+
+
+enum AVMediaType
+tvh_codec_get_type(TVHCodec *self)
+{
+ return self->codec ? codec_get_type(self->codec) : AVMEDIA_TYPE_UNKNOWN;
+}
+
+
+const char *
+tvh_codec_get_type_string(TVHCodec *self)
+{
+ return self->codec ? codec_get_type_string(self->codec) : "<unknown>";
+}
+
+
+AVCodec *
+tvh_codec_get_codec(TVHCodec *self)
+{
+ return self->codec;
+}
+
+
+int
+tvh_codec_is_enabled(TVHCodec *self)
+{
+ return self->codec ? 1 : 0;
+}
+
+
+TVHCodec *
+tvh_codec_find(const char *name)
+{
+ TVHCodec *codec = NULL;
+
+ SLIST_FOREACH(codec, &tvh_codecs, link) {
+ if (!strcmp(codec->name, name)) {
+ return codec;
+ }
+ }
+ return NULL;
+}
+
+
+void
+tvh_codecs_register()
+{
+ SLIST_INIT(&tvh_codecs);
+ tvh_codec_register(&tvh_codec_mpeg2video);
+ tvh_codec_register(&tvh_codec_mp2);
+ tvh_codec_register(&tvh_codec_aac);
+ tvh_codec_register(&tvh_codec_vorbis);
+
+#if ENABLE_LIBX264
+ tvh_codec_register(&tvh_codec_libx264);
+#endif
+#if ENABLE_LIBX265
+ tvh_codec_register(&tvh_codec_libx265);
+#endif
+
+#if ENABLE_LIBVPX
+ tvh_codec_register(&tvh_codec_libvpx_vp8);
+ tvh_codec_register(&tvh_codec_libvpx_vp9);
+#endif
+
+#if ENABLE_LIBTHEORA
+ tvh_codec_register(&tvh_codec_libtheora);
+#endif
+#if ENABLE_LIBVORBIS
+ tvh_codec_register(&tvh_codec_libvorbis);
+#endif
+
+#if ENABLE_LIBFDKAAC
+ tvh_codec_register(&tvh_codec_libfdk_aac);
+#endif
+
+#if ENABLE_LIBOPUS
+ tvh_codec_register(&tvh_codec_libopus);
+#endif
+
+#if ENABLE_VAAPI
+ tvh_codec_register(&tvh_codec_vaapi_h264);
+ tvh_codec_register(&tvh_codec_vaapi_hevc);
+#endif
+
+#if ENABLE_OMX
+ tvh_codec_register(&tvh_codec_omx_h264);
+#endif
+}
+
+
+void
+tvh_codecs_forget()
+{
+ tvhinfo(LS_CODEC, "forgetting codecs");
+ while (!SLIST_EMPTY(&tvh_codecs)) {
+ SLIST_REMOVE_HEAD(&tvh_codecs, link);
+ }
+}
--- /dev/null
+/*
+ * tvheadend - Codec Profiles
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/codec/internals.h"
+
+
+/* aac ====================================================================== */
+
+static const AVProfile aac_profiles[] = {
+ { FF_PROFILE_AAC_MAIN, "Main" },
+ { FF_PROFILE_AAC_LOW, "LC" },
+ { FF_PROFILE_AAC_LTP, "LTP" },
+ { FF_PROFILE_UNKNOWN },
+};
+
+// see aac_chan_configs in ffmpeg-3.0.2/libavcodec/aacenctab.h
+static const uint64_t aac_channel_layouts[] = {
+ AV_CH_LAYOUT_MONO,
+ AV_CH_LAYOUT_STEREO,
+ AV_CH_LAYOUT_SURROUND,
+ AV_CH_LAYOUT_4POINT0,
+ AV_CH_LAYOUT_5POINT0_BACK,
+ AV_CH_LAYOUT_5POINT1_BACK,
+ AV_CH_LAYOUT_7POINT1_WIDE_BACK,
+ 0
+};
+
+
+typedef struct {
+ TVHAudioCodecProfile;
+ const char *coder;
+} tvh_codec_profile_aac_t;
+
+
+static int
+tvh_codec_profile_aac_open(tvh_codec_profile_aac_t *self, AVDictionary **opts)
+{
+ // bit_rate or global_quality
+ if (self->bit_rate) {
+ AV_DICT_SET_BIT_RATE(opts, self->bit_rate);
+ }
+ else {
+ AV_DICT_SET_GLOBAL_QUALITY(opts, self->qscale, 1);
+ }
+ AV_DICT_SET(opts, "aac_coder", self->coder, 0);
+ return 0;
+}
+
+
+static htsmsg_t *
+codec_profile_aac_class_coder_list(void *obj, const char *lang)
+{
+ static const struct strtab_str tab[] = {
+ {N_("anmr: ANMR method (Not currently recommended)"), "anmr"},
+ {N_("twoloop: Two loop searching method"), "twoloop"},
+ {N_("fast: Constant quantizer (Not recommended)"), "fast"}
+ };
+ return strtab2htsmsg_str(tab, 1, lang);
+}
+
+
+static const codec_profile_class_t codec_profile_aac_class = {
+ {
+ .ic_super = (idclass_t *)&codec_profile_audio_class,
+ .ic_class = "codec_profile_aac",
+ .ic_caption = N_("aac"),
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_DBL,
+ .id = "bit_rate",
+ .name = N_("Bitrate (kb/s) (0=auto)"),
+ .desc = N_("Constant bitrate (CBR) mode."),
+ .group = 3,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(TVHCodecProfile, bit_rate),
+ .def.d = 0,
+ },
+ {
+ .type = PT_DBL,
+ .id = "qscale",
+ .name = N_("Quality (0=auto)"),
+ .desc = N_("Variable bitrate (VBR) mode [0-2]."),
+ .group = 3,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(TVHCodecProfile, qscale),
+ .intextra = INTEXTRA_RANGE(0, 2, 1),
+ .def.d = 0,
+ },
+ {
+ .type = PT_STR,
+ .id = "coder",
+ .name = N_("Coding algorithm"),
+ .desc = N_("Coding algorithm."),
+ .group = 5,
+ .opts = PO_EXPERT,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(tvh_codec_profile_aac_t, coder),
+ .list = codec_profile_aac_class_coder_list,
+ .def.s = "twoloop",
+ },
+ {}
+ }
+ },
+ .open = (codec_profile_open_meth)tvh_codec_profile_aac_open,
+};
+
+
+TVHAudioCodec tvh_codec_aac = {
+ .name = "aac",
+ .size = sizeof(tvh_codec_profile_aac_t),
+ .idclass = &codec_profile_aac_class,
+ .profiles = aac_profiles,
+ .channel_layouts = aac_channel_layouts,
+};
--- /dev/null
+/*
+ * tvheadend - Codec Profiles
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/codec/internals.h"
+
+
+/* libfdk_aac =============================================================== */
+
+typedef struct {
+ TVHAudioCodecProfile;
+ int vbr;
+ int afterburner;
+ int eld_sbr;
+ int signaling;
+} tvh_codec_profile_libfdk_aac_t;
+
+
+static int
+tvh_codec_profile_libfdk_aac_open(tvh_codec_profile_libfdk_aac_t *self,
+ AVDictionary **opts)
+{
+ AV_DICT_SET_FLAGS_GLOBAL_HEADER(opts);
+ // bit_rate or vbr
+ if (self->bit_rate) {
+ AV_DICT_SET_BIT_RATE(opts, self->bit_rate);
+ }
+ else {
+ AV_DICT_SET_INT(opts, "vbr", self->vbr ? self->vbr : 3, 0);
+ }
+ AV_DICT_SET_INT(opts, "afterburner", self->afterburner, 0);
+ AV_DICT_SET_INT(opts, "eld_sbr", self->eld_sbr, 0);
+ AV_DICT_SET_INT(opts, "signaling", self->signaling, 0);
+ return 0;
+}
+
+
+static htsmsg_t *
+codec_profile_libfdk_aac_class_signaling_list(void *obj, const char *lang)
+{
+ static const struct strtab tab[] = {
+ {N_("default"), -1},
+ {N_("implicit: Implicit backwards compatible signaling"), 0},
+ {N_("explicit_sbr: Explicit SBR, implicit PS signaling"), 1},
+ {N_("explicit_hierarchical: Explicit hierarchical signaling"), 2}
+ };
+ return strtab2htsmsg(tab, 1, lang);
+}
+
+
+static const codec_profile_class_t codec_profile_libfdk_aac_class = {
+ {
+ .ic_super = (idclass_t *)&codec_profile_audio_class,
+ .ic_class = "codec_profile_libfdk_aac",
+ .ic_caption = N_("libfdk_aac"),
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_DBL,
+ .id = "bit_rate",
+ .name = N_("Bitrate (kb/s) (0=auto)"),
+ .desc = N_("Constant bitrate (CBR) mode."),
+ .group = 3,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(TVHCodecProfile, bit_rate),
+ .def.d = 0,
+ },
+ {
+ .type = PT_INT,
+ .id = "vbr",
+ .name = N_("Quality (0=auto)"),
+ .desc = N_("Variable bitrate (VBR) mode [0-5]."),
+ .group = 3,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(tvh_codec_profile_libfdk_aac_t, vbr),
+ .intextra = INTEXTRA_RANGE(0, 5, 1),
+ .def.i = 0,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "afterburner",
+ .name = N_("Afterburner (improved quality)"),
+ .desc = N_("Afterburner (improved quality)."),
+ .group = 5,
+ .opts = PO_EXPERT,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(tvh_codec_profile_libfdk_aac_t, afterburner),
+ .def.i = 1,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "eld_sbr",
+ .name = N_("Enable SBR for ELD"),
+ .desc = N_("Enable SBR for ELD."),
+ .group = 5,
+ .opts = PO_EXPERT | PO_HIDDEN,
+ .get_opts = codec_profile_class_profile_get_opts,
+ .off = offsetof(tvh_codec_profile_libfdk_aac_t, eld_sbr),
+ .def.i = 0,
+ },
+ {
+ .type = PT_INT,
+ .id = "signaling",
+ .name = N_("Signaling"),
+ .desc = N_("SBR/PS signaling style."),
+ .group = 5,
+ .opts = PO_EXPERT | PO_HIDDEN,
+ .get_opts = codec_profile_class_profile_get_opts,
+ .off = offsetof(tvh_codec_profile_libfdk_aac_t, signaling),
+ .list = codec_profile_libfdk_aac_class_signaling_list,
+ .def.i = -1,
+ },
+ {}
+ }
+ },
+ .open = (codec_profile_open_meth)tvh_codec_profile_libfdk_aac_open,
+};
+
+
+TVHAudioCodec tvh_codec_libfdk_aac = {
+ .name = "libfdk_aac",
+ .size = sizeof(tvh_codec_profile_libfdk_aac_t),
+ .idclass = &codec_profile_libfdk_aac_class,
+};
--- /dev/null
+/*
+ * tvheadend - Codec Profiles
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/codec/internals.h"
+
+#include <opus/opus_defines.h>
+
+
+/* libopus ================================================================== */
+
+typedef struct {
+ TVHAudioCodecProfile;
+ int vbr;
+ int application;
+ int complexity;
+} tvh_codec_profile_libopus_t;
+
+
+static int
+tvh_codec_profile_libopus_open(tvh_codec_profile_libopus_t *self,
+ AVDictionary **opts)
+{
+ AV_DICT_SET_BIT_RATE(opts, self->bit_rate);
+ AV_DICT_SET_INT(opts, "vbr", self->vbr, 0);
+ AV_DICT_SET_INT(opts, "application", self->application, 0);
+ AV_DICT_SET_INT(opts, "compression_level", self->complexity, 0);
+ return 0;
+}
+
+
+static htsmsg_t *
+codec_profile_libopus_class_vbr_list(void *obj, const char *lang)
+{
+ static const struct strtab tab[] = {
+ {N_("off: Use constant bit rate"), 0},
+ {N_("on: Use variable bit rate"), 1},
+ {N_("constrained: Use constrained VBR"), 2}
+ };
+ return strtab2htsmsg(tab, 1, lang);
+}
+
+
+static htsmsg_t *
+codec_profile_libopus_class_application_list(void *obj, const char *lang)
+{
+ static const struct strtab tab[] = {
+ {N_("voip: Favor improved speech intelligibility"), OPUS_APPLICATION_VOIP},
+ {N_("audio: Favor faithfulness to the input"), OPUS_APPLICATION_AUDIO},
+ {N_("lowdelay: Restrict to only the lowest delay modes"), OPUS_APPLICATION_RESTRICTED_LOWDELAY}
+ };
+ return strtab2htsmsg(tab, 1, lang);
+}
+
+
+static const codec_profile_class_t codec_profile_libopus_class = {
+ {
+ .ic_super = (idclass_t *)&codec_profile_audio_class,
+ .ic_class = "codec_profile_libopus",
+ .ic_caption = N_("libopus"),
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_DBL,
+ .id = "bit_rate",
+ .name = N_("Bitrate (kb/s) (0=auto)"),
+ .desc = N_("Constant bitrate (CBR) mode."),
+ .group = 3,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(TVHCodecProfile, bit_rate),
+ .def.d = 0,
+ },
+ {
+ .type = PT_INT,
+ .id = "vbr",
+ .name = N_("Bitrate mode"),
+ .desc = N_("Bitrate mode."),
+ .group = 3,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(tvh_codec_profile_libopus_t, vbr),
+ .list = codec_profile_libopus_class_vbr_list,
+ .def.i = 0,
+ },
+ {
+ .type = PT_INT,
+ .id = "application",
+ .name = N_("Application"),
+ .desc = N_("Intended application type."),
+ .group = 5,
+ .opts = PO_EXPERT,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(tvh_codec_profile_libopus_t, application),
+ .list = codec_profile_libopus_class_application_list,
+ .def.i = OPUS_APPLICATION_AUDIO,
+ },
+ {
+ .type = PT_INT,
+ .id = "complexity",
+ .name = N_("Encoding algorithm complexity"),
+ .desc = N_("0 gives the fastest encodes but lower quality, "
+ "while 10 gives the highest quality but slowest "
+ "encoding [0-10]."),
+ .group = 5,
+ .opts = PO_EXPERT,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(tvh_codec_profile_libopus_t, complexity),
+ .intextra = INTEXTRA_RANGE(0, 10, 1),
+ .def.i = 10,
+ },
+ {}
+ }
+ },
+ .open = (codec_profile_open_meth)tvh_codec_profile_libopus_open,
+};
+
+
+TVHAudioCodec tvh_codec_libopus = {
+ .name = "libopus",
+ .size = sizeof(tvh_codec_profile_libopus_t),
+ .idclass = &codec_profile_libopus_class,
+};
--- /dev/null
+/*
+ * tvheadend - Codec Profiles
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/codec/internals.h"
+
+
+/* libtheora ================================================================ */
+
+static int
+tvh_codec_profile_libtheora_open(TVHCodecProfile *self, AVDictionary **opts)
+{
+ // bit_rate or global_quality
+ if (self->bit_rate) {
+ AV_DICT_SET_BIT_RATE(opts, self->bit_rate);
+ }
+ else {
+ AV_DICT_SET_GLOBAL_QUALITY(opts, self->qscale, 6);
+ }
+ return 0;
+}
+
+
+static const codec_profile_class_t codec_profile_libtheora_class = {
+ {
+ .ic_super = (idclass_t *)&codec_profile_video_class,
+ .ic_class = "codec_profile_libtheora",
+ .ic_caption = N_("libtheora"),
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_DBL,
+ .id = "bit_rate",
+ .name = N_("Bitrate (kb/s) (0=auto)"),
+ .desc = N_("Constant bitrate (CBR) mode."),
+ .group = 3,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(TVHCodecProfile, bit_rate),
+ .def.d = 0,
+ },
+ {
+ .type = PT_DBL,
+ .id = "qscale",
+ .name = N_("Quality (0=auto)"),
+ .desc = N_("Variable bitrate (VBR) mode [0-10]."),
+ .group = 3,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(TVHCodecProfile, qscale),
+ .intextra = INTEXTRA_RANGE(0, 10, 1),
+ .def.d = 0,
+ },
+ {}
+ }
+ },
+ .open = tvh_codec_profile_libtheora_open,
+};
+
+
+TVHVideoCodec tvh_codec_libtheora = {
+ .name = "libtheora",
+ .size = sizeof(TVHVideoCodecProfile),
+ .idclass = &codec_profile_libtheora_class,
+};
--- /dev/null
+/*
+ * tvheadend - Codec Profiles
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/codec/internals.h"
+
+
+/* libvorbis ================================================================ */
+
+// see libvorbis_setup() in ffmpeg-3.0.2/libavcodec/libvorbisenc.c
+static const uint64_t libvorbis_channel_layouts[] = {
+ AV_CH_LAYOUT_MONO,
+ AV_CH_LAYOUT_STEREO,
+ AV_CH_LAYOUT_SURROUND,
+ AV_CH_LAYOUT_2_2,
+ AV_CH_LAYOUT_QUAD,
+ AV_CH_LAYOUT_5POINT0,
+ AV_CH_LAYOUT_5POINT0_BACK,
+ AV_CH_LAYOUT_5POINT1,
+ AV_CH_LAYOUT_5POINT1_BACK,
+ AV_CH_LAYOUT_6POINT1,
+ AV_CH_LAYOUT_7POINT1,
+ //AV_CH_LAYOUT_HEXADECAGONAL,
+ //AV_CH_LAYOUT_STEREO_DOWNMIX,
+ 0
+};
+
+
+static int
+tvh_codec_profile_libvorbis_open(TVHCodecProfile *self, AVDictionary **opts)
+{
+ // bit_rate or global_quality
+ if (self->bit_rate) {
+ AV_DICT_SET_BIT_RATE(opts, self->bit_rate);
+ }
+ else {
+ AV_DICT_SET_GLOBAL_QUALITY(opts, self->qscale, 5);
+ }
+ return 0;
+}
+
+
+static const codec_profile_class_t codec_profile_libvorbis_class = {
+ {
+ .ic_super = (idclass_t *)&codec_profile_audio_class,
+ .ic_class = "codec_profile_libvorbis",
+ .ic_caption = N_("libvorbis"),
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_DBL,
+ .id = "bit_rate",
+ .name = N_("Bitrate (kb/s) (0=auto)"),
+ .desc = N_("Average bitrate (ABR) mode."),
+ .group = 3,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(TVHCodecProfile, bit_rate),
+ .def.d = 0,
+ },
+ {
+ .type = PT_DBL,
+ .id = "qscale",
+ .name = N_("Quality (0=auto)"),
+ .desc = N_("Variable bitrate (VBR) mode [0-10]."),
+ .group = 3,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(TVHCodecProfile, qscale),
+ .intextra = INTEXTRA_RANGE(0, 10, 1),
+ .def.d = 0,
+ },
+ {}
+ }
+ },
+ .open = tvh_codec_profile_libvorbis_open,
+};
+
+
+TVHAudioCodec tvh_codec_libvorbis = {
+ .name = "libvorbis",
+ .size = sizeof(TVHAudioCodecProfile),
+ .idclass = &codec_profile_libvorbis_class,
+ .channel_layouts = libvorbis_channel_layouts,
+};
--- /dev/null
+/*
+ * tvheadend - Codec Profiles
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/codec/internals.h"
+
+#include <vpx/vp8cx.h>
+
+
+/* libvpx =================================================================== */
+
+typedef struct {
+ TVHVideoCodecProfile;
+ int deadline;
+ int cpu_used;
+ int tune;
+} tvh_codec_profile_libvpx_t;
+
+
+static int
+tvh_codec_profile_libvpx_open(tvh_codec_profile_libvpx_t *self,
+ AVDictionary **opts)
+{
+ AV_DICT_SET_TVH_REQUIRE_META(opts, 0);
+ AV_DICT_SET_BIT_RATE(opts, self->bit_rate ? self->bit_rate : 2560);
+ if (self->crf) {
+ AV_DICT_SET_CRF(opts, self->crf, 10);
+ }
+ AV_DICT_SET_INT(opts, "deadline", self->deadline, 0);
+ AV_DICT_SET_INT(opts, "cpu-used", self->cpu_used, 0);
+ AV_DICT_SET_INT(opts, "tune", self->tune, 0);
+ AV_DICT_SET_INT(opts, "threads", 0, 0);
+ return 0;
+}
+
+
+static htsmsg_t *
+codec_profile_libvpx_class_deadline_list(void *obj, const char *lang)
+{
+ static const struct strtab tab[] = {
+ {N_("best"), VPX_DL_BEST_QUALITY},
+ {N_("good"), VPX_DL_GOOD_QUALITY},
+ {N_("realtime"), VPX_DL_REALTIME}
+ };
+ return strtab2htsmsg(tab, 1, lang);
+}
+
+
+static htsmsg_t *
+codec_profile_libvpx_class_tune_list(void *obj, const char *lang)
+{
+ static const struct strtab tab[] = {
+ {N_("psnr"), VP8_TUNE_PSNR},
+ {N_("ssim"), VP8_TUNE_SSIM}
+ };
+ return strtab2htsmsg(tab, 1, lang);
+}
+
+
+static const codec_profile_class_t codec_profile_libvpx_class = {
+ {
+ .ic_super = (idclass_t *)&codec_profile_video_class,
+ .ic_class = "codec_profile_libvpx",
+ .ic_caption = N_("libvpx"),
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_DBL,
+ .id = "bit_rate",
+ .name = N_("Bitrate (kb/s) (0=auto)"),
+ .desc = N_("Constant bitrate (CBR) mode."),
+ .group = 3,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(TVHCodecProfile, bit_rate),
+ .def.d = 0,
+ },
+ {
+ .type = PT_INT,
+ .id = "crf",
+ .name = N_("Constant Rate Factor (0=auto)"),
+ .desc = N_("Select the quality for constant quality mode [0-63]."),
+ .group = 3,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(TVHVideoCodecProfile, crf),
+ .intextra = INTEXTRA_RANGE(0, 63, 1),
+ .def.i = 0,
+ },
+ {
+ .type = PT_INT,
+ .id = "deadline",
+ .name = N_("Quality"),
+ .desc = N_("Time to spend encoding, in microseconds."),
+ .group = 5,
+ .opts = PO_EXPERT,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(tvh_codec_profile_libvpx_t, deadline),
+ .list = codec_profile_libvpx_class_deadline_list,
+ .def.i = VPX_DL_REALTIME,
+ },
+ {
+ .type = PT_INT,
+ .id = "cpu-used",
+ .name = N_("Speed"),
+ .desc = N_("Quality/Speed ratio modifier."),
+ .group = 5,
+ .opts = PO_EXPERT,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(tvh_codec_profile_libvpx_t, cpu_used),
+ .intextra = INTEXTRA_RANGE(0, 15, 1),
+ .def.i = 8,
+ },
+ {
+ .type = PT_INT,
+ .id = "tune",
+ .name = N_("Tune"),
+ .desc = N_("Tune the encoding to a specific scenario."),
+ .group = 5,
+ .opts = PO_EXPERT,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(tvh_codec_profile_libvpx_t, tune),
+ .list = codec_profile_libvpx_class_tune_list,
+ .def.i = VP8_TUNE_PSNR,
+ },
+ {}
+ }
+ },
+ .open = (codec_profile_open_meth)tvh_codec_profile_libvpx_open,
+};
+
+
+/* libvpx_vp8 =============================================================== */
+
+TVHVideoCodec tvh_codec_libvpx_vp8 = {
+ .name = "libvpx",
+ .size = sizeof(tvh_codec_profile_libvpx_t),
+ .idclass = &codec_profile_libvpx_class,
+};
+
+
+/* libvpx_vp9 =============================================================== */
+
+TVHVideoCodec tvh_codec_libvpx_vp9 = {
+ .name = "libvpx-vp9",
+ .size = sizeof(tvh_codec_profile_libvpx_t),
+ .idclass = &codec_profile_libvpx_class,
+};
--- /dev/null
+/*
+ * tvheadend - Codec Profiles
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/codec/internals.h"
+
+
+/* utils ==================================================================== */
+
+static htsmsg_t *
+strlist2htsmsg(const char * const *values)
+{
+ htsmsg_t *list = NULL, *map = NULL;
+ int i;
+ const char *value = NULL;
+
+ if ((list = htsmsg_create_list())) {
+ for (i = 0; (value = values[i]); i++) {
+ if (!(map = htsmsg_create_map())) {
+ htsmsg_destroy(list);
+ list = NULL;
+ break;
+ }
+ ADD_STR_VAL(list, map, value);
+ }
+ }
+ return list;
+}
+
+
+/* libx26x ================================================================== */
+
+typedef struct {
+ TVHVideoCodecProfile;
+ const char *preset;
+ const char *tune;
+ const char *params;
+} tvh_codec_profile_libx26x_t;
+
+
+static int
+tvh_codec_profile_libx26x_open(tvh_codec_profile_libx26x_t *self,
+ AVDictionary **opts)
+{
+ AV_DICT_SET(opts, "preset", self->preset, 0);
+ AV_DICT_SET(opts, "tune", self->tune, 0);
+ return 0;
+}
+
+
+static const codec_profile_class_t codec_profile_libx26x_class = {
+ {
+ .ic_super = (idclass_t *)&codec_profile_video_class,
+ .ic_class = "codec_profile_libx26x",
+ .ic_caption = N_("libx26x"),
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_DBL,
+ .id = "bit_rate",
+ .name = N_("Bitrate (kb/s) (0=auto)"),
+ .desc = N_("Average bitrate (ABR) mode."),
+ .group = 3,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(TVHCodecProfile, bit_rate),
+ .def.d = 0,
+ },
+ {
+ .type = PT_INT,
+ .id = "crf",
+ .name = N_("Constant Rate Factor (0=auto)"),
+ .desc = N_("Select the quality for constant quality mode [0-51]."),
+ .group = 3,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(TVHVideoCodecProfile, crf),
+ .intextra = INTEXTRA_RANGE(0, 51, 1),
+ .def.i = 0,
+ },
+ {}
+ }
+ },
+ .open = (codec_profile_open_meth)tvh_codec_profile_libx26x_open,
+};
+
+
+/* libx264 ================================================================== */
+
+#if ENABLE_LIBX264
+
+#include <x264.h>
+
+static const AVProfile libx264_profiles[] = {
+ { FF_PROFILE_H264_BASELINE, "Baseline" },
+ { FF_PROFILE_H264_MAIN, "Main" },
+ { FF_PROFILE_H264_HIGH, "High" },
+ { FF_PROFILE_H264_HIGH_10, "High 10" },
+ { FF_PROFILE_H264_HIGH_422, "High 4:2:2" },
+ { FF_PROFILE_H264_HIGH_444, "High 4:4:4" },
+ { FF_PROFILE_UNKNOWN },
+};
+
+
+static int
+tvh_codec_profile_libx264_open(tvh_codec_profile_libx26x_t *self,
+ AVDictionary **opts)
+{
+ // bit_rate or crf
+ if (self->bit_rate) {
+ AV_DICT_SET_BIT_RATE(opts, self->bit_rate);
+ }
+ else {
+ AV_DICT_SET_CRF(opts, self->crf, 15);
+ }
+ if (self->params && strlen(self->params)) {
+ AV_DICT_SET(opts, "x264-params", self->params, 0);
+ }
+ return 0;
+}
+
+
+static htsmsg_t *
+codec_profile_libx264_class_preset_list(void *obj, const char *lang)
+{
+ return strlist2htsmsg(x264_preset_names);
+}
+
+
+static htsmsg_t *
+codec_profile_libx264_class_tune_list(void *obj, const char *lang)
+{
+ return strlist2htsmsg(x264_tune_names);
+}
+
+
+static const codec_profile_class_t codec_profile_libx264_class = {
+ {
+ .ic_super = (idclass_t *)&codec_profile_libx26x_class,
+ .ic_class = "codec_profile_libx264",
+ .ic_caption = N_("libx264"),
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_STR,
+ .id = "preset",
+ .name = N_("Preset"),
+ .desc = N_("Set the encoding preset (cf. x264 --fullhelp)."),
+ .group = 5,
+ .opts = PO_EXPERT,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(tvh_codec_profile_libx26x_t, preset),
+ .list = codec_profile_libx264_class_preset_list,
+ .def.s = "faster",
+ },
+ {
+ .type = PT_STR,
+ .id = "tune",
+ .name = N_("Tune"),
+ .desc = N_("Tune the encoding params (cf. x264 --fullhelp)."),
+ .group = 5,
+ .opts = PO_EXPERT,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(tvh_codec_profile_libx26x_t, tune),
+ .list = codec_profile_libx264_class_tune_list,
+ .def.s = "zerolatency",
+ },
+ {
+ .type = PT_STR,
+ .id = "params",
+ .name = N_("Parameters"),
+ .desc = N_("Override the configuration using a ':' separated "
+ "list of key=value parameters."),
+ .group = 5,
+ .opts = PO_EXPERT,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(tvh_codec_profile_libx26x_t, params),
+ },
+ {}
+ }
+ },
+ .open = (codec_profile_open_meth)tvh_codec_profile_libx264_open,
+};
+
+
+TVHVideoCodec tvh_codec_libx264 = {
+ .name = "libx264",
+ .size = sizeof(tvh_codec_profile_libx26x_t),
+ .idclass = &codec_profile_libx264_class,
+ .profiles = libx264_profiles,
+};
+
+#endif
+
+
+/* libx265 ================================================================== */
+
+#if ENABLE_LIBX265
+
+#include <x265.h>
+
+
+static int
+tvh_codec_profile_libx265_open(tvh_codec_profile_libx26x_t *self,
+ AVDictionary **opts)
+{
+ // bit_rate or crf
+ if (self->bit_rate) {
+ AV_DICT_SET_BIT_RATE(opts, self->bit_rate);
+ }
+ else {
+ AV_DICT_SET_CRF(opts, self->crf, 18);
+ }
+ if (self->params && strlen(self->params)) {
+ AV_DICT_SET(opts, "x265-params", self->params, 0);
+ }
+ return 0;
+}
+
+
+static htsmsg_t *
+codec_profile_libx265_class_preset_list(void *obj, const char *lang)
+{
+ return strlist2htsmsg(x265_preset_names);
+}
+
+
+static htsmsg_t *
+codec_profile_libx265_class_tune_list(void *obj, const char *lang)
+{
+ return strlist2htsmsg(x265_tune_names);
+}
+
+
+static const codec_profile_class_t codec_profile_libx265_class = {
+ {
+ .ic_super = (idclass_t *)&codec_profile_libx26x_class,
+ .ic_class = "codec_profile_libx265",
+ .ic_caption = N_("libx265"),
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_STR,
+ .id = "preset",
+ .name = N_("Preset"),
+ .desc = N_("set the x265 preset."),
+ .group = 5,
+ .opts = PO_EXPERT,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(tvh_codec_profile_libx26x_t, preset),
+ .list = codec_profile_libx265_class_preset_list,
+ .def.s = "superfast",
+ },
+ {
+ .type = PT_STR,
+ .id = "tune",
+ .name = N_("Tune"),
+ .desc = N_("set the x265 tune parameter."),
+ .group = 5,
+ .opts = PO_EXPERT,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(tvh_codec_profile_libx26x_t, tune),
+ .list = codec_profile_libx265_class_tune_list,
+ .def.s = "fastdecode",
+ },
+ {
+ .type = PT_STR,
+ .id = "params",
+ .name = N_("Parameters"),
+ .desc = N_("Override the configuration using a ':' separated "
+ "list of key=value parameters."),
+ .group = 5,
+ .opts = PO_EXPERT,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(tvh_codec_profile_libx26x_t, params),
+ },
+ {}
+ }
+ },
+ .open = (codec_profile_open_meth)tvh_codec_profile_libx265_open,
+};
+
+
+TVHVideoCodec tvh_codec_libx265 = {
+ .name = "libx265",
+ .size = sizeof(tvh_codec_profile_libx26x_t),
+ .idclass = &codec_profile_libx265_class,
+};
+
+#endif
--- /dev/null
+/*
+ * tvheadend - Codec Profiles
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/codec/internals.h"
+
+
+/* omx ====================================================================== */
+
+typedef struct {
+ TVHVideoCodecProfile;
+ const char *libname;
+ const char *libprefix;
+ int zerocopy;
+} tvh_codec_profile_omx_t;
+
+
+static int
+tvh_codec_profile_omx_open(tvh_codec_profile_omx_t *self, AVDictionary **opts)
+{
+ if (self->libname && strlen(self->libname)) {
+ AV_DICT_SET(opts, "omx_libname", self->libname, 0);
+ }
+ if (self->libprefix && strlen(self->libprefix)) {
+ AV_DICT_SET(opts, "omx_libprefix", self->libprefix, 0);
+ }
+ AV_DICT_SET_INT(opts, "zerocopy", self->zerocopy, 0);
+ return 0;
+}
+
+
+static const codec_profile_class_t codec_profile_omx_class = {
+ {
+ .ic_super = (idclass_t *)&codec_profile_video_class,
+ .ic_class = "codec_profile_omx",
+ .ic_caption = N_("omx_h264"),
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_STR,
+ .id = "omx_libname",
+ .name = N_("Library name"),
+ .desc = N_("OpenMAX library name."),
+ .group = 5,
+ .opts = PO_EXPERT,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(tvh_codec_profile_omx_t, libname),
+ },
+ {
+ .type = PT_STR,
+ .id = "omx_libprefix",
+ .name = N_("Library prefix"),
+ .desc = N_("OpenMAX library prefix."),
+ .group = 5,
+ .opts = PO_EXPERT,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(tvh_codec_profile_omx_t, libprefix),
+ },
+ {
+ .type = PT_BOOL,
+ .id = "zerocopy",
+ .name = N_("Zerocopy"),
+ .desc = N_("Try to avoid copying input frames if possible."),
+ .group = 5,
+ .opts = PO_EXPERT,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(tvh_codec_profile_omx_t, zerocopy),
+ .def.i = 0,
+ },
+ {}
+ }
+ },
+ .open = (codec_profile_open_meth)tvh_codec_profile_omx_open,
+};
+
+
+/* h264_omx ================================================================= */
+
+TVHVideoCodec tvh_codec_omx_h264 = {
+ .name = "h264_omx",
+ .size = sizeof(tvh_codec_profile_omx_t),
+ .idclass = &codec_profile_omx_class,
+};
--- /dev/null
+/*
+ * tvheadend - Codec Profiles
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/codec/internals.h"
+
+
+#define AV_DICT_SET_QP(d, v, a) \
+ AV_DICT_SET_INT((d), "qp", (v) ? (v) : (a), AV_DICT_DONT_OVERWRITE)
+
+
+/* vaapi ==================================================================== */
+
+typedef struct {
+ TVHVideoCodecProfile;
+ int qp;
+ int quality;
+} tvh_codec_profile_vaapi_t;
+
+
+static int
+tvh_codec_profile_vaapi_open(tvh_codec_profile_vaapi_t *self,
+ AVDictionary **opts)
+{
+ // pix_fmt
+ AV_DICT_SET_PIX_FMT(opts, self->pix_fmt, AV_PIX_FMT_VAAPI);
+ return 0;
+}
+
+
+static const codec_profile_class_t codec_profile_vaapi_class = {
+ {
+ .ic_super = (idclass_t *)&codec_profile_video_class,
+ .ic_class = "codec_profile_vaapi",
+ .ic_caption = N_("vaapi"),
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_DBL,
+ .id = "bit_rate",
+ .name = N_("Bitrate (kb/s) (0=auto)"),
+ .desc = N_("Target bitrate."),
+ .group = 3,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(TVHCodecProfile, bit_rate),
+ .def.d = 0,
+ },
+ {
+ .type = PT_INT,
+ .id = "qp",
+ .name = N_("Constant QP (0=auto)"),
+ .group = 3,
+ .desc = N_("Fixed QP of P frames [0-52]."),
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(tvh_codec_profile_vaapi_t, qp),
+ .intextra = INTEXTRA_RANGE(0, 52, 1),
+ .def.i = 0,
+ },
+ {}
+ }
+ },
+ .open = (codec_profile_open_meth)tvh_codec_profile_vaapi_open,
+};
+
+
+/* h264_vaapi =============================================================== */
+
+static const AVProfile vaapi_h264_profiles[] = {
+ { FF_PROFILE_H264_BASELINE, "Baseline" },
+ { FF_PROFILE_H264_CONSTRAINED_BASELINE, "Constrained Baseline" },
+ { FF_PROFILE_H264_MAIN, "Main" },
+ { FF_PROFILE_H264_HIGH, "High" },
+ { FF_PROFILE_UNKNOWN },
+};
+
+static int
+tvh_codec_profile_vaapi_h264_open(tvh_codec_profile_vaapi_t *self,
+ AVDictionary **opts)
+{
+ // bit_rate or qp
+ if (self->bit_rate) {
+ AV_DICT_SET_BIT_RATE(opts, self->bit_rate);
+ }
+ else {
+ AV_DICT_SET_QP(opts, self->qp, 20);
+ }
+ AV_DICT_SET_INT(opts, "quality", self->quality, 0);
+ return 0;
+}
+
+
+static const codec_profile_class_t codec_profile_vaapi_h264_class = {
+ {
+ .ic_super = (idclass_t *)&codec_profile_vaapi_class,
+ .ic_class = "codec_profile_vaapi_h264",
+ .ic_caption = N_("vaapi_h264"),
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_INT,
+ .id = "quality",
+ .name = N_("Quality (0=auto)"),
+ .desc = N_("Set encode quality (trades off against speed, "
+ "higher is faster) [0-8]."),
+ .group = 5,
+ .opts = PO_EXPERT,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(tvh_codec_profile_vaapi_t, quality),
+ .intextra = INTEXTRA_RANGE(0, 8, 1),
+ .def.i = 0,
+ },
+ {}
+ }
+ },
+ .open = (codec_profile_open_meth)tvh_codec_profile_vaapi_h264_open,
+};
+
+
+TVHVideoCodec tvh_codec_vaapi_h264 = {
+ .name = "h264_vaapi",
+ .size = sizeof(tvh_codec_profile_vaapi_t),
+ .idclass = &codec_profile_vaapi_h264_class,
+ .profiles = vaapi_h264_profiles,
+};
+
+
+/* hevc_vaapi =============================================================== */
+
+static const AVProfile vaapi_hevc_profiles[] = {
+ { FF_PROFILE_HEVC_MAIN, "Main" },
+ { FF_PROFILE_UNKNOWN },
+};
+
+static int
+tvh_codec_profile_vaapi_hevc_open(tvh_codec_profile_vaapi_t *self,
+ AVDictionary **opts)
+{
+ // bit_rate or qp
+ if (self->bit_rate) {
+ AV_DICT_SET_BIT_RATE(opts, self->bit_rate);
+ }
+ else {
+ AV_DICT_SET_QP(opts, self->qp, 25);
+ }
+ return 0;
+}
+
+
+static const codec_profile_class_t codec_profile_vaapi_hevc_class = {
+ {
+ .ic_super = (idclass_t *)&codec_profile_vaapi_class,
+ .ic_class = "codec_profile_vaapi_hevc",
+ .ic_caption = N_("vaapi_hevc")
+ },
+ .open = (codec_profile_open_meth)tvh_codec_profile_vaapi_hevc_open,
+};
+
+
+TVHVideoCodec tvh_codec_vaapi_hevc = {
+ .name = "hevc_vaapi",
+ .size = sizeof(tvh_codec_profile_vaapi_t),
+ .idclass = &codec_profile_vaapi_hevc_class,
+ .profiles = vaapi_hevc_profiles,
+};
--- /dev/null
+/*
+ * tvheadend - Codec Profiles
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/codec/internals.h"
+
+
+/* mp2 ====================================================================== */
+
+static int
+tvh_codec_profile_mp2_open(TVHCodecProfile *self, AVDictionary **opts)
+{
+ AV_DICT_SET_TVH_REQUIRE_META(opts, 0);
+ // bit_rate
+ if (self->bit_rate) {
+ AV_DICT_SET_BIT_RATE(opts, self->bit_rate);
+ }
+ return 0;
+}
+
+
+static htsmsg_t *
+codec_profile_mp2_class_bit_rate_list(void *obj, const char *lang)
+{
+ // This is a place holder.
+ // The real list is set in src/webui/static/app/codec.js
+ return htsmsg_create_list();
+}
+
+
+static const codec_profile_class_t codec_profile_mp2_class = {
+ {
+ .ic_super = (idclass_t *)&codec_profile_audio_class,
+ .ic_class = "codec_profile_mp2",
+ .ic_caption = N_("mp2"),
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_DBL,
+ .id = "bit_rate",
+ .name = N_("Bitrate (kb/s)"),
+ .desc = N_("Constant bitrate (CBR) mode."),
+ .group = 3,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(TVHCodecProfile, bit_rate),
+ .list = codec_profile_mp2_class_bit_rate_list,
+ .def.d = 0,
+ },
+ {}
+ }
+ },
+ .open = tvh_codec_profile_mp2_open,
+};
+
+
+TVHAudioCodec tvh_codec_mp2 = {
+ .name = "mp2",
+ .size = sizeof(TVHAudioCodecProfile),
+ .idclass = &codec_profile_mp2_class,
+};
--- /dev/null
+/*
+ * tvheadend - Codec Profiles
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/codec/internals.h"
+
+
+/* mpeg2video =============================================================== */
+
+static int
+tvh_codec_profile_mpeg2video_open(TVHCodecProfile *self, AVDictionary **opts)
+{
+ // bit_rate or global_quality
+ if (self->bit_rate) {
+ AV_DICT_SET_BIT_RATE(opts, self->bit_rate);
+ }
+ else {
+ AV_DICT_SET_GLOBAL_QUALITY(opts, self->qscale, 5);
+ }
+ return 0;
+}
+
+
+static const codec_profile_class_t codec_profile_mpeg2video_class = {
+ {
+ .ic_super = (idclass_t *)&codec_profile_video_class,
+ .ic_class = "codec_profile_mpeg2video",
+ .ic_caption = N_("mpeg2video"),
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_DBL,
+ .id = "bit_rate",
+ .name = N_("Bitrate (kb/s) (0=auto)"),
+ .desc = N_("Constant bitrate (CBR) mode."),
+ .group = 3,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(TVHCodecProfile, bit_rate),
+ .def.d = 0,
+ },
+ {
+ .type = PT_DBL,
+ .id = "qscale",
+ .name = N_("Quality (0=auto)"),
+ .desc = N_("Variable bitrate (VBR) mode [0-31]."),
+ .group = 3,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(TVHCodecProfile, qscale),
+ .intextra = INTEXTRA_RANGE(0, 31, 1),
+ .def.d = 0,
+ },
+ {}
+ }
+ },
+ .open = tvh_codec_profile_mpeg2video_open,
+};
+
+
+TVHVideoCodec tvh_codec_mpeg2video = {
+ .name = "mpeg2video",
+ .size = sizeof(TVHVideoCodecProfile),
+ .idclass = &codec_profile_mpeg2video_class,
+};
--- /dev/null
+/*
+ * tvheadend - Codec Profiles
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/codec/internals.h"
+
+
+/* vorbis =================================================================== */
+
+static int
+tvh_codec_profile_vorbis_open(TVHCodecProfile *self, AVDictionary **opts)
+{
+ AV_DICT_SET_GLOBAL_QUALITY(opts, self->qscale, 5);
+ return 0;
+}
+
+
+// see vorbis_encode_init() in ffmpeg-3.0.2/libavcodec/vorbisenc.c
+static const uint64_t vorbis_channel_layouts[] = {
+ AV_CH_LAYOUT_STEREO,
+ 0
+};
+
+
+static const codec_profile_class_t codec_profile_vorbis_class = {
+ {
+ .ic_super = (idclass_t *)&codec_profile_audio_class,
+ .ic_class = "codec_profile_vorbis",
+ .ic_caption = N_("vorbis"),
+ .ic_properties = (const property_t[]){
+ {
+ .type = PT_DBL,
+ .id = "qscale",
+ .name = N_("Quality (0=auto)"),
+ .desc = N_("Variable bitrate (VBR) mode [0-10]."),
+ .group = 3,
+ .get_opts = codec_profile_class_get_opts,
+ .off = offsetof(TVHCodecProfile, qscale),
+ .intextra = INTEXTRA_RANGE(0, 10, 1),
+ .def.d = 0,
+ },
+ {}
+ }
+ },
+ .open = tvh_codec_profile_vorbis_open,
+};
+
+
+TVHAudioCodec tvh_codec_vorbis = {
+ .name = "vorbis",
+ .size = sizeof(TVHAudioCodecProfile),
+ .idclass = &codec_profile_vorbis_class,
+ .channel_layouts = vorbis_channel_layouts,
+};
--- /dev/null
+/*
+ * tvheadend - Codec Profiles
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef TVH_TRANSCODING_CODEC_INTERNALS_H__
+#define TVH_TRANSCODING_CODEC_INTERNALS_H__
+
+
+#include "transcoding/codec.h"
+#include "transcoding/memutils.h"
+
+
+#define AUTO_STR "auto"
+
+#define ADD_ENTRY(l, m, kt, k, vt, v) \
+ do { \
+ htsmsg_add_##kt((m), "key", (k)); \
+ htsmsg_add_##vt((m), "val", (v)); \
+ htsmsg_add_msg((l), NULL, (m)); \
+ } while (0)
+
+#define ADD_VAL(l, m, t, v) ADD_ENTRY(l, m, t, v, t, v)
+
+#define ADD_STR_VAL(l, m, v) ADD_VAL(l, m, str, v)
+#define ADD_S32_VAL(l, m, v) ADD_VAL(l, m, s32, v)
+#define ADD_U32_VAL(l, m, v) ADD_VAL(l, m, u32, v)
+#define ADD_S64_VAL(l, m, v) ADD_VAL(l, m, s64, v)
+
+
+#define _tvh_codec_get_opts(c, a, o, T) \
+ ((c) ? tvh_codec_base_get_opts((c), (o), ((((T *)(c))->a) != NULL)) : (o))
+
+#define tvh_codec_get_opts(c, a, o) \
+ _tvh_codec_get_opts(c, a, o, TVHCodec)
+
+#define tvh_codec_video_get_opts(c, a, o) \
+ _tvh_codec_get_opts(c, a, o, TVHVideoCodec)
+
+#define tvh_codec_audio_get_opts(c, a, o) \
+ _tvh_codec_get_opts(c, a, o, TVHAudioCodec)
+
+
+#define _tvh_codec_get_list(c, a, T, n) \
+ (((c) && tvh_codec_is_enabled((c))) ? tvh_codec##n##get_list_##a(((T *)(c))) : NULL)
+
+#define tvh_codec_get_list(c, a) \
+ _tvh_codec_get_list(c, a, TVHCodec, _)
+
+#define tvh_codec_video_get_list(c, a) \
+ _tvh_codec_get_list(c, a, TVHVideoCodec, _video_)
+
+#define tvh_codec_audio_get_list(c, a) \
+ _tvh_codec_get_list(c, a, TVHAudioCodec, _audio_)
+
+
+#define _tvh_codec_getattr(c, a, t, T) \
+ (((c) && tvh_codec_is_enabled((c)) && tvh_codec_get_type((c)) == (t)) ? (((T *)(c))->a) : NULL)
+
+#define tvh_codec_video_getattr(c, a) \
+ _tvh_codec_getattr(c, a, AVMEDIA_TYPE_VIDEO, TVHVideoCodec)
+
+#define tvh_codec_audio_getattr(c, a) \
+ _tvh_codec_getattr(c, a, AVMEDIA_TYPE_AUDIO, TVHAudioCodec)
+
+
+
+#define AV_DICT_SET(d, k, v, f) \
+ do { \
+ if (av_dict_set((d), (k), (v), (f)) < 0) { \
+ return -1; \
+ } \
+ } while (0)
+
+#define AV_DICT_SET_INT(d, k, v, f) \
+ do { \
+ if (av_dict_set_int((d), (k), (v), (f)) < 0) { \
+ return -1; \
+ } \
+ } while (0)
+
+#define AV_DICT_SET_TVH_REQUIRE_META(d, v) \
+ AV_DICT_SET_INT((d), "tvh_require_meta", (v), AV_DICT_DONT_OVERWRITE)
+
+#define AV_DICT_SET_FLAGS(d, v) \
+ AV_DICT_SET((d), "flags", (v), AV_DICT_APPEND)
+
+#define AV_DICT_SET_FLAGS_GLOBAL_HEADER(d) \
+ AV_DICT_SET_FLAGS((d), "+global_header")
+
+#define AV_DICT_SET_BIT_RATE(d, v) \
+ AV_DICT_SET_INT((d), "b", (v) * 1000, AV_DICT_DONT_OVERWRITE)
+
+#define AV_DICT_SET_GLOBAL_QUALITY(d, v, a) \
+ do { \
+ AV_DICT_SET_FLAGS((d), "+qscale"); \
+ AV_DICT_SET_INT((d), "global_quality", ((v) ? (v) : (a)) * FF_QP2LAMBDA, \
+ AV_DICT_DONT_OVERWRITE); \
+ } while (0)
+
+#define AV_DICT_SET_CRF(d, v, a) \
+ AV_DICT_SET_INT((d), "crf", (v) ? (v) : (a), AV_DICT_DONT_OVERWRITE)
+
+#define AV_DICT_SET_PIX_FMT(d, v, a) \
+ AV_DICT_SET_INT((d), "pix_fmt", ((v) != AV_PIX_FMT_NONE) ? (v) : (a), \
+ AV_DICT_DONT_OVERWRITE)
+
+
+/* codec_profile_class ====================================================== */
+
+uint32_t
+codec_profile_class_get_opts(void *obj, uint32_t opts);
+
+uint32_t
+codec_profile_class_profile_get_opts(void *obj, uint32_t opts);
+
+
+/* AVCodec ================================================================== */
+
+const char *
+codec_get_title(AVCodec *self);
+
+
+/* TVHCodec ================================================================= */
+
+enum AVMediaType
+tvh_codec_get_type(TVHCodec *self);
+
+const char *
+tvh_codec_get_type_string(TVHCodec *self);
+
+AVCodec *
+tvh_codec_get_codec(TVHCodec *self);
+
+int
+tvh_codec_is_enabled(TVHCodec *self);
+
+TVHCodec *
+tvh_codec_find(const char *name);
+
+void
+tvh_codecs_register(void);
+
+void
+tvh_codecs_forget(void);
+
+
+/* codec_profile_class */
+
+uint32_t
+tvh_codec_base_get_opts(TVHCodec *self, uint32_t opts, int visible);
+
+htsmsg_t *
+tvh_codec_get_list_profiles(TVHCodec *self);
+
+/* codec_profile_video_class */
+
+typedef struct tvh_codec_video_t {
+ TVHCodec;
+ const enum AVPixelFormat *pix_fmts;
+} TVHVideoCodec;
+
+
+/* codec_profile_audio_class */
+
+typedef struct tvh_codec_audio_t {
+ TVHCodec;
+ const enum AVSampleFormat *sample_fmts;
+ const int *sample_rates;
+ const uint64_t *channel_layouts;
+} TVHAudioCodec;
+
+
+/* TVHCodecProfile ========================================================== */
+
+void
+tvh_codec_profile_remove(TVHCodecProfile *self, int delete);
+
+TVHCodec *
+tvh_codec_profile_get_codec(TVHCodecProfile *self);
+
+TVHCodecProfile *
+tvh_codec_profile_find(const char *name);
+
+void
+tvh_codec_profiles_load(void);
+
+void
+tvh_codec_profiles_remove(void);
+
+
+/* video */
+
+extern const codec_profile_class_t codec_profile_video_class;
+
+typedef struct tvh_codec_profile_video_t {
+ TVHCodecProfile;
+ int deinterlace;
+ int height;
+ int hwaccel;
+ int pix_fmt;
+ int crf;
+ AVRational size;
+} TVHVideoCodecProfile;
+
+
+/* audio */
+
+extern const codec_profile_class_t codec_profile_audio_class;
+
+typedef struct tvh_codec_profile_audio_t {
+ TVHCodecProfile;
+ const char *language;
+ int sample_fmt;
+ int sample_rate;
+ int64_t channel_layout;
+} TVHAudioCodecProfile;
+
+
+#endif // TVH_TRANSCODING_CODEC_INTERNALS_H__
--- /dev/null
+/*
+ * tvheadend - Codec Profiles
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "internals.h"
+
+
+/* module level ============================================================= */
+
+TVHCodecProfile *
+codec_find_profile(const char *name)
+{
+ return name ? tvh_codec_profile_find(name) : NULL;
+}
+
+
+htsmsg_t *
+codec_get_profiles_list(enum AVMediaType media_type)
+{
+ htsmsg_t *list = NULL, *map = NULL;
+ TVHCodecProfile *profile = NULL;
+ TVHCodec *codec = NULL;
+
+
+ if ((list = htsmsg_create_list())) {
+ LIST_FOREACH(profile, &tvh_codec_profiles, link) {
+ if ((codec = tvh_codec_profile_get_codec(profile)) &&
+ tvh_codec_get_type(codec) == media_type) {
+ if (!(map = htsmsg_create_map())) {
+ htsmsg_destroy(list);
+ list = NULL;
+ break;
+ }
+ ADD_ENTRY(list, map,
+ str, tvh_codec_profile_get_name(profile),
+ str, tvh_codec_profile_get_title(profile));
+ }
+ }
+ }
+ return list;
+}
+
+
+void
+codec_init(void)
+{
+ // codecs
+ tvh_codecs_register();
+ // codec profiles
+ tvh_codec_profiles_load();
+}
+
+
+void
+codec_done(void)
+{
+ pthread_mutex_lock(&global_lock);
+
+ // codec profiles
+ tvh_codec_profiles_remove();
+ // codecs
+ tvh_codecs_forget();
+
+ pthread_mutex_unlock(&global_lock);
+}
--- /dev/null
+/*
+ * tvheadend - Codec Profiles
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "internals.h"
+
+#include "settings.h"
+
+
+struct TVHCodecProfiles tvh_codec_profiles;
+
+
+/* TVHCodecProfile ========================================================== */
+
+static TVHCodecProfile *
+tvh_codec_profile_alloc(TVHCodec *codec)
+{
+ TVHCodecProfile *self = NULL;
+
+ if ((self = calloc(1, codec->size))) {
+ self->codec = codec;
+ }
+ return self;
+}
+
+
+static void
+tvh_codec_profile_free(TVHCodecProfile *self)
+{
+ if (self) {
+ self->codec = NULL;
+ free(self);
+ self = NULL;
+ }
+}
+
+
+static void
+tvh_codec_profile_load(htsmsg_field_t *config)
+{
+ htsmsg_t *conf = NULL;
+ const char *name = NULL;
+
+ if ((conf = htsmsg_field_get_map(config)) &&
+ tvh_codec_profile_create(conf, config->hmf_name, 0)) {
+ tvherror(LS_CODEC, "unable to load codec profile: '%s'",
+ (name = htsmsg_get_str(conf, "name")) ? name : "<unknown>");
+ }
+}
+
+
+static int
+tvh_codec_profile_setup(TVHCodecProfile *self, tvh_ssc_t *ssc)
+{
+ const idclass_t *idclass = (&self->idnode)->in_class;
+ const codec_profile_class_t *codec_profile_class = NULL;
+ int ret = 0;
+
+ while (idclass) {
+ codec_profile_class = (codec_profile_class_t *)idclass;
+ if (codec_profile_class->setup &&
+ (ret = codec_profile_class->setup(self, ssc))) {
+ break;
+ }
+ idclass = idclass->ic_super;
+ }
+ return ret;
+}
+
+
+/* exposed */
+
+int
+tvh_codec_profile_create(htsmsg_t *conf, const char *uuid, int save)
+{
+ const char *profile_name = NULL, *codec_name = NULL;
+ TVHCodec *codec = NULL;
+ TVHCodecProfile *profile = NULL;
+
+ lock_assert(&global_lock);
+
+ if ((!(profile_name = htsmsg_get_str(conf, "name")) ||
+ profile_name[0] == '\0' || !strcmp(profile_name, "copy") ||
+ strlen(profile_name) >= TVH_NAME_LEN) ||
+ (!(codec_name = htsmsg_get_str(conf, "codec_name")) ||
+ codec_name[0] == '\0')) {
+ tvherror(LS_CODEC, "missing/empty/wrong 'name' or 'codec_name'");
+ return EINVAL;
+ }
+ if ((profile = tvh_codec_profile_find(profile_name))) {
+ tvherror(LS_CODEC, "codec profile '%s' already exists", profile_name);
+ return EEXIST;
+ }
+ if (!(codec = tvh_codec_find(codec_name))) {
+ tvherror(LS_CODEC, "codec '%s' not found", codec_name);
+ return ENOENT;
+ }
+ if (!(profile = tvh_codec_profile_alloc(codec))) {
+ tvherror(LS_CODEC, "failed to allocate TVHCodecProfile");
+ return ENOMEM;
+ }
+ if (idnode_insert(&profile->idnode, uuid, (idclass_t *)codec->idclass, 0)) {
+ tvh_codec_profile_free(profile);
+ tvherror(LS_CODEC, "failed to insert idnode");
+ return EINVAL;
+ }
+ idnode_load(&profile->idnode, conf);
+ LIST_INSERT_HEAD(&tvh_codec_profiles, profile, link);
+ if (save) {
+ idnode_changed(&profile->idnode);
+ }
+ tvhinfo(LS_CODEC, "'%s' codec profile created", profile_name);
+ return 0;
+}
+
+
+const char *
+tvh_codec_profile_get_status(TVHCodecProfile *self)
+{
+ TVHCodec *codec = tvh_codec_profile_get_codec(self);
+
+ if (codec && tvh_codec_is_enabled(codec)) {
+ return "codecEnabled";
+ }
+ return "codecDisabled";
+}
+
+
+const char *
+tvh_codec_profile_get_name(TVHCodecProfile *self)
+{
+ return self->name;
+}
+
+
+const char *
+tvh_codec_profile_get_title(TVHCodecProfile *self)
+{
+ static char profile_title[TVH_TITLE_LEN];
+
+ memset(profile_title, 0, sizeof(profile_title));
+ if (
+ str_snprintf(profile_title, sizeof(profile_title),
+ (self->description && strcmp(self->description, "")) ? "%s (%s)" : "%s%s",
+ self->name, self->description ? self->description : "")
+ ) {
+ return NULL;
+ }
+ return profile_title;
+}
+
+
+AVCodec *
+tvh_codec_profile_get_avcodec(TVHCodecProfile *self)
+{
+ TVHCodec *codec = tvh_codec_profile_get_codec(self);
+ return codec ? tvh_codec_get_codec(codec) : NULL;
+}
+
+
+/* transcode api */
+int
+tvh_codec_profile_is_copy(TVHCodecProfile *self, tvh_ssc_t *ssc)
+{
+ const idclass_t *idclass = NULL;
+ const codec_profile_class_t *codec_profile_class = NULL;
+ AVCodec *avcodec = NULL;
+ tvh_sct_t out_type = SCT_UNKNOWN;
+
+
+ if (tvh_codec_profile_setup(self, ssc)) {
+ return -1;
+ }
+ if (!(avcodec = tvh_codec_profile_get_avcodec(self))) {
+ tvherror(LS_CODEC, "profile '%s' is disabled", self->name);
+ return -1;
+ }
+ if ((out_type = codec_id2streaming_component_type(avcodec->id)) == SCT_UNKNOWN) {
+ tvherror(LS_CODEC, "unknown type for profile '%s'", self->name);
+ return -1;
+ }
+ if (out_type == ssc->ssc_type) {
+ idclass = (&self->idnode)->in_class;
+ while (idclass) {
+ codec_profile_class = (codec_profile_class_t *)idclass;
+ if (codec_profile_class->is_copy &&
+ !codec_profile_class->is_copy(self, ssc)) {
+ return 0;
+ }
+ idclass = idclass->ic_super;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+
+int
+tvh_codec_profile_open(TVHCodecProfile *self, AVDictionary **opts)
+{
+ const idclass_t *idclass = (&self->idnode)->in_class;
+ const codec_profile_class_t *codec_profile_class = NULL;
+ int ret = 0;
+
+ while (idclass) {
+ codec_profile_class = (codec_profile_class_t *)idclass;
+ if (codec_profile_class->open &&
+ (ret = codec_profile_class->open(self, opts))) {
+ break;
+ }
+ idclass = idclass->ic_super;
+ }
+ return ret;
+}
+
+
+/* video */
+int
+tvh_codec_profile_video_get_hwaccel(TVHCodecProfile *self)
+{
+ TVHCodec *codec = tvh_codec_profile_get_codec(self);
+ if (codec && tvh_codec_is_enabled(codec) &&
+ tvh_codec_get_type(codec) == AVMEDIA_TYPE_VIDEO) {
+ return ((TVHVideoCodecProfile *)self)->hwaccel;
+ }
+ return -1;
+}
+
+const enum AVPixelFormat *
+tvh_codec_profile_video_get_pix_fmts(TVHCodecProfile *self)
+{
+ TVHCodec *codec = tvh_codec_profile_get_codec(self);
+ return tvh_codec_video_getattr(codec, pix_fmts);
+}
+
+
+/* audio */
+const enum AVSampleFormat *
+tvh_codec_profile_audio_get_sample_fmts(TVHCodecProfile *self)
+{
+ TVHCodec *codec = tvh_codec_profile_get_codec(self);
+ return tvh_codec_audio_getattr(codec, sample_fmts);
+}
+
+const int *
+tvh_codec_profile_audio_get_sample_rates(TVHCodecProfile *self)
+{
+ TVHCodec *codec = tvh_codec_profile_get_codec(self);
+ return tvh_codec_audio_getattr(codec, sample_rates);
+}
+
+const uint64_t *
+tvh_codec_profile_audio_get_channel_layouts(TVHCodecProfile *self)
+{
+ TVHCodec *codec = tvh_codec_profile_get_codec(self);
+ return tvh_codec_audio_getattr(codec, channel_layouts);
+}
+
+
+/* internal api */
+void
+tvh_codec_profile_remove(TVHCodecProfile *self, int delete)
+{
+ static char uuid[UUID_HEX_SIZE];
+
+ memset(uuid, 0, sizeof(uuid));
+ idnode_save_check(&self->idnode, delete);
+ if (delete) {
+ hts_settings_remove("codec/%s", idnode_uuid_as_str(&self->idnode, uuid));
+ }
+ LIST_REMOVE(self, link);
+ idnode_unlink(&self->idnode);
+ tvh_codec_profile_free(self);
+}
+
+
+TVHCodec *
+tvh_codec_profile_get_codec(TVHCodecProfile *self)
+{
+ return self ? self->codec : NULL;
+}
+
+
+TVHCodecProfile *
+tvh_codec_profile_find(const char *name)
+{
+ TVHCodecProfile *profile = NULL;
+
+ LIST_FOREACH(profile, &tvh_codec_profiles, link) {
+ if (!strcmp(profile->name, name)) {
+ return profile;
+ }
+ }
+ return NULL;
+}
+
+
+void
+tvh_codec_profiles_load()
+{
+ htsmsg_t *settings = NULL;
+ htsmsg_field_t *config = NULL;
+
+ LIST_INIT(&tvh_codec_profiles);
+ if ((settings = hts_settings_load("codec"))) {
+ HTSMSG_FOREACH(config, settings) {
+ tvh_codec_profile_load(config);
+ }
+ htsmsg_destroy(settings);
+ settings = NULL;
+ }
+}
+
+
+void
+tvh_codec_profiles_remove()
+{
+ tvhinfo(LS_CODEC, "removing codec profiles");
+ while (!LIST_EMPTY(&tvh_codec_profiles)) {
+ tvh_codec_profile_remove(LIST_FIRST(&tvh_codec_profiles), 0);
+ }
+}
--- /dev/null
+/*
+ * tvheadend - Codec Profiles
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "internals.h"
+
+#include "lang_codes.h"
+
+
+/* TVHCodec ================================================================= */
+
+static htsmsg_t *
+tvh_codec_audio_get_list_sample_fmts(TVHAudioCodec *self)
+{
+ htsmsg_t *list = NULL, *map = NULL;
+ const enum AVSampleFormat *sample_fmts = self->sample_fmts;
+ enum AVSampleFormat f = AV_SAMPLE_FMT_NONE;
+ const char *f_str = NULL;
+ int i;
+
+ if (sample_fmts && (list = htsmsg_create_list())) {
+ if (!(map = htsmsg_create_map())) {
+ htsmsg_destroy(list);
+ list = NULL;
+ }
+ else {
+ ADD_ENTRY(list, map, s32, f, str, AUTO_STR);
+ for (i = 0; (f = sample_fmts[i]) != AV_SAMPLE_FMT_NONE; i++) {
+ if (!(f_str = av_get_sample_fmt_name(f)) ||
+ !(map = htsmsg_create_map())) {
+ htsmsg_destroy(list);
+ list = NULL;
+ break;
+ }
+ ADD_ENTRY(list, map, s32, f, str, f_str);
+ }
+ }
+ }
+ return list;
+}
+
+
+static htsmsg_t *
+tvh_codec_audio_get_list_sample_rates(TVHAudioCodec *self)
+{
+ htsmsg_t *list = NULL, *map = NULL;
+ const int *sample_rates = self->sample_rates;
+ int r = 0, i;
+
+ if (sample_rates && (list = htsmsg_create_list())) {
+ if (!(map = htsmsg_create_map())) {
+ htsmsg_destroy(list);
+ list = NULL;
+ }
+ else {
+ ADD_ENTRY(list, map, s32, r, str, AUTO_STR);
+ for (i = 0; (r = sample_rates[i]); i++) {
+ if (!(map = htsmsg_create_map())) {
+ htsmsg_destroy(list);
+ list = NULL;
+ break;
+ }
+ ADD_S32_VAL(list, map, r);
+ }
+ }
+ }
+ return list;
+}
+
+
+static htsmsg_t *
+tvh_codec_audio_get_list_channel_layouts(TVHAudioCodec *self)
+{
+ htsmsg_t *list = NULL, *map = NULL;
+ const uint64_t *channel_layouts = self->channel_layouts;
+ uint64_t l = 0;
+ static char l_buf[16];
+ int i;
+
+ if (channel_layouts && (list = htsmsg_create_list())) {
+ if (!(map = htsmsg_create_map())) {
+ htsmsg_destroy(list);
+ list = NULL;
+ }
+ else {
+ ADD_ENTRY(list, map, s64, l, str, AUTO_STR);
+ for (i = 0; (l = channel_layouts[i]); i++) {
+ if (l < INT64_MAX) {
+ if (!(map = htsmsg_create_map())) {
+ htsmsg_destroy(list);
+ list = NULL;
+ break;
+ }
+ memset(l_buf, 0, sizeof(l_buf));
+ av_get_channel_layout_string(l_buf, sizeof(l_buf), 0, l);
+ ADD_ENTRY(list, map, s64, l, str, l_buf);
+ }
+ }
+ }
+ }
+ return list;
+}
+
+
+/* TVHCodecProfile ========================================================== */
+
+static int
+tvh_codec_profile_audio_is_copy(TVHAudioCodecProfile *self, tvh_ssc_t *ssc)
+{
+ // TODO: fix me
+ // assuming default channel_layout (AV_CH_LAYOUT_STEREO)
+ // and sample_rate (48kHz) for input
+ int ssc_channels = ssc->ssc_channels ? ssc->ssc_channels : 2;
+ int ssc_sr = ssc->ssc_sri ? sri_to_rate(ssc->ssc_sri) : 48000;
+ if ((self->channel_layout &&
+ ssc_channels != av_get_channel_layout_nb_channels(self->channel_layout)) ||
+ (self->sample_rate && ssc_sr != self->sample_rate)) {
+ return 0;
+ }
+ return 1;
+}
+
+
+static int
+tvh_codec_profile_audio_open(TVHAudioCodecProfile *self, AVDictionary **opts)
+{
+ if (self->sample_fmt != AV_SAMPLE_FMT_NONE) {
+ AV_DICT_SET_INT(opts, "sample_fmt", self->sample_fmt,
+ AV_DICT_DONT_OVERWRITE);
+ }
+ if (self->sample_rate) {
+ AV_DICT_SET_INT(opts, "sample_rate", self->sample_rate,
+ AV_DICT_DONT_OVERWRITE);
+ }
+ if (self->channel_layout) {
+ AV_DICT_SET_INT(opts, "channel_layout", self->channel_layout,
+ AV_DICT_DONT_OVERWRITE);
+ }
+ return 0;
+}
+
+
+/* codec_profile_audio_class ================================================ */
+
+/* codec_profile_audio_class.language */
+
+static htsmsg_t *
+codec_profile_audio_class_language_list(void *obj, const char *lang)
+{
+ // TODO: rewrite
+ htsmsg_t *list = htsmsg_create_list();
+ lang_code_t *lc = (lang_code_t *)lang_codes;
+ static char lc_buf[128];
+
+ while (lc->code2b) {
+ htsmsg_t *map = htsmsg_create_map();
+ if (!strcmp(lc->code2b, "und")) {
+ htsmsg_add_str(map, "key", "");
+ htsmsg_add_str(map, "val", tvh_gettext_lang(lang, N_("Use original")));
+ }
+ else {
+ memset(lc_buf, 0, sizeof(lc_buf));
+ if (!str_snprintf(lc_buf, sizeof(lc_buf), "%s (%s)", lc->desc, lc->code2b)) {
+ htsmsg_add_str(map, "key", lc->code2b);
+ htsmsg_add_str(map, "val", lc_buf);
+ }
+ else {
+ htsmsg_destroy(list);
+ list = NULL;
+ break;
+ }
+ }
+ htsmsg_add_msg(list, NULL, map);
+ lc++;
+ }
+ return list;
+}
+
+
+/* codec_profile_audio_class.sample_fmt */
+
+static uint32_t
+codec_profile_audio_class_sample_fmt_get_opts(void *obj, uint32_t opts)
+{
+ TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+ return tvh_codec_audio_get_opts(codec, sample_fmts, opts);
+}
+
+
+static htsmsg_t *
+codec_profile_audio_class_sample_fmt_list(void *obj, const char *lang)
+{
+ TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+ return tvh_codec_audio_get_list(codec, sample_fmts);
+}
+
+
+/* codec_profile_audio_class.sample_rate */
+
+static uint32_t
+codec_profile_audio_class_sample_rate_get_opts(void *obj, uint32_t opts)
+{
+ TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+ return tvh_codec_audio_get_opts(codec, sample_rates, opts);
+}
+
+
+static htsmsg_t *
+codec_profile_audio_class_sample_rate_list(void *obj, const char *lang)
+{
+ TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+ return tvh_codec_audio_get_list(codec, sample_rates);
+}
+
+
+/* codec_profile_audio_class.channel_layout */
+
+static uint32_t
+codec_profile_audio_class_channel_layout_get_opts(void *obj, uint32_t opts)
+{
+ TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+ return tvh_codec_audio_get_opts(codec, channel_layouts, opts);
+}
+
+
+static htsmsg_t *
+codec_profile_audio_class_channel_layout_list(void *obj, const char *lang)
+{
+ TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+ return tvh_codec_audio_get_list(codec, channel_layouts);
+}
+
+
+/* codec_profile_audio_class */
+
+const codec_profile_class_t codec_profile_audio_class = {
+ {
+ .ic_super = (idclass_t *)&codec_profile_class,
+ .ic_class = "codec_profile_audio",
+ .ic_caption = N_("audio"),
+ .ic_properties = (const property_t[]) {
+ {
+ .type = PT_STR,
+ .id = "language",
+ .name = N_("Language"),
+ .desc = N_("Preferred audio language."),
+ .group = 2,
+ .off = offsetof(TVHAudioCodecProfile, language),
+ .list = codec_profile_audio_class_language_list,
+ .def.s = "",
+ },
+ {
+ .type = PT_INT,
+ .id = "sample_fmt",
+ .name = N_("Sample format"),
+ .desc = N_("Audio sample format."),
+ .group = 4,
+ .opts = PO_ADVANCED | PO_HIDDEN,
+ .get_opts = codec_profile_audio_class_sample_fmt_get_opts,
+ .off = offsetof(TVHAudioCodecProfile, sample_fmt),
+ .list = codec_profile_audio_class_sample_fmt_list,
+ .def.i = AV_SAMPLE_FMT_NONE,
+ },
+ {
+ .type = PT_INT,
+ .id = "sample_rate",
+ .name = N_("Sample rate"),
+ .desc = N_("Samples per second."),
+ .group = 4,
+ .opts = PO_ADVANCED | PO_HIDDEN,
+ .get_opts = codec_profile_audio_class_sample_rate_get_opts,
+ .off = offsetof(TVHAudioCodecProfile, sample_rate),
+ .list = codec_profile_audio_class_sample_rate_list,
+ .def.i = 0,
+ },
+ {
+ .type = PT_S64,
+ .id = "channel_layout",
+ .name = N_("Channel layout"),
+ .desc = N_("Audio channel layout."),
+ .group = 4,
+ .opts = PO_ADVANCED | PO_HIDDEN,
+ .get_opts = codec_profile_audio_class_channel_layout_get_opts,
+ .off = offsetof(TVHAudioCodecProfile, channel_layout),
+ .list = codec_profile_audio_class_channel_layout_list,
+ .def.s64 = 0,
+ },
+ {}
+ }
+ },
+ .is_copy = (codec_profile_is_copy_meth)tvh_codec_profile_audio_is_copy,
+ .open = (codec_profile_open_meth)tvh_codec_profile_audio_open,
+};
--- /dev/null
+/*
+ * tvheadend - Codec Profiles
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "internals.h"
+
+#include "access.h"
+
+
+/* TVHCodec ================================================================= */
+
+uint32_t
+tvh_codec_base_get_opts(TVHCodec *self, uint32_t opts, int visible)
+{
+ if (!tvh_codec_is_enabled(self)) {
+ opts |= PO_RDONLY;
+ }
+ else if (visible) {
+ opts &= ~(PO_HIDDEN);
+ }
+ return opts;
+}
+
+
+htsmsg_t *
+tvh_codec_get_list_profiles(TVHCodec *self)
+{
+ htsmsg_t *list = NULL, *map = NULL;
+ const AVProfile *profiles = self->profiles;
+ AVProfile *p = NULL;
+
+ if (profiles && (list = htsmsg_create_list())) {
+ if (!(map = htsmsg_create_map())) {
+ htsmsg_destroy(list);
+ list = NULL;
+ }
+ else {
+ ADD_ENTRY(list, map, s32, FF_PROFILE_UNKNOWN, str, AUTO_STR);
+ for (p = (AVProfile *)profiles; p->profile != FF_PROFILE_UNKNOWN; p++) {
+ if (!(map = htsmsg_create_map())) {
+ htsmsg_destroy(list);
+ list = NULL;
+ break;
+ }
+ ADD_ENTRY(list, map, s32, p->profile, str, p->name);
+ }
+ }
+ }
+ return list;
+}
+
+
+/* TVHCodecProfile ========================================================== */
+
+static int
+tvh_codec_profile_base_is_copy(TVHCodecProfile *self, tvh_ssc_t *ssc)
+{
+ return (!(self->bit_rate || self->qscale));
+}
+
+
+static int
+tvh_codec_profile_base_open(TVHCodecProfile *self, AVDictionary **opts)
+{
+ AV_DICT_SET_TVH_REQUIRE_META(opts, 1);
+ // profile
+ if (self->profile != FF_PROFILE_UNKNOWN) {
+ AV_DICT_SET_INT(opts, "profile", self->profile, 0);
+ }
+ return 0;
+}
+
+
+/* codec_profile_class ====================================================== */
+
+static htsmsg_t *
+codec_profile_class_save(idnode_t *idnode, char *filename, size_t fsize)
+{
+ htsmsg_t *map = NULL;
+ static char uuid[UUID_HEX_SIZE];
+
+ memset(uuid, 0, sizeof(uuid));
+ if (!str_snprintf(filename, fsize, "codec/%s", idnode_uuid_as_str(idnode, uuid)) &&
+ (map = htsmsg_create_map())) {
+ idnode_save(idnode, map);
+ }
+ return map;
+}
+
+
+static void
+codec_profile_class_delete(idnode_t *idnode)
+{
+ tvh_codec_profile_remove((TVHCodecProfile *)idnode, 1);
+}
+
+
+/* codec_profile_class.name */
+
+static int
+codec_profile_class_name_set(void *obj, const void *val)
+{
+ TVHCodecProfile *self = (TVHCodecProfile *)obj, *other = NULL;
+ const char *name = (const char *)val;
+
+ if (self) {
+ if (name && name[0] != '\0' && strcmp(name, "copy") &&
+ strlen(name) < TVH_NAME_LEN &&
+ strcmp(name, self->name ? self->name : "")) {
+ if ((other = tvh_codec_profile_find(name)) && other != self) {
+ tvherror(LS_CODEC, "profile '%s' already exists", name);
+ }
+ else {
+ if (self->name) {
+ free((void *)self->name);
+ self->name = NULL;
+ }
+ self->name = strdup(name);
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+
+/* codec_profile_class.type */
+
+static const void *
+codec_profile_class_type_get(void *obj)
+{
+ static const char *type;
+
+ TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+ type = codec ? tvh_codec_get_type_string(codec) : "<unknown>";
+ return &type;
+}
+
+
+/* codec_profile_class.enabled */
+
+static const void *
+codec_profile_class_enabled_get(void *obj)
+{
+ static int enabled;
+
+ TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+ enabled = codec ? tvh_codec_is_enabled(codec) : 0;
+ return &enabled;
+}
+
+
+/* codec_profile_class.profile */
+
+static htsmsg_t *
+codec_profile_class_profile_list(void *obj, const char *lang)
+{
+ TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+ return tvh_codec_get_list(codec, profiles);
+}
+
+
+/* codec_profile_class */
+
+const codec_profile_class_t codec_profile_class = {
+ {
+ .ic_class = "codec_profile",
+ .ic_caption = N_("Codec Profile"),
+ .ic_event = "codec_profile",
+ .ic_perm_def = ACCESS_ADMIN,
+ .ic_save = codec_profile_class_save,
+ .ic_delete = codec_profile_class_delete,
+ .ic_groups = (const property_group_t[]) {
+ {
+ .name = N_("Identification"),
+ .number = 1,
+ },
+ {
+ .name = N_("Profile Settings"),
+ .number = 2,
+ },
+ {
+ .name = N_("Codec Settings"),
+ .number = 3,
+ },
+ {
+ .name = N_("Advanced Settings"),
+ .number = 4,
+ },
+ {
+ .name = N_("Expert Settings"),
+ .number = 5,
+ },
+ {}
+ },
+ .ic_properties = (const property_t[]) {
+ {
+ .type = PT_STR,
+ .id = "name",
+ .name = N_("Name"),
+ .desc = N_("Name."),
+ .group = 1,
+ .off = offsetof(TVHCodecProfile, name),
+ .set = codec_profile_class_name_set,
+ },
+ {
+ .type = PT_STR,
+ .id = "description",
+ .name = N_("Description"),
+ .desc = N_("Profile description."),
+ .group = 1,
+ .off = offsetof(TVHCodecProfile, description),
+ },
+ {
+ .type = PT_STR,
+ .id = "codec_name",
+ .name = N_("Codec"),
+ .desc = N_("Codec name."),
+ .group = 1,
+ .opts = PO_RDONLY,
+ .off = offsetof(TVHCodecProfile, codec_name),
+ },
+ {
+ .type = PT_STR,
+ .id = "type",
+ .name = N_("Type"),
+ .desc = N_("Codec type."),
+ .group = 1,
+ .opts = PO_RDONLY | PO_NOSAVE,
+ .get = codec_profile_class_type_get,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "enabled",
+ .name = N_("Enabled"),
+ .desc = N_("Codec status."),
+ .group = 1,
+ .opts = PO_RDONLY | PO_NOSAVE,
+ .get = codec_profile_class_enabled_get,
+ },
+ {
+ .type = PT_INT,
+ .id = "profile",
+ .name = N_("Profile"),
+ .desc = N_("Profile."),
+ .group = 4,
+ .opts = PO_ADVANCED | PO_HIDDEN,
+ .get_opts = codec_profile_class_profile_get_opts,
+ .off = offsetof(TVHCodecProfile, profile),
+ .list = codec_profile_class_profile_list,
+ .def.i = FF_PROFILE_UNKNOWN,
+ },
+ {}
+ }
+ },
+ .is_copy = tvh_codec_profile_base_is_copy,
+ .open = tvh_codec_profile_base_open,
+};
+
+
+/* exposed */
+
+uint32_t
+codec_profile_class_get_opts(void *obj, uint32_t opts)
+{
+ TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+ return codec ? tvh_codec_base_get_opts(codec, opts, 0) : opts;
+}
+
+
+uint32_t
+codec_profile_class_profile_get_opts(void *obj, uint32_t opts)
+{
+ TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+ return tvh_codec_get_opts(codec, profiles, opts);
+}
--- /dev/null
+/*
+ * tvheadend - Codec Profiles
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "internals.h"
+
+#include <libavutil/pixdesc.h>
+
+
+/* TVHCodec ================================================================= */
+
+static htsmsg_t *
+tvh_codec_video_get_list_pix_fmts(TVHVideoCodec *self)
+{
+ htsmsg_t *list = NULL, *map = NULL;
+ const enum AVPixelFormat *pix_fmts = self->pix_fmts;
+ enum AVPixelFormat f = AV_PIX_FMT_NONE;
+ const char *f_str = NULL;
+ int i;
+
+ if (pix_fmts && (list = htsmsg_create_list())) {
+ if (!(map = htsmsg_create_map())) {
+ htsmsg_destroy(list);
+ list = NULL;
+ }
+ else {
+ ADD_ENTRY(list, map, s32, f, str, AUTO_STR);
+ for (i = 0; (f = pix_fmts[i]) != AV_PIX_FMT_NONE; i++) {
+ if (!(f_str = av_get_pix_fmt_name(f)) ||
+ !(map = htsmsg_create_map())) {
+ htsmsg_destroy(list);
+ list = NULL;
+ break;
+ }
+ ADD_ENTRY(list, map, s32, f, str, f_str);
+ }
+ }
+ }
+ return list;
+}
+
+
+/* TVHCodecProfile ========================================================== */
+
+static int
+tvh_codec_profile_video_setup(TVHVideoCodecProfile *self, tvh_ssc_t *ssc)
+{
+ self->size.den = ssc->ssc_height;
+ self->size.num = ssc->ssc_width;
+ if (self->height) {
+ self->size.den = self->height;
+ self->size.den += self->size.den & 1;
+ self->size.num = self->size.den * ((double)ssc->ssc_width / ssc->ssc_height);
+ self->size.num += self->size.num & 1;
+ }
+ return 0;
+}
+
+
+static int
+tvh_codec_profile_video_is_copy(TVHVideoCodecProfile *self, tvh_ssc_t *ssc)
+{
+ return (!self->deinterlace && (self->size.den == ssc->ssc_height));
+}
+
+
+static int
+tvh_codec_profile_video_open(TVHVideoCodecProfile *self, AVDictionary **opts)
+{
+ AV_DICT_SET_INT(opts, "tvh_filter_deint", self->deinterlace, 0);
+ // video_size
+ AV_DICT_SET_INT(opts, "width", self->size.num, 0);
+ AV_DICT_SET_INT(opts, "height", self->size.den, 0);
+ // crf
+ if (self->crf) {
+ AV_DICT_SET_INT(opts, "crf", self->crf, AV_DICT_DONT_OVERWRITE);
+ }
+ // pix_fmt
+ AV_DICT_SET_PIX_FMT(opts, self->pix_fmt, AV_PIX_FMT_YUV420P);
+ return 0;
+}
+
+
+/* codec_profile_video_class ================================================ */
+
+/* codec_profile_video_class.pix_fmt */
+
+static uint32_t
+codec_profile_video_class_pix_fmt_get_opts(void *obj, uint32_t opts)
+{
+ TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+ return tvh_codec_video_get_opts(codec, pix_fmts, opts);
+}
+
+
+static htsmsg_t *
+codec_profile_video_class_pix_fmt_list(void *obj, const char *lang)
+{
+ TVHCodec *codec = tvh_codec_profile_get_codec(obj);
+ return tvh_codec_video_get_list(codec, pix_fmts);
+}
+
+
+/* codec_profile_video_class */
+
+const codec_profile_class_t codec_profile_video_class = {
+ {
+ .ic_super = (idclass_t *)&codec_profile_class,
+ .ic_class = "codec_profile_video",
+ .ic_caption = N_("video"),
+ .ic_properties = (const property_t[]) {
+ {
+ .type = PT_BOOL,
+ .id = "deinterlace",
+ .name = N_("Deinterlace"),
+ .desc = N_("Deinterlace."),
+ .group = 2,
+ .off = offsetof(TVHVideoCodecProfile, deinterlace),
+ .def.i = 1,
+ },
+ {
+ .type = PT_INT,
+ .id = "height",
+ .name = N_("Height (pixels) (0=no scaling)"),
+ .desc = N_("Height of the output video stream. Horizontal resolution "
+ "is adjusted automatically to preserve aspect ratio. "
+ "When set to 0, the input resolution is used."),
+ .group = 2,
+ .off = offsetof(TVHVideoCodecProfile, height),
+ .def.i = 0,
+ },
+ {
+ .type = PT_BOOL,
+ .id = "hwaccel",
+ .name = N_("Hardware acceleration"),
+ .desc = N_("Use hardware acceleration for decoding if available."),
+ .group = 2,
+ .off = offsetof(TVHVideoCodecProfile, hwaccel),
+ .def.i = 0,
+ },
+ {
+ .type = PT_INT,
+ .id = "pix_fmt",
+ .name = N_("Pixel format"),
+ .desc = N_("Video pixel format."),
+ .group = 4,
+ .opts = PO_ADVANCED | PO_HIDDEN,
+ .get_opts = codec_profile_video_class_pix_fmt_get_opts,
+ .off = offsetof(TVHVideoCodecProfile, pix_fmt),
+ .list = codec_profile_video_class_pix_fmt_list,
+ .def.i = AV_PIX_FMT_NONE,
+ },
+ {}
+ }
+ },
+ .setup = (codec_profile_is_copy_meth)tvh_codec_profile_video_setup,
+ .is_copy = (codec_profile_is_copy_meth)tvh_codec_profile_video_is_copy,
+ .open = (codec_profile_open_meth)tvh_codec_profile_video_open,
+};
--- /dev/null
+/*
+ * tvheadend - Transcoding
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "memutils.h"
+
+#include "memoryinfo.h"
+
+#include <libavutil/mem.h>
+
+
+static char *
+str_add(const char *sep, const char *str)
+{
+ size_t sep_len = strlen(sep), str_len = strlen(str);
+ char *result = NULL;
+
+ if ((result = calloc(1, sep_len + str_len + 1))) {
+ strncpy(result, sep, sep_len);
+ strncat(result, str, str_len);
+ }
+ return result;
+}
+
+
+static char *
+str_vjoin(const char *separator, va_list ap)
+{
+ const char *sep = separator ? separator : "", *va_str = NULL;
+ char *str = NULL, *tmp_result = NULL, *result = NULL;
+ size_t str_len = 0, result_size = 1;
+
+ if ((result = calloc(1, result_size))) {
+ while ((va_str = va_arg(ap, const char *))) {
+ if (strlen(va_str)) {
+ if ((str = str_add(strlen(result) ? sep : "", va_str))) {
+ if ((str_len = strlen(str))) {
+ result_size += str_len;
+ if ((tmp_result = realloc(result, result_size))) {
+ result = tmp_result;
+ strncat(result, str, str_len);
+ }
+ else {
+ str_clear(result);
+ }
+ }
+ str_clear(str);
+ }
+ else {
+ str_clear(result);
+ }
+ if (!result) {
+ break;
+ }
+ }
+ }
+ }
+ return result;
+}
+
+
+char *
+str_join(const char *separator, ...)
+{
+ va_list ap;
+ va_start(ap, separator);
+ char *result = str_vjoin(separator, ap);
+ va_end(ap);
+ return result;
+}
+
+
+int
+str_snprintf(char *str, size_t size, const char *format, ...)
+{
+ va_list ap;
+ va_start(ap, format);
+ int ret = vsnprintf(str, size, format, ap);
+ va_end(ap);
+ return (ret < 0 || ret >= size) ? -1 : 0;
+}
+
+
+/* meant to replace streaming_msg_create_pkt in streaming.h
+ IMPORTANT: takes ownership of pkt */
+streaming_message_t *
+msg_create(th_pkt_t *pkt)
+{
+ static const size_t msg_size = sizeof(streaming_message_t);
+ streaming_message_t *msg = NULL;
+
+ if ((msg = calloc(1, msg_size))) {
+ msg->sm_type = SMT_PACKET;
+#if ENABLE_TIMESHIFT
+ msg->sm_time = 0;
+#endif
+ msg->sm_data = pkt; // takes ownership
+ }
+ else {
+ TVHPKT_DECREF(pkt);
+ }
+ return msg;
+}
+
+
+/* _IMPORTANT!_: need to check for pb->pb_size and pb->pb_data
+ _BEFORE_ calling pktbuf_copy_data */
+uint8_t *
+pktbuf_copy_data(pktbuf_t *pb)
+{
+ uint8_t *data = av_mallocz(pb->pb_size);
+ if (data) {
+ memcpy(data, pb->pb_data, pb->pb_size);
+ }
+ return data;
+}
+
+
+/* meant to replace pktbuf_alloc in packet.h */
+pktbuf_t *
+pktbuf_create(const uint8_t *data, size_t size)
+{
+ static const size_t pktbuf_size = sizeof(pktbuf_t);
+ pktbuf_t *pktbuf = NULL;
+ uint8_t *buffer = NULL;
+
+ if (size) {
+ if (!(buffer = calloc(1, size))) {
+ return NULL;
+ }
+ else if (data) {
+ memcpy(buffer, data, size);
+ }
+ }
+ if ((pktbuf = calloc(1, pktbuf_size))) {
+ pktbuf->pb_refcount = 1;
+ pktbuf->pb_err = 0;
+ pktbuf->pb_data = buffer;
+ pktbuf->pb_size = size;
+ memoryinfo_alloc(&pktbuf_memoryinfo, pktbuf_size + pktbuf->pb_size);
+ }
+ else if (buffer) {
+ free(buffer);
+ buffer = NULL;
+ }
+ return pktbuf;
+}
+
+
+/* meant to replace pkt_alloc in packet.h */
+th_pkt_t *
+pkt_create(const uint8_t *data, size_t size, int64_t pts, int64_t dts)
+{
+ static const size_t pkt_size = sizeof(th_pkt_t);
+ th_pkt_t *pkt = NULL;
+ pktbuf_t *payload = NULL;
+
+ if (size && !(payload = pktbuf_create(data, size))) {
+ return NULL;
+ }
+ if ((pkt = calloc(1, pkt_size))) {
+ pkt->pkt_refcount = 1;
+ pkt->pkt_pts = pts;
+ pkt->pkt_dts = dts;
+ pkt->pkt_payload = payload;
+ memoryinfo_alloc(&pkt_memoryinfo, pkt_size);
+ }
+ else if (payload) {
+ pktbuf_ref_dec(payload);
+ payload = NULL;
+ }
+ return pkt;
+}
--- /dev/null
+/*
+ * tvheadend - Transcoding
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef TVH_TRANSCODING_MEMUTILS_H__
+#define TVH_TRANSCODING_MEMUTILS_H__
+
+
+#include "tvheadend.h"
+#include "streaming.h"
+
+
+#define TVH_INPUT_BUFFER_MAX_SIZE (INT_MAX - AV_INPUT_BUFFER_PADDING_SIZE)
+
+
+#define TVHPKT_INCREF pkt_ref_inc
+
+#define TVHPKT_DECREF pkt_ref_dec
+
+#define TVHPKT_CLEAR(ptr) \
+ do { \
+ th_pkt_t *_tmp = (ptr); \
+ if (_tmp != NULL) { \
+ (ptr) = NULL; \
+ TVHPKT_DECREF(_tmp); \
+ } \
+ } while (0)
+
+#define TVHPKT_SET(ptr, pkt) \
+ do { \
+ TVHPKT_INCREF((pkt)); \
+ TVHPKT_CLEAR((ptr)); \
+ (ptr) = (pkt); \
+ } while (0)
+
+
+#define str_clear(str) \
+ do { \
+ if ((str) != NULL) { \
+ free((str)); \
+ (str) = NULL; \
+ } \
+ } while (0)
+
+char *
+str_join(const char *separator, ...);
+
+int
+str_snprintf(char *str, size_t size, const char *format, ...);
+
+
+streaming_message_t *
+msg_create(th_pkt_t *pkt);
+
+/* _IMPORTANT!_: need to check for pb->pb_size and pb->pb_data
+ _BEFORE_ calling pktbuf_copy_data */
+uint8_t *
+pktbuf_copy_data(pktbuf_t *pb);
+
+pktbuf_t *
+pktbuf_create(const uint8_t *data, size_t size);
+
+th_pkt_t *
+pkt_create(const uint8_t *data, size_t size, int64_t pts, int64_t dts);
+
+
+#endif // TVH_TRANSCODING_MEMUTILS_H__
--- /dev/null
+/*
+ * tvheadend - Transcoding
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef TVH_TRANSCODING_TRANSCODE_H__
+#define TVH_TRANSCODING_TRANSCODE_H__
+
+
+#include "tvheadend.h"
+
+
+/* TVHTranscoder ============================================================ */
+
+streaming_target_t *
+transcoder_create(streaming_target_t *output, const char **profiles);
+
+void
+transcoder_destroy(streaming_target_t *st);
+
+
+/* module level ============================================================= */
+
+void
+transcode_init(void);
+
+void
+transcode_done(void);
+
+
+#endif // TVH_TRANSCODING_TRANSCODE_H__
--- /dev/null
+/*
+ * tvheadend - Transcoding
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "internals.h"
+
+
+static enum AVSampleFormat
+_audio_context_sample_fmt(TVHContext *self, AVDictionary **opts)
+{
+ const enum AVSampleFormat *sample_fmts =
+ tvh_codec_profile_audio_get_sample_fmts(self->profile);
+ enum AVSampleFormat ifmt = self->iavctx->sample_fmt;
+ enum AVSampleFormat ofmt = AV_SAMPLE_FMT_NONE;
+ int i;
+
+ if (!tvh_context_get_int_opt(opts, "sample_fmt", &ofmt) &&
+ ofmt == AV_SAMPLE_FMT_NONE) {
+ if (sample_fmts) {
+ for (i = 0; (ofmt = sample_fmts[i]) != AV_SAMPLE_FMT_NONE; i++) {
+ if (ofmt == ifmt) {
+ break;
+ }
+ }
+ ofmt = (ofmt != AV_SAMPLE_FMT_NONE) ? ofmt : sample_fmts[0];
+ }
+ else {
+ ofmt = ifmt;
+ }
+ }
+ return ofmt;
+}
+
+
+static int
+_audio_context_sample_rate(TVHContext *self, AVDictionary **opts)
+{
+ const int *sample_rates =
+ tvh_codec_profile_audio_get_sample_rates(self->profile);
+ int irate = self->iavctx->sample_rate, altrate = 0, orate = 0, i;
+
+ if (!tvh_context_get_int_opt(opts, "sample_rate", &orate) &&
+ !orate && sample_rates) {
+ for (i = 0; (orate = sample_rates[i]); i++) {
+ if (orate == irate) {
+ break;
+ }
+ if (orate < irate) {
+ altrate = orate;
+ }
+ }
+ }
+ return orate ? orate : (altrate ? altrate : 48000);
+}
+
+
+static uint64_t
+_audio_context_channel_layout(TVHContext *self, AVDictionary **opts)
+{
+ const uint64_t *channel_layouts =
+ tvh_codec_profile_audio_get_channel_layouts(self->profile);
+ uint64_t ilayout = self->iavctx->channel_layout;
+ uint64_t altlayout = 0, olayout = 0;
+ int tmp = 0, ichannels = 0, i;
+
+
+ if (!tvh_context_get_int_opt(opts, "channel_layout", &tmp) &&
+ !(olayout = tmp) && channel_layouts) {
+ ichannels = av_get_channel_layout_nb_channels(ilayout);
+ for (i = 0; (olayout = channel_layouts[i]); i++) {
+ if (olayout == ilayout) {
+ break;
+ }
+ if (av_get_channel_layout_nb_channels(olayout) <= ichannels) {
+ altlayout = olayout;
+ }
+ }
+ }
+ return olayout ? olayout : (altlayout ? altlayout : AV_CH_LAYOUT_STEREO);
+}
+
+
+static int
+tvh_audio_context_open_encoder(TVHContext *self, AVDictionary **opts)
+{
+ // XXX: is this a safe assumption?
+ if (!self->iavctx->time_base.den) {
+ self->iavctx->time_base = av_make_q(1, 90000);
+ }
+ // sample_fmt
+ self->oavctx->sample_fmt = _audio_context_sample_fmt(self, opts);
+ if (self->oavctx->sample_fmt == AV_SAMPLE_FMT_NONE) {
+ tvh_context_log(self, LOG_ERR,
+ "audio encoder has no suitable sample format");
+ return -1;
+ }
+ // sample_rate
+ self->oavctx->sample_rate = _audio_context_sample_rate(self, opts);
+ if (!self->oavctx->sample_rate) {
+ tvh_context_log(self, LOG_ERR,
+ "audio encoder has no suitable sample rate");
+ return -1;
+ }
+ self->oavctx->time_base = av_make_q(1, self->oavctx->sample_rate);
+ self->sri = rate_to_sri(self->oavctx->sample_rate);
+ // channel_layout
+ self->oavctx->channel_layout = _audio_context_channel_layout(self, opts);
+ if (!self->oavctx->channel_layout) {
+ tvh_context_log(self, LOG_ERR,
+ "audio encoder has no suitable channel layout");
+ return -1;
+ }
+ self->oavctx->channels =
+ av_get_channel_layout_nb_channels(self->oavctx->channel_layout);
+ return 0;
+}
+
+
+static int
+tvh_audio_context_open_filters(TVHContext *self, AVDictionary **opts)
+{
+ static char source_args[128];
+ static char filters[16];
+ int resample = (self->iavctx->sample_rate != self->oavctx->sample_rate);
+
+ // source args
+ memset(source_args, 0, sizeof(source_args));
+ if (str_snprintf(source_args, sizeof(source_args),
+ "time_base=%d/%d:sample_rate=%d:sample_fmt=%d:channel_layout=0x%"PRIx64,
+ self->iavctx->time_base.num,
+ self->iavctx->time_base.den,
+ self->iavctx->sample_rate,
+ self->iavctx->sample_fmt,
+ self->iavctx->channel_layout)) {
+ return -1;
+ }
+
+ // context filters
+ memset(filters, 0, sizeof(filters));
+ if (str_snprintf(filters, sizeof(filters), "%s",
+ (resample) ? "aresample" : "anull")) {
+ return -1;
+ }
+
+ int ret = tvh_context_open_filters(self,
+ "abuffer", source_args, // source
+ filters, // filters
+ "abuffersink", // sink
+ "channel_layouts", &self->oavctx->channel_layout, // sink option: channel_layout
+ sizeof(self->oavctx->channel_layout),
+ "sample_fmts", &self->oavctx->sample_fmt, // sink option: sample_fmt
+ sizeof(self->oavctx->sample_fmt),
+ "sample_rates", &self->oavctx->sample_rate, // sink option: sample_rate
+ sizeof(self->oavctx->sample_rate),
+ NULL); // _IMPORTANT!_
+ if (!ret) {
+ av_buffersink_set_frame_size(self->oavfltctx, self->oavctx->frame_size);
+ }
+ return ret;
+}
+
+
+static int
+tvh_audio_context_open(TVHContext *self, TVHOpenPhase phase, AVDictionary **opts)
+{
+ switch (phase) {
+ case OPEN_ENCODER:
+ return tvh_audio_context_open_encoder(self, opts);
+ case OPEN_ENCODER_POST:
+ self->delta = av_rescale_q(self->oavctx->frame_size,
+ self->oavctx->time_base,
+ self->iavctx->time_base);
+ return tvh_audio_context_open_filters(self, opts);
+ default:
+ break;
+ }
+ return 0;
+}
+
+
+static int
+tvh_audio_context_decode(TVHContext *self, AVPacket *avpkt)
+{
+ int64_t prev_pts = self->pts;
+
+ if (prev_pts != (self->pts = avpkt->pts - self->duration)) {
+ tvh_context_log(self, LOG_WARNING, "Detected framedrop in audio");
+ }
+ self->duration += avpkt->duration;
+ return 0;
+}
+
+
+static int
+tvh_audio_context_encode(TVHContext *self, AVFrame *avframe)
+{
+ avframe->nb_samples = self->oavctx->frame_size;
+ avframe->pts = self->pts;
+ self->pts += self->delta;
+ self->duration -= self->delta;
+ return 0;
+}
+
+
+static int
+tvh_audio_context_ship(TVHContext *self, AVPacket *avpkt)
+{
+ return (avpkt->pts >= 0);
+}
+
+
+static int
+tvh_audio_context_wrap(TVHContext *self, AVPacket *avpkt, th_pkt_t *pkt)
+{
+ pkt->pkt_duration = avpkt->duration;
+ pkt->a.pkt_channels = self->oavctx->channels;
+ pkt->a.pkt_sri = self->sri;
+ return 0;
+}
+
+
+TVHContextType TVHAudioContext = {
+ .media_type = AVMEDIA_TYPE_AUDIO,
+ .open = tvh_audio_context_open,
+ .decode = tvh_audio_context_decode,
+ .encode = tvh_audio_context_encode,
+ .ship = tvh_audio_context_ship,
+ .wrap = tvh_audio_context_wrap,
+};
--- /dev/null
+/*
+ * tvheadend - Transcoding
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "internals.h"
+
+#include <libavutil/opt.h>
+
+
+#define TVH_BUFFERSRC_FLAGS AV_BUFFERSRC_FLAG_PUSH | AV_BUFFERSRC_FLAG_KEEP_REF
+#define TVH_BUFFERSINK_FLAGS AV_BUFFERSINK_FLAG_NO_REQUEST
+
+
+/* TVHContextType =========================================================== */
+
+extern TVHContextType TVHVideoContext;
+extern TVHContextType TVHAudioContext;
+
+
+SLIST_HEAD(TVHContextTypes, tvh_context_type_t);
+static struct TVHContextTypes tvh_context_types;
+
+
+static void
+tvh_context_type_register(TVHContextType *self)
+{
+ if (self->media_type <= AVMEDIA_TYPE_UNKNOWN ||
+ self->media_type >= AVMEDIA_TYPE_NB ||
+ !self->ship) {
+ tvherror(LS_TRANSCODE, "incomplete/wrong definition for context type");
+ return;
+ }
+ SLIST_INSERT_HEAD(&tvh_context_types, self, link);
+ tvhinfo(LS_TRANSCODE, "'%s' context type registered",
+ av_get_media_type_string(self->media_type));
+}
+
+
+static TVHContextType *
+tvh_context_type_find(enum AVMediaType media_type)
+{
+ TVHContextType *type = NULL;
+
+ SLIST_FOREACH(type, &tvh_context_types, link) {
+ if (type->media_type == media_type) {
+ return type;
+ }
+ }
+ return NULL;
+}
+
+
+void
+tvh_context_types_register()
+{
+ SLIST_INIT(&tvh_context_types);
+ tvh_context_type_register(&TVHVideoContext);
+ tvh_context_type_register(&TVHAudioContext);
+}
+
+
+void
+tvh_context_types_forget()
+{
+ tvhinfo(LS_TRANSCODE, "forgetting context types");
+ while (!SLIST_EMPTY(&tvh_context_types)) {
+ SLIST_REMOVE_HEAD(&tvh_context_types, link);
+ }
+}
+
+
+/* TVHContext =============================================================== */
+
+// wrappers
+
+static void
+_context_print_opts(TVHContext *self, AVDictionary *opts)
+{
+ char *buffer = NULL;
+
+ if (opts) {
+ av_dict_get_string(opts, &buffer, '=', ',');
+ tvh_context_log(self, LOG_DEBUG, "opts: %s", buffer);
+ av_freep(&buffer);
+ }
+}
+
+
+static int
+_context_filters_apply_sink_options(TVHContext *self, va_list ap)
+{
+ const char *opt_name = NULL;
+ const uint8_t *opt_val = NULL;
+ int opt_size = 0;
+ int ret = -1;
+
+ while ((opt_name = va_arg(ap, const char *))) {
+ opt_val = va_arg(ap, const uint8_t *);
+ opt_size = va_arg(ap, int);
+ if ((ret = av_opt_set_bin(self->oavfltctx, opt_name, opt_val, opt_size,
+ AV_OPT_SEARCH_CHILDREN))) {
+ tvh_context_log(self, LOG_ERR, "filters: failed to set option: '%s'",
+ opt_name);
+ break;
+ }
+ }
+ return ret;
+}
+
+
+static int
+_context_open(TVHContext *self, TVHOpenPhase phase, AVDictionary **opts)
+{
+ return (self->type->open) ? self->type->open(self, phase, opts) : 0;
+}
+
+
+static int
+_context_decode(TVHContext *self, AVPacket *avpkt)
+{
+ return (self->type->decode) ? self->type->decode(self, avpkt) : 0;
+}
+
+
+static int
+_context_encode(TVHContext *self, AVFrame *avframe)
+{
+ return (self->type->encode) ? self->type->encode(self, avframe) : 0;
+ /*int ret = -1;
+
+ if (!(ret = (self->type->encode) ? self->type->encode(self, avframe) : 0) &&
+ (self->helper && self->helper->encode)) {
+ ret = self->helper->encode(self, avframe);
+ }
+ return ret;*/
+}
+
+
+static int
+_context_meta(TVHContext *self, AVPacket *avpkt, th_pkt_t *pkt)
+{
+ if (self->require_meta) {
+ if (self->helper && self->helper->meta) {
+ self->require_meta = self->helper->meta(self, avpkt, pkt);
+ }
+ else if (self->oavctx->extradata_size) {
+ pkt->pkt_meta = pktbuf_create(self->oavctx->extradata,
+ self->oavctx->extradata_size);
+ self->require_meta = (pkt->pkt_meta) ? 0 : -1;
+ }
+ }
+ return (self->require_meta < 0) ? -1 : 0;
+}
+
+
+static int
+_context_wrap(TVHContext *self, AVPacket *avpkt, th_pkt_t *pkt)
+{
+ return (self->type->wrap) ? self->type->wrap(self, avpkt, pkt) : 0;
+}
+
+
+// creation
+
+static AVCodecContext *
+tvh_context_alloc_avctx(AVCodec *avcodec)
+{
+ AVCodecContext *avctx = NULL;
+
+ if ((avctx = avcodec_alloc_context3(avcodec))) {
+ avctx->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL;
+ avctx->refcounted_frames = 1;
+ }
+ return avctx;
+}
+
+
+static int
+tvh_context_setup(TVHContext *self, AVCodec *iavcodec, AVCodec *oavcodec)
+{
+ enum AVMediaType media_type = iavcodec->type;
+ const char *media_type_name = av_get_media_type_string(media_type);
+
+ if (!(self->type = tvh_context_type_find(media_type))) {
+ tvh_stream_log(self->stream, LOG_ERR,
+ "failed to find context type for '%s' media type",
+ media_type_name ? media_type_name : "<unknown>");
+ return -1;
+ }
+ if (!(self->iavctx = tvh_context_alloc_avctx(iavcodec)) ||
+ !(self->oavctx = tvh_context_alloc_avctx(oavcodec)) ||
+ !(self->iavframe = av_frame_alloc()) ||
+ !(self->oavframe = av_frame_alloc())) {
+ tvh_stream_log(self->stream, LOG_ERR,
+ "failed to allocate AVCodecContext/AVFrame");
+ return -1;
+ }
+ return 0;
+}
+
+
+// open
+
+static int
+tvh_context_open(TVHContext *self, TVHOpenPhase phase)
+{
+ AVCodecContext *avctx = NULL;
+ TVHContextHelper *helper = NULL;
+ AVDictionary *opts = NULL;
+ int ret = 0;
+
+ switch (phase) {
+ case OPEN_DECODER:
+ avctx = self->iavctx;
+ helper = tvh_decoder_helper_find(avctx->codec);
+ break;
+ case OPEN_ENCODER:
+ avctx = self->oavctx;
+ helper = self->helper = tvh_encoder_helper_find(avctx->codec);
+ ret = tvh_codec_profile_open(self->profile, &opts);
+ break;
+ default:
+ tvh_context_log(self, LOG_ERR, "invalid open phase");
+ ret = AVERROR(EINVAL);
+ break;
+ }
+ if (!ret) {
+ _context_print_opts(self, opts);
+ ret = tvh_context_get_int_opt(&opts, "tvh_require_meta",
+ &self->require_meta);
+ if (!ret && !(ret = _context_open(self, phase, &opts)) && // pre open
+ !(ret = (helper && helper->open) ? helper->open(self, &opts) : 0) && // pre open
+ !(ret = avcodec_open2(avctx, NULL, &opts))) { // open
+ ret = _context_open(self, phase + 1, &opts); // post open
+ }
+ _context_print_opts(self, opts);
+ }
+ if (opts) {
+ av_dict_free(&opts);
+ }
+ return ret;
+}
+
+
+// shipping
+
+static th_pkt_t *
+tvh_context_pack(TVHContext *self, AVPacket *avpkt)
+{
+ th_pkt_t *pkt = NULL;
+
+ if (self->helper && self->helper->pack) {
+ pkt = self->helper->pack(self, avpkt);
+ }
+ else {
+ pkt = pkt_create(avpkt->data, avpkt->size, avpkt->pts, avpkt->dts);
+ }
+ if (!pkt) {
+ tvh_context_log(self, LOG_ERR, "failed to create packet");
+ }
+ else if (_context_meta(self, avpkt, pkt) ||
+ _context_wrap(self, avpkt, pkt)) {
+ TVHPKT_CLEAR(pkt);
+ }
+ return pkt;
+}
+
+
+static int
+tvh_context_ship(TVHContext *self, AVPacket *avpkt)
+{
+ th_pkt_t *pkt = NULL;
+ int ret = -1;
+
+ if (((ret = self->type->ship(self, avpkt)) > 0) &&
+ (pkt = tvh_context_pack(self, avpkt))) {
+ ret = tvh_context_deliver(self, pkt);
+ }
+ if ((ret = (ret != 0) ? -1 : ret)) {
+ av_packet_unref(avpkt);
+ }
+ return ret;
+}
+
+
+// encoding
+
+static int
+tvh_context_receive_packet(TVHContext *self)
+{
+ AVPacket avpkt;
+ int ret = -1;
+
+ av_init_packet(&avpkt);
+ while ((ret = avcodec_receive_packet(self->oavctx, &avpkt)) != AVERROR(EAGAIN)) {
+ if (ret || (ret = tvh_context_ship(self, &avpkt))) {
+ break;
+ }
+ }
+ return (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) ? 0 : ret;
+}
+
+
+static int
+tvh_context_encode_frame(TVHContext *self, AVFrame *avframe)
+{
+ int ret = -1;
+
+ if (!(ret = avframe ? _context_encode(self, avframe) : 0)) {
+ while ((ret = avcodec_send_frame(self->oavctx, avframe)) == AVERROR(EAGAIN)) {
+ if ((ret = tvh_context_receive_packet(self))) {
+ break;
+ }
+ }
+ }
+ if (!ret) {
+ ret = tvh_context_receive_packet(self);
+ }
+ if ((ret = (ret == AVERROR_EOF) ? 0 : ret)) {
+ av_frame_unref(avframe);
+ }
+ return ret;
+}
+
+
+static int
+tvh_context_pull_frame(TVHContext *self, AVFrame *avframe)
+{
+ int ret = -1;
+
+ av_frame_unref(avframe);
+ ret = av_buffersink_get_frame_flags(self->oavfltctx, avframe, TVH_BUFFERSINK_FLAGS);
+ return (ret > 0) ? 0 : ret;
+}
+
+
+static int
+tvh_context_push_frame(TVHContext *self, AVFrame *avframe)
+{
+ int ret = -1;
+
+ ret = av_buffersrc_add_frame_flags(self->iavfltctx, avframe, TVH_BUFFERSRC_FLAGS);
+ return (ret > 0) ? 0 : ret;
+}
+
+
+static int
+tvh_context_encode(TVHContext *self, AVFrame *avframe)
+{
+ int ret = 0;
+
+ if (!avcodec_is_open(self->oavctx)) {
+ ret = tvh_context_open(self, OPEN_ENCODER);
+ }
+ if (!ret && !(ret = tvh_context_push_frame(self, avframe))) {
+ while ((ret = tvh_context_pull_frame(self, self->oavframe)) != AVERROR(EAGAIN)) {
+ if (ret || (ret = tvh_context_encode_frame(self, self->oavframe))) {
+ break;
+ }
+ }
+ }
+ if ((ret = (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) ? 0 : ret)) {
+ av_frame_unref(avframe);
+ }
+ return ret;
+}
+
+
+// decoding
+
+static int
+tvh_context_receive_frame(TVHContext *self, AVFrame *avframe)
+{
+ int ret = -1;
+
+ while ((ret = avcodec_receive_frame(self->iavctx, avframe)) != AVERROR(EAGAIN)) {
+ if (ret || (ret = tvh_context_encode(self, avframe))) {
+ break;
+ }
+ }
+ return (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) ? 0 : ret;
+}
+
+
+static int
+tvh_context_decode_packet(TVHContext *self, const AVPacket *avpkt)
+{
+ int ret = -1;
+
+ while ((ret = avcodec_send_packet(self->iavctx, avpkt)) == AVERROR(EAGAIN)) {
+ if ((ret = tvh_context_receive_frame(self, self->iavframe))) {
+ break;
+ }
+ }
+ if (!ret) {
+ ret = tvh_context_receive_frame(self, self->iavframe);
+ }
+ return (ret == AVERROR_EOF) ? 0 : ret;
+}
+
+
+static int
+tvh_context_decode(TVHContext *self, AVPacket *avpkt)
+{
+ int ret = 0;
+
+ if (!avcodec_is_open(self->iavctx)) {
+ ret = tvh_context_open(self, OPEN_DECODER);
+ }
+ if (!ret && !(ret = _context_decode(self, avpkt))) {
+ ret = tvh_context_decode_packet(self, avpkt);
+ }
+ return (ret == AVERROR(EAGAIN) || ret == AVERROR_INVALIDDATA) ? 0 : ret;
+}
+
+
+static void
+tvh_context_flush(TVHContext *self)
+{
+ tvh_context_decode_packet(self, NULL);
+ tvh_context_encode_frame(self, NULL);
+}
+
+
+/* exposed */
+
+int
+tvh_context_get_int_opt(AVDictionary **opts, const char *key, int *value)
+{
+ AVDictionaryEntry *entry = NULL;
+
+ if (*opts &&
+ (entry = av_dict_get(*opts, key, NULL, AV_DICT_MATCH_CASE))) {
+ *value = atoi(entry->value);
+ return (av_dict_set(opts, key, NULL, 0) < 0) ? -1 : 0;
+ }
+ return 0;
+}
+
+
+int
+tvh_context_get_str_opt(AVDictionary **opts, const char *key, char **value)
+{
+ AVDictionaryEntry *entry = NULL;
+
+ if (*opts &&
+ (entry = av_dict_get(*opts, key, NULL, AV_DICT_MATCH_CASE))) {
+ *value = strdup(entry->value);
+ return (av_dict_set(opts, key, NULL, 0) < 0) ? -1 : 0;
+ }
+ return 0;
+}
+
+
+void
+tvh_context_close(TVHContext *self, int flush)
+{
+ if (flush) {
+ tvh_context_flush(self);
+ }
+ if (self->type->close) {
+ self->type->close(self);
+ }
+ if (self->oavctx && avcodec_is_open(self->oavctx)) {
+ avcodec_close(self->oavctx);
+ }
+ if (self->iavctx && avcodec_is_open(self->iavctx)) {
+ avcodec_close(self->iavctx);
+ }
+}
+
+
+int
+tvh_context_open_filters(TVHContext *self,
+ const char *source_name, const char *source_args,
+ const char *filters, const char *sink_name, ...)
+{
+ AVFilter *iavflt = NULL, *oavflt = NULL;
+ AVFilterInOut *iavfltio = NULL, *oavfltio = NULL;
+ AVBufferSrcParameters *par = NULL;
+ int i, ret = -1;
+
+ tvh_context_log(self, LOG_DEBUG, "filters: source args: '%s'", source_args);
+ tvh_context_log(self, LOG_DEBUG, "filters: filters: '%s'", filters);
+
+ if (!(self->avfltgraph = avfilter_graph_alloc()) ||
+ !(iavfltio = avfilter_inout_alloc()) ||
+ !(oavfltio = avfilter_inout_alloc()) ||
+ !(par = av_buffersrc_parameters_alloc())) {
+ ret = AVERROR(ENOMEM);
+ goto finish;
+ }
+
+ // source
+ if (!(iavflt = avfilter_get_by_name(source_name))) {
+ tvh_context_log(self, LOG_ERR, "filters: source filter'%s' not found",
+ source_name);
+ ret = AVERROR_FILTER_NOT_FOUND;
+ goto finish;
+ }
+ ret = avfilter_graph_create_filter(&self->iavfltctx, iavflt, "in",
+ source_args, NULL, self->avfltgraph);
+ if (ret < 0) {
+ tvh_context_log(self, LOG_ERR, "filters: failed to create 'in' filter");
+ goto finish;
+ }
+
+ // additional buffersrc params
+ if (!strcmp("buffer", source_name) && self->iavctx->hw_frames_ctx) { // hmm...
+ memset(par, 0, sizeof(AVBufferSrcParameters));
+ par->format = AV_PIX_FMT_NONE;
+ par->hw_frames_ctx = self->iavctx->hw_frames_ctx;
+ ret = av_buffersrc_parameters_set(self->iavfltctx, par);
+ if (ret < 0) {
+ tvh_context_log(self, LOG_ERR,
+ "filters: failed to set additional parameters");
+ goto finish;
+ }
+ }
+
+ // sink
+ if (!(oavflt = avfilter_get_by_name(sink_name))) {
+ tvh_context_log(self, LOG_ERR, "filters: sink filter '%s' not found",
+ sink_name);
+ ret = AVERROR_FILTER_NOT_FOUND;
+ goto finish;
+ }
+ ret = avfilter_graph_create_filter(&self->oavfltctx, oavflt, "out",
+ NULL, NULL, self->avfltgraph);
+ if (ret < 0) {
+ tvh_context_log(self, LOG_ERR, "filters: failed to create 'out' filter");
+ goto finish;
+ }
+
+ // sink options
+ va_list ap;
+ va_start(ap, sink_name);
+ ret = _context_filters_apply_sink_options(self, ap);
+ va_end(ap);
+ if (ret) {
+ goto finish;
+ }
+
+ // Endpoints for the filter graph.
+ iavfltio->name = av_strdup("out");
+ iavfltio->filter_ctx = self->oavfltctx;
+ iavfltio->pad_idx = 0;
+ iavfltio->next = NULL;
+
+ oavfltio->name = av_strdup("in");
+ oavfltio->filter_ctx = self->iavfltctx;
+ oavfltio->pad_idx = 0;
+ oavfltio->next = NULL;
+
+ if (!iavfltio->name || !oavfltio->name) {
+ ret = AVERROR(ENOMEM);
+ goto finish;
+ }
+
+ // filters
+ ret = avfilter_graph_parse_ptr(self->avfltgraph, filters,
+ &iavfltio, &oavfltio, NULL);
+ if (ret < 0) {
+ tvh_context_log(self, LOG_ERR, "filters: failed to add filters: '%s'",
+ filters);
+ goto finish;
+ }
+
+ // additional filtergraph params
+ if (!strcmp("buffer", source_name) && self->oavctx->opaque) { // hmm...
+ AVBufferRef *hw_device_ctx = self->oavctx->opaque;
+ for (i = 0; i < self->avfltgraph->nb_filters; i++) {
+ if (!(self->avfltgraph->filters[i]->hw_device_ctx =
+ av_buffer_ref(hw_device_ctx))) {
+ ret = -1;
+ goto finish;
+ }
+ }
+ }
+
+ avfilter_graph_set_auto_convert(self->avfltgraph,
+ AVFILTER_AUTO_CONVERT_NONE);
+
+ if ((ret = avfilter_graph_config(self->avfltgraph, NULL)) < 0) {
+ tvh_context_log(self, LOG_ERR, "filters: failed to config filter graph");
+ }
+
+finish:
+ if (par) {
+ av_freep(&par);
+ }
+ if (iavfltio) {
+ avfilter_inout_free(&iavfltio);
+ }
+ if (oavfltio) {
+ avfilter_inout_free(&oavfltio);
+ }
+ return (ret > 0) ? 0 : ret;
+}
+
+
+int
+tvh_context_deliver(TVHContext *self, th_pkt_t *pkt)
+{
+ return tvh_stream_deliver(self->stream, pkt);
+}
+
+
+int
+tvh_context_handle(TVHContext *self, th_pkt_t *pkt)
+{
+ int ret = 0;
+ uint8_t *data = NULL;
+ size_t size = 0;
+ AVPacket avpkt;
+
+ if ((size = pktbuf_len(pkt->pkt_payload)) && pktbuf_ptr(pkt->pkt_payload)) {
+ if (size >= TVH_INPUT_BUFFER_MAX_SIZE) {
+ tvh_context_log(self, LOG_ERR, "packet payload too big");
+ ret = AVERROR(EOVERFLOW);
+ }
+ else if (!(data = pktbuf_copy_data(pkt->pkt_payload))) {
+ tvh_context_log(self, LOG_ERR, "failed to copy packet payload");
+ ret = AVERROR(ENOMEM);
+ }
+ else {
+ av_init_packet(&avpkt);
+ if ((ret = av_packet_from_data(&avpkt, data, size))) { // takes ownership of data
+ tvh_context_log(self, LOG_ERR,
+ "failed to allocate AVPacket buffer");
+ av_freep(data);
+ }
+ else {
+ if (!self->input_gh && pkt->pkt_meta) {
+ pktbuf_ref_inc(pkt->pkt_meta);
+ self->input_gh = pkt->pkt_meta;
+ }
+ avpkt.pts = pkt->pkt_pts;
+ avpkt.dts = pkt->pkt_dts;
+ avpkt.duration = pkt->pkt_duration;
+ TVHPKT_SET(self->src_pkt, pkt);
+ ret = tvh_context_decode(self, &avpkt);
+ av_packet_unref(&avpkt); // will free data
+ }
+ }
+ }
+ return ret;
+}
+
+
+TVHContext *
+tvh_context_create(TVHStream *stream, TVHCodecProfile *profile,
+ AVCodec *iavcodec, AVCodec *oavcodec, pktbuf_t *input_gh)
+{
+ TVHContext *self = NULL;
+
+ if (!(self = calloc(1, sizeof(TVHContext)))) {
+ tvh_stream_log(stream, LOG_ERR, "failed to allocate context");
+ return NULL;
+ }
+ self->stream = stream;
+ self->profile = profile;
+ if (tvh_context_setup(self, iavcodec, oavcodec)) {
+ tvh_context_destroy(self);
+ return NULL;
+ }
+ if (input_gh) {
+ pktbuf_ref_inc(input_gh);
+ self->input_gh = input_gh;
+ }
+ return self;
+}
+
+
+void
+tvh_context_destroy(TVHContext *self)
+{
+ if (self) {
+ tvh_context_close(self, 0);
+ TVHPKT_CLEAR(self->src_pkt);
+ if (self->avfltgraph) {
+ avfilter_graph_free(&self->avfltgraph); // frees filter contexts
+ self->oavfltctx = NULL;
+ self->iavfltctx = NULL;
+ self->avfltgraph = NULL;
+ }
+ if (self->helper) {
+ self->helper = NULL;
+ }
+ if (self->input_gh) {
+ pktbuf_ref_dec(self->input_gh);
+ self->input_gh = NULL;
+ }
+ if (self->oavframe) {
+ av_frame_free(&self->oavframe);
+ self->oavframe = NULL;
+ }
+ if (self->iavframe) {
+ av_frame_free(&self->iavframe);
+ self->iavframe = NULL;
+ }
+ if (self->oavctx) {
+ avcodec_free_context(&self->oavctx); // frees extradata
+ self->oavctx = NULL;
+ }
+ if (self->iavctx) {
+ avcodec_free_context(&self->iavctx);
+ self->iavctx = NULL;
+ }
+ self->type = NULL;
+ self->profile = NULL;
+ self->stream = NULL;
+ free(self);
+ self = NULL;
+ }
+}
--- /dev/null
+/*
+ * tvheadend - Transcoding
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "internals.h"
+
+#include "parsers/bitstream.h"
+#include "parsers/parser_avc.h"
+#include "parsers/parser_h264.h"
+#include "parsers/parser_hevc.h"
+
+
+SLIST_HEAD(TVHContextHelpers, tvh_context_helper_t);
+static struct TVHContextHelpers tvh_decoder_helpers;
+static struct TVHContextHelpers tvh_encoder_helpers;
+
+
+#define tvh_context_helper_register(list, helper, kind) \
+ do { \
+ if ((helper)->type <= AVMEDIA_TYPE_UNKNOWN || \
+ (helper)->type >= AVMEDIA_TYPE_NB) { \
+ tvherror(LS_TRANSCODE, \
+ "incomplete/wrong definition for " #helper " helper"); \
+ return; \
+ } \
+ SLIST_INSERT_HEAD((list), (helper), link); \
+ tvhinfo(LS_TRANSCODE, "'" #helper "' %s helper registered", (kind)); \
+ } while (0)
+
+#define tvh_decoder_helper_register(helper) \
+ do { \
+ if (!(helper)->open) { \
+ tvherror(LS_TRANSCODE, \
+ "incomplete/wrong definition for " #helper " helper"); \
+ return; \
+ } \
+ tvh_context_helper_register(&tvh_decoder_helpers, helper, "decoder"); \
+ } while (0)
+
+#define tvh_encoder_helper_register(helper) \
+ do { \
+ if (!((helper)->open || (helper)->pack || (helper)->meta)) { \
+ tvherror(LS_TRANSCODE, \
+ "incomplete/wrong definition for " #helper " helper"); \
+ return; \
+ } \
+ tvh_context_helper_register(&tvh_encoder_helpers, helper, "encoder"); \
+ } while (0)
+
+
+static TVHContextHelper *
+tvh_context_helper_find(struct TVHContextHelpers *list, const AVCodec *codec)
+{
+ TVHContextHelper *helper = NULL;
+
+ SLIST_FOREACH(helper, list, link) {
+ if (helper->type == codec->type && helper->id == codec->id) {
+ tvhdebug(LS_TRANSCODE, "found helper for: %s", codec->name);
+ return helper;
+ }
+ }
+ return NULL;
+}
+
+
+/* decoders ================================================================= */
+
+/* shared by H264, AAC and VORBIS */
+static int
+tvh_extradata_open(TVHContext *self, AVDictionary **opts)
+{
+ size_t extradata_size = 0;
+
+ if (!(extradata_size = pktbuf_len(self->input_gh))) {
+ return AVERROR(EAGAIN);
+ }
+ if (extradata_size >= TVH_INPUT_BUFFER_MAX_SIZE) {
+ tvh_context_log(self, LOG_ERR, "extradata too big");
+ return AVERROR(EOVERFLOW);
+ }
+ if (self->iavctx->extradata) {
+ tvh_context_log(self, LOG_ERR, "extradata already set!");
+ return AVERROR(EALREADY);
+ }
+ if (!(self->iavctx->extradata =
+ av_mallocz(extradata_size + AV_INPUT_BUFFER_PADDING_SIZE))) {
+ tvh_context_log(self, LOG_ERR, "failed to alloc extradata");
+ return AVERROR(ENOMEM);
+ }
+ memcpy(self->iavctx->extradata, pktbuf_ptr(self->input_gh), extradata_size);
+ self->iavctx->extradata_size = extradata_size;
+ return 0;
+}
+
+
+/* H264 */
+static TVHContextHelper TVHH264Decoder = {
+ .type = AVMEDIA_TYPE_VIDEO,
+ .id = AV_CODEC_ID_H264,
+ .open = tvh_extradata_open,
+};
+
+/* THEORA */
+static TVHContextHelper TVHTHEORADecoder = {
+ .type = AVMEDIA_TYPE_VIDEO,
+ .id = AV_CODEC_ID_THEORA,
+ .open = tvh_extradata_open,
+};
+
+
+/* AAC */
+static TVHContextHelper TVHAACDecoder = {
+ .type = AVMEDIA_TYPE_AUDIO,
+ .id = AV_CODEC_ID_AAC,
+ .open = tvh_extradata_open,
+};
+
+
+/* VORBIS */
+static TVHContextHelper TVHVORBISDecoder = {
+ .type = AVMEDIA_TYPE_AUDIO,
+ .id = AV_CODEC_ID_VORBIS,
+ .open = tvh_extradata_open,
+};
+
+
+/* OPUS */
+static TVHContextHelper TVHOPUSDecoder = {
+ .type = AVMEDIA_TYPE_AUDIO,
+ .id = AV_CODEC_ID_OPUS,
+ .open = tvh_extradata_open,
+};
+
+
+static void
+tvh_decoder_helpers_register()
+{
+ SLIST_INIT(&tvh_decoder_helpers);
+ /* video */
+ tvh_decoder_helper_register(&TVHH264Decoder);
+ tvh_decoder_helper_register(&TVHTHEORADecoder);
+ /* audio */
+ tvh_decoder_helper_register(&TVHAACDecoder);
+ tvh_decoder_helper_register(&TVHVORBISDecoder);
+ tvh_decoder_helper_register(&TVHOPUSDecoder);
+}
+
+
+/* encoders ================================================================= */
+
+/* MPEG2VIDEO */
+static int
+tvh_mpeg2video_open(TVHContext *self, AVDictionary **opts)
+{
+ self->oavctx->gop_size = MIN(600, self->oavctx->gop_size);
+ self->oavctx->max_b_frames = MIN(16, self->oavctx->max_b_frames);
+ return 0;
+}
+
+static int
+tvh_mpeg2video_meta(TVHContext *self, AVPacket *avpkt, th_pkt_t *pkt)
+{
+ const uint8_t *data = avpkt->data;
+ size_t size = 12; // header size
+
+ if ((avpkt->flags & AV_PKT_FLAG_KEY)) {
+ // sequence header
+ if (avpkt->size < size || RB32(data) != 0x000001b3) { // SEQ_START_CODE
+ tvh_context_log(self, LOG_ERR,
+ "MPEG2 header: missing sequence header");
+ return -1;
+ }
+ // load intra quantiser matrix
+ if (data[size-1] & 0x02) {
+ if (avpkt->size < size + 64) {
+ tvh_context_log(self, LOG_ERR,
+ "MPEG2 header: missing load intra quantiser matrix");
+ return -1;
+ }
+ size += 64;
+ }
+ // load non-intra quantiser matrix
+ if (data[size-1] & 0x01) {
+ if (avpkt->size < size + 64) {
+ tvh_context_log(self, LOG_ERR,
+ "MPEG2 header: missing load non-intra quantiser matrix");
+ return -1;
+ }
+ size += 64;
+ }
+ // sequence extension
+ if (avpkt->size < size + 10 || RB32(data + size) != 0x000001b5) { // EXT_START_CODE
+ tvh_context_log(self, LOG_ERR,
+ "MPEG2 header: missing sequence extension");
+ return -1;
+ }
+ size += 10;
+ // sequence display extension
+ if (RB32(data + size) == 0x000001b5) { // EXT_START_CODE
+ if (avpkt->size < size + 12) {
+ tvh_context_log(self, LOG_ERR,
+ "MPEG2 header: missing sequence display extension");
+ return -1;
+ }
+ size += 12;
+ }
+ // group of pictures
+ if (avpkt->size < size + 8 || RB32(data + size) != 0x000001b8) { // GOP_START_CODE
+ tvh_context_log(self, LOG_ERR,
+ "MPEG2 header: missing group of pictures");
+ return -1;
+ }
+ size += 8;
+ if (!(pkt->pkt_meta = pktbuf_create(data, size))) {
+ return -1;
+ }
+ return 0;
+ }
+ return 1;
+}
+
+static TVHContextHelper TVHMPEG2VIDEOEncoder = {
+ .type = AVMEDIA_TYPE_VIDEO,
+ .id = AV_CODEC_ID_MPEG2VIDEO,
+ .open = tvh_mpeg2video_open,
+ .meta = tvh_mpeg2video_meta,
+};
+
+
+/* H264 */
+static int
+tvh_h264_meta(TVHContext *self, AVPacket *avpkt, th_pkt_t *pkt)
+{
+ if ((avpkt->flags & AV_PKT_FLAG_KEY) && (avpkt->size > 6) &&
+ (RB32(avpkt->data) == 0x00000001 || RB24(avpkt->data) == 0x000001)) {
+ const uint8_t *p = avpkt->data;
+ const uint8_t *end = p + avpkt->size;
+ const uint8_t *nal_start, *nal_end;
+ int size = 0;
+
+ nal_start = avc_find_startcode(p, end);
+ for (;;) {
+ while (nal_start < end && !*(nal_start++));
+ if (nal_start == end) {
+ break;
+ }
+ nal_end = avc_find_startcode(nal_start, end);
+ int nal_len = nal_end - nal_start;
+ uint8_t nal_type = (nal_start[0] & 0x1f);
+ if ((nal_type == H264_NAL_SPS &&
+ nal_len >= 4 && nal_len <= UINT16_MAX) ||
+ (nal_type == H264_NAL_PPS &&
+ nal_len <= UINT16_MAX)) {
+ size = nal_end - p;
+ nal_start = nal_end;
+ continue;
+ }
+ break;
+ }
+ if (size) {
+ if (!(pkt->pkt_meta = pktbuf_create(avpkt->data, size))) {
+ return -1;
+ }
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static TVHContextHelper TVHH264Encoder = {
+ .type = AVMEDIA_TYPE_VIDEO,
+ .id = AV_CODEC_ID_H264,
+ .meta = tvh_h264_meta,
+};
+
+
+/* HEVC */
+static int
+tvh_hevc_meta(TVHContext *self, AVPacket *avpkt, th_pkt_t *pkt)
+{
+
+ if ((avpkt->flags & AV_PKT_FLAG_KEY) && (avpkt->size >= 6) &&
+ (*avpkt->data != 1) &&
+ (RB24(avpkt->data) == 1 || RB32(avpkt->data) == 1)) {
+ const uint8_t *p = avpkt->data;
+ const uint8_t *end = p + avpkt->size;
+ const uint8_t *nal_start, *nal_end;
+ int size = 0;
+
+ nal_start = avc_find_startcode(p, end);
+ for (;;) {
+ while (nal_start < end && !*(nal_start++));
+ if (nal_start == end) {
+ break;
+ }
+ nal_end = avc_find_startcode(nal_start, end);
+ uint8_t nal_type = ((nal_start[0] >> 1) & 0x3f);
+ switch (nal_type) {
+ case HEVC_NAL_VPS:
+ case HEVC_NAL_SPS:
+ case HEVC_NAL_PPS:
+ case HEVC_NAL_SEI_PREFIX:
+ case HEVC_NAL_SEI_SUFFIX:
+ size = nal_end - p;
+ nal_start = nal_end;
+ continue;
+ default:
+ break;
+ }
+ break;
+ }
+ if (size) {
+ if (!(pkt->pkt_meta = pktbuf_create(avpkt->data, size))) {
+ return -1;
+ }
+ return 0;
+ }
+ }
+ return 1;
+}
+
+static TVHContextHelper TVHHEVCEncoder = {
+ .type = AVMEDIA_TYPE_VIDEO,
+ .id = AV_CODEC_ID_HEVC,
+ .meta = tvh_hevc_meta,
+};
+
+
+/* VPX */
+static int
+tvh_libvpx_open(TVHContext *self, AVDictionary **opts)
+{
+ self->oavctx->gop_size = MIN(128, self->oavctx->gop_size);
+ return 0;
+}
+
+static TVHContextHelper TVHVP8Encoder = {
+ .type = AVMEDIA_TYPE_VIDEO,
+ .id = AV_CODEC_ID_VP8,
+ .open = tvh_libvpx_open,
+};
+
+static TVHContextHelper TVHVP9Encoder = {
+ .type = AVMEDIA_TYPE_VIDEO,
+ .id = AV_CODEC_ID_VP9,
+ .open = tvh_libvpx_open,
+};
+
+
+/* AAC */
+static void
+tvh_aac_pack_adts_header(TVHContext *self, pktbuf_t *pb)
+{
+ // XXX: this really should happen in the muxer
+ int chan_conf = (self->oavctx->channels == 8) ? 7 : self->oavctx->channels;
+ bitstream_t bs;
+
+ // https://wiki.multimedia.cx/index.php?title=ADTS
+ init_wbits(&bs, pktbuf_ptr(pb), 56); // 7 bytes
+ put_bits(&bs, 0xfff, 12); /* syncword */
+ put_bits(&bs, 0, 1); /* version */
+ put_bits(&bs, 0, 2); /* layer */
+ put_bits(&bs, 1, 1); /* protection absent */
+ put_bits(&bs, self->oavctx->profile, 2); /* profile (aot-1) */
+ put_bits(&bs, self->sri, 4); /* sample rate index */
+ put_bits(&bs, 0, 1); /* private bit */
+ put_bits(&bs, chan_conf, 3); /* channel configuration */
+ put_bits(&bs, 0, 1); /* originality */
+ put_bits(&bs, 0, 1); /* home */
+ put_bits(&bs, 0, 1); /* copyrighted id bit */
+ put_bits(&bs, 0, 1); /* copyright id start */
+ put_bits(&bs, pktbuf_len(pb), 13); /* frame length */
+ put_bits(&bs, 0x7ff, 11); /* buffer fullness */
+ put_bits(&bs, 0, 2); /* rdbs in frame (-1)*/
+}
+
+static th_pkt_t *
+tvh_aac_pack(TVHContext *self, AVPacket *avpkt)
+{
+ static const size_t header_size = 7, max_size = ((1 << 13) - 1);
+ size_t pkt_size = 0;
+ th_pkt_t *pkt = NULL;
+
+ // afaict: we write an adts header if there isn't one already, why would
+ // there be one in the first place?.
+ // originally there was a check for avpkt->size < 2, I don't get it.
+ // max aac frame size = 768 bytes per channel, max writable size 13 bits
+ if (avpkt->size > (768 * self->oavctx->channels)) {
+ tvh_context_log(self, LOG_WARNING,
+ "packet size (%d) > aac max frame size (%d for %d channels)",
+ avpkt->size, (768 * self->oavctx->channels),
+ self->oavctx->channels);
+ }
+ if ((pkt_size = avpkt->size + header_size) > max_size) {
+ tvh_context_log(self, LOG_ERR, "aac frame data too big");
+ }
+ else if (avpkt->data[0] != 0xff || (avpkt->data[1] & 0xf0) != 0xf0) {
+ if ((pkt = pkt_create(NULL, pkt_size, avpkt->pts, avpkt->dts))) {
+ tvh_aac_pack_adts_header(self, pkt->pkt_payload);
+ memcpy(pktbuf_ptr(pkt->pkt_payload) + header_size,
+ avpkt->data, avpkt->size);
+ }
+ }
+ else {
+ pkt = pkt_create(avpkt->data, avpkt->size, avpkt->pts, avpkt->dts);
+ }
+ return pkt;
+}
+
+static TVHContextHelper TVHAACEncoder = {
+ .type = AVMEDIA_TYPE_AUDIO,
+ .id = AV_CODEC_ID_AAC,
+ .pack = tvh_aac_pack,
+};
+
+
+static void
+tvh_encoder_helpers_register()
+{
+ SLIST_INIT(&tvh_encoder_helpers);
+ /* video */
+ tvh_encoder_helper_register(&TVHMPEG2VIDEOEncoder);
+ tvh_encoder_helper_register(&TVHH264Encoder);
+ tvh_encoder_helper_register(&TVHHEVCEncoder);
+ tvh_encoder_helper_register(&TVHVP8Encoder);
+ tvh_encoder_helper_register(&TVHVP9Encoder);
+ /* audio */
+ tvh_encoder_helper_register(&TVHAACEncoder);
+}
+
+
+/* public =================================================================== */
+
+TVHContextHelper *
+tvh_decoder_helper_find(const AVCodec *codec)
+{
+ return tvh_context_helper_find(&tvh_decoder_helpers, codec);
+}
+
+
+TVHContextHelper *
+tvh_encoder_helper_find(const AVCodec *codec)
+{
+ return tvh_context_helper_find(&tvh_encoder_helpers, codec);
+}
+
+
+void
+tvh_context_helpers_register()
+{
+ tvh_decoder_helpers_register();
+ tvh_encoder_helpers_register();
+}
+
+
+void
+tvh_context_helpers_forget()
+{
+ tvhinfo(LS_TRANSCODE, "forgetting context helpers");
+ while (!SLIST_EMPTY(&tvh_encoder_helpers)) {
+ SLIST_REMOVE_HEAD(&tvh_encoder_helpers, link);
+ }
+ while (!SLIST_EMPTY(&tvh_decoder_helpers)) {
+ SLIST_REMOVE_HEAD(&tvh_decoder_helpers, link);
+ }
+}
--- /dev/null
+/*
+ * tvheadend - Transcoding
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hwaccels.h"
+
+#if ENABLE_VAAPI
+#include "vaapi.h"
+#endif
+
+#include <libavutil/pixdesc.h>
+
+
+/* decoding ================================================================= */
+
+/* lifted from libavcodec/utils.c */
+static AVHWAccel *
+find_hwaccel(enum AVCodecID codec_id, enum AVPixelFormat pix_fmt)
+{
+ AVHWAccel *hwaccel = NULL;
+
+ while ((hwaccel = av_hwaccel_next(hwaccel))) {
+ if (hwaccel->id == codec_id && hwaccel->pix_fmt == pix_fmt) {
+ return hwaccel;
+ }
+ }
+ return NULL;
+}
+
+
+static int
+hwaccels_decode_setup_context(AVCodecContext *avctx,
+ const enum AVPixelFormat pix_fmt)
+{
+ AVHWAccel *hwa = NULL;
+
+ if (!(hwa = find_hwaccel(avctx->codec_id, pix_fmt))) {
+ tvherror(LS_TRANSCODE, "no AVHWAccel for the pixel format");
+ return AVERROR(ENOENT);
+ }
+ switch (pix_fmt) {
+#if ENABLE_VAAPI
+ case AV_PIX_FMT_VAAPI:
+ return vaapi_decode_setup_context(avctx);
+#endif
+ default:
+ break;
+ }
+ return -1;
+}
+
+
+enum AVPixelFormat
+hwaccels_decode_get_format(AVCodecContext *avctx,
+ const enum AVPixelFormat *pix_fmts)
+{
+ enum AVPixelFormat pix_fmt = AV_PIX_FMT_NONE;
+ const AVPixFmtDescriptor *desc;
+ int i;
+
+ for (i = 0; pix_fmts[i] != AV_PIX_FMT_NONE; i++) {
+ pix_fmt = pix_fmts[i];
+ if ((desc = av_pix_fmt_desc_get(pix_fmt))) {
+ tvhdebug(LS_TRANSCODE, "trying pix_fmt: %s", desc->name);
+ if ((desc->flags & AV_PIX_FMT_FLAG_HWACCEL) &&
+ !hwaccels_decode_setup_context(avctx, pix_fmt)) {
+ break;
+ }
+ }
+ }
+ return pix_fmt;
+}
+
+
+void
+hwaccels_decode_close_context(AVCodecContext *avctx)
+{
+ if (avctx->hwaccel_context) {
+ switch (avctx->pix_fmt) {
+#if ENABLE_VAAPI
+ case AV_PIX_FMT_VAAPI:
+ vaapi_decode_close_context(avctx);
+ break;
+#endif
+ default:
+ break;
+ }
+ avctx->hwaccel_context = NULL;
+ }
+}
+
+
+/* encoding ================================================================= */
+
+int
+hwaccels_encode_setup_context(AVCodecContext *avctx)
+{
+ switch (avctx->pix_fmt) {
+#if ENABLE_VAAPI
+ case AV_PIX_FMT_VAAPI:
+ return vaapi_encode_setup_context(avctx);
+#endif
+ default:
+ break;
+ }
+ return 0;
+}
+
+
+void
+hwaccels_encode_close_context(AVCodecContext *avctx)
+{
+ switch (avctx->pix_fmt) {
+#if ENABLE_VAAPI
+ case AV_PIX_FMT_VAAPI:
+ vaapi_encode_close_context(avctx);
+ break;
+#endif
+ default:
+ break;
+ }
+}
+
+
+/* module =================================================================== */
+
+void
+hwaccels_init(void)
+{
+}
+
+
+void
+hwaccels_done(void)
+{
+#if ENABLE_VAAPI
+ vaapi_done();
+#endif
+}
--- /dev/null
+/*
+ * tvheadend - Transcoding
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef TVH_TRANSCODING_TRANSCODE_HWACCELS_H__
+#define TVH_TRANSCODING_TRANSCODE_HWACCELS_H__
+
+
+#include "tvheadend.h"
+
+#include <libavcodec/avcodec.h>
+
+
+/* decoding ================================================================= */
+
+enum AVPixelFormat
+hwaccels_decode_get_format(AVCodecContext *avctx,
+ const enum AVPixelFormat *pix_fmts);
+
+void
+hwaccels_decode_close_context(AVCodecContext *avctx);
+
+
+/* encoding ================================================================= */
+
+int
+hwaccels_encode_setup_context(AVCodecContext *avctx);
+
+void
+hwaccels_encode_close_context(AVCodecContext *avctx);
+
+
+/* module =================================================================== */
+
+void
+hwaccels_init(void);
+
+void
+hwaccels_done(void);
+
+
+#endif // TVH_TRANSCODING_TRANSCODE_HWACCELS_H__
--- /dev/null
+/*
+ * tvheadend - Transcoding
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "vaapi.h"
+
+#include <libavcodec/vaapi.h>
+#include <libavutil/hwcontext.h>
+#include <libavutil/hwcontext_vaapi.h>
+#include <libavutil/pixdesc.h>
+
+#include <va/va.h>
+
+
+static AVBufferRef *hw_device_ref = NULL;
+
+
+typedef struct tvh_vaapi_context_t {
+ struct vaapi_context;
+ VAEntrypoint entrypoint;
+ enum AVPixelFormat io_format;
+ enum AVPixelFormat sw_format;
+ int width;
+ int height;
+ AVBufferRef *hw_device_ref;
+ AVBufferRef *hw_frames_ref;
+} TVHVAContext;
+
+
+static int
+tvhva_init()
+{
+ static const char *renderD_fmt = "/dev/dri/renderD%d";
+ static const char *card_fmt = "/dev/dri/card%d";
+ int i, dev_num;
+ char device[32];
+
+ //search for valid graphics device
+ for (i = 0; i < 6; i++) {
+ memset(device, 0, sizeof(device));
+ if (i < 3) {
+ dev_num = i + 128;
+ snprintf(device, sizeof(device), renderD_fmt, dev_num);
+ }
+ else {
+ dev_num = i - 3;
+ snprintf(device, sizeof(device), card_fmt, dev_num);
+ }
+ tvhdebug(LS_VAAPI, "trying device: %s", device);
+ if (av_hwdevice_ctx_create(&hw_device_ref, AV_HWDEVICE_TYPE_VAAPI,
+ device, NULL, 0)) {
+ tvhdebug(LS_VAAPI,
+ "failed to create a context for device: %s", device);
+ continue;
+ }
+ else {
+ tvhinfo(LS_VAAPI,
+ "succesful context creation for device: %s", device);
+ return 0;
+ }
+ }
+ tvherror(LS_VAAPI, "failed to find suitable VAAPI device");
+ return -1;
+}
+
+
+static void
+tvhva_done()
+{
+ if (hw_device_ref) {
+ av_buffer_unref(&hw_device_ref);
+ hw_device_ref = NULL;
+ }
+}
+
+
+/* TVHVAContext ============================================================= */
+
+static void
+tvhva_context_destroy(TVHVAContext *self)
+{
+ if (self) {
+ if (self->context_id != VA_INVALID_ID) {
+ vaDestroyContext(self->display, self->context_id);
+ self->context_id = VA_INVALID_ID;
+ }
+ if (self->config_id != VA_INVALID_ID) {
+ vaDestroyConfig(self->display, self->config_id);
+ self->config_id = VA_INVALID_ID;
+ }
+ self->display = NULL;
+ if (self->hw_frames_ref) {
+ av_buffer_unref(&self->hw_frames_ref);
+ self->hw_frames_ref = NULL;
+ }
+ if (self->hw_device_ref) {
+ av_buffer_unref(&self->hw_device_ref);
+ self->hw_device_ref = NULL;
+ }
+ free(self);
+ self = NULL;
+ }
+}
+
+
+static VADisplay *
+tvhva_context_display(TVHVAContext *self)
+{
+ AVHWDeviceContext *hw_device_ctx = NULL;
+
+ if (!hw_device_ref && tvhva_init()) {
+ return NULL;
+ }
+ if (!(self->hw_device_ref = av_buffer_ref(hw_device_ref))) {
+ return NULL;
+ }
+ hw_device_ctx = (AVHWDeviceContext*)self->hw_device_ref->data;
+ return ((AVVAAPIDeviceContext *)hw_device_ctx->hwctx)->display;
+}
+
+
+static VAProfile
+tvhva_context_profile(TVHVAContext *self, AVCodecContext *avctx)
+{
+ VAStatus va_res = VA_STATUS_ERROR_UNKNOWN;
+ VAProfile profile = VAProfileNone, check = VAProfileNone, *profiles = NULL;
+ int i, j, profiles_max, profiles_len;
+
+ switch (avctx->codec->id) {
+ case AV_CODEC_ID_MPEG2VIDEO:
+ switch (avctx->profile) {
+ case FF_PROFILE_UNKNOWN:
+ case FF_PROFILE_MPEG2_MAIN:
+ check = VAProfileMPEG2Main;
+ break;
+ case FF_PROFILE_MPEG2_SIMPLE:
+ check = VAProfileMPEG2Simple;
+ break;
+ default:
+ break;
+ }
+ break;
+ case AV_CODEC_ID_H264:
+ switch (avctx->profile) {
+ case FF_PROFILE_UNKNOWN:
+ case FF_PROFILE_H264_HIGH:
+ check = VAProfileH264High;
+ break;
+ case FF_PROFILE_H264_BASELINE:
+ check = VAProfileH264Baseline;
+ break;
+ case FF_PROFILE_H264_CONSTRAINED_BASELINE:
+ check = VAProfileH264ConstrainedBaseline;
+ break;
+ case FF_PROFILE_H264_MAIN:
+ check = VAProfileH264Main;
+ break;
+ default:
+ break;
+ }
+ break;
+ case AV_CODEC_ID_HEVC:
+ switch (avctx->profile) {
+ case FF_PROFILE_UNKNOWN:
+ case FF_PROFILE_HEVC_MAIN:
+ check = VAProfileHEVCMain;
+ break;
+ case FF_PROFILE_HEVC_MAIN_10:
+ check = VAProfileHEVCMain10;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (check != VAProfileNone &&
+ (profiles_max = vaMaxNumProfiles(self->display)) > 0 &&
+ (profiles = calloc(profiles_max, sizeof(VAProfile)))) {
+ for (j = 0; j < profiles_max; j++) {
+ profiles[j] = VAProfileNone;
+ }
+ va_res = vaQueryConfigProfiles(self->display,
+ profiles, &profiles_len);
+ if (va_res == VA_STATUS_SUCCESS) {
+ for (i = 0; i < profiles_len; i++) {
+ if (profiles[i] == check) {
+ profile = check;
+ break;
+ }
+ }
+ }
+ free(profiles);
+ }
+
+ return profile;
+}
+
+
+static int
+tvhva_context_check_profile(TVHVAContext *self, VAProfile profile)
+{
+ VAStatus va_res = VA_STATUS_ERROR_UNKNOWN;
+ VAEntrypoint *entrypoints = NULL;
+ int res = -1, i, entrypoints_max, entrypoints_len;
+
+ if ((entrypoints_max = vaMaxNumEntrypoints(self->display)) > 0 &&
+ (entrypoints = calloc(entrypoints_max, sizeof(VAEntrypoint)))) {
+ va_res = vaQueryConfigEntrypoints(self->display, profile,
+ entrypoints, &entrypoints_len);
+ if (va_res == VA_STATUS_SUCCESS) {
+ for (i = 0; i < entrypoints_len; i++) {
+ if (entrypoints[i] == self->entrypoint) {
+ res = 0;
+ break;
+ }
+ }
+ }
+ free(entrypoints);
+ }
+ return res;
+}
+
+
+static unsigned int
+tvhva_get_format(enum AVPixelFormat pix_fmt)
+{
+ switch (pix_fmt) {
+ //case AV_PIX_FMT_YUV420P: // the cake is a lie
+ case AV_PIX_FMT_NV12:
+ return VA_RT_FORMAT_YUV420;
+ case AV_PIX_FMT_YUV422P:
+ case AV_PIX_FMT_UYVY422:
+ case AV_PIX_FMT_YUYV422:
+ return VA_RT_FORMAT_YUV422;
+ case AV_PIX_FMT_GRAY8:
+ return VA_RT_FORMAT_YUV400;
+ default:
+ return 0;
+ }
+}
+
+
+static int
+tvhva_context_config(TVHVAContext *self, VAProfile profile, unsigned int format)
+{
+ VAStatus va_res = VA_STATUS_ERROR_UNKNOWN;
+ VAConfigAttrib attrib = { VAConfigAttribRTFormat, VA_ATTRIB_NOT_SUPPORTED };
+
+ // vaCreateConfig
+ va_res = vaGetConfigAttributes(self->display, profile, self->entrypoint,
+ &attrib, 1);
+ if (va_res != VA_STATUS_SUCCESS) {
+ tvherror(LS_VAAPI, "vaGetConfigAttributes: %s", vaErrorStr(va_res));
+ return -1;
+ }
+ if (attrib.value == VA_ATTRIB_NOT_SUPPORTED || !(attrib.value & format)) {
+ tvherror(LS_VAAPI, "unsupported VA_RT_FORMAT");
+ return -1;
+ }
+ attrib.value = format;
+ va_res = vaCreateConfig(self->display, profile, self->entrypoint,
+ &attrib, 1, &self->config_id);
+ if (va_res != VA_STATUS_SUCCESS) {
+ tvherror(LS_VAAPI, "vaCreateConfig: %s", vaErrorStr(va_res));
+ return -1;
+ }
+ return 0;
+}
+
+
+static int
+tvhva_context_check_constraints(TVHVAContext *self)
+{
+ AVVAAPIHWConfig *va_config = NULL;
+ AVHWFramesConstraints *hw_constraints = NULL;
+ enum AVPixelFormat sw_format = AV_PIX_FMT_NONE;
+ const AVPixFmtDescriptor *sw_desc = NULL, *io_desc = NULL;
+ int i, ret = 0;
+
+ if (!(va_config = av_hwdevice_hwconfig_alloc(self->hw_device_ref))) {
+ tvherror(LS_VAAPI, "failed to allocate hwconfig");
+ return AVERROR(ENOMEM);
+ }
+ va_config->config_id = self->config_id;
+
+ hw_constraints =
+ av_hwdevice_get_hwframe_constraints(self->hw_device_ref, va_config);
+ if (!hw_constraints) {
+ tvherror(LS_VAAPI, "failed to get constraints");
+ av_freep(&va_config);
+ return -1;
+ }
+
+ if (self->io_format != AV_PIX_FMT_NONE) {
+ for (i = 0; hw_constraints->valid_sw_formats[i] != AV_PIX_FMT_NONE; i++) {
+ sw_format = hw_constraints->valid_sw_formats[i];
+ if (sw_format == self->io_format) {
+ self->sw_format = sw_format;
+ break;
+ }
+ }
+ if (self->sw_format == AV_PIX_FMT_NONE &&
+ (io_desc = av_pix_fmt_desc_get(self->io_format))) {
+ for (i = 0; hw_constraints->valid_sw_formats[i] != AV_PIX_FMT_NONE; i++) {
+ sw_format = hw_constraints->valid_sw_formats[i];
+ if ((sw_desc = av_pix_fmt_desc_get(sw_format)) &&
+ sw_desc->nb_components == io_desc->nb_components &&
+ sw_desc->log2_chroma_w == io_desc->log2_chroma_w &&
+ sw_desc->log2_chroma_h == io_desc->log2_chroma_h) {
+ self->sw_format = sw_format;
+ break;
+ }
+ }
+ }
+ }
+ if (self->sw_format == AV_PIX_FMT_NONE) {
+ tvherror(LS_VAAPI, "VAAPI hardware does not support pixel format: %s",
+ av_get_pix_fmt_name(self->io_format));
+ ret = AVERROR(EINVAL);
+ goto end;
+ }
+
+ // Ensure the picture size is supported by the hardware.
+ if (self->width < hw_constraints->min_width ||
+ self->height < hw_constraints->min_height ||
+ self->width > hw_constraints->max_width ||
+ self->height > hw_constraints->max_height) {
+ tvherror(LS_VAAPI, "VAAPI hardware does not support image "
+ "size %dx%d (constraints: width %d-%d height %d-%d).",
+ self->width, self->height,
+ hw_constraints->min_width, hw_constraints->max_width,
+ hw_constraints->min_height, hw_constraints->max_height);
+ ret = AVERROR(EINVAL);
+ }
+
+end:
+ av_hwframe_constraints_free(&hw_constraints);
+ av_freep(&va_config);
+ return ret;
+}
+
+
+static int
+tvhva_context_setup(TVHVAContext *self, AVCodecContext *avctx)
+{
+ VAProfile profile = VAProfileNone;
+ unsigned int format = 0;
+ VAStatus va_res = VA_STATUS_ERROR_UNKNOWN;
+ AVHWFramesContext *hw_frames_ctx = NULL;
+ AVVAAPIFramesContext *va_frames = NULL;
+
+ if (!(self->display = tvhva_context_display(self))) {
+ return -1;
+ }
+ if ((profile = tvhva_context_profile(self, avctx)) == VAProfileNone ||
+ tvhva_context_check_profile(self, profile)) {
+ tvherror(LS_VAAPI, "unsupported codec: %s and/or profile: %s",
+ avctx->codec->name,
+ av_get_profile_name(avctx->codec, avctx->profile));
+ return -1;
+ }
+ if (!(format = tvhva_get_format(self->io_format))) {
+ tvherror(LS_VAAPI, "unsupported pixel format: %s",
+ av_get_pix_fmt_name(self->io_format));
+ return -1;
+ }
+
+ if (tvhva_context_config(self, profile, format) ||
+ tvhva_context_check_constraints(self)) {
+ return -1;
+ }
+
+ if (!(self->hw_frames_ref = av_hwframe_ctx_alloc(self->hw_device_ref))) {
+ tvherror(LS_VAAPI, "failed to create VAAPI frame context.");
+ return AVERROR(ENOMEM);
+ }
+ hw_frames_ctx = (AVHWFramesContext*)self->hw_frames_ref->data;
+ hw_frames_ctx->format = AV_PIX_FMT_VAAPI;
+ hw_frames_ctx->sw_format = self->sw_format;
+ hw_frames_ctx->width = self->width;
+ hw_frames_ctx->height = self->height;
+ hw_frames_ctx->initial_pool_size = 32;
+
+ if (av_hwframe_ctx_init(self->hw_frames_ref) < 0) {
+ tvherror(LS_VAAPI, "failed to initialise VAAPI frame context");
+ return -1;
+ }
+
+ // vaCreateContext
+ if (self->entrypoint == VAEntrypointVLD) { // decode only
+ va_frames = hw_frames_ctx->hwctx;
+ va_res = vaCreateContext(self->display, self->config_id,
+ self->width, self->height,
+ VA_PROGRESSIVE,
+ va_frames->surface_ids, va_frames->nb_surfaces,
+ &self->context_id);
+ if (va_res != VA_STATUS_SUCCESS) {
+ tvherror(LS_VAAPI, "vaCreateContext: %s", vaErrorStr(va_res));
+ return -1;
+ }
+ }
+
+ if (!(avctx->hw_frames_ctx = av_buffer_ref(self->hw_frames_ref))) {
+ return AVERROR(ENOMEM);
+ }
+
+ avctx->sw_pix_fmt = self->sw_format;
+ avctx->thread_count = 1;
+
+ return 0;
+}
+
+
+static TVHVAContext *
+tvhva_context_create(AVCodecContext *avctx, VAEntrypoint entrypoint)
+{
+ TVHVAContext *self = NULL;
+
+ if (!(self = calloc(1, sizeof(TVHVAContext)))) {
+ tvherror(LS_VAAPI, "failed to allocate vaapi context");
+ return NULL;
+ }
+ self->display = NULL;
+ self->config_id = VA_INVALID_ID;
+ self->context_id = VA_INVALID_ID;
+ self->entrypoint = entrypoint;
+ if (avctx->sw_pix_fmt == AV_PIX_FMT_YUV420P) {
+ self->io_format = AV_PIX_FMT_NV12;
+ }
+ else {
+ self->io_format = avctx->sw_pix_fmt;
+ }
+ self->sw_format = AV_PIX_FMT_NONE;
+ self->width = avctx->coded_width;
+ self->height = avctx->coded_height;
+ self->hw_device_ref = NULL;
+ self->hw_frames_ref = NULL;
+ if (tvhva_context_setup(self, avctx)) {
+ tvhva_context_destroy(self);
+ return NULL;
+ }
+ return self;
+}
+
+
+/* decoding ================================================================= */
+
+static int
+vaapi_get_buffer2(AVCodecContext *avctx, AVFrame *avframe, int flags)
+{
+ if (!(avctx->codec->capabilities & AV_CODEC_CAP_DR1)) {
+ return avcodec_default_get_buffer2(avctx, avframe, flags);
+ }
+ return av_hwframe_get_buffer(avctx->hw_frames_ctx, avframe, 0);
+}
+
+
+int
+vaapi_decode_setup_context(AVCodecContext *avctx)
+{
+ if (!(avctx->hwaccel_context =
+ tvhva_context_create(avctx, VAEntrypointVLD))) {
+ return -1;
+ }
+
+ avctx->get_buffer2 = vaapi_get_buffer2;
+ avctx->thread_safe_callbacks = 0;
+
+ return 0;
+}
+
+
+void
+vaapi_decode_close_context(AVCodecContext *avctx)
+{
+ /*if (avctx->hw_frames_ctx) {
+ av_buffer_unref(&avctx->hw_frames_ctx);
+ avctx->hw_frames_ctx = NULL;
+ }*/
+ tvhva_context_destroy(avctx->hwaccel_context);
+}
+
+
+/* encoding ================================================================= */
+
+int
+vaapi_encode_setup_context(AVCodecContext *avctx)
+{
+ TVHVAContext *hwaccel_context = NULL;
+
+ if (!(hwaccel_context =
+ tvhva_context_create(avctx, VAEntrypointEncSlice))) {
+ return -1;
+ }
+ if (!(avctx->opaque = av_buffer_ref(hwaccel_context->hw_device_ref))) {
+ tvhva_context_destroy(hwaccel_context);
+ return AVERROR(ENOMEM);
+ }
+ tvhva_context_destroy(hwaccel_context);
+ return 0;
+}
+
+
+void
+vaapi_encode_close_context(AVCodecContext *avctx)
+{
+ /*if (avctx->hw_frames_ctx) {
+ av_buffer_unref(&avctx->hw_frames_ctx);
+ avctx->hw_frames_ctx = NULL;
+ }*/
+ if (avctx->opaque) {
+ AVBufferRef *hw_device_ctx = avctx->opaque;
+ av_buffer_unref(&hw_device_ctx);
+ avctx->opaque = NULL;
+ }
+}
+
+
+/* module =================================================================== */
+
+void
+vaapi_done()
+{
+ tvhva_done();
+}
--- /dev/null
+/*
+ * tvheadend - Transcoding
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef TVH_TRANSCODING_TRANSCODE_HWACCELS_VAAPI_H__
+#define TVH_TRANSCODING_TRANSCODE_HWACCELS_VAAPI_H__
+
+
+#include "tvheadend.h"
+
+#include <libavcodec/avcodec.h>
+
+
+/* decoding ================================================================= */
+
+int
+vaapi_decode_setup_context(AVCodecContext *avctx);
+
+void
+vaapi_decode_close_context(AVCodecContext *avctx);
+
+
+/* encoding ================================================================= */
+
+int
+vaapi_encode_setup_context(AVCodecContext *avctx);
+
+void
+vaapi_encode_close_context(AVCodecContext *avctx);
+
+
+/* module =================================================================== */
+
+void
+vaapi_done(void);
+
+
+#endif // TVH_TRANSCODING_TRANSCODE_HWACCELS_VAAPI_H__
--- /dev/null
+/*
+ * tvheadend - Transcoding
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef TVH_TRANSCODING_TRANSCODE_INTERNALS_H__
+#define TVH_TRANSCODING_TRANSCODE_INTERNALS_H__
+
+
+#include "log.h"
+
+#include "transcoding/codec.h"
+#include "transcoding/memutils.h"
+
+#include <libavcodec/avcodec.h>
+#include <libavfilter/avfilter.h>
+#include <libavfilter/buffersrc.h>
+#include <libavfilter/buffersink.h>
+#include <libavutil/pixdesc.h>
+
+
+#define tvh_st_t streaming_target_t
+#define tvh_ss_t streaming_start_t
+#define tvh_sm_t streaming_message_t
+
+
+extern TVHCodecProfile *tvh_codec_profile_copy;
+
+
+struct tvh_transcoder_t;
+typedef struct tvh_transcoder_t TVHTranscoder;
+
+struct tvh_stream_t;
+typedef struct tvh_stream_t TVHStream;
+
+struct tvh_context_type_t;
+typedef struct tvh_context_type_t TVHContextType;
+
+struct tvh_context_t;
+typedef struct tvh_context_t TVHContext;
+
+struct tvh_context_helper_t;
+typedef struct tvh_context_helper_t TVHContextHelper;
+
+// post phase _MUST_ be == phase + 1
+typedef enum {
+ OPEN_DECODER,
+ OPEN_DECODER_POST,
+ OPEN_ENCODER,
+ OPEN_ENCODER_POST
+} TVHOpenPhase;
+
+
+/* TVHTranscoder ============================================================ */
+
+SLIST_HEAD(TVHStreams, tvh_stream_t);
+
+typedef struct tvh_transcoder_t {
+ tvh_st_t input;
+ struct TVHStreams streams;
+ uint32_t id;
+ tvh_st_t *output;
+ TVHCodecProfile *profiles[AVMEDIA_TYPE_NB];
+} TVHTranscoder;
+
+int
+tvh_transcoder_deliver(TVHTranscoder *self, th_pkt_t *pkt);
+
+TVHTranscoder *
+tvh_transcoder_create(tvh_st_t *output, const char **profiles);
+
+void
+tvh_transcoder_destroy(TVHTranscoder *self);
+
+
+/* TVHStream ================================================================ */
+
+typedef struct tvh_stream_t {
+ TVHTranscoder *transcoder;
+ int id;
+ int index;
+ tvh_sct_t type;
+ TVHContext *context;
+ SLIST_ENTRY(tvh_stream_t) link;
+} TVHStream;
+
+void
+tvh_stream_stop(TVHStream *self, int flush);
+
+int
+tvh_stream_handle(TVHStream *self, th_pkt_t *pkt);
+
+int
+tvh_stream_deliver(TVHStream *self, th_pkt_t *pkt);
+
+TVHStream *
+tvh_stream_create(TVHTranscoder *transcoder, TVHCodecProfile *profile,
+ tvh_ssc_t *ssc);
+
+void
+tvh_stream_destroy(TVHStream *self);
+
+
+/* TVHContextType =========================================================== */
+
+typedef int (*tvh_context_open_meth)(TVHContext *, TVHOpenPhase, AVDictionary **);
+typedef int (*tvh_context_decode_meth)(TVHContext *, AVPacket *);
+typedef int (*tvh_context_encode_meth)(TVHContext *, AVFrame *);
+typedef int (*tvh_context_ship_meth)(TVHContext *, AVPacket *);
+typedef int (*tvh_context_wrap_meth)(TVHContext *, AVPacket *, th_pkt_t *);
+typedef void (*tvh_context_close_meth)(TVHContext *);
+
+typedef struct tvh_context_type_t {
+ enum AVMediaType media_type;
+ tvh_context_open_meth open;
+ tvh_context_decode_meth decode;
+ tvh_context_encode_meth encode;
+ tvh_context_ship_meth ship;
+ tvh_context_wrap_meth wrap;
+ tvh_context_close_meth close;
+ SLIST_ENTRY(tvh_context_type_t) link;
+} TVHContextType;
+
+void
+tvh_context_types_register(void);
+
+void
+tvh_context_types_forget(void);
+
+
+/* TVHContext =============================================================== */
+
+typedef struct tvh_context_t {
+ TVHStream *stream;
+ TVHCodecProfile *profile;
+ TVHContextType *type;
+ AVCodecContext *iavctx;
+ AVCodecContext *oavctx;
+ AVFrame *iavframe;
+ AVFrame *oavframe;
+ pktbuf_t *input_gh;
+ TVHContextHelper *helper; // encoder helper
+ AVFilterGraph *avfltgraph;
+ AVFilterContext *iavfltctx;
+ AVFilterContext *oavfltctx;
+ th_pkt_t *src_pkt;
+ int require_meta;
+ // only for audio
+ int64_t duration;
+ int64_t delta;
+ int64_t pts;
+ uint8_t sri;
+} TVHContext;
+
+int
+tvh_context_get_int_opt(AVDictionary **opts, const char *key, int *value);
+
+int
+tvh_context_get_str_opt(AVDictionary **opts, const char *key, char **value);
+
+void
+tvh_context_close(TVHContext *self, int flush);
+
+/* __VA_ARGS__ = NULL terminated list of sink options
+ sink option = (const char *name, const uint8_t *value, int size) */
+int
+tvh_context_open_filters(TVHContext *self,
+ const char *source_name, const char *source_args,
+ const char *filters, const char *sink_name, ...);
+
+int
+tvh_context_handle(TVHContext *self, th_pkt_t *pkt);
+
+int
+tvh_context_deliver(TVHContext *self, th_pkt_t *pkt);
+
+TVHContext *
+tvh_context_create(TVHStream *stream, TVHCodecProfile *profile,
+ AVCodec *iavcodec, AVCodec *oavcodec, pktbuf_t *input_gh);
+
+void
+tvh_context_destroy(TVHContext *self);
+
+
+/* TVHContextHelper ========================================================= */
+
+typedef int (*tvh_context_helper_open_meth)(TVHContext *, AVDictionary **);
+typedef th_pkt_t *(*tvh_context_helper_pack_meth)(TVHContext *, AVPacket *);
+typedef int (*tvh_context_helper_meta_meth)(TVHContext *, AVPacket *, th_pkt_t *);
+
+typedef struct tvh_context_helper_t {
+ enum AVMediaType type;
+ enum AVCodecID id;
+ tvh_context_helper_open_meth open;
+ tvh_context_helper_pack_meth pack;
+ tvh_context_helper_meta_meth meta;
+ SLIST_ENTRY(tvh_context_helper_t) link;
+} TVHContextHelper;
+
+TVHContextHelper *
+tvh_decoder_helper_find(const AVCodec *avcodec);
+
+TVHContextHelper *
+tvh_encoder_helper_find(const AVCodec *avcodec);
+
+void
+tvh_context_helpers_register(void);
+
+void
+tvh_context_helpers_forget(void);
+
+
+#endif // TVH_TRANSCODING_TRANSCODE_INTERNALS_H__
--- /dev/null
+/*
+ * tvheadend - Transcoding
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#ifndef TVH_TRANSCODING_TRANSCODE_LOG_H__
+#define TVH_TRANSCODING_TRANSCODE_LOG_H__
+
+
+#define tvh_transcoder_log(self, level, fmt, ...) \
+ do { \
+ tvhlog((level), LS_TRANSCODE, "%04X: " fmt, \
+ (((self)->id) & 0xffff), \
+ ##__VA_ARGS__); \
+ } while (0)
+
+#define _stream_log(level, fmt, transcoder, id, type, ...) \
+ do { \
+ tvh_transcoder_log((transcoder), (level), "%02d:%s: " fmt, \
+ (id), \
+ streaming_component_type2txt((type)), \
+ ##__VA_ARGS__); \
+ } while (0)
+
+#define tvh_ssc_log(ssc, level, fmt, transcoder, ...) \
+ do { \
+ _stream_log((level), fmt, \
+ (transcoder), \
+ (ssc)->ssc_index, \
+ (ssc)->ssc_type, \
+ ##__VA_ARGS__); \
+ } while (0)
+
+#define tvh_stream_log(self, level, fmt, ...) \
+ do { \
+ _stream_log((level), fmt, \
+ (self)->transcoder, \
+ (self)->id, \
+ (self)->type, \
+ ##__VA_ARGS__); \
+ } while (0)
+
+#define tvh_context_log(self, level, fmt, ...) \
+ do { \
+ tvh_stream_log((self)->stream, (level), "[%s => %s]: " fmt, \
+ ((self)->iavctx) ? (self)->iavctx->codec->name : "<unknown>", \
+ ((self)->oavctx) ? (self)->oavctx->codec->name : "<unknown>", \
+ ##__VA_ARGS__); \
+ } while (0)
+
+
+#endif // TVH_TRANSCODING_TRANSCODE_LOG_H__
--- /dev/null
+/*
+ * tvheadend - Transcoding
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "transcoding/transcode.h"
+#include "internals.h"
+
+#if ENABLE_HWACCELS
+#include "hwaccels/hwaccels.h"
+#endif
+
+
+/* TVHTranscoder ============================================================ */
+
+streaming_target_t *
+transcoder_create(streaming_target_t *output, const char **profiles)
+{
+ return (streaming_target_t *)tvh_transcoder_create(output, profiles);
+}
+
+
+void
+transcoder_destroy(streaming_target_t *st)
+{
+ tvh_transcoder_destroy((TVHTranscoder *)st);
+}
+
+
+/* module level ============================================================= */
+
+void
+transcode_init()
+{
+ tvh_context_types_register();
+ tvh_context_helpers_register();
+#if ENABLE_HWACCELS
+ hwaccels_init();
+#endif
+}
+
+void
+transcode_done()
+{
+#if ENABLE_HWACCELS
+ hwaccels_done();
+#endif
+ tvh_context_helpers_forget();
+ tvh_context_types_forget();
+}
--- /dev/null
+/*
+ * tvheadend - Transcoding
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "internals.h"
+
+
+/* TVHStream ================================================================ */
+
+static int
+tvh_stream_is_copy(TVHCodecProfile *profile, tvh_ssc_t *ssc)
+{
+ if (profile == tvh_codec_profile_copy) {
+ return 1;
+ }
+ return tvh_codec_profile_is_copy(profile, ssc);
+}
+
+
+static int
+tvh_stream_setup(TVHStream *self, TVHCodecProfile *profile, tvh_ssc_t *ssc)
+{
+ enum AVCodecID icodec_id = streaming_component_type2codec_id(ssc->ssc_type);
+ AVCodec *icodec = NULL, *ocodec = NULL;
+
+ if (icodec_id == AV_CODEC_ID_NONE) {
+ tvh_stream_log(self, LOG_ERR, "unknown decoder id for '%s'",
+ streaming_component_type2txt(ssc->ssc_type));
+ return -1;
+ }
+ if (!(icodec = avcodec_find_decoder(icodec_id))) {
+ tvh_stream_log(self, LOG_ERR, "failed to find decoder for '%s'",
+ streaming_component_type2txt(ssc->ssc_type));
+ return -1;
+ }
+ if (!(ocodec = tvh_codec_profile_get_avcodec(profile))) {
+ tvh_stream_log(self, LOG_ERR, "profile '%s' is disabled",
+ tvh_codec_profile_get_name(profile));
+ return -1;
+ }
+ if (icodec->type == AVMEDIA_TYPE_UNKNOWN ||
+ icodec->type != ocodec->type) { // is this even possible?
+ tvh_stream_log(self, LOG_ERR, "unknown or mismatch media type");
+ return -1;
+ }
+
+ if (!(self->context = tvh_context_create(self, profile,
+ icodec, ocodec, ssc->ssc_gh))) {
+ return -1;
+ }
+ self->type = ssc->ssc_type = codec_id2streaming_component_type(ocodec->id);
+ ssc->ssc_gh = NULL;
+ return 0;
+}
+
+
+/* exposed */
+
+void
+tvh_stream_stop(TVHStream *self, int flush)
+{
+ if (self->index >= 0) {
+ if (self->context) {
+ tvh_context_close(self->context, flush);
+ }
+ self->index = -1;
+ }
+}
+
+
+int
+tvh_stream_handle(TVHStream *self, th_pkt_t *pkt)
+{
+ if (pkt->pkt_payload && self->context) {
+ return (tvh_context_handle(self->context, pkt) < 0) ? -1 : 0;
+ }
+ TVHPKT_INCREF(pkt);
+ return tvh_transcoder_deliver(self->transcoder, pkt);
+}
+
+
+int
+tvh_stream_deliver(TVHStream *self, th_pkt_t *pkt)
+{
+ pkt->pkt_componentindex = self->index;
+ return tvh_transcoder_deliver(self->transcoder, pkt);
+}
+
+
+TVHStream *
+tvh_stream_create(TVHTranscoder *transcoder, TVHCodecProfile *profile,
+ tvh_ssc_t *ssc)
+{
+ TVHStream *self = NULL;
+ int is_copy = -1;
+
+ if (!(self = calloc(1, sizeof(TVHStream)))) {
+ tvh_ssc_log(ssc, LOG_ERR, "failed to allocate stream", transcoder);
+ return NULL;
+ }
+ self->transcoder = transcoder;
+ self->id = self->index = ssc->ssc_index;
+ self->type = ssc->ssc_type;
+ if ((is_copy = tvh_stream_is_copy(profile, ssc)) > 0) {
+ if (ssc->ssc_gh) {
+ pktbuf_ref_inc(ssc->ssc_gh);
+ }
+ tvh_stream_log(self, LOG_INFO, "==> Passthrough");
+ }
+ else if (is_copy < 0 || tvh_stream_setup(self, profile, ssc)) {
+ tvh_stream_destroy(self);
+ return NULL;
+ }
+ return self;
+}
+
+
+void
+tvh_stream_destroy(TVHStream *self)
+{
+ if (self) {
+ tvh_stream_stop(self, 0);
+ if (self->context) {
+ tvh_context_destroy(self->context);
+ self->context = NULL;
+ }
+ self->transcoder = NULL;
+ free(self);
+ self = NULL;
+ }
+}
--- /dev/null
+/*
+ * tvheadend - Transcoding
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "internals.h"
+
+
+static TVHCodecProfile _codec_profile_copy = { .name = "copy" };
+TVHCodecProfile *tvh_codec_profile_copy = &_codec_profile_copy;
+
+
+/* utils ==================================================================== */
+
+static enum AVMediaType
+ssc_get_media_type(tvh_ssc_t *ssc)
+{
+ if (SCT_ISVIDEO(ssc->ssc_type)) {
+ return AVMEDIA_TYPE_VIDEO;
+ }
+ if (SCT_ISAUDIO(ssc->ssc_type)) {
+ return AVMEDIA_TYPE_AUDIO;
+ }
+ if (SCT_ISSUBTITLE(ssc->ssc_type)) {
+ return AVMEDIA_TYPE_SUBTITLE;
+ }
+ return AVMEDIA_TYPE_UNKNOWN;
+}
+
+
+static TVHCodecProfile *
+ssc_is_stream(tvh_ssc_t *ssc, TVHCodecProfile **profiles)
+{
+ enum AVMediaType media_type = AVMEDIA_TYPE_UNKNOWN;
+
+ if (!ssc->ssc_disabled &&
+ (media_type = ssc_get_media_type(ssc)) != AVMEDIA_TYPE_UNKNOWN) {
+ return profiles[media_type];
+ }
+ return NULL;
+}
+
+
+static int
+stream_count(tvh_ss_t *ss, TVHCodecProfile **profiles)
+{
+ int i, count = 0;
+ tvh_ssc_t *ssc = NULL;
+
+ for (i = 0; i < ss->ss_num_components; i++) {
+ if ((ssc = &ss->ss_components[i]) && ssc_is_stream(ssc, profiles)) {
+ count++;
+ }
+ }
+ return count;
+}
+
+
+static TVHCodecProfile *
+_find_profile(const char *name)
+{
+ if (!strcmp(name, "copy")) {
+ return tvh_codec_profile_copy;
+ }
+ return codec_find_profile(name);
+}
+
+
+/* TVHTranscoder ============================================================ */
+
+static void
+tvh_transcoder_handle(TVHTranscoder *self, th_pkt_t *pkt)
+{
+ TVHStream *stream = NULL;
+
+ SLIST_FOREACH(stream, &self->streams, link) {
+ if (pkt->pkt_componentindex == stream->index) {
+ if (tvh_stream_handle(stream, pkt)) {
+ tvh_stream_stop(stream, 0);
+ }
+ break;
+ }
+ }
+}
+
+
+static tvh_ss_t *
+tvh_transcoder_start(TVHTranscoder *self, tvh_ss_t *ss_src)
+{
+ // TODO: rewrite, include language selection
+ tvh_ss_t *ss = NULL;
+ tvh_ssc_t *ssc_src = NULL, *ssc = NULL;
+ TVHCodecProfile *profile = NULL;
+ TVHStream *stream = NULL;
+ int i, j, count = stream_count(ss_src, self->profiles);
+
+ ss = calloc(1, (sizeof(tvh_ss_t) + (sizeof(tvh_ssc_t) * count)));
+ if (ss) {
+ ss->ss_refcount = 1;
+ ss->ss_pcr_pid = ss_src->ss_pcr_pid;
+ ss->ss_pmt_pid = ss_src->ss_pmt_pid;
+ service_source_info_copy(&ss->ss_si, &ss_src->ss_si);
+ ss->ss_num_components = count;
+ for (i = j = 0; i < ss_src->ss_num_components && j < count; i++) {
+ ssc_src = &ss_src->ss_components[i];
+ ssc = &ss->ss_components[j];
+ if (ssc) {
+ if ((profile = ssc_is_stream(ssc_src, self->profiles))) {
+ *ssc = *ssc_src;
+ j++;
+ if ((stream = tvh_stream_create(self, profile, ssc))) {
+ SLIST_INSERT_HEAD(&self->streams, stream, link);
+ }
+ else {
+ tvh_ssc_log(ssc, LOG_ERR, "==> Filtered out", self);
+ }
+ }
+ else {
+ tvh_ssc_log(ssc, LOG_INFO, "==> Filtered out", self);
+ }
+ }
+ }
+ }
+ return ss;
+}
+
+
+static void
+tvh_transcoder_stop(TVHTranscoder *self, int flush)
+{
+ TVHStream *stream = NULL;
+
+ SLIST_FOREACH(stream, &self->streams, link) {
+ tvh_stream_stop(stream, flush);
+ }
+}
+
+
+static void
+tvh_transcoder_stream(void *opaque, tvh_sm_t *msg)
+{
+ TVHTranscoder *self = opaque;
+ tvh_ss_t *ss = NULL;
+
+ switch (msg->sm_type) {
+ case SMT_PACKET:
+ if(msg->sm_data) {
+ tvh_transcoder_handle(self, msg->sm_data);
+ TVHPKT_CLEAR(msg->sm_data);
+ }
+ streaming_msg_free(msg);
+ break;
+ case SMT_START:
+ if (msg->sm_data) {
+ ss = tvh_transcoder_start(self, msg->sm_data);
+ streaming_start_unref(msg->sm_data);
+ msg->sm_data = ss;
+ }
+ streaming_target_deliver2(self->output, msg);
+ break;
+ case SMT_STOP:
+ tvh_transcoder_stop(self, 1);
+ /* !!! FALLTHROUGH !!! */
+ default:
+ streaming_target_deliver2(self->output, msg);
+ break;
+ }
+}
+
+
+static int
+tvh_transcoder_setup(TVHTranscoder *self, const char **profiles)
+{
+ const char *profile = NULL;
+ int i;
+
+ for (i = 0; i < AVMEDIA_TYPE_NB; i++) {
+ if ((profile = profiles[i]) && strlen(profile)) {
+ if (!(self->profiles[i] = _find_profile(profile))) {
+ tvh_transcoder_log(self, LOG_ERR,
+ "failed to find codec profile: '%s'", profile);
+ return -1;
+ }
+ }
+ }
+ return 0;
+}
+
+
+static htsmsg_t *
+tvh_transcoder_info(void *opaque, htsmsg_t *list)
+{
+ TVHTranscoder *self = opaque;
+ streaming_target_t *st = self->output;
+ htsmsg_add_str(list, NULL, "transcoder input");
+ return st->st_ops.st_info(st->st_opaque, list);
+}
+
+
+/* exposed */
+
+int
+tvh_transcoder_deliver(TVHTranscoder *self, th_pkt_t *pkt)
+{
+ tvh_sm_t *msg = NULL;
+
+ if (!(msg = msg_create(pkt))) { // takes ownership of pkt
+ tvh_transcoder_log(self, LOG_ERR, "failed to create message");
+ return -1;
+ }
+ streaming_target_deliver2(self->output, msg);
+ return 0;
+}
+
+
+static streaming_ops_t tvh_transcoder_ops = {
+ .st_cb = tvh_transcoder_stream,
+ .st_info = tvh_transcoder_info
+};
+
+
+TVHTranscoder *
+tvh_transcoder_create(tvh_st_t *output, const char **profiles)
+{
+ static uint32_t id = 0;
+ TVHTranscoder *self = NULL;
+
+ if (!(self = calloc(1, sizeof(TVHTranscoder)))) {
+ tvherror(LS_TRANSCODE, "failed to allocate transcoder");
+ return NULL;
+ }
+ SLIST_INIT(&self->streams);
+ self->id = ++id;
+ if (!self->id) {
+ self->id = ++id;
+ }
+ self->output = output;
+ if (tvh_transcoder_setup(self, profiles)) {
+ tvh_transcoder_destroy(self);
+ return NULL;
+ }
+ streaming_target_init(&self->input, &tvh_transcoder_ops, self, 0);
+ return self;
+}
+
+
+void
+tvh_transcoder_destroy(TVHTranscoder *self)
+{
+ TVHStream *stream = NULL;
+
+ if (self) {
+ tvh_transcoder_stop(self, 0);
+ self->output = NULL;
+ while (!SLIST_EMPTY(&self->streams)) {
+ stream = SLIST_FIRST(&self->streams);
+ SLIST_REMOVE_HEAD(&self->streams, link);
+ tvh_stream_destroy(stream);
+ }
+ free(self);
+ self = NULL;
+ }
+}
--- /dev/null
+/*
+ * tvheadend - Transcoding
+ *
+ * Copyright (C) 2016 Tvheadend
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include "internals.h"
+
+#if ENABLE_HWACCELS
+#include "hwaccels/hwaccels.h"
+#endif
+
+
+static int
+_video_filters_hw_pix_fmt(enum AVPixelFormat pix_fmt)
+{
+ const AVPixFmtDescriptor *desc;
+
+ if ((desc = av_pix_fmt_desc_get(pix_fmt)) &&
+ (desc->flags & AV_PIX_FMT_FLAG_HWACCEL)) {
+ return 1;
+ }
+ return 0;
+}
+
+
+static int
+_video_filters_get_filters(TVHContext *self, AVDictionary **opts, char **filters)
+{
+ static char download[48];
+ static char deint[8];
+ static char scale[24];
+ static char upload[48];
+ int ihw = _video_filters_hw_pix_fmt(self->iavctx->pix_fmt);
+ int ohw = _video_filters_hw_pix_fmt(self->oavctx->pix_fmt);
+ int filter_scale = (self->iavctx->height != self->oavctx->height);
+ int filter_deint = 0, filter_download = 0, filter_upload = 0;
+
+ if (tvh_context_get_int_opt(opts, "tvh_filter_deint", &filter_deint)) {
+ return -1;
+ }
+ filter_download = (ihw && (!ohw || filter_scale || filter_deint)) ? 1 : 0;
+ filter_upload = ((filter_download || !ihw) && ohw) ? 1 : 0;
+
+ memset(download, 0, sizeof(download));
+ if (filter_download &&
+ str_snprintf(download, sizeof(download), "hwdownload,format=pix_fmts=%s",
+ av_get_pix_fmt_name(self->iavctx->sw_pix_fmt))) {
+ return -1;
+ }
+
+ memset(deint, 0, sizeof(deint));
+ if (filter_deint && str_snprintf(deint, sizeof(deint), "yadif")) {
+ return -1;
+ }
+
+ memset(scale, 0, sizeof(scale));
+ if (filter_scale &&
+ str_snprintf(scale, sizeof(scale), "scale=w=-2:h=%d",
+ self->oavctx->height)) {
+ return -1;
+ }
+
+ memset(upload, 0, sizeof(upload));
+ if (filter_upload &&
+ str_snprintf(upload, sizeof(upload), "format=pix_fmts=%s|%s,hwupload",
+ av_get_pix_fmt_name(self->oavctx->sw_pix_fmt),
+ av_get_pix_fmt_name(self->oavctx->pix_fmt))) {
+ return -1;
+ }
+
+ if (!(*filters = str_join(",", download, deint, scale, upload, NULL))) {
+ return -1;
+ }
+
+ return 0;
+}
+
+
+static int
+tvh_video_context_open_decoder(TVHContext *self, AVDictionary **opts)
+{
+#if ENABLE_HWACCELS
+ int hwaccel = -1;
+ if ((hwaccel = tvh_codec_profile_video_get_hwaccel(self->profile)) < 0) {
+ return -1;
+ }
+ if (hwaccel) {
+ self->iavctx->get_format = hwaccels_decode_get_format;
+ }
+#endif
+ return 0;
+}
+
+
+static int
+tvh_video_context_notify_gh(TVHContext *self)
+{
+ /* notify global headers that we're live */
+ /* the video packets might be delayed */
+ th_pkt_t *pkt = NULL;
+
+ pkt = pkt_create(NULL, 0, self->src_pkt->pkt_pts, self->src_pkt->pkt_dts);
+ if (pkt) {
+ return tvh_context_deliver(self, pkt);
+ }
+ return -1;
+}
+
+
+static int
+tvh_video_context_open_encoder(TVHContext *self, AVDictionary **opts)
+{
+ AVRational ticks_per_frame;
+
+ if (tvh_context_get_int_opt(opts, "pix_fmt", &self->oavctx->pix_fmt) ||
+ tvh_context_get_int_opt(opts, "width", &self->oavctx->width) ||
+ tvh_context_get_int_opt(opts, "height", &self->oavctx->height)) {
+ return -1;
+ }
+
+#if ENABLE_HWACCELS
+ self->oavctx->coded_width = self->oavctx->width;
+ self->oavctx->coded_height = self->oavctx->height;
+ if (hwaccels_encode_setup_context(self->oavctx)) {
+ return -1;
+ }
+#endif
+
+ // XXX: is this a safe assumption?
+ if (!self->iavctx->framerate.num) {
+ self->iavctx->framerate = av_make_q(30, 1);
+ }
+ self->oavctx->framerate = self->iavctx->framerate;
+ self->oavctx->ticks_per_frame = self->iavctx->ticks_per_frame;
+ ticks_per_frame = av_make_q(self->oavctx->ticks_per_frame, 1);
+ self->oavctx->time_base = av_inv_q(av_mul_q(
+ self->oavctx->framerate, ticks_per_frame));
+ self->oavctx->gop_size = ceil(av_q2d(av_inv_q(av_mul_q(
+ self->oavctx->time_base, ticks_per_frame))));
+ self->oavctx->gop_size *= 3;
+ self->oavctx->max_b_frames = 3;
+ return 0;
+}
+
+
+static int
+tvh_video_context_open_filters(TVHContext *self, AVDictionary **opts)
+{
+ static char source_args[128];
+ char *filters = NULL;
+
+ // source args
+ memset(source_args, 0, sizeof(source_args));
+ if (str_snprintf(source_args, sizeof(source_args),
+ "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
+ self->iavctx->width,
+ self->iavctx->height,
+ self->iavctx->pix_fmt,
+ self->iavctx->time_base.num,
+ self->iavctx->time_base.den,
+ self->iavctx->sample_aspect_ratio.num,
+ self->iavctx->sample_aspect_ratio.den)) {
+ return -1;
+ }
+
+ // filters
+ if (_video_filters_get_filters(self, opts, &filters)) {
+ return -1;
+ }
+
+ int ret = tvh_context_open_filters(self,
+ "buffer", source_args, // source
+ strlen(filters) ? filters : "null", // filters
+ "buffersink", // sink
+ "pix_fmts", &self->oavctx->pix_fmt, // sink option: pix_fmt
+ sizeof(self->oavctx->pix_fmt),
+ NULL); // _IMPORTANT!_
+ str_clear(filters);
+ return ret;
+}
+
+
+static int
+tvh_video_context_open(TVHContext *self, TVHOpenPhase phase, AVDictionary **opts)
+{
+ switch (phase) {
+ case OPEN_DECODER:
+ return tvh_video_context_open_decoder(self, opts);
+ case OPEN_DECODER_POST:
+ return tvh_video_context_notify_gh(self);
+ case OPEN_ENCODER:
+ return tvh_video_context_open_encoder(self, opts);
+ case OPEN_ENCODER_POST:
+ return tvh_video_context_open_filters(self, opts);
+ default:
+ break;
+ }
+ return 0;
+}
+
+
+static int
+tvh_video_context_encode(TVHContext *self, AVFrame *avframe)
+{
+ avframe->pts = av_frame_get_best_effort_timestamp(avframe);
+ /*tvhinfo(LS_TRANSCODE, "encode avframe: pts: %"PRId64 ", pkt_pts: %"PRId64 ", pkt_dts: %"PRId64,
+ avframe->pts, avframe->pkt_pts, avframe->pkt_dts);*/
+ return 0;
+}
+
+
+static int
+tvh_video_context_ship(TVHContext *self, AVPacket *avpkt)
+{
+ /*tvhinfo(LS_TRANSCODE, "ship avpkt: pts: %"PRId64 ", dts: %"PRId64,
+ avpkt->pts, avpkt->dts);*/
+ if (avpkt->size < 0 || avpkt->pts < avpkt->dts) {
+ tvh_context_log(self, LOG_ERR, "encode failed");
+ return -1;
+ }
+ return avpkt->size;
+}
+
+
+static int
+tvh_video_context_wrap(TVHContext *self, AVPacket *avpkt, th_pkt_t *pkt)
+{
+ uint8_t *qsdata = NULL;
+ int qsdata_size = 0;
+ enum AVPictureType pict_type = AV_PICTURE_TYPE_NONE;
+
+ qsdata = av_packet_get_side_data(avpkt, AV_PKT_DATA_QUALITY_STATS,
+ &qsdata_size);
+ if (qsdata && qsdata_size >= 5) {
+ pict_type = qsdata[4];
+ }
+#if FF_API_CODED_FRAME
+ else if (self->oavctx->coded_frame) {
+ pict_type = self->oavctx->coded_frame->pict_type;
+ }
+#endif
+ switch (pict_type) {
+ case AV_PICTURE_TYPE_I:
+ pkt->v.pkt_frametype = PKT_I_FRAME;
+ break;
+ case AV_PICTURE_TYPE_P:
+ pkt->v.pkt_frametype = PKT_P_FRAME;
+ break;
+ case AV_PICTURE_TYPE_B:
+ pkt->v.pkt_frametype = PKT_B_FRAME;
+ break;
+ default:
+ break;
+ }
+ pkt->pkt_duration = avpkt->duration;
+ pkt->pkt_commercial = self->src_pkt->pkt_commercial;
+ pkt->v.pkt_field = self->src_pkt->v.pkt_field;
+ pkt->v.pkt_aspect_num = self->src_pkt->v.pkt_aspect_num;
+ pkt->v.pkt_aspect_den = self->src_pkt->v.pkt_aspect_den;
+ return 0;
+}
+
+
+static void
+tvh_video_context_close(TVHContext *self)
+{
+#if ENABLE_HWACCELS
+ hwaccels_encode_close_context(self->oavctx);
+ hwaccels_decode_close_context(self->iavctx);
+#endif
+}
+
+
+TVHContextType TVHVideoContext = {
+ .media_type = AVMEDIA_TYPE_VIDEO,
+ .open = tvh_video_context_open,
+ .encode = tvh_video_context_encode,
+ .ship = tvh_video_context_ship,
+ .wrap = tvh_video_context_wrap,
+ .close = tvh_video_context_close,
+};
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))
/**
* 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,
*
* 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,
*
*/
typedef struct streaming_queue {
-
+
streaming_target_t sq_st;
pthread_mutex_t sq_mutex; /* Protects sp_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;
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)
#define PRItime_t "ld"
#endif
+/* transcoding */
+#define TVH_NAME_LEN 32
+#define TVH_TITLE_LEN 256
+
#endif /* TVHEADEND_H */
[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") },
[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") },
[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
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 {
fprintf(*fp, "%s [%7s]:%s\n", t, ltxt, msg->msg);
}
}
-
+
free(msg->msg);
free(msg);
}
path = NULL;
}
}
- options = tvhlog_options;
+ options = tvhlog_options;
pthread_mutex_unlock(&tvhlog_mutex);
tvhlog_process(msg, options, &fp, path);
pthread_mutex_lock(&tvhlog_mutex);
/*
* Initialise
*/
-void
+void
tvhlog_init ( int level, int options, const char *path )
{
tvhlog_level = level;
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 );
LS_SCANFILE,
LS_TSFILE,
LS_TSDEBUG,
+ LS_CODEC,
+ LS_VAAPI,
LS_LAST /* keep this last */
};
--- /dev/null
+/*
+ * 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
+ });
+ }
+};
/*
- * 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, {
* Ext JS Library 2.1
* Copyright(c) 2006-2008, Ext JS, LLC.
* licensing@extjs.com
- *
+ *
* http://extjs.com/license
- */
+ */
#header {
font-family: tahoma,arial;
background-color: #507AAA;
background-image: url(../icons/exclamation.png) !important;
}
+.codecDisabled {
+ background-image: url(../icons/plugin_disabled.png) !important;
+}
+
+.codecEnabled {
+ background-image: url(../icons/plugin.png) !important;
+}
+
.x-linked {
display: inline-block;
background-image: url(../icons/linked.gif) !important;
height: auto;
}
-/** vim: ts=4:sw=4:nu:fdc=4:nospell
+/** vim: ts=4:sw=4:nu:fdc=4:nospell
*
- * Ext.ux.grid.RowActions.css
+ * Ext.ux.grid.RowActions.css
*
* Style sheets for Grid RowActions Plugin
*
* the Open Source LGPL 3.0 license. Commercial use is permitted to the extent
* that the code/component(s) do NOT become part of another Open Source or Commercially
* licensed development library or toolkit without explicit permission.
- *
+ *
* License details: http://www.gnu.org/licenses/lgpl.html
*/
* @version $Id: Ext.ux.form.LovCombo.css 189 2008-04-16 21:01:06Z jozo $
*
* @license Ext.ux.form.LovCombo.css is licensed under the terms of the Open Source
- * LGPL 3.0 license. Commercial use is permitted to the extent that the
+ * LGPL 3.0 license. Commercial use is permitted to the extent that the
* code/component(s) do NOT become part of another Open Source or Commercially
* licensed development library or toolkit without explicit permission.
- *
+ *
* License details: http://www.gnu.org/licenses/lgpl.html
*/
.ux-lovcombo-icon {
}
.ux-lovcombo-icon-unchecked {
- background: transparent
+ background: transparent
url(../extjs/resources/images/default/menu/unchecked.gif);
}
var df = [];
var groups = null;
var width = 0;
+ var hiddenFields = [];
/* Fields */
for (var i = 0; i < d.length; i++) {
]
});
}
- 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 = {};
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) {
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);
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);
}
text: _('Add'),
disabled: false,
handler: function() {
+ if (!conf.add.forms && conf.forms) {
+ conf.add['forms'] = conf['forms'];
+ }
tvheadend.idnode_create(conf.add, true);
}
});
noButtons: true,
width: 730,
noautoWidth: true,
- showpwd: conf.showpwd
+ showpwd: conf.showpwd,
+ forms: conf.forms
});
abuttons.save.setDisabled(false);
abuttons.undo.setDisabled(false);
--- /dev/null
+/*
+ * 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
+ });
+};
tvheadend.doc_win.close();
tvheadend.doc_win = null;
}
-
+
if (title)
title = title[title.length-1];
if (history)
history += '<hr/>';
}
-
+
var bodyid = Ext.id();
var text = '<div id="' + bodyid + '">';
if (tvheadend.docs_toc || history)
tvheadend.doc_win = win;
}
- var helpfailuremsg = function() {
+ var helpfailuremsg = function() {
Ext.MessageBox.show({
title:_('Error'),
msg: _('There was a problem displaying the Help!') + '<br>' +
}
/**
- * 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.
if (o.uilevel)
tvheadend.uilevel = o.uilevel;
-
+
if (o.theme)
tvheadend.theme = o.theme;
-
+
tvheadend.quicktips = o.quicktips ? true : false;
if (o.uilevel_nochange)
tvheadend.baseconf(general);
tvheadend.imgcacheconf(general);
tvheadend.satipsrvconf(general);
-
+
cp.add(general);
/* Users */
tvheadend.acleditor(users);
tvheadend.passwdeditor(users);
tvheadend.ipblockeditor(users);
-
+
cp.add(users);
/* DVB inputs, networks, muxes, services */
iconCls: 'film_edit',
items: []
});
+ tvheadend.profile_tab(stream);
+ tvheadend.codec_tab(stream);
tvheadend.esfilter_tab(stream);
cp.add(stream);
});
tvheadend.dvr_settings(tsdvr);
if (tvheadend.capabilities.indexOf('timeshift') !== -1)
- tvheadend.timeshift(tsdvr);
+ tvheadend.timeshift(tsdvr);
cp.add(tsdvr);
*
*/
tvheadend.RootTabExtraComponent = Ext.extend(Ext.Component, {
-
+
onRender1: function(tab, before) {
if (!this.componentTpl) {
var tt = new Ext.Template(
});
tvheadend.RootTabExtraClickComponent = Ext.extend(Ext.Component, {
-
+
onRender1: function(tab, before, click_cb) {
if (!this.componentTpl) {
var tt = new Ext.Template(
}
}
}
-
+
if (this.extra.time)
window.setInterval(this.setTime, 1000);
},
+++ /dev/null
-../../../../../../vendor/ext-3.4/examples/ux/Spinner.js
\ No newline at end of file
--- /dev/null
+/*
+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;
+++ /dev/null
-../../../../../../vendor/ext-3.4/examples/ux/SpinnerField.js
\ No newline at end of file
--- /dev/null
+/*
+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;
--- /dev/null
+../../../../vendor/famfamsilk/plugin_disabled.png
\ No newline at end of file
+++ /dev/null
-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);
-
---- 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)
-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")
---- 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)