diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4b14fb9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +/m4/ +/autom4te.cache/ +/aclocal.m4 +/config.guess +/config.h.in +/config.h.in~ +/config.sub +/configure +/depcomp +/install-sh +/common/Makefile.in +/libeplayer3/Makefile.in +/libspark/Makefile.in +/libtriple/Makefile.in +/azbox/Makefile.in +/generic-pc/Makefile.in +/raspi/Makefile.in +/ltmain.sh +/missing +/Makefile.in +/tools/Makefile.in diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..36e8f10 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,42 @@ +ACLOCAL_AMFLAGS = -I m4 + +lib_LTLIBRARIES = libstb-hal.la +libstb_hal_la_SOURCES = +SUBDIRS = common tools +bin_PROGRAMS = libstb-hal-test + +libstb_hal_la_LIBADD = \ + common/libcommon.la + +libstb_hal_test_SOURCES = libtest.cpp +libstb_hal_test_LDADD = libstb-hal.la + +# there has to be a better way to do this... +if BOXTYPE_TRIPLE +SUBDIRS += libtriple +libstb_hal_la_LIBADD += \ + libtriple/libtriple.la +endif +if BOXTYPE_AZBOX +SUBDIRS += azbox +libstb_hal_la_LIBADD += \ + azbox/libazbox.la +endif +if BOXTYPE_GENERIC +if BOXMODEL_RASPI +SUBDIRS += raspi +libstb_hal_la_LIBADD += \ + raspi/libraspi.la +else +SUBDIRS += generic-pc +libstb_hal_la_LIBADD += \ + generic-pc/libgeneric.la +endif +endif +if BOXTYPE_SPARK +libstb_hal_test_LDADD += -lasound +SUBDIRS += libspark libeplayer3 +libstb_hal_la_LIBADD += \ + libspark/libspark.la \ + libeplayer3/libeplayer3.la +endif diff --git a/acinclude.m4 b/acinclude.m4 new file mode 100644 index 0000000..64db50e --- /dev/null +++ b/acinclude.m4 @@ -0,0 +1,407 @@ +AC_DEFUN([TUXBOX_APPS],[ +AM_CONFIG_HEADER(config.h) +AM_MAINTAINER_MODE + +AC_SYS_LARGEFILE + +AC_ARG_WITH(target, + [ --with-target=TARGET target for compilation [[native,cdk]]], + [TARGET="$withval"],[TARGET="native"]) + +AC_ARG_WITH(targetprefix, + [ --with-targetprefix=PATH prefix relative to target root (only applicable in cdk mode)], + [targetprefix="$withval"],[targetprefix="NONE"]) + +AC_ARG_WITH(debug, + [ --without-debug disable debugging code], + [DEBUG="$withval"],[DEBUG="yes"]) + +if test "$DEBUG" = "yes"; then + DEBUG_CFLAGS="-g3 -ggdb" + AC_DEFINE(DEBUG,1,[Enable debug messages]) +fi + +AC_MSG_CHECKING(target) + +if test "$TARGET" = "native"; then + AC_MSG_RESULT(native) + + if test "$CFLAGS" = "" -a "$CXXFLAGS" = ""; then + CFLAGS="-Wall -O2 -pipe $DEBUG_CFLAGS" + CXXFLAGS="-Wall -O2 -pipe $DEBUG_CFLAGS" + fi + if test "$prefix" = "NONE"; then + prefix=/usr/local + fi + targetprefix=$prefix +elif test "$TARGET" = "cdk"; then + AC_MSG_RESULT(cdk) + + if test "$prefix" = "NONE"; then + AC_MSG_ERROR(invalid prefix, you need to specify one in cdk mode) + fi + if test "$targetprefix" = "NONE"; then + targetprefix="" + fi +else + AC_MSG_RESULT(none) + AC_MSG_ERROR([invalid target $TARGET, choose on from native,cdk]); +fi + +AC_CANONICAL_BUILD +AC_CANONICAL_HOST + +check_path () { + return $(perl -e "if(\"$1\"=~m#^/usr/(local/)?bin#){print \"0\"}else{print \"1\";}") +} + +]) + +dnl expand nested ${foo}/bar +AC_DEFUN([TUXBOX_EXPAND_VARIABLE],[__$1="$2" + for __CNT in false false false false true; do dnl max 5 levels of indirection + + $1=`eval echo "$__$1"` + echo ${$1} | grep -q '\$' || break # 'grep -q' is POSIX, exit if no $ in variable + __$1="${$1}" + done + $__CNT && AC_MSG_ERROR([can't expand variable $1=$2]) dnl bail out if we did not expand +]) + +AC_DEFUN([TUXBOX_APPS_DIRECTORY_ONE],[ +AC_ARG_WITH($1,[ $6$7 [[PREFIX$4$5]]],[ + _$2=$withval + if test "$TARGET" = "cdk"; then + $2=`eval echo "${targetprefix}$withval"` # no indirection possible IMNSHO + else + $2=$withval + fi + TARGET_$2=${$2} +],[ + # RFC 1925: "you can always add another level of indirection..." + TUXBOX_EXPAND_VARIABLE($2,"${$3}$5") + if test "$TARGET" = "cdk"; then + TUXBOX_EXPAND_VARIABLE(_$2,"${target$3}$5") + else + _$2=${$2} + fi + TARGET_$2=$_$2 +]) + +dnl automake <= 1.6 don't support this +dnl AC_SUBST($2) +AC_DEFINE_UNQUOTED($2,"$_$2",$7) +AC_SUBST(TARGET_$2) +]) + +AC_DEFUN([TUXBOX_APPS_DIRECTORY],[ +AC_REQUIRE([TUXBOX_APPS]) + +if test "$TARGET" = "cdk"; then + datadir="\${prefix}/share" + sysconfdir="\${prefix}/etc" + localstatedir="\${prefix}/var" + libdir="\${prefix}/lib" + targetdatadir="\${targetprefix}/share" + targetsysconfdir="\${targetprefix}/etc" + targetlocalstatedir="\${targetprefix}/var" + targetlibdir="\${targetprefix}/lib" +fi + +TUXBOX_APPS_DIRECTORY_ONE(configdir,CONFIGDIR,localstatedir,/var,/tuxbox/config, + [--with-configdir=PATH ],[where to find the config files]) + +TUXBOX_APPS_DIRECTORY_ONE(datadir,DATADIR,datadir,/share,/tuxbox, + [--with-datadir=PATH ],[where to find data]) + +TUXBOX_APPS_DIRECTORY_ONE(fontdir,FONTDIR,datadir,/share,/fonts, + [--with-fontdir=PATH ],[where to find the fonts]) + +TUXBOX_APPS_DIRECTORY_ONE(gamesdir,GAMESDIR,localstatedir,/var,/tuxbox/games, + [--with-gamesdir=PATH ],[where games data is stored]) + +TUXBOX_APPS_DIRECTORY_ONE(libdir,LIBDIR,libdir,/lib,/tuxbox, + [--with-libdir=PATH ],[where to find the internal libs]) + +TUXBOX_APPS_DIRECTORY_ONE(plugindir,PLUGINDIR,libdir,/lib,/tuxbox/plugins, + [--with-plugindir=PATH ],[where to find the plugins]) + +TUXBOX_APPS_DIRECTORY_ONE(ucodedir,UCODEDIR,localstatedir,/var,/tuxbox/ucodes, + [--with-ucodedir=PATH ],[where to find the ucodes]) + +TUXBOX_APPS_DIRECTORY_ONE(themesdir,THEMESDIR,datadir,/share,/tuxbox/neutrino/themes, + [--with-themesdir=PATH ],[where to find the themes (don't change)]) +]) + +dnl automake <= 1.6 needs this specifications +AC_SUBST(CONFIGDIR) +AC_SUBST(DATADIR) +AC_SUBST(FONTDIR) +AC_SUBST(GAMESDIR) +AC_SUBST(LIBDIR) +AC_SUBST(PLUGINDIR) +AC_SUBST(UCODEDIR) +AC_SUBST(THEMESDIR) +dnl end workaround + +AC_DEFUN([TUXBOX_APPS_ENDIAN],[ +AC_CHECK_HEADERS(endian.h) +AC_C_BIGENDIAN +]) + +AC_DEFUN([TUXBOX_APPS_DVB],[ +AC_ARG_WITH(dvbincludes, + [ --with-dvbincludes=PATH path for dvb includes [[NONE]]], + [DVBINCLUDES="$withval"],[DVBINCLUDES=""]) + +if test "$DVBINCLUDES"; then + CPPFLAGS="$CPPFLAGS -I$DVBINCLUDES" +fi + +if test -z "$DVB_API_VERSION"; then +AC_CHECK_HEADERS(linux/dvb/version.h,[ + AC_LANG_PREPROC_REQUIRE() + AC_REQUIRE([AC_PROG_EGREP]) + AC_LANG_CONFTEST([AC_LANG_SOURCE([[ +#include +version DVB_API_VERSION + ]])]) + DVB_API_VERSION=`(eval "$ac_cpp conftest.$ac_ext") 2>&AS_MESSAGE_LOG_FD | $EGREP "^version" | sed "s,version\ ,,"` + rm -f conftest* + + AC_MSG_NOTICE([found dvb version $DVB_API_VERSION]) +]) +fi + +if test "$DVB_API_VERSION"; then + AC_DEFINE(HAVE_DVB,1,[Define to 1 if you have the dvb includes]) + AC_DEFINE_UNQUOTED(HAVE_DVB_API_VERSION,$DVB_API_VERSION,[Define to the version of the dvb api]) +else + AC_MSG_ERROR([can't find dvb headers]) +fi +]) + +AC_DEFUN([_TUXBOX_APPS_LIB_CONFIG],[ +AC_PATH_PROG($1_CONFIG,$2,no) +if test "$$1_CONFIG" != "no"; then + if test "$TARGET" = "cdk" && check_path "$$1_CONFIG"; then + AC_MSG_$3([could not find a suitable version of $2]); + else + if test "$1" = "CURL"; then + $1_CFLAGS=$($$1_CONFIG --cflags) + $1_LIBS=$($$1_CONFIG --libs) + else + if test "$1" = "FREETYPE"; then + $1_CFLAGS=$($$1_CONFIG --cflags) + $1_LIBS=$($$1_CONFIG --libs) + else + $1_CFLAGS=$($$1_CONFIG --prefix=$targetprefix --cflags) + $1_LIBS=$($$1_CONFIG --prefix=$targetprefix --libs) + fi + fi + fi +fi + +AC_SUBST($1_CFLAGS) +AC_SUBST($1_LIBS) +]) + +AC_DEFUN([TUXBOX_APPS_LIB_CONFIG],[ +_TUXBOX_APPS_LIB_CONFIG($1,$2,ERROR) +if test "$$1_CONFIG" = "no"; then + AC_MSG_ERROR([could not find $2]); +fi +]) + +AC_DEFUN([TUXBOX_APPS_LIB_CONFIG_CHECK],[ +_TUXBOX_APPS_LIB_CONFIG($1,$2,WARN) +]) + +AC_DEFUN([TUXBOX_APPS_PKGCONFIG],[ +m4_pattern_forbid([^_?PKG_[A-Z_]+$]) +m4_pattern_allow([^PKG_CONFIG(_PATH)?$]) +AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])dnl +if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then + AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) +fi +if test x"$PKG_CONFIG" = x"" ; then + AC_MSG_ERROR([could not find pkg-config]); +fi +]) + +AC_DEFUN([_TUXBOX_APPS_LIB_PKGCONFIG],[ +AC_REQUIRE([TUXBOX_APPS_PKGCONFIG]) +AC_MSG_CHECKING(for package $2) +if $PKG_CONFIG --exists "$2" ; then + AC_MSG_RESULT(yes) + $1_CFLAGS=$($PKG_CONFIG --cflags "$2") + $1_LIBS=$($PKG_CONFIG --libs "$2") + $1_EXISTS=yes +else + AC_MSG_RESULT(no) +fi + +AC_SUBST($1_CFLAGS) +AC_SUBST($1_LIBS) +]) + +AC_DEFUN([TUXBOX_APPS_LIB_PKGCONFIG],[ +_TUXBOX_APPS_LIB_PKGCONFIG($1,$2) +if test x"$$1_EXISTS" != xyes; then + AC_MSG_ERROR([could not find package $2]); +fi +]) + +AC_DEFUN([TUXBOX_APPS_LIB_PKGCONFIG_CHECK],[ +_TUXBOX_APPS_LIB_PKGCONFIG($1,$2) +]) + +AC_DEFUN([_TUXBOX_APPS_LIB_SYMBOL],[ +AC_CHECK_LIB($2,$3,HAVE_$1="yes",HAVE_$1="no") +if test "$HAVE_$1" = "yes"; then + $1_LIBS=-l$2 +fi + +AC_SUBST($1_LIBS) +]) + +AC_DEFUN([TUXBOX_APPS_LIB_SYMBOL],[ +_TUXBOX_APPS_LIB_SYMBOL($1,$2,$3,ERROR) +if test "$HAVE_$1" = "no"; then + AC_MSG_ERROR([could not find $2]); +fi +]) + +AC_DEFUN([TUXBOX_APPS_LIB_CONFIG_SYMBOL],[ +_TUXBOX_APPS_LIB_SYMBOL($1,$2,$3,WARN) +]) + +AC_DEFUN([TUXBOX_BOXTYPE],[ +AC_ARG_WITH(boxtype, + [ --with-boxtype valid values: dbox2,tripledragon,dreambox,ipbox,coolstream,spark,azbox,generic], + [case "${withval}" in + dbox2|dreambox|ipbox|tripledragon|coolstream|spark|azbox|generic) + BOXTYPE="$withval" + ;; + dm*) + BOXTYPE="dreambox" + BOXMODEL="$withval" + ;; + *) + AC_MSG_ERROR([bad value $withval for --with-boxtype]) ;; + esac], [BOXTYPE="generic"]) + +AC_ARG_WITH(boxmodel, + [ --with-boxmodel valid for dreambox: dm500, dm500plus, dm600pvr, dm56x0, dm7000, dm7020, dm7025 + valid for ipbox: ip200, ip250, ip350, ip400], + [case "${withval}" in + dm500|dm500plus|dm600pvr|dm56x0|dm7000|dm7020|dm7025) + if test "$BOXTYPE" = "dreambox"; then + BOXMODEL="$withval" + else + AC_MSG_ERROR([unknown model $withval for boxtype $BOXTYPE]) + fi + ;; + ip200|ip250|ip350|ip400) + if test "$BOXTYPE" = "ipbox"; then + BOXMODEL="$withval" + else + AC_MSG_ERROR([unknown model $withval for boxtype $BOXTYPE]) + fi + ;; + raspi) + if test "$BOXTYPE" = "generic"; then + BOXMODEL="$withval" + else + AC_MSG_ERROR([unknown model $withval for boxtype $BOXTYPE]) + fi + ;; + *) + AC_MSG_ERROR([unsupported value $withval for --with-boxmodel]) + ;; + esac], + [if test "$BOXTYPE" = "dreambox" -o "$BOXTYPE" = "ipbox" && test -z "$BOXMODEL"; then + AC_MSG_ERROR([Dreambox/IPBox needs --with-boxmodel]) + fi]) + +AC_SUBST(BOXTYPE) +AC_SUBST(BOXMODEL) + +AM_CONDITIONAL(BOXTYPE_AZBOX, test "$BOXTYPE" = "azbox") +AM_CONDITIONAL(BOXTYPE_DBOX2, test "$BOXTYPE" = "dbox2") +AM_CONDITIONAL(BOXTYPE_TRIPLE, test "$BOXTYPE" = "tripledragon") +AM_CONDITIONAL(BOXTYPE_SPARK, test "$BOXTYPE" = "spark") +AM_CONDITIONAL(BOXTYPE_DREAMBOX, test "$BOXTYPE" = "dreambox") +AM_CONDITIONAL(BOXTYPE_IPBOX, test "$BOXTYPE" = "ipbox") +AM_CONDITIONAL(BOXTYPE_COOL, test "$BOXTYPE" = "coolstream") +AM_CONDITIONAL(BOXTYPE_GENERIC, test "$BOXTYPE" = "generic") + +AM_CONDITIONAL(BOXMODEL_DM500,test "$BOXMODEL" = "dm500") +AM_CONDITIONAL(BOXMODEL_DM500PLUS,test "$BOXMODEL" = "dm500plus") +AM_CONDITIONAL(BOXMODEL_DM600PVR,test "$BOXMODEL" = "dm600pvr") +AM_CONDITIONAL(BOXMODEL_DM56x0,test "$BOXMODEL" = "dm56x0") +AM_CONDITIONAL(BOXMODEL_DM7000,test "$BOXMODEL" = "dm7000" -o "$BOXMODEL" = "dm7020" -o "$BOXMODEL" = "dm7025") + +AM_CONDITIONAL(BOXMODEL_IP200,test "$BOXMODEL" = "ip200") +AM_CONDITIONAL(BOXMODEL_IP250,test "$BOXMODEL" = "ip250") +AM_CONDITIONAL(BOXMODEL_IP350,test "$BOXMODEL" = "ip350") +AM_CONDITIONAL(BOXMODEL_IP400,test "$BOXMODEL" = "ip400") + +AM_CONDITIONAL(BOXMODEL_RASPI,test "$BOXMODEL" = "raspi") + +if test "$BOXTYPE" = "dbox2"; then + AC_DEFINE(HAVE_DBOX_HARDWARE, 1, [building for a dbox2]) +elif test "$BOXTYPE" = "azbox"; then + AC_DEFINE(HAVE_AZBOX_HARDWARE, 1, [building for an azbox]) +elif test "$BOXTYPE" = "tripledragon"; then + AC_DEFINE(HAVE_TRIPLEDRAGON, 1, [building for a tripledragon]) +elif test "$BOXTYPE" = "spark"; then + AC_DEFINE(HAVE_SPARK_HARDWARE, 1, [building for a spark st7111 box]) +elif test "$BOXTYPE" = "dreambox"; then + AC_DEFINE(HAVE_DREAMBOX_HARDWARE, 1, [building for a dreambox]) +elif test "$BOXTYPE" = "ipbox"; then + AC_DEFINE(HAVE_IPBOX_HARDWARE, 1, [building for an ipbox]) +elif test "$BOXTYPE" = "coolstream"; then + AC_DEFINE(HAVE_COOL_HARDWARE, 1, [building for a coolstream]) +elif test "$BOXTYPE" = "generic"; then + AC_DEFINE(HAVE_GENERIC_HARDWARE, 1, [building for a generic device like a standard PC]) +fi + +# TODO: do we need more defines? +if test "$BOXMODEL" = "dm500"; then + AC_DEFINE(BOXMODEL_DM500, 1, [dreambox 500]) +elif test "$BOXMODEL" = "ip200"; then + AC_DEFINE(BOXMODEL_IP200, 1, [ipbox 200]) +elif test "$BOXMODEL" = "ip250"; then + AC_DEFINE(BOXMODEL_IP250, 1, [ipbox 250]) +elif test "$BOXMODEL" = "ip350"; then + AC_DEFINE(BOXMODEL_IP350, 1, [ipbox 350]) +elif test "$BOXMODEL" = "ip400"; then + AC_DEFINE(BOXMODEL_IP400, 1, [ipbox 400]) +elif test "$BOXMODEL" = "raspi"; then + AC_DEFINE(BOXMODEL_RASPI, 1, [Raspberry pi]) +fi +]) + +dnl backward compatiblity +AC_DEFUN([AC_GNU_SOURCE], +[AH_VERBATIM([_GNU_SOURCE], +[/* Enable GNU extensions on systems that have them. */ +#ifndef _GNU_SOURCE +# undef _GNU_SOURCE +#endif])dnl +AC_BEFORE([$0], [AC_COMPILE_IFELSE])dnl +AC_BEFORE([$0], [AC_RUN_IFELSE])dnl +AC_DEFINE([_GNU_SOURCE]) +]) + +AC_DEFUN([AC_PROG_EGREP], +[AC_CACHE_CHECK([for egrep], [ac_cv_prog_egrep], + [if echo a | (grep -E '(a|b)') >/dev/null 2>&1 + then ac_cv_prog_egrep='grep -E' + else ac_cv_prog_egrep='egrep' + fi]) + EGREP=$ac_cv_prog_egrep + AC_SUBST([EGREP]) +]) + diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..4b211a4 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +cd $(dirname $0) +aclocal --force +libtoolize --force +autoconf --force +autoheader --force +automake --add-missing --force-missing --foreign diff --git a/azbox/Makefile.am b/azbox/Makefile.am new file mode 100644 index 0000000..0e6b74f --- /dev/null +++ b/azbox/Makefile.am @@ -0,0 +1,18 @@ +noinst_LTLIBRARIES = libazbox.la + +AM_CPPFLAGS = \ + -I$(top_srcdir)/common + +AM_CXXFLAGS = -fno-rtti -fno-exceptions -fno-strict-aliasing +AM_LDFLAGS = -lpthread + +libazbox_la_SOURCES = \ + hardware_caps.c \ + dmx.cpp \ + video.cpp \ + audio.cpp \ + init.cpp \ + playback.cpp \ + pwrmngr.cpp \ + record.cpp + diff --git a/azbox/audio.cpp b/azbox/audio.cpp new file mode 100644 index 0000000..329b8f9 --- /dev/null +++ b/azbox/audio.cpp @@ -0,0 +1,398 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "audio_lib.h" +#include "lt_debug.h" + +#define AUDIO_DEVICE "/dev/dvb/adapter0/audio0" +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_AUDIO, this, args) +#define lt_info(args...) _lt_info(TRIPLE_DEBUG_AUDIO, this, args) + +#include + +cAudio * audioDecoder = NULL; + +cAudio::cAudio(void *, void *, void *) +{ + fd = -1; + clipfd = -1; + mixer_fd = -1; + openDevice(); + Muted = false; +} + +cAudio::~cAudio(void) +{ + closeDevice(); +} + +void cAudio::openDevice(void) +{ + lt_debug("%s\n", __func__); + if (fd < 0) + { + if ((fd = open(AUDIO_DEVICE, O_RDONLY|O_CLOEXEC)) < 0) + lt_info("openDevice: open failed (%m)\n"); + do_mute(true, false); + } + else + lt_info("openDevice: already open (fd = %d)\n", fd); +} + +void cAudio::closeDevice(void) +{ + lt_debug("%s\n", __func__); + ioctl(fd, AUDIO_CONTINUE); /* enigma2 also does CONTINUE before close... */ + if (fd >= 0) + close(fd); + fd = -1; + if (clipfd >= 0) + close(clipfd); + clipfd = -1; + if (mixer_fd >= 0) + close(mixer_fd); + mixer_fd = -1; +} + +int cAudio::do_mute(bool enable, bool remember) +{ + lt_debug("%s(%d, %d)\n", __func__, enable, remember); + + if (remember) + Muted = enable; +#if 0 + /* does not work? */ + if (ioctl(fd, AUDIO_SET_MUTE, enable) < 0 ) + lt_info("%s: AUDIO_SET_MUTE failed (%m)\n", __func__); +#else + char s[2] = { 0, 0 }; + s[0] = '0' + (int)enable; + proc_put("/proc/stb/audio/j1_mute", s, 2); +#endif + return 0; +} + +int map_volume(const int volume) +{ + unsigned char vol = volume; + if (vol > 100) + vol = 100; + + vol = 63 - vol * 63 / 100; + return vol; +} + + +int cAudio::setVolume(unsigned int left, unsigned int right) +{ + lt_debug("%s(%d, %d)\n", __func__, left, right); + + volume = (left + right) / 2; + if (clipfd != -1 && mixer_fd != -1) { + int tmp = 0; + /* not sure if left / right is correct here, but it is always the same anyways ;-) */ + if (! Muted) + tmp = left << 8 | right; + int ret = ioctl(mixer_fd, MIXER_WRITE(mixer_num), &tmp); + if (ret == -1) + lt_info("%s: MIXER_WRITE(%d),%04x: %m\n", __func__, mixer_num, tmp); + return ret; + } + + audio_mixer_t mixer; + mixer.volume_left = map_volume(left); + mixer.volume_right = map_volume(right); + + if (ioctl(fd, AUDIO_SET_MIXER, &mixer) < 0) + lt_info("%s: AUDIO_SET_MIXER failed (%m)\n", __func__); + + return 0; +} + +int cAudio::Start(void) +{ + lt_debug("%s\n", __func__); + int ret; + ioctl(fd, AUDIO_CONTINUE); + ret = ioctl(fd, AUDIO_PLAY); + return ret; +} + +int cAudio::Stop(void) +{ + lt_debug("%s\n", __func__); + ioctl(fd, AUDIO_STOP); + ioctl(fd, AUDIO_CONTINUE); /* no idea why we have to stop and then continue => enigma2 does it, too */ + return 0; +} + +bool cAudio::Pause(bool /*Pcm*/) +{ + return true; +}; + +void cAudio::SetSyncMode(AVSYNC_TYPE Mode) +{ + lt_debug("%s %d\n", __func__, Mode); + ioctl(fd, AUDIO_SET_AV_SYNC, Mode); +}; + +//AUDIO_ENCODING_AC3 +#define AUDIO_STREAMTYPE_AC3 0 +//AUDIO_ENCODING_MPEG2 +#define AUDIO_STREAMTYPE_MPEG 1 +//AUDIO_ENCODING_DTS +#define AUDIO_STREAMTYPE_DTS 2 + +#define AUDIO_ENCODING_LPCM 2 +#define AUDIO_ENCODING_LPCMA 11 +void cAudio::SetStreamType(AUDIO_FORMAT type) +{ + int bypass = AUDIO_STREAMTYPE_MPEG; + lt_debug("%s %d\n", __func__, type); + StreamType = type; + + switch (type) + { + case AUDIO_FMT_DOLBY_DIGITAL: + bypass = AUDIO_STREAMTYPE_AC3; + break; + case AUDIO_FMT_DTS: + bypass = AUDIO_STREAMTYPE_DTS; + break; + case AUDIO_FMT_MPEG: + default: + break; + } + + // Normaly the encoding should be set using AUDIO_SET_ENCODING + // But as we implemented the behavior to bypass (cause of e2) this is correct here + if (ioctl(fd, AUDIO_SET_BYPASS_MODE, bypass) < 0) + lt_info("%s: AUDIO_SET_BYPASS_MODE failed (%m)\n", __func__); +}; + +int cAudio::setChannel(int channel) +{ + return 0; +}; + +int cAudio::PrepareClipPlay(int ch, int srate, int bits, int little_endian) +{ + int fmt; + unsigned int devmask, stereo, usable; + const char *dsp_dev = getenv("DSP_DEVICE"); + const char *mix_dev = getenv("MIX_DEVICE"); + lt_debug("%s ch %d srate %d bits %d le %d\n", __FUNCTION__, ch, srate, bits, little_endian); + if (clipfd >= 0) { + lt_info("%s: clipfd already opened (%d)\n", __FUNCTION__, clipfd); + return -1; + } + mixer_num = -1; + mixer_fd = -1; + /* a different DSP device can be given with DSP_DEVICE and MIX_DEVICE + * if this device cannot be opened, we fall back to the internal OSS device + * Example: + * modprobe snd-usb-audio + * export DSP_DEVICE=/dev/sound/dsp2 + * export MIX_DEVICE=/dev/sound/mixer2 + * neutrino + */ + if ((!dsp_dev) || (access(dsp_dev, W_OK))) { + if (dsp_dev) + lt_info("%s: DSP_DEVICE is set (%s) but cannot be opened," + " fall back to /dev/dsp1\n", __func__, dsp_dev); + dsp_dev = "/dev/dsp1"; + } + lt_info("%s: dsp_dev %s mix_dev %s\n", __func__, dsp_dev, mix_dev); /* NULL mix_dev is ok */ + /* the tdoss dsp driver seems to work only on the second open(). really. */ + clipfd = open(dsp_dev, O_WRONLY|O_CLOEXEC); + if (clipfd < 0) { + lt_info("%s open %s: %m\n", dsp_dev, __FUNCTION__); + return -1; + } + /* no idea if we ever get little_endian == 0 */ + if (little_endian) + fmt = AFMT_S16_BE; + else + fmt = AFMT_S16_LE; + if (ioctl(clipfd, SNDCTL_DSP_SETFMT, &fmt)) + perror("SNDCTL_DSP_SETFMT"); + if (ioctl(clipfd, SNDCTL_DSP_CHANNELS, &ch)) + perror("SNDCTL_DSP_CHANNELS"); + if (ioctl(clipfd, SNDCTL_DSP_SPEED, &srate)) + perror("SNDCTL_DSP_SPEED"); + if (ioctl(clipfd, SNDCTL_DSP_RESET)) + perror("SNDCTL_DSP_RESET"); + + if (!mix_dev) + return 0; + + mixer_fd = open(mix_dev, O_RDWR|O_CLOEXEC); + if (mixer_fd < 0) { + lt_info("%s: open mixer %s failed (%m)\n", __func__, mix_dev); + /* not a real error */ + return 0; + } + if (ioctl(mixer_fd, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) { + lt_info("%s: SOUND_MIXER_READ_DEVMASK %m\n", __func__); + devmask = 0; + } + if (ioctl(mixer_fd, SOUND_MIXER_READ_STEREODEVS, &stereo) == -1) { + lt_info("%s: SOUND_MIXER_READ_STEREODEVS %m\n", __func__); + stereo = 0; + } + usable = devmask & stereo; + if (usable == 0) { + lt_info("%s: devmask: %08x stereo: %08x, no usable dev :-(\n", + __func__, devmask, stereo); + close(mixer_fd); + mixer_fd = -1; + return 0; /* TODO: should we treat this as error? */ + } + /* __builtin_popcount needs GCC, it counts the set bits... */ + if (__builtin_popcount (usable) != 1) { + /* TODO: this code is not yet tested as I have only single-mixer devices... */ + lt_info("%s: more than one mixer control: devmask %08x stereo %08x\n" + "%s: querying MIX_NUMBER environment variable...\n", + __func__, devmask, stereo, __func__); + const char *tmp = getenv("MIX_NUMBER"); + if (tmp) + mixer_num = atoi(tmp); + lt_info("%s: mixer_num is %d -> device %08x\n", + __func__, mixer_num, (mixer_num >= 0) ? (1 << mixer_num) : 0); + /* no error checking, you'd better know what you are doing... */ + } else { + mixer_num = 0; + while (!(usable & 0x01)) { + mixer_num++; + usable >>= 1; + } + } + setVolume(volume, volume); + + return 0; +}; + +int cAudio::WriteClip(unsigned char *buffer, int size) +{ + int ret; + // lt_debug("cAudio::%s\n", __FUNCTION__); + if (clipfd <= 0) { + lt_info("%s: clipfd not yet opened\n", __FUNCTION__); + return -1; + } + ret = write(clipfd, buffer, size); + if (ret < 0) + lt_info("%s: write error (%m)\n", __FUNCTION__); + return ret; +}; + +int cAudio::StopClip() +{ + lt_debug("%s\n", __FUNCTION__); + if (clipfd <= 0) { + lt_info("%s: clipfd not yet opened\n", __FUNCTION__); + return -1; + } + close(clipfd); + clipfd = -1; + if (mixer_fd >= 0) + close(mixer_fd); + mixer_fd = -1; + setVolume(volume, volume); + return 0; +}; + +void cAudio::getAudioInfo(int &type, int &layer, int &freq, int &bitrate, int &mode) +{ + lt_debug("%s\n", __FUNCTION__); + type = 0; + layer = 0; + freq = 0; + bitrate = 0; + mode = 0; +#if 0 + unsigned int atype; + static const int freq_mpg[] = {44100, 48000, 32000, 0}; + static const int freq_ac3[] = {48000, 44100, 32000, 0}; + scratchl2 i; + if (ioctl(fd, MPEG_AUD_GET_DECTYP, &atype) < 0) + perror("cAudio::getAudioInfo MPEG_AUD_GET_DECTYP"); + if (ioctl(fd, MPEG_AUD_GET_STATUS, &i) < 0) + perror("cAudio::getAudioInfo MPEG_AUD_GET_STATUS"); + + type = atype; +#if 0 +/* this does not work, some of the values are negative?? */ + AMPEGStatus A; + memcpy(&A, &i.word00, sizeof(i.word00)); + layer = A.audio_mpeg_layer; + mode = A.audio_mpeg_mode; + bitrate = A.audio_mpeg_bitrate; + switch(A.audio_mpeg_frequency) +#endif + /* layer and bitrate are not used anyway... */ + layer = 0; //(i.word00 >> 17) & 3; + bitrate = 0; //(i.word00 >> 12) & 3; + switch (type) + { + case 0: /* MPEG */ + mode = (i.word00 >> 6) & 3; + freq = freq_mpg[(i.word00 >> 10) & 3]; + break; + case 1: /* AC3 */ + mode = (i.word00 >> 28) & 7; + freq = freq_ac3[(i.word00 >> 16) & 3]; + break; + default: + mode = 0; + freq = 0; + } + //fprintf(stderr, "type: %d layer: %d freq: %d bitrate: %d mode: %d\n", type, layer, freq, bitrate, mode); +#endif +}; + +void cAudio::SetSRS(int /*iq_enable*/, int /*nmgr_enable*/, int /*iq_mode*/, int /*iq_level*/) +{ + lt_debug("%s\n", __FUNCTION__); +}; + +void cAudio::SetHdmiDD(bool enable) +{ + lt_debug("%s %d\n", __func__, enable); +}; + +void cAudio::SetSpdifDD(bool enable) +{ + lt_debug("%s %d\n", __func__, enable); + setBypassMode(!enable); +}; + +void cAudio::ScheduleMute(bool On) +{ + lt_debug("%s %d\n", __FUNCTION__, On); +}; + +void cAudio::EnableAnalogOut(bool enable) +{ + lt_debug("%s %d\n", __FUNCTION__, enable); +}; + +#define AUDIO_BYPASS_ON 0 +#define AUDIO_BYPASS_OFF 1 +void cAudio::setBypassMode(bool disable) +{ + lt_debug("%s %d\n", __func__, disable); + int mode = disable ? AUDIO_BYPASS_OFF : AUDIO_BYPASS_ON; + if (ioctl(fd, AUDIO_SET_BYPASS_MODE, mode) < 0) + lt_info("%s AUDIO_SET_BYPASS_MODE %d: %m\n", __func__, mode); + return; +} diff --git a/azbox/audio_lib.h b/azbox/audio_lib.h new file mode 100644 index 0000000..871060d --- /dev/null +++ b/azbox/audio_lib.h @@ -0,0 +1,98 @@ +/* public header file */ + +#ifndef _AUDIO_LIB_H_ +#define _AUDIO_LIB_H_ + +#include "../common/cs_types.h" + +typedef enum +{ + AUDIO_SYNC_WITH_PTS, + AUDIO_NO_SYNC, + AUDIO_SYNC_AUDIO_MASTER +} AUDIO_SYNC_MODE; + +typedef enum { + HDMI_ENCODED_OFF, + HDMI_ENCODED_AUTO, + HDMI_ENCODED_FORCED +} HDMI_ENCODED_MODE; + +typedef enum +{ + AUDIO_FMT_AUTO = 0, + AUDIO_FMT_MPEG, + AUDIO_FMT_MP3, + AUDIO_FMT_DOLBY_DIGITAL, + AUDIO_FMT_BASIC = AUDIO_FMT_DOLBY_DIGITAL, + AUDIO_FMT_AAC, + AUDIO_FMT_AAC_PLUS, + AUDIO_FMT_DD_PLUS, + AUDIO_FMT_DTS, + AUDIO_FMT_AVS, + AUDIO_FMT_MLP, + AUDIO_FMT_WMA, + AUDIO_FMT_MPG1, // TD only. For Movieplayer / cPlayback + AUDIO_FMT_ADVANCED = AUDIO_FMT_MLP +} AUDIO_FORMAT; + +class cAudio +{ + friend class cPlayback; + private: + int fd; + bool Muted; + + int clipfd; /* for pcm playback */ + int mixer_fd; /* if we are using the OSS mixer */ + int mixer_num; /* oss mixer to use, if any */ + + AUDIO_FORMAT StreamType; + AUDIO_SYNC_MODE SyncMode; + bool started; + + int volume; + + void openDevice(void); + void closeDevice(void); + + int do_mute(bool enable, bool remember); + void setBypassMode(bool disable); + public: + /* construct & destruct */ + cAudio(void *, void *, void *); + ~cAudio(void); + + void *GetHandle() { return NULL; }; + /* shut up */ + int mute(bool remember = true) { return do_mute(true, remember); }; + int unmute(bool remember = true) { return do_mute(false, remember); }; + + /* volume, min = 0, max = 255 */ + int setVolume(unsigned int left, unsigned int right); + int getVolume(void) { return volume;} + bool getMuteStatus(void) { return Muted; }; + + /* start and stop audio */ + int Start(void); + int Stop(void); + bool Pause(bool Pcm = true); + void SetStreamType(AUDIO_FORMAT type); + void SetSyncMode(AVSYNC_TYPE Mode); + + /* select channels */ + int setChannel(int channel); + int PrepareClipPlay(int uNoOfChannels, int uSampleRate, int uBitsPerSample, int bLittleEndian); + int WriteClip(unsigned char * buffer, int size); + int StopClip(); + void getAudioInfo(int &type, int &layer, int& freq, int &bitrate, int &mode); + void SetSRS(int iq_enable, int nmgr_enable, int iq_mode, int iq_level); + bool IsHdmiDDSupported(); + void SetHdmiDD(bool enable); + void SetSpdifDD(bool enable); + void ScheduleMute(bool On); + void EnableAnalogOut(bool enable); +}; + +#endif + diff --git a/azbox/cs_api.h b/azbox/cs_api.h new file mode 120000 index 0000000..a794ffd --- /dev/null +++ b/azbox/cs_api.h @@ -0,0 +1 @@ +../libspark/cs_api.h \ No newline at end of file diff --git a/azbox/dmx.cpp b/azbox/dmx.cpp new file mode 100644 index 0000000..cacd823 --- /dev/null +++ b/azbox/dmx.cpp @@ -0,0 +1,523 @@ +/* + * cDemux implementation for azbox receivers (tested on azbox me and minime) + * + * derived from libtriple/dmx_td.cpp + * + * (C) 2010-2013 Stefan Seyfried + * + * 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 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "dmx_lib.h" +#include "lt_debug.h" + +/* Ugh... see comment in destructor for details... */ +#include "video_lib.h" +extern cVideo *videoDecoder; + +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_DEMUX, this, args) +#define lt_info(args...) _lt_info(TRIPLE_DEBUG_DEMUX, this, args) +#define lt_info_c(args...) _lt_info(TRIPLE_DEBUG_DEMUX, NULL, args) + +#define dmx_err(_errfmt, _errstr, _revents) do { \ + uint16_t _pid = (uint16_t)-1; uint16_t _f = 0;\ + if (dmx_type == DMX_PSI_CHANNEL) { \ + _pid = s_flt.pid; _f = s_flt.filter.filter[0]; \ + } else { \ + _pid = p_flt.pid; \ + }; \ + lt_info("%s " _errfmt " fd:%d, ev:0x%x %s pid:0x%04hx flt:0x%02hx\n", \ + __func__, _errstr, fd, _revents, DMX_T[dmx_type], _pid, _f); \ +} while(0); + +cDemux *videoDemux = NULL; +cDemux *audioDemux = NULL; +//cDemux *pcrDemux = NULL; + +static const char *DMX_T[] = { + "DMX_INVALID", + "DMX_VIDEO", + "DMX_AUDIO", + "DMX_PES", + "DMX_PSI", + "DMX_PIP", + "DMX_TP", + "DMX_PCR" +}; + +/* map the device numbers. for now only demux0 is used */ +static const char *devname[] = { + "/dev/dvb/adapter0/demux0", + "/dev/dvb/adapter0/demux0", + "/dev/dvb/adapter0/demux0" +}; + +/* uuuugly */ +static int dmx_tp_count = 0; +#define MAX_TS_COUNT 8 + +cDemux::cDemux(int n) +{ + if (n < 0 || n > 2) + { + lt_info("%s ERROR: n invalid (%d)\n", __FUNCTION__, n); + num = 0; + } + else + num = n; + fd = -1; + measure = false; + last_measure = 0; + last_data = 0; +} + +cDemux::~cDemux() +{ + lt_debug("%s #%d fd: %d\n", __FUNCTION__, num, fd); + Close(); + /* in zapit.cpp, videoDemux is deleted after videoDecoder + * in the video watchdog, we access videoDecoder + * the thread still runs after videoDecoder has been deleted + * => set videoDecoder to NULL here to make the check in the + * watchdog thread pick this up. + * This is ugly, but it saves me from changing neutrino + * + * if the delete order in neutrino will ever be changed, this + * will blow up badly :-( + */ + if (dmx_type == DMX_VIDEO_CHANNEL) + videoDecoder = NULL; +} + +bool cDemux::Open(DMX_CHANNEL_TYPE pes_type, void * /*hVideoBuffer*/, int uBufferSize) +{ + int devnum = num; + int flags = O_RDWR|O_CLOEXEC; + if (fd > -1) + lt_info("%s FD ALREADY OPENED? fd = %d\n", __FUNCTION__, fd); + + if (pes_type != DMX_PSI_CHANNEL) + flags |= O_NONBLOCK; + + fd = open(devname[devnum], flags); + if (fd < 0) + { + lt_info("%s %s: %m\n", __FUNCTION__, devname[devnum]); + return false; + } + lt_debug("%s #%d pes_type: %s(%d), uBufferSize: %d fd: %d\n", __func__, + num, DMX_T[pes_type], pes_type, uBufferSize, fd); + + dmx_type = pes_type; +#if 0 + if (!pesfds.empty()) + { + lt_info("%s ERROR! pesfds not empty!\n", __FUNCTION__); /* TODO: error handling */ + return false; + } + int n = DMX_SOURCE_FRONT0; + if (ioctl(fd, DMX_SET_SOURCE, &n) < 0) + lt_info("%s DMX_SET_SOURCE failed!\n", __func__); +#endif + if (uBufferSize > 0) + { + /* probably uBufferSize == 0 means "use default size". TODO: find a reasonable default */ + if (ioctl(fd, DMX_SET_BUFFER_SIZE, uBufferSize) < 0) + lt_info("%s DMX_SET_BUFFER_SIZE failed (%m)\n", __func__); + } + buffersize = uBufferSize; + + return true; +} + +void cDemux::Close(void) +{ + lt_debug("%s #%d, fd = %d\n", __FUNCTION__, num, fd); + if (fd < 0) + { + lt_info("%s #%d: not open!\n", __FUNCTION__, num); + return; + } + + pesfds.clear(); + ioctl(fd, DMX_STOP); + close(fd); + fd = -1; + if (measure) + return; + if (dmx_type == DMX_TP_CHANNEL) + { + dmx_tp_count--; + if (dmx_tp_count < 0) + { + lt_info("%s dmx_tp_count < 0!!\n", __func__); + dmx_tp_count = 0; + } + } +} + +bool cDemux::Start(bool) +{ + lt_debug("%s #%d fd: %d type: %s\n", __func__, num, fd, DMX_T[dmx_type]); + if (fd < 0) + { + lt_info("%s #%d: not open!\n", __FUNCTION__, num); + return false; + } + ioctl(fd, DMX_START); + return true; +} + +bool cDemux::Stop(void) +{ + lt_debug("%s #%d fd: %d type: %s\n", __func__, num, fd, DMX_T[dmx_type]); + if (fd < 0) + { + lt_info("%s #%d: not open!\n", __FUNCTION__, num); + return false; + } + ioctl(fd, DMX_STOP); + return true; +} + +int cDemux::Read(unsigned char *buff, int len, int timeout) +{ +#if 0 + if (len != 4095 && timeout != 100) + fprintf(stderr, "cDemux::%s #%d fd: %d type: %s len: %d timeout: %d\n", + __FUNCTION__, num, fd, DMX_T[dmx_type], len, timeout); +#endif + int rc; + int to = timeout; + /* using a one-dimensional array seems to avoid strange segfaults / memory corruption?? */ + struct pollfd ufds[1]; + ufds[0].fd = fd; + ufds[0].events = POLLIN|POLLPRI|POLLERR; + ufds[0].revents = 0; + + /* hack: if the frontend loses and regains lock, the demuxer often will not + * return from read(), so as a "emergency exit" for e.g. NIT scan, set a (long) + * timeout here */ + if (dmx_type == DMX_PSI_CHANNEL && timeout <= 0) + to = 60 * 1000; + + if (to > 0) + { + retry: + rc = ::poll(ufds, 1, to); + if (!rc) + { + if (timeout == 0) /* we took the emergency exit */ + { + dmx_err("timed out for timeout=0!, %s", "", 0); + return -1; /* this timeout is an error */ + } + return 0; // timeout + } + else if (rc < 0) + { + dmx_err("poll: %s,", strerror(errno), 0) + //lt_info("%s poll: %m\n", __FUNCTION__); + /* happens, when running under gdb... */ + if (errno == EINTR) + goto retry; + return -1; + } +#if 0 + if (ufds.revents & POLLERR) /* POLLERR means buffer error, i.e. buffer overflow */ + { + dmx_err("received %s,", "POLLERR", ufds.revents); + /* this seems to happen sometimes at recording start, without bad effects */ + return 0; + } +#endif + if (ufds[0].revents & POLLHUP) /* we get POLLHUP if e.g. a too big DMX_BUFFER_SIZE was set */ + { + dmx_err("received %s,", "POLLHUP", ufds[0].revents); + return -1; + } + if (!(ufds[0].revents & POLLIN)) /* we requested POLLIN but did not get it? */ + { + dmx_err("received %s, please report!", "POLLIN", ufds[0].revents); + return 0; + } + } + + rc = ::read(fd, buff, len); + //fprintf(stderr, "fd %d ret: %d\n", fd, rc); + if (rc < 0) + dmx_err("read: %s", strerror(errno), 0); + + return rc; +} + +bool cDemux::sectionFilter(unsigned short pid, const unsigned char * const filter, + const unsigned char * const mask, int len, int timeout, + const unsigned char * const negmask) +{ + memset(&s_flt, 0, sizeof(s_flt)); + + if (len > DMX_FILTER_SIZE) + { + lt_info("%s #%d: len too long: %d, DMX_FILTER_SIZE %d\n", __func__, num, len, DMX_FILTER_SIZE); + len = DMX_FILTER_SIZE; + } + s_flt.pid = pid; + s_flt.timeout = timeout; + memcpy(s_flt.filter.filter, filter, len); + memcpy(s_flt.filter.mask, mask, len); + if (negmask != NULL) + memcpy(s_flt.filter.mode, negmask, len); + + s_flt.flags = DMX_IMMEDIATE_START|DMX_CHECK_CRC; + + int to = 0; + switch (filter[0]) { + case 0x00: /* program_association_section */ + to = 2000; + break; + case 0x01: /* conditional_access_section */ + to = 6000; + break; + case 0x02: /* program_map_section */ + to = 1500; + break; + case 0x03: /* transport_stream_description_section */ + to = 10000; + break; + /* 0x04 - 0x3F: reserved */ + case 0x40: /* network_information_section - actual_network */ + to = 10000; + break; + case 0x41: /* network_information_section - other_network */ + to = 15000; + break; + case 0x42: /* service_description_section - actual_transport_stream */ + to = 10000; + break; + /* 0x43 - 0x45: reserved for future use */ + case 0x46: /* service_description_section - other_transport_stream */ + to = 10000; + break; + /* 0x47 - 0x49: reserved for future use */ + case 0x4A: /* bouquet_association_section */ + to = 11000; + break; + /* 0x4B - 0x4D: reserved for future use */ + case 0x4E: /* event_information_section - actual_transport_stream, present/following */ + to = 2000; + break; + case 0x4F: /* event_information_section - other_transport_stream, present/following */ + to = 10000; + break; + /* 0x50 - 0x5F: event_information_section - actual_transport_stream, schedule */ + /* 0x60 - 0x6F: event_information_section - other_transport_stream, schedule */ + case 0x70: /* time_date_section */ + s_flt.flags &= ~DMX_CHECK_CRC; /* section has no CRC */ + s_flt.flags |= DMX_ONESHOT; + //s_flt.pid = 0x0014; + to = 30000; + break; + case 0x71: /* running_status_section */ + s_flt.flags &= ~DMX_CHECK_CRC; /* section has no CRC */ + to = 0; + break; + case 0x72: /* stuffing_section */ + s_flt.flags &= ~DMX_CHECK_CRC; /* section has no CRC */ + to = 0; + break; + case 0x73: /* time_offset_section */ + s_flt.flags |= DMX_ONESHOT; + //s_flt.pid = 0x0014; + to = 30000; + break; + /* 0x74 - 0x7D: reserved for future use */ + case 0x7E: /* discontinuity_information_section */ + s_flt.flags &= ~DMX_CHECK_CRC; /* section has no CRC */ + to = 0; + break; + case 0x7F: /* selection_information_section */ + to = 0; + break; + /* 0x80 - 0x8F: ca_message_section */ + /* 0x90 - 0xFE: user defined */ + /* 0xFF: reserved */ + default: + break; +// return -1; + } + /* the negmask == NULL is a hack: the users of negmask are PMT-update + * and sectionsd EIT-Version change. And they really want no timeout + * if timeout == 0 instead of "default timeout" */ + if (timeout == 0 && negmask == NULL) + s_flt.timeout = to; + + lt_debug("%s #%d pid:0x%04hx fd:%d type:%s len:%d to:%d flags:%x flt[0]:%02x\n", __func__, num, + pid, fd, DMX_T[dmx_type], len, s_flt.timeout,s_flt.flags, s_flt.filter.filter[0]); +#if 0 + fprintf(stderr,"filt: ");for(int i=0;i= 0x0002 && pid <= 0x000f) || pid >= 0x1fff) + return false; + + lt_debug("%s #%d pid: 0x%04hx fd: %d type: %s\n", __FUNCTION__, num, pid, fd, DMX_T[dmx_type]); + + memset(&p_flt, 0, sizeof(p_flt)); + p_flt.pid = pid; + p_flt.output = DMX_OUT_DECODER; + p_flt.input = DMX_IN_FRONTEND; + + switch (dmx_type) { + case DMX_PCR_ONLY_CHANNEL: + p_flt.pes_type = DMX_PES_PCR; + break; + case DMX_AUDIO_CHANNEL: + p_flt.pes_type = DMX_PES_AUDIO; + break; + case DMX_VIDEO_CHANNEL: + p_flt.pes_type = DMX_PES_VIDEO; + break; + case DMX_PES_CHANNEL: + p_flt.pes_type = DMX_PES_OTHER; + p_flt.output = DMX_OUT_TAP; + break; + case DMX_TP_CHANNEL: + p_flt.pes_type = DMX_PES_OTHER; + p_flt.output = DMX_OUT_TSDEMUX_TAP; + break; + default: + lt_info("%s #%d invalid dmx_type %d!\n", __func__, num, dmx_type); + return false; + } + return (ioctl(fd, DMX_SET_PES_FILTER, &p_flt) >= 0); +} + +void cDemux::SetSyncMode(AVSYNC_TYPE /*mode*/) +{ + lt_debug("%s #%d\n", __FUNCTION__, num); +} + +void *cDemux::getBuffer() +{ + lt_debug("%s #%d\n", __FUNCTION__, num); + return NULL; +} + +void *cDemux::getChannel() +{ + lt_debug("%s #%d\n", __FUNCTION__, num); + return NULL; +} + +bool cDemux::addPid(unsigned short Pid) +{ + lt_debug("%s: pid 0x%04hx\n", __func__, Pid); + pes_pids pfd; + int ret; + if (dmx_type != DMX_TP_CHANNEL) + { + lt_info("%s pes_type %s not implemented yet! pid=%hx\n", __FUNCTION__, DMX_T[dmx_type], Pid); + return false; + } + if (fd == -1) + lt_info("%s bucketfd not yet opened? pid=%hx\n", __FUNCTION__, Pid); + pfd.fd = fd; /* dummy */ + pfd.pid = Pid; + pesfds.push_back(pfd); + ret = (ioctl(fd, DMX_ADD_PID, &Pid)); + if (ret < 0) + lt_info("%s: DMX_ADD_PID (%m)\n", __func__); + return (ret != -1); +} + +void cDemux::removePid(unsigned short Pid) +{ + if (dmx_type != DMX_TP_CHANNEL) + { + lt_info("%s pes_type %s not implemented yet! pid=%hx\n", __FUNCTION__, DMX_T[dmx_type], Pid); + return; + } + for (std::vector::iterator i = pesfds.begin(); i != pesfds.end(); ++i) + { + if ((*i).pid == Pid) { + lt_debug("removePid: removing demux fd %d pid 0x%04x\n", fd, Pid); + if (ioctl(fd, DMX_REMOVE_PID, Pid) < 0) + lt_info("%s: (DMX_REMOVE_PID, 0x%04hx): %m\n", __func__, Pid); + pesfds.erase(i); + return; /* TODO: what if the same PID is there multiple times */ + } + } + lt_info("%s pid 0x%04x not found\n", __FUNCTION__, Pid); +} + +void cDemux::getSTC(int64_t * STC) +{ + /* apparently I can only get the PTS of the video decoder, + * but that's good enough for dvbsub */ + lt_debug("%s #%d\n", __func__, num); + int64_t pts = 0; + if (videoDecoder) + pts = videoDecoder->GetPTS(); + *STC = pts; +} + +int cDemux::getUnit(void) +{ + lt_debug("%s #%d\n", __FUNCTION__, num); + /* just guessed that this is the right thing to do. + right now this is only used by the CA code which is stubbed out + anyway */ + return num; +} + +bool cDemux::SetSource(int unit, int source) +{ + lt_info_c("%s(%d, %d): not implemented yet\n", __func__, unit, source); + return true; +} + +int cDemux::GetSource(int unit) +{ + lt_info_c("%s(%d): not implemented yet\n", __func__, unit); + return 0; +} diff --git a/azbox/dmx_cs.h b/azbox/dmx_cs.h new file mode 100644 index 0000000..175d8cb --- /dev/null +++ b/azbox/dmx_cs.h @@ -0,0 +1 @@ +#include "dmx_lib.h" diff --git a/azbox/dmx_lib.h b/azbox/dmx_lib.h new file mode 100644 index 0000000..754511e --- /dev/null +++ b/azbox/dmx_lib.h @@ -0,0 +1,70 @@ +#ifndef __DEMUX_TD_H +#define __DEMUX_TD_H + +#include +#include +#include +#include +#include +#include "../common/cs_types.h" + +#define MAX_DMX_UNITS 4 + +typedef enum +{ + DMX_INVALID = 0, + DMX_VIDEO_CHANNEL = 1, + DMX_AUDIO_CHANNEL, + DMX_PES_CHANNEL, + DMX_PSI_CHANNEL, + DMX_PIP_CHANNEL, + DMX_TP_CHANNEL, + DMX_PCR_ONLY_CHANNEL +} DMX_CHANNEL_TYPE; + +typedef struct +{ + int fd; + unsigned short pid; +} pes_pids; + +class cDemux +{ + private: + int num; + int fd; + int buffersize; + bool measure; + uint64_t last_measure, last_data; + DMX_CHANNEL_TYPE dmx_type; + std::vector pesfds; + struct dmx_sct_filter_params s_flt; + struct dmx_pes_filter_params p_flt; + public: + + bool Open(DMX_CHANNEL_TYPE pes_type, void * x = NULL, int y = 0); + void Close(void); + bool Start(bool record = false); + bool Stop(void); + int Read(unsigned char *buff, int len, int Timeout = 0); + bool sectionFilter(unsigned short pid, const unsigned char * const filter, const unsigned char * const mask, int len, int Timeout = 0, const unsigned char * const negmask = NULL); + bool pesFilter(const unsigned short pid); + void SetSyncMode(AVSYNC_TYPE mode); + void * getBuffer(); + void * getChannel(); + DMX_CHANNEL_TYPE getChannelType(void) { return dmx_type; }; + bool addPid(unsigned short pid); + void getSTC(int64_t * STC); + int getUnit(void); + static bool SetSource(int unit, int source); + static int GetSource(int unit); + // TD only functions + int getFD(void) { return fd; }; /* needed by cPlayback class */ + void removePid(unsigned short Pid); /* needed by cRecord class */ + std::vector getPesPids(void) { return pesfds; }; + // + cDemux(int num = 0); + ~cDemux(); +}; + +#endif //__DEMUX_H diff --git a/azbox/e2mruainclude.h b/azbox/e2mruainclude.h new file mode 100644 index 0000000..27a1859 --- /dev/null +++ b/azbox/e2mruainclude.h @@ -0,0 +1,83 @@ +//Additional Azbox +enum key_command { +KEY_COMMAND_QUIT_ALL = 100, +KEY_COMMAND_QUIT, +KEY_COMMAND_PLAY, +KEY_COMMAND_PAUSE, +KEY_COMMAND_RESUME, +KEY_COMMAND_STOP, +KEY_COMMAND_SEEK_TO_TIME, +KEY_COMMAND_SEEK_TO_PERCENT, +KEY_COMMAND_NEXT_PICT, +KEY_COMMAND_FAST_FWD_ALL_FRAMES, +KEY_COMMAND_SLOW_FWD_ALL_FRAMES, +KEY_COMMAND_IFRAMES_FWD, +KEY_COMMAND_IFRAMES_BWD, +KEY_COMMAND_SILENT_FWD, +KEY_COMMAND_SILENT_BWD, +KEY_COMMAND_SWITCH_VIDEO, +KEY_COMMAND_SWITCH_AUDIO, +KEY_COMMAND_SWITCH_PROGRAM, +KEY_COMMAND_SWITCH_SUBS, +KEY_COMMAND_SWITCH_MULTICAST, +KEY_COMMAND_APPLY_AV_DELAY, +KEY_COMMAND_SUBS_CHANGE_DELAY, +KEY_COMMAND_SUBS_INCREASE_FONT_SIZE, +KEY_COMMAND_SUBS_DECREASE_FONT_SIZE, +KEY_COMMAND_SUBS_INCREASE_POS_Y, +KEY_COMMAND_SUBS_DECREASE_POS_Y, +KEY_COMMAND_SUBS_SWITCH_ENCODING, +KEY_COMMAND_SUBS_RESET_ALL, +KEY_COMMAND_SUBS_CHANGE_COLOR, +KEY_COMMAND_DEBUG, +KEY_COMMAND_PRINT_INFO, +KEY_COMMAND_FULL_SCREEN, +KEY_COMMAND_HALF_SCREEN, +KEY_COMMAND_INCREASE_SIZE, +KEY_COMMAND_DECREASE_SIZE, +KEY_COMMAND_MOVE_LEFT, +KEY_COMMAND_MOVE_RIGHT, +KEY_COMMAND_MOVE_TOP, +KEY_COMMAND_MOVE_BOTTOM, +KEY_COMMAND_NONLINEAR_WIDTH, +KEY_COMMAND_NONLINEAR_LEVEL, +KEY_COMMAND_SWITCH_SCALER, +KEY_COMMAND_HELP, +KEY_COMMAND_FAST_FWD_WITH_AUDIO, +KEY_COMMAND_SLOW_FWD_WITH_AUDIO, +KEY_COMMAND_PRINT_TXT, +SPECIAL_KEY_COMMAND_IFRAMES_FWD, +SPECIAL_KEY_COMMAND_IFRAMES_BWD, +SPECIAL_KEY_COMMAND_NEXT_AUDIO, +SPECIAL_KEY_COMMAND_NEXT_SUBS, +}; + +enum custom_command { +CUSTOM_COMMAND_GETLENGTH = 200, +CUSTOM_COMMAND_GETPOSITION, +CUSTOM_COMMAND_AUDIOGETPOSITION, +CUSTOM_COMMAND_SEEK_RELATIVE_FWD, +CUSTOM_COMMAND_SEEK_RELATIVE_BWD, +CUSTOM_COMMAND_SUBS_COUNT, +CUSTOM_COMMAND_GET_SUB_BY_ID, +CUSTOM_COMMAND_AUDIO_COUNT, +CUSTOM_COMMAND_GET_AUDIO_BY_ID, +CUSTOM_COMMAND_AUDIO_CUR_STREAM, +CUSTOM_COMMAND_SUBS_CUR_STREAM, +CUSTOM_COMMAND_TRICK_SEEK, +CUSTOM_COMMAND_SET_SUB_SIZE, +CUSTOM_COMMAND_SET_SUB_ENCODING, +CUSTOM_COMMAND_SET_SUB_POS, +}; + +enum event_msg { +EVENT_MSG_FDOPEN = 300, +EVENT_MSG_PLAYBACK_STARTED, +EVENT_MSG_STOPPED, +EVENT_MSG_PAUSED, +EVENT_MSG_BUFFERING, +EVENT_MSG_EOS, +EVENT_MSG_SUB_CHANGED, +}; + +//int fd_cmd, fd_in, fd_out, fd_event, msg; diff --git a/azbox/hardware_caps.c b/azbox/hardware_caps.c new file mode 100644 index 0000000..6489378 --- /dev/null +++ b/azbox/hardware_caps.c @@ -0,0 +1,50 @@ +/* + * determine the capabilities of the hardware. + * part of libstb-hal + * + * (C) 2010-2012 Stefan Seyfried + * + * License: GPL v2 or later + */ + +#include +#include +#include +#include +#include +#include +#include + +static int initialized = 0; +static hw_caps_t caps; + +hw_caps_t *get_hwcaps(void) +{ + if (initialized) + return ∩︀ + + memset(&caps, 0, sizeof(hw_caps_t)); + + initialized = 1; + caps.can_shutdown = 1; + caps.display_type = HW_DISPLAY_LINE_TEXT; + caps.has_HDMI = 1; + caps.display_xres = 8; + strcpy(caps.boxvendor, "AZBox"); + const char *tmp; + char buf[64]; + int len = -1; + int fd = open("/proc/stb/info/model", O_RDONLY); + if (fd != -1) { + len = read(fd, buf, sizeof(buf) - 1); + close(fd); + } + if (len > 0) { + buf[len] = 0; + strcpy(caps.boxname, buf); + } + else + strcpy(caps.boxname, "(unknown model)"); + + return ∩︀ +} diff --git a/azbox/init.cpp b/azbox/init.cpp new file mode 100644 index 0000000..273cc04 --- /dev/null +++ b/azbox/init.cpp @@ -0,0 +1,21 @@ +#include +#include "init_lib.h" +#include "lt_debug.h" +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_INIT, NULL, args) +#define lt_info(args...) _lt_info(TRIPLE_DEBUG_INIT, NULL, args) + +static bool initialized = false; + +void init_td_api() +{ + if (!initialized) + lt_debug_init(); + lt_info("%s begin, initialized=%d, debug=0x%02x\n", __func__, (int)initialized, debuglevel); + initialized = true; +} + +void shutdown_td_api() +{ + lt_info("%s, initialized = %d\n", __func__, (int)initialized); + initialized = false; +} diff --git a/azbox/init_lib.h b/azbox/init_lib.h new file mode 100644 index 0000000..d9a6f09 --- /dev/null +++ b/azbox/init_lib.h @@ -0,0 +1,5 @@ +#ifndef __INIT_TD_H +#define __INIT_TD_H +void init_td_api(); +void shutdown_td_api(); +#endif diff --git a/azbox/playback.cpp b/azbox/playback.cpp new file mode 100644 index 0000000..1e656f3 --- /dev/null +++ b/azbox/playback.cpp @@ -0,0 +1,521 @@ +/* + * cPlayback implementation for azbox + * this is actually just a wrapper around rmfp_player which does + * all the heavy listing + * + * based on the original aztrino implementation, but almost + * completely rewritten since then + * + * some of the methods and constants were found by stracing the + * AZPlay enigma2 plugin... + * + * (C) 2012 Stefan Seyfried + * + * 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 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lt_debug.h" +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_PLAYBACK, this, args) +#define lt_info(args...) _lt_info(TRIPLE_DEBUG_PLAYBACK, this, args) +#define lt_info_c(args...) _lt_info(TRIPLE_DEBUG_PLAYBACK, NULL, args) + +#include + +/* the file-based commands work better than the FIFOs... */ +#define CMD_FILE "/tmp/rmfp.cmd2" +#define IN_FILE "/tmp/rmfp.in2" +#define OUT_FILE "/tmp/rmfp.out2" + +#include "playback.h" + +extern "C"{ +#include "e2mruainclude.h" +} + +#if 0 +/* useful for debugging */ +static time_t monotonic_ms(void) +{ + struct timespec t; + time_t ret; + if (clock_gettime(CLOCK_MONOTONIC, &t)) + { + perror("monotonic_ms clock_gettime"); + return -1; + } + ret = ((t.tv_sec + 604800)& 0x01FFFFF) * 1000; /* avoid overflow */ + ret += t.tv_nsec / 1000000; + return ret; +} +#endif + +/* the mutex makes sure that commands are not interspersed */ +bool cPlayback::rmfp_command(int cmd, int param, bool has_param, char *buf, int buflen) +{ + lt_info("%s: %d %d %d %d\n", __func__, cmd, param, has_param, buflen); + bool ret = true; + int fd; + if (cmd == 222) + { + if (pthread_mutex_trylock(&rmfp_cmd_mutex)) + return false; + } + else + pthread_mutex_lock(&rmfp_cmd_mutex); + unlink(OUT_FILE); + if (has_param) + { + fd = open(IN_FILE, O_WRONLY|O_CREAT|O_TRUNC, 0666); + dprintf(fd, "%i", param); + close(fd); + } + fd = open(CMD_FILE, O_WRONLY|O_CREAT|O_TRUNC, 0666); + dprintf(fd, "%i", cmd); + close(fd); + int n = 0, m = 0; + if (buflen > 0) + { + while ((fd = open(OUT_FILE, O_RDONLY)) == -1) { + if (++m > 500) { /* don't wait more than 5 seconds */ + lt_info("%s: timed out waiting for %s (cmd %d par %d buflen %d\n", + __func__, OUT_FILE, cmd, param, buflen); + ret = false; + goto out; + } + usleep(10000); + } + unlink(OUT_FILE); + n = read(fd, buf, buflen); + close(fd); + /* some commands (CUSTOM_COMMAND_GET_AUDIO_BY_ID for example) actually + * return the answer in two successive writes, as we are not using the + * FIFO, we need to make sure that the file is deleted immediately, because + * rmfp_player will not overwrite it if it exists */ + while ((fd = open(OUT_FILE, O_RDONLY)) == -1) { + if (++m > 10) + break; + usleep(1000); + } + if (fd > -1) + { + read(fd, buf + n, buflen - n); + unlink(OUT_FILE); + close(fd); + } + buf[buflen - 1] = '0'; + } + out: + pthread_mutex_unlock(&rmfp_cmd_mutex); + if (cmd != 222) /* called tooo often :-) */ + lt_info("%s: reply: '%s' ret: %d m:%d\n", __func__, buf?buf:"(none)", ret, m); + return ret; +} + +/* + * runs the rmfp_player in a terminal + * just doing popen() or similar does not work because then + * the output will be buffered after starting up and we will only + * see "Playback has started..." after the player exits + */ +void cPlayback::run_rmfp() +{ + lt_debug("%s: starting\n", __func__); + thread_started = true; + //Watch for the space at the end + std::string base = "rmfp_player -dram 1 -ve 1 -waitexit "; + std::string filename(mfilename); + std::string file = '"' + filename + '"'; + std::string final = base + file; + + if (playMode == PLAYMODE_TS && mduration != 0) + { + std::stringstream duration; + duration << (mduration /** 60000LL*/); + final = base + "-duration " + duration.str() + " " + file; + } + + pid_t pid = 0; + int master; + pid = forkpty(&master, NULL, NULL, NULL); + if (! pid) { + execl("/bin/sh", "sh", "-c", final.c_str(), (char *)0); + lt_info("%s: execl returned: %m\n", __func__); + exit(0); + } + + if (pid > 0) { + char *output=NULL; + size_t n = 0; + ssize_t len; + FILE *f = fdopen(master, "r"); + while ((len = getline(&output, &n, f)) != -1) + { + while (len > 0) + { + len--; + if (!isspace(output[len])) + break; + output[len] = '\0'; + } + lt_info("%s out: '%s'\n", __func__, output); + if (strstr(output, "Playback has started...")) + { + playing = 1; + lt_info("%s: ===================> playing = true\n", __func__); + } + else if (strstr(output, "End of file...")) + { + playing = 1; /* this can happen without "Playback has started..." */ + eof_reached = true; + lt_info("%s: ===================> eof_reached = true\n", __func__); + } + } + fclose(f); + int s; + while (waitpid(pid, &s, WNOHANG) > 0) {}; + if (output) + free(output); + } + + lt_info("%s: terminating\n", __func__); + if (playing == 0) /* playback did not start */ + playing = 2; + else + playing = 0; + eof_reached = true; + pthread_exit(NULL); +} + +/* helper function to call the cpp thread loop */ +void *execute_rua_thread(void *c) +{ + cPlayback *obj = (cPlayback *)c; + lt_info_c("%s\n", __func__); + obj->run_rmfp(); + /* free(obj); // this is most likely wrong */ + + return NULL; +} + +//Used by Fileplay +bool cPlayback::Open(playmode_t PlayMode) +{ + static const char *aPLAYMODE[] = { + "PLAYMODE_TS", + "PLAYMODE_FILE" + }; + playMode = PlayMode; + if (playMode > 1) + { + lt_info("%s: PlayMode %d out of range!\n", __func__, PlayMode); + playMode = PLAYMODE_FILE; + } + + lt_info("%s: mode %d (%s)\n", __func__, PlayMode, aPLAYMODE[PlayMode]); +#if 0 + while (access("/tmp/continue", R_OK)) + sleep(1); +#endif + + char c[2] = "0"; + int i = 0; + + for(;;) + { + proc_get("/proc/player", c, 2); + if (c[0] != '0') + break; + i++; + if (i > 10) + { + lt_info("%s: ERROR - player is not idle after 10 seconds!\n", __func__); + open_success = false; + return false; + } + sleep(1); + } + + proc_put("/proc/player", "2", 2); + lt_info("%s: /proc/player switched to '2'\n", __func__); + + unlink(CMD_FILE); + unlink(IN_FILE); + unlink(OUT_FILE); + + open_success = true; + return 0; +} + +//Used by Fileplay +bool cPlayback::Start(char *filename, unsigned short vpid, int vtype, unsigned short _apid, + int ac3, unsigned int duration) +{ + bool ret = true; + + lt_info("%s: filename=%s\n", __func__, filename); + lt_info("%s: vpid=%u vtype=%d apid=%u ac3=%d duration=%i open_success=%d\n", + __func__, vpid, vtype, _apid, ac3, duration, open_success); + + if (!open_success) + return false; + + eof_reached = false; + //create playback path + apid = 0; + subpid = 0; + mfilename = filename; + mduration = duration; + if (pthread_create(&thread, 0, execute_rua_thread, this) != 0) + { + lt_info("%s: error creating rmfp_player thread! (out of memory?)\n", __func__); + ret = false; + } + while (! playing) + sleep(1); + if (playing == 2) + playing = 0; + return ret; +} + +void cPlayback::Close(void) +{ + lt_info("%s: playing %d thread_started %d\n", __func__, playing, thread_started); + + if (thread_started) + { + rmfp_command(KEY_COMMAND_QUIT_ALL, 0, false, NULL, 0); + + if (pthread_join(thread, NULL)) + lt_info("%s: error joining rmfp thread (%m)\n", __func__); + playing = 0; + thread_started = false; + } + else + lt_info("%s: Warning: thread_started == false!\n", __func__); + + if (open_success) + { + proc_put("/proc/player", "1", 2); + open_success = false; + lt_info("%s: /proc/player switched to '1'\n", __func__); + usleep(1000000); + } +} + +bool cPlayback::SetAPid(unsigned short pid, int /*ac3*/) +{ + lt_info("%s: pid %i\n", __func__, pid); + if (pid != apid) { + rmfp_command(KEY_COMMAND_SWITCH_AUDIO, pid, true, NULL, 0); + apid = pid; + } + return true; +} + +bool cPlayback::SelectSubtitles(int pid) +{ + lt_info("%s: pid %i\n", __func__, pid); + if (pid != subpid) + { + rmfp_command(KEY_COMMAND_SWITCH_SUBS, pid, true, NULL, 0); + subpid = pid; + } + return true; +} + +bool cPlayback::SetSpeed(int speed) +{ + lt_info("%s: playing %d speed %d\n", __func__, playing, speed); + + if (!playing) + return false; + + playback_speed = speed; + + if (speed > 1 || speed < 0) + rmfp_command(CUSTOM_COMMAND_TRICK_SEEK, speed, true, NULL, 0); + else if (speed == 0) + rmfp_command(KEY_COMMAND_PAUSE, 0, false, NULL, 0); + else + rmfp_command(KEY_COMMAND_PLAY, 0, false, NULL, 0); + + return true; +} + +bool cPlayback::GetSpeed(int &/*speed*/) const +{ +#if 0 + lt_info("%s:\n", __func__); + speed = playback_speed; +#endif + return true; +} + +// in milliseconds +bool cPlayback::GetPosition(int &position, int &duration) +{ + lt_debug("%s: playing %d\n", __func__, playing); + + if (eof_reached) + { + position = mduration; + duration = mduration; + return true; + } + + if (!playing) + return false; + + char buf[32]; + /* custom command 222 returns "12345\n1234\n", + * first line is duration, second line is position */ + if (! rmfp_command(222, 0, false, buf, 32)) + return false; + duration = atoi(buf); + char *p = strchr(buf, '\n'); + if (!p) + return false; + position = atoi(++p); + /* some mpegs return length 0... which would lead to "eof" after 10 seconds */ + if (duration == 0) + duration = position + 1000; + + if (playMode == PLAYMODE_TS) + { + if (position > mduration) + mduration = position + 1000; + duration = mduration; + return true; + } + return true; +} + +bool cPlayback::SetPosition(int position, bool absolute) +{ + lt_info("%s: pos %d abs %d playing %d\n", __func__, position, absolute, playing); + + if (!playing) + return false; + + int seconds = position / 1000;; + + if (absolute) + { + rmfp_command(KEY_COMMAND_SEEK_TO_TIME, seconds, true, NULL, 0); + return true; + } + + if (position > 0) + rmfp_command(CUSTOM_COMMAND_SEEK_RELATIVE_FWD, seconds, true, NULL, 0); + else if (position < 0) + rmfp_command(CUSTOM_COMMAND_SEEK_RELATIVE_BWD, seconds, true, NULL, 0); + return true; +} + +void cPlayback::FindAllPids(uint16_t *apids, unsigned short *ac3flags, uint16_t *numpida, std::string *language) +{ + lt_info("%s\n", __func__); + char buf[32]; + rmfp_command(CUSTOM_COMMAND_AUDIO_COUNT, 0, false, buf, 3); + unsigned int audio_count = atoi(buf); + + *numpida = audio_count; + if (audio_count > 0) + { + for (unsigned int aid = 0; aid < audio_count; aid++) + { + char streamidstring[11]; + char audio_lang[21]; + memset(buf, 0, sizeof(buf)); + rmfp_command(CUSTOM_COMMAND_GET_AUDIO_BY_ID, aid, true, buf, 32); + memcpy(streamidstring, buf, 10); + streamidstring[10] = '\0'; + memcpy(audio_lang, buf + 10, 20); + audio_lang[20] = '\0'; + apids[aid] = atoi(streamidstring); + ac3flags[aid] = 0; + language[aid] = audio_lang; + lt_info("%s: #%d apid:%d lang: %s\n", __func__, aid, apids[aid], audio_lang); + } + } +} + +void cPlayback::FindAllSubs(uint16_t *spids, unsigned short *supported, uint16_t *numpids, std::string *language) +{ + lt_info("%s\n", __func__); + char buf[32]; + + rmfp_command(CUSTOM_COMMAND_SUBS_COUNT, 0, false, buf, 3); + unsigned int spu_count = atoi(buf); + *numpids = spu_count; + + if (spu_count > 0) + { + for (unsigned int sid = 0; sid < spu_count; sid++) + { + char streamidstring[11]; + char spu_lang[21]; + memset(buf, 0, sizeof(buf)); + rmfp_command(CUSTOM_COMMAND_GET_SUB_BY_ID, sid, true, buf, 32); + memcpy(streamidstring, buf, 10); + streamidstring[10] = '\0'; + memcpy(spu_lang, buf + 10, 20); + spu_lang[20] = '\0'; + spids[sid] = atoi(streamidstring); + language[sid] = spu_lang; + supported[sid] = 1; + lt_info("%s: #%d apid:%d lang: %s\n", __func__, sid, spids[sid], spu_lang); + } + } + //Add streamid -1 to be able to disable subtitles + *numpids = spu_count + 1; + spids[spu_count] = -1; + language[spu_count] = "Disable"; +} + +/* DVD support is not yet ready... */ +void cPlayback::GetChapters(std::vector &positions, std::vector &titles) +{ + positions.clear(); + titles.clear(); +} + +cPlayback::cPlayback(int /*num*/) +{ + lt_info("%s: constructor\n", __func__); + playing = 0; + thread_started = false; + eof_reached = false; + open_success = false; + pthread_mutex_init(&rmfp_cmd_mutex, NULL); +} + +cPlayback::~cPlayback() +{ + lt_info("%s\n", __func__); + pthread_mutex_destroy(&rmfp_cmd_mutex); +} diff --git a/azbox/playback.h b/azbox/playback.h new file mode 100644 index 0000000..c56c69e --- /dev/null +++ b/azbox/playback.h @@ -0,0 +1,62 @@ +#ifndef __PLAYBACK_H +#define __PLAYBACK_H + +#include +#include +#include + +typedef enum { + PLAYMODE_TS = 0, + PLAYMODE_FILE, +} playmode_t; + +class cPlayback +{ + private: + pthread_mutex_t rmfp_cmd_mutex; + int playing; + bool eof_reached; + int playback_speed; + playmode_t playMode; + bool open_success; + uint16_t apid; + uint16_t subpid; + char *mfilename; + int mduration; + pthread_t thread; + bool thread_started; + /* private functions */ + bool rmfp_command(int cmd, int param, bool has_param, char *buf, int buflen); + public: + cPlayback(int num = 0); + ~cPlayback(); + + void run_rmfp(); + + bool Open(playmode_t PlayMode); + void Close(void); + bool Start(char *filename, unsigned short vpid, int vtype, unsigned short apid, + int ac3, unsigned int duration); + bool SetAPid(unsigned short pid, int ac3); + bool SetSpeed(int speed); + bool GetSpeed(int &speed) const; + bool GetPosition(int &position, int &duration); /* pos: current time in ms, dur: file length in ms */ + bool SetPosition(int position, bool absolute = false); /* position: jump in ms */ + void FindAllPids(uint16_t *apids, unsigned short *ac3flags, uint16_t *numpida, std::string *language); + void FindAllSubs(uint16_t *pids, unsigned short *supported, uint16_t *numpida, std::string *language); + bool SelectSubtitles(int pid); + void GetChapters(std::vector &positions, std::vector &titles); +#if 0 + // Functions that are not used by movieplayer.cpp: + bool Stop(void); + bool GetOffset(off64_t &offset); + bool IsPlaying(void) const { return playing; } + bool IsEnabled(void) const { return enabled; } + void * GetHandle(void); + void * GetDmHandle(void); + int GetCurrPlaybackSpeed(void) const { return nPlaybackSpeed; } + void PlaybackNotify (int Event, void *pData, void *pTag); + void DMNotify(int Event, void *pTsBuf, void *Tag); +#endif +}; +#endif diff --git a/azbox/pwrmngr.cpp b/azbox/pwrmngr.cpp new file mode 120000 index 0000000..cee9acb --- /dev/null +++ b/azbox/pwrmngr.cpp @@ -0,0 +1 @@ +../libspark/pwrmngr.cpp \ No newline at end of file diff --git a/azbox/pwrmngr.h b/azbox/pwrmngr.h new file mode 120000 index 0000000..e63e97b --- /dev/null +++ b/azbox/pwrmngr.h @@ -0,0 +1 @@ +../libspark/pwrmngr.h \ No newline at end of file diff --git a/azbox/record.cpp b/azbox/record.cpp new file mode 120000 index 0000000..4daae8d --- /dev/null +++ b/azbox/record.cpp @@ -0,0 +1 @@ +../libspark/record.cpp \ No newline at end of file diff --git a/azbox/record_lib.h b/azbox/record_lib.h new file mode 120000 index 0000000..1ec9332 --- /dev/null +++ b/azbox/record_lib.h @@ -0,0 +1 @@ +../libspark/record_lib.h \ No newline at end of file diff --git a/azbox/video.cpp b/azbox/video.cpp new file mode 100644 index 0000000..bf08c65 --- /dev/null +++ b/azbox/video.cpp @@ -0,0 +1,635 @@ +/* + * (C) 2002-2003 Andreas Oberritter + * (C) 2010-2012 Stefan Seyfried + * + * 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 2 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, write to the Free Software + * Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include + +#include "video_lib.h" +#define VIDEO_DEVICE "/dev/dvb/adapter0/video0" +#include "lt_debug.h" +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_VIDEO, this, args) +#define lt_info(args...) _lt_info(TRIPLE_DEBUG_VIDEO, this, args) +#define lt_debug_c(args...) _lt_debug(TRIPLE_DEBUG_VIDEO, NULL, args) +#define lt_info_c(args...) _lt_info(TRIPLE_DEBUG_VIDEO, NULL, args) + +#define fop(cmd, args...) ({ \ + int _r; \ + if (fd >= 0) { \ + if ((_r = ::cmd(fd, args)) < 0) \ + lt_info(#cmd"(fd, "#args")\n"); \ + else \ + lt_debug(#cmd"(fd, "#args")\n");\ + } \ + else { _r = fd; } \ + _r; \ +}) + +cVideo * videoDecoder = NULL; +int system_rev = 0; + +static bool stillpicture = false; +static unsigned char *blank_data; +static ssize_t blank_size; +static pthread_mutex_t stillp_mutex = PTHREAD_MUTEX_INITIALIZER; +/* prototype */ +static void show_iframe(int fd, unsigned char *iframe, size_t st_size); + +#define VIDEO_STREAMTYPE_MPEG2 0 +#define VIDEO_STREAMTYPE_MPEG4_H264 1 +#define VIDEO_STREAMTYPE_VC1 3 +#define VIDEO_STREAMTYPE_MPEG4_Part2 4 +#define VIDEO_STREAMTYPE_VC1_SM 5 +#define VIDEO_STREAMTYPE_MPEG1 6 + +cVideo::cVideo(int, void *, void *, unsigned int) +{ + lt_debug("%s\n", __FUNCTION__); + + //croppingMode = VID_DISPMODE_NORM; + //outputformat = VID_OUTFMT_RGBC_SVIDEO; + scartvoltage = -1; + video_standby = 0; + fd = -1; + + const char *blankname = "/share/tuxbox/blank_576.mpg"; + int blankfd; + struct stat st; + + blank_data = NULL; /* initialize */ + blank_size = 0; + blankfd = open(blankname, O_RDONLY|O_CLOEXEC); + if (blankfd < 0) + lt_info("%s cannot open %s: %m", __func__, blankname); + else + { + if (fstat(blankfd, &st) != -1 && st.st_size > 0) + { + blank_size = st.st_size; + blank_data = (unsigned char *)malloc(blank_size); + if (! blank_data) + lt_info("%s malloc failed (%m)\n", __func__); + else if (read(blankfd, blank_data, blank_size) != blank_size) + { + lt_info("%s short read (%m)\n", __func__); + free(blank_data); /* don't leak... */ + blank_data = NULL; + } + } + close(blankfd); + } + openDevice(); + Pig(-1, -1, -1, -1); +} + +cVideo::~cVideo(void) +{ + closeDevice(); + if (blank_data) + free(blank_data); +} + +void cVideo::openDevice(void) +{ + int n = 0; + lt_debug("%s\n", __func__); + /* todo: this fd checking is racy, should be protected by a lock */ + if (fd != -1) /* already open */ + return; +retry: + if ((fd = open(VIDEO_DEVICE, O_RDWR|O_CLOEXEC)) < 0) + { + if (errno == EBUSY) + { + /* sometimes we get busy quickly after close() */ + usleep(50000); + if (++n < 10) + goto retry; + } + lt_info("%s cannot open %s: %m, retries %d\n", __func__, VIDEO_DEVICE, n); + } + playstate = VIDEO_STOPPED; +} + +void cVideo::closeDevice(void) +{ + lt_debug("%s\n", __func__); + if (fd >= 0) + close(fd); + fd = -1; + playstate = VIDEO_STOPPED; +} + +int cVideo::setAspectRatio(int aspect, int mode) +{ + static const char *a[] = { "n/a", "4:3", "14:9", "16:9" }; + /* { "panscan", "letterbox", "fullscreen", "14:9", "(unset)" } */ + static const char *m[] = { "1", "2", "0", "1", "(unset)" }; + int n; + lt_debug("%s: a:%d m:%d %s\n", __func__, aspect, mode, m[(mode < 0||mode > 3) ? 4 : mode]); + + if (aspect > 3 || aspect == 0) + lt_info("%s: invalid aspect: %d\n", __func__, aspect); + else if (aspect > 0) /* -1 == don't set */ + { + lt_debug("%s: /proc/stb/video/aspect -> %s\n", __func__, a[aspect]); + n = proc_put("/proc/stb/video/aspect", a[aspect], strlen(a[aspect])); + if (n < 0) + lt_info("%s: proc_put /proc/stb/video/aspect (%m)\n", __func__); + } + + if (mode == -1) + return 0; + + lt_debug("%s: /proc/scalingmode -> %s\n", __func__, m[mode]); + n = proc_put("/proc/scalingmode", m[mode], strlen(m[mode])); + if (n < 0) + return 1; + return 0; +} + +int cVideo::getAspectRatio(void) +{ + video_size_t s; + if (fd == -1) + { + /* in movieplayer mode, fd is not opened -> fall back to procfs */ + int n = proc_get_hex("/proc/stb/vmpeg/0/aspect"); + return n * 2 + 1; + } + if (fop(ioctl, VIDEO_GET_SIZE, &s) < 0) + { + lt_info("%s: VIDEO_GET_SIZE %m\n", __func__); + return -1; + } + lt_debug("%s: %d\n", __func__, s.aspect_ratio); + return s.aspect_ratio * 2 + 1; +} + +int cVideo::setCroppingMode(int /*vidDispMode_t format*/) +{ + return 0; +#if 0 + croppingMode = format; + const char *format_string[] = { "norm", "letterbox", "unknown", "mode_1_2", "mode_1_4", "mode_2x", "scale", "disexp" }; + const char *f; + if (format >= VID_DISPMODE_NORM && format <= VID_DISPMODE_DISEXP) + f = format_string[format]; + else + f = "ILLEGAL format!"; + lt_debug("%s(%d) => %s\n", __FUNCTION__, format, f); + return fop(ioctl, MPEG_VID_SET_DISPMODE, format); +#endif +} + +int cVideo::Start(void * /*PcrChannel*/, unsigned short /*PcrPid*/, unsigned short /*VideoPid*/, void * /*hChannel*/) +{ + lt_debug("%s playstate=%d\n", __FUNCTION__, playstate); +#if 0 + if (playstate == VIDEO_PLAYING) + return 0; + if (playstate == VIDEO_FREEZED) /* in theory better, but not in practice :-) */ + fop(ioctl, MPEG_VID_CONTINUE); +#endif + playstate = VIDEO_PLAYING; + return fop(ioctl, VIDEO_PLAY); +} + +int cVideo::Stop(bool blank) +{ + lt_debug("%s(%d)\n", __FUNCTION__, blank); + if (stillpicture) + { + lt_debug("%s: stillpicture == true\n", __func__); + return -1; + } + /* blank parameter seems to not work on VIDEO_STOP */ + if (blank) + setBlank(1); + playstate = blank ? VIDEO_STOPPED : VIDEO_FREEZED; + return fop(ioctl, VIDEO_STOP, blank ? 1 : 0); +} + +int cVideo::setBlank(int) +{ + pthread_mutex_lock(&stillp_mutex); + if (blank_data) + show_iframe(fd, blank_data, blank_size); + pthread_mutex_unlock(&stillp_mutex); + return 1; +} + +int cVideo::SetVideoSystem(int video_system, bool remember) +{ + lt_debug("%s(%d, %d)\n", __func__, video_system, remember); + char current[32]; + static const char *modes[] = { + "480i", // VIDEO_STD_NTSC + "576i", // VIDEO_STD_SECAM + "576i", // VIDEO_STD_PAL + "480p", // VIDEO_STD_480P + "576p", // VIDEO_STD_576P + "720p60", // VIDEO_STD_720P60 + "1080i60", // VIDEO_STD_1080I60 + "720p50", // VIDEO_STD_720P50 + "1080i50", // VIDEO_STD_1080I50 + "1080p30", // VIDEO_STD_1080P30 + "1080p24", // VIDEO_STD_1080P24 + "1080p25", // VIDEO_STD_1080P25 + "720p50", // VIDEO_STD_AUTO -> not implemented + "1080p50" // VIDEO_STD_1080P50 -> SPARK only + }; + + if (video_system > VIDEO_STD_MAX) + { + lt_info("%s: video_system (%d) > VIDEO_STD_MAX (%d)\n", __func__, video_system, VIDEO_STD_MAX); + return -1; + } + int ret = proc_get("/proc/stb/video/videomode", current, 32); + if (strcmp(current, modes[video_system]) == 0) + { + lt_info("%s: video_system %d (%s) already set, skipping\n", __func__, video_system, current); + return 0; + } + lt_info("%s: old: '%s' new: '%s'\n", __func__, current, modes[video_system]); + ret = proc_put("/proc/stb/video/videomode", modes[video_system],strlen(modes[video_system])); + + return ret; +} + +int cVideo::getPlayState(void) +{ + return playstate; +} + +void cVideo::SetVideoMode(analog_mode_t mode) +{ + lt_debug("%s(%d)\n", __func__, mode); + if (!(mode & ANALOG_SCART_MASK)) + { + lt_debug("%s: non-SCART mode ignored\n", __func__); + return; + } + const char *m; + switch(mode) + { + case ANALOG_SD_YPRPB_SCART: + m = "yuv"; + break; + case ANALOG_SD_RGB_SCART: + m = "rgb"; + break; + default: + lt_info("%s unknown mode %d\n", __func__, mode); + m = "rgb"; + break; /* default to rgb */ + } + proc_put("/proc/stb/avs/0/colorformat", m, strlen(m)); +} + +void cVideo::ShowPicture(const char * fname) +{ + lt_debug("%s(%s)\n", __func__, fname); + char destname[512]; + char cmd[512]; + char *p; + int mfd; + unsigned char *iframe; + struct stat st, st2; + if (video_standby) + { + /* does not work and the driver does not seem to like it */ + lt_info("%s: video_standby == true\n", __func__); + return; + } + if (fd < 0) + { + lt_info("%s: decoder not opened?\n", __func__); + return; + } + strcpy(destname, "/var/cache"); + if (stat(fname, &st2)) + { + lt_info("%s: could not stat %s (%m)\n", __func__, fname); + return; + } + mkdir(destname, 0755); + /* the cache filename is (example for /share/tuxbox/neutrino/icons/radiomode.jpg): + /var/cache/share.tuxbox.neutrino.icons.radiomode.jpg.m2v + build that filename first... + TODO: this could cause name clashes, use a hashing function instead... */ + strcat(destname, fname); + p = &destname[strlen("/var/cache/")]; + while ((p = strchr(p, '/')) != NULL) + *p = '.'; + strcat(destname, ".m2v"); + /* ...then check if it exists already... */ + if (stat(destname, &st) || (st.st_mtime != st2.st_mtime) || (st.st_size == 0)) + { + struct utimbuf u; + u.actime = time(NULL); + u.modtime = st2.st_mtime; + /* it does not exist or has a different date, so call ffmpeg... */ + sprintf(cmd, "ffmpeg -y -f mjpeg -i '%s' -s 1280x720 '%s' %d\n", __func__, !ret); + return !ret; +} + +/* this function is regularly called, checks if video parameters + changed and triggers appropriate actions */ +void cVideo::VideoParamWatchdog(void) +{ +#if 0 + static unsigned int _v_info = (unsigned int) -1; + unsigned int v_info; + if (fd == -1) + return; + ioctl(fd, MPEG_VID_GET_V_INFO_RAW, &v_info); + if (_v_info != v_info) + { + lt_debug("%s params changed. old: %08x new: %08x\n", __FUNCTION__, _v_info, v_info); + setAspectRatio(-1, -1); + } + _v_info = v_info; +#endif +} + +void cVideo::Pig(int x, int y, int w, int h, int osd_w, int osd_h) +{ + char buffer[64]; + int _x, _y, _w, _h; + /* the target "coordinates" seem to be in a PAL sized plane + * TODO: check this in the driver sources */ + int xres = 720; /* proc_get_hex("/proc/stb/vmpeg/0/xres") */ + int yres = 576; /* proc_get_hex("/proc/stb/vmpeg/0/yres") */ + lt_debug("%s: x:%d y:%d w:%d h:%d ow:%d oh:%d\n", __func__, x, y, w, h, osd_w, osd_h); + if (x == -1 && y == -1 && w == -1 && h == -1) + { + _w = xres; + _h = yres; + _x = 0; + _y = 0; + } + else + { + _x = x * xres / osd_w; + _w = w * xres / osd_w; + _y = y * yres / osd_h; + _h = h * yres / osd_h; + } + lt_debug("%s: x:%d y:%d w:%d h:%d xr:%d yr:%d\n", __func__, _x, _y, _w, _h, xres, yres); + sprintf(buffer, "%x", _x); + proc_put("/proc/stb/vmpeg/0/dst_left", buffer, strlen(buffer)); + sprintf(buffer, "%x", _y); + proc_put("/proc/stb/vmpeg/0/dst_top", buffer, strlen(buffer)); + sprintf(buffer, "%x", _w); + proc_put("/proc/stb/vmpeg/0/dst_width", buffer, strlen(buffer)); + sprintf(buffer, "%x", _h); + proc_put("/proc/stb/vmpeg/0/dst_height", buffer, strlen(buffer)); +} + +static inline int rate2csapi(int rate) +{ + switch (rate) + { + /* no idea how the float values are represented by the driver */ + case 23976: + return 0; + case 24: + return 1; + case 25: + return 2; + case 29976: + return 3; + case 30: + return 4; + case 50: + return 5; + case 50940: + return 6; + case 60: + return 7; + default: + break; + } + return -1; +} + +void cVideo::getPictureInfo(int &width, int &height, int &rate) +{ + video_size_t s; + int r; + if (fd == -1) + { + /* in movieplayer mode, fd is not opened -> fall back to procfs */ + r = proc_get_hex("/proc/stb/vmpeg/0/framerate"); + width = proc_get_hex("/proc/stb/vmpeg/0/xres"); + height = proc_get_hex("/proc/stb/vmpeg/0/yres"); + rate = rate2csapi(r); + return; + } + ioctl(fd, VIDEO_GET_SIZE, &s); + ioctl(fd, VIDEO_GET_FRAME_RATE, &r); + rate = rate2csapi(r); + height = s.h; + width = s.w; + lt_debug("%s: rate: %d, width: %d height: %d\n", __func__, rate, width, height); +} + +void cVideo::SetSyncMode(AVSYNC_TYPE mode) +{ + lt_debug("%s %d\n", __func__, mode); + /* + * { 0, LOCALE_OPTIONS_OFF }, + * { 1, LOCALE_OPTIONS_ON }, + * { 2, LOCALE_AUDIOMENU_AVSYNC_AM } + */ +#if 0 + switch(Mode) + { + case 0: + ioctl(fd, MPEG_VID_SYNC_OFF); + break; + case 1: + ioctl(fd, MPEG_VID_SYNC_ON, VID_SYNC_VID); + break; + default: + ioctl(fd, MPEG_VID_SYNC_ON, VID_SYNC_AUD); + break; + } +#endif +}; + +int cVideo::SetStreamType(VIDEO_FORMAT type) +{ + static const char *VF[] = { + "VIDEO_FORMAT_MPEG2", + "VIDEO_FORMAT_MPEG4", + "VIDEO_FORMAT_VC1", + "VIDEO_FORMAT_JPEG", + "VIDEO_FORMAT_GIF", + "VIDEO_FORMAT_PNG" + }; + int t; + lt_debug("%s type=%s\n", __FUNCTION__, VF[type]); + + switch (type) + { + case VIDEO_FORMAT_MPEG4: + t = VIDEO_STREAMTYPE_MPEG4_H264; + break; + case VIDEO_FORMAT_VC1: + t = VIDEO_STREAMTYPE_VC1; + break; + case VIDEO_FORMAT_MPEG2: + default: + t = VIDEO_STREAMTYPE_MPEG2; + break; + } + + if (ioctl(fd, VIDEO_SET_STREAMTYPE, t) < 0) + lt_info("%s VIDEO_SET_STREAMTYPE(%d) failed: %m\n", __func__, t); + return 0; +} + +int64_t cVideo::GetPTS(void) +{ + int64_t pts = 0; + if (ioctl(fd, VIDEO_GET_PTS, &pts) < 0) + lt_info("%s: GET_PTS failed (%m)\n", __func__); + return pts; +} + +static void show_iframe(int fd, unsigned char *iframe, size_t st_size) +{ + static const unsigned char pes_header[] = { 0, 0, 1, 0xE0, 0, 0, 0x80, 0x80, 5, 0x21, 0, 1, 0, 1}; + static const unsigned char seq_end[] = { 0x00, 0x00, 0x01, 0xB7 }; + unsigned char stuffing[128]; + bool seq_end_avail = false; + size_t pos = 0; + int count = 7; + if (ioctl(fd, VIDEO_SET_FORMAT, VIDEO_FORMAT_16_9) < 0) + lt_info_c("%s: VIDEO_SET_FORMAT failed (%m)\n", __func__); + ioctl(fd, VIDEO_SET_STREAMTYPE, VIDEO_FORMAT_MPEG2); + ioctl(fd, VIDEO_SELECT_SOURCE, VIDEO_SOURCE_MEMORY); + ioctl(fd, VIDEO_PLAY); + ioctl(fd, VIDEO_CONTINUE); + ioctl(fd, VIDEO_CLEAR_BUFFER); + while (pos <= (st_size-4) && !(seq_end_avail = (!iframe[pos] && !iframe[pos+1] && iframe[pos+2] == 1 && iframe[pos+3] == 0xB7))) + ++pos; + while (count--) + { + if ((iframe[3] >> 4) != 0xE) // no pes header + write(fd, pes_header, sizeof(pes_header)); + else + iframe[4] = iframe[5] = 0x00; + write(fd, iframe, st_size); + usleep(8000); + } + if (!seq_end_avail) + write(fd, seq_end, sizeof(seq_end)); + + memset(stuffing, 0, sizeof(stuffing)); + for (count = 0; count < 8192 / (int)sizeof(stuffing); count++) + write(fd, stuffing, sizeof(stuffing)); + ioctl(fd, VIDEO_STOP, 0); + ioctl(fd, VIDEO_SELECT_SOURCE, VIDEO_SOURCE_DEMUX); +} + +void cVideo::SetDemux(cDemux *) +{ + lt_debug("%s: not implemented yet\n", __func__); +} diff --git a/azbox/video_lib.h b/azbox/video_lib.h new file mode 100644 index 0000000..c7286e9 --- /dev/null +++ b/azbox/video_lib.h @@ -0,0 +1,196 @@ +#ifndef _VIDEO_TD_H +#define _VIDEO_TD_H + +#include +#include "../common/cs_types.h" +#include "dmx_lib.h" + +typedef enum { + ANALOG_SD_RGB_CINCH = 0x00, + ANALOG_SD_YPRPB_CINCH, + ANALOG_HD_RGB_CINCH, + ANALOG_HD_YPRPB_CINCH, + ANALOG_SD_RGB_SCART = 0x10, + ANALOG_SD_YPRPB_SCART, + ANALOG_HD_RGB_SCART, + ANALOG_HD_YPRPB_SCART, + ANALOG_SCART_MASK = 0x10 +} analog_mode_t; + + +typedef enum { + VIDEO_FORMAT_MPEG2 = 0, + VIDEO_FORMAT_MPEG4, + VIDEO_FORMAT_VC1, + VIDEO_FORMAT_JPEG, + VIDEO_FORMAT_GIF, + VIDEO_FORMAT_PNG +} VIDEO_FORMAT; + +typedef enum { + VIDEO_SD = 0, + VIDEO_HD, + VIDEO_120x60i, + VIDEO_320x240i, + VIDEO_1440x800i, + VIDEO_360x288i +} VIDEO_DEFINITION; + +typedef enum { + VIDEO_FRAME_RATE_23_976 = 0, + VIDEO_FRAME_RATE_24, + VIDEO_FRAME_RATE_25, + VIDEO_FRAME_RATE_29_97, + VIDEO_FRAME_RATE_30, + VIDEO_FRAME_RATE_50, + VIDEO_FRAME_RATE_59_94, + VIDEO_FRAME_RATE_60 +} VIDEO_FRAME_RATE; + +typedef enum { + DISPLAY_AR_1_1, + DISPLAY_AR_4_3, + DISPLAY_AR_14_9, + DISPLAY_AR_16_9, + DISPLAY_AR_20_9, + DISPLAY_AR_RAW, +} DISPLAY_AR; + +typedef enum { + DISPLAY_AR_MODE_PANSCAN = 0, + DISPLAY_AR_MODE_LETTERBOX, + DISPLAY_AR_MODE_NONE, + DISPLAY_AR_MODE_PANSCAN2 +} DISPLAY_AR_MODE; + +typedef enum { + VIDEO_DB_DR_NEITHER = 0, + VIDEO_DB_ON, + VIDEO_DB_DR_BOTH +} VIDEO_DB_DR; + +typedef enum { + VIDEO_PLAY_STILL = 0, + VIDEO_PLAY_CLIP, + VIDEO_PLAY_TRICK, + VIDEO_PLAY_MOTION, + VIDEO_PLAY_MOTION_NO_SYNC +} VIDEO_PLAY_MODE; + +typedef enum { + VIDEO_STD_NTSC, + VIDEO_STD_SECAM, + VIDEO_STD_PAL, + VIDEO_STD_480P, + VIDEO_STD_576P, + VIDEO_STD_720P60, + VIDEO_STD_1080I60, + VIDEO_STD_720P50, + VIDEO_STD_1080I50, + VIDEO_STD_1080P30, + VIDEO_STD_1080P24, + VIDEO_STD_1080P25, + VIDEO_STD_AUTO, + VIDEO_STD_1080P50, /* SPARK only */ + VIDEO_STD_MAX +} VIDEO_STD; + +/* not used, for dummy functions */ +typedef enum { + VIDEO_HDMI_CEC_MODE_OFF = 0, + VIDEO_HDMI_CEC_MODE_TUNER, + VIDEO_HDMI_CEC_MODE_RECORDER +} VIDEO_HDMI_CEC_MODE; + +typedef enum +{ + VIDEO_CONTROL_BRIGHTNESS = 0, + VIDEO_CONTROL_CONTRAST, + VIDEO_CONTROL_SATURATION, + VIDEO_CONTROL_HUE, + VIDEO_CONTROL_SHARPNESS, + VIDEO_CONTROL_MAX = VIDEO_CONTROL_SHARPNESS +} VIDEO_CONTROL; + + +class cVideo +{ + friend class cDemux; + friend class cPlayback; + private: + /* video device */ + int fd; + /* apparently we cannot query the driver's state + => remember it */ + video_play_state_t playstate; + int /*vidDispMode_t*/ croppingMode; + int /*vidOutFmt_t*/ outputformat; + int scartvoltage; + + VIDEO_FORMAT StreamType; + VIDEO_DEFINITION VideoDefinition; + DISPLAY_AR DisplayAR; + VIDEO_PLAY_MODE SyncMode; + DISPLAY_AR_MODE ARMode; + VIDEO_DB_DR eDbDr; + DISPLAY_AR PictureAR; + VIDEO_FRAME_RATE FrameRate; + int video_standby; + int64_t GetPTS(void); + + void openDevice(void); + void closeDevice(void); + public: + /* constructor & destructor */ + cVideo(int mode, void *, void *, unsigned int unit = 0); + ~cVideo(void); + + void * GetTVEnc() { return NULL; }; + void * GetTVEncSD() { return NULL; }; + + /* aspect ratio */ + int getAspectRatio(void); + void getPictureInfo(int &width, int &height, int &rate); + int setAspectRatio(int aspect, int mode); + + /* cropping mode */ + int setCroppingMode(int x = 0 /*vidDispMode_t x = VID_DISPMODE_NORM*/); + + /* get play state */ + int getPlayState(void); + + /* blank on freeze */ + int getBlank(void); + int setBlank(int enable); + + /* change video play state. Parameters are all unused. */ + int Start(void *PcrChannel = NULL, unsigned short PcrPid = 0, unsigned short VideoPid = 0, void *x = NULL); + int Stop(bool blank = true); + bool Pause(void); + + /* set video_system */ + int SetVideoSystem(int video_system, bool remember = true); + int SetStreamType(VIDEO_FORMAT type); + void SetSyncMode(AVSYNC_TYPE mode); + bool SetCECMode(VIDEO_HDMI_CEC_MODE) { return true; }; + void SetCECAutoView(bool) { return; }; + void SetCECAutoStandby(bool) { return; }; + void ShowPicture(const char * fname); + void StopPicture(); + void Standby(unsigned int bOn); + void Pig(int x, int y, int w, int h, int osd_w = 1064, int osd_h = 600); + void SetControl(int, int) { return; }; + void VideoParamWatchdog(void); + void setContrast(int val); + void SetVideoMode(analog_mode_t mode); + void SetDBDR(int) { return; }; + void SetAudioHandle(void *) { return; }; + void SetAutoModes(int [VIDEO_STD_MAX]) { return; }; + int OpenVBI(int) { return 0; }; + int CloseVBI(void) { return 0; }; + int StartVBI(unsigned short) { return 0; }; + int StopVBI(void) { return 0; }; + void SetDemux(cDemux *dmx); +}; + +#endif diff --git a/common/Makefile.am b/common/Makefile.am new file mode 100644 index 0000000..f44757c --- /dev/null +++ b/common/Makefile.am @@ -0,0 +1,8 @@ +noinst_LTLIBRARIES = libcommon.la + +AM_CXXFLAGS = -fno-rtti -fno-exceptions -fno-strict-aliasing + +libcommon_la_SOURCES = \ + ca.cpp \ + lt_debug.cpp \ + proc_tools.c diff --git a/common/ca.cpp b/common/ca.cpp new file mode 100644 index 0000000..f6ebea2 --- /dev/null +++ b/common/ca.cpp @@ -0,0 +1,110 @@ +#include + +#include "ca.h" +#include "lt_debug.h" +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_CA, this, args) + + +static cCA *inst = NULL; + +/* those are all dummies for now.. */ +cCA::cCA(void) +{ + lt_debug("%s\n", __FUNCTION__); +} + +cCA::~cCA() +{ + lt_debug("%s\n", __FUNCTION__); +} + +cCA *cCA::GetInstance() +{ + _lt_debug(TRIPLE_DEBUG_CA, NULL, "%s\n", __FUNCTION__); + if (inst == NULL) + inst = new cCA(); + + return inst; +} + +void cCA::MenuEnter(enum CA_SLOT_TYPE, uint32_t p) +{ + lt_debug("%s param:%d\n", __FUNCTION__, (int)p); +} + +void cCA::MenuAnswer(enum CA_SLOT_TYPE, uint32_t p, uint32_t /*choice*/) +{ + lt_debug("%s param:%d\n", __FUNCTION__, (int)p); +} + +void cCA::InputAnswer(enum CA_SLOT_TYPE, uint32_t p, uint8_t * /*Data*/, int /*Len*/) +{ + lt_debug("%s param:%d\n", __FUNCTION__, (int)p); +} + +void cCA::MenuClose(enum CA_SLOT_TYPE, uint32_t p) +{ + lt_debug("%s param:%d\n", __FUNCTION__, (int)p); +} + +uint32_t cCA::GetNumberCISlots(void) +{ + lt_debug("%s\n", __FUNCTION__); + return 0; +} + +uint32_t cCA::GetNumberSmartCardSlots(void) +{ + lt_debug("%s\n", __FUNCTION__); + return 0; +} + +void cCA::ModuleName(enum CA_SLOT_TYPE, uint32_t p, char * /*Name*/) +{ + /* TODO: waht to do with *Name? */ + lt_debug("%s param:%d\n", __FUNCTION__, (int)p); +} + +bool cCA::ModulePresent(enum CA_SLOT_TYPE, uint32_t p) +{ + lt_debug("%s param:%d\n", __FUNCTION__, (int)p); + return false; +} + +void cCA::ModuleReset(enum CA_SLOT_TYPE, uint32_t p) +{ + lt_debug("%s param:%d\n", __FUNCTION__, (int)p); +} + +bool cCA::SendPMT(int, unsigned char *, int, CA_SLOT_TYPE) +{ + lt_debug("%s\n", __FUNCTION__); + return true; +} + +bool cCA::SendMessage(const CA_MESSAGE *) +{ + lt_debug("%s\n", __FUNCTION__); + return true; +} + +bool cCA::Start(void) +{ + lt_debug("%s\n", __FUNCTION__); + return true; +} + +void cCA::Stop(void) +{ + lt_debug("%s\n", __FUNCTION__); +} + +void cCA::Ready(bool p) +{ + lt_debug("%s param:%d\n", __FUNCTION__, (int)p); +} + +void cCA::SetInitMask(enum CA_INIT_MASK p) +{ + lt_debug("%s param:%d\n", __FUNCTION__, (int)p); +} diff --git a/common/ca.h b/common/ca.h new file mode 100644 index 0000000..bcb1f76 --- /dev/null +++ b/common/ca.h @@ -0,0 +1,112 @@ +/* + * dummy functions to implement ca_cs.h interface + */ +#ifndef __CA_LIBTRIPLE_H_ +#define __CA_LIBTRIPLE_H_ + +#include +#include "cs_types.h" +#include +typedef std::vector CaIdVector; +typedef std::vector::iterator CaIdVectorIterator; +typedef std::vector::const_iterator CaIdVectorConstIterator; + +enum CA_INIT_MASK { + CA_INIT_SC = 1, + CA_INIT_CI, + CA_INIT_BOTH +}; + +enum CA_SLOT_TYPE { + CA_SLOT_TYPE_SMARTCARD, + CA_SLOT_TYPE_CI, + CA_SLOT_TYPE_ALL, +}; + +enum CA_MESSAGE_FLAGS { + CA_MESSAGE_EMPTY = (1 << 0), + CA_MESSAGE_HAS_PARAM1_DATA = (1 << 1), // Free after use! + CA_MESSAGE_HAS_PARAM1_INT = (1 << 2), + CA_MESSAGE_HAS_PARAM1_PTR = (1 << 3), + CA_MESSAGE_HAS_PARAM2_INT = (1 << 4), + CA_MESSAGE_HAS_PARAM2_PTR = (1 << 5), + CA_MESSAGE_HAS_PARAM2_DATA = (1 << 6), + CA_MESSAGE_HAS_PARAM3_DATA = (1 << 7), // Free after use! + CA_MESSAGE_HAS_PARAM3_INT = (1 << 8), + CA_MESSAGE_HAS_PARAM3_PTR = (1 << 9), + CA_MESSAGE_HAS_PARAM4_INT = (1 << 10), + CA_MESSAGE_HAS_PARAM4_PTR = (1 << 11), + CA_MESSAGE_HAS_PARAM4_DATA = (1 << 12), + CA_MESSAGE_HAS_PARAM5_INT = (1 << 13), + CA_MESSAGE_HAS_PARAM5_PTR = (1 << 14), + CA_MESSAGE_HAS_PARAM5_DATA = (1 << 15), + CA_MESSAGE_HAS_PARAM6_INT = (1 << 16), + CA_MESSAGE_HAS_PARAM6_PTR = (1 << 17), + CA_MESSAGE_HAS_PARAM6_DATA = (1 << 18), + CA_MESSAGE_HAS_PARAM1_LONG = (1 << 19), + CA_MESSAGE_HAS_PARAM2_LONG = (1 << 20), + CA_MESSAGE_HAS_PARAM3_LONG = (1 << 21), + CA_MESSAGE_HAS_PARAM4_LONG = (1 << 22) +}; + +enum CA_MESSAGE_MSGID { + CA_MESSAGE_MSG_INSERTED, + CA_MESSAGE_MSG_REMOVED, + CA_MESSAGE_MSG_INIT_OK, + CA_MESSAGE_MSG_INIT_FAILED, + CA_MESSAGE_MSG_MMI_MENU, + CA_MESSAGE_MSG_MMI_MENU_ENTER, + CA_MESSAGE_MSG_MMI_MENU_ANSWER, + CA_MESSAGE_MSG_MMI_LIST, + CA_MESSAGE_MSG_MMI_TEXT, + CA_MESSAGE_MSG_MMI_REQ_INPUT, + CA_MESSAGE_MSG_MMI_CLOSE, + CA_MESSAGE_MSG_INTERNAL, + CA_MESSAGE_MSG_PMT_ARRIVED, + CA_MESSAGE_MSG_CAT_ARRIVED, + CA_MESSAGE_MSG_ECM_ARRIVED, + CA_MESSAGE_MSG_EMM_ARRIVED, + CA_MESSAGE_MSG_CHANNEL_CHANGE, + CA_MESSAGE_MSG_EXIT, +}; + +typedef struct CA_MESSAGE { + uint32_t MsgId; + enum CA_SLOT_TYPE SlotType; + int Slot; + uint32_t Flags; + union { + uint8_t *Data[4]; + uint32_t Param[4]; + void *Ptr[4]; + uint64_t ParamLong[4]; + } Msg; +} CA_MESSAGE; + +class cCA { +private: + cCA(void); +public: + uint32_t GetNumberCISlots(void); + uint32_t GetNumberSmartCardSlots(void); + static cCA *GetInstance(void); + bool SendPMT(int Unit, unsigned char *Data, int Len, CA_SLOT_TYPE SlotType = CA_SLOT_TYPE_ALL); + bool SendCAPMT(u64 /*Source*/, u8 /*DemuxSource*/, u8 /*DemuxMask*/, const unsigned char * /*CAPMT*/, u32 /*CAPMTLen*/, const unsigned char * /*RawPMT*/, u32 /*RawPMTLen*/) { return true; }; + bool SendMessage(const CA_MESSAGE *Msg); + void SetInitMask(enum CA_INIT_MASK InitMask); + int GetCAIDS(CaIdVector & /*Caids*/) { return 0; }; + bool Start(void); + void Stop(void); + void Ready(bool Set); + void ModuleReset(enum CA_SLOT_TYPE, uint32_t Slot); + bool ModulePresent(enum CA_SLOT_TYPE, uint32_t Slot); + void ModuleName(enum CA_SLOT_TYPE, uint32_t Slot, char *Name); + void MenuEnter(enum CA_SLOT_TYPE, uint32_t Slot); + void MenuAnswer(enum CA_SLOT_TYPE, uint32_t Slot, uint32_t choice); + void InputAnswer(enum CA_SLOT_TYPE, uint32_t Slot, uint8_t * Data, int Len); + void MenuClose(enum CA_SLOT_TYPE, uint32_t Slot); + void SetTSClock(u32 /*Speed*/) { return; }; + virtual ~cCA(); +}; + +#endif // __CA_LIBTRIPLE_H_ diff --git a/common/cs_types.h b/common/cs_types.h new file mode 100644 index 0000000..d044475 --- /dev/null +++ b/common/cs_types.h @@ -0,0 +1,23 @@ +/* pretty useless, but we don't need to work around this if + * we just copy it over... */ + +#ifndef __CS_TYPES_H_ +#define __CS_TYPES_H_ + +typedef enum +{ + AVSYNC_DISABLED, + AVSYNC_ENABLED, + AVSYNC_AUDIO_IS_MASTER +} AVSYNC_TYPE; + +typedef unsigned long long u64; +typedef unsigned int u32; +typedef unsigned short u16; +typedef unsigned char u8; +typedef signed long long s64; +typedef signed int s32; +typedef signed short s16; +typedef signed char s8; + +#endif // __CS_TYPES_H_ diff --git a/common/hardware_caps.h b/common/hardware_caps.h new file mode 120000 index 0000000..3bb479b --- /dev/null +++ b/common/hardware_caps.h @@ -0,0 +1 @@ +../include/hardware_caps.h \ No newline at end of file diff --git a/common/lt_debug.cpp b/common/lt_debug.cpp new file mode 100644 index 0000000..06434f7 --- /dev/null +++ b/common/lt_debug.cpp @@ -0,0 +1,90 @@ +/* libtriple debug functions */ + +#include +#include +#include +#include +#include +#include + + +int cnxt_debug = 0; /* compat, unused */ + +int debuglevel = -1; + +static const char* lt_facility[] = { + "audio ", + "video ", + "demux ", + "play ", + "power ", + "init ", + "ca ", + "record", + NULL +}; + +void _lt_info(int facility, const void *func, const char *fmt, ...) +{ + /* %p does print "(nil)" instead of 0x00000000 for NULL */ + fprintf(stderr, "[LT:%08lx:%s] ", (long) func, lt_facility[facility]); + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); +} + + +void _lt_debug(int facility, const void *func, const char *fmt, ...) +{ + if (debuglevel < 0) + fprintf(stderr, "lt_debug: debuglevel not initialized!\n"); + + if (! ((1 << facility) & debuglevel)) + return; + + fprintf(stderr, "[LT:%08lx:%s] ", (long)func, lt_facility[facility]); + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); +} + +void lt_debug_init(void) +{ + int i = 0; + char *tmp = getenv("HAL_DEBUG"); + if (! tmp) + tmp = getenv("TRIPLE_DEBUG"); /* backwards compatibility... */ + if (! tmp) + debuglevel = 0; + else + debuglevel = (int) strtol(tmp, NULL, 0); + + if (debuglevel == 0) + { + fprintf(stderr, "libstb-hal debug options can be set by exporting HAL_DEBUG.\n"); + fprintf(stderr, "The following values (or bitwise OR combinations) are valid:\n"); + while (lt_facility[i]) { + fprintf(stderr, "\tcomponent: %s 0x%02x\n", lt_facility[i], 1 << i); + i++; + } + fprintf(stderr, "\tall components: 0x%02x\n", (1 << i) - 1); + } else { + fprintf(stderr, "libstb-hal debug is active for the following components:\n"); + while (lt_facility[i]) { + if (debuglevel & (1 << i)) + fprintf(stderr, "%s ", lt_facility[i]); + i++; + } + fprintf(stderr, "\n"); + } +} + +void hal_set_threadname(const char *name) +{ + char threadname[17]; + strncpy(threadname, name, sizeof(threadname)); + threadname[16] = 0; + prctl (PR_SET_NAME, (unsigned long)&threadname); +} diff --git a/common/lt_debug.h b/common/lt_debug.h new file mode 100644 index 0000000..69a451a --- /dev/null +++ b/common/lt_debug.h @@ -0,0 +1,30 @@ +#ifndef __LT_DEBUG_H +#define __LT_DEBUG_H + +#define TRIPLE_DEBUG_AUDIO 0 +#define TRIPLE_DEBUG_VIDEO 1 +#define TRIPLE_DEBUG_DEMUX 2 +#define TRIPLE_DEBUG_PLAYBACK 3 +#define TRIPLE_DEBUG_PWRMNGR 4 +#define TRIPLE_DEBUG_INIT 5 +#define TRIPLE_DEBUG_CA 6 +#define TRIPLE_DEBUG_RECORD 7 +#define TRIPLE_DEBUG_ALL ((1<<8)-1) + +#define HAL_DEBUG_AUDIO 0 +#define HAL_DEBUG_VIDEO 1 +#define HAL_DEBUG_DEMUX 2 +#define HAL_DEBUG_PLAYBACK 3 +#define HAL_DEBUG_PWRMNGR 4 +#define HAL_DEBUG_INIT 5 +#define HAL_DEBUG_CA 6 +#define HAL_DEBUG_RECORD 7 +#define HAL_DEBUG_ALL ((1<<8)-1) + +extern int debuglevel; + +void hal_set_threadname(const char *name); +void _lt_debug(int facility, const void *, const char *fmt, ...) __attribute__ ((format (printf, 3, 4))); +void _lt_info(int facility, const void *, const char *fmt, ...) __attribute__ ((format (printf, 3, 4))); +void lt_debug_init(void); +#endif diff --git a/common/proc_tools.c b/common/proc_tools.c new file mode 100644 index 0000000..0983de9 --- /dev/null +++ b/common/proc_tools.c @@ -0,0 +1,59 @@ +/* + * helper functions to interact with files, usually in the proc filesystem + * + * (C) 2012 Stefan Seyfried + * + * License: GPLv2 or later + * + */ +#include +#include +#include +#include +#include /* isspace */ +#include /* sscanf */ + +#include "proc_tools.h" + +int proc_put(const char *path, const char *value, const int len) +{ + int ret, ret2; + int pfd = open(path, O_WRONLY); + if (pfd < 0) + return pfd; + ret = write(pfd, value, len); + ret2 = close(pfd); + if (ret2 < 0) + return ret2; + return ret; +} + +int proc_get(const char *path, char *value, const int len) +{ + int ret, ret2; + int pfd = open(path, O_RDONLY); + if (pfd < 0) + return pfd; + ret = read(pfd, value, len); + value[len-1] = '\0'; /* make sure string is terminated */ + if (ret >= 0) + { + while (ret > 0 && isspace(value[ret-1])) + ret--; /* remove trailing whitespace */ + value[ret] = '\0'; /* terminate, even if ret = 0 */ + } + ret2 = close(pfd); + if (ret2 < 0) + return ret2; + return ret; +} + +unsigned int proc_get_hex(const char *path) +{ + unsigned int n, ret = 0; + char buf[16]; + n = proc_get(path, buf, 16); + if (n > 0) + sscanf(buf, "%x", &ret); + return ret; +} diff --git a/common/proc_tools.h b/common/proc_tools.h new file mode 100644 index 0000000..2a3a8cd --- /dev/null +++ b/common/proc_tools.h @@ -0,0 +1,20 @@ +/* + * helper functions to interact with files, usually in the proc filesystem + * + * (C) 2012 Stefan Seyfried + * + * License: GPLv2 or later + * + */ +#ifndef __PROC_TOOLS_H__ +#define __PROC_TOOLS_H__ +#ifdef __cplusplus +extern "C" { +#endif +int proc_put(const char *path, const char *value, const int len); +int proc_get(const char *path, char *value, const int len); +unsigned int proc_get_hex(const char *path); +#ifdef __cplusplus +} +#endif +#endif diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..e92283c --- /dev/null +++ b/configure.ac @@ -0,0 +1,45 @@ +AC_INIT([libstb-hal], [0.1.1]) +AM_INIT_AUTOMAKE +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES]) +AC_CONFIG_MACRO_DIR([m4]) +AC_GNU_SOURCE +LT_INIT + +## ugly, disables shared library build (not wanted yet) +enable_shared=no + +TUXBOX_APPS +TUXBOX_APPS_DIRECTORY +TUXBOX_APPS_PKGCONFIG +TUXBOX_BOXTYPE + +AC_PROG_CC +AC_PROG_CXX +AC_DISABLE_STATIC +AC_SYS_LARGEFILE +AM_PROG_LIBTOOL + +if test x"$BOXTYPE" = x"tripledragon"; then + TUXBOX_APPS_LIB_PKGCONFIG(DIRECTFB, directfb) +fi + +if test x$BOXTYPE = xgeneric -a x$BOXMODEL != xraspi; then + PKG_CHECK_MODULES([AVFORMAT], [libavformat >= 53.21.1]) + PKG_CHECK_MODULES([AVCODEC], [libavcodec >= 54.28.0]) + # don't know which version is exactly needed here... + PKG_CHECK_MODULES([AVUTIL], [libavutil]) + PKG_CHECK_MODULES([SWSCALE], [libswscale]) + PKG_CHECK_MODULES([SWRESAMPLE], [libswresample]) +fi +AC_OUTPUT([ +Makefile +common/Makefile +libeplayer3/Makefile +azbox/Makefile +generic-pc/Makefile +libtriple/Makefile +libspark/Makefile +raspi/Makefile +tools/Makefile +]) + diff --git a/generic-pc/Makefile.am b/generic-pc/Makefile.am new file mode 100644 index 0000000..f4dbbc7 --- /dev/null +++ b/generic-pc/Makefile.am @@ -0,0 +1,28 @@ +noinst_LTLIBRARIES = libgeneric.la + +AM_CPPFLAGS = -D__STDC_FORMAT_MACROS -D__STDC_CONSTANT_MACROS +AM_CPPFLAGS += \ + -I$(top_srcdir)/common + +AM_CXXFLAGS = -fno-rtti -fno-exceptions -fno-strict-aliasing + +AM_LDFLAGS = \ + -lglut -lGL -lGLU -lGLEW -lao \ + -lOpenThreads \ + @AVFORMAT_LIBS@ \ + @AVUTIL_LIBS@ \ + @AVCODEC_LIBS@ \ + @SWRESAMPLE_LIBS@ \ + @SWSCALE_LIBS@ + +libgeneric_la_SOURCES = \ + hardware_caps.c \ + dmx.cpp \ + video.cpp \ + audio.cpp \ + glfb.cpp \ + init.cpp \ + playback.cpp \ + pwrmngr.cpp \ + record.cpp + diff --git a/generic-pc/audio.cpp b/generic-pc/audio.cpp new file mode 100644 index 0000000..8d6f317 --- /dev/null +++ b/generic-pc/audio.cpp @@ -0,0 +1,461 @@ +/* + * (C) 2010-2013 Stefan Seyfried + * + * 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 2 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 . + * + * cAudio implementation with decoder. + * uses libao for output + * ffmpeg for demuxing / decoding / format conversion + */ + +#include +#include + +#include "audio_lib.h" +#include "dmx_lib.h" +#include "lt_debug.h" + +#define lt_debug(args...) _lt_debug(HAL_DEBUG_AUDIO, this, args) +#define lt_info(args...) _lt_info(HAL_DEBUG_AUDIO, this, args) + +#include + +extern "C" { +#include +#include +#include +#include +#include +} +/* ffmpeg buf 2k */ +#define INBUF_SIZE 0x0800 +/* my own buf 16k */ +#define DMX_BUF_SZ 0x4000 + +cAudio * audioDecoder = NULL; +extern cDemux *audioDemux; +static uint8_t *dmxbuf = NULL; +static int bufpos; + +extern bool HAL_nodec; + +static cAudio *gThiz = NULL; + +static ao_device *adevice = NULL; +static ao_sample_format sformat; + +static AVCodecContext *c= NULL; + +cAudio::cAudio(void *, void *, void *) +{ + thread_started = false; + if (!HAL_nodec) + dmxbuf = (uint8_t *)malloc(DMX_BUF_SZ); + bufpos = 0; + curr_pts = 0; + gThiz = this; + ao_initialize(); +} + +cAudio::~cAudio(void) +{ + closeDevice(); + free(dmxbuf); + if (adevice) + ao_close(adevice); + adevice = NULL; + ao_shutdown(); +} + +void cAudio::openDevice(void) +{ + lt_debug("%s\n", __func__); +} + +void cAudio::closeDevice(void) +{ + lt_debug("%s\n", __func__); +} + +int cAudio::do_mute(bool enable, bool remember) +{ + lt_debug("%s(%d, %d)\n", __func__, enable, remember); + return 0; +} + +int cAudio::setVolume(unsigned int left, unsigned int right) +{ + lt_debug("%s(%d, %d)\n", __func__, left, right); + return 0; +} + +int cAudio::Start(void) +{ + lt_debug("%s >\n", __func__); + if (! HAL_nodec) + OpenThreads::Thread::start(); + lt_debug("%s <\n", __func__); + return 0; +} + +int cAudio::Stop(void) +{ + lt_debug("%s >\n", __func__); + if (thread_started) + { + thread_started = false; + OpenThreads::Thread::join(); + } + lt_debug("%s <\n", __func__); + return 0; +} + +bool cAudio::Pause(bool /*Pcm*/) +{ + return true; +}; + +void cAudio::SetSyncMode(AVSYNC_TYPE Mode) +{ + lt_debug("%s %d\n", __func__, Mode); +}; + +void cAudio::SetStreamType(AUDIO_FORMAT type) +{ + lt_debug("%s %d\n", __func__, type); +}; + +int cAudio::setChannel(int /*channel*/) +{ + return 0; +}; + +int cAudio::PrepareClipPlay(int ch, int srate, int bits, int le) +{ + lt_debug("%s ch %d srate %d bits %d le %d adevice %p\n", __func__, ch, srate, bits, le, adevice);; + int driver; + int byte_format = le ? AO_FMT_LITTLE : AO_FMT_BIG; + if (sformat.bits != bits || sformat.channels != ch || sformat.rate != srate || + sformat.byte_format != byte_format || adevice == NULL) + { + driver = ao_default_driver_id(); + sformat.bits = bits; + sformat.channels = ch; + sformat.rate = srate; + sformat.byte_format = byte_format; + sformat.matrix = 0; + if (adevice) + ao_close(adevice); + adevice = ao_open_live(driver, &sformat, NULL); + ao_info *ai = ao_driver_info(driver); + lt_info("%s: changed params ch %d srate %d bits %d le %d adevice %p\n", + __func__, ch, srate, bits, le, adevice);; + lt_info("libao driver: %d name '%s' short '%s' author '%s'\n", + driver, ai->name, ai->short_name, ai->author); + } + return 0; +}; + +int cAudio::WriteClip(unsigned char *buffer, int size) +{ + lt_debug("cAudio::%s buf 0x%p size %d\n", __func__, buffer, size); + if (!adevice) { + lt_info("%s: adevice not opened?\n", __func__); + return 0; + } + ao_play(adevice, (char *)buffer, size); + return size; +}; + +int cAudio::StopClip() +{ + lt_debug("%s\n", __func__); +#if 0 + /* don't do anything - closing / reopening ao all the time makes for long delays + * reinit on-demand (e.g. for changed parameters) instead */ + if (!adevice) { + lt_info("%s: adevice not opened?\n", __func__); + return 0; + } + ao_close(adevice); + adevice = NULL; +#endif + return 0; +}; + +void cAudio::getAudioInfo(int &type, int &layer, int &freq, int &bitrate, int &mode) +{ + type = 0; + layer = 0; /* not used */ + freq = 0; + bitrate = 0; /* not used, but easy to get :-) */ + mode = 0; /* default: stereo */ + if (c) { + type = (c->codec_id != AV_CODEC_ID_MP2); /* only mpeg / not mpeg is indicated */ + freq = c->sample_rate; + bitrate = c->bit_rate; + if (c->channels == 1) + mode = 3; /* for AV_CODEC_ID_MP2, only stereo / mono is detected for now */ + if (c->codec_id != AV_CODEC_ID_MP2) { + switch (c->channel_layout) { + case AV_CH_LAYOUT_MONO: + mode = 1; // "C" + break; + case AV_CH_LAYOUT_STEREO: + mode = 2; // "L/R" + break; + case AV_CH_LAYOUT_2_1: + case AV_CH_LAYOUT_SURROUND: + mode = 3; // "L/C/R" + break; + case AV_CH_LAYOUT_2POINT1: + mode = 4; // "L/R/S" + break; + case AV_CH_LAYOUT_3POINT1: + mode = 5; // "L/C/R/S" + break; + case AV_CH_LAYOUT_2_2: + case AV_CH_LAYOUT_QUAD: + mode = 6; // "L/R/SL/SR" + break; + case AV_CH_LAYOUT_5POINT0: + case AV_CH_LAYOUT_5POINT1: + mode = 7; // "L/C/R/SL/SR" + break; + default: + lt_info("%s: unknown ch_layout 0x%" PRIx64 "\n", + __func__, c->channel_layout); + } + } + } + lt_debug("%s t: %d l: %d f: %d b: %d m: %d codec_id: %x\n", + __func__, type, layer, freq, bitrate, mode, c->codec_id); +}; + +void cAudio::SetSRS(int /*iq_enable*/, int /*nmgr_enable*/, int /*iq_mode*/, int /*iq_level*/) +{ + lt_debug("%s\n", __func__); +}; + +void cAudio::SetHdmiDD(bool enable) +{ + lt_debug("%s %d\n", __func__, enable); +}; + +void cAudio::SetSpdifDD(bool enable) +{ + lt_debug("%s %d\n", __func__, enable); +}; + +void cAudio::ScheduleMute(bool On) +{ + lt_debug("%s %d\n", __func__, On); +}; + +void cAudio::EnableAnalogOut(bool enable) +{ + lt_debug("%s %d\n", __func__, enable); +}; + +void cAudio::setBypassMode(bool disable) +{ + lt_debug("%s %d\n", __func__, disable); +} + +static int _my_read(void *, uint8_t *buf, int buf_size) +{ + return gThiz->my_read(buf, buf_size); +} + +int cAudio::my_read(uint8_t *buf, int buf_size) +{ + int tmp = 0; + if (audioDecoder && bufpos < DMX_BUF_SZ - 4096) { + while (bufpos < buf_size && ++tmp < 20) { /* retry max 20 times */ + int ret = audioDemux->Read(dmxbuf + bufpos, DMX_BUF_SZ - bufpos, 10); + if (ret > 0) + bufpos += ret; + if (! thread_started) + break; + } + } + if (bufpos == 0) + return 0; + //lt_info("%s buf_size %d bufpos %d th %d tmp %d\n", __func__, buf_size, bufpos, thread_started, tmp); + if (bufpos > buf_size) { + memcpy(buf, dmxbuf, buf_size); + memmove(dmxbuf, dmxbuf + buf_size, bufpos - buf_size); + bufpos -= buf_size; + return buf_size; + } + memcpy(buf, dmxbuf, bufpos); + tmp = bufpos; + bufpos = 0; + return tmp; +} + +void cAudio::run() +{ + lt_info("====================== start decoder thread ================================\n"); + /* libavcodec & friends */ + av_register_all(); + + AVCodec *codec; + AVFormatContext *avfc = NULL; + AVInputFormat *inp; + AVFrame *frame; + uint8_t *inbuf = (uint8_t *)av_malloc(INBUF_SIZE); + AVPacket avpkt; + int ret, driver; + /* libao */ + ao_info *ai; + // ao_device *adevice; + // ao_sample_format sformat; + /* resample */ + SwrContext *swr = NULL; + uint8_t *obuf = NULL; + int obuf_sz = 0; /* in samples */ + int obuf_sz_max = 0; + int o_ch, o_sr; /* output channels and sample rate */ + uint64_t o_layout; /* output channels layout */ + char tmp[64] = "unknown"; + + curr_pts = 0; + av_init_packet(&avpkt); + inp = av_find_input_format("mpegts"); + AVIOContext *pIOCtx = avio_alloc_context(inbuf, INBUF_SIZE, // internal Buffer and its size + 0, // bWriteable (1=true,0=false) + NULL, // user data; will be passed to our callback functions + _my_read, // read callback + NULL, // write callback + NULL); // seek callback + avfc = avformat_alloc_context(); + avfc->pb = pIOCtx; + avfc->iformat = inp; + avfc->probesize = 188*5; + thread_started = true; + + if (avformat_open_input(&avfc, NULL, inp, NULL) < 0) { + lt_info("%s: avformat_open_input() failed.\n", __func__); + goto out; + } + ret = avformat_find_stream_info(avfc, NULL); + lt_debug("%s: avformat_find_stream_info: %d\n", __func__, ret); + if (avfc->nb_streams != 1) + { + lt_info("%s: nb_streams: %d, should be 1!\n", __func__, avfc->nb_streams); + goto out; + } + if (avfc->streams[0]->codec->codec_type != AVMEDIA_TYPE_AUDIO) + lt_info("%s: stream 0 no audio codec? 0x%x\n", __func__, avfc->streams[0]->codec->codec_type); + + c = avfc->streams[0]->codec; + codec = avcodec_find_decoder(c->codec_id); + if (!codec) { + lt_info("%s: Codec for %s not found\n", __func__, avcodec_get_name(c->codec_id)); + goto out; + } + if (avcodec_open2(c, codec, NULL) < 0) { + lt_info("%s: avcodec_open2() failed\n", __func__); + goto out; + } + frame = avcodec_alloc_frame(); + if (!frame) { + lt_info("%s: avcodec_alloc_frame failed\n", __func__); + goto out2; + } + /* output sample rate, channels, layout could be set here if necessary */ + o_ch = c->channels; /* 2 */ + o_sr = c->sample_rate; /* 48000 */ + o_layout = c->channel_layout; /* AV_CH_LAYOUT_STEREO */ + if (sformat.channels != o_ch || sformat.rate != o_sr || + sformat.byte_format != AO_FMT_NATIVE || sformat.bits != 16 || adevice == NULL) + { + driver = ao_default_driver_id(); + sformat.bits = 16; + sformat.channels = o_ch; + sformat.rate = o_sr; + sformat.byte_format = AO_FMT_NATIVE; + sformat.matrix = 0; + if (adevice) + ao_close(adevice); + adevice = ao_open_live(driver, &sformat, NULL); + ai = ao_driver_info(driver); + lt_info("%s: changed params ch %d srate %d bits %d adevice %p\n", + __func__, o_ch, o_sr, 16, adevice);; + lt_info("libao driver: %d name '%s' short '%s' author '%s'\n", + driver, ai->name, ai->short_name, ai->author); + } +#if 0 + lt_info(" driver options:"); + for (int i = 0; i < ai->option_count; ++i) + fprintf(stderr, " %s", ai->options[i]); + fprintf(stderr, "\n"); +#endif + av_get_sample_fmt_string(tmp, sizeof(tmp), c->sample_fmt); + lt_info("decoding %s, sample_fmt %d (%s) sample_rate %d channels %d\n", + avcodec_get_name(c->codec_id), c->sample_fmt, tmp, c->sample_rate, c->channels); + swr = swr_alloc_set_opts(swr, + o_layout, AV_SAMPLE_FMT_S16, o_sr, /* output */ + c->channel_layout, c->sample_fmt, c->sample_rate, /* input */ + 0, NULL); + if (! swr) { + lt_info("could not alloc resample context\n"); + goto out3; + } + swr_init(swr); + while (thread_started) { + int gotframe = 0; + if (av_read_frame(avfc, &avpkt) < 0) + break; + avcodec_decode_audio4(c, frame, &gotframe, &avpkt); + if (gotframe && thread_started) { + int out_linesize; + obuf_sz = av_rescale_rnd(swr_get_delay(swr, c->sample_rate) + + frame->nb_samples, o_sr, c->sample_rate, AV_ROUND_UP); + if (obuf_sz > obuf_sz_max) { + lt_info("obuf_sz: %d old: %d\n", obuf_sz, obuf_sz_max); + av_free(obuf); + if (av_samples_alloc(&obuf, &out_linesize, o_ch, + frame->nb_samples, AV_SAMPLE_FMT_S16, 1) < 0) { + lt_info("av_samples_alloc failed\n"); + av_free_packet(&avpkt); + break; /* while (thread_started) */ + } + obuf_sz_max = obuf_sz; + } + obuf_sz = swr_convert(swr, &obuf, obuf_sz, + (const uint8_t **)frame->extended_data, frame->nb_samples); + curr_pts = av_frame_get_best_effort_timestamp(frame); + lt_debug("%s: pts 0x%" PRIx64 " %3f\n", __func__, curr_pts, curr_pts/90000.0); + int o_buf_sz = av_samples_get_buffer_size(&out_linesize, o_ch, + obuf_sz, AV_SAMPLE_FMT_S16, 1); + ao_play(adevice, (char *)obuf, o_buf_sz); + } + av_free_packet(&avpkt); + } + // ao_close(adevice); /* can take long :-( */ + av_free(obuf); + swr_free(&swr); + out3: + avcodec_free_frame(&frame); + out2: + avcodec_close(c); + c = NULL; + out: + avformat_close_input(&avfc); + av_free(pIOCtx->buffer); + av_free(pIOCtx); + lt_info("======================== end decoder thread ================================\n"); +} diff --git a/generic-pc/audio_lib.h b/generic-pc/audio_lib.h new file mode 100644 index 0000000..36ad41a --- /dev/null +++ b/generic-pc/audio_lib.h @@ -0,0 +1,105 @@ +/* public header file */ + +#ifndef _AUDIO_LIB_H_ +#define _AUDIO_LIB_H_ + +#include +#include +#include "../common/cs_types.h" + +typedef enum +{ + AUDIO_SYNC_WITH_PTS, + AUDIO_NO_SYNC, + AUDIO_SYNC_AUDIO_MASTER +} AUDIO_SYNC_MODE; + +typedef enum { + HDMI_ENCODED_OFF, + HDMI_ENCODED_AUTO, + HDMI_ENCODED_FORCED +} HDMI_ENCODED_MODE; + +typedef enum +{ + AUDIO_FMT_AUTO = 0, + AUDIO_FMT_MPEG, + AUDIO_FMT_MP3, + AUDIO_FMT_DOLBY_DIGITAL, + AUDIO_FMT_BASIC = AUDIO_FMT_DOLBY_DIGITAL, + AUDIO_FMT_AAC, + AUDIO_FMT_AAC_PLUS, + AUDIO_FMT_DD_PLUS, + AUDIO_FMT_DTS, + AUDIO_FMT_AVS, + AUDIO_FMT_MLP, + AUDIO_FMT_WMA, + AUDIO_FMT_MPG1, // TD only. For Movieplayer / cPlayback + AUDIO_FMT_ADVANCED = AUDIO_FMT_MLP +} AUDIO_FORMAT; + +class cAudio : public OpenThreads::Thread +{ + friend class cPlayback; + private: + int fd; + bool Muted; + + int clipfd; /* for pcm playback */ + int mixer_fd; /* if we are using the OSS mixer */ + int mixer_num; /* oss mixer to use, if any */ + + AUDIO_FORMAT StreamType; + AUDIO_SYNC_MODE SyncMode; + bool started; + bool thread_started; + + int volume; + int64_t curr_pts; + + void openDevice(void); + void closeDevice(void); + + int do_mute(bool enable, bool remember); + void setBypassMode(bool disable); + void run(); + public: + /* construct & destruct */ + cAudio(void *, void *, void *); + ~cAudio(void); + int64_t getPts() { return curr_pts; } + + void *GetHandle() { return NULL; }; + /* shut up */ + int mute(bool remember = true) { return do_mute(true, remember); }; + int unmute(bool remember = true) { return do_mute(false, remember); }; + + /* volume, min = 0, max = 255 */ + int setVolume(unsigned int left, unsigned int right); + int getVolume(void) { return volume;} + bool getMuteStatus(void) { return Muted; }; + + /* start and stop audio */ + int Start(void); + int Stop(void); + bool Pause(bool Pcm = true); + void SetStreamType(AUDIO_FORMAT type); + void SetSyncMode(AVSYNC_TYPE Mode); + + /* select channels */ + int setChannel(int channel); + int PrepareClipPlay(int uNoOfChannels, int uSampleRate, int uBitsPerSample, int bLittleEndian); + int WriteClip(unsigned char * buffer, int size); + int StopClip(); + void getAudioInfo(int &type, int &layer, int& freq, int &bitrate, int &mode); + void SetSRS(int iq_enable, int nmgr_enable, int iq_mode, int iq_level); + bool IsHdmiDDSupported(); + void SetHdmiDD(bool enable); + void SetSpdifDD(bool enable); + void ScheduleMute(bool On); + void EnableAnalogOut(bool enable); + int my_read(uint8_t *buf, int buf_size); +}; + +#endif + diff --git a/generic-pc/cs_api.h b/generic-pc/cs_api.h new file mode 120000 index 0000000..a794ffd --- /dev/null +++ b/generic-pc/cs_api.h @@ -0,0 +1 @@ +../libspark/cs_api.h \ No newline at end of file diff --git a/generic-pc/dmx.cpp b/generic-pc/dmx.cpp new file mode 100644 index 0000000..fe1d161 --- /dev/null +++ b/generic-pc/dmx.cpp @@ -0,0 +1,505 @@ +/* + * cDemux implementation for generic dvbapi + * + * derived from libtriple/dmx_td.cpp + * + * (C) 2010-2013 Stefan Seyfried + * + * 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 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include "config.h" +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "dmx_lib.h" +#include "lt_debug.h" + +/* needed for getSTC :-( */ +#include "video_lib.h" +extern cVideo *videoDecoder; + +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_DEMUX, this, args) +#define lt_info(args...) _lt_info(TRIPLE_DEBUG_DEMUX, this, args) +#define lt_info_c(args...) _lt_info(TRIPLE_DEBUG_DEMUX, NULL, args) + +#define dmx_err(_errfmt, _errstr, _revents) do { \ + uint16_t _pid = (uint16_t)-1; uint16_t _f = 0;\ + if (dmx_type == DMX_PSI_CHANNEL) { \ + _pid = s_flt.pid; _f = s_flt.filter.filter[0]; \ + } else { \ + _pid = p_flt.pid; \ + }; \ + lt_info("%s " _errfmt " fd:%d, ev:0x%x %s pid:0x%04hx flt:0x%02hx\n", \ + __func__, _errstr, fd, _revents, DMX_T[dmx_type], _pid, _f); \ +} while(0); + +cDemux *videoDemux = NULL; +cDemux *audioDemux = NULL; +//cDemux *pcrDemux = NULL; + +static const char *DMX_T[] = { + "DMX_INVALID", + "DMX_VIDEO", + "DMX_AUDIO", + "DMX_PES", + "DMX_PSI", + "DMX_PIP", + "DMX_TP", + "DMX_PCR" +}; + +/* map the device numbers. for now only demux0 is used */ +static const char *devname[] = { + "/dev/dvb/adapter0/demux0", + "/dev/dvb/adapter0/demux0", + "/dev/dvb/adapter0/demux0" +}; + +/* uuuugly */ +static int dmx_tp_count = 0; +#define MAX_TS_COUNT 8 + +extern bool HAL_nodec; + +cDemux::cDemux(int n) +{ + if (n < 0 || n > 2) + { + lt_info("%s ERROR: n invalid (%d)\n", __FUNCTION__, n); + num = 0; + } + else + num = n; + fd = -1; + measure = false; + last_measure = 0; + last_data = 0; +} + +cDemux::~cDemux() +{ + lt_debug("%s #%d fd: %d\n", __FUNCTION__, num, fd); + Close(); +} + +bool cDemux::Open(DMX_CHANNEL_TYPE pes_type, void * /*hVideoBuffer*/, int uBufferSize) +{ + int devnum = num; + int flags = O_RDWR|O_CLOEXEC; + if (fd > -1) + lt_info("%s FD ALREADY OPENED? fd = %d\n", __FUNCTION__, fd); + + dmx_type = pes_type; + if (pes_type != DMX_PSI_CHANNEL) + flags |= O_NONBLOCK; + + fd = open(devname[devnum], flags); + if (fd < 0) + { + lt_info("%s %s: %m\n", __FUNCTION__, devname[devnum]); + return false; + } + lt_debug("%s #%d pes_type: %s(%d), uBufferSize: %d fd: %d\n", __func__, + num, DMX_T[pes_type], pes_type, uBufferSize, fd); + + if (dmx_type == DMX_VIDEO_CHANNEL) + uBufferSize = 0x100000; /* 1MB */ + if (dmx_type == DMX_AUDIO_CHANNEL) + uBufferSize = 0x10000; /* 64k */ +#if 0 + if (!pesfds.empty()) + { + lt_info("%s ERROR! pesfds not empty!\n", __FUNCTION__); /* TODO: error handling */ + return false; + } + int n = DMX_SOURCE_FRONT0; + if (ioctl(fd, DMX_SET_SOURCE, &n) < 0) + lt_info("%s DMX_SET_SOURCE %d failed! (%m)\n", __func__, n); +#endif + if (uBufferSize > 0) + { + /* probably uBufferSize == 0 means "use default size". TODO: find a reasonable default */ + if (ioctl(fd, DMX_SET_BUFFER_SIZE, uBufferSize) < 0) + lt_info("%s DMX_SET_BUFFER_SIZE failed (%m)\n", __func__); + } + buffersize = uBufferSize; + + return true; +} + +void cDemux::Close(void) +{ + lt_debug("%s #%d, fd = %d\n", __FUNCTION__, num, fd); + if (fd < 0) + { + lt_info("%s #%d: not open!\n", __FUNCTION__, num); + return; + } + pesfds.clear(); + ioctl(fd, DMX_STOP); + close(fd); + fd = -1; + if (measure) + return; + if (dmx_type == DMX_TP_CHANNEL) + { + dmx_tp_count--; + if (dmx_tp_count < 0) + { + lt_info("%s dmx_tp_count < 0!!\n", __func__); + dmx_tp_count = 0; + } + } +} + +bool cDemux::Start(bool) +{ + lt_debug("%s #%d fd: %d type: %s\n", __func__, num, fd, DMX_T[dmx_type]); + if (fd < 0) + { + lt_info("%s #%d: not open!\n", __FUNCTION__, num); + return false; + } + ioctl(fd, DMX_START); + return true; +} + +bool cDemux::Stop(void) +{ + lt_debug("%s #%d fd: %d type: %s\n", __func__, num, fd, DMX_T[dmx_type]); + if (fd < 0) + { + lt_info("%s #%d: not open!\n", __FUNCTION__, num); + return false; + } + ioctl(fd, DMX_STOP); + return true; +} + +int cDemux::Read(unsigned char *buff, int len, int timeout) +{ +#if 0 + if (len != 4095 && timeout != 100) + fprintf(stderr, "cDemux::%s #%d fd: %d type: %s len: %d timeout: %d\n", + __FUNCTION__, num, fd, DMX_T[dmx_type], len, timeout); +#endif + int rc; + struct pollfd ufds; + ufds.fd = fd; + ufds.events = POLLIN|POLLPRI|POLLERR; + ufds.revents = 0; + + if (timeout > 0) + { + retry: + rc = ::poll(&ufds, 1, timeout); + if (!rc) + return 0; // timeout + else if (rc < 0) + { + dmx_err("poll: %s,", strerror(errno), 0) + //lt_info("%s poll: %m\n", __FUNCTION__); + /* happens, when running under gdb... */ + if (errno == EINTR) + goto retry; + return -1; + } +#if 0 + if (ufds.revents & POLLERR) /* POLLERR means buffer error, i.e. buffer overflow */ + { + dmx_err("received %s,", "POLLERR", ufds.revents); + /* this seems to happen sometimes at recording start, without bad effects */ + return 0; + } +#endif + if (ufds.revents & POLLHUP) /* we get POLLHUP if e.g. a too big DMX_BUFFER_SIZE was set */ + { + dmx_err("received %s,", "POLLHUP", ufds.revents); + return -1; + } + if (!(ufds.revents & POLLIN)) /* we requested POLLIN but did not get it? */ + { + dmx_err("received %s, please report!", "POLLIN", ufds.revents); + return 0; + } + } + + rc = ::read(fd, buff, len); + //fprintf(stderr, "fd %d ret: %d\n", fd, rc); + if (rc < 0) + dmx_err("read: %s", strerror(errno), 0); + + return rc; +} + +bool cDemux::sectionFilter(unsigned short pid, const unsigned char * const filter, + const unsigned char * const mask, int len, int timeout, + const unsigned char * const negmask) +{ + memset(&s_flt, 0, sizeof(s_flt)); + + if (len > DMX_FILTER_SIZE) + { + lt_info("%s #%d: len too long: %d, DMX_FILTER_SIZE %d\n", __func__, num, len, DMX_FILTER_SIZE); + len = DMX_FILTER_SIZE; + } + s_flt.pid = pid; + s_flt.timeout = timeout; + memcpy(s_flt.filter.filter, filter, len); + memcpy(s_flt.filter.mask, mask, len); + if (negmask != NULL) + memcpy(s_flt.filter.mode, negmask, len); + + s_flt.flags = DMX_IMMEDIATE_START|DMX_CHECK_CRC; + + int to = 0; + switch (filter[0]) { + case 0x00: /* program_association_section */ + to = 2000; + break; + case 0x01: /* conditional_access_section */ + to = 6000; + break; + case 0x02: /* program_map_section */ + to = 1500; + break; + case 0x03: /* transport_stream_description_section */ + to = 10000; + break; + /* 0x04 - 0x3F: reserved */ + case 0x40: /* network_information_section - actual_network */ + to = 10000; + break; + case 0x41: /* network_information_section - other_network */ + to = 15000; + break; + case 0x42: /* service_description_section - actual_transport_stream */ + to = 10000; + break; + /* 0x43 - 0x45: reserved for future use */ + case 0x46: /* service_description_section - other_transport_stream */ + to = 10000; + break; + /* 0x47 - 0x49: reserved for future use */ + case 0x4A: /* bouquet_association_section */ + to = 11000; + break; + /* 0x4B - 0x4D: reserved for future use */ + case 0x4E: /* event_information_section - actual_transport_stream, present/following */ + to = 2000; + break; + case 0x4F: /* event_information_section - other_transport_stream, present/following */ + to = 10000; + break; + /* 0x50 - 0x5F: event_information_section - actual_transport_stream, schedule */ + /* 0x60 - 0x6F: event_information_section - other_transport_stream, schedule */ + case 0x70: /* time_date_section */ + s_flt.flags &= ~DMX_CHECK_CRC; /* section has no CRC */ + //s_flt.pid = 0x0014; + to = 30000; + break; + case 0x71: /* running_status_section */ + s_flt.flags &= ~DMX_CHECK_CRC; /* section has no CRC */ + to = 0; + break; + case 0x72: /* stuffing_section */ + s_flt.flags &= ~DMX_CHECK_CRC; /* section has no CRC */ + to = 0; + break; + case 0x73: /* time_offset_section */ + //s_flt.pid = 0x0014; + to = 30000; + break; + /* 0x74 - 0x7D: reserved for future use */ + case 0x7E: /* discontinuity_information_section */ + s_flt.flags &= ~DMX_CHECK_CRC; /* section has no CRC */ + to = 0; + break; + case 0x7F: /* selection_information_section */ + to = 0; + break; + /* 0x80 - 0x8F: ca_message_section */ + /* 0x90 - 0xFE: user defined */ + /* 0xFF: reserved */ + default: + break; +// return -1; + } + /* the negmask == NULL is a hack: the users of negmask are PMT-update + * and sectionsd EIT-Version change. And they really want no timeout + * if timeout == 0 instead of "default timeout" */ + if (timeout == 0 && negmask == NULL) + s_flt.timeout = to; + + lt_debug("%s #%d pid:0x%04hx fd:%d type:%s len:%d to:%d flags:%x flt[0]:%02x\n", __func__, num, + pid, fd, DMX_T[dmx_type], len, s_flt.timeout,s_flt.flags, s_flt.filter.filter[0]); +#if 0 + fprintf(stderr,"filt: ");for(int i=0;i= 0x0002 && pid <= 0x000f) || pid >= 0x1fff) + return false; + + lt_debug("%s #%d pid: 0x%04hx fd: %d type: %s\n", __FUNCTION__, num, pid, fd, DMX_T[dmx_type]); + + memset(&p_flt, 0, sizeof(p_flt)); + p_flt.pid = pid; + p_flt.output = DMX_OUT_DECODER; + p_flt.input = DMX_IN_FRONTEND; + + switch (dmx_type) { + case DMX_PCR_ONLY_CHANNEL: + p_flt.pes_type = DMX_PES_PCR; + if (HAL_nodec) + return true; + break; + case DMX_AUDIO_CHANNEL: + p_flt.pes_type = DMX_PES_OTHER; + p_flt.output = DMX_OUT_TSDEMUX_TAP; + if (HAL_nodec) /* no need to demux if we don't decode... */ + return true; + break; + case DMX_VIDEO_CHANNEL: + p_flt.pes_type = DMX_PES_OTHER; + p_flt.output = DMX_OUT_TSDEMUX_TAP; + if (HAL_nodec) + return true; + break; + case DMX_PES_CHANNEL: + p_flt.pes_type = DMX_PES_OTHER; + p_flt.output = DMX_OUT_TAP; + break; + case DMX_TP_CHANNEL: + p_flt.pes_type = DMX_PES_OTHER; + p_flt.output = DMX_OUT_TSDEMUX_TAP; + break; + default: + lt_info("%s #%d invalid dmx_type %d!\n", __func__, num, dmx_type); + return false; + } + return (ioctl(fd, DMX_SET_PES_FILTER, &p_flt) >= 0); +} + +void cDemux::SetSyncMode(AVSYNC_TYPE /*mode*/) +{ + lt_debug("%s #%d\n", __FUNCTION__, num); +} + +void *cDemux::getBuffer() +{ + lt_debug("%s #%d\n", __FUNCTION__, num); + return NULL; +} + +void *cDemux::getChannel() +{ + lt_debug("%s #%d\n", __FUNCTION__, num); + return NULL; +} + +bool cDemux::addPid(unsigned short Pid) +{ + lt_debug("%s: pid 0x%04hx\n", __func__, Pid); + pes_pids pfd; + int ret; + if (dmx_type != DMX_TP_CHANNEL) + { + lt_info("%s pes_type %s not implemented yet! pid=%hx\n", __FUNCTION__, DMX_T[dmx_type], Pid); + return false; + } + if (fd == -1) + lt_info("%s bucketfd not yet opened? pid=%hx\n", __FUNCTION__, Pid); + pfd.fd = fd; /* dummy */ + pfd.pid = Pid; + pesfds.push_back(pfd); + ret = (ioctl(fd, DMX_ADD_PID, &Pid)); + if (ret < 0) + lt_info("%s: DMX_ADD_PID (%m)\n", __func__); + return (ret != -1); +} + +void cDemux::removePid(unsigned short Pid) +{ + if (dmx_type != DMX_TP_CHANNEL) + { + lt_info("%s pes_type %s not implemented yet! pid=%hx\n", __FUNCTION__, DMX_T[dmx_type], Pid); + return; + } + for (std::vector::iterator i = pesfds.begin(); i != pesfds.end(); ++i) + { + if ((*i).pid == Pid) { + lt_debug("removePid: removing demux fd %d pid 0x%04x\n", fd, Pid); + if (ioctl(fd, DMX_REMOVE_PID, Pid) < 0) + lt_info("%s: (DMX_REMOVE_PID, 0x%04hx): %m\n", __func__, Pid); + pesfds.erase(i); + return; /* TODO: what if the same PID is there multiple times */ + } + } + lt_info("%s pid 0x%04x not found\n", __FUNCTION__, Pid); +} + +void cDemux::getSTC(int64_t * STC) +{ + int64_t pts = 0; + if (videoDecoder) + pts = videoDecoder->GetPTS(); + *STC = pts; +} + +int cDemux::getUnit(void) +{ + lt_debug("%s #%d\n", __FUNCTION__, num); + /* just guessed that this is the right thing to do. + right now this is only used by the CA code which is stubbed out + anyway */ + return num; +} + +bool cDemux::SetSource(int unit, int source) +{ + lt_info_c("%s(%d, %d): not implemented yet\n", __func__, unit, source); + return true; +} + +int cDemux::GetSource(int unit) +{ + lt_info_c("%s(%d): not implemented yet\n", __func__, unit); + return 0; +} diff --git a/generic-pc/dmx_cs.h b/generic-pc/dmx_cs.h new file mode 100644 index 0000000..175d8cb --- /dev/null +++ b/generic-pc/dmx_cs.h @@ -0,0 +1 @@ +#include "dmx_lib.h" diff --git a/generic-pc/dmx_lib.h b/generic-pc/dmx_lib.h new file mode 100644 index 0000000..754511e --- /dev/null +++ b/generic-pc/dmx_lib.h @@ -0,0 +1,70 @@ +#ifndef __DEMUX_TD_H +#define __DEMUX_TD_H + +#include +#include +#include +#include +#include +#include "../common/cs_types.h" + +#define MAX_DMX_UNITS 4 + +typedef enum +{ + DMX_INVALID = 0, + DMX_VIDEO_CHANNEL = 1, + DMX_AUDIO_CHANNEL, + DMX_PES_CHANNEL, + DMX_PSI_CHANNEL, + DMX_PIP_CHANNEL, + DMX_TP_CHANNEL, + DMX_PCR_ONLY_CHANNEL +} DMX_CHANNEL_TYPE; + +typedef struct +{ + int fd; + unsigned short pid; +} pes_pids; + +class cDemux +{ + private: + int num; + int fd; + int buffersize; + bool measure; + uint64_t last_measure, last_data; + DMX_CHANNEL_TYPE dmx_type; + std::vector pesfds; + struct dmx_sct_filter_params s_flt; + struct dmx_pes_filter_params p_flt; + public: + + bool Open(DMX_CHANNEL_TYPE pes_type, void * x = NULL, int y = 0); + void Close(void); + bool Start(bool record = false); + bool Stop(void); + int Read(unsigned char *buff, int len, int Timeout = 0); + bool sectionFilter(unsigned short pid, const unsigned char * const filter, const unsigned char * const mask, int len, int Timeout = 0, const unsigned char * const negmask = NULL); + bool pesFilter(const unsigned short pid); + void SetSyncMode(AVSYNC_TYPE mode); + void * getBuffer(); + void * getChannel(); + DMX_CHANNEL_TYPE getChannelType(void) { return dmx_type; }; + bool addPid(unsigned short pid); + void getSTC(int64_t * STC); + int getUnit(void); + static bool SetSource(int unit, int source); + static int GetSource(int unit); + // TD only functions + int getFD(void) { return fd; }; /* needed by cPlayback class */ + void removePid(unsigned short Pid); /* needed by cRecord class */ + std::vector getPesPids(void) { return pesfds; }; + // + cDemux(int num = 0); + ~cDemux(); +}; + +#endif //__DEMUX_H diff --git a/generic-pc/glfb.cpp b/generic-pc/glfb.cpp new file mode 100644 index 0000000..cf6b77b --- /dev/null +++ b/generic-pc/glfb.cpp @@ -0,0 +1,597 @@ +/* + Copyright 2010 Carsten Juttner + Copyright 2012,2013 Stefan Seyfried + + 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 2 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 . + + openGL based framebuffer implementation + based on Carjay's neutrino-hd-dvbapi work, see + http://gitorious.org/neutrino-hd/neutrino-hd-dvbapi + + TODO: AV-Sync code is "experimental" at best +*/ + +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "glfb.h" +#include "video_lib.h" +#include "audio_lib.h" + +#include "lt_debug.h" + +#define lt_debug_c(args...) _lt_debug(HAL_DEBUG_INIT, NULL, args) +#define lt_info_c(args...) _lt_info(HAL_DEBUG_INIT, NULL, args) +#define lt_debug(args...) _lt_debug(HAL_DEBUG_INIT, this, args) +#define lt_info(args...) _lt_info(HAL_DEBUG_INIT, this, args) + + +extern cVideo *videoDecoder; +extern cAudio *audioDecoder; + +static GLFramebuffer *gThiz = 0; /* GLUT does not allow for an arbitrary argument to the render func */ + +GLFramebuffer::GLFramebuffer(int x, int y): mReInit(true), mShutDown(false), mInitDone(false) +{ + mState.width = x; + mState.height = y; + mX = &_mX[0]; + mY = &_mY[0]; + *mX = x; + *mY = y; + av_reduce(&mOA.num, &mOA.den, x, y, INT_MAX); + mVA = mOA; /* initial aspect ratios are from the FB resolution, those */ + _mVA = mVA; /* will be updated by the videoDecoder functions anyway */ + mVAchanged = true; + mCrop = DISPLAY_AR_MODE_PANSCAN; + zoom = 1.0; + xscale = 1.0; + const char *tmp = getenv("GLFB_FULLSCREEN"); + mFullscreen = !!(tmp); + + mState.blit = true; + last_apts = 0; + + /* linux framebuffer compat mode */ + screeninfo.bits_per_pixel = 32; + screeninfo.xres = mState.width; + screeninfo.xres_virtual = screeninfo.xres; + screeninfo.yres = mState.height; + screeninfo.yres_virtual = screeninfo.yres; + screeninfo.blue.length = 8; + screeninfo.blue.offset = 0; + screeninfo.green.length = 8; + screeninfo.green.offset = 8; + screeninfo.red.length = 8; + screeninfo.red.offset = 16; + screeninfo.transp.length = 8; + screeninfo.transp.offset = 24; + + unlink("/tmp/neutrino.input"); + mkfifo("/tmp/neutrino.input", 0600); + input_fd = open("/tmp/neutrino.input", O_RDWR|O_CLOEXEC|O_NONBLOCK); + if (input_fd < 0) + lt_info("%s: could not open /tmp/neutrino.input FIFO: %m\n", __func__); + initKeys(); + OpenThreads::Thread::start(); + while (!mInitDone) + usleep(1); +} + +GLFramebuffer::~GLFramebuffer() +{ + mShutDown = true; + OpenThreads::Thread::join(); + if (input_fd >= 0) + close(input_fd); +} + +void GLFramebuffer::initKeys() +{ + mSpecialMap[GLUT_KEY_UP] = KEY_UP; + mSpecialMap[GLUT_KEY_DOWN] = KEY_DOWN; + mSpecialMap[GLUT_KEY_LEFT] = KEY_LEFT; + mSpecialMap[GLUT_KEY_RIGHT] = KEY_RIGHT; + + mSpecialMap[GLUT_KEY_F1] = KEY_RED; + mSpecialMap[GLUT_KEY_F2] = KEY_GREEN; + mSpecialMap[GLUT_KEY_F3] = KEY_YELLOW; + mSpecialMap[GLUT_KEY_F4] = KEY_BLUE; + + mSpecialMap[GLUT_KEY_F5] = KEY_WWW; + mSpecialMap[GLUT_KEY_F6] = KEY_SUBTITLE; + mSpecialMap[GLUT_KEY_F7] = KEY_MOVE; + mSpecialMap[GLUT_KEY_F8] = KEY_SLEEP; + + mSpecialMap[GLUT_KEY_PAGE_UP] = KEY_PAGEUP; + mSpecialMap[GLUT_KEY_PAGE_DOWN] = KEY_PAGEDOWN; + + mKeyMap[0x0d] = KEY_OK; + mKeyMap[0x1b] = KEY_EXIT; + mKeyMap['e'] = KEY_EPG; + mKeyMap['i'] = KEY_INFO; + mKeyMap['m'] = KEY_MENU; + + mKeyMap['+'] = KEY_VOLUMEUP; + mKeyMap['-'] = KEY_VOLUMEDOWN; + mKeyMap['.'] = KEY_MUTE; + mKeyMap['h'] = KEY_HELP; + mKeyMap['p'] = KEY_POWER; + + mKeyMap['0'] = KEY_0; + mKeyMap['1'] = KEY_1; + mKeyMap['2'] = KEY_2; + mKeyMap['3'] = KEY_3; + mKeyMap['4'] = KEY_4; + mKeyMap['5'] = KEY_5; + mKeyMap['6'] = KEY_6; + mKeyMap['7'] = KEY_7; + mKeyMap['8'] = KEY_8; + mKeyMap['9'] = KEY_9; + + mKeyMap['r'] = KEY_RED; + mKeyMap['g'] = KEY_GREEN; + mKeyMap['y'] = KEY_YELLOW; + mKeyMap['b'] = KEY_BLUE; +} + +void GLFramebuffer::run() +{ + setupCtx(); + setupOSDBuffer(); + mInitDone = true; /* signal that setup is finished */ + + /* init the good stuff */ + GLenum err = glewInit(); + if(err == GLEW_OK) + { + if((!GLEW_VERSION_1_5)||(!GLEW_EXT_pixel_buffer_object)||(!GLEW_ARB_texture_non_power_of_two)) + { + lt_info("GLFB: Sorry, your graphics card is not supported. " + "Needs at least OpenGL 1.5, pixel buffer objects and NPOT textures.\n"); + lt_info("incompatible graphics card: %m"); + _exit(1); /* Life is hard */ + } + else + { + gThiz = this; + glutSetCursor(GLUT_CURSOR_NONE); + glutDisplayFunc(GLFramebuffer::rendercb); + glutKeyboardFunc(GLFramebuffer::keyboardcb); + glutSpecialFunc(GLFramebuffer::specialcb); + glutReshapeFunc(GLFramebuffer::resizecb); + setupGLObjects(); /* needs GLEW prototypes */ + glutSetOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_CONTINUE_EXECUTION); + glutMainLoop(); + releaseGLObjects(); + } + } + else + lt_info("GLFB: error initializing glew: %d\n", err); + lt_info("GLFB: GL thread stopping\n"); +} + + +void GLFramebuffer::setupCtx() +{ + int argc = 1; + /* some dummy commandline for GLUT to be happy */ + char const *argv[2] = { "neutrino", 0 }; + lt_info("GLFB: GL thread starting\n"); + glutInit(&argc, const_cast(argv)); + glutInitWindowSize(mX[0], mY[0]); + glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH); + glutCreateWindow("Neutrino"); +} + +void GLFramebuffer::setupOSDBuffer() +{ /* the OSD buffer size can be decoupled from the actual + window size since the GL can blit-stretch with no + trouble at all, ah, the luxury of ignorance... */ + // mMutex.lock(); + if (mState.width && mState.height) + { + /* 32bit FB depth, *2 because tuxtxt uses a shadow buffer */ + int fbmem = mState.width * mState.height * 4 * 2; + mOSDBuffer.resize(fbmem); + lt_info("GLFB: OSD buffer set to %d bytes\n", fbmem); + } +} + +void GLFramebuffer::setupGLObjects() +{ + unsigned char buf[4] = { 0, 0, 0, 0 }; /* 1 black pixel */ + glGenTextures(1, &mState.osdtex); + glGenTextures(1, &mState.displaytex); + glBindTexture(GL_TEXTURE_2D, mState.osdtex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, mState.width, mState.height, 0, GL_BGRA, GL_UNSIGNED_BYTE, 0); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glBindTexture(GL_TEXTURE_2D, mState.displaytex); /* we do not yet know the size so will set that inline */ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE); + + glGenBuffers(1, &mState.pbo); + glGenBuffers(1, &mState.displaypbo); + + /* hack to start with black video buffer instead of white */ + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mState.displaypbo); + glBufferData(GL_PIXEL_UNPACK_BUFFER, sizeof(buf), buf, GL_STREAM_DRAW_ARB); + glBindTexture(GL_TEXTURE_2D, mState.displaytex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_BGRA, GL_UNSIGNED_BYTE, 0); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); +} + + +void GLFramebuffer::releaseGLObjects() +{ + glDeleteBuffers(1, &mState.pbo); + glDeleteBuffers(1, &mState.displaypbo); + glDeleteTextures(1, &mState.osdtex); + glDeleteTextures(1, &mState.displaytex); +} + + +/* static */ void GLFramebuffer::rendercb() +{ + gThiz->render(); +} + + +/* static */ void GLFramebuffer::keyboardcb(unsigned char key, int /*x*/, int /*y*/) +{ + lt_debug_c("GLFB::%s: 0x%x\n", __func__, key); + struct input_event ev; + if (key == 'f') + { + lt_info_c("GLFB::%s: toggle fullscreen %s\n", __func__, gThiz->mFullscreen?"off":"on"); + gThiz->mFullscreen = !(gThiz->mFullscreen); + gThiz->mReInit = true; + return; + } + std::map::const_iterator i = gThiz->mKeyMap.find(key); + if (i == gThiz->mKeyMap.end()) + return; + ev.code = i->second; + ev.value = 1; /* key own */ + ev.type = EV_KEY; + gettimeofday(&ev.time, NULL); + lt_debug_c("GLFB::%s: pushing 0x%x\n", __func__, ev.code); + write(gThiz->input_fd, &ev, sizeof(ev)); + ev.value = 0; /* neutrino is stupid, so push key up directly after key down */ + write(gThiz->input_fd, &ev, sizeof(ev)); +} + +/* static */ void GLFramebuffer::specialcb(int key, int /*x*/, int /*y*/) +{ + lt_debug_c("GLFB::%s: 0x%x\n", __func__, key); + struct input_event ev; + std::map::const_iterator i = gThiz->mSpecialMap.find(key); + if (i == gThiz->mSpecialMap.end()) + return; + ev.code = i->second; + ev.value = 1; + ev.type = EV_KEY; + gettimeofday(&ev.time, NULL); + lt_debug_c("GLFB::%s: pushing 0x%x\n", __func__, ev.code); + write(gThiz->input_fd, &ev, sizeof(ev)); + ev.value = 0; + write(gThiz->input_fd, &ev, sizeof(ev)); +} + +int sleep_us = 30000; + +void GLFramebuffer::render() +{ + if(mShutDown) + glutLeaveMainLoop(); + + mReInitLock.lock(); + if (mReInit) + { + int xoff = 0; + int yoff = 0; + mVAchanged = true; + mReInit = false; + mX = &_mX[mFullscreen]; + mY = &_mY[mFullscreen]; + if (mFullscreen) { + int x = glutGet(GLUT_SCREEN_WIDTH); + int y = glutGet(GLUT_SCREEN_HEIGHT); + *mX = x; + *mY = y; + AVRational a = { x, y }; + if (av_cmp_q(a, mOA) < 0) + *mY = x * mOA.den / mOA.num; + else if (av_cmp_q(a, mOA) > 0) + *mX = y * mOA.num / mOA.den; + xoff = (x - *mX) / 2; + yoff = (y - *mY) / 2; + glutFullScreen(); + } else + *mX = *mY * mOA.num / mOA.den; + lt_info("%s: reinit mX:%d mY:%d xoff:%d yoff:%d fs %d\n", + __func__, *mX, *mY, xoff, yoff, mFullscreen); + glViewport(xoff, yoff, *mX, *mY); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + float aspect = static_cast(*mX)/ *mY; + float osdaspect = static_cast(mOA.den) / mOA.num; + + glOrtho(aspect*-osdaspect, aspect*osdaspect, -1.0, 1.0, -1.0, 1.0 ); + glClearColor(0.0, 0.0, 0.0, 1.0); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glEnable(GL_BLEND); + glEnable(GL_TEXTURE_2D); + glDisable(GL_DEPTH_TEST); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + mReInitLock.unlock(); + if (!mFullscreen && (*mX != glutGet(GLUT_WINDOW_WIDTH) || *mY != glutGet(GLUT_WINDOW_HEIGHT))) + glutReshapeWindow(*mX, *mY); + + bltDisplayBuffer(); /* decoded video stream */ + if (mState.blit) { + /* only blit manually after fb->blit(), this helps to find missed blit() calls */ + mState.blit = false; + lt_debug("GLFB::%s blit!\n", __func__); + bltOSDBuffer(); /* OSD */ + } + + glBindTexture(GL_TEXTURE_2D, mState.osdtex); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + if (mVAchanged) + { + mVAchanged = false; + zoom = 1.0; + xscale = 1.0; + int cmp = (mCrop == DISPLAY_AR_MODE_NONE) ? 0 : av_cmp_q(mVA, mOA); + const AVRational a149 = { 14, 9 }; + switch (cmp) { + default: + case INT_MIN: /* invalid */ + case 0: /* identical */ + lt_debug("%s: mVA == mOA (or fullscreen mode :-)\n", __func__); + break; + case 1: /* mVA > mOA -- video is wider than display */ + lt_debug("%s: mVA > mOA\n", __func__); + xscale = av_q2d(mVA) / av_q2d(mOA); + switch (mCrop) { + case DISPLAY_AR_MODE_PANSCAN: + break; + case DISPLAY_AR_MODE_LETTERBOX: + zoom = av_q2d(mOA) / av_q2d(mVA); + break; + case DISPLAY_AR_MODE_PANSCAN2: + zoom = av_q2d(mOA) / av_q2d(a149); + break; + default: + break; + } + break; + case -1: /* mVA < mOA -- video is taller than display */ + lt_debug("%s: mVA < mOA\n", __func__); + xscale = av_q2d(mVA) / av_q2d(mOA); + switch (mCrop) { + case DISPLAY_AR_MODE_LETTERBOX: + break; + case DISPLAY_AR_MODE_PANSCAN2: + if (av_cmp_q(a149, mOA) < 0) { + zoom = av_q2d(mVA) * av_q2d(a149) / av_q2d(mOA); + break; + } + /* fallthrough for output format 14:9 */ + case DISPLAY_AR_MODE_PANSCAN: + zoom = av_q2d(mOA) / av_q2d(mVA); + break; + default: + break; + } + break; + } + } + glBindTexture(GL_TEXTURE_2D, mState.displaytex); + drawSquare(zoom, xscale); + glBindTexture(GL_TEXTURE_2D, mState.osdtex); + drawSquare(1.0, -100); + + glFlush(); + glutSwapBuffers(); + + GLuint err = glGetError(); + if (err != 0) + lt_info("GLFB::%s: GLError:%d 0x%04x\n", __func__, err, err); + if (sleep_us > 0) + usleep(sleep_us); + glutPostRedisplay(); +} + +/* static */ void GLFramebuffer::resizecb(int w, int h) +{ + gThiz->checkReinit(w, h); +} + +void GLFramebuffer::checkReinit(int x, int y) +{ + static int last_x = 0, last_y = 0; + + mReInitLock.lock(); + if (!mFullscreen && !mReInit && (x != *mX || y != *mY)) { + if (x != *mX && abs(x - last_x) > 2) { + *mX = x; + *mY = *mX * mOA.den / mOA.num; + } else if (y != *mY && abs(y - last_y) > 2) { + *mY = y; + *mX = *mY * mOA.num / mOA.den; + } + mReInit = true; + } + mReInitLock.unlock(); + last_x = x; + last_y = y; +} + +void GLFramebuffer::drawSquare(float size, float x_factor) +{ + GLfloat vertices[] = { + 1.0f, 1.0f, + -1.0f, 1.0f, + -1.0f, -1.0f, + 1.0f, -1.0f, + }; + + GLubyte indices[] = { 0, 1, 2, 3 }; + + GLfloat texcoords[] = { + 1.0, 0.0, + 0.0, 0.0, + 0.0, 1.0, + 1.0, 1.0, + }; + if (x_factor > -99.0) { /* x_factor == -100 => OSD */ + if (videoDecoder && + videoDecoder->pig_x > 0 && videoDecoder->pig_y > 0 && + videoDecoder->pig_w > 0 && videoDecoder->pig_h > 0) { + /* these calculations even consider cropping and panscan mode + * maybe this could be done with some clever opengl tricks? */ + double w2 = (double)mState.width * 0.5l; + double h2 = (double)mState.height * 0.5l; + double x = (double)(videoDecoder->pig_x - w2) / w2 / x_factor / size; + double y = (double)(h2 - videoDecoder->pig_y) / h2 / size; + double w = (double)videoDecoder->pig_w / w2; + double h = (double)videoDecoder->pig_h / h2; + x += ((1.0l - x_factor * size) / 2.0l) * w / x_factor / size; + y += ((size - 1.0l) / 2.0l) * h / size; + vertices[0] = x + w; /* top right x */ + vertices[1] = y; /* top right y */ + vertices[2] = x; /* top left x */ + vertices[3] = y; /* top left y */ + vertices[4] = x; /* bottom left x */ + vertices[5] = y - h; /* bottom left y */ + vertices[6] = vertices[0]; /* bottom right x */ + vertices[7] = vertices[5]; /* bottom right y */ + } + } else + x_factor = 1.0; /* OSD */ + + glPushMatrix(); + glScalef(size * x_factor, size, size); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glVertexPointer(2, GL_FLOAT, 0, vertices); + glTexCoordPointer(2, GL_FLOAT, 0, texcoords); + glDrawElements(GL_QUADS, 4, GL_UNSIGNED_BYTE, indices); + glDisableClientState(GL_VERTEX_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glPopMatrix(); +} + + +void GLFramebuffer::bltOSDBuffer() +{ + /* FIXME: copy each time */ + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mState.pbo); + glBufferData(GL_PIXEL_UNPACK_BUFFER, mOSDBuffer.size(), &mOSDBuffer[0], GL_STREAM_DRAW_ARB); + + glBindTexture(GL_TEXTURE_2D, mState.osdtex); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, mState.width, mState.height, GL_BGRA, GL_UNSIGNED_BYTE, 0); + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); +} + +void GLFramebuffer::bltDisplayBuffer() +{ + if (!videoDecoder) /* cannot start yet */ + return; + static bool warn = true; + cVideo::SWFramebuffer *buf = videoDecoder->getDecBuf(); + if (!buf) { + if (warn) + lt_info("GLFB::%s did not get a buffer...\n", __func__); + warn = false; + return; + } + warn = true; + int w = buf->width(), h = buf->height(); + if (w == 0 || h == 0) + return; + + AVRational a = buf->AR(); + if (a.den != 0 && a.num != 0 && av_cmp_q(a, _mVA)) { + _mVA = a; + /* _mVA is the raw buffer's aspect, mVA is the real scaled output aspect */ + av_reduce(&mVA.num, &mVA.den, w * a.num, h * a.den, INT_MAX); + mVAchanged = true; + } + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, mState.displaypbo); + glBufferData(GL_PIXEL_UNPACK_BUFFER, buf->size(), &(*buf)[0], GL_STREAM_DRAW_ARB); + + glBindTexture(GL_TEXTURE_2D, mState.displaytex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_BGRA, GL_UNSIGNED_BYTE, 0); + + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + + /* "rate control" mechanism starts here... + * this implementation is pretty naive and not working too well, but + * better this than nothing... :-) */ + int64_t apts = 0; + /* 18000 is the magic value for A/V sync in my libao->pulseaudio->intel_hda setup */ + int64_t vpts = buf->pts() + 18000; + if (audioDecoder) + apts = audioDecoder->getPts(); + if (apts != last_apts) { + int rate, dummy1, dummy2; + if (apts < vpts) + sleep_us = (sleep_us * 2 + (vpts - apts)*10/9) / 3; + else if (sleep_us > 1000) + sleep_us -= 1000; + last_apts = apts; + videoDecoder->getPictureInfo(dummy1, dummy2, rate); + if (rate > 0) + rate = 2000000 / rate; /* limit to half the frame rate */ + else + rate = 50000; /* minimum 20 fps */ + if (sleep_us > rate) + sleep_us = rate; + else if (sleep_us < 1) + sleep_us = 1; + } + lt_debug("vpts: 0x%" PRIx64 " apts: 0x%" PRIx64 " diff: %6.3f sleep_us %d buf %d\n", + buf->pts(), apts, (buf->pts() - apts)/90000.0, sleep_us, videoDecoder->buf_num); +} + +void GLFramebuffer::clear() +{ + /* clears front and back buffer */ + memset(&mOSDBuffer[0], 0, mOSDBuffer.size()); +} diff --git a/generic-pc/glfb.h b/generic-pc/glfb.h new file mode 100644 index 0000000..c6999d5 --- /dev/null +++ b/generic-pc/glfb.h @@ -0,0 +1,107 @@ +/* + Copyright 2010 Carsten Juttner + Copyright 2012,2013 Stefan Seyfried + + 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 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef __glthread__ +#define __glthread__ +#include +#include +#include +#include +#include +#include +#include +#include /* for screeninfo etc. */ +extern "C" { +#include +} + +class GLFramebuffer : public OpenThreads::Thread +{ +public: + GLFramebuffer(int x, int y); + ~GLFramebuffer(); + + void run(); + std::vector *getOSDBuffer() { return &mOSDBuffer; } /* pointer to OSD bounce buffer */ + + int getOSDWidth() { return mState.width; } + int getOSDHeight() { return mState.height; } + void blit() { mState.blit = true; } + + void setOutputFormat(AVRational a, int h, int c) { mOA = a; *mY = h; mCrop = c; mReInit = true; } + + void clear(); + fb_var_screeninfo getScreenInfo() { return screeninfo; } + +private: + fb_var_screeninfo screeninfo; + int *mX; + int *mY; + int _mX[2]; /* output window size */ + int _mY[2]; /* [0] = normal, [1] = fullscreen */ + AVRational mOA; /* output window aspect ratio */ + AVRational mVA; /* video aspect ratio */ + AVRational _mVA; /* for detecting changes in mVA */ + bool mVAchanged; + float zoom; /* for cropping */ + float xscale; /* and aspect ratio */ + int mCrop; /* DISPLAY_AR_MODE */ + + bool mFullscreen; /* fullscreen? */ + bool mReInit; /* setup things for GL */ + OpenThreads::Mutex mReInitLock; + bool mShutDown; /* if set main loop is left */ + bool mInitDone; /* condition predicate */ + // OpenThreads::Condition mInitCond; /* condition variable for init */ + // mutable OpenThreads::Mutex mMutex; /* lock our data */ + + std::vector mOSDBuffer; /* silly bounce buffer */ + + std::map mKeyMap; + std::map mSpecialMap; + int input_fd; + int64_t last_apts; + + static void rendercb(); /* callback for GLUT */ + void render(); /* actual render function */ + static void keyboardcb(unsigned char key, int x, int y); + static void specialcb(int key, int x, int y); + static void resizecb(int w, int h); + void checkReinit(int w, int h); /* e.g. in case window was resized */ + + void initKeys(); /* setup key bindings for window */ + void setupCtx(); /* create the window and make the context current */ + void setupOSDBuffer(); /* create the OSD buffer */ + void setupGLObjects(); /* PBOs, textures and stuff */ + void releaseGLObjects(); + void drawSquare(float size, float x_factor = 1); /* do not be square */ + + struct { + int width; /* width and height, fixed for a framebuffer instance */ + int height; + GLuint osdtex; /* holds the OSD texture */ + GLuint pbo; /* PBO we use for transfer to texture */ + GLuint displaytex; /* holds the display texture */ + GLuint displaypbo; + bool blit; + } mState; + + void bltOSDBuffer(); + void bltDisplayBuffer(); +}; +#endif diff --git a/generic-pc/hardware_caps.c b/generic-pc/hardware_caps.c new file mode 100644 index 0000000..36b2b35 --- /dev/null +++ b/generic-pc/hardware_caps.c @@ -0,0 +1,37 @@ +/* + * determine the capabilities of the hardware. + * part of libstb-hal + * + * (C) 2010-2012 Stefan Seyfried + * + * License: GPL v2 or later + */ + +#include +#include +#include +#include +#include +#include +#include + +static int initialized = 0; +static hw_caps_t caps; + +hw_caps_t *get_hwcaps(void) +{ + if (initialized) + return ∩︀ + + memset(&caps, 0, sizeof(hw_caps_t)); + + initialized = 1; + caps.can_shutdown = 1; /* for testing */ + caps.display_type = HW_DISPLAY_LINE_TEXT; + caps.has_HDMI = 1; + caps.display_xres = 8; + strcpy(caps.boxvendor, "Generic"); + strcpy(caps.boxname, "PC"); + + return ∩︀ +} diff --git a/generic-pc/init.cpp b/generic-pc/init.cpp new file mode 100644 index 0000000..c26048b --- /dev/null +++ b/generic-pc/init.cpp @@ -0,0 +1,54 @@ +#include +#include + +#include "init_lib.h" +#include "lt_debug.h" +#include "glfb.h" +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_INIT, NULL, args) +#define lt_info(args...) _lt_info(TRIPLE_DEBUG_INIT, NULL, args) + +static bool initialized = false; +GLFramebuffer *glfb = NULL; +bool HAL_nodec = false; + +void init_td_api() +{ + if (!initialized) + lt_debug_init(); + lt_info("%s begin, initialized=%d, debug=0x%02x\n", __func__, (int)initialized, debuglevel); + if (! glfb) { + int x = 1280, y = 720; /* default OSD FB resolution */ + /* + * export GLFB_RESOLUTION=720,576 + * to restore old default behviour + */ + const char *tmp = getenv("GLFB_RESOLUTION"); + const char *p = NULL; + if (tmp) + p = strchr(tmp, ','); + if (p) { + x = atoi(tmp); + y = atoi(p + 1); + } + lt_info("%s: setting GL Framebuffer size to %dx%d\n", __func__, x, y); + if (!p) + lt_info("%s: export GLFB_RESOLUTION=\",\" to set another resolution\n", __func__); + + glfb = new GLFramebuffer(x, y); /* hard coded to PAL resolution for now */ + } + /* allow disabling of Audio/video decoders in case we just want to + * valgrind-check other parts... export HAL_NOAVDEC=1 */ + if (getenv("HAL_NOAVDEC")) + HAL_nodec = true; + /* hack, this triggers that the simple_display thread does not blit() once per second... */ + setenv("SPARK_NOBLIT", "1", 1); + initialized = true; +} + +void shutdown_td_api() +{ + lt_info("%s, initialized = %d\n", __func__, (int)initialized); + if (glfb) + delete glfb; + initialized = false; +} diff --git a/generic-pc/init_lib.h b/generic-pc/init_lib.h new file mode 100644 index 0000000..d9a6f09 --- /dev/null +++ b/generic-pc/init_lib.h @@ -0,0 +1,5 @@ +#ifndef __INIT_TD_H +#define __INIT_TD_H +void init_td_api(); +void shutdown_td_api(); +#endif diff --git a/generic-pc/playback.cpp b/generic-pc/playback.cpp new file mode 100644 index 0000000..f08de60 --- /dev/null +++ b/generic-pc/playback.cpp @@ -0,0 +1,129 @@ +#include + +#include "playback.h" + +static const char * FILENAME = "playback-dummy"; + +bool cPlayback::Open(playmode_t) +{ + return 0; +} + +void cPlayback::Close(void) +{ +} + +bool cPlayback::Start(char * filename, int vpid, int vtype, int apid, int ac3, int duration) +{ + printf("%s:%s - filename=%s vpid=%u vtype=%d apid=%u ac3=%d duration=%i\n", + FILENAME, __func__, filename, vpid, vtype, apid, ac3, duration); + return true; +} + +bool cPlayback::SetAPid(int pid, bool /*ac3*/) +{ + printf("%s:%s pid %i\n", FILENAME, __func__, pid); + return true; +} + +bool cPlayback::SelectSubtitles(int pid) +{ + printf("%s:%s pid %i\n", FILENAME, __func__, pid); + return true; +} + +bool cPlayback::SetSpeed(int speed) +{ + printf("%s:%s playing %d speed %d\n", FILENAME, __func__, playing, speed); + return true; +} + +bool cPlayback::GetSpeed(int &/*speed*/) const +{ + return true; +} + +bool cPlayback::GetPosition(int &position, int &duration) +{ + printf("%s:%s %d %d\n", FILENAME, __func__, position, duration); + position = 0; + duration = 0; + return true; +} + +bool cPlayback::SetPosition(int position, bool) +{ + printf("%s:%s %d\n", FILENAME, __func__,position); + return true; +} + +void cPlayback::FindAllPids(int *, unsigned int *, unsigned int *numpida, std::string *) +{ + printf("%s:%s\n", FILENAME, __func__); + *numpida = 0; +} + +void cPlayback::FindAllSubtitlePids(int * /*pids*/, unsigned int *numpids, std::string * /*language*/) +{ + *numpids = 0; +} + +bool cPlayback::SetSubtitlePid(int /*pid*/) +{ + return true; +} + +void cPlayback::GetPts(uint64_t &/*pts*/) +{ +} + +bool cPlayback::SetTeletextPid(int /*pid*/) +{ + return true; +} + +void cPlayback::FindAllTeletextsubtitlePids(int *, unsigned int *numpids, std::string *, int *, int *) +{ + *numpids = 0; +} + +void cPlayback::SuspendSubtitle(bool /*b*/) +{ +} + +void cPlayback::RequestAbort() +{ +} + +int cPlayback::GetTeletextPid(void) +{ + return -1; +} + +void cPlayback::FindAllSubs(int *, unsigned int *, unsigned int *numpida, std::string *) +{ + printf("%s:%s\n", FILENAME, __func__); + *numpida = 0; +} + +void cPlayback::GetChapters(std::vector &positions, std::vector &titles) +{ + positions.clear(); + titles.clear(); +} + +void cPlayback::GetMetadata(std::vector &keys, std::vector &values) +{ + keys.clear(); + values.clear(); +} + +cPlayback::cPlayback(int /*num*/) +{ + printf("%s:%s\n", FILENAME, __func__); +} + +cPlayback::~cPlayback() +{ + printf("%s:%s\n", FILENAME, __func__); +} diff --git a/generic-pc/playback.h b/generic-pc/playback.h new file mode 100644 index 0000000..aad792a --- /dev/null +++ b/generic-pc/playback.h @@ -0,0 +1,54 @@ +#ifndef __PLAYBACK_H +#define __PLAYBACK_H + +#include +#include +#include + +typedef enum { + PLAYMODE_TS = 0, + PLAYMODE_FILE, +} playmode_t; + +class cPlayback +{ + private: + bool playing; + int mAudioStream; + int mSubtitleStream; + int mTeletextStream; + public: + cPlayback(int); + bool Open(playmode_t PlayMode); + void Close(void); + bool Start(char *filename, int vpid, int vtype, int apid, int ac3, int duration); + bool SetAPid(int pid, bool ac3); + bool SetSubtitlePid(int pid); + bool SetTeletextPid(int pid); + int GetAPid(void) { return mAudioStream; } + int GetSubtitlePid(void) { return mSubtitleStream; } + int GetTeletextPid(void); + void SuspendSubtitle(bool); + int GetFirstTeletextPid(void); + bool SetSpeed(int speed); + bool GetSpeed(int &speed) const; + bool GetPosition(int &position, int &duration); + void GetPts(uint64_t &pts); + bool SetPosition(int position, bool absolute = false); + void FindAllPids(int *apids, unsigned int *ac3flags, unsigned int *numpida, std::string *language); + void FindAllSubtitlePids(int *pids, unsigned int *numpids, std::string *language); + void FindAllTeletextsubtitlePids(int *pids, unsigned int *numpidt, std::string *tlanguage, int *mags, int *pages); + void RequestAbort(void); + bool IsPlaying(void) { return false; } + uint64_t GetReadCount(void); + + void FindAllSubs(int *pids, unsigned int *supported, unsigned int *numpida, std::string *language); + bool SelectSubtitles(int pid); + + void GetChapters(std::vector &positions, std::vector &titles); + void GetMetadata(std::vector &keys, std::vector &values); + // + ~cPlayback(); +}; + +#endif diff --git a/generic-pc/pwrmngr.cpp b/generic-pc/pwrmngr.cpp new file mode 120000 index 0000000..cee9acb --- /dev/null +++ b/generic-pc/pwrmngr.cpp @@ -0,0 +1 @@ +../libspark/pwrmngr.cpp \ No newline at end of file diff --git a/generic-pc/pwrmngr.h b/generic-pc/pwrmngr.h new file mode 120000 index 0000000..e63e97b --- /dev/null +++ b/generic-pc/pwrmngr.h @@ -0,0 +1 @@ +../libspark/pwrmngr.h \ No newline at end of file diff --git a/generic-pc/record.cpp b/generic-pc/record.cpp new file mode 120000 index 0000000..4daae8d --- /dev/null +++ b/generic-pc/record.cpp @@ -0,0 +1 @@ +../libspark/record.cpp \ No newline at end of file diff --git a/generic-pc/record_lib.h b/generic-pc/record_lib.h new file mode 120000 index 0000000..1ec9332 --- /dev/null +++ b/generic-pc/record_lib.h @@ -0,0 +1 @@ +../libspark/record_lib.h \ No newline at end of file diff --git a/generic-pc/video.cpp b/generic-pc/video.cpp new file mode 100644 index 0000000..cf9e9cb --- /dev/null +++ b/generic-pc/video.cpp @@ -0,0 +1,667 @@ +/* + * (C) 2002-2003 Andreas Oberritter + * (C) 2010-2012 Stefan Seyfried + * + * 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 2 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, write to the Free Software + * Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA + * + * cVideo implementation with decoder. + * uses ffmpeg for demuxing / decoding + * decoded frames are stored in SWFramebuffer class + * + * TODO: buffer handling surely needs some locking... + */ + +#include +#include +#include +#include + +extern "C" { +#include +#include +} + +/* ffmpeg buf 32k */ +#define INBUF_SIZE 0x8000 +/* my own buf 256k */ +#define DMX_BUF_SZ 0x20000 + +#include "video_lib.h" +#include "dmx_lib.h" +#include "glfb.h" +#include "lt_debug.h" +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_VIDEO, this, args) +#define lt_info(args...) _lt_info(TRIPLE_DEBUG_VIDEO, this, args) +#define lt_info_c(args...) _lt_info(TRIPLE_DEBUG_VIDEO, NULL, args) + +cVideo *videoDecoder = NULL; +extern cDemux *videoDemux; +extern GLFramebuffer *glfb; +int system_rev = 0; + +extern bool HAL_nodec; + +static uint8_t *dmxbuf; +static int bufpos; + +static const AVRational aspect_ratios[6] = { + { 1, 1 }, + { 4, 3 }, + { 14, 9 }, + { 16, 9 }, + { 20, 9 }, + { -1,-1 } +}; + +cVideo::cVideo(int, void *, void *, unsigned int) +{ + lt_debug("%s\n", __func__); + av_register_all(); + if (!HAL_nodec) + dmxbuf = (uint8_t *)malloc(DMX_BUF_SZ); + bufpos = 0; + thread_running = false; + w_h_changed = false; + dec_w = dec_h = 0; + buf_num = 0; + buf_in = 0; + buf_out = 0; + pig_x = pig_y = pig_w = pig_h = 0; + display_aspect = DISPLAY_AR_16_9; + display_crop = DISPLAY_AR_MODE_LETTERBOX; + v_format = VIDEO_FORMAT_MPEG2; +} + +cVideo::~cVideo(void) +{ + Stop(); + /* ouch :-( */ + videoDecoder = NULL; +} + +int cVideo::setAspectRatio(int vformat, int cropping) +{ + lt_info("%s(%d, %d)\n", __func__, vformat, cropping); + if (vformat >= 0) + display_aspect = (DISPLAY_AR) vformat; + if (cropping >= 0) + display_crop = (DISPLAY_AR_MODE) cropping; + if (display_aspect < DISPLAY_AR_RAW) /* don't know what to do with this */ + glfb->setOutputFormat(aspect_ratios[display_aspect], output_h, display_crop); + return 0; +} + +int cVideo::getAspectRatio(void) +{ + buf_m.lock(); + int ret = 0; + int w, h, ar; + AVRational a; + if (buf_num == 0) + goto out; + a = buffers[buf_out].AR(); + w = buffers[buf_out].width(); + h = buffers[buf_out].height(); + if (a.den == 0 || h == 0) + goto out; + ar = w * 100 * a.num / h / a.den; + if (ar < 100 || ar > 225) /* < 4:3, > 20:9 */ + ; /* ret = 0: N/A */ + else if (ar < 140) /* 4:3 */ + ret = 1; + else if (ar < 165) /* 14:9 */ + ret = 2; + else if (ar < 200) /* 16:9 */ + ret = 3; + else + ret = 4; /* 20:9 */ + out: + buf_m.unlock(); + return ret; +} + +int cVideo::setCroppingMode(int) +{ + return 0; +} + +int cVideo::Start(void *, unsigned short, unsigned short, void *) +{ + lt_debug("%s running %d >\n", __func__, thread_running); + if (!thread_running && !HAL_nodec) + OpenThreads::Thread::start(); + lt_debug("%s running %d <\n", __func__, thread_running); + return 0; +} + +int cVideo::Stop(bool) +{ + lt_debug("%s running %d >\n", __func__, thread_running); + if (thread_running) { + thread_running = false; + OpenThreads::Thread::join(); + } + lt_debug("%s running %d <\n", __func__, thread_running); + return 0; +} + +int cVideo::setBlank(int) +{ + return 1; +} + +int cVideo::SetVideoSystem(int system, bool) +{ + int h; + switch(system) + { + case VIDEO_STD_NTSC: + case VIDEO_STD_480P: + h = 480; + break; + case VIDEO_STD_1080I60: + case VIDEO_STD_1080I50: + case VIDEO_STD_1080P30: + case VIDEO_STD_1080P24: + case VIDEO_STD_1080P25: + case VIDEO_STD_1080P50: + h = 1080; + break; + case VIDEO_STD_720P50: + case VIDEO_STD_720P60: + h = 720; + break; + case VIDEO_STD_AUTO: + lt_info("%s: VIDEO_STD_AUTO not implemented\n", __func__); + // fallthrough + case VIDEO_STD_SECAM: + case VIDEO_STD_PAL: + case VIDEO_STD_576P: + h = 576; + break; + default: + lt_info("%s: unhandled value %d\n", __func__, system); + return 0; + } + v_std = (VIDEO_STD) system; + output_h = h; + if (display_aspect < DISPLAY_AR_RAW) /* don't know what to do with this */ + glfb->setOutputFormat(aspect_ratios[display_aspect], output_h, display_crop); + return 0; +} + +int cVideo::getPlayState(void) +{ + return VIDEO_PLAYING; +} + +void cVideo::SetVideoMode(analog_mode_t) +{ +} + +void cVideo::ShowPicture(const char *fname, const char *) +{ + lt_info("%s(%s)\n", __func__, fname); + if (access(fname, R_OK)) + return; + + unsigned int i; + int stream_id = -1; + int got_frame = 0; + int len; + AVFormatContext *avfc = NULL; + AVCodecContext *c = NULL; + AVCodec *codec; + AVFrame *frame, *rgbframe; + AVPacket avpkt; + + if (avformat_open_input(&avfc, fname, NULL, NULL) < 0) { + lt_info("%s: Could not open file %s\n", __func__, fname); + return; + } + + if (avformat_find_stream_info(avfc, NULL) < 0) { + lt_info("%s: Could not find file info %s\n", __func__, fname); + goto out_close; + } + for (i = 0; i < avfc->nb_streams; i++) { + if (avfc->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { + stream_id = i; + break; + } + } + if (stream_id < 0) + goto out_close; + c = avfc->streams[stream_id]->codec; + codec = avcodec_find_decoder(c->codec_id); + if (!avcodec_open2(c, codec, NULL) < 0) { + lt_info("%s: Could not find/open the codec, id 0x%x\n", __func__, c->codec_id); + goto out_close; + } + frame = avcodec_alloc_frame(); + rgbframe = avcodec_alloc_frame(); + if (!frame || !rgbframe) { + lt_info("%s: Could not allocate video frame\n", __func__); + goto out_free; + } + av_init_packet(&avpkt); + if (av_read_frame(avfc, &avpkt) < 0) { + lt_info("%s: av_read_frame < 0\n", __func__); + goto out_free; + } + len = avcodec_decode_video2(c, frame, &got_frame, &avpkt); + if (len < 0) { + lt_info("%s: avcodec_decode_video2 %d\n", __func__, len); + av_free_packet(&avpkt); + goto out_free; + } + if (avpkt.size > len) + lt_info("%s: WARN: pkt->size %d != len %d\n", __func__, avpkt.size, len); + if (got_frame) { + unsigned int need = avpicture_get_size(PIX_FMT_RGB32, c->width, c->height); + struct SwsContext *convert = sws_getContext(c->width, c->height, c->pix_fmt, + c->width, c->height, PIX_FMT_RGB32, + SWS_BICUBIC, 0, 0, 0); + if (!convert) + lt_info("%s: ERROR setting up SWS context\n", __func__); + else { + buf_m.lock(); + SWFramebuffer *f = &buffers[buf_in]; + if (f->size() < need) + f->resize(need); + avpicture_fill((AVPicture *)rgbframe, &(*f)[0], PIX_FMT_RGB32, + c->width, c->height); + sws_scale(convert, frame->data, frame->linesize, 0, c->height, + rgbframe->data, rgbframe->linesize); + sws_freeContext(convert); + f->width(c->width); + f->height(c->height); + f->pts(AV_NOPTS_VALUE); + AVRational a = av_guess_sample_aspect_ratio(avfc, avfc->streams[stream_id], frame); + f->AR(a); + buf_in++; + buf_in %= VDEC_MAXBUFS; + buf_num++; + if (buf_num > (VDEC_MAXBUFS - 1)) { + lt_info("%s: buf_num overflow\n", __func__); + buf_out++; + buf_out %= VDEC_MAXBUFS; + buf_num--; + } + buf_m.unlock(); + } + } + av_free_packet(&avpkt); + out_free: + avcodec_close(c); + avcodec_free_frame(&frame); + avcodec_free_frame(&rgbframe); + out_close: + avformat_close_input(&avfc); + lt_debug("%s(%s) end\n", __func__, fname); +} + +void cVideo::StopPicture() +{ +} + +void cVideo::Standby(unsigned int) +{ +} + +int cVideo::getBlank(void) +{ + return 0; +} + +void cVideo::Pig(int x, int y, int w, int h, int /*osd_w*/, int /*osd_h*/, int /*startx*/, int /*starty*/, int /*endx*/, int /*endy*/) +{ + pig_x = x; + pig_y = y; + pig_w = w; + pig_h = h; +} + +void cVideo::getPictureInfo(int &width, int &height, int &rate) +{ + width = dec_w; + height = dec_h; + rate = dec_r; +} + +void cVideo::SetSyncMode(AVSYNC_TYPE) +{ +}; + +int cVideo::SetStreamType(VIDEO_FORMAT v) +{ + v_format = v; + return 0; +} + +cVideo::SWFramebuffer *cVideo::getDecBuf(void) +{ + buf_m.lock(); + if (buf_num == 0) { + buf_m.unlock(); + return NULL; + } + SWFramebuffer *p = &buffers[buf_out]; + buf_out++; + buf_num--; + buf_out %= VDEC_MAXBUFS; + buf_m.unlock(); + return p; +} + +static int my_read(void *, uint8_t *buf, int buf_size) +{ + int tmp = 0; + if (videoDecoder && bufpos < DMX_BUF_SZ - 4096) { + while (bufpos < buf_size && ++tmp < 20) { /* retry max 20 times */ + int ret = videoDemux->Read(dmxbuf + bufpos, DMX_BUF_SZ - bufpos, 20); + if (ret > 0) + bufpos += ret; + } + } + if (bufpos == 0) + return 0; + if (bufpos > buf_size) { + memcpy(buf, dmxbuf, buf_size); + memmove(dmxbuf, dmxbuf + buf_size, bufpos - buf_size); + bufpos -= buf_size; + return buf_size; + } + memcpy(buf, dmxbuf, bufpos); + tmp = bufpos; + bufpos = 0; + return tmp; +} + +void cVideo::run(void) +{ + lt_info("====================== start decoder thread ================================\n"); + AVCodec *codec; + AVCodecContext *c= NULL; + AVFormatContext *avfc = NULL; + AVInputFormat *inp; + AVFrame *frame, *rgbframe; + uint8_t *inbuf = (uint8_t *)av_malloc(INBUF_SIZE); + AVPacket avpkt; + struct SwsContext *convert = NULL; + + time_t warn_r = 0; /* last read error */ + time_t warn_d = 0; /* last decode error */ + + bufpos = 0; + buf_num = 0; + buf_in = 0; + buf_out = 0; + dec_r = 0; + + av_init_packet(&avpkt); + inp = av_find_input_format("mpegts"); + AVIOContext *pIOCtx = avio_alloc_context(inbuf, INBUF_SIZE, // internal Buffer and its size + 0, // bWriteable (1=true,0=false) + NULL, // user data; will be passed to our callback functions + my_read, // read callback + NULL, // write callback + NULL); // seek callback + avfc = avformat_alloc_context(); + avfc->pb = pIOCtx; + avfc->iformat = inp; + avfc->probesize = 188*5; + + thread_running = true; + if (avformat_open_input(&avfc, NULL, inp, NULL) < 0) { + lt_info("%s: Could not open input\n", __func__); + goto out; + } + while (avfc->nb_streams < 1) + { + lt_info("%s: nb_streams %d, should be 1 => retry\n", __func__, avfc->nb_streams); + if (av_read_frame(avfc, &avpkt) < 0) + lt_info("%s: av_read_frame < 0\n", __func__); + av_free_packet(&avpkt); + if (! thread_running) + goto out; + } + + if (avfc->streams[0]->codec->codec_type != AVMEDIA_TYPE_VIDEO) + lt_info("%s: no video codec? 0x%x\n", __func__, avfc->streams[0]->codec->codec_type); + + c = avfc->streams[0]->codec; + codec = avcodec_find_decoder(c->codec_id); + if (!codec) { + lt_info("%s: Codec for %s not found\n", __func__, avcodec_get_name(c->codec_id)); + goto out; + } + if (avcodec_open2(c, codec, NULL) < 0) { + lt_info("%s: Could not open codec\n", __func__); + goto out; + } + frame = avcodec_alloc_frame(); + rgbframe = avcodec_alloc_frame(); + if (!frame || !rgbframe) { + lt_info("%s: Could not allocate video frame\n", __func__); + goto out2; + } + lt_info("decoding %s\n", avcodec_get_name(c->codec_id)); + while (thread_running) { + if (av_read_frame(avfc, &avpkt) < 0) { + if (warn_r - time(NULL) > 4) { + lt_info("%s: av_read_frame < 0\n", __func__); + warn_r = time(NULL); + } + usleep(10000); + continue; + } + int got_frame = 0; + int len = avcodec_decode_video2(c, frame, &got_frame, &avpkt); + if (len < 0) { + if (warn_d - time(NULL) > 4) { + lt_info("%s: avcodec_decode_video2 %d\n", __func__, len); + warn_d = time(NULL); + } + av_free_packet(&avpkt); + continue; + } + if (avpkt.size > len) + lt_info("%s: WARN: pkt->size %d != len %d\n", __func__, avpkt.size, len); + if (got_frame) { + unsigned int need = avpicture_get_size(PIX_FMT_RGB32, c->width, c->height); + convert = sws_getCachedContext(convert, + c->width, c->height, c->pix_fmt, + c->width, c->height, PIX_FMT_RGB32, + SWS_BICUBIC, 0, 0, 0); + if (!convert) + lt_info("%s: ERROR setting up SWS context\n", __func__); + else { + buf_m.lock(); + SWFramebuffer *f = &buffers[buf_in]; + if (f->size() < need) + f->resize(need); + avpicture_fill((AVPicture *)rgbframe, &(*f)[0], PIX_FMT_RGB32, + c->width, c->height); + sws_scale(convert, frame->data, frame->linesize, 0, c->height, + rgbframe->data, rgbframe->linesize); + if (dec_w != c->width || dec_h != c->height) { + lt_info("%s: pic changed %dx%d -> %dx%d\n", __func__, + dec_w, dec_h, c->width, c->height); + dec_w = c->width; + dec_h = c->height; + w_h_changed = true; + } + f->width(c->width); + f->height(c->height); + int64_t vpts = av_frame_get_best_effort_timestamp(frame); + if (v_format == VIDEO_FORMAT_MPEG2) + vpts += 90000*3/10; /* 300ms */ + f->pts(vpts); + AVRational a = av_guess_sample_aspect_ratio(avfc, avfc->streams[0], frame); + f->AR(a); + buf_in++; + buf_in %= VDEC_MAXBUFS; + buf_num++; + if (buf_num > (VDEC_MAXBUFS - 1)) { + lt_info("%s: buf_num overflow\n", __func__); + buf_out++; + buf_out %= VDEC_MAXBUFS; + buf_num--; + } + dec_r = c->time_base.den/(c->time_base.num * c->ticks_per_frame); + buf_m.unlock(); + } + lt_debug("%s: time_base: %d/%d, ticks: %d rate: %d pts 0x%" PRIx64 "\n", __func__, + c->time_base.num, c->time_base.den, c->ticks_per_frame, dec_r, + av_frame_get_best_effort_timestamp(frame)); + } + av_free_packet(&avpkt); + } + sws_freeContext(convert); + out2: + avcodec_close(c); + avcodec_free_frame(&frame); + avcodec_free_frame(&rgbframe); + out: + avformat_close_input(&avfc); + av_free(pIOCtx->buffer); + av_free(pIOCtx); + /* reset output buffers */ + bufpos = 0; + buf_num = 0; + buf_in = 0; + buf_out = 0; + lt_info("======================== end decoder thread ================================\n"); +} + +static bool swscale(unsigned char *src, unsigned char *dst, int sw, int sh, int dw, int dh) +{ + bool ret = false; + struct SwsContext *scale = NULL; + AVFrame *sframe, *dframe; + scale = sws_getCachedContext(scale, sw, sh, PIX_FMT_RGB32, dw, dh, PIX_FMT_RGB32, SWS_BICUBIC, 0, 0, 0); + if (!scale) { + lt_info_c("%s: ERROR setting up SWS context\n", __func__); + return false; + } + sframe = avcodec_alloc_frame(); + dframe = avcodec_alloc_frame(); + if (!sframe || !dframe) { + lt_info_c("%s: could not alloc sframe (%p) or dframe (%p)\n", __func__, sframe, dframe); + goto out; + } + avpicture_fill((AVPicture *)sframe, &(src[0]), PIX_FMT_RGB32, sw, sh); + avpicture_fill((AVPicture *)dframe, &(dst[0]), PIX_FMT_RGB32, dw, dh); + sws_scale(scale, sframe->data, sframe->linesize, 0, sh, dframe->data, dframe->linesize); + out: + avcodec_free_frame(&sframe); + avcodec_free_frame(&dframe); + sws_freeContext(scale); + return ret; +} + +bool cVideo::GetScreenImage(unsigned char * &data, int &xres, int &yres, bool get_video, bool get_osd, bool scale_to_video) +{ + lt_info("%s: data 0x%p xres %d yres %d vid %d osd %d scale %d\n", + __func__, data, xres, yres, get_video, get_osd, scale_to_video); + SWFramebuffer video; + std::vector *osd = NULL; + std::vector s_osd; /* scaled OSD */ + int vid_w = 0, vid_h = 0; + int osd_w = glfb->getOSDWidth(); + int osd_h = glfb->getOSDHeight(); + xres = osd_w; + yres = osd_h; + if (get_video) { + buf_m.lock(); + video = buffers[buf_out]; + buf_m.unlock(); + vid_w = video.width(); + vid_h = video.height(); + if (scale_to_video || !get_osd) { + xres = vid_w; + yres = vid_h; + AVRational a = video.AR(); + /* TODO: this does not consider display_aspect and display_crop */ + if (a.num > 0 && a.den > 0) + xres = vid_w * a.num / a.den; + } + } + if (get_osd) + osd = glfb->getOSDBuffer(); + unsigned int need = avpicture_get_size(PIX_FMT_RGB32, xres, yres); + data = (unsigned char *)realloc(data, need); /* will be freed by caller */ + if (data == NULL) /* out of memory? */ + return false; + + if (get_video) { + if (vid_w != xres || vid_h != yres) /* scale video into data... */ + swscale(&video[0], data, vid_w, vid_h, xres, yres); + else /* get_video and no fancy scaling needed */ + memcpy(data, &video[0], xres * yres * sizeof(uint32_t)); + } + + if (get_osd && (osd_w != xres || osd_h != yres)) { + /* rescale osd */ + s_osd.resize(need); + swscale(&(*osd)[0], &s_osd[0], osd_w, osd_h, xres, yres); + osd = &s_osd; + } + + if (get_video && get_osd) { + /* alpha blend osd onto data (video). TODO: maybe libavcodec can do this? */ + uint32_t *d = (uint32_t *)data; + uint32_t *pixpos = (uint32_t *)&(*osd)[0]; + for (int count = 0; count < yres; count++) { + for (int count2 = 0; count2 < xres; count2++ ) { + uint32_t pix = *pixpos; + if ((pix & 0xff000000) == 0xff000000) + *d = pix; + else { + uint8_t *in = (uint8_t *)(pixpos); + uint8_t *out = (uint8_t *)d; + int a = in[3]; /* TODO: big/little endian? */ + *out = (*out + ((*in - *out) * a) / 256); + in++; out++; + *out = (*out + ((*in - *out) * a) / 256); + in++; out++; + *out = (*out + ((*in - *out) * a) / 256); + } + d++; + pixpos++; + } + } + } + else if (get_osd) /* only get_osd, data is not yet populated */ + memcpy(data, &(*osd)[0], xres * yres * sizeof(uint32_t)); + + return true; +} + +int64_t cVideo::GetPTS(void) +{ + int64_t pts = 0; + buf_m.lock(); + if (buf_num != 0) + pts = buffers[buf_out].pts(); + buf_m.unlock(); + return pts; +} + +void cVideo::SetDemux(cDemux *) +{ + lt_debug("%s: not implemented yet\n", __func__); +} diff --git a/generic-pc/video_lib.h b/generic-pc/video_lib.h new file mode 100644 index 0000000..077154c --- /dev/null +++ b/generic-pc/video_lib.h @@ -0,0 +1,220 @@ +#ifndef _VIDEO_TD_H +#define _VIDEO_TD_H + +#include +#include +#include +#include +#include "../common/cs_types.h" +#include "dmx_lib.h" +extern "C" { +#include +} + +typedef enum { + ANALOG_SD_RGB_CINCH = 0x00, + ANALOG_SD_YPRPB_CINCH, + ANALOG_HD_RGB_CINCH, + ANALOG_HD_YPRPB_CINCH, + ANALOG_SD_RGB_SCART = 0x10, + ANALOG_SD_YPRPB_SCART, + ANALOG_HD_RGB_SCART, + ANALOG_HD_YPRPB_SCART, + ANALOG_SCART_MASK = 0x10 +} analog_mode_t; + + +typedef enum { + VIDEO_FORMAT_MPEG2 = 0, + VIDEO_FORMAT_MPEG4, + VIDEO_FORMAT_VC1, + VIDEO_FORMAT_JPEG, + VIDEO_FORMAT_GIF, + VIDEO_FORMAT_PNG +} VIDEO_FORMAT; + +typedef enum { + VIDEO_SD = 0, + VIDEO_HD, + VIDEO_120x60i, + VIDEO_320x240i, + VIDEO_1440x800i, + VIDEO_360x288i +} VIDEO_DEFINITION; + +typedef enum { + VIDEO_FRAME_RATE_23_976 = 0, + VIDEO_FRAME_RATE_24, + VIDEO_FRAME_RATE_25, + VIDEO_FRAME_RATE_29_97, + VIDEO_FRAME_RATE_30, + VIDEO_FRAME_RATE_50, + VIDEO_FRAME_RATE_59_94, + VIDEO_FRAME_RATE_60 +} VIDEO_FRAME_RATE; + +typedef enum { + DISPLAY_AR_1_1, + DISPLAY_AR_4_3, + DISPLAY_AR_14_9, + DISPLAY_AR_16_9, + DISPLAY_AR_20_9, + DISPLAY_AR_RAW, +} DISPLAY_AR; + +typedef enum { + DISPLAY_AR_MODE_PANSCAN = 0, + DISPLAY_AR_MODE_LETTERBOX, + DISPLAY_AR_MODE_NONE, + DISPLAY_AR_MODE_PANSCAN2 +} DISPLAY_AR_MODE; + +typedef enum { + VIDEO_DB_DR_NEITHER = 0, + VIDEO_DB_ON, + VIDEO_DB_DR_BOTH +} VIDEO_DB_DR; + +typedef enum { + VIDEO_PLAY_STILL = 0, + VIDEO_PLAY_CLIP, + VIDEO_PLAY_TRICK, + VIDEO_PLAY_MOTION, + VIDEO_PLAY_MOTION_NO_SYNC +} VIDEO_PLAY_MODE; + +typedef enum { + VIDEO_STD_NTSC, + VIDEO_STD_SECAM, + VIDEO_STD_PAL, + VIDEO_STD_480P, + VIDEO_STD_576P, + VIDEO_STD_720P60, + VIDEO_STD_1080I60, + VIDEO_STD_720P50, + VIDEO_STD_1080I50, + VIDEO_STD_1080P30, + VIDEO_STD_1080P24, + VIDEO_STD_1080P25, + VIDEO_STD_AUTO, + VIDEO_STD_1080P50, /* SPARK only */ + VIDEO_STD_MAX +} VIDEO_STD; + +/* not used, for dummy functions */ +typedef enum { + VIDEO_HDMI_CEC_MODE_OFF = 0, + VIDEO_HDMI_CEC_MODE_TUNER, + VIDEO_HDMI_CEC_MODE_RECORDER +} VIDEO_HDMI_CEC_MODE; + +typedef enum +{ + VIDEO_CONTROL_BRIGHTNESS = 0, + VIDEO_CONTROL_CONTRAST, + VIDEO_CONTROL_SATURATION, + VIDEO_CONTROL_HUE, + VIDEO_CONTROL_SHARPNESS, + VIDEO_CONTROL_MAX = VIDEO_CONTROL_SHARPNESS +} VIDEO_CONTROL; + + +#define VDEC_MAXBUFS 0x30 +class cVideo : public OpenThreads::Thread +{ + friend class GLFramebuffer; + friend class cDemux; + private: + /* called from GL thread */ + class SWFramebuffer : public std::vector + { + public: + SWFramebuffer() : mWidth(0), mHeight(0) {} + void width(int w) { mWidth = w; } + void height(int h) { mHeight = h; } + void pts(uint64_t p) { mPts = p; } + void AR(AVRational a) { mAR = a; } + int width() const { return mWidth; } + int height() const { return mHeight; } + int64_t pts() const { return mPts; } + AVRational AR() const { return mAR; } + private: + int mWidth; + int mHeight; + int64_t mPts; + AVRational mAR; + }; + int buf_in, buf_out, buf_num; + int64_t GetPTS(void); + public: + /* constructor & destructor */ + cVideo(int mode, void *, void *, unsigned int unit = 0); + ~cVideo(void); + + void * GetTVEnc() { return NULL; }; + void * GetTVEncSD() { return NULL; }; + + /* aspect ratio */ + int getAspectRatio(void); + void getPictureInfo(int &width, int &height, int &rate); + int setAspectRatio(int aspect, int mode); + + /* cropping mode */ + int setCroppingMode(int x = 0 /*vidDispMode_t x = VID_DISPMODE_NORM*/); + + /* get play state */ + int getPlayState(void); + + /* blank on freeze */ + int getBlank(void); + int setBlank(int enable); + + /* change video play state. Parameters are all unused. */ + int Start(void *PcrChannel = NULL, unsigned short PcrPid = 0, unsigned short VideoPid = 0, void *x = NULL); + int Stop(bool blank = true); + bool Pause(void); + + /* set video_system */ + int SetVideoSystem(int video_system, bool remember = true); + int SetStreamType(VIDEO_FORMAT type); + void SetSyncMode(AVSYNC_TYPE mode); + bool SetCECMode(VIDEO_HDMI_CEC_MODE) { return true; }; + void SetCECAutoView(bool) { return; }; + void SetCECAutoStandby(bool) { return; }; + void ShowPicture(const char * fname, const char * destname = NULL); + void StopPicture(); + void Standby(unsigned int bOn); + void Pig(int x, int y, int w, int h, int osd_w = 1064, int osd_h = 600, int startx = 0, int starty = 0, int endx = 1279, int endy = 719); + void SetControl(int, int) { return; }; + void setContrast(int val); + void SetVideoMode(analog_mode_t mode); + void SetDBDR(int) { return; }; + void SetAudioHandle(void *) { return; }; + void SetAutoModes(int [VIDEO_STD_MAX]) { return; }; + int OpenVBI(int) { return 0; }; + int CloseVBI(void) { return 0; }; + int StartVBI(unsigned short) { return 0; }; + int StopVBI(void) { return 0; }; + void SetDemux(cDemux *dmx); + bool GetScreenImage(unsigned char * &data, int &xres, int &yres, bool get_video = true, bool get_osd = false, bool scale_to_video = false); + SWFramebuffer *getDecBuf(void); + private: + void run(); + SWFramebuffer buffers[VDEC_MAXBUFS]; + int dec_w, dec_h; + int dec_r; + bool w_h_changed; + bool thread_running; + VIDEO_FORMAT v_format; + VIDEO_STD v_std; + OpenThreads::Mutex buf_m; + DISPLAY_AR display_aspect; + DISPLAY_AR_MODE display_crop; + int output_h; + int pig_x; + int pig_y; + int pig_w; + int pig_h; +}; + +#endif diff --git a/include/audio_td.h b/include/audio_td.h new file mode 100644 index 0000000..9cb74a1 --- /dev/null +++ b/include/audio_td.h @@ -0,0 +1,17 @@ +#include +#if HAVE_TRIPLEDRAGON +#include "../libtriple/audio_td.h" +#elif HAVE_SPARK_HARDWARE +#include "../libspark/audio_lib.h" +#include "../libspark/audio_mixer.h" +#elif HAVE_AZBOX_HARDWARE +#include "../azbox/audio_lib.h" +#elif HAVE_GENERIC_HARDWARE +#if BOXMODEL_RASPI +#include "../raspi/audio_lib.h" +#else +#include "../generic-pc/audio_lib.h" +#endif +#else +#error neither HAVE_TRIPLEDRAGON nor HAVE_SPARK_HARDWARE defined +#endif diff --git a/include/ca_cs.h b/include/ca_cs.h new file mode 100644 index 0000000..f05f210 --- /dev/null +++ b/include/ca_cs.h @@ -0,0 +1 @@ +#include "../common/ca.h" diff --git a/include/cs_api.h b/include/cs_api.h new file mode 100644 index 0000000..fff64d5 --- /dev/null +++ b/include/cs_api.h @@ -0,0 +1,16 @@ +#include +#if HAVE_TRIPLEDRAGON +#include "../libtriple/cs_api.h" +#elif HAVE_SPARK_HARDWARE +#include "../libspark/cs_api.h" +#elif HAVE_AZBOX_HARDWARE +#include "../azbox/cs_api.h" +#elif HAVE_GENERIC_HARDWARE +#if BOXMODEL_RASPI +#include "../raspi/cs_api.h" +#else +#include "../generic-pc/cs_api.h" +#endif +#else +#error neither HAVE_TRIPLEDRAGON nor HAVE_SPARK_HARDWARE defined +#endif diff --git a/include/dmx_cs.h b/include/dmx_cs.h new file mode 100644 index 0000000..4f0dbc1 --- /dev/null +++ b/include/dmx_cs.h @@ -0,0 +1 @@ +#include "dmx_td.h" diff --git a/include/dmx_td.h b/include/dmx_td.h new file mode 100644 index 0000000..b2b3751 --- /dev/null +++ b/include/dmx_td.h @@ -0,0 +1,16 @@ +#include +#if HAVE_TRIPLEDRAGON +#include "../libtriple/dmx_td.h" +#elif HAVE_SPARK_HARDWARE +#include "../libspark/dmx_lib.h" +#elif HAVE_AZBOX_HARDWARE +#include "../azbox/dmx_lib.h" +#elif HAVE_GENERIC_HARDWARE +#if BOXMODEL_RASPI +#include "../raspi/dmx_lib.h" +#else +#include "../generic-pc/dmx_lib.h" +#endif +#else +#error neither HAVE_TRIPLEDRAGON nor HAVE_SPARK_HARDWARE defined +#endif diff --git a/include/glfb.h b/include/glfb.h new file mode 100644 index 0000000..ea7addd --- /dev/null +++ b/include/glfb.h @@ -0,0 +1,10 @@ +#include +#if HAVE_GENERIC_HARDWARE +#if BOXMODEL_RASPI +#include "../raspi/glfb.h" +#else +#include "../generic-pc/glfb.h" +#endif +#else +#error glfb.h only works with HAVE_GENERIC_HARDWARE defined +#endif diff --git a/include/hardware_caps.h b/include/hardware_caps.h new file mode 100644 index 0000000..641ed88 --- /dev/null +++ b/include/hardware_caps.h @@ -0,0 +1,46 @@ +/* + * determine the capabilities of the hardware. + * part of libstb-hal + * + * (C) 2010-2012 Stefan Seyfried + * + * License: GPL v2 or later + */ +#ifndef __HARDWARE_CAPS_H__ +#define __HARDWARE_CAPS_H__ + +#ifdef __cplusplus +extern "C" { +#endif +typedef enum +{ + HW_DISPLAY_NONE, + HW_DISPLAY_LED_NUM, /* simple 7 segment LED display */ + HW_DISPLAY_LINE_TEXT, /* 1 line text display */ + HW_DISPLAY_GFX +} display_type_t; + + +typedef struct hw_caps +{ + int has_fan; + int has_HDMI; + int has_SCART; + int has_SCART_input; + int has_YUV_cinch; + int can_shutdown; + int can_cec; + display_type_t display_type; + int display_xres; /* x resolution or chars per line */ + int display_yres; + char boxvendor[64]; + char boxname[64]; + int boxtype; +} hw_caps_t; + +hw_caps_t *get_hwcaps(void); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/include/init_cs.h b/include/init_cs.h new file mode 100644 index 0000000..609c3e1 --- /dev/null +++ b/include/init_cs.h @@ -0,0 +1,2 @@ +#warning using init_cs.h from libstb-hal +#include "init_td.h" diff --git a/include/init_td.h b/include/init_td.h new file mode 100644 index 0000000..d9a6f09 --- /dev/null +++ b/include/init_td.h @@ -0,0 +1,5 @@ +#ifndef __INIT_TD_H +#define __INIT_TD_H +void init_td_api(); +void shutdown_td_api(); +#endif diff --git a/include/lt_debug.h b/include/lt_debug.h new file mode 100644 index 0000000..513f17d --- /dev/null +++ b/include/lt_debug.h @@ -0,0 +1,8 @@ +#include +#if HAVE_TRIPLEDRAGON +#include "../libtriple/playback_td.h" +#elif HAVE_SPARK_HARDWARE +#include "../libspark/playback_lib.h" +#else +#error neither HAVE_TRIPLEDRAGON nor HAVE_SPARK_HARDWARE defined +#endif diff --git a/include/mmi.h b/include/mmi.h new file mode 100644 index 0000000..3fec80f --- /dev/null +++ b/include/mmi.h @@ -0,0 +1,27 @@ +#ifndef __MMI_H_ +#define __MMI_H_ + +#define MAX_MMI_ITEMS 40 +#define MAX_MMI_TEXT_LEN 255 +#define MAX_MMI_CHOICE_TEXT_LEN 255 + +typedef struct { + int choice_nb; + char title[MAX_MMI_TEXT_LEN]; + char subtitle[MAX_MMI_TEXT_LEN]; + char bottom[MAX_MMI_TEXT_LEN]; + char choice_item[MAX_MMI_ITEMS][MAX_MMI_CHOICE_TEXT_LEN]; +} MMI_MENU_LIST_INFO; + +typedef struct { + int blind; + int answerlen; + char enquiryText[MAX_MMI_TEXT_LEN]; +} MMI_ENQUIRY_INFO; + +/* compat */ +#define enguiryText enquiryText +#define MMI_ENGUIRY_INFO MMI_ENQUIRY_INFO + +#endif // __MMI_H_ + diff --git a/include/playback.h b/include/playback.h new file mode 100644 index 0000000..0e8ce00 --- /dev/null +++ b/include/playback.h @@ -0,0 +1,3 @@ +/* playback_*.cpp uses off_t */ +#include +#include "playback_td.h" diff --git a/include/playback_td.h b/include/playback_td.h new file mode 100644 index 0000000..301b6c8 --- /dev/null +++ b/include/playback_td.h @@ -0,0 +1,16 @@ +#include +#if HAVE_TRIPLEDRAGON +#include "../libtriple/playback_td.h" +#elif HAVE_SPARK_HARDWARE +#include "../libspark/playback_libeplayer3.h" +#elif HAVE_AZBOX_HARDWARE +#include "../azbox/playback.h" +#elif HAVE_GENERIC_HARDWARE +#if BOXMODEL_RASPI +#include "../raspi/playback.h" +#else +#include "../generic-pc/playback.h" +#endif +#else +#error neither HAVE_TRIPLEDRAGON nor HAVE_SPARK_HARDWARE defined +#endif diff --git a/include/pwrmngr.h b/include/pwrmngr.h new file mode 100644 index 0000000..3b3dcfe --- /dev/null +++ b/include/pwrmngr.h @@ -0,0 +1,16 @@ +#include +#if HAVE_TRIPLEDRAGON +#include "../libtriple/pwrmngr.h" +#elif HAVE_SPARK_HARDWARE +#include "../libspark/pwrmngr.h" +#elif HAVE_AZBOX_HARDWARE +#include "../azbox/pwrmngr.h" +#elif HAVE_GENERIC_HARDWARE +#if BOXMODEL_RASPI +#include "../raspi/pwrmngr.h" +#else +#include "../generic-pc/pwrmngr.h" +#endif +#else +#error neither HAVE_TRIPLEDRAGON nor HAVE_SPARK_HARDWARE defined +#endif diff --git a/include/record_td.h b/include/record_td.h new file mode 100644 index 0000000..0b63c66 --- /dev/null +++ b/include/record_td.h @@ -0,0 +1,16 @@ +#include +#if HAVE_TRIPLEDRAGON +#include "../libtriple/record_td.h" +#elif HAVE_SPARK_HARDWARE +#include "../libspark/record_lib.h" +#elif HAVE_AZBOX_HARDWARE +#include "../azbox/record_lib.h" +#elif HAVE_GENERIC_HARDWARE +#if BOXMODEL_RASPI +#include "../raspi/record_lib.h" +#else +#include "../generic-pc/record_lib.h" +#endif +#else +#error neither HAVE_TRIPLEDRAGON nor HAVE_SPARK_HARDWARE defined +#endif diff --git a/include/video_td.h b/include/video_td.h new file mode 100644 index 0000000..479d230 --- /dev/null +++ b/include/video_td.h @@ -0,0 +1,20 @@ +#include +#if HAVE_TRIPLEDRAGON +#include "../libtriple/video_td.h" +#elif HAVE_SPARK_HARDWARE +#include "../libspark/video_lib.h" +#elif HAVE_AZBOX_HARDWARE +#include "../azbox/video_lib.h" +#elif HAVE_GENERIC_HARDWARE +#if BOXMODEL_RASPI +#include "../raspi/video_lib.h" +#else +#include "../generic-pc/video_lib.h" +#endif +#else +#error neither HAVE_TRIPLEDRAGON nor HAVE_SPARK_HARDWARE defined +#endif + +#if STB_HAL_VIDEO_HAS_GETSCREENIMAGE +#define SCREENSHOT 1 +#endif diff --git a/libeplayer3/Makefile.am b/libeplayer3/Makefile.am new file mode 100644 index 0000000..7a6b9e0 --- /dev/null +++ b/libeplayer3/Makefile.am @@ -0,0 +1,15 @@ +noinst_LTLIBRARIES = libeplayer3.la + +AM_CPPFLAGS = -D__STDC_CONSTANT_MACROS -D__STDC_LIMIT_MACROS +AM_CPPFLAGS += -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE64_SOURCE +AM_CPPFLAGS += -I$(srcdir)/include +AM_CXXFLAGS = -fno-rtti -fno-exceptions -fno-strict-aliasing + +libeplayer3_la_SOURCES = \ + input.cpp output.cpp manager.cpp player.cpp \ + writer/writer.cpp writer/wmv.cpp writer/ac3.cpp writer/divx.cpp writer/pes.cpp \ + writer/dts.cpp writer/mpeg2.cpp writer/mp3.cpp writer/misc.cpp writer/h264.cpp \ + writer/h263.cpp writer/vc1.cpp writer/flac.cpp writer/pcm.cpp + +LIBEPLAYER3_LIBS = libeplayer3.la -lpthread -lavformat -lavcodec -lavutil -lswresample -lm + diff --git a/libeplayer3/README b/libeplayer3/README new file mode 100644 index 0000000..e59e16d --- /dev/null +++ b/libeplayer3/README @@ -0,0 +1,78 @@ +This is a revised libeplayer3 version for Neutrino, rewritten in C++, with +various code parts (e.g. subtitle processing, non-working decoders) removed. +--martii + +The original libeplayer3 README follows: + +/* + * SCOPE: + * ------- + * + * libeplayer3 was developed to create a cleaner and more stable + * version of the libeplayer2. + * Currently the lib supports only one container, which handle all + * files by using the ffmpeg library. + * + * FEATURES: + * ----------------------- + * + * - more stable than libeplayer2. + * - more multimedia files are supported than libeplayer2. + * - mms stream support. + * - new videocodec support: + * - wmv and vc1 (sti7109 & sti7111 & sti7105 only). + * - flv. + * - improved http streaming support + * - subtitle rendering (ssa / ass) by using libass + * + * STYLE GUIDELINES: + * ------------------ + * + * If you decide to add some lines of code please ensure the following: + * - do not use a windows editor. + * - a tab must be emulated by 4 spaces (most editors support this). + * If you accidental break this rule use astyle to reorganize indentation, + * and dos2unix to remove windows style. + * + * Programming GUIDLINES: + * ----------------------- + * + * - the compiler is intentionally set to Wall, it would be nice if all + * programmer looks for warnings and solve them. + * - make sanity checks where ever you can. + * - freeing memory is an act of solidarity, but it also increases uptime + * of your receiver. ;) + * - if you detect stuff which may be generic, then make it generic. + * - commenting code is not a bad idea. + * + * KNOWN BUGS / PROBLEMS: + * ---------------------- + * + * - reverse playback needs improvement + * - some formats makes problems ? + * - getting stream info currently leads to a memory leak in e2. this is + * not a problem of this implementation its also exists in libeplayer2. + * e2 delivers a strdupped variable which is overwritten by what the container + * delivers. this is very hacky ;) -> (see comment in container_ffmpeg_get_info) + * + * License: + * -------- + * + * Copyright (C) 2010 crow, schischu, hellmaster1024 and konfetti. + * + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ diff --git a/libeplayer3/include/input.h b/libeplayer3/include/input.h new file mode 100644 index 0000000..9fef39c --- /dev/null +++ b/libeplayer3/include/input.h @@ -0,0 +1,86 @@ +/* + * input class + * + * Copyright (C) 2014 martii + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __INPUT_H__ +#define __INPUT_H__ + +#include +#include +#include +#include + +#include +#include +#include + +extern "C" { +#include +#include +#include +#include +#include +} + +class Player; +class Track; + +class Input +{ + friend class Player; + friend int interrupt_cb(void *arg); + + private: + Track *videoTrack; + Track *audioTrack; + Track *subtitleTrack; + Track *teletextTrack; + + int hasPlayThreadStarted; + int64_t seek_avts_abs; + int64_t seek_avts_rel; + bool isContainerRunning; + bool abortPlayback; + + Player *player; + AVFormatContext *avfc; + uint64_t readCount; + int64_t calcPts(AVStream * stream, int64_t pts); + + public: + Input(); + ~Input(); + + bool ReadSubtitle(const char *filename, const char *format, int pid); + bool ReadSubtitles(const char *filename); + bool Init(const char *filename); + bool UpdateTracks(); + bool Play(); + bool Stop(); + bool Seek(int64_t sec, bool absolute); + bool GetDuration(int64_t &duration); + bool SwitchAudio(Track *track); + bool SwitchSubtitle(Track *track); + bool SwitchTeletext(Track *track); + bool SwitchVideo(Track *track); + bool GetMetadata(std::vector &keys, std::vector &values); + bool GetReadCount(uint64_t &readcount); +}; + +#endif diff --git a/libeplayer3/include/manager.h b/libeplayer3/include/manager.h new file mode 100644 index 0000000..2442b5c --- /dev/null +++ b/libeplayer3/include/manager.h @@ -0,0 +1,89 @@ +/* + * manager class + * + * Copyright (C) 2014 martii + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __MANAGER_H__ +#define __MANAGER_H__ + +#include +#include +#include +#include + +#include +#include +#include + +extern "C" { +#include +#include +#include +#include +#include +} + +class Player; + +struct Track +{ + std::string title; + int pid; + int64_t duration; + AVFormatContext *avfc; + AVStream *stream; + bool inactive; + bool is_static; + int ac3flags; + int type, mag, page; // for teletext + Track() : pid(-1), duration(-1), avfc(NULL), stream(NULL), inactive(0), is_static(0), ac3flags(0) {} +}; + +class Manager +{ + friend class Player; + + private: + Player *player; + OpenThreads::Mutex mutex; + std::map videoTracks, audioTracks, subtitleTracks, teletextTracks; + void addTrack(std::map &tracks, Track &track); + Track *getTrack(std::map &tracks, int pid); + std::vector getTracks(std::map &tracks); + public: + void addVideoTrack(Track &track); + void addAudioTrack(Track &track); + void addSubtitleTrack(Track &track); + void addTeletextTrack(Track &track); + + std::vector getVideoTracks(); + std::vector getAudioTracks(); + std::vector getSubtitleTracks(); + std::vector getTeletextTracks(); + + Track *getVideoTrack(int pid); + Track *getAudioTrack(int pid); + Track *getSubtitleTrack(int pid); + Track *getTeletextTrack(int pid); + + bool initTrackUpdate(); + void clearTracks(); + + ~Manager(); +}; +#endif diff --git a/libeplayer3/include/misc.h b/libeplayer3/include/misc.h new file mode 100644 index 0000000..34b2eb2 --- /dev/null +++ b/libeplayer3/include/misc.h @@ -0,0 +1,20 @@ +#ifndef misc_123 +#define misc_123 + +/* some useful things needed by many files ... */ + +#include + +#define INVALID_PTS_VALUE 0x200000000ll + +struct BitPacker_t +{ + uint8_t *Ptr; /* write pointer */ + unsigned int BitBuffer; /* bitreader shifter */ + int Remaining; /* number of remaining in the shifter */ +}; + +void PutBits(BitPacker_t * ld, unsigned int code, unsigned int length); +void FlushBits(BitPacker_t * ld); + +#endif diff --git a/libeplayer3/include/output.h b/libeplayer3/include/output.h new file mode 100644 index 0000000..fd84d04 --- /dev/null +++ b/libeplayer3/include/output.h @@ -0,0 +1,80 @@ +/* + * output class + * + * Copyright (C) 2014 martii + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __OUTPUT_H__ +#define __OUTPUT_H__ + +#include +#include +#include +#include + +#include +#include +#include + +extern "C" { +#include +#include +#include +#include +#include +} + +#include "writer.h" + +class Player; + +class Output +{ + friend class Player; + + private: + int videofd; + int audiofd; + Writer *videoWriter, *audioWriter; + OpenThreads::Mutex audioMutex, videoMutex; + AVStream *audioStream, *videoStream; + Player *player; + public: + Output(); + ~Output(); + bool Open(); + bool Close(); + bool Play(); + bool Stop(); + bool Pause(); + bool Continue(); + bool Mute(bool); + bool Flush(); + bool FastForward(int speed); + bool SlowMotion(int speed); + bool AVSync(bool); + bool Clear(); + bool ClearAudio(); + bool ClearVideo(); + bool GetPts(int64_t &pts); + bool GetFrameCount(int64_t &framecount); + bool SwitchAudio(AVStream *stream); + bool SwitchVideo(AVStream *stream); + bool Write(AVFormatContext *avfc, AVStream *stream, AVPacket *packet, int64_t Pts); +}; + +#endif diff --git a/libeplayer3/include/pes.h b/libeplayer3/include/pes.h new file mode 100644 index 0000000..c9c63a5 --- /dev/null +++ b/libeplayer3/include/pes.h @@ -0,0 +1,34 @@ +#ifndef pes_123 +#define pes_123 + +#include + +#define PES_MAX_HEADER_SIZE 64 +#define PES_PRIVATE_DATA_FLAG 0x80 +#define PES_PRIVATE_DATA_LENGTH 8 +#define PES_LENGTH_BYTE_0 5 +#define PES_LENGTH_BYTE_1 4 +#define PES_FLAGS_BYTE 7 +#define PES_EXTENSION_DATA_PRESENT 0x01 +#define PES_HEADER_DATA_LENGTH_BYTE 8 +#define PES_START_CODE_RESERVED_4 0xfd +#define PES_VERSION_FAKE_START_CODE 0x31 + + +#define MAX_PES_PACKET_SIZE 65535 + + +/* start codes */ +#define PCM_PES_START_CODE 0xbd +#define PRIVATE_STREAM_1_PES_START_CODE 0xbd +#define H263_VIDEO_PES_START_CODE 0xfe +#define H264_VIDEO_PES_START_CODE 0xe2 +#define MPEG_VIDEO_PES_START_CODE 0xe0 +#define MPEG_AUDIO_PES_START_CODE 0xc0 +#define VC1_VIDEO_PES_START_CODE 0xfd +#define AAC_AUDIO_PES_START_CODE 0xcf + +int InsertPesHeader(uint8_t *data, int size, uint8_t stream_id, int64_t pts, int pic_start_code); +int InsertVideoPrivateDataHeader(uint8_t *data, int payload_size); + +#endif diff --git a/libeplayer3/include/player.h b/libeplayer3/include/player.h new file mode 100644 index 0000000..9b6e44b --- /dev/null +++ b/libeplayer3/include/player.h @@ -0,0 +1,119 @@ +/* + * player class + * + * Copyright (C) 2014 martii + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __PLAYER_H__ +#define __PLAYER_H__ + +#include +#include +#include + +extern "C" { +#include +#include +#include +#include +#include +} + +#include +#include + +#include +#include +#include + +#include "input.h" +#include "output.h" +#include "manager.h" + +struct Chapter +{ + std::string title; + int64_t start; + int64_t end; +}; + +class Player { + friend class Input; + friend class Output; + friend class Manager; + friend class cPlayback; + friend int interrupt_cb(void *arg); + + private: + Input input; + Output output; + Manager manager; + OpenThreads::Mutex chapterMutex; + std::vector chapters; + pthread_t playThread; + + bool abortRequested; + bool isHttp; + bool isPaused; + bool isSlowMotion; + bool hasThreadStarted; + bool isForwarding; + bool isBackWard; + bool isPlaying; + + int Speed; + + uint64_t readCount; + + std::string url; + bool noprobe; /* hack: only minimal probing in av_find_stream_info */ + + void SetChapters(std::vector &Chapters); + static void* playthread(void*); + public: + bool SwitchAudio(int pid); + bool SwitchVideo(int pid); + bool SwitchTeletext(int pid); + bool SwitchSubtitle(int pid); + + int GetAudioPid(); + int GetVideoPid(); + int GetSubtitlePid(); + int GetTeletextPid(); + + bool GetPts(int64_t &pts); + bool GetFrameCount(int64_t &framecount); + bool GetDuration(int64_t &duration); + + bool GetMetadata(std::vector &keys, std::vector &values); + bool SlowMotion(int repeats); + bool FastBackward(int speed); + bool FastForward(int speed); + + bool Open(const char *Url, bool noprobe = false); + bool Close(); + bool Play(); + bool Pause(); + bool Continue(); + bool Stop(); + bool Seek(int64_t pos, bool absolute); + void RequestAbort(); + bool GetChapters(std::vector &positions, std::vector &titles); + + Player(); +}; +#endif diff --git a/libeplayer3/include/writer.h b/libeplayer3/include/writer.h new file mode 100644 index 0000000..7de9f3e --- /dev/null +++ b/libeplayer3/include/writer.h @@ -0,0 +1,52 @@ +/* + * writer class headers + * + * Copyright (C) 2014 martii + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __WRITER_H__ +#define __WRITER_H__ + +#include +#include +#include + +extern "C" { +#include +#include +#include +#include +#include +} + +#include + +#define AV_CODEC_ID_INJECTPCM AV_CODEC_ID_PCM_S16LE + +class Writer +{ + public: + static void Register(Writer *w, enum AVCodecID id, video_encoding_t encoding); + static void Register(Writer *w, enum AVCodecID id, audio_encoding_t encoding); + static video_encoding_t GetVideoEncoding(enum AVCodecID id); + static audio_encoding_t GetAudioEncoding(enum AVCodecID id); + static Writer *GetWriter(enum AVCodecID id, enum AVMediaType codec_type); + + virtual void Init(void) { } + virtual bool Write(int fd, AVFormatContext *avfc, AVStream *stream, AVPacket *packet, int64_t pts); +}; +#endif diff --git a/libeplayer3/input.cpp b/libeplayer3/input.cpp new file mode 100644 index 0000000..1301052 --- /dev/null +++ b/libeplayer3/input.cpp @@ -0,0 +1,622 @@ +/* + * input class + * + * based on libeplayer3 container_ffmpeg.c, konfetti 2010; based on code from crow + * + * Copyright (C) 2014 martii + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "player.h" +#include "misc.h" + +#define averror(_err,_fun) ({ \ + if (_err < 0) { \ + char _error[512]; \ + av_strerror(_err, _error, sizeof(_error)); \ + fprintf(stderr, "%s %d: %s: %d (%s)\n", __FILE__, __LINE__, #_fun, _err, _error); \ + } \ + _err; \ +}) + +Input::Input() +{ + videoTrack = NULL; + audioTrack = NULL; + subtitleTrack = NULL; + teletextTrack = NULL; + + hasPlayThreadStarted = 0; + seek_avts_abs = INT64_MIN; + seek_avts_rel = 0; + abortPlayback = false; +} + +Input::~Input() +{ +} + +int64_t Input::calcPts(AVStream * stream, int64_t pts) +{ + if (pts == AV_NOPTS_VALUE) + return INVALID_PTS_VALUE; + + pts = av_rescale(90000ll * stream->time_base.num, pts, stream->time_base.den); + if (avfc->start_time != AV_NOPTS_VALUE) + pts -= av_rescale(90000ll, avfc->start_time, AV_TIME_BASE); + + if (pts < 0) + return INVALID_PTS_VALUE; + + return pts; +} + +// from neutrino-mp/lib/libdvbsubtitle/dvbsub.cpp +extern void dvbsub_write(AVSubtitle *, int64_t); +extern void dvbsub_ass_write(AVCodecContext *c, AVSubtitle *sub, int pid); +extern void dvbsub_ass_clear(void); +// from neutrino-mp/lib/lib/libtuxtxt/tuxtxt_common.h +extern void teletext_write(int pid, uint8_t *data, int size); + +bool Input::Play() +{ + hasPlayThreadStarted = 1; + + int64_t showtime = 0; + bool restart_audio_resampling = false; + bool bof = false; + int warnAudioWrite = 0; + int warnVideoWrite = 0; + + while (player->isPlaying && !player->abortRequested) { + + //IF MOVIE IS PAUSED, WAIT + if (player->isPaused) { + fprintf(stderr, "paused\n"); + usleep(100000); + continue; + } + + int seek_target_flag = 0; + int64_t seek_target = INT64_MIN; // in AV_TIME_BASE units + + if (seek_avts_rel) { + if (avfc->iformat->flags & AVFMT_TS_DISCONT) { + if (avfc->bit_rate) { + seek_target_flag = AVSEEK_FLAG_BYTE; + seek_target = avio_tell(avfc->pb) + av_rescale(seek_avts_rel, avfc->bit_rate, 8 * AV_TIME_BASE); + } + } else { + int64_t pts; + if(player->output.GetPts(pts)) + seek_target = av_rescale(pts, AV_TIME_BASE, 90000ll) + seek_avts_rel; + } + seek_avts_rel = 0; + } else if (seek_avts_abs != INT64_MIN) { + if (avfc->iformat->flags & AVFMT_TS_DISCONT) { + if (avfc->bit_rate) { + seek_target_flag = AVSEEK_FLAG_BYTE; + seek_target = av_rescale(seek_avts_abs, avfc->bit_rate, 8 * AV_TIME_BASE); + } + } else { + seek_target = seek_avts_abs; + } + seek_avts_abs = INT64_MIN; + } else if (player->isBackWard && av_gettime() >= showtime) { + player->output.ClearVideo(); + + if (bof) { + showtime = av_gettime(); + usleep(100000); + continue; + } + seek_avts_rel = player->Speed * AV_TIME_BASE; + showtime = av_gettime() + 300000; //jump back every 300ms + continue; + } else { + bof = false; + } + + if (seek_target > INT64_MIN) { + int res; + if (seek_target < 0) + seek_target = 0; + res = avformat_seek_file(avfc, -1, INT64_MIN, seek_target, INT64_MAX, seek_target_flag); + + if (res < 0 && player->isBackWard) + bof = true; + + seek_target = INT64_MIN; + restart_audio_resampling = true; + + // flush streams + unsigned int i; + for (i = 0; i < avfc->nb_streams; i++) + if (avfc->streams[i]->codec && avfc->streams[i]->codec->codec) + avcodec_flush_buffers(avfc->streams[i]->codec); + player->output.ClearAudio(); + player->output.ClearVideo(); + } + + AVPacket packet; + av_init_packet(&packet); + + int err = av_read_frame(avfc, &packet); + if (err == AVERROR(EAGAIN)) { + av_free_packet(&packet); + continue; + } + if (averror(err, av_read_frame)) // EOF? + break; // while + + player->readCount += packet.size; + + AVStream *stream = avfc->streams[packet.stream_index]; + Track *_videoTrack = videoTrack; + Track *_audioTrack = audioTrack; + Track *_subtitleTrack = subtitleTrack; + Track *_teletextTrack = teletextTrack; + + if (_videoTrack && (_videoTrack->stream == stream)) { + int64_t pts = calcPts(stream, packet.pts); + if (!player->output.Write(avfc, stream, &packet, pts)) { + if (warnVideoWrite) + warnVideoWrite--; + else { + fprintf(stderr, "writing data to %s device failed\n", "video"); + warnVideoWrite = 100; + } + } + } else if (_audioTrack && (_audioTrack->stream == stream)) { + if (restart_audio_resampling) { + restart_audio_resampling = false; + player->output.Write(avfc, stream, NULL, 0); + } + if (!player->isBackWard) { + int64_t pts = calcPts(stream, packet.pts); + if (!player->output.Write(avfc, stream, &packet, _videoTrack ? pts : 0)) { + if (warnAudioWrite) + warnAudioWrite--; + else { + fprintf(stderr, "writing data to %s device failed\n", "audio"); + warnAudioWrite = 100; + } + } + } + } else if (_subtitleTrack && (_subtitleTrack->stream == stream)) { + if (stream->codec->codec) { + AVSubtitle sub; + memset(&sub, 0, sizeof(sub)); + int got_sub_ptr = 0; + + err = avcodec_decode_subtitle2(stream->codec, &sub, &got_sub_ptr, &packet); + averror(err, avcodec_decode_subtitle2); + + if (got_sub_ptr && sub.num_rects > 0) { + switch (sub.rects[0]->type) { + case SUBTITLE_TEXT: // FIXME? + case SUBTITLE_ASS: + dvbsub_ass_write(stream->codec, &sub, stream->id); + break; + case SUBTITLE_BITMAP: { + int64_t pts = calcPts(stream, packet.pts); + dvbsub_write(&sub, pts); + // avsubtitle_free() will be called by handler + break; + } + default: + break; + } + } + } + } else if (_teletextTrack && (_teletextTrack->stream == stream)) { + teletext_write(stream->id, packet.data, packet.size); + } + + av_free_packet(&packet); + } /* while */ + + if (player->abortRequested) + player->output.Clear(); + else + player->output.Flush(); + + dvbsub_ass_clear(); + abortPlayback = true; + hasPlayThreadStarted = false; + + return true; +} + +/*static*/ int interrupt_cb(void *arg) +{ + Player *player = (Player *) arg; + bool res = player->input.abortPlayback || player->abortRequested; + if (res) + fprintf(stderr, "%s %s %d: abort requested (%d/%d)\n", __FILE__, __func__, __LINE__, player->input.abortPlayback, player->abortRequested); + return res; +} + +static void log_callback(void *ptr __attribute__ ((unused)), int lvl __attribute__ ((unused)), const char *format, va_list ap) +{ +// if (debug_level > 10) + vfprintf(stderr, format, ap); +} + +bool Input::ReadSubtitle(const char *filename, const char *format, int pid) +{ + const char *lastDot = strrchr(filename, '.'); + if (!lastDot) + return false; + char *subfile = (char *) alloca(strlen(filename) + strlen(format)); + strcpy(subfile, filename); + strcpy(subfile + (lastDot + 1 - filename), format); + + if (access(subfile, R_OK)) + return false; + + AVFormatContext *subavfc = avformat_alloc_context(); + int err = avformat_open_input(&subavfc, subfile, av_find_input_format(format), 0); + if (averror(err, avformat_open_input)) { + avformat_free_context(subavfc); + return false; + } + + avformat_find_stream_info(subavfc, NULL); + if (subavfc->nb_streams != 1) { + avformat_free_context(subavfc); + return false; + } + + AVCodecContext *c = subavfc->streams[0]->codec; + AVCodec *codec = avcodec_find_decoder(c->codec_id); + if (!codec) { + avformat_free_context(subavfc); + return false; + } + + err = avcodec_open2(c, codec, NULL); + if (averror(err, avcodec_open2)) { + avformat_free_context(subavfc); + return false; + } + + AVPacket packet; + av_init_packet(&packet); + + if (c->subtitle_header) + fprintf(stderr, "%s\n", c->subtitle_header); + + while (av_read_frame(subavfc, &packet) > -1) { + AVSubtitle sub; + memset(&sub, 0, sizeof(sub)); + int got_sub = 0; + avcodec_decode_subtitle2(c, &sub, &got_sub, &packet); + if (got_sub) + dvbsub_ass_write(c, &sub, pid); + av_free_packet(&packet); + } + avformat_close_input(&subavfc); + avformat_free_context(subavfc); + + Track track; + track.title = format; + track.is_static = 1; + track.pid = pid; + player->manager.addSubtitleTrack(track); + return true; +} + +bool Input::ReadSubtitles(const char *filename) { + if (strncmp(filename, "file://", 7)) + return false; + filename += 7; + bool ret = false; + ret |= ReadSubtitle(filename, "srt", 0xFFFF); + ret |= ReadSubtitle(filename, "ass", 0xFFFE); + ret |= ReadSubtitle(filename, "ssa", 0xFFFD); + return ret; +} + +bool Input::Init(const char *filename) +{ + abortPlayback = false; + av_log_set_callback(log_callback); + + if (!filename) { + fprintf(stderr, "filename NULL\n"); + return false; + } + fprintf(stderr, "%s %s %d: %s\n", __FILE__, __func__, __LINE__, filename); + + avcodec_register_all(); + av_register_all(); + avformat_network_init(); + + videoTrack = NULL; + audioTrack = NULL; + subtitleTrack = NULL; + teletextTrack = NULL; + +again: + avfc = avformat_alloc_context(); + avfc->interrupt_callback.callback = interrupt_cb; + avfc->interrupt_callback.opaque = (void *) player; + + int err = avformat_open_input(&avfc, filename, NULL, 0); + if (averror(err, avformat_open_input)) { + avformat_free_context(avfc); + return false; + } + + avfc->iformat->flags |= AVFMT_SEEK_TO_PTS; + avfc->flags = AVFMT_FLAG_GENPTS; + if (player->noprobe) { + avfc->max_analyze_duration = 1; + avfc->probesize = 131072; + } + + err = avformat_find_stream_info(avfc, NULL); + if (averror(err, avformat_find_stream_info)) { + avformat_close_input(&avfc); + if (player->noprobe) { + player->noprobe = false; + goto again; + } + return false; + } + + bool res = UpdateTracks(); + + if (!videoTrack && !audioTrack) { + avformat_close_input(&avfc); + return false; + } + + if (videoTrack) + player->output.SwitchVideo(videoTrack->stream); + if (audioTrack) + player->output.SwitchAudio(audioTrack->stream); + + ReadSubtitles(filename); + + return res; +} + +bool Input::UpdateTracks() +{ + if (abortPlayback) + return true; + + std::vector chapters; + for (unsigned int i = 0; i < avfc->nb_chapters; i++) { + AVChapter *ch = avfc->chapters[i]; + AVDictionaryEntry* title = av_dict_get(ch->metadata, "title", NULL, 0); + Chapter chapter; + chapter.title = title ? title->value : ""; + chapter.start = av_rescale(ch->time_base.num * AV_TIME_BASE, ch->start, ch->time_base.den); + chapter.end = av_rescale(ch->time_base.num * AV_TIME_BASE, ch->end, ch->time_base.den); + chapters.push_back(chapter); + } + player->SetChapters(chapters); + + player->manager.initTrackUpdate(); + + av_dump_format(avfc, 0, player->url.c_str(), 0); + + for (unsigned int n = 0; n < avfc->nb_streams; n++) { + AVStream *stream = avfc->streams[n]; + + if (!stream->id) + stream->id = n + 1; + + Track track; + track.avfc = avfc; + track.stream = stream; + AVDictionaryEntry *lang = av_dict_get(stream->metadata, "language", NULL, 0); + track.title = lang ? lang->value : ""; + track.pid = stream->id; + if (stream->duration == AV_NOPTS_VALUE) + track.duration = avfc->duration; + else + track.duration = av_rescale(stream->time_base.num * AV_TIME_BASE, stream->duration, stream->time_base.den); + + switch (stream->codec->codec_type) { + case AVMEDIA_TYPE_VIDEO: { + player->manager.addVideoTrack(track); + if (!videoTrack) + videoTrack = player->manager.getVideoTrack(track.pid); + break; + } + case AVMEDIA_TYPE_AUDIO: { + switch(stream->codec->codec_id) { + case AV_CODEC_ID_MP2: + track.ac3flags = 9; + break; + case AV_CODEC_ID_MP3: + track.ac3flags = 4; + break; + case AV_CODEC_ID_AC3: + track.ac3flags = 1; + break; + case AV_CODEC_ID_EAC3: + track.ac3flags = 7; + break; + case AV_CODEC_ID_DTS: + track.ac3flags = 6; + break; + case AV_CODEC_ID_AAC: + track.ac3flags = 5; + break; + default: + track.ac3flags = 0; + } + player->manager.addAudioTrack(track); + if (!audioTrack) + audioTrack = player->manager.getAudioTrack(track.pid); + break; + } + case AVMEDIA_TYPE_SUBTITLE: { + if (stream->codec->codec_id == AV_CODEC_ID_DVB_TELETEXT) { + std::string l = lang ? lang->value : ""; + uint8_t *data = stream->codec->extradata; + int size = stream->codec->extradata_size; + if (size > 0 && 2 * size - 1 == (int) l.length()) + for (int i = 0; i < size; i += 2) { + track.title = l.substr(i * 2, 3); + track.type = data[i] >> 3; + track.mag = data[i] & 7; + track.page = data[i + 1]; + player->manager.addTeletextTrack(track); + } + } else { + if (!stream->codec->codec) { + stream->codec->codec = avcodec_find_decoder(stream->codec->codec_id); + if (!stream->codec->codec) + fprintf(stderr, "avcodec_find_decoder failed for subtitle track %d\n", n); + else { + int err = avcodec_open2(stream->codec, stream->codec->codec, NULL); + if (averror(err, avcodec_open2)) + stream->codec->codec = NULL; + } + } + if (stream->codec->codec) + player->manager.addSubtitleTrack(track); + } + break; + } + default: + fprintf(stderr, "not handled or unknown codec_type %d\n", stream->codec->codec_type); + break; + } + } + + return true; +} + +bool Input::Stop() +{ + abortPlayback = true; + + while (hasPlayThreadStarted != 0) + usleep(100000); + + if (avfc) + avformat_close_input(&avfc); + + avformat_network_deinit(); + + return true; +} + +bool Input::Seek(int64_t avts, bool absolute) +{ + if (absolute) + seek_avts_abs = avts, seek_avts_rel = 0; + else + seek_avts_abs = INT64_MIN, seek_avts_rel = avts; + return true; +} + +bool Input::GetDuration(int64_t &duration) +{ + duration = 0; + + Track *track = videoTrack; + if (track && track->duration) { + duration = track->duration; + return true; + } + track = audioTrack; + if (track && track->duration) { + duration = track->duration; + return true; + } + track = subtitleTrack; + if (track && track->duration) { + duration = track->duration; + return true; + } + return false; +} + +bool Input::SwitchAudio(Track *track) +{ + audioTrack = track; + player->output.SwitchAudio(track ? track->stream : NULL); + player->Seek(-5000, false); + return true; +} + +bool Input::SwitchSubtitle(Track *track) +{ + subtitleTrack = track; + return true; +} + +bool Input::SwitchTeletext(Track *track) +{ + teletextTrack = track; + return true; +} + +bool Input::SwitchVideo(Track *track) +{ + videoTrack = track; + return true; +} + +bool Input::GetMetadata(std::vector &keys, std::vector &values) +{ + keys.clear(); + values.clear(); + + if (avfc) { + AVDictionaryEntry *tag = NULL; + + if (avfc->metadata) + while ((tag = av_dict_get(avfc->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { + keys.push_back(tag->key); + values.push_back(tag->value); + } + + if (videoTrack) + while ((tag = av_dict_get(videoTrack->stream->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { + keys.push_back(tag->key); + values.push_back(tag->value); + } + + if (audioTrack) + while ((tag = av_dict_get(audioTrack->stream->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { + keys.push_back(tag->key); + values.push_back(tag->value); + } + } + return true; +} + +bool Input::GetReadCount(uint64_t &readcount) +{ + readcount = readCount; + return true; +} diff --git a/libeplayer3/manager.cpp b/libeplayer3/manager.cpp new file mode 100644 index 0000000..bb163a4 --- /dev/null +++ b/libeplayer3/manager.cpp @@ -0,0 +1,160 @@ +/* + * manager class + * + * Copyright (C) 2014 martii + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include "manager.h" +#include "player.h" + +void Manager::addTrack(std::map &tracks, Track &track) +{ + OpenThreads::ScopedLock m_lock(mutex); + std::map::iterator it = tracks.find(track.pid); + if (it == tracks.end()) { + Track *t = new Track; + *t = track; + tracks[track.pid] = t; + } else + *it->second = track; +} + +void Manager::addVideoTrack(Track &track) +{ + addTrack(videoTracks, track); +} + +void Manager::addAudioTrack(Track &track) +{ + addTrack(audioTracks, track); +} + +void Manager::addSubtitleTrack(Track &track) +{ + addTrack(subtitleTracks, track); +} + +void Manager::addTeletextTrack(Track &track) +{ + addTrack(teletextTracks, track); +} + +std::vector Manager::getTracks(std::map &tracks) +{ + player->input.UpdateTracks(); + std::vector res; + OpenThreads::ScopedLock m_lock(mutex); + for(std::map::iterator it = tracks.begin(); it != tracks.end(); ++it) + if (!it->second->inactive) + res.push_back(*it->second); + return res; +} + +std::vector Manager::getVideoTracks() +{ + return getTracks(videoTracks); +} + +std::vector Manager::getAudioTracks() +{ + return getTracks(audioTracks); +} + +std::vector Manager::getSubtitleTracks() +{ + return getTracks(subtitleTracks); +} + +std::vector Manager::getTeletextTracks() +{ + return getTracks(teletextTracks); +} + +Track *Manager::getTrack(std::map &tracks, int pid) +{ + OpenThreads::ScopedLock m_lock(mutex); + std::map::iterator it = tracks.find(pid); + if (it != tracks.end() && !it->second->inactive) + return it->second; + return NULL; +} +Track *Manager::getVideoTrack(int pid) +{ + return getTrack(videoTracks, pid); +} + +Track *Manager::getAudioTrack(int pid) +{ + return getTrack(audioTracks, pid); +} + +Track *Manager::getSubtitleTrack(int pid) +{ + return getTrack(subtitleTracks, pid); +} + +Track *Manager::getTeletextTrack(int pid) +{ + return getTrack(teletextTracks, pid); +} + +bool Manager::initTrackUpdate() +{ + OpenThreads::ScopedLock m_lock(mutex); + + for (std::map::iterator it = audioTracks.begin(); it != audioTracks.end(); ++it) + it->second->inactive = !it->second->is_static; + + for (std::map::iterator it = videoTracks.begin(); it != videoTracks.end(); ++it) + it->second->inactive = !it->second->is_static; + + for (std::map::iterator it = subtitleTracks.begin(); it != subtitleTracks.end(); ++it) + it->second->inactive = !it->second->is_static; + + for (std::map::iterator it = teletextTracks.begin(); it != teletextTracks.end(); ++it) + it->second->inactive = !it->second->is_static; + + return true; +} + +void Manager::clearTracks() +{ + OpenThreads::ScopedLock m_lock(mutex); + + for (std::map::iterator it = audioTracks.begin(); it != audioTracks.end(); ++it) + delete it->second; + audioTracks.clear(); + + for (std::map::iterator it = videoTracks.begin(); it != videoTracks.end(); ++it) + delete it->second; + videoTracks.clear(); + + for (std::map::iterator it = subtitleTracks.begin(); it != subtitleTracks.end(); ++it) + delete it->second; + subtitleTracks.clear(); + + for (std::map::iterator it = teletextTracks.begin(); it != teletextTracks.end(); ++it) + delete it->second; + teletextTracks.clear(); +} + +Manager::~Manager() +{ + clearTracks(); +} diff --git a/libeplayer3/output.cpp b/libeplayer3/output.cpp new file mode 100644 index 0000000..9792a46 --- /dev/null +++ b/libeplayer3/output.cpp @@ -0,0 +1,348 @@ +/* + * output class + * + * based on libeplayer3 LinuxDVB Output handling. + * + * Copyright (C) 2014 martii + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "player.h" +#include "output.h" +#include "writer.h" +#include "misc.h" +#include "pes.h" + +#define dioctl(fd,req,arg) ({ \ + int _r = ioctl(fd,req,arg); \ + if (_r) \ + fprintf(stderr, "%s %d: ioctl '%s' failed: %d (%s)\n", __FILE__, __LINE__, #req, errno, strerror(errno)); \ + _r; \ +}) + +#define VIDEODEV "/dev/dvb/adapter0/video0" +#define AUDIODEV "/dev/dvb/adapter0/audio0" + +Output::Output() +{ + videofd = audiofd = -1; + videoWriter = audioWriter = NULL; + videoStream = audioStream = NULL; +} + +Output::~Output() +{ + Close(); +} + +bool Output::Open() +{ + OpenThreads::ScopedLock v_lock(videoMutex); + OpenThreads::ScopedLock a_lock(audioMutex); + + if (videofd < 0) + videofd = open(VIDEODEV, O_RDWR); + + if (videofd < 0) + return false; + + ioctl(videofd, VIDEO_CLEAR_BUFFER, NULL); + dioctl(videofd, VIDEO_SELECT_SOURCE, (void *) VIDEO_SOURCE_MEMORY); + dioctl(videofd, VIDEO_SET_STREAMTYPE, (void *) STREAM_TYPE_PROGRAM); + dioctl(videofd, VIDEO_SET_SPEED, DVB_SPEED_NORMAL_PLAY); + + if (audiofd < 0) + audiofd = open(AUDIODEV, O_RDWR); + + if (audiofd < 0) { + close(videofd); + videofd = -1; + return false; + } + + ioctl(audiofd, AUDIO_CLEAR_BUFFER, NULL); + dioctl(audiofd, AUDIO_SELECT_SOURCE, (void *) AUDIO_SOURCE_MEMORY); + dioctl(audiofd, AUDIO_SET_STREAMTYPE, (void *) STREAM_TYPE_PROGRAM); + + return true; +} + +bool Output::Close() +{ + Stop(); + + OpenThreads::ScopedLock v_lock(videoMutex); + OpenThreads::ScopedLock a_lock(audioMutex); + + if (videofd > -1) { + close(videofd); + videofd = -1; + } + if (audiofd > -1) { + close(audiofd); + audiofd = -1; + } + + videoStream = NULL; + audioStream = NULL; + + return true; +} + +bool Output::Play() +{ + bool ret = true; + + OpenThreads::ScopedLock v_lock(videoMutex); + OpenThreads::ScopedLock a_lock(audioMutex); + + if (videoStream && videofd > -1) { + videoWriter = Writer::GetWriter(videoStream->codec->codec_id, videoStream->codec->codec_type); + videoWriter->Init(); + if (dioctl(videofd, VIDEO_SET_ENCODING, videoWriter->GetVideoEncoding(videoStream->codec->codec_id)) + || dioctl(videofd, VIDEO_PLAY, NULL)) + ret = false; + } + + if (audioStream && audiofd > -1) { + audioWriter = Writer::GetWriter(audioStream->codec->codec_id, audioStream->codec->codec_type); + audioWriter->Init(); + if (dioctl(audiofd, AUDIO_SET_ENCODING, audioWriter->GetAudioEncoding(audioStream->codec->codec_id)) + || dioctl(audiofd, AUDIO_PLAY, NULL)) + ret = false; + } + return ret; +} + +bool Output::Stop() +{ + bool ret = true; + + OpenThreads::ScopedLock v_lock(videoMutex); + OpenThreads::ScopedLock a_lock(audioMutex); + + if (videofd > -1) { + ioctl(videofd, VIDEO_CLEAR_BUFFER, NULL); + /* set back to normal speed (end trickmodes) */ + dioctl(videofd, VIDEO_SET_SPEED, DVB_SPEED_NORMAL_PLAY); + if (dioctl(videofd, VIDEO_STOP, NULL)) + ret = false; + } + + if (audiofd > -1) { + ioctl(audiofd, AUDIO_CLEAR_BUFFER, NULL); + /* set back to normal speed (end trickmodes) */ + dioctl(audiofd, AUDIO_SET_SPEED, DVB_SPEED_NORMAL_PLAY); + if (dioctl(audiofd, AUDIO_STOP, NULL)) + ret = false; + } + + return ret; +} + +bool Output::Pause() +{ + bool ret = true; + + OpenThreads::ScopedLock v_lock(videoMutex); + OpenThreads::ScopedLock a_lock(audioMutex); + + if (videofd > -1) { + if (dioctl(videofd, VIDEO_FREEZE, NULL)) + ret = false; + } + + if (audiofd > -1) { + if (dioctl(audiofd, AUDIO_PAUSE, NULL)) + ret = false; + } + + return ret; +} + +bool Output::Continue() +{ + bool ret = true; + + OpenThreads::ScopedLock v_lock(videoMutex); + OpenThreads::ScopedLock a_lock(audioMutex); + + if (videofd > -1 && dioctl(videofd, VIDEO_CONTINUE, NULL)) + ret = false; + + if (audiofd > -1 && dioctl(audiofd, AUDIO_CONTINUE, NULL)) + ret = false; + + return ret; +} + +bool Output::Mute(bool b) +{ + OpenThreads::ScopedLock a_lock(audioMutex); + //AUDIO_SET_MUTE has no effect with new player + return audiofd > -1 && !dioctl(audiofd, b ? AUDIO_STOP : AUDIO_PLAY, NULL); +} + + +bool Output::Flush() +{ + bool ret = true; + + OpenThreads::ScopedLock v_lock(videoMutex); + OpenThreads::ScopedLock a_lock(audioMutex); + + if (videofd > -1 && ioctl(videofd, VIDEO_FLUSH, NULL)) + ret = false; + + if (audiofd > -1 && ioctl(audiofd, AUDIO_FLUSH, NULL)) + ret = false; + + return ret; +} + +bool Output::FastForward(int speed) +{ + OpenThreads::ScopedLock v_lock(videoMutex); + return videofd > -1 && !dioctl(videofd, VIDEO_FAST_FORWARD, speed); +} + +bool Output::SlowMotion(int speed) +{ + OpenThreads::ScopedLock v_lock(videoMutex); + return videofd > -1 && !dioctl(videofd, VIDEO_SLOWMOTION, speed); +} + +bool Output::AVSync(bool b) +{ + OpenThreads::ScopedLock a_lock(audioMutex); + return audiofd > -1 && !dioctl(audiofd, AUDIO_SET_AV_SYNC, b); +} + +bool Output::ClearAudio() +{ + OpenThreads::ScopedLock a_lock(audioMutex); + return audiofd > -1 && !ioctl(audiofd, AUDIO_CLEAR_BUFFER, NULL); +} + +bool Output::ClearVideo() +{ + OpenThreads::ScopedLock v_lock(videoMutex); + return videofd > -1 && !ioctl(videofd, VIDEO_CLEAR_BUFFER, NULL); +} + +bool Output::Clear() +{ + bool aret = ClearAudio(); + bool vret = ClearVideo(); + return aret && vret; +} + +bool Output::GetPts(int64_t &pts) +{ + pts = 0; + return ((videofd > -1 && !ioctl(videofd, VIDEO_GET_PTS, (void *) &pts)) || + (audiofd > -1 && !ioctl(audiofd, AUDIO_GET_PTS, (void *) &pts))); +} + +bool Output::GetFrameCount(int64_t &framecount) +{ + dvb_play_info_t playInfo; + + if ((videofd > -1 && !dioctl(videofd, VIDEO_GET_PLAY_INFO, (void *) &playInfo)) || + (audiofd > -1 && !dioctl(audiofd, AUDIO_GET_PLAY_INFO, (void *) &playInfo))) { + framecount = playInfo.frame_count; + return true; + } + return false; +} + +bool Output::SwitchAudio(AVStream *stream) +{ + OpenThreads::ScopedLock a_lock(audioMutex); + if (stream == audioStream) + return true; + if (audiofd > -1) { + dioctl(audiofd, AUDIO_STOP, NULL); + ioctl(audiofd, AUDIO_CLEAR_BUFFER, NULL); + } + audioStream = stream; + if (stream) { + audioWriter = Writer::GetWriter(stream->codec->codec_id, stream->codec->codec_type); + audioWriter->Init(); + if (audiofd > -1) { + dioctl (audiofd, AUDIO_SET_ENCODING, Writer::GetAudioEncoding(stream->codec->codec_id)); + dioctl(audiofd, AUDIO_PLAY, NULL); + } + } + return true; +} + +bool Output::SwitchVideo(AVStream *stream) +{ + OpenThreads::ScopedLock v_lock(videoMutex); + if (stream == videoStream) + return true; + if (videofd > -1) { + dioctl(videofd, VIDEO_STOP, NULL); + ioctl(videofd, VIDEO_CLEAR_BUFFER, NULL); + } + videoStream = stream; + if (stream) { + videoWriter = Writer::GetWriter(stream->codec->codec_id, stream->codec->codec_type); + videoWriter->Init(); + if (videofd > -1) { + dioctl(videofd, VIDEO_SET_ENCODING, Writer::GetVideoEncoding(stream->codec->codec_id)); + dioctl(videofd, VIDEO_PLAY, NULL); + } + } + return true; +} + +bool Output::Write(AVFormatContext *avfc, AVStream *stream, AVPacket *packet, int64_t pts) +{ + switch (stream->codec->codec_type) { + case AVMEDIA_TYPE_VIDEO: { + OpenThreads::ScopedLock v_lock(videoMutex); + return videofd > -1 && videoWriter && videoWriter->Write(videofd, avfc, stream, packet, pts); + } + case AVMEDIA_TYPE_AUDIO: { + OpenThreads::ScopedLock a_lock(audioMutex); + return audiofd > -1 && audioWriter && audioWriter->Write(audiofd, avfc, stream, packet, pts); + } + default: + return false; + } +} diff --git a/libeplayer3/player.cpp b/libeplayer3/player.cpp new file mode 100644 index 0000000..48f4d2c --- /dev/null +++ b/libeplayer3/player.cpp @@ -0,0 +1,410 @@ +/* + * linuxdvb output/writer handling + * + * Copyright (C) 2010 duckbox + * Copyright (C) 2014 martii (based on code from libeplayer3) + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "player.h" +#include "misc.h" + +#define cMaxSpeed_ff 128 /* fixme: revise */ +#define cMaxSpeed_fr -320 /* fixme: revise */ + +Player::Player() +{ + input.player = this; + output.player = this; + manager.player = this; + hasThreadStarted = false; + + isPaused = false; + isPlaying = false; + isForwarding = false; + isBackWard = false; + isSlowMotion = false; + Speed = 0; +} + +void *Player::playthread(void *arg) +{ + char threadname[17]; + strncpy(threadname, __func__, sizeof(threadname)); + threadname[16] = 0; + prctl(PR_SET_NAME, (unsigned long) threadname); + + Player *player = (Player *) arg; + player->hasThreadStarted = true; + player->input.Play(); + player->hasThreadStarted = false; + player->Stop(); + pthread_exit(NULL); +} + +bool Player::Open(const char *Url, bool _noprobe) +{ + fprintf(stderr, "URL=%s\n", Url); + + isHttp = false; + noprobe = _noprobe; + abortRequested = false; + + manager.clearTracks(); + + if (!strncmp("mms://", Url, 6)) { + url = "mmst"; + url += Url + 3; + isHttp = true; + } else if (strstr(Url, "://")) { + url = Url; + } else { + fprintf(stderr, "%s %s %d: Unknown stream (%s)\n", __FILE__, __func__, __LINE__, Url); + return false; + } + + return input.Init(url.c_str()); +} + +bool Player::Close() +{ + isPaused = false; + isPlaying = false; + isForwarding = false; + isBackWard = false; + isSlowMotion = false; + Speed = 0; + url.clear(); + + return true; +} + +bool Player::Play() +{ + bool ret = true; + + if (!isPlaying) { + output.AVSync(true); + + ret = output.Play(); + + if (ret) { + isPlaying = true; + isPaused = false; + isForwarding = false; + if (isBackWard) { + isBackWard = false; + output.Mute(false); + } + isSlowMotion = false; + Speed = 1; + + if (!hasThreadStarted) { + int err = pthread_create(&playThread, NULL, playthread, this); + + if (err) { + fprintf(stderr, "%s %s %d: pthread_create: %d (%s)\n", __FILE__, __func__, __LINE__, err, strerror(err)); + ret = false; + isPlaying = false; + } else { + pthread_detach(playThread); + } + } + } + + } else { + fprintf(stderr,"playback already running\n"); + ret = false; + } + return ret; +} + +bool Player::Pause() +{ + bool ret = true; + + if (isPlaying && !isPaused) { + + if (isSlowMotion) + output.Clear(); + + output.Pause(); + + isPaused = true; + //isPlaying = 1; + isForwarding = false; + if (isBackWard) { + isBackWard = false; + output.Mute(false); + } + isSlowMotion = false; + Speed = 1; + } else { + fprintf(stderr,"playback not playing or already in pause mode\n"); + ret = false; + } + return ret; +} + +bool Player::Continue() +{ + int ret = true; + + if (isPlaying && (isPaused || isForwarding || isBackWard || isSlowMotion)) { + + if (isSlowMotion) + output.Clear(); + + output.Continue(); + + isPaused = false; + //isPlaying = 1; + isForwarding = false; + if (isBackWard) { + isBackWard = false; + output.Mute(false); + } + isSlowMotion = false; + Speed = 1; + } else { + fprintf(stderr,"continue not possible\n"); + ret = false; + } + + return ret; +} + +bool Player::Stop() +{ + bool ret = true; + + if (isPlaying) { + isPaused = false; + isPlaying = false; + isForwarding = false; + if (isBackWard) { + isBackWard = false; + output.Mute(false); + } + isSlowMotion = false; + Speed = 0; + + output.Stop(); + input.Stop(); + + } else { + fprintf(stderr,"stop not possible\n"); + ret = false; + } + + while (hasThreadStarted) + usleep(100000); + + return ret; +} + +bool Player::FastForward(int speed) +{ + int ret = true; + + /* Audio only forwarding not supported */ + if (input.videoTrack && !isHttp && !isBackWard && (!isPaused || isPlaying)) { + + if ((speed <= 0) || (speed > cMaxSpeed_ff)) { + fprintf(stderr, "speed %d out of range (1 - %d) \n", speed, cMaxSpeed_ff); + return false; + } + + isForwarding = 1; + Speed = speed; + output.FastForward(speed); + } else { + fprintf(stderr,"fast forward not possible\n"); + ret = false; + } + + return ret; +} + +bool Player::FastBackward(int speed) +{ + bool ret = true; + + /* Audio only reverse play not supported */ + if (input.videoTrack && !isForwarding && (!isPaused || isPlaying)) { + + if ((speed > 0) || (speed < cMaxSpeed_fr)) { + fprintf(stderr, "speed %d out of range (0 - %d) \n", speed, cMaxSpeed_fr); + return false; + } + + if (speed == 0) { + isBackWard = false; + Speed = 0; /* reverse end */ + } else { + Speed = speed; + isBackWard = true; + } + + output.Clear(); +#if 0 + if (output->Command(player, OUTPUT_REVERSE, NULL) < 0) { + fprintf(stderr,"OUTPUT_REVERSE failed\n"); + isBackWard = false; + Speed = 1; + ret = false; + } +#endif + } else { + fprintf(stderr,"fast backward not possible\n"); + ret = false; + } + + if (isBackWard) + output.Mute(true); + + return ret; +} + +bool Player::SlowMotion(int repeats) +{ + if (input.videoTrack && !isHttp && isPlaying) { + if (isPaused) + Continue(); + + switch (repeats) { + case 2: + case 4: + case 8: + isSlowMotion = true; + break; + default: + repeats = 0; + } + + output.SlowMotion(repeats); + return true; + } + fprintf(stderr, "slowmotion not possible\n"); + return false; +} + +bool Player::Seek(int64_t pos, bool absolute) +{ + output.Clear(); + return input.Seek(pos, absolute); +} + +bool Player::GetPts(int64_t &pts) +{ + pts = INVALID_PTS_VALUE; + return isPlaying && output.GetPts(pts); +} + +bool Player::GetFrameCount(int64_t &frameCount) +{ + return isPlaying && output.GetFrameCount(frameCount); +} + +bool Player::GetDuration(int64_t &duration) +{ + duration = -1; + return isPlaying && input.GetDuration(duration); +} + +bool Player::SwitchVideo(int pid) +{ + Track *track = manager.getVideoTrack(pid); + return input.SwitchVideo(track); +} + +bool Player::SwitchAudio(int pid) +{ + Track *track = manager.getAudioTrack(pid); + return input.SwitchAudio(track); +} + +bool Player::SwitchSubtitle(int pid) +{ + Track *track = manager.getSubtitleTrack(pid); + return input.SwitchSubtitle(track); +} + +bool Player::SwitchTeletext(int pid) +{ + Track *track = manager.getTeletextTrack(pid); + return input.SwitchTeletext(track); +} + +bool Player::GetMetadata(std::vector &keys, std::vector &values) +{ + return input.GetMetadata(keys, values); +} + +bool Player::GetChapters(std::vector &positions, std::vector &titles) +{ + positions.clear(); + titles.clear(); + input.UpdateTracks(); + OpenThreads::ScopedLock m_lock(chapterMutex); + for (std::vector::iterator it = chapters.begin(); it != chapters.end(); ++it) { + positions.push_back(it->start/1000); + titles.push_back(it->title); + } + return true; +} + +void Player::SetChapters(std::vector &Chapters) +{ + OpenThreads::ScopedLock m_lock(chapterMutex); + chapters = Chapters; +} + +void Player::RequestAbort() +{ + abortRequested = true; +} + +int Player::GetVideoPid() +{ + Track *track = input.videoTrack; + return track ? track->pid : 0; +} + +int Player::GetAudioPid() +{ + Track *track = input.audioTrack; + return track ? track->pid : 0; +} + +int Player::GetSubtitlePid() +{ + Track *track = input.subtitleTrack; + return track ? track->pid : 0; +} + +int Player::GetTeletextPid() +{ + Track *track = input.teletextTrack; + return track ? track->pid : 0; +} diff --git a/libeplayer3/writer/ac3.cpp b/libeplayer3/writer/ac3.cpp new file mode 100644 index 0000000..7ac84e3 --- /dev/null +++ b/libeplayer3/writer/ac3.cpp @@ -0,0 +1,62 @@ +/* + * linuxdvb output/writer handling + * + * Copyright (C) 2010 konfetti (based on code from libeplayer2) + * Copyright (C) 2014 martii (based on code from libeplayer3) + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "misc.h" +#include "pes.h" +#include "writer.h" + +class WriterAC3 : public Writer +{ + public: + bool Write(int fd, AVFormatContext *avfc, AVStream *stream, AVPacket *packet, int64_t pts); + WriterAC3(); +}; + +bool WriterAC3::Write(int fd, AVFormatContext * /* avfc */, AVStream * /* stream */, AVPacket *packet, int64_t pts) +{ + if (fd < 0 || !packet) + return false; + + uint8_t PesHeader[PES_MAX_HEADER_SIZE]; + struct iovec iov[2]; + + iov[0].iov_base = PesHeader; + iov[0].iov_len = InsertPesHeader(PesHeader, packet->size, PRIVATE_STREAM_1_PES_START_CODE, pts, 0); + iov[1].iov_base = packet->data; + iov[1].iov_len = packet->size; + + return writev(fd, iov, 2) > -1; +} + +WriterAC3::WriterAC3() +{ + Register(this, AV_CODEC_ID_AC3, AUDIO_ENCODING_AC3); + Register(this, AV_CODEC_ID_EAC3, AUDIO_ENCODING_AC3); +} + +static WriterAC3 writer_ac3 __attribute__ ((init_priority (300))); diff --git a/libeplayer3/writer/divx.cpp b/libeplayer3/writer/divx.cpp new file mode 100644 index 0000000..283ef63 --- /dev/null +++ b/libeplayer3/writer/divx.cpp @@ -0,0 +1,107 @@ +/* + * linuxdvb output/writer handling + * + * Copyright (C) 2010 konfetti (based on code from libeplayer2) + * Copyright (C) 2014 martii (based on code from libeplayer3) + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "misc.h" +#include "pes.h" +#include "writer.h" + +class WriterDIVX : public Writer +{ + private: + bool initialHeader; + public: + bool Write(int fd, AVFormatContext *avfc, AVStream *stream, AVPacket *packet, int64_t pts); + void Init(); + WriterDIVX(); +}; + +void WriterDIVX::Init() +{ + initialHeader = true; +} + +bool WriterDIVX::Write(int fd, AVFormatContext * /* avfc */, AVStream *stream, AVPacket *packet, int64_t pts) +{ + if (fd < 0 || !packet) + return false; + + uint8_t PesHeader[PES_MAX_HEADER_SIZE]; + uint8_t FakeHeaders[64] = { 0 }; // 64bytes should be enough to make the fake headers + unsigned int FakeHeaderLength; + uint8_t Version = 5; + unsigned int FakeStartCode = (Version << 8) | PES_VERSION_FAKE_START_CODE; + BitPacker_t ld = { FakeHeaders, 0, 32 }; + + unsigned int usecPerFrame = av_rescale(AV_TIME_BASE, stream->r_frame_rate.den, stream->r_frame_rate.num); + + /* Create info record for frame parser */ + /* divx4 & 5 + VOS + PutBits(&ld, 0x0, 8); + PutBits(&ld, 0x0, 8); + */ + PutBits(&ld, 0x1b0, 32); // startcode + PutBits(&ld, 0, 8); // profile = reserved + PutBits(&ld, 0x1b2, 32); // startcode (user data) + PutBits(&ld, 0x53545443, 32); // STTC - an embedded ST timecode from an avi file + PutBits(&ld, usecPerFrame, 32); // microseconds per frame + FlushBits(&ld); + + FakeHeaderLength = (ld.Ptr - FakeHeaders); + + struct iovec iov[4]; + int ic = 0; + iov[ic].iov_base = PesHeader; + iov[ic++].iov_len = InsertPesHeader(PesHeader, packet->size, MPEG_VIDEO_PES_START_CODE, pts, FakeStartCode); + iov[ic].iov_base = FakeHeaders; + iov[ic++].iov_len = FakeHeaderLength; + + if (initialHeader) { + iov[ic].iov_base = stream->codec->extradata; + iov[ic++].iov_len = stream->codec->extradata_size; + initialHeader = false; + } + iov[ic].iov_base = packet->data; + iov[ic++].iov_len = packet->size; + + return writev(fd, iov, ic) > -1; +} + +WriterDIVX::WriterDIVX() +{ + Register(this, AV_CODEC_ID_MPEG4, VIDEO_ENCODING_MPEG4P2); + Register(this, AV_CODEC_ID_MSMPEG4V1, VIDEO_ENCODING_MPEG4P2); + Register(this, AV_CODEC_ID_MSMPEG4V2, VIDEO_ENCODING_MPEG4P2); + Register(this, AV_CODEC_ID_MSMPEG4V3, VIDEO_ENCODING_MPEG4P2); + Init(); +} + +static WriterDIVX writer_divx __attribute__ ((init_priority (300))); diff --git a/libeplayer3/writer/dts.cpp b/libeplayer3/writer/dts.cpp new file mode 100644 index 0000000..2ac0ec8 --- /dev/null +++ b/libeplayer3/writer/dts.cpp @@ -0,0 +1,82 @@ +/* + * linuxdvb output/writer handling + * + * Copyright (C) 2010 konfetti (based on code from libeplayer2) + * Copyright (C) 2014 martii (based on code from libeplayer3) + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "misc.h" +#include "pes.h" +#include "writer.h" + +#define PES_AUDIO_PRIVATE_HEADER_SIZE 16 // consider maximum private header size. +#define PES_AUDIO_HEADER_SIZE (32 + PES_AUDIO_PRIVATE_HEADER_SIZE) + +class WriterDTS : public Writer +{ + public: + bool Write(int fd, AVFormatContext *avfc, AVStream *stream, AVPacket *packet, int64_t pts); + WriterDTS(); +}; + +bool WriterDTS::Write(int fd, AVFormatContext * /* avfc */, AVStream * /* stream */, AVPacket *packet, int64_t pts) +{ + if (fd < 0 || !packet) + return false; + + uint8_t PesHeader[PES_AUDIO_HEADER_SIZE]; + +// #define DO_BYTESWAP +#ifdef DO_BYTESWAP + uint8_t Data[packet->size]; + memcpy(Data, packet->data, packet->size); + + /* 16-bit byte swap all data before injecting it */ + for (i = 0; i < packet->size; i += 2) { + uint8_t Tmp = Data[i]; + Data[i] = Data[i + 1]; + Data[i + 1] = Tmp; + } +#endif + + struct iovec iov[2]; + + iov[0].iov_base = PesHeader; + iov[0].iov_len = InsertPesHeader(PesHeader, packet->size, MPEG_AUDIO_PES_START_CODE /*PRIVATE_STREAM_1_PES_START_CODE */ , pts, 0); +#ifdef DO_BYTESPWAP + iov[1].iov_base = Data; +#else + iov[1].iov_base = packet->data; +#endif + iov[1].iov_len = packet->size; + + return writev(fd, iov, 2) > -1; +} + +WriterDTS::WriterDTS() +{ + Register(this, AV_CODEC_ID_DTS, AUDIO_ENCODING_DTS); +} + +static WriterDTS writer_dts __attribute__ ((init_priority (300))); diff --git a/libeplayer3/writer/flac.cpp b/libeplayer3/writer/flac.cpp new file mode 100644 index 0000000..44f03d4 --- /dev/null +++ b/libeplayer3/writer/flac.cpp @@ -0,0 +1,61 @@ +/* + * linuxdvb output/writer handling + * + * Copyright (C) 2010 konfetti (based on code from libeplayer2) + * Copyright (C) 2014 martii (based on code from libeplayer3) + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "pes.h" +#include "misc.h" +#include "writer.h" + +class WriterFLAC : public Writer +{ + public: + bool Write(int fd, AVFormatContext * /* avfc */, AVStream *stream, AVPacket *packet, int64_t pts); + WriterFLAC(); +}; + +bool WriterFLAC::Write(int fd, AVFormatContext * /* avfc */, AVStream * /* stream */, AVPacket *packet, int64_t pts) +{ + if (fd < 0 || !packet) + return false; + + uint8_t PesHeader[PES_MAX_HEADER_SIZE]; + struct iovec iov[2]; + + iov[0].iov_base = PesHeader; + iov[0].iov_len = InsertPesHeader(PesHeader, packet->size, MPEG_AUDIO_PES_START_CODE, pts, 0); + iov[1].iov_base = packet->data; + iov[1].iov_len = packet->size; + + return writev(fd, iov, 2) > -1; +} + +WriterFLAC::WriterFLAC() +{ + Register(this, AV_CODEC_ID_FLAC, AUDIO_ENCODING_LPCM); +} + +static WriterFLAC writer_flac __attribute__ ((init_priority (300))); diff --git a/libeplayer3/writer/h263.cpp b/libeplayer3/writer/h263.cpp new file mode 100644 index 0000000..aa0c30e --- /dev/null +++ b/libeplayer3/writer/h263.cpp @@ -0,0 +1,74 @@ +/* + * linuxdvb output/writer handling + * + * Copyright (C) 2010 crow + * Copyright (C) 2010 konfetti (based on code from libeplayer2) + * Copyright (C) 2014 martii (based on code from libeplayer3) + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include + +#include "misc.h" +#include "pes.h" +#include "writer.h" + +class WriterH263 : public Writer +{ + public: + bool Write(int fd, AVFormatContext *avfc, AVStream *stream, AVPacket *packet, int64_t pts); + WriterH263(); +}; + +bool WriterH263::Write(int fd, AVFormatContext * /* avfc */, AVStream * /* stream */, AVPacket *packet, int64_t pts) +{ + if (fd < 0 || !packet) + return false; + uint8_t PesHeader[PES_MAX_HEADER_SIZE]; + + int HeaderLength = InsertPesHeader(PesHeader, packet->size, H263_VIDEO_PES_START_CODE, pts, 0); + + int PrivateHeaderLength = InsertVideoPrivateDataHeader(&PesHeader[HeaderLength], packet->size); + + int PesLength = PesHeader[PES_LENGTH_BYTE_0] + (PesHeader[PES_LENGTH_BYTE_1] << 8) + PrivateHeaderLength; + + PesHeader[PES_LENGTH_BYTE_0] = PesLength & 0xff; + PesHeader[PES_LENGTH_BYTE_1] = (PesLength >> 8) & 0xff; + PesHeader[PES_HEADER_DATA_LENGTH_BYTE] += PrivateHeaderLength; + PesHeader[PES_FLAGS_BYTE] |= PES_EXTENSION_DATA_PRESENT; + + HeaderLength += PrivateHeaderLength; + + struct iovec iov[2]; + iov[0].iov_base = PesHeader; + iov[0].iov_len = HeaderLength; + iov[1].iov_base = packet->data; + iov[1].iov_len = packet->size; + return writev(fd, iov, 2) > -1; +} + +WriterH263::WriterH263() +{ + Register(this, AV_CODEC_ID_H263, VIDEO_ENCODING_H263); + Register(this, AV_CODEC_ID_H263P, VIDEO_ENCODING_H263); + Register(this, AV_CODEC_ID_H263I, VIDEO_ENCODING_H263); + Register(this, AV_CODEC_ID_FLV1, VIDEO_ENCODING_FLV1); +} + +static WriterH263 writer_h263 __attribute__ ((init_priority (300))); diff --git a/libeplayer3/writer/h264.cpp b/libeplayer3/writer/h264.cpp new file mode 100644 index 0000000..1d65c1c --- /dev/null +++ b/libeplayer3/writer/h264.cpp @@ -0,0 +1,255 @@ +/* + * linuxdvb output/writer handling + * + * Copyright (C) 2010 konfetti (based on code from libeplayer2) + * Copyright (C) 2014 martii (based on code from libeplayer3) + * + * Copyright (C) 2014 martii + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "misc.h" +#include "pes.h" +#include "writer.h" + +#define NALU_TYPE_PLAYER2_CONTAINER_PARAMETERS 24 +#define CONTAINER_PARAMETERS_VERSION 0x00 + +typedef struct avcC_s { + uint8_t Version; // configurationVersion + uint8_t Profile; // AVCProfileIndication + uint8_t Compatibility; // profile_compatibility + uint8_t Level; // AVCLevelIndication + uint8_t NalLengthMinusOne; // held in bottom two bits + uint8_t NumParamSets; // held in bottom 5 bits + uint8_t Params[1]; // {length,params}{length,params}...sequence then picture +} avcC_t; + +static uint8_t Head[] = { 0, 0, 0, 1 }; + +class WriterH264 : public Writer +{ + private: + bool initialHeader; + unsigned int NalLengthBytes; + public: + bool Write(int fd, AVFormatContext *avfc, AVStream *stream, AVPacket *packet, int64_t pts); + void Init(); + WriterH264(); +}; + +void WriterH264::Init(void) +{ + initialHeader = true; + NalLengthBytes = 1; +} + +bool WriterH264::Write(int fd, AVFormatContext * /* avfc */, AVStream *stream, AVPacket *packet, int64_t pts) +{ + if (fd < 0 || !packet) + return false; + uint8_t PesHeader[PES_MAX_HEADER_SIZE]; + unsigned int TimeDelta; + unsigned int TimeScale; + int len = 0; + int ic = 0; + struct iovec iov[128]; + + TimeDelta = av_rescale(1000ll, stream->r_frame_rate.num, stream->r_frame_rate.den); + TimeScale = (TimeDelta < 23970) ? 1001 : 1000; /* fixme: revise this */ + + if ((packet->size > 3) + && ((packet->data[0] == 0x00 && packet->data[1] == 0x00 && packet->data[2] == 0x00 && packet->data[3] == 0x01) + || (packet->data[0] == 0xff && packet->data[1] == 0xff && packet->data[2] == 0xff && packet->data[3] == 0xff))) { + unsigned int FakeStartCode = /* (call->Version << 8) | */ PES_VERSION_FAKE_START_CODE; + iov[ic++].iov_base = PesHeader; + if (initialHeader) { + initialHeader = false; + iov[ic].iov_base = stream->codec->extradata; + iov[ic++].iov_len = stream->codec->extradata_size; + len += stream->codec->extradata_size; + } + iov[ic].iov_base = packet->data; + iov[ic++].iov_len = packet->size; + len += packet->size; + // Hellmaster1024: + // some packets will only be accepted by the player if we send one byte more than data is available. + // The content of this byte does not matter. It will be ignored by the player + iov[ic].iov_base = (void *) ""; + iov[ic++].iov_len = 1; + iov[0].iov_len = InsertPesHeader(PesHeader, len, MPEG_VIDEO_PES_START_CODE, pts, FakeStartCode); + return writev(fd, iov, ic) > -1; + } + + if (initialHeader) { + avcC_t *avcCHeader = (avcC_t *) stream->codec->extradata; + + if (!avcCHeader) { + fprintf(stderr, "stream->codec->extradata == NULL\n"); + return false; + } + + if (avcCHeader->Version != 1) + fprintf(stderr, "Error unknown avcC version (%x). Expect problems.\n", avcCHeader->Version); + + uint8_t Header[19]; + unsigned int HeaderLen = 0; + Header[HeaderLen++] = 0x00; // Start code + Header[HeaderLen++] = 0x00; + Header[HeaderLen++] = 0x01; + Header[HeaderLen++] = NALU_TYPE_PLAYER2_CONTAINER_PARAMETERS; + // Container message version - changes when/if we vary the format of the message + Header[HeaderLen++] = CONTAINER_PARAMETERS_VERSION; + Header[HeaderLen++] = 0xff; // Field separator + + if (TimeDelta == 0xffffffff) + TimeDelta = (TimeScale > 1000) ? 1001 : 1; + + Header[HeaderLen++] = (TimeScale >> 24) & 0xff; // Output the timescale + Header[HeaderLen++] = (TimeScale >> 16) & 0xff; + Header[HeaderLen++] = 0xff; + Header[HeaderLen++] = (TimeScale >> 8) & 0xff; + Header[HeaderLen++] = TimeScale & 0xff; + Header[HeaderLen++] = 0xff; + + Header[HeaderLen++] = (TimeDelta >> 24) & 0xff; // Output frame period + Header[HeaderLen++] = (TimeDelta >> 16) & 0xff; + Header[HeaderLen++] = 0xff; + Header[HeaderLen++] = (TimeDelta >> 8) & 0xff; + Header[HeaderLen++] = TimeDelta & 0xff; + Header[HeaderLen++] = 0xff; + Header[HeaderLen++] = 0x80; // Rsbp trailing bits + + ic = 0; + iov[ic].iov_base = PesHeader; + iov[ic++].iov_len = InsertPesHeader(PesHeader, HeaderLen, MPEG_VIDEO_PES_START_CODE, INVALID_PTS_VALUE, 0); + iov[ic].iov_base = Header; + iov[ic++].iov_len = HeaderLen; + len = writev(fd, iov, ic); + if (len < 0) + return false; + + NalLengthBytes = (avcCHeader->NalLengthMinusOne & 0x03) + 1; + unsigned int ParamSets = avcCHeader->NumParamSets & 0x1f; + unsigned int ParamOffset = 0; + unsigned int InitialHeaderLength = 0; + + ic = 0; + iov[ic++].iov_base = PesHeader; + for (unsigned int i = 0; i < ParamSets; i++) { + unsigned int PsLength = (avcCHeader->Params[ParamOffset] << 8) + avcCHeader->Params[ParamOffset + 1]; + + iov[ic].iov_base = (char *) Head; + iov[ic++].iov_len = sizeof(Head); + InitialHeaderLength += sizeof(Head); + iov[ic].iov_base = &avcCHeader->Params[ParamOffset + 2]; + iov[ic++].iov_len = PsLength; + InitialHeaderLength += PsLength; + ParamOffset += PsLength + 2; + } + + ParamSets = avcCHeader->Params[ParamOffset++]; + + for (unsigned int i = 0; i < ParamSets; i++) { + unsigned int PsLength = (avcCHeader->Params[ParamOffset] << 8) + avcCHeader->Params[ParamOffset + 1]; + + iov[ic].iov_base = (char *) Head; + iov[ic++].iov_len = sizeof(Head); + InitialHeaderLength += sizeof(Head); + iov[ic].iov_base = &avcCHeader->Params[ParamOffset + 2]; + iov[ic++].iov_len = PsLength; + InitialHeaderLength += PsLength; + ParamOffset += PsLength + 2; + } + + iov[0].iov_len = InsertPesHeader(PesHeader, InitialHeaderLength, MPEG_VIDEO_PES_START_CODE, INVALID_PTS_VALUE, 0); + ssize_t l = writev(fd, iov, ic); + if (l < 0) + return false; + len += l; + + initialHeader = 0; + } + + unsigned int SampleSize = packet->size; + unsigned int NalStart = 0; + unsigned int VideoPosition = 0; + + do { + unsigned int NalLength; + uint8_t NalData[4]; + + memcpy(NalData, packet->data + VideoPosition, NalLengthBytes); + VideoPosition += NalLengthBytes; + NalStart += NalLengthBytes; + switch (NalLengthBytes) { + case 1: + NalLength = (NalData[0]); + break; + case 2: + NalLength = (NalData[0] << 8) | (NalData[1]); + break; + case 3: + NalLength = (NalData[0] << 16) | (NalData[1] << 8) | (NalData[2]); + break; + default: + NalLength = (NalData[0] << 24) | (NalData[1] << 16) | (NalData[2] << 8) | (NalData[3]); + break; + } + + if (NalStart + NalLength > SampleSize) { + fprintf(stderr, "nal length past end of buffer - size %u frame offset %u left %u\n", NalLength, NalStart, SampleSize - NalStart); + NalStart = SampleSize; + } else { + NalStart += NalLength; + ic = 0; + iov[ic++].iov_base = PesHeader; + + iov[ic].iov_base = Head; + iov[ic++].iov_len = sizeof(Head); + + iov[ic].iov_base = packet->data + VideoPosition; + iov[ic++].iov_len = NalLength; + + VideoPosition += NalLength; + + iov[0].iov_len = InsertPesHeader(PesHeader, NalLength, MPEG_VIDEO_PES_START_CODE, pts, 0); + ssize_t l = writev(fd, iov, ic); + if (l < 0) + return false; + len += l; + + pts = INVALID_PTS_VALUE; + } + } while (NalStart < SampleSize); + + return len > -1; +} + +WriterH264::WriterH264() +{ + Register(this, AV_CODEC_ID_H264, VIDEO_ENCODING_H264); + Init(); +} + +static WriterH264 writerh264 __attribute__ ((init_priority (300))); diff --git a/libeplayer3/writer/misc.cpp b/libeplayer3/writer/misc.cpp new file mode 100644 index 0000000..c63e659 --- /dev/null +++ b/libeplayer3/writer/misc.cpp @@ -0,0 +1,88 @@ +/* + * LinuxDVB Output handling. + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "misc.h" + +void PutBits(BitPacker_t * ld, unsigned int code, unsigned int length) +{ + unsigned int bit_buf; + unsigned int bit_left; + + bit_buf = ld->BitBuffer; + bit_left = ld->Remaining; + +#ifdef DEBUG_PUTBITS + if (ld->debug) + dprintf("code = %d, length = %d, bit_buf = 0x%x, bit_left = %d\n", + code, length, bit_buf, bit_left); +#endif + + if (length < bit_left) { + /* fits into current buffer */ + bit_buf = (bit_buf << length) | code; + bit_left -= length; + } else { + /* doesn't fit */ + bit_buf <<= bit_left; + bit_buf |= code >> (length - bit_left); + ld->Ptr[0] = (uint8_t) (bit_buf >> 24); + ld->Ptr[1] = (uint8_t) (bit_buf >> 16); + ld->Ptr[2] = (uint8_t) (bit_buf >> 8); + ld->Ptr[3] = (uint8_t) bit_buf; + ld->Ptr += 4; + length -= bit_left; + bit_buf = code & ((1 << length) - 1); + bit_left = 32 - length; + bit_buf = code; + } + +#ifdef DEBUG_PUTBITS + if (ld->debug) + dprintf("bit_left = %d, bit_buf = 0x%x\n", bit_left, bit_buf); +#endif + + /* writeback */ + ld->BitBuffer = bit_buf; + ld->Remaining = bit_left; +} + +void FlushBits(BitPacker_t * ld) +{ + ld->BitBuffer <<= ld->Remaining; + while (ld->Remaining < 32) { +#ifdef DEBUG_PUTBITS + if (ld->debug) + dprintf("flushing 0x%2.2x\n", ld->BitBuffer >> 24); +#endif + *ld->Ptr++ = ld->BitBuffer >> 24; + ld->BitBuffer <<= 8; + ld->Remaining += 8; + } + ld->Remaining = 32; + ld->BitBuffer = 0; +} diff --git a/libeplayer3/writer/mp3.cpp b/libeplayer3/writer/mp3.cpp new file mode 100644 index 0000000..024bb4f --- /dev/null +++ b/libeplayer3/writer/mp3.cpp @@ -0,0 +1,63 @@ +/* + * linuxdvb output/writer handling + * + * Copyright (C) 2010 konfetti (based on code from libeplayer2) + * Copyright (C) 2014 martii (based on code from libeplayer3) + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include "misc.h" +#include "pes.h" +#include "writer.h" + +class WriterMP3 : public Writer +{ + public: + bool Write(int fd, AVFormatContext *avfc, AVStream *stream, AVPacket *packet, int64_t pts); + WriterMP3(); +}; + +bool WriterMP3::Write(int fd, AVFormatContext * /* avfc */, AVStream * /* stream */, AVPacket *packet, int64_t pts) +{ + if (fd < 0 || !packet) + return false; + + uint8_t PesHeader[PES_MAX_HEADER_SIZE]; + struct iovec iov[2]; + + iov[0].iov_base = PesHeader; + iov[0].iov_len = InsertPesHeader(PesHeader, packet->size, MPEG_AUDIO_PES_START_CODE, pts, 0); + iov[1].iov_base = packet->data; + iov[1].iov_len = packet->size; + + return writev(fd, iov, 2) > -1; +} + +WriterMP3::WriterMP3() +{ + Register(this, AV_CODEC_ID_MP3, AUDIO_ENCODING_MP3); + Register(this, AV_CODEC_ID_MP2, AUDIO_ENCODING_MPEG2); +// Register(this, AV_CODEC_ID_VORBIS, AUDIO_ENCODING_VORBIS); +} + +static WriterMP3 writer_mp3 __attribute__ ((init_priority (300))); diff --git a/libeplayer3/writer/mpeg2.cpp b/libeplayer3/writer/mpeg2.cpp new file mode 100644 index 0000000..a9143ec --- /dev/null +++ b/libeplayer3/writer/mpeg2.cpp @@ -0,0 +1,73 @@ +/* + * linuxdvb output/writer handling + * + * Copyright (C) 2010 konfetti (based on code from libeplayer2) + * Copyright (C) 2014 martii (based on code from libeplayer3) + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "misc.h" +#include "pes.h" +#include "writer.h" + +class WriterMPEG2 : public Writer +{ + public: + bool Write(int fd, AVFormatContext *avfc, AVStream *stream, AVPacket *packet, int64_t pts); + WriterMPEG2(); +}; + +bool WriterMPEG2::Write(int fd, AVFormatContext * /* avfc */, AVStream * /* stream */, AVPacket *packet, int64_t pts) +{ + if (fd < 0 || !packet) + return false; + + uint8_t PesHeader[PES_MAX_HEADER_SIZE]; + + for (int Position = 0; Position < packet->size; ) { + int PacketLength = std::min(packet->size - Position, MAX_PES_PACKET_SIZE); + struct iovec iov[2]; + iov[0].iov_base = PesHeader; + iov[0].iov_len = InsertPesHeader(PesHeader, PacketLength, 0xe0, pts, 0); + iov[1].iov_base = packet->data + Position; + iov[1].iov_len = PacketLength; + + ssize_t l = writev(fd, iov, 2); + if (l < 0) { + return false; + break; + } + Position += PacketLength; + pts = INVALID_PTS_VALUE; + } + return true; +} + +WriterMPEG2::WriterMPEG2() +{ + Register(this, AV_CODEC_ID_MPEG2TS, VIDEO_ENCODING_AUTO); +} + +static WriterMPEG2 writer_mpeg2 __attribute__ ((init_priority (300))); diff --git a/libeplayer3/writer/pcm.cpp b/libeplayer3/writer/pcm.cpp new file mode 100644 index 0000000..cfc42dd --- /dev/null +++ b/libeplayer3/writer/pcm.cpp @@ -0,0 +1,355 @@ +/* + * linuxdvb output/writer handling. + * + * Copyright (C) 2010 konfetti (based on code from libeplayer2) + * Copyright (C) 2014 martii (based on code from libeplayer3) + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "misc.h" +#include "pes.h" +#include "writer.h" + +extern "C" { +#include +#include +#include +#include +#include +} + +// reference: search for TypeLpcmDVDAudio in player/frame_parser/frame_parser_audio_lpcm.cpp +static const uint8_t clpcm_prv[14] = { + 0xA0, //sub_stream_id + 0, 0, //resvd and UPC_EAN_ISRC stuff, unused + 0x0A, //private header length + 0, 9, //first_access_unit_pointer + 0x00, //emph,rsvd,stereo,downmix + 0x0F, //quantisation word length 1,2 + 0x0F, //audio sampling freqency 1,2 + 0, //resvd, multi channel type + 0, //bit shift on channel GR2, assignment + 0x80, //dynamic range control + 0, 0 //resvd for copyright management +}; + +class WriterPCM : public Writer +{ + private: + unsigned int SubFrameLen; + unsigned int SubFramesPerPES; + uint8_t lpcm_prv[14]; + uint8_t injectBuffer[2048]; + uint8_t breakBuffer[sizeof(injectBuffer)]; + unsigned int breakBufferFillSize; + int uNoOfChannels; + int uSampleRate; + int uBitsPerSample; + + SwrContext *swr; + AVFrame *decoded_frame; + int out_sample_rate; + int out_channels; + uint64_t out_channel_layout; + bool initialHeader; + bool restart_audio_resampling; + + public: + bool Write(int fd, AVFormatContext *avfc, AVStream *stream, AVPacket *packet, int64_t pts); + bool prepareClipPlay(); + bool writePCM(int fd, int64_t Pts, uint8_t *data, unsigned int size); + void Init(); + WriterPCM(); +}; + +bool WriterPCM::prepareClipPlay() +{ + SubFrameLen = 0; + SubFramesPerPES = 0; + breakBufferFillSize = 0; + + memcpy(lpcm_prv, clpcm_prv, sizeof(lpcm_prv)); + + // figure out size of subframe and set up sample rate + switch (uSampleRate) { + case 48000: + SubFrameLen = 40; + break; + case 96000: + lpcm_prv[8] |= 0x10; + SubFrameLen = 80; + break; + case 192000: + lpcm_prv[8] |= 0x20; + SubFrameLen = 160; + break; + case 44100: + lpcm_prv[8] |= 0x80; + SubFrameLen = 40; + break; + case 88200: + lpcm_prv[8] |= 0x90; + SubFrameLen = 80; + break; + case 176400: + lpcm_prv[8] |= 0xA0; + SubFrameLen = 160; + break; + default: + break; + } + + SubFrameLen *= uNoOfChannels; + SubFrameLen *= uBitsPerSample / 8; + + //rewrite PES size to have as many complete subframes per PES as we can + // FIXME: PES header size was hardcoded to 18 in earlier code. Actual size returned by InsertPesHeader is 14. + SubFramesPerPES = ((sizeof(injectBuffer) - 18) - sizeof(lpcm_prv)) / SubFrameLen; + SubFrameLen *= SubFramesPerPES; + + //set number of channels + lpcm_prv[10] = uNoOfChannels - 1; + + switch (uBitsPerSample) { + case 24: + lpcm_prv[7] |= 0x20; + case 16: + break; + default: + printf("inappropriate bits per sample (%d) - must be 16 or 24\n", uBitsPerSample); + return false; + } + + return true; +} + +bool WriterPCM::writePCM(int fd, int64_t Pts, uint8_t *data, unsigned int size) +{ + bool res = true; + uint8_t PesHeader[PES_MAX_HEADER_SIZE]; + + if (initialHeader) { + initialHeader = false; + prepareClipPlay(); + ioctl(fd, AUDIO_CLEAR_BUFFER, NULL); + } + + size += breakBufferFillSize; + + while (size >= SubFrameLen) { + if (breakBufferFillSize) + memcpy(injectBuffer, breakBuffer, breakBufferFillSize); + memcpy(injectBuffer + breakBufferFillSize, data, SubFrameLen - breakBufferFillSize); + size -= SubFrameLen; + data += SubFrameLen - breakBufferFillSize; + breakBufferFillSize = 0; + + //write the PCM data + if (uBitsPerSample == 16) { + for (unsigned int n = 0; n < SubFrameLen; n += 2) { + uint8_t tmp = injectBuffer[n]; + injectBuffer[n] = injectBuffer[n + 1]; + injectBuffer[n + 1] = tmp; + } + } else { + // 0 1 2 3 4 5 6 7 8 9 10 11 + // A1c A1b A1a B1c B1b B1a A2c A2b A2a B2c B2b B2a + // to A1a A1b B1a B1b A2a A2b B2a B2b A1c B1c A2c B2c + for (unsigned int n = 0; n < SubFrameLen; n += 12) { + uint8_t t, *p = injectBuffer + n; + t = p[0]; + p[0] = p[2]; + p[2] = p[5]; + p[5] = p[7]; + p[7] = p[11]; + p[11] = p[9]; + p[9] = p[3]; + p[3] = p[4]; + p[4] = p[8]; + p[8] = t; + } + } + + //increment err... subframe count? + lpcm_prv[1] = ((lpcm_prv[1] + SubFramesPerPES) & 0x1F); + + struct iovec iov[3]; + iov[0].iov_base = PesHeader; + iov[1].iov_base = lpcm_prv; + iov[1].iov_len = sizeof(lpcm_prv); + iov[2].iov_base = injectBuffer; + iov[2].iov_len = SubFrameLen; + iov[0].iov_len = InsertPesHeader(PesHeader, iov[1].iov_len + iov[2].iov_len, PCM_PES_START_CODE, Pts, 0); + int len = writev(fd, iov, 3); + if (len < 0) { + res = false; + break; + } + } + if (size) { + breakBufferFillSize = size; + memcpy(breakBuffer, data, size); + } + + return res; +} + +void WriterPCM::Init() +{ + initialHeader = true; + restart_audio_resampling = true; +} + +bool WriterPCM::Write(int fd, AVFormatContext * /*avfc*/, AVStream *stream, AVPacket *packet, int64_t pts) +{ + if (fd < 0) + return false; + + if (!packet) { + restart_audio_resampling = true; + return true; + } + + AVCodecContext *c = stream->codec; + unsigned int packet_size = packet->size; + + if (restart_audio_resampling) { + restart_audio_resampling = false; + initialHeader = true; + + if (swr) + swr_free(&swr); + if (decoded_frame) + av_frame_free(&decoded_frame); + + AVCodec *codec = avcodec_find_decoder(c->codec_id); + + if (!codec || avcodec_open2(c, codec, NULL)) { + fprintf(stderr, "%s %d: avcodec_open2 failed\n", __func__, __LINE__); + return false; + } + } + + if (!swr) { + int rates[] = { 48000, 96000, 192000, 44100, 88200, 176400, 0 }; + int *rate = rates; + int in_rate = c->sample_rate; + while (*rate && ((*rate / in_rate) * in_rate != *rate) && (in_rate / *rate) * *rate != in_rate) + rate++; + out_sample_rate = *rate ? *rate : 44100; + out_channels = c->channels; + if (c->channel_layout == 0) { + // FIXME -- need to guess, looks pretty much like a bug in the FFMPEG WMA decoder + c->channel_layout = AV_CH_LAYOUT_STEREO; + } + + out_channel_layout = c->channel_layout; + // player2 won't play mono + if (out_channel_layout == AV_CH_LAYOUT_MONO) { + out_channel_layout = AV_CH_LAYOUT_STEREO; + out_channels = 2; + } + + uSampleRate = out_sample_rate; + uNoOfChannels = av_get_channel_layout_nb_channels(out_channel_layout); + uBitsPerSample = 16; + + swr = swr_alloc(); + if (!swr) { + fprintf(stderr, "%s %d: swr_alloc failed\n", __func__, __LINE__); + return false; + } + av_opt_set_int(swr, "in_channel_layout", c->channel_layout, 0); + av_opt_set_int(swr, "out_channel_layout", out_channel_layout, 0); + av_opt_set_int(swr, "in_sample_rate", c->sample_rate, 0); + av_opt_set_int(swr, "out_sample_rate", out_sample_rate, 0); + av_opt_set_sample_fmt(swr, "in_sample_fmt", c->sample_fmt, 0); + av_opt_set_sample_fmt(swr, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0); + + int e = swr_init(swr); + if (e < 0) { + fprintf(stderr, "swr_init: %d (icl=%d ocl=%d isr=%d osr=%d isf=%d osf=%d\n", + -e, (int) c->channel_layout, + (int) out_channel_layout, c->sample_rate, out_sample_rate, c->sample_fmt, AV_SAMPLE_FMT_S16); + swr_free(&swr); + restart_audio_resampling = true; + return false; + } + } + + while (packet_size > 0) { + int got_frame = 0; + if (decoded_frame) + av_frame_unref(decoded_frame); + else if (!(decoded_frame = av_frame_alloc())) { + fprintf(stderr, "out of memory\n"); + exit(1); + } + + int len = avcodec_decode_audio4(c, decoded_frame, &got_frame, packet); + if (len < 0) { + restart_audio_resampling = true; + break; + } + + packet_size -= len; + + if (!got_frame) + continue; + + int in_samples = decoded_frame->nb_samples; + int out_samples = av_rescale_rnd(swr_get_delay(swr, c->sample_rate) + in_samples, out_sample_rate, c->sample_rate, AV_ROUND_UP); + + uint8_t *output = NULL; + int e = av_samples_alloc(&output, NULL, out_channels, out_samples, AV_SAMPLE_FMT_S16, 1); + if (e < 0) { + fprintf(stderr, "av_samples_alloc: %d\n", -e); + break; + } + out_samples = swr_convert(swr, &output, out_samples, (const uint8_t **) &decoded_frame->data[0], in_samples); + + if(!writePCM(fd, pts, output, out_samples * sizeof(short) * out_channels)) { + restart_audio_resampling = true; + av_free(output); + break; + } + + av_free(output); + pts = 0; + } + return true; +} + +WriterPCM::WriterPCM() +{ + swr = NULL; + decoded_frame = NULL; + + Register(this, AV_CODEC_ID_INJECTPCM, AUDIO_ENCODING_LPCMA); + Init(); +} + +static WriterPCM writer_pcm __attribute__ ((init_priority (300))); diff --git a/libeplayer3/writer/pes.cpp b/libeplayer3/writer/pes.cpp new file mode 100644 index 0000000..f32da30 --- /dev/null +++ b/libeplayer3/writer/pes.cpp @@ -0,0 +1,116 @@ +/* + * linuxdvb output/writer handling. + * + * Copyright (C) 2010 konfetti (based on code from libeplayer2) + * Copyright (C) 2014 martii (based on code from libeplayer3) + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "misc.h" +#include "pes.h" + +int InsertVideoPrivateDataHeader(uint8_t *data, int payload_size) +{ + BitPacker_t ld2 = { data, 0, 32 }; + int i; + + PutBits(&ld2, PES_PRIVATE_DATA_FLAG, 8); + PutBits(&ld2, payload_size & 0xff, 8); + PutBits(&ld2, (payload_size >> 8) & 0xff, 8); + PutBits(&ld2, (payload_size >> 16) & 0xff, 8); + + for (i = 4; i < (PES_PRIVATE_DATA_LENGTH + 1); i++) + PutBits(&ld2, 0, 8); + + FlushBits(&ld2); + + return PES_PRIVATE_DATA_LENGTH + 1; + +} + +int InsertPesHeader(uint8_t *data, int size, uint8_t stream_id, int64_t pts, int pic_start_code) +{ + BitPacker_t ld2 = { data, 0, 32 }; + + if (size > MAX_PES_PACKET_SIZE) + size = 0; // unbounded + + PutBits(&ld2, 0x0, 8); + PutBits(&ld2, 0x0, 8); + PutBits(&ld2, 0x1, 8); // Start Code + PutBits(&ld2, stream_id, 8); // Stream_id = Audio Stream + //4 + PutBits(&ld2, size + 3 + (pts != INVALID_PTS_VALUE ? 5 : 0) + (pic_start_code ? (5) : 0), 16); // PES_packet_length + //6 = 4+2 + PutBits(&ld2, 0x2, 2); // 10 + PutBits(&ld2, 0x0, 2); // PES_Scrambling_control + PutBits(&ld2, 0x0, 1); // PES_Priority + PutBits(&ld2, 0x0, 1); // data_alignment_indicator + PutBits(&ld2, 0x0, 1); // Copyright + PutBits(&ld2, 0x0, 1); // Original or Copy + //7 = 6+1 + + if (pts != INVALID_PTS_VALUE) + PutBits(&ld2, 0x2, 2); + else + PutBits(&ld2, 0x0, 2); // PTS_DTS flag + + PutBits(&ld2, 0x0, 1); // ESCR_flag + PutBits(&ld2, 0x0, 1); // ES_rate_flag + PutBits(&ld2, 0x0, 1); // DSM_trick_mode_flag + PutBits(&ld2, 0x0, 1); // additional_copy_ingo_flag + PutBits(&ld2, 0x0, 1); // PES_CRC_flag + PutBits(&ld2, 0x0, 1); // PES_extension_flag + //8 = 7+1 + + if (pts != INVALID_PTS_VALUE) + PutBits(&ld2, 0x5, 8); + else + PutBits(&ld2, 0x0, 8); // PES_header_data_length + //9 = 8+1 + + if (pts != INVALID_PTS_VALUE) { + PutBits(&ld2, 0x2, 4); + PutBits(&ld2, (pts >> 30) & 0x7, 3); + PutBits(&ld2, 0x1, 1); + PutBits(&ld2, (pts >> 15) & 0x7fff, 15); + PutBits(&ld2, 0x1, 1); + PutBits(&ld2, pts & 0x7fff, 15); + PutBits(&ld2, 0x1, 1); + } + //14 = 9+5 + + if (pic_start_code) { + PutBits(&ld2, 0x0, 8); + PutBits(&ld2, 0x0, 8); + PutBits(&ld2, 0x1, 8); // Start Code + PutBits(&ld2, pic_start_code & 0xff, 8); // 00, for picture start + PutBits(&ld2, (pic_start_code >> 8) & 0xff, 8); // For any extra information (like in mpeg4p2, the pic_start_code) + //14 + 4 = 18 + } + + FlushBits(&ld2); + + return (ld2.Ptr - data); +} diff --git a/libeplayer3/writer/vc1.cpp b/libeplayer3/writer/vc1.cpp new file mode 100644 index 0000000..74152aa --- /dev/null +++ b/libeplayer3/writer/vc1.cpp @@ -0,0 +1,187 @@ +/* + * linuxdvb output/writer handling + * + * Copyright (C) 2010 konfetti (based on code from libeplayer2) + * Copyright (C) 2014 martii (based on code from libeplayer3) + * + * Copyright (C) 2014 martii + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "misc.h" +#include "pes.h" +#include "writer.h" + +#define WMV3_PRIVATE_DATA_LENGTH 4 + +#define METADATA_STRUCT_A_START 12 +#define METADATA_STRUCT_B_START 24 +#define METADATA_STRUCT_B_FRAMERATE_START 32 +#define METADATA_STRUCT_C_START 8 + + +#define VC1_SEQUENCE_LAYER_METADATA_START_CODE 0x80 +#define VC1_FRAME_START_CODE 0x0d + +class WriterVC1 : public Writer +{ + private: + bool initialHeader; + uint8_t FrameHeaderSeen; + public: + bool Write(int fd, AVFormatContext *avfc, AVStream *stream, AVPacket *packet, int64_t pts); + void Init(); + WriterVC1(); +}; + +void WriterVC1::Init() +{ + initialHeader = true; +} + +bool WriterVC1::Write(int fd, AVFormatContext * /* avfc */, AVStream *stream, AVPacket *packet, int64_t pts) +{ + if (fd < 0 || !packet) + return false; + + if (initialHeader) { + initialHeader = false; + FrameHeaderSeen = false; + + const uint8_t SequenceLayerStartCode[] = + { 0x00, 0x00, 0x01, VC1_SEQUENCE_LAYER_METADATA_START_CODE }; + + + const uint8_t Metadata[] = { + 0x00, 0x00, 0x00, 0xc5, + 0x04, 0x00, 0x00, 0x00, + 0xc0, 0x00, 0x00, 0x00, /* Struct C set for for advanced profile */ + 0x00, 0x00, 0x00, 0x00, /* Struct A */ + 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, + 0x60, 0x00, 0x00, 0x00, /* Struct B */ + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 + }; + + uint8_t PesHeader[PES_MAX_HEADER_SIZE]; + uint8_t PesPayload[128]; + uint8_t *PesPtr; + unsigned int usecPerFrame = av_rescale(AV_TIME_BASE, stream->r_frame_rate.den, stream->r_frame_rate.num); + struct iovec iov[2]; + + + memset(PesPayload, 0, sizeof(PesPayload)); + + PesPtr = PesPayload; + + memcpy(PesPtr, SequenceLayerStartCode, sizeof(SequenceLayerStartCode)); + PesPtr += sizeof(SequenceLayerStartCode); + + memcpy(PesPtr, Metadata, sizeof(Metadata)); + PesPtr += METADATA_STRUCT_C_START; + PesPtr += WMV3_PRIVATE_DATA_LENGTH; + + /* Metadata Header Struct A */ + *PesPtr++ = (stream->codec->height >> 0) & 0xff; + *PesPtr++ = (stream->codec->height >> 8) & 0xff; + *PesPtr++ = (stream->codec->height >> 16) & 0xff; + *PesPtr++ = stream->codec->height >> 24; + *PesPtr++ = (stream->codec->width >> 0) & 0xff; + *PesPtr++ = (stream->codec->width >> 8) & 0xff; + *PesPtr++ = (stream->codec->width >> 16) & 0xff; + *PesPtr++ = stream->codec->width >> 24; + + PesPtr += 12; /* Skip flag word and Struct B first 8 bytes */ + + *PesPtr++ = (usecPerFrame >> 0) & 0xff; + *PesPtr++ = (usecPerFrame >> 8) & 0xff; + *PesPtr++ = (usecPerFrame >> 16) & 0xff; + *PesPtr++ = usecPerFrame >> 24; + + iov[0].iov_base = PesHeader; + iov[1].iov_base = PesPayload; + iov[1].iov_len = PesPtr - PesPayload; + iov[0].iov_len = InsertPesHeader(PesHeader, iov[1].iov_len, VC1_VIDEO_PES_START_CODE, INVALID_PTS_VALUE, 0); + if (writev(fd, iov, 2) < 0) + return false; + + /* For VC1 the codec private data is a standard vc1 sequence header so we just copy it to the output */ + iov[0].iov_base = PesHeader; + iov[1].iov_base = stream->codec->extradata; + iov[1].iov_len = stream->codec->extradata_size; + iov[0].iov_len = InsertPesHeader(PesHeader, iov[1].iov_len, VC1_VIDEO_PES_START_CODE, INVALID_PTS_VALUE, 0); + if (writev(fd, iov, 2) < 0) + return false; + + initialHeader = false; + } + + if (packet->size > 0) { + int Position = 0; + bool insertSampleHeader = true; + + while (Position < packet->size) { + int PacketLength = std::min(packet->size - Position, MAX_PES_PACKET_SIZE); + uint8_t PesHeader[PES_MAX_HEADER_SIZE]; + int HeaderLength = InsertPesHeader(PesHeader, PacketLength, VC1_VIDEO_PES_START_CODE, pts, 0); + + if (insertSampleHeader) { + const uint8_t Vc1FrameStartCode[] = { 0, 0, 1, VC1_FRAME_START_CODE }; + + if (!FrameHeaderSeen && (packet->size > 3) && (memcmp(packet->data, Vc1FrameStartCode, 4) == 0)) + FrameHeaderSeen = true; + if (!FrameHeaderSeen) { + memcpy(&PesHeader[HeaderLength], Vc1FrameStartCode, sizeof(Vc1FrameStartCode)); + HeaderLength += sizeof(Vc1FrameStartCode); + } + insertSampleHeader = false; + } + + struct iovec iov[2]; + iov[0].iov_base = PesHeader; + iov[0].iov_len = HeaderLength; + iov[1].iov_base = packet->data + Position; + iov[1].iov_len = PacketLength; + + ssize_t l = writev(fd, iov, 2); + if (l < 0) + return false; + + Position += PacketLength; + pts = INVALID_PTS_VALUE; + } + } + + return true; +} + +WriterVC1::WriterVC1() +{ + Register(this, AV_CODEC_ID_VC1, VIDEO_ENCODING_VC1); + Init(); +} + +static WriterVC1 writer_vc1 __attribute__ ((init_priority (300))); diff --git a/libeplayer3/writer/wmv.cpp b/libeplayer3/writer/wmv.cpp new file mode 100644 index 0000000..644c8ce --- /dev/null +++ b/libeplayer3/writer/wmv.cpp @@ -0,0 +1,170 @@ +/* + * linuxdvb output/writer handling + * + * Copyright (C) 2010 konfetti (based on code from libeplayer2) + * Copyright (C) 2014 martii (based on code from libeplayer3) + * + * Copyright (C) 2014 martii + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "misc.h" +#include "pes.h" +#include "writer.h" + +#include + +#define WMV3_PRIVATE_DATA_LENGTH 4 + +static const uint8_t Metadata[] = { + 0x00, 0x00, 0x00, 0xc5, + 0x04, 0x00, 0x00, 0x00, +#define METADATA_STRUCT_C_START 8 + 0xc0, 0x00, 0x00, 0x00, /* Struct C set for for advanced profile */ +#define METADATA_STRUCT_A_START 12 + 0x00, 0x00, 0x00, 0x00, /* Struct A */ + 0x00, 0x00, 0x00, 0x00, + 0x0c, 0x00, 0x00, 0x00, +#define METADATA_STRUCT_B_START 24 + 0x60, 0x00, 0x00, 0x00, /* Struct B */ + 0x00, 0x00, 0x00, 0x00, +#define METADATA_STRUCT_B_FRAMERATE_START 32 + 0x00, 0x00, 0x00, 0x00 +}; + +class WriterWMV : public Writer +{ + private: + bool initialHeader; + public: + bool Write(int fd, AVFormatContext *avfc, AVStream *stream, AVPacket *packet, int64_t pts); + void Init(); + WriterWMV(); +}; + +void WriterWMV::Init() +{ + initialHeader = true; +} + +bool WriterWMV::Write(int fd, AVFormatContext * /* avfc */, AVStream *stream, AVPacket *packet, int64_t pts) +{ + if (fd < 0 || !packet) + return false; + + if (initialHeader) { +#define PES_MIN_HEADER_SIZE 9 + uint8_t PesPacket[PES_MIN_HEADER_SIZE + 128]; + uint8_t *PesPtr; + unsigned int MetadataLength; + unsigned int usecPerFrame = av_rescale(AV_TIME_BASE, stream->r_frame_rate.den, stream->r_frame_rate.num); + + PesPtr = &PesPacket[PES_MIN_HEADER_SIZE]; + + memcpy(PesPtr, Metadata, sizeof(Metadata)); + PesPtr += METADATA_STRUCT_C_START; + + uint8_t privateData[WMV3_PRIVATE_DATA_LENGTH] = { 0 }; + memcpy(privateData, stream->codec->extradata, stream->codec->extradata_size > WMV3_PRIVATE_DATA_LENGTH ? WMV3_PRIVATE_DATA_LENGTH : stream->codec->extradata_size); + + memcpy(PesPtr, privateData, WMV3_PRIVATE_DATA_LENGTH); + PesPtr += WMV3_PRIVATE_DATA_LENGTH; + + /* Metadata Header Struct A */ + *PesPtr++ = (stream->codec->height >> 0) & 0xff; + *PesPtr++ = (stream->codec->height >> 8) & 0xff; + *PesPtr++ = (stream->codec->height >> 16) & 0xff; + *PesPtr++ = stream->codec->height >> 24; + *PesPtr++ = (stream->codec->width >> 0) & 0xff; + *PesPtr++ = (stream->codec->width >> 8) & 0xff; + *PesPtr++ = (stream->codec->width >> 16) & 0xff; + *PesPtr++ = stream->codec->width >> 24; + + PesPtr += 12; /* Skip flag word and Struct B first 8 bytes */ + + *PesPtr++ = (usecPerFrame >> 0) & 0xff; + *PesPtr++ = (usecPerFrame >> 8) & 0xff; + *PesPtr++ = (usecPerFrame >> 16) & 0xff; + *PesPtr++ = usecPerFrame >> 24; + + MetadataLength = PesPtr - &PesPacket[PES_MIN_HEADER_SIZE]; + + int HeaderLength = InsertPesHeader(PesPacket, MetadataLength, VC1_VIDEO_PES_START_CODE, INVALID_PTS_VALUE, 0); + + if (write(fd, PesPacket, HeaderLength + MetadataLength) < 0) + return false; + + initialHeader = false; + } + + if (packet->size > 0 && packet->data) { + int Position = 0; + bool insertSampleHeader = true; + + while (Position < packet->size) { + + int PacketLength = std::min(packet->size - Position, MAX_PES_PACKET_SIZE); + + uint8_t PesHeader[PES_MAX_HEADER_SIZE] = { 0 }; + int HeaderLength = InsertPesHeader(PesHeader, PacketLength, VC1_VIDEO_PES_START_CODE, pts, 0); + + if (insertSampleHeader) { + unsigned int PesLength; + unsigned int PrivateHeaderLength; + + PrivateHeaderLength = InsertVideoPrivateDataHeader(&PesHeader[HeaderLength], packet->size); + /* Update PesLength */ + PesLength = PesHeader[PES_LENGTH_BYTE_0] + (PesHeader[PES_LENGTH_BYTE_1] << 8) + PrivateHeaderLength; + PesHeader[PES_LENGTH_BYTE_0] = PesLength & 0xff; + PesHeader[PES_LENGTH_BYTE_1] = (PesLength >> 8) & 0xff; + PesHeader[PES_HEADER_DATA_LENGTH_BYTE] += PrivateHeaderLength; + PesHeader[PES_FLAGS_BYTE] |= PES_EXTENSION_DATA_PRESENT; + + HeaderLength += PrivateHeaderLength; + insertSampleHeader = false; + } + + uint8_t PacketStart[packet->size + HeaderLength]; + memcpy(PacketStart, PesHeader, HeaderLength); + memcpy(PacketStart + HeaderLength, packet->data + Position, PacketLength); + if (write(fd, PacketStart, PacketLength + HeaderLength) < 0) + return false; + + Position += PacketLength; + pts = INVALID_PTS_VALUE; + } + } + + return true; +} + +WriterWMV::WriterWMV() +{ + Register(this, AV_CODEC_ID_WMV1, VIDEO_ENCODING_WMV); + Register(this, AV_CODEC_ID_WMV2, VIDEO_ENCODING_WMV); + Register(this, AV_CODEC_ID_WMV3, VIDEO_ENCODING_WMV); + Init(); +} + +static WriterWMV writer_wmv __attribute__ ((init_priority (300))); diff --git a/libeplayer3/writer/writer.cpp b/libeplayer3/writer/writer.cpp new file mode 100644 index 0000000..e15a426 --- /dev/null +++ b/libeplayer3/writer/writer.cpp @@ -0,0 +1,103 @@ +/* + * linuxdvb output/writer handling + * + * Copyright (C) 2014 martii + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include + +#include +#include + +#include "pes.h" +#include "writer.h" + +// This does suck ... the original idea was to just link the object files and let them register themselves. +// Alas, that didn't work as expected. +#include "ac3.cpp" +#include "divx.cpp" +#include "dts.cpp" +#include "flac.cpp" +#include "h263.cpp" +#include "h264.cpp" +#include "mp3.cpp" +#include "mpeg2.cpp" +#include "pcm.cpp" +#include "vc1.cpp" +#include "wmv.cpp" + +static std::mapwriters __attribute__ ((init_priority (200))); +static std::mapvencoding __attribute__ ((init_priority (200))); +static std::mapaencoding __attribute__ ((init_priority (200))); + +void Writer::Register(Writer *w, enum AVCodecID id, video_encoding_t encoding) +{ + writers[id] = w; + vencoding[id] = encoding; +} + +void Writer::Register(Writer *w, enum AVCodecID id, audio_encoding_t encoding) +{ + writers[id] = w; + aencoding[id] = encoding; +} + +bool Writer::Write(int /* fd */, AVFormatContext * /* avfc */, AVStream * /*stream*/, AVPacket * /* packet */, int64_t /* pts */) +{ + return false; +} + +static Writer writer __attribute__ ((init_priority (300))); + +Writer *Writer::GetWriter(enum AVCodecID id, enum AVMediaType codec_type) +{ + std::map::iterator it = writers.find(id); + if (it != writers.end()) + return it->second; + switch (codec_type) { + case AVMEDIA_TYPE_AUDIO: + if (id == AV_CODEC_ID_INJECTPCM) // should not happen + break; + return GetWriter(AV_CODEC_ID_INJECTPCM, codec_type); + case AVMEDIA_TYPE_VIDEO: + if (id == AV_CODEC_ID_MPEG2TS) // should not happen + break; + return GetWriter(AV_CODEC_ID_MPEG2TS, codec_type); + default: + break; + } + return &writer; +} + +video_encoding_t Writer::GetVideoEncoding(enum AVCodecID id) +{ + std::map::iterator it = vencoding.find(id); + if (it != vencoding.end()) + return it->second; + return VIDEO_ENCODING_AUTO; +} + +audio_encoding_t Writer::GetAudioEncoding(enum AVCodecID id) +{ + std::map::iterator it = aencoding.find(id); + if (it != aencoding.end()) + return it->second; + return AUDIO_ENCODING_LPCMA; +} diff --git a/libspark/Makefile.am b/libspark/Makefile.am new file mode 100644 index 0000000..768de2f --- /dev/null +++ b/libspark/Makefile.am @@ -0,0 +1,27 @@ +noinst_LTLIBRARIES = libspark.la + +AM_CPPFLAGS = -D__STDC_FORMAT_MACROS -D__STDC_CONSTANT_MACROS +AM_CPPFLAGS += \ + -I$(top_srcdir)/common \ + -I$(top_srcdir)/libeplayer3/include + +AM_CXXFLAGS = -fno-rtti -fno-exceptions -fno-strict-aliasing + +AM_LDFLAGS = -lpthread -lasound -lass -lrt\ + @AVFORMAT_LIBS@ \ + @AVUTIL_LIBS@ \ + @AVCODEC_LIBS@ \ + @SWRESAMPLE_LIBS@ + +libspark_la_SOURCES = \ + hardware_caps.c \ + dmx.cpp \ + video.cpp \ + audio.cpp \ + audio_mixer.cpp \ + init.cpp \ + playback_libeplayer3.cpp \ + pwrmngr.cpp \ + record.cpp + +#AM_CPPFLAGS = -DF_INTERRUPTS=20000 -DIRMP_EMBED -DLIRC_IRMP diff --git a/libspark/README.libtriple b/libspark/README.libtriple new file mode 100644 index 0000000..89f65b7 --- /dev/null +++ b/libspark/README.libtriple @@ -0,0 +1,72 @@ +libtriple reimplements the interfaces of the libcoolstrem library for +the Tripledragon receiver. + +There are a few debugging or configuration helpers which affect the +way libtriple does some things. They are all configured by exporting +environment variables, which are described here: + +TRIPLE_NOSCART=1 - makes neutrino *not* do any voltage switching on + SCART pin 8, probably not useful for anyone but me + +TRIPLE_DEBUG=... - controls various debugging levels in libtriple + valid values for the different component: + audio 0x01 + video 0x02 + demux 0x04 + play 0x08 + power 0x10 + init 0x20 + ca 0x40 + record 0x80 + all 0xff + multiple levels are added / ORed together, so if you want to + debug record and playback code, do "export TRIPLE_DEBUG=0x88" + for audio & video use TRIPLE_DEBUG=0x3 + +DSP_DEVICE +MIX_DEVICE - alternative audio devices for the audioplayer and internet + radio. Those are used to output music to e.g. USB audio devices. + Here is what you need to do: + * look in /dev/sound which devices are already there. Probably + /dev/sound/dsp and /dev/sound/mixer, created by the tdoss driver + * make sure that the USB HC driver is loaded: + modprobe ohci-hcd + * load the USB audio driver: + modprobe audio + * plug in your USB audio device, check with "dmesg" that it is + recognised by the kernel + * look in /dev/sound which new devices are there. Probably it's + /dev/sound/dsp1 and /dev/sound/mixer1. If there are more - well + it's time to experiment ;) + * export DSP_DEVICE=/dev/sound/dsp1 and MIX_DEVICE=/dev/sound/mixer1 + (of course the devices you found in the last step) + * from the same shell you exported the variables, start neutrino + (make sure that an already running neutrino is stopped before you + do that) + * start the audioplayer, play a track. Look for log lines like + [LT:106b5788:audio ] PrepareClipPlay: dsp_dev /dev/sound/dsp1 mix_dev /dev/sound/mixer1 + * if it works - fine :-) + * if it does not work, look for: + PrepareClipPlay: DSP_DEVICE is set (/dev/sound/dsp1) but cannot be opened, fall back to /dev/sound/dsp + PrepareClipPlay: dsp_dev /dev/sound/dsp mix_dev /dev/sound/mixer1 + PrepareClipPlay: open mixer /dev/sound/mixer1 failed (No such file or directory) + * this basically means that the device is not there. Different errors + will get different messages - I cannot trigger those now, so you'll + need to find them out by yourself ;) + * another possible messag you may get is: + PrepareClipPlay: more than one mixer control: devmask 00000021 stereo 00000021 + This means that your device has more than one mixer. The set bit + numbers in the devmask are the different mixers, in this case + it would be number 0 and 5. To select one of those, export + MIX_NUMBER=5 or MIX_NUMBER=0 (this code is untested, there may + be bugs) + + So now I found out what devices to use, but how do I make that permanent? + That's easy: + * create or extend /etc/rcS.local with + modprobe ohci-hcd + modprobe audio + * create or extend /etc/profile.local with + export DSP_DEVICE=/dev/sound/dsp1 + export MIX_DEVICE=/dev/sound/mixer1 + * reboot. Enjoy. diff --git a/libspark/audio.cpp b/libspark/audio.cpp new file mode 100644 index 0000000..c9aefb8 --- /dev/null +++ b/libspark/audio.cpp @@ -0,0 +1,416 @@ +#include +#include +#include +#include +#include +#include + +#include +#include "audio_lib.h" +#include "lt_debug.h" + +#define AUDIO_DEVICE "/dev/dvb/adapter0/audio0" +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_AUDIO, this, args) +#define lt_info(args...) _lt_info(TRIPLE_DEBUG_AUDIO, this, args) + +#include + +cAudio * audioDecoder = NULL; + +static int proc_put(const char *path, const char *value, const int len) +{ + int ret, ret2; + int pfd = open(path, O_WRONLY); + if (pfd < 0) + return pfd; + ret = write(pfd, value, len); + ret2 = close(pfd); + if (ret2 < 0) + return ret2; + return ret; +} + +cAudio::cAudio(void *, void *, void *) +{ + fd = -1; + clipfd = -1; + mixer_fd = -1; + openDevice(); + Muted = false; +} + +cAudio::~cAudio(void) +{ + closeDevice(); +} + +void cAudio::openDevice(void) +{ + if (fd < 0) + { + if ((fd = open(AUDIO_DEVICE, O_RDWR)) < 0) + lt_info("openDevice: open failed (%m)\n"); + fcntl(fd, F_SETFD, FD_CLOEXEC); + do_mute(true, false); + } + else + lt_info("openDevice: already open (fd = %d)\n", fd); +} + +void cAudio::closeDevice(void) +{ + if (fd > -1) { + close(fd); + fd = -1; + } + if (clipfd > -1) { + close(clipfd); + clipfd = -1; + } + if (mixer_fd > -1) { + close(mixer_fd); + mixer_fd = -1; + } +} + +int cAudio::do_mute(bool enable, bool remember) +{ + lt_debug("%s(%d, %d)\n", __FUNCTION__, enable, remember); + char str[4]; + + if (remember) + Muted = enable; + + sprintf(str, "%d", Muted); + proc_put("/proc/stb/audio/j1_mute", str, strlen(str)); + + if (!enable) + { + int f = open("/proc/stb/avs/0/volume", O_RDWR); + read(f, str, 4); + close(f); + str[3] = '\0'; + proc_put("/proc/stb/avs/0/volume", str, strlen(str)); + } + return 0; +} + +int map_volume(const int volume) +{ + unsigned char vol = volume; + if (vol > 100) + vol = 100; + + vol = 63 - vol * 63 / 100; + return vol; +} + + +int cAudio::setVolume(unsigned int left, unsigned int right) +{ + lt_debug("%s(%d, %d)\n", __func__, left, right); + + volume = (left + right) / 2; + int v = map_volume(volume); +#if 0 + if (clipfd != -1 && mixer_fd != -1) { + int tmp = 0; + /* not sure if left / right is correct here, but it is always the same anyways ;-) */ + if (! Muted) + tmp = left << 8 | right; + int ret = ioctl(mixer_fd, MIXER_WRITE(mixer_num), &tmp); + if (ret == -1) + lt_info("%s: MIXER_WRITE(%d),%04x: %m\n", __func__, mixer_num, tmp); + return ret; + } +#endif + + char str[4]; + sprintf(str, "%d", v); + + proc_put("/proc/stb/avs/0/volume", str, strlen(str)); + return 0; +} + +int cAudio::Start(void) +{ + int ret; + ret = ioctl(fd, AUDIO_PLAY); + return ret; +} + +int cAudio::Stop(void) +{ + return ioctl(fd, AUDIO_STOP); +} + +bool cAudio::Pause(bool Pcm) +{ + ioctl(fd, Pcm ? AUDIO_PAUSE : AUDIO_CONTINUE, 1); + return true; +} + +void cAudio::SetSyncMode(AVSYNC_TYPE Mode) +{ + lt_debug("%s %d\n", __func__, Mode); + ioctl(fd, AUDIO_SET_AV_SYNC, Mode); +} + +// E2 streamtype values. These correspond to +// player2/linux/drivers/media/dvb/stm/dvb/dvb_audio.c:AudioIoctlSetBypassMode +#define AUDIO_STREAMTYPE_AC3 0 +#define AUDIO_STREAMTYPE_MPEG 1 +#define AUDIO_STREAMTYPE_DTS 2 +#define AUDIO_STREAMTYPE_AAC 8 +#define AUDIO_STREAMTYPE_AACHE 9 + +void cAudio::SetStreamType(AUDIO_FORMAT type) +{ + int bypass = AUDIO_STREAMTYPE_MPEG; + lt_debug("%s %d\n", __FUNCTION__, type); + StreamType = type; + + switch (type) + { + case AUDIO_FMT_DD_PLUS: + case AUDIO_FMT_DOLBY_DIGITAL: + bypass = AUDIO_STREAMTYPE_AC3; + break; + case AUDIO_FMT_AAC: + bypass = AUDIO_STREAMTYPE_AAC; + break; + case AUDIO_FMT_AAC_PLUS: + bypass = AUDIO_STREAMTYPE_AACHE; + break; + case AUDIO_FMT_DTS: + bypass = AUDIO_STREAMTYPE_DTS; + break; + default: + break; + } + + // Normaly the encoding should be set using AUDIO_SET_ENCODING + // But as we implemented the behavior to bypass (cause of e2) this is correct here + if (ioctl(fd, AUDIO_SET_BYPASS_MODE, bypass) < 0) + lt_info("%s: AUDIO_SET_BYPASS_MODE failed (%m)\n", __func__); +} + +int cAudio::setChannel(int channel) +{ + lt_debug("%s %d\n", __FUNCTION__, channel); + return 0; +} + +int cAudio::PrepareClipPlay(int ch, int srate, int bits, int little_endian) +{ + int fmt; + unsigned int devmask, stereo, usable; + const char *dsp_dev = getenv("DSP_DEVICE"); + const char *mix_dev = getenv("MIX_DEVICE"); + lt_debug("%s ch %d srate %d bits %d le %d\n", __FUNCTION__, ch, srate, bits, little_endian); + if (clipfd > -1) { + lt_info("%s: clipfd already opened (%d)\n", __FUNCTION__, clipfd); + return -1; + } + mixer_num = -1; + /* a different DSP device can be given with DSP_DEVICE and MIX_DEVICE + * if this device cannot be opened, we fall back to the internal OSS device + * Example: + * modprobe snd-usb-audio + * export DSP_DEVICE=/dev/sound/dsp2 + * export MIX_DEVICE=/dev/sound/mixer2 + * neutrino + */ + if ((!dsp_dev) || (access(dsp_dev, W_OK))) { + if (dsp_dev) + lt_info("%s: DSP_DEVICE is set (%s) but cannot be opened," + " fall back to /dev/dsp1\n", __func__, dsp_dev); + dsp_dev = "/dev/dsp1"; + } + lt_info("%s: dsp_dev %s mix_dev %s\n", __func__, dsp_dev, mix_dev); /* NULL mix_dev is ok */ + /* the tdoss dsp driver seems to work only on the second open(). really. */ + clipfd = open(dsp_dev, O_WRONLY); + if (clipfd < 0) { + lt_info("%s open %s: %m\n", dsp_dev, __FUNCTION__); + return -1; + } + fcntl(clipfd, F_SETFD, FD_CLOEXEC); + /* no idea if we ever get little_endian == 0 */ + if (little_endian) + fmt = AFMT_S16_BE; + else + fmt = AFMT_S16_LE; + if (ioctl(clipfd, SNDCTL_DSP_SETFMT, &fmt)) + perror("SNDCTL_DSP_SETFMT"); + if (ioctl(clipfd, SNDCTL_DSP_CHANNELS, &ch)) + perror("SNDCTL_DSP_CHANNELS"); + if (ioctl(clipfd, SNDCTL_DSP_SPEED, &srate)) + perror("SNDCTL_DSP_SPEED"); + if (ioctl(clipfd, SNDCTL_DSP_RESET)) + perror("SNDCTL_DSP_RESET"); + + if (!mix_dev) + return 0; + + mixer_fd = open(mix_dev, O_RDWR); + if (mixer_fd < 0) { + lt_info("%s: open mixer %s failed (%m)\n", __func__, mix_dev); + /* not a real error */ + return 0; + } + if (ioctl(mixer_fd, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) { + lt_info("%s: SOUND_MIXER_READ_DEVMASK %m\n", __func__); + devmask = 0; + } + if (ioctl(mixer_fd, SOUND_MIXER_READ_STEREODEVS, &stereo) == -1) { + lt_info("%s: SOUND_MIXER_READ_STEREODEVS %m\n", __func__); + stereo = 0; + } + usable = devmask & stereo; + if (usable == 0) { + lt_info("%s: devmask: %08x stereo: %08x, no usable dev :-(\n", + __func__, devmask, stereo); + close(mixer_fd); + mixer_fd = -1; + return 0; /* TODO: should we treat this as error? */ + } + /* __builtin_popcount needs GCC, it counts the set bits... */ + if (__builtin_popcount (usable) != 1) { + /* TODO: this code is not yet tested as I have only single-mixer devices... */ + lt_info("%s: more than one mixer control: devmask %08x stereo %08x\n" + "%s: querying MIX_NUMBER environment variable...\n", + __func__, devmask, stereo, __func__); + const char *tmp = getenv("MIX_NUMBER"); + if (tmp) + mixer_num = atoi(tmp); + lt_info("%s: mixer_num is %d -> device %08x\n", + __func__, mixer_num, (mixer_num >= 0) ? (1 << mixer_num) : 0); + /* no error checking, you'd better know what you are doing... */ + } else { + mixer_num = 0; + while (!(usable & 0x01)) { + mixer_num++; + usable >>= 1; + } + } + setVolume(volume, volume); + + return 0; +} + +int cAudio::WriteClip(unsigned char *buffer, int size) +{ + int ret; + // lt_debug("cAudio::%s\n", __FUNCTION__); + if (clipfd < 0) { + lt_info("%s: clipfd not yet opened\n", __FUNCTION__); + return -1; + } + ret = write(clipfd, buffer, size); + if (ret < 0) + lt_info("%s: write error (%m)\n", __FUNCTION__); + return ret; +}; + +int cAudio::StopClip() +{ + lt_debug("%s\n", __FUNCTION__); + if (clipfd < 0) { + lt_info("%s: clipfd not yet opened\n", __FUNCTION__); + return -1; + } + close(clipfd); + clipfd = -1; + if (mixer_fd >= -1) { + close(mixer_fd); + mixer_fd = -1; + } + setVolume(volume, volume); + return 0; +}; + +void cAudio::getAudioInfo(int &type, int &layer, int &freq, int &bitrate, int &mode) +{ + lt_debug("%s\n", __FUNCTION__); + type = 0; + layer = 0; + freq = 0; + bitrate = 0; + mode = 0; +#if 0 + unsigned int atype; + static const int freq_mpg[] = {44100, 48000, 32000, 0}; + static const int freq_ac3[] = {48000, 44100, 32000, 0}; + scratchl2 i; + if (ioctl(fd, MPEG_AUD_GET_DECTYP, &atype) < 0) + perror("cAudio::getAudioInfo MPEG_AUD_GET_DECTYP"); + if (ioctl(fd, MPEG_AUD_GET_STATUS, &i) < 0) + perror("cAudio::getAudioInfo MPEG_AUD_GET_STATUS"); + + type = atype; +#if 0 +/* this does not work, some of the values are negative?? */ + AMPEGStatus A; + memcpy(&A, &i.word00, sizeof(i.word00)); + layer = A.audio_mpeg_layer; + mode = A.audio_mpeg_mode; + bitrate = A.audio_mpeg_bitrate; + switch(A.audio_mpeg_frequency) +#endif + /* layer and bitrate are not used anyway... */ + layer = 0; //(i.word00 >> 17) & 3; + bitrate = 0; //(i.word00 >> 12) & 3; + switch (type) + { + case 0: /* MPEG */ + mode = (i.word00 >> 6) & 3; + freq = freq_mpg[(i.word00 >> 10) & 3]; + break; + case 1: /* AC3 */ + mode = (i.word00 >> 28) & 7; + freq = freq_ac3[(i.word00 >> 16) & 3]; + break; + default: + mode = 0; + freq = 0; + } + //fprintf(stderr, "type: %d layer: %d freq: %d bitrate: %d mode: %d\n", type, layer, freq, bitrate, mode); +#endif +}; + +void cAudio::SetSRS(int /*iq_enable*/, int /*nmgr_enable*/, int /*iq_mode*/, int /*iq_level*/) +{ + lt_debug("%s\n", __FUNCTION__); +}; + +void cAudio::SetHdmiDD(bool enable) +{ + const char *opt[] = { "pcm", "spdif" }; + lt_debug("%s %d\n", __func__, enable); + proc_put("/proc/stb/hdmi/audio_source", opt[enable], strlen(opt[enable])); +} + +void cAudio::SetSpdifDD(bool enable) +{ + lt_debug("%s %d\n", __func__, enable); + setBypassMode(!enable); +} + +void cAudio::ScheduleMute(bool On) +{ + lt_debug("%s %d\n", __FUNCTION__, On); +} + +void cAudio::EnableAnalogOut(bool enable) +{ + lt_debug("%s %d\n", __FUNCTION__, enable); +} + +#define AUDIO_BYPASS_ON 0 +#define AUDIO_BYPASS_OFF 1 +void cAudio::setBypassMode(bool disable) +{ + const char *opt[] = { "passthrough", "downmix" }; + lt_debug("%s %d\n", __func__, disable); + proc_put("/proc/stb/audio/ac3", opt[disable], strlen(opt[disable])); +} diff --git a/libspark/audio_lib.h b/libspark/audio_lib.h new file mode 100644 index 0000000..7e5cb34 --- /dev/null +++ b/libspark/audio_lib.h @@ -0,0 +1,98 @@ +/* public header file */ + +#ifndef _AUDIO_TD_H_ +#define _AUDIO_TD_H_ +#include "../common/cs_types.h" + +typedef enum +{ + AUDIO_SYNC_WITH_PTS, + AUDIO_NO_SYNC, + AUDIO_SYNC_AUDIO_MASTER +} AUDIO_SYNC_MODE; + +typedef enum { + HDMI_ENCODED_OFF, + HDMI_ENCODED_AUTO, + HDMI_ENCODED_FORCED +} HDMI_ENCODED_MODE; + +typedef enum +{ + AUDIO_FMT_AUTO = 0, + AUDIO_FMT_MPEG, + AUDIO_FMT_MP3, + AUDIO_FMT_DOLBY_DIGITAL, + AUDIO_FMT_BASIC = AUDIO_FMT_DOLBY_DIGITAL, + AUDIO_FMT_AAC, + AUDIO_FMT_AAC_PLUS, + AUDIO_FMT_DD_PLUS, + AUDIO_FMT_DTS, + AUDIO_FMT_AVS, + AUDIO_FMT_MLP, + AUDIO_FMT_WMA, + AUDIO_FMT_MPG1, // TD only. For Movieplayer / cPlayback + AUDIO_FMT_ADVANCED = AUDIO_FMT_MLP +} AUDIO_FORMAT; + +class cAudio +{ + friend class cPlayback; + private: + int fd; + bool Muted; + + int clipfd; /* for pcm playback */ + int mixer_fd; /* if we are using the OSS mixer */ + int mixer_num; /* oss mixer to use, if any */ + + AUDIO_FORMAT StreamType; + AUDIO_SYNC_MODE SyncMode; + bool started; + + int volume; + + void openDevice(void); + void closeDevice(void); + + int do_mute(bool enable, bool remember); + void setBypassMode(bool disable); + public: + /* construct & destruct */ + cAudio(void *, void *, void *); + ~cAudio(void); + + void *GetHandle() { return NULL; }; + /* shut up */ + int mute(bool remember = true) { return do_mute(true, remember); }; + int unmute(bool remember = true) { return do_mute(false, remember); }; + + /* volume, min = 0, max = 255 */ + int setVolume(unsigned int left, unsigned int right); + int getVolume(void) { return volume;} + bool getMuteStatus(void) { return Muted; }; + + /* start and stop audio */ + int Start(void); + int Stop(void); + bool Pause(bool Pcm = true); + void SetStreamType(AUDIO_FORMAT type); + AUDIO_FORMAT GetStreamType(void) { return StreamType; } + void SetSyncMode(AVSYNC_TYPE Mode); + + /* select channels */ + int setChannel(int channel); + int PrepareClipPlay(int uNoOfChannels, int uSampleRate, int uBitsPerSample, int bLittleEndian); + int WriteClip(unsigned char * buffer, int size); + int StopClip(); + void getAudioInfo(int &type, int &layer, int& freq, int &bitrate, int &mode); + void SetSRS(int iq_enable, int nmgr_enable, int iq_mode, int iq_level); + bool IsHdmiDDSupported() { return true; }; + void SetHdmiDD(bool enable); + void SetSpdifDD(bool enable); + void ScheduleMute(bool On); + void EnableAnalogOut(bool enable); +}; + +#endif + diff --git a/libspark/audio_mixer.cpp b/libspark/audio_mixer.cpp new file mode 100644 index 0000000..f361209 --- /dev/null +++ b/libspark/audio_mixer.cpp @@ -0,0 +1,68 @@ +/* + * audio_mixer.cpp + * + * (C) 2012 martii + * + * 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 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +mixerVolume::mixerVolume(const char *name, const char *card, long volume) { + snd_mixer_selem_id_t *sid = NULL; + elem = NULL; + handle = NULL; + min = 0; + max = 100; + char cardId[10]; + + if (!name || !card) + return; + + int cx = snd_card_get_index(card); + if (cx < 0 || cx > 31) + return; + snprintf(cardId, sizeof(cardId), "hw:%i", cx); + + if (0 > snd_mixer_open(&handle, 0)) + return; + if (0 > snd_mixer_attach(handle, cardId)) + return; + if (0 > snd_mixer_selem_register(handle, NULL, NULL)) + return; + if (0 > snd_mixer_load(handle)) + return; + snd_mixer_selem_id_alloca(&sid); + if (!sid) + return; + snd_mixer_selem_id_set_index(sid, 0); + snd_mixer_selem_id_set_name(sid, name); + elem = snd_mixer_find_selem(handle, sid); + if (elem) { + snd_mixer_selem_get_playback_volume_range(elem, &min, &max); + setVolume(volume); + } +} +mixerVolume::~mixerVolume() +{ + if (handle) + snd_mixer_close(handle); +} + +bool mixerVolume::setVolume(long volume) { + return elem + && (volume > -1) + && (volume < 101) + && !snd_mixer_selem_set_playback_volume_all(elem, min + volume * (max - min)/100); +} diff --git a/libspark/audio_mixer.h b/libspark/audio_mixer.h new file mode 100644 index 0000000..2a6f6fc --- /dev/null +++ b/libspark/audio_mixer.h @@ -0,0 +1,36 @@ +/* + * audio_mixer.h + * + * (C) 2012 martii + * + * 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 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __AUDIO_MIXER_H__ +#define __AUDIO_MIXER_H__ +#include + +class mixerVolume +{ + private: + long min, max; + snd_mixer_t *handle; + snd_mixer_elem_t* elem; + public: + mixerVolume(const char *selem_name, const char *Card, long volume = -1); + ~mixerVolume(void); + bool setVolume(long volume); +}; +#endif + diff --git a/libspark/cs_api.h b/libspark/cs_api.h new file mode 100644 index 0000000..fb5d613 --- /dev/null +++ b/libspark/cs_api.h @@ -0,0 +1,66 @@ +/* compatibility header for tripledragon. I'm lazy, so I just left it + as "cs_api.h" so that I don't need too many ifdefs in the code */ + +#ifndef __CS_API_H_ +#define __CS_API_H_ + +#include "init_lib.h" +typedef void (*cs_messenger) (unsigned int msg, unsigned int data); + +#if 0 +enum CS_LOG_MODULE { + CS_LOG_CI = 0, + CS_LOG_HDMI_CEC, + CS_LOG_HDMI, + CS_LOG_VIDEO, + CS_LOG_VIDEO_DRM, + CS_LOG_AUDIO, + CS_LOG_DEMUX, + CS_LOG_DENC, + CS_LOG_PVR_RECORD, + CS_LOG_PVR_PLAY, + CS_LOG_POWER_CTRL, + CS_LOG_POWER_CLK, + CS_LOG_MEM, + CS_LOG_API, +}; +#endif + +inline void cs_api_init() +{ + init_td_api(); +}; + +inline void cs_api_exit() +{ + shutdown_td_api(); +}; + +#define cs_malloc_uncached malloc +#define cs_free_uncached free + +// Callback function helpers +static inline void cs_register_messenger(cs_messenger) { return; }; +static inline void cs_deregister_messenger(void) { return; }; +//cs_messenger cs_get_messenger(void); + +#if 0 +// Logging functions +void cs_log_enable(void); +void cs_log_disable(void); +void cs_log_message(const char *prefix, const char *fmt, ...); +void cs_log_module_enable(enum CS_LOG_MODULE module); +void cs_log_module_disable(enum CS_LOG_MODULE module); +void cs_log_module_message(enum CS_LOG_MODULE module, const char *fmt, ...); + +// TS Routing +unsigned int cs_get_ts_output(void); +int cs_set_ts_output(unsigned int port); + +// Serial nr and revision accessors +unsigned long long cs_get_serial(void); +#endif +/* compat... HD1 seems to be version 6. everything newer ist > 6... */ +static inline unsigned int cs_get_revision(void) { return 1; }; +extern int cnxt_debug; +#endif //__CS_API_H_ diff --git a/libspark/dmx.cpp b/libspark/dmx.cpp new file mode 100644 index 0000000..d3ba4e7 --- /dev/null +++ b/libspark/dmx.cpp @@ -0,0 +1,615 @@ +/* + * cDemux implementation for SH4 receivers (tested on fulan spark and + * fulan spark7162 hardware) + * + * derived from libtriple/dmx_td.cpp + * + * (C) 2010-2013 Stefan Seyfried + * + * 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 2 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 . + */ + +/* + * Theory of operation (or "why is this dmx_source thing so strange and + * what is the _open() good for?") + * + * the sh4 pti driver, driving the /dev/dvb/adapter0/dmxN devices, can + * apparently only map one input to on demux device at a time, so e.g. + * DMX_SOURCE_FRONT1 -> demux0 + * DMX_SOURCE_FRONT2 -> demux0 + * DMX_SOURCE_FRONT1 -> demux1 + * does not work. The driver makes sure that a one-to-one mapping of + * DMX_SOURCE_FRONTn to demuxM is maintained, and it does by e.g changing + * the default of + * FRONT0 -> demux0 + * FRONT1 -> demux1 + * FRONT2 -> demux2 + * to + * FRONT1 -> demux0 + * FRONT0 -> demux1 + * FRONT2 -> demux2 + * if you do a DMX_SET_SOURCE(FRONT1) ioctl on demux0. + * This means, it also changes demux1's source on the SET_SOURCE ioctl on + * demux0, potentially disturbing any operation on demux1 (e.g. recording). + * + * In order to avoid this, I do not change the source->demuxdev mapping + * but instead just always use the demux device that is attached to the + * correct source. + * + * The tricky part is, that the source might actually be changed after + * Open() has been called, so Open() gets a dummy placeholder that just + * sets some variables while the real device open is put into _open(). + * _open() gets called later, whenever the device is actually used or + * configured and -- if the source has changed -- closes the old and + * opens the correct new device node. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "dmx_lib.h" +#include "lt_debug.h" + +/* Ugh... see comment in destructor for details... */ +#include "video_lib.h" +extern cVideo *videoDecoder; + +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_DEMUX, this, args) +#define lt_info(args...) _lt_info(TRIPLE_DEBUG_DEMUX, this, args) +#define lt_info_c(args...) _lt_info(TRIPLE_DEBUG_DEMUX, NULL, args) + +#define dmx_err(_errfmt, _errstr, _revents) do { \ + uint16_t _pid = (uint16_t)-1; uint16_t _f = 0;\ + if (dmx_type == DMX_PSI_CHANNEL) { \ + _pid = s_flt.pid; _f = s_flt.filter.filter[0]; \ + } else { \ + _pid = p_flt.pid; \ + }; \ + lt_info("%s " _errfmt " fd:%d, ev:0x%x %s pid:0x%04hx flt:0x%02hx\n", \ + __func__, _errstr, fd, _revents, DMX_T[dmx_type], _pid, _f); \ +} while(0); + +cDemux *videoDemux = NULL; +cDemux *audioDemux = NULL; +//cDemux *pcrDemux = NULL; + +static const char *DMX_T[] = { + "DMX_INVALID", + "DMX_VIDEO", + "DMX_AUDIO", + "DMX_PES", + "DMX_PSI", + "DMX_PIP", + "DMX_TP", + "DMX_PCR" +}; + +/* this is the number of different cDemux() units, not the number of + * /dev/dvb/.../demuxX devices! */ +#define NUM_DEMUX 4 +/* the current source of each cDemux unit */ +static int dmx_source[NUM_DEMUX] = { 0, 0, 0, 0 }; + +/* map the device numbers. */ +#define NUM_DEMUXDEV 3 +static const char *devname[NUM_DEMUXDEV] = { + "/dev/dvb/adapter0/demux0", + "/dev/dvb/adapter0/demux1", + "/dev/dvb/adapter0/demux2" +}; +/* did we already DMX_SET_SOURCE on that demux device? */ +static bool init[NUM_DEMUXDEV] = { false, false, false }; + +/* uuuugly */ +static int dmx_tp_count = 0; +#define MAX_TS_COUNT 1 + +cDemux::cDemux(int n) +{ + if (n < 0 || n >= NUM_DEMUX) + { + lt_info("%s ERROR: n invalid (%d)\n", __FUNCTION__, n); + num = 0; + } + else + num = n; + fd = -1; + measure = false; + last_measure = 0; + last_data = 0; + last_source = -1; +} + +cDemux::~cDemux() +{ + lt_debug("%s #%d fd: %d\n", __FUNCTION__, num, fd); + Close(); + /* in zapit.cpp, videoDemux is deleted after videoDecoder + * in the video watchdog, we access videoDecoder + * the thread still runs after videoDecoder has been deleted + * => set videoDecoder to NULL here to make the check in the + * watchdog thread pick this up. + * This is ugly, but it saves me from changing neutrino + * + * if the delete order in neutrino will ever be changed, this + * will blow up badly :-( + */ + if (dmx_type == DMX_VIDEO_CHANNEL) + videoDecoder = NULL; +} + +bool cDemux::Open(DMX_CHANNEL_TYPE pes_type, void * /*hVideoBuffer*/, int uBufferSize) +{ + if (fd > -1) + lt_info("%s FD ALREADY OPENED? fd = %d\n", __FUNCTION__, fd); + + dmx_type = pes_type; + buffersize = uBufferSize; + + /* return code is unchecked anyway... */ + return true; +} + +bool cDemux::_open(void) +{ + int flags = O_RDWR|O_CLOEXEC; + int devnum = dmx_source[num]; + if (last_source == devnum) { + lt_debug("%s #%d: source (%d) did not change\n", __func__, num, last_source); + if (fd > -1) + return true; + } + if (fd > -1) { + /* we changed source -> close and reopen the fd */ + lt_debug("%s #%d: FD ALREADY OPENED fd = %d lastsource %d devnum %d\n", + __func__, num, fd, last_source, devnum); + close(fd); + } + + if (dmx_type != DMX_PSI_CHANNEL) + flags |= O_NONBLOCK; + + fd = open(devname[devnum], flags); + if (fd < 0) + { + lt_info("%s %s: %m\n", __FUNCTION__, devname[devnum]); + return false; + } + lt_debug("%s #%d pes_type: %s(%d), uBufferSize: %d fd: %d\n", __func__, + num, DMX_T[dmx_type], dmx_type, buffersize, fd); + + /* this would actually need locking, but the worst that weill happen is, that + * we'll DMX_SET_SOURCE twice per device, so don't bother... */ + if (!init[devnum]) + { + /* this should not change anything... */ + int n = DMX_SOURCE_FRONT0 + devnum; + lt_info("%s: setting %s to source %d\n", __func__, devname[devnum], n); + if (ioctl(fd, DMX_SET_SOURCE, &n) < 0) + lt_info("%s DMX_SET_SOURCE failed!\n", __func__); + else + init[devnum] = true; + } + if (buffersize == 0) + buffersize = 0xffff; // may or may not be reasonable --martii + if (buffersize > 0) + { + /* probably uBufferSize == 0 means "use default size". TODO: find a reasonable default */ + if (ioctl(fd, DMX_SET_BUFFER_SIZE, buffersize) < 0) + lt_info("%s DMX_SET_BUFFER_SIZE failed (%m)\n", __func__); + } + + last_source = devnum; + return true; +} + +void cDemux::Close(void) +{ + lt_debug("%s #%d, fd = %d\n", __FUNCTION__, num, fd); + if (fd < 0) + { + lt_info("%s #%d: not open!\n", __FUNCTION__, num); + return; + } + + pesfds.clear(); + ioctl(fd, DMX_STOP); + close(fd); + fd = -1; + if (measure) + return; + if (dmx_type == DMX_TP_CHANNEL) + { + dmx_tp_count--; + if (dmx_tp_count < 0) + { + lt_info("%s dmx_tp_count < 0!!\n", __func__); + dmx_tp_count = 0; + } + } +} + +bool cDemux::Start(bool) +{ + if (fd < 0) + { + lt_info("%s #%d: not open!\n", __FUNCTION__, num); + return false; + } + ioctl(fd, DMX_START); + return true; +} + +bool cDemux::Stop(void) +{ + if (fd < 0) + { + lt_info("%s #%d: not open!\n", __FUNCTION__, num); + return false; + } + ioctl(fd, DMX_STOP); + return true; +} + +int cDemux::Read(unsigned char *buff, int len, int timeout) +{ +#if 0 + if (len != 4095 && timeout != 10) + fprintf(stderr, "cDemux::%s #%d fd: %d type: %s len: %d timeout: %d\n", + __FUNCTION__, num, fd, DMX_T[dmx_type], len, timeout); +#endif + if (fd < 0) + { + lt_info("%s #%d: not open!\n", __func__, num); + return -1; + } + int rc; + int to = timeout; + struct pollfd ufds; + ufds.fd = fd; + ufds.events = POLLIN|POLLPRI|POLLERR; + ufds.revents = 0; + + /* hack: if the frontend loses and regains lock, the demuxer often will not + * return from read(), so as a "emergency exit" for e.g. NIT scan, set a (long) + * timeout here */ + if (dmx_type == DMX_PSI_CHANNEL && timeout <= 0) + to = 60 * 1000; + + if (to > 0) + { + retry: + rc = ::poll(&ufds, 1, to); + if (!rc) + { + if (timeout == 0) /* we took the emergency exit */ + { + dmx_err("timed out for timeout=0!, %s", "", 0); + return -1; /* this timeout is an error */ + } + return 0; // timeout + } + else if (rc < 0) + { + dmx_err("poll: %s,", strerror(errno), 0) + //lt_info("%s poll: %m\n", __FUNCTION__); + /* happens, when running under gdb... */ + if (errno == EINTR) + goto retry; + return -1; + } +#if 0 + if (ufds.revents & POLLERR) /* POLLERR means buffer error, i.e. buffer overflow */ + { + dmx_err("received %s,", "POLLERR", ufds.revents); + /* this seems to happen sometimes at recording start, without bad effects */ + return 0; + } +#endif + if (ufds.revents & POLLHUP) /* we get POLLHUP if e.g. a too big DMX_BUFFER_SIZE was set */ + { + dmx_err("received %s,", "POLLHUP", ufds.revents); + return -1; + } + if (!(ufds.revents & POLLIN)) /* we requested POLLIN but did not get it? */ + { + dmx_err("received %s, please report!", "POLLIN", ufds.revents); + return 0; + } + } + + rc = ::read(fd, buff, len); + //fprintf(stderr, "fd %d ret: %d\n", fd, rc); + if (rc < 0) + dmx_err("read: %s", strerror(errno), 0); + + return rc; +} + +bool cDemux::sectionFilter(unsigned short pid, const unsigned char * const filter, + const unsigned char * const mask, int len, int timeout, + const unsigned char * const negmask) +{ + memset(&s_flt, 0, sizeof(s_flt)); + + _open(); + + if (len > DMX_FILTER_SIZE) + { + lt_info("%s #%d: len too long: %d, DMX_FILTER_SIZE %d\n", __func__, num, len, DMX_FILTER_SIZE); + len = DMX_FILTER_SIZE; + } + s_flt.pid = pid; + s_flt.timeout = timeout; + memcpy(s_flt.filter.filter, filter, len); + memcpy(s_flt.filter.mask, mask, len); + if (negmask != NULL) + memcpy(s_flt.filter.mode, negmask, len); + + s_flt.flags = DMX_IMMEDIATE_START|DMX_CHECK_CRC; + + int to = 0; + switch (filter[0]) { + case 0x00: /* program_association_section */ + to = 2000; + break; + case 0x01: /* conditional_access_section */ + to = 6000; + break; + case 0x02: /* program_map_section */ + to = 1500; + break; + case 0x03: /* transport_stream_description_section */ + to = 10000; + break; + /* 0x04 - 0x3F: reserved */ + case 0x40: /* network_information_section - actual_network */ + to = 10000; + break; + case 0x41: /* network_information_section - other_network */ + to = 15000; + break; + case 0x42: /* service_description_section - actual_transport_stream */ + to = 10000; + break; + /* 0x43 - 0x45: reserved for future use */ + case 0x46: /* service_description_section - other_transport_stream */ + to = 10000; + break; + /* 0x47 - 0x49: reserved for future use */ + case 0x4A: /* bouquet_association_section */ + to = 11000; + break; + /* 0x4B - 0x4D: reserved for future use */ + case 0x4E: /* event_information_section - actual_transport_stream, present/following */ + to = 2000; + break; + case 0x4F: /* event_information_section - other_transport_stream, present/following */ + to = 10000; + break; + /* 0x50 - 0x5F: event_information_section - actual_transport_stream, schedule */ + /* 0x60 - 0x6F: event_information_section - other_transport_stream, schedule */ + case 0x70: /* time_date_section */ + s_flt.flags &= ~DMX_CHECK_CRC; /* section has no CRC */ + s_flt.flags |= DMX_ONESHOT; + //s_flt.pid = 0x0014; + to = 30000; + break; + case 0x71: /* running_status_section */ + s_flt.flags &= ~DMX_CHECK_CRC; /* section has no CRC */ + to = 0; + break; + case 0x72: /* stuffing_section */ + s_flt.flags &= ~DMX_CHECK_CRC; /* section has no CRC */ + to = 0; + break; + case 0x73: /* time_offset_section */ + s_flt.flags |= DMX_ONESHOT; + //s_flt.pid = 0x0014; + to = 30000; + break; + /* 0x74 - 0x7D: reserved for future use */ + case 0x7E: /* discontinuity_information_section */ + s_flt.flags &= ~DMX_CHECK_CRC; /* section has no CRC */ + to = 0; + break; + case 0x7F: /* selection_information_section */ + to = 0; + break; + /* 0x80 - 0x8F: ca_message_section */ + /* 0x90 - 0xFE: user defined */ + /* 0xFF: reserved */ + default: + break; +// return -1; + } + /* the negmask == NULL is a hack: the users of negmask are PMT-update + * and sectionsd EIT-Version change. And they really want no timeout + * if timeout == 0 instead of "default timeout" */ + if (timeout == 0 && negmask == NULL) + s_flt.timeout = to; + + lt_debug("%s #%d pid:0x%04hx fd:%d type:%s len:%d to:%d flags:%x flt[0]:%02x\n", __func__, num, + pid, fd, DMX_T[dmx_type], len, s_flt.timeout,s_flt.flags, s_flt.filter.filter[0]); +#if 0 + fprintf(stderr,"filt: ");for(int i=0;i= 0x0002 && pid <= 0x000f) || pid >= 0x1fff) + return false; + + lt_debug("%s #%d pid: 0x%04hx fd: %d type: %s\n", __FUNCTION__, num, pid, fd, DMX_T[dmx_type]); + + _open(); + + memset(&p_flt, 0, sizeof(p_flt)); + p_flt.pid = pid; + p_flt.output = DMX_OUT_DECODER; + p_flt.input = DMX_IN_FRONTEND; + p_flt.flags = DMX_IMMEDIATE_START; + + switch (dmx_type) { + case DMX_PCR_ONLY_CHANNEL: + p_flt.pes_type = DMX_PES_PCR; + break; + case DMX_AUDIO_CHANNEL: + p_flt.pes_type = DMX_PES_AUDIO; + break; + case DMX_VIDEO_CHANNEL: + p_flt.pes_type = DMX_PES_VIDEO; + break; + case DMX_PIP_CHANNEL: /* PIP is a special version of DMX_VIDEO_CHANNEL */ + p_flt.pes_type = DMX_PES_VIDEO1; + break; + case DMX_PES_CHANNEL: + p_flt.pes_type = DMX_PES_OTHER; + p_flt.output = DMX_OUT_TAP; + break; + case DMX_TP_CHANNEL: + p_flt.pes_type = DMX_PES_OTHER; + p_flt.output = DMX_OUT_TSDEMUX_TAP; + break; + default: + lt_info("%s #%d invalid dmx_type %d!\n", __func__, num, dmx_type); + return false; + } + return (ioctl(fd, DMX_SET_PES_FILTER, &p_flt) >= 0); +} + +void cDemux::SetSyncMode(AVSYNC_TYPE /*mode*/) +{ + lt_debug("%s #%d\n", __FUNCTION__, num); +} + +void *cDemux::getBuffer() +{ + lt_debug("%s #%d\n", __FUNCTION__, num); + return NULL; +} + +void *cDemux::getChannel() +{ + lt_debug("%s #%d\n", __FUNCTION__, num); + return NULL; +} + +bool cDemux::addPid(unsigned short Pid) +{ + lt_debug("%s: pid 0x%04hx\n", __func__, Pid); + pes_pids pfd; + int ret; + if (dmx_type != DMX_TP_CHANNEL) + { + lt_info("%s pes_type %s not implemented yet! pid=%hx\n", __FUNCTION__, DMX_T[dmx_type], Pid); + return false; + } + _open(); + if (fd == -1) + lt_info("%s bucketfd not yet opened? pid=%hx\n", __FUNCTION__, Pid); + pfd.fd = fd; /* dummy */ + pfd.pid = Pid; + pesfds.push_back(pfd); + ret = (ioctl(fd, DMX_ADD_PID, &Pid)); + if (ret < 0) + lt_info("%s: DMX_ADD_PID (%m) pid=%hx\n", __func__, Pid); + return (ret != -1); +} + +void cDemux::removePid(unsigned short Pid) +{ + if (dmx_type != DMX_TP_CHANNEL) + { + lt_info("%s pes_type %s not implemented yet! pid=%hx\n", __FUNCTION__, DMX_T[dmx_type], Pid); + return; + } + for (std::vector::iterator i = pesfds.begin(); i != pesfds.end(); ++i) + { + if ((*i).pid == Pid) { + lt_debug("removePid: removing demux fd %d pid 0x%04x\n", fd, Pid); + if (ioctl(fd, DMX_REMOVE_PID, Pid) < 0) + lt_info("%s: (DMX_REMOVE_PID, 0x%04hx): %m\n", __func__, Pid); + pesfds.erase(i); + return; /* TODO: what if the same PID is there multiple times */ + } + } + lt_info("%s pid 0x%04x not found\n", __FUNCTION__, Pid); +} + +void cDemux::getSTC(int64_t * STC) +{ + /* apparently I can only get the PTS of the video decoder, + * but that's good enough for dvbsub */ + lt_debug("%s #%d\n", __func__, num); + int64_t pts = 0; + if (videoDecoder) + pts = videoDecoder->GetPTS(); + *STC = pts; +} + +int cDemux::getUnit(void) +{ + lt_debug("%s #%d\n", __FUNCTION__, num); + /* just guessed that this is the right thing to do. + right now this is only used by the CA code which is stubbed out + anyway */ + return num; +} + +bool cDemux::SetSource(int unit, int source) +{ + if (unit >= NUM_DEMUX || unit < 0) { + lt_info_c("%s: unit (%d) out of range, NUM_DEMUX %d\n", __func__, unit, NUM_DEMUX); + return false; + } + lt_info_c("%s(%d, %d) => %d to %d\n", __func__, unit, source, dmx_source[unit], source); + if (source < 0 || source >= NUM_DEMUXDEV) + lt_info_c("%s(%d, %d) ERROR: source %d out of range!\n", __func__, unit, source, source); + else + dmx_source[unit] = source; + return true; +} + +int cDemux::GetSource(int unit) +{ + if (unit >= NUM_DEMUX || unit < 0) { + lt_info_c("%s: unit (%d) out of range, NUM_DEMUX %d\n", __func__, unit, NUM_DEMUX); + return -1; + } + lt_info_c("%s(%d) => %d\n", __func__, unit, dmx_source[unit]); + return dmx_source[unit]; +} diff --git a/libspark/dmx_cs.h b/libspark/dmx_cs.h new file mode 100644 index 0000000..175d8cb --- /dev/null +++ b/libspark/dmx_cs.h @@ -0,0 +1 @@ +#include "dmx_lib.h" diff --git a/libspark/dmx_lib.h b/libspark/dmx_lib.h new file mode 100644 index 0000000..af87a02 --- /dev/null +++ b/libspark/dmx_lib.h @@ -0,0 +1,72 @@ +#ifndef __DEMUX_TD_H +#define __DEMUX_TD_H + +#include +#include +#include +#include +#include +#include "../common/cs_types.h" + +#define MAX_DMX_UNITS 4 + +typedef enum +{ + DMX_INVALID = 0, + DMX_VIDEO_CHANNEL = 1, + DMX_AUDIO_CHANNEL, + DMX_PES_CHANNEL, + DMX_PSI_CHANNEL, + DMX_PIP_CHANNEL, + DMX_TP_CHANNEL, + DMX_PCR_ONLY_CHANNEL +} DMX_CHANNEL_TYPE; + +typedef struct +{ + int fd; + unsigned short pid; +} pes_pids; + +class cDemux +{ + private: + int num; + int fd; + int buffersize; + bool measure; + uint64_t last_measure, last_data; + DMX_CHANNEL_TYPE dmx_type; + std::vector pesfds; + struct dmx_sct_filter_params s_flt; + struct dmx_pes_filter_params p_flt; + int last_source; + bool _open(void); + public: + + bool Open(DMX_CHANNEL_TYPE pes_type, void * unused = NULL, int bufsize = 0); + void Close(void); + bool Start(bool record = false); + bool Stop(void); + int Read(unsigned char *buff, int len, int Timeout = 0); + bool sectionFilter(unsigned short pid, const unsigned char * const filter, const unsigned char * const mask, int len, int Timeout = 0, const unsigned char * const negmask = NULL); + bool pesFilter(const unsigned short pid); + void SetSyncMode(AVSYNC_TYPE mode); + void * getBuffer(); + void * getChannel(); + DMX_CHANNEL_TYPE getChannelType(void) { return dmx_type; }; + bool addPid(unsigned short pid); + void getSTC(int64_t * STC); + int getUnit(void); + static bool SetSource(int unit, int source); + static int GetSource(int unit); + // TD only functions + int getFD(void) { return fd; }; /* needed by cPlayback class */ + void removePid(unsigned short Pid); /* needed by cRecord class */ + std::vector getPesPids(void) { return pesfds; }; + // + cDemux(int num = 0); + ~cDemux(); +}; + +#endif //__DEMUX_H diff --git a/libspark/hardware_caps.c b/libspark/hardware_caps.c new file mode 100644 index 0000000..333c9af --- /dev/null +++ b/libspark/hardware_caps.c @@ -0,0 +1,163 @@ +/* + * determine the capabilities of the hardware. + * part of libstb-hal + * + * (C) 2010-2012 Stefan Seyfried + * + * License: GPL v2 or later + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define FP_DEV "/dev/vfd" +static int initialized = 0; +static hw_caps_t caps; + +hw_caps_t *get_hwcaps(void) +{ + if (initialized) + return ∩︀ + + memset(&caps, 0, sizeof(hw_caps_t)); + + initialized = 1; + caps.can_cec = 1; + caps.can_shutdown = 1; + caps.display_type = HW_DISPLAY_LED_NUM; + caps.has_HDMI = 1; + caps.display_xres = 4; + strcpy(caps.boxvendor, "SPARK"); + const char *tmp; + char buf[1024]; + int len = -1, ret, val; + int fd = open (FP_DEV, O_RDWR); + if (fd != -1) { + ret = ioctl(fd, VFDGETVERSION, &val); + if (ret < 0) + fprintf(stderr, "[hardware_caps] %s: VFDGETVERSION %m\n", __func__); + else if (val & 1) { /* VFD = 1, DVFD = 3 */ + caps.display_type = HW_DISPLAY_LINE_TEXT; + caps.display_xres = 8; + } + close(fd); + } + fd = open("/proc/cmdline", O_RDONLY); + if (fd != -1) { + len = read(fd, buf, sizeof(buf) - 1); + close(fd); + } + if (len > 0) { + buf[len] = 0; + char *p = strstr(buf, "STB_ID="); + int h0, h1, h2; + if (p && sscanf(p, "STB_ID=%x:%x:%x:", &h0, &h1, &h2) == 3) { + int sys_id = (h0 << 16) | (h1 << 8) | h2; + switch (sys_id) { + case 0x090003: + tmp = "Truman Premier 1+"; + caps.has_SCART = 1; + break; + case 0x090007: + tmp = "GoldenMedia GM990"; + caps.has_SCART = 1; + break; + case 0x090008: + tmp = "Edision Pingulux"; + caps.has_SCART = 1; // RCA qualifies ... --martii + if (caps.display_type == HW_DISPLAY_LINE_TEXT) + tmp = "Edision Pingulux Plus"; + break; + case 0x09000a: + tmp = "Amiko Alien SDH8900"; + caps.has_SCART = 1; + break; + case 0x09000b: + tmp = "GalaxyInnovations S8120"; + caps.has_SCART = 1; + break; + case 0x09000d: + tmp = "Dynavision Spark"; + caps.has_SCART = 1; + break; + case 0x09000e: + tmp = "SAB Unix F+ Solo (S902)"; + caps.has_SCART = 1; + break; + case 0x090015: + tmp = "Superbox S 750 HD"; + caps.has_SCART = 1; + break; + case 0x09001d: + tmp = "Fulan Spark I+"; + caps.has_SCART = 1; + break; + case 0x090020: + tmp = "SAMSAT LINUX 1"; + caps.has_SCART = 1; + break; + case 0x090021: + tmp = "Visionnet Hammer 5400"; // or Startrack SRT 2020 HD, or Visionnet Fireball 101 + caps.has_SCART = 1; + break; + case 0x0c0003: + tmp = "Truman Top Box 2"; + caps.has_SCART = 1; + break; + case 0x0c0007: + tmp = "GoldenMedia Triplex"; + caps.has_SCART = 1; + break; + case 0x0c000a: + tmp = "Amiko Alien 2"; + caps.has_SCART = 1; + break; + case 0x0c000b: + tmp = "GalaxyInnovations Avatar 3 (8820)"; + caps.has_SCART = 1; + break; + case 0x0c000d: + tmp = "Dynavision 7162"; + caps.has_SCART = 1; + break; + case 0x0c000e: + tmp = "SAB Unix Triple HD (S903)"; + caps.has_SCART = 1; + break; + case 0x0c001d: + tmp = "Satcom 7162"; + caps.has_SCART = 1; + break; + case 0x0c0020: + tmp = "Samsat 7162"; + caps.has_SCART = 1; + break; + case 0x0c0021: + tmp = "Visionnet Falcon"; + caps.has_SCART = 1; + break; + case 0x0c002b00: + tmp = "Icecrypt S3700 CHD"; + caps.has_SCART = 1; + break; + default: + tmp = p; + } + if ((sys_id & 0xff0000) == 0x090000) + caps.boxtype = 7111; + else + caps.boxtype = 7162; + } else + tmp = "(NO STB_ID FOUND)"; + strcpy(caps.boxname, tmp); + } + return ∩︀ +} diff --git a/libspark/init.cpp b/libspark/init.cpp new file mode 100644 index 0000000..f316eac --- /dev/null +++ b/libspark/init.cpp @@ -0,0 +1,312 @@ +#include + +#include "init_lib.h" +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "pwrmngr.h" + +#include "lt_debug.h" +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_INIT, NULL, args) +#define lt_info(args...) _lt_info(TRIPLE_DEBUG_INIT, NULL, args) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define VIRTUALINPUT "/sys/devices/virtual/input" +#define DEVINPUT "/dev/input" + +typedef struct { + const char *name; + const char *desc; + int fd; + unsigned int major; + unsigned int minor; + time_t next_discovery; +} input_device_t; + +static input_device_t input_device[] = { + { "/dev/input/nevis_ir", "lircd", -1, 0, 0, 0 }, + { "/dev/input/tdt_rc", "TDT RC event driver", -1, 0, 0, 0 }, + { "/dev/input/fulan_fp", "fulan front panel buttons", -1, 0, 0, 0 }, + { "/dev/input/event0", NULL, -1, 0, 0, 0 }, + { "/dev/input/event1", NULL, -1, 0, 0, 0 }, + { "/dev/input/event2", NULL, -1, 0, 0, 0 }, + { "/dev/input/event3", NULL, -1, 0, 0, 0 }, + { "/dev/input/event4", NULL, -1, 0, 0, 0 }, + { "/dev/input/event5", NULL, -1, 0, 0, 0 }, + { "/dev/input/event6", NULL, -1, 0, 0, 0 }, + { "/dev/input/event7", NULL, -1, 0, 0, 0 }, + { NULL, NULL, -1, 0, 0, 0 } +}; + +static int number_of_input_devices = 0; + +static void do_mknod(int i, char *d_name) { + char name[255]; + int dev = -1; + // I've no idea how the event device number is actually calculated. Just loop. --martii + + for (int j = 0; j < 99 && dev < 0; j++) { + snprintf(name, sizeof(name), VIRTUALINPUT "/%s/event%d/dev", d_name, j); + dev = open (name, O_RDONLY); + } + + if (dev > -1) { + char buf[255]; + int l = read(dev, buf, sizeof(buf) - 1); + close(dev); + if (l > -1) { + buf[l] = 0; + if (2 == sscanf(buf, "%d:%d", &input_device[i].major, &input_device[i].minor)) { + mknod(input_device[i].name, 0666 | S_IFCHR, + gnu_dev_makedev(input_device[i].major, input_device[i].minor)); + } + } + } +} + +static void create_input_devices (void) { + DIR *d = opendir (VIRTUALINPUT); + if (d) { + struct dirent *e; + while ((e = readdir(d))) { + char name[255]; + if (e->d_name[0] == '.') + continue; + snprintf(name, sizeof(name), VIRTUALINPUT "/%s/name", e->d_name); + int n = open(name, O_RDONLY); + if (n > -1) { + char buf[255]; + int l = read(n, buf, sizeof(buf) - 1); + close(n); + if (l > 1) { + do + buf[l--] = 0; + while (l > 1 && buf[l] == '\n'); + + for (int i = 0; i < number_of_input_devices; i++) + if (input_device[i].desc && !strcmp(buf, input_device[i].desc)) { + do_mknod(i, e->d_name); + break; + } + } + } + } + closedir(d); + } + // remove any event* files left that point to our "well-known" inputs + d = opendir (DEVINPUT); + if (d) { + struct dirent *e; + while ((e = readdir(d))) { + char name[255]; + if (strncmp(e->d_name, "event", 5)) + continue; + snprintf(name, sizeof(name), DEVINPUT "/%s", e->d_name); + struct stat st; + if (stat(name, &st)) + continue; + for (int i = 0; i < number_of_input_devices; i++) + if (input_device[i].major && + gnu_dev_major(st.st_rdev) == input_device[i].major && + gnu_dev_minor(st.st_rdev) == input_device[i].minor) + unlink(name); + } + closedir(d); + } +} + +static pthread_t inmux_task = 0; +static int inmux_thread_running = 0; + +static void count_input_devices(void) { + input_device_t *i = input_device; + while (i->name) + i++, number_of_input_devices++; +} + +static void open_input_devices(void) { + time_t now = time(NULL); + for (int i = 0; i < number_of_input_devices; i++) + if ((input_device[i].fd < 0) && (input_device[i].next_discovery <= now)) { + input_device[i].next_discovery = now + 60; + input_device[i].fd = open(input_device[i].name, O_RDWR | O_NONBLOCK); + } +} + +static void reopen_input_devices(void) { + create_input_devices(); + time_t now = time(NULL); + for (int i = 0; i < number_of_input_devices; i++) { + input_device[i].next_discovery = now + 60; + int fd = open(input_device[i].name, O_RDWR | O_NONBLOCK); + if (fd > -1) { + if (input_device[i].fd > -1) { + dup2(fd, input_device[i].fd); + close (fd); + } else { + input_device[i].fd = fd; + } + } else if (input_device[i].fd > -1) { + close (input_device[i].fd); + input_device[i].fd = -1; + } + } +} + +static void close_input_devices(void) { + for (int i = 0; i < number_of_input_devices; i++) + if (input_device[i].fd > -1) { + close(input_device[i].fd); + input_device[i].fd = -1; + } +} + +static void poll_input_devices(void) { + struct pollfd fds[number_of_input_devices]; + input_device_t *inputs[number_of_input_devices]; + int nfds = 0; + for (int i = 1; i < number_of_input_devices; i++) + if (input_device[i].fd > -1) { + fds[nfds].fd = input_device[i].fd; + fds[nfds].events = POLLIN | POLLHUP | POLLERR; + fds[nfds].revents = 0; + inputs[nfds] = &input_device[i]; + nfds++; + } + + if (nfds == 0) { + // Only a single input device, which happens to be our master. poll() to avoid looping too fast. + fds[0].fd = input_device[0].fd; + fds[0].events = POLLIN | POLLHUP | POLLERR; + fds[0].revents = 0; + poll(fds, 1, 60000 /* ms */); + return; + } + + int r = poll(fds, nfds, 60000 /* ms */); + if (r < 0) { + if (errno != EAGAIN) { + lt_info("%s: poll(): %m\n", __func__); + inmux_thread_running = 0; + } + return; + } + for (int i = 0; i < nfds && r > 0; i++) { + if (fds[i].revents & POLLIN) { +//fprintf(stderr, "### input from fd %d (%s)\n", fds[i].fd, inputs[i]->name); + struct input_event ev; + while (sizeof(ev) == read(fds[i].fd, &ev, sizeof(ev))) + write(input_device[0].fd, &ev, sizeof(ev)); + r--; + } else if (fds[i].revents & (POLLHUP | POLLERR | POLLNVAL)) { +//fprintf(stderr, "### error on %d (%s)\n", fds[i].fd, inputs[i]->name); + close (fds[i].fd); + inputs[i]->fd = -1; + r--; + } + } +} + +static void *inmux_thread(void *) +{ + char threadname[17]; + strncpy(threadname, __func__, sizeof(threadname)); + threadname[16] = 0; + prctl (PR_SET_NAME, (unsigned long)&threadname); + + inmux_thread_running = 1; + while (inmux_thread_running) { + open_input_devices(); + poll_input_devices(); + } + + return NULL; +} + +void start_inmux_thread(void) +{ + input_device[0].fd = open(input_device[0].name, O_RDWR | O_NONBLOCK); // nevis_ir. This is mandatory. + if (input_device[0].fd < 0){ + lt_info("%s: open(%s): %m\n", __func__, input_device[0].name); + return; + } + if (pthread_create(&inmux_task, 0, inmux_thread, NULL) != 0) + { + lt_info("%s: inmux thread pthread_create: %m\n", __func__); + inmux_thread_running = 0; + return; + } + pthread_detach(inmux_task); +} + +void stop_inmux_thread(void) +{ + inmux_thread_running = 0; +} + +static bool initialized = false; + +void init_td_api() +{ + if (!initialized) + lt_debug_init(); + lt_info("%s begin, initialized=%d, debug=0x%02x\n", __FUNCTION__, (int)initialized, debuglevel); + if (!initialized) + { + cCpuFreqManager f; + f.SetCpuFreq(0); /* CPUFREQ == 0 is the trigger for leaving standby */ + count_input_devices(); + create_input_devices(); + start_inmux_thread(); + + /* this is a strange hack: the drivers seem to only work correctly after + * demux0 has been used once. After that, we can use demux1,2,... */ + struct dmx_pes_filter_params p; + int dmx = open("/dev/dvb/adapter0/demux0", O_RDWR|O_CLOEXEC); + if (dmx < 0) + lt_info("%s: ERROR open /dev/dvb/adapter0/demux0 (%m)\n", __func__); + else + { + memset(&p, 0, sizeof(p)); + p.output = DMX_OUT_DECODER; + p.input = DMX_IN_FRONTEND; + p.flags = DMX_IMMEDIATE_START; + p.pes_type = DMX_PES_VIDEO; + ioctl(dmx, DMX_SET_PES_FILTER, &p); + ioctl(dmx, DMX_STOP); + close(dmx); + } + } else + reopen_input_devices(); + initialized = true; + lt_info("%s end\n", __FUNCTION__); +} + +void shutdown_td_api() +{ + lt_info("%s, initialized = %d\n", __FUNCTION__, (int)initialized); + if (initialized) { + stop_inmux_thread(); + close_input_devices(); + } + initialized = false; +} diff --git a/libspark/init_cs.h b/libspark/init_cs.h new file mode 100644 index 0000000..7f9e341 --- /dev/null +++ b/libspark/init_cs.h @@ -0,0 +1,2 @@ +#warning using init_cs.h from libspark +#include "init_lib.h" diff --git a/libspark/init_lib.h b/libspark/init_lib.h new file mode 100644 index 0000000..d9a6f09 --- /dev/null +++ b/libspark/init_lib.h @@ -0,0 +1,5 @@ +#ifndef __INIT_TD_H +#define __INIT_TD_H +void init_td_api(); +void shutdown_td_api(); +#endif diff --git a/libspark/irmp.c b/libspark/irmp.c new file mode 100644 index 0000000..68b82cc --- /dev/null +++ b/libspark/irmp.c @@ -0,0 +1,4284 @@ +/*--------------------------------------------------------------------------------------------------------------------------------------------------- + * irmp.c - infrared multi-protocol decoder, supports several remote control protocols + * + * Copyright (c) 2009-2011 Frank Meyer - frank(at)fli4l.de + * + * $Id: irmp.c,v 1.115 2012/02/21 08:41:46 fm Exp $ + * + * ATMEGA88 @ 8 MHz + * + * Supported mikrocontrollers: + * + * ATtiny45, ATtiny85 + * ATtiny84 + * ATmega8, ATmega16, ATmega32 + * ATmega162 + * ATmega164, ATmega324, ATmega644, ATmega644P, ATmega1284 + * ATmega88, ATmega88P, ATmega168, ATmega168P, ATmega328P + * + * Typical manufacturers of remote controls: + * + * SIRCS - Sony + * NEC - NEC, Yamaha, Canon, Tevion, Harman/Kardon, Hitachi, JVC, Pioneer, Toshiba, Xoro, Orion, and many other Japanese manufacturers + * SAMSUNG - Samsung + * SAMSUNG32 - Samsung + * MATSUSHITA - Matsushita + * KASEIKYO - Panasonic, Denon & other Japanese manufacturers (members of "Japan's Association for Electric Home Application") + * RECS80 - Philips, Nokia, Thomson, Nordmende, Telefunken, Saba + * RC5 - Philips and other European manufacturers + * DENON - Denon, Sharp + * RC6 - Philips and other European manufacturers + * APPLE - Apple + * NUBERT - Nubert Subwoofer System + * B&O - Bang & Olufsen + * PANASONIC - Panasonic (older, yet not implemented) + * GRUNDIG - Grundig + * NOKIA - Nokia + * SIEMENS - Siemens, e.g. Gigaset M740AV + * FDC - FDC IR keyboard + * RCCAR - IR remote control for RC cars + * JVC - JVC + * THOMSON - Thomson + * NIKON - Nikon cameras + * RUWIDO - T-Home + * KATHREIN - Kathrein + * LEGO - Lego Power Functions RC + * + *--------------------------------------------------------------------------------------------------------------------------------------------------- + * + * SIRCS + * ----- + * + * frame: 1 start bit + 12-20 data bits + no stop bit + * data: 7 command bits + 5 address bits + 0 to 8 additional bits + * + * start bit: data "0": data "1": stop bit: + * -----------------_________ ------_____ ------------______ + * 2400us 600us 600us 600us 1200us 600 us no stop bit + * + *--------------------------------------------------------------------------------------------------------------------------------------------------- + * + * NEC + extended NEC + * ------------------------- + * + * frame: 1 start bit + 32 data bits + 1 stop bit + * data NEC: 8 address bits + 8 inverted address bits + 8 command bits + 8 inverted command bits + * data extended NEC: 16 address bits + 8 command bits + 8 inverted command bits + * + * start bit: data "0": data "1": stop bit: + * -----------------_________ ------______ ------________________ ------______.... + * 9000us 4500us 560us 560us 560us 1690 us 560us + * + * + * Repetition frame: + * + * -----------------_________------______ .... ~100ms Pause, then repeat + * 9000us 2250us 560us + * + *--------------------------------------------------------------------------------------------------------------------------------------------------- + * + * SAMSUNG + * ------- + * + * frame: 1 start bit + 16 data(1) bits + 1 sync bit + additional 20 data(2) bits + 1 stop bit + * data(1): 16 address bits + * data(2): 4 ID bits + 8 command bits + 8 inverted command bits + * + * start bit: data "0": data "1": sync bit: stop bit: + * ----------______________ ------______ ------________________ ------______________ ------______.... + * 4500us 4500us 550us 450us 550us 1450us 550us 4500us 550us + * + *--------------------------------------------------------------------------------------------------------------------------------------------------- + * + * SAMSUNG32 + * ---------- + * + * frame: 1 start bit + 32 data bits + 1 stop bit + * data: 16 address bits + 16 command bits + * + * start bit: data "0": data "1": stop bit: + * ----------______________ ------______ ------________________ ------______.... + * 4500us 4500us 550us 450us 550us 1450us 550us + * + *--------------------------------------------------------------------------------------------------------------------------------------------------- + * + * MATSUSHITA + * ---------- + * + * frame: 1 start bit + 24 data bits + 1 stop bit + * data: 6 custom bits + 6 command bits + 12 address bits + * + * start bit: data "0": data "1": stop bit: + * ----------_________ ------______ ------________________ ------______.... + * 3488us 3488us 872us 872us 872us 2616us 872us + * + *--------------------------------------------------------------------------------------------------------------------------------------------------- + * + * KASEIKYO + * -------- + * + * frame: 1 start bit + 48 data bits + 1 stop bit + * data: 16 manufacturer bits + 4 parity bits + 4 genre1 bits + 4 genre2 bits + 10 command bits + 2 id bits + 8 parity bits + * + * start bit: data "0": data "1": stop bit: + * ----------______ ------______ ------________________ ------______.... + * 3380us 1690us 423us 423us 423us 1269us 423us + * + *--------------------------------------------------------------------------------------------------------------------------------------------------- + * + * RECS80 + * ------ + * + * frame: 2 start bits + 10 data bits + 1 stop bit + * data: 1 toggle bit + 3 address bits + 6 command bits + * + * start bit: data "0": data "1": stop bit: + * -----_____________________ -----____________ -----______________ ------_______.... + * 158us 7432us 158us 4902us 158us 7432us 158us + * + *--------------------------------------------------------------------------------------------------------------------------------------------------- + * + * RECS80EXT + * --------- + * + * frame: 2 start bits + 11 data bits + 1 stop bit + * data: 1 toggle bit + 4 address bits + 6 command bits + * + * start bit: data "0": data "1": stop bit: + * -----_____________________ -----____________ -----______________ ------_______.... + * 158us 3637us 158us 4902us 158us 7432us 158us + * + *--------------------------------------------------------------------------------------------------------------------------------------------------- + * + * RC5 + RC5X + * ---------- + * + * RC5 frame: 2 start bits + 12 data bits + no stop bit + * RC5 data: 1 toggle bit + 5 address bits + 6 command bits + * RC5X frame: 1 start bit + 13 data bits + no stop bit + * RC5X data: 1 inverted command bit + 1 toggle bit + 5 address bits + 6 command bits + * + * start bit: data "0": data "1": + * ______----- ------______ ______------ + * 889us 889us 889us 889us 889us 889us + * + *--------------------------------------------------------------------------------------------------------------------------------------------------- + * + * DENON + * ----- + * + * frame: 0 start bits + 16 data bits + stop bit + 65ms pause + 16 inverted data bits + stop bit + * data: 5 address bits + 10 command bits + * + * Theory: + * + * data "0": data "1": + * ------________________ ------______________ + * 275us 775us 275us 1900us + * + * Practice: + * + * data "0": data "1": + * ------________________ ------______________ + * 310us 745us 310us 1780us + * + *--------------------------------------------------------------------------------------------------------------------------------------------------- + * + * RC6 + * --- + * + * RC6 frame: 1 start bit + 1 bit "1" + 3 mode bits + 1 toggle bit + 16 data bits + 2666 us pause + * RC6 data: 8 address bits + 8 command bits + * + * start bit toggle bit "0": toggle bit "1": data/mode "0": data/mode "1": + * ____________------- _______------- -------_______ _______------- -------_______ + * 2666us 889us 889us 889us 889us 889us 444us 444us 444us 444us + * + *--------------------------------------------------------------------------------------------------------------------------------------------------- + * + * APPLE + * ----- + * + * frame: 1 start bit + 32 data bits + 1 stop bit + * data: 16 address bits + 11100000 + 8 command bits + * + * start bit: data "0": data "1": stop bit: + * -----------------_________ ------______ ------________________ ------______.... + * 9000us 4500us 560us 560us 560us 1690 us 560us + * + *--------------------------------------------------------------------------------------------------------------------------------------------------- + * + * NUBERT (subwoofer system) + * ------------------------- + * + * frame: 1 start bit + 10 data bits + 1 stop bit + * data: 0 address bits + 10 command bits ? + * + * start bit: data "0": data "1": stop bit: + * ----------_____ ------______ ------________________ ------______.... + * 1340us 340us 500us 1300us 1340us 340us 500us + * + *--------------------------------------------------------------------------------------------------------------------------------------------------- + * + * BANG_OLUFSEN + * ------------ + * + * frame: 4 start bits + 16 data bits + 1 trailer bit + 1 stop bit + * data: 0 address bits + 16 command bits + * + * 1st start bit: 2nd start bit: 3rd start bit: 4th start bit: + * -----________ -----________ -----_____________ -----________ + * 210us 3000us 210us 3000us 210us 15000us 210us 3000us + * + * data "0": data "1": data "repeat bit": trailer bit: stop bit: + * -----________ -----_____________ -----___________ -----_____________ -----____... + * 210us 3000us 210us 9000us 210us 6000us 210us 12000us 210us + * + *--------------------------------------------------------------------------------------------------------------------------------------------------- + * + * GRUNDIG + * ------- + * + * packet: 1 start frame + 19,968ms pause + N info frames + 117,76ms pause + 1 stop frame + * frame: 1 pre bit + 1 start bit + 9 data bits + no stop bit + * pause between info frames: 117,76ms + * + * data of start frame: 9 x 1 + * data of info frame: 9 command bits + * data of stop frame: 9 x 1 + * + * pre bit: start bit data "0": data "1": + * ------____________ ------______ ______------ ------______ + * 528us 2639us 528us 528us 528us 528us 528us 528us + * + *--------------------------------------------------------------------------------------------------------------------------------------------------- + * + * NOKIA: + * ------ + * + * Timing similar to Grundig, but 16 data bits: + * frame: 1 pre bit + 1 start bit + 8 command bits + 8 address bits + no stop bit + * + *--------------------------------------------------------------------------------------------------------------------------------------------------- + * + * SIEMENS or RUWIDO: + * ------------------ + * + * SIEMENS frame: 1 start bit + 22 data bits + no stop bit + * SIEMENS data: 13 address bits + 1 repeat bit + 7 data bits + 1 unknown bit + * + * start bit data "0": data "1": + * -------_______ _______------- -------_______ + * 250us 250us 250us 250us 250us 250us + * + *--------------------------------------------------------------------------------------------------------------------------------------------------- + * + * PANASONIC (older protocol, yet not implemented, see also MATSUSHITA, timing very similar) + * ----------------------------------------------------------------------------------------- + * + * frame: 1 start bit + 22 data bits + 1 stop bit + * 22 data bits = 5 custom bits + 6 data bits + 5 inverted custom bits + 6 inverted data bits + * + * European version: T = 456us + * USA & Canada version: T = 422us + * + * start bit: data "0": data "1": stop bit: + * 8T 8T 2T 2T 2T 6T 2T + * -------------____________ ------_____ ------_____________ ------_______.... + * 3648us 3648us 912us 912us 912us 2736us 912us (Europe) + * 3376us 3376us 844us 844us 844us 2532us 844us (US) + * + *--------------------------------------------------------------------------------------------------------------------------------------------------- + * + * 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 2 of the License, or + * (at your option) any later version. + *--------------------------------------------------------------------------------------------------------------------------------------------------- + */ + +#if defined(__18CXX) +#define PIC_C18 // Microchip C18 Compiler +#endif + +#if defined(__PCM__) || defined(__PCB__) || defined(__PCH__) // CCS PIC Compiler instead of AVR +#define PIC_CCS_COMPILER +#endif + +#ifdef unix // test on linux/unix +#include +#include +#include +#include +#include + +/* for crazy lirc stuff... */ +#include +#include +#include +#include + +#define ANALYZE +#define PROGMEM +#define memcpy_P memcpy + +#else // not unix: + +#ifdef WIN32 +#include +#include +typedef unsigned char uint8_t; +typedef unsigned short uint16_t; +#define ANALYZE +#define PROGMEM +#define memcpy_P memcpy + +#else + +#if defined (PIC_CCS_COMPILER) || defined(PIC_C18) + +#include +#define PROGMEM +#define memcpy_P memcpy + +#if defined (PIC_CCS_COMPILER) +typedef unsigned int8 uint8_t; +typedef unsigned int16 uint16_t; +#endif + +#else // AVR: + +#include +#include +#include +#include +#include +#include + +#endif // PIC_CCS_COMPILER or PIC_C18 + +#endif // windows +#endif // unix + +#ifndef IRMP_USE_AS_LIB +#include "irmpconfig.h" +#endif +#include "irmp.h" + +#if IRMP_SUPPORT_GRUNDIG_PROTOCOL == 1 || IRMP_SUPPORT_NOKIA_PROTOCOL == 1 || IRMP_SUPPORT_IR60_PROTOCOL == 1 +#define IRMP_SUPPORT_GRUNDIG_NOKIA_IR60_PROTOCOL 1 +#else +#define IRMP_SUPPORT_GRUNDIG_NOKIA_IR60_PROTOCOL 0 +#endif + +#if IRMP_SUPPORT_SIEMENS_PROTOCOL == 1 || IRMP_SUPPORT_RUWIDO_PROTOCOL == 1 +#define IRMP_SUPPORT_SIEMENS_OR_RUWIDO_PROTOCOL 1 +#else +#define IRMP_SUPPORT_SIEMENS_OR_RUWIDO_PROTOCOL 0 +#endif + +#if IRMP_SUPPORT_RC5_PROTOCOL == 1 || \ + IRMP_SUPPORT_RC6_PROTOCOL == 1 || \ + IRMP_SUPPORT_GRUNDIG_NOKIA_IR60_PROTOCOL == 1 || \ + IRMP_SUPPORT_SIEMENS_OR_RUWIDO_PROTOCOL == 1 || \ + IRMP_SUPPORT_GRUNDIG2_PROTOCOL == 1 || \ + IRMP_SUPPORT_IR60_PROTOCOL +#define IRMP_SUPPORT_MANCHESTER 1 +#else +#define IRMP_SUPPORT_MANCHESTER 0 +#endif + +#if IRMP_SUPPORT_NETBOX_PROTOCOL == 1 +#define IRMP_SUPPORT_SERIAL 1 +#else +#define IRMP_SUPPORT_SERIAL 0 +#endif + +#define IRMP_KEY_REPETITION_LEN (uint16_t)(F_INTERRUPTS * 150.0e-3 + 0.5) // autodetect key repetition within 150 msec + +#define MIN_TOLERANCE_00 1.0 // -0% +#define MAX_TOLERANCE_00 1.0 // +0% + +#define MIN_TOLERANCE_05 0.95 // -5% +#define MAX_TOLERANCE_05 1.05 // +5% + +#define MIN_TOLERANCE_10 0.9 // -10% +#define MAX_TOLERANCE_10 1.1 // +10% + +#define MIN_TOLERANCE_15 0.85 // -15% +#define MAX_TOLERANCE_15 1.15 // +15% + +#define MIN_TOLERANCE_20 0.8 // -20% +#define MAX_TOLERANCE_20 1.2 // +20% + +#define MIN_TOLERANCE_30 0.7 // -30% +#define MAX_TOLERANCE_30 1.3 // +30% + +#define MIN_TOLERANCE_40 0.6 // -40% +#define MAX_TOLERANCE_40 1.4 // +40% + +#define MIN_TOLERANCE_50 0.5 // -50% +#define MAX_TOLERANCE_50 1.5 // +50% + +#define MIN_TOLERANCE_60 0.4 // -60% +#define MAX_TOLERANCE_60 1.6 // +60% + +#define MIN_TOLERANCE_70 0.3 // -70% +#define MAX_TOLERANCE_70 1.7 // +70% + +#define SIRCS_START_BIT_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * SIRCS_START_BIT_PULSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define SIRCS_START_BIT_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * SIRCS_START_BIT_PULSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define SIRCS_START_BIT_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * SIRCS_START_BIT_PAUSE_TIME * MIN_TOLERANCE_20 + 0.5) - 1) +#if IRMP_SUPPORT_NETBOX_PROTOCOL // only 5% to avoid conflict with NETBOX: +#define SIRCS_START_BIT_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * SIRCS_START_BIT_PAUSE_TIME * MAX_TOLERANCE_05 + 0.5)) +#else // only 5% + 1 to avoid conflict with RC6: +#define SIRCS_START_BIT_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * SIRCS_START_BIT_PAUSE_TIME * MAX_TOLERANCE_05 + 0.5) + 1) +#endif +#define SIRCS_1_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * SIRCS_1_PULSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define SIRCS_1_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * SIRCS_1_PULSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define SIRCS_0_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * SIRCS_0_PULSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define SIRCS_0_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * SIRCS_0_PULSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define SIRCS_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * SIRCS_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define SIRCS_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * SIRCS_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) + +#define NEC_START_BIT_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * NEC_START_BIT_PULSE_TIME * MIN_TOLERANCE_30 + 0.5) - 1) +#define NEC_START_BIT_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * NEC_START_BIT_PULSE_TIME * MAX_TOLERANCE_30 + 0.5) + 1) +#define NEC_START_BIT_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * NEC_START_BIT_PAUSE_TIME * MIN_TOLERANCE_30 + 0.5) - 1) +#define NEC_START_BIT_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * NEC_START_BIT_PAUSE_TIME * MAX_TOLERANCE_30 + 0.5) + 1) +#define NEC_REPEAT_START_BIT_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * NEC_REPEAT_START_BIT_PAUSE_TIME * MIN_TOLERANCE_30 + 0.5) - 1) +#define NEC_REPEAT_START_BIT_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * NEC_REPEAT_START_BIT_PAUSE_TIME * MAX_TOLERANCE_30 + 0.5) + 1) +#define NEC_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * NEC_PULSE_TIME * MIN_TOLERANCE_30 + 0.5) - 1) +#define NEC_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * NEC_PULSE_TIME * MAX_TOLERANCE_30 + 0.5) + 1) +#define NEC_1_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * NEC_1_PAUSE_TIME * MIN_TOLERANCE_30 + 0.5) - 1) +#define NEC_1_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * NEC_1_PAUSE_TIME * MAX_TOLERANCE_30 + 0.5) + 1) +#define NEC_0_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * NEC_0_PAUSE_TIME * MIN_TOLERANCE_30 + 0.5) - 1) +#define NEC_0_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * NEC_0_PAUSE_TIME * MAX_TOLERANCE_30 + 0.5) + 1) +// autodetect nec repetition frame within 50 msec: +// NEC seems to send the first repetition frame after 40ms, further repetition frames after 100 ms +#if 0 +#define NEC_FRAME_REPEAT_PAUSE_LEN_MAX (uint16_t)(F_INTERRUPTS * NEC_FRAME_REPEAT_PAUSE_TIME * MAX_TOLERANCE_20 + 0.5) +#else +#define NEC_FRAME_REPEAT_PAUSE_LEN_MAX (uint16_t)(F_INTERRUPTS * 100.0e-3 * MAX_TOLERANCE_20 + 0.5) +#endif + +#define SAMSUNG_START_BIT_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * SAMSUNG_START_BIT_PULSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define SAMSUNG_START_BIT_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * SAMSUNG_START_BIT_PULSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define SAMSUNG_START_BIT_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * SAMSUNG_START_BIT_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define SAMSUNG_START_BIT_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * SAMSUNG_START_BIT_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define SAMSUNG_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * SAMSUNG_PULSE_TIME * MIN_TOLERANCE_30 + 0.5) - 1) +#define SAMSUNG_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * SAMSUNG_PULSE_TIME * MAX_TOLERANCE_30 + 0.5) + 1) +#define SAMSUNG_1_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * SAMSUNG_1_PAUSE_TIME * MIN_TOLERANCE_30 + 0.5) - 1) +#define SAMSUNG_1_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * SAMSUNG_1_PAUSE_TIME * MAX_TOLERANCE_30 + 0.5) + 1) +#define SAMSUNG_0_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * SAMSUNG_0_PAUSE_TIME * MIN_TOLERANCE_30 + 0.5) - 1) +#define SAMSUNG_0_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * SAMSUNG_0_PAUSE_TIME * MAX_TOLERANCE_30 + 0.5) + 1) + +#define MATSUSHITA_START_BIT_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * MATSUSHITA_START_BIT_PULSE_TIME * MIN_TOLERANCE_20 + 0.5) - 1) +#define MATSUSHITA_START_BIT_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * MATSUSHITA_START_BIT_PULSE_TIME * MAX_TOLERANCE_20 + 0.5) + 1) +#define MATSUSHITA_START_BIT_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * MATSUSHITA_START_BIT_PAUSE_TIME * MIN_TOLERANCE_20 + 0.5) - 1) +#define MATSUSHITA_START_BIT_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * MATSUSHITA_START_BIT_PAUSE_TIME * MAX_TOLERANCE_20 + 0.5) + 1) +#define MATSUSHITA_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * MATSUSHITA_PULSE_TIME * MIN_TOLERANCE_40 + 0.5) - 1) +#define MATSUSHITA_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * MATSUSHITA_PULSE_TIME * MAX_TOLERANCE_40 + 0.5) + 1) +#define MATSUSHITA_1_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * MATSUSHITA_1_PAUSE_TIME * MIN_TOLERANCE_40 + 0.5) - 1) +#define MATSUSHITA_1_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * MATSUSHITA_1_PAUSE_TIME * MAX_TOLERANCE_40 + 0.5) + 1) +#define MATSUSHITA_0_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * MATSUSHITA_0_PAUSE_TIME * MIN_TOLERANCE_40 + 0.5) - 1) +#define MATSUSHITA_0_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * MATSUSHITA_0_PAUSE_TIME * MAX_TOLERANCE_40 + 0.5) + 1) + +#define KASEIKYO_START_BIT_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * KASEIKYO_START_BIT_PULSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define KASEIKYO_START_BIT_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * KASEIKYO_START_BIT_PULSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define KASEIKYO_START_BIT_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * KASEIKYO_START_BIT_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define KASEIKYO_START_BIT_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * KASEIKYO_START_BIT_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define KASEIKYO_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * KASEIKYO_PULSE_TIME * MIN_TOLERANCE_50 + 0.5) - 1) +#define KASEIKYO_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * KASEIKYO_PULSE_TIME * MAX_TOLERANCE_50 + 0.5) + 1) +#define KASEIKYO_1_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * KASEIKYO_1_PAUSE_TIME * MIN_TOLERANCE_30 + 0.5) - 1) +#define KASEIKYO_1_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * KASEIKYO_1_PAUSE_TIME * MAX_TOLERANCE_30 + 0.5) + 1) +#define KASEIKYO_0_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * KASEIKYO_0_PAUSE_TIME * MIN_TOLERANCE_50 + 0.5) - 1) +#define KASEIKYO_0_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * KASEIKYO_0_PAUSE_TIME * MAX_TOLERANCE_50 + 0.5) + 1) + +#define RECS80_START_BIT_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * RECS80_START_BIT_PULSE_TIME * MIN_TOLERANCE_00 + 0.5) - 1) +#define RECS80_START_BIT_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * RECS80_START_BIT_PULSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define RECS80_START_BIT_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * RECS80_START_BIT_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define RECS80_START_BIT_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * RECS80_START_BIT_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define RECS80_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * RECS80_PULSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define RECS80_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * RECS80_PULSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define RECS80_1_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * RECS80_1_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define RECS80_1_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * RECS80_1_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define RECS80_0_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * RECS80_0_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define RECS80_0_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * RECS80_0_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) + +#define RC5_START_BIT_LEN_MIN ((uint8_t)(F_INTERRUPTS * RC5_BIT_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define RC5_START_BIT_LEN_MAX ((uint8_t)(F_INTERRUPTS * RC5_BIT_TIME * MAX_TOLERANCE_10 + 0.5) + 1) + +#define RC5_BIT_LEN_MIN ((uint8_t)(F_INTERRUPTS * RC5_BIT_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define RC5_BIT_LEN_MAX ((uint8_t)(F_INTERRUPTS * RC5_BIT_TIME * MAX_TOLERANCE_10 + 0.5) + 1) + +#define DENON_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * DENON_PULSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define DENON_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * DENON_PULSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define DENON_1_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * DENON_1_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define DENON_1_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * DENON_1_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#if IRMP_SUPPORT_SIEMENS_OR_RUWIDO_PROTOCOL == 1 +#define DENON_0_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * DENON_0_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5)) // no -1, avoid conflict with RUWIDO +#else +#define DENON_0_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * DENON_0_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) // be more tolerant +#endif +#define DENON_0_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * DENON_0_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) + +#define THOMSON_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * THOMSON_PULSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define THOMSON_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * THOMSON_PULSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define THOMSON_1_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * THOMSON_1_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define THOMSON_1_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * THOMSON_1_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define THOMSON_0_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * THOMSON_0_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define THOMSON_0_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * THOMSON_0_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) + +#define RC6_START_BIT_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * RC6_START_BIT_PULSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define RC6_START_BIT_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * RC6_START_BIT_PULSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define RC6_START_BIT_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * RC6_START_BIT_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define RC6_START_BIT_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * RC6_START_BIT_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define RC6_TOGGLE_BIT_LEN_MIN ((uint8_t)(F_INTERRUPTS * RC6_TOGGLE_BIT_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define RC6_TOGGLE_BIT_LEN_MAX ((uint8_t)(F_INTERRUPTS * RC6_TOGGLE_BIT_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define RC6_BIT_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * RC6_BIT_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define RC6_BIT_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * RC6_BIT_TIME * MAX_TOLERANCE_60 + 0.5) + 1) // pulses: 300 - 800 +#define RC6_BIT_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * RC6_BIT_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define RC6_BIT_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * RC6_BIT_TIME * MAX_TOLERANCE_20 + 0.5) + 1) // pauses: 300 - 600 + +#define RECS80EXT_START_BIT_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * RECS80EXT_START_BIT_PULSE_TIME * MIN_TOLERANCE_00 + 0.5) - 1) +#define RECS80EXT_START_BIT_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * RECS80EXT_START_BIT_PULSE_TIME * MAX_TOLERANCE_00 + 0.5) + 1) +#define RECS80EXT_START_BIT_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * RECS80EXT_START_BIT_PAUSE_TIME * MIN_TOLERANCE_05 + 0.5) - 1) +#define RECS80EXT_START_BIT_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * RECS80EXT_START_BIT_PAUSE_TIME * MAX_TOLERANCE_05 + 0.5) + 1) +#define RECS80EXT_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * RECS80EXT_PULSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define RECS80EXT_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * RECS80EXT_PULSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define RECS80EXT_1_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * RECS80EXT_1_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define RECS80EXT_1_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * RECS80EXT_1_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define RECS80EXT_0_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * RECS80EXT_0_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define RECS80EXT_0_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * RECS80EXT_0_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) + +#define NUBERT_START_BIT_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * NUBERT_START_BIT_PULSE_TIME * MIN_TOLERANCE_20 + 0.5) - 1) +#define NUBERT_START_BIT_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * NUBERT_START_BIT_PULSE_TIME * MAX_TOLERANCE_20 + 0.5) + 1) +#define NUBERT_START_BIT_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * NUBERT_START_BIT_PAUSE_TIME * MIN_TOLERANCE_20 + 0.5) - 1) +#define NUBERT_START_BIT_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * NUBERT_START_BIT_PAUSE_TIME * MAX_TOLERANCE_20 + 0.5) + 1) +#define NUBERT_1_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * NUBERT_1_PULSE_TIME * MIN_TOLERANCE_20 + 0.5) - 1) +#define NUBERT_1_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * NUBERT_1_PULSE_TIME * MAX_TOLERANCE_20 + 0.5) + 1) +#define NUBERT_1_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * NUBERT_1_PAUSE_TIME * MIN_TOLERANCE_20 + 0.5) - 1) +#define NUBERT_1_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * NUBERT_1_PAUSE_TIME * MAX_TOLERANCE_20 + 0.5) + 1) +#define NUBERT_0_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * NUBERT_0_PULSE_TIME * MIN_TOLERANCE_20 + 0.5) - 1) +#define NUBERT_0_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * NUBERT_0_PULSE_TIME * MAX_TOLERANCE_20 + 0.5) + 1) +#define NUBERT_0_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * NUBERT_0_PAUSE_TIME * MIN_TOLERANCE_20 + 0.5) - 1) +#define NUBERT_0_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * NUBERT_0_PAUSE_TIME * MAX_TOLERANCE_20 + 0.5) + 1) + +#define BANG_OLUFSEN_START_BIT1_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * BANG_OLUFSEN_START_BIT1_PULSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define BANG_OLUFSEN_START_BIT1_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * BANG_OLUFSEN_START_BIT1_PULSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define BANG_OLUFSEN_START_BIT1_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * BANG_OLUFSEN_START_BIT1_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define BANG_OLUFSEN_START_BIT1_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * BANG_OLUFSEN_START_BIT1_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define BANG_OLUFSEN_START_BIT2_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * BANG_OLUFSEN_START_BIT2_PULSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define BANG_OLUFSEN_START_BIT2_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * BANG_OLUFSEN_START_BIT2_PULSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define BANG_OLUFSEN_START_BIT2_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * BANG_OLUFSEN_START_BIT2_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define BANG_OLUFSEN_START_BIT2_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * BANG_OLUFSEN_START_BIT2_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define BANG_OLUFSEN_START_BIT3_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * BANG_OLUFSEN_START_BIT3_PULSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define BANG_OLUFSEN_START_BIT3_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * BANG_OLUFSEN_START_BIT3_PULSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define BANG_OLUFSEN_START_BIT3_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * BANG_OLUFSEN_START_BIT3_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define BANG_OLUFSEN_START_BIT3_PAUSE_LEN_MAX ((PAUSE_LEN)(F_INTERRUPTS * BANG_OLUFSEN_START_BIT3_PAUSE_TIME * MAX_TOLERANCE_05 + 0.5) + 1) // value must be below IRMP_TIMEOUT +#define BANG_OLUFSEN_START_BIT4_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * BANG_OLUFSEN_START_BIT4_PULSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define BANG_OLUFSEN_START_BIT4_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * BANG_OLUFSEN_START_BIT4_PULSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define BANG_OLUFSEN_START_BIT4_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * BANG_OLUFSEN_START_BIT4_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define BANG_OLUFSEN_START_BIT4_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * BANG_OLUFSEN_START_BIT4_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define BANG_OLUFSEN_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * BANG_OLUFSEN_PULSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define BANG_OLUFSEN_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * BANG_OLUFSEN_PULSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define BANG_OLUFSEN_1_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * BANG_OLUFSEN_1_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define BANG_OLUFSEN_1_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * BANG_OLUFSEN_1_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define BANG_OLUFSEN_0_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * BANG_OLUFSEN_0_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define BANG_OLUFSEN_0_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * BANG_OLUFSEN_0_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define BANG_OLUFSEN_R_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * BANG_OLUFSEN_R_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define BANG_OLUFSEN_R_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * BANG_OLUFSEN_R_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define BANG_OLUFSEN_TRAILER_BIT_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * BANG_OLUFSEN_TRAILER_BIT_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define BANG_OLUFSEN_TRAILER_BIT_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * BANG_OLUFSEN_TRAILER_BIT_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) + +#define IR60_TIMEOUT_LEN ((uint8_t)(F_INTERRUPTS * IR60_TIMEOUT_TIME * 0.5)) +#define GRUNDIG_NOKIA_IR60_START_BIT_LEN_MIN ((uint8_t)(F_INTERRUPTS * GRUNDIG_NOKIA_IR60_BIT_TIME * MIN_TOLERANCE_20 + 0.5) - 1) +#define GRUNDIG_NOKIA_IR60_START_BIT_LEN_MAX ((uint8_t)(F_INTERRUPTS * GRUNDIG_NOKIA_IR60_BIT_TIME * MAX_TOLERANCE_20 + 0.5) + 1) +#define GRUNDIG_NOKIA_IR60_BIT_LEN_MIN ((uint8_t)(F_INTERRUPTS * GRUNDIG_NOKIA_IR60_BIT_TIME * MIN_TOLERANCE_20 + 0.5) - 1) +#define GRUNDIG_NOKIA_IR60_BIT_LEN_MAX ((uint8_t)(F_INTERRUPTS * GRUNDIG_NOKIA_IR60_BIT_TIME * MAX_TOLERANCE_20 + 0.5) + 1) +#define GRUNDIG_NOKIA_IR60_PRE_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * GRUNDIG_NOKIA_IR60_PRE_PAUSE_TIME * MIN_TOLERANCE_20 + 0.5) + 1) +#define GRUNDIG_NOKIA_IR60_PRE_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * GRUNDIG_NOKIA_IR60_PRE_PAUSE_TIME * MAX_TOLERANCE_20 + 0.5) + 1) + +#define SIEMENS_OR_RUWIDO_START_BIT_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * SIEMENS_OR_RUWIDO_START_BIT_PULSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define SIEMENS_OR_RUWIDO_START_BIT_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * SIEMENS_OR_RUWIDO_START_BIT_PULSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define SIEMENS_OR_RUWIDO_START_BIT_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * SIEMENS_OR_RUWIDO_START_BIT_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define SIEMENS_OR_RUWIDO_START_BIT_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * SIEMENS_OR_RUWIDO_START_BIT_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define SIEMENS_OR_RUWIDO_BIT_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * SIEMENS_OR_RUWIDO_BIT_PULSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define SIEMENS_OR_RUWIDO_BIT_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * SIEMENS_OR_RUWIDO_BIT_PULSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define SIEMENS_OR_RUWIDO_BIT_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * SIEMENS_OR_RUWIDO_BIT_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define SIEMENS_OR_RUWIDO_BIT_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * SIEMENS_OR_RUWIDO_BIT_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) + +#define GRUNDIG2_START_BIT_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * GRUNDIG2_START_BIT_PULSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define GRUNDIG2_START_BIT_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * GRUNDIG2_START_BIT_PULSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define GRUNDIG2_START_BIT_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * GRUNDIG2_START_BIT_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define GRUNDIG2_START_BIT_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * GRUNDIG2_START_BIT_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define GRUNDIG2_BIT_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * GRUNDIG2_BIT_PULSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define GRUNDIG2_BIT_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * GRUNDIG2_BIT_PULSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define GRUNDIG2_BIT_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * GRUNDIG2_BIT_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define GRUNDIG2_BIT_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * GRUNDIG2_BIT_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) + +#define FDC_START_BIT_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * FDC_START_BIT_PULSE_TIME * MIN_TOLERANCE_05 + 0.5) - 1) // 5%: avoid conflict with NETBOX +#define FDC_START_BIT_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * FDC_START_BIT_PULSE_TIME * MAX_TOLERANCE_05 + 0.5)) +#define FDC_START_BIT_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * FDC_START_BIT_PAUSE_TIME * MIN_TOLERANCE_05 + 0.5) - 1) +#define FDC_START_BIT_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * FDC_START_BIT_PAUSE_TIME * MAX_TOLERANCE_05 + 0.5)) +#define FDC_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * FDC_PULSE_TIME * MIN_TOLERANCE_40 + 0.5) - 1) +#define FDC_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * FDC_PULSE_TIME * MAX_TOLERANCE_50 + 0.5) + 1) +#define FDC_1_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * FDC_1_PAUSE_TIME * MIN_TOLERANCE_20 + 0.5) - 1) +#define FDC_1_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * FDC_1_PAUSE_TIME * MAX_TOLERANCE_20 + 0.5) + 1) +#if 0 +#define FDC_0_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * FDC_0_PAUSE_TIME * MIN_TOLERANCE_40 + 0.5) - 1) // could be negative: 255 +#else +#define FDC_0_PAUSE_LEN_MIN (1) // simply use 1 +#endif +#define FDC_0_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * FDC_0_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) + +#define RCCAR_START_BIT_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * RCCAR_START_BIT_PULSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define RCCAR_START_BIT_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * RCCAR_START_BIT_PULSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define RCCAR_START_BIT_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * RCCAR_START_BIT_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define RCCAR_START_BIT_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * RCCAR_START_BIT_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define RCCAR_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * RCCAR_PULSE_TIME * MIN_TOLERANCE_20 + 0.5) - 1) +#define RCCAR_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * RCCAR_PULSE_TIME * MAX_TOLERANCE_20 + 0.5) + 1) +#define RCCAR_1_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * RCCAR_1_PAUSE_TIME * MIN_TOLERANCE_30 + 0.5) - 1) +#define RCCAR_1_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * RCCAR_1_PAUSE_TIME * MAX_TOLERANCE_30 + 0.5) + 1) +#define RCCAR_0_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * RCCAR_0_PAUSE_TIME * MIN_TOLERANCE_30 + 0.5) - 1) +#define RCCAR_0_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * RCCAR_0_PAUSE_TIME * MAX_TOLERANCE_30 + 0.5) + 1) + +#define JVC_START_BIT_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * JVC_START_BIT_PULSE_TIME * MIN_TOLERANCE_40 + 0.5) - 1) +#define JVC_START_BIT_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * JVC_START_BIT_PULSE_TIME * MAX_TOLERANCE_40 + 0.5) + 1) +#define JVC_REPEAT_START_BIT_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * (JVC_FRAME_REPEAT_PAUSE_TIME - IRMP_TIMEOUT_TIME) * MIN_TOLERANCE_40 + 0.5) - 1) // HACK! +#define JVC_REPEAT_START_BIT_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * (JVC_FRAME_REPEAT_PAUSE_TIME - IRMP_TIMEOUT_TIME) * MAX_TOLERANCE_70 + 0.5) - 1) // HACK! +#define JVC_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * JVC_PULSE_TIME * MIN_TOLERANCE_40 + 0.5) - 1) +#define JVC_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * JVC_PULSE_TIME * MAX_TOLERANCE_40 + 0.5) + 1) +#define JVC_1_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * JVC_1_PAUSE_TIME * MIN_TOLERANCE_40 + 0.5) - 1) +#define JVC_1_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * JVC_1_PAUSE_TIME * MAX_TOLERANCE_40 + 0.5) + 1) +#define JVC_0_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * JVC_0_PAUSE_TIME * MIN_TOLERANCE_40 + 0.5) - 1) +#define JVC_0_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * JVC_0_PAUSE_TIME * MAX_TOLERANCE_40 + 0.5) + 1) +// autodetect JVC repetition frame within 50 msec: +#define JVC_FRAME_REPEAT_PAUSE_LEN_MAX (uint16_t)(F_INTERRUPTS * JVC_FRAME_REPEAT_PAUSE_TIME * MAX_TOLERANCE_20 + 0.5) + +#define NIKON_START_BIT_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * NIKON_START_BIT_PULSE_TIME * MIN_TOLERANCE_20 + 0.5) - 1) +#define NIKON_START_BIT_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * NIKON_START_BIT_PULSE_TIME * MAX_TOLERANCE_20 + 0.5) + 1) +#define NIKON_START_BIT_PAUSE_LEN_MIN ((uint16_t)(F_INTERRUPTS * NIKON_START_BIT_PAUSE_TIME * MIN_TOLERANCE_20 + 0.5) - 1) +#define NIKON_START_BIT_PAUSE_LEN_MAX ((uint16_t)(F_INTERRUPTS * NIKON_START_BIT_PAUSE_TIME * MAX_TOLERANCE_20 + 0.5) + 1) +#define NIKON_REPEAT_START_BIT_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * NIKON_REPEAT_START_BIT_PAUSE_TIME * MIN_TOLERANCE_20 + 0.5) - 1) +#define NIKON_REPEAT_START_BIT_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * NIKON_REPEAT_START_BIT_PAUSE_TIME * MAX_TOLERANCE_20 + 0.5) + 1) +#define NIKON_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * NIKON_PULSE_TIME * MIN_TOLERANCE_20 + 0.5) - 1) +#define NIKON_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * NIKON_PULSE_TIME * MAX_TOLERANCE_20 + 0.5) + 1) +#define NIKON_1_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * NIKON_1_PAUSE_TIME * MIN_TOLERANCE_20 + 0.5) - 1) +#define NIKON_1_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * NIKON_1_PAUSE_TIME * MAX_TOLERANCE_20 + 0.5) + 1) +#define NIKON_0_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * NIKON_0_PAUSE_TIME * MIN_TOLERANCE_20 + 0.5) - 1) +#define NIKON_0_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * NIKON_0_PAUSE_TIME * MAX_TOLERANCE_20 + 0.5) + 1) +#define NIKON_FRAME_REPEAT_PAUSE_LEN_MAX (uint16_t)(F_INTERRUPTS * NIKON_FRAME_REPEAT_PAUSE_TIME * MAX_TOLERANCE_20 + 0.5) + +#define KATHREIN_START_BIT_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * KATHREIN_START_BIT_PULSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define KATHREIN_START_BIT_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * KATHREIN_START_BIT_PULSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define KATHREIN_START_BIT_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * KATHREIN_START_BIT_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define KATHREIN_START_BIT_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * KATHREIN_START_BIT_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define KATHREIN_1_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * KATHREIN_1_PULSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define KATHREIN_1_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * KATHREIN_1_PULSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define KATHREIN_1_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * KATHREIN_1_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define KATHREIN_1_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * KATHREIN_1_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define KATHREIN_0_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * KATHREIN_0_PULSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define KATHREIN_0_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * KATHREIN_0_PULSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define KATHREIN_0_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * KATHREIN_0_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define KATHREIN_0_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * KATHREIN_0_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define KATHREIN_SYNC_BIT_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * KATHREIN_SYNC_BIT_PAUSE_LEN_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define KATHREIN_SYNC_BIT_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * KATHREIN_SYNC_BIT_PAUSE_LEN_TIME * MAX_TOLERANCE_10 + 0.5) + 1) + +#define NETBOX_START_BIT_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * NETBOX_START_BIT_PULSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define NETBOX_START_BIT_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * NETBOX_START_BIT_PULSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define NETBOX_START_BIT_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * NETBOX_START_BIT_PAUSE_TIME * MIN_TOLERANCE_10 + 0.5) - 1) +#define NETBOX_START_BIT_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * NETBOX_START_BIT_PAUSE_TIME * MAX_TOLERANCE_10 + 0.5) + 1) +#define NETBOX_PULSE_LEN ((uint8_t)(F_INTERRUPTS * NETBOX_PULSE_TIME)) +#define NETBOX_PAUSE_LEN ((uint8_t)(F_INTERRUPTS * NETBOX_PAUSE_TIME)) +#define NETBOX_PULSE_REST_LEN ((uint8_t)(F_INTERRUPTS * NETBOX_PULSE_TIME / 4)) +#define NETBOX_PAUSE_REST_LEN ((uint8_t)(F_INTERRUPTS * NETBOX_PAUSE_TIME / 4)) + +#define LEGO_START_BIT_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * LEGO_START_BIT_PULSE_TIME * MIN_TOLERANCE_40 + 0.5) - 1) +#define LEGO_START_BIT_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * LEGO_START_BIT_PULSE_TIME * MAX_TOLERANCE_40 + 0.5) + 1) +#define LEGO_START_BIT_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * LEGO_START_BIT_PAUSE_TIME * MIN_TOLERANCE_40 + 0.5) - 1) +#define LEGO_START_BIT_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * LEGO_START_BIT_PAUSE_TIME * MAX_TOLERANCE_40 + 0.5) + 1) +#define LEGO_PULSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * LEGO_PULSE_TIME * MIN_TOLERANCE_40 + 0.5) - 1) +#define LEGO_PULSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * LEGO_PULSE_TIME * MAX_TOLERANCE_40 + 0.5) + 1) +#define LEGO_1_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * LEGO_1_PAUSE_TIME * MIN_TOLERANCE_40 + 0.5) - 1) +#define LEGO_1_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * LEGO_1_PAUSE_TIME * MAX_TOLERANCE_40 + 0.5) + 1) +#define LEGO_0_PAUSE_LEN_MIN ((uint8_t)(F_INTERRUPTS * LEGO_0_PAUSE_TIME * MIN_TOLERANCE_40 + 0.5) - 1) +#define LEGO_0_PAUSE_LEN_MAX ((uint8_t)(F_INTERRUPTS * LEGO_0_PAUSE_TIME * MAX_TOLERANCE_40 + 0.5) + 1) + +#define AUTO_FRAME_REPETITION_LEN (uint16_t)(F_INTERRUPTS * AUTO_FRAME_REPETITION_TIME + 0.5) // use uint16_t! + +#ifdef ANALYZE +#define ANALYZE_PUTCHAR(a) { if (! silent) { putchar (a); } } +#ifndef LIRC_IRMP +#define ANALYZE_ONLY_NORMAL_PUTCHAR(a) { if (! silent && !verbose) { putchar (a); } } +#else +#define ANALYZE_ONLY_NORMAL_PUTCHAR(a) +#endif +#define ANALYZE_PRINTF(...) { if (verbose) { printf (__VA_ARGS__); } } +#define ANALYZE_NEWLINE() { if (verbose) { putchar ('\n'); } } +static int silent = TRUE; +static int time_counter; +static int verbose; +#else +#define ANALYZE_PUTCHAR(a) +#define ANALYZE_ONLY_NORMAL_PUTCHAR(a) +#define ANALYZE_PRINTF(...) +#define ANALYZE_NEWLINE() +#endif + +#if IRMP_USE_CALLBACK == 1 +static void (*irmp_callback_ptr) (uint8_t); +#endif // IRMP_USE_CALLBACK == 1 + +/*--------------------------------------------------------------------------------------------------------------------------------------------------- + * Protocol names + *--------------------------------------------------------------------------------------------------------------------------------------------------- + */ +#if IRMP_PROTOCOL_NAMES == 1 +char * +irmp_protocol_names[IRMP_N_PROTOCOLS + 1] = +{ + "UNKNOWN", + "SIRCS", + "NEC", + "SAMSUNG", + "MATSUSH", + "KASEIKYO", + "RECS80", + "RC5", + "DENON", + "RC6", + "SAMSG32", + "APPLE", + "RECS80EX", + "NUBERT", + "BANG OLU", + "GRUNDIG", + "NOKIA", + "SIEMENS", + "FDC", + "RCCAR", + "JVC", + "RC6A", + "NIKON", + "RUWIDO", + "IR60", + "KATHREIN", + "NETBOX", + "NEC16", + "NEC42", + "LEGO", + "THOMSON" +}; +#endif + +/*--------------------------------------------------------------------------------------------------------------------------------------------------- + * Logging + *--------------------------------------------------------------------------------------------------------------------------------------------------- + */ +#if IRMP_LOGGING == 1 // logging via UART + +#if IRMP_EXT_LOGGING == 1 // use external logging +#include "irmpextlog.h" +#else // normal UART log (IRMP_EXT_LOGGING == 0) +#define BAUD 9600L +#include + +#ifdef UBRR0H + +#define UART0_UBRRH UBRR0H +#define UART0_UBRRL UBRR0L +#define UART0_UCSRA UCSR0A +#define UART0_UCSRB UCSR0B +#define UART0_UCSRC UCSR0C +#define UART0_UDRE_BIT_VALUE (1< ENDBITS) + { // if stop condition is true, output on uart + uint16_t i; + + for (i = 0; i < STARTCYCLES; i++) + { + irmp_uart_putc ('0'); // the ignored starting zeros + } + + for (i = 0; i < (buf_idx - ENDBITS + 20) / 8; i++) // transform bitset into uart chars + { + uint8_t d = buf[i]; + uint8_t j; + + for (j = 0; j < 8; j++) + { + irmp_uart_putc ((d & 1) + '0'); + d >>= 1; + } + } + + irmp_uart_putc ('\n'); + buf_idx = 0; + } + } + else + { + cnt = 0; + } + } + } +} + +#else +#define irmp_log(val) +#endif //IRMP_LOGGING + +typedef struct +{ + uint8_t protocol; // ir protocol + uint8_t pulse_1_len_min; // minimum length of pulse with bit value 1 + uint8_t pulse_1_len_max; // maximum length of pulse with bit value 1 + uint8_t pause_1_len_min; // minimum length of pause with bit value 1 + uint8_t pause_1_len_max; // maximum length of pause with bit value 1 + uint8_t pulse_0_len_min; // minimum length of pulse with bit value 0 + uint8_t pulse_0_len_max; // maximum length of pulse with bit value 0 + uint8_t pause_0_len_min; // minimum length of pause with bit value 0 + uint8_t pause_0_len_max; // maximum length of pause with bit value 0 + uint8_t address_offset; // address offset + uint8_t address_end; // end of address + uint8_t command_offset; // command offset + uint8_t command_end; // end of command + uint8_t complete_len; // complete length of frame + uint8_t stop_bit; // flag: frame has stop bit + uint8_t lsb_first; // flag: LSB first + uint8_t flags; // some flags +} IRMP_PARAMETER; + +#if IRMP_SUPPORT_SIRCS_PROTOCOL == 1 + +static const PROGMEM IRMP_PARAMETER sircs_param = +{ + IRMP_SIRCS_PROTOCOL, // protocol: ir protocol + SIRCS_1_PULSE_LEN_MIN, // pulse_1_len_min: minimum length of pulse with bit value 1 + SIRCS_1_PULSE_LEN_MAX, // pulse_1_len_max: maximum length of pulse with bit value 1 + SIRCS_PAUSE_LEN_MIN, // pause_1_len_min: minimum length of pause with bit value 1 + SIRCS_PAUSE_LEN_MAX, // pause_1_len_max: maximum length of pause with bit value 1 + SIRCS_0_PULSE_LEN_MIN, // pulse_0_len_min: minimum length of pulse with bit value 0 + SIRCS_0_PULSE_LEN_MAX, // pulse_0_len_max: maximum length of pulse with bit value 0 + SIRCS_PAUSE_LEN_MIN, // pause_0_len_min: minimum length of pause with bit value 0 + SIRCS_PAUSE_LEN_MAX, // pause_0_len_max: maximum length of pause with bit value 0 + SIRCS_ADDRESS_OFFSET, // address_offset: address offset + SIRCS_ADDRESS_OFFSET + SIRCS_ADDRESS_LEN, // address_end: end of address + SIRCS_COMMAND_OFFSET, // command_offset: command offset + SIRCS_COMMAND_OFFSET + SIRCS_COMMAND_LEN, // command_end: end of command + SIRCS_COMPLETE_DATA_LEN, // complete_len: complete length of frame + SIRCS_STOP_BIT, // stop_bit: flag: frame has stop bit + SIRCS_LSB, // lsb_first: flag: LSB first + SIRCS_FLAGS // flags: some flags +}; + +#endif + +#if IRMP_SUPPORT_NEC_PROTOCOL == 1 + +static const PROGMEM IRMP_PARAMETER nec_param = +{ + IRMP_NEC_PROTOCOL, // protocol: ir protocol + NEC_PULSE_LEN_MIN, // pulse_1_len_min: minimum length of pulse with bit value 1 + NEC_PULSE_LEN_MAX, // pulse_1_len_max: maximum length of pulse with bit value 1 + NEC_1_PAUSE_LEN_MIN, // pause_1_len_min: minimum length of pause with bit value 1 + NEC_1_PAUSE_LEN_MAX, // pause_1_len_max: maximum length of pause with bit value 1 + NEC_PULSE_LEN_MIN, // pulse_0_len_min: minimum length of pulse with bit value 0 + NEC_PULSE_LEN_MAX, // pulse_0_len_max: maximum length of pulse with bit value 0 + NEC_0_PAUSE_LEN_MIN, // pause_0_len_min: minimum length of pause with bit value 0 + NEC_0_PAUSE_LEN_MAX, // pause_0_len_max: maximum length of pause with bit value 0 + NEC_ADDRESS_OFFSET, // address_offset: address offset + NEC_ADDRESS_OFFSET + NEC_ADDRESS_LEN, // address_end: end of address + NEC_COMMAND_OFFSET, // command_offset: command offset + NEC_COMMAND_OFFSET + NEC_COMMAND_LEN, // command_end: end of command + NEC_COMPLETE_DATA_LEN, // complete_len: complete length of frame + NEC_STOP_BIT, // stop_bit: flag: frame has stop bit + NEC_LSB, // lsb_first: flag: LSB first + NEC_FLAGS // flags: some flags +}; + +static const PROGMEM IRMP_PARAMETER nec_rep_param = +{ + IRMP_NEC_PROTOCOL, // protocol: ir protocol + NEC_PULSE_LEN_MIN, // pulse_1_len_min: minimum length of pulse with bit value 1 + NEC_PULSE_LEN_MAX, // pulse_1_len_max: maximum length of pulse with bit value 1 + NEC_1_PAUSE_LEN_MIN, // pause_1_len_min: minimum length of pause with bit value 1 + NEC_1_PAUSE_LEN_MAX, // pause_1_len_max: maximum length of pause with bit value 1 + NEC_PULSE_LEN_MIN, // pulse_0_len_min: minimum length of pulse with bit value 0 + NEC_PULSE_LEN_MAX, // pulse_0_len_max: maximum length of pulse with bit value 0 + NEC_0_PAUSE_LEN_MIN, // pause_0_len_min: minimum length of pause with bit value 0 + NEC_0_PAUSE_LEN_MAX, // pause_0_len_max: maximum length of pause with bit value 0 + 0, // address_offset: address offset + 0, // address_end: end of address + 0, // command_offset: command offset + 0, // command_end: end of command + 0, // complete_len: complete length of frame + NEC_STOP_BIT, // stop_bit: flag: frame has stop bit + NEC_LSB, // lsb_first: flag: LSB first + NEC_FLAGS // flags: some flags +}; + +#endif + +#if IRMP_SUPPORT_NEC42_PROTOCOL == 1 + +static const PROGMEM IRMP_PARAMETER nec42_param = +{ + IRMP_NEC42_PROTOCOL, // protocol: ir protocol + NEC_PULSE_LEN_MIN, // pulse_1_len_min: minimum length of pulse with bit value 1 + NEC_PULSE_LEN_MAX, // pulse_1_len_max: maximum length of pulse with bit value 1 + NEC_1_PAUSE_LEN_MIN, // pause_1_len_min: minimum length of pause with bit value 1 + NEC_1_PAUSE_LEN_MAX, // pause_1_len_max: maximum length of pause with bit value 1 + NEC_PULSE_LEN_MIN, // pulse_0_len_min: minimum length of pulse with bit value 0 + NEC_PULSE_LEN_MAX, // pulse_0_len_max: maximum length of pulse with bit value 0 + NEC_0_PAUSE_LEN_MIN, // pause_0_len_min: minimum length of pause with bit value 0 + NEC_0_PAUSE_LEN_MAX, // pause_0_len_max: maximum length of pause with bit value 0 + NEC42_ADDRESS_OFFSET, // address_offset: address offset + NEC42_ADDRESS_OFFSET + NEC42_ADDRESS_LEN, // address_end: end of address + NEC42_COMMAND_OFFSET, // command_offset: command offset + NEC42_COMMAND_OFFSET + NEC42_COMMAND_LEN, // command_end: end of command + NEC42_COMPLETE_DATA_LEN, // complete_len: complete length of frame + NEC_STOP_BIT, // stop_bit: flag: frame has stop bit + NEC_LSB, // lsb_first: flag: LSB first + NEC_FLAGS // flags: some flags +}; + +#endif + +#if IRMP_SUPPORT_SAMSUNG_PROTOCOL == 1 + +static const PROGMEM IRMP_PARAMETER samsung_param = +{ + IRMP_SAMSUNG_PROTOCOL, // protocol: ir protocol + SAMSUNG_PULSE_LEN_MIN, // pulse_1_len_min: minimum length of pulse with bit value 1 + SAMSUNG_PULSE_LEN_MAX, // pulse_1_len_max: maximum length of pulse with bit value 1 + SAMSUNG_1_PAUSE_LEN_MIN, // pause_1_len_min: minimum length of pause with bit value 1 + SAMSUNG_1_PAUSE_LEN_MAX, // pause_1_len_max: maximum length of pause with bit value 1 + SAMSUNG_PULSE_LEN_MIN, // pulse_0_len_min: minimum length of pulse with bit value 0 + SAMSUNG_PULSE_LEN_MAX, // pulse_0_len_max: maximum length of pulse with bit value 0 + SAMSUNG_0_PAUSE_LEN_MIN, // pause_0_len_min: minimum length of pause with bit value 0 + SAMSUNG_0_PAUSE_LEN_MAX, // pause_0_len_max: maximum length of pause with bit value 0 + SAMSUNG_ADDRESS_OFFSET, // address_offset: address offset + SAMSUNG_ADDRESS_OFFSET + SAMSUNG_ADDRESS_LEN, // address_end: end of address + SAMSUNG_COMMAND_OFFSET, // command_offset: command offset + SAMSUNG_COMMAND_OFFSET + SAMSUNG_COMMAND_LEN, // command_end: end of command + SAMSUNG_COMPLETE_DATA_LEN, // complete_len: complete length of frame + SAMSUNG_STOP_BIT, // stop_bit: flag: frame has stop bit + SAMSUNG_LSB, // lsb_first: flag: LSB first + SAMSUNG_FLAGS // flags: some flags +}; + +#endif + +#if IRMP_SUPPORT_MATSUSHITA_PROTOCOL == 1 + +static const PROGMEM IRMP_PARAMETER matsushita_param = +{ + IRMP_MATSUSHITA_PROTOCOL, // protocol: ir protocol + MATSUSHITA_PULSE_LEN_MIN, // pulse_1_len_min: minimum length of pulse with bit value 1 + MATSUSHITA_PULSE_LEN_MAX, // pulse_1_len_max: maximum length of pulse with bit value 1 + MATSUSHITA_1_PAUSE_LEN_MIN, // pause_1_len_min: minimum length of pause with bit value 1 + MATSUSHITA_1_PAUSE_LEN_MAX, // pause_1_len_max: maximum length of pause with bit value 1 + MATSUSHITA_PULSE_LEN_MIN, // pulse_0_len_min: minimum length of pulse with bit value 0 + MATSUSHITA_PULSE_LEN_MAX, // pulse_0_len_max: maximum length of pulse with bit value 0 + MATSUSHITA_0_PAUSE_LEN_MIN, // pause_0_len_min: minimum length of pause with bit value 0 + MATSUSHITA_0_PAUSE_LEN_MAX, // pause_0_len_max: maximum length of pause with bit value 0 + MATSUSHITA_ADDRESS_OFFSET, // address_offset: address offset + MATSUSHITA_ADDRESS_OFFSET + MATSUSHITA_ADDRESS_LEN, // address_end: end of address + MATSUSHITA_COMMAND_OFFSET, // command_offset: command offset + MATSUSHITA_COMMAND_OFFSET + MATSUSHITA_COMMAND_LEN, // command_end: end of command + MATSUSHITA_COMPLETE_DATA_LEN, // complete_len: complete length of frame + MATSUSHITA_STOP_BIT, // stop_bit: flag: frame has stop bit + MATSUSHITA_LSB, // lsb_first: flag: LSB first + MATSUSHITA_FLAGS // flags: some flags +}; + +#endif + +#if IRMP_SUPPORT_KASEIKYO_PROTOCOL == 1 + +static const PROGMEM IRMP_PARAMETER kaseikyo_param = +{ + IRMP_KASEIKYO_PROTOCOL, // protocol: ir protocol + KASEIKYO_PULSE_LEN_MIN, // pulse_1_len_min: minimum length of pulse with bit value 1 + KASEIKYO_PULSE_LEN_MAX, // pulse_1_len_max: maximum length of pulse with bit value 1 + KASEIKYO_1_PAUSE_LEN_MIN, // pause_1_len_min: minimum length of pause with bit value 1 + KASEIKYO_1_PAUSE_LEN_MAX, // pause_1_len_max: maximum length of pause with bit value 1 + KASEIKYO_PULSE_LEN_MIN, // pulse_0_len_min: minimum length of pulse with bit value 0 + KASEIKYO_PULSE_LEN_MAX, // pulse_0_len_max: maximum length of pulse with bit value 0 + KASEIKYO_0_PAUSE_LEN_MIN, // pause_0_len_min: minimum length of pause with bit value 0 + KASEIKYO_0_PAUSE_LEN_MAX, // pause_0_len_max: maximum length of pause with bit value 0 + KASEIKYO_ADDRESS_OFFSET, // address_offset: address offset + KASEIKYO_ADDRESS_OFFSET + KASEIKYO_ADDRESS_LEN, // address_end: end of address + KASEIKYO_COMMAND_OFFSET, // command_offset: command offset + KASEIKYO_COMMAND_OFFSET + KASEIKYO_COMMAND_LEN, // command_end: end of command + KASEIKYO_COMPLETE_DATA_LEN, // complete_len: complete length of frame + KASEIKYO_STOP_BIT, // stop_bit: flag: frame has stop bit + KASEIKYO_LSB, // lsb_first: flag: LSB first + KASEIKYO_FLAGS // flags: some flags +}; + +#endif + +#if IRMP_SUPPORT_RECS80_PROTOCOL == 1 + +static const PROGMEM IRMP_PARAMETER recs80_param = +{ + IRMP_RECS80_PROTOCOL, // protocol: ir protocol + RECS80_PULSE_LEN_MIN, // pulse_1_len_min: minimum length of pulse with bit value 1 + RECS80_PULSE_LEN_MAX, // pulse_1_len_max: maximum length of pulse with bit value 1 + RECS80_1_PAUSE_LEN_MIN, // pause_1_len_min: minimum length of pause with bit value 1 + RECS80_1_PAUSE_LEN_MAX, // pause_1_len_max: maximum length of pause with bit value 1 + RECS80_PULSE_LEN_MIN, // pulse_0_len_min: minimum length of pulse with bit value 0 + RECS80_PULSE_LEN_MAX, // pulse_0_len_max: maximum length of pulse with bit value 0 + RECS80_0_PAUSE_LEN_MIN, // pause_0_len_min: minimum length of pause with bit value 0 + RECS80_0_PAUSE_LEN_MAX, // pause_0_len_max: maximum length of pause with bit value 0 + RECS80_ADDRESS_OFFSET, // address_offset: address offset + RECS80_ADDRESS_OFFSET + RECS80_ADDRESS_LEN, // address_end: end of address + RECS80_COMMAND_OFFSET, // command_offset: command offset + RECS80_COMMAND_OFFSET + RECS80_COMMAND_LEN, // command_end: end of command + RECS80_COMPLETE_DATA_LEN, // complete_len: complete length of frame + RECS80_STOP_BIT, // stop_bit: flag: frame has stop bit + RECS80_LSB, // lsb_first: flag: LSB first + RECS80_FLAGS // flags: some flags +}; + +#endif + +#if IRMP_SUPPORT_RC5_PROTOCOL == 1 + +static const PROGMEM IRMP_PARAMETER rc5_param = +{ + IRMP_RC5_PROTOCOL, // protocol: ir protocol + RC5_BIT_LEN_MIN, // pulse_1_len_min: here: minimum length of short pulse + RC5_BIT_LEN_MAX, // pulse_1_len_max: here: maximum length of short pulse + RC5_BIT_LEN_MIN, // pause_1_len_min: here: minimum length of short pause + RC5_BIT_LEN_MAX, // pause_1_len_max: here: maximum length of short pause + 0, // pulse_0_len_min: here: not used + 0, // pulse_0_len_max: here: not used + 0, // pause_0_len_min: here: not used + 0, // pause_0_len_max: here: not used + RC5_ADDRESS_OFFSET, // address_offset: address offset + RC5_ADDRESS_OFFSET + RC5_ADDRESS_LEN, // address_end: end of address + RC5_COMMAND_OFFSET, // command_offset: command offset + RC5_COMMAND_OFFSET + RC5_COMMAND_LEN, // command_end: end of command + RC5_COMPLETE_DATA_LEN, // complete_len: complete length of frame + RC5_STOP_BIT, // stop_bit: flag: frame has stop bit + RC5_LSB, // lsb_first: flag: LSB first + RC5_FLAGS // flags: some flags +}; + +#endif + +#if IRMP_SUPPORT_DENON_PROTOCOL == 1 + +static const PROGMEM IRMP_PARAMETER denon_param = +{ + IRMP_DENON_PROTOCOL, // protocol: ir protocol + DENON_PULSE_LEN_MIN, // pulse_1_len_min: minimum length of pulse with bit value 1 + DENON_PULSE_LEN_MAX, // pulse_1_len_max: maximum length of pulse with bit value 1 + DENON_1_PAUSE_LEN_MIN, // pause_1_len_min: minimum length of pause with bit value 1 + DENON_1_PAUSE_LEN_MAX, // pause_1_len_max: maximum length of pause with bit value 1 + DENON_PULSE_LEN_MIN, // pulse_0_len_min: minimum length of pulse with bit value 0 + DENON_PULSE_LEN_MAX, // pulse_0_len_max: maximum length of pulse with bit value 0 + DENON_0_PAUSE_LEN_MIN, // pause_0_len_min: minimum length of pause with bit value 0 + DENON_0_PAUSE_LEN_MAX, // pause_0_len_max: maximum length of pause with bit value 0 + DENON_ADDRESS_OFFSET, // address_offset: address offset + DENON_ADDRESS_OFFSET + DENON_ADDRESS_LEN, // address_end: end of address + DENON_COMMAND_OFFSET, // command_offset: command offset + DENON_COMMAND_OFFSET + DENON_COMMAND_LEN, // command_end: end of command + DENON_COMPLETE_DATA_LEN, // complete_len: complete length of frame + DENON_STOP_BIT, // stop_bit: flag: frame has stop bit + DENON_LSB, // lsb_first: flag: LSB first + DENON_FLAGS // flags: some flags +}; + +#endif + +#if IRMP_SUPPORT_RC6_PROTOCOL == 1 + +static const PROGMEM IRMP_PARAMETER rc6_param = +{ + IRMP_RC6_PROTOCOL, // protocol: ir protocol + + RC6_BIT_PULSE_LEN_MIN, // pulse_1_len_min: here: minimum length of short pulse + RC6_BIT_PULSE_LEN_MAX, // pulse_1_len_max: here: maximum length of short pulse + RC6_BIT_PAUSE_LEN_MIN, // pause_1_len_min: here: minimum length of short pause + RC6_BIT_PAUSE_LEN_MAX, // pause_1_len_max: here: maximum length of short pause + 0, // pulse_0_len_min: here: not used + 0, // pulse_0_len_max: here: not used + 0, // pause_0_len_min: here: not used + 0, // pause_0_len_max: here: not used + RC6_ADDRESS_OFFSET, // address_offset: address offset + RC6_ADDRESS_OFFSET + RC6_ADDRESS_LEN, // address_end: end of address + RC6_COMMAND_OFFSET, // command_offset: command offset + RC6_COMMAND_OFFSET + RC6_COMMAND_LEN, // command_end: end of command + RC6_COMPLETE_DATA_LEN_SHORT, // complete_len: complete length of frame + RC6_STOP_BIT, // stop_bit: flag: frame has stop bit + RC6_LSB, // lsb_first: flag: LSB first + RC6_FLAGS // flags: some flags +}; + +#endif + +#if IRMP_SUPPORT_RECS80EXT_PROTOCOL == 1 + +static const PROGMEM IRMP_PARAMETER recs80ext_param = +{ + IRMP_RECS80EXT_PROTOCOL, // protocol: ir protocol + RECS80EXT_PULSE_LEN_MIN, // pulse_1_len_min: minimum length of pulse with bit value 1 + RECS80EXT_PULSE_LEN_MAX, // pulse_1_len_max: maximum length of pulse with bit value 1 + RECS80EXT_1_PAUSE_LEN_MIN, // pause_1_len_min: minimum length of pause with bit value 1 + RECS80EXT_1_PAUSE_LEN_MAX, // pause_1_len_max: maximum length of pause with bit value 1 + RECS80EXT_PULSE_LEN_MIN, // pulse_0_len_min: minimum length of pulse with bit value 0 + RECS80EXT_PULSE_LEN_MAX, // pulse_0_len_max: maximum length of pulse with bit value 0 + RECS80EXT_0_PAUSE_LEN_MIN, // pause_0_len_min: minimum length of pause with bit value 0 + RECS80EXT_0_PAUSE_LEN_MAX, // pause_0_len_max: maximum length of pause with bit value 0 + RECS80EXT_ADDRESS_OFFSET, // address_offset: address offset + RECS80EXT_ADDRESS_OFFSET + RECS80EXT_ADDRESS_LEN, // address_end: end of address + RECS80EXT_COMMAND_OFFSET, // command_offset: command offset + RECS80EXT_COMMAND_OFFSET + RECS80EXT_COMMAND_LEN, // command_end: end of command + RECS80EXT_COMPLETE_DATA_LEN, // complete_len: complete length of frame + RECS80EXT_STOP_BIT, // stop_bit: flag: frame has stop bit + RECS80EXT_LSB, // lsb_first: flag: LSB first + RECS80EXT_FLAGS // flags: some flags +}; + +#endif + +#if IRMP_SUPPORT_NUBERT_PROTOCOL == 1 + +static const PROGMEM IRMP_PARAMETER nubert_param = +{ + IRMP_NUBERT_PROTOCOL, // protocol: ir protocol + NUBERT_1_PULSE_LEN_MIN, // pulse_1_len_min: minimum length of pulse with bit value 1 + NUBERT_1_PULSE_LEN_MAX, // pulse_1_len_max: maximum length of pulse with bit value 1 + NUBERT_1_PAUSE_LEN_MIN, // pause_1_len_min: minimum length of pause with bit value 1 + NUBERT_1_PAUSE_LEN_MAX, // pause_1_len_max: maximum length of pause with bit value 1 + NUBERT_0_PULSE_LEN_MIN, // pulse_0_len_min: minimum length of pulse with bit value 0 + NUBERT_0_PULSE_LEN_MAX, // pulse_0_len_max: maximum length of pulse with bit value 0 + NUBERT_0_PAUSE_LEN_MIN, // pause_0_len_min: minimum length of pause with bit value 0 + NUBERT_0_PAUSE_LEN_MAX, // pause_0_len_max: maximum length of pause with bit value 0 + NUBERT_ADDRESS_OFFSET, // address_offset: address offset + NUBERT_ADDRESS_OFFSET + NUBERT_ADDRESS_LEN, // address_end: end of address + NUBERT_COMMAND_OFFSET, // command_offset: command offset + NUBERT_COMMAND_OFFSET + NUBERT_COMMAND_LEN, // command_end: end of command + NUBERT_COMPLETE_DATA_LEN, // complete_len: complete length of frame + NUBERT_STOP_BIT, // stop_bit: flag: frame has stop bit + NUBERT_LSB, // lsb_first: flag: LSB first + NUBERT_FLAGS // flags: some flags +}; + +#endif + +#if IRMP_SUPPORT_BANG_OLUFSEN_PROTOCOL == 1 + +static const PROGMEM IRMP_PARAMETER bang_olufsen_param = +{ + IRMP_BANG_OLUFSEN_PROTOCOL, // protocol: ir protocol + BANG_OLUFSEN_PULSE_LEN_MIN, // pulse_1_len_min: minimum length of pulse with bit value 1 + BANG_OLUFSEN_PULSE_LEN_MAX, // pulse_1_len_max: maximum length of pulse with bit value 1 + BANG_OLUFSEN_1_PAUSE_LEN_MIN, // pause_1_len_min: minimum length of pause with bit value 1 + BANG_OLUFSEN_1_PAUSE_LEN_MAX, // pause_1_len_max: maximum length of pause with bit value 1 + BANG_OLUFSEN_PULSE_LEN_MIN, // pulse_0_len_min: minimum length of pulse with bit value 0 + BANG_OLUFSEN_PULSE_LEN_MAX, // pulse_0_len_max: maximum length of pulse with bit value 0 + BANG_OLUFSEN_0_PAUSE_LEN_MIN, // pause_0_len_min: minimum length of pause with bit value 0 + BANG_OLUFSEN_0_PAUSE_LEN_MAX, // pause_0_len_max: maximum length of pause with bit value 0 + BANG_OLUFSEN_ADDRESS_OFFSET, // address_offset: address offset + BANG_OLUFSEN_ADDRESS_OFFSET + BANG_OLUFSEN_ADDRESS_LEN, // address_end: end of address + BANG_OLUFSEN_COMMAND_OFFSET, // command_offset: command offset + BANG_OLUFSEN_COMMAND_OFFSET + BANG_OLUFSEN_COMMAND_LEN, // command_end: end of command + BANG_OLUFSEN_COMPLETE_DATA_LEN, // complete_len: complete length of frame + BANG_OLUFSEN_STOP_BIT, // stop_bit: flag: frame has stop bit + BANG_OLUFSEN_LSB, // lsb_first: flag: LSB first + BANG_OLUFSEN_FLAGS // flags: some flags +}; + +#endif + +#if IRMP_SUPPORT_GRUNDIG_NOKIA_IR60_PROTOCOL == 1 + +static uint8_t first_bit; + +static const PROGMEM IRMP_PARAMETER grundig_param = +{ + IRMP_GRUNDIG_PROTOCOL, // protocol: ir protocol + + GRUNDIG_NOKIA_IR60_BIT_LEN_MIN, // pulse_1_len_min: here: minimum length of short pulse + GRUNDIG_NOKIA_IR60_BIT_LEN_MAX, // pulse_1_len_max: here: maximum length of short pulse + GRUNDIG_NOKIA_IR60_BIT_LEN_MIN, // pause_1_len_min: here: minimum length of short pause + GRUNDIG_NOKIA_IR60_BIT_LEN_MAX, // pause_1_len_max: here: maximum length of short pause + 0, // pulse_0_len_min: here: not used + 0, // pulse_0_len_max: here: not used + 0, // pause_0_len_min: here: not used + 0, // pause_0_len_max: here: not used + GRUNDIG_ADDRESS_OFFSET, // address_offset: address offset + GRUNDIG_ADDRESS_OFFSET + GRUNDIG_ADDRESS_LEN, // address_end: end of address + GRUNDIG_COMMAND_OFFSET, // command_offset: command offset + GRUNDIG_COMMAND_OFFSET + GRUNDIG_COMMAND_LEN + 1, // command_end: end of command (USE 1 bit MORE to STORE NOKIA DATA!) + NOKIA_COMPLETE_DATA_LEN, // complete_len: complete length of frame, here: NOKIA instead of GRUNDIG! + GRUNDIG_NOKIA_IR60_STOP_BIT, // stop_bit: flag: frame has stop bit + GRUNDIG_NOKIA_IR60_LSB, // lsb_first: flag: LSB first + GRUNDIG_NOKIA_IR60_FLAGS // flags: some flags +}; + +#endif + +#if IRMP_SUPPORT_SIEMENS_OR_RUWIDO_PROTOCOL == 1 + +static const PROGMEM IRMP_PARAMETER ruwido_param = +{ + IRMP_RUWIDO_PROTOCOL, // protocol: ir protocol + SIEMENS_OR_RUWIDO_BIT_PULSE_LEN_MIN, // pulse_1_len_min: here: minimum length of short pulse + SIEMENS_OR_RUWIDO_BIT_PULSE_LEN_MAX, // pulse_1_len_max: here: maximum length of short pulse + SIEMENS_OR_RUWIDO_BIT_PAUSE_LEN_MIN, // pause_1_len_min: here: minimum length of short pause + SIEMENS_OR_RUWIDO_BIT_PAUSE_LEN_MAX, // pause_1_len_max: here: maximum length of short pause + 0, // pulse_0_len_min: here: not used + 0, // pulse_0_len_max: here: not used + 0, // pause_0_len_min: here: not used + 0, // pause_0_len_max: here: not used + RUWIDO_ADDRESS_OFFSET, // address_offset: address offset + RUWIDO_ADDRESS_OFFSET + RUWIDO_ADDRESS_LEN, // address_end: end of address + RUWIDO_COMMAND_OFFSET, // command_offset: command offset + RUWIDO_COMMAND_OFFSET + RUWIDO_COMMAND_LEN, // command_end: end of command + SIEMENS_COMPLETE_DATA_LEN, // complete_len: complete length of frame, here: SIEMENS instead of RUWIDO! + SIEMENS_OR_RUWIDO_STOP_BIT, // stop_bit: flag: frame has stop bit + SIEMENS_OR_RUWIDO_LSB, // lsb_first: flag: LSB first + SIEMENS_OR_RUWIDO_FLAGS // flags: some flags +}; + +#endif + +#if IRMP_SUPPORT_GRUNDIG2_PROTOCOL == 1 + +static const PROGMEM IRMP_PARAMETER grundig2_param = +{ + IRMP_GRUNDIG2_PROTOCOL, // protocol: ir protocol + GRUNDIG2_BIT_PULSE_LEN_MIN, // pulse_1_len_min: here: minimum length of short pulse + GRUNDIG2_BIT_PULSE_LEN_MAX, // pulse_1_len_max: here: maximum length of short pulse + GRUNDIG2_BIT_PAUSE_LEN_MIN, // pause_1_len_min: here: minimum length of short pause + GRUNDIG2_BIT_PAUSE_LEN_MAX, // pause_1_len_max: here: maximum length of short pause + 0, // pulse_0_len_min: here: not used + 0, // pulse_0_len_max: here: not used + 0, // pause_0_len_min: here: not used + 0, // pause_0_len_max: here: not used + GRUNDIG2_ADDRESS_OFFSET, // address_offset: address offset + GRUNDIG2_ADDRESS_OFFSET + GRUNDIG2_ADDRESS_LEN, // address_end: end of address + GRUNDIG2_COMMAND_OFFSET, // command_offset: command offset + GRUNDIG2_COMMAND_OFFSET + GRUNDIG2_COMMAND_LEN, // command_end: end of command + GRUNDIG2_COMPLETE_DATA_LEN, // complete_len: complete length of frame + GRUNDIG2_STOP_BIT, // stop_bit: flag: frame has stop bit + GRUNDIG2_LSB, // lsb_first: flag: LSB first + GRUNDIG2_FLAGS // flags: some flags +}; + +#endif + +#if IRMP_SUPPORT_FDC_PROTOCOL == 1 + +static const PROGMEM IRMP_PARAMETER fdc_param = +{ + IRMP_FDC_PROTOCOL, // protocol: ir protocol + FDC_PULSE_LEN_MIN, // pulse_1_len_min: minimum length of pulse with bit value 1 + FDC_PULSE_LEN_MAX, // pulse_1_len_max: maximum length of pulse with bit value 1 + FDC_1_PAUSE_LEN_MIN, // pause_1_len_min: minimum length of pause with bit value 1 + FDC_1_PAUSE_LEN_MAX, // pause_1_len_max: maximum length of pause with bit value 1 + FDC_PULSE_LEN_MIN, // pulse_0_len_min: minimum length of pulse with bit value 0 + FDC_PULSE_LEN_MAX, // pulse_0_len_max: maximum length of pulse with bit value 0 + FDC_0_PAUSE_LEN_MIN, // pause_0_len_min: minimum length of pause with bit value 0 + FDC_0_PAUSE_LEN_MAX, // pause_0_len_max: maximum length of pause with bit value 0 + FDC_ADDRESS_OFFSET, // address_offset: address offset + FDC_ADDRESS_OFFSET + FDC_ADDRESS_LEN, // address_end: end of address + FDC_COMMAND_OFFSET, // command_offset: command offset + FDC_COMMAND_OFFSET + FDC_COMMAND_LEN, // command_end: end of command + FDC_COMPLETE_DATA_LEN, // complete_len: complete length of frame + FDC_STOP_BIT, // stop_bit: flag: frame has stop bit + FDC_LSB, // lsb_first: flag: LSB first + FDC_FLAGS // flags: some flags +}; + +#endif + +#if IRMP_SUPPORT_RCCAR_PROTOCOL == 1 + +static const PROGMEM IRMP_PARAMETER rccar_param = +{ + IRMP_RCCAR_PROTOCOL, // protocol: ir protocol + RCCAR_PULSE_LEN_MIN, // pulse_1_len_min: minimum length of pulse with bit value 1 + RCCAR_PULSE_LEN_MAX, // pulse_1_len_max: maximum length of pulse with bit value 1 + RCCAR_1_PAUSE_LEN_MIN, // pause_1_len_min: minimum length of pause with bit value 1 + RCCAR_1_PAUSE_LEN_MAX, // pause_1_len_max: maximum length of pause with bit value 1 + RCCAR_PULSE_LEN_MIN, // pulse_0_len_min: minimum length of pulse with bit value 0 + RCCAR_PULSE_LEN_MAX, // pulse_0_len_max: maximum length of pulse with bit value 0 + RCCAR_0_PAUSE_LEN_MIN, // pause_0_len_min: minimum length of pause with bit value 0 + RCCAR_0_PAUSE_LEN_MAX, // pause_0_len_max: maximum length of pause with bit value 0 + RCCAR_ADDRESS_OFFSET, // address_offset: address offset + RCCAR_ADDRESS_OFFSET + RCCAR_ADDRESS_LEN, // address_end: end of address + RCCAR_COMMAND_OFFSET, // command_offset: command offset + RCCAR_COMMAND_OFFSET + RCCAR_COMMAND_LEN, // command_end: end of command + RCCAR_COMPLETE_DATA_LEN, // complete_len: complete length of frame + RCCAR_STOP_BIT, // stop_bit: flag: frame has stop bit + RCCAR_LSB, // lsb_first: flag: LSB first + RCCAR_FLAGS // flags: some flags +}; + +#endif + +#if IRMP_SUPPORT_NIKON_PROTOCOL == 1 + +static const PROGMEM IRMP_PARAMETER nikon_param = +{ + IRMP_NIKON_PROTOCOL, // protocol: ir protocol + NIKON_PULSE_LEN_MIN, // pulse_1_len_min: minimum length of pulse with bit value 1 + NIKON_PULSE_LEN_MAX, // pulse_1_len_max: maximum length of pulse with bit value 1 + NIKON_1_PAUSE_LEN_MIN, // pause_1_len_min: minimum length of pause with bit value 1 + NIKON_1_PAUSE_LEN_MAX, // pause_1_len_max: maximum length of pause with bit value 1 + NIKON_PULSE_LEN_MIN, // pulse_0_len_min: minimum length of pulse with bit value 0 + NIKON_PULSE_LEN_MAX, // pulse_0_len_max: maximum length of pulse with bit value 0 + NIKON_0_PAUSE_LEN_MIN, // pause_0_len_min: minimum length of pause with bit value 0 + NIKON_0_PAUSE_LEN_MAX, // pause_0_len_max: maximum length of pause with bit value 0 + NIKON_ADDRESS_OFFSET, // address_offset: address offset + NIKON_ADDRESS_OFFSET + NIKON_ADDRESS_LEN, // address_end: end of address + NIKON_COMMAND_OFFSET, // command_offset: command offset + NIKON_COMMAND_OFFSET + NIKON_COMMAND_LEN, // command_end: end of command + NIKON_COMPLETE_DATA_LEN, // complete_len: complete length of frame + NIKON_STOP_BIT, // stop_bit: flag: frame has stop bit + NIKON_LSB, // lsb_first: flag: LSB first + NIKON_FLAGS // flags: some flags +}; + +#endif + +#if IRMP_SUPPORT_KATHREIN_PROTOCOL == 1 + +static const PROGMEM IRMP_PARAMETER kathrein_param = +{ + IRMP_KATHREIN_PROTOCOL, // protocol: ir protocol + KATHREIN_1_PULSE_LEN_MIN, // pulse_1_len_min: minimum length of pulse with bit value 1 + KATHREIN_1_PULSE_LEN_MAX, // pulse_1_len_max: maximum length of pulse with bit value 1 + KATHREIN_1_PAUSE_LEN_MIN, // pause_1_len_min: minimum length of pause with bit value 1 + KATHREIN_1_PAUSE_LEN_MAX, // pause_1_len_max: maximum length of pause with bit value 1 + KATHREIN_0_PULSE_LEN_MIN, // pulse_0_len_min: minimum length of pulse with bit value 0 + KATHREIN_0_PULSE_LEN_MAX, // pulse_0_len_max: maximum length of pulse with bit value 0 + KATHREIN_0_PAUSE_LEN_MIN, // pause_0_len_min: minimum length of pause with bit value 0 + KATHREIN_0_PAUSE_LEN_MAX, // pause_0_len_max: maximum length of pause with bit value 0 + KATHREIN_ADDRESS_OFFSET, // address_offset: address offset + KATHREIN_ADDRESS_OFFSET + KATHREIN_ADDRESS_LEN, // address_end: end of address + KATHREIN_COMMAND_OFFSET, // command_offset: command offset + KATHREIN_COMMAND_OFFSET + KATHREIN_COMMAND_LEN, // command_end: end of command + KATHREIN_COMPLETE_DATA_LEN, // complete_len: complete length of frame + KATHREIN_STOP_BIT, // stop_bit: flag: frame has stop bit + KATHREIN_LSB, // lsb_first: flag: LSB first + KATHREIN_FLAGS // flags: some flags +}; + +#endif + +#if IRMP_SUPPORT_NETBOX_PROTOCOL == 1 + +static const PROGMEM IRMP_PARAMETER netbox_param = +{ + IRMP_NETBOX_PROTOCOL, // protocol: ir protocol + NETBOX_PULSE_LEN, // pulse_1_len_min: minimum length of pulse with bit value 1, here: exact value + NETBOX_PULSE_REST_LEN, // pulse_1_len_max: maximum length of pulse with bit value 1, here: rest value + NETBOX_PAUSE_LEN, // pause_1_len_min: minimum length of pause with bit value 1, here: exact value + NETBOX_PAUSE_REST_LEN, // pause_1_len_max: maximum length of pause with bit value 1, here: rest value + NETBOX_PULSE_LEN, // pulse_0_len_min: minimum length of pulse with bit value 0, here: exact value + NETBOX_PULSE_REST_LEN, // pulse_0_len_max: maximum length of pulse with bit value 0, here: rest value + NETBOX_PAUSE_LEN, // pause_0_len_min: minimum length of pause with bit value 0, here: exact value + NETBOX_PAUSE_REST_LEN, // pause_0_len_max: maximum length of pause with bit value 0, here: rest value + NETBOX_ADDRESS_OFFSET, // address_offset: address offset + NETBOX_ADDRESS_OFFSET + NETBOX_ADDRESS_LEN, // address_end: end of address + NETBOX_COMMAND_OFFSET, // command_offset: command offset + NETBOX_COMMAND_OFFSET + NETBOX_COMMAND_LEN, // command_end: end of command + NETBOX_COMPLETE_DATA_LEN, // complete_len: complete length of frame + NETBOX_STOP_BIT, // stop_bit: flag: frame has stop bit + NETBOX_LSB, // lsb_first: flag: LSB first + NETBOX_FLAGS // flags: some flags +}; + +#endif + +#if IRMP_SUPPORT_LEGO_PROTOCOL == 1 + +static const PROGMEM IRMP_PARAMETER lego_param = +{ + IRMP_LEGO_PROTOCOL, // protocol: ir protocol + LEGO_PULSE_LEN_MIN, // pulse_1_len_min: minimum length of pulse with bit value 1 + LEGO_PULSE_LEN_MAX, // pulse_1_len_max: maximum length of pulse with bit value 1 + LEGO_1_PAUSE_LEN_MIN, // pause_1_len_min: minimum length of pause with bit value 1 + LEGO_1_PAUSE_LEN_MAX, // pause_1_len_max: maximum length of pause with bit value 1 + LEGO_PULSE_LEN_MIN, // pulse_0_len_min: minimum length of pulse with bit value 0 + LEGO_PULSE_LEN_MAX, // pulse_0_len_max: maximum length of pulse with bit value 0 + LEGO_0_PAUSE_LEN_MIN, // pause_0_len_min: minimum length of pause with bit value 0 + LEGO_0_PAUSE_LEN_MAX, // pause_0_len_max: maximum length of pause with bit value 0 + LEGO_ADDRESS_OFFSET, // address_offset: address offset + LEGO_ADDRESS_OFFSET + LEGO_ADDRESS_LEN, // address_end: end of address + LEGO_COMMAND_OFFSET, // command_offset: command offset + LEGO_COMMAND_OFFSET + LEGO_COMMAND_LEN, // command_end: end of command + LEGO_COMPLETE_DATA_LEN, // complete_len: complete length of frame + LEGO_STOP_BIT, // stop_bit: flag: frame has stop bit + LEGO_LSB, // lsb_first: flag: LSB first + LEGO_FLAGS // flags: some flags +}; + +#endif + +#if IRMP_SUPPORT_THOMSON_PROTOCOL == 1 + +static const PROGMEM IRMP_PARAMETER thomson_param = +{ + IRMP_THOMSON_PROTOCOL, // protocol: ir protocol + THOMSON_PULSE_LEN_MIN, // pulse_1_len_min: minimum length of pulse with bit value 1 + THOMSON_PULSE_LEN_MAX, // pulse_1_len_max: maximum length of pulse with bit value 1 + THOMSON_1_PAUSE_LEN_MIN, // pause_1_len_min: minimum length of pause with bit value 1 + THOMSON_1_PAUSE_LEN_MAX, // pause_1_len_max: maximum length of pause with bit value 1 + THOMSON_PULSE_LEN_MIN, // pulse_0_len_min: minimum length of pulse with bit value 0 + THOMSON_PULSE_LEN_MAX, // pulse_0_len_max: maximum length of pulse with bit value 0 + THOMSON_0_PAUSE_LEN_MIN, // pause_0_len_min: minimum length of pause with bit value 0 + THOMSON_0_PAUSE_LEN_MAX, // pause_0_len_max: maximum length of pause with bit value 0 + THOMSON_ADDRESS_OFFSET, // address_offset: address offset + THOMSON_ADDRESS_OFFSET + THOMSON_ADDRESS_LEN, // address_end: end of address + THOMSON_COMMAND_OFFSET, // command_offset: command offset + THOMSON_COMMAND_OFFSET + THOMSON_COMMAND_LEN, // command_end: end of command + THOMSON_COMPLETE_DATA_LEN, // complete_len: complete length of frame + THOMSON_STOP_BIT, // stop_bit: flag: frame has stop bit + THOMSON_LSB, // lsb_first: flag: LSB first + THOMSON_FLAGS // flags: some flags +}; + +#endif + +static uint8_t irmp_bit; // current bit position +static IRMP_PARAMETER irmp_param; + +#if IRMP_SUPPORT_RC5_PROTOCOL == 1 && (IRMP_SUPPORT_FDC_PROTOCOL == 1 || IRMP_SUPPORT_RCCAR_PROTOCOL == 1) +static IRMP_PARAMETER irmp_param2; +#endif + +static volatile uint8_t irmp_ir_detected; +static volatile uint8_t irmp_protocol; +static volatile uint16_t irmp_address; +static volatile uint16_t irmp_command; +static volatile uint16_t irmp_id; // only used for SAMSUNG protocol +static volatile uint8_t irmp_flags; +// static volatile uint8_t irmp_busy_flag; + +#ifdef ANALYZE +static uint8_t IRMP_PIN; +#endif + +/*--------------------------------------------------------------------------------------------------------------------------------------------------- + * Initialize IRMP decoder + * @details Configures IRMP input pin + *--------------------------------------------------------------------------------------------------------------------------------------------------- + */ +#ifndef ANALYZE +void +irmp_init (void) +{ +#ifndef ARDUINO +#if !defined(PIC_CCS_COMPILER) && !defined(PIC_C18) // only AVR + IRMP_PORT &= ~(1<> 8) == (~irmp_command & 0x00FF)) + { + irmp_command &= 0xff; + irmp_command |= irmp_id << 8; + rtc = TRUE; + } + break; +#endif +#if IRMP_SUPPORT_NEC_PROTOCOL == 1 + case IRMP_NEC_PROTOCOL: + if ((irmp_command >> 8) == (~irmp_command & 0x00FF)) + { + irmp_command &= 0xff; + rtc = TRUE; + } + else if (irmp_address == 0x87EE) + { + ANALYZE_PRINTF ("Switching to APPLE protocol\n"); + irmp_protocol = IRMP_APPLE_PROTOCOL; + irmp_address = (irmp_command & 0xFF00) >> 8; + irmp_command &= 0x00FF; + rtc = TRUE; + } + break; +#endif +#if IRMP_SUPPORT_SIEMENS_OR_RUWIDO_PROTOCOL == 1 + case IRMP_SIEMENS_PROTOCOL: + case IRMP_RUWIDO_PROTOCOL: + if (((irmp_command >> 1) & 0x0001) == (~irmp_command & 0x0001)) + { + irmp_command >>= 1; + rtc = TRUE; + } + break; +#endif +#if IRMP_SUPPORT_GRUNDIG2_PROTOCOL == 1 + case IRMP_GRUNDIG2_PROTOCOL: + if (irmp_command & 0x0001) + { + irmp_command >>= 1; + rtc = TRUE; + } + break; +#endif +#if IRMP_SUPPORT_KATHREIN_PROTOCOL == 1 + case IRMP_KATHREIN_PROTOCOL: + if (irmp_command != 0x0000) + { + rtc = TRUE; + } + break; +#endif +#if IRMP_SUPPORT_RC5_PROTOCOL == 1 + case IRMP_RC5_PROTOCOL: + irmp_address &= ~0x20; // clear toggle bit + rtc = TRUE; + break; +#endif +#if IRMP_SUPPORT_IR60_PROTOCOL == 1 + case IRMP_IR60_PROTOCOL: + if (irmp_command != 0x007d) // 0x007d (== 62<<1 + 1) is start instruction frame + { + rtc = TRUE; + } + break; +#endif +#if IRMP_SUPPORT_RCCAR_PROTOCOL == 1 + case IRMP_RCCAR_PROTOCOL: + // frame in irmp_data: + // Bit 12 11 10 9 8 7 6 5 4 3 2 1 0 + // V D7 D6 D5 D4 D3 D2 D1 D0 A1 A0 C1 C0 // 10 9 8 7 6 5 4 3 2 1 0 + irmp_address = (irmp_command & 0x000C) >> 2; // addr: 0 0 0 0 0 0 0 0 0 A1 A0 + irmp_command = ((irmp_command & 0x1000) >> 2) | // V-Bit: V 0 0 0 0 0 0 0 0 0 0 + ((irmp_command & 0x0003) << 8) | // C-Bits: 0 C1 C0 0 0 0 0 0 0 0 0 + ((irmp_command & 0x0FF0) >> 4); // D-Bits: D7 D6 D5 D4 D3 D2 D1 D0 + rtc = TRUE; // Summe: V C1 C0 D7 D6 D5 D4 D3 D2 D1 D0 + break; +#endif + +#if IRMP_SUPPORT_NETBOX_PROTOCOL == 1 // squeeze code to 8 bit, upper bit indicates release-key + case IRMP_NETBOX_PROTOCOL: + if (irmp_command & 0x1000) // last bit set? + { + if ((irmp_command & 0x1f) == 0x15) // key pressed: 101 01 (LSB) + { + irmp_command >>= 5; + irmp_command &= 0x7F; + rtc = TRUE; + } + else if ((irmp_command & 0x1f) == 0x10) // key released: 000 01 (LSB) + { + irmp_command >>= 5; + irmp_command |= 0x80; + rtc = TRUE; + } + else + { + ANALYZE_PRINTF("error NETBOX: bit6/7 must be 0/1\n"); + } + } + else + { + ANALYZE_PRINTF("error NETBOX: last bit not set\n"); + } + break; +#endif +#if IRMP_SUPPORT_LEGO_PROTOCOL == 1 + case IRMP_LEGO_PROTOCOL: + { + uint8_t crc = 0x0F ^ ((irmp_command & 0xF000) >> 12) ^ ((irmp_command & 0x0F00) >> 8) ^ ((irmp_command & 0x00F0) >> 4); + + if ((irmp_command & 0x000F) == crc) + { + irmp_command >>= 4; + rtc = TRUE; + } + else + { + ANALYZE_PRINTF ("CRC error in LEGO protocol\n"); + rtc = TRUE; + } + break; + } +#endif + default: + rtc = TRUE; + } + + if (rtc) + { + irmp_data_p->protocol = irmp_protocol; + irmp_data_p->address = irmp_address; + irmp_data_p->command = irmp_command; + irmp_data_p->flags = irmp_flags; + irmp_command = 0; + irmp_address = 0; + irmp_flags = 0; + } + + irmp_ir_detected = FALSE; + } + + return rtc; +} + +// uint8_t +// irmp_is_busy (void) +// { +// return irmp_busy_flag; +// } + +#if IRMP_USE_CALLBACK == 1 +void +irmp_set_callback_ptr (void (*cb)(uint8_t)) +{ + irmp_callback_ptr = cb; +} +#endif // IRMP_USE_CALLBACK == 1 + +// these statics must not be volatile, because they are only used by irmp_store_bit(), which is called by irmp_ISR() +static uint16_t irmp_tmp_address; // ir address +static uint16_t irmp_tmp_command; // ir command + +#if IRMP_SUPPORT_RC5_PROTOCOL == 1 && (IRMP_SUPPORT_FDC_PROTOCOL == 1 || IRMP_SUPPORT_RCCAR_PROTOCOL == 1) || IRMP_SUPPORT_NEC42_PROTOCOL == 1 +static uint16_t irmp_tmp_address2; // ir address +static uint16_t irmp_tmp_command2; // ir command +#endif + +#if IRMP_SUPPORT_SAMSUNG_PROTOCOL == 1 +static uint16_t irmp_tmp_id; // ir id (only SAMSUNG) +#endif +#if IRMP_SUPPORT_KASEIKYO_PROTOCOL == 1 +static uint8_t xor_check[6]; // check kaseikyo "parity" bits +static uint8_t genre2; // save genre2 bits here, later copied to MSB in flags +#endif + +/*--------------------------------------------------------------------------------------------------------------------------------------------------- + * store bit + * @details store bit in temp address or temp command + * @param value to store: 0 or 1 + *--------------------------------------------------------------------------------------------------------------------------------------------------- + */ +// verhindert, dass irmp_store_bit() inline compiliert wird: +// static void irmp_store_bit (uint8_t) __attribute__ ((noinline)); + +static void +irmp_store_bit (uint8_t value) +{ +#if IRMP_SUPPORT_GRUNDIG_NOKIA_IR60_PROTOCOL == 1 + if (irmp_bit == 0 && irmp_param.protocol == IRMP_GRUNDIG_PROTOCOL) + { + first_bit = value; + } + else +#endif + + if (irmp_bit >= irmp_param.address_offset && irmp_bit < irmp_param.address_end) + { + if (irmp_param.lsb_first) + { + irmp_tmp_address |= (((uint16_t) (value)) << (irmp_bit - irmp_param.address_offset)); // CV wants cast + } + else + { + irmp_tmp_address <<= 1; + irmp_tmp_address |= value; + } + } + else if (irmp_bit >= irmp_param.command_offset && irmp_bit < irmp_param.command_end) + { + if (irmp_param.lsb_first) + { + irmp_tmp_command |= (((uint16_t) (value)) << (irmp_bit - irmp_param.command_offset)); // CV wants cast + } + else + { + irmp_tmp_command <<= 1; + irmp_tmp_command |= value; + } + } + +#if IRMP_SUPPORT_NEC42_PROTOCOL == 1 + else if (irmp_param.protocol == IRMP_NEC42_PROTOCOL && irmp_bit >= 13 && irmp_bit < 26) + { + irmp_tmp_address2 |= (((uint16_t) (value)) << (irmp_bit - 13)); // CV wants cast + } +#endif + +#if IRMP_SUPPORT_SAMSUNG_PROTOCOL == 1 + else if (irmp_param.protocol == IRMP_SAMSUNG_PROTOCOL && irmp_bit >= SAMSUNG_ID_OFFSET && irmp_bit < SAMSUNG_ID_OFFSET + SAMSUNG_ID_LEN) + { + irmp_tmp_id |= (((uint16_t) (value)) << (irmp_bit - SAMSUNG_ID_OFFSET)); // store with LSB first + } +#endif + +#if IRMP_SUPPORT_KASEIKYO_PROTOCOL == 1 + else if (irmp_param.protocol == IRMP_KASEIKYO_PROTOCOL) + { + if (irmp_bit >= 20 && irmp_bit < 24) + { + irmp_tmp_command |= (((uint16_t) (value)) << (irmp_bit - 8)); // store 4 system bits (genre 1) in upper nibble with LSB first + } + else if (irmp_bit >= 24 && irmp_bit < 28) + { + genre2 |= (((uint8_t) (value)) << (irmp_bit - 20)); // store 4 system bits (genre 2) in upper nibble with LSB first + } + + if (irmp_bit < KASEIKYO_COMPLETE_DATA_LEN) + { + if (value) + { + xor_check[irmp_bit / 8] |= 1 << (irmp_bit % 8); + } + else + { + xor_check[irmp_bit / 8] &= ~(1 << (irmp_bit % 8)); + } + } + } +#endif + + irmp_bit++; +} + +/*--------------------------------------------------------------------------------------------------------------------------------------------------- + * store bit + * @details store bit in temp address or temp command + * @param value to store: 0 or 1 + *--------------------------------------------------------------------------------------------------------------------------------------------------- + */ +#if IRMP_SUPPORT_RC5_PROTOCOL == 1 && (IRMP_SUPPORT_FDC_PROTOCOL == 1 || IRMP_SUPPORT_RCCAR_PROTOCOL == 1) +static void +irmp_store_bit2 (uint8_t value) +{ + uint8_t irmp_bit2; + + if (irmp_param.protocol) + { + irmp_bit2 = irmp_bit - 2; + } + else + { + irmp_bit2 = irmp_bit - 1; + } + + if (irmp_bit2 >= irmp_param2.address_offset && irmp_bit2 < irmp_param2.address_end) + { + irmp_tmp_address2 |= (((uint16_t) (value)) << (irmp_bit2 - irmp_param2.address_offset)); // CV wants cast + } + else if (irmp_bit2 >= irmp_param2.command_offset && irmp_bit2 < irmp_param2.command_end) + { + irmp_tmp_command2 |= (((uint16_t) (value)) << (irmp_bit2 - irmp_param2.command_offset)); // CV wants cast + } +} +#endif // IRMP_SUPPORT_RC5_PROTOCOL == 1 && (IRMP_SUPPORT_FDC_PROTOCOL == 1 || IRMP_SUPPORT_RCCAR_PROTOCOL == 1) + +/*--------------------------------------------------------------------------------------------------------------------------------------------------- + * ISR routine + * @details ISR routine, called 10000 times per second + *--------------------------------------------------------------------------------------------------------------------------------------------------- + */ +uint8_t +irmp_ISR (uint8_t x42) +{ + static uint8_t irmp_start_bit_detected; // flag: start bit detected + static uint8_t wait_for_space; // flag: wait for data bit space + static uint8_t wait_for_start_space; // flag: wait for start bit space + static uint8_t irmp_pulse_time; // count bit time for pulse + static PAUSE_LEN irmp_pause_time; // count bit time for pause + static uint16_t last_irmp_address = 0xFFFF; // save last irmp address to recognize key repetition + static uint16_t last_irmp_command = 0xFFFF; // save last irmp command to recognize key repetition + static uint16_t repetition_len; // SIRCS repeats frame 2-5 times with 45 ms pause + static uint8_t repetition_frame_number; +#if IRMP_SUPPORT_DENON_PROTOCOL == 1 + static uint16_t last_irmp_denon_command; // save last irmp command to recognize DENON frame repetition +#endif +#if IRMP_SUPPORT_RC5_PROTOCOL == 1 + static uint8_t rc5_cmd_bit6; // bit 6 of RC5 command is the inverted 2nd start bit +#endif +#if IRMP_SUPPORT_MANCHESTER == 1 + static PAUSE_LEN last_pause; // last pause value +#endif +#if IRMP_SUPPORT_MANCHESTER == 1 || IRMP_SUPPORT_BANG_OLUFSEN_PROTOCOL == 1 + static uint8_t last_value; // last bit value +#endif + uint8_t irmp_input; // input value + +#ifdef ANALYZE + time_counter++; +#endif + + irmp_input = input(x42); + +#if IRMP_USE_CALLBACK == 1 + if (irmp_callback_ptr) + { + static uint8_t last_inverted_input; + + if (last_inverted_input != !irmp_input) + { + (*irmp_callback_ptr) (! irmp_input); + last_inverted_input = !irmp_input; + } + } +#endif // IRMP_USE_CALLBACK == 1 + + irmp_log(irmp_input); // log ir signal, if IRMP_LOGGING defined + + if (! irmp_ir_detected) // ir code already detected? + { // no... + if (! irmp_start_bit_detected) // start bit detected? + { // no... + if (! irmp_input) // receiving burst? + { // yes... +// irmp_busy_flag = TRUE; +#ifdef ANALYZE + if (! irmp_pulse_time) + { + ANALYZE_PRINTF("%8.3fms [starting pulse]\n", (double) (time_counter * 1000) / F_INTERRUPTS); + } +#endif + irmp_pulse_time++; // increment counter + } + else + { // no... + if (irmp_pulse_time) // it's dark.... + { // set flags for counting the time of darkness... + irmp_start_bit_detected = 1; + wait_for_start_space = 1; + wait_for_space = 0; + irmp_tmp_command = 0; + irmp_tmp_address = 0; +#if IRMP_SUPPORT_KASEIKYO_PROTOCOL == 1 + genre2 = 0; +#endif + +#if IRMP_SUPPORT_RC5_PROTOCOL == 1 && (IRMP_SUPPORT_FDC_PROTOCOL == 1 || IRMP_SUPPORT_RCCAR_PROTOCOL == 1) || IRMP_SUPPORT_NEC42_PROTOCOL == 1 + irmp_tmp_command2 = 0; + irmp_tmp_address2 = 0; +#endif + + irmp_bit = 0xff; + irmp_pause_time = 1; // 1st pause: set to 1, not to 0! +#if IRMP_SUPPORT_RC5_PROTOCOL == 1 + rc5_cmd_bit6 = 0; // fm 2010-03-07: bugfix: reset it after incomplete RC5 frame! +#endif + } + else + { + if (repetition_len < 0xFFFF) // avoid overflow of counter + { + repetition_len++; + } + } + } + } + else + { + if (wait_for_start_space) // we have received start bit... + { // ...and are counting the time of darkness + if (irmp_input) // still dark? + { // yes + irmp_pause_time++; // increment counter + +#if IRMP_SUPPORT_NIKON_PROTOCOL == 1 + if (((irmp_pulse_time < NIKON_START_BIT_PULSE_LEN_MIN || irmp_pulse_time > NIKON_START_BIT_PULSE_LEN_MAX) && irmp_pause_time > IRMP_TIMEOUT_LEN) || + irmp_pause_time > IRMP_TIMEOUT_NIKON_LEN) +#else + if (irmp_pause_time > IRMP_TIMEOUT_LEN) // timeout? +#endif + { // yes... +#if IRMP_SUPPORT_JVC_PROTOCOL == 1 + if (irmp_protocol == IRMP_JVC_PROTOCOL) // don't show eror if JVC protocol, irmp_pulse_time has been set below! + { + ; + } + else +#endif // IRMP_SUPPORT_JVC_PROTOCOL == 1 + { + ANALYZE_PRINTF ("%8.3fms error 1: pause after start bit pulse %d too long: %d\n", (double) (time_counter * 1000) / F_INTERRUPTS, irmp_pulse_time, irmp_pause_time); + ANALYZE_ONLY_NORMAL_PUTCHAR ('\n'); + } +// irmp_busy_flag = FALSE; + irmp_start_bit_detected = 0; // reset flags, let's wait for another start bit + irmp_pulse_time = 0; + irmp_pause_time = 0; + } + } + else + { // receiving first data pulse! + IRMP_PARAMETER * irmp_param_p = (IRMP_PARAMETER *) 0; + +#if IRMP_SUPPORT_RC5_PROTOCOL == 1 && (IRMP_SUPPORT_FDC_PROTOCOL == 1 || IRMP_SUPPORT_RCCAR_PROTOCOL == 1) + irmp_param2.protocol = 0; +#endif + + ANALYZE_PRINTF ("%8.3fms [start-bit: pulse = %2d, pause = %2d]\n", (double) (time_counter * 1000) / F_INTERRUPTS, irmp_pulse_time, irmp_pause_time); + +#if IRMP_SUPPORT_SIRCS_PROTOCOL == 1 + if (irmp_pulse_time >= SIRCS_START_BIT_PULSE_LEN_MIN && irmp_pulse_time <= SIRCS_START_BIT_PULSE_LEN_MAX && + irmp_pause_time >= SIRCS_START_BIT_PAUSE_LEN_MIN && irmp_pause_time <= SIRCS_START_BIT_PAUSE_LEN_MAX) + { // it's SIRCS + ANALYZE_PRINTF ("protocol = SIRCS, start bit timings: pulse: %3d - %3d, pause: %3d - %3d\n", + SIRCS_START_BIT_PULSE_LEN_MIN, SIRCS_START_BIT_PULSE_LEN_MAX, + SIRCS_START_BIT_PAUSE_LEN_MIN, SIRCS_START_BIT_PAUSE_LEN_MAX); + irmp_param_p = (IRMP_PARAMETER *) (IRMP_PARAMETER *) &sircs_param; + } + else +#endif // IRMP_SUPPORT_SIRCS_PROTOCOL == 1 + +#if IRMP_SUPPORT_JVC_PROTOCOL == 1 + if (irmp_protocol == IRMP_JVC_PROTOCOL && // last protocol was JVC, awaiting repeat frame + irmp_pulse_time >= JVC_START_BIT_PULSE_LEN_MIN && irmp_pulse_time <= JVC_START_BIT_PULSE_LEN_MAX && + irmp_pause_time >= JVC_REPEAT_START_BIT_PAUSE_LEN_MIN && irmp_pause_time <= JVC_REPEAT_START_BIT_PAUSE_LEN_MAX) + { + ANALYZE_PRINTF ("protocol = NEC or JVC (type 1) repeat frame, start bit timings: pulse: %3d - %3d, pause: %3d - %3d\n", + JVC_START_BIT_PULSE_LEN_MIN, JVC_START_BIT_PULSE_LEN_MAX, + JVC_REPEAT_START_BIT_PAUSE_LEN_MIN, JVC_REPEAT_START_BIT_PAUSE_LEN_MAX); + irmp_param_p = (IRMP_PARAMETER *) &nec_param; + } + else +#endif // IRMP_SUPPORT_JVC_PROTOCOL == 1 + +#if IRMP_SUPPORT_NEC_PROTOCOL == 1 + if (irmp_pulse_time >= NEC_START_BIT_PULSE_LEN_MIN && irmp_pulse_time <= NEC_START_BIT_PULSE_LEN_MAX && + irmp_pause_time >= NEC_START_BIT_PAUSE_LEN_MIN && irmp_pause_time <= NEC_START_BIT_PAUSE_LEN_MAX) + { +#if IRMP_SUPPORT_NEC42_PROTOCOL == 1 + ANALYZE_PRINTF ("protocol = NEC42, start bit timings: pulse: %3d - %3d, pause: %3d - %3d\n", + NEC_START_BIT_PULSE_LEN_MIN, NEC_START_BIT_PULSE_LEN_MAX, + NEC_START_BIT_PAUSE_LEN_MIN, NEC_START_BIT_PAUSE_LEN_MAX); + irmp_param_p = (IRMP_PARAMETER *) &nec42_param; +#else + ANALYZE_PRINTF ("protocol = NEC, start bit timings: pulse: %3d - %3d, pause: %3d - %3d\n", + NEC_START_BIT_PULSE_LEN_MIN, NEC_START_BIT_PULSE_LEN_MAX, + NEC_START_BIT_PAUSE_LEN_MIN, NEC_START_BIT_PAUSE_LEN_MAX); + irmp_param_p = (IRMP_PARAMETER *) &nec_param; +#endif + + } + else if (irmp_pulse_time >= NEC_START_BIT_PULSE_LEN_MIN && irmp_pulse_time <= NEC_START_BIT_PULSE_LEN_MAX && + irmp_pause_time >= NEC_REPEAT_START_BIT_PAUSE_LEN_MIN && irmp_pause_time <= NEC_REPEAT_START_BIT_PAUSE_LEN_MAX) + { // it's NEC +#if IRMP_SUPPORT_JVC_PROTOCOL == 1 + if (irmp_protocol == IRMP_JVC_PROTOCOL) // last protocol was JVC, awaiting repeat frame + { // some jvc remote controls use nec repetition frame for jvc repetition frame + ANALYZE_PRINTF ("protocol = JVC repeat frame type 2, start bit timings: pulse: %3d - %3d, pause: %3d - %3d\n", + NEC_START_BIT_PULSE_LEN_MIN, NEC_START_BIT_PULSE_LEN_MAX, + NEC_REPEAT_START_BIT_PAUSE_LEN_MIN, NEC_REPEAT_START_BIT_PAUSE_LEN_MAX); + irmp_param_p = (IRMP_PARAMETER *) &nec_param; + } + else +#endif // IRMP_SUPPORT_JVC_PROTOCOL == 1 + { + ANALYZE_PRINTF ("protocol = NEC (repetition frame), start bit timings: pulse: %3d - %3d, pause: %3d - %3d\n", + NEC_START_BIT_PULSE_LEN_MIN, NEC_START_BIT_PULSE_LEN_MAX, + NEC_REPEAT_START_BIT_PAUSE_LEN_MIN, NEC_REPEAT_START_BIT_PAUSE_LEN_MAX); + + irmp_param_p = (IRMP_PARAMETER *) &nec_rep_param; + } + } + else + +#if IRMP_SUPPORT_JVC_PROTOCOL == 1 + if (irmp_protocol == IRMP_JVC_PROTOCOL && // last protocol was JVC, awaiting repeat frame + irmp_pulse_time >= NEC_START_BIT_PULSE_LEN_MIN && irmp_pulse_time <= NEC_START_BIT_PULSE_LEN_MAX && + irmp_pause_time >= NEC_0_PAUSE_LEN_MIN && irmp_pause_time <= NEC_0_PAUSE_LEN_MAX) + { // it's JVC repetition type 3 + ANALYZE_PRINTF ("protocol = JVC repeat frame type 3, start bit timings: pulse: %3d - %3d, pause: %3d - %3d\n", + NEC_START_BIT_PULSE_LEN_MIN, NEC_START_BIT_PULSE_LEN_MAX, + NEC_0_PAUSE_LEN_MIN, NEC_0_PAUSE_LEN_MAX); + irmp_param_p = (IRMP_PARAMETER *) &nec_param; + } + else +#endif // IRMP_SUPPORT_JVC_PROTOCOL == 1 + +#endif // IRMP_SUPPORT_NEC_PROTOCOL == 1 + +#if IRMP_SUPPORT_NIKON_PROTOCOL == 1 + if (irmp_pulse_time >= NIKON_START_BIT_PULSE_LEN_MIN && irmp_pulse_time <= NIKON_START_BIT_PULSE_LEN_MAX && + irmp_pause_time >= NIKON_START_BIT_PAUSE_LEN_MIN && irmp_pause_time <= NIKON_START_BIT_PAUSE_LEN_MAX) + { + ANALYZE_PRINTF ("protocol = NIKON, start bit timings: pulse: %3d - %3d, pause: %3d - %3d\n", + NIKON_START_BIT_PULSE_LEN_MIN, NIKON_START_BIT_PULSE_LEN_MAX, + NIKON_START_BIT_PAUSE_LEN_MIN, NIKON_START_BIT_PAUSE_LEN_MAX); + irmp_param_p = (IRMP_PARAMETER *) &nikon_param; + } + else +#endif // IRMP_SUPPORT_NIKON_PROTOCOL == 1 + +#if IRMP_SUPPORT_SAMSUNG_PROTOCOL == 1 + if (irmp_pulse_time >= SAMSUNG_START_BIT_PULSE_LEN_MIN && irmp_pulse_time <= SAMSUNG_START_BIT_PULSE_LEN_MAX && + irmp_pause_time >= SAMSUNG_START_BIT_PAUSE_LEN_MIN && irmp_pause_time <= SAMSUNG_START_BIT_PAUSE_LEN_MAX) + { // it's SAMSUNG + ANALYZE_PRINTF ("protocol = SAMSUNG, start bit timings: pulse: %3d - %3d, pause: %3d - %3d\n", + SAMSUNG_START_BIT_PULSE_LEN_MIN, SAMSUNG_START_BIT_PULSE_LEN_MAX, + SAMSUNG_START_BIT_PAUSE_LEN_MIN, SAMSUNG_START_BIT_PAUSE_LEN_MAX); + irmp_param_p = (IRMP_PARAMETER *) &samsung_param; + } + else +#endif // IRMP_SUPPORT_SAMSUNG_PROTOCOL == 1 + +#if IRMP_SUPPORT_MATSUSHITA_PROTOCOL == 1 + if (irmp_pulse_time >= MATSUSHITA_START_BIT_PULSE_LEN_MIN && irmp_pulse_time <= MATSUSHITA_START_BIT_PULSE_LEN_MAX && + irmp_pause_time >= MATSUSHITA_START_BIT_PAUSE_LEN_MIN && irmp_pause_time <= MATSUSHITA_START_BIT_PAUSE_LEN_MAX) + { // it's MATSUSHITA + ANALYZE_PRINTF ("protocol = MATSUSHITA, start bit timings: pulse: %3d - %3d, pause: %3d - %3d\n", + MATSUSHITA_START_BIT_PULSE_LEN_MIN, MATSUSHITA_START_BIT_PULSE_LEN_MAX, + MATSUSHITA_START_BIT_PAUSE_LEN_MIN, MATSUSHITA_START_BIT_PAUSE_LEN_MAX); + irmp_param_p = (IRMP_PARAMETER *) &matsushita_param; + } + else +#endif // IRMP_SUPPORT_MATSUSHITA_PROTOCOL == 1 + +#if IRMP_SUPPORT_KASEIKYO_PROTOCOL == 1 + if (irmp_pulse_time >= KASEIKYO_START_BIT_PULSE_LEN_MIN && irmp_pulse_time <= KASEIKYO_START_BIT_PULSE_LEN_MAX && + irmp_pause_time >= KASEIKYO_START_BIT_PAUSE_LEN_MIN && irmp_pause_time <= KASEIKYO_START_BIT_PAUSE_LEN_MAX) + { // it's KASEIKYO + ANALYZE_PRINTF ("protocol = KASEIKYO, start bit timings: pulse: %3d - %3d, pause: %3d - %3d\n", + KASEIKYO_START_BIT_PULSE_LEN_MIN, KASEIKYO_START_BIT_PULSE_LEN_MAX, + KASEIKYO_START_BIT_PAUSE_LEN_MIN, KASEIKYO_START_BIT_PAUSE_LEN_MAX); + irmp_param_p = (IRMP_PARAMETER *) &kaseikyo_param; + } + else +#endif // IRMP_SUPPORT_KASEIKYO_PROTOCOL == 1 + +#if IRMP_SUPPORT_RECS80_PROTOCOL == 1 + if (irmp_pulse_time >= RECS80_START_BIT_PULSE_LEN_MIN && irmp_pulse_time <= RECS80_START_BIT_PULSE_LEN_MAX && + irmp_pause_time >= RECS80_START_BIT_PAUSE_LEN_MIN && irmp_pause_time <= RECS80_START_BIT_PAUSE_LEN_MAX) + { // it's RECS80 + ANALYZE_PRINTF ("protocol = RECS80, start bit timings: pulse: %3d - %3d, pause: %3d - %3d\n", + RECS80_START_BIT_PULSE_LEN_MIN, RECS80_START_BIT_PULSE_LEN_MAX, + RECS80_START_BIT_PAUSE_LEN_MIN, RECS80_START_BIT_PAUSE_LEN_MAX); + irmp_param_p = (IRMP_PARAMETER *) &recs80_param; + } + else +#endif // IRMP_SUPPORT_RECS80_PROTOCOL == 1 + +#if IRMP_SUPPORT_RC5_PROTOCOL == 1 + if (((irmp_pulse_time >= RC5_START_BIT_LEN_MIN && irmp_pulse_time <= RC5_START_BIT_LEN_MAX) || + (irmp_pulse_time >= 2 * RC5_START_BIT_LEN_MIN && irmp_pulse_time <= 2 * RC5_START_BIT_LEN_MAX)) && + ((irmp_pause_time >= RC5_START_BIT_LEN_MIN && irmp_pause_time <= RC5_START_BIT_LEN_MAX) || + (irmp_pause_time >= 2 * RC5_START_BIT_LEN_MIN && irmp_pause_time <= 2 * RC5_START_BIT_LEN_MAX))) + { // it's RC5 +#if IRMP_SUPPORT_FDC_PROTOCOL == 1 + if (irmp_pulse_time >= FDC_START_BIT_PULSE_LEN_MIN && irmp_pulse_time <= FDC_START_BIT_PULSE_LEN_MAX && + irmp_pause_time >= FDC_START_BIT_PAUSE_LEN_MIN && irmp_pause_time <= FDC_START_BIT_PAUSE_LEN_MAX) + { + ANALYZE_PRINTF ("protocol = RC5 or FDC\n"); + ANALYZE_PRINTF ("FDC start bit timings: pulse: %3d - %3d, pause: %3d - %3d\n", + FDC_START_BIT_PULSE_LEN_MIN, FDC_START_BIT_PULSE_LEN_MAX, + FDC_START_BIT_PAUSE_LEN_MIN, FDC_START_BIT_PAUSE_LEN_MAX); + ANALYZE_PRINTF ("RC5 start bit timings: pulse: %3d - %3d, pause: %3d - %3d\n", + RC5_START_BIT_LEN_MIN, RC5_START_BIT_LEN_MAX, + RC5_START_BIT_LEN_MIN, RC5_START_BIT_LEN_MAX); + memcpy_P (&irmp_param2, &fdc_param, sizeof (IRMP_PARAMETER)); + } + else +#endif // IRMP_SUPPORT_FDC_PROTOCOL == 1 + +#if IRMP_SUPPORT_RCCAR_PROTOCOL == 1 + if (irmp_pulse_time >= RCCAR_START_BIT_PULSE_LEN_MIN && irmp_pulse_time <= RCCAR_START_BIT_PULSE_LEN_MAX && + irmp_pause_time >= RCCAR_START_BIT_PAUSE_LEN_MIN && irmp_pause_time <= RCCAR_START_BIT_PAUSE_LEN_MAX) + { + ANALYZE_PRINTF ("protocol = RC5 or RCCAR\n"); + ANALYZE_PRINTF ("RCCAR start bit timings: pulse: %3d - %3d, pause: %3d - %3d\n", + RCCAR_START_BIT_PULSE_LEN_MIN, RCCAR_START_BIT_PULSE_LEN_MAX, + RCCAR_START_BIT_PAUSE_LEN_MIN, RCCAR_START_BIT_PAUSE_LEN_MAX); + ANALYZE_PRINTF ("RC5 start bit timings: pulse: %3d - %3d, pause: %3d - %3d\n", + RC5_START_BIT_LEN_MIN, RC5_START_BIT_LEN_MAX, + RC5_START_BIT_LEN_MIN, RC5_START_BIT_LEN_MAX); + memcpy_P (&irmp_param2, &rccar_param, sizeof (IRMP_PARAMETER)); + } + else +#endif // IRMP_SUPPORT_RCCAR_PROTOCOL == 1 + { + ANALYZE_PRINTF ("protocol = RC5, start bit timings: pulse: %3d - %3d, pause: %3d - %3d or pulse: %3d - %3d, pause: %3d - %3d\n", + RC5_START_BIT_LEN_MIN, RC5_START_BIT_LEN_MAX, + 2 * RC5_START_BIT_LEN_MIN, 2 * RC5_START_BIT_LEN_MAX, + RC5_START_BIT_LEN_MIN, RC5_START_BIT_LEN_MAX, + 2 * RC5_START_BIT_LEN_MIN, 2 * RC5_START_BIT_LEN_MAX); + } + + irmp_param_p = (IRMP_PARAMETER *) &rc5_param; + last_pause = irmp_pause_time; + + if ((irmp_pulse_time > RC5_START_BIT_LEN_MAX && irmp_pulse_time <= 2 * RC5_START_BIT_LEN_MAX) || + (irmp_pause_time > RC5_START_BIT_LEN_MAX && irmp_pause_time <= 2 * RC5_START_BIT_LEN_MAX)) + { + last_value = 0; + rc5_cmd_bit6 = 1<<6; + } + else + { + last_value = 1; + } + } + else +#endif // IRMP_SUPPORT_RC5_PROTOCOL == 1 + +#if IRMP_SUPPORT_DENON_PROTOCOL == 1 + if ( (irmp_pulse_time >= DENON_PULSE_LEN_MIN && irmp_pulse_time <= DENON_PULSE_LEN_MAX) && + ((irmp_pause_time >= DENON_1_PAUSE_LEN_MIN && irmp_pause_time <= DENON_1_PAUSE_LEN_MAX) || + (irmp_pause_time >= DENON_0_PAUSE_LEN_MIN && irmp_pause_time <= DENON_0_PAUSE_LEN_MAX))) + { // it's DENON + ANALYZE_PRINTF ("protocol = DENON, start bit timings: pulse: %3d - %3d, pause: %3d - %3d or %3d - %3d\n", + DENON_PULSE_LEN_MIN, DENON_PULSE_LEN_MAX, + DENON_1_PAUSE_LEN_MIN, DENON_1_PAUSE_LEN_MAX, + DENON_0_PAUSE_LEN_MIN, DENON_0_PAUSE_LEN_MAX); + irmp_param_p = (IRMP_PARAMETER *) &denon_param; + } + else +#endif // IRMP_SUPPORT_DENON_PROTOCOL == 1 + +#if IRMP_SUPPORT_THOMSON_PROTOCOL == 1 + if ( (irmp_pulse_time >= THOMSON_PULSE_LEN_MIN && irmp_pulse_time <= THOMSON_PULSE_LEN_MAX) && + ((irmp_pause_time >= THOMSON_1_PAUSE_LEN_MIN && irmp_pause_time <= THOMSON_1_PAUSE_LEN_MAX) || + (irmp_pause_time >= THOMSON_0_PAUSE_LEN_MIN && irmp_pause_time <= THOMSON_0_PAUSE_LEN_MAX))) + { // it's THOMSON + ANALYZE_PRINTF ("protocol = THOMSON, start bit timings: pulse: %3d - %3d, pause: %3d - %3d or %3d - %3d\n", + THOMSON_PULSE_LEN_MIN, THOMSON_PULSE_LEN_MAX, + THOMSON_1_PAUSE_LEN_MIN, THOMSON_1_PAUSE_LEN_MAX, + THOMSON_0_PAUSE_LEN_MIN, THOMSON_0_PAUSE_LEN_MAX); + irmp_param_p = (IRMP_PARAMETER *) &thomson_param; + } + else +#endif // IRMP_SUPPORT_THOMSON_PROTOCOL == 1 + +#if IRMP_SUPPORT_RC6_PROTOCOL == 1 + if (irmp_pulse_time >= RC6_START_BIT_PULSE_LEN_MIN && irmp_pulse_time <= RC6_START_BIT_PULSE_LEN_MAX && + irmp_pause_time >= RC6_START_BIT_PAUSE_LEN_MIN && irmp_pause_time <= RC6_START_BIT_PAUSE_LEN_MAX) + { // it's RC6 + ANALYZE_PRINTF ("protocol = RC6, start bit timings: pulse: %3d - %3d, pause: %3d - %3d\n", + RC6_START_BIT_PULSE_LEN_MIN, RC6_START_BIT_PULSE_LEN_MAX, + RC6_START_BIT_PAUSE_LEN_MIN, RC6_START_BIT_PAUSE_LEN_MAX); + irmp_param_p = (IRMP_PARAMETER *) &rc6_param; + last_pause = 0; + last_value = 1; + } + else +#endif // IRMP_SUPPORT_RC6_PROTOCOL == 1 + +#if IRMP_SUPPORT_RECS80EXT_PROTOCOL == 1 + if (irmp_pulse_time >= RECS80EXT_START_BIT_PULSE_LEN_MIN && irmp_pulse_time <= RECS80EXT_START_BIT_PULSE_LEN_MAX && + irmp_pause_time >= RECS80EXT_START_BIT_PAUSE_LEN_MIN && irmp_pause_time <= RECS80EXT_START_BIT_PAUSE_LEN_MAX) + { // it's RECS80EXT + ANALYZE_PRINTF ("protocol = RECS80EXT, start bit timings: pulse: %3d - %3d, pause: %3d - %3d\n", + RECS80EXT_START_BIT_PULSE_LEN_MIN, RECS80EXT_START_BIT_PULSE_LEN_MAX, + RECS80EXT_START_BIT_PAUSE_LEN_MIN, RECS80EXT_START_BIT_PAUSE_LEN_MAX); + irmp_param_p = (IRMP_PARAMETER *) &recs80ext_param; + } + else +#endif // IRMP_SUPPORT_RECS80EXT_PROTOCOL == 1 + +#if IRMP_SUPPORT_NUBERT_PROTOCOL == 1 + if (irmp_pulse_time >= NUBERT_START_BIT_PULSE_LEN_MIN && irmp_pulse_time <= NUBERT_START_BIT_PULSE_LEN_MAX && + irmp_pause_time >= NUBERT_START_BIT_PAUSE_LEN_MIN && irmp_pause_time <= NUBERT_START_BIT_PAUSE_LEN_MAX) + { // it's NUBERT + ANALYZE_PRINTF ("protocol = NUBERT, start bit timings: pulse: %3d - %3d, pause: %3d - %3d\n", + NUBERT_START_BIT_PULSE_LEN_MIN, NUBERT_START_BIT_PULSE_LEN_MAX, + NUBERT_START_BIT_PAUSE_LEN_MIN, NUBERT_START_BIT_PAUSE_LEN_MAX); + irmp_param_p = (IRMP_PARAMETER *) &nubert_param; + } + else +#endif // IRMP_SUPPORT_NUBERT_PROTOCOL == 1 + +#if IRMP_SUPPORT_BANG_OLUFSEN_PROTOCOL == 1 + if (irmp_pulse_time >= BANG_OLUFSEN_START_BIT1_PULSE_LEN_MIN && irmp_pulse_time <= BANG_OLUFSEN_START_BIT1_PULSE_LEN_MAX && + irmp_pause_time >= BANG_OLUFSEN_START_BIT1_PAUSE_LEN_MIN && irmp_pause_time <= BANG_OLUFSEN_START_BIT1_PAUSE_LEN_MAX) + { // it's BANG_OLUFSEN + ANALYZE_PRINTF ("protocol = BANG_OLUFSEN\n"); + ANALYZE_PRINTF ("start bit 1 timings: pulse: %3d - %3d, pause: %3d - %3d\n", + BANG_OLUFSEN_START_BIT1_PULSE_LEN_MIN, BANG_OLUFSEN_START_BIT1_PULSE_LEN_MAX, + BANG_OLUFSEN_START_BIT1_PAUSE_LEN_MIN, BANG_OLUFSEN_START_BIT1_PAUSE_LEN_MAX); + ANALYZE_PRINTF ("start bit 2 timings: pulse: %3d - %3d, pause: %3d - %3d\n", + BANG_OLUFSEN_START_BIT2_PULSE_LEN_MIN, BANG_OLUFSEN_START_BIT2_PULSE_LEN_MAX, + BANG_OLUFSEN_START_BIT2_PAUSE_LEN_MIN, BANG_OLUFSEN_START_BIT2_PAUSE_LEN_MAX); + ANALYZE_PRINTF ("start bit 3 timings: pulse: %3d - %3d, pause: %3d - %3d\n", + BANG_OLUFSEN_START_BIT3_PULSE_LEN_MIN, BANG_OLUFSEN_START_BIT3_PULSE_LEN_MAX, + BANG_OLUFSEN_START_BIT3_PAUSE_LEN_MIN, BANG_OLUFSEN_START_BIT3_PAUSE_LEN_MAX); + ANALYZE_PRINTF ("start bit 4 timings: pulse: %3d - %3d, pause: %3d - %3d\n", + BANG_OLUFSEN_START_BIT4_PULSE_LEN_MIN, BANG_OLUFSEN_START_BIT4_PULSE_LEN_MAX, + BANG_OLUFSEN_START_BIT4_PAUSE_LEN_MIN, BANG_OLUFSEN_START_BIT4_PAUSE_LEN_MAX); + irmp_param_p = (IRMP_PARAMETER *) &bang_olufsen_param; + last_value = 0; + } + else +#endif // IRMP_SUPPORT_BANG_OLUFSEN_PROTOCOL == 1 + +#if IRMP_SUPPORT_GRUNDIG_NOKIA_IR60_PROTOCOL == 1 + if (irmp_pulse_time >= GRUNDIG_NOKIA_IR60_START_BIT_LEN_MIN && irmp_pulse_time <= GRUNDIG_NOKIA_IR60_START_BIT_LEN_MAX && + irmp_pause_time >= GRUNDIG_NOKIA_IR60_PRE_PAUSE_LEN_MIN && irmp_pause_time <= GRUNDIG_NOKIA_IR60_PRE_PAUSE_LEN_MAX) + { // it's GRUNDIG + ANALYZE_PRINTF ("protocol = GRUNDIG, pre bit timings: pulse: %3d - %3d, pause: %3d - %3d\n", + GRUNDIG_NOKIA_IR60_START_BIT_LEN_MIN, GRUNDIG_NOKIA_IR60_START_BIT_LEN_MAX, + GRUNDIG_NOKIA_IR60_PRE_PAUSE_LEN_MIN, GRUNDIG_NOKIA_IR60_PRE_PAUSE_LEN_MAX); + irmp_param_p = (IRMP_PARAMETER *) &grundig_param; + last_pause = irmp_pause_time; + last_value = 1; + } + else +#endif // IRMP_SUPPORT_GRUNDIG_NOKIA_IR60_PROTOCOL == 1 + +#if IRMP_SUPPORT_SIEMENS_OR_RUWIDO_PROTOCOL == 1 + if (((irmp_pulse_time >= SIEMENS_OR_RUWIDO_START_BIT_PULSE_LEN_MIN && irmp_pulse_time <= SIEMENS_OR_RUWIDO_START_BIT_PULSE_LEN_MAX) || + (irmp_pulse_time >= 2 * SIEMENS_OR_RUWIDO_START_BIT_PULSE_LEN_MIN && irmp_pulse_time <= 2 * SIEMENS_OR_RUWIDO_START_BIT_PULSE_LEN_MAX)) && + ((irmp_pause_time >= SIEMENS_OR_RUWIDO_START_BIT_PAUSE_LEN_MIN && irmp_pause_time <= SIEMENS_OR_RUWIDO_START_BIT_PAUSE_LEN_MAX) || + (irmp_pause_time >= 2 * SIEMENS_OR_RUWIDO_START_BIT_PAUSE_LEN_MIN && irmp_pause_time <= 2 * SIEMENS_OR_RUWIDO_START_BIT_PAUSE_LEN_MAX))) + { // it's RUWIDO or SIEMENS + ANALYZE_PRINTF ("protocol = RUWIDO, start bit timings: pulse: %3d - %3d or %3d - %3d, pause: %3d - %3d or %3d - %3d\n", + SIEMENS_OR_RUWIDO_START_BIT_PULSE_LEN_MIN, SIEMENS_OR_RUWIDO_START_BIT_PULSE_LEN_MAX, + 2 * SIEMENS_OR_RUWIDO_START_BIT_PULSE_LEN_MIN, 2 * SIEMENS_OR_RUWIDO_START_BIT_PULSE_LEN_MAX, + SIEMENS_OR_RUWIDO_START_BIT_PAUSE_LEN_MIN, SIEMENS_OR_RUWIDO_START_BIT_PAUSE_LEN_MAX, + 2 * SIEMENS_OR_RUWIDO_START_BIT_PAUSE_LEN_MIN, 2 * SIEMENS_OR_RUWIDO_START_BIT_PAUSE_LEN_MAX); + irmp_param_p = (IRMP_PARAMETER *) &ruwido_param; + last_pause = irmp_pause_time; + last_value = 1; + } + else +#endif // IRMP_SUPPORT_SIEMENS_OR_RUWIDO_PROTOCOL == 1 + +#if IRMP_SUPPORT_GRUNDIG2_PROTOCOL == 1 + if ((irmp_pulse_time >= GRUNDIG2_START_BIT_PULSE_LEN_MIN && irmp_pulse_time <= GRUNDIG2_START_BIT_PULSE_LEN_MAX) && + (irmp_pause_time >= GRUNDIG2_START_BIT_PAUSE_LEN_MIN && irmp_pause_time <= GRUNDIG2_START_BIT_PAUSE_LEN_MAX)) + { // it's GRUNDIG2 + ANALYZE_PRINTF ("protocol = GRUNDIG2, start bit timings: pulse: %3d - %3d or %3d - %3d, pause: %3d - %3d or %3d - %3d\n", + GRUNDIG2_START_BIT_PULSE_LEN_MIN, GRUNDIG2_START_BIT_PULSE_LEN_MAX, + 2 * GRUNDIG2_START_BIT_PULSE_LEN_MIN, 2 * GRUNDIG2_START_BIT_PULSE_LEN_MAX, + GRUNDIG2_START_BIT_PAUSE_LEN_MIN, GRUNDIG2_START_BIT_PAUSE_LEN_MAX, + 2 * GRUNDIG2_START_BIT_PAUSE_LEN_MIN, 2 * GRUNDIG2_START_BIT_PAUSE_LEN_MAX); + irmp_param_p = (IRMP_PARAMETER *) &grundig2_param; + last_pause = irmp_pause_time; + last_value = 1; + } + else +#endif // IRMP_SUPPORT_SIEMENS_OR_RUWIDO_PROTOCOL == 1 + +#if IRMP_SUPPORT_FDC_PROTOCOL == 1 + if (irmp_pulse_time >= FDC_START_BIT_PULSE_LEN_MIN && irmp_pulse_time <= FDC_START_BIT_PULSE_LEN_MAX && + irmp_pause_time >= FDC_START_BIT_PAUSE_LEN_MIN && irmp_pause_time <= FDC_START_BIT_PAUSE_LEN_MAX) + { + ANALYZE_PRINTF ("protocol = FDC, start bit timings: pulse: %3d - %3d, pause: %3d - %3d\n", + FDC_START_BIT_PULSE_LEN_MIN, FDC_START_BIT_PULSE_LEN_MAX, + FDC_START_BIT_PAUSE_LEN_MIN, FDC_START_BIT_PAUSE_LEN_MAX); + irmp_param_p = (IRMP_PARAMETER *) &fdc_param; + } + else +#endif // IRMP_SUPPORT_FDC_PROTOCOL == 1 + +#if IRMP_SUPPORT_RCCAR_PROTOCOL == 1 + if (irmp_pulse_time >= RCCAR_START_BIT_PULSE_LEN_MIN && irmp_pulse_time <= RCCAR_START_BIT_PULSE_LEN_MAX && + irmp_pause_time >= RCCAR_START_BIT_PAUSE_LEN_MIN && irmp_pause_time <= RCCAR_START_BIT_PAUSE_LEN_MAX) + { + ANALYZE_PRINTF ("protocol = RCCAR, start bit timings: pulse: %3d - %3d, pause: %3d - %3d\n", + RCCAR_START_BIT_PULSE_LEN_MIN, RCCAR_START_BIT_PULSE_LEN_MAX, + RCCAR_START_BIT_PAUSE_LEN_MIN, RCCAR_START_BIT_PAUSE_LEN_MAX); + irmp_param_p = (IRMP_PARAMETER *) &rccar_param; + } + else +#endif // IRMP_SUPPORT_RCCAR_PROTOCOL == 1 + +#if IRMP_SUPPORT_KATHREIN_PROTOCOL == 1 + if (irmp_pulse_time >= KATHREIN_START_BIT_PULSE_LEN_MIN && irmp_pulse_time <= KATHREIN_START_BIT_PULSE_LEN_MAX && + irmp_pause_time >= KATHREIN_START_BIT_PAUSE_LEN_MIN && irmp_pause_time <= KATHREIN_START_BIT_PAUSE_LEN_MAX) + { // it's KATHREIN + ANALYZE_PRINTF ("protocol = KATHREIN, start bit timings: pulse: %3d - %3d, pause: %3d - %3d\n", + KATHREIN_START_BIT_PULSE_LEN_MIN, KATHREIN_START_BIT_PULSE_LEN_MAX, + KATHREIN_START_BIT_PAUSE_LEN_MIN, KATHREIN_START_BIT_PAUSE_LEN_MAX); + irmp_param_p = (IRMP_PARAMETER *) &kathrein_param; + } + else +#endif // IRMP_SUPPORT_KATHREIN_PROTOCOL == 1 + +#if IRMP_SUPPORT_NETBOX_PROTOCOL == 1 + if (irmp_pulse_time >= NETBOX_START_BIT_PULSE_LEN_MIN && irmp_pulse_time <= NETBOX_START_BIT_PULSE_LEN_MAX && + irmp_pause_time >= NETBOX_START_BIT_PAUSE_LEN_MIN && irmp_pause_time <= NETBOX_START_BIT_PAUSE_LEN_MAX) + { // it's NETBOX + ANALYZE_PRINTF ("protocol = NETBOX, start bit timings: pulse: %3d - %3d, pause: %3d - %3d\n", + NETBOX_START_BIT_PULSE_LEN_MIN, NETBOX_START_BIT_PULSE_LEN_MAX, + NETBOX_START_BIT_PAUSE_LEN_MIN, NETBOX_START_BIT_PAUSE_LEN_MAX); + irmp_param_p = (IRMP_PARAMETER *) &netbox_param; + } + else +#endif // IRMP_SUPPORT_NETBOX_PROTOCOL == 1 + +#if IRMP_SUPPORT_LEGO_PROTOCOL == 1 + if (irmp_pulse_time >= LEGO_START_BIT_PULSE_LEN_MIN && irmp_pulse_time <= LEGO_START_BIT_PULSE_LEN_MAX && + irmp_pause_time >= LEGO_START_BIT_PAUSE_LEN_MIN && irmp_pause_time <= LEGO_START_BIT_PAUSE_LEN_MAX) + { + ANALYZE_PRINTF ("protocol = LEGO, start bit timings: pulse: %3d - %3d, pause: %3d - %3d\n", + LEGO_START_BIT_PULSE_LEN_MIN, LEGO_START_BIT_PULSE_LEN_MAX, + LEGO_START_BIT_PAUSE_LEN_MIN, LEGO_START_BIT_PAUSE_LEN_MAX); + irmp_param_p = (IRMP_PARAMETER *) &lego_param; + } + else +#endif // IRMP_SUPPORT_LEGO_PROTOCOL == 1 + + { + ANALYZE_PRINTF ("protocol = UNKNOWN\n"); +// irmp_busy_flag = FALSE; + irmp_start_bit_detected = 0; // wait for another start bit... + } + + if (irmp_start_bit_detected) + { + memcpy_P (&irmp_param, irmp_param_p, sizeof (IRMP_PARAMETER)); + +#ifdef ANALYZE + if (! (irmp_param.flags & IRMP_PARAM_FLAG_IS_MANCHESTER)) + { + ANALYZE_PRINTF ("pulse_1: %3d - %3d\n", irmp_param.pulse_1_len_min, irmp_param.pulse_1_len_max); + ANALYZE_PRINTF ("pause_1: %3d - %3d\n", irmp_param.pause_1_len_min, irmp_param.pause_1_len_max); + } + else + { + ANALYZE_PRINTF ("pulse: %3d - %3d or %3d - %3d\n", irmp_param.pulse_1_len_min, irmp_param.pulse_1_len_max, + 2 * irmp_param.pulse_1_len_min, 2 * irmp_param.pulse_1_len_max); + ANALYZE_PRINTF ("pause: %3d - %3d or %3d - %3d\n", irmp_param.pause_1_len_min, irmp_param.pause_1_len_max, + 2 * irmp_param.pause_1_len_min, 2 * irmp_param.pause_1_len_max); + } + +#if IRMP_SUPPORT_RC5_PROTOCOL == 1 && (IRMP_SUPPORT_FDC_PROTOCOL == 1 || IRMP_SUPPORT_RCCAR_PROTOCOL == 1) + if (irmp_param2.protocol) + { + ANALYZE_PRINTF ("pulse_0: %3d - %3d\n", irmp_param2.pulse_0_len_min, irmp_param2.pulse_0_len_max); + ANALYZE_PRINTF ("pause_0: %3d - %3d\n", irmp_param2.pause_0_len_min, irmp_param2.pause_0_len_max); + ANALYZE_PRINTF ("pulse_1: %3d - %3d\n", irmp_param2.pulse_1_len_min, irmp_param2.pulse_1_len_max); + ANALYZE_PRINTF ("pause_1: %3d - %3d\n", irmp_param2.pause_1_len_min, irmp_param2.pause_1_len_max); + } +#endif + + +#if IRMP_SUPPORT_RC6_PROTOCOL == 1 + if (irmp_param.protocol == IRMP_RC6_PROTOCOL) + { + ANALYZE_PRINTF ("pulse_toggle: %3d - %3d\n", RC6_TOGGLE_BIT_LEN_MIN, RC6_TOGGLE_BIT_LEN_MAX); + } +#endif + + if (! (irmp_param.flags & IRMP_PARAM_FLAG_IS_MANCHESTER)) + { + ANALYZE_PRINTF ("pulse_0: %3d - %3d\n", irmp_param.pulse_0_len_min, irmp_param.pulse_0_len_max); + ANALYZE_PRINTF ("pause_0: %3d - %3d\n", irmp_param.pause_0_len_min, irmp_param.pause_0_len_max); + } + else + { + ANALYZE_PRINTF ("pulse: %3d - %3d or %3d - %3d\n", irmp_param.pulse_0_len_min, irmp_param.pulse_0_len_max, + 2 * irmp_param.pulse_0_len_min, 2 * irmp_param.pulse_0_len_max); + ANALYZE_PRINTF ("pause: %3d - %3d or %3d - %3d\n", irmp_param.pause_0_len_min, irmp_param.pause_0_len_max, + 2 * irmp_param.pause_0_len_min, 2 * irmp_param.pause_0_len_max); + } + +#if IRMP_SUPPORT_BANG_OLUFSEN_PROTOCOL == 1 + if (irmp_param.protocol == IRMP_BANG_OLUFSEN_PROTOCOL) + { + ANALYZE_PRINTF ("pulse_r: %3d - %3d\n", irmp_param.pulse_0_len_min, irmp_param.pulse_0_len_max); + ANALYZE_PRINTF ("pause_r: %3d - %3d\n", BANG_OLUFSEN_R_PAUSE_LEN_MIN, BANG_OLUFSEN_R_PAUSE_LEN_MAX); + } +#endif + + ANALYZE_PRINTF ("command_offset: %2d\n", irmp_param.command_offset); + ANALYZE_PRINTF ("command_len: %3d\n", irmp_param.command_end - irmp_param.command_offset); + ANALYZE_PRINTF ("complete_len: %3d\n", irmp_param.complete_len); + ANALYZE_PRINTF ("stop_bit: %3d\n", irmp_param.stop_bit); +#endif // ANALYZE + } + + irmp_bit = 0; + +#if IRMP_SUPPORT_MANCHESTER == 1 + if ((irmp_param.flags & IRMP_PARAM_FLAG_IS_MANCHESTER) && + irmp_param.protocol != IRMP_RUWIDO_PROTOCOL && // Manchester, but not RUWIDO + irmp_param.protocol != IRMP_RC6_PROTOCOL) // Manchester, but not RC6 + { + if (irmp_pause_time > irmp_param.pulse_1_len_max && irmp_pause_time <= 2 * irmp_param.pulse_1_len_max) + { + ANALYZE_PRINTF ("%8.3fms [bit %2d: pulse = %3d, pause = %3d] ", (double) (time_counter * 1000) / F_INTERRUPTS, irmp_bit, irmp_pulse_time, irmp_pause_time); + ANALYZE_PUTCHAR ((irmp_param.flags & IRMP_PARAM_FLAG_1ST_PULSE_IS_1) ? '0' : '1'); + ANALYZE_NEWLINE (); + irmp_store_bit ((irmp_param.flags & IRMP_PARAM_FLAG_1ST_PULSE_IS_1) ? 0 : 1); + } + else if (! last_value) // && irmp_pause_time >= irmp_param.pause_1_len_min && irmp_pause_time <= irmp_param.pause_1_len_max) + { + ANALYZE_PRINTF ("%8.3fms [bit %2d: pulse = %3d, pause = %3d] ", (double) (time_counter * 1000) / F_INTERRUPTS, irmp_bit, irmp_pulse_time, irmp_pause_time); + + ANALYZE_PUTCHAR ((irmp_param.flags & IRMP_PARAM_FLAG_1ST_PULSE_IS_1) ? '1' : '0'); + ANALYZE_NEWLINE (); + irmp_store_bit ((irmp_param.flags & IRMP_PARAM_FLAG_1ST_PULSE_IS_1) ? 1 : 0); + } + } + else +#endif // IRMP_SUPPORT_MANCHESTER == 1 + +#if IRMP_SUPPORT_SERIAL == 1 + if (irmp_param.flags & IRMP_PARAM_FLAG_IS_SERIAL) + { + ; // do nothing + } + else +#endif // IRMP_SUPPORT_SERIAL == 1 + + +#if IRMP_SUPPORT_DENON_PROTOCOL == 1 + if (irmp_param.protocol == IRMP_DENON_PROTOCOL) + { + ANALYZE_PRINTF ("%8.3fms [bit %2d: pulse = %3d, pause = %3d] ", (double) (time_counter * 1000) / F_INTERRUPTS, irmp_bit, irmp_pulse_time, irmp_pause_time); + + if (irmp_pause_time >= DENON_1_PAUSE_LEN_MIN && irmp_pause_time <= DENON_1_PAUSE_LEN_MAX) + { // pause timings correct for "1"? + ANALYZE_PUTCHAR ('1'); // yes, store 1 + ANALYZE_NEWLINE (); + irmp_store_bit (1); + } + else // if (irmp_pause_time >= DENON_0_PAUSE_LEN_MIN && irmp_pause_time <= DENON_0_PAUSE_LEN_MAX) + { // pause timings correct for "0"? + ANALYZE_PUTCHAR ('0'); // yes, store 0 + ANALYZE_NEWLINE (); + irmp_store_bit (0); + } + } + else +#endif // IRMP_SUPPORT_DENON_PROTOCOL == 1 +#if IRMP_SUPPORT_THOMSON_PROTOCOL == 1 + if (irmp_param.protocol == IRMP_THOMSON_PROTOCOL) + { + ANALYZE_PRINTF ("%8.3fms [bit %2d: pulse = %3d, pause = %3d] ", (double) (time_counter * 1000) / F_INTERRUPTS, irmp_bit, irmp_pulse_time, irmp_pause_time); + + if (irmp_pause_time >= THOMSON_1_PAUSE_LEN_MIN && irmp_pause_time <= THOMSON_1_PAUSE_LEN_MAX) + { // pause timings correct for "1"? + ANALYZE_PUTCHAR ('1'); // yes, store 1 + ANALYZE_NEWLINE (); + irmp_store_bit (1); + } + else // if (irmp_pause_time >= THOMSON_0_PAUSE_LEN_MIN && irmp_pause_time <= THOMSON_0_PAUSE_LEN_MAX) + { // pause timings correct for "0"? + ANALYZE_PUTCHAR ('0'); // yes, store 0 + ANALYZE_NEWLINE (); + irmp_store_bit (0); + } + } + else +#endif // IRMP_SUPPORT_THOMSON_PROTOCOL == 1 + { + ; // else do nothing + } + + irmp_pulse_time = 1; // set counter to 1, not 0 + irmp_pause_time = 0; + wait_for_start_space = 0; + } + } + else if (wait_for_space) // the data section.... + { // counting the time of darkness.... + uint8_t got_light = FALSE; + + if (irmp_input) // still dark? + { // yes... + if (irmp_bit == irmp_param.complete_len && irmp_param.stop_bit == 1) + { + if ( +#if IRMP_SUPPORT_MANCHESTER == 1 + (irmp_param.flags & IRMP_PARAM_FLAG_IS_MANCHESTER) || +#endif +#if IRMP_SUPPORT_SERIAL == 1 + (irmp_param.flags & IRMP_PARAM_FLAG_IS_SERIAL) || +#endif + (irmp_pulse_time >= irmp_param.pulse_0_len_min && irmp_pulse_time <= irmp_param.pulse_0_len_max)) + { +#ifdef ANALYZE + if (! (irmp_param.flags & IRMP_PARAM_FLAG_IS_MANCHESTER)) + { + ANALYZE_PRINTF ("stop bit detected\n"); + } +#endif + irmp_param.stop_bit = 0; + } + else + { + ANALYZE_PRINTF ("error: stop bit timing wrong, irmp_bit = %d, irmp_pulse_time = %d, pulse_0_len_min = %d, pulse_0_len_max = %d\n", + irmp_bit, irmp_pulse_time, irmp_param.pulse_0_len_min, irmp_param.pulse_0_len_max); + +// irmp_busy_flag = FALSE; + irmp_start_bit_detected = 0; // wait for another start bit... + irmp_pulse_time = 0; + irmp_pause_time = 0; + } + } + else + { + irmp_pause_time++; // increment counter + +#if IRMP_SUPPORT_SIRCS_PROTOCOL == 1 + if (irmp_param.protocol == IRMP_SIRCS_PROTOCOL && // Sony has a variable number of bits: + irmp_pause_time > SIRCS_PAUSE_LEN_MAX && // minimum is 12 + irmp_bit >= 12 - 1) // pause too long? + { // yes, break and close this frame + irmp_param.complete_len = irmp_bit + 1; // set new complete length + got_light = TRUE; // this is a lie, but helps (generates stop bit) + irmp_tmp_address |= (irmp_bit - SIRCS_MINIMUM_DATA_LEN + 1) << 8; // new: store number of additional bits in upper byte of address! + irmp_param.command_end = irmp_param.command_offset + irmp_bit + 1; // correct command length + irmp_pause_time = SIRCS_PAUSE_LEN_MAX - 1; // correct pause length + } + else +#endif +#if IRMP_SUPPORT_SERIAL == 1 + // NETBOX generates no stop bit, here is the timeout condition: + if ((irmp_param.flags & IRMP_PARAM_FLAG_IS_SERIAL) && irmp_param.protocol == IRMP_NETBOX_PROTOCOL && + irmp_pause_time >= NETBOX_PULSE_LEN * (NETBOX_COMPLETE_DATA_LEN - irmp_bit)) + { + got_light = TRUE; // this is a lie, but helps (generates stop bit) + } + else +#endif +#if IRMP_SUPPORT_GRUNDIG_NOKIA_IR60_PROTOCOL == 1 + if (irmp_param.protocol == IRMP_GRUNDIG_PROTOCOL && !irmp_param.stop_bit) + { + if (irmp_pause_time > IR60_TIMEOUT_LEN && irmp_bit == 6) + { + ANALYZE_PRINTF ("Switching to IR60 protocol\n"); + got_light = TRUE; // this is a lie, but generates a stop bit ;-) + irmp_param.stop_bit = TRUE; // set flag + + irmp_param.protocol = IRMP_IR60_PROTOCOL; // change protocol + irmp_param.complete_len = IR60_COMPLETE_DATA_LEN; // correct complete len + irmp_param.address_offset = IR60_ADDRESS_OFFSET; + irmp_param.address_end = IR60_ADDRESS_OFFSET + IR60_ADDRESS_LEN; + irmp_param.command_offset = IR60_COMMAND_OFFSET; + irmp_param.command_end = IR60_COMMAND_OFFSET + IR60_COMMAND_LEN; + + irmp_tmp_command <<= 1; + irmp_tmp_command |= first_bit; + } + else if (irmp_pause_time >= 2 * irmp_param.pause_1_len_max && irmp_bit >= GRUNDIG_COMPLETE_DATA_LEN - 2) + { // special manchester decoder + irmp_param.complete_len = GRUNDIG_COMPLETE_DATA_LEN; // correct complete len + got_light = TRUE; // this is a lie, but generates a stop bit ;-) + irmp_param.stop_bit = TRUE; // set flag + } + else if (irmp_bit >= GRUNDIG_COMPLETE_DATA_LEN) + { + ANALYZE_PRINTF ("Switching to NOKIA protocol\n"); + irmp_param.protocol = IRMP_NOKIA_PROTOCOL; // change protocol + irmp_param.address_offset = NOKIA_ADDRESS_OFFSET; + irmp_param.address_end = NOKIA_ADDRESS_OFFSET + NOKIA_ADDRESS_LEN; + irmp_param.command_offset = NOKIA_COMMAND_OFFSET; + irmp_param.command_end = NOKIA_COMMAND_OFFSET + NOKIA_COMMAND_LEN; + + if (irmp_tmp_command & 0x300) + { + irmp_tmp_address = (irmp_tmp_command >> 8); + irmp_tmp_command &= 0xFF; + } + } + } + else +#endif +#if IRMP_SUPPORT_SIEMENS_OR_RUWIDO_PROTOCOL == 1 + if (irmp_param.protocol == IRMP_RUWIDO_PROTOCOL && !irmp_param.stop_bit) + { + if (irmp_pause_time >= 2 * irmp_param.pause_1_len_max && irmp_bit >= RUWIDO_COMPLETE_DATA_LEN - 2) + { // special manchester decoder + irmp_param.complete_len = RUWIDO_COMPLETE_DATA_LEN; // correct complete len + got_light = TRUE; // this is a lie, but generates a stop bit ;-) + irmp_param.stop_bit = TRUE; // set flag + } + else if (irmp_bit >= RUWIDO_COMPLETE_DATA_LEN) + { + ANALYZE_PRINTF ("Switching to SIEMENS protocol\n"); + irmp_param.protocol = IRMP_SIEMENS_PROTOCOL; // change protocol + irmp_param.address_offset = SIEMENS_ADDRESS_OFFSET; + irmp_param.address_end = SIEMENS_ADDRESS_OFFSET + SIEMENS_ADDRESS_LEN; + irmp_param.command_offset = SIEMENS_COMMAND_OFFSET; + irmp_param.command_end = SIEMENS_COMMAND_OFFSET + SIEMENS_COMMAND_LEN; + + // 76543210 + // RUWIDO: AAAAAAAAACCCCCCCp + // SIEMENS: AAAAAAAAAAACCCCCCCCCCp + irmp_tmp_address <<= 2; + irmp_tmp_address |= (irmp_tmp_command >> 6); + irmp_tmp_command &= 0x003F; + irmp_tmp_command <<= 4; + irmp_tmp_command |= last_value; + } + } + else +#endif +#if IRMP_SUPPORT_MANCHESTER == 1 + if ((irmp_param.flags & IRMP_PARAM_FLAG_IS_MANCHESTER) && + irmp_pause_time >= 2 * irmp_param.pause_1_len_max && irmp_bit >= irmp_param.complete_len - 2 && !irmp_param.stop_bit) + { // special manchester decoder + got_light = TRUE; // this is a lie, but generates a stop bit ;-) + irmp_param.stop_bit = TRUE; // set flag + } + else +#endif // IRMP_SUPPORT_MANCHESTER == 1 + if (irmp_pause_time > IRMP_TIMEOUT_LEN) // timeout? + { // yes... + if (irmp_bit == irmp_param.complete_len - 1 && irmp_param.stop_bit == 0) + { + irmp_bit++; + } +#if IRMP_SUPPORT_JVC_PROTOCOL == 1 + else if (irmp_param.protocol == IRMP_NEC_PROTOCOL && (irmp_bit == 16 || irmp_bit == 17)) // it was a JVC stop bit + { + ANALYZE_PRINTF ("Switching to JVC protocol, irmp_bit = %d\n", irmp_bit); + irmp_param.stop_bit = TRUE; // set flag + irmp_param.protocol = IRMP_JVC_PROTOCOL; // switch protocol + irmp_param.complete_len = irmp_bit; // patch length: 16 or 17 + irmp_tmp_command = (irmp_tmp_address >> 4); // set command: upper 12 bits are command bits + irmp_tmp_address = irmp_tmp_address & 0x000F; // lower 4 bits are address bits + irmp_start_bit_detected = 1; // tricky: don't wait for another start bit... + } +#endif // IRMP_SUPPORT_JVC_PROTOCOL == 1 + +#if IRMP_SUPPORT_NEC42_PROTOCOL == 1 +#if IRMP_SUPPORT_NEC_PROTOCOL == 1 + else if (irmp_param.protocol == IRMP_NEC42_PROTOCOL && irmp_bit == 32) // it was a NEC stop bit + { + ANALYZE_PRINTF ("Switching to NEC protocol\n"); + irmp_param.stop_bit = TRUE; // set flag + irmp_param.protocol = IRMP_NEC_PROTOCOL; // switch protocol + irmp_param.complete_len = irmp_bit; // patch length: 16 or 17 + + // 0123456789ABC0123456789ABC0123456701234567 + // NEC42: AAAAAAAAAAAAAaaaaaaaaaaaaaCCCCCCCCcccccccc + // NEC: AAAAAAAAaaaaaaaaCCCCCCCCcccccccc + irmp_tmp_address |= (irmp_tmp_address2 & 0x0007) << 13; // fm 2012-02-13: 12 -> 13 + irmp_tmp_command = (irmp_tmp_address2 >> 3) | (irmp_tmp_command << 10); + } +#endif // IRMP_SUPPORT_NEC_PROTOCOL == 1 +#if IRMP_SUPPORT_JVC_PROTOCOL == 1 + else if (irmp_param.protocol == IRMP_NEC42_PROTOCOL && (irmp_bit == 16 || irmp_bit == 17)) // it was a JVC stop bit + { + ANALYZE_PRINTF ("Switching to JVC protocol, irmp_bit = %d\n", irmp_bit); + irmp_param.stop_bit = TRUE; // set flag + irmp_param.protocol = IRMP_JVC_PROTOCOL; // switch protocol + irmp_param.complete_len = irmp_bit; // patch length: 16 or 17 + + // 0123456789ABC0123456789ABC0123456701234567 + // NEC42: AAAAAAAAAAAAAaaaaaaaaaaaaaCCCCCCCCcccccccc + // JVC: AAAACCCCCCCCCCCC + irmp_tmp_command = (irmp_tmp_address >> 4) | (irmp_tmp_address2 << 9); // set command: upper 12 bits are command bits + irmp_tmp_address = irmp_tmp_address & 0x000F; // lower 4 bits are address bits + } +#endif // IRMP_SUPPORT_JVC_PROTOCOL == 1 +#endif // IRMP_SUPPORT_NEC42_PROTOCOL == 1 + else + { + ANALYZE_PRINTF ("error 2: pause %d after data bit %d too long\n", irmp_pause_time, irmp_bit); + ANALYZE_ONLY_NORMAL_PUTCHAR ('\n'); + +// irmp_busy_flag = FALSE; + irmp_start_bit_detected = 0; // wait for another start bit... + irmp_pulse_time = 0; + irmp_pause_time = 0; + } + } + } + } + else + { // got light now! + got_light = TRUE; + } + + if (got_light) + { + ANALYZE_PRINTF ("%8.3fms [bit %2d: pulse = %3d, pause = %3d] ", (double) (time_counter * 1000) / F_INTERRUPTS, irmp_bit, irmp_pulse_time, irmp_pause_time); + +#if IRMP_SUPPORT_MANCHESTER == 1 + if ((irmp_param.flags & IRMP_PARAM_FLAG_IS_MANCHESTER)) // Manchester + { +#if 1 + if (irmp_pulse_time > irmp_param.pulse_1_len_max /* && irmp_pulse_time <= 2 * irmp_param.pulse_1_len_max */) +#else // better, but some IR-RCs use asymmetric timings :-/ + if (irmp_pulse_time > irmp_param.pulse_1_len_max && irmp_pulse_time <= 2 * irmp_param.pulse_1_len_max && + irmp_pause_time <= 2 * irmp_param.pause_1_len_max) +#endif + { +#if IRMP_SUPPORT_RC6_PROTOCOL == 1 + if (irmp_param.protocol == IRMP_RC6_PROTOCOL && irmp_bit == 4 && irmp_pulse_time > RC6_TOGGLE_BIT_LEN_MIN) // RC6 toggle bit + { + ANALYZE_PUTCHAR ('T'); + if (irmp_param.complete_len == RC6_COMPLETE_DATA_LEN_LONG) // RC6 mode 6A + { + irmp_store_bit (1); + last_value = 1; + } + else // RC6 mode 0 + { + irmp_store_bit (0); + last_value = 0; + } + ANALYZE_NEWLINE (); + } + else +#endif // IRMP_SUPPORT_RC6_PROTOCOL == 1 + { + ANALYZE_PUTCHAR ((irmp_param.flags & IRMP_PARAM_FLAG_1ST_PULSE_IS_1) ? '0' : '1'); + irmp_store_bit ((irmp_param.flags & IRMP_PARAM_FLAG_1ST_PULSE_IS_1) ? 0 : 1 ); + +#if IRMP_SUPPORT_RC6_PROTOCOL == 1 + if (irmp_param.protocol == IRMP_RC6_PROTOCOL && irmp_bit == 4 && irmp_pulse_time > RC6_TOGGLE_BIT_LEN_MIN) // RC6 toggle bit + { + ANALYZE_PUTCHAR ('T'); + irmp_store_bit (1); + + if (irmp_pause_time > 2 * irmp_param.pause_1_len_max) + { + last_value = 0; + } + else + { + last_value = 1; + } + ANALYZE_NEWLINE (); + } + else +#endif // IRMP_SUPPORT_RC6_PROTOCOL == 1 + { + ANALYZE_PUTCHAR ((irmp_param.flags & IRMP_PARAM_FLAG_1ST_PULSE_IS_1) ? '1' : '0'); + irmp_store_bit ((irmp_param.flags & IRMP_PARAM_FLAG_1ST_PULSE_IS_1) ? 1 : 0 ); +#if IRMP_SUPPORT_RC5_PROTOCOL == 1 && (IRMP_SUPPORT_FDC_PROTOCOL == 1 || IRMP_SUPPORT_RCCAR_PROTOCOL == 1) + if (! irmp_param2.protocol) +#endif + { + ANALYZE_NEWLINE (); + } + last_value = (irmp_param.flags & IRMP_PARAM_FLAG_1ST_PULSE_IS_1) ? 1 : 0; + } + } + } + else if (irmp_pulse_time >= irmp_param.pulse_1_len_min && irmp_pulse_time <= irmp_param.pulse_1_len_max + /* && irmp_pause_time <= 2 * irmp_param.pause_1_len_max */) + { + uint8_t manchester_value; + + if (last_pause > irmp_param.pause_1_len_max && last_pause <= 2 * irmp_param.pause_1_len_max) + { + manchester_value = last_value ? 0 : 1; + last_value = manchester_value; + } + else + { + manchester_value = last_value; + } + + ANALYZE_PUTCHAR (manchester_value + '0'); + +#if IRMP_SUPPORT_RC5_PROTOCOL == 1 && (IRMP_SUPPORT_FDC_PROTOCOL == 1 || IRMP_SUPPORT_RCCAR_PROTOCOL == 1) + if (! irmp_param2.protocol) +#endif + { + ANALYZE_NEWLINE (); + } + +#if IRMP_SUPPORT_RC6_PROTOCOL == 1 + if (irmp_param.protocol == IRMP_RC6_PROTOCOL && irmp_bit == 1 && manchester_value == 1) // RC6 mode != 0 ??? + { + ANALYZE_PRINTF ("Switching to RC6A protocol\n"); + irmp_param.complete_len = RC6_COMPLETE_DATA_LEN_LONG; + irmp_param.address_offset = 5; + irmp_param.address_end = irmp_param.address_offset + 15; + irmp_param.command_offset = irmp_param.address_end + 1; // skip 1 system bit, changes like a toggle bit + irmp_param.command_end = irmp_param.command_offset + 16 - 1; + irmp_tmp_address = 0; + } +#endif // IRMP_SUPPORT_RC6_PROTOCOL == 1 + + irmp_store_bit (manchester_value); + } + else + { +#if IRMP_SUPPORT_RC5_PROTOCOL == 1 && IRMP_SUPPORT_FDC_PROTOCOL == 1 + if (irmp_param2.protocol == IRMP_FDC_PROTOCOL && + irmp_pulse_time >= FDC_PULSE_LEN_MIN && irmp_pulse_time <= FDC_PULSE_LEN_MAX && + ((irmp_pause_time >= FDC_1_PAUSE_LEN_MIN && irmp_pause_time <= FDC_1_PAUSE_LEN_MAX) || + (irmp_pause_time >= FDC_0_PAUSE_LEN_MIN && irmp_pause_time <= FDC_0_PAUSE_LEN_MAX))) + { + ANALYZE_PUTCHAR ('?'); + irmp_param.protocol = 0; // switch to FDC, see below + } + else +#endif // IRMP_SUPPORT_FDC_PROTOCOL == 1 +#if IRMP_SUPPORT_RC5_PROTOCOL == 1 && IRMP_SUPPORT_RCCAR_PROTOCOL == 1 + if (irmp_param2.protocol == IRMP_RCCAR_PROTOCOL && + irmp_pulse_time >= RCCAR_PULSE_LEN_MIN && irmp_pulse_time <= RCCAR_PULSE_LEN_MAX && + ((irmp_pause_time >= RCCAR_1_PAUSE_LEN_MIN && irmp_pause_time <= RCCAR_1_PAUSE_LEN_MAX) || + (irmp_pause_time >= RCCAR_0_PAUSE_LEN_MIN && irmp_pause_time <= RCCAR_0_PAUSE_LEN_MAX))) + { + ANALYZE_PUTCHAR ('?'); + irmp_param.protocol = 0; // switch to RCCAR, see below + } + else +#endif // IRMP_SUPPORT_RCCAR_PROTOCOL == 1 + { + ANALYZE_PUTCHAR ('?'); + ANALYZE_NEWLINE (); + ANALYZE_PRINTF ("error 3 manchester: timing not correct: data bit %d, pulse: %d, pause: %d\n", irmp_bit, irmp_pulse_time, irmp_pause_time); + ANALYZE_ONLY_NORMAL_PUTCHAR ('\n'); +// irmp_busy_flag = FALSE; + irmp_start_bit_detected = 0; // reset flags and wait for next start bit + irmp_pause_time = 0; + } + } + +#if IRMP_SUPPORT_RC5_PROTOCOL == 1 && IRMP_SUPPORT_FDC_PROTOCOL == 1 + if (irmp_param2.protocol == IRMP_FDC_PROTOCOL && irmp_pulse_time >= FDC_PULSE_LEN_MIN && irmp_pulse_time <= FDC_PULSE_LEN_MAX) + { + if (irmp_pause_time >= FDC_1_PAUSE_LEN_MIN && irmp_pause_time <= FDC_1_PAUSE_LEN_MAX) + { + ANALYZE_PRINTF (" 1 (FDC)\n"); + irmp_store_bit2 (1); + } + else if (irmp_pause_time >= FDC_0_PAUSE_LEN_MIN && irmp_pause_time <= FDC_0_PAUSE_LEN_MAX) + { + ANALYZE_PRINTF (" 0 (FDC)\n"); + irmp_store_bit2 (0); + } + + if (! irmp_param.protocol) + { + ANALYZE_PRINTF ("Switching to FDC protocol\n"); + memcpy (&irmp_param, &irmp_param2, sizeof (IRMP_PARAMETER)); + irmp_param2.protocol = 0; + irmp_tmp_address = irmp_tmp_address2; + irmp_tmp_command = irmp_tmp_command2; + } + } +#endif // IRMP_SUPPORT_FDC_PROTOCOL == 1 +#if IRMP_SUPPORT_RC5_PROTOCOL == 1 && IRMP_SUPPORT_RCCAR_PROTOCOL == 1 + if (irmp_param2.protocol == IRMP_RCCAR_PROTOCOL && irmp_pulse_time >= RCCAR_PULSE_LEN_MIN && irmp_pulse_time <= RCCAR_PULSE_LEN_MAX) + { + if (irmp_pause_time >= RCCAR_1_PAUSE_LEN_MIN && irmp_pause_time <= RCCAR_1_PAUSE_LEN_MAX) + { + ANALYZE_PRINTF (" 1 (RCCAR)\n"); + irmp_store_bit2 (1); + } + else if (irmp_pause_time >= RCCAR_0_PAUSE_LEN_MIN && irmp_pause_time <= RCCAR_0_PAUSE_LEN_MAX) + { + ANALYZE_PRINTF (" 0 (RCCAR)\n"); + irmp_store_bit2 (0); + } + + if (! irmp_param.protocol) + { + ANALYZE_PRINTF ("Switching to RCCAR protocol\n"); + memcpy (&irmp_param, &irmp_param2, sizeof (IRMP_PARAMETER)); + irmp_param2.protocol = 0; + irmp_tmp_address = irmp_tmp_address2; + irmp_tmp_command = irmp_tmp_command2; + } + } +#endif // IRMP_SUPPORT_RCCAR_PROTOCOL == 1 + + last_pause = irmp_pause_time; + wait_for_space = 0; + } + else +#endif // IRMP_SUPPORT_MANCHESTER == 1 + +#if IRMP_SUPPORT_SERIAL == 1 + if (irmp_param.flags & IRMP_PARAM_FLAG_IS_SERIAL) + { + while (irmp_bit < irmp_param.complete_len && irmp_pulse_time > irmp_param.pulse_1_len_max) + { + ANALYZE_PUTCHAR ('1'); + irmp_store_bit (1); + + if (irmp_pulse_time >= irmp_param.pulse_1_len_min) + { + irmp_pulse_time -= irmp_param.pulse_1_len_min; + } + else + { + irmp_pulse_time = 0; + } + } + + while (irmp_bit < irmp_param.complete_len && irmp_pause_time > irmp_param.pause_1_len_max) + { + ANALYZE_PUTCHAR ('0'); + irmp_store_bit (0); + + if (irmp_pause_time >= irmp_param.pause_1_len_min) + { + irmp_pause_time -= irmp_param.pause_1_len_min; + } + else + { + irmp_pause_time = 0; + } + } + ANALYZE_NEWLINE (); + wait_for_space = 0; + } + else +#endif // IRMP_SUPPORT_SERIAL == 1 + +#if IRMP_SUPPORT_SAMSUNG_PROTOCOL == 1 + if (irmp_param.protocol == IRMP_SAMSUNG_PROTOCOL && irmp_bit == 16) // Samsung: 16th bit + { + if (irmp_pulse_time >= SAMSUNG_PULSE_LEN_MIN && irmp_pulse_time <= SAMSUNG_PULSE_LEN_MAX && + irmp_pause_time >= SAMSUNG_START_BIT_PAUSE_LEN_MIN && irmp_pause_time <= SAMSUNG_START_BIT_PAUSE_LEN_MAX) + { + ANALYZE_PRINTF ("SYNC\n"); + wait_for_space = 0; + irmp_tmp_id = 0; + irmp_bit++; + } + else if (irmp_pulse_time >= SAMSUNG_PULSE_LEN_MIN && irmp_pulse_time <= SAMSUNG_PULSE_LEN_MAX) + { + irmp_param.protocol = IRMP_SAMSUNG32_PROTOCOL; + irmp_param.command_offset = SAMSUNG32_COMMAND_OFFSET; + irmp_param.command_end = SAMSUNG32_COMMAND_OFFSET + SAMSUNG32_COMMAND_LEN; + irmp_param.complete_len = SAMSUNG32_COMPLETE_DATA_LEN; + + if (irmp_pause_time >= SAMSUNG_1_PAUSE_LEN_MIN && irmp_pause_time <= SAMSUNG_1_PAUSE_LEN_MAX) + { + ANALYZE_PUTCHAR ('1'); + ANALYZE_NEWLINE (); + irmp_store_bit (1); + wait_for_space = 0; + } + else + { + ANALYZE_PUTCHAR ('0'); + ANALYZE_NEWLINE (); + irmp_store_bit (0); + wait_for_space = 0; + } + + ANALYZE_PRINTF ("Switching to SAMSUNG32 protocol\n"); + } + else + { // timing incorrect! + ANALYZE_PRINTF ("error 3 Samsung: timing not correct: data bit %d, pulse: %d, pause: %d\n", irmp_bit, irmp_pulse_time, irmp_pause_time); + ANALYZE_ONLY_NORMAL_PUTCHAR ('\n'); +// irmp_busy_flag = FALSE; + irmp_start_bit_detected = 0; // reset flags and wait for next start bit + irmp_pause_time = 0; + } + } + else +#endif // IRMP_SUPPORT_SAMSUNG_PROTOCOL + +#if IRMP_SUPPORT_NEC16_PROTOCOL +#if IRMP_SUPPORT_NEC42_PROTOCOL == 1 + if (irmp_param.protocol == IRMP_NEC42_PROTOCOL && +#else // IRMP_SUPPORT_NEC_PROTOCOL instead + if (irmp_param.protocol == IRMP_NEC_PROTOCOL && +#endif // IRMP_SUPPORT_NEC42_PROTOCOL == 1 + irmp_bit == 8 && irmp_pause_time >= NEC_START_BIT_PAUSE_LEN_MIN && irmp_pause_time <= NEC_START_BIT_PAUSE_LEN_MAX) + { + ANALYZE_PRINTF ("Switching to NEC16 protocol\n"); + irmp_param.protocol = IRMP_NEC16_PROTOCOL; + irmp_param.address_offset = NEC16_ADDRESS_OFFSET; + irmp_param.address_end = NEC16_ADDRESS_OFFSET + NEC16_ADDRESS_LEN; + irmp_param.command_offset = NEC16_COMMAND_OFFSET; + irmp_param.command_end = NEC16_COMMAND_OFFSET + NEC16_COMMAND_LEN; + irmp_param.complete_len = NEC16_COMPLETE_DATA_LEN; + wait_for_space = 0; + } + else +#endif // IRMP_SUPPORT_NEC16_PROTOCOL + +#if IRMP_SUPPORT_BANG_OLUFSEN_PROTOCOL == 1 + if (irmp_param.protocol == IRMP_BANG_OLUFSEN_PROTOCOL) + { + if (irmp_pulse_time >= BANG_OLUFSEN_PULSE_LEN_MIN && irmp_pulse_time <= BANG_OLUFSEN_PULSE_LEN_MAX) + { + if (irmp_bit == 1) // Bang & Olufsen: 3rd bit + { + if (irmp_pause_time >= BANG_OLUFSEN_START_BIT3_PAUSE_LEN_MIN && irmp_pause_time <= BANG_OLUFSEN_START_BIT3_PAUSE_LEN_MAX) + { + ANALYZE_PRINTF ("3rd start bit\n"); + wait_for_space = 0; + irmp_bit++; + } + else + { // timing incorrect! + ANALYZE_PRINTF ("error 3a B&O: timing not correct: data bit %d, pulse: %d, pause: %d\n", irmp_bit, irmp_pulse_time, irmp_pause_time); + ANALYZE_ONLY_NORMAL_PUTCHAR ('\n'); +// irmp_busy_flag = FALSE; + irmp_start_bit_detected = 0; // reset flags and wait for next start bit + irmp_pause_time = 0; + } + } + else if (irmp_bit == 19) // Bang & Olufsen: trailer bit + { + if (irmp_pause_time >= BANG_OLUFSEN_TRAILER_BIT_PAUSE_LEN_MIN && irmp_pause_time <= BANG_OLUFSEN_TRAILER_BIT_PAUSE_LEN_MAX) + { + ANALYZE_PRINTF ("trailer bit\n"); + wait_for_space = 0; + irmp_bit++; + } + else + { // timing incorrect! + ANALYZE_PRINTF ("error 3b B&O: timing not correct: data bit %d, pulse: %d, pause: %d\n", irmp_bit, irmp_pulse_time, irmp_pause_time); + ANALYZE_ONLY_NORMAL_PUTCHAR ('\n'); +// irmp_busy_flag = FALSE; + irmp_start_bit_detected = 0; // reset flags and wait for next start bit + irmp_pause_time = 0; + } + } + else + { + if (irmp_pause_time >= BANG_OLUFSEN_1_PAUSE_LEN_MIN && irmp_pause_time <= BANG_OLUFSEN_1_PAUSE_LEN_MAX) + { // pulse & pause timings correct for "1"? + ANALYZE_PUTCHAR ('1'); + ANALYZE_NEWLINE (); + irmp_store_bit (1); + last_value = 1; + wait_for_space = 0; + } + else if (irmp_pause_time >= BANG_OLUFSEN_0_PAUSE_LEN_MIN && irmp_pause_time <= BANG_OLUFSEN_0_PAUSE_LEN_MAX) + { // pulse & pause timings correct for "0"? + ANALYZE_PUTCHAR ('0'); + ANALYZE_NEWLINE (); + irmp_store_bit (0); + last_value = 0; + wait_for_space = 0; + } + else if (irmp_pause_time >= BANG_OLUFSEN_R_PAUSE_LEN_MIN && irmp_pause_time <= BANG_OLUFSEN_R_PAUSE_LEN_MAX) + { + ANALYZE_PUTCHAR (last_value + '0'); + ANALYZE_NEWLINE (); + irmp_store_bit (last_value); + wait_for_space = 0; + } + else + { // timing incorrect! + ANALYZE_PRINTF ("error 3c B&O: timing not correct: data bit %d, pulse: %d, pause: %d\n", irmp_bit, irmp_pulse_time, irmp_pause_time); + ANALYZE_ONLY_NORMAL_PUTCHAR ('\n'); +// irmp_busy_flag = FALSE; + irmp_start_bit_detected = 0; // reset flags and wait for next start bit + irmp_pause_time = 0; + } + } + } + else + { // timing incorrect! + ANALYZE_PRINTF ("error 3d B&O: timing not correct: data bit %d, pulse: %d, pause: %d\n", irmp_bit, irmp_pulse_time, irmp_pause_time); + ANALYZE_ONLY_NORMAL_PUTCHAR ('\n'); +// irmp_busy_flag = FALSE; + irmp_start_bit_detected = 0; // reset flags and wait for next start bit + irmp_pause_time = 0; + } + } + else +#endif // IRMP_SUPPORT_BANG_OLUFSEN_PROTOCOL + + if (irmp_pulse_time >= irmp_param.pulse_1_len_min && irmp_pulse_time <= irmp_param.pulse_1_len_max && + irmp_pause_time >= irmp_param.pause_1_len_min && irmp_pause_time <= irmp_param.pause_1_len_max) + { // pulse & pause timings correct for "1"? + ANALYZE_PUTCHAR ('1'); + ANALYZE_NEWLINE (); + irmp_store_bit (1); + wait_for_space = 0; + } + else if (irmp_pulse_time >= irmp_param.pulse_0_len_min && irmp_pulse_time <= irmp_param.pulse_0_len_max && + irmp_pause_time >= irmp_param.pause_0_len_min && irmp_pause_time <= irmp_param.pause_0_len_max) + { // pulse & pause timings correct for "0"? + ANALYZE_PUTCHAR ('0'); + ANALYZE_NEWLINE (); + irmp_store_bit (0); + wait_for_space = 0; + } + else +#if IRMP_SUPPORT_KATHREIN_PROTOCOL + + if (irmp_param.protocol == IRMP_KATHREIN_PROTOCOL && + irmp_pulse_time >= KATHREIN_1_PULSE_LEN_MIN && irmp_pulse_time <= KATHREIN_1_PULSE_LEN_MAX && + (((irmp_bit == 8 || irmp_bit == 6) && + irmp_pause_time >= KATHREIN_SYNC_BIT_PAUSE_LEN_MIN && irmp_pause_time <= KATHREIN_SYNC_BIT_PAUSE_LEN_MAX) || + (irmp_bit == 12 && + irmp_pause_time >= KATHREIN_START_BIT_PAUSE_LEN_MIN && irmp_pause_time <= KATHREIN_START_BIT_PAUSE_LEN_MAX))) + + { + if (irmp_bit == 8) + { + irmp_bit++; + ANALYZE_PUTCHAR ('S'); + ANALYZE_NEWLINE (); + irmp_tmp_command <<= 1; + } + else + { + ANALYZE_PUTCHAR ('S'); + ANALYZE_NEWLINE (); + irmp_store_bit (1); + } + wait_for_space = 0; + } + else +#endif // IRMP_SUPPORT_KATHREIN_PROTOCOL + { // timing incorrect! + ANALYZE_PRINTF ("error 3: timing not correct: data bit %d, pulse: %d, pause: %d\n", irmp_bit, irmp_pulse_time, irmp_pause_time); + ANALYZE_ONLY_NORMAL_PUTCHAR ('\n'); +// irmp_busy_flag = FALSE; + irmp_start_bit_detected = 0; // reset flags and wait for next start bit + irmp_pause_time = 0; + } + + irmp_pulse_time = 1; // set counter to 1, not 0 + } + } + else + { // counting the pulse length ... + if (! irmp_input) // still light? + { // yes... + irmp_pulse_time++; // increment counter + } + else + { // now it's dark! + wait_for_space = 1; // let's count the time (see above) + irmp_pause_time = 1; // set pause counter to 1, not 0 + } + } + + if (irmp_start_bit_detected && irmp_bit == irmp_param.complete_len && irmp_param.stop_bit == 0) // enough bits received? + { + if (last_irmp_command == irmp_tmp_command && repetition_len < AUTO_FRAME_REPETITION_LEN) + { + repetition_frame_number++; + } + else + { + repetition_frame_number = 0; + } + +#if IRMP_SUPPORT_SIRCS_PROTOCOL == 1 + // if SIRCS protocol and the code will be repeated within 50 ms, we will ignore 2nd and 3rd repetition frame + if (irmp_param.protocol == IRMP_SIRCS_PROTOCOL && (repetition_frame_number == 1 || repetition_frame_number == 2)) + { + ANALYZE_PRINTF ("code skipped: SIRCS auto repetition frame #%d, counter = %d, auto repetition len = %d\n", + repetition_frame_number + 1, repetition_len, AUTO_FRAME_REPETITION_LEN); + repetition_len = 0; + } + else +#endif + +#if IRMP_SUPPORT_KASEIKYO_PROTOCOL == 1 + // if KASEIKYO protocol and the code will be repeated within 50 ms, we will ignore 2nd repetition frame + if (irmp_param.protocol == IRMP_KASEIKYO_PROTOCOL && repetition_frame_number == 1) + { + ANALYZE_PRINTF ("code skipped: KASEIKYO auto repetition frame #%d, counter = %d, auto repetition len = %d\n", + repetition_frame_number + 1, repetition_len, AUTO_FRAME_REPETITION_LEN); + repetition_len = 0; + } + else +#endif + +#if IRMP_SUPPORT_SAMSUNG_PROTOCOL == 1 + // if SAMSUNG32 protocol and the code will be repeated within 50 ms, we will ignore every 2nd frame + if (irmp_param.protocol == IRMP_SAMSUNG32_PROTOCOL && (repetition_frame_number & 0x01)) + { + ANALYZE_PRINTF ("code skipped: SAMSUNG32 auto repetition frame #%d, counter = %d, auto repetition len = %d\n", + repetition_frame_number + 1, repetition_len, AUTO_FRAME_REPETITION_LEN); + repetition_len = 0; + } + else +#endif + +#if IRMP_SUPPORT_NUBERT_PROTOCOL == 1 + // if NUBERT protocol and the code will be repeated within 50 ms, we will ignore every 2nd frame + if (irmp_param.protocol == IRMP_NUBERT_PROTOCOL && (repetition_frame_number & 0x01)) + { + ANALYZE_PRINTF ("code skipped: NUBERT auto repetition frame #%d, counter = %d, auto repetition len = %d\n", + repetition_frame_number + 1, repetition_len, AUTO_FRAME_REPETITION_LEN); + repetition_len = 0; + } + else +#endif + + { + ANALYZE_PRINTF ("%8.3fms code detected, length = %d\n", (double) (time_counter * 1000) / F_INTERRUPTS, irmp_bit); + irmp_ir_detected = TRUE; + +#if IRMP_SUPPORT_DENON_PROTOCOL == 1 + if (irmp_param.protocol == IRMP_DENON_PROTOCOL) + { // check for repetition frame + if ((~irmp_tmp_command & 0x3FF) == last_irmp_denon_command) // command bits must be inverted + { + irmp_tmp_command = last_irmp_denon_command; // use command received before! + + irmp_protocol = irmp_param.protocol; // store protocol + irmp_address = irmp_tmp_address; // store address + irmp_command = irmp_tmp_command ; // store command + } + else + { + ANALYZE_PRINTF ("waiting for inverted command repetition\n"); + irmp_ir_detected = FALSE; + last_irmp_denon_command = irmp_tmp_command; + } + } + else +#endif // IRMP_SUPPORT_DENON_PROTOCOL + +#if IRMP_SUPPORT_GRUNDIG_PROTOCOL == 1 + if (irmp_param.protocol == IRMP_GRUNDIG_PROTOCOL && irmp_tmp_command == 0x01ff) + { // Grundig start frame? + ANALYZE_PRINTF ("Detected GRUNDIG start frame, ignoring it\n"); + irmp_ir_detected = FALSE; + } + else +#endif // IRMP_SUPPORT_GRUNDIG_PROTOCOL + +#if IRMP_SUPPORT_NOKIA_PROTOCOL == 1 + if (irmp_param.protocol == IRMP_NOKIA_PROTOCOL && irmp_tmp_address == 0x00ff && irmp_tmp_command == 0x00fe) + { // Nokia start frame? + ANALYZE_PRINTF ("Detected NOKIA start frame, ignoring it\n"); + irmp_ir_detected = FALSE; + } + else +#endif // IRMP_SUPPORT_NOKIA_PROTOCOL + { +#if IRMP_SUPPORT_NEC_PROTOCOL == 1 + if (irmp_param.protocol == IRMP_NEC_PROTOCOL && irmp_bit == 0) // repetition frame + { + if (repetition_len < NEC_FRAME_REPEAT_PAUSE_LEN_MAX) + { + ANALYZE_PRINTF ("Detected NEC repetition frame, repetition_len = %d\n", repetition_len); + irmp_tmp_address = last_irmp_address; // address is last address + irmp_tmp_command = last_irmp_command; // command is last command + irmp_flags |= IRMP_FLAG_REPETITION; + repetition_len = 0; + } + else + { + ANALYZE_PRINTF ("Detected NEC repetition frame, ignoring it: timeout occured, repetition_len = %d > %d\n", + repetition_len, NEC_FRAME_REPEAT_PAUSE_LEN_MAX); + irmp_ir_detected = FALSE; + } + } +#endif // IRMP_SUPPORT_NEC_PROTOCOL + +#if IRMP_SUPPORT_KASEIKYO_PROTOCOL == 1 + if (irmp_param.protocol == IRMP_KASEIKYO_PROTOCOL) + { + uint8_t xor; + // ANALYZE_PRINTF ("0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x\n", + // xor_check[0], xor_check[1], xor_check[2], xor_check[3], xor_check[4], xor_check[5]); + + xor = (xor_check[0] & 0x0F) ^ ((xor_check[0] & 0xF0) >> 4) ^ (xor_check[1] & 0x0F) ^ ((xor_check[1] & 0xF0) >> 4); + + if (xor != (xor_check[2] & 0x0F)) + { + ANALYZE_PRINTF ("error 4: wrong XOR check for customer id: 0x%1x 0x%1x\n", xor, xor_check[2] & 0x0F); + irmp_ir_detected = FALSE; + } + + xor = xor_check[2] ^ xor_check[3] ^ xor_check[4]; + + if (xor != xor_check[5]) + { + ANALYZE_PRINTF ("error 4: wrong XOR check for data bits: 0x%02x 0x%02x\n", xor, xor_check[5]); + irmp_ir_detected = FALSE; + } + + irmp_flags |= genre2; // write the genre2 bits into MSB of the flag byte + } +#endif // IRMP_SUPPORT_KASEIKYO_PROTOCOL == 1 + +#if IRMP_SUPPORT_RC6_PROTOCOL == 1 + if (irmp_param.protocol == IRMP_RC6_PROTOCOL && irmp_param.complete_len == RC6_COMPLETE_DATA_LEN_LONG) // RC6 mode = 6? + { + irmp_protocol = IRMP_RC6A_PROTOCOL; + } + else +#endif // IRMP_SUPPORT_RC6_PROTOCOL == 1 + + irmp_protocol = irmp_param.protocol; + +#if IRMP_SUPPORT_FDC_PROTOCOL == 1 + if (irmp_param.protocol == IRMP_FDC_PROTOCOL) + { + if (irmp_tmp_command & 0x000F) // released key? + { + irmp_tmp_command = (irmp_tmp_command >> 4) | 0x80; // yes, set bit 7 + } + else + { + irmp_tmp_command >>= 4; // no, it's a pressed key + } + irmp_tmp_command |= (irmp_tmp_address << 2) & 0x0F00; // 000000CCCCAAAAAA -> 0000CCCC00000000 + irmp_tmp_address &= 0x003F; + } +#endif + + irmp_address = irmp_tmp_address; // store address +#if IRMP_SUPPORT_NEC_PROTOCOL == 1 + if (irmp_param.protocol == IRMP_NEC_PROTOCOL) + { + last_irmp_address = irmp_tmp_address; // store as last address, too + } +#endif + +#if IRMP_SUPPORT_RC5_PROTOCOL == 1 + if (irmp_param.protocol == IRMP_RC5_PROTOCOL) + { + irmp_tmp_command |= rc5_cmd_bit6; // store bit 6 + } +#endif + irmp_command = irmp_tmp_command; // store command + +#if IRMP_SUPPORT_SAMSUNG_PROTOCOL == 1 + irmp_id = irmp_tmp_id; +#endif + } + } + + if (irmp_ir_detected) + { + if (last_irmp_command == irmp_tmp_command && + last_irmp_address == irmp_tmp_address && + repetition_len < IRMP_KEY_REPETITION_LEN) + { + irmp_flags |= IRMP_FLAG_REPETITION; + } + + last_irmp_address = irmp_tmp_address; // store as last address, too + last_irmp_command = irmp_tmp_command; // store as last command, too + + repetition_len = 0; + } + else + { + ANALYZE_ONLY_NORMAL_PUTCHAR ('\n'); + } + +// irmp_busy_flag = FALSE; + irmp_start_bit_detected = 0; // and wait for next start bit + irmp_tmp_command = 0; + irmp_pulse_time = 0; + irmp_pause_time = 0; + +#if IRMP_SUPPORT_JVC_PROTOCOL == 1 + if (irmp_protocol == IRMP_JVC_PROTOCOL) // the stop bit of JVC frame is also start bit of next frame + { // set pulse time here! + irmp_pulse_time = ((uint8_t)(F_INTERRUPTS * JVC_START_BIT_PULSE_TIME)); + } +#endif // IRMP_SUPPORT_JVC_PROTOCOL == 1 + } + } + } + return (irmp_ir_detected); +} + +#ifdef ANALYZE + +/*--------------------------------------------------------------------------------------------------------------------------------------------------- + * main functions - for Unix/Linux + Windows only! + * + * AVR: see main.c! + * + * Compile it under linux with: + * cc irmp.c -o irmp + * + * usage: ./irmp [-v|-s|-a|-l|-p] < file + * + * options: + * -v verbose + * -s silent + * -a analyze + * -l list pulse/pauses + * -p print timings + *--------------------------------------------------------------------------------------------------------------------------------------------------- + */ + +#ifndef IRMP_EMBED +static void +print_timings (void) +{ + printf ("IRMP_TIMEOUT_LEN: %d [%d byte(s)]\n", IRMP_TIMEOUT_LEN, sizeof (PAUSE_LEN)); + printf ("IRMP_KEY_REPETITION_LEN %d\n", IRMP_KEY_REPETITION_LEN); + puts (""); + printf ("PROTOCOL S S-PULSE S-PAUSE PULSE-0 PAUSE-0 PULSE-1 PAUSE-1\n"); + printf ("====================================================================================\n"); + printf ("SIRCS 1 %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d\n", + SIRCS_START_BIT_PULSE_LEN_MIN, SIRCS_START_BIT_PULSE_LEN_MAX, SIRCS_START_BIT_PAUSE_LEN_MIN, SIRCS_START_BIT_PAUSE_LEN_MAX, + SIRCS_0_PULSE_LEN_MIN, SIRCS_0_PULSE_LEN_MAX, SIRCS_PAUSE_LEN_MIN, SIRCS_PAUSE_LEN_MAX, + SIRCS_1_PULSE_LEN_MIN, SIRCS_1_PULSE_LEN_MAX, SIRCS_PAUSE_LEN_MIN, SIRCS_PAUSE_LEN_MAX); + + printf ("NEC 1 %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d\n", + NEC_START_BIT_PULSE_LEN_MIN, NEC_START_BIT_PULSE_LEN_MAX, NEC_START_BIT_PAUSE_LEN_MIN, NEC_START_BIT_PAUSE_LEN_MAX, + NEC_PULSE_LEN_MIN, NEC_PULSE_LEN_MAX, NEC_0_PAUSE_LEN_MIN, NEC_0_PAUSE_LEN_MAX, + NEC_PULSE_LEN_MIN, NEC_PULSE_LEN_MAX, NEC_1_PAUSE_LEN_MIN, NEC_1_PAUSE_LEN_MAX); + + printf ("NEC (rep) 1 %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d\n", + NEC_START_BIT_PULSE_LEN_MIN, NEC_START_BIT_PULSE_LEN_MAX, NEC_REPEAT_START_BIT_PAUSE_LEN_MIN, NEC_REPEAT_START_BIT_PAUSE_LEN_MAX, + NEC_PULSE_LEN_MIN, NEC_PULSE_LEN_MAX, NEC_0_PAUSE_LEN_MIN, NEC_0_PAUSE_LEN_MAX, + NEC_PULSE_LEN_MIN, NEC_PULSE_LEN_MAX, NEC_1_PAUSE_LEN_MIN, NEC_1_PAUSE_LEN_MAX); + + printf ("SAMSUNG 1 %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d\n", + SAMSUNG_START_BIT_PULSE_LEN_MIN, SAMSUNG_START_BIT_PULSE_LEN_MAX, SAMSUNG_START_BIT_PAUSE_LEN_MIN, SAMSUNG_START_BIT_PAUSE_LEN_MAX, + SAMSUNG_PULSE_LEN_MIN, SAMSUNG_PULSE_LEN_MAX, SAMSUNG_0_PAUSE_LEN_MIN, SAMSUNG_0_PAUSE_LEN_MAX, + SAMSUNG_PULSE_LEN_MIN, SAMSUNG_PULSE_LEN_MAX, SAMSUNG_1_PAUSE_LEN_MIN, SAMSUNG_1_PAUSE_LEN_MAX); + + printf ("MATSUSHITA 1 %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d\n", + MATSUSHITA_START_BIT_PULSE_LEN_MIN, MATSUSHITA_START_BIT_PULSE_LEN_MAX, MATSUSHITA_START_BIT_PAUSE_LEN_MIN, MATSUSHITA_START_BIT_PAUSE_LEN_MAX, + MATSUSHITA_PULSE_LEN_MIN, MATSUSHITA_PULSE_LEN_MAX, MATSUSHITA_0_PAUSE_LEN_MIN, MATSUSHITA_0_PAUSE_LEN_MAX, + MATSUSHITA_PULSE_LEN_MIN, MATSUSHITA_PULSE_LEN_MAX, MATSUSHITA_1_PAUSE_LEN_MIN, MATSUSHITA_1_PAUSE_LEN_MAX); + + printf ("KASEIKYO 1 %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d\n", + KASEIKYO_START_BIT_PULSE_LEN_MIN, KASEIKYO_START_BIT_PULSE_LEN_MAX, KASEIKYO_START_BIT_PAUSE_LEN_MIN, KASEIKYO_START_BIT_PAUSE_LEN_MAX, + KASEIKYO_PULSE_LEN_MIN, KASEIKYO_PULSE_LEN_MAX, KASEIKYO_0_PAUSE_LEN_MIN, KASEIKYO_0_PAUSE_LEN_MAX, + KASEIKYO_PULSE_LEN_MIN, KASEIKYO_PULSE_LEN_MAX, KASEIKYO_1_PAUSE_LEN_MIN, KASEIKYO_1_PAUSE_LEN_MAX); + + printf ("RECS80 1 %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d\n", + RECS80_START_BIT_PULSE_LEN_MIN, RECS80_START_BIT_PULSE_LEN_MAX, RECS80_START_BIT_PAUSE_LEN_MIN, RECS80_START_BIT_PAUSE_LEN_MAX, + RECS80_PULSE_LEN_MIN, RECS80_PULSE_LEN_MAX, RECS80_0_PAUSE_LEN_MIN, RECS80_0_PAUSE_LEN_MAX, + RECS80_PULSE_LEN_MIN, RECS80_PULSE_LEN_MAX, RECS80_1_PAUSE_LEN_MIN, RECS80_1_PAUSE_LEN_MAX); + + printf ("RC5 1 %3d - %3d %3d - %3d %3d - %3d\n", + RC5_START_BIT_LEN_MIN, RC5_START_BIT_LEN_MAX, RC5_START_BIT_LEN_MIN, RC5_START_BIT_LEN_MAX, + RC5_BIT_LEN_MIN, RC5_BIT_LEN_MAX); + + printf ("DENON 1 %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d\n", + DENON_PULSE_LEN_MIN, DENON_PULSE_LEN_MAX, + DENON_PULSE_LEN_MIN, DENON_PULSE_LEN_MAX, DENON_0_PAUSE_LEN_MIN, DENON_0_PAUSE_LEN_MAX, + DENON_PULSE_LEN_MIN, DENON_PULSE_LEN_MAX, DENON_1_PAUSE_LEN_MIN, DENON_1_PAUSE_LEN_MAX); + + printf ("THOMSON 1 %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d\n", + THOMSON_PULSE_LEN_MIN, THOMSON_PULSE_LEN_MAX, + THOMSON_PULSE_LEN_MIN, THOMSON_PULSE_LEN_MAX, THOMSON_0_PAUSE_LEN_MIN, THOMSON_0_PAUSE_LEN_MAX, + THOMSON_PULSE_LEN_MIN, THOMSON_PULSE_LEN_MAX, THOMSON_1_PAUSE_LEN_MIN, THOMSON_1_PAUSE_LEN_MAX); + + printf ("RC6 1 %3d - %3d %3d - %3d %3d - %3d %3d - %3d\n", + RC6_START_BIT_PULSE_LEN_MIN, RC6_START_BIT_PULSE_LEN_MAX, RC6_START_BIT_PAUSE_LEN_MIN, RC6_START_BIT_PAUSE_LEN_MAX, + RC6_BIT_PULSE_LEN_MIN, RC6_BIT_PULSE_LEN_MAX, RC6_BIT_PAUSE_LEN_MIN, RC6_BIT_PAUSE_LEN_MAX); + + printf ("RECS80EXT 1 %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d\n", + RECS80EXT_START_BIT_PULSE_LEN_MIN, RECS80EXT_START_BIT_PULSE_LEN_MAX, RECS80EXT_START_BIT_PAUSE_LEN_MIN, RECS80EXT_START_BIT_PAUSE_LEN_MAX, + RECS80EXT_PULSE_LEN_MIN, RECS80EXT_PULSE_LEN_MAX, RECS80EXT_0_PAUSE_LEN_MIN, RECS80EXT_0_PAUSE_LEN_MAX, + RECS80EXT_PULSE_LEN_MIN, RECS80EXT_PULSE_LEN_MAX, RECS80EXT_1_PAUSE_LEN_MIN, RECS80EXT_1_PAUSE_LEN_MAX); + + printf ("NUBERT 1 %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d\n", + NUBERT_START_BIT_PULSE_LEN_MIN, NUBERT_START_BIT_PULSE_LEN_MAX, NUBERT_START_BIT_PAUSE_LEN_MIN, NUBERT_START_BIT_PAUSE_LEN_MAX, + NUBERT_0_PULSE_LEN_MIN, NUBERT_0_PULSE_LEN_MAX, NUBERT_0_PAUSE_LEN_MIN, NUBERT_0_PAUSE_LEN_MAX, + NUBERT_1_PULSE_LEN_MIN, NUBERT_1_PULSE_LEN_MAX, NUBERT_1_PAUSE_LEN_MIN, NUBERT_1_PAUSE_LEN_MAX); + + printf ("BANG_OLUFSEN 1 %3d - %3d %3d - %3d\n", + BANG_OLUFSEN_START_BIT1_PULSE_LEN_MIN, BANG_OLUFSEN_START_BIT1_PULSE_LEN_MAX, + BANG_OLUFSEN_START_BIT1_PAUSE_LEN_MIN, BANG_OLUFSEN_START_BIT1_PAUSE_LEN_MAX); + + printf ("BANG_OLUFSEN 2 %3d - %3d %3d - %3d\n", + BANG_OLUFSEN_START_BIT2_PULSE_LEN_MIN, BANG_OLUFSEN_START_BIT2_PULSE_LEN_MAX, + BANG_OLUFSEN_START_BIT2_PAUSE_LEN_MIN, BANG_OLUFSEN_START_BIT2_PAUSE_LEN_MAX); + + printf ("BANG_OLUFSEN 3 %3d - %3d %3d - %3d\n", + BANG_OLUFSEN_START_BIT3_PULSE_LEN_MIN, BANG_OLUFSEN_START_BIT3_PULSE_LEN_MAX, + BANG_OLUFSEN_START_BIT3_PAUSE_LEN_MIN, BANG_OLUFSEN_START_BIT3_PAUSE_LEN_MAX); + + printf ("BANG_OLUFSEN 4 %3d - %3d %3d - %3d\n", + BANG_OLUFSEN_START_BIT4_PULSE_LEN_MIN, BANG_OLUFSEN_START_BIT4_PULSE_LEN_MAX, + BANG_OLUFSEN_START_BIT4_PAUSE_LEN_MIN, BANG_OLUFSEN_START_BIT4_PAUSE_LEN_MAX); + + printf ("BANG_OLUFSEN - %3d - %3d %3d - %3d %3d - %3d %3d - %3d\n", + BANG_OLUFSEN_PULSE_LEN_MIN, BANG_OLUFSEN_PULSE_LEN_MAX, BANG_OLUFSEN_0_PAUSE_LEN_MIN, BANG_OLUFSEN_0_PAUSE_LEN_MAX, + BANG_OLUFSEN_PULSE_LEN_MIN, BANG_OLUFSEN_PULSE_LEN_MAX, BANG_OLUFSEN_1_PAUSE_LEN_MIN, BANG_OLUFSEN_1_PAUSE_LEN_MAX); + + printf ("GRUNDIG/NOKIA 1 %3d - %3d %3d - %3d %3d - %3d\n", + GRUNDIG_NOKIA_IR60_START_BIT_LEN_MIN, GRUNDIG_NOKIA_IR60_START_BIT_LEN_MAX, + GRUNDIG_NOKIA_IR60_PRE_PAUSE_LEN_MIN, GRUNDIG_NOKIA_IR60_PRE_PAUSE_LEN_MAX, + GRUNDIG_NOKIA_IR60_BIT_LEN_MIN, GRUNDIG_NOKIA_IR60_BIT_LEN_MAX); + + printf ("SIEMENS/RUWIDO 1 %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d\n", + SIEMENS_OR_RUWIDO_START_BIT_PULSE_LEN_MIN, SIEMENS_OR_RUWIDO_START_BIT_PULSE_LEN_MAX, + SIEMENS_OR_RUWIDO_START_BIT_PAUSE_LEN_MIN, SIEMENS_OR_RUWIDO_START_BIT_PAUSE_LEN_MAX, + SIEMENS_OR_RUWIDO_BIT_PULSE_LEN_MIN, SIEMENS_OR_RUWIDO_BIT_PULSE_LEN_MAX, + SIEMENS_OR_RUWIDO_BIT_PAUSE_LEN_MIN, SIEMENS_OR_RUWIDO_BIT_PAUSE_LEN_MAX, + 2 * SIEMENS_OR_RUWIDO_BIT_PULSE_LEN_MIN, 2 * SIEMENS_OR_RUWIDO_BIT_PULSE_LEN_MAX, + 2 * SIEMENS_OR_RUWIDO_BIT_PAUSE_LEN_MIN, 2 * SIEMENS_OR_RUWIDO_BIT_PAUSE_LEN_MAX); + + printf ("GRUNDIG2 1 %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d\n", + GRUNDIG2_START_BIT_PULSE_LEN_MIN, GRUNDIG2_START_BIT_PULSE_LEN_MAX, + GRUNDIG2_START_BIT_PAUSE_LEN_MIN, GRUNDIG2_START_BIT_PAUSE_LEN_MAX, + GRUNDIG2_BIT_PULSE_LEN_MIN, GRUNDIG2_BIT_PULSE_LEN_MAX, + GRUNDIG2_BIT_PAUSE_LEN_MIN, GRUNDIG2_BIT_PAUSE_LEN_MAX, + 2 * GRUNDIG2_BIT_PULSE_LEN_MIN, 2 * GRUNDIG2_BIT_PULSE_LEN_MAX, + 2 * GRUNDIG2_BIT_PAUSE_LEN_MIN, 2 * GRUNDIG2_BIT_PAUSE_LEN_MAX); + + printf ("FDC 1 %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d\n", + FDC_START_BIT_PULSE_LEN_MIN, FDC_START_BIT_PULSE_LEN_MAX, FDC_START_BIT_PAUSE_LEN_MIN, FDC_START_BIT_PAUSE_LEN_MAX, + FDC_PULSE_LEN_MIN, FDC_PULSE_LEN_MAX, FDC_0_PAUSE_LEN_MIN, FDC_0_PAUSE_LEN_MAX, + FDC_PULSE_LEN_MIN, FDC_PULSE_LEN_MAX, FDC_1_PAUSE_LEN_MIN, FDC_1_PAUSE_LEN_MAX); + + printf ("RCCAR 1 %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d\n", + RCCAR_START_BIT_PULSE_LEN_MIN, RCCAR_START_BIT_PULSE_LEN_MAX, RCCAR_START_BIT_PAUSE_LEN_MIN, RCCAR_START_BIT_PAUSE_LEN_MAX, + RCCAR_PULSE_LEN_MIN, RCCAR_PULSE_LEN_MAX, RCCAR_0_PAUSE_LEN_MIN, RCCAR_0_PAUSE_LEN_MAX, + RCCAR_PULSE_LEN_MIN, RCCAR_PULSE_LEN_MAX, RCCAR_1_PAUSE_LEN_MIN, RCCAR_1_PAUSE_LEN_MAX); + + printf ("NIKON 1 %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d\n", + NIKON_START_BIT_PULSE_LEN_MIN, NIKON_START_BIT_PULSE_LEN_MAX, NIKON_START_BIT_PAUSE_LEN_MIN, NIKON_START_BIT_PAUSE_LEN_MAX, + NIKON_PULSE_LEN_MIN, NIKON_PULSE_LEN_MAX, NIKON_0_PAUSE_LEN_MIN, NIKON_0_PAUSE_LEN_MAX, + NIKON_PULSE_LEN_MIN, NIKON_PULSE_LEN_MAX, NIKON_1_PAUSE_LEN_MIN, NIKON_1_PAUSE_LEN_MAX); + + printf ("LEGO 1 %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d %3d - %3d\n", + LEGO_START_BIT_PULSE_LEN_MIN, LEGO_START_BIT_PULSE_LEN_MAX, LEGO_START_BIT_PAUSE_LEN_MIN, LEGO_START_BIT_PAUSE_LEN_MAX, + LEGO_PULSE_LEN_MIN, LEGO_PULSE_LEN_MAX, LEGO_0_PAUSE_LEN_MIN, LEGO_0_PAUSE_LEN_MAX, + LEGO_PULSE_LEN_MIN, LEGO_PULSE_LEN_MAX, LEGO_1_PAUSE_LEN_MIN, LEGO_1_PAUSE_LEN_MAX); + +} + +void +print_spectrum (char * text, int * buf, int is_pulse) +{ + int i; + int j; + int min; + int max; + int max_value = 0; + int value; + int sum = 0; + int counter = 0; + double average = 0; + double tolerance; + + puts ("-------------------------------------------------------------------------------"); + printf ("%s:\n", text); + + for (i = 0; i < 256; i++) + { + if (buf[i] > max_value) + { + max_value = buf[i]; + } + } + + for (i = 1; i < 100; i++) + { + if (buf[i] > 0) + { + printf ("%3d ", i); + value = (buf[i] * 60) / max_value; + + for (j = 0; j < value; j++) + { + putchar ('o'); + } + printf (" %d\n", buf[i]); + + sum += i * buf[i]; + counter += buf[i]; + } + else + { + max = i - 1; + + if (counter > 0) + { + average = (float) sum / (float) counter; + + if (is_pulse) + { + printf ("pulse "); + } + else + { + printf ("pause "); + } + + printf ("avg: %4.1f=%6.1f us, ", average, (1000000. * average) / (float) F_INTERRUPTS); + printf ("min: %2d=%6.1f us, ", min, (1000000. * min) / (float) F_INTERRUPTS); + printf ("max: %2d=%6.1f us, ", max, (1000000. * max) / (float) F_INTERRUPTS); + + tolerance = (max - average); + + if (average - min > tolerance) + { + tolerance = average - min; + } + + tolerance = tolerance * 100 / average; + printf ("tol: %4.1f%%\n", tolerance); + } + + counter = 0; + sum = 0; + min = i + 1; + } + } +} +#endif + +#define STATE_LEFT_SHIFT 0x01 +#define STATE_RIGHT_SHIFT 0x02 +#define STATE_LEFT_CTRL 0x04 +#define STATE_LEFT_ALT 0x08 +#define STATE_RIGHT_ALT 0x10 + +#define KEY_ESCAPE 0x1B // keycode = 0x006e +#define KEY_MENUE 0x80 // keycode = 0x0070 +#define KEY_BACK 0x81 // keycode = 0x0071 +#define KEY_FORWARD 0x82 // keycode = 0x0072 +#define KEY_ADDRESS 0x83 // keycode = 0x0073 +#define KEY_WINDOW 0x84 // keycode = 0x0074 +#define KEY_1ST_PAGE 0x85 // keycode = 0x0075 +#define KEY_STOP 0x86 // keycode = 0x0076 +#define KEY_MAIL 0x87 // keycode = 0x0077 +#define KEY_FAVORITES 0x88 // keycode = 0x0078 +#define KEY_NEW_PAGE 0x89 // keycode = 0x0079 +#define KEY_SETUP 0x8A // keycode = 0x007a +#define KEY_FONT 0x8B // keycode = 0x007b +#define KEY_PRINT 0x8C // keycode = 0x007c +#define KEY_ON_OFF 0x8E // keycode = 0x007c + +#define KEY_INSERT 0x90 // keycode = 0x004b +#define KEY_DELETE 0x91 // keycode = 0x004c +#define KEY_LEFT 0x92 // keycode = 0x004f +#define KEY_HOME 0x93 // keycode = 0x0050 +#define KEY_END 0x94 // keycode = 0x0051 +#define KEY_UP 0x95 // keycode = 0x0053 +#define KEY_DOWN 0x96 // keycode = 0x0054 +#define KEY_PAGE_UP 0x97 // keycode = 0x0055 +#define KEY_PAGE_DOWN 0x98 // keycode = 0x0056 +#define KEY_RIGHT 0x99 // keycode = 0x0059 +#define KEY_MOUSE_1 0x9E // keycode = 0x0400 +#define KEY_MOUSE_2 0x9F // keycode = 0x0800 + +#ifndef LIRC_IRMP +static uint8_t +get_fdc_key (uint16_t cmd) +{ + static uint8_t key_table[128] = + { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 0, '^', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'ß', '´', 0, '\b', + '\t','q', 'w', 'e', 'r', 't', 'z', 'u', 'i', 'o', 'p', 'ü', '+', 0, 0, 'a', + 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'ö', 'ä', '#', '\r', 0, '<', 'y', 'x', + 'c', 'v', 'b', 'n', 'm', ',', '.', '-', 0, 0, 0, 0, 0, ' ', 0, 0, + + 0, '°', '!', '"', '§', '$', '%', '&', '/', '(', ')', '=', '?', '`', 0, '\b', + '\t','Q', 'W', 'E', 'R', 'T', 'Z', 'U', 'I', 'O', 'P', 'Ü', '*', 0, 0, 'A', + 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'Ö', 'Ä', '\'','\r', 0, '>', 'Y', 'X', + 'C', 'V', 'B', 'N', 'M', ';', ':', '_', 0, 0, 0, 0, 0, ' ', 0, 0 + }; + static uint8_t state; + + uint8_t key = 0; + + switch (cmd) + { + case 0x002C: state |= STATE_LEFT_SHIFT; break; // pressed left shift + case 0x00AC: state &= ~STATE_LEFT_SHIFT; break; // released left shift + case 0x0039: state |= STATE_RIGHT_SHIFT; break; // pressed right shift + case 0x00B9: state &= ~STATE_RIGHT_SHIFT; break; // released right shift + case 0x003A: state |= STATE_LEFT_CTRL; break; // pressed left ctrl + case 0x00BA: state &= ~STATE_LEFT_CTRL; break; // released left ctrl + case 0x003C: state |= STATE_LEFT_ALT; break; // pressed left alt + case 0x00BC: state &= ~STATE_LEFT_ALT; break; // released left alt + case 0x003E: state |= STATE_RIGHT_ALT; break; // pressed left alt + case 0x00BE: state &= ~STATE_RIGHT_ALT; break; // released left alt + + case 0x006e: key = KEY_ESCAPE; break; + case 0x004b: key = KEY_INSERT; break; + case 0x004c: key = KEY_DELETE; break; + case 0x004f: key = KEY_LEFT; break; + case 0x0050: key = KEY_HOME; break; + case 0x0051: key = KEY_END; break; + case 0x0053: key = KEY_UP; break; + case 0x0054: key = KEY_DOWN; break; + case 0x0055: key = KEY_PAGE_UP; break; + case 0x0056: key = KEY_PAGE_DOWN; break; + case 0x0059: key = KEY_RIGHT; break; + case 0x0400: key = KEY_MOUSE_1; break; + case 0x0800: key = KEY_MOUSE_2; break; + + default: + { + if (!(cmd & 0x80)) // pressed key + { + if (cmd >= 0x70 && cmd <= 0x7F) // function keys + { + key = cmd + 0x10; // 7x -> 8x + } + else if (cmd < 64) // key listed in key_table + { + if (state & (STATE_LEFT_ALT | STATE_RIGHT_ALT)) + { + switch (cmd) + { + case 0x0003: key = '²'; break; + case 0x0008: key = '{'; break; + case 0x0009: key = '['; break; + case 0x000A: key = ']'; break; + case 0x000B: key = '}'; break; + case 0x000C: key = '\\'; break; + case 0x001C: key = '~'; break; + case 0x002D: key = '|'; break; + case 0x0034: key = 0xB5; break; // Mu + } + } + else if (state & (STATE_LEFT_CTRL)) + { + if (key_table[cmd] >= 'a' && key_table[cmd] <= 'z') + { + key = key_table[cmd] - 'a' + 1; + } + else + { + key = key_table[cmd]; + } + } + else + { + int idx = cmd + ((state & (STATE_LEFT_SHIFT | STATE_RIGHT_SHIFT)) ? 64 : 0); + + if (key_table[idx]) + { + key = key_table[idx]; + } + } + } + } + break; + } + } + + return (key); +} + +static int analyze = FALSE; +static int list = FALSE; +static IRMP_DATA irmp_data; + +static void +next_tick (void) +{ + if (! analyze && ! list) + { + (void) irmp_ISR (); + + if (irmp_get_data (&irmp_data)) + { + uint8_t key; + + ANALYZE_ONLY_NORMAL_PUTCHAR (' '); + + if (verbose) + { + printf ("%8.3fms ", (double) (time_counter * 1000) / F_INTERRUPTS); + } + + if (irmp_data.protocol == IRMP_FDC_PROTOCOL && (key = get_fdc_key (irmp_data.command)) != 0) + { + if ((key >= 0x20 && key < 0x7F) || key >= 0xA0) + { + printf ("p = %2d, a = 0x%04x, c = 0x%04x, f = 0x%02x, asc = 0x%02x, key = '%c'\n", + irmp_data.protocol, irmp_data.address, irmp_data.command, irmp_data.flags, key, key); + } + else if (key == '\r' || key == '\t' || key == KEY_ESCAPE || (key >= 0x80 && key <= 0x9F)) // function keys + { + char * p = (char *) NULL; + + switch (key) + { + case '\t' : p = "TAB"; break; + case '\r' : p = "CR"; break; + case KEY_ESCAPE : p = "ESCAPE"; break; + case KEY_MENUE : p = "MENUE"; break; + case KEY_BACK : p = "BACK"; break; + case KEY_FORWARD : p = "FORWARD"; break; + case KEY_ADDRESS : p = "ADDRESS"; break; + case KEY_WINDOW : p = "WINDOW"; break; + case KEY_1ST_PAGE : p = "1ST_PAGE"; break; + case KEY_STOP : p = "STOP"; break; + case KEY_MAIL : p = "MAIL"; break; + case KEY_FAVORITES : p = "FAVORITES"; break; + case KEY_NEW_PAGE : p = "NEW_PAGE"; break; + case KEY_SETUP : p = "SETUP"; break; + case KEY_FONT : p = "FONT"; break; + case KEY_PRINT : p = "PRINT"; break; + case KEY_ON_OFF : p = "ON_OFF"; break; + + case KEY_INSERT : p = "INSERT"; break; + case KEY_DELETE : p = "DELETE"; break; + case KEY_LEFT : p = "LEFT"; break; + case KEY_HOME : p = "HOME"; break; + case KEY_END : p = "END"; break; + case KEY_UP : p = "UP"; break; + case KEY_DOWN : p = "DOWN"; break; + case KEY_PAGE_UP : p = "PAGE_UP"; break; + case KEY_PAGE_DOWN : p = "PAGE_DOWN"; break; + case KEY_RIGHT : p = "RIGHT"; break; + case KEY_MOUSE_1 : p = "KEY_MOUSE_1"; break; + case KEY_MOUSE_2 : p = "KEY_MOUSE_2"; break; + default : p = ""; break; + } + + printf ("p = %2d, a = 0x%04x, c = 0x%04x, f = 0x%02x, asc = 0x%02x, key = %s\n", + irmp_data.protocol, irmp_data.address, irmp_data.command, irmp_data.flags, key, p); + } + else + { + printf ("p = %2d, a = 0x%04x, c = 0x%04x, f = 0x%02x, asc = 0x%02x\n", + irmp_data.protocol, irmp_data.address, irmp_data.command, irmp_data.flags, key); + } + } + else + { + printf ("p = %2d, a = 0x%04x, c = 0x%04x, f = 0x%02x\n", + irmp_data.protocol, irmp_data.address, irmp_data.command, irmp_data.flags); + } + } + } +} +#endif + +#ifndef LIRC_IRMP +int +main (int argc, char ** argv) +{ + int i; + int ch; + int last_ch = 0; + int pulse = 0; + int pause = 0; + + int start_pulses[256]; + int start_pauses[256]; + int pulses[256]; + int pauses[256]; + + int first_pulse = TRUE; + int first_pause = TRUE; + + if (argc == 2) + { + if (! strcmp (argv[1], "-v")) + { + verbose = TRUE; + } + else if (! strcmp (argv[1], "-l")) + { + list = TRUE; + } + else if (! strcmp (argv[1], "-a")) + { + analyze = TRUE; + } + else if (! strcmp (argv[1], "-s")) + { + silent = TRUE; + } + else if (! strcmp (argv[1], "-p")) + { + print_timings (); + return (0); + } + } + + for (i = 0; i < 256; i++) + { + start_pulses[i] = 0; + start_pauses[i] = 0; + pulses[i] = 0; + pauses[i] = 0; + } + + IRMP_PIN = 0xFF; + + while ((ch = getchar ()) != EOF) + { + if (ch == '_' || ch == '0') + { + if (last_ch != ch) + { + if (pause > 0) + { + if (list) + { + printf ("pause: %d\n", pause); + } + + if (analyze) + { + if (first_pause) + { + if (pause < 256) + { + start_pauses[pause]++; + } + first_pause = FALSE; + } + else + { + if (pause < 256) + { + pauses[pause]++; + } + } + } + } + pause = 0; + } + pulse++; + IRMP_PIN = 0x00; + } + else if (ch == 0xaf || ch == '-' || ch == '1') + { + if (last_ch != ch) + { + if (list) + { + printf ("pulse: %d ", pulse); + } + + if (analyze) + { + if (first_pulse) + { + if (pulse < 256) + { + start_pulses[pulse]++; + } + first_pulse = FALSE; + } + else + { + if (pulse < 256) + { + pulses[pulse]++; + } + } + } + pulse = 0; + } + + pause++; + IRMP_PIN = 0xff; + } + else if (ch == '\n') + { + IRMP_PIN = 0xff; + + if (list && pause > 0) + { + printf ("pause: %d\n", pause); + } + pause = 0; + + if (! analyze) + { + for (i = 0; i < (int) ((8000.0 * F_INTERRUPTS) / 10000); i++) // newline: long pause of 800 msec + { + next_tick (); + } + } + first_pulse = TRUE; + first_pause = TRUE; + } + else if (ch == '#') + { + if (analyze) + { + while ((ch = getchar()) != '\n' && ch != EOF) + { + ; + } + } + else + { + puts ("-------------------------------------------------------------------"); + putchar (ch); + + while ((ch = getchar()) != '\n' && ch != EOF) + { + if (ch != '\r') // ignore CR in DOS/Windows files + { + putchar (ch); + } + } + putchar ('\n'); + } + + } + + last_ch = ch; + + next_tick (); + } + + if (analyze) + { + print_spectrum ("START PULSES", start_pulses, TRUE); + print_spectrum ("START PAUSES", start_pauses, FALSE); + print_spectrum ("PULSES", pulses, TRUE); + print_spectrum ("PAUSES", pauses, FALSE); + puts ("-------------------------------------------------------------------------------"); + } + return 0; +} +#else +#ifndef IRMP_EMBED +/* 50 ms. This should be longer than the longest light pulse */ +#define POLL_MS (50 * 1000) +#define LIRC_PULSE 0x01000000 +#define LIRC_PULSE_MASK 0x00FFFFFF + +int main (int argc, char ** argv) +{ + int fd; + int pulse; + int last_pulse = 1; + uint32_t lircdata; /* lirc_t to be correct... */ + unsigned int count = 0; /* how many timeouts? */ + IRMP_DATA d; + + silent = TRUE; + + if (argc == 2) + { + if (! strcmp (argv[1], "-v")) + { + verbose = TRUE; + silent = FALSE; + } + else if (! strcmp (argv[1], "-p")) + { + print_timings (); + return (0); + } + } + + IRMP_PIN = 0xFF; + fd = open("/dev/lirc", O_RDONLY); + if (fd < 0) + { + perror ("open /dev/lirc"); + return 1; + } + /* TODO: ioctl to find out if we have a compatible LIRC_MODE2 device */ + + while(1) + { + fd_set fds; + struct timeval tv; + int ret; + + FD_ZERO(&fds); + FD_SET(fd, &fds); + tv.tv_sec = 0; + tv.tv_usec = POLL_MS; + /* any singal can interrupt select. we rely on the linux-only feature + * that the timeout is automatcally recalculated in this case! */ + do { + ret = select(fd + 1, &fds, NULL, NULL, &tv); + } while (ret == -1 && errno == EINTR); + + if (ret == -1) { + /* errno != EINTR... */ + perror("lirmp: select"); + break; + } + + if (ret == 0) + { + count++; + lircdata = POLL_MS; /* timeout */ + pulse = !last_pulse; /* lirc sends data on signal change */ + } + else + { + if (read(fd, &lircdata, sizeof(lircdata)) != sizeof(lircdata)) + { + perror("read"); + break; + } + pulse = (lircdata & LIRC_PULSE); /* we got light... */ + last_pulse = pulse; + lircdata &= LIRC_PULSE_MASK; /* how long the pulse was in microseconds */ + } + + if (ret && count) + { + if (count * POLL_MS > lircdata) + lircdata = 0; + else + lircdata -= count * POLL_MS; + count = 0; + } + //printf("lircdata: ret:%d c:%d %d\n", ret, ch - '0', lircdata); + lircdata /= (1000000 / F_INTERRUPTS); + + if (pulse) + IRMP_PIN = 0x00; + else + IRMP_PIN = 0xff; + + do { + (void) irmp_ISR (); + if (irmp_get_data (&d)) + { + printf("protocol: %2d address: 0x%04x command: 0x%04x flags: %d\n", + d.protocol, d.address, d.command, d.flags); + + /* do something else here... */ + + /* todo: do we need to complete the loop if we already + * detected the singal in this pulse? */ + } + } while (lircdata-- > 0); + } + return 0; +} +#endif // IRMP_EMBED +#endif // LIRC_IRMP +#endif // ANALYZE diff --git a/libspark/irmp.h b/libspark/irmp.h new file mode 100644 index 0000000..1e3852b --- /dev/null +++ b/libspark/irmp.h @@ -0,0 +1,528 @@ +/*--------------------------------------------------------------------------------------------------------------------------------------------------- + * irmp.h + * + * Copyright (c) 2009-2011 Frank Meyer - frank(at)fli4l.de + * + * $Id: irmp.h,v 1.70 2012/02/21 08:41:46 fm Exp $ + * + * ATMEGA88 @ 8 MHz + * + * 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 2 of the License, or + * (at your option) any later version. + *--------------------------------------------------------------------------------------------------------------------------------------------------- + */ + +#ifndef _WC_IRMP_H_ +#define _WC_IRMP_H_ + +#if defined(__18CXX) // Microchip C18 declaration of missing typedef +typedef unsigned char uint8_t; +typedef unsigned int uint16_t; +#endif //Microchip C18 + +/*--------------------------------------------------------------------------------------------------------------------------------------------------- + * timing constants: + *--------------------------------------------------------------------------------------------------------------------------------------------------- + */ +// fm 22.09.2011: may not be more than 16000L, otherwise some JVC codes will not be accepted +#define IRMP_TIMEOUT_TIME 15500.0e-6 // timeout after 15.5 ms darkness +#define IRMP_TIMEOUT_TIME_MS 15500L // timeout after 15.5 ms darkness + +#if IRMP_SUPPORT_NIKON_PROTOCOL == 1 +#define IRMP_TIMEOUT_NIKON_TIME 29500.0e-6 // 2nd timeout after 29.5 ms darkness (only for NIKON!) +#define IRMP_TIMEOUT_NIKON_TIME_MS 29500L // 2nd timeout after 29.5 ms darkness +typedef uint16_t PAUSE_LEN; +#define IRMP_TIMEOUT_NIKON_LEN (PAUSE_LEN)(F_INTERRUPTS * IRMP_TIMEOUT_NIKON_TIME + 0.5) +#else +#if (F_INTERRUPTS * IRMP_TIMEOUT_TIME_MS) / 1000000 >= 254 +typedef uint16_t PAUSE_LEN; +#else +typedef uint8_t PAUSE_LEN; +#endif +#endif + +#define IRMP_TIMEOUT_LEN (PAUSE_LEN)(F_INTERRUPTS * IRMP_TIMEOUT_TIME + 0.5) + +/*--------------------------------------------------------------------------------------------------------------------------------------------------- + * IR protocols + *--------------------------------------------------------------------------------------------------------------------------------------------------- + */ +#define IRMP_SIRCS_PROTOCOL 1 // Sony +#define IRMP_NEC_PROTOCOL 2 // NEC, Pioneer, JVC, Toshiba, NoName etc. +#define IRMP_SAMSUNG_PROTOCOL 3 // Samsung +#define IRMP_MATSUSHITA_PROTOCOL 4 // Matsushita +#define IRMP_KASEIKYO_PROTOCOL 5 // Kaseikyo (Panasonic etc) +#define IRMP_RECS80_PROTOCOL 6 // Philips, Thomson, Nordmende, Telefunken, Saba +#define IRMP_RC5_PROTOCOL 7 // Philips etc +#define IRMP_DENON_PROTOCOL 8 // Denon, Sharp +#define IRMP_RC6_PROTOCOL 9 // Philips etc +#define IRMP_SAMSUNG32_PROTOCOL 10 // Samsung32: no sync pulse at bit 16, length 32 instead of 37 +#define IRMP_APPLE_PROTOCOL 11 // Apple, very similar to NEC +#define IRMP_RECS80EXT_PROTOCOL 12 // Philips, Technisat, Thomson, Nordmende, Telefunken, Saba +#define IRMP_NUBERT_PROTOCOL 13 // Nubert +#define IRMP_BANG_OLUFSEN_PROTOCOL 14 // Bang & Olufsen +#define IRMP_GRUNDIG_PROTOCOL 15 // Grundig +#define IRMP_NOKIA_PROTOCOL 16 // Nokia +#define IRMP_SIEMENS_PROTOCOL 17 // Siemens, e.g. Gigaset +#define IRMP_FDC_PROTOCOL 18 // FDC keyboard +#define IRMP_RCCAR_PROTOCOL 19 // RC Car +#define IRMP_JVC_PROTOCOL 20 // JVC (NEC with 16 bits) +#define IRMP_RC6A_PROTOCOL 21 // RC6A, e.g. Kathrein, XBOX +#define IRMP_NIKON_PROTOCOL 22 // Nikon +#define IRMP_RUWIDO_PROTOCOL 23 // Ruwido, e.g. T-Home Mediareceiver +#define IRMP_IR60_PROTOCOL 24 // IR60 (SAB2008) +#define IRMP_KATHREIN_PROTOCOL 25 // Kathrein +#define IRMP_NETBOX_PROTOCOL 26 // Netbox keyboard (bitserial) +#define IRMP_NEC16_PROTOCOL 27 // NEC with 16 bits (incl. sync) +#define IRMP_NEC42_PROTOCOL 28 // NEC with 42 bits +#define IRMP_LEGO_PROTOCOL 29 // LEGO Power Functions RC +#define IRMP_THOMSON_PROTOCOL 30 // Thomson +#define IRMP_GRUNDIG2_PROTOCOL 31 // Grundig, e.g. TP400 + +#define IRMP_N_PROTOCOLS 31 // number of supported protocols + +// some flags of struct IRMP_PARAMETER: +#define IRMP_PARAM_FLAG_IS_MANCHESTER 0x01 +#define IRMP_PARAM_FLAG_1ST_PULSE_IS_1 0x02 +#define IRMP_PARAM_FLAG_IS_SERIAL 0x04 + +#define SIRCS_START_BIT_PULSE_TIME 2400.0e-6 // 2400 usec pulse +#define SIRCS_START_BIT_PAUSE_TIME 600.0e-6 // 600 usec pause +#define SIRCS_1_PULSE_TIME 1200.0e-6 // 1200 usec pulse +#define SIRCS_0_PULSE_TIME 600.0e-6 // 600 usec pulse +#define SIRCS_PAUSE_TIME 600.0e-6 // 600 usec pause +#define SIRCS_FRAMES 3 // SIRCS sends each frame 3 times +#define SIRCS_AUTO_REPETITION_PAUSE_TIME 25.0e-3 // auto repetition after 25ms +#define SIRCS_FRAME_REPEAT_PAUSE_TIME 25.0e-3 // frame repeat after 25ms +#define SIRCS_ADDRESS_OFFSET 15 // skip 15 bits +#define SIRCS_ADDRESS_LEN 5 // read up to 5 address bits +#define SIRCS_COMMAND_OFFSET 0 // skip 0 bits +#define SIRCS_COMMAND_LEN 15 // read 12-15 command bits +#define SIRCS_MINIMUM_DATA_LEN 12 // minimum data length +#define SIRCS_COMPLETE_DATA_LEN 20 // complete length - may be up to 20 +#define SIRCS_STOP_BIT 0 // has no stop bit +#define SIRCS_LSB 1 // LSB...MSB +#define SIRCS_FLAGS 0 // flags + +#define NEC_START_BIT_PULSE_TIME 9000.0e-6 // 9000 usec pulse +#define NEC_START_BIT_PAUSE_TIME 4500.0e-6 // 4500 usec pause +#define NEC_REPEAT_START_BIT_PAUSE_TIME 2250.0e-6 // 2250 usec pause +#define NEC_PULSE_TIME 560.0e-6 // 560 usec pulse +#define NEC_1_PAUSE_TIME 1690.0e-6 // 1690 usec pause +#define NEC_0_PAUSE_TIME 560.0e-6 // 560 usec pause +#define NEC_FRAME_REPEAT_PAUSE_TIME 40.0e-3 // frame repeat after 40ms +#define NEC_ADDRESS_OFFSET 0 // skip 0 bits +#define NEC_ADDRESS_LEN 16 // read 16 address bits +#define NEC_COMMAND_OFFSET 16 // skip 16 bits (8 address + 8 /address) +#define NEC_COMMAND_LEN 16 // read 16 bits (8 command + 8 /command) +#define NEC_COMPLETE_DATA_LEN 32 // complete length +#define NEC_STOP_BIT 1 // has stop bit +#define NEC_LSB 1 // LSB...MSB +#define NEC_FLAGS 0 // flags + +#define NEC42_ADDRESS_OFFSET 0 // skip 0 bits +#define NEC42_ADDRESS_LEN 13 // read 13 address bits +#define NEC42_COMMAND_OFFSET 26 // skip 26 bits (2 x 13 address bits) +#define NEC42_COMMAND_LEN 8 // read 8 command bits +#define NEC42_COMPLETE_DATA_LEN 42 // complete length (2 x 13 + 2 x 8) + +#define NEC16_ADDRESS_OFFSET 0 // skip 0 bits +#define NEC16_ADDRESS_LEN 8 // read 8 address bits +#define NEC16_COMMAND_OFFSET 8 // skip 8 bits (8 address) +#define NEC16_COMMAND_LEN 8 // read 8 bits (8 command) +#define NEC16_COMPLETE_DATA_LEN 16 // complete length + +#define SAMSUNG_START_BIT_PULSE_TIME 4500.0e-6 // 4500 usec pulse +#define SAMSUNG_START_BIT_PAUSE_TIME 4500.0e-6 // 4500 usec pause +#define SAMSUNG_PULSE_TIME 550.0e-6 // 550 usec pulse +#define SAMSUNG_1_PAUSE_TIME 1650.0e-6 // 1650 usec pause +#define SAMSUNG_0_PAUSE_TIME 550.0e-6 // 550 usec pause + +#define SAMSUNG_FRAME_REPEAT_PAUSE_TIME 25.0e-3 // frame repeat after 25ms +#define SAMSUNG_ADDRESS_OFFSET 0 // skip 0 bits +#define SAMSUNG_ADDRESS_LEN 16 // read 16 address bits +#define SAMSUNG_ID_OFFSET 17 // skip 16 + 1 sync bit +#define SAMSUNG_ID_LEN 4 // read 4 id bits +#define SAMSUNG_COMMAND_OFFSET 21 // skip 16 + 1 sync + 4 data bits +#define SAMSUNG_COMMAND_LEN 16 // read 16 command bits +#define SAMSUNG_COMPLETE_DATA_LEN 37 // complete length +#define SAMSUNG_STOP_BIT 1 // has stop bit +#define SAMSUNG_LSB 1 // LSB...MSB? +#define SAMSUNG_FLAGS 0 // flags + +#define SAMSUNG32_COMMAND_OFFSET 16 // skip 16 bits +#define SAMSUNG32_COMMAND_LEN 16 // read 16 command bits +#define SAMSUNG32_COMPLETE_DATA_LEN 32 // complete length +#define SAMSUNG32_FRAMES 1 // SAMSUNG32 sends each frame 1 times +#define SAMSUNG32_AUTO_REPETITION_PAUSE_TIME 47.0e-3 // repetition after 47 ms +#define SAMSUNG32_FRAME_REPEAT_PAUSE_TIME 47.0e-3 // frame repeat after 47ms + +#define MATSUSHITA_START_BIT_PULSE_TIME 3488.0e-6 // 3488 usec pulse +#define MATSUSHITA_START_BIT_PAUSE_TIME 3488.0e-6 // 3488 usec pause +#define MATSUSHITA_PULSE_TIME 872.0e-6 // 872 usec pulse +#define MATSUSHITA_1_PAUSE_TIME 2616.0e-6 // 2616 usec pause +#define MATSUSHITA_0_PAUSE_TIME 872.0e-6 // 872 usec pause +#define MATSUSHITA_FRAME_REPEAT_PAUSE_TIME 45.0e-3 // frame repeat after 45ms +#define MATSUSHITA_ADDRESS_OFFSET 12 // skip 12 bits +#define MATSUSHITA_ADDRESS_LEN 12 // read 12 address bits +#define MATSUSHITA_COMMAND_OFFSET 0 // skip 0 bits +#define MATSUSHITA_COMMAND_LEN 12 // read 12 bits (6 custom + 6 command) +#define MATSUSHITA_COMPLETE_DATA_LEN 24 // complete length +#define MATSUSHITA_STOP_BIT 1 // has stop bit +#define MATSUSHITA_LSB 1 // LSB...MSB? +#define MATSUSHITA_FLAGS 0 // flags + +#define KASEIKYO_START_BIT_PULSE_TIME 3380.0e-6 // 3380 usec pulse +#define KASEIKYO_START_BIT_PAUSE_TIME 1690.0e-6 // 1690 usec pause +#define KASEIKYO_PULSE_TIME 423.0e-6 // 525 usec pulse +#define KASEIKYO_1_PAUSE_TIME 1269.0e-6 // 525 usec pause +#define KASEIKYO_0_PAUSE_TIME 423.0e-6 // 1690 usec pause +#define KASEIKYO_AUTO_REPETITION_PAUSE_TIME 74.0e-3 // repetition after 74 ms +#define KASEIKYO_FRAME_REPEAT_PAUSE_TIME 74.0e-3 // frame repeat after 74 ms +#define KASEIKYO_ADDRESS_OFFSET 0 // skip 0 bits +#define KASEIKYO_ADDRESS_LEN 16 // read 16 address bits +#define KASEIKYO_COMMAND_OFFSET 28 // skip 28 bits (16 manufacturer & 4 parity & 8 genre) +#define KASEIKYO_COMMAND_LEN 12 // read 12 command bits (10 real command & 2 id) +#define KASEIKYO_COMPLETE_DATA_LEN 48 // complete length +#define KASEIKYO_STOP_BIT 1 // has stop bit +#define KASEIKYO_LSB 1 // LSB...MSB? +#define KASEIKYO_FRAMES 2 // KASEIKYO sends 1st frame 2 times +#define KASEIKYO_FLAGS 0 // flags + +#define RECS80_START_BIT_PULSE_TIME 158.0e-6 // 158 usec pulse +#define RECS80_START_BIT_PAUSE_TIME 7432.0e-6 // 7432 usec pause +#define RECS80_PULSE_TIME 158.0e-6 // 158 usec pulse +#define RECS80_1_PAUSE_TIME 7432.0e-6 // 7432 usec pause +#define RECS80_0_PAUSE_TIME 4902.0e-6 // 4902 usec pause +#define RECS80_FRAME_REPEAT_PAUSE_TIME 45.0e-3 // frame repeat after 45ms +#define RECS80_ADDRESS_OFFSET 1 // skip 1 bit (toggle bit) +#define RECS80_ADDRESS_LEN 3 // read 3 address bits +#define RECS80_COMMAND_OFFSET 4 // skip 4 bits (1 toggle + 3 address) +#define RECS80_COMMAND_LEN 6 // read 6 command bits +#define RECS80_COMPLETE_DATA_LEN 10 // complete length +#define RECS80_STOP_BIT 1 // has stop bit +#define RECS80_LSB 0 // MSB...LSB +#define RECS80_FLAGS 0 // flags + +#define RC5_BIT_TIME 889.0e-6 // 889 usec pulse/pause +#define RC5_FRAME_REPEAT_PAUSE_TIME 45.0e-3 // frame repeat after 45ms + +#define RC5_ADDRESS_OFFSET 1 // skip 1 bit (2nd start) +#define RC5_ADDRESS_LEN 6 // read 1 toggle bit (for key repetition detection) + 5 address bits +#define RC5_COMMAND_OFFSET 7 // skip 5 bits (2nd start + 1 toggle + 5 address) +#define RC5_COMMAND_LEN 6 // read 6 command bits +#define RC5_COMPLETE_DATA_LEN 13 // complete length +#define RC5_STOP_BIT 0 // has no stop bit +#define RC5_LSB 0 // MSB...LSB +#define RC5_FLAGS IRMP_PARAM_FLAG_IS_MANCHESTER // flags + +#define DENON_PULSE_TIME 310.0e-6 // 310 usec pulse in practice, 275 in theory +#define DENON_1_PAUSE_TIME 1780.0e-6 // 1780 usec pause in practice, 1900 in theory +#define DENON_0_PAUSE_TIME 745.0e-6 // 745 usec pause in practice, 775 in theory +#define DENON_FRAMES 2 // DENON sends each frame 2 times +#define DENON_AUTO_REPETITION_PAUSE_TIME 65.0e-3 // inverted repetition after 65ms +#define DENON_FRAME_REPEAT_PAUSE_TIME 65.0e-3 // frame repeat after 65ms +#define DENON_ADDRESS_OFFSET 0 // skip 0 bits +#define DENON_ADDRESS_LEN 5 // read 5 address bits +#define DENON_COMMAND_OFFSET 5 // skip 5 +#define DENON_COMMAND_LEN 10 // read 10 command bits +#define DENON_COMPLETE_DATA_LEN 15 // complete length +#define DENON_STOP_BIT 1 // has stop bit +#define DENON_LSB 0 // MSB...LSB +#define DENON_FLAGS 0 // flags + +#define RC6_START_BIT_PULSE_TIME 2666.0e-6 // 2.666 msec pulse +#define RC6_START_BIT_PAUSE_TIME 889.0e-6 // 889 usec pause +#define RC6_TOGGLE_BIT_TIME 889.0e-6 // 889 msec pulse/pause +#define RC6_BIT_TIME 444.0e-6 // 889 usec pulse/pause +#define RC6_FRAME_REPEAT_PAUSE_TIME 45.0e-3 // frame repeat after 45ms +#define RC6_ADDRESS_OFFSET 5 // skip "1" + 3 mode bits + 1 toggle bit +#define RC6_ADDRESS_LEN 8 // read 8 address bits +#define RC6_COMMAND_OFFSET 13 // skip 12 bits ("1" + 3 mode + 1 toggle + 8 address) +#define RC6_COMMAND_LEN 8 // read 8 command bits +#define RC6_COMPLETE_DATA_LEN_SHORT 21 // complete length +#define RC6_COMPLETE_DATA_LEN_LONG 36 // complete length +#define RC6_STOP_BIT 0 // has no stop bit +#define RC6_LSB 0 // MSB...LSB +#define RC6_FLAGS (IRMP_PARAM_FLAG_IS_MANCHESTER | IRMP_PARAM_FLAG_1ST_PULSE_IS_1) // flags + +#define RECS80EXT_START_BIT_PULSE_TIME 158.0e-6 // 158 usec pulse +#define RECS80EXT_START_BIT_PAUSE_TIME 3637.0e-6 // 3637 usec pause +#define RECS80EXT_PULSE_TIME 158.0e-6 // 158 usec pulse +#define RECS80EXT_1_PAUSE_TIME 7432.0e-6 // 7432 usec pause +#define RECS80EXT_0_PAUSE_TIME 4902.0e-6 // 4902 usec pause +#define RECS80EXT_FRAME_REPEAT_PAUSE_TIME 45.0e-3 // frame repeat after 45ms +#define RECS80EXT_ADDRESS_OFFSET 2 // skip 2 bits (2nd start + 1 toggle) +#define RECS80EXT_ADDRESS_LEN 4 // read 4 address bits +#define RECS80EXT_COMMAND_OFFSET 6 // skip 6 bits (2nd start + 1 toggle + 4 address) +#define RECS80EXT_COMMAND_LEN 6 // read 6 command bits +#define RECS80EXT_COMPLETE_DATA_LEN 12 // complete length +#define RECS80EXT_STOP_BIT 1 // has stop bit +#define RECS80EXT_LSB 0 // MSB...LSB +#define RECS80EXT_FLAGS 0 // flags + +#define NUBERT_START_BIT_PULSE_TIME 1340.0e-6 // 1340 usec pulse +#define NUBERT_START_BIT_PAUSE_TIME 340.0e-6 // 340 usec pause +#define NUBERT_1_PULSE_TIME 1340.0e-6 // 1340 usec pulse +#define NUBERT_1_PAUSE_TIME 340.0e-6 // 340 usec pause +#define NUBERT_0_PULSE_TIME 500.0e-6 // 500 usec pulse +#define NUBERT_0_PAUSE_TIME 1300.0e-6 // 1300 usec pause +#define NUBERT_FRAMES 2 // Nubert sends 2 frames +#define NUBERT_AUTO_REPETITION_PAUSE_TIME 35.0e-3 // auto repetition after 35ms +#define NUBERT_FRAME_REPEAT_PAUSE_TIME 35.0e-3 // frame repeat after 45ms +#define NUBERT_ADDRESS_OFFSET 0 // skip 0 bits +#define NUBERT_ADDRESS_LEN 0 // read 0 address bits +#define NUBERT_COMMAND_OFFSET 0 // skip 0 bits +#define NUBERT_COMMAND_LEN 10 // read 10 bits +#define NUBERT_COMPLETE_DATA_LEN 10 // complete length +#define NUBERT_STOP_BIT 1 // has stop bit +#define NUBERT_LSB 0 // MSB? +#define NUBERT_FLAGS 0 // flags + +#define BANG_OLUFSEN_START_BIT1_PULSE_TIME 200.0e-6 // 200 usec pulse +#define BANG_OLUFSEN_START_BIT1_PAUSE_TIME 3125.0e-6 // 3125 usec pause +#define BANG_OLUFSEN_START_BIT2_PULSE_TIME 200.0e-6 // 200 usec pulse +#define BANG_OLUFSEN_START_BIT2_PAUSE_TIME 3125.0e-6 // 3125 usec pause +#define BANG_OLUFSEN_START_BIT3_PULSE_TIME 200.0e-6 // 200 usec pulse +#define BANG_OLUFSEN_START_BIT3_PAUSE_TIME 15625.0e-6 // 15625 usec pause +#define BANG_OLUFSEN_START_BIT4_PULSE_TIME 200.0e-6 // 200 usec pulse +#define BANG_OLUFSEN_START_BIT4_PAUSE_TIME 3125.0e-6 // 3125 usec pause +#define BANG_OLUFSEN_PULSE_TIME 200.0e-6 // 200 usec pulse +#define BANG_OLUFSEN_1_PAUSE_TIME 9375.0e-6 // 9375 usec pause +#define BANG_OLUFSEN_0_PAUSE_TIME 3125.0e-6 // 3125 usec pause +#define BANG_OLUFSEN_R_PAUSE_TIME 6250.0e-6 // 6250 usec pause (repeat last bit) +#define BANG_OLUFSEN_TRAILER_BIT_PAUSE_TIME 12500.0e-6 // 12500 usec pause (trailer bit) +#define BANG_OLUFSEN_FRAME_REPEAT_PAUSE_TIME 45.0e-3 // frame repeat after 45ms +#define BANG_OLUFSEN_ADDRESS_OFFSET 0 // no address bits +#define BANG_OLUFSEN_ADDRESS_LEN 0 // no address bits +#define BANG_OLUFSEN_COMMAND_OFFSET 3 // skip startbits 2, 3, 4 +#define BANG_OLUFSEN_COMMAND_LEN 16 // read 16 command bits +#define BANG_OLUFSEN_COMPLETE_DATA_LEN 20 // complete length: startbits 2, 3, 4 + 16 data bits + trailer bit +#define BANG_OLUFSEN_STOP_BIT 1 // has stop bit +#define BANG_OLUFSEN_LSB 0 // MSB...LSB +#define BANG_OLUFSEN_FLAGS 0 // flags + +#define GRUNDIG_NOKIA_IR60_BIT_TIME 528.0e-6 // 528 usec pulse/pause +#define GRUNDIG_NOKIA_IR60_PRE_PAUSE_TIME 2639.0e-6 // 2639 usec pause after pre bit +#define GRUNDIG_NOKIA_IR60_FRAME_REPEAT_PAUSE_TIME 117.76e-3 // info frame repeat after 117.76 ms +#define GRUNDIG_NOKIA_IR60_STOP_BIT 0 // has no stop bit +#define GRUNDIG_NOKIA_IR60_LSB 1 // MSB...LSB +#define GRUNDIG_NOKIA_IR60_FLAGS (IRMP_PARAM_FLAG_IS_MANCHESTER | IRMP_PARAM_FLAG_1ST_PULSE_IS_1) // flags + +#define GRUNDIG_FRAMES 2 // GRUNDIG sends each frame 1+1 times +#define GRUNDIG_AUTO_REPETITION_PAUSE_TIME 20.0e-3 // repetition after 20ms +#define GRUNDIG_ADDRESS_OFFSET 0 // no address +#define GRUNDIG_ADDRESS_LEN 0 // no address +#define GRUNDIG_COMMAND_OFFSET 1 // skip 1 start bit +#define GRUNDIG_COMMAND_LEN 9 // read 9 command bits +#define GRUNDIG_COMPLETE_DATA_LEN 10 // complete length: 1 start bit + 9 data bits + +#define NOKIA_FRAMES 3 // NOKIA sends each frame 1 + 1 + 1 times +#define NOKIA_AUTO_REPETITION_PAUSE_TIME 20.0e-3 // repetition after 20ms +#define NOKIA_ADDRESS_OFFSET 9 // skip 9 bits (1 start bit + 8 data bits) +#define NOKIA_ADDRESS_LEN 8 // 7 address bits +#define NOKIA_COMMAND_OFFSET 1 // skip 1 bit (1 start bit) +#define NOKIA_COMMAND_LEN 8 // read 8 command bits +#define NOKIA_COMPLETE_DATA_LEN 17 // complete length: 1 start bit + 8 address bits + 8 command bits + +#define IR60_TIMEOUT_TIME 5000.0e-6 // timeout grundig frame, switch to IR60 +#define IR60_ADDRESS_OFFSET 0 // skip 1 bits +#define IR60_ADDRESS_LEN 0 // read 0 address bits +#define IR60_COMMAND_OFFSET 0 // skip 1 bit (start bit after pre bit, always 1) +#define IR60_COMMAND_LEN 7 // read 6 command bits +#define IR60_COMPLETE_DATA_LEN 7 // complete length + +#define SIEMENS_OR_RUWIDO_START_BIT_PULSE_TIME 275.0e-6 // 275 usec pulse +#define SIEMENS_OR_RUWIDO_START_BIT_PAUSE_TIME 550.0e-6 // 550 usec pause +#define SIEMENS_OR_RUWIDO_BIT_PULSE_TIME 275.0e-6 // 275 usec short pulse +#define SIEMENS_OR_RUWIDO_BIT_PULSE_TIME_2 550.0e-6 // 550 usec long pulse +#define SIEMENS_OR_RUWIDO_BIT_PAUSE_TIME 275.0e-6 // 275 usec short pause +#define SIEMENS_OR_RUWIDO_BIT_PAUSE_TIME_2 550.0e-6 // 550 usec long pause +#define SIEMENS_OR_RUWIDO_FRAME_REPEAT_PAUSE_TIME 45.0e-3 // frame repeat after 45ms +#define SIEMENS_OR_RUWIDO_STOP_BIT 0 // has no stop bit +#define SIEMENS_OR_RUWIDO_LSB 0 // MSB...LSB +#define SIEMENS_OR_RUWIDO_FLAGS (IRMP_PARAM_FLAG_IS_MANCHESTER | IRMP_PARAM_FLAG_1ST_PULSE_IS_1) // flags + +#define RUWIDO_ADDRESS_OFFSET 0 // skip 0 bits +#define RUWIDO_ADDRESS_LEN 9 // read 9 address bits +#define RUWIDO_COMMAND_OFFSET 9 // skip 9 bits +#define RUWIDO_COMMAND_LEN 8 // read 7 + 1 command bits, last bit is only check bit +#define RUWIDO_COMPLETE_DATA_LEN 17 // complete length + +#define SIEMENS_ADDRESS_OFFSET 0 // skip 0 bits +#define SIEMENS_ADDRESS_LEN 11 // read 11 bits +#define SIEMENS_COMMAND_OFFSET 11 // skip 11 bits +#define SIEMENS_COMMAND_LEN 11 // read 10 + 1 command bits, last bit is only check bit +#define SIEMENS_COMPLETE_DATA_LEN 22 // complete length + +#define FDC_START_BIT_PULSE_TIME 2085.0e-6 // 2085 usec pulse +#define FDC_START_BIT_PAUSE_TIME 966.0e-6 // 966 usec pause +#define FDC_PULSE_TIME 300.0e-6 // 300 usec pulse +#define FDC_1_PAUSE_TIME 715.0e-6 // 715 usec pause +#define FDC_0_PAUSE_TIME 220.0e-6 // 220 usec pause +#define FDC_FRAME_REPEAT_PAUSE_TIME 60.0e-3 // frame repeat after 60ms +#define FDC_ADDRESS_OFFSET 0 // skip 0 bits +#define FDC_ADDRESS_LEN 14 // read 14 address bits, but use only 6, shift 8 into command +#define FDC_COMMAND_OFFSET 20 // skip 20 bits +#define FDC_COMMAND_LEN 12 // read 12 bits +#define FDC_COMPLETE_DATA_LEN 40 // complete length +#define FDC_STOP_BIT 1 // has stop bit +#define FDC_LSB 1 // LSB...MSB +#define FDC_FLAGS 0 // flags + +#define RCCAR_START_BIT_PULSE_TIME 2000.0e-6 // 2000 usec pulse +#define RCCAR_START_BIT_PAUSE_TIME 2000.0e-6 // 2000 usec pause +#define RCCAR_PULSE_TIME 600.0e-6 // 360 usec pulse +#define RCCAR_1_PAUSE_TIME 450.0e-6 // 650 usec pause +#define RCCAR_0_PAUSE_TIME 900.0e-6 // 180 usec pause +#define RCCAR_FRAME_REPEAT_PAUSE_TIME 40.0e-3 // frame repeat after 40ms +#define RCCAR_ADDRESS_OFFSET 0 // skip 0 bits +#define RCCAR_ADDRESS_LEN 0 // read 0 address bits +#define RCCAR_COMMAND_OFFSET 0 // skip 0 bits +#define RCCAR_COMMAND_LEN 13 // read 13 bits +#define RCCAR_COMPLETE_DATA_LEN 13 // complete length +#define RCCAR_STOP_BIT 1 // has stop bit +#define RCCAR_LSB 1 // LSB...MSB +#define RCCAR_FLAGS 0 // flags + +#define JVC_START_BIT_PULSE_TIME 9000.0e-6 // 9000 usec pulse +#define JVC_START_BIT_PAUSE_TIME 4500.0e-6 // 4500 usec pause +#define JVC_PULSE_TIME 560.0e-6 // 560 usec pulse +#define JVC_1_PAUSE_TIME 1690.0e-6 // 1690 usec pause +#define JVC_0_PAUSE_TIME 560.0e-6 // 560 usec pause +#define JVC_FRAME_REPEAT_PAUSE_TIME 22.0e-3 // frame repeat after 22ms +#define JVC_ADDRESS_OFFSET 0 // skip 0 bits +#define JVC_ADDRESS_LEN 4 // read 4 address bits +#define JVC_COMMAND_OFFSET 4 // skip 4 bits +#define JVC_COMMAND_LEN 12 // read 12 bits +#define JVC_COMPLETE_DATA_LEN 16 // complete length +#define JVC_STOP_BIT 1 // has stop bit +#define JVC_LSB 1 // LSB...MSB +#define JVC_FLAGS 0 // flags + +#define NIKON_START_BIT_PULSE_TIME 2200.0e-6 // 2200 usec pulse +#define NIKON_START_BIT_PAUSE_TIME 27100.0e-6 // 27100 usec pause +#define NIKON_PULSE_TIME 500.0e-6 // 500 usec pulse +#define NIKON_1_PAUSE_TIME 3500.0e-6 // 3500 usec pause +#define NIKON_0_PAUSE_TIME 1500.0e-6 // 1500 usec pause +#define NIKON_FRAME_REPEAT_PAUSE_TIME 60.0e-3 // frame repeat after 60ms +#define NIKON_ADDRESS_OFFSET 0 // skip 0 bits +#define NIKON_ADDRESS_LEN 0 // read 0 address bits +#define NIKON_COMMAND_OFFSET 0 // skip 0 bits +#define NIKON_COMMAND_LEN 2 // read 2 bits +#define NIKON_COMPLETE_DATA_LEN 2 // complete length +#define NIKON_STOP_BIT 1 // has stop bit +#define NIKON_LSB 0 // LSB...MSB +#define NIKON_FLAGS 0 // flags + +#define KATHREIN_START_BIT_PULSE_TIME 210.0e-6 // 1340 usec pulse +#define KATHREIN_START_BIT_PAUSE_TIME 6218.0e-6 // 340 usec pause +#define KATHREIN_1_PULSE_TIME 210.0e-6 // 1340 usec pulse +#define KATHREIN_1_PAUSE_TIME 3000.0e-6 // 340 usec pause +#define KATHREIN_0_PULSE_TIME 210.0e-6 // 500 usec pulse +#define KATHREIN_0_PAUSE_TIME 1400.0e-6 // 1300 usec pause +#define KATHREIN_SYNC_BIT_PAUSE_LEN_TIME 4600.0e-6 // 4600 usec sync (on 6th and/or 8th bit) +#define KATHREIN_FRAMES 1 // Kathrein sends 1 frame +#define KATHREIN_AUTO_REPETITION_PAUSE_TIME 35.0e-3 // auto repetition after 35ms +#define KATHREIN_FRAME_REPEAT_PAUSE_TIME 35.0e-3 // frame repeat after 35ms +#define KATHREIN_ADDRESS_OFFSET 1 // skip 1 bits +#define KATHREIN_ADDRESS_LEN 4 // read 4 address bits +#define KATHREIN_COMMAND_OFFSET 5 // skip 5 bits +#define KATHREIN_COMMAND_LEN 7 // read 7 bits +#define KATHREIN_COMPLETE_DATA_LEN 13 // complete length +#define KATHREIN_STOP_BIT 1 // has stop bit +#define KATHREIN_LSB 0 // MSB +#define KATHREIN_FLAGS 0 // flags + +#define NETBOX_START_BIT_PULSE_TIME 2400.0e-6 // 2400 usec pulse +#define NETBOX_START_BIT_PAUSE_TIME 800.0e-6 // 800 usec pause +#define NETBOX_PULSE_TIME 800.0e-6 // 800 usec pulse +#define NETBOX_PAUSE_TIME 800.0e-6 // 800 usec pause +#define NETBOX_FRAMES 1 // Netbox sends 1 frame +#define NETBOX_AUTO_REPETITION_PAUSE_TIME 35.0e-3 // auto repetition after 35ms +#define NETBOX_FRAME_REPEAT_PAUSE_TIME 35.0e-3 // frame repeat after 35ms +#define NETBOX_ADDRESS_OFFSET 0 // skip 0 bits +#define NETBOX_ADDRESS_LEN 3 // read 3 address bits +#define NETBOX_COMMAND_OFFSET 3 // skip 3 bits +#define NETBOX_COMMAND_LEN 13 // read 13 bits +#define NETBOX_COMPLETE_DATA_LEN 16 // complete length +#define NETBOX_STOP_BIT 0 // has no stop bit +#define NETBOX_LSB 1 // LSB +#define NETBOX_FLAGS IRMP_PARAM_FLAG_IS_SERIAL // flags + +#define LEGO_START_BIT_PULSE_TIME 158.0e-6 // 158 usec pulse ( 6 x 1/38kHz) +#define LEGO_START_BIT_PAUSE_TIME 1026.0e-6 // 1026 usec pause (39 x 1/38kHz) +#define LEGO_PULSE_TIME 158.0e-6 // 158 usec pulse ( 6 x 1/38kHz) +#define LEGO_1_PAUSE_TIME 553.0e-6 // 553 usec pause (21 x 1/38kHz) +#define LEGO_0_PAUSE_TIME 263.0e-6 // 263 usec pause (10 x 1/38kHz) +#define LEGO_FRAME_REPEAT_PAUSE_TIME 40.0e-3 // frame repeat after 40ms +#define LEGO_ADDRESS_OFFSET 0 // skip 0 bits +#define LEGO_ADDRESS_LEN 0 // read 0 address bits +#define LEGO_COMMAND_OFFSET 0 // skip 0 bits +#define LEGO_COMMAND_LEN 16 // read 16 bits (12 command + 4 CRC) +#define LEGO_COMPLETE_DATA_LEN 16 // complete length +#define LEGO_STOP_BIT 1 // has stop bit +#define LEGO_LSB 0 // MSB...LSB +#define LEGO_FLAGS 0 // flags + +#define THOMSON_PULSE_TIME 550.0e-6 // 550 usec pulse +#define THOMSON_1_PAUSE_TIME 4500.0e-6 // 4500 usec pause +#define THOMSON_0_PAUSE_TIME 2000.0e-6 // 2000 usec pause +#define THOMSON_FRAMES 1 // THOMSON sends 1 frame +#define THOMSON_AUTO_REPETITION_PAUSE_TIME 65.0e-3 // repetition after 65ms +#define THOMSON_FRAME_REPEAT_PAUSE_TIME 65.0e-3 // frame repeat after 65ms +#define THOMSON_ADDRESS_OFFSET 0 // skip 0 bits +#define THOMSON_ADDRESS_LEN 4 // read 4 address bits +#define THOMSON_COMMAND_OFFSET 5 // skip 4 address bits + 1 toggle bit +#define THOMSON_COMMAND_LEN 7 // read 7 command bits +#define THOMSON_COMPLETE_DATA_LEN 12 // complete length +#define THOMSON_STOP_BIT 1 // has stop bit +#define THOMSON_LSB 0 // MSB...LSB +#define THOMSON_FLAGS 0 // flags + +#define GRUNDIG2_START_BIT_PULSE_TIME 550.0e-6 // 550 usec pulse +#define GRUNDIG2_START_BIT_PAUSE_TIME 2700.0e-6 // 2700 usec pause +#define GRUNDIG2_BIT_PULSE_TIME 550.0e-6 // 550 usec short pulse +#define GRUNDIG2_BIT_PAUSE_TIME 550.0e-6 // 550 usec short pause +#define GRUNDIG2_FRAME_REPEAT_PAUSE_TIME 100.0e-3 // frame repeat after 100ms +#define GRUNDIG2_STOP_BIT 0 // has no stop bit +#define GRUNDIG2_LSB 1 // MSB...LSB +#define GRUNDIG2_FLAGS (IRMP_PARAM_FLAG_IS_MANCHESTER | IRMP_PARAM_FLAG_1ST_PULSE_IS_1) // flags +#define GRUNDIG2_ADDRESS_OFFSET 0 // skip 0 bits +#define GRUNDIG2_ADDRESS_LEN 0 // read 0 bits +#define GRUNDIG2_COMMAND_OFFSET 0 // skip 0 bits +#define GRUNDIG2_COMMAND_LEN 7 // read 6 + 1 command bits, last bit is always 1 +#define GRUNDIG2_COMPLETE_DATA_LEN 7 // complete length + +#define AUTO_FRAME_REPETITION_TIME 80.0e-3 // SIRCS/SAMSUNG32/NUBERT: automatic repetition after 25-50ms + // KASEIKYO: automatic repetition after 75ms + +#define TRUE 1 +#define FALSE 0 + +#define IRMP_FLAG_REPETITION 0x01 + +typedef struct +{ + uint8_t protocol; // protocol, i.e. NEC_PROTOCOL + uint16_t address; // address + uint16_t command; // command + uint8_t flags; // flags, e.g. repetition +} IRMP_DATA; + +extern void irmp_init (void); +extern uint8_t irmp_get_data (IRMP_DATA *); +extern uint8_t irmp_is_busy (void); +extern uint8_t irmp_ISR (uint8_t); + +#if IRMP_PROTOCOL_NAMES == 1 +extern char * irmp_protocol_names[IRMP_N_PROTOCOLS + 1]; +#endif + +#if IRMP_USE_CALLBACK == 1 +extern void irmp_set_callback_ptr (void (*cb)(uint8_t)); +#endif // IRSND_USE_CALLBACK == 1 + +#endif /* _WC_IRMP_H_ */ diff --git a/libspark/irmpconfig.h b/libspark/irmpconfig.h new file mode 100644 index 0000000..10b34b7 --- /dev/null +++ b/libspark/irmpconfig.h @@ -0,0 +1,191 @@ +/*--------------------------------------------------------------------------------------------------------------------------------------------------- + * irmpconfig.h + * + * Copyright (c) 2009-2011 Frank Meyer - frank(at)fli4l.de + * + * $Id: irmpconfig.h,v 1.80 2012/02/21 08:41:46 fm Exp $ + * + * ATMEGA88 @ 8 MHz + * + * 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 2 of the License, or + * (at your option) any later version. + *--------------------------------------------------------------------------------------------------------------------------------------------------- + */ + +#ifndef _IRMPCONFIG_H_ +#define _IRMPCONFIG_H_ + +/*--------------------------------------------------------------------------------------------------------------------------------------------------- + * Change F_INTERRUPTS if you change the number of interrupts per second, + * Normally, F_INTERRUPTS should be in the range from 10000 to 15000, typical is 15000 + * A value above 15000 costs additional program space, absolute maximum value is 20000. + *--------------------------------------------------------------------------------------------------------------------------------------------------- + */ +#ifndef F_INTERRUPTS +#define F_INTERRUPTS 15000 // interrupts per second, min: 10000, max: 20000, typ: 15000 +#endif + +/*--------------------------------------------------------------------------------------------------------------------------------------------------- + * Change settings from 1 to 0 if you want to disable one or more decoders. + * This saves program space. + * + * 1 enable decoder + * 0 disable decoder + * + * The standard decoders are enabled per default. + * Less common protocols are disabled here, you need to enable them manually. + * + * If you want to use FDC or RCCAR simultaneous with RC5 protocol, additional program space is required. + * If you don't need RC5 when using FDC/RCCAR, you should disable RC5. + *--------------------------------------------------------------------------------------------------------------------------------------------------- + */ + +// typical protocols, disable here! Enable Remarks F_INTERRUPTS Program Space +#define IRMP_SUPPORT_SIRCS_PROTOCOL 1 // Sony SIRCS >= 10000 ~150 bytes +#define IRMP_SUPPORT_NEC_PROTOCOL 1 // NEC + APPLE >= 10000 ~300 bytes +#define IRMP_SUPPORT_SAMSUNG_PROTOCOL 1 // Samsung + Samsung32 >= 10000 ~300 bytes +#define IRMP_SUPPORT_MATSUSHITA_PROTOCOL 1 // Matsushita >= 10000 ~50 bytes +#define IRMP_SUPPORT_KASEIKYO_PROTOCOL 1 // Kaseikyo >= 10000 ~250 bytes +#define IRMP_SUPPORT_DENON_PROTOCOL 1 // DENON, Sharp >= 10000 ~250 bytes + +// more protocols, enable here! Enable Remarks F_INTERRUPTS Program Space +#define IRMP_SUPPORT_RC5_PROTOCOL 0 // RC5 >= 10000 ~250 bytes +#define IRMP_SUPPORT_RC6_PROTOCOL 0 // RC6 & RC6A >= 10000 ~250 bytes +#define IRMP_SUPPORT_JVC_PROTOCOL 0 // JVC >= 10000 ~150 bytes +#define IRMP_SUPPORT_NEC16_PROTOCOL 0 // NEC16 >= 10000 ~100 bytes +#define IRMP_SUPPORT_NEC42_PROTOCOL 0 // NEC42 >= 10000 ~300 bytes +#define IRMP_SUPPORT_IR60_PROTOCOL 0 // IR60 (SAB2008) >= 10000 ~300 bytes +#define IRMP_SUPPORT_GRUNDIG_PROTOCOL 0 // Grundig >= 10000 ~300 bytes +#define IRMP_SUPPORT_SIEMENS_PROTOCOL 0 // Siemens Gigaset >= 15000 ~550 bytes +#define IRMP_SUPPORT_NOKIA_PROTOCOL 0 // Nokia >= 10000 ~300 bytes + +// exotic protocols, enable here! Enable Remarks F_INTERRUPTS Program Space +#define IRMP_SUPPORT_GRUNDIG2_PROTOCOL 0 // Grundig TP400 >= 10000 ~300 bytes +#define IRMP_SUPPORT_KATHREIN_PROTOCOL 0 // Kathrein >= 10000 ~200 bytes +#define IRMP_SUPPORT_NUBERT_PROTOCOL 0 // NUBERT >= 10000 ~50 bytes +#define IRMP_SUPPORT_BANG_OLUFSEN_PROTOCOL 0 // Bang & Olufsen >= 10000 ~200 bytes +#define IRMP_SUPPORT_RECS80_PROTOCOL 0 // RECS80 (SAA3004) >= 15000 ~50 bytes +#define IRMP_SUPPORT_RECS80EXT_PROTOCOL 0 // RECS80EXT (SAA3008) >= 15000 ~50 bytes +#define IRMP_SUPPORT_THOMSON_PROTOCOL 0 // Thomson >= 10000 ~250 bytes +#define IRMP_SUPPORT_NIKON_PROTOCOL 0 // NIKON camera >= 10000 ~250 bytes +#define IRMP_SUPPORT_NETBOX_PROTOCOL 0 // Netbox keyboard >= 10000 ~400 bytes (PROTOTYPE!) +#define IRMP_SUPPORT_FDC_PROTOCOL 0 // FDC3402 keyboard >= 10000 (better 15000) ~150 bytes (~400 in combination with RC5) +#define IRMP_SUPPORT_RCCAR_PROTOCOL 0 // RC Car >= 10000 (better 15000) ~150 bytes (~500 in combination with RC5) +#define IRMP_SUPPORT_RUWIDO_PROTOCOL 0 // RUWIDO, T-Home >= 15000 ~550 bytes +#define IRMP_SUPPORT_LEGO_PROTOCOL 0 // LEGO Power RC >= 20000 ~150 bytes + +/*--------------------------------------------------------------------------------------------------------------------------------------------------- + * Change hardware pin here: + *--------------------------------------------------------------------------------------------------------------------------------------------------- + */ +#if defined (PIC_C18) // Microchip C18 Compiler +#include // main PIC18 h file +#define IRMP_PIN PORTBbits.RB4 // use RB4 as IR input on PIC +#define input(x) (x) + +#elif defined (PIC_CCS_COMPILER) // PIC CCS Compiler: +#define IRMP_PIN PIN_B4 // use PB4 as IR input on PIC + +#else // AVR: + +#ifndef ARDUINO +#define IRMP_PORT PORTB +#define IRMP_DDR DDRB +#define IRMP_PIN PINB +#define IRMP_BIT 6 // use PB6 as IR input on AVR +#else // ARDUINO +#define IRMP_PIN PIND // use digital pin 2 as IR input +#define IRMP_BIT 2 // on arduino +#endif // ARDUINO + +#define input(x) ((x) & (1 << IRMP_BIT)) +#endif + +/*--------------------------------------------------------------------------------------------------------------------------------------------------- + * Set IRMP_LOGGING to 1 if want to log data to UART with 9600Bd + *--------------------------------------------------------------------------------------------------------------------------------------------------- + */ +#ifndef IRMP_LOGGING +#define IRMP_LOGGING 0 // 1: log IR signal (scan), 0: do not (default) +#endif + +/*--------------------------------------------------------------------------------------------------------------------------------------------------- + * Use external logging routines + * If you enable external logging, you have also to enable IRMP_LOGGING above + *--------------------------------------------------------------------------------------------------------------------------------------------------- + */ +#ifndef IRMP_EXT_LOGGING +#define IRMP_EXT_LOGGING 0 // 1:log, 0: do not log ; +#endif + +/*--------------------------------------------------------------------------------------------------------------------------------------------------- + * Set IRMP_PROTOCOL_NAMES to 1 if want to access protocol names (for logging etc), costs ~300 bytes RAM! + *--------------------------------------------------------------------------------------------------------------------------------------------------- + */ +#define IRMP_PROTOCOL_NAMES 0 // 1: access protocol names, 0: do not (default), + +/*--------------------------------------------------------------------------------------------------------------------------------------------------- + * Use Callbacks to indicate input signal + *--------------------------------------------------------------------------------------------------------------------------------------------------- + */ +#define IRMP_USE_CALLBACK 0 // flag: 0 = don't use callbacks, 1 = use callbacks, default is 0 + +/*--------------------------------------------------------------------------------------------------------------------------------------------------- + * DO NOT CHANGE THE FOLLOWING LINES ! + *--------------------------------------------------------------------------------------------------------------------------------------------------- + */ +#if IRMP_SUPPORT_SIEMENS_PROTOCOL == 1 && F_INTERRUPTS < 15000 +# warning F_INTERRUPTS too low, SIEMENS protocol disabled (should be at least 15000) +# undef IRMP_SUPPORT_SIEMENS_PROTOCOL +# define IRMP_SUPPORT_SIEMENS_PROTOCOL 0 +#endif + +#if IRMP_SUPPORT_RUWIDO_PROTOCOL == 1 && F_INTERRUPTS < 15000 +# warning F_INTERRUPTS too low, RUWIDO protocol disabled (should be at least 15000) +# undef IRMP_SUPPORT_RUWIDO_PROTOCOL +# define IRMP_SUPPORT_RUWIDO_PROTOCOL 0 +#endif + +#if IRMP_SUPPORT_RECS80_PROTOCOL == 1 && F_INTERRUPTS < 15000 +# warning F_INTERRUPTS too low, RECS80 protocol disabled (should be at least 15000) +# undef IRMP_SUPPORT_RECS80_PROTOCOL +# define IRMP_SUPPORT_RECS80_PROTOCOL 0 +#endif + +#if IRMP_SUPPORT_RECS80EXT_PROTOCOL == 1 && F_INTERRUPTS < 15000 +# warning F_INTERRUPTS too low, RECS80EXT protocol disabled (should be at least 15000) +# undef IRMP_SUPPORT_RECS80EXT_PROTOCOL +# define IRMP_SUPPORT_RECS80EXT_PROTOCOL 0 +#endif + +#if IRMP_SUPPORT_LEGO_PROTOCOL == 1 && F_INTERRUPTS < 20000 +# warning F_INTERRUPTS too low, LEGO protocol disabled (should be at least 20000) +# undef IRMP_SUPPORT_LEGO_PROTOCOL +# define IRMP_SUPPORT_LEGO_PROTOCOL 0 +#endif + +#if IRMP_SUPPORT_JVC_PROTOCOL == 1 && IRMP_SUPPORT_NEC_PROTOCOL == 0 +# warning JVC protocol needs also NEC protocol, NEC protocol enabled +# undef IRMP_SUPPORT_NEC_PROTOCOL +# define IRMP_SUPPORT_NEC_PROTOCOL 1 +#endif + +#if IRMP_SUPPORT_NEC16_PROTOCOL == 1 && IRMP_SUPPORT_NEC_PROTOCOL == 0 +# warning NEC16 protocol needs also NEC protocol, NEC protocol enabled +# undef IRMP_SUPPORT_NEC_PROTOCOL +# define IRMP_SUPPORT_NEC_PROTOCOL 1 +#endif + +#if IRMP_SUPPORT_NEC42_PROTOCOL == 1 && IRMP_SUPPORT_NEC_PROTOCOL == 0 +# warning NEC42 protocol needs also NEC protocol, NEC protocol enabled +# undef IRMP_SUPPORT_NEC_PROTOCOL +# define IRMP_SUPPORT_NEC_PROTOCOL 1 +#endif + +#if F_INTERRUPTS > 20000 +#error F_INTERRUPTS too high (should be not greater than 20000) +#endif + +#endif /* _WC_IRMPCONFIG_H_ */ diff --git a/libspark/lirmp_input.cpp b/libspark/lirmp_input.cpp new file mode 100644 index 0000000..73dcc06 --- /dev/null +++ b/libspark/lirmp_input.cpp @@ -0,0 +1,445 @@ +/* + * Simulate a linux input device via uinput + * Get lirc remote events, decode with IRMP and inject them via uinput + * + * (C) 2012 Stefan Seyfried + * + * 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 2 + * 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 . + */ + +/* the C++ compiler did not like this code, so let's put it into a + * separate file and compile with gcc insead of g++... + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "lirmp_input.h" +extern "C" { +#include "irmp.h" +} +static uint8_t IRMP_PIN; + +#include +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_INIT, NULL, args) +#define lt_info(args...) _lt_info(TRIPLE_DEBUG_INIT, NULL, args) + +/* same defines as in neutrino's rcinput.h */ +#define KEY_TTTV KEY_FN_1 +#define KEY_TTZOOM KEY_FN_2 +#define KEY_REVEAL KEY_FN_D +/* only defined in newer kernels / headers... */ +#ifndef KEY_ZOOMIN +#define KEY_ZOOMIN KEY_FN_E +#endif +#ifndef KEY_ZOOMOUT +#define KEY_ZOOMOUT KEY_FN_F +#endif + +typedef struct { + uint16_t ir; /* IR command */ + int code; /* input key code */ +} key_map_t; + +static const key_map_t key_map[] = { + { 0x13, KEY_0 }, + { 0x1a, KEY_1 }, + { 0x1f, KEY_2 }, + { 0x58, KEY_3 }, + { 0x16, KEY_4 }, + { 0x1b, KEY_5 }, + { 0x54, KEY_6 }, + { 0x12, KEY_7 }, + { 0x17, KEY_8 }, + { 0x50, KEY_9 }, + { 0x5f, KEY_OK }, + { 0x59, KEY_TIME }, + { 0x43, KEY_FAVORITES }, + { 0x4f, KEY_SAT }, + { 0x0f, KEY_NEXT }, /* V.Format */ + { 0x1e, KEY_POWER }, + { 0x5a, KEY_MUTE }, + { 0x1c, KEY_MENU }, + { 0x5d, KEY_EPG }, + { 0x07, KEY_INFO }, + { 0x60, KEY_EXIT }, + { 0x48, KEY_PAGEUP }, + { 0x44, KEY_PAGEDOWN }, + { 0x02, KEY_LEFT }, + { 0x40, KEY_RIGHT }, + { 0x03, KEY_UP }, + { 0x5e, KEY_DOWN }, + { 0x0a, KEY_VOLUMEUP }, + { 0x06, KEY_VOLUMEDOWN }, + { 0x49, KEY_RED }, + { 0x4e, KEY_GREEN }, + { 0x11, KEY_YELLOW }, + { 0x4a, KEY_BLUE }, + { 0x4c, KEY_TV }, /* TV/Radio */ + { 0x5c, KEY_VIDEO }, /* FIND */ + { 0x19, KEY_AUDIO }, /* FOLDER */ +/* KEY_AUX, + KEY_TEXT, + KEY_TTTV, + KEY_TTZOOM, + KEY_REVEAL, +*/ + { 0x01, KEY_REWIND }, + { 0x53, KEY_FORWARD }, + { 0x22, KEY_STOP }, + { 0x4d, KEY_PAUSE }, + { 0x15, KEY_PLAY }, + { 0x20, KEY_PREVIOUS }, + { 0x23, KEY_NEXT }, +// KEY_EJECTCD, + { 0x10, KEY_RECORD } +}; + +static const int key_list[] = { + KEY_0, + KEY_1, + KEY_2, + KEY_3, + KEY_4, + KEY_5, + KEY_6, + KEY_7, + KEY_8, + KEY_9, + KEY_OK, + KEY_TIME, + KEY_FAVORITES, + KEY_SAT, + KEY_ZOOMOUT, + KEY_ZOOMIN, + KEY_NEXT, + KEY_POWER, + KEY_MUTE, + KEY_MENU, + KEY_EPG, + KEY_INFO, + KEY_EXIT, + KEY_PAGEUP, + KEY_PAGEDOWN, + KEY_LEFT, + KEY_RIGHT, + KEY_UP, + KEY_DOWN, + KEY_VOLUMEUP, + KEY_VOLUMEDOWN, + KEY_RED, + KEY_GREEN, + KEY_YELLOW, + KEY_BLUE, + KEY_TV, + KEY_VIDEO, + KEY_AUDIO, +// KEY_AUX, +// KEY_TEXT, +// KEY_TTTV, +// KEY_TTZOOM, +// KEY_REVEAL, + KEY_REWIND, + KEY_STOP, + KEY_PAUSE, + KEY_PLAY, + KEY_FORWARD, + KEY_PREVIOUS, + KEY_NEXT, +// KEY_EJECTCD, + KEY_RECORD, + -1 +}; + +static pthread_t thread; +static int thread_running; + +static void *input_thread(void *) +{ + int uinput; + struct input_event u; + struct uinput_user_dev ud; + FILE *f; + int lircfd; + int pulse; + int i = 0; + int last_pulse = 1; + int last_code = -1; + uint32_t lircdata; /* lirc_t to be correct... */ + unsigned int count = 0; /* how many timeouts? */ + unsigned int nodec = 0; /* how many timeouts since last decoded? */ + int aotom_fd = -1; + IRMP_DATA d; + + lt_info("LIRC/IRMP input converter thread starting...\n"); + + /* modprobe does not complain if the module is already loaded... */ + system("/sbin/modprobe uinput"); + do { + usleep(100000); /* mdev needs some time to create the device? */ + uinput = open("/dev/uinput", O_WRONLY|O_NDELAY); + } while (uinput < 0 && ++count < 100); + + if (uinput < 0) + { + lt_info("LIRC/IRMP input thread: unable to open /dev/uinput (%m)\n"); + thread_running = 2; + return NULL; + } + + fcntl(uinput, F_SETFD, FD_CLOEXEC); + ioctl(uinput, UI_SET_EVBIT, EV_KEY); + /* do not use kernel repeat EV_REP since neutrino will be confused by the + * generated SYN_REPORT events... + ioctl(uinput, UI_SET_EVBIT, EV_REP); + */ + /* register keys */ + for (i = 0; key_list[i] != -1; i++) + ioctl(uinput, UI_SET_KEYBIT, key_list[i]); + + /* configure the device */ + memset(&ud, 0, sizeof(ud)); + strncpy(ud.name, "Neutrino LIRC/IRMP to Input Device converter", UINPUT_MAX_NAME_SIZE); + ud.id.version = 0x42; + ud.id.vendor = 0x1234; + ud.id.product = 0x5678; + ud.id.bustype = BUS_I2C; /* ?? */ + write(uinput, &ud, sizeof(ud)); + + if (ioctl(uinput, UI_DEV_CREATE)) + { + lt_info("LIRC/IRMP input thread UI_DEV_CREATE: %m\n"); + close(uinput); + return NULL; + } + + /* this is ugly: parse the new input device from /proc/...devices + * and symlink it to /dev/input/nevis_ir... */ +#define DEVLINE "I: Bus=0018 Vendor=1234 Product=5678 Version=0042" + f = fopen("/proc/bus/input/devices", "r"); + if (f) + { + int found = 0; + int evdev = -1; + size_t n = 0; + char *line = NULL; + char *p; + char newdev[20]; + while (getline(&line, &n, f) != -1) + { + switch(line[0]) + { + case 'I': + if (strncmp(line, DEVLINE, strlen(DEVLINE)) == 0) + found = 1; + break; + case 'H': + if (! found) + break; + p = strstr(line, " event"); + if (! p) + { + evdev = -1; + break; + } + evdev = atoi(p + 6); + sprintf(newdev, "event%d", evdev); + lt_info("LIRC/IRMP input thread: symlink /dev/input/nevis_ir to %s\n", newdev); + unlink("/dev/input/nevis_ir"); + symlink(newdev, "/dev/input/nevis_ir"); + break; + default: + break; + } + if (evdev != -1) + break; + } + fclose(f); + free(line); + } + + u.type = EV_KEY; + u.value = 0; /* initialize: first event wil be a key press */ + + lircfd = open("/dev/lirc", O_RDONLY); + if (lircfd < 0) + { + lt_info("%s: open /dev/lirc: %m\n", __func__); + goto out; + } + IRMP_PIN = 0xFF; + +/* 50 ms. This should be longer than the longest light pulse */ +#define POLL_MS (100 * 1000) +#define LIRC_PULSE 0x01000000 +#define LIRC_PULSE_MASK 0x00FFFFFF + lt_info("LIRC/IRMP input converter going into main loop...\n"); + + aotom_fd = open("/dev/vfd", O_RDONLY); + + /* TODO: ioctl to find out if we have a compatible LIRC_MODE2 device */ + thread_running = 1; + while (thread_running) + { + fd_set fds; + struct timeval tv; + int ret; + + FD_ZERO(&fds); + FD_SET(lircfd, &fds); + tv.tv_sec = 0; + tv.tv_usec = POLL_MS; + /* any singal can interrupt select. we rely on the linux-only feature + * that the timeout is automatcally recalculated in this case! */ + do { + ret = select(lircfd + 1, &fds, NULL, NULL, &tv); + } while (ret == -1 && errno == EINTR); + + if (ret == -1) { + /* errno != EINTR... */ + lt_info("%s: lirmp: lircfd select: %m\n", __func__); + break; + } + + if (ret == 0) + { + count++; + nodec++; + lircdata = POLL_MS; /* timeout */ + pulse = !last_pulse; /* lirc sends data on signal change */ + if (last_code != -1 && nodec > 1) + { + // fprintf(stderr, "timeout!\n"); + u.code = last_code; + u.value = 0; /* release */ + write(uinput, &u, sizeof(u)); + last_code = -1; + } + } + else + { + if (read(lircfd, &lircdata, sizeof(lircdata)) != sizeof(lircdata)) + { + perror("read"); + break; + } + pulse = (lircdata & LIRC_PULSE); /* we got light... */ + last_pulse = pulse; + lircdata &= LIRC_PULSE_MASK; /* how long the pulse was in microseconds */ + } + + if (ret && count) + { + if (count * POLL_MS > lircdata) + lircdata = 0; + else + lircdata -= count * POLL_MS; + count = 0; + } + //printf("lircdata: ret:%d c:%d %d\n", ret, ch - '0', lircdata); + lircdata /= (1000000 / F_INTERRUPTS); + + if (pulse) + IRMP_PIN = 0x00; + else + IRMP_PIN = 0xff; + + do { + (void) irmp_ISR (IRMP_PIN); + if (irmp_get_data (&d)) + { + nodec = 0; + lt_debug("irmp_get_data proto: %2d addr: 0x%04x cmd: 0x%04x fl: %d\n", + d.protocol, d.address, d.command, d.flags); + + /* todo: do we need to complete the loop if we already + * detected the singal in this pulse? */ + if (d.protocol == IRMP_NEC_PROTOCOL && d.address == 0xba45) + { + for (i = 0; i < (int)(sizeof(key_map)/sizeof(key_map_t)); i++) + { + if (key_map[i].ir == d.command) + { + if (last_code != -1 && last_code != key_map[i].code) + { + u.code = last_code; + u.value = 0; + write(uinput, &u, sizeof(u)); + } + u.code = key_map[i].code; + u.value = (d.flags & 0x1) + 1; + //lt_debug("uinput write: value: %d code: %d\n", u.value, u.code); + last_code = u.code; + write(uinput, &u, sizeof(u)); + if (aotom_fd > -1) { + struct aotom_ioctl_data vfd_data; + vfd_data.u.led.led_nr = 1; + vfd_data.u.led.on = 10; + ioctl(aotom_fd, VFDSETLED, &vfd_data); + } + break; + } + } + } + } + } while (lircdata-- > 0); + } + /* clean up */ + close (lircfd); + + if (aotom_fd > -1) + close(aotom_fd); + + out: + ioctl(uinput, UI_DEV_DESTROY); + return NULL; +} + +void start_input_thread(void) +{ + if (pthread_create(&thread, 0, input_thread, NULL) != 0) + { + lt_info("%s: LIRC/IRMP input thread pthread_create: %m\n", __func__); + thread_running = 0; + return; + } + /* wait until the device is created before continuing */ + while (! thread_running) + usleep(1000); + if (thread_running == 2) /* failed... :-( */ + thread_running = 0; +} + +void stop_input_thread(void) +{ + if (! thread_running) + return; + thread_running = 0; + pthread_join(thread, NULL); +} diff --git a/libspark/lirmp_input.h b/libspark/lirmp_input.h new file mode 100644 index 0000000..c277dda --- /dev/null +++ b/libspark/lirmp_input.h @@ -0,0 +1,7 @@ +/* functions from lirmp_input.cpp */ + +#ifndef __LIRMP_INPUT_H_ +#define __LIRMP_INPUT_H_ +void start_input_thread(void); +void stop_input_thread(void); +#endif diff --git a/libspark/mmi.h b/libspark/mmi.h new file mode 100644 index 0000000..76ff992 --- /dev/null +++ b/libspark/mmi.h @@ -0,0 +1,23 @@ +#ifndef __MMI_H_ +#define __MMI_H_ + +#define MAX_MMI_ITEMS 40 +#define MAX_MMI_TEXT_LEN 255 +#define MAX_MMI_CHOICE_TEXT_LEN 255 + +typedef struct { + int choice_nb; + char title[MAX_MMI_TEXT_LEN]; + char subtitle[MAX_MMI_TEXT_LEN]; + char bottom[MAX_MMI_TEXT_LEN]; + char choice_item[MAX_MMI_ITEMS][MAX_MMI_CHOICE_TEXT_LEN]; +} MMI_MENU_LIST_INFO; + +typedef struct { + int blind; + int answerlen; + char enguiryText[MAX_MMI_TEXT_LEN]; +} MMI_ENGUIRY_INFO; + +#endif // __MMI_H_ + diff --git a/libspark/playback.cpp b/libspark/playback.cpp new file mode 100644 index 0000000..adb2e67 --- /dev/null +++ b/libspark/playback.cpp @@ -0,0 +1,1466 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include +#include "playback_lib.h" +#include "dmx_lib.h" +#include "audio_lib.h" +#include "video_lib.h" +#include "lt_debug.h" +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_PLAYBACK, this, args) +#define lt_info(args...) _lt_info(TRIPLE_DEBUG_PLAYBACK, this, args) +#define lt_info_c(args...) _lt_info(TRIPLE_DEBUG_PLAYBACK, NULL, args) + +#define DVR "/dev/dvb/adapter0/pvr0" + +static int mp_syncPES(uint8_t *, int, bool quiet = false); +static int sync_ts(uint8_t *, int); +static inline uint16_t get_pid(uint8_t *buf); +static void *start_playthread(void *c); +static void playthread_cleanup_handler(void *); + +static pthread_cond_t playback_ready_cond = PTHREAD_COND_INITIALIZER; +static pthread_mutex_t playback_ready_mutex = PTHREAD_MUTEX_INITIALIZER; + +static pthread_mutex_t currpos_mutex = PTHREAD_MUTEX_INITIALIZER; + +static int dvrfd = -1; +static int streamtype; + +extern cDemux *videoDemux; +extern cDemux *audioDemux; +extern cVideo *videoDecoder; +extern cAudio *audioDecoder; + +static const char *FILETYPE[] = { + "FILETYPE_UNKNOWN", + "FILETYPE_TS", + "FILETYPE_MPG", + "FILETYPE_VDR" +}; + +cPlayback::cPlayback(int) +{ + lt_debug("%s\n", __FUNCTION__); + thread_started = false; + inbuf = NULL; + pesbuf = NULL; + filelist.clear(); + curr_fileno = -1; + in_fd = -1; + streamtype = 0; +} + +cPlayback::~cPlayback() +{ + lt_debug("%s\n", __FUNCTION__); + Close(); +} + + +bool cPlayback::Open(playmode_t mode) +{ + static const char *PMODE[] = { + "PLAYMODE_TS", + "PLAYMODE_FILE" + }; + + lt_debug("%s: PlayMode = %s\n", __FUNCTION__, PMODE[mode]); + thread_started = false; + playMode = mode; + filetype = FILETYPE_TS; + playback_speed = 0; + last_size = 0; + _pts_end = 0; + astreams.clear(); + memset(&cc, 0, 256); + return true; +} + +//Used by Fileplay +void cPlayback::Close(void) +{ + lt_info("%s\n", __FUNCTION__); + playstate = STATE_STOP; + if (thread_started) + { + lt_info("%s: before pthread_join\n", __FUNCTION__); + pthread_join(thread, NULL); + } + thread_started = false; + lt_info("%s: after pthread_join\n", __FUNCTION__); + mf_close(); + filelist.clear(); + + if (inbuf) + free(inbuf); + inbuf = NULL; + if (pesbuf) + free(pesbuf); + pesbuf = NULL; + //Stop(); +} + +bool cPlayback::Start(char *filename, unsigned short vp, int vtype, unsigned short ap, int _ac3, unsigned int) +{ + struct stat s; + off_t r; + vpid = vp; + apid = ap; + ac3 = _ac3; + lt_info("%s name = '%s' vpid 0x%04hx vtype %d apid 0x%04hx ac3 %d filelist.size: %u\n", + __FUNCTION__, filename, vpid, vtype, apid, ac3, filelist.size()); + if (!filelist.empty()) + { + lt_info("filelist not empty?\n"); + return false; + } + if (stat(filename, &s)) + { + lt_info("filename does not exist? (%m)\n"); + return false; + } + if (!inbuf) + inbuf = (uint8_t *)malloc(INBUF_SIZE); /* 256 k */ + if (!inbuf) + { + lt_info("allocating input buffer failed (%m)\n"); + return false; + } + if (!pesbuf) + pesbuf = (uint8_t *)malloc(PESBUF_SIZE); /* 128 k */ + if (!pesbuf) + { + lt_info("allocating PES buffer failed (%m)\n"); + return false; + } + filelist_t file; + file.Name = std::string(filename); + file.Size = s.st_size; + if (file.Name.rfind(".ts") == file.Name.length() - 3 || + file.Name.rfind(".TS") == file.Name.length() - 3) + filetype = FILETYPE_TS; + else + { + if (file.Name.rfind(".vdr") == file.Name.length() - 4) + { + filetype = FILETYPE_VDR; + std::string::size_type p = file.Name.rfind("info.vdr"); + if (p == std::string::npos) + p = file.Name.rfind("index.vdr"); + if (p != std::string::npos) + { + file.Name.replace(p, std::string::npos, "001.vdr"); + lt_info("replaced filename with '%s'\n", file.Name.c_str()); + if (stat(file.Name.c_str(), &s)) + { + lt_info("filename does not exist? (%m)\n"); + return false; + } + file.Size = s.st_size; + } + } + else + filetype = FILETYPE_MPG; + vpid = 0x40; + } + + lt_info("detected (ok, guessed) filetype: %s\n", FILETYPE[filetype]); + + filelist.push_back(file); + filelist_auto_add(); + if (mf_open(0) < 0) + return false; + + pts_start = pts_end = pts_curr = -1; + pesbuf_pos = 0; + curr_pos = 0; + inbuf_pos = 0; + inbuf_sync = 0; + r = mf_getsize(); + + if (r > INBUF_SIZE) + { + if (mp_seekSync(r - INBUF_SIZE) < 0) + return false; + while(true) { + if (inbuf_read() <= 0) + break; // EOF + if (curr_pos >= r) //just to make sure... + break; + } + if (filetype == FILETYPE_TS) + for (r = (inbuf_pos / 188) * 188; r > 0; r -= 188) + { + pts_end = get_pts(inbuf + r, false, inbuf_pos - r); + if (pts_end > -1) + break; + } + else + pts_end = pts_curr; + } + else + pts_end = -1; /* unknown */ + + if (mp_seekSync(0) < 0) + return false; + + pesbuf_pos = 0; + inbuf_pos = 0; + inbuf_sync = 0; + while (inbuf_pos < INBUF_SIZE / 2 && inbuf_read() > 0) {}; + for (r = 0; r < inbuf_pos - 188; r += 188) + { + pts_start = get_pts(inbuf + r, false, inbuf_pos - r); + if (pts_start > -1) + break; + } + pts_curr = pts_start; + bytes_per_second = -1; + if (pts_end != -1 && pts_start > pts_end) /* PTS overflow during this file */ + pts_end += 0x200000000ULL; + int duration = (pts_end - pts_start) / 90000; + if (duration > 0) + bytes_per_second = mf_getsize() / duration; + lt_info("start: %lld end %lld duration %d bps %lld\n", pts_start, pts_end, duration, bytes_per_second); + /* yes, we start in pause mode... */ + playback_speed = 0; + if (pts_start == -1) + playstate = STATE_INIT; + else + playstate = STATE_PAUSE; + pthread_mutex_lock(&playback_ready_mutex); + if (pthread_create(&thread, 0, start_playthread, this) != 0) + lt_info("pthread_create failed\n"); + else + pthread_cond_wait(&playback_ready_cond, &playback_ready_mutex); + pthread_mutex_unlock(&playback_ready_mutex); + return true; +} + +static void *start_playthread(void *c) +{ + cPlayback *obj = (cPlayback *)c; + obj->playthread(); + return NULL; +} + +void cPlayback::playthread(void) +{ +#if 0 + thread_started = true; + int ret, towrite; + dvrfd = open(DVR, O_WRONLY); + if (dvrfd < 0) + { + lt_info("%s open tdpvr failed: %m\n", __FUNCTION__); + pthread_exit(NULL); + } + fcntl(dvrfd, F_SETFD, FD_CLOEXEC); + + pthread_cleanup_push(playthread_cleanup_handler, 0); + + ioctl(audioDemux->getFD(), DEMUX_SELECT_SOURCE, INPUT_FROM_PVR); + if (ac3) + audioDecoder->SetStreamType(AUDIO_FMT_DOLBY_DIGITAL); + else + { + if (streamtype == 1) /* mpeg 1 */ + audioDecoder->SetStreamType(AUDIO_FMT_MPG1); + else /* default */ + audioDecoder->SetStreamType(AUDIO_FMT_MPEG); + } + + audioDemux->pesFilter(apid); + videoDemux->pesFilter(vpid); + +// audioDemux->Start(); + videoDemux->Start(); + +// videoDecoder->setBlank(1); +// videoDecoder->Start(); +// audioDecoder->Start(); + /* everything is set up now, signal ::Start() that it can return */ + pthread_mutex_lock(&playback_ready_mutex); + pthread_cond_broadcast(&playback_ready_cond); + pthread_mutex_unlock(&playback_ready_mutex); + + while (playstate != STATE_STOP) + { + if (playstate == STATE_INIT) + { + /* hack for timeshift to determine start PTS */ + if (inbuf_read() < 0) + break; + usleep(100000); + if (pts_start == -1) + continue; + } + + if (playback_speed == 0) + { + playstate = STATE_PAUSE; + usleep(1); + continue; + } + if (inbuf_read() < 0) + break; + + /* autoselect PID for PLAYMODE_FILE */ + if (apid == 0 && astreams.size() > 0) + { + for (std::map::iterator aI = astreams.begin(); aI != astreams.end(); aI++) + { + if (!aI->second.ac3) + { + apid = aI->first; + lt_info("%s setting Audio pid to 0x%04hx\n", __FUNCTION__, apid); + SetAPid(apid, 0); + break; + } + } + } + + towrite = inbuf_pos / 188 * 188; /* TODO: smaller chunks? */ + if (towrite == 0) + continue; + retry: + ret = write(dvrfd, inbuf, towrite); + if (ret < 0) + { + if (errno == EAGAIN && playstate != STATE_STOP) + goto retry; + lt_info("%s write dvr failed: %m\n", __FUNCTION__); + break; + } + memmove(inbuf, inbuf + ret, inbuf_pos - ret); + inbuf_pos -= ret; + } + + pthread_cleanup_pop(1); + pthread_exit(NULL); +#endif +} + +static void playthread_cleanup_handler(void *) +{ + lt_info_c("%s\n", __FUNCTION__); +// ioctl(audioDemux->getFD(), DEMUX_SELECT_SOURCE, INPUT_FROM_CHANNEL0); + audioDemux->Stop(); + videoDemux->Stop(); + audioDecoder->Stop(); + videoDecoder->Stop(); + close(dvrfd); + dvrfd = -1; +} + +bool cPlayback::SetAPid(unsigned short pid, int _ac3) +{ + lt_info("%s pid: 0x%04hx ac3: %d\n", __FUNCTION__, pid, _ac3); + apid = pid; + ac3 = _ac3; + + audioDemux->Stop(); + audioDecoder->Stop(); + videoDemux->Stop(); + videoDecoder->Stop(false); + + if (ac3) + audioDecoder->SetStreamType(AUDIO_FMT_DOLBY_DIGITAL); + else + { + if (streamtype == 1) /* mpeg 1 */ + audioDecoder->SetStreamType(AUDIO_FMT_MPG1); + else /* default */ + audioDecoder->SetStreamType(AUDIO_FMT_MPEG); + } + audioDemux->pesFilter(apid); + + videoDemux->Start(); + audioDemux->Start(); + audioDecoder->Start(); + videoDecoder->Start(); + return true; +} + +bool cPlayback::SetSpeed(int speed) +{ + lt_info("%s speed = %d\n", __FUNCTION__, speed); + if (speed < 0) + speed = 1; /* fast rewind not yet implemented... */ + if (speed == 1 && playback_speed != 1) + { + if (playback_speed == 0) + { + videoDemux->Stop(); + videoDemux->Start(); + audioDemux->Start(); + } + else + { + audioDecoder->Stop(); + videoDecoder->Stop(); + } + audioDecoder->Start(); + videoDecoder->Start(); + playstate = STATE_PLAY; + } + if (playback_speed == 1 && speed > 1) + { + audioDecoder->mute(false); + videoDecoder->FastForwardMode(); + } + playback_speed = speed; + if (playback_speed == 0) + { + audioDecoder->Stop(); + audioDemux->Stop(); + videoDecoder->Stop(false); + } + return true; +} + +bool cPlayback::GetSpeed(int &speed) const +{ + lt_debug("%s\n", __FUNCTION__); + speed = playback_speed; + return true; +} + +// in milliseconds +bool cPlayback::GetPosition(int &position, int &duration) +{ + int64_t tmppts; + lt_debug("%s\n", __FUNCTION__); + off_t currsize = mf_getsize(); + bool update = false; + /* handle a growing file, e.g. for timeshift. + this might be pretty expensive... */ + if (filetype == FILETYPE_TS && filelist.size() == 1) + { + off_t tmppos = currsize - PESBUF_SIZE; + if (currsize > last_size && (currsize - last_size) < 10485760 && + bytes_per_second > 0 && _pts_end > 0) + { + /* guess the current endpts... */ + tmppts = (currsize - last_size) * 90000 / bytes_per_second; + pts_end = _pts_end + tmppts; + } + else if (currsize != last_size && tmppos > 0) + { + pthread_mutex_lock(&currpos_mutex); + off_t oldpos = curr_pos; + ssize_t n, r; + int s; + mf_lseek(tmppos); + n = read(in_fd, pesbuf, PESBUF_SIZE); /* abuse the pesbuf... */ + s = sync_ts(pesbuf, n); + if (s >= 0) + { + n -= s; + for (r = (n / 188) * 188; r > 0; r -= 188) + { + tmppts = get_pts(pesbuf + r + s, false, n - r); + if (tmppts > -1) + { + lt_debug("n: %d s: %d endpts %lld size: %lld\n", n, s, tmppts, currsize); + pts_end = tmppts; + _pts_end = tmppts; + update = true; + /* file size has changed => update endpts */ + last_size = currsize; + break; + } + } + } + mf_lseek(oldpos); + pthread_mutex_unlock(&currpos_mutex); + } + } + if (pts_end != -1 && pts_start > pts_end) /* should trigger only once ;) */ + { + pts_end += 0x200000000ULL; + update = true; + } + + if (pts_curr != -1 && pts_curr < pts_start) + tmppts = pts_curr + 0x200000000ULL - pts_start; + else + tmppts = pts_curr - pts_start; + if (pts_end != -1 && pts_curr != -1) + { + position = tmppts / 90; + duration = (pts_end - pts_start) / 90; + if (update && duration >= 4000) + { + bytes_per_second = currsize / (duration / 1000); + lt_debug("%s: updated bps: %lld size: %lld duration %d\n", + __FUNCTION__, bytes_per_second, currsize, duration); + } + return true; + } + position = 0; + duration = 0; + return false; +} + +bool cPlayback::SetPosition(int position, bool absolute) +{ + lt_info("%s pos = %d abs = %d\n", __FUNCTION__, position, absolute); + int currpos, target, duration, oldspeed; + bool ret; + + if (absolute) + target = position; + else + { + GetPosition(currpos, duration); + target = currpos + position; + lt_info("current position %d target %d\n", currpos, target); + } + + oldspeed = playback_speed; +// if (oldspeed != 0) + SetSpeed(0); /* request pause */ + + while (playstate == STATE_PLAY) /* playthread did not acknowledge pause */ + usleep(1); + if (playstate == STATE_STOP) /* we did get stopped by someone else */ + return false; + + ret = (seek_to_pts(target * 90) > 0); + + if (oldspeed != 0) + { + SetSpeed(oldspeed); + /* avoid ugly artifacts */ + videoDecoder->Stop(); + videoDecoder->Start(); + } + return ret; +} + +void cPlayback::FindAllPids(uint16_t *apids, unsigned short *ac3flags, uint16_t *numpida, std::string *language) +{ + lt_info("%s\n", __FUNCTION__); + int i = 0; + for (std::map::iterator aI = astreams.begin(); aI != astreams.end(); aI++) + { + apids[i] = aI->first; + ac3flags[i] = aI->second.ac3 ? 1 : 0; + language[i] = aI->second.lang; + i++; + if (i > 10) /* REC_MAX_APIDS in vcrcontrol.h */ + break; + } + *numpida = i; +} + +off_t cPlayback::seek_to_pts(int64_t pts) +{ + off_t newpos = curr_pos; + int64_t tmppts, ptsdiff; + int count = 0; + if (pts_start < 0 || pts_end < 0 || bytes_per_second < 0) + { + lt_info("%s pts_start (%lld) or pts_end (%lld) or bytes_per_second (%lld) not initialized\n", + __FUNCTION__, pts_start, pts_end, bytes_per_second); + return -1; + } + /* sanity check: buffer is without locking, so we must only seek while in pause mode */ + if (playstate != STATE_PAUSE) + { + lt_info("%s playstate (%d) != STATE_PAUSE, not seeking\n", __FUNCTION__, playstate); + return -1; + } + + /* tmppts is normalized current pts */ + if (pts_curr < pts_start) + tmppts = pts_curr + 0x200000000ULL - pts_start; + else + tmppts = pts_curr - pts_start; + while (abs(pts - tmppts) > 90000LL && count < 10) + { + count++; + ptsdiff = pts - tmppts; + newpos += ptsdiff * bytes_per_second / 90000; + lt_info("%s try #%d seek from %lldms to %lldms dt %lldms pos %lldk newpos %lldk kB/s %lld\n", + __FUNCTION__, count, tmppts / 90, pts / 90, ptsdiff / 90, curr_pos / 1024, newpos / 1024, bytes_per_second / 1024); + if (newpos < 0) + newpos = 0; + newpos = mp_seekSync(newpos); + if (newpos < 0) + return newpos; + inbuf_pos = 0; + inbuf_sync = 0; + while (inbuf_pos < INBUF_SIZE * 8 / 10) { + if (inbuf_read() <= 0) + break; // EOF + } + if (pts_curr < pts_start) + tmppts = pts_curr + 0x200000000ULL - pts_start; + else + tmppts = pts_curr - pts_start; + } + lt_info("%s end after %d tries, ptsdiff now %lld sec\n", __FUNCTION__, count, (pts - tmppts) / 90000); + return newpos; +} + +bool cPlayback::filelist_auto_add() +{ + if (filelist.size() != 1) + return false; + + const char *filename = filelist[0].Name.c_str(); + const char *ext; + ext = strrchr(filename, '.'); // FOO-xxx-2007-12-31.001.ts <- the dot before "ts" + // 001.vdr <- the dot before "vdr" + // check if there is something to do... + if (! ext) + return false; + if (!((ext - 7 >= filename && !strcmp(ext, ".ts") && *(ext - 4) == '.') || + (ext - 4 >= filename && !strcmp(ext, ".vdr")))) + return false; + + int num = 0; + struct stat s; + size_t numpos = strlen(filename) - strlen(ext) - 3; + sscanf(filename + numpos, "%d", &num); + do { + num++; + char nextfile[strlen(filename) + 1]; /* todo: use fixed buffer? */ + memcpy(nextfile, filename, numpos); + sprintf(nextfile + numpos, "%03d%s", num, ext); + if (stat(nextfile, &s)) + break; // file does not exist + filelist_t file; + file.Name = std::string(nextfile); + file.Size = s.st_size; + lt_info("%s auto-adding '%s' to playlist\n", __FUNCTION__, nextfile); + filelist.push_back(file); + } while (true && num < 999); + + return (filelist.size() > 1); +} + +/* the mf_* functions are wrappers for multiple-file I/O */ +int cPlayback::mf_open(int fileno) +{ + if (filelist.empty()) + return -1; + + if (fileno >= (int)filelist.size()) + return -1; + + mf_close(); + + in_fd = open(filelist[fileno].Name.c_str(), O_RDONLY); + fcntl(in_fd, F_SETFD, FD_CLOEXEC); + if (in_fd != -1) + curr_fileno = fileno; + + return in_fd; +} + +int cPlayback::mf_close(void) +{ + int ret = 0; + lt_info("%s in_fd = %d curr_fileno = %d\n", __FUNCTION__, in_fd, curr_fileno); + if (in_fd != -1) + ret = close(in_fd); + in_fd = curr_fileno = -1; + + return ret; +} + +off_t cPlayback::mf_getsize(void) +{ + off_t ret = 0; + if (filelist.size() == 1 && in_fd != -1) + { + /* for timeshift, we need to deal with a growing file... */ + struct stat st; + if (fstat(in_fd, &st) == 0) + return st.st_size; + /* else, fallback to filelist.size() */ + } + for (unsigned int i = 0; i < filelist.size(); i++) + ret += filelist[i].Size; + return ret; +} + +off_t cPlayback::mf_lseek(off_t pos) +{ + off_t offset = 0, lpos = pos, ret; + unsigned int fileno; + /* this is basically needed for timeshifting - to allow + growing files to be handled... */ + if (filelist.size() == 1 && filetype == FILETYPE_TS) + { + if (lpos > mf_getsize()) + return -2; + fileno = 0; + } + else + { + for (fileno = 0; fileno < filelist.size(); fileno++) + { + if (lpos < filelist[fileno].Size) + break; + offset += filelist[fileno].Size; + lpos -= filelist[fileno].Size; + } + if (fileno == filelist.size()) + return -2; // EOF + } + + if ((int)fileno != curr_fileno) + { + lt_info("%s old fileno: %d new fileno: %d, offset: %lld\n", __FUNCTION__, curr_fileno, fileno, (long long)lpos); + in_fd = mf_open(fileno); + if (in_fd < 0) + { + lt_info("cannot open file %d:%s (%m)\n", fileno, filelist[fileno].Name.c_str()); + return -1; + } + } + + ret = lseek(in_fd, lpos, SEEK_SET); + if (ret < 0) + return ret; + + curr_pos = offset + ret; + return curr_pos; +} + +/* gets the PTS at a specific file position from a PES + ATTENTION! resets buf! */ +int64_t cPlayback::get_PES_PTS(uint8_t *buf, int len, bool last) +{ + int64_t pts = -1; + int off, plen; + uint8_t *p; + + off = mp_syncPES(buf, len); + + if (off < 0) + return off; + + p = buf + off; + while (off < len - 14 && (pts == -1 || last)) + { + plen = ((p[4] << 8) | p[5]) + 6; + + switch(p[3]) + { + int64_t tmppts; + case 0xe0 ... 0xef: // video! + tmppts = get_pts(p, true, len - off); + if (tmppts >= 0) + pts = tmppts; + break; + case 0xbb: + case 0xbe: + case 0xbf: + case 0xf0 ... 0xf3: + case 0xff: + case 0xc0 ... 0xcf: + case 0xd0 ... 0xdf: + break; + case 0xb9: + case 0xba: + case 0xbc: + default: + plen = 1; + break; + } + p += plen; + off += plen; + } + return pts; +} + +ssize_t cPlayback::inbuf_read() +{ + if (filetype == FILETYPE_UNKNOWN) + return -1; + if (filetype == FILETYPE_TS) + return read_ts(); + /* FILETYPE_MPG or FILETYPE_VDR */ + return read_mpeg(); +} + +ssize_t cPlayback::read_ts() +{ + ssize_t toread, ret = 0, sync, off; + toread = INBUF_SIZE - inbuf_pos; + bool retry = true; + uint8_t *buf; + /* fprintf(stderr, "%s:%d curr_pos %lld, inbuf_pos: %ld, toread: %ld\n", + __FUNCTION__, __LINE__, (long long)curr_pos, (long)inbuf_pos, (long)toread); */ + + if (playback_speed > 1) + { + sync = 0; + ssize_t tmpread = PESBUF_SIZE / 188 * 188; + int n, skipped = 0; + bool skip = false; + bool eof = true; + pthread_mutex_lock(&currpos_mutex); + while (toread > 0) + { + ssize_t done = 0; + while (done < tmpread) + { + ret = read(in_fd, pesbuf, tmpread - done); + if (ret == 0 && retry) /* EOF */ + { + mf_lseek(curr_pos); + retry = false; + continue; + } + if (ret < 0) + { + lt_info("%s failed1: %m\n", __FUNCTION__); + pthread_mutex_unlock(&currpos_mutex); + return ret; + } + if (ret == 0 && eof) + goto out; + eof = false; + done += ret; + curr_pos += ret; + } + sync = sync_ts(pesbuf, ret); + if (sync != 0) + { + lt_info("%s out of sync: %d\n", __FUNCTION__, sync); + if (sync < 0) + { + pthread_mutex_unlock(&currpos_mutex); + return -1; + } + memmove(pesbuf, pesbuf + sync, ret - sync); + if (pesbuf[0] != 0x47) + lt_info("%s:%d??????????????????????????????\n", __FUNCTION__, __LINE__); + } + for (n = 0; n < done / 188 * 188; n += 188) + { + buf = pesbuf + n; + if (buf[1] & 0x40) // PUSI + { + /* only video packets... */ + int of = 4; + if (buf[3] & 0x20) // adaptation field + of += buf[4] + 1; + if ((buf[of + 3] & 0xF0) == 0xE0 && // Video stream + buf[of + 2] == 0x01 && buf[of + 1] == 0x00 && buf[of] == 0x00) // PES + { + skip = true; + skipped++; + if (skipped >= playback_speed) + { + skipped = 0; + skip = false; + } + } + } + if (! skip) + { + memcpy(inbuf + inbuf_pos, buf, 188); + inbuf_pos += 188; + toread -= 188; + if (toread <= 0) + { + /* the output buffer is full, discard the input :-( */ + if (done - n > 0) + { + lt_debug("%s not done: %d, resetting filepos\n", + __FUNCTION__, done - n); + mf_lseek(curr_pos - (done - n)); + } + break; + } + } + } + } + out: + pthread_mutex_unlock(&currpos_mutex); + if (eof) + return 0; + } + else + { + pthread_mutex_lock(&currpos_mutex); + while(true) + { + ret = read(in_fd, inbuf + inbuf_pos, toread); + if (ret == 0 && retry) /* EOF */ + { + mf_lseek(curr_pos); + retry = false; + continue; + } + break; + } + if (ret <= 0) + { + pthread_mutex_unlock(&currpos_mutex); + if (ret < 0) + lt_info("%s failed2: %m\n", __FUNCTION__); + return ret; + } + inbuf_pos += ret; + curr_pos += ret; + pthread_mutex_unlock(&currpos_mutex); + + sync = sync_ts(inbuf + inbuf_sync, INBUF_SIZE - inbuf_sync); + if (sync < 0) + { + lt_info("%s cannot sync\n", __FUNCTION__); + return ret; + } + inbuf_sync += sync; + } + /* check for A/V PIDs */ + uint16_t pid; + int i; + int64_t pts; + //fprintf(stderr, "inbuf_pos: %ld - sync: %ld, inbuf_syc: %ld\n", (long)inbuf_pos, (long)sync, (long)inbuf_sync); + int synccnt = 0; + for (i = 0; i < inbuf_pos - inbuf_sync - 13;) { + buf = inbuf + inbuf_sync + i; + if (*buf != 0x47) + { + synccnt++; + i++; + continue; + } + if (synccnt) + lt_info("%s TS went out of sync %d\n", __FUNCTION__, synccnt); + synccnt = 0; + if (!(buf[1] & 0x40)) /* PUSI */ + { + i += 188; + continue; + } + off = 0; + if (buf[3] & 0x20) /* adaptation field? */ + off = buf[4] + 1; + pid = get_pid(buf + 1); + /* PES signature is at buf + 4, streamtype is after 00 00 01 */ + switch (buf[4 + 3 + off]) + { + case 0xe0 ... 0xef: /* video stream */ + if (vpid == 0) + vpid = pid; + pts = get_pts(buf + 4 + off, true, inbuf_pos - inbuf_sync - i - off - 4); + if (pts < 0) + break; + pts_curr = pts; + if (pts_start < 0) + { + lt_info("%s updating pts_start to %lld ", __FUNCTION__, pts); + pts_start = pts; + if (pts_end > -1) + { + if (pts_end < pts_start) + { + pts_end += 0x200000000ULL; + fprintf(stderr, "pts_end to %lld ", pts_end); + } + int duration = (pts_end - pts_start) / 90000; + if (duration > 0) + { + bytes_per_second = (mf_getsize() - curr_pos) / duration; + fprintf(stderr, "bytes_per_second to %lldk duration to %ds at %lldk", + bytes_per_second / 1024, duration, curr_pos / 1024); + } + } + fprintf(stderr, "\n"); + } + break; + case 0xbd: /* private stream 1 - ac3 */ + case 0xc0 ... 0xdf: /* audio stream */ + if (astreams.find(pid) != astreams.end()) + break; + AStream tmp; + if (buf[7 + off] == 0xbd) + { + if (buf[12 + off] == 0x24) /* 0x24 == TTX */ + break; + tmp.ac3 = true; + } + else + tmp.ac3 = false; + tmp.lang = ""; + astreams.insert(std::make_pair(pid, tmp)); + lt_info("%s found apid #%d 0x%04hx ac3:%d\n", __func__, astreams.size(), pid, tmp.ac3); + break; + } + i += 188; + } + + // fprintf(stderr, "%s:%d ret %ld\n", __FUNCTION__, __LINE__, (long long)ret); + return ret; +} + +ssize_t cPlayback::read_mpeg() +{ + ssize_t toread, ret, sync; + //toread = PESBUF_SIZE - pesbuf_pos; + /* experiments found, that 80kB is the best buffer size, otherwise a/v sync seems + to suffer and / or audio stutters */ + toread = 80 * 1024 - pesbuf_pos; + bool retry = true; + + if (INBUF_SIZE - inbuf_pos < toread) + { + lt_info("%s inbuf full, setting toread to %d (old: %zd)\n", __FUNCTION__, INBUF_SIZE - inbuf_pos, toread); + toread = INBUF_SIZE - inbuf_pos; + } + pthread_mutex_lock(&currpos_mutex); + while(true) + { + ret = read(in_fd, pesbuf + pesbuf_pos, toread); + if (ret == 0 && retry) /* EOF */ + { + mf_lseek(curr_pos); + retry = false; + continue; + } + break; + } + if (ret < 0) + { + pthread_mutex_unlock(&currpos_mutex); + lt_info("%s failed: %m, pesbuf_pos: %zd, toread: %zd\n", __FUNCTION__, pesbuf_pos, toread); + return ret; + } + pesbuf_pos += ret; + curr_pos += ret; + pthread_mutex_unlock(&currpos_mutex); + + int count = 0; + uint16_t pid = 0; + bool resync = true; + while (count < pesbuf_pos - 10) + { + if (resync) + { + sync = mp_syncPES(pesbuf + count, pesbuf_pos - count - 10); + if (sync < 0) + { + if (pesbuf_pos - count - 10 > 4) + lt_info("%s cannot sync (count=%d, pesbuf_pos=%zd)\n", + __FUNCTION__, count, pesbuf_pos); + break; + } + if (sync) + lt_info("%s needed sync %zd\n", __FUNCTION__, sync); + count += sync; + } + uint8_t *ppes = pesbuf + count; + int av = 0; // 1 = video, 2 = audio + int64_t pts; + switch(ppes[3]) + { + case 0xba: //pack header; + // fprintf(stderr, "pack start code, 0x%02x\n", ppes[4]); + if ((ppes[4] & 0xf0) == 0x20) /* mpeg 1 */ + { + streamtype = 1; /* for audio setup */ + count += 12; + } + else if ((ppes[4] & 0xc0) == 0x40) /* mpeg 2 */ + { + streamtype = 0; + count += 14; /* correct: 14 + (ppes[13] & 0x07) */ + } + else + { + lt_info("%s weird pack header: 0x%2x\n", __FUNCTION__, ppes[4]); + count++; + } + resync = true; + continue; + break; + case 0xbd: // AC3 + { + int off = ppes[8] + 8 + 1; // ppes[8] is often 0 + if (count + off >= pesbuf_pos) + break; + uint16_t subid = ppes[off]; + // if (offset == 0x24 && subid == 0x10 ) // TTX? + if (subid < 0x80 || subid > 0x87) + break; + lt_debug("AC3: ofs 0x%02x subid 0x%02x\n", off, subid); + //subid -= 0x60; // normalize to 32...39 (hex 0x20..0x27) + + if (astreams.find(subid) == astreams.end()) + { + AStream tmp; + tmp.ac3 = true; + tmp.lang = ""; + astreams.insert(std::make_pair(subid, tmp)); + lt_info("%s found aid: %02x\n", __FUNCTION__, subid); + } + pid = subid; + av = 2; + break; + } + case 0xbb: + case 0xbe: + case 0xbf: + case 0xf0 ... 0xf3: + case 0xff: + //skip = (ppes[4] << 8 | ppes[5]) + 6; + //DBG("0x%02x header, skip = %d\n", ppes[3], skip); + break; + case 0xc0 ... 0xcf: + case 0xd0 ... 0xdf: + { + // fprintf(stderr, "audio stream 0x%02x\n", ppes[3]); + uint16_t id = ppes[3]; + if (astreams.find(id) == astreams.end()) + { + AStream tmp; + tmp.ac3 = false; + tmp.lang = ""; + astreams.insert(std::make_pair(id, tmp)); + lt_info("%s found aid: %02x\n", __FUNCTION__, id); + } + pid = id; + av = 2; + break; + } + case 0xe0 ... 0xef: + // fprintf(stderr, "video stream 0x%02x, %02x %02x \n", ppes[3], ppes[4], ppes[5]); + pid = 0x40; + av = 1; + pts = get_pts(ppes, true, pesbuf_pos - count); + if (pts < 0) + break; + pts_curr = pts; + if (pts_start < 0) + pts_start = pts; + break; + case 0xb9: + case 0xbc: + lt_debug("%s:%d %s\n", __FUNCTION__, __LINE__, + (ppes[3] == 0xb9) ? "program_end_code" : "program_stream_map"); + //resync = true; + // fallthrough. TODO: implement properly. + default: + //if (! resync) + // DBG("Unknown stream id: 0x%X.\n", ppes[3]); + count++; + resync = true; + continue; + break; + } + + int pesPacketLen = ((ppes[4] << 8) | ppes[5]) + 6; + if (count + pesPacketLen >= pesbuf_pos) + { + lt_debug("buffer len: %ld, pesPacketLen: %d :-(\n", pesbuf_pos - count, pesPacketLen); + break; + } + + int tsPacksCount = pesPacketLen / 184; + if ((tsPacksCount + 1) * 188 > INBUF_SIZE - inbuf_pos) + { + lt_info("not enough size in inbuf (needed %d, got %d)\n", (tsPacksCount + 1) * 188, INBUF_SIZE - inbuf_pos); + break; + } + + if (av) + { + int rest = pesPacketLen % 184; + + // divide PES packet into small TS packets + uint8_t pusi = 0x40; + int j; + uint8_t *ts = inbuf + inbuf_pos; + for (j = 0; j < tsPacksCount; j++) + { + ts[0] = 0x47; // SYNC Byte + ts[1] = pusi; // Set PUSI if first packet + ts[2] = pid; // PID (low) + ts[3] = 0x10 | (cc[pid] & 0x0F); // No adaptation field, payload only, continuity counter + cc[pid]++; + memcpy(ts + 4, ppes + j * 184, 184); + pusi = 0x00; // clear PUSI + ts += 188; + inbuf_pos += 188; + } + + if (rest > 0) + { + ts[0] = 0x47; // SYNC Byte + ts[1] = pusi; // Set PUSI or + ts[2] = pid; // PID (low) + ts[3] = 0x30 | (cc[pid] & 0x0F); // adaptation field, payload, continuity counter + cc[pid]++; + ts[4] = 183 - rest; + if (ts[4] > 0) + { + ts[5] = 0x00; + memset(ts + 6, 0xFF, ts[4] - 1); + } + memcpy(ts + 188 - rest, ppes + j * 184, rest); + inbuf_pos += 188; + } + } //if (av) + + count += pesPacketLen; + } + memmove(pesbuf, pesbuf + count, pesbuf_pos - count); + pesbuf_pos -= count; + return ret; +} + +//== seek to pos with sync to next proper TS packet == +//== returns offset to start of TS packet or actual == +//== pos on failure. == +//==================================================== +off_t cPlayback::mp_seekSync(off_t pos) +{ + off_t npos = pos; + off_t ret; + uint8_t pkt[1024]; + + pthread_mutex_lock(&currpos_mutex); + ret = mf_lseek(npos); + if (ret < 0) + lt_info("%s:%d lseek ret < 0 (%m)\n", __FUNCTION__, __LINE__); + + if (filetype != FILETYPE_TS) + { + int offset = 0; + int s; + ssize_t r; + bool retry = false; + while (true) + { + r = read(in_fd, &pkt[offset], 1024 - offset); + if (r < 0) + { + lt_info("%s read failed: %m\n", __FUNCTION__); + break; + } + if (r == 0) // EOF? + { + if (retry) + break; + if (mf_lseek(npos) < 0) /* next file in list? */ + { + lt_info("%s:%d lseek ret < 0 (%m)\n", __FUNCTION__, __LINE__); + break; + } + retry = true; + continue; + } + s = mp_syncPES(pkt, r + offset, true); + if (s < 0) + { + /* if the last 3 bytes of the buffer were 00 00 01, then + mp_sync_PES would not find it. So keep them and check + again in the next iteration */ + memmove(pkt, &pkt[r + offset - 3], 3); + npos += r; + offset = 3; + } + else + { + npos += s; + lt_info("%s sync after %lld\n", __FUNCTION__, npos - pos); + ret = mf_lseek(npos); + pthread_mutex_unlock(&currpos_mutex); + if (ret < 0) + lt_info("%s:%d lseek ret < 0 (%m)\n", __FUNCTION__, __LINE__); + return ret; + } + if (npos > (pos + 0x20000)) /* 128k enough? */ + break; + } + lt_info("%s could not sync to PES offset: %d r: %zd\n", __FUNCTION__, offset, r); + ret = mf_lseek(pos); + pthread_mutex_unlock(&currpos_mutex); + return ret; + } + + /* TODO: use bigger buffer here, too and handle EOF / next splitfile */ + while (read(in_fd, pkt, 1) > 0) + { + //-- check every byte until sync word reached -- + npos++; + if (*pkt == 0x47) + { + //-- if found double check for next sync word -- + if (read(in_fd, pkt, 188) == 188) + { + if(pkt[188-1] == 0x47) + { + ret = mf_lseek(npos - 1); // assume sync ok + pthread_mutex_unlock(&currpos_mutex); + if (ret < 0) + lt_info("%s:%d lseek ret < 0 (%m)\n", __FUNCTION__, __LINE__); + return ret; + } + else + { + ret = mf_lseek(npos); // oops, next pkt doesn't start with sync + if (ret < 0) + lt_info("%s:%d lseek ret < 0 (%m)\n", __FUNCTION__, __LINE__); + } + } + } + + //-- check probe limits -- + if (npos > (pos + 100 * 188)) + break; + } + + //-- on error stay on actual position -- + ret = mf_lseek(pos); + pthread_mutex_unlock(&currpos_mutex); + return ret; +} + +static int sync_ts(uint8_t *p, int len) +{ + int count; + if (len < 189) + return -1; + + count = 0; + while (*p != 0x47 || *(p + 188) != 0x47) + { + count++; + p++; + if (count + 188 > len) + return -1; + } + return count; +} + +/* get the pts value from a TS or PES packet + pes == true selects PES mode. */ +int64_t cPlayback::get_pts(uint8_t *p, bool pes, int bufsize) +{ + const uint8_t *end = p + bufsize; /* check for overflow */ + if (bufsize < 14) + return -1; + if (!pes) + { + if (p[0] != 0x47) + return -1; + if (!(p[1] & 0x40)) + return -1; + if (get_pid(p + 1) != vpid) + return -1; + if (!(p[3] & 0x10)) + return -1; + + if (p[3] & 0x20) + p += p[4] + 4 + 1; + else + p += 4; + + if (p + 13 > end) + return -1; + /* p is now pointing at the PES header. hopefully */ + if (p[0] || p[1] || (p[2] != 1)) + return -1; + } + + if ((p[6] & 0xC0) != 0x80) // MPEG1 + { + p += 6; + while (*p == 0xff) + { + p++; + if (p > end) + return -1; + } + if ((*p & 0xc0) == 0x40) + p += 2; + p -= 9; /* so that the p[9]...p[13] matches the below */ + if (p + 13 > end) + return -1; + } + else + { + /* MPEG2 */ + if ((p[7] & 0x80) == 0) // packets with both pts, don't care for dts + // if ((p[7] & 0xC0) != 0x80) // packets with only pts + // if ((p[7] & 0xC0) != 0xC0) // packets with pts and dts + return -1; + if (p[8] < 5) + return -1; + } + + if (!(p[9] & 0x20)) + return -1; + + int64_t pts = + ((p[ 9] & 0x0EULL) << 29) | + ((p[10] & 0xFFULL) << 22) | + ((p[11] & 0xFEULL) << 14) | + ((p[12] & 0xFFULL) << 7) | + ((p[13] & 0xFEULL) >> 1); + + //int msec = pts / 90; + //INFO("time: %02d:%02d:%02d\n", msec / 3600000, (msec / 60000) % 60, (msec / 1000) % 60); + return pts; +} + +/* returns: 0 == was already synchronous, > 0 == is now synchronous, -1 == could not sync */ +static int mp_syncPES(uint8_t *buf, int len, bool quiet) +{ + int ret = 0; + while (ret < len - 4) + { + if (buf[ret + 2] != 0x01) + { + ret++; + continue; + } + if (buf[ret + 1] != 0x00) + { + ret += 2; + continue; + } + if (buf[ret] != 0x00) + { + ret += 3; + continue; + } + /* all stream IDs are > 0x80 */ + if ((buf[ret + 3] & 0x80) != 0x80) + { + /* we already checked for 00 00 01, if the stream ID + is not valid, we can skip those 3 bytes */ + ret += 3; + continue; + } + return ret; + } + + if (!quiet && len > 5) /* only warn if enough space was available... */ + lt_info_c("%s No valid PES signature found. %d Bytes deleted.\n", __FUNCTION__, ret); + return -1; +} + +static inline uint16_t get_pid(uint8_t *buf) +{ + return (*buf & 0x1f) << 8 | *(buf + 1); +} + diff --git a/libspark/playback_lib.h b/libspark/playback_lib.h new file mode 100644 index 0000000..dcb78e3 --- /dev/null +++ b/libspark/playback_lib.h @@ -0,0 +1,125 @@ +#ifndef __PLAYBACK_TD_H +#define __PLAYBACK_TD_H + +#include +#include +#include +#include + +/* almost 256kB */ +#define INBUF_SIZE (1394 * 188) +#define PESBUF_SIZE (128 * 1024) + +typedef enum { + PLAYMODE_TS = 0, + PLAYMODE_FILE, +} playmode_t; + +typedef enum { + FILETYPE_UNKNOWN, + FILETYPE_TS, + FILETYPE_MPG, + FILETYPE_VDR +} filetype_t; + +typedef enum { + STATE_STOP, + STATE_PLAY, + STATE_PAUSE, + STATE_FF, + STATE_REW, + STATE_INIT +} playstate_t; + +typedef struct { + std::string Name; + off_t Size; +} filelist_t; + +class cPlayback +{ + private: + uint8_t *inbuf; + ssize_t inbuf_pos; + ssize_t inbuf_sync; + uint8_t *pesbuf; + ssize_t pesbuf_pos; + ssize_t inbuf_read(void); + ssize_t read_ts(void); + ssize_t read_mpeg(void); + + uint8_t cc[256]; + + int in_fd; + + int video_type; + int playback_speed; + int mSpeed; + playmode_t playMode; + std::vector filelist; /* for multi-file playback */ + + bool filelist_auto_add(void); + int mf_open(int fileno); + int mf_close(void); + off_t mf_lseek(off_t pos); + off_t mf_getsize(void); + int curr_fileno; + off_t curr_pos; + off_t last_size; + off_t bytes_per_second; + + uint16_t vpid; + uint16_t apid; + bool ac3; + struct AStream { + // uint16_t pid; + bool ac3; + std::string lang; /* not yet really used */ + }; + std::map astreams; /* stores AStream sorted by pid */ + + int64_t pts_start; + int64_t pts_end; + int64_t _pts_end; /* last good endpts */ + int64_t pts_curr; + int64_t get_pts(uint8_t *p, bool pes, int bufsize); + + filetype_t filetype; + playstate_t playstate; + + off_t seek_to_pts(int64_t pts); + off_t mp_seekSync(off_t pos); + int64_t get_PES_PTS(uint8_t *buf, int len, bool until_eof); + + pthread_t thread; + bool thread_started; + public: + cPlayback(int num = 0); + ~cPlayback(); + + void playthread(); + + bool Open(playmode_t PlayMode); + void Close(void); + bool Start(char *filename, unsigned short vpid, int vtype, unsigned short apid, + int ac3, unsigned int duration); + bool SetAPid(unsigned short pid, int ac3); + bool SetSpeed(int speed); + bool GetSpeed(int &speed) const; + bool GetPosition(int &position, int &duration); /* pos: current time in ms, dur: file length in ms */ + bool SetPosition(int position, bool absolute = false); /* position: jump in ms */ + void FindAllPids(uint16_t *apids, unsigned short *ac3flags, uint16_t *numpida, std::string *language); +#if 0 + // Functions that are not used by movieplayer.cpp: + bool Stop(void); + bool GetOffset(off64_t &offset); + bool IsPlaying(void) const { return playing; } + bool IsEnabled(void) const { return enabled; } + void * GetHandle(void); + void * GetDmHandle(void); + int GetCurrPlaybackSpeed(void) const { return nPlaybackSpeed; } + void PlaybackNotify (int Event, void *pData, void *pTag); + void DMNotify(int Event, void *pTsBuf, void *Tag); +#endif +}; +#endif diff --git a/libspark/playback_libeplayer3.cpp b/libspark/playback_libeplayer3.cpp new file mode 100644 index 0000000..2dfcbf4 --- /dev/null +++ b/libspark/playback_libeplayer3.cpp @@ -0,0 +1,364 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include "player.h" + +#include "playback_libeplayer3.h" + +extern cAudio *audioDecoder; +extern cVideo *videoDecoder; + +//Used by Fileplay +bool cPlayback::Open(playmode_t PlayMode) +{ + if (PlayMode != PLAYMODE_TS) { + audioDecoder->closeDevice(); + videoDecoder->closeDevice(); + decoders_closed = true; + } + + pm = PlayMode; + fn_ts = ""; + fn_xml = ""; + last_size = 0; + nPlaybackSpeed = 0; + init_jump = -1; + + return 0; +} + +void cPlayback::Close(void) +{ + printf("%s %s %d\n", __FILE__, __func__, __LINE__); + + //Dagobert: movieplayer does not call stop, it calls close ;) + Stop(); + if (decoders_closed) { + audioDecoder->openDevice(); + videoDecoder->openDevice(); + decoders_closed = false; + } +} + +bool cPlayback::Start(char *filename, int vpid, int vtype, int apid, int ac3, int) +{ + bool ret = false; + bool isHTTP = false; + no_probe = false; + + fprintf(stderr, "%s:%s - filename=%s vpid=%u vtype=%d apid=%u ac3=%d\n", __FILE__, __func__, filename, vpid, vtype, apid, ac3); + + init_jump = -1; + + std::string file; + if (*filename == '/') + file = "file://"; + file += filename; + + if (file.substr(0, 7) == "file://") { + if (file.substr(file.length() - 3) == ".ts") { + fn_ts = file.substr(7); + fn_xml = file.substr(7, file.length() - 9); + fn_xml += "xml"; + no_probe = true; + } + } else + isHTTP = true; + + if (player->Open(file.c_str(), no_probe)) { + if (pm == PLAYMODE_TS) { + struct stat s; + if (!stat(file.c_str(), &s)) + last_size = s.st_size; + ret = true; + videoDecoder->Stop(false); + audioDecoder->Stop(); + } else { + playing = true; + player->output.Open(); + if (apid) + SetAPid(apid, 0); + ret = player->Play(); + if (ret && !isHTTP) + playing = ret = player->Pause(); + } + } + return ret; +} + +bool cPlayback::Stop(void) +{ + printf("%s:%s playing %d\n", __FILE__, __func__, playing); + + player->Stop(); + player->output.Close(); + player->Close(); + + playing = false; + return true; +} + +bool cPlayback::SetAPid(int pid, bool /* ac3 */) +{ + return player->SwitchAudio(pid); +} + +bool cPlayback::SetSubtitlePid(int pid) +{ + return player->SwitchSubtitle(pid); +} + +bool cPlayback::SetTeletextPid(int pid) +{ + return player->SwitchTeletext(pid); +} + +bool cPlayback::SetSpeed(int speed) +{ + printf("%s:%s playing %d speed %d\n", __FILE__, __func__, playing, speed); + + if (!decoders_closed) { + audioDecoder->closeDevice(); + videoDecoder->closeDevice(); + decoders_closed = true; + usleep(500000); + player->output.Open(); + playing = player->Play(); + } + + if(!playing) + return false; + + bool res = true; + + nPlaybackSpeed = speed; + + if (speed > 1) { + /* direction switch ? */ + if (player->isBackWard) + player->FastBackward(0); + res = player->FastForward(speed); + } else if (speed < 0) { + /* direction switch ? */ + if (player->isForwarding) + player->Continue(); + res = player->FastBackward(speed); + } else if (speed == 0) { + /* konfetti: hmmm accessing the member isn't very proper */ + if ((player->isForwarding) || (!player->isBackWard)) + /* res = */ player->Pause(); + else + /* res = */ player->FastForward(0); + } else /* speed == 1 */ { + res = player->Continue(); + } + + if (init_jump > -1) { + SetPosition(init_jump); + init_jump = -1; + } + return res; +} + +bool cPlayback::GetSpeed(int &speed) const +{ + speed = nPlaybackSpeed; + return true; +} + +void cPlayback::GetPts(uint64_t &pts) +{ + player->GetPts((int64_t &) pts); +} + +// in milliseconds +bool cPlayback::GetPosition(int &position, int &duration) +{ + bool got_duration = false; + + /* hack: if the file is growing (timeshift), then determine its length + * by comparing the mtime with the mtime of the xml file */ + if (pm == PLAYMODE_TS) { + struct stat s; + if (!stat(fn_ts.c_str(), &s)) { + if (!playing || last_size != s.st_size) { + last_size = s.st_size; + time_t curr_time = s.st_mtime; + if (!stat(fn_xml.c_str(), &s)) { + duration = (curr_time - s.st_mtime) * 1000; + if (!playing) + return true; + got_duration = true; + } + } + } + } + + if (!playing) + return false; + + if (!player->isPlaying) { + printf("cPlayback::%s !!!!EOF!!!! < -1\n", __func__); + position = duration + 1000; + return false; + } + + int64_t vpts = 0; + player->GetPts(vpts); + + if(vpts <= 0) { + //printf("ERROR: vpts==0"); + } else { + /* len is in nanoseconds. we have 90 000 pts per second. */ + position = vpts/90; + } + + if (got_duration) + return true; + + int64_t length = 0; + + player->GetDuration(length); + + if(length <= 0) + duration = position + AV_TIME_BASE / 1000; + else + duration = length * 1000 / AV_TIME_BASE; + + return true; +} + +bool cPlayback::SetPosition(int position, bool absolute) +{ + if (!playing) { + /* the calling sequence is: + * Start() - paused + * SetPosition() - which fails if not running + * SetSpeed() - to start playing + * so let's remember the initial jump position and later jump to it + */ + init_jump = position; + return false; + } + player->Seek((int64_t)position * (AV_TIME_BASE / 1000), absolute); + return true; +} + +void cPlayback::FindAllPids(int *pids, unsigned int *ac3flags, unsigned int *numpids, std::string *language) +{ + unsigned int i = 0; + + std::vector tracks = player->manager.getAudioTracks(); + for (std::vector::iterator it = tracks.begin(); it != tracks.end() && i < *numpids; ++it) { + pids[i] = it->pid; + ac3flags[i] = it->ac3flags; + language[i] = it->title; + i++; + } + + *numpids = i; +} + +void cPlayback::FindAllSubtitlePids(int *pids, unsigned int *numpids, std::string *language) +{ + unsigned int i = 0; + + std::vector tracks = player->manager.getSubtitleTracks(); + for (std::vector::iterator it = tracks.begin(); it != tracks.end() && i < *numpids; ++it) { + pids[i] = it->pid; + language[i] = it->title; + i++; + } + + *numpids = i; +} + +void cPlayback::FindAllTeletextsubtitlePids(int *pids, unsigned int *numpids, std::string *language, int *mags, int *pages) +{ + unsigned int i = 0; + + std::vector tracks = player->manager.getTeletextTracks(); + for (std::vector::iterator it = tracks.begin(); it != tracks.end() && i < *numpids; ++it) { + if (it->type != 2 && it->type != 5) // return subtitles only + continue; + pids[i] = it->pid; + language[i] = it->title; + mags[i] = it->mag; + pages[i] = it->page; + i++; + } + + *numpids = i; +} + +int cPlayback::GetFirstTeletextPid(void) +{ + std::vector tracks = player->manager.getTeletextTracks(); + for (std::vector::iterator it = tracks.begin(); it != tracks.end(); ++it) { + if (it->type == 1) + return it->pid; + } + return -1; +} + +void cPlayback::GetChapters(std::vector &positions, std::vector &titles) +{ + player->GetChapters(positions, titles); +} + +void cPlayback::GetMetadata(std::vector &keys, std::vector &values) +{ + player->input.GetMetadata(keys, values); +} + +cPlayback::cPlayback(int num __attribute__((unused))) +{ + playing = false; + decoders_closed = false; + player = new Player(); +} + +cPlayback::~cPlayback() +{ + delete player; +} + +void cPlayback::RequestAbort() { + player->RequestAbort(); + while (player->isPlaying) + usleep(100000); +} + +bool cPlayback::IsPlaying() { + return player->isPlaying; +} + +uint64_t cPlayback::GetReadCount() { + return player->readCount; +} + +int cPlayback::GetAPid(void) +{ + return player->GetAudioPid(); +} + +int cPlayback::GetVPid(void) +{ + return player->GetVideoPid(); +} + +int cPlayback::GetSubtitlePid(void) +{ + return player->GetSubtitlePid(); +} + +int cPlayback::GetTeletextPid(void) +{ + return player->GetTeletextPid(); +} diff --git a/libspark/playback_libeplayer3.h b/libspark/playback_libeplayer3.h new file mode 100644 index 0000000..0929223 --- /dev/null +++ b/libspark/playback_libeplayer3.h @@ -0,0 +1,73 @@ +#ifndef __HAL_PLAYBACK_H +#define __HAL_PLAYBACK_H + +#include +#include + +typedef enum { + PLAYMODE_TS = 0, + PLAYMODE_FILE +} playmode_t; + +class Player; + +class cPlayback +{ + private: + bool enabled; + bool playing; + bool no_probe; + int nPlaybackSpeed; + bool Stop(void); + bool decoders_closed; + playmode_t pm; + std::string fn_ts; + std::string fn_xml; + off_t last_size; + int init_jump; + Player *player; + public: + cPlayback(int num = 0); + ~cPlayback(); + + bool Open(playmode_t PlayMode); + void Close(void); + bool Start(char *filename, int vpid, int vtype, int apid, int ac3, int duration); + bool SetAPid(int pid, bool ac3); + bool SetSubtitlePid(int pid); + bool SetTeletextPid(int pid); + int GetAPid(void); + int GetVPid(void); + int GetSubtitlePid(void); + int GetTeletextPid(void); + int GetFirstTeletextPid(void); + bool SetSpeed(int speed); + bool GetSpeed(int &speed) const; + bool GetPosition(int &position, int &duration); + void GetPts(uint64_t &pts); + bool SetPosition(int position, bool absolute = false); + void FindAllPids(int *apids, unsigned int *ac3flags, unsigned int *numpida, std::string *language); + void FindAllSubtitlePids(int *pids, unsigned int *numpids, std::string *language); + void FindAllTeletextsubtitlePids(int *pids, unsigned int *numpidt, std::string *tlanguage, int *mags, int *pages); + void RequestAbort(void); + bool IsPlaying(void); + uint64_t GetReadCount(void); +#if 0 + void FindAllSubs(uint16_t *pids, unsigned short *supported, uint16_t *numpida, std::string *language); + bool SelectSubtitles(int pid); +#endif + void GetChapters(std::vector &positions, std::vector &titles); + void GetMetadata(std::vector &keys, std::vector &values); +#if 0 + // Functions that are not used by movieplayer.cpp: + bool GetOffset(off64_t &offset); + bool IsPlaying(void) const; + bool IsEnabled(void) const; + void * GetHandle(void); + void * GetDmHandle(void); + int GetCurrPlaybackSpeed(void) const; + void PlaybackNotify (int Event, void *pData, void *pTag); + void DMNotify(int Event, void *pTsBuf, void *Tag); +#endif +}; +#endif diff --git a/libspark/pwrmngr.cpp b/libspark/pwrmngr.cpp new file mode 100644 index 0000000..d51074b --- /dev/null +++ b/libspark/pwrmngr.cpp @@ -0,0 +1,102 @@ +#include + +#include "pwrmngr.h" +#include "lt_debug.h" +#include +#include +#include +#include +#include + +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_PWRMNGR, this, args) +void cCpuFreqManager::Up(void) { lt_debug("%s\n", __FUNCTION__); } +void cCpuFreqManager::Down(void) { lt_debug("%s\n", __FUNCTION__); } +void cCpuFreqManager::Reset(void) { lt_debug("%s\n", __FUNCTION__); } +/* those function dummies return true or "harmless" values */ +bool cCpuFreqManager::SetDelta(unsigned long) { lt_debug("%s\n", __FUNCTION__); return true; } +#if HAVE_SPARK_HARDWARE +unsigned long cCpuFreqManager::GetCpuFreq(void) { + int freq = 0; + if (FILE *pll0 = fopen("/proc/cpu_frequ/pll0_ndiv_mdiv", "r")) { + char buffer[120]; + while(fgets(buffer, sizeof(buffer), pll0)) { + if (1 == sscanf(buffer, "SH4 = %d MHZ", &freq)) + break; + } + fclose(pll0); + return 1000 * 1000 * (unsigned long) freq; + } + return 0; +} +#else +unsigned long cCpuFreqManager::GetCpuFreq(void) { lt_debug("%s\n", __FUNCTION__); return 0; } +#endif +unsigned long cCpuFreqManager::GetDelta(void) { lt_debug("%s\n", __FUNCTION__); return 0; } +// +cCpuFreqManager::cCpuFreqManager(void) { lt_debug("%s\n", __FUNCTION__); } + +bool cPowerManager::SetState(PWR_STATE) { lt_debug("%s\n", __FUNCTION__); return true; } + +bool cPowerManager::Open(void) { lt_debug("%s\n", __FUNCTION__); return true; } +void cPowerManager::Close(void) { lt_debug("%s\n", __FUNCTION__); } +// +bool cPowerManager::SetStandby(bool Active, bool Passive) +{ + lt_debug("%s(%d, %d)\n", __FUNCTION__, Active, Passive); + return true; +} + +bool cCpuFreqManager::SetCpuFreq(unsigned long f) +{ +#if HAVE_SPARK_HARDWARE + if (f) { + FILE *pll0 = fopen ("/proc/cpu_frequ/pll0_ndiv_mdiv", "w"); + if (pll0) { + f /= 1000000; + fprintf(pll0, "%lu\n", (f/10 << 8) | 3); + fclose (pll0); + return false; + } + } +#else + /* actually SetCpuFreq is used to determine if the system is in standby + this is an "elegant" hack, because: + * during a recording, cpu freq is kept "high", even if the box is sent to standby + * the "SetStandby" call is made even if a recording is running + On the TD, setting standby disables the frontend, so we must not do it + if a recording is running. + For now, the values in neutrino are hardcoded: + * f == 0 => max => not standby + * f == 50000000 => min => standby + */ + lt_debug("%s(%lu) => set standby = %s\n", __FUNCTION__, f, f?"true":"false"); +#if 0 + int fd = open("/dev/stb/tdsystem", O_RDONLY); + if (fd < 0) + { + perror("open tdsystem"); + return false; + } + if (f) + { + ioctl(fd, IOC_AVS_SET_VOLUME, 31); /* mute AVS to avoid ugly noise */ + ioctl(fd, IOC_AVS_STANDBY_ENTER); + } + else + { + ioctl(fd, IOC_AVS_SET_VOLUME, 31); /* mute AVS to avoid ugly noise */ + ioctl(fd, IOC_AVS_STANDBY_LEAVE); + /* unmute will be done by cAudio::do_mute(). Ugly, but prevents pops */ + // ioctl(fd, IOC_AVS_SET_VOLUME, 0); /* max gain */ + } + + close(fd); +#endif +#endif + return true; +} + +// +cPowerManager::cPowerManager(void) { lt_debug("%s\n", __FUNCTION__); } +cPowerManager::~cPowerManager() { lt_debug("%s\n", __FUNCTION__); } + diff --git a/libspark/pwrmngr.h b/libspark/pwrmngr.h new file mode 100644 index 0000000..55dc984 --- /dev/null +++ b/libspark/pwrmngr.h @@ -0,0 +1,53 @@ +#ifndef __PWRMNGR_H__ +#define __PWRMNGR_H__ + +// -- cCpuFreqManager ---------------------------------------------------------- + +class cCpuFreqManager { +private: + unsigned long startCpuFreq; + unsigned long delta; +public: + void Up(void); + void Down(void); + void Reset(void); + // + bool SetCpuFreq(unsigned long CpuFreq); + bool SetDelta(unsigned long Delta); + unsigned long GetCpuFreq(void); + unsigned long GetDelta(void); + // + cCpuFreqManager(void); + +}; + +// -- cPowerManageger ---------------------------------------------------------- + +typedef enum +{ + PWR_INIT = 1, + PWR_FULL_ACTIVE, /* all devices/clocks up */ + PWR_ACTIVE_STANDBY, + PWR_PASSIVE_STANDBY, + PWR_INVALID +} PWR_STATE; + +class cPowerManager { +private: + bool init; + bool opened; + PWR_STATE powerState; + // + static void ApplicationCallback(void *, void *, signed long, void *, void *) {} + bool SetState(PWR_STATE PowerState); +public: + bool Open(void); + void Close(void); + // + bool SetStandby(bool Active, bool Passive); + // + cPowerManager(void); + virtual ~cPowerManager(); +}; + +#endif // __PWRMNGR_H__ diff --git a/libspark/record.cpp b/libspark/record.cpp new file mode 100644 index 0000000..ad8a5cf --- /dev/null +++ b/libspark/record.cpp @@ -0,0 +1,340 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "record_lib.h" +#include "lt_debug.h" +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_RECORD, this, args) +#define lt_info(args...) _lt_info(TRIPLE_DEBUG_RECORD, this, args) + +/* helper function to call the cpp thread loop */ +void *execute_record_thread(void *c) +{ + cRecord *obj = (cRecord *)c; + obj->RecordThread(); + return NULL; +} + +cRecord::cRecord(int num, int bs_dmx, int bs) +{ + lt_info("%s %d\n", __func__, num); + dmx = NULL; + record_thread_running = false; + file_fd = -1; + exit_flag = RECORD_STOPPED; + dmx_num = num; + bufsize = bs; + bufsize_dmx = bs_dmx; + failureCallback = NULL; + failureData = NULL; +} + +cRecord::~cRecord() +{ + lt_info("%s: calling ::Stop()\n", __func__); + Stop(); + lt_info("%s: end\n", __func__); +} + +bool cRecord::Open(void) +{ + lt_info("%s\n", __func__); + exit_flag = RECORD_STOPPED; + return true; +} + +#if 0 +// unused +void cRecord::Close(void) +{ + lt_info("%s: \n", __func__); +} +#endif + +bool cRecord::Start(int fd, unsigned short vpid, unsigned short *apids, int numpids, uint64_t) +{ + lt_info("%s: fd %d, vpid 0x%03x\n", __func__, fd, vpid); + int i; + + if (!dmx) + dmx = new cDemux(dmx_num); + + dmx->Open(DMX_TP_CHANNEL, NULL, bufsize_dmx); + dmx->pesFilter(vpid); + + for (i = 0; i < numpids; i++) + dmx->addPid(apids[i]); + + file_fd = fd; + exit_flag = RECORD_RUNNING; + if (posix_fadvise(file_fd, 0, 0, POSIX_FADV_DONTNEED)) + perror("posix_fadvise"); + + i = pthread_create(&record_thread, 0, execute_record_thread, this); + if (i != 0) + { + exit_flag = RECORD_FAILED_READ; + errno = i; + lt_info("%s: error creating thread! (%m)\n", __func__); + delete dmx; + dmx = NULL; + return false; + } + record_thread_running = true; + return true; +} + +bool cRecord::Stop(void) +{ + lt_info("%s\n", __func__); + + if (exit_flag != RECORD_RUNNING) + lt_info("%s: status not RUNNING? (%d)\n", __func__, exit_flag); + + exit_flag = RECORD_STOPPED; + if (record_thread_running) + pthread_join(record_thread, NULL); + record_thread_running = false; + + /* We should probably do that from the destructor... */ + if (!dmx) + lt_info("%s: dmx == NULL?\n", __func__); + else + delete dmx; + dmx = NULL; + + if (file_fd != -1) + close(file_fd); + else + lt_info("%s: file_fd not open??\n", __func__); + file_fd = -1; + return true; +} + +bool cRecord::ChangePids(unsigned short /*vpid*/, unsigned short *apids, int numapids) +{ + std::vector pids; + int j; + bool found; + unsigned short pid; + lt_info("%s\n", __func__); + if (!dmx) { + lt_info("%s: DMX = NULL\n", __func__); + return false; + } + pids = dmx->getPesPids(); + /* the first PID is the video pid, so start with the second PID... */ + for (std::vector::const_iterator i = pids.begin() + 1; i != pids.end(); ++i) { + found = false; + pid = (*i).pid; + for (j = 0; j < numapids; j++) { + if (pid == apids[j]) { + found = true; + break; + } + } + if (!found) + dmx->removePid(pid); + } + for (j = 0; j < numapids; j++) { + found = false; + for (std::vector::const_iterator i = pids.begin() + 1; i != pids.end(); ++i) { + if ((*i).pid == apids[j]) { + found = true; + break; + } + } + if (!found) + dmx->addPid(apids[j]); + } + return true; +} + +bool cRecord::AddPid(unsigned short pid) +{ + std::vector pids; + lt_info("%s: \n", __func__); + if (!dmx) { + lt_info("%s: DMX = NULL\n", __func__); + return false; + } + pids = dmx->getPesPids(); + for (std::vector::const_iterator i = pids.begin(); i != pids.end(); ++i) { + if ((*i).pid == pid) + return true; /* or is it an error to try to add the same PID twice? */ + } + return dmx->addPid(pid); +} + +void cRecord::RecordThread() +{ + lt_info("%s: begin\n", __func__); + char threadname[17]; + strncpy(threadname, "RecordThread", sizeof(threadname)); + threadname[16] = 0; + prctl (PR_SET_NAME, (unsigned long)&threadname); + int readsize = bufsize/16; + int buf_pos = 0; + int queued = 0; + uint8_t *buf; + struct aiocb a; + + buf = (uint8_t *)malloc(bufsize); + if (!buf) + { + exit_flag = RECORD_FAILED_MEMORY; + lt_info("%s: unable to allocate buffer! (out of memory)\n", __func__); + if (failureCallback) + failureCallback(failureData); + lt_info("%s: end\n", __func__); + pthread_exit(NULL); + } + + int val = fcntl(file_fd, F_GETFL); + if (fcntl(file_fd, F_SETFL, val|O_APPEND)) + lt_info("%s: O_APPEND? (%m)\n", __func__); + + memset(&a, 0, sizeof(a)); + a.aio_fildes = file_fd; + a.aio_sigevent.sigev_notify = SIGEV_NONE; + + dmx->Start(); + int overflow_count = 0; + bool overflow = false; + int r = 0; + while (exit_flag == RECORD_RUNNING) + { + if (buf_pos < bufsize) + { + if (overflow_count) { + lt_info("%s: Overflow cleared after %d iterations\n", __func__, overflow_count); + overflow_count = 0; + } + int toread = bufsize - buf_pos; + if (toread > readsize) + toread = readsize; + ssize_t s = dmx->Read(buf + buf_pos, toread, 50); + lt_debug("%s: buf_pos %6d s %6d / %6d\n", __func__, + buf_pos, (int)s, bufsize - buf_pos); + if (s < 0) + { + if (errno != EAGAIN && (errno != EOVERFLOW || !overflow)) + { + lt_info("%s: read failed: %m\n", __func__); + exit_flag = RECORD_FAILED_READ; + break; + } + } + else + { + overflow = false; + buf_pos += s; + } + } + else + { + if (!overflow) + overflow_count = 0; + overflow = true; + if (!(overflow_count % 10)) + lt_info("%s: buffer full! Overflow? (%d)\n", __func__, ++overflow_count); + } + r = aio_error(&a); + if (r == EINPROGRESS) + { + lt_debug("%s: aio in progress, free: %d\n", __func__, bufsize - buf_pos); + continue; + } + // not calling aio_return causes a memory leak --martii + r = aio_return(&a); + if (r < 0) + { + exit_flag = RECORD_FAILED_FILE; + lt_debug("%s: aio_return = %d (%m)\n", __func__, r); + break; + } + else + lt_debug("%s: aio_return = %d, free: %d\n", __func__, r, bufsize - buf_pos); + if (posix_fadvise(file_fd, 0, 0, POSIX_FADV_DONTNEED)) + perror("posix_fadvise"); + if (queued) + { + memmove(buf, buf + queued, buf_pos - queued); + buf_pos -= queued; + } + queued = buf_pos; + a.aio_buf = buf; + a.aio_nbytes = queued; + r = aio_write(&a); + if (r) + { + lt_info("%s: aio_write %d (%m)\n", __func__, r); + exit_flag = RECORD_FAILED_FILE; + break; + } + } + dmx->Stop(); + while (true) /* write out the unwritten buffer content */ + { + lt_debug("%s: run-out write, buf_pos %d\n", __func__, buf_pos); + r = aio_error(&a); + if (r == EINPROGRESS) + { + usleep(50000); + continue; + } + r = aio_return(&a); + if (r < 0) + { + exit_flag = RECORD_FAILED_FILE; + lt_info("%s: aio_result: %d (%m)\n", __func__, r); + break; + } + if (!queued) + break; + memmove(buf, buf + queued, buf_pos - queued); + buf_pos -= queued; + queued = buf_pos; + a.aio_buf = buf; + a.aio_nbytes = queued; + r = aio_write(&a); + } + free(buf); + +#if 0 + // TODO: do we need to notify neutrino about failing recording? + CEventServer eventServer; + eventServer.registerEvent2(NeutrinoMessages::EVT_RECORDING_ENDED, CEventServer::INITID_NEUTRINO, "/tmp/neutrino.sock"); + stream2file_status2_t s; + s.status = exit_flag; + strncpy(s.filename,basename(myfilename),512); + s.filename[511] = '\0'; + strncpy(s.dir,dirname(myfilename),100); + s.dir[99] = '\0'; + eventServer.sendEvent(NeutrinoMessages::EVT_RECORDING_ENDED, CEventServer::INITID_NEUTRINO, &s, sizeof(s)); + printf("[stream2file]: pthreads exit code: %i, dir: '%s', filename: '%s' myfilename: '%s'\n", exit_flag, s.dir, s.filename, myfilename); +#endif + + if ((exit_flag != RECORD_STOPPED) && failureCallback) + failureCallback(failureData); + lt_info("%s: end\n", __func__); + pthread_exit(NULL); +} + +int cRecord::GetStatus() +{ + return (exit_flag == RECORD_STOPPED) ? REC_STATUS_STOPPED : REC_STATUS_OK; +} + +void cRecord::ResetStatus() +{ + return; +} diff --git a/libspark/record_lib.h b/libspark/record_lib.h new file mode 100644 index 0000000..e7c5772 --- /dev/null +++ b/libspark/record_lib.h @@ -0,0 +1,50 @@ +#ifndef __RECORD_TD_H +#define __RECORD_TD_H + +#include +#include "dmx_lib.h" + +#define REC_STATUS_OK 0 +#define REC_STATUS_SLOW 1 +#define REC_STATUS_OVERFLOW 2 +#define REC_STATUS_STOPPED 4 + +typedef enum { + RECORD_RUNNING, + RECORD_STOPPED, + RECORD_FAILED_READ, /* failed to read from DMX */ + RECORD_FAILED_OVERFLOW, /* cannot write fast enough */ + RECORD_FAILED_FILE, /* cannot write to file */ + RECORD_FAILED_MEMORY /* out of memory */ +} record_state_t; + +class cRecord +{ + private: + int file_fd; + int dmx_num; + cDemux *dmx; + pthread_t record_thread; + bool record_thread_running; + record_state_t exit_flag; + int state; + int bufsize; + int bufsize_dmx; + void (*failureCallback)(void *); + void *failureData; + public: + cRecord(int num = 0, int bs_dmx = 100 * 188 * 1024, int bs = 100 * 188 * 1024); + void setFailureCallback(void (*f)(void *), void *d) { failureCallback = f; failureData = d; } + ~cRecord(); + + bool Open(); + bool Start(int fd, unsigned short vpid, unsigned short *apids, int numapids, uint64_t ch = 0); + bool Stop(void); + bool AddPid(unsigned short pid); + int GetStatus(); + void ResetStatus(); + bool ChangePids(unsigned short vpid, unsigned short *apids, int numapids); + + void RecordThread(); +}; +#endif diff --git a/libspark/td-compat/td-audio-compat.h b/libspark/td-compat/td-audio-compat.h new file mode 100644 index 0000000..3e0b4a7 --- /dev/null +++ b/libspark/td-compat/td-audio-compat.h @@ -0,0 +1,38 @@ +/* + * compatibility stuff for Tripledragon audio API + * + * (C) 2009 Stefan Seyfried + * + * 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; version 2 of the License. + * + * 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef __td_audio_compat_h__ +#define __td_audio_compat_h__ + +#include +// types +typedef enum { + AUDIO_SOURCE_DEMUX = AUD_SOURCE_DEMUX, + AUDIO_SOURCE_MEMORY = AUD_SOURCE_MEMORY +} audio_stream_source_t; +#define audio_channel_select_t audChannel_t +// ioctls +#define AUDIO_CHANNEL_SELECT MPEG_AUD_SELECT_CHANNEL +#define AUDIO_SELECT_SOURCE MPEG_AUD_SELECT_SOURCE +#define AUDIO_PLAY MPEG_AUD_PLAY +#define AUDIO_STOP MPEG_AUD_STOP +#define AUDIO_SET_MUTE MPEG_AUD_SET_MUTE + +#endif /* __td_audio_compat_h__ */ diff --git a/libspark/td-compat/td-demux-compat.h b/libspark/td-compat/td-demux-compat.h new file mode 100644 index 0000000..8feacfe --- /dev/null +++ b/libspark/td-compat/td-demux-compat.h @@ -0,0 +1,45 @@ +/* + * compatibility stuff for Tripledragon demux API + * + * (C) 2009 Stefan Seyfried + * + * 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; version 2 of the License. + * + * 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef __td_demux_compat_h__ +#define __td_demux_compat_h__ + +#include +#include +// types +#define dmx_output_t OutDevice +#define dmx_pes_type_t PesType +#define dmx_sct_filter_params demux_filter_para +#define dmx_pes_filter_params demux_pes_para +#define pes_type pesType +// defines +#define DMX_FILTER_SIZE FILTER_LENGTH +#define DMX_ONESHOT XPDF_ONESHOT +#define DMX_CHECK_CRC 0 // TD checks CRC by default +#define DMX_IMMEDIATE_START XPDF_IMMEDIATE_START +#define DMX_OUT_DECODER OUT_DECODER +// ioctls +#define DMX_SET_FILTER DEMUX_FILTER_SET +#define DMX_SET_PES_FILTER DEMUX_FILTER_PES_SET +#define DMX_START DEMUX_START +#define DMX_STOP DEMUX_STOP +#define DMX_SET_BUFFER_SIZE DEMUX_SET_BUFFER_SIZE + +#endif /* __td_demux_compat_h__ */ diff --git a/libspark/td-compat/td-frontend-compat.h b/libspark/td-compat/td-frontend-compat.h new file mode 100644 index 0000000..46781ce --- /dev/null +++ b/libspark/td-compat/td-frontend-compat.h @@ -0,0 +1,120 @@ +/* + * compatibility stuff for Tripledragon frontend API + * + * (C) 2009 Stefan Seyfried + * + * 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; version 2 of the License. + * + * 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef __td_frontend_compat_h__ +#define __td_frontend_compat_h__ + +#ifdef __cplusplus +extern "C" { +#endif + #include +#ifdef __cplusplus +} +#endif + +/* I know that those are different. But functions that get a + dvb_frontend_parameters struct passed on dbox/dreambox will most likely + get a tunersetup struct on TD, so it keeps the differences in headers + and function prototypes small. Of course, the functions itself will have + #ifdef TRIPLEDRAGON or similar... */ +#define dvb_frontend_parameters tunersetup + +/* compat stuff for settings.cpp */ +enum { + INVERSION_OFF, + INVERSION_ON, + INVERSION_AUTO +}; +typedef enum fe_code_rate { + FEC_NONE = 0, + FEC_1_2, + FEC_2_3, + FEC_3_4, + FEC_4_5, + FEC_5_6, + FEC_6_7, + FEC_7_8, + FEC_8_9, + FEC_AUTO +} fe_code_rate_t; + +enum td_code_rate { + TD_FEC_AUTO = 0, + TD_FEC_1_2, + TD_FEC_2_3, + TD_FEC_3_4, + TD_FEC_5_6, + TD_FEC_7_8 +}; + +typedef enum fe_sec_tone_mode { + SEC_TONE_ON, + SEC_TONE_OFF +} fe_sec_tone_mode_t; + +typedef enum fe_sec_voltage { + SEC_VOLTAGE_13, + SEC_VOLTAGE_18, + SEC_VOLTAGE_OFF +} fe_sec_voltage_t; + +typedef enum fe_sec_mini_cmd { + SEC_MINI_A, + SEC_MINI_B +} fe_sec_mini_cmd_t; + +struct dvb_diseqc_master_cmd { + unsigned char msg [6]; /* { framing, address, command, data [3] } */ + unsigned char msg_len; /* valid values are 3...6 */ +}; + +typedef enum fe_type { + FE_QPSK, + FE_QAM, + FE_OFDM, + FE_ATSC +} fe_type_t; + +struct dvb_frontend_info { +// char name[128]; + fe_type_t type; +#if 0 + __u32 frequency_min; + __u32 frequency_max; + __u32 frequency_stepsize; + __u32 frequency_tolerance; + __u32 symbol_rate_min; + __u32 symbol_rate_max; + __u32 symbol_rate_tolerance; /* ppm */ + __u32 notifier_delay; /* DEPRECATED */ + fe_caps_t caps; +#endif +}; + +struct dvb_frontend_event { + fe_status_t status; + tunersetup parameters; +}; + +#ifdef _DVBFRONTEND_H_ +#error _DVBFRONTEND_H_ included +#endif + +#endif /* __td_frontend_compat_h__ */ diff --git a/libspark/td-compat/td-value-compat.h b/libspark/td-compat/td-value-compat.h new file mode 100644 index 0000000..f7bb952 --- /dev/null +++ b/libspark/td-compat/td-value-compat.h @@ -0,0 +1,93 @@ +/* + * compatibility stuff for conversion of Tripledragon API values to DVB API + * and vice versa + * + * (C) 2009 Stefan Seyfried + * + * Released under the GPL V2. + */ + +#ifndef _td_value_compat_ +#define _td_value_compat_ + +#undef FE_GET_INFO +#undef FE_READ_BER +#undef FE_READ_SIGNAL_STRENGTH +#undef FE_READ_SNR +#undef FE_READ_UNCORRECTED_BLOCKS +#undef FE_GET_EVENT +#undef FE_READ_STATUS +#undef FE_SET_PROPERTY +#undef FE_GET_EVENT +#undef FE_GET_EVENT +#undef FE_SET_PROPERTY +#undef FE_SET_TONE +#undef FE_ENABLE_HIGH_LNB_VOLTAGE +#undef FE_SET_VOLTAGE +#undef FE_DISEQC_SEND_MASTER_CMD +#undef FE_DISEQC_SEND_BURST +/* hack, linux/dvb/frontend.h already defines fe_status */ +#define fe_status td_fe_status +#define fe_status_t td_fe_status_t +#define FE_HAS_SIGNAL TD_FE_HAS_SIGNAL +#define FE_HAS_CARRIER TD_FE_HAS_CARRIER +#define FE_HAS_VITERBI TD_FE_HAS_VITERBI +#define FE_HAS_SYNC TD_FE_HAS_SYNC +#define FE_HAS_LOCK TD_FE_HAS_LOCK +#define FE_TIMEDOUT TD_FE_TIMEDOUT +#define FE_REINIT TD_FE_REINIT +#include +#undef fe_status +#undef fe_status_t +#undef FE_HAS_SIGNAL +#undef FE_HAS_CARRIER +#undef FE_HAS_VITERBI +#undef FE_HAS_SYNC +#undef FE_HAS_LOCK +#undef FE_TIMEDOUT +#undef FE_REINIT +enum td_code_rate { + TD_FEC_AUTO = 0, + TD_FEC_1_2, + TD_FEC_2_3, + TD_FEC_3_4, + TD_FEC_5_6, + TD_FEC_7_8 +}; + +static inline unsigned int dvbfec2tdfec(fe_code_rate_t fec) +{ + switch (fec) { + case FEC_1_2: // FEC_1_2 ... FEC_3_4 are equal to TD_FEC_1_2 ... TD_FEC_3_4 + case FEC_2_3: + case FEC_3_4: + return (unsigned int)fec; + case FEC_5_6: + return TD_FEC_5_6; + case FEC_7_8: + return TD_FEC_7_8; + default: + break; + } + return TD_FEC_AUTO; +} + +static inline fe_code_rate_t tdfec2dvbfec(unsigned int tdfec) +{ + switch (tdfec) + { + case TD_FEC_1_2: + case TD_FEC_2_3: + case TD_FEC_3_4: + return (fe_code_rate_t)tdfec; + case TD_FEC_5_6: + return FEC_5_6; + case TD_FEC_7_8: + return FEC_7_8; + default: + break; + } + return FEC_AUTO; +} + +#endif /* _td_value_compat_ */ diff --git a/libspark/td-compat/td-video-compat.h b/libspark/td-compat/td-video-compat.h new file mode 100644 index 0000000..137a346 --- /dev/null +++ b/libspark/td-compat/td-video-compat.h @@ -0,0 +1,46 @@ +/* + * compatibility stuff for Tripledragon video API + * + * (C) 2009 Stefan Seyfried + * + * 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; version 2 of the License. + * + * 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __td_video_compat_h__ +#define __td_video_compat_h__ + +#include +// types +#define video_format_t vidDispSize_t +#define video_displayformat_t vidDispMode_t +typedef enum { + VIDEO_SOURCE_DEMUX = VID_SOURCE_DEMUX, + VIDEO_SOURCE_MEMORY = VID_SOURCE_MEMORY +} video_stream_source_t; +typedef enum { + VIDEO_STOPPED, /* Video is stopped */ + VIDEO_PLAYING, /* Video is currently playing */ + VIDEO_FREEZED /* Video is freezed */ +} video_play_state_t; +//#define video_play_state_t vidState_t +// ioctls +#define VIDEO_SET_SYSTEM MPEG_VID_SET_DISPFMT +#define VIDEO_SET_FORMAT MPEG_VID_SET_DISPSIZE +#define VIDEO_SET_DISPLAY_FORMAT MPEG_VID_SET_DISPMODE +#define VIDEO_SELECT_SOURCE MPEG_VID_SELECT_SOURCE +#define VIDEO_PLAY MPEG_VID_PLAY +#define VIDEO_STOP MPEG_VID_STOP +#define VIDEO_SET_BLANK MPEG_VID_SET_BLANK + +#endif /* __td_video_compat_h__ */ diff --git a/libspark/video.cpp b/libspark/video.cpp new file mode 100644 index 0000000..ea72b70 --- /dev/null +++ b/libspark/video.cpp @@ -0,0 +1,795 @@ +/* + * (C) 2002-2003 Andreas Oberritter + * (C) 2010-2013 Stefan Seyfried + * + * 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 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include "video_lib.h" +#include "lt_debug.h" +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_VIDEO, this, args) +#define lt_info(args...) _lt_info(TRIPLE_DEBUG_VIDEO, this, args) +#define lt_debug_c(args...) _lt_debug(TRIPLE_DEBUG_VIDEO, NULL, args) +#define lt_info_c(args...) _lt_info(TRIPLE_DEBUG_VIDEO, NULL, args) + +#define fop(cmd, args...) ({ \ + int _r; \ + if (fd >= 0) { \ + if ((_r = ::cmd(fd, args)) < 0) \ + lt_info(#cmd"(fd, "#args")\n"); \ + else \ + lt_debug(#cmd"(fd, "#args")\n");\ + } \ + else { _r = fd; } \ + _r; \ +}) + +cVideo * videoDecoder = NULL; +cVideo * pipDecoder = NULL; +int system_rev = 0; + +static bool hdmi_enabled = true; +static bool stillpicture = false; + +static const char *VDEV[] = { + "/dev/dvb/adapter0/video0", + "/dev/dvb/adapter0/video1" +}; +static const char *VMPEG_aspect[] = { + "/proc/stb/vmpeg/0/aspect", + "/proc/stb/vmpeg/1/aspect" +}; + +static const char *VMPEG_xres[] = { + "/proc/stb/vmpeg/0/xres", + "/proc/stb/vmpeg/1/xres" +}; + +static const char *VMPEG_yres[] = { + "/proc/stb/vmpeg/0/yres", + "/proc/stb/vmpeg/1/yres" +}; + +static const char *VMPEG_dst_all[] = { + "/proc/stb/vmpeg/0/dst_all", + "/proc/stb/vmpeg/1/dst_all" +}; + +static const char *VMPEG_framerate[] = { + "/proc/stb/vmpeg/0/framerate", + "/proc/stb/vmpeg/1/framerate" +}; + + +#define VIDEO_STREAMTYPE_MPEG2 0 +#define VIDEO_STREAMTYPE_MPEG4_H264 1 +#define VIDEO_STREAMTYPE_VC1 3 +#define VIDEO_STREAMTYPE_MPEG4_Part2 4 +#define VIDEO_STREAMTYPE_VC1_SM 5 +#define VIDEO_STREAMTYPE_MPEG1 6 + + +static int proc_put(const char *path, const char *value, const int len) +{ + int ret, ret2; + int pfd = open(path, O_WRONLY); + if (pfd < 0) + return pfd; + ret = write(pfd, value, len); + ret2 = close(pfd); + if (ret2 < 0) + return ret2; + return ret; +} + +static int proc_get(const char *path, char *value, const int len) +{ + int ret, ret2; + int pfd = open(path, O_RDONLY); + if (pfd < 0) + return pfd; + ret = read(pfd, value, len); + value[len-1] = '\0'; /* make sure string is terminated */ + while (ret > 0 && isspace(value[ret-1])) + value[--ret] = '\0'; /* remove trailing whitespace */ + ret2 = close(pfd); + if (ret2 < 0) + return ret2; + return ret; +} + +static unsigned int proc_get_hex(const char *path) +{ + unsigned int n, ret = 0; + char buf[16]; + n = proc_get(path, buf, 16); + if (n > 0) + sscanf(buf, "%x", &ret); + return ret; +} + +static int hdmi_out(bool enable) +{ + struct stmfbio_output_configuration out; + int ret = -1; + lt_info_c("%s(%d)\n", __func__, enable); + int fb = open("/dev/fb0", O_RDWR); + if (fb < 0) + { + lt_debug_c("%s: can't open /dev/fb0 (%m)\n", __func__); + return -1; + } + out.outputid = STMFBIO_OUTPUTID_MAIN; + if (ioctl(fb, STMFBIO_GET_OUTPUT_CONFIG, &out) < 0) + { + lt_debug_c("%s: STMFBIO_GET_OUTPUT_CONFIG (%m)\n", __func__); + goto out; + } + hdmi_enabled = enable; + out.caps = STMFBIO_OUTPUT_CAPS_HDMI_CONFIG; + out.activate = STMFBIO_ACTIVATE_IMMEDIATE; + out.analogue_config = 0; + if (enable) + out.hdmi_config &= ~STMFBIO_OUTPUT_HDMI_DISABLED; + else + out.hdmi_config |= STMFBIO_OUTPUT_HDMI_DISABLED; + + ret = ioctl(fb, STMFBIO_SET_OUTPUT_CONFIG, &out); + if (ret < 0) + _lt_debug(TRIPLE_DEBUG_VIDEO, NULL, "%s: STMFBIO_SET_OUTPUT_CONFIG (%m)\n", __func__); +out: + close(fb); + return ret; +} + + +cVideo::cVideo(int, void *, void *, unsigned int unit) +{ + lt_debug("%s unit %u\n", __func__, unit); + + brightness = -1; + contrast = -1; + saturation = -1; + hue = -1; + + //croppingMode = VID_DISPMODE_NORM; + //outputformat = VID_OUTFMT_RGBC_SVIDEO; + scartvoltage = -1; + video_standby = 0; + if (unit > 1) { + lt_info("%s: unit %d out of range, setting to 0\n", __func__, unit); + devnum = 0; + } else + devnum = unit; + fd = -1; + openDevice(); +} + +cVideo::~cVideo(void) +{ + closeDevice(); +} + +void cVideo::openDevice(void) +{ + int n = 0; + lt_debug("#%d: %s\n", devnum, __func__); + /* todo: this fd checking is racy, should be protected by a lock */ + if (fd != -1) /* already open */ + return; +retry: + if ((fd = open(VDEV[devnum], O_RDWR|O_CLOEXEC)) < 0) + { + if (errno == EBUSY) + { + /* sometimes we get busy quickly after close() */ + usleep(50000); + if (++n < 10) + goto retry; + } + lt_info("#%d: %s cannot open %s: %m, retries %d\n", devnum, __func__, VDEV[devnum], n); + } + playstate = VIDEO_STOPPED; +} + +void cVideo::closeDevice(void) +{ + lt_debug("%s\n", __func__); + /* looks like sometimes close is unhappy about non-empty buffers */ + Start(); + if (fd >= 0) + close(fd); + fd = -1; + playstate = VIDEO_STOPPED; +} + +int cVideo::setAspectRatio(int aspect, int mode) +{ + static const char *a[] = { "n/a", "4:3", "14:9", "16:9" }; + static const char *m[] = { "panscan", "letterbox", "bestfit", "nonlinear", "(unset)" }; + int n; + lt_debug("%s: a:%d m:%d %s\n", __func__, aspect, mode, m[(mode < 0||mode > 3) ? 4 : mode]); + + if (aspect > 3 || aspect == 0) + lt_info("%s: invalid aspect: %d\n", __func__, aspect); + else if (aspect > 0) /* -1 == don't set */ + { + lt_debug("%s: /proc/stb/video/aspect -> %s\n", __func__, a[aspect]); + n = proc_put("/proc/stb/video/aspect", a[aspect], strlen(a[aspect])); + if (n < 0) + lt_info("%s: proc_put /proc/stb/video/aspect (%m)\n", __func__); + } + + if (mode == -1) + return 0; + + lt_debug("%s: /proc/stb/video/policy -> %s\n", __func__, m[mode]); + n = proc_put("/proc/stb/video/policy", m[mode], strlen(m[mode])); + if (n < 0) + return 1; + return 0; +} + +int cVideo::getAspectRatio(void) +{ + video_size_t s; + if (fd == -1) + { + /* in movieplayer mode, fd is not opened -> fall back to procfs */ + int n = proc_get_hex(VMPEG_aspect[devnum]); + return n * 2 + 1; + } + if (fop(ioctl, VIDEO_GET_SIZE, &s) < 0) + { + lt_info("%s: VIDEO_GET_SIZE %m\n", __func__); + return -1; + } + lt_debug("#%d: %s: %d\n", devnum, __func__, s.aspect_ratio); + return s.aspect_ratio * 2 + 1; +} + +int cVideo::setCroppingMode(int /*vidDispMode_t format*/) +{ + return 0; +#if 0 + croppingMode = format; + const char *format_string[] = { "norm", "letterbox", "unknown", "mode_1_2", "mode_1_4", "mode_2x", "scale", "disexp" }; + const char *f; + if (format >= VID_DISPMODE_NORM && format <= VID_DISPMODE_DISEXP) + f = format_string[format]; + else + f = "ILLEGAL format!"; + lt_debug("%s(%d) => %s\n", __FUNCTION__, format, f); + return fop(ioctl, MPEG_VID_SET_DISPMODE, format); +#endif +} + +int cVideo::Start(void * /*PcrChannel*/, unsigned short /*PcrPid*/, unsigned short /*VideoPid*/, void * /*hChannel*/) +{ + lt_debug("#%d: %s playstate=%d\n", devnum, __func__, playstate); +#if 0 + if (playstate == VIDEO_PLAYING) + return 0; + if (playstate == VIDEO_FREEZED) /* in theory better, but not in practice :-) */ + fop(ioctl, MPEG_VID_CONTINUE); +#endif + playstate = VIDEO_PLAYING; + fop(ioctl, VIDEO_SELECT_SOURCE, VIDEO_SOURCE_DEMUX); + int res = fop(ioctl, VIDEO_PLAY); + if (brightness > -1) { + SetControl(VIDEO_CONTROL_BRIGHTNESS, brightness); + brightness = -1; + } + if (contrast > -1) { + SetControl(VIDEO_CONTROL_CONTRAST, contrast); + contrast = -1; + } + if (saturation > -1) { + SetControl(VIDEO_CONTROL_SATURATION, saturation); + saturation = -1; + } + if (hue > -1) { + SetControl(VIDEO_CONTROL_HUE, hue); + hue = -1; + } + return res; +} + +int cVideo::Stop(bool blank) +{ + lt_debug("#%d: %s(%d)\n", devnum, __func__, blank); + if (stillpicture) + { + lt_debug("%s: stillpicture == true\n", __func__); + return -1; + } + playstate = blank ? VIDEO_STOPPED : VIDEO_FREEZED; + return fop(ioctl, VIDEO_STOP, blank ? 1 : 0); +} + +int cVideo::setBlank(int) +{ + fop(ioctl, VIDEO_PLAY); + fop(ioctl, VIDEO_CONTINUE); + video_still_picture sp = { NULL, 0 }; + fop(ioctl, VIDEO_STILLPICTURE, &sp); + return Stop(1); +} + +int cVideo::SetVideoSystem(int video_system, bool remember) +{ + lt_debug("%s(%d, %d)\n", __func__, video_system, remember); + char current[32]; + static const char *modes[] = { + "pal", // VIDEO_STD_NTSC + "pal", // VIDEO_STD_SECAM + "pal", // VIDEO_STD_PAL + "480p", // VIDEO_STD_480P + "576p50", // VIDEO_STD_576P + "720p60", // VIDEO_STD_720P60 + "1080i60", // VIDEO_STD_1080I60 + "720p50", // VIDEO_STD_720P50 + "1080i50", // VIDEO_STD_1080I50 + "1080p30", // VIDEO_STD_1080P30 + "1080p24", // VIDEO_STD_1080P24 + "1080p25", // VIDEO_STD_1080P25 + "720p50", // VIDEO_STD_AUTO -> not implemented + "1080p50" // VIDEO_STD_1080P50 -> SPARK only + }; + + if (video_system > VIDEO_STD_MAX) + { + lt_info("%s: video_system (%d) > VIDEO_STD_MAX (%d)\n", __func__, video_system, VIDEO_STD_MAX); + return -1; + } + int ret = proc_get("/proc/stb/video/videomode", current, 32); + if (strcmp(current, modes[video_system]) == 0) + { + lt_info("%s: video_system %d (%s) already set, skipping\n", __func__, video_system, current); + return 0; + } + lt_info("%s: old: '%s' new: '%s'\n", __func__, current, modes[video_system]); + bool stopped = false; + if (playstate == VIDEO_PLAYING) + { + lt_info("%s: playstate == VIDEO_PLAYING, stopping video\n", __func__); + Stop(); + stopped = true; + } + hdmi_out(false); + ret = proc_put("/proc/stb/video/videomode", modes[video_system],strlen(modes[video_system])); + hdmi_out(true); + if (stopped) + Start(); + + return ret; +} + +int cVideo::getPlayState(void) +{ + return playstate; +} + +void cVideo::SetVideoMode(analog_mode_t mode) +{ + lt_debug("#%d: %s(%d)\n", devnum, __func__, mode); + if (!(mode & ANALOG_SCART_MASK)) + { + lt_debug("%s: non-SCART mode ignored\n", __func__); + return; + } + const char *m; + switch(mode) + { + case ANALOG_SD_YPRPB_SCART: + m = "yuv"; + break; + case ANALOG_SD_RGB_SCART: + m = "rgb"; + break; + default: + lt_info("%s unknown mode %d\n", __func__, mode); + m = "rgb"; + break; /* default to rgb */ + } + proc_put("/proc/stb/avs/0/colorformat", m, strlen(m)); +} + +void cVideo::ShowPicture(const char * fname, const char *_destname) +{ + lt_debug("%s(%s)\n", __func__, fname); + char destname[512]; + char cmd[512]; + char *p; + int mfd; + struct stat st, st2; + if (video_standby) + { + /* does not work and the driver does not seem to like it */ + lt_info("%s: video_standby == true\n", __func__); + return; + } + const char *lastDot = strrchr(fname, '.'); + if (lastDot && !strcasecmp(lastDot + 1, "m2v")) + strncpy(destname, fname, sizeof(destname)); + else { + if (_destname) + strncpy(destname, _destname, sizeof(destname)); + else { + strcpy(destname, "/var/cache"); + if (stat(fname, &st2)) + { + lt_info("%s: could not stat %s (%m)\n", __func__, fname); + return; + } + mkdir(destname, 0755); + /* the cache filename is (example for /share/tuxbox/neutrino/icons/radiomode.jpg): + /var/cache/share.tuxbox.neutrino.icons.radiomode.jpg.m2v + build that filename first... + TODO: this could cause name clashes, use a hashing function instead... */ + strcat(destname, fname); + p = &destname[strlen("/var/cache/")]; + while ((p = strchr(p, '/')) != NULL) + *p = '.'; + strcat(destname, ".m2v"); + } + /* ...then check if it exists already... */ + if (stat(destname, &st) || (st.st_mtime != st2.st_mtime) || (st.st_size == 0)) + { + struct utimbuf u; + u.actime = time(NULL); + u.modtime = st2.st_mtime; + /* it does not exist or has a different date, so call ffmpeg... */ + sprintf(cmd, "ffmpeg -y -f mjpeg -i '%s' -s 1280x720 '%s' = 0) + { + stillpicture = true; + + if (ioctl(fd, VIDEO_SET_FORMAT, VIDEO_FORMAT_16_9) < 0) + lt_info("%s: VIDEO_SET_FORMAT failed (%m)\n", __func__); + char *iframe = (char *)malloc(st.st_size); + if (! iframe) + { + lt_info("%s: malloc failed (%m)\n", __func__); + goto out; + } + read(mfd, iframe, st.st_size); + fop(ioctl, VIDEO_PLAY); + fop(ioctl, VIDEO_CONTINUE); + video_still_picture sp = { iframe, st.st_size }; + fop(ioctl, VIDEO_STILLPICTURE, &sp); + free(iframe); + } + out: + close(mfd); + return; +} + +void cVideo::StopPicture() +{ + lt_debug("%s\n", __func__); + stillpicture = false; +} + +void cVideo::Standby(unsigned int bOn) +{ + lt_debug("%s(%d)\n", __func__, bOn); + if (bOn) + { + closeDevice(); + hdmi_out(false); + } + else + { + /* only enable HDMI output when coming from standby, not on + * start. I have no idea why, but enabling it on startup leads + * to strange locking problems of the framebuffer driver :-( */ + if (!hdmi_enabled) + { + hdmi_out(true); + /* make sure the driver has time to settle. + * again - lame, but makes it work... */ + sleep(1); + } + openDevice(); + } + video_standby = bOn; +} + +int cVideo::getBlank(void) +{ + static unsigned int lastcount = 0; + unsigned int count = 0; + size_t n = 0; + ssize_t r; + char *line = NULL; + /* hack: the "mailbox" irq is not increasing if + * no audio or video is decoded... */ + FILE *f = fopen("/proc/interrupts", "r"); + if (! f) /* huh? */ + return 0; + while ((r = getline(&line, &n, f)) != -1) + { + if (r <= (ssize_t) strlen("mailbox")) /* should not happen... */ + continue; + line[r - 1] = 0; /* remove \n */ + if (!strcmp(&line[r - 1 - strlen("mailbox")], "mailbox")) + { + count = atoi(line + 5); + break; + } + } + free(line); + fclose(f); + int ret = (count == lastcount); /* no new decode -> return 1 */ + lt_debug("#%d: %s: %d (irq++: %d)\n", devnum, __func__, ret, count - lastcount); + lastcount = count; + return ret; +} + +/* this function is regularly called, checks if video parameters + changed and triggers appropriate actions */ +void cVideo::VideoParamWatchdog(void) +{ +#if 0 + static unsigned int _v_info = (unsigned int) -1; + unsigned int v_info; + if (fd == -1) + return; + ioctl(fd, MPEG_VID_GET_V_INFO_RAW, &v_info); + if (_v_info != v_info) + { + lt_debug("%s params changed. old: %08x new: %08x\n", __FUNCTION__, _v_info, v_info); + setAspectRatio(-1, -1); + } + _v_info = v_info; +#endif +} + +void cVideo::Pig(int x, int y, int w, int h, int osd_w, int osd_h, int startx, int starty, int endx, int endy) +{ + char buffer[64]; + int _x, _y, _w, _h; + /* the target "coordinates" seem to be in a PAL sized plane + * TODO: check this in the driver sources */ + int xres = 720; /* proc_get_hex("/proc/stb/vmpeg/0/xres") */ + int yres = 576; /* proc_get_hex("/proc/stb/vmpeg/0/yres") */ + lt_debug("#%d %s: x:%d y:%d w:%d h:%d ow:%d oh:%d\n", devnum, __func__, x, y, w, h, osd_w, osd_h); + if (x == -1 && y == -1 && w == -1 && h == -1) + { + _w = xres; + _h = yres; + _x = 0; + _y = 0; + } + else + { + // need to do some additional adjustments because osd border is handled by blitter + x += startx; + x *= endx - startx + 1; + y += starty; + y *= endy - starty + 1; + w *= endx - startx + 1; + h *= endy - starty + 1; + _x = x * xres / osd_w; + _w = w * xres / osd_w; + _y = y * yres / osd_h; + _h = h * yres / osd_h; + _x /= 1280; + _y /= 720; + _w /= 1280; + _h /= 720; + } + lt_debug("#%d %s: x:%d y:%d w:%d h:%d xr:%d yr:%d\n", devnum, __func__, _x, _y, _w, _h, xres, yres); + sprintf(buffer, "%x %x %x %x", _x, _y, _w, _h); + proc_put(VMPEG_dst_all[devnum], buffer, strlen(buffer)); +} + +static inline int rate2csapi(int rate) +{ + switch (rate) + { + case 23976: + return 0; + case 24000: + return 1; + case 25000: + return 2; + case 29976: + return 3; + case 30000: + return 4; + case 50000: + return 5; + case 50940: + return 6; + case 60000: + return 7; + default: + break; + } + return -1; +} + +void cVideo::getPictureInfo(int &width, int &height, int &rate) +{ + video_size_t s; + int r; + if (fd == -1) + { + /* in movieplayer mode, fd is not opened -> fall back to procfs */ + r = proc_get_hex(VMPEG_framerate[devnum]); + width = proc_get_hex(VMPEG_xres[devnum]); + height = proc_get_hex(VMPEG_yres[devnum]); + rate = rate2csapi(r); + return; + } + ioctl(fd, VIDEO_GET_SIZE, &s); + ioctl(fd, VIDEO_GET_FRAME_RATE, &r); + rate = rate2csapi(r); + height = s.h; + width = s.w; + lt_debug("#%d: %s: rate: %d, width: %d height: %d\n", devnum, __func__, rate, width, height); +} + +void cVideo::SetSyncMode(AVSYNC_TYPE mode) +{ + lt_debug("%s %d\n", __func__, mode); + /* + * { 0, LOCALE_OPTIONS_OFF }, + * { 1, LOCALE_OPTIONS_ON }, + * { 2, LOCALE_AUDIOMENU_AVSYNC_AM } + */ + const char *apply[] = { "disapply", "apply" }; + const char *clock[] = { "video", "audio" }; + const char *a = apply[mode > 0]; /* mode == 1 or mode == 2 -> "apply" */ + const char *c = clock[mode > 1]; /* mode == 2 -> "audio" */ + proc_put("/proc/stb/stream/policy/AV_SYNC", a, strlen(a)); + proc_put("/proc/stb/stream/policy/MASTER_CLOCK", c, strlen(c)); +}; + +int cVideo::SetStreamType(VIDEO_FORMAT type) +{ + static const char *VF[] = { + "VIDEO_FORMAT_MPEG2", + "VIDEO_FORMAT_MPEG4", + "VIDEO_FORMAT_VC1", + "VIDEO_FORMAT_JPEG", + "VIDEO_FORMAT_GIF", + "VIDEO_FORMAT_PNG" + }; + int t; + lt_debug("#%d: %s type=%s\n", devnum, __func__, VF[type]); + + switch (type) + { + case VIDEO_FORMAT_MPEG4: + t = VIDEO_STREAMTYPE_MPEG4_H264; + break; + case VIDEO_FORMAT_VC1: + t = VIDEO_STREAMTYPE_VC1; + break; + case VIDEO_FORMAT_MPEG2: + default: + t = VIDEO_STREAMTYPE_MPEG2; + break; + } + + if (ioctl(fd, VIDEO_SET_STREAMTYPE, t) < 0) + lt_info("%s VIDEO_SET_STREAMTYPE(%d) failed: %m\n", __func__, t); + return 0; +} + +int64_t cVideo::GetPTS(void) +{ + int64_t pts = 0; + if (ioctl(fd, VIDEO_GET_PTS, &pts) < 0) + lt_info("%s: GET_PTS failed (%m)\n", __func__); + return pts; +} + +void cVideo::SetDemux(cDemux *) +{ + lt_debug("#%d %s not implemented yet\n", devnum, __func__); +} + +void cVideo::SetControl(int control, int value) { + const char *p = NULL; + switch (control) { + case VIDEO_CONTROL_BRIGHTNESS: + brightness = value; + p = "/proc/stb/video/plane/psi_brightness"; + break; + case VIDEO_CONTROL_CONTRAST: + contrast = value; + p = "/proc/stb/video/plane/psi_contrast"; + break; + case VIDEO_CONTROL_SATURATION: + saturation = value; + p = "/proc/stb/video/plane/psi_saturation"; + break; + case VIDEO_CONTROL_HUE: + hue = value; + p = "/proc/stb/video/plane/psi_tint"; + break; + } + if (p) { + char buf[20]; + int len = snprintf(buf, sizeof(buf), "%d", value); + if (len < (int) sizeof(buf)) + proc_put(p, buf, len); + } +} + +void cVideo::SetColorFormat(COLOR_FORMAT color_format) { + const char *p = NULL; + switch(color_format) { + case COLORFORMAT_RGB: + p = "rgb"; + break; + case COLORFORMAT_YUV: + p = "yuv"; + break; + case COLORFORMAT_CVBS: + p = "cvbs"; + break; + case COLORFORMAT_SVIDEO: + p = "svideo"; + break; + case COLORFORMAT_HDMI_RGB: + p = "hdmi_rgb"; + break; + case COLORFORMAT_HDMI_YCBCR444: + p = "hdmi_yuv"; + break; + case COLORFORMAT_HDMI_YCBCR422: + p = "hdmi_422"; + break; + } + if (p) + proc_put("/proc/stb/avs/0/colorformat", p, strlen(p)); +} diff --git a/libspark/video_lib.h b/libspark/video_lib.h new file mode 100644 index 0000000..5b4e1af --- /dev/null +++ b/libspark/video_lib.h @@ -0,0 +1,210 @@ +#ifndef _VIDEO_TD_H +#define _VIDEO_TD_H + +#include +#include "../common/cs_types.h" +#include "dmx_lib.h" + +typedef enum { + ANALOG_SD_RGB_CINCH = 0x00, + ANALOG_SD_YPRPB_CINCH, + ANALOG_HD_RGB_CINCH, + ANALOG_HD_YPRPB_CINCH, + ANALOG_SD_RGB_SCART = 0x10, + ANALOG_SD_YPRPB_SCART, + ANALOG_HD_RGB_SCART, + ANALOG_HD_YPRPB_SCART, + ANALOG_SCART_MASK = 0x10 +} analog_mode_t; + +typedef enum { + COLORFORMAT_RGB = 0x10, // keep compatible with analog_mode_t + COLORFORMAT_YUV, + COLORFORMAT_CVBS, + COLORFORMAT_SVIDEO, + COLORFORMAT_HDMI_RGB, + COLORFORMAT_HDMI_YCBCR444, + COLORFORMAT_HDMI_YCBCR422 +} COLOR_FORMAT; + + +typedef enum { + VIDEO_FORMAT_MPEG2 = 0, + VIDEO_FORMAT_MPEG4, + VIDEO_FORMAT_VC1, + VIDEO_FORMAT_JPEG, + VIDEO_FORMAT_GIF, + VIDEO_FORMAT_PNG +} VIDEO_FORMAT; + +typedef enum { + VIDEO_SD = 0, + VIDEO_HD, + VIDEO_120x60i, + VIDEO_320x240i, + VIDEO_1440x800i, + VIDEO_360x288i +} VIDEO_DEFINITION; + +typedef enum { + VIDEO_FRAME_RATE_23_976 = 0, + VIDEO_FRAME_RATE_24, + VIDEO_FRAME_RATE_25, + VIDEO_FRAME_RATE_29_97, + VIDEO_FRAME_RATE_30, + VIDEO_FRAME_RATE_50, + VIDEO_FRAME_RATE_59_94, + VIDEO_FRAME_RATE_60 +} VIDEO_FRAME_RATE; + +typedef enum { + DISPLAY_AR_1_1, + DISPLAY_AR_4_3, + DISPLAY_AR_14_9, + DISPLAY_AR_16_9, + DISPLAY_AR_20_9, + DISPLAY_AR_RAW, +} DISPLAY_AR; + +typedef enum { + DISPLAY_AR_MODE_PANSCAN = 0, + DISPLAY_AR_MODE_LETTERBOX, + DISPLAY_AR_MODE_NONE, + DISPLAY_AR_MODE_PANSCAN2 +} DISPLAY_AR_MODE; + +typedef enum { + VIDEO_DB_DR_NEITHER = 0, + VIDEO_DB_ON, + VIDEO_DB_DR_BOTH +} VIDEO_DB_DR; + +typedef enum { + VIDEO_PLAY_STILL = 0, + VIDEO_PLAY_CLIP, + VIDEO_PLAY_TRICK, + VIDEO_PLAY_MOTION, + VIDEO_PLAY_MOTION_NO_SYNC +} VIDEO_PLAY_MODE; + +typedef enum { + VIDEO_STD_NTSC, + VIDEO_STD_SECAM, + VIDEO_STD_PAL, + VIDEO_STD_480P, + VIDEO_STD_576P, + VIDEO_STD_720P60, + VIDEO_STD_1080I60, + VIDEO_STD_720P50, + VIDEO_STD_1080I50, + VIDEO_STD_1080P30, + VIDEO_STD_1080P24, + VIDEO_STD_1080P25, + VIDEO_STD_AUTO, + VIDEO_STD_1080P50, /* SPARK only */ + VIDEO_STD_MAX +} VIDEO_STD; + +/* not used, for dummy functions */ +typedef enum { + VIDEO_HDMI_CEC_MODE_OFF = 0, + VIDEO_HDMI_CEC_MODE_TUNER, + VIDEO_HDMI_CEC_MODE_RECORDER +} VIDEO_HDMI_CEC_MODE; + +typedef enum +{ + VIDEO_CONTROL_BRIGHTNESS = 0, + VIDEO_CONTROL_CONTRAST, + VIDEO_CONTROL_SATURATION, + VIDEO_CONTROL_HUE, + VIDEO_CONTROL_SHARPNESS, + VIDEO_CONTROL_MAX = VIDEO_CONTROL_SHARPNESS +} VIDEO_CONTROL; + + +class cVideo +{ + friend class cDemux; + friend class cPlayback; + private: + /* video device */ + int fd; + unsigned int devnum; + /* apparently we cannot query the driver's state + => remember it */ + video_play_state_t playstate; + int /*vidDispMode_t*/ croppingMode; + int /*vidOutFmt_t*/ outputformat; + int scartvoltage; + + VIDEO_FORMAT StreamType; + VIDEO_DEFINITION VideoDefinition; + DISPLAY_AR DisplayAR; + VIDEO_PLAY_MODE SyncMode; + DISPLAY_AR_MODE ARMode; + VIDEO_DB_DR eDbDr; + DISPLAY_AR PictureAR; + VIDEO_FRAME_RATE FrameRate; + int video_standby; + int64_t GetPTS(void); + + int brightness, contrast, saturation, hue; + + void openDevice(void); + void closeDevice(void); + public: + /* constructor & destructor */ + cVideo(int mode, void *, void *, unsigned int unit = 0); + ~cVideo(void); + + void * GetTVEnc() { return NULL; }; + void * GetTVEncSD() { return NULL; }; + + /* aspect ratio */ + int getAspectRatio(void); + void getPictureInfo(int &width, int &height, int &rate); + int setAspectRatio(int aspect, int mode); + + /* cropping mode */ + int setCroppingMode(int x = 0 /*vidDispMode_t x = VID_DISPMODE_NORM*/); + + /* get play state */ + int getPlayState(void); + + /* blank on freeze */ + int getBlank(void); + int setBlank(int enable); + + /* change video play state. Parameters are all unused. */ + int Start(void *PcrChannel = NULL, unsigned short PcrPid = 0, unsigned short VideoPid = 0, void *x = NULL); + int Stop(bool blank = true); + bool Pause(void); + + /* set video_system */ + int SetVideoSystem(int video_system, bool remember = true); + int SetStreamType(VIDEO_FORMAT type); + void SetSyncMode(AVSYNC_TYPE mode); + bool SetCECMode(VIDEO_HDMI_CEC_MODE) { return true; }; + void SetCECAutoView(bool) { return; }; + void SetCECAutoStandby(bool) { return; }; + void ShowPicture(const char * fname, const char *_destname = NULL); + void StopPicture(); + void Standby(unsigned int bOn); + void Pig(int x, int y, int w, int h, int osd_w = 1064, int osd_h = 600, int startx = 0, int starty = 0, int endx = 1279, int endy = 719); + void SetControl(int, int); + void VideoParamWatchdog(void); + void setContrast(int val); + void SetVideoMode(analog_mode_t mode); + void SetDBDR(int) { return; }; + void SetAudioHandle(void *) { return; }; + void SetAutoModes(int [VIDEO_STD_MAX]) { return; }; + int OpenVBI(int) { return 0; }; + int CloseVBI(void) { return 0; }; + int StartVBI(unsigned short) { return 0; }; + int StopVBI(void) { return 0; }; + void SetDemux(cDemux *dmx); + void SetColorFormat(COLOR_FORMAT color_format); +}; + +#endif diff --git a/libtest.cpp b/libtest.cpp new file mode 100644 index 0000000..b66024b --- /dev/null +++ b/libtest.cpp @@ -0,0 +1,46 @@ +/* minimal test program for libstb-hal + * (C) 2012-2013 Stefan Seyfried + * License: GPL v2 or later + * + * this does just test the input converter thread for now... + */ + +#include +#include +#include +#include +#if HAVE_GENERIC_HARDWARE +#include + +extern GLFramebuffer *glfb; +#define fb_pixel_t uint32_t +#endif + +int main(int argc __attribute__((unused)), char ** argv __attribute__((unused))) +{ + init_td_api(); +#if HAVE_GENERIC_HARDWARE + int available = glfb->getOSDBuffer()->size(); /* allocated in glfb constructor */ + fb_pixel_t *lfb = reinterpret_cast(glfb->getOSDBuffer()->data()); + + int x = 0; +#endif + while (1) { +#if HAVE_GENERIC_HARDWARE + fb_pixel_t c = (0xff << (8 * x))|0xff000000; + x++; + if (x > 3) x = 0; + for (int i = 0; i < available / 4; i++) + *(lfb + i) = c; + glfb->blit(); +#endif + sleep(1); + if (! access("/tmp/endtest", R_OK)) + { + unlink("/tmp/endtest"); + break; + } + }; + shutdown_td_api(); + return 0; +} diff --git a/libtriple/Makefile.am b/libtriple/Makefile.am new file mode 100644 index 0000000..6df87cb --- /dev/null +++ b/libtriple/Makefile.am @@ -0,0 +1,20 @@ +noinst_LTLIBRARIES = libtriple.la + +AM_CPPFLAGS = \ + -I$(top_srcdir)/common \ + @DIRECTFB_CFLAGS@ + +AM_CXXFLAGS = -fno-rtti -fno-exceptions -fno-strict-aliasing +AM_LDFLAGS = \ + @DIRECTFB_LIBS@ + +libtriple_la_SOURCES = \ + hardware_caps.c \ + lt_dfbinput.cpp \ + dmx_td.cpp \ + video_td.cpp \ + audio_td.cpp \ + init_td.cpp \ + playback_td.cpp \ + pwrmngr.cpp \ + record_td.cpp diff --git a/libtriple/README.libtriple b/libtriple/README.libtriple new file mode 100644 index 0000000..c13dbf8 --- /dev/null +++ b/libtriple/README.libtriple @@ -0,0 +1,75 @@ +libtriple reimplements the interfaces of the libcoolstrem library for +the Tripledragon receiver. + +There are a few debugging or configuration helpers which affect the +way libtriple does some things. They are all configured by exporting +environment variables, which are described here: + +TRIPLE_NOSCART=1 - makes neutrino *not* do any voltage switching on + SCART pin 8, probably not useful for anyone but me + +TRIPLE_LCDBACKLIGHT=1 - makes the LCD backlight stay on in standby, + may disturb others + +HAL_DEBUG=... - controls various debugging levels in libtriple + valid values for the different component: + audio 0x01 + video 0x02 + demux 0x04 + play 0x08 + power 0x10 + init 0x20 + ca 0x40 + record 0x80 + all 0xff + multiple levels are added / ORed together, so if you want to + debug record and playback code, do "export TRIPLE_DEBUG=0x88" + for audio & video use TRIPLE_DEBUG=0x3 + +DSP_DEVICE +MIX_DEVICE - alternative audio devices for the audioplayer and internet + radio. Those are used to output music to e.g. USB audio devices. + Here is what you need to do: + * look in /dev/sound which devices are already there. Probably + /dev/sound/dsp and /dev/sound/mixer, created by the tdoss driver + * make sure that the USB HC driver is loaded: + modprobe ohci-hcd + * load the USB audio driver: + modprobe audio + * plug in your USB audio device, check with "dmesg" that it is + recognised by the kernel + * look in /dev/sound which new devices are there. Probably it's + /dev/sound/dsp1 and /dev/sound/mixer1. If there are more - well + it's time to experiment ;) + * export DSP_DEVICE=/dev/sound/dsp1 and MIX_DEVICE=/dev/sound/mixer1 + (of course the devices you found in the last step) + * from the same shell you exported the variables, start neutrino + (make sure that an already running neutrino is stopped before you + do that) + * start the audioplayer, play a track. Look for log lines like + [LT:106b5788:audio ] PrepareClipPlay: dsp_dev /dev/sound/dsp1 mix_dev /dev/sound/mixer1 + * if it works - fine :-) + * if it does not work, look for: + PrepareClipPlay: DSP_DEVICE is set (/dev/sound/dsp1) but cannot be opened, fall back to /dev/sound/dsp + PrepareClipPlay: dsp_dev /dev/sound/dsp mix_dev /dev/sound/mixer1 + PrepareClipPlay: open mixer /dev/sound/mixer1 failed (No such file or directory) + * this basically means that the device is not there. Different errors + will get different messages - I cannot trigger those now, so you'll + need to find them out by yourself ;) + * another possible messag you may get is: + PrepareClipPlay: more than one mixer control: devmask 00000021 stereo 00000021 + This means that your device has more than one mixer. The set bit + numbers in the devmask are the different mixers, in this case + it would be number 0 and 5. To select one of those, export + MIX_NUMBER=5 or MIX_NUMBER=0 (this code is untested, there may + be bugs) + + So now I found out what devices to use, but how do I make that permanent? + That's easy: + * create or extend /etc/rcS.local with + modprobe ohci-hcd + modprobe audio + * create or extend /etc/profile.local with + export DSP_DEVICE=/dev/sound/dsp1 + export MIX_DEVICE=/dev/sound/mixer1 + * reboot. Enjoy. diff --git a/libtriple/audio_td.cpp b/libtriple/audio_td.cpp new file mode 100644 index 0000000..eb21456 --- /dev/null +++ b/libtriple/audio_td.cpp @@ -0,0 +1,414 @@ +#include +#include +#include +#include +#include + + +#include +#include +#define AUDIO_DEVICE "/dev/" DEVICE_NAME_AUDIO +#include "audio_td.h" +#include "lt_debug.h" +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_AUDIO, this, args) +#define lt_info(args...) _lt_info(TRIPLE_DEBUG_AUDIO, this, args) + +#include + +cAudio * audioDecoder = NULL; + +cAudio::cAudio(void *, void *, void *) +{ + fd = -1; + clipfd = -1; + mixer_fd = -1; + openDevice(); + Muted = false; +} + +cAudio::~cAudio(void) +{ + closeDevice(); +} + +void cAudio::openDevice(void) +{ + if (fd < 0) + { + if ((fd = open(AUDIO_DEVICE, O_RDWR)) < 0) + lt_info("openDevice: open failed (%m)\n"); + fcntl(fd, F_SETFD, FD_CLOEXEC); + do_mute(true, false); + } + else + lt_info("openDevice: already open (fd = %d)\n", fd); +} + +void cAudio::closeDevice(void) +{ + if (fd >= 0) + close(fd); + fd = -1; + if (clipfd >= 0) + close(clipfd); + clipfd = -1; + if (mixer_fd >= 0) + close(mixer_fd); + mixer_fd = -1; +} + +int cAudio::do_mute(bool enable, bool remember) +{ + lt_debug("%s(%d, %d)\n", __FUNCTION__, enable, remember); + int avsfd; + int ret; + if (remember) + Muted = enable; + ret = ioctl(fd, MPEG_AUD_SET_MUTE, enable); + if (ret < 0) + lt_info("%s(%d) failed (%m)\n", __FUNCTION__, (int)enable); + + /* are we using alternative DSP / mixer? */ + if (clipfd != -1 || mixer_fd != -1) + setVolume(volume,volume); /* considers "Muted" variable, "remember" + is basically always true in this context */ + avsfd = open("/dev/stb/tdsystem", O_RDONLY); + if (avsfd >= 0) + { + if (enable) + ioctl(avsfd, IOC_AVS_SET_VOLUME, 31); + else + ioctl(avsfd, IOC_AVS_SET_VOLUME, 0); + close(avsfd); + } + return ret; +} + +int map_volume(const int volume) +{ + unsigned char vol = volume; + if (vol > 100) + vol = 100; + +// vol = (invlog63[volume] + 1) / 2; + vol = 31 - vol * 31 / 100; + return vol; +} + +int cAudio::setVolume(unsigned int left, unsigned int right) +{ +// int avsfd; + int ret; + int vl = map_volume(left); + int vr = map_volume(right); + volume = (left + right) / 2; + int v = map_volume(volume); + if (clipfd != -1 && mixer_fd != -1) { + int tmp = 0; + /* not sure if left / right is correct here, but it is always the same anyways ;-) */ + if (! Muted) + tmp = left << 8 | right; + ret = ioctl(mixer_fd, MIXER_WRITE(mixer_num), &tmp); + if (ret == -1) + lt_info("%s: MIXER_WRITE(%d),%04x: %m\n", __func__, mixer_num, tmp); + return ret; + } +// if (settings.volume_type == CControld::TYPE_OST || forcetype == (int)CControld::TYPE_OST) + { + AUDVOL vol; + vol.frontleft = vl; + vol.frontright = vr; + vol.rearleft = vl; + vol.rearright = vr; + vol.center = v; + vol.lfe = v; + ret = ioctl(fd, MPEG_AUD_SET_VOL, &vol); + if (ret < 0) + lt_info("setVolume MPEG_AUD_SET_VOL failed (%m)\n"); + return ret; + } +#if 0 + else if (settings.volume_type == CControld::TYPE_AVS || forcetype == (int)CControld::TYPE_AVS) + { + if ((avsfd = open(AVS_DEVICE, O_RDWR)) < 0) + perror("[controld] " AVS_DEVICE); + else { + if (ioctl(avsfd, IOC_AVS_SET_VOLUME, v)) + perror("[controld] IOC_AVS_SET_VOLUME"); + close(avsfd); + return 0; + } + } + fprintf(stderr, "CAudio::setVolume: invalid settings.volume_type = %d\n", settings.volume_type); + return -1; +#endif +} + +int cAudio::Start(void) +{ + int ret; + ret = ioctl(fd, MPEG_AUD_PLAY); + /* this seems to be not strictly necessary since neutrino + re-mutes all the time, but is certainly more correct */ + ioctl(fd, MPEG_AUD_SET_MUTE, Muted); + return ret; +} + +int cAudio::Stop(void) +{ + return ioctl(fd, MPEG_AUD_STOP); +} + +bool cAudio::Pause(bool /*Pcm*/) +{ + return true; +}; + +void cAudio::SetSyncMode(AVSYNC_TYPE Mode) +{ + lt_debug("%s %d\n", __FUNCTION__, Mode); + switch (Mode) + { + case 0: + ioctl(fd, MPEG_AUD_SYNC_OFF); + break; + default: + ioctl(fd, MPEG_AUD_SYNC_ON); + break; + } +}; + +void cAudio::SetStreamType(AUDIO_FORMAT type) +{ + int bypass_disable; + lt_debug("%s %d\n", __FUNCTION__, type); + StreamType = type; + + if (StreamType != AUDIO_FMT_DOLBY_DIGITAL && StreamType != AUDIO_FMT_MPEG && StreamType != AUDIO_FMT_MPG1) + lt_info("%s unhandled AUDIO_FORMAT %d\n", __FUNCTION__, StreamType); + + bypass_disable = (StreamType != AUDIO_FMT_DOLBY_DIGITAL); + setBypassMode(bypass_disable); + + if (StreamType == AUDIO_FMT_MPEG) + ioctl(fd, MPEG_AUD_SET_STREAM_TYPE, AUD_STREAM_TYPE_PES); + if (StreamType == AUDIO_FMT_MPG1) + ioctl(fd, MPEG_AUD_SET_STREAM_TYPE, AUD_STREAM_TYPE_MPEG1); +}; + +int cAudio::setChannel(int channel) +{ + lt_debug("%s %d\n", __FUNCTION__, channel); + return 0; +}; + +int cAudio::PrepareClipPlay(int ch, int srate, int bits, int little_endian) +{ + int fmt; + unsigned int devmask, stereo, usable; + const char *dsp_dev = getenv("DSP_DEVICE"); + const char *mix_dev = getenv("MIX_DEVICE"); + lt_debug("%s ch %d srate %d bits %d le %d\n", __FUNCTION__, ch, srate, bits, little_endian); + if (clipfd >= 0) { + lt_info("%s: clipfd already opened (%d)\n", __FUNCTION__, clipfd); + return -1; + } + mixer_num = -1; + mixer_fd = -1; + /* a different DSP device can be given with DSP_DEVICE and MIX_DEVICE + * if this device cannot be opened, we fall back to the internal TD OSS device + * Example: + * modprobe ohci-hcd + * modprobe audio + * export DSP_DEVICE=/dev/sound/dsp1 + * export MIX_DEVICE=/dev/sound/mixer1 + * neutrino + */ + if ((!dsp_dev) || (access(dsp_dev, W_OK))) { + if (dsp_dev) + lt_info("%s: DSP_DEVICE is set (%s) but cannot be opened," + " fall back to /dev/sound/dsp\n", __func__, dsp_dev); + dsp_dev = "/dev/sound/dsp"; + } + lt_info("%s: dsp_dev %s mix_dev %s\n", __func__, dsp_dev, mix_dev); /* NULL mix_dev is ok */ + /* the tdoss dsp driver seems to work only on the second open(). really. */ + clipfd = open(dsp_dev, O_WRONLY); + close(clipfd); + clipfd = open(dsp_dev, O_WRONLY); + if (clipfd < 0) { + lt_info("%s open %s: %m\n", dsp_dev, __FUNCTION__); + return -1; + } + fcntl(clipfd, F_SETFD, FD_CLOEXEC); + /* no idea if we ever get little_endian == 0 */ + if (little_endian) + fmt = AFMT_S16_BE; + else + fmt = AFMT_S16_LE; + if (ioctl(clipfd, SNDCTL_DSP_SETFMT, &fmt)) + perror("SNDCTL_DSP_SETFMT"); + if (ioctl(clipfd, SNDCTL_DSP_CHANNELS, &ch)) + perror("SNDCTL_DSP_CHANNELS"); + if (ioctl(clipfd, SNDCTL_DSP_SPEED, &srate)) + perror("SNDCTL_DSP_SPEED"); + if (ioctl(clipfd, SNDCTL_DSP_RESET)) + perror("SNDCTL_DSP_RESET"); + + if (!mix_dev) + return 0; + + mixer_fd = open(mix_dev, O_RDWR); + if (mixer_fd < 0) { + lt_info("%s: open mixer %s failed (%m)\n", __func__, mix_dev); + /* not a real error */ + return 0; + } + if (ioctl(mixer_fd, SOUND_MIXER_READ_DEVMASK, &devmask) == -1) { + lt_info("%s: SOUND_MIXER_READ_DEVMASK %m\n", __func__); + devmask = 0; + } + if (ioctl(mixer_fd, SOUND_MIXER_READ_STEREODEVS, &stereo) == -1) { + lt_info("%s: SOUND_MIXER_READ_STEREODEVS %m\n", __func__); + stereo = 0; + } + usable = devmask & stereo; + if (usable == 0) { + lt_info("%s: devmask: %08x stereo: %08x, no usable dev :-(\n", + __func__, devmask, stereo); + close(mixer_fd); + mixer_fd = -1; + return 0; /* TODO: should we treat this as error? */ + } + /* __builtin_popcount needs GCC, it counts the set bits... */ + if (__builtin_popcount (usable) != 1) { + /* TODO: this code is not yet tested as I have only single-mixer devices... */ + lt_info("%s: more than one mixer control: devmask %08x stereo %08x\n" + "%s: querying MIX_NUMBER environment variable...\n", + __func__, devmask, stereo, __func__); + const char *tmp = getenv("MIX_NUMBER"); + if (tmp) + mixer_num = atoi(tmp); + lt_info("%s: mixer_num is %d -> device %08x\n", + __func__, (mixer_num >= 0) ? (1 << mixer_num) : 0); + /* no error checking, you'd better know what you are doing... */ + } else { + mixer_num = 0; + while (!(usable & 0x01)) { + mixer_num++; + usable >>= 1; + } + } + setVolume(volume, volume); + + return 0; +}; + +int cAudio::WriteClip(unsigned char *buffer, int size) +{ + int ret; + // lt_debug("cAudio::%s\n", __FUNCTION__); + if (clipfd <= 0) { + lt_info("%s: clipfd not yet opened\n", __FUNCTION__); + return -1; + } + ret = write(clipfd, buffer, size); + if (ret < 0) + lt_info("%s: write error (%m)\n", __FUNCTION__); + return ret; +}; + +int cAudio::StopClip() +{ + lt_debug("%s\n", __FUNCTION__); + if (clipfd <= 0) { + lt_info("%s: clipfd not yet opened\n", __FUNCTION__); + return -1; + } + close(clipfd); + clipfd = -1; + if (mixer_fd >= 0) + close(mixer_fd); + mixer_fd = -1; + setVolume(volume, volume); + return 0; +}; + +void cAudio::getAudioInfo(int &type, int &layer, int &freq, int &bitrate, int &mode) +{ + lt_debug("%s\n", __FUNCTION__); + unsigned int atype; + static const int freq_mpg[] = {44100, 48000, 32000, 0}; + static const int freq_ac3[] = {48000, 44100, 32000, 0}; + scratchl2 i; + if (ioctl(fd, MPEG_AUD_GET_DECTYP, &atype) < 0) + perror("cAudio::getAudioInfo MPEG_AUD_GET_DECTYP"); + if (ioctl(fd, MPEG_AUD_GET_STATUS, &i) < 0) + perror("cAudio::getAudioInfo MPEG_AUD_GET_STATUS"); + + type = atype; +#if 0 +/* this does not work, some of the values are negative?? */ + AMPEGStatus A; + memcpy(&A, &i.word00, sizeof(i.word00)); + layer = A.audio_mpeg_layer; + mode = A.audio_mpeg_mode; + bitrate = A.audio_mpeg_bitrate; + switch(A.audio_mpeg_frequency) +#endif + /* layer and bitrate are not used anyway... */ + layer = 0; //(i.word00 >> 17) & 3; + bitrate = 0; //(i.word00 >> 12) & 3; + switch (type) + { + case 0: /* MPEG */ + mode = (i.word00 >> 6) & 3; + freq = freq_mpg[(i.word00 >> 10) & 3]; + break; + case 1: /* AC3 */ + mode = (i.word00 >> 28) & 7; + freq = freq_ac3[(i.word00 >> 16) & 3]; + break; + default: + mode = 0; + freq = 0; + } + //fprintf(stderr, "type: %d layer: %d freq: %d bitrate: %d mode: %d\n", type, layer, freq, bitrate, mode); +}; + +void cAudio::SetSRS(int /*iq_enable*/, int /*nmgr_enable*/, int /*iq_mode*/, int /*iq_level*/) +{ + lt_debug("%s\n", __FUNCTION__); +}; + +void cAudio::SetSpdifDD(bool enable) +{ + lt_debug("%s %d\n", __FUNCTION__, enable); +}; + +void cAudio::ScheduleMute(bool On) +{ + lt_debug("%s %d\n", __FUNCTION__, On); +}; + +void cAudio::EnableAnalogOut(bool enable) +{ + lt_debug("%s %d\n", __FUNCTION__, enable); +}; + +void cAudio::setBypassMode(bool disable) +{ + lt_debug("%s %d\n", __FUNCTION__, disable); + /* disable = true: audio is MPEG, disable = false: audio is AC3 */ + if (disable) + { + ioctl(fd, MPEG_AUD_SET_MODE, AUD_MODE_MPEG); + return; + } + /* dvb2001 does always set AUD_MODE_DTS before setting AUD_MODE_AC3, + this might be some workaround, so we do the same... */ + ioctl(fd, MPEG_AUD_SET_MODE, AUD_MODE_DTS); + ioctl(fd, MPEG_AUD_SET_MODE, AUD_MODE_AC3); + return; + /* all those ioctl aways return "invalid argument", but they seem to + work anyway, so there's no use in checking the return value */ +} diff --git a/libtriple/audio_td.h b/libtriple/audio_td.h new file mode 100644 index 0000000..bc320f1 --- /dev/null +++ b/libtriple/audio_td.h @@ -0,0 +1,99 @@ +/* public header file */ + +#ifndef _AUDIO_TD_H_ +#define _AUDIO_TD_H_ + +#include +#include "../common/cs_types.h" + +typedef enum +{ + AUDIO_SYNC_WITH_PTS, + AUDIO_NO_SYNC, + AUDIO_SYNC_AUDIO_MASTER +} AUDIO_SYNC_MODE; + +typedef enum { + HDMI_ENCODED_OFF, + HDMI_ENCODED_AUTO, + HDMI_ENCODED_FORCED +} HDMI_ENCODED_MODE; + +typedef enum +{ + AUDIO_FMT_AUTO = 0, + AUDIO_FMT_MPEG, + AUDIO_FMT_MP3, + AUDIO_FMT_DOLBY_DIGITAL, + AUDIO_FMT_BASIC = AUDIO_FMT_DOLBY_DIGITAL, + AUDIO_FMT_AAC, + AUDIO_FMT_AAC_PLUS, + AUDIO_FMT_DD_PLUS, + AUDIO_FMT_DTS, + AUDIO_FMT_AVS, + AUDIO_FMT_MLP, + AUDIO_FMT_WMA, + AUDIO_FMT_MPG1, // TD only. For Movieplayer / cPlayback + AUDIO_FMT_ADVANCED = AUDIO_FMT_MLP +} AUDIO_FORMAT; + +class cAudio +{ + friend class cPlayback; + private: + int fd; + bool Muted; + + int clipfd; /* for pcm playback */ + int mixer_fd; /* if we are using the OSS mixer */ + int mixer_num; /* oss mixer to use, if any */ + + AUDIO_FORMAT StreamType; + AUDIO_SYNC_MODE SyncMode; + bool started; + + int volume; + + void openDevice(void); + void closeDevice(void); + + int do_mute(bool enable, bool remember); + void setBypassMode(bool disable); + public: + /* construct & destruct */ + cAudio(void *, void *, void *); + ~cAudio(void); + + void *GetHandle() { return NULL; }; + /* shut up */ + int mute(bool remember = true) { return do_mute(true, remember); }; + int unmute(bool remember = true) { return do_mute(false, remember); }; + + /* volume, min = 0, max = 255 */ + int setVolume(unsigned int left, unsigned int right); + int getVolume(void) { return volume;} + bool getMuteStatus(void) { return Muted; }; + + /* start and stop audio */ + int Start(void); + int Stop(void); + bool Pause(bool Pcm = true); + void SetStreamType(AUDIO_FORMAT type); + void SetSyncMode(AVSYNC_TYPE Mode); + + /* select channels */ + int setChannel(int channel); + int PrepareClipPlay(int uNoOfChannels, int uSampleRate, int uBitsPerSample, int bLittleEndian); + int WriteClip(unsigned char * buffer, int size); + int StopClip(); + void getAudioInfo(int &type, int &layer, int& freq, int &bitrate, int &mode); + void SetSRS(int iq_enable, int nmgr_enable, int iq_mode, int iq_level); + bool IsHdmiDDSupported() { return false; }; + void SetHdmiDD(bool) { return; }; + void SetSpdifDD(bool enable); + void ScheduleMute(bool On); + void EnableAnalogOut(bool enable); +}; + +#endif + diff --git a/libtriple/cs_api.h b/libtriple/cs_api.h new file mode 100644 index 0000000..292430d --- /dev/null +++ b/libtriple/cs_api.h @@ -0,0 +1,66 @@ +/* compatibility header for tripledragon. I'm lazy, so I just left it + as "cs_api.h" so that I don't need too many ifdefs in the code */ + +#ifndef __CS_API_H_ +#define __CS_API_H_ + +#include "init_td.h" +typedef void (*cs_messenger) (unsigned int msg, unsigned int data); + +#if 0 +enum CS_LOG_MODULE { + CS_LOG_CI = 0, + CS_LOG_HDMI_CEC, + CS_LOG_HDMI, + CS_LOG_VIDEO, + CS_LOG_VIDEO_DRM, + CS_LOG_AUDIO, + CS_LOG_DEMUX, + CS_LOG_DENC, + CS_LOG_PVR_RECORD, + CS_LOG_PVR_PLAY, + CS_LOG_POWER_CTRL, + CS_LOG_POWER_CLK, + CS_LOG_MEM, + CS_LOG_API, +}; +#endif + +inline void cs_api_init() +{ + init_td_api(); +}; + +inline void cs_api_exit() +{ + shutdown_td_api(); +}; + +#define cs_malloc_uncached malloc +#define cs_free_uncached free + +// Callback function helpers +static inline void cs_register_messenger(cs_messenger) { return; }; +static inline void cs_deregister_messenger(void) { return; }; +//cs_messenger cs_get_messenger(void); + +#if 0 +// Logging functions +void cs_log_enable(void); +void cs_log_disable(void); +void cs_log_message(const char *prefix, const char *fmt, ...); +void cs_log_module_enable(enum CS_LOG_MODULE module); +void cs_log_module_disable(enum CS_LOG_MODULE module); +void cs_log_module_message(enum CS_LOG_MODULE module, const char *fmt, ...); + +// TS Routing +unsigned int cs_get_ts_output(void); +int cs_set_ts_output(unsigned int port); + +// Serial nr and revision accessors +unsigned long long cs_get_serial(void); +#endif +/* compat... HD1 seems to be version 6. everything newer ist > 6... */ +static inline unsigned int cs_get_revision(void) { return 1; }; +extern int cnxt_debug; +#endif //__CS_API_H_ diff --git a/libtriple/dmx_cs.h b/libtriple/dmx_cs.h new file mode 100644 index 0000000..4f0dbc1 --- /dev/null +++ b/libtriple/dmx_cs.h @@ -0,0 +1 @@ +#include "dmx_td.h" diff --git a/libtriple/dmx_td.cpp b/libtriple/dmx_td.cpp new file mode 100644 index 0000000..92242d3 --- /dev/null +++ b/libtriple/dmx_td.cpp @@ -0,0 +1,638 @@ +/* + * cDemux implementation for the Tripledragon dbs3000 receiver + * + * (C) 2010-2013 Stefan Seyfried + * + * 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 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "dmx_td.h" +#include "lt_debug.h" + +/* Ugh... see comment in destructor for details... */ +#include "video_td.h" +extern cVideo *videoDecoder; + +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_DEMUX, this, args) +#define lt_info(args...) _lt_info(TRIPLE_DEBUG_DEMUX, this, args) + +#define dmx_err(_errfmt, _errstr, _revents) do { \ + uint16_t _pid = (uint16_t)-1; uint16_t _f = 0;\ + if (dmx_type == DMX_PSI_CHANNEL) { \ + _pid = s_flt.pid; _f = s_flt.filter[0]; \ + } else { \ + _pid = p_flt.pid; \ + }; \ + lt_info("%s " _errfmt " fd:%d, ev:0x%x %s pid:0x%04hx flt:0x%02hx\n", \ + __func__, _errstr, fd, _revents, DMX_T[dmx_type], _pid, _f); \ +} while(0); + +cDemux *videoDemux = NULL; +cDemux *audioDemux = NULL; +//cDemux *pcrDemux = NULL; + +static const char *DMX_T[] = { + "DMX_INVALID", + "DMX_VIDEO", + "DMX_AUDIO", + "DMX_PES", + "DMX_PSI", + "DMX_PIP", + "DMX_TP", + "DMX_PCR" +}; + +/* map the device numbers as used to the TD devices */ +static const char *devname[] = { + "/dev/" DEVICE_NAME_DEMUX "0", + "/dev/" DEVICE_NAME_DEMUX "1", + "/dev/" DEVICE_NAME_DEMUX "2", +}; + +/* uuuugly */ +static int dmx_tp_count = 0; +#define MAX_TS_COUNT 1 + +cDemux::cDemux(int n) +{ + if (n < 0 || n > 2) + { + lt_info("%s ERROR: n invalid (%d)\n", __FUNCTION__, n); + num = 0; + } + else + num = n; + fd = -1; + measure = false; + last_measure = 0; + last_data = 0; +} + +cDemux::~cDemux() +{ + lt_debug("%s #%d fd: %d\n", __FUNCTION__, num, fd); + Close(); + /* in zapit.cpp, videoDemux is deleted after videoDecoder + * in the video watchdog, we access videoDecoder + * the thread still runs after videoDecoder has been deleted + * => set videoDecoder to NULL here to make the check in the + * watchdog thread pick this up. + * This is ugly, but it saves me from changing neutrino + * + * if the delete order in neutrino will ever be changed, this + * will blow up badly :-( + */ + if (dmx_type == DMX_VIDEO_CHANNEL) + videoDecoder = NULL; +} + +bool cDemux::Open(DMX_CHANNEL_TYPE pes_type, void * /*hVideoBuffer*/, int uBufferSize) +{ + devnum = num; + int flags = O_RDWR; + if (fd > -1) + lt_info("%s FD ALREADY OPENED? fd = %d\n", __FUNCTION__, fd); + if (pes_type == DMX_TP_CHANNEL) + { + /* see neutrino's src/gui/streaminfo2.cpp for the buffer size */ + if (num == 0 && uBufferSize == 3 * 3008 * 62) /* streaminfo measurement, let's cheat... */ + { + lt_info("%s num=0 and DMX_TP_CHANNEL => measurement demux\n", __func__); + devnum = 2; /* demux 0 is used for live, demux 1 for recording */ + measure = true; + last_measure = 0; + last_data = 0; + flags |= O_NONBLOCK; + } + else + { + /* it looks like the drivers can only do one TS at a time */ + if (dmx_tp_count >= MAX_TS_COUNT) + { + lt_info("%s too many DMX_TP_CHANNEL requests :-(\n", __FUNCTION__); + dmx_type = DMX_INVALID; + fd = -1; + return false; + } + dmx_tp_count++; + devnum = dmx_tp_count; + } + } + fd = open(devname[devnum], flags); + if (fd < 0) + { + lt_info("%s %s: %m\n", __FUNCTION__, devname[devnum]); + return false; + } + fcntl(fd, F_SETFD, FD_CLOEXEC); + lt_debug("%s #%d pes_type: %s(%d), uBufferSize: %d dev:%s fd: %d\n", __func__, + num, DMX_T[pes_type], pes_type, uBufferSize, devname[devnum] + strlen("/dev/stb/"), fd); + + dmx_type = pes_type; + + if (!pesfds.empty()) + { + lt_info("%s ERROR! pesfds not empty!\n", __FUNCTION__); /* TODO: error handling */ + return false; + } + if (pes_type == DMX_TP_CHANNEL) + { + if (measure) + return true; + struct demux_bucket_para bp; + bp.unloader.unloader_type = UNLOADER_TYPE_TRANSPORT; + bp.unloader.threshold = 128; + ioctl(fd, DEMUX_SELECT_SOURCE, INPUT_FROM_CHANNEL0); + ioctl(fd, DEMUX_SET_BUFFER_SIZE, 230400); + ioctl(fd, DEMUX_FILTER_BUCKET_SET, &bp); + return true; + } + if (uBufferSize > 0) + { + /* probably uBufferSize == 0 means "use default size". TODO: find a reasonable default */ + if (ioctl(fd, DEMUX_SET_BUFFER_SIZE, uBufferSize) < 0) + lt_info("%s DEMUX_SET_BUFFER_SIZE failed (%m)\n", __FUNCTION__); + } + buffersize = uBufferSize; + + return true; +} + +void cDemux::Close(void) +{ + lt_debug("%s #%d, fd = %d\n", __FUNCTION__, num, fd); + if (fd < 0) + { + lt_info("%s #%d: not open!\n", __FUNCTION__, num); + return; + } + + for (std::vector::const_iterator i = pesfds.begin(); i != pesfds.end(); ++i) + { + lt_debug("%s stopping and closing demux fd %d pid 0x%04x\n", __FUNCTION__, (*i).fd, (*i).pid); + if (ioctl((*i).fd, DEMUX_STOP) < 0) + perror("DEMUX_STOP"); + if (close((*i).fd) < 0) + perror("close"); + } + pesfds.clear(); + ioctl(fd, DEMUX_STOP); + close(fd); + fd = -1; + if (measure) + return; + if (dmx_type == DMX_TP_CHANNEL) + { + dmx_tp_count--; + if (dmx_tp_count < 0) + { + lt_info("%s dmx_tp_count < 0!!\n", __func__); + dmx_tp_count = 0; + } + } +} + +bool cDemux::Start(bool) +{ + if (fd < 0) + { + lt_info("%s #%d: not open!\n", __FUNCTION__, num); + return false; + } + + for (std::vector::const_iterator i = pesfds.begin(); i != pesfds.end(); ++i) + { + lt_debug("%s starting demux fd %d pid 0x%04x\n", __FUNCTION__, (*i).fd, (*i).pid); + if (ioctl((*i).fd, DEMUX_START) < 0) + perror("DEMUX_START"); + } + ioctl(fd, DEMUX_START); + return true; +} + +bool cDemux::Stop(void) +{ + if (fd < 0) + { + lt_info("%s #%d: not open!\n", __FUNCTION__, num); + return false; + } + for (std::vector::const_iterator i = pesfds.begin(); i != pesfds.end(); ++i) + { + lt_debug("%s stopping demux fd %d pid 0x%04x\n", __FUNCTION__, (*i).fd, (*i).pid); + if (ioctl((*i).fd, DEMUX_STOP) < 0) + perror("DEMUX_STOP"); + } + ioctl(fd, DEMUX_STOP); + return true; +} + +int cDemux::Read(unsigned char *buff, int len, int timeout) +{ +#if 0 + if (len != 4095 && timeout != 10) + fprintf(stderr, "cDemux::%s #%d fd: %d type: %s len: %d timeout: %d\n", + __FUNCTION__, num, fd, DMX_T[dmx_type], len, timeout); +#endif + int rc; + struct pollfd ufds; + ufds.fd = fd; + ufds.events = POLLIN; + ufds.revents = 0; + + if (measure) + { + if (timeout) + usleep(timeout * 1000); + uint64_t now; + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + now = t.tv_sec * 1000; + now += t.tv_nsec / 1000000; + if (now - last_measure < 333) + return 0; + unsigned char dummy[12]; + unsigned long long bit_s = 0; + S_STREAM_MEASURE m; + ioctl(fd, DEMUX_STOP); + rc = read(fd, dummy, 12); + lt_debug("%s measure read: %d\n", __func__, rc); + if (rc == 12) + { + ioctl(fd, DEMUX_GET_MEASURE_TIMING, &m); + if (m.rx_bytes > 0 && m.rx_time_us > 0) + { + // -- current bandwidth in kbit/sec + // --- cast to unsigned long long so it doesn't overflow as + // --- early, add time / 2 before division for correct rounding + /* the correction factor is found out like that: + - with 8000 (guessed), a 256 kbit radio stream shows as 262kbit... + - 8000*256/262 = 7816.793131 + BUT! this is only true for some Radio stations (DRS3 for example), for + others (DLF) 8000 does just fine. + bit_s = (m.rx_bytes * 7816793ULL + (m.rx_time_us / 2ULL)) / m.rx_time_us; + */ + bit_s = (m.rx_bytes * 8000ULL + (m.rx_time_us / 2ULL)) / m.rx_time_us; + if (now - last_data < 5000) + rc = bit_s * (now - last_data) / 8ULL; + else + rc = 0; + lt_debug("%s measure bit_s: %llu rc: %d timediff: %lld\n", + __func__, bit_s, rc, (now - last_data)); + last_data = now; + } else + rc = 0; + } + last_measure = now; + ioctl(fd, DEMUX_START); + return rc; + } + if (timeout > 0) + { + retry: + rc = ::poll(&ufds, 1, timeout); + if (!rc) + return 0; // timeout + else if (rc < 0) + { + dmx_err("poll: %s,", strerror(errno), 0) + //lt_info("%s poll: %m\n", __FUNCTION__); + /* happens, when running under gdb... */ + if (errno == EINTR) + goto retry; + return -1; + } + if (ufds.revents & POLLERR) /* POLLERR means buffer error, i.e. buffer overflow */ + { + dmx_err("received %s,", "POLLERR", ufds.revents); + /* this seems to happen sometimes at recording start, without bad effects */ + return 0; + } + if (ufds.revents & POLLHUP) /* we get POLLHUP if e.g. a too big DMX_BUFFER_SIZE was set */ + { + dmx_err("received %s,", "POLLHUP", ufds.revents); + return -1; + } + if (!(ufds.revents & POLLIN)) /* we requested POLLIN but did not get it? */ + { + dmx_err("received %s, please report!", "POLLIN", ufds.revents); + return 0; + } + } + + rc = ::read(fd, buff, len); + //fprintf(stderr, "fd %d ret: %d\n", fd, rc); + if (rc < 0) + dmx_err("read: %s", strerror(errno), 0); + + return rc; +} + +bool cDemux::sectionFilter(unsigned short pid, const unsigned char * const filter, + const unsigned char * const mask, int len, int timeout, + const unsigned char * const negmask) +{ + int length; + memset(&s_flt, 0, sizeof(s_flt)); + + if (len > FILTER_LENGTH - 2) + lt_info("%s #%d: len too long: %d, FILTER_LENGTH: %d\n", __func__, num, len, FILTER_LENGTH); + if (len < 1) /* memcpy below will be unhappy */ + lt_info("%s #%d: len too small: %d\n", __func__, num, len); + + length = (len + 2 + 1) & 0xfe; /* reportedly, the TD drivers don't handle odd filter */ + if (length > FILTER_LENGTH) /* lengths well. So make sure the length is a multiple */ + length = FILTER_LENGTH; /* of 2. The unused mask is zeroed anyway. */ + s_flt.pid = pid; + s_flt.filter_length = length; + s_flt.filter[0] = filter[0]; + s_flt.mask[0] = mask[0]; + s_flt.timeout = timeout; + memcpy(&s_flt.filter[3], &filter[1], len - 1); + memcpy(&s_flt.mask[3], &mask[1], len - 1); + if (negmask != NULL) + { + s_flt.positive[0] = negmask[0]; + memcpy(&s_flt.positive[3], &negmask[1], len - 1); + } + + s_flt.flags = XPDF_IMMEDIATE_START; + + int to = 0; + switch (filter[0]) { + case 0x00: /* program_association_section */ + to = 2000; + break; + case 0x01: /* conditional_access_section */ + to = 6000; + break; + case 0x02: /* program_map_section */ + to = 1500; + break; + case 0x03: /* transport_stream_description_section */ + to = 10000; + break; + /* 0x04 - 0x3F: reserved */ + case 0x40: /* network_information_section - actual_network */ + to = 10000; + break; + case 0x41: /* network_information_section - other_network */ + to = 15000; + break; + case 0x42: /* service_description_section - actual_transport_stream */ + to = 10000; + break; + /* 0x43 - 0x45: reserved for future use */ + case 0x46: /* service_description_section - other_transport_stream */ + to = 10000; + break; + /* 0x47 - 0x49: reserved for future use */ + case 0x4A: /* bouquet_association_section */ + to = 11000; + break; + /* 0x4B - 0x4D: reserved for future use */ + case 0x4E: /* event_information_section - actual_transport_stream, present/following */ + to = 2000; + break; + case 0x4F: /* event_information_section - other_transport_stream, present/following */ + to = 10000; + break; + /* 0x50 - 0x5F: event_information_section - actual_transport_stream, schedule */ + /* 0x60 - 0x6F: event_information_section - other_transport_stream, schedule */ + case 0x70: /* time_date_section */ + s_flt.flags |= (XPDF_NO_CRC); /* section has no CRC */ + //s_flt.pid = 0x0014; + to = 30000; + break; + case 0x71: /* running_status_section */ + s_flt.flags |= (XPDF_NO_CRC); /* section has no CRC */ + to = 0; + break; + case 0x72: /* stuffing_section */ + s_flt.flags |= (XPDF_NO_CRC); /* section has no CRC */ + to = 0; + break; + case 0x73: /* time_offset_section */ + //s_flt.pid = 0x0014; + to = 30000; + break; + /* 0x74 - 0x7D: reserved for future use */ + case 0x7E: /* discontinuity_information_section */ + s_flt.flags |= (XPDF_NO_CRC); /* section has no CRC */ + to = 0; + break; + case 0x7F: /* selection_information_section */ + to = 0; + break; + /* 0x80 - 0x8F: ca_message_section */ + /* 0x90 - 0xFE: user defined */ + /* 0xFF: reserved */ + default: + break; +// return -1; + } + if (timeout == 0) + s_flt.timeout = to; + + lt_debug("%s #%d pid:0x%04hx fd:%d type:%s len:%d/%d to:%d flags:%x flt[0]:%02x\n", __func__, num, + pid, fd, DMX_T[dmx_type], len,s_flt.filter_length, s_flt.timeout,s_flt.flags, s_flt.filter[0]); +#if 0 + fprintf(stderr,"filt: ");for(int i=0;i= 0x0002 && pid <= 0x000f) || pid >= 0x1fff) + return false; + + lt_debug("%s #%d pid: 0x%04hx fd: %d type: %s\n", __FUNCTION__, num, pid, fd, DMX_T[dmx_type]); + + if (dmx_type == DMX_TP_CHANNEL && !measure) + { + unsigned int n = pesfds.size(); + addPid(pid); + return (n != pesfds.size()); + } + memset(&p_flt, 0, sizeof(p_flt)); + p_flt.pid = pid; + p_flt.output = OUT_DECODER; + switch (dmx_type) { + case DMX_PCR_ONLY_CHANNEL: + p_flt.pesType = DMX_PES_PCR; + break; + case DMX_AUDIO_CHANNEL: + p_flt.pesType = DMX_PES_AUDIO; + break; + case DMX_VIDEO_CHANNEL: + p_flt.pesType = DMX_PES_VIDEO; + break; + case DMX_PES_CHANNEL: + p_flt.unloader.unloader_type = UNLOADER_TYPE_PAYLOAD; + if (buffersize <= 0x10000) // dvbsubtitle, instant delivery... + p_flt.unloader.threshold = 1; + else + p_flt.unloader.threshold = 8; // 1k, teletext + p_flt.pesType = DMX_PES_OTHER; + p_flt.output = OUT_MEMORY; + break; + case DMX_TP_CHANNEL: + /* must be measure == true or we would have returned above */ + p_flt.output = OUT_MEMORY; + p_flt.pesType = DMX_PES_OTHER; + p_flt.unloader.threshold = 1; + p_flt.unloader.unloader_type = UNLOADER_TYPE_MEASURE_DUMMY; + ioctl(fd, DEMUX_SET_MEASURE_TIME, 250000); + break; + default: + p_flt.pesType = DMX_PES_OTHER; + } + return (ioctl(fd, DEMUX_FILTER_PES_SET, &p_flt) >= 0); +} + +void cDemux::SetSyncMode(AVSYNC_TYPE /*mode*/) +{ + lt_debug("%s #%d\n", __FUNCTION__, num); +} + +void *cDemux::getBuffer() +{ + lt_debug("%s #%d\n", __FUNCTION__, num); + return NULL; +} + +void *cDemux::getChannel() +{ + lt_debug("%s #%d\n", __FUNCTION__, num); + return NULL; +} + +bool cDemux::addPid(unsigned short Pid) +{ + pes_pids pfd; + int ret; + struct demux_pes_para p; + if (dmx_type != DMX_TP_CHANNEL) + { + lt_info("%s pes_type %s not implemented yet! pid=%hx\n", __FUNCTION__, DMX_T[dmx_type], Pid); + return false; + } + if (measure) + { + lt_info("%s measurement demux -> skipping\n", __func__); + return true; + } + if (fd == -1) + lt_info("%s bucketfd not yet opened? pid=%hx\n", __FUNCTION__, Pid); + pfd.fd = open(devname[devnum], O_RDWR); + if (pfd.fd < 0) + { + lt_info("%s #%d Pid = %hx open failed (%m)\n", __FUNCTION__, num, Pid); + return false; + } + fcntl(pfd.fd, F_SETFD, FD_CLOEXEC); + lt_debug("%s #%d Pid = %hx pfd = %d\n", __FUNCTION__, num, Pid, pfd.fd); + + p.pid = Pid; + p.pesType = DMX_PES_OTHER; + p.output = OUT_NOTHING; + p.flags = 0; + p.unloader.unloader_type = UNLOADER_TYPE_BUCKET; + p.unloader.threshold = 128; + + ioctl(pfd.fd, DEMUX_SELECT_SOURCE, INPUT_FROM_CHANNEL0); + ret = ioctl(pfd.fd, DEMUX_SET_BUFFER_SIZE, 0x10000); // 64k + if (ret == -1) + perror("DEMUX_SET_BUFFER_SIZE"); + else + { + ret = ioctl(pfd.fd, DEMUX_FILTER_PES_SET, &p); + if (ret == -1) + perror("DEMUX_FILTER_PES_SET"); + } + pfd.pid = Pid; + if (ret != -1) + /* success! */ + pesfds.push_back(pfd); + else + /* error! */ + close(pfd.fd); + return (ret != -1); +} + +void cDemux::removePid(unsigned short Pid) +{ + if (dmx_type != DMX_TP_CHANNEL) + { + lt_info("%s pes_type %s not implemented yet! pid=%hx\n", __FUNCTION__, DMX_T[dmx_type], Pid); + return; + } + for (std::vector::iterator i = pesfds.begin(); i != pesfds.end(); ++i) + { + if ((*i).pid == Pid) { + lt_debug("removePid: removing demux fd %d pid 0x%04x\n", (*i).fd, Pid); + if (ioctl((*i).fd, DEMUX_STOP) < 0) + perror("DEMUX_STOP"); + if (close((*i).fd) < 0) + perror("close"); + pesfds.erase(i); + return; /* TODO: what if the same PID is there multiple times */ + } + } + lt_info("%s pid 0x%04x not found\n", __FUNCTION__, Pid); +} + +void cDemux::getSTC(int64_t * STC) +{ + lt_debug("%s #%d\n", __FUNCTION__, num); + /* this is a guess, but seems to work... int32_t gives errno 515... */ +#define STC_TYPE uint64_t + STC_TYPE stc; + if (ioctl(fd, DEMUX_GET_CURRENT_STC, &stc)) + perror("cDemux::getSTC DEMUX_GET_CURRENT_STC"); + *STC = (stc >> 32); +} + +int cDemux::getUnit(void) +{ + lt_debug("%s #%d\n", __FUNCTION__, num); + /* just guessed that this is the right thing to do. + right now this is only used by the CA code which is stubbed out + anyway */ + return num; +} diff --git a/libtriple/dmx_td.h b/libtriple/dmx_td.h new file mode 100644 index 0000000..a253f3c --- /dev/null +++ b/libtriple/dmx_td.h @@ -0,0 +1,78 @@ +#ifndef __DEMUX_TD_H +#define __DEMUX_TD_H + +#include +#include +extern "C" { +#include +#include +#include +} +#include "../common/cs_types.h" +#if defined DMX_FILTER_SIZE +#undef DMX_FILTER_SIZE +#endif +#define DMX_FILTER_SIZE FILTER_LENGTH + +#define MAX_DMX_UNITS 4 + +typedef enum +{ + DMX_INVALID = 0, + DMX_VIDEO_CHANNEL = 1, + DMX_AUDIO_CHANNEL, + DMX_PES_CHANNEL, + DMX_PSI_CHANNEL, + DMX_PIP_CHANNEL, + DMX_TP_CHANNEL, + DMX_PCR_ONLY_CHANNEL +} DMX_CHANNEL_TYPE; + +typedef struct +{ + int fd; + unsigned short pid; +} pes_pids; + +class cDemux +{ + private: + int num; + int devnum; + int fd; + int buffersize; + bool measure; + uint64_t last_measure, last_data; + DMX_CHANNEL_TYPE dmx_type; + std::vector pesfds; + struct demux_filter_para s_flt; + demux_pes_para p_flt; + public: + + bool Open(DMX_CHANNEL_TYPE pes_type, void * x = NULL, int y = 0); + void Close(void); + bool Start(bool record = false); + bool Stop(void); + int Read(unsigned char *buff, int len, int Timeout = 0); + bool sectionFilter(unsigned short pid, const unsigned char * const filter, const unsigned char * const mask, int len, int Timeout = 0, const unsigned char * const negmask = NULL); + bool pesFilter(const unsigned short pid); + void SetSyncMode(AVSYNC_TYPE mode); + void * getBuffer(); + void * getChannel(); + DMX_CHANNEL_TYPE getChannelType(void) { return dmx_type; }; + bool addPid(unsigned short pid); + void getSTC(int64_t * STC); + int getUnit(void); + /* tripledragon is unlikely to get a second tuner, so stub it out right here */ + static bool SetSource(int /*unit*/, int /*source*/) { return true; }; + static int GetSource(int /*unit*/) { return 0; }; + // TD only functions + int getFD(void) { return fd; }; /* needed by cPlayback class */ + void removePid(unsigned short Pid); /* needed by cRecord class */ + std::vector getPesPids(void) { return pesfds; }; + // + cDemux(int num = 0); + ~cDemux(); +}; + +#endif //__DEMUX_H diff --git a/libtriple/hardware_caps.c b/libtriple/hardware_caps.c new file mode 100644 index 0000000..facb1b1 --- /dev/null +++ b/libtriple/hardware_caps.c @@ -0,0 +1,30 @@ +/* + * determine the capabilities of the hardware. + * part of libstb-hal + * + * (C) 2010-2012 Stefan Seyfried + * + * License: GPL v2 or later + */ + +#include "hardware_caps.h" + +static hw_caps_t caps = { + .has_fan = 0, + .has_SCART = 1, + .has_SCART_input = 1, + .has_HDMI = 0, + .has_YUV_cinch = 0, + .can_shutdown = 0, + .can_cec = 0, + .display_type = HW_DISPLAY_GFX, + .display_xres = 128, + .display_yres = 64, + .boxvendor = "Armas", + .boxname = "TripleDragon" +}; + +hw_caps_t *get_hwcaps(void) +{ + return ∩︀ +} diff --git a/libtriple/init_cs.h b/libtriple/init_cs.h new file mode 100644 index 0000000..5894a14 --- /dev/null +++ b/libtriple/init_cs.h @@ -0,0 +1,2 @@ +#warning using init_cs.h from libtriple +#include "init_td.h" diff --git a/libtriple/init_td.cpp b/libtriple/init_td.cpp new file mode 100644 index 0000000..121297d --- /dev/null +++ b/libtriple/init_td.cpp @@ -0,0 +1,159 @@ +#include + +#include "init_td.h" +#include +#include +#include +#include +#include +#include +#include + +#include + +extern "C" { +#include +#include +#include +} +#include "lt_dfbinput.h" +#include "pwrmngr.h" + +#include "lt_debug.h" +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_INIT, NULL, args) +#define lt_info(args...) _lt_info(TRIPLE_DEBUG_INIT, NULL, args) + +static bool initialized = false; + +/* the super interface */ +IDirectFB *dfb; +/* the primary surface */ +static IDirectFBSurface *primary; +IDirectFBSurface *dfbdest; +static IDirectFBDisplayLayer *layer; +int gfxfd = -1; + +#define DFBCHECK(x...) \ + err = x; \ + if (err != DFB_OK) { \ + fprintf(stderr, "%s <%d>:\n\t", __FILE__, __LINE__ ); \ + DirectFBErrorFatal(#x, err ); \ + } + +static void dfb_init() +{ + int argc = 0; + DFBResult err; + DFBSurfaceDescription dsc; + DFBSurfacePixelFormat pixelformat; + int SW, SH; + + DFBCHECK(DirectFBInit(&argc, NULL)); + /* neutrino does its own VT handling */ + DirectFBSetOption("no-vt-switch", NULL); + DirectFBSetOption("no-vt", NULL); + /* signal handling seems to interfere with neutrino */ + DirectFBSetOption("no-sighandler", NULL); + /* if DirectFB grabs the remote, neutrino does not get events */ + /* now we handle the input via a DFB thread and push it to + * neutrino via uinput, so reenable tdremote module + DirectFBSetOption("disable-module", "tdremote"); + */ + DirectFBSetOption("disable-module", "keyboard"); + DirectFBSetOption("disable-module", "linux_input"); + DFBCHECK(DirectFBCreate(&dfb)); + + err = dfb->SetCooperativeLevel(dfb, DFSCL_FULLSCREEN); + if (err) + DirectFBError("Failed to get exclusive access", err); + + dsc.flags = DSDESC_CAPS; + dsc.caps = DSCAPS_PRIMARY; + + DFBCHECK(dfb->CreateSurface( dfb, &dsc, &primary )); + /* set pixel alpha mode */ + dfb->GetDisplayLayer(dfb, DLID_PRIMARY, &layer); + DFBCHECK(layer->SetCooperativeLevel(layer, DLSCL_EXCLUSIVE)); + DFBDisplayLayerConfig conf; + DFBCHECK(layer->GetConfiguration(layer, &conf)); + conf.flags = DLCONF_OPTIONS; + conf.options = (DFBDisplayLayerOptions)((conf.options & ~DLOP_OPACITY) | DLOP_ALPHACHANNEL); + DFBCHECK(layer->SetConfiguration(layer, &conf)); + + primary->GetPixelFormat(primary, &pixelformat); + primary->GetSize(primary, &SW, &SH); + primary->Clear(primary, 0, 0, 0, 0); + primary->GetSubSurface(primary, NULL, &dfbdest); + dfbdest->Clear(dfbdest, 0, 0, 0, 0); + + start_input_thread(dfb); +} + +static void dfb_deinit() +{ + stop_input_thread(); + dfbdest->Release(dfbdest); + primary->Release(primary); + layer->Release(layer); + dfb->Release(dfb); +} + +static void rc_init() +{ + /* set remote control address from bootloader config */ + int fd = open("/dev/stb/tdsystem", O_RDWR); + struct BIOS_CONFIG_AREA bca; + unsigned short rc_addr = 0xff; + if (ioctl(fd, IOC_AVS_GET_LOADERCONFIG, &bca) != 0) + fprintf(stderr, "%s: IOC_AVS_GET_LOADERCONFIG failed: %m\n", __FUNCTION__); + else + rc_addr = bca.ir_adrs; + close(fd); + fd = open("/dev/stb/tdremote", O_RDWR); + if (ioctl(fd, IOC_IR_SET_ADDRESS, rc_addr) < 0) + fprintf(stderr, "%s: IOC_IR_SET_ADDRESS %d failed: %m\n", __FUNCTION__, rc_addr); + /* short delay in the driver improves responsiveness and reduces spurious + "key up" events during zapping */ + //ioctl(fd, IOC_IR_SET_DELAY, 1); TODO: needs more work in rcinput + close(fd); + lt_info("%s rc_addr=0x%02hx\n", __FUNCTION__, rc_addr); +} + +void init_td_api() +{ + if (!initialized) + lt_debug_init(); + lt_info("%s begin, initialized=%d, debug=0x%02x\n", __FUNCTION__, (int)initialized, debuglevel); + if (!initialized) + { + /* leave standby early, this avoids popping noise on audio device */ + cCpuFreqManager f; + f.SetCpuFreq(0); /* CPUFREQ == 0 is the trigger for leaving standby */ + /* DirectFB does setpgid(0,0), which disconnects us from controlling terminal + and thus disables e.g. ctrl-C. work around that. */ + pid_t pid = getpgid(0); + dfb_init(); + if (setpgid(0, pid)) + perror("setpgid"); + rc_init(); + gfxfd = open("/dev/stb/tdgfx", O_RDWR); + if (gfxfd < 0) + perror("open /dev/stb/tdgfx"); + fcntl(gfxfd, F_SETFD, FD_CLOEXEC); + } + /* load the module which converts the TD tuner to a Linux-DVB frontend... */ + system("/sbin/modprobe td-dvb-frontend"); + initialized = true; + lt_info("%s end\n", __FUNCTION__); +} + +void shutdown_td_api() +{ + lt_info("%s, initialized = %d\n", __FUNCTION__, (int)initialized); + if (initialized) + dfb_deinit(); + if (gfxfd > -1) + close(gfxfd); + gfxfd = -1; + initialized = false; +} diff --git a/libtriple/init_td.h b/libtriple/init_td.h new file mode 100644 index 0000000..d9a6f09 --- /dev/null +++ b/libtriple/init_td.h @@ -0,0 +1,5 @@ +#ifndef __INIT_TD_H +#define __INIT_TD_H +void init_td_api(); +void shutdown_td_api(); +#endif diff --git a/libtriple/lt_dfbinput.cpp b/libtriple/lt_dfbinput.cpp new file mode 100644 index 0000000..6000ff8 --- /dev/null +++ b/libtriple/lt_dfbinput.cpp @@ -0,0 +1,367 @@ +/* + * Simulate a linux input device via uinput + * Get td remote events via DirectFB and inject them via uinput + * + * (C) 2012 Stefan Seyfried + * + * 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 2 + * 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 . + */ + +/* the C++ compiler does not like this code, so let's put it into a + * separate file and compile with gcc insead of g++... + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include "lt_dfbinput.h" + +/* needed for videodecoder watchdog */ +#include "video_td.h" +extern cVideo *videoDecoder; + +/* same defines as in neutrino's rcinput.h */ +#define KEY_TTTV KEY_FN_1 +#define KEY_TTZOOM KEY_FN_2 +#define KEY_REVEAL KEY_FN_D +/* only defined in newer kernels / headers... */ +#ifndef KEY_ZOOMIN +#define KEY_ZOOMIN KEY_FN_E +#endif +#ifndef KEY_ZOOMOUT +#define KEY_ZOOMOUT KEY_FN_F +#endif + +#define DFBCHECK(x...) \ + err = x; \ + if (err != DFB_OK) { \ + fprintf(stderr, "%s <%d>:\n\t", __FILE__, __LINE__ ); \ + DirectFBErrorFatal(#x, err ); \ + } + +typedef struct _DeviceInfo DeviceInfo; +struct _DeviceInfo { + DFBInputDeviceID device_id; + DFBInputDeviceDescription desc; + DeviceInfo *next; +}; + +static const int key_list[] = { + KEY_0, + KEY_1, + KEY_2, + KEY_3, + KEY_4, + KEY_5, + KEY_6, + KEY_7, + KEY_8, + KEY_9, + KEY_OK, + KEY_TIME, + KEY_FAVORITES, + KEY_ZOOMOUT, + KEY_ZOOMIN, + KEY_NEXT, + KEY_POWER, + KEY_MUTE, + KEY_MENU, + KEY_EPG, + KEY_INFO, + KEY_EXIT, + KEY_PAGEUP, + KEY_PAGEDOWN, + KEY_LEFT, + KEY_RIGHT, + KEY_UP, + KEY_DOWN, + KEY_VOLUMEUP, + KEY_VOLUMEDOWN, + KEY_RED, + KEY_GREEN, + KEY_YELLOW, + KEY_BLUE, + KEY_TV, + KEY_VIDEO, + KEY_AUDIO, + KEY_AUX, + KEY_TEXT, + KEY_TTTV, + KEY_TTZOOM, + KEY_REVEAL, + KEY_REWIND, + KEY_STOP, + KEY_PAUSE, + KEY_FORWARD, +/* KEY_PREV, */ + KEY_EJECTCD, + KEY_RECORD, +/* KEY_NEXT, */ + -1 +}; + +static IDirectFBEventBuffer *events; +static DeviceInfo *inputs = NULL; + +static pthread_t thread; +static int thread_running; + +static DFBEnumerationResult enum_input_device(DFBInputDeviceID device_id, + DFBInputDeviceDescription desc, + void *data) +{ + DeviceInfo **devices = (DeviceInfo **)data; + DeviceInfo *device; + + device = (DeviceInfo *)malloc(sizeof(DeviceInfo)); + + device->device_id = device_id; + device->desc = desc; + device->next = *devices; + + *devices = device; + + return DFENUM_OK; +} + +static void *input_thread(void *data) +{ + int uinput; + int i; + struct input_event u; + struct uinput_user_dev ud; + FILE *f; + + DFBResult err; + IDirectFB *dfb = (IDirectFB *)data; + fprintf(stderr, "DFB input converter thread starting...\n"); + + /* modprobe does not complain if the module is already loaded... */ + system("/sbin/modprobe uinput"); + system("/sbin/modprobe evdev"); + uinput = open("/dev/misc/uinput", O_WRONLY|O_NDELAY); + if (uinput < 0) + { + fprintf(stderr, "DFB input thread: unable to open /dev/misc/uinput (%m)\n"); + return NULL; + } + + memset(&u, 0, sizeof(u)); + fcntl(uinput, F_SETFD, FD_CLOEXEC); + + /* configure the device */ + memset(&ud, 0, sizeof(ud)); + strncpy(ud.name, "Neutrino TD to Input Device converter", UINPUT_MAX_NAME_SIZE); + ud.id.version = 0x42; + ud.id.vendor = 0x1234; + ud.id.product = 0x5678; + ud.id.bustype = BUS_I2C; /* ?? */ + write(uinput, &ud, sizeof(ud)); + ioctl(uinput, UI_SET_EVBIT, EV_KEY); + ioctl(uinput, UI_SET_EVBIT, EV_REP); + /* register keys */ + for (i = 0; key_list[i] != -1; i++) + ioctl(uinput, UI_SET_KEYBIT, key_list[i]); + + if (ioctl(uinput, UI_DEV_CREATE)) + { + perror("DFB input thread UI_DEV_CREATE"); + close(uinput); + return NULL; + } + + /* this is ugly: parse the new input device from /proc/...devices + * and symlink it to /dev/input/nevis_ir... */ +#define DEVLINE "I: Bus=0018 Vendor=1234 Product=5678 Version=0042" + f = fopen("/proc/bus/input/devices", "r"); + if (f) + { + int found = 0; + int evdev = -1; + size_t n = 0; + char *line = NULL; + char *p; + char newdev[20]; + while (getline(&line, &n, f) != -1) + { + switch(line[0]) + { + case 'I': + if (strncmp(line, DEVLINE, strlen(DEVLINE)) == 0) + found = 1; + break; + case 'H': + if (! found) + break; + p = strstr(line, " event"); + if (! p) + { + evdev = -1; + break; + } + evdev = atoi(p + 6); + sprintf(newdev, "event%d", evdev); + fprintf(stderr, "DFB input thread: symlink /dev/input/nevis_ir to %s\n", newdev); + unlink("/dev/input/nevis_ir"); + symlink(newdev, "/dev/input/nevis_ir"); + break; + default: + break; + } + if (evdev != -1) + break; + } + fclose(f); + free(line); + } + + u.type = EV_KEY; + u.value = 0; /* initialize: first event wil be a key press */ + + dfb->EnumInputDevices(dfb, enum_input_device, &inputs); + DFBCHECK(dfb->CreateInputEventBuffer(dfb, DICAPS_ALL, DFB_FALSE, &events)); + + thread_running = 1; + while (thread_running) + { + /* check every 250ms (if a key is pressed on remote, we might + * even check earlier, but it does not really hurt... */ + if (videoDecoder) + videoDecoder->VideoParamWatchdog(); + + if (events->WaitForEventWithTimeout(events, 0, 250) == DFB_TIMEOUT) + continue; + DFBInputEvent e; + while (events->GetEvent(events, DFB_EVENT(&e)) == DFB_OK) + { +#if 0 + fprintf(stderr, "type: %x devid: %x flags: %03x " + "key_id: %4x key_sym: %4x keycode: %d\n", + e.type, e.device_id, e.flags, + e.key_id, e.key_symbol, e.key_code); +#endif + switch (e.key_symbol) + { + /* will a lookup table be more efficient? */ + case 0x0030: u.code = KEY_0; break; + case 0x0031: u.code = KEY_1; break; + case 0x0032: u.code = KEY_2; break; + case 0x0033: u.code = KEY_3; break; + case 0x0034: u.code = KEY_4; break; + case 0x0035: u.code = KEY_5; break; + case 0x0036: u.code = KEY_6; break; + case 0x0037: u.code = KEY_7; break; + case 0x0038: u.code = KEY_8; break; + case 0x0039: u.code = KEY_9; break; + case 0x000d: u.code = KEY_OK; break; + case 0xf504: u.code = KEY_TIME; break; + case 0xf01a: u.code = KEY_FAVORITES; break; /* blue heart */ + case 0xf021: u.code = KEY_ZOOMOUT; break; + case 0xf022: u.code = KEY_ZOOMIN; break; + case 0xf505: u.code = KEY_NEXT; break; /* red hand */ + case 0xf00f: u.code = KEY_POWER; break; + case 0xf04e: u.code = KEY_MUTE; break; + case 0xf012: u.code = KEY_MENU; break; + case 0xf01b: u.code = KEY_EPG; break; + case 0xf014: u.code = KEY_INFO; break; + case 0x001b: u.code = KEY_EXIT; break; + case 0xf046: u.code = KEY_PAGEUP; break; + case 0xf047: u.code = KEY_PAGEDOWN; break; + case 0xf000: u.code = KEY_LEFT; break; + case 0xf001: u.code = KEY_RIGHT; break; + case 0xf002: u.code = KEY_UP; break; + case 0xf003: u.code = KEY_DOWN; break; + case 0xf04c: u.code = KEY_VOLUMEUP; break; + case 0xf04d: u.code = KEY_VOLUMEDOWN; break; + case 0xf042: u.code = KEY_RED; break; + case 0xf043: u.code = KEY_GREEN; break; + case 0xf044: u.code = KEY_YELLOW; break; + case 0xf045: u.code = KEY_BLUE; break; + case 0xf027: u.code = KEY_TV; break; + case 0xf035: u.code = KEY_VIDEO; break; + case 0xf033: u.code = KEY_AUDIO; break; + case 0xf034: u.code = KEY_AUX; break; + case 0xf032: u.code = KEY_TEXT; break; + case 0xf501: u.code = KEY_TTTV; break; + case 0xf502: u.code = KEY_TTZOOM; break; + case 0xf503: u.code = KEY_REVEAL; break; + case 0xf059: u.code = KEY_REWIND; break; + case 0xf052: u.code = KEY_STOP; break; + case 0xf051: u.code = KEY_PAUSE; break; + case 0xf05a: u.code = KEY_FORWARD; break; + /* case 0xf05b: u.code = KEY_PREV; break; */ + case 0xf057: u.code = KEY_EJECTCD; break; + case 0xf056: u.code = KEY_RECORD; break; + /* case 0xf05c: u.code = KEY_NEXT; break; */ + /* front panel left / right */ + case 0xf506: u.code = KEY_LEFT; break; + case 0xf507: u.code = KEY_RIGHT; break; + default: + continue; + } + switch (e.type) + { + case 1: u.value = 1; break; /* 1 = key press */ + case 2: u.value = 0; break; /* 0 = key release */ + /* 2 = key repeat (not used) */ + default: + continue; + } + // fprintf(stderr, "uinput write: value: %d code: %d\n", u.value, u.code); + write(uinput, &u, sizeof(u)); + } + } + /* clean up */ + ioctl(uinput, UI_DEV_DESTROY); + while (inputs) { + DeviceInfo *next = inputs->next; + free(inputs); + inputs = next; + } + events->Release(events); + return NULL; +} + +void start_input_thread(IDirectFB *dfb) +{ + if (pthread_create(&thread, 0, input_thread, dfb) != 0) + { + perror("DFB input thread pthread_create"); + thread_running = 0; + return; + } + /* wait until the device is created before continuing */ + while (! thread_running) + usleep(1000); +} + +void stop_input_thread(void) +{ + if (! thread_running) + return; + thread_running = 0; + pthread_join(thread, NULL); +} diff --git a/libtriple/lt_dfbinput.h b/libtriple/lt_dfbinput.h new file mode 100644 index 0000000..1a74fb2 --- /dev/null +++ b/libtriple/lt_dfbinput.h @@ -0,0 +1,7 @@ +/* functions from lt_dfbinput.c */ + +#ifndef __LT_DFB_INPUT_H_ +#define __LT_DFB_INPUT_H_ +void start_input_thread(IDirectFB *dfb); +void stop_input_thread(void); +#endif diff --git a/libtriple/mmi.h b/libtriple/mmi.h new file mode 100644 index 0000000..76ff992 --- /dev/null +++ b/libtriple/mmi.h @@ -0,0 +1,23 @@ +#ifndef __MMI_H_ +#define __MMI_H_ + +#define MAX_MMI_ITEMS 40 +#define MAX_MMI_TEXT_LEN 255 +#define MAX_MMI_CHOICE_TEXT_LEN 255 + +typedef struct { + int choice_nb; + char title[MAX_MMI_TEXT_LEN]; + char subtitle[MAX_MMI_TEXT_LEN]; + char bottom[MAX_MMI_TEXT_LEN]; + char choice_item[MAX_MMI_ITEMS][MAX_MMI_CHOICE_TEXT_LEN]; +} MMI_MENU_LIST_INFO; + +typedef struct { + int blind; + int answerlen; + char enguiryText[MAX_MMI_TEXT_LEN]; +} MMI_ENGUIRY_INFO; + +#endif // __MMI_H_ + diff --git a/libtriple/playback.h b/libtriple/playback.h new file mode 100644 index 0000000..6e6b4c5 --- /dev/null +++ b/libtriple/playback.h @@ -0,0 +1 @@ +#include "playback_td.h" diff --git a/libtriple/playback_td.cpp b/libtriple/playback_td.cpp new file mode 100644 index 0000000..2fee33a --- /dev/null +++ b/libtriple/playback_td.cpp @@ -0,0 +1,1501 @@ +#include +#include +#include +#include +#include +#include + +#include +#include "playback_td.h" +#include "dmx_td.h" +#include "audio_td.h" +#include "video_td.h" +#include "lt_debug.h" +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_PLAYBACK, this, args) +#define lt_info(args...) _lt_info(TRIPLE_DEBUG_PLAYBACK, this, args) +#define lt_info_c(args...) _lt_info(TRIPLE_DEBUG_PLAYBACK, NULL, args) + +#include +#define DVR "/dev/" DEVICE_NAME_PVR + +static int mp_syncPES(uint8_t *, int, bool quiet = false); +static int sync_ts(uint8_t *, int); +static inline uint16_t get_pid(uint8_t *buf); +static void *start_playthread(void *c); +static void playthread_cleanup_handler(void *); + +static pthread_cond_t playback_ready_cond = PTHREAD_COND_INITIALIZER; +static pthread_mutex_t playback_ready_mutex = PTHREAD_MUTEX_INITIALIZER; + +static pthread_mutex_t currpos_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t inbufpos_mutex = PTHREAD_MUTEX_INITIALIZER; + +static int dvrfd = -1; +static int streamtype; + +extern cDemux *videoDemux; +extern cDemux *audioDemux; +extern cVideo *videoDecoder; +extern cAudio *audioDecoder; + +static const char *FILETYPE[] = { + "FILETYPE_UNKNOWN", + "FILETYPE_TS", + "FILETYPE_MPG", + "FILETYPE_VDR" +}; + +cPlayback::cPlayback(int) +{ + lt_debug("%s\n", __FUNCTION__); + thread_started = false; + inbuf = NULL; + pesbuf = NULL; + filelist.clear(); + curr_fileno = -1; + in_fd = -1; + streamtype = 0; +} + +cPlayback::~cPlayback() +{ + lt_debug("%s\n", __FUNCTION__); + Close(); +} + + +bool cPlayback::Open(playmode_t mode) +{ + static const char *PMODE[] = { + "PLAYMODE_TS", + "PLAYMODE_FILE" + }; + + lt_debug("%s: PlayMode = %s\n", __FUNCTION__, PMODE[mode]); + thread_started = false; + playMode = mode; + filetype = FILETYPE_TS; + playback_speed = 0; + last_size = 0; + _pts_end = 0; + astreams.clear(); + memset(&cc, 0, 256); + return true; +} + +//Used by Fileplay +void cPlayback::Close(void) +{ + lt_info("%s\n", __FUNCTION__); + playstate = STATE_STOP; + if (thread_started) + { + lt_info("%s: before pthread_join\n", __FUNCTION__); + pthread_join(thread, NULL); + } + thread_started = false; + lt_info("%s: after pthread_join\n", __FUNCTION__); + mf_close(); + filelist.clear(); + + if (inbuf) + free(inbuf); + inbuf = NULL; + if (pesbuf) + free(pesbuf); + pesbuf = NULL; + + /* don't crash */ + if (audioDecoder) + audioDecoder->do_mute(audioDecoder->Muted, false); +} + +bool cPlayback::Start(char *filename, unsigned short vp, int vtype, unsigned short ap, int _ac3, unsigned int) +{ + struct stat s; + off_t r; + vpid = vp; + apid = ap; + ac3 = _ac3; + lt_info("%s name = '%s' vpid 0x%04hx vtype %d apid 0x%04hx ac3 %d filelist.size: %u\n", + __FUNCTION__, filename, vpid, vtype, apid, ac3, filelist.size()); + if (!filelist.empty()) + { + lt_info("filelist not empty?\n"); + return false; + } + if (stat(filename, &s)) + { + lt_info("filename does not exist? (%m)\n"); + return false; + } + if (!inbuf) + inbuf = (uint8_t *)malloc(INBUF_SIZE); /* 256 k */ + if (!inbuf) + { + lt_info("allocating input buffer failed (%m)\n"); + return false; + } + if (!pesbuf) + pesbuf = (uint8_t *)malloc(PESBUF_SIZE); /* 128 k */ + if (!pesbuf) + { + lt_info("allocating PES buffer failed (%m)\n"); + return false; + } + filelist_t file; + file.Name = std::string(filename); + file.Size = s.st_size; + if (file.Name.rfind(".ts") == file.Name.length() - 3 || + file.Name.rfind(".TS") == file.Name.length() - 3) + filetype = FILETYPE_TS; + else + { + if (file.Name.rfind(".vdr") == file.Name.length() - 4) + { + filetype = FILETYPE_VDR; + std::string::size_type p = file.Name.rfind("info.vdr"); + if (p == std::string::npos) + p = file.Name.rfind("index.vdr"); + if (p != std::string::npos) + { + file.Name.replace(p, std::string::npos, "001.vdr"); + lt_info("replaced filename with '%s'\n", file.Name.c_str()); + if (stat(file.Name.c_str(), &s)) + { + lt_info("filename does not exist? (%m)\n"); + return false; + } + file.Size = s.st_size; + } + } + else + filetype = FILETYPE_MPG; + vpid = 0x40; + } + + lt_info("detected (ok, guessed) filetype: %s\n", FILETYPE[filetype]); + + filelist.push_back(file); + filelist_auto_add(); + if (mf_open(0) < 0) + return false; + + pts_start = pts_end = pts_curr = -1; + pesbuf_pos = 0; + curr_pos = 0; + inbuf_pos = 0; + inbuf_sync = 0; + r = mf_getsize(); + + if (r > INBUF_SIZE) + { + if (mp_seekSync(r - INBUF_SIZE) < 0) + return false; + while(true) { + if (inbuf_read() <= 0) + break; // EOF + if (curr_pos >= r) //just to make sure... + break; + } + if (filetype == FILETYPE_TS) + for (r = (inbuf_pos / 188) * 188; r > 0; r -= 188) + { + pts_end = get_pts(inbuf + r, false, inbuf_pos - r); + if (pts_end > -1) + break; + } + else + pts_end = pts_curr; + } + else + pts_end = -1; /* unknown */ + + if (mp_seekSync(0) < 0) + return false; + + pesbuf_pos = 0; + inbuf_pos = 0; + inbuf_sync = 0; + while (inbuf_pos < INBUF_SIZE / 2 && inbuf_read() > 0) {}; + for (r = 0; r < inbuf_pos - 188; r += 188) + { + pts_start = get_pts(inbuf + r, false, inbuf_pos - r); + if (pts_start > -1) + break; + } + pts_curr = pts_start; + bytes_per_second = -1; + if (pts_end != -1 && pts_start > pts_end) /* PTS overflow during this file */ + pts_end += 0x200000000ULL; + int duration = (pts_end - pts_start) / 90000; + if (duration > 0) + bytes_per_second = mf_getsize() / duration; + lt_info("start: %lld end %lld duration %d bps %lld\n", pts_start, pts_end, duration, bytes_per_second); + /* yes, we start in pause mode... */ + playback_speed = 0; + if (pts_start == -1) + playstate = STATE_INIT; + else + playstate = STATE_PAUSE; + pthread_mutex_lock(&playback_ready_mutex); + if (pthread_create(&thread, 0, start_playthread, this) != 0) + lt_info("pthread_create failed\n"); + else + pthread_cond_wait(&playback_ready_cond, &playback_ready_mutex); + pthread_mutex_unlock(&playback_ready_mutex); + return true; +} + +static void *start_playthread(void *c) +{ + cPlayback *obj = (cPlayback *)c; + obj->playthread(); + return NULL; +} + +void cPlayback::playthread(void) +{ + thread_started = true; + int ret, towrite; + dvrfd = open(DVR, O_WRONLY); + if (dvrfd < 0) + { + lt_info("%s open tdpvr failed: %m\n", __FUNCTION__); + pthread_exit(NULL); + } + fcntl(dvrfd, F_SETFD, FD_CLOEXEC); + + pthread_cleanup_push(playthread_cleanup_handler, 0); + + ioctl(audioDemux->getFD(), DEMUX_SELECT_SOURCE, INPUT_FROM_PVR); + if (ac3) + audioDecoder->SetStreamType(AUDIO_FMT_DOLBY_DIGITAL); + else + { + if (streamtype == 1) /* mpeg 1 */ + audioDecoder->SetStreamType(AUDIO_FMT_MPG1); + else /* default */ + audioDecoder->SetStreamType(AUDIO_FMT_MPEG); + } + + audioDemux->pesFilter(apid); + videoDemux->pesFilter(vpid); + +// audioDemux->Start(); + videoDemux->Start(); + +// videoDecoder->setBlank(1); +// videoDecoder->Start(); +// audioDecoder->Start(); + /* everything is set up now, signal ::Start() that it can return */ + pthread_mutex_lock(&playback_ready_mutex); + pthread_cond_broadcast(&playback_ready_cond); + pthread_mutex_unlock(&playback_ready_mutex); + + while (playstate != STATE_STOP) + { + if (playstate == STATE_INIT) + { + /* hack for timeshift to determine start PTS */ + pthread_mutex_lock(&inbufpos_mutex); + ret = inbuf_read(); + pthread_mutex_unlock(&inbufpos_mutex); + if (ret < 0) + break; + usleep(100000); + if (pts_start == -1) + continue; + } + + if (playback_speed == 0) + { + playstate = STATE_PAUSE; + usleep(1); + continue; + } + pthread_mutex_lock(&inbufpos_mutex); + ret = inbuf_read(); + pthread_mutex_unlock(&inbufpos_mutex); + if (ret < 0) + break; + + /* autoselect PID for PLAYMODE_FILE */ + if (apid == 0 && astreams.size() > 0) + { + for (std::map::iterator aI = astreams.begin(); aI != astreams.end(); aI++) + { + if (!aI->second.ac3) + { + apid = aI->first; + lt_info("%s setting Audio pid to 0x%04hx\n", __FUNCTION__, apid); + SetAPid(apid, 0); + break; + } + } + } + + pthread_mutex_lock(&inbufpos_mutex); + towrite = inbuf_pos / 188 * 188; /* TODO: smaller chunks? */ + if (towrite == 0) + { + pthread_mutex_unlock(&inbufpos_mutex); + continue; + } + retry: + ret = write(dvrfd, inbuf, towrite); + if (ret < 0) + { + if (errno == EAGAIN && playstate != STATE_STOP) + goto retry; + lt_info("%s write dvr failed: %m\n", __FUNCTION__); + break; + } + memmove(inbuf, inbuf + ret, inbuf_pos - ret); + inbuf_pos -= ret; + pthread_mutex_unlock(&inbufpos_mutex); + } + + pthread_cleanup_pop(1); + pthread_exit(NULL); +} + +static void playthread_cleanup_handler(void *) +{ + lt_info_c("%s\n", __FUNCTION__); + ioctl(audioDemux->getFD(), DEMUX_SELECT_SOURCE, INPUT_FROM_CHANNEL0); + audioDemux->Stop(); + videoDemux->Stop(); + audioDecoder->Stop(); + videoDecoder->Stop(); + close(dvrfd); + dvrfd = -1; +} + +bool cPlayback::SetAPid(unsigned short pid, int _ac3) +{ + lt_info("%s pid: 0x%04hx ac3: %d\n", __FUNCTION__, pid, _ac3); + apid = pid; + ac3 = _ac3; + + audioDemux->Stop(); + audioDecoder->Stop(); + videoDemux->Stop(); + videoDecoder->Stop(false); + + if (ac3) + audioDecoder->SetStreamType(AUDIO_FMT_DOLBY_DIGITAL); + else + { + if (streamtype == 1) /* mpeg 1 */ + audioDecoder->SetStreamType(AUDIO_FMT_MPG1); + else /* default */ + audioDecoder->SetStreamType(AUDIO_FMT_MPEG); + } + audioDemux->pesFilter(apid); + + videoDemux->Start(); + audioDemux->Start(); + audioDecoder->Start(); + videoDecoder->Start(); + return true; +} + +bool cPlayback::SetSpeed(int speed) +{ + lt_info("%s speed = %d\n", __FUNCTION__, speed); + if (speed < 0) + speed = 1; /* fast rewind not yet implemented... */ + if (speed == 1 && playback_speed != 1) + { + if (playback_speed == 0) + { + videoDemux->Stop(); + videoDemux->Start(); + audioDemux->Start(); + } + else + { + audioDecoder->Stop(); + videoDecoder->Stop(); + } + audioDecoder->Start(); + videoDecoder->Start(); + playstate = STATE_PLAY; + /* cPlayback is a friend of cAudio and can use private methods */ + audioDecoder->do_mute(audioDecoder->Muted, false); + } + if (playback_speed == 1 && speed > 1) + { + audioDecoder->mute(false); + videoDecoder->FastForwardMode(); + } + playback_speed = speed; + if (playback_speed == 0) + { + audioDecoder->Stop(); + audioDemux->Stop(); + videoDecoder->Stop(false); + } + return true; +} + +bool cPlayback::GetSpeed(int &speed) const +{ + lt_debug("%s\n", __FUNCTION__); + speed = playback_speed; + return true; +} + +// in milliseconds +bool cPlayback::GetPosition(int &position, int &duration) +{ + int64_t tmppts; + lt_debug("%s\n", __FUNCTION__); + off_t currsize = mf_getsize(); + bool update = false; + /* handle a growing file, e.g. for timeshift. + this might be pretty expensive... */ + if (filetype == FILETYPE_TS && filelist.size() == 1) + { + off_t tmppos = currsize - PESBUF_SIZE; + if (currsize > last_size && (currsize - last_size) < 10485760 && + bytes_per_second > 0 && _pts_end > 0) + { + /* guess the current endpts... */ + tmppts = (currsize - last_size) * 90000 / bytes_per_second; + pts_end = _pts_end + tmppts; + } + else if (currsize != last_size && tmppos > 0) + { + pthread_mutex_lock(&currpos_mutex); + off_t oldpos = curr_pos; + ssize_t n, r; + int s; + mf_lseek(tmppos); + n = read(in_fd, pesbuf, PESBUF_SIZE); /* abuse the pesbuf... */ + s = sync_ts(pesbuf, n); + if (s >= 0) + { + n -= s; + for (r = (n / 188) * 188; r > 0; r -= 188) + { + tmppts = get_pts(pesbuf + r + s, false, n - r); + if (tmppts > -1) + { + lt_debug("n: %d s: %d endpts %lld size: %lld\n", n, s, tmppts, currsize); + pts_end = tmppts; + _pts_end = tmppts; + update = true; + /* file size has changed => update endpts */ + last_size = currsize; + break; + } + } + } + mf_lseek(oldpos); + pthread_mutex_unlock(&currpos_mutex); + } + } + if (pts_end != -1 && pts_start > pts_end) /* should trigger only once ;) */ + { + pts_end += 0x200000000ULL; + update = true; + } + + if (pts_curr != -1 && pts_curr < pts_start) + tmppts = pts_curr + 0x200000000ULL - pts_start; + else + tmppts = pts_curr - pts_start; + if (pts_end != -1 && pts_curr != -1) + { + position = tmppts / 90; + duration = (pts_end - pts_start) / 90; + if (update && duration >= 4000) + { + bytes_per_second = currsize / (duration / 1000); + lt_debug("%s: updated bps: %lld size: %lld duration %d\n", + __FUNCTION__, bytes_per_second, currsize, duration); + } + return true; + } + position = 0; + duration = 0; + return false; +} + +bool cPlayback::SetPosition(int position, bool absolute) +{ + lt_info("%s pos = %d abs = %d\n", __FUNCTION__, position, absolute); + int currpos, target, duration, oldspeed; + bool ret; + + if (absolute) + target = position; + else + { + GetPosition(currpos, duration); + target = currpos + position; + lt_info("current position %d target %d\n", currpos, target); + } + + oldspeed = playback_speed; +// if (oldspeed != 0) + SetSpeed(0); /* request pause */ + + while (playstate == STATE_PLAY) /* playthread did not acknowledge pause */ + usleep(1); + if (playstate == STATE_STOP) /* we did get stopped by someone else */ + return false; + + ret = (seek_to_pts(target * 90) > 0); + + if (oldspeed != 0) + { + SetSpeed(oldspeed); + /* avoid ugly artifacts */ + videoDecoder->Stop(); + videoDecoder->Start(); + } + return ret; +} + +void cPlayback::FindAllPids(uint16_t *apids, unsigned short *ac3flags, uint16_t *numpida, std::string *language) +{ + lt_info("%s\n", __FUNCTION__); + int i = 0; + for (std::map::iterator aI = astreams.begin(); aI != astreams.end(); aI++) + { + apids[i] = aI->first; + ac3flags[i] = aI->second.ac3 ? 1 : 0; + language[i] = aI->second.lang; + i++; + if (i > 10) /* REC_MAX_APIDS in vcrcontrol.h */ + break; + } + *numpida = i; +} + +/* it is unlikely that subtitle support will be implemented soon */ +void cPlayback::FindAllSubs(uint16_t *, unsigned short *, uint16_t *num, std::string *) +{ + *num = 0; +} + +bool cPlayback::SelectSubtitles(int) +{ + return false; +} + +/* DVD support is also unlikely... */ +void cPlayback::GetChapters(std::vector &positions, std::vector &titles) +{ + positions.clear(); + titles.clear(); +} + +off_t cPlayback::seek_to_pts(int64_t pts) +{ + off_t newpos = curr_pos; + int64_t tmppts, ptsdiff; + int count = 0; + if (pts_start < 0 || pts_end < 0 || bytes_per_second < 0) + { + lt_info("%s pts_start (%lld) or pts_end (%lld) or bytes_per_second (%lld) not initialized\n", + __FUNCTION__, pts_start, pts_end, bytes_per_second); + return -1; + } + /* sanity check: buffer is without locking, so we must only seek while in pause mode */ + if (playstate != STATE_PAUSE) + { + lt_info("%s playstate (%d) != STATE_PAUSE, not seeking\n", __FUNCTION__, playstate); + return -1; + } + + /* tmppts is normalized current pts */ + if (pts_curr < pts_start) + tmppts = pts_curr + 0x200000000ULL - pts_start; + else + tmppts = pts_curr - pts_start; + while (abs(pts - tmppts) > 90000LL && count < 10) + { + count++; + ptsdiff = pts - tmppts; + newpos += ptsdiff * bytes_per_second / 90000; + lt_info("%s try #%d seek from %lldms to %lldms dt %lldms pos %lldk newpos %lldk kB/s %lld\n", + __FUNCTION__, count, tmppts / 90, pts / 90, ptsdiff / 90, curr_pos / 1024, newpos / 1024, bytes_per_second / 1024); + if (newpos < 0) + newpos = 0; + newpos = mp_seekSync(newpos); + if (newpos < 0) + return newpos; + pthread_mutex_lock(&inbufpos_mutex); + inbuf_pos = 0; + inbuf_sync = 0; + while (inbuf_pos < INBUF_SIZE * 8 / 10) { + if (inbuf_read() <= 0) + break; // EOF + } + pthread_mutex_unlock(&inbufpos_mutex); + if (pts_curr < pts_start) + tmppts = pts_curr + 0x200000000ULL - pts_start; + else + tmppts = pts_curr - pts_start; + } + lt_info("%s end after %d tries, ptsdiff now %lld sec\n", __FUNCTION__, count, (pts - tmppts) / 90000); + return newpos; +} + +bool cPlayback::filelist_auto_add() +{ + if (filelist.size() != 1) + return false; + + const char *filename = filelist[0].Name.c_str(); + const char *ext; + ext = strrchr(filename, '.'); // FOO-xxx-2007-12-31.001.ts <- the dot before "ts" + // 001.vdr <- the dot before "vdr" + // check if there is something to do... + if (! ext) + return false; + if (!((ext - 7 >= filename && !strcmp(ext, ".ts") && *(ext - 4) == '.') || + (ext - 4 >= filename && !strcmp(ext, ".vdr")))) + return false; + + int num = 0; + struct stat s; + size_t numpos = strlen(filename) - strlen(ext) - 3; + sscanf(filename + numpos, "%d", &num); + do { + num++; + char nextfile[strlen(filename) + 1]; /* todo: use fixed buffer? */ + memcpy(nextfile, filename, numpos); + sprintf(nextfile + numpos, "%03d%s", num, ext); + if (stat(nextfile, &s)) + break; // file does not exist + filelist_t file; + file.Name = std::string(nextfile); + file.Size = s.st_size; + lt_info("%s auto-adding '%s' to playlist\n", __FUNCTION__, nextfile); + filelist.push_back(file); + } while (true && num < 999); + + return (filelist.size() > 1); +} + +/* the mf_* functions are wrappers for multiple-file I/O */ +int cPlayback::mf_open(int fileno) +{ + if (filelist.empty()) + return -1; + + if (fileno >= (int)filelist.size()) + return -1; + + mf_close(); + + in_fd = open(filelist[fileno].Name.c_str(), O_RDONLY); + fcntl(in_fd, F_SETFD, FD_CLOEXEC); + if (in_fd != -1) + curr_fileno = fileno; + + return in_fd; +} + +int cPlayback::mf_close(void) +{ + int ret = 0; + lt_info("%s in_fd = %d curr_fileno = %d\n", __FUNCTION__, in_fd, curr_fileno); + if (in_fd != -1) + ret = close(in_fd); + in_fd = curr_fileno = -1; + + return ret; +} + +off_t cPlayback::mf_getsize(void) +{ + off_t ret = 0; + if (filelist.size() == 1 && in_fd != -1) + { + /* for timeshift, we need to deal with a growing file... */ + struct stat st; + if (fstat(in_fd, &st) == 0) + return st.st_size; + /* else, fallback to filelist.size() */ + } + for (unsigned int i = 0; i < filelist.size(); i++) + ret += filelist[i].Size; + return ret; +} + +off_t cPlayback::mf_lseek(off_t pos) +{ + off_t offset = 0, lpos = pos, ret; + unsigned int fileno; + /* this is basically needed for timeshifting - to allow + growing files to be handled... */ + if (filelist.size() == 1 && filetype == FILETYPE_TS) + { + if (lpos > mf_getsize()) + return -2; + fileno = 0; + } + else + { + for (fileno = 0; fileno < filelist.size(); fileno++) + { + if (lpos < filelist[fileno].Size) + break; + offset += filelist[fileno].Size; + lpos -= filelist[fileno].Size; + } + if (fileno == filelist.size()) + return -2; // EOF + } + + if ((int)fileno != curr_fileno) + { + lt_info("%s old fileno: %d new fileno: %d, offset: %lld\n", __FUNCTION__, curr_fileno, fileno, (long long)lpos); + in_fd = mf_open(fileno); + if (in_fd < 0) + { + lt_info("cannot open file %d:%s (%m)\n", fileno, filelist[fileno].Name.c_str()); + return -1; + } + } + + ret = lseek(in_fd, lpos, SEEK_SET); + if (ret < 0) + return ret; + + curr_pos = offset + ret; + return curr_pos; +} + +/* gets the PTS at a specific file position from a PES + ATTENTION! resets buf! */ +int64_t cPlayback::get_PES_PTS(uint8_t *buf, int len, bool last) +{ + int64_t pts = -1; + int off, plen; + uint8_t *p; + + off = mp_syncPES(buf, len); + + if (off < 0) + return off; + + p = buf + off; + while (off < len - 14 && (pts == -1 || last)) + { + plen = ((p[4] << 8) | p[5]) + 6; + + switch(p[3]) + { + int64_t tmppts; + case 0xe0 ... 0xef: // video! + tmppts = get_pts(p, true, len - off); + if (tmppts >= 0) + pts = tmppts; + break; + case 0xbb: + case 0xbe: + case 0xbf: + case 0xf0 ... 0xf3: + case 0xff: + case 0xc0 ... 0xcf: + case 0xd0 ... 0xdf: + break; + case 0xb9: + case 0xba: + case 0xbc: + default: + plen = 1; + break; + } + p += plen; + off += plen; + } + return pts; +} + +/* needs to be called with inbufpos_mutex locked! */ +ssize_t cPlayback::inbuf_read() +{ + if (filetype == FILETYPE_UNKNOWN) + return -1; + if (filetype == FILETYPE_TS) + return read_ts(); + /* FILETYPE_MPG or FILETYPE_VDR */ + return read_mpeg(); +} + +ssize_t cPlayback::read_ts() +{ + ssize_t toread, ret = 0, sync, off; + toread = INBUF_SIZE - inbuf_pos; + bool retry = true; + uint8_t *buf; + /* fprintf(stderr, "%s:%d curr_pos %lld, inbuf_pos: %ld, toread: %ld\n", + __FUNCTION__, __LINE__, (long long)curr_pos, (long)inbuf_pos, (long)toread); */ + + if (playback_speed > 1) + { + sync = 0; + ssize_t tmpread = PESBUF_SIZE / 188 * 188; + int n, skipped = 0; + bool skip = false; + bool eof = true; + pthread_mutex_lock(&currpos_mutex); + while (toread > 0) + { + ssize_t done = 0; + while (done < tmpread) + { + ret = read(in_fd, pesbuf, tmpread - done); + if (ret == 0 && retry) /* EOF */ + { + mf_lseek(curr_pos); + retry = false; + continue; + } + if (ret < 0) + { + lt_info("%s failed1: %m\n", __FUNCTION__); + pthread_mutex_unlock(&currpos_mutex); + return ret; + } + if (ret == 0 && eof) + goto out; + eof = false; + done += ret; + curr_pos += ret; + } + sync = sync_ts(pesbuf, ret); + if (sync != 0) + { + lt_info("%s out of sync: %d\n", __FUNCTION__, sync); + if (sync < 0) + { + pthread_mutex_unlock(&currpos_mutex); + return -1; + } + memmove(pesbuf, pesbuf + sync, ret - sync); + if (pesbuf[0] != 0x47) + lt_info("%s:%d??????????????????????????????\n", __FUNCTION__, __LINE__); + } + for (n = 0; n < done / 188 * 188; n += 188) + { + buf = pesbuf + n; + if (buf[1] & 0x40) // PUSI + { + /* only video packets... */ + int of = 4; + if (buf[3] & 0x20) // adaptation field + of += buf[4] + 1; + if ((buf[of + 3] & 0xF0) == 0xE0 && // Video stream + buf[of + 2] == 0x01 && buf[of + 1] == 0x00 && buf[of] == 0x00) // PES + { + skip = true; + skipped++; + if (skipped >= playback_speed) + { + skipped = 0; + skip = false; + } + } + } + if (! skip) + { + memcpy(inbuf + inbuf_pos, buf, 188); + inbuf_pos += 188; + toread -= 188; + if (toread <= 0) + { + /* the output buffer is full, discard the input :-( */ + if (done - n > 0) + { + lt_debug("%s not done: %d, resetting filepos\n", + __FUNCTION__, done - n); + mf_lseek(curr_pos - (done - n)); + } + break; + } + } + } + } + out: + pthread_mutex_unlock(&currpos_mutex); + if (eof) + return 0; + } + else + { + pthread_mutex_lock(&currpos_mutex); + while(true) + { + ret = read(in_fd, inbuf + inbuf_pos, toread); + if (ret == 0 && retry) /* EOF */ + { + mf_lseek(curr_pos); + retry = false; + continue; + } + break; + } + if (ret <= 0) + { + pthread_mutex_unlock(&currpos_mutex); + if (ret < 0) + lt_info("%s failed2: %m\n", __FUNCTION__); + return ret; + } + inbuf_pos += ret; + curr_pos += ret; + pthread_mutex_unlock(&currpos_mutex); + + sync = sync_ts(inbuf + inbuf_sync, INBUF_SIZE - inbuf_sync); + if (sync < 0) + { + lt_info("%s cannot sync\n", __FUNCTION__); + return ret; + } + inbuf_sync += sync; + } + /* check for A/V PIDs */ + uint16_t pid; + int i; + int64_t pts; + //fprintf(stderr, "inbuf_pos: %ld - sync: %ld, inbuf_syc: %ld\n", (long)inbuf_pos, (long)sync, (long)inbuf_sync); + int synccnt = 0; + for (i = 0; i < inbuf_pos - inbuf_sync - 13;) { + buf = inbuf + inbuf_sync + i; + if (*buf != 0x47) + { + synccnt++; + i++; + continue; + } + if (synccnt) + lt_info("%s TS went out of sync %d\n", __FUNCTION__, synccnt); + synccnt = 0; + if (!(buf[1] & 0x40)) /* PUSI */ + { + i += 188; + continue; + } + off = 0; + if (buf[3] & 0x20) /* adaptation field? */ + off = buf[4] + 1; + pid = get_pid(buf + 1); + /* PES signature is at buf + 4, streamtype is after 00 00 01 */ + switch (buf[4 + 3 + off]) + { + case 0xe0 ... 0xef: /* video stream */ + if (vpid == 0) + vpid = pid; + pts = get_pts(buf + 4 + off, true, inbuf_pos - inbuf_sync - i - off - 4); + if (pts < 0) + break; + pts_curr = pts; + if (pts_start < 0) + { + lt_info("%s updating pts_start to %lld ", __FUNCTION__, pts); + pts_start = pts; + if (pts_end > -1) + { + if (pts_end < pts_start) + { + pts_end += 0x200000000ULL; + fprintf(stderr, "pts_end to %lld ", pts_end); + } + int duration = (pts_end - pts_start) / 90000; + if (duration > 0) + { + bytes_per_second = (mf_getsize() - curr_pos) / duration; + fprintf(stderr, "bytes_per_second to %lldk duration to %ds at %lldk", + bytes_per_second / 1024, duration, curr_pos / 1024); + } + } + fprintf(stderr, "\n"); + } + break; + case 0xbd: /* private stream 1 - ac3 */ + case 0xc0 ... 0xdf: /* audio stream */ + if (astreams.find(pid) != astreams.end()) + break; + AStream tmp; + if (buf[7 + off] == 0xbd) + { + if (buf[12 + off] == 0x24) /* 0x24 == TTX */ + break; + tmp.ac3 = true; + } + else + tmp.ac3 = false; + tmp.lang = ""; + astreams.insert(std::make_pair(pid, tmp)); + lt_info("%s found apid #%d 0x%04hx ac3:%d\n", __func__, astreams.size(), pid, tmp.ac3); + break; + } + i += 188; + } + + // fprintf(stderr, "%s:%d ret %ld\n", __FUNCTION__, __LINE__, (long long)ret); + return ret; +} + +ssize_t cPlayback::read_mpeg() +{ + ssize_t toread, ret, sync; + //toread = PESBUF_SIZE - pesbuf_pos; + /* experiments found, that 80kB is the best buffer size, otherwise a/v sync seems + to suffer and / or audio stutters */ + toread = 80 * 1024 - pesbuf_pos; + bool retry = true; + + if (INBUF_SIZE - inbuf_pos < toread) + { + lt_info("%s inbuf full, setting toread to %d (old: %zd)\n", __FUNCTION__, INBUF_SIZE - inbuf_pos, toread); + toread = INBUF_SIZE - inbuf_pos; + } + pthread_mutex_lock(&currpos_mutex); + while(true) + { + ret = read(in_fd, pesbuf + pesbuf_pos, toread); + if (ret == 0 && retry) /* EOF */ + { + mf_lseek(curr_pos); + retry = false; + continue; + } + break; + } + if (ret < 0) + { + pthread_mutex_unlock(&currpos_mutex); + lt_info("%s failed: %m, pesbuf_pos: %zd, toread: %zd\n", __FUNCTION__, pesbuf_pos, toread); + return ret; + } + pesbuf_pos += ret; + curr_pos += ret; + pthread_mutex_unlock(&currpos_mutex); + + int count = 0; + uint16_t pid = 0; + bool resync = true; + while (count < pesbuf_pos - 10) + { + if (resync) + { + sync = mp_syncPES(pesbuf + count, pesbuf_pos - count - 10); + if (sync < 0) + { + if (pesbuf_pos - count - 10 > 4) + lt_info("%s cannot sync (count=%d, pesbuf_pos=%zd)\n", + __FUNCTION__, count, pesbuf_pos); + break; + } + if (sync) + lt_info("%s needed sync %zd\n", __FUNCTION__, sync); + count += sync; + } + uint8_t *ppes = pesbuf + count; + int av = 0; // 1 = video, 2 = audio + int64_t pts; + switch(ppes[3]) + { + case 0xba: //pack header; + // fprintf(stderr, "pack start code, 0x%02x\n", ppes[4]); + if ((ppes[4] & 0xf0) == 0x20) /* mpeg 1 */ + { + streamtype = 1; /* for audio setup */ + count += 12; + } + else if ((ppes[4] & 0xc0) == 0x40) /* mpeg 2 */ + { + streamtype = 0; + count += 14; /* correct: 14 + (ppes[13] & 0x07) */ + } + else + { + lt_info("%s weird pack header: 0x%2x\n", __FUNCTION__, ppes[4]); + count++; + } + resync = true; + continue; + break; + case 0xbd: // AC3 + { + int off = ppes[8] + 8 + 1; // ppes[8] is often 0 + if (count + off >= pesbuf_pos) + break; + uint16_t subid = ppes[off]; + // if (offset == 0x24 && subid == 0x10 ) // TTX? + if (subid < 0x80 || subid > 0x87) + break; + lt_debug("AC3: ofs 0x%02x subid 0x%02x\n", off, subid); + //subid -= 0x60; // normalize to 32...39 (hex 0x20..0x27) + + if (astreams.find(subid) == astreams.end()) + { + AStream tmp; + tmp.ac3 = true; + tmp.lang = ""; + astreams.insert(std::make_pair(subid, tmp)); + lt_info("%s found aid: %02x\n", __FUNCTION__, subid); + } + pid = subid; + av = 2; + break; + } + case 0xbb: + case 0xbe: + case 0xbf: + case 0xf0 ... 0xf3: + case 0xff: + //skip = (ppes[4] << 8 | ppes[5]) + 6; + //DBG("0x%02x header, skip = %d\n", ppes[3], skip); + break; + case 0xc0 ... 0xcf: + case 0xd0 ... 0xdf: + { + // fprintf(stderr, "audio stream 0x%02x\n", ppes[3]); + uint16_t id = ppes[3]; + if (astreams.find(id) == astreams.end()) + { + AStream tmp; + tmp.ac3 = false; + tmp.lang = ""; + astreams.insert(std::make_pair(id, tmp)); + lt_info("%s found aid: %02x\n", __FUNCTION__, id); + } + pid = id; + av = 2; + break; + } + case 0xe0 ... 0xef: + // fprintf(stderr, "video stream 0x%02x, %02x %02x \n", ppes[3], ppes[4], ppes[5]); + pid = 0x40; + av = 1; + pts = get_pts(ppes, true, pesbuf_pos - count); + if (pts < 0) + break; + pts_curr = pts; + if (pts_start < 0) + pts_start = pts; + break; + case 0xb9: + case 0xbc: + lt_debug("%s:%d %s\n", __FUNCTION__, __LINE__, + (ppes[3] == 0xb9) ? "program_end_code" : "program_stream_map"); + //resync = true; + // fallthrough. TODO: implement properly. + default: + //if (! resync) + // DBG("Unknown stream id: 0x%X.\n", ppes[3]); + count++; + resync = true; + continue; + break; + } + + int pesPacketLen = ((ppes[4] << 8) | ppes[5]) + 6; + if (count + pesPacketLen >= pesbuf_pos) + { + lt_debug("buffer len: %ld, pesPacketLen: %d :-(\n", pesbuf_pos - count, pesPacketLen); + break; + } + + int tsPacksCount = pesPacketLen / 184; + if ((tsPacksCount + 1) * 188 > INBUF_SIZE - inbuf_pos) + { + lt_info("not enough size in inbuf (needed %d, got %d)\n", (tsPacksCount + 1) * 188, INBUF_SIZE - inbuf_pos); + break; + } + + if (av) + { + int rest = pesPacketLen % 184; + + // divide PES packet into small TS packets + uint8_t pusi = 0x40; + int j; + uint8_t *ts = inbuf + inbuf_pos; + for (j = 0; j < tsPacksCount; j++) + { + ts[0] = 0x47; // SYNC Byte + ts[1] = pusi; // Set PUSI if first packet + ts[2] = pid; // PID (low) + ts[3] = 0x10 | (cc[pid] & 0x0F); // No adaptation field, payload only, continuity counter + cc[pid]++; + memcpy(ts + 4, ppes + j * 184, 184); + pusi = 0x00; // clear PUSI + ts += 188; + inbuf_pos += 188; + } + + if (rest > 0) + { + ts[0] = 0x47; // SYNC Byte + ts[1] = pusi; // Set PUSI or + ts[2] = pid; // PID (low) + ts[3] = 0x30 | (cc[pid] & 0x0F); // adaptation field, payload, continuity counter + cc[pid]++; + ts[4] = 183 - rest; + if (ts[4] > 0) + { + ts[5] = 0x00; + memset(ts + 6, 0xFF, ts[4] - 1); + } + memcpy(ts + 188 - rest, ppes + j * 184, rest); + inbuf_pos += 188; + } + } //if (av) + + count += pesPacketLen; + } + memmove(pesbuf, pesbuf + count, pesbuf_pos - count); + pesbuf_pos -= count; + return ret; +} + +//== seek to pos with sync to next proper TS packet == +//== returns offset to start of TS packet or actual == +//== pos on failure. == +//==================================================== +off_t cPlayback::mp_seekSync(off_t pos) +{ + off_t npos = pos; + off_t ret; + uint8_t pkt[1024]; + + pthread_mutex_lock(&currpos_mutex); + ret = mf_lseek(npos); + if (ret < 0) + lt_info("%s:%d lseek ret < 0 (%m)\n", __FUNCTION__, __LINE__); + + if (filetype != FILETYPE_TS) + { + int offset = 0; + int s; + ssize_t r; + bool retry = false; + while (true) + { + r = read(in_fd, &pkt[offset], 1024 - offset); + if (r < 0) + { + lt_info("%s read failed: %m\n", __FUNCTION__); + break; + } + if (r == 0) // EOF? + { + if (retry) + break; + if (mf_lseek(npos) < 0) /* next file in list? */ + { + lt_info("%s:%d lseek ret < 0 (%m)\n", __FUNCTION__, __LINE__); + break; + } + retry = true; + continue; + } + s = mp_syncPES(pkt, r + offset, true); + if (s < 0) + { + /* if the last 3 bytes of the buffer were 00 00 01, then + mp_sync_PES would not find it. So keep them and check + again in the next iteration */ + memmove(pkt, &pkt[r + offset - 3], 3); + npos += r; + offset = 3; + } + else + { + npos += s; + lt_info("%s sync after %lld\n", __FUNCTION__, npos - pos); + ret = mf_lseek(npos); + pthread_mutex_unlock(&currpos_mutex); + if (ret < 0) + lt_info("%s:%d lseek ret < 0 (%m)\n", __FUNCTION__, __LINE__); + return ret; + } + if (npos > (pos + 0x20000)) /* 128k enough? */ + break; + } + lt_info("%s could not sync to PES offset: %d r: %zd\n", __FUNCTION__, offset, r); + ret = mf_lseek(pos); + pthread_mutex_unlock(&currpos_mutex); + return ret; + } + + /* TODO: use bigger buffer here, too and handle EOF / next splitfile */ + while (read(in_fd, pkt, 1) > 0) + { + //-- check every byte until sync word reached -- + npos++; + if (*pkt == 0x47) + { + //-- if found double check for next sync word -- + if (read(in_fd, pkt, 188) == 188) + { + if(pkt[188-1] == 0x47) + { + ret = mf_lseek(npos - 1); // assume sync ok + pthread_mutex_unlock(&currpos_mutex); + if (ret < 0) + lt_info("%s:%d lseek ret < 0 (%m)\n", __FUNCTION__, __LINE__); + return ret; + } + else + { + ret = mf_lseek(npos); // oops, next pkt doesn't start with sync + if (ret < 0) + lt_info("%s:%d lseek ret < 0 (%m)\n", __FUNCTION__, __LINE__); + } + } + } + + //-- check probe limits -- + if (npos > (pos + 100 * 188)) + break; + } + + //-- on error stay on actual position -- + ret = mf_lseek(pos); + pthread_mutex_unlock(&currpos_mutex); + return ret; +} + +static int sync_ts(uint8_t *p, int len) +{ + int count; + if (len < 189) + return -1; + + count = 0; + while (*p != 0x47 || *(p + 188) != 0x47) + { + count++; + p++; + if (count + 188 > len) + return -1; + } + return count; +} + +/* get the pts value from a TS or PES packet + pes == true selects PES mode. */ +int64_t cPlayback::get_pts(uint8_t *p, bool pes, int bufsize) +{ + const uint8_t *end = p + bufsize; /* check for overflow */ + if (bufsize < 14) + return -1; + if (!pes) + { + if (p[0] != 0x47) + return -1; + if (!(p[1] & 0x40)) + return -1; + if (get_pid(p + 1) != vpid) + return -1; + if (!(p[3] & 0x10)) + return -1; + + if (p[3] & 0x20) + p += p[4] + 4 + 1; + else + p += 4; + + if (p + 13 > end) + return -1; + /* p is now pointing at the PES header. hopefully */ + if (p[0] || p[1] || (p[2] != 1)) + return -1; + } + + if ((p[6] & 0xC0) != 0x80) // MPEG1 + { + p += 6; + while (*p == 0xff) + { + p++; + if (p > end) + return -1; + } + if ((*p & 0xc0) == 0x40) + p += 2; + p -= 9; /* so that the p[9]...p[13] matches the below */ + if (p + 13 > end) + return -1; + } + else + { + /* MPEG2 */ + if ((p[7] & 0x80) == 0) // packets with both pts, don't care for dts + // if ((p[7] & 0xC0) != 0x80) // packets with only pts + // if ((p[7] & 0xC0) != 0xC0) // packets with pts and dts + return -1; + if (p[8] < 5) + return -1; + } + + if (!(p[9] & 0x20)) + return -1; + + int64_t pts = + ((p[ 9] & 0x0EULL) << 29) | + ((p[10] & 0xFFULL) << 22) | + ((p[11] & 0xFEULL) << 14) | + ((p[12] & 0xFFULL) << 7) | + ((p[13] & 0xFEULL) >> 1); + + //int msec = pts / 90; + //INFO("time: %02d:%02d:%02d\n", msec / 3600000, (msec / 60000) % 60, (msec / 1000) % 60); + return pts; +} + +/* returns: 0 == was already synchronous, > 0 == is now synchronous, -1 == could not sync */ +static int mp_syncPES(uint8_t *buf, int len, bool quiet) +{ + int ret = 0; + while (ret < len - 4) + { + if (buf[ret + 2] != 0x01) + { + ret++; + continue; + } + if (buf[ret + 1] != 0x00) + { + ret += 2; + continue; + } + if (buf[ret] != 0x00) + { + ret += 3; + continue; + } + /* all stream IDs are > 0x80 */ + if ((buf[ret + 3] & 0x80) != 0x80) + { + /* we already checked for 00 00 01, if the stream ID + is not valid, we can skip those 3 bytes */ + ret += 3; + continue; + } + return ret; + } + + if (!quiet && len > 5) /* only warn if enough space was available... */ + lt_info_c("%s No valid PES signature found. %d Bytes deleted.\n", __FUNCTION__, ret); + return -1; +} + +static inline uint16_t get_pid(uint8_t *buf) +{ + return (*buf & 0x1f) << 8 | *(buf + 1); +} + diff --git a/libtriple/playback_td.h b/libtriple/playback_td.h new file mode 100644 index 0000000..51d5ac1 --- /dev/null +++ b/libtriple/playback_td.h @@ -0,0 +1,128 @@ +#ifndef __PLAYBACK_TD_H +#define __PLAYBACK_TD_H + +#include +#include +#include +#include + +/* almost 256kB */ +#define INBUF_SIZE (1394 * 188) +#define PESBUF_SIZE (128 * 1024) + +typedef enum { + PLAYMODE_TS = 0, + PLAYMODE_FILE, +} playmode_t; + +typedef enum { + FILETYPE_UNKNOWN, + FILETYPE_TS, + FILETYPE_MPG, + FILETYPE_VDR +} filetype_t; + +typedef enum { + STATE_STOP, + STATE_PLAY, + STATE_PAUSE, + STATE_FF, + STATE_REW, + STATE_INIT +} playstate_t; + +typedef struct { + std::string Name; + off_t Size; +} filelist_t; + +class cPlayback +{ + private: + uint8_t *inbuf; + ssize_t inbuf_pos; + ssize_t inbuf_sync; + uint8_t *pesbuf; + ssize_t pesbuf_pos; + ssize_t inbuf_read(void); + ssize_t read_ts(void); + ssize_t read_mpeg(void); + + uint8_t cc[256]; + + int in_fd; + + int video_type; + int playback_speed; + int mSpeed; + playmode_t playMode; + std::vector filelist; /* for multi-file playback */ + + bool filelist_auto_add(void); + int mf_open(int fileno); + int mf_close(void); + off_t mf_lseek(off_t pos); + off_t mf_getsize(void); + int curr_fileno; + off_t curr_pos; + off_t last_size; + off_t bytes_per_second; + + uint16_t vpid; + uint16_t apid; + bool ac3; + struct AStream { + // uint16_t pid; + bool ac3; + std::string lang; /* not yet really used */ + }; + std::map astreams; /* stores AStream sorted by pid */ + + int64_t pts_start; + int64_t pts_end; + int64_t _pts_end; /* last good endpts */ + int64_t pts_curr; + int64_t get_pts(uint8_t *p, bool pes, int bufsize); + + filetype_t filetype; + playstate_t playstate; + + off_t seek_to_pts(int64_t pts); + off_t mp_seekSync(off_t pos); + int64_t get_PES_PTS(uint8_t *buf, int len, bool until_eof); + + pthread_t thread; + bool thread_started; + public: + cPlayback(int num = 0); + ~cPlayback(); + + void playthread(); + + bool Open(playmode_t PlayMode); + void Close(void); + bool Start(char *filename, unsigned short vpid, int vtype, unsigned short apid, + int ac3, unsigned int duration); + bool SetAPid(unsigned short pid, int ac3); + bool SetSpeed(int speed); + bool GetSpeed(int &speed) const; + bool GetPosition(int &position, int &duration); /* pos: current time in ms, dur: file length in ms */ + bool SetPosition(int position, bool absolute = false); /* position: jump in ms */ + void FindAllPids(uint16_t *apids, unsigned short *ac3flags, uint16_t *numpida, std::string *language); + void FindAllSubs(uint16_t *pids, unsigned short *supported, uint16_t *numpida, std::string *language); + bool SelectSubtitles(int pid); + void GetChapters(std::vector &positions, std::vector &titles); +#if 0 + // Functions that are not used by movieplayer.cpp: + bool Stop(void); + bool GetOffset(off64_t &offset); + bool IsPlaying(void) const { return playing; } + bool IsEnabled(void) const { return enabled; } + void * GetHandle(void); + void * GetDmHandle(void); + int GetCurrPlaybackSpeed(void) const { return nPlaybackSpeed; } + void PlaybackNotify (int Event, void *pData, void *pTag); + void DMNotify(int Event, void *pTsBuf, void *Tag); +#endif +}; +#endif diff --git a/libtriple/pwrmngr.cpp b/libtriple/pwrmngr.cpp new file mode 100644 index 0000000..e526246 --- /dev/null +++ b/libtriple/pwrmngr.cpp @@ -0,0 +1,87 @@ +#include +#include + +#include "pwrmngr.h" +#include "lt_debug.h" +#include +#include +#include +#include +#include + +#include +#include + +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_PWRMNGR, this, args) +#define lt_info(args...) _lt_info(TRIPLE_DEBUG_PWRMNGR, this, args) +void cCpuFreqManager::Up(void) { lt_debug("%s\n", __FUNCTION__); } +void cCpuFreqManager::Down(void) { lt_debug("%s\n", __FUNCTION__); } +void cCpuFreqManager::Reset(void) { lt_debug("%s\n", __FUNCTION__); } +/* those function dummies return true or "harmless" values */ +bool cCpuFreqManager::SetDelta(unsigned long) { lt_debug("%s\n", __FUNCTION__); return true; } +unsigned long cCpuFreqManager::GetCpuFreq(void) { lt_debug("%s\n", __FUNCTION__); return 0; } +unsigned long cCpuFreqManager::GetDelta(void) { lt_debug("%s\n", __FUNCTION__); return 0; } +// +cCpuFreqManager::cCpuFreqManager(void) { lt_debug("%s\n", __FUNCTION__); } + +bool cPowerManager::SetState(PWR_STATE) { lt_debug("%s\n", __FUNCTION__); return true; } + +bool cPowerManager::Open(void) { lt_debug("%s\n", __FUNCTION__); return true; } +void cPowerManager::Close(void) { lt_debug("%s\n", __FUNCTION__); } +// +bool cPowerManager::SetStandby(bool Active, bool Passive) +{ + lt_debug("%s(%d, %d)\n", __FUNCTION__, Active, Passive); + return true; +} + +bool cCpuFreqManager::SetCpuFreq(unsigned long f) +{ + /* actually SetCpuFreq is used to determine if the system is in standby + this is an "elegant" hack, because: + * during a recording, cpu freq is kept "high", even if the box is sent to standby + * the "SetStandby" call is made even if a recording is running + On the TD, setting standby disables the frontend, so we must not do it + if a recording is running. + For now, the values in neutrino are hardcoded: + * f == 0 => max => not standby + * f == 50000000 => min => standby + */ + lt_debug("%s(%lu) => set standby = %s\n", __FUNCTION__, f, f?"true":"false"); + int fd = open("/dev/stb/tdsystem", O_RDONLY); + if (fd < 0) + { + perror("open tdsystem"); + return false; + } + if (f) + { + ioctl(fd, IOC_AVS_SET_VOLUME, 31); /* mute AVS to avoid ugly noise */ + ioctl(fd, IOC_AVS_STANDBY_ENTER); + if (getenv("TRIPLE_LCDBACKLIGHT")) + { + lt_info("%s: TRIPLE_LCDBACKLIGHT is set: keeping LCD backlight on\n", __func__); + close(fd); + fd = open("/dev/stb/tdlcd", O_RDONLY); + if (fd < 0) + lt_info("%s: open tdlcd error: %m\n", __func__); + else + ioctl(fd, IOC_LCD_BACKLIGHT_ON); + } + } + else + { + ioctl(fd, IOC_AVS_SET_VOLUME, 31); /* mute AVS to avoid ugly noise */ + ioctl(fd, IOC_AVS_STANDBY_LEAVE); + /* unmute will be done by cAudio::do_mute(). Ugly, but prevents pops */ + // ioctl(fd, IOC_AVS_SET_VOLUME, 0); /* max gain */ + } + + close(fd); + return true; +} + +// +cPowerManager::cPowerManager(void) { lt_debug("%s\n", __FUNCTION__); } +cPowerManager::~cPowerManager() { lt_debug("%s\n", __FUNCTION__); } + diff --git a/libtriple/pwrmngr.h b/libtriple/pwrmngr.h new file mode 100644 index 0000000..55dc984 --- /dev/null +++ b/libtriple/pwrmngr.h @@ -0,0 +1,53 @@ +#ifndef __PWRMNGR_H__ +#define __PWRMNGR_H__ + +// -- cCpuFreqManager ---------------------------------------------------------- + +class cCpuFreqManager { +private: + unsigned long startCpuFreq; + unsigned long delta; +public: + void Up(void); + void Down(void); + void Reset(void); + // + bool SetCpuFreq(unsigned long CpuFreq); + bool SetDelta(unsigned long Delta); + unsigned long GetCpuFreq(void); + unsigned long GetDelta(void); + // + cCpuFreqManager(void); + +}; + +// -- cPowerManageger ---------------------------------------------------------- + +typedef enum +{ + PWR_INIT = 1, + PWR_FULL_ACTIVE, /* all devices/clocks up */ + PWR_ACTIVE_STANDBY, + PWR_PASSIVE_STANDBY, + PWR_INVALID +} PWR_STATE; + +class cPowerManager { +private: + bool init; + bool opened; + PWR_STATE powerState; + // + static void ApplicationCallback(void *, void *, signed long, void *, void *) {} + bool SetState(PWR_STATE PowerState); +public: + bool Open(void); + void Close(void); + // + bool SetStandby(bool Active, bool Passive); + // + cPowerManager(void); + virtual ~cPowerManager(); +}; + +#endif // __PWRMNGR_H__ diff --git a/libtriple/record_td.cpp b/libtriple/record_td.cpp new file mode 100644 index 0000000..584aba7 --- /dev/null +++ b/libtriple/record_td.cpp @@ -0,0 +1,271 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "record_td.h" +#include "lt_debug.h" +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_RECORD, this, args) +#define lt_info(args...) _lt_info(TRIPLE_DEBUG_RECORD, this, args) + +/* helper function to call the cpp thread loop */ +void *execute_record_thread(void *c) +{ + cRecord *obj = (cRecord *)c; + obj->RecordThread(); + return NULL; +} + +cRecord::cRecord(int /*num*/) +{ + lt_info("%s\n", __func__); + dmx = NULL; + record_thread_running = false; + file_fd = -1; + exit_flag = RECORD_STOPPED; +} + +cRecord::~cRecord() +{ + lt_info("%s: calling ::Stop()\n", __func__); + Stop(); + lt_info("%s: end\n", __func__); +} + +bool cRecord::Open(void) +{ + lt_info("%s\n", __func__); + exit_flag = RECORD_STOPPED; + return true; +} + +#if 0 +// unused +void cRecord::Close(void) +{ + lt_info("%s: \n", __func__); +} +#endif + +bool cRecord::Start(int fd, unsigned short vpid, unsigned short *apids, int numpids, uint64_t) +{ + lt_info("%s: fd %d, vpid 0x%03x\n", __func__, fd, vpid); + int i; + + if (!dmx) + dmx = new cDemux(1); + + dmx->Open(DMX_TP_CHANNEL, NULL, 0); + dmx->pesFilter(vpid); + + for (i = 0; i < numpids; i++) + dmx->addPid(apids[i]); + + file_fd = fd; + exit_flag = RECORD_RUNNING; + if (posix_fadvise(file_fd, 0, 0, POSIX_FADV_DONTNEED)) + perror("posix_fadvise"); + + i = pthread_create(&record_thread, 0, execute_record_thread, this); + if (i != 0) + { + exit_flag = RECORD_FAILED_READ; + errno = i; + lt_info("%s: error creating thread! (%m)\n", __func__); + delete dmx; + dmx = NULL; + return false; + } + record_thread_running = true; + return true; +} + +bool cRecord::Stop(void) +{ + lt_info("%s\n", __func__); + + if (exit_flag != RECORD_RUNNING) + lt_info("%s: status not RUNNING? (%d)\n", __func__, exit_flag); + + exit_flag = RECORD_STOPPED; + if (record_thread_running) + pthread_join(record_thread, NULL); + record_thread_running = false; + + /* We should probably do that from the destructor... */ + if (!dmx) + lt_info("%s: dmx == NULL?\n", __func__); + else + delete dmx; + dmx = NULL; + + if (file_fd != -1) + close(file_fd); + else + lt_info("%s: file_fd not open??\n", __func__); + file_fd = -1; + return true; +} + +bool cRecord::ChangePids(unsigned short /*vpid*/, unsigned short *apids, int numapids) +{ + std::vector pids; + int j; + bool found; + unsigned short pid; + lt_info("%s\n", __func__); + if (!dmx) { + lt_info("%s: DMX = NULL\n", __func__); + return false; + } + pids = dmx->getPesPids(); + /* the first PID is the video pid, so start with the second PID... */ + for (std::vector::const_iterator i = pids.begin() + 1; i != pids.end(); ++i) { + found = false; + pid = (*i).pid; + for (j = 0; j < numapids; j++) { + if (pid == apids[j]) { + found = true; + break; + } + } + if (!found) + dmx->removePid(pid); + } + for (j = 0; j < numapids; j++) { + found = false; + for (std::vector::const_iterator i = pids.begin() + 1; i != pids.end(); ++i) { + if ((*i).pid == apids[j]) { + found = true; + break; + } + } + if (!found) + dmx->addPid(apids[j]); + } + return true; +} + +bool cRecord::AddPid(unsigned short pid) +{ + std::vector pids; + lt_info("%s: \n", __func__); + if (!dmx) { + lt_info("%s: DMX = NULL\n", __func__); + return false; + } + pids = dmx->getPesPids(); + for (std::vector::const_iterator i = pids.begin(); i != pids.end(); ++i) { + if ((*i).pid == pid) + return true; /* or is it an error to try to add the same PID twice? */ + } + return dmx->addPid(pid); +} + +void cRecord::RecordThread() +{ + lt_info("%s: begin\n", __func__); +#define BUFSIZE (1 << 19) /* 512 kB */ + ssize_t r = 0; + int buf_pos = 0; + uint8_t *buf; + buf = (uint8_t *)malloc(BUFSIZE); + + if (!buf) + { + exit_flag = RECORD_FAILED_MEMORY; + lt_info("%s: unable to allocate buffer! (out of memory)\n", __func__); + } + + dmx->Start(); + while (exit_flag == RECORD_RUNNING) + { + if (buf_pos < BUFSIZE) + { + r = dmx->Read(buf + buf_pos, BUFSIZE - 1 - buf_pos, 100); + lt_debug("%s: buf_pos %6d r %6d / %6d\n", __func__, + buf_pos, (int)r, BUFSIZE - 1 - buf_pos); + if (r < 0) + { + if (errno != EAGAIN) + { + lt_info("%s: read failed: %m\n", __func__); + exit_flag = RECORD_FAILED_READ; + break; + } + lt_info("%s: EAGAIN\n", __func__); + } + else + buf_pos += r; + } + else + lt_info("%s: buffer full! Overflow?\n", __func__); + if (buf_pos > (BUFSIZE / 3)) /* start writeout */ + { + size_t towrite = BUFSIZE / 2; + if (buf_pos < BUFSIZE / 2) + towrite = buf_pos; + r = write(file_fd, buf, towrite); + if (r < 0) + { + exit_flag = RECORD_FAILED_FILE; + lt_info("%s: write error: %m\n", __func__); + break; + } + buf_pos -= r; + memmove(buf, buf + r, buf_pos); + lt_debug("%s: buf_pos %6d w %6d / %6d\n", __func__, buf_pos, (int)r, (int)towrite); +#if 0 + if (fdatasync(file_fd)) + perror("cRecord::FileThread() fdatasync"); +#endif + if (posix_fadvise(file_fd, 0, 0, POSIX_FADV_DONTNEED)) + perror("posix_fadvise"); + } + } + dmx->Stop(); + while (buf_pos > 0) /* write out the unwritten buffer content */ + { + r = write(file_fd, buf, buf_pos); + if (r < 0) + { + exit_flag = RECORD_FAILED_FILE; + lt_info("%s: write error: %m\n", __func__); + break; + } + buf_pos -= r; + memmove(buf, buf + r, buf_pos); + } + free(buf); + +#if 0 + // TODO: do we need to notify neutrino about failing recording? + CEventServer eventServer; + eventServer.registerEvent2(NeutrinoMessages::EVT_RECORDING_ENDED, CEventServer::INITID_NEUTRINO, "/tmp/neutrino.sock"); + stream2file_status2_t s; + s.status = exit_flag; + strncpy(s.filename,basename(myfilename),512); + s.filename[511] = '\0'; + strncpy(s.dir,dirname(myfilename),100); + s.dir[99] = '\0'; + eventServer.sendEvent(NeutrinoMessages::EVT_RECORDING_ENDED, CEventServer::INITID_NEUTRINO, &s, sizeof(s)); + printf("[stream2file]: pthreads exit code: %i, dir: '%s', filename: '%s' myfilename: '%s'\n", exit_flag, s.dir, s.filename, myfilename); +#endif + + lt_info("%s: end", __func__); + pthread_exit(NULL); +} + +int cRecord::GetStatus() +{ + /* dummy for now */ + return REC_STATUS_OK; +} + +void cRecord::ResetStatus() +{ + return; +} diff --git a/libtriple/record_td.h b/libtriple/record_td.h new file mode 100644 index 0000000..c2f60ea --- /dev/null +++ b/libtriple/record_td.h @@ -0,0 +1,43 @@ +#ifndef __RECORD_TD_H +#define __RECORD_TD_H + +#include +#include "dmx_td.h" + +#define REC_STATUS_OK 0 +#define REC_STATUS_SLOW 1 +#define REC_STATUS_OVERFLOW 2 + +typedef enum { + RECORD_RUNNING, + RECORD_STOPPED, + RECORD_FAILED_READ, /* failed to read from DMX */ + RECORD_FAILED_OVERFLOW, /* cannot write fast enough */ + RECORD_FAILED_FILE, /* cannot write to file */ + RECORD_FAILED_MEMORY /* out of memory */ +} record_state_t; + +class cRecord +{ + private: + int file_fd; + cDemux *dmx; + pthread_t record_thread; + bool record_thread_running; + record_state_t exit_flag; + int state; + public: + cRecord(int num = 0); + ~cRecord(); + + bool Open(); + bool Start(int fd, unsigned short vpid, unsigned short *apids, int numapids, uint64_t ch = 0); + bool Stop(void); + bool AddPid(unsigned short pid); + int GetStatus(); + void ResetStatus(); + bool ChangePids(unsigned short vpid, unsigned short *apids, int numapids); + + void RecordThread(); +}; +#endif diff --git a/libtriple/td-compat/td-audio-compat.h b/libtriple/td-compat/td-audio-compat.h new file mode 100644 index 0000000..3e0b4a7 --- /dev/null +++ b/libtriple/td-compat/td-audio-compat.h @@ -0,0 +1,38 @@ +/* + * compatibility stuff for Tripledragon audio API + * + * (C) 2009 Stefan Seyfried + * + * 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; version 2 of the License. + * + * 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef __td_audio_compat_h__ +#define __td_audio_compat_h__ + +#include +// types +typedef enum { + AUDIO_SOURCE_DEMUX = AUD_SOURCE_DEMUX, + AUDIO_SOURCE_MEMORY = AUD_SOURCE_MEMORY +} audio_stream_source_t; +#define audio_channel_select_t audChannel_t +// ioctls +#define AUDIO_CHANNEL_SELECT MPEG_AUD_SELECT_CHANNEL +#define AUDIO_SELECT_SOURCE MPEG_AUD_SELECT_SOURCE +#define AUDIO_PLAY MPEG_AUD_PLAY +#define AUDIO_STOP MPEG_AUD_STOP +#define AUDIO_SET_MUTE MPEG_AUD_SET_MUTE + +#endif /* __td_audio_compat_h__ */ diff --git a/libtriple/td-compat/td-demux-compat.h b/libtriple/td-compat/td-demux-compat.h new file mode 100644 index 0000000..8feacfe --- /dev/null +++ b/libtriple/td-compat/td-demux-compat.h @@ -0,0 +1,45 @@ +/* + * compatibility stuff for Tripledragon demux API + * + * (C) 2009 Stefan Seyfried + * + * 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; version 2 of the License. + * + * 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef __td_demux_compat_h__ +#define __td_demux_compat_h__ + +#include +#include +// types +#define dmx_output_t OutDevice +#define dmx_pes_type_t PesType +#define dmx_sct_filter_params demux_filter_para +#define dmx_pes_filter_params demux_pes_para +#define pes_type pesType +// defines +#define DMX_FILTER_SIZE FILTER_LENGTH +#define DMX_ONESHOT XPDF_ONESHOT +#define DMX_CHECK_CRC 0 // TD checks CRC by default +#define DMX_IMMEDIATE_START XPDF_IMMEDIATE_START +#define DMX_OUT_DECODER OUT_DECODER +// ioctls +#define DMX_SET_FILTER DEMUX_FILTER_SET +#define DMX_SET_PES_FILTER DEMUX_FILTER_PES_SET +#define DMX_START DEMUX_START +#define DMX_STOP DEMUX_STOP +#define DMX_SET_BUFFER_SIZE DEMUX_SET_BUFFER_SIZE + +#endif /* __td_demux_compat_h__ */ diff --git a/libtriple/td-compat/td-frontend-compat.h b/libtriple/td-compat/td-frontend-compat.h new file mode 100644 index 0000000..46781ce --- /dev/null +++ b/libtriple/td-compat/td-frontend-compat.h @@ -0,0 +1,120 @@ +/* + * compatibility stuff for Tripledragon frontend API + * + * (C) 2009 Stefan Seyfried + * + * 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; version 2 of the License. + * + * 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef __td_frontend_compat_h__ +#define __td_frontend_compat_h__ + +#ifdef __cplusplus +extern "C" { +#endif + #include +#ifdef __cplusplus +} +#endif + +/* I know that those are different. But functions that get a + dvb_frontend_parameters struct passed on dbox/dreambox will most likely + get a tunersetup struct on TD, so it keeps the differences in headers + and function prototypes small. Of course, the functions itself will have + #ifdef TRIPLEDRAGON or similar... */ +#define dvb_frontend_parameters tunersetup + +/* compat stuff for settings.cpp */ +enum { + INVERSION_OFF, + INVERSION_ON, + INVERSION_AUTO +}; +typedef enum fe_code_rate { + FEC_NONE = 0, + FEC_1_2, + FEC_2_3, + FEC_3_4, + FEC_4_5, + FEC_5_6, + FEC_6_7, + FEC_7_8, + FEC_8_9, + FEC_AUTO +} fe_code_rate_t; + +enum td_code_rate { + TD_FEC_AUTO = 0, + TD_FEC_1_2, + TD_FEC_2_3, + TD_FEC_3_4, + TD_FEC_5_6, + TD_FEC_7_8 +}; + +typedef enum fe_sec_tone_mode { + SEC_TONE_ON, + SEC_TONE_OFF +} fe_sec_tone_mode_t; + +typedef enum fe_sec_voltage { + SEC_VOLTAGE_13, + SEC_VOLTAGE_18, + SEC_VOLTAGE_OFF +} fe_sec_voltage_t; + +typedef enum fe_sec_mini_cmd { + SEC_MINI_A, + SEC_MINI_B +} fe_sec_mini_cmd_t; + +struct dvb_diseqc_master_cmd { + unsigned char msg [6]; /* { framing, address, command, data [3] } */ + unsigned char msg_len; /* valid values are 3...6 */ +}; + +typedef enum fe_type { + FE_QPSK, + FE_QAM, + FE_OFDM, + FE_ATSC +} fe_type_t; + +struct dvb_frontend_info { +// char name[128]; + fe_type_t type; +#if 0 + __u32 frequency_min; + __u32 frequency_max; + __u32 frequency_stepsize; + __u32 frequency_tolerance; + __u32 symbol_rate_min; + __u32 symbol_rate_max; + __u32 symbol_rate_tolerance; /* ppm */ + __u32 notifier_delay; /* DEPRECATED */ + fe_caps_t caps; +#endif +}; + +struct dvb_frontend_event { + fe_status_t status; + tunersetup parameters; +}; + +#ifdef _DVBFRONTEND_H_ +#error _DVBFRONTEND_H_ included +#endif + +#endif /* __td_frontend_compat_h__ */ diff --git a/libtriple/td-compat/td-value-compat.h b/libtriple/td-compat/td-value-compat.h new file mode 100644 index 0000000..f7bb952 --- /dev/null +++ b/libtriple/td-compat/td-value-compat.h @@ -0,0 +1,93 @@ +/* + * compatibility stuff for conversion of Tripledragon API values to DVB API + * and vice versa + * + * (C) 2009 Stefan Seyfried + * + * Released under the GPL V2. + */ + +#ifndef _td_value_compat_ +#define _td_value_compat_ + +#undef FE_GET_INFO +#undef FE_READ_BER +#undef FE_READ_SIGNAL_STRENGTH +#undef FE_READ_SNR +#undef FE_READ_UNCORRECTED_BLOCKS +#undef FE_GET_EVENT +#undef FE_READ_STATUS +#undef FE_SET_PROPERTY +#undef FE_GET_EVENT +#undef FE_GET_EVENT +#undef FE_SET_PROPERTY +#undef FE_SET_TONE +#undef FE_ENABLE_HIGH_LNB_VOLTAGE +#undef FE_SET_VOLTAGE +#undef FE_DISEQC_SEND_MASTER_CMD +#undef FE_DISEQC_SEND_BURST +/* hack, linux/dvb/frontend.h already defines fe_status */ +#define fe_status td_fe_status +#define fe_status_t td_fe_status_t +#define FE_HAS_SIGNAL TD_FE_HAS_SIGNAL +#define FE_HAS_CARRIER TD_FE_HAS_CARRIER +#define FE_HAS_VITERBI TD_FE_HAS_VITERBI +#define FE_HAS_SYNC TD_FE_HAS_SYNC +#define FE_HAS_LOCK TD_FE_HAS_LOCK +#define FE_TIMEDOUT TD_FE_TIMEDOUT +#define FE_REINIT TD_FE_REINIT +#include +#undef fe_status +#undef fe_status_t +#undef FE_HAS_SIGNAL +#undef FE_HAS_CARRIER +#undef FE_HAS_VITERBI +#undef FE_HAS_SYNC +#undef FE_HAS_LOCK +#undef FE_TIMEDOUT +#undef FE_REINIT +enum td_code_rate { + TD_FEC_AUTO = 0, + TD_FEC_1_2, + TD_FEC_2_3, + TD_FEC_3_4, + TD_FEC_5_6, + TD_FEC_7_8 +}; + +static inline unsigned int dvbfec2tdfec(fe_code_rate_t fec) +{ + switch (fec) { + case FEC_1_2: // FEC_1_2 ... FEC_3_4 are equal to TD_FEC_1_2 ... TD_FEC_3_4 + case FEC_2_3: + case FEC_3_4: + return (unsigned int)fec; + case FEC_5_6: + return TD_FEC_5_6; + case FEC_7_8: + return TD_FEC_7_8; + default: + break; + } + return TD_FEC_AUTO; +} + +static inline fe_code_rate_t tdfec2dvbfec(unsigned int tdfec) +{ + switch (tdfec) + { + case TD_FEC_1_2: + case TD_FEC_2_3: + case TD_FEC_3_4: + return (fe_code_rate_t)tdfec; + case TD_FEC_5_6: + return FEC_5_6; + case TD_FEC_7_8: + return FEC_7_8; + default: + break; + } + return FEC_AUTO; +} + +#endif /* _td_value_compat_ */ diff --git a/libtriple/td-compat/td-video-compat.h b/libtriple/td-compat/td-video-compat.h new file mode 100644 index 0000000..137a346 --- /dev/null +++ b/libtriple/td-compat/td-video-compat.h @@ -0,0 +1,46 @@ +/* + * compatibility stuff for Tripledragon video API + * + * (C) 2009 Stefan Seyfried + * + * 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; version 2 of the License. + * + * 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, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __td_video_compat_h__ +#define __td_video_compat_h__ + +#include +// types +#define video_format_t vidDispSize_t +#define video_displayformat_t vidDispMode_t +typedef enum { + VIDEO_SOURCE_DEMUX = VID_SOURCE_DEMUX, + VIDEO_SOURCE_MEMORY = VID_SOURCE_MEMORY +} video_stream_source_t; +typedef enum { + VIDEO_STOPPED, /* Video is stopped */ + VIDEO_PLAYING, /* Video is currently playing */ + VIDEO_FREEZED /* Video is freezed */ +} video_play_state_t; +//#define video_play_state_t vidState_t +// ioctls +#define VIDEO_SET_SYSTEM MPEG_VID_SET_DISPFMT +#define VIDEO_SET_FORMAT MPEG_VID_SET_DISPSIZE +#define VIDEO_SET_DISPLAY_FORMAT MPEG_VID_SET_DISPMODE +#define VIDEO_SELECT_SOURCE MPEG_VID_SELECT_SOURCE +#define VIDEO_PLAY MPEG_VID_PLAY +#define VIDEO_STOP MPEG_VID_STOP +#define VIDEO_SET_BLANK MPEG_VID_SET_BLANK + +#endif /* __td_video_compat_h__ */ diff --git a/libtriple/video_td.cpp b/libtriple/video_td.cpp new file mode 100644 index 0000000..f5c2fd6 --- /dev/null +++ b/libtriple/video_td.cpp @@ -0,0 +1,1105 @@ +/* + * (C) 2002-2003 Andreas Oberritter + * (C) 2010-2013 Stefan Seyfried + * + * 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 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include "video_td.h" +#include +#define VIDEO_DEVICE "/dev/" DEVICE_NAME_VIDEO +#include "lt_debug.h" +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_VIDEO, this, args) +#define lt_info(args...) _lt_info(TRIPLE_DEBUG_VIDEO, this, args) + +#define fop(cmd, args...) ({ \ + int _r; \ + if (fd >= 0) { \ + if ((_r = ::cmd(fd, args)) < 0) \ + lt_info(#cmd"(fd, "#args")\n"); \ + else \ + lt_debug(#cmd"(fd, "#args")\n");\ + } \ + else { _r = fd; } \ + _r; \ +}) + +cVideo * videoDecoder = NULL; +int system_rev = 0; + +#if 0 +/* this would be necessary for the DirectFB implementation of ShowPicture */ +#include +#include +extern IDirectFB *dfb; +extern IDirectFBSurface *dfbdest; +#endif + +extern struct Ssettings settings; +static pthread_mutex_t stillp_mutex = PTHREAD_MUTEX_INITIALIZER; + +/* debugging hacks */ +static bool noscart = false; + +cVideo::cVideo(int, void *, void *, unsigned int) +{ + lt_debug("%s\n", __FUNCTION__); + if ((fd = open(VIDEO_DEVICE, O_RDWR)) < 0) + lt_info("%s cannot open %s: %m\n", __FUNCTION__, VIDEO_DEVICE); + fcntl(fd, F_SETFD, FD_CLOEXEC); + + playstate = VIDEO_STOPPED; + croppingMode = VID_DISPMODE_NORM; + outputformat = VID_OUTFMT_RGBC_SVIDEO; + scartvoltage = -1; + z[0] = 100; + z[1] = 100; + zoomvalue = &z[0]; + const char *blanknames[2] = { "/share/tuxbox/blank_576.mpg", "/share/tuxbox/blank_480.mpg" }; + int blankfd; + struct stat st; + + for (int i = 0; i < 2; i++) + { + blank_data[i] = NULL; /* initialize */ + blank_size[i] = 0; + blankfd = open(blanknames[i], O_RDONLY); + if (blankfd < 0) + { + lt_info("%s cannot open %s: %m", __FUNCTION__, blanknames[i]); + continue; + } + if (fstat(blankfd, &st) != -1 && st.st_size > 0) + { + blank_size[i] = st.st_size; + blank_data[i] = malloc(blank_size[i]); + if (! blank_data[i]) + lt_info("%s malloc failed (%m)\n", __FUNCTION__); + else if (read(blankfd, blank_data[i], blank_size[i]) != blank_size[i]) + { + lt_info("%s short read (%m)\n", __FUNCTION__); + free(blank_data[i]); /* don't leak... */ + blank_data[i] = NULL; + } + else + { /* set framerate to 24fps... see getBlank() */ + ((char *)blank_data[i])[7] &= 0xF0; + ((char *)blank_data[i])[7] += 2; + } + } + close(blankfd); + } + video_standby = 0; + noscart = (getenv("TRIPLE_NOSCART") != NULL); + if (noscart) + lt_info("%s TRIPLE_NOSCART variable prevents SCART switching\n", __FUNCTION__); +} + +cVideo::~cVideo(void) +{ + playstate = VIDEO_STOPPED; + for (int i = 0; i < 2; i++) + { + if (blank_data[i]) + free(blank_data[i]); + blank_data[i] = NULL; + } + /* disable DACs and SCART voltage */ + Standby(true); + if (fd >= 0) + close(fd); +} + +int cVideo::setAspectRatio(int aspect, int mode) +{ + static int _mode = -1; + static int _aspect = -1; + vidDispSize_t dsize = VID_DISPSIZE_UNKNOWN; + vidDispMode_t dmode = VID_DISPMODE_NORM; + /* 1 = 4:3, 3 = 16:9, 4 = 2.21:1, 0 = unknown */ + int v_ar = getAspectRatio(); + + if (aspect != -1) + _aspect = aspect; + if (mode != -1) + _mode = mode; + lt_info("%s(%d, %d)_(%d, %d) v_ar %d\n", __FUNCTION__, aspect, mode, _aspect, _mode, v_ar); + + /* values are hardcoded in neutrino_menue.cpp, "2" is 14:9 -> not used */ + if (_aspect != -1) + { + switch(_aspect) + { + case 1: + dsize = VID_DISPSIZE_4x3; + scartvoltage = 12; + break; + case 3: + dsize = VID_DISPSIZE_16x9; + scartvoltage = 6; + break; + default: + break; + } + } + if (_mode != -1) + { + int zoom = 100 * 16 / 14; /* 16:9 vs 14:9 */ + switch(_mode) + { + case DISPLAY_AR_MODE_NONE: + if (v_ar < 3) + dsize = VID_DISPSIZE_4x3; + else + dsize = VID_DISPSIZE_16x9; + break; + case DISPLAY_AR_MODE_LETTERBOX: + dmode = VID_DISPMODE_LETTERBOX; + break; + case DISPLAY_AR_MODE_PANSCAN: + zoom = 100 * 5 / 4; + case DISPLAY_AR_MODE_PANSCAN2: + if ((v_ar < 3 && _aspect == 3) || (v_ar >= 3 && _aspect == 1)) + { + /* unfortunately, this partly reimplements the setZoom code... */ + dsize = VID_DISPSIZE_UNKNOWN; + dmode = VID_DISPMODE_SCALE; + SCALEINFO s; + memset(&s, 0, sizeof(s)); + if (v_ar < 3) { /* 4:3 */ + s.src.hori_size = 720; + s.src.vert_size = 2 * 576 - 576 * zoom / 100; + s.des.hori_size = zoom * 720 * 3/4 / 100; + s.des.vert_size = 576; + } else { + s.src.hori_size = 2 * 720 - 720 * zoom / 100; + s.src.vert_size = 576; + s.des.hori_size = 720; + s.des.vert_size = zoom * 576 * 3/4 / 100; + } + s.des.vert_off = (576 - s.des.vert_size) / 2; + s.des.hori_off = (720 - s.des.hori_size) / 2; + lt_debug("PANSCAN2: %d%% src: %d:%d:%d:%d dst: %d:%d:%d:%d\n", zoom, + s.src.hori_off,s.src.vert_off,s.src.hori_size,s.src.vert_size, + s.des.hori_off,s.des.vert_off,s.des.hori_size,s.des.vert_size); + fop(ioctl, MPEG_VID_SCALE_ON); + fop(ioctl, MPEG_VID_SET_SCALE_POS, &s); + } + default: + break; + } + if (dmode != VID_DISPMODE_SCALE) + fop(ioctl, MPEG_VID_SCALE_OFF); + setCroppingMode(dmode); + } + const char *ds[] = { "4x3", "16x9", "2.21", "unknown" }; + const char *d; + if (dsize >=0 && dsize < 4) + d = ds[dsize]; + else + d = "invalid!"; + lt_debug("%s dispsize(%d) (%s)\n", __FUNCTION__, dsize, d); + fop(ioctl, MPEG_VID_SET_DISPSIZE, dsize); + + int avsfd = open("/dev/stb/tdsystem", O_RDONLY); + if (avsfd < 0) + { + perror("open tdsystem"); + return 0; + } + if (!noscart && scartvoltage > 0 && video_standby == 0) + { + lt_info("%s set SCART_PIN_8 to %dV\n", __FUNCTION__, scartvoltage); + if (ioctl(avsfd, IOC_AVS_SCART_PIN8_SET, scartvoltage) < 0) + perror("IOC_AVS_SCART_PIN8_SET"); + } + close(avsfd); + return 0; +} + +int cVideo::getAspectRatio(void) +{ + VIDEOINFO v; + /* this memset silences *TONS* of valgrind warnings */ + memset(&v, 0, sizeof(v)); + ioctl(fd, MPEG_VID_GET_V_INFO, &v); + if (v.pel_aspect_ratio < VID_DISPSIZE_4x3 || v.pel_aspect_ratio > VID_DISPSIZE_UNKNOWN) + { + lt_info("%s invalid value %d, returning 0/unknown fd: %d", __FUNCTION__, v.pel_aspect_ratio, fd); + return 0; + } + /* convert to Coolstream api values. Taken from streaminfo2.cpp */ + switch (v.pel_aspect_ratio) + { + case VID_DISPSIZE_4x3: + return 1; + case VID_DISPSIZE_16x9: + return 3; + case VID_DISPSIZE_221x100: + return 4; + default: + return 0; + } +} + +int cVideo::setCroppingMode(vidDispMode_t format) +{ + croppingMode = format; + const char *format_string[] = { "norm", "letterbox", "unknown", "mode_1_2", "mode_1_4", "mode_2x", "scale", "disexp" }; + const char *f; + if (format >= VID_DISPMODE_NORM && format <= VID_DISPMODE_DISEXP) + f = format_string[format]; + else + f = "ILLEGAL format!"; + lt_debug("%s(%d) => %s\n", __FUNCTION__, format, f); + return fop(ioctl, MPEG_VID_SET_DISPMODE, format); +} + +int cVideo::Start(void * /*PcrChannel*/, unsigned short /*PcrPid*/, unsigned short /*VideoPid*/, void * /*hChannel*/) +{ + lt_debug("%s playstate=%d\n", __FUNCTION__, playstate); + if (playstate == VIDEO_PLAYING) + return 0; + if (playstate == VIDEO_FREEZED) /* in theory better, but not in practice :-) */ + fop(ioctl, MPEG_VID_CONTINUE); + playstate = VIDEO_PLAYING; + fop(ioctl, MPEG_VID_PLAY); + return fop(ioctl, MPEG_VID_SYNC_ON, VID_SYNC_AUD); +} + +int cVideo::Stop(bool blank) +{ + lt_debug("%s(%d)\n", __FUNCTION__, blank); + if (blank) + { + playstate = VIDEO_STOPPED; + fop(ioctl, MPEG_VID_STOP); + return setBlank(1); + } + playstate = VIDEO_FREEZED; + return fop(ioctl, MPEG_VID_FREEZE); +} + +int cVideo::setBlank(int) +{ + lt_debug("%s\n", __FUNCTION__); + /* The TripleDragon has no VIDEO_SET_BLANK ioctl. + instead, you write a black still-MPEG Iframe into the decoder. + The original software uses different files for 4:3 and 16:9 and + for PAL and NTSC. I optimized that a little bit + */ + int index = 0; /* default PAL */ + int ret = 0; + VIDEOINFO v; + BUFINFO buf; + pthread_mutex_lock(&stillp_mutex); + memset(&v, 0, sizeof(v)); + ioctl(fd, MPEG_VID_GET_V_INFO, &v); + + if ((v.v_size % 240) == 0) /* NTSC */ + { + lt_info("%s NTSC format detected", __FUNCTION__); + index = 1; + } + + if (blank_data[index] == NULL) /* no MPEG found */ + { + ret = -1; + goto out; + } + /* hack: this might work only on those two still-MPEG files! + I diff'ed the 4:3 and the 16:9 still mpeg from the original + soft and spotted the single bit difference, so there is no + need to keep two different MPEGs in memory + If we would read them from disk all the time it would be + slower and it might wake up the drive occasionally */ + if (v.pel_aspect_ratio == VID_DISPSIZE_4x3) + ((char *)blank_data[index])[7] &= ~0x10; // clear the bit + else + ((char *)blank_data[index])[7] |= 0x10; // set the bit + + //WARN("blank[7] == 0x%02x", ((char *)blank_data[index])[7]); + + buf.ulLen = blank_size[index]; + buf.ulStartAdrOff = (int)blank_data[index]; + fop(ioctl, MPEG_VID_STILLP_WRITE, &buf); + ret = fop(ioctl, MPEG_VID_SELECT_SOURCE, VID_SOURCE_DEMUX); + out: + pthread_mutex_unlock(&stillp_mutex); + return ret; +} + +int cVideo::SetVideoSystem(int video_system, bool remember) +{ + lt_info("%s(%d, %d)\n", __FUNCTION__, video_system, remember); + if (video_system > VID_DISPFMT_SECAM || video_system < 0) + video_system = VID_DISPFMT_PAL; + return fop(ioctl, MPEG_VID_SET_DISPFMT, video_system); +} + +int cVideo::getPlayState(void) +{ + return playstate; +} + +void cVideo::SetVideoMode(analog_mode_t mode) +{ + lt_debug("%s(%d)\n", __FUNCTION__, mode); + switch(mode) + { + case ANALOG_SD_YPRPB_SCART: + outputformat = VID_OUTFMT_YBR_SVIDEO; + break; + case ANALOG_SD_RGB_SCART: + outputformat = VID_OUTFMT_RGBC_SVIDEO; + break; + default: + lt_info("%s unknown mode %d\n", __FUNCTION__, mode); + return; + } + fop(ioctl, MPEG_VID_SET_OUTFMT, outputformat); +} + +void cVideo::ShowPicture(const char * fname) +{ + lt_debug("%s(%s)\n", __FUNCTION__, fname); + char destname[512]; + char cmd[512]; + char *p; + void *data; + int mfd; + struct stat st, st2; + strcpy(destname, "/var/cache"); + if (stat(fname, &st2)) + { + lt_info("%s: could not stat %s (%m)\n", __func__, fname); + return; + } + mkdir(destname, 0755); + /* the cache filename is (example for /share/tuxbox/neutrino/icons/radiomode.jpg): + /var/cache/share.tuxbox.neutrino.icons.radiomode.jpg.m2v + build that filename first... + TODO: this could cause name clashes, use a hashing function instead... */ + strcat(destname, fname); + p = &destname[strlen("/var/cache/")]; + while ((p = strchr(p, '/')) != NULL) + *p = '.'; + strcat(destname, ".m2v"); + /* ...then check if it exists already... */ + if (stat(destname, &st) || (st.st_mtime != st2.st_mtime) || (st.st_size == 0)) + { + struct utimbuf u; + u.actime = time(NULL); + u.modtime = st2.st_mtime; + /* it does not exist or has a different date, so call ffmpeg... */ + sprintf(cmd, "ffmpeg -y -f mjpeg -i '%s' -s 704x576 '%s' 0) + { + data = malloc(st.st_size); + if (! data) + lt_info("%s malloc failed (%m)\n", __FUNCTION__); + else if (read(mfd, data, st.st_size) != st.st_size) + lt_info("%s short read (%m)\n", __FUNCTION__); + else + { + BUFINFO buf; + buf.ulLen = st.st_size; + buf.ulStartAdrOff = (int)data; + Stop(false); + fop(ioctl, MPEG_VID_STILLP_WRITE, &buf); + } + free(data); + } + close(mfd); + out: + pthread_mutex_unlock(&stillp_mutex); + return; +#if 0 + /* DirectFB based picviewer: works, but is slow and the infobar + draws in the same plane */ + int width; + int height; + if (!fname) + return; + + IDirectFBImageProvider *provider; + DFBResult err = dfb->CreateImageProvider(dfb, fname, &provider); + if (err) + { + fprintf(stderr, "cVideo::ShowPicture: CreateImageProvider error!\n"); + return; + } + + DFBSurfaceDescription desc; + provider->GetSurfaceDescription (provider, &desc); + width = desc.width; + height = desc.height; + provider->RenderTo(provider, dfbdest, NULL); + provider->Release(provider); +#endif +} + +void cVideo::StopPicture() +{ + lt_debug("%s\n", __FUNCTION__); + fop(ioctl, MPEG_VID_SELECT_SOURCE, VID_SOURCE_DEMUX); +} + +void cVideo::Standby(unsigned int bOn) +{ + lt_debug("%s(%d)\n", __FUNCTION__, bOn); + if (bOn) + { + setBlank(1); + fop(ioctl, MPEG_VID_SET_OUTFMT, VID_OUTFMT_DISABLE_DACS); + } else + fop(ioctl, MPEG_VID_SET_OUTFMT, outputformat); + routeVideo(bOn); + video_standby = bOn; +} + +int cVideo::getBlank(void) +{ + VIDEOINFO v; + memset(&v, 0, sizeof(v)); + ioctl(fd, MPEG_VID_GET_V_INFO, &v); + /* HACK HACK HACK :-) + * setBlank() puts a 24fps black mpeg into the decoder... + * regular broadcast does not have 24fps, so if it is still + * there, video did not decode... */ + lt_debug("%s: %hu (blank = 2)\n", __func__, v.frame_rate); + return (v.frame_rate == 2); +} + +/* set zoom in percent (100% == 1:1) */ +int cVideo::setZoom(int zoom) +{ + if (zoom == -1) // "auto" reset + zoom = *zoomvalue; + + if (zoom > 150 || zoom < 100) + return -1; + + *zoomvalue = zoom; + + if (zoom == 100) + { + setCroppingMode(croppingMode); + return fop(ioctl, MPEG_VID_SCALE_OFF); + } + + /* the SCALEINFO describes the source and destination of the scaled + video. "src" is the part of the source picture that gets scaled, + "dst" is the area on the screen where this part is displayed + Messing around with MPEG_VID_SET_SCALE_POS disables the automatic + letterboxing, which, as I guess, is only a special case of + MPEG_VID_SET_SCALE_POS. Therefor we need to care for letterboxing + etc here, which is probably not yet totally correct */ + SCALEINFO s; + memset(&s, 0, sizeof(s)); + if (zoom > 100) + { + /* 1 = 4:3, 3 = 16:9, 4 = 2.21:1, 0 = unknown */ + int x = getAspectRatio(); + if (x < 3 && croppingMode == VID_DISPMODE_NORM) + { + s.src.hori_size = 720; + s.des.hori_size = 720 * 3/4 * zoom / 100; + if (s.des.hori_size > 720) + { + /* the destination exceeds the screen size. + TODO: decrease source size to allow higher + zoom factors (is this useful ?) */ + s.des.hori_size = 720; + zoom = 133; // (720*4*100)/(720*3) + *zoomvalue = zoom; + } + } + else + { + s.src.hori_size = 2 * 720 - 720 * zoom / 100; + s.des.hori_size = 720; + } + s.src.vert_size = 2 * 576 - 576 * zoom / 100; + s.des.hori_off = (720 - s.des.hori_size) / 2; + s.des.vert_size = 576; + } +/* not working correctly (wrong formula) and does not make sense IMHO + else + { + s.src.hori_size = 720; + s.src.vert_size = 576; + s.des.hori_size = 720 * zoom / 100; + s.des.vert_size = 576 * zoom / 100; + s.des.hori_off = (720 - s.des.hori_size) / 2; + s.des.vert_off = (576 - s.des.vert_size) / 2; + } + */ + lt_debug("%s %d%% src: %d:%d:%d:%d dst: %d:%d:%d:%d\n", __FUNCTION__, zoom, + s.src.hori_off,s.src.vert_off,s.src.hori_size,s.src.vert_size, + s.des.hori_off,s.des.vert_off,s.des.hori_size,s.des.vert_size); + fop(ioctl, MPEG_VID_SET_DISPMODE, VID_DISPMODE_SCALE); + fop(ioctl, MPEG_VID_SCALE_ON); + return fop(ioctl, MPEG_VID_SET_SCALE_POS, &s); +} + +#if 0 +int cVideo::getZoom(void) +{ + return *zoomvalue; +} + +void cVideo::setZoomAspect(int index) +{ + if (index < 0 || index > 1) + WARN("index out of range"); + else + zoomvalue = &z[index]; +} +#endif + +/* this function is regularly called, checks if video parameters + changed and triggers appropriate actions */ +void cVideo::VideoParamWatchdog(void) +{ + static unsigned int _v_info = (unsigned int) -1; + unsigned int v_info; + if (fd == -1) + return; + ioctl(fd, MPEG_VID_GET_V_INFO_RAW, &v_info); + if (_v_info != v_info) + { + lt_debug("%s params changed. old: %08x new: %08x\n", __FUNCTION__, _v_info, v_info); + setAspectRatio(-1, -1); + } + _v_info = v_info; +} + +void cVideo::Pig(int x, int y, int w, int h, int /*osd_w*/, int /*osd_h*/) +{ + /* x = y = w = h = -1 -> reset / "hide" PIG */ + if (x == -1 && y == -1 && w == -1 && h == -1) + { + setZoom(-1); + setAspectRatio(-1, -1); + return; + } + SCALEINFO s; + memset(&s, 0, sizeof(s)); + s.src.hori_size = 720; + s.src.vert_size = 576; + s.des.hori_off = x; + s.des.vert_off = y; + s.des.hori_size = w; + s.des.vert_size = h; + lt_debug("%s src: %d:%d:%d:%d dst: %d:%d:%d:%d", __FUNCTION__, + s.src.hori_off,s.src.vert_off,s.src.hori_size,s.src.vert_size, + s.des.hori_off,s.des.vert_off,s.des.hori_size,s.des.vert_size); + fop(ioctl, MPEG_VID_SET_DISPMODE, VID_DISPMODE_SCALE); + fop(ioctl, MPEG_VID_SCALE_ON); + fop(ioctl, MPEG_VID_SET_SCALE_POS, &s); +} + +void cVideo::getPictureInfo(int &width, int &height, int &rate) +{ + VIDEOINFO v; + /* this memset silences *TONS* of valgrind warnings */ + memset(&v, 0, sizeof(v)); + ioctl(fd, MPEG_VID_GET_V_INFO, &v); + /* convert to Coolstream API */ + rate = (int)v.frame_rate - 1; + width = (int)v.h_size; + height = (int)v.v_size; +} + +void cVideo::SetSyncMode(AVSYNC_TYPE Mode) +{ + lt_debug("%s %d\n", __FUNCTION__, Mode); + /* + * { 0, LOCALE_OPTIONS_OFF }, + * { 1, LOCALE_OPTIONS_ON }, + * { 2, LOCALE_AUDIOMENU_AVSYNC_AM } + */ + switch(Mode) + { + case 0: + ioctl(fd, MPEG_VID_SYNC_OFF); + break; + case 1: + ioctl(fd, MPEG_VID_SYNC_ON, VID_SYNC_VID); + break; + default: + ioctl(fd, MPEG_VID_SYNC_ON, VID_SYNC_AUD); + break; + } +}; + +int cVideo::SetStreamType(VIDEO_FORMAT type) +{ + static const char *VF[] = { + "VIDEO_FORMAT_MPEG2", + "VIDEO_FORMAT_MPEG4", + "VIDEO_FORMAT_VC1", + "VIDEO_FORMAT_JPEG", + "VIDEO_FORMAT_GIF", + "VIDEO_FORMAT_PNG" + }; + + lt_debug("%s type=%s\n", __FUNCTION__, VF[type]); + return 0; +} + +void cVideo::routeVideo(int standby) +{ + lt_debug("%s(%d)\n", __FUNCTION__, standby); + + int avsfd = open("/dev/stb/tdsystem", O_RDONLY); + if (avsfd < 0) + { + perror("open tdsystem"); + return; + } + + /* in standby, we always route VCR scart to the TV. Once there is a UI + to configure this, we can think more about this... */ + if (standby) + { + lt_info("%s set fastblank and pin8 to follow VCR SCART, route VCR to TV\n", __FUNCTION__); + if (ioctl(avsfd, IOC_AVS_FASTBLANK_SET, (unsigned char)3) < 0) + perror("IOC_AVS_FASTBLANK_SET, 3"); + /* TODO: should probably depend on aspect ratio setting */ + if (ioctl(avsfd, IOC_AVS_SCART_PIN8_FOLLOW_VCR) < 0) + perror("IOC_AVS_SCART_PIN8_FOLLOW_VCR"); + if (ioctl(avsfd, IOC_AVS_ROUTE_VCR2TV) < 0) + perror("IOC_AVS_ROUTE_VCR2TV"); + } else { + unsigned char fblk = 1; + lt_info("%s set fastblank=%d pin8=%dV, route encoder to TV\n", __FUNCTION__, fblk, scartvoltage); + if (ioctl(avsfd, IOC_AVS_FASTBLANK_SET, fblk) < 0) + perror("IOC_AVS_FASTBLANK_SET, fblk"); + if (!noscart && ioctl(avsfd, IOC_AVS_SCART_PIN8_SET, scartvoltage) < 0) + perror("IOC_AVS_SCART_PIN8_SET"); + if (ioctl(avsfd, IOC_AVS_ROUTE_ENC2TV) < 0) + perror("IOC_AVS_ROUTE_ENC2TV"); + } + close(avsfd); +} + +void cVideo::FastForwardMode(int mode) +{ + lt_debug("%s\n", __FUNCTION__); + fop(ioctl, MPEG_VID_FASTFORWARD, mode); +} + +/* get an image of the video screen + * this code is inspired by dreambox AIO-grab, + * git://schwerkraft.elitedvb.net/aio-grab/aio-grab.git */ +/* static lookup tables for faster yuv2rgb conversion */ +static const int yuv2rgbtable_y[256] = { + 0xFFED5EA0, 0xFFEE88B6, 0xFFEFB2CC, 0xFFF0DCE2, 0xFFF206F8, 0xFFF3310E, 0xFFF45B24, 0xFFF5853A, + 0xFFF6AF50, 0xFFF7D966, 0xFFF9037C, 0xFFFA2D92, 0xFFFB57A8, 0xFFFC81BE, 0xFFFDABD4, 0xFFFED5EA, + 0x00000000, 0x00012A16, 0x0002542C, 0x00037E42, 0x0004A858, 0x0005D26E, 0x0006FC84, 0x0008269A, + 0x000950B0, 0x000A7AC6, 0x000BA4DC, 0x000CCEF2, 0x000DF908, 0x000F231E, 0x00104D34, 0x0011774A, + 0x0012A160, 0x0013CB76, 0x0014F58C, 0x00161FA2, 0x001749B8, 0x001873CE, 0x00199DE4, 0x001AC7FA, + 0x001BF210, 0x001D1C26, 0x001E463C, 0x001F7052, 0x00209A68, 0x0021C47E, 0x0022EE94, 0x002418AA, + 0x002542C0, 0x00266CD6, 0x002796EC, 0x0028C102, 0x0029EB18, 0x002B152E, 0x002C3F44, 0x002D695A, + 0x002E9370, 0x002FBD86, 0x0030E79C, 0x003211B2, 0x00333BC8, 0x003465DE, 0x00358FF4, 0x0036BA0A, + 0x0037E420, 0x00390E36, 0x003A384C, 0x003B6262, 0x003C8C78, 0x003DB68E, 0x003EE0A4, 0x00400ABA, + 0x004134D0, 0x00425EE6, 0x004388FC, 0x0044B312, 0x0045DD28, 0x0047073E, 0x00483154, 0x00495B6A, + 0x004A8580, 0x004BAF96, 0x004CD9AC, 0x004E03C2, 0x004F2DD8, 0x005057EE, 0x00518204, 0x0052AC1A, + 0x0053D630, 0x00550046, 0x00562A5C, 0x00575472, 0x00587E88, 0x0059A89E, 0x005AD2B4, 0x005BFCCA, + 0x005D26E0, 0x005E50F6, 0x005F7B0C, 0x0060A522, 0x0061CF38, 0x0062F94E, 0x00642364, 0x00654D7A, + 0x00667790, 0x0067A1A6, 0x0068CBBC, 0x0069F5D2, 0x006B1FE8, 0x006C49FE, 0x006D7414, 0x006E9E2A, + 0x006FC840, 0x0070F256, 0x00721C6C, 0x00734682, 0x00747098, 0x00759AAE, 0x0076C4C4, 0x0077EEDA, + 0x007918F0, 0x007A4306, 0x007B6D1C, 0x007C9732, 0x007DC148, 0x007EEB5E, 0x00801574, 0x00813F8A, + 0x008269A0, 0x008393B6, 0x0084BDCC, 0x0085E7E2, 0x008711F8, 0x00883C0E, 0x00896624, 0x008A903A, + 0x008BBA50, 0x008CE466, 0x008E0E7C, 0x008F3892, 0x009062A8, 0x00918CBE, 0x0092B6D4, 0x0093E0EA, + 0x00950B00, 0x00963516, 0x00975F2C, 0x00988942, 0x0099B358, 0x009ADD6E, 0x009C0784, 0x009D319A, + 0x009E5BB0, 0x009F85C6, 0x00A0AFDC, 0x00A1D9F2, 0x00A30408, 0x00A42E1E, 0x00A55834, 0x00A6824A, + 0x00A7AC60, 0x00A8D676, 0x00AA008C, 0x00AB2AA2, 0x00AC54B8, 0x00AD7ECE, 0x00AEA8E4, 0x00AFD2FA, + 0x00B0FD10, 0x00B22726, 0x00B3513C, 0x00B47B52, 0x00B5A568, 0x00B6CF7E, 0x00B7F994, 0x00B923AA, + 0x00BA4DC0, 0x00BB77D6, 0x00BCA1EC, 0x00BDCC02, 0x00BEF618, 0x00C0202E, 0x00C14A44, 0x00C2745A, + 0x00C39E70, 0x00C4C886, 0x00C5F29C, 0x00C71CB2, 0x00C846C8, 0x00C970DE, 0x00CA9AF4, 0x00CBC50A, + 0x00CCEF20, 0x00CE1936, 0x00CF434C, 0x00D06D62, 0x00D19778, 0x00D2C18E, 0x00D3EBA4, 0x00D515BA, + 0x00D63FD0, 0x00D769E6, 0x00D893FC, 0x00D9BE12, 0x00DAE828, 0x00DC123E, 0x00DD3C54, 0x00DE666A, + 0x00DF9080, 0x00E0BA96, 0x00E1E4AC, 0x00E30EC2, 0x00E438D8, 0x00E562EE, 0x00E68D04, 0x00E7B71A, + 0x00E8E130, 0x00EA0B46, 0x00EB355C, 0x00EC5F72, 0x00ED8988, 0x00EEB39E, 0x00EFDDB4, 0x00F107CA, + 0x00F231E0, 0x00F35BF6, 0x00F4860C, 0x00F5B022, 0x00F6DA38, 0x00F8044E, 0x00F92E64, 0x00FA587A, + 0x00FB8290, 0x00FCACA6, 0x00FDD6BC, 0x00FF00D2, 0x01002AE8, 0x010154FE, 0x01027F14, 0x0103A92A, + 0x0104D340, 0x0105FD56, 0x0107276C, 0x01085182, 0x01097B98, 0x010AA5AE, 0x010BCFC4, 0x010CF9DA, + 0x010E23F0, 0x010F4E06, 0x0110781C, 0x0111A232, 0x0112CC48, 0x0113F65E, 0x01152074, 0x01164A8A +}; +static const int yuv2rgbtable_ru[256] = { + 0xFEFDA500, 0xFEFFA9B6, 0xFF01AE6C, 0xFF03B322, 0xFF05B7D8, 0xFF07BC8E, 0xFF09C144, 0xFF0BC5FA, + 0xFF0DCAB0, 0xFF0FCF66, 0xFF11D41C, 0xFF13D8D2, 0xFF15DD88, 0xFF17E23E, 0xFF19E6F4, 0xFF1BEBAA, + 0xFF1DF060, 0xFF1FF516, 0xFF21F9CC, 0xFF23FE82, 0xFF260338, 0xFF2807EE, 0xFF2A0CA4, 0xFF2C115A, + 0xFF2E1610, 0xFF301AC6, 0xFF321F7C, 0xFF342432, 0xFF3628E8, 0xFF382D9E, 0xFF3A3254, 0xFF3C370A, + 0xFF3E3BC0, 0xFF404076, 0xFF42452C, 0xFF4449E2, 0xFF464E98, 0xFF48534E, 0xFF4A5804, 0xFF4C5CBA, + 0xFF4E6170, 0xFF506626, 0xFF526ADC, 0xFF546F92, 0xFF567448, 0xFF5878FE, 0xFF5A7DB4, 0xFF5C826A, + 0xFF5E8720, 0xFF608BD6, 0xFF62908C, 0xFF649542, 0xFF6699F8, 0xFF689EAE, 0xFF6AA364, 0xFF6CA81A, + 0xFF6EACD0, 0xFF70B186, 0xFF72B63C, 0xFF74BAF2, 0xFF76BFA8, 0xFF78C45E, 0xFF7AC914, 0xFF7CCDCA, + 0xFF7ED280, 0xFF80D736, 0xFF82DBEC, 0xFF84E0A2, 0xFF86E558, 0xFF88EA0E, 0xFF8AEEC4, 0xFF8CF37A, + 0xFF8EF830, 0xFF90FCE6, 0xFF93019C, 0xFF950652, 0xFF970B08, 0xFF990FBE, 0xFF9B1474, 0xFF9D192A, + 0xFF9F1DE0, 0xFFA12296, 0xFFA3274C, 0xFFA52C02, 0xFFA730B8, 0xFFA9356E, 0xFFAB3A24, 0xFFAD3EDA, + 0xFFAF4390, 0xFFB14846, 0xFFB34CFC, 0xFFB551B2, 0xFFB75668, 0xFFB95B1E, 0xFFBB5FD4, 0xFFBD648A, + 0xFFBF6940, 0xFFC16DF6, 0xFFC372AC, 0xFFC57762, 0xFFC77C18, 0xFFC980CE, 0xFFCB8584, 0xFFCD8A3A, + 0xFFCF8EF0, 0xFFD193A6, 0xFFD3985C, 0xFFD59D12, 0xFFD7A1C8, 0xFFD9A67E, 0xFFDBAB34, 0xFFDDAFEA, + 0xFFDFB4A0, 0xFFE1B956, 0xFFE3BE0C, 0xFFE5C2C2, 0xFFE7C778, 0xFFE9CC2E, 0xFFEBD0E4, 0xFFEDD59A, + 0xFFEFDA50, 0xFFF1DF06, 0xFFF3E3BC, 0xFFF5E872, 0xFFF7ED28, 0xFFF9F1DE, 0xFFFBF694, 0xFFFDFB4A, + 0x00000000, 0x000204B6, 0x0004096C, 0x00060E22, 0x000812D8, 0x000A178E, 0x000C1C44, 0x000E20FA, + 0x001025B0, 0x00122A66, 0x00142F1C, 0x001633D2, 0x00183888, 0x001A3D3E, 0x001C41F4, 0x001E46AA, + 0x00204B60, 0x00225016, 0x002454CC, 0x00265982, 0x00285E38, 0x002A62EE, 0x002C67A4, 0x002E6C5A, + 0x00307110, 0x003275C6, 0x00347A7C, 0x00367F32, 0x003883E8, 0x003A889E, 0x003C8D54, 0x003E920A, + 0x004096C0, 0x00429B76, 0x0044A02C, 0x0046A4E2, 0x0048A998, 0x004AAE4E, 0x004CB304, 0x004EB7BA, + 0x0050BC70, 0x0052C126, 0x0054C5DC, 0x0056CA92, 0x0058CF48, 0x005AD3FE, 0x005CD8B4, 0x005EDD6A, + 0x0060E220, 0x0062E6D6, 0x0064EB8C, 0x0066F042, 0x0068F4F8, 0x006AF9AE, 0x006CFE64, 0x006F031A, + 0x007107D0, 0x00730C86, 0x0075113C, 0x007715F2, 0x00791AA8, 0x007B1F5E, 0x007D2414, 0x007F28CA, + 0x00812D80, 0x00833236, 0x008536EC, 0x00873BA2, 0x00894058, 0x008B450E, 0x008D49C4, 0x008F4E7A, + 0x00915330, 0x009357E6, 0x00955C9C, 0x00976152, 0x00996608, 0x009B6ABE, 0x009D6F74, 0x009F742A, + 0x00A178E0, 0x00A37D96, 0x00A5824C, 0x00A78702, 0x00A98BB8, 0x00AB906E, 0x00AD9524, 0x00AF99DA, + 0x00B19E90, 0x00B3A346, 0x00B5A7FC, 0x00B7ACB2, 0x00B9B168, 0x00BBB61E, 0x00BDBAD4, 0x00BFBF8A, + 0x00C1C440, 0x00C3C8F6, 0x00C5CDAC, 0x00C7D262, 0x00C9D718, 0x00CBDBCE, 0x00CDE084, 0x00CFE53A, + 0x00D1E9F0, 0x00D3EEA6, 0x00D5F35C, 0x00D7F812, 0x00D9FCC8, 0x00DC017E, 0x00DE0634, 0x00E00AEA, + 0x00E20FA0, 0x00E41456, 0x00E6190C, 0x00E81DC2, 0x00EA2278, 0x00EC272E, 0x00EE2BE4, 0x00F0309A, + 0x00F23550, 0x00F43A06, 0x00F63EBC, 0x00F84372, 0x00FA4828, 0x00FC4CDE, 0x00FE5194, 0x00100564A +}; +static const int yuv2rgbtable_gu[256] = { + 0xFFCDD300, 0xFFCE375A, 0xFFCE9BB4, 0xFFCF000E, 0xFFCF6468, 0xFFCFC8C2, 0xFFD02D1C, 0xFFD09176, + 0xFFD0F5D0, 0xFFD15A2A, 0xFFD1BE84, 0xFFD222DE, 0xFFD28738, 0xFFD2EB92, 0xFFD34FEC, 0xFFD3B446, + 0xFFD418A0, 0xFFD47CFA, 0xFFD4E154, 0xFFD545AE, 0xFFD5AA08, 0xFFD60E62, 0xFFD672BC, 0xFFD6D716, + 0xFFD73B70, 0xFFD79FCA, 0xFFD80424, 0xFFD8687E, 0xFFD8CCD8, 0xFFD93132, 0xFFD9958C, 0xFFD9F9E6, + 0xFFDA5E40, 0xFFDAC29A, 0xFFDB26F4, 0xFFDB8B4E, 0xFFDBEFA8, 0xFFDC5402, 0xFFDCB85C, 0xFFDD1CB6, + 0xFFDD8110, 0xFFDDE56A, 0xFFDE49C4, 0xFFDEAE1E, 0xFFDF1278, 0xFFDF76D2, 0xFFDFDB2C, 0xFFE03F86, + 0xFFE0A3E0, 0xFFE1083A, 0xFFE16C94, 0xFFE1D0EE, 0xFFE23548, 0xFFE299A2, 0xFFE2FDFC, 0xFFE36256, + 0xFFE3C6B0, 0xFFE42B0A, 0xFFE48F64, 0xFFE4F3BE, 0xFFE55818, 0xFFE5BC72, 0xFFE620CC, 0xFFE68526, + 0xFFE6E980, 0xFFE74DDA, 0xFFE7B234, 0xFFE8168E, 0xFFE87AE8, 0xFFE8DF42, 0xFFE9439C, 0xFFE9A7F6, + 0xFFEA0C50, 0xFFEA70AA, 0xFFEAD504, 0xFFEB395E, 0xFFEB9DB8, 0xFFEC0212, 0xFFEC666C, 0xFFECCAC6, + 0xFFED2F20, 0xFFED937A, 0xFFEDF7D4, 0xFFEE5C2E, 0xFFEEC088, 0xFFEF24E2, 0xFFEF893C, 0xFFEFED96, + 0xFFF051F0, 0xFFF0B64A, 0xFFF11AA4, 0xFFF17EFE, 0xFFF1E358, 0xFFF247B2, 0xFFF2AC0C, 0xFFF31066, + 0xFFF374C0, 0xFFF3D91A, 0xFFF43D74, 0xFFF4A1CE, 0xFFF50628, 0xFFF56A82, 0xFFF5CEDC, 0xFFF63336, + 0xFFF69790, 0xFFF6FBEA, 0xFFF76044, 0xFFF7C49E, 0xFFF828F8, 0xFFF88D52, 0xFFF8F1AC, 0xFFF95606, + 0xFFF9BA60, 0xFFFA1EBA, 0xFFFA8314, 0xFFFAE76E, 0xFFFB4BC8, 0xFFFBB022, 0xFFFC147C, 0xFFFC78D6, + 0xFFFCDD30, 0xFFFD418A, 0xFFFDA5E4, 0xFFFE0A3E, 0xFFFE6E98, 0xFFFED2F2, 0xFFFF374C, 0xFFFF9BA6, + 0x00000000, 0x0000645A, 0x0000C8B4, 0x00012D0E, 0x00019168, 0x0001F5C2, 0x00025A1C, 0x0002BE76, + 0x000322D0, 0x0003872A, 0x0003EB84, 0x00044FDE, 0x0004B438, 0x00051892, 0x00057CEC, 0x0005E146, + 0x000645A0, 0x0006A9FA, 0x00070E54, 0x000772AE, 0x0007D708, 0x00083B62, 0x00089FBC, 0x00090416, + 0x00096870, 0x0009CCCA, 0x000A3124, 0x000A957E, 0x000AF9D8, 0x000B5E32, 0x000BC28C, 0x000C26E6, + 0x000C8B40, 0x000CEF9A, 0x000D53F4, 0x000DB84E, 0x000E1CA8, 0x000E8102, 0x000EE55C, 0x000F49B6, + 0x000FAE10, 0x0010126A, 0x001076C4, 0x0010DB1E, 0x00113F78, 0x0011A3D2, 0x0012082C, 0x00126C86, + 0x0012D0E0, 0x0013353A, 0x00139994, 0x0013FDEE, 0x00146248, 0x0014C6A2, 0x00152AFC, 0x00158F56, + 0x0015F3B0, 0x0016580A, 0x0016BC64, 0x001720BE, 0x00178518, 0x0017E972, 0x00184DCC, 0x0018B226, + 0x00191680, 0x00197ADA, 0x0019DF34, 0x001A438E, 0x001AA7E8, 0x001B0C42, 0x001B709C, 0x001BD4F6, + 0x001C3950, 0x001C9DAA, 0x001D0204, 0x001D665E, 0x001DCAB8, 0x001E2F12, 0x001E936C, 0x001EF7C6, + 0x001F5C20, 0x001FC07A, 0x002024D4, 0x0020892E, 0x0020ED88, 0x002151E2, 0x0021B63C, 0x00221A96, + 0x00227EF0, 0x0022E34A, 0x002347A4, 0x0023ABFE, 0x00241058, 0x002474B2, 0x0024D90C, 0x00253D66, + 0x0025A1C0, 0x0026061A, 0x00266A74, 0x0026CECE, 0x00273328, 0x00279782, 0x0027FBDC, 0x00286036, + 0x0028C490, 0x002928EA, 0x00298D44, 0x0029F19E, 0x002A55F8, 0x002ABA52, 0x002B1EAC, 0x002B8306, + 0x002BE760, 0x002C4BBA, 0x002CB014, 0x002D146E, 0x002D78C8, 0x002DDD22, 0x002E417C, 0x002EA5D6, + 0x002F0A30, 0x002F6E8A, 0x002FD2E4, 0x0030373E, 0x00309B98, 0x0030FFF2, 0x0031644C, 0x0031C8A6 +}; +static const int yuv2rgbtable_gv[256] = { + 0xFF97E900, 0xFF98B92E, 0xFF99895C, 0xFF9A598A, 0xFF9B29B8, 0xFF9BF9E6, 0xFF9CCA14, 0xFF9D9A42, + 0xFF9E6A70, 0xFF9F3A9E, 0xFFA00ACC, 0xFFA0DAFA, 0xFFA1AB28, 0xFFA27B56, 0xFFA34B84, 0xFFA41BB2, + 0xFFA4EBE0, 0xFFA5BC0E, 0xFFA68C3C, 0xFFA75C6A, 0xFFA82C98, 0xFFA8FCC6, 0xFFA9CCF4, 0xFFAA9D22, + 0xFFAB6D50, 0xFFAC3D7E, 0xFFAD0DAC, 0xFFADDDDA, 0xFFAEAE08, 0xFFAF7E36, 0xFFB04E64, 0xFFB11E92, + 0xFFB1EEC0, 0xFFB2BEEE, 0xFFB38F1C, 0xFFB45F4A, 0xFFB52F78, 0xFFB5FFA6, 0xFFB6CFD4, 0xFFB7A002, + 0xFFB87030, 0xFFB9405E, 0xFFBA108C, 0xFFBAE0BA, 0xFFBBB0E8, 0xFFBC8116, 0xFFBD5144, 0xFFBE2172, + 0xFFBEF1A0, 0xFFBFC1CE, 0xFFC091FC, 0xFFC1622A, 0xFFC23258, 0xFFC30286, 0xFFC3D2B4, 0xFFC4A2E2, + 0xFFC57310, 0xFFC6433E, 0xFFC7136C, 0xFFC7E39A, 0xFFC8B3C8, 0xFFC983F6, 0xFFCA5424, 0xFFCB2452, + 0xFFCBF480, 0xFFCCC4AE, 0xFFCD94DC, 0xFFCE650A, 0xFFCF3538, 0xFFD00566, 0xFFD0D594, 0xFFD1A5C2, + 0xFFD275F0, 0xFFD3461E, 0xFFD4164C, 0xFFD4E67A, 0xFFD5B6A8, 0xFFD686D6, 0xFFD75704, 0xFFD82732, + 0xFFD8F760, 0xFFD9C78E, 0xFFDA97BC, 0xFFDB67EA, 0xFFDC3818, 0xFFDD0846, 0xFFDDD874, 0xFFDEA8A2, + 0xFFDF78D0, 0xFFE048FE, 0xFFE1192C, 0xFFE1E95A, 0xFFE2B988, 0xFFE389B6, 0xFFE459E4, 0xFFE52A12, + 0xFFE5FA40, 0xFFE6CA6E, 0xFFE79A9C, 0xFFE86ACA, 0xFFE93AF8, 0xFFEA0B26, 0xFFEADB54, 0xFFEBAB82, + 0xFFEC7BB0, 0xFFED4BDE, 0xFFEE1C0C, 0xFFEEEC3A, 0xFFEFBC68, 0xFFF08C96, 0xFFF15CC4, 0xFFF22CF2, + 0xFFF2FD20, 0xFFF3CD4E, 0xFFF49D7C, 0xFFF56DAA, 0xFFF63DD8, 0xFFF70E06, 0xFFF7DE34, 0xFFF8AE62, + 0xFFF97E90, 0xFFFA4EBE, 0xFFFB1EEC, 0xFFFBEF1A, 0xFFFCBF48, 0xFFFD8F76, 0xFFFE5FA4, 0xFFFF2FD2, + 0x00000000, 0x0000D02E, 0x0001A05C, 0x0002708A, 0x000340B8, 0x000410E6, 0x0004E114, 0x0005B142, + 0x00068170, 0x0007519E, 0x000821CC, 0x0008F1FA, 0x0009C228, 0x000A9256, 0x000B6284, 0x000C32B2, + 0x000D02E0, 0x000DD30E, 0x000EA33C, 0x000F736A, 0x00104398, 0x001113C6, 0x0011E3F4, 0x0012B422, + 0x00138450, 0x0014547E, 0x001524AC, 0x0015F4DA, 0x0016C508, 0x00179536, 0x00186564, 0x00193592, + 0x001A05C0, 0x001AD5EE, 0x001BA61C, 0x001C764A, 0x001D4678, 0x001E16A6, 0x001EE6D4, 0x001FB702, + 0x00208730, 0x0021575E, 0x0022278C, 0x0022F7BA, 0x0023C7E8, 0x00249816, 0x00256844, 0x00263872, + 0x002708A0, 0x0027D8CE, 0x0028A8FC, 0x0029792A, 0x002A4958, 0x002B1986, 0x002BE9B4, 0x002CB9E2, + 0x002D8A10, 0x002E5A3E, 0x002F2A6C, 0x002FFA9A, 0x0030CAC8, 0x00319AF6, 0x00326B24, 0x00333B52, + 0x00340B80, 0x0034DBAE, 0x0035ABDC, 0x00367C0A, 0x00374C38, 0x00381C66, 0x0038EC94, 0x0039BCC2, + 0x003A8CF0, 0x003B5D1E, 0x003C2D4C, 0x003CFD7A, 0x003DCDA8, 0x003E9DD6, 0x003F6E04, 0x00403E32, + 0x00410E60, 0x0041DE8E, 0x0042AEBC, 0x00437EEA, 0x00444F18, 0x00451F46, 0x0045EF74, 0x0046BFA2, + 0x00478FD0, 0x00485FFE, 0x0049302C, 0x004A005A, 0x004AD088, 0x004BA0B6, 0x004C70E4, 0x004D4112, + 0x004E1140, 0x004EE16E, 0x004FB19C, 0x005081CA, 0x005151F8, 0x00522226, 0x0052F254, 0x0053C282, + 0x005492B0, 0x005562DE, 0x0056330C, 0x0057033A, 0x0057D368, 0x0058A396, 0x005973C4, 0x005A43F2, + 0x005B1420, 0x005BE44E, 0x005CB47C, 0x005D84AA, 0x005E54D8, 0x005F2506, 0x005FF534, 0x0060C562, + 0x00619590, 0x006265BE, 0x006335EC, 0x0064061A, 0x0064D648, 0x0065A676, 0x006676A4, 0x006746D2 +}; +static const int yuv2rgbtable_bv[256] = { + 0xFF33A280, 0xFF353B3B, 0xFF36D3F6, 0xFF386CB1, 0xFF3A056C, 0xFF3B9E27, 0xFF3D36E2, 0xFF3ECF9D, + 0xFF406858, 0xFF420113, 0xFF4399CE, 0xFF453289, 0xFF46CB44, 0xFF4863FF, 0xFF49FCBA, 0xFF4B9575, + 0xFF4D2E30, 0xFF4EC6EB, 0xFF505FA6, 0xFF51F861, 0xFF53911C, 0xFF5529D7, 0xFF56C292, 0xFF585B4D, + 0xFF59F408, 0xFF5B8CC3, 0xFF5D257E, 0xFF5EBE39, 0xFF6056F4, 0xFF61EFAF, 0xFF63886A, 0xFF652125, + 0xFF66B9E0, 0xFF68529B, 0xFF69EB56, 0xFF6B8411, 0xFF6D1CCC, 0xFF6EB587, 0xFF704E42, 0xFF71E6FD, + 0xFF737FB8, 0xFF751873, 0xFF76B12E, 0xFF7849E9, 0xFF79E2A4, 0xFF7B7B5F, 0xFF7D141A, 0xFF7EACD5, + 0xFF804590, 0xFF81DE4B, 0xFF837706, 0xFF850FC1, 0xFF86A87C, 0xFF884137, 0xFF89D9F2, 0xFF8B72AD, + 0xFF8D0B68, 0xFF8EA423, 0xFF903CDE, 0xFF91D599, 0xFF936E54, 0xFF95070F, 0xFF969FCA, 0xFF983885, + 0xFF99D140, 0xFF9B69FB, 0xFF9D02B6, 0xFF9E9B71, 0xFFA0342C, 0xFFA1CCE7, 0xFFA365A2, 0xFFA4FE5D, + 0xFFA69718, 0xFFA82FD3, 0xFFA9C88E, 0xFFAB6149, 0xFFACFA04, 0xFFAE92BF, 0xFFB02B7A, 0xFFB1C435, + 0xFFB35CF0, 0xFFB4F5AB, 0xFFB68E66, 0xFFB82721, 0xFFB9BFDC, 0xFFBB5897, 0xFFBCF152, 0xFFBE8A0D, + 0xFFC022C8, 0xFFC1BB83, 0xFFC3543E, 0xFFC4ECF9, 0xFFC685B4, 0xFFC81E6F, 0xFFC9B72A, 0xFFCB4FE5, + 0xFFCCE8A0, 0xFFCE815B, 0xFFD01A16, 0xFFD1B2D1, 0xFFD34B8C, 0xFFD4E447, 0xFFD67D02, 0xFFD815BD, + 0xFFD9AE78, 0xFFDB4733, 0xFFDCDFEE, 0xFFDE78A9, 0xFFE01164, 0xFFE1AA1F, 0xFFE342DA, 0xFFE4DB95, + 0xFFE67450, 0xFFE80D0B, 0xFFE9A5C6, 0xFFEB3E81, 0xFFECD73C, 0xFFEE6FF7, 0xFFF008B2, 0xFFF1A16D, + 0xFFF33A28, 0xFFF4D2E3, 0xFFF66B9E, 0xFFF80459, 0xFFF99D14, 0xFFFB35CF, 0xFFFCCE8A, 0xFFFE6745, + 0x00000000, 0x000198BB, 0x00033176, 0x0004CA31, 0x000662EC, 0x0007FBA7, 0x00099462, 0x000B2D1D, + 0x000CC5D8, 0x000E5E93, 0x000FF74E, 0x00119009, 0x001328C4, 0x0014C17F, 0x00165A3A, 0x0017F2F5, + 0x00198BB0, 0x001B246B, 0x001CBD26, 0x001E55E1, 0x001FEE9C, 0x00218757, 0x00232012, 0x0024B8CD, + 0x00265188, 0x0027EA43, 0x002982FE, 0x002B1BB9, 0x002CB474, 0x002E4D2F, 0x002FE5EA, 0x00317EA5, + 0x00331760, 0x0034B01B, 0x003648D6, 0x0037E191, 0x00397A4C, 0x003B1307, 0x003CABC2, 0x003E447D, + 0x003FDD38, 0x004175F3, 0x00430EAE, 0x0044A769, 0x00464024, 0x0047D8DF, 0x0049719A, 0x004B0A55, + 0x004CA310, 0x004E3BCB, 0x004FD486, 0x00516D41, 0x005305FC, 0x00549EB7, 0x00563772, 0x0057D02D, + 0x005968E8, 0x005B01A3, 0x005C9A5E, 0x005E3319, 0x005FCBD4, 0x0061648F, 0x0062FD4A, 0x00649605, + 0x00662EC0, 0x0067C77B, 0x00696036, 0x006AF8F1, 0x006C91AC, 0x006E2A67, 0x006FC322, 0x00715BDD, + 0x0072F498, 0x00748D53, 0x0076260E, 0x0077BEC9, 0x00795784, 0x007AF03F, 0x007C88FA, 0x007E21B5, + 0x007FBA70, 0x0081532B, 0x0082EBE6, 0x008484A1, 0x00861D5C, 0x0087B617, 0x00894ED2, 0x008AE78D, + 0x008C8048, 0x008E1903, 0x008FB1BE, 0x00914A79, 0x0092E334, 0x00947BEF, 0x009614AA, 0x0097AD65, + 0x00994620, 0x009ADEDB, 0x009C7796, 0x009E1051, 0x009FA90C, 0x00A141C7, 0x00A2DA82, 0x00A4733D, + 0x00A60BF8, 0x00A7A4B3, 0x00A93D6E, 0x00AAD629, 0x00AC6EE4, 0x00AE079F, 0x00AFA05A, 0x00B13915, + 0x00B2D1D0, 0x00B46A8B, 0x00B60346, 0x00B79C01, 0x00B934BC, 0x00BACD77, 0x00BC6632, 0x00BDFEED, + 0x00BF97A8, 0x00C13063, 0x00C2C91E, 0x00C461D9, 0x00C5FA94, 0x00C7934F, 0x00C92C0A, 0x00CAC4C5 +}; + +#define CLAMP(x) ((x < 0) ? 0 : ((x > 255) ? 255 : x)) +#define SWAP(x,y) { x ^= y; y ^= x; x ^= y; } +#define VIDEO_MEM _MPEG_VIDEO_MEM_BASE +#define VIDEO_SIZE _MPEG_VIDEO_MEM_SIZE +#define WIDTH_OFF (VID_USER_MEM_BASE + 0x0100) +#define HEIGHT_OFF (VID_USER_MEM_BASE + 0x0102) +#define GFXFB_MEM _GFX_FB_MEM_BASE +#define GFXFB_SIZE _GFX_FB_MEM_SIZE +/* TODO: aspect ratio correction and PIP */ +bool cVideo::GetScreenImage(unsigned char * &video, int &xres, int &yres, bool get_video, bool get_osd, bool /*scale_to_video*/) +{ + lt_info("%s: get_video: %d get_osd: %d\n", __func__, get_video, get_osd); + uint8_t *map; + int mfd = open("/dev/mem", O_RDWR); + if (mfd < 0) { + lt_info("%s: cannot open open /dev/mem (%m)\n", __func__); + return false; + } + /* this hints at incorrect usage */ + if (video != NULL) + lt_info("%s: WARNING, video != NULL?\n", __func__); + + if (get_video) + { + map = (uint8_t *)mmap(NULL, VIDEO_SIZE, PROT_READ, MAP_SHARED, mfd, VIDEO_MEM); + if (map == MAP_FAILED) { + lt_info("%s: cannot mmap /dev/mem vor VIDEO (%m)\n", __func__); + close(mfd); + return false; + } + uint16_t w = *(uint16_t *)(map + WIDTH_OFF); + uint16_t h = *(uint16_t *)(map + HEIGHT_OFF); + if (w > 720 || h > 576) { + lt_info("%s: unhandled resolution %dx%d, is the tuner locked?\n", __func__, w, h); + munmap(map, VIDEO_SIZE); + close(mfd); + return false; + } + uint8_t *luma, *chroma; + int needmem = w * h * 5 / 4; /* chroma is 1/4 in size of luma */ + int lumasize = w * h; + int chromasize = needmem - lumasize; + uint8_t *buf = (uint8_t *)malloc(needmem); + if (!buf) { + lt_info("%s: cannot allocate %d bytes (%m)\n", __func__, needmem); + munmap(map, VIDEO_SIZE); + close(mfd); + return false; + } + /* luma is at the beginning of the buffer */ + memcpy(buf, map, lumasize); + /* it looks like the chroma plane is always 720*576 bytes offset to the luma plane */ + memcpy(buf + lumasize, map + 720 * 576, chromasize); + /* release the video buffer */ + munmap(map, VIDEO_SIZE); + + if (get_osd) + { /* in this case, the framebuffer determines the output resolution */ + xres = 720; + yres = 576; + } + else + { + xres = w; + yres = h; + } + video = (unsigned char *)malloc(xres * yres * 4); + if (!video) { + lt_info("%s: cannot allocate %d bytes for video buffer (%m)\n", __func__, yres * yres * 4); + free(buf); + close(mfd); + return false; + } + + luma = buf; + chroma = buf + lumasize; + + int Y, U, V, y, x, out1, pos, RU, GU, GV, BV, rgbstride, i; + + // yuv2rgb conversion (4:2:0) + lt_info("%s: converting Video from YUV to RGB color space\n", __func__); + out1 = pos = 0; + rgbstride = w * 4; + + for (y = h; y != 0; y -= 2) + { + for (x = w; x != 0; x -= 4) + { + U = *chroma++; + V = *chroma++; + RU = yuv2rgbtable_ru[U]; // use lookup tables to speedup the whole thing + GU = yuv2rgbtable_gu[U]; + GV = yuv2rgbtable_gv[V]; + BV = yuv2rgbtable_bv[V]; + // now we do 8 pixels on each iteration this is more code but much faster + for (i = 0; i < 4; i++) + { + Y = yuv2rgbtable_y[luma[pos]]; + video[out1 ] = CLAMP((Y + RU) >> 16); + video[out1 + 1] = CLAMP((Y - GV - GU) >> 16); + video[out1 + 2] = CLAMP((Y + BV) >> 16); + video[out1 + 3] = 0xff; + + Y = yuv2rgbtable_y[luma[w + pos]]; + video[out1 + rgbstride] = CLAMP((Y + RU) >> 16); + video[out1 + 1 + rgbstride] = CLAMP((Y - GV - GU) >> 16); + video[out1 + 2 + rgbstride] = CLAMP((Y + BV) >> 16); + video[out1 + 3 + rgbstride] = 0xff; + + pos++; + out1 += 4; + } + } + out1 += rgbstride; + pos += w; + } + if (get_osd && (w < xres || h < yres)) + { + /* most trivial scaling algorithm: + * - no smoothing/antialiasing or similar + * - only upscaling + * more or less just memcpy + * works "backwards" (from bottom right up left), + * so that no extra buffer is needed */ + int j, k; + uint32_t *v = (uint32_t *)video; /* int pointer to the video buffer */ + uint32_t *srcline; /* the start of the current line (unscaled) */ + uint32_t *dst = v + xres * yres; /* the current scaled pixel */ + for (j = yres -1 ; j >= 0; j--) + { + srcline = v + (((j * h) / yres) * w); + for (k = xres - 1; k >= 0; k--) { + dst--; + *dst = *(srcline + ((k * w) / xres)); + } + } + } + lt_info("%s: Video-Size: %d x %d\n", __func__, xres, yres); + free(buf); + } + if (get_osd) + { + if (! get_video) + { + /* we don't use other framebuffer resolutions */ + xres = 720; + yres = 576; + video = (unsigned char *)calloc(xres * yres, 4); + if (!video) + { + lt_info("%s: cannot allocate %d bytes for video buffer (%m)\n", __func__, yres * yres * 4); + close(mfd); + return false; + } + } + /* we don't need the framebufferdevice, we know where the FB is located */ + map = (uint8_t *)mmap(NULL, GFXFB_SIZE, PROT_READ, MAP_SHARED, mfd, GFXFB_MEM); + if (map == MAP_FAILED) { + lt_info("%s: cannot mmap /dev/mem for GFXFB (%m)\n", __func__); + close(mfd); + return false; + } + unsigned int i, r, g, b, a, a2; + uint8_t *p = map; + uint8_t *q = video; + for (i = xres * yres; i != 0; i--) + { + a = *p++; + r = *p++; + g = *p++; + b = *p++; + a2 = 0xff - a; + /* blue */ + *q = ((*q * a2 ) + (b * a)) >> 8; + q++; + /* green */ + *q = ((*q * a2 ) + (g * a)) >> 8; + q++; + /* red */ + *q = ((*q * a2 ) + (r * a)) >> 8; + q++; + q++; /* skip alpha byte */ + } + munmap(map, GFXFB_SIZE); + } + close(mfd); + return true; +} + +void cVideo::SetDemux(cDemux *) +{ + lt_debug("%s: not implemented yet\n", __func__); +} diff --git a/libtriple/video_td.h b/libtriple/video_td.h new file mode 100644 index 0000000..d689420 --- /dev/null +++ b/libtriple/video_td.h @@ -0,0 +1,196 @@ +#ifndef _VIDEO_TD_H +#define _VIDEO_TD_H + +#include +#define video_format_t vidDispSize_t +//#define video_displayformat_t vidDispMode_t +#include "../common/cs_types.h" +#include "dmx_td.h" + +#define STB_HAL_VIDEO_HAS_GETSCREENIMAGE 1 + +typedef enum { + ANALOG_SD_RGB_SCART = 0x00, + ANALOG_SD_YPRPB_SCART, + ANALOG_HD_RGB_SCART, + ANALOG_HD_YPRPB_SCART, + ANALOG_SD_RGB_CINCH = 0x80, + ANALOG_SD_YPRPB_CINCH, + ANALOG_HD_RGB_CINCH, + ANALOG_HD_YPRPB_CINCH, +} analog_mode_t; + +typedef enum { + VIDEO_FORMAT_MPEG2 = 0, + VIDEO_FORMAT_MPEG4, + VIDEO_FORMAT_VC1, + VIDEO_FORMAT_JPEG, + VIDEO_FORMAT_GIF, + VIDEO_FORMAT_PNG +} VIDEO_FORMAT; + +typedef enum { + VIDEO_SD = 0, + VIDEO_HD, + VIDEO_120x60i, + VIDEO_320x240i, + VIDEO_1440x800i, + VIDEO_360x288i +} VIDEO_DEFINITION; + +typedef enum { + VIDEO_FRAME_RATE_23_976 = 0, + VIDEO_FRAME_RATE_24, + VIDEO_FRAME_RATE_25, + VIDEO_FRAME_RATE_29_97, + VIDEO_FRAME_RATE_30, + VIDEO_FRAME_RATE_50, + VIDEO_FRAME_RATE_59_94, + VIDEO_FRAME_RATE_60 +} VIDEO_FRAME_RATE; + +typedef enum { + DISPLAY_AR_1_1, + DISPLAY_AR_4_3, + DISPLAY_AR_14_9, + DISPLAY_AR_16_9, + DISPLAY_AR_20_9, + DISPLAY_AR_RAW, +} DISPLAY_AR; + +typedef enum { + DISPLAY_AR_MODE_PANSCAN = 0, + DISPLAY_AR_MODE_LETTERBOX, + DISPLAY_AR_MODE_NONE, + DISPLAY_AR_MODE_PANSCAN2 +} DISPLAY_AR_MODE; + +typedef enum { + VIDEO_DB_DR_NEITHER = 0, + VIDEO_DB_ON, + VIDEO_DB_DR_BOTH +} VIDEO_DB_DR; + +typedef enum { + VIDEO_PLAY_STILL = 0, + VIDEO_PLAY_CLIP, + VIDEO_PLAY_TRICK, + VIDEO_PLAY_MOTION, + VIDEO_PLAY_MOTION_NO_SYNC +} VIDEO_PLAY_MODE; + +typedef enum { + VIDEO_STD_NTSC = VID_DISPFMT_NTSC, /* 0 */ + VIDEO_STD_PAL = VID_DISPFMT_PAL, /* 1 */ + VIDEO_STD_SECAM = VID_DISPFMT_SECAM, /* 4 */ + VIDEO_STD_1080I50 = VIDEO_STD_PAL, /* hack, this is used in neutrino settings default */ + VIDEO_STD_MAX = VIDEO_STD_SECAM + 1 +} VIDEO_STD; + +typedef enum { + VIDEO_STOPPED, /* Video is stopped */ + VIDEO_PLAYING, /* Video is currently playing */ + VIDEO_FREEZED /* Video is freezed */ +} video_play_state_t; + +/* not used, for dummy functions */ +typedef enum { + VIDEO_HDMI_CEC_MODE_OFF = 0, + VIDEO_HDMI_CEC_MODE_TUNER, + VIDEO_HDMI_CEC_MODE_RECORDER +} VIDEO_HDMI_CEC_MODE; + +typedef enum +{ + VIDEO_CONTROL_BRIGHTNESS = 0, + VIDEO_CONTROL_CONTRAST, + VIDEO_CONTROL_SATURATION, + VIDEO_CONTROL_HUE, + VIDEO_CONTROL_SHARPNESS, + VIDEO_CONTROL_MAX = VIDEO_CONTROL_SHARPNESS +} VIDEO_CONTROL; + + +class cVideo +{ + private: + /* video device */ + int fd; + /* apparently we cannot query the driver's state + => remember it */ + video_play_state_t playstate; + vidDispMode_t croppingMode; + vidOutFmt_t outputformat; + int scartvoltage; + int z[2]; /* zoomvalue for 4:3 (0) and 16:9 (1) in percent */ + int *zoomvalue; + void *blank_data[2]; /* we store two blank MPEGs (PAL/NTSC) in there */ + int blank_size[2]; + + VIDEO_FORMAT StreamType; + VIDEO_DEFINITION VideoDefinition; + DISPLAY_AR DisplayAR; + VIDEO_PLAY_MODE SyncMode; + DISPLAY_AR_MODE ARMode; + VIDEO_DB_DR eDbDr; + DISPLAY_AR PictureAR; + VIDEO_FRAME_RATE FrameRate; + void routeVideo(int standby); + int video_standby; + public: + /* constructor & destructor */ + cVideo(int mode, void *, void *, unsigned int unit = 0); + ~cVideo(void); + + void * GetTVEnc() { return NULL; }; + void * GetTVEncSD() { return NULL; }; + + /* aspect ratio */ + int getAspectRatio(void); + void getPictureInfo(int &width, int &height, int &rate); + int setAspectRatio(int aspect, int mode); + + /* cropping mode */ + int setCroppingMode(vidDispMode_t x = VID_DISPMODE_NORM); + + /* get play state */ + int getPlayState(void); + + /* blank on freeze */ + int getBlank(void); + int setBlank(int enable); + + /* change video play state. Parameters are all unused. */ + int Start(void *PcrChannel = NULL, unsigned short PcrPid = 0, unsigned short VideoPid = 0, void *x = NULL); + int Stop(bool blank = true); + bool Pause(void); + + /* set video_system */ + int SetVideoSystem(int video_system, bool remember = true); + int SetStreamType(VIDEO_FORMAT type); + void SetSyncMode(AVSYNC_TYPE mode); + bool SetCECMode(VIDEO_HDMI_CEC_MODE) { return true; }; + void SetCECAutoView(bool) { return; }; + void SetCECAutoStandby(bool) { return; }; + void ShowPicture(const char * fname); + void StopPicture(); + void Standby(unsigned int bOn); + void Pig(int x, int y, int w, int h, int osd_w = 1064, int osd_h = 600); + void SetControl(int, int) { return; }; + int setZoom(int); + void VideoParamWatchdog(void); + void setContrast(int val); + void SetVideoMode(analog_mode_t mode); + void SetDBDR(int) { return; }; + void SetAudioHandle(void *) { return; }; + void FastForwardMode(int mode = 0); + void SetAutoModes(int [VIDEO_STD_MAX]) { return; }; + int OpenVBI(int) { return 0; }; + int CloseVBI(void) { return 0; }; + int StartVBI(unsigned short) { return 0; }; + int StopVBI(void) { return 0; }; + void SetDemux(cDemux *dmx); + bool GetScreenImage(unsigned char * &data, int &xres, int &yres, bool get_video = true, bool get_osd = false, bool scale_to_video = false); +}; + +#endif diff --git a/raspi/Makefile.am b/raspi/Makefile.am new file mode 100644 index 0000000..32cd87a --- /dev/null +++ b/raspi/Makefile.am @@ -0,0 +1,26 @@ +noinst_LTLIBRARIES = libraspi.la + +AM_CPPFLAGS = -D__STDC_FORMAT_MACROS -D__STDC_CONSTANT_MACROS +AM_CPPFLAGS += \ + -I/opt/vc/include \ + -I/opt/vc/include/interface/vcos/pthreads/ \ + -I/opt/vc/include/interface/vmcs_host/linux \ + -I$(top_srcdir)/common + +AM_CXXFLAGS = -fno-rtti -fno-exceptions -fno-strict-aliasing + +AM_LDFLAGS = \ + -L/opt/vc/lib/ -lopenmaxil -lbcm_host -lvcos -lvchiq_arm -lpthread -lrt \ + -lOpenThreads + +libraspi_la_SOURCES = \ + hardware_caps.c \ + dmx.cpp \ + video.cpp \ + audio.cpp \ + glfb.cpp \ + init.cpp \ + playback.cpp \ + pwrmngr.cpp \ + record.cpp + diff --git a/raspi/audio.cpp b/raspi/audio.cpp new file mode 100644 index 0000000..73e0ec2 --- /dev/null +++ b/raspi/audio.cpp @@ -0,0 +1,154 @@ +/* + * (C) 2010-2013 Stefan Seyfried + * + * 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 2 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 . + * + * cAudio dummy implementation + */ + +#include +#include + +#include "audio_lib.h" +#include "dmx_lib.h" +#include "lt_debug.h" + +#define lt_debug(args...) _lt_debug(HAL_DEBUG_AUDIO, this, args) +#define lt_info(args...) _lt_info(HAL_DEBUG_AUDIO, this, args) + +cAudio * audioDecoder = NULL; + +cAudio::cAudio(void *, void *, void *) +{ + lt_debug("%s\n", __func__); +} + +cAudio::~cAudio(void) +{ + lt_debug("%s\n", __func__); +} + +void cAudio::openDevice(void) +{ + lt_debug("%s\n", __func__); +} + +void cAudio::closeDevice(void) +{ + lt_debug("%s\n", __func__); +} + +int cAudio::do_mute(bool enable, bool remember) +{ + lt_debug("%s(%d, %d)\n", __func__, enable, remember); + return 0; +} + +int cAudio::setVolume(unsigned int left, unsigned int right) +{ + lt_debug("%s(%d, %d)\n", __func__, left, right); + return 0; +} + +int cAudio::Start(void) +{ + lt_debug("%s >\n", __func__); + return 0; +} + +int cAudio::Stop(void) +{ + lt_debug("%s >\n", __func__); + return 0; +} + +bool cAudio::Pause(bool /*Pcm*/) +{ + return true; +}; + +void cAudio::SetSyncMode(AVSYNC_TYPE Mode) +{ + lt_debug("%s %d\n", __func__, Mode); +}; + +void cAudio::SetStreamType(AUDIO_FORMAT type) +{ + lt_debug("%s %d\n", __func__, type); +}; + +int cAudio::setChannel(int /*channel*/) +{ + return 0; +}; + +int cAudio::PrepareClipPlay(int ch, int srate, int bits, int le) +{ + lt_debug("%s ch %d srate %d bits %d le %d\n", __func__, ch, srate, bits, le);; + return 0; +}; + +int cAudio::WriteClip(unsigned char *buffer, int size) +{ + lt_debug("cAudio::%s buf 0x%p size %d\n", __func__, buffer, size); + return size; +}; + +int cAudio::StopClip() +{ + lt_debug("%s\n", __func__); + return 0; +}; + +void cAudio::getAudioInfo(int &type, int &layer, int &freq, int &bitrate, int &mode) +{ + type = 0; + layer = 0; /* not used */ + freq = 0; + bitrate = 0; /* not used, but easy to get :-) */ + mode = 0; /* default: stereo */ + lt_debug("%s t: %d l: %d f: %d b: %d m: %d\n", + __func__, type, layer, freq, bitrate, mode); +}; + +void cAudio::SetSRS(int /*iq_enable*/, int /*nmgr_enable*/, int /*iq_mode*/, int /*iq_level*/) +{ + lt_debug("%s\n", __func__); +}; + +void cAudio::SetHdmiDD(bool enable) +{ + lt_debug("%s %d\n", __func__, enable); +}; + +void cAudio::SetSpdifDD(bool enable) +{ + lt_debug("%s %d\n", __func__, enable); +}; + +void cAudio::ScheduleMute(bool On) +{ + lt_debug("%s %d\n", __func__, On); +}; + +void cAudio::EnableAnalogOut(bool enable) +{ + lt_debug("%s %d\n", __func__, enable); +}; + +void cAudio::setBypassMode(bool disable) +{ + lt_debug("%s %d\n", __func__, disable); +} + diff --git a/raspi/audio_lib.h b/raspi/audio_lib.h new file mode 100644 index 0000000..cfd1b81 --- /dev/null +++ b/raspi/audio_lib.h @@ -0,0 +1,103 @@ +/* public header file */ + +#ifndef _AUDIO_LIB_H_ +#define _AUDIO_LIB_H_ + +#include +#include "../common/cs_types.h" + +typedef enum +{ + AUDIO_SYNC_WITH_PTS, + AUDIO_NO_SYNC, + AUDIO_SYNC_AUDIO_MASTER +} AUDIO_SYNC_MODE; + +typedef enum { + HDMI_ENCODED_OFF, + HDMI_ENCODED_AUTO, + HDMI_ENCODED_FORCED +} HDMI_ENCODED_MODE; + +typedef enum +{ + AUDIO_FMT_AUTO = 0, + AUDIO_FMT_MPEG, + AUDIO_FMT_MP3, + AUDIO_FMT_DOLBY_DIGITAL, + AUDIO_FMT_BASIC = AUDIO_FMT_DOLBY_DIGITAL, + AUDIO_FMT_AAC, + AUDIO_FMT_AAC_PLUS, + AUDIO_FMT_DD_PLUS, + AUDIO_FMT_DTS, + AUDIO_FMT_AVS, + AUDIO_FMT_MLP, + AUDIO_FMT_WMA, + AUDIO_FMT_MPG1, // TD only. For Movieplayer / cPlayback + AUDIO_FMT_ADVANCED = AUDIO_FMT_MLP +} AUDIO_FORMAT; + +class cAudio +{ + friend class cPlayback; + private: + int fd; + bool Muted; + + int clipfd; /* for pcm playback */ + int mixer_fd; /* if we are using the OSS mixer */ + int mixer_num; /* oss mixer to use, if any */ + + AUDIO_FORMAT StreamType; + AUDIO_SYNC_MODE SyncMode; + bool started; + bool thread_started; + + int volume; + int64_t curr_pts; + + void openDevice(void); + void closeDevice(void); + + int do_mute(bool enable, bool remember); + void setBypassMode(bool disable); + public: + /* construct & destruct */ + cAudio(void *, void *, void *); + ~cAudio(void); + int64_t getPts() { return curr_pts; } + + void *GetHandle() { return NULL; }; + /* shut up */ + int mute(bool remember = true) { return do_mute(true, remember); }; + int unmute(bool remember = true) { return do_mute(false, remember); }; + + /* volume, min = 0, max = 255 */ + int setVolume(unsigned int left, unsigned int right); + int getVolume(void) { return volume;} + bool getMuteStatus(void) { return Muted; }; + + /* start and stop audio */ + int Start(void); + int Stop(void); + bool Pause(bool Pcm = true); + void SetStreamType(AUDIO_FORMAT type); + void SetSyncMode(AVSYNC_TYPE Mode); + + /* select channels */ + int setChannel(int channel); + int PrepareClipPlay(int uNoOfChannels, int uSampleRate, int uBitsPerSample, int bLittleEndian); + int WriteClip(unsigned char * buffer, int size); + int StopClip(); + void getAudioInfo(int &type, int &layer, int& freq, int &bitrate, int &mode); + void SetSRS(int iq_enable, int nmgr_enable, int iq_mode, int iq_level); + bool IsHdmiDDSupported(); + void SetHdmiDD(bool enable); + void SetSpdifDD(bool enable); + void ScheduleMute(bool On); + void EnableAnalogOut(bool enable); + int my_read(uint8_t *buf, int buf_size); +}; + +#endif + diff --git a/raspi/cs_api.h b/raspi/cs_api.h new file mode 120000 index 0000000..a794ffd --- /dev/null +++ b/raspi/cs_api.h @@ -0,0 +1 @@ +../libspark/cs_api.h \ No newline at end of file diff --git a/raspi/dmx.cpp b/raspi/dmx.cpp new file mode 100644 index 0000000..e5fdf4f --- /dev/null +++ b/raspi/dmx.cpp @@ -0,0 +1,500 @@ +/* + * cDemux implementation for generic dvbapi + * + * derived from libtriple/dmx_td.cpp + * + * (C) 2010-2013 Stefan Seyfried + * + * 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 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + + +#include "config.h" +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "dmx_lib.h" +#include "lt_debug.h" + +/* needed for getSTC :-( */ +#include "video_lib.h" +extern cVideo *videoDecoder; + +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_DEMUX, this, args) +#define lt_info(args...) _lt_info(TRIPLE_DEBUG_DEMUX, this, args) +#define lt_info_c(args...) _lt_info(TRIPLE_DEBUG_DEMUX, NULL, args) + +#define dmx_err(_errfmt, _errstr, _revents) do { \ + uint16_t _pid = (uint16_t)-1; uint16_t _f = 0;\ + if (dmx_type == DMX_PSI_CHANNEL) { \ + _pid = s_flt.pid; _f = s_flt.filter.filter[0]; \ + } else { \ + _pid = p_flt.pid; \ + }; \ + lt_info("%s " _errfmt " fd:%d, ev:0x%x %s pid:0x%04hx flt:0x%02hx\n", \ + __func__, _errstr, fd, _revents, DMX_T[dmx_type], _pid, _f); \ +} while(0); + +cDemux *videoDemux = NULL; +cDemux *audioDemux = NULL; +//cDemux *pcrDemux = NULL; + +static const char *DMX_T[] = { + "DMX_INVALID", + "DMX_VIDEO", + "DMX_AUDIO", + "DMX_PES", + "DMX_PSI", + "DMX_PIP", + "DMX_TP", + "DMX_PCR" +}; + +/* map the device numbers. for now only demux0 is used */ +static const char *devname[] = { + "/dev/dvb/adapter0/demux0", + "/dev/dvb/adapter0/demux0", + "/dev/dvb/adapter0/demux0" +}; + +/* uuuugly */ +static int dmx_tp_count = 0; +#define MAX_TS_COUNT 8 + +cDemux::cDemux(int n) +{ + if (n < 0 || n > 2) + { + lt_info("%s ERROR: n invalid (%d)\n", __FUNCTION__, n); + num = 0; + } + else + num = n; + fd = -1; + measure = false; + last_measure = 0; + last_data = 0; +} + +cDemux::~cDemux() +{ + lt_debug("%s #%d fd: %d\n", __FUNCTION__, num, fd); + Close(); +} + +bool cDemux::Open(DMX_CHANNEL_TYPE pes_type, void * /*hVideoBuffer*/, int uBufferSize) +{ + int devnum = num; + int flags = O_RDWR|O_CLOEXEC; + if (fd > -1) + lt_info("%s FD ALREADY OPENED? fd = %d\n", __FUNCTION__, fd); + + dmx_type = pes_type; + if (pes_type != DMX_PSI_CHANNEL) + flags |= O_NONBLOCK; + + fd = open(devname[devnum], flags); + if (fd < 0) + { + lt_info("%s %s: %m\n", __FUNCTION__, devname[devnum]); + return false; + } + lt_debug("%s #%d pes_type: %s(%d), uBufferSize: %d fd: %d\n", __func__, + num, DMX_T[pes_type], pes_type, uBufferSize, fd); + + if (dmx_type == DMX_VIDEO_CHANNEL) + uBufferSize = 0x100000; /* 1MB */ + if (dmx_type == DMX_AUDIO_CHANNEL) + uBufferSize = 0x10000; /* 64k */ +#if 0 + if (!pesfds.empty()) + { + lt_info("%s ERROR! pesfds not empty!\n", __FUNCTION__); /* TODO: error handling */ + return false; + } + int n = DMX_SOURCE_FRONT0; + if (ioctl(fd, DMX_SET_SOURCE, &n) < 0) + lt_info("%s DMX_SET_SOURCE %d failed! (%m)\n", __func__, n); +#endif + if (uBufferSize > 0) + { + /* probably uBufferSize == 0 means "use default size". TODO: find a reasonable default */ + if (ioctl(fd, DMX_SET_BUFFER_SIZE, uBufferSize) < 0) + lt_info("%s DMX_SET_BUFFER_SIZE failed (%m)\n", __func__); + } + buffersize = uBufferSize; + + return true; +} + +void cDemux::Close(void) +{ + lt_debug("%s #%d, fd = %d\n", __FUNCTION__, num, fd); + if (fd < 0) + { + lt_info("%s #%d: not open!\n", __FUNCTION__, num); + return; + } + pesfds.clear(); + ioctl(fd, DMX_STOP); + close(fd); + fd = -1; + if (measure) + return; + if (dmx_type == DMX_TP_CHANNEL) + { + dmx_tp_count--; + if (dmx_tp_count < 0) + { + lt_info("%s dmx_tp_count < 0!!\n", __func__); + dmx_tp_count = 0; + } + } +} + +bool cDemux::Start(bool) +{ + lt_debug("%s #%d fd: %d type: %s\n", __func__, num, fd, DMX_T[dmx_type]); + if (fd < 0) + { + lt_info("%s #%d: not open!\n", __FUNCTION__, num); + return false; + } + ioctl(fd, DMX_START); + return true; +} + +bool cDemux::Stop(void) +{ + lt_debug("%s #%d fd: %d type: %s\n", __func__, num, fd, DMX_T[dmx_type]); + if (fd < 0) + { + lt_info("%s #%d: not open!\n", __FUNCTION__, num); + return false; + } + ioctl(fd, DMX_STOP); + return true; +} + +int cDemux::Read(unsigned char *buff, int len, int timeout) +{ +#if 0 + if (len != 4095 && timeout != 100) + fprintf(stderr, "cDemux::%s #%d fd: %d type: %s len: %d timeout: %d\n", + __FUNCTION__, num, fd, DMX_T[dmx_type], len, timeout); +#endif + int rc; + struct pollfd ufds; + ufds.fd = fd; + ufds.events = POLLIN|POLLPRI|POLLERR; + ufds.revents = 0; + + if (timeout > 0) + { + retry: + rc = ::poll(&ufds, 1, timeout); + if (!rc) + return 0; // timeout + else if (rc < 0) + { + dmx_err("poll: %s,", strerror(errno), 0) + //lt_info("%s poll: %m\n", __FUNCTION__); + /* happens, when running under gdb... */ + if (errno == EINTR) + goto retry; + return -1; + } +#if 0 + if (ufds.revents & POLLERR) /* POLLERR means buffer error, i.e. buffer overflow */ + { + dmx_err("received %s,", "POLLERR", ufds.revents); + /* this seems to happen sometimes at recording start, without bad effects */ + return 0; + } +#endif + if (ufds.revents & POLLHUP) /* we get POLLHUP if e.g. a too big DMX_BUFFER_SIZE was set */ + { + dmx_err("received %s,", "POLLHUP", ufds.revents); + return -1; + } + if (!(ufds.revents & POLLIN)) /* we requested POLLIN but did not get it? */ + { + dmx_err("received %s, please report!", "POLLIN", ufds.revents); + return 0; + } + } + + rc = ::read(fd, buff, len); + //fprintf(stderr, "fd %d ret: %d\n", fd, rc); + if (rc < 0) + dmx_err("read: %s", strerror(errno), 0); + + return rc; +} + +bool cDemux::sectionFilter(unsigned short pid, const unsigned char * const filter, + const unsigned char * const mask, int len, int timeout, + const unsigned char * const negmask) +{ + memset(&s_flt, 0, sizeof(s_flt)); + + if (len > DMX_FILTER_SIZE) + { + lt_info("%s #%d: len too long: %d, DMX_FILTER_SIZE %d\n", __func__, num, len, DMX_FILTER_SIZE); + len = DMX_FILTER_SIZE; + } + s_flt.pid = pid; + s_flt.timeout = timeout; + memcpy(s_flt.filter.filter, filter, len); + memcpy(s_flt.filter.mask, mask, len); + if (negmask != NULL) + memcpy(s_flt.filter.mode, negmask, len); + + s_flt.flags = DMX_IMMEDIATE_START|DMX_CHECK_CRC; + + int to = 0; + switch (filter[0]) { + case 0x00: /* program_association_section */ + to = 2000; + break; + case 0x01: /* conditional_access_section */ + to = 6000; + break; + case 0x02: /* program_map_section */ + to = 1500; + break; + case 0x03: /* transport_stream_description_section */ + to = 10000; + break; + /* 0x04 - 0x3F: reserved */ + case 0x40: /* network_information_section - actual_network */ + to = 10000; + break; + case 0x41: /* network_information_section - other_network */ + to = 15000; + break; + case 0x42: /* service_description_section - actual_transport_stream */ + to = 10000; + break; + /* 0x43 - 0x45: reserved for future use */ + case 0x46: /* service_description_section - other_transport_stream */ + to = 10000; + break; + /* 0x47 - 0x49: reserved for future use */ + case 0x4A: /* bouquet_association_section */ + to = 11000; + break; + /* 0x4B - 0x4D: reserved for future use */ + case 0x4E: /* event_information_section - actual_transport_stream, present/following */ + to = 2000; + break; + case 0x4F: /* event_information_section - other_transport_stream, present/following */ + to = 10000; + break; + /* 0x50 - 0x5F: event_information_section - actual_transport_stream, schedule */ + /* 0x60 - 0x6F: event_information_section - other_transport_stream, schedule */ + case 0x70: /* time_date_section */ + s_flt.flags &= ~DMX_CHECK_CRC; /* section has no CRC */ + //s_flt.pid = 0x0014; + to = 30000; + break; + case 0x71: /* running_status_section */ + s_flt.flags &= ~DMX_CHECK_CRC; /* section has no CRC */ + to = 0; + break; + case 0x72: /* stuffing_section */ + s_flt.flags &= ~DMX_CHECK_CRC; /* section has no CRC */ + to = 0; + break; + case 0x73: /* time_offset_section */ + //s_flt.pid = 0x0014; + to = 30000; + break; + /* 0x74 - 0x7D: reserved for future use */ + case 0x7E: /* discontinuity_information_section */ + s_flt.flags &= ~DMX_CHECK_CRC; /* section has no CRC */ + to = 0; + break; + case 0x7F: /* selection_information_section */ + to = 0; + break; + /* 0x80 - 0x8F: ca_message_section */ + /* 0x90 - 0xFE: user defined */ + /* 0xFF: reserved */ + default: + break; +// return -1; + } + /* the negmask == NULL is a hack: the users of negmask are PMT-update + * and sectionsd EIT-Version change. And they really want no timeout + * if timeout == 0 instead of "default timeout" */ + if (timeout == 0 && negmask == NULL) + s_flt.timeout = to; + + lt_debug("%s #%d pid:0x%04hx fd:%d type:%s len:%d to:%d flags:%x flt[0]:%02x\n", __func__, num, + pid, fd, DMX_T[dmx_type], len, s_flt.timeout,s_flt.flags, s_flt.filter.filter[0]); +#if 0 + fprintf(stderr,"filt: ");for(int i=0;i= 0x0002 && pid <= 0x000f) || pid >= 0x1fff) + return false; + + lt_debug("%s #%d pid: 0x%04hx fd: %d type: %s\n", __FUNCTION__, num, pid, fd, DMX_T[dmx_type]); + + memset(&p_flt, 0, sizeof(p_flt)); + p_flt.pid = pid; + p_flt.output = DMX_OUT_DECODER; + p_flt.input = DMX_IN_FRONTEND; + + /* for now, output to TS_TAP for live mode, + * test playback with "omxplayer /dev/dvb/adapter0/dvr0" */ + switch (dmx_type) { + case DMX_PCR_ONLY_CHANNEL: + p_flt.pes_type = DMX_PES_OTHER; + p_flt.output = DMX_OUT_TS_TAP; + break; + case DMX_AUDIO_CHANNEL: + p_flt.pes_type = DMX_PES_OTHER; + p_flt.output = DMX_OUT_TS_TAP; + break; + case DMX_VIDEO_CHANNEL: + p_flt.pes_type = DMX_PES_OTHER; + p_flt.output = DMX_OUT_TS_TAP; + break; + case DMX_PES_CHANNEL: + p_flt.pes_type = DMX_PES_OTHER; + p_flt.output = DMX_OUT_TAP; + break; + case DMX_TP_CHANNEL: + p_flt.pes_type = DMX_PES_OTHER; + p_flt.output = DMX_OUT_TSDEMUX_TAP; + break; + default: + lt_info("%s #%d invalid dmx_type %d!\n", __func__, num, dmx_type); + return false; + } + return (ioctl(fd, DMX_SET_PES_FILTER, &p_flt) >= 0); +} + +void cDemux::SetSyncMode(AVSYNC_TYPE /*mode*/) +{ + lt_debug("%s #%d\n", __FUNCTION__, num); +} + +void *cDemux::getBuffer() +{ + lt_debug("%s #%d\n", __FUNCTION__, num); + return NULL; +} + +void *cDemux::getChannel() +{ + lt_debug("%s #%d\n", __FUNCTION__, num); + return NULL; +} + +bool cDemux::addPid(unsigned short Pid) +{ + lt_debug("%s: pid 0x%04hx\n", __func__, Pid); + pes_pids pfd; + int ret; + if (dmx_type != DMX_TP_CHANNEL) + { + lt_info("%s pes_type %s not implemented yet! pid=%hx\n", __FUNCTION__, DMX_T[dmx_type], Pid); + return false; + } + if (fd == -1) + lt_info("%s bucketfd not yet opened? pid=%hx\n", __FUNCTION__, Pid); + pfd.fd = fd; /* dummy */ + pfd.pid = Pid; + pesfds.push_back(pfd); + ret = (ioctl(fd, DMX_ADD_PID, &Pid)); + if (ret < 0) + lt_info("%s: DMX_ADD_PID (%m)\n", __func__); + return (ret != -1); +} + +void cDemux::removePid(unsigned short Pid) +{ + if (dmx_type != DMX_TP_CHANNEL) + { + lt_info("%s pes_type %s not implemented yet! pid=%hx\n", __FUNCTION__, DMX_T[dmx_type], Pid); + return; + } + for (std::vector::iterator i = pesfds.begin(); i != pesfds.end(); ++i) + { + if ((*i).pid == Pid) { + lt_debug("removePid: removing demux fd %d pid 0x%04x\n", fd, Pid); + if (ioctl(fd, DMX_REMOVE_PID, Pid) < 0) + lt_info("%s: (DMX_REMOVE_PID, 0x%04hx): %m\n", __func__, Pid); + pesfds.erase(i); + return; /* TODO: what if the same PID is there multiple times */ + } + } + lt_info("%s pid 0x%04x not found\n", __FUNCTION__, Pid); +} + +void cDemux::getSTC(int64_t * STC) +{ + int64_t pts = 0; + if (videoDecoder) + pts = videoDecoder->GetPTS(); + *STC = pts; +} + +int cDemux::getUnit(void) +{ + lt_debug("%s #%d\n", __FUNCTION__, num); + /* just guessed that this is the right thing to do. + right now this is only used by the CA code which is stubbed out + anyway */ + return num; +} + +bool cDemux::SetSource(int unit, int source) +{ + lt_info_c("%s(%d, %d): not implemented yet\n", __func__, unit, source); + return true; +} + +int cDemux::GetSource(int unit) +{ + lt_info_c("%s(%d): not implemented yet\n", __func__, unit); + return 0; +} diff --git a/raspi/dmx_cs.h b/raspi/dmx_cs.h new file mode 100644 index 0000000..175d8cb --- /dev/null +++ b/raspi/dmx_cs.h @@ -0,0 +1 @@ +#include "dmx_lib.h" diff --git a/raspi/dmx_lib.h b/raspi/dmx_lib.h new file mode 100644 index 0000000..754511e --- /dev/null +++ b/raspi/dmx_lib.h @@ -0,0 +1,70 @@ +#ifndef __DEMUX_TD_H +#define __DEMUX_TD_H + +#include +#include +#include +#include +#include +#include "../common/cs_types.h" + +#define MAX_DMX_UNITS 4 + +typedef enum +{ + DMX_INVALID = 0, + DMX_VIDEO_CHANNEL = 1, + DMX_AUDIO_CHANNEL, + DMX_PES_CHANNEL, + DMX_PSI_CHANNEL, + DMX_PIP_CHANNEL, + DMX_TP_CHANNEL, + DMX_PCR_ONLY_CHANNEL +} DMX_CHANNEL_TYPE; + +typedef struct +{ + int fd; + unsigned short pid; +} pes_pids; + +class cDemux +{ + private: + int num; + int fd; + int buffersize; + bool measure; + uint64_t last_measure, last_data; + DMX_CHANNEL_TYPE dmx_type; + std::vector pesfds; + struct dmx_sct_filter_params s_flt; + struct dmx_pes_filter_params p_flt; + public: + + bool Open(DMX_CHANNEL_TYPE pes_type, void * x = NULL, int y = 0); + void Close(void); + bool Start(bool record = false); + bool Stop(void); + int Read(unsigned char *buff, int len, int Timeout = 0); + bool sectionFilter(unsigned short pid, const unsigned char * const filter, const unsigned char * const mask, int len, int Timeout = 0, const unsigned char * const negmask = NULL); + bool pesFilter(const unsigned short pid); + void SetSyncMode(AVSYNC_TYPE mode); + void * getBuffer(); + void * getChannel(); + DMX_CHANNEL_TYPE getChannelType(void) { return dmx_type; }; + bool addPid(unsigned short pid); + void getSTC(int64_t * STC); + int getUnit(void); + static bool SetSource(int unit, int source); + static int GetSource(int unit); + // TD only functions + int getFD(void) { return fd; }; /* needed by cPlayback class */ + void removePid(unsigned short Pid); /* needed by cRecord class */ + std::vector getPesPids(void) { return pesfds; }; + // + cDemux(int num = 0); + ~cDemux(); +}; + +#endif //__DEMUX_H diff --git a/raspi/glfb.cpp b/raspi/glfb.cpp new file mode 100644 index 0000000..45d6473 --- /dev/null +++ b/raspi/glfb.cpp @@ -0,0 +1,171 @@ +/* + Copyright 2013 Stefan Seyfried + + 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 2 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 . + + The GLFB namespace is just because it's already established by the + generic-pc implementation. +*/ + +#include +#include + +#include "glfb.h" +#include "bcm_host.h" + +#include "lt_debug.h" + +#define lt_debug_c(args...) _lt_debug(HAL_DEBUG_INIT, NULL, args) +#define lt_info_c(args...) _lt_info(HAL_DEBUG_INIT, NULL, args) +#define lt_debug(args...) _lt_debug(HAL_DEBUG_INIT, this, args) +#define lt_info(args...) _lt_info(HAL_DEBUG_INIT, this, args) + +/* I don't want to assert right now */ +#define CHECK(x) if (!(x)) { lt_info("GLFB: %s:%d warning: %s\n", __func__, __LINE__, #x); } + +/* TODO: encapsulate this into pdata? */ +static DISPMANX_RESOURCE_HANDLE_T res[2]; +static uint32_t vc_img_ptr[2]; +static DISPMANX_UPDATE_HANDLE_T update; +static DISPMANX_ELEMENT_HANDLE_T element; +static DISPMANX_DISPLAY_HANDLE_T display; +static DISPMANX_MODEINFO_T info; +static VC_RECT_T dst_rect; +static void *image; +static int curr_res; +static int pitch; +static VC_IMAGE_TYPE_T type = VC_IMAGE_ARGB8888; + +static OpenThreads::Mutex blit_mutex; +static OpenThreads::Condition blit_cond; + +static bool goodbye = false; /* if set main loop is left */ +static bool ready = false; /* condition predicate */ + +static int width; /* width and height, fixed for a framebuffer instance */ +static int height; + +GLFramebuffer::GLFramebuffer(int x, int y) +{ + width = x; + height = y; + + /* linux framebuffer compat mode */ + si.bits_per_pixel = 32; + si.xres = si.xres_virtual = width; + si.yres = si.yres_virtual = height; + si.blue.length = si.green.length = si.red.length = si.transp.length = 8; + si.blue.offset = 0; + si.green.offset = 8; + si.red.offset = 16; + si.transp.offset = 24; + + OpenThreads::Thread::start(); + while (!ready) + usleep(1); +} + +GLFramebuffer::~GLFramebuffer() +{ + goodbye = true; + blit(); /* wake up thread */ + OpenThreads::Thread::join(); +} + +void GLFramebuffer::run() +{ + hal_set_threadname("hal:fbuff"); + setup(); + ready = true; /* signal that setup is finished */ + blit_mutex.lock(); + while (!goodbye) + { + blit_cond.wait(&blit_mutex); + blit_osd(); + } + blit_mutex.unlock(); + lt_info("GLFB: GL thread stopping\n"); +} + +void GLFramebuffer::blit() +{ + blit_mutex.lock(); + blit_cond.signal(); + blit_mutex.unlock(); +} + +void GLFramebuffer::setup() +{ + lt_info("GLFB: raspi OMX fb setup\n"); + int ret; + VC_RECT_T src_rect, dsp_rect; /* source and display size will not change. period. */ + pitch = ALIGN_UP(width * 4, 32); + /* broadcom example code has this ALIGN_UP in there for a reasin, I suppose */ + if (pitch != width * 4) + lt_info("GLFB: WARNING: width not a multiple of 8? I doubt this will work...\n"); + + /* global alpha nontransparent (255) */ + VC_DISPMANX_ALPHA_T alpha = { DISPMANX_FLAGS_ALPHA_FROM_SOURCE, 255, 0 }; + + bcm_host_init(); + + display = vc_dispmanx_display_open(0); + ret = vc_dispmanx_display_get_info(display, &info); + CHECK(ret == 0); + /* 32bit FB depth, *2 because tuxtxt uses a shadow buffer */ + osd_buf.resize(pitch * height * 2); + lt_info("GLFB: Display is %d x %d, FB is %d x %d, memory size %d\n", + info.width, info.height, width, height, osd_buf.size()); + image = &osd_buf[0]; + /* initialize to half-transparent grey */ + memset(image, 0x7f, osd_buf.size()); + for (int i = 0; i < 2; i++) { + res[i] = vc_dispmanx_resource_create(type, width, height, &vc_img_ptr[i]); + CHECK(res[i]); + } + vc_dispmanx_rect_set(&dst_rect, 0, 0, width, height); + ret = vc_dispmanx_resource_write_data(res[curr_res], type, pitch, image, &dst_rect); + CHECK(ret == 0); + update = vc_dispmanx_update_start(10); + CHECK(update); + vc_dispmanx_rect_set(&src_rect, 0, 0, width << 16, height << 16); + vc_dispmanx_rect_set(&dsp_rect, 0, 0, info.width, info.height); + element = vc_dispmanx_element_add(update, + display, + 2000 /*layer*/, + &dsp_rect, + res[curr_res], + &src_rect, + DISPMANX_PROTECTION_NONE, + &alpha, + NULL, + DISPMANX_NO_ROTATE); + ret = vc_dispmanx_update_submit_sync(update); + CHECK(ret == 0); + curr_res = !curr_res; +} + +void GLFramebuffer::blit_osd() +{ + int ret; + ret = vc_dispmanx_resource_write_data(res[curr_res], type, pitch, image, &dst_rect); + CHECK(ret == 0); + update = vc_dispmanx_update_start(10); + CHECK(update); + ret = vc_dispmanx_element_change_source(update, element, res[curr_res]); + CHECK(ret == 0); + ret = vc_dispmanx_update_submit_sync(update); + CHECK(ret == 0); + curr_res = !curr_res; +} diff --git a/raspi/glfb.h b/raspi/glfb.h new file mode 100644 index 0000000..c5fcce6 --- /dev/null +++ b/raspi/glfb.h @@ -0,0 +1,42 @@ +/* + Copyright 2013 Stefan Seyfried + + 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 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef __glthread__ +#define __glthread__ +#include +#include +#include /* for screeninfo etc. */ + +class GLFramebuffer : public OpenThreads::Thread +{ +public: + GLFramebuffer(int x, int y); + ~GLFramebuffer(); + std::vector *getOSDBuffer() { return &osd_buf; } /* pointer to OSD bounce buffer */ + void blit(); + fb_var_screeninfo getScreenInfo() { return si; } + +private: + void *pdata; /* not yet used */ + fb_var_screeninfo si; + std::vector osd_buf; /* silly bounce buffer */ + void run(); /* for OpenThreads::Thread */ + + void setup(); + void blit_osd(); +}; +#endif diff --git a/raspi/hardware_caps.c b/raspi/hardware_caps.c new file mode 100644 index 0000000..a4f4a3f --- /dev/null +++ b/raspi/hardware_caps.c @@ -0,0 +1,37 @@ +/* + * determine the capabilities of the hardware. + * part of libstb-hal + * + * (C) 2010-2013 Stefan Seyfried + * + * License: GPL v2 or later + */ + +#include +#include +#include +#include +#include +#include +#include + +static int initialized = 0; +static hw_caps_t caps; + +hw_caps_t *get_hwcaps(void) +{ + if (initialized) + return ∩︀ + + memset(&caps, 0, sizeof(hw_caps_t)); + + initialized = 1; + caps.can_shutdown = 1; /* for testing */ + caps.display_type = HW_DISPLAY_LINE_TEXT; + caps.has_HDMI = 1; + caps.display_xres = 8; + strcpy(caps.boxvendor, "Raspberry"); + strcpy(caps.boxname, "Pi"); + + return ∩︀ +} diff --git a/raspi/init.cpp b/raspi/init.cpp new file mode 100644 index 0000000..557d823 --- /dev/null +++ b/raspi/init.cpp @@ -0,0 +1,229 @@ +/* + * (C) 2010-2012 Stefan Seyfried + * + * 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 2 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 . + * + * libstb-hal initialisation and input conversion routines + * for the Raspberry Pi + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "init_lib.h" +#include "lt_debug.h" +#include "glfb.h" +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_INIT, NULL, args) +#define lt_info(args...) _lt_info(TRIPLE_DEBUG_INIT, NULL, args) + +static bool initialized = false; +GLFramebuffer *glfb = NULL; + +typedef std::map keymap_t; +static keymap_t kmap; + +static void init_keymap(void) +{ + /* same as generic-pc/glfb.cpp */ + kmap[KEY_ENTER] = KEY_OK; + kmap[KEY_ESC] = KEY_EXIT; + kmap[KEY_E] = KEY_EPG; + kmap[KEY_I] = KEY_INFO; + kmap[KEY_M] = KEY_MENU; + kmap[KEY_F12] = KEY_VOLUMEUP; /* different than glfb, as we */ + kmap[KEY_F11] = KEY_VOLUMEDOWN; /* don't consider the keyboard */ + kmap[KEY_F10] = KEY_MUTE; /* layout... */ + kmap[KEY_H] = KEY_HELP; + kmap[KEY_P] = KEY_POWER; + kmap[KEY_F1] = KEY_RED; + kmap[KEY_F2] = KEY_GREEN; + kmap[KEY_F3] = KEY_YELLOW; + kmap[KEY_F4] = KEY_BLUE; + kmap[KEY_F5] = KEY_WWW; + kmap[KEY_F6] = KEY_SUBTITLE; + kmap[KEY_F7] = KEY_MOVE; + kmap[KEY_F8] = KEY_SLEEP; +} + +class Input: public OpenThreads::Thread +{ + public: + Input(); + ~Input(); + private: + void run(); + bool running; +}; + +Input::Input() +{ + Init(); + start(); +} + +Input::~Input() +{ + running = false; + join(); +} + +static int dirfilter(const struct dirent *d) +{ + return !strncmp(d->d_name, "event", 5); +} + +void Input::run() +{ + struct input_event in; + int out_fd; + struct dirent **namelist; + int n; + unsigned long bit = 0; + char inputstr[] = "/dev/input/event9999999"; + std::setin_fds; + int fd_max = 0; + fd_set rfds; + hal_set_threadname("hal:input"); + init_keymap(); + unlink("/tmp/neutrino.input"); + mkfifo("/tmp/neutrino.input", 0600); + out_fd = open("/tmp/neutrino.input", O_RDWR|O_CLOEXEC|O_NONBLOCK); + if (out_fd < 0) + lt_info("could not create /tmp/neutrino.input. good luck. error: %m\n"); + + n = scandir("/dev/input", &namelist, dirfilter, NULL); + if (n < 0) + lt_info("no input devices /dev/input/eventX??\n"); + else + { + while (n--) { + strcpy(inputstr + strlen("/dev/input/"), namelist[n]->d_name); + free(namelist[n]); + int fd = open(inputstr, O_RDWR|O_CLOEXEC|O_NONBLOCK); + if (fd < 0) { + lt_info("could not open %s:%m\n", inputstr); + continue; + } + ioctl(fd, EVIOCGBIT(0, EV_MAX), &bit); + if ((bit & (1 << EV_KEY)) == 0) { + close(fd); + continue; + } + lt_info("input dev: %s bit: 0x%08lx fd: %d\n", inputstr, bit, fd); + in_fds.insert(fd); + if (fd > fd_max) + fd_max = fd; + } + free(namelist); + } + + fd_max++; + running = true; + while (running) { + FD_ZERO(&rfds); + for (std::set::iterator i = in_fds.begin(); i != in_fds.end(); ++i) + FD_SET((*i), &rfds); + + /* timeout should not be necessary, but somehow cancel / cleanup did not + * work correctly with OpenThreads::Thread :-( */ + struct timeval timeout = { 0, 100000 }; /* 100ms */ + int ret = select(fd_max, &rfds, NULL, NULL, &timeout); + if (ret == 0) /* timed out */ + continue; + if (ret < 0) { + lt_info("input: select returned %d (%m)\n", ret); + continue; + } + + for (std::set::iterator i = in_fds.begin(); i != in_fds.end(); ++i) { + if (!FD_ISSET((*i), &rfds)) + continue; + + ret = read(*i, &in, sizeof(in)); + if (ret != sizeof(in)) { + if (errno == ENODEV) { + close(*i); + lt_info("input fd %d vanished?\n", *i); + in_fds.erase(i); + } + continue; + } + if (in.type != EV_KEY) + continue; + keymap_t::const_iterator j = kmap.find(in.code); + if (j != kmap.end()) + in.code = j->second; + lt_debug("GLFB::%s:(fd %d) pushing 0x%x, value %d\n", __func__, *i, in.code, in.value); + write(out_fd, &in, sizeof(in)); + } + } + for (std::set::iterator i = in_fds.begin(); i != in_fds.end(); ++i) + close(*i); + in_fds.clear(); +} + +static Input *thread = NULL; + +void init_td_api() +{ + if (!initialized) + lt_debug_init(); + lt_info("%s begin, initialized=%d, debug=0x%02x\n", __func__, (int)initialized, debuglevel); + if (! glfb) { + int x = 1280, y = 720; /* default OSD FB resolution */ + /* + * export GLFB_RESOLUTION=720,576 + * to restore old default behviour + */ + const char *tmp = getenv("GLFB_RESOLUTION"); + const char *p = NULL; + if (tmp) + p = strchr(tmp, ','); + if (p) { + x = atoi(tmp); + y = atoi(p + 1); + } + lt_info("%s: setting Framebuffer size to %dx%d\n", __func__, x, y); + if (!p) + lt_info("%s: export GLFB_RESOLUTION=\",\" to set another resolution\n", __func__); + + glfb = new GLFramebuffer(x, y); /* hard coded to PAL resolution for now */ + } + if (! thread) + thread = new Input(); + initialized = true; +} + +void shutdown_td_api() +{ + lt_info("%s, initialized = %d\n", __func__, (int)initialized); + if (glfb) + delete glfb; + if (thread) + delete thread; + initialized = false; +} diff --git a/raspi/init_lib.h b/raspi/init_lib.h new file mode 100644 index 0000000..d9a6f09 --- /dev/null +++ b/raspi/init_lib.h @@ -0,0 +1,5 @@ +#ifndef __INIT_TD_H +#define __INIT_TD_H +void init_td_api(); +void shutdown_td_api(); +#endif diff --git a/raspi/playback.cpp b/raspi/playback.cpp new file mode 120000 index 0000000..d2e1498 --- /dev/null +++ b/raspi/playback.cpp @@ -0,0 +1 @@ +../generic-pc/playback.cpp \ No newline at end of file diff --git a/raspi/playback.h b/raspi/playback.h new file mode 120000 index 0000000..1363d92 --- /dev/null +++ b/raspi/playback.h @@ -0,0 +1 @@ +../generic-pc/playback.h \ No newline at end of file diff --git a/raspi/pwrmngr.cpp b/raspi/pwrmngr.cpp new file mode 120000 index 0000000..cee9acb --- /dev/null +++ b/raspi/pwrmngr.cpp @@ -0,0 +1 @@ +../libspark/pwrmngr.cpp \ No newline at end of file diff --git a/raspi/pwrmngr.h b/raspi/pwrmngr.h new file mode 120000 index 0000000..e63e97b --- /dev/null +++ b/raspi/pwrmngr.h @@ -0,0 +1 @@ +../libspark/pwrmngr.h \ No newline at end of file diff --git a/raspi/record.cpp b/raspi/record.cpp new file mode 120000 index 0000000..4daae8d --- /dev/null +++ b/raspi/record.cpp @@ -0,0 +1 @@ +../libspark/record.cpp \ No newline at end of file diff --git a/raspi/record_lib.h b/raspi/record_lib.h new file mode 120000 index 0000000..1ec9332 --- /dev/null +++ b/raspi/record_lib.h @@ -0,0 +1 @@ +../libspark/record_lib.h \ No newline at end of file diff --git a/raspi/video.cpp b/raspi/video.cpp new file mode 100644 index 0000000..91c18d1 --- /dev/null +++ b/raspi/video.cpp @@ -0,0 +1,189 @@ +/* + * (C) 2010-2013 Stefan Seyfried + * + * 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 2 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, write to the Free Software + * Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA + * + * cVideo dummy implementation + */ + +#include +#include +#include +#include + +#include "video_lib.h" +#include "lt_debug.h" +#define lt_debug(args...) _lt_debug(TRIPLE_DEBUG_VIDEO, this, args) +#define lt_info(args...) _lt_info(TRIPLE_DEBUG_VIDEO, this, args) +#define lt_info_c(args...) _lt_info(TRIPLE_DEBUG_VIDEO, NULL, args) + +cVideo *videoDecoder = NULL; +int system_rev = 0; + +cVideo::cVideo(int, void *, void *, unsigned int) +{ + lt_debug("%s\n", __func__); + display_aspect = DISPLAY_AR_16_9; + display_crop = DISPLAY_AR_MODE_LETTERBOX; + v_format = VIDEO_FORMAT_MPEG2; +} + +cVideo::~cVideo(void) +{ +} + +int cVideo::setAspectRatio(int vformat, int cropping) +{ + lt_info("%s(%d, %d)\n", __func__, vformat, cropping); + return 0; +} + +int cVideo::getAspectRatio(void) +{ + int ret = 0; + return ret; +} + +int cVideo::setCroppingMode(int) +{ + return 0; +} + +int cVideo::Start(void *, unsigned short, unsigned short, void *) +{ + lt_debug("%s running %d >\n", __func__, thread_running); + return 0; +} + +int cVideo::Stop(bool) +{ + lt_debug("%s running %d >\n", __func__, thread_running); + return 0; +} + +int cVideo::setBlank(int) +{ + return 1; +} + +int cVideo::SetVideoSystem(int system, bool) +{ + int h; + switch(system) + { + case VIDEO_STD_NTSC: + case VIDEO_STD_480P: + h = 480; + break; + case VIDEO_STD_1080I60: + case VIDEO_STD_1080I50: + case VIDEO_STD_1080P30: + case VIDEO_STD_1080P24: + case VIDEO_STD_1080P25: + case VIDEO_STD_1080P50: + h = 1080; + break; + case VIDEO_STD_720P50: + case VIDEO_STD_720P60: + h = 720; + break; + case VIDEO_STD_AUTO: + lt_info("%s: VIDEO_STD_AUTO not implemented\n", __func__); + // fallthrough + case VIDEO_STD_SECAM: + case VIDEO_STD_PAL: + case VIDEO_STD_576P: + h = 576; + break; + default: + lt_info("%s: unhandled value %d\n", __func__, system); + return 0; + } + v_std = (VIDEO_STD) system; + output_h = h; + return 0; +} + +int cVideo::getPlayState(void) +{ + return VIDEO_PLAYING; +} + +void cVideo::SetVideoMode(analog_mode_t) +{ +} + +void cVideo::ShowPicture(const char *fname) +{ + lt_info("%s(%s)\n", __func__, fname); + if (access(fname, R_OK)) + return; +} + +void cVideo::StopPicture() +{ +} + +void cVideo::Standby(unsigned int) +{ +} + +int cVideo::getBlank(void) +{ + return 0; +} + +void cVideo::Pig(int x, int y, int w, int h, int, int) +{ + pig_x = x; + pig_y = y; + pig_w = w; + pig_h = h; +} + +void cVideo::getPictureInfo(int &width, int &height, int &rate) +{ + width = dec_w; + height = dec_h; + rate = dec_r; +} + +void cVideo::SetSyncMode(AVSYNC_TYPE) +{ +}; + +int cVideo::SetStreamType(VIDEO_FORMAT v) +{ + v_format = v; + return 0; +} + +bool cVideo::GetScreenImage(unsigned char * &data, int &xres, int &yres, bool get_video, bool get_osd, bool scale_to_video) +{ + lt_info("%s: data 0x%p xres %d yres %d vid %d osd %d scale %d\n", + __func__, data, xres, yres, get_video, get_osd, scale_to_video); + return false; +} + +int64_t cVideo::GetPTS(void) +{ + int64_t pts = 0; + return pts; +} + +void cVideo::SetDemux(cDemux *) +{ + lt_debug("%s: not implemented yet\n", __func__); +} diff --git a/raspi/video_lib.h b/raspi/video_lib.h new file mode 100644 index 0000000..07718b1 --- /dev/null +++ b/raspi/video_lib.h @@ -0,0 +1,215 @@ +#ifndef _VIDEO_TD_H +#define _VIDEO_TD_H + +#include +#include +#include "../common/cs_types.h" +#include "dmx_lib.h" +extern "C" { +#include +} + +typedef enum { + ANALOG_SD_RGB_CINCH = 0x00, + ANALOG_SD_YPRPB_CINCH, + ANALOG_HD_RGB_CINCH, + ANALOG_HD_YPRPB_CINCH, + ANALOG_SD_RGB_SCART = 0x10, + ANALOG_SD_YPRPB_SCART, + ANALOG_HD_RGB_SCART, + ANALOG_HD_YPRPB_SCART, + ANALOG_SCART_MASK = 0x10 +} analog_mode_t; + + +typedef enum { + VIDEO_FORMAT_MPEG2 = 0, + VIDEO_FORMAT_MPEG4, + VIDEO_FORMAT_VC1, + VIDEO_FORMAT_JPEG, + VIDEO_FORMAT_GIF, + VIDEO_FORMAT_PNG +} VIDEO_FORMAT; + +typedef enum { + VIDEO_SD = 0, + VIDEO_HD, + VIDEO_120x60i, + VIDEO_320x240i, + VIDEO_1440x800i, + VIDEO_360x288i +} VIDEO_DEFINITION; + +typedef enum { + VIDEO_FRAME_RATE_23_976 = 0, + VIDEO_FRAME_RATE_24, + VIDEO_FRAME_RATE_25, + VIDEO_FRAME_RATE_29_97, + VIDEO_FRAME_RATE_30, + VIDEO_FRAME_RATE_50, + VIDEO_FRAME_RATE_59_94, + VIDEO_FRAME_RATE_60 +} VIDEO_FRAME_RATE; + +typedef enum { + DISPLAY_AR_1_1, + DISPLAY_AR_4_3, + DISPLAY_AR_14_9, + DISPLAY_AR_16_9, + DISPLAY_AR_20_9, + DISPLAY_AR_RAW, +} DISPLAY_AR; + +typedef enum { + DISPLAY_AR_MODE_PANSCAN = 0, + DISPLAY_AR_MODE_LETTERBOX, + DISPLAY_AR_MODE_NONE, + DISPLAY_AR_MODE_PANSCAN2 +} DISPLAY_AR_MODE; + +typedef enum { + VIDEO_DB_DR_NEITHER = 0, + VIDEO_DB_ON, + VIDEO_DB_DR_BOTH +} VIDEO_DB_DR; + +typedef enum { + VIDEO_PLAY_STILL = 0, + VIDEO_PLAY_CLIP, + VIDEO_PLAY_TRICK, + VIDEO_PLAY_MOTION, + VIDEO_PLAY_MOTION_NO_SYNC +} VIDEO_PLAY_MODE; + +typedef enum { + VIDEO_STD_NTSC, + VIDEO_STD_SECAM, + VIDEO_STD_PAL, + VIDEO_STD_480P, + VIDEO_STD_576P, + VIDEO_STD_720P60, + VIDEO_STD_1080I60, + VIDEO_STD_720P50, + VIDEO_STD_1080I50, + VIDEO_STD_1080P30, + VIDEO_STD_1080P24, + VIDEO_STD_1080P25, + VIDEO_STD_AUTO, + VIDEO_STD_1080P50, /* SPARK only */ + VIDEO_STD_MAX +} VIDEO_STD; + +/* not used, for dummy functions */ +typedef enum { + VIDEO_HDMI_CEC_MODE_OFF = 0, + VIDEO_HDMI_CEC_MODE_TUNER, + VIDEO_HDMI_CEC_MODE_RECORDER +} VIDEO_HDMI_CEC_MODE; + +typedef enum +{ + VIDEO_CONTROL_BRIGHTNESS = 0, + VIDEO_CONTROL_CONTRAST, + VIDEO_CONTROL_SATURATION, + VIDEO_CONTROL_HUE, + VIDEO_CONTROL_SHARPNESS, + VIDEO_CONTROL_MAX = VIDEO_CONTROL_SHARPNESS +} VIDEO_CONTROL; + + +#define VDEC_MAXBUFS 0x30 +class cVideo +{ + friend class GLFramebuffer; + friend class cDemux; + private: + /* called from GL thread */ + class SWFramebuffer : public std::vector + { + public: + SWFramebuffer() : mWidth(0), mHeight(0) {} + void width(int w) { mWidth = w; } + void height(int h) { mHeight = h; } + void pts(uint64_t p) { mPts = p; } + void AR(AVRational a) { mAR = a; } + int width() const { return mWidth; } + int height() const { return mHeight; } + int64_t pts() const { return mPts; } + AVRational AR() const { return mAR; } + private: + int mWidth; + int mHeight; + int64_t mPts; + AVRational mAR; + }; + int buf_in, buf_out, buf_num; + int64_t GetPTS(void); + public: + /* constructor & destructor */ + cVideo(int mode, void *, void *, unsigned int unit = 0); + ~cVideo(void); + + void * GetTVEnc() { return NULL; }; + void * GetTVEncSD() { return NULL; }; + + /* aspect ratio */ + int getAspectRatio(void); + void getPictureInfo(int &width, int &height, int &rate); + int setAspectRatio(int aspect, int mode); + + /* cropping mode */ + int setCroppingMode(int x = 0 /*vidDispMode_t x = VID_DISPMODE_NORM*/); + + /* get play state */ + int getPlayState(void); + + /* blank on freeze */ + int getBlank(void); + int setBlank(int enable); + + /* change video play state. Parameters are all unused. */ + int Start(void *PcrChannel = NULL, unsigned short PcrPid = 0, unsigned short VideoPid = 0, void *x = NULL); + int Stop(bool blank = true); + bool Pause(void); + + /* set video_system */ + int SetVideoSystem(int video_system, bool remember = true); + int SetStreamType(VIDEO_FORMAT type); + void SetSyncMode(AVSYNC_TYPE mode); + bool SetCECMode(VIDEO_HDMI_CEC_MODE) { return true; }; + void SetCECAutoView(bool) { return; }; + void SetCECAutoStandby(bool) { return; }; + void ShowPicture(const char * fname); + void StopPicture(); + void Standby(unsigned int bOn); + void Pig(int x, int y, int w, int h, int osd_w = 1064, int osd_h = 600); + void SetControl(int, int) { return; }; + void setContrast(int val); + void SetVideoMode(analog_mode_t mode); + void SetDBDR(int) { return; }; + void SetAudioHandle(void *) { return; }; + void SetAutoModes(int [VIDEO_STD_MAX]) { return; }; + int OpenVBI(int) { return 0; }; + int CloseVBI(void) { return 0; }; + int StartVBI(unsigned short) { return 0; }; + int StopVBI(void) { return 0; }; + void SetDemux(cDemux *dmx); + bool GetScreenImage(unsigned char * &data, int &xres, int &yres, bool get_video = true, bool get_osd = false, bool scale_to_video = false); + private: + SWFramebuffer buffers[VDEC_MAXBUFS]; + int dec_w, dec_h; + int dec_r; + bool w_h_changed; + bool thread_running; + VIDEO_FORMAT v_format; + VIDEO_STD v_std; + DISPLAY_AR display_aspect; + DISPLAY_AR_MODE display_crop; + int output_h; + int pig_x; + int pig_y; + int pig_w; + int pig_h; +}; + +#endif diff --git a/tools/Makefile.am b/tools/Makefile.am new file mode 100644 index 0000000..5ee4d5b --- /dev/null +++ b/tools/Makefile.am @@ -0,0 +1,9 @@ +bin_PROGRAMS = + +if BOXTYPE_SPARK +bin_PROGRAMS += spark_fp +spark_fp_SOURCES = spark_fp.c +endif + +bin_PROGRAMS += pic2m2v +pic2m2v_SOURCES = pic2m2v.c diff --git a/tools/pic2m2v.c b/tools/pic2m2v.c new file mode 100644 index 0000000..11b98ea --- /dev/null +++ b/tools/pic2m2v.c @@ -0,0 +1,85 @@ +/* + * (C) 2012 Stefan Seyfried + * + * 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 2 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 . + */ + +#ifdef HAVE_CONFIG_H +#include +#endif +#include +#include +#include +#include +#include +#include + +#if HAVE_TRIPLEDRAGON +#define TARGETRES "704x576" +#else +#define TARGETRES "1280x720" +#endif + +int main(int argc, char **argv) +{ + char destname[512]; + char cmd[512]; + char *p; + char *fname; + struct stat st, st2; + + if (argc < 2) + { + fprintf(stderr, "usage: pic2m2v /path/pic1.jpg [/path/pic2.jpg...]\n\n"); + return 1; + } + strcpy(destname, "/var/cache"); + mkdir(destname, 0755); + argv++; + for (fname = *argv; fname != NULL; fname = *++argv) + { + if (stat(fname, &st2)) + { + fprintf(stderr, "pic2m2v: could not stat '%s' (%m)\n", fname); + continue; + } + strcpy(destname, "/var/cache"); + /* the cache filename is (example for /share/tuxbox/neutrino/icons/radiomode.jpg): + /var/cache/share.tuxbox.neutrino.icons.radiomode.jpg.m2v + build that filename first... + TODO: this could cause name clashes, use a hashing function instead... */ + strcat(destname, fname); + p = &destname[strlen("/var/cache/")]; + while ((p = strchr(p, '/')) != NULL) + *p = '.'; + strcat(destname, ".m2v"); + /* ...then check if it exists already... */ + if (stat(destname, &st) || (st.st_mtime != st2.st_mtime) || (st.st_size == 0)) + { + struct utimbuf u; + u.actime = time(NULL); + u.modtime = st2.st_mtime; + printf("converting %s -> %s\n", fname, destname); + /* it does not exist or has a different date, so call ffmpeg... */ + sprintf(cmd, "ffmpeg -y -f mjpeg -i '%s' -s %s '%s' + * + * License: GPL v2 or later + * + * 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 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FP_DEV "/dev/vfd" + +void usage() +{ + printf("usage spark_fp