diff --git a/server/connection.c b/server/connection.c index 1cd1d21..764ca02 100644 --- a/server/connection.c +++ b/server/connection.c @@ -9,17 +9,10 @@ #include #include -#include #include #include #include -// device occupied timeout to prevent VDR main loop to immediately switch back -// when streamdev switched the live TV channel. -// Note that there is still a gap between the GetDevice() and SetOccupied() -// calls where the VDR main loop could strike -#define STREAMDEVTUNETIMEOUT 5 - cServerConnection::cServerConnection(const char *Protocol, int Type): cTBSocket(Type), m_Protocol(Protocol), @@ -28,9 +21,7 @@ cServerConnection::cServerConnection(const char *Protocol, int Type): m_ReadBytes(0), m_WriteBytes(0), m_WriteIndex(0), - m_Streamer(NULL), - m_OccupiedDev(NULL), - m_SwitchTo(NULL) + m_Streamer(NULL) { } @@ -206,66 +197,5 @@ bool cServerConnection::Close() return cTBSocket::Close(); } -bool cServerConnection::UsedByLiveTV(cDevice *device) -{ - return device == cTransferControl::ReceiverDevice() || - (device->IsPrimaryDevice() && device->HasDecoder() && !device->Replaying()); -} - -cDevice *cServerConnection::SwitchDevice(const cChannel *Channel, int Priority) -{ - // turn off the streams of this connection - Detach(); - - cDevice *device = cDevice::GetDevice(Channel, Priority, false); - if (!device) { - // can't switch - continue the current stream - Attach(); - dsyslog("streamdev: GetDevice failed for channel %d (%s) at priority %d (PrimaryDevice=%d, ActualDevice=%d)", Channel->Number(), Channel->Name(), Priority, cDevice::PrimaryDevice()->CardIndex(), cDevice::ActualDevice()->CardIndex()); - } - else if (!device->IsTunedToTransponder(Channel) && UsedByLiveTV(device)) { - // make sure VDR main loop doesn't switch back - device->SetOccupied(STREAMDEVTUNETIMEOUT); - if (device->SwitchChannel(Channel, false)) { - // switched away live TV - m_OccupiedDev = device; - m_SwitchTo = Channel; - } - else { - dsyslog("streamdev: SwitchChannel (live) failed for channel %d (%s) at priority %d (PrimaryDevice=%d, ActualDevice=%d, device=%d)", Channel->Number(), Channel->Name(), Priority, cDevice::PrimaryDevice()->CardIndex(), cDevice::ActualDevice()->CardIndex(), device->CardIndex()); - device->SetOccupied(0); - device = NULL; - } - } - else if (!device->SwitchChannel(Channel, false)) { - dsyslog("streamdev: SwitchChannel failed for channel %d (%s) at priority %d (PrimaryDevice=%d, ActualDevice=%d, device=%d)", Channel->Number(), Channel->Name(), Priority, cDevice::PrimaryDevice()->CardIndex(), cDevice::ActualDevice()->CardIndex(), device->CardIndex()); - device = NULL; - } - return device; -} - -bool cServerConnection::ProvidesChannel(const cChannel *Channel, int Priority) -{ - cDevice *device = cDevice::GetDevice(Channel, Priority, false, true); - if (!device) - dsyslog("streamdev: No device provides channel %d (%s) at priority %d", Channel->Number(), Channel->Name(), Priority); - return device; -} - -void cServerConnection::MainThreadHook() -{ - if (m_SwitchTo) - { - // switched away live TV. Try previous channel on other device first - if (!Channels.SwitchTo(cDevice::CurrentChannel())) { - // switch to streamdev channel otherwise - Channels.SwitchTo(m_SwitchTo->Number()); - Skins.Message(mtInfo, tr("Streaming active")); - } - m_OccupiedDev->SetOccupied(0); - m_SwitchTo = NULL; - } -} - cString cServerConnection::ToText() const { return cString::sprintf("%s\t%s:%d", Protocol(), RemoteIp().c_str(), RemotePort()); } diff --git a/server/connection.h b/server/connection.h index 1c40fb6..e7379be 100644 --- a/server/connection.h +++ b/server/connection.h @@ -16,7 +16,6 @@ typedef std::map tStrStrMap; typedef std::pair tStrStr; class cChannel; -class cDevice; /* Basic capabilities of a straight text-based protocol, most functions virtual to support more complicated protocols */ @@ -37,17 +36,8 @@ private: cStreamdevStreamer *m_Streamer; - /* Set to occupied device when live TV was interrupted */ - cDevice *m_OccupiedDev; - /* Set to this connection's current channel when live TV was interrupted */ - const cChannel *m_SwitchTo; - tStrStrMap m_Headers; - /* Test if device is in use as the transfer mode receiver device - or a FF card, displaying live TV from internal tuner */ - static bool UsedByLiveTV(cDevice *device); - protected: /* Will be called when a command terminated by a newline has been received */ @@ -113,24 +103,8 @@ public: /* Close the socket */ virtual bool Close(void); - /* Check if a device would be available for transferring the given - channel. This call has no side effects. */ - static cDevice *CheckDevice(const cChannel *Channel, int Priority, bool LiveView, const cDevice *AvoidDevice = NULL); - - /* Find a suitable device and tune it to the requested channel. */ - cDevice *SwitchDevice(const cChannel *Channel, int Priority); - - /* Test if a call to GetDevice would return a usable device. */ - bool ProvidesChannel(const cChannel *Channel, int Priority); - - /* Do things which must be done in VDR's main loop */ - void MainThreadHook(); - virtual void Flushed(void) {} - virtual void Attach(void) { if (m_Streamer != NULL) m_Streamer->Attach(); } - virtual void Detach(void) { if (m_Streamer != NULL) m_Streamer->Detach(); } - /* This connections protocol name */ virtual const char* Protocol(void) const { return m_Protocol; } diff --git a/server/connectionHTTP.c b/server/connectionHTTP.c index 0f89a95..bcb4b58 100644 --- a/server/connectionHTTP.c +++ b/server/connectionHTTP.c @@ -173,14 +173,10 @@ bool cConnectionHTTP::ProcessRequest(void) if (m_MenuList) return Respond("%s", true, m_MenuList->HttpHeader().c_str()); else if (m_Channel != NULL) { - cDevice *device = NULL; - if (ProvidesChannel(m_Channel, StreamdevServerSetup.HTTPPriority)) - device = SwitchDevice(m_Channel, StreamdevServerSetup.HTTPPriority); - if (device != NULL) { - cStreamdevLiveStreamer* liveStreamer = new cStreamdevLiveStreamer(StreamdevServerSetup.HTTPPriority, this); - SetStreamer(liveStreamer); - if (liveStreamer->SetChannel(m_Channel, m_StreamType, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL)) { - liveStreamer->SetDevice(device); + if (cStreamdevLiveStreamer::ProvidesChannel(m_Channel, StreamdevServerSetup.HTTPPriority)) { + cStreamdevLiveStreamer* liveStreamer = new cStreamdevLiveStreamer(this, m_Channel, StreamdevServerSetup.HTTPPriority, m_StreamType, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL); + if (liveStreamer->GetDevice()) { + SetStreamer(liveStreamer); if (!SetDSCP()) LOG_ERROR_STR("unable to set DSCP sockopt"); if (m_StreamType == stEXT) { @@ -194,6 +190,7 @@ bool cConnectionHTTP::ProcessRequest(void) } } SetStreamer(NULL); + delete liveStreamer; } return HttpResponse(503, true); } @@ -233,10 +230,9 @@ bool cConnectionHTTP::ProcessRequest(void) return Respond("%s", true, m_MenuList->HttpHeader().c_str()); } else if (m_Channel != NULL) { - if (ProvidesChannel(m_Channel, StreamdevServerSetup.HTTPPriority)) { + if (cStreamdevLiveStreamer::ProvidesChannel(m_Channel, StreamdevServerSetup.HTTPPriority)) { if (m_StreamType == stEXT) { - cStreamdevLiveStreamer *liveStreamer = new cStreamdevLiveStreamer(StreamdevServerSetup.HTTPPriority, this); - liveStreamer->SetChannel(m_Channel, m_StreamType, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL); + cStreamdevLiveStreamer *liveStreamer = new cStreamdevLiveStreamer(this, m_Channel, IDLEPRIORITY, m_StreamType, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL); SetStreamer(liveStreamer); return Respond("HTTP/1.0 200 OK"); } else if (m_StreamType == stES && (m_Apid[0] || m_Dpid[0] || ISRADIO(m_Channel))) { diff --git a/server/connectionIGMP.c b/server/connectionIGMP.c index b73324b..ced2209 100644 --- a/server/connectionIGMP.c +++ b/server/connectionIGMP.c @@ -40,22 +40,19 @@ bool cConnectionIGMP::SetChannel(cChannel *Channel, in_addr_t Dst) void cConnectionIGMP::Welcome() { - cDevice *device = NULL; - if (ProvidesChannel(m_Channel, StreamdevServerSetup.IGMPPriority)) - device = SwitchDevice(m_Channel, StreamdevServerSetup.IGMPPriority); - if (device != NULL) { - cStreamdevLiveStreamer * liveStreamer = new cStreamdevLiveStreamer(StreamdevServerSetup.IGMPPriority, this); - SetStreamer(liveStreamer); - if (liveStreamer->SetChannel(m_Channel, m_StreamType)) { - liveStreamer->SetDevice(device); + if (cStreamdevLiveStreamer::ProvidesChannel(m_Channel, StreamdevServerSetup.IGMPPriority)) { + cStreamdevLiveStreamer * liveStreamer = new cStreamdevLiveStreamer(this, m_Channel, StreamdevServerSetup.IGMPPriority, m_StreamType); + if (liveStreamer->GetDevice()) { + SetStreamer(liveStreamer); if (!SetDSCP()) LOG_ERROR_STR("unable to set DSCP sockopt"); Dprintf("streamer start\n"); liveStreamer->Start(this); } else { - esyslog("streamdev-server IGMP: SetChannel failed"); SetStreamer(NULL); + delete liveStreamer; + esyslog("streamdev-server IGMP: SetChannel failed"); } } else diff --git a/server/connectionVTP.c b/server/connectionVTP.c index 7ea1768..802748e 100644 --- a/server/connectionVTP.c +++ b/server/connectionVTP.c @@ -787,12 +787,12 @@ void cConnectionVTP::Reject(void) void cConnectionVTP::Detach(void) { if (m_FilterStreamer) m_FilterStreamer->Detach(); - cServerConnection::Detach(); + if (m_LiveSocket && Streamer()) ((cStreamdevLiveStreamer *) Streamer())->Detach(); } void cConnectionVTP::Attach(void) { - cServerConnection::Attach(); + if (m_LiveSocket && Streamer()) ((cStreamdevLiveStreamer *) Streamer())->Attach(); if (m_FilterStreamer) m_FilterStreamer->Attach(); } @@ -936,7 +936,7 @@ bool cConnectionVTP::CmdPROV(char *Opts) LOOP_PREVENTION(chan); - if (ProvidesChannel(chan, prio)) { + if (cStreamdevLiveStreamer::ProvidesChannel(chan, prio)) { m_TuneChannel = chan; m_TunePriority = prio; return Respond(220, "Channel available"); @@ -945,7 +945,7 @@ bool cConnectionVTP::CmdPROV(char *Opts) // so get our own receiver temporarily out of the way if (m_ClientVersion == 0) { Detach(); - bool provided = ProvidesChannel(chan, prio); + bool provided = cStreamdevLiveStreamer::ProvidesChannel(chan, prio); Attach(); if (provided) { m_TuneChannel = chan; @@ -1117,16 +1117,18 @@ bool cConnectionVTP::CmdTUNE(char *Opts) if (chan != m_TuneChannel) { isyslog("streamdev-server TUNE %s: Priority unknown - using 0", Opts); prio = 0; - if (!ProvidesChannel(chan, prio)) + if (!cStreamdevLiveStreamer::ProvidesChannel(chan, prio)) return Respond(560, "Channel not available (ProvidesChannel)"); } - if ((dev = SwitchDevice(chan, prio)) == NULL) - return Respond(560, "Channel not available (SwitchDevice)"); - cStreamdevLiveStreamer* liveStreamer = new cStreamdevLiveStreamer(prio, this); + cStreamdevLiveStreamer* liveStreamer = new cStreamdevLiveStreamer(this, chan, prio, m_StreamType); + if ((dev = liveStreamer->GetDevice()) == NULL) { + SetStreamer(NULL); + delete liveStreamer; + return Respond(560, "Channel not available (SwitchDevice)"); + } SetStreamer(liveStreamer); - liveStreamer->SetChannel(chan, m_StreamType); - liveStreamer->SetDevice(dev); + if(m_LiveSocket) liveStreamer->Start(m_LiveSocket); diff --git a/server/livestreamer.c b/server/livestreamer.c index c06710a..bb21ef1 100644 --- a/server/livestreamer.c +++ b/server/livestreamer.c @@ -8,6 +8,7 @@ #include "remux/ts2es.h" #include "remux/extern.h" +#include #include #include "server/livestreamer.h" @@ -16,6 +17,12 @@ using namespace Streamdev; +// device occupied timeout to prevent VDR main loop to immediately switch back +// when streamdev switched the live TV channel. +// Note that there is still a gap between the GetDevice() and SetOccupied() +// calls where the VDR main loop could strike +#define STREAMDEVTUNETIMEOUT 5 + // --- cStreamdevLiveReceiver ------------------------------------------------- class cStreamdevLiveReceiver: public cReceiver { @@ -328,19 +335,28 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i // --- cStreamdevLiveStreamer ------------------------------------------------- -cStreamdevLiveStreamer::cStreamdevLiveStreamer(int Priority, const cServerConnection *Connection): +cStreamdevLiveStreamer::cStreamdevLiveStreamer(const cServerConnection *Connection, const cChannel *Channel, int Priority, eStreamType StreamType, const int* Apid, const int *Dpid) : cStreamdevStreamer("streamdev-livestreaming", Connection), m_Priority(Priority), m_NumPids(0), m_StreamType(stTSPIDS), - m_Channel(NULL), + m_Channel(Channel), m_Device(NULL), m_Receiver(NULL), m_PatFilter(NULL), - m_Remux(NULL) + m_Remux(NULL), + m_SwitchLive(false) { m_ReceiveBuffer = new cStreamdevBuffer(LIVEBUFSIZE, TS_SIZE *2, true, "streamdev-livestreamer"), m_ReceiveBuffer->SetTimeouts(0, 100); + if (Priority == IDLEPRIORITY) { + SetChannel(Channel, StreamType, Apid, Dpid); + } + else { + m_Device = SwitchDevice(Channel, Priority); + if (m_Device) + SetChannel(Channel, StreamType, Apid, Dpid); + } } cStreamdevLiveStreamer::~cStreamdevLiveStreamer() @@ -613,6 +629,66 @@ void cStreamdevLiveStreamer::Detach(void) } } +bool cStreamdevLiveStreamer::UsedByLiveTV(cDevice *device) +{ + return device == cTransferControl::ReceiverDevice() || + (device->IsPrimaryDevice() && device->HasDecoder() && !device->Replaying()); +} + +cDevice *cStreamdevLiveStreamer::SwitchDevice(const cChannel *Channel, int Priority) +{ + // turn off the streams of this connection + Detach(); + + cDevice *device = cDevice::GetDevice(Channel, Priority, false); + if (!device) { + // can't switch - continue the current stream + Attach(); + dsyslog("streamdev: GetDevice failed for channel %d (%s) at priority %d (PrimaryDevice=%d, ActualDevice=%d)", Channel->Number(), Channel->Name(), Priority, cDevice::PrimaryDevice()->CardIndex(), cDevice::ActualDevice()->CardIndex()); + } + else if (!device->IsTunedToTransponder(Channel) && UsedByLiveTV(device)) { + // make sure VDR main loop doesn't switch back + device->SetOccupied(STREAMDEVTUNETIMEOUT); + if (device->SwitchChannel(Channel, false)) { + // switched away live TV + m_SwitchLive = true; + } + else { + dsyslog("streamdev: SwitchChannel (live) failed for channel %d (%s) at priority %d (PrimaryDevice=%d, ActualDevice=%d, device=%d)", Channel->Number(), Channel->Name(), Priority, cDevice::PrimaryDevice()->CardIndex(), cDevice::ActualDevice()->CardIndex(), device->CardIndex()); + device->SetOccupied(0); + device = NULL; + } + } + else if (!device->SwitchChannel(Channel, false)) { + dsyslog("streamdev: SwitchChannel failed for channel %d (%s) at priority %d (PrimaryDevice=%d, ActualDevice=%d, device=%d)", Channel->Number(), Channel->Name(), Priority, cDevice::PrimaryDevice()->CardIndex(), cDevice::ActualDevice()->CardIndex(), device->CardIndex()); + device = NULL; + } + return device; +} + +bool cStreamdevLiveStreamer::ProvidesChannel(const cChannel *Channel, int Priority) +{ + cDevice *device = cDevice::GetDevice(Channel, Priority, false, true); + if (!device) + dsyslog("streamdev: No device provides channel %d (%s) at priority %d", Channel->Number(), Channel->Name(), Priority); + return device; +} + +void cStreamdevLiveStreamer::MainThreadHook() +{ + if (m_SwitchLive) { + // switched away live TV. Try previous channel on other device first + if (!Channels.SwitchTo(cDevice::CurrentChannel())) { + // switch to streamdev channel otherwise + Channels.SwitchTo(m_Channel->Number()); + Skins.Message(mtInfo, tr("Streaming active")); + } + if (m_Device) + m_Device->SetOccupied(0); + m_SwitchLive = false; + } +} + std::string cStreamdevLiveStreamer::Report(void) { std::string result; diff --git a/server/livestreamer.h b/server/livestreamer.h index b83bb99..4e89cf8 100644 --- a/server/livestreamer.h +++ b/server/livestreamer.h @@ -5,6 +5,7 @@ #include #include "server/streamer.h" +#include "server/streamdev-server.h" #include "common.h" #define LIVEBUFSIZE (20000 * TS_SIZE) @@ -17,7 +18,7 @@ class cStreamdevLiveReceiver; // --- cStreamdevLiveStreamer ------------------------------------------------- -class cStreamdevLiveStreamer: public cStreamdevStreamer { +class cStreamdevLiveStreamer: public cStreamdevStreamer, public cMainThreadHookSubscriber { private: int m_Priority; int m_Pids[MAXRECEIVEPIDS + 1]; @@ -29,10 +30,22 @@ private: cStreamdevBuffer *m_ReceiveBuffer; cStreamdevPatFilter *m_PatFilter; Streamdev::cTSRemux *m_Remux; + bool m_SwitchLive; void StartReceiver(void); bool HasPid(int Pid); + /* Test if device is in use as the transfer mode receiver device + or a FF card, displaying live TV from internal tuner */ + static bool UsedByLiveTV(cDevice *device); + + /* Find a suitable device and tune it to the requested channel. */ + cDevice *SwitchDevice(const cChannel *Channel, int Priority); + + void SetDevice(cDevice *Device) { m_Device = Device; } + + bool SetChannel(const cChannel *Channel, eStreamType StreamType, const int* Apid = NULL, const int* Dpid = NULL); + protected: virtual uchar* GetFromReceiver(int &Count) { return m_ReceiveBuffer->Get(Count); } virtual void DelFromReceiver(int Count) { m_ReceiveBuffer->Del(Count); } @@ -41,13 +54,11 @@ protected: virtual void Action(void); public: - cStreamdevLiveStreamer(int Priority, const cServerConnection *Connection); + cStreamdevLiveStreamer(const cServerConnection *Connection, const cChannel *Channel, int Priority, eStreamType StreamType, const int* Apid = NULL, const int* Dpid = NULL); virtual ~cStreamdevLiveStreamer(); - void SetDevice(cDevice *Device) { m_Device = Device; } bool SetPid(int Pid, bool On); bool SetPids(int Pid, const int *Pids1 = NULL, const int *Pids2 = NULL, const int *Pids3 = NULL); - bool SetChannel(const cChannel *Channel, eStreamType StreamType, const int* Apid = NULL, const int* Dpid = NULL); void SetPriority(int Priority); void GetSignal(int *DevNum, int *Strength, int *Quality) const; virtual cString ToText() const; @@ -61,6 +72,14 @@ public: virtual void Attach(void); virtual void Detach(void); + cDevice *GetDevice() const { return m_Device; } + + /* Test if a call to GetDevice would return a usable device. */ + static bool ProvidesChannel(const cChannel *Channel, int Priority); + + /* Do things which must be done in VDR's main loop */ + void MainThreadHook(); + // Statistical purposes: virtual std::string Report(void); }; diff --git a/server/streamdev-server.c b/server/streamdev-server.c index e5c6e45..f3e62df 100644 --- a/server/streamdev-server.c +++ b/server/streamdev-server.c @@ -18,6 +18,29 @@ #error "VDR-1.7.25 or greater required to compile server! Use 'make client' to compile client only." #endif +cList cMainThreadHookSubscriber::m_Subscribers; +cMutex cMainThreadHookSubscriber::m_Mutex; + +const cList& cMainThreadHookSubscriber::Subscribers(cMutexLock& Lock) +{ + Lock.Lock(&m_Mutex); + return m_Subscribers; +} + +cMainThreadHookSubscriber::cMainThreadHookSubscriber() +{ + m_Mutex.Lock(); + m_Subscribers.Add(this); + m_Mutex.Unlock(); +} + +cMainThreadHookSubscriber::~cMainThreadHookSubscriber() +{ + m_Mutex.Lock(); + m_Subscribers.Del(this, false); + m_Mutex.Unlock(); +} + const char *cPluginStreamdevServer::DESCRIPTION = trNOOP("VDR Streaming Server"); cPluginStreamdevServer::cPluginStreamdevServer(void) @@ -139,9 +162,9 @@ void cPluginStreamdevServer::MainThreadHook(void) m_Suspend = false; } - cThreadLock lock; - const cList& clients = cStreamdevServer::Clients(lock); - for (cServerConnection *s = clients.First(); s; s = clients.Next(s)) + cMutexLock lock; + const cList& subs = cMainThreadHookSubscriber::Subscribers(lock); + for (cMainThreadHookSubscriber *s = subs.First(); s; s = subs.Next(s)) s->MainThreadHook(); } diff --git a/server/streamdev-server.h b/server/streamdev-server.h index a89f6c7..e149fd3 100644 --- a/server/streamdev-server.h +++ b/server/streamdev-server.h @@ -7,8 +7,22 @@ #include "common.h" +#include #include +class cMainThreadHookSubscriber: public cListObject { +private: + static cList m_Subscribers; + static cMutex m_Mutex; +public: + static const cList& Subscribers(cMutexLock& Lock); + + virtual void MainThreadHook() = 0; + + cMainThreadHookSubscriber(); + virtual ~cMainThreadHookSubscriber(); +}; + class cPluginStreamdevServer : public cPlugin { private: static const char *DESCRIPTION;