diff --git a/libspark/Makefile.am b/libspark/Makefile.am new file mode 100644 index 0000000..ba38724 --- /dev/null +++ b/libspark/Makefile.am @@ -0,0 +1,18 @@ +INCLUDES = \ + @DIRECTFB_CFLAGS@ + +noinst_LIBRARIES = libtriple.a + +AM_CXXFLAGS = -fno-rtti -fno-exceptions -fno-strict-aliasing + +libtriple_a_SOURCES = \ + lt_dfbinput.cpp \ + lt_debug.cpp \ + dmx_td.cpp \ + ca.cpp \ + video_td.cpp \ + audio_td.cpp \ + init_td.cpp \ + playback_td.cpp \ + pwrmngr.cpp \ + record_td.cpp 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_td.cpp b/libspark/audio_td.cpp new file mode 100644 index 0000000..eb21456 --- /dev/null +++ b/libspark/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/libspark/audio_td.h b/libspark/audio_td.h new file mode 100644 index 0000000..5a62db3 --- /dev/null +++ b/libspark/audio_td.h @@ -0,0 +1,92 @@ +/* public header file */ + +#ifndef _AUDIO_TD_H_ +#define _AUDIO_TD_H_ + +#include + +typedef enum +{ + AUDIO_SYNC_WITH_PTS, + AUDIO_NO_SYNC, + AUDIO_SYNC_AUDIO_MASTER +} AUDIO_SYNC_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 +{ + 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); +#define AVSYNC_TYPE int + 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/libspark/ca.cpp b/libspark/ca.cpp new file mode 100644 index 0000000..f6ebea2 --- /dev/null +++ b/libspark/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/libspark/ca.h b/libspark/ca.h new file mode 100644 index 0000000..8a29e56 --- /dev/null +++ b/libspark/ca.h @@ -0,0 +1,97 @@ +/* + * dummy functions to implement ca_cs.h interface + */ +#ifndef __CA_LIBTRIPLE_H_ +#define __CA_LIBTRIPLE_H_ + +#include +/* used in cam_menu.cpp */ +typedef uint32_t u32; + +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_PARAM_LONG = (1 << 13), +}; + +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; + } 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 SendMessage(const CA_MESSAGE *Msg); + void SetInitMask(enum CA_INIT_MASK InitMask); + 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); + virtual ~cCA(); +}; + +#endif // __CA_LIBTRIPLE_H_ diff --git a/libspark/ca_cs.h b/libspark/ca_cs.h new file mode 100644 index 0000000..dae70d6 --- /dev/null +++ b/libspark/ca_cs.h @@ -0,0 +1 @@ +#include "ca.h" diff --git a/libspark/cs_api.h b/libspark/cs_api.h new file mode 100644 index 0000000..292430d --- /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_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/libspark/dmx_cs.h b/libspark/dmx_cs.h new file mode 100644 index 0000000..4f0dbc1 --- /dev/null +++ b/libspark/dmx_cs.h @@ -0,0 +1 @@ +#include "dmx_td.h" diff --git a/libspark/dmx_td.cpp b/libspark/dmx_td.cpp new file mode 100644 index 0000000..8f9247b --- /dev/null +++ b/libspark/dmx_td.cpp @@ -0,0 +1,610 @@ +#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_PES_CHANNEL) { \ + _pid = p_flt.pid; \ + } else if (dmx_type == DMX_PSI_CHANNEL) { \ + _pid = s_flt.pid; _f = s_flt.filter[0]; \ + }; \ + 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) +{ + int 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) + { + if (num == 0) /* 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) + { + 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", __FUNCTION__, num, len, FILTER_LENGTH); + + 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[num], 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/libspark/dmx_td.h b/libspark/dmx_td.h new file mode 100644 index 0000000..1eb9ba1 --- /dev/null +++ b/libspark/dmx_td.h @@ -0,0 +1,72 @@ +#ifndef __DEMUX_TD_H +#define __DEMUX_TD_H + +#include +#include +extern "C" { +#include +#include +#include +} +#if defined DMX_FILTER_SIZE +#undef DMX_FILTER_SIZE +#endif +#define DMX_FILTER_SIZE FILTER_LENGTH + +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 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); +#define AVSYNC_TYPE int + 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); + // 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/init_cs.h b/libspark/init_cs.h new file mode 100644 index 0000000..5894a14 --- /dev/null +++ b/libspark/init_cs.h @@ -0,0 +1,2 @@ +#warning using init_cs.h from libtriple +#include "init_td.h" diff --git a/libspark/init_td.cpp b/libspark/init_td.cpp new file mode 100644 index 0000000..121297d --- /dev/null +++ b/libspark/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/libspark/init_td.h b/libspark/init_td.h new file mode 100644 index 0000000..d9a6f09 --- /dev/null +++ b/libspark/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/libspark/lt_debug.cpp b/libspark/lt_debug.cpp new file mode 100644 index 0000000..831d265 --- /dev/null +++ b/libspark/lt_debug.cpp @@ -0,0 +1,76 @@ +/* libtriple debug functions */ + +#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("TRIPLE_DEBUG"); + if (! tmp) + debuglevel = 0; + else + debuglevel = (int) strtol(tmp, NULL, 0); + + if (debuglevel == 0) + { + fprintf(stderr, "libtriple debug options can be set by exporting TRIPLE_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, "libtriple 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"); + } +} diff --git a/libspark/lt_debug.h b/libspark/lt_debug.h new file mode 100644 index 0000000..13b08d1 --- /dev/null +++ b/libspark/lt_debug.h @@ -0,0 +1,19 @@ +#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) + +extern int debuglevel; + +void _lt_debug(int facility, const void *, const char *fmt, ...); +void _lt_info(int facility, const void *, const char *fmt, ...); +void lt_debug_init(void); +#endif diff --git a/libspark/lt_dfbinput.cpp b/libspark/lt_dfbinput.cpp new file mode 100644 index 0000000..252d14f --- /dev/null +++ b/libspark/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; + } + + 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 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)); + + 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; */ + default: + continue; + } + switch (e.type) + { + case 1: if (u.value < 2) /* 1 = key press */ + u.value++; /* 2 = key repeat */ + break; + case 2: u.value = 0; break; /* 0 = key release */ + 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/libspark/lt_dfbinput.h b/libspark/lt_dfbinput.h new file mode 100644 index 0000000..1a74fb2 --- /dev/null +++ b/libspark/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/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.h b/libspark/playback.h new file mode 100644 index 0000000..6e6b4c5 --- /dev/null +++ b/libspark/playback.h @@ -0,0 +1 @@ +#include "playback_td.h" diff --git a/libspark/playback_td.cpp b/libspark/playback_td.cpp new file mode 100644 index 0000000..0455a79 --- /dev/null +++ b/libspark/playback_td.cpp @@ -0,0 +1,1463 @@ +#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 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) +{ + 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); +} + +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_td.h b/libspark/playback_td.h new file mode 100644 index 0000000..dcb78e3 --- /dev/null +++ b/libspark/playback_td.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/pwrmngr.cpp b/libspark/pwrmngr.cpp new file mode 100644 index 0000000..f16297e --- /dev/null +++ b/libspark/pwrmngr.cpp @@ -0,0 +1,74 @@ +#include + +#include "pwrmngr.h" +#include "lt_debug.h" +#include +#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; } +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); + } + 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/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_td.cpp b/libspark/record_td.cpp new file mode 100644 index 0000000..32c1a01 --- /dev/null +++ b/libspark/record_td.cpp @@ -0,0 +1,261 @@ +#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) +{ + 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); +} + diff --git a/libspark/record_td.h b/libspark/record_td.h new file mode 100644 index 0000000..75099f7 --- /dev/null +++ b/libspark/record_td.h @@ -0,0 +1,36 @@ +#ifndef __RECORD_TD_H +#define __RECORD_TD_H + +#include +#include "dmx_td.h" + +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; + public: + cRecord(int num = 0); + ~cRecord(); + + bool Open(); + bool Start(int fd, unsigned short vpid, unsigned short *apids, int numapids); + bool Stop(void); + bool AddPid(unsigned short pid); + 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_td.cpp b/libspark/video_td.cpp new file mode 100644 index 0000000..2ad99d5 --- /dev/null +++ b/libspark/video_td.cpp @@ -0,0 +1,713 @@ +/* + * (C) 2002-2003 Andreas Oberritter + * (C) 2010-2011 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 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, 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 "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 *) +{ + 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; + } + } + 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; + strcpy(destname, "/var/cache"); + 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... + TODO: check if the cache file is older than the jpeg file... */ + if (access(destname, R_OK)) + { + /* it does not exist, so call ffmpeg to create it... */ + 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) +{ + lt_debug("%s\n", __FUNCTION__); + return 0; +} + +/* 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); +} diff --git a/libspark/video_td.h b/libspark/video_td.h new file mode 100644 index 0000000..0493880 --- /dev/null +++ b/libspark/video_td.h @@ -0,0 +1,192 @@ +#ifndef _VIDEO_TD_H +#define _VIDEO_TD_H + +#include +#define video_format_t vidDispSize_t +//#define video_displayformat_t vidDispMode_t + + +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 +} 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 *); + ~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); +#define AVSYNC_TYPE int + 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; }; +}; + +#endif