summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--HISTORY21
-rw-r--r--Makefile9
-rw-r--r--PLUGINS.html8
-rw-r--r--audio.c11
-rw-r--r--audio.h13
-rw-r--r--config.c29
-rw-r--r--config.h10
-rw-r--r--device.c1017
-rw-r--r--device.h199
-rw-r--r--dvbplayer.c733
-rw-r--r--dvbplayer.h60
-rw-r--r--eit.c3
-rw-r--r--eitscan.c20
-rw-r--r--interface.c6
-rw-r--r--menu.c212
-rw-r--r--menu.h21
-rw-r--r--osd.c6
-rw-r--r--player.c55
-rw-r--r--player.h51
-rw-r--r--receiver.c55
-rw-r--r--receiver.h48
-rw-r--r--recorder.c149
-rw-r--r--recorder.h43
-rw-r--r--recording.c350
-rw-r--r--recording.h61
-rw-r--r--ringbuffer.c93
-rw-r--r--ringbuffer.h42
-rw-r--r--status.c14
-rw-r--r--status.h23
-rw-r--r--svdrp.c26
-rw-r--r--thread.c3
-rw-r--r--thread.h4
-rw-r--r--tools.h9
-rw-r--r--vdr.c51
34 files changed, 3144 insertions, 311 deletions
diff --git a/HISTORY b/HISTORY
index 48c4e882..36df4217 100644
--- a/HISTORY
+++ b/HISTORY
@@ -1301,7 +1301,7 @@ Video Disk Recorder Revision History
- Removed compiler option '-m486' to make it work on non-Intel platforms (thanks
to Alastair McKinstry for pointing this out).
-2002-05-26: Version 1.1.3
+2002-06-16: Version 1.1.3
- Improved the VDR Makefile to avoid a warning if the '.dependencies' file does
not exist, and also using $(MAKE) to call recursive makes.
@@ -1328,3 +1328,22 @@ Video Disk Recorder Revision History
- Removed compiler option '-m486' to make it work on non-Intel platforms. If you
have already started a plugin project, you may want to make sure you remove this
option from your existing Makefile.
+- Completely rearranged the recording and replay functions to make them available
+ to plugins.
+- Replay is now done in a single thread (no more syncing between input and output
+ thread necessary).
+- It is now possible to record several channels on the same transponder with "budget
+ cards". VDR automatically attaches a recording timer to a card that already
+ records on the appropriate transponder. How many parallel recordings can actually
+ be done depends on the computer's performance. Currently any number of recordings
+ gets attached to a card, so you should carefully plan your timers to not exceed
+ the limit. On a K6-II/450 it was possible to record three channels from transponder
+ 12480 with a single WinTV NOVA-S.
+- Timers that record two successive shows on the same channel may now overlap and
+ will use the same DVB card. During the time where both timers record the data
+ is simply saved to both files.
+- The following limitations apply to this version:
+ + Transfer mode doesn't work yet.
+ + The '-a' option (for Dolby Digital audio) doesn't work yet.
+ + Switching between different language tracks doesn't work yet.
+ + Cutting doesn't work yet.
diff --git a/Makefile b/Makefile
index d0964ba3..09654b21 100644
--- a/Makefile
+++ b/Makefile
@@ -4,7 +4,7 @@
# See the main source file 'vdr.c' for copyright information and
# how to reach the author.
#
-# $Id: Makefile 1.39 2002/06/10 16:26:51 kls Exp $
+# $Id: Makefile 1.40 2002/06/10 16:31:34 kls Exp $
.DELETE_ON_ERROR:
@@ -21,9 +21,10 @@ INCLUDES = -I$(DVBDIR)/ost/include
DTVLIB = $(DTVDIR)/libdtv.a
-OBJS = config.o dvbapi.o dvbosd.o eit.o eitscan.o font.o i18n.o interface.o menu.o\
- menuitems.o osdbase.o osd.o plugin.o recording.o remote.o remux.o ringbuffer.o\
- status.o svdrp.o thread.o tools.o vdr.o videodir.o
+OBJS = audio.o config.o device.o dvbplayer.o dvbosd.o eit.o eitscan.o font.o i18n.o\
+ interface.o menu.o menuitems.o osdbase.o osd.o player.o plugin.o receiver.o\
+ recorder.o recording.o remote.o remux.o ringbuffer.o status.o svdrp.o thread.o\
+ tools.o vdr.o videodir.o
OSDFONT = -adobe-helvetica-medium-r-normal--23-*-100-100-p-*-iso8859-1
FIXFONT = -adobe-courier-bold-r-normal--25-*-100-100-m-*-iso8859-1
diff --git a/PLUGINS.html b/PLUGINS.html
index f531f361..a0f7ebc9 100644
--- a/PLUGINS.html
+++ b/PLUGINS.html
@@ -864,15 +864,15 @@ If a plugin wants to get informed on various events in VDR, it can derive a clas
class cMyStatusMonitor : public cStatusMonitor {
protected:
- virtual void ChannelSwitch(const cDvbApi *DvbApi, int ChannelNumber);
+ virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber);
};
-void cMyStatusMonitor::ChannelSwitch(const cDvbApi *DvbApi, int ChannelNumber)
+void cMyStatusMonitor::ChannelSwitch(const cDevice *Device, int ChannelNumber)
{
if (ChannelNumber)
- dsyslog("channel switched to %d on DVB %d", ChannelNumber, DvbApi->CardIndex());
+ dsyslog("channel switched to %d on DVB %d", ChannelNumber, Device->CardIndex());
else
- dsyslog("about to switch channel on DVB %d", DvbApi->CardIndex());
+ dsyslog("about to switch channel on DVB %d", Device->CardIndex());
}
</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
diff --git a/config.c b/config.c
index f28d1a54..6753ce8d 100644
--- a/config.c
+++ b/config.c
@@ -4,16 +4,16 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: config.c 1.101 2002/05/13 16:28:12 kls Exp $
+ * $Id: config.c 1.102 2002/06/16 12:57:31 kls Exp $
*/
#include "config.h"
#include <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) {
diff --git a/config.h b/config.h
index 8ef6f69a..1d650803 100644
--- a/config.h
+++ b/config.h
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: config.h 1.117 2002/05/14 21:21:53 kls Exp $
+ * $Id: config.h 1.118 2002/06/10 16:30:00 kls Exp $
*/
#ifndef __CONFIG_H
@@ -15,7 +15,7 @@
#include <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
diff --git a/eit.c b/eit.c
index 7f1bacf4..8584c645 100644
--- a/eit.c
+++ b/eit.c
@@ -16,7 +16,7 @@
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
- * $Id: eit.c 1.45 2002/05/13 16:35:49 kls Exp $
+ * $Id: eit.c 1.46 2002/06/10 16:30:00 kls Exp $
***************************************************************************/
#include "eit.h"
@@ -26,6 +26,7 @@
#include <iomanip.h>
#include <iostream.h>
#include <limits.h>
+#include <ost/dmx.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
diff --git a/eitscan.c b/eitscan.c
index 88271822..21fbe74e 100644
--- a/eitscan.c
+++ b/eitscan.c
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: eitscan.c 1.1 2002/05/20 10:58:10 kls Exp $
+ * $Id: eitscan.c 1.2 2002/06/10 16:30:00 kls Exp $
*/
#include "eitscan.h"
@@ -48,11 +48,11 @@ void cEITScanner::Process(void)
if (Setup.EPGScanTimeout && Channels.MaxNumber() > 1) {
time_t now = time(NULL);
if (now - lastScan > ScanTimeout && now - lastActivity > ActivityTimeout) {
- for (int i = 0; i < MAXDVBAPI; i++) {
- cDvbApi *DvbApi = cDvbApi::GetDvbApi(i + 1, MAXPRIORITY + 1);
- if (DvbApi) {
- if (DvbApi != cDvbApi::PrimaryDvbApi || (cDvbApi::NumDvbApis == 1 && Setup.EPGScanTimeout && now - lastActivity > Setup.EPGScanTimeout * 3600)) {
- if (!(DvbApi->Recording() || DvbApi->Replaying() || DvbApi->Transferring())) {
+ for (int i = 0; i < MAXDEVICES; i++) {
+ cDevice *Device = cDevice::GetDevice(i + 1, MAXPRIORITY + 1);
+ if (Device) {
+ if (Device != cDevice::PrimaryDevice() || (cDevice::NumDevices() == 1 && Setup.EPGScanTimeout && now - lastActivity > Setup.EPGScanTimeout * 3600)) {
+ if (!(Device->Receiving() || Device->Replaying()/*XXX+ || Device->Transferring()XXX*/)) {
int oldCh = lastChannel;
int ch = oldCh + 1;
while (ch != oldCh) {
@@ -62,12 +62,12 @@ void cEITScanner::Process(void)
}
cChannel *Channel = Channels.GetByNumber(ch);
if (Channel) {
- if (Channel->ca <= MAXDVBAPI && !DvbApi->ProvidesCa(Channel->ca))
+ if (Channel->ca <= MAXDEVICES && !Device->ProvidesCa(Channel->ca))
break; // the channel says it explicitly needs a different card
if (Channel->pnr && !TransponderScanned(Channel)) {
- if (DvbApi == cDvbApi::PrimaryDvbApi && !currentChannel)
- currentChannel = DvbApi->Channel();
- Channel->Switch(DvbApi, false);
+ if (Device == cDevice::PrimaryDevice() && !currentChannel)
+ currentChannel = Device->Channel();
+ Channel->Switch(Device, false);
lastChannel = ch;
break;
}
diff --git a/interface.c b/interface.c
index 9c2e918d..ed9d0b1e 100644
--- a/interface.c
+++ b/interface.c
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: interface.c 1.50 2002/05/19 12:02:57 kls Exp $
+ * $Id: interface.c 1.51 2002/06/10 16:30:00 kls Exp $
*/
#include "interface.h"
@@ -484,6 +484,6 @@ void cInterface::DisplayRecording(int Index, bool On)
bool cInterface::Recording(void)
{
- // This is located here because the Interface has to do with the "PrimaryDvbApi" anyway
- return cDvbApi::PrimaryDvbApi->Recording();
+ // This is located here because the Interface has to do with the "PrimaryDevice" anyway
+ return cDevice::PrimaryDevice()->Receiving();
}
diff --git a/menu.c b/menu.c
index 40119efa..7f063348 100644
--- a/menu.c
+++ b/menu.c
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: menu.c 1.195 2002/05/19 14:55:22 kls Exp $
+ * $Id: menu.c 1.196 2002/06/16 12:11:02 kls Exp $
*/
#include "menu.h"
@@ -18,6 +18,7 @@
#include "i18n.h"
#include "menuitems.h"
#include "plugin.h"
+#include "recording.h"
#include "status.h"
#include "videodir.h"
@@ -25,6 +26,7 @@
#define MAXWAIT4EPGINFO 10 // seconds
#define MODETIMEOUT 3 // seconds
+#define MAXRECORDCONTROLS (MAXDEVICES * MAXRECEIVERS)
#define MAXINSTANTRECTIME (24 * 60 - 1) // 23:59 hours
#define CHNUMWIDTH (Channels.Count() > 999 ? 5 : 4) // there are people with more than 999 channels...
@@ -376,7 +378,7 @@ eOSState cMenuEditCaItem::ProcessKey(eKeys Key)
}
}
else if (NORMALKEY(Key) == kRight) {
- if (ca && ca->Next() && (allowCardNr || ((cCaDefinition *)ca->Next())->Number() > MAXDVBAPI)) {
+ if (ca && ca->Next() && (allowCardNr || ((cCaDefinition *)ca->Next())->Number() > MAXDEVICES)) {
ca = (cCaDefinition *)ca->Next();
*value = ca->Number();
}
@@ -494,7 +496,7 @@ cMenuChannels::cMenuChannels(void)
//TODO
int i = 0;
cChannel *channel;
- int curr = ((channel = Channels.GetByNumber(cDvbApi::CurrentChannel())) != NULL) ? channel->Index() : -1;
+ int curr = ((channel = Channels.GetByNumber(cDevice::CurrentChannel())) != NULL) ? channel->Index() : -1;
while ((channel = Channels.Get(i)) != NULL) {
Add(new cMenuChannelItem(i, channel), i == curr);
@@ -1145,7 +1147,7 @@ cMenuSchedule::cMenuSchedule(void)
{
now = next = false;
otherChannel = 0;
- cChannel *channel = Channels.GetByNumber(cDvbApi::CurrentChannel());
+ cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel());
if (channel) {
cMenuWhatsOn::SetCurrentChannel(channel->number);
schedules = cSIProcessor::Schedules(mutexLock);
@@ -1264,7 +1266,7 @@ eOSState cMenuSchedule::ProcessKey(eKeys Key)
cChannel *channel = Channels.GetByServiceID(ei->GetServiceID());
if (channel) {
PrepareSchedule(channel);
- if (channel->number != cDvbApi::CurrentChannel()) {
+ if (channel->number != cDevice::CurrentChannel()) {
otherChannel = channel->number;
SetHelp(tr("Record"), tr("Now"), tr("Next"), tr("Switch"));
}
@@ -1441,7 +1443,7 @@ eOSState cMenuRecordings::Rewind(void)
{
cMenuRecordingItem *ri = (cMenuRecordingItem *)Get(Current());
if (ri && !ri->IsDirectory()) {
- cDvbApi::PrimaryDvbApi->StopReplay(); // must do this first to be able to rewind the currently replayed recording
+ cDevice::PrimaryDevice()->StopReplay(); // must do this first to be able to rewind the currently replayed recording
cResumeFile ResumeFile(ri->FileName());
ResumeFile.Delete();
return Play();
@@ -1614,7 +1616,7 @@ public:
cMenuSetupDVB::cMenuSetupDVB(void)
{
SetSection(tr("DVB"));
- Add(new cMenuEditIntItem( tr("Setup.DVB$Primary DVB interface"), &data.PrimaryDVB, 1, cDvbApi::NumDvbApis));
+ Add(new cMenuEditIntItem( tr("Setup.DVB$Primary DVB interface"), &data.PrimaryDVB, 1, cDevice::NumDevices()));
Add(new cMenuEditBoolItem(tr("Setup.DVB$Video format"), &data.VideoFormat, "4:3", "16:9"));
}
@@ -1626,7 +1628,7 @@ eOSState cMenuSetupDVB::ProcessKey(eKeys Key)
if (state == osBack && Key == kOk) {
if (Setup.PrimaryDVB != oldPrimaryDVB) {
state = osSwitchDvb;
- cDvbApi::PrimaryDvbApi->SetVideoFormat(Setup.VideoFormat ? VIDEO_FORMAT_16_9 : VIDEO_FORMAT_4_3);
+ cDevice::PrimaryDevice()->SetVideoFormat(Setup.VideoFormat ? VIDEO_FORMAT_16_9 : VIDEO_FORMAT_4_3);
}
}
return state;
@@ -1659,7 +1661,7 @@ public:
cMenuSetupCICAM::cMenuSetupCICAM(void)
{
SetSection(tr("CICAM"));
- for (int d = 0; d < cDvbApi::NumDvbApis; d++) {
+ for (int d = 0; d < cDevice::NumDevices(); d++) {
for (int i = 0; i < 2; i++) {
char buffer[32];
snprintf(buffer, sizeof(buffer), "%s%d %d", tr("Setup.CICAM$CICAM DVB"), d + 1, i + 1);
@@ -1673,7 +1675,7 @@ eOSState cMenuSetupCICAM::ProcessKey(eKeys Key)
eOSState state = cMenuSetupBase::ProcessKey(Key);
if (state == osBack && Key == kOk)
- cDvbApi::SetCaCaps();
+ cDevice::SetCaCaps();
return state;
}
@@ -2024,12 +2026,14 @@ void cMenuMain::Set(void)
// Editing control:
+ /*XXX+
if (cVideoCutter::Active())
Add(new cOsdItem(tr(" Cancel editing"), osCancelEdit));
+ XXX*/
// Color buttons:
- SetHelp(tr("Record"), cDvbApi::PrimaryDvbApi->CanToggleAudioTrack() ? tr("Language") : NULL, NULL, replaying ? tr("Button$Stop") : cReplayControl::LastReplayed() ? tr("Resume") : NULL);
+ SetHelp(tr("Record"), /*XXX+ cDevice::PrimaryDevice()->CanToggleAudioTrack() ? tr("Language") :XXX*/ NULL, NULL, replaying ? tr("Button$Stop") : cReplayControl::LastReplayed() ? tr("Resume") : NULL);
Display();
lastActivity = time(NULL);
}
@@ -2059,7 +2063,7 @@ eOSState cMenuMain::ProcessKey(eKeys Key)
}
break;
case osCancelEdit: if (Interface->Confirm(tr("Cancel editing?"))) {
- cVideoCutter::Stop();
+ //XXX+cVideoCutter::Stop();
return osEnd;
}
break;
@@ -2082,11 +2086,13 @@ eOSState cMenuMain::ProcessKey(eKeys Key)
state = osRecord;
break;
case kGreen: if (!HasSubMenu()) {
- if (cDvbApi::PrimaryDvbApi->CanToggleAudioTrack()) {
+ /*XXX+
+ if (cDevice::PrimaryDevice()->CanToggleAudioTrack()) {
Interface->Clear();
- cDvbApi::PrimaryDvbApi->ToggleAudioTrack();
+ cDevice::PrimaryDevice()->ToggleAudioTrack();
state = osEnd;
}
+ XXX*/
}
break;
case kBlue: if (!HasSubMenu())
@@ -2134,7 +2140,7 @@ cDisplayChannel::cDisplayChannel(eKeys FirstKey)
:cOsdObject(true)
{
group = -1;
- oldNumber = cDvbApi::CurrentChannel();
+ oldNumber = cDevice::CurrentChannel();
number = 0;
lastTime = time_ms();
int EpgLines = Setup.ShowInfoOnChSwitch ? 5 : 1;
@@ -2253,7 +2259,7 @@ eOSState cDisplayChannel::ProcessKey(eKeys Key)
case kRight:
withInfo = false;
if (group < 0) {
- cChannel *channel = Channels.GetByNumber(cDvbApi::CurrentChannel());
+ cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel());
if (channel)
group = channel->Index();
}
@@ -2328,7 +2334,7 @@ cDisplayVolume::cDisplayVolume(void)
:cOsdObject(true)
{
displayVolume = this;
- timeout = time_ms() + (cDvbApi::PrimaryDvbApi->IsMute() ? MUTETIMEOUT : VOLUMETIMEOUT);
+ timeout = time_ms() + (cDevice::PrimaryDevice()->IsMute() ? MUTETIMEOUT : VOLUMETIMEOUT);
Interface->Open(Setup.OSDwidth, -1);
Show();
}
@@ -2341,13 +2347,13 @@ cDisplayVolume::~cDisplayVolume()
void cDisplayVolume::Show(void)
{
- cDvbApi *dvbApi = cDvbApi::PrimaryDvbApi;
- if (dvbApi->IsMute()) {
+ cDevice *device = cDevice::PrimaryDevice();
+ if (device->IsMute()) {
Interface->Fill(0, 0, Width(), 1, clrTransparent);
Interface->Write(0, 0, tr("Mute"), clrGreen);
}
else {
- int Current = cDvbApi::CurrentVolume();
+ int Current = cDevice::CurrentVolume();
int Total = MAXVOLUME;
const char *Prompt = tr("Volume ");
#ifdef DEBUG_OSD
@@ -2387,7 +2393,7 @@ eOSState cDisplayVolume::ProcessKey(eKeys Key)
timeout = time_ms() + VOLUMETIMEOUT;
break;
case kMute:
- if (cDvbApi::PrimaryDvbApi->IsMute()) {
+ if (cDevice::PrimaryDevice()->IsMute()) {
Show();
timeout = time_ms() + MUTETIMEOUT;
}
@@ -2405,43 +2411,45 @@ eOSState cDisplayVolume::ProcessKey(eKeys Key)
// --- cRecordControl --------------------------------------------------------
-cRecordControl::cRecordControl(cDvbApi *DvbApi, cTimer *Timer)
+cRecordControl::cRecordControl(cDevice *Device, cTimer *Timer)
{
eventInfo = NULL;
instantId = NULL;
fileName = NULL;
- dvbApi = DvbApi;
- if (!dvbApi) dvbApi = cDvbApi::PrimaryDvbApi;//XXX
+ recorder = NULL;
+ device = Device;
+ if (!device) device = cDevice::PrimaryDevice();//XXX
timer = Timer;
if (!timer) {
timer = new cTimer(true);
Timers.Add(timer);
Timers.Save();
- asprintf(&instantId, cDvbApi::NumDvbApis > 1 ? "%s - %d" : "%s", Channels.GetChannelNameByNumber(timer->channel), dvbApi->CardIndex() + 1);
+ asprintf(&instantId, cDevice::NumDevices() > 1 ? "%s - %d" : "%s", Channels.GetChannelNameByNumber(timer->channel), device->CardIndex() + 1);
}
timer->SetPending(true);
timer->SetRecording(true);
- if (Channels.SwitchTo(timer->channel, dvbApi)) {
- const char *Title = NULL;
- const char *Subtitle = NULL;
- const char *Summary = NULL;
- if (GetEventInfo()) {
- Title = eventInfo->GetTitle();
- Subtitle = eventInfo->GetSubtitle();
- Summary = eventInfo->GetExtendedDescription();
- dsyslog("Title: '%s' Subtitle: '%s'", Title, Subtitle);
- }
- cRecording Recording(timer, Title, Subtitle, Summary);
- fileName = strdup(Recording.FileName());
- cRecordingUserCommand::InvokeCommand(RUC_BEFORERECORDING, fileName);
- if (dvbApi->StartRecord(fileName, Channels.GetByNumber(timer->channel)->ca, timer->priority)) {
- Recording.WriteSummary();
- cStatusMonitor::MsgRecording(dvbApi, fileName);
- }
- Interface->DisplayRecording(dvbApi->CardIndex(), true);
+
+ const char *Title = NULL;
+ const char *Subtitle = NULL;
+ const char *Summary = NULL;
+ if (GetEventInfo()) {
+ Title = eventInfo->GetTitle();
+ Subtitle = eventInfo->GetSubtitle();
+ Summary = eventInfo->GetExtendedDescription();
+ dsyslog("Title: '%s' Subtitle: '%s'", Title, Subtitle);
+ }
+ cRecording Recording(timer, Title, Subtitle, Summary);
+ fileName = strdup(Recording.FileName());
+ cRecordingUserCommand::InvokeCommand(RUC_BEFORERECORDING, fileName);
+ cChannel *ch = Channels.GetByNumber(timer->channel);
+ recorder = new cRecorder(fileName, ch->ca, timer->priority, ch->vpid, ch->apid1, ch->apid2, ch->dpid1, ch->dpid2);
+ if (device->Attach(recorder)) {
+ Recording.WriteSummary();
+ cStatusMonitor::MsgRecording(device, fileName);
+ Interface->DisplayRecording(device->CardIndex(), true);
}
else
- cThread::EmergencyExit(true);
+ DELETENULL(recorder);
}
cRecordControl::~cRecordControl()
@@ -2484,8 +2492,8 @@ bool cRecordControl::GetEventInfo(void)
void cRecordControl::Stop(bool KeepInstant)
{
if (timer) {
- cStatusMonitor::MsgRecording(dvbApi, NULL);
- dvbApi->StopRecord();
+ cStatusMonitor::MsgRecording(device, NULL);
+ DELETENULL(recorder);
timer->SetRecording(false);
if ((IsInstant() && !KeepInstant) || (timer->IsSingleEvent() && !timer->Matches())) {
// checking timer->Matches() to make sure we don't delete the timer
@@ -2495,14 +2503,14 @@ void cRecordControl::Stop(bool KeepInstant)
Timers.Save();
}
timer = NULL;
- Interface->DisplayRecording(dvbApi->CardIndex(), false);
+ Interface->DisplayRecording(device->CardIndex(), false);
cRecordingUserCommand::InvokeCommand(RUC_AFTERRECORDING, fileName);
}
}
bool cRecordControl::Process(time_t t)
{
- if (!timer || !timer->Matches(t))
+ if (!recorder || !timer || !timer->Matches(t))
return false;
AssertFreeDiskSpace(timer->priority);
return true;
@@ -2510,20 +2518,27 @@ bool cRecordControl::Process(time_t t)
// --- cRecordControls -------------------------------------------------------
-cRecordControl *cRecordControls::RecordControls[MAXDVBAPI] = { NULL };
+cRecordControl *cRecordControls::RecordControls[MAXRECORDCONTROLS] = { NULL };
bool cRecordControls::Start(cTimer *Timer)
{
- int ch = Timer ? Timer->channel : cDvbApi::CurrentChannel();
+ int ch = Timer ? Timer->channel : cDevice::CurrentChannel();
cChannel *channel = Channels.GetByNumber(ch);
if (channel) {
- cDvbApi *dvbApi = cDvbApi::GetDvbApi(channel->ca, Timer ? Timer->priority : Setup.DefaultPriority);
- if (dvbApi) {
- Stop(dvbApi);
- for (int i = 0; i < MAXDVBAPI; i++) {
+ bool ReUse = false;
+ cDevice *device = cDevice::GetDevice(channel->ca, Timer ? Timer->priority : Setup.DefaultPriority, channel->frequency, channel->vpid, &ReUse);
+ if (device) {
+ if (!ReUse) {
+ Stop(device);
+ if (!channel->Switch(device)) {
+ cThread::EmergencyExit(true);
+ return false;
+ }
+ }
+ for (int i = 0; i < MAXRECORDCONTROLS; i++) {
if (!RecordControls[i]) {
- RecordControls[i] = new cRecordControl(dvbApi, Timer);
+ RecordControls[i] = new cRecordControl(device, Timer);
return true;
}
}
@@ -2538,7 +2553,7 @@ bool cRecordControls::Start(cTimer *Timer)
void cRecordControls::Stop(const char *InstantId)
{
- for (int i = 0; i < MAXDVBAPI; i++) {
+ for (int i = 0; i < MAXRECORDCONTROLS; i++) {
if (RecordControls[i]) {
const char *id = RecordControls[i]->InstantId();
if (id && strcmp(id, InstantId) == 0)
@@ -2547,12 +2562,12 @@ void cRecordControls::Stop(const char *InstantId)
}
}
-void cRecordControls::Stop(cDvbApi *DvbApi)
+void cRecordControls::Stop(cDevice *Device)
{
- for (int i = 0; i < MAXDVBAPI; i++) {
+ for (int i = 0; i < MAXRECORDCONTROLS; i++) {
if (RecordControls[i]) {
- if (RecordControls[i]->Uses(DvbApi)) {
- isyslog("stopping recording on DVB device %d due to higher priority", DvbApi->CardIndex() + 1);
+ if (RecordControls[i]->Uses(Device)) {
+ isyslog("stopping recording on DVB device %d due to higher priority", Device->CardIndex() + 1);
RecordControls[i]->Stop(true);
}
}
@@ -2561,11 +2576,11 @@ void cRecordControls::Stop(cDvbApi *DvbApi)
bool cRecordControls::StopPrimary(bool DoIt)
{
- if (cDvbApi::PrimaryDvbApi->Recording()) {
- cDvbApi *dvbApi = cDvbApi::GetDvbApi(cDvbApi::PrimaryDvbApi->Ca(), 0);
- if (dvbApi) {
+ if (cDevice::PrimaryDevice()->Receiving()) {
+ cDevice *device = cDevice::GetDevice(cDevice::PrimaryDevice()->Ca(), 0);
+ if (device) {
if (DoIt)
- Stop(cDvbApi::PrimaryDvbApi);
+ Stop(cDevice::PrimaryDevice());
return true;
}
}
@@ -2574,7 +2589,7 @@ bool cRecordControls::StopPrimary(bool DoIt)
const char *cRecordControls::GetInstantId(const char *LastInstantId)
{
- for (int i = 0; i < MAXDVBAPI; i++) {
+ for (int i = 0; i < MAXRECORDCONTROLS; i++) {
if (RecordControls[i]) {
if (!LastInstantId && RecordControls[i]->InstantId())
return RecordControls[i]->InstantId();
@@ -2587,7 +2602,7 @@ const char *cRecordControls::GetInstantId(const char *LastInstantId)
cRecordControl *cRecordControls::GetRecordControl(const char *FileName)
{
- for (int i = 0; i < MAXDVBAPI; i++) {
+ for (int i = 0; i < MAXRECORDCONTROLS; i++) {
if (RecordControls[i] && strcmp(RecordControls[i]->FileName(), FileName) == 0)
return RecordControls[i];
}
@@ -2596,7 +2611,7 @@ cRecordControl *cRecordControls::GetRecordControl(const char *FileName)
void cRecordControls::Process(time_t t)
{
- for (int i = 0; i < MAXDVBAPI; i++) {
+ for (int i = 0; i < MAXRECORDCONTROLS; i++) {
if (RecordControls[i]) {
if (!RecordControls[i]->Process(t))
DELETENULL(RecordControls[i]);
@@ -2606,13 +2621,19 @@ void cRecordControls::Process(time_t t)
bool cRecordControls::Active(void)
{
- for (int i = 0; i < MAXDVBAPI; i++) {
+ for (int i = 0; i < MAXRECORDCONTROLS; i++) {
if (RecordControls[i])
return true;
}
return false;
}
+void cRecordControls::Shutdown(void)
+{
+ for (int i = 0; i < MAXRECORDCONTROLS; i++)
+ DELETENULL(RecordControls[i]);
+}
+
// --- cProgressBar ----------------------------------------------------------
class cProgressBar : public cBitmap {
@@ -2664,25 +2685,24 @@ char *cReplayControl::title = NULL;
cReplayControl::cReplayControl(void)
{
- dvbApi = cDvbApi::PrimaryDvbApi;
visible = modeOnly = shown = displayFrames = false;
lastCurrent = lastTotal = -1;
timeoutShow = 0;
timeSearchActive = false;
if (fileName) {
marks.Load(fileName);
- if (!dvbApi->StartReplay(fileName))
- Interface->Error(tr("Channel locked (recording)!"));
+ if (!Start(fileName))
+ Interface->Error(tr("Channel locked (recording)!"));//XXX+
else
- cStatusMonitor::MsgReplaying(dvbApi, fileName);
+ cStatusMonitor::MsgReplaying(this, fileName);
}
}
cReplayControl::~cReplayControl()
{
Hide();
- cStatusMonitor::MsgReplaying(dvbApi, NULL);
- dvbApi->StopReplay();
+ cStatusMonitor::MsgReplaying(this, NULL);
+ Stop();
}
void cReplayControl::SetRecording(const char *FileName, const char *Title)
@@ -2744,7 +2764,7 @@ void cReplayControl::ShowMode(void)
if (Setup.ShowReplayMode && !timeSearchActive) {
bool Play, Forward;
int Speed;
- if (dvbApi->GetReplayMode(Play, Forward, Speed)) {
+ if (GetReplayMode(Play, Forward, Speed)) {
bool NormalPlay = (Play && Speed == -1);
if (!visible) {
@@ -2782,7 +2802,7 @@ bool cReplayControl::ShowProgress(bool Initial)
{
int Current, Total;
- if (dvbApi->GetIndex(Current, Total) && Total > 0) {
+ if (GetIndex(Current, Total) && Total > 0) {
if (!visible) {
Interface->Open(Setup.OSDwidth, -3);
needsFastResponse = visible = true;
@@ -2857,14 +2877,14 @@ void cReplayControl::TimeSearchProcess(eKeys Key)
int dir = (Key == kRight ? 1 : -1);
if (dir > 0)
Seconds = min(Total - Current - STAY_SECONDS_OFF_END, Seconds);
- dvbApi->SkipSeconds(Seconds * dir);
+ SkipSeconds(Seconds * dir);
timeSearchActive = false;
}
break;
case kUp:
case kDown:
Seconds = min(Total - STAY_SECONDS_OFF_END, Seconds);
- dvbApi->Goto(Seconds * FRAMESPERSEC, Key == kDown);
+ Goto(Seconds * FRAMESPERSEC, Key == kDown);
timeSearchActive = false;
break;
default:
@@ -2902,7 +2922,7 @@ void cReplayControl::TimeSearch(void)
void cReplayControl::MarkToggle(void)
{
int Current, Total;
- if (dvbApi->GetIndex(Current, Total, true)) {
+ if (GetIndex(Current, Total, true)) {
cMark *m = marks.Get(Current);
lastCurrent = -1; // triggers redisplay
if (m)
@@ -2919,10 +2939,10 @@ void cReplayControl::MarkJump(bool Forward)
{
if (marks.Count()) {
int Current, Total;
- if (dvbApi->GetIndex(Current, Total)) {
+ if (GetIndex(Current, Total)) {
cMark *m = Forward ? marks.GetNext(Current) : marks.GetPrev(Current);
if (m)
- dvbApi->Goto(m->position, true);
+ Goto(m->position, true);
}
displayFrames = true;
}
@@ -2931,11 +2951,11 @@ void cReplayControl::MarkJump(bool Forward)
void cReplayControl::MarkMove(bool Forward)
{
int Current, Total;
- if (dvbApi->GetIndex(Current, Total)) {
+ if (GetIndex(Current, Total)) {
cMark *m = marks.Get(Current);
if (m) {
displayFrames = true;
- int p = dvbApi->SkipFrames(Forward ? 1 : -1);
+ int p = SkipFrames(Forward ? 1 : -1);
cMark *m2;
if (Forward) {
if ((m2 = marks.Next(m)) != NULL && m2->position <= p)
@@ -2945,7 +2965,7 @@ void cReplayControl::MarkMove(bool Forward)
if ((m2 = marks.Prev(m)) != NULL && m2->position >= p)
return;
}
- dvbApi->Goto(m->position = p, true);
+ Goto(m->position = p, true);
marks.Save();
}
}
@@ -2953,6 +2973,7 @@ void cReplayControl::MarkMove(bool Forward)
void cReplayControl::EditCut(void)
{
+ /*XXX+
if (fileName) {
Hide();
if (!cVideoCutter::Active()) {
@@ -2965,12 +2986,13 @@ void cReplayControl::EditCut(void)
Interface->Error(tr("Editing process already active!"));
ShowMode();
}
+ XXX*/
}
void cReplayControl::EditTest(void)
{
int Current, Total;
- if (dvbApi->GetIndex(Current, Total)) {
+ if (GetIndex(Current, Total)) {
cMark *m = marks.Get(Current);
if (!m)
m = marks.GetNext(Current);
@@ -2978,8 +3000,8 @@ void cReplayControl::EditTest(void)
if ((m->Index() & 0x01) != 0)
m = marks.Next(m);
if (m) {
- dvbApi->Goto(m->position - dvbApi->SecondsToFrames(3));
- dvbApi->Play();
+ Goto(m->position - SecondsToFrames(3));
+ Play();
}
}
}
@@ -2987,7 +3009,7 @@ void cReplayControl::EditTest(void)
eOSState cReplayControl::ProcessKey(eKeys Key)
{
- if (!dvbApi->Replaying())
+ if (!Active())
return osEnd;
if (visible) {
if (timeoutShow && time(NULL) > timeoutShow) {
@@ -3009,21 +3031,21 @@ eOSState cReplayControl::ProcessKey(eKeys Key)
bool DoShowMode = true;
switch (Key) {
// Positioning:
- case kUp: dvbApi->Play(); break;
- case kDown: dvbApi->Pause(); break;
+ case kUp: Play(); break;
+ case kDown: Pause(); break;
case kLeft|k_Release:
if (Setup.MultiSpeedMode) break;
- case kLeft: dvbApi->Backward(); break;
+ case kLeft: Backward(); break;
case kRight|k_Release:
if (Setup.MultiSpeedMode) break;
- case kRight: dvbApi->Forward(); break;
+ case kRight: Forward(); break;
case kRed: TimeSearch(); break;
case kGreen|k_Repeat:
- case kGreen: dvbApi->SkipSeconds(-60); break;
+ case kGreen: SkipSeconds(-60); break;
case kYellow|k_Repeat:
- case kYellow: dvbApi->SkipSeconds( 60); break;
+ case kYellow: SkipSeconds( 60); break;
case kBlue: Hide();
- dvbApi->StopReplay();
+ Stop();
return osEnd;
default: {
DoShowMode = false;
diff --git a/menu.h b/menu.h
index 70aba6e6..9dfe1a40 100644
--- a/menu.h
+++ b/menu.h
@@ -4,14 +4,16 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: menu.h 1.43 2002/05/18 12:36:06 kls Exp $
+ * $Id: menu.h 1.44 2002/06/14 12:33:35 kls Exp $
*/
#ifndef __MENU_H
#define __MENU_H
-#include "dvbapi.h"
+#include "device.h"
#include "osd.h"
+#include "dvbplayer.h"
+#include "recorder.h"
#include "recording.h"
class cMenuMain : public cOsdMenu {
@@ -76,17 +78,18 @@ public:
class cRecordControl {
private:
- cDvbApi *dvbApi;
+ cDevice *device;
cTimer *timer;
+ cRecorder *recorder;
const cEventInfo *eventInfo;
char *instantId;
char *fileName;
bool GetEventInfo(void);
public:
- cRecordControl(cDvbApi *DvbApi, cTimer *Timer = NULL);
+ cRecordControl(cDevice *Device, cTimer *Timer = NULL);
virtual ~cRecordControl();
bool Process(time_t t);
- bool Uses(cDvbApi *DvbApi) { return DvbApi == dvbApi; }
+ bool Uses(cDevice *Device) { return Device == device; }
void Stop(bool KeepInstant = false);
bool IsInstant(void) { return instantId; }
const char *InstantId(void) { return instantId; }
@@ -96,21 +99,21 @@ public:
class cRecordControls {
private:
- static cRecordControl *RecordControls[MAXDVBAPI];
+ static cRecordControl *RecordControls[];
public:
static bool Start(cTimer *Timer = NULL);
static void Stop(const char *InstantId);
- static void Stop(cDvbApi *DvbApi);
+ static void Stop(cDevice *Device);
static bool StopPrimary(bool DoIt = false);
static const char *GetInstantId(const char *LastInstantId);
static cRecordControl *GetRecordControl(const char *FileName);
static void Process(time_t t);
static bool Active(void);
+ static void Shutdown(void);
};
-class cReplayControl : public cOsdObject {
+class cReplayControl : public cDvbPlayerControl {
private:
- cDvbApi *dvbApi;
cMarks marks;
bool visible, modeOnly, shown, displayFrames;
int lastCurrent, lastTotal;
diff --git a/osd.c b/osd.c
index 09285c00..22093e24 100644
--- a/osd.c
+++ b/osd.c
@@ -4,12 +4,12 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: osd.c 1.27 2002/05/19 12:56:57 kls Exp $
+ * $Id: osd.c 1.28 2002/06/10 16:30:00 kls Exp $
*/
#include "osd.h"
#include <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
diff --git a/status.c b/status.c
index cc01ad3b..5f3ae7d4 100644
--- a/status.c
+++ b/status.c
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: status.c 1.1 2002/05/19 14:54:30 kls Exp $
+ * $Id: status.c 1.2 2002/06/16 12:10:44 kls Exp $
*/
#include "status.h"
@@ -23,22 +23,22 @@ cStatusMonitor::~cStatusMonitor()
statusMonitors.Del(this, false);
}
-void cStatusMonitor::MsgChannelSwitch(const cDvbApi *DvbApi, int ChannelNumber)
+void cStatusMonitor::MsgChannelSwitch(const cDevice *Device, int ChannelNumber)
{
for (cStatusMonitor *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm))
- sm->ChannelSwitch(DvbApi, ChannelNumber);
+ sm->ChannelSwitch(Device, ChannelNumber);
}
-void cStatusMonitor::MsgRecording(const cDvbApi *DvbApi, const char *Name)
+void cStatusMonitor::MsgRecording(const cDevice *Device, const char *Name)
{
for (cStatusMonitor *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm))
- sm->Recording(DvbApi, Name);
+ sm->Recording(Device, Name);
}
-void cStatusMonitor::MsgReplaying(const cDvbApi *DvbApi, const char *Name)
+void cStatusMonitor::MsgReplaying(const cDvbPlayerControl *DvbPlayerControl, const char *Name)
{
for (cStatusMonitor *sm = statusMonitors.First(); sm; sm = statusMonitors.Next(sm))
- sm->Replaying(DvbApi, Name);
+ sm->Replaying(DvbPlayerControl, Name);
}
void cStatusMonitor::MsgSetVolume(int Volume, bool Absolute)
diff --git a/status.h b/status.h
index db842a28..e478ee1d 100644
--- a/status.h
+++ b/status.h
@@ -4,14 +4,15 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: status.h 1.1 2002/05/19 14:54:15 kls Exp $
+ * $Id: status.h 1.2 2002/06/16 12:09:55 kls Exp $
*/
#ifndef __STATUS_H
#define __STATUS_H
#include "config.h"
-#include "dvbapi.h"
+#include "device.h"
+#include "dvbplayer.h"
#include "tools.h"
class cStatusMonitor : public cListObject {
@@ -19,15 +20,15 @@ private:
static cList<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);
diff --git a/svdrp.c b/svdrp.c
index ff3a775a..5290c9a6 100644
--- a/svdrp.c
+++ b/svdrp.c
@@ -10,7 +10,7 @@
* and interact with the Video Disk Recorder - or write a full featured
* graphical interface that sits on top of an SVDRP connection.
*
- * $Id: svdrp.c 1.37 2002/05/13 16:32:05 kls Exp $
+ * $Id: svdrp.c 1.38 2002/06/10 16:30:00 kls Exp $
*/
#include "svdrp.h"
@@ -27,7 +27,7 @@
#include <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)
diff --git a/thread.c b/thread.c
index 925fc4d9..3db0f908 100644
--- a/thread.c
+++ b/thread.c
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: thread.c 1.20 2002/05/13 16:32:09 kls Exp $
+ * $Id: thread.c 1.21 2002/06/10 16:30:00 kls Exp $
*/
#include "thread.h"
@@ -147,6 +147,7 @@ bool cThread::Active(void)
void cThread::Cancel(int WaitSeconds)
{
+ running = false;
if (WaitSeconds > 0) {
for (time_t t0 = time(NULL) + WaitSeconds; time(NULL) < t0; ) {
if (!Active())
diff --git a/thread.h b/thread.h
index 3ac398a2..d9c8f3a6 100644
--- a/thread.h
+++ b/thread.h
@@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: thread.h 1.12 2002/02/23 13:53:38 kls Exp $
+ * $Id: thread.h 1.13 2002/06/10 16:30:00 kls Exp $
*/
#ifndef __THREAD_H
@@ -54,9 +54,9 @@ private:
static bool signalHandlerInstalled;
static void SignalHandler(int signum);
static void *StartThread(cThread *Thread);
+protected:
void Lock(void) { mutex.Lock(); }
void Unlock(void) { mutex.Unlock(); }
-protected:
void WakeUp(void);
virtual void Action(void) = 0;
void Cancel(int WaitSeconds = 0);
diff --git a/tools.h b/tools.h
index 0c29e7cb..e107f185 100644
--- a/tools.h
+++ b/tools.h
@@ -4,13 +4,13 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
- * $Id: tools.h 1.46 2002/05/18 15:10:10 kls Exp $
+ * $Id: tools.h 1.47 2002/06/10 16:30:00 kls Exp $
*/
#ifndef __TOOLS_H
#define __TOOLS_H
-//#include <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; }
diff --git a/vdr.c b/vdr.c
index afad5ffa..bef3919f 100644
--- a/vdr.c
+++ b/vdr.c
@@ -22,7 +22,7 @@
*
* The project's page is at http://www.cadsoft.de/people/kls/vdr
*
- * $Id: vdr.c 1.113 2002/05/20 11:02:10 kls Exp $
+ * $Id: vdr.c 1.114 2002/06/16 11:30:28 kls Exp $
*/
#include <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");