From a4bfddd2f995ad03409de005bc3015437c10aa06 Mon Sep 17 00:00:00 2001 From: Klaus Schmidinger Date: Sun, 16 Jun 2002 12:57:31 +0200 Subject: [PATCH] Totally rearranged device/player/recorder structures --- HISTORY | 21 +- Makefile | 9 +- PLUGINS.html | 8 +- audio.c | 11 + audio.h | 13 + config.c | 29 +- config.h | 10 +- device.c | 1017 ++++++++++++++++++++++++++++++++++++++++++++++++++ device.h | 199 ++++++++++ dvbplayer.c | 733 ++++++++++++++++++++++++++++++++++++ dvbplayer.h | 60 +++ eit.c | 3 +- eitscan.c | 20 +- interface.c | 6 +- menu.c | 212 ++++++----- menu.h | 21 +- osd.c | 6 +- player.c | 55 +++ player.h | 51 +++ receiver.c | 55 +++ receiver.h | 48 +++ recorder.c | 149 ++++++++ recorder.h | 43 +++ recording.c | 350 ++++++++++++++++- recording.h | 61 ++- ringbuffer.c | 93 ++--- ringbuffer.h | 42 +-- status.c | 14 +- status.h | 23 +- svdrp.c | 26 +- thread.c | 3 +- thread.h | 4 +- tools.h | 9 +- vdr.c | 51 +-- 34 files changed, 3144 insertions(+), 311 deletions(-) create mode 100644 audio.c create mode 100644 audio.h create mode 100644 device.c create mode 100644 device.h create mode 100644 dvbplayer.c create mode 100644 dvbplayer.h create mode 100644 player.c create mode 100644 player.h create mode 100644 receiver.c create mode 100644 receiver.h create mode 100644 recorder.c create mode 100644 recorder.h diff --git a/HISTORY b/HISTORY index 48c4e882..36df4217 100644 --- a/HISTORY +++ b/HISTORY @@ -1301,7 +1301,7 @@ Video Disk Recorder Revision History - Removed compiler option '-m486' to make it work on non-Intel platforms (thanks to Alastair McKinstry for pointing this out). -2002-05-26: Version 1.1.3 +2002-06-16: Version 1.1.3 - Improved the VDR Makefile to avoid a warning if the '.dependencies' file does not exist, and also using $(MAKE) to call recursive makes. @@ -1328,3 +1328,22 @@ Video Disk Recorder Revision History - Removed compiler option '-m486' to make it work on non-Intel platforms. If you have already started a plugin project, you may want to make sure you remove this option from your existing Makefile. +- Completely rearranged the recording and replay functions to make them available + to plugins. +- Replay is now done in a single thread (no more syncing between input and output + thread necessary). +- It is now possible to record several channels on the same transponder with "budget + cards". VDR automatically attaches a recording timer to a card that already + records on the appropriate transponder. How many parallel recordings can actually + be done depends on the computer's performance. Currently any number of recordings + gets attached to a card, so you should carefully plan your timers to not exceed + the limit. On a K6-II/450 it was possible to record three channels from transponder + 12480 with a single WinTV NOVA-S. +- Timers that record two successive shows on the same channel may now overlap and + will use the same DVB card. During the time where both timers record the data + is simply saved to both files. +- The following limitations apply to this version: + + Transfer mode doesn't work yet. + + The '-a' option (for Dolby Digital audio) doesn't work yet. + + Switching between different language tracks doesn't work yet. + + Cutting doesn't work yet. diff --git a/Makefile b/Makefile index d0964ba3..09654b21 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ # See the main source file 'vdr.c' for copyright information and # how to reach the author. # -# $Id: Makefile 1.39 2002/06/10 16:26:51 kls Exp $ +# $Id: Makefile 1.40 2002/06/10 16:31:34 kls Exp $ .DELETE_ON_ERROR: @@ -21,9 +21,10 @@ INCLUDES = -I$(DVBDIR)/ost/include DTVLIB = $(DTVDIR)/libdtv.a -OBJS = config.o dvbapi.o dvbosd.o eit.o eitscan.o font.o i18n.o interface.o menu.o\ - menuitems.o osdbase.o osd.o plugin.o recording.o remote.o remux.o ringbuffer.o\ - status.o svdrp.o thread.o tools.o vdr.o videodir.o +OBJS = audio.o config.o device.o dvbplayer.o dvbosd.o eit.o eitscan.o font.o i18n.o\ + interface.o menu.o menuitems.o osdbase.o osd.o player.o plugin.o receiver.o\ + recorder.o recording.o remote.o remux.o ringbuffer.o status.o svdrp.o thread.o\ + tools.o vdr.o videodir.o OSDFONT = -adobe-helvetica-medium-r-normal--23-*-100-100-p-*-iso8859-1 FIXFONT = -adobe-courier-bold-r-normal--25-*-100-100-m-*-iso8859-1 diff --git a/PLUGINS.html b/PLUGINS.html index f531f361..a0f7ebc9 100644 --- a/PLUGINS.html +++ b/PLUGINS.html @@ -864,15 +864,15 @@ If a plugin wants to get informed on various events in VDR, it can derive a clas class cMyStatusMonitor : public cStatusMonitor { protected: - virtual void ChannelSwitch(const cDvbApi *DvbApi, int ChannelNumber); + virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber); }; -void cMyStatusMonitor::ChannelSwitch(const cDvbApi *DvbApi, int ChannelNumber) +void cMyStatusMonitor::ChannelSwitch(const cDevice *Device, int ChannelNumber) { if (ChannelNumber) - dsyslog("channel switched to %d on DVB %d", ChannelNumber, DvbApi->CardIndex()); + dsyslog("channel switched to %d on DVB %d", ChannelNumber, Device->CardIndex()); else - dsyslog("about to switch channel on DVB %d", DvbApi->CardIndex()); + dsyslog("about to switch channel on DVB %d", Device->CardIndex()); }

diff --git a/audio.c b/audio.c new file mode 100644 index 00000000..5e7bb73b --- /dev/null +++ b/audio.c @@ -0,0 +1,11 @@ +/* + * audio.c: The basic audio interface + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * $Id: audio.c 1.1 2002/06/10 16:30:00 kls Exp $ + */ + +#include "audio.h" + diff --git a/audio.h b/audio.h new file mode 100644 index 00000000..2541cdcb --- /dev/null +++ b/audio.h @@ -0,0 +1,13 @@ +/* + * audio.h: The basic audio interface + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * $Id: audio.h 1.1 2002/06/10 16:30:00 kls Exp $ + */ + +#ifndef __AUDIO_H +#define __AUDIO_H + +#endif //__AUDIO_H diff --git a/config.c b/config.c index f28d1a54..6753ce8d 100644 --- a/config.c +++ b/config.c @@ -4,16 +4,16 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: config.c 1.101 2002/05/13 16:28:12 kls Exp $ + * $Id: config.c 1.102 2002/06/16 12:57:31 kls Exp $ */ #include "config.h" #include #include -#include "dvbapi.h" #include "i18n.h" #include "interface.h" #include "plugin.h" +#include "recording.h" // IMPORTANT NOTE: in the 'sscanf()' calls there is a blank after the '%d' // format characters in order to allow any number of blanks after a numeric @@ -293,15 +293,15 @@ bool cChannel::Save(FILE *f) return fprintf(f, ToText()) > 0; } -bool cChannel::Switch(cDvbApi *DvbApi, bool Log) +bool cChannel::Switch(cDevice *Device, bool Log) { - if (!DvbApi) - DvbApi = cDvbApi::PrimaryDvbApi; - if (!DvbApi->Recording() && !groupSep) { + if (!Device) + Device = cDevice::PrimaryDevice(); + if (!(Device->IsPrimaryDevice() && Device->Receiving()) && !groupSep) { if (Log) isyslog("switching to channel %d", number); for (int i = 3; i--;) { - switch (DvbApi->SetChannel(number, frequency, polarization, diseqc, srate, vpid, apid1, apid2, dpid1, dpid2, tpid, ca, pnr)) { + switch (Device->SetChannel(number, frequency, polarization, diseqc, srate, vpid, apid1, tpid, ca, pnr)) { case scrOk: return true; case scrNoTransfer: if (Interface) Interface->Error(tr("Can't start Transfer Mode!")); @@ -312,7 +312,7 @@ bool cChannel::Switch(cDvbApi *DvbApi, bool Log) } return false; } - if (DvbApi->Recording()) + if (Device->IsPrimaryDevice() && Device->Receiving()) Interface->Error(tr("Channel locked (recording)!")); return false; } @@ -326,7 +326,7 @@ cTimer::cTimer(bool Instant) startTime = stopTime = 0; recording = pending = false; active = Instant ? taActInst : taInactive; - cChannel *ch = Channels.GetByNumber(cDvbApi::CurrentChannel()); + cChannel *ch = Channels.GetByNumber(cDevice::CurrentChannel()); channel = ch ? ch->number : 0; time_t t = time(NULL); struct tm tm_r; @@ -836,10 +836,10 @@ cChannel *cChannels::GetByServiceID(unsigned short ServiceId) return NULL; } -bool cChannels::SwitchTo(int Number, cDvbApi *DvbApi) +bool cChannels::SwitchTo(int Number, cDevice *Device) { cChannel *channel = GetByNumber(Number); - return channel && channel->Switch(DvbApi); + return channel && channel->Switch(Device); } const char *cChannels::GetChannelNameByNumber(int Number) @@ -957,6 +957,7 @@ bool cSetupLine::operator< (const cListObject &ListObject) bool cSetupLine::Parse(char *s) { + //dsyslog("cSetupLine::Parse '%s'", s);//XXX- char *p = strchr(s, '='); if (p) { *p = 0; @@ -974,6 +975,7 @@ bool cSetupLine::Parse(char *s) } name = strdup(Name); value = strdup(Value); + //dsyslog("cSetupLine::Parse '%s' = '%s'", name, value);//XXX- return true; } } @@ -982,6 +984,7 @@ bool cSetupLine::Parse(char *s) bool cSetupLine::Save(FILE *f) { + //dsyslog("cSetupLine::Save '%s' = '%s'", name, value);//XXX- return fprintf(f, "%s%s%s = %s\n", plugin ? plugin : "", plugin ? "." : "", name, value) > 0; } @@ -1095,7 +1098,7 @@ bool cSetup::Load(const char *FileName) void cSetup::StoreCaCaps(const char *Name) { - for (int d = 0; d < MAXDVBAPI; d++) { + for (int d = 0; d < MAXDEVICES; d++) { char buffer[MAXPARSEBUFFER]; char *q = buffer; *buffer = 0; @@ -1115,7 +1118,7 @@ bool cSetup::ParseCaCaps(const char *Value) { char *p; int d = strtol(Value, &p, 10); - if (d > 0 && d <= MAXDVBAPI) { + if (d > 0 && d <= MAXDEVICES) { d--; int i = 0; while (p != Value && p && *p) { diff --git a/config.h b/config.h index 8ef6f69a..1d650803 100644 --- a/config.h +++ b/config.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: config.h 1.117 2002/05/14 21:21:53 kls Exp $ + * $Id: config.h 1.118 2002/06/10 16:30:00 kls Exp $ */ #ifndef __CONFIG_H @@ -15,7 +15,7 @@ #include #include #include -#include "dvbapi.h" +#include "device.h" #include "eit.h" #include "tools.h" @@ -118,7 +118,7 @@ public: const char *ToText(void); bool Parse(const char *s); bool Save(FILE *f); - bool Switch(cDvbApi *DvbApi = NULL, bool Log = true); + bool Switch(cDevice *Device = NULL, bool Log = true); }; enum eTimerActive { taInactive = 0, @@ -294,7 +294,7 @@ public: cChannel *GetByNumber(int Number); cChannel *GetByServiceID(unsigned short ServiceId); const char *GetChannelNameByNumber(int Number); - bool SwitchTo(int Number, cDvbApi *DvbApi = NULL); + bool SwitchTo(int Number, cDevice *Device = NULL); int MaxNumber(void) { return maxNumber; } }; @@ -385,7 +385,7 @@ public: int MinEventTimeout, MinUserInactivity; int MultiSpeedMode; int ShowReplayMode; - int CaCaps[MAXDVBAPI][MAXCACAPS]; + int CaCaps[MAXDEVICES][MAXCACAPS]; int CurrentChannel; int CurrentVolume; int __EndData__; diff --git a/device.c b/device.c new file mode 100644 index 00000000..8da7fdde --- /dev/null +++ b/device.c @@ -0,0 +1,1017 @@ +/* + * device.c: The basic device interface + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * $Id: device.c 1.1 2002/06/16 12:29:09 kls Exp $ + */ + +#include "device.h" +#include +extern "C" { +#define HAVE_BOOLEAN +#include +} +#include +#include +#include +#include +#include +#include "player.h" +#include "receiver.h" +#include "status.h" + +#define DEV_VIDEO "/dev/video" +#define DEV_OST_OSD "/dev/ost/osd" +#define DEV_OST_FRONTEND "/dev/ost/frontend" +#define DEV_OST_SEC "/dev/ost/sec" +#define DEV_OST_DVR "/dev/ost/dvr" +#define DEV_OST_DEMUX "/dev/ost/demux" +#define DEV_OST_VIDEO "/dev/ost/video" +#define DEV_OST_AUDIO "/dev/ost/audio" + +// The default priority for non-primary DVB cards: +#define DEFAULTPRIORITY -2 + +#define TS_SIZE 188 +#define TS_SYNC_BYTE 0x47 +#define PID_MASK_HI 0x1F + +// The maximum time we wait before assuming that a recorded video data stream +// is broken: +#define MAXBROKENTIMEOUT 30 // seconds + +static const char *OstName(const char *Name, int n) +{ + static char buffer[_POSIX_PATH_MAX]; + snprintf(buffer, sizeof(buffer), "%s%d", Name, n); + return buffer; +} + +static int OstOpen(const char *Name, int n, int Mode, bool ReportError = false) +{ + const char *FileName = OstName(Name, n); + int fd = open(FileName, Mode); + if (fd < 0 && ReportError) + LOG_ERROR_STR(FileName); + return fd; +} + +int cDevice::numDevices = 0; +int cDevice::useDevice = 0; +cDevice *cDevice::device[MAXDEVICES] = { NULL }; +cDevice *cDevice::primaryDevice = NULL; + +cDevice::cDevice(int n) +{ + frontendType = FrontendType(-1); // don't know how else to initialize this - there is no FE_UNKNOWN + siProcessor = NULL; + cardIndex = n; + + // Devices that are present on all card types: + + fd_frontend = OstOpen(DEV_OST_FRONTEND, n, O_RDWR); + + // Devices that are only present on DVB-S cards: + + fd_sec = OstOpen(DEV_OST_SEC, n, O_RDWR); + + // Devices that are only present on cards with decoders: + + fd_osd = OstOpen(DEV_OST_OSD, n, O_RDWR); + fd_video = OstOpen(DEV_OST_VIDEO, n, O_RDWR | O_NONBLOCK); + fd_audio = OstOpen(DEV_OST_AUDIO, n, O_RDWR | O_NONBLOCK); + + // Video format: + + SetVideoFormat(Setup.VideoFormat ? VIDEO_FORMAT_16_9 : VIDEO_FORMAT_4_3); + + // We only check the devices that must be present - the others will be checked before accessing them://XXX + + if (fd_frontend >= 0) { + siProcessor = new cSIProcessor(OstName(DEV_OST_DEMUX, n)); + FrontendInfo feinfo; + if (ioctl(fd_frontend, FE_GET_INFO, &feinfo) >= 0) + frontendType = feinfo.type; + else + LOG_ERROR; + } + else + esyslog("ERROR: can't open video device %d", n); + + dvrFileName = strdup(OstName(DEV_OST_DVR, CardIndex())); + active = false; + + currentChannel = 0; + frequency = 0; + + mute = false; + volume = Setup.CurrentVolume; + + player = NULL; + + for (int i = 0; i < MAXRECEIVERS; i++) + receiver[i] = NULL; + ca = -1; +} + +cDevice::~cDevice() +{ + delete dvrFileName; + delete siProcessor; + Detach(player); + for (int i = 0; i < MAXRECEIVERS; i++) + Detach(receiver[i]); + // We're not explicitly closing any device files here, since this sometimes + // caused segfaults. Besides, the program is about to terminate anyway... +} + +void cDevice::SetUseDevice(int n) +{ + if (n < MAXDEVICES) + useDevice |= (1 << n); +} + +bool cDevice::SetPrimaryDevice(int n) +{ + n--; + if (0 <= n && n < numDevices && device[n]) { + isyslog("setting primary device to %d", n + 1); + primaryDevice = device[n]; + return true; + } + esyslog("invalid devive number: %d", n + 1); + return false; +} + +cDevice *cDevice::GetDevice(int Ca, int Priority, int Frequency, int Vpid, bool *ReUse) +{ + if (ReUse) + *ReUse = false; + cDevice *d = NULL; + int Provides[MAXDEVICES]; + // Check which devices provide Ca: + for (int i = 0; i < numDevices; i++) { + if ((Provides[i] = device[i]->ProvidesCa(Ca)) != 0) { // this device is basicly able to do the job + //XXX+ dsyslog("GetDevice: %d %d %d %5d %5d", i, device[i]->HasDecoder(), device[i]->Receiving(), Frequency, device[i]->frequency);//XXX + if ( (!device[i]->HasDecoder() // it's a "budget card" which can receive multiple channels... + && device[i]->frequency == Frequency // ...and it is tuned to the requested frequency... + && device[i]->Receiving() // ...and is already receiving + // need not check priority - if a budget card is already receiving on the requested + // frequency, we can attach another receiver regardless of priority + ) + || (device[i]->HasDecoder() // it's a "full featured card" which can receive only one channel... + && device[i]->frequency == Frequency // ...and it is tuned to the requested frequency... + && device[i]->pidHandles[ptVideo].pid == Vpid // ...and the requested video PID... + && device[i]->Receiving() // ...and is already receiving + // need not check priority - if a full featured card is already receiving the requested + // frequency and video PID, we can attach another receiver regardless of priority + ) + ) { + d = device[i]; + if (ReUse) + *ReUse = true; + break; + } + if (Priority > device[i]->Priority() // Priority is high enough to use this device + && (!d // we don't have a device yet, or... + || device[i]->Priority() < d->Priority() // ...this one has an even lower Priority + || (device[i]->Priority() == d->Priority() // ...same Priority... + && Provides[i] < Provides[d->CardIndex()] // ...but this one provides fewer Ca values + ) + ) + ) + d = device[i]; + } + } + /*XXX+ too complex with multiple recordings per device + if (!d && Ca > MAXDEVICES) { + // We didn't find one the easy way, so now we have to try harder: + int ShiftLevel = -1; + for (int i = 0; i < numDevices; i++) { + if (Provides[i]) { // this device is basicly able to do the job, but for some reason we didn't get it above + int sl = device[i]->CanShift(Ca, Priority); // asks this device to shift its job to another device + if (sl >= 0 && (ShiftLevel < 0 || sl < ShiftLevel)) { + d = device[i]; // found one that can be shifted with the fewest number of subsequent shifts + ShiftLevel = sl; + } + } + } + } + XXX*/ + return d; +} + +void cDevice::SetCaCaps(void) +{ + for (int d = 0; d < numDevices; d++) { + for (int i = 0; i < MAXCACAPS; i++) + device[d]->caCaps[i] = Setup.CaCaps[device[d]->CardIndex()][i]; + } +} + +bool cDevice::Probe(const char *FileName) +{ + if (access(FileName, F_OK) == 0) { + dsyslog("probing %s", FileName); + int f = open(FileName, O_RDONLY); + if (f >= 0) { + close(f); + return true; + } + else if (errno != ENODEV && errno != EINVAL) + LOG_ERROR_STR(FileName); + } + else if (errno != ENOENT) + LOG_ERROR_STR(FileName); + return false; +} + +bool cDevice::Initialize(void) +{ + numDevices = 0; + for (int i = 0; i < MAXDEVICES; i++) { + if (useDevice == 0 || (useDevice & (1 << i)) != 0) { + if (Probe(OstName(DEV_OST_FRONTEND, i))) + device[numDevices++] = new cDevice(i); + else + break; + } + } + primaryDevice = device[0]; + if (numDevices > 0) { + isyslog("found %d video device%s", numDevices, numDevices > 1 ? "s" : ""); + SetCaCaps(); + } + else + esyslog("ERROR: no video device found, giving up!"); + return numDevices > 0; +} + +void cDevice::Shutdown(void) +{ + for (int i = 0; i < numDevices; i++) { + delete device[i]; + device[i] = NULL; + } + primaryDevice = NULL; +} + +bool cDevice::GrabImage(const char *FileName, bool Jpeg, int Quality, int SizeX, int SizeY) +{ + int videoDev = OstOpen(DEV_VIDEO, CardIndex(), O_RDWR, true); + if (videoDev >= 0) { + int result = 0; + struct video_mbuf mbuf; + result |= ioctl(videoDev, VIDIOCGMBUF, &mbuf); + if (result == 0) { + int msize = mbuf.size; + unsigned char *mem = (unsigned char *)mmap(0, msize, PROT_READ | PROT_WRITE, MAP_SHARED, videoDev, 0); + if (mem && mem != (unsigned char *)-1) { + // set up the size and RGB + struct video_capability vc; + result |= ioctl(videoDev, VIDIOCGCAP, &vc); + struct video_mmap vm; + vm.frame = 0; + if ((SizeX > 0) && (SizeX <= vc.maxwidth) && + (SizeY > 0) && (SizeY <= vc.maxheight)) { + vm.width = SizeX; + vm.height = SizeY; + } + else { + vm.width = vc.maxwidth; + vm.height = vc.maxheight; + } + vm.format = VIDEO_PALETTE_RGB24; + result |= ioctl(videoDev, VIDIOCMCAPTURE, &vm); + result |= ioctl(videoDev, VIDIOCSYNC, &vm.frame); + // make RGB out of BGR: + int memsize = vm.width * vm.height; + unsigned char *mem1 = mem; + for (int i = 0; i < memsize; i++) { + unsigned char tmp = mem1[2]; + mem1[2] = mem1[0]; + mem1[0] = tmp; + mem1 += 3; + } + + if (Quality < 0) + Quality = 255; //XXX is this 'best'??? + + isyslog("grabbing to %s (%s %d %d %d)", FileName, Jpeg ? "JPEG" : "PNM", Quality, vm.width, vm.height); + FILE *f = fopen(FileName, "wb"); + if (f) { + if (Jpeg) { + // write JPEG file: + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_compress(&cinfo); + jpeg_stdio_dest(&cinfo, f); + cinfo.image_width = vm.width; + cinfo.image_height = vm.height; + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, Quality, true); + jpeg_start_compress(&cinfo, true); + + int rs = vm.width * 3; + JSAMPROW rp[vm.height]; + for (int k = 0; k < vm.height; k++) + rp[k] = &mem[rs * k]; + jpeg_write_scanlines(&cinfo, rp, vm.height); + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + } + else { + // write PNM file: + if (fprintf(f, "P6\n%d\n%d\n255\n", vm.width, vm.height) < 0 || + fwrite(mem, vm.width * vm.height * 3, 1, f) < 0) { + LOG_ERROR_STR(FileName); + result |= 1; + } + } + fclose(f); + } + else { + LOG_ERROR_STR(FileName); + result |= 1; + } + munmap(mem, msize); + } + else + result |= 1; + } + close(videoDev); + return result == 0; + } + return false; +} + +void cDevice::SetVideoFormat(videoFormat_t Format) +{ + if (HasDecoder()) + CHECK(ioctl(fd_video, VIDEO_SET_FORMAT, Format)); +} + +// ptVideo ptAudio ptTeletext ptDolby ptOther +dmxPesType_t PesTypes[] = { DMX_PES_VIDEO, DMX_PES_AUDIO, DMX_PES_TELETEXT, DMX_PES_OTHER, DMX_PES_OTHER }; + +//#define PRINTPIDS(s) { char b[500]; char *q = b; q += sprintf(q, "%d %s ", CardIndex(), s); for (int i = 0; i < MAXPIDHANDLES; i++) q += sprintf(q, " %s%4d %d", i == ptOther ? "* " : "", pidHandles[i].pid, pidHandles[i].used); dsyslog(b); } //XXX+ +#define PRINTPIDS(s) + +bool cDevice::AddPid(int Pid, ePidType PidType) +{ + if (Pid) { + int n = -1; + int a = -1; + for (int i = 0; i < MAXPIDHANDLES; i++) { + if (pidHandles[i].pid == Pid) + n = i; + else if (a < 0 && i >= ptOther && !pidHandles[i].used) + a = i; + } + dmxPesType_t PesType = PesTypes[ptOther]; + if (n >= 0) { + // The Pid is already in use + if (++pidHandles[n].used == 2 && n <= ptTeletext) { + // It's a special PID that has to be switched into "tap" mode + PRINTPIDS("A");//XXX+ + return SetPid(pidHandles[n].fd, PesTypes[n], Pid, DMX_OUT_TS_TAP); + } + PRINTPIDS("a");//XXX+ + return true; + } + else if (PidType < ptOther) { + // The Pid is not yet in use and it is a special one + n = PidType; + PesType = PesTypes[PidType]; + PRINTPIDS("B");//XXX+ + } + else if (a >= 0) { + // The Pid is not yet in use and we have a free slot + n = a; + } + else + esyslog("ERROR: no free slot for PID %d", Pid); + if (n >= 0) { + pidHandles[n].pid = Pid; + pidHandles[n].fd = OstOpen(DEV_OST_DEMUX, CardIndex(), O_RDWR | O_NONBLOCK, true); + pidHandles[n].used = 1; + PRINTPIDS("C");//XXX+ + return SetPid(pidHandles[n].fd, PesType, Pid, PidType <= ptTeletext ? DMX_OUT_DECODER : DMX_OUT_TS_TAP); + } + } + return true; +} + +bool cDevice::DelPid(int Pid) +{ + if (Pid) { + for (int i = 0; i < MAXPIDHANDLES; i++) { + if (pidHandles[i].pid == Pid) { + switch (--pidHandles[i].used) { + case 0: CHECK(ioctl(pidHandles[i].fd, DMX_STOP));//XXX+ is this necessary??? + close(pidHandles[i].fd); + pidHandles[i].fd = -1; + pidHandles[i].pid = 0; + break; + case 1: if (i <= ptTeletext) + SetPid(pidHandles[i].fd, PesTypes[i], Pid, DMX_OUT_DECODER); + break; + } + PRINTPIDS("D");//XXX+ + return pidHandles[i].used; + } + } + } + return false; +} + +bool cDevice::SetPid(int fd, dmxPesType_t PesType, int Pid, dmxOutput_t Output) +{ + if (Pid) { + CHECK(ioctl(fd, DMX_STOP)); + if (Pid != 0x1FFF) { + dmxPesFilterParams pesFilterParams; + pesFilterParams.pid = Pid; + pesFilterParams.input = DMX_IN_FRONTEND; + pesFilterParams.output = Output; + pesFilterParams.pesType = PesType; + pesFilterParams.flags = DMX_IMMEDIATE_START; + //XXX+ pesFilterParams.flags = DMX_CHECK_CRC;//XXX + if (ioctl(fd, DMX_SET_PES_FILTER, &pesFilterParams) < 0) { + LOG_ERROR; + return false; + } + //XXX+ CHECK(ioctl(fd, DMX_SET_BUFFER_SIZE, KILOBYTE(32)));//XXX + //XXX+ CHECK(ioctl(fd, DMX_START));//XXX + } + } + return true; +} + +eSetChannelResult cDevice::SetChannel(int ChannelNumber, int Frequency, char Polarization, int Diseqc, int Srate, int Vpid, int Apid, int Tpid, int Ca, int Pnr) +{ + //XXX+StopTransfer(); + //XXX+StopReplay(); + + cStatusMonitor::MsgChannelSwitch(this, 0); + + // Must set this anyway to avoid getting stuck when switching through + // channels with 'Up' and 'Down' keys: + currentChannel = ChannelNumber; + + // Avoid noise while switching: + + if (HasDecoder()) { + CHECK(ioctl(fd_audio, AUDIO_SET_MUTE, true)); + CHECK(ioctl(fd_video, VIDEO_SET_BLANK, true)); + CHECK(ioctl(fd_audio, AUDIO_CLEAR_BUFFER)); + CHECK(ioctl(fd_video, VIDEO_CLEAR_BUFFER)); + } + + // Stop setting system time: + + if (siProcessor) + siProcessor->SetCurrentTransponder(0); + + // If this card can't receive this channel, we must not actually switch + // the channel here, because that would irritate the driver when we + // start replaying in Transfer Mode immediately after switching the channel: + bool NeedsTransferMode = (IsPrimaryDevice() && !ProvidesCa(Ca)); + + if (!NeedsTransferMode) { + + // Turn off current PIDs: + + if (HasDecoder()) { + DelPid(pidHandles[ptVideo].pid); + DelPid(pidHandles[ptAudio].pid); + DelPid(pidHandles[ptTeletext].pid); + DelPid(pidHandles[ptDolby].pid); + } + + FrontendParameters Frontend; + + switch (frontendType) { + case FE_QPSK: { // DVB-S + + // Frequency offsets: + + unsigned int freq = Frequency; + int tone = SEC_TONE_OFF; + + if (freq < (unsigned int)Setup.LnbSLOF) { + freq -= Setup.LnbFrequLo; + tone = SEC_TONE_OFF; + } + else { + freq -= Setup.LnbFrequHi; + tone = SEC_TONE_ON; + } + + Frontend.Frequency = freq * 1000UL; + Frontend.Inversion = INVERSION_AUTO; + Frontend.u.qpsk.SymbolRate = Srate * 1000UL; + Frontend.u.qpsk.FEC_inner = FEC_AUTO; + + int volt = (Polarization == 'v' || Polarization == 'V') ? SEC_VOLTAGE_13 : SEC_VOLTAGE_18; + + // DiseqC: + + secCommand scmd; + scmd.type = 0; + scmd.u.diseqc.addr = 0x10; + scmd.u.diseqc.cmd = 0x38; + scmd.u.diseqc.numParams = 1; + scmd.u.diseqc.params[0] = 0xF0 | ((Diseqc * 4) & 0x0F) | (tone == SEC_TONE_ON ? 1 : 0) | (volt == SEC_VOLTAGE_18 ? 2 : 0); + + secCmdSequence scmds; + scmds.voltage = volt; + scmds.miniCommand = SEC_MINI_NONE; + scmds.continuousTone = tone; + scmds.numCommands = Setup.DiSEqC ? 1 : 0; + scmds.commands = &scmd; + + CHECK(ioctl(fd_sec, SEC_SEND_SEQUENCE, &scmds)); + } + break; + case FE_QAM: { // DVB-C + + // Frequency and symbol rate: + + Frontend.Frequency = Frequency * 1000000UL; + Frontend.Inversion = INVERSION_AUTO; + Frontend.u.qam.SymbolRate = Srate * 1000UL; + Frontend.u.qam.FEC_inner = FEC_AUTO; + Frontend.u.qam.QAM = QAM_64; + } + break; + case FE_OFDM: { // DVB-T + + // Frequency and OFDM paramaters: + + Frontend.Frequency = Frequency * 1000UL; + Frontend.Inversion = INVERSION_AUTO; + Frontend.u.ofdm.bandWidth=BANDWIDTH_8_MHZ; + Frontend.u.ofdm.HP_CodeRate=FEC_2_3; + Frontend.u.ofdm.LP_CodeRate=FEC_1_2; + Frontend.u.ofdm.Constellation=QAM_64; + Frontend.u.ofdm.TransmissionMode=TRANSMISSION_MODE_2K; + Frontend.u.ofdm.guardInterval=GUARD_INTERVAL_1_32; + Frontend.u.ofdm.HierarchyInformation=HIERARCHY_NONE; + } + break; + default: + esyslog("ERROR: attempt to set channel with unknown DVB frontend type"); + return scrFailed; + } + + // Tuning: + + CHECK(ioctl(fd_frontend, FE_SET_FRONTEND, &Frontend)); + + // Wait for channel sync: + + if (cFile::FileReady(fd_frontend, 5000)) { + FrontendEvent event; + int res = ioctl(fd_frontend, FE_GET_EVENT, &event); + if (res >= 0) { + if (event.type != FE_COMPLETION_EV) { + esyslog("ERROR: channel %d not sync'ed on DVB card %d!", ChannelNumber, CardIndex() + 1); + if (IsPrimaryDevice()) + cThread::RaisePanic(); + return scrFailed; + } + } + else + esyslog("ERROR %d in frontend get event (channel %d, card %d)", res, ChannelNumber, CardIndex() + 1); + } + else + esyslog("ERROR: timeout while tuning"); + + frequency = Frequency; + + // PID settings: + + if (HasDecoder()) { + if (!(AddPid(Vpid, ptVideo) && AddPid(Apid, ptAudio))) {//XXX+ dolby Dpid1!!! (if audio plugins are attached) + esyslog("ERROR: failed to set PIDs for channel %d", ChannelNumber); + return scrFailed; + } + if (IsPrimaryDevice()) + AddPid(Tpid, ptTeletext); + CHECK(ioctl(fd_audio, AUDIO_SET_AV_SYNC, true)); + } + } + + if (IsPrimaryDevice() && siProcessor) + siProcessor->SetCurrentServiceID(Pnr); + + eSetChannelResult Result = scrOk; + + // If this DVB card can't receive this channel, let's see if we can + // use the card that actually can receive it and transfer data from there: + + if (NeedsTransferMode) { + cDevice *CaDevice = GetDevice(Ca, 0); + if (CaDevice && !CaDevice->Receiving()) { + if ((Result = CaDevice->SetChannel(ChannelNumber, Frequency, Polarization, Diseqc, Srate, Vpid, Apid, Tpid, Ca, Pnr)) == scrOk) { + //XXX+SetModeReplay(); + //XXX+transferringFromDevice = CaDevice->StartTransfer(fd_video); + } + } + else + Result = scrNoTransfer; + } + + if (HasDecoder()) { + CHECK(ioctl(fd_audio, AUDIO_SET_MUTE, false)); + CHECK(ioctl(fd_video, VIDEO_SET_BLANK, false)); + } + + // Start setting system time: + + if (Result == scrOk && siProcessor) + siProcessor->SetCurrentTransponder(Frequency); + + cStatusMonitor::MsgChannelSwitch(this, ChannelNumber); + + return Result; +} + +bool cDevice::ToggleMute(void) +{ + int OldVolume = volume; + mute = !mute; + SetVolume(0, mute); + volume = OldVolume; + return mute; +} + +void cDevice::SetVolume(int Volume, bool Absolute) +{ + if (HasDecoder()) { + volume = min(max(Absolute ? Volume : volume + Volume, 0), MAXVOLUME); + audioMixer_t am; + am.volume_left = am.volume_right = volume; + CHECK(ioctl(fd_audio, AUDIO_SET_MIXER, &am)); + cStatusMonitor::MsgSetVolume(volume, Absolute); + if (volume > 0) + mute = false; + } +} + +void cDevice::TrickSpeed(int Speed) +{ + if (fd_video >= 0) + CHECK(ioctl(fd_video, VIDEO_SLOWMOTION, Speed)); +} + +void cDevice::Clear(void) +{ + if (fd_video >= 0) + CHECK(ioctl(fd_video, VIDEO_CLEAR_BUFFER)); + if (fd_audio >= 0) + CHECK(ioctl(fd_audio, AUDIO_CLEAR_BUFFER)); +} + +void cDevice::Play(void) +{ + if (fd_audio >= 0) + CHECK(ioctl(fd_audio, AUDIO_SET_AV_SYNC, true)); + if (fd_video >= 0) + CHECK(ioctl(fd_video, VIDEO_CONTINUE)); +} + +void cDevice::Freeze(void) +{ + if (fd_audio >= 0) + CHECK(ioctl(fd_audio, AUDIO_SET_AV_SYNC, false)); + if (fd_video >= 0) + CHECK(ioctl(fd_video, VIDEO_FREEZE)); +} + +void cDevice::Mute(void) +{ + if (fd_audio >= 0) { + CHECK(ioctl(fd_audio, AUDIO_SET_AV_SYNC, false)); + CHECK(ioctl(fd_audio, AUDIO_SET_MUTE, true)); + } +} + +void cDevice::StillPicture(const uchar *Data, int Length) +{ + Mute(); +/* Using the VIDEO_STILLPICTURE ioctl call would be the + correct way to display a still frame, but unfortunately this + doesn't work with frames from VDR. So let's do pretty much the + same here as in DVB/driver/dvb.c's play_iframe() - I have absolutely + no idea why it works this way, but doesn't work with VIDEO_STILLPICTURE. + If anybody ever finds out what could be changed so that VIDEO_STILLPICTURE + could be used, please let me know! + kls 2002-03-23 +*/ +//#define VIDEO_STILLPICTURE_WORKS_WITH_VDR_FRAMES +#ifdef VIDEO_STILLPICTURE_WORKS_WITH_VDR_FRAMES + videoDisplayStillPicture sp = { (char *)Data, Length }; + CHECK(ioctl(fd_video, VIDEO_STILLPICTURE, &sp)); +#else +#define MIN_IFRAME 400000 + for (int i = MIN_IFRAME / Length + 1; i > 0; i--) { + safe_write(fd_video, Data, Length); + usleep(1); // allows the buffer to be displayed in case the progress display is active + } +#endif +} + +bool cDevice::Replaying(void) +{ + /*XXX+ + if (replayBuffer && !replayBuffer->Active()) + StopReplay(); + return replayBuffer != NULL; + XXX*/ + return player != NULL; +} + +bool cDevice::Attach(cPlayer *Player) +{ + if (Receiving()) { + esyslog("ERROR: attempt to attach a cPlayer while receiving on device %d - ignored", CardIndex() + 1); + return false; + } + if (HasDecoder()) { + if (player) + Detach(player); + + if (siProcessor) + siProcessor->SetStatus(false); + CHECK(ioctl(fd_video, VIDEO_SET_BLANK, true)); + CHECK(ioctl(fd_audio, AUDIO_SELECT_SOURCE, AUDIO_SOURCE_MEMORY)); + CHECK(ioctl(fd_audio, AUDIO_SET_AV_SYNC, true)); + CHECK(ioctl(fd_audio, AUDIO_PLAY)); + CHECK(ioctl(fd_video, VIDEO_SELECT_SOURCE, VIDEO_SOURCE_MEMORY)); + CHECK(ioctl(fd_video, VIDEO_PLAY)); + + player = Player; + player->device = this; + player->deviceFileHandle = fd_video; + player->Activate(true); + return true; + } + return false; +} + +void cDevice::Detach(cPlayer *Player) +{ + if (Player && player == Player) { + player->Activate(false); + player->deviceFileHandle = -1; + player->device = NULL; + player = NULL; + + CHECK(ioctl(fd_video, VIDEO_STOP, true)); + CHECK(ioctl(fd_audio, AUDIO_STOP, true)); + CHECK(ioctl(fd_video, VIDEO_CLEAR_BUFFER)); + CHECK(ioctl(fd_audio, AUDIO_CLEAR_BUFFER)); + CHECK(ioctl(fd_video, VIDEO_SELECT_SOURCE, VIDEO_SOURCE_DEMUX)); + CHECK(ioctl(fd_audio, AUDIO_SELECT_SOURCE, AUDIO_SOURCE_DEMUX)); + CHECK(ioctl(fd_audio, AUDIO_SET_AV_SYNC, true)); + CHECK(ioctl(fd_audio, AUDIO_SET_MUTE, false)); + if (siProcessor) + siProcessor->SetStatus(true); + } +} + +void cDevice::StopReplay(void) +{ + if (player) { + Detach(player); + /*XXX+ + if (IsPrimaryDevice()) { + // let's explicitly switch the channel back in case it was in Transfer Mode: + cChannel *Channel = Channels.GetByNumber(currentChannel); + if (Channel) { + Channel->Switch(this, false); + usleep(100000); // allow driver to sync in case a new replay will start immediately + } + } + XXX*/ + } +} + +int cDevice::PlayVideo(const uchar *Data, int Length) +{ + if (fd_video >= 0) + return write(fd_video, Data, Length); + return -1; +} + +int cDevice::PlayAudio(const uchar *Data, int Length) +{ + //XXX+ + return -1; +} + +int cDevice::Priority(void) +{ + if (IsPrimaryDevice() && !Receiving()) + return Setup.PrimaryLimit - 1; + int priority = DEFAULTPRIORITY; + for (int i = 0; i < MAXRECEIVERS; i++) { + if (receiver[i]) + priority = max(receiver[i]->priority, priority); + } + return priority; +} + +int cDevice::CanShift(int Ca, int Priority, int UsedCards) +{ + return -1;//XXX+ too complex with multiple recordings per device + // Test whether a receiving on this DVB device can be shifted to another one + // in order to perform a new receiving with the given Ca and Priority on this device: + int ShiftLevel = -1; // default means this device can't be shifted + if (UsedCards & (1 << CardIndex()) != 0) + return ShiftLevel; // otherwise we would get into a loop + if (Receiving()) { + if (ProvidesCa(Ca) // this device provides the requested Ca + && (Ca != this->Ca() // the requested Ca is different from the one currently used... + || Priority > this->Priority())) { // ...or the request comes from a higher priority + cDevice *d = NULL; + int Provides[MAXDEVICES]; + UsedCards |= (1 << CardIndex()); + for (int i = 0; i < numDevices; i++) { + if ((Provides[i] = device[i]->ProvidesCa(this->Ca())) != 0) { // this device is basicly able to do the job + if (device[i] != this) { // it is not _this_ device + int sl = device[i]->CanShift(this->Ca(), Priority, UsedCards); // this is the original Priority! + if (sl >= 0 && (ShiftLevel < 0 || sl < ShiftLevel)) { + d = device[i]; + ShiftLevel = sl; + } + } + } + } + if (ShiftLevel >= 0) + ShiftLevel++; // adds the device's own shift + } + } + else if (Priority > this->Priority()) + ShiftLevel = 0; // no shifting necessary, this device can do the job + return ShiftLevel; +} + +int cDevice::ProvidesCa(int Ca) +{ + if (Ca == CardIndex() + 1) + return 1; // exactly _this_ card was requested + if (Ca && Ca <= MAXDEVICES) + return 0; // a specific card was requested, but not _this_ one + int result = Ca ? 0 : 1; // by default every card can provide FTA + int others = Ca ? 1 : 0; + for (int i = 0; i < MAXCACAPS; i++) { + if (caCaps[i]) { + if (caCaps[i] == Ca) + result = 1; + else + others++; + } + } + return result ? result + others : 0; +} + +bool cDevice::Receiving(void) +{ + for (int i = 0; i < MAXRECEIVERS; i++) { + if (receiver[i]) + return true; + } + return false; +} + +void cDevice::Action(void) +{ + dsyslog("receiver thread started on device %d (pid=%d)", CardIndex() + 1, getpid()); + + int fd_dvr = open(dvrFileName, O_RDONLY | O_NONBLOCK); + if (fd_dvr >= 0) { + pollfd pfd; + pfd.fd = fd_dvr; + pfd.events = pfd.revents = POLLIN; + uchar b[TS_SIZE]; + time_t t = time(NULL); + active = true; + for (; active;) { + + // Read data from the DVR device: + + if (pfd.revents & POLLIN != 0) { + int r = read(fd_dvr, b, sizeof(b)); + if (r == TS_SIZE) { + if (*b == TS_SYNC_BYTE) { + // We're locked on to a TS packet + int Pid = (((uint16_t)b[1] & PID_MASK_HI) << 8) | b[2]; + // Distribute the packet to all attached receivers: + Lock(); + for (int i = 0; i < MAXRECEIVERS; i++) { + if (receiver[i] && receiver[i]->WantsPid(Pid)) + receiver[i]->Receive(b, TS_SIZE); + } + Unlock(); + } + t = time(NULL); + } + else if (r > 0) + esyslog("ERROR: got incomplete TS packet (%d bytes)", r);//XXX+ TODO do we have to read the rest??? + else if (r < 0) { + if (FATALERRNO) { + if (errno == EBUFFEROVERFLOW) // this error code is not defined in the library + esyslog("ERROR: DVB driver buffer overflow on device %d", CardIndex() + 1); + else { + LOG_ERROR; + break; + } + } + } + } + + // Wait for more data to become available: + + poll(&pfd, 1, 100); + + //XXX+ put this into the recorder??? or give the receiver a flag whether it wants this? + if (time(NULL) - t > MAXBROKENTIMEOUT) { + esyslog("ERROR: video data stream broken on device %d", CardIndex() + 1); + cThread::EmergencyExit(true); + t = time(NULL); + } + } + close(fd_dvr); + } + else + LOG_ERROR_STR(dvrFileName); + + dsyslog("receiver thread ended on device %d (pid=%d)", CardIndex() + 1, getpid()); +} + +bool cDevice::Attach(cReceiver *Receiver) +{ + //XXX+ check for same transponder??? + if (!Receiver) + return false; + if (Receiver->device == this) + return true; + StopReplay(); + for (int i = 0; i < MAXRECEIVERS; i++) { + if (!receiver[i]) { + //siProcessor->SetStatus(false);//XXX+ + for (int n = 0; n < MAXRECEIVEPIDS; n++) { + if (Receiver->pids[n]) + AddPid(Receiver->pids[n]);//XXX+ retval! + else + break; + } + Receiver->Activate(true); + Lock(); + Receiver->device = this; + receiver[i] = Receiver; + Unlock(); + Start(); + return true; + } + } + esyslog("ERROR: no free receiver slot!"); + return false; +} + +void cDevice::Detach(cReceiver *Receiver) +{ + if (!Receiver || Receiver->device != this) + return; + bool receiversLeft = false; + for (int i = 0; i < MAXRECEIVERS; i++) { + if (receiver[i] == Receiver) { + Receiver->Activate(false); + Lock(); + receiver[i] = NULL; + Receiver->device = NULL; + Unlock(); + for (int n = 0; n < MAXRECEIVEPIDS; n++) { + if (Receiver->pids[n]) + DelPid(Receiver->pids[n]); + else + break; + } + } + else if (receiver[i]) + receiversLeft = true; + } + if (!receiversLeft) { + active = false; + Cancel(3); + } +} diff --git a/device.h b/device.h new file mode 100644 index 00000000..e598430a --- /dev/null +++ b/device.h @@ -0,0 +1,199 @@ +/* + * device.h: The basic device interface + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * $Id: device.h 1.1 2002/06/10 16:30:00 kls Exp $ + */ + +#ifndef __DEVICE_H +#define __DEVICE_H + +#include // FIXME: this is apparently necessary for the ost/... header files + // FIXME: shouldn't every header file include ALL the other header + // FIXME: files it depends on? The sequence in which header files + // FIXME: are included here should not matter - and it should NOT + // FIXME: be necessary to include here! +#include +#include +#include +#include +#include "eit.h" +#include "thread.h" + +enum eSetChannelResult { scrOk, scrNoTransfer, scrFailed }; + +#define MAXDEVICES 4 // the maximum number of devices in the system +#define MAXCACAPS 16 // the maximum number of different CA values per DVB device +#define MAXPIDHANDLES 16 // the maximum number of different PIDs per DVB device +#define MAXRECEIVERS 16 // the maximum number of receivers per DVB device +#define MAXVOLUME 255 +#define VOLUMEDELTA 5 // used to increase/decrease the volume + +class cPlayer; +class cReceiver; + +class cDevice : cThread { + friend class cOsd;//XXX +private: + static int numDevices; + static int useDevice; + static cDevice *device[MAXDEVICES]; + static cDevice *primaryDevice; +public: + static int NumDevices(void) { return numDevices; } + // Returns the total number of DVB devices. + static void SetUseDevice(int n); + // Sets the 'useDevice' flag of the given DVB device. + // If this function is not called before Initialize(), all DVB devices + // will be used. + static bool SetPrimaryDevice(int n); + // Sets the primary DVB device to 'n' (which must be in the range + // 1...numDevices) and returns true if this was possible. + static cDevice *PrimaryDevice(void) { return primaryDevice; } + // Returns the primary DVB device. + static cDevice *GetDevice(int Ca, int Priority, int Frequency = 0, int Vpid = 0, bool *ReUse = NULL); + // Selects a free DVB device, avoiding the primaryDevice if possible. + // If Ca is not 0, the device with the given number will be returned + // in case Ca is <= MAXDEVICES, or the device that provides the given + // value in its caCaps. + // If there is a device that is already tuned to the given Frequency, + // and that device is able to receive multiple channels ("budget" cards), + // that device will be returned. Else if a ("full featured") device is + // tuned to Frequency and Vpid, that one will be returned. + // If all DVB devices are currently receiving, the one receiving the + // lowest priority timer (if any) that is lower than the given Priority + // will be returned. + // If ReUse is given, the caller will be informed whether the device can be re-used + // for a new recording. If ReUse returns 'true', the caller must NOT switch the channel + // (the device is already properly tuned). Otherwise the caller MUST switch the channel. + static void SetCaCaps(void); + // Sets the CaCaps of all DVB devices according to the Setup data. + static bool Probe(const char *FileName); + // Probes for existing DVB devices. + static bool Initialize(void); + // Initializes the DVB devices. + // Must be called before accessing any DVB functions. + static void Shutdown(void); + // Closes down all DVB devices. + // Must be called at the end of the program. +private: + int cardIndex; + int caCaps[MAXCACAPS]; + FrontendType frontendType; + char *dvrFileName; + bool active; + int fd_osd, fd_frontend, fd_sec, fd_audio, fd_video; + int OsdDeviceHandle(void) { return fd_osd; } +public: + cDevice(int n); + virtual ~cDevice(); + bool IsPrimaryDevice(void) { return this == primaryDevice; } + int CardIndex(void) const { return cardIndex; } + // Returns the card index of this device (0 ... MAXDEVICES - 1). + int ProvidesCa(int Ca); + // Checks whether this DVB device provides the given value in its + // caCaps. Returns 0 if the value is not provided, 1 if only this + // value is provided, and > 1 if this and other values are provided. + // If the given value is equal to the number of this DVB device, + // 1 is returned. If it is 0 (FTA), 1 plus the number of other values + // in caCaps is returned. + bool HasDecoder(void) const { return fd_video >= 0 && fd_audio >= 0; } + +// Channel facilities + +private: + int currentChannel; + int frequency; +public: + eSetChannelResult SetChannel(int ChannelNumber, int Frequency, char Polarization, int Diseqc, int Srate, int Vpid, int Apid, int Tpid, int Ca, int Pnr); + static int CurrentChannel(void) { return primaryDevice ? primaryDevice->currentChannel : 0; } + int Channel(void) { return currentChannel; } + +// PID handle facilities + +private: + enum ePidType { ptVideo, ptAudio, ptTeletext, ptDolby, ptOther }; + class cPidHandle { + public: + int pid; + int fd; + int used; + cPidHandle(void) { pid = used = 0; fd = -1; } + }; + cPidHandle pidHandles[MAXPIDHANDLES]; + bool AddPid(int Pid, ePidType PidType = ptOther); + bool DelPid(int Pid); + bool SetPid(int fd, dmxPesType_t PesType, int Pid, dmxOutput_t Output); + virtual void Action(void); + +// Image Grab facilities + +public: + bool GrabImage(const char *FileName, bool Jpeg = true, int Quality = -1, int SizeX = -1, int SizeY = -1); + +// Video format facilities + +public: + virtual void SetVideoFormat(videoFormat_t Format); + +// Volume facilities + +private: + bool mute; + int volume; +public: + bool IsMute(void) { return mute; } + bool ToggleMute(void); + // Turns the volume off or on and returns the new mute state. + void SetVolume(int Volume, bool Absolute = false); + // Sets the volume to the given value, either absolutely or relative to + // the current volume. + static int CurrentVolume(void) { return primaryDevice ? primaryDevice->volume : 0; }//XXX??? + + // EIT facilities + +private: + cSIProcessor *siProcessor; + +// Player facilities + +private: + cPlayer *player; +public: + void TrickSpeed(int Speed); + void Clear(void); + void Play(void); + void Freeze(void); + void Mute(void); + void StillPicture(const uchar *Data, int Length); + bool Replaying(void); + // Returns true if we are currently replaying. + void StopReplay(void); + // Stops the current replay session (if any). + bool Attach(cPlayer *Player); + void Detach(cPlayer *Player); + virtual int PlayVideo(const uchar *Data, int Length); + virtual int PlayAudio(const uchar *Data, int Length); + +// Receiver facilities + +private: + cReceiver *receiver[MAXRECEIVERS]; + int ca; + int Priority(void); + // Returns the priority of the current receiving session (0..MAXPRIORITY), + // or -1 if no receiver is currently active. The primary DVB device will + // always return at least Setup.PrimaryLimit-1. + int CanShift(int Ca, int Priority, int UsedCards = 0); +public: + int Ca(void) { return ca; } + // Returns the ca of the current receiving session. + bool Receiving(void); + // Returns true if we are currently receiving. + bool Attach(cReceiver *Receiver); + void Detach(cReceiver *Receiver); + }; + +#endif //__DEVICE_H diff --git a/dvbplayer.c b/dvbplayer.c new file mode 100644 index 00000000..10fdf4b5 --- /dev/null +++ b/dvbplayer.c @@ -0,0 +1,733 @@ +/* + * dvbplayer.c: The DVB player + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * $Id: dvbplayer.c 1.1 2002/06/16 10:59:45 kls Exp $ + */ + +#include "dvbplayer.h" +#include +#include "recording.h" +#include "ringbuffer.h" +#include "thread.h" + +// --- ReadFrame ------------------------------------------------------------- + +int ReadFrame(int f, uchar *b, int Length, int Max) +{ + if (Length == -1) + Length = Max; // this means we read up to EOF (see cIndex) + else if (Length > Max) { + esyslog("ERROR: frame larger than buffer (%d > %d)", Length, Max); + Length = Max; + } + int r = safe_read(f, b, Length); + if (r < 0) + LOG_ERROR; + return r; +} + +// --- cBackTrace ---------------------------------------------------------- + +#define AVG_FRAME_SIZE 15000 // an assumption about the average frame size +#define DVB_BUF_SIZE (256 * 1024) // an assumption about the dvb firmware buffer size +#define BACKTRACE_ENTRIES (DVB_BUF_SIZE / AVG_FRAME_SIZE + 20) // how many entries are needed to backtrace buffer contents + +class cBackTrace { +private: + int index[BACKTRACE_ENTRIES]; + int length[BACKTRACE_ENTRIES]; + int pos, num; +public: + cBackTrace(void); + void Clear(void); + void Add(int Index, int Length); + int Get(bool Forward); + }; + +cBackTrace::cBackTrace(void) +{ + Clear(); +} + +void cBackTrace::Clear(void) +{ + pos = num = 0; +} + +void cBackTrace::Add(int Index, int Length) +{ + index[pos] = Index; + length[pos] = Length; + if (++pos >= BACKTRACE_ENTRIES) + pos = 0; + if (num < BACKTRACE_ENTRIES) + num++; +} + +int cBackTrace::Get(bool Forward) +{ + int p = pos; + int n = num; + int l = DVB_BUF_SIZE + (Forward ? 0 : 256 * 1024); //XXX (256 * 1024) == DVB_BUF_SIZE ??? + int i = -1; + + while (n && l > 0) { + if (--p < 0) + p = BACKTRACE_ENTRIES - 1; + i = index[p] - 1; + l -= length[p]; + n--; + } + return i; +} + +// --- cDvbPlayer ------------------------------------------------------------ + +//XXX+ also used in recorder.c - find a better place??? +// The size of the array used to buffer video data: +// (must be larger than MINVIDEODATA - see remux.h) +#define VIDEOBUFSIZE MEGABYTE(1) + +// The maximum size of a single frame: +#define MAXFRAMESIZE KILOBYTE(192) + +// The number of frames to back up when resuming an interrupted replay session: +#define RESUMEBACKUP (10 * FRAMESPERSEC) + +class cDvbPlayer : public cPlayer, cThread { +private: + enum ePlayModes { pmPlay, pmPause, pmSlow, pmFast, pmStill }; + enum ePlayDirs { pdForward, pdBackward }; + static int Speeds[]; + cRingBufferFrame *ringBuffer; + cBackTrace *backTrace; + cFileName *fileName; + cIndexFile *index; + int replayFile; + bool eof; + bool active; + ePlayModes playMode; + ePlayDirs playDir; + int trickSpeed; + int readIndex, writeIndex; + cFrame *readFrame; + const cFrame *playFrame; + void TrickSpeed(int Increment); + void Empty(void); + void StripAudioPackets(uchar *b, int Length, uchar Except = 0x00); + bool NextFile(uchar FileNumber = 0, int FileOffset = -1); + int Resume(void); + bool Save(void); +protected: + virtual void Activate(bool On); + virtual void Action(void); +public: + cDvbPlayer(const char *FileName); + virtual ~cDvbPlayer(); + bool Active(void) { return active; } + void Pause(void); + void Play(void); + void Forward(void); + void Backward(void); + int SkipFrames(int Frames); + void SkipSeconds(int Seconds); + void Goto(int Position, bool Still = false); + void GetIndex(int &Current, int &Total, bool SnapToIFrame = false); + bool GetReplayMode(bool &Play, bool &Forward, int &Speed); + }; + +#define MAX_VIDEO_SLOWMOTION 63 // max. arg to pass to VIDEO_SLOWMOTION // TODO is this value correct? +#define NORMAL_SPEED 4 // the index of the '1' entry in the following array +#define MAX_SPEEDS 3 // the offset of the maximum speed from normal speed in either direction +#define SPEED_MULT 12 // the speed multiplier +int cDvbPlayer::Speeds[] = { 0, -2, -4, -8, 1, 2, 4, 12, 0 }; + +cDvbPlayer::cDvbPlayer(const char *FileName) +{ + ringBuffer = NULL; + backTrace = NULL; + index = NULL; + eof = false; + active = false; + playMode = pmPlay; + playDir = pdForward; + trickSpeed = NORMAL_SPEED; + readIndex = writeIndex = -1; + readFrame = NULL; + playFrame = NULL; + isyslog("replay %s", FileName); + fileName = new cFileName(FileName, false); + replayFile = fileName->Open(); + if (replayFile < 0) + return; + ringBuffer = new cRingBufferFrame(VIDEOBUFSIZE); + // Create the index file: + index = new cIndexFile(FileName, false); + if (!index) + esyslog("ERROR: can't allocate index"); + else if (!index->Ok()) { + delete index; + index = NULL; + } + backTrace = new cBackTrace; +} + +cDvbPlayer::~cDvbPlayer() +{ + Detach(); + Save(); + delete index; + delete fileName; + delete backTrace; + delete ringBuffer; +} + +void cDvbPlayer::TrickSpeed(int Increment) +{ + int nts = trickSpeed + Increment; + if (Speeds[nts] == 1) { + trickSpeed = nts; + if (playMode == pmFast) + Play(); + else + Pause(); + } + else if (Speeds[nts]) { + trickSpeed = nts; + int Mult = (playMode == pmSlow && playDir == pdForward) ? 1 : SPEED_MULT; + int sp = (Speeds[nts] > 0) ? Mult / Speeds[nts] : -Speeds[nts] * Mult; + if (sp > MAX_VIDEO_SLOWMOTION) + sp = MAX_VIDEO_SLOWMOTION; + DeviceTrickSpeed(sp); + } +} + +void cDvbPlayer::Empty(void) +{ + Lock(); + if ((readIndex = backTrace->Get(playDir == pdForward)) < 0) + readIndex = writeIndex; + readFrame = NULL; + playFrame = NULL; + ringBuffer->Clear(); + backTrace->Clear(); + DeviceClear(); + Unlock(); +} + +void cDvbPlayer::StripAudioPackets(uchar *b, int Length, uchar Except) +{ + if (index) { + for (int i = 0; i < Length - 6; i++) { + if (b[i] == 0x00 && b[i + 1] == 0x00 && b[i + 2] == 0x01) { + uchar c = b[i + 3]; + int l = b[i + 4] * 256 + b[i + 5] + 6; + switch (c) { + case 0xBD: // dolby + if (Except) + ;//XXX+ PlayExternalDolby(&b[i], Length - i); + // continue with deleting the data - otherwise it disturbs DVB replay + case 0xC0 ... 0xC1: // audio + if (c == 0xC1) + ;//XXX+ canToggleAudioTrack = true; + if (!Except || c != Except) { + int n = l; + for (int j = i; j < Length && n--; j++) + b[j] = 0x00; + } + break; + case 0xE0 ... 0xEF: // video + break; + default: + //esyslog("ERROR: unexpected packet id %02X", c); + l = 0; + } + if (l) + i += l - 1; // the loop increments, too! + } + /*XXX + else + esyslog("ERROR: broken packet header"); + XXX*/ + } + } +} + +bool cDvbPlayer::NextFile(uchar FileNumber, int FileOffset) +{ + if (FileNumber > 0) + replayFile = fileName->SetOffset(FileNumber, FileOffset); + else if (replayFile >= 0 && eof) + replayFile = fileName->NextFile(); + eof = false; + return replayFile >= 0; +} + +int cDvbPlayer::Resume(void) +{ + if (index) { + int Index = index->GetResume(); + if (Index >= 0) { + uchar FileNumber; + int FileOffset; + if (index->Get(Index, &FileNumber, &FileOffset) && NextFile(FileNumber, FileOffset)) + return Index; + } + } + return -1; +} + +bool cDvbPlayer::Save(void) +{ + if (index) { + int Index = writeIndex; + if (Index >= 0) { + Index -= RESUMEBACKUP; + if (Index > 0) + Index = index->GetNextIFrame(Index, false); + else + Index = 0; + if (Index >= 0) + return index->StoreResume(Index); + } + } + return false; +} + +void cDvbPlayer::Activate(bool On) +{ + if (On) { + if (replayFile >= 0) + Start(); + } + else if (active) { + active = false; + Cancel(3); + } +} + +void cDvbPlayer::Action(void) +{ + active = true; + dsyslog("dvbplayer thread started (pid=%d)", getpid()); + + uchar b[MAXFRAMESIZE]; + const uchar *p = NULL; + int pc = 0; + + pollfd pfd[2]; + pfd[0].fd = DeviceFileHandle(); + pfd[0].events = pfd[0].revents = POLLOUT; + pfd[1].fd = replayFile; + pfd[1].events = pfd[1].revents = POLLIN; + + readIndex = Resume(); + if (readIndex >= 0) + isyslog("resuming replay at index %d (%s)", readIndex, IndexToHMSF(readIndex, true)); + + while (active && NextFile()) { + { + LOCK_THREAD; + + // Read the next frame from the file: + + if (!readFrame && (pfd[1].revents & POLLIN)) { + if (playMode != pmStill) { + int r = 0; + if (playMode == pmFast || (playMode == pmSlow && playDir == pdBackward)) { + uchar FileNumber; + int FileOffset, Length; + int Index = index->GetNextIFrame(readIndex, playDir == pdForward, &FileNumber, &FileOffset, &Length, true); + if (Index >= 0) { + if (!NextFile(FileNumber, FileOffset)) + break; + } + else { + // can't call Play() here, because those functions may only be + // called from the foreground thread - and we also don't need + // to empty the buffer here + DevicePlay(); + playMode = pmPlay; + playDir = pdForward; + continue; + } + readIndex = Index; + r = ReadFrame(replayFile, b, Length, sizeof(b)); + // must call StripAudioPackets() here because the buffer is not emptied + // when falling back from "fast forward" to "play" (see above) + StripAudioPackets(b, r); + } + else if (index) { + uchar FileNumber; + int FileOffset, Length; + readIndex++; + if (!(index->Get(readIndex, &FileNumber, &FileOffset, NULL, &Length) && NextFile(FileNumber, FileOffset))) + break; + r = ReadFrame(replayFile, b, Length, sizeof(b)); + } + else // allows replay even if the index file is missing + r = read(replayFile, b, sizeof(b)); + if (r > 0) + readFrame = new cFrame(b, r, ftUnknown, readIndex); + else if (r == 0) + eof = true; + else if (r < 0 && FATALERRNO) { + LOG_ERROR; + break; + } + } + else//XXX + usleep(1); // this keeps the CPU load low + } + + // Store the frame in the buffer: + + if (readFrame) { + if (ringBuffer->Put(readFrame)) + readFrame = NULL; + } + + // Get the next frame from the buffer: + + if (!playFrame) { + playFrame = ringBuffer->Get(); + p = NULL; + pc = 0; + } + + // Play the frame: + + if (playFrame && (pfd[0].revents & POLLOUT)) { + if (!p) { + p = playFrame->Data(); + pc = playFrame->Count(); + } + if (p) { + int w = PlayVideo(p, pc); + if (w > 0) { + p += w; + pc -= w; + } + else if (w < 0 && FATALERRNO) { + LOG_ERROR; + break; + } + } + if (pc == 0) { + writeIndex = playFrame->Index(); + backTrace->Add(playFrame->Index(), playFrame->Count()); + ringBuffer->Drop(playFrame); + playFrame = NULL; + p = 0; + } + } + } + + // Wait for input or output to become ready: + + if (poll(pfd, readFrame ? 1 : 2, 10) < 0 && FATALERRNO) { + LOG_ERROR; + break; + } + } + + dsyslog("dvbplayer thread ended (pid=%d)", getpid()); + active = false; +} + +void cDvbPlayer::Pause(void) +{ + if (playMode == pmPause || playMode == pmStill) + Play(); + else { + LOCK_THREAD; + if (playMode == pmFast || (playMode == pmSlow && playDir == pdBackward)) + Empty(); + DeviceFreeze(); + playMode = pmPause; + } +} + +void cDvbPlayer::Play(void) +{ + if (playMode != pmPlay) { + LOCK_THREAD; + if (playMode == pmStill || playMode == pmFast || (playMode == pmSlow && playDir == pdBackward)) + Empty(); + DevicePlay(); + playMode = pmPlay; + playDir = pdForward; + } +} + +void cDvbPlayer::Forward(void) +{ + if (index) { + switch (playMode) { + case pmFast: + if (Setup.MultiSpeedMode) { + TrickSpeed(playDir == pdForward ? 1 : -1); + break; + } + else if (playDir == pdForward) { + Play(); + break; + } + // run into pmPlay + case pmPlay: { + LOCK_THREAD; + Empty(); + DeviceMute(); + playMode = pmFast; + playDir = pdForward; + trickSpeed = NORMAL_SPEED; + TrickSpeed(Setup.MultiSpeedMode ? 1 : MAX_SPEEDS); + } + break; + case pmSlow: + if (Setup.MultiSpeedMode) { + TrickSpeed(playDir == pdForward ? -1 : 1); + break; + } + else if (playDir == pdForward) { + Pause(); + break; + } + // run into pmPause + case pmStill: + case pmPause: + DeviceMute(); + playMode = pmSlow; + playDir = pdForward; + trickSpeed = NORMAL_SPEED; + TrickSpeed(Setup.MultiSpeedMode ? -1 : -MAX_SPEEDS); + break; + } + } +} + +void cDvbPlayer::Backward(void) +{ + if (index) { + switch (playMode) { + case pmFast: + if (Setup.MultiSpeedMode) { + TrickSpeed(playDir == pdBackward ? 1 : -1); + break; + } + else if (playDir == pdBackward) { + Play(); + break; + } + // run into pmPlay + case pmPlay: { + LOCK_THREAD; + Empty(); + DeviceMute(); + playMode = pmFast; + playDir = pdBackward; + trickSpeed = NORMAL_SPEED; + TrickSpeed(Setup.MultiSpeedMode ? 1 : MAX_SPEEDS); + } + break; + case pmSlow: + if (Setup.MultiSpeedMode) { + TrickSpeed(playDir == pdBackward ? -1 : 1); + break; + } + else if (playDir == pdBackward) { + Pause(); + break; + } + // run into pmPause + case pmStill: + case pmPause: { + LOCK_THREAD; + Empty(); + DeviceMute(); + playMode = pmSlow; + playDir = pdBackward; + trickSpeed = NORMAL_SPEED; + TrickSpeed(Setup.MultiSpeedMode ? -1 : -MAX_SPEEDS); + } + break; + } + } +} + +int cDvbPlayer::SkipFrames(int Frames) +{ + if (index && Frames) { + int Current, Total; + GetIndex(Current, Total, true); + int OldCurrent = Current; + Current = index->GetNextIFrame(Current + Frames, Frames > 0); + return Current >= 0 ? Current : OldCurrent; + } + return -1; +} + +void cDvbPlayer::SkipSeconds(int Seconds) +{ + if (index && Seconds) { + LOCK_THREAD; + Empty(); + int Index = writeIndex; + if (Index >= 0) { + Index = max(Index + Seconds * FRAMESPERSEC, 0); + if (Index > 0) + Index = index->GetNextIFrame(Index, false, NULL, NULL, NULL, true); + if (Index >= 0) + readIndex = writeIndex = Index - 1; // Input() will first increment it! + } + Play(); + } +} + +void cDvbPlayer::Goto(int Index, bool Still) +{ + if (index) { + LOCK_THREAD; + Empty(); + if (++Index <= 0) + Index = 1; // not '0', to allow GetNextIFrame() below to work! + uchar FileNumber; + int FileOffset, Length; + Index = index->GetNextIFrame(Index, false, &FileNumber, &FileOffset, &Length); + if (Index >= 0 && NextFile(FileNumber, FileOffset) && Still) { + uchar b[MAXFRAMESIZE]; + int r = ReadFrame(replayFile, b, Length, sizeof(b)); + if (r > 0) { + if (playMode == pmPause) + DevicePlay(); + StripAudioPackets(b, r); + DeviceStillPicture(b, r); + } + playMode = pmStill; + } + readIndex = writeIndex = Index; + } +} + +void cDvbPlayer::GetIndex(int &Current, int &Total, bool SnapToIFrame) +{ + if (index) { + if (playMode == pmStill) + Current = max(readIndex, 0); + else { + Current = max(writeIndex, 0); + if (SnapToIFrame) { + int i1 = index->GetNextIFrame(Current + 1, false); + int i2 = index->GetNextIFrame(Current, true); + Current = (abs(Current - i1) <= abs(Current - i2)) ? i1 : i2; + } + } + Total = index->Last(); + } + else + Current = Total = -1; +} + +bool cDvbPlayer::GetReplayMode(bool &Play, bool &Forward, int &Speed) +{ + Play = (playMode == pmPlay || playMode == pmFast); + Forward = (playDir == pdForward); + if (playMode == pmFast || playMode == pmSlow) + Speed = Setup.MultiSpeedMode ? abs(trickSpeed - NORMAL_SPEED) : 0; + else + Speed = -1; + return true; +} + +// --- cDvbPlayerControl ----------------------------------------------------- + +cDvbPlayerControl::cDvbPlayerControl(void) +{ + player = NULL; +} + +cDvbPlayerControl::~cDvbPlayerControl() +{ + Stop(); +} + +bool cDvbPlayerControl::Active(void) +{ + return player && player->Active(); +} + +bool cDvbPlayerControl::Start(const char *FileName) +{ + delete player; + player = new cDvbPlayer(FileName); + if (cDevice::PrimaryDevice()->Attach(player)) + return true; + Stop(); + return false; +} + +void cDvbPlayerControl::Stop(void) +{ + delete player; + player = NULL; +} + +void cDvbPlayerControl::Pause(void) +{ + if (player) + player->Pause(); +} + +void cDvbPlayerControl::Play(void) +{ + if (player) + player->Play(); +} + +void cDvbPlayerControl::Forward(void) +{ + if (player) + player->Forward(); +} + +void cDvbPlayerControl::Backward(void) +{ + if (player) + player->Backward(); +} + +void cDvbPlayerControl::SkipSeconds(int Seconds) +{ + if (player) + player->SkipSeconds(Seconds); +} + +int cDvbPlayerControl::SkipFrames(int Frames) +{ + if (player) + return player->SkipFrames(Frames); + return -1; +} + +bool cDvbPlayerControl::GetIndex(int &Current, int &Total, bool SnapToIFrame) +{ + if (player) { + player->GetIndex(Current, Total, SnapToIFrame); + return true; + } + return false; +} + +bool cDvbPlayerControl::GetReplayMode(bool &Play, bool &Forward, int &Speed) +{ + return player && player->GetReplayMode(Play, Forward, Speed); +} + +void cDvbPlayerControl::Goto(int Position, bool Still) +{ + if (player) + player->Goto(Position, Still); +} diff --git a/dvbplayer.h b/dvbplayer.h new file mode 100644 index 00000000..b05bcdfa --- /dev/null +++ b/dvbplayer.h @@ -0,0 +1,60 @@ +/* + * dvbplayer.h: The DVB player + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * $Id: dvbplayer.h 1.1 2002/06/16 10:59:14 kls Exp $ + */ + +#ifndef __DVBPLAYER_H +#define __DVBPLAYER_H + +#include "player.h" +#include "thread.h" + +class cDvbPlayer; + +class cDvbPlayerControl : public cControl { +private: + cDvbPlayer *player; +public: + cDvbPlayerControl(void); + virtual ~cDvbPlayerControl(); + bool Active(void); + bool Start(const char *FileName); + // Starts replaying the given file. + void Stop(void); + // Stops the current replay session (if any). + void Pause(void); + // Pauses the current replay session, or resumes a paused session. + void Play(void); + // Resumes normal replay mode. + void Forward(void); + // Runs the current replay session forward at a higher speed. + void Backward(void); + // Runs the current replay session backwards at a higher speed. + int SkipFrames(int Frames); + // Returns the new index into the current replay session after skipping + // the given number of frames (no actual repositioning is done!). + // The sign of 'Frames' determines the direction in which to skip. + void SkipSeconds(int Seconds); + // Skips the given number of seconds in the current replay session. + // The sign of 'Seconds' determines the direction in which to skip. + // Use a very large negative value to go all the way back to the + // beginning of the recording. + bool GetIndex(int &Current, int &Total, bool SnapToIFrame = false); + // Returns the current and total frame index, optionally snapped to the + // nearest I-frame. + bool GetReplayMode(bool &Play, bool &Forward, int &Speed); + // Returns the current replay mode (if applicable). + // 'Play' tells whether we are playing or pausing, 'Forward' tells whether + // we are going forward or backward and 'Speed' is -1 if this is normal + // play/pause mode, 0 if it is single speed fast/slow forward/back mode + // and >0 if this is multi speed mode. + void Goto(int Index, bool Still = false); + // Positions to the given index and displays that frame as a still picture + // if Still is true. + }; + +#endif //__DVBPLAYER_H diff --git a/eit.c b/eit.c index 7f1bacf4..8584c645 100644 --- a/eit.c +++ b/eit.c @@ -16,7 +16,7 @@ * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * - * $Id: eit.c 1.45 2002/05/13 16:35:49 kls Exp $ + * $Id: eit.c 1.46 2002/06/10 16:30:00 kls Exp $ ***************************************************************************/ #include "eit.h" @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include diff --git a/eitscan.c b/eitscan.c index 88271822..21fbe74e 100644 --- a/eitscan.c +++ b/eitscan.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: eitscan.c 1.1 2002/05/20 10:58:10 kls Exp $ + * $Id: eitscan.c 1.2 2002/06/10 16:30:00 kls Exp $ */ #include "eitscan.h" @@ -48,11 +48,11 @@ void cEITScanner::Process(void) if (Setup.EPGScanTimeout && Channels.MaxNumber() > 1) { time_t now = time(NULL); if (now - lastScan > ScanTimeout && now - lastActivity > ActivityTimeout) { - for (int i = 0; i < MAXDVBAPI; i++) { - cDvbApi *DvbApi = cDvbApi::GetDvbApi(i + 1, MAXPRIORITY + 1); - if (DvbApi) { - if (DvbApi != cDvbApi::PrimaryDvbApi || (cDvbApi::NumDvbApis == 1 && Setup.EPGScanTimeout && now - lastActivity > Setup.EPGScanTimeout * 3600)) { - if (!(DvbApi->Recording() || DvbApi->Replaying() || DvbApi->Transferring())) { + for (int i = 0; i < MAXDEVICES; i++) { + cDevice *Device = cDevice::GetDevice(i + 1, MAXPRIORITY + 1); + if (Device) { + if (Device != cDevice::PrimaryDevice() || (cDevice::NumDevices() == 1 && Setup.EPGScanTimeout && now - lastActivity > Setup.EPGScanTimeout * 3600)) { + if (!(Device->Receiving() || Device->Replaying()/*XXX+ || Device->Transferring()XXX*/)) { int oldCh = lastChannel; int ch = oldCh + 1; while (ch != oldCh) { @@ -62,12 +62,12 @@ void cEITScanner::Process(void) } cChannel *Channel = Channels.GetByNumber(ch); if (Channel) { - if (Channel->ca <= MAXDVBAPI && !DvbApi->ProvidesCa(Channel->ca)) + if (Channel->ca <= MAXDEVICES && !Device->ProvidesCa(Channel->ca)) break; // the channel says it explicitly needs a different card if (Channel->pnr && !TransponderScanned(Channel)) { - if (DvbApi == cDvbApi::PrimaryDvbApi && !currentChannel) - currentChannel = DvbApi->Channel(); - Channel->Switch(DvbApi, false); + if (Device == cDevice::PrimaryDevice() && !currentChannel) + currentChannel = Device->Channel(); + Channel->Switch(Device, false); lastChannel = ch; break; } diff --git a/interface.c b/interface.c index 9c2e918d..ed9d0b1e 100644 --- a/interface.c +++ b/interface.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: interface.c 1.50 2002/05/19 12:02:57 kls Exp $ + * $Id: interface.c 1.51 2002/06/10 16:30:00 kls Exp $ */ #include "interface.h" @@ -484,6 +484,6 @@ void cInterface::DisplayRecording(int Index, bool On) bool cInterface::Recording(void) { - // This is located here because the Interface has to do with the "PrimaryDvbApi" anyway - return cDvbApi::PrimaryDvbApi->Recording(); + // This is located here because the Interface has to do with the "PrimaryDevice" anyway + return cDevice::PrimaryDevice()->Receiving(); } diff --git a/menu.c b/menu.c index 40119efa..7f063348 100644 --- a/menu.c +++ b/menu.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: menu.c 1.195 2002/05/19 14:55:22 kls Exp $ + * $Id: menu.c 1.196 2002/06/16 12:11:02 kls Exp $ */ #include "menu.h" @@ -18,6 +18,7 @@ #include "i18n.h" #include "menuitems.h" #include "plugin.h" +#include "recording.h" #include "status.h" #include "videodir.h" @@ -25,6 +26,7 @@ #define MAXWAIT4EPGINFO 10 // seconds #define MODETIMEOUT 3 // seconds +#define MAXRECORDCONTROLS (MAXDEVICES * MAXRECEIVERS) #define MAXINSTANTRECTIME (24 * 60 - 1) // 23:59 hours #define CHNUMWIDTH (Channels.Count() > 999 ? 5 : 4) // there are people with more than 999 channels... @@ -376,7 +378,7 @@ eOSState cMenuEditCaItem::ProcessKey(eKeys Key) } } else if (NORMALKEY(Key) == kRight) { - if (ca && ca->Next() && (allowCardNr || ((cCaDefinition *)ca->Next())->Number() > MAXDVBAPI)) { + if (ca && ca->Next() && (allowCardNr || ((cCaDefinition *)ca->Next())->Number() > MAXDEVICES)) { ca = (cCaDefinition *)ca->Next(); *value = ca->Number(); } @@ -494,7 +496,7 @@ cMenuChannels::cMenuChannels(void) //TODO int i = 0; cChannel *channel; - int curr = ((channel = Channels.GetByNumber(cDvbApi::CurrentChannel())) != NULL) ? channel->Index() : -1; + int curr = ((channel = Channels.GetByNumber(cDevice::CurrentChannel())) != NULL) ? channel->Index() : -1; while ((channel = Channels.Get(i)) != NULL) { Add(new cMenuChannelItem(i, channel), i == curr); @@ -1145,7 +1147,7 @@ cMenuSchedule::cMenuSchedule(void) { now = next = false; otherChannel = 0; - cChannel *channel = Channels.GetByNumber(cDvbApi::CurrentChannel()); + cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel()); if (channel) { cMenuWhatsOn::SetCurrentChannel(channel->number); schedules = cSIProcessor::Schedules(mutexLock); @@ -1264,7 +1266,7 @@ eOSState cMenuSchedule::ProcessKey(eKeys Key) cChannel *channel = Channels.GetByServiceID(ei->GetServiceID()); if (channel) { PrepareSchedule(channel); - if (channel->number != cDvbApi::CurrentChannel()) { + if (channel->number != cDevice::CurrentChannel()) { otherChannel = channel->number; SetHelp(tr("Record"), tr("Now"), tr("Next"), tr("Switch")); } @@ -1441,7 +1443,7 @@ eOSState cMenuRecordings::Rewind(void) { cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current()); if (ri && !ri->IsDirectory()) { - cDvbApi::PrimaryDvbApi->StopReplay(); // must do this first to be able to rewind the currently replayed recording + cDevice::PrimaryDevice()->StopReplay(); // must do this first to be able to rewind the currently replayed recording cResumeFile ResumeFile(ri->FileName()); ResumeFile.Delete(); return Play(); @@ -1614,7 +1616,7 @@ public: cMenuSetupDVB::cMenuSetupDVB(void) { SetSection(tr("DVB")); - Add(new cMenuEditIntItem( tr("Setup.DVB$Primary DVB interface"), &data.PrimaryDVB, 1, cDvbApi::NumDvbApis)); + Add(new cMenuEditIntItem( tr("Setup.DVB$Primary DVB interface"), &data.PrimaryDVB, 1, cDevice::NumDevices())); Add(new cMenuEditBoolItem(tr("Setup.DVB$Video format"), &data.VideoFormat, "4:3", "16:9")); } @@ -1626,7 +1628,7 @@ eOSState cMenuSetupDVB::ProcessKey(eKeys Key) if (state == osBack && Key == kOk) { if (Setup.PrimaryDVB != oldPrimaryDVB) { state = osSwitchDvb; - cDvbApi::PrimaryDvbApi->SetVideoFormat(Setup.VideoFormat ? VIDEO_FORMAT_16_9 : VIDEO_FORMAT_4_3); + cDevice::PrimaryDevice()->SetVideoFormat(Setup.VideoFormat ? VIDEO_FORMAT_16_9 : VIDEO_FORMAT_4_3); } } return state; @@ -1659,7 +1661,7 @@ public: cMenuSetupCICAM::cMenuSetupCICAM(void) { SetSection(tr("CICAM")); - for (int d = 0; d < cDvbApi::NumDvbApis; d++) { + for (int d = 0; d < cDevice::NumDevices(); d++) { for (int i = 0; i < 2; i++) { char buffer[32]; snprintf(buffer, sizeof(buffer), "%s%d %d", tr("Setup.CICAM$CICAM DVB"), d + 1, i + 1); @@ -1673,7 +1675,7 @@ eOSState cMenuSetupCICAM::ProcessKey(eKeys Key) eOSState state = cMenuSetupBase::ProcessKey(Key); if (state == osBack && Key == kOk) - cDvbApi::SetCaCaps(); + cDevice::SetCaCaps(); return state; } @@ -2024,12 +2026,14 @@ void cMenuMain::Set(void) // Editing control: + /*XXX+ if (cVideoCutter::Active()) Add(new cOsdItem(tr(" Cancel editing"), osCancelEdit)); + XXX*/ // Color buttons: - SetHelp(tr("Record"), cDvbApi::PrimaryDvbApi->CanToggleAudioTrack() ? tr("Language") : NULL, NULL, replaying ? tr("Button$Stop") : cReplayControl::LastReplayed() ? tr("Resume") : NULL); + SetHelp(tr("Record"), /*XXX+ cDevice::PrimaryDevice()->CanToggleAudioTrack() ? tr("Language") :XXX*/ NULL, NULL, replaying ? tr("Button$Stop") : cReplayControl::LastReplayed() ? tr("Resume") : NULL); Display(); lastActivity = time(NULL); } @@ -2059,7 +2063,7 @@ eOSState cMenuMain::ProcessKey(eKeys Key) } break; case osCancelEdit: if (Interface->Confirm(tr("Cancel editing?"))) { - cVideoCutter::Stop(); + //XXX+cVideoCutter::Stop(); return osEnd; } break; @@ -2082,11 +2086,13 @@ eOSState cMenuMain::ProcessKey(eKeys Key) state = osRecord; break; case kGreen: if (!HasSubMenu()) { - if (cDvbApi::PrimaryDvbApi->CanToggleAudioTrack()) { + /*XXX+ + if (cDevice::PrimaryDevice()->CanToggleAudioTrack()) { Interface->Clear(); - cDvbApi::PrimaryDvbApi->ToggleAudioTrack(); + cDevice::PrimaryDevice()->ToggleAudioTrack(); state = osEnd; } + XXX*/ } break; case kBlue: if (!HasSubMenu()) @@ -2134,7 +2140,7 @@ cDisplayChannel::cDisplayChannel(eKeys FirstKey) :cOsdObject(true) { group = -1; - oldNumber = cDvbApi::CurrentChannel(); + oldNumber = cDevice::CurrentChannel(); number = 0; lastTime = time_ms(); int EpgLines = Setup.ShowInfoOnChSwitch ? 5 : 1; @@ -2253,7 +2259,7 @@ eOSState cDisplayChannel::ProcessKey(eKeys Key) case kRight: withInfo = false; if (group < 0) { - cChannel *channel = Channels.GetByNumber(cDvbApi::CurrentChannel()); + cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel()); if (channel) group = channel->Index(); } @@ -2328,7 +2334,7 @@ cDisplayVolume::cDisplayVolume(void) :cOsdObject(true) { displayVolume = this; - timeout = time_ms() + (cDvbApi::PrimaryDvbApi->IsMute() ? MUTETIMEOUT : VOLUMETIMEOUT); + timeout = time_ms() + (cDevice::PrimaryDevice()->IsMute() ? MUTETIMEOUT : VOLUMETIMEOUT); Interface->Open(Setup.OSDwidth, -1); Show(); } @@ -2341,13 +2347,13 @@ cDisplayVolume::~cDisplayVolume() void cDisplayVolume::Show(void) { - cDvbApi *dvbApi = cDvbApi::PrimaryDvbApi; - if (dvbApi->IsMute()) { + cDevice *device = cDevice::PrimaryDevice(); + if (device->IsMute()) { Interface->Fill(0, 0, Width(), 1, clrTransparent); Interface->Write(0, 0, tr("Mute"), clrGreen); } else { - int Current = cDvbApi::CurrentVolume(); + int Current = cDevice::CurrentVolume(); int Total = MAXVOLUME; const char *Prompt = tr("Volume "); #ifdef DEBUG_OSD @@ -2387,7 +2393,7 @@ eOSState cDisplayVolume::ProcessKey(eKeys Key) timeout = time_ms() + VOLUMETIMEOUT; break; case kMute: - if (cDvbApi::PrimaryDvbApi->IsMute()) { + if (cDevice::PrimaryDevice()->IsMute()) { Show(); timeout = time_ms() + MUTETIMEOUT; } @@ -2405,43 +2411,45 @@ eOSState cDisplayVolume::ProcessKey(eKeys Key) // --- cRecordControl -------------------------------------------------------- -cRecordControl::cRecordControl(cDvbApi *DvbApi, cTimer *Timer) +cRecordControl::cRecordControl(cDevice *Device, cTimer *Timer) { eventInfo = NULL; instantId = NULL; fileName = NULL; - dvbApi = DvbApi; - if (!dvbApi) dvbApi = cDvbApi::PrimaryDvbApi;//XXX + recorder = NULL; + device = Device; + if (!device) device = cDevice::PrimaryDevice();//XXX timer = Timer; if (!timer) { timer = new cTimer(true); Timers.Add(timer); Timers.Save(); - asprintf(&instantId, cDvbApi::NumDvbApis > 1 ? "%s - %d" : "%s", Channels.GetChannelNameByNumber(timer->channel), dvbApi->CardIndex() + 1); + asprintf(&instantId, cDevice::NumDevices() > 1 ? "%s - %d" : "%s", Channels.GetChannelNameByNumber(timer->channel), device->CardIndex() + 1); } timer->SetPending(true); timer->SetRecording(true); - if (Channels.SwitchTo(timer->channel, dvbApi)) { - const char *Title = NULL; - const char *Subtitle = NULL; - const char *Summary = NULL; - if (GetEventInfo()) { - Title = eventInfo->GetTitle(); - Subtitle = eventInfo->GetSubtitle(); - Summary = eventInfo->GetExtendedDescription(); - dsyslog("Title: '%s' Subtitle: '%s'", Title, Subtitle); - } - cRecording Recording(timer, Title, Subtitle, Summary); - fileName = strdup(Recording.FileName()); - cRecordingUserCommand::InvokeCommand(RUC_BEFORERECORDING, fileName); - if (dvbApi->StartRecord(fileName, Channels.GetByNumber(timer->channel)->ca, timer->priority)) { - Recording.WriteSummary(); - cStatusMonitor::MsgRecording(dvbApi, fileName); - } - Interface->DisplayRecording(dvbApi->CardIndex(), true); + + const char *Title = NULL; + const char *Subtitle = NULL; + const char *Summary = NULL; + if (GetEventInfo()) { + Title = eventInfo->GetTitle(); + Subtitle = eventInfo->GetSubtitle(); + Summary = eventInfo->GetExtendedDescription(); + dsyslog("Title: '%s' Subtitle: '%s'", Title, Subtitle); + } + cRecording Recording(timer, Title, Subtitle, Summary); + fileName = strdup(Recording.FileName()); + cRecordingUserCommand::InvokeCommand(RUC_BEFORERECORDING, fileName); + cChannel *ch = Channels.GetByNumber(timer->channel); + recorder = new cRecorder(fileName, ch->ca, timer->priority, ch->vpid, ch->apid1, ch->apid2, ch->dpid1, ch->dpid2); + if (device->Attach(recorder)) { + Recording.WriteSummary(); + cStatusMonitor::MsgRecording(device, fileName); + Interface->DisplayRecording(device->CardIndex(), true); } else - cThread::EmergencyExit(true); + DELETENULL(recorder); } cRecordControl::~cRecordControl() @@ -2484,8 +2492,8 @@ bool cRecordControl::GetEventInfo(void) void cRecordControl::Stop(bool KeepInstant) { if (timer) { - cStatusMonitor::MsgRecording(dvbApi, NULL); - dvbApi->StopRecord(); + cStatusMonitor::MsgRecording(device, NULL); + DELETENULL(recorder); timer->SetRecording(false); if ((IsInstant() && !KeepInstant) || (timer->IsSingleEvent() && !timer->Matches())) { // checking timer->Matches() to make sure we don't delete the timer @@ -2495,14 +2503,14 @@ void cRecordControl::Stop(bool KeepInstant) Timers.Save(); } timer = NULL; - Interface->DisplayRecording(dvbApi->CardIndex(), false); + Interface->DisplayRecording(device->CardIndex(), false); cRecordingUserCommand::InvokeCommand(RUC_AFTERRECORDING, fileName); } } bool cRecordControl::Process(time_t t) { - if (!timer || !timer->Matches(t)) + if (!recorder || !timer || !timer->Matches(t)) return false; AssertFreeDiskSpace(timer->priority); return true; @@ -2510,20 +2518,27 @@ bool cRecordControl::Process(time_t t) // --- cRecordControls ------------------------------------------------------- -cRecordControl *cRecordControls::RecordControls[MAXDVBAPI] = { NULL }; +cRecordControl *cRecordControls::RecordControls[MAXRECORDCONTROLS] = { NULL }; bool cRecordControls::Start(cTimer *Timer) { - int ch = Timer ? Timer->channel : cDvbApi::CurrentChannel(); + int ch = Timer ? Timer->channel : cDevice::CurrentChannel(); cChannel *channel = Channels.GetByNumber(ch); if (channel) { - cDvbApi *dvbApi = cDvbApi::GetDvbApi(channel->ca, Timer ? Timer->priority : Setup.DefaultPriority); - if (dvbApi) { - Stop(dvbApi); - for (int i = 0; i < MAXDVBAPI; i++) { + bool ReUse = false; + cDevice *device = cDevice::GetDevice(channel->ca, Timer ? Timer->priority : Setup.DefaultPriority, channel->frequency, channel->vpid, &ReUse); + if (device) { + if (!ReUse) { + Stop(device); + if (!channel->Switch(device)) { + cThread::EmergencyExit(true); + return false; + } + } + for (int i = 0; i < MAXRECORDCONTROLS; i++) { if (!RecordControls[i]) { - RecordControls[i] = new cRecordControl(dvbApi, Timer); + RecordControls[i] = new cRecordControl(device, Timer); return true; } } @@ -2538,7 +2553,7 @@ bool cRecordControls::Start(cTimer *Timer) void cRecordControls::Stop(const char *InstantId) { - for (int i = 0; i < MAXDVBAPI; i++) { + for (int i = 0; i < MAXRECORDCONTROLS; i++) { if (RecordControls[i]) { const char *id = RecordControls[i]->InstantId(); if (id && strcmp(id, InstantId) == 0) @@ -2547,12 +2562,12 @@ void cRecordControls::Stop(const char *InstantId) } } -void cRecordControls::Stop(cDvbApi *DvbApi) +void cRecordControls::Stop(cDevice *Device) { - for (int i = 0; i < MAXDVBAPI; i++) { + for (int i = 0; i < MAXRECORDCONTROLS; i++) { if (RecordControls[i]) { - if (RecordControls[i]->Uses(DvbApi)) { - isyslog("stopping recording on DVB device %d due to higher priority", DvbApi->CardIndex() + 1); + if (RecordControls[i]->Uses(Device)) { + isyslog("stopping recording on DVB device %d due to higher priority", Device->CardIndex() + 1); RecordControls[i]->Stop(true); } } @@ -2561,11 +2576,11 @@ void cRecordControls::Stop(cDvbApi *DvbApi) bool cRecordControls::StopPrimary(bool DoIt) { - if (cDvbApi::PrimaryDvbApi->Recording()) { - cDvbApi *dvbApi = cDvbApi::GetDvbApi(cDvbApi::PrimaryDvbApi->Ca(), 0); - if (dvbApi) { + if (cDevice::PrimaryDevice()->Receiving()) { + cDevice *device = cDevice::GetDevice(cDevice::PrimaryDevice()->Ca(), 0); + if (device) { if (DoIt) - Stop(cDvbApi::PrimaryDvbApi); + Stop(cDevice::PrimaryDevice()); return true; } } @@ -2574,7 +2589,7 @@ bool cRecordControls::StopPrimary(bool DoIt) const char *cRecordControls::GetInstantId(const char *LastInstantId) { - for (int i = 0; i < MAXDVBAPI; i++) { + for (int i = 0; i < MAXRECORDCONTROLS; i++) { if (RecordControls[i]) { if (!LastInstantId && RecordControls[i]->InstantId()) return RecordControls[i]->InstantId(); @@ -2587,7 +2602,7 @@ const char *cRecordControls::GetInstantId(const char *LastInstantId) cRecordControl *cRecordControls::GetRecordControl(const char *FileName) { - for (int i = 0; i < MAXDVBAPI; i++) { + for (int i = 0; i < MAXRECORDCONTROLS; i++) { if (RecordControls[i] && strcmp(RecordControls[i]->FileName(), FileName) == 0) return RecordControls[i]; } @@ -2596,7 +2611,7 @@ cRecordControl *cRecordControls::GetRecordControl(const char *FileName) void cRecordControls::Process(time_t t) { - for (int i = 0; i < MAXDVBAPI; i++) { + for (int i = 0; i < MAXRECORDCONTROLS; i++) { if (RecordControls[i]) { if (!RecordControls[i]->Process(t)) DELETENULL(RecordControls[i]); @@ -2606,13 +2621,19 @@ void cRecordControls::Process(time_t t) bool cRecordControls::Active(void) { - for (int i = 0; i < MAXDVBAPI; i++) { + for (int i = 0; i < MAXRECORDCONTROLS; i++) { if (RecordControls[i]) return true; } return false; } +void cRecordControls::Shutdown(void) +{ + for (int i = 0; i < MAXRECORDCONTROLS; i++) + DELETENULL(RecordControls[i]); +} + // --- cProgressBar ---------------------------------------------------------- class cProgressBar : public cBitmap { @@ -2664,25 +2685,24 @@ char *cReplayControl::title = NULL; cReplayControl::cReplayControl(void) { - dvbApi = cDvbApi::PrimaryDvbApi; visible = modeOnly = shown = displayFrames = false; lastCurrent = lastTotal = -1; timeoutShow = 0; timeSearchActive = false; if (fileName) { marks.Load(fileName); - if (!dvbApi->StartReplay(fileName)) - Interface->Error(tr("Channel locked (recording)!")); + if (!Start(fileName)) + Interface->Error(tr("Channel locked (recording)!"));//XXX+ else - cStatusMonitor::MsgReplaying(dvbApi, fileName); + cStatusMonitor::MsgReplaying(this, fileName); } } cReplayControl::~cReplayControl() { Hide(); - cStatusMonitor::MsgReplaying(dvbApi, NULL); - dvbApi->StopReplay(); + cStatusMonitor::MsgReplaying(this, NULL); + Stop(); } void cReplayControl::SetRecording(const char *FileName, const char *Title) @@ -2744,7 +2764,7 @@ void cReplayControl::ShowMode(void) if (Setup.ShowReplayMode && !timeSearchActive) { bool Play, Forward; int Speed; - if (dvbApi->GetReplayMode(Play, Forward, Speed)) { + if (GetReplayMode(Play, Forward, Speed)) { bool NormalPlay = (Play && Speed == -1); if (!visible) { @@ -2782,7 +2802,7 @@ bool cReplayControl::ShowProgress(bool Initial) { int Current, Total; - if (dvbApi->GetIndex(Current, Total) && Total > 0) { + if (GetIndex(Current, Total) && Total > 0) { if (!visible) { Interface->Open(Setup.OSDwidth, -3); needsFastResponse = visible = true; @@ -2857,14 +2877,14 @@ void cReplayControl::TimeSearchProcess(eKeys Key) int dir = (Key == kRight ? 1 : -1); if (dir > 0) Seconds = min(Total - Current - STAY_SECONDS_OFF_END, Seconds); - dvbApi->SkipSeconds(Seconds * dir); + SkipSeconds(Seconds * dir); timeSearchActive = false; } break; case kUp: case kDown: Seconds = min(Total - STAY_SECONDS_OFF_END, Seconds); - dvbApi->Goto(Seconds * FRAMESPERSEC, Key == kDown); + Goto(Seconds * FRAMESPERSEC, Key == kDown); timeSearchActive = false; break; default: @@ -2902,7 +2922,7 @@ void cReplayControl::TimeSearch(void) void cReplayControl::MarkToggle(void) { int Current, Total; - if (dvbApi->GetIndex(Current, Total, true)) { + if (GetIndex(Current, Total, true)) { cMark *m = marks.Get(Current); lastCurrent = -1; // triggers redisplay if (m) @@ -2919,10 +2939,10 @@ void cReplayControl::MarkJump(bool Forward) { if (marks.Count()) { int Current, Total; - if (dvbApi->GetIndex(Current, Total)) { + if (GetIndex(Current, Total)) { cMark *m = Forward ? marks.GetNext(Current) : marks.GetPrev(Current); if (m) - dvbApi->Goto(m->position, true); + Goto(m->position, true); } displayFrames = true; } @@ -2931,11 +2951,11 @@ void cReplayControl::MarkJump(bool Forward) void cReplayControl::MarkMove(bool Forward) { int Current, Total; - if (dvbApi->GetIndex(Current, Total)) { + if (GetIndex(Current, Total)) { cMark *m = marks.Get(Current); if (m) { displayFrames = true; - int p = dvbApi->SkipFrames(Forward ? 1 : -1); + int p = SkipFrames(Forward ? 1 : -1); cMark *m2; if (Forward) { if ((m2 = marks.Next(m)) != NULL && m2->position <= p) @@ -2945,7 +2965,7 @@ void cReplayControl::MarkMove(bool Forward) if ((m2 = marks.Prev(m)) != NULL && m2->position >= p) return; } - dvbApi->Goto(m->position = p, true); + Goto(m->position = p, true); marks.Save(); } } @@ -2953,6 +2973,7 @@ void cReplayControl::MarkMove(bool Forward) void cReplayControl::EditCut(void) { + /*XXX+ if (fileName) { Hide(); if (!cVideoCutter::Active()) { @@ -2965,12 +2986,13 @@ void cReplayControl::EditCut(void) Interface->Error(tr("Editing process already active!")); ShowMode(); } + XXX*/ } void cReplayControl::EditTest(void) { int Current, Total; - if (dvbApi->GetIndex(Current, Total)) { + if (GetIndex(Current, Total)) { cMark *m = marks.Get(Current); if (!m) m = marks.GetNext(Current); @@ -2978,8 +3000,8 @@ void cReplayControl::EditTest(void) if ((m->Index() & 0x01) != 0) m = marks.Next(m); if (m) { - dvbApi->Goto(m->position - dvbApi->SecondsToFrames(3)); - dvbApi->Play(); + Goto(m->position - SecondsToFrames(3)); + Play(); } } } @@ -2987,7 +3009,7 @@ void cReplayControl::EditTest(void) eOSState cReplayControl::ProcessKey(eKeys Key) { - if (!dvbApi->Replaying()) + if (!Active()) return osEnd; if (visible) { if (timeoutShow && time(NULL) > timeoutShow) { @@ -3009,21 +3031,21 @@ eOSState cReplayControl::ProcessKey(eKeys Key) bool DoShowMode = true; switch (Key) { // Positioning: - case kUp: dvbApi->Play(); break; - case kDown: dvbApi->Pause(); break; + case kUp: Play(); break; + case kDown: Pause(); break; case kLeft|k_Release: if (Setup.MultiSpeedMode) break; - case kLeft: dvbApi->Backward(); break; + case kLeft: Backward(); break; case kRight|k_Release: if (Setup.MultiSpeedMode) break; - case kRight: dvbApi->Forward(); break; + case kRight: Forward(); break; case kRed: TimeSearch(); break; case kGreen|k_Repeat: - case kGreen: dvbApi->SkipSeconds(-60); break; + case kGreen: SkipSeconds(-60); break; case kYellow|k_Repeat: - case kYellow: dvbApi->SkipSeconds( 60); break; + case kYellow: SkipSeconds( 60); break; case kBlue: Hide(); - dvbApi->StopReplay(); + Stop(); return osEnd; default: { DoShowMode = false; diff --git a/menu.h b/menu.h index 70aba6e6..9dfe1a40 100644 --- a/menu.h +++ b/menu.h @@ -4,14 +4,16 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: menu.h 1.43 2002/05/18 12:36:06 kls Exp $ + * $Id: menu.h 1.44 2002/06/14 12:33:35 kls Exp $ */ #ifndef __MENU_H #define __MENU_H -#include "dvbapi.h" +#include "device.h" #include "osd.h" +#include "dvbplayer.h" +#include "recorder.h" #include "recording.h" class cMenuMain : public cOsdMenu { @@ -76,17 +78,18 @@ public: class cRecordControl { private: - cDvbApi *dvbApi; + cDevice *device; cTimer *timer; + cRecorder *recorder; const cEventInfo *eventInfo; char *instantId; char *fileName; bool GetEventInfo(void); public: - cRecordControl(cDvbApi *DvbApi, cTimer *Timer = NULL); + cRecordControl(cDevice *Device, cTimer *Timer = NULL); virtual ~cRecordControl(); bool Process(time_t t); - bool Uses(cDvbApi *DvbApi) { return DvbApi == dvbApi; } + bool Uses(cDevice *Device) { return Device == device; } void Stop(bool KeepInstant = false); bool IsInstant(void) { return instantId; } const char *InstantId(void) { return instantId; } @@ -96,21 +99,21 @@ public: class cRecordControls { private: - static cRecordControl *RecordControls[MAXDVBAPI]; + static cRecordControl *RecordControls[]; public: static bool Start(cTimer *Timer = NULL); static void Stop(const char *InstantId); - static void Stop(cDvbApi *DvbApi); + static void Stop(cDevice *Device); static bool StopPrimary(bool DoIt = false); static const char *GetInstantId(const char *LastInstantId); static cRecordControl *GetRecordControl(const char *FileName); static void Process(time_t t); static bool Active(void); + static void Shutdown(void); }; -class cReplayControl : public cOsdObject { +class cReplayControl : public cDvbPlayerControl { private: - cDvbApi *dvbApi; cMarks marks; bool visible, modeOnly, shown, displayFrames; int lastCurrent, lastTotal; diff --git a/osd.c b/osd.c index 09285c00..22093e24 100644 --- a/osd.c +++ b/osd.c @@ -4,12 +4,12 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: osd.c 1.27 2002/05/19 12:56:57 kls Exp $ + * $Id: osd.c 1.28 2002/06/10 16:30:00 kls Exp $ */ #include "osd.h" #include -#include "dvbapi.h" +#include "device.h" #include "i18n.h" #include "status.h" @@ -95,7 +95,7 @@ void cOsd::Open(int w, int h) int x = (720 - w + charWidth) / 2; //TODO PAL vs. NTSC??? int y = (576 - Setup.OSDheight * lineHeight) / 2 + d; //XXX - osd = new cDvbOsd(cDvbApi::PrimaryDvbApi->OsdDeviceHandle(), x, y); + osd = new cDvbOsd(cDevice::PrimaryDevice()->OsdDeviceHandle(), x, y); //XXX TODO this should be transferred to the places where the individual windows are requested (there's too much detailed knowledge here!) if (h / lineHeight == 5) { //XXX channel display osd->Create(0, 0, w, h, 4); diff --git a/player.c b/player.c new file mode 100644 index 00000000..fe0a2771 --- /dev/null +++ b/player.c @@ -0,0 +1,55 @@ +/* + * player.c: The basic player interface + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * $Id: player.c 1.1 2002/06/16 10:34:50 kls Exp $ + */ + +#include "player.h" + +// --- cPlayer --------------------------------------------------------------- + +cPlayer::cPlayer(void) +{ + device = NULL; + deviceFileHandle = -1; +} + +cPlayer::~cPlayer() +{ + Detach(); +} + +int cPlayer::PlayVideo(const uchar *Data, int Length) +{ + if (device) + return device->PlayVideo(Data, Length); + esyslog("ERROR: attempt to use cPlayer::PlayVideo() without attaching to a cDevice!"); + return -1; +} + +int cPlayer::PlayAudio(const uchar *Data, int Length) +{ + if (device) + return device->PlayAudio(Data, Length); + esyslog("ERROR: attempt to use cPlayer::PlayAudio() without attaching to a cDevice!"); + return -1; +} + +void cPlayer::Detach(void) +{ + if (device) + device->Detach(this); +} + +// --- cControl -------------------------------------------------------------- + +cControl::cControl(void) +{ +} + +cControl::~cControl() +{ +} diff --git a/player.h b/player.h new file mode 100644 index 00000000..1221c244 --- /dev/null +++ b/player.h @@ -0,0 +1,51 @@ +/* + * player.h: The basic player interface + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * $Id: player.h 1.1 2002/06/16 11:52:45 kls Exp $ + */ + +#ifndef __PLAYER_H +#define __PLAYER_H + +#include "device.h" +#include "osd.h" + +class cPlayer { + friend class cDevice; +private: + cDevice *device; + int deviceFileHandle; +protected: + int DeviceFileHandle(void) { return deviceFileHandle; } //XXX+ needed for polling + void DeviceTrickSpeed(int Speed) { if (device) device->TrickSpeed(Speed); } + void DeviceClear(void) { if (device) device->Clear(); } + void DevicePlay(void) { if (device) device->Play(); } + void DeviceFreeze(void) { if (device) device->Freeze(); } + void DeviceMute(void) { if (device) device->Mute(); } + void DeviceStillPicture(const uchar *Data, int Length) { if (device) device->StillPicture(Data, Length); } + void Detach(void); + virtual void Activate(bool On) {} + // This function is called right after the cPlayer has been attached to + // (On == true) or before it gets detached from (On == false) a cDevice. + // It can be used to do things like starting/stopping a thread. + int PlayVideo(const uchar *Data, int Length); + // Sends the given Data to the video device and returns the number of + // bytes that have actually been accepted by the video device (or a + // negative value in case of an error). + int PlayAudio(const uchar *Data, int Length); + // XXX+ TODO +public: + cPlayer(void); + virtual ~cPlayer(); + }; + +class cControl : public cOsdObject { +public: + cControl(void); + virtual ~cControl(); + }; + +#endif //__PLAYER_H diff --git a/receiver.c b/receiver.c new file mode 100644 index 00000000..6ddbaa64 --- /dev/null +++ b/receiver.c @@ -0,0 +1,55 @@ +/* + * receiver.c: The basic receiver interface + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * $Id: receiver.c 1.1 2002/06/10 16:30:00 kls Exp $ + */ + +#include +#include +#include "receiver.h" + +cReceiver::cReceiver(int Ca, int Priority, int NumPids, ...) +{ + device = NULL; + ca = Ca; + priority = Priority; + if (NumPids) { + va_list ap; + va_start(ap, NumPids); + int n = 0; + while (n < MAXRECEIVEPIDS && NumPids--) { + if ((pids[n] = va_arg(ap, int)) != 0) + n++; + } + va_end(ap); + } + else + esyslog("ERROR: cReceiver called without a PID!"); +} + +cReceiver::~cReceiver() +{ + Detach(); +} + +bool cReceiver::WantsPid(int Pid) +{ + if (Pid) { + for (int i = 0; i < MAXRECEIVEPIDS; i++) { + if (pids[i] == Pid) + return true; + if (!pids[i]) + break; + } + } + return false; +} + +void cReceiver::Detach(void) +{ + if (device) + device->Detach(this); +} diff --git a/receiver.h b/receiver.h new file mode 100644 index 00000000..87fe9b86 --- /dev/null +++ b/receiver.h @@ -0,0 +1,48 @@ +/* + * receiver.h: The basic receiver interface + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * $Id: receiver.h 1.1 2002/06/10 16:30:00 kls Exp $ + */ + +#ifndef __RECEIVER_H +#define __RECEIVER_H + +#include "device.h" + +#define MAXRECEIVEPIDS 16 // the maximum number of PIDs per receiver + +class cReceiver { + friend class cDevice; +private: + cDevice *device; + int ca; + int priority; + int pids[MAXRECEIVEPIDS]; + bool WantsPid(int Pid); +protected: + void Detach(void); + virtual void Activate(bool On) {} + // This function is called just before the cReceiver gets attached to + // (On == true) or detached from (On == false) a cDevice. It can be used + // to do things like starting/stopping a thread. + // It is guaranteed that Receive() will not be called before Activate(true). + virtual void Receive(uchar *Data, int Length) = 0; + // This function is called from the cDevice we are attached to, and + // delivers one TS packet from the set of PIDs the cReceiver has requested. + // The data packet must be accepted immediately, and the call must return + // as soon as possible, without any unnecessary delay. Each TS packet + // will be delivered only ONCE, so the cReceiver must make sure that + // it will be able to buffer the data if necessary. +public: + cReceiver(int Ca, int Priority, int NumPids, ...); + // Creates a new receiver that requires conditional access Ca and has + // the given Priority. NumPids defines the number of PIDs that follow + // this parameter. If any of these PIDs are 0, they will be silently ignored. + // The total number of non-zero PIDs must not exceed MAXRECEIVEPIDS. + virtual ~cReceiver(); + }; + +#endif //__RECEIVER_H diff --git a/recorder.c b/recorder.c new file mode 100644 index 00000000..3a0941bb --- /dev/null +++ b/recorder.c @@ -0,0 +1,149 @@ +/* + * recorder.h: The actual DVB recorder + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * $Id: recorder.c 1.1 2002/06/16 10:03:25 kls Exp $ + */ + +#include +#include +#include +#include "recorder.h" + +// The size of the array used to buffer video data: +// (must be larger than MINVIDEODATA - see remux.h) +#define VIDEOBUFSIZE MEGABYTE(1) + +#define MINFREEDISKSPACE (512) // MB +#define DISKCHECKINTERVAL 100 // seconds + +cRecorder::cRecorder(const char *FileName, int Ca, int Priority, int VPid, int APid1, int APid2, int DPid1, int DPid2) +:cReceiver(Ca, Priority, 5, VPid, APid1, APid2, DPid1, DPid2) +{ + ringBuffer = NULL; + remux = NULL; + fileName = NULL; + index = NULL; + pictureType = NO_PICTURE; + fileSize = 0; + active = false; + lastDiskSpaceCheck = time(NULL); + isyslog("record %s", FileName); + + // Create directories if necessary: + + if (!MakeDirs(FileName, true)) + return; + + // Make sure the disk is up and running: + + SpinUpDisk(FileName); + + ringBuffer = new cRingBufferLinear(VIDEOBUFSIZE, true); + remux = new cRemux(VPid, APid1, APid2, DPid1, DPid2, true); + fileName = new cFileName(FileName, true); + recordFile = fileName->Open(); + if (recordFile < 0) + return; + // Create the index file: + index = new cIndexFile(FileName, true); + if (!index) + esyslog("ERROR: can't allocate index"); + // let's continue without index, so we'll at least have the recording +} + +cRecorder::~cRecorder() +{ + Detach(); + delete index; + delete fileName; + delete remux; + delete ringBuffer; +} + +void cRecorder::Activate(bool On) +{ + if (On) { + if (recordFile >= 0) + Start(); + } + else if (active) { + active = false; + Cancel(3); + } +} + +bool cRecorder::RunningLowOnDiskSpace(void) +{ + if (time(NULL) > lastDiskSpaceCheck + DISKCHECKINTERVAL) { + int Free = FreeDiskSpaceMB(fileName->Name()); + lastDiskSpaceCheck = time(NULL); + if (Free < MINFREEDISKSPACE) { + dsyslog("low disk space (%d MB, limit is %d MB)", Free, MINFREEDISKSPACE); + return true; + } + } + return false; +} + +bool cRecorder::NextFile(void) +{ + if (recordFile >= 0 && pictureType == I_FRAME) { // every file shall start with an I_FRAME + if (fileSize > MEGABYTE(Setup.MaxVideoFileSize) || RunningLowOnDiskSpace()) { + recordFile = fileName->NextFile(); + fileSize = 0; + } + } + return recordFile >= 0; +} + +void cRecorder::Receive(uchar *Data, int Length) +{ + int p = ringBuffer->Put(Data, Length); + if (p != Length && active) + esyslog("ERROR: ring buffer overflow (%d bytes dropped)", Length - p); +} + +void cRecorder::Action(void) +{ + dsyslog("recording thread started (pid=%d)", getpid()); + + uchar b[MINVIDEODATA]; + int r = 0; + active = true; + while (active) { + int g = ringBuffer->Get(b + r, sizeof(b) - r); + if (g > 0) + r += g; + if (r > 0) { + int Count = r, Result; + const uchar *p = remux->Process(b, Count, Result, &pictureType); + if (p) { + //XXX+ active??? see old version (Busy) + if (!active && pictureType == I_FRAME) // finish the recording before the next 'I' frame + break; + if (NextFile()) { + if (index && pictureType != NO_PICTURE) + index->Write(pictureType, fileName->Number(), fileSize); + if (safe_write(recordFile, p, Result) < 0) { + LOG_ERROR_STR(fileName->Name()); + break; + } + fileSize += Result; + } + else + break; + } + if (Count > 0) { + r -= Count; + memmove(b, b + Count, r); + } + } + else + usleep(1); // this keeps the CPU load low + } + + dsyslog("recording thread ended (pid=%d)", getpid()); +} diff --git a/recorder.h b/recorder.h new file mode 100644 index 00000000..7493f0b2 --- /dev/null +++ b/recorder.h @@ -0,0 +1,43 @@ +/* + * recorder.h: The actual DVB recorder + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * $Id: recorder.h 1.1 2002/06/10 16:30:00 kls Exp $ + */ + +#ifndef __RECORDER_H +#define __RECORDER_H + +#include "receiver.h" +#include "recording.h" +#include "remux.h" +#include "ringbuffer.h" +#include "thread.h" + +class cRecorder : public cReceiver, cThread { +private: + cRingBufferLinear *ringBuffer; + cRemux *remux; + cFileName *fileName; + cIndexFile *index; + uchar pictureType; + int fileSize; + int recordFile; + bool active; + time_t lastDiskSpaceCheck; + bool RunningLowOnDiskSpace(void); + bool NextFile(void); +protected: + virtual void Activate(bool On); + virtual void Receive(uchar *Data, int Length); + virtual void Action(void); +public: + cRecorder(const char *FileName, int Ca, int Priority, int VPid, int APid1, int APid2, int DPid1, int DPid2); + // Creates a new recorder that requires conditional access Ca, has + // the given Priority and will record the given PIDs into the file FileName. + virtual ~cRecorder(); + }; + +#endif //__RECORDER_H diff --git a/recording.c b/recording.c index c65aaaf9..7bc896c5 100644 --- a/recording.c +++ b/recording.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: recording.c 1.62 2002/05/13 16:31:21 kls Exp $ + * $Id: recording.c 1.63 2002/06/16 11:29:27 kls Exp $ */ #include "recording.h" @@ -16,6 +16,7 @@ #include #include "i18n.h" #include "interface.h" +#include "remux.h" //XXX+ I_FRAME #include "tools.h" #include "videodir.h" @@ -732,3 +733,350 @@ void cRecordingUserCommand::InvokeCommand(const char *State, const char *Recordi delete cmd; } } + +// --- XXX+ + +//XXX+ somewhere else??? +// --- cIndexFile ------------------------------------------------------------ + +#define INDEXFILESUFFIX "/index.vdr" + +// The maximum time to wait before giving up while catching up on an index file: +#define MAXINDEXCATCHUP 2 // seconds + +cIndexFile::cIndexFile(const char *FileName, bool Record) +:resumeFile(FileName) +{ + f = -1; + fileName = NULL; + size = 0; + last = -1; + index = NULL; + if (FileName) { + fileName = new char[strlen(FileName) + strlen(INDEXFILESUFFIX) + 1]; + if (fileName) { + strcpy(fileName, FileName); + char *pFileExt = fileName + strlen(fileName); + strcpy(pFileExt, INDEXFILESUFFIX); + int delta = 0; + if (access(fileName, R_OK) == 0) { + struct stat buf; + if (stat(fileName, &buf) == 0) { + delta = buf.st_size % sizeof(tIndex); + if (delta) { + delta = sizeof(tIndex) - delta; + esyslog("ERROR: invalid file size (%ld) in '%s'", buf.st_size, fileName); + } + last = (buf.st_size + delta) / sizeof(tIndex) - 1; + if (!Record && last >= 0) { + size = last + 1; + index = new tIndex[size]; + if (index) { + f = open(fileName, O_RDONLY); + if (f >= 0) { + if ((int)safe_read(f, index, buf.st_size) != buf.st_size) { + esyslog("ERROR: can't read from file '%s'", fileName); + delete index; + index = NULL; + close(f); + f = -1; + } + // we don't close f here, see CatchUp()! + } + else + LOG_ERROR_STR(fileName); + } + else + esyslog("ERROR: can't allocate %d bytes for index '%s'", size * sizeof(tIndex), fileName); + } + } + else + LOG_ERROR; + } + else if (!Record) + isyslog("missing index file %s", fileName); + if (Record) { + if ((f = open(fileName, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) >= 0) { + if (delta) { + esyslog("ERROR: padding index file with %d '0' bytes", delta); + while (delta--) + writechar(f, 0); + } + } + else + LOG_ERROR_STR(fileName); + } + } + else + esyslog("ERROR: can't copy file name '%s'", FileName); + } +} + +cIndexFile::~cIndexFile() +{ + if (f >= 0) + close(f); + delete fileName; + delete index; +} + +bool cIndexFile::CatchUp(int Index) +{ + if (index && f >= 0) { + for (int i = 0; i <= MAXINDEXCATCHUP && (Index < 0 || Index >= last); i++) { + struct stat buf; + if (fstat(f, &buf) == 0) { + int newLast = buf.st_size / sizeof(tIndex) - 1; + if (newLast > last) { + if (size <= newLast) { + size *= 2; + if (size <= newLast) + size = newLast + 1; + } + index = (tIndex *)realloc(index, size * sizeof(tIndex)); + if (index) { + int offset = (last + 1) * sizeof(tIndex); + int delta = (newLast - last) * sizeof(tIndex); + if (lseek(f, offset, SEEK_SET) == offset) { + if (safe_read(f, &index[last + 1], delta) != delta) { + esyslog("ERROR: can't read from index"); + delete index; + index = NULL; + close(f); + f = -1; + break; + } + last = newLast; + } + else + LOG_ERROR_STR(fileName); + } + else + esyslog("ERROR: can't realloc() index"); + } + } + else + LOG_ERROR_STR(fileName); + if (Index >= last) + sleep(1); + else + return true; + } + } + return false; +} + +bool cIndexFile::Write(uchar PictureType, uchar FileNumber, int FileOffset) +{ + if (f >= 0) { + tIndex i = { FileOffset, PictureType, FileNumber, 0 }; + if (safe_write(f, &i, sizeof(i)) < 0) { + LOG_ERROR_STR(fileName); + close(f); + f = -1; + return false; + } + last++; + } + return f >= 0; +} + +bool cIndexFile::Get(int Index, uchar *FileNumber, int *FileOffset, uchar *PictureType, int *Length) +{ + if (index) { + CatchUp(Index); + if (Index >= 0 && Index <= last) { + *FileNumber = index[Index].number; + *FileOffset = index[Index].offset; + if (PictureType) + *PictureType = index[Index].type; + if (Length) { + int fn = index[Index + 1].number; + int fo = index[Index + 1].offset; + if (fn == *FileNumber) + *Length = fo - *FileOffset; + else + *Length = -1; // this means "everything up to EOF" (the buffer's Read function will act accordingly) + } + return true; + } + } + return false; +} + +int cIndexFile::GetNextIFrame(int Index, bool Forward, uchar *FileNumber, int *FileOffset, int *Length, bool StayOffEnd) +{ + if (index) { + CatchUp(); + int d = Forward ? 1 : -1; + for (;;) { + Index += d; + if (Index >= 0 && Index < last - ((Forward && StayOffEnd) ? 100 : 0)) { + if (index[Index].type == I_FRAME) { + if (FileNumber) + *FileNumber = index[Index].number; + else + FileNumber = &index[Index].number; + if (FileOffset) + *FileOffset = index[Index].offset; + else + FileOffset = &index[Index].offset; + if (Length) { + // all recordings end with a non-I_FRAME, so the following should be safe: + int fn = index[Index + 1].number; + int fo = index[Index + 1].offset; + if (fn == *FileNumber) + *Length = fo - *FileOffset; + else { + esyslog("ERROR: 'I' frame at end of file #%d", *FileNumber); + *Length = -1; + } + } + return Index; + } + } + else + break; + } + } + return -1; +} + +int cIndexFile::Get(uchar FileNumber, int FileOffset) +{ + if (index) { + CatchUp(); + //TODO implement binary search! + int i; + for (i = 0; i < last; i++) { + if (index[i].number > FileNumber || (index[i].number == FileNumber) && index[i].offset >= FileOffset) + break; + } + return i; + } + return -1; +} + +// --- cFileName ------------------------------------------------------------- + +#include +#include +#include "videodir.h" + +#define MAXFILESPERRECORDING 255 +#define RECORDFILESUFFIX "/%03d.vdr" +#define RECORDFILESUFFIXLEN 20 // some additional bytes for safety... + +cFileName::cFileName(const char *FileName, bool Record, bool Blocking) +{ + file = -1; + fileNumber = 0; + record = Record; + blocking = Blocking; + // Prepare the file name: + fileName = new char[strlen(FileName) + RECORDFILESUFFIXLEN]; + if (!fileName) { + esyslog("ERROR: can't copy file name '%s'", fileName); + return; + } + strcpy(fileName, FileName); + pFileNumber = fileName + strlen(fileName); + SetOffset(1); +} + +cFileName::~cFileName() +{ + Close(); + delete fileName; +} + +int cFileName::Open(void) +{ + if (file < 0) { + int BlockingFlag = blocking ? 0 : O_NONBLOCK; + if (record) { + dsyslog("recording to '%s'", fileName); + file = OpenVideoFile(fileName, O_RDWR | O_CREAT | BlockingFlag); + if (file < 0) + LOG_ERROR_STR(fileName); + } + else { + if (access(fileName, R_OK) == 0) { + dsyslog("playing '%s'", fileName); + file = open(fileName, O_RDONLY | BlockingFlag); + if (file < 0) + LOG_ERROR_STR(fileName); + } + else if (errno != ENOENT) + LOG_ERROR_STR(fileName); + } + } + return file; +} + +void cFileName::Close(void) +{ + if (file >= 0) { + if ((record && CloseVideoFile(file) < 0) || (!record && close(file) < 0)) + LOG_ERROR_STR(fileName); + file = -1; + } +} + +int cFileName::SetOffset(int Number, int Offset) +{ + if (fileNumber != Number) + Close(); + if (0 < Number && Number <= MAXFILESPERRECORDING) { + fileNumber = Number; + sprintf(pFileNumber, RECORDFILESUFFIX, fileNumber); + if (record) { + if (access(fileName, F_OK) == 0) // file exists, let's try next suffix + return SetOffset(Number + 1); + else if (errno != ENOENT) { // something serious has happened + LOG_ERROR_STR(fileName); + return -1; + } + // found a non existing file suffix + } + if (Open() >= 0) { + if (!record && Offset >= 0 && lseek(file, Offset, SEEK_SET) != Offset) { + LOG_ERROR_STR(fileName); + return -1; + } + } + return file; + } + esyslog("ERROR: max number of files (%d) exceeded", MAXFILESPERRECORDING); + return -1; +} + +int cFileName::NextFile(void) +{ + return SetOffset(fileNumber + 1); +} + +const char *IndexToHMSF(int Index, bool WithFrame) +{ + static char buffer[16]; + int f = (Index % FRAMESPERSEC) + 1; + int s = (Index / FRAMESPERSEC); + int m = s / 60 % 60; + int h = s / 3600; + s %= 60; + snprintf(buffer, sizeof(buffer), WithFrame ? "%d:%02d:%02d.%02d" : "%d:%02d:%02d", h, m, s, f); + return buffer; +} + +int HMSFToIndex(const char *HMSF) +{ + int h, m, s, f = 0; + if (3 <= sscanf(HMSF, "%d:%d:%d.%d", &h, &m, &s, &f)) + return (h * 3600 + m * 60 + s) * FRAMESPERSEC + f - 1; + return 0; +} + +int SecondsToFrames(int Seconds) +{ + return Seconds * FRAMESPERSEC; +} diff --git a/recording.h b/recording.h index 38626f85..16c6506f 100644 --- a/recording.h +++ b/recording.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: recording.h 1.22 2002/02/03 11:59:49 kls Exp $ + * $Id: recording.h 1.23 2002/06/16 11:29:47 kls Exp $ */ #ifndef __RECORDING_H @@ -104,4 +104,63 @@ public: static void InvokeCommand(const char *State, const char *RecordingFileName); }; +//XXX+ +#define FRAMESPERSEC 25 + +// The maximum file size is limited by the range that can be covered +// with 'int'. 4GB might be possible (if the range is considered +// 'unsigned'), 2GB should be possible (even if the range is considered +// 'signed'), so let's use 2000MB for absolute safety (the actual file size +// may be slightly higher because we stop recording only before the next +// 'I' frame, to have a complete Group Of Pictures): +#define MAXVIDEOFILESIZE 2000 // MB +#define MINVIDEOFILESIZE 100 // MB + +class cIndexFile { +private: + struct tIndex { int offset; uchar type; uchar number; short reserved; }; + int f; + char *fileName; + int size, last; + tIndex *index; + cResumeFile resumeFile; + bool CatchUp(int Index = -1); +public: + cIndexFile(const char *FileName, bool Record); + ~cIndexFile(); + bool Ok(void) { return index != NULL; } + bool Write(uchar PictureType, uchar FileNumber, int FileOffset); + bool Get(int Index, uchar *FileNumber, int *FileOffset, uchar *PictureType = NULL, int *Length = NULL); + int GetNextIFrame(int Index, bool Forward, uchar *FileNumber = NULL, int *FileOffset = NULL, int *Length = NULL, bool StayOffEnd = false); + int Get(uchar FileNumber, int FileOffset); + int Last(void) { CatchUp(); return last; } + int GetResume(void) { return resumeFile.Read(); } + bool StoreResume(int Index) { return resumeFile.Save(Index); } + }; + +class cFileName { +private: + int file; + int fileNumber; + char *fileName, *pFileNumber; + bool record; + bool blocking; +public: + cFileName(const char *FileName, bool Record, bool Blocking = false); + ~cFileName(); + const char *Name(void) { return fileName; } + int Number(void) { return fileNumber; } + int Open(void); + void Close(void); + int SetOffset(int Number, int Offset = 0); + int NextFile(void); + }; + +const char *IndexToHMSF(int Index, bool WithFrame = false); + // Converts the given index to a string, optionally containing the frame number. +int HMSFToIndex(const char *HMSF); + // Converts the given string (format: "hh:mm:ss.ff") to an index. +int SecondsToFrames(int Seconds); //XXX+ ->player??? + // Returns the number of frames corresponding to the given number of seconds. + #endif //__RECORDING_H diff --git a/ringbuffer.c b/ringbuffer.c index d7562f96..421f19f0 100644 --- a/ringbuffer.c +++ b/ringbuffer.c @@ -1,5 +1,5 @@ /* - * ringbuffer.c: A threaded ring buffer + * ringbuffer.c: A ring buffer * * See the main source file 'vdr.c' for copyright information and * how to reach the author. @@ -7,50 +7,25 @@ * Parts of this file were inspired by the 'ringbuffy.c' from the * LinuxDVB driver (see linuxtv.org). * - * $Id: ringbuffer.c 1.8 2002/05/18 08:54:52 kls Exp $ + * $Id: ringbuffer.c 1.9 2002/06/16 11:24:40 kls Exp $ */ #include "ringbuffer.h" +#include #include "tools.h" -// --- cRingBufferInputThread ------------------------------------------------- - -class cRingBufferInputThread : public cThread { -private: - cRingBuffer *ringBuffer; -protected: - virtual void Action(void) { ringBuffer->Input(); } -public: - cRingBufferInputThread(cRingBuffer *RingBuffer) { ringBuffer = RingBuffer; } - }; - -// --- cRingBufferOutputThread ------------------------------------------------ - -class cRingBufferOutputThread : public cThread { -private: - cRingBuffer *ringBuffer; -protected: - virtual void Action(void) { ringBuffer->Output(); } -public: - cRingBufferOutputThread(cRingBuffer *RingBuffer) { ringBuffer = RingBuffer; } - }; - -// --- cRingBuffer ------------------------------------------------------------ +// --- cRingBuffer ----------------------------------------------------------- cRingBuffer::cRingBuffer(int Size, bool Statistics) { size = Size; statistics = Statistics; - inputThread = NULL; - outputThread = NULL; - busy = false; maxFill = 0; + lastPercent = 0; } cRingBuffer::~cRingBuffer() { - delete inputThread; - delete outputThread; if (statistics) dsyslog("buffer stats: %d (%d%%) used", maxFill, maxFill * 100 / (size - 1)); } @@ -79,45 +54,13 @@ void cRingBuffer::EnableGet(void) readyForGet.Broadcast(); } -bool cRingBuffer::Start(void) -{ - if (!busy) { - busy = true; - outputThread = new cRingBufferOutputThread(this); - if (!outputThread->Start()) - DELETENULL(outputThread); - inputThread = new cRingBufferInputThread(this); - if (!inputThread->Start()) { - DELETENULL(inputThread); - DELETENULL(outputThread); - } - busy = outputThread && inputThread; - } - return busy; -} - -bool cRingBuffer::Active(void) -{ - return outputThread && outputThread->Active() && inputThread && inputThread->Active(); -} - -void cRingBuffer::Stop(void) -{ - busy = false; - for (time_t t0 = time(NULL) + 3; time(NULL) < t0; ) { - if (!((outputThread && outputThread->Active()) || (inputThread && inputThread->Active()))) - break; - } - DELETENULL(inputThread); - DELETENULL(outputThread); -} - -// --- cRingBufferLinear ---------------------------------------------------- +// --- cRingBufferLinear ----------------------------------------------------- cRingBufferLinear::cRingBufferLinear(int Size, bool Statistics) :cRingBuffer(Size, Statistics) { buffer = NULL; + getThreadPid = -1; if (Size > 1) { // 'Size - 1' must not be 0! buffer = new uchar[Size]; if (!buffer) @@ -146,6 +89,8 @@ void cRingBufferLinear::Clear(void) Lock(); head = tail = 0; Unlock(); + EnablePut(); + EnableGet(); } int cRingBufferLinear::Put(const uchar *Data, int Count) @@ -159,11 +104,13 @@ int cRingBufferLinear::Put(const uchar *Data, int Count) int fill = Size() - free - 1 + Count; if (fill >= Size()) fill = Size() - 1; - if (fill > maxFill) { + if (fill > maxFill) maxFill = fill; - int percent = maxFill * 100 / (Size() - 1); + int percent = maxFill * 100 / (Size() - 1) / 5 * 5; + if (abs(lastPercent - percent) >= 5) { if (percent > 75) - dsyslog("buffer usage: %d%%", percent); + dsyslog("buffer usage: %d%% (pid=%d)", percent, getThreadPid); + lastPercent = percent; } } if (free > 0) { @@ -185,6 +132,7 @@ int cRingBufferLinear::Put(const uchar *Data, int Count) else Count = 0; Unlock(); + EnableGet(); } return Count; } @@ -193,6 +141,8 @@ int cRingBufferLinear::Get(uchar *Data, int Count) { if (Count > 0) { Lock(); + if (getThreadPid < 0) + getThreadPid = getpid(); int rest = Size() - tail; int diff = head - tail; int cont = (diff >= 0) ? diff : Size() + diff; @@ -213,6 +163,8 @@ int cRingBufferLinear::Get(uchar *Data, int Count) else Count = 0; Unlock(); + if (Count == 0) + WaitForGet(); } return Count; } @@ -255,7 +207,7 @@ void cRingBufferFrame::Clear(void) { Lock(); const cFrame *p; - while ((p = Get(false)) != NULL) + while ((p = Get()) != NULL) Drop(p); Unlock(); EnablePut(); @@ -279,17 +231,14 @@ bool cRingBufferFrame::Put(cFrame *Frame) EnableGet(); return true; } - WaitForPut(); return false; } -const cFrame *cRingBufferFrame::Get(bool Wait) +const cFrame *cRingBufferFrame::Get(void) { Lock(); cFrame *p = head ? head->next : NULL; Unlock(); - if (!p && Wait) - WaitForGet(); return p; } diff --git a/ringbuffer.h b/ringbuffer.h index 7e1025b1..43176bde 100644 --- a/ringbuffer.h +++ b/ringbuffer.h @@ -1,10 +1,10 @@ /* - * ringbuffer.h: A threaded ring buffer + * ringbuffer.h: A ring buffer * * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: ringbuffer.h 1.5 2001/11/03 10:41:33 kls Exp $ + * $Id: ringbuffer.h 1.6 2002/06/16 11:30:07 kls Exp $ */ #ifndef __RINGBUFFER_H @@ -12,24 +12,17 @@ #include "thread.h" -typedef unsigned char uchar; - -class cRingBufferInputThread; -class cRingBufferOutputThread; +typedef unsigned char uchar;//XXX+ class cRingBuffer { - friend class cRingBufferInputThread; - friend class cRingBufferOutputThread; private: - cRingBufferInputThread *inputThread; - cRingBufferOutputThread *outputThread; cMutex mutex; cCondVar readyForPut, readyForGet; cMutex putMutex, getMutex; int size; - bool busy; protected: int maxFill;//XXX + int lastPercent; bool statistics;//XXX void WaitForPut(void); void WaitForGet(void); @@ -41,26 +34,19 @@ protected: void Lock(void) { mutex.Lock(); } void Unlock(void) { mutex.Unlock(); } int Size(void) { return size; } - bool Busy(void) { return busy; } - virtual void Input(void) = 0; - // Runs as a separate thread and shall continuously read data from - // a source and call Put() to store the data in the ring buffer. - virtual void Output(void) = 0; - // Runs as a separate thread and shall continuously call Get() to - // retrieve data from the ring buffer and write it to a destination. public: cRingBuffer(int Size, bool Statistics = false); virtual ~cRingBuffer(); - bool Start(void); - bool Active(void); - void Stop(void); }; class cRingBufferLinear : public cRingBuffer { private: int head, tail; uchar *buffer; -protected: + pid_t getThreadPid; +public: + cRingBufferLinear(int Size, bool Statistics = false); + virtual ~cRingBufferLinear(); virtual int Available(void); virtual void Clear(void); // Immediately clears the ring buffer. @@ -70,9 +56,6 @@ protected: int Get(uchar *Data, int Count); // Gets at most Count bytes of Data from the ring buffer. // Returns the number of bytes actually retrieved. -public: - cRingBufferLinear(int Size, bool Statistics = false); - virtual ~cRingBufferLinear(); }; enum eFrameType { ftUnknown, ftVideo, ftAudio, ftDolby }; @@ -99,21 +82,20 @@ private: cFrame *head; int currentFill; void Delete(const cFrame *Frame); -protected: +public: + cRingBufferFrame(int Size, bool Statistics = false); + virtual ~cRingBufferFrame(); virtual int Available(void); virtual void Clear(void); // Immediately clears the ring buffer. bool Put(cFrame *Frame); // Puts the Frame into the ring buffer. // Returns true if this was possible. - const cFrame *Get(bool Wait = true); + const cFrame *Get(void); // Gets the next frame from the ring buffer. // The actual data still remains in the buffer until Drop() is called. void Drop(const cFrame *Frame); // Drops the Frame that has just been fetched with Get(). -public: - cRingBufferFrame(int Size, bool Statistics = false); - virtual ~cRingBufferFrame(); }; #endif // __RINGBUFFER_H diff --git a/status.c b/status.c index cc01ad3b..5f3ae7d4 100644 --- a/status.c +++ b/status.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: status.c 1.1 2002/05/19 14:54:30 kls Exp $ + * $Id: status.c 1.2 2002/06/16 12:10:44 kls Exp $ */ #include "status.h" @@ -23,22 +23,22 @@ cStatusMonitor::~cStatusMonitor() statusMonitors.Del(this, false); } -void cStatusMonitor::MsgChannelSwitch(const cDvbApi *DvbApi, int ChannelNumber) +void cStatusMonitor::MsgChannelSwitch(const cDevice *Device, int ChannelNumber) { for (cStatusMonitor *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) - sm->ChannelSwitch(DvbApi, ChannelNumber); + sm->ChannelSwitch(Device, ChannelNumber); } -void cStatusMonitor::MsgRecording(const cDvbApi *DvbApi, const char *Name) +void cStatusMonitor::MsgRecording(const cDevice *Device, const char *Name) { for (cStatusMonitor *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) - sm->Recording(DvbApi, Name); + sm->Recording(Device, Name); } -void cStatusMonitor::MsgReplaying(const cDvbApi *DvbApi, const char *Name) +void cStatusMonitor::MsgReplaying(const cDvbPlayerControl *DvbPlayerControl, const char *Name) { for (cStatusMonitor *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm)) - sm->Replaying(DvbApi, Name); + sm->Replaying(DvbPlayerControl, Name); } void cStatusMonitor::MsgSetVolume(int Volume, bool Absolute) diff --git a/status.h b/status.h index db842a28..e478ee1d 100644 --- a/status.h +++ b/status.h @@ -4,14 +4,15 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: status.h 1.1 2002/05/19 14:54:15 kls Exp $ + * $Id: status.h 1.2 2002/06/16 12:09:55 kls Exp $ */ #ifndef __STATUS_H #define __STATUS_H #include "config.h" -#include "dvbapi.h" +#include "device.h" +#include "dvbplayer.h" #include "tools.h" class cStatusMonitor : public cListObject { @@ -19,15 +20,15 @@ private: static cList statusMonitors; protected: // These functions can be implemented by derived classes to receive status information: - virtual void ChannelSwitch(const cDvbApi *DvbApi, int ChannelNumber) {} - // Indicates a channel switch on DVB device DvbApi. + virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber) {} + // Indicates a channel switch on the given DVB device. // If ChannelNumber is 0, this is before the channel is being switched, // otherwise ChannelNumber is the number of the channel that has been switched to. - virtual void Recording(const cDvbApi *DvbApi, const char *Name) {} - // DVB device DvbApi has started recording Name. Name is the full directory + virtual void Recording(const cDevice *Device, const char *Name) {} + // The given DVB device has started recording Name. Name is the full directory // name of the recording. If Name is NULL, the recording has ended. - virtual void Replaying(const cDvbApi *DvbApi, const char *Name) {} - // DVB device DvbApi has started replaying Name. Name is the full directory + virtual void Replaying(const cDvbPlayerControl *DvbPlayerControl, const char *Name) {} + // The given player control has started replaying Name. Name is the full directory // name of the recording. If Name is NULL, the replay has ended. virtual void SetVolume(int Volume, bool Absolute) {} // The volume has been set to the given value, either @@ -57,9 +58,9 @@ public: cStatusMonitor(void); virtual ~cStatusMonitor(); // These functions are called whenever the related status information changes: - static void MsgChannelSwitch(const cDvbApi *DvbApi, int ChannelNumber); - static void MsgRecording(const cDvbApi *DvbApi, const char *Name); - static void MsgReplaying(const cDvbApi *DvbApi, const char *Name); + static void MsgChannelSwitch(const cDevice *Device, int ChannelNumber); + static void MsgRecording(const cDevice *Device, const char *Name); + static void MsgReplaying(const cDvbPlayerControl *DvbPlayerControl, const char *Name); static void MsgSetVolume(int Volume, bool Absolute); static void MsgOsdClear(void); static void MsgOsdTitle(const char *Title); diff --git a/svdrp.c b/svdrp.c index ff3a775a..5290c9a6 100644 --- a/svdrp.c +++ b/svdrp.c @@ -10,7 +10,7 @@ * and interact with the Video Disk Recorder - or write a full featured * graphical interface that sits on top of an SVDRP connection. * - * $Id: svdrp.c 1.37 2002/05/13 16:32:05 kls Exp $ + * $Id: svdrp.c 1.38 2002/06/10 16:30:00 kls Exp $ */ #include "svdrp.h" @@ -27,7 +27,7 @@ #include #include #include "config.h" -#include "dvbapi.h" +#include "device.h" #include "interface.h" #include "tools.h" @@ -390,12 +390,12 @@ void cSVDRP::CmdCHAN(const char *Option) n = o; } else if (strcmp(Option, "-") == 0) { - n = cDvbApi::CurrentChannel(); + n = cDevice::CurrentChannel(); if (n > 1) n--; } else if (strcmp(Option, "+") == 0) { - n = cDvbApi::CurrentChannel(); + n = cDevice::CurrentChannel(); if (n < Channels.MaxNumber()) n++; } @@ -430,11 +430,11 @@ void cSVDRP::CmdCHAN(const char *Option) return; } } - cChannel *channel = Channels.GetByNumber(cDvbApi::CurrentChannel()); + cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel()); if (channel) Reply(250, "%d %s", channel->number, channel->name); else - Reply(550, "Unable to find channel \"%d\"", cDvbApi::CurrentChannel()); + Reply(550, "Unable to find channel \"%d\"", cDevice::CurrentChannel()); } void cSVDRP::CmdDELC(const char *Option) @@ -541,7 +541,7 @@ void cSVDRP::CmdGRAB(const char *Option) Reply(501, "Unexpected parameter \"%s\"", p); return; } - if (cDvbApi::PrimaryDvbApi->GrabImage(FileName, Jpeg, Quality, SizeX, SizeY)) + if (cDevice::PrimaryDevice()->GrabImage(FileName, Jpeg, Quality, SizeX, SizeY)) Reply(250, "Grabbed image %s", Option); else Reply(451, "Grab image failed"); @@ -926,22 +926,22 @@ void cSVDRP::CmdVOLU(const char *Option) { if (*Option) { if (isnumber(Option)) - cDvbApi::PrimaryDvbApi->SetVolume(strtol(Option, NULL, 10), true); + cDevice::PrimaryDevice()->SetVolume(strtol(Option, NULL, 10), true); else if (strcmp(Option, "+") == 0) - cDvbApi::PrimaryDvbApi->SetVolume(VOLUMEDELTA); + cDevice::PrimaryDevice()->SetVolume(VOLUMEDELTA); else if (strcmp(Option, "-") == 0) - cDvbApi::PrimaryDvbApi->SetVolume(-VOLUMEDELTA); + cDevice::PrimaryDevice()->SetVolume(-VOLUMEDELTA); else if (strcasecmp(Option, "MUTE") == 0) - cDvbApi::PrimaryDvbApi->ToggleMute(); + cDevice::PrimaryDevice()->ToggleMute(); else { Reply(501, "Unknown option: \"%s\"", Option); return; } } - if (cDvbApi::PrimaryDvbApi->IsMute()) + if (cDevice::PrimaryDevice()->IsMute()) Reply(250, "Audio is mute"); else - Reply(250, "Audio volume is %d", cDvbApi::CurrentVolume()); + Reply(250, "Audio volume is %d", cDevice::CurrentVolume()); } #define CMD(c) (strcasecmp(Cmd, c) == 0) diff --git a/thread.c b/thread.c index 925fc4d9..3db0f908 100644 --- a/thread.c +++ b/thread.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: thread.c 1.20 2002/05/13 16:32:09 kls Exp $ + * $Id: thread.c 1.21 2002/06/10 16:30:00 kls Exp $ */ #include "thread.h" @@ -147,6 +147,7 @@ bool cThread::Active(void) void cThread::Cancel(int WaitSeconds) { + running = false; if (WaitSeconds > 0) { for (time_t t0 = time(NULL) + WaitSeconds; time(NULL) < t0; ) { if (!Active()) diff --git a/thread.h b/thread.h index 3ac398a2..d9c8f3a6 100644 --- a/thread.h +++ b/thread.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: thread.h 1.12 2002/02/23 13:53:38 kls Exp $ + * $Id: thread.h 1.13 2002/06/10 16:30:00 kls Exp $ */ #ifndef __THREAD_H @@ -54,9 +54,9 @@ private: static bool signalHandlerInstalled; static void SignalHandler(int signum); static void *StartThread(cThread *Thread); +protected: void Lock(void) { mutex.Lock(); } void Unlock(void) { mutex.Unlock(); } -protected: void WakeUp(void); virtual void Action(void) = 0; void Cancel(int WaitSeconds = 0); diff --git a/tools.h b/tools.h index 0c29e7cb..e107f185 100644 --- a/tools.h +++ b/tools.h @@ -4,13 +4,13 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: tools.h 1.46 2002/05/18 15:10:10 kls Exp $ + * $Id: tools.h 1.47 2002/06/10 16:30:00 kls Exp $ */ #ifndef __TOOLS_H #define __TOOLS_H -//#include +#include #include #include #include @@ -18,6 +18,8 @@ #include #include +typedef unsigned char uchar; + extern int SysLogLevel; #define esyslog(a...) void( (SysLogLevel > 0) ? syslog(LOG_ERR, a) : void() ) @@ -36,6 +38,9 @@ extern int SysLogLevel; #define DELETENULL(p) (delete (p), p = NULL) +#define CHECK(s) { if ((s) < 0) LOG_ERROR; } // used for 'ioctl()' calls +#define FATALERRNO (errno != EAGAIN && errno != EINTR) + template inline T min(T a, T b) { return a <= b ? a : b; } template inline T max(T a, T b) { return a >= b ? a : b; } template inline void swap(T &a, T &b) { T t = a; a = b; b = t; } diff --git a/vdr.c b/vdr.c index afad5ffa..bef3919f 100644 --- a/vdr.c +++ b/vdr.c @@ -22,7 +22,7 @@ * * The project's page is at http://www.cadsoft.de/people/kls/vdr * - * $Id: vdr.c 1.113 2002/05/20 11:02:10 kls Exp $ + * $Id: vdr.c 1.114 2002/06/16 11:30:28 kls Exp $ */ #include @@ -31,7 +31,7 @@ #include #include #include "config.h" -#include "dvbapi.h" +#include "device.h" #include "eitscan.h" #include "i18n.h" #include "interface.h" @@ -118,15 +118,17 @@ int main(int argc, char *argv[]) int c; while ((c = getopt_long(argc, argv, "a:c:dD:E:hl:L:mp:P:r:s:t:v:Vw:", long_options, NULL)) != -1) { switch (c) { - case 'a': cDvbApi::SetAudioCommand(optarg); + /*XXX+ + case 'a': cDevice::SetAudioCommand(optarg); break; + XXX*/ case 'c': ConfigDirectory = optarg; break; case 'd': DaemonMode = true; break; case 'D': if (isnumber(optarg)) { int n = atoi(optarg); - if (0 <= n && n < MAXDVBAPI) { - cDvbApi::SetUseDvbApi(n); + if (0 <= n && n < MAXDEVICES) { + cDevice::SetUseDevice(n); break; } } @@ -323,10 +325,10 @@ int main(int argc, char *argv[]) // DVB interfaces: - if (!cDvbApi::Initialize()) + if (!cDevice::Initialize()) return 2; - cDvbApi::SetPrimaryDvbApi(Setup.PrimaryDVB); + cDevice::SetPrimaryDevice(Setup.PrimaryDVB); cSIProcessor::Read(); @@ -343,9 +345,9 @@ int main(int argc, char *argv[]) Channels.SwitchTo(Setup.CurrentChannel); if (MuteAudio) - cDvbApi::PrimaryDvbApi->ToggleMute(); + cDevice::PrimaryDevice()->ToggleMute(); else - cDvbApi::PrimaryDvbApi->SetVolume(Setup.CurrentVolume, true); + cDevice::PrimaryDevice()->SetVolume(Setup.CurrentVolume, true); cEITScanner EITScanner; @@ -371,7 +373,7 @@ int main(int argc, char *argv[]) cOsdObject *Menu = NULL; cReplayControl *ReplayControl = NULL; int LastChannel = -1; - int PreviousChannel = cDvbApi::CurrentChannel(); + int PreviousChannel = cDevice::CurrentChannel(); time_t LastActivity = 0; int MaxLatencyTime = 0; bool ForceShutdown = false; @@ -396,12 +398,12 @@ int main(int argc, char *argv[]) } } // Channel display: - if (!EITScanner.Active() && cDvbApi::CurrentChannel() != LastChannel) { + if (!EITScanner.Active() && cDevice::CurrentChannel() != LastChannel) { if (!Menu) - Menu = new cDisplayChannel(cDvbApi::CurrentChannel(), LastChannel > 0); + Menu = new cDisplayChannel(cDevice::CurrentChannel(), LastChannel > 0); if (LastChannel > 0) PreviousChannel = LastChannel; - LastChannel = cDvbApi::CurrentChannel(); + LastChannel = cDevice::CurrentChannel(); } // Timers and Recordings: if (!Menu) { @@ -429,11 +431,11 @@ int main(int argc, char *argv[]) case kVolDn: case kMute: if (key == kMute) { - if (!cDvbApi::PrimaryDvbApi->ToggleMute() && !Menu) + if (!cDevice::PrimaryDevice()->ToggleMute() && !Menu) break; // no need to display "mute off" } else - cDvbApi::PrimaryDvbApi->SetVolume(NORMALKEY(key) == kVolDn ? -VOLUMEDELTA : VOLUMEDELTA); + cDevice::PrimaryDevice()->SetVolume(NORMALKEY(key) == kVolDn ? -VOLUMEDELTA : VOLUMEDELTA); if (!Menu && (!ReplayControl || !ReplayControl->Visible())) Menu = cDisplayVolume::Create(); cDisplayVolume::Process(key); @@ -477,7 +479,7 @@ int main(int argc, char *argv[]) case osSwitchDvb: DELETENULL(*Interact); Interface->Info(tr("Switching primary DVB...")); - cDvbApi::SetPrimaryDvbApi(Setup.PrimaryDVB); + cDevice::SetPrimaryDevice(Setup.PrimaryDVB); break; case osBack: case osEnd: DELETENULL(*Interact); @@ -490,7 +492,7 @@ int main(int argc, char *argv[]) switch (key) { // Toggle channels: case k0: { - int CurrentChannel = cDvbApi::CurrentChannel(); + int CurrentChannel = cDevice::CurrentChannel(); Channels.SwitchTo(PreviousChannel); PreviousChannel = CurrentChannel; break; @@ -511,7 +513,7 @@ int main(int argc, char *argv[]) case kUp: case kDown|k_Repeat: case kDown: { - int n = cDvbApi::CurrentChannel() + (NORMALKEY(key) == kUp ? 1 : -1); + int n = cDevice::CurrentChannel() + (NORMALKEY(key) == kUp ? 1 : -1); cChannel *channel = Channels.GetByNumber(n); if (channel) channel->Switch(); @@ -527,14 +529,16 @@ int main(int argc, char *argv[]) } if (!Menu) { EITScanner.Process(); + /*XXX+ if (!cVideoCutter::Active() && cVideoCutter::Ended()) { if (cVideoCutter::Error()) Interface->Error(tr("Editing process failed!")); else Interface->Info(tr("Editing process finished")); } + XXX*/ } - if (!*Interact && ((!cRecordControls::Active() && !cVideoCutter::Active()) || ForceShutdown)) { + if (!*Interact && ((!cRecordControls::Active() /*XXX+&& !cVideoCutter::Active()XXX*/) || ForceShutdown)) { time_t Now = time(NULL); if (Now - LastActivity > ACTIVITYTIMEOUT) { // Shutdown: @@ -593,16 +597,17 @@ int main(int argc, char *argv[]) } if (Interrupted) isyslog("caught signal %d", Interrupted); - cVideoCutter::Stop(); + cRecordControls::Shutdown(); + //XXX+cVideoCutter::Stop(); delete Menu; delete ReplayControl; delete Interface; cOsd::Shutdown(); PluginManager.Shutdown(true); - Setup.CurrentChannel = cDvbApi::CurrentChannel(); - Setup.CurrentVolume = cDvbApi::CurrentVolume(); + Setup.CurrentChannel = cDevice::CurrentChannel(); + Setup.CurrentVolume = cDevice::CurrentVolume(); Setup.Save(); - cDvbApi::Shutdown(); + cDevice::Shutdown(); if (WatchdogTimeout > 0) dsyslog("max. latency time %d seconds", MaxLatencyTime); isyslog("exiting");