Merge remote-tracking branch 'dev/master'

Conflicts:
	data/conf/charset
This commit is contained in:
Wojciech Myrda 2013-05-22 09:10:08 +02:00
commit 21e8e272fb
191 changed files with 16164 additions and 6142 deletions

4
.gitignore vendored
View file

@ -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
View file

114
Makefile
View file

@ -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
View file

@ -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
View 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
View file

@ -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

View file

@ -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 $?

View file

@ -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

View file

@ -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
View file

@ -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
View file

@ -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
View file

@ -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}

View file

@ -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=""

View file

@ -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 ;;

View file

@ -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

View file

@ -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>

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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>

View file

@ -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>

View 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>

View file

@ -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.

View file

@ -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>

View file

@ -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>

View file

@ -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).

View file

@ -1,16 +1,8 @@
<div class="hts-doc-text">
<p>
<center>
<h1>HTS Tvheadend 3.2</h1>
&copy; 2006 - 2012, Andreas Öman, et al.<br><br>
<h1>Tvheadend</h1>
&copy; 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>

View file

@ -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>

View file

@ -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

View file

@ -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

View file

@ -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");
}

View file

@ -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_ */

View file

@ -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
}

View file

@ -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) {

View file

@ -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);

File diff suppressed because it is too large Load diff

View file

@ -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);
}

View file

@ -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
View file

@ -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
View 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;
}

View file

@ -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

View file

@ -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);
}

View file

@ -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_ */

View file

@ -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)

View file

@ -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;

View 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
View 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);
}

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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();

View file

@ -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) {

View file

@ -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

View file

@ -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 */

View file

@ -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) {

View file

@ -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
View 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
View 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
View file

@ -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",
}
};

View file

@ -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);
/* ************************************************************************

View file

@ -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);

View file

@ -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;

View file

@ -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 );

View file

@ -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);
}

View file

@ -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 )

View file

@ -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++);
}
}

View file

@ -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);
}

View file

@ -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);
}
}
}

View file

@ -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);

View file

@ -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 = "&lt;"; break;
case '>': esc = "&gt;"; break;
case '&': esc = "&amp;"; break;
case '\'': esc = "&apos;"; break;
case '"': esc = "&quot;"; 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;
}

View file

@ -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__ */

View file

@ -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);
}

View file

@ -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_ */

View file

@ -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);
}

View file

@ -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

View file

@ -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);

View file

@ -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);
}

View file

@ -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
View 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
View 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__ */

View file

@ -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;

View file

@ -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);
}
}
}

View file

@ -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
View 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
View 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

View file

@ -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", &timeshift_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(&gtimers, gti, gti_link, gtimercmp);
if (LIST_FIRST(&gtimers) == gti)
pthread_cond_signal(&gtimer_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(&gtimers)) != 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(&gtimers) == NULL) || (ts.tv_sec > (dispatch_clock + 1))) {
ts.tv_sec = dispatch_clock + 1;
ts.tv_nsec = 0;
}
/* Wait */
pthread_cond_timedwait(&gtimer_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(&gtimer_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
View 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
View 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
View 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
View 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);

View file

@ -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);
}

View file

@ -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
View 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;
}

View file

@ -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

View file

@ -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;
}

View file

@ -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