1
0
mirror of https://github.com/VDR4Arch/vdr.git synced 2023-10-10 13:36:52 +02:00
vdr/device.c

883 lines
22 KiB
C
Raw Normal View History

/*
* device.c: The basic device interface
*
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
2004-10-23 10:18:01 +02:00
* $Id: device.c 1.61 2004/10/23 10:15:31 kls Exp $
*/
#include "device.h"
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
2002-11-03 11:53:58 +01:00
#include "audio.h"
#include "channels.h"
#include "i18n.h"
#include "player.h"
#include "receiver.h"
#include "status.h"
2002-06-22 13:45:53 +02:00
#include "transfer.h"
2002-09-08 09:03:10 +02:00
// --- cDevice ---------------------------------------------------------------
// The default priority for non-primary devices:
#define DEFAULTPRIORITY -1
int cDevice::numDevices = 0;
int cDevice::useDevice = 0;
int cDevice::nextCardIndex = 0;
int cDevice::currentChannel = 1;
cDevice *cDevice::device[MAXDEVICES] = { NULL };
cDevice *cDevice::primaryDevice = NULL;
cDevice::cDevice(void)
{
cardIndex = nextCardIndex++;
SetDescription("receiver on device %d", CardIndex() + 1);
SetVideoFormat(Setup.VideoFormat);
active = false;
mute = false;
volume = Setup.CurrentVolume;
sectionHandler = NULL;
eitFilter = NULL;
patFilter = NULL;
sdtFilter = NULL;
nitFilter = NULL;
ciHandler = NULL;
player = NULL;
for (int i = 0; i < MAXRECEIVERS; i++)
receiver[i] = NULL;
2004-02-08 15:11:07 +01:00
if (numDevices < MAXDEVICES)
device[numDevices++] = this;
else
esyslog("ERROR: too many devices!");
}
cDevice::~cDevice()
{
Detach(player);
for (int i = 0; i < MAXRECEIVERS; i++)
Detach(receiver[i]);
delete ciHandler;
delete nitFilter;
delete sdtFilter;
delete patFilter;
delete eitFilter;
delete sectionHandler;
}
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;
}
2002-10-07 16:21:46 +02:00
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("ERROR: invalid primary device number: %d", n + 1);
return false;
}
bool cDevice::HasDecoder(void) const
{
return false;
}
2002-09-08 14:17:51 +02:00
cSpuDecoder *cDevice::GetSpuDecoder(void)
{
return NULL;
}
2003-05-11 09:01:51 +02:00
cDevice *cDevice::ActualDevice(void)
{
cDevice *d = cTransferControl::ReceiverDevice();
if (!d)
d = PrimaryDevice();
return d;
}
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;
int select = 7, pri;
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
if (device[i]->Receiving() && !ndr)
pri = 0; // receiving and allows additional receivers
else if (d && !device[i]->Receiving() && device[i]->ProvidesCa(Channel) < d->ProvidesCa(Channel))
pri = 1; // free and fewer Ca's
else if (!device[i]->Receiving() && !device[i]->IsPrimaryDevice())
pri = 2; // free and not the primary device
else if (!device[i]->Receiving())
pri = 3; // free
else if (d && device[i]->Priority() < d->Priority())
pri = 4; // receiving but priority is lower
else if (d && device[i]->Priority() == d->Priority() && device[i]->ProvidesCa(Channel) < d->ProvidesCa(Channel))
pri = 5; // receiving with same priority but fewer Ca's
else
pri = 6; // all others
if (pri < select) {
select = pri;
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::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)
{
}
eVideoSystem cDevice::GetVideoSystem(void)
{
return vsPAL;
}
//#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)
2002-09-29 13:40:45 +02:00
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 || PidType == ptPcr) {
int n = -1;
int a = -1;
if (PidType != ptPcr) { // PPID always has to be explicit
for (int i = 0; i < MAXPIDHANDLES; i++) {
if (i != ptPcr) {
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");
if (!SetPid(&pidHandles[n], n, true)) {
esyslog("ERROR: can't set PID %d on device %d", Pid, CardIndex() + 1);
DelPid(Pid, PidType);
return false;
}
}
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 on device %d", Pid, CardIndex() + 1);
return false;
}
if (n >= 0) {
pidHandles[n].pid = Pid;
pidHandles[n].used = 1;
PRINTPIDS("C");
if (!SetPid(&pidHandles[n], n, true)) {
esyslog("ERROR: can't set PID %d on device %d", Pid, CardIndex() + 1);
DelPid(Pid, PidType);
return false;
}
}
}
return true;
}
void cDevice::DelPid(int Pid, ePidType PidType)
{
if (Pid || PidType == ptPcr) {
int n = -1;
if (PidType == ptPcr)
n = PidType; // PPID always has to be explicit
else {
for (int i = 0; i < MAXPIDHANDLES; i++) {
if (pidHandles[i].pid == Pid) {
n = i;
break;
}
}
}
if (n >= 0 && pidHandles[n].used) {
PRINTPIDS("D");
if (--pidHandles[n].used < 2) {
SetPid(&pidHandles[n], n, false);
if (pidHandles[n].used == 0) {
pidHandles[n].handle = -1;
pidHandles[n].pid = 0;
}
}
PRINTPIDS("E");
}
}
}
bool cDevice::SetPid(cPidHandle *Handle, int Type, bool On)
{
return false;
}
void cDevice::StartSectionHandler(void)
{
if (!sectionHandler) {
sectionHandler = new cSectionHandler(this);
AttachFilter(eitFilter = new cEitFilter);
AttachFilter(patFilter = new cPatFilter);
AttachFilter(sdtFilter = new cSdtFilter(patFilter));
AttachFilter(nitFilter = new cNitFilter);
}
}
int cDevice::OpenFilter(u_short Pid, u_char Tid, u_char Mask)
{
return -1;
}
void cDevice::AttachFilter(cFilter *Filter)
{
if (sectionHandler)
sectionHandler->Attach(Filter);
}
void cDevice::Detach(cFilter *Filter)
{
if (sectionHandler)
sectionHandler->Detach(Filter);
}
bool cDevice::ProvidesSource(int Source) const
{
return false;
}
bool cDevice::ProvidesTransponder(const cChannel *Channel) const
{
return false;
}
2002-09-29 13:40:45 +02:00
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: Skins.Message(mtInfo, tr("Channel not available!"));
return false;
2004-05-16 10:35:36 +02:00
case scrNoTransfer: Skins.Message(mtError, 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;
cChannel *channel;
2002-10-19 15:33:37 +02:00
while ((channel = Channels.GetByNumber(n, Direction)) != NULL) {
// try only channels which are currently available
if (PrimaryDevice()->ProvidesChannel(channel, Setup.PrimaryLimit) || PrimaryDevice()->CanReplay() && GetDevice(channel, 0))
break;
n = channel->Number() + 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;
}
2004-05-16 10:35:36 +02:00
else if (n != first)
Skins.Message(mtError, 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 && CanReplay()) {
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 {
Channels.Lock(false);
cStatus::MsgChannelSwitch(this, 0); // only report status if we are actually going to switch the channel
// Stop section handling:
if (sectionHandler) {
sectionHandler->SetStatus(false);
sectionHandler->SetChannel(NULL);
}
if (SetChannelDevice(Channel, LiveView)) {
// Start section handling:
if (sectionHandler) {
sectionHandler->SetChannel(Channel);
sectionHandler->SetStatus(true);
}
}
else
Result = scrFailed;
Channels.Unlock();
}
if (Result == scrOk) {
if (LiveView && IsPrimaryDevice())
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;
}
bool cDevice::HasLock(void)
{
return true;
}
bool cDevice::HasProgramme(void)
{
return Replaying() || pidHandles[ptAudio].pid || pidHandles[ptVideo].pid;
}
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;
//XXX why is it necessary to use different sequences???
if (mute) {
SetVolume(0, mute);
Audios.MuteAudio(mute); // Mute external audio after analog audio
}
else {
Audios.MuteAudio(mute); // Enable external audio before analog audio
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);
2002-11-03 11:53:58 +01:00
if (volume > 0) {
mute = false;
2002-11-03 11:53:58 +01:00
Audios.MuteAudio(mute);
}
}
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::CanReplay(void) const
{
return HasDecoder();
}
bool cDevice::SetPlayMode(ePlayMode PlayMode)
{
return false;
}
2003-11-07 14:16:25 +01:00
int64_t cDevice::GetSTC(void)
{
return -1;
}
void cDevice::TrickSpeed(int Speed)
{
}
void cDevice::Clear(void)
{
2002-11-03 11:53:58 +01:00
Audios.ClearAudio();
}
void cDevice::Play(void)
{
Audios.MuteAudio(mute);
}
void cDevice::Freeze(void)
{
Audios.MuteAudio(true);
}
void cDevice::Mute(void)
{
2002-11-03 11:53:58 +01:00
Audios.MuteAudio(true);
}
void cDevice::StillPicture(const uchar *Data, int Length)
{
}
2002-09-29 13:40:45 +02:00
bool cDevice::Replaying(void) const
{
return player != NULL;
}
2002-06-22 13:45:53 +02:00
bool cDevice::AttachPlayer(cPlayer *Player)
{
if (CanReplay()) {
if (player)
Detach(player);
player = Player;
SetPlayMode(player->playMode);
2004-10-23 10:18:01 +02:00
player->device = this;
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);
Audios.ClearAudio();
}
}
void cDevice::StopReplay(void)
{
if (player) {
Detach(player);
if (IsPrimaryDevice())
cControl::Shutdown();
}
}
2002-08-16 09:22:29 +02:00
bool cDevice::Poll(cPoller &Poller, int TimeoutMs)
2002-08-15 10:13:03 +02:00
{
return false;
}
2004-06-19 08:58:14 +02:00
bool cDevice::Flush(int TimeoutMs)
{
return true;
}
int cDevice::PlayVideo(const uchar *Data, int Length)
{
return -1;
}
2002-11-03 11:53:58 +01:00
void cDevice::PlayAudio(const uchar *Data, int Length)
{
2002-11-03 11:53:58 +01:00
Audios.PlayAudio(Data, Length);
}
int cDevice::Ca(void) const
{
int ca = 0;
for (int i = 0; i < MAXRECEIVERS; i++) {
if (receiver[i] && (ca = receiver[i]->ca) != 0)
break; // all receivers have the same ca
}
return ca;
}
2002-09-29 13:40:45 +02:00
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;
}
2002-09-29 13:40:45 +02:00
int cDevice::CanShift(int Ca, int Priority, int UsedCards) const
{
return -1;//XXX+ too complex with multiple recordings per device
/*XXX
// 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;
XXX*/
}
int cDevice::ProvidesCa(const cChannel *Channel) const
{
int Ca = Channel->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
return !Ca; // by default every card can provide FTA
}
bool cDevice::Receiving(bool CheckAny) const
{
for (int i = 0; i < MAXRECEIVERS; i++) {
if (receiver[i] && (CheckAny || receiver[i]->priority >= 0)) // cReceiver with priority < 0 doesn't count
return true;
}
return false;
}
void cDevice::Action(void)
{
if (OpenDvr()) {
active = true;
for (; active;) {
// Read data from the DVR device:
2002-09-08 09:03:10 +02:00
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();
}
}
2002-09-08 09:03:10 +02:00
else
break;
}
CloseDvr();
}
}
bool cDevice::OpenDvr(void)
{
return false;
}
void cDevice::CloseDvr(void)
{
}
2002-09-08 09:03:10 +02:00
bool cDevice::GetTSPacket(uchar *&Data)
{
2002-09-08 09:03:10 +02:00
return false;
}
2002-06-22 13:45:53 +02:00
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++) {
if (!AddPid(Receiver->pids[n])) {
for ( ; n-- > 0; )
DelPid(Receiver->pids[n]);
return false;
}
}
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();
2002-07-28 11:29:32 +02:00
for (int n = 0; n < MAXRECEIVEPIDS; n++)
DelPid(Receiver->pids[n]);
}
else if (receiver[i])
receiversLeft = true;
}
if (!receiversLeft) {
active = false;
Cancel(3);
}
}
2002-09-08 09:03:10 +02:00
// --- cTSBuffer -------------------------------------------------------------
cTSBuffer::cTSBuffer(int File, int Size, int CardIndex)
{
2004-10-16 09:36:28 +02:00
SetDescription("TS buffer on device %d", CardIndex);
2002-09-08 09:03:10 +02:00
f = File;
cardIndex = CardIndex;
2004-10-16 09:36:28 +02:00
active = false;
delivered = false;
ringBuffer = new cRingBufferLinear(Size, TS_SIZE, true, "TS");
ringBuffer->SetTimeouts(100, 100);
Start();
2002-09-08 09:03:10 +02:00
}
cTSBuffer::~cTSBuffer()
{
2004-10-16 09:36:28 +02:00
active = false;
Cancel(3);
delete ringBuffer;
}
void cTSBuffer::Action(void)
{
if (ringBuffer) {
bool firstRead = true;
cPoller Poller(f);
active = true;
for (; active;) {
if (firstRead || Poller.Poll(100)) {
firstRead = false;
int r = ringBuffer->Read(f);
if (r < 0 && FATALERRNO) {
if (errno == EOVERFLOW)
esyslog("ERROR: driver buffer overflow on device %d", cardIndex);
else {
LOG_ERROR;
break;
}
}
}
}
2002-09-08 09:03:10 +02:00
}
}
uchar *cTSBuffer::Get(void)
{
2004-10-16 09:36:28 +02:00
int Count = 0;
if (delivered) {
ringBuffer->Del(TS_SIZE);
delivered = false;
}
uchar *p = ringBuffer->Get(Count);
if (p && Count >= TS_SIZE) {
2002-09-08 09:03:10 +02:00
if (*p != TS_SYNC_BYTE) {
2004-10-16 09:36:28 +02:00
for (int i = 1; i < Count; i++) {
if (p[i] == TS_SYNC_BYTE) {
Count = i;
break;
2002-09-08 09:03:10 +02:00
}
}
2004-10-16 09:36:28 +02:00
ringBuffer->Del(Count);
esyslog("ERROR: skipped %d bytes to sync on TS packet on device %d", Count, cardIndex);
2002-09-08 09:03:10 +02:00
return NULL;
}
2004-10-16 09:36:28 +02:00
delivered = true;
2002-09-08 09:03:10 +02:00
return p;
}
return NULL;
}