Merge remote-tracking branch 'dev/master'
Conflicts: data/conf/charset
This commit is contained in:
commit
21e8e272fb
191 changed files with 16164 additions and 6142 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -3,14 +3,16 @@ build.*
|
|||
|
||||
src/version.c
|
||||
|
||||
data/dvb-scan
|
||||
|
||||
.cproject
|
||||
.project
|
||||
.settings
|
||||
data/dvb-scan
|
||||
|
||||
*.pyc
|
||||
.*.sw[op]
|
||||
|
||||
debian/changelog
|
||||
debian/files
|
||||
debian/tvheadend
|
||||
debian/tvheadend-dbg
|
||||
|
|
0
.gitmodules
vendored
Normal file
0
.gitmodules
vendored
Normal file
114
Makefile
114
Makefile
|
@ -20,8 +20,8 @@
|
|||
# Configuration
|
||||
#
|
||||
|
||||
include ${CURDIR}/.config.mk
|
||||
PROG = ${BUILDDIR}/tvheadend
|
||||
include $(dir $(lastword $(MAKEFILE_LIST))).config.mk
|
||||
PROG := $(BUILDDIR)/tvheadend
|
||||
|
||||
#
|
||||
# Common compiler flags
|
||||
|
@ -31,8 +31,11 @@ CFLAGS += -Wall -Werror -Wwrite-strings -Wno-deprecated-declarations
|
|||
CFLAGS += -Wmissing-prototypes -fms-extensions
|
||||
CFLAGS += -g -funsigned-char -O2
|
||||
CFLAGS += -D_FILE_OFFSET_BITS=64
|
||||
CFLAGS += -I${BUILDDIR} -I${CURDIR}/src -I${CURDIR}
|
||||
LDFLAGS += -lrt -ldl -lpthread
|
||||
CFLAGS += -I${BUILDDIR} -I${ROOTDIR}/src -I${ROOTDIR}
|
||||
LDFLAGS += -lrt -ldl -lpthread -lm
|
||||
|
||||
vpath %.c $(ROOTDIR)
|
||||
vpath %.h $(ROOTDIR)
|
||||
|
||||
#
|
||||
# Other config
|
||||
|
@ -45,16 +48,16 @@ BUNDLE_FLAGS = ${BUNDLE_FLAGS-yes}
|
|||
# Binaries/Scripts
|
||||
#
|
||||
|
||||
MKBUNDLE = $(PYTHON) $(CURDIR)/support/mkbundle
|
||||
MKBUNDLE = $(PYTHON) $(ROOTDIR)/support/mkbundle
|
||||
|
||||
#
|
||||
# Debug/Output
|
||||
#
|
||||
|
||||
ifndef V
|
||||
ECHO = printf "$(1)\t\t%s\n" $(2)
|
||||
ECHO = printf "%-16s%s\n" $(1) $(2)
|
||||
BRIEF = CC MKBUNDLE CXX
|
||||
MSG = $@
|
||||
MSG = $(subst $(BUILDDIR)/,,$@)
|
||||
$(foreach VAR,$(BRIEF), \
|
||||
$(eval $(VAR) = @$$(call ECHO,$(VAR),$$(MSG)); $($(VAR))))
|
||||
endif
|
||||
|
@ -62,10 +65,11 @@ endif
|
|||
#
|
||||
# Core
|
||||
#
|
||||
SRCS = src/main.c \
|
||||
SRCS = src/version.c \
|
||||
src/main.c \
|
||||
src/tvhlog.c \
|
||||
src/utils.c \
|
||||
src/wrappers.c \
|
||||
src/version.c \
|
||||
src/access.c \
|
||||
src/dtable.c \
|
||||
src/tcp.c \
|
||||
|
@ -88,12 +92,14 @@ SRCS = src/main.c \
|
|||
src/parser_latm.c \
|
||||
src/tsdemux.c \
|
||||
src/bitstream.c \
|
||||
src/htsp.c \
|
||||
src/htsp_server.c \
|
||||
src/serviceprobe.c \
|
||||
src/htsmsg.c \
|
||||
src/htsmsg_binary.c \
|
||||
src/htsmsg_json.c \
|
||||
src/htsmsg_xml.c \
|
||||
src/misc/dbl.c \
|
||||
src/misc/json.c \
|
||||
src/settings.c \
|
||||
src/htsbuf.c \
|
||||
src/trap.c \
|
||||
|
@ -104,28 +110,28 @@ SRCS = src/main.c \
|
|||
src/avc.c \
|
||||
src/huffman.c \
|
||||
src/filebundle.c \
|
||||
src/muxes.c \
|
||||
src/config2.c \
|
||||
src/lang_codes.c \
|
||||
src/lang_str.c \
|
||||
src/imagecache.c \
|
||||
src/tvhtime.c
|
||||
|
||||
SRCS += src/epggrab/module.c\
|
||||
src/epggrab/channel.c\
|
||||
src/epggrab/otamux.c\
|
||||
src/epggrab/module/pyepg.c\
|
||||
src/epggrab/module/xmltv.c\
|
||||
|
||||
SRCS-$(CONFIG_LINUXDVB) += src/epggrab/otamux.c\
|
||||
src/epggrab/module/eit.c \
|
||||
src/epggrab/module/opentv.c \
|
||||
src/epggrab/support/freesat_huffman.c \
|
||||
|
||||
SRCS += src/plumbing/tsfix.c \
|
||||
src/plumbing/globalheaders.c \
|
||||
src/plumbing/globalheaders.c
|
||||
|
||||
SRCS += src/dvr/dvr_db.c \
|
||||
src/dvr/dvr_rec.c \
|
||||
src/dvr/dvr_autorec.c \
|
||||
src/dvr/ebml.c \
|
||||
src/dvr/mkmux.c \
|
||||
|
||||
SRCS += src/webui/webui.c \
|
||||
src/webui/comet.c \
|
||||
|
@ -135,13 +141,22 @@ SRCS += src/webui/webui.c \
|
|||
src/webui/html.c\
|
||||
|
||||
SRCS += src/muxer.c \
|
||||
src/muxer_pass.c \
|
||||
src/muxer_tvh.c \
|
||||
src/muxer/muxer_pass.c \
|
||||
src/muxer/muxer_tvh.c \
|
||||
src/muxer/tvh/ebml.c \
|
||||
src/muxer/tvh/mkmux.c \
|
||||
|
||||
#
|
||||
# Optional code
|
||||
#
|
||||
|
||||
# Timeshift
|
||||
SRCS-${CONFIG_TIMESHIFT} += \
|
||||
src/timeshift.c \
|
||||
src/timeshift/timeshift_filemgr.c \
|
||||
src/timeshift/timeshift_writer.c \
|
||||
src/timeshift/timeshift_reader.c \
|
||||
|
||||
# DVB
|
||||
SRCS-${CONFIG_LINUXDVB} += \
|
||||
src/dvb/dvb.c \
|
||||
|
@ -152,30 +167,46 @@ SRCS-${CONFIG_LINUXDVB} += \
|
|||
src/dvb/diseqc.c \
|
||||
src/dvb/dvb_adapter.c \
|
||||
src/dvb/dvb_multiplex.c \
|
||||
src/dvb/dvb_transport.c \
|
||||
src/dvb/dvb_service.c \
|
||||
src/dvb/dvb_preconf.c \
|
||||
src/dvb/dvb_satconf.c \
|
||||
src/dvb/dvb_input_filtered.c \
|
||||
src/dvb/dvb_input_raw.c \
|
||||
src/webui/extjs_dvb.c \
|
||||
src/muxes.c \
|
||||
|
||||
# Inotify
|
||||
SRCS-${CONFIG_INOTIFY} += \
|
||||
src/dvr/dvr_inotify.c \
|
||||
|
||||
# V4L
|
||||
SRCS-${CONFIG_V4L} += \
|
||||
src/v4l.c \
|
||||
src/webui/extjs_v4l.c \
|
||||
|
||||
# CWC
|
||||
SRCS-${CONFIG_CWC} += src/cwc.c \
|
||||
src/capmt.c \
|
||||
src/ffdecsa/ffdecsa_interface.c \
|
||||
src/ffdecsa/ffdecsa_int.c
|
||||
|
||||
# Avahi
|
||||
SRCS-$(CONFIG_AVAHI) += src/avahi.c
|
||||
|
||||
# Optimised code
|
||||
# libav
|
||||
SRCS-$(CONFIG_LIBAV) += src/libav.c \
|
||||
src/muxer/muxer_libav.c \
|
||||
src/plumbing/transcoding.c \
|
||||
|
||||
# CWC
|
||||
SRCS-${CONFIG_CWC} += src/cwc.c \
|
||||
src/capmt.c
|
||||
|
||||
# FFdecsa
|
||||
ifneq ($(CONFIG_DVBCSA),yes)
|
||||
SRCS-${CONFIG_CWC} += src/ffdecsa/ffdecsa_interface.c \
|
||||
src/ffdecsa/ffdecsa_int.c
|
||||
ifeq ($(CONFIG_CWC),yes)
|
||||
SRCS-${CONFIG_MMX} += src/ffdecsa/ffdecsa_mmx.c
|
||||
SRCS-${CONFIG_SSE2} += src/ffdecsa/ffdecsa_sse2.c
|
||||
endif
|
||||
${BUILDDIR}/src/ffdecsa/ffdecsa_mmx.o : CFLAGS += -mmmx
|
||||
${BUILDDIR}/src/ffdecsa/ffdecsa_sse2.o : CFLAGS += -msse2
|
||||
endif
|
||||
|
||||
# File bundles
|
||||
SRCS-${CONFIG_BUNDLE} += bundle.c
|
||||
|
@ -207,16 +238,26 @@ DEPS = ${OBJS:%.o=%.d}
|
|||
all: ${PROG}
|
||||
|
||||
# Special
|
||||
.PHONY: clean distclean
|
||||
.PHONY: clean distclean check_config reconfigure
|
||||
|
||||
# Check configure output is valid
|
||||
check_config:
|
||||
@test $(ROOTDIR)/.config.mk -nt $(ROOTDIR)/configure\
|
||||
|| echo "./configure output is old, please re-run"
|
||||
@test $(ROOTDIR)/.config.mk -nt $(ROOTDIR)/configure
|
||||
|
||||
# Recreate configuration
|
||||
reconfigure:
|
||||
$(ROOTDIR)/configure $(CONFIGURE_ARGS)
|
||||
|
||||
# Binary
|
||||
${PROG}: $(OBJS) $(ALLDEPS)
|
||||
${PROG}: check_config $(OBJS) $(ALLDEPS)
|
||||
$(CC) -o $@ $(OBJS) $(CFLAGS) $(LDFLAGS)
|
||||
|
||||
# Object
|
||||
${BUILDDIR}/%.o: %.c
|
||||
@mkdir -p $(dir $@)
|
||||
$(CC) -MD -MP $(CFLAGS) -c -o $@ $(CURDIR)/$<
|
||||
$(CC) -MD -MP $(CFLAGS) -c -o $@ $<
|
||||
|
||||
# Add-on
|
||||
${BUILDDIR}/%.so: ${SRCS_EXTRA}
|
||||
|
@ -229,25 +270,26 @@ clean:
|
|||
find . -name "*~" | xargs rm -f
|
||||
|
||||
distclean: clean
|
||||
rm -rf ${CURDIR}/build.*
|
||||
rm -f ${CURDIR}/.config.mk
|
||||
rm -rf ${ROOTDIR}/build.*
|
||||
rm -f ${ROOTDIR}/.config.mk
|
||||
|
||||
# Create buildversion.h
|
||||
src/version.c: FORCE
|
||||
@$(CURDIR)/support/version $@ > /dev/null
|
||||
# Create version
|
||||
$(BUILDDIR)/src/version.o: $(ROOTDIR)/src/version.c
|
||||
$(ROOTDIR)/src/version.c: FORCE
|
||||
@$(ROOTDIR)/support/version $@ > /dev/null
|
||||
FORCE:
|
||||
|
||||
# Include dependency files if they exist.
|
||||
-include $(DEPS)
|
||||
|
||||
# Include OS specific targets
|
||||
include support/${OSENV}.mk
|
||||
include ${ROOTDIR}/support/${OSENV}.mk
|
||||
|
||||
# Bundle files
|
||||
$(BUILDDIR)/bundle.o: $(BUILDDIR)/bundle.c
|
||||
@mkdir -p $(dir $@)
|
||||
$(CC) -I${CURDIR}/src -c -o $@ $<
|
||||
$(CC) -I${ROOTDIR}/src -c -o $@ $<
|
||||
|
||||
$(BUILDDIR)/bundle.c:
|
||||
@mkdir -p $(dir $@)
|
||||
$(MKBUNDLE) -o $@ -d ${BUILDDIR}/bundle.d $(BUNDLE_FLAGS) $(BUNDLES)
|
||||
$(MKBUNDLE) -o $@ -d ${BUILDDIR}/bundle.d $(BUNDLE_FLAGS) $(BUNDLES:%=$(ROOTDIR)/%)
|
||||
|
|
31
README
31
README
|
@ -1,30 +1,33 @@
|
|||
Tvheadend TV streaming server
|
||||
=============================
|
||||
|
||||
(c) 2006 - 2012 Andreas Öman, et al.
|
||||
|
||||
Tvheadend (TV streaming server) v3.5
|
||||
====================================
|
||||
(c) 2006 - 2013 Andreas Öman, et al.
|
||||
|
||||
How to build for Linux
|
||||
======================
|
||||
----------------------
|
||||
|
||||
First you need to configure:
|
||||
|
||||
$ ./configure
|
||||
$ ./configure
|
||||
|
||||
If any dependencies are missing the configure script will complain or attempt
|
||||
to disable optional features.
|
||||
|
||||
$ make
|
||||
Build the binary:
|
||||
|
||||
$ make
|
||||
|
||||
After build, the binary resides in `build.linux/`.
|
||||
|
||||
Build the binary, after build the binary resides in 'build.linux/'.
|
||||
Thus, to start it, just type:
|
||||
|
||||
$ ./build.linux/tvheadend
|
||||
$ ./build.linux/tvheadend
|
||||
|
||||
Settings are stored in $HOME/.hts/tvheadend
|
||||
Settings are stored in `$HOME/.hts/tvheadend`.
|
||||
|
||||
Further information
|
||||
===================
|
||||
-------------------
|
||||
|
||||
For more information about building, including generating packages please
|
||||
visit https://www.lonelycoder.com/redmine/projects/tvheadend/wiki/Building
|
||||
For more information about building, including generating packages please visit:
|
||||
> https://tvheadend.org/projects/tvheadend/wiki/Building
|
||||
> https://tvheadend.org/projects/tvheadend/wiki/Packaging
|
||||
> https://tvheadend.org/projects/tvheadend/wiki/Git
|
||||
|
|
33
README.md
Normal file
33
README.md
Normal file
|
@ -0,0 +1,33 @@
|
|||
Tvheadend (TV streaming server)
|
||||
====================================
|
||||
(c) 2006 - 2013 Andreas Öman, et al.
|
||||
|
||||
How to build for Linux
|
||||
----------------------
|
||||
|
||||
First you need to configure:
|
||||
|
||||
$ ./configure
|
||||
|
||||
If any dependencies are missing the configure script will complain or attempt
|
||||
to disable optional features.
|
||||
|
||||
Build the binary:
|
||||
|
||||
$ make
|
||||
|
||||
After build, the binary resides in `build.linux/`.
|
||||
|
||||
Thus, to start it, just type:
|
||||
|
||||
$ ./build.linux/tvheadend
|
||||
|
||||
Settings are stored in `$HOME/.hts/tvheadend`.
|
||||
|
||||
Further information
|
||||
-------------------
|
||||
|
||||
For more information about building, including generating packages please visit:
|
||||
> https://tvheadend.org/projects/tvheadend/wiki/Building
|
||||
> https://tvheadend.org/projects/tvheadend/wiki/Packaging
|
||||
> https://tvheadend.org/projects/tvheadend/wiki/Git
|
105
configure
vendored
105
configure
vendored
|
@ -9,7 +9,7 @@
|
|||
# Setup
|
||||
# ###########################################################################
|
||||
|
||||
ROOTDIR=$(dirname $0)
|
||||
ROOTDIR=$(cd "$(dirname "$0")"; pwd)
|
||||
|
||||
#
|
||||
# Options
|
||||
|
@ -20,22 +20,30 @@ OPTIONS=(
|
|||
"v4l:yes"
|
||||
"linuxdvb:yes"
|
||||
"dvbscan:yes"
|
||||
"timeshift:yes"
|
||||
"trace:yes"
|
||||
"imagecache:auto"
|
||||
"avahi:auto"
|
||||
"zlib:auto"
|
||||
"libav:auto"
|
||||
"inotify:auto"
|
||||
"bundle:no"
|
||||
"dvbcsa:no"
|
||||
)
|
||||
|
||||
#
|
||||
# Begin
|
||||
#
|
||||
|
||||
. $ROOTDIR/support/configure.inc
|
||||
. "$ROOTDIR/support/configure.inc"
|
||||
parse_args $*
|
||||
|
||||
# ###########################################################################
|
||||
# Checks
|
||||
# ###########################################################################
|
||||
|
||||
echo "Checking support/features"
|
||||
|
||||
#
|
||||
# Compiler
|
||||
#
|
||||
|
@ -47,6 +55,11 @@ check_cc_option sse2
|
|||
check_cc_snippet getloadavg '#include <stdlib.h>
|
||||
void test() { getloadavg(NULL,0); }'
|
||||
|
||||
check_cc_snippet atomic64 '#include <stdint.h>
|
||||
uint64_t test(uint64_t *ptr){
|
||||
return __sync_fetch_and_add(ptr, 1);
|
||||
}'
|
||||
|
||||
#
|
||||
# Python
|
||||
#
|
||||
|
@ -98,22 +111,86 @@ if enabled_or_auto avahi; then
|
|||
fi
|
||||
fi
|
||||
|
||||
#
|
||||
# libav
|
||||
#
|
||||
if enabled_or_auto libav; then
|
||||
has_libav=true
|
||||
|
||||
if $has_libav && ! check_pkg libavcodec "<=55.0.0"; then
|
||||
has_libav=false
|
||||
fi
|
||||
|
||||
if $has_libav && ! check_pkg libavcodec ">=52.96.0"; then
|
||||
has_libav=false
|
||||
fi
|
||||
|
||||
if $has_libav && ! check_pkg libavutil ">=50.43.0"; then
|
||||
has_libav=false
|
||||
fi
|
||||
|
||||
if $has_libav && ! check_pkg libavformat "<=55.0.0"; then
|
||||
has_libav=false
|
||||
fi
|
||||
|
||||
if $has_libav && ! check_pkg libavformat ">=53.10.0"; then
|
||||
has_libav=false
|
||||
fi
|
||||
|
||||
if $has_libav && ! check_pkg libswscale ">=0.13.0"; then
|
||||
has_libav=false
|
||||
fi
|
||||
|
||||
if $has_libav; then
|
||||
enable libav
|
||||
elif enabled libav; then
|
||||
die "libav development support not found (use --disable-libav)"
|
||||
fi
|
||||
fi
|
||||
|
||||
#
|
||||
# Inotify
|
||||
#
|
||||
if enabled_or_auto inotify; then
|
||||
if check_cc_header "sys/inotify" inotify_h; then
|
||||
enable inotify
|
||||
elif enabled inotify; then
|
||||
die "Inotify support not found (use --disable-inotify)"
|
||||
fi
|
||||
fi
|
||||
|
||||
#
|
||||
# libdvbcsa
|
||||
#
|
||||
if enabled cwc && enabled dvbcsa; then
|
||||
(check_cc_header "dvbcsa/dvbcsa" dvbcsa_h &&\
|
||||
check_cc_lib dvbcsa dvbcsa_l) ||\
|
||||
die "Failed to find dvbcsa support (use --disable-dvbcsa)"
|
||||
LDFLAGS="$LDFLAGS -ldvbcsa"
|
||||
fi
|
||||
|
||||
#
|
||||
# Icon caching
|
||||
#
|
||||
if enabled_or_auto imagecache; then
|
||||
if check_pkg libcurl; then
|
||||
enable imagecache
|
||||
elif enabled imagecache; then
|
||||
die "Libcurl support not found (use --disable-imagecache)"
|
||||
fi
|
||||
fi
|
||||
|
||||
#
|
||||
# DVB scan
|
||||
#
|
||||
if enabled linuxdvb && enabled dvbscan; then
|
||||
if [ ! -d ${ROOTDIR}/data/dvb-scan ]; then
|
||||
if ! enabled bin_bzip2; then
|
||||
die "Bzip2 not found, fetch dvb-scan files (use --disable-dvbscan)"
|
||||
fi
|
||||
echo -n "Fetching dvb-scan files... "
|
||||
if ${ROOTDIR}/support/getmuxlist &> /dev/null; then
|
||||
echo "done"
|
||||
else
|
||||
echo
|
||||
die "Failed to fetch dvb-scan files (use --disable-dvbscan to skip)"
|
||||
fi
|
||||
printf "${TAB}" "fetching dvb-scan files ..."
|
||||
"${ROOTDIR}/support/getmuxlist"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "fail"
|
||||
die "Failed to fetch dvb-scan data (use --disable-dvbscan)"
|
||||
fi
|
||||
echo "ok"
|
||||
fi
|
||||
|
||||
# ###########################################################################
|
||||
|
@ -122,7 +199,7 @@ fi
|
|||
|
||||
# Write config
|
||||
write_config
|
||||
cat >> ${CONFIG_H} <<EOF
|
||||
cat >> "${CONFIG_H}" <<EOF
|
||||
#define TVHEADEND_DATADIR "$(eval echo ${datadir})/tvheadend"
|
||||
EOF
|
||||
|
||||
|
|
|
@ -1,128 +0,0 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# tvheadend Start/Stop the hts tvheadend daemon.
|
||||
#
|
||||
# chkconfig: 345 90 60
|
||||
# description: Tvheadend is a TV streaming server for Linux supporting
|
||||
# DVB-S, DVB-S2, DVB-C, DVB-T, ATSC, IPTV, and Analog video
|
||||
# (V4L) as input sources. It also comes with a powerful and
|
||||
# easy to use web interface both used for configuration and
|
||||
# day-to-day operations, such as searching the EPG and
|
||||
# scheduling recordings.
|
||||
|
||||
### BEGIN INIT INFO
|
||||
# Provides: tvheadend
|
||||
# Required-Start: $local_fs $syslog
|
||||
# Required-Stop: $local_fs $syslog
|
||||
# Default-Start: 345
|
||||
# Default-Stop: 90
|
||||
# Short-Description: run tvheadend daemon
|
||||
# Description: Tvheadend is a TV streaming server for Linux supporting
|
||||
# DVB-S, DVB-S2, DVB-C, DVB-T, ATSC, IPTV, and Analog video
|
||||
# (V4L) as input sources. It also comes with a powerful and
|
||||
# easy to use web interface both used for configuration and
|
||||
# day-to-day operations, such as searching the EPG and
|
||||
# scheduling recordings.
|
||||
### END INIT INFO
|
||||
|
||||
[ -f /etc/sysconfig/tvheadend ] || {
|
||||
[ "$1" = "status" ] && exit 4 || exit 6
|
||||
}
|
||||
|
||||
RETVAL=0
|
||||
prog="tvheadend"
|
||||
exec=/usr/local/bin/tvheadend
|
||||
lockfile=/var/lock/subsys/tvheadend
|
||||
sysconfig=/etc/sysconfig/tvheadend
|
||||
|
||||
# Source function library.
|
||||
. /etc/rc.d/init.d/functions
|
||||
|
||||
[ -e /etc/sysconfig/$prog ] && . /etc/sysconfig/$prog
|
||||
|
||||
start() {
|
||||
[ -x $exec ] || exit 5
|
||||
[ -f $sysconfig ] || exit 6
|
||||
echo -n $"Starting $prog: "
|
||||
daemon $exec $OPTIONS
|
||||
retval=$?
|
||||
echo
|
||||
[ $retval -eq 0 ] && touch $lockfile
|
||||
}
|
||||
|
||||
stop() {
|
||||
echo -n $"Stopping $prog: "
|
||||
if [ -n "`pidfileofproc $exec`" ]; then
|
||||
killproc $exec
|
||||
RETVAL=3
|
||||
else
|
||||
failure $"Stopping $prog"
|
||||
fi
|
||||
retval=$?
|
||||
echo
|
||||
[ $retval -eq 0 ] && rm -f $lockfile
|
||||
}
|
||||
|
||||
restart() {
|
||||
stop
|
||||
start
|
||||
}
|
||||
|
||||
reload() {
|
||||
echo -n $"Reloading $prog: "
|
||||
if [ -n "`pidfileofproc $exec`" ]; then
|
||||
killproc $exec -HUP
|
||||
else
|
||||
failure $"Reloading $prog"
|
||||
fi
|
||||
retval=$?
|
||||
echo
|
||||
}
|
||||
|
||||
force_reload() {
|
||||
# new configuration takes effect after restart
|
||||
restart
|
||||
}
|
||||
|
||||
rh_status() {
|
||||
# run checks to determine if the service is running or use generic status
|
||||
status -p /var/run/tvheadend.pid $prog
|
||||
}
|
||||
|
||||
rh_status_q() {
|
||||
rh_status >/dev/null 2>&1
|
||||
}
|
||||
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
rh_status_q && exit 0
|
||||
$1
|
||||
;;
|
||||
stop)
|
||||
rh_status_q || exit 0
|
||||
$1
|
||||
;;
|
||||
restart)
|
||||
$1
|
||||
;;
|
||||
reload)
|
||||
rh_status_q || exit 7
|
||||
$1
|
||||
;;
|
||||
force-reload)
|
||||
force_reload
|
||||
;;
|
||||
status)
|
||||
rh_status
|
||||
;;
|
||||
condrestart|try-restart)
|
||||
rh_status_q || exit 0
|
||||
restart
|
||||
;;
|
||||
*)
|
||||
echo $"Usage: $0 {start|stop|status|restart|condrestart|try-restart|reload|force-reload}"
|
||||
exit 2
|
||||
esac
|
||||
exit $?
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
Name: tvheadend
|
||||
Summary: TV streaming server
|
||||
Version: 2.12.cae47cf
|
||||
Release: 1%{dist}
|
||||
License: GPL
|
||||
Group: Applications/Multimedia
|
||||
URL: http://www.lonelycoder.com/tvheadend
|
||||
Packager: Jouk Hettema
|
||||
Source: tvheadend-%{version}.tar.bz2
|
||||
Prefix: /usr
|
||||
BuildRequires: avahi-devel, openssl, glibc, zlib
|
||||
|
||||
%description
|
||||
Tvheadend is a TV streaming server for Linux supporting DVB-S, DVB-S2,
|
||||
DVB-C, DVB-T, ATSC, IPTV, and Analog video (V4L) as input sources.
|
||||
|
||||
%prep
|
||||
%setup -q
|
||||
|
||||
%build
|
||||
%configure --release --prefix=%{prefix}/bin
|
||||
%{__make}
|
||||
|
||||
%install
|
||||
%{__rm} -rf %{buildroot}
|
||||
|
||||
mkdir -p $RPM_BUILD_ROOT/%{prefix}
|
||||
|
||||
%{__install} -d -m0755 %{buildroot}%{prefix}/bin
|
||||
%{__install} -d -m0755 %{buildroot}/etc/tvheadend
|
||||
%{__install} -d -m0755 %{buildroot}/etc/sysconfig
|
||||
%{__install} -d -m0755 %{buildroot}/etc/rc.d/init.d
|
||||
%{__install} -d -m0755 %{buildroot}%{prefix}/shared/man/man1
|
||||
|
||||
%{__install} -m0755 build.Linux/tvheadend %{buildroot}%{prefix}/bin/
|
||||
%{__install} -m0755 man/tvheadend.1 %{buildroot}%{prefix}/shared/man/man1
|
||||
%{__install} -m0755 contrib/redhat/tvheadend %{buildroot}/etc/rc.d/init.d
|
||||
|
||||
cat >> %{buildroot}/etc/sysconfig/tvheadend << EOF
|
||||
OPTIONS="-f -u tvheadend -g tvheadend -c /etc/tvheadend -s -C"
|
||||
EOF
|
||||
|
||||
%pre
|
||||
groupadd -f -r tvheadend >/dev/null 2>&! || :
|
||||
useradd -s /sbin/nologin -c "Tvheadend" -M -r tvheadend -g tvheadend -G video >/dev/null 2>&! || :
|
||||
|
||||
%preun
|
||||
if [ $1 = 0 ]; then
|
||||
/sbin/service tvheadend stop >/dev/null 2>&1
|
||||
/sbin/chkconfig --del tvheadend
|
||||
fi
|
||||
|
||||
%post
|
||||
/sbin/chkconfig --add tvheadend
|
||||
|
||||
if [ "$1" -ge "1" ]; then
|
||||
/sbin/service tvheadend condrestart >/dev/null 2>&1 || :
|
||||
fi
|
||||
|
||||
%clean
|
||||
%{__rm} -rf %{buildroot}
|
||||
|
||||
%files
|
||||
%defattr(-,root,root)
|
||||
%doc debian/changelog LICENSE README
|
||||
%dir %attr(-,tvheadend,root) %{prefix}
|
||||
%{prefix}/bin/tvheadend
|
||||
%{prefix}/shared/man/man1/tvheadend.1*
|
||||
%{_sysconfdir}/rc.d/init.d/tvheadend
|
||||
/etc/tvheadend
|
||||
%config /etc/sysconfig/tvheadend
|
||||
|
||||
%changelog
|
||||
* Fri Feb 18 2011 Jouk Hettema <joukio@gmail.com> - 2.12.cae47cf
|
||||
- initial build for fedora 14
|
|
@ -1,408 +0,0 @@
|
|||
[
|
||||
{
|
||||
"tsid": 1048,
|
||||
"onid": 1,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 4310,
|
||||
"description": "Astra 19.2E TV Trwam"
|
||||
},
|
||||
{
|
||||
"tsid": 1059,
|
||||
"onid": 1,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 0,
|
||||
"description": "Astra 19.2E TVP"
|
||||
},
|
||||
{
|
||||
"tsid": 1111,
|
||||
"onid": 1,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 7269,
|
||||
"description": "Astra 19.2E TV Trwam"
|
||||
},
|
||||
{
|
||||
"tsid": 8100,
|
||||
"onid": 156,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 1500,
|
||||
"description": "Eurobird 9.0E TV Polonia"
|
||||
},
|
||||
{
|
||||
"tsid": 8300,
|
||||
"onid": 156,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 620,
|
||||
"description": "Eurobird 9.0E TVN International"
|
||||
},
|
||||
{
|
||||
"tsid": 200,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 13834,
|
||||
"description": "Hotbird 13.0 Eutelsat Eurosport PL"
|
||||
},
|
||||
{
|
||||
"tsid": 200,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 13864,
|
||||
"description": "Hotbird 13.0 Eutelsat Eurosport 2 PL"
|
||||
},
|
||||
{
|
||||
"tsid": 200,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 13865,
|
||||
"description": "Hotbird 13.0 Eutelsat Eurosport PL"
|
||||
},
|
||||
{
|
||||
"tsid": 200,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 13878,
|
||||
"description": "Hotbird 13.0 Eutelsat Eurosport 2 NE PL"
|
||||
},
|
||||
{
|
||||
"tsid": 300,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 0,
|
||||
"description": "Hotbird 13.0 ITI/Nka HD PL"
|
||||
},
|
||||
{
|
||||
"tsid": 400,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 13020,
|
||||
"description": "Hotbird 13.0 Cyfra+ Canal+ HD Polska"
|
||||
},
|
||||
{
|
||||
"tsid": 400,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 13022,
|
||||
"description": "Hotbird 13.0 Cyfra+ Canal+ Sport HD Polska"
|
||||
},
|
||||
{
|
||||
"tsid": 400,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 13023,
|
||||
"description": "Hotbird 13.0 Cyfra+ National Geographic HD Polska"
|
||||
},
|
||||
{
|
||||
"tsid": 400,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 13025,
|
||||
"description": "Hotbird 13.0 Cyfra+ Filmbox HD"
|
||||
},
|
||||
{
|
||||
"tsid": 400,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 13026,
|
||||
"description": "Hotbird 13.0 Cyfra+ AXN Spin HD"
|
||||
},
|
||||
{
|
||||
"tsid": 400,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 13027,
|
||||
"description": "Hotbird 13.0 Cyfra+ TVN 7 HD"
|
||||
},
|
||||
{
|
||||
"tsid": 400,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 13070,
|
||||
"description": "Hotbird 13.0 Cyfra+ Eurosport HD PL"
|
||||
},
|
||||
{
|
||||
"tsid": 400,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 13080,
|
||||
"description": "Hotbird 13.0 Cyfra+ Eurosport HD PL"
|
||||
},
|
||||
{
|
||||
"tsid": 400,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 13081,
|
||||
"description": "Hotbird 13.0 Cyfra+ Eurosport HD PL"
|
||||
},
|
||||
{
|
||||
"tsid": 400,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 13082,
|
||||
"description": "Hotbird 13.0 Cyfra+ Eurosport HD PL"
|
||||
},
|
||||
{
|
||||
"tsid": 1000,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 0,
|
||||
"description": "Hotbird 13.0 ITI/Nka TVN"
|
||||
},
|
||||
{
|
||||
"tsid": 1100,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 0,
|
||||
"description": "Hotbird 13.0 Cyfra+"
|
||||
},
|
||||
{
|
||||
"tsid": 1300,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 0,
|
||||
"description": "Hotbird 13.0 ITI/Nka"
|
||||
},
|
||||
{
|
||||
"tsid": 1400,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 30,
|
||||
"description": "Hotbird 13.0 Nick Junior"
|
||||
},
|
||||
{
|
||||
"tsid": 1400,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 31,
|
||||
"description": "Hotbird 13.0 Nickelodeon HD"
|
||||
},
|
||||
{
|
||||
"tsid": 1500,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 0,
|
||||
"description": "Hotbird 13.0 Cyfra+"
|
||||
},
|
||||
{
|
||||
"tsid": 1600,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 0,
|
||||
"description": "Hotbird 13.0 ITI/Nka EskaTV, TVN"
|
||||
},
|
||||
{
|
||||
"tsid": 1800,
|
||||
"onid": 200,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 3623,
|
||||
"description": "Hotbird 13.0 Polo TV"
|
||||
},
|
||||
{
|
||||
"tsid": 7400,
|
||||
"onid": 113,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 0,
|
||||
"description": "Hotbird 13.0 Cyfrowy Polsat"
|
||||
},
|
||||
{
|
||||
"tsid": 7700,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 117,
|
||||
"description": "Hotbird 13.0 Hot TV PL"
|
||||
},
|
||||
{
|
||||
"tsid": 7800,
|
||||
"onid": 113,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 0,
|
||||
"description": "Hotbird 13.0 Cyfrowy Polsat"
|
||||
},
|
||||
{
|
||||
"tsid": 7900,
|
||||
"onid": 113,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 0,
|
||||
"description": "Hotbird 13.0 Cyfrowy Polsat"
|
||||
},
|
||||
{
|
||||
"tsid": 8100,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 14900,
|
||||
"description": "Hotbird 13.0 Eutelsat Sport Klub PL"
|
||||
},
|
||||
{
|
||||
"tsid": 8100,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 14901,
|
||||
"description": "Hotbird 13.0 Eutelsat Universal CE"
|
||||
},
|
||||
{
|
||||
"tsid": 8100,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 14902,
|
||||
"description": "Hotbird 13.0 Eutelsat Sci-Fi CE"
|
||||
},
|
||||
{
|
||||
"tsid": 8100,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 14910,
|
||||
"description": "Hotbird 13.0 Eutelsat Sport Klub PL"
|
||||
},
|
||||
{
|
||||
"tsid": 8100,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 14911,
|
||||
"description": "Hotbird 13.0 Eutelsat Universal CE"
|
||||
},
|
||||
{
|
||||
"tsid": 8100,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 14912,
|
||||
"description": "Hotbird 13.0 Eutelsat Sci-Fi CE"
|
||||
},
|
||||
{
|
||||
"tsid": 11000,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 0,
|
||||
"description": "Hotbird 13.0 Cyfra+"
|
||||
},
|
||||
{
|
||||
"tsid": 11100,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 4661,
|
||||
"description": "Hotbird 13.0 Eutelsat Wedding TV"
|
||||
},
|
||||
{
|
||||
"tsid": 11200,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 0,
|
||||
"description": "Hotbird 13.0 Polsat Cyfrowy PPV"
|
||||
},
|
||||
{
|
||||
"tsid": 11400,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 0,
|
||||
"description": "Hotbird 13.0 Cyfra+"
|
||||
},
|
||||
{
|
||||
"tsid": 11600,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 0,
|
||||
"description": "Hotbird 13.0 ITI/Nka"
|
||||
},
|
||||
{
|
||||
"tsid": 11900,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 0,
|
||||
"description": "Hotbird 13.0 Cyfra+"
|
||||
},
|
||||
{
|
||||
"tsid": 12000,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 0,
|
||||
"description": "Hotbird 13.0 Polsat Cyfrowy"
|
||||
},
|
||||
{
|
||||
"tsid": 12200,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 7457,
|
||||
"description": "Hotbird 13.0 Globecast Cartoon Network PL / TCM CE"
|
||||
},
|
||||
{
|
||||
"tsid": 12200,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 7466,
|
||||
"description": "Hotbird 13.0 Globecast Disney Channel PL"
|
||||
},
|
||||
{
|
||||
"tsid": 12200,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 7467,
|
||||
"description": "Hotbird 13.0 Globecast Cartoon Network CE"
|
||||
},
|
||||
{
|
||||
"tsid": 12200,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 7468,
|
||||
"description": "Hotbird 13.0 Globecast TCM CE"
|
||||
},
|
||||
{
|
||||
"tsid": 12800,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 0,
|
||||
"description": "Hotbird 13.0 Viacom MTV / VH1 Polska"
|
||||
},
|
||||
{
|
||||
"tsid": 13000,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 0,
|
||||
"description": "Hotbird 13.0 Globecast Orange PL"
|
||||
},
|
||||
{
|
||||
"tsid": 13100,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 7322,
|
||||
"description": "Hotbird 13.0 TV5 Monde Europe"
|
||||
},
|
||||
{
|
||||
"tsid": 13100,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 7324,
|
||||
"description": "Hotbird 13.0 Crime & Investigation"
|
||||
},
|
||||
{
|
||||
"tsid": 13100,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 7325,
|
||||
"description": "Hotbird 13.0 Crime & Investigation"
|
||||
},
|
||||
{
|
||||
"tsid": 13200,
|
||||
"onid": 113,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 0,
|
||||
"description": "Hotbird 13.0 Cyfrowy Polsat"
|
||||
},
|
||||
{
|
||||
"tsid": 13400,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 4754,
|
||||
"description": "Hotbird 13.0 TVR"
|
||||
},
|
||||
{
|
||||
"tsid": 15400,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 13511,
|
||||
"description": "Hotbird 13.0 Rebel TV"
|
||||
}
|
||||
{
|
||||
"tsid": 15700,
|
||||
"onid": 318,
|
||||
"charset": "PL_AUTO",
|
||||
"sid": 10626,
|
||||
"description": "Hotbird 13.0 Disco TV"
|
||||
}
|
||||
]
|
6
debian/changelog
vendored
6
debian/changelog
vendored
|
@ -1,6 +0,0 @@
|
|||
tvheadend (3.3) unstable; urgency=low
|
||||
|
||||
* The full changelog can be found at
|
||||
http://www.lonelycoder.com/tvheadend/download
|
||||
|
||||
-- Andreas Öman <andreas@lonelycoder.com> Tue, 18 Sep 2012 12:45:49 +0100
|
6
debian/control
vendored
6
debian/control
vendored
|
@ -1,8 +1,8 @@
|
|||
Source: tvheadend
|
||||
Section: video
|
||||
Priority: extra
|
||||
Maintainer: Andreas Öman <andreas@lonelycoder.com>
|
||||
Build-Depends: debhelper (>= 7.0.50), pkg-config, libavahi-client-dev, libssl-dev, zlib1g-dev, wget, bzip2
|
||||
Maintainer: Andreas Öman <andreas@tvheadend.org>
|
||||
Build-Depends: debhelper (>= 7.0.50), pkg-config, libavahi-client-dev, libssl-dev, zlib1g-dev, wget, bzip2, libcurl4-gnutls-dev, git-core
|
||||
Standards-Version: 3.7.3
|
||||
|
||||
Package: tvheadend
|
||||
|
@ -11,7 +11,7 @@ Depends: ${shlibs:Depends}, libavahi-client3, zlib1g
|
|||
Recommends: xmltv-util
|
||||
Enhances: showtime
|
||||
Replaces: hts-tvheadend
|
||||
Homepage: http://www.lonelycoder.com/tvheadend
|
||||
Homepage: https://tvheadend.org
|
||||
Description: Tvheadend
|
||||
Tvheadend is a TV streaming server for Linux supporting DVB, ATSC, IPTV, and Analog video (V4L) as input sources.
|
||||
Can be used as a backend to Showtime, XBMC and various other clients.
|
||||
|
|
2
debian/rules
vendored
2
debian/rules
vendored
|
@ -5,7 +5,7 @@ export DH_VERBOSE=1
|
|||
dh $@
|
||||
|
||||
override_dh_auto_configure:
|
||||
dh_auto_configure -- ${JOBSARGS}
|
||||
dh_auto_configure -- ${AUTOBUILD_CONFIGURE_EXTRA} ${JOBSARGS}
|
||||
|
||||
override_dh_auto_build:
|
||||
make ${JARGS}
|
||||
|
|
14
debian/tvheadend.default
vendored
14
debian/tvheadend.default
vendored
|
@ -25,10 +25,20 @@ TVH_CONF_DIR=""
|
|||
# set as "0,1"
|
||||
TVH_ADAPTERS=""
|
||||
|
||||
# TVH_IPV6
|
||||
# if set to 1 will enable IPv6 support
|
||||
TVH_IPV6=0
|
||||
|
||||
# TVH_HTTP_PORT
|
||||
# if set to "" will use binary default
|
||||
TVH_HTTP_PORT=""
|
||||
|
||||
# TVH_HTTP_ROOT
|
||||
# if set to "" will use binary default
|
||||
# else will change the webui root context, useful for proxied
|
||||
# servers
|
||||
TVH_HTTP_ROOT=""
|
||||
|
||||
# TVH_HTSP_PORT
|
||||
# if set to "" will use binary default
|
||||
TVH_HTSP_PORT=""
|
||||
|
@ -37,6 +47,10 @@ TVH_HTSP_PORT=""
|
|||
# if set to 1 will output debug to syslog
|
||||
TVH_DEBUG=0
|
||||
|
||||
# TVH_DELAY
|
||||
# if set startup will be delayed N seconds to allow hardware init
|
||||
TVH_DELAY=""
|
||||
|
||||
# TVH_ARGS
|
||||
# add any other arguments
|
||||
TVH_ARGS=""
|
||||
|
|
7
debian/tvheadend.init
vendored
7
debian/tvheadend.init
vendored
|
@ -33,8 +33,10 @@ ARGS="-f"
|
|||
[ -z "$TVH_GROUP" ] || ARGS="$ARGS -g $TVH_GROUP"
|
||||
[ -z "$TVH_CONF_DIR" ] || ARGS="$ARGS -c $TVH_CONF_DIR"
|
||||
[ -z "$TVH_ADAPTERS" ] || ARGS="$ARGS -a $TVH_ADAPTERS"
|
||||
[ -z "$TVH_HTTP_PORT" ] || ARGS="$ARGS -w $TVH_HTTP_PORT"
|
||||
[ -z "$TVH_HTSP_PORT" ] || ARGS="$ARGS -e $TVH_HTSP_PORT"
|
||||
[ "$TVH_IPV6" = "1" ] && ARGS="$ARGS -6"
|
||||
[ -z "$TVH_HTTP_PORT" ] || ARGS="$ARGS --http_port $TVH_HTTP_PORT"
|
||||
[ -z "$TVH_HTTP_ROOT" ] || ARGS="$ARGS --http_root $TVH_HTTP_ROOT"
|
||||
[ -z "$TVH_HTSP_PORT" ] || ARGS="$ARGS --htsp_port $TVH_HTSP_PORT"
|
||||
[ "$TVH_DEBUG" = "1" ] && ARGS="$ARGS -s"
|
||||
|
||||
# Load the VERBOSE setting and other rcS variables
|
||||
|
@ -83,6 +85,7 @@ do_stop()
|
|||
case "$1" in
|
||||
start)
|
||||
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
|
||||
[ ! -z "$TVH_DELAY" ] && sleep $TVH_DELAY
|
||||
do_start
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
|
|
8
debian/tvheadend.upstart
vendored
8
debian/tvheadend.upstart
vendored
|
@ -22,9 +22,13 @@ script
|
|||
[ -z "$TVH_GROUP" ] || ARGS="$ARGS -g $TVH_GROUP"
|
||||
[ -z "$TVH_CONF_DIR" ] || ARGS="$ARGS -c $TVH_CONF_DIR"
|
||||
[ -z "$TVH_ADAPTERS" ] || ARGS="$ARGS -a $TVH_ADAPTERS"
|
||||
[ -z "$TVH_HTTP_PORT" ] || ARGS="$ARGS -w $TVH_HTTP_PORT"
|
||||
[ -z "$TVH_HTSP_PORT" ] || ARGS="$ARGS -e $TVH_HTSP_PORT"
|
||||
[ "$TVH_IPV6" = "1" ] && ARGS="$ARGS -6"
|
||||
[ -z "$TVH_HTTP_PORT" ] || ARGS="$ARGS --http_port $TVH_HTTP_PORT"
|
||||
[ -z "$TVH_HTTP_ROOT" ] || ARGS="$ARGS --http_root $TVH_HTTP_ROOT"
|
||||
[ -z "$TVH_HTSP_PORT" ] || ARGS="$ARGS --htsp_port $TVH_HTSP_PORT"
|
||||
[ "$TVH_DEBUG" = "1" ] && ARGS="$ARGS -s"
|
||||
|
||||
[ ! -z "$TVH_DELAY" ] && sleep $TVH_DELAY
|
||||
|
||||
exec tvheadend $ARGS $TVH_ARGS
|
||||
end script
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 18 KiB |
|
@ -6,7 +6,7 @@ wide open.
|
|||
<img src="docresources/accessconfig.png">
|
||||
|
||||
<p>
|
||||
When Tvheadend verifies access is scan thru all the enabled access control entries.
|
||||
When Tvheadend verifies access is scan through all the enabled access control entries.
|
||||
The permission flags are combined for all matching access entries.
|
||||
An access entry is said to match if the username / password matches and the IP source
|
||||
address of the requesting peer is within the prefix.
|
||||
|
@ -42,7 +42,7 @@ The columns have the following functions:
|
|||
<dt>Username
|
||||
<dd>
|
||||
Name of user, if no username is needed for match it should contain a
|
||||
single asterix (*).
|
||||
single asterisk (*).
|
||||
|
||||
<dt>Password
|
||||
<dd>
|
||||
|
|
|
@ -49,6 +49,15 @@
|
|||
This module will communicate the received control-words back to
|
||||
Tvheadend via Port 9000
|
||||
|
||||
<dt>OSCam mode
|
||||
<dd>If selected, connection will be made directly to oscam without using LD_PRELOAD hack.<br>
|
||||
Port 9000 will be used automatically.<br>
|
||||
The following lines are required in <b>[dvbapi]</b> section of oscam.conf file:
|
||||
<dl>
|
||||
<dt>boxtype = pc<br>
|
||||
pmt_mode = 4
|
||||
</dl>
|
||||
|
||||
<dt>Comment
|
||||
<dd>Allows the administrator to set a comment only visible in this editor.
|
||||
It does not serve any active purpose.
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<img src="docresources/channeltab.png">
|
||||
|
||||
<div>
|
||||
The channels are listed / edited in a grid.
|
||||
The channels are listed / edited in a grid.
|
||||
|
||||
<ul>
|
||||
<li>To edit a cell, double-click on it. After a cell is changed it
|
||||
|
@ -37,7 +37,7 @@
|
|||
player.
|
||||
|
||||
<dt>EPG Grab Source
|
||||
<dd>Name of the Internet based EPG provider (typically XMLTV) channel
|
||||
<dd>Name of the Internet-based EPG provider (typically XMLTV) channel
|
||||
that should be used to update this channels EPG info.
|
||||
By default Tvheadend tries to match the name itself, but sometimes
|
||||
it might not match correctly in which case you can do the mapping
|
||||
|
|
|
@ -53,7 +53,7 @@
|
|||
This will attempt to close all available device handles. This can be
|
||||
necessary to allow some devices to go into low power states.
|
||||
<br/>
|
||||
However this option has been known to cause problems with some multi tuner
|
||||
However, this option has been known to cause problems with some multi-tuner
|
||||
DVB cards. If you have signal problems, try disabling this option.
|
||||
<br/>
|
||||
Note: this option has no effect if idle scanning is enabled.
|
||||
|
@ -110,8 +110,8 @@
|
|||
<dt>Turn off LNB when idle
|
||||
<dd>
|
||||
This option can be enabled to disable the power to the LNB when the adapter
|
||||
is not in use. This can reduce power consumption, however for poorly shieled
|
||||
multi tuner setups you may some inteference when the LNB is re-enabled.
|
||||
is not in use. This can reduce power consumption, however for poorly shielded
|
||||
multi-tuner setups you may have some interference when the LNB is re-enabled.
|
||||
|
||||
</dl>
|
||||
</dl>
|
||||
|
@ -143,25 +143,25 @@
|
|||
temporary broken
|
||||
|
||||
<dt>Network
|
||||
<dd>Network name as given in the DVB stream. Can not be changed
|
||||
<dd>Network name as given in the DVB stream. Cannot be changed
|
||||
|
||||
<dt>Frequency
|
||||
<dd>Center frequency for the mux. Can not be changed
|
||||
<dd>Center frequency for the mux. Cannot be changed
|
||||
|
||||
<dt>Modulation
|
||||
<dd>Information about the modulation used on the mux. Can not be changed
|
||||
<dd>Information about the modulation used on the mux. Cannot be changed
|
||||
|
||||
<dt>Polarisation
|
||||
<dd>Information about the polarisation used on the mux. Can not be changed
|
||||
<dd>Information about the polarisation used on the mux. Cannot be changed
|
||||
|
||||
<dt>Satellite config (DVB-S only)
|
||||
<dd>The satellite configuration in use on this mux. Can not be changed.
|
||||
<dd>The satellite configuration in use on this mux. Cannot be changed.
|
||||
|
||||
<dt>Frontend status
|
||||
<dd>The status of the frontend signal last time the mux was tuned. Can not be changed
|
||||
<dd>The status of the frontend signal last time the mux was tuned. Cannot be changed
|
||||
|
||||
<dt>Mux id
|
||||
<dd>Unique ID for this mux in the dvb network. Can not be changed
|
||||
<dd>Unique ID for this mux in the dvb network. Cannot be changed
|
||||
|
||||
<dt>Quality
|
||||
<dd>Tvheadend's estimated quality for the mux.
|
||||
|
@ -180,7 +180,7 @@
|
|||
'Save changes' button. In order to change a Checkbox cell you only
|
||||
have to click once in it.
|
||||
|
||||
<li>Service can not be deleted since they are directly inherited /
|
||||
<li>Service cannot be deleted since they are directly inherited /
|
||||
discovered from a mux they will reappear in just a few seconds
|
||||
should one delete them.
|
||||
</ul>
|
||||
|
@ -194,7 +194,7 @@
|
|||
temporary broken
|
||||
|
||||
<dt>Service name
|
||||
<dd>Service name as given in the DVB stream. Can not be changed
|
||||
<dd>Service name as given in the DVB stream. Cannot be changed
|
||||
|
||||
<dt>Play
|
||||
<dd>Open the VLC plugin window to play this service.
|
||||
|
@ -210,13 +210,13 @@
|
|||
charset to use when none is specified by the broadcaster.
|
||||
|
||||
<dt>EPG
|
||||
<dd>Uncheck this if EPG data should not be retreived for this service.
|
||||
<dd>Uncheck this if EPG data should not be retrieved for this service.
|
||||
|
||||
<dt>Type
|
||||
<dd>Type of service. Can not be changed
|
||||
<dd>Type of service. Cannot be changed
|
||||
|
||||
<dt>Provider
|
||||
<dd>Provider as given in the DVB stream. Can not be changed
|
||||
<dd>Provider as given in the DVB stream. Cannot be changed
|
||||
|
||||
<dt>Network
|
||||
<dd>Network name for the mux this service resides on
|
||||
|
@ -257,6 +257,10 @@
|
|||
|
||||
<dt>Switchport
|
||||
<dd>Port number to select for this configuration (numbering begins at 0).
|
||||
<dd>In DiSEqC 1.0/2.0 mode, ports 0-3 are valid.
|
||||
<dd>In DiSEqC 1.1/2.1 mode, ports 0-63 are valid.
|
||||
<dd>Use numbers 0-3 for LNBs behind first input on the uncommitted switch,
|
||||
then 4-7 and so on to support up to 64 ports using DiSEqC 1.1/2.1.
|
||||
|
||||
<dt>LNB type
|
||||
<dd>Select the LNB type from the list of supported LNBs. If your LNB
|
||||
|
|
|
@ -74,6 +74,10 @@
|
|||
<dd>If checked, media containers that support metadata will be tagged with
|
||||
the metadata associated with the event being recorded.
|
||||
|
||||
<dt>Skip commercials
|
||||
<dd>If checked, commercials will be dropped from the recordings. At the
|
||||
moment, commercial detection only works for the swedish channel TV4.
|
||||
|
||||
<dt>Post-processor command
|
||||
<dd>Command to run after finishing a recording. The command will be
|
||||
run in background and is executed even if a recording is aborted
|
||||
|
|
|
@ -1,28 +1,51 @@
|
|||
<div class="hts-doc-text">
|
||||
|
||||
<p>
|
||||
This tab is used to configure EPG grabbing capabilities. TVheadend supports
|
||||
a variety of different EPG grabbing mechanisms. These fall into 3 broad
|
||||
categories, within which there are a variety of specific grabber
|
||||
implementations.
|
||||
This tab is used to configure the Electronic Program Guide (EPG) grabbing
|
||||
capabilities. Tvheadend supports a variety of different EPG grabbing
|
||||
mechanisms. These fall into 3 broad categories, within which there are a
|
||||
variety of specific grabber implementations.
|
||||
</p>
|
||||
|
||||
<h2>Grabber Types</h2>
|
||||
<ul>
|
||||
<li>Over-the-Air (OTA) - These grabbers receive EPG data directly from the DVB network. This is often the easiest way to get up and running and does provide timely updates should scheduling change. However the information isn't always as rich as some of the other grabbers.
|
||||
<li>Internal - These are grabbers which can be internally initiated from within TVheadend using a very simple scheduler. These are typically Internet based services. This can be a quick way to get richer EPG data where you don't have decent OTA support.
|
||||
<li>External - These provide the option to run grabber scripts externally and to send data into TVheadend via Unix domain sockets. It provides the ability to run more complex configurations using things like cronjob's, script chains, etc.
|
||||
<li>Over-the-Air (OTA) - These grabbers receive EPG data directly from the
|
||||
DVB network. This is often the easiest way to get up and running and does
|
||||
provide timely updates should scheduling change. However, the information
|
||||
isn't always as rich as some of the other grabbers.
|
||||
<li>Internal - These are grabbers which can be internally initiated from
|
||||
within Tvheadend using a very simple scheduler. These are typically
|
||||
Internet-based services. This can be a quick way to get richer EPG data
|
||||
where you don't have decent OTA support.
|
||||
<li>External - These provide the option to run grabber scripts externally and
|
||||
to send data into Tvheadend via Unix domain sockets. It provides the ability
|
||||
to run more complex configurations using things like cronjob's, script
|
||||
chains, etc.
|
||||
</ul>
|
||||
|
||||
<h2>Grabber Modules</h2>
|
||||
<ul>
|
||||
<li>EIT - This is a DVB standards compatible EIT grabber. Typically it will
|
||||
retrieve now/next information, though on some networks there may be more
|
||||
<li>EIT - This is a DVB standards compatible EIT grabber. Typically it
|
||||
will retrieve now/next information, though on some networks there may be more
|
||||
extensive data published.
|
||||
<li>Freesat/view - This is an extended version of EIT that is used by the Free-to-air DVB providers in the UK. It includes additional information such as series links and episode identifiers.
|
||||
<li>OpenTV - This is a proprietary OTA EPG grabber. Its known to be used on the SKY networks, but others may use it. You need two configuration files to define settings for your particular network, if you don't see yours listed please visit IRC #hts for help.
|
||||
<li>XMLTV - This is am Internet based suite of scripts, for more information about XMLTV please visit <a href="http://www.xmltv.org">http://www.xmltv.org</a>. To make use of the internal XMLTV grabber you typically require the xmltv-utils package to be installed. If you install new grabbers you will need to restart TVheadend to pick these up as they're loaded at startup. If you see no XMLTV grabbers listed then most probably XMLTV is not properly installed and in the PATH.
|
||||
<li>PyEPG - This is another Internet based scraper. It currently only supports the Atlas UK system (for which you need a key), but it does provide a very rich EPG data set. For more information see <a href='http://github.com/adamsutton/PyEPG'>http://github.com/adamsutton/PyEPG</a>.</li>
|
||||
<li>Freesat/view - This is an extended version of EIT that is used by the
|
||||
Free-to-air DVB providers in the UK. It includes additional information such
|
||||
as series links and episode identifiers.
|
||||
<li>OpenTV - This is a proprietary OTA EPG grabber. It's known to be used on
|
||||
the SKY networks, but others may use it. You need two configuration files to
|
||||
define settings for your particular network, if you don't see yours listed
|
||||
please visit IRC #hts for help.
|
||||
<li>XMLTV - This is am Internet-based suite of scripts, for more information
|
||||
about XMLTV please visit <a href="http://www.xmltv.org">http://www.xmltv.org</a>.
|
||||
To make use of the internal XMLTV grabber you typically require the xmltv-utils
|
||||
package to be installed. If you install new grabbers you will need to
|
||||
restart Tvheadend to pick these up as they're loaded at startup. If you see
|
||||
no XMLTV grabbers listed then most probably XMLTV is not properly installed
|
||||
and in the PATH.
|
||||
<li>PyEPG - This is another Internet-based scraper. It currently only
|
||||
supports the Atlas UK system (for which you need a key), but it does provide
|
||||
a very rich EPG data set. For more information see
|
||||
<a href='http://github.com/adamsutton/PyEPG'>http://github.com/adamsutton/PyEPG</a>.</li>
|
||||
</ul>
|
||||
|
||||
<h2>Configuration options</h2>
|
||||
|
@ -38,6 +61,12 @@
|
|||
<dt>Update channel name
|
||||
<dd>Automatically update channel icons using information provided
|
||||
by the enabled EPG providers.
|
||||
<dt>Periodic save EPG to disk Interval
|
||||
<dd>Writes the current in-memory EPG database to disk every x Hours
|
||||
(user defined), so should a crash/unexpected shutdown occur EPG
|
||||
data is saved periodically to the database (Re-read on
|
||||
next startup)
|
||||
Set to 0 to disable.
|
||||
</dl>
|
||||
|
||||
<h3>Internal Grabber</h3>
|
||||
|
@ -46,7 +75,7 @@
|
|||
<dd>Select which internal grabber to use.
|
||||
|
||||
<dt>Grab interval
|
||||
<dd>Time period between grabs. Value and unit are indepdently set.
|
||||
<dd>Time period between grabs. Value and unit are independently set.
|
||||
</dl>
|
||||
|
||||
<h3>Over-the-air Grabbers</h3>
|
||||
|
|
|
@ -19,6 +19,60 @@
|
|||
Select the path to use for DVB scan configuration files. Typically
|
||||
dvb-apps stores these in /usr/share/dvb/. Leave blank to use TVH's internal
|
||||
file set.
|
||||
</dl>
|
||||
|
||||
<p>
|
||||
Icon caching - this will cache any channel icons or other images (such as
|
||||
EPG metadata). These will then be served from the local webserver, this
|
||||
can be useful for multi-client systems and generally to reduce hits on
|
||||
upstream providers.
|
||||
</p>
|
||||
|
||||
<dl>
|
||||
|
||||
<dt>Enabled
|
||||
<dd>
|
||||
Select whether or not to enable caching. Note: even with this disabled
|
||||
you can still specify local (file://) icons and these will be served by
|
||||
the built-in webserver.
|
||||
|
||||
<dt>Re-fetch period (hours)
|
||||
<dd>
|
||||
How frequently the upstream provider is checked for changes.
|
||||
|
||||
<dt>Re-try period (hours)
|
||||
<dd>
|
||||
How frequently it will re-try fetching an image that has failed to be
|
||||
fetched.
|
||||
|
||||
<dt>Ignore invalid SSL certificates
|
||||
<dd>Ignore invalid/unverifiable (expired, self-certified, etc.) certificates
|
||||
|
||||
</dl>
|
||||
|
||||
<p>
|
||||
Time Update - TVH now has a built-in capability to update the systme time.
|
||||
However you should bare in mind that DVB time is not highly accurate and is
|
||||
prone to both jitter and variation between different transponders.
|
||||
<br/>
|
||||
Where possible its probably still better to use an internet based NTP source
|
||||
to synchronise the system clock.
|
||||
</p>
|
||||
|
||||
<dl>
|
||||
<dt>Update time
|
||||
<dd>Enable system time updates, this will only work if the user
|
||||
running TVH has rights to update the system clock (normally only root).
|
||||
|
||||
<dt>Enable NTP driver
|
||||
<dd>This will create an NTP driver (using shmem interface) that you can feed
|
||||
into ntpd. This can be run without root priviledges, but generally the
|
||||
performance is not that great.
|
||||
|
||||
<dt>Update tolerance (milliseconds)
|
||||
<dd>Only update the system clock (doesn't affect NTP driver) if the delta
|
||||
between the system clock and DVB time is greater than this. This can help
|
||||
stop horrible oscillations on the system clock.
|
||||
|
||||
</dl>
|
||||
</div>
|
||||
|
|
49
docs/html/config_timeshift.html
Normal file
49
docs/html/config_timeshift.html
Normal file
|
@ -0,0 +1,49 @@
|
|||
<div class="hts-doc-text">
|
||||
|
||||
<p>
|
||||
This tab is used to configure timeshift properties.
|
||||
|
||||
<p>
|
||||
Configuration options:
|
||||
<dl>
|
||||
<dt>Enabled
|
||||
<dd>Turn on and off timeshift.
|
||||
|
||||
<dt>On-Demand
|
||||
<dd>Turn this on to start timeshift buffer on pause. In this mode
|
||||
you cannot rewind the buffer (it always begins on the currently playing
|
||||
frame).
|
||||
|
||||
Without this option there will be a permanent, circular, buffer up to
|
||||
the limits defined below.
|
||||
|
||||
<dt>Storage Path:
|
||||
<dd>Where the timeshift data will be stored. If nothing is specified this
|
||||
will default to CONF_DIR/timeshift/buffer
|
||||
|
||||
<dt>Max. Period (mins):
|
||||
<dd>Specify the maximum time period that will be buffered for any given
|
||||
(client) subscription.
|
||||
|
||||
<dt>Unlimited:
|
||||
<dd>If checked, this allows the timeshift buffer to grow unbounded until
|
||||
your storage media runs out of space (WARNING: this could be dangerous!).
|
||||
|
||||
<dt>Max. Size (MegaBytes)
|
||||
<dd>Specifies the maximum combined size of all timeshift buffers. If you
|
||||
specify an unlimited period its highly recommended you specifying a value
|
||||
here.
|
||||
|
||||
<dt>Unlimited:
|
||||
<dd>If checked, this allows the combined size of all timeshift buffers to
|
||||
potentially grow unbounded until your storage media runs out of space
|
||||
(WARNING: this could be dangerous!).
|
||||
|
||||
</dl>
|
||||
Changes to any of these settings must be confirmed by pressing the
|
||||
'Save configuration' button before taking effect.
|
||||
|
||||
NOTE: These settings represent server side maximums, however the clients can
|
||||
request smaller buffers or even not to create a buffer at all (for example
|
||||
should they not support timeshifting).
|
||||
</div>
|
|
@ -1,6 +1,12 @@
|
|||
<div class="hts-doc-text">
|
||||
The DVR log displays a paged grid containing all scheduled, current
|
||||
and completed recordings. The list is sorted based on start time.
|
||||
The DVR log is split into a series of paged grids:
|
||||
|
||||
<ul>
|
||||
<li>Upcoming Recordings - stuff scheduled to be recorded in the future
|
||||
<li>Finished Recordings - stuff that has succesfully finished recording
|
||||
<li>Failed Recordings - stuff that failed to record
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
Use the bottom toolbar (not displayed in this manual) to navigate
|
||||
between pages in the grid.
|
||||
|
|
|
@ -62,7 +62,7 @@ sorted based on start time.
|
|||
[Record series] button that will record all entries in the series.
|
||||
<p>
|
||||
For events without any series link information, a [Autorec] button will be
|
||||
provided to create a pseudo series link using hte Autorec feature.
|
||||
provided to create a pseudo series link using the Autorec feature.
|
||||
<p>
|
||||
<img src="docresources/epg2.png">
|
||||
<p>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<dt>Multicasted IPTV.
|
||||
<dd>
|
||||
Both raw transport streams in UDP and transport stream in RTP in UDP
|
||||
is supported. The precense of RTP is autodetected.
|
||||
is supported. The presence of RTP is autodetected.
|
||||
</dd>
|
||||
<dt>Analog TV
|
||||
<dd>
|
||||
|
|
|
@ -38,7 +38,7 @@ Parts of this documentation is also available in the Tvheadend man page.
|
|||
access to all features / settings</b> in the web user interface. If
|
||||
this is the first time you setup Tvheadend you are most encouraged to
|
||||
enter the web user interface, selected the 'Configuration' + 'Access Control'
|
||||
tab and make reasonable changes. Futher help / documentation can be obtained
|
||||
tab and make reasonable changes. Further help / documentation can be obtained
|
||||
inside the web interface.
|
||||
|
||||
<dt>Settings storage
|
||||
|
@ -47,7 +47,7 @@ Parts of this documentation is also available in the Tvheadend man page.
|
|||
|
||||
<dt>Logging
|
||||
<dd>
|
||||
All activity inside tvheadend is logged to syslog using log facility.
|
||||
All activity inside Tvheadend is logged to syslog using log facility.
|
||||
Also, if logged in to the web interface you will receive the same log in the bottom
|
||||
tab (System log).
|
||||
|
||||
|
|
|
@ -1,16 +1,8 @@
|
|||
<div class="hts-doc-text">
|
||||
<p>
|
||||
|
||||
|
||||
<center>
|
||||
<h1>HTS Tvheadend 3.2</h1>
|
||||
© 2006 - 2012, Andreas Öman, et al.<br><br>
|
||||
<h1>Tvheadend</h1>
|
||||
© 2006 - 2013, Andreas Öman, et al.<br><br>
|
||||
<img style="padding: 0px" src="docresources/tvheadendlogo.png">
|
||||
</center>
|
||||
|
||||
Tvheadend is part of the <b>HTS</b> project hosted at
|
||||
<a href="http://www.lonelycoder.com/hts">http://www.lonelycoder.com/hts</a>
|
||||
<p>
|
||||
It functions primarily as a TV streaming back end for the Showtime Mediaplayer but
|
||||
can be used standalone for other purposes, such as a Digital Video Recorder.
|
||||
</div>
|
||||
|
|
|
@ -1,16 +1,9 @@
|
|||
<div class="hts-doc-text">
|
||||
<p>
|
||||
If you want to build tvheadend from source, please visit
|
||||
<a href="http://trac.lonelycoder.com/hts/wiki/howtobuildhts">this</a> page.
|
||||
Please notice that wiki development site only reflects the work
|
||||
in HEAD (-current). It should not deviate much should you want to
|
||||
If you want to build Tvheadend from source, please visit
|
||||
<a href="https://tvheadend.org/projects/tvheadend/wiki/Building">this</a> page.
|
||||
Please note: that wiki development site only reflects the work
|
||||
in master. It should not deviate much should you want to
|
||||
build a released version from source, but you never know.
|
||||
<p>
|
||||
|
||||
Tvheadend is part of the <b>HTS</b> project hosted at
|
||||
<a href="http://hts.lonelycoder.com/">http://hts.lonelycoder.com/</a>.
|
||||
|
||||
<p>
|
||||
It functions primarily as a TV backend for the Showtime Media player but
|
||||
can be used standalone for other purposes.
|
||||
</div>
|
||||
|
|
|
@ -129,8 +129,12 @@ def serialize ( msg ):
|
|||
return int2bin(cnt) + binary_write(msg)
|
||||
|
||||
# Deserialize an htsmsg
|
||||
def deserialize0 ( data ):
|
||||
msg = {}
|
||||
def deserialize0 ( data, typ = HMF_MAP ):
|
||||
islist = False
|
||||
msg = {}
|
||||
if (typ == HMF_LIST):
|
||||
islist = True
|
||||
msg = []
|
||||
while len(data) > 5:
|
||||
typ = ord(data[0])
|
||||
nlen = ord(data[1])
|
||||
|
@ -152,10 +156,13 @@ def deserialize0 ( data ):
|
|||
item = (item << 8) | ord(data[i])
|
||||
i = i - 1
|
||||
elif typ in [ HMF_LIST, HMF_MAP ]:
|
||||
item = deserialize0(data[:dlen])
|
||||
item = deserialize0(data[:dlen], typ)
|
||||
else:
|
||||
raise Exception('invalid data type %d' % typ)
|
||||
msg[name] = item
|
||||
if islist:
|
||||
msg.append(item)
|
||||
else:
|
||||
msg[name] = item
|
||||
data = data[dlen:]
|
||||
return msg
|
||||
|
||||
|
|
|
@ -13,21 +13,82 @@ Media player.
|
|||
.SH OPTIONS
|
||||
All arguments are optional.
|
||||
.TP
|
||||
\fB\-v\fR, \fB\-\-version\fR
|
||||
Show version information.
|
||||
.TP
|
||||
\fB\-h\fR, \fB\-\-help\fR
|
||||
Show built-in help information (may be more up to date).
|
||||
.TP
|
||||
\fB\-c\fR, \fB\-\-config\fR
|
||||
Specify an alternate config path; the default is \fI${HOME}/.hts\fR
|
||||
.TP
|
||||
\fB\-f
|
||||
Fork and become a background process (deamon). Default no.
|
||||
.TP
|
||||
\fB\-u \fR\fIusername\fR
|
||||
\fB\-u\fR \fIusername\fR, \fB\-\-user\fR \fIusername\fR
|
||||
Run as user \fIusername\fR. Only applicable if daemonizing. Default is to
|
||||
use the uid of 1 (daemon on most systems).
|
||||
.TP
|
||||
\fB\-g \fR\fIgroupname\fR
|
||||
\fB\-g\fR \fIgroupname\fR, \fB\-\-group \fR\fIgroupname\fR
|
||||
Run as group \fR\fIgroupname\fR. Only applicable if daemonizing. Default is to use the 'video' group. If the 'video' group does not exist, gid 1 (daemon) will be used.
|
||||
.TP
|
||||
\fB\-C
|
||||
\fB\-p\fR \fIpidpath\fR, \fB\-\-pid \fR\fIpidpath\fR
|
||||
Specify alternative PID path file (default /var/run/tvheadend.pid).
|
||||
.TP
|
||||
\fB\-C\fR, \fB\-\-firstrun\fR
|
||||
If no useraccount exist then create one with no username and no
|
||||
password. Use with care as it will allow world-wide administrative
|
||||
access to your Tvheadend installation until you edit the
|
||||
access-control from within the Tvheadend UI.
|
||||
.TP
|
||||
\fB\-a\fR, \fB\-\-adapters\fR
|
||||
Only use specified DVB adapters (comma separated).
|
||||
.TP
|
||||
\fB\-6\fR, \fB\-\-ipv6\fR
|
||||
Listen on IPv6.
|
||||
.TP
|
||||
\fB\-b\fR \fIaddress\fR, \fB\-\-bindaddr\fR \fIaddress\fR
|
||||
Specify an interface IP address on which incoming HTTP and HTSP connections
|
||||
will be accepted. By default, connections are accepted on all interfaces.
|
||||
.TP
|
||||
\fB\-\-http_port
|
||||
Specify alternative http port (default 9881).
|
||||
.TP
|
||||
\fB\-\-http_root
|
||||
Specify alternative http webroot.
|
||||
.TP
|
||||
\fB\-\-htsp_port
|
||||
Specify alternative htsp port (default 9882).
|
||||
.TP
|
||||
\fB\-\-htsp_port2
|
||||
Specify extra htsp port.
|
||||
.TP
|
||||
\fB\-d\fR, \fB\-\-debug\fR
|
||||
Enable all debug.
|
||||
.TP
|
||||
\fB\-s\fR, \fB\-\-syslog\fR
|
||||
Enable debug to syslog.
|
||||
.TP
|
||||
\fB\-\-uidebug
|
||||
Enable web UI debug.
|
||||
.TP
|
||||
\fB\-l\fR, \fB\-\-log\fR
|
||||
Log to file.
|
||||
.TP
|
||||
\fB\-A\fR, \fB\-\-abort\fR
|
||||
Immediately abort on startup (for debug).
|
||||
.TP
|
||||
\fB\-\-noacl
|
||||
Do not perform any access control checking.
|
||||
.TP
|
||||
\fB\-R\fR, \fB\-\-dvbraw\fR
|
||||
Use rawts file to create virtual adapter.
|
||||
.TP
|
||||
\fB\-r\fR, \fB\-\-rawts\fR
|
||||
Use rawts file to generate virtual services.
|
||||
.TP
|
||||
\fB\-j\fR, \fB\-\-join\fR
|
||||
Subscribe to a service permanently.
|
||||
.SH "LOGGING"
|
||||
All activity inside tvheadend is logged to syslog using log facility
|
||||
\fBLOG_DAEMON\fR.
|
||||
|
@ -71,8 +132,8 @@ If daemonizing, tvheadend will writes it pid in /var/run/tvheadend.pid
|
|||
.B Tvheadend
|
||||
and this man page is maintained by Andreas Oeman
|
||||
.PP
|
||||
(andreas a lonelycoder d com)
|
||||
(andreas a tvheadend d org)
|
||||
.PP
|
||||
You may also visit #hts at irc.freenode.net
|
||||
.SH "SEE ALSO"
|
||||
.BR http://www.lonelycoder.com/hts/
|
||||
.BR https://tvheadend.org
|
||||
|
|
249
src/access.c
249
src/access.c
|
@ -43,6 +43,8 @@ struct access_ticket_queue access_tickets;
|
|||
const char *superuser_username;
|
||||
const char *superuser_password;
|
||||
|
||||
static int access_noacl;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -149,6 +151,76 @@ access_ticket_verify(const char *id, const char *resource)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static int
|
||||
netmask_verify(access_entry_t *ae, struct sockaddr *src)
|
||||
{
|
||||
access_ipmask_t *ai;
|
||||
int isv4v6 = 0;
|
||||
uint32_t v4v6 = 0;
|
||||
|
||||
if(src->sa_family == AF_INET6)
|
||||
{
|
||||
struct in6_addr *in6 = &(((struct sockaddr_in6 *)src)->sin6_addr);
|
||||
uint32_t *a32 = (uint32_t*)in6->s6_addr;
|
||||
if(a32[0] == 0 && a32[1] == 0 && ntohl(a32[2]) == 0x0000FFFFu)
|
||||
{
|
||||
isv4v6 = 1;
|
||||
v4v6 = ntohl(a32[3]);
|
||||
}
|
||||
}
|
||||
|
||||
TAILQ_FOREACH(ai, &ae->ae_ipmasks, ai_link)
|
||||
{
|
||||
if(ai->ai_ipv6 == 0 && src->sa_family == AF_INET)
|
||||
{
|
||||
struct sockaddr_in *in4 = (struct sockaddr_in *)src;
|
||||
uint32_t b = ntohl(in4->sin_addr.s_addr);
|
||||
if((b & ai->ai_netmask) == ai->ai_network)
|
||||
return 1;
|
||||
}
|
||||
else if(ai->ai_ipv6 == 0 && isv4v6)
|
||||
{
|
||||
if((v4v6 & ai->ai_netmask) == ai->ai_network)
|
||||
return 1;
|
||||
}
|
||||
else if(ai->ai_ipv6 && isv4v6)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if(ai->ai_ipv6 && src->sa_family == AF_INET6)
|
||||
{
|
||||
struct in6_addr *in6 = &(((struct sockaddr_in6 *)src)->sin6_addr);
|
||||
uint8_t *a8 = (uint8_t*)in6->s6_addr;
|
||||
uint8_t *m8 = (uint8_t*)ai->ai_ip6.s6_addr;
|
||||
int slen = ai->ai_prefixlen;
|
||||
uint32_t apos = 0;
|
||||
uint8_t lastMask = (0xFFu << (8 - (slen % 8)));
|
||||
|
||||
if(slen < 0 || slen > 128)
|
||||
continue;
|
||||
|
||||
while(slen >= 8)
|
||||
{
|
||||
if(a8[apos] != m8[apos])
|
||||
break;
|
||||
|
||||
apos += 1;
|
||||
slen -= 8;
|
||||
}
|
||||
if(slen >= 8)
|
||||
continue;
|
||||
|
||||
if(slen == 0 || (a8[apos] & lastMask) == (m8[apos] & lastMask))
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -157,10 +229,11 @@ access_verify(const char *username, const char *password,
|
|||
struct sockaddr *src, uint32_t mask)
|
||||
{
|
||||
uint32_t bits = 0;
|
||||
struct sockaddr_in *si = (struct sockaddr_in *)src;
|
||||
uint32_t b = ntohl(si->sin_addr.s_addr);
|
||||
access_entry_t *ae;
|
||||
|
||||
if (access_noacl)
|
||||
return 0;
|
||||
|
||||
if(username != NULL && superuser_username != NULL &&
|
||||
password != NULL && superuser_password != NULL &&
|
||||
!strcmp(username, superuser_username) &&
|
||||
|
@ -182,7 +255,7 @@ access_verify(const char *username, const char *password,
|
|||
continue; /* username/password mismatch */
|
||||
}
|
||||
|
||||
if((b & ae->ae_netmask) != ae->ae_network)
|
||||
if(!netmask_verify(ae, src))
|
||||
continue; /* IP based access mismatches */
|
||||
|
||||
bits |= ae->ae_rights;
|
||||
|
@ -200,14 +273,15 @@ access_get_hashed(const char *username, const uint8_t digest[20],
|
|||
const uint8_t *challenge, struct sockaddr *src,
|
||||
int *entrymatch)
|
||||
{
|
||||
struct sockaddr_in *si = (struct sockaddr_in *)src;
|
||||
uint32_t b = ntohl(si->sin_addr.s_addr);
|
||||
access_entry_t *ae;
|
||||
SHA_CTX shactx;
|
||||
uint8_t d[20];
|
||||
uint32_t r = 0;
|
||||
int match = 0;
|
||||
|
||||
if(access_noacl)
|
||||
return 0xffffffff;
|
||||
|
||||
if(superuser_username != NULL && superuser_password != NULL) {
|
||||
|
||||
SHA1_Init(&shactx);
|
||||
|
@ -226,7 +300,7 @@ access_get_hashed(const char *username, const uint8_t digest[20],
|
|||
if(!ae->ae_enabled)
|
||||
continue;
|
||||
|
||||
if((b & ae->ae_netmask) != ae->ae_network)
|
||||
if(!netmask_verify(ae, src))
|
||||
continue; /* IP based access mismatches */
|
||||
|
||||
SHA1_Init(&shactx);
|
||||
|
@ -253,17 +327,18 @@ access_get_hashed(const char *username, const uint8_t digest[20],
|
|||
uint32_t
|
||||
access_get_by_addr(struct sockaddr *src)
|
||||
{
|
||||
struct sockaddr_in *si = (struct sockaddr_in *)src;
|
||||
uint32_t b = ntohl(si->sin_addr.s_addr);
|
||||
access_entry_t *ae;
|
||||
uint32_t r = 0;
|
||||
|
||||
TAILQ_FOREACH(ae, &access_entries, ae_link) {
|
||||
|
||||
if(!ae->ae_enabled)
|
||||
continue;
|
||||
|
||||
if(ae->ae_username[0] != '*')
|
||||
continue;
|
||||
|
||||
if((b & ae->ae_netmask) != ae->ae_network)
|
||||
if(!netmask_verify(ae, src))
|
||||
continue; /* IP based access mismatches */
|
||||
|
||||
r |= ae->ae_rights;
|
||||
|
@ -293,28 +368,90 @@ static void
|
|||
access_set_prefix(access_entry_t *ae, const char *prefix)
|
||||
{
|
||||
char buf[100];
|
||||
char tokbuf[4096];
|
||||
int prefixlen;
|
||||
char *p;
|
||||
char *p, *tok, *saveptr;
|
||||
access_ipmask_t *ai;
|
||||
|
||||
if(strlen(prefix) > 90)
|
||||
return;
|
||||
|
||||
strcpy(buf, prefix);
|
||||
p = strchr(buf, '/');
|
||||
if(p) {
|
||||
*p++ = 0;
|
||||
prefixlen = atoi(p);
|
||||
if(prefixlen > 32)
|
||||
return;
|
||||
} else {
|
||||
prefixlen = 32;
|
||||
while((ai = TAILQ_FIRST(&ae->ae_ipmasks)) != NULL)
|
||||
{
|
||||
TAILQ_REMOVE(&ae->ae_ipmasks, ai, ai_link);
|
||||
free(ai);
|
||||
}
|
||||
|
||||
ae->ae_ip.s_addr = inet_addr(buf);
|
||||
ae->ae_prefixlen = prefixlen;
|
||||
strncpy(tokbuf, prefix, 4095);
|
||||
tokbuf[4095] = 0;
|
||||
tok = strtok_r(tokbuf, ",;| ", &saveptr);
|
||||
|
||||
ae->ae_netmask = prefixlen ? 0xffffffff << (32 - prefixlen) : 0;
|
||||
ae->ae_network = ntohl(ae->ae_ip.s_addr) & ae->ae_netmask;
|
||||
while(tok != NULL)
|
||||
{
|
||||
ai = calloc(1, sizeof(access_ipmask_t));
|
||||
|
||||
if(strlen(tok) > 90 || strlen(tok) == 0)
|
||||
{
|
||||
free(ai);
|
||||
tok = strtok_r(NULL, ",;| ", &saveptr);
|
||||
continue;
|
||||
}
|
||||
|
||||
strcpy(buf, tok);
|
||||
|
||||
if(strchr(buf, ':') != NULL)
|
||||
ai->ai_ipv6 = 1;
|
||||
else
|
||||
ai->ai_ipv6 = 0;
|
||||
|
||||
if(ai->ai_ipv6)
|
||||
{
|
||||
p = strchr(buf, '/');
|
||||
if(p)
|
||||
{
|
||||
*p++ = 0;
|
||||
prefixlen = atoi(p);
|
||||
if(prefixlen > 128)
|
||||
{
|
||||
free(ai);
|
||||
tok = strtok_r(NULL, ",;| ", &saveptr);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
prefixlen = 128;
|
||||
}
|
||||
|
||||
ai->ai_prefixlen = prefixlen;
|
||||
inet_pton(AF_INET6, buf, &ai->ai_ip6);
|
||||
|
||||
ai->ai_netmask = 0xffffffff;
|
||||
ai->ai_network = 0x00000000;
|
||||
}
|
||||
else
|
||||
{
|
||||
p = strchr(buf, '/');
|
||||
if(p)
|
||||
{
|
||||
*p++ = 0;
|
||||
prefixlen = atoi(p);
|
||||
if(prefixlen > 32)
|
||||
{
|
||||
free(ai);
|
||||
tok = strtok_r(NULL, ",;| ", &saveptr);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
prefixlen = 32;
|
||||
}
|
||||
|
||||
ai->ai_ip.s_addr = inet_addr(buf);
|
||||
ai->ai_prefixlen = prefixlen;
|
||||
|
||||
ai->ai_netmask = prefixlen ? 0xffffffff << (32 - prefixlen) : 0;
|
||||
ai->ai_network = ntohl(ai->ai_ip.s_addr) & ai->ai_netmask;
|
||||
}
|
||||
|
||||
TAILQ_INSERT_TAIL(&ae->ae_ipmasks, ai, ai_link);
|
||||
|
||||
tok = strtok_r(NULL, ",;| ", &saveptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -325,6 +462,7 @@ access_set_prefix(access_entry_t *ae, const char *prefix)
|
|||
static access_entry_t *
|
||||
access_entry_find(const char *id, int create)
|
||||
{
|
||||
access_ipmask_t *ai;
|
||||
access_entry_t *ae;
|
||||
char buf[20];
|
||||
static int tally;
|
||||
|
@ -351,6 +489,13 @@ access_entry_find(const char *id, int create)
|
|||
ae->ae_username = strdup("*");
|
||||
ae->ae_password = strdup("*");
|
||||
ae->ae_comment = strdup("New entry");
|
||||
TAILQ_INIT(&ae->ae_ipmasks);
|
||||
ai = calloc(1, sizeof(access_ipmask_t));
|
||||
ai->ai_ipv6 = 1;
|
||||
TAILQ_INSERT_HEAD(&ae->ae_ipmasks, ai, ai_link);
|
||||
ai = calloc(1, sizeof(access_ipmask_t));
|
||||
ai->ai_ipv6 = 0;
|
||||
TAILQ_INSERT_HEAD(&ae->ae_ipmasks, ai, ai_link);
|
||||
TAILQ_INSERT_TAIL(&access_entries, ae, ae_link);
|
||||
return ae;
|
||||
}
|
||||
|
@ -363,6 +508,14 @@ access_entry_find(const char *id, int create)
|
|||
static void
|
||||
access_entry_destroy(access_entry_t *ae)
|
||||
{
|
||||
access_ipmask_t *ai;
|
||||
|
||||
while((ai = TAILQ_FIRST(&ae->ae_ipmasks)) != NULL)
|
||||
{
|
||||
TAILQ_REMOVE(&ae->ae_ipmasks, ai, ai_link);
|
||||
free(ai);
|
||||
}
|
||||
|
||||
free(ae->ae_id);
|
||||
free(ae->ae_username);
|
||||
free(ae->ae_password);
|
||||
|
@ -378,7 +531,12 @@ static htsmsg_t *
|
|||
access_record_build(access_entry_t *ae)
|
||||
{
|
||||
htsmsg_t *e = htsmsg_create_map();
|
||||
char buf[100];
|
||||
access_ipmask_t *ai;
|
||||
char addrbuf[50];
|
||||
char buf[4096];
|
||||
int pos = 0;
|
||||
|
||||
memset(buf, 0, 4096);
|
||||
|
||||
htsmsg_add_u32(e, "enabled", !!ae->ae_enabled);
|
||||
|
||||
|
@ -386,8 +544,22 @@ access_record_build(access_entry_t *ae)
|
|||
htsmsg_add_str(e, "password", ae->ae_password);
|
||||
htsmsg_add_str(e, "comment", ae->ae_comment);
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s/%d", inet_ntoa(ae->ae_ip), ae->ae_prefixlen);
|
||||
htsmsg_add_str(e, "prefix", buf);
|
||||
TAILQ_FOREACH(ai, &ae->ae_ipmasks, ai_link)
|
||||
{
|
||||
if(sizeof(buf)-pos <= 0)
|
||||
break;
|
||||
|
||||
if(ai->ai_ipv6)
|
||||
{
|
||||
inet_ntop(AF_INET6, &ai->ai_ip6, addrbuf, 50);
|
||||
pos += snprintf(buf+pos, sizeof(buf)-pos, ",%s/%d", addrbuf, ai->ai_prefixlen);
|
||||
}
|
||||
else
|
||||
{
|
||||
pos += snprintf(buf+pos, sizeof(buf)-pos, ",%s/%d", inet_ntoa(ai->ai_ip), ai->ai_prefixlen);
|
||||
}
|
||||
}
|
||||
htsmsg_add_str(e, "prefix", buf + 1);
|
||||
|
||||
htsmsg_add_u32(e, "streaming", ae->ae_rights & ACCESS_STREAMING ? 1 : 0);
|
||||
htsmsg_add_u32(e, "dvr" , ae->ae_rights & ACCESS_RECORDER ? 1 : 0);
|
||||
|
@ -532,11 +704,12 @@ static const dtable_class_t access_dtc = {
|
|||
*
|
||||
*/
|
||||
void
|
||||
access_init(int createdefault)
|
||||
access_init(int createdefault, int noacl)
|
||||
{
|
||||
dtable_t *dt;
|
||||
htsmsg_t *r, *m;
|
||||
access_entry_t *ae;
|
||||
access_ipmask_t *ai;
|
||||
const char *s;
|
||||
|
||||
static struct {
|
||||
|
@ -544,6 +717,10 @@ access_init(int createdefault)
|
|||
struct timeval tv;
|
||||
} randseed;
|
||||
|
||||
access_noacl = noacl;
|
||||
if (noacl)
|
||||
tvhlog(LOG_WARNING, "access", "Access control checking disabled");
|
||||
|
||||
randseed.pid = getpid();
|
||||
gettimeofday(&randseed.tv, NULL);
|
||||
RAND_seed(&randseed, sizeof(randseed));
|
||||
|
@ -563,11 +740,21 @@ access_init(int createdefault)
|
|||
ae->ae_enabled = 1;
|
||||
ae->ae_rights = 0xffffffff;
|
||||
|
||||
TAILQ_INIT(&ae->ae_ipmasks);
|
||||
|
||||
ai = calloc(1, sizeof(access_ipmask_t));
|
||||
ai->ai_ipv6 = 1;
|
||||
TAILQ_INSERT_HEAD(&ae->ae_ipmasks, ai, ai_link);
|
||||
|
||||
ai = calloc(1, sizeof(access_ipmask_t));
|
||||
ai->ai_ipv6 = 0;
|
||||
TAILQ_INSERT_HEAD(&ae->ae_ipmasks, ai, ai_link);
|
||||
|
||||
r = access_record_build(ae);
|
||||
dtable_record_store(dt, ae->ae_id, r);
|
||||
htsmsg_destroy(r);
|
||||
|
||||
tvhlog(LOG_WARNING, "accesscontrol",
|
||||
tvhlog(LOG_WARNING, "access",
|
||||
"Created default wide open access controle entry");
|
||||
}
|
||||
|
||||
|
|
21
src/access.h
21
src/access.h
|
@ -20,6 +20,20 @@
|
|||
#define ACCESS_H_
|
||||
|
||||
|
||||
typedef struct access_ipmask {
|
||||
TAILQ_ENTRY(access_ipmask) ai_link;
|
||||
|
||||
int ai_ipv6;
|
||||
|
||||
struct in_addr ai_ip;
|
||||
struct in6_addr ai_ip6;
|
||||
|
||||
int ai_prefixlen;
|
||||
|
||||
uint32_t ai_network;
|
||||
uint32_t ai_netmask;
|
||||
} access_ipmask_t;
|
||||
|
||||
TAILQ_HEAD(access_entry_queue, access_entry);
|
||||
|
||||
extern struct access_entry_queue access_entries;
|
||||
|
@ -31,15 +45,12 @@ typedef struct access_entry {
|
|||
char *ae_username;
|
||||
char *ae_password;
|
||||
char *ae_comment;
|
||||
struct in_addr ae_ip;
|
||||
int ae_prefixlen;
|
||||
int ae_enabled;
|
||||
|
||||
|
||||
uint32_t ae_rights;
|
||||
|
||||
uint32_t ae_network; /* derived from ae_ip */
|
||||
uint32_t ae_netmask; /* derived from ae_prefixlen */
|
||||
TAILQ_HEAD(, access_ipmask) ae_ipmasks;
|
||||
} access_entry_t;
|
||||
|
||||
TAILQ_HEAD(access_ticket_queue, access_ticket);
|
||||
|
@ -99,6 +110,6 @@ uint32_t access_get_by_addr(struct sockaddr *src);
|
|||
/**
|
||||
*
|
||||
*/
|
||||
void access_init(int createdefault);
|
||||
void access_init(int createdefault, int noacl);
|
||||
|
||||
#endif /* ACCESS_H_ */
|
||||
|
|
107
src/atomic.h
107
src/atomic.h
|
@ -16,16 +16,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef HTSATOMIC_H__
|
||||
#define HTSATOMIC_H__
|
||||
|
||||
/**
|
||||
* Atomically add 'incr' to *ptr and return the previous value
|
||||
*/
|
||||
|
||||
|
||||
|
||||
#if defined(linux) && __GNUC__ >= 4 && __GNUC_MINOR__ >=3
|
||||
#pragma once
|
||||
|
||||
static inline int
|
||||
atomic_add(volatile int *ptr, int incr)
|
||||
|
@ -33,80 +24,38 @@ atomic_add(volatile int *ptr, int incr)
|
|||
return __sync_fetch_and_add(ptr, incr);
|
||||
}
|
||||
|
||||
#elif defined(__i386__) || defined(__x86_64__)
|
||||
static inline int
|
||||
atomic_add(volatile int *ptr, int incr)
|
||||
atomic_exchange(volatile int *ptr, int new)
|
||||
{
|
||||
int r;
|
||||
asm volatile("lock; xaddl %0, %1" :
|
||||
"=r"(r), "=m"(*ptr) : "0" (incr), "m" (*ptr) : "memory");
|
||||
return r;
|
||||
}
|
||||
#elif defined(WII)
|
||||
|
||||
#include <ogc/machine/processor.h>
|
||||
|
||||
static inline int
|
||||
atomic_add(volatile int *ptr, int incr)
|
||||
{
|
||||
int r, level;
|
||||
|
||||
/*
|
||||
* Last time i checked libogc's context switcher did not do the
|
||||
* necessary operations to clear locks held by lwarx/stwcx.
|
||||
* Thus we need to resort to other means
|
||||
*/
|
||||
|
||||
_CPU_ISR_Disable(level);
|
||||
|
||||
r = *ptr;
|
||||
*ptr = *ptr + incr;
|
||||
|
||||
_CPU_ISR_Restore(level);
|
||||
|
||||
return r;
|
||||
}
|
||||
#elif defined(__ppc__) || defined(__PPC__)
|
||||
|
||||
/* somewhat based on code from darwin gcc */
|
||||
static inline int
|
||||
atomic_add (volatile int *ptr, int incr)
|
||||
{
|
||||
int tmp, res;
|
||||
asm volatile("0:\n"
|
||||
"lwarx %1,0,%2\n"
|
||||
"add%I3 %0,%1,%3\n"
|
||||
"stwcx. %0,0,%2\n"
|
||||
"bne- 0b\n"
|
||||
: "=&r"(tmp), "=&b"(res)
|
||||
: "r" (ptr), "Ir"(incr)
|
||||
: "cr0", "memory");
|
||||
|
||||
return res;
|
||||
return __sync_lock_test_and_set(ptr, new);
|
||||
}
|
||||
|
||||
#elif defined(__arm__)
|
||||
|
||||
static inline int
|
||||
atomic_add(volatile int *ptr, int val)
|
||||
static inline uint64_t
|
||||
atomic_add_u64(volatile uint64_t *ptr, uint64_t incr)
|
||||
{
|
||||
int a, b, c;
|
||||
|
||||
asm volatile( "0:\n\t"
|
||||
"ldr %0, [%3]\n\t"
|
||||
"add %1, %0, %4\n\t"
|
||||
"swp %2, %1, [%3]\n\t"
|
||||
"cmp %0, %2\n\t"
|
||||
"swpne %1, %2, [%3]\n\t"
|
||||
"bne 0b"
|
||||
: "=&r" (a), "=&r" (b), "=&r" (c)
|
||||
: "r" (ptr), "r" (val)
|
||||
: "cc", "memory");
|
||||
return a;
|
||||
}
|
||||
|
||||
#if ENABLE_ATOMIC64
|
||||
return __sync_fetch_and_add(ptr, incr);
|
||||
#else
|
||||
#error Missing atomic ops
|
||||
uint64_t ret;
|
||||
pthread_mutex_lock(&atomic_lock);
|
||||
ret = *ptr;
|
||||
*ptr += incr;
|
||||
pthread_mutex_unlock(&atomic_lock);
|
||||
return ret;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif /* HTSATOMIC_H__ */
|
||||
static inline uint64_t
|
||||
atomic_pre_add_u64(volatile uint64_t *ptr, uint64_t incr)
|
||||
{
|
||||
#if ENABLE_ATOMIC64
|
||||
return __sync_add_and_fetch(ptr, incr);
|
||||
#else
|
||||
uint64_t ret;
|
||||
pthread_mutex_lock(&atomic_lock);
|
||||
*ptr += incr;
|
||||
ret = *ptr;
|
||||
pthread_mutex_unlock(&atomic_lock);
|
||||
return ret;
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -133,7 +133,7 @@ create_services(AvahiClient *c)
|
|||
/* Add the service for HTSP */
|
||||
if ((ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC,
|
||||
AVAHI_PROTO_UNSPEC, 0, name,
|
||||
"_htsp._tcp", NULL, NULL,htsp_port,
|
||||
"_htsp._tcp", NULL, NULL,tvheadend_htsp_port,
|
||||
NULL)) < 0) {
|
||||
|
||||
if (ret == AVAHI_ERR_COLLISION)
|
||||
|
@ -149,7 +149,7 @@ create_services(AvahiClient *c)
|
|||
/* Add the service for HTTP */
|
||||
if ((ret = avahi_entry_group_add_service(group, AVAHI_IF_UNSPEC,
|
||||
AVAHI_PROTO_UNSPEC, 0, name,
|
||||
"_http._tcp", NULL, NULL, webui_port,
|
||||
"_http._tcp", NULL, NULL, tvheadend_webui_port,
|
||||
"path=/",
|
||||
NULL)) < 0) {
|
||||
|
||||
|
|
|
@ -186,8 +186,6 @@ avc_convert_pkt(th_pkt_t *src)
|
|||
pkt->pkt_header = NULL;
|
||||
pkt->pkt_payload = NULL;
|
||||
|
||||
pkt->pkt_payload = malloc(sizeof(pktbuf_t));
|
||||
pkt->pkt_payload->pb_refcount=1;
|
||||
if (src->pkt_header) {
|
||||
sbuf_t headers;
|
||||
sbuf_init(&headers);
|
||||
|
|
861
src/capmt.c
861
src/capmt.c
File diff suppressed because it is too large
Load diff
|
@ -38,7 +38,8 @@
|
|||
#include "dtable.h"
|
||||
#include "notify.h"
|
||||
#include "dvr/dvr.h"
|
||||
#include "htsp.h"
|
||||
#include "htsp_server.h"
|
||||
#include "imagecache.h"
|
||||
|
||||
struct channel_tree channel_name_tree;
|
||||
static struct channel_tree channel_identifier_tree;
|
||||
|
@ -113,7 +114,6 @@ chidcmp(const channel_t *a, const channel_t *b)
|
|||
return a->ch_id - b->ch_id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -199,7 +199,9 @@ channel_create2(const char *name, int number)
|
|||
channel_t *
|
||||
channel_create ( void )
|
||||
{
|
||||
return channel_create2(NULL, 0);
|
||||
channel_t *ch = channel_create2(NULL, 0);
|
||||
channel_save(ch);
|
||||
return ch;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -210,14 +212,14 @@ channel_find_by_name(const char *name, int create, int channel_number)
|
|||
{
|
||||
channel_t skel, *ch;
|
||||
|
||||
if (!name || !*name) return NULL;
|
||||
|
||||
lock_assert(&global_lock);
|
||||
|
||||
skel.ch_name = (char *)name;
|
||||
ch = RB_FIND(&channel_name_tree, &skel, ch_name_link, channelcmp);
|
||||
if(ch != NULL || create == 0)
|
||||
return ch;
|
||||
if (name) {
|
||||
skel.ch_name = (char *)name;
|
||||
ch = RB_FIND(&channel_name_tree, &skel, ch_name_link, channelcmp);
|
||||
if(ch != NULL || create == 0)
|
||||
return ch;
|
||||
}
|
||||
return channel_create2(name, channel_number);
|
||||
}
|
||||
|
||||
|
@ -268,6 +270,7 @@ channel_load_one(htsmsg_t *c, int id)
|
|||
epggrab_channel_add(ch);
|
||||
|
||||
tvh_str_update(&ch->ch_icon, htsmsg_get_str(c, "icon"));
|
||||
imagecache_get_id(ch->ch_icon);
|
||||
|
||||
htsmsg_get_s32(c, "dvr_extra_time_pre", &ch->ch_dvr_extra_time_pre);
|
||||
htsmsg_get_s32(c, "dvr_extra_time_post", &ch->ch_dvr_extra_time_post);
|
||||
|
@ -343,6 +346,7 @@ channel_save(channel_t *ch)
|
|||
int
|
||||
channel_rename(channel_t *ch, const char *newname)
|
||||
{
|
||||
dvr_entry_t *de;
|
||||
service_t *t;
|
||||
|
||||
lock_assert(&global_lock);
|
||||
|
@ -361,6 +365,11 @@ channel_rename(channel_t *ch, const char *newname)
|
|||
|
||||
LIST_FOREACH(t, &ch->ch_services, s_ch_link)
|
||||
t->s_config_save(t);
|
||||
|
||||
LIST_FOREACH(de, &ch->ch_dvrs, de_channel_link) {
|
||||
dvr_entry_save(de);
|
||||
dvr_entry_notify(de);
|
||||
}
|
||||
|
||||
channel_save(ch);
|
||||
htsp_channel_update(ch);
|
||||
|
@ -452,6 +461,7 @@ channel_set_icon(channel_t *ch, const char *icon)
|
|||
|
||||
free(ch->ch_icon);
|
||||
ch->ch_icon = strdup(icon);
|
||||
imagecache_get_id(icon);
|
||||
channel_save(ch);
|
||||
htsp_channel_update(ch);
|
||||
}
|
||||
|
|
|
@ -43,6 +43,31 @@ htsmsg_t *config_get_all ( void )
|
|||
return htsmsg_copy(config);
|
||||
}
|
||||
|
||||
static int _config_set_str ( const char *fld, const char *val )
|
||||
{
|
||||
const char *c = htsmsg_get_str(config, fld);
|
||||
if (!c || strcmp(c, val)) {
|
||||
if (c) htsmsg_delete_field(config, fld);
|
||||
htsmsg_add_str(config, fld, val);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if 0
|
||||
static int _config_set_u32 ( const char *fld, uint32_t val )
|
||||
{
|
||||
uint32_t u32;
|
||||
int ret = htsmsg_get_u32(config, fld, &u32);
|
||||
if (ret || (u32 != val)) {
|
||||
if (!ret) htsmsg_delete_field(config, fld);
|
||||
htsmsg_add_u32(config, fld, val);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
const char *config_get_language ( void )
|
||||
{
|
||||
return htsmsg_get_str(config, "language");
|
||||
|
@ -50,13 +75,7 @@ const char *config_get_language ( void )
|
|||
|
||||
int config_set_language ( const char *lang )
|
||||
{
|
||||
const char *c = config_get_language();
|
||||
if (!c || strcmp(c, lang)) {
|
||||
if (c) htsmsg_delete_field(config, "language");
|
||||
htsmsg_add_str(config, "language", lang);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
return _config_set_str("language", lang);
|
||||
}
|
||||
|
||||
const char *config_get_muxconfpath ( void )
|
||||
|
@ -66,11 +85,5 @@ const char *config_get_muxconfpath ( void )
|
|||
|
||||
int config_set_muxconfpath ( const char *path )
|
||||
{
|
||||
const char *c = config_get_muxconfpath();
|
||||
if (!c || strcmp(c, path)) {
|
||||
if (c) htsmsg_delete_field(config, "muxconfpath");
|
||||
htsmsg_add_str(config, "muxconfpath", path);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
return _config_set_str("muxconfpath", path);
|
||||
}
|
||||
|
|
302
src/cwc.c
302
src/cwc.c
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* tvheadend, CWC interface
|
||||
* Copyright (C) 2007 Andreas Öman
|
||||
* Copyright (C) 2007 Andreas Öman
|
||||
*
|
||||
* 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
|
||||
|
@ -27,19 +27,24 @@
|
|||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <ctype.h>
|
||||
#include <openssl/des.h>
|
||||
|
||||
#include "tvheadend.h"
|
||||
#include "tcp.h"
|
||||
#include "psi.h"
|
||||
#include "tsdemux.h"
|
||||
#include "ffdecsa/FFdecsa.h"
|
||||
#include "cwc.h"
|
||||
#include "notify.h"
|
||||
#include "atomic.h"
|
||||
#include "dtable.h"
|
||||
#include "subscriptions.h"
|
||||
#include "service.h"
|
||||
|
||||
#include <openssl/des.h>
|
||||
#if ENABLE_DVBCSA
|
||||
#include <dvbcsa/dvbcsa.h>
|
||||
#else
|
||||
#include "ffdecsa/FFdecsa.h"
|
||||
#endif
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -144,7 +149,16 @@ typedef struct cwc_service {
|
|||
|
||||
LIST_ENTRY(cwc_service) cs_link;
|
||||
|
||||
int cs_okchannel;
|
||||
int cs_channel;
|
||||
|
||||
/**
|
||||
* ECM Status
|
||||
*/
|
||||
enum {
|
||||
ECM_INIT,
|
||||
ECM_VALID,
|
||||
ECM_RESET
|
||||
} ecm_state;
|
||||
|
||||
/**
|
||||
* Status of the key(s) in cs_keys
|
||||
|
@ -156,8 +170,12 @@ typedef struct cwc_service {
|
|||
CS_IDLE
|
||||
} cs_keystate;
|
||||
|
||||
#if ENABLE_DVBCSA
|
||||
struct dvbcsa_bs_key_s *cs_key_even;
|
||||
struct dvbcsa_bs_key_s *cs_key_odd;
|
||||
#else
|
||||
void *cs_keys;
|
||||
|
||||
#endif
|
||||
|
||||
uint8_t cs_cw[16];
|
||||
int cs_pending_cw_update;
|
||||
|
@ -168,6 +186,12 @@ typedef struct cwc_service {
|
|||
int cs_cluster_size;
|
||||
uint8_t *cs_tsbcluster;
|
||||
int cs_fill;
|
||||
#if ENABLE_DVBCSA
|
||||
struct dvbcsa_bs_batch_s *cs_tsbbatch_even;
|
||||
struct dvbcsa_bs_batch_s *cs_tsbbatch_odd;
|
||||
int cs_fill_even;
|
||||
int cs_fill_odd;
|
||||
#endif
|
||||
|
||||
LIST_HEAD(, ecm_pid) cs_pids;
|
||||
|
||||
|
@ -462,7 +486,7 @@ cwc_send_msg(cwc_t *cwc, const uint8_t *msg, size_t len, int sid, int enq)
|
|||
{
|
||||
cwc_message_t *cm = malloc(sizeof(cwc_message_t));
|
||||
uint8_t *buf = cm->cm_data;
|
||||
int seq, n;
|
||||
int seq;
|
||||
|
||||
if(len + 12 > CWS_NETMSGSIZE) {
|
||||
free(cm);
|
||||
|
@ -498,8 +522,7 @@ cwc_send_msg(cwc_t *cwc, const uint8_t *msg, size_t len, int sid, int enq)
|
|||
pthread_cond_signal(&cwc->cwc_writer_cond);
|
||||
pthread_mutex_unlock(&cwc->cwc_writer_mutex);
|
||||
} else {
|
||||
n = write(cwc->cwc_fd, buf, len);
|
||||
if(n != len)
|
||||
if (tvh_write(cwc->cwc_fd, buf, len))
|
||||
tvhlog(LOG_INFO, "cwc", "write error %s", strerror(errno));
|
||||
|
||||
free(cm);
|
||||
|
@ -754,28 +777,20 @@ handle_ecm_reply(cwc_service_t *ct, ecm_section_t *es, uint8_t *msg,
|
|||
int len, int seq)
|
||||
{
|
||||
service_t *t = ct->cs_service;
|
||||
ecm_pid_t *ep, *epn;
|
||||
cwc_service_t *ct2;
|
||||
cwc_t *cwc2;
|
||||
ecm_pid_t *ep;
|
||||
char chaninfo[32];
|
||||
int i;
|
||||
int64_t delay = (getmonoclock() - es->es_time) / 1000LL; // in ms
|
||||
|
||||
if(es->es_channel != -1) {
|
||||
snprintf(chaninfo, sizeof(chaninfo), " (channel %d)", es->es_channel);
|
||||
} else {
|
||||
chaninfo[0] = 0;
|
||||
}
|
||||
|
||||
es->es_pending = 0;
|
||||
|
||||
snprintf(chaninfo, sizeof(chaninfo), " (PID %d)", es->es_channel);
|
||||
|
||||
if(len < 19) {
|
||||
|
||||
/* ERROR */
|
||||
|
||||
if(ct->cs_okchannel == es->es_channel)
|
||||
ct->cs_okchannel = -1;
|
||||
|
||||
if (es->es_nok < 3)
|
||||
es->es_nok++;
|
||||
|
||||
|
@ -823,13 +838,23 @@ forbid:
|
|||
"Can not descramble service \"%s\", access denied (seqno: %d "
|
||||
"Req delay: %"PRId64" ms)",
|
||||
t->s_svcname, seq, delay);
|
||||
|
||||
ct->cs_keystate = CS_FORBIDDEN;
|
||||
ct->ecm_state = ECM_RESET;
|
||||
|
||||
return;
|
||||
|
||||
} else {
|
||||
|
||||
ct->cs_okchannel = es->es_channel;
|
||||
es->es_nok = 0;
|
||||
ct->cs_channel = es->es_channel;
|
||||
ct->ecm_state = ECM_VALID;
|
||||
|
||||
if(t->s_prefcapid == 0 || t->s_prefcapid != ct->cs_channel) {
|
||||
t->s_prefcapid = ct->cs_channel;
|
||||
tvhlog(LOG_DEBUG, "cwc", "Saving prefered PID %d", t->s_prefcapid);
|
||||
service_request_save(t, 0);
|
||||
}
|
||||
|
||||
tvhlog(LOG_DEBUG, "cwc",
|
||||
"Received ECM reply%s for service \"%s\" "
|
||||
|
@ -857,7 +882,7 @@ forbid:
|
|||
}
|
||||
|
||||
if(ct->cs_keystate != CS_RESOLVED)
|
||||
tvhlog(LOG_INFO, "cwc",
|
||||
tvhlog(LOG_DEBUG, "cwc",
|
||||
"Obtained key for service \"%s\" in %"PRId64" ms, from %s:%i",
|
||||
t->s_svcname, delay, ct->cs_cwc->cwc_hostname,
|
||||
ct->cs_cwc->cwc_port);
|
||||
|
@ -865,6 +890,22 @@ forbid:
|
|||
ct->cs_keystate = CS_RESOLVED;
|
||||
memcpy(ct->cs_cw, msg + 3, 16);
|
||||
ct->cs_pending_cw_update = 1;
|
||||
|
||||
ep = LIST_FIRST(&ct->cs_pids);
|
||||
while(ep != NULL) {
|
||||
if (ct->cs_channel == ep->ep_pid) {
|
||||
ep = LIST_NEXT(ep, ep_link);
|
||||
}
|
||||
else {
|
||||
epn = LIST_NEXT(ep, ep_link);
|
||||
for(i = 0; i < 256; i++)
|
||||
free(ep->ep_sections[i]);
|
||||
LIST_REMOVE(ep, ep_link);
|
||||
tvhlog(LOG_WARNING, "cwc", "Delete ECM (PID %d) for service \"%s\"", ep->ep_pid, t->s_svcname);
|
||||
free(ep);
|
||||
ep = epn;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -902,6 +943,9 @@ cwc_running_reply(cwc_t *cwc, uint8_t msgtype, uint8_t *msg, int len)
|
|||
}
|
||||
}
|
||||
tvhlog(LOG_WARNING, "cwc", "Got unexpected ECM reply (seqno: %d)", seq);
|
||||
LIST_FOREACH(ct, &cwc->cwc_services, cs_link) {
|
||||
ct->ecm_state = ECM_RESET;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
|
@ -995,7 +1039,8 @@ cwc_writer_thread(void *aux)
|
|||
TAILQ_REMOVE(&cwc->cwc_writeq, cm, cm_link);
|
||||
pthread_mutex_unlock(&cwc->cwc_writer_mutex);
|
||||
// int64_t ts = getmonoclock();
|
||||
r = write(cwc->cwc_fd, cm->cm_data, cm->cm_len);
|
||||
if (tvh_write(cwc->cwc_fd, cm->cm_data, cm->cm_len))
|
||||
tvhlog(LOG_INFO, "cwc", "write error %s", strerror(errno));
|
||||
// printf("Write took %lld usec\n", getmonoclock() - ts);
|
||||
free(cm);
|
||||
pthread_mutex_lock(&cwc->cwc_writer_mutex);
|
||||
|
@ -1592,11 +1637,40 @@ cwc_table_input(struct th_descrambler *td, struct service *t,
|
|||
}
|
||||
|
||||
if(ep == NULL) {
|
||||
ep = calloc(1, sizeof(ecm_pid_t));
|
||||
ep->ep_pid = st->es_pid;
|
||||
LIST_INSERT_HEAD(&ct->cs_pids, ep, ep_link);
|
||||
if (ct->ecm_state == ECM_RESET) {
|
||||
ct->ecm_state = ECM_INIT;
|
||||
ct->cs_channel = -1;
|
||||
t->s_prefcapid = 0;
|
||||
tvhlog(LOG_DEBUG, "cwc", "Reset after unexpected or no reply for service \"%s\"", t->s_svcname);
|
||||
}
|
||||
|
||||
if (ct->ecm_state == ECM_INIT) {
|
||||
// Validate prefered ECM PID
|
||||
if(t->s_prefcapid != 0) {
|
||||
struct elementary_stream *prefca = service_stream_find(t, t->s_prefcapid);
|
||||
if (!prefca || prefca->es_type != SCT_CA) {
|
||||
tvhlog(LOG_DEBUG, "cwc", "Invalid prefered ECM (PID %d) found for service \"%s\"", t->s_prefcapid, t->s_svcname);
|
||||
t->s_prefcapid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(t->s_prefcapid == st->es_pid) {
|
||||
ep = calloc(1, sizeof(ecm_pid_t));
|
||||
ep->ep_pid = t->s_prefcapid;
|
||||
LIST_INSERT_HEAD(&ct->cs_pids, ep, ep_link);
|
||||
tvhlog(LOG_DEBUG, "cwc", "Insert prefered ECM (PID %d) for service \"%s\"", t->s_prefcapid, t->s_svcname);
|
||||
}
|
||||
else if(t->s_prefcapid == 0) {
|
||||
ep = calloc(1, sizeof(ecm_pid_t));
|
||||
ep->ep_pid = st->es_pid;
|
||||
LIST_INSERT_HEAD(&ct->cs_pids, ep, ep_link);
|
||||
tvhlog(LOG_DEBUG, "cwc", "Insert new ECM (PID %d) for service \"%s\"", st->es_pid, t->s_svcname);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(ep == NULL)
|
||||
return;
|
||||
|
||||
LIST_FOREACH(c, &st->es_caids, link) {
|
||||
if(cwc->cwc_caid == c->caid)
|
||||
|
@ -1615,17 +1689,16 @@ cwc_table_input(struct th_descrambler *td, struct service *t,
|
|||
/* ECM */
|
||||
|
||||
if(cwc->cwc_caid >> 8 == 6) {
|
||||
channel = data[6] << 8 | data[7];
|
||||
snprintf(chaninfo, sizeof(chaninfo), " (channel %d)", channel);
|
||||
ep->ep_last_section = data[5];
|
||||
section = data[4];
|
||||
} else {
|
||||
channel = -1;
|
||||
chaninfo[0] = 0;
|
||||
ep->ep_last_section = 0;
|
||||
section = 0;
|
||||
}
|
||||
|
||||
channel = st->es_pid;
|
||||
snprintf(chaninfo, sizeof(chaninfo), " (PID %d)", channel);
|
||||
|
||||
if(ep->ep_sections[section] == NULL)
|
||||
ep->ep_sections[section] = calloc(1, sizeof(ecm_section_t));
|
||||
|
||||
|
@ -1650,18 +1723,17 @@ cwc_table_input(struct th_descrambler *td, struct service *t,
|
|||
memcpy(es->es_ecm, data, len);
|
||||
es->es_ecmsize = len;
|
||||
|
||||
if(ct->cs_okchannel != -1 && channel != -1 &&
|
||||
ct->cs_okchannel != channel) {
|
||||
tvhlog(LOG_DEBUG, "cwc", "Filtering ECM channel %d", channel);
|
||||
if(ct->cs_channel >= 0 && channel != -1 &&
|
||||
ct->cs_channel != channel) {
|
||||
tvhlog(LOG_DEBUG, "cwc", "Filtering ECM (PID %d)", channel);
|
||||
return;
|
||||
}
|
||||
|
||||
es->es_seq = cwc_send_msg(cwc, data, len, sid, 1);
|
||||
|
||||
tvhlog(LOG_DEBUG, "cwc",
|
||||
"Sending ECM%s section=%d/%d, for service %s (seqno: %d) PID %d",
|
||||
chaninfo, section, ep->ep_last_section, t->s_svcname, es->es_seq,
|
||||
st->es_pid);
|
||||
"Sending ECM%s section=%d/%d, for service \"%s\" (seqno: %d)",
|
||||
chaninfo, section, ep->ep_last_section, t->s_svcname, es->es_seq);
|
||||
es->es_time = getmonoclock();
|
||||
break;
|
||||
|
||||
|
@ -1761,7 +1833,6 @@ cwc_emm_cryptoworks(cwc_t *cwc, uint8_t *data, int len)
|
|||
if (cwc->cwc_cryptoworks_emm.shared_emm) {
|
||||
free(cwc->cwc_cryptoworks_emm.shared_emm);
|
||||
cwc->cwc_cryptoworks_emm.shared_len = 0;
|
||||
cwc->cwc_cryptoworks_emm.shared_emm = (uint8_t *)malloc(len);
|
||||
}
|
||||
cwc->cwc_cryptoworks_emm.shared_emm = malloc(len);
|
||||
if (cwc->cwc_cryptoworks_emm.shared_emm) {
|
||||
|
@ -1789,7 +1860,8 @@ cwc_emm_cryptoworks(cwc_t *cwc, uint8_t *data, int len)
|
|||
cwc_send_msg(cwc, composed, elen + 12, 0, 1);
|
||||
free(composed);
|
||||
free(tmp);
|
||||
}
|
||||
} else if (tmp)
|
||||
free(tmp);
|
||||
cwc->cwc_cryptoworks_emm.shared_emm = NULL;
|
||||
cwc->cwc_cryptoworks_emm.shared_len = 0;
|
||||
}
|
||||
|
@ -1812,19 +1884,15 @@ cwc_emm_bulcrypt(cwc_t *cwc, uint8_t *data, int len)
|
|||
int match = 0;
|
||||
|
||||
switch (data[0]) {
|
||||
case 0x82: /* unique */
|
||||
case 0x85: /* unique */
|
||||
case 0x82: /* unique - bulcrypt (1 card) */
|
||||
case 0x8a: /* unique - polaris (1 card) */
|
||||
case 0x85: /* unique - bulcrypt (4 cards) */
|
||||
case 0x8b: /* unique - polaris (4 cards) */
|
||||
match = len >= 10 && memcmp(data + 3, cwc->cwc_ua + 2, 3) == 0;
|
||||
break;
|
||||
case 0x84: /* shared */
|
||||
case 0x84: /* shared - (1024 cards) */
|
||||
match = len >= 10 && memcmp(data + 3, cwc->cwc_ua + 2, 2) == 0;
|
||||
break;
|
||||
case 0x8b: /* shared-unknown */
|
||||
match = len >= 10 && memcmp(data + 4, cwc->cwc_ua + 2, 2) == 0;
|
||||
break;
|
||||
case 0x8a: /* global */
|
||||
match = len >= 10 && memcmp(data + 4, cwc->cwc_ua + 2, 1) == 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (match)
|
||||
|
@ -1841,13 +1909,21 @@ update_keys(cwc_service_t *ct)
|
|||
ct->cs_pending_cw_update = 0;
|
||||
for(i = 0; i < 8; i++)
|
||||
if(ct->cs_cw[i]) {
|
||||
#if ENABLE_DVBCSA
|
||||
dvbcsa_bs_key_set(ct->cs_cw, ct->cs_key_even);
|
||||
#else
|
||||
set_even_control_word(ct->cs_keys, ct->cs_cw);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
for(i = 0; i < 8; i++)
|
||||
if(ct->cs_cw[8 + i]) {
|
||||
#if ENABLE_DVBCSA
|
||||
dvbcsa_bs_key_set(ct->cs_cw + 8, ct->cs_key_odd);
|
||||
#else
|
||||
set_odd_control_word(ct->cs_keys, ct->cs_cw + 8);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -1856,6 +1932,101 @@ update_keys(cwc_service_t *ct)
|
|||
/**
|
||||
*
|
||||
*/
|
||||
#if ENABLE_DVBCSA
|
||||
static int
|
||||
cwc_descramble(th_descrambler_t *td, service_t *t, struct elementary_stream *st,
|
||||
const uint8_t *tsb)
|
||||
{
|
||||
cwc_service_t *ct = (cwc_service_t *)td;
|
||||
uint8_t *pkt;
|
||||
int xc0;
|
||||
int ev_od;
|
||||
int len;
|
||||
int offset;
|
||||
int n;
|
||||
// FIXME: //int residue;
|
||||
|
||||
if(ct->cs_keystate == CS_FORBIDDEN)
|
||||
return 1;
|
||||
|
||||
if(ct->cs_keystate != CS_RESOLVED)
|
||||
return -1;
|
||||
|
||||
if(ct->cs_fill == 0 && ct->cs_pending_cw_update)
|
||||
update_keys(ct);
|
||||
|
||||
pkt = ct->cs_tsbcluster + ct->cs_fill * 188;
|
||||
memcpy(pkt, tsb, 188);
|
||||
ct->cs_fill++;
|
||||
|
||||
do { // handle this packet
|
||||
xc0 = pkt[3] & 0xc0;
|
||||
if(xc0 == 0x00) { // clear
|
||||
break;
|
||||
}
|
||||
if(xc0 == 0x40) { // reserved
|
||||
break;
|
||||
}
|
||||
if(xc0 == 0x80 || xc0 == 0xc0) { // encrypted
|
||||
ev_od = (xc0 & 0x40) >> 6; // 0 even, 1 odd
|
||||
pkt[3] &= 0x3f; // consider it decrypted now
|
||||
if(pkt[3] & 0x20) { // incomplete packet
|
||||
offset = 4 + pkt[4] + 1;
|
||||
len = 188 - offset;
|
||||
n = len >> 3;
|
||||
// FIXME: //residue = len - (n << 3);
|
||||
if(n == 0) { // decrypted==encrypted!
|
||||
break; // this doesn't need more processing
|
||||
}
|
||||
} else {
|
||||
len = 184;
|
||||
offset = 4;
|
||||
// FIXME: //n = 23;
|
||||
// FIXME: //residue = 0;
|
||||
}
|
||||
if(ev_od == 0) {
|
||||
ct->cs_tsbbatch_even[ct->cs_fill_even].data = pkt + offset;
|
||||
ct->cs_tsbbatch_even[ct->cs_fill_even].len = len;
|
||||
ct->cs_fill_even++;
|
||||
} else {
|
||||
ct->cs_tsbbatch_odd[ct->cs_fill_odd].data = pkt + offset;
|
||||
ct->cs_tsbbatch_odd[ct->cs_fill_odd].len = len;
|
||||
ct->cs_fill_odd++;
|
||||
}
|
||||
}
|
||||
} while(0);
|
||||
|
||||
if(ct->cs_fill != ct->cs_cluster_size)
|
||||
return 0;
|
||||
|
||||
if(ct->cs_fill_even) {
|
||||
ct->cs_tsbbatch_even[ct->cs_fill_even].data = NULL;
|
||||
dvbcsa_bs_decrypt(ct->cs_key_even, ct->cs_tsbbatch_even, 184);
|
||||
ct->cs_fill_even = 0;
|
||||
}
|
||||
if(ct->cs_fill_odd) {
|
||||
ct->cs_tsbbatch_odd[ct->cs_fill_odd].data = NULL;
|
||||
dvbcsa_bs_decrypt(ct->cs_key_odd, ct->cs_tsbbatch_odd, 184);
|
||||
ct->cs_fill_odd = 0;
|
||||
}
|
||||
|
||||
{
|
||||
int i;
|
||||
const uint8_t *t0 = ct->cs_tsbcluster;
|
||||
|
||||
for(i = 0; i < ct->cs_fill; i++) {
|
||||
ts_recv_packet2(t, t0);
|
||||
t0 += 188;
|
||||
}
|
||||
}
|
||||
ct->cs_fill = 0;
|
||||
|
||||
if(ct->cs_pending_cw_update)
|
||||
update_keys(ct);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static int
|
||||
cwc_descramble(th_descrambler_t *td, service_t *t, struct elementary_stream *st,
|
||||
const uint8_t *tsb)
|
||||
|
@ -1914,6 +2085,7 @@ cwc_descramble(th_descrambler_t *td, service_t *t, struct elementary_stream *st,
|
|||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* cwc_mutex is held
|
||||
|
@ -1937,7 +2109,14 @@ cwc_service_destroy(th_descrambler_t *td)
|
|||
|
||||
LIST_REMOVE(ct, cs_link);
|
||||
|
||||
#if ENABLE_DVBCSA
|
||||
dvbcsa_bs_key_free(ct->cs_key_odd);
|
||||
dvbcsa_bs_key_free(ct->cs_key_even);
|
||||
free(ct->cs_tsbbatch_odd);
|
||||
free(ct->cs_tsbbatch_even);
|
||||
#else
|
||||
free_key_struct(ct->cs_keys);
|
||||
#endif
|
||||
free(ct->cs_tsbcluster);
|
||||
free(ct);
|
||||
}
|
||||
|
@ -1981,14 +2160,27 @@ cwc_service_start(service_t *t)
|
|||
if(cwc_find_stream_by_caid(t, cwc->cwc_caid) == NULL)
|
||||
continue;
|
||||
|
||||
ct = calloc(1, sizeof(cwc_service_t));
|
||||
ct->cs_cluster_size = get_suggested_cluster_size();
|
||||
ct->cs_tsbcluster = malloc(ct->cs_cluster_size * 188);
|
||||
|
||||
ct->cs_keys = get_key_struct();
|
||||
ct->cs_cwc = cwc;
|
||||
ct->cs_service = t;
|
||||
ct->cs_okchannel = -1;
|
||||
ct = calloc(1, sizeof(cwc_service_t));
|
||||
#if ENABLE_DVBCSA
|
||||
ct->cs_cluster_size = dvbcsa_bs_batch_size();
|
||||
#else
|
||||
ct->cs_cluster_size = get_suggested_cluster_size();
|
||||
#endif
|
||||
ct->cs_tsbcluster = malloc(ct->cs_cluster_size * 188);
|
||||
#if ENABLE_DVBCSA
|
||||
ct->cs_tsbbatch_even = malloc((ct->cs_cluster_size + 1) *
|
||||
sizeof(struct dvbcsa_bs_batch_s));
|
||||
ct->cs_tsbbatch_odd = malloc((ct->cs_cluster_size + 1) *
|
||||
sizeof(struct dvbcsa_bs_batch_s));
|
||||
ct->cs_key_even = dvbcsa_bs_key_alloc();
|
||||
ct->cs_key_odd = dvbcsa_bs_key_alloc();
|
||||
#else
|
||||
ct->cs_keys = get_key_struct();
|
||||
#endif
|
||||
ct->cs_cwc = cwc;
|
||||
ct->cs_service = t;
|
||||
ct->cs_channel = -1;
|
||||
ct->ecm_state = ECM_INIT;
|
||||
|
||||
td = &ct->cs_head;
|
||||
td->td_stop = cwc_service_destroy;
|
||||
|
@ -2012,11 +2204,9 @@ cwc_service_start(service_t *t)
|
|||
static void
|
||||
cwc_destroy(cwc_t *cwc)
|
||||
{
|
||||
pthread_mutex_lock(&cwc_mutex);
|
||||
TAILQ_REMOVE(&cwcs, cwc, cwc_link);
|
||||
cwc->cwc_running = 0;
|
||||
pthread_cond_signal(&cwc->cwc_cond);
|
||||
pthread_mutex_unlock(&cwc_mutex);
|
||||
}
|
||||
|
||||
|
||||
|
|
136
src/dvb/diseqc.c
Normal file → Executable file
136
src/dvb/diseqc.c
Normal file → Executable file
|
@ -5,34 +5,6 @@
|
|||
#include "tvheadend.h"
|
||||
#include "diseqc.h"
|
||||
|
||||
//#define DISEQC_TRACE
|
||||
|
||||
struct dvb_diseqc_master_cmd diseqc_commited_cmds[] = {
|
||||
{ { 0xe0, 0x10, 0x38, 0xf0, 0x00, 0x00 }, 4 },
|
||||
{ { 0xe0, 0x10, 0x38, 0xf1, 0x00, 0x00 }, 4 },
|
||||
{ { 0xe0, 0x10, 0x38, 0xf2, 0x00, 0x00 }, 4 },
|
||||
{ { 0xe0, 0x10, 0x38, 0xf3, 0x00, 0x00 }, 4 },
|
||||
{ { 0xe0, 0x10, 0x38, 0xf4, 0x00, 0x00 }, 4 },
|
||||
{ { 0xe0, 0x10, 0x38, 0xf5, 0x00, 0x00 }, 4 },
|
||||
{ { 0xe0, 0x10, 0x38, 0xf6, 0x00, 0x00 }, 4 },
|
||||
{ { 0xe0, 0x10, 0x38, 0xf7, 0x00, 0x00 }, 4 },
|
||||
{ { 0xe0, 0x10, 0x38, 0xf8, 0x00, 0x00 }, 4 },
|
||||
{ { 0xe0, 0x10, 0x38, 0xf9, 0x00, 0x00 }, 4 },
|
||||
{ { 0xe0, 0x10, 0x38, 0xfa, 0x00, 0x00 }, 4 },
|
||||
{ { 0xe0, 0x10, 0x38, 0xfb, 0x00, 0x00 }, 4 },
|
||||
{ { 0xe0, 0x10, 0x38, 0xfc, 0x00, 0x00 }, 4 },
|
||||
{ { 0xe0, 0x10, 0x38, 0xfd, 0x00, 0x00 }, 4 },
|
||||
{ { 0xe0, 0x10, 0x38, 0xfe, 0x00, 0x00 }, 4 },
|
||||
{ { 0xe0, 0x10, 0x38, 0xff, 0x00, 0x00 }, 4 }
|
||||
};
|
||||
|
||||
struct dvb_diseqc_master_cmd diseqc_uncommited_cmds[] = {
|
||||
{ { 0xe0, 0x10, 0x39, 0xf0, 0x00, 0x00 }, 4 },
|
||||
{ { 0xe0, 0x10, 0x39, 0xf1, 0x00, 0x00 }, 4 },
|
||||
{ { 0xe0, 0x10, 0x39, 0xf2, 0x00, 0x00 }, 4 },
|
||||
{ { 0xe0, 0x10, 0x39, 0xf3, 0x00, 0x00 }, 4 }
|
||||
};
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
static inline void
|
||||
|
@ -45,60 +17,106 @@ msleep(uint32_t msec)
|
|||
}
|
||||
|
||||
int
|
||||
diseqc_setup(int fe_fd, int input, int voltage, int band, int diseqc_ver)
|
||||
diseqc_send_msg(int fe_fd, __u8 framing_byte, __u8 address, __u8 cmd,
|
||||
__u8 data_1, __u8 data_2, __u8 data_3, __u8 msg_len)
|
||||
{
|
||||
int i = (input % 4) * 4 + voltage * 2 + (band ? 1 : 0);
|
||||
int j = input / 4;
|
||||
int err;
|
||||
struct dvb_diseqc_master_cmd message;
|
||||
|
||||
#ifdef DISEQC_TRACE
|
||||
tvhlog(LOG_INFO, "diseqc",
|
||||
"fe_fd %i, input %i, voltage %i, band %i, diseqc_ver %i, i %i, j %i",
|
||||
fe_fd, input, voltage, band, diseqc_ver, i, j);
|
||||
#endif
|
||||
/* check for invalid input number or diseqc command indexes */
|
||||
if(input < 0 || input >=16 || i < 0 || i >= 16 || j < 0 || j >= 4)
|
||||
tvhtrace("diseqc", "sending %X %X %X %X %X %X",
|
||||
framing_byte, address, cmd, data_1, data_2, data_3);
|
||||
|
||||
message.msg[0] = framing_byte;
|
||||
message.msg[1] = address;
|
||||
message.msg[2] = cmd;
|
||||
message.msg[3] = data_1;
|
||||
message.msg[4] = data_2;
|
||||
message.msg[5] = data_3;
|
||||
message.msg_len = msg_len;
|
||||
if ((err = ioctl(fe_fd, FE_DISEQC_SEND_MASTER_CMD, &message))) {
|
||||
tvhlog(LOG_ERR, "diseqc", "error sending diseqc command");
|
||||
return err;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
diseqc_setup(int fe_fd, int lnb_num, int voltage, int band,
|
||||
uint32_t version, uint32_t repeats)
|
||||
{
|
||||
int i = (lnb_num % 4) * 4 + voltage * 2 + (band ? 1 : 0);
|
||||
int j = lnb_num / 4;
|
||||
int k, err;
|
||||
|
||||
tvhtrace("diseqc",
|
||||
"fe_fd=%i, lnb_num=%i, voltage=%i, band=%i, version=%i, repeats=%i",
|
||||
fe_fd, lnb_num, voltage, band, version, repeats);
|
||||
|
||||
/* verify lnb number and diseqc data */
|
||||
if(lnb_num < 0 || lnb_num >=64 || i < 0 || i >= 16 || j < 0 || j >= 16)
|
||||
return -1;
|
||||
|
||||
/* turn off continuous tone */
|
||||
if ((err = ioctl(fe_fd, FE_SET_TONE, SEC_TONE_OFF)))
|
||||
tvhtrace("diseqc", "disabling continuous tone");
|
||||
if ((err = ioctl(fe_fd, FE_SET_TONE, SEC_TONE_OFF))) {
|
||||
tvhlog(LOG_ERR, "diseqc", "error trying to turn off continuous tone");
|
||||
return err;
|
||||
}
|
||||
|
||||
/* set lnb voltage */
|
||||
if ((err = ioctl(fe_fd, FE_SET_VOLTAGE,
|
||||
(i/2) % 2 ? SEC_VOLTAGE_18 : SEC_VOLTAGE_13)))
|
||||
tvhtrace("diseqc", "setting lnb voltage to %iV", (i/2) % 2 ? 18 : 13);
|
||||
if ((err = ioctl(fe_fd, FE_SET_VOLTAGE, (i/2) % 2 ? SEC_VOLTAGE_18 : SEC_VOLTAGE_13))) {
|
||||
tvhlog(LOG_ERR, "diseqc", "error setting lnb voltage");
|
||||
return err;
|
||||
}
|
||||
msleep(15);
|
||||
|
||||
/* send uncommited command */
|
||||
if ((err = ioctl(fe_fd, FE_DISEQC_SEND_MASTER_CMD,
|
||||
&diseqc_uncommited_cmds[j])))
|
||||
return err;
|
||||
if (repeats == 0) { /* uncommited msg, wait 15ms, commited msg */
|
||||
if ((err = diseqc_send_msg(fe_fd, 0xE0, 0x10, 0x39, 0xF0 | j, 0, 0, 4)))
|
||||
return err;
|
||||
msleep(15);
|
||||
if ((err = diseqc_send_msg(fe_fd, 0xE0, 0x10, 0x38, 0xF0 | i, 0, 0, 4)))
|
||||
return err;
|
||||
} else { /* commited msg, 25ms, uncommited msg, 25ms, commited msg, etc */
|
||||
if ((err = diseqc_send_msg(fe_fd, 0xE0, 0x10, 0x38, 0xF0 | i, 0, 0, 4)))
|
||||
return err;
|
||||
for (k = 0; k < repeats; k++) {
|
||||
msleep(25);
|
||||
if ((err = diseqc_send_msg(fe_fd, 0xE0, 0x10, 0x39, 0xF0 | j, 0, 0, 4)))
|
||||
return err;
|
||||
msleep(25);
|
||||
if ((err = diseqc_send_msg(fe_fd, 0xE1, 0x10, 0x38, 0xF0 | i, 0, 0, 4)))
|
||||
return err;
|
||||
}
|
||||
}
|
||||
msleep(15);
|
||||
|
||||
/* send commited command */
|
||||
if ((err = ioctl(fe_fd, FE_DISEQC_SEND_MASTER_CMD,
|
||||
&diseqc_commited_cmds[i])))
|
||||
return err;
|
||||
#ifdef DISEQC_TRACE
|
||||
tvhlog(LOG_INFO, "diseqc", "E0 10 39 F%X - E0 10 38 F%X sent", j, i);
|
||||
#endif
|
||||
msleep(15);
|
||||
|
||||
/* send toneburst command */
|
||||
if ((err = ioctl(fe_fd, FE_DISEQC_SEND_BURST,
|
||||
(i/4) % 2 ? SEC_MINI_B : SEC_MINI_A)))
|
||||
/* set toneburst */
|
||||
tvhtrace("diseqc", (i/4) % 2 ? "sending mini diseqc B" : "sending mini diseqc A");
|
||||
if ((err = ioctl(fe_fd, FE_DISEQC_SEND_BURST, (i/4) % 2 ? SEC_MINI_B : SEC_MINI_A))) {
|
||||
tvhlog(LOG_ERR, "diseqc", "error sending mini diseqc command");
|
||||
return err;
|
||||
}
|
||||
msleep(15);
|
||||
|
||||
/* set continuous tone */
|
||||
if ((ioctl(fe_fd, FE_SET_TONE, i % 2 ? SEC_TONE_ON : SEC_TONE_OFF)))
|
||||
tvhtrace("diseqc", i % 2 ? "enabling continous tone" : "disabling continuous tone");
|
||||
if ((err = ioctl(fe_fd, FE_SET_TONE, i % 2 ? SEC_TONE_ON : SEC_TONE_OFF))) {
|
||||
tvhlog(LOG_ERR, "diseqc", "error setting continuous tone");
|
||||
return err;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
diseqc_voltage_off(int fe_fd)
|
||||
{
|
||||
return ioctl(fe_fd, FE_SET_VOLTAGE, SEC_VOLTAGE_OFF);
|
||||
int err;
|
||||
|
||||
tvhtrace("diseqc", "sending diseqc voltage off command");
|
||||
if ((err = ioctl(fe_fd, FE_SET_VOLTAGE, SEC_VOLTAGE_OFF))) {
|
||||
tvhlog(LOG_ERR, "diseqc", "error sending diseqc voltage off command");
|
||||
return err;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,10 @@
|
|||
/**
|
||||
* set up the switch to position/voltage/tone
|
||||
*/
|
||||
int diseqc_setup(int fe_fd, int input, int voltage, int band, int diseqc_ver);
|
||||
int diseqc_send_msg(int fe_fd, __u8 framing_byte, __u8 address, __u8 cmd,
|
||||
__u8 data_1, __u8 data_2, __u8 data_3, __u8 msg_len);
|
||||
int diseqc_setup(int fe_fd, int lnb_num, int voltage, int band,
|
||||
uint32_t version, uint32_t repeats);
|
||||
int diseqc_voltage_off(int fe_fd);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -23,8 +23,8 @@
|
|||
#include "dvb_charset.h"
|
||||
|
||||
void
|
||||
dvb_init(uint32_t adapter_mask)
|
||||
dvb_init(uint32_t adapter_mask, const char *rawfile)
|
||||
{
|
||||
dvb_charset_init();
|
||||
dvb_adapter_init(adapter_mask);
|
||||
dvb_adapter_init(adapter_mask, rawfile);
|
||||
}
|
||||
|
|
170
src/dvb/dvb.h
170
src/dvb/dvb.h
|
@ -23,6 +23,17 @@
|
|||
#include <linux/dvb/frontend.h>
|
||||
#include <pthread.h>
|
||||
#include "htsmsg.h"
|
||||
#include "psi.h"
|
||||
|
||||
struct service;
|
||||
struct th_dvb_table;
|
||||
struct th_dvb_mux_instance;
|
||||
|
||||
#define TDA_OPT_FE 0x1
|
||||
#define TDA_OPT_DVR 0x2
|
||||
#define TDA_OPT_DMX 0x4
|
||||
#define TDA_OPT_PWR 0x8
|
||||
#define TDA_OPT_ALL (TDA_OPT_FE | TDA_OPT_DVR | TDA_OPT_DMX | TDA_OPT_PWR)
|
||||
|
||||
#define DVB_VER_INT(maj,min) (((maj) << 16) + (min))
|
||||
|
||||
|
@ -42,7 +53,7 @@ TAILQ_HEAD(dvb_satconf_queue, dvb_satconf);
|
|||
typedef struct dvb_satconf {
|
||||
char *sc_id;
|
||||
TAILQ_ENTRY(dvb_satconf) sc_adapter_link;
|
||||
int sc_port; // diseqc switchport (0 - 15)
|
||||
int sc_port; // diseqc switchport (0 - 63)
|
||||
|
||||
char *sc_name;
|
||||
char *sc_comment;
|
||||
|
@ -92,8 +103,10 @@ typedef struct th_dvb_mux_instance {
|
|||
|
||||
struct th_dvb_adapter *tdmi_adapter;
|
||||
|
||||
uint16_t tdmi_snr, tdmi_signal;
|
||||
uint32_t tdmi_ber, tdmi_uncorrected_blocks;
|
||||
uint16_t tdmi_signal;
|
||||
uint32_t tdmi_ber, tdmi_unc;
|
||||
float tdmi_unc_avg;
|
||||
float tdmi_snr;
|
||||
|
||||
#define TDMI_FEC_ERR_HISTOGRAM_SIZE 10
|
||||
uint32_t tdmi_fec_err_histogram[TDMI_FEC_ERR_HISTOGRAM_SIZE];
|
||||
|
@ -103,6 +116,8 @@ typedef struct th_dvb_mux_instance {
|
|||
|
||||
|
||||
LIST_HEAD(, th_dvb_table) tdmi_tables;
|
||||
int tdmi_num_tables;
|
||||
|
||||
TAILQ_HEAD(, th_dvb_table) tdmi_table_queue;
|
||||
int tdmi_table_initial;
|
||||
|
||||
|
@ -143,9 +158,27 @@ typedef struct th_dvb_mux_instance {
|
|||
|
||||
TAILQ_HEAD(, epggrab_ota_mux) tdmi_epg_grab;
|
||||
|
||||
struct th_subscription_list tdmi_subscriptions;
|
||||
|
||||
} th_dvb_mux_instance_t;
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* When in raw mode we need to enqueue raw TS packet
|
||||
* to a different thread because we need to hold
|
||||
* global_lock when doing delivery of the tables
|
||||
*/
|
||||
TAILQ_HEAD(dvb_table_feed_queue, dvb_table_feed);
|
||||
|
||||
typedef struct dvb_table_feed {
|
||||
TAILQ_ENTRY(dvb_table_feed) dtf_link;
|
||||
uint8_t dtf_tsb[188];
|
||||
} dvb_table_feed_t;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* DVB Adapter (one of these per physical adapter)
|
||||
*/
|
||||
|
@ -173,6 +206,11 @@ typedef struct th_dvb_adapter {
|
|||
|
||||
int tda_table_epollfd;
|
||||
|
||||
uint32_t tda_enabled;
|
||||
|
||||
int tda_locked;
|
||||
time_t tda_monitor;
|
||||
|
||||
const char *tda_rootpath;
|
||||
char *tda_identifier;
|
||||
uint32_t tda_autodiscovery;
|
||||
|
@ -185,21 +223,25 @@ typedef struct th_dvb_adapter {
|
|||
uint32_t tda_sidtochan;
|
||||
uint32_t tda_nitoid;
|
||||
uint32_t tda_diseqc_version;
|
||||
uint32_t tda_diseqc_repeats;
|
||||
uint32_t tda_disable_pmt_monitor;
|
||||
int32_t tda_full_mux_rx;
|
||||
uint32_t tda_grace_period;
|
||||
char *tda_displayname;
|
||||
|
||||
char *tda_fe_path;
|
||||
int tda_fe_fd;
|
||||
int tda_type;
|
||||
int tda_snr_valid;
|
||||
struct dvb_frontend_info *tda_fe_info;
|
||||
|
||||
int tda_adapter_num;
|
||||
|
||||
char *tda_demux_path;
|
||||
|
||||
char *tda_dvr_path;
|
||||
char *tda_dvr_path;
|
||||
pthread_t tda_dvr_thread;
|
||||
int tda_dvr_pipe[2];
|
||||
th_pipe_t tda_dvr_pipe;
|
||||
|
||||
int tda_hostconnection;
|
||||
|
||||
|
@ -209,7 +251,6 @@ typedef struct th_dvb_adapter {
|
|||
struct service_list tda_transports; /* Currently bound transports */
|
||||
|
||||
gtimer_t tda_fe_monitor_timer;
|
||||
int tda_fe_monitor_hold;
|
||||
|
||||
int tda_sat; // Set if this adapter is a satellite receiver (DVB-S, etc)
|
||||
|
||||
|
@ -218,11 +259,6 @@ typedef struct th_dvb_adapter {
|
|||
|
||||
struct th_dvb_mux_instance_list tda_mux_list;
|
||||
|
||||
uint32_t tda_dump_muxes;
|
||||
|
||||
int tda_allpids_dmx_fd;
|
||||
int tda_dump_fd;
|
||||
|
||||
uint32_t tda_last_fec;
|
||||
|
||||
int tda_unc_is_delta; /* 1 if we believe FE_READ_UNCORRECTED_BLOCKS
|
||||
|
@ -230,6 +266,27 @@ typedef struct th_dvb_adapter {
|
|||
|
||||
uint32_t tda_extrapriority; // extra priority for choosing the best adapter/service
|
||||
|
||||
void (*tda_open_service)(struct th_dvb_adapter *tda, struct service *s);
|
||||
void (*tda_close_service)(struct th_dvb_adapter *tda, struct service *s);
|
||||
void (*tda_open_table)(struct th_dvb_mux_instance *tdmi, struct th_dvb_table *s);
|
||||
void (*tda_close_table)(struct th_dvb_mux_instance *tdmi, struct th_dvb_table *s);
|
||||
|
||||
int tda_rawmode;
|
||||
|
||||
int tda_bytes;
|
||||
|
||||
// Full mux streaming, protected via the delivery mutex
|
||||
|
||||
streaming_pad_t tda_streaming_pad;
|
||||
|
||||
|
||||
struct dvb_table_feed_queue tda_table_feed;
|
||||
pthread_cond_t tda_table_feed_cond; // Bound to tda_delivery_mutex
|
||||
|
||||
// PIDs that needs to be requeued and processed as tables
|
||||
uint8_t tda_table_filter[8192];
|
||||
|
||||
|
||||
} th_dvb_adapter_t;
|
||||
|
||||
/**
|
||||
|
@ -256,6 +313,7 @@ typedef struct th_dvb_table {
|
|||
int tdt_fd;
|
||||
|
||||
LIST_ENTRY(th_dvb_table) tdt_link;
|
||||
th_dvb_mux_instance_t *tdt_tdmi;
|
||||
|
||||
char *tdt_name;
|
||||
|
||||
|
@ -267,31 +325,39 @@ typedef struct th_dvb_table {
|
|||
int tdt_count;
|
||||
int tdt_pid;
|
||||
|
||||
struct dmx_sct_filter_params *tdt_fparams;
|
||||
|
||||
int tdt_id;
|
||||
|
||||
int tdt_table;
|
||||
int tdt_mask;
|
||||
|
||||
int tdt_destroyed;
|
||||
int tdt_refcount;
|
||||
|
||||
psi_section_t tdt_sect; // Manual reassembly
|
||||
|
||||
} th_dvb_table_t;
|
||||
|
||||
|
||||
extern struct th_dvb_adapter_queue dvb_adapters;
|
||||
extern struct th_dvb_mux_instance_tree dvb_muxes;
|
||||
|
||||
void dvb_init(uint32_t adapter_mask);
|
||||
void dvb_init(uint32_t adapter_mask, const char *rawfile);
|
||||
|
||||
/**
|
||||
* DVB Adapter
|
||||
*/
|
||||
void dvb_adapter_init(uint32_t adapter_mask);
|
||||
void dvb_adapter_init(uint32_t adapter_mask, const char *rawfile);
|
||||
|
||||
void dvb_adapter_mux_scanner(void *aux);
|
||||
|
||||
void dvb_adapter_start (th_dvb_adapter_t *tda);
|
||||
void dvb_adapter_start (th_dvb_adapter_t *tda, int opt);
|
||||
|
||||
void dvb_adapter_stop (th_dvb_adapter_t *tda);
|
||||
void dvb_adapter_stop (th_dvb_adapter_t *tda, int opt);
|
||||
|
||||
void dvb_adapter_set_displayname(th_dvb_adapter_t *tda, const char *s);
|
||||
|
||||
void dvb_adapter_set_enabled(th_dvb_adapter_t *tda, int on);
|
||||
|
||||
void dvb_adapter_set_auto_discovery(th_dvb_adapter_t *tda, int on);
|
||||
|
||||
void dvb_adapter_set_skip_initialscan(th_dvb_adapter_t *tda, int on);
|
||||
|
@ -302,8 +368,6 @@ void dvb_adapter_set_skip_checksubscr(th_dvb_adapter_t *tda, int on);
|
|||
|
||||
void dvb_adapter_set_qmon(th_dvb_adapter_t *tda, int on);
|
||||
|
||||
void dvb_adapter_set_dump_muxes(th_dvb_adapter_t *tda, int on);
|
||||
|
||||
void dvb_adapter_set_idleclose(th_dvb_adapter_t *tda, int on);
|
||||
|
||||
void dvb_adapter_set_poweroff(th_dvb_adapter_t *tda, int on);
|
||||
|
@ -314,8 +378,15 @@ void dvb_adapter_set_nitoid(th_dvb_adapter_t *tda, int nitoid);
|
|||
|
||||
void dvb_adapter_set_diseqc_version(th_dvb_adapter_t *tda, unsigned int v);
|
||||
|
||||
void dvb_adapter_set_diseqc_repeats(th_dvb_adapter_t *tda,
|
||||
unsigned int repeats);
|
||||
|
||||
void dvb_adapter_set_disable_pmt_monitor(th_dvb_adapter_t *tda, int on);
|
||||
|
||||
void dvb_adapter_set_full_mux_rx(th_dvb_adapter_t *tda, int r);
|
||||
|
||||
void dvb_adapter_set_grace_period(th_dvb_adapter_t *tda, uint32_t p);
|
||||
|
||||
void dvb_adapter_clone(th_dvb_adapter_t *dst, th_dvb_adapter_t *src);
|
||||
|
||||
void dvb_adapter_clean(th_dvb_adapter_t *tda);
|
||||
|
@ -332,6 +403,12 @@ void dvb_adapter_set_extrapriority(th_dvb_adapter_t *tda, int extrapriority);
|
|||
|
||||
void dvb_adapter_poweroff(th_dvb_adapter_t *tda);
|
||||
|
||||
void dvb_input_filtered_setup(th_dvb_adapter_t *tda);
|
||||
|
||||
void dvb_input_raw_setup(th_dvb_adapter_t *tda);
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* DVB Multiplex
|
||||
*/
|
||||
|
@ -358,11 +435,13 @@ th_dvb_mux_instance_t *dvb_mux_create(th_dvb_adapter_t *tda,
|
|||
uint16_t onid, uint16_t tsid, const char *network,
|
||||
const char *logprefix, int enabled,
|
||||
int initialscan, const char *identifier,
|
||||
dvb_satconf_t *satconf);
|
||||
dvb_satconf_t *satconf, int create, th_dvb_mux_instance_t *src);
|
||||
|
||||
void dvb_mux_set_networkname(th_dvb_mux_instance_t *tdmi, const char *name);
|
||||
|
||||
void dvb_mux_set_tsid(th_dvb_mux_instance_t *tdmi, uint16_t tsid);
|
||||
void dvb_mux_set_tsid(th_dvb_mux_instance_t *tdmi, uint16_t tsid, int force);
|
||||
|
||||
void dvb_mux_set_onid(th_dvb_mux_instance_t *tdmi, uint16_t onid, int force);
|
||||
|
||||
void dvb_mux_set_enable(th_dvb_mux_instance_t *tdmi, int enabled);
|
||||
|
||||
|
@ -393,24 +472,33 @@ int dvb_mux_copy(th_dvb_adapter_t *dst, th_dvb_mux_instance_t *tdmi_src,
|
|||
|
||||
void dvb_mux_add_to_scan_queue (th_dvb_mux_instance_t *tdmi);
|
||||
|
||||
th_dvb_mux_instance_t *dvb_mux_find
|
||||
(th_dvb_adapter_t *tda, const char *netname, uint16_t onid, uint16_t tsid,
|
||||
int enabled );
|
||||
|
||||
/**
|
||||
* DVB Transport (aka DVB service)
|
||||
*/
|
||||
void dvb_transport_load(th_dvb_mux_instance_t *tdmi, const char *tdmi_identifier);
|
||||
void dvb_service_load(th_dvb_mux_instance_t *tdmi, const char *tdmi_identifier);
|
||||
|
||||
struct service *dvb_transport_find(th_dvb_mux_instance_t *tdmi,
|
||||
struct service *dvb_service_find(th_dvb_mux_instance_t *tdmi,
|
||||
uint16_t sid, int pmt_pid,
|
||||
const char *identifier);
|
||||
|
||||
struct service *dvb_transport_find2(th_dvb_mux_instance_t *tdmi,
|
||||
struct service *dvb_service_find2(th_dvb_mux_instance_t *tdmi,
|
||||
uint16_t sid, int pmt_pid,
|
||||
const char *identifier, int *save);
|
||||
|
||||
void dvb_transport_notify(struct service *t);
|
||||
struct service *dvb_service_find3
|
||||
(th_dvb_adapter_t *tda, th_dvb_mux_instance_t *tdmi,
|
||||
const char *netname, uint16_t onid, uint16_t tsid, uint16_t sid,
|
||||
int enabled, int epgprimary);
|
||||
|
||||
void dvb_transport_notify_by_adapter(th_dvb_adapter_t *tda);
|
||||
void dvb_service_notify(struct service *t);
|
||||
|
||||
htsmsg_t *dvb_transport_build_msg(struct service *t);
|
||||
void dvb_service_notify_by_adapter(th_dvb_adapter_t *tda);
|
||||
|
||||
htsmsg_t *dvb_service_build_msg(struct service *t);
|
||||
|
||||
/**
|
||||
* DVB Frontend
|
||||
|
@ -433,13 +521,10 @@ void dvb_table_add_pmt(th_dvb_mux_instance_t *tdmi, int pmt_pid);
|
|||
|
||||
void dvb_table_rem_pmt(th_dvb_mux_instance_t *tdmi, int pmt_pid);
|
||||
|
||||
struct dmx_sct_filter_params *dvb_fparams_alloc(void);
|
||||
|
||||
void
|
||||
tdt_add(th_dvb_mux_instance_t *tdmi, struct dmx_sct_filter_params *fparams,
|
||||
int (*callback)(th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len,
|
||||
uint8_t tableid, void *opaque), void *opaque,
|
||||
const char *name, int flags, int pid, th_dvb_table_t *tdt);
|
||||
void tdt_add(th_dvb_mux_instance_t *tdmi, int table, int mask,
|
||||
int (*callback)(th_dvb_mux_instance_t *tdmi, uint8_t *buf, int len,
|
||||
uint8_t tableid, void *opaque), void *opaque,
|
||||
const char *name, int flags, int pid);
|
||||
|
||||
int dvb_pidx11_callback
|
||||
(th_dvb_mux_instance_t *tdmi, uint8_t *ptr, int len,
|
||||
|
@ -450,6 +535,10 @@ int dvb_pidx11_callback
|
|||
#define TDT_CA 0x4
|
||||
#define TDT_TDT 0x8
|
||||
|
||||
void dvb_table_dispatch(uint8_t *sec, int r, th_dvb_table_t *tdt);
|
||||
|
||||
void dvb_table_release(th_dvb_table_t *tdt);
|
||||
|
||||
/**
|
||||
* Satellite configuration
|
||||
*/
|
||||
|
@ -465,4 +554,17 @@ dvb_satconf_t *dvb_satconf_entry_find(th_dvb_adapter_t *tda,
|
|||
void dvb_lnb_get_frequencies(const char *id,
|
||||
int *f_low, int *f_hi, int *f_switch);
|
||||
|
||||
|
||||
/**
|
||||
* Raw demux
|
||||
*/
|
||||
struct th_subscription;
|
||||
struct th_subscription *dvb_subscription_create_from_tdmi(th_dvb_mux_instance_t *tdmi,
|
||||
const char *name,
|
||||
streaming_target_t *st,
|
||||
const char *hostname,
|
||||
const char *username,
|
||||
const char *client);
|
||||
|
||||
#endif /* DVB_H_ */
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
#include <pthread.h>
|
||||
#include <assert.h>
|
||||
|
||||
|
@ -23,6 +24,8 @@
|
|||
#include <sys/stat.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/types.h>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
|
@ -44,11 +47,22 @@
|
|||
#include "service.h"
|
||||
#include "epggrab.h"
|
||||
#include "diseqc.h"
|
||||
#include "atomic.h"
|
||||
|
||||
struct th_dvb_adapter_queue dvb_adapters;
|
||||
struct th_dvb_mux_instance_tree dvb_muxes;
|
||||
static void *dvb_adapter_input_dvr(void *aux);
|
||||
static void tda_init(th_dvb_adapter_t *tda);
|
||||
|
||||
/**
|
||||
* Adapters that are known to have SNR support
|
||||
*/
|
||||
static const char* dvb_adapter_snr_whitelist[] = {
|
||||
"Sony CXD2820R",
|
||||
"stv090x",
|
||||
"TurboSight",
|
||||
NULL
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -64,10 +78,7 @@ tda_alloc(void)
|
|||
TAILQ_INIT(&tda->tda_scan_queues[i]);
|
||||
TAILQ_INIT(&tda->tda_initial_scan_queue);
|
||||
TAILQ_INIT(&tda->tda_satconfs);
|
||||
|
||||
tda->tda_allpids_dmx_fd = -1;
|
||||
tda->tda_dump_fd = -1;
|
||||
|
||||
streaming_pad_init(&tda->tda_streaming_pad);
|
||||
return tda;
|
||||
}
|
||||
|
||||
|
@ -82,24 +93,59 @@ tda_save(th_dvb_adapter_t *tda)
|
|||
|
||||
lock_assert(&global_lock);
|
||||
|
||||
htsmsg_add_u32(m, "enabled", tda->tda_enabled);
|
||||
if (tda->tda_fe_path)
|
||||
htsmsg_add_str(m, "fe_path", tda->tda_fe_path);
|
||||
if (tda->tda_demux_path)
|
||||
htsmsg_add_str(m, "dmx_path", tda->tda_demux_path);
|
||||
if (tda->tda_dvr_path)
|
||||
htsmsg_add_str(m, "dvr_path", tda->tda_dvr_path);
|
||||
htsmsg_add_str(m, "type", dvb_adaptertype_to_str(tda->tda_type));
|
||||
htsmsg_add_str(m, "displayname", tda->tda_displayname);
|
||||
htsmsg_add_u32(m, "autodiscovery", tda->tda_autodiscovery);
|
||||
htsmsg_add_u32(m, "idlescan", tda->tda_idlescan);
|
||||
htsmsg_add_u32(m, "idleclose", tda->tda_idleclose);
|
||||
htsmsg_add_u32(m, "skip_checksubscr", tda->tda_skip_checksubscr);
|
||||
htsmsg_add_u32(m, "sidtochan", tda->tda_sidtochan);
|
||||
htsmsg_add_u32(m, "qmon", tda->tda_qmon);
|
||||
htsmsg_add_u32(m, "dump_muxes", tda->tda_dump_muxes);
|
||||
htsmsg_add_u32(m, "poweroff", tda->tda_poweroff);
|
||||
htsmsg_add_u32(m, "nitoid", tda->tda_nitoid);
|
||||
htsmsg_add_u32(m, "diseqc_version", tda->tda_diseqc_version);
|
||||
htsmsg_add_u32(m, "diseqc_repeats", tda->tda_diseqc_repeats);
|
||||
htsmsg_add_u32(m, "extrapriority", tda->tda_extrapriority);
|
||||
htsmsg_add_u32(m, "skip_initialscan", tda->tda_skip_initialscan);
|
||||
htsmsg_add_u32(m, "disable_pmt_monitor", tda->tda_disable_pmt_monitor);
|
||||
htsmsg_add_s32(m, "full_mux_rx", tda->tda_full_mux_rx);
|
||||
htsmsg_add_u32(m, "grace_period", tda->tda_grace_period);
|
||||
hts_settings_save(m, "dvbadapters/%s", tda->tda_identifier);
|
||||
htsmsg_destroy(m);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
dvb_adapter_set_enabled(th_dvb_adapter_t *tda, int on)
|
||||
{
|
||||
if(tda->tda_enabled == on)
|
||||
return;
|
||||
|
||||
lock_assert(&global_lock);
|
||||
|
||||
tvhlog(LOG_NOTICE, "dvb", "Adapter \"%s\" %s",
|
||||
tda->tda_displayname, on ? "Enabled" : "Disabled");
|
||||
|
||||
tda->tda_enabled = on;
|
||||
tda_save(tda);
|
||||
if (!on) {
|
||||
gtimer_disarm(&tda->tda_mux_scanner_timer);
|
||||
if (tda->tda_mux_current)
|
||||
dvb_fe_stop(tda->tda_mux_current, 0);
|
||||
dvb_adapter_stop(tda, TDA_OPT_ALL);
|
||||
} else {
|
||||
tda_init(tda);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -123,7 +169,6 @@ dvb_adapter_set_displayname(th_dvb_adapter_t *tda, const char *s)
|
|||
dvb_adapter_notify(tda);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -272,25 +317,6 @@ dvb_adapter_set_poweroff(th_dvb_adapter_t *tda, int on)
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
dvb_adapter_set_dump_muxes(th_dvb_adapter_t *tda, int on)
|
||||
{
|
||||
if(tda->tda_dump_muxes == on)
|
||||
return;
|
||||
|
||||
lock_assert(&global_lock);
|
||||
|
||||
tvhlog(LOG_NOTICE, "dvb", "Adapter \"%s\" dump of DVB mux input set to: %s",
|
||||
tda->tda_displayname, on ? "On" : "Off");
|
||||
|
||||
tda->tda_dump_muxes = on;
|
||||
tda_save(tda);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -333,6 +359,21 @@ dvb_adapter_set_diseqc_version(th_dvb_adapter_t *tda, unsigned int v)
|
|||
tda_save(tda);
|
||||
}
|
||||
|
||||
/**
|
||||
* sets the number of diseqc repeats to perform
|
||||
*/
|
||||
void
|
||||
dvb_adapter_set_diseqc_repeats(th_dvb_adapter_t *tda, unsigned int repeats)
|
||||
{
|
||||
if(tda->tda_diseqc_repeats == repeats)
|
||||
return;
|
||||
lock_assert(&global_lock);
|
||||
tvhlog(LOG_NOTICE, "dvb", "Adapter \"%s\" DiSEqC repeats set to: %i",
|
||||
tda->tda_displayname, repeats);
|
||||
tda->tda_diseqc_repeats = repeats;
|
||||
tda_save(tda);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -370,6 +411,47 @@ dvb_adapter_set_disable_pmt_monitor(th_dvb_adapter_t *tda, int on)
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
dvb_adapter_set_full_mux_rx(th_dvb_adapter_t *tda, int on)
|
||||
{
|
||||
const char* label[] = { "Auto", "Off", "On" };
|
||||
|
||||
if (on < -1) on = -1;
|
||||
if (on > 1) on = 1;
|
||||
|
||||
if(tda->tda_full_mux_rx == on)
|
||||
return;
|
||||
|
||||
lock_assert(&global_lock);
|
||||
|
||||
tvhlog(LOG_NOTICE, "dvb",
|
||||
"Adapter \"%s\" disabled full MUX receive set to: %s",
|
||||
tda->tda_displayname, label[on+1]);
|
||||
|
||||
tda->tda_full_mux_rx = on;
|
||||
tda_save(tda);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
dvb_adapter_set_grace_period(th_dvb_adapter_t *tda, uint32_t p)
|
||||
{
|
||||
if (tda->tda_grace_period == p)
|
||||
return;
|
||||
|
||||
tvhlog(LOG_NOTICE, "dvb",
|
||||
"Adapter \"%s\" set grace period to %d", tda->tda_displayname, p);
|
||||
|
||||
tda->tda_grace_period = p;
|
||||
tda_save(tda);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -380,7 +462,44 @@ dvb_adapter_checkspeed(th_dvb_adapter_t *tda)
|
|||
|
||||
snprintf(dev, sizeof(dev), "dvb/dvb%d.dvr0", tda->tda_adapter_num);
|
||||
tda->tda_hostconnection = get_device_connection(dev);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Return 1 if an adapter is capable of receiving a full mux
|
||||
*/
|
||||
static int
|
||||
check_full_stream(th_dvb_adapter_t *tda)
|
||||
{
|
||||
struct dmx_pes_filter_params dmx_param;
|
||||
int r;
|
||||
|
||||
if(tda->tda_full_mux_rx != -1)
|
||||
return tda->tda_full_mux_rx;
|
||||
|
||||
if(tda->tda_hostconnection == HOSTCONNECTION_USB12)
|
||||
return 0; // Don't even bother, device <-> host interface is too slow
|
||||
|
||||
if(tda->tda_hostconnection == HOSTCONNECTION_USB480)
|
||||
return 0; // USB in general appears to have CPU loading issues?
|
||||
|
||||
int fd = tvh_open(tda->tda_demux_path, O_RDWR, 0);
|
||||
if(fd == -1)
|
||||
return 0;
|
||||
|
||||
memset(&dmx_param, 0, sizeof(dmx_param));
|
||||
dmx_param.pid = 0x2000;
|
||||
dmx_param.input = DMX_IN_FRONTEND;
|
||||
dmx_param.output = DMX_OUT_TS_TAP;
|
||||
dmx_param.pes_type = DMX_PES_OTHER;
|
||||
dmx_param.flags = DMX_IMMEDIATE_START;
|
||||
|
||||
r = ioctl(fd, DMX_SET_PES_FILTER, &dmx_param);
|
||||
close(fd);
|
||||
return !r;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
|
@ -389,51 +508,140 @@ dvb_adapter_checkspeed(th_dvb_adapter_t *tda)
|
|||
static void
|
||||
tda_add(int adapter_num)
|
||||
{
|
||||
char path[200], fname[256];
|
||||
int fe, i, r;
|
||||
char buf[1024], path[200], fepath[256], dmxpath[256], dvrpath[256];
|
||||
int i, j, fd;
|
||||
th_dvb_adapter_t *tda;
|
||||
struct dvb_frontend_info fe_info;
|
||||
DIR *dirp;
|
||||
const char **str;
|
||||
|
||||
/* Check valid adapter */
|
||||
snprintf(path, sizeof(path), "/dev/dvb/adapter%d", adapter_num);
|
||||
dirp = opendir(path);
|
||||
if (!dirp)
|
||||
return;
|
||||
closedir(dirp);
|
||||
|
||||
/* Check each frontend */
|
||||
// Note: this algo will fail if there are really exotic variations
|
||||
// of tuners and demuxers etc... but you can always manually
|
||||
// override the config
|
||||
for (i = 0; i < 32; i++) {
|
||||
|
||||
/* Open/Query frontend */
|
||||
snprintf(fepath, sizeof(fepath), "%s/frontend%d", path, i);
|
||||
fd = tvh_open(fepath, O_RDONLY, 0);
|
||||
if (fd == -1) {
|
||||
if (errno != ENOENT)
|
||||
tvhlog(LOG_ALERT, "dvb",
|
||||
"%s: unable to open (err=%s)", fepath, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
if (ioctl(fd, FE_GET_INFO, &fe_info)) {
|
||||
tvhlog(LOG_ALERT, "dvb",
|
||||
"%s: unable to query (err=%s)", fepath, strerror(errno));
|
||||
close(fd);
|
||||
continue;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
/* Find Demux */
|
||||
snprintf(dmxpath, sizeof(dmxpath), "%s/demux%d", path, i);
|
||||
fd = tvh_open(dmxpath, O_RDONLY, 0);
|
||||
if (fd == -1) {
|
||||
snprintf(dmxpath, sizeof(dmxpath), "%s/demux%d", path, 0);
|
||||
fd = tvh_open(dmxpath, O_RDONLY, 0);
|
||||
}
|
||||
if (fd == -1) {
|
||||
tvhlog(LOG_ALERT, "dvb", "%s: unable to find demux", fepath);
|
||||
continue;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
/* Find DVR */
|
||||
snprintf(dvrpath, sizeof(dvrpath), "%s/dvr%d", path, i);
|
||||
fd = tvh_open(dvrpath, O_RDONLY, 0);
|
||||
if (fd == -1) {
|
||||
snprintf(dvrpath, sizeof(dvrpath), "%s/dvr%d", path, 0);
|
||||
fd = tvh_open(dvrpath, O_RDONLY, 0);
|
||||
}
|
||||
if (fd == -1) {
|
||||
tvhlog(LOG_ALERT, "dvb", "%s: unable to find dvr", fepath);
|
||||
continue;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
/* Create entry */
|
||||
tda = tda_alloc();
|
||||
tda->tda_adapter_num = adapter_num;
|
||||
tda->tda_rootpath = strdup(path);
|
||||
tda->tda_fe_path = strdup(fepath);
|
||||
tda->tda_demux_path = strdup(dmxpath);
|
||||
tda->tda_dvr_path = strdup(dvrpath);
|
||||
tda->tda_fe_fd = -1;
|
||||
tda->tda_dvr_pipe.rd = -1;
|
||||
tda->tda_full_mux_rx = -1;
|
||||
tda->tda_type = fe_info.type;
|
||||
tda->tda_autodiscovery = tda->tda_type != FE_QPSK;
|
||||
tda->tda_idlescan = 1;
|
||||
tda->tda_sat = tda->tda_type == FE_QPSK;
|
||||
tda->tda_displayname = strdup(fe_info.name);
|
||||
tda->tda_fe_info = malloc(sizeof(struct dvb_frontend_info));
|
||||
memcpy(tda->tda_fe_info, &fe_info, sizeof(struct dvb_frontend_info));
|
||||
|
||||
/* ID the device (for configuration etc..) */
|
||||
// Note: would be better to include frontend dev in name, but that
|
||||
// would require additional work to migrate config
|
||||
snprintf(buf, sizeof(buf), "%s_%s", tda->tda_rootpath,
|
||||
tda->tda_fe_info->name);
|
||||
for(j = 0; j < strlen(buf); j++)
|
||||
if(!isalnum((int)buf[j]))
|
||||
buf[j] = '_';
|
||||
tda->tda_identifier = strdup(buf);
|
||||
|
||||
/* Check device connection */
|
||||
dvb_adapter_checkspeed(tda);
|
||||
|
||||
/* Adapters known to provide valid SNR */
|
||||
str = dvb_adapter_snr_whitelist;
|
||||
while (*str) {
|
||||
if (strcasestr(fe_info.name, *str)) {
|
||||
tda->tda_snr_valid = 1;
|
||||
break;
|
||||
}
|
||||
str++;
|
||||
}
|
||||
|
||||
/* Store */
|
||||
tvhlog(LOG_INFO, "dvb",
|
||||
"Found adapter %s (%s) via %s%s", path, tda->tda_fe_info->name,
|
||||
hostconnection2str(tda->tda_hostconnection),
|
||||
tda->tda_snr_valid ? ", Reports valid SNR values" : "");
|
||||
TAILQ_INSERT_TAIL(&dvb_adapters, tda, tda_global_link);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
tda_add_from_file(const char *filename)
|
||||
{
|
||||
int i, r;
|
||||
th_dvb_adapter_t *tda;
|
||||
char buf[400];
|
||||
|
||||
snprintf(path, sizeof(path), "/dev/dvb/adapter%d", adapter_num);
|
||||
snprintf(fname, sizeof(fname), "%s/frontend0", path);
|
||||
|
||||
fe = tvh_open(fname, O_RDWR | O_NONBLOCK, 0);
|
||||
if(fe == -1) {
|
||||
if(errno != ENOENT)
|
||||
tvhlog(LOG_ALERT, "dvb",
|
||||
"Unable to open %s -- %s", fname, strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
tda = tda_alloc();
|
||||
|
||||
tda->tda_adapter_num = adapter_num;
|
||||
tda->tda_rootpath = strdup(path);
|
||||
tda->tda_demux_path = malloc(256);
|
||||
snprintf(tda->tda_demux_path, 256, "%s/demux0", path);
|
||||
tda->tda_dvr_path = malloc(256);
|
||||
snprintf(tda->tda_dvr_path, 256, "%s/dvr0", path);
|
||||
tda->tda_fe_path = strdup(fname);
|
||||
tda->tda_adapter_num = -1;
|
||||
tda->tda_fe_fd = -1;
|
||||
tda->tda_dvr_pipe[0] = -1;
|
||||
tda->tda_dvr_pipe.rd = -1;
|
||||
|
||||
tda->tda_fe_info = malloc(sizeof(struct dvb_frontend_info));
|
||||
tda->tda_enabled = 1;
|
||||
|
||||
if(ioctl(fe, FE_GET_INFO, tda->tda_fe_info)) {
|
||||
tvhlog(LOG_ALERT, "dvb", "%s: Unable to query adapter", fname);
|
||||
close(fe);
|
||||
free(tda);
|
||||
return;
|
||||
}
|
||||
if (tda->tda_idlescan || !tda->tda_idleclose)
|
||||
tda->tda_fe_fd = fe;
|
||||
else
|
||||
close(fe);
|
||||
tda->tda_type = -1;
|
||||
|
||||
tda->tda_type = tda->tda_fe_info->type;
|
||||
|
||||
snprintf(buf, sizeof(buf), "%s_%s", tda->tda_rootpath,
|
||||
tda->tda_fe_info->name);
|
||||
snprintf(buf, sizeof(buf), "%s", filename);
|
||||
|
||||
r = strlen(buf);
|
||||
for(i = 0; i < r; i++)
|
||||
|
@ -442,145 +650,190 @@ tda_add(int adapter_num)
|
|||
|
||||
tda->tda_identifier = strdup(buf);
|
||||
|
||||
tda->tda_autodiscovery = tda->tda_type != FE_QPSK;
|
||||
tda->tda_idlescan = 1;
|
||||
tda->tda_autodiscovery = 0;
|
||||
tda->tda_idlescan = 0;
|
||||
|
||||
tda->tda_sat = tda->tda_type == FE_QPSK;
|
||||
tda->tda_sat = 0;
|
||||
|
||||
tda->tda_full_mux_rx = 1;
|
||||
|
||||
/* Come up with an initial displayname, user can change it and it will
|
||||
be overridden by any stored settings later on */
|
||||
|
||||
tda->tda_displayname = strdup(tda->tda_fe_info->name);
|
||||
|
||||
dvb_adapter_checkspeed(tda);
|
||||
|
||||
tvhlog(LOG_INFO, "dvb",
|
||||
"Found adapter %s (%s) via %s", path, tda->tda_fe_info->name,
|
||||
hostconnection2str(tda->tda_hostconnection));
|
||||
tda->tda_displayname = strdup(filename);
|
||||
|
||||
TAILQ_INSERT_TAIL(&dvb_adapters, tda, tda_global_link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiliase
|
||||
*/
|
||||
static void tda_init (th_dvb_adapter_t *tda)
|
||||
{
|
||||
/* Disabled - ignore */
|
||||
if (!tda->tda_enabled || !tda->tda_rootpath) return;
|
||||
|
||||
dvb_table_init(tda);
|
||||
|
||||
if(tda->tda_sat)
|
||||
dvb_satconf_init(tda);
|
||||
/* Initiliase input mode */
|
||||
if(!tda->tda_open_service) {
|
||||
if(tda->tda_type == -1 || check_full_stream(tda)) {
|
||||
tvhlog(LOG_INFO, "dvb", "Adapter %s will run in full mux mode", tda->tda_rootpath);
|
||||
dvb_input_raw_setup(tda);
|
||||
} else {
|
||||
tvhlog(LOG_INFO, "dvb", "Adapter %s will run in filtered mode", tda->tda_rootpath);
|
||||
dvb_input_filtered_setup(tda);
|
||||
}
|
||||
}
|
||||
|
||||
/* Enable mux scanner */
|
||||
gtimer_arm(&tda->tda_mux_scanner_timer, dvb_adapter_mux_scanner, tda, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
dvb_adapter_start ( th_dvb_adapter_t *tda )
|
||||
dvb_adapter_start ( th_dvb_adapter_t *tda, int opt )
|
||||
{
|
||||
if(tda->tda_enabled == 0) {
|
||||
tvhlog(LOG_INFO, "dvb", "Adapter \"%s\" cannot be started - it's disabled", tda->tda_displayname);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Default to ALL */
|
||||
if (!opt)
|
||||
opt = TDA_OPT_ALL;
|
||||
|
||||
/* Open front end */
|
||||
if (tda->tda_fe_fd == -1) {
|
||||
if ((opt & TDA_OPT_FE) && (tda->tda_fe_fd == -1)) {
|
||||
tda->tda_fe_fd = tvh_open(tda->tda_fe_path, O_RDWR | O_NONBLOCK, 0);
|
||||
if (tda->tda_fe_fd == -1) return;
|
||||
tvhlog(LOG_DEBUG, "dvb", "%s opened frontend %s", tda->tda_rootpath, tda->tda_fe_path);
|
||||
}
|
||||
|
||||
/* Start DVR thread */
|
||||
if (tda->tda_dvr_pipe[0] == -1) {
|
||||
assert(pipe(tda->tda_dvr_pipe) != -1);
|
||||
fcntl(tda->tda_dvr_pipe[0], F_SETFD, fcntl(tda->tda_dvr_pipe[0], F_GETFD) | FD_CLOEXEC);
|
||||
fcntl(tda->tda_dvr_pipe[0], F_SETFL, fcntl(tda->tda_dvr_pipe[0], F_GETFL) | O_NONBLOCK);
|
||||
fcntl(tda->tda_dvr_pipe[1], F_SETFD, fcntl(tda->tda_dvr_pipe[1], F_GETFD) | FD_CLOEXEC);
|
||||
if ((opt & TDA_OPT_DVR) && (tda->tda_dvr_pipe.rd == -1)) {
|
||||
int err = tvh_pipe(O_NONBLOCK, &tda->tda_dvr_pipe);
|
||||
assert(err != -1);
|
||||
pthread_create(&tda->tda_dvr_thread, NULL, dvb_adapter_input_dvr, tda);
|
||||
tvhlog(LOG_DEBUG, "dvb", "%s started dvr thread", tda->tda_rootpath);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
dvb_adapter_stop ( th_dvb_adapter_t *tda )
|
||||
dvb_adapter_stop ( th_dvb_adapter_t *tda, int opt )
|
||||
{
|
||||
/* Poweroff */
|
||||
dvb_adapter_poweroff(tda);
|
||||
if (opt & TDA_OPT_PWR)
|
||||
dvb_adapter_poweroff(tda);
|
||||
|
||||
/* Don't stop/close */
|
||||
if (!tda->tda_idleclose) return;
|
||||
/* Stop DVR thread */
|
||||
if ((opt & TDA_OPT_DVR) && (tda->tda_dvr_pipe.rd != -1)) {
|
||||
tvhlog(LOG_DEBUG, "dvb", "%s stopping thread", tda->tda_rootpath);
|
||||
int err = tvh_write(tda->tda_dvr_pipe.wr, "", 1);
|
||||
assert(!err);
|
||||
pthread_join(tda->tda_dvr_thread, NULL);
|
||||
close(tda->tda_dvr_pipe.rd);
|
||||
close(tda->tda_dvr_pipe.wr);
|
||||
tda->tda_dvr_pipe.rd = -1;
|
||||
tvhlog(LOG_DEBUG, "dvb", "%s stopped thread", tda->tda_rootpath);
|
||||
}
|
||||
|
||||
dvb_adapter_notify(tda);
|
||||
|
||||
/* Don't close FE */
|
||||
if (!tda->tda_idleclose && tda->tda_enabled) return;
|
||||
|
||||
/* Close front end */
|
||||
if (tda->tda_fe_fd != -1) {
|
||||
if ((opt & TDA_OPT_FE) && (tda->tda_fe_fd != -1)) {
|
||||
tvhlog(LOG_DEBUG, "dvb", "%s closing frontend", tda->tda_rootpath);
|
||||
close(tda->tda_fe_fd);
|
||||
tda->tda_fe_fd = -1;
|
||||
}
|
||||
|
||||
/* Stop DVR thread */
|
||||
if (tda->tda_dvr_pipe[0] != -1) {
|
||||
tvhlog(LOG_DEBUG, "dvb", "%s stopping thread", tda->tda_rootpath);
|
||||
assert(write(tda->tda_dvr_pipe[1], "", 1) == 1);
|
||||
pthread_join(tda->tda_dvr_thread, NULL);
|
||||
close(tda->tda_dvr_pipe[0]);
|
||||
close(tda->tda_dvr_pipe[1]);
|
||||
tda->tda_dvr_pipe[0] = -1;
|
||||
tvhlog(LOG_DEBUG, "dvb", "%s stopped thread", tda->tda_rootpath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
dvb_adapter_init(uint32_t adapter_mask)
|
||||
dvb_adapter_init(uint32_t adapter_mask, const char *rawfile)
|
||||
{
|
||||
htsmsg_t *l, *c;
|
||||
htsmsg_field_t *f;
|
||||
const char *name, *s;
|
||||
const char *s;
|
||||
int i, type;
|
||||
uint32_t u32;
|
||||
th_dvb_adapter_t *tda;
|
||||
|
||||
TAILQ_INIT(&dvb_adapters);
|
||||
|
||||
/* Initialise hardware */
|
||||
for(i = 0; i < 32; i++)
|
||||
if ((1 << i) & adapter_mask)
|
||||
tda_add(i);
|
||||
|
||||
l = hts_settings_load("dvbadapters");
|
||||
if(l != NULL) {
|
||||
/* Initialise rawts test file */
|
||||
if(rawfile)
|
||||
tda_add_from_file(rawfile);
|
||||
|
||||
/* Load configuration */
|
||||
if ((l = hts_settings_load("dvbadapters")) != NULL) {
|
||||
HTSMSG_FOREACH(f, l) {
|
||||
if((c = htsmsg_get_map_by_field(f)) == NULL)
|
||||
continue;
|
||||
continue;
|
||||
|
||||
name = htsmsg_get_str(c, "displayname");
|
||||
|
||||
if((s = htsmsg_get_str(c, "type")) == NULL ||
|
||||
(type = dvb_str_to_adaptertype(s)) < 0)
|
||||
continue;
|
||||
(type = dvb_str_to_adaptertype(s)) < 0)
|
||||
continue;
|
||||
|
||||
// TODO: do we really want to do this? useful for debug?
|
||||
if((tda = dvb_adapter_find_by_identifier(f->hmf_name)) == NULL) {
|
||||
/* Not discovered by hardware, create it */
|
||||
|
||||
tda = tda_alloc();
|
||||
tda->tda_identifier = strdup(f->hmf_name);
|
||||
tda->tda_type = type;
|
||||
TAILQ_INSERT_TAIL(&dvb_adapters, tda, tda_global_link);
|
||||
/* Not discovered by hardware, create it */
|
||||
tda = tda_alloc();
|
||||
tda->tda_identifier = strdup(f->hmf_name);
|
||||
tda->tda_type = type;
|
||||
TAILQ_INSERT_TAIL(&dvb_adapters, tda, tda_global_link);
|
||||
} else {
|
||||
if(type != tda->tda_type)
|
||||
continue; /* Something is wrong, ignore */
|
||||
if(type != tda->tda_type)
|
||||
continue; /* Something is wrong, ignore */
|
||||
}
|
||||
|
||||
free(tda->tda_displayname);
|
||||
tda->tda_displayname = strdup(name);
|
||||
tvh_str_update(&tda->tda_displayname, htsmsg_get_str(c, "displayname"));
|
||||
tvh_str_update(&tda->tda_dvr_path, htsmsg_get_str(c, "dvr_path"));
|
||||
tvh_str_update(&tda->tda_demux_path, htsmsg_get_str(c, "dmx_path"));
|
||||
|
||||
htsmsg_get_u32(c, "autodiscovery", &tda->tda_autodiscovery);
|
||||
htsmsg_get_u32(c, "idlescan", &tda->tda_idlescan);
|
||||
htsmsg_get_u32(c, "idleclose", &tda->tda_idleclose);
|
||||
htsmsg_get_u32(c, "skip_checksubscr", &tda->tda_skip_checksubscr);
|
||||
htsmsg_get_u32(c, "qmon", &tda->tda_qmon);
|
||||
htsmsg_get_u32(c, "dump_muxes", &tda->tda_dump_muxes);
|
||||
htsmsg_get_u32(c, "poweroff", &tda->tda_poweroff);
|
||||
htsmsg_get_u32(c, "nitoid", &tda->tda_nitoid);
|
||||
htsmsg_get_u32(c, "diseqc_version", &tda->tda_diseqc_version);
|
||||
htsmsg_get_u32(c, "extrapriority", &tda->tda_extrapriority);
|
||||
htsmsg_get_u32(c, "skip_initialscan", &tda->tda_skip_initialscan);
|
||||
if (htsmsg_get_u32(c, "enabled", &tda->tda_enabled))
|
||||
tda->tda_enabled = 1;
|
||||
htsmsg_get_u32(c, "autodiscovery", &tda->tda_autodiscovery);
|
||||
htsmsg_get_u32(c, "idlescan", &tda->tda_idlescan);
|
||||
htsmsg_get_u32(c, "idleclose", &tda->tda_idleclose);
|
||||
htsmsg_get_u32(c, "skip_checksubscr", &tda->tda_skip_checksubscr);
|
||||
htsmsg_get_u32(c, "sidtochan", &tda->tda_sidtochan);
|
||||
htsmsg_get_u32(c, "qmon", &tda->tda_qmon);
|
||||
htsmsg_get_u32(c, "poweroff", &tda->tda_poweroff);
|
||||
htsmsg_get_u32(c, "nitoid", &tda->tda_nitoid);
|
||||
htsmsg_get_u32(c, "diseqc_version", &tda->tda_diseqc_version);
|
||||
htsmsg_get_u32(c, "diseqc_repeats", &tda->tda_diseqc_repeats);
|
||||
htsmsg_get_u32(c, "extrapriority", &tda->tda_extrapriority);
|
||||
htsmsg_get_u32(c, "skip_initialscan", &tda->tda_skip_initialscan);
|
||||
htsmsg_get_u32(c, "disable_pmt_monitor", &tda->tda_disable_pmt_monitor);
|
||||
if (htsmsg_get_u32(c, "grace_period", &tda->tda_grace_period))
|
||||
tda->tda_grace_period = 10;
|
||||
if (htsmsg_get_s32(c, "full_mux_rx", &tda->tda_full_mux_rx))
|
||||
if (!htsmsg_get_u32(c, "disable_full_mux_rx", &u32) && u32)
|
||||
tda->tda_full_mux_rx = 0;
|
||||
}
|
||||
htsmsg_destroy(l);
|
||||
}
|
||||
|
||||
TAILQ_FOREACH(tda, &dvb_adapters, tda_global_link)
|
||||
/* Initialise devices */
|
||||
TAILQ_FOREACH(tda, &dvb_adapters, tda_global_link) {
|
||||
tda_init(tda);
|
||||
|
||||
if(tda->tda_sat)
|
||||
dvb_satconf_init(tda);
|
||||
|
||||
dvb_mux_load(tda);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -598,6 +851,9 @@ dvb_adapter_mux_scanner(void *aux)
|
|||
if(tda->tda_rootpath == NULL)
|
||||
return; // No hardware
|
||||
|
||||
if(!tda->tda_enabled)
|
||||
return; // disabled
|
||||
|
||||
// default period
|
||||
gtimer_arm(&tda->tda_mux_scanner_timer, dvb_adapter_mux_scanner, tda, 20);
|
||||
|
||||
|
@ -611,6 +867,10 @@ dvb_adapter_mux_scanner(void *aux)
|
|||
if(service_compute_weight(&tda->tda_transports) > 0)
|
||||
return;
|
||||
|
||||
if(tda->tda_mux_current != NULL &&
|
||||
LIST_FIRST(&tda->tda_mux_current->tdmi_subscriptions) != NULL)
|
||||
return; // Someone is doing full mux dump
|
||||
|
||||
/* Check if we have muxes pending for quickscan, if so, choose them */
|
||||
if((tdmi = TAILQ_FIRST(&tda->tda_initial_scan_queue)) != NULL) {
|
||||
dvb_fe_tune(tdmi, "Initial autoscan");
|
||||
|
@ -719,8 +979,39 @@ dvb_adapter_clean(th_dvb_adapter_t *tda)
|
|||
service_remove_subscriber(t, NULL, SM_CODE_SUBSCRIPTION_OVERRIDDEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Install RAW PES filter
|
||||
*/
|
||||
static int
|
||||
dvb_adapter_raw_filter(th_dvb_adapter_t *tda)
|
||||
{
|
||||
int dmx = -1;
|
||||
struct dmx_pes_filter_params dmx_param;
|
||||
|
||||
dmx = tvh_open(tda->tda_demux_path, O_RDWR, 0);
|
||||
if(dmx == -1) {
|
||||
tvhlog(LOG_ALERT, "dvb", "Unable to open %s -- %s",
|
||||
tda->tda_demux_path, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(&dmx_param, 0, sizeof(dmx_param));
|
||||
dmx_param.pid = 0x2000;
|
||||
dmx_param.input = DMX_IN_FRONTEND;
|
||||
dmx_param.output = DMX_OUT_TS_TAP;
|
||||
dmx_param.pes_type = DMX_PES_OTHER;
|
||||
dmx_param.flags = DMX_IMMEDIATE_START;
|
||||
|
||||
if(ioctl(dmx, DMX_SET_PES_FILTER, &dmx_param) == -1) {
|
||||
tvhlog(LOG_ERR, "dvb",
|
||||
"Unable to configure demuxer \"%s\" for all PIDs -- %s",
|
||||
tda->tda_demux_path, strerror(errno));
|
||||
close(dmx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return dmx;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -729,56 +1020,81 @@ static void *
|
|||
dvb_adapter_input_dvr(void *aux)
|
||||
{
|
||||
th_dvb_adapter_t *tda = aux;
|
||||
int fd, i, r, c, efd, nfds;
|
||||
int fd = -1, i, r, c, efd, nfds, dmx = -1;
|
||||
uint8_t tsb[188 * 10];
|
||||
service_t *t;
|
||||
struct epoll_event ev;
|
||||
int delay = 10;
|
||||
|
||||
fd = tvh_open(tda->tda_dvr_path, O_RDONLY | O_NONBLOCK, 0);
|
||||
if(fd == -1) {
|
||||
tvhlog(LOG_ALERT, "dvb", "%s: unable to open dvr", tda->tda_dvr_path);
|
||||
/* Install RAW demux */
|
||||
if (tda->tda_rawmode) {
|
||||
if ((dmx = dvb_adapter_raw_filter(tda)) == -1) {
|
||||
tvhlog(LOG_ALERT, "dvb", "Unable to install raw mux filter");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Open DVR */
|
||||
if ((fd = tvh_open(tda->tda_dvr_path, O_RDONLY | O_NONBLOCK, 0)) == -1) {
|
||||
close(dmx);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Create poll */
|
||||
efd = epoll_create(2);
|
||||
memset(&ev, 0, sizeof(ev));
|
||||
ev.events = EPOLLIN;
|
||||
ev.data.fd = tda->tda_dvr_pipe.rd;
|
||||
epoll_ctl(efd, EPOLL_CTL_ADD, tda->tda_dvr_pipe.rd, &ev);
|
||||
ev.data.fd = fd;
|
||||
epoll_ctl(efd, EPOLL_CTL_ADD, fd, &ev);
|
||||
ev.data.fd = tda->tda_dvr_pipe[0];
|
||||
epoll_ctl(efd, EPOLL_CTL_ADD, tda->tda_dvr_pipe[0], &ev);
|
||||
|
||||
r = i = 0;
|
||||
while(1) {
|
||||
|
||||
/* Wait for input */
|
||||
nfds = epoll_wait(efd, &ev, 1, -1);
|
||||
nfds = epoll_wait(efd, &ev, 1, delay);
|
||||
|
||||
/* No data */
|
||||
if (nfds < 1) continue;
|
||||
|
||||
/* Exit */
|
||||
if (ev.data.fd != fd) break;
|
||||
|
||||
/* Read data */
|
||||
c = read(fd, tsb+r, sizeof(tsb)-r);
|
||||
if (c < 0) {
|
||||
if (errno == EAGAIN || errno == EINTR)
|
||||
continue;
|
||||
else
|
||||
else if (errno == EOVERFLOW) {
|
||||
tvhlog(LOG_WARNING, "dvb", "\"%s\" read() EOVERFLOW",
|
||||
tda->tda_identifier);
|
||||
continue;
|
||||
} else {
|
||||
// TODO: should we try to recover?
|
||||
tvhlog(LOG_ERR, "dvb", "\"%s\" read() error %d",
|
||||
tda->tda_identifier, errno);
|
||||
break;
|
||||
}
|
||||
}
|
||||
r += c;
|
||||
atomic_add(&tda->tda_bytes, c);
|
||||
|
||||
/* not enough data */
|
||||
if (r < 188) continue;
|
||||
|
||||
int wakeup_table_feed = 0; // Just wanna wakeup once
|
||||
|
||||
pthread_mutex_lock(&tda->tda_delivery_mutex);
|
||||
|
||||
/* debug */
|
||||
if(tda->tda_dump_fd != -1) {
|
||||
if(write(tda->tda_dump_fd, tsb, r) != r) {
|
||||
tvhlog(LOG_ERR, "dvb",
|
||||
"\"%s\" unable to write to mux dump file -- %s",
|
||||
tda->tda_identifier, strerror(errno));
|
||||
close(tda->tda_dump_fd);
|
||||
tda->tda_dump_fd = -1;
|
||||
}
|
||||
if(LIST_FIRST(&tda->tda_streaming_pad.sp_targets) != NULL) {
|
||||
streaming_message_t sm;
|
||||
pktbuf_t *pb = pktbuf_alloc(tsb, r);
|
||||
memset(&sm, 0, sizeof(sm));
|
||||
sm.sm_type = SMT_MPEGTS;
|
||||
sm.sm_data = pb;
|
||||
streaming_pad_deliver(&tda->tda_streaming_pad, &sm);
|
||||
pktbuf_ref_dec(pb);
|
||||
}
|
||||
|
||||
/* Process */
|
||||
|
@ -786,9 +1102,21 @@ dvb_adapter_input_dvr(void *aux)
|
|||
|
||||
/* sync */
|
||||
if (tsb[i] == 0x47) {
|
||||
LIST_FOREACH(t, &tda->tda_transports, s_active_link)
|
||||
if(t->s_dvb_mux_instance == tda->tda_mux_current)
|
||||
ts_recv_packet1(t, tsb + i, NULL);
|
||||
int pid = (tsb[i+1] & 0x1f) << 8 | tsb[i+2];
|
||||
|
||||
if(tda->tda_table_filter[pid]) {
|
||||
if(!(tsb[i+1] & 0x80)) { // Only dispatch to table parser if not error
|
||||
dvb_table_feed_t *dtf = malloc(sizeof(dvb_table_feed_t));
|
||||
memcpy(dtf->dtf_tsb, tsb + i, 188);
|
||||
TAILQ_INSERT_TAIL(&tda->tda_table_feed, dtf, dtf_link);
|
||||
wakeup_table_feed = 1;
|
||||
}
|
||||
} else {
|
||||
LIST_FOREACH(t, &tda->tda_transports, s_active_link)
|
||||
if(t->s_dvb_mux_instance == tda->tda_mux_current)
|
||||
ts_recv_packet1(t, tsb + i, NULL);
|
||||
}
|
||||
|
||||
i += 188;
|
||||
r -= 188;
|
||||
|
||||
|
@ -800,6 +1128,9 @@ dvb_adapter_input_dvr(void *aux)
|
|||
}
|
||||
}
|
||||
|
||||
if(wakeup_table_feed)
|
||||
pthread_cond_signal(&tda->tda_table_feed_cond);
|
||||
|
||||
pthread_mutex_unlock(&tda->tda_delivery_mutex);
|
||||
|
||||
/* reset buffer */
|
||||
|
@ -807,6 +1138,8 @@ dvb_adapter_input_dvr(void *aux)
|
|||
i = 0;
|
||||
}
|
||||
|
||||
if(dmx != -1)
|
||||
close(dmx);
|
||||
close(efd);
|
||||
close(fd);
|
||||
return NULL;
|
||||
|
@ -845,8 +1178,23 @@ dvb_adapter_build_msg(th_dvb_adapter_t *tda)
|
|||
htsmsg_add_u32(m, "initialMuxes", tda->tda_initial_num_mux);
|
||||
|
||||
if(tda->tda_mux_current != NULL) {
|
||||
th_dvb_mux_instance_t *tdmi = tda->tda_mux_current;
|
||||
|
||||
dvb_mux_nicename(buf, sizeof(buf), tda->tda_mux_current);
|
||||
htsmsg_add_str(m, "currentMux", buf);
|
||||
|
||||
htsmsg_add_u32(m, "signal", MIN(tdmi->tdmi_signal * 100 / 65535, 100));
|
||||
htsmsg_add_u32(m, "snr", tdmi->tdmi_snr);
|
||||
htsmsg_add_u32(m, "ber", tdmi->tdmi_ber);
|
||||
htsmsg_add_u32(m, "unc", tdmi->tdmi_unc);
|
||||
htsmsg_add_u32(m, "uncavg", tdmi->tdmi_unc_avg);
|
||||
} else {
|
||||
htsmsg_add_str(m, "currentMux", "");
|
||||
htsmsg_add_u32(m, "signal", 0);
|
||||
htsmsg_add_u32(m, "snr", 0);
|
||||
htsmsg_add_u32(m, "ber", 0);
|
||||
htsmsg_add_u32(m, "unc", 0);
|
||||
htsmsg_add_u32(m, "uncavg", 0);
|
||||
}
|
||||
|
||||
if(tda->tda_rootpath == NULL)
|
||||
|
@ -924,16 +1272,17 @@ dvb_fe_opts(th_dvb_adapter_t *tda, const char *which)
|
|||
return a;
|
||||
}
|
||||
|
||||
#if DVB_API_VERSION >= 5
|
||||
if(!strcmp(which, "delsys")) {
|
||||
if(c & FE_CAN_QPSK) {
|
||||
#if DVB_API_VERSION >= 5
|
||||
fe_opts_add(a, "SYS_DVBS", SYS_DVBS);
|
||||
fe_opts_add(a, "SYS_DVBS2", SYS_DVBS2);
|
||||
} else
|
||||
fe_opts_add(a, "SYS_UNDEFINED", SYS_UNDEFINED);
|
||||
#else
|
||||
fe_opts_add(a, "SYS_DVBS", -1);
|
||||
#endif
|
||||
}
|
||||
return a;
|
||||
}
|
||||
#endif
|
||||
|
||||
if(!strcmp(which, "transmissionmodes")) {
|
||||
if(c & FE_CAN_TRANSMISSION_MODE_AUTO)
|
||||
|
|
311
src/dvb/dvb_fe.c
311
src/dvb/dvb_fe.c
|
@ -41,6 +41,7 @@
|
|||
#include "dvr/dvr.h"
|
||||
#include "service.h"
|
||||
#include "streaming.h"
|
||||
#include "atomic.h"
|
||||
|
||||
#include "epggrab.h"
|
||||
|
||||
|
@ -90,14 +91,15 @@ dvb_fe_monitor(void *aux)
|
|||
{
|
||||
th_dvb_adapter_t *tda = aux;
|
||||
fe_status_t fe_status;
|
||||
int status, v, update = 0, vv, i, fec, q;
|
||||
int status, v, vv, i, fec, q, bw;
|
||||
th_dvb_mux_instance_t *tdmi = tda->tda_mux_current;
|
||||
char buf[50];
|
||||
signal_status_t sigstat;
|
||||
streaming_message_t sm;
|
||||
struct service *t;
|
||||
|
||||
gtimer_arm(&tda->tda_fe_monitor_timer, dvb_fe_monitor, tda, 1);
|
||||
int store = 0;
|
||||
int notify = 0;
|
||||
|
||||
if(tdmi == NULL)
|
||||
return;
|
||||
|
@ -106,9 +108,8 @@ dvb_fe_monitor(void *aux)
|
|||
* Read out front end status
|
||||
*/
|
||||
if(ioctl(tda->tda_fe_fd, FE_READ_STATUS, &fe_status))
|
||||
fe_status = 0;
|
||||
|
||||
if(fe_status & FE_HAS_LOCK)
|
||||
status = TDMI_FE_UNKNOWN;
|
||||
else if(fe_status & FE_HAS_LOCK)
|
||||
status = -1;
|
||||
else if(fe_status & (FE_HAS_SYNC | FE_HAS_VITERBI | FE_HAS_CARRIER))
|
||||
status = TDMI_FE_BAD_SIGNAL;
|
||||
|
@ -117,23 +118,58 @@ dvb_fe_monitor(void *aux)
|
|||
else
|
||||
status = TDMI_FE_NO_SIGNAL;
|
||||
|
||||
if(tda->tda_fe_monitor_hold > 0) {
|
||||
/* Post tuning threshold */
|
||||
if(status == -1) { /* We have a lock, don't hold off */
|
||||
tda->tda_fe_monitor_hold = 0;
|
||||
/* Reset FEC counter */
|
||||
dvb_fe_get_unc(tda);
|
||||
/**
|
||||
* Waiting for initial lock
|
||||
*/
|
||||
if(tda->tda_locked == 0) {
|
||||
|
||||
/* Read */
|
||||
if (status == -1) {
|
||||
tda->tda_locked = 1;
|
||||
dvb_adapter_start(tda, TDA_OPT_ALL);
|
||||
gtimer_arm(&tda->tda_fe_monitor_timer, dvb_fe_monitor, tda, 1);
|
||||
|
||||
/* Install table handlers */
|
||||
dvb_table_add_default(tdmi);
|
||||
epggrab_mux_start(tdmi);
|
||||
|
||||
/* Service filters */
|
||||
pthread_mutex_lock(&tda->tda_delivery_mutex);
|
||||
LIST_FOREACH(t, &tda->tda_transports, s_active_link) {
|
||||
if (t->s_dvb_mux_instance == tdmi) {
|
||||
tda->tda_open_service(tda, t);
|
||||
dvb_table_add_pmt(tdmi, t->s_pmt_pid);
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&tda->tda_delivery_mutex);
|
||||
|
||||
/* Re-arm (50ms) */
|
||||
} else {
|
||||
tda->tda_fe_monitor_hold--;
|
||||
return;
|
||||
gtimer_arm_ms(&tda->tda_fe_monitor_timer, dvb_fe_monitor, tda, 50);
|
||||
|
||||
/* Monitor (1 per sec) */
|
||||
if (dispatch_clock < tda->tda_monitor)
|
||||
return;
|
||||
tda->tda_monitor = dispatch_clock + 1;
|
||||
}
|
||||
|
||||
} else {
|
||||
gtimer_arm(&tda->tda_fe_monitor_timer, dvb_fe_monitor, tda, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Update stats
|
||||
*/
|
||||
if(status == -1) {
|
||||
/* Read FEC counter (delta) */
|
||||
|
||||
fec = dvb_fe_get_unc(tda);
|
||||
|
||||
|
||||
if(tdmi->tdmi_unc != fec) {
|
||||
tdmi->tdmi_unc = fec;
|
||||
notify = 1;
|
||||
}
|
||||
|
||||
tdmi->tdmi_fec_err_histogram[tdmi->tdmi_fec_err_ptr++] = fec;
|
||||
if(tdmi->tdmi_fec_err_ptr == TDMI_FEC_ERR_HISTOGRAM_SIZE)
|
||||
tdmi->tdmi_fec_err_ptr = 0;
|
||||
|
@ -144,7 +180,13 @@ dvb_fe_monitor(void *aux)
|
|||
v++;
|
||||
vv += tdmi->tdmi_fec_err_histogram[i];
|
||||
}
|
||||
vv = vv / TDMI_FEC_ERR_HISTOGRAM_SIZE;
|
||||
|
||||
float avg = (float)vv / TDMI_FEC_ERR_HISTOGRAM_SIZE;
|
||||
|
||||
if(tdmi->tdmi_unc_avg != avg) {
|
||||
tdmi->tdmi_unc_avg = avg;
|
||||
notify = 1;
|
||||
}
|
||||
|
||||
if(v == 0) {
|
||||
status = TDMI_FE_OK;
|
||||
|
@ -154,27 +196,40 @@ dvb_fe_monitor(void *aux)
|
|||
status = TDMI_FE_CONSTANT_FEC;
|
||||
}
|
||||
|
||||
int v;
|
||||
/* bit error rate */
|
||||
if(ioctl(tda->tda_fe_fd, FE_READ_BER, &tdmi->tdmi_ber) == -1)
|
||||
tdmi->tdmi_ber = -2;
|
||||
if(ioctl(tda->tda_fe_fd, FE_READ_BER, &v) != -1 && v != tdmi->tdmi_ber) {
|
||||
tdmi->tdmi_ber = v;
|
||||
notify = 1;
|
||||
}
|
||||
|
||||
/* signal strength */
|
||||
if(ioctl(tda->tda_fe_fd, FE_READ_SIGNAL_STRENGTH, &tdmi->tdmi_signal) == -1)
|
||||
tdmi->tdmi_signal = -2;
|
||||
if(ioctl(tda->tda_fe_fd, FE_READ_SIGNAL_STRENGTH, &v) != -1 && v != tdmi->tdmi_signal) {
|
||||
tdmi->tdmi_signal = v;
|
||||
notify = 1;
|
||||
}
|
||||
|
||||
/* signal/noise ratio */
|
||||
if(ioctl(tda->tda_fe_fd, FE_READ_SNR, &tdmi->tdmi_snr) == -1)
|
||||
tdmi->tdmi_snr = -2;
|
||||
if(tda->tda_snr_valid) {
|
||||
if(ioctl(tda->tda_fe_fd, FE_READ_SNR, &v) != -1) {
|
||||
float snr = v / 10.0;
|
||||
if(tdmi->tdmi_snr != snr) {
|
||||
tdmi->tdmi_snr = snr;
|
||||
notify = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(status != tdmi->tdmi_fe_status) {
|
||||
tdmi->tdmi_fe_status = status;
|
||||
|
||||
dvb_mux_nicename(buf, sizeof(buf), tdmi);
|
||||
tvhlog(LOG_DEBUG,
|
||||
tvhlog(LOG_DEBUG,
|
||||
"dvb", "\"%s\" on adapter \"%s\", status changed to %s",
|
||||
buf, tda->tda_displayname, dvb_mux_status(tdmi));
|
||||
update = 1;
|
||||
store = 1;
|
||||
notify = 1;
|
||||
}
|
||||
|
||||
if(status != TDMI_FE_UNKNOWN) {
|
||||
|
@ -186,26 +241,46 @@ dvb_fe_monitor(void *aux)
|
|||
}
|
||||
if(q != tdmi->tdmi_quality) {
|
||||
tdmi->tdmi_quality = q;
|
||||
update = 1;
|
||||
store = 1;
|
||||
notify = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(update) {
|
||||
bw = atomic_exchange(&tda->tda_bytes, 0);
|
||||
|
||||
if(notify) {
|
||||
htsmsg_t *m = htsmsg_create_map();
|
||||
|
||||
htsmsg_add_str(m, "id", tdmi->tdmi_identifier);
|
||||
htsmsg_add_u32(m, "quality", tdmi->tdmi_quality);
|
||||
htsmsg_add_u32(m, "signal", tdmi->tdmi_signal);
|
||||
|
||||
if(tda->tda_snr_valid)
|
||||
htsmsg_add_dbl(m, "snr", tdmi->tdmi_snr);
|
||||
htsmsg_add_u32(m, "ber", tdmi->tdmi_ber);
|
||||
htsmsg_add_u32(m, "unc", tdmi->tdmi_unc);
|
||||
notify_by_msg("dvbMux", m);
|
||||
|
||||
dvb_mux_save(tdmi);
|
||||
m = htsmsg_create_map();
|
||||
htsmsg_add_str(m, "identifier", tda->tda_identifier);
|
||||
htsmsg_add_u32(m, "signal", MIN(tdmi->tdmi_signal * 100 / 65535, 100));
|
||||
if(tda->tda_snr_valid)
|
||||
htsmsg_add_dbl(m, "snr", tdmi->tdmi_snr);
|
||||
htsmsg_add_u32(m, "ber", tdmi->tdmi_ber);
|
||||
htsmsg_add_u32(m, "unc", tdmi->tdmi_unc);
|
||||
htsmsg_add_dbl(m, "uncavg", tdmi->tdmi_unc_avg);
|
||||
htsmsg_add_u32(m, "bw", bw);
|
||||
notify_by_msg("tvAdapter", m);
|
||||
}
|
||||
|
||||
if(store)
|
||||
dvb_mux_save(tdmi);
|
||||
|
||||
/* Streaming message */
|
||||
sigstat.status_text = dvb_mux_status(tdmi);
|
||||
sigstat.snr = tdmi->tdmi_snr;
|
||||
sigstat.signal = tdmi->tdmi_signal;
|
||||
sigstat.ber = tdmi->tdmi_ber;
|
||||
sigstat.unc = tdmi->tdmi_uncorrected_blocks;
|
||||
sigstat.unc = tdmi->tdmi_unc;
|
||||
sm.sm_type = SMT_SIGNAL_STATUS;
|
||||
sm.sm_data = &sigstat;
|
||||
LIST_FOREACH(t, &tda->tda_transports, s_active_link)
|
||||
|
@ -225,27 +300,26 @@ void
|
|||
dvb_fe_stop(th_dvb_mux_instance_t *tdmi, int retune)
|
||||
{
|
||||
th_dvb_adapter_t *tda = tdmi->tdmi_adapter;
|
||||
dvb_table_feed_t *dtf;
|
||||
|
||||
lock_assert(&global_lock);
|
||||
|
||||
assert(tdmi == tda->tda_mux_current);
|
||||
tda->tda_mux_current = NULL;
|
||||
|
||||
if(tda->tda_allpids_dmx_fd != -1) {
|
||||
close(tda->tda_allpids_dmx_fd);
|
||||
tda->tda_allpids_dmx_fd = -1;
|
||||
}
|
||||
|
||||
if(tda->tda_dump_fd != -1) {
|
||||
close(tda->tda_dump_fd);
|
||||
tda->tda_dump_fd = -1;
|
||||
}
|
||||
|
||||
if(tdmi->tdmi_table_initial) {
|
||||
tdmi->tdmi_table_initial = 0;
|
||||
tda->tda_initial_num_mux--;
|
||||
dvb_mux_save(tdmi);
|
||||
}
|
||||
|
||||
dvb_adapter_stop(tda, TDA_OPT_DVR);
|
||||
pthread_mutex_lock(&tda->tda_delivery_mutex);
|
||||
while((dtf = TAILQ_FIRST(&tda->tda_table_feed)))
|
||||
TAILQ_REMOVE(&tda->tda_table_feed, dtf, dtf_link);
|
||||
pthread_mutex_unlock(&tda->tda_delivery_mutex);
|
||||
dvb_table_flush_all(tdmi);
|
||||
tda->tda_locked = 0;
|
||||
|
||||
assert(tdmi->tdmi_scan_queue == NULL);
|
||||
|
||||
|
@ -259,125 +333,12 @@ dvb_fe_stop(th_dvb_mux_instance_t *tdmi, int retune)
|
|||
|
||||
if (!retune) {
|
||||
gtimer_disarm(&tda->tda_fe_monitor_timer);
|
||||
dvb_adapter_stop(tda);
|
||||
dvb_adapter_stop(tda, TDA_OPT_ALL);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Open a dump file which we write the entire mux output to
|
||||
*/
|
||||
static void
|
||||
dvb_adapter_open_dump_file(th_dvb_adapter_t *tda)
|
||||
{
|
||||
struct dmx_pes_filter_params dmx_param;
|
||||
char fullname[1000];
|
||||
char path[500];
|
||||
const char *fname = tda->tda_mux_current->tdmi_identifier;
|
||||
|
||||
int fd = tvh_open(tda->tda_demux_path, O_RDWR, 0);
|
||||
if(fd == -1)
|
||||
return;
|
||||
|
||||
memset(&dmx_param, 0, sizeof(dmx_param));
|
||||
dmx_param.pid = 0x2000;
|
||||
dmx_param.input = DMX_IN_FRONTEND;
|
||||
dmx_param.output = DMX_OUT_TS_TAP;
|
||||
dmx_param.pes_type = DMX_PES_OTHER;
|
||||
dmx_param.flags = DMX_IMMEDIATE_START;
|
||||
|
||||
if(ioctl(fd, DMX_SET_PES_FILTER, &dmx_param)) {
|
||||
tvhlog(LOG_ERR, "dvb",
|
||||
"\"%s\" unable to configure demuxer \"%s\" for all PIDs -- %s",
|
||||
fname, tda->tda_demux_path,
|
||||
strerror(errno));
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
snprintf(path, sizeof(path), "%s/muxdumps",
|
||||
dvr_config_find_by_name_default("")->dvr_storage);
|
||||
|
||||
if(mkdir(path, 0777) && errno != EEXIST) {
|
||||
tvhlog(LOG_ERR, "dvb", "\"%s\" unable to create mux dump dir %s -- %s",
|
||||
fname, path, strerror(errno));
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
int attempt = 1;
|
||||
|
||||
while(1) {
|
||||
struct stat st;
|
||||
snprintf(fullname, sizeof(fullname), "%s/%s.dump%d.ts",
|
||||
path, fname, attempt);
|
||||
|
||||
if(stat(fullname, &st) == -1)
|
||||
break;
|
||||
|
||||
attempt++;
|
||||
}
|
||||
|
||||
int f = open(fullname, O_CREAT | O_TRUNC | O_WRONLY, 0777);
|
||||
|
||||
if(f == -1) {
|
||||
tvhlog(LOG_ERR, "dvb", "\"%s\" unable to create mux dump file %s -- %s",
|
||||
fname, fullname, strerror(errno));
|
||||
close(fd);
|
||||
return;
|
||||
}
|
||||
|
||||
tvhlog(LOG_WARNING, "dvb", "\"%s\" writing to mux dump file %s",
|
||||
fname, fullname);
|
||||
|
||||
tda->tda_allpids_dmx_fd = fd;
|
||||
tda->tda_dump_fd = f;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#if DVB_API_VERSION >= 5
|
||||
|
||||
static int check_frontend (int fe_fd, int dvr, int human_readable) {
|
||||
(void)dvr;
|
||||
fe_status_t status;
|
||||
uint16_t snr, signal;
|
||||
uint32_t ber;
|
||||
int timeout = 0;
|
||||
|
||||
do {
|
||||
if (ioctl(fe_fd, FE_READ_STATUS, &status) == -1)
|
||||
perror("FE_READ_STATUS failed");
|
||||
/* some frontends might not support all these ioctls, thus we
|
||||
* avoid printing errors
|
||||
*/
|
||||
if (ioctl(fe_fd, FE_READ_SIGNAL_STRENGTH, &signal) == -1)
|
||||
signal = -2;
|
||||
if (ioctl(fe_fd, FE_READ_SNR, &snr) == -1)
|
||||
snr = -2;
|
||||
if (ioctl(fe_fd, FE_READ_BER, &ber) == -1)
|
||||
ber = -2;
|
||||
|
||||
if (human_readable) {
|
||||
printf ("status %02x | signal %3u%% | snr %3u%% | ber %d | ",
|
||||
status, (signal * 100) / 0xffff, (snr * 100) / 0xffff, ber);
|
||||
} else {
|
||||
printf ("status %02x | signal %04x | snr %04x | ber %08x | ",
|
||||
status, signal, snr, ber);
|
||||
}
|
||||
if (status & FE_HAS_LOCK)
|
||||
printf("FE_HAS_LOCK");
|
||||
printf("\n");
|
||||
|
||||
if ((status & FE_HAS_LOCK) || (++timeout >= 10))
|
||||
break;
|
||||
|
||||
usleep(1000000);
|
||||
} while (1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct dtv_property clear_p[] = {
|
||||
{ .cmd = DTV_CLEAR },
|
||||
};
|
||||
|
@ -387,7 +348,6 @@ static struct dtv_properties clear_cmdseq = {
|
|||
.props = clear_p
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -430,8 +390,6 @@ dvb_fe_tune_s2(th_dvb_mux_instance_t *tdmi, dvb_mux_conf_t *dmc)
|
|||
/* do tuning now */
|
||||
r = ioctl(tda->tda_fe_fd, FE_SET_PROPERTY, &_dvbs_cmdseq);
|
||||
|
||||
if(0)
|
||||
check_frontend (tda->tda_fe_fd, 0, 1);
|
||||
return r;
|
||||
|
||||
}
|
||||
|
@ -453,9 +411,11 @@ dvb_fe_tune(th_dvb_mux_instance_t *tdmi, const char *reason)
|
|||
char buf[256];
|
||||
int r;
|
||||
|
||||
|
||||
lock_assert(&global_lock);
|
||||
|
||||
if(tda->tda_enabled == 0)
|
||||
return SM_CODE_TUNING_FAILED;
|
||||
|
||||
if(tda->tda_mux_current == tdmi)
|
||||
return 0;
|
||||
|
||||
|
@ -466,8 +426,8 @@ dvb_fe_tune(th_dvb_mux_instance_t *tdmi, const char *reason)
|
|||
|
||||
if(tda->tda_mux_current != NULL)
|
||||
dvb_fe_stop(tda->tda_mux_current, 1);
|
||||
else
|
||||
dvb_adapter_start(tda);
|
||||
|
||||
dvb_adapter_start(tda, TDA_OPT_FE | TDA_OPT_PWR);
|
||||
|
||||
if(tda->tda_type == FE_QPSK) {
|
||||
|
||||
|
@ -507,19 +467,16 @@ dvb_fe_tune(th_dvb_mux_instance_t *tdmi, const char *reason)
|
|||
p->frequency = abs(p->frequency - lowfreq);
|
||||
}
|
||||
|
||||
if ((r = diseqc_setup(tda->tda_fe_fd,
|
||||
port,
|
||||
pol == POLARISATION_HORIZONTAL ||
|
||||
pol == POLARISATION_CIRCULAR_LEFT,
|
||||
hiband, tda->tda_diseqc_version)) != 0)
|
||||
if ((r = diseqc_setup(tda->tda_fe_fd, port,
|
||||
pol == POLARISATION_HORIZONTAL ||
|
||||
pol == POLARISATION_CIRCULAR_LEFT,
|
||||
hiband, tda->tda_diseqc_version,
|
||||
tda->tda_diseqc_repeats)) != 0)
|
||||
tvhlog(LOG_ERR, "dvb", "diseqc setup failed %d\n", r);
|
||||
}
|
||||
}
|
||||
|
||||
dvb_mux_nicename(buf, sizeof(buf), tdmi);
|
||||
|
||||
tda->tda_fe_monitor_hold = 4;
|
||||
|
||||
|
||||
#if DVB_API_VERSION >= 5
|
||||
if (tda->tda_type == FE_QPSK) {
|
||||
tvhlog(LOG_DEBUG, "dvb", "\"%s\" tuning via s2api to \"%s\" (%d, %d Baud, "
|
||||
|
@ -547,20 +504,18 @@ dvb_fe_tune(th_dvb_mux_instance_t *tdmi, const char *reason)
|
|||
}
|
||||
|
||||
/* Mark as bad */
|
||||
dvb_mux_set_enable(tdmi, 0);
|
||||
if (errno == EINVAL)
|
||||
dvb_mux_set_enable(tdmi, 0);
|
||||
|
||||
dvb_adapter_stop(tda, TDA_OPT_ALL);
|
||||
return SM_CODE_TUNING_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
tda->tda_mux_current = tdmi;
|
||||
|
||||
if(tda->tda_dump_muxes)
|
||||
dvb_adapter_open_dump_file(tda);
|
||||
|
||||
gtimer_arm(&tda->tda_fe_monitor_timer, dvb_fe_monitor, tda, 1);
|
||||
|
||||
|
||||
dvb_table_add_default(tdmi);
|
||||
epggrab_mux_start(tdmi);
|
||||
time(&tda->tda_monitor);
|
||||
tda->tda_monitor += 4; // wait a few secs before monitoring (unlocked)
|
||||
gtimer_arm_ms(&tda->tda_fe_monitor_timer, dvb_fe_monitor, tda, 50);
|
||||
|
||||
dvb_adapter_notify(tda);
|
||||
return 0;
|
||||
|
|
259
src/dvb/dvb_input_filtered.c
Normal file
259
src/dvb/dvb_input_filtered.c
Normal file
|
@ -0,0 +1,259 @@
|
|||
/*
|
||||
* TV Input - Linux DVB interface
|
||||
* Copyright (C) 2012 Andreas Öman
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* DVB input using hardware filters
|
||||
*/
|
||||
#include <assert.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/dvb/frontend.h>
|
||||
#include <linux/dvb/dmx.h>
|
||||
#include <sys/epoll.h>
|
||||
|
||||
#include "tvheadend.h"
|
||||
#include "dvb.h"
|
||||
#include "service.h"
|
||||
|
||||
/**
|
||||
* Install filters for a service
|
||||
*
|
||||
* global_lock must be held
|
||||
*/
|
||||
static void
|
||||
open_service(th_dvb_adapter_t *tda, service_t *s)
|
||||
{
|
||||
struct dmx_pes_filter_params dmx_param;
|
||||
int fd;
|
||||
elementary_stream_t *st;
|
||||
|
||||
TAILQ_FOREACH(st, &s->s_components, es_link) {
|
||||
if(st->es_pid >= 0x2000)
|
||||
continue;
|
||||
|
||||
if(st->es_demuxer_fd != -1)
|
||||
continue;
|
||||
|
||||
fd = tvh_open(tda->tda_demux_path, O_RDWR, 0);
|
||||
st->es_cc_valid = 0;
|
||||
|
||||
if(fd == -1) {
|
||||
st->es_demuxer_fd = -1;
|
||||
tvhlog(LOG_ERR, "dvb",
|
||||
"\"%s\" unable to open demuxer \"%s\" for pid %d -- %s",
|
||||
s->s_identifier, tda->tda_demux_path,
|
||||
st->es_pid, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
|
||||
memset(&dmx_param, 0, sizeof(dmx_param));
|
||||
dmx_param.pid = st->es_pid;
|
||||
dmx_param.input = DMX_IN_FRONTEND;
|
||||
dmx_param.output = DMX_OUT_TS_TAP;
|
||||
dmx_param.pes_type = DMX_PES_OTHER;
|
||||
dmx_param.flags = DMX_IMMEDIATE_START;
|
||||
|
||||
if(ioctl(fd, DMX_SET_PES_FILTER, &dmx_param)) {
|
||||
tvhlog(LOG_ERR, "dvb",
|
||||
"\"%s\" unable to configure demuxer \"%s\" for pid %d -- %s",
|
||||
s->s_identifier, tda->tda_demux_path,
|
||||
st->es_pid, strerror(errno));
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
|
||||
st->es_demuxer_fd = fd;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove filters for a service
|
||||
*
|
||||
* global_lock must be held
|
||||
*/
|
||||
static void
|
||||
close_service(th_dvb_adapter_t *tda, service_t *s)
|
||||
{
|
||||
elementary_stream_t *es;
|
||||
|
||||
TAILQ_FOREACH(es, &s->s_components, es_link) {
|
||||
if(es->es_demuxer_fd != -1) {
|
||||
close(es->es_demuxer_fd);
|
||||
es->es_demuxer_fd = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
open_table(th_dvb_mux_instance_t *tdmi, th_dvb_table_t *tdt)
|
||||
{
|
||||
th_dvb_adapter_t *tda = tdmi->tdmi_adapter;
|
||||
struct epoll_event e;
|
||||
static int tdt_id_tally;
|
||||
|
||||
tdt->tdt_fd = tvh_open(tda->tda_demux_path, O_RDWR, 0);
|
||||
|
||||
if(tdt->tdt_fd != -1) {
|
||||
|
||||
tdt->tdt_id = ++tdt_id_tally;
|
||||
|
||||
e.events = EPOLLIN;
|
||||
e.data.u64 = ((uint64_t)tdt->tdt_fd << 32) | tdt->tdt_id;
|
||||
|
||||
if(epoll_ctl(tda->tda_table_epollfd, EPOLL_CTL_ADD, tdt->tdt_fd, &e)) {
|
||||
close(tdt->tdt_fd);
|
||||
tdt->tdt_fd = -1;
|
||||
} else {
|
||||
|
||||
struct dmx_sct_filter_params fp = {0};
|
||||
|
||||
fp.filter.filter[0] = tdt->tdt_table;
|
||||
fp.filter.mask[0] = tdt->tdt_mask;
|
||||
|
||||
if(tdt->tdt_flags & TDT_CRC)
|
||||
fp.flags |= DMX_CHECK_CRC;
|
||||
fp.flags |= DMX_IMMEDIATE_START;
|
||||
fp.pid = tdt->tdt_pid;
|
||||
|
||||
if(ioctl(tdt->tdt_fd, DMX_SET_FILTER, &fp)) {
|
||||
close(tdt->tdt_fd);
|
||||
tdt->tdt_fd = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(tdt->tdt_fd == -1)
|
||||
TAILQ_INSERT_TAIL(&tdmi->tdmi_table_queue, tdt, tdt_pending_link);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Close FD for the given table and put table on the pending list
|
||||
*/
|
||||
static void
|
||||
tdt_close_fd(th_dvb_mux_instance_t *tdmi, th_dvb_table_t *tdt)
|
||||
{
|
||||
th_dvb_adapter_t *tda = tdmi->tdmi_adapter;
|
||||
|
||||
assert(tdt->tdt_fd != -1);
|
||||
|
||||
epoll_ctl(tda->tda_table_epollfd, EPOLL_CTL_DEL, tdt->tdt_fd, NULL);
|
||||
close(tdt->tdt_fd);
|
||||
|
||||
tdt->tdt_fd = -1;
|
||||
TAILQ_INSERT_TAIL(&tdmi->tdmi_table_queue, tdt, tdt_pending_link);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void *
|
||||
dvb_table_input(void *aux)
|
||||
{
|
||||
th_dvb_adapter_t *tda = aux;
|
||||
int r, i, tid, fd, x;
|
||||
struct epoll_event ev[1];
|
||||
uint8_t sec[4096];
|
||||
th_dvb_mux_instance_t *tdmi;
|
||||
th_dvb_table_t *tdt;
|
||||
int64_t cycle_barrier = 0;
|
||||
|
||||
while(1) {
|
||||
x = epoll_wait(tda->tda_table_epollfd, ev, sizeof(ev) / sizeof(ev[0]), -1);
|
||||
|
||||
for(i = 0; i < x; i++) {
|
||||
|
||||
tid = ev[i].data.u64 & 0xffffffff;
|
||||
fd = ev[i].data.u64 >> 32;
|
||||
|
||||
if(!(ev[i].events & EPOLLIN))
|
||||
continue;
|
||||
|
||||
if((r = read(fd, sec, sizeof(sec))) < 3)
|
||||
continue;
|
||||
|
||||
pthread_mutex_lock(&global_lock);
|
||||
if((tdmi = tda->tda_mux_current) != NULL) {
|
||||
LIST_FOREACH(tdt, &tdmi->tdmi_tables, tdt_link)
|
||||
if(tdt->tdt_id == tid)
|
||||
break;
|
||||
|
||||
if(tdt != NULL) {
|
||||
dvb_table_dispatch(sec, r, tdt);
|
||||
|
||||
/* Any tables pending (that wants a filter/fd), close this one */
|
||||
if(TAILQ_FIRST(&tdmi->tdmi_table_queue) != NULL &&
|
||||
cycle_barrier < getmonoclock()) {
|
||||
tdt_close_fd(tdmi, tdt);
|
||||
cycle_barrier = getmonoclock() + 100000;
|
||||
tdt = TAILQ_FIRST(&tdmi->tdmi_table_queue);
|
||||
assert(tdt != NULL);
|
||||
TAILQ_REMOVE(&tdmi->tdmi_table_queue, tdt, tdt_pending_link);
|
||||
|
||||
open_table(tdmi, tdt);
|
||||
}
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
close_table(th_dvb_mux_instance_t *tdmi, th_dvb_table_t *tdt)
|
||||
{
|
||||
th_dvb_adapter_t *tda = tdmi->tdmi_adapter;
|
||||
|
||||
if(tdt->tdt_fd == -1) {
|
||||
TAILQ_REMOVE(&tdmi->tdmi_table_queue, tdt, tdt_pending_link);
|
||||
} else {
|
||||
epoll_ctl(tda->tda_table_epollfd, EPOLL_CTL_DEL, tdt->tdt_fd, NULL);
|
||||
close(tdt->tdt_fd);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
dvb_input_filtered_setup(th_dvb_adapter_t *tda)
|
||||
{
|
||||
tda->tda_open_service = open_service;
|
||||
tda->tda_close_service = close_service;
|
||||
tda->tda_open_table = open_table;
|
||||
tda->tda_close_table = close_table;
|
||||
|
||||
pthread_t ptid;
|
||||
tda->tda_table_epollfd = epoll_create(50);
|
||||
pthread_create(&ptid, NULL, dvb_table_input, tda);
|
||||
}
|
||||
|
176
src/dvb/dvb_input_raw.c
Normal file
176
src/dvb/dvb_input_raw.c
Normal file
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* TV Input - Linux DVB interface
|
||||
* Copyright (C) 2012 Andreas Öman
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* DVB input from a raw transport stream
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/dvb/frontend.h>
|
||||
#include <linux/dvb/dmx.h>
|
||||
#include <sys/epoll.h>
|
||||
|
||||
#include "tvheadend.h"
|
||||
#include "dvb.h"
|
||||
#include "service.h"
|
||||
|
||||
/**
|
||||
* Install filters for a service
|
||||
*
|
||||
* global_lock must be held
|
||||
*/
|
||||
static void
|
||||
open_service(th_dvb_adapter_t *tda, service_t *s)
|
||||
{
|
||||
// NOP -- We receive all PIDs anyway
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove filters for a service
|
||||
*
|
||||
* global_lock must be held
|
||||
*/
|
||||
static void
|
||||
close_service(th_dvb_adapter_t *tda, service_t *s)
|
||||
{
|
||||
// NOP -- open_service() is a NOP
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
open_table(th_dvb_mux_instance_t *tdmi, th_dvb_table_t *tdt)
|
||||
{
|
||||
th_dvb_adapter_t *tda = tdmi->tdmi_adapter;
|
||||
assert(tdt->tdt_pid < 0x2000);
|
||||
tda->tda_table_filter[tdt->tdt_pid] = 1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
close_table(th_dvb_mux_instance_t *tdmi, th_dvb_table_t *tdt)
|
||||
{
|
||||
th_dvb_adapter_t *tda = tdmi->tdmi_adapter;
|
||||
assert(tdt->tdt_pid < 0x2000);
|
||||
tda->tda_table_filter[tdt->tdt_pid] = 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
got_section(const uint8_t *data, size_t len, void *opaque)
|
||||
{
|
||||
th_dvb_table_t *tdt = opaque;
|
||||
dvb_table_dispatch((uint8_t *)data, len, tdt);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* All the tables can be destroyed from any of the callbacks
|
||||
* so we need to be a bit careful here
|
||||
*/
|
||||
static void
|
||||
dvb_table_raw_dispatch(th_dvb_mux_instance_t *tdmi,
|
||||
const dvb_table_feed_t *dtf)
|
||||
{
|
||||
int pid = (dtf->dtf_tsb[1] & 0x1f) << 8 | dtf->dtf_tsb[2];
|
||||
th_dvb_table_t *vec[tdmi->tdmi_num_tables], *tdt;
|
||||
int i = 0;
|
||||
LIST_FOREACH(tdt, &tdmi->tdmi_tables, tdt_link) {
|
||||
vec[i++] = tdt;
|
||||
tdt->tdt_refcount++;
|
||||
}
|
||||
assert(i == tdmi->tdmi_num_tables);
|
||||
int len = tdmi->tdmi_num_tables; // can change during callbacks
|
||||
for(i = 0; i < len; i++) {
|
||||
tdt = vec[i];
|
||||
if(!tdt->tdt_destroyed) {
|
||||
if(tdt->tdt_pid == pid)
|
||||
psi_section_reassemble(&tdt->tdt_sect, dtf->dtf_tsb,
|
||||
0, got_section, tdt);
|
||||
}
|
||||
dvb_table_release(tdt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void *
|
||||
dvb_table_input(void *aux)
|
||||
{
|
||||
th_dvb_adapter_t *tda = aux;
|
||||
th_dvb_mux_instance_t *tdmi;
|
||||
dvb_table_feed_t *dtf;
|
||||
|
||||
while(1) {
|
||||
|
||||
pthread_mutex_lock(&tda->tda_delivery_mutex);
|
||||
|
||||
while((dtf = TAILQ_FIRST(&tda->tda_table_feed)) == NULL)
|
||||
pthread_cond_wait(&tda->tda_table_feed_cond, &tda->tda_delivery_mutex);
|
||||
TAILQ_REMOVE(&tda->tda_table_feed, dtf, dtf_link);
|
||||
|
||||
pthread_mutex_unlock(&tda->tda_delivery_mutex);
|
||||
|
||||
pthread_mutex_lock(&global_lock);
|
||||
|
||||
if((tdmi = tda->tda_mux_current) != NULL)
|
||||
dvb_table_raw_dispatch(tdmi, dtf);
|
||||
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
free(dtf);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
dvb_input_raw_setup(th_dvb_adapter_t *tda)
|
||||
{
|
||||
tda->tda_rawmode = 1;
|
||||
tda->tda_open_service = open_service;
|
||||
tda->tda_close_service = close_service;
|
||||
tda->tda_open_table = open_table;
|
||||
tda->tda_close_table = close_table;
|
||||
|
||||
TAILQ_INIT(&tda->tda_table_feed);
|
||||
pthread_cond_init(&tda->tda_table_feed_cond, NULL);
|
||||
|
||||
pthread_t ptid;
|
||||
pthread_create(&ptid, NULL, dvb_table_input, tda);
|
||||
|
||||
}
|
||||
|
|
@ -58,9 +58,6 @@ static struct strtab muxfestatustab[] = {
|
|||
{ "OK", TDMI_FE_OK },
|
||||
};
|
||||
|
||||
static void tdmi_set_enable(th_dvb_mux_instance_t *tdmi, int enabled);
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -102,14 +99,17 @@ tdmi_global_cmp(th_dvb_mux_instance_t *a, th_dvb_mux_instance_t *b)
|
|||
*/
|
||||
static int
|
||||
tdmi_compare_key(const struct dvb_mux_conf *a,
|
||||
const struct dvb_mux_conf *b)
|
||||
const struct dvb_mux_conf *b,
|
||||
const dvb_satconf_t *satconf)
|
||||
{
|
||||
int32_t fd = (int32_t)a->dmc_fe_params.frequency
|
||||
- (int32_t)b->dmc_fe_params.frequency;
|
||||
if (!satconf)
|
||||
satconf = b->dmc_satconf;
|
||||
fd = labs(fd);
|
||||
return fd < 2000 &&
|
||||
a->dmc_polarisation == b->dmc_polarisation &&
|
||||
a->dmc_satconf == b->dmc_satconf;
|
||||
a->dmc_satconf == satconf;
|
||||
}
|
||||
|
||||
|
||||
|
@ -155,26 +155,59 @@ th_dvb_mux_instance_t *
|
|||
dvb_mux_create(th_dvb_adapter_t *tda, const struct dvb_mux_conf *dmc,
|
||||
uint16_t onid, uint16_t tsid, const char *network, const char *source,
|
||||
int enabled, int initialscan, const char *identifier,
|
||||
dvb_satconf_t *satconf)
|
||||
dvb_satconf_t *satconf, int create, th_dvb_mux_instance_t *src)
|
||||
{
|
||||
th_dvb_mux_instance_t *tdmi, *c;
|
||||
char buf[200];
|
||||
|
||||
lock_assert(&global_lock);
|
||||
|
||||
if (!satconf)
|
||||
satconf = dmc->dmc_satconf;
|
||||
|
||||
/* HACK - we hash/compare based on 2KHz spacing and compare on +/-500Hz */
|
||||
LIST_FOREACH(tdmi, &tda->tda_mux_list, tdmi_adapter_hash_link) {
|
||||
if(tdmi_compare_key(&tdmi->tdmi_conf, dmc))
|
||||
if(tdmi_compare_key(&tdmi->tdmi_conf, dmc, satconf))
|
||||
break; /* Mux already exist */
|
||||
}
|
||||
|
||||
if(tdmi != NULL) {
|
||||
|
||||
/* Update stuff ... */
|
||||
int save = 0;
|
||||
char buf2[1024];
|
||||
buf2[0] = 0;
|
||||
int master = 0;
|
||||
if (!src)
|
||||
master = 1;
|
||||
else if (src->tdmi_network_id == tdmi->tdmi_network_id)
|
||||
master = 1;
|
||||
|
||||
if(tdmi_compare_conf(tda->tda_type, &tdmi->tdmi_conf, dmc)) {
|
||||
/* Network ID */
|
||||
if(tsid != 0xFFFF && tdmi->tdmi_transport_stream_id != tsid) {
|
||||
if (tdmi->tdmi_transport_stream_id == 0xFFFF || master) {
|
||||
tdmi->tdmi_transport_stream_id = tsid;
|
||||
save = 1;
|
||||
}
|
||||
}
|
||||
if(onid && tdmi->tdmi_network_id != onid) {
|
||||
if (!tdmi->tdmi_network_id || master) {
|
||||
tdmi->tdmi_network_id = onid;
|
||||
save = 1;
|
||||
}
|
||||
}
|
||||
if(network && *network && strcmp(tdmi->tdmi_network ?: "", network)) {
|
||||
if (!tdmi->tdmi_network || master) {
|
||||
free(tdmi->tdmi_network);
|
||||
tdmi->tdmi_network = strdup(network);
|
||||
save = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tuning Info */
|
||||
// TODO: same protection here?
|
||||
if(tdmi->tdmi_adapter->tda_autodiscovery &&
|
||||
tdmi_compare_conf(tda->tda_type, &tdmi->tdmi_conf, dmc)) {
|
||||
#if DVB_API_VERSION >= 5
|
||||
snprintf(buf2, sizeof(buf2), " (");
|
||||
if (tdmi->tdmi_conf.dmc_fe_modulation != dmc->dmc_fe_modulation)
|
||||
|
@ -196,19 +229,10 @@ dvb_mux_create(th_dvb_adapter_t *tda, const struct dvb_mux_conf *dmc,
|
|||
save = 1;
|
||||
}
|
||||
|
||||
if(tsid != 0xFFFF && tdmi->tdmi_transport_stream_id != tsid) {
|
||||
tdmi->tdmi_transport_stream_id = tsid;
|
||||
save = 1;
|
||||
}
|
||||
if(onid && tdmi->tdmi_network_id != onid) {
|
||||
tdmi->tdmi_network_id = onid;
|
||||
save = 1;
|
||||
}
|
||||
|
||||
/* HACK - load old transports and remove old mux config */
|
||||
if(identifier) {
|
||||
save = 1;
|
||||
dvb_transport_load(tdmi, identifier);
|
||||
dvb_service_load(tdmi, identifier);
|
||||
hts_settings_remove("dvbmuxes/%s/%s",
|
||||
tda->tda_identifier, identifier);
|
||||
}
|
||||
|
@ -225,6 +249,9 @@ dvb_mux_create(th_dvb_adapter_t *tda, const struct dvb_mux_conf *dmc,
|
|||
return NULL;
|
||||
}
|
||||
|
||||
if (!create)
|
||||
return NULL;
|
||||
|
||||
tdmi = calloc(1, sizeof(th_dvb_mux_instance_t));
|
||||
|
||||
if(identifier == NULL) {
|
||||
|
@ -238,9 +265,8 @@ dvb_mux_create(th_dvb_adapter_t *tda, const struct dvb_mux_conf *dmc,
|
|||
|
||||
snprintf(buf, sizeof(buf), "%s%d%s%s%s",
|
||||
tda->tda_identifier, dmc->dmc_fe_params.frequency, qpsktxt,
|
||||
(satconf || dmc->dmc_satconf) ? "_satconf_" : "",
|
||||
(satconf ? satconf->sc_id :
|
||||
(dmc->dmc_satconf ? dmc->dmc_satconf->sc_id : "")));
|
||||
satconf ? "_satconf_" : "",
|
||||
satconf ? satconf->sc_id : "");
|
||||
|
||||
tdmi->tdmi_identifier = strdup(buf);
|
||||
} else {
|
||||
|
@ -288,19 +314,21 @@ dvb_mux_create(th_dvb_adapter_t *tda, const struct dvb_mux_conf *dmc,
|
|||
tvhlog(LOG_NOTICE, "dvb", "New mux \"%s\" created by %s", buf, source);
|
||||
|
||||
dvb_mux_save(tdmi);
|
||||
dvb_adapter_notify(tda);
|
||||
}
|
||||
|
||||
dvb_transport_load(tdmi, identifier);
|
||||
dvb_service_load(tdmi, identifier);
|
||||
dvb_mux_notify(tdmi);
|
||||
|
||||
if(enabled && initialscan) {
|
||||
tda->tda_initial_num_mux++;
|
||||
tdmi->tdmi_table_initial = 1;
|
||||
mux_link_initial(tda, tdmi);
|
||||
} else {
|
||||
dvb_mux_add_to_scan_queue(tdmi);
|
||||
if(enabled) {
|
||||
if(initialscan) {
|
||||
tda->tda_initial_num_mux++;
|
||||
tdmi->tdmi_table_initial = 1;
|
||||
mux_link_initial(tda, tdmi);
|
||||
} else {
|
||||
dvb_mux_add_to_scan_queue(tdmi);
|
||||
}
|
||||
}
|
||||
dvb_adapter_notify(tda);
|
||||
|
||||
return tdmi;
|
||||
}
|
||||
|
@ -326,7 +354,7 @@ dvb_mux_destroy(th_dvb_mux_instance_t *tdmi)
|
|||
service_destroy(t);
|
||||
}
|
||||
|
||||
dvb_transport_notify_by_adapter(tda);
|
||||
dvb_service_notify_by_adapter(tda);
|
||||
|
||||
if(tda->tda_mux_current == tdmi)
|
||||
dvb_fe_stop(tda->tda_mux_current, 0);
|
||||
|
@ -789,7 +817,7 @@ tdmi_create_by_msg(th_dvb_adapter_t *tda, htsmsg_t *m, const char *identifier)
|
|||
tdmi = dvb_mux_create(tda, &dmc,
|
||||
onid, tsid, htsmsg_get_str(m, "network"), NULL, enabled,
|
||||
initscan,
|
||||
identifier, NULL);
|
||||
identifier, NULL, 1, NULL);
|
||||
if(tdmi != NULL) {
|
||||
|
||||
if((s = htsmsg_get_str(m, "status")) != NULL)
|
||||
|
@ -836,6 +864,12 @@ dvb_mux_set_networkname(th_dvb_mux_instance_t *tdmi, const char *networkname)
|
|||
{
|
||||
htsmsg_t *m;
|
||||
|
||||
if (!networkname || !*networkname)
|
||||
return;
|
||||
|
||||
if (!strcmp(tdmi->tdmi_network ?: "", networkname))
|
||||
return;
|
||||
|
||||
free(tdmi->tdmi_network);
|
||||
tdmi->tdmi_network = strdup(networkname);
|
||||
dvb_mux_save(tdmi);
|
||||
|
@ -851,12 +885,19 @@ dvb_mux_set_networkname(th_dvb_mux_instance_t *tdmi, const char *networkname)
|
|||
*
|
||||
*/
|
||||
void
|
||||
dvb_mux_set_tsid(th_dvb_mux_instance_t *tdmi, uint16_t tsid)
|
||||
dvb_mux_set_tsid(th_dvb_mux_instance_t *tdmi, uint16_t tsid, int force)
|
||||
{
|
||||
htsmsg_t *m;
|
||||
|
||||
if (!force)
|
||||
if (tdmi->tdmi_transport_stream_id != 0xFFFF || tsid == 0xFFFF)
|
||||
return;
|
||||
|
||||
if (tdmi->tdmi_transport_stream_id == tsid)
|
||||
return;
|
||||
|
||||
tdmi->tdmi_transport_stream_id = tsid;
|
||||
|
||||
|
||||
dvb_mux_save(tdmi);
|
||||
|
||||
m = htsmsg_create_map();
|
||||
|
@ -865,17 +906,42 @@ dvb_mux_set_tsid(th_dvb_mux_instance_t *tdmi, uint16_t tsid)
|
|||
notify_by_msg("dvbMux", m);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
dvb_mux_set_onid(th_dvb_mux_instance_t *tdmi, uint16_t onid, int force)
|
||||
{
|
||||
htsmsg_t *m;
|
||||
|
||||
if (!force)
|
||||
if (tdmi->tdmi_network_id != 0 || onid == 0)
|
||||
return;
|
||||
|
||||
if (tdmi->tdmi_network_id == onid)
|
||||
return;
|
||||
|
||||
tdmi->tdmi_network_id = onid;
|
||||
|
||||
dvb_mux_save(tdmi);
|
||||
|
||||
m = htsmsg_create_map();
|
||||
htsmsg_add_str(m, "id", tdmi->tdmi_identifier);
|
||||
htsmsg_add_u32(m, "onid", tdmi->tdmi_network_id);
|
||||
notify_by_msg("dvbMux", m);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
static int
|
||||
tdmi_set_enable(th_dvb_mux_instance_t *tdmi, int enabled)
|
||||
{
|
||||
th_dvb_adapter_t *tda = tdmi->tdmi_adapter;
|
||||
|
||||
if(tdmi->tdmi_enabled == enabled)
|
||||
return;
|
||||
return 0;
|
||||
|
||||
if(tdmi->tdmi_enabled) {
|
||||
|
||||
|
@ -894,6 +960,7 @@ tdmi_set_enable(th_dvb_mux_instance_t *tdmi, int enabled)
|
|||
mux_link_initial(tda, tdmi);
|
||||
|
||||
subscription_reschedule();
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -902,8 +969,8 @@ tdmi_set_enable(th_dvb_mux_instance_t *tdmi, int enabled)
|
|||
void
|
||||
dvb_mux_set_enable(th_dvb_mux_instance_t *tdmi, int enabled)
|
||||
{
|
||||
tdmi_set_enable(tdmi, enabled);
|
||||
dvb_mux_save(tdmi);
|
||||
if (tdmi_set_enable(tdmi, enabled))
|
||||
dvb_mux_save(tdmi);
|
||||
}
|
||||
|
||||
|
||||
|
@ -990,6 +1057,8 @@ dvb_mux_build_msg(th_dvb_mux_instance_t *tdmi)
|
|||
htsmsg_t *m = htsmsg_create_map();
|
||||
char buf[100];
|
||||
|
||||
htsmsg_add_str(m, "adapterId", tdmi->tdmi_adapter->tda_identifier);
|
||||
|
||||
htsmsg_add_str(m, "id", tdmi->tdmi_identifier);
|
||||
htsmsg_add_u32(m, "enabled", tdmi->tdmi_enabled);
|
||||
htsmsg_add_str(m, "network", tdmi->tdmi_network ?: "");
|
||||
|
@ -1129,7 +1198,7 @@ dvb_mux_add_by_params(th_dvb_adapter_t *tda,
|
|||
break;
|
||||
|
||||
case FE_ATSC:
|
||||
dmc.dmc_fe_params.frequency = freq;
|
||||
dmc.dmc_fe_params.frequency = freq * 1000;
|
||||
|
||||
if(!val2str(constellation, qamtab))
|
||||
return "Invalid VSB constellation";
|
||||
|
@ -1148,7 +1217,7 @@ dvb_mux_add_by_params(th_dvb_adapter_t *tda,
|
|||
}
|
||||
dmc.dmc_polarisation = polarisation;
|
||||
|
||||
tdmi = dvb_mux_create(tda, &dmc, 0, 0xffff, NULL, NULL, 1, 1, NULL, NULL);
|
||||
tdmi = dvb_mux_create(tda, &dmc, 0, 0xffff, NULL, NULL, 1, 1, NULL, NULL, 1, NULL);
|
||||
|
||||
if(tdmi == NULL)
|
||||
return "Mux already exist";
|
||||
|
@ -1175,13 +1244,13 @@ dvb_mux_copy(th_dvb_adapter_t *dst, th_dvb_mux_instance_t *tdmi_src,
|
|||
tdmi_src->tdmi_transport_stream_id,
|
||||
tdmi_src->tdmi_network,
|
||||
"copy operation", tdmi_src->tdmi_enabled,
|
||||
1, NULL, satconf);
|
||||
1, NULL, satconf, 1, tdmi_src);
|
||||
|
||||
if(tdmi_dst == NULL)
|
||||
return -1; // Already exist
|
||||
|
||||
LIST_FOREACH(t_src, &tdmi_src->tdmi_transports, s_group_link) {
|
||||
t_dst = dvb_transport_find(tdmi_dst,
|
||||
t_dst = dvb_service_find(tdmi_dst,
|
||||
t_src->s_dvb_service_id,
|
||||
t_src->s_pmt_pid, NULL);
|
||||
|
||||
|
@ -1244,3 +1313,86 @@ void dvb_mux_add_to_scan_queue ( th_dvb_mux_instance_t *tdmi )
|
|||
tdmi->tdmi_scan_queue = &tda->tda_scan_queues[ti];
|
||||
TAILQ_INSERT_TAIL(tdmi->tdmi_scan_queue, tdmi, tdmi_scan_link);
|
||||
}
|
||||
|
||||
th_dvb_mux_instance_t *dvb_mux_find
|
||||
( th_dvb_adapter_t *tda, const char *netname, uint16_t onid, uint16_t tsid,
|
||||
int enabled )
|
||||
{
|
||||
th_dvb_mux_instance_t *tdmi;
|
||||
if (tda) {
|
||||
LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) {
|
||||
if (enabled && !tdmi->tdmi_enabled) continue;
|
||||
if (onid && onid != tdmi->tdmi_network_id) continue;
|
||||
if (tsid && tsid != tdmi->tdmi_transport_stream_id) continue;
|
||||
if (netname && strcmp(netname, tdmi->tdmi_network ?: "")) continue;
|
||||
return tdmi;
|
||||
}
|
||||
} else {
|
||||
TAILQ_FOREACH(tda, &dvb_adapters, tda_global_link)
|
||||
if ((tdmi = dvb_mux_find(tda, netname, onid, tsid, enabled)))
|
||||
return tdmi;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
th_subscription_t *
|
||||
dvb_subscription_create_from_tdmi(th_dvb_mux_instance_t *tdmi,
|
||||
const char *name,
|
||||
streaming_target_t *st,
|
||||
const char *hostname,
|
||||
const char *username,
|
||||
const char *client)
|
||||
{
|
||||
th_subscription_t *s;
|
||||
streaming_message_t *sm;
|
||||
streaming_start_t *ss;
|
||||
int r;
|
||||
th_dvb_adapter_t *tda = tdmi->tdmi_adapter;
|
||||
char buf[100];
|
||||
|
||||
s = subscription_create(INT32_MAX, name, st, SUBSCRIPTION_RAW_MPEGTS,
|
||||
NULL, hostname, username, client);
|
||||
|
||||
|
||||
s->ths_tdmi = tdmi;
|
||||
LIST_INSERT_HEAD(&tdmi->tdmi_subscriptions, s, ths_tdmi_link);
|
||||
|
||||
r = dvb_fe_tune(tdmi, "Full mux subscription");
|
||||
|
||||
pthread_mutex_lock(&tda->tda_delivery_mutex);
|
||||
|
||||
streaming_target_connect(&tda->tda_streaming_pad, &s->ths_input);
|
||||
|
||||
if(r) {
|
||||
sm = streaming_msg_create_code(SMT_NOSTART, SM_CODE_NO_INPUT);
|
||||
streaming_target_deliver(s->ths_output, sm);
|
||||
} else {
|
||||
ss = calloc(1, sizeof(streaming_start_t));
|
||||
ss->ss_num_components = 0;
|
||||
ss->ss_refcount = 1;
|
||||
|
||||
ss->ss_si.si_type = S_MPEG_TS;
|
||||
ss->ss_si.si_device = strdup(tdmi->tdmi_adapter->tda_rootpath);
|
||||
ss->ss_si.si_adapter = strdup(tdmi->tdmi_adapter->tda_displayname);
|
||||
ss->ss_si.si_service = strdup("Full mux subscription");
|
||||
|
||||
if(tdmi->tdmi_network != NULL)
|
||||
ss->ss_si.si_network = strdup(tdmi->tdmi_network);
|
||||
|
||||
dvb_mux_nicename(buf, sizeof(buf), tdmi);
|
||||
ss->ss_si.si_mux = strdup(buf);
|
||||
|
||||
|
||||
sm = streaming_msg_create_data(SMT_START, ss);
|
||||
streaming_target_deliver(s->ths_output, sm);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&tda->tda_delivery_mutex);
|
||||
|
||||
notify_reload("subscriptions");
|
||||
return s;
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ dvb_mux_preconf_add(th_dvb_adapter_t *tda, const network_t *net,
|
|||
|
||||
dmc.dmc_satconf = dvb_satconf_entry_find(tda, satconf, 0);
|
||||
|
||||
dvb_mux_create(tda, &dmc, 0, 0xffff, NULL, source, 1, 1, NULL, NULL);
|
||||
dvb_mux_create(tda, &dmc, 0, 0xffff, NULL, source, 1, 1, NULL, NULL, 1, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -296,6 +296,7 @@ dvb_lnblist_get(void)
|
|||
add_to_lnblist(array, "C-Band");
|
||||
add_to_lnblist(array, "C-Multi");
|
||||
add_to_lnblist(array, "Circular 10750");
|
||||
add_to_lnblist(array, "Ku 11300");
|
||||
return array;
|
||||
}
|
||||
|
||||
|
@ -338,5 +339,9 @@ dvb_lnb_get_frequencies(const char *id, int *f_low, int *f_hi, int *f_switch)
|
|||
*f_low = 10750000;
|
||||
*f_hi = 0;
|
||||
*f_switch = 0;
|
||||
} else if(!strcmp(id, "Ku 11300")) {
|
||||
*f_low = 11300000;
|
||||
*f_hi = 0;
|
||||
*f_switch = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,54 +44,6 @@
|
|||
#include "dvb_support.h"
|
||||
#include "notify.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
dvb_transport_open_demuxers(th_dvb_adapter_t *tda, service_t *t)
|
||||
{
|
||||
struct dmx_pes_filter_params dmx_param;
|
||||
int fd;
|
||||
elementary_stream_t *st;
|
||||
|
||||
TAILQ_FOREACH(st, &t->s_components, es_link) {
|
||||
if(st->es_pid >= 0x2000)
|
||||
continue;
|
||||
|
||||
if(st->es_demuxer_fd != -1)
|
||||
continue;
|
||||
|
||||
fd = tvh_open(tda->tda_demux_path, O_RDWR, 0);
|
||||
st->es_cc_valid = 0;
|
||||
|
||||
if(fd == -1) {
|
||||
st->es_demuxer_fd = -1;
|
||||
tvhlog(LOG_ERR, "dvb",
|
||||
"\"%s\" unable to open demuxer \"%s\" for pid %d -- %s",
|
||||
t->s_identifier, tda->tda_demux_path,
|
||||
st->es_pid, strerror(errno));
|
||||
continue;
|
||||
}
|
||||
|
||||
memset(&dmx_param, 0, sizeof(dmx_param));
|
||||
dmx_param.pid = st->es_pid;
|
||||
dmx_param.input = DMX_IN_FRONTEND;
|
||||
dmx_param.output = DMX_OUT_TS_TAP;
|
||||
dmx_param.pes_type = DMX_PES_OTHER;
|
||||
dmx_param.flags = DMX_IMMEDIATE_START;
|
||||
|
||||
if(ioctl(fd, DMX_SET_PES_FILTER, &dmx_param)) {
|
||||
tvhlog(LOG_ERR, "dvb",
|
||||
"\"%s\" unable to configure demuxer \"%s\" for pid %d -- %s",
|
||||
t->s_identifier, tda->tda_demux_path,
|
||||
st->es_pid, strerror(errno));
|
||||
close(fd);
|
||||
fd = -1;
|
||||
}
|
||||
|
||||
st->es_demuxer_fd = fd;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -103,7 +55,7 @@ dvb_transport_open_demuxers(th_dvb_adapter_t *tda, service_t *t)
|
|||
* transports that is subscribing to the adapter
|
||||
*/
|
||||
static int
|
||||
dvb_transport_start(service_t *t, unsigned int weight, int force_start)
|
||||
dvb_service_start(service_t *t, unsigned int weight, int force_start)
|
||||
{
|
||||
int w, r;
|
||||
th_dvb_adapter_t *tda = t->s_dvb_mux_instance->tdmi_adapter;
|
||||
|
@ -127,22 +79,28 @@ dvb_transport_start(service_t *t, unsigned int weight, int force_start)
|
|||
if(w && w >= weight && !force_start)
|
||||
/* We are outranked by weight, cant use it */
|
||||
return SM_CODE_NOT_FREE;
|
||||
|
||||
|
||||
if(LIST_FIRST(&tdmi->tdmi_subscriptions) != NULL)
|
||||
return SM_CODE_NOT_FREE;
|
||||
|
||||
dvb_adapter_clean(tda);
|
||||
}
|
||||
|
||||
r = dvb_fe_tune(t->s_dvb_mux_instance, "Transport start");
|
||||
|
||||
pthread_mutex_lock(&tda->tda_delivery_mutex);
|
||||
|
||||
r = dvb_fe_tune(t->s_dvb_mux_instance, "Transport start");
|
||||
if(!r)
|
||||
LIST_INSERT_HEAD(&tda->tda_transports, t, s_active_link);
|
||||
|
||||
pthread_mutex_unlock(&tda->tda_delivery_mutex);
|
||||
|
||||
if(!r)
|
||||
dvb_transport_open_demuxers(tda, t);
|
||||
if (tda->tda_locked) {
|
||||
if(!r)
|
||||
tda->tda_open_service(tda, t);
|
||||
|
||||
dvb_table_add_pmt(t->s_dvb_mux_instance, t->s_pmt_pid);
|
||||
dvb_table_add_pmt(t->s_dvb_mux_instance, t->s_pmt_pid);
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
@ -152,10 +110,9 @@ dvb_transport_start(service_t *t, unsigned int weight, int force_start)
|
|||
*
|
||||
*/
|
||||
static void
|
||||
dvb_transport_stop(service_t *t)
|
||||
dvb_service_stop(service_t *t)
|
||||
{
|
||||
th_dvb_adapter_t *tda = t->s_dvb_mux_instance->tdmi_adapter;
|
||||
elementary_stream_t *st;
|
||||
|
||||
lock_assert(&global_lock);
|
||||
|
||||
|
@ -163,12 +120,8 @@ dvb_transport_stop(service_t *t)
|
|||
LIST_REMOVE(t, s_active_link);
|
||||
pthread_mutex_unlock(&tda->tda_delivery_mutex);
|
||||
|
||||
TAILQ_FOREACH(st, &t->s_components, es_link) {
|
||||
if(st->es_demuxer_fd != -1) {
|
||||
close(st->es_demuxer_fd);
|
||||
st->es_demuxer_fd = -1;
|
||||
}
|
||||
}
|
||||
tda->tda_close_service(tda, t);
|
||||
|
||||
t->s_status = SERVICE_IDLE;
|
||||
}
|
||||
|
||||
|
@ -177,12 +130,23 @@ dvb_transport_stop(service_t *t)
|
|||
*
|
||||
*/
|
||||
static void
|
||||
dvb_transport_refresh(service_t *t)
|
||||
dvb_service_refresh(service_t *t)
|
||||
{
|
||||
th_dvb_adapter_t *tda = t->s_dvb_mux_instance->tdmi_adapter;
|
||||
|
||||
lock_assert(&global_lock);
|
||||
dvb_transport_open_demuxers(tda, t);
|
||||
tda->tda_open_service(tda, t);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static int
|
||||
dvb_service_is_enabled(service_t *t)
|
||||
{
|
||||
th_dvb_mux_instance_t *tdmi = t->s_dvb_mux_instance;
|
||||
th_dvb_adapter_t *tda = tdmi->tdmi_adapter;
|
||||
return tda->tda_enabled && tdmi->tdmi_enabled && t->s_enabled && t->s_pmt_pid;
|
||||
}
|
||||
|
||||
|
||||
|
@ -190,7 +154,7 @@ dvb_transport_refresh(service_t *t)
|
|||
*
|
||||
*/
|
||||
static void
|
||||
dvb_transport_save(service_t *t)
|
||||
dvb_service_save(service_t *t)
|
||||
{
|
||||
htsmsg_t *m = htsmsg_create_map();
|
||||
|
||||
|
@ -221,6 +185,9 @@ dvb_transport_save(service_t *t)
|
|||
if(t->s_default_authority)
|
||||
htsmsg_add_str(m, "default_authority", t->s_default_authority);
|
||||
|
||||
if(t->s_prefcapid)
|
||||
htsmsg_add_u32(m, "prefcapid", t->s_prefcapid);
|
||||
|
||||
pthread_mutex_lock(&t->s_stream_mutex);
|
||||
psi_save_service_settings(m, t);
|
||||
pthread_mutex_unlock(&t->s_stream_mutex);
|
||||
|
@ -230,7 +197,7 @@ dvb_transport_save(service_t *t)
|
|||
t->s_identifier);
|
||||
|
||||
htsmsg_destroy(m);
|
||||
dvb_transport_notify(t);
|
||||
dvb_service_notify(t);
|
||||
}
|
||||
|
||||
|
||||
|
@ -238,7 +205,7 @@ dvb_transport_save(service_t *t)
|
|||
* Load config for the given mux
|
||||
*/
|
||||
void
|
||||
dvb_transport_load(th_dvb_mux_instance_t *tdmi, const char *tdmi_identifier)
|
||||
dvb_service_load(th_dvb_mux_instance_t *tdmi, const char *tdmi_identifier)
|
||||
{
|
||||
htsmsg_t *l, *c;
|
||||
htsmsg_field_t *f;
|
||||
|
@ -269,7 +236,7 @@ dvb_transport_load(th_dvb_mux_instance_t *tdmi, const char *tdmi_identifier)
|
|||
if(htsmsg_get_u32(c, "pmt", &pmt))
|
||||
continue;
|
||||
|
||||
t = dvb_transport_find(tdmi, sid, pmt, f->hmf_name);
|
||||
t = dvb_service_find(tdmi, sid, pmt, f->hmf_name);
|
||||
|
||||
htsmsg_get_u32(c, "stype", &t->s_servicetype);
|
||||
if(htsmsg_get_u32(c, "scrambled", &u32))
|
||||
|
@ -309,9 +276,13 @@ dvb_transport_load(th_dvb_mux_instance_t *tdmi, const char *tdmi_identifier)
|
|||
if(s && u32)
|
||||
service_map_channel(t, channel_find_by_name(s, 1, 0), 0);
|
||||
|
||||
if(htsmsg_get_u32(c, "prefcapid", &u32))
|
||||
u32 = 0;
|
||||
t->s_prefcapid = u32;
|
||||
|
||||
/* HACK - force save for old config */
|
||||
if(old)
|
||||
dvb_transport_save(t);
|
||||
dvb_service_save(t);
|
||||
}
|
||||
|
||||
/* HACK - remove old settings */
|
||||
|
@ -332,7 +303,7 @@ dvb_transport_load(th_dvb_mux_instance_t *tdmi, const char *tdmi_identifier)
|
|||
* return that value
|
||||
*/
|
||||
static int
|
||||
dvb_transport_quality(service_t *t)
|
||||
dvb_service_quality(service_t *t)
|
||||
{
|
||||
th_dvb_mux_instance_t *tdmi = t->s_dvb_mux_instance;
|
||||
|
||||
|
@ -346,7 +317,7 @@ dvb_transport_quality(service_t *t)
|
|||
* Generate a descriptive name for the source
|
||||
*/
|
||||
static void
|
||||
dvb_transport_setsourceinfo(service_t *t, struct source_info *si)
|
||||
dvb_service_setsourceinfo(service_t *t, struct source_info *si)
|
||||
{
|
||||
th_dvb_mux_instance_t *tdmi = t->s_dvb_mux_instance;
|
||||
char buf[100];
|
||||
|
@ -355,6 +326,8 @@ dvb_transport_setsourceinfo(service_t *t, struct source_info *si)
|
|||
|
||||
lock_assert(&global_lock);
|
||||
|
||||
si->si_type = S_MPEG_TS;
|
||||
|
||||
if(tdmi->tdmi_adapter->tda_rootpath != NULL)
|
||||
si->si_device = strdup(tdmi->tdmi_adapter->tda_rootpath);
|
||||
|
||||
|
@ -380,9 +353,47 @@ dvb_transport_setsourceinfo(service_t *t, struct source_info *si)
|
|||
static int
|
||||
dvb_grace_period(service_t *t)
|
||||
{
|
||||
if (t->s_dvb_mux_instance && t->s_dvb_mux_instance->tdmi_adapter)
|
||||
return t->s_dvb_mux_instance->tdmi_adapter->tda_grace_period ?: 10;
|
||||
return 10;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find transport based on the DVB identification
|
||||
*/
|
||||
service_t *
|
||||
dvb_service_find3
|
||||
(th_dvb_adapter_t *tda, th_dvb_mux_instance_t *tdmi,
|
||||
const char *netname, uint16_t onid, uint16_t tsid, uint16_t sid,
|
||||
int enabled, int epgprimary)
|
||||
{
|
||||
service_t *svc;
|
||||
if (tdmi) {
|
||||
LIST_FOREACH(svc, &tdmi->tdmi_transports, s_group_link) {
|
||||
if (sid != svc->s_dvb_service_id) continue;
|
||||
if (enabled && !svc->s_enabled) continue;
|
||||
if (epgprimary && !service_is_primary_epg(svc)) continue;
|
||||
return svc;
|
||||
}
|
||||
} else if (tda) {
|
||||
LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) {
|
||||
if (enabled && !tdmi->tdmi_enabled) continue;
|
||||
if (onid && onid != tdmi->tdmi_network_id) continue;
|
||||
if (tsid && tsid != tdmi->tdmi_transport_stream_id) continue;
|
||||
if (netname && strcmp(netname, tdmi->tdmi_network ?: "")) continue;
|
||||
if ((svc = dvb_service_find3(tda, tdmi, NULL, 0, 0, sid,
|
||||
enabled, epgprimary)))
|
||||
return svc;
|
||||
}
|
||||
} else {
|
||||
TAILQ_FOREACH(tda, &dvb_adapters, tda_global_link)
|
||||
if ((svc = dvb_service_find3(tda, NULL, netname, onid, tsid,
|
||||
sid, enabled, epgprimary)))
|
||||
return svc;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Find a transport based on 'serviceid' on the given mux
|
||||
|
@ -390,14 +401,14 @@ dvb_grace_period(service_t *t)
|
|||
* If it cannot be found we create it if 'pmt_pid' is also set
|
||||
*/
|
||||
service_t *
|
||||
dvb_transport_find(th_dvb_mux_instance_t *tdmi, uint16_t sid, int pmt_pid,
|
||||
dvb_service_find(th_dvb_mux_instance_t *tdmi, uint16_t sid, int pmt_pid,
|
||||
const char *identifier)
|
||||
{
|
||||
return dvb_transport_find2(tdmi, sid, pmt_pid, identifier, NULL);
|
||||
return dvb_service_find2(tdmi, sid, pmt_pid, identifier, NULL);
|
||||
}
|
||||
|
||||
service_t *
|
||||
dvb_transport_find2(th_dvb_mux_instance_t *tdmi, uint16_t sid, int pmt_pid,
|
||||
dvb_service_find2(th_dvb_mux_instance_t *tdmi, uint16_t sid, int pmt_pid,
|
||||
const char *identifier, int *save)
|
||||
{
|
||||
service_t *t;
|
||||
|
@ -408,12 +419,18 @@ dvb_transport_find2(th_dvb_mux_instance_t *tdmi, uint16_t sid, int pmt_pid,
|
|||
|
||||
LIST_FOREACH(t, &tdmi->tdmi_transports, s_group_link) {
|
||||
if(t->s_dvb_service_id == sid)
|
||||
return t;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Existing - updated PMT_PID if required */
|
||||
if (t) {
|
||||
if (pmt_pid && pmt_pid != t->s_pmt_pid) {
|
||||
t->s_pmt_pid = pmt_pid;
|
||||
*save = 1;
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
if(pmt_pid == 0)
|
||||
return NULL;
|
||||
|
||||
if(identifier == NULL) {
|
||||
snprintf(tmp, sizeof(tmp), "%s_%04x", tdmi->tdmi_identifier, sid);
|
||||
identifier = tmp;
|
||||
|
@ -428,13 +445,14 @@ dvb_transport_find2(th_dvb_mux_instance_t *tdmi, uint16_t sid, int pmt_pid,
|
|||
t->s_dvb_service_id = sid;
|
||||
t->s_pmt_pid = pmt_pid;
|
||||
|
||||
t->s_start_feed = dvb_transport_start;
|
||||
t->s_refresh_feed = dvb_transport_refresh;
|
||||
t->s_stop_feed = dvb_transport_stop;
|
||||
t->s_config_save = dvb_transport_save;
|
||||
t->s_setsourceinfo = dvb_transport_setsourceinfo;
|
||||
t->s_quality_index = dvb_transport_quality;
|
||||
t->s_start_feed = dvb_service_start;
|
||||
t->s_refresh_feed = dvb_service_refresh;
|
||||
t->s_stop_feed = dvb_service_stop;
|
||||
t->s_config_save = dvb_service_save;
|
||||
t->s_setsourceinfo = dvb_service_setsourceinfo;
|
||||
t->s_quality_index = dvb_service_quality;
|
||||
t->s_grace_period = dvb_grace_period;
|
||||
t->s_is_enabled = dvb_service_is_enabled;
|
||||
|
||||
t->s_dvb_mux_instance = tdmi;
|
||||
LIST_INSERT_HEAD(&tdmi->tdmi_transports, t, s_group_link);
|
||||
|
@ -452,10 +470,11 @@ dvb_transport_find2(th_dvb_mux_instance_t *tdmi, uint16_t sid, int pmt_pid,
|
|||
*
|
||||
*/
|
||||
htsmsg_t *
|
||||
dvb_transport_build_msg(service_t *t)
|
||||
dvb_service_build_msg(service_t *t)
|
||||
{
|
||||
th_dvb_mux_instance_t *tdmi = t->s_dvb_mux_instance;
|
||||
htsmsg_t *m = htsmsg_create_map();
|
||||
uint16_t caid;
|
||||
char buf[100];
|
||||
|
||||
htsmsg_add_str(m, "id", t->s_identifier);
|
||||
|
@ -466,16 +485,25 @@ dvb_transport_build_msg(service_t *t)
|
|||
htsmsg_add_u32(m, "pmt", t->s_pmt_pid);
|
||||
htsmsg_add_u32(m, "pcr", t->s_pcr_pid);
|
||||
|
||||
htsmsg_add_str(m, "type", service_servicetype_txt(t));
|
||||
snprintf(buf, sizeof(buf), "%s (0x%04X)", service_servicetype_txt(t), t->s_servicetype);
|
||||
htsmsg_add_str(m, "type", buf);
|
||||
htsmsg_add_str(m, "typestr", service_servicetype_txt(t));
|
||||
htsmsg_add_u32(m, "typenum", t->s_servicetype);
|
||||
|
||||
htsmsg_add_str(m, "svcname", t->s_svcname ?: "");
|
||||
htsmsg_add_str(m, "provider", t->s_provider ?: "");
|
||||
|
||||
htsmsg_add_str(m, "network", tdmi->tdmi_network ?: "");
|
||||
|
||||
if((caid = service_get_encryption(t)) != 0)
|
||||
htsmsg_add_str(m, "encryption", psi_caid2name(caid));
|
||||
|
||||
dvb_mux_nicefreq(buf, sizeof(buf), tdmi);
|
||||
htsmsg_add_str(m, "mux", buf);
|
||||
|
||||
if(tdmi->tdmi_conf.dmc_satconf != NULL)
|
||||
htsmsg_add_str(m, "satconf", tdmi->tdmi_conf.dmc_satconf->sc_id);
|
||||
|
||||
if(t->s_ch != NULL)
|
||||
htsmsg_add_str(m, "channelname", t->s_ch->ch_name);
|
||||
|
||||
|
@ -484,6 +512,8 @@ dvb_transport_build_msg(service_t *t)
|
|||
|
||||
htsmsg_add_u32(m, "dvb_eit_enable", t->s_dvb_eit_enable);
|
||||
|
||||
htsmsg_add_u32(m, "prefcapid", t->s_prefcapid);
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
|
@ -492,7 +522,7 @@ dvb_transport_build_msg(service_t *t)
|
|||
*
|
||||
*/
|
||||
void
|
||||
dvb_transport_notify_by_adapter(th_dvb_adapter_t *tda)
|
||||
dvb_service_notify_by_adapter(th_dvb_adapter_t *tda)
|
||||
{
|
||||
htsmsg_t *m = htsmsg_create_map();
|
||||
htsmsg_add_str(m, "adapterId", tda->tda_identifier);
|
||||
|
@ -504,7 +534,7 @@ dvb_transport_notify_by_adapter(th_dvb_adapter_t *tda)
|
|||
*
|
||||
*/
|
||||
void
|
||||
dvb_transport_notify(service_t *t)
|
||||
dvb_service_notify(service_t *t)
|
||||
{
|
||||
th_dvb_mux_instance_t *tdmi = t->s_dvb_mux_instance;
|
||||
htsmsg_t *m = htsmsg_create_map();
|
|
@ -95,10 +95,7 @@ static inline size_t conv_8859(int conv,
|
|||
(*dstlen)--;
|
||||
dst++;
|
||||
} else if (c <= 0x9f) {
|
||||
// codes 0x80 - 0x9f (control codes) are mapped to ' '
|
||||
*dst = ' ';
|
||||
(*dstlen)--;
|
||||
dst++;
|
||||
// codes 0x80 - 0x9f (control codes) are ignored
|
||||
} else {
|
||||
// map according to character table, skipping
|
||||
// unmapped chars (value 0 in the table)
|
||||
|
@ -135,10 +132,7 @@ static inline size_t conv_6937(const uint8_t *src, size_t srclen,
|
|||
(*dstlen)--;
|
||||
dst++;
|
||||
} else if (c <= 0x9f) {
|
||||
// codes 0x80 - 0x9f (control codes) are mapped to ' '
|
||||
*dst = ' ';
|
||||
(*dstlen)--;
|
||||
dst++;
|
||||
// codes 0x80 - 0x9f (control codes) are ignored
|
||||
} else {
|
||||
uint16_t uc;
|
||||
if (c >= 0xc0 && c <= 0xcf) {
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
#define DVB_DESC_PARENTAL_RAT 0x55
|
||||
#define DVB_DESC_TELETEXT 0x56
|
||||
#define DVB_DESC_SUBTITLE 0x59
|
||||
#define DVB_DESC_TERR 0x5a
|
||||
#define DVB_DESC_AC3 0x6a
|
||||
#define DVB_DESC_DEF_AUTHORITY 0x73
|
||||
#define DVB_DESC_CRID 0x76
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -65,6 +65,7 @@ extern struct dvr_entry_list dvrentries;
|
|||
#define DVR_EPISODE_IN_TITLE 0x80
|
||||
#define DVR_CLEAN_TITLE 0x100
|
||||
#define DVR_TAG_FILES 0x200
|
||||
#define DVR_SKIP_COMMERCIALS 0x400
|
||||
|
||||
typedef enum {
|
||||
DVR_PRIO_IMPORTANT,
|
||||
|
@ -113,6 +114,8 @@ typedef struct dvr_entry {
|
|||
channel_t *de_channel;
|
||||
LIST_ENTRY(dvr_entry) de_channel_link;
|
||||
|
||||
char *de_channel_name;
|
||||
|
||||
gtimer_t de_timer;
|
||||
|
||||
/**
|
||||
|
@ -134,6 +137,8 @@ typedef struct dvr_entry {
|
|||
lang_str_t *de_desc; /* Description in UTF-8 (from EPG) */
|
||||
epg_genre_t de_content_type; /* Content type (from EPG) */
|
||||
|
||||
uint16_t de_dvb_eid;
|
||||
|
||||
dvr_prio_t de_pri;
|
||||
|
||||
uint32_t de_dont_reschedule;
|
||||
|
@ -188,8 +193,16 @@ typedef struct dvr_entry {
|
|||
|
||||
struct muxer *de_mux;
|
||||
|
||||
/**
|
||||
* Inotify
|
||||
*/
|
||||
#if ENABLE_INOTIFY
|
||||
LIST_ENTRY(dvr_entry) de_inotify_link;
|
||||
#endif
|
||||
|
||||
} dvr_entry_t;
|
||||
|
||||
#define DVR_CH_NAME(e) ((e)->de_channel == NULL ? (e)->de_channel_name : (e)-> de_channel->ch_name)
|
||||
|
||||
/**
|
||||
* Autorec entry
|
||||
|
@ -247,6 +260,8 @@ void dvr_config_delete(const char *name);
|
|||
|
||||
void dvr_entry_notify(dvr_entry_t *de);
|
||||
|
||||
void dvr_entry_save(dvr_entry_t *de);
|
||||
|
||||
const char *dvr_entry_status(dvr_entry_t *de);
|
||||
|
||||
const char *dvr_entry_schedstatus(dvr_entry_t *de);
|
||||
|
@ -280,6 +295,8 @@ void dvr_init(void);
|
|||
|
||||
void dvr_autorec_init(void);
|
||||
|
||||
void dvr_autorec_update(void);
|
||||
|
||||
void dvr_destroy_by_channel(channel_t *ch);
|
||||
|
||||
void dvr_rec_subscribe(dvr_entry_t *de);
|
||||
|
@ -298,7 +315,7 @@ dvr_entry_t *dvr_entry_find_by_event_fuzzy(epg_broadcast_t *e);
|
|||
|
||||
dvr_entry_t *dvr_entry_find_by_episode(epg_broadcast_t *e);
|
||||
|
||||
off_t dvr_get_filesize(dvr_entry_t *de);
|
||||
int64_t dvr_get_filesize(dvr_entry_t *de);
|
||||
|
||||
dvr_entry_t *dvr_entry_cancel(dvr_entry_t *de);
|
||||
|
||||
|
@ -331,10 +348,19 @@ typedef struct dvr_query_result {
|
|||
int dqr_alloced;
|
||||
} dvr_query_result_t;
|
||||
|
||||
typedef int (dvr_entry_filter)(dvr_entry_t *entry);
|
||||
typedef int (dvr_entry_comparator)(const void *a, const void *b);
|
||||
|
||||
void dvr_query(dvr_query_result_t *dqr);
|
||||
void dvr_query_filter(dvr_query_result_t *dqr, dvr_entry_filter filter);
|
||||
void dvr_query_free(dvr_query_result_t *dqr);
|
||||
|
||||
void dvr_query_sort_cmp(dvr_query_result_t *dqr, dvr_entry_comparator cmp);
|
||||
void dvr_query_sort(dvr_query_result_t *dqr);
|
||||
|
||||
int dvr_sort_start_descending(const void *A, const void *B);
|
||||
int dvr_sort_start_ascending(const void *A, const void *B);
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -364,4 +390,11 @@ dvr_prio_t dvr_pri2val(const char *s);
|
|||
|
||||
const char *dvr_val2pri(dvr_prio_t v);
|
||||
|
||||
/**
|
||||
* Inotify support
|
||||
*/
|
||||
void dvr_inotify_init ( void );
|
||||
void dvr_inotify_add ( dvr_entry_t *de );
|
||||
void dvr_inotify_del ( dvr_entry_t *de );
|
||||
|
||||
#endif /* DVR_H */
|
||||
|
|
|
@ -38,12 +38,14 @@ dtable_t *autorec_dt;
|
|||
|
||||
TAILQ_HEAD(dvr_autorec_entry_queue, dvr_autorec_entry);
|
||||
|
||||
static int dvr_autorec_in_init = 0;
|
||||
|
||||
struct dvr_autorec_entry_queue autorec_entries;
|
||||
|
||||
static void dvr_autorec_changed(dvr_autorec_entry_t *dae);
|
||||
static void dvr_autorec_changed(dvr_autorec_entry_t *dae, int purge);
|
||||
|
||||
/**
|
||||
*
|
||||
* Unlink - and remove any unstarted
|
||||
*/
|
||||
static void
|
||||
dvr_autorec_purge_spawns(dvr_autorec_entry_t *dae)
|
||||
|
@ -53,7 +55,10 @@ dvr_autorec_purge_spawns(dvr_autorec_entry_t *dae)
|
|||
while((de = LIST_FIRST(&dae->dae_spawns)) != NULL) {
|
||||
LIST_REMOVE(de, de_autorec_link);
|
||||
de->de_autorec = NULL;
|
||||
dvr_entry_cancel(de);
|
||||
if (de->de_sched_state == DVR_SCHEDULED)
|
||||
dvr_entry_cancel(de);
|
||||
else
|
||||
dvr_entry_save(de);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -385,7 +390,8 @@ autorec_record_update(void *opaque, const char *id, htsmsg_t *values,
|
|||
}
|
||||
}
|
||||
|
||||
dae->dae_content_type.code = htsmsg_get_u32_or_default(values, "contenttype", 0);
|
||||
if (!htsmsg_get_u32(values, "contenttype", &u32))
|
||||
dae->dae_content_type.code = u32;
|
||||
|
||||
if((s = htsmsg_get_str(values, "approx_time")) != NULL) {
|
||||
if(strchr(s, ':') != NULL) {
|
||||
|
@ -422,7 +428,8 @@ autorec_record_update(void *opaque, const char *id, htsmsg_t *values,
|
|||
if (dae->dae_serieslink)
|
||||
dae->dae_serieslink->getref(dae->dae_serieslink);
|
||||
}
|
||||
dvr_autorec_changed(dae);
|
||||
if (!dvr_autorec_in_init)
|
||||
dvr_autorec_changed(dae, 1);
|
||||
|
||||
return autorec_record_build(dae);
|
||||
}
|
||||
|
@ -465,7 +472,18 @@ dvr_autorec_init(void)
|
|||
{
|
||||
TAILQ_INIT(&autorec_entries);
|
||||
autorec_dt = dtable_create(&autorec_dtc, "autorec", NULL);
|
||||
dvr_autorec_in_init = 1;
|
||||
dtable_load(autorec_dt);
|
||||
dvr_autorec_in_init = 0;
|
||||
}
|
||||
|
||||
void
|
||||
dvr_autorec_update(void)
|
||||
{
|
||||
dvr_autorec_entry_t *dae;
|
||||
TAILQ_FOREACH(dae, &autorec_entries, dae_link) {
|
||||
dvr_autorec_changed(dae, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -520,12 +538,10 @@ _dvr_autorec_add(const char *config_name,
|
|||
htsmsg_destroy(m);
|
||||
|
||||
/* Notify web clients that we have messed with the tables */
|
||||
|
||||
m = htsmsg_create_map();
|
||||
htsmsg_add_u32(m, "reload", 1);
|
||||
notify_by_msg("autorec", m);
|
||||
|
||||
dvr_autorec_changed(dae);
|
||||
notify_reload("autorec");
|
||||
|
||||
dvr_autorec_changed(dae, 1);
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -544,9 +560,11 @@ void dvr_autorec_add_series_link
|
|||
( const char *dvr_config_name, epg_broadcast_t *event,
|
||||
const char *creator, const char *comment )
|
||||
{
|
||||
char *title;
|
||||
if (!event || !event->episode) return;
|
||||
title = regexp_escape(epg_broadcast_get_title(event, NULL));
|
||||
_dvr_autorec_add(dvr_config_name,
|
||||
epg_broadcast_get_title(event, NULL),
|
||||
title,
|
||||
event->channel,
|
||||
NULL, 0, // tag/content type
|
||||
NULL,
|
||||
|
@ -554,6 +572,8 @@ void dvr_autorec_add_series_link
|
|||
event->serieslink,
|
||||
0, NULL,
|
||||
creator, comment);
|
||||
if (title)
|
||||
free(title);
|
||||
}
|
||||
|
||||
|
||||
|
@ -564,14 +584,10 @@ void
|
|||
dvr_autorec_check_event(epg_broadcast_t *e)
|
||||
{
|
||||
dvr_autorec_entry_t *dae;
|
||||
dvr_entry_t *existingde;
|
||||
|
||||
TAILQ_FOREACH(dae, &autorec_entries, dae_link)
|
||||
if(autorec_cmp(dae, e)) {
|
||||
existingde = dvr_entry_find_by_event_fuzzy(e);
|
||||
if (existingde == NULL)
|
||||
dvr_entry_create_by_autorec(e, dae);
|
||||
}
|
||||
if(autorec_cmp(dae, e))
|
||||
dvr_entry_create_by_autorec(e, dae);
|
||||
// Note: no longer updating event here as it will be done from EPG
|
||||
// anyway
|
||||
}
|
||||
|
@ -598,12 +614,13 @@ void dvr_autorec_check_serieslink(epg_serieslink_t *s)
|
|||
*
|
||||
*/
|
||||
static void
|
||||
dvr_autorec_changed(dvr_autorec_entry_t *dae)
|
||||
dvr_autorec_changed(dvr_autorec_entry_t *dae, int purge)
|
||||
{
|
||||
channel_t *ch;
|
||||
epg_broadcast_t *e;
|
||||
|
||||
dvr_autorec_purge_spawns(dae);
|
||||
if (purge)
|
||||
dvr_autorec_purge_spawns(dae);
|
||||
|
||||
RB_FOREACH(ch, &channel_name_tree, ch_name_link) {
|
||||
RB_FOREACH(e, &ch->ch_epg_schedule, sched_link) {
|
||||
|
|
329
src/dvr/dvr_db.c
329
src/dvr/dvr_db.c
|
@ -27,7 +27,7 @@
|
|||
#include "tvheadend.h"
|
||||
#include "dvr.h"
|
||||
#include "notify.h"
|
||||
#include "htsp.h"
|
||||
#include "htsp_server.h"
|
||||
#include "streaming.h"
|
||||
|
||||
static int de_tally;
|
||||
|
@ -37,11 +37,21 @@ int dvr_iov_max;
|
|||
struct dvr_config_list dvrconfigs;
|
||||
struct dvr_entry_list dvrentries;
|
||||
|
||||
static void dvr_entry_save(dvr_entry_t *de);
|
||||
|
||||
static void dvr_timer_expire(void *aux);
|
||||
static void dvr_timer_start_recording(void *aux);
|
||||
|
||||
/*
|
||||
* Completed
|
||||
*/
|
||||
static void
|
||||
_dvr_entry_completed(dvr_entry_t *de)
|
||||
{
|
||||
de->de_sched_state = DVR_COMPLETED;
|
||||
#if ENABLE_INOTIFY
|
||||
dvr_inotify_add(de);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Return printable status for a dvr entry
|
||||
*/
|
||||
|
@ -70,6 +80,8 @@ dvr_entry_status(dvr_entry_t *de)
|
|||
}
|
||||
|
||||
case DVR_COMPLETED:
|
||||
if(dvr_get_filesize(de) == -1)
|
||||
return "File Missing";
|
||||
if(de->de_last_error)
|
||||
return streaming_code2txt(de->de_last_error);
|
||||
else
|
||||
|
@ -99,7 +111,7 @@ dvr_entry_schedstatus(dvr_entry_t *de)
|
|||
else
|
||||
return "recording";
|
||||
case DVR_COMPLETED:
|
||||
if(de->de_last_error)
|
||||
if(de->de_last_error || dvr_get_filesize(de) == -1)
|
||||
return "completedError";
|
||||
else
|
||||
return "completed";
|
||||
|
@ -162,7 +174,7 @@ dvr_make_title(char *output, size_t outlen, dvr_entry_t *de)
|
|||
dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name);
|
||||
|
||||
if(cfg->dvr_flags & DVR_CHANNEL_IN_TITLE)
|
||||
snprintf(output, outlen, "%s-", de->de_channel->ch_name);
|
||||
snprintf(output, outlen, "%s-", DVR_CH_NAME(de));
|
||||
else
|
||||
output[0] = 0;
|
||||
|
||||
|
@ -203,19 +215,12 @@ dvr_make_title(char *output, size_t outlen, dvr_entry_t *de)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
dvr_entry_link(dvr_entry_t *de)
|
||||
dvr_entry_set_timer(dvr_entry_t *de)
|
||||
{
|
||||
time_t now, preamble;
|
||||
dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name);
|
||||
|
||||
de->de_refcnt = 1;
|
||||
|
||||
LIST_INSERT_HEAD(&dvrentries, de, de_global_link);
|
||||
|
||||
time(&now);
|
||||
|
||||
preamble = de->de_start - (60 * de->de_start_extra) - 30;
|
||||
|
@ -224,18 +229,68 @@ dvr_entry_link(dvr_entry_t *de)
|
|||
if(de->de_filename == NULL)
|
||||
de->de_sched_state = DVR_MISSED_TIME;
|
||||
else
|
||||
de->de_sched_state = DVR_COMPLETED;
|
||||
_dvr_entry_completed(de);
|
||||
gtimer_arm_abs(&de->de_timer, dvr_timer_expire, de,
|
||||
de->de_stop + cfg->dvr_retention_days * 86400);
|
||||
|
||||
} else {
|
||||
} else if (de->de_channel) {
|
||||
de->de_sched_state = DVR_SCHEDULED;
|
||||
|
||||
tvhtrace("dvr", "entry timer scheduled for %"PRItime_t, preamble);
|
||||
gtimer_arm_abs(&de->de_timer, dvr_timer_start_recording, de, preamble);
|
||||
} else {
|
||||
de->de_sched_state = DVR_NOSTATE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
dvr_entry_link(dvr_entry_t *de)
|
||||
{
|
||||
de->de_refcnt = 1;
|
||||
|
||||
LIST_INSERT_HEAD(&dvrentries, de, de_global_link);
|
||||
|
||||
dvr_entry_set_timer(de);
|
||||
|
||||
htsp_dvr_entry_add(de);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find dvr entry using 'fuzzy' search
|
||||
*/
|
||||
static int
|
||||
dvr_entry_fuzzy_match(dvr_entry_t *de, epg_broadcast_t *e)
|
||||
{
|
||||
time_t t1, t2;
|
||||
const char *title1, *title2;
|
||||
|
||||
/* Matching ID */
|
||||
if (de->de_dvb_eid && de->de_dvb_eid == e->dvb_eid)
|
||||
return 1;
|
||||
|
||||
/* No title */
|
||||
if (!(title1 = epg_broadcast_get_title(e, NULL)))
|
||||
return 0;
|
||||
if (!(title2 = lang_str_get(de->de_title, NULL)))
|
||||
return 0;
|
||||
|
||||
/* Wrong length (+/-20%) */
|
||||
t1 = de->de_stop - de->de_start;
|
||||
t2 = e->stop - e->start;
|
||||
if ( abs(t2 - t1) > (t1 / 5) )
|
||||
return 0;
|
||||
|
||||
/* Outside of window (should it be configurable)? */
|
||||
if ( abs(e->start - de->de_start) > 86400 )
|
||||
return 0;
|
||||
|
||||
/* Title match (or contains?) */
|
||||
return strcmp(title1, title2) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the event
|
||||
*/
|
||||
|
@ -258,19 +313,6 @@ static dvr_entry_t *_dvr_entry_create (
|
|||
if(de->de_start == start && de->de_sched_state != DVR_COMPLETED)
|
||||
return NULL;
|
||||
|
||||
/* Reject duplicate episodes (unless earlier) */
|
||||
if (e && cfg->dvr_dup_detect_episode) {
|
||||
de = dvr_entry_find_by_episode(e);
|
||||
if (de) {
|
||||
if (de->de_start > start) {
|
||||
dvr_event_replaced(de->de_bcast, e);
|
||||
return de;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
de = calloc(1, sizeof(dvr_entry_t));
|
||||
de->de_id = ++de_tally;
|
||||
|
||||
|
@ -300,6 +342,7 @@ static dvr_entry_t *_dvr_entry_create (
|
|||
de->de_desc = NULL;
|
||||
// TODO: this really needs updating
|
||||
if (e) {
|
||||
de->de_dvb_eid = e->dvb_eid;
|
||||
if (e->episode && e->episode->title)
|
||||
de->de_title = lang_str_copy(e->episode->title);
|
||||
if (e->description)
|
||||
|
@ -334,9 +377,10 @@ static dvr_entry_t *_dvr_entry_create (
|
|||
LIST_INSERT_HEAD(&dae->dae_spawns, de, de_autorec_link);
|
||||
}
|
||||
|
||||
tvhlog(LOG_INFO, "dvr", "\"%s\" on \"%s\" starting at %s, "
|
||||
tvhlog(LOG_INFO, "dvr", "entry %d \"%s\" on \"%s\" starting at %s, "
|
||||
"scheduled for recording by \"%s\"",
|
||||
lang_str_get(de->de_title, NULL), de->de_channel->ch_name, tbuf, creator);
|
||||
de->de_id,
|
||||
lang_str_get(de->de_title, NULL), DVR_CH_NAME(de), tbuf, creator);
|
||||
|
||||
dvrdb_changed();
|
||||
dvr_entry_save(de);
|
||||
|
@ -449,12 +493,18 @@ dvr_entry_remove(dvr_entry_t *de)
|
|||
hts_settings_remove("dvr/log/%d", de->de_id);
|
||||
|
||||
htsp_dvr_entry_delete(de);
|
||||
|
||||
#if ENABLE_INOTIFY
|
||||
dvr_inotify_del(de);
|
||||
#endif
|
||||
|
||||
gtimer_disarm(&de->de_timer);
|
||||
|
||||
LIST_REMOVE(de, de_channel_link);
|
||||
if (de->de_channel)
|
||||
LIST_REMOVE(de, de_channel_link);
|
||||
LIST_REMOVE(de, de_global_link);
|
||||
de->de_channel = NULL;
|
||||
free(de->de_channel_name);
|
||||
|
||||
dvrdb_changed();
|
||||
|
||||
|
@ -469,9 +519,9 @@ static void
|
|||
dvr_db_load_one(htsmsg_t *c, int id)
|
||||
{
|
||||
dvr_entry_t *de;
|
||||
const char *s, *creator;
|
||||
const char *chname, *s, *creator;
|
||||
channel_t *ch;
|
||||
uint32_t start, stop, bcid;
|
||||
uint32_t start, stop, bcid, u32;
|
||||
int d;
|
||||
dvr_config_t *cfg;
|
||||
lang_str_t *title, *ls;
|
||||
|
@ -481,11 +531,10 @@ dvr_db_load_one(htsmsg_t *c, int id)
|
|||
if(htsmsg_get_u32(c, "stop", &stop))
|
||||
return;
|
||||
|
||||
if((s = htsmsg_get_str(c, "channel")) == NULL)
|
||||
if((chname = htsmsg_get_str(c, "channel")) == NULL)
|
||||
return;
|
||||
if((ch = channel_find_by_name(s, 0, 0)) == NULL)
|
||||
return;
|
||||
|
||||
ch = channel_find_by_name(chname, 0, 0);
|
||||
|
||||
s = htsmsg_get_str(c, "config_name");
|
||||
cfg = dvr_config_find_by_name_default(s);
|
||||
|
||||
|
@ -500,8 +549,12 @@ dvr_db_load_one(htsmsg_t *c, int id)
|
|||
|
||||
de_tally = MAX(id, de_tally);
|
||||
|
||||
de->de_channel = ch;
|
||||
LIST_INSERT_HEAD(&de->de_channel->ch_dvrs, de, de_channel_link);
|
||||
if (ch) {
|
||||
de->de_channel = ch;
|
||||
LIST_INSERT_HEAD(&de->de_channel->ch_dvrs, de, de_channel_link);
|
||||
} else {
|
||||
de->de_channel_name = strdup(chname);
|
||||
}
|
||||
|
||||
de->de_start = start;
|
||||
de->de_stop = stop;
|
||||
|
@ -509,9 +562,11 @@ dvr_db_load_one(htsmsg_t *c, int id)
|
|||
de->de_creator = strdup(creator);
|
||||
de->de_title = title;
|
||||
de->de_pri = dvr_pri2val(htsmsg_get_str(c, "pri"));
|
||||
if (!htsmsg_get_u32(c, "dvb_eid", &u32))
|
||||
de->de_dvb_eid = (uint16_t)u32;
|
||||
|
||||
if(htsmsg_get_s32(c, "start_extra", &d))
|
||||
if (ch->ch_dvr_extra_time_pre)
|
||||
if (ch && ch->ch_dvr_extra_time_pre)
|
||||
de->de_start_extra = ch->ch_dvr_extra_time_pre;
|
||||
else
|
||||
de->de_start_extra = cfg->dvr_extra_time_pre;
|
||||
|
@ -519,7 +574,7 @@ dvr_db_load_one(htsmsg_t *c, int id)
|
|||
de->de_start_extra = d;
|
||||
|
||||
if(htsmsg_get_s32(c, "stop_extra", &d))
|
||||
if (ch->ch_dvr_extra_time_post)
|
||||
if (ch && ch->ch_dvr_extra_time_post)
|
||||
de->de_stop_extra = ch->ch_dvr_extra_time_post;
|
||||
else
|
||||
de->de_stop_extra = cfg->dvr_extra_time_post;
|
||||
|
@ -586,14 +641,14 @@ dvr_db_load(void)
|
|||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
void
|
||||
dvr_entry_save(dvr_entry_t *de)
|
||||
{
|
||||
htsmsg_t *m = htsmsg_create_map();
|
||||
|
||||
lock_assert(&global_lock);
|
||||
|
||||
htsmsg_add_str(m, "channel", de->de_channel->ch_name);
|
||||
htsmsg_add_str(m, "channel", DVR_CH_NAME(de));
|
||||
htsmsg_add_u32(m, "start", de->de_start);
|
||||
htsmsg_add_u32(m, "stop", de->de_stop);
|
||||
|
||||
|
@ -609,6 +664,9 @@ dvr_entry_save(dvr_entry_t *de)
|
|||
|
||||
lang_str_serialize(de->de_title, m, "title");
|
||||
|
||||
if(de->de_dvb_eid)
|
||||
htsmsg_add_u32(m, "dvb_eid", de->de_dvb_eid);
|
||||
|
||||
if(de->de_desc != NULL)
|
||||
lang_str_serialize(de->de_desc, m, "description");
|
||||
|
||||
|
@ -677,6 +735,8 @@ static dvr_entry_t *_dvr_entry_update
|
|||
de->de_stop_extra = stop_extra;
|
||||
save = 1;
|
||||
}
|
||||
if (save)
|
||||
dvr_entry_set_timer(de);
|
||||
|
||||
/* Title */
|
||||
if (e && e->episode && e->episode->title) {
|
||||
|
@ -686,6 +746,12 @@ static dvr_entry_t *_dvr_entry_update
|
|||
if (!de->de_title) de->de_title = lang_str_create();
|
||||
save = lang_str_add(de->de_title, title, lang, 1);
|
||||
}
|
||||
|
||||
/* EID */
|
||||
if (e && e->dvb_eid != de->de_dvb_eid) {
|
||||
de->de_dvb_eid = e->dvb_eid;
|
||||
save = 1;
|
||||
}
|
||||
|
||||
// TODO: description
|
||||
|
||||
|
@ -700,7 +766,8 @@ static dvr_entry_t *_dvr_entry_update
|
|||
|
||||
/* Broadcast */
|
||||
if (e && (de->de_bcast != e)) {
|
||||
de->de_bcast->putref(de->de_bcast);
|
||||
if (de->de_bcast)
|
||||
de->de_bcast->putref(de->de_bcast);
|
||||
de->de_bcast = e;
|
||||
e->getref(e);
|
||||
save = 1;
|
||||
|
@ -712,7 +779,7 @@ static dvr_entry_t *_dvr_entry_update
|
|||
htsp_dvr_entry_update(de);
|
||||
dvr_entry_notify(de);
|
||||
tvhlog(LOG_INFO, "dvr", "\"%s\" on \"%s\": Updated Timer",
|
||||
lang_str_get(de->de_title, NULL), de->de_channel->ch_name);
|
||||
lang_str_get(de->de_title, NULL), DVR_CH_NAME(de));
|
||||
}
|
||||
|
||||
return de;
|
||||
|
@ -734,23 +801,53 @@ dvr_entry_update
|
|||
|
||||
/**
|
||||
* Used to notify the DVR that an event has been replaced in the EPG
|
||||
*
|
||||
* TODO: I think this will record the title slot event if its now a
|
||||
* completely different episode etc...
|
||||
*/
|
||||
void
|
||||
dvr_event_replaced(epg_broadcast_t *e, epg_broadcast_t *new_e)
|
||||
{
|
||||
dvr_entry_t *de, *ude;
|
||||
dvr_entry_t *de;
|
||||
assert(e != NULL);
|
||||
assert(new_e != NULL);
|
||||
|
||||
/* Ignore */
|
||||
if ( e == new_e ) return;
|
||||
|
||||
de = dvr_entry_find_by_event(e);
|
||||
if (de != NULL) {
|
||||
ude = dvr_entry_find_by_event_fuzzy(new_e);
|
||||
if (ude == NULL && de->de_sched_state == DVR_SCHEDULED)
|
||||
dvr_entry_cancel(de);
|
||||
else if(new_e->episode && new_e->episode->title)
|
||||
_dvr_entry_update(de, new_e, NULL, NULL, NULL, 0, 0, 0, 0);
|
||||
/* Existing entry */
|
||||
if ((de = dvr_entry_find_by_event(e))) {
|
||||
tvhtrace("dvr",
|
||||
"dvr entry %d event replaced %s on %s @ %"PRItime_t
|
||||
" to %"PRItime_t,
|
||||
de->de_id, epg_broadcast_get_title(e, NULL), e->channel->ch_name,
|
||||
e->start, e->stop);
|
||||
|
||||
/* Ignore - already in progress */
|
||||
if (de->de_sched_state != DVR_SCHEDULED)
|
||||
return;
|
||||
|
||||
/* Unlink the broadcast */
|
||||
e->putref(e);
|
||||
de->de_bcast = NULL;
|
||||
|
||||
/* If this was craeted by autorec - just remove it, it'll get recreated */
|
||||
if (de->de_autorec) {
|
||||
dvr_entry_remove(de);
|
||||
|
||||
/* Find match */
|
||||
} else {
|
||||
RB_FOREACH(e, &e->channel->ch_epg_schedule, sched_link) {
|
||||
if (dvr_entry_fuzzy_match(de, e)) {
|
||||
tvhtrace("dvr",
|
||||
" replacement event %s on %s @ %"PRItime_t
|
||||
" to %"PRItime_t,
|
||||
epg_broadcast_get_title(e, NULL), e->channel->ch_name,
|
||||
e->start, e->stop);
|
||||
e->getref(e);
|
||||
de->de_bcast = e;
|
||||
_dvr_entry_update(de, e, NULL, NULL, NULL, 0, 0, 0, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -758,7 +855,27 @@ void dvr_event_updated ( epg_broadcast_t *e )
|
|||
{
|
||||
dvr_entry_t *de;
|
||||
de = dvr_entry_find_by_event(e);
|
||||
if (de) _dvr_entry_update(de, e, NULL, NULL, NULL, 0, 0, 0, 0);
|
||||
if (de)
|
||||
_dvr_entry_update(de, e, NULL, NULL, NULL, 0, 0, 0, 0);
|
||||
else {
|
||||
LIST_FOREACH(de, &dvrentries, de_global_link) {
|
||||
if (de->de_sched_state != DVR_SCHEDULED) continue;
|
||||
if (de->de_bcast) continue;
|
||||
if (de->de_channel != e->channel) continue;
|
||||
if (dvr_entry_fuzzy_match(de, e)) {
|
||||
tvhtrace("dvr",
|
||||
"dvr entry %d link to event %s on %s @ %"PRItime_t
|
||||
" to %"PRItime_t,
|
||||
de->de_id, epg_broadcast_get_title(e, NULL),
|
||||
e->channel->ch_name,
|
||||
e->start, e->stop);
|
||||
e->getref(e);
|
||||
de->de_bcast = e;
|
||||
_dvr_entry_update(de, e, NULL, NULL, NULL, 0, 0, 0, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -772,13 +889,13 @@ dvr_stop_recording(dvr_entry_t *de, int stopcode)
|
|||
if (de->de_rec_state == DVR_RS_PENDING || de->de_rec_state == DVR_RS_WAIT_PROGRAM_START)
|
||||
de->de_sched_state = DVR_MISSED_TIME;
|
||||
else
|
||||
de->de_sched_state = DVR_COMPLETED;
|
||||
_dvr_entry_completed(de);
|
||||
|
||||
dvr_rec_unsubscribe(de, stopcode);
|
||||
|
||||
tvhlog(LOG_INFO, "dvr", "\"%s\" on \"%s\": "
|
||||
"End of program: %s",
|
||||
lang_str_get(de->de_title, NULL), de->de_channel->ch_name,
|
||||
lang_str_get(de->de_title, NULL), DVR_CH_NAME(de),
|
||||
dvr_entry_status(de));
|
||||
|
||||
dvr_entry_save(de);
|
||||
|
@ -813,7 +930,7 @@ dvr_timer_start_recording(void *aux)
|
|||
de->de_rec_state = DVR_RS_PENDING;
|
||||
|
||||
tvhlog(LOG_INFO, "dvr", "\"%s\" on \"%s\" recorder starting",
|
||||
lang_str_get(de->de_title, NULL), de->de_channel->ch_name);
|
||||
lang_str_get(de->de_title, NULL), DVR_CH_NAME(de));
|
||||
|
||||
dvr_entry_notify(de);
|
||||
htsp_dvr_entry_update(de);
|
||||
|
@ -846,29 +963,13 @@ dvr_entry_find_by_event(epg_broadcast_t *e)
|
|||
{
|
||||
dvr_entry_t *de;
|
||||
|
||||
if(!e->channel) return NULL;
|
||||
|
||||
LIST_FOREACH(de, &e->channel->ch_dvrs, de_channel_link)
|
||||
if(de->de_bcast == e) return de;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find dvr entry using 'fuzzy' search
|
||||
*/
|
||||
dvr_entry_t *
|
||||
dvr_entry_find_by_event_fuzzy(epg_broadcast_t *e)
|
||||
{
|
||||
dvr_entry_t *de;
|
||||
|
||||
if (!e->episode || !e->episode->title)
|
||||
return NULL;
|
||||
|
||||
LIST_FOREACH(de, &e->channel->ch_dvrs, de_channel_link)
|
||||
if ((abs(de->de_start - e->start) < 600) && (abs(de->de_stop - e->stop) < 600)) {
|
||||
return de;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find DVR entry based on an episode
|
||||
*/
|
||||
|
@ -926,8 +1027,6 @@ dvr_entry_purge(dvr_entry_t *de)
|
|||
{
|
||||
if(de->de_sched_state == DVR_RECORDING)
|
||||
dvr_stop_recording(de, SM_CODE_SOURCE_DELETED);
|
||||
|
||||
dvr_entry_remove(de);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -938,8 +1037,12 @@ dvr_destroy_by_channel(channel_t *ch)
|
|||
{
|
||||
dvr_entry_t *de;
|
||||
|
||||
while((de = LIST_FIRST(&ch->ch_dvrs)) != NULL)
|
||||
while((de = LIST_FIRST(&ch->ch_dvrs)) != NULL) {
|
||||
LIST_REMOVE(de, de_channel_link);
|
||||
de->de_channel = NULL;
|
||||
de->de_channel_name = strdup(ch->ch_name);
|
||||
dvr_entry_purge(de);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1015,6 +1118,9 @@ dvr_init(void)
|
|||
if(!htsmsg_get_u32(m, "tag-files", &u32) && !u32)
|
||||
cfg->dvr_flags &= ~DVR_TAG_FILES;
|
||||
|
||||
if(!htsmsg_get_u32(m, "skip-commercials", &u32) && !u32)
|
||||
cfg->dvr_flags &= ~DVR_SKIP_COMMERCIALS;
|
||||
|
||||
tvh_str_set(&cfg->dvr_postproc, htsmsg_get_str(m, "postproc"));
|
||||
}
|
||||
|
||||
|
@ -1047,9 +1153,12 @@ dvr_init(void)
|
|||
}
|
||||
}
|
||||
|
||||
dvr_db_load();
|
||||
|
||||
#if ENABLE_INOTIFY
|
||||
dvr_inotify_init();
|
||||
#endif
|
||||
dvr_autorec_init();
|
||||
dvr_db_load();
|
||||
dvr_autorec_update();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1110,7 +1219,7 @@ dvr_config_create(const char *name)
|
|||
cfg->dvr_config_name = strdup(name);
|
||||
cfg->dvr_retention_days = 31;
|
||||
cfg->dvr_mc = MC_MATROSKA;
|
||||
cfg->dvr_flags = DVR_TAG_FILES;
|
||||
cfg->dvr_flags = DVR_TAG_FILES | DVR_SKIP_COMMERCIALS;
|
||||
|
||||
/* series link support */
|
||||
cfg->dvr_sl_brand_lock = 1; // use brand linking
|
||||
|
@ -1175,6 +1284,7 @@ dvr_save(dvr_config_t *cfg)
|
|||
htsmsg_add_u32(m, "episode-in-title", !!(cfg->dvr_flags & DVR_EPISODE_IN_TITLE));
|
||||
htsmsg_add_u32(m, "clean-title", !!(cfg->dvr_flags & DVR_CLEAN_TITLE));
|
||||
htsmsg_add_u32(m, "tag-files", !!(cfg->dvr_flags & DVR_TAG_FILES));
|
||||
htsmsg_add_u32(m, "skip-commercials", !!(cfg->dvr_flags & DVR_SKIP_COMMERCIALS));
|
||||
if(cfg->dvr_postproc != NULL)
|
||||
htsmsg_add_str(m, "postproc", cfg->dvr_postproc);
|
||||
|
||||
|
@ -1312,6 +1422,22 @@ dvr_query_add_entry(dvr_query_result_t *dqr, dvr_entry_t *de)
|
|||
dqr->dqr_array[dqr->dqr_entries++] = de;
|
||||
}
|
||||
|
||||
void
|
||||
dvr_query_filter(dvr_query_result_t *dqr, dvr_entry_filter filter)
|
||||
{
|
||||
dvr_entry_t *de;
|
||||
|
||||
memset(dqr, 0, sizeof(dvr_query_result_t));
|
||||
|
||||
LIST_FOREACH(de, &dvrentries, de_global_link)
|
||||
if (filter(de))
|
||||
dvr_query_add_entry(dqr, de);
|
||||
}
|
||||
|
||||
static int all_filter(dvr_entry_t *entry)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -1319,15 +1445,9 @@ dvr_query_add_entry(dvr_query_result_t *dqr, dvr_entry_t *de)
|
|||
void
|
||||
dvr_query(dvr_query_result_t *dqr)
|
||||
{
|
||||
dvr_entry_t *de;
|
||||
|
||||
memset(dqr, 0, sizeof(dvr_query_result_t));
|
||||
|
||||
LIST_FOREACH(de, &dvrentries, de_global_link)
|
||||
dvr_query_add_entry(dqr, de);
|
||||
return dvr_query_filter(dqr, all_filter);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -1340,7 +1460,7 @@ dvr_query_free(dvr_query_result_t *dqr)
|
|||
/**
|
||||
* Sorting functions
|
||||
*/
|
||||
static int
|
||||
int
|
||||
dvr_sort_start_descending(const void *A, const void *B)
|
||||
{
|
||||
dvr_entry_t *a = *(dvr_entry_t **)A;
|
||||
|
@ -1348,36 +1468,44 @@ dvr_sort_start_descending(const void *A, const void *B)
|
|||
return b->de_start - a->de_start;
|
||||
}
|
||||
|
||||
int
|
||||
dvr_sort_start_ascending(const void *A, const void *B)
|
||||
{
|
||||
return -dvr_sort_start_descending(A, B);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
dvr_query_sort(dvr_query_result_t *dqr)
|
||||
dvr_query_sort_cmp(dvr_query_result_t *dqr, dvr_entry_comparator sf)
|
||||
{
|
||||
int (*sf)(const void *a, const void *b);
|
||||
|
||||
if(dqr->dqr_array == NULL)
|
||||
return;
|
||||
|
||||
sf = dvr_sort_start_descending;
|
||||
qsort(dqr->dqr_array, dqr->dqr_entries, sizeof(dvr_entry_t *), sf);
|
||||
}
|
||||
|
||||
void
|
||||
dvr_query_sort(dvr_query_result_t *dqr)
|
||||
{
|
||||
dvr_query_sort_cmp(dqr, dvr_sort_start_descending);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
off_t
|
||||
int64_t
|
||||
dvr_get_filesize(dvr_entry_t *de)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
if(de->de_filename == NULL)
|
||||
return 0;
|
||||
return -1;
|
||||
|
||||
if(stat(de->de_filename, &st) != 0)
|
||||
return 0;
|
||||
return -1;
|
||||
|
||||
return st.st_size;
|
||||
}
|
||||
|
@ -1415,6 +1543,9 @@ void
|
|||
dvr_entry_delete(dvr_entry_t *de)
|
||||
{
|
||||
if(de->de_filename != NULL) {
|
||||
#if ENABLE_INOTIFY
|
||||
dvr_inotify_del(de);
|
||||
#endif
|
||||
if(unlink(de->de_filename) && errno != ENOENT)
|
||||
tvhlog(LOG_WARNING, "dvr", "Unable to remove file '%s' from disk -- %s",
|
||||
de->de_filename, strerror(errno));
|
||||
|
|
305
src/dvr/dvr_inotify.c
Normal file
305
src/dvr/dvr_inotify.c
Normal file
|
@ -0,0 +1,305 @@
|
|||
/*
|
||||
* Digital Video Recorder - inotify processing
|
||||
* Copyright (C) 2012 Adam Sutton
|
||||
*
|
||||
* 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 <string.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include <poll.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "tvheadend.h"
|
||||
#include "redblack.h"
|
||||
#include "dvr/dvr.h"
|
||||
#include "htsp_server.h"
|
||||
|
||||
/* inotify limits */
|
||||
#define EVENT_SIZE ( sizeof (struct inotify_event) )
|
||||
#define EVENT_BUF_LEN ( 10 * ( EVENT_SIZE + 16 ) )
|
||||
#define EVENT_MASK IN_CREATE | IN_DELETE | IN_DELETE_SELF |\
|
||||
IN_MOVE_SELF | IN_MOVED_FROM | IN_MOVED_TO
|
||||
|
||||
static int _inot_fd;
|
||||
static RB_HEAD(,dvr_inotify_entry) _inot_tree;
|
||||
|
||||
typedef struct dvr_inotify_entry
|
||||
{
|
||||
RB_ENTRY(dvr_inotify_entry) link;
|
||||
char *path;
|
||||
int fd;
|
||||
struct dvr_entry_list entries;
|
||||
} dvr_inotify_entry_t;
|
||||
|
||||
static void* _dvr_inotify_thread ( void *p );
|
||||
|
||||
static int _str_cmp ( void *a, void *b )
|
||||
{
|
||||
return strcmp(((dvr_inotify_entry_t*)a)->path, ((dvr_inotify_entry_t*)b)->path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise threads
|
||||
*/
|
||||
void dvr_inotify_init ( void )
|
||||
{
|
||||
pthread_t tid;
|
||||
|
||||
_inot_fd = inotify_init();
|
||||
if (_inot_fd == -1) {
|
||||
tvhlog(LOG_ERR, "dvr", "failed to initialise inotify (err=%s)",
|
||||
strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_create(&tid, NULL, _dvr_inotify_thread, NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an entry for monitoring
|
||||
*/
|
||||
void dvr_inotify_add ( dvr_entry_t *de )
|
||||
{
|
||||
static dvr_inotify_entry_t *skel = NULL;
|
||||
dvr_inotify_entry_t *e;
|
||||
char *path;
|
||||
struct stat st;
|
||||
|
||||
if (_inot_fd == -1)
|
||||
return;
|
||||
|
||||
if (!de->de_filename || stat(de->de_filename, &st))
|
||||
return;
|
||||
|
||||
path = strdup(de->de_filename);
|
||||
|
||||
if (!skel)
|
||||
skel = calloc(1, sizeof(dvr_inotify_entry_t));
|
||||
skel->path = dirname(path);
|
||||
|
||||
if (stat(skel->path, &st))
|
||||
return;
|
||||
|
||||
e = RB_INSERT_SORTED(&_inot_tree, skel, link, _str_cmp);
|
||||
if (!e) {
|
||||
e = skel;
|
||||
skel = NULL;
|
||||
e->path = strdup(e->path);
|
||||
e->fd = inotify_add_watch(_inot_fd, e->path, EVENT_MASK);
|
||||
if (e->fd == -1) {
|
||||
tvhlog(LOG_ERR, "dvr", "failed to add inotify watch to %s (err=%s)",
|
||||
e->path, strerror(errno));
|
||||
free(path);
|
||||
dvr_inotify_del(de);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LIST_INSERT_HEAD(&e->entries, de, de_inotify_link);
|
||||
|
||||
free(path);
|
||||
}
|
||||
|
||||
/*
|
||||
* Delete an entry from the monitor
|
||||
*/
|
||||
void dvr_inotify_del ( dvr_entry_t *de )
|
||||
{
|
||||
dvr_entry_t *det;
|
||||
dvr_inotify_entry_t *e;
|
||||
RB_FOREACH(e, &_inot_tree, link) {
|
||||
LIST_FOREACH(det, &e->entries, de_inotify_link)
|
||||
if (det == de) break;
|
||||
if (det) break;
|
||||
}
|
||||
|
||||
if (e && det) {
|
||||
LIST_REMOVE(det, de_inotify_link);
|
||||
if (LIST_FIRST(&e->entries) == NULL) {
|
||||
RB_REMOVE(&_inot_tree, e, link);
|
||||
inotify_rm_watch(_inot_fd, e->fd);
|
||||
free(e->path);
|
||||
free(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Find inotify entry
|
||||
*/
|
||||
static dvr_inotify_entry_t *
|
||||
_dvr_inotify_find
|
||||
( int fd )
|
||||
{
|
||||
dvr_inotify_entry_t *e = NULL;
|
||||
RB_FOREACH(e, &_inot_tree, link)
|
||||
if (e->fd == fd)
|
||||
break;
|
||||
return e;
|
||||
}
|
||||
|
||||
/*
|
||||
* Find DVR entry
|
||||
*/
|
||||
static dvr_entry_t *
|
||||
_dvr_inotify_find2
|
||||
( dvr_inotify_entry_t *die, const char *name )
|
||||
{
|
||||
dvr_entry_t *de = NULL;
|
||||
char path[512];
|
||||
|
||||
snprintf(path, sizeof(path), "%s/%s", die->path, name);
|
||||
|
||||
LIST_FOREACH(de, &die->entries, de_inotify_link)
|
||||
if (!strcmp(path, de->de_filename))
|
||||
break;
|
||||
|
||||
return de;
|
||||
}
|
||||
|
||||
/*
|
||||
* File moved
|
||||
*/
|
||||
static void
|
||||
_dvr_inotify_moved
|
||||
( int fd, const char *from, const char *to )
|
||||
{
|
||||
dvr_inotify_entry_t *die;
|
||||
dvr_entry_t *de;
|
||||
|
||||
if (!(die = _dvr_inotify_find(fd)))
|
||||
return;
|
||||
|
||||
if (!(de = _dvr_inotify_find2(die, from)))
|
||||
return;
|
||||
|
||||
if (to) {
|
||||
char path[512];
|
||||
snprintf(path, sizeof(path), "%s/%s", die->path, to);
|
||||
tvh_str_update(&de->de_filename, path);
|
||||
dvr_entry_save(de);
|
||||
} else
|
||||
dvr_inotify_del(de);
|
||||
|
||||
htsp_dvr_entry_update(de);
|
||||
dvr_entry_notify(de);
|
||||
}
|
||||
|
||||
/*
|
||||
* File deleted
|
||||
*/
|
||||
static void
|
||||
_dvr_inotify_delete
|
||||
( int fd, const char *path )
|
||||
{
|
||||
_dvr_inotify_moved(fd, path, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Directory moved
|
||||
*/
|
||||
static void
|
||||
_dvr_inotify_moved_all
|
||||
( int fd, const char *to )
|
||||
{
|
||||
dvr_entry_t *de;
|
||||
dvr_inotify_entry_t *die;
|
||||
|
||||
if (!(die = _dvr_inotify_find(fd)))
|
||||
return;
|
||||
|
||||
while ((de = LIST_FIRST(&die->entries))) {
|
||||
htsp_dvr_entry_update(de);
|
||||
dvr_entry_notify(de);
|
||||
dvr_inotify_del(de);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Directory deleted
|
||||
*/
|
||||
static void
|
||||
_dvr_inotify_delete_all
|
||||
( int fd )
|
||||
{
|
||||
_dvr_inotify_moved_all(fd, NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Process events
|
||||
*/
|
||||
void* _dvr_inotify_thread ( void *p )
|
||||
{
|
||||
int i, len;
|
||||
char buf[EVENT_BUF_LEN];
|
||||
const char *from;
|
||||
int fromfd;
|
||||
int cookie;
|
||||
|
||||
while (1) {
|
||||
|
||||
/* Read events */
|
||||
fromfd = 0;
|
||||
cookie = 0;
|
||||
from = NULL;
|
||||
i = 0;
|
||||
len = read(_inot_fd, buf, EVENT_BUF_LEN);
|
||||
|
||||
/* Process */
|
||||
pthread_mutex_lock(&global_lock);
|
||||
while ( i < len ) {
|
||||
struct inotify_event *ev = (struct inotify_event*)&buf[i];
|
||||
i += EVENT_SIZE + ev->len;
|
||||
|
||||
/* Moved */
|
||||
if (ev->mask & IN_MOVED_FROM) {
|
||||
from = ev->name;
|
||||
fromfd = ev->wd;
|
||||
cookie = ev->cookie;
|
||||
continue;
|
||||
|
||||
} else if ((ev->mask & IN_MOVED_TO) && from && ev->cookie == cookie) {
|
||||
_dvr_inotify_moved(ev->wd, from, ev->name);
|
||||
from = NULL;
|
||||
|
||||
/* Removed */
|
||||
} else if (ev->mask & IN_DELETE) {
|
||||
_dvr_inotify_delete(ev->wd, ev->name);
|
||||
|
||||
/* Moved self */
|
||||
} else if (ev->mask & IN_MOVE_SELF) {
|
||||
_dvr_inotify_moved_all(ev->wd, NULL);
|
||||
|
||||
/* Removed self */
|
||||
} else if (ev->mask & IN_DELETE_SELF) {
|
||||
_dvr_inotify_delete_all(ev->wd);
|
||||
}
|
||||
|
||||
if (from) {
|
||||
_dvr_inotify_moved(fromfd, from, NULL);
|
||||
from = NULL;
|
||||
cookie = 0;
|
||||
}
|
||||
}
|
||||
if (from)
|
||||
_dvr_inotify_moved(fromfd, from, NULL);
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
232
src/dvr/dvr_rec.c
Executable file → Normal file
232
src/dvr/dvr_rec.c
Executable file → Normal file
|
@ -32,6 +32,7 @@
|
|||
#include "service.h"
|
||||
#include "plumbing/tsfix.h"
|
||||
#include "plumbing/globalheaders.h"
|
||||
#include "htsp_server.h"
|
||||
|
||||
#include "muxer.h"
|
||||
|
||||
|
@ -80,13 +81,14 @@ dvr_rec_subscribe(dvr_entry_t *de)
|
|||
} else {
|
||||
streaming_queue_init(&de->de_sq, 0);
|
||||
de->de_gh = globalheaders_create(&de->de_sq.sq_st);
|
||||
de->de_tsfix = tsfix_create(de->de_gh);
|
||||
st = de->de_tsfix;
|
||||
st = de->de_tsfix = tsfix_create(de->de_gh);
|
||||
tsfix_set_start_time(de->de_tsfix, de->de_start - (60 * de->de_start_extra));
|
||||
flags = 0;
|
||||
}
|
||||
|
||||
de->de_s = subscription_create_from_channel(de->de_channel, weight,
|
||||
buf, st, flags);
|
||||
buf, st, flags,
|
||||
NULL, NULL, NULL);
|
||||
|
||||
pthread_create(&de->de_thread, NULL, dvr_thread, de);
|
||||
}
|
||||
|
@ -116,55 +118,6 @@ dvr_rec_unsubscribe(dvr_entry_t *de, int stopcode)
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static int
|
||||
makedirs(const char *path)
|
||||
{
|
||||
struct stat st;
|
||||
char *p;
|
||||
int l, r;
|
||||
|
||||
if(stat(path, &st) == 0 && S_ISDIR(st.st_mode))
|
||||
return 0; /* Dir already there */
|
||||
|
||||
if(mkdir(path, 0777) == 0)
|
||||
return 0; /* Dir created ok */
|
||||
|
||||
if(errno == ENOENT) {
|
||||
|
||||
/* Parent does not exist, try to create it */
|
||||
/* Allocate new path buffer and strip off last directory component */
|
||||
|
||||
l = strlen(path);
|
||||
p = alloca(l + 1);
|
||||
memcpy(p, path, l);
|
||||
p[l--] = 0;
|
||||
|
||||
for(; l >= 0; l--)
|
||||
if(p[l] == '/')
|
||||
break;
|
||||
if(l == 0) {
|
||||
return ENOENT;
|
||||
}
|
||||
p[l] = 0;
|
||||
|
||||
if((r = makedirs(p)) != 0)
|
||||
return r;
|
||||
|
||||
/* Try again */
|
||||
if(mkdir(path, 0777) == 0)
|
||||
return 0; /* Dir created ok */
|
||||
}
|
||||
r = errno;
|
||||
|
||||
tvhlog(LOG_ERR, "dvr", "Unable to create directory \"%s\" -- %s",
|
||||
path, strerror(r));
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Replace various chars with a dash
|
||||
*/
|
||||
|
@ -173,12 +126,18 @@ cleanupfilename(char *s, int dvr_flags)
|
|||
{
|
||||
int i, len = strlen(s);
|
||||
for(i = 0; i < len; i++) {
|
||||
if(s[i] == '/' || s[i] == ':' || s[i] == '\\' || s[i] == '<' ||
|
||||
s[i] == '>' || s[i] == '|' || s[i] == '*' || s[i] == '?')
|
||||
|
||||
if(s[i] == '/')
|
||||
s[i] = '-';
|
||||
|
||||
if((dvr_flags & DVR_WHITESPACE_IN_TITLE) && s[i] == ' ')
|
||||
else if((dvr_flags & DVR_WHITESPACE_IN_TITLE) &&
|
||||
(s[i] == ' ' || s[i] == '\t'))
|
||||
s[i] = '-';
|
||||
|
||||
else if((dvr_flags & DVR_CLEAN_TITLE) &&
|
||||
((s[i] < 32) || (s[i] > 122) ||
|
||||
(strchr("/:\\<>|*?'\"", s[i]) != NULL)))
|
||||
s[i] = '-';
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,6 +164,11 @@ pvr_generate_filename(dvr_entry_t *de, const streaming_start_t *ss)
|
|||
|
||||
snprintf(path, sizeof(path), "%s", cfg->dvr_storage);
|
||||
|
||||
/* Remove trailing slash */
|
||||
|
||||
if (path[strlen(path)-1] == '/')
|
||||
path[strlen(path)-1] = '\0';
|
||||
|
||||
/* Append per-day directory */
|
||||
|
||||
if(cfg->dvr_flags & DVR_DIR_PER_DAY) {
|
||||
|
@ -219,7 +183,7 @@ pvr_generate_filename(dvr_entry_t *de, const streaming_start_t *ss)
|
|||
|
||||
if(cfg->dvr_flags & DVR_DIR_PER_CHANNEL) {
|
||||
|
||||
char *chname = strdup(de->de_channel->ch_name);
|
||||
char *chname = strdup(DVR_CH_NAME(de));
|
||||
cleanupfilename(chname,cfg->dvr_flags);
|
||||
snprintf(path + strlen(path), sizeof(path) - strlen(path),
|
||||
"/%s", chname);
|
||||
|
@ -241,7 +205,7 @@ pvr_generate_filename(dvr_entry_t *de, const streaming_start_t *ss)
|
|||
|
||||
|
||||
/* */
|
||||
if(makedirs(path) != 0) {
|
||||
if(makedirs(path, 0777) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -316,7 +280,7 @@ dvr_rec_set_state(dvr_entry_t *de, dvr_rs_state_t newstate, int error)
|
|||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
static int
|
||||
dvr_rec_start(dvr_entry_t *de, const streaming_start_t *ss)
|
||||
{
|
||||
const source_info_t *si = &ss->ss_si;
|
||||
|
@ -324,31 +288,31 @@ dvr_rec_start(dvr_entry_t *de, const streaming_start_t *ss)
|
|||
int i;
|
||||
dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name);
|
||||
|
||||
de->de_mux = muxer_create(de->de_s->ths_service, de->de_mc);
|
||||
de->de_mux = muxer_create(de->de_mc);
|
||||
if(!de->de_mux) {
|
||||
dvr_rec_fatal_error(de, "Unable to create muxer");
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(pvr_generate_filename(de, ss) != 0) {
|
||||
dvr_rec_fatal_error(de, "Unable to create directories");
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(muxer_open_file(de->de_mux, de->de_filename)) {
|
||||
dvr_rec_fatal_error(de, "Unable to open file");
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(muxer_init(de->de_mux, ss, lang_str_get(de->de_title, NULL))) {
|
||||
dvr_rec_fatal_error(de, "Unable to init file");
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(cfg->dvr_flags & DVR_TAG_FILES) {
|
||||
if(cfg->dvr_flags & DVR_TAG_FILES && de->de_bcast) {
|
||||
if(muxer_write_meta(de->de_mux, de->de_bcast)) {
|
||||
dvr_rec_fatal_error(de, "Unable to write meta data");
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -365,25 +329,32 @@ dvr_rec_start(dvr_entry_t *de, const streaming_start_t *ss)
|
|||
|
||||
|
||||
tvhlog(LOG_INFO, "dvr",
|
||||
" # %-20s %-4s %-16s %-10s %-10s",
|
||||
" # %-16s %-4s %-10s %-12s %-11s %-8s",
|
||||
"type",
|
||||
"lang",
|
||||
"resolution",
|
||||
"samplerate",
|
||||
"aspect ratio",
|
||||
"sample rate",
|
||||
"channels");
|
||||
|
||||
for(i = 0; i < ss->ss_num_components; i++) {
|
||||
ssc = &ss->ss_components[i];
|
||||
|
||||
char res[16];
|
||||
char res[11];
|
||||
char asp[6];
|
||||
char sr[6];
|
||||
char ch[7];
|
||||
|
||||
if(SCT_ISAUDIO(ssc->ssc_type)) {
|
||||
snprintf(sr, sizeof(sr), "%d", sri_to_rate(ssc->ssc_sri));
|
||||
if(ssc->ssc_sri)
|
||||
snprintf(sr, sizeof(sr), "%d", sri_to_rate(ssc->ssc_sri));
|
||||
else
|
||||
strcpy(sr, "?");
|
||||
|
||||
if(ssc->ssc_channels == 6)
|
||||
snprintf(ch, sizeof(ch), "5.1");
|
||||
else if(ssc->ssc_channels == 0)
|
||||
strcpy(ch, "?");
|
||||
else
|
||||
snprintf(ch, sizeof(ch), "%d", ssc->ssc_channels);
|
||||
} else {
|
||||
|
@ -391,24 +362,39 @@ dvr_rec_start(dvr_entry_t *de, const streaming_start_t *ss)
|
|||
ch[0] = 0;
|
||||
}
|
||||
|
||||
|
||||
if(SCT_ISVIDEO(ssc->ssc_type)) {
|
||||
snprintf(res, sizeof(res), "%d x %d",
|
||||
ssc->ssc_width, ssc->ssc_height);
|
||||
if(ssc->ssc_width && ssc->ssc_height)
|
||||
snprintf(res, sizeof(res), "%dx%d",
|
||||
ssc->ssc_width, ssc->ssc_height);
|
||||
else
|
||||
strcpy(res, "?");
|
||||
} else {
|
||||
res[0] = 0;
|
||||
}
|
||||
|
||||
if(SCT_ISVIDEO(ssc->ssc_type)) {
|
||||
if(ssc->ssc_aspect_num && ssc->ssc_aspect_den)
|
||||
snprintf(asp, sizeof(asp), "%d:%d",
|
||||
ssc->ssc_aspect_num, ssc->ssc_aspect_den);
|
||||
else
|
||||
strcpy(asp, "?");
|
||||
} else {
|
||||
asp[0] = 0;
|
||||
}
|
||||
|
||||
tvhlog(LOG_INFO, "dvr",
|
||||
"%2d %-20s %-4s %-16s %-10s %-10s %s",
|
||||
"%2d %-16s %-4s %-10s %-12s %-11s %-8s %s",
|
||||
ssc->ssc_index,
|
||||
streaming_component_type2txt(ssc->ssc_type),
|
||||
ssc->ssc_lang,
|
||||
res,
|
||||
asp,
|
||||
sr,
|
||||
ch,
|
||||
ssc->ssc_disabled ? "<disabled, no valid input>" : "");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
@ -419,9 +405,14 @@ static void *
|
|||
dvr_thread(void *aux)
|
||||
{
|
||||
dvr_entry_t *de = aux;
|
||||
dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name);
|
||||
streaming_queue_t *sq = &de->de_sq;
|
||||
streaming_message_t *sm;
|
||||
th_pkt_t *pkt;
|
||||
int run = 1;
|
||||
int started = 0;
|
||||
int comm_skip = (cfg->dvr_flags & DVR_SKIP_COMMERCIALS);
|
||||
int commercial = COMMERCIAL_UNKNOWN;
|
||||
|
||||
pthread_mutex_lock(&sq->sq_mutex);
|
||||
|
||||
|
@ -437,47 +428,89 @@ dvr_thread(void *aux)
|
|||
pthread_mutex_unlock(&sq->sq_mutex);
|
||||
|
||||
switch(sm->sm_type) {
|
||||
case SMT_MPEGTS:
|
||||
|
||||
case SMT_PACKET:
|
||||
if(dispatch_clock > de->de_start - (60 * de->de_start_extra)) {
|
||||
pkt = sm->sm_data;
|
||||
if(pkt->pkt_commercial == COMMERCIAL_YES)
|
||||
dvr_rec_set_state(de, DVR_RS_COMMERCIAL, 0);
|
||||
else
|
||||
dvr_rec_set_state(de, DVR_RS_RUNNING, 0);
|
||||
|
||||
if(!muxer_write_pkt(de->de_mux, sm->sm_data))
|
||||
sm->sm_data = NULL;
|
||||
if(pkt->pkt_commercial == COMMERCIAL_YES && comm_skip)
|
||||
break;
|
||||
|
||||
if(commercial != pkt->pkt_commercial)
|
||||
muxer_add_marker(de->de_mux);
|
||||
|
||||
commercial = pkt->pkt_commercial;
|
||||
|
||||
if(started) {
|
||||
muxer_write_pkt(de->de_mux, sm->sm_type, sm->sm_data);
|
||||
sm->sm_data = NULL;
|
||||
}
|
||||
break;
|
||||
|
||||
case SMT_MPEGTS:
|
||||
if(started) {
|
||||
dvr_rec_set_state(de, DVR_RS_RUNNING, 0);
|
||||
muxer_write_pkt(de->de_mux, sm->sm_type, sm->sm_data);
|
||||
sm->sm_data = NULL;
|
||||
}
|
||||
break;
|
||||
|
||||
case SMT_START:
|
||||
pthread_mutex_lock(&global_lock);
|
||||
dvr_rec_set_state(de, DVR_RS_WAIT_PROGRAM_START, 0);
|
||||
dvr_rec_start(de, sm->sm_data);
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
if(started &&
|
||||
muxer_reconfigure(de->de_mux, sm->sm_data) < 0) {
|
||||
tvhlog(LOG_WARNING,
|
||||
"dvr", "Unable to reconfigure \"%s\"",
|
||||
de->de_filename ?: lang_str_get(de->de_title, NULL));
|
||||
|
||||
// Try to restart the recording if the muxer doesn't
|
||||
// support reconfiguration of the streams.
|
||||
dvr_thread_epilog(de);
|
||||
started = 0;
|
||||
}
|
||||
|
||||
if(!started) {
|
||||
pthread_mutex_lock(&global_lock);
|
||||
dvr_rec_set_state(de, DVR_RS_WAIT_PROGRAM_START, 0);
|
||||
if(dvr_rec_start(de, sm->sm_data) == 0) {
|
||||
started = 1;
|
||||
dvr_entry_notify(de);
|
||||
htsp_dvr_entry_update(de);
|
||||
dvr_entry_save(de);
|
||||
}
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
}
|
||||
break;
|
||||
|
||||
case SMT_STOP:
|
||||
if(sm->sm_code == SM_CODE_SOURCE_RECONFIGURED) {
|
||||
// Subscription is restarting, wait for SMT_START
|
||||
|
||||
if(sm->sm_code == 0) {
|
||||
/* Completed */
|
||||
} else if(sm->sm_code == 0) {
|
||||
// Recording is completed
|
||||
|
||||
de->de_last_error = 0;
|
||||
|
||||
tvhlog(LOG_INFO,
|
||||
"dvr", "Recording completed: \"%s\"",
|
||||
de->de_filename ?: lang_str_get(de->de_title, NULL));
|
||||
|
||||
} else {
|
||||
dvr_thread_epilog(de);
|
||||
started = 0;
|
||||
|
||||
if(de->de_last_error != sm->sm_code) {
|
||||
dvr_rec_set_state(de, DVR_RS_ERROR, sm->sm_code);
|
||||
}else if(de->de_last_error != sm->sm_code) {
|
||||
// Error during recording
|
||||
|
||||
tvhlog(LOG_ERR,
|
||||
"dvr", "Recording stopped: \"%s\": %s",
|
||||
de->de_filename ?: lang_str_get(de->de_title, NULL),
|
||||
streaming_code2txt(sm->sm_code));
|
||||
}
|
||||
dvr_rec_set_state(de, DVR_RS_ERROR, sm->sm_code);
|
||||
tvhlog(LOG_ERR,
|
||||
"dvr", "Recording stopped: \"%s\": %s",
|
||||
de->de_filename ?: lang_str_get(de->de_title, NULL),
|
||||
streaming_code2txt(sm->sm_code));
|
||||
|
||||
dvr_thread_epilog(de);
|
||||
started = 0;
|
||||
}
|
||||
|
||||
dvr_thread_epilog(de);
|
||||
break;
|
||||
|
||||
case SMT_SERVICE_STATUS:
|
||||
|
@ -516,7 +549,10 @@ dvr_thread(void *aux)
|
|||
}
|
||||
break;
|
||||
|
||||
case SMT_SPEED:
|
||||
case SMT_SKIP:
|
||||
case SMT_SIGNAL_STATUS:
|
||||
case SMT_TIMESHIFT_STATUS:
|
||||
break;
|
||||
|
||||
case SMT_EXIT:
|
||||
|
@ -528,6 +564,10 @@ dvr_thread(void *aux)
|
|||
pthread_mutex_lock(&sq->sq_mutex);
|
||||
}
|
||||
pthread_mutex_unlock(&sq->sq_mutex);
|
||||
|
||||
if(de->de_mux)
|
||||
dvr_thread_epilog(de);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -559,7 +599,7 @@ dvr_spawn_postproc(dvr_entry_t *de, const char *dvr_postproc)
|
|||
memset(fmap, 0, sizeof(fmap));
|
||||
fmap['f'] = de->de_filename; /* full path to recoding */
|
||||
fmap['b'] = basename(fbasename); /* basename of recoding */
|
||||
fmap['c'] = de->de_channel->ch_name; /* channel name */
|
||||
fmap['c'] = DVR_CH_NAME(de); /* channel name */
|
||||
fmap['C'] = de->de_creator; /* user who created this recording */
|
||||
fmap['t'] = lang_str_get(de->de_title, NULL); /* program title */
|
||||
fmap['d'] = lang_str_get(de->de_desc, NULL); /* program description */
|
||||
|
@ -595,6 +635,6 @@ dvr_thread_epilog(dvr_entry_t *de)
|
|||
de->de_mux = NULL;
|
||||
|
||||
dvr_config_t *cfg = dvr_config_find_by_name_default(de->de_config_name);
|
||||
if(cfg->dvr_postproc)
|
||||
if(cfg->dvr_postproc && de->de_filename)
|
||||
dvr_spawn_postproc(de,cfg->dvr_postproc);
|
||||
}
|
||||
|
|
355
src/epg.c
355
src/epg.c
|
@ -30,8 +30,9 @@
|
|||
#include "settings.h"
|
||||
#include "epg.h"
|
||||
#include "dvr/dvr.h"
|
||||
#include "htsp.h"
|
||||
#include "htsp_server.h"
|
||||
#include "epggrab.h"
|
||||
#include "imagecache.h"
|
||||
|
||||
/* Broadcast hashing */
|
||||
#define EPG_HASH_WIDTH 1024
|
||||
|
@ -99,8 +100,8 @@ void epg_updated ( void )
|
|||
|
||||
/* Remove unref'd */
|
||||
while ((eo = LIST_FIRST(&epg_object_unref))) {
|
||||
tvhlog(LOG_DEBUG, "epg",
|
||||
"unref'd object %u (%s) created during update", eo->id, eo->uri);
|
||||
tvhtrace("epg",
|
||||
"unref'd object %u (%s) created during update", eo->id, eo->uri);
|
||||
LIST_REMOVE(eo, un_link);
|
||||
eo->destroy(eo);
|
||||
}
|
||||
|
@ -125,10 +126,8 @@ static void _epg_object_destroy
|
|||
( epg_object_t *eo, epg_object_tree_t *tree )
|
||||
{
|
||||
assert(eo->refcount == 0);
|
||||
#ifdef EPG_TRACE
|
||||
tvhlog(LOG_DEBUG, "epg", "eo [%p, %u, %d, %s] destroy",
|
||||
eo, eo->id, eo->type, eo->uri);
|
||||
#endif
|
||||
tvhtrace("epg", "eo [%p, %u, %d, %s] destroy",
|
||||
eo, eo->id, eo->type, eo->uri);
|
||||
if (eo->uri) free(eo->uri);
|
||||
if (tree) RB_REMOVE(tree, eo, uri_link);
|
||||
if (eo->_updated) LIST_REMOVE(eo, up_link);
|
||||
|
@ -138,10 +137,8 @@ static void _epg_object_destroy
|
|||
static void _epg_object_getref ( void *o )
|
||||
{
|
||||
epg_object_t *eo = o;
|
||||
#ifdef EPG_TRACE
|
||||
tvhlog(LOG_DEBUG, "epg", "eo [%p, %u, %d, %s] getref %d",
|
||||
eo, eo->id, eo->type, eo->uri, eo->refcount+1);
|
||||
#endif
|
||||
tvhtrace("epg", "eo [%p, %u, %d, %s] getref %d",
|
||||
eo, eo->id, eo->type, eo->uri, eo->refcount+1);
|
||||
if (eo->refcount == 0) LIST_REMOVE(eo, un_link);
|
||||
eo->refcount++;
|
||||
}
|
||||
|
@ -149,10 +146,8 @@ static void _epg_object_getref ( void *o )
|
|||
static void _epg_object_putref ( void *o )
|
||||
{
|
||||
epg_object_t *eo = o;
|
||||
#ifdef EPG_TRACE
|
||||
tvhlog(LOG_DEBUG, "epg", "eo [%p, %u, %d, %s] putref %d",
|
||||
eo, eo->id, eo->type, eo->uri, eo->refcount-1);
|
||||
#endif
|
||||
tvhtrace("epg", "eo [%p, %u, %d, %s] putref %d",
|
||||
eo, eo->id, eo->type, eo->uri, eo->refcount-1);
|
||||
assert(eo->refcount>0);
|
||||
eo->refcount--;
|
||||
if (!eo->refcount) eo->destroy(eo);
|
||||
|
@ -162,10 +157,8 @@ static void _epg_object_set_updated ( void *o )
|
|||
{
|
||||
epg_object_t *eo = o;
|
||||
if (!eo->_updated) {
|
||||
#ifdef EPG_TRACE
|
||||
tvhlog(LOG_DEBUG, "epg", "eo [%p, %u, %d, %s] updated",
|
||||
eo, eo->id, eo->type, eo->uri);
|
||||
#endif
|
||||
tvhtrace("epg", "eo [%p, %u, %d, %s] updated",
|
||||
eo, eo->id, eo->type, eo->uri);
|
||||
eo->_updated = 1;
|
||||
eo->updated = dispatch_clock;
|
||||
LIST_INSERT_HEAD(&epg_object_updated, eo, up_link);
|
||||
|
@ -179,10 +172,8 @@ static void _epg_object_create ( void *o )
|
|||
else if (eo->id > _epg_object_idx) _epg_object_idx = eo->id;
|
||||
if (!eo->getref) eo->getref = _epg_object_getref;
|
||||
if (!eo->putref) eo->putref = _epg_object_putref;
|
||||
#ifdef EPG_TRACE
|
||||
tvhlog(LOG_DEBUG, "epg", "eo [%p, %u, %d, %s] created",
|
||||
eo, eo->id, eo->type, eo->uri);
|
||||
#endif
|
||||
tvhtrace("epg", "eo [%p, %u, %d, %s] created",
|
||||
eo, eo->id, eo->type, eo->uri);
|
||||
_epg_object_set_updated(eo);
|
||||
LIST_INSERT_HEAD(&epg_object_unref, eo, un_link);
|
||||
LIST_INSERT_HEAD(&epg_objects[eo->id & EPG_HASH_MASK], eo, id_link);
|
||||
|
@ -230,10 +221,8 @@ epg_object_t *epg_object_find_by_id ( uint32_t id, epg_object_type_t type )
|
|||
static htsmsg_t * _epg_object_serialize ( void *o )
|
||||
{
|
||||
epg_object_t *eo = o;
|
||||
#ifdef EPG_TRACE
|
||||
tvhlog(LOG_DEBUG, "epg", "eo [%p, %u, %d, %s] serialize",
|
||||
eo, eo->id, eo->type, eo->uri);
|
||||
#endif
|
||||
tvhtrace("epg", "eo [%p, %u, %d, %s] serialize",
|
||||
eo, eo->id, eo->type, eo->uri);
|
||||
htsmsg_t *m;
|
||||
if ( !eo->id || !eo->type ) return NULL;
|
||||
m = htsmsg_create_map();
|
||||
|
@ -262,10 +251,8 @@ static epg_object_t *_epg_object_deserialize ( htsmsg_t *m, epg_object_t *eo )
|
|||
_epg_object_set_updated(eo);
|
||||
eo->updated = s64;
|
||||
}
|
||||
#ifdef EPG_TRACE
|
||||
tvhlog(LOG_DEBUG, "epg", "eo [%p, %u, %d, %s] deserialize",
|
||||
eo, eo->id, eo->type, eo->uri);
|
||||
#endif
|
||||
tvhtrace("epg", "eo [%p, %u, %d, %s] deserialize",
|
||||
eo, eo->id, eo->type, eo->uri);
|
||||
return eo;
|
||||
}
|
||||
|
||||
|
@ -459,8 +446,12 @@ int epg_brand_set_summary
|
|||
int epg_brand_set_image
|
||||
( epg_brand_t *brand, const char *image, epggrab_module_t *src )
|
||||
{
|
||||
int save;
|
||||
if (!brand || !image) return 0;
|
||||
return _epg_object_set_str(brand, &brand->image, image, src);
|
||||
save = _epg_object_set_str(brand, &brand->image, image, src);
|
||||
if (save)
|
||||
imagecache_get_id(image);
|
||||
return save;
|
||||
}
|
||||
|
||||
int epg_brand_set_season_count
|
||||
|
@ -628,8 +619,12 @@ int epg_season_set_summary
|
|||
int epg_season_set_image
|
||||
( epg_season_t *season, const char *image, epggrab_module_t *src )
|
||||
{
|
||||
int save;
|
||||
if (!season || !image) return 0;
|
||||
return _epg_object_set_str(season, &season->image, image, src);
|
||||
save = _epg_object_set_str(season, &season->image, image, src);
|
||||
if (save)
|
||||
imagecache_get_id(image);
|
||||
return save;
|
||||
}
|
||||
|
||||
int epg_season_set_episode_count
|
||||
|
@ -746,13 +741,13 @@ static htsmsg_t *epg_episode_num_serialize ( epg_episode_num_t *num )
|
|||
if (num->e_cnt)
|
||||
htsmsg_add_u32(m, "e_cnt", num->e_cnt);
|
||||
if (num->s_num)
|
||||
htsmsg_add_u32(m, "s_num", num->e_num);
|
||||
htsmsg_add_u32(m, "s_num", num->s_num);
|
||||
if (num->s_cnt)
|
||||
htsmsg_add_u32(m, "s_cnt", num->e_cnt);
|
||||
htsmsg_add_u32(m, "s_cnt", num->s_cnt);
|
||||
if (num->p_num)
|
||||
htsmsg_add_u32(m, "p_num", num->e_num);
|
||||
htsmsg_add_u32(m, "p_num", num->p_num);
|
||||
if (num->p_cnt)
|
||||
htsmsg_add_u32(m, "p_cnt", num->e_cnt);
|
||||
htsmsg_add_u32(m, "p_cnt", num->p_cnt);
|
||||
if (num->text)
|
||||
htsmsg_add_str(m, "text", num->text);
|
||||
return m;
|
||||
|
@ -891,8 +886,12 @@ int epg_episode_set_description
|
|||
int epg_episode_set_image
|
||||
( epg_episode_t *episode, const char *image, epggrab_module_t *src )
|
||||
{
|
||||
int save;
|
||||
if (!episode || !image) return 0;
|
||||
return _epg_object_set_str(episode, &episode->image, image, src);
|
||||
save = _epg_object_set_str(episode, &episode->image, image, src);
|
||||
if (save)
|
||||
imagecache_get_id(image);
|
||||
return save;
|
||||
}
|
||||
|
||||
int epg_episode_set_number
|
||||
|
@ -990,6 +989,7 @@ int epg_episode_set_genre
|
|||
g2 = LIST_NEXT(g1, link);
|
||||
if (!epg_genre_list_contains(genre, g1, 0)) {
|
||||
LIST_REMOVE(g1, link);
|
||||
free(g1);
|
||||
save = 1;
|
||||
}
|
||||
g1 = g2;
|
||||
|
@ -1077,8 +1077,8 @@ size_t epg_episode_number_format
|
|||
if ( cfmt && num.e_cnt )
|
||||
i+= snprintf(&buf[i], len-i, cfmt, num.e_cnt);
|
||||
} else if ( num.text ) {
|
||||
strncpy(buf, num.text, len);
|
||||
i = strlen(buf);
|
||||
if (pre) i += snprintf(&buf[i], len-i, "%s", pre);
|
||||
i += snprintf(&buf[i], len-i, "%s", num.text);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
@ -1377,8 +1377,8 @@ static void _epg_channel_timer_callback ( void *p )
|
|||
|
||||
/* Expire */
|
||||
if ( ebc->stop <= dispatch_clock ) {
|
||||
tvhlog(LOG_DEBUG, "epg", "expire event %u from %s",
|
||||
ebc->id, ch->ch_name);
|
||||
tvhlog(LOG_DEBUG, "epg", "expire event %u (%s) from %s",
|
||||
ebc->id, epg_broadcast_get_title(ebc, NULL), ch->ch_name);
|
||||
_epg_channel_rem_broadcast(ch, ebc, NULL);
|
||||
continue; // skip to next
|
||||
|
||||
|
@ -1447,6 +1447,8 @@ static epg_broadcast_t *_epg_channel_add_broadcast
|
|||
_epg_object_create(ret);
|
||||
// Note: sets updated
|
||||
_epg_object_getref(ret);
|
||||
tvhtrace("epg", "added event %u (%s) on %s @ %"PRItime_t " to %"PRItime_t,
|
||||
ret->id, epg_broadcast_get_title(ret, NULL), ch->ch_name, ret->start, ret->stop);
|
||||
|
||||
/* Existing */
|
||||
} else {
|
||||
|
@ -1460,6 +1462,8 @@ static epg_broadcast_t *_epg_channel_add_broadcast
|
|||
} else {
|
||||
ret->stop = (*bcast)->stop;
|
||||
_epg_object_set_updated(ret);
|
||||
tvhtrace("epg", "updated event %u (%s) on %s @ %"PRItime_t " to %"PRItime_t,
|
||||
ret->id, epg_broadcast_get_title(ret, NULL), ch->ch_name, ret->start, ret->stop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1470,12 +1474,16 @@ static epg_broadcast_t *_epg_channel_add_broadcast
|
|||
/* Remove overlapping (before) */
|
||||
while ( (ebc = RB_PREV(ret, sched_link)) != NULL ) {
|
||||
if ( ebc->stop <= ret->start ) break;
|
||||
tvhtrace("epg", "remove overlap (b) event %u (%s) on %s @ %"PRItime_t " to %"PRItime_t,
|
||||
ebc->id, epg_broadcast_get_title(ebc, NULL), ch->ch_name, ebc->start, ebc->stop);
|
||||
_epg_channel_rem_broadcast(ch, ebc, ret);
|
||||
}
|
||||
|
||||
/* Remove overlapping (after) */
|
||||
while ( (ebc = RB_NEXT(ret, sched_link)) != NULL ) {
|
||||
if ( ebc->start >= ret->stop ) break;
|
||||
tvhtrace("epg", "remove overlap (a) event %u (%s) on %s @ %"PRItime_t " to %"PRItime_t,
|
||||
ebc->id, epg_broadcast_get_title(ebc, NULL), ch->ch_name, ebc->start, ebc->stop);
|
||||
_epg_channel_rem_broadcast(ch, ebc, ret);
|
||||
}
|
||||
|
||||
|
@ -1839,10 +1847,15 @@ epg_broadcast_t *epg_broadcast_deserialize
|
|||
if (!htsmsg_get_u32(m, "is_repeat", &u32))
|
||||
*save |= epg_broadcast_set_is_repeat(ebc, u32, NULL);
|
||||
|
||||
if ((ls = lang_str_deserialize(m, "summary")))
|
||||
if ((ls = lang_str_deserialize(m, "summary"))) {
|
||||
*save |= epg_broadcast_set_summary2(ebc, ls, NULL);
|
||||
if ((ls = lang_str_deserialize(m, "description")))
|
||||
lang_str_destroy(ls);
|
||||
}
|
||||
|
||||
if ((ls = lang_str_deserialize(m, "description"))) {
|
||||
*save |= epg_broadcast_set_description2(ebc, ls, NULL);
|
||||
lang_str_destroy(ls);
|
||||
}
|
||||
|
||||
/* Series link */
|
||||
if ((str = htsmsg_get_str(m, "serieslink")))
|
||||
|
@ -1866,153 +1879,153 @@ epg_broadcast_t *epg_broadcast_deserialize
|
|||
static const char *_epg_genre_names[16][16] = {
|
||||
{ "" },
|
||||
{
|
||||
"Movie/Drama",
|
||||
"detective/thriller",
|
||||
"adventure/western/war",
|
||||
"science fiction/fantasy/horror",
|
||||
"comedy",
|
||||
"soap/melodrama/folkloric",
|
||||
"romance",
|
||||
"serious/classical/religious/historical movie/drama",
|
||||
"adult movie/drama",
|
||||
"adult movie/drama",
|
||||
"adult movie/drama",
|
||||
"adult movie/drama",
|
||||
"adult movie/drama",
|
||||
"adult movie/drama",
|
||||
"Movie / Drama",
|
||||
"Detective / Thriller",
|
||||
"Adventure / Western / War",
|
||||
"Science fiction / Fantasy / Horror",
|
||||
"Comedy",
|
||||
"Soap / Melodrama / Folkloric",
|
||||
"Romance",
|
||||
"Serious / Classical / Religious / Historical movie / Drama",
|
||||
"Adult movie / Drama",
|
||||
"Adult movie / Drama",
|
||||
"Adult movie / Drama",
|
||||
"Adult movie / Drama",
|
||||
"Adult movie / Drama",
|
||||
"Adult movie / Drama",
|
||||
},
|
||||
{
|
||||
"News/Current affairs",
|
||||
"news/weather report",
|
||||
"news magazine",
|
||||
"documentary",
|
||||
"discussion/interview/debate",
|
||||
"discussion/interview/debate",
|
||||
"discussion/interview/debate",
|
||||
"discussion/interview/debate",
|
||||
"discussion/interview/debate",
|
||||
"discussion/interview/debate",
|
||||
"discussion/interview/debate",
|
||||
"discussion/interview/debate",
|
||||
"discussion/interview/debate",
|
||||
"discussion/interview/debate",
|
||||
"News / Current affairs",
|
||||
"News / Weather report",
|
||||
"News magazine",
|
||||
"Documentary",
|
||||
"Discussion / Interview / Debate",
|
||||
"Discussion / Interview / Debate",
|
||||
"Discussion / Interview / Debate",
|
||||
"Discussion / Interview / Debate",
|
||||
"Discussion / Interview / Debate",
|
||||
"Discussion / Interview / Debate",
|
||||
"Discussion / Interview / Debate",
|
||||
"Discussion / Interview / Debate",
|
||||
"Discussion / Interview / Debate",
|
||||
"Discussion / Interview / Debate",
|
||||
},
|
||||
{
|
||||
"Show/Game show",
|
||||
"game show/quiz/contest",
|
||||
"variety show",
|
||||
"talk show",
|
||||
"talk show",
|
||||
"talk show",
|
||||
"talk show",
|
||||
"talk show",
|
||||
"talk show",
|
||||
"talk show",
|
||||
"talk show",
|
||||
"talk show",
|
||||
"talk show",
|
||||
"talk show",
|
||||
"Show / Game show",
|
||||
"Game show / Quiz / Contest",
|
||||
"Variety show",
|
||||
"Talk show",
|
||||
"Talk show",
|
||||
"Talk show",
|
||||
"Talk show",
|
||||
"Talk show",
|
||||
"Talk show",
|
||||
"Talk show",
|
||||
"Talk show",
|
||||
"Talk show",
|
||||
"Talk show",
|
||||
"Talk show",
|
||||
},
|
||||
{
|
||||
"Sports",
|
||||
"special events (Olympic Games, World Cup, etc.)",
|
||||
"sports magazines",
|
||||
"football/soccer",
|
||||
"tennis/squash",
|
||||
"team sports (excluding football)",
|
||||
"athletics",
|
||||
"motor sport",
|
||||
"water sport",
|
||||
"Special events (Olympic Games, World Cup, etc.)",
|
||||
"Sports magazines",
|
||||
"Football / Soccer",
|
||||
"Tennis / Squash",
|
||||
"Team sports (excluding football)",
|
||||
"Athletics",
|
||||
"Motor sport",
|
||||
"Water sport",
|
||||
},
|
||||
{
|
||||
"Children's/Youth programmes",
|
||||
"pre-school children's programmes",
|
||||
"entertainment programmes for 6 to14",
|
||||
"entertainment programmes for 10 to 16",
|
||||
"informational/educational/school programmes",
|
||||
"cartoons/puppets",
|
||||
"cartoons/puppets",
|
||||
"cartoons/puppets",
|
||||
"cartoons/puppets",
|
||||
"cartoons/puppets",
|
||||
"cartoons/puppets",
|
||||
"cartoons/puppets",
|
||||
"cartoons/puppets",
|
||||
"cartoons/puppets",
|
||||
"Children's / Youth programmes",
|
||||
"Pre-school children's programmes",
|
||||
"Entertainment programmes for 6 to 14",
|
||||
"Entertainment programmes for 10 to 16",
|
||||
"Informational / Educational / School programmes",
|
||||
"Cartoons / Puppets",
|
||||
"Cartoons / Puppets",
|
||||
"Cartoons / Puppets",
|
||||
"Cartoons / Puppets",
|
||||
"Cartoons / Puppets",
|
||||
"Cartoons / Puppets",
|
||||
"Cartoons / Puppets",
|
||||
"Cartoons / Puppets",
|
||||
"Cartoons / Puppets",
|
||||
},
|
||||
{
|
||||
"Music/Ballet/Dance",
|
||||
"rock/pop",
|
||||
"serious music/classical music",
|
||||
"folk/traditional music",
|
||||
"jazz",
|
||||
"musical/opera",
|
||||
"musical/opera",
|
||||
"musical/opera",
|
||||
"musical/opera",
|
||||
"musical/opera",
|
||||
"musical/opera",
|
||||
"musical/opera",
|
||||
"musical/opera",
|
||||
"Music / Ballet / Dance",
|
||||
"Rock / Pop",
|
||||
"Serious music / Classical music",
|
||||
"Folk / Traditional music",
|
||||
"Jazz",
|
||||
"Musical / Opera",
|
||||
"Musical / Opera",
|
||||
"Musical / Opera",
|
||||
"Musical / Opera",
|
||||
"Musical / Opera",
|
||||
"Musical / Opera",
|
||||
"Musical / Opera",
|
||||
"Musical / Opera",
|
||||
},
|
||||
{
|
||||
"Arts/Culture (without music)",
|
||||
"performing arts",
|
||||
"fine arts",
|
||||
"religion",
|
||||
"popular culture/traditional arts",
|
||||
"literature",
|
||||
"film/cinema",
|
||||
"experimental film/video",
|
||||
"broadcasting/press",
|
||||
"Arts / Culture (without music)",
|
||||
"Performing arts",
|
||||
"Fine arts",
|
||||
"Religion",
|
||||
"Popular culture / Traditional arts",
|
||||
"Literature",
|
||||
"Film / Cinema",
|
||||
"Experimental film / Video",
|
||||
"Broadcasting / Press",
|
||||
},
|
||||
{
|
||||
"Social/Political issues/Economics",
|
||||
"magazines/reports/documentary",
|
||||
"economics/social advisory",
|
||||
"remarkable people",
|
||||
"remarkable people",
|
||||
"remarkable people",
|
||||
"remarkable people",
|
||||
"remarkable people",
|
||||
"remarkable people",
|
||||
"remarkable people",
|
||||
"remarkable people",
|
||||
"remarkable people",
|
||||
"remarkable people",
|
||||
"remarkable people",
|
||||
"Social / Political issues / Economics",
|
||||
"Magazines / Reports / Documentary",
|
||||
"Economics / Social advisory",
|
||||
"Remarkable people",
|
||||
"Remarkable people",
|
||||
"Remarkable people",
|
||||
"Remarkable people",
|
||||
"Remarkable people",
|
||||
"Remarkable people",
|
||||
"Remarkable people",
|
||||
"Remarkable people",
|
||||
"Remarkable people",
|
||||
"Remarkable people",
|
||||
"Remarkable people",
|
||||
},
|
||||
{
|
||||
"Education/Science/Factual topics",
|
||||
"nature/animals/environment",
|
||||
"technology/natural sciences",
|
||||
"medicine/physiology/psychology",
|
||||
"foreign countries/expeditions",
|
||||
"social/spiritual sciences",
|
||||
"further education",
|
||||
"languages",
|
||||
"languages",
|
||||
"languages",
|
||||
"languages",
|
||||
"languages",
|
||||
"languages",
|
||||
"languages",
|
||||
"Education / Science / Factual topics",
|
||||
"Nature / Animals / Environment",
|
||||
"Technology / Natural sciences",
|
||||
"Medicine / Physiology / Psychology",
|
||||
"Foreign countries / Expeditions",
|
||||
"Social / Spiritual sciences",
|
||||
"Further education",
|
||||
"Languages",
|
||||
"Languages",
|
||||
"Languages",
|
||||
"Languages",
|
||||
"Languages",
|
||||
"Languages",
|
||||
"Languages",
|
||||
},
|
||||
{
|
||||
"Leisure hobbies",
|
||||
"tourism/travel",
|
||||
"handicraft",
|
||||
"motoring",
|
||||
"fitness and health",
|
||||
"cooking",
|
||||
"advertisement/shopping",
|
||||
"gardening",
|
||||
"gardening",
|
||||
"gardening",
|
||||
"gardening",
|
||||
"gardening",
|
||||
"gardening",
|
||||
"gardening",
|
||||
"Tourism / Travel",
|
||||
"Handicraft",
|
||||
"Motoring",
|
||||
"Fitness and health",
|
||||
"Cooking",
|
||||
"Advertisement / Shopping",
|
||||
"Gardening",
|
||||
"Gardening",
|
||||
"Gardening",
|
||||
"Gardening",
|
||||
"Gardening",
|
||||
"Gardening",
|
||||
"Gardening",
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -556,7 +556,7 @@ void epg_query(epg_query_result_t *eqr, const char *channel, const char *tag,
|
|||
* ***********************************************************************/
|
||||
|
||||
void epg_init (void);
|
||||
void epg_save (void);
|
||||
void epg_save (void*);
|
||||
void epg_updated (void);
|
||||
|
||||
/* ************************************************************************
|
||||
|
|
10
src/epgdb.c
10
src/epgdb.c
|
@ -20,6 +20,7 @@
|
|||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#include "tvheadend.h"
|
||||
#include "queue.h"
|
||||
|
@ -241,9 +242,8 @@ static int _epg_write ( int fd, htsmsg_t *m )
|
|||
int r = htsmsg_binary_serialize(m, &msgdata, &msglen, 0x10000);
|
||||
htsmsg_destroy(m);
|
||||
if (!r) {
|
||||
ssize_t w = write(fd, msgdata, msglen);
|
||||
ret = tvh_write(fd, msgdata, msglen);
|
||||
free(msgdata);
|
||||
if(w == msglen) ret = 0;
|
||||
}
|
||||
} else {
|
||||
ret = 0;
|
||||
|
@ -263,13 +263,17 @@ static int _epg_write_sect ( int fd, const char *sect )
|
|||
return _epg_write(fd, m);
|
||||
}
|
||||
|
||||
void epg_save ( void )
|
||||
void epg_save ( void *p )
|
||||
{
|
||||
int fd;
|
||||
epg_object_t *eo;
|
||||
epg_broadcast_t *ebc;
|
||||
channel_t *ch;
|
||||
epggrab_stats_t stats;
|
||||
extern gtimer_t epggrab_save_timer;
|
||||
|
||||
if (epggrab_epgdb_periodicsave)
|
||||
gtimer_arm(&epggrab_save_timer, epg_save, NULL, epggrab_epgdb_periodicsave);
|
||||
|
||||
fd = hts_settings_open_file(1, "epgdb.v%d", EPG_DB_VERSION);
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <wordexp.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/un.h>
|
||||
|
@ -39,9 +38,9 @@
|
|||
#include "service.h"
|
||||
|
||||
/* Thread protection */
|
||||
static int epggrab_confver;
|
||||
static int epggrab_confver;
|
||||
pthread_mutex_t epggrab_mutex;
|
||||
static pthread_cond_t epggrab_cond;
|
||||
static pthread_cond_t epggrab_cond;
|
||||
|
||||
/* Config */
|
||||
uint32_t epggrab_interval;
|
||||
|
@ -50,6 +49,9 @@ epggrab_module_list_t epggrab_modules;
|
|||
uint32_t epggrab_channel_rename;
|
||||
uint32_t epggrab_channel_renumber;
|
||||
uint32_t epggrab_channel_reicon;
|
||||
uint32_t epggrab_epgdb_periodicsave;
|
||||
|
||||
gtimer_t epggrab_save_timer;
|
||||
|
||||
/* **************************************************************************
|
||||
* Internal Grab Thread
|
||||
|
@ -139,6 +141,10 @@ static void _epggrab_load ( void )
|
|||
htsmsg_get_u32(m, "channel_rename", &epggrab_channel_rename);
|
||||
htsmsg_get_u32(m, "channel_renumber", &epggrab_channel_renumber);
|
||||
htsmsg_get_u32(m, "channel_reicon", &epggrab_channel_reicon);
|
||||
htsmsg_get_u32(m, "epgdb_periodicsave", &epggrab_epgdb_periodicsave);
|
||||
if (epggrab_epgdb_periodicsave)
|
||||
gtimer_arm(&epggrab_save_timer, epg_save, NULL,
|
||||
epggrab_epgdb_periodicsave);
|
||||
if (!htsmsg_get_u32(m, old ? "grab-interval" : "interval",
|
||||
&epggrab_interval)) {
|
||||
if (old) epggrab_interval *= 3600;
|
||||
|
@ -213,8 +219,10 @@ static void _epggrab_load ( void )
|
|||
}
|
||||
|
||||
/* Load module config (channels) */
|
||||
#if ENABLE_LINUXDVB
|
||||
eit_load();
|
||||
opentv_load();
|
||||
#endif
|
||||
pyepg_load();
|
||||
xmltv_load();
|
||||
}
|
||||
|
@ -233,6 +241,7 @@ void epggrab_save ( void )
|
|||
htsmsg_add_u32(m, "channel_rename", epggrab_channel_rename);
|
||||
htsmsg_add_u32(m, "channel_renumber", epggrab_channel_renumber);
|
||||
htsmsg_add_u32(m, "channel_reicon", epggrab_channel_reicon);
|
||||
htsmsg_add_u32(m, "epgdb_periodicsave", epggrab_epgdb_periodicsave);
|
||||
htsmsg_add_u32(m, "interval", epggrab_interval);
|
||||
if ( epggrab_module )
|
||||
htsmsg_add_str(m, "module", epggrab_module->id);
|
||||
|
@ -298,6 +307,25 @@ int epggrab_set_channel_renumber ( uint32_t e )
|
|||
return save;
|
||||
}
|
||||
|
||||
/*
|
||||
* Config from the webui for period save of db to disk
|
||||
*/
|
||||
int epggrab_set_periodicsave ( uint32_t e )
|
||||
{
|
||||
int save = 0;
|
||||
if ( e != epggrab_epgdb_periodicsave ) {
|
||||
epggrab_epgdb_periodicsave = e;
|
||||
pthread_mutex_lock(&global_lock);
|
||||
if (!e)
|
||||
gtimer_disarm(&epggrab_save_timer);
|
||||
else
|
||||
epg_save(NULL); // will arm the timer
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
save = 1;
|
||||
}
|
||||
return save;
|
||||
}
|
||||
|
||||
int epggrab_set_channel_reicon ( uint32_t e )
|
||||
{
|
||||
int save = 0;
|
||||
|
@ -342,22 +370,36 @@ void epggrab_resched ( void )
|
|||
*/
|
||||
void epggrab_init ( void )
|
||||
{
|
||||
/* Defaults */
|
||||
epggrab_interval = 0;
|
||||
epggrab_module = NULL;
|
||||
epggrab_channel_rename = 0;
|
||||
epggrab_channel_renumber = 0;
|
||||
epggrab_channel_reicon = 0;
|
||||
epggrab_epgdb_periodicsave = 0;
|
||||
|
||||
/* Lists */
|
||||
#if ENABLE_LINUXDVB
|
||||
extern TAILQ_HEAD(, epggrab_ota_mux) ota_mux_all;
|
||||
TAILQ_INIT(&ota_mux_all);
|
||||
#endif
|
||||
|
||||
pthread_mutex_init(&epggrab_mutex, NULL);
|
||||
pthread_cond_init(&epggrab_cond, NULL);
|
||||
|
||||
/* Initialise modules */
|
||||
#if ENABLE_LINUXDVB
|
||||
eit_init();
|
||||
opentv_init();
|
||||
#endif
|
||||
pyepg_init();
|
||||
xmltv_init();
|
||||
|
||||
/* Load config */
|
||||
_epggrab_load();
|
||||
#if ENABLE_LINUXDVB
|
||||
epggrab_ota_load();
|
||||
#endif
|
||||
|
||||
/* Start internal grab thread */
|
||||
pthread_t tid;
|
||||
|
|
|
@ -226,6 +226,7 @@ extern epggrab_module_int_t* epggrab_module;
|
|||
extern uint32_t epggrab_channel_rename;
|
||||
extern uint32_t epggrab_channel_renumber;
|
||||
extern uint32_t epggrab_channel_reicon;
|
||||
extern uint32_t epggrab_epgdb_periodicsave;
|
||||
|
||||
/*
|
||||
* Set configuration
|
||||
|
@ -236,6 +237,7 @@ int epggrab_set_module_by_id ( const char *id );
|
|||
int epggrab_set_channel_rename ( uint32_t e );
|
||||
int epggrab_set_channel_renumber ( uint32_t e );
|
||||
int epggrab_set_channel_reicon ( uint32_t e );
|
||||
int epggrab_set_periodicsave ( uint32_t e );
|
||||
int epggrab_enable_module ( epggrab_module_t *mod, uint8_t e );
|
||||
int epggrab_enable_module_by_id ( const char *id, uint8_t e );
|
||||
|
||||
|
|
|
@ -185,7 +185,7 @@ epggrab_channel_t *epggrab_channel_find
|
|||
|
||||
htsmsg_t *epggrab_channel_list ( void )
|
||||
{
|
||||
char name[100];
|
||||
char name[500];
|
||||
epggrab_module_t *mod;
|
||||
epggrab_channel_t *ec;
|
||||
htsmsg_t *e, *m;
|
||||
|
@ -198,9 +198,10 @@ htsmsg_t *epggrab_channel_list ( void )
|
|||
htsmsg_add_str(e, "id", ec->id);
|
||||
if (ec->name)
|
||||
htsmsg_add_str(e, "name", ec->name);
|
||||
sprintf(name, "%s|%s", mod->id, ec->id);
|
||||
snprintf(name, sizeof(name), "%s|%s", mod->id, ec->id);
|
||||
htsmsg_add_str(e, "mod-id", name);
|
||||
sprintf(name, "%s: %s (%s)", mod->name, ec->name, ec->id);
|
||||
snprintf(name, sizeof(name), "%s: %s (%s)",
|
||||
mod->name, ec->name, ec->id);
|
||||
htsmsg_add_str(e, "mod-name", name);
|
||||
htsmsg_add_msg(m, NULL, e);
|
||||
}
|
||||
|
|
|
@ -228,7 +228,7 @@ static int _eit_desc_short_event
|
|||
{
|
||||
int r;
|
||||
char lang[4];
|
||||
char buf[256];
|
||||
char buf[512];
|
||||
|
||||
if ( len < 5 ) return -1;
|
||||
|
||||
|
@ -242,7 +242,7 @@ static int _eit_desc_short_event
|
|||
if ( (r = _eit_get_string_with_len(mod, buf, sizeof(buf),
|
||||
ptr, len, ev->default_charset)) < 0 ) {
|
||||
return -1;
|
||||
} else {
|
||||
} else if ( r > 1 ) {
|
||||
if (!ev->title) ev->title = lang_str_create();
|
||||
lang_str_add(ev->title, buf, lang, 0);
|
||||
}
|
||||
|
@ -255,7 +255,7 @@ static int _eit_desc_short_event
|
|||
if ( (r = _eit_get_string_with_len(mod, buf, sizeof(buf),
|
||||
ptr, len, ev->default_charset)) < 0 ) {
|
||||
return -1;
|
||||
} else {
|
||||
} else if ( r > 1 ) {
|
||||
if (!ev->summary) ev->summary = lang_str_create();
|
||||
lang_str_add(ev->summary, buf, lang, 0);
|
||||
}
|
||||
|
@ -269,9 +269,10 @@ static int _eit_desc_short_event
|
|||
static int _eit_desc_ext_event
|
||||
( epggrab_module_t *mod, uint8_t *ptr, int len, eit_event_t *ev )
|
||||
{
|
||||
int r, nitem;
|
||||
char ikey[256], ival[256];
|
||||
char buf[256], lang[4];
|
||||
int r, ilen;
|
||||
char ikey[512], ival[512];
|
||||
char buf[512], lang[4];
|
||||
uint8_t *iptr;
|
||||
|
||||
if (len < 6) return -1;
|
||||
|
||||
|
@ -286,28 +287,34 @@ static int _eit_desc_ext_event
|
|||
ptr += 3;
|
||||
|
||||
/* Key/Value items */
|
||||
nitem = *ptr;
|
||||
ilen = *ptr;
|
||||
len -= 1;
|
||||
ptr += 1;
|
||||
if (len < (2 * nitem) + 1) return -1;
|
||||
iptr = ptr;
|
||||
if (len < ilen) return -1;
|
||||
|
||||
while (nitem--) {
|
||||
/* Skip past */
|
||||
ptr += ilen;
|
||||
len -= ilen;
|
||||
|
||||
/* Process */
|
||||
while (ilen) {
|
||||
|
||||
/* Key */
|
||||
if ( (r = _eit_get_string_with_len(mod, ikey, sizeof(ikey),
|
||||
ptr, len, ev->default_charset)) < 0 )
|
||||
return -1;
|
||||
|
||||
len -= r;
|
||||
ptr += r;
|
||||
iptr, ilen, ev->default_charset)) < 0 )
|
||||
break;
|
||||
|
||||
ilen -= r;
|
||||
iptr += r;
|
||||
|
||||
/* Value */
|
||||
if ( (r = _eit_get_string_with_len(mod, ival, sizeof(ival),
|
||||
ptr, len, ev->default_charset)) < 0 )
|
||||
return -1;
|
||||
iptr, ilen, ev->default_charset)) < 0 )
|
||||
break;
|
||||
|
||||
len -= r;
|
||||
ptr += r;
|
||||
ilen -= r;
|
||||
iptr += r;
|
||||
|
||||
/* Store */
|
||||
// TODO: extend existing?
|
||||
|
@ -323,9 +330,7 @@ static int _eit_desc_ext_event
|
|||
if ( _eit_get_string_with_len(mod,
|
||||
buf, sizeof(buf),
|
||||
ptr, len,
|
||||
ev->default_charset) < 0 ) {
|
||||
return -1;
|
||||
} else {
|
||||
ev->default_charset) > 1 ) {
|
||||
if (!ev->desc) ev->desc = lang_str_create();
|
||||
lang_str_append(ev->desc, buf, lang);
|
||||
}
|
||||
|
@ -441,7 +446,7 @@ static int _eit_desc_crid
|
|||
{
|
||||
int r;
|
||||
uint8_t type;
|
||||
char buf[257], *crid;
|
||||
char buf[512], *crid;
|
||||
int clen;
|
||||
|
||||
while (len > 3) {
|
||||
|
@ -455,6 +460,7 @@ static int _eit_desc_crid
|
|||
ptr+1, len-1,
|
||||
ev->default_charset);
|
||||
if (r < 0) return -1;
|
||||
if (r == 0) continue;
|
||||
|
||||
/* Episode */
|
||||
if (type == 0x1 || type == 0x31) {
|
||||
|
@ -527,10 +533,8 @@ static int _eit_process_event
|
|||
|
||||
/* Find broadcast */
|
||||
ebc = epg_broadcast_find_by_time(svc->s_ch, start, stop, eid, 1, &save2);
|
||||
#ifdef EPG_EIT_TRACE
|
||||
tvhlog(LOG_DEBUG, mod->id, "eid=%5d, start=%lu, stop=%lu, ebc=%p",
|
||||
tvhtrace("eit", "eid=%5d, start=%lu, stop=%lu, ebc=%p",
|
||||
eid, start, stop, ebc);
|
||||
#endif
|
||||
if (!ebc) return dllen + 12;
|
||||
|
||||
/* Mark re-schedule detect (only now/next) */
|
||||
|
@ -692,20 +696,17 @@ static int _eit_callback
|
|||
lst = ptr[4];
|
||||
seg = ptr[9];
|
||||
ver = (ptr[2] >> 1) & 0x1f;
|
||||
#ifdef EPG_EIT_TRACE
|
||||
tvhlog(LOG_DEBUG, mod->id,
|
||||
"tid=0x%02X, onid=0x%04X, tsid=0x%04X, sid=0x%04X, sec=%3d/%3d, seg=%3d, ver=%2d, cur=%d",
|
||||
tableid, onid, tsid, sid, sec, lst, seg, ver, ptr[2] & 1);
|
||||
#endif
|
||||
tvhtrace("eit",
|
||||
"tid=0x%02X, onid=0x%04X, tsid=0x%04X, sid=0x%04X"
|
||||
", sec=%3d/%3d, seg=%3d, ver=%2d, cur=%d",
|
||||
tableid, onid, tsid, sid, sec, lst, seg, ver, ptr[2] & 1);
|
||||
|
||||
/* Don't process */
|
||||
if((ptr[2] & 1) == 0) return 0;
|
||||
|
||||
/* Current status */
|
||||
tsta = eit_status_find(sta, tableid, onid, tsid, sid, sec, lst, seg, ver);
|
||||
#ifdef EPG_EIT_TRACE
|
||||
tvhlog(LOG_DEBUG, mod->id, tsta && tsta->state != EIT_STATUS_DONE ? "section process" : "section seen");
|
||||
#endif
|
||||
tvhtrace("eit", tsta && tsta->state != EIT_STATUS_DONE ? "section process" : "section seen");
|
||||
if (!tsta) return 0; // already seen, no state change
|
||||
if (tsta->state == EIT_STATUS_DONE) goto done;
|
||||
|
||||
|
@ -713,31 +714,23 @@ static int _eit_callback
|
|||
// Note: tableid=0x4f,0x60-0x6f is other TS
|
||||
// so must find the tdmi
|
||||
if(tableid == 0x4f || tableid >= 0x60) {
|
||||
tda = tdmi->tdmi_adapter;
|
||||
LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link)
|
||||
if(tdmi->tdmi_transport_stream_id == tsid &&
|
||||
tdmi->tdmi_network_id == onid)
|
||||
break;
|
||||
tda = tdmi->tdmi_adapter;
|
||||
tdmi = dvb_mux_find(tda, NULL, onid, tsid, 1);
|
||||
} else {
|
||||
if (tdmi->tdmi_transport_stream_id != tsid ||
|
||||
tdmi->tdmi_network_id != onid) {
|
||||
#ifdef EPG_EIT_TRACE
|
||||
tvhlog(LOG_DEBUG, mod->id,
|
||||
"invalid transport id found tid 0x%02X, onid:tsid %d:%d != %d:%d",
|
||||
tableid, tdmi->tdmi_network_id, tdmi->tdmi_transport_stream_id,
|
||||
onid, tsid);
|
||||
#endif
|
||||
tvhtrace("eit",
|
||||
"invalid tsid found tid 0x%02X, onid:tsid %d:%d != %d:%d",
|
||||
tableid, tdmi->tdmi_network_id, tdmi->tdmi_transport_stream_id,
|
||||
onid, tsid);
|
||||
tdmi = NULL;
|
||||
}
|
||||
}
|
||||
if(!tdmi) goto done;
|
||||
|
||||
/* Get service */
|
||||
svc = dvb_transport_find(tdmi, sid, 0, NULL);
|
||||
if (!svc || !svc->s_enabled || !svc->s_ch) goto done;
|
||||
|
||||
/* Ignore (not primary EPG service) */
|
||||
if (!service_is_primary_epg(svc)) goto done;
|
||||
svc = dvb_service_find3(NULL, tdmi, NULL, 0, 0, sid, 1, 1);
|
||||
if (!svc || !svc->s_ch) goto done;
|
||||
|
||||
/* Register as interesting */
|
||||
if (tableid < 0x50)
|
||||
|
@ -773,24 +766,25 @@ done:
|
|||
if (!tsta) epggrab_ota_complete(ota);
|
||||
}
|
||||
}
|
||||
#ifdef EPG_EIT_TRACE
|
||||
#if ENABLE_TRACE
|
||||
if (ota->state != EPGGRAB_OTA_MUX_COMPLETE)
|
||||
{
|
||||
int total = 0;
|
||||
int finished = 0;
|
||||
tvhlog(LOG_DEBUG, mod->id, "scan status");
|
||||
tvhtrace("eit", "scan status:");
|
||||
LIST_FOREACH(tsta, &sta->tables, link) {
|
||||
total++;
|
||||
tvhlog(LOG_DEBUG, mod->id,
|
||||
" tid=0x%02X, onid=0x%04X, tsid=0x%04X, sid=0x%04X, ver=%02d, done=%d, "
|
||||
"mask=%08X|%08X|%08X|%08X|%08X|%08X|%08X|%08X",
|
||||
tsta->tid, tsta->onid, tsta->tsid, tsta->sid, tsta->ver,
|
||||
tsta->state == EIT_STATUS_DONE,
|
||||
tsta->sec[7], tsta->sec[6], tsta->sec[5], tsta->sec[4],
|
||||
tsta->sec[3], tsta->sec[2], tsta->sec[1], tsta->sec[0]);
|
||||
tvhtrace("eit",
|
||||
" tid=0x%02X, onid=0x%04X, tsid=0x%04X, sid=0x%04X, ver=%02d"
|
||||
", done=%d, "
|
||||
"mask=%08X|%08X|%08X|%08X|%08X|%08X|%08X|%08X",
|
||||
tsta->tid, tsta->onid, tsta->tsid, tsta->sid, tsta->ver,
|
||||
tsta->state == EIT_STATUS_DONE,
|
||||
tsta->sec[7], tsta->sec[6], tsta->sec[5], tsta->sec[4],
|
||||
tsta->sec[3], tsta->sec[2], tsta->sec[1], tsta->sec[0]);
|
||||
if (tsta->state == EIT_STATUS_DONE) finished++;
|
||||
}
|
||||
tvhlog(LOG_DEBUG, mod->id, " completed %d of %d", finished, total);
|
||||
tvhtrace("eit", " completed %d of %d", finished, total);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -829,6 +823,7 @@ static void _eit_start
|
|||
if (!m->enabled) return;
|
||||
|
||||
/* Freeview (switch to EIT, ignore if explicitly enabled) */
|
||||
// Note: do this as PID is the same
|
||||
if (!strcmp(m->id, "uk_freeview")) {
|
||||
m = (epggrab_module_ota_t*)epggrab_module_find_by_id("eit");
|
||||
if (m->enabled) return;
|
||||
|
@ -841,16 +836,22 @@ static void _eit_start
|
|||
ota->destroy = _eit_ota_destroy;
|
||||
}
|
||||
|
||||
/* Add PIDs (freesat uses non-standard) */
|
||||
/* Freesat (3002/3003) */
|
||||
if (!strcmp("uk_freesat", m->id)) {
|
||||
#ifdef IGNORE_TOO_SLOW
|
||||
tdt_add(tdmi, NULL, dvb_pidx11_callback, m, m->id, TDT_CRC, 3840, NULL);
|
||||
tdt_add(tdmi, NULL, _eit_callback, m, m->id, TDT_CRC, 3841, NULL);
|
||||
tdt_add(tdmi, 0, 0, dvb_pidx11_callback, m, m->id, TDT_CRC, 3840, NULL);
|
||||
tdt_add(tdmi, 0, 0, _eit_callback, m, m->id, TDT_CRC, 3841, NULL);
|
||||
#endif
|
||||
tdt_add(tdmi, NULL, dvb_pidx11_callback, m, m->id, TDT_CRC, 3002, NULL);
|
||||
tdt_add(tdmi, NULL, _eit_callback, m, m->id, TDT_CRC, 3003, NULL);
|
||||
tdt_add(tdmi, 0, 0, dvb_pidx11_callback, m, m->id, TDT_CRC, 3002);
|
||||
tdt_add(tdmi, 0, 0, _eit_callback, m, m->id, TDT_CRC, 3003);
|
||||
|
||||
/* Viasat Baltic (0x39) */
|
||||
} else if (!strcmp("viasat_baltic", m->id)) {
|
||||
tdt_add(tdmi, 0, 0, _eit_callback, m, m->id, TDT_CRC, 0x39);
|
||||
|
||||
/* Standard (0x12) */
|
||||
} else {
|
||||
tdt_add(tdmi, NULL, _eit_callback, m, m->id, TDT_CRC, 0x12, NULL);
|
||||
tdt_add(tdmi, 0, 0, _eit_callback, m, m->id, TDT_CRC, 0x12);
|
||||
}
|
||||
tvhlog(LOG_DEBUG, m->id, "install table handlers");
|
||||
}
|
||||
|
@ -892,6 +893,8 @@ void eit_init ( void )
|
|||
_eit_start, _eit_enable, NULL);
|
||||
epggrab_module_ota_create(NULL, "uk_freeview", "UK: Freeview", 5,
|
||||
_eit_start, _eit_enable, NULL);
|
||||
epggrab_module_ota_create(NULL, "viasat_baltic", "VIASAT: Baltic", 5,
|
||||
_eit_start, _eit_enable, NULL);
|
||||
}
|
||||
|
||||
void eit_load ( void )
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <regex.h>
|
||||
#include <linux/dvb/dmx.h>
|
||||
#include "tvheadend.h"
|
||||
#include "dvb/dvb.h"
|
||||
|
@ -189,23 +190,6 @@ static epggrab_channel_t *_opentv_find_epggrab_channel
|
|||
(epggrab_module_t*)mod);
|
||||
}
|
||||
|
||||
static service_t *_opentv_find_service ( int onid, int tsid, int sid )
|
||||
{
|
||||
th_dvb_adapter_t *tda;
|
||||
th_dvb_mux_instance_t *tdmi;
|
||||
service_t *t = NULL;
|
||||
TAILQ_FOREACH(tda, &dvb_adapters, tda_global_link) {
|
||||
LIST_FOREACH(tdmi, &tda->tda_muxes, tdmi_adapter_link) {
|
||||
if (tdmi->tdmi_transport_stream_id != tsid) continue;
|
||||
if (tdmi->tdmi_network_id != onid) continue;
|
||||
LIST_FOREACH(t, &tdmi->tdmi_transports, s_group_link) {
|
||||
if (t->s_dvb_service_id == sid) return t;
|
||||
}
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ************************************************************************
|
||||
* OpenTV event processing
|
||||
* ***********************************************************************/
|
||||
|
@ -234,6 +218,8 @@ static char *_opentv_parse_string
|
|||
int ok = 0;
|
||||
char *ret, *tmp;
|
||||
|
||||
if (len <= 0) return NULL;
|
||||
|
||||
// Note: unlikely decoded string will be longer (though its possible)
|
||||
ret = tmp = malloc(2*len);
|
||||
*ret = 0;
|
||||
|
@ -265,15 +251,17 @@ static int _opentv_parse_event_record
|
|||
if (rlen+2 <= len) {
|
||||
switch (rtag) {
|
||||
case 0xb5: // title
|
||||
ev->start = (((int)buf[2] << 9) | (buf[3] << 1))
|
||||
+ mjd;
|
||||
ev->stop = (((int)buf[4] << 9) | (buf[5] << 1))
|
||||
+ ev->start;
|
||||
ev->cat = buf[6];
|
||||
if (prov->genre)
|
||||
ev->cat = prov->genre->map[ev->cat];
|
||||
if (!ev->title)
|
||||
ev->title = _opentv_parse_string(prov, buf+9, rlen-7);
|
||||
if (rlen >= 7) {
|
||||
ev->start = (((int)buf[2] << 9) | (buf[3] << 1))
|
||||
+ mjd;
|
||||
ev->stop = (((int)buf[4] << 9) | (buf[5] << 1))
|
||||
+ ev->start;
|
||||
ev->cat = buf[6];
|
||||
if (prov->genre)
|
||||
ev->cat = prov->genre->map[ev->cat];
|
||||
if (!ev->title)
|
||||
ev->title = _opentv_parse_string(prov, buf+9, rlen-7);
|
||||
}
|
||||
break;
|
||||
case 0xb9: // summary
|
||||
if (!ev->summary)
|
||||
|
@ -410,6 +398,25 @@ static int _opentv_parse_event_section
|
|||
save |= epg_episode_set_genre(ee, egl, src);
|
||||
epg_genre_list_destroy(egl);
|
||||
}
|
||||
if (ev.summary) {
|
||||
regex_t preg;
|
||||
regmatch_t match[3];
|
||||
|
||||
/* Parse Series/Episode
|
||||
* TODO: HACK: this needs doing properly */
|
||||
regcomp(&preg, " *\\(S ?([0-9]+),? Ep? ?([0-9]+)\\)$",
|
||||
REG_ICASE | REG_EXTENDED);
|
||||
if (!regexec(&preg, ev.summary, 3, match, 0)) {
|
||||
epg_episode_num_t en;
|
||||
memset(&en, 0, sizeof(en));
|
||||
if (match[1].rm_so != -1)
|
||||
en.s_num = atoi(ev.summary + match[1].rm_so);
|
||||
if (match[2].rm_so != -1)
|
||||
en.e_num = atoi(ev.summary + match[2].rm_so);
|
||||
save |= epg_episode_set_epnum(ee, &en, src);
|
||||
}
|
||||
regfree(&preg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Cleanup */
|
||||
|
@ -442,8 +449,8 @@ static void _opentv_parse_channels
|
|||
cnum = ((int)buf[i+5] << 8) | buf[i+6];
|
||||
|
||||
/* Find the service */
|
||||
svc = _opentv_find_service(onid, tsid, sid);
|
||||
if (svc && svc->s_ch && service_is_primary_epg(svc)) {
|
||||
svc = dvb_service_find3(NULL, NULL, NULL, onid, tsid, sid, 1, 1);
|
||||
if (svc && svc->s_ch) {
|
||||
ec =_opentv_find_epggrab_channel(mod, cid, 1, &save);
|
||||
ecl = LIST_FIRST(&ec->channels);
|
||||
if (!ecl) {
|
||||
|
@ -658,7 +665,6 @@ static void _opentv_start
|
|||
( epggrab_module_ota_t *m, th_dvb_mux_instance_t *tdmi )
|
||||
{
|
||||
int *t;
|
||||
struct dmx_sct_filter_params *fp;
|
||||
epggrab_ota_mux_t *ota;
|
||||
opentv_module_t *mod = (opentv_module_t*)m;
|
||||
opentv_status_t *sta;
|
||||
|
@ -685,34 +691,25 @@ static void _opentv_start
|
|||
/* Channels */
|
||||
t = mod->channel;
|
||||
while (*t) {
|
||||
fp = dvb_fparams_alloc();
|
||||
fp->filter.filter[0] = 0x4a;
|
||||
fp->filter.mask[0] = 0xff;
|
||||
// TODO: what about 0x46 (service description)
|
||||
tdt_add(tdmi, fp, _opentv_channel_callback, m,
|
||||
m->id, TDT_CRC, *t++, NULL);
|
||||
tdt_add(tdmi, 0x4a, 0xff, _opentv_channel_callback, m,
|
||||
m->id, TDT_CRC, *t++);
|
||||
}
|
||||
|
||||
/* Titles */
|
||||
t = mod->title;
|
||||
while (*t) {
|
||||
fp = dvb_fparams_alloc();
|
||||
fp->filter.filter[0] = 0xa0;
|
||||
fp->filter.mask[0] = 0xfc;
|
||||
_opentv_status_get_pid(sta, *t);
|
||||
tdt_add(tdmi, fp, _opentv_title_callback, m,
|
||||
m->id, TDT_CRC | TDT_TDT, *t++, NULL);
|
||||
tdt_add(tdmi, 0xa0, 0xfc, _opentv_title_callback, m,
|
||||
m->id, TDT_CRC | TDT_TDT, *t++);
|
||||
}
|
||||
|
||||
/* Summaries */
|
||||
t = mod->summary;
|
||||
while (*t) {
|
||||
fp = dvb_fparams_alloc();
|
||||
fp->filter.filter[0] = 0xa8;
|
||||
fp->filter.mask[0] = 0xfc;
|
||||
_opentv_status_get_pid(sta, *t);
|
||||
tdt_add(tdmi, fp, _opentv_summary_callback, m,
|
||||
m->id, TDT_CRC | TDT_TDT, *t++, NULL);
|
||||
tdt_add(tdmi, 0xa8, 0xfc, _opentv_summary_callback, m,
|
||||
m->id, TDT_CRC | TDT_TDT, *t++);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -184,8 +184,8 @@ static const char *xmltv_ns_get_parse_num
|
|||
|
||||
|
||||
out:
|
||||
if(ap) *ap = a + 1;
|
||||
if(bp) *bp = b + 1;
|
||||
if(ap && a >= 0) *ap = a + 1;
|
||||
if(bp && b >= 0) *bp = b;
|
||||
return s;
|
||||
}
|
||||
|
||||
|
@ -208,7 +208,10 @@ static void parse_xmltv_dd_progid
|
|||
snprintf(buf, sizeof(buf)-1, "ddprogid://%s/%s", mod->id, s);
|
||||
|
||||
/* SH - series without episode id so ignore */
|
||||
if (strncmp("SH", s, 2)) *uri = strdup(buf);
|
||||
if (strncmp("SH", s, 2))
|
||||
*uri = strdup(buf);
|
||||
else
|
||||
*suri = strdup(buf);
|
||||
|
||||
/* Episode */
|
||||
if (!strncmp("EP", s, 2)) {
|
||||
|
@ -351,14 +354,22 @@ static int _xmltv_parse_previously_shown
|
|||
* Star rating
|
||||
*/
|
||||
static int _xmltv_parse_star_rating
|
||||
( epggrab_module_t *mod, epg_episode_t *ee, htsmsg_t *tags )
|
||||
( epggrab_module_t *mod, epg_episode_t *ee, htsmsg_t *body )
|
||||
{
|
||||
int a, b;
|
||||
const char *stars;
|
||||
if (!mod || !ee || !tags) return 0;
|
||||
if (!(stars = htsmsg_xml_get_cdata_str(tags, "star-rating"))) return 0;
|
||||
if (sscanf(stars, "%d/%d", &a, &b) != 2) return 0;
|
||||
return epg_episode_set_star_rating(ee, (5 * a) / b, mod);
|
||||
double a, b;
|
||||
htsmsg_t *stars, *tags;
|
||||
const char *s1, *s2;
|
||||
|
||||
if (!mod || !ee || !body) return 0;
|
||||
if (!(stars = htsmsg_get_map(body, "star-rating"))) return 0;
|
||||
if (!(tags = htsmsg_get_map(stars, "tags"))) return 0;
|
||||
if (!(s1 = htsmsg_xml_get_cdata_str(tags, "value"))) return 0;
|
||||
if (!(s2 = strstr(s1, "/"))) return 0;
|
||||
|
||||
a = atof(s1);
|
||||
b = atof(s2 + 1);
|
||||
|
||||
return epg_episode_set_star_rating(ee, (100 * a) / b, mod);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -631,7 +642,7 @@ static void _xmltv_load_grabbers ( void )
|
|||
size_t i, p, n;
|
||||
char *outbuf;
|
||||
char name[1000];
|
||||
char *tmp, *path;
|
||||
char *tmp, *tmp2 = NULL, *path;
|
||||
|
||||
/* Load data */
|
||||
outlen = spawn_and_store_stdout(XMLTV_FIND, NULL, &outbuf);
|
||||
|
@ -665,7 +676,7 @@ static void _xmltv_load_grabbers ( void )
|
|||
NULL
|
||||
};
|
||||
path = strdup(tmp);
|
||||
tmp = strtok(path, ":");
|
||||
tmp = strtok_r(path, ":", &tmp2);
|
||||
while (tmp) {
|
||||
DIR *dir;
|
||||
struct dirent *de;
|
||||
|
@ -674,7 +685,8 @@ static void _xmltv_load_grabbers ( void )
|
|||
while ((de = readdir(dir))) {
|
||||
if (strstr(de->d_name, XMLTV_GRAB) != de->d_name) continue;
|
||||
snprintf(bin, sizeof(bin), "%s/%s", tmp, de->d_name);
|
||||
if (lstat(bin, &st)) continue;
|
||||
if (epggrab_module_find_by_id(bin)) continue;
|
||||
if (stat(bin, &st)) continue;
|
||||
if (!(st.st_mode & S_IEXEC)) continue;
|
||||
if (!S_ISREG(st.st_mode)) continue;
|
||||
if ((outlen = spawn_and_store_stdout(bin, argv, &outbuf)) > 0) {
|
||||
|
@ -687,7 +699,7 @@ static void _xmltv_load_grabbers ( void )
|
|||
}
|
||||
closedir(dir);
|
||||
}
|
||||
tmp = strtok(NULL, ":");
|
||||
tmp = strtok_r(NULL, ":", &tmp2);
|
||||
}
|
||||
free(path);
|
||||
}
|
||||
|
|
|
@ -89,9 +89,10 @@ th_dvb_mux_instance_t *epggrab_mux_next ( th_dvb_adapter_t *tda )
|
|||
epggrab_ota_mux_t *ota;
|
||||
time(&now);
|
||||
TAILQ_FOREACH(ota, &ota_mux_all, glob_link) {
|
||||
if (ota->tdmi->tdmi_adapter != tda) continue;
|
||||
if (ota->interval + ota->completed > now) return NULL;
|
||||
if (!ota->is_reg) return NULL;
|
||||
if (ota->tdmi->tdmi_adapter == tda) break;
|
||||
break;
|
||||
}
|
||||
return ota ? ota->tdmi : NULL;
|
||||
}
|
||||
|
@ -136,6 +137,7 @@ void epggrab_ota_load ( void )
|
|||
if ((l = htsmsg_get_list_by_field(f)))
|
||||
_epggrab_ota_load_one((epggrab_module_ota_t*)mod, l);
|
||||
}
|
||||
htsmsg_destroy(m);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,6 +173,7 @@ void epggrab_ota_save ( void )
|
|||
}
|
||||
|
||||
hts_settings_save(m, "epggrab/otamux");
|
||||
htsmsg_destroy(m);
|
||||
}
|
||||
|
||||
/* **************************************************************************
|
||||
|
@ -335,7 +338,21 @@ static void _epggrab_ota_finished ( epggrab_ota_mux_t *ota )
|
|||
/* Reinsert into reg queue */
|
||||
else {
|
||||
TAILQ_REMOVE(&ota_mux_all, ota, glob_link);
|
||||
TAILQ_INSERT_SORTED(&ota_mux_all, ota, glob_link, _ota_time_cmp);
|
||||
// Find the last queue entry that can run before ota
|
||||
// (i.e _ota_time_cmp(ota, entry)>0) and re-insert ota
|
||||
// directly after this entry. If no matching entry is
|
||||
// found (i.e ota can run before any other entry),
|
||||
// re-insert ota at the queue head.
|
||||
epggrab_ota_mux_t *entry = NULL;
|
||||
epggrab_ota_mux_t *tmp;
|
||||
TAILQ_FOREACH(tmp, &ota_mux_all, glob_link) {
|
||||
if(_ota_time_cmp(ota, tmp)>0) entry = tmp;
|
||||
}
|
||||
if (entry) {
|
||||
TAILQ_INSERT_AFTER(&ota_mux_all, entry, ota, glob_link);
|
||||
} else {
|
||||
TAILQ_INSERT_HEAD(&ota_mux_all, ota, glob_link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -214,12 +214,13 @@ fb_dir *fb_opendir ( const char *path )
|
|||
|
||||
/* Bundle */
|
||||
#if ENABLE_BUNDLE
|
||||
char *tmp1 = strdup(path);
|
||||
char *tmp2 = strtok(tmp1, "/");
|
||||
char *tmp1, *tmp2, *tmp3 = NULL;
|
||||
tmp1 = strdup(path);
|
||||
tmp2 = strtok_r(tmp1, "/", &tmp3);
|
||||
filebundle_entry_t *fb = filebundle_root;
|
||||
while (fb && tmp2) {
|
||||
if (fb->type == FB_DIR && !strcmp(fb->name, tmp2)) {
|
||||
tmp2 = strtok(NULL, "/");
|
||||
tmp2 = strtok_r(NULL, "/", &tmp3);
|
||||
if (tmp2) fb = fb->d.child;
|
||||
} else {
|
||||
fb = fb->next;
|
||||
|
@ -383,7 +384,7 @@ fb_file *fb_open2
|
|||
} else {
|
||||
char path[512];
|
||||
snprintf(path, sizeof(path), "%s/%s", dir->d.root, name);
|
||||
FILE *fp = fopen(path, "r");
|
||||
FILE *fp = fopen(path, "rb");
|
||||
if (fp) {
|
||||
struct stat st;
|
||||
lstat(path, &st);
|
||||
|
@ -492,7 +493,8 @@ ssize_t fb_read ( fb_file *fp, void *buf, size_t count )
|
|||
memcpy(buf, fp->buf + fp->pos, count);
|
||||
fp->pos += count;
|
||||
} else if (fp->type == FB_DIRECT) {
|
||||
fp->pos += fread(buf, 1, count, fp->d.cur);
|
||||
count = fread(buf, 1, count, fp->d.cur);
|
||||
fp->pos += count;
|
||||
} else {
|
||||
count = MIN(count, fp->b.root->f.size - fp->pos);
|
||||
memcpy(buf, fp->b.root->f.data + fp->pos, count);
|
||||
|
|
182
src/htsbuf.c
182
src/htsbuf.c
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Buffer management functions
|
||||
* Copyright (C) 2008 Andreas Öman
|
||||
* Copyright (C) 2008 Andreas Öman
|
||||
*
|
||||
* 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
|
||||
|
@ -22,17 +22,9 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include "htsbuf.h"
|
||||
|
||||
#include "tvheadend.h"
|
||||
|
||||
#ifndef MIN
|
||||
#define MIN(a, b) ((a) < (b) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
#ifndef MAX
|
||||
#define MAX(a, b) ((a) > (b) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
#include "htsbuf.h"
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -206,6 +198,7 @@ htsbuf_peek(htsbuf_queue_t *hq, void *buf, size_t len)
|
|||
c = MIN(hd->hd_data_len - hd->hd_data_off, len);
|
||||
memcpy(buf, hd->hd_data + hd->hd_data_off, c);
|
||||
|
||||
r += c;
|
||||
buf += c;
|
||||
len -= c;
|
||||
|
||||
|
@ -233,7 +226,7 @@ htsbuf_drop(htsbuf_queue_t *hq, size_t len)
|
|||
len -= c;
|
||||
hd->hd_data_off += c;
|
||||
hq->hq_size -= c;
|
||||
|
||||
r += c;
|
||||
if(hd->hd_data_off == hd->hd_data_len)
|
||||
htsbuf_data_free(hq, hd);
|
||||
}
|
||||
|
@ -314,19 +307,162 @@ htsbuf_appendq(htsbuf_queue_t *hq, htsbuf_queue_t *src)
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
uint32_t
|
||||
htsbuf_crc32(htsbuf_queue_t *hq, uint32_t crc)
|
||||
void
|
||||
htsbuf_dump_raw_stderr(htsbuf_queue_t *hq)
|
||||
{
|
||||
htsbuf_data_t *hd;
|
||||
|
||||
TAILQ_FOREACH(hd, &hq->hq_q, hd_link)
|
||||
crc = tvh_crc32(hd->hd_data + hd->hd_data_off,
|
||||
hd->hd_data_len - hd->hd_data_off,
|
||||
crc);
|
||||
return crc;
|
||||
char n = '\n';
|
||||
|
||||
TAILQ_FOREACH(hd, &hq->hq_q, hd_link) {
|
||||
if(write(2, hd->hd_data + hd->hd_data_off,
|
||||
hd->hd_data_len - hd->hd_data_off)
|
||||
!= hd->hd_data_len - hd->hd_data_off)
|
||||
break;
|
||||
}
|
||||
if(write(2, &n, 1) != 1)
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
htsbuf_hexdump(htsbuf_queue_t *hq, const char *prefix)
|
||||
{
|
||||
void *r = malloc(hq->hq_size);
|
||||
htsbuf_peek(hq, r, hq->hq_size);
|
||||
hexdump(prefix, r, hq->hq_size);
|
||||
free(r);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
htsbuf_append_and_escape_xml(htsbuf_queue_t *hq, const char *s)
|
||||
{
|
||||
const char *c = s;
|
||||
const char *e = s + strlen(s);
|
||||
if(e == s)
|
||||
return;
|
||||
|
||||
while(1) {
|
||||
const char *esc;
|
||||
switch(*c++) {
|
||||
case '<': esc = "<"; break;
|
||||
case '>': esc = ">"; break;
|
||||
case '&': esc = "&"; break;
|
||||
case '\'': esc = "'"; break;
|
||||
case '"': esc = """; break;
|
||||
default: esc = NULL; break;
|
||||
}
|
||||
|
||||
if(esc != NULL) {
|
||||
htsbuf_append(hq, s, c - s - 1);
|
||||
htsbuf_append(hq, esc, strlen(esc));
|
||||
s = c;
|
||||
}
|
||||
|
||||
if(c == e) {
|
||||
htsbuf_append(hq, s, c - s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
htsbuf_append_and_escape_url(htsbuf_queue_t *hq, const char *s)
|
||||
{
|
||||
const char *c = s;
|
||||
const char *e = s + strlen(s);
|
||||
char C;
|
||||
if(e == s)
|
||||
return;
|
||||
|
||||
while(1) {
|
||||
const char *esc;
|
||||
char buf[4];
|
||||
C = *c++;
|
||||
|
||||
if((C >= '0' && C <= '9') ||
|
||||
(C >= 'a' && C <= 'z') ||
|
||||
(C >= 'A' && C <= 'Z') ||
|
||||
C == '_' ||
|
||||
C == '~' ||
|
||||
C == '.' ||
|
||||
C == '-') {
|
||||
esc = NULL;
|
||||
} else {
|
||||
static const char hexchars[16] = "0123456789ABCDEF";
|
||||
buf[0] = '%';
|
||||
buf[1] = hexchars[(C >> 4) & 0xf];
|
||||
buf[2] = hexchars[C & 0xf];;
|
||||
buf[3] = 0;
|
||||
esc = buf;
|
||||
}
|
||||
|
||||
if(esc != NULL) {
|
||||
htsbuf_append(hq, s, c - s - 1);
|
||||
htsbuf_append(hq, esc, strlen(esc));
|
||||
s = c;
|
||||
}
|
||||
|
||||
if(c == e) {
|
||||
htsbuf_append(hq, s, c - s);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
htsbuf_append_and_escape_jsonstr(htsbuf_queue_t *hq, const char *str)
|
||||
{
|
||||
const char *s = str;
|
||||
|
||||
htsbuf_append(hq, "\"", 1);
|
||||
|
||||
while(*s != 0) {
|
||||
if(*s == '"' || *s == '\\' || *s == '\n' || *s == '\r' || *s == '\t') {
|
||||
htsbuf_append(hq, str, s - str);
|
||||
|
||||
if(*s == '"')
|
||||
htsbuf_append(hq, "\\\"", 2);
|
||||
else if(*s == '\n')
|
||||
htsbuf_append(hq, "\\n", 2);
|
||||
else if(*s == '\r')
|
||||
htsbuf_append(hq, "\\r", 2);
|
||||
else if(*s == '\t')
|
||||
htsbuf_append(hq, "\\t", 2);
|
||||
else
|
||||
htsbuf_append(hq, "\\\\", 2);
|
||||
s++;
|
||||
str = s;
|
||||
} else {
|
||||
s++;
|
||||
}
|
||||
}
|
||||
htsbuf_append(hq, str, s - str);
|
||||
htsbuf_append(hq, "\"", 1);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
char *
|
||||
htsbuf_to_string(htsbuf_queue_t *hq)
|
||||
{
|
||||
char *r = malloc(hq->hq_size + 1);
|
||||
r[hq->hq_size] = 0;
|
||||
htsbuf_read(hq, r, hq->hq_size);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
|
29
src/htsbuf.h
29
src/htsbuf.h
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Buffer management functions
|
||||
* Copyright (C) 2008 Andreas Öman
|
||||
* Copyright (C) 2008 Andreas Öman
|
||||
*
|
||||
* 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
|
||||
|
@ -20,10 +20,11 @@
|
|||
#define HTSBUF_H__
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include "queue.h"
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "queue.h"
|
||||
|
||||
|
||||
TAILQ_HEAD(htsbuf_data_queue, htsbuf_data);
|
||||
|
||||
typedef struct htsbuf_data {
|
||||
|
@ -48,15 +49,12 @@ void htsbuf_queue_flush(htsbuf_queue_t *hq);
|
|||
|
||||
void htsbuf_vqprintf(htsbuf_queue_t *hq, const char *fmt, va_list ap);
|
||||
|
||||
void htsbuf_qprintf(htsbuf_queue_t *hq, const char *fmt, ...)
|
||||
__attribute__((format(printf,2,3)));
|
||||
void htsbuf_qprintf(htsbuf_queue_t *hq, const char *fmt, ...);
|
||||
|
||||
void htsbuf_append(htsbuf_queue_t *hq, const void *buf, size_t len);
|
||||
|
||||
void htsbuf_append_prealloc(htsbuf_queue_t *hq, const void *buf, size_t len);
|
||||
|
||||
void htsbuf_appendq(htsbuf_queue_t *hq, htsbuf_queue_t *src);
|
||||
|
||||
void htsbuf_data_free(htsbuf_queue_t *hq, htsbuf_data_t *hd);
|
||||
|
||||
size_t htsbuf_read(htsbuf_queue_t *hq, void *buf, size_t len);
|
||||
|
@ -67,6 +65,21 @@ size_t htsbuf_drop(htsbuf_queue_t *hq, size_t len);
|
|||
|
||||
size_t htsbuf_find(htsbuf_queue_t *hq, uint8_t v);
|
||||
|
||||
uint32_t htsbuf_crc32(htsbuf_queue_t *q, uint32_t crc);
|
||||
void htsbuf_appendq(htsbuf_queue_t *hq, htsbuf_queue_t *src);
|
||||
|
||||
void htsbuf_append_and_escape_xml(htsbuf_queue_t *hq, const char *str);
|
||||
|
||||
void htsbuf_append_and_escape_url(htsbuf_queue_t *hq, const char *s);
|
||||
|
||||
void htsbuf_append_and_escape_jsonstr(htsbuf_queue_t *hq, const char *s);
|
||||
|
||||
void htsbuf_dump_raw_stderr(htsbuf_queue_t *hq);
|
||||
|
||||
char *htsbuf_to_string(htsbuf_queue_t *hq);
|
||||
|
||||
struct rstr;
|
||||
struct rstr *htsbuf_to_rstr(htsbuf_queue_t *hq, const char *prefix);
|
||||
|
||||
void htsbuf_hexdump(htsbuf_queue_t *hq, const char *prefix);
|
||||
|
||||
#endif /* HTSBUF_H__ */
|
||||
|
|
188
src/htsmsg.c
188
src/htsmsg.c
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Functions for manipulating HTS messages
|
||||
* Copyright (C) 2007 Andreas Öman
|
||||
* Copyright (C) 2007 Andreas Öman
|
||||
*
|
||||
* 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
|
||||
|
@ -27,10 +27,10 @@
|
|||
|
||||
static void htsmsg_clear(htsmsg_t *msg);
|
||||
|
||||
/*
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
void
|
||||
htsmsg_field_destroy(htsmsg_t *msg, htsmsg_field_t *f)
|
||||
{
|
||||
TAILQ_REMOVE(&msg->hm_fields, f, hmf_link);
|
||||
|
@ -102,7 +102,7 @@ htsmsg_field_add(htsmsg_t *msg, const char *name, int type, int flags)
|
|||
/*
|
||||
*
|
||||
*/
|
||||
static htsmsg_field_t *
|
||||
htsmsg_field_t *
|
||||
htsmsg_field_find(htsmsg_t *msg, const char *name)
|
||||
{
|
||||
htsmsg_field_t *f;
|
||||
|
@ -186,6 +186,21 @@ htsmsg_add_u32(htsmsg_t *msg, const char *name, uint32_t u32)
|
|||
f->hmf_s64 = u32;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
int
|
||||
htsmsg_set_u32(htsmsg_t *msg, const char *name, uint32_t u32)
|
||||
{
|
||||
htsmsg_field_t *f = htsmsg_field_find(msg, name);
|
||||
if (!f)
|
||||
f = htsmsg_field_add(msg, name, HMF_S64, HMF_NAME_ALLOCED);
|
||||
if (f->hmf_type != HMF_S64)
|
||||
return 1;
|
||||
f->hmf_s64 = u32;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
|
@ -196,16 +211,6 @@ htsmsg_add_s64(htsmsg_t *msg, const char *name, int64_t s64)
|
|||
f->hmf_s64 = s64;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
void
|
||||
htsmsg_add_u64(htsmsg_t *msg, const char *name, uint64_t u64)
|
||||
{
|
||||
htsmsg_field_t *f = htsmsg_field_add(msg, name, HMF_S64, HMF_NAME_ALLOCED);
|
||||
f->hmf_s64 = u64;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
|
@ -217,6 +222,17 @@ htsmsg_add_s32(htsmsg_t *msg, const char *name, int32_t s32)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
void
|
||||
htsmsg_add_dbl(htsmsg_t *msg, const char *name, double dbl)
|
||||
{
|
||||
htsmsg_field_t *f = htsmsg_field_add(msg, name, HMF_DBL, HMF_NAME_ALLOCED);
|
||||
f->hmf_dbl = dbl;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
|
@ -267,8 +283,8 @@ htsmsg_add_msg(htsmsg_t *msg, const char *name, htsmsg_t *sub)
|
|||
HMF_NAME_ALLOCED);
|
||||
|
||||
assert(sub->hm_data == NULL);
|
||||
TAILQ_MOVE(&f->hmf_msg.hm_fields, &sub->hm_fields, hmf_link);
|
||||
f->hmf_msg.hm_islist = sub->hm_islist;
|
||||
TAILQ_MOVE(&f->hmf_msg.hm_fields, &sub->hm_fields, hmf_link);
|
||||
free(sub);
|
||||
}
|
||||
|
||||
|
@ -312,10 +328,14 @@ htsmsg_get_s64(htsmsg_t *msg, const char *name, int64_t *s64p)
|
|||
case HMF_S64:
|
||||
*s64p = f->hmf_s64;
|
||||
break;
|
||||
case HMF_DBL:
|
||||
*s64p = f->hmf_dbl;
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -326,30 +346,6 @@ htsmsg_get_s64_or_default(htsmsg_t *msg, const char *name, int64_t def)
|
|||
return htsmsg_get_s64(msg, name, &s64) ? def : s64;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
int
|
||||
htsmsg_get_u64(htsmsg_t *msg, const char *name, uint64_t *u64p)
|
||||
{
|
||||
htsmsg_field_t *f;
|
||||
|
||||
if((f = htsmsg_field_find(msg, name)) == NULL)
|
||||
return HTSMSG_ERR_FIELD_NOT_FOUND;
|
||||
|
||||
switch(f->hmf_type) {
|
||||
default:
|
||||
return HTSMSG_ERR_CONVERSION_IMPOSSIBLE;
|
||||
case HMF_STR:
|
||||
*u64p = strtoull(f->hmf_str, NULL, 0);
|
||||
break;
|
||||
case HMF_S64:
|
||||
*u64p = f->hmf_s64;
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
|
@ -373,13 +369,26 @@ htsmsg_get_u32(htsmsg_t *msg, const char *name, uint32_t *u32p)
|
|||
/**
|
||||
*
|
||||
*/
|
||||
uint32_t
|
||||
int
|
||||
htsmsg_get_u32_or_default(htsmsg_t *msg, const char *name, uint32_t def)
|
||||
{
|
||||
uint32_t u32;
|
||||
return htsmsg_get_u32(msg, name, &u32) ? def : u32;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
int32_t
|
||||
htsmsg_get_s32_or_default(htsmsg_t *msg, const char *name, int32_t def)
|
||||
{
|
||||
int32_t s32;
|
||||
return htsmsg_get_s32(msg, name, &s32) ? def : s32;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
|
@ -400,6 +409,25 @@ htsmsg_get_s32(htsmsg_t *msg, const char *name, int32_t *s32p)
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
int
|
||||
htsmsg_get_dbl(htsmsg_t *msg, const char *name, double *dblp)
|
||||
{
|
||||
htsmsg_field_t *f;
|
||||
|
||||
if((f = htsmsg_field_find(msg, name)) == NULL)
|
||||
return HTSMSG_ERR_FIELD_NOT_FOUND;
|
||||
|
||||
if(f->hmf_type != HMF_DBL)
|
||||
return HTSMSG_ERR_CONVERSION_IMPOSSIBLE;
|
||||
|
||||
*dblp = f->hmf_dbl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
|
@ -457,13 +485,6 @@ htsmsg_get_str(htsmsg_t *msg, const char *name)
|
|||
|
||||
}
|
||||
|
||||
const char *
|
||||
htsmsg_get_str_or_default(htsmsg_t *msg, const char *name, const char *def)
|
||||
{
|
||||
const char *str = htsmsg_get_str(msg, name);
|
||||
return str ?: def;
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
|
@ -493,6 +514,32 @@ htsmsg_get_map_multi(htsmsg_t *msg, ...)
|
|||
return msg;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const char *
|
||||
htsmsg_get_str_multi(htsmsg_t *msg, ...)
|
||||
{
|
||||
va_list ap;
|
||||
const char *n;
|
||||
htsmsg_field_t *f;
|
||||
va_start(ap, msg);
|
||||
|
||||
while((n = va_arg(ap, char *)) != NULL) {
|
||||
if((f = htsmsg_field_find(msg, n)) == NULL)
|
||||
return NULL;
|
||||
else if(f->hmf_type == HMF_STR)
|
||||
return f->hmf_str;
|
||||
else if(f->hmf_type == HMF_MAP)
|
||||
msg = &f->hmf_msg;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
*/
|
||||
|
@ -565,6 +612,10 @@ htsmsg_print0(htsmsg_t *msg, int indent)
|
|||
case HMF_S64:
|
||||
printf("S64) = %" PRId64 "\n", f->hmf_s64);
|
||||
break;
|
||||
|
||||
case HMF_DBL:
|
||||
printf("DBL) = %f\n", f->hmf_dbl);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -611,6 +662,10 @@ htsmsg_copy_i(htsmsg_t *src, htsmsg_t *dst)
|
|||
case HMF_BIN:
|
||||
htsmsg_add_bin(dst, f->hmf_name, f->hmf_bin, f->hmf_binsize);
|
||||
break;
|
||||
|
||||
case HMF_DBL:
|
||||
htsmsg_add_dbl(dst, f->hmf_name, f->hmf_dbl);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -623,13 +678,44 @@ htsmsg_copy(htsmsg_t *src)
|
|||
return dst;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
htsmsg_t *
|
||||
htsmsg_get_map_in_list(htsmsg_t *m, int num)
|
||||
{
|
||||
htsmsg_field_t *f;
|
||||
|
||||
HTSMSG_FOREACH(f, m) {
|
||||
if(!--num)
|
||||
return htsmsg_get_map_by_field(f);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
htsmsg_dtor(htsmsg_t **mp)
|
||||
htsmsg_t *
|
||||
htsmsg_get_map_by_field_if_name(htsmsg_field_t *f, const char *name)
|
||||
{
|
||||
if(*mp != NULL)
|
||||
htsmsg_destroy(*mp);
|
||||
if(f->hmf_type != HMF_MAP)
|
||||
return NULL;
|
||||
if(strcmp(f->hmf_name, name))
|
||||
return NULL;
|
||||
return &f->hmf_msg;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
const char *
|
||||
htsmsg_get_cdata(htsmsg_t *m, const char *field)
|
||||
{
|
||||
return htsmsg_get_str_multi(m, field, "cdata", NULL);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
118
src/htsmsg.h
118
src/htsmsg.h
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Functions for manipulating HTS messages
|
||||
* Copyright (C) 2007 Andreas Öman
|
||||
* Copyright (C) 2007 Andreas Öman
|
||||
*
|
||||
* 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
|
||||
|
@ -16,9 +16,8 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef HTSMSG_H_
|
||||
#define HTSMSG_H_
|
||||
|
||||
#pragma once
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
#include "queue.h"
|
||||
|
||||
|
@ -50,6 +49,7 @@ typedef struct htsmsg {
|
|||
#define HMF_STR 3
|
||||
#define HMF_BIN 4
|
||||
#define HMF_LIST 5
|
||||
#define HMF_DBL 6
|
||||
|
||||
typedef struct htsmsg_field {
|
||||
TAILQ_ENTRY(htsmsg_field) hmf_link;
|
||||
|
@ -68,6 +68,7 @@ typedef struct htsmsg_field {
|
|||
size_t len;
|
||||
} bin;
|
||||
htsmsg_t msg;
|
||||
double dbl;
|
||||
} u;
|
||||
} htsmsg_field_t;
|
||||
|
||||
|
@ -76,11 +77,12 @@ typedef struct htsmsg_field {
|
|||
#define hmf_str u.str
|
||||
#define hmf_bin u.bin.data
|
||||
#define hmf_binsize u.bin.len
|
||||
#define hmf_dbl u.dbl
|
||||
|
||||
#define htsmsg_get_map_by_field(f) \
|
||||
((f)->hmf_type == HMF_MAP ? &(f)->hmf_msg : NULL)
|
||||
#define htsmsg_get_list_by_field(f) \
|
||||
((f)->hmf_type == HMF_LIST ? &(f)->hmf_msg : NULL)
|
||||
((f)->hmf_type == HMF_LIST ? &(f)->hmf_msg : NULL)
|
||||
|
||||
#define HTSMSG_FOREACH(f, msg) TAILQ_FOREACH(f, &(msg)->hm_fields, hmf_link)
|
||||
|
||||
|
@ -94,6 +96,11 @@ htsmsg_t *htsmsg_create_map(void);
|
|||
*/
|
||||
htsmsg_t *htsmsg_create_list(void);
|
||||
|
||||
/**
|
||||
* Remove a given field from a msg
|
||||
*/
|
||||
void htsmsg_field_destroy(htsmsg_t *msg, htsmsg_field_t *f);
|
||||
|
||||
/**
|
||||
* Destroys a message (map or list)
|
||||
*/
|
||||
|
@ -104,16 +111,16 @@ void htsmsg_destroy(htsmsg_t *msg);
|
|||
*/
|
||||
void htsmsg_add_u32(htsmsg_t *msg, const char *name, uint32_t u32);
|
||||
|
||||
/**
|
||||
* Add/update an integer field
|
||||
*/
|
||||
int htsmsg_set_u32(htsmsg_t *msg, const char *name, uint32_t u32);
|
||||
|
||||
/**
|
||||
* Add an integer field where source is signed 32 bit.
|
||||
*/
|
||||
void htsmsg_add_s32(htsmsg_t *msg, const char *name, int32_t s32);
|
||||
|
||||
/**
|
||||
* Add an integer field where source is unsigned 64 bit.
|
||||
*/
|
||||
void htsmsg_add_u64(htsmsg_t *msg, const char *name, uint64_t u64);
|
||||
|
||||
/**
|
||||
* Add an integer field where source is signed 64 bit.
|
||||
*/
|
||||
|
@ -129,6 +136,11 @@ void htsmsg_add_str(htsmsg_t *msg, const char *name, const char *str);
|
|||
*/
|
||||
void htsmsg_add_msg(htsmsg_t *msg, const char *name, htsmsg_t *sub);
|
||||
|
||||
/**
|
||||
* Add an field where source is a double
|
||||
*/
|
||||
void htsmsg_add_dbl(htsmsg_t *msg, const char *name, double dbl);
|
||||
|
||||
/**
|
||||
* Add an field where source is a list or map message.
|
||||
*
|
||||
|
@ -160,15 +172,6 @@ void htsmsg_add_binptr(htsmsg_t *msg, const char *name, const void *bin,
|
|||
*/
|
||||
int htsmsg_get_u32(htsmsg_t *msg, const char *name, uint32_t *u32p);
|
||||
|
||||
/**
|
||||
* Return the field \p name as an u32.
|
||||
*
|
||||
* @return An unsigned 32 bit integer or def if the field can not be found
|
||||
* or if conversion is not possible.
|
||||
*/
|
||||
uint32_t htsmsg_get_u32_or_default
|
||||
(htsmsg_t *msg, const char *name, uint32_t def);
|
||||
|
||||
/**
|
||||
* Get an integer as an signed 32 bit integer.
|
||||
*
|
||||
|
@ -187,24 +190,11 @@ int htsmsg_get_s32(htsmsg_t *msg, const char *name, int32_t *s32p);
|
|||
*/
|
||||
int htsmsg_get_s64(htsmsg_t *msg, const char *name, int64_t *s64p);
|
||||
|
||||
|
||||
/**
|
||||
/*
|
||||
* Return the field \p name as an s64.
|
||||
*
|
||||
* @return A signed 64 bit integer or def if the field can not be found
|
||||
* or if conversion is not possible.
|
||||
*/
|
||||
int64_t htsmsg_get_s64_or_default
|
||||
(htsmsg_t *msg, const char *name, int64_t def);
|
||||
int64_t htsmsg_get_s64_or_default(htsmsg_t *msg, const char *name, int64_t def);
|
||||
|
||||
/**
|
||||
* Get an integer as an unsigned 64 bit integer.
|
||||
*
|
||||
* @return HTSMSG_ERR_FIELD_NOT_FOUND - Field does not exist
|
||||
* HTSMSG_ERR_CONVERSION_IMPOSSIBLE - Field is not an integer or
|
||||
* out of range for the requested storage.
|
||||
*/
|
||||
int htsmsg_get_u64(htsmsg_t *msg, const char *name, uint64_t *u64p);
|
||||
|
||||
/**
|
||||
* Get pointer to a binary field. No copying of data is performed.
|
||||
|
@ -236,15 +226,6 @@ htsmsg_t *htsmsg_get_list(htsmsg_t *msg, const char *name);
|
|||
*/
|
||||
const char *htsmsg_get_str(htsmsg_t *msg, const char *name);
|
||||
|
||||
/**
|
||||
* Get a field of type 'string'. No copying is done.
|
||||
*
|
||||
* @return def if the field can not be found or not of string type.
|
||||
* Otherwise a pointer to the data is returned.
|
||||
*/
|
||||
const char *htsmsg_get_str_or_default
|
||||
(htsmsg_t *msg, const char *name, const char *def);
|
||||
|
||||
/**
|
||||
* Get a field of type 'map'. No copying is done.
|
||||
*
|
||||
|
@ -256,7 +237,23 @@ htsmsg_t *htsmsg_get_map(htsmsg_t *msg, const char *name);
|
|||
/**
|
||||
* Traverse a hierarchy of htsmsg's to find a specific child.
|
||||
*/
|
||||
htsmsg_t *htsmsg_get_map_multi(htsmsg_t *msg, ...);
|
||||
htsmsg_t *htsmsg_get_map_multi(htsmsg_t *msg, ...)
|
||||
__attribute__((__sentinel__(0)));
|
||||
|
||||
/**
|
||||
* Traverse a hierarchy of htsmsg's to find a specific child.
|
||||
*/
|
||||
const char *htsmsg_get_str_multi(htsmsg_t *msg, ...)
|
||||
__attribute__((__sentinel__(0)));
|
||||
|
||||
/**
|
||||
* Get a field of type 'double'.
|
||||
*
|
||||
* @return HTSMSG_ERR_FIELD_NOT_FOUND - Field does not exist
|
||||
* HTSMSG_ERR_CONVERSION_IMPOSSIBLE - Field is not an integer or
|
||||
* out of range for the requested storage.
|
||||
*/
|
||||
int htsmsg_get_dbl(htsmsg_t *msg, const char *name, double *dblp);
|
||||
|
||||
/**
|
||||
* Given the field \p f, return a string if it is of type string, otherwise
|
||||
|
@ -264,6 +261,23 @@ htsmsg_t *htsmsg_get_map_multi(htsmsg_t *msg, ...);
|
|||
*/
|
||||
const char *htsmsg_field_get_string(htsmsg_field_t *f);
|
||||
|
||||
/**
|
||||
* Return the field \p name as an u32.
|
||||
*
|
||||
* @return An unsigned 32 bit integer or NULL if the field can not be found
|
||||
* or if conversion is not possible.
|
||||
*/
|
||||
int htsmsg_get_u32_or_default(htsmsg_t *msg, const char *name, uint32_t def);
|
||||
|
||||
/**
|
||||
* Return the field \p name as an s32.
|
||||
*
|
||||
* @return A signed 32 bit integer or NULL if the field can not be found
|
||||
* or if conversion is not possible.
|
||||
*/
|
||||
int32_t htsmsg_get_s32_or_default(htsmsg_t *msg, const char *name,
|
||||
int32_t def);
|
||||
|
||||
/**
|
||||
* Remove the given field called \p name from the message \p msg.
|
||||
*/
|
||||
|
@ -288,6 +302,12 @@ void htsmsg_print(htsmsg_t *msg);
|
|||
htsmsg_field_t *htsmsg_field_add(htsmsg_t *msg, const char *name,
|
||||
int type, int flags);
|
||||
|
||||
/**
|
||||
* Get a field, return NULL if it does not exist
|
||||
*/
|
||||
htsmsg_field_t *htsmsg_field_find(htsmsg_t *msg, const char *name);
|
||||
|
||||
|
||||
/**
|
||||
* Clone a message.
|
||||
*/
|
||||
|
@ -296,8 +316,12 @@ htsmsg_t *htsmsg_copy(htsmsg_t *src);
|
|||
#define HTSMSG_FOREACH(f, msg) TAILQ_FOREACH(f, &(msg)->hm_fields, hmf_link)
|
||||
|
||||
|
||||
extern void htsmsg_dtor(htsmsg_t **mp);
|
||||
/**
|
||||
* Misc
|
||||
*/
|
||||
htsmsg_t *htsmsg_get_map_in_list(htsmsg_t *m, int num);
|
||||
|
||||
#define htsmsg_autodtor(n) htsmsg_t *n __attribute__((cleanup(htsmsg_dtor)))
|
||||
htsmsg_t *htsmsg_get_map_by_field_if_name(htsmsg_field_t *f, const char *name);
|
||||
|
||||
const char *htsmsg_get_cdata(htsmsg_t *m, const char *field);
|
||||
|
||||
#endif /* HTSMSG_H_ */
|
||||
|
|
|
@ -25,54 +25,19 @@
|
|||
|
||||
#include "htsmsg_json.h"
|
||||
#include "htsbuf.h"
|
||||
#include "misc/json.h"
|
||||
#include "misc/dbl.h"
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
htsmsg_json_encode_string(const char *str, htsbuf_queue_t *hq)
|
||||
{
|
||||
const char *s = str;
|
||||
|
||||
htsbuf_append(hq, "\"", 1);
|
||||
|
||||
while(*s != 0) {
|
||||
if(*s == '"' || *s == '\\' || *s == '\n' || *s == '\t' || *s == '\r') {
|
||||
htsbuf_append(hq, str, s - str);
|
||||
|
||||
if(*s == '"')
|
||||
htsbuf_append(hq, "\\\"", 2);
|
||||
else if(*s == '\n')
|
||||
htsbuf_append(hq, "\\n", 2);
|
||||
else if(*s == '\t')
|
||||
htsbuf_append(hq, "\\t", 2);
|
||||
else if(*s == '\r')
|
||||
htsbuf_append(hq, "\\r", 2);
|
||||
else
|
||||
htsbuf_append(hq, "\\\\", 2);
|
||||
s++;
|
||||
str = s;
|
||||
} else {
|
||||
s++;
|
||||
}
|
||||
}
|
||||
htsbuf_append(hq, str, s - str);
|
||||
htsbuf_append(hq, "\"", 1);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Note to future:
|
||||
* If your about to add support for numbers with decimal point,
|
||||
* remember to always serialize them with '.' as decimal point character
|
||||
* no matter what current locale says. This is according to the JSON spec.
|
||||
*/
|
||||
static void
|
||||
htsmsg_json_write(htsmsg_t *msg, htsbuf_queue_t *hq, int isarray,
|
||||
int indent, int pretty)
|
||||
{
|
||||
htsmsg_field_t *f;
|
||||
char buf[30];
|
||||
char buf[100];
|
||||
static const char *indentor = "\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";
|
||||
|
||||
htsbuf_append(hq, isarray ? "[" : "{", 1);
|
||||
|
@ -83,7 +48,7 @@ htsmsg_json_write(htsmsg_t *msg, htsbuf_queue_t *hq, int isarray,
|
|||
htsbuf_append(hq, indentor, indent < 16 ? indent : 16);
|
||||
|
||||
if(!isarray) {
|
||||
htsmsg_json_encode_string(f->hmf_name ?: "noname", hq);
|
||||
htsbuf_append_and_escape_jsonstr(hq, f->hmf_name ?: "noname");
|
||||
htsbuf_append(hq, ": ", 2);
|
||||
}
|
||||
|
||||
|
@ -97,11 +62,11 @@ htsmsg_json_write(htsmsg_t *msg, htsbuf_queue_t *hq, int isarray,
|
|||
break;
|
||||
|
||||
case HMF_STR:
|
||||
htsmsg_json_encode_string(f->hmf_str, hq);
|
||||
htsbuf_append_and_escape_jsonstr(hq, f->hmf_str);
|
||||
break;
|
||||
|
||||
case HMF_BIN:
|
||||
htsmsg_json_encode_string("binary", hq);
|
||||
htsbuf_append_and_escape_jsonstr(hq, "binary");
|
||||
break;
|
||||
|
||||
case HMF_S64:
|
||||
|
@ -109,6 +74,11 @@ htsmsg_json_write(htsmsg_t *msg, htsbuf_queue_t *hq, int isarray,
|
|||
htsbuf_append(hq, buf, strlen(buf));
|
||||
break;
|
||||
|
||||
case HMF_DBL:
|
||||
my_double2str(buf, sizeof(buf), f->hmf_dbl);
|
||||
htsbuf_append(hq, buf, strlen(buf));
|
||||
break;
|
||||
|
||||
default:
|
||||
abort();
|
||||
}
|
||||
|
@ -125,343 +95,109 @@ htsmsg_json_write(htsmsg_t *msg, htsbuf_queue_t *hq, int isarray,
|
|||
/**
|
||||
*
|
||||
*/
|
||||
int
|
||||
void
|
||||
htsmsg_json_serialize(htsmsg_t *msg, htsbuf_queue_t *hq, int pretty)
|
||||
{
|
||||
htsmsg_json_write(msg, hq, msg->hm_islist, 2, pretty);
|
||||
if(pretty)
|
||||
htsbuf_append(hq, "\n", 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static const char *htsmsg_json_parse_value(const char *s,
|
||||
htsmsg_t *parent, char *name);
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static char *
|
||||
htsmsg_json_parse_string(const char *s, const char **endp)
|
||||
{
|
||||
const char *start;
|
||||
char *r, *a, *b;
|
||||
int l, esc = 0;
|
||||
|
||||
while(*s > 0 && *s < 33)
|
||||
s++;
|
||||
|
||||
if(*s != '"')
|
||||
return NULL;
|
||||
|
||||
s++;
|
||||
start = s;
|
||||
|
||||
while(1) {
|
||||
if(*s == 0)
|
||||
return NULL;
|
||||
|
||||
if(*s == '\\') {
|
||||
esc = 1;
|
||||
/* skip the escape */
|
||||
s++;
|
||||
if (*s == 'u') s += 4;
|
||||
// Note: we could detect the lack of support here!
|
||||
} else if(*s == '"') {
|
||||
|
||||
*endp = s + 1;
|
||||
|
||||
/* End */
|
||||
l = s - start;
|
||||
r = malloc(l + 1);
|
||||
memcpy(r, start, l);
|
||||
r[l] = 0;
|
||||
|
||||
if(esc) {
|
||||
/* Do deescaping inplace */
|
||||
|
||||
a = b = r;
|
||||
|
||||
while(*a) {
|
||||
if(*a == '\\') {
|
||||
a++;
|
||||
if(*a == 'b')
|
||||
*b++ = '\b';
|
||||
else if(*a == '\\')
|
||||
*b++ = '\\';
|
||||
else if(*a == 'f')
|
||||
*b++ = '\f';
|
||||
else if(*a == 'n')
|
||||
*b++ = '\n';
|
||||
else if(*a == 'r')
|
||||
*b++ = '\r';
|
||||
else if(*a == 't')
|
||||
*b++ = '\t';
|
||||
else if(*a == 'u') {
|
||||
/* 4 hexdigits: Not supported */
|
||||
free(r);
|
||||
return NULL;
|
||||
} else {
|
||||
*b++ = *a;
|
||||
}
|
||||
a++;
|
||||
} else {
|
||||
*b++ = *a++;
|
||||
}
|
||||
}
|
||||
*b = 0;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static htsmsg_t *
|
||||
htsmsg_json_parse_object(const char *s, const char **endp)
|
||||
char *
|
||||
htsmsg_json_serialize_to_str(htsmsg_t *msg, int pretty)
|
||||
{
|
||||
char *name;
|
||||
const char *s2;
|
||||
htsmsg_t *r;
|
||||
|
||||
while(*s > 0 && *s < 33)
|
||||
s++;
|
||||
|
||||
if(*s != '{')
|
||||
return NULL;
|
||||
|
||||
s++;
|
||||
|
||||
r = htsmsg_create_map();
|
||||
|
||||
while(*s > 0 && *s < 33)
|
||||
s++;
|
||||
|
||||
if(*s != '}') while(1) {
|
||||
|
||||
if((name = htsmsg_json_parse_string(s, &s2)) == NULL) {
|
||||
htsmsg_destroy(r);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s = s2;
|
||||
|
||||
while(*s > 0 && *s < 33)
|
||||
s++;
|
||||
|
||||
if(*s != ':') {
|
||||
htsmsg_destroy(r);
|
||||
free(name);
|
||||
return NULL;
|
||||
}
|
||||
s++;
|
||||
|
||||
s2 = htsmsg_json_parse_value(s, r, name);
|
||||
free(name);
|
||||
|
||||
if(s2 == NULL) {
|
||||
htsmsg_destroy(r);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s = s2;
|
||||
|
||||
while(*s > 0 && *s < 33)
|
||||
s++;
|
||||
|
||||
if(*s == '}')
|
||||
break;
|
||||
|
||||
if(*s != ',') {
|
||||
htsmsg_destroy(r);
|
||||
return NULL;
|
||||
}
|
||||
s++;
|
||||
}
|
||||
|
||||
s++;
|
||||
*endp = s;
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static htsmsg_t *
|
||||
htsmsg_json_parse_array(const char *s, const char **endp)
|
||||
{
|
||||
const char *s2;
|
||||
htsmsg_t *r;
|
||||
|
||||
while(*s > 0 && *s < 33)
|
||||
s++;
|
||||
|
||||
if(*s != '[')
|
||||
return NULL;
|
||||
|
||||
s++;
|
||||
|
||||
r = htsmsg_create_list();
|
||||
|
||||
while(*s > 0 && *s < 33)
|
||||
s++;
|
||||
|
||||
if(*s != ']') {
|
||||
|
||||
while(1) {
|
||||
|
||||
s2 = htsmsg_json_parse_value(s, r, NULL);
|
||||
|
||||
if(s2 == NULL) {
|
||||
htsmsg_destroy(r);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s = s2;
|
||||
|
||||
while(*s > 0 && *s < 33)
|
||||
s++;
|
||||
|
||||
if(*s == ']')
|
||||
break;
|
||||
|
||||
if(*s != ',') {
|
||||
htsmsg_destroy(r);
|
||||
return NULL;
|
||||
}
|
||||
s++;
|
||||
}
|
||||
}
|
||||
s++;
|
||||
*endp = s;
|
||||
return r;
|
||||
}
|
||||
|
||||
/*
|
||||
* locale independent strtod.
|
||||
* does not support hex floats as the standard strtod
|
||||
*/
|
||||
static double
|
||||
_strntod(const char *s, char decimal_point_char, char **ep)
|
||||
{
|
||||
static char locale_decimal_point_char = 0;
|
||||
char buf[64];
|
||||
const char *c;
|
||||
double d;
|
||||
|
||||
/* ugly but very portable way to get decimal point char */
|
||||
if(locale_decimal_point_char == 0) {
|
||||
snprintf(buf, sizeof(buf), "%f", 0.0);
|
||||
locale_decimal_point_char = buf[1];
|
||||
assert(locale_decimal_point_char != 0);
|
||||
}
|
||||
|
||||
for(c = s;
|
||||
*c != '\0' &&
|
||||
((*c > 0 && *c < 33) || /* skip whitespace */
|
||||
(*c == decimal_point_char || strchr("+-0123456789", *c) != NULL)); c++)
|
||||
;
|
||||
|
||||
strncpy(buf, s, c - s);
|
||||
buf[c - s] = '\0';
|
||||
|
||||
/* replace if specified char is not same as current locale */
|
||||
if(decimal_point_char != locale_decimal_point_char) {
|
||||
char *r = strchr(buf, decimal_point_char);
|
||||
if(r != NULL)
|
||||
*r = locale_decimal_point_char;
|
||||
}
|
||||
|
||||
d = strtod(buf, ep);
|
||||
|
||||
/* figure out offset in original string */
|
||||
if(ep != NULL)
|
||||
*ep = (char *)s + (*ep - buf);
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static char *
|
||||
htsmsg_json_parse_number(const char *s, double *dp)
|
||||
{
|
||||
char *ep;
|
||||
double d = _strntod(s, '.', &ep);
|
||||
|
||||
if(ep == s)
|
||||
return NULL;
|
||||
|
||||
*dp = d;
|
||||
return ep;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static const char *
|
||||
htsmsg_json_parse_value(const char *s, htsmsg_t *parent, char *name)
|
||||
{
|
||||
const char *s2;
|
||||
htsbuf_queue_t hq;
|
||||
char *str;
|
||||
double d = 0;
|
||||
htsmsg_t *c;
|
||||
|
||||
if((c = htsmsg_json_parse_object(s, &s2)) != NULL) {
|
||||
htsmsg_add_msg(parent, name, c);
|
||||
return s2;
|
||||
} else if((c = htsmsg_json_parse_array(s, &s2)) != NULL) {
|
||||
htsmsg_add_msg(parent, name, c);
|
||||
return s2;
|
||||
} else if((str = htsmsg_json_parse_string(s, &s2)) != NULL) {
|
||||
htsmsg_add_str(parent, name, str);
|
||||
free(str);
|
||||
return s2;
|
||||
} else if((s2 = htsmsg_json_parse_number(s, &d)) != NULL) {
|
||||
htsmsg_add_s64(parent, name, d);
|
||||
return s2;
|
||||
}
|
||||
|
||||
if(!strncmp(s, "true", 4)) {
|
||||
htsmsg_add_u32(parent, name, 1);
|
||||
return s + 4;
|
||||
}
|
||||
|
||||
if(!strncmp(s, "false", 5)) {
|
||||
htsmsg_add_u32(parent, name, 0);
|
||||
return s + 5;
|
||||
}
|
||||
|
||||
if(!strncmp(s, "null", 4)) {
|
||||
/* Don't add anything */
|
||||
return s + 4;
|
||||
}
|
||||
return NULL;
|
||||
htsbuf_queue_init(&hq, 0);
|
||||
htsmsg_json_serialize(msg, &hq, pretty);
|
||||
str = htsbuf_to_string(&hq);
|
||||
htsbuf_queue_flush(&hq);
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void *
|
||||
create_map(void *opaque)
|
||||
{
|
||||
return htsmsg_create_map();
|
||||
}
|
||||
|
||||
static void *
|
||||
create_list(void *opaque)
|
||||
{
|
||||
return htsmsg_create_list();
|
||||
}
|
||||
|
||||
static void
|
||||
destroy_obj(void *opaque, void *obj)
|
||||
{
|
||||
return htsmsg_destroy(obj);
|
||||
}
|
||||
|
||||
static void
|
||||
add_obj(void *opaque, void *parent, const char *name, void *child)
|
||||
{
|
||||
htsmsg_add_msg(parent, name, child);
|
||||
}
|
||||
|
||||
static void
|
||||
add_string(void *opaque, void *parent, const char *name, char *str)
|
||||
{
|
||||
htsmsg_add_str(parent, name, str);
|
||||
free(str);
|
||||
}
|
||||
|
||||
static void
|
||||
add_long(void *opaque, void *parent, const char *name, long v)
|
||||
{
|
||||
htsmsg_add_s64(parent, name, v);
|
||||
}
|
||||
|
||||
static void
|
||||
add_double(void *opaque, void *parent, const char *name, double v)
|
||||
{
|
||||
htsmsg_add_dbl(parent, name, v);
|
||||
}
|
||||
|
||||
static void
|
||||
add_bool(void *opaque, void *parent, const char *name, int v)
|
||||
{
|
||||
htsmsg_add_u32(parent, name, v);
|
||||
}
|
||||
|
||||
static void
|
||||
add_null(void *opaque, void *parent, const char *name)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static const json_deserializer_t json_to_htsmsg = {
|
||||
.jd_create_map = create_map,
|
||||
.jd_create_list = create_list,
|
||||
.jd_destroy_obj = destroy_obj,
|
||||
.jd_add_obj = add_obj,
|
||||
.jd_add_string = add_string,
|
||||
.jd_add_long = add_long,
|
||||
.jd_add_double = add_double,
|
||||
.jd_add_bool = add_bool,
|
||||
.jd_add_null = add_null,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
htsmsg_t *
|
||||
htsmsg_json_deserialize(const char *src)
|
||||
{
|
||||
const char *end;
|
||||
htsmsg_t *c;
|
||||
|
||||
if((c = htsmsg_json_parse_object(src, &end)) != NULL)
|
||||
return c;
|
||||
|
||||
if((c = htsmsg_json_parse_array(src, &end)) != NULL) {
|
||||
c->hm_islist = 1;
|
||||
return c;
|
||||
}
|
||||
return NULL;
|
||||
return json_deserialize(src, &json_to_htsmsg, NULL, NULL, 0);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Functions converting HTSMSGs to/from JSON
|
||||
* Copyright (C) 2008 Andreas Öman
|
||||
* Copyright (C) 2008 Andreas Öman
|
||||
*
|
||||
* 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
|
||||
|
@ -21,12 +21,16 @@
|
|||
|
||||
#include "htsmsg.h"
|
||||
#include "htsbuf.h"
|
||||
|
||||
struct rstr;
|
||||
/**
|
||||
* htsmsg_binary_deserialize
|
||||
*/
|
||||
htsmsg_t *htsmsg_json_deserialize(const char *src);
|
||||
|
||||
int htsmsg_json_serialize(htsmsg_t *msg, htsbuf_queue_t *hq, int pretty);
|
||||
void htsmsg_json_serialize(htsmsg_t *msg, htsbuf_queue_t *hq, int pretty);
|
||||
|
||||
char *htsmsg_json_serialize_to_str(htsmsg_t *msg, int pretty);
|
||||
|
||||
struct rstr *htsmsg_json_serialize_to_rstr(htsmsg_t *msg, const char *prefix);
|
||||
|
||||
#endif /* HTSMSG_JSON_H_ */
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -22,7 +22,7 @@
|
|||
#include "epg.h"
|
||||
#include "dvr/dvr.h"
|
||||
|
||||
void htsp_init(void);
|
||||
void htsp_init(const char *bindaddr);
|
||||
|
||||
void htsp_channel_update_current(channel_t *ch);
|
||||
|
52
src/http.c
52
src/http.c
|
@ -173,18 +173,18 @@ http_send_header(http_connection_t *hc, int rc, const char *content,
|
|||
|
||||
tm = gmtime_r(&t, &tm0);
|
||||
htsbuf_qprintf(&hdrs,
|
||||
"Last-Modified: %s, %02d %s %d %02d:%02d:%02d GMT\r\n",
|
||||
cachedays[tm->tm_wday], tm->tm_year + 1900,
|
||||
cachemonths[tm->tm_mon], tm->tm_mday,
|
||||
tm->tm_hour, tm->tm_min, tm->tm_sec);
|
||||
"Last-Modified: %s, %d %s %02d %02d:%02d:%02d GMT\r\n",
|
||||
cachedays[tm->tm_wday], tm->tm_mday,
|
||||
cachemonths[tm->tm_mon], tm->tm_year + 1900,
|
||||
tm->tm_hour, tm->tm_min, tm->tm_sec);
|
||||
|
||||
t += maxage;
|
||||
|
||||
tm = gmtime_r(&t, &tm0);
|
||||
htsbuf_qprintf(&hdrs,
|
||||
"Expires: %s, %02d %s %d %02d:%02d:%02d GMT\r\n",
|
||||
cachedays[tm->tm_wday], tm->tm_year + 1900,
|
||||
cachemonths[tm->tm_mon], tm->tm_mday,
|
||||
"Expires: %s, %d %s %02d %02d:%02d:%02d GMT\r\n",
|
||||
cachedays[tm->tm_wday], tm->tm_mday,
|
||||
cachemonths[tm->tm_mon], tm->tm_year + 1900,
|
||||
tm->tm_hour, tm->tm_min, tm->tm_sec);
|
||||
|
||||
htsbuf_qprintf(&hdrs, "Cache-Control: max-age=%d\r\n", maxage);
|
||||
|
@ -247,9 +247,12 @@ void
|
|||
http_error(http_connection_t *hc, int error)
|
||||
{
|
||||
const char *errtxt = http_rc2str(error);
|
||||
char *addrstr = (char*)malloc(50);
|
||||
tcp_get_ip_str((struct sockaddr*)hc->hc_peer, addrstr, 50);
|
||||
|
||||
tvhlog(LOG_ERR, "HTTP", "%s: %s -- %d",
|
||||
inet_ntoa(hc->hc_peer->sin_addr), hc->hc_url, error);
|
||||
addrstr, hc->hc_url, error);
|
||||
free(addrstr);
|
||||
|
||||
htsbuf_queue_flush(&hc->hc_reply);
|
||||
|
||||
|
@ -315,10 +318,13 @@ http_access_verify(http_connection_t *hc, int mask)
|
|||
{
|
||||
const char *ticket_id = http_arg_get(&hc->hc_req_args, "ticket");
|
||||
|
||||
if(!access_ticket_verify(ticket_id, hc->hc_url)) {
|
||||
if(!access_ticket_verify(ticket_id, hc->hc_url))
|
||||
{
|
||||
char *addrstr = (char*)malloc(50);
|
||||
tcp_get_ip_str((struct sockaddr*)hc->hc_peer, addrstr, 50);
|
||||
tvhlog(LOG_INFO, "HTTP", "%s: using ticket %s for %s",
|
||||
inet_ntoa(hc->hc_peer->sin_addr), ticket_id,
|
||||
hc->hc_url);
|
||||
addrstr, ticket_id, hc->hc_url);
|
||||
free(addrstr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -504,11 +510,9 @@ process_request(http_connection_t *hc, htsbuf_queue_t *spill)
|
|||
if(hc->hc_username != NULL) {
|
||||
hc->hc_representative = strdup(hc->hc_username);
|
||||
} else {
|
||||
hc->hc_representative = malloc(30);
|
||||
hc->hc_representative = malloc(50);
|
||||
/* Not threadsafe ? */
|
||||
snprintf(hc->hc_representative, 30,
|
||||
"%s", inet_ntoa(hc->hc_peer->sin_addr));
|
||||
|
||||
tcp_get_ip_str((struct sockaddr*)hc->hc_peer, hc->hc_representative, 50);
|
||||
}
|
||||
|
||||
switch(hc->hc_version) {
|
||||
|
@ -607,9 +611,15 @@ http_path_add(const char *path, void *opaque, http_callback_t *callback,
|
|||
uint32_t accessmask)
|
||||
{
|
||||
http_path_t *hp = malloc(sizeof(http_path_t));
|
||||
char *tmp;
|
||||
|
||||
hp->hp_len = strlen(path);
|
||||
hp->hp_path = strdup(path);
|
||||
if (tvheadend_webroot) {
|
||||
size_t len = strlen(tvheadend_webroot) + strlen(path) + 1;
|
||||
hp->hp_path = tmp = malloc(len);
|
||||
sprintf(tmp, "%s%s", tvheadend_webroot, path);
|
||||
} else
|
||||
hp->hp_path = strdup(path);
|
||||
hp->hp_len = strlen(hp->hp_path);
|
||||
hp->hp_opaque = opaque;
|
||||
hp->hp_callback = callback;
|
||||
hp->hp_accessmask = accessmask;
|
||||
|
@ -771,8 +781,8 @@ http_serve_requests(http_connection_t *hc, htsbuf_queue_t *spill)
|
|||
*
|
||||
*/
|
||||
static void
|
||||
http_serve(int fd, void *opaque, struct sockaddr_in *peer,
|
||||
struct sockaddr_in *self)
|
||||
http_serve(int fd, void *opaque, struct sockaddr_storage *peer,
|
||||
struct sockaddr_storage *self)
|
||||
{
|
||||
htsbuf_queue_t spill;
|
||||
http_connection_t hc;
|
||||
|
@ -806,7 +816,7 @@ http_serve(int fd, void *opaque, struct sockaddr_in *peer,
|
|||
* Fire up HTTP server
|
||||
*/
|
||||
void
|
||||
http_server_init(void)
|
||||
http_server_init(const char *bindaddr)
|
||||
{
|
||||
http_server = tcp_server_create(webui_port, http_serve, NULL);
|
||||
http_server = tcp_server_create(bindaddr, tvheadend_webui_port, http_serve, NULL);
|
||||
}
|
||||
|
|
|
@ -39,8 +39,8 @@ typedef struct http_arg {
|
|||
|
||||
typedef struct http_connection {
|
||||
int hc_fd;
|
||||
struct sockaddr_in *hc_peer;
|
||||
struct sockaddr_in *hc_self;
|
||||
struct sockaddr_storage *hc_peer;
|
||||
struct sockaddr_storage *hc_self;
|
||||
char *hc_representative;
|
||||
|
||||
char *hc_url;
|
||||
|
@ -133,7 +133,7 @@ http_path_t *http_path_add(const char *path, void *opaque,
|
|||
|
||||
|
||||
|
||||
void http_server_init(void);
|
||||
void http_server_init(const char *bindaddr);
|
||||
|
||||
int http_access_verify(http_connection_t *hc, int mask);
|
||||
|
||||
|
|
463
src/imagecache.c
Normal file
463
src/imagecache.c
Normal file
|
@ -0,0 +1,463 @@
|
|||
/*
|
||||
* Icon file server operations
|
||||
* Copyright (C) 2012 Andy Brown
|
||||
*
|
||||
* 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 <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "settings.h"
|
||||
#include "tvheadend.h"
|
||||
#include "filebundle.h"
|
||||
#include "imagecache.h"
|
||||
#include "queue.h"
|
||||
#include "redblack.h"
|
||||
|
||||
#if ENABLE_IMAGECACHE
|
||||
#define CURL_STATICLIB
|
||||
#include <curl/curl.h>
|
||||
#include <curl/easy.h>
|
||||
#endif
|
||||
|
||||
// TODO: icon cache flushing?
|
||||
// TODO: md5 validation?
|
||||
// TODO: allow cache to be disabled by users
|
||||
|
||||
/*
|
||||
* Image metadata
|
||||
*/
|
||||
typedef struct imagecache_image
|
||||
{
|
||||
int id; ///< Internal ID
|
||||
const char *url; ///< Upstream URL
|
||||
int failed; ///< Last update failed
|
||||
time_t updated; ///< Last time the file was checked
|
||||
enum {
|
||||
IDLE,
|
||||
QUEUED,
|
||||
FETCHING
|
||||
} state; ///< fetch status
|
||||
|
||||
TAILQ_ENTRY(imagecache_image) q_link; ///< Fetch Q link
|
||||
RB_ENTRY(imagecache_image) id_link; ///< Index by ID
|
||||
RB_ENTRY(imagecache_image) url_link; ///< Index by URL
|
||||
} imagecache_image_t;
|
||||
|
||||
static int _imagecache_id;
|
||||
static RB_HEAD(,imagecache_image) _imagecache_by_id;
|
||||
static RB_HEAD(,imagecache_image) _imagecache_by_url;
|
||||
|
||||
pthread_mutex_t imagecache_mutex;
|
||||
|
||||
static void _imagecache_save ( imagecache_image_t *img );
|
||||
|
||||
#if ENABLE_IMAGECACHE
|
||||
uint32_t imagecache_enabled;
|
||||
uint32_t imagecache_ok_period;
|
||||
uint32_t imagecache_fail_period;
|
||||
uint32_t imagecache_ignore_sslcert;
|
||||
|
||||
static pthread_cond_t _imagecache_cond;
|
||||
static TAILQ_HEAD(, imagecache_image) _imagecache_queue;
|
||||
static void _imagecache_add ( imagecache_image_t *img );
|
||||
static void* _imagecache_thread ( void *p );
|
||||
static int _imagecache_fetch ( imagecache_image_t *img );
|
||||
#endif
|
||||
|
||||
static int _url_cmp ( void *a, void *b )
|
||||
{
|
||||
return strcmp(((imagecache_image_t*)a)->url, ((imagecache_image_t*)b)->url);
|
||||
}
|
||||
|
||||
static int _id_cmp ( void *a, void *b )
|
||||
{
|
||||
return ((imagecache_image_t*)a)->id - ((imagecache_image_t*)b)->id;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialise
|
||||
*/
|
||||
void imagecache_init ( void )
|
||||
{
|
||||
htsmsg_t *m, *e;
|
||||
htsmsg_field_t *f;
|
||||
imagecache_image_t *img, *i;
|
||||
const char *url;
|
||||
uint32_t id;
|
||||
|
||||
/* Init vars */
|
||||
_imagecache_id = 0;
|
||||
#if ENABLE_IMAGECACHE
|
||||
imagecache_enabled = 0;
|
||||
imagecache_ok_period = 24 * 7; // weekly
|
||||
imagecache_fail_period = 24; // daily
|
||||
imagecache_ignore_sslcert = 0;
|
||||
#endif
|
||||
|
||||
/* Create threads */
|
||||
pthread_mutex_init(&imagecache_mutex, NULL);
|
||||
#if ENABLE_IMAGECACHE
|
||||
pthread_cond_init(&_imagecache_cond, NULL);
|
||||
TAILQ_INIT(&_imagecache_queue);
|
||||
#endif
|
||||
|
||||
/* Load settings */
|
||||
#if ENABLE_IMAGECACHE
|
||||
if ((m = hts_settings_load("imagecache/config"))) {
|
||||
htsmsg_get_u32(m, "enabled", &imagecache_enabled);
|
||||
htsmsg_get_u32(m, "ok_period", &imagecache_ok_period);
|
||||
htsmsg_get_u32(m, "fail_period", &imagecache_fail_period);
|
||||
htsmsg_get_u32(m, "ignore_sslcert", &imagecache_ignore_sslcert);
|
||||
htsmsg_destroy(m);
|
||||
}
|
||||
#endif
|
||||
if ((m = hts_settings_load("imagecache/meta"))) {
|
||||
HTSMSG_FOREACH(f, m) {
|
||||
if (!(e = htsmsg_get_map_by_field(f))) continue;
|
||||
if (!(id = atoi(f->hmf_name))) continue;
|
||||
if (!(url = htsmsg_get_str(e, "url"))) continue;
|
||||
img = calloc(1, sizeof(imagecache_image_t));
|
||||
img->id = id;
|
||||
img->url = strdup(url);
|
||||
img->updated = htsmsg_get_s64_or_default(e, "updated", 0);
|
||||
i = RB_INSERT_SORTED(&_imagecache_by_url, img, url_link, _url_cmp);
|
||||
if (i) {
|
||||
hts_settings_remove("imagecache/meta/%d", id);
|
||||
hts_settings_remove("imagecache/data/%d", id);
|
||||
free(img);
|
||||
continue;
|
||||
}
|
||||
i = RB_INSERT_SORTED(&_imagecache_by_id, img, id_link, _id_cmp);
|
||||
assert(!i);
|
||||
if (id > _imagecache_id)
|
||||
_imagecache_id = id;
|
||||
#if ENABLE_IMAGECACHE
|
||||
if (!img->updated)
|
||||
_imagecache_add(img);
|
||||
#endif
|
||||
}
|
||||
htsmsg_destroy(m);
|
||||
}
|
||||
|
||||
/* Start threads */
|
||||
#if ENABLE_IMAGECACHE
|
||||
{
|
||||
pthread_t tid;
|
||||
pthread_create(&tid, NULL, _imagecache_thread, NULL);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* Save settings
|
||||
*/
|
||||
#if ENABLE_IMAGECACHE
|
||||
void imagecache_save ( void )
|
||||
{
|
||||
htsmsg_t *m = htsmsg_create_map();
|
||||
htsmsg_add_u32(m, "enabled", imagecache_enabled);
|
||||
htsmsg_add_u32(m, "ok_period", imagecache_ok_period);
|
||||
htsmsg_add_u32(m, "fail_period", imagecache_fail_period);
|
||||
htsmsg_add_u32(m, "ignore_sslcert", imagecache_ignore_sslcert);
|
||||
hts_settings_save(m, "imagecache/config");
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable/disable
|
||||
*/
|
||||
int imagecache_set_enabled ( uint32_t e )
|
||||
{
|
||||
if (e == imagecache_enabled)
|
||||
return 0;
|
||||
imagecache_enabled = e;
|
||||
if (e)
|
||||
pthread_cond_broadcast(&_imagecache_cond);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set ok period
|
||||
*/
|
||||
int imagecache_set_ok_period ( uint32_t p )
|
||||
{
|
||||
if (p == imagecache_ok_period)
|
||||
return 0;
|
||||
imagecache_ok_period = p;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set fail period
|
||||
*/
|
||||
int imagecache_set_fail_period ( uint32_t p )
|
||||
{
|
||||
if (p == imagecache_fail_period)
|
||||
return 0;
|
||||
imagecache_fail_period = p;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Set ignore SSL cert
|
||||
*/
|
||||
int imagecache_set_ignore_sslcert ( uint32_t p )
|
||||
{
|
||||
if (p == imagecache_ignore_sslcert)
|
||||
return 0;
|
||||
imagecache_ignore_sslcert = p;
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Fetch a URLs ID
|
||||
*/
|
||||
uint32_t imagecache_get_id ( const char *url )
|
||||
{
|
||||
uint32_t id = 0;
|
||||
imagecache_image_t *i;
|
||||
static imagecache_image_t *skel = NULL;
|
||||
|
||||
/* Invalid */
|
||||
if (!url)
|
||||
return 0;
|
||||
|
||||
/* Disabled */
|
||||
#if !ENABLE_IMAGECACHE
|
||||
if (strncasecmp(url, "file://", 7))
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
/* Skeleton */
|
||||
if (!skel)
|
||||
skel = calloc(1, sizeof(imagecache_image_t));
|
||||
skel->url = url;
|
||||
|
||||
/* Create/Find */
|
||||
pthread_mutex_lock(&imagecache_mutex);
|
||||
i = RB_INSERT_SORTED(&_imagecache_by_url, skel, url_link, _url_cmp);
|
||||
if (!i) {
|
||||
i = skel;
|
||||
i->url = strdup(url);
|
||||
i->id = ++_imagecache_id;
|
||||
skel = RB_INSERT_SORTED(&_imagecache_by_id, i, id_link, _id_cmp);
|
||||
assert(!skel);
|
||||
#if ENABLE_IMAGECACHE
|
||||
_imagecache_add(i);
|
||||
#endif
|
||||
_imagecache_save(i);
|
||||
}
|
||||
#if ENABLE_IMAGECACHE
|
||||
if (!strncasecmp(url, "file://", 7) || imagecache_enabled)
|
||||
id = i->id;
|
||||
#else
|
||||
if (!strncasecmp(url, "file://", 7))
|
||||
id = i->id;
|
||||
#endif
|
||||
pthread_mutex_unlock(&imagecache_mutex);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get data
|
||||
*/
|
||||
int imagecache_open ( uint32_t id )
|
||||
{
|
||||
imagecache_image_t skel, *i;
|
||||
int fd = -1;
|
||||
|
||||
pthread_mutex_lock(&imagecache_mutex);
|
||||
|
||||
/* Find */
|
||||
skel.id = id;
|
||||
i = RB_FIND(&_imagecache_by_id, &skel, id_link, _id_cmp);
|
||||
|
||||
/* Invalid */
|
||||
if (!i) {
|
||||
pthread_mutex_unlock(&imagecache_mutex);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Local file */
|
||||
if (!strncasecmp(i->url, "file://", 7))
|
||||
fd = open(i->url + 7, O_RDONLY);
|
||||
|
||||
/* Remote file */
|
||||
#if ENABLE_IMAGECACHE
|
||||
else if (imagecache_enabled) {
|
||||
struct timespec ts;
|
||||
int err;
|
||||
if (i->updated) {
|
||||
// use existing
|
||||
} else if (i->state == FETCHING) {
|
||||
ts.tv_nsec = 0;
|
||||
time(&ts.tv_sec);
|
||||
ts.tv_sec += 10; // TODO: sensible timeout?
|
||||
err = pthread_cond_timedwait(&_imagecache_cond, &imagecache_mutex, &ts);
|
||||
if (err == ETIMEDOUT) {
|
||||
pthread_mutex_unlock(&imagecache_mutex);
|
||||
return -1;
|
||||
}
|
||||
} else if (i->state == QUEUED) {
|
||||
i->state = FETCHING;
|
||||
TAILQ_REMOVE(&_imagecache_queue, i, q_link);
|
||||
pthread_mutex_unlock(&imagecache_mutex);
|
||||
if (_imagecache_fetch(i))
|
||||
return -1;
|
||||
pthread_mutex_lock(&imagecache_mutex);
|
||||
}
|
||||
fd = hts_settings_open_file(0, "imagecache/data/%d", i->id);
|
||||
}
|
||||
#endif
|
||||
pthread_mutex_unlock(&imagecache_mutex);
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
static void _imagecache_save ( imagecache_image_t *img )
|
||||
{
|
||||
htsmsg_t *m = htsmsg_create_map();
|
||||
|
||||
htsmsg_add_str(m, "url", img->url);
|
||||
if (img->updated)
|
||||
htsmsg_add_s64(m, "updated", img->updated);
|
||||
|
||||
hts_settings_save(m, "imagecache/meta/%d", img->id);
|
||||
}
|
||||
|
||||
#if ENABLE_IMAGECACHE
|
||||
static void _imagecache_add ( imagecache_image_t *img )
|
||||
{
|
||||
if (strncasecmp("file://", img->url, 7)) {
|
||||
img->state = QUEUED;
|
||||
TAILQ_INSERT_TAIL(&_imagecache_queue, img, q_link);
|
||||
pthread_cond_broadcast(&_imagecache_cond);
|
||||
} else {
|
||||
time(&img->updated);
|
||||
}
|
||||
}
|
||||
|
||||
static void *_imagecache_thread ( void *p )
|
||||
{
|
||||
int err;
|
||||
imagecache_image_t *img;
|
||||
struct timespec ts;
|
||||
ts.tv_nsec = 0;
|
||||
|
||||
while (1) {
|
||||
|
||||
/* Get entry */
|
||||
pthread_mutex_lock(&imagecache_mutex);
|
||||
if (!imagecache_enabled) {
|
||||
pthread_cond_wait(&_imagecache_cond, &imagecache_mutex);
|
||||
pthread_mutex_unlock(&imagecache_mutex);
|
||||
continue;
|
||||
}
|
||||
img = TAILQ_FIRST(&_imagecache_queue);
|
||||
if (!img) {
|
||||
time(&ts.tv_sec);
|
||||
ts.tv_sec += 60;
|
||||
err = pthread_cond_timedwait(&_imagecache_cond, &imagecache_mutex, &ts);
|
||||
if (err == ETIMEDOUT) {
|
||||
uint32_t period;
|
||||
RB_FOREACH(img, &_imagecache_by_url, url_link) {
|
||||
if (img->state != IDLE) continue;
|
||||
period = img->failed ? imagecache_fail_period : imagecache_ok_period;
|
||||
period *= 3600;
|
||||
if (period && ((ts.tv_sec - img->updated) > period))
|
||||
_imagecache_add(img);
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&imagecache_mutex);
|
||||
continue;
|
||||
}
|
||||
img->state = FETCHING;
|
||||
TAILQ_REMOVE(&_imagecache_queue, img, q_link);
|
||||
pthread_mutex_unlock(&imagecache_mutex);
|
||||
|
||||
/* Fetch */
|
||||
_imagecache_fetch(img);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int _imagecache_fetch ( imagecache_image_t *img )
|
||||
{
|
||||
int res;
|
||||
CURL *curl;
|
||||
FILE *fp;
|
||||
char tmp[256], path[256];
|
||||
|
||||
/* Open file */
|
||||
if (hts_settings_buildpath(path, sizeof(path), "imagecache/data/%d",
|
||||
img->id))
|
||||
return 1;
|
||||
if (hts_settings_makedirs(path))
|
||||
return 1;
|
||||
snprintf(tmp, sizeof(tmp), "%s.tmp", path);
|
||||
if (!(fp = fopen(tmp, "wb")))
|
||||
return 1;
|
||||
|
||||
/* Build command */
|
||||
pthread_mutex_lock(&imagecache_mutex);
|
||||
tvhlog(LOG_DEBUG, "imagecache", "fetch %s", img->url);
|
||||
curl = curl_easy_init();
|
||||
curl_easy_setopt(curl, CURLOPT_URL, img->url);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, fp);
|
||||
curl_easy_setopt(curl, CURLOPT_USERAGENT, "TVHeadend");
|
||||
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 120);
|
||||
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
|
||||
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
|
||||
if (imagecache_ignore_sslcert)
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
|
||||
pthread_mutex_unlock(&imagecache_mutex);
|
||||
|
||||
/* Fetch */
|
||||
res = curl_easy_perform(curl);
|
||||
curl_easy_cleanup(curl);
|
||||
fclose(fp);
|
||||
|
||||
/* Process */
|
||||
pthread_mutex_lock(&imagecache_mutex);
|
||||
img->state = IDLE;
|
||||
time(&img->updated); // even if failed (possibly request sooner?)
|
||||
if (res) {
|
||||
img->failed = 1;
|
||||
unlink(tmp);
|
||||
tvhlog(LOG_WARNING, "imagecache", "failed to download %s", img->url);
|
||||
} else {
|
||||
img->failed = 0;
|
||||
unlink(path);
|
||||
rename(tmp, path);
|
||||
tvhlog(LOG_DEBUG, "imagecache", "downloaded %s", img->url);
|
||||
}
|
||||
_imagecache_save(img);
|
||||
pthread_cond_broadcast(&_imagecache_cond);
|
||||
pthread_mutex_unlock(&imagecache_mutex);
|
||||
|
||||
return res;
|
||||
};
|
||||
#endif
|
61
src/imagecache.h
Normal file
61
src/imagecache.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Icon file serve operations
|
||||
* Copyright (C) 2012 Andy Brown
|
||||
*
|
||||
* 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 __IMAGE_CACHE_H__
|
||||
#define __IMAGE_CACHE_H__
|
||||
|
||||
#include <pthread.h>
|
||||
|
||||
extern uint32_t imagecache_enabled;
|
||||
extern uint32_t imagecache_ok_period;
|
||||
extern uint32_t imagecache_fail_period;
|
||||
extern uint32_t imagecache_ignore_sslcert;
|
||||
|
||||
extern pthread_mutex_t imagecache_mutex;
|
||||
|
||||
void imagecache_init ( void );
|
||||
|
||||
void imagecache_save ( void );
|
||||
|
||||
int imagecache_set_enabled ( uint32_t e )
|
||||
__attribute__((warn_unused_result));
|
||||
int imagecache_set_ok_period ( uint32_t e )
|
||||
__attribute__((warn_unused_result));
|
||||
int imagecache_set_fail_period ( uint32_t e )
|
||||
__attribute__((warn_unused_result));
|
||||
int imagecache_set_ignore_sslcert ( uint32_t e )
|
||||
__attribute__((warn_unused_result));
|
||||
|
||||
// Note: will return 0 if invalid (must serve original URL)
|
||||
uint32_t imagecache_get_id ( const char *url );
|
||||
|
||||
int imagecache_open ( uint32_t id );
|
||||
|
||||
#define htsmsg_add_imageurl(_msg, _fld, _fmt, _url)\
|
||||
{\
|
||||
char _tmp[64];\
|
||||
uint32_t _id = imagecache_get_id(_url);\
|
||||
if (_id) {\
|
||||
snprintf(_tmp, sizeof(_tmp), _fmt, _id);\
|
||||
htsmsg_add_str(_msg, _fld, _tmp);\
|
||||
} else {\
|
||||
htsmsg_add_str(_msg, _fld, _url);\
|
||||
}\
|
||||
}
|
||||
|
||||
#endif /* __IMAGE_CACHE_H__ */
|
|
@ -461,6 +461,14 @@ iptv_service_quality(service_t *t)
|
|||
return 100;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static int
|
||||
iptv_service_is_enabled(service_t *t)
|
||||
{
|
||||
return t->s_enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a descriptive name for the source
|
||||
|
@ -471,6 +479,7 @@ iptv_service_setsourceinfo(service_t *t, struct source_info *si)
|
|||
char straddr[INET6_ADDRSTRLEN];
|
||||
memset(si, 0, sizeof(struct source_info));
|
||||
|
||||
si->si_type = S_MPEG_TS;
|
||||
si->si_adapter = t->s_iptv_iface ? strdup(t->s_iptv_iface) : NULL;
|
||||
if(t->s_iptv_group.s_addr != 0) {
|
||||
si->si_mux = strdup(inet_ntoa(t->s_iptv_group));
|
||||
|
@ -542,6 +551,7 @@ iptv_service_find(const char *id, int create)
|
|||
t->s_config_save = iptv_service_save;
|
||||
t->s_setsourceinfo = iptv_service_setsourceinfo;
|
||||
t->s_quality_index = iptv_service_quality;
|
||||
t->s_is_enabled = iptv_service_is_enabled;
|
||||
t->s_grace_period = iptv_grace_period;
|
||||
t->s_dtor = iptv_service_dtor;
|
||||
t->s_iptv_fd = -1;
|
||||
|
|
|
@ -1,211 +0,0 @@
|
|||
/*
|
||||
* Output functions for fixed multicast streaming
|
||||
* Copyright (C) 2007 Andreas Öman
|
||||
*
|
||||
* 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 <assert.h>
|
||||
#include <pthread.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <syslog.h>
|
||||
|
||||
#include "tvhead.h"
|
||||
#include "iptv_output.h"
|
||||
#include "dispatch.h"
|
||||
#include "channels.h"
|
||||
#include "subscriptions.h"
|
||||
#include "tsmux.h"
|
||||
#include "rtp.h"
|
||||
|
||||
#define MULTICAST_PKT_SIZ (188 * 7)
|
||||
|
||||
typedef struct output_multicast {
|
||||
ts_muxer_t *om_muxer;
|
||||
int om_fd;
|
||||
struct sockaddr_in om_dst;
|
||||
|
||||
int om_corruption;
|
||||
|
||||
int om_inter_drop_rate;
|
||||
int om_inter_drop_cnt;
|
||||
|
||||
int om_seq;
|
||||
enum {
|
||||
OM_RAWUDP,
|
||||
OM_RTP,
|
||||
} om_encapsulation;
|
||||
|
||||
} output_multicast_t;
|
||||
|
||||
/**
|
||||
* Output MPEG TS
|
||||
*/
|
||||
static void
|
||||
iptv_output_ts(void *opaque, th_subscription_t *s, uint8_t *pkt,
|
||||
int blocks, int64_t pcr)
|
||||
{
|
||||
output_multicast_t *om = opaque;
|
||||
|
||||
om->om_seq++;
|
||||
|
||||
if(om->om_inter_drop_rate &&
|
||||
++om->om_inter_drop_cnt == om->om_inter_drop_rate) {
|
||||
om->om_inter_drop_cnt = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
switch(om->om_encapsulation) {
|
||||
case OM_RTP:
|
||||
rtp_sendmsg(pkt, blocks, pcr, om->om_fd,
|
||||
(struct sockaddr *)&om->om_dst, sizeof(struct sockaddr_in),
|
||||
om->om_seq);
|
||||
break;
|
||||
|
||||
case OM_RAWUDP:
|
||||
sendto(om->om_fd, pkt, blocks * 188, 0,
|
||||
(struct sockaddr *)&om->om_dst, sizeof(struct sockaddr_in));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called when a subscription gets/loses access to a transport
|
||||
*/
|
||||
static void
|
||||
iptv_subscription_callback(struct th_subscription *s,
|
||||
subscription_event_t event, void *opaque)
|
||||
{
|
||||
output_multicast_t *om = opaque;
|
||||
|
||||
switch(event) {
|
||||
case TRANSPORT_AVAILABLE:
|
||||
assert(om->om_muxer == NULL);
|
||||
om->om_muxer = ts_muxer_init(s, iptv_output_ts, om, TS_SEEK,
|
||||
om->om_corruption);
|
||||
ts_muxer_play(om->om_muxer, 0);
|
||||
break;
|
||||
|
||||
case TRANSPORT_UNAVAILABLE:
|
||||
ts_muxer_deinit(om->om_muxer, s);
|
||||
om->om_muxer = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Setup IPTV (TS over UDP) output
|
||||
*/
|
||||
static void
|
||||
output_multicast_load(struct config_head *head)
|
||||
{
|
||||
const char *name, *s, *b;
|
||||
channel_t *ch;
|
||||
output_multicast_t *om;
|
||||
int ttl = 32;
|
||||
struct sockaddr_in sin;
|
||||
char title[100];
|
||||
char title2[100];
|
||||
|
||||
if((name = config_get_str_sub(head, "channel", NULL)) == NULL)
|
||||
return;
|
||||
|
||||
ch = channel_find_by_name(name, 1);
|
||||
|
||||
om = calloc(1, sizeof(output_multicast_t));
|
||||
|
||||
om->om_fd = tvh_socket(AF_INET, SOCK_DGRAM, 0);
|
||||
|
||||
if((b = config_get_str_sub(head, "interface-address", NULL)) != NULL) {
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_port = 0;
|
||||
sin.sin_addr.s_addr = inet_addr(b);
|
||||
|
||||
if(bind(om->om_fd, (struct sockaddr *)&sin, sizeof(sin))==-1) {
|
||||
fprintf(stderr, "cannot bind to %s\n", b);
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
if((s = config_get_str_sub(head, "group-address", NULL)) == NULL) {
|
||||
fprintf(stderr, "no group address configured\n");
|
||||
goto err;
|
||||
}
|
||||
om->om_dst.sin_addr.s_addr = inet_addr(s);
|
||||
|
||||
if((s = config_get_str_sub(head, "port", NULL)) == NULL) {
|
||||
fprintf(stderr, "no port configured\n");
|
||||
goto err;
|
||||
}
|
||||
om->om_dst.sin_port = htons(atoi(s));
|
||||
|
||||
if((s = config_get_str_sub(head, "ttl", NULL)) != NULL)
|
||||
ttl = atoi(s);
|
||||
|
||||
|
||||
if((s = config_get_str_sub(head, "corruption-interval", NULL)) != NULL)
|
||||
om->om_corruption = atoi(s);
|
||||
|
||||
if((s = config_get_str_sub(head, "inter-drop-rate", NULL)) != NULL)
|
||||
om->om_inter_drop_rate = atoi(s);
|
||||
|
||||
if((s = config_get_str_sub(head, "encapsulation", NULL)) != NULL) {
|
||||
if(!strcasecmp(s, "rtp"))
|
||||
om->om_encapsulation = OM_RTP;
|
||||
}
|
||||
|
||||
setsockopt(om->om_fd, SOL_IP, IP_MULTICAST_TTL, &ttl, sizeof(int));
|
||||
|
||||
snprintf(title, sizeof(title), "%s:%d", inet_ntoa(om->om_dst.sin_addr),
|
||||
ntohs(om->om_dst.sin_port));
|
||||
|
||||
syslog(LOG_INFO, "Static multicast output: \"%s\" to %s, source %s ",
|
||||
ch->ch_name, title, inet_ntoa(sin.sin_addr));
|
||||
|
||||
snprintf(title2, sizeof(title2), "IPTV-OUT: %s", title);
|
||||
|
||||
subscription_create(ch, 900, title2, iptv_subscription_callback, om, 0);
|
||||
return;
|
||||
|
||||
err:
|
||||
close(om->om_fd);
|
||||
free(om);
|
||||
}
|
||||
|
||||
|
||||
|
||||
void
|
||||
output_multicast_setup(void)
|
||||
{
|
||||
config_entry_t *ce;
|
||||
|
||||
TAILQ_FOREACH(ce, &config_list, ce_link) {
|
||||
if(ce->ce_type == CFG_SUB && !strcasecmp("multicast-output", ce->ce_key)) {
|
||||
output_multicast_load(&ce->ce_sub);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -180,6 +180,7 @@ const lang_code_t lang_codes[] = {
|
|||
{ "gor", NULL, NULL , "Gorontalo" },
|
||||
{ "got", NULL, NULL , "Gothic" },
|
||||
{ "grb", NULL, NULL , "Grebo" },
|
||||
{ "gre", "el", NULL , "Greek" },
|
||||
{ "grn", "gn", NULL , "Guarani" },
|
||||
{ "gsw", NULL, NULL , "Swiss German; Alemannic; Alsatian" },
|
||||
{ "guj", "gu", NULL , "Gujarati" },
|
||||
|
|
153
src/libav.c
Normal file
153
src/libav.c
Normal file
|
@ -0,0 +1,153 @@
|
|||
#include "libav.h"
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
libav_log_callback(void *ptr, int level, const char *fmt, va_list vl)
|
||||
{
|
||||
char message[8192];
|
||||
char *nl;
|
||||
char *l;
|
||||
|
||||
memset(message, 0, sizeof(message));
|
||||
vsnprintf(message, sizeof(message), fmt, vl);
|
||||
|
||||
l = message;
|
||||
|
||||
if(level == AV_LOG_DEBUG)
|
||||
level = LOG_DEBUG;
|
||||
else if(level == AV_LOG_VERBOSE)
|
||||
level = LOG_INFO;
|
||||
else if(level == AV_LOG_INFO)
|
||||
level = LOG_NOTICE;
|
||||
else if(level == AV_LOG_WARNING)
|
||||
level = LOG_WARNING;
|
||||
else if(level == AV_LOG_ERROR)
|
||||
level = LOG_ERR;
|
||||
else if(level == AV_LOG_FATAL)
|
||||
level = LOG_CRIT;
|
||||
else if(level == AV_LOG_PANIC)
|
||||
level = LOG_EMERG;
|
||||
|
||||
while(l < message + sizeof(message)) {
|
||||
nl = strstr(l, "\n");
|
||||
if(nl)
|
||||
*nl = '\0';
|
||||
|
||||
if(!strlen(l))
|
||||
break;
|
||||
|
||||
tvhlog(level, "libav", "%s", l);
|
||||
|
||||
l += strlen(message);
|
||||
|
||||
if(!nl)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate a component type to a libavcodec id
|
||||
*/
|
||||
enum CodecID
|
||||
streaming_component_type2codec_id(streaming_component_type_t type)
|
||||
{
|
||||
enum CodecID codec_id = CODEC_ID_NONE;
|
||||
|
||||
switch(type) {
|
||||
case SCT_H264:
|
||||
codec_id = CODEC_ID_H264;
|
||||
break;
|
||||
case SCT_MPEG2VIDEO:
|
||||
codec_id = CODEC_ID_MPEG2VIDEO;
|
||||
break;
|
||||
case SCT_AC3:
|
||||
codec_id = CODEC_ID_AC3;
|
||||
break;
|
||||
case SCT_EAC3:
|
||||
codec_id = CODEC_ID_EAC3;
|
||||
break;
|
||||
case SCT_AAC:
|
||||
codec_id = CODEC_ID_AAC;
|
||||
break;
|
||||
case SCT_MPEG2AUDIO:
|
||||
codec_id = CODEC_ID_MP2;
|
||||
break;
|
||||
case SCT_DVBSUB:
|
||||
codec_id = CODEC_ID_DVB_SUBTITLE;
|
||||
break;
|
||||
case SCT_TEXTSUB:
|
||||
codec_id = CODEC_ID_TEXT;
|
||||
break;
|
||||
case SCT_TELETEXT:
|
||||
codec_id = CODEC_ID_DVB_TELETEXT;
|
||||
break;
|
||||
default:
|
||||
codec_id = CODEC_ID_NONE;
|
||||
break;
|
||||
}
|
||||
|
||||
return codec_id;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Translate a libavcodec id to a component type
|
||||
*/
|
||||
streaming_component_type_t
|
||||
codec_id2streaming_component_type(enum CodecID id)
|
||||
{
|
||||
streaming_component_type_t type = CODEC_ID_NONE;
|
||||
|
||||
switch(id) {
|
||||
case CODEC_ID_H264:
|
||||
type = SCT_H264;
|
||||
break;
|
||||
case CODEC_ID_MPEG2VIDEO:
|
||||
type = SCT_MPEG2VIDEO;
|
||||
break;
|
||||
case CODEC_ID_AC3:
|
||||
type = SCT_AC3;
|
||||
break;
|
||||
case CODEC_ID_EAC3:
|
||||
type = SCT_EAC3;
|
||||
break;
|
||||
case CODEC_ID_AAC:
|
||||
type = SCT_AAC;
|
||||
break;
|
||||
case CODEC_ID_MP2:
|
||||
type = SCT_MPEG2AUDIO;
|
||||
break;
|
||||
case CODEC_ID_DVB_SUBTITLE:
|
||||
type = SCT_DVBSUB;
|
||||
break;
|
||||
case CODEC_ID_TEXT:
|
||||
type = SCT_TEXTSUB;
|
||||
break;
|
||||
case CODEC_ID_DVB_TELETEXT:
|
||||
type = SCT_TELETEXT;
|
||||
break;
|
||||
case CODEC_ID_NONE:
|
||||
type = SCT_NONE;
|
||||
break;
|
||||
default:
|
||||
type = SCT_UNKNOWN;
|
||||
break;
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
libav_init(void)
|
||||
{
|
||||
av_log_set_callback(libav_log_callback);
|
||||
av_log_set_level(AV_LOG_VERBOSE);
|
||||
av_register_all();
|
||||
}
|
||||
|
31
src/libav.h
Normal file
31
src/libav.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* tvheadend, libav utils
|
||||
* Copyright (C) 2012 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 <htmlui://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef LIBAV_H_
|
||||
#define LIBAV_H_
|
||||
|
||||
|
||||
#include <libavformat/avformat.h>
|
||||
#include "tvheadend.h"
|
||||
|
||||
enum CodecID streaming_component_type2codec_id(streaming_component_type_t type);
|
||||
streaming_component_type_t codec_id2streaming_component_type(enum CodecID id);
|
||||
void libav_init(void);
|
||||
|
||||
#endif
|
||||
|
802
src/main.c
802
src/main.c
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* TVheadend
|
||||
* Copyright (C) 2007 - 2010 Andreas Öman
|
||||
* Copyright (C) 2007 - 2010 Andreas <EFBFBD>man
|
||||
*
|
||||
* 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
|
||||
|
@ -49,7 +49,7 @@
|
|||
#include "cwc.h"
|
||||
#include "capmt.h"
|
||||
#include "dvr/dvr.h"
|
||||
#include "htsp.h"
|
||||
#include "htsp_server.h"
|
||||
#include "rawtsinput.h"
|
||||
#include "avahi.h"
|
||||
#include "iptv_input.h"
|
||||
|
@ -60,22 +60,100 @@
|
|||
#include "ffdecsa/FFdecsa.h"
|
||||
#include "muxes.h"
|
||||
#include "config2.h"
|
||||
#include "imagecache.h"
|
||||
#include "timeshift.h"
|
||||
#if ENABLE_LIBAV
|
||||
#include "libav.h"
|
||||
#include "plumbing/transcoding.h"
|
||||
#endif
|
||||
|
||||
/* Command line option struct */
|
||||
typedef struct {
|
||||
const char sopt;
|
||||
const char *lopt;
|
||||
const char *desc;
|
||||
enum {
|
||||
OPT_STR,
|
||||
OPT_INT,
|
||||
OPT_BOOL
|
||||
} type;
|
||||
void *param;
|
||||
} cmdline_opt_t;
|
||||
|
||||
static cmdline_opt_t* cmdline_opt_find
|
||||
( cmdline_opt_t *opts, int num, const char *arg )
|
||||
{
|
||||
int i;
|
||||
int isshort = 0;
|
||||
|
||||
if (strlen(arg) < 2 || *arg != '-')
|
||||
return NULL;
|
||||
arg++;
|
||||
|
||||
if (strlen(arg) == 1)
|
||||
isshort = 1;
|
||||
else if (*arg == '-')
|
||||
arg++;
|
||||
else
|
||||
return NULL;
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
if (!opts[i].lopt) continue;
|
||||
if (isshort && opts[i].sopt == *arg)
|
||||
return &opts[i];
|
||||
if (!isshort && !strcmp(opts[i].lopt, arg))
|
||||
return &opts[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Globals
|
||||
*/
|
||||
int tvheadend_webui_port;
|
||||
int tvheadend_webui_debug;
|
||||
int tvheadend_htsp_port;
|
||||
int tvheadend_htsp_port_extra;
|
||||
const char *tvheadend_cwd;
|
||||
const char *tvheadend_webroot;
|
||||
const tvh_caps_t tvheadend_capabilities[] = {
|
||||
#if ENABLE_CWC
|
||||
{ "cwc", NULL },
|
||||
#endif
|
||||
#if ENABLE_V4L
|
||||
{ "v4l", NULL },
|
||||
#endif
|
||||
#if ENABLE_LINUXDVB
|
||||
{ "linuxdvb", NULL },
|
||||
#endif
|
||||
#if ENABLE_LIBAV
|
||||
{ "transcoding", &transcoding_enabled },
|
||||
#endif
|
||||
#if ENABLE_IMAGECACHE
|
||||
{ "imagecache", &imagecache_enabled },
|
||||
#endif
|
||||
#if ENABLE_TIMESHIFT
|
||||
{ "timeshift", ×hift_enabled },
|
||||
#endif
|
||||
#if ENABLE_TRACE
|
||||
{ "trace", NULL },
|
||||
#endif
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
int running;
|
||||
time_t dispatch_clock;
|
||||
static LIST_HEAD(, gtimer) gtimers;
|
||||
pthread_mutex_t global_lock;
|
||||
pthread_mutex_t ffmpeg_lock;
|
||||
pthread_mutex_t fork_lock;
|
||||
static int log_stderr;
|
||||
static int log_decorate;
|
||||
pthread_mutex_t atomic_lock;
|
||||
|
||||
int log_debug_to_syslog;
|
||||
int log_debug_to_console;
|
||||
|
||||
int webui_port;
|
||||
int htsp_port;
|
||||
char *tvheadend_cwd;
|
||||
/*
|
||||
* Locals
|
||||
*/
|
||||
static int running;
|
||||
static LIST_HEAD(, gtimer) gtimers;
|
||||
static pthread_cond_t gtimer_cond;
|
||||
|
||||
static void
|
||||
handle_sigpipe(int x)
|
||||
|
@ -111,31 +189,50 @@ get_user_groups (const struct passwd *pw, gid_t* glist, size_t gmax)
|
|||
static int
|
||||
gtimercmp(gtimer_t *a, gtimer_t *b)
|
||||
{
|
||||
if(a->gti_expire < b->gti_expire)
|
||||
if(a->gti_expire.tv_sec < b->gti_expire.tv_sec)
|
||||
return -1;
|
||||
else if(a->gti_expire > b->gti_expire)
|
||||
if(a->gti_expire.tv_sec > b->gti_expire.tv_sec)
|
||||
return 1;
|
||||
return 0;
|
||||
if(a->gti_expire.tv_nsec < b->gti_expire.tv_nsec)
|
||||
return -1;
|
||||
if(a->gti_expire.tv_nsec > b->gti_expire.tv_nsec)
|
||||
return 1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
gtimer_arm_abs(gtimer_t *gti, gti_callback_t *callback, void *opaque,
|
||||
time_t when)
|
||||
gtimer_arm_abs2
|
||||
(gtimer_t *gti, gti_callback_t *callback, void *opaque, struct timespec *when)
|
||||
{
|
||||
lock_assert(&global_lock);
|
||||
|
||||
if(gti->gti_callback != NULL)
|
||||
if (gti->gti_callback != NULL)
|
||||
LIST_REMOVE(gti, gti_link);
|
||||
|
||||
|
||||
gti->gti_callback = callback;
|
||||
gti->gti_opaque = opaque;
|
||||
gti->gti_expire = when;
|
||||
gti->gti_opaque = opaque;
|
||||
gti->gti_expire = *when;
|
||||
|
||||
LIST_INSERT_SORTED(>imers, gti, gti_link, gtimercmp);
|
||||
|
||||
if (LIST_FIRST(>imers) == gti)
|
||||
pthread_cond_signal(>imer_cond); // force timer re-check
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
gtimer_arm_abs
|
||||
(gtimer_t *gti, gti_callback_t *callback, void *opaque, time_t when)
|
||||
{
|
||||
struct timespec ts;
|
||||
ts.tv_nsec = 0;
|
||||
ts.tv_sec = when;
|
||||
gtimer_arm_abs2(gti, callback, opaque, &ts);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -144,10 +241,22 @@ gtimer_arm_abs(gtimer_t *gti, gti_callback_t *callback, void *opaque,
|
|||
void
|
||||
gtimer_arm(gtimer_t *gti, gti_callback_t *callback, void *opaque, int delta)
|
||||
{
|
||||
time_t now;
|
||||
time(&now);
|
||||
|
||||
gtimer_arm_abs(gti, callback, opaque, now + delta);
|
||||
gtimer_arm_abs(gti, callback, opaque, dispatch_clock + delta);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
gtimer_arm_ms
|
||||
(gtimer_t *gti, gti_callback_t *callback, void *opaque, long delta_ms )
|
||||
{
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
ts.tv_nsec += (1000000 * delta_ms);
|
||||
ts.tv_sec += (ts.tv_nsec / 1000000000);
|
||||
ts.tv_nsec %= 1000000000;
|
||||
gtimer_arm_abs2(gti, callback, opaque, &ts);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -162,47 +271,61 @@ gtimer_disarm(gtimer_t *gti)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show version info
|
||||
*/
|
||||
static void
|
||||
show_version(const char *argv0)
|
||||
{
|
||||
printf("%s: version %s\n", argv0, tvheadend_version);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void
|
||||
usage(const char *argv0)
|
||||
show_usage
|
||||
(const char *argv0, cmdline_opt_t *opts, int num, const char *err, ...)
|
||||
{
|
||||
printf("HTS Tvheadend %s\n", tvheadend_version);
|
||||
printf("usage: %s [options]\n", argv0);
|
||||
int i;
|
||||
char buf[256];
|
||||
printf("Usage: %s [OPTIONS]\n", argv0);
|
||||
for (i = 0; i < num; i++) {
|
||||
|
||||
/* Section */
|
||||
if (!opts[i].lopt) {
|
||||
printf("\n%s\n\n",
|
||||
opts[i].desc);
|
||||
|
||||
/* Option */
|
||||
} else {
|
||||
char sopt[4];
|
||||
char *desc, *tok;
|
||||
if (opts[i].sopt)
|
||||
snprintf(sopt, sizeof(sopt), "-%c,", opts[i].sopt);
|
||||
else
|
||||
strcpy(sopt, " ");
|
||||
snprintf(buf, sizeof(buf), " %s --%s", sopt, opts[i].lopt);
|
||||
desc = strdup(opts[i].desc);
|
||||
tok = strtok(desc, "\n");
|
||||
while (tok) {
|
||||
printf("%-30s%s\n", buf, tok);
|
||||
tok = buf;
|
||||
while (*tok) {
|
||||
*tok = ' ';
|
||||
tok++;
|
||||
}
|
||||
tok = strtok(NULL, "\n");
|
||||
}
|
||||
free(desc);
|
||||
}
|
||||
}
|
||||
printf("\n");
|
||||
printf(" -a <adapters> Use only DVB adapters specified (csv)\n");
|
||||
printf(" -c <directory> Alternate configuration path.\n"
|
||||
" Defaults to [$HOME/.hts/tvheadend]\n");
|
||||
printf(" -m <directory> Alternate mux configuration directory\n");
|
||||
printf(" -f Fork and daemonize\n");
|
||||
printf(" -p <pidfile> Write pid to <pidfile> instead of /var/run/tvheadend.pid,\n"
|
||||
" only works with -f\n");
|
||||
printf(" -u <username> Run as user <username>, only works with -f\n");
|
||||
printf(" -g <groupname> Run as group <groupname>, only works with -f\n");
|
||||
printf(" -C If no useraccount exist then create one with\n"
|
||||
" no username and no password. Use with care as\n"
|
||||
" it will allow world-wide administrative access\n"
|
||||
" to your Tvheadend installation until you edit\n"
|
||||
" the access-control from within the Tvheadend UI\n");
|
||||
printf(" -s Log debug to syslog\n");
|
||||
printf(" -w <portnumber> WebUI access port [default 9981]\n");
|
||||
printf(" -e <portnumber> HTSP access port [default 9982]\n");
|
||||
printf("\n");
|
||||
printf("Development options\n");
|
||||
printf("\n");
|
||||
printf(" -d Log debug to console\n");
|
||||
printf(" -j <id> Statically join the given transport id\n");
|
||||
printf(" -r <tsfile> Read the given transport stream file and present\n"
|
||||
" found services as channels\n");
|
||||
printf(" -A Immediately call abort()\n");
|
||||
|
||||
printf("\n");
|
||||
printf("For more information read the man page or visit\n");
|
||||
printf(" http://www.lonelycoder.com/hts/\n");
|
||||
printf("For more information please visit the Tvheadend website:\n");
|
||||
printf(" https://tvheadend.org\n");
|
||||
printf("\n");
|
||||
exit(0);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -215,28 +338,50 @@ mainloop(void)
|
|||
{
|
||||
gtimer_t *gti;
|
||||
gti_callback_t *cb;
|
||||
struct timespec ts;
|
||||
|
||||
while(running) {
|
||||
sleep(1);
|
||||
spawn_reaper();
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
|
||||
time(&dispatch_clock);
|
||||
/* 1sec stuff */
|
||||
if (ts.tv_sec > dispatch_clock) {
|
||||
dispatch_clock = ts.tv_sec;
|
||||
|
||||
comet_flush(); /* Flush idle comet mailboxes */
|
||||
spawn_reaper(); /* reap spawned processes */
|
||||
|
||||
comet_flush(); /* Flush idle comet mailboxes */
|
||||
}
|
||||
|
||||
/* Global timers */
|
||||
pthread_mutex_lock(&global_lock);
|
||||
|
||||
// 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
|
||||
|
||||
while((gti = LIST_FIRST(>imers)) != NULL) {
|
||||
if(gti->gti_expire > dispatch_clock)
|
||||
break;
|
||||
|
||||
if ((gti->gti_expire.tv_sec > ts.tv_sec) ||
|
||||
((gti->gti_expire.tv_sec == ts.tv_sec) &&
|
||||
(gti->gti_expire.tv_nsec > ts.tv_nsec))) {
|
||||
ts = gti->gti_expire;
|
||||
break;
|
||||
}
|
||||
|
||||
cb = gti->gti_callback;
|
||||
|
||||
LIST_REMOVE(gti, gti_link);
|
||||
gti->gti_callback = NULL;
|
||||
|
||||
cb(gti->gti_opaque);
|
||||
|
||||
}
|
||||
|
||||
/* Bound wait */
|
||||
if ((LIST_FIRST(>imers) == NULL) || (ts.tv_sec > (dispatch_clock + 1))) {
|
||||
ts.tv_sec = dispatch_clock + 1;
|
||||
ts.tv_nsec = 0;
|
||||
}
|
||||
|
||||
/* Wait */
|
||||
pthread_cond_timedwait(>imer_cond, &global_lock, &ts);
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
}
|
||||
}
|
||||
|
@ -248,203 +393,346 @@ mainloop(void)
|
|||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
int c;
|
||||
int forkaway = 0;
|
||||
FILE *pidfile;
|
||||
const char *pidpath = "/var/run/tvheadend.pid";
|
||||
struct group *grp;
|
||||
struct passwd *pw;
|
||||
const char *usernam = NULL;
|
||||
const char *groupnam = NULL;
|
||||
int logfacility = LOG_DAEMON;
|
||||
int createdefault = 0;
|
||||
int i;
|
||||
sigset_t set;
|
||||
const char *homedir;
|
||||
const char *rawts_input = NULL;
|
||||
const char *join_transport = NULL;
|
||||
const char *confpath = NULL;
|
||||
char *p, *endp;
|
||||
uint32_t adapter_mask = 0xffffffff;
|
||||
int crash = 0;
|
||||
webui_port = 9981;
|
||||
htsp_port = 9982;
|
||||
#if ENABLE_LINUXDVB
|
||||
uint32_t adapter_mask;
|
||||
#endif
|
||||
int log_level = LOG_INFO;
|
||||
int log_options = TVHLOG_OPT_MILLIS | TVHLOG_OPT_STDERR | TVHLOG_OPT_SYSLOG;
|
||||
const char *log_subsys = NULL;
|
||||
|
||||
/* Defaults */
|
||||
tvheadend_webui_port = 9981;
|
||||
tvheadend_webroot = NULL;
|
||||
tvheadend_htsp_port = 9982;
|
||||
tvheadend_htsp_port_extra = 0;
|
||||
|
||||
/* Command line options */
|
||||
int opt_help = 0,
|
||||
opt_version = 0,
|
||||
opt_fork = 0,
|
||||
opt_firstrun = 0,
|
||||
opt_stderr = 0,
|
||||
opt_syslog = 0,
|
||||
opt_uidebug = 0,
|
||||
opt_abort = 0,
|
||||
opt_noacl = 0,
|
||||
opt_trace = 0,
|
||||
opt_fileline = 0,
|
||||
opt_ipv6 = 0;
|
||||
const char *opt_config = NULL,
|
||||
*opt_user = NULL,
|
||||
*opt_group = NULL,
|
||||
*opt_logpath = NULL,
|
||||
*opt_log_subsys = NULL,
|
||||
*opt_pidpath = "/var/run/tvheadend.pid",
|
||||
#if ENABLE_LINUXDVB
|
||||
*opt_dvb_adapters = NULL,
|
||||
*opt_dvb_raw = NULL,
|
||||
#endif
|
||||
*opt_rawts = NULL,
|
||||
*opt_bindaddr = NULL,
|
||||
*opt_subscribe = NULL;
|
||||
cmdline_opt_t cmdline_opts[] = {
|
||||
{ 0, NULL, "Generic Options", OPT_BOOL, NULL },
|
||||
{ 'h', "help", "Show this page", OPT_BOOL, &opt_help },
|
||||
{ 'v', "version", "Show version infomation", OPT_BOOL, &opt_version },
|
||||
|
||||
{ 0, NULL, "Service Configuration", OPT_BOOL, NULL },
|
||||
{ 'c', "config", "Alternate config path", OPT_STR, &opt_config },
|
||||
{ 'f', "fork", "Fork and run as daemon", OPT_BOOL, &opt_fork },
|
||||
{ 'u', "user", "Run as user", OPT_STR, &opt_user },
|
||||
{ 'g', "group", "Run as group", OPT_STR, &opt_group },
|
||||
{ 'p', "pid", "Alternate pid path", OPT_STR, &opt_pidpath },
|
||||
{ 'C', "firstrun", "If no user account exists then create one with\n"
|
||||
"no username and no password. Use with care as\n"
|
||||
"it will allow world-wide administrative access\n"
|
||||
"to your Tvheadend installation until you edit\n"
|
||||
"the access-control from within the Tvheadend UI",
|
||||
OPT_BOOL, &opt_firstrun },
|
||||
#if ENABLE_LINUXDVB
|
||||
{ 'a', "adapters", "Only use specified DVB adapters (comma separated)",
|
||||
OPT_STR, &opt_dvb_adapters },
|
||||
#endif
|
||||
{ 0, NULL, "Server Connectivity", OPT_BOOL, NULL },
|
||||
{ '6', "ipv6", "Listen on IPv6", OPT_BOOL, &opt_ipv6 },
|
||||
{ 'b', "bindaddr", "Specify bind address", OPT_STR, &opt_bindaddr},
|
||||
{ 0, "http_port", "Specify alternative http port",
|
||||
OPT_INT, &tvheadend_webui_port },
|
||||
{ 0, "http_root", "Specify alternative http webroot",
|
||||
OPT_STR, &tvheadend_webroot },
|
||||
{ 0, "htsp_port", "Specify alternative htsp port",
|
||||
OPT_INT, &tvheadend_htsp_port },
|
||||
{ 0, "htsp_port2", "Specify extra htsp port",
|
||||
OPT_INT, &tvheadend_htsp_port_extra },
|
||||
|
||||
{ 0, NULL, "Debug Options", OPT_BOOL, NULL },
|
||||
{ 'd', "stderr", "Enable debug on stderr", OPT_BOOL, &opt_stderr },
|
||||
{ 's', "syslog", "Enable debug to syslog", OPT_BOOL, &opt_syslog },
|
||||
{ 'l', "logfile", "Enable debug to file", OPT_STR, &opt_logpath },
|
||||
{ 0, "subsys", "Enable debug subsystems", OPT_STR, &opt_log_subsys },
|
||||
{ 0, "fileline", "Add file and line numbers to debug", OPT_BOOL, &opt_fileline },
|
||||
#if ENABLE_TRACE
|
||||
{ 0, "trace", "Enable low level debug", OPT_BOOL, &opt_trace },
|
||||
#endif
|
||||
{ 0, "uidebug", "Enable webUI debug (non-minified JS)", OPT_BOOL, &opt_uidebug },
|
||||
{ 'A', "abort", "Immediately abort", OPT_BOOL, &opt_abort },
|
||||
{ 0, "noacl", "Disable all access control checks",
|
||||
OPT_BOOL, &opt_noacl },
|
||||
#if ENABLE_LINUXDVB
|
||||
{ 'R', "dvbraw", "Use rawts file to create virtual adapter",
|
||||
OPT_STR, &opt_dvb_raw },
|
||||
#endif
|
||||
{ 'r', "rawts", "Use rawts file to generate virtual services",
|
||||
OPT_STR, &opt_rawts },
|
||||
{ 'j', "join", "Subscribe to a service permanently",
|
||||
OPT_STR, &opt_subscribe }
|
||||
};
|
||||
|
||||
/* Get current directory */
|
||||
tvheadend_cwd = dirname(dirname(strdup(argv[0])));
|
||||
tvheadend_cwd = dirname(dirname(tvh_strdupa(argv[0])));
|
||||
|
||||
/* Set locale */
|
||||
setlocale(LC_ALL, "");
|
||||
setlocale(LC_NUMERIC, "C");
|
||||
|
||||
// make sure the timezone is set
|
||||
/* make sure the timezone is set */
|
||||
tzset();
|
||||
|
||||
while((c = getopt(argc, argv, "Aa:fp:u:g:c:Chdr:j:sw:e:")) != -1) {
|
||||
switch(c) {
|
||||
case 'a':
|
||||
adapter_mask = 0x0;
|
||||
p = strtok(optarg, ",");
|
||||
if (p != NULL) {
|
||||
do {
|
||||
int adapter = strtol(p, &endp, 10);
|
||||
if (*endp != 0 || adapter < 0 || adapter > 31) {
|
||||
fprintf(stderr, "Invalid adapter number '%s'\n", p);
|
||||
return 1;
|
||||
}
|
||||
adapter_mask |= (1 << adapter);
|
||||
} while ((p = strtok(NULL, ",")) != NULL);
|
||||
if (adapter_mask == 0x0) {
|
||||
fprintf(stderr, "No adapters specified!\n");
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
usage(argv[0]);
|
||||
}
|
||||
break;
|
||||
case 'A':
|
||||
crash = 1;
|
||||
break;
|
||||
case 'f':
|
||||
forkaway = 1;
|
||||
break;
|
||||
case 'p':
|
||||
pidpath = optarg;
|
||||
break;
|
||||
case 'w':
|
||||
webui_port = atoi(optarg);
|
||||
break;
|
||||
case 'e':
|
||||
htsp_port = atoi(optarg);
|
||||
break;
|
||||
case 'u':
|
||||
usernam = optarg;
|
||||
break;
|
||||
case 'g':
|
||||
groupnam = optarg;
|
||||
break;
|
||||
case 'c':
|
||||
confpath = optarg;
|
||||
break;
|
||||
case 'd':
|
||||
log_debug_to_console = 1;
|
||||
break;
|
||||
case 's':
|
||||
log_debug_to_syslog = 1;
|
||||
break;
|
||||
case 'C':
|
||||
createdefault = 1;
|
||||
break;
|
||||
case 'r':
|
||||
rawts_input = optarg;
|
||||
break;
|
||||
case 'j':
|
||||
join_transport = optarg;
|
||||
break;
|
||||
default:
|
||||
usage(argv[0]);
|
||||
}
|
||||
/* Process command line */
|
||||
for (i = 1; i < argc; i++) {
|
||||
|
||||
/* Find option */
|
||||
cmdline_opt_t *opt
|
||||
= cmdline_opt_find(cmdline_opts, ARRAY_SIZE(cmdline_opts), argv[i]);
|
||||
if (!opt)
|
||||
show_usage(argv[0], cmdline_opts, ARRAY_SIZE(cmdline_opts),
|
||||
"invalid option specified [%s]", argv[i]);
|
||||
|
||||
/* Process */
|
||||
if (opt->type == OPT_BOOL)
|
||||
*((int*)opt->param) = 1;
|
||||
else if (++i == argc)
|
||||
show_usage(argv[0], cmdline_opts, ARRAY_SIZE(cmdline_opts),
|
||||
"option %s requires a value", opt->lopt);
|
||||
else if (opt->type == OPT_INT)
|
||||
*((int*)opt->param) = atoi(argv[i]);
|
||||
else
|
||||
*((char**)opt->param) = argv[i];
|
||||
|
||||
/* Stop processing */
|
||||
if (opt_help)
|
||||
show_usage(argv[0], cmdline_opts, ARRAY_SIZE(cmdline_opts), NULL);
|
||||
if (opt_version)
|
||||
show_version(argv[0]);
|
||||
}
|
||||
|
||||
signal(SIGPIPE, handle_sigpipe);
|
||||
/* Additional cmdline processing */
|
||||
#if ENABLE_LINUXDVB
|
||||
if (!opt_dvb_adapters) {
|
||||
adapter_mask = ~0;
|
||||
} else {
|
||||
char *p, *e;
|
||||
char *r = NULL;
|
||||
char *dvb_adapters = strdup(opt_dvb_adapters);
|
||||
adapter_mask = 0x0;
|
||||
p = strtok_r(dvb_adapters, ",", &r);
|
||||
while (p) {
|
||||
int a = strtol(p, &e, 10);
|
||||
if (*e != 0 || a < 0 || a > 31) {
|
||||
tvhlog(LOG_ERR, "START", "Invalid adapter number '%s'", p);
|
||||
free(dvb_adapters);
|
||||
return 1;
|
||||
}
|
||||
adapter_mask |= (1 << a);
|
||||
p = strtok_r(NULL, ",", &r);
|
||||
}
|
||||
free(dvb_adapters);
|
||||
if (!adapter_mask) {
|
||||
tvhlog(LOG_ERR, "START", "No adapters specified!");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (tvheadend_webroot) {
|
||||
char *tmp;
|
||||
if (*tvheadend_webroot == '/')
|
||||
tmp = strdup(tvheadend_webroot);
|
||||
else {
|
||||
tmp = malloc(strlen(tvheadend_webroot)+1);
|
||||
*tmp = '/';
|
||||
strcpy(tmp+1, tvheadend_webroot);
|
||||
}
|
||||
if (tmp[strlen(tmp)-1] == '/')
|
||||
tmp[strlen(tmp)-1] = '\0';
|
||||
tvheadend_webroot = tmp;
|
||||
}
|
||||
|
||||
if(forkaway) {
|
||||
grp = getgrnam(groupnam ?: "video");
|
||||
pw = usernam ? getpwnam(usernam) : NULL;
|
||||
/* Setup logging */
|
||||
if (isatty(2))
|
||||
log_options |= TVHLOG_OPT_DECORATE;
|
||||
if (opt_stderr || opt_syslog || opt_logpath) {
|
||||
log_subsys = "all";
|
||||
log_level = LOG_DEBUG;
|
||||
if (opt_stderr)
|
||||
log_options |= TVHLOG_OPT_DBG_STDERR;
|
||||
if (opt_syslog)
|
||||
log_options |= TVHLOG_OPT_DBG_SYSLOG;
|
||||
if (opt_logpath)
|
||||
log_options |= TVHLOG_OPT_DBG_FILE;
|
||||
}
|
||||
if (opt_fileline)
|
||||
log_options |= TVHLOG_OPT_FILELINE;
|
||||
if (opt_trace)
|
||||
log_level = LOG_TRACE;
|
||||
if (opt_log_subsys)
|
||||
log_subsys = opt_log_subsys;
|
||||
|
||||
tvhlog_init(log_level, log_options, opt_logpath);
|
||||
tvhlog_set_subsys(log_subsys);
|
||||
|
||||
signal(SIGPIPE, handle_sigpipe); // will be redundant later
|
||||
|
||||
/* Daemonise */
|
||||
if(opt_fork) {
|
||||
const char *homedir;
|
||||
gid_t gid;
|
||||
uid_t uid;
|
||||
struct group *grp = getgrnam(opt_group ?: "video");
|
||||
struct passwd *pw = opt_user ? getpwnam(opt_user) : NULL;
|
||||
FILE *pidfile = fopen(opt_pidpath, "w+");
|
||||
|
||||
if(grp != NULL) {
|
||||
gid = grp->gr_gid;
|
||||
} else {
|
||||
gid = 1;
|
||||
}
|
||||
|
||||
if (pw != NULL) {
|
||||
if (getuid() != pw->pw_uid) {
|
||||
gid_t glist[10];
|
||||
int gnum;
|
||||
gnum = get_user_groups(pw, glist, 10);
|
||||
if (setgroups(gnum, glist)) {
|
||||
tvhlog(LOG_ALERT, "START",
|
||||
"setgroups() failed, do you have permission?");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
uid = pw->pw_uid;
|
||||
homedir = pw->pw_dir;
|
||||
setenv("HOME", homedir, 1);
|
||||
} else {
|
||||
uid = 1;
|
||||
}
|
||||
if ((getgid() != gid) && setgid(gid)) {
|
||||
tvhlog(LOG_ALERT, "START",
|
||||
"setgid() failed, do you have permission?");
|
||||
return 1;
|
||||
}
|
||||
if ((getuid() != uid) && setuid(uid)) {
|
||||
tvhlog(LOG_ALERT, "START",
|
||||
"setuid() failed, do you have permission?");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if(daemon(0, 0)) {
|
||||
exit(2);
|
||||
}
|
||||
pidfile = fopen(pidpath, "w+");
|
||||
if(pidfile != NULL) {
|
||||
fprintf(pidfile, "%d\n", getpid());
|
||||
fclose(pidfile);
|
||||
}
|
||||
|
||||
if(grp != NULL) {
|
||||
setgid(grp->gr_gid);
|
||||
} else {
|
||||
setgid(1);
|
||||
}
|
||||
|
||||
if (pw != NULL) {
|
||||
gid_t glist[10];
|
||||
int gnum = get_user_groups(pw, glist, 10);
|
||||
setgroups(gnum, glist);
|
||||
setuid(pw->pw_uid);
|
||||
homedir = pw->pw_dir;
|
||||
setenv("HOME", homedir, 1);
|
||||
} else {
|
||||
setuid(1);
|
||||
}
|
||||
|
||||
umask(0);
|
||||
}
|
||||
|
||||
log_stderr = !forkaway;
|
||||
log_decorate = isatty(2);
|
||||
|
||||
sigfillset(&set);
|
||||
sigprocmask(SIG_BLOCK, &set, NULL);
|
||||
|
||||
openlog("tvheadend", LOG_PID, logfacility);
|
||||
|
||||
hts_settings_init(confpath);
|
||||
/* Alter logging */
|
||||
if (opt_fork)
|
||||
tvhlog_options &= ~TVHLOG_OPT_STDERR;
|
||||
if (!isatty(2))
|
||||
tvhlog_options &= ~TVHLOG_OPT_DECORATE;
|
||||
|
||||
/* Initialise configuration */
|
||||
hts_settings_init(opt_config);
|
||||
|
||||
/* Setup global mutexes */
|
||||
pthread_mutex_init(&ffmpeg_lock, NULL);
|
||||
pthread_mutex_init(&fork_lock, NULL);
|
||||
pthread_mutex_init(&global_lock, NULL);
|
||||
|
||||
pthread_mutex_init(&atomic_lock, NULL);
|
||||
pthread_mutex_lock(&global_lock);
|
||||
pthread_cond_init(>imer_cond, NULL);
|
||||
|
||||
time(&dispatch_clock);
|
||||
|
||||
/* Signal handling */
|
||||
sigfillset(&set);
|
||||
sigprocmask(SIG_BLOCK, &set, NULL);
|
||||
trap_init(argv[0]);
|
||||
|
||||
/**
|
||||
* Initialize subsystems
|
||||
*/
|
||||
|
||||
#if ENABLE_LIBAV
|
||||
libav_init();
|
||||
#endif
|
||||
|
||||
config_init();
|
||||
|
||||
muxes_init();
|
||||
imagecache_init();
|
||||
|
||||
service_init();
|
||||
|
||||
channels_init();
|
||||
|
||||
access_init(createdefault);
|
||||
subscription_init();
|
||||
|
||||
access_init(opt_firstrun, opt_noacl);
|
||||
|
||||
tcp_server_init();
|
||||
#if ENABLE_LINUXDVB
|
||||
dvb_init(adapter_mask);
|
||||
muxes_init();
|
||||
dvb_init(adapter_mask, opt_dvb_raw);
|
||||
#endif
|
||||
|
||||
iptv_input_init();
|
||||
|
||||
#if ENABLE_V4L
|
||||
v4l_init();
|
||||
#endif
|
||||
http_server_init();
|
||||
|
||||
#if ENABLE_TIMESHIFT
|
||||
timeshift_init();
|
||||
#endif
|
||||
|
||||
tcp_server_init(opt_ipv6);
|
||||
http_server_init(opt_bindaddr);
|
||||
webui_init();
|
||||
|
||||
serviceprobe_init();
|
||||
|
||||
#if ENABLE_CWC
|
||||
cwc_init();
|
||||
|
||||
capmt_init();
|
||||
#if (!ENABLE_DVBCSA)
|
||||
ffdecsa_init();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
epggrab_init();
|
||||
epg_init();
|
||||
|
||||
dvr_init();
|
||||
|
||||
htsp_init();
|
||||
htsp_init(opt_bindaddr);
|
||||
|
||||
ffdecsa_init();
|
||||
|
||||
if(rawts_input != NULL)
|
||||
rawts_init(rawts_input);
|
||||
if(opt_rawts != NULL)
|
||||
rawts_init(opt_rawts);
|
||||
|
||||
if(join_transport != NULL)
|
||||
subscription_dummy_join(join_transport, 1);
|
||||
if(opt_subscribe != NULL)
|
||||
subscription_dummy_join(opt_subscribe, 1);
|
||||
|
||||
#ifdef CONFIG_AVAHI
|
||||
avahi_init();
|
||||
|
@ -454,7 +742,6 @@ main(int argc, char **argv)
|
|||
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
|
||||
|
||||
/**
|
||||
* Wait for SIGTERM / SIGINT, but only in this thread
|
||||
*/
|
||||
|
@ -474,125 +761,30 @@ main(int argc, char **argv)
|
|||
tvheadend_version,
|
||||
getpid(), getuid(), getgid(), hts_settings_get_root());
|
||||
|
||||
if(crash)
|
||||
if(opt_abort)
|
||||
abort();
|
||||
|
||||
mainloop();
|
||||
|
||||
epg_save();
|
||||
// Note: the locking is obviously a bit redundant, but without
|
||||
// we need to disable the gtimer_arm call in epg_save()
|
||||
pthread_mutex_lock(&global_lock);
|
||||
epg_save(NULL);
|
||||
|
||||
#if ENABLE_TIMESHIFT
|
||||
timeshift_term();
|
||||
#endif
|
||||
pthread_mutex_unlock(&global_lock);
|
||||
|
||||
tvhlog(LOG_NOTICE, "STOP", "Exiting HTS Tvheadend");
|
||||
|
||||
if(forkaway)
|
||||
unlink("/var/run/tvheadend.pid");
|
||||
if(opt_fork)
|
||||
unlink(opt_pidpath);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
static const char *logtxtmeta[8][2] = {
|
||||
{"EMERGENCY", "\033[31m"},
|
||||
{"ALERT", "\033[31m"},
|
||||
{"CRITICAL", "\033[31m"},
|
||||
{"ERROR", "\033[31m"},
|
||||
{"WARNING", "\033[33m"},
|
||||
{"NOTICE", "\033[36m"},
|
||||
{"INFO", "\033[32m"},
|
||||
{"DEBUG", "\033[32m"},
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal log function
|
||||
*/
|
||||
static void
|
||||
tvhlogv(int notify, int severity, const char *subsys, const char *fmt,
|
||||
va_list ap)
|
||||
{
|
||||
char buf[2048];
|
||||
char buf2[2048];
|
||||
char t[50];
|
||||
int l;
|
||||
struct tm tm;
|
||||
time_t now;
|
||||
|
||||
l = snprintf(buf, sizeof(buf), "%s: ", subsys);
|
||||
|
||||
vsnprintf(buf + l, sizeof(buf) - l, fmt, ap);
|
||||
|
||||
if(log_debug_to_syslog || severity < LOG_DEBUG)
|
||||
syslog(severity, "%s", buf);
|
||||
|
||||
/**
|
||||
* Get time (string)
|
||||
*/
|
||||
time(&now);
|
||||
localtime_r(&now, &tm);
|
||||
strftime(t, sizeof(t), "%b %d %H:%M:%S", &tm);
|
||||
|
||||
/**
|
||||
* Send notification to Comet (Push interface to web-clients)
|
||||
*/
|
||||
if(notify) {
|
||||
htsmsg_t *m;
|
||||
|
||||
snprintf(buf2, sizeof(buf2), "%s %s", t, buf);
|
||||
m = htsmsg_create_map();
|
||||
htsmsg_add_str(m, "notificationClass", "logmessage");
|
||||
htsmsg_add_str(m, "logtxt", buf2);
|
||||
comet_mailbox_add_message(m, severity == LOG_DEBUG);
|
||||
htsmsg_destroy(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to stderr
|
||||
*/
|
||||
|
||||
if(log_stderr && (log_debug_to_console || severity < LOG_DEBUG)) {
|
||||
const char *leveltxt = logtxtmeta[severity][0];
|
||||
const char *sgr = logtxtmeta[severity][1];
|
||||
const char *sgroff;
|
||||
|
||||
if(!log_decorate) {
|
||||
sgr = "";
|
||||
sgroff = "";
|
||||
} else {
|
||||
sgroff = "\033[0m";
|
||||
}
|
||||
fprintf(stderr, "%s%s [%s]:%s%s\n", sgr, t, leveltxt, buf, sgroff);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void
|
||||
tvhlog(int severity, const char *subsys, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
tvhlogv(1, severity, subsys, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* May be invoked from a forked process so we can't do any notification
|
||||
* to comet directly.
|
||||
*
|
||||
* @todo Perhaps do it via a pipe?
|
||||
*/
|
||||
void
|
||||
tvhlog_spawn(int severity, const char *subsys, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
tvhlogv(0, severity, subsys, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
|
295
src/misc/dbl.c
Normal file
295
src/misc/dbl.c
Normal file
|
@ -0,0 +1,295 @@
|
|||
/*
|
||||
* Floating point conversion functions.
|
||||
* Not accurate but should be enough for Showtime's needs
|
||||
*
|
||||
* Copyright (C) 2011 Andreas Öman
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#define _ISOC99_SOURCE
|
||||
|
||||
#include <math.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "dbl.h"
|
||||
|
||||
|
||||
double
|
||||
my_str2double(const char *str, const char **endp)
|
||||
{
|
||||
double ret = 1.0f;
|
||||
int n = 0, m = 0, o = 0, e = 0;
|
||||
|
||||
if(*str == '-') {
|
||||
ret = -1.0f;
|
||||
str++;
|
||||
}
|
||||
|
||||
while(*str >= '0' && *str <= '9')
|
||||
n = n * 10 + *str++ - '0';
|
||||
|
||||
if(*str != '.') {
|
||||
ret *= n;
|
||||
} else {
|
||||
|
||||
str++;
|
||||
|
||||
while(*str >= '0' && *str <= '9') {
|
||||
o = o * 10 + *str++ - '0';
|
||||
m--;
|
||||
}
|
||||
|
||||
ret *= (n + pow(10, m) * o);
|
||||
}
|
||||
|
||||
if(*str == 'e' || *str == 'E') {
|
||||
int esign = 1;
|
||||
str++;
|
||||
|
||||
if(*str == '+')
|
||||
str++;
|
||||
else if(*str == '-') {
|
||||
str++;
|
||||
esign = -1;
|
||||
}
|
||||
|
||||
while(*str >= '0' && *str <= '9')
|
||||
e = e * 10 + *str++ - '0';
|
||||
ret *= pow(10, e * esign);
|
||||
}
|
||||
|
||||
if(endp != NULL)
|
||||
*endp = str;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** The code that follow is based on "printf" code that dates from the
|
||||
** 1980s. It is in the public domain. The original comments are
|
||||
** included here for completeness. They are very out-of-date but
|
||||
** might be useful as an historical reference.
|
||||
**
|
||||
**************************************************************************
|
||||
**
|
||||
** The following modules is an enhanced replacement for the "printf" subroutines
|
||||
** found in the standard C library. The following enhancements are
|
||||
** supported:
|
||||
**
|
||||
** + Additional functions. The standard set of "printf" functions
|
||||
** includes printf, fprintf, sprintf, vprintf, vfprintf, and
|
||||
** vsprintf. This module adds the following:
|
||||
**
|
||||
** * snprintf -- Works like sprintf, but has an extra argument
|
||||
** which is the size of the buffer written to.
|
||||
**
|
||||
** * mprintf -- Similar to sprintf. Writes output to memory
|
||||
** obtained from malloc.
|
||||
**
|
||||
** * xprintf -- Calls a function to dispose of output.
|
||||
**
|
||||
** * nprintf -- No output, but returns the number of characters
|
||||
** that would have been output by printf.
|
||||
**
|
||||
** * A v- version (ex: vsnprintf) of every function is also
|
||||
** supplied.
|
||||
**
|
||||
** + A few extensions to the formatting notation are supported:
|
||||
**
|
||||
** * The "=" flag (similar to "-") causes the output to be
|
||||
** be centered in the appropriately sized field.
|
||||
**
|
||||
** * The %b field outputs an integer in binary notation.
|
||||
**
|
||||
** * The %c field now accepts a precision. The character output
|
||||
** is repeated by the number of times the precision specifies.
|
||||
**
|
||||
** * The %' field works like %c, but takes as its character the
|
||||
** next character of the format string, instead of the next
|
||||
** argument. For example, printf("%.78'-") prints 78 minus
|
||||
** signs, the same as printf("%.78c",'-').
|
||||
**
|
||||
** + When compiled using GCC on a SPARC, this version of printf is
|
||||
** faster than the library printf for SUN OS 4.1.
|
||||
**
|
||||
** + All functions are fully reentrant.
|
||||
**
|
||||
*/
|
||||
|
||||
|
||||
static char
|
||||
getdigit(double *val, int *cnt)
|
||||
{
|
||||
int digit;
|
||||
double d;
|
||||
if( (*cnt)++ >= 16 ) return '0';
|
||||
digit = (int)*val;
|
||||
d = digit;
|
||||
digit += '0';
|
||||
*val = (*val - d)*10.0;
|
||||
return (char)digit;
|
||||
}
|
||||
|
||||
#define xGENERIC 0
|
||||
#define xFLOAT 1
|
||||
#define xEXP 2
|
||||
|
||||
|
||||
int
|
||||
my_double2str(char *buf, size_t bufsize, double realvalue)
|
||||
{
|
||||
int precision = -1;
|
||||
char *bufpt;
|
||||
char prefix;
|
||||
char xtype = xGENERIC;
|
||||
int idx, exp, e2;
|
||||
double rounder;
|
||||
char flag_exp;
|
||||
char flag_rtz;
|
||||
char flag_dp;
|
||||
char flag_alternateform = 0;
|
||||
char flag_altform2 = 0;
|
||||
int nsd;
|
||||
|
||||
if(bufsize < 8)
|
||||
return -1;
|
||||
|
||||
if( precision<0 ) precision = 20; /* Set default precision */
|
||||
if( precision>bufsize/2-10 ) precision = bufsize/2-10;
|
||||
if( realvalue<0.0 ){
|
||||
realvalue = -realvalue;
|
||||
prefix = '-';
|
||||
}else{
|
||||
prefix = 0;
|
||||
}
|
||||
if( xtype==xGENERIC && precision>0 ) precision--;
|
||||
for(idx=precision, rounder=0.5; idx>0; idx--, rounder*=0.1){}
|
||||
|
||||
if( xtype==xFLOAT ) realvalue += rounder;
|
||||
/* Normalize realvalue to within 10.0 > realvalue >= 1.0 */
|
||||
exp = 0;
|
||||
|
||||
if(isnan(realvalue)) {
|
||||
strcpy(buf, "NaN");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if( realvalue>0.0 ){
|
||||
while( realvalue>=1e32 && exp<=350 ){ realvalue *= 1e-32; exp+=32; }
|
||||
while( realvalue>=1e8 && exp<=350 ){ realvalue *= 1e-8; exp+=8; }
|
||||
while( realvalue>=10.0 && exp<=350 ){ realvalue *= 0.1; exp++; }
|
||||
while( realvalue<1e-8 ){ realvalue *= 1e8; exp-=8; }
|
||||
while( realvalue<1.0 ){ realvalue *= 10.0; exp--; }
|
||||
if( exp>350 ){
|
||||
if( prefix=='-' ){
|
||||
strcpy(buf, "-Inf");
|
||||
}else{
|
||||
strcpy(buf, "Inf");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
bufpt = buf;
|
||||
|
||||
/*
|
||||
** If the field type is etGENERIC, then convert to either etEXP
|
||||
** or etFLOAT, as appropriate.
|
||||
*/
|
||||
flag_exp = xtype==xEXP;
|
||||
if( xtype != xFLOAT ){
|
||||
realvalue += rounder;
|
||||
if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; }
|
||||
}
|
||||
if( xtype==xGENERIC ){
|
||||
flag_rtz = !flag_alternateform;
|
||||
if( exp<-4 || exp>precision ){
|
||||
xtype = xEXP;
|
||||
}else{
|
||||
precision = precision - exp;
|
||||
xtype = xFLOAT;
|
||||
}
|
||||
}else{
|
||||
flag_rtz = 0;
|
||||
}
|
||||
if( xtype==xEXP ){
|
||||
e2 = 0;
|
||||
}else{
|
||||
e2 = exp;
|
||||
}
|
||||
nsd = 0;
|
||||
flag_dp = (precision>0 ?1:0) | flag_alternateform | flag_altform2;
|
||||
/* The sign in front of the number */
|
||||
if( prefix ){
|
||||
*(bufpt++) = prefix;
|
||||
}
|
||||
/* Digits prior to the decimal point */
|
||||
if( e2<0 ){
|
||||
*(bufpt++) = '0';
|
||||
}else{
|
||||
for(; e2>=0; e2--){
|
||||
*(bufpt++) = getdigit(&realvalue,&nsd);
|
||||
}
|
||||
}
|
||||
/* The decimal point */
|
||||
if( flag_dp ){
|
||||
*(bufpt++) = '.';
|
||||
}
|
||||
/* "0" digits after the decimal point but before the first
|
||||
** significant digit of the number */
|
||||
for(e2++; e2<0; precision--, e2++){
|
||||
assert( precision>0 );
|
||||
*(bufpt++) = '0';
|
||||
}
|
||||
/* Significant digits after the decimal point */
|
||||
while( (precision--)>0 ){
|
||||
*(bufpt++) = getdigit(&realvalue,&nsd);
|
||||
}
|
||||
|
||||
/* Remove trailing zeros and the "." if no digits follow the "." */
|
||||
if( flag_rtz && flag_dp ){
|
||||
while( bufpt[-1]=='0' ) *(--bufpt) = 0;
|
||||
assert( bufpt>buf );
|
||||
if( bufpt[-1]=='.' ){
|
||||
if( flag_altform2 ){
|
||||
*(bufpt++) = '0';
|
||||
}else{
|
||||
*(--bufpt) = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Add the "eNNN" suffix */
|
||||
if( flag_exp || xtype==xEXP ){
|
||||
*(bufpt++) = 'e';
|
||||
if( exp<0 ){
|
||||
*(bufpt++) = '-'; exp = -exp;
|
||||
}else{
|
||||
*(bufpt++) = '+';
|
||||
}
|
||||
if( exp>=100 ){
|
||||
*(bufpt++) = (char)((exp/100)+'0'); /* 100's digit */
|
||||
exp %= 100;
|
||||
}
|
||||
*(bufpt++) = (char)(exp/10+'0'); /* 10's digit */
|
||||
*(bufpt++) = (char)(exp%10+'0'); /* 1's digit */
|
||||
}
|
||||
*bufpt = 0;
|
||||
return 0;
|
||||
}
|
||||
|
5
src/misc/dbl.h
Normal file
5
src/misc/dbl.h
Normal file
|
@ -0,0 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
double my_str2double(const char *str, const char **endp);
|
||||
|
||||
int my_double2str(char *buf, size_t bufsize, double realvalue);
|
438
src/misc/json.c
Normal file
438
src/misc/json.c
Normal file
|
@ -0,0 +1,438 @@
|
|||
/*
|
||||
* JSON helpers
|
||||
* Copyright (C) 2011 Andreas Öman
|
||||
*
|
||||
* 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 <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "json.h"
|
||||
#include "dbl.h"
|
||||
#include "tvheadend.h"
|
||||
|
||||
#define NOT_THIS_TYPE ((void *)-1)
|
||||
|
||||
static const char *json_parse_value(const char *s, void *parent,
|
||||
const char *name,
|
||||
const json_deserializer_t *jd,
|
||||
void *opaque,
|
||||
const char **failp, const char **failmsg);
|
||||
|
||||
/**
|
||||
* Returns a newly allocated string
|
||||
*/
|
||||
static char *
|
||||
json_parse_string(const char *s, const char **endp,
|
||||
const char **failp, const char **failmsg)
|
||||
{
|
||||
const char *start;
|
||||
char *r, *a, *b;
|
||||
int l, esc = 0;
|
||||
|
||||
while(*s > 0 && *s < 33)
|
||||
s++;
|
||||
|
||||
if(*s != '"')
|
||||
return NOT_THIS_TYPE;
|
||||
|
||||
s++;
|
||||
start = s;
|
||||
|
||||
while(1) {
|
||||
if(*s == 0) {
|
||||
*failmsg = "Unexpected end of JSON message";
|
||||
*failp = s;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(*s == '\\') {
|
||||
esc = 1;
|
||||
/* skip the escape */
|
||||
s++;
|
||||
if (*s == 'u') s += 4;
|
||||
// Note: we could detect the lack of support here!
|
||||
} else if(*s == '"') {
|
||||
|
||||
*endp = s + 1;
|
||||
|
||||
/* End */
|
||||
l = s - start;
|
||||
r = malloc(l + 1);
|
||||
memcpy(r, start, l);
|
||||
r[l] = 0;
|
||||
|
||||
if(esc) {
|
||||
/* Do deescaping inplace */
|
||||
|
||||
a = b = r;
|
||||
|
||||
while(*a) {
|
||||
if(*a == '\\') {
|
||||
a++;
|
||||
if(*a == 'b')
|
||||
*b++ = '\b';
|
||||
else if(*a == '\\')
|
||||
*b++ = '\\';
|
||||
else if(*a == 'f')
|
||||
*b++ = '\f';
|
||||
else if(*a == 'n')
|
||||
*b++ = '\n';
|
||||
else if(*a == 'r')
|
||||
*b++ = '\r';
|
||||
else if(*a == 't')
|
||||
*b++ = '\t';
|
||||
else if(*a == 'u') {
|
||||
// Unicode character
|
||||
int i, v = 0;
|
||||
|
||||
a++;
|
||||
for(i = 0; i < 4; i++) {
|
||||
v = v << 4;
|
||||
switch(a[i]) {
|
||||
case '0' ... '9':
|
||||
v |= a[i] - '0';
|
||||
break;
|
||||
case 'a' ... 'f':
|
||||
v |= a[i] - 'a' + 10;
|
||||
break;
|
||||
case 'A' ... 'F':
|
||||
v |= a[i] - 'F' + 10;
|
||||
break;
|
||||
default:
|
||||
free(r);
|
||||
*failmsg = "Incorrect escape sequence";
|
||||
*failp = (a - r) + start;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
a+=3;
|
||||
b += put_utf8(b, v);
|
||||
} else {
|
||||
*b++ = *a;
|
||||
}
|
||||
a++;
|
||||
} else {
|
||||
*b++ = *a++;
|
||||
}
|
||||
}
|
||||
*b = 0;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void *
|
||||
json_parse_map(const char *s, const char **endp, const json_deserializer_t *jd,
|
||||
void *opaque, const char **failp, const char **failmsg)
|
||||
|
||||
{
|
||||
char *name;
|
||||
const char *s2;
|
||||
void *r;
|
||||
|
||||
while(*s > 0 && *s < 33)
|
||||
s++;
|
||||
|
||||
if(*s != '{')
|
||||
return NOT_THIS_TYPE;
|
||||
|
||||
s++;
|
||||
|
||||
r = jd->jd_create_map(opaque);
|
||||
|
||||
while(*s > 0 && *s < 33)
|
||||
s++;
|
||||
|
||||
if(*s != '}') {
|
||||
|
||||
while(1) {
|
||||
name = json_parse_string(s, &s2, failp, failmsg);
|
||||
if(name == NOT_THIS_TYPE) {
|
||||
*failmsg = "Expected string";
|
||||
*failp = s;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(name == NULL)
|
||||
return NULL;
|
||||
|
||||
s = s2;
|
||||
|
||||
while(*s > 0 && *s < 33)
|
||||
s++;
|
||||
|
||||
if(*s != ':') {
|
||||
jd->jd_destroy_obj(opaque, r);
|
||||
free(name);
|
||||
*failmsg = "Expected ':'";
|
||||
*failp = s;
|
||||
return NULL;
|
||||
}
|
||||
s++;
|
||||
|
||||
s2 = json_parse_value(s, r, name, jd, opaque, failp, failmsg);
|
||||
free(name);
|
||||
|
||||
if(s2 == NULL) {
|
||||
jd->jd_destroy_obj(opaque, r);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s = s2;
|
||||
|
||||
while(*s > 0 && *s < 33)
|
||||
s++;
|
||||
|
||||
if(*s == '}')
|
||||
break;
|
||||
|
||||
if(*s != ',') {
|
||||
jd->jd_destroy_obj(opaque, r);
|
||||
*failmsg = "Expected ','";
|
||||
*failp = s;
|
||||
return NULL;
|
||||
}
|
||||
s++;
|
||||
}
|
||||
}
|
||||
|
||||
s++;
|
||||
*endp = s;
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static void *
|
||||
json_parse_list(const char *s, const char **endp, const json_deserializer_t *jd,
|
||||
void *opaque, const char **failp, const char **failmsg)
|
||||
{
|
||||
const char *s2;
|
||||
void *r;
|
||||
|
||||
while(*s > 0 && *s < 33)
|
||||
s++;
|
||||
|
||||
if(*s != '[')
|
||||
return NOT_THIS_TYPE;
|
||||
|
||||
s++;
|
||||
|
||||
r = jd->jd_create_list(opaque);
|
||||
|
||||
while(*s > 0 && *s < 33)
|
||||
s++;
|
||||
|
||||
if(*s != ']') {
|
||||
|
||||
while(1) {
|
||||
|
||||
s2 = json_parse_value(s, r, NULL, jd, opaque, failp, failmsg);
|
||||
|
||||
if(s2 == NULL) {
|
||||
jd->jd_destroy_obj(opaque, r);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
s = s2;
|
||||
|
||||
while(*s > 0 && *s < 33)
|
||||
s++;
|
||||
|
||||
if(*s == ']')
|
||||
break;
|
||||
|
||||
if(*s != ',') {
|
||||
jd->jd_destroy_obj(opaque, r);
|
||||
*failmsg = "Expected ','";
|
||||
*failp = s;
|
||||
return NULL;
|
||||
}
|
||||
s++;
|
||||
}
|
||||
}
|
||||
s++;
|
||||
*endp = s;
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static const char *
|
||||
json_parse_double(const char *s, double *dp)
|
||||
{
|
||||
const char *ep;
|
||||
while(*s > 0 && *s < 33)
|
||||
s++;
|
||||
|
||||
double d = my_str2double(s, &ep);
|
||||
|
||||
if(ep == s)
|
||||
return NULL;
|
||||
|
||||
*dp = d;
|
||||
return ep;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static char *
|
||||
json_parse_integer(const char *s, long *lp)
|
||||
{
|
||||
char *ep;
|
||||
while(*s > 0 && *s < 33)
|
||||
s++;
|
||||
const char *s2 = s;
|
||||
if(*s2 == '-')
|
||||
s2++;
|
||||
while(*s2 >= '0' && *s2 <= '9')
|
||||
s2++;
|
||||
|
||||
if(*s2 == 0)
|
||||
return NULL;
|
||||
if(s2[0] == '.' || s2[0] == 'e' || s2[0] == 'E')
|
||||
return NULL; // Is floating point
|
||||
|
||||
long v = strtol(s, &ep, 10);
|
||||
if(v == LONG_MIN || v == LONG_MAX)
|
||||
return NULL;
|
||||
|
||||
if(ep == s)
|
||||
return NULL;
|
||||
|
||||
*lp = v;
|
||||
return ep;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
static const char *
|
||||
json_parse_value(const char *s, void *parent, const char *name,
|
||||
const json_deserializer_t *jd, void *opaque,
|
||||
const char **failp, const char **failmsg)
|
||||
{
|
||||
const char *s2;
|
||||
char *str;
|
||||
double d = 0;
|
||||
long l = 0;
|
||||
void *c;
|
||||
|
||||
if((c = json_parse_map(s, &s2, jd, opaque, failp, failmsg)) == NULL)
|
||||
return NULL;
|
||||
|
||||
if(c != NOT_THIS_TYPE) {
|
||||
jd->jd_add_obj(opaque, parent, name, c);
|
||||
return s2;
|
||||
}
|
||||
|
||||
if((c = json_parse_list(s, &s2, jd, opaque, failp, failmsg)) == NULL)
|
||||
return NULL;
|
||||
|
||||
if(c != NOT_THIS_TYPE) {
|
||||
jd->jd_add_obj(opaque, parent, name, c);
|
||||
return s2;
|
||||
}
|
||||
|
||||
if((str = json_parse_string(s, &s2, failp, failmsg)) == NULL)
|
||||
return NULL;
|
||||
|
||||
if(str != NOT_THIS_TYPE) {
|
||||
jd->jd_add_string(opaque, parent, name, str);
|
||||
return s2;
|
||||
}
|
||||
|
||||
if((s2 = json_parse_integer(s, &l)) != NULL) {
|
||||
jd->jd_add_long(opaque, parent, name, l);
|
||||
return s2;
|
||||
} else if((s2 = json_parse_double(s, &d)) != NULL) {
|
||||
jd->jd_add_double(opaque, parent, name, d);
|
||||
return s2;
|
||||
}
|
||||
|
||||
while(*s > 0 && *s < 33)
|
||||
s++;
|
||||
|
||||
if(!strncmp(s, "true", 4)) {
|
||||
jd->jd_add_bool(opaque, parent, name, 1);
|
||||
return s + 4;
|
||||
}
|
||||
|
||||
if(!strncmp(s, "false", 5)) {
|
||||
jd->jd_add_bool(opaque, parent, name, 0);
|
||||
return s + 5;
|
||||
}
|
||||
|
||||
if(!strncmp(s, "null", 4)) {
|
||||
jd->jd_add_null(opaque, parent, name);
|
||||
return s + 4;
|
||||
}
|
||||
|
||||
*failmsg = "Unknown token";
|
||||
*failp = s;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
void *
|
||||
json_deserialize(const char *src, const json_deserializer_t *jd, void *opaque,
|
||||
char *errbuf, size_t errlen)
|
||||
{
|
||||
const char *end;
|
||||
void *c;
|
||||
const char *errmsg;
|
||||
const char *errp;
|
||||
|
||||
c = json_parse_map(src, &end, jd, opaque, &errp, &errmsg);
|
||||
if(c == NOT_THIS_TYPE)
|
||||
c = json_parse_list(src, &end, jd, opaque, &errp, &errmsg);
|
||||
|
||||
if(c == NOT_THIS_TYPE) {
|
||||
snprintf(errbuf, errlen, "Invalid JSON, expected '{' or '['");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(c == NULL) {
|
||||
size_t len = strlen(src);
|
||||
ssize_t offset = errp - src;
|
||||
if(offset > len || offset < 0) {
|
||||
snprintf(errbuf, errlen, "%s at (bad) offset %d", errmsg, (int)offset);
|
||||
} else {
|
||||
offset -= 10;
|
||||
if(offset < 0)
|
||||
offset = 0;
|
||||
snprintf(errbuf, errlen, "%s at offset %d : '%.20s'", errmsg, (int)offset,
|
||||
src + offset);
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
31
src/misc/json.h
Normal file
31
src/misc/json.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
#pragma once
|
||||
|
||||
typedef struct json_deserializer {
|
||||
void *(*jd_create_map)(void *jd_opaque);
|
||||
void *(*jd_create_list)(void *jd_opaque);
|
||||
|
||||
void (*jd_destroy_obj)(void *jd_opaque, void *obj);
|
||||
|
||||
void (*jd_add_obj)(void *jd_opaque, void *parent,
|
||||
const char *name, void *child);
|
||||
|
||||
// str must be free'd by callee
|
||||
void (*jd_add_string)(void *jd_opaque, void *parent,
|
||||
const char *name, char *str);
|
||||
|
||||
void (*jd_add_long)(void *jd_opaque, void *parent,
|
||||
const char *name, long v);
|
||||
|
||||
void (*jd_add_double)(void *jd_opaque, void *parent,
|
||||
const char *name, double d);
|
||||
|
||||
void (*jd_add_bool)(void *jd_opaque, void *parent,
|
||||
const char *name, int v);
|
||||
|
||||
void (*jd_add_null)(void *jd_opaque, void *parent,
|
||||
const char *name);
|
||||
|
||||
} json_deserializer_t;
|
||||
|
||||
void *json_deserialize(const char *src, const json_deserializer_t *jd,
|
||||
void *opaque, char *errbuf, size_t errlen);
|
120
src/muxer.c
120
src/muxer.c
|
@ -21,9 +21,11 @@
|
|||
#include "tvheadend.h"
|
||||
#include "service.h"
|
||||
#include "muxer.h"
|
||||
#include "muxer_tvh.h"
|
||||
#include "muxer_pass.h"
|
||||
|
||||
#include "muxer/muxer_tvh.h"
|
||||
#include "muxer/muxer_pass.h"
|
||||
#if CONFIG_LIBAV
|
||||
#include "muxer/muxer_libav.h"
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Mime type for containers containing only audio
|
||||
|
@ -34,6 +36,7 @@ static struct strtab container_audio_mime[] = {
|
|||
{ "audio/x-mpegts", MC_MPEGTS },
|
||||
{ "audio/mpeg", MC_MPEGPS },
|
||||
{ "application/octet-stream", MC_PASS },
|
||||
{ "application/octet-stream", MC_RAW },
|
||||
};
|
||||
|
||||
|
||||
|
@ -46,6 +49,7 @@ static struct strtab container_video_mime[] = {
|
|||
{ "video/x-mpegts", MC_MPEGTS },
|
||||
{ "video/mpeg", MC_MPEGPS },
|
||||
{ "application/octet-stream", MC_PASS },
|
||||
{ "application/octet-stream", MC_RAW },
|
||||
};
|
||||
|
||||
|
||||
|
@ -58,6 +62,7 @@ static struct strtab container_name[] = {
|
|||
{ "mpegts", MC_MPEGTS },
|
||||
{ "mpegps", MC_MPEGPS },
|
||||
{ "pass", MC_PASS },
|
||||
{ "raw", MC_RAW },
|
||||
};
|
||||
|
||||
|
||||
|
@ -70,6 +75,7 @@ static struct strtab container_audio_file_suffix[] = {
|
|||
{ "ts", MC_MPEGTS },
|
||||
{ "mpeg", MC_MPEGPS },
|
||||
{ "bin", MC_PASS },
|
||||
{ "bin", MC_RAW },
|
||||
};
|
||||
|
||||
|
||||
|
@ -82,6 +88,7 @@ static struct strtab container_video_file_suffix[] = {
|
|||
{ "ts", MC_MPEGTS },
|
||||
{ "mpeg", MC_MPEGPS },
|
||||
{ "bin", MC_PASS },
|
||||
{ "bin", MC_RAW },
|
||||
};
|
||||
|
||||
|
||||
|
@ -89,7 +96,7 @@ static struct strtab container_video_file_suffix[] = {
|
|||
* Get the mime type for a container
|
||||
*/
|
||||
const char*
|
||||
muxer_container_mimetype(muxer_container_type_t mc, int video)
|
||||
muxer_container_type2mime(muxer_container_type_t mc, int video)
|
||||
{
|
||||
const char *str;
|
||||
|
||||
|
@ -141,7 +148,46 @@ muxer_container_type2txt(muxer_container_type_t mc)
|
|||
|
||||
|
||||
/**
|
||||
* Convert a string to a container type
|
||||
* Get a list of supported containers
|
||||
*/
|
||||
int
|
||||
muxer_container_list(htsmsg_t *array)
|
||||
{
|
||||
htsmsg_t *mc;
|
||||
int c = 0;
|
||||
|
||||
mc = htsmsg_create_map();
|
||||
htsmsg_add_str(mc, "name", muxer_container_type2txt(MC_MATROSKA));
|
||||
htsmsg_add_str(mc, "description", "Matroska");
|
||||
htsmsg_add_msg(array, NULL, mc);
|
||||
c++;
|
||||
|
||||
mc = htsmsg_create_map();
|
||||
htsmsg_add_str(mc, "name", muxer_container_type2txt(MC_PASS));
|
||||
htsmsg_add_str(mc, "description", "Same as source (pass through)");
|
||||
htsmsg_add_msg(array, NULL, mc);
|
||||
c++;
|
||||
|
||||
#if ENABLE_LIBAV
|
||||
mc = htsmsg_create_map();
|
||||
htsmsg_add_str(mc, "name", muxer_container_type2txt(MC_MPEGTS));
|
||||
htsmsg_add_str(mc, "description", "MPEG-TS");
|
||||
htsmsg_add_msg(array, NULL, mc);
|
||||
c++;
|
||||
|
||||
mc = htsmsg_create_map();
|
||||
htsmsg_add_str(mc, "name", muxer_container_type2txt(MC_MPEGPS));
|
||||
htsmsg_add_str(mc, "description", "MPEG-PS (DVD)");
|
||||
htsmsg_add_msg(array, NULL, mc);
|
||||
c++;
|
||||
#endif
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert a container name to a container type
|
||||
*/
|
||||
muxer_container_type_t
|
||||
muxer_container_txt2type(const char *str)
|
||||
|
@ -159,19 +205,46 @@ muxer_container_txt2type(const char *str)
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert a mime-string to a container type
|
||||
*/
|
||||
muxer_container_type_t
|
||||
muxer_container_mime2type(const char *str)
|
||||
{
|
||||
muxer_container_type_t mc;
|
||||
|
||||
if(!str)
|
||||
return MC_UNKNOWN;
|
||||
|
||||
mc = str2val(str, container_video_mime);
|
||||
if(mc == -1)
|
||||
mc = str2val(str, container_audio_mime);
|
||||
|
||||
if(mc == -1)
|
||||
return MC_UNKNOWN;
|
||||
|
||||
return mc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new muxer
|
||||
*/
|
||||
muxer_t*
|
||||
muxer_create(service_t *s, muxer_container_type_t mc)
|
||||
muxer_create(muxer_container_type_t mc)
|
||||
{
|
||||
muxer_t *m;
|
||||
|
||||
m = pass_muxer_create(s, mc);
|
||||
m = pass_muxer_create(mc);
|
||||
|
||||
if(!m)
|
||||
m = tvh_muxer_create(mc);
|
||||
|
||||
#if CONFIG_LIBAV
|
||||
if(!m)
|
||||
m = lav_muxer_create(mc);
|
||||
#endif
|
||||
|
||||
if(!m)
|
||||
tvhlog(LOG_ERR, "mux", "Can't find a muxer that supports '%s' container",
|
||||
muxer_container_type2txt(mc));
|
||||
|
@ -200,6 +273,7 @@ const char*
|
|||
muxer_suffix(muxer_t *m, const struct streaming_start *ss)
|
||||
{
|
||||
const char *mime;
|
||||
muxer_container_type_t mc;
|
||||
int video;
|
||||
|
||||
if(!m || !ss)
|
||||
|
@ -207,8 +281,9 @@ muxer_suffix(muxer_t *m, const struct streaming_start *ss)
|
|||
|
||||
mime = m->m_mime(m, ss);
|
||||
video = memcmp("audio", mime, 5);
|
||||
mc = muxer_container_mime2type(mime);
|
||||
|
||||
return muxer_container_suffix(m->m_container, video);
|
||||
return muxer_container_suffix(mc, video);
|
||||
}
|
||||
|
||||
|
||||
|
@ -225,6 +300,31 @@ muxer_init(muxer_t *m, const struct streaming_start *ss, const char *name)
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* sanity wrapper arround m_reconfigure()
|
||||
*/
|
||||
int
|
||||
muxer_reconfigure(muxer_t *m, const struct streaming_start *ss)
|
||||
{
|
||||
if(!m || !ss)
|
||||
return -1;
|
||||
|
||||
return m->m_reconfigure(m, ss);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* sanity wrapper arround m_add_marker()
|
||||
*/
|
||||
int
|
||||
muxer_add_marker(muxer_t *m)
|
||||
{
|
||||
if(!m)
|
||||
return -1;
|
||||
|
||||
return m->m_add_marker(m);
|
||||
}
|
||||
|
||||
/**
|
||||
* sanity wrapper arround m_open_file()
|
||||
*/
|
||||
|
@ -295,12 +395,12 @@ muxer_write_meta(muxer_t *m, struct epg_broadcast *eb)
|
|||
* sanity wrapper arround m_write_pkt()
|
||||
*/
|
||||
int
|
||||
muxer_write_pkt(muxer_t *m, void *data)
|
||||
muxer_write_pkt(muxer_t *m, streaming_message_type_t smt, void *data)
|
||||
{
|
||||
if(!m || !data)
|
||||
return -1;
|
||||
|
||||
return m->m_write_pkt(m, data);
|
||||
return m->m_write_pkt(m, smt, data);
|
||||
}
|
||||
|
||||
|
||||
|
|
31
src/muxer.h
31
src/muxer.h
|
@ -19,12 +19,15 @@
|
|||
#ifndef MUXER_H_
|
||||
#define MUXER_H_
|
||||
|
||||
#include "htsmsg.h"
|
||||
|
||||
typedef enum {
|
||||
MC_UNKNOWN = 0,
|
||||
MC_MATROSKA = 1,
|
||||
MC_MPEGTS = 2,
|
||||
MC_MPEGPS = 3,
|
||||
MC_PASS = 4,
|
||||
MC_RAW = 5,
|
||||
} muxer_container_type_t;
|
||||
|
||||
|
||||
|
@ -42,33 +45,45 @@ typedef struct muxer {
|
|||
int (*m_init) (struct muxer *, // Init The muxer with streams
|
||||
const struct streaming_start *,
|
||||
const char *);
|
||||
int (*m_reconfigure)(struct muxer *, // Reconfigure the muxer on
|
||||
const struct streaming_start *); // stream changes
|
||||
int (*m_close) (struct muxer *); // Close the muxer
|
||||
void (*m_destroy) (struct muxer *); // Free the memory
|
||||
int (*m_write_meta) (struct muxer *, struct epg_broadcast *); // Append epg data
|
||||
int (*m_write_pkt) (struct muxer *, void *); // Append a media packet
|
||||
int (*m_write_pkt) (struct muxer *, // Append a media packet
|
||||
streaming_message_type_t,
|
||||
void *);
|
||||
int (*m_add_marker) (struct muxer *); // Add a marker (or chapter)
|
||||
|
||||
int m_errors; // Number of errors
|
||||
muxer_container_type_t m_container; // The type of the container
|
||||
} muxer_t;
|
||||
|
||||
|
||||
// type <==> txt converters
|
||||
const char * muxer_container_type2txt(muxer_container_type_t mc);
|
||||
muxer_container_type_t muxer_container_txt2type(const char *str);
|
||||
const char* muxer_container_mimetype(muxer_container_type_t mc, int video);
|
||||
const char* muxer_container_suffix (muxer_container_type_t mc, int video);
|
||||
// type <==> string converters
|
||||
const char * muxer_container_type2txt (muxer_container_type_t mc);
|
||||
const char* muxer_container_type2mime (muxer_container_type_t mc, int video);
|
||||
|
||||
muxer_container_type_t muxer_container_txt2type (const char *str);
|
||||
muxer_container_type_t muxer_container_mime2type (const char *str);
|
||||
|
||||
const char* muxer_container_suffix(muxer_container_type_t mc, int video);
|
||||
|
||||
int muxer_container_list(htsmsg_t *array);
|
||||
|
||||
// Muxer factory
|
||||
muxer_t *muxer_create(struct service *s, muxer_container_type_t mc);
|
||||
muxer_t *muxer_create(muxer_container_type_t mc);
|
||||
|
||||
// Wrapper functions
|
||||
int muxer_open_file (muxer_t *m, const char *filename);
|
||||
int muxer_open_stream (muxer_t *m, int fd);
|
||||
int muxer_init (muxer_t *m, const struct streaming_start *ss, const char *name);
|
||||
int muxer_reconfigure (muxer_t *m, const struct streaming_start *ss);
|
||||
int muxer_add_marker (muxer_t *m);
|
||||
int muxer_close (muxer_t *m);
|
||||
int muxer_destroy (muxer_t *m);
|
||||
int muxer_write_meta (muxer_t *m, struct epg_broadcast *eb);
|
||||
int muxer_write_pkt (muxer_t *m, void *data);
|
||||
int muxer_write_pkt (muxer_t *m, streaming_message_type_t smt, void *data);
|
||||
const char* muxer_mime (muxer_t *m, const struct streaming_start *ss);
|
||||
const char* muxer_suffix (muxer_t *m, const struct streaming_start *ss);
|
||||
|
||||
|
|
536
src/muxer/muxer_libav.c
Normal file
536
src/muxer/muxer_libav.c
Normal file
|
@ -0,0 +1,536 @@
|
|||
/*
|
||||
* tvheadend, libavformat based muxer
|
||||
* Copyright (C) 2012 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 <htmlui://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/mathematics.h>
|
||||
|
||||
#include "tvheadend.h"
|
||||
#include "streaming.h"
|
||||
#include "epg.h"
|
||||
#include "channels.h"
|
||||
#include "libav.h"
|
||||
#include "muxer_libav.h"
|
||||
|
||||
typedef struct lav_muxer {
|
||||
muxer_t;
|
||||
AVFormatContext *lm_oc;
|
||||
AVBitStreamFilterContext *lm_h264_filter;
|
||||
int lm_fd;
|
||||
int lm_init;
|
||||
} lav_muxer_t;
|
||||
|
||||
#define MUX_BUF_SIZE 4096
|
||||
|
||||
static const AVRational mpeg_tc = {1, 90000};
|
||||
|
||||
|
||||
/**
|
||||
* Callback function for libavformat
|
||||
*/
|
||||
static int
|
||||
lav_muxer_write(void *opaque, uint8_t *buf, int buf_size)
|
||||
{
|
||||
int r;
|
||||
lav_muxer_t *lm = (lav_muxer_t*)opaque;
|
||||
|
||||
r = write(lm->lm_fd, buf, buf_size);
|
||||
lm->m_errors += (r != buf_size);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add a stream to the muxer
|
||||
*/
|
||||
static int
|
||||
lav_muxer_add_stream(lav_muxer_t *lm,
|
||||
const streaming_start_component_t *ssc)
|
||||
{
|
||||
AVStream *st;
|
||||
AVCodecContext *c;
|
||||
|
||||
st = avformat_new_stream(lm->lm_oc, NULL);
|
||||
if (!st)
|
||||
return -1;
|
||||
|
||||
st->id = ssc->ssc_index;
|
||||
c = st->codec;
|
||||
c->codec_id = streaming_component_type2codec_id(ssc->ssc_type);
|
||||
|
||||
switch(lm->m_container) {
|
||||
case MC_MATROSKA:
|
||||
st->time_base.num = 1000000;
|
||||
st->time_base.den = 1;
|
||||
break;
|
||||
|
||||
case MC_MPEGPS:
|
||||
c->rc_buffer_size = 224*1024*8;
|
||||
//Fall-through
|
||||
case MC_MPEGTS:
|
||||
st->time_base.num = 90000;
|
||||
st->time_base.den = 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
st->time_base = AV_TIME_BASE_Q;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if(ssc->ssc_gh) {
|
||||
c->extradata_size = pktbuf_len(ssc->ssc_gh);
|
||||
c->extradata = av_malloc(c->extradata_size);
|
||||
memcpy(c->extradata, pktbuf_ptr(ssc->ssc_gh),
|
||||
pktbuf_len(ssc->ssc_gh));
|
||||
}
|
||||
|
||||
if(SCT_ISAUDIO(ssc->ssc_type)) {
|
||||
c->codec_type = AVMEDIA_TYPE_AUDIO;
|
||||
c->sample_fmt = AV_SAMPLE_FMT_S16;
|
||||
|
||||
c->sample_rate = sri_to_rate(ssc->ssc_sri);
|
||||
c->channels = ssc->ssc_channels;
|
||||
|
||||
c->time_base.num = 1;
|
||||
c->time_base.den = c->sample_rate;
|
||||
|
||||
av_dict_set(&st->metadata, "language", ssc->ssc_lang, 0);
|
||||
|
||||
} else if(SCT_ISVIDEO(ssc->ssc_type)) {
|
||||
c->codec_type = AVMEDIA_TYPE_VIDEO;
|
||||
c->width = ssc->ssc_width;
|
||||
c->height = ssc->ssc_height;
|
||||
|
||||
c->time_base.num = 1;
|
||||
c->time_base.den = 25;
|
||||
|
||||
c->sample_aspect_ratio.num = ssc->ssc_aspect_num;
|
||||
c->sample_aspect_ratio.den = ssc->ssc_aspect_den;
|
||||
|
||||
st->sample_aspect_ratio.num = c->sample_aspect_ratio.num;
|
||||
st->sample_aspect_ratio.den = c->sample_aspect_ratio.den;
|
||||
|
||||
} else if(SCT_ISSUBTITLE(ssc->ssc_type)) {
|
||||
c->codec_type = AVMEDIA_TYPE_SUBTITLE;
|
||||
av_dict_set(&st->metadata, "language", ssc->ssc_lang, 0);
|
||||
}
|
||||
|
||||
if(lm->lm_oc->oformat->flags & AVFMT_GLOBALHEADER)
|
||||
c->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if a container supports a given streaming component
|
||||
*/
|
||||
static int
|
||||
lav_muxer_support_stream(muxer_container_type_t mc,
|
||||
streaming_component_type_t type)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
switch(mc) {
|
||||
case MC_MATROSKA:
|
||||
ret |= SCT_ISAUDIO(type);
|
||||
ret |= SCT_ISVIDEO(type);
|
||||
ret |= SCT_ISSUBTITLE(type);
|
||||
break;
|
||||
|
||||
case MC_MPEGTS:
|
||||
ret |= (type == SCT_MPEG2VIDEO);
|
||||
ret |= (type == SCT_H264);
|
||||
|
||||
ret |= (type == SCT_MPEG2AUDIO);
|
||||
ret |= (type == SCT_AC3);
|
||||
ret |= (type == SCT_AAC);
|
||||
ret |= (type == SCT_MP4A);
|
||||
ret |= (type == SCT_EAC3);
|
||||
|
||||
//Some pids lack pts, disable for now
|
||||
//ret |= (type == SCT_TELETEXT);
|
||||
ret |= (type == SCT_DVBSUB);
|
||||
break;
|
||||
|
||||
case MC_MPEGPS:
|
||||
ret |= (type == SCT_MPEG2VIDEO);
|
||||
ret |= (type == SCT_MPEG2AUDIO);
|
||||
ret |= (type == SCT_AC3);
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Figure out the mime-type for the muxed data stream
|
||||
*/
|
||||
static const char*
|
||||
lav_muxer_mime(muxer_t* m, const struct streaming_start *ss)
|
||||
{
|
||||
int i;
|
||||
int has_audio;
|
||||
int has_video;
|
||||
const streaming_start_component_t *ssc;
|
||||
|
||||
has_audio = 0;
|
||||
has_video = 0;
|
||||
|
||||
for(i=0; i < ss->ss_num_components; i++) {
|
||||
ssc = &ss->ss_components[i];
|
||||
|
||||
if(ssc->ssc_disabled)
|
||||
continue;
|
||||
|
||||
if(!lav_muxer_support_stream(m->m_container, ssc->ssc_type))
|
||||
continue;
|
||||
|
||||
has_video |= SCT_ISVIDEO(ssc->ssc_type);
|
||||
has_audio |= SCT_ISAUDIO(ssc->ssc_type);
|
||||
}
|
||||
|
||||
if(has_video)
|
||||
return muxer_container_type2mime(m->m_container, 1);
|
||||
else if(has_audio)
|
||||
return muxer_container_type2mime(m->m_container, 0);
|
||||
else
|
||||
return muxer_container_type2mime(MC_UNKNOWN, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Init the muxer with streams
|
||||
*/
|
||||
static int
|
||||
lav_muxer_init(muxer_t* m, const struct streaming_start *ss, const char *name)
|
||||
{
|
||||
int i;
|
||||
const streaming_start_component_t *ssc;
|
||||
AVFormatContext *oc;
|
||||
lav_muxer_t *lm = (lav_muxer_t*)m;
|
||||
char app[128];
|
||||
|
||||
snprintf(app, sizeof(app), "Tvheadend %s", tvheadend_version);
|
||||
|
||||
oc = lm->lm_oc;
|
||||
|
||||
av_dict_set(&oc->metadata, "title", name, 0);
|
||||
av_dict_set(&oc->metadata, "service_name", name, 0);
|
||||
av_dict_set(&oc->metadata, "service_provider", app, 0);
|
||||
|
||||
if(lm->m_container == MC_MPEGTS)
|
||||
lm->lm_h264_filter = av_bitstream_filter_init("h264_mp4toannexb");
|
||||
|
||||
oc->max_delay = 0.7 * AV_TIME_BASE;
|
||||
|
||||
for(i=0; i < ss->ss_num_components; i++) {
|
||||
ssc = &ss->ss_components[i];
|
||||
|
||||
if(ssc->ssc_disabled)
|
||||
continue;
|
||||
|
||||
if(!lav_muxer_support_stream(lm->m_container, ssc->ssc_type)) {
|
||||
tvhlog(LOG_WARNING, "libav", "%s is not supported in %s",
|
||||
streaming_component_type2txt(ssc->ssc_type),
|
||||
muxer_container_type2txt(lm->m_container));
|
||||
continue;
|
||||
}
|
||||
|
||||
if(lav_muxer_add_stream(lm, ssc)) {
|
||||
tvhlog(LOG_ERR, "libav", "Failed to add %s stream",
|
||||
streaming_component_type2txt(ssc->ssc_type));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if(!lm->lm_oc->nb_streams) {
|
||||
tvhlog(LOG_ERR, "libav", "No supported streams available");
|
||||
lm->m_errors++;
|
||||
return -1;
|
||||
} else if(avformat_write_header(lm->lm_oc, NULL) < 0) {
|
||||
tvhlog(LOG_ERR, "libav", "Failed to write %s header",
|
||||
muxer_container_type2txt(lm->m_container));
|
||||
lm->m_errors++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
lm->lm_init = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle changes to the streams (usually PMT updates)
|
||||
*/
|
||||
static int
|
||||
lav_muxer_reconfigure(muxer_t* m, const struct streaming_start *ss)
|
||||
{
|
||||
lav_muxer_t *lm = (lav_muxer_t*)m;
|
||||
|
||||
lm->m_errors++;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Open the muxer and write the header
|
||||
*/
|
||||
static int
|
||||
lav_muxer_open_stream(muxer_t *m, int fd)
|
||||
{
|
||||
uint8_t *buf;
|
||||
AVIOContext *pb;
|
||||
lav_muxer_t *lm = (lav_muxer_t*)m;
|
||||
|
||||
buf = av_malloc(MUX_BUF_SIZE);
|
||||
pb = avio_alloc_context(buf, MUX_BUF_SIZE, 1, lm, NULL,
|
||||
lav_muxer_write, NULL);
|
||||
pb->seekable = 0;
|
||||
lm->lm_oc->pb = pb;
|
||||
lm->lm_fd = fd;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int
|
||||
lav_muxer_open_file(muxer_t *m, const char *filename)
|
||||
{
|
||||
AVFormatContext *oc;
|
||||
lav_muxer_t *lm = (lav_muxer_t*)m;
|
||||
|
||||
oc = lm->lm_oc;
|
||||
snprintf(oc->filename, sizeof(oc->filename), "%s", filename);
|
||||
|
||||
if(avio_open(&oc->pb, filename, AVIO_FLAG_WRITE) < 0) {
|
||||
tvhlog(LOG_ERR, "libav", "Could not open %s", filename);
|
||||
lm->m_errors++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Write a packet to the muxer
|
||||
*/
|
||||
static int
|
||||
lav_muxer_write_pkt(muxer_t *m, streaming_message_type_t smt, void *data)
|
||||
{
|
||||
int i;
|
||||
AVFormatContext *oc;
|
||||
AVStream *st;
|
||||
AVPacket packet;
|
||||
th_pkt_t *pkt = (th_pkt_t*)data;
|
||||
lav_muxer_t *lm = (lav_muxer_t*)m;
|
||||
int rc = 0;
|
||||
|
||||
assert(smt == SMT_PACKET);
|
||||
|
||||
oc = lm->lm_oc;
|
||||
|
||||
if(!oc->nb_streams) {
|
||||
tvhlog(LOG_ERR, "libav", "No streams to mux");
|
||||
rc = -1;
|
||||
goto ret;
|
||||
}
|
||||
|
||||
if(!lm->lm_init) {
|
||||
tvhlog(LOG_ERR, "libav", "Muxer not initialized correctly");
|
||||
rc = -1;
|
||||
goto ret;
|
||||
}
|
||||
|
||||
for(i=0; i<oc->nb_streams; i++) {
|
||||
st = oc->streams[i];
|
||||
|
||||
if(st->id != pkt->pkt_componentindex)
|
||||
continue;
|
||||
|
||||
av_init_packet(&packet);
|
||||
|
||||
if(st->codec->codec_id == CODEC_ID_MPEG2VIDEO)
|
||||
pkt = pkt_merge_header(pkt);
|
||||
|
||||
if(lm->lm_h264_filter && st->codec->codec_id == CODEC_ID_H264) {
|
||||
if(av_bitstream_filter_filter(lm->lm_h264_filter,
|
||||
st->codec,
|
||||
NULL,
|
||||
&packet.data,
|
||||
&packet.size,
|
||||
pktbuf_ptr(pkt->pkt_payload),
|
||||
pktbuf_len(pkt->pkt_payload),
|
||||
pkt->pkt_frametype < PKT_P_FRAME) < 0) {
|
||||
tvhlog(LOG_WARNING, "libav", "Failed to filter bitstream");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
packet.data = pktbuf_ptr(pkt->pkt_payload);
|
||||
packet.size = pktbuf_len(pkt->pkt_payload);
|
||||
}
|
||||
|
||||
packet.stream_index = st->index;
|
||||
|
||||
packet.pts = av_rescale_q(pkt->pkt_pts , mpeg_tc, st->time_base);
|
||||
packet.dts = av_rescale_q(pkt->pkt_dts , mpeg_tc, st->time_base);
|
||||
packet.duration = av_rescale_q(pkt->pkt_duration, mpeg_tc, st->time_base);
|
||||
|
||||
if(pkt->pkt_frametype < PKT_P_FRAME)
|
||||
packet.flags |= AV_PKT_FLAG_KEY;
|
||||
|
||||
if((rc = av_interleaved_write_frame(oc, &packet)))
|
||||
tvhlog(LOG_WARNING, "libav", "Failed to write frame");
|
||||
|
||||
// h264_mp4toannexb filter might allocate new data.
|
||||
if(packet.data != pktbuf_ptr(pkt->pkt_payload))
|
||||
av_free(packet.data);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
ret:
|
||||
lm->m_errors += (rc != 0);
|
||||
pkt_ref_dec(pkt);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* NOP
|
||||
*/
|
||||
static int
|
||||
lav_muxer_write_meta(muxer_t *m, struct epg_broadcast *eb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* NOP
|
||||
*/
|
||||
static int
|
||||
lav_muxer_add_marker(muxer_t* m)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Close the muxer and append trailer to output
|
||||
*/
|
||||
static int
|
||||
lav_muxer_close(muxer_t *m)
|
||||
{
|
||||
int i;
|
||||
int ret = 0;
|
||||
lav_muxer_t *lm = (lav_muxer_t*)m;
|
||||
|
||||
if(lm->lm_init && av_write_trailer(lm->lm_oc) < 0) {
|
||||
tvhlog(LOG_WARNING, "libav", "Failed to write %s trailer",
|
||||
muxer_container_type2txt(lm->m_container));
|
||||
lm->m_errors++;
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
if(lm->lm_h264_filter)
|
||||
av_bitstream_filter_close(lm->lm_h264_filter);
|
||||
|
||||
for(i=0; i<lm->lm_oc->nb_streams; i++)
|
||||
av_freep(&lm->lm_oc->streams[i]->codec->extradata);
|
||||
|
||||
lm->lm_oc->nb_streams = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Free all memory associated with the muxer
|
||||
*/
|
||||
static void
|
||||
lav_muxer_destroy(muxer_t *m)
|
||||
{
|
||||
lav_muxer_t *lm = (lav_muxer_t*)m;
|
||||
|
||||
if(lm->lm_oc && lm->lm_oc->pb)
|
||||
av_free(lm->lm_oc->pb);
|
||||
|
||||
if(lm->lm_oc)
|
||||
av_free(lm->lm_oc);
|
||||
|
||||
free(lm);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new libavformat based muxer
|
||||
*/
|
||||
muxer_t*
|
||||
lav_muxer_create(muxer_container_type_t mc)
|
||||
{
|
||||
const char *mux_name;
|
||||
lav_muxer_t *lm;
|
||||
AVOutputFormat *fmt;
|
||||
|
||||
switch(mc) {
|
||||
case MC_MPEGPS:
|
||||
mux_name = "dvd";
|
||||
break;
|
||||
default:
|
||||
mux_name = muxer_container_type2txt(mc);
|
||||
break;
|
||||
}
|
||||
|
||||
fmt = av_guess_format(mux_name, NULL, NULL);
|
||||
if(!fmt) {
|
||||
tvhlog(LOG_ERR, "libav", "Can't find the '%s' muxer", mux_name);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
lm = calloc(1, sizeof(lav_muxer_t));
|
||||
lm->m_open_stream = lav_muxer_open_stream;
|
||||
lm->m_open_file = lav_muxer_open_file;
|
||||
lm->m_init = lav_muxer_init;
|
||||
lm->m_reconfigure = lav_muxer_reconfigure;
|
||||
lm->m_mime = lav_muxer_mime;
|
||||
lm->m_add_marker = lav_muxer_add_marker;
|
||||
lm->m_write_meta = lav_muxer_write_meta;
|
||||
lm->m_write_pkt = lav_muxer_write_pkt;
|
||||
lm->m_close = lav_muxer_close;
|
||||
lm->m_destroy = lav_muxer_destroy;
|
||||
lm->m_container = mc;
|
||||
lm->lm_oc = avformat_alloc_context();
|
||||
lm->lm_oc->oformat = fmt;
|
||||
lm->lm_fd = -1;
|
||||
lm->lm_init = 0;
|
||||
|
||||
return (muxer_t*)lm;
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Output functions for fixed multicast streaming
|
||||
* Copyright (C) 2007 Andreas Öman
|
||||
* tvheadend, muxing of packets with libavformat
|
||||
* Copyright (C) 2012 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
|
||||
|
@ -13,12 +13,14 @@
|
|||
* 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/>.
|
||||
* along with this program. If not, see <htmlui://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef IPTV_OUTPUT_H_
|
||||
#define IPTV_OUTPUT_H_
|
||||
#ifndef LAV_MUXER_H_
|
||||
#define LAV_MUXER_H_
|
||||
|
||||
void output_multicast_setup(void);
|
||||
#include "muxer.h"
|
||||
|
||||
#endif /* IPTV_OUTPUT_H_ */
|
||||
muxer_t* lav_muxer_create(muxer_container_type_t mc);
|
||||
|
||||
#endif
|
|
@ -19,6 +19,7 @@
|
|||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "tvheadend.h"
|
||||
#include "streaming.h"
|
||||
|
@ -48,9 +49,10 @@ typedef struct pass_muxer {
|
|||
char *pm_filename;
|
||||
|
||||
/* TS muxing */
|
||||
uint8_t pm_injection;
|
||||
uint8_t *pm_pat;
|
||||
uint8_t *pm_pmt;
|
||||
uint16_t pm_pmt_pid;
|
||||
uint16_t pm_pmt_version;
|
||||
uint32_t pm_ic; // Injection counter
|
||||
uint32_t pm_pc; // Packet counter
|
||||
} pass_muxer_t;
|
||||
|
@ -65,8 +67,10 @@ pass_muxer_mime(muxer_t* m, const struct streaming_start *ss)
|
|||
int i;
|
||||
int has_audio;
|
||||
int has_video;
|
||||
muxer_container_type_t mc;
|
||||
const streaming_start_component_t *ssc;
|
||||
|
||||
const source_info_t *si = &ss->ss_si;
|
||||
|
||||
has_audio = 0;
|
||||
has_video = 0;
|
||||
|
||||
|
@ -80,12 +84,62 @@ pass_muxer_mime(muxer_t* m, const struct streaming_start *ss)
|
|||
has_audio |= SCT_ISAUDIO(ssc->ssc_type);
|
||||
}
|
||||
|
||||
if(has_video)
|
||||
return muxer_container_mimetype(m->m_container, 1);
|
||||
else if(has_audio)
|
||||
return muxer_container_mimetype(m->m_container, 0);
|
||||
if(si->si_type == S_MPEG_TS)
|
||||
mc = MC_MPEGTS;
|
||||
else if(si->si_type == S_MPEG_PS)
|
||||
mc = MC_MPEGPS;
|
||||
else
|
||||
return muxer_container_mimetype(MC_UNKNOWN, 0);
|
||||
mc = MC_UNKNOWN;
|
||||
|
||||
if(has_video)
|
||||
return muxer_container_type2mime(mc, 1);
|
||||
else if(has_audio)
|
||||
return muxer_container_type2mime(mc, 0);
|
||||
else
|
||||
return muxer_container_type2mime(MC_UNKNOWN, 0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate the pmt and pat from a streaming start message
|
||||
*/
|
||||
static int
|
||||
pass_muxer_reconfigure(muxer_t* m, const struct streaming_start *ss)
|
||||
{
|
||||
pass_muxer_t *pm = (pass_muxer_t*)m;
|
||||
const source_info_t *si = &ss->ss_si;
|
||||
|
||||
if(si->si_type == S_MPEG_TS && ss->ss_pmt_pid) {
|
||||
pm->pm_pat = realloc(pm->pm_pat, 188);
|
||||
memset(pm->pm_pat, 0xff, 188);
|
||||
pm->pm_pat[0] = 0x47;
|
||||
pm->pm_pat[1] = 0x40;
|
||||
pm->pm_pat[2] = 0x00;
|
||||
pm->pm_pat[3] = 0x10;
|
||||
pm->pm_pat[4] = 0x00;
|
||||
if(psi_build_pat(NULL, pm->pm_pat+5, 183, ss->ss_pmt_pid) < 0) {
|
||||
pm->m_errors++;
|
||||
tvhlog(LOG_ERR, "pass", "%s: Unable to build pat", pm->pm_filename);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pm->pm_pmt = realloc(pm->pm_pmt, 188);
|
||||
memset(pm->pm_pmt, 0xff, 188);
|
||||
pm->pm_pmt[0] = 0x47;
|
||||
pm->pm_pmt[1] = 0x40 | (ss->ss_pmt_pid >> 8);
|
||||
pm->pm_pmt[2] = 0x00 | (ss->ss_pmt_pid >> 0);
|
||||
pm->pm_pmt[3] = 0x10;
|
||||
pm->pm_pmt[4] = 0x00;
|
||||
if(psi_build_pmt(ss, pm->pm_pmt+5, 183, pm->pm_pmt_version,
|
||||
ss->ss_pcr_pid) < 0) {
|
||||
pm->m_errors++;
|
||||
tvhlog(LOG_ERR, "pass", "%s: Unable to build pmt", pm->pm_filename);
|
||||
return -1;
|
||||
}
|
||||
pm->pm_pmt_version++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
@ -95,36 +149,7 @@ pass_muxer_mime(muxer_t* m, const struct streaming_start *ss)
|
|||
static int
|
||||
pass_muxer_init(muxer_t* m, const struct streaming_start *ss, const char *name)
|
||||
{
|
||||
pass_muxer_t *pm = (pass_muxer_t*)m;
|
||||
|
||||
if(pm->m_container == MC_MPEGTS) {
|
||||
|
||||
memset(pm->pm_pat, 0xff, 188);
|
||||
pm->pm_pat[0] = 0x47;
|
||||
pm->pm_pat[1] = 0x40;
|
||||
pm->pm_pat[2] = 0x00;
|
||||
pm->pm_pat[3] = 0x10;
|
||||
pm->pm_pat[4] = 0x00;
|
||||
if(psi_build_pat(NULL, pm->pm_pat+5, 183, pm->pm_pmt_pid) < 0) {
|
||||
pm->m_errors++;
|
||||
tvhlog(LOG_ERR, "pass", "%s: Unable to build pat", pm->pm_filename);
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(pm->pm_pmt, 0xff, 188);
|
||||
pm->pm_pmt[0] = 0x47;
|
||||
pm->pm_pmt[1] = 0x40 | (pm->pm_pmt_pid >> 8);
|
||||
pm->pm_pmt[2] = 0x00 | (pm->pm_pmt_pid >> 0);
|
||||
pm->pm_pmt[3] = 0x10;
|
||||
pm->pm_pmt[4] = 0x00;
|
||||
if(psi_build_pmt(ss, pm->pm_pmt+5, 183, ss->ss_pcr_pid) < 0) {
|
||||
pm->m_errors++;
|
||||
tvhlog(LOG_ERR, "pass", "%s: Unable to build pmt", pm->pm_filename);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
return pass_muxer_reconfigure(m, ss);
|
||||
}
|
||||
|
||||
|
||||
|
@ -170,16 +195,16 @@ pass_muxer_open_file(muxer_t *m, const char *filename)
|
|||
|
||||
|
||||
/**
|
||||
* Write TS packets to the file descriptor
|
||||
* Write data to the file descriptor
|
||||
*/
|
||||
static void
|
||||
pass_muxer_write(muxer_t *m, const void *ts, size_t len)
|
||||
pass_muxer_write(muxer_t *m, const void *data, size_t size)
|
||||
{
|
||||
pass_muxer_t *pm = (pass_muxer_t*)m;
|
||||
|
||||
if(pm->pm_error) {
|
||||
pm->m_errors++;
|
||||
} else if(write(pm->pm_fd, ts, len) != len) {
|
||||
} else if(tvh_write(pm->pm_fd, data, size)) {
|
||||
pm->pm_error = errno;
|
||||
tvhlog(LOG_ERR, "pass", "%s: Write failed -- %s", pm->pm_filename,
|
||||
strerror(errno));
|
||||
|
@ -197,14 +222,18 @@ pass_muxer_write_ts(muxer_t *m, pktbuf_t *pb)
|
|||
pass_muxer_t *pm = (pass_muxer_t*)m;
|
||||
int rem;
|
||||
|
||||
// Inject pmt and pat into the stream
|
||||
rem = pm->pm_pc % TS_INJECTION_RATE;
|
||||
if(!rem) {
|
||||
pm->pm_pat[3] = (pm->pm_pat[3] & 0xf0) | (pm->pm_ic & 0x0f);
|
||||
pm->pm_pmt[3] = (pm->pm_pat[3] & 0xf0) | (pm->pm_ic & 0x0f);
|
||||
pass_muxer_write(m, pm->pm_pmt, 188);
|
||||
pass_muxer_write(m, pm->pm_pat, 188);
|
||||
pm->pm_ic++;
|
||||
if(pm->pm_pat != NULL &&
|
||||
pm->pm_pmt != NULL &&
|
||||
pm->pm_injection) {
|
||||
// Inject pmt and pat into the stream
|
||||
rem = pm->pm_pc % TS_INJECTION_RATE;
|
||||
if(!rem) {
|
||||
pm->pm_pat[3] = (pm->pm_pat[3] & 0xf0) | (pm->pm_ic & 0x0f);
|
||||
pm->pm_pmt[3] = (pm->pm_pmt[3] & 0xf0) | (pm->pm_ic & 0x0f);
|
||||
pass_muxer_write(m, pm->pm_pat, 188);
|
||||
pass_muxer_write(m, pm->pm_pmt, 188);
|
||||
pm->pm_ic++;
|
||||
}
|
||||
}
|
||||
|
||||
pass_muxer_write(m, pb->pb_data, pb->pb_size);
|
||||
|
@ -217,22 +246,23 @@ pass_muxer_write_ts(muxer_t *m, pktbuf_t *pb)
|
|||
* Write a packet directly to the file descriptor
|
||||
*/
|
||||
static int
|
||||
pass_muxer_write_pkt(muxer_t *m, void *data)
|
||||
pass_muxer_write_pkt(muxer_t *m, streaming_message_type_t smt, void *data)
|
||||
{
|
||||
pktbuf_t *pb = (pktbuf_t*)data;
|
||||
pass_muxer_t *pm = (pass_muxer_t*)m;
|
||||
|
||||
switch(pm->m_container) {
|
||||
case MC_MPEGTS:
|
||||
assert(smt == SMT_MPEGTS);
|
||||
|
||||
switch(smt) {
|
||||
case SMT_MPEGTS:
|
||||
pass_muxer_write_ts(m, pb);
|
||||
break;
|
||||
default:
|
||||
//NOP
|
||||
//TODO: add support for v4l (MPEG-PS)
|
||||
break;
|
||||
}
|
||||
|
||||
if(!pm->pm_error)
|
||||
pktbuf_ref_dec(pb);
|
||||
pktbuf_ref_dec(pb);
|
||||
|
||||
return pm->pm_error;
|
||||
}
|
||||
|
@ -293,31 +323,25 @@ pass_muxer_destroy(muxer_t *m)
|
|||
* Create a new passthrough muxer
|
||||
*/
|
||||
muxer_t*
|
||||
pass_muxer_create(service_t *s, muxer_container_type_t mc)
|
||||
pass_muxer_create(muxer_container_type_t mc)
|
||||
{
|
||||
pass_muxer_t *pm;
|
||||
|
||||
if(mc != MC_PASS)
|
||||
if(mc != MC_PASS && mc != MC_RAW)
|
||||
return NULL;
|
||||
|
||||
pm = calloc(1, sizeof(pass_muxer_t));
|
||||
pm->m_open_stream = pass_muxer_open_stream;
|
||||
pm->m_open_file = pass_muxer_open_file;
|
||||
pm->m_init = pass_muxer_init;
|
||||
pm->m_reconfigure = pass_muxer_reconfigure;
|
||||
pm->m_mime = pass_muxer_mime;
|
||||
pm->m_write_meta = pass_muxer_write_meta;
|
||||
pm->m_write_pkt = pass_muxer_write_pkt;
|
||||
pm->m_close = pass_muxer_close;
|
||||
pm->m_destroy = pass_muxer_destroy;
|
||||
|
||||
if(s->s_type == SERVICE_TYPE_V4L) {
|
||||
pm->m_container = MC_MPEGPS;
|
||||
} else {
|
||||
pm->m_container = MC_MPEGTS;
|
||||
pm->pm_pmt_pid = s->s_pmt_pid;
|
||||
pm->pm_pat = malloc(188);
|
||||
pm->pm_pmt = malloc(188);
|
||||
}
|
||||
pm->pm_fd = -1;
|
||||
pm->pm_injection = (mc == MC_PASS);
|
||||
|
||||
return (muxer_t *)pm;
|
||||
}
|
|
@ -21,6 +21,6 @@
|
|||
|
||||
#include "muxer.h"
|
||||
|
||||
muxer_t* pass_muxer_create(struct service *s, muxer_container_type_t mc);
|
||||
muxer_t* pass_muxer_create(muxer_container_type_t mc);
|
||||
|
||||
#endif
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue