diff options
-rw-r--r-- | HISTORY | 21 | ||||
-rw-r--r-- | Makefile | 9 | ||||
-rw-r--r-- | PLUGINS.html | 8 | ||||
-rw-r--r-- | audio.c | 11 | ||||
-rw-r--r-- | audio.h | 13 | ||||
-rw-r--r-- | config.c | 29 | ||||
-rw-r--r-- | config.h | 10 | ||||
-rw-r--r-- | device.c | 1017 | ||||
-rw-r--r-- | device.h | 199 | ||||
-rw-r--r-- | dvbplayer.c | 733 | ||||
-rw-r--r-- | dvbplayer.h | 60 | ||||
-rw-r--r-- | eit.c | 3 | ||||
-rw-r--r-- | eitscan.c | 20 | ||||
-rw-r--r-- | interface.c | 6 | ||||
-rw-r--r-- | menu.c | 212 | ||||
-rw-r--r-- | menu.h | 21 | ||||
-rw-r--r-- | osd.c | 6 | ||||
-rw-r--r-- | player.c | 55 | ||||
-rw-r--r-- | player.h | 51 | ||||
-rw-r--r-- | receiver.c | 55 | ||||
-rw-r--r-- | receiver.h | 48 | ||||
-rw-r--r-- | recorder.c | 149 | ||||
-rw-r--r-- | recorder.h | 43 | ||||
-rw-r--r-- | recording.c | 350 | ||||
-rw-r--r-- | recording.h | 61 | ||||
-rw-r--r-- | ringbuffer.c | 93 | ||||
-rw-r--r-- | ringbuffer.h | 42 | ||||
-rw-r--r-- | status.c | 14 | ||||
-rw-r--r-- | status.h | 23 | ||||
-rw-r--r-- | svdrp.c | 26 | ||||
-rw-r--r-- | thread.c | 3 | ||||
-rw-r--r-- | thread.h | 4 | ||||
-rw-r--r-- | tools.h | 9 | ||||
-rw-r--r-- | vdr.c | 51 |
34 files changed, 3144 insertions, 311 deletions
@@ -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. @@ -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()); } </pre></td></tr></table><p> 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 @@ -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 <ctype.h> #include <stdlib.h> -#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) { @@ -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 <string.h> #include <time.h> #include <unistd.h> -#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 <errno.h> +extern "C" { +#define HAVE_BOOLEAN +#include <jpeglib.h> +} +#include <linux/videodev.h> +#include <ost/sec.h> +#include <poll.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#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 <stdlib.h> // 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 <stdlib.h> here! +#include <ost/dmx.h> +#include <ost/frontend.h> +#include <ost/audio.h> +#include <ost/video.h> +#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 <poll.h> +#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 @@ -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 <iomanip.h> #include <iostream.h> #include <limits.h> +#include <ost/dmx.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -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(); } @@ -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; @@ -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; @@ -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 <string.h> -#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 <stdarg.h> +#include <stdio.h> +#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 <stdarg.h> +#include <stdio.h> +#include <unistd.h> +#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 <unistd.h> #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 <errno.h> +#include <unistd.h> +#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 <unistd.h> #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 @@ -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) @@ -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<cStatusMonitor> 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); @@ -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 <sys/time.h> #include <unistd.h> #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) @@ -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()) @@ -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); @@ -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 <errno.h> +#include <errno.h> #include <fcntl.h> #include <stdio.h> #include <string.h> @@ -18,6 +18,8 @@ #include <sys/stat.h> #include <sys/types.h> +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<class T> inline T min(T a, T b) { return a <= b ? a : b; } template<class T> inline T max(T a, T b) { return a >= b ? a : b; } template<class T> inline void swap(T &a, T &b) { T t = a; a = b; b = t; } @@ -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 <getopt.h> @@ -31,7 +31,7 @@ #include <stdlib.h> #include <unistd.h> #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"); |