diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 2e8d5e3..7dc9960 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -38,6 +38,7 @@ Rolf Ahrenberg for suggesting to include the charset in HTTP replies for requesting replacement of asprintf calls for suggesting to change the URL path from EXTERN to EXT + for suggesting increased thread priorities for cStreamdevWriter/Streamer Rantanen Teemu for providing vdr-incompletesections.diff @@ -153,7 +154,20 @@ carel for helping to find a way to cleanly shutdown externremux with mencoder wolfi.m - for reporting a typo in externermux quality parameter value + for reporting a typo in externremux quality parameter value Norman Thiel for reporting a wrong URL path in m3u playlists + +vel_tins + for reporting that externremux x264 uses value of ABR for VBR + for various suggestions to improve externremux.sh + +Matthias Prill + for reporting a compiler error with older libstdc++ versions + +Timothy D. Lenz + for reporting missing support for invisible channel groups in HTTP menu + +Rainer Blickle + for reporting that channel switches may interrupt live TV on the server diff --git a/HISTORY b/HISTORY index 3ca77bc..9cdc58d 100644 --- a/HISTORY +++ b/HISTORY @@ -1,6 +1,44 @@ VDR Plugin 'streamdev' Revision History --------------------------------------- +- VTP no longer uses a static priority value for its server-side receivers. + The server stores channel and priority requested with the PROV command and + re-uses these values in a subsequent TUNE for the same channel. The new + PRIO command is used to update the receiver's priority if necessary. +- added parameter HEIGHT to externremux.sh +- fixed syslog messages reporting local instead of remote IP and port +- fixed regression of the GetDevice(...) change. Filter streaming to clients + with a recent VDR version no longer worked. +- log an error if externremux.sh is missing or not executable +- since VDR 1.5.0 cDevice::GetDevice(...) is no longer a query only method. + It detaches all receivers of the device it returns. So it is no longer + suitable for testing the availability of a device. Added a copy of VDR's + cDevice::GetDevice(...) without the detach receivers part as a workaround + until a better solution is available +- added dsyslog messages to help troubleshouting channel switch issues +- VTP command SUSP didn't attach the player to the primary device +- fixed incompatibilities with older make versions +- replacing a connections receiver is now an atomic operation. Solves + stuttering audio/video due to lost TS packets when adding/removing PIDs +- disabled attribute warn_unused_result in libdvbmpeg +- slightly increased thread priorities of cStreamdevWriter/Streamer + (suggested by Rolf Ahrenberg) +- fixed missing support for invisible channel groups (groups without name) + in HTTP menu (reported by Timothy D. Lenz) +- don't quote actual program call in externremux.sh, so you can run the + program through e.g. nice or taskset just by extending the variable + which holds the program name +- in externremux.sh each mencoder audio and video codec has a dedicated + variable for a default option string now. Still you can override each + default option with an URL parameter +- externremux.sh mencoder now uses scale parameter with negative height + instead of -xy for scaling (suggested by vel_tins@vdrportal) +- added FPS (frames per second) parameter to externremux.sh (suggested by + vel_tins@vdrportal) +- don't use std::map.at(). It's not available in older libstdc++ version + (reported by Matthias Prill) +- fixed extremux x264 using value of ABR for VBR (thanks to vel_tins@vdrportal) + 2010-07-20: Version 0.5.0b - fixed wrong URL path in m3u playlists (reported by Norman Thiel) diff --git a/Makefile b/Makefile index 2ead512..b375844 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ # # Makefile for a Video Disk Recorder plugin # -# $Id: Makefile,v 1.22 2010/07/19 13:49:24 schmirl Exp $ +# $Id: Makefile,v 1.23 2010/08/02 10:36:59 schmirl Exp $ # The main source file name. # @@ -35,7 +35,8 @@ TSPLAYVERSNUM = $(shell grep 'define TSPLAY_PATCH_VERSION ' $(VDRDIR)/device.h | ifeq ($(shell test $(APIVERSNUM) -ge 10713; echo $$?),0) include $(VDRDIR)/Make.global -else ifeq ($(shell test $(APIVERSNUM) -ge 10704 -o -n "$(TSPLAYVERSNUM)" ; echo $$?),0) +else +ifeq ($(shell test $(APIVERSNUM) -ge 10704 -o -n "$(TSPLAYVERSNUM)" ; echo $$?),0) DEFINES += -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE CFLAGS += -fPIC CXXFLAGS += -fPIC @@ -43,13 +44,14 @@ else CFLAGS += -fPIC CXXFLAGS += -fPIC endif +endif -include $(VDRDIR)/Make.config ### export all vars for sub-makes, using absolute paths -VDRDIR := $(abspath $(VDRDIR)) -LIBDIR := $(abspath $(LIBDIR)) +VDRDIR := $(shell cd $(VDRDIR) >/dev/null 2>&1 && pwd) +LIBDIR := $(shell cd $(LIBDIR) >/dev/null 2>&1 && pwd) export unexport PLUGIN diff --git a/client/device.c b/client/device.c index d53bde1..fb80107 100644 --- a/client/device.c +++ b/client/device.c @@ -1,5 +1,5 @@ /* - * $Id: device.c,v 1.26 2010/06/08 05:55:17 schmirl Exp $ + * $Id: device.c,v 1.27 2010/08/18 10:26:55 schmirl Exp $ */ #include "client/device.h" @@ -8,6 +8,7 @@ #include "tools/select.h" +#include #include #include #include @@ -32,6 +33,7 @@ cStreamdevDevice::cStreamdevDevice(void) { m_Device = this; m_Pids = 0; + m_Priority = -1; m_DvrClosed = true; } @@ -107,7 +109,7 @@ bool cStreamdevDevice::ProvidesChannel(const cChannel *Channel, int Priority, res = prio && ClientSocket.ProvidesChannel(Channel, Priority); ndr = true; } - + if (NeedsDetachReceivers) *NeedsDetachReceivers = ndr; Dprintf("prov res = %d, ndr = %d\n", res, ndr); @@ -118,6 +120,9 @@ bool cStreamdevDevice::SetChannelDevice(const cChannel *Channel, bool LiveView) { Dprintf("SetChannelDevice Channel: %s, LiveView: %s\n", Channel->Name(), LiveView ? "true" : "false"); + LOCK_THREAD; + + m_UpdatePriority = ClientSocket.SupportsPrio(); if (LiveView) return false; @@ -140,6 +145,8 @@ bool cStreamdevDevice::SetPid(cPidHandle *Handle, int Type, bool On) { Handle->used); LOCK_THREAD; + m_UpdatePriority = ClientSocket.SupportsPrio(); + if (On && !m_TSBuffer) { Dprintf("SetPid: no data connection -> OpenDvr()"); OpenDvrInt(); @@ -301,3 +308,16 @@ bool cStreamdevDevice::ReInit(void) { return StreamdevClientSetup.StartClient ? Init() : true; } +void cStreamdevDevice::UpdatePriority(void) { + if (m_Device) { + m_Device->Lock(); + if (m_Device->m_UpdatePriority && ClientSocket.DataSocket(siLive)) { + int Priority = m_Device->Priority(); + if (m_Device == cDevice::ActualDevice() && Priority < Setup.PrimaryLimit) + Priority = Setup.PrimaryLimit; + if (m_Device->m_Priority != Priority && ClientSocket.SetPriority(Priority)) + m_Device->m_Priority = Priority; + } + m_Device->Unlock(); + } +} diff --git a/client/device.h b/client/device.h index e96b05f..b2eed70 100644 --- a/client/device.h +++ b/client/device.h @@ -1,5 +1,5 @@ /* - * $Id: device.h,v 1.9 2009/06/23 10:26:54 schmirl Exp $ + * $Id: device.h,v 1.10 2010/08/18 10:26:55 schmirl Exp $ */ #ifndef VDR_STREAMDEV_DEVICE_H @@ -15,13 +15,14 @@ class cTBString; #define CMD_LOCK_OBJ(x) cMutexLock CmdLock((cMutex*)&(x)->m_Mutex) class cStreamdevDevice: public cDevice { - friend class cRemoteRecordings; private: const cChannel *m_Channel; cTSBuffer *m_TSBuffer; cStreamdevFilters *m_Filters; int m_Pids; + int m_Priority; + bool m_UpdatePriority; bool m_DvrClosed; static cStreamdevDevice *m_Device; @@ -59,6 +60,7 @@ public: #endif virtual bool IsTunedToTransponder(const cChannel *Channel); + static void UpdatePriority(void); static bool Init(void); static bool ReInit(void); diff --git a/client/socket.c b/client/socket.c index bd2f9ba..0e8974f 100644 --- a/client/socket.c +++ b/client/socket.c @@ -1,5 +1,5 @@ /* - * $Id: socket.c,v 1.13 2010/06/08 05:55:17 schmirl Exp $ + * $Id: socket.c,v 1.15 2010/08/18 10:26:55 schmirl Exp $ */ #include @@ -20,6 +20,7 @@ cClientSocket ClientSocket; cClientSocket::cClientSocket(void) { memset(m_DataSockets, 0, sizeof(cTBSocket*) * si_Count); + m_Prio = false; Reset(); } @@ -142,8 +143,14 @@ bool cClientSocket::CheckConnection(void) { if(Command("CAPS FILTERS", 220)) Filters = ",FILTERS"; - isyslog("Streamdev: Connected to server %s:%d using capabilities TSPIDS%s", - RemoteIp().c_str(), RemotePort(), Filters); + const char *Prio = ""; + if(Command("CAPS PRIO", 220)) { + Prio = ",PRIO"; + m_Prio = true; + } + + isyslog("Streamdev: Connected to server %s:%d using capabilities TSPIDS%s%s", + RemoteIp().c_str(), RemotePort(), Filters, Prio); return true; } @@ -241,7 +248,7 @@ bool cClientSocket::SetChannelDevice(const cChannel *Channel) { CMD_LOCK; std::string command = (std::string)"TUNE " - + (const char*)Channel->GetChannelID().ToString(); + + (const char*)Channel->GetChannelID().ToString(); if (!Command(command, 220)) { if (errno == 0) esyslog("ERROR: Streamdev: Couldn't tune %s:%d to channel %s", @@ -251,6 +258,21 @@ bool cClientSocket::SetChannelDevice(const cChannel *Channel) { return true; } +bool cClientSocket::SetPriority(int Priority) { + if (!CheckConnection()) return false; + + CMD_LOCK; + + std::string command = (std::string)"PRIO " + (const char*)itoa(Priority); + if (!Command(command, 220)) { + if (errno == 0) + esyslog("Streamdev: Failed to update priority on %s:%d", RemoteIp().c_str(), + RemotePort()); + return false; + } + return true; +} + bool cClientSocket::SetPid(int Pid, bool On) { if (!CheckConnection()) return false; @@ -259,8 +281,8 @@ bool cClientSocket::SetPid(int Pid, bool On) { std::string command = (std::string)(On ? "ADDP " : "DELP ") + (const char*)itoa(Pid); if (!Command(command, 220)) { if (errno == 0) - esyslog("Streamdev: Pid %d not available from %s:%d", Pid, LocalIp().c_str(), - LocalPort()); + esyslog("Streamdev: Pid %d not available from %s:%d", Pid, RemoteIp().c_str(), + RemotePort()); return false; } return true; @@ -276,7 +298,7 @@ bool cClientSocket::SetFilter(ushort Pid, uchar Tid, uchar Mask, bool On) { if (!Command(command, 220)) { if (errno == 0) esyslog("Streamdev: Filter %hu, %hhu, %hhu not available from %s:%d", - Pid, Tid, Mask, LocalIp().c_str(), LocalPort()); + Pid, Tid, Mask, RemoteIp().c_str(), RemotePort()); return false; } return true; diff --git a/client/socket.h b/client/socket.h index 7ad9a80..1197678 100644 --- a/client/socket.h +++ b/client/socket.h @@ -1,5 +1,5 @@ /* - * $Id: socket.h,v 1.7 2010/06/08 05:55:17 schmirl Exp $ + * $Id: socket.h,v 1.8 2010/08/18 10:26:55 schmirl Exp $ */ #ifndef VDR_STREAMDEV_CLIENT_CONNECTION_H @@ -20,6 +20,7 @@ private: cTBSocket *m_DataSockets[si_Count]; cMutex m_Mutex; char m_Buffer[BUFSIZ + 1]; // various uses + bool m_Prio; // server supports command PRIO protected: /* Send Command, and return true if the command results in Expected. @@ -45,6 +46,8 @@ public: bool CreateDataConnection(eSocketId Id); bool CloseDataConnection(eSocketId Id); bool SetChannelDevice(const cChannel *Channel); + bool SupportsPrio() { return m_Prio; } + bool SetPriority(int Priority); bool SetPid(int Pid, bool On); bool SetFilter(ushort Pid, uchar Tid, uchar Mask, bool On); bool CloseDvr(void); diff --git a/client/streamdev-client.c b/client/streamdev-client.c index b03fbbe..00fa90c 100644 --- a/client/streamdev-client.c +++ b/client/streamdev-client.c @@ -3,7 +3,7 @@ * * See the README file for copyright information and how to reach the author. * - * $Id: streamdev-client.c,v 1.2 2010/07/19 13:49:25 schmirl Exp $ + * $Id: streamdev-client.c,v 1.3 2010/08/18 10:26:56 schmirl Exp $ */ #include "streamdev-client.h" @@ -52,4 +52,8 @@ bool cPluginStreamdevClient::SetupParse(const char *Name, const char *Value) { return StreamdevClientSetup.SetupParse(Name, Value); } +void cPluginStreamdevClient::MainThreadHook(void) { + cStreamdevDevice::UpdatePriority(); +} + VDRPLUGINCREATOR(cPluginStreamdevClient); // Don't touch this! diff --git a/client/streamdev-client.h b/client/streamdev-client.h index 0972dcc..1885ed1 100644 --- a/client/streamdev-client.h +++ b/client/streamdev-client.h @@ -1,5 +1,5 @@ /* - * $Id: streamdev-client.h,v 1.2 2010/07/19 13:49:25 schmirl Exp $ + * $Id: streamdev-client.h,v 1.3 2010/08/18 10:26:56 schmirl Exp $ */ #ifndef VDR_STREAMDEVCLIENT_H @@ -23,6 +23,7 @@ public: virtual cOsdObject *MainMenuAction(void); virtual cMenuSetupPage *SetupMenu(void); virtual bool SetupParse(const char *Name, const char *Value); + virtual void MainThreadHook(void); }; #endif // VDR_STREAMDEVCLIENT_H diff --git a/common.c b/common.c index 3ad290b..47898aa 100644 --- a/common.c +++ b/common.c @@ -10,7 +10,7 @@ using namespace std; -const char *VERSION = "0.5.0"; +const char *VERSION = "0.5.0-CVS"; const char cMenuEditIpItem::IpCharacters[] = "0123456789."; diff --git a/libdvbmpeg/Makefile b/libdvbmpeg/Makefile index 71e1c5d..4a78a12 100644 --- a/libdvbmpeg/Makefile +++ b/libdvbmpeg/Makefile @@ -1,12 +1,16 @@ # # Makefile for a Video Disk Recorder plugin # -# $Id: Makefile,v 1.4 2010/07/19 13:49:26 schmirl Exp $ +# $Id: Makefile,v 1.5 2010/07/30 10:49:28 schmirl Exp $ ### The object files (add further files here): OBJS = ctools.o remux.o ringbuffy.o transform.o +### Disable attribute warn_unused_result + +DEFINES += -U_FORTIFY_SOURCE + ### The main target: .PHONY: clean diff --git a/remux/extern.c b/remux/extern.c index ce76883..cf766d3 100644 --- a/remux/extern.c +++ b/remux/extern.c @@ -151,7 +151,10 @@ cTSExt::cTSExt(cRingBufferLinear *ResultBuffer, const cServerConnection *Connect // look for section parameters: /path;param1=value1;param2=value2/ std::string::size_type begin, end; - std::string path = Connection->Headers().at("PATH_INFO"); + const static std::string PATH_INFO("PATH_INFO"); + + tStrStrMap::const_iterator it_pathinfo = Connection->Headers().find(PATH_INFO); + const std::string& path = it_pathinfo == Connection->Headers().end() ? "/" : it_pathinfo->second; begin = path.find(';', 0); begin = path.find_first_not_of(';', begin); end = path.find_first_of(";/", begin); @@ -188,6 +191,11 @@ cTSExt::cTSExt(cRingBufferLinear *ResultBuffer, const cServerConnection *Connect if (setpgid(0, 0) == -1) esyslog("streamdev-server: externremux setpgid failed: %m"); + if (access(opt_remux, X_OK) == -1) { + esyslog("streamdev-server %s: %m", opt_remux); + _exit(-1); + } + if (execle("/bin/sh", "sh", "-c", opt_remux, NULL, env) == -1) { esyslog("streamdev-server: externremux script '%s' execution failed: %m", opt_remux); _exit(-1); diff --git a/server/connection.c b/server/connection.c index 956c42a..2ba99f3 100644 --- a/server/connection.c +++ b/server/connection.c @@ -1,5 +1,5 @@ /* - * $Id: connection.c,v 1.14 2010/07/19 13:49:31 schmirl Exp $ + * $Id: connection.c,v 1.16 2010/08/03 10:51:53 schmirl Exp $ */ #include "server/connection.h" @@ -8,6 +8,7 @@ #include "common.h" #include +#include #include #include #include @@ -184,75 +185,172 @@ bool cServerConnection::Respond(const char *Message, bool Last, ...) m_Pending = !Last; return true; } - + +#if APIVERSNUM >= 10700 +static int GetClippedNumProvidedSystems(int AvailableBits, cDevice *Device) +{ + int MaxNumProvidedSystems = 1 << AvailableBits; + int NumProvidedSystems = Device->NumProvidedSystems(); + if (NumProvidedSystems > MaxNumProvidedSystems) { + esyslog("ERROR: device %d supports %d modulation systems but cDevice::GetDevice() currently only supports %d delivery systems which should be fixed", Device->CardIndex() + 1, NumProvidedSystems, MaxNumProvidedSystems); + NumProvidedSystems = MaxNumProvidedSystems; + } + else if (NumProvidedSystems <= 0) { + esyslog("ERROR: device %d reported an invalid number (%d) of supported delivery systems - assuming 1", Device->CardIndex() + 1, NumProvidedSystems); + NumProvidedSystems = 1; + } + return NumProvidedSystems; +} +#endif + +/* + * copy of cDevice::GetDevice(...) but without side effects (not detaching receivers) + */ +cDevice* cServerConnection::CheckDevice(const cChannel *Channel, int Priority, bool LiveView, const cDevice *AvoidDevice) +{ + //cDevice *AvoidDevice = avoidDevice; + //avoidDevice = NULL; + // Collect the current priorities of all CAM slots that can decrypt the channel: + int NumCamSlots = CamSlots.Count(); + int SlotPriority[NumCamSlots]; + int NumUsableSlots = 0; + if (Channel->Ca() >= CA_ENCRYPTED_MIN) { + for (cCamSlot *CamSlot = CamSlots.First(); CamSlot; CamSlot = CamSlots.Next(CamSlot)) { + SlotPriority[CamSlot->Index()] = MAXPRIORITY + 1; // assumes it can't be used + if (CamSlot->ModuleStatus() == msReady) { + if (CamSlot->ProvidesCa(Channel->Caids())) { + if (!ChannelCamRelations.CamChecked(Channel->GetChannelID(), CamSlot->SlotNumber())) { + SlotPriority[CamSlot->Index()] = CamSlot->Priority(); + NumUsableSlots++; + } + } + } + } + if (!NumUsableSlots) + return NULL; // no CAM is able to decrypt this channel + } + + bool NeedsDetachReceivers = false; + cDevice *d = NULL; + //cCamSlot *s = NULL; + + uint32_t Impact = 0xFFFFFFFF; // we're looking for a device with the least impact + for (int j = 0; j < NumCamSlots || !NumUsableSlots; j++) { + if (NumUsableSlots && SlotPriority[j] > MAXPRIORITY) + continue; // there is no CAM available in this slot + for (int i = 0; i < cDevice::NumDevices(); i++) { + cDevice *device = cDevice::GetDevice(i); + if (device == AvoidDevice) + continue; // we've been asked to skip this device + if (Channel->Ca() && Channel->Ca() <= CA_DVB_MAX && Channel->Ca() != device->CardIndex() + 1) + continue; // a specific card was requested, but not this one + if (NumUsableSlots && !CamSlots.Get(j)->Assign(device, true)) + continue; // CAM slot can't be used with this device + bool ndr; + if (device->ProvidesChannel(Channel, Priority, &ndr)) { // this device is basicly able to do the job + if (NumUsableSlots && device->CamSlot() && device->CamSlot() != CamSlots.Get(j)) + ndr = true; // using a different CAM slot requires detaching receivers + // Put together an integer number that reflects the "impact" using + // this device would have on the overall system. Each condition is represented + // by one bit in the number (or several bits, if the condition is actually + // a numeric value). The sequence in which the conditions are listed corresponds + // to their individual severity, where the one listed first will make the most + // difference, because it results in the most significant bit of the result. + uint32_t imp = 0; + imp <<= 1; imp |= LiveView ? !device->IsPrimaryDevice() || ndr : 0; // prefer the primary device for live viewing if we don't need to detach existing receivers + imp <<= 1; imp |= !device->Receiving() && (device != cTransferControl::ReceiverDevice() || device->IsPrimaryDevice()) || ndr; // use receiving devices if we don't need to detach existing receivers, but avoid primary device in local transfer mode + imp <<= 1; imp |= device->Receiving(); // avoid devices that are receiving +#if APIVERSNUM >= 10700 + imp <<= 2; imp |= GetClippedNumProvidedSystems(2, device) - 1; // avoid cards which support multiple delivery systems +#endif + imp <<= 1; imp |= device == cTransferControl::ReceiverDevice(); // avoid the Transfer Mode receiver device + imp <<= 8; imp |= min(max(device->Priority() + MAXPRIORITY, 0), 0xFF); // use the device with the lowest priority (+MAXPRIORITY to assure that values -99..99 can be used) + imp <<= 8; imp |= min(max((NumUsableSlots ? SlotPriority[j] : 0) + MAXPRIORITY, 0), 0xFF); // use the CAM slot with the lowest priority (+MAXPRIORITY to assure that values -99..99 can be used) + imp <<= 1; imp |= ndr; // avoid devices if we need to detach existing receivers + imp <<= 1; imp |= device->IsPrimaryDevice(); // avoid the primary device + imp <<= 1; imp |= NumUsableSlots ? 0 : device->HasCi(); // avoid cards with Common Interface for FTA channels + imp <<= 1; imp |= device->HasDecoder(); // avoid full featured cards + imp <<= 1; imp |= NumUsableSlots ? !ChannelCamRelations.CamDecrypt(Channel->GetChannelID(), j + 1) : 0; // prefer CAMs that are known to decrypt this channel + if (imp < Impact) { + // This device has less impact than any previous one, so we take it. + Impact = imp; + d = device; + NeedsDetachReceivers = ndr; + } + } + } + if (!NumUsableSlots) + break; // no CAM necessary, so just one loop over the devices + } + return d; +} + cDevice *cServerConnection::GetDevice(const cChannel *Channel, int Priority) { - cDevice *device = NULL; + const cChannel *current = Channels.GetByNumber(cDevice::CurrentChannel()); - /*Dprintf("+ Statistics:\n"); - Dprintf("+ Current Channel: %d\n", cDevice::CurrentChannel()); - Dprintf("+ Current Device: %d\n", cDevice::ActualDevice()->CardIndex()); - Dprintf("+ Transfer Mode: %s\n", cDevice::ActualDevice() - == cDevice::PrimaryDevice() ? "false" : "true"); - Dprintf("+ Replaying: %s\n", cDevice::PrimaryDevice()->Replaying() ? "true" - : "false");*/ + // turn off the streams of this connection + Detach(); + // This call may detach receivers of the device it returns + cDevice *device = cDevice::GetDevice(Channel, Priority, false); - Dprintf(" * GetDevice(const cChannel*, int)\n"); - Dprintf(" * -------------------------------\n"); - - device = cDevice::GetDevice(Channel, Priority, false); - - Dprintf(" * Found following device: %p (%d)\n", device, - device ? device->CardIndex() + 1 : 0); - if (device == cDevice::ActualDevice()) - Dprintf(" * is actual device\n"); - if (!cSuspendCtl::IsActive() && StreamdevServerSetup.SuspendMode != smAlways) - Dprintf(" * NOT suspended\n"); - - if (!device || (device == cDevice::ActualDevice() + if (device && device == cDevice::ActualDevice() && !cSuspendCtl::IsActive() - && StreamdevServerSetup.SuspendMode != smAlways)) { - // mustn't switch actual device - // maybe a device would be free if THIS connection did turn off its streams? - Dprintf(" * trying again...\n"); - const cChannel *current = Channels.GetByNumber(cDevice::CurrentChannel()); - isyslog("streamdev-server: Detaching current receiver"); - Detach(); - device = cDevice::GetDevice(Channel, Priority, false); - Attach(); - Dprintf(" * Found following device: %p (%d)\n", device, - device ? device->CardIndex() + 1 : 0); - if (device == cDevice::ActualDevice()) - Dprintf(" * is actual device\n"); - if (!cSuspendCtl::IsActive() - && StreamdevServerSetup.SuspendMode != smAlways) - Dprintf(" * NOT suspended\n"); - if (current && !TRANSPONDER(Channel, current)) - Dprintf(" * NOT same transponder\n"); - if (device && (device == cDevice::ActualDevice() - && !cSuspendCtl::IsActive() - && StreamdevServerSetup.SuspendMode != smAlways - && current != NULL - && !TRANSPONDER(Channel, current))) { - // now we would have to switch away live tv...let's see if live tv - // can be handled by another device - cDevice *newdev = NULL; - for (int i = 0; i < cDevice::NumDevices(); ++i) { - cDevice *dev = cDevice::GetDevice(i); - if (dev->ProvidesChannel(current, 0) && dev != device) { - newdev = dev; - break; - } - } - Dprintf(" * Found device for live tv: %p (%d)\n", newdev, - newdev ? newdev->CardIndex() + 1 : 0); - if (newdev == NULL || newdev == device) - // no suitable device to continue live TV, giving up... - device = NULL; - else - newdev->SwitchChannel(current, true); - } + && StreamdevServerSetup.SuspendMode != smAlways + && current != NULL + && !TRANSPONDER(Channel, current)) { + // now we would have to switch away live tv...let's see if live tv + // can be handled by another device +#if VDRVERSNUM >= 10516 + cDevice::SetAvoidDevice(device); + cDevice *newdev = cDevice::GetDevice(current, 0, true); +#else + cDevice *newdev = CheckDevice(current, 0, true, device); +#endif + if (newdev) + newdev->SwitchChannel(current, true); + else + device = NULL; } + if (!device) { + // can't switch - continue the current stream + Attach(); + dsyslog("streamdev: GetDevice failed for channel %s at priority %d", Channel->Name(), Priority); + } + return device; +} + +bool cServerConnection::ProvidesChannel(const cChannel *Channel, int Priority) +{ + const cChannel *current = Channels.GetByNumber(cDevice::CurrentChannel()); + + cDevice *device = CheckDevice(Channel, Priority, false); + if (!device || (device == cDevice::ActualDevice() + && !cSuspendCtl::IsActive() + && StreamdevServerSetup.SuspendMode != smAlways + && current != NULL + && !TRANSPONDER(Channel, current))) { + // mustn't switch actual device + // maybe a device would be free if THIS connection did turn off its streams? + Detach(); + device = CheckDevice(Channel, Priority, false); + Attach(); + if (device && device == cDevice::ActualDevice() + && !cSuspendCtl::IsActive() + && StreamdevServerSetup.SuspendMode != smAlways + && current != NULL + && !TRANSPONDER(Channel, current)) { + // now we would have to switch away live tv...let's see if live tv + // can be handled by another device + cDevice *newdev = CheckDevice(current, 0, true, device); + if (!newdev) { + device = NULL; + dsyslog("streamdev: Not providing channel %s at priority %d - live TV not suspended", Channel->Name(), Priority); + } + } + else if (!device) + dsyslog("streamdev: No device provides channel %s at priority %d", Channel->Name(), Priority); + } return device; } diff --git a/server/connection.h b/server/connection.h index ff5ee98..22301b1 100644 --- a/server/connection.h +++ b/server/connection.h @@ -1,5 +1,5 @@ /* - * $Id: connection.h,v 1.9 2010/07/19 13:49:31 schmirl Exp $ + * $Id: connection.h,v 1.10 2010/08/03 10:46:41 schmirl Exp $ */ #ifndef VDR_STREAMDEV_SERVER_CONNECTION_H @@ -35,6 +35,11 @@ private: tStrStrMap m_Headers; + /* Check if a device would be available for transfering the given + channel. This call has no side effects except for temporarily + detaching this connection's receivers. */ + cDevice *CheckDevice(const cChannel *Channel, int Priority, bool LiveView, const cDevice *AvoidDevice = NULL); + protected: /* Will be called when a command terminated by a newline has been received */ @@ -91,10 +96,14 @@ public: /* Will make the socket close after sending all queued output data */ void DeferClose(void) { m_DeferClose = true; } - /* Will retrieve an unused device for transmitting data. Use the returned + /* Will retrieve an unused device for transmitting data. Receivers have + already been attached from the device if necessary. Use the returned cDevice in a following call to StartTransfer */ cDevice *GetDevice(const cChannel *Channel, int Priority); + /* Test if a call to GetDevice would return a usable device. */ + bool ProvidesChannel(const cChannel *Channel, int Priority); + virtual void Flushed(void) {} virtual void Detach(void) = 0; diff --git a/server/connectionHTTP.c b/server/connectionHTTP.c index 028b9ea..54ccf5a 100644 --- a/server/connectionHTTP.c +++ b/server/connectionHTTP.c @@ -1,5 +1,5 @@ /* - * $Id: connectionHTTP.c,v 1.19 2010/07/20 12:26:29 schmirl Exp $ + * $Id: connectionHTTP.c,v 1.21 2010/08/03 10:46:41 schmirl Exp $ */ #include @@ -140,11 +140,18 @@ bool cConnectionHTTP::ProcessRequest(void) } } - if (Headers().at(REQUEST_METHOD).compare("GET") == 0 && ProcessURI(Headers().at(PATH_INFO))) { + tStrStrMap::const_iterator it_method = Headers().find(REQUEST_METHOD); + tStrStrMap::const_iterator it_pathinfo = Headers().find(PATH_INFO); + if (it_method == Headers().end() || it_pathinfo == Headers().end()) { + // should never happen + esyslog("streamdev-server connectionHTTP: Missing method or pathinfo"); + } else if (it_method->second.compare("GET") == 0 && ProcessURI(it_pathinfo->second)) { if (m_ChannelList) return Respond("%s", true, m_ChannelList->HttpHeader().c_str()); else if (m_Channel != NULL) { - cDevice *device = GetDevice(m_Channel, 0); + cDevice *device = NULL; + if (ProvidesChannel(m_Channel, 0)) + device = GetDevice(m_Channel, 0); if (device != NULL) { device->SwitchChannel(m_Channel, false); m_LiveStreamer = new cStreamdevLiveStreamer(0, this); @@ -176,13 +183,12 @@ bool cConnectionHTTP::ProcessRequest(void) return Respond("HTTP/1.0 404 not found") && Respond(""); } - } else if (Headers().at(REQUEST_METHOD).compare("HEAD") == 0 && ProcessURI(Headers().at(PATH_INFO))) { + } else if (it_method->second.compare("HEAD") == 0 && ProcessURI(it_pathinfo->second)) { DeferClose(); if (m_ChannelList) return Respond("%s", true, m_ChannelList->HttpHeader().c_str()); else if (m_Channel != NULL) { - cDevice *device = GetDevice(m_Channel, 0); - if (device != NULL) { + if (ProvidesChannel(m_Channel, 0)) { if (m_StreamType == stEXT) { // TODO return Respond("HTTP/1.0 200 OK") @@ -249,7 +255,8 @@ cChannelList* cConnectionHTTP::ChannelListFromString(const std::string& Path, co const static std::string QUERY_STRING("QUERY_STRING"); const static std::string HOST("HTTP_HOST"); - const std::string query = Headers().at(QUERY_STRING); + tStrStrMap::const_iterator it_query = Headers().find(QUERY_STRING); + const std::string& query = it_query == Headers().end() ? "" : it_query->second; std::string groupTarget; cChannelIterator *iterator = NULL; diff --git a/server/connectionIGMP.c b/server/connectionIGMP.c index 50db15b..708f41c 100644 --- a/server/connectionIGMP.c +++ b/server/connectionIGMP.c @@ -1,5 +1,5 @@ /* - * $Id: connectionIGMP.c,v 1.2 2010/07/19 13:49:31 schmirl Exp $ + * $Id: connectionIGMP.c,v 1.3 2010/08/03 10:46:41 schmirl Exp $ */ #include @@ -25,7 +25,9 @@ cConnectionIGMP::~cConnectionIGMP() bool cConnectionIGMP::Start(cChannel *Channel, in_addr_t Dst) { if (Channel != NULL) { - cDevice *device = GetDevice(Channel, 0); + cDevice *device = NULL; + if (ProvidesChannel(Channel, 0)) + device = GetDevice(Channel, 0); if (device != NULL) { device->SwitchChannel(Channel, false); struct in_addr ip; diff --git a/server/connectionVTP.c b/server/connectionVTP.c index 861bc83..0f92db0 100644 --- a/server/connectionVTP.c +++ b/server/connectionVTP.c @@ -1,5 +1,5 @@ /* - * $Id: connectionVTP.c,v 1.28 2010/07/19 13:49:31 schmirl Exp $ + * $Id: connectionVTP.c,v 1.31 2010/08/18 10:26:54 schmirl Exp $ */ #include "server/connectionVTP.h" @@ -746,6 +746,8 @@ cConnectionVTP::cConnectionVTP(void): m_StreamType(stTSPIDS), m_FiltersSupport(false), m_RecPlayer(NULL), + m_TuneChannel(NULL), + m_TunePriority(0), m_LSTEHandler(NULL), m_LSTCHandler(NULL), m_LSTTHandler(NULL), @@ -836,6 +838,7 @@ bool cConnectionVTP::Command(char *Cmd) else if (strcasecmp(Cmd, "READ") == 0) return CmdREAD(param); else if (strcasecmp(Cmd, "TUNE") == 0) return CmdTUNE(param); else if (strcasecmp(Cmd, "PLAY") == 0) return CmdPLAY(param); + else if (strcasecmp(Cmd, "PRIO") == 0) return CmdPRIO(param); else if (strcasecmp(Cmd, "ADDP") == 0) return CmdADDP(param); else if (strcasecmp(Cmd, "DELP") == 0) return CmdDELP(param); else if (strcasecmp(Cmd, "ADDF") == 0) return CmdADDF(param); @@ -894,6 +897,11 @@ bool cConnectionVTP::CmdCAPS(char *Opts) return Respond(220, "Capability \"%s\" accepted", Opts); } + // Command PRIO is known + if (strcasecmp(Opts, "PRIO") == 0) { + return Respond(220, "Capability \"%s\" accepted", Opts); + } + return Respond(561, "Capability \"%s\" not known", Opts); } @@ -911,9 +919,15 @@ bool cConnectionVTP::CmdPROV(char *Opts) if ((chan = ChannelFromString(Opts)) == NULL) return Respond(550, "Undefined channel \"%s\"", Opts); - return GetDevice(chan, prio) != NULL - ? Respond(220, "Channel available") - : Respond(560, "Channel not available"); + if (ProvidesChannel(chan, prio)) { + m_TuneChannel = chan; + m_TunePriority = prio; + return Respond(220, "Channel available"); + } + else { + m_TuneChannel = NULL; + return Respond(560, "Channel not available"); + } } bool cConnectionVTP::CmdPORT(char *Opts) @@ -1067,18 +1081,23 @@ bool cConnectionVTP::CmdTUNE(char *Opts) { const cChannel *chan; cDevice *dev; + int prio = m_TunePriority; if ((chan = ChannelFromString(Opts)) == NULL) return Respond(550, "Undefined channel \"%s\"", Opts); - if ((dev = GetDevice(chan, 0)) == NULL) + if (chan != m_TuneChannel) { + esyslog("streamdev-server TUNE %s: Priority unknown - using 0", Opts); + prio = 0; + } + if ((dev = GetDevice(chan, prio)) == NULL) return Respond(560, "Channel not available"); if (!dev->SwitchChannel(chan, false)) return Respond(560, "Channel not available"); delete m_LiveStreamer; - m_LiveStreamer = new cStreamdevLiveStreamer(1, this); + m_LiveStreamer = new cStreamdevLiveStreamer(prio, this); m_LiveStreamer->SetChannel(chan, m_StreamType); m_LiveStreamer->SetDevice(dev); if(m_LiveSocket) @@ -1119,6 +1138,22 @@ bool cConnectionVTP::CmdPLAY(char *Opts) } } +bool cConnectionVTP::CmdPRIO(char *Opts) +{ + int prio; + char *end; + + prio = strtoul(Opts, &end, 10); + if (end == Opts || (*end != '\0' && *end != ' ')) + return Respond(500, "Use: PRIO Priority"); + + if (m_LiveStreamer) { + m_LiveStreamer->SetPriority(prio); + return Respond(220, "Priority changed to %d", prio); + } + return Respond(550, "Priority not applicable"); +} + bool cConnectionVTP::CmdADDP(char *Opts) { int pid; @@ -1243,6 +1278,7 @@ bool cConnectionVTP::CmdSUSP(void) else if (StreamdevServerSetup.SuspendMode == smOffer && StreamdevServerSetup.AllowSuspend) { cControl::Launch(new cSuspendCtl); + cControl::Attach(); return Respond(220, "Server is suspended"); } else return Respond(550, "Client may not suspend server"); diff --git a/server/connectionVTP.h b/server/connectionVTP.h index b938fe6..ee842fe 100644 --- a/server/connectionVTP.h +++ b/server/connectionVTP.h @@ -31,6 +31,11 @@ private: bool m_FiltersSupport; RecPlayer *m_RecPlayer; + // Priority is only known in PROV command + // Store in here for later use in TUNE call + const cChannel *m_TuneChannel; + int m_TunePriority; + // Members adopted for SVDRP cLSTEHandler *m_LSTEHandler; cLSTCHandler *m_LSTCHandler; @@ -59,6 +64,7 @@ public: bool CmdREAD(char *Opts); bool CmdTUNE(char *Opts); bool CmdPLAY(char *Opts); + bool CmdPRIO(char *Opts); bool CmdADDP(char *Opts); bool CmdDELP(char *Opts); bool CmdADDF(char *Opts); diff --git a/server/livestreamer.c b/server/livestreamer.c index 2b0065c..1286abd 100644 --- a/server/livestreamer.c +++ b/server/livestreamer.c @@ -289,7 +289,7 @@ void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, i if (written != TS_SIZE) siBuffer.ReportOverflow(TS_SIZE - written); if (pmtPid != prevPmtPid) { - m_Streamer->SetPids(pmtPid); + m_Streamer->SetPid(pmtPid, true); Add(pmtPid, 0x02); pmtVersion = -1; } @@ -434,17 +434,24 @@ bool cStreamdevLiveStreamer::SetPids(int Pid, const int *Pids1, const int *Pids2 return true; } +void cStreamdevLiveStreamer::SetPriority(int Priority) +{ + m_Priority = Priority; + StartReceiver(); +} + void cStreamdevLiveStreamer::StartReceiver(void) { - DELETENULL(m_Receiver); - if (m_NumPids > 0) { + if (m_Device != NULL && m_NumPids > 0 && IsRunning()) { Dprintf("Creating Receiver to respect changed pids\n"); + cReceiver *current = m_Receiver; m_Receiver = new cStreamdevLiveReceiver(this, m_Channel->GetChannelID(), m_Priority, m_Pids); - if (IsRunning() && m_Device != NULL) { - Dprintf("Attaching new receiver\n"); - Attach(); - } + cThreadLock ThreadLock(m_Device); + Attach(); + delete current; } + else + DELETENULL(m_Receiver); } bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType StreamType, const int* Apid, const int *Dpid) @@ -631,12 +638,10 @@ void cStreamdevFilterStreamer::SetDevice(cDevice *Device) { Dprintf("cStreamdevFilterStreamer::SetDevice()\n"); LOCK_THREAD; - if(Device != m_Device) { - Detach(); - m_Device = Device; - //m_Channel = NULL; - Attach(); - } + Detach(); + m_Device = Device; + //m_Channel = NULL; + Attach(); } bool cStreamdevFilterStreamer::SetFilter(u_short Pid, u_char Tid, u_char Mask, bool On) diff --git a/server/livestreamer.h b/server/livestreamer.h index 283a395..71feb4c 100644 --- a/server/livestreamer.h +++ b/server/livestreamer.h @@ -38,6 +38,7 @@ public: 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); virtual int Put(const uchar *Data, int Count); virtual uchar *Get(int &Count); diff --git a/server/menuHTTP.c b/server/menuHTTP.c index 5fb5010..d7fb817 100644 --- a/server/menuHTTP.c +++ b/server/menuHTTP.c @@ -2,7 +2,7 @@ #include "server/menuHTTP.h" //**************************** cChannelIterator ************** -cChannelIterator::cChannelIterator(cChannel *First): channel(First) +cChannelIterator::cChannelIterator(const cChannel *First): channel(First) {} const cChannel* cChannelIterator::Next() @@ -19,7 +19,7 @@ cListAll::cListAll(): cChannelIterator(Channels.First()) const cChannel* cListAll::NextChannel(const cChannel *Channel) { if (Channel) - Channel = Channels.Next(Channel); + Channel = SkipFakeGroups(Channels.Next(Channel)); return Channel; } @@ -46,14 +46,19 @@ const cChannel* cListGroups::NextChannel(const cChannel *Channel) } // // ********************* cListGroup **************** -cListGroup::cListGroup(const cChannel *Group): cChannelIterator((Group && Group->GroupSep() && Channels.Next(Group) && !Channels.Next(Group)->GroupSep()) ? Channels.Next(Group) : NULL) +cListGroup::cListGroup(const cChannel *Group): cChannelIterator(GetNextChannelInGroup(Group)) {} +const cChannel* cListGroup::GetNextChannelInGroup(const cChannel *Channel) +{ + if (Channel) + Channel = SkipFakeGroups(Channels.Next(Channel)); + return Channel && !Channel->GroupSep() ? Channel : NULL; +} + const cChannel* cListGroup::NextChannel(const cChannel *Channel) { - if (Channel) - Channel = Channels.Next(Channel); - return (Channel && !Channel->GroupSep()) ? Channel : NULL; + return GetNextChannelInGroup(Channel); } // // ********************* cListTree **************** @@ -68,7 +73,7 @@ const cChannel* cListTree::NextChannel(const cChannel *Channel) if (currentGroup == selectedGroup) { if (Channel) - Channel = Channels.Next(Channel); + Channel = SkipFakeGroups(Channels.Next(Channel)); if (Channel && Channel->GroupSep()) currentGroup = Channel; } diff --git a/server/menuHTTP.h b/server/menuHTTP.h index cbd7b59..4ef6363 100644 --- a/server/menuHTTP.h +++ b/server/menuHTTP.h @@ -13,9 +13,10 @@ class cChannelIterator const cChannel *channel; protected: virtual const cChannel* NextChannel(const cChannel *Channel) = 0; + static inline const cChannel* SkipFakeGroups(const cChannel *Channel); public: const cChannel* Next(); - cChannelIterator(cChannel *First); + cChannelIterator(const cChannel *First); virtual ~cChannelIterator() {}; }; @@ -48,6 +49,8 @@ class cListGroups: public cChannelIterator class cListGroup: public cChannelIterator { + private: + static const cChannel* GetNextChannelInGroup(const cChannel *Channel); protected: virtual const cChannel* NextChannel(const cChannel *Channel); public: @@ -140,4 +143,11 @@ class cM3uChannelList: public cChannelList virtual ~cM3uChannelList(); }; +inline const cChannel* cChannelIterator::SkipFakeGroups(const cChannel* Group) +{ + while (Group && Group->GroupSep() && !*Group->Name()) + Group = Channels.Next(Group); + return Group; +} + #endif diff --git a/server/streamer.c b/server/streamer.c index c92e7ee..ec7d3c3 100644 --- a/server/streamer.c +++ b/server/streamer.c @@ -1,5 +1,5 @@ /* - * $Id: streamer.c,v 1.20 2010/07/19 13:49:32 schmirl Exp $ + * $Id: streamer.c,v 1.21 2010/07/30 10:01:11 schmirl Exp $ */ #include @@ -46,6 +46,9 @@ void cStreamdevWriter::Action(void) uchar *block = NULL; int count, offset = 0; +#if APIVERSNUM >= 10705 + SetPriority(-3); +#endif sel.Clear(); sel.Add(*m_Socket, true); while (Running()) { @@ -149,6 +152,9 @@ void cStreamdevStreamer::Stop(void) void cStreamdevStreamer::Action(void) { +#if APIVERSNUM >= 10705 + SetPriority(-3); +#endif while (Running()) { int got; uchar *block = m_RingBuffer->Get(got); diff --git a/streamdev-server/externremux.sh b/streamdev-server/externremux.sh index 21cc60d..20fda78 100755 --- a/streamdev-server/externremux.sh +++ b/streamdev-server/externremux.sh @@ -16,6 +16,8 @@ # VBR video bitrate (kbit) # VOPTS custom video options # WIDTH scale video to width +# HEIGHT scale video to height +# FPS output frames per second # AC audio codec # ABR audio bitrate (kbit) # AOPTS custom audio options @@ -42,14 +44,22 @@ ABR_MONO=64 ### # mencoder binary MENCODER=mencoder +### video part # Default video codec (e.g. lavc/x264/copy) MENCODER_VC=lavc +# Default video options if lavc is used (-ovc lavc -lavcopts ...) +MENCODER_LAVC_VOPTS=vcodec=mpeg4 +# Default video options if x264 is used (-ovc x264 -x264encopts ...) +MENCODER_X264_VOPTS=threads=auto +### audio part # Default audio codec (e.g. lavc/mp3lame/faac/copy) MENCODER_AC=mp3lame -# Default video codec if lavc is used (-ovc lavc -lavcopts vcodec=) -MENCODER_LAVC_VC=mpeg4 -# Default audio codec if lavc is used (-oac lavc -lavcopts acodec=) -MENCODER_LAVC_AC=mp2 +# Default audio options if lavc is used (-oac lavc -lavcopts ...) +MENCODER_LAVC_AOPTS=acodec=mp2 +# Default audio options if mp3lame is used (-oac mp3lame -lameopts ...) +MENCODER_LAME_AOPTS= +# Default audio options if faac is used (-oac faac -faacopts ...) +MENCODER_FAAC_AOPTS= ### ### MENCODER CONFIG END @@ -63,6 +73,8 @@ OGG_SPEED=1 OGG_VQUALITY=0 # audioquality - higher value gives better quality but is slower (0..10) OGG_AQUALITY=0 +# aspect ratio used for scaling if only one of HEIGHT/WIDTH given (16/9 or 4/3) +OGG_ASPECT='4 / 3' ### ### OGG CONFIG END @@ -70,7 +82,19 @@ OGG_AQUALITY=0 function hasOpt { echo "$1" | grep -q "\b${2}\b"; } -function isNumeric() { echo "$@" | grep -q '^[0-9]\{1,\}$'; } +# $1: concatenation of already set option=value pairs +# $2-$n: option=value pairs to be echod if the option is not present in $1 +function addOpts +{ + local opts="$1" + shift + while [ $# -gt 0 ]; do + hasOpt "$opts" ${1%%=*}= || echo $1 + shift + done +} + +function isNumeric() { echo "$@" | grep -q '^-\?[0-9]\{1,\}$'; } function remux_cat { @@ -87,21 +111,32 @@ function remux_mencoder # Assemble video options VC=${REMUX_PARAM_VC:-$MENCODER_VC} VOPTS=${REMUX_PARAM_VOPTS} - WIDTH=${REMUX_PARAM_WIDTH:-$WIDTH} + FPS=${REMUX_PARAM_FPS:-$FPS} + + # if only one of HEIGHT/WIDTH given: + # have mencoder calculate other value depending on actual aspect ratio + if [ "$HEIGHT" -a -z "$WIDTH" ]; then + WIDTH=-3 + elif [ "$WIDTH" -a -z "$HEIGHT" ]; then + HEIGHT=-3 + fi + case "$VC" in lavc) LAVCOPTS=( ${VOPTS} - $(hasOpt "$VOPTS" vcodec || echo "vcodec=$MENCODER_LAVC_VC") + $(IFS=$IFS:; addOpts "$VOPTS" $MENCODER_LAVC_VOPTS) ${VBR:+vbitrate=$VBR} ) [ ${#LAVCOPTS[*]} -gt 0 ] && VOPTS=$(IFS=:; echo -lavcopts "${LAVCOPTS[*]}") ;; x264) + isNumeric "$HEIGHT" && [ $HEIGHT -lt 0 -a $HEIGHT -gt -8 ] && ((HEIGHT-=8)) + isNumeric "$WIDTH" && [ $WIDTH -lt 0 -a $WIDTH -gt -8 ] && ((WIDTH-=8)) X264OPTS=( ${VOPTS} - $(hasOpt "$VOPTS" threads || echo "threads=auto") - ${VBR:+bitrate=$ABR} + $(IFS=$IFS:; addOpts "$VOPTS" $MENCODER_X264_VOPTS) + ${VBR:+bitrate=$VBR} ) [ ${#X264OPTS[*]} -gt 0 ] && VOPTS=$(IFS=:; echo -x264encopts "${X264OPTS[*]}") ;; @@ -121,7 +156,7 @@ function remux_mencoder LAVCOPTS=( ${LAVCOPTS[*]} ${AOPTS} - $(hasOpt "$AOPTS" acodec || echo "acodec=$MENCODER_LAVC_AC") + $(IFS=$IFS:; addOpts "$AOPTS" $MENCODER_LAVC_AOPTS) ${ABR:+abitrate=$ABR} ) @@ -133,6 +168,7 @@ function remux_mencoder LAMEOPTS=( ${AOPTS} $(isNumeric "${ABR}" && [ "${ABR}" -lt "$ABR_MONO" ] && ! hasOpt "${AOPTS}" mode ] && echo 'mode=3') + $(IFS=$IFS:; addOpts "$AOPTS" $MENCODER_LAME_AOPTS) ${ABR:+preset=$ABR} ) [ ${#LAMEOPTS[*]} -gt 0 ] && AOPTS=$(IFS=:; echo -lameopts "${LAMEOPTS[*]}") @@ -140,6 +176,7 @@ function remux_mencoder faac) FAACOPTS=( ${AOPTS} + $(IFS=$IFS:; addOpts "$AOPTS" $MENCODER_FAAC_AOPTS) ${ABR:+br=$ABR} ) [ ${#FAACOPTS[*]} -gt 0 ] && AOPTS=$(IFS=:; echo -faacopts "${FAACOPTS[*]}") @@ -155,15 +192,17 @@ function remux_mencoder startReply exec 3<&0 - echo "$MENCODER" \ + echo $MENCODER \ -ovc $VC $VOPTS \ -oac $AC $AOPTS \ - ${WIDTH:+-vf scale -zoom -xy $WIDTH} \ + ${WIDTH:+-vf scale=$WIDTH:$HEIGHT -zoom} \ + ${FPS:+-ofps $FPS} \ -o "$FIFO" -- - >&2 - "$MENCODER" \ + $MENCODER \ -ovc $VC $VOPTS \ -oac $AC $AOPTS \ - ${WIDTH:+-vf scale -zoom -xy $WIDTH} \ + ${WIDTH:+-vf scale=$WIDTH:$HEIGHT -zoom} \ + ${FPS:+-ofps $FPS} \ -o "$FIFO" -- - 0<&3 >/dev/null & } @@ -171,7 +210,15 @@ function remux_ogg { VOPTS=${REMUX_PARAM_VOPTS//[:=]/ } AOPTS=${REMUX_PARAM_AOPTS//[:=]/ } - WIDTH=${REMUX_PARAM_WIDTH:-$WIDTH} + + # if only one of HEIGHT/WIDTH given: + # calculate other value depending on configured aspect ratio + # trim to multiple of 8 + if [ "$HEIGHT" -a -z "$WIDTH" ]; then + WIDTH=$((HEIGHT * $OGG_ASPECT / 8 * 8)) + elif [ "$WIDTH" -a -z "$HEIGHT" ]; then + HEIGHT=$(($WIDTH * $( echo $OGG_ASPECT | sed 's#^\([0-9]\+\) */ *\([0-9]\+\)$#\2 / \1#') / 8 * 8)) + fi OGGOPTS=( ${VOPTS} @@ -187,14 +234,14 @@ function remux_ogg startReply exec 3<&0 - echo "$OGG" --format ts \ + echo $OGG --format ts \ ${OGGOPTS[*]} \ - ${WIDTH:+--width $WIDTH --height $(($WIDTH * 3 / 4 / 8 * 8))} \ + ${WIDTH:+--width $WIDTH --height $HEIGHT} \ --title "VDR Streamdev: ${REMUX_CHANNEL_NAME}" \ --output "$FIFO" -- - 0<&3 >&2 - "$OGG" --format ts \ + $OGG --format ts \ ${OGGOPTS[*]} \ - ${WIDTH:+--width $WIDTH --height $(($WIDTH * 3 / 4 / 8 * 8))} \ + ${WIDTH:+--width $WIDTH --height $HEIGHT} \ --title "VDR Streamdev: ${REMUX_CHANNEL_NAME}" \ --output "$FIFO" -- - 0<&3 >/dev/null & } @@ -254,6 +301,7 @@ esac ABR=${REMUX_PARAM_ABR:-$ABR} VBR=${REMUX_PARAM_VBR:-$VBR} WIDTH=${REMUX_PARAM_WIDTH:-$WIDTH} +HEIGHT=${REMUX_PARAM_HEIGHT:-$HEIGHT} PROG=${REMUX_PARAM_PROG:-$PROG} case "$PROG" in