mirror of
https://github.com/vdr-projects/vdr.git
synced 2025-03-01 10:50:46 +00:00
- Added cDevice::DeviceNumber() to get the number of a device, not counting any gaps that might be in the index count. - Fixed fetching the current/next information to handle cases where the duration of an event is set wrongly and would last beyond the start time of the next event. - Adapted type names to the new HEAD version of the driver (which the previous NEWSTRUCT branch has been merged into). Note that to use this driver version you still need to add NEWSTRUCT=1 to the make call when building VDR. You need a HEAD version of the LinuxDVB driver dated 2002-10-11 or later to compile VDR with NEWSTRUCT=1. - Fixed radio channels in channels.conf.cable (thanks to Robert Schiele and Uwe Scheffler). - Fixed switching the video format in the Setup/DVB menu (thanks to Uwe Scheffler for reporting this one). - Reactivated full handling of second audio PID (even in 'Transfer Mode'). - Fixed a crash when closing down with remote control plugins (thanks to Oliver Endriss helping to debug this one). - Commands in the file 'commands.conf' can now have a '?' at the end of their title, which will result in a confirmation prompt before executing the command. - Changed a few leftover 'new char[...]' to MALLOC(char, ...). - If a command executed from the "Commands" menu doesn't return any output, the OSD will now be closed automatically. - The SVDRP command PUTE now triggers an immediate write of the 'epg.data' file (suggested by Gerhard Steiner). - The new configuration file 'reccmds.conf' can be used to define commands that shall be executed from the "Recordings" menu; see MANUAL and 'man vdr(5)' for details (suggested by Gerhard Steiner).
788 lines
21 KiB
C
788 lines
21 KiB
C
/*
|
|
* 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.27 2002/10/12 13:24:37 kls Exp $
|
|
*/
|
|
|
|
#include "device.h"
|
|
#include <errno.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/mman.h>
|
|
#include "channels.h"
|
|
#include "eit.h"
|
|
#include "i18n.h"
|
|
#include "player.h"
|
|
#include "receiver.h"
|
|
#include "status.h"
|
|
#include "transfer.h"
|
|
|
|
// --- cDevice ---------------------------------------------------------------
|
|
|
|
// The default priority for non-primary devices:
|
|
#define DEFAULTPRIORITY -2
|
|
|
|
// The maximum time we wait before assuming that a recorded video data stream
|
|
// is broken:
|
|
#define MAXBROKENTIMEOUT 30 // seconds
|
|
|
|
int cDevice::numDevices = 0;
|
|
int cDevice::useDevice = 0;
|
|
int cDevice::nextCardIndex = 0;
|
|
int cDevice::currentChannel = 0;
|
|
cDevice *cDevice::device[MAXDEVICES] = { NULL };
|
|
cDevice *cDevice::primaryDevice = NULL;
|
|
|
|
cDevice::cDevice(void)
|
|
{
|
|
cardIndex = nextCardIndex++;
|
|
|
|
SetVideoFormat(Setup.VideoFormat);
|
|
|
|
active = false;
|
|
|
|
mute = false;
|
|
volume = Setup.CurrentVolume;
|
|
|
|
player = NULL;
|
|
|
|
playerDetached = false;
|
|
|
|
for (int i = 0; i < MAXRECEIVERS; i++)
|
|
receiver[i] = NULL;
|
|
ca = -1;
|
|
|
|
if (numDevices < MAXDEVICES) {
|
|
device[numDevices++] = this;
|
|
SetCaCaps(cardIndex);
|
|
}
|
|
else
|
|
esyslog("ERROR: too many devices!");
|
|
}
|
|
|
|
cDevice::~cDevice()
|
|
{
|
|
Detach(player);
|
|
for (int i = 0; i < MAXRECEIVERS; i++)
|
|
Detach(receiver[i]);
|
|
}
|
|
|
|
void cDevice::SetUseDevice(int n)
|
|
{
|
|
if (n < MAXDEVICES)
|
|
useDevice |= (1 << n);
|
|
}
|
|
|
|
int cDevice::NextCardIndex(int n)
|
|
{
|
|
if (n > 0) {
|
|
nextCardIndex += n;
|
|
if (nextCardIndex >= MAXDEVICES)
|
|
esyslog("ERROR: nextCardIndex too big (%d)", nextCardIndex);
|
|
}
|
|
else if (n < 0)
|
|
esyslog("ERROR: illegal value in IncCardIndex(%d)", n);
|
|
return nextCardIndex;
|
|
}
|
|
|
|
int cDevice::DeviceNumber(void) const
|
|
{
|
|
for (int i = 0; i < numDevices; i++) {
|
|
if (device[i] == this)
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void cDevice::MakePrimaryDevice(bool On)
|
|
{
|
|
}
|
|
|
|
bool cDevice::SetPrimaryDevice(int n)
|
|
{
|
|
n--;
|
|
if (0 <= n && n < numDevices && device[n]) {
|
|
isyslog("setting primary device to %d", n + 1);
|
|
if (primaryDevice)
|
|
primaryDevice->MakePrimaryDevice(false);
|
|
primaryDevice = device[n];
|
|
primaryDevice->MakePrimaryDevice(true);
|
|
return true;
|
|
}
|
|
esyslog("invalid primary device number: %d", n + 1);
|
|
return false;
|
|
}
|
|
|
|
bool cDevice::HasDecoder(void) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool cDevice::PlayerDetached(void)
|
|
{
|
|
bool result = playerDetached;
|
|
playerDetached = false;
|
|
return result;
|
|
}
|
|
|
|
cOsdBase *cDevice::NewOsd(int x, int y)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
cSpuDecoder *cDevice::GetSpuDecoder(void)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
cDevice *cDevice::GetDevice(int Index)
|
|
{
|
|
return (0 <= Index && Index < numDevices) ? device[Index] : NULL;
|
|
}
|
|
|
|
cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers)
|
|
{
|
|
cDevice *d = NULL;
|
|
for (int i = 0; i < numDevices; i++) {
|
|
bool ndr;
|
|
if (device[i]->ProvidesChannel(Channel, Priority, &ndr) // this device is basicly able to do the job
|
|
&& (!d // we don't have a device yet, or...
|
|
|| (device[i]->Receiving() && !ndr) // ...this one is already receiving and allows additional receivers, or...
|
|
|| !d->Receiving() // ...the one we have is not receiving...
|
|
&& (device[i]->Priority() < d->Priority() // ...this one has an even lower Priority, or...
|
|
|| device[i]->Priority() == d->Priority() // ...same Priority...
|
|
&& device[i]->ProvidesCa(Channel->Ca()) < d->ProvidesCa(Channel->Ca()) // ...but this one provides fewer Ca values
|
|
)
|
|
)
|
|
) {
|
|
d = device[i];
|
|
if (NeedsDetachReceivers)
|
|
*NeedsDetachReceivers = ndr;
|
|
}
|
|
}
|
|
/*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(int Index)
|
|
{
|
|
for (int d = 0; d < numDevices; d++) {
|
|
if (Index < 0 || Index == device[d]->CardIndex()) {
|
|
for (int i = 0; i < MAXCACAPS; i++)
|
|
device[d]->caCaps[i] = Setup.CaCaps[device[d]->CardIndex()][i];
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void cDevice::SetVideoFormat(bool VideoFormat16_9)
|
|
{
|
|
}
|
|
|
|
//#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); }
|
|
#define PRINTPIDS(s)
|
|
|
|
bool cDevice::HasPid(int Pid) const
|
|
{
|
|
for (int i = 0; i < MAXPIDHANDLES; i++) {
|
|
if (pidHandles[i].pid == Pid)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
}
|
|
if (n >= 0) {
|
|
// The Pid is already in use
|
|
if (++pidHandles[n].used == 2 && n <= ptTeletext) {
|
|
// It's a special PID that may have to be switched into "tap" mode
|
|
PRINTPIDS("A");
|
|
return SetPid(&pidHandles[n], n, true);
|
|
}
|
|
PRINTPIDS("a");
|
|
return true;
|
|
}
|
|
else if (PidType < ptOther) {
|
|
// The Pid is not yet in use and it is a special one
|
|
n = PidType;
|
|
}
|
|
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].used = 1;
|
|
PRINTPIDS("C");
|
|
return SetPid(&pidHandles[n], n, true);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void cDevice::DelPid(int Pid)
|
|
{
|
|
if (Pid) {
|
|
for (int i = 0; i < MAXPIDHANDLES; i++) {
|
|
if (pidHandles[i].pid == Pid) {
|
|
PRINTPIDS("D");
|
|
if (--pidHandles[i].used < 2) {
|
|
SetPid(&pidHandles[i], i, false);
|
|
if (pidHandles[i].used == 0) {
|
|
pidHandles[i].handle = -1;
|
|
pidHandles[i].pid = 0;
|
|
}
|
|
}
|
|
PRINTPIDS("E");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool cDevice::SetPid(cPidHandle *Handle, int Type, bool On)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool cDevice::ProvidesSource(int Source) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool cDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool cDevice::SwitchChannel(const cChannel *Channel, bool LiveView)
|
|
{
|
|
if (LiveView)
|
|
isyslog("switching to channel %d", Channel->Number());
|
|
for (int i = 3; i--;) {
|
|
switch (SetChannel(Channel, LiveView)) {
|
|
case scrOk: return true;
|
|
case scrNotAvailable: if (Interface)
|
|
Interface->Error(tr("Channel not available!"));
|
|
return false;
|
|
case scrNoTransfer: if (Interface)
|
|
Interface->Error(tr("Can't start Transfer Mode!"));
|
|
return false;
|
|
case scrFailed: break; // loop will retry
|
|
}
|
|
esyslog("retrying");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool cDevice::SwitchChannel(int Direction)
|
|
{
|
|
bool result = false;
|
|
Direction = sgn(Direction);
|
|
if (Direction) {
|
|
int n = CurrentChannel() + Direction;
|
|
int first = n;
|
|
PrimaryDevice()->StopReplay(); // otherwise a running Transfer Mode would block channels
|
|
cChannel *channel;
|
|
while ((channel = Channels.GetByNumber(n)) != NULL) {
|
|
// try only channels which are currently available
|
|
if (PrimaryDevice()->ProvidesChannel(channel, Setup.PrimaryLimit) || GetDevice(channel, 0))
|
|
break;
|
|
n += Direction;
|
|
}
|
|
if (channel) {
|
|
int d = n - first;
|
|
if (abs(d) == 1)
|
|
dsyslog("skipped channel %d", first);
|
|
else if (d)
|
|
dsyslog("skipped channels %d..%d", first, n - sgn(d));
|
|
if (PrimaryDevice()->SwitchChannel(channel, true))
|
|
result = true;
|
|
}
|
|
else if (n != first && Interface)
|
|
Interface->Error(tr("Channel not available!"));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView)
|
|
{
|
|
if (LiveView)
|
|
StopReplay();
|
|
|
|
// 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 = (LiveView && IsPrimaryDevice() && !ProvidesChannel(Channel, Setup.PrimaryLimit));
|
|
|
|
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(Channel, 0);
|
|
if (CaDevice) {
|
|
cStatus::MsgChannelSwitch(this, 0); // only report status if we are actually going to switch the channel
|
|
if (CaDevice->SetChannel(Channel, false) == scrOk) // calling SetChannel() directly, not SwitchChannel()!
|
|
cControl::Launch(new cTransferControl(CaDevice, Channel->Vpid(), Channel->Apid1(), Channel->Apid2(), Channel->Dpid1(), Channel->Dpid2()));//XXX+
|
|
else
|
|
Result = scrNoTransfer;
|
|
}
|
|
else
|
|
Result = scrNotAvailable;
|
|
}
|
|
else {
|
|
cStatus::MsgChannelSwitch(this, 0); // only report status if we are actually going to switch the channel
|
|
if (!SetChannelDevice(Channel, LiveView))
|
|
Result = scrFailed;
|
|
}
|
|
|
|
if (Result == scrOk) {
|
|
if (LiveView && IsPrimaryDevice()) {
|
|
cSIProcessor::SetCurrentServiceID(Channel->Sid());
|
|
currentChannel = Channel->Number();
|
|
}
|
|
cStatus::MsgChannelSwitch(this, Channel->Number()); // only report status if channel switch successfull
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
bool cDevice::SetChannelDevice(const cChannel *Channel, bool LiveView)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void cDevice::SetVolumeDevice(int Volume)
|
|
{
|
|
}
|
|
|
|
int cDevice::NumAudioTracksDevice(void) const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
const char **cDevice::GetAudioTracksDevice(int *CurrentTrack) const
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
void cDevice::SetAudioTrackDevice(int Index)
|
|
{
|
|
}
|
|
|
|
bool cDevice::ToggleMute(void)
|
|
{
|
|
int OldVolume = volume;
|
|
mute = !mute;
|
|
SetVolume(0, mute);
|
|
volume = OldVolume;
|
|
return mute;
|
|
}
|
|
|
|
void cDevice::SetVolume(int Volume, bool Absolute)
|
|
{
|
|
volume = min(max(Absolute ? Volume : volume + Volume, 0), MAXVOLUME);
|
|
SetVolumeDevice(volume);
|
|
cStatus::MsgSetVolume(volume, Absolute);
|
|
if (volume > 0)
|
|
mute = false;
|
|
}
|
|
|
|
int cDevice::NumAudioTracks(void) const
|
|
{
|
|
return player ? player->NumAudioTracks() : NumAudioTracksDevice();
|
|
}
|
|
|
|
const char **cDevice::GetAudioTracks(int *CurrentTrack) const
|
|
{
|
|
return player ? player->GetAudioTracks(CurrentTrack) : GetAudioTracksDevice(CurrentTrack);
|
|
}
|
|
|
|
void cDevice::SetAudioTrack(int Index)
|
|
{
|
|
if (player)
|
|
player->SetAudioTrack(Index);
|
|
else
|
|
SetAudioTrackDevice(Index);
|
|
}
|
|
|
|
bool cDevice::SetPlayMode(ePlayMode PlayMode)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void cDevice::TrickSpeed(int Speed)
|
|
{
|
|
}
|
|
|
|
void cDevice::Clear(void)
|
|
{
|
|
}
|
|
|
|
void cDevice::Play(void)
|
|
{
|
|
}
|
|
|
|
void cDevice::Freeze(void)
|
|
{
|
|
}
|
|
|
|
void cDevice::Mute(void)
|
|
{
|
|
}
|
|
|
|
void cDevice::StillPicture(const uchar *Data, int Length)
|
|
{
|
|
}
|
|
|
|
bool cDevice::Replaying(void) const
|
|
{
|
|
return player != NULL;
|
|
}
|
|
|
|
bool cDevice::AttachPlayer(cPlayer *Player)
|
|
{
|
|
if (HasDecoder()) {
|
|
if (player)
|
|
Detach(player);
|
|
player = Player;
|
|
player->device = this;
|
|
SetPlayMode(player->playMode);
|
|
player->Activate(true);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void cDevice::Detach(cPlayer *Player)
|
|
{
|
|
if (Player && player == Player) {
|
|
player->Activate(false);
|
|
player->device = NULL;
|
|
player = NULL;
|
|
SetPlayMode(pmNone);
|
|
playerDetached = true;
|
|
}
|
|
}
|
|
|
|
void cDevice::StopReplay(void)
|
|
{
|
|
if (player) {
|
|
Detach(player);
|
|
if (IsPrimaryDevice())
|
|
cControl::Shutdown();
|
|
}
|
|
}
|
|
|
|
bool cDevice::Poll(cPoller &Poller, int TimeoutMs)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int cDevice::PlayVideo(const uchar *Data, int Length)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
int cDevice::PlayAudio(const uchar *Data, int Length)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
int cDevice::Priority(void) const
|
|
{
|
|
int priority = IsPrimaryDevice() ? Setup.PrimaryLimit - 1 : 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) const
|
|
{
|
|
return -1;//XXX+ too complex with multiple recordings per device
|
|
// Test whether a receiver on this 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) const
|
|
{
|
|
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) const
|
|
{
|
|
for (int i = 0; i < MAXRECEIVERS; i++) {
|
|
if (receiver[i] && receiver[i]->priority >= 0) // cReceiver with priority < 0 doesn't count
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void cDevice::Action(void)
|
|
{
|
|
dsyslog("receiver thread started on device %d (pid=%d)", CardIndex() + 1, getpid());
|
|
|
|
if (OpenDvr()) {
|
|
time_t t = time(NULL);
|
|
active = true;
|
|
for (; active;) {
|
|
// Read data from the DVR device:
|
|
uchar *b = NULL;
|
|
if (GetTSPacket(b)) {
|
|
if (b) {
|
|
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
|
|
break;
|
|
|
|
//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);
|
|
}
|
|
}
|
|
CloseDvr();
|
|
}
|
|
|
|
dsyslog("receiver thread ended on device %d (pid=%d)", CardIndex() + 1, getpid());
|
|
}
|
|
|
|
bool cDevice::OpenDvr(void)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void cDevice::CloseDvr(void)
|
|
{
|
|
}
|
|
|
|
bool cDevice::GetTSPacket(uchar *&Data)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool cDevice::AttachReceiver(cReceiver *Receiver)
|
|
{
|
|
if (!Receiver)
|
|
return false;
|
|
if (Receiver->device == this)
|
|
return true;
|
|
for (int i = 0; i < MAXRECEIVERS; i++) {
|
|
if (!receiver[i]) {
|
|
for (int n = 0; n < MAXRECEIVEPIDS; n++)
|
|
AddPid(Receiver->pids[n]);//XXX+ retval!
|
|
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++)
|
|
DelPid(Receiver->pids[n]);
|
|
}
|
|
else if (receiver[i])
|
|
receiversLeft = true;
|
|
}
|
|
if (!receiversLeft) {
|
|
active = false;
|
|
Cancel(3);
|
|
}
|
|
}
|
|
|
|
// --- cTSBuffer -------------------------------------------------------------
|
|
|
|
cTSBuffer::cTSBuffer(int File, int Size, int CardIndex)
|
|
{
|
|
f = File;
|
|
size = Size / TS_SIZE * TS_SIZE;
|
|
cardIndex = CardIndex;
|
|
tsRead = tsWrite = 0;
|
|
buf = (f >= 0 && size >= TS_SIZE) ? MALLOC(uchar, size + TS_SIZE) : NULL;
|
|
// the '+ TS_SIZE' allocates some extra space for handling packets that got split by a buffer roll-over
|
|
firstRead = true;
|
|
}
|
|
|
|
cTSBuffer::~cTSBuffer()
|
|
{
|
|
free(buf);
|
|
}
|
|
|
|
int cTSBuffer::Read(void)
|
|
{
|
|
if (buf) {
|
|
cPoller Poller(f, false);
|
|
bool repeat;
|
|
int total = 0;
|
|
do {
|
|
repeat = false;
|
|
if (firstRead || Used() > TS_SIZE || Poller.Poll(100)) { // only wait if there's not enough data in the buffer
|
|
firstRead = false;
|
|
if (tsRead == tsWrite)
|
|
tsRead = tsWrite = 0; // keep the maximum buffer space available
|
|
if (tsWrite >= size && tsRead > 0)
|
|
tsWrite = 0;
|
|
int free = tsRead <= tsWrite ? size - tsWrite : tsRead - tsWrite - 1;
|
|
if (free > 0) {
|
|
int r = read(f, buf + tsWrite, free);
|
|
if (r > 0) {
|
|
total += r;
|
|
tsWrite += r;
|
|
if (tsWrite >= size && tsRead > 0) {
|
|
tsWrite = 0;
|
|
repeat = true; // read again after a boundary roll-over
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} while (repeat);
|
|
return total;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
uchar *cTSBuffer::Get(void)
|
|
{
|
|
if (Used() >= TS_SIZE) {
|
|
uchar *p = buf + tsRead;
|
|
if (*p != TS_SYNC_BYTE) {
|
|
esyslog("ERROR: not sync'ed to TS packet on device %d", cardIndex);
|
|
int tsMax = tsRead < tsWrite ? tsWrite : size;
|
|
for (int i = tsRead; i < tsMax; i++) {
|
|
if (buf[i] == TS_SYNC_BYTE) {
|
|
esyslog("ERROR: skipped %d bytes to sync on TS packet on device %d", i - tsRead, cardIndex);
|
|
tsRead = i;
|
|
return NULL;
|
|
}
|
|
}
|
|
if ((tsRead = tsMax) >= size)
|
|
tsRead = 0;
|
|
return NULL;
|
|
}
|
|
if (tsRead + TS_SIZE > size) {
|
|
// the packet rolled over the buffer boundary, so let's fetch the rest from the beginning (which MUST be there, since Used() >= TS_SIZE)
|
|
int rest = TS_SIZE - (size - tsRead);
|
|
memcpy(buf + size, buf, rest);
|
|
tsRead = rest;
|
|
}
|
|
else if ((tsRead += TS_SIZE) >= size)
|
|
tsRead = 0;
|
|
return p;
|
|
}
|
|
return NULL;
|
|
}
|