diff options
author | kwacker <vdr@w-i-r.com> | 2010-04-11 13:46:11 +0200 |
---|---|---|
committer | kwacker <vdr@w-i-r.com> | 2010-04-11 13:46:11 +0200 |
commit | 9b144d30e0ea8ce900c37b96ba2cbdda14b0ae88 (patch) | |
tree | 3a52de029f950dcd9f9856a53fd67abef8519e68 | |
parent | 9cd931834ecadbf5efefdf484abb966e9248ebbb (diff) | |
download | x-vdr-9b144d30e0ea8ce900c37b96ba2cbdda14b0ae88.tar.gz x-vdr-9b144d30e0ea8ce900c37b96ba2cbdda14b0ae88.tar.bz2 |
Burn 0.2.0-beta3 und Streamdev mit Paches aktualisiert
133 files changed, 23709 insertions, 6 deletions
diff --git a/plugins/burn/plugin.sh b/plugins/burn/plugin.sh index 59c7ae3..c100ed9 100644 --- a/plugins/burn/plugin.sh +++ b/plugins/burn/plugin.sh @@ -3,16 +3,16 @@ # x-vdr (Installations-Skript fuer einen VDR mit Debian als Basis) # von Marc Wernecke - www.zulu-entertainment.de # 05.03.2009 - +# kwacker: Burn auf 0.2.0-beta3 aktualisiert # vdr-burn # defaults source ./../../x-vdr.conf source ./../../setup.conf source ./../../functions - -WEB="http://www.zulu-entertainment.de/files/vdr-burn/vdr-burn-0.1.0-pre22x.tgz" -VERSION="burn-0.1.0-pre22x" +WEB="http://projects.vdr-developer.org/attachments/download/285/vdr-burn-0.2.0-beta3.tgz" +#WEB="http://www.zulu-entertainment.de/files/vdr-burn/vdr-burn-0.1.0-pre22x.tgz" +VERSION="burn-0.2.0-beta3" LINK="burn" VAR=`basename $WEB` diff --git a/plugins/streamdev/patches/p1/streamdev-cvs221109-AddCallbackMsg.diff b/plugins/streamdev/patches/p1/streamdev-cvs221109-AddCallbackMsg.diff new file mode 100644 index 0000000..7facc61 --- /dev/null +++ b/plugins/streamdev/patches/p1/streamdev-cvs221109-AddCallbackMsg.diff @@ -0,0 +1,120 @@ +diff -NaurwB streamdev-unpatched/common.h streamdev/common.h +--- streamdev-unpatched/common.h 2009-09-18 12:41:41.000000000 +0200 ++++ streamdev/common.h 2009-11-23 04:54:04.000000000 +0100 +@@ -57,6 +57,8 @@ + si_Count + }; + ++#define MAX_RESPONSE_SIZE 1024 ++ + extern const char *VERSION; + + class cMenuEditIpItem: public cMenuEditItem { +diff -NaurwB streamdev-unpatched/server/connectionVTP.c streamdev/server/connectionVTP.c +--- streamdev-unpatched/server/connectionVTP.c 2009-10-13 08:38:47.000000000 +0200 ++++ streamdev/server/connectionVTP.c 2009-11-23 14:23:33.000000000 +0100 +@@ -1714,3 +1714,69 @@ + Code < 0 ? -Code : Code, + Code < 0 ? '-' : ' ', *str); + } ++ ++void cConnectionVTP::TimerChange(const cTimer *Timer, eTimerChange Change) ++{ ++ if (m_DataSocket) { ++ char buf[MAX_RESPONSE_SIZE]; ++ if (Change == tcMod) { ++ snprintf(buf, MAX_RESPONSE_SIZE, "MODT %s\0", Timer ? *Timer->ToText(true) : "-"); ++ } ++ if (Change == tcAdd) { ++ snprintf(buf, MAX_RESPONSE_SIZE, "ADDT %s\0", Timer ? *Timer->ToText(true) : "-"); ++ } ++ if (Change == tcDel) { ++ snprintf(buf, MAX_RESPONSE_SIZE, "DELT %s\0", Timer ? *Timer->ToText(true) : "-"); ++ } ++ ++ m_DataSocket->SysWrite(buf, strlen(buf)); ++ } ++} ++ ++#ifdef USE_STATUS_EXTENSION ++void cConnectionVTP::OsdStatusMessage(eMessageType type, const char *Message) ++#else ++void cConnectionVTP::OsdStatusMessage(const char *Message) ++#endif ++{ ++ if (m_DataSocket && Message) { ++ char buf[MAX_RESPONSE_SIZE]; ++ ++ /* Ignore this messages */ ++ if (strcasecmp(Message, trVDR("Channel not available!")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Delete timer?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Delete recording?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Press any key to cancel shutdown")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Press any key to cancel restart")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Editing - shut down anyway?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Recording - shut down anyway?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("shut down anyway?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Recording - restart anyway?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Editing - restart anyway?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Delete channel?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Timer still recording - really delete?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Delete marks information?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Delete resume information?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("CAM is in use - really reset?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Really restart?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Stop recording?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Cancel editing?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("Cutter already running - Add to cutting queue?")) == 0) return; ++ else if (strcasecmp(Message, trVDR("No index-file found. Creating may take minutes. Create one?")) == 0) return; ++ ++#ifdef USE_STATUS_EXTENSION ++ if (type == mtStatus) ++ snprintf(buf, MAX_RESPONSE_SIZE, "SMSG %s\0", Message); ++ else if (type == mtInfo) ++ snprintf(buf, MAX_RESPONSE_SIZE, "IMSG %s\0", Message); ++ else if (type == mtWarning) ++ snprintf(buf, MAX_RESPONSE_SIZE, "WMSG %s\0", Message); ++ else if (type == mtError) ++ snprintf(buf, MAX_RESPONSE_SIZE, "EMSG %s\0", Message); ++ else ++#endif ++ snprintf(buf, MAX_RESPONSE_SIZE, "IMSG %s\0", Message); ++ ++ m_DataSocket->SysWrite(buf, strlen(buf)); ++ } ++} +diff -NaurwB streamdev-unpatched/server/connectionVTP.h streamdev/server/connectionVTP.h +--- streamdev-unpatched/server/connectionVTP.h 2009-07-01 12:46:16.000000000 +0200 ++++ streamdev/server/connectionVTP.h 2009-11-23 14:23:33.000000000 +0100 +@@ -1,6 +1,7 @@ + #ifndef VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H + #define VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H + ++#include <vdr/status.h> + #include "server/connection.h" + #include "server/recplayer.h" + +@@ -12,7 +13,8 @@ + class cLSTTHandler; + class cLSTRHandler; + +-class cConnectionVTP: public cServerConnection { ++class cConnectionVTP: public cServerConnection ++ , public cStatus { + friend class cLSTEHandler; + #if !defined __GNUC__ || __GNUC__ >= 3 + using cServerConnection::Respond; +@@ -41,6 +43,13 @@ + template<class cHandler> + bool CmdLSTX(cHandler *&Handler, char *Option); + ++ virtual void TimerChange(const cTimer *Timer, eTimerChange Change); ++#ifdef USE_STATUS_EXTENSION ++ virtual void OsdStatusMessage(eMessageType type, const char *Message); ++#else ++ virtual void OsdStatusMessage(const char *Message); ++#endif ++ + public: + cConnectionVTP(void); + virtual ~cConnectionVTP(); diff --git a/plugins/streamdev/patches/p1/streamdev-cvs221109-AddFemonV1.diff b/plugins/streamdev/patches/p1/streamdev-cvs221109-AddFemonV1.diff new file mode 100644 index 0000000..39872e4 --- /dev/null +++ b/plugins/streamdev/patches/p1/streamdev-cvs221109-AddFemonV1.diff @@ -0,0 +1,210 @@ +diff -NaurwB streamdev-unpatched/server/connectionVTP.c streamdev/server/connectionVTP.c +--- streamdev-unpatched/server/connectionVTP.c 2009-10-13 08:38:47.000000000 +0200 ++++ streamdev/server/connectionVTP.c 2009-11-22 20:04:07.000000000 +0100 +@@ -7,6 +7,8 @@ + #include "server/suspend.h" + #include "setup.h" + ++#include "../services/femonservice.h" ++ + #include <vdr/tools.h> + #include <vdr/videodir.h> + #include <vdr/menu.h> +@@ -710,6 +712,102 @@ + return false; + } + ++ ++// --- cLSTQHandler ----------------------------------------------------------- ++ ++class cLSTQHandler ++{ ++private: ++ enum eStates { Device, Status, Signal, SNR, BER, UNC, Video, ++ Audio, Dolby, EndQuality }; ++ cConnectionVTP *m_Client; ++ FemonService_v1_0 m_femon; ++ int m_Errno; ++ int m_Channel; ++ cString m_Error; ++ eStates m_State; ++public: ++ cLSTQHandler(cConnectionVTP *Client, const char *Option); ++ ~cLSTQHandler(); ++ bool Next(bool &Last); ++}; ++ ++cLSTQHandler::cLSTQHandler(cConnectionVTP *Client, const char *Option): ++ m_Client(Client), ++ m_Errno(0), ++ m_State(Device), ++ m_Channel(-1) ++{ ++// if (*Option) { ++// if (isnumber(Option)) { ++// m_Channel = atoi(Option); ++// } ++// } ++ ++ cPlugin *p; ++ p = cPluginManager::CallFirstService("FemonService-v1.0", &m_femon); ++ if (!p) { ++ m_Errno = 550; ++ m_Error = cString::sprintf("No support for Signal Quality found"); ++ } ++} ++ ++cLSTQHandler::~cLSTQHandler() ++{ ++} ++ ++bool cLSTQHandler::Next(bool &Last) ++{ ++ if (*m_Error != NULL) { ++ Last = true; ++ cString str(m_Error); ++ m_Error = NULL; ++ return m_Client->Respond(m_Errno, "%s", *str); ++ } ++ ++ Last = false; ++ switch (m_State) { ++ case Device: ++ m_State = Status; ++ if (*m_femon.fe_name != NULL) ++ return m_Client->Respond(-215, "Device : %s", *m_femon.fe_name); ++ else ++ return m_Client->Respond(-215, "Device : "); ++ case Status: ++ m_State = Signal; ++ if (*m_femon.fe_status != NULL) ++ return m_Client->Respond(-215, "Status : %s", *m_femon.fe_status); ++ else ++ return m_Client->Respond(-215, "Status : "); ++ case Signal: ++ m_State = SNR; ++ return m_Client->Respond(-215, "Signal : %04X (%2d%%)", m_femon.fe_signal, m_femon.fe_signal / 655); ++ case SNR: ++ m_State = BER; ++ return m_Client->Respond(-215, "SNR : %04X (%2d%%)", m_femon.fe_snr, m_femon.fe_snr / 655); ++ case BER: ++ m_State = UNC; ++ return m_Client->Respond(-215, "BER : %08X", m_femon.fe_ber); ++ case UNC: ++ m_State = Video; ++ return m_Client->Respond(-215, "UNC : %08X", m_femon.fe_unc); ++ case Video: ++ m_State = Audio; ++ return m_Client->Respond(-215, "Video : %.2f Mbit/s", m_femon.video_bitrate); ++ case Audio: ++ m_State = Dolby; ++ return m_Client->Respond(-215, "Audio : %.0f kbit/s", m_femon.audio_bitrate); ++ case Dolby: ++ m_State = EndQuality; ++ return m_Client->Respond(-215, "Dolby : %.0f kbit/s", m_femon.dolby_bitrate); ++ case EndQuality: ++ Last = true; ++ return m_Client->Respond(215, "End of quality information"); ++ } ++ return false; ++} ++ ++ + // --- cConnectionVTP --------------------------------------------------------- + + cConnectionVTP::cConnectionVTP(void): +@@ -727,7 +825,8 @@ + m_LSTEHandler(NULL), + m_LSTCHandler(NULL), + m_LSTTHandler(NULL), +- m_LSTRHandler(NULL) ++ m_LSTRHandler(NULL), ++ m_LSTQHandler(NULL) + { + } + +@@ -745,6 +844,7 @@ + delete m_LSTCHandler; + delete m_LSTEHandler; + delete m_LSTRHandler; ++ delete m_LSTQHandler; + delete m_RecPlayer; + } + +@@ -801,6 +901,7 @@ + else if (strcasecmp(Cmd, "LSTR") == 0) return CmdLSTR(param); + else if (strcasecmp(Cmd, "LSTT") == 0) return CmdLSTT(param); + else if (strcasecmp(Cmd, "LSTC") == 0) return CmdLSTC(param); ++ else if (strcasecmp(Cmd, "LSTQ") == 0) return CmdLSTQ(param); + + if (param == NULL) { + esyslog("ERROR: streamdev: this seriously shouldn't happen at %s:%d", +@@ -1268,6 +1369,11 @@ + return CmdLSTX(m_LSTRHandler, Option); + } + ++bool cConnectionVTP::CmdLSTQ(char *Option) ++{ ++ return CmdLSTX(m_LSTQHandler, Option); ++} ++ + // Functions adopted from SVDRP + #define INIT_WRAPPER() bool _res + #define Reply(c,m...) _res = Respond(c,m) +diff -NaurwB streamdev-unpatched/server/connectionVTP.h streamdev/server/connectionVTP.h +--- streamdev-unpatched/server/connectionVTP.h 2009-07-01 12:46:16.000000000 +0200 ++++ streamdev/server/connectionVTP.h 2009-11-22 16:08:51.000000000 +0100 +@@ -11,6 +11,7 @@ + class cLSTCHandler; + class cLSTTHandler; + class cLSTRHandler; ++class cLSTQHandler; + + class cConnectionVTP: public cServerConnection { + friend class cLSTEHandler; +@@ -36,6 +37,7 @@ + cLSTCHandler *m_LSTCHandler; + cLSTTHandler *m_LSTTHandler; + cLSTRHandler *m_LSTRHandler; ++ cLSTQHandler *m_LSTQHandler; + + protected: + template<class cHandler> +@@ -72,6 +74,7 @@ + bool CmdLSTC(char *Opts); + bool CmdLSTT(char *Opts); + bool CmdLSTR(char *Opts); ++ bool CmdLSTQ(char *Opts); + + // Commands adopted from SVDRP + bool CmdSTAT(const char *Option); +diff -NaurwB streamdev-unpatched/services/femonservice.h streamdev/services/femonservice.h +--- streamdev-unpatched/services/femonservice.h 1970-01-01 01:00:00.000000000 +0100 ++++ streamdev/services/femonservice.h 2009-10-01 03:20:00.000000000 +0200 +@@ -0,0 +1,26 @@ ++/* ++ * Frontend Status Monitor plugin for the Video Disk Recorder ++ * ++ * See the README file for copyright information and how to reach the author. ++ * ++ */ ++ ++#ifndef __FEMONSERVICE_H ++#define __FEMONSERVICE_H ++ ++#include <linux/dvb/frontend.h> ++ ++struct FemonService_v1_0 { ++ cString fe_name; ++ cString fe_status; ++ uint16_t fe_snr; ++ uint16_t fe_signal; ++ uint32_t fe_ber; ++ uint32_t fe_unc; ++ double video_bitrate; ++ double audio_bitrate; ++ double dolby_bitrate; ++ }; ++ ++#endif //__FEMONSERVICE_H ++ diff --git a/plugins/streamdev/plugin.sh b/plugins/streamdev/plugin.sh index 81e6278..5bd439b 100755 --- a/plugins/streamdev/plugin.sh +++ b/plugins/streamdev/plugin.sh @@ -3,7 +3,7 @@ # x-vdr (Installations-Skript fuer einen VDR mit Debian als Basis) # von Marc Wernecke - www.zulu-entertainment.de # 07.04.2009 -# +# kwacker: auf cvs umgestellt und Patches hinzugefuegt 11.04.2010 # vdr-streamdev # defaults @@ -14,7 +14,7 @@ source ./../../functions WEB="http://www.zulu-entertainment.de/files/vdr-streamdev/vdr-streamdev-0.5.0-pre.tgz" VERSION="streamdev-0.5.0-pre" LINK="streamdev" -CVS="0" +CVS="1" [ "$CVS" = "1" ] && VERSION="streamdev-cvs" diff --git a/plugins/streamdev/streamdev-cvs/CONTRIBUTORS b/plugins/streamdev/streamdev-cvs/CONTRIBUTORS new file mode 100644 index 0000000..33141a7 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/CONTRIBUTORS @@ -0,0 +1,144 @@ +Special thanks go to the following persons (if you think your name is missing +here, please send an email to vdrdev@schmirler.de): + +Klaus Schmidinger + for VDR as a whole + for permission to use VDR 1.6.0 cRemux code for PES remuxing + +Sascha Volkenandt, the original author, + for this great plugin + +The Metzler Brothers + as a lot of code has been taken from their libdvbmpeg package + +Angelus (DOm) + for providing Italian language texts + for reporting problems with the Elchi-Patch + +Michal + for sending a patch to select the HTTP streamtype via remote + +Rolf Ahrenberg + for providing Finnish language texts + for adding externremux.sh commandline parameter + for silencing compiler warnings + for adding PAT, PMT, PCR and EIT to HTTP TS streams + for fixing a memory leak in buffer overflow situations + for adding a return code check to vasprintf() + for suggesting a fix of the Makefile's default target + for a TS PAT repacker based on Petri Laine's VDR TS recording patch + for making it possible to pass parameters to externremux.sh + for removing pre VDR 1.4 legacy code + for adding gettext support + for fixing output format of some debug messages + for replacing private members by cThread::Running()/Active() + for improving externremux script termination + for fixing PAT repacker version field + for improving LIMIKUUTIO and PARENTALRATING patch detection + for suggesting to include the charset in HTTP replies + for requesting replacement of asprintf calls + +Rantanen Teemu + for providing vdr-incompletesections.diff + +Thomas Keil + for providing vdr-localchannelprovide.diff + for maintaining the plugin for a while + +Artur Skawina + for fixing an fd leak + +Norad + for reporting a problem terminated externremux.sh children + +Udo Richter + for fixing streamdev-server shutdown + for speeding up cPluginStreamdevServer::Active() + for adapting to VDR 1.5.0 API + for adapting to VDR 1.7.1 + +greenman + for reporting that the log could get flooded on connection failures. + +Petri Hintukainen + for making section filtering work + for fixing a segfault with VDR 1.5 + for fixing high CPU load when data stream is disconnected + for adding PAT, PMT and PCR to HTTP TS streams + for fixing a segfault / deadlock when shutting down + for fixing compiler warnings + for adding M3U playlists + +ollo + for suggesting support for WMM capable WLAN accesspoints + +vdr-freak + for reporting connection aborts when externremux ringbuffer is full + +alexw + for reporting client reconnect problems after a server restart + for a workaround for tuning problems with 1.5.x clients + +Olli Lammi + for fixing a busy wait when client isn't accepting data fast enough + for suggesting signaling instead of sleeping when writing to buffers + +Joerg Pulz + for his FreeBSD compatibility patch + +tobi + for pointing to unused files in the libdvbmpeg directory + +Diego Pierotto + for providing Italian language texts + +micky979 + for providing French language texts + +Tiroler + for reporting a problem when switching between encrypted channels + +Pixelpeter + for an initial fix to the "switching between ecncrypted channels" problem + +Anssi Hannula + for the vdr-1.6.0-intcamdevices patch + for fixing insecure format strings in LSTX handlers + +wirbel + for pointing out that section filtering is optional for VDR devices + for reporting a problem with Makefile defines in VDR 1.7.4+ + +Jori Hamalainen + for extensive testing while making stream compatible to Network Media Tank + for adding Network Media Tank browser support to HTML pages + +owagner + for pointing out a problem with the encrypted channel switching fix + for suggesting use of SO_KEEPALIVE socket option to detect dead sockets + +Joachim König-Baltes + for fixing Min/MaxPriority parsing + +Artem Makhutov + for suggesting and heavy testing IGMP based multicast streaming + +Alwin Esch + for adding XBMC support by extending VTP capabilities + for adding VDR 1.7.11 parental rating support for VTP LSTE command + for adding the DELT FORCE option to delete running timers + +BBlack + for reporting that updating recordings list on CmdPLAY is a bad idea + +Milan Hrala + for providing Slovak language texts + +Valdemaras Pipiras + for providing Lithuanian language texts + +sk8ter + for fixing failures when switching between two encrypted channels + +lhanisch + for fixing a memory leak in cStreamdevPatFilter::GetPid diff --git a/plugins/streamdev/streamdev-cvs/COPYING b/plugins/streamdev/streamdev-cvs/COPYING new file mode 100644 index 0000000..5b6e7c6 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/plugins/streamdev/streamdev-cvs/CVS/Entries b/plugins/streamdev/streamdev-cvs/CVS/Entries new file mode 100644 index 0000000..e441497 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/CVS/Entries @@ -0,0 +1,13 @@ +/CONTRIBUTORS/1.34/Sat Feb 20 23:02:10 2010// +/COPYING/1.1.1.1/Thu Dec 30 22:43:56 2004// +/HISTORY/1.56/Sat Feb 20 23:02:10 2010// +/Makefile/1.20/Wed Nov 4 11:12:20 2009// +/PROTOCOL/1.1.1.1/Thu Dec 30 22:43:58 2004// +/README/1.20/Mon Oct 19 06:19:10 2009// +/common.c/1.11/Fri Sep 18 10:41:41 2009// +/common.h/1.15/Fri Sep 18 10:41:41 2009// +/streamdev-client.c/1.6/Tue Apr 8 14:18:15 2008// +/streamdev-client.h/1.1.1.1/Thu Dec 30 22:43:59 2004// +/streamdev-server.c/1.12/Fri Jun 19 06:32:38 2009// +/streamdev-server.h/1.4/Mon Feb 19 12:08:16 2007// +D diff --git a/plugins/streamdev/streamdev-cvs/CVS/Entries.Log b/plugins/streamdev/streamdev-cvs/CVS/Entries.Log new file mode 100644 index 0000000..70e37b9 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/CVS/Entries.Log @@ -0,0 +1,8 @@ +A D/client//// +A D/libdvbmpeg//// +A D/patches//// +A D/po//// +A D/remux//// +A D/server//// +A D/streamdev//// +A D/tools//// diff --git a/plugins/streamdev/streamdev-cvs/CVS/Repository b/plugins/streamdev/streamdev-cvs/CVS/Repository new file mode 100644 index 0000000..d57072e --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/CVS/Repository @@ -0,0 +1 @@ +streamdev diff --git a/plugins/streamdev/streamdev-cvs/CVS/Root b/plugins/streamdev/streamdev-cvs/CVS/Root new file mode 100644 index 0000000..2c7f6ce --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/CVS/Root @@ -0,0 +1 @@ +:pserver:anoncvs@vdr-developer.org:/var/cvsroot diff --git a/plugins/streamdev/streamdev-cvs/HISTORY b/plugins/streamdev/streamdev-cvs/HISTORY new file mode 100644 index 0000000..c4fe3b7 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/HISTORY @@ -0,0 +1,333 @@ +VDR Plugin 'streamdev' Revision History +--------------------------------------- + +- fixed a memory leak in cStreamdevPatFilter::GetPid (thanks to lhanisch) +- length -1 is the correct value for streams in M3U playlists +- switching between two encrypted channels on the same transponder didn't + always work (thanks to sk8ter@vdrportal) +- added DELT FORCE option to delete running timers (thanks to Alwin Esch) +- added VDR 1.7.11 parental rating support for VTP LSTE command (thanks to + Alwin Esch) +- added Lithuanian translation (thanks to Valdemaras Pipiras) +- fixed missing virtual destructor for cTSRemux +- added defines for large file support to Makefile as required by VDR 1.7.4+ + (reported by wirbel@vdrportal) +- added Slovak translation (thanks to Milan Hrala) +- fixed regression from fix for switching between encrypted channels. It was + no longer possible to receive multiple (FTA) streams from the same + transponder +- silenced warnings concerning asprintf (requested by Rolf Ahrenberg) +- don't update recordings list on CmdPLAY (reported by BBlack) +- cleaned up common.h / common.c +- dropped cStreamdevMenuSetupPage +- report charset in HTTP replies (suggested by Rolf Ahrenberg) +- use SO_KEEPALIVE option on all sockets do detect dead sockets (thanks to + owagner) +- enable PatFilter for externremux, so VLC can be used as remuxer or client +- fixed insecure format strings in LSTX handlers (thanks to Anssi Hannula) +- updated Finish translation (thanks to Rolf Ahrenberg) +- removed redefinitions in includes - caused problems in older compilers +- fixed ts2ps.h defines +- fixed missing virtual for cTS2PESRemux destructor +- silenced format mismatch warning on 64bit OS +- added XBMC support by extending VTP capabilities (thanks to Alwin Esch) +- now there's a common baseclass for all remuxers, make use of it +- added cDevice::NumProvidedSystems() which was introduced in VDR 1.7.0 +- added namespace to remuxers +- increased WRITERBUFSIZE - buffer was too small for high bandwidth content +- removed cStreamdevStreamer::m_Running +- eliminated potential busy waits in remuxers +- updated cTSRemux static helpers to code of their VDR 1.6.0 counterparts +- re-enabled PES vor VDR 1.7.3+. Streamdev now uses a copy of VDR 1.6.0's + cRemux for TS to PES remuxing. +- make sure that only complete TS packets are written to ringbuffers +- use signaling instead of sleeps when writing to ringbuffers +- optimized cStreamdevPatFilter PAT packet initialization +- fixed cStreamdevPatFilter not processing PATs with length > TS_SIZE - 5 +- use a small ringbuffer for cStreamdevPatFilter instead of writing to + cStreamdevStreamers SendBuffer as two threads mustn't write to the same + ringbuffer +- added missing call to StopSectionHandler which could cause crashes when + shutting down VDR +- added IGMP based multicast streaming +- ignore trailing blank lines in HTTP requests +- fixed parsing Min/MaxPriority from config (thanks to Joachim König-Baltes) +- updated Finnish translation (thanks to Rolf Ahrenberg) +- added Min/MaxPriority parameters. Can be used to keep client VDR from + using streamdev e.g. when recording +- disabled PES for VDR 1.7.3+ +- added Network Media Tank browser support to HTML pages (thanks to Jori + Hamalainen) +- minor fixes of PAT repacker +- repack and send every PAT packet we receive +- fixed null pointer in server.c when cConnection::Accept() failes +- consider Pids from channels.conf when HTTP TS streaming. Section filtering + is an optional feature for VDR devices, so we must not rely on the PMT + alone (pointed out by wirbel@vdrportal) +- improved externremux script termination (thanks to Rolf Ahrenberg) +- use cThread::Running()/Active() instead of private members (thanks to + Rolf Ahrenberg) +- fixed output format of some debug messages (thanks to Rolf Ahrenberg) +- added HTTP authentication +- compatibility for VDR 1.7.1 (thanks to Udo Richter) +- added vdr-1.6.0-intcamdevices.patch (thanks to Anssi Hannula) +- fixed problem when switching from one encrypted channel to an other + (reported by Tiroler@vdrportal, initial bugfix by pixelpeter@vdrportal, + another fix by owagner@vdrportal) +- added preprocessor directive for ancient gcc +- added Russian translation (thanks to Oleg Roitburd) +- fixed assignment of externremux.sh's default location (reported by plautze) +- added French translation (thanks to micky979) +- added Italian translation (thanks to Diego Pierotto) +- added gettext support (thanks to Rolf Ahrenberg) +- added vdr-1.6.0-ignore_missing_cam patch +- dropped obsolete respect_ca patch +- removed legacy code for < VDR 1.5.9 (thanks to Rolf Ahrenberg) + +2008-04-07: Branched v0_4 + +- changed location of streamdevhosts.conf to VDRCONFDIR/plugins/streamdev +- changed externremux.sh's default location to VDRCONFDIR/plugins/streamdev +- added sample externremux.sh from http://www.vdr-wiki.de/ +- stop providing channels after client has been disabled at runtime +- added logging of the client device's card index +- changed default suspend mode to "Always suspended" +- added "Hide Mainmenu Entry" setup option on client +- resurrected clients "Suspend Server" menu item as its mainmenu entry +- dropped unused code for remote timers/recordings on client side +- dropped unused files client/{assembler,menu,remote}.[hc] +- dropped unused files in libdvbmpeg (reported by tobi) +- dropped patches for pre VDR 1.4 +- removed legacy code for pre VDR 1.4 (thanks to Rolf Ahrenberg) + +2008-03-31: Version 0.3.4 + +- added possibility to pass parameter to externremux.sh (thanks to Rolf + Ahrenberg) +- use HTTP host header in absolute URLs for DNAT / reverse proxy support +- rewrite of the HTTP menu part +- added M3U playlists (thanks to Petri Hinutkainen) +- enable section filtering only with compatible clients (thanks to Petri + Hintukainen) +- fixed compiler warning +- added EIT to HTTP TS streams (thanks to Rolf Ahrenberg) +- compatibility for FreeBSD (thanks to Joerg Pulz) +- added TS PAT repacker (thanks to Rolf Ahrenberg) +- fixed Makefile's default target (suggested by Rolf Ahrenberg) +- workaround for tuning problems on 1.5.x clients (thanks to alexw) +- added VTP support for PS, PES and EXTERN (PS requested by mpanczyk) +- fixed gcc-4.3.0 warnings (thanks to Petri Hintukainen) +- fixed busy wait when client isn't accepting data fast enough (thanks to + Olli Lammi) +- fixed client reconnect after server restart (reported by alexw) +- added lock in ~cStreamdevDevice (thanks to Petri Hintukainen) +- externremux: check for ringbuffer full condition (reported by + vdr-freak@vdrportal) +- diffserv support for traffic shaping and WMM capable WLAN accesspoint + (suggested by ollo@vdrportal) +- check vasprintf() return code (thanks to Rolf Ahrenberg) +- fixed memory leak in buffer overflow situations (thanks to Rolf Ahrenberg) +- added PAT, PMT and PCR to HTTP TS streams (thanks to Petri Hintukainen and + Rolf Ahrenberg) +- detect data stream disconnections. Fixes high CPU load (thanks to Petri + Hintukainen) +- fixed segfault with VDR 1.5 (thanks to Petri Hintukainen) +- made section filtering work (thanks to Petri Hintukainen) +- added compiler flag -Wall and fixed corresponding warnings (thanks to + Rolf Ahrenberg) +- close pipe when externremux is gone. Fixes high CPU load problem +- close connection when client is gone. Fixes high CPU load problem +- silenced compiler warnings (thanks to Rolf Ahrenberg) +- added commandline parameter for externremux script (thanks to Rolf + Ahrenberg) +- detach receivers before switching transponders +- API changes for VDR 1.5.0 (thanks to Udo Richter) +- log connections failures only every 10s (reported by greenman@vdrportal) +- replaced uint64 by uint64_t +- added Recursion patch for vdr 1.4 +- added LocalChannelProvide for vdr 1.4.x +- added respect_ca patch +- speedup cPluginStreamdevServer::Active() by caching translation (thanks + to Udo Richter) +- periodically check if streamdev-server needs to shutdown (thanks to Udo + Richter) +- collect terminated externremux.sh processes (reported by Norad@vdrportal) +- avoid fd leaks when we fail to spawn externremux.sh +- detach all receivers before tuning to a different transponder +- Re-enabled logging for the Detach()/Attach() issue +- Added -fPIC compiler flag required on AMD64 architectures + +2006-08-17: End of maintenance by Thomas Keil + +- updated Finish translation (thanks to Rolf Ahrenberg) +- fixed fd leak (thanks to Artur Skawina) +- re-enabled Detach/Attach to temporarily release the device used by + streamdev while checking if we can switch transponders (thanks to + PanamaJack@vdrportal) +- adopted to VDR 1.4.x + +2006-01-26: End of maintenance by Sascha Volkenandt + +- fixed http error response +- added class forward declaration for gcc >= 4.0 +- adopted to VDR >= 1.3.36 +- added LocalChannelProvide for vdr 1.3.24 +- fixed missing include +- added TS compatibility mode +- deleting whole block instead of fractions now +- fixed wrong remux usage +- added finish translations (thanks to Rolf Ahrenberg) +- protected cStreamer::Stop() from being called concurrently +- some compilers complained about missing declarations, added <ctype.h> +- removed assembler and thus saving one ringbuffer +- fixed destruction order on channel switch (fixes one crash that happens + occasionally when switching) +- removed client menu code temporarily +- streamer now gets stopped when connection terminates unexpectedly +- fixed recursive delete in streamer +- fixed pure virtual crash in server +- audio track selection for http + +2004-??-??: Version 0.3.3 + +- dropped support for non-ts streaming in vdr-to-vdr clients +- implemented packet buffer that seems to improve distortions +- greatly re-worked device selection on server and client + (vdr-to-vdr clients should behave exactly like clients with one card, + can't test conditional access, though) +- now printing an error and exiting if streamdevhosts.conf is not existing +- increased client stream priority to 1 +- implemented remote schedule to program remote timers directly from schedule +- the servers are turned on by default now +- new setup parameters "Bind to IP" for both servers for binding to a specific + interface +- re-implemented section streaming (turned off by default, see setup menu) +- implemented a possibility to prevent a shutdown when clients are connected + (patch VDR with patches/vdr-pluginactivity.diff if you want this feature) +- implemented channel listing through channels.htm(l) URI + +????-??-??: Version 0.3.2 + +... has myteriously disappeared :-) + +2004-02-16: Version 0.3.1 (unstable) + +- Added finnish language texts (thanks to Rolf Ahrenberg) +- Increased all ringbuffer sizes to 3 MB +- Autodetecting VDR 1.2.x, 1.2.x with AutoPID and 1.3.x on compilation +- Server is only restarted if necessary after confirming setup +- Implemented PID-based streaming (only needed PIDs are transferred instead of + all PIDs of the requested channel) (configurable) +- Implemented an editor for remote timers +- Implemented manual EPG synchronization from client +- Implemented Server Suspend remotely from client (configurable) +- Implemented an IP-Editor for the setup menu +- Separated Client and Server into two PlugIns +- Increased initial number of clients to five +- Implemented host-based authorization (syntax is equal to svdrphosts.conf) +- Removed two irritating messages that appeared sometimes while exiting VDR +- Implemented "Choose, Always, Never" for Suspend Mode, so it can be configured + to behave like 0.2.0 (Always), 0.3.0 (Choose) or completely different (Never) +- Added missing translation entries +- Added PlugIn description to translation table +- Fully upgraded to VDR 1.3.X regarding threading (but still works with 1.2.6) +- Reworked manual (almost everything) + +2003-10-10: Version 0.3.0 (unstable) + +- Implemented "Suspend Live TV" in the VDR server (configurable) +- Reimplemented choice of device for live streaming (better for switching on + client, and server doesn't loose live-tv) +- Added missing translation entries +- Increased client's streaming buffer size from 1 to 3 MB +- Updated installation instructions (including a patch to VDR that is + recommended currently) +- Updated manual + +2003-10-04: Version 0.2.0 + +- Removed those silly warnings in the toolbox-headers +- Implemented intelligent buffer overflow logging (doesn't flood syslog) +- Implemented EPG synchronization in the VDR client (configurable) +- Station name is transmitted in radio streaming now (Shoutcast-format). + +2003-09-24: Version 0.1.1beta1 + +- Restructured remuxer code +- Added an ES-remuxer for radio channels (currently only manually) + +2003-09-20: Version 0.1.0 + +- Fixed thread-abortion timeout in server thread + +2003-08-31: Version 0.1.0beta4 + +- Added italian language texts (thanks to Angelus (DOm)) +- Added a missing i18n translation (thanks to DOm) +- Added an #ifdef so the setup menu is displayed correctly with ElchiAIO + (thanks to DOm for reporting this one) +- It's possible to select the HTTP streamtype remotely, specified in the + URL in addition to the old behaviour (thanks to Michal Novotny) +- Fixed creation ob remuxer objects in the server +- Fixed handling of timeout in cTBSelect + +2003-06-08: Version 0.1.0beta3 + +- Fixed setup menu - now the cursor starts at the first visible entry +- Added PS streaming for HTTP (should work with most players now) +- Debugging symbols are only compiled with DEBUG=1 set + +2003-06-06: Version 0.1.0beta2 + +- Added an #ifdef so this PlugIn will compile cleanly with the next + AUTOPID-patches +- Added categories to the menu +- Fixed segfault when closing the menu with OK +- Added an AnalogTV section to the README +- Added some missing i18n entries +- Corrected client reinitialization code (when changing client settings) +- Added PS streaming for HTTP (should work with most players now) +- Added -D_GNU_SOURCE to the Makefile (.......) + +2003-06-03: Version 0.1.0beta1 + +- Replaced the toolbox with a current version +- Rewrote the server core from scratch +- Rewrote the client core from scratch +- Reduced the size of blocks processed in a transceiver turn to 10 TS packets +- Added TS transmission for HTTP (configurable via setup) +- Most client settings can be done on-the-fly now +- MIME type for radio channels now "audio/mpeg" instead of "video/mpeg" + (still doesn't work really) + +2003-05-08: Version 0.0.3beta1 + +- Server stops correctly on VDR exit +- Fixed a race condition when several threads access the client device +- Made server code more modular +- Structured the directories +- Fixed a bug in informational log-message +- Added Apid2, Dpid1 and Ppid in TS mode (silly me;) ) + +2003-05-03: Version 0.0.2 + +- Device is not deactivated anymore, since VDR does that itself +- Server is correctly deactivated, so it can be faultlessly reactivated +- Did some major code cleanup +- Added new command to the PROTOCOL (to negotiate stream types) +- Added the possibility to stream TS between two VDR's (which adds the + possibility of having AC3, Teletext etc. on the client) - this is + autonegotiated +- Streamtype can be changed in the setup menu, if TS works too unreliable +- Fixed a bug in multi-threaded device operation +- Sharing an epg.data with a server will be possible even if there is no + DVB-Device present +- Added a basic HTTP daemon to the server code + +2003-03-17: Version 0.0.1a + +- Corrected some bugs in the README and on the homepage *g* + +2003-03-17: Version 0.0.1 + +- Initial revision. diff --git a/plugins/streamdev/streamdev-cvs/Makefile b/plugins/streamdev/streamdev-cvs/Makefile new file mode 100644 index 0000000..d4a7ed3 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/Makefile @@ -0,0 +1,150 @@ +# +# Makefile for a Video Disk Recorder plugin +# +# $Id: Makefile,v 1.20 2009/11/04 11:12:20 schmirl Exp $ + +# The official name of this plugin. +# This name will be used in the '-P...' option of VDR to load the plugin. +# By default the main source file also carries this name. +# +PLUGIN = streamdev + +### The version number of this plugin (taken from the main source file): + +VERSION = $(shell grep 'const char \*VERSION *=' common.c | awk '{ print $$5 }' | sed -e 's/[";]//g') + +### The C++ compiler and options: + +CXX ?= g++ +CXXFLAGS ?= -fPIC -g -O2 -Wall -Woverloaded-virtual -Wno-parentheses + +### The directory environment: + +VDRDIR = ../../.. +LIBDIR = ../../lib +TMPDIR = /tmp + +### Allow user defined options to overwrite defaults: + +-include $(VDRDIR)/Make.config + +### The version number of VDR (taken from VDR's "config.h"): + +APIVERSION = $(shell grep 'define APIVERSION ' $(VDRDIR)/config.h | awk '{ print $$3 }' | sed -e 's/"//g') +APIVERSNUM = $(shell grep 'define APIVERSNUM ' $(VDRDIR)/config.h | awk '{ print $$3 }' | sed -e 's/"//g') + +### The name of the distribution archive: + +ARCHIVE = $(PLUGIN)-$(VERSION) +PACKAGE = vdr-$(ARCHIVE) + +### Includes and Defines (add further entries here): + +INCLUDES += -I$(VDRDIR)/include -I. + +DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"' + +ifeq ($(shell test $(APIVERSNUM) -ge 10704; echo $$?),0) + DEFINES += -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE +endif + +### The object files (add further files here): + +COMMONOBJS = common.o \ + \ + tools/source.o tools/select.o tools/socket.o tools/tools.o + +CLIENTOBJS = $(PLUGIN)-client.o \ + \ + client/socket.o client/device.o client/setup.o \ + client/filter.o + + +SERVEROBJS = $(PLUGIN)-server.o \ + \ + server/server.o server/component.o server/connection.o \ + server/componentVTP.o server/componentHTTP.o server/componentIGMP.o \ + server/connectionVTP.o server/connectionHTTP.o server/connectionIGMP.o \ + server/streamer.o server/livestreamer.o server/livefilter.o \ + server/suspend.o server/setup.o server/menuHTTP.o server/recplayer.o \ + remux/tsremux.o remux/ts2pes.o remux/ts2ps.o remux/ts2es.o remux/extern.o + +ifdef DEBUG + DEFINES += -DDEBUG +endif + +### The main target: + +.PHONY: all i18n dist clean +all: libvdr-$(PLUGIN)-client.so libvdr-$(PLUGIN)-server.so i18n + +### Implicit rules: + +%.o: %.c + $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $< + +# Dependencies: + +MAKEDEP = $(CXX) -MM -MG +DEPFILE = .dependencies +ifdef GCC3 +$(DEPFILE): Makefile + @rm -f $@ + @for i in $(CLIENTOBJS:%.o=%.c) $(SERVEROBJS:%.o=%.c) $(COMMONOBJS:%.o=%.c) ; do \ + $(MAKEDEP) $(DEFINES) $(INCLUDES) -MT "`dirname $$i`/`basename $$i .c`.o" $$i >>$@ ; \ + done +else +$(DEPFILE): Makefile + @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(CLIENTOBJS:%.o=%.c) $(SERVEROBJS:%.o=%.c) \ + $(COMMONOBJS:%.o=%.c) > $@ +endif + +-include $(DEPFILE) + +### Internationalization (I18N): + +PODIR = po +LOCALEDIR = $(VDRDIR)/locale +I18Npo = $(wildcard $(PODIR)/*.po) +I18Nmsgs = $(addprefix $(LOCALEDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file)))))) +I18Npot = $(PODIR)/$(PLUGIN).pot + +%.mo: %.po + msgfmt -c -o $@ $< + +$(I18Npot): $(CLIENTOBJS:%.o=%.c) $(SERVEROBJS:%.o=%.c) $(COMMONOBJS:%.o=%.c) + xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --msgid-bugs-address='<http://www.vdr-developer.org/mantisbt/>' -o $@ $^ + +%.po: $(I18Npot) + msgmerge -U --no-wrap --no-location --backup=none -q $@ $< + @touch $@ + +$(I18Nmsgs): $(LOCALEDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo + @mkdir -p $(dir $@) + cp $< $@ + +i18n: $(I18Nmsgs) + +### Targets: + +libdvbmpeg/libdvbmpegtools.a: libdvbmpeg/*.c libdvbmpeg/*.h + $(MAKE) -C ./libdvbmpeg libdvbmpegtools.a + +libvdr-$(PLUGIN)-client.so: $(CLIENTOBJS) $(COMMONOBJS) libdvbmpeg/libdvbmpegtools.a +libvdr-$(PLUGIN)-server.so: $(SERVEROBJS) $(COMMONOBJS) libdvbmpeg/libdvbmpegtools.a + +%.so: + $(CXX) $(CXXFLAGS) -shared $^ -o $@ + @cp $@ $(LIBDIR)/$@.$(APIVERSION) + +dist: clean + @-rm -rf $(TMPDIR)/$(ARCHIVE) + @mkdir $(TMPDIR)/$(ARCHIVE) + @cp -a * $(TMPDIR)/$(ARCHIVE) + @tar czf $(PACKAGE).tgz --exclude CVS -C $(TMPDIR) $(ARCHIVE) + @-rm -rf $(TMPDIR)/$(ARCHIVE) + @echo Distribution package created as $(PACKAGE).tgz + +clean: + @-rm -f $(COMMONOBJS) $(CLIENTOBJS) $(SERVEROBJS) $(DEPFILE) $(PODIR)/*.mo $(PODIR)/*.pot *.so *.tgz core* *~ + $(MAKE) -C ./libdvbmpeg clean diff --git a/plugins/streamdev/streamdev-cvs/PROTOCOL b/plugins/streamdev/streamdev-cvs/PROTOCOL new file mode 100644 index 0000000..a0c999e --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/PROTOCOL @@ -0,0 +1,139 @@ +Written by: Sascha Volkenandt <sascha@akv-soft.de> + +Project's homepage: http://www.magoa.net/linux/ + +Version: 0.0.3 + +Description: +------------ + +I call this protocol "VTP", the Video Transfer Protocol. I hope that's not +already claimed by someone ;). + +This Protocol was created for Video Transfers over a Network. It is a text- +based protocol like the FTP, and is used by a client to communicate with a +server providing different types of video data, such as live streams, +recordings or disc media. The basic communication consists of short text +commands sent by the client, answered by numerical codes accompanied by +human-readable messages. All messages should be finished by a full CR/LF +line-ending, which should preferably written as "\015\012", as this is fully +platform-independent. Nevertheless, a client or (especially) a server should +also act on "\n" line-endings. The MPEG data is being transmitted over a +separate data connection. + +TODO: +- PORT behaviour changed +- TUNE syntax changed +- connection IDs +- new command PLAY + + +Response Code Summary + +Code Meaning +220 Last command ok / connection ready +221 Service is closing the connection afterwards +500 The command was not recognized +501 The parameters couldn't be interpreted correctly +550 Action not taken, for various reason +551 Action not taken, a subsequent connection was unsuccessful +560 Live-Stream not available currently [changed in 0.0.3] +561 Capability not known [new in 0.0.2] +562 Pid currently not available [new in 0.0.3] +563 Stream not available currently [new in 0.0.3] + + +Command Reference + +Command: Connect to VTP Server +Responses: 220 - The server is ready +Description: Upon connection to the server (which usually listens at port + 2004), the first thing the client has to expect is a welcome message with + the "220" response code. The client may now send a CAPS command, to tell + the server it's capabilities. + +Command: CAPS <Capability> +Responses: 220 - This capability is known and will be used from now on. + 561 - This capability is unknown, try anotherone +Description: This command tells the server to serve media data in a specific + format, like "PES" (for MPEG2-PES) or "TS" (for MPEG2-TS). A client can + do several CAPS commands until the server accepts one. So a client should + try all formats it can handle, descending from the most preffered one. If + no such command is sent, streaming is defaulted to PES. + Capabilities currently used: + TS Transport Stream (all PIDs belonging to a channel) + TSPIDS Only in conjunction with TS: Stream PIDs separately upon request + (this enables the ADDP/DELP commands) + PES Program Elementary stream (Video and primary Audio stream) +[new in 0.0.2,updated in 0.0.3] + +Command: PROV <Priority> <Media> +Responses: 220 - Media available for receive + 501 - The parameters were incorrect + 550 - The media couldn't be identified + 560 - This server can currently not serve that media +Description: With this command, the server is asked if the given media can + be received. The Priority is a number between 0 and 100 (in case a media + can not be received by an unlimited number of clients, the server shall + grant higher priorities before lower ones, and it shall also quit streams + with lower permissions if a higher one is requested), or -1 to ask the + server if this media is available at all. + The Media is a string defining the wanted media type. This is currently for + free use, it could for example carry a VDR unique channel id, to specify + a TV channel. + +Command: PORT <Id> <Address and Port> +Responses: 220 - The data connection was opened + 501 - The parameter list was wrong + 551 - The data connection was refused by the client or timed out +Description: The PORT command tells the server the target of a following + media transmission. The parameter Id specifies an index which is used to + establish multiple data connections over one control connection. It is used + later in the ABRT command to close a specific data connection. The second + parameter argument has six comma-separated fields, of which the first four + represent the target IP address, in the byte-order as the dot-notation + would be printed. The last two fields represent the target port, with the + high-byte first. To calculate the actual values, you could use the + following: + Field(5) = (RealPort & 0xFF00) shr 8 + Field(6) = RealPort & 0xFF + Reversed: + RealPort = (Field(5) shl 8) + Field(6) + After receiving the port command, the data connection is opened but no data + is transferred, yet. + Id's currently used: + 0 Data connection for live streams + 1 Data connection for saved streams +[changed in 0.0.3] + +Command: TUNE <Priority> <Media> +Responses: 220 - Data connection was opened successfully + 550 - The media couldn't be identified + 560 - The live stream is unavailable +Description: This command tells the media server to start the transfer over a + connection to a remote target established by the PORT command before. + Please look at the PROV command for the meaning of the parameters. The + server begins to send MPEG data. After the transfer has been started, the + response code "220" is sent. + +Command: ADDP <Pid> +Responses: 220 - The requested Pid is transferring + 560 - The requested Pid is not available currently +Description: This tells the server to start the transfer of a specific Pid + over a data connection established by the PORT command before. + +Command: DELP <Pid> +Responses: 220 - The requested Pid is transferring + 560 - The requested Pid was not transferring +Description: This tells the server to stop the transfer of a specific Pid + enabled by DELP before. + +Command: ABRT <Id> +Responses: 220 - Data connection closed +Description: This one should be sent before requesting another media or when + a media isn't needed anymore. It terminates the data connection previously + opened by PORT. + +Command: QUIT +Responses: 221 - Connection is being closed afterwards +Description: This commands terminates the client connection. diff --git a/plugins/streamdev/streamdev-cvs/README b/plugins/streamdev/streamdev-cvs/README new file mode 100644 index 0000000..8909653 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/README @@ -0,0 +1,432 @@ +This is a "plugin" for the Video Disk Recorder (VDR). + +Written by: Sascha Volkenandt <sascha@akv-soft.de> +Current maintainer: Frank Schmirler <vdrdev@schmirler.de> + +Project's homepage: http://streamdev.vdr-developer.org/ +Former project homepage: http://linux.kompiliert.net/ + +Latest version available at: http://streamdev.vdr-developer.org/ + +See the file COPYING for license information. + +Contents: +--------- + +1. Description +2. Installation +2.1 VDR 1.4.x and older +2.2 VDR 1.6.0 and above +2.3 Updating from streamdev 0.3.x +3. Usage +3.1 Usage HTTP server +3.2 Usage IGMP multicast server +3.3 Usage VDR-to-VDR server +3.4 Usage VDR-to-VDR client +4. Other useful Plugins +4.1 Plugins for VDR-to-VDR clients +4.2 Plugins for Server +4.3 Alternatives +5. Known Problems + + +1. Description: +--------------- + +This PlugIn is a VDR implementation of the VTP (Video Transfer Protocol) +Version 0.0.3 (see file PROTOCOL) and a basic HTTP Streaming Protocol. + +It consists of a server and a client part, but both parts are compiled together +with the PlugIn source, but appear as separate PlugIns to VDR. + +The client part acts as a full Input Device, so it can be used in conjunction +with a DXR3-Card, XINE, SoftDevice or others to act as a working VDR +installation without any DVB-Hardware including EPG-Handling. + +The server part acts as a Receiver-Device and works transparently in the +background within your running VDR. It can serve multiple clients and it can +distribute multiple input streams (i.e. from multiple DVB-cards) to multiple +clients using the native VTP protocol (for VDR-clients), or using the HTTP +protocol supporting clients such as XINE, MPlayer and so on. With XMMS or +WinAMP, you can also listen to radio channels over a HTTP connection. + +It is possible to attach as many clients as the bus and network can handle, as +long as there is a device which can receive a specific channel. Multiple +channels homed on the same transponder (which is determined by it's frequency) +can be broadcasted with a single device. + +Additional clients can be programmed using the Protocol Instructions inside +the PROTOCOL file. + + +2. Installation: +---------------- + +Let's say streamdev's version is 0.4.0 and vdr's version is 1.X.X. If you +use anything else please exchange the version numbers appropriately (this +way I don't have to update this section all the times;) ). + +After compiling the PlugIn as stated below, start either (or both) parts of it +by specifying "-P streamdev-client" and/or "-P streamdev-server" on the VDR +command line. + +What's important is that the client requests a channel using its Unique Channel +ID. So, in order to find the channel at the server, it must have the same ID +that is used on the client. You can achieve this by putting the server's +channels.conf on the client, preferably after scanning. + +If you want to drive additional Input-Devices (with different sources) on the +client, you can merge the channels.conf files. VDR will detect if the local +device or the network device can receive the channels. + +Last, but not least you have to copy the streamdev folder into the +"plugins/streamdev" subfolder of VDR's config-directory (which is equal to your +video-directory if not specified otherwise). For example, if you didn't specify +a separate config-directory, and specified your video directory as "/video0", +the directory has to be copied to /video0/plugins/streamdev. + +The directory contains a file named streamdevhosts.conf which you must adjust +to your needs. The syntax is the same as for svdrphosts.conf, so please consult +VDR's documentation on how to fill that file, if you can't do it on-the-fly. + +There's also a sample externremux.sh script in this directory. It is used by +streamdev's external remux feature. The sample script uses mencoder. Please +check the script for further information. You can specify a different script +location with the -r parameter. The VDR commandline would then include a +"-P 'streamdev-server -r /usr/local/bin/remux.sh'". Note the additional quotes, +as otherwise -r will be passed to VDR and not to streamdev. + + +2.1 VDR 1.4.x and older: +------------------------ + +This version is not compatible to VDR releases older than 1.5.9. Take one of +the streamdev-0.4.x releases if you are running at least VDR 1.4.x. For older +VDRs you will probably need one of the streamdev-0.3.x releases. + +2.2 VDR 1.6.0 and above: +------------------------ + +cd vdr-1.X.X/PLUGINS/src +tar xvfz vdr-streamdev-0.4.0.tgz +ln -s streamdev-0.4.0 streamdev +cp -r streamdev/streamdev VDRCONFDIR/plugins/ +cd ../.. +make [options, if necessary] vdr +make [options, if necessary] plugins + +2.3 Updating from streamdev 0.3.x +---------------------------------- + +Starting with streamdev 0.4.0, all additional files are kept in a directory +called "streamdev" inside VDR's plugin config directory. It is the new default +location of externremux.sh and the new place where streamdev-server expects the +file "streamdevhosts.conf". You will have to move this file to its new location: + +mv VDRCONFDIR/plugins/streamdevhosts.conf VDRCONFDIR/plugins/streamdev/ + +(Directory VDRCONFDIR/plugins/streamdev already exists, as you copied the +whole folder from the sources directory as suggested above, right?) + +Now check the contents of streamdevhosts.conf. Does it contain a "0.0.0.0/0" +entry? If your VDR machine is connected to the Internet, this line gives +*anyone* full access to streamdev, unless you took some other measures to +prevent this (e.g. firewall). You might want to remove this line and enable +HTTP authentication instead. + + +3. Usage: +--------- + +Start the server core itself by specifying -Pstreamdev-server on your VDR +commandline. To use the client core, specify -Pstreamdev-client. Both parts +can run in one VDR instance, if necessary. + +The parameter "Suspend behaviour" allows you to specify how the server should +react in case the client requests a channel that would require switching the +primary device (i.e. disrupt live-tv). If set to "Offer suspend mode", you will +have a new entry in the main menu. Activating that will put the server into +"Suspend Mode" (a picture is displayed on TV). Then, a client may switch the +primary card to wherever it likes to. While watching TV (Suspend deactivated), +the client may not switch the transponder on the primary device. If you set +the behaviour to "Always suspended" (the default), there will be normal live-tv +on the server, but whenever a client decides to switch the transponder, the +server will lose it's live-tv. Set to "Never suspended", the server always +prevents the client from switching transponders. If you set "Client may +suspend" to yes, the client can suspend the server remotely (this only applies +if "Offer suspend mode" is selected). + +NOTE: This mainly applies to One-Card-Systems, since with multiple cards there +is no need to switch transponders on the primary interface, if the secondary +can stream a given channel (i.e. if it is not blocked by a recording). If both +cards are in use (i.e. when something is recorded, or by multiple clients), +this applies to Multiple-Card-Systems as well. + +3.1 Usage HTTP server: +---------------------- + +You can use the HTTP part by accessing the server with a HTTP-capable media +player (such as XINE, MPlayer, and so on, if you have appropriate MPEG2-codecs +installed). In the PlugIn's Setup, you can specify the port the server will +listen to with the parameter "HTTP Server Port". The parameter "HTTP Streamtype" +allows you to specify a default stream type, which is used if no specific type +has been requested in the URL (see below). The supported stream types are: + +TS Transport Stream (i.e. a dump from the device) +PES Packetized Elemetary Stream (VDR's native recording format) +PS Program Stream (SVCD, DVD like stream) +ES Elementary Stream (only Video, if available, otherwise only Audio) +EXTERN Pass stream through external script (e.g. for converting with mencoder) + +Assuming that you leave the default port (3000), point your web browser to + +http://hostname:3000/ + +You will be presented a menu with links to various channel lists, including M3U +playlist formats. + +If you don't want to use the HTML menu or the M3U playlists, you can access the +streams directly like this: + +http://hostname:3000/3 +http://hostname:3000/S19.2E-0-12480-898 + +The first one will deliver a channel by number on the server, the second one +will request the channel by unique channel id. In addition, you can specify +the desired stream type as a path to the channel. + +http://hostname:3000/TS/3 +http://hostname:3000/PES/S19.2E-0-12480-898 + +The first one would deliver the stream in TS, the second one in PES format. +Possible values are 'PES', 'TS', 'PS', 'ES' and 'EXTERN'. You need to specify +the ES format explicitly if you want to listen to radio channels. Play them +back i.e. with mpg123. + +mpg123 http://hostname:3000/ES/200 + +With 'EXTERN' you can also add a parameter which is passed as argument to the +externremux script. + +http://hostname:3000/EXTERN;some_parameter/3 + +If you want to access streamdev's HTTP server from the Internet, do *not* grant +access for anyone by allowing any IP in "streamdevhosts.conf". Instead, pass the +"-a" commandline option to streamdev-server. It takes a username and a password +as argument. Clients with an IP not accepted by "streamdevhosts.conf" will then +have to login. The VDR commandline will have to look like this: + +vdr ... -P 'streamdev-server -a vdr:secret' ... + +Note the single quotes, as otherwise "-a" will be passed to VDR and not to +streamdev-server. The login ("vdr" in the example above) doesn't have to exist +as a system account. + +3.2 Usage IGMP multicast server: +-------------------------------- + +IGMP based multicast streaming is often used by settop boxes to receive IP TV. +Streamdev's multicast server allows you to feed live TV from VDR to such a +settop box. VLC is known to work well if you look for a software client. + +The advantage of multicasting is that the actual stream is sent out only once, +regardless of how many clients want to receive it. The downside is, that you +cannot simply multicast across network boundaries. You need multicast routers. +For multicast streaming over the public Internet you would even need to register +for your own IP range. So don't even think of multicasting via Internet with +streamdev! Streamdev will send the stream only to one local ethernet segment and +all clients must be connected to this same segment. There must not be a router +inbetween. Also note that the client must not run on the streamdev-server +machine. + +Each channel is offered on a different multicast IP. Channel 1 is available from +multicast IP 239.255.0.1, channel 2 from 239.255.0.2 and so on. The upper limit +is 239.255.254.255 which corresponds to channel 65279 (239.255.255.0/24 is +reserved according to RFC-2365). + +Before you can use streamdev's multicast server, you might need to patch VDR. +Binding an IGMP socket is a privileged operation, so you must start VDR as root. +If you pass the -u option to VDR, it will drop almost all priviledges before +streamdev is even loaded. Apply vdr-cap_net_raw.diff to keep VDR from dropping +the CAP_NET_RAW capability required to bind the IGMP socket. The patch is part +of streamdev's source distribution. Check the patches subdirectory. There's no +need to patch VDR if it is kept running as root (not recommended). + +The multicast server is disabled by default. Enter the streamdev-server setup +menu to enable it and - IMPORTANT - bind the multicast server to the IP of your +VDR server's LAN ethernet card. The multicast server will refuse to start with +the default bind adresse "0.0.0.0". + +Now edit your streamdevhosts.conf. To allow streaming of all channels, it must +contain "239.255.0.0/16". Note that you cannot limit connections by client IP +here. You can however restrict which channels are allowed to be multicasted. +Enter individual multicast IPs instead of "239.255.0.0/16". + +By default, the linux kernel will refuse to join more than 20 multicast groups. +You might want to increase this up to "number_of_channels + 1". Note that it's +"number_of_channels", not "maximum_channel_number". + + #First 100 channels: + bash# sysctl -w net.ipv4.igmp_max_memberships=101 + + #All channels: + bash# COUNT=$(grep -c '^[^:]' PATH_TO_YOUR/channels.conf) + bash# sysctl -w net.ipv4.igmp_max_memberships=$COUNT + +A multicast server never knows how many clients are actually receiving a stream. +If a client signals that it leaves a multicast group, the server has to query +for other listeners before it can stop the stream. This may delay zapping from +one transponder to an other. The client will probably requests the new channel +before the previous stream has been stopped. If there's no free DVB card, VDR +won't be able to fulfill the request until a DVB card becomes available and the +client resends the request. + +3.3 Usage VDR-to-VDR server: +---------------------------- + +You can activate the VDR-to-VDR server part in the PlugIn's Setup Menu. It is +deactivated by default. The Parameter "VDR-to-VDR Server Port" specifies the +port where you want the server to listen for incoming connections. The server +will be activated when you push the OK button inside the setup menu, so there's +no need to restart VDR. + +3.4 Usage VDR-to-VDR client: +---------------------------- + +Streamdev-client adds a "Suspend Server" item to VDR's mainmenu. With the +setup parameter "Hide Mainmenu Entry" you can hide this menu item if you don't +need it. "Suspend Server" is only useful if the server runs in "Offer suspend +mode" with "Client may suspend" enabled. + +The parameter "Remote IP" uses an IP-Adress-Editor, where you can just enter +the IP number with the number keys on your remote. After three digits (or if +the next digit would result in an invalid IP adress, or if the first digit is +0), the current position jumps to the next one. You can change positions with +the left and right buttons, and you can cycle the current position using up +and down. To confirm the entered address, press OK. So, if you want to enter +the IP address "127.0.0.1", just mark the corresponding entry as active and +type "127001<OK>" on your remote. If you want to enter "192.168.1.12", type +"1921681<Right>12<OK>". + +The parameters "Remote IP" and "Remote Port" in the client's setup specify the +address of the remote VDR-to-VDR server to connect to. Activate the client by +setting "Start Client" to yes. It is disabled by default, because it wouldn't +make much sense to start the client without specifying a server anyway. The +client is activated after you push the OK button, so there's no need to restart +VDR. Deactivation on-the-fly is not possible, so in order to deactivate the +client, you will have to restart VDR. However requests to switch channels will +be refused by streamdev-client once it has been deactivated. All other settings +can be changed without restarting VDR. + +The client will try to connect to the server (in case it isn't yet) whenever +a remote channel is requested. Just activate the client and switch to a +channel that's not available by local devices. If anything goes wrong with the +connection between the two, you will see it in the logfile instantly. If you +now switch the client to a channel which isn't covered by it's own local +devices, it will ask the server for it. If the server can (currently) receive +that channel, the client will show it until you switch again, or until the +server needs that card (if no other is free) for a recording on a different +transponder. + +Only the needed PIDs are transferred, and additional PIDs can be turned on +during an active transfer. This makes it possible to switch languages, receive +additional channels (for recording on the client) and use plugins that use +receivers themselves (like osdteletext). + +With "Filter Streaming" enabled, the client will receive meta information like +EPG data and service information, just as if the client had its own DVB card. +Link channels and even a client-side EPG scan have been reported to work. + +The next parameter, "Synchronize EPG", will have the client synchronize it's +program table with the server every now and then, but not regularly. This +happens when starting the client, and everytime VDR does its housekeeping +tasks. The only thing that's guaranteed is, that there will be a minimum +interval of ten seconds between each EPG synchronization. With "Filter +Streaming" this option has been obsoleted. If you still need to synchronize +EPG as additional information is available from the server, you should use the +epgsync-plugin instead (http://vdr.schmirler.de). + +Finally with the maximum and minimum priority, you can keep VDR from considering +streamdev in certain cases. If for instance you have a streamdev client with its +own DVB card, VDR would normally use streamdev for recording. If this is not +what you want, you could set the maximum priority to 0. As recordings usually +have a much higher priority (default 50), streamdev is now no longer used for +recordings. The two parameters define the inclusive range of priorities for +which streamdev will accept to tune. Setting the minimum priority to a higher +value than the maximum, you will get two ranges: "up to maximum" and "minimum +and above". + +4. Other useful Plugins: +------------------------ + +4.1 Plugins for VDR-to-VDR clients: +----------------------------------- + +The following plugins are useful for VDR-to-VDR clients (i.e. VDRs running the +streamdev-client): + +* remotetimers (http://vdr.schmirler.de/) +Add, edit, delete timers on client and server + +* timersync (http://phivdr.dyndns.org/vdr/vdr-timersync/) +Automatically syncronizes timer lists of client and server. All recordings will +be made on the server + +* remoteosd (http://vdr.schmirler.de/) +Provides access to the server's OSD menu + +* epgsync (http://vdr.schmirler.de/) +Import EPG from server VDR + +* femon (http://www.saunalahti.fi/~rahrenbe/vdr/femon/) +Display signal information from server's DVB card. SVDRP support must be enabled +in femon's setup + +4.2 Plugins for Server: +----------------------- + +* dummydevice (http://phivdr.dyndns.org/vdr/vdr-dummydevice/) +Recommended on a headless server (i.e. a server with no real output device). +Without this plugin, a budget DVB card could become VDR's primary device. This +causes unwanted sideeffects in certain situations. + +4.3 Alternatives: +----------------- + +* xineliboutput (http://phivdr.dyndns.org/vdr/vdr-xineliboutput/) +With its networking option, xineliboutput provides an alternative to streamdev. +You will get the picture of the server VDR, including its OSD. However you +won't get independent clients, as they all share the same output. + +5. Known Problems: +------------------ + +* In VDR-to-VDR setup, the availability of a channel is checked with a different +priority than the actual channel switch. The later always uses priority 0. +Usually a channel switch for live TV has priority 0 anyway, so it is not a +problem here. However timers usually have a higher priority. Either avoid +client side recordings or set the priority of client side timers to 0. + +* There have been reports that channel switching with VDR 1.5.x/1.6.x clients +sometimes fails. Current version includes a workaround which seems to work, but +YMMV ;) + +* Viewing encrypted channels became an issue with VDR's new CAM handling code. +Streamdev doesn't provide a (dummy) CAM, so out of the box, VDR won't ever try +to receive encrypted channels from streamdev. Pick one of the following +solutions to work around the problem: + +1. Force VDR to use streamdev. Open the channels menu on the client (or edit its +channels.conf if you know how to do this) and set the CA field of all channels +that only the server can decrypt to streamdev's device index. Usually streamdev +will get number 9 or 10. Streamdev logs the actual device number when starting +up. So please consider the logs for the correct value. Remember to fill in +hexadecimal values if you are using an editor to modify your channels.conf +(number 10 becomes an "a", number 11 a "b", ...). + +2. Apply either patch "patches/vdr-1.6.0-intcamdevices.patch" or patch +"patches/vdr-1.6.0-ignore_missing_cam.diff" to your client VDR. Intcamdevices +is the clean solution. But as it modifies the VDR API, so you will need to +recompile all of your plugins. The ignore_missing_cam patch is trivial, no need +to recompile other plugins. However it is not suitable for clients with a DVB +card of their own. diff --git a/plugins/streamdev/streamdev-cvs/client/CVS/Entries b/plugins/streamdev/streamdev-cvs/client/CVS/Entries new file mode 100644 index 0000000..8647f66 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/client/CVS/Entries @@ -0,0 +1,9 @@ +/device.c/1.25/Wed Feb 17 12:39:03 2010// +/device.h/1.9/Tue Jun 23 10:26:54 2009// +/filter.c/1.14/Fri Feb 13 13:02:39 2009// +/filter.h/1.5/Mon Apr 7 14:27:28 2008// +/setup.c/1.9/Fri Sep 18 10:43:26 2009// +/setup.h/1.6/Fri Sep 18 10:43:26 2009// +/socket.c/1.12/Tue Apr 8 14:18:16 2008// +/socket.h/1.6/Mon Apr 7 14:40:40 2008// +D diff --git a/plugins/streamdev/streamdev-cvs/client/CVS/Repository b/plugins/streamdev/streamdev-cvs/client/CVS/Repository new file mode 100644 index 0000000..885b4a3 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/client/CVS/Repository @@ -0,0 +1 @@ +streamdev/client diff --git a/plugins/streamdev/streamdev-cvs/client/CVS/Root b/plugins/streamdev/streamdev-cvs/client/CVS/Root new file mode 100644 index 0000000..2c7f6ce --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/client/CVS/Root @@ -0,0 +1 @@ +:pserver:anoncvs@vdr-developer.org:/var/cvsroot diff --git a/plugins/streamdev/streamdev-cvs/client/device.c b/plugins/streamdev/streamdev-cvs/client/device.c new file mode 100644 index 0000000..551d7c2 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/client/device.c @@ -0,0 +1,306 @@ +/* + * $Id: device.c,v 1.25 2010/02/17 12:39:03 schmirl Exp $ + */ + +#include "client/device.h" +#include "client/setup.h" +#include "client/filter.h" + +#include "tools/select.h" + +#include <vdr/channels.h> +#include <vdr/ringbuffer.h> +#include <vdr/eit.h> +#include <vdr/timers.h> + +#include <time.h> +#include <iostream> + +using namespace std; + +#define VIDEOBUFSIZE MEGABYTE(3) + +cStreamdevDevice *cStreamdevDevice::m_Device = NULL; + +cStreamdevDevice::cStreamdevDevice(void) { + m_Channel = NULL; + m_TSBuffer = NULL; + + m_Filters = new cStreamdevFilters; + StartSectionHandler(); + isyslog("streamdev-client: got device number %d", CardIndex() + 1); + + m_Device = this; + m_Pids = 0; + m_DvrClosed = true; + + if (StreamdevClientSetup.SyncEPG) + ClientSocket.SynchronizeEPG(); +} + +cStreamdevDevice::~cStreamdevDevice() { + Dprintf("Device gets destructed\n"); + + Lock(); + m_Device = NULL; + m_Filters->SetConnection(-1); + ClientSocket.Quit(); + ClientSocket.Reset(); + Unlock(); + + Cancel(3); + +#if APIVERSNUM >= 10515 + StopSectionHandler(); +#endif + DELETENULL(m_Filters); + DELETENULL(m_TSBuffer); +} + +bool cStreamdevDevice::ProvidesSource(int Source) const { + Dprintf("ProvidesSource, Source=%d\n", Source); + return true; +} + +bool cStreamdevDevice::ProvidesTransponder(const cChannel *Channel) const +{ + Dprintf("ProvidesTransponder\n"); + return true; +} + +bool cStreamdevDevice::IsTunedToTransponder(const cChannel *Channel) +{ + bool res = false; + if (ClientSocket.DataSocket(siLive) != NULL + && TRANSPONDER(Channel, m_Channel) + && Channel->Ca() == CA_FTA + && m_Channel->Ca() == CA_FTA) + res = true; + return res; +} + +bool cStreamdevDevice::ProvidesChannel(const cChannel *Channel, int Priority, + bool *NeedsDetachReceivers) const { + bool res = false; + bool prio = Priority < 0 || Priority > this->Priority(); + bool ndr = false; + + if (!StreamdevClientSetup.StartClient) + return false; + + Dprintf("ProvidesChannel, Channel=%s, Prio=%d\n", Channel->Name(), Priority); + + if (StreamdevClientSetup.MinPriority <= StreamdevClientSetup.MaxPriority) + { + if (Priority < StreamdevClientSetup.MinPriority || + Priority > StreamdevClientSetup.MaxPriority) + return false; + } + else + { + if (Priority < StreamdevClientSetup.MinPriority && + Priority > StreamdevClientSetup.MaxPriority) + return false; + } + + if (ClientSocket.DataSocket(siLive) != NULL + && TRANSPONDER(Channel, m_Channel)) + res = true; + else { + res = prio && ClientSocket.ProvidesChannel(Channel, Priority); + ndr = true; + } + + if (NeedsDetachReceivers) + *NeedsDetachReceivers = ndr; + Dprintf("prov res = %d, ndr = %d\n", res, ndr); + return res; +} + +bool cStreamdevDevice::SetChannelDevice(const cChannel *Channel, + bool LiveView) { + Dprintf("SetChannelDevice Channel: %s, LiveView: %s\n", Channel->Name(), + LiveView ? "true" : "false"); + + if (LiveView) + return false; + + if (ClientSocket.DataSocket(siLive) != NULL + && TRANSPONDER(Channel, m_Channel) + && Channel->Ca() == CA_FTA + && m_Channel->Ca() == CA_FTA) + return true; + + DetachAllReceivers(); + m_Channel = Channel; + bool r = ClientSocket.SetChannelDevice(m_Channel); + Dprintf("setchanneldevice r=%d\n", r); + return r; +} + +bool cStreamdevDevice::SetPid(cPidHandle *Handle, int Type, bool On) { + Dprintf("SetPid, Pid=%d, Type=%d, On=%d, used=%d\n", Handle->pid, Type, On, + Handle->used); + LOCK_THREAD; + + if (On && !m_TSBuffer) { + Dprintf("SetPid: no data connection -> OpenDvr()"); + OpenDvrInt(); + } + + bool res = true; + if (Handle->pid && (On || !Handle->used)) { + res = ClientSocket.SetPid(Handle->pid, On); + + m_Pids += (!res) ? 0 : On ? 1 : -1; + if (m_Pids < 0) + m_Pids = 0; + + if(m_Pids < 1 && m_DvrClosed) { + Dprintf("SetPid: 0 pids left -> CloseDvr()"); + CloseDvrInt(); + } + } + + return res; +} + +bool cStreamdevDevice::OpenDvrInt(void) { + Dprintf("OpenDvrInt\n"); + LOCK_THREAD; + + CloseDvrInt(); + if (m_TSBuffer) { + Dprintf("cStreamdevDevice::OpenDvrInt(): DVR connection already open\n"); + return true; + } + + Dprintf("cStreamdevDevice::OpenDvrInt(): Connecting ...\n"); + if (ClientSocket.CreateDataConnection(siLive)) { + m_TSBuffer = new cTSBuffer(*ClientSocket.DataSocket(siLive), MEGABYTE(2), CardIndex() + 1); + return true; + } + esyslog("cStreamdevDevice::OpenDvrInt(): DVR connection FAILED"); + return false; +} + +bool cStreamdevDevice::OpenDvr(void) { + Dprintf("OpenDvr\n"); + LOCK_THREAD; + + m_DvrClosed = false; + return OpenDvrInt(); +} + +void cStreamdevDevice::CloseDvrInt(void) { + Dprintf("CloseDvrInt\n"); + LOCK_THREAD; + + if (ClientSocket.CheckConnection()) { + if (!m_DvrClosed) { + Dprintf("cStreamdevDevice::CloseDvrInt(): m_DvrClosed=false -> not closing yet\n"); + return; + } + if (m_Pids > 0) { + Dprintf("cStreamdevDevice::CloseDvrInt(): %d active pids -> not closing yet\n", m_Pids); + return; + } + } else { + Dprintf("cStreamdevDevice::CloseDvrInt(): Control connection gone !\n"); + } + + Dprintf("cStreamdevDevice::CloseDvrInt(): Closing DVR connection\n"); + // Hack for VDR 1.5.x clients (sometimes sending ABRT after TUNE) + // TODO: Find a clean solution to fix this + ClientSocket.SetChannelDevice(m_Channel); + ClientSocket.CloseDvr(); + DELETENULL(m_TSBuffer); +} + +void cStreamdevDevice::CloseDvr(void) { + Dprintf("CloseDvr\n"); + LOCK_THREAD; + + m_DvrClosed = true; + CloseDvrInt(); +} + +bool cStreamdevDevice::GetTSPacket(uchar *&Data) { + if (m_TSBuffer && m_Device) { + Data = m_TSBuffer->Get(); +#if 1 // TODO: this should be fixed in vdr cTSBuffer + // simple disconnect detection + static int m_TSFails = 0; + if (!Data) { + LOCK_THREAD; + if(!ClientSocket.DataSocket(siLive)) { + return false; // triggers CloseDvr() + OpenDvr() in cDevice + } + cPoller Poller(*ClientSocket.DataSocket(siLive)); + errno = 0; + if (Poller.Poll() && !errno) { + char tmp[1]; + if (recv(*ClientSocket.DataSocket(siLive), tmp, 1, MSG_PEEK) == 0 && !errno) { +esyslog("cStreamDevice::GetTSPacket: GetChecked: NOTHING (%d)", m_TSFails); + m_TSFails++; + if (m_TSFails > 10) { + isyslog("cStreamdevDevice::GetTSPacket(): disconnected"); + m_Pids = 0; + CloseDvrInt(); + m_TSFails = 0; + return false; + } + return true; + } + } + m_TSFails = 0; + } +#endif + return true; + } + return false; +} + +int cStreamdevDevice::OpenFilter(u_short Pid, u_char Tid, u_char Mask) { + Dprintf("OpenFilter\n"); + + if (!StreamdevClientSetup.StreamFilters) + return -1; + + + if (!ClientSocket.DataSocket(siLiveFilter)) { + if (ClientSocket.CreateDataConnection(siLiveFilter)) { + m_Filters->SetConnection(*ClientSocket.DataSocket(siLiveFilter)); + } else { + isyslog("cStreamdevDevice::OpenFilter: connect failed: %m"); + return -1; + } + } + + if (ClientSocket.SetFilter(Pid, Tid, Mask, true)) + return m_Filters->OpenFilter(Pid, Tid, Mask); + + return -1; +} + +bool cStreamdevDevice::Init(void) { + if (m_Device == NULL && StreamdevClientSetup.StartClient) + new cStreamdevDevice; + return true; +} + +bool cStreamdevDevice::ReInit(void) { + if(m_Device) { + m_Device->Lock(); + m_Device->m_Filters->SetConnection(-1); + m_Device->m_Pids = 0; + } + ClientSocket.Quit(); + ClientSocket.Reset(); + if (m_Device != NULL) { + //DELETENULL(m_Device->m_TSBuffer); + m_Device->Unlock(); + } + return StreamdevClientSetup.StartClient ? Init() : true; +} + diff --git a/plugins/streamdev/streamdev-cvs/client/device.h b/plugins/streamdev/streamdev-cvs/client/device.h new file mode 100644 index 0000000..e96b05f --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/client/device.h @@ -0,0 +1,68 @@ +/* + * $Id: device.h,v 1.9 2009/06/23 10:26:54 schmirl Exp $ + */ + +#ifndef VDR_STREAMDEV_DEVICE_H +#define VDR_STREAMDEV_DEVICE_H + +#include <vdr/device.h> + +#include "client/socket.h" +#include "client/filter.h" + +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; + bool m_DvrClosed; + + static cStreamdevDevice *m_Device; + + bool OpenDvrInt(void); + void CloseDvrInt(void); + +protected: + virtual bool SetChannelDevice(const cChannel *Channel, bool LiveView); + virtual bool HasLock(int TimeoutMs) + { + //printf("HasLock is %d\n", (ClientSocket.DataSocket(siLive) != NULL)); + //return ClientSocket.DataSocket(siLive) != NULL; + return true; + } + + virtual bool SetPid(cPidHandle *Handle, int Type, bool On); + virtual bool OpenDvr(void); + virtual void CloseDvr(void); + virtual bool GetTSPacket(uchar *&Data); + + virtual int OpenFilter(u_short Pid, u_char Tid, u_char Mask); + +public: + cStreamdevDevice(void); + virtual ~cStreamdevDevice(); + + virtual bool HasInternalCam(void) { return true; } + virtual bool ProvidesSource(int Source) const; + virtual bool ProvidesTransponder(const cChannel *Channel) const; + virtual bool ProvidesChannel(const cChannel *Channel, int Priority = -1, + bool *NeedsDetachReceivers = NULL) const; +#if APIVERSNUM >= 10700 + virtual int NumProvidedSystems(void) const { return 1; } +#endif + virtual bool IsTunedToTransponder(const cChannel *Channel); + + static bool Init(void); + static bool ReInit(void); + + static cStreamdevDevice *GetDevice(void) { return m_Device; } +}; + +#endif // VDR_STREAMDEV_DEVICE_H diff --git a/plugins/streamdev/streamdev-cvs/client/filter.c b/plugins/streamdev/streamdev-cvs/client/filter.c new file mode 100644 index 0000000..c187e05 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/client/filter.c @@ -0,0 +1,292 @@ +/* + * $Id: filter.c,v 1.14 2009/02/13 13:02:39 schmirl Exp $ + */ + +#include "client/filter.h" +#include "client/socket.h" +#include "tools/select.h" +#include "common.h" + +#include <vdr/device.h> + +#define PID_MASK_HI 0x1F +// --- cStreamdevFilter ------------------------------------------------------ + +class cStreamdevFilter: public cListObject { +private: + uchar m_Buffer[4096]; + int m_Used; + int m_Pipe[2]; + u_short m_Pid; + u_char m_Tid; + u_char m_Mask; + +public: + cStreamdevFilter(u_short Pid, u_char Tid, u_char Mask); + virtual ~cStreamdevFilter(); + + bool Matches(u_short Pid, u_char Tid); + bool PutSection(const uchar *Data, int Length, bool Pusi); + int ReadPipe(void) const { return m_Pipe[0]; } + + bool IsClosed(void); + void Reset(void); + + u_short Pid(void) const { return m_Pid; } + u_char Tid(void) const { return m_Tid; } + u_char Mask(void) const { return m_Mask; } +}; + +inline bool cStreamdevFilter::Matches(u_short Pid, u_char Tid) { + return m_Pid == Pid && m_Tid == (Tid & m_Mask); +} + +cStreamdevFilter::cStreamdevFilter(u_short Pid, u_char Tid, u_char Mask) { + m_Used = 0; + m_Pid = Pid; + m_Tid = Tid; + m_Mask = Mask; + m_Pipe[0] = m_Pipe[1] = -1; + +#ifdef SOCK_SEQPACKET + // SOCK_SEQPACKET (since kernel 2.6.4) + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, m_Pipe) != 0) { + esyslog("streamdev-client: socketpair(SOCK_SEQPACKET) failed: %m, trying SOCK_DGRAM"); + } +#endif + if (m_Pipe[0] < 0 && socketpair(AF_UNIX, SOCK_DGRAM, 0, m_Pipe) != 0) { + esyslog("streamdev-client: couldn't open section filter socket: %m"); + } + + else if(fcntl(m_Pipe[0], F_SETFL, O_NONBLOCK) != 0 || + fcntl(m_Pipe[1], F_SETFL, O_NONBLOCK) != 0) { + esyslog("streamdev-client: couldn't set section filter socket to non-blocking mode: %m"); + } +} + +cStreamdevFilter::~cStreamdevFilter() { + Dprintf("~cStreamdevFilter %p\n", this); + + // ownership of handle m_Pipe[0] has been transferred to VDR section handler + //if (m_Pipe[0] >= 0) + // close(m_Pipe[0]); + if (m_Pipe[1] >= 0) + close(m_Pipe[1]); +} + +bool cStreamdevFilter::PutSection(const uchar *Data, int Length, bool Pusi) { + + if (!m_Used && !Pusi) /* wait for payload unit start indicator */ + return true; + if (m_Used && Pusi) /* reset at payload unit start */ + Reset(); + + if (m_Used + Length >= (int)sizeof(m_Buffer)) { + esyslog("ERROR: Streamdev: Section handler buffer overflow (%d bytes lost)", + Length); + Reset(); + return true; + } + + memcpy(m_Buffer + m_Used, Data, Length); + m_Used += Length; + if (m_Used > 3) { + int length = (((m_Buffer[1] & 0x0F) << 8) | m_Buffer[2]) + 3; + if (m_Used == length) { + m_Used = 0; + if (write(m_Pipe[1], m_Buffer, length) < 0) { + if(errno == EAGAIN || errno == EWOULDBLOCK) + dsyslog("cStreamdevFilter::PutSection socket overflow, " + "Pid %4d Tid %3d", m_Pid, m_Tid); + + else + return false; + } + } + + if (m_Used > length) { + dsyslog("cStreamdevFilter::PutSection: m_Used > length ! Pid %2d, Tid%2d " + "(len %3d, got %d/%d)", m_Pid, m_Tid, Length, m_Used, length); + if(Length < TS_SIZE-5) { + // TS packet not full -> this must be last TS packet of section data -> safe to reset now + Reset(); + } + } + + } + return true; +} + +void cStreamdevFilter::Reset(void) { + if(m_Used) + dsyslog("cStreamdevFilter::Reset skipping %d bytes", m_Used); + m_Used = 0; +} + +bool cStreamdevFilter::IsClosed(void) { + char m_Buffer[3] = {0,0,0}; /* tid 0, 0 bytes */ + + // Test if pipe/socket has been closed by writing empty section + if (write(m_Pipe[1], m_Buffer, 3) < 0 && + errno != EAGAIN && + errno != EWOULDBLOCK) { + + if (errno != ECONNREFUSED && + errno != ECONNRESET && + errno != EPIPE) + esyslog("cStreamdevFilter::IsClosed failed: %m"); + + return true; + } + + return false; +} + +// --- cStreamdevFilters ----------------------------------------------------- + +cStreamdevFilters::cStreamdevFilters(void): + cThread("streamdev-client: sections assembler") { + m_TSBuffer = NULL; +} + +cStreamdevFilters::~cStreamdevFilters() { + SetConnection(-1); +} + +int cStreamdevFilters::OpenFilter(u_short Pid, u_char Tid, u_char Mask) { + CarbageCollect(); + + cStreamdevFilter *f = new cStreamdevFilter(Pid, Tid, Mask); + int fh = f->ReadPipe(); + + Lock(); + Add(f); + Unlock(); + + return fh; +} + +void cStreamdevFilters::CarbageCollect(void) { + LOCK_THREAD; + for (cStreamdevFilter *fi = First(); fi;) { + if (fi->IsClosed()) { + if (errno == ECONNREFUSED || + errno == ECONNRESET || + errno == EPIPE) { + ClientSocket.SetFilter(fi->Pid(), fi->Tid(), fi->Mask(), false); + Dprintf("cStreamdevFilters::CarbageCollector: filter closed: Pid %4d, Tid %3d, Mask %2x (%d filters left)", + (int)fi->Pid(), (int)fi->Tid(), fi->Mask(), Count()-1); + + cStreamdevFilter *next = Prev(fi); + Del(fi); + fi = next ? Next(next) : First(); + } else { + esyslog("cStreamdevFilters::CarbageCollector() error: " + "Pid %4d, Tid %3d, Mask %2x (%d filters left) failed", + (int)fi->Pid(), (int)fi->Tid(), fi->Mask(), Count()-1); + LOG_ERROR; + fi = Next(fi); + } + } else { + fi = Next(fi); + } + } +} + +bool cStreamdevFilters::ReActivateFilters(void) +{ + LOCK_THREAD; + + bool res = true; + CarbageCollect(); + for (cStreamdevFilter *fi = First(); fi; fi = Next(fi)) { + res = ClientSocket.SetFilter(fi->Pid(), fi->Tid(), fi->Mask(), true) && res; + Dprintf("ReActivateFilters(%d, %d, %d) -> %s", fi->Pid(), fi->Tid(), fi->Mask(), res ? "Ok" :"FAIL"); + } + return res; +} + +void cStreamdevFilters::SetConnection(int Handle) { + + Cancel(2); + DELETENULL(m_TSBuffer); + + if (Handle >= 0) { + m_TSBuffer = new cTSBuffer(Handle, MEGABYTE(1), 1); + ReActivateFilters(); + Start(); + } +} + +void cStreamdevFilters::Action(void) { + int fails = 0; + + while (Running()) { + const uchar *block = m_TSBuffer->Get(); + if (block) { + u_short pid = (((u_short)block[1] & PID_MASK_HI) << 8) | block[2]; + u_char tid = block[3]; + bool Pusi = block[1] & 0x40; + // proprietary extension + int len = block[4]; +#if 0 + if (block[1] == 0xff && + block[2] == 0xff && + block[3] == 0xff && + block[4] == 0x7f) + isyslog("*********** TRANSPONDER -> %s **********", block+5); +#endif + LOCK_THREAD; + cStreamdevFilter *f = First(); + while (f) { + cStreamdevFilter *next = Next(f); + if (f->Matches(pid, tid)) { + + if (f->PutSection(block + 5, len, Pusi)) + break; + + if (errno != ECONNREFUSED && + errno != ECONNRESET && + errno != EPIPE) { + Dprintf("FATAL ERROR: %m\n"); + esyslog("streamdev-client: couldn't send section packet: %m"); + } + ClientSocket.SetFilter(f->Pid(), f->Tid(), f->Mask(), false); + Del(f); + // Filter was closed. + // - need to check remaining filters for another match + } + f = next; + } + } else { +#if 1 // TODO: this should be fixed in vdr cTSBuffer + // Check disconnection + int fd = *ClientSocket.DataSocket(siLiveFilter); + if(fd < 0) + break; + cPoller Poller(fd); + if (Poller.Poll()) { + char tmp[1]; + errno = 0; + Dprintf("cStreamdevFilters::Action(): checking connection"); + if (recv(fd, tmp, 1, MSG_PEEK) == 0 && errno != EAGAIN) { + ++fails; + if (fails >= 10) { + esyslog("cStreamdevFilters::Action(): stream disconnected ?"); + ClientSocket.CloseDataConnection(siLiveFilter); + break; + } + } else { + fails = 0; + } + } else { + fails = 0; + } + cCondWait::SleepMs(10); +#endif + } + } + + DELETENULL(m_TSBuffer); + dsyslog("StreamdevFilters::Action() ended"); +} diff --git a/plugins/streamdev/streamdev-cvs/client/filter.h b/plugins/streamdev/streamdev-cvs/client/filter.h new file mode 100644 index 0000000..889006a --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/client/filter.h @@ -0,0 +1,33 @@ +/* + * $Id: filter.h,v 1.5 2008/04/07 14:27:28 schmirl Exp $ + */ + +#ifndef VDR_STREAMDEV_FILTER_H +#define VDR_STREAMDEV_FILTER_H + +#include <vdr/config.h> +#include <vdr/tools.h> +#include <vdr/thread.h> + +class cTSBuffer; +class cStreamdevFilter; + +class cStreamdevFilters: public cList<cStreamdevFilter>, public cThread { +private: + cTSBuffer *m_TSBuffer; + +protected: + virtual void Action(void); + void CarbageCollect(void); + + bool ReActivateFilters(void); + +public: + cStreamdevFilters(void); + virtual ~cStreamdevFilters(); + + void SetConnection(int Handle); + int OpenFilter(u_short Pid, u_char Tid, u_char Mask); +}; + +#endif // VDR_STREAMDEV_FILTER_H diff --git a/plugins/streamdev/streamdev-cvs/client/setup.c b/plugins/streamdev/streamdev-cvs/client/setup.c new file mode 100644 index 0000000..2ac359f --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/client/setup.c @@ -0,0 +1,80 @@ +/* + * $Id: setup.c,v 1.9 2009/09/18 10:43:26 schmirl Exp $ + */ + +#include <vdr/menuitems.h> + +#include "client/setup.h" +#include "client/device.h" + +cStreamdevClientSetup StreamdevClientSetup; + +cStreamdevClientSetup::cStreamdevClientSetup(void) { + StartClient = false; + RemotePort = 2004; + StreamFilters = false; + SyncEPG = false; + HideMenuEntry = false; + MinPriority = -1; + MaxPriority = MAXPRIORITY; + strcpy(RemoteIp, ""); +} + +bool cStreamdevClientSetup::SetupParse(const char *Name, const char *Value) { + if (strcmp(Name, "StartClient") == 0) StartClient = atoi(Value); + else if (strcmp(Name, "RemoteIp") == 0) { + if (strcmp(Value, "-none-") == 0) + strcpy(RemoteIp, ""); + else + strcpy(RemoteIp, Value); + } + else if (strcmp(Name, "RemotePort") == 0) RemotePort = atoi(Value); + else if (strcmp(Name, "StreamFilters") == 0) StreamFilters = atoi(Value); + else if (strcmp(Name, "SyncEPG") == 0) SyncEPG = atoi(Value); + else if (strcmp(Name, "HideMenuEntry") == 0) HideMenuEntry = atoi(Value); + else if (strcmp(Name, "MinPriority") == 0) MinPriority = atoi(Value); + else if (strcmp(Name, "MaxPriority") == 0) MaxPriority = atoi(Value); + else return false; + return true; +} + +cStreamdevClientMenuSetupPage::cStreamdevClientMenuSetupPage(void) { + m_NewSetup = StreamdevClientSetup; + + Add(new cMenuEditBoolItem(tr("Hide Mainmenu Entry"), &m_NewSetup.HideMenuEntry)); + Add(new cMenuEditBoolItem(tr("Start Client"), &m_NewSetup.StartClient)); + Add(new cMenuEditIpItem (tr("Remote IP"), m_NewSetup.RemoteIp)); + Add(new cMenuEditIntItem (tr("Remote Port"), &m_NewSetup.RemotePort, 0, 65535)); + Add(new cMenuEditBoolItem(tr("Filter Streaming"), &m_NewSetup.StreamFilters)); + Add(new cMenuEditBoolItem(tr("Synchronize EPG"), &m_NewSetup.SyncEPG)); + Add(new cMenuEditIntItem (tr("Minimum Priority"), &m_NewSetup.MinPriority, -1, MAXPRIORITY)); + Add(new cMenuEditIntItem (tr("Maximum Priority"), &m_NewSetup.MaxPriority, -1, MAXPRIORITY)); + SetCurrent(Get(0)); +} + +cStreamdevClientMenuSetupPage::~cStreamdevClientMenuSetupPage() { +} + +void cStreamdevClientMenuSetupPage::Store(void) { + if (m_NewSetup.StartClient != StreamdevClientSetup.StartClient) { + if (m_NewSetup.StartClient) + cStreamdevDevice::Init(); + } + + SetupStore("StartClient", m_NewSetup.StartClient); + if (strcmp(m_NewSetup.RemoteIp, "") == 0) + SetupStore("RemoteIp", "-none-"); + else + SetupStore("RemoteIp", m_NewSetup.RemoteIp); + SetupStore("RemotePort", m_NewSetup.RemotePort); + SetupStore("StreamFilters", m_NewSetup.StreamFilters); + SetupStore("SyncEPG", m_NewSetup.SyncEPG); + SetupStore("HideMenuEntry", m_NewSetup.HideMenuEntry); + SetupStore("MinPriority", m_NewSetup.MinPriority); + SetupStore("MaxPriority", m_NewSetup.MaxPriority); + + StreamdevClientSetup = m_NewSetup; + + cStreamdevDevice::ReInit(); +} + diff --git a/plugins/streamdev/streamdev-cvs/client/setup.h b/plugins/streamdev/streamdev-cvs/client/setup.h new file mode 100644 index 0000000..f7cba08 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/client/setup.h @@ -0,0 +1,39 @@ +/* + * $Id: setup.h,v 1.6 2009/09/18 10:43:26 schmirl Exp $ + */ + +#ifndef VDR_STREAMDEV_SETUPCLIENT_H +#define VDR_STREAMDEV_SETUPCLIENT_H + +#include "common.h" + +struct cStreamdevClientSetup { + cStreamdevClientSetup(void); + + bool SetupParse(const char *Name, const char *Value); + + int StartClient; + char RemoteIp[20]; + int RemotePort; + int StreamFilters; + int SyncEPG; + int HideMenuEntry; + int MinPriority; + int MaxPriority; +}; + +extern cStreamdevClientSetup StreamdevClientSetup; + +class cStreamdevClientMenuSetupPage: public cMenuSetupPage { +private: + cStreamdevClientSetup m_NewSetup; + +protected: + virtual void Store(void); + +public: + cStreamdevClientMenuSetupPage(void); + virtual ~cStreamdevClientMenuSetupPage(); +}; + +#endif // VDR_STREAMDEV_SETUPCLIENT_H diff --git a/plugins/streamdev/streamdev-cvs/client/socket.c b/plugins/streamdev/streamdev-cvs/client/socket.c new file mode 100644 index 0000000..02f501d --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/client/socket.c @@ -0,0 +1,374 @@ +/* + * $Id: socket.c,v 1.12 2008/04/08 14:18:16 schmirl Exp $ + */ + +#include <tools/select.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> +#include <stdint.h> +#include <time.h> + +#define MINLOGREPEAT 10 //don't log connect failures too often (seconds) + +#include "client/socket.h" +#include "client/setup.h" +#include "common.h" + +cClientSocket ClientSocket; + +cClientSocket::cClientSocket(void) +{ + memset(m_DataSockets, 0, sizeof(cTBSocket*) * si_Count); + Reset(); +} + +cClientSocket::~cClientSocket() +{ + Reset(); + if (IsOpen()) Quit(); +} + +void cClientSocket::Reset(void) +{ + for (int it = 0; it < si_Count; ++it) { + if (m_DataSockets[it] != NULL) + DELETENULL(m_DataSockets[it]); + } +} + +cTBSocket *cClientSocket::DataSocket(eSocketId Id) const { + return m_DataSockets[Id]; +} + +bool cClientSocket::Command(const std::string &Command, uint Expected, uint TimeoutMs) +{ + errno = 0; + + std::string pkt = Command + "\015\012"; + Dprintf("OUT: |%s|\n", Command.c_str()); + + cTimeMs starttime; + if (!TimedWrite(pkt.c_str(), pkt.size(), TimeoutMs)) { + esyslog("Streamdev: Lost connection to %s:%d: %s", RemoteIp().c_str(), RemotePort(), + strerror(errno)); + Close(); + return false; + } + + uint64_t elapsed = starttime.Elapsed(); + if (Expected != 0) { // XXX+ What if elapsed > TimeoutMs? + TimeoutMs -= elapsed; + return Expect(Expected, NULL, TimeoutMs); + } + + return true; +} + +bool cClientSocket::Expect(uint Expected, std::string *Result, uint TimeoutMs) { + char *endptr; + int bufcount; + bool res; + + errno = 0; + + if ((bufcount = ReadUntil(m_Buffer, sizeof(m_Buffer) - 1, "\012", TimeoutMs)) == -1) { + esyslog("Streamdev: Lost connection to %s:%d: %s", RemoteIp().c_str(), RemotePort(), + strerror(errno)); + Close(); + return false; + } + if (m_Buffer[bufcount - 1] == '\015') + --bufcount; + m_Buffer[bufcount] = '\0'; + Dprintf("IN: |%s|\n", m_Buffer); + + if (Result != NULL) + *Result = m_Buffer; + + res = strtoul(m_Buffer, &endptr, 10) == Expected; + return res; +} + +bool cClientSocket::CheckConnection(void) { + CMD_LOCK; + + if (IsOpen()) { + cTBSelect select; + + Dprintf("connection open\n"); + + // XXX+ check if connection is still alive (is there a better way?) + // There REALLY shouldn't be anything readable according to PROTOCOL here + // If there is, assume it's an eof signal (subseq. read would return 0) + select.Add(*this, false); + int res; + if ((res = select.Select(0)) == 0) { + Dprintf("select said nothing happened\n"); + return true; + } + Dprintf("closing connection (res was %d)", res); + Close(); + } + + if (!Connect(StreamdevClientSetup.RemoteIp, StreamdevClientSetup.RemotePort)){ + static time_t lastTime = 0; + if (time(NULL) - lastTime > MINLOGREPEAT) { + esyslog("ERROR: Streamdev: Couldn't connect to %s:%d: %s", + (const char*)StreamdevClientSetup.RemoteIp, + StreamdevClientSetup.RemotePort, strerror(errno)); + lastTime = time(NULL); + } + return false; + } + + if (!Expect(220)) { + if (errno == 0) + esyslog("ERROR: Streamdev: Didn't receive greeting from %s:%d", + RemoteIp().c_str(), RemotePort()); + Close(); + return false; + } + + if (!Command("CAPS TSPIDS", 220)) { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't negotiate capabilities on %s:%d", + RemoteIp().c_str(), RemotePort()); + Close(); + return false; + } + + const char *Filters = ""; + if(Command("CAPS FILTERS", 220)) + Filters = ",FILTERS"; + + isyslog("Streamdev: Connected to server %s:%d using capabilities TSPIDS%s", + RemoteIp().c_str(), RemotePort(), Filters); + return true; +} + +bool cClientSocket::ProvidesChannel(const cChannel *Channel, int Priority) { + if (!CheckConnection()) return false; + + CMD_LOCK; + + std::string command = (std::string)"PROV " + (const char*)itoa(Priority) + " " + + (const char*)Channel->GetChannelID().ToString(); + if (!Command(command)) + return false; + + std::string buffer; + if (!Expect(220, &buffer)) { + if (buffer.substr(0, 3) != "560" && errno == 0) + esyslog("ERROR: Streamdev: Couldn't check if %s:%d provides channel %s", + RemoteIp().c_str(), RemotePort(), Channel->Name()); + return false; + } + return true; +} + +bool cClientSocket::CreateDataConnection(eSocketId Id) { + cTBSocket listen(SOCK_STREAM); + + if (!CheckConnection()) return false; + + if (m_DataSockets[Id] != NULL) + DELETENULL(m_DataSockets[Id]); + + if (!listen.Listen(LocalIp(), 0, 1)) { + esyslog("ERROR: Streamdev: Couldn't create data connection: %s", + strerror(errno)); + return false; + } + + std::string command = (std::string)"PORT " + (const char*)itoa(Id) + " " + + LocalIp().c_str() + "," + + (const char*)itoa((listen.LocalPort() >> 8) & 0xff) + "," + + (const char*)itoa(listen.LocalPort() & 0xff); + size_t idx = 4; + while ((idx = command.find('.', idx + 1)) != (size_t)-1) + command[idx] = ','; + + CMD_LOCK; + + if (!Command(command, 220)) { + Dprintf("error: %m\n"); + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't establish data connection to %s:%d", + RemoteIp().c_str(), RemotePort()); + return false; + } + + /* The server SHOULD do the following: + * - get PORT command + * - connect to socket + * - return 220 + */ + + m_DataSockets[Id] = new cTBSocket; + if (!m_DataSockets[Id]->Accept(listen)) { + esyslog("ERROR: Streamdev: Couldn't establish data connection to %s:%d%s%s", + RemoteIp().c_str(), RemotePort(), errno == 0 ? "" : ": ", + errno == 0 ? "" : strerror(errno)); + DELETENULL(m_DataSockets[Id]); + return false; + } + + return true; +} + +bool cClientSocket::CloseDataConnection(eSocketId Id) { + //if (!CheckConnection()) return false; + + CMD_LOCK; + + if(Id == siLive || Id == siLiveFilter) + if (m_DataSockets[Id] != NULL) { + std::string command = (std::string)"ABRT " + (const char*)itoa(Id); + if (!Command(command, 220)) { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't cleanly close data connection"); + //return false; + } + DELETENULL(m_DataSockets[Id]); + } + return true; +} + +bool cClientSocket::SetChannelDevice(const cChannel *Channel) { + if (!CheckConnection()) return false; + + CMD_LOCK; + + std::string command = (std::string)"TUNE " + + (const char*)Channel->GetChannelID().ToString(); + if (!Command(command, 220)) { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't tune %s:%d to channel %s", + RemoteIp().c_str(), RemotePort(), Channel->Name()); + return false; + } + return true; +} + +bool cClientSocket::SetPid(int Pid, bool On) { + if (!CheckConnection()) return false; + + CMD_LOCK; + + 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()); + return false; + } + return true; +} + +bool cClientSocket::SetFilter(ushort Pid, uchar Tid, uchar Mask, bool On) { + if (!CheckConnection()) return false; + + CMD_LOCK; + + std::string command = (std::string)(On ? "ADDF " : "DELF ") + (const char*)itoa(Pid) + + " " + (const char*)itoa(Tid) + " " + (const char*)itoa(Mask); + 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()); + return false; + } + return true; +} + +bool cClientSocket::CloseDvr(void) { + if (!CheckConnection()) return false; + + CMD_LOCK; + + if (m_DataSockets[siLive] != NULL) { + std::string command = (std::string)"ABRT " + (const char*)itoa(siLive); + if (!Command(command, 220)) { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't cleanly close data connection"); + return false; + } + + DELETENULL(m_DataSockets[siLive]); + } + return true; +} + +bool cClientSocket::SynchronizeEPG(void) { + std::string buffer; + bool result; + FILE *epgfd; + + if (!CheckConnection()) return false; + + isyslog("Streamdev: Synchronizing EPG from server\n"); + + CMD_LOCK; + + if (!Command("LSTE")) + return false; + + if ((epgfd = tmpfile()) == NULL) { + esyslog("ERROR: Streamdev: Error while processing EPG data: %s", + strerror(errno)); + return false; + } + + while ((result = Expect(215, &buffer))) { + if (buffer[3] == ' ') break; + fputs(buffer.c_str() + 4, epgfd); + fputc('\n', epgfd); + } + + if (!result) { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't fetch EPG data from %s:%d", + RemoteIp().c_str(), RemotePort()); + fclose(epgfd); + return false; + } + + rewind(epgfd); + if (cSchedules::Read(epgfd)) + cSchedules::Cleanup(true); + else { + esyslog("ERROR: Streamdev: Parsing EPG data failed"); + fclose(epgfd); + return false; + } + fclose(epgfd); + return true; +} + +bool cClientSocket::Quit(void) { + bool res; + + if (!CheckConnection()) return false; + + if (!(res = Command("QUIT", 221))) { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't quit command connection to %s:%d", + RemoteIp().c_str(), RemotePort()); + } + Close(); + return res; +} + +bool cClientSocket::SuspendServer(void) { + if (!CheckConnection()) return false; + + CMD_LOCK; + + if (!Command("SUSP", 220)) { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't suspend server"); + return false; + } + return true; +} diff --git a/plugins/streamdev/streamdev-cvs/client/socket.h b/plugins/streamdev/streamdev-cvs/client/socket.h new file mode 100644 index 0000000..a0400e6 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/client/socket.h @@ -0,0 +1,60 @@ +/* + * $Id: socket.h,v 1.6 2008/04/07 14:40:40 schmirl Exp $ + */ + +#ifndef VDR_STREAMDEV_CLIENT_CONNECTION_H +#define VDR_STREAMDEV_CLIENT_CONNECTION_H + +#include <tools/socket.h> + +#include "common.h" + +#include <string> + +#define CMD_LOCK cMutexLock CmdLock((cMutex*)&m_Mutex) + +class cPES2TSRemux; + +class cClientSocket: public cTBSocket { +private: + cTBSocket *m_DataSockets[si_Count]; + cMutex m_Mutex; + char m_Buffer[BUFSIZ + 1]; // various uses + +protected: + /* Send Command, and return true if the command results in Expected. + Returns false on failure, setting errno appropriately if it has been + a system failure. If Expected is zero, returns immediately after + sending the command. */ + bool Command(const std::string &Command, uint Expected = 0, uint TimeoutMs = 1500); + + /* Fetch results from an ongoing Command called with Expected == 0. Returns + true if the response has the code Expected, returning an internal buffer + in the array pointer pointed to by Result. Returns false on failure, + setting errno appropriately if it has been a system failure. */ + bool Expect(uint Expected, std::string *Result = NULL, uint TimeoutMs = 1500); + +public: + cClientSocket(void); + virtual ~cClientSocket(); + + void Reset(void); + + bool CheckConnection(void); + bool ProvidesChannel(const cChannel *Channel, int Priority); + bool CreateDataConnection(eSocketId Id); + bool CloseDataConnection(eSocketId Id); + bool SetChannelDevice(const cChannel *Channel); + bool SetPid(int Pid, bool On); + bool SetFilter(ushort Pid, uchar Tid, uchar Mask, bool On); + bool CloseDvr(void); + bool SynchronizeEPG(void); + bool SuspendServer(void); + bool Quit(void); + + cTBSocket *DataSocket(eSocketId Id) const; +}; + +extern class cClientSocket ClientSocket; + +#endif // VDR_STREAMDEV_CLIENT_CONNECTION_H diff --git a/plugins/streamdev/streamdev-cvs/common.c b/plugins/streamdev/streamdev-cvs/common.c new file mode 100644 index 0000000..279e909 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/common.c @@ -0,0 +1,152 @@ +/* + * $Id: common.c,v 1.11 2009/09/18 10:41:41 schmirl Exp $ + */ + +#include <vdr/channels.h> +#include <iostream> + +#include "common.h" +#include "tools/select.h" + +using namespace std; + +const char *VERSION = "0.5.0-pre"; + +const char cMenuEditIpItem::IpCharacters[] = "0123456789."; + +cMenuEditIpItem::cMenuEditIpItem(const char *Name, char *Value): + cMenuEditItem(Name) { + value = Value; + curNum = -1; + pos = -1; + step = false; + Set(); +} + +cMenuEditIpItem::~cMenuEditIpItem() { +} + +void cMenuEditIpItem::Set(void) { + char buf[1000]; + if (pos >= 0) { + in_addr_t addr = inet_addr(value); + if ((int)addr == -1) + addr = 0; + int p = 0; + for (int i = 0; i < 4; ++i) { + p += snprintf(buf + p, sizeof(buf) - p, pos == i ? "[%d]" : "%d", + pos == i ? curNum : (addr >> (i * 8)) & 0xff); + if (i < 3) + buf[p++] = '.'; + } + SetValue(buf); + } else + SetValue(value); +} + +eOSState cMenuEditIpItem::ProcessKey(eKeys Key) { + in_addr addr; + addr.s_addr = inet_addr(value); + if ((int)addr.s_addr == -1) + addr.s_addr = 0; + + switch (Key) { + case kUp: + if (pos >= 0) { + if (curNum < 255) ++curNum; + } else + return cMenuEditItem::ProcessKey(Key); + break; + + case kDown: + if (pos >= 0) { + if (curNum > 0) --curNum; + } else + return cMenuEditItem::ProcessKey(Key); + break; + + case kOk: + if (pos >= 0) { + addr.s_addr = inet_addr(value); + if ((int)addr.s_addr == -1) + addr.s_addr = 0; + addr.s_addr &= ~(0xff << (pos * 8)); + addr.s_addr |= curNum << (pos * 8); + strcpy(value, inet_ntoa(addr)); + } else + return cMenuEditItem::ProcessKey(Key); + curNum = -1; + pos = -1; + break; + + case kRight: + if (pos >= 0) { + addr.s_addr = inet_addr(value); + if ((int)addr.s_addr == -1) + addr.s_addr = 0; + addr.s_addr &= ~(0xff << (pos * 8)); + addr.s_addr |= curNum << (pos * 8); + strcpy(value, inet_ntoa(addr)); + } + + if (pos == -1 || pos == 3) + pos = 0; + else + ++pos; + + curNum = (addr.s_addr >> (pos * 8)) & 0xff; + step = true; + break; + + case kLeft: + if (pos >= 0) { + addr.s_addr = inet_addr(value); + if ((int)addr.s_addr == -1) + addr.s_addr = 0; + addr.s_addr &= ~(0xff << (pos * 8)); + addr.s_addr |= curNum << (pos * 8); + strcpy(value, inet_ntoa(addr)); + } + + if (pos <= 0) + pos = 3; + else + --pos; + + curNum = (addr.s_addr >> (pos * 8)) & 0xff; + step = true; + break; + + case k0 ... k9: + if (pos == -1) + pos = 0; + + if (curNum == -1 || step) { + curNum = Key - k0; + step = false; + } else + curNum = curNum * 10 + (Key - k0); + + if ((curNum * 10 > 255) || (curNum == 0)) { + in_addr addr; + addr.s_addr = inet_addr(value); + if ((int)addr.s_addr == -1) + addr.s_addr = 0; + addr.s_addr &= ~(0xff << (pos * 8)); + addr.s_addr |= curNum << (pos * 8); + strcpy(value, inet_ntoa(addr)); + if (++pos == 4) + pos = 0; + curNum = (addr.s_addr >> (pos * 8)) & 0xff; + step = true; + } + break; + + default: + return cMenuEditItem::ProcessKey(Key); + } + + Set(); + return osContinue; +} + diff --git a/plugins/streamdev/streamdev-cvs/common.h b/plugins/streamdev/streamdev-cvs/common.h new file mode 100644 index 0000000..ef6ef9e --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/common.h @@ -0,0 +1,80 @@ +/* + * $Id: common.h,v 1.15 2009/09/18 10:41:41 schmirl Exp $ + */ + +#ifndef VDR_STREAMDEV_COMMON_H +#define VDR_STREAMDEV_COMMON_H + +/* FreeBSD has it's own version of isnumber(), + but VDR's version is incompatible */ +#ifdef __FreeBSD__ +#undef isnumber +#endif + +#include <vdr/tools.h> +#include <vdr/plugin.h> + +#include "tools/socket.h" + +#ifdef DEBUG +# include <stdio.h> +# define Dprintf(x...) fprintf(stderr, x) +#else +# define Dprintf(x...) +#endif + +#define TRANSPONDER(c1, c2) (c1->Transponder() == c2->Transponder()) + +#define MAXPARSEBUFFER KILOBYTE(16) + +/* Check if a channel is a radio station. */ +#define ISRADIO(x) ((x)->Vpid()==0||(x)->Vpid()==1||(x)->Vpid()==0x1fff) + +class cChannel; + +enum eStreamType { + stTS, + stPES, + stPS, + stES, + stExtern, + stTSPIDS, + st_Count +}; + +enum eSuspendMode { + smOffer, + smAlways, + smNever, + sm_Count +}; + +enum eSocketId { + siLive, + siReplay, + siLiveFilter, + siDataRespond, + si_Count +}; + +extern const char *VERSION; + +class cMenuEditIpItem: public cMenuEditItem { +private: + static const char IpCharacters[]; + char *value; + int curNum; + int pos; + bool step; + +protected: + virtual void Set(void); + +public: + cMenuEditIpItem(const char *Name, char *Value); // Value must be 16 bytes + ~cMenuEditIpItem(); + + virtual eOSState ProcessKey(eKeys Key); +}; + +#endif // VDR_STREAMDEV_COMMON_H diff --git a/plugins/streamdev/streamdev-cvs/libdvbmpeg/.cvsignore b/plugins/streamdev/streamdev-cvs/libdvbmpeg/.cvsignore new file mode 100644 index 0000000..4671378 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/libdvbmpeg/.cvsignore @@ -0,0 +1 @@ +.depend diff --git a/plugins/streamdev/streamdev-cvs/libdvbmpeg/CVS/Entries b/plugins/streamdev/streamdev-cvs/libdvbmpeg/CVS/Entries new file mode 100644 index 0000000..027b45f --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/libdvbmpeg/CVS/Entries @@ -0,0 +1,11 @@ +/.cvsignore/1.1.1.1/Thu Dec 30 22:44:04 2004// +/Makefile/1.3/Mon Apr 7 14:40:40 2008// +/ctools.c/1.3/Wed Mar 12 09:36:27 2008// +/ctools.h/1.1.1.1/Thu Dec 30 22:44:10 2004// +/remux.c/1.2/Fri Sep 21 11:55:56 2007// +/remux.h/1.1.1.1/Thu Dec 30 22:44:13 2004// +/ringbuffy.c/1.1.1.1/Thu Dec 30 22:44:13 2004// +/ringbuffy.h/1.1.1.1/Thu Dec 30 22:44:13 2004// +/transform.c/1.1.1.1/Thu Dec 30 22:44:17 2004// +/transform.h/1.2/Fri Jun 19 06:32:40 2009// +D diff --git a/plugins/streamdev/streamdev-cvs/libdvbmpeg/CVS/Repository b/plugins/streamdev/streamdev-cvs/libdvbmpeg/CVS/Repository new file mode 100644 index 0000000..7926483 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/libdvbmpeg/CVS/Repository @@ -0,0 +1 @@ +streamdev/libdvbmpeg diff --git a/plugins/streamdev/streamdev-cvs/libdvbmpeg/CVS/Root b/plugins/streamdev/streamdev-cvs/libdvbmpeg/CVS/Root new file mode 100644 index 0000000..2c7f6ce --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/libdvbmpeg/CVS/Root @@ -0,0 +1 @@ +:pserver:anoncvs@vdr-developer.org:/var/cvsroot diff --git a/plugins/streamdev/streamdev-cvs/libdvbmpeg/Makefile b/plugins/streamdev/streamdev-cvs/libdvbmpeg/Makefile new file mode 100644 index 0000000..a586182 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/libdvbmpeg/Makefile @@ -0,0 +1,25 @@ +INCS = -I. +CFLAGS = -g -Wall -O2 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -fPIC +MFLAG = -M +OBJS = ctools.o ringbuffy.o remux.o transform.o +SRC = $(wildcard *.c) + +DESTDIR = /usr/local + +.PHONY: clean + +clean: + - rm -f *.o *~ *.a .depend + +libdvbmpegtools.a: $(OBJS) + ar -rcs libdvbmpegtools.a $(OBJS) + +%.o: %.c + $(CC) -c $(CFLAGS) $(INCS) $(DEFINES) $< + +.depend: + $(CXX) $(DEFINES) $(MFLAG) $(SRC) $(INCS)> .depend + + + +-include .depend diff --git a/plugins/streamdev/streamdev-cvs/libdvbmpeg/ctools.c b/plugins/streamdev/streamdev-cvs/libdvbmpeg/ctools.c new file mode 100644 index 0000000..4766ea2 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/libdvbmpeg/ctools.c @@ -0,0 +1,2403 @@ +/* + * dvb-mpegtools for the Siemens Fujitsu DVB PCI card + * + * Copyright (C) 2000, 2001 Marcus Metzler + * for convergence integrated media GmbH + * Copyright (C) 2002 Marcus Metzler + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + + * The author can be reached at mocm@metzlerbros.de, + */ + +#include "ctools.h" + +#define MAX_SEARCH 1024 * 1024 + + +/* + + PES + +*/ + +ssize_t save_read(int fd, void *buf, size_t count) +{ + ssize_t neof = 1; + size_t re = 0; + + while(neof >= 0 && re < count){ + neof = read(fd, buf+re, count - re); + if (neof > 0) re += neof; + else break; + } + + if (neof < 0 && re == 0) return neof; + else return re; +} + +void init_pes(pes_packet *p){ + p->stream_id = 0; + p->llength[0] = 0; + p->llength[1] = 0; + p->length = 0; + p->flags1 = 0x80; + p->flags2 = 0; + p->pes_hlength = 0; + p->trick = 0; + p->add_cpy = 0; + p->priv_flags = 0; + p->pack_field_length = 0; + p->pack_header = (uint8_t *) NULL; + p->pck_sqnc_cntr = 0; + p->org_stuff_length = 0; + p->pes_ext_lngth = 0; + p->pes_ext = (uint8_t *) NULL; + p->pes_pckt_data = (uint8_t *) NULL; + p->padding = 0; + p->mpeg = 2; // DEFAULT MPEG2 + p->mpeg1_pad = 0; + p->mpeg1_headr = NULL; + p->stuffing = 0; +} + +void kill_pes(pes_packet *p){ + if (p->pack_header) + free(p->pack_header); + if (p->pes_ext) + free(p->pes_ext); + if (p->pes_pckt_data) + free(p->pes_pckt_data); + if (p->mpeg1_headr) + free(p->mpeg1_headr); + init_pes(p); +} + +void setlength_pes(pes_packet *p){ + short *ll; + ll = (short *) p->llength; + p->length = ntohs(*ll); +} + +static void setl_pes(pes_packet *p){ + setlength_pes(p); + if (p->length) + p->pes_pckt_data = (uint8_t *)malloc(p->length); +} + +void nlength_pes(pes_packet *p){ + if (p->length <= 0xFFFF){ + short *ll = (short *) p->llength; + short l = p->length; + *ll = htons(l); + } else { + p->llength[0] =0x00; + p->llength[1] =0x00; + } +} + +static void nl_pes(pes_packet *p) +{ + nlength_pes(p); + p->pes_pckt_data = (uint8_t *) malloc(p->length); +} + +void pts2pts(uint8_t *av_pts, uint8_t *pts) +{ + + av_pts[0] = ((pts[0] & 0x06) << 5) | + ((pts[1] & 0xFC) >> 2); + av_pts[1] = ((pts[1] & 0x03) << 6) | + ((pts[2] & 0xFC) >> 2); + av_pts[2] = ((pts[2] & 0x02) << 6) | + ((pts[3] & 0xFE) >> 1); + av_pts[3] = ((pts[3] & 0x01) << 7) | + ((pts[4] & 0xFE) >> 1); + +} + + +int cwrite_pes(uint8_t *buf, pes_packet *p, long length){ + int count,i; + uint8_t dummy; + int more = 0; + uint8_t headr[3] = { 0x00, 0x00 , 0x01}; + + if (length < p->length+p->pes_hlength){ + fprintf(stderr,"Wrong buffer size in cwrite_pes\n"); + exit(1); + } + + + memcpy(buf,headr,3); + count = 3; + buf[count] = p->stream_id; + count++; + + switch ( p->stream_id ) { + + case PROG_STREAM_MAP: + case PRIVATE_STREAM2: + case PROG_STREAM_DIR: + case ECM_STREAM : + case EMM_STREAM : + case PADDING_STREAM : + buf[count] = p->llength[0]; + count++; + buf[count] = p->llength[1]; + count++; + memcpy(buf+count,p->pes_pckt_data,p->length); + count += p->length; + break; + case DSM_CC_STREAM : + case ISO13522_STREAM: + case PRIVATE_STREAM1: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + buf[count] = p->llength[0]; + count++; + buf[count] = p->llength[1]; + count++; + more = 1; + break; + } + + + if ( more ) { + if ( p->mpeg == 2 ){ + memcpy(buf+count,&p->flags1,1); + count++; + memcpy(buf+count,&p->flags2,1); + count++; + memcpy(buf+count,&p->pes_hlength,1); + count++; + + if ((p->flags2 & PTS_DTS_FLAGS) == PTS_ONLY){ + memcpy(buf+count,p->pts,5); + count += 5; + } else + if ((p->flags2 & PTS_DTS_FLAGS) == PTS_DTS){ + memcpy(buf+count,p->pts,5); + count += 5; + memcpy(buf+count,p->dts,5); + count += 5; + } + if (p->flags2 & ESCR_FLAG){ + memcpy(buf+count,p->escr,6); + count += 6; + } + if (p->flags2 & ES_RATE_FLAG){ + memcpy(buf+count,p->es_rate,3); + count += 3; + } + if (p->flags2 & DSM_TRICK_FLAG){ + memcpy(buf+count,&p->trick,1); + count++; + } + if (p->flags2 & ADD_CPY_FLAG){ + memcpy(buf+count,&p->add_cpy,1); + count++; + } + if (p->flags2 & PES_CRC_FLAG){ + memcpy(buf+count,p->prev_pes_crc,2); + count += 2; + } + if (p->flags2 & PES_EXT_FLAG){ + memcpy(buf+count,&p->priv_flags,1); + count++; + + if (p->priv_flags & PRIVATE_DATA){ + memcpy(buf+count,p->pes_priv_data,16); + count += 16; + } + if (p->priv_flags & HEADER_FIELD){ + memcpy(buf+count,&p->pack_field_length, + 1); + count++; + memcpy(buf+count,p->pack_header, + p->pack_field_length); + count += p->pack_field_length; + + } + + if ( p->priv_flags & PACK_SEQ_CTR){ + memcpy(buf+count,&p->pck_sqnc_cntr,1); + count++; + memcpy(buf+count,&p->org_stuff_length, + 1); + count++; + } + + if ( p->priv_flags & P_STD_BUFFER){ + memcpy(buf+count,p->p_std,2); + count += 2; + } + if ( p->priv_flags & PES_EXT_FLAG2){ + memcpy(buf+count,&p->pes_ext_lngth,1); + count++; + memcpy(buf+count,p->pes_ext, + p->pes_ext_lngth); + count += p->pes_ext_lngth; + } + } + dummy = 0xFF; + for (i=0;i<p->stuffing;i++) { + memcpy(buf+count,&dummy,1); + count++; + } + } else { + if (p->mpeg1_pad){ + memcpy(buf+count,p->mpeg1_headr,p->mpeg1_pad); + count += p->mpeg1_pad; + } + if ((p->flags2 & PTS_DTS_FLAGS) == PTS_ONLY){ + memcpy(buf+count,p->pts,5); + count += 5; + } + else if ((p->flags2 & PTS_DTS_FLAGS) == + PTS_DTS){ + memcpy(buf+count,p->pts,5); + count += 5; + memcpy(buf+count,p->dts,5); + count += 5; + } + } + memcpy(buf+count,p->pes_pckt_data,p->length); + count += p->length; + } + + return count; + +} + +void write_pes(int fd, pes_packet *p){ + long length; + uint8_t *buf; + int l = p->length+p->pes_hlength; + + buf = (uint8_t *) malloc(l); + length = cwrite_pes(buf,p,l); + write(fd,buf,length); + free(buf); +} + +static unsigned int find_length(int f){ + uint64_t p = 0; + uint64_t start = 0; + uint64_t q = 0; + int found = 0; + uint8_t sync4[4]; + int neof = 1; + + start = lseek(f,0,SEEK_CUR); + start -=2; + lseek(f,start,SEEK_SET); + while ( neof > 0 && !found ){ + p = lseek(f,0,SEEK_CUR); + neof = save_read(f,&sync4,4); + if (sync4[0] == 0x00 && sync4[1] == 0x00 && sync4[2] == 0x01) { + switch ( sync4[3] ) { + + case PROG_STREAM_MAP: + case PRIVATE_STREAM2: + case PROG_STREAM_DIR: + case ECM_STREAM : + case EMM_STREAM : + case PADDING_STREAM : + case DSM_CC_STREAM : + case ISO13522_STREAM: + case PRIVATE_STREAM1: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + found = 1; + break; + default: + q = lseek(f,0,SEEK_CUR); + break; + } + } + } + q = lseek(f,0,SEEK_CUR); + lseek(f,start+2,SEEK_SET); + if (found) return (unsigned int)(q-start)-4-2; + else return (unsigned int)(q-start-2); + +} + + +void cread_pes(char *buf, pes_packet *p){ + + uint8_t count, dummy, check; + int i; + uint64_t po = 0; + int c=0; + + switch ( p->stream_id ) { + + case PROG_STREAM_MAP: + case PRIVATE_STREAM2: + case PROG_STREAM_DIR: + case ECM_STREAM : + case EMM_STREAM : + memcpy(p->pes_pckt_data,buf+c,p->length); + return; + break; + case PADDING_STREAM : + p->padding = p->length; + memcpy(p->pes_pckt_data,buf+c,p->length); + return; + break; + case DSM_CC_STREAM : + case ISO13522_STREAM: + case PRIVATE_STREAM1: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + break; + default: + return; + break; + } + + po = c; + memcpy(&p->flags1,buf+c,1); + c++; + if ( (p->flags1 & 0xC0) == 0x80 ) p->mpeg = 2; + else p->mpeg = 1; + + if ( p->mpeg == 2 ){ + memcpy(&p->flags2,buf+c,1); + c++; + memcpy(&p->pes_hlength,buf+c,1); + c++; + + p->length -=p->pes_hlength+3; + count = p->pes_hlength; + + if ((p->flags2 & PTS_DTS_FLAGS) == PTS_ONLY){ + memcpy(p->pts,buf+c,5); + c += 5; + count -=5; + } else + if ((p->flags2 & PTS_DTS_FLAGS) == PTS_DTS){ + memcpy(p->pts,buf+c,5); + c += 5; + memcpy(p->dts,buf+c,5); + c += 5; + count -= 10; + } + + if (p->flags2 & ESCR_FLAG){ + memcpy(p->escr,buf+c,6); + c += 6; + count -= 6; + } + + if (p->flags2 & ES_RATE_FLAG){ + memcpy(p->es_rate,buf+c,3); + c += 3; + count -= 3; + } + + if (p->flags2 & DSM_TRICK_FLAG){ + memcpy(&p->trick,buf+c,1); + c += 1; + count -= 1; + } + + if (p->flags2 & ADD_CPY_FLAG){ + memcpy(&p->add_cpy,buf+c,1); + c++; + count -= 1; + } + + if (p->flags2 & PES_CRC_FLAG){ + memcpy(p->prev_pes_crc,buf+c,2); + c += 2; + count -= 2; + } + + if (p->flags2 & PES_EXT_FLAG){ + memcpy(&p->priv_flags,buf+c,1); + c++; + count -= 1; + + if (p->priv_flags & PRIVATE_DATA){ + memcpy(p->pes_priv_data,buf+c,16); + c += 16; + count -= 16; + } + + if (p->priv_flags & HEADER_FIELD){ + memcpy(&p->pack_field_length,buf+c,1); + c++; + p->pack_header = (uint8_t *) + malloc(p->pack_field_length); + memcpy(p->pack_header,buf+c, + p->pack_field_length); + c += p->pack_field_length; + count -= 1+p->pack_field_length; + } + + if ( p->priv_flags & PACK_SEQ_CTR){ + memcpy(&p->pck_sqnc_cntr,buf+c,1); + c++; + memcpy(&p->org_stuff_length,buf+c,1); + c++; + count -= 2; + } + + if ( p->priv_flags & P_STD_BUFFER){ + memcpy(p->p_std,buf+c,2); + c += 2; + count -= 2; + } + + if ( p->priv_flags & PES_EXT_FLAG2){ + memcpy(&p->pes_ext_lngth,buf+c,1); + c++; + p->pes_ext = (uint8_t *) + malloc(p->pes_ext_lngth); + memcpy(p->pes_ext,buf+c, + p->pes_ext_lngth); + c += p->pes_ext_lngth; + count -= 1+p->pes_ext_lngth; + } + } + p->stuffing = count; + for(i = 0; i< count ;i++){ + memcpy(&dummy,buf+c,1); + c++; + } + } else { + p->mpeg1_pad = 1; + check = p->flags1; + while (check == 0xFF){ + memcpy(&check,buf+c,1); + c++; + p->mpeg1_pad++; + } + + if ( (check & 0xC0) == 0x40){ + memcpy(&check,buf+c,1); + c++; + p->mpeg1_pad++; + memcpy(&check,buf+c,1); + c++; + p->mpeg1_pad++; + } + p->flags2 = 0; + p->length -= p->mpeg1_pad; + + c = po; + if ( (check & 0x30)){ + p->length ++; + p->mpeg1_pad --; + + if (check == p->flags1){ + p->pes_hlength = 0; + } else { + p->mpeg1_headr = (uint8_t *) + malloc(p->mpeg1_pad); + p->pes_hlength = p->mpeg1_pad; + memcpy(p->mpeg1_headr,buf+c, + p->mpeg1_pad); + c += p->mpeg1_pad; + } + + p->flags2 = (check & 0xF0) << 2; + if ((p->flags2 & PTS_DTS_FLAGS) == PTS_ONLY){ + memcpy(p->pts,buf+c,5); + c += 5; + p->length -= 5; + p->pes_hlength += 5; + } + else if ((p->flags2 & PTS_DTS_FLAGS) == + PTS_DTS){ + memcpy(p->pts,buf+c,5); + c += 5; + memcpy(p->dts,buf+c,5); + c += 5; + p->length -= 10; + p->pes_hlength += 10; + } + } else { + p->mpeg1_headr = (uint8_t *) malloc(p->mpeg1_pad); + p->pes_hlength = p->mpeg1_pad; + memcpy(p->mpeg1_headr,buf+c, + p->mpeg1_pad); + c += p->mpeg1_pad; + } + } + memcpy(p->pes_pckt_data,buf+c,p->length); +} + + +int read_pes(int f, pes_packet *p){ + + uint8_t sync4[4]; + int found=0; + uint64_t po = 0; + int neof = 1; + uint8_t *buf; + + while (neof > 0 && !found) { + po = lseek(f,0,SEEK_CUR); + if (po < 0) return -1; + if ((neof = save_read(f,&sync4,4)) < 4) return -1; + if (sync4[0] == 0x00 && sync4[1] == 0x00 && sync4[2] == 0x01) { + p->stream_id = sync4[3]; + switch ( sync4[3] ) { + + case PROG_STREAM_MAP: + case PRIVATE_STREAM2: + case PROG_STREAM_DIR: + case ECM_STREAM : + case EMM_STREAM : + case PADDING_STREAM : + case DSM_CC_STREAM : + case ISO13522_STREAM: + case PRIVATE_STREAM1: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + if((neof = save_read(f,p->llength,2)) < 2) + return -1; + setl_pes(p); + if (!p->length){ + p->length = find_length(f); + nl_pes(p); + } + found = 1; + break; + + default: + if (lseek(f,po+1,SEEK_SET) < po+1) return -1; + break; + } + } else if(lseek(f,po+1,SEEK_SET) < po+1) return -1; + } + + if (!found || !p->length) return 0; + + if (p->length >0){ + buf = (uint8_t *) malloc(p->length); + if((neof = save_read(f,buf,p->length))< p->length) return -1; + cread_pes((char *)buf,p); + free(buf); + } else return 0; + + return neof; +} + +/* + + Transport Stream + +*/ + +void init_ts(ts_packet *p){ + p->pid[0] = 0; + p->pid[1] = 0; + p->flags = 0; + p->count = 0; + p->adapt_length = 0; + p->adapt_flags = 0; + p->splice_count = 0; + p->priv_dat_len = 0; + p->priv_dat = NULL; + p->adapt_ext_len = 0; + p->adapt_eflags = 0; + p->rest = 0; + p->stuffing = 0; +} + +void kill_ts(ts_packet *p){ + if (p->priv_dat) + free(p->priv_dat); + init_ts(p); +} + + + +unsigned short pid_ts(ts_packet *p) +{ + return get_pid(p->pid); +} + +int cwrite_ts(uint8_t *buf, ts_packet *p, long length){ + long count,i; + uint8_t sync,dummy; + + sync = 0x47; + memcpy(buf,&sync,1); + count = 1; + memcpy(buf+count,p->pid,2); + count += 2; + memcpy(buf+count,&p->flags,1); + count++; + + + if (! (p->flags & ADAPT_FIELD) && (p->flags & PAYLOAD)){ + memcpy(buf+count,p->data,184); + count += 184; + } else { + memcpy(buf+count,&p->adapt_length,1); + count++; + memcpy(buf+count,&p->adapt_flags,1); + count++; + + if ( p->adapt_flags & PCR_FLAG ){ + memcpy(buf+count, p->pcr,6); + count += 6; + } + if ( p->adapt_flags & OPCR_FLAG ){ + memcpy(buf+count, p->opcr,6); + count += 6; + } + if ( p->adapt_flags & SPLICE_FLAG ){ + memcpy(buf+count, &p->splice_count,1); + count++; + } + if( p->adapt_flags & TRANS_PRIV){ + memcpy(buf+count,&p->priv_dat_len,1); + count++; + memcpy(buf+count,p->priv_dat,p->priv_dat_len); + count += p->priv_dat_len; + } + + if( p->adapt_flags & ADAP_EXT_FLAG){ + memcpy(buf+count,&p->adapt_ext_len,1); + count++; + memcpy(buf+count,&p->adapt_eflags,1); + count++; + + if( p->adapt_eflags & LTW_FLAG){ + memcpy(buf+count,p->ltw,2); + count += 2; + } + if( p->adapt_eflags & PIECE_RATE){ + memcpy(buf+count,p->piece_rate,3); + count += 3; + } + if( p->adapt_eflags & SEAM_SPLICE){ + memcpy(buf+count,p->dts,5); + count += 5; + } + } + dummy = 0xFF; + for(i=0; i < p->stuffing ; i++){ + memcpy(buf+count,&dummy,1); + count++; + } + if (p->flags & PAYLOAD){ + memcpy(buf+count,p->data,p->rest); + count += p->rest; + } + } + + + return count; +} + +void write_ts(int fd, ts_packet *p){ + long length; + uint8_t buf[TS_SIZE]; + + length = cwrite_ts(buf,p,TS_SIZE); + write(fd,buf,length); +} + +int read_ts (int f, ts_packet *p){ + uint8_t sync; + int found=0; + uint64_t po,q; + int neof = 1; + + sync=0; + while (neof > 0 && !found) { + neof = save_read(f,&sync,1); + if (sync == 0x47) + found = 1; + } + neof = save_read(f,p->pid,2); + neof = save_read(f,&p->flags,1); + p->count = p->flags & COUNT_MASK; + + if (!(p->flags & ADAPT_FIELD) && (p->flags & PAYLOAD)){ + //no adapt. field only payload + neof = save_read(f,p->data,184); + p->rest = 184; + return neof; + } + + if ( p->flags & ADAPT_FIELD ) { + // adaption field + neof = save_read(f,&p->adapt_length,1); + po = lseek(f,0,SEEK_CUR); + neof = save_read(f,&p->adapt_flags,1); + + if ( p->adapt_flags & PCR_FLAG ) + neof = save_read(f, p->pcr,6); + + if ( p->adapt_flags & OPCR_FLAG ) + neof = save_read(f, p->opcr,6); + + if ( p->adapt_flags & SPLICE_FLAG ) + neof = save_read(f, &p->splice_count,1); + + if( p->adapt_flags & TRANS_PRIV){ + neof = save_read(f,&p->priv_dat_len,1); + p->priv_dat = (uint8_t *) malloc(p->priv_dat_len); + neof = save_read(f,p->priv_dat,p->priv_dat_len); + } + + if( p->adapt_flags & ADAP_EXT_FLAG){ + neof = save_read(f,&p->adapt_ext_len,1); + neof = save_read(f,&p->adapt_eflags,1); + if( p->adapt_eflags & LTW_FLAG) + neof = save_read(f,p->ltw,2); + + if( p->adapt_eflags & PIECE_RATE) + neof = save_read(f,p->piece_rate,3); + + if( p->adapt_eflags & SEAM_SPLICE) + neof = save_read(f,p->dts,5); + } + q = lseek(f,0,SEEK_CUR); + p->stuffing = p->adapt_length -(q-po); + p->rest = 183-p->adapt_length; + lseek(f,q+p->stuffing,SEEK_SET); + if (p->flags & PAYLOAD) // payload + neof = save_read(f,p->data,p->rest); + else + lseek(f,q+p->rest,SEEK_SET); + } + return neof; +} + +void cread_ts (char *buf, ts_packet *p, long length){ + uint8_t sync; + int found=0; + uint64_t po,q; + long count=0; + + sync=0; + while (count < length && !found) { + sync=buf[count]; + count++; + if (sync == 0x47) + found = 1; + } + memcpy(p->pid,buf+count,2); + count += 2; + p->flags = buf[count]; + count++; + p->count = p->flags & COUNT_MASK; + + if (!(p->flags & ADAPT_FIELD) && (p->flags & PAYLOAD)){ + //no adapt. field only payload + memcpy(p->data,buf+count,184); + p->rest = 184; + return; + } + + if ( p->flags & ADAPT_FIELD ) { + // adaption field + p->adapt_length = buf[count]; + count++; + po = count; + memcpy(&p->adapt_flags,buf+count,1); + count++; + + if ( p->adapt_flags & PCR_FLAG ){ + memcpy( p->pcr,buf+count,6); + count += 6; + } + if ( p->adapt_flags & OPCR_FLAG ){ + memcpy( p->opcr,buf+count,6); + count += 6; + } + if ( p->adapt_flags & SPLICE_FLAG ){ + memcpy( &p->splice_count,buf+count,1); + count++; + } + if( p->adapt_flags & TRANS_PRIV){ + memcpy(&p->priv_dat_len,buf+count,1); + count++; + p->priv_dat = (uint8_t *) malloc(p->priv_dat_len); + memcpy(p->priv_dat,buf+count,p->priv_dat_len); + count += p->priv_dat_len; + } + + if( p->adapt_flags & ADAP_EXT_FLAG){ + memcpy(&p->adapt_ext_len,buf+count,1); + count++; + memcpy(&p->adapt_eflags,buf+count,1); + count++; + if( p->adapt_eflags & LTW_FLAG){ + memcpy(p->ltw,buf+count,2); + count += 2; + } + if( p->adapt_eflags & PIECE_RATE){ + memcpy(p->piece_rate,buf+count,3); + count += 3; + } + if( p->adapt_eflags & SEAM_SPLICE){ + memcpy(p->dts,buf+count,5); + count += 5; + } + } + q = count; + p->stuffing = p->adapt_length -(q-po); + p->rest = 183-p->adapt_length; + count = q+p->stuffing; + if (p->flags & PAYLOAD){ // payload + memcpy(p->data,buf+count,p->rest); + count += p->rest; + } else + count = q+p->rest; + } +} + + +/* + + Program Stream + +*/ + + +void init_ps(ps_packet *p) +{ + p->stuff_length=0xF8; + p->data = NULL; + p->sheader_length = 0; + p->audio_bound = 0; + p->video_bound = 0; + p->npes = 0; + p->mpeg = 2; +} + +void kill_ps(ps_packet *p) +{ + if (p->data) + free(p->data); + init_ps(p); +} + +void setlength_ps(ps_packet *p) +{ + short *ll; + ll = (short *) p->sheader_llength; + if (p->mpeg == 2) + p->sheader_length = ntohs(*ll) - 6; + else + p->sheader_length = ntohs(*ll); +} + +static void setl_ps(ps_packet *p) +{ + setlength_ps(p); + p->data = (uint8_t *) malloc(p->sheader_length); +} + +int mux_ps(ps_packet *p) +{ + uint32_t mux = 0; + uint8_t *i = (uint8_t *)&mux; + + i[1] = p->mux_rate[0]; + i[2] = p->mux_rate[1]; + i[3] = p->mux_rate[2]; + mux = ntohl(mux); + mux = (mux >>2); + return mux; +} + +int rate_ps(ps_packet *p) +{ + uint32_t rate=0; + uint8_t *i= (uint8_t *) &rate; + + i[1] = p->rate_bound[0] & 0x7F; + i[2] = p->rate_bound[1]; + i[3] = p->rate_bound[2]; + + rate = ntohl(rate); + rate = (rate >> 1); + return rate; +} + + +uint32_t scr_base_ps(ps_packet *p) // only 32 bit!! +{ + uint32_t base = 0; + uint8_t *buf = (uint8_t *)&base; + + buf[0] |= (long int)((p->scr[0] & 0x18) << 3); + buf[0] |= (long int)((p->scr[0] & 0x03) << 4); + buf[0] |= (long int)((p->scr[1] & 0xF0) >> 4); + + buf[1] |= (long int)((p->scr[1] & 0x0F) << 4); + buf[1] |= (long int)((p->scr[2] & 0xF0) >> 4); + + buf[2] |= (long int)((p->scr[2] & 0x08) << 4); + buf[2] |= (long int)((p->scr[2] & 0x03) << 5); + buf[2] |= (long int)((p->scr[3] & 0xF8) >> 3); + + buf[3] |= (long int)((p->scr[3] & 0x07) << 5); + buf[3] |= (long int)((p->scr[4] & 0xF8) >> 3); + + base = ntohl(base); + return base; +} + +uint16_t scr_ext_ps(ps_packet *p) +{ + short ext = 0; + + ext = (short)(p->scr[5] >> 1); + ext += (short) (p->scr[4] & 0x03) * 128; + + return ext; +} + +int cwrite_ps(uint8_t *buf, ps_packet *p, long length) +{ + long count,i; + uint8_t headr1[4] = {0x00, 0x00, 0x01, 0xBA }; + uint8_t headr2[4] = {0x00, 0x00, 0x01, 0xBB }; + uint8_t buffy = 0xFF; + + + memcpy(buf,headr1,4); + count = 4; + if (p->mpeg == 2){ + memcpy(buf+count,p->scr,6); + count += 6; + memcpy(buf+count,p->mux_rate,3); + count += 3; + memcpy(buf+count,&p->stuff_length,1); + count++; + for(i=0; i< (p->stuff_length & 3); i++){ + memcpy(buf+count,&buffy,1); + count++; + } + } else { + memcpy(buf+count,p->scr,5); + count += 5; + memcpy(buf+count,p->mux_rate,3); + count += 3; + } + if (p->sheader_length){ + memcpy(buf+count,headr2,4); + count += 4; + memcpy(buf+count,p->sheader_llength,2); + count += 2; + if ( p->mpeg == 2){ + memcpy(buf+count,p->rate_bound,3); + count += 3; + memcpy(buf+count,&p->audio_bound,1); + count++; + memcpy(buf+count,&p->video_bound,1); + count++; + memcpy(buf+count,&p->reserved,1); + count++; + } + memcpy(buf+count,p->data,p->sheader_length); + count += p->sheader_length; + } + + return count; +} + +void write_ps(int fd, ps_packet *p){ + long length; + uint8_t buf[PS_MAX]; + + length = cwrite_ps(buf,p,PS_MAX); + write(fd,buf,length); +} + +int read_ps (int f, ps_packet *p){ + uint8_t headr[4]; + pes_packet pes; + int i,done; + int found=0; + uint64_t po = 0; + uint64_t q = 0; + long count = 0; + int neof = 1; + + po = lseek(f,0,SEEK_CUR); + while (neof > 0 && !found && count < MAX_SEARCH) { + neof = save_read(f,&headr,4); + if (headr[0] == 0x00 && headr[1] == 0x00 && headr[2] == 0x01){ + if ( headr[3] == 0xBA ) + found = 1; + else + if ( headr[3] == 0xB9 ) break; + else lseek(f,po+1,SEEK_SET); + } + count++; + } + + if (found){ + neof = save_read(f,p->scr,6); + if (p->scr[0] & 0x40) + p->mpeg = 2; + else + p->mpeg = 1; + + if (p->mpeg == 2){ + neof = save_read(f,p->mux_rate,3); + neof = save_read(f,&p->stuff_length,1); + po = lseek(f,0,SEEK_CUR); + lseek(f,po+(p->stuff_length & 3),SEEK_SET); + } else { + p->mux_rate[0] = p->scr[5]; //mpeg1 scr is only 5 bytes + neof = save_read(f,p->mux_rate+1,2); + } + + po = lseek(f,0,SEEK_CUR); + neof = save_read(f,headr,4); + if (headr[0] == 0x00 && headr[1] == 0x00 && + headr[2] == 0x01 && headr[3] == 0xBB ) { + neof = save_read(f,p->sheader_llength,2); + setl_ps(p); + if (p->mpeg == 2){ + neof = save_read(f,p->rate_bound,3); + neof = save_read(f,&p->audio_bound,1); + neof = save_read(f,&p->video_bound,1); + neof = save_read(f,&p->reserved,1); + } + neof = save_read(f,p->data,p->sheader_length); + } else { + lseek(f,po,SEEK_SET); + p->sheader_length = 0; + } + + i = 0; + done = 0; + q = lseek(f,0,SEEK_CUR); + do { + po = lseek(f,0,SEEK_CUR); + neof = save_read(f,headr,4); + lseek(f,po,SEEK_SET); + if ( headr[0] == 0x00 && headr[1] == 0x00 + && headr[2] == 0x01 && headr[3] != 0xBA){ + init_pes(&pes); + neof = read_pes(f,&pes); + i++; + } else done = 1; + kill_pes(&pes); + } while ( neof > 0 && !done); + p->npes = i; + lseek(f,q,SEEK_SET); + } + return neof; +} + +void cread_ps (char *buf, ps_packet *p, long length){ + uint8_t *headr; + pes_packet pes; + int i,done; + int found=0; + uint64_t po = 0; + uint64_t q = 0; + long count = 0; + long c = 0; + + po = c; + while ( count < length && !found && count < MAX_SEARCH) { + headr = (uint8_t *)buf+c; + c += 4; + if (headr[0] == 0x00 && headr[1] == 0x00 && headr[2] == 0x01){ + if ( headr[3] == 0xBA ) + found = 1; + else + if ( headr[3] == 0xB9 ) break; + else c = po+1; + } + count++; + } + + if (found){ + memcpy(p->scr,buf+c,6); + c += 6; + if (p->scr[0] & 0x40) + p->mpeg = 2; + else + p->mpeg = 1; + + if (p->mpeg == 2){ + memcpy(p->mux_rate,buf+c,3); + c += 3; + memcpy(&p->stuff_length,buf+c,1); + c++; + po = c; + c = po+(p->stuff_length & 3); + } else { + p->mux_rate[0] = p->scr[5]; //mpeg1 scr is only 5 bytes + memcpy(p->mux_rate+1,buf+c,2); + c += 2; + } + + po = c; + headr = (uint8_t *)buf+c; + c += 4; + if (headr[0] == 0x00 && headr[1] == 0x00 && + headr[2] == 0x01 && headr[3] == 0xBB ) { + memcpy(p->sheader_llength,buf+c,2); + c += 2; + setl_ps(p); + if (p->mpeg == 2){ + memcpy(p->rate_bound,buf+c,3); + c += 3; + memcpy(&p->audio_bound,buf+c,1); + c++; + memcpy(&p->video_bound,buf+c,1); + c++; + memcpy(&p->reserved,buf+c,1); + c++; + } + memcpy(p->data,buf+c,p->sheader_length); + c += p->sheader_length; + } else { + c = po; + p->sheader_length = 0; + } + + i = 0; + done = 0; + q = c; + do { + headr = (uint8_t *)buf+c; + if ( headr[0] == 0x00 && headr[1] == 0x00 + && headr[2] == 0x01 && headr[3] != 0xBA){ + init_pes(&pes); + // cread_pes(buf+c,&pes); + i++; + } else done = 1; + kill_pes(&pes); + } while (c < length && !done); + p->npes = i; + c = q; + } +} + + + + + + + +/* + conversion +*/ + +void init_trans(trans *p) +{ + int i; + + p->found = 0; + p->pes = 0; + p->is_full = 0; + p->pes_start = 0; + p->pes_started = 0; + p->set = 0; + + for (i = 0; i < MASKL*MAXFILT ; i++){ + p->mask[i] = 0; + p->filt[i] = 0; + } + for (i = 0; i < MAXFILT ; i++){ + p->sec[i].found = 0; + p->sec[i].length = 0; + } +} + +int set_trans_filt(trans *p, int filtn, uint16_t pid, uint8_t *mask, uint8_t *filt, int pes) +{ + int i; + int off; + + if ( filtn > MAXFILT-1 || filtn<0 ) return -1; + p->pid[filtn] = pid; + if (pes) p->pes |= (tflags)(1 << filtn); + else { + off = MASKL*filtn; + p->pes &= ~((tflags) (1 << filtn) ); + for (i = 0; i < MASKL ; i++){ + p->mask[off+i] = mask[i]; + p->filt[off+i] = filt[i]; + } + } + p->set |= (tflags) (1 << filtn); + return 0; +} + +void clear_trans_filt(trans *p,int filtn) +{ + int i; + + p->set &= ~((tflags) (1 << filtn) ); + p->pes &= ~((tflags) (1 << filtn) ); + p->is_full &= ~((tflags) (1 << filtn) ); + p->pes_start &= ~((tflags) (1 << filtn) ); + p->pes_started &= ~((tflags) (1 << filtn) ); + + for (i = MASKL*filtn; i < MASKL*(filtn+1) ; i++){ + p->mask[i] = 0; + p->filt[i] = 0; + } + p->sec[filtn].found = 0; + p->sec[filtn].length = 0; +} + +int filt_is_set(trans *p, int filtn) +{ + if (p->set & ((tflags)(1 << filtn))) return 1; + return 0; +} + +int pes_is_set(trans *p, int filtn) +{ + if (p->pes & ((tflags)(1 << filtn))) return 1; + return 0; +} + +int pes_is_started(trans *p, int filtn) +{ + if (p->pes_started & ((tflags)(1 << filtn))) return 1; + return 0; +} + +int pes_is_start(trans *p, int filtn) +{ + if (p->pes_start & ((tflags)(1 << filtn))) return 1; + return 0; +} + +int filt_is_ready(trans *p,int filtn) +{ + if (p->is_full & ((tflags)(1 << filtn))) return 1; + return 0; +} + +void trans_filt(uint8_t *buf, int count, trans *p) +{ + int c=0; + //fprintf(stderr,"trans_filt\n"); + + + while (c < count && p->found <1 ){ + if ( buf[c] == 0x47) p->found = 1; + c++; + p->packet[0] = 0x47; + } + if (c == count) return; + + while( c < count && p->found < 188 && p->found > 0 ){ + p->packet[p->found] = buf[c]; + c++; + p->found++; + } + if (p->found == 188){ + p->found = 0; + tfilter(p); + } + + if (c < count) trans_filt(buf+c,count-c,p); +} + + +void tfilter(trans *p) +{ + int l,c; + int tpid; + uint8_t flag,flags; + uint8_t adapt_length = 0; + uint8_t cpid[2]; + + + // fprintf(stderr,"tfilter\n"); + + cpid[0] = p->packet[1]; + cpid[1] = p->packet[2]; + tpid = get_pid(cpid); + + if ( p->packet[1]&0x80){ + fprintf(stderr,"Error in TS for PID: %d\n", + tpid); + } + + flag = cpid[0]; + flags = p->packet[3]; + + if ( flags & ADAPT_FIELD ) { + // adaption field + adapt_length = p->packet[4]; + } + + c = 5 + adapt_length - (int)(!(flags & ADAPT_FIELD)); + if (flags & PAYLOAD){ + for ( l = 0; l < MAXFILT ; l++){ + if ( filt_is_set(p,l) ) { + if ( p->pid[l] == tpid) { + if ( pes_is_set(p,l) ){ + if (cpid[0] & PAY_START){ + p->pes_started |= + (tflags) + (1 << l); + p->pes_start |= + (tflags) + (1 << l); + } else { + p->pes_start &= ~ + ((tflags) + (1 << l)); + } + pes_filter(p,l,c); + } else { + sec_filter(p,l,c); + } + } + } + } + } +} + + +void pes_filter(trans *p, int filtn, int off) +{ + int count,c; + uint8_t *buf; + + if (filtn < 0 || filtn >= MAXFILT) return; + + count = 188 - off; + c = 188*filtn; + buf = p->packet+off; + if (pes_is_started(p,filtn)){ + p->is_full |= (tflags) (1 << filtn); + memcpy(p->transbuf+c,buf,count); + p->transcount[filtn] = count; + } +} + +section *get_filt_sec(trans *p, int filtn) +{ + section *sec; + + sec = &p->sec[filtn]; + p->is_full &= ~((tflags) (1 << filtn) ); + return sec; +} + +int get_filt_buf(trans *p, int filtn,uint8_t **buf) +{ + *buf = p->transbuf+188*filtn; + p->is_full &= ~((tflags) (1 << filtn) ); + return p->transcount[filtn]; +} + + + + +void sec_filter(trans *p, int filtn, int off) +{ + int i,j; + int error; + int count,c; + uint8_t *buf, *secbuf; + section *sec; + + // fprintf(stderr,"sec_filter\n"); + + if (filtn < 0 || filtn >= MAXFILT) return; + + count = 188 - off; + c = 0; + buf = p->packet+off; + sec = &p->sec[filtn]; + secbuf = sec->payload; + if(!filt_is_ready(p,filtn)){ + p->is_full &= ~((tflags) (1 << filtn) ); + sec->found = 0; + sec->length = 0; + } + + if ( !sec->found ){ + c = buf[c]+1; + if (c >= count) return; + sec->id = buf[c]; + secbuf[0] = buf[c]; + c++; + sec->found++; + sec->length = 0; + } + + while ( c < count && sec->found < 3){ + secbuf[sec->found] = buf[c]; + c++; + sec->found++; + } + if (c == count) return; + + if (!sec->length && sec->found == 3){ + sec->length |= ((secbuf[1] & 0x0F) << 8); + sec->length |= (secbuf[2] & 0xFF); + } + + while ( c < count && sec->found < sec->length+3){ + secbuf[sec->found] = buf[c]; + c++; + sec->found++; + } + + if ( sec->length && sec->found == sec->length+3 ){ + error=0; + for ( i = 0; i < MASKL; i++){ + if (i > 0 ) j=2+i; + else j = 0; + error += (sec->payload[j]&p->mask[MASKL*filtn+i])^ + (p->filt[MASKL*filtn+i]& + p->mask[MASKL*filtn+i]); + } + if (!error){ + p->is_full |= (tflags) (1 << filtn); + } + if (buf[0]+1 < c ) c=count; + } + + if ( c < count ) sec_filter(p, filtn, off); + +} + +#define MULT 1024 + + +void write_ps_headr( ps_packet *p, uint8_t *pts,int fd) +{ + long muxr = 37500; + uint8_t audio_bound = 1; + uint8_t fixed = 0; + uint8_t CSPS = 0; + uint8_t audio_lock = 1; + uint8_t video_lock = 1; + uint8_t video_bound = 1; + uint8_t stream1 = 0XC0; + uint8_t buffer1_scale = 1; + uint32_t buffer1_size = 32; + uint8_t stream2 = 0xE0; + uint8_t buffer2_scale = 1; + uint32_t buffer2_size = 230; + + init_ps(p); + + p->mpeg = 2; +// SCR = 0 + p->scr[0] = 0x44; + p->scr[1] = 0x00; + p->scr[2] = 0x04; + p->scr[3] = 0x00; + p->scr[4] = 0x04; + p->scr[5] = 0x01; + +// SCR = PTS + p->scr[0] = 0x44 | ((pts[0] >> 3)&0x18) | ((pts[0] >> 4)&0x03); + p->scr[1] = 0x00 | ((pts[0] << 4)&0xF0) | ((pts[1] >> 4)&0x0F); + p->scr[2] = 0x04 | ((pts[1] << 4)&0xF0) | ((pts[2] >> 4)&0x08) + | ((pts[2] >> 5)&0x03); + p->scr[3] = 0x00 | ((pts[2] << 3)&0xF8) | ((pts[3] >> 5)&0x07); + p->scr[4] = 0x04 | ((pts[3] << 3)&0xF8); + p->scr[5] = 0x01; + + p->mux_rate[0] = (uint8_t)(muxr >> 14); + p->mux_rate[1] = (uint8_t)(0xff & (muxr >> 6)); + p->mux_rate[2] = (uint8_t)(0x03 | ((muxr & 0x3f) << 2)); + + p->stuff_length = 0xF8; + + p->sheader_llength[0] = 0x00; + p->sheader_llength[1] = 0x0c; + + setl_ps(p); + + p->rate_bound[0] = (uint8_t)(0x80 | (muxr >>15)); + p->rate_bound[1] = (uint8_t)(0xff & (muxr >> 7)); + p->rate_bound[2] = (uint8_t)(0x01 | ((muxr & 0x7f)<<1)); + + + p->audio_bound = (uint8_t)((audio_bound << 2)|(fixed << 1)|CSPS); + p->video_bound = (uint8_t)((audio_lock << 7)| + (video_lock << 6)|0x20|video_bound); + p->reserved = (uint8_t)(0xFF); + + p->data[0] = stream2; + p->data[1] = (uint8_t) (0xc0 | (buffer2_scale << 5) | + (buffer2_size >> 8)); + p->data[2] = (uint8_t) (buffer2_size & 0xff); + p->data[3] = stream1; + p->data[4] = (uint8_t) (0xc0 | (buffer1_scale << 5) | + (buffer1_size >> 8)); + p->data[5] = (uint8_t) (buffer1_size & 0xff); + + write_ps(fd, p); + kill_ps(p); +} + + + +void twrite(uint8_t const *buf) +{ + int l = TS_SIZE; + int c = 0; + int w; + + + while (l){ + w = write(STDOUT_FILENO,buf+c,l); + if (w>=0){ + l-=w; + c+=w; + } + } +} + +void init_p2t(p2t_t *p, void (*fkt)(uint8_t const *buf)) +{ + memset(p->pes,0,TS_SIZE); + p->counter = 0; + p->pos = 0; + p->frags = 0; + if (fkt) p->t_out = fkt; + else p->t_out = twrite; +} + +void clear_p2t(p2t_t *p) +{ + memset(p->pes,0,TS_SIZE); + p->counter = 0; + p->pos = 0; + p->frags = 0; +} + + +long int find_pes_header(uint8_t const *buf, long int length, int *frags) +{ + int c = 0; + int found = 0; + + *frags = 0; + + while (c < length-3 && !found) { + if (buf[c] == 0x00 && buf[c+1] == 0x00 && + buf[c+2] == 0x01) { + switch ( buf[c+3] ) { + case 0xBA: + case PROG_STREAM_MAP: + case PRIVATE_STREAM2: + case PROG_STREAM_DIR: + case ECM_STREAM : + case EMM_STREAM : + case PADDING_STREAM : + case DSM_CC_STREAM : + case ISO13522_STREAM: + case PRIVATE_STREAM1: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + found = 1; + break; + + default: + c++; + break; + } + } else c++; + } + if (c == length-3 && !found){ + if (buf[length-1] == 0x00) *frags = 1; + if (buf[length-2] == 0x00 && + buf[length-1] == 0x00) *frags = 2; + if (buf[length-3] == 0x00 && + buf[length-2] == 0x00 && + buf[length-1] == 0x01) *frags = 3; + return -1; + } + + return c; +} + +void pes_to_ts( uint8_t const *buf, long int length, uint16_t pid, p2t_t *p) +{ + int c,c2,l,add; + int check,rest; + + c = 0; + c2 = 0; + if (p->frags){ + check = 0; + switch(p->frags){ + case 1: + if ( buf[c] == 0x00 && buf[c+1] == 0x01 ){ + check = 1; + c += 2; + } + break; + case 2: + if ( buf[c] == 0x01 ){ + check = 1; + c++; + } + break; + case 3: + check = 1; + } + if(check){ + switch ( buf[c] ) { + + case PROG_STREAM_MAP: + case PRIVATE_STREAM2: + case PROG_STREAM_DIR: + case ECM_STREAM : + case EMM_STREAM : + case PADDING_STREAM : + case DSM_CC_STREAM : + case ISO13522_STREAM: + case PRIVATE_STREAM1: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + p->pes[0] = 0x00; + p->pes[1] = 0x00; + p->pes[2] = 0x01; + p->pes[3] = buf[c]; + p->pos=4; + memcpy(p->pes+p->pos,buf+c,TS_SIZE-4-p->pos); + c += TS_SIZE-4-p->pos; + p_to_t(p->pes,TS_SIZE-4,pid,&p->counter, + p->t_out); + clear_p2t(p); + break; + + default: + c=0; + break; + } + } + p->frags = 0; + } + + if (p->pos){ + c2 = find_pes_header(buf+c,length-c,&p->frags); + if (c2 >= 0 && c2 < TS_SIZE-4-p->pos){ + l = c2+c; + } else l = TS_SIZE-4-p->pos; + memcpy(p->pes+p->pos,buf,l); + c += l; + p->pos += l; + p_to_t(p->pes,p->pos,pid,&p->counter, + p->t_out); + clear_p2t(p); + } + + add = 0; + while (c < length){ + c2 = find_pes_header(buf+c+add,length-c-add,&p->frags); + if (c2 >= 0) { + c2 += c+add; + if (c2 > c){ + p_to_t(buf+c,c2-c,pid,&p->counter, + p->t_out); + c = c2; + clear_p2t(p); + add = 0; + } else add = 1; + } else { + l = length-c; + rest = l % (TS_SIZE-4); + l -= rest; + p_to_t(buf+c,l,pid,&p->counter, + p->t_out); + memcpy(p->pes,buf+c+l,rest); + p->pos = rest; + c = length; + } + } +} + + + +void p_to_t( uint8_t const *buf, long int length, uint16_t pid, uint8_t *counter, + void (*ts_write)(uint8_t const *)) +{ + + int l, pes_start; + uint8_t obuf[TS_SIZE]; + long int c = 0; + pes_start = 0; + if ( length > 3 && + buf[0] == 0x00 && buf[1] == 0x00 && buf[2] == 0x01 ) + switch (buf[3]){ + case PROG_STREAM_MAP: + case PRIVATE_STREAM2: + case PROG_STREAM_DIR: + case ECM_STREAM : + case EMM_STREAM : + case PADDING_STREAM : + case DSM_CC_STREAM : + case ISO13522_STREAM: + case PRIVATE_STREAM1: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + pes_start = 1; + break; + + default: + break; + } + + while ( c < length ){ + memset(obuf,0,TS_SIZE); + if (length - c >= TS_SIZE-4){ + l = write_ts_header(pid, counter, pes_start + , obuf, TS_SIZE-4); + memcpy(obuf+l, buf+c, TS_SIZE-l); + c += TS_SIZE-l; + } else { + l = write_ts_header(pid, counter, pes_start + , obuf, length-c); + memcpy(obuf+l, buf+c, TS_SIZE-l); + c = length; + } + ts_write(obuf); + pes_start = 0; + } +} + + +int write_ps_header(uint8_t *buf, + uint32_t SCR, + long muxr, + uint8_t audio_bound, + uint8_t fixed, + uint8_t CSPS, + uint8_t audio_lock, + uint8_t video_lock, + uint8_t video_bound, + uint8_t stream1, + uint8_t buffer1_scale, + uint32_t buffer1_size, + uint8_t stream2, + uint8_t buffer2_scale, + uint32_t buffer2_size) +{ + ps_packet p; + uint8_t *pts; + long lpts; + init_ps(&p); + + lpts = htonl(SCR); + pts = (uint8_t *) &lpts; + + + p.mpeg = 2; +// SCR = 0 + p.scr[0] = 0x44; + p.scr[1] = 0x00; + p.scr[2] = 0x04; + p.scr[3] = 0x00; + p.scr[4] = 0x04; + p.scr[5] = 0x01; + +// SCR = PTS + p.scr[0] = 0x44 | ((pts[0] >> 3)&0x18) | ((pts[0] >> 4)&0x03); + p.scr[1] = 0x00 | ((pts[0] << 4)&0xF0) | ((pts[1] >> 4)&0x0F); + p.scr[2] = 0x04 | ((pts[1] << 4)&0xF0) | ((pts[2] >> 4)&0x08) + | ((pts[2] >> 5)&0x03); + p.scr[3] = 0x00 | ((pts[2] << 3)&0xF8) | ((pts[3] >> 5)&0x07); + p.scr[4] = 0x04 | ((pts[3] << 3)&0xF8); + p.scr[5] = 0x01; + + p.mux_rate[0] = (uint8_t)(muxr >> 14); + p.mux_rate[1] = (uint8_t)(0xff & (muxr >> 6)); + p.mux_rate[2] = (uint8_t)(0x03 | ((muxr & 0x3f) << 2)); + + p.stuff_length = 0xF8; + + if (stream1 && stream2){ + p.sheader_llength[0] = 0x00; + p.sheader_llength[1] = 0x0c; + + setl_ps(&p); + + p.rate_bound[0] = (uint8_t)(0x80 | (muxr >>15)); + p.rate_bound[1] = (uint8_t)(0xff & (muxr >> 7)); + p.rate_bound[2] = (uint8_t)(0x01 | ((muxr & 0x7f)<<1)); + + + p.audio_bound = (uint8_t)((audio_bound << 2)|(fixed << 1)|CSPS); + p.video_bound = (uint8_t)((audio_lock << 7)| + (video_lock << 6)|0x20|video_bound); + p.reserved = (uint8_t)(0xFF >> 1); + + p.data[0] = stream2; + p.data[1] = (uint8_t) (0xc0 | (buffer2_scale << 5) | + (buffer2_size >> 8)); + p.data[2] = (uint8_t) (buffer2_size & 0xff); + p.data[3] = stream1; + p.data[4] = (uint8_t) (0xc0 | (buffer1_scale << 5) | + (buffer1_size >> 8)); + p.data[5] = (uint8_t) (buffer1_size & 0xff); + + cwrite_ps(buf, &p, PS_HEADER_L2); + kill_ps(&p); + return PS_HEADER_L2; + } else { + cwrite_ps(buf, &p, PS_HEADER_L1); + kill_ps(&p); + return PS_HEADER_L1; + } +} + + + +#define MAX_BASE 80 +#define MAX_PATH 256 +#define MAX_EXT 10 + +int break_up_filename(char *name, char *base_name, char *path, char *ext) +{ + int l,i,sstop,sstart; + + l = strlen(name); + sstop = l; + sstart = -1; + for( i= l-1; i >= 0; i--){ + if (sstop == l && name[i] == '.') sstop = i; + if (sstart<0 && name[i] == '/') sstart = i+1; + } + if (sstart < 0) sstart = 0; + if (sstop-sstart < MAX_BASE){ + strncpy(base_name, name+sstart, sstop-sstart); + base_name[sstop-sstart]=0; + if(sstart > 0){ + if( l - sstop + sstart < MAX_PATH){ + strncpy(path, name, sstart); + path[sstart] = 0; + } else { + fprintf(stderr,"PATH too long\n"); + return -1; + } + + } else { + strcpy(path, "./"); + } + + if(sstop < l){ + if( l - sstop -1 < MAX_EXT){ + strncpy(ext, name+sstop+1, l-sstop-1); + ext[l-sstop-1]=0; + } else { + fprintf(stderr,"Extension too long\n"); + return -1; + } + + } else { + strcpy(ext, ""); + } + + } else { + fprintf(stderr,"Name too long\n"); + return -1; + } +/* + printf("%d %d\n",sstart, sstop); + printf("%s %d\n",name, strlen(name)); + printf("%s %d\n",base_name, strlen(base_name)); + printf("%s %d\n",path,strlen(path)); + printf("%s %d\n",ext,strlen(ext)); +*/ + return 0; +} + + +int seek_mpg_start(uint8_t *buf, int size) +{ + int found = 0; + int c=0; + int seq = 0; + int mpeg = 0; + int mark = 0; + + while ( !seq ){ + while (found != 4){ + switch (found) { + case 0: + if ( buf[c] == 0x00 ) found++; + c++; + break; + case 1: + if ( buf[c] == 0x00 ) found++; + else found = 0; + c++; + break; + case 2: + if ( buf[c] == 0x01 ) found++; + else found = 0; + if ( buf[c] == 0x00 ) found = 2; + c++; + break; + + case 3: + if ( (buf[c] & 0xe0) == 0xe0 ) found++; + else found = 0; + c++; + break; + } + if (c >= size) return -1; + } + + if (found == 4){ + mark = c-4; + c+=2; + if (c >= size) return -1; + + if ( (buf[c] & 0xC0) == 0x80 ){ + mpeg = 2; + c += 2; + if (c >= size) return -1; + c += buf[c]+1; + if (c >= size) return -1; + } else { + mpeg = 1; + while( buf[c] == 0xFF ) { + c++; + if (c >= size) return -1; + } + if ( (buf[c] & 0xC0) == 0x40) c+=2; + if (c >= size) return -1; + if ( (buf[c] & 0x30) ){ + if ( (buf[c] & 0x30) == 0x20) c+=5; + else c+=10; + } else c++; + if (c >= size) return -1; + } + + if ( buf[c] == 0x00 && + buf[c+1] == 0x00 && + buf[c+2] == 0x01 && + buf[c+3] == 0xB3 ) + seq = 1; + } + found = 0; + } + + return size-mark; +} + + +void write_mpg(int fstart, uint64_t length, int fdin, int fdout) +{ +// uint8_t mpeg_end[4] = { 0x00, 0x00, 0x01, 0xB9 }; + uint8_t *buf; + uint64_t l=0; + uint64_t count = 0; + struct stat sb; + int buf_size; + + fstat (fdout, &sb); + buf_size = sb.st_blksize; + + buf = (uint8_t *) alloca (buf_size + sizeof (int)); + + lseek(fdin, fstart, SEEK_SET); + + while ( count < length && (l = read(fdin,buf,buf_size)) >= 0){ + if (l > 0) count+=l; + write(fdout,buf,l); + printf("written %02.2f%%\r",(100.*count)/length); + } + printf("\n"); + + //write( fdout, mpeg_end, 4); +} + + +#define CHECKBUF (1024*1024) +#define ONE_GIG (1024UL*1024UL*1024UL) +void split_mpg(char *name, uint64_t size) +{ + char base_name[MAX_BASE]; + char path[MAX_PATH]; + char ext[MAX_EXT]; + char new_name[256]; + uint8_t buf[CHECKBUF]; + int fdin; + int fdout; + uint64_t length = 0; + uint64_t last; + int i; + int mark, csize; + struct stat sb; + + if (break_up_filename(name,base_name,path,ext) < 0) exit(1); + + +#ifdef __FreeBSD__ + if ( (fdin = open(name, O_RDONLY)) < 0){ +#else + if ( (fdin = open(name, O_RDONLY|O_LARGEFILE)) < 0){ +#endif + fprintf(stderr,"Can't open %s\n",name); + exit(1); + } + + fstat (fdin, &sb); + + length = sb.st_size; + if ( length < ONE_GIG ) + printf("Filelength = %2.2f MB\n", length/1024./1024.); + else + printf("Filelength = %2.2f GB\n", length/1024./1024./1024.); + + if ( length < size ) length = size; + + printf("Splitting %s into Files with size <= %2.2f MB\n",name, + size/1024./1024.); + + csize = CHECKBUF; + read(fdin, buf, csize); + if ( (mark = seek_mpg_start(buf,csize)) < 0){ + fprintf(stderr,"Couldn't find sequence header\n"); + exit(1); + } + + last = csize-mark; + + for ( i = 0 ; i < length/size; i++){ + csize = CHECKBUF; + + if (csize > length-last) csize = length-last; + lseek(fdin, last+size-csize, SEEK_SET); + read(fdin, buf, csize); + if ( (mark = seek_mpg_start(buf,csize)) < 0){ + fprintf(stderr,"Couldn't find sequence header\n"); + exit(1); + } + + sprintf(new_name,"%s-%03d.%s",base_name,i,ext); + printf("writing %s\n",new_name); + +#ifdef __FreeBSD__ + if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC, +#else + if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC + |O_LARGEFILE, +#endif + S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP| + S_IROTH|S_IWOTH)) < 0){ + fprintf(stderr,"Can't open %s\n",new_name); + exit(1); + } + write_mpg(last, size-mark, fdin, fdout); + last = last + size - mark; + } + sprintf(new_name,"%s-%03d.%s",base_name,i,ext); + printf("writing %s\n",new_name); + +#ifdef __FreeBSD__ + if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC, +#else + if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC + |O_LARGEFILE, +#endif + S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP| + S_IROTH|S_IWOTH)) < 0){ + fprintf(stderr,"Can't open %s\n",new_name); + exit(1); + } + write_mpg(last, length-last, fdin, fdout); +} + + + + +void cut_mpg(char *name, uint64_t size) +{ + char base_name[MAX_BASE]; + char path[MAX_PATH]; + char ext[MAX_EXT]; + char new_name[256]; + uint8_t buf[CHECKBUF]; + int fdin; + int fdout; + uint64_t length = 0; + uint64_t last; + int mark, csize; + struct stat sb; + + if (break_up_filename(name,base_name,path,ext) < 0) exit(1); + + +#ifdef __FreeBSD__ + if ( (fdin = open(name, O_RDONLY)) < 0){ +#else + if ( (fdin = open(name, O_RDONLY|O_LARGEFILE)) < 0){ +#endif + fprintf(stderr,"Can't open %s\n",name); + exit(1); + } + + fstat (fdin, &sb); + + length = sb.st_size; + if ( length < ONE_GIG ) + printf("Filelength = %2.2f MB\n", length/1024./1024.); + else + printf("Filelength = %2.2f GB\n", length/1024./1024./1024.); + + if ( length < size ) length = size; + + printf("Splitting %s into 2 Files with length %.2f MB and %.2f MB\n", + name, size/1024./1024., (length-size)/1024./1024.); + + csize = CHECKBUF; + read(fdin, buf, csize); + if ( (mark = seek_mpg_start(buf,csize)) < 0){ + fprintf(stderr,"Couldn't find sequence header\n"); + exit(1); + } + + last = csize-mark; + + if (csize > length-last) csize = length-last; + lseek(fdin, last+size-csize, SEEK_SET); + read(fdin, buf, csize); + if ( (mark = seek_mpg_start(buf,csize)) < 0){ + fprintf(stderr,"Couldn't find sequence header\n"); + exit(1); + } + + sprintf(new_name,"%s-1.%s",base_name,ext); + printf("writing %s\n",new_name); + +#ifdef __FreeBSD__ + if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC, +#else + if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC + |O_LARGEFILE, +#endif + S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP| + S_IROTH|S_IWOTH)) < 0){ + fprintf(stderr,"Can't open %s\n",new_name); + exit(1); + } + write_mpg(last, size-mark, fdin, fdout); + last = last + size - mark; + + sprintf(new_name,"%s-2.%s",base_name,ext); + printf("writing %s\n",new_name); + +#ifdef __FreeBSD__ + if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC, +#else + if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC + |O_LARGEFILE, +#endif + S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP| + S_IROTH|S_IWOTH)) < 0){ + fprintf(stderr,"Can't open %s\n",new_name); + exit(1); + } + write_mpg(last, length-last, fdin, fdout); +} + + + + +void write_all (int fd, const char *data, int length) +{ + int r; + + while (length) { + if ((r = write(fd, data, length)) > 0) { + data += r; + length -= r; + } + } +} + + + +void read_all (int fd, char *data, int length) +{ + int c = 0; + + while(1) { + if( read(fd, data+c, 1) == 1) { + c++; + if(data[c-1] == '\n') { + data[c] = 0; + break; + } + } + else { + fprintf (stderr, "Error reading socket\n"); + exit(1); + } + } +} + + + +char *url2host (char *url, char **name, uint32_t *ip, uint32_t *port) +{ + char *murl; + struct hostent *hoste; + struct in_addr haddr; + int found_ip = 1; + + if (!(strncmp(url, "http://", 7))) + url += 7; + + *name = strdup(url); + if (!(*name)) { + *name = NULL; + return (NULL); + } + + murl = url; + while (*murl && *murl != ':' && *murl != '/') { + if ((*murl < '0' || *murl > '9') && *murl != '.') + found_ip = 0; + murl++; + } + + (*name)[murl - url] = 0; + if (found_ip) { + if ((*ip = inet_addr(*name)) == INADDR_NONE) + return (NULL); + } else { + if (!(hoste = gethostbyname(*name))) + return (NULL); + memcpy (&haddr, hoste->h_addr, sizeof(haddr)); + *ip = haddr.s_addr; + } + + if (!*murl || *murl == '/') { + *port = 80; + return (murl); + } + *port = atoi(++murl); + + while (*murl && *murl != '/') + murl++; + return (murl); +} + +#define ACCEPT "Accept: video/mpeg, video/x-mpegurl, */*\r\n" + +int http_open (char *url) +{ + char purl[1024], *host, req[1024], *sptr; + uint32_t ip; + uint32_t port; + int sock; + int reloc, relocnum = 0; + struct sockaddr_in server; + int mfd; + + strncpy (purl, url, 1023); + purl[1023] = '\0'; + + do { + host = NULL; + strcpy (req, "GET "); + if (!(sptr = url2host(purl, &host, &ip, &port))) { + fprintf (stderr, "Unknown host\n"); + exit (1); + } + strcat (req, sptr); + sprintf (req + strlen(req), + " HTTP/1.0\r\nUser-Agent: %s/%s\r\n", + "whatever", "you want"); + if (host) { + sprintf(req + strlen(req), + "Host: %s:%u\r\n", host, port); + free (host); + } + + strcat (req, ACCEPT); + strcat (req, "\r\n"); + + server.sin_port = htons(port); + server.sin_family = AF_INET; + server.sin_addr.s_addr = ip; + + if ((sock = socket(PF_INET, SOCK_STREAM, 6)) < 0) { + perror ("socket"); + exit (1); + } + + if (connect(sock, (struct sockaddr *)&server, + sizeof(server))) { + perror ("connect"); + exit (1); + } + + write_all (sock, req, strlen(req)); + if (!(mfd = fileno(fdopen(sock, "rb")))) { + perror ("open"); + exit (1); + } + reloc = 0; + purl[0] = '\0'; + read_all (mfd, req, 1023); + if ((sptr = strchr(req, ' '))) { + switch (sptr[1]) { + case '2': + break; + case '3': + reloc = 1; + default: + fprintf (stderr, "HTTP req failed:%s", + sptr+1); + exit (1); + } + } + do { + read_all (mfd,req, 1023); + if (!strncmp(req, "Location:", 9)) + strncpy (purl, req+10, 1023); + } while (req[0] != '\r' && req[0] != '\n'); + } while (reloc && purl[0] && relocnum++ < 3); + if (reloc) { + fprintf (stderr, "Too many HTTP relocations.\n"); + exit (1); + } + + return sock; +} + +extern int errno; +const char * strerrno (void) +{ + return strerror(errno); +} diff --git a/plugins/streamdev/streamdev-cvs/libdvbmpeg/ctools.h b/plugins/streamdev/streamdev-cvs/libdvbmpeg/ctools.h new file mode 100644 index 0000000..a7b0271 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/libdvbmpeg/ctools.h @@ -0,0 +1,404 @@ +/* + * dvb-mpegtools for the Siemens Fujitsu DVB PCI card + * + * Copyright (C) 2000, 2001 Marcus Metzler + * for convergence integrated media GmbH + * Copyright (C) 2002, 2003 Marcus Metzler + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + + * The author can be reached at mocm@metzlerbros.de + + */ + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <netinet/in.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <libgen.h> +#include <stdint.h> +#include <netdb.h> +#include <sys/param.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + + +#include "ringbuffy.h" +#include "transform.h" + +#ifndef _CTOOLS_H_ +#define _CTOOLS_H_ + +#define VIDEO_MODE_PAL 0 +#define VIDEO_MODE_NTSC 1 + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + enum {PS_STREAM, TS_STREAM, PES_STREAM}; + enum {pDUNNO, pPAL, pNTSC}; + + uint64_t trans_pts_dts(uint8_t *pts); + +/* + PES +*/ + +#define PROG_STREAM_MAP 0xBC +#ifndef PRIVATE_STREAM1 +#define PRIVATE_STREAM1 0xBD +#endif +#define PADDING_STREAM 0xBE +#ifndef PRIVATE_STREAM2 +#define PRIVATE_STREAM2 0xBF +#endif +#define AUDIO_STREAM_S 0xC0 +#define AUDIO_STREAM_E 0xDF +#define VIDEO_STREAM_S 0xE0 +#define VIDEO_STREAM_E 0xEF +#define ECM_STREAM 0xF0 +#define EMM_STREAM 0xF1 +#define DSM_CC_STREAM 0xF2 +#define ISO13522_STREAM 0xF3 +#define PROG_STREAM_DIR 0xFF + +#define BUFFYSIZE 10*MAX_PLENGTH +#define MAX_PTS 8192 +#define MAX_FRAME 8192 +#define MAX_PACK_L 4096 +#define PS_HEADER_L1 14 +#define PS_HEADER_L2 (PS_HEADER_L1+18) +#define MAX_H_SIZE (PES_H_MIN + PS_HEADER_L1 + 5) +#define PES_MIN 7 +#define PES_H_MIN 9 + +//flags1 +#define FLAGS 0x40 +#define SCRAMBLE_FLAGS 0x30 +#define PRIORITY_FLAG 0x08 +#define DATA_ALIGN_FLAG 0x04 +#define COPYRIGHT_FLAG 0x02 +#define ORIGINAL_FLAG 0x01 + +//flags2 +#define PTS_DTS_FLAGS 0xC0 +#define ESCR_FLAG 0x20 +#define ES_RATE_FLAG 0x10 +#define DSM_TRICK_FLAG 0x08 +#define ADD_CPY_FLAG 0x04 +#define PES_CRC_FLAG 0x02 +#define PES_EXT_FLAG 0x01 + +//pts_dts flags +#define PTS_ONLY 0x80 +#define PTS_DTS 0xC0 + +//private flags +#define PRIVATE_DATA 0x80 +#define HEADER_FIELD 0x40 +#define PACK_SEQ_CTR 0x20 +#define P_STD_BUFFER 0x10 +#define PES_EXT_FLAG2 0x01 + +#define MPEG1_2_ID 0x40 +#define STFF_LNGTH_MASK 0x3F + + + typedef struct pes_packet_{ + uint8_t stream_id; + uint8_t llength[2]; + uint32_t length; + uint8_t flags1; + uint8_t flags2; + uint8_t pes_hlength; + uint8_t pts[5]; + uint8_t dts[5]; + uint8_t escr[6]; + uint8_t es_rate[3]; + uint8_t trick; + uint8_t add_cpy; + uint8_t prev_pes_crc[2]; + uint8_t priv_flags; + uint8_t pes_priv_data[16]; + uint8_t pack_field_length; + uint8_t *pack_header; + uint8_t pck_sqnc_cntr; + uint8_t org_stuff_length; + uint8_t p_std[2]; + uint8_t pes_ext_lngth; + uint8_t *pes_ext; + uint8_t *pes_pckt_data; + int padding; + int mpeg; + int mpeg1_pad; + uint8_t *mpeg1_headr; + uint8_t stuffing; + } pes_packet; + + void init_pes(pes_packet *p); + void kill_pes(pes_packet *p); + void setlength_pes(pes_packet *p); + void nlength_pes(pes_packet *p); + int cwrite_pes(uint8_t *buf, pes_packet *p, long length); + void write_pes(int fd, pes_packet *p); + int read_pes(int f, pes_packet *p); + void cread_pes(char *buf, pes_packet *p); + +/* + Transport Stream +*/ + +#define TS_SIZE 188 +#define TRANS_ERROR 0x80 +#define PAY_START 0x40 +#define TRANS_PRIO 0x20 +#define PID_MASK_HI 0x1F +//flags +#define TRANS_SCRMBL1 0x80 +#define TRANS_SCRMBL2 0x40 +#define ADAPT_FIELD 0x20 +#define PAYLOAD 0x10 +#define COUNT_MASK 0x0F + +// adaptation flags +#define DISCON_IND 0x80 +#define RAND_ACC_IND 0x40 +#define ES_PRI_IND 0x20 +#define PCR_FLAG 0x10 +#define OPCR_FLAG 0x08 +#define SPLICE_FLAG 0x04 +#define TRANS_PRIV 0x02 +#define ADAP_EXT_FLAG 0x01 + +// adaptation extension flags +#define LTW_FLAG 0x80 +#define PIECE_RATE 0x40 +#define SEAM_SPLICE 0x20 + + typedef struct ts_packet_{ + uint8_t pid[2]; + uint8_t flags; + uint8_t count; + uint8_t data[184]; + uint8_t adapt_length; + uint8_t adapt_flags; + uint8_t pcr[6]; + uint8_t opcr[6]; + uint8_t splice_count; + uint8_t priv_dat_len; + uint8_t *priv_dat; + uint8_t adapt_ext_len; + uint8_t adapt_eflags; + uint8_t ltw[2]; + uint8_t piece_rate[3]; + uint8_t dts[5]; + int rest; + uint8_t stuffing; + } ts_packet; + + void init_ts(ts_packet *p); + void kill_ts(ts_packet *p); + unsigned short pid_ts(ts_packet *p); + int cwrite_ts(uint8_t *buf, ts_packet *p, long length); + void write_ts(int fd, ts_packet *p); + int read_ts(int f, ts_packet *p); + void cread_ts (char *buf, ts_packet *p, long length); + + +/* + Program Stream +*/ + +#define PACK_STUFF_MASK 0x07 + +#define FIXED_FLAG 0x02 +#define CSPS_FLAG 0x01 +#define SAUDIO_LOCK_FLAG 0x80 +#define SVIDEO_LOCK_FLAG 0x40 + +#define PS_MAX 200 + + typedef struct ps_packet_{ + uint8_t scr[6]; + uint8_t mux_rate[3]; + uint8_t stuff_length; + uint8_t *data; + uint8_t sheader_llength[2]; + int sheader_length; + uint8_t rate_bound[3]; + uint8_t audio_bound; + uint8_t video_bound; + uint8_t reserved; + int npes; + int mpeg; + } ps_packet; + + void init_ps(ps_packet *p); + void kill_ps(ps_packet *p); + void setlength_ps(ps_packet *p); + uint32_t scr_base_ps(ps_packet *p); + uint16_t scr_ext_ps(ps_packet *p); + int mux_ps(ps_packet *p); + int rate_ps(ps_packet *p); + int cwrite_ps(uint8_t *buf, ps_packet *p, long length); + void write_ps(int fd, ps_packet *p); + int read_ps (int f, ps_packet *p); + void cread_ps (char *buf, ps_packet *p, long length); + + + +#define MAX_PLENGTH 0xFFFF + + typedef struct sectionstruct { + int id; + int length; + int found; + uint8_t payload[4096+3]; + } section; + + + typedef uint32_t tflags; +#define MAXFILT 32 +#define MASKL 16 + typedef struct trans_struct { + int found; + uint8_t packet[188]; + uint16_t pid[MAXFILT]; + uint8_t mask[MAXFILT*MASKL]; + uint8_t filt[MAXFILT*MASKL]; + uint8_t transbuf[MAXFILT*188]; + int transcount[MAXFILT]; + section sec[MAXFILT]; + tflags is_full; + tflags pes_start; + tflags pes_started; + tflags pes; + tflags set; + } trans; + + + void init_trans(trans *p); + int set_trans_filt(trans *p, int filtn, uint16_t pid, uint8_t *mask, + uint8_t *filt, int pes); + + void clear_trans_filt(trans *p,int filtn); + int filt_is_set(trans *p, int filtn); + int pes_is_set(trans *p, int filtn); + int pes_is_started(trans *p, int filtn); + int pes_is_start(trans *p, int filtn); + int filt_is_ready(trans *p,int filtn); + + void trans_filt(uint8_t *buf, int count, trans *p); + void tfilter(trans *p); + void pes_filter(trans *p, int filtn, int off); + void sec_filter(trans *p, int filtn, int off); + int get_filt_buf(trans *p, int filtn,uint8_t **buf); + section *get_filt_sec(trans *p, int filtn); + + + typedef struct a2pstruct{ + int type; + int fd; + int found; + int length; + int headr; + int plength; + uint8_t cid; + uint8_t flags; + uint8_t abuf[MAX_PLENGTH]; + int alength; + uint8_t vbuf[MAX_PLENGTH]; + int vlength; + uint8_t last_av_pts[4]; + uint8_t av_pts[4]; + uint8_t scr[4]; + uint8_t pid0; + uint8_t pid1; + uint8_t pidv; + uint8_t pida; + } a2p; + + + + void get_pespts(uint8_t *av_pts,uint8_t *pts); + void init_a2p(a2p *p); + void av_pes_to_pes(uint8_t *buf,int count, a2p *p); + int w_pesh(uint8_t id,int length ,uint8_t *pts, uint8_t *obuf); + int w_tsh(uint8_t id,int length ,uint8_t *pts, uint8_t *obuf,a2p *p,int startpes); + void pts2pts(uint8_t *av_pts, uint8_t *pts); + void write_ps_headr(ps_packet *p,uint8_t *pts,int fd); + + typedef struct p2t_s{ + uint8_t pes[TS_SIZE]; + uint8_t counter; + long int pos; + int frags; + void (*t_out)(uint8_t const *buf); + } p2t_t; + + void twrite(uint8_t const *buf); + void init_p2t(p2t_t *p, void (*fkt)(uint8_t const *buf)); + long int find_pes_header(uint8_t const *buf, long int length, int *frags); + void pes_to_ts( uint8_t const *buf, long int length, uint16_t pid, p2t_t *p); + void p_to_t( uint8_t const *buf, long int length, uint16_t pid, + uint8_t *counter, void (*ts_write)(uint8_t const *)); + + + int write_pes_header(uint8_t id,int length , long PTS, + uint8_t *obuf, int stuffing); + + int write_ps_header(uint8_t *buf, + uint32_t SCR, + long muxr, + uint8_t audio_bound, + uint8_t fixed, + uint8_t CSPS, + uint8_t audio_lock, + uint8_t video_lock, + uint8_t video_bound, + uint8_t stream1, + uint8_t buffer1_scale, + uint32_t buffer1_size, + uint8_t stream2, + uint8_t buffer2_scale, + uint32_t buffer2_size); + + + int seek_mpg_start(uint8_t *buf, int size); + + + void split_mpg(char *name, uint64_t size); + void cut_mpg(char *name, uint64_t size); + int http_open (char *url); + ssize_t save_read(int fd, void *buf, size_t count); + + const char * strerrno(void); +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /*_CTOOLS_H_*/ diff --git a/plugins/streamdev/streamdev-cvs/libdvbmpeg/remux.c b/plugins/streamdev/streamdev-cvs/libdvbmpeg/remux.c new file mode 100644 index 0000000..1d65525 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/libdvbmpeg/remux.c @@ -0,0 +1,1215 @@ +/* + * dvb-mpegtools for the Siemens Fujitsu DVB PCI card + * + * Copyright (C) 2000, 2001 Marcus Metzler + * for convergence integrated media GmbH + * Copyright (C) 2002 Marcus Metzler + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + + * The author can be reached at mocm@metzlerbros.de, + */ + +#include "remux.h" + +unsigned int bitrates[3][16] = +{{0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0}, + {0,32,48,56,64,80,96,112,128,160,192,224,256,320,384,0}, + {0,32,40,48,56,64,80,96,112,128,160,192,224,256,320,0}}; + +uint32_t freq[4] = {441, 480, 320, 0}; +static uint32_t samples[4] = { 384, 1152, 0, 0}; +char *frames[3] = {"I-Frame","P-Frame","B-Frame"}; + + +void copy_ptslm(PTS_List *a, PTS_List *b) +{ + a->pos = b->pos; + a->PTS = b->PTS; + a->dts = b->dts; + a->spos = b->spos; +} + +void clear_ptslm(PTS_List *a) +{ + a->pos = 0; + a->PTS = 0; + a->dts = 0; + a->spos = 0; +} + +void init_ptsl(PTS_List *ptsl) +{ + int i; + for (i=0;i< MAX_PTS;i++){ + clear_ptslm(&ptsl[i]); + } +} + +int del_pts(PTS_List *ptsl, int pos, int nr) +{ + int i; + int del = 0; + + for( i = 0; i < nr-1; i++){ + if(pos > ptsl[i].pos && pos >= ptsl[i+1].pos) del++; + } + + if(del) + for( i = 0; i < nr-del; i++){ + copy_ptslm(&ptsl[i], &ptsl[i+del]); + } + + return nr-del; +} + +int del_ptss(PTS_List *ptsl, int pts, int *nb) +{ + int i; + int del = 0; + int sum = 0; + int nr = *nb; + + for( i = 0; i < nr; i++){ + if(pts > ptsl[i].PTS){ + del++; + sum += ptsl[i].pos; + } + } + + if(del) + for( i = 0; i < nr-del; i++){ + copy_ptslm(&ptsl[i], &ptsl[i+del]); + } + + *nb = nr-del; + return sum; +} + +int add_pts(PTS_List *ptsl, uint32_t pts, int pos, int spos, int nr, uint32_t dts) +{ + int i; + + for ( i=0;i < nr; i++) if (spos && ptsl[i].pos == pos) return nr; + if (nr == MAX_PTS) { + nr = del_pts(ptsl, ptsl[1].pos+1, nr); + } else nr++; + i = nr-1; + + ptsl[i].pos = pos; + ptsl[i].spos = spos; + ptsl[i].PTS = pts; + ptsl[i].dts = dts; + return nr; +} + +void add_vpts(Remux *rem, uint8_t *pts) +{ + uint32_t PTS = trans_pts_dts(pts); + rem->vptsn = add_pts(rem->vpts_list, PTS, rem->vwrite, rem->awrite, + rem->vptsn, PTS); +} + +void add_apts(Remux *rem, uint8_t *pts) +{ + uint32_t PTS = trans_pts_dts(pts); + rem->aptsn = add_pts(rem->apts_list, PTS, rem->awrite, rem->vwrite, + rem->aptsn, PTS); +} + +void del_vpts(Remux *rem) +{ + rem->vptsn = del_pts(rem->vpts_list, rem->vread, rem->vptsn); +} + +void del_apts(Remux *rem) +{ + rem->aptsn = del_pts(rem->apts_list, rem->aread, rem->aptsn); +} + + +void copy_framelm(FRAME_List *a, FRAME_List *b) +{ + a->type = b->type; + a->pos = b->pos; + a->FRAME = b->FRAME; + a->time = b->time; + a->pts = b->pts; + a->dts = b->dts; +} + +void clear_framelm(FRAME_List *a) +{ + a->type = 0; + a->pos = 0; + a->FRAME = 0; + a->time = 0; + a->pts = 0; + a->dts = 0; +} + +void init_framel(FRAME_List *framel) +{ + int i; + for (i=0;i< MAX_FRAME;i++){ + clear_framelm(&framel[i]); + } +} + +int del_frame(FRAME_List *framel, int pos, int nr) +{ + int i; + int del = 0; + + for( i = 0; i < nr-1; i++){ + if(pos > framel[i].pos && pos >= framel[i+1].pos) del++; + } + + if(del) + for( i = 0; i < nr-del; i++){ + copy_framelm(&framel[i], &framel[i+del]); + } + + return nr-del; +} + +int add_frame(FRAME_List *framel, uint32_t frame, int pos, int type, int nr, + uint32_t time, uint32_t pts, uint32_t dts) +{ + int i; + + if (nr == MAX_FRAME) { + nr = del_frame(framel, framel[1].pos+1, nr); + } else nr++; + i = nr-1; + + framel[i].type = type; + framel[i].pos = pos; + framel[i].FRAME = frame; + framel[i].time = time; + framel[i].pts = pts; + framel[i].dts = dts; + return nr; +} + +void add_vframe(Remux *rem, uint32_t frame, long int pos, int type, int time, + uint32_t pts, uint32_t dts) +{ + rem->vframen = add_frame(rem->vframe_list, frame, pos, type, + rem->vframen, time, pts, dts); +} + +void add_aframe(Remux *rem, uint32_t frame, long int pos, uint32_t pts) +{ + rem->aframen = add_frame(rem->aframe_list, frame, pos, 0, + rem->aframen, 0, pts, pts); +} + +void del_vframe(Remux *rem) +{ + rem->vframen = del_frame(rem->vframe_list, rem->vread, rem->vframen); +} + +void del_aframe(Remux *rem) +{ + rem->aframen = del_frame(rem->aframe_list, rem->aread, rem->aframen); +} + + +void printpts(uint32_t pts) +{ + fprintf(stderr,"%2d:%02d:%02d.%03d", + (int)(pts/90000.)/3600, + ((int)(pts/90000.)%3600)/60, + ((int)(pts/90000.)%3600)%60, + (((int)(pts/90.)%3600000)%60000)%1000 + ); +} + + +void find_vframes( Remux *rem, uint8_t *buf, int l) +{ + int c = 0; + int type; + uint32_t time = 0; + int hour; + int min; + int sec; + uint64_t pts=0; + uint64_t dts=0; + uint32_t tempref = 0; + + while ( c < l - 6){ + if (buf[c] == 0x00 && + buf[c+1] == 0x00 && + buf[c+2] == 0x01 && + buf[c+3] == 0xB8) { + c += 4; + hour = (int)((buf[c]>>2)& 0x1F); + min = (int)(((buf[c]<<4)& 0x30)| + ((buf[c+1]>>4)& 0x0F)); + sec = (int)(((buf[c+1]<<3)& 0x38)| + ((buf[c+2]>>5)& 0x07)); + + time = 3600*hour + 60*min + sec; + if ( rem->time_off){ + time = (uint32_t)((uint64_t)time - rem->time_off); + hour = time/3600; + min = (time%3600)/60; + sec = (time%3600)%60; + /* + buf[c] |= (hour & 0x1F) << 2; + buf[c] |= (min & 0x30) >> 4; + buf[c+1] |= (min & 0x0F) << 4; + buf[c+1] |= (sec & 0x38) >> 3; + buf[c+2] |= (sec & 0x07) >> 5;*/ + } + rem->group++; + rem->groupframe = 0; + } + if ( buf[c] == 0x00 && + buf[c+1] == 0x00 && + buf[c+2] == 0x01 && + buf[c+3] == 0x00) { + c += 4; + tempref = (buf[c+1]>>6) & 0x03; + tempref |= buf[c] << 2; + + type = ((buf[c+1]&0x38) >>3); + if ( rem->video_info.framerate){ + pts = ((uint64_t)rem->vframe + tempref + 1 + - rem->groupframe ) * 90000ULL + /rem->video_info.framerate + + rem->vpts_off; + dts = (uint64_t)rem->vframe * 90000ULL/ + rem->video_info.framerate + + rem->vpts_off; + + +fprintf(stderr,"MYPTS:"); +printpts((uint32_t)pts-rem->vpts_off); + fprintf(stderr," REALPTS:"); + printpts(rem->vpts_list[rem->vptsn-1].PTS-rem->vpts_off); + fprintf(stderr," DIFF:"); + printpts(pts-(uint64_t)rem->vpts_list[rem->vptsn-1].PTS); +// fprintf(stderr," DIST: %4d",-rem->vpts_list[rem->vptsn-1].pos+(rem->vwrite+c-4)); + //fprintf(stderr," ERR: %3f",(double)(-rem->vpts_list[rem->vptsn-1].PTS+pts)/(rem->vframe+1)); + fprintf(stderr,"\r"); + + + + rem->vptsn = add_pts(rem->vpts_list,(uint32_t)pts + ,rem->vwrite+c-4, + rem->awrite, + rem->vptsn, + (uint32_t)dts); + + + + } + rem->vframe++; + rem->groupframe++; + add_vframe( rem, rem->vframe, rem->vwrite+c, type, + time, pts, dts); + } else c++; + } +} + +void find_aframes( Remux *rem, uint8_t *buf, int l) +{ + int c = 0; + uint64_t pts = 0; + int sam; + uint32_t fr; + + + while ( c < l - 2){ + if ( buf[c] == 0xFF && + (buf[c+1] & 0xF8) == 0xF8) { + c += 2; + if ( rem->audio_info.layer >= 0){ + sam = samples[3-rem->audio_info.layer]; + fr = freq[rem->audio_info.frequency] ; + + pts = ( (uint64_t)rem->aframe * sam * 900ULL)/fr + + rem->apts_off; + + +fprintf(stderr,"MYPTS:"); +printpts((uint32_t)pts-rem->apts_off); + fprintf(stderr," REALPTS:"); + printpts(rem->apts_list[rem->aptsn-1].PTS-rem->apts_off); + fprintf(stderr," DIFF:"); + printpts((uint32_t)((uint64_t)rem->apts_list[rem->aptsn-1].PTS-pts)); +// fprintf(stderr," DIST: %4d",-rem->apts_list[rem->aptsn-1].pos+(rem->awrite+c-2)); + fprintf(stderr,"\r"); + + rem->aptsn = add_pts(rem->apts_list,(uint32_t)pts + ,rem->awrite+c-2, + rem->vwrite, + rem->aptsn, + (uint32_t)pts); + } + + rem->aframe++; + add_aframe( rem, rem->aframe, rem->awrite+c, pts); + + } else c++; + } +} + +int refill_buffy(Remux *rem) +{ + pes_packet pes; + int count = 0; + int acount, vcount; + ringbuffy *vbuf = &rem->vid_buffy; + ringbuffy *abuf = &rem->aud_buffy; + int fin = rem->fin; + + acount = abuf->size-ring_rest(abuf); + vcount = vbuf->size-ring_rest(vbuf); + + + while ( acount > MAX_PLENGTH && vcount > MAX_PLENGTH && count < 10){ + int neof; + count++; + init_pes(&pes); + if ((neof = read_pes(fin,&pes)) <= 0) return -1; + switch(pes.stream_id){ + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + rem->apes++; + if( rem->audio_info.layer < 0 && + (pes.flags2 & PTS_DTS) ) + add_apts(rem, pes.pts); + find_aframes( rem, pes.pes_pckt_data, pes.length); + ring_write(abuf,(char *)pes.pes_pckt_data,pes.length); + rem->awrite += pes.length; + break; + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + rem->vpes++; + if( !rem->video_info.framerate && + (pes.flags2 & PTS_DTS) ) + add_vpts(rem, pes.pts); + + find_vframes( rem, pes.pes_pckt_data, pes.length); + + ring_write(vbuf,(char *)pes.pes_pckt_data,pes.length); + rem->vwrite += pes.length; + break; + } + acount = abuf->size-ring_rest(abuf); + vcount = vbuf->size-ring_rest(vbuf); + kill_pes(&pes); + } + if (count < 10) return 0; + return 1; +} + +int vring_read( Remux *rem, uint8_t *buf, int l) +{ + int c = 0; + int r = 0; + + if (ring_rest(&rem->vid_buffy) <= l) + r = refill_buffy(rem); + if (r) return -1; + + c = ring_read(&rem->vid_buffy, (char *) buf, l); + rem->vread += c; + del_vpts(rem); + del_vframe(rem); + return c; +} + +int aring_read( Remux *rem, uint8_t *buf, int l) +{ + int c = 0; + int r = 0; + + if (ring_rest(&rem->aud_buffy) <= l) + r = refill_buffy(rem); + if (r) return -1; + + c = ring_read(&rem->aud_buffy, (char *)buf, l); + rem->aread += c; + del_apts(rem); + del_aframe(rem); + return c; +} + +int vring_peek( Remux *rem, uint8_t *buf, int l, long off) +{ + int c = 0; + + if (ring_rest(&rem->vid_buffy) <= l) + refill_buffy(rem); + + c = ring_peek(&rem->vid_buffy, (char *) buf, l, off); + return c; +} + +int aring_peek( Remux *rem, uint8_t *buf, int l, long off) +{ + int c = 0; + + if (ring_rest(&rem->aud_buffy) <= l) + refill_buffy(rem); + + c = ring_peek(&rem->aud_buffy, (char *)buf, l, off); + return c; +} + + +int get_video_info(Remux *rem) +{ + uint8_t buf[12]; + uint8_t *headr; + int found = 0; + int sw; + long off = 0; + int form = -1; + ringbuffy *vid_buffy = &rem->vid_buffy; + VideoInfo *vi = &rem->video_info; + + while (found < 4 && ring_rest(vid_buffy)){ + uint8_t b[4]; + + vring_peek( rem, b, 4, 0); + if ( b[0] == 0x00 && b[1] == 0x00 && b[2] == 0x01 + && b[3] == 0xb3) found = 4; + else { + off++; + vring_read( rem, b, 1); + } + } + rem->vframe = rem->vframen-1; + + if (! found) return -1; + buf[0] = 0x00; buf[1] = 0x00; buf[2] = 0x01; buf[3] = 0xb3; + headr = buf+4; + if(vring_peek(rem, buf, 12, 0) < 12) return -1; + + vi->horizontal_size = ((headr[1] &0xF0) >> 4) | (headr[0] << 4); + vi->vertical_size = ((headr[1] &0x0F) << 8) | (headr[2]); + + sw = (int)((headr[3]&0xF0) >> 4) ; + + switch( sw ){ + case 1: + fprintf(stderr,"Videostream: ASPECT: 1:1"); + vi->aspect_ratio = 100; + break; + case 2: + fprintf(stderr,"Videostream: ASPECT: 4:3"); + vi->aspect_ratio = 133; + break; + case 3: + fprintf(stderr,"Videostream: ASPECT: 16:9"); + vi->aspect_ratio = 177; + break; + case 4: + fprintf(stderr,"Videostream: ASPECT: 2.21:1"); + vi->aspect_ratio = 221; + break; + + case 5 ... 15: + fprintf(stderr,"Videostream: ASPECT: reserved"); + vi->aspect_ratio = 0; + break; + + default: + vi->aspect_ratio = 0; + return -1; + } + + fprintf(stderr," Size = %dx%d",vi->horizontal_size,vi->vertical_size); + + sw = (int)(headr[3]&0x0F); + + switch ( sw ) { + case 1: + fprintf(stderr," FRate: 23.976 fps"); + vi->framerate = 24000/1001.; + form = -1; + break; + case 2: + fprintf(stderr," FRate: 24 fps"); + vi->framerate = 24; + form = -1; + break; + case 3: + fprintf(stderr," FRate: 25 fps"); + vi->framerate = 25; + form = VIDEO_MODE_PAL; + break; + case 4: + fprintf(stderr," FRate: 29.97 fps"); + vi->framerate = 30000/1001.; + form = VIDEO_MODE_NTSC; + break; + case 5: + fprintf(stderr," FRate: 30 fps"); + vi->framerate = 30; + form = VIDEO_MODE_NTSC; + break; + case 6: + fprintf(stderr," FRate: 50 fps"); + vi->framerate = 50; + form = VIDEO_MODE_PAL; + break; + case 7: + fprintf(stderr," FRate: 60 fps"); + vi->framerate = 60; + form = VIDEO_MODE_NTSC; + break; + } + + rem->dts_delay = (int)(7.0/vi->framerate/2.0*90000); + + vi->bit_rate = 400*(((headr[4] << 10) & 0x0003FC00UL) + | ((headr[5] << 2) & 0x000003FCUL) | + (((headr[6] & 0xC0) >> 6) & 0x00000003UL)); + + fprintf(stderr," BRate: %.2f Mbit/s",(vi->bit_rate)/1000000.); + + fprintf(stderr,"\n"); + vi->video_format = form; + + /* + marker_bit (&video_bs, 1); + vi->vbv_buffer_size = getbits (&video_bs, 10); + vi->CSPF = get1bit (&video_bs); + */ + return form; +} + + +int get_audio_info( Remux *rem) +{ + uint8_t *headr; + uint8_t buf[3]; + long off = 0; + int found = 0; + ringbuffy *aud_buffy = &rem->aud_buffy; + AudioInfo *ai = &rem->audio_info; + + while(!ring_rest(aud_buffy) && !refill_buffy(rem)); + while (found < 2 && ring_rest(aud_buffy)){ + uint8_t b[2]; + refill_buffy(rem); + aring_peek( rem, b, 2, 0); + + if ( b[0] == 0xff && (b[1] & 0xf8) == 0xf8) + found = 2; + else { + off++; + aring_read( rem, b, 1); + } + } + + if (!found) return -1; + rem->aframe = rem->aframen-1; + + if (aring_peek(rem, buf, 3, 0) < 1) return -1; + headr = buf+2; + + ai->layer = (buf[1] & 0x06) >> 1; + + fprintf(stderr,"Audiostream: Layer: %d", 4-ai->layer); + + + ai->bit_rate = bitrates[(3-ai->layer)][(headr[0] >> 4 )]*1000; + + if (ai->bit_rate == 0) + fprintf (stderr," Bit rate: free"); + else if (ai->bit_rate == 0xf) + fprintf (stderr," BRate: reserved"); + else + fprintf (stderr," BRate: %d kb/s", ai->bit_rate/1000); + + + ai->frequency = (headr[0] & 0x0c ) >> 2; + if (ai->frequency == 3) + fprintf (stderr, " Freq: reserved\n"); + else + fprintf (stderr," Freq: %2.1f kHz\n", + freq[ai->frequency]/10.); + + return 0; +} + + + +void init_remux(Remux *rem, int fin, int fout, int mult) +{ + rem->video_info.framerate = 0; + rem->audio_info.layer = -1; + rem->fin = fin; + rem->fout = fout; + ring_init(&rem->vid_buffy, 40*BUFFYSIZE*mult); + ring_init(&rem->aud_buffy, BUFFYSIZE*mult); + init_ptsl(rem->vpts_list); + init_ptsl(rem->apts_list); + init_framel(rem->vframe_list); + init_framel(rem->aframe_list); + + rem->vptsn = 0; + rem->aptsn = 0; + rem->vframen = 0; + rem->aframen = 0; + rem->vframe = 0; + rem->aframe = 0; + rem->vcframe = 0; + rem->acframe = 0; + rem->vpts = 0; + rem->vdts = 0; + rem->apts_off = 0; + rem->vpts_off = 0; + rem->apts_delay= 0; + rem->vpts_delay= 0; + rem->dts_delay = 0; + rem->apts = 0; + rem->vpes = 0; + rem->apes = 0; + rem->vpts_old = 0; + rem->apts_old = 0; + rem->SCR = 0; + rem->vwrite = 0; + rem->awrite = 0; + rem->vread = 0; + rem->aread = 0; + rem->group = 0; + rem->groupframe= 0; + rem->pack_size = 0; + rem->muxr = 0; + rem->time_off = 0; +} + +uint32_t bytes2pts(int bytes, int rate) +{ + if (bytes < 0xFFFFFFFFUL/720000UL) + return (uint32_t)(bytes*720000UL/rate); + else + return (uint32_t)(bytes/rate*720000UL); +} + +long pts2bytes( uint32_t pts, int rate) +{ + if (pts < 0xEFFFFFFFUL/rate) + return (pts*rate/720000); + else + return (pts* (rate/720000)); +} + +int write_audio_pes( Remux *rem, uint8_t *buf, int *alength) +{ + int add; + int pos = 0; + int p = 0; + uint32_t pts = 0; + int stuff = 0; + int length = *alength; + + if (!length) return 0; + p = PS_HEADER_L1+PES_H_MIN; + + if (rem->apts_old != rem->apts){ + pts = (uint32_t)((uint64_t)rem->apts + rem->apts_delay - rem->apts_off); + p += 5; + } + if ( length+p >= rem->pack_size){ + length = rem->pack_size; + } else { + if (rem->pack_size-length-p <= PES_MIN){ + stuff = rem->pack_size - length; + length = rem->pack_size; + } else + length = length+p; + } + pos = write_ps_header(buf,rem->SCR,rem->muxr, 1, 0, 0, 1, 1, 1, + 0, 0, 0, 0, 0, 0); + + pos += write_pes_header( 0xC0, length-pos, pts, buf+pos, stuff); + add = aring_read( rem, buf+pos, length-pos); + *alength = add; + if (add < 0) return -1; + pos += add; + rem->apts_old = rem->apts; + rem->apts = rem->apts_list[0].PTS; + + if (pos+PES_MIN < rem->pack_size){ + pos += write_pes_header( PADDING_STREAM, rem->pack_size-pos, 0, + buf+pos, 0); + pos = rem->pack_size; + } + if (pos != rem->pack_size) { + fprintf(stderr,"apos: %d\n",pos); + exit(1); + } + + return pos; +} + +int write_video_pes( Remux *rem, uint8_t *buf, int *vlength) +{ + int add; + int pos = 0; + int p = 0; + uint32_t pts = 0; + uint32_t dts = 0; + int stuff = 0; + int length = *vlength; + long diff = 0; + + if (! length) return 0; + p = PS_HEADER_L1+PES_H_MIN; + + if (rem->vpts_old != rem->vpts){ + pts = (uint32_t)((uint64_t)rem->vpts + rem->vpts_delay - rem->vpts_off); + p += 5; + } + if ( length+p >= rem->pack_size){ + length = rem->pack_size; + } else { + if (rem->pack_size - length - p <= PES_MIN){ + stuff = rem->pack_size - length; + length = rem->pack_size; + } else + length = length+p; + } + + pos = write_ps_header(buf,rem->SCR,rem->muxr, 1, 0, 0, 1, 1, 1, + 0, 0, 0, 0, 0, 0); + + pos += write_pes_header( 0xE0, length-pos, pts, buf+pos, stuff); + add = vring_read( rem, buf+pos, length-pos); + *vlength = add; + if (add < 0) return -1; + pos += add; + rem->vpts_old = rem->vpts; + dts = rem->vdts; + rem->vpts = rem->vpts_list[0].PTS; + rem->vdts = rem->vpts_list[0].dts; + if ( diff > 0) rem->SCR += diff; + if (pos+PES_MIN < rem->pack_size){ + // fprintf(stderr,"vstuffing: %d \n",rem->pack_size-pos); + pos += write_pes_header( PADDING_STREAM, rem->pack_size-pos, 0, + buf+pos, 0); + pos = rem->pack_size; + } + return pos; +} + +void print_info( Remux *rem , int ret) +{ + int newtime = 0; + static int time = 0; + int i = 0; + + while(! newtime && i < rem->vframen) { + if( (newtime = rem->vframe_list[i].time)) break; + i++; + } + if (newtime) time = newtime; + + fprintf(stderr,"SCR:"); + printpts(rem->SCR); + fprintf(stderr," VDTS:"); + printpts((uint32_t)((uint64_t)rem->vdts - rem->vpts_off + rem->vpts_delay)); + fprintf(stderr," APTS:"); + printpts((uint32_t)((uint64_t)rem->apts - rem->apts_off + rem->apts_delay)); + fprintf(stderr," TIME:%2d:", time/3600); + fprintf(stderr,"%02d:", (time%3600)/60); + fprintf(stderr,"%02d", (time%3600)%60); + if (ret) fprintf(stderr,"\n"); + else fprintf(stderr,"\r"); +} + +void remux(int fin, int fout, int pack_size, int mult) +{ + Remux rem; + long ptsdiff; + uint8_t buf[MAX_PACK_L]; + long pos = 0; + int r = 0; + int i, r1, r2; + long packets = 0; + uint8_t mpeg_end[4] = { 0x00, 0x00, 0x01, 0xB9 }; + uint32_t SCR_inc = 0; + int data_size; + long vbuf, abuf; + long vbuf_max, abuf_max; + PTS_List abufl[MAX_PTS]; + PTS_List vbufl[MAX_PTS]; + int abufn = 0; + int vbufn = 0; + uint64_t pts_d = 0; + int ok_audio; + int ok_video; + uint32_t apos = 0; + uint32_t vpos = 0; + int vpack_size = 0; + int apack_size = 0; + + init_ptsl(abufl); + init_ptsl(vbufl); + + if (mult < 0 || mult >1000){ + fprintf(stderr,"Multipler too large\n"); + exit(1); + } + init_remux(&rem, fin, fout, mult); + rem.pack_size = pack_size; + data_size = pack_size - MAX_H_SIZE; + fprintf(stderr,"pack_size: %d header_size: %d data size: %d\n", + pack_size, MAX_H_SIZE, data_size); + refill_buffy(&rem); + fprintf(stderr,"Package size: %d\n",pack_size); + + if ( get_video_info(&rem) < 0 ){ + fprintf(stderr,"ERROR: Can't find valid video stream\n"); + exit(1); + } + + i = 0; + while(! rem.time_off && i < rem.vframen) { + if( (rem.time_off = rem.vframe_list[i].time)) break; + i++; + } + + if ( get_audio_info(&rem) < 0 ){ + fprintf(stderr,"ERROR: Can't find valid audio stream\n"); + exit(1); + } + + rem.vpts = rem.vpts_list[0].PTS; + rem.vdts = rem.vpts; + rem.vpts_off = rem.vpts; + fprintf(stderr,"Video start PTS = %fs \n",rem.vpts_off/90000.); + rem.apts = rem.apts_list[0].PTS; + rem.apts_off = rem.apts; + ptsdiff = rem.vpts - rem.apts; + if (ptsdiff > 0) rem.vpts_off -= ptsdiff; + else rem.apts_off -= -ptsdiff; + fprintf(stderr,"Audio start PTS = %fs\n",rem.apts_off/90000.); + fprintf(stderr,"Difference Video - Audio = %fs\n",ptsdiff/90000.); + fprintf(stderr,"Time offset = %ds\n",rem.time_off); + + rem.muxr = (rem.video_info.bit_rate + + rem.audio_info.bit_rate)/400; + fprintf(stderr,"MUXRATE: %.2f Mb/sec\n",rem.muxr/2500.); + SCR_inc = 1800 * pack_size / rem.muxr; + + r = 0; + while ( rem.vptsn < 2 && !r) r = refill_buffy(&rem); + r = 0; + while ( rem.aptsn < 2 && !r) r = refill_buffy(&rem); + + //rem.vpts_delay = (uint32_t)(2*90000ULL* (uint64_t)pack_size/rem.muxr); + rem.vpts_delay = rem.dts_delay; + rem.apts_delay = rem.vpts_delay; + + vbuf_max = 29440; + abuf_max = 4096; + vbuf = 0; + abuf = 0; + pos = write_ps_header(buf,rem.SCR,rem.muxr, 1, 0, 0, 1, 1, 1, + 0xC0, 0, 32, 0xE0, 1, 230); + pos += write_pes_header( PADDING_STREAM, pack_size-pos, 0, buf+pos,0); + pos = rem.pack_size; + write( fout, buf, pos); + + apos = rem.aread; + vpos = rem.vread; + print_info( &rem, 1 ); + + while( ring_rest(&rem.aud_buffy) && ring_rest(&rem.vid_buffy) ){ + uint32_t next_apts; + uint32_t next_vdts; + int asize, vsize; + + r1 = 0; + r2 = 0; + while ( rem.aframen < 2 && !r1) + r1 = refill_buffy(&rem); + while ( rem.vframen < 2 && !r2) + r2 = refill_buffy(&rem); + if (r1 && r2) break; + + if ( !r1 && apos <= rem.aread) + apos = rem.aframe_list[1].pos; + if ( !r2 && vpos <= rem.vread) + vpos = rem.vframe_list[1].pos; + apack_size = apos - rem.aread; + vpack_size = vpos - rem.vread; + + + next_vdts = (uint32_t)((uint64_t)rem.vdts + rem.vpts_delay + - rem.vpts_off) ; + ok_video = ( rem.SCR < next_vdts); + + next_apts = (uint32_t)((uint64_t)rem.apts + rem.apts_delay + - rem.apts_off) ; + ok_audio = ( rem.SCR < next_apts); + + asize = (apack_size > data_size ? data_size: apack_size); + vsize = (vpack_size > data_size ? data_size: vpack_size); + + fprintf(stderr,"vframen: %d aframen: %d v_ok: %d a_ok: %d v_buf: %d a_buf: %d vpacks: %d apacks: %d\n",rem.vframen,rem.aframen, ok_video, ok_audio, (int)vbuf,(int)abuf,vsize, asize); + + + if( vbuf+vsize < vbuf_max && vsize && ok_audio ){ + fprintf(stderr,"1 "); + pos = write_video_pes( &rem, buf, &vpack_size); + write( fout, buf, pos); + vbuf += vpack_size; + vbufn = add_pts( vbufl, rem.vdts, vpack_size, + 0, vbufn, 0); + packets++; + } else if ( abuf+asize < abuf_max && asize && + ok_video ){ + fprintf(stderr,"2 "); + pos = write_audio_pes( &rem, buf, &apack_size); + write( fout, buf, pos); + abuf += apack_size; + abufn = add_pts( abufl, rem.apts, apack_size, + 0, abufn, 0); + packets++; + } else if ( abuf+asize < abuf_max && asize && + !ok_audio){ + fprintf(stderr,"3 "); + pos = write_audio_pes( &rem, buf, &apack_size); + write( fout, buf, pos); + abuf += apack_size; + abufn = add_pts( abufl, rem.apts, apack_size, + 0, abufn, 0); + packets++; + } else if (vbuf+vsize < vbuf_max && vsize && + !ok_video){ + fprintf(stderr,"4 "); + pos = write_video_pes( &rem, buf, &vpack_size); + write( fout, buf, pos); + vbuf += vpack_size; + vbufn = add_pts( vbufl, rem.vdts, vpack_size, + 0, vbufn, 0); + packets++; + } else { + fprintf(stderr,"5 "); + pos = write_ps_header(buf,rem.SCR,rem.muxr, 1, 0, 0, + 1, 1, 1, 0, 0, 0, 0, 0, 0); + + pos += write_pes_header( PADDING_STREAM, pack_size-pos, + 0, buf+pos, 0); + write( fout, buf, pos); + } + + + //fprintf(stderr,"vbufn: %d abufn: %d ", vbufn,abufn); + //fprintf(stderr,"vbuf: %5d abuf: %4d\n", vbuf,abuf); + + if (rem.SCR > rem.vdts+rem.vpts_off -rem.vpts_delay) + rem.SCR = rem.vdts-rem.vpts_off; + rem.SCR = (uint32_t)((uint64_t) rem.SCR + SCR_inc); + + if ( rem.apts_off + rem.SCR < rem.apts_delay ) pts_d = 0; + else pts_d = (uint64_t) rem.SCR + rem.apts_off - rem.apts_delay; + abuf -= del_ptss( abufl, (uint32_t) pts_d, &abufn); + + if ( rem.vpts_off + rem.SCR < rem.vpts_delay ) pts_d = 0; + else pts_d = (uint64_t) rem.SCR + rem.vpts_off - rem.vpts_delay; + vbuf -= del_ptss( vbufl, (uint32_t) pts_d, &vbufn); + + print_info( &rem, 1); + //fprintf(stderr,"vbufn: %d abufn: %d ", vbufn,abufn); + //fprintf(stderr,"vbuf: %5d abuf: %4d\n\n", vbuf,abuf); + + + } + pos = write_ps_header(buf,rem.SCR,rem.muxr, 1, 0, 0, 1, 1, 1, + 0, 0, 0, 0, 0, 0); + + pos += write_pes_header( PADDING_STREAM, pack_size-pos-4, 0, + buf+pos, 0); + pos = rem.pack_size-4; + write( fout, buf, pos); + + write( fout, mpeg_end, 4); + fprintf(stderr,"\ndone\n"); +} + + +typedef +struct pes_buffer_s{ + ringbuffy pes_buffy; + uint8_t type; + PTS_List pts_list[MAX_PTS]; + FRAME_List frame_list[MAX_FRAME]; + int pes_size; + uint64_t written; + uint64_t read; +} PESBuffer; + + +void init_PESBuffer(PESBuffer *pbuf, int pes_size, int buf_size, uint8_t type) +{ + init_framel( pbuf->frame_list); + init_ptsl( pbuf->pts_list); + ring_init( &pbuf->pes_buffy, buf_size); + pbuf->pes_size = pes_size; + pbuf->type = type; + pbuf->written = 0; + pbuf->read = 0; +} + + +#define MAX_PBUF 4 + +typedef +struct remux_s{ + PESBuffer pbuf_list[MAX_PBUF]; + int num_pbuf; +} REMUX; + + +void init_REMUX(REMUX *rem) +{ + rem->num_pbuf = 0; +} + + + +#define REPACK 2048 +#define ABUF_SIZE REPACK*1024 +#define VBUF_SIZE REPACK*10240 + +void remux_main(uint8_t *buf, int count, void *pr) +{ + int i, b; + int bufsize = 0; + p2p *p = (p2p *) pr; + PESBuffer *pbuf; + REMUX *rem = (REMUX *) p->data; + uint8_t type = buf[3]; + int *npbuf = &(rem->num_pbuf); + + switch ( type ){ + case PRIVATE_STREAM1: + bufsize = ABUF_SIZE; + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + if (!bufsize) bufsize = VBUF_SIZE; + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + if (!bufsize) bufsize = ABUF_SIZE; + b = -1; + for ( i = 0; i < *npbuf; i++){ + if ( type == rem->pbuf_list[i].type ){ + b = i; + break; + } + } + if (b < 0){ + if ( *npbuf < MAX_PBUF ){ + init_PESBuffer(&rem->pbuf_list[*npbuf], + p->repack+6, bufsize, type); + b = *npbuf; + (*npbuf)++; + } else { + fprintf(stderr,"Not enough PES buffers\n"); + exit(1); + } + } + break; + default: + return; + } + + pbuf = &(rem->pbuf_list[b]); + if (ring_write(&(pbuf->pes_buffy),(char *)buf,count) != count){ + fprintf(stderr,"buffer overflow type 0x%2x\n",type); + exit(1); + } else { + pbuf->written += count; + if ((p->flag2 & PTS_DTS_FLAGS)){ + uint32_t PTS = trans_pts_dts(p->pts); + add_pts(pbuf->pts_list, PTS, pbuf->written, + pbuf->written, 0, 0); + } + p->flag2 = 0; + } + +} + +void output_mux(p2p *p) +{ + int i, filling; + PESBuffer *pbuf; + ringbuffy *pes_buffy; + REMUX *rem = (REMUX *) p->data; + int repack = p->repack; + int npbuf = rem->num_pbuf; + + for ( i = 0; i < npbuf; i++){ + pbuf = &(rem->pbuf_list[i]); + pes_buffy = &pbuf->pes_buffy; + filling = pes_buffy->size - ring_rest(pes_buffy); + if (filling/(2 *repack)){ + pbuf->read += ring_read_file(pes_buffy, p->fd1, + (filling/repack)*repack); + } + } +} + + + +#define SIZE 32768 + +void remux2(int fdin, int fdout) +{ + p2p p; + int count = 1; + uint8_t buf[SIZE]; + uint64_t length = 0; + uint64_t l = 0; + int verb = 0; + REMUX rem; + + init_p2p(&p, remux_main, REPACK); + p.fd1 = fdout; + p.data = (void *) &rem; + + + if (fdin != STDIN_FILENO) verb = 1; + + if (verb) { + length = lseek(fdin, 0, SEEK_END); + lseek(fdin,0,SEEK_SET); + } + + while (count > 0){ + count = read(fdin,buf,SIZE); + l += count; + if (verb) + fprintf(stderr,"Writing %2.2f %%\r", + 100.*l/length); + + get_pes(buf,count,&p,pes_repack); + output_mux(&p); + } + +} diff --git a/plugins/streamdev/streamdev-cvs/libdvbmpeg/remux.h b/plugins/streamdev/streamdev-cvs/libdvbmpeg/remux.h new file mode 100644 index 0000000..76c128b --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/libdvbmpeg/remux.h @@ -0,0 +1,149 @@ +/* + * dvb-mpegtools for the Siemens Fujitsu DVB PCI card + * + * Copyright (C) 2000, 2001 Marcus Metzler + * for convergence integrated media GmbH + * Copyright (C) 2002 Marcus Metzler + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + + * The author can be reached at mocm@metzlerbros.de, + */ + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> +#include <netinet/in.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/ioctl.h> +//#include <libgen.h> +#include <stdint.h> + +#include "ringbuffy.h" +#include "ctools.h" + +#ifndef _REMUX_H_ +#define _REMUX_H_ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + typedef struct video_i{ + uint32_t horizontal_size; + uint32_t vertical_size ; + uint32_t aspect_ratio ; + double framerate ; + uint32_t video_format; + uint32_t bit_rate ; + uint32_t comp_bit_rate ; + uint32_t vbv_buffer_size; + uint32_t CSPF ; + uint32_t off; + } VideoInfo; + + typedef struct audio_i{ + int layer; + uint32_t bit_rate; + uint32_t frequency; + uint32_t mode; + uint32_t mode_extension; + uint32_t emphasis; + uint32_t framesize; + uint32_t off; + } AudioInfo; + + + + typedef + struct PTS_list_struct{ + uint32_t PTS; + int pos; + uint32_t dts; + int spos; + } PTS_List; + + typedef + struct frame_list_struct{ + int type; + int pos; + uint32_t FRAME; + uint32_t time; + uint32_t pts; + uint32_t dts; + } FRAME_List; + + typedef + struct remux_struct{ + ringbuffy vid_buffy; + ringbuffy aud_buffy; + PTS_List vpts_list[MAX_PTS]; + PTS_List apts_list[MAX_PTS]; + FRAME_List vframe_list[MAX_FRAME]; + FRAME_List aframe_list[MAX_FRAME]; + int vptsn; + int aptsn; + int vframen; + int aframen; + long apes; + long vpes; + uint32_t vframe; + uint32_t aframe; + uint32_t vcframe; + uint32_t acframe; + uint32_t vpts; + uint32_t vdts; + uint32_t apts; + uint32_t vpts_old; + uint32_t apts_old; + uint32_t SCR; + uint32_t apts_off; + uint32_t vpts_off; + uint32_t apts_delay; + uint32_t vpts_delay; + uint32_t dts_delay; + AudioInfo audio_info; + VideoInfo video_info; + int fin; + int fout; + long int awrite; + long int vwrite; + long int aread; + long int vread; + uint32_t group; + uint32_t groupframe; + uint32_t muxr; + int pack_size; + uint32_t time_off; + } Remux; + + enum { NONE, I_FRAME, P_FRAME, B_FRAME, D_FRAME }; + + void remux(int fin, int fout, int pack_size, int mult); + void remux2(int fdin, int fdout); +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /*_REMUX_H_*/ diff --git a/plugins/streamdev/streamdev-cvs/libdvbmpeg/ringbuffy.c b/plugins/streamdev/streamdev-cvs/libdvbmpeg/ringbuffy.c new file mode 100644 index 0000000..965e49a --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/libdvbmpeg/ringbuffy.c @@ -0,0 +1,201 @@ +/* + Ringbuffer Implementation for gtvscreen + + Copyright (C) 2000 Marcus Metzler (mocm@metzlerbros.de) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "ringbuffy.h" +#include <string.h> + +int ring_init (ringbuffy *rbuf, int size) +{ + if (size > 0){ + rbuf->size = size; + if( !(rbuf->buffy = (char *) malloc(sizeof(char)*size)) ){ + fprintf(stderr,"Not enough memory for ringbuffy\n"); + return -1; + } + } else { + fprintf(stderr,"Wrong size for ringbuffy\n"); + return -1; + } + rbuf->read_pos = 0; + rbuf->write_pos = 0; + return 0; +} + + +void ring_destroy(ringbuffy *rbuf) +{ + free(rbuf->buffy); +} + + +int ring_write(ringbuffy *rbuf, char *data, int count) +{ + + int diff, free, pos, rest; + + if (count <=0 ) return 0; + pos = rbuf->write_pos; + rest = rbuf->size - pos; + diff = rbuf->read_pos - pos; + free = (diff > 0) ? diff-1 : rbuf->size+diff-1; + + if ( free <= 0 ) return FULL_BUFFER; + if ( free < count ) count = free; + + if (count >= rest){ + memcpy (rbuf->buffy+pos, data, rest); + if (count - rest) + memcpy (rbuf->buffy, data+rest, count - rest); + rbuf->write_pos = count - rest; + } else { + memcpy (rbuf->buffy+pos, data, count); + rbuf->write_pos += count; + } + + return count; +} + + + + +int ring_peek(ringbuffy *rbuf, char *data, int count, long off) +{ + + int diff, free, pos, rest; + + if (count <=0 ) return 0; + pos = rbuf->read_pos+off; + rest = rbuf->size - pos ; + diff = rbuf->write_pos - pos; + free = (diff >= 0) ? diff : rbuf->size+diff; + + if ( free <= 0 ) return FULL_BUFFER; + if ( free < count ) count = free; + + if ( count < rest ){ + memcpy(data, rbuf->buffy+pos, count); + } else { + memcpy(data, rbuf->buffy+pos, rest); + if ( count - rest) + memcpy(data+rest, rbuf->buffy, count - rest); + } + + return count; +} + +int ring_read(ringbuffy *rbuf, char *data, int count) +{ + + int diff, free, pos, rest; + + if (count <=0 ) return 0; + pos = rbuf->read_pos; + rest = rbuf->size - pos; + diff = rbuf->write_pos - pos; + free = (diff >= 0) ? diff : rbuf->size+diff; + + if ( rest <= 0 ) return 0; + if ( free < count ) count = free; + + if ( count < rest ){ + memcpy(data, rbuf->buffy+pos, count); + rbuf->read_pos += count; + } else { + memcpy(data, rbuf->buffy+pos, rest); + if ( count - rest) + memcpy(data+rest, rbuf->buffy, count - rest); + rbuf->read_pos = count - rest; + } + + return count; +} + + + +int ring_write_file(ringbuffy *rbuf, int fd, int count) +{ + + int diff, free, pos, rest, rr; + + if (count <=0 ) return 0; + pos = rbuf->write_pos; + rest = rbuf->size - pos; + diff = rbuf->read_pos - pos; + free = (diff > 0) ? diff-1 : rbuf->size+diff-1; + + if ( rest <= 0 ) return 0; + if ( free < count ) count = free; + + if (count >= rest){ + rr = read (fd, rbuf->buffy+pos, rest); + if (rr == rest && count - rest) + rr += read (fd, rbuf->buffy, count - rest); + if (rr >=0) + rbuf->write_pos = (pos + rr) % rbuf->size; + } else { + rr = read (fd, rbuf->buffy+pos, count); + if (rr >=0) + rbuf->write_pos += rr; + } + + return rr; +} + + + +int ring_read_file(ringbuffy *rbuf, int fd, int count) +{ + + int diff, free, pos, rest, rr; + + if (count <=0 ) return 0; + pos = rbuf->read_pos; + rest = rbuf->size - pos; + diff = rbuf->write_pos - pos; + free = (diff >= 0) ? diff : rbuf->size+diff; + + if ( free <= 0 ) return FULL_BUFFER; + if ( free < count ) count = free; + + if (count >= rest){ + rr = write (fd, rbuf->buffy+pos, rest); + if (rr == rest && count - rest) + rr += write (fd, rbuf->buffy, count - rest); + if (rr >=0) + rbuf->read_pos = (pos + rr) % rbuf->size; + } else { + rr = write (fd, rbuf->buffy+pos, count); + if (rr >=0) + rbuf->read_pos += rr; + } + + + return rr; +} + +int ring_rest(ringbuffy *rbuf){ + int diff, free, pos, rest; + pos = rbuf->read_pos; + rest = rbuf->size - pos; + diff = rbuf->write_pos - pos; + free = (diff >= 0) ? diff : rbuf->size+diff; + + return free; +} diff --git a/plugins/streamdev/streamdev-cvs/libdvbmpeg/ringbuffy.h b/plugins/streamdev/streamdev-cvs/libdvbmpeg/ringbuffy.h new file mode 100644 index 0000000..16011d7 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/libdvbmpeg/ringbuffy.h @@ -0,0 +1,52 @@ +/* + Ringbuffer Implementation for gtvscreen + + Copyright (C) 2000 Marcus Metzler (mocm@metzlerbros.de) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef RINGBUFFY_H +#define RINGBUFFY_H + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#define FULL_BUFFER -1000 +typedef struct ringbuffy{ + int read_pos; + int write_pos; + int size; + char *buffy; +} ringbuffy; + +int ring_init (ringbuffy *rbuf, int size); +void ring_destroy(ringbuffy *rbuf); +int ring_write(ringbuffy *rbuf, char *data, int count); +int ring_read(ringbuffy *rbuf, char *data, int count); +int ring_write_file(ringbuffy *rbuf, int fd, int count); +int ring_read_file(ringbuffy *rbuf, int fd, int count); +int ring_rest(ringbuffy *rbuf); +int ring_peek(ringbuffy *rbuf, char *data, int count, long off); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* RINGBUFFY_H */ diff --git a/plugins/streamdev/streamdev-cvs/libdvbmpeg/transform.c b/plugins/streamdev/streamdev-cvs/libdvbmpeg/transform.c new file mode 100644 index 0000000..c53f1fb --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/libdvbmpeg/transform.c @@ -0,0 +1,2681 @@ +/* + * dvb-mpegtools for the Siemens Fujitsu DVB PCI card + * + * Copyright (C) 2000, 2001 Marcus Metzler + * for convergence integrated media GmbH + * Copyright (C) 2002 Marcus Metzler + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + + * The author can be reached at marcus@convergence.de, + + * the project's page is at http://linuxtv.org/dvb/ + */ + + +#include "transform.h" +#include <stdlib.h> +#include <string.h> +#include "ctools.h" + +static uint8_t tspid0[TS_SIZE] = { + 0x47, 0x40, 0x00, 0x10, 0x00, 0x00, 0xb0, 0x11, + 0x00, 0x00, 0xcb, 0x00, 0x00, 0x00, 0x00, 0xe0, + 0x10, 0x00, 0x01, 0xe4, 0x00, 0x2a, 0xd6, 0x1a, + 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff +}; + +static uint8_t tspid1[TS_SIZE] = { + 0x47, 0x44, 0x00, 0x10, 0x00, 0x02, 0xb0, 0x1c, + 0x00, 0x01, 0xcb, 0x00, 0x00, 0xe0, 0xa0, 0xf0, + 0x05, 0x48, 0x03, 0x01, 0x00, 0x00, 0x02, 0xe0, + 0xa0, 0xf0, 0x00, 0x03, 0xe0, 0x50, 0xf0, 0x00, + 0xae, 0xea, 0x4e, 0x48, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff +}; + +// CRC32 lookup table for polynomial 0x04c11db7 +static const uint32_t crc_table[256] = { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, + 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, + 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef, + 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, + 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, + 0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, + 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, + 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050, + 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1, + 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, + 0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9, + 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, + 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, + 0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, + 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, + 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676, + 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 +}; + +static uint32_t +calc_crc32 (const uint8_t *sec, uint8_t len) +{ + int i; + uint32_t crc = 0xffffffff; + + for (i = 0; i < len; i++) + crc = (crc << 8) ^ crc_table[((crc >> 24) ^ *sec++) & 0xff]; + + return crc; +} + + +uint64_t trans_pts_dts(uint8_t *pts) +{ + uint64_t wts; + + wts = ((uint64_t)((pts[0] & 0x0E) << 5) | + ((pts[1] & 0xFC) >> 2)) << 24; + wts |= (((pts[1] & 0x03) << 6) | + ((pts[2] & 0xFC) >> 2)) << 16; + wts |= (((pts[2] & 0x02) << 6) | + ((pts[3] & 0xFE) >> 1)) << 8; + wts |= (((pts[3] & 0x01) << 7) | + ((pts[4] & 0xFE) >> 1)); + return wts; +} + + +void get_pespts(uint8_t *av_pts,uint8_t *pts) +{ + + pts[0] = 0x21 | + ((av_pts[0] & 0xC0) >>5); + pts[1] = ((av_pts[0] & 0x3F) << 2) | + ((av_pts[1] & 0xC0) >> 6); + pts[2] = 0x01 | ((av_pts[1] & 0x3F) << 2) | + ((av_pts[2] & 0x80) >> 6); + pts[3] = ((av_pts[2] & 0x7F) << 1) | + ((av_pts[3] & 0x80) >> 7); + pts[4] = 0x01 | ((av_pts[3] & 0x7F) << 1); +} + +uint16_t get_pid(uint8_t *pid) +{ + uint16_t pp = 0; + + pp = (pid[0] & PID_MASK_HI)<<8; + pp |= pid[1]; + + return pp; +} + +int write_ts_header(uint16_t pid, uint8_t *counter, int pes_start, + uint8_t *buf, uint8_t length) +{ + int i; + int c = 0; + int fill; + uint8_t tshead[4] = { 0x47, 0x00, 0x00, 0x10}; + + + fill = TS_SIZE-4-length; + if (pes_start) tshead[1] = 0x40; + if (fill) tshead[3] = 0x30; + tshead[1] |= (uint8_t)((pid & 0x1F00) >> 8); + tshead[2] |= (uint8_t)(pid & 0x00FF); + tshead[3] |= ((*counter)++ & 0x0F) ; + memcpy(buf,tshead,4); + c+=4; + + + if (fill){ + buf[4] = fill-1; + c++; + if (fill >1){ + buf[5] = 0x00; + c++; + } + for ( i = 6; i < fill+4; i++){ + buf[i] = 0xFF; + c++; + } + } + + return c; +} + + +int write_pes_header(uint8_t id,int length , long PTS, uint8_t *obuf, + int stuffing) +{ + uint8_t le[2]; + uint8_t dummy[3]; + uint8_t *pts; + uint8_t ppts[5]; + long lpts; + int c; + uint8_t headr[3] = {0x00, 0x00, 0x01}; + + lpts = htonl(PTS); + pts = (uint8_t *) &lpts; + + get_pespts(pts,ppts); + + c = 0; + memcpy(obuf+c,headr,3); + c += 3; + memcpy(obuf+c,&id,1); + c++; + + le[0] = 0; + le[1] = 0; + length -= 6+stuffing; + + le[0] |= ((uint8_t)(length >> 8) & 0xFF); + le[1] |= ((uint8_t)(length) & 0xFF); + memcpy(obuf+c,le,2); + c += 2; + + if (id == PADDING_STREAM){ + memset(obuf+c,0xff,length); + c+= length; + return c; + } + + dummy[0] = 0x80; + dummy[1] = 0; + dummy[2] = 0; + if (PTS){ + dummy[1] |= PTS_ONLY; + dummy[2] = 5+stuffing; + } + memcpy(obuf+c,dummy,3); + c += 3; + memset(obuf+c,0xFF,stuffing); + + if (PTS){ + memcpy(obuf+c,ppts,5); + c += 5; + } + + return c; +} + + +void init_p2p(p2p *p, void (*func)(uint8_t *buf, int count, void *p), + int repack){ + p->found = 0; + p->cid = 0; + p->mpeg = 0; + memset(p->buf,0,MMAX_PLENGTH); + p->done = 0; + p->fd1 = -1; + p->func = func; + p->bigend_repack = 0; + p->repack = 0; + if ( repack < MAX_PLENGTH && repack > 265 ){ + p->repack = repack-6; + p->bigend_repack = (uint16_t)htons((short) + ((repack-6) & 0xFFFF)); + } else { + fprintf(stderr, "Repack size %d is out of range\n",repack); + exit(1); + } +} + + + +void pes_repack(p2p *p) +{ + int count = 0; + int repack = p->repack; + int rest = p->plength; + uint8_t buf[MAX_PLENGTH]; + int bfill = 0; + int diff; + uint16_t length; + + if (rest < 0) { + fprintf(stderr,"Error in repack\n"); + return; + } + + if (!repack){ + fprintf(stderr,"forgot to set repack size\n"); + return; + } + + if (p->plength == repack){ + memcpy(p->buf+4,(char *)&p->bigend_repack,2); + p->func(p->buf, repack+6, p); + return; + } + + buf[0] = 0x00; + buf[1] = 0x00; + buf[2] = 0x01; + buf[3] = p->cid; + memcpy(buf+4,(char *)&p->bigend_repack,2); + memset(buf+6,0,MAX_PLENGTH-6); + + if (p->mpeg == 2){ + + if ( rest > repack){ + memcpy(p->buf+4,(char *)&p->bigend_repack,2); + p->func(p->buf, repack+6, p); + count += repack+6; + rest -= repack; + } else { + memcpy(buf,p->buf,9+p->hlength); + bfill = p->hlength; + count += 9+p->hlength; + rest -= p->hlength+3; + } + + while (rest >= repack-3){ + memset(buf+6,0,MAX_PLENGTH-6); + buf[6] = 0x80; + buf[7] = 0x00; + buf[8] = 0x00; + memcpy(buf+9,p->buf+count,repack-3); + rest -= repack-3; + count += repack-3; + p->func(buf, repack+6, p); + } + + if (rest){ + diff = repack - 3 - rest - bfill; + if (!bfill){ + buf[6] = 0x80; + buf[7] = 0x00; + buf[8] = 0x00; + } + + if ( diff < PES_MIN){ + length = rest+ diff + bfill+3; + buf[4] = (uint8_t)((length & 0xFF00) >> 8); + buf[5] = (uint8_t)(length & 0x00FF); + buf[8] = (uint8_t)(bfill+diff); + memset(buf+9+bfill,0xFF,diff); + memcpy(buf+9+bfill+diff,p->buf+count,rest); + } else { + length = rest+ bfill+3; + buf[4] = (uint8_t)((length & 0xFF00) >> 8); + buf[5] = (uint8_t)(length & 0x00FF); + memcpy(buf+9+bfill,p->buf+count,rest); + bfill += rest+9; + write_pes_header( PADDING_STREAM, diff, 0, + buf+bfill, 0); + } + p->func(buf, repack+6, p); + } + } + + if (p->mpeg == 1){ + + if ( rest > repack){ + memcpy(p->buf+4,(char *)&p->bigend_repack,2); + p->func(p->buf, repack+6, p); + count += repack+6; + rest -= repack; + } else { + memcpy(buf,p->buf,6+p->hlength); + bfill = p->hlength; + count += 6; + rest -= p->hlength; + } + + while (rest >= repack-1){ + memset(buf+6,0,MAX_PLENGTH-6); + buf[6] = 0x0F; + memcpy(buf+7,p->buf+count,repack-1); + rest -= repack-1; + count += repack-1; + p->func(buf, repack+6, p); + } + + + if (rest){ + diff = repack - 1 - rest - bfill; + + if ( diff < PES_MIN){ + length = rest+ diff + bfill+1; + buf[4] = (uint8_t)((length & 0xFF00) >> 8); + buf[5] = (uint8_t)(length & 0x00FF); + memset(buf+6,0xFF,diff); + if (!bfill){ + buf[6+diff] = 0x0F; + } + memcpy(buf+7+diff,p->buf+count,rest+bfill); + } else { + length = rest+ bfill+1; + buf[4] = (uint8_t)((length & 0xFF00) >> 8); + buf[5] = (uint8_t)(length & 0x00FF); + if (!bfill){ + buf[6] = 0x0F; + memcpy(buf+7,p->buf+count,rest); + bfill = rest+7; + } else { + memcpy(buf+6,p->buf+count,rest+bfill); + bfill += rest+6; + } + write_pes_header( PADDING_STREAM, diff, 0, + buf+bfill, 0); + } + p->func(buf, repack+6, p); + } + } +} + + + + + + + + +int filter_pes (uint8_t *buf, int count, p2p *p, int (*func)(p2p *p)) +{ + + int l; + unsigned short *pl; + int c=0; + int ret = 1; + + uint8_t headr[3] = { 0x00, 0x00, 0x01} ; + + while (c < count && (p->mpeg == 0 || + (p->mpeg == 1 && p->found < 7) || + (p->mpeg == 2 && p->found < 9)) + && (p->found < 5 || !p->done)){ + switch ( p->found ){ + case 0: + case 1: + if (buf[c] == 0x00) p->found++; + else { + if (p->fd1 >= 0) + write(p->fd1,buf+c,1); + p->found = 0; + } + c++; + break; + case 2: + if (buf[c] == 0x01) p->found++; + else if (buf[c] == 0){ + p->found = 2; + } else { + if (p->fd1 >= 0) + write(p->fd1,buf+c,1); + p->found = 0; + } + c++; + break; + case 3: + p->cid = 0; + switch (buf[c]){ + case PROG_STREAM_MAP: + case PRIVATE_STREAM2: + case PROG_STREAM_DIR: + case ECM_STREAM : + case EMM_STREAM : + case PADDING_STREAM : + case DSM_CC_STREAM : + case ISO13522_STREAM: + if (p->fd1 >= 0) + write(p->fd1,buf+c,1); + p->done = 1; + case PRIVATE_STREAM1: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + p->found++; + p->cid = buf[c]; + c++; + break; + default: + if (p->fd1 >= 0) + write(p->fd1,buf+c,1); + p->found = 0; + break; + } + break; + + + case 4: + if (count-c > 1){ + pl = (unsigned short *) (buf+c); + p->plength = ntohs(*pl); + p->plen[0] = buf[c]; + c++; + p->plen[1] = buf[c]; + c++; + p->found+=2; + } else { + p->plen[0] = buf[c]; + p->found++; + return 1; + } + break; + case 5: + p->plen[1] = buf[c]; + c++; + pl = (unsigned short *) p->plen; + p->plength = ntohs(*pl); + p->found++; + break; + + + case 6: + if (!p->done){ + p->flag1 = buf[c]; + c++; + p->found++; + if ( (p->flag1 & 0xC0) == 0x80 ) p->mpeg = 2; + else { + p->hlength = 0; + p->which = 0; + p->mpeg = 1; + p->flag2 = 0; + } + } + break; + + case 7: + if ( !p->done && p->mpeg == 2){ + p->flag2 = buf[c]; + c++; + p->found++; + } + break; + + case 8: + if ( !p->done && p->mpeg == 2){ + p->hlength = buf[c]; + c++; + p->found++; + } + break; + + default: + + break; + } + } + + if (!p->plength) p->plength = MMAX_PLENGTH-6; + + + if ( p->done || ((p->mpeg == 2 && p->found >= 9) || + (p->mpeg == 1 && p->found >= 7)) ){ + switch (p->cid){ + + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + case PRIVATE_STREAM1: + + memcpy(p->buf, headr, 3); + p->buf[3] = p->cid; + memcpy(p->buf+4,p->plen,2); + + if (p->mpeg == 2 && p->found == 9){ + p->buf[6] = p->flag1; + p->buf[7] = p->flag2; + p->buf[8] = p->hlength; + } + + if (p->mpeg == 1 && p->found == 7){ + p->buf[6] = p->flag1; + } + + + if (p->mpeg == 2 && (p->flag2 & PTS_ONLY) && + p->found < 14){ + while (c < count && p->found < 14){ + p->pts[p->found-9] = buf[c]; + p->buf[p->found] = buf[c]; + c++; + p->found++; + } + if (c == count) return 1; + } + + if (p->mpeg == 1 && p->which < 2000){ + + if (p->found == 7) { + p->check = p->flag1; + p->hlength = 1; + } + + while (!p->which && c < count && + p->check == 0xFF){ + p->check = buf[c]; + p->buf[p->found] = buf[c]; + c++; + p->found++; + p->hlength++; + } + + if ( c == count) return 1; + + if ( (p->check & 0xC0) == 0x40 && !p->which){ + p->check = buf[c]; + p->buf[p->found] = buf[c]; + c++; + p->found++; + p->hlength++; + + p->which = 1; + if ( c == count) return 1; + p->check = buf[c]; + p->buf[p->found] = buf[c]; + c++; + p->found++; + p->hlength++; + p->which = 2; + if ( c == count) return 1; + } + + if (p->which == 1){ + p->check = buf[c]; + p->buf[p->found] = buf[c]; + c++; + p->found++; + p->hlength++; + p->which = 2; + if ( c == count) return 1; + } + + if ( (p->check & 0x30) && p->check != 0xFF){ + p->flag2 = (p->check & 0xF0) << 2; + p->pts[0] = p->check; + p->which = 3; + } + + if ( c == count) return 1; + if (p->which > 2){ + if ((p->flag2 & PTS_DTS_FLAGS) + == PTS_ONLY){ + while (c < count && + p->which < 7){ + p->pts[p->which-2] = + buf[c]; + p->buf[p->found] = + buf[c]; + c++; + p->found++; + p->which++; + p->hlength++; + } + if ( c == count) return 1; + } else if ((p->flag2 & PTS_DTS_FLAGS) + == PTS_DTS){ + while (c < count && + p->which< 12){ + if (p->which< 7) + p->pts[p->which + -2] = + buf[c]; + p->buf[p->found] = + buf[c]; + c++; + p->found++; + p->which++; + p->hlength++; + } + if ( c == count) return 1; + } + p->which = 2000; + } + + } + + while (c < count && p->found < p->plength+6){ + l = count -c; + if (l+p->found > p->plength+6) + l = p->plength+6-p->found; + memcpy(p->buf+p->found, buf+c, l); + p->found += l; + c += l; + } + if(p->found == p->plength+6){ + if (func(p)){ + if (p->fd1 >= 0){ + write(p->fd1,p->buf, + p->plength+6); + } + } else ret = 0; + } + break; + } + + + if ( p->done ){ + if( p->found + count - c < p->plength+6){ + p->found += count-c; + c = count; + } else { + c += p->plength+6 - p->found; + p->found = p->plength+6; + } + } + + if (p->plength && p->found == p->plength+6) { + p->found = 0; + p->done = 0; + p->plength = 0; + memset(p->buf, 0, MAX_PLENGTH); + if (c < count) + return filter_pes(buf+c, count-c, p, func); + } + } + return ret; +} + + +#define SIZE 4096 + + +int audio_pes_filt(p2p *p) +{ + uint8_t off; + + switch(p->cid){ + case PRIVATE_STREAM1: + if ( p->cid == p->filter) { + off = 9+p->buf[8]; + if (p->buf[off] == p->subid){ + return 1; + } + } + break; + + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + if ( p->cid == p->filter) + return 1; + break; + + default: + return 1; + break; + } + return 0; +} + + +void filter_audio_from_pes(int fdin, int fdout, uint8_t id, uint8_t subid) +{ + p2p p; + int count = 1; + uint8_t buf[2048]; + + init_p2p(&p, NULL, 2048); + p.fd1 = -1; + p.filter = id; + p.subid = subid; + + while (count > 0){ + count = read(fdin,buf,2048); + if(filter_pes(buf,count,&p,audio_pes_filt)) + write(fdout,buf,2048); + } +} + + +void pes_filt(p2p *p) +{ + int factor = p->mpeg-1; + + if ( p->cid == p->filter) { + if (p->es) + write(p->fd1,p->buf+p->hlength+6+3*factor, + p->plength-p->hlength-3*factor); + else + write(p->fd1,p->buf,p->plength+6); + } +} + +void extract_from_pes(int fdin, int fdout, uint8_t id, int es) +{ + p2p p; + int count = 1; + uint8_t buf[SIZE]; + + init_p2p(&p, NULL, 2048); + p.fd1 = fdout; + p.filter = id; + p.es = es; + + while (count > 0){ + count = read(fdin,buf,SIZE); + get_pes(buf,count,&p,pes_filt); + } +} + + +void pes_dfilt(p2p *p) +{ + int factor = p->mpeg-1; + int fd =0; + int head=0; + int type = NOPES; + int streamid; + int c = 6+p->hlength+3*factor; + + + switch ( p->cid ) { + case PRIVATE_STREAM1: + streamid = p->buf[c]; + head = 4; + if ((streamid & 0xF8) == 0x80+p->es-1){ + fd = p->fd1; + type = AC3; + } + break; + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + fd = p->fd1; + type = AUDIO; + break; + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + fd = p->fd2; + type = VIDEO; + break; + } + + if (p->es && !p->startv && type == VIDEO){ + int found = 0; + + if ( p->flag2 & PTS_DTS ) + p->vpts = trans_pts_dts(p->pts); + else return; + + while ( !found && c+3 < p->plength+6 ){ + if ( p->buf[c] == 0x00 && + p->buf[c+1] == 0x00 && + p->buf[c+2] == 0x01 && + p->buf[c+3] == 0xb3) + found = 1; + else c++; + } + if (found){ + p->startv = 1; + write(fd, p->buf+c, p->plength+6-c); + } + fd = 0; + } + + + if ( p->es && !p->starta && type == AUDIO){ + int found = 0; + if ( p->flag2 & PTS_DTS ) + p->apts = trans_pts_dts(p->pts); + else return; + + if (p->startv) + while ( !found && c+1 < p->plength+6){ + if ( p->buf[c] == 0xFF && + (p->buf[c+1] & 0xF8) == 0xF8) + found = 1; + else c++; + } + if (found){ + p->starta = 1; + write(fd, p->buf+c, p->plength+6-c); + } + fd = 0; + } + + if ( p->es && !p->starta && type == AC3){ + if ( p->flag2 & PTS_DTS ) + p->apts = trans_pts_dts(p->pts); + else return; + + if (p->startv){ + c+= ((p->buf[c+2] << 8)| p->buf[c+3]); + p->starta = 1; + write(fd, p->buf+c, p->plength+6-c); + } + fd = 0; + } + + + if (fd){ + if (p->es) + write(fd,p->buf+p->hlength+6+3*factor+head, + p->plength-p->hlength-3*factor-head); + else + write(fd,p->buf,p->plength+6); + } +} + +int64_t pes_dmx( int fdin, int fdouta, int fdoutv, int es) +{ + p2p p; + int count = 1; + uint8_t buf[SIZE]; + uint64_t length = 0; + uint64_t l = 0; + int verb = 0; + int percent, oldPercent = -1; + + init_p2p(&p, NULL, 2048); + p.fd1 = fdouta; + p.fd2 = fdoutv; + p.es = es; + p.startv = 0; + p.starta = 0; + p.apts=-1; + p.vpts=-1; + + if (fdin != STDIN_FILENO) verb = 1; + + if (verb) { + length = lseek(fdin, 0, SEEK_END); + lseek(fdin,0,SEEK_SET); + } + + while (count > 0){ + count = read(fdin,buf,SIZE); + l += count; + if (verb){ + percent = 100 * l / length; + + if (percent != oldPercent) { + fprintf(stderr, "Demuxing %d %%\r", percent); + oldPercent = percent; + } + } + get_pes(buf,count,&p,pes_dfilt); + } + + return (int64_t)p.vpts - (int64_t)p.apts; + +} + + +/* SV: made non-static */ +void pes_in_ts(p2p *p) +{ + int l, pes_start; + uint8_t obuf[TS_SIZE]; + long int c = 0; + int length = p->plength+6; + uint16_t pid; + uint8_t *counter; + pes_start = 1; + switch ( p->cid ) { + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + pid = p->pida; + counter = &p->acounter; + break; + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + pid = p->pidv; + counter = &p->acounter; + + tspid0[3] |= (p->count0++) + & 0x0F ; + tspid1[3] |= (p->count1++) + & 0x0F ; + + tspid1[24] = p->pidv; + tspid1[23] |= (p->pidv >> 8) & 0x3F; + tspid1[29] = p->pida; + tspid1[28] |= (p->pida >> 8) & 0x3F; + + p->func(tspid0,188,p); + p->func(tspid1,188,p); + break; + default: + return; + } + + while ( c < length ){ + memset(obuf,0,TS_SIZE); + if (length - c >= TS_SIZE-4){ + l = write_ts_header(pid, counter, pes_start + , obuf, TS_SIZE-4); + memcpy(obuf+l, p->buf+c, TS_SIZE-l); + c += TS_SIZE-l; + } else { + l = write_ts_header(pid, counter, pes_start + , obuf, length-c); + memcpy(obuf+l, p->buf+c, TS_SIZE-l); + c = length; + } + p->func(obuf,188,p); + pes_start = 0; + } +} + +static +void write_out(uint8_t *buf, int count,void *p) +{ + write(STDOUT_FILENO, buf, count); +} + + +void pes_to_ts2( int fdin, int fdout, uint16_t pida, uint16_t pidv) +{ + p2p p; + int count = 1; + uint8_t buf[SIZE]; + uint64_t length = 0; + uint64_t l = 0; + int verb = 0; + + init_p2p(&p, NULL, 2048); + p.fd1 = fdout; + p.pida = pida; + p.pidv = pidv; + p.acounter = 0; + p.vcounter = 0; + p.count1 = 0; + p.count0 = 0; + p.func = write_out; + + if (fdin != STDIN_FILENO) verb = 1; + + if (verb) { + length = lseek(fdin, 0, SEEK_END); + lseek(fdin,0,SEEK_SET); + } + + while (count > 0){ + count = read(fdin,buf,SIZE); + l += count; + if (verb) + fprintf(stderr,"Writing TS %2.2f %%\r", + 100.*l/length); + + get_pes(buf,count,&p,pes_in_ts); + } + +} + + +#define IN_SIZE TS_SIZE*10 +void find_avpids(int fd, uint16_t *vpid, uint16_t *apid) +{ + uint8_t buf[IN_SIZE]; + int count; + int i; + int off =0; + + while ( *apid == 0 || *vpid == 0){ + count = read(fd, buf, IN_SIZE); + for (i = 0; i < count-7; i++){ + if (buf[i] == 0x47){ + if (buf[i+1] & 0x40){ + off = 0; + if ( buf[3+i] & 0x20)//adapt field? + off = buf[4+i] + 1; + switch(buf[i+7+off]){ + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + *vpid = get_pid(buf+i+1); + break; + case PRIVATE_STREAM1: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + *apid = get_pid(buf+i+1); + break; + } + } + i += 187; + } + if (*apid != 0 && *vpid != 0) break; + } + } +} + +void find_bavpids(uint8_t *buf, int count, uint16_t *vpid, uint16_t *apid) +{ + int i; + int founda = 0; + int foundb = 0; + int off = 0; + + *vpid = 0; + *apid = 0; + for (i = 0; i < count-7; i++){ + if (buf[i] == 0x47){ + if ((buf[i+1] & 0xF0) == 0x40){ + off = 0; + if ( buf[3+i] & 0x20) // adaptation field? + off = buf[4+i] + 1; + + if (buf[off+i+4] == 0x00 && + buf[off+i+5] == 0x00 && + buf[off+i+6] == 0x01){ + switch(buf[off+i+7]){ + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + *vpid = get_pid(buf+i+1); + foundb=1; + break; + case PRIVATE_STREAM1: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + *apid = get_pid(buf+i+1); + founda=1; + break; + } + } + } + i += 187; + } + if (founda && foundb) break; + } +} + + +void ts_to_pes( int fdin, uint16_t pida, uint16_t pidv, int ps) +{ + + uint8_t buf[IN_SIZE]; + uint8_t mbuf[TS_SIZE]; + int i; + int count = 1; + uint16_t pid; + uint16_t dummy; + ipack pa, pv; + ipack *p; + + if (fdin != STDIN_FILENO && (!pida || !pidv)) + find_avpids(fdin, &pidv, &pida); + + init_ipack(&pa, IPACKS,write_out, ps); + init_ipack(&pv, IPACKS,write_out, ps); + + if ((count = save_read(fdin,mbuf,TS_SIZE))<0) + perror("reading"); + + for ( i = 0; i < 188 ; i++){ + if ( mbuf[i] == 0x47 ) break; + } + if ( i == 188){ + fprintf(stderr,"Not a TS\n"); + return; + } else { + memcpy(buf,mbuf+i,TS_SIZE-i); + if ((count = save_read(fdin,mbuf,i))<0) + perror("reading"); + memcpy(buf+TS_SIZE-i,mbuf,i); + i = 188; + } + count = 1; + while (count > 0){ + if ((count = save_read(fdin,buf+i,IN_SIZE-i)+i)<0) + perror("reading"); + + + if (!pidv){ + find_bavpids(buf+i, IN_SIZE-i, &pidv, &dummy); + if (pidv) fprintf(stderr, "vpid %d (0x%02x)\n", + pidv,pidv); + } + + if (!pida){ + find_bavpids(buf+i, IN_SIZE-i, &dummy, &pida); + if (pida) fprintf(stderr, "apid %d (0x%02x)\n", + pida,pida); + } + + + for( i = 0; i < count; i+= TS_SIZE){ + uint8_t off = 0; + + if ( count - i < TS_SIZE) break; + + pid = get_pid(buf+i+1); + if (!(buf[3+i]&0x10)) // no payload? + continue; + if ( buf[1+i]&0x80){ + fprintf(stderr,"Error in TS for PID: %d\n", + pid); + } + if (pid == pidv){ + p = &pv; + } else { + if (pid == pida){ + p = &pa; + } else continue; + } + + if ( buf[1+i]&0x40) { + if (p->plength == MMAX_PLENGTH-6){ + p->plength = p->found-6; + p->found = 0; + send_ipack(p); + reset_ipack(p); + } + } + + if ( buf[3+i] & 0x20) { // adaptation field? + off = buf[4+i] + 1; + } + + instant_repack(buf+4+off+i, TS_SIZE-4-off, p); + } + i = 0; + + } + +} + + +#define INN_SIZE 2*IN_SIZE +void insert_pat_pmt( int fdin, int fdout) +{ + + uint8_t buf[INN_SIZE]; + uint8_t mbuf[TS_SIZE]; + int i; + int count = 1; + uint16_t pida = 0; + uint16_t pidv = 0; + int written,c; + uint8_t c0 = 0; + uint8_t c1 = 0; + uint8_t pmt_len; + uint32_t crc32; + + + find_avpids(fdin, &pidv, &pida); + + count = save_read(fdin,mbuf,TS_SIZE); + for ( i = 0; i < 188 ; i++){ + if ( mbuf[i] == 0x47 ) break; + } + if ( i == 188){ + fprintf(stderr,"Not a TS\n"); + return; + } else { + memcpy(buf,mbuf+i,TS_SIZE-i); + count = save_read(fdin,mbuf,i); + memcpy(buf+TS_SIZE-i,mbuf,i); + i = 188; + } + + count = 1; + /* length is not correct, but we only create a very small + * PMT, so it doesn't matter :-) + */ + pmt_len = tspid1[7] + 3; + while (count > 0){ + tspid1[24] = pidv; + tspid1[23] |= (pidv >> 8) & 0x3F; + tspid1[29] = pida; + tspid1[28] |= (pida >> 8) & 0x3F; + crc32 = calc_crc32 (&tspid1[5], pmt_len - 4); + tspid1[5 + pmt_len - 4] = (crc32 & 0xff000000) >> 24; + tspid1[5 + pmt_len - 3] = (crc32 & 0x00ff0000) >> 16; + tspid1[5 + pmt_len - 2] = (crc32 & 0x0000ff00) >> 8; + tspid1[5 + pmt_len - 1] = (crc32 & 0x000000ff) >> 0; + + write(fdout,tspid0,188); + write(fdout,tspid1,188); + + count = save_read(fdin,buf+i,INN_SIZE-i); + + written = 0; + while (written < IN_SIZE){ + c = write(fdout,buf,INN_SIZE); + if (c>0) written += c; + } + tspid0[3] &= 0xF0 ; + tspid0[3] |= (c0++)& 0x0F ; + + tspid1[3] &= 0xF0 ; + tspid1[3] |= (c1++)& 0x0F ; + + i=0; + } + +} + +void get_pes (uint8_t *buf, int count, p2p *p, void (*func)(p2p *p)) +{ + + int l; + unsigned short *pl; + int c=0; + + uint8_t headr[3] = { 0x00, 0x00, 0x01} ; + + while (c < count && (p->mpeg == 0 || + (p->mpeg == 1 && p->found < 7) || + (p->mpeg == 2 && p->found < 9)) + && (p->found < 5 || !p->done)){ + switch ( p->found ){ + case 0: + case 1: + if (buf[c] == 0x00) p->found++; + else p->found = 0; + c++; + break; + case 2: + if (buf[c] == 0x01) p->found++; + else if (buf[c] == 0){ + p->found = 2; + } else p->found = 0; + c++; + break; + case 3: + p->cid = 0; + switch (buf[c]){ + case PROG_STREAM_MAP: + case PRIVATE_STREAM2: + case PROG_STREAM_DIR: + case ECM_STREAM : + case EMM_STREAM : + case PADDING_STREAM : + case DSM_CC_STREAM : + case ISO13522_STREAM: + p->done = 1; + case PRIVATE_STREAM1: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + p->found++; + p->cid = buf[c]; + c++; + break; + default: + p->found = 0; + break; + } + break; + + + case 4: + if (count-c > 1){ + pl = (unsigned short *) (buf+c); + p->plength = ntohs(*pl); + p->plen[0] = buf[c]; + c++; + p->plen[1] = buf[c]; + c++; + p->found+=2; + } else { + p->plen[0] = buf[c]; + p->found++; + return; + } + break; + case 5: + p->plen[1] = buf[c]; + c++; + pl = (unsigned short *) p->plen; + p->plength = ntohs(*pl); + p->found++; + break; + + + case 6: + if (!p->done){ + p->flag1 = buf[c]; + c++; + p->found++; + if ( (p->flag1 & 0xC0) == 0x80 ) p->mpeg = 2; + else { + p->hlength = 0; + p->which = 0; + p->mpeg = 1; + p->flag2 = 0; + } + } + break; + + case 7: + if ( !p->done && p->mpeg == 2){ + p->flag2 = buf[c]; + c++; + p->found++; + } + break; + + case 8: + if ( !p->done && p->mpeg == 2){ + p->hlength = buf[c]; + c++; + p->found++; + } + break; + + default: + + break; + } + } + + if (!p->plength) p->plength = MMAX_PLENGTH-6; + + + if ( p->done || ((p->mpeg == 2 && p->found >= 9) || + (p->mpeg == 1 && p->found >= 7)) ){ + switch (p->cid){ + + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + case PRIVATE_STREAM1: + + memcpy(p->buf, headr, 3); + p->buf[3] = p->cid; + memcpy(p->buf+4,p->plen,2); + + if (p->mpeg == 2 && p->found == 9){ + p->buf[6] = p->flag1; + p->buf[7] = p->flag2; + p->buf[8] = p->hlength; + } + + if (p->mpeg == 1 && p->found == 7){ + p->buf[6] = p->flag1; + } + + + if (p->mpeg == 2 && (p->flag2 & PTS_ONLY) && + p->found < 14){ + while (c < count && p->found < 14){ + p->pts[p->found-9] = buf[c]; + p->buf[p->found] = buf[c]; + c++; + p->found++; + } + if (c == count) return; + } + + if (p->mpeg == 1 && p->which < 2000){ + + if (p->found == 7) { + p->check = p->flag1; + p->hlength = 1; + } + + while (!p->which && c < count && + p->check == 0xFF){ + p->check = buf[c]; + p->buf[p->found] = buf[c]; + c++; + p->found++; + p->hlength++; + } + + if ( c == count) return; + + if ( (p->check & 0xC0) == 0x40 && !p->which){ + p->check = buf[c]; + p->buf[p->found] = buf[c]; + c++; + p->found++; + p->hlength++; + + p->which = 1; + if ( c == count) return; + p->check = buf[c]; + p->buf[p->found] = buf[c]; + c++; + p->found++; + p->hlength++; + p->which = 2; + if ( c == count) return; + } + + if (p->which == 1){ + p->check = buf[c]; + p->buf[p->found] = buf[c]; + c++; + p->found++; + p->hlength++; + p->which = 2; + if ( c == count) return; + } + + if ( (p->check & 0x30) && p->check != 0xFF){ + p->flag2 = (p->check & 0xF0) << 2; + p->pts[0] = p->check; + p->which = 3; + } + + if ( c == count) return; + if (p->which > 2){ + if ((p->flag2 & PTS_DTS_FLAGS) + == PTS_ONLY){ + while (c < count && + p->which < 7){ + p->pts[p->which-2] = + buf[c]; + p->buf[p->found] = + buf[c]; + c++; + p->found++; + p->which++; + p->hlength++; + } + if ( c == count) return; + } else if ((p->flag2 & PTS_DTS_FLAGS) + == PTS_DTS){ + while (c < count && + p->which< 12){ + if (p->which< 7) + p->pts[p->which + -2] = + buf[c]; + p->buf[p->found] = + buf[c]; + c++; + p->found++; + p->which++; + p->hlength++; + } + if ( c == count) return; + } + p->which = 2000; + } + + } + + while (c < count && p->found < p->plength+6){ + l = count -c; + if (l+p->found > p->plength+6) + l = p->plength+6-p->found; + memcpy(p->buf+p->found, buf+c, l); + p->found += l; + c += l; + } + if(p->found == p->plength+6) + func(p); + + break; + } + + + if ( p->done ){ + if( p->found + count - c < p->plength+6){ + p->found += count-c; + c = count; + } else { + c += p->plength+6 - p->found; + p->found = p->plength+6; + } + } + + if (p->plength && p->found == p->plength+6) { + p->found = 0; + p->done = 0; + p->plength = 0; + memset(p->buf, 0, MAX_PLENGTH); + if (c < count) + get_pes(buf+c, count-c, p, func); + } + } + return; +} + + + + +void setup_pes2ts( p2p *p, uint32_t pida, uint32_t pidv, + void (*ts_write)(uint8_t *buf, int count, void *p)) +{ + init_p2p( p, ts_write, 2048); + p->pida = pida; + p->pidv = pidv; + p->acounter = 0; + p->vcounter = 0; + p->count1 = 0; + p->count0 = 0; +} + +void kpes_to_ts( p2p *p,uint8_t *buf ,int count ) +{ + get_pes(buf,count, p,pes_in_ts); +} + + +void setup_ts2pes( p2p *pa, p2p *pv, uint32_t pida, uint32_t pidv, + void (*pes_write)(uint8_t *buf, int count, void *p)) +{ + init_p2p( pa, pes_write, 2048); + init_p2p( pv, pes_write, 2048); + pa->pid = pida; + pv->pid = pidv; +} + +void kts_to_pes( p2p *p, uint8_t *buf) // don't need count (=188) +{ + uint8_t off = 0; + uint16_t pid = 0; + + if (!(buf[3]&PAYLOAD)) // no payload? + return; + + pid = get_pid(buf+1); + + if (pid != p->pid) return; + if ( buf[1]&0x80){ + fprintf(stderr,"Error in TS for PID: %d\n", + pid); + } + + if ( buf[1]&PAY_START) { + if (p->plength == MMAX_PLENGTH-6){ + p->plength = p->found-6; + p->found = 0; + pes_repack(p); + } + } + + if ( buf[3] & ADAPT_FIELD) { // adaptation field? + off = buf[4] + 1; + if (off+4 > 187) return; + } + + get_pes(buf+4+off, TS_SIZE-4-off, p , pes_repack); +} + + + + +// instant repack + + +void reset_ipack(ipack *p) +{ + p->found = 0; + p->cid = 0; + p->plength = 0; + p->flag1 = 0; + p->flag2 = 0; + p->hlength = 0; + p->mpeg = 0; + p->check = 0; + p->which = 0; + p->done = 0; + p->count = 0; + p->size = p->size_orig; +} + +void init_ipack(ipack *p, int size, + void (*func)(uint8_t *buf, int size, void *priv), int ps) +{ + if ( !(p->buf = malloc(size)) ){ + fprintf(stderr,"Couldn't allocate memory for ipack\n"); + exit(1); + } + p->ps = ps; + p->size_orig = size; + p->func = func; + reset_ipack(p); + p->has_ai = 0; + p->has_vi = 0; + p->start = 0; +} + +void free_ipack(ipack * p) +{ + if (p->buf) free(p->buf); +} + + + +int get_vinfo(uint8_t *mbuf, int count, VideoInfo *vi, int pr) +{ + uint8_t *headr; + int found = 0; + int sw; + int form = -1; + int c = 0; + + while (found < 4 && c+4 < count){ + uint8_t *b; + + b = mbuf+c; + if ( b[0] == 0x00 && b[1] == 0x00 && b[2] == 0x01 + && b[3] == 0xb3) found = 4; + else { + c++; + } + } + + if (! found) return -1; + c += 4; + if (c+12 >= count) return -1; + headr = mbuf+c; + + vi->horizontal_size = ((headr[1] &0xF0) >> 4) | (headr[0] << 4); + vi->vertical_size = ((headr[1] &0x0F) << 8) | (headr[2]); + + sw = (int)((headr[3]&0xF0) >> 4) ; + + switch( sw ){ + case 1: + if (pr) + fprintf(stderr,"Videostream: ASPECT: 1:1"); + vi->aspect_ratio = 100; + break; + case 2: + if (pr) + fprintf(stderr,"Videostream: ASPECT: 4:3"); + vi->aspect_ratio = 133; + break; + case 3: + if (pr) + fprintf(stderr,"Videostream: ASPECT: 16:9"); + vi->aspect_ratio = 177; + break; + case 4: + if (pr) + fprintf(stderr,"Videostream: ASPECT: 2.21:1"); + vi->aspect_ratio = 221; + break; + + case 5 ... 15: + if (pr) + fprintf(stderr,"Videostream: ASPECT: reserved"); + vi->aspect_ratio = 0; + break; + + default: + vi->aspect_ratio = 0; + return -1; + } + + if (pr) + fprintf(stderr," Size = %dx%d",vi->horizontal_size, + vi->vertical_size); + + sw = (int)(headr[3]&0x0F); + + switch ( sw ) { + case 1: + if (pr) + fprintf(stderr," FRate: 23.976 fps"); + vi->framerate = 24000/1001.; + form = -1; + break; + case 2: + if (pr) + fprintf(stderr," FRate: 24 fps"); + vi->framerate = 24; + form = -1; + break; + case 3: + if (pr) + fprintf(stderr," FRate: 25 fps"); + vi->framerate = 25; + form = VIDEO_MODE_PAL; + break; + case 4: + if (pr) + fprintf(stderr," FRate: 29.97 fps"); + vi->framerate = 30000/1001.; + form = VIDEO_MODE_NTSC; + break; + case 5: + if (pr) + fprintf(stderr," FRate: 30 fps"); + vi->framerate = 30; + form = VIDEO_MODE_NTSC; + break; + case 6: + if (pr) + fprintf(stderr," FRate: 50 fps"); + vi->framerate = 50; + form = VIDEO_MODE_PAL; + break; + case 7: + if (pr) + fprintf(stderr," FRate: 60 fps"); + vi->framerate = 60; + form = VIDEO_MODE_NTSC; + break; + } + + vi->bit_rate = 400*(((headr[4] << 10) & 0x0003FC00UL) + | ((headr[5] << 2) & 0x000003FCUL) | + (((headr[6] & 0xC0) >> 6) & 0x00000003UL)); + + if (pr){ + fprintf(stderr," BRate: %.2f Mbit/s",(vi->bit_rate)/1000000.); + fprintf(stderr,"\n"); + } + vi->video_format = form; + + vi->off = c-4; + return c-4; +} + +extern unsigned int bitrates[3][16]; +extern uint32_t freq[4]; + +int get_ainfo(uint8_t *mbuf, int count, AudioInfo *ai, int pr) +{ + uint8_t *headr; + int found = 0; + int c = 0; + int fr =0; + + while (!found && c < count){ + uint8_t *b = mbuf+c; + + if ( b[0] == 0xff && (b[1] & 0xf8) == 0xf8) + found = 1; + else { + c++; + } + } + + if (!found) return -1; + + if (c+3 >= count) return -1; + headr = mbuf+c; + + ai->layer = (headr[1] & 0x06) >> 1; + + if (pr) + fprintf(stderr,"Audiostream: Layer: %d", 4-ai->layer); + + + ai->bit_rate = bitrates[(3-ai->layer)][(headr[2] >> 4 )]*1000; + + if (pr){ + if (ai->bit_rate == 0) + fprintf (stderr," Bit rate: free"); + else if (ai->bit_rate == 0xf) + fprintf (stderr," BRate: reserved"); + else + fprintf (stderr," BRate: %d kb/s", ai->bit_rate/1000); + } + + fr = (headr[2] & 0x0c ) >> 2; + ai->frequency = freq[fr]*100; + + if (pr){ + if (ai->frequency == 3) + fprintf (stderr, " Freq: reserved\n"); + else + fprintf (stderr," Freq: %2.1f kHz\n", + ai->frequency/1000.); + } + ai->off = c; + return c; +} + +unsigned int ac3_bitrates[32] = + {32,40,48,56,64,80,96,112,128,160,192,224,256,320,384,448,512,576,640, + 0,0,0,0,0,0,0,0,0,0,0,0,0}; + +uint32_t ac3_freq[4] = {480, 441, 320, 0}; +uint32_t ac3_frames[3][32] = + {{64,80,96,112,128,160,192,224,256,320,384,448,512,640,768,896,1024, + 1152,1280,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {69,87,104,121,139,174,208,243,278,348,417,487,557,696,835,975,1114, + 1253,1393,0,0,0,0,0,0,0,0,0,0,0,0,0}, + {96,120,144,168,192,240,288,336,384,480,576,672,768,960,1152,1344, + 1536,1728,1920,0,0,0,0,0,0,0,0,0,0,0,0,0}}; + +int get_ac3info(uint8_t *mbuf, int count, AudioInfo *ai, int pr) +{ + uint8_t *headr; + int found = 0; + int c = 0; + uint8_t frame; + int fr = 0; + + while ( !found && c < count){ + uint8_t *b = mbuf+c; + if ( b[0] == 0x0b && b[1] == 0x77 ) + found = 1; + else { + c++; + } + } + + + if (!found){ + return -1; + } + ai->off = c; + + if (c+5 >= count) return -1; + + ai->layer = 0; // 0 for AC3 + headr = mbuf+c+2; + + frame = (headr[2]&0x3f); + ai->bit_rate = ac3_bitrates[frame>>1]*1000; + + if (pr) fprintf (stderr," BRate: %d kb/s", ai->bit_rate/1000); + + fr = (headr[2] & 0xc0 ) >> 6; + ai->frequency = freq[fr]*100; + if (pr) fprintf (stderr," Freq: %d Hz\n", ai->frequency); + + ai->framesize = ac3_frames[fr][frame >> 1]; + if ((frame & 1) && (fr == 1)) ai->framesize++; + ai->framesize = ai->framesize << 1; + if (pr) fprintf (stderr," Framesize %d\n", ai->framesize); + + return c; +} + + +void ps_pes(ipack *p) +{ + int check; + uint8_t pbuf[PS_HEADER_L2]; + static int muxr = 0; + static int ai = 0; + static int vi = 0; + static int start = 0; + static uint32_t SCR = 0; + + if (p->mpeg == 2){ + switch(p->buf[3]){ + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + if (!p->has_vi){ + if(get_vinfo(p->buf, p->count, &p->vi,1) >=0) { + p->has_vi = 1; + vi = p->vi.bit_rate; + } + } + break; + + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + if (!p->has_ai){ + if(get_ainfo(p->buf, p->count, &p->ai,1) >=0) { + p->has_ai = 1; + ai = p->ai.bit_rate; + } + } + break; + } + + if (p->has_vi && vi && !muxr){ + muxr = (vi+ai)/400; + } + + if ( start && muxr && (p->buf[7] & PTS_ONLY) && (p->has_ai || + p->buf[9+p->buf[8]+4] == 0xb3)){ + SCR = trans_pts_dts(p->pts)-3600; + + check = write_ps_header(pbuf, + SCR, + muxr, 1, 0, 0, 1, 1, 1, + 0, 0, 0, 0, 0, 0); + + p->func(pbuf, check , p->data); + } + + if (muxr && !start && vi){ + SCR = trans_pts_dts(p->pts)-3600; + check = write_ps_header(pbuf, + SCR, + muxr, 1, 0, 0, 1, 1, 1, + 0xC0, 0, 64, 0xE0, 1, 460); + start = 1; + p->func(pbuf, check , p->data); + } + + if (start) + p->func(p->buf, p->count, p->data); + } +} + +void send_ipack(ipack *p) +{ + int streamid=0; + int off; + int ac3_off = 0; + AudioInfo ai; + int nframes= 0; + int f=0; + + if (p->count < 10) return; + p->buf[3] = p->cid; + p->buf[4] = (uint8_t)(((p->count-6) & 0xFF00) >> 8); + p->buf[5] = (uint8_t)((p->count-6) & 0x00FF); + + + if (p->cid == PRIVATE_STREAM1){ + + off = 9+p->buf[8]; + streamid = p->buf[off]; + if ((streamid & 0xF8) == 0x80){ + ai.off = 0; + ac3_off = ((p->buf[off+2] << 8)| p->buf[off+3]); + if (ac3_off < p->count) + f=get_ac3info(p->buf+off+3+ac3_off, + p->count-ac3_off, &ai,0); + if ( !f ){ + nframes = (p->count-off-3-ac3_off)/ + ai.framesize + 1; + p->buf[off+1] = nframes; + p->buf[off+2] = (ac3_off >> 8)& 0xFF; + p->buf[off+3] = (ac3_off)& 0xFF; + + ac3_off += nframes * ai.framesize - p->count; + } + } + } + + if (p->ps) ps_pes(p); + else p->func(p->buf, p->count, p->data); + + switch ( p->mpeg ){ + case 2: + + p->buf[6] = 0x80; + p->buf[7] = 0x00; + p->buf[8] = 0x00; + p->count = 9; + + if (p->cid == PRIVATE_STREAM1 && (streamid & 0xF8)==0x80 ){ + p->count += 4; + p->buf[9] = streamid; + p->buf[10] = 0; + p->buf[11] = (ac3_off >> 8)& 0xFF; + p->buf[12] = (ac3_off)& 0xFF; + } + + break; + case 1: + p->buf[6] = 0x0F; + p->count = 7; + break; + } + +} + + +static void write_ipack(ipack *p, uint8_t *data, int count) +{ + AudioInfo ai; + uint8_t headr[3] = { 0x00, 0x00, 0x01} ; + int diff =0; + + if (p->count < 6){ + if (trans_pts_dts(p->pts) > trans_pts_dts(p->last_pts)) + memcpy(p->last_pts, p->pts, 5); + p->count = 0; + memcpy(p->buf+p->count, headr, 3); + p->count += 6; + } + if ( p->size == p->size_orig && p->plength && + (diff = 6+p->plength - p->found + p->count +count) > p->size && + diff < 3*p->size/2){ + + p->size = diff/2; +// fprintf(stderr,"size: %d \n",p->size); + } + + if (p->cid == PRIVATE_STREAM1 && p->count == p->hlength+9){ + if ((data[0] & 0xF8) != 0x80){ + int ac3_off; + + ac3_off = get_ac3info(data, count, &ai,0); + if (ac3_off>=0 && ai.framesize){ + p->buf[p->count] = 0x80; + p->buf[p->count+1] = (p->size - p->count + - 4 - ac3_off)/ + ai.framesize + 1; + p->buf[p->count+2] = (ac3_off >> 8)& 0xFF; + p->buf[p->count+3] = (ac3_off)& 0xFF; + p->count+=4; + + } + } + } + + if (p->count + count < p->size){ + memcpy(p->buf+p->count, data, count); + p->count += count; + } else { + int rest = p->size - p->count; + if (rest < 0) rest = 0; + memcpy(p->buf+p->count, data, rest); + p->count += rest; +// fprintf(stderr,"count: %d \n",p->count); + send_ipack(p); + if (count - rest > 0) + write_ipack(p, data+rest, count-rest); + } +} + +void instant_repack (uint8_t *buf, int count, ipack *p) +{ + + int l; + unsigned short *pl; + int c=0; + + while (c < count && (p->mpeg == 0 || + (p->mpeg == 1 && p->found < 7) || + (p->mpeg == 2 && p->found < 9)) + && (p->found < 5 || !p->done)){ + switch ( p->found ){ + case 0: + case 1: + if (buf[c] == 0x00) p->found++; + else p->found = 0; + c++; + break; + case 2: + if (buf[c] == 0x01) p->found++; + else if (buf[c] == 0){ + p->found = 2; + } else p->found = 0; + c++; + break; + case 3: + p->cid = 0; + switch (buf[c]){ + case PROG_STREAM_MAP: + case PRIVATE_STREAM2: + case PROG_STREAM_DIR: + case ECM_STREAM : + case EMM_STREAM : + case PADDING_STREAM : + case DSM_CC_STREAM : + case ISO13522_STREAM: + p->done = 1; + case PRIVATE_STREAM1: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + p->found++; + p->cid = buf[c]; + c++; + break; + default: + p->found = 0; + break; + } + break; + + + case 4: + if (count-c > 1){ + pl = (unsigned short *) (buf+c); + p->plength = ntohs(*pl); + p->plen[0] = buf[c]; + c++; + p->plen[1] = buf[c]; + c++; + p->found+=2; + } else { + p->plen[0] = buf[c]; + p->found++; + return; + } + break; + case 5: + p->plen[1] = buf[c]; + c++; + pl = (unsigned short *) p->plen; + p->plength = ntohs(*pl); + p->found++; + break; + + + case 6: + if (!p->done){ + p->flag1 = buf[c]; + c++; + p->found++; + if ( (p->flag1 & 0xC0) == 0x80 ) p->mpeg = 2; + else { + p->hlength = 0; + p->which = 0; + p->mpeg = 1; + p->flag2 = 0; + } + } + break; + + case 7: + if ( !p->done && p->mpeg == 2){ + p->flag2 = buf[c]; + c++; + p->found++; + } + break; + + case 8: + if ( !p->done && p->mpeg == 2){ + p->hlength = buf[c]; + c++; + p->found++; + } + break; + + default: + + break; + } + } + + + if (c == count) return; + + if (!p->plength) p->plength = MMAX_PLENGTH-6; + + + if ( p->done || ((p->mpeg == 2 && p->found >= 9) || + (p->mpeg == 1 && p->found >= 7)) ){ + switch (p->cid){ + + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + case PRIVATE_STREAM1: + + if (p->mpeg == 2 && p->found == 9){ + write_ipack(p, &p->flag1, 1); + write_ipack(p, &p->flag2, 1); + write_ipack(p, &p->hlength, 1); + } + + if (p->mpeg == 1 && p->found == 7){ + write_ipack(p, &p->flag1, 1); + } + + + if (p->mpeg == 2 && (p->flag2 & PTS_ONLY) && + p->found < 14){ + while (c < count && p->found < 14){ + p->pts[p->found-9] = buf[c]; + write_ipack(p, buf+c, 1); + c++; + p->found++; + } + if (c == count) return; + } + + if (p->mpeg == 1 && p->which < 2000){ + + if (p->found == 7) { + p->check = p->flag1; + p->hlength = 1; + } + + while (!p->which && c < count && + p->check == 0xFF){ + p->check = buf[c]; + write_ipack(p, buf+c, 1); + c++; + p->found++; + p->hlength++; + } + + if ( c == count) return; + + if ( (p->check & 0xC0) == 0x40 && !p->which){ + p->check = buf[c]; + write_ipack(p, buf+c, 1); + c++; + p->found++; + p->hlength++; + + p->which = 1; + if ( c == count) return; + p->check = buf[c]; + write_ipack(p, buf+c, 1); + c++; + p->found++; + p->hlength++; + p->which = 2; + if ( c == count) return; + } + + if (p->which == 1){ + p->check = buf[c]; + write_ipack(p, buf+c, 1); + c++; + p->found++; + p->hlength++; + p->which = 2; + if ( c == count) return; + } + + if ( (p->check & 0x30) && p->check != 0xFF){ + p->flag2 = (p->check & 0xF0) << 2; + p->pts[0] = p->check; + p->which = 3; + } + + if ( c == count) return; + if (p->which > 2){ + if ((p->flag2 & PTS_DTS_FLAGS) + == PTS_ONLY){ + while (c < count && + p->which < 7){ + p->pts[p->which-2] = + buf[c]; + write_ipack(p,buf+c,1); + c++; + p->found++; + p->which++; + p->hlength++; + } + if ( c == count) return; + } else if ((p->flag2 & PTS_DTS_FLAGS) + == PTS_DTS){ + while (c < count && + p->which< 12){ + if (p->which< 7) + p->pts[p->which + -2] = + buf[c]; + write_ipack(p,buf+c,1); + c++; + p->found++; + p->which++; + p->hlength++; + } + if ( c == count) return; + } + p->which = 2000; + } + + } + + while (c < count && p->found < p->plength+6){ + l = count -c; + if (l+p->found > p->plength+6) + l = p->plength+6-p->found; + write_ipack(p, buf+c, l); + p->found += l; + c += l; + } + + break; + } + + + if ( p->done ){ + if( p->found + count - c < p->plength+6){ + p->found += count-c; + c = count; + } else { + c += p->plength+6 - p->found; + p->found = p->plength+6; + } + } + + if (p->plength && p->found == p->plength+6) { + send_ipack(p); + reset_ipack(p); + if (c < count) + instant_repack(buf+c, count-c, p); + } + } + return; +} + +void write_out_es(uint8_t *buf, int count,void *priv) +{ + ipack *p = (ipack *) priv; + uint8_t payl = buf[8]+9+p->start-1; + + write(p->fd, buf+payl, count-payl); + p->start = 1; +} + +void write_out_pes(uint8_t *buf, int count,void *priv) +{ + ipack *p = (ipack *) priv; + write(p->fd, buf, count); +} + + + +int64_t ts_demux(int fdin, int fdv_out,int fda_out,uint16_t pida, + uint16_t pidv, int es) +{ + uint8_t buf[IN_SIZE]; + uint8_t mbuf[TS_SIZE]; + int i; + int count = 1; + uint16_t pid; + ipack pa, pv; + ipack *p; + uint8_t *sb; + int64_t apts=0; + int64_t vpts=0; + int verb = 0; + uint64_t length =0; + uint64_t l=0; + int perc =0; + int last_perc =0; + + if (fdin != STDIN_FILENO) verb = 1; + + if (verb) { + length = lseek(fdin, 0, SEEK_END); + lseek(fdin,0,SEEK_SET); + } + + if (!pida || !pidv) + find_avpids(fdin, &pidv, &pida); + + if (es){ + init_ipack(&pa, IPACKS,write_out_es, 0); + init_ipack(&pv, IPACKS,write_out_es, 0); + } else { + init_ipack(&pa, IPACKS,write_out_pes, 0); + init_ipack(&pv, IPACKS,write_out_pes, 0); + } + pa.fd = fda_out; + pv.fd = fdv_out; + pa.data = (void *)&pa; + pv.data = (void *)&pv; + + count = save_read(fdin,mbuf,TS_SIZE); + if (count) l+=count; + for ( i = 0; i < 188 ; i++){ + if ( mbuf[i] == 0x47 ) break; + } + if ( i == 188){ + fprintf(stderr,"Not a TS\n"); + return 0; + } else { + memcpy(buf,mbuf+i,TS_SIZE-i); + count = save_read(fdin,mbuf,i); + if (count) l+=count; + memcpy(buf+TS_SIZE-i,mbuf,i); + i = 188; + } + + count = 1; + while (count > 0){ + count = save_read(fdin,buf+i,IN_SIZE-i)+i; + if (count) l+=count; + if (verb && perc >last_perc){ + perc = (100*l)/length; + fprintf(stderr,"Reading TS %d %%\r",perc); + last_perc = perc; + } + + for( i = 0; i < count; i+= TS_SIZE){ + uint8_t off = 0; + + if ( count - i < TS_SIZE) break; + + pid = get_pid(buf+i+1); + if (!(buf[3+i]&0x10)) // no payload? + continue; + if ( buf[1+i]&0x80){ + fprintf(stderr,"Error in TS for PID: %d\n", + pid); + } + if (pid == pidv){ + p = &pv; + } else { + if (pid == pida){ + p = &pa; + } else continue; + } + + if ( buf[3+i] & 0x20) { // adaptation field? + off = buf[4+i] + 1; + } + + if ( buf[1+i]&0x40) { + if (p->plength == MMAX_PLENGTH-6){ + p->plength = p->found-6; + p->found = 0; + send_ipack(p); + reset_ipack(p); + } + sb = buf+4+off+i; + if( es && + !p->start && (sb[7] & PTS_DTS_FLAGS)){ + uint8_t *pay = sb+sb[8]+9; + int l = TS_SIZE - 13 - off - sb[8]; + if ( pid == pidv && + (p->start = + get_vinfo( pay, l,&p->vi,1)+1) >0 + ){ + vpts = trans_pts_dts(sb+9); + printf("vpts : %fs\n", + vpts/90000.); + } + if ( pid == pida && es==1 && + (p->start = + get_ainfo( pay, l,&p->ai,1)+1) >0 + ){ + apts = trans_pts_dts(sb+9); + printf("apts : %fs\n", + apts/90000.); + } + if ( pid == pida && es==2 && + (p->start = + get_ac3info( pay, l,&p->ai,1)+1) >0 + ){ + apts = trans_pts_dts(sb+9); + printf("apts : %fs\n", + apts/90000.); + } + } + } + + if (p->start) + instant_repack(buf+4+off+i, TS_SIZE-4-off, p); + } + i = 0; + + } + + return (vpts-apts); +} + +void ts2es_opt(int fdin, uint16_t pidv, ipack *p, int verb) +{ + uint8_t buf[IN_SIZE]; + uint8_t mbuf[TS_SIZE]; + int i; + int count = 1; + uint64_t length =0; + uint64_t l=0; + int perc =0; + int last_perc =0; + uint16_t pid; + + if (verb) { + length = lseek(fdin, 0, SEEK_END); + lseek(fdin,0,SEEK_SET); + } + + count = save_read(fdin,mbuf,TS_SIZE); + if (count) l+=count; + for ( i = 0; i < 188 ; i++){ + if ( mbuf[i] == 0x47 ) break; + } + if ( i == 188){ + fprintf(stderr,"Not a TS\n"); + return; + } else { + memcpy(buf,mbuf+i,TS_SIZE-i); + count = save_read(fdin,mbuf,i); + if (count) l+=count; + memcpy(buf+TS_SIZE-i,mbuf,i); + i = 188; + } + + count = 1; + while (count > 0){ + count = save_read(fdin,buf+i,IN_SIZE-i)+i; + if (count) l+=count; + if (verb && perc >last_perc){ + perc = (100*l)/length; + fprintf(stderr,"Reading TS %d %%\r",perc); + last_perc = perc; + } + + for( i = 0; i < count; i+= TS_SIZE){ + uint8_t off = 0; + + if ( count - i < TS_SIZE) break; + + pid = get_pid(buf+i+1); + if (!(buf[3+i]&0x10)) // no payload? + continue; + if ( buf[1+i]&0x80){ + fprintf(stderr,"Error in TS for PID: %d\n", + pid); + } + if (pid != pidv){ + continue; + } + + if ( buf[3+i] & 0x20) { // adaptation field? + off = buf[4+i] + 1; + } + + if ( buf[1+i]&0x40) { + if (p->plength == MMAX_PLENGTH-6){ + p->plength = p->found-6; + p->found = 0; + send_ipack(p); + reset_ipack(p); + } + } + + instant_repack(buf+4+off+i, TS_SIZE-4-off, p); + } + i = 0; + + } +} + +void ts2es(int fdin, uint16_t pidv) +{ + ipack p; + int verb = 0; + + init_ipack(&p, IPACKS,write_out_es, 0); + p.fd = STDOUT_FILENO; + p.data = (void *)&p; + + if (fdin != STDIN_FILENO) verb = 1; + + ts2es_opt(fdin, pidv, &p, verb); +} + + +void change_aspect(int fdin, int fdout, int aspect) +{ + ps_packet ps; + pes_packet pes; + int neof,i; + + do { + init_ps(&ps); + neof = read_ps(fdin,&ps); + write_ps(fdout,&ps); + for (i = 0; i < ps.npes; i++){ + uint8_t *buf; + int c = 0; + int l; + + init_pes(&pes); + read_pes(fdin, &pes); + + buf = pes.pes_pckt_data; + + switch (pes.stream_id){ + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + l=pes.length; + break; + default: + l = 0; + break; + } + while ( c < l - 6){ + if (buf[c] == 0x00 && + buf[c+1] == 0x00 && + buf[c+2] == 0x01 && + buf[c+3] == 0xB3) { + c += 4; + buf[c+3] &= 0x0f; + buf[c+3] |= aspect; + } + else c++; + } + write_pes(fdout,&pes); + } + } while( neof > 0 ); +} diff --git a/plugins/streamdev/streamdev-cvs/libdvbmpeg/transform.h b/plugins/streamdev/streamdev-cvs/libdvbmpeg/transform.h new file mode 100644 index 0000000..c65fa0c --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/libdvbmpeg/transform.h @@ -0,0 +1,250 @@ +/* + * dvb-mpegtools for the Siemens Fujitsu DVB PCI card + * + * Copyright (C) 2000, 2001 Marcus Metzler + * for convergence integrated media GmbH + * Copyright (C) 2002 Marcus Metzler + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + + * The author can be reached at mocm@metzlerbros.de, + + */ + +#ifndef _TS_TRANSFORM_H_ +#define _TS_TRANSFORM_H_ + +#include <stdint.h> +#include <netinet/in.h> +#include <stdio.h> +#include <unistd.h> +#include "remux.h" + +#define PROG_STREAM_MAP 0xBC +#ifndef PRIVATE_STREAM1 +#define PRIVATE_STREAM1 0xBD +#endif +#define PADDING_STREAM 0xBE +#ifndef PRIVATE_STREAM2 +#define PRIVATE_STREAM2 0xBF +#endif +#define AUDIO_STREAM_S 0xC0 +#define AUDIO_STREAM_E 0xDF +#define VIDEO_STREAM_S 0xE0 +#define VIDEO_STREAM_E 0xEF +#define ECM_STREAM 0xF0 +#define EMM_STREAM 0xF1 +#define DSM_CC_STREAM 0xF2 +#define ISO13522_STREAM 0xF3 +#define PROG_STREAM_DIR 0xFF + +#define BUFFYSIZE 10*MAX_PLENGTH +#define MAX_PTS 8192 +#define MAX_FRAME 8192 +#define MAX_PACK_L 4096 +#define PS_HEADER_L1 14 +#define PS_HEADER_L2 (PS_HEADER_L1+18) +#define MAX_H_SIZE (PES_H_MIN + PS_HEADER_L1 + 5) +#define PES_MIN 7 +#define PES_H_MIN 9 + +//flags2 +#define PTS_DTS_FLAGS 0xC0 +#define ESCR_FLAG 0x20 +#define ES_RATE_FLAG 0x10 +#define DSM_TRICK_FLAG 0x08 +#define ADD_CPY_FLAG 0x04 +#define PES_CRC_FLAG 0x02 +#define PES_EXT_FLAG 0x01 + +//pts_dts flags +#define PTS_ONLY 0x80 +#define PTS_DTS 0xC0 + +#define TS_SIZE 188 +#define TRANS_ERROR 0x80 +#define PAY_START 0x40 +#define TRANS_PRIO 0x20 +#define PID_MASK_HI 0x1F +//flags +#define TRANS_SCRMBL1 0x80 +#define TRANS_SCRMBL2 0x40 +#define ADAPT_FIELD 0x20 +#define PAYLOAD 0x10 +#define COUNT_MASK 0x0F + +// adaptation flags +#define DISCON_IND 0x80 +#define RAND_ACC_IND 0x40 +#define ES_PRI_IND 0x20 +#define PCR_FLAG 0x10 +#define OPCR_FLAG 0x08 +#define SPLICE_FLAG 0x04 +#define TRANS_PRIV 0x02 +#define ADAP_EXT_FLAG 0x01 + +// adaptation extension flags +#define LTW_FLAG 0x80 +#define PIECE_RATE 0x40 +#define SEAM_SPLICE 0x20 + + +#define MAX_PLENGTH 0xFFFF +#define MMAX_PLENGTH (64*MAX_PLENGTH) + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#define P2P_LENGTH 2048 + + enum{NOPES, AUDIO, VIDEO, AC3}; + + typedef struct p2pstruct { + int found; + uint8_t buf[MMAX_PLENGTH]; + uint8_t cid; + uint8_t subid; + uint32_t plength; + uint8_t plen[2]; + uint8_t flag1; + uint8_t flag2; + uint8_t hlength; + uint8_t pts[5]; + int mpeg; + uint8_t check; + int fd1; + int fd2; + int es; + int filter; + int which; + int done; + int repack; + uint16_t bigend_repack; + void (*func)(uint8_t *buf, int count, void *p); + int startv; + int starta; + int64_t apts; + int64_t vpts; + uint16_t pid; + uint16_t pida; + uint16_t pidv; + uint8_t acounter; + uint8_t vcounter; + uint8_t count0; + uint8_t count1; + void *data; + } p2p; + + + uint64_t trans_pts_dts(uint8_t *pts); + int write_ts_header(uint16_t pid, uint8_t *counter, int pes_start, + uint8_t *buf, uint8_t length); + uint16_t get_pid(uint8_t *pid); + void init_p2p(p2p *p, void (*func)(uint8_t *buf, int count, void *p), + int repack); + void get_pes (uint8_t *buf, int count, p2p *p, void (*func)(p2p *p)); + void get_pes (uint8_t *buf, int count, p2p *p, void (*func)(p2p *p)); + void pes_repack(p2p *p); + void setup_pes2ts( p2p *p, uint32_t pida, uint32_t pidv, + void (*ts_write)(uint8_t *buf, int count, void *p)); + void kpes_to_ts( p2p *p,uint8_t *buf ,int count ); + void setup_ts2pes( p2p *pa, p2p *pv, uint32_t pida, uint32_t pidv, + void (*pes_write)(uint8_t *buf, int count, void *p)); + void kts_to_pes( p2p *p, uint8_t *buf); + void pes_repack(p2p *p); + void extract_from_pes(int fdin, int fdout, uint8_t id, int es); + int64_t pes_dmx(int fdin, int fdouta, int fdoutv, int es); + void pes_to_ts2( int fdin, int fdout, uint16_t pida, uint16_t pidv); + void ts_to_pes( int fdin, uint16_t pida, uint16_t pidv, int pad); + int get_ainfo(uint8_t *mbuf, int count, AudioInfo *ai, int pr); + int get_vinfo(uint8_t *mbuf, int count, VideoInfo *vi, int pr); + int get_ac3info(uint8_t *mbuf, int count, AudioInfo *ai, int pr); + void filter_audio_from_pes(int fdin, int fdout, uint8_t id, + uint8_t subid); + + +//instant repack + + typedef struct ipack_s { + int size; + int size_orig; + int found; + int ps; + int has_ai; + int has_vi; + AudioInfo ai; + VideoInfo vi; + uint8_t *buf; + uint8_t cid; + uint32_t plength; + uint8_t plen[2]; + uint8_t flag1; + uint8_t flag2; + uint8_t hlength; + uint8_t pts[5]; + uint8_t last_pts[5]; + int mpeg; + uint8_t check; + int which; + int done; + void *data; + void *data2; + void (*func)(uint8_t *buf, int size, void *priv); + int count; + int start; + int fd; + int fd1; + int fd2; + int ffd; + int playing; + } ipack; + + void instant_repack (uint8_t *buf, int count, ipack *p); + void init_ipack(ipack *p, int size, + void (*func)(uint8_t *buf, int size, void *priv), + int pad); + void free_ipack(ipack * p); + void send_ipack(ipack *p); + void reset_ipack(ipack *p); + void ps_pes(ipack *p); + // use with ipack structure, repack size and callback func + + int64_t ts_demux(int fd_in, int fdv_out,int fda_out,uint16_t pida, + uint16_t pidv, int es); + + void ts2es(int fdin, uint16_t pidv); + void ts2es_opt(int fdin, uint16_t pidv, ipack *p, int verb); + void insert_pat_pmt( int fdin, int fdout); + void change_aspect(int fdin, int fdout, int aspect); + +// SV: all made non-static: + void pes_in_ts(p2p *p); + +// SV: moved from .c file: +#define IPACKS 2048 + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _TS_TRANSFORM_H_*/ + + + diff --git a/plugins/streamdev/streamdev-cvs/patches/CVS/Entries b/plugins/streamdev/streamdev-cvs/patches/CVS/Entries new file mode 100644 index 0000000..820adef --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/patches/CVS/Entries @@ -0,0 +1,7 @@ +/vdr-1.4.3-recursion.diff/1.1/Thu Jan 11 11:48:23 2007// +/vdr-1.4.3-recursion_bigpatch.diff/1.1/Thu Jan 11 11:48:23 2007// +/vdr-1.4.x-localchannelprovide.diff/1.1/Thu Jan 11 11:44:01 2007// +/vdr-1.6.0-ignore_missing_cam.diff/1.1/Tue Apr 8 14:18:17 2008// +/vdr-1.6.0-intcamdevices.patch/1.1/Thu Oct 2 07:14:48 2008// +/vdr-cap_net_raw.diff/1.1/Fri Feb 13 10:39:21 2009// +D diff --git a/plugins/streamdev/streamdev-cvs/patches/CVS/Repository b/plugins/streamdev/streamdev-cvs/patches/CVS/Repository new file mode 100644 index 0000000..b4967d6 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/patches/CVS/Repository @@ -0,0 +1 @@ +streamdev/patches diff --git a/plugins/streamdev/streamdev-cvs/patches/CVS/Root b/plugins/streamdev/streamdev-cvs/patches/CVS/Root new file mode 100644 index 0000000..2c7f6ce --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/patches/CVS/Root @@ -0,0 +1 @@ +:pserver:anoncvs@vdr-developer.org:/var/cvsroot diff --git a/plugins/streamdev/streamdev-cvs/patches/vdr-1.4.3-recursion.diff b/plugins/streamdev/streamdev-cvs/patches/vdr-1.4.3-recursion.diff new file mode 100644 index 0000000..7a06c92 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/patches/vdr-1.4.3-recursion.diff @@ -0,0 +1,85 @@ +# If you have two or more VDRs and you like them to mutually share +# there DVB cards you might need to apply this patch first. +# +# IMPORTANT: As this patch does not only modify streamdev-server but +# also an exported method of VDR, you will need to +# +# !!!!! RECOMPILE VDR AND ALL PLUGINS !!!!! +# +# Why do I need the patch? +# -------------------------- +# Before switching channels VDR will consider all of its devices to +# find the one with the least impact. This includes the device provided +# by the streamdev-client plugin. Streamdev-client will forward the +# request to its server which in turn checks all of its devices. Now if +# the server is running streamdev-client, too, the request will again +# be forwarded to its server and finally you will endup in a loop. +# +# What does the patch do? +# ----------------------- +# The patch adds the additional parameter "bool DVBCardsOnly" to VDR's +# device selection method cDevice::GetDevice(...). The parameter +# defaults to false which gives you the standard behaviour of GetDevice. +# When set to true, GetDevice will use only those devices with a card +# index < MAXDVBDEVICES, so only real DVB cards will be considered. +# Other devices like streamdev-client or DVB cards provided by plugin +# (Hauppauge PVR) won't be used. +# +# Author: Frank Schmirler (http://vdr.schmirler.de) +# +--- device.h.orig 2006-11-15 12:01:34.000000000 +0100 ++++ device.h 2006-11-15 12:02:15.000000000 +0100 +@@ -128,7 +128,7 @@ + ///< Gets the device with the given Index. + ///< \param Index must be in the range 0..numDevices-1. + ///< \return A pointer to the device, or NULL if the Index was invalid. +- static cDevice *GetDevice(const cChannel *Channel, int Priority = -1, bool *NeedsDetachReceivers = NULL); ++ static cDevice *GetDevice(const cChannel *Channel, int Priority = -1, bool *NeedsDetachReceivers = NULL, bool DVBCardsOnly = false); + ///< Returns a device that is able to receive the given Channel at the + ///< given Priority, with the least impact on active recordings and + ///< live viewing. +--- device.c.orig 2006-11-15 12:01:30.000000000 +0100 ++++ device.c 2006-11-22 12:28:05.000000000 +0100 +@@ -8,6 +8,7 @@ + */ + + #include "device.h" ++#include "dvbdevice.h" + #include <errno.h> + #include <sys/ioctl.h> + #include <sys/mman.h> +@@ -278,11 +279,13 @@ + return (0 <= Index && Index < numDevices) ? device[Index] : NULL; + } + +-cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers) ++cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers, bool DVBCardsOnly) + { + cDevice *d = NULL; + uint Impact = 0xFFFFFFFF; // we're looking for a device with the least impact + for (int i = 0; i < numDevices; i++) { ++ if (DVBCardsOnly && device[i]->CardIndex() >= MAXDVBDEVICES) ++ continue; + bool ndr; + if (device[i]->ProvidesChannel(Channel, Priority, &ndr)) { // this device is basicly able to do the job + // Put together an integer number that reflects the "impact" using +--- PLUGINS/src/streamdev/server/connection.c.orig 2006-11-15 12:10:11.000000000 +0100 ++++ PLUGINS/src/streamdev/server/connection.c 2006-11-15 12:10:59.000000000 +0100 +@@ -132,7 +132,7 @@ + Dprintf(" * GetDevice(const cChannel*, int)\n"); + Dprintf(" * -------------------------------\n"); + +- device = cDevice::GetDevice(Channel, Priority); ++ device = cDevice::GetDevice(Channel, Priority, NULL, true); + + Dprintf(" * Found following device: %p (%d)\n", device, + device ? device->CardIndex() + 1 : 0); +@@ -150,7 +150,7 @@ + const cChannel *current = Channels.GetByNumber(cDevice::CurrentChannel()); + isyslog("streamdev-server: Detaching current receiver"); + Detach(); +- device = cDevice::GetDevice(Channel, Priority); ++ device = cDevice::GetDevice(Channel, Priority, NULL, true); + Attach(); + Dprintf(" * Found following device: %p (%d)\n", device, + device ? device->CardIndex() + 1 : 0); diff --git a/plugins/streamdev/streamdev-cvs/patches/vdr-1.4.3-recursion_bigpatch.diff b/plugins/streamdev/streamdev-cvs/patches/vdr-1.4.3-recursion_bigpatch.diff new file mode 100644 index 0000000..5bb7854 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/patches/vdr-1.4.3-recursion_bigpatch.diff @@ -0,0 +1,88 @@ +# If you have two or more VDRs and you like them to mutually share +# there DVB cards you might need to apply this patch first. +# +# This is a modified version of the patch for VDRs with BIGPATCH. +# Thanks to p_body@vdrportal. +# +# IMPORTANT: As this patch does not only modify streamdev-server but +# also an exported method of VDR, you will need to +# +# !!!!! RECOMPILE VDR AND ALL PLUGINS !!!!! +# +# Why do I need the patch? +# -------------------------- +# Before switching channels VDR will consider all of its devices to +# find the one with the least impact. This includes the device provided +# by the streamdev-client plugin. Streamdev-client will forward the +# request to its server which in turn checks all of its devices. Now if +# the server is running streamdev-client, too, the request will again +# be forwarded to its server and finally you will endup in a loop. +# +# What does the patch do? +# ----------------------- +# The patch adds the additional parameter "bool DVBCardsOnly" to VDR's +# device selection method cDevice::GetDevice(...). The parameter +# defaults to false which gives you the standard behaviour of GetDevice. +# When set to true, GetDevice will use only those devices with a card +# index < MAXDVBDEVICES, so only real DVB cards will be considered. +# Other devices like streamdev-client or DVB cards provided by plugin +# (Hauppauge PVR) won't be used. +# +# Author: Frank Schmirler (http://vdr.schmirler.de) +# +--- device.h.orig 2006-11-15 12:01:34.000000000 +0100 ++++ device.h 2006-11-15 12:02:15.000000000 +0100 +@@ -128,7 +128,7 @@ + ///< Gets the device with the given Index. + ///< \param Index must be in the range 0..numDevices-1. + ///< \return A pointer to the device, or NULL if the Index was invalid. +- static cDevice *GetDevice(const cChannel *Channel, int Priority = -1, bool *NeedsDetachReceivers = NULL, bool LiveView = false); ++ static cDevice *GetDevice(const cChannel *Channel, int Priority = -1, bool *NeedsDetachReceivers = NULL, bool LiveView = false, bool DVBCardsOnly = false); + ///< Returns a device that is able to receive the given Channel at the + ///< given Priority, with the least impact on active recordings and + ///< live viewing. +--- device.c.orig 2006-11-15 12:01:30.000000000 +0100 ++++ device.c 2006-11-22 12:28:05.000000000 +0100 +@@ -8,6 +8,7 @@ + */ + + #include "device.h" ++#include "dvbdevice.h" + #include <errno.h> + #include <sys/ioctl.h> + #include <sys/mman.h> +@@ -278,11 +279,13 @@ + return (0 <= Index && Index < numDevices) ? device[Index] : NULL; + } + +-cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers, bool LiveView) ++cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers, bool LiveView, bool DVBCardsOnly) + { + cDevice *d = NULL; + uint Impact = 0xFFFFFFFF; // we're looking for a device with the least impact + for (int i = 0; i < numDevices; i++) { ++ if (DVBCardsOnly && device[i]->CardIndex() >= MAXDVBDEVICES) ++ continue; + bool ndr; + if (device[i]->ProvidesChannel(Channel, Priority, &ndr)) { // this device is basicly able to do the job + // Put together an integer number that reflects the "impact" using +--- PLUGINS/src/streamdev/server/connection.c.orig 2006-11-15 12:10:11.000000000 +0100 ++++ PLUGINS/src/streamdev/server/connection.c 2006-11-15 12:10:59.000000000 +0100 +@@ -132,7 +132,7 @@ + Dprintf(" * GetDevice(const cChannel*, int)\n"); + Dprintf(" * -------------------------------\n"); + +- device = cDevice::GetDevice(Channel, Priority); ++ device = cDevice::GetDevice(Channel, Priority, NULL, NULL, true); + + Dprintf(" * Found following device: %p (%d)\n", device, + device ? device->CardIndex() + 1 : 0); +@@ -150,7 +150,7 @@ + const cChannel *current = Channels.GetByNumber(cDevice::CurrentChannel()); + isyslog("streamdev-server: Detaching current receiver"); + Detach(); +- device = cDevice::GetDevice(Channel, Priority); ++ device = cDevice::GetDevice(Channel, Priority, NULL, NULL, true); + Attach(); + Dprintf(" * Found following device: %p (%d)\n", device, + device ? device->CardIndex() + 1 : 0); diff --git a/plugins/streamdev/streamdev-cvs/patches/vdr-1.4.x-localchannelprovide.diff b/plugins/streamdev/streamdev-cvs/patches/vdr-1.4.x-localchannelprovide.diff new file mode 100644 index 0000000..857c1a2 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/patches/vdr-1.4.x-localchannelprovide.diff @@ -0,0 +1,102 @@ +# Apply this patch to VDR if you want to use a fullfeatured DVB card +# as pure output device. Infact the patch will keep VDR from using the +# tuner of any local DVB card (also budget cards). It will not affect +# other input devices like e.g. streamdev-client or DVB cards provided +# by plugins (e.g. Hauppauge PVR). +# +# By default the patch is DISABLED. There will be a new OSD menu entry +# in Setup->DVB which allows you to enable or disable the patch at any +# time. +diff -ru vdr-1.4.3.orig/config.c vdr-1.4.3/config.c +--- vdr-1.4.3.orig/config.c 2006-07-22 13:57:51.000000000 +0200 ++++ vdr-1.4.3/config.c 2006-11-16 08:16:37.000000000 +0100 +@@ -273,6 +273,7 @@ + CurrentChannel = -1; + CurrentVolume = MAXVOLUME; + CurrentDolby = 0; ++ LocalChannelProvide = 1; + InitialChannel = 0; + InitialVolume = -1; + } +@@ -434,6 +435,7 @@ + else if (!strcasecmp(Name, "CurrentChannel")) CurrentChannel = atoi(Value); + else if (!strcasecmp(Name, "CurrentVolume")) CurrentVolume = atoi(Value); + else if (!strcasecmp(Name, "CurrentDolby")) CurrentDolby = atoi(Value); ++ else if (!strcasecmp(Name, "LocalChannelProvide")) LocalChannelProvide = atoi(Value); + else if (!strcasecmp(Name, "InitialChannel")) InitialChannel = atoi(Value); + else if (!strcasecmp(Name, "InitialVolume")) InitialVolume = atoi(Value); + else +@@ -502,6 +504,7 @@ + Store("CurrentChannel", CurrentChannel); + Store("CurrentVolume", CurrentVolume); + Store("CurrentDolby", CurrentDolby); ++ Store("LocalChannelProvide",LocalChannelProvide); + Store("InitialChannel", InitialChannel); + Store("InitialVolume", InitialVolume); + +diff -ru vdr-1.4.3.orig/config.h vdr-1.4.3/config.h +--- vdr-1.4.3.orig/config.h 2006-09-23 15:56:08.000000000 +0200 ++++ vdr-1.4.3/config.h 2006-11-16 08:16:57.000000000 +0100 +@@ -250,6 +250,7 @@ + int CurrentChannel; + int CurrentVolume; + int CurrentDolby; ++ int LocalChannelProvide; + int InitialChannel; + int InitialVolume; + int __EndData__; +diff -ru vdr-1.4.3.orig/dvbdevice.c vdr-1.4.3/dvbdevice.c +--- vdr-1.4.3.orig/dvbdevice.c 2006-08-14 11:38:32.000000000 +0200 ++++ vdr-1.4.3/dvbdevice.c 2006-11-16 08:17:58.000000000 +0100 +@@ -766,6 +766,8 @@ + + bool cDvbDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers) const + { ++ if (Setup.LocalChannelProvide != 1) ++ return false; + bool result = false; + bool hasPriority = Priority < 0 || Priority > this->Priority(); + bool needsDetachReceivers = false; +diff -ru vdr-1.4.3.orig/i18n.c vdr-1.4.3/i18n.c +--- vdr-1.4.3.orig/i18n.c 2006-09-16 11:08:30.000000000 +0200 ++++ vdr-1.4.3/i18n.c 2006-11-16 08:36:53.000000000 +0100 +@@ -3546,6 +3546,28 @@ + "Foretrukket sprog", + "Preferovaný jazyk", + }, ++ { "Setup.DVB$Use DVB receivers", ++ "DVB Empfangsteile benutzen", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ }, + { "Setup.DVB$Primary DVB interface", + "Primäres DVB-Interface", + "Primarna naprava", +diff -ru vdr-1.4.3.orig/menu.c vdr-1.4.3/menu.c +--- vdr-1.4.3.orig/menu.c 2006-07-23 11:23:11.000000000 +0200 ++++ vdr-1.4.3/menu.c 2006-11-16 08:37:27.000000000 +0100 +@@ -2354,6 +2354,7 @@ + + Clear(); + ++ Add(new cMenuEditBoolItem(tr("Setup.DVB$Use DVB receivers"), &data.LocalChannelProvide)); + Add(new cMenuEditIntItem( tr("Setup.DVB$Primary DVB interface"), &data.PrimaryDVB, 1, cDevice::NumDevices())); + Add(new cMenuEditBoolItem(tr("Setup.DVB$Video format"), &data.VideoFormat, "4:3", "16:9")); + if (data.VideoFormat == 0) diff --git a/plugins/streamdev/streamdev-cvs/patches/vdr-1.6.0-ignore_missing_cam.diff b/plugins/streamdev/streamdev-cvs/patches/vdr-1.6.0-ignore_missing_cam.diff new file mode 100644 index 0000000..60d93bd --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/patches/vdr-1.6.0-ignore_missing_cam.diff @@ -0,0 +1,13 @@ +--- device.c.orig 2008-03-28 11:47:25.000000000 +0100 ++++ device.c 2008-03-28 11:47:09.000000000 +0100 +@@ -375,8 +375,8 @@ + } + } + } +- if (!NumUsableSlots) +- return NULL; // no CAM is able to decrypt this channel ++// if (!NumUsableSlots) ++// return NULL; // no CAM is able to decrypt this channel + } + + bool NeedsDetachReceivers = false; diff --git a/plugins/streamdev/streamdev-cvs/patches/vdr-1.6.0-intcamdevices.patch b/plugins/streamdev/streamdev-cvs/patches/vdr-1.6.0-intcamdevices.patch new file mode 100644 index 0000000..aab1fb4 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/patches/vdr-1.6.0-intcamdevices.patch @@ -0,0 +1,78 @@ +Index: vdr-1.6.0-nocamdevices/device.c +=================================================================== +--- vdr-1.6.0-nocamdevices/device.c ++++ vdr-1.6.0-nocamdevices/device.c 2008-04-27 18:55:37.000000000 +0300 +@@ -363,6 +363,7 @@ + int NumCamSlots = CamSlots.Count(); + int SlotPriority[NumCamSlots]; + int NumUsableSlots = 0; ++ bool InternalCamNeeded = false; + 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 +@@ -376,7 +377,7 @@ + } + } + if (!NumUsableSlots) +- return NULL; // no CAM is able to decrypt this channel ++ InternalCamNeeded = true; // no CAM is able to decrypt this channel + } + + bool NeedsDetachReceivers = false; +@@ -392,11 +393,13 @@ + continue; // this device shall be temporarily avoided + if (Channel->Ca() && Channel->Ca() <= CA_DVB_MAX && Channel->Ca() != device[i]->CardIndex() + 1) + continue; // a specific card was requested, but not this one +- if (NumUsableSlots && !CamSlots.Get(j)->Assign(device[i], true)) ++ if (InternalCamNeeded && !device[i]->HasInternalCam()) ++ continue; // no CAM is able to decrypt this channel and the device uses vdr handled CAMs ++ if (NumUsableSlots && !device[i]->HasInternalCam() && !CamSlots.Get(j)->Assign(device[i], true)) + continue; // CAM slot can't be used with this device + bool ndr; + if (device[i]->ProvidesChannel(Channel, Priority, &ndr)) { // this device is basicly able to do the job +- if (NumUsableSlots && device[i]->CamSlot() && device[i]->CamSlot() != CamSlots.Get(j)) ++ if (NumUsableSlots && !device[i]->HasInternalCam() && device[i]->CamSlot() && device[i]->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 +@@ -410,18 +413,18 @@ + imp <<= 1; imp |= device[i]->Receiving(); // avoid devices that are receiving + imp <<= 1; imp |= device[i] == cTransferControl::ReceiverDevice(); // avoid the Transfer Mode receiver device + imp <<= 8; imp |= min(max(device[i]->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 <<= 8; imp |= min(max(((NumUsableSlots && !device[i]->HasInternalCam()) ? 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[i]->IsPrimaryDevice(); // avoid the primary device +- imp <<= 1; imp |= NumUsableSlots ? 0 : device[i]->HasCi(); // avoid cards with Common Interface for FTA channels ++ imp <<= 1; imp |= (NumUsableSlots || InternalCamNeeded) ? 0 : device[i]->HasCi(); // avoid cards with Common Interface for FTA channels + imp <<= 1; imp |= device[i]->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 ++ imp <<= 1; imp |= (NumUsableSlots && !device[i]->HasInternalCam()) ? !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[i]; + NeedsDetachReceivers = ndr; +- if (NumUsableSlots) ++ if (NumUsableSlots && !device[i]->HasInternalCam()) + s = CamSlots.Get(j); + } + } +Index: vdr-1.6.0-nocamdevices/device.h +=================================================================== +--- vdr-1.6.0-nocamdevices/device.h ++++ vdr-1.6.0-nocamdevices/device.h 2008-04-27 18:55:49.000000000 +0300 +@@ -335,6 +335,12 @@ + public: + virtual bool HasCi(void); + ///< Returns true if this device has a Common Interface. ++ virtual bool HasInternalCam(void) { return false; } ++ ///< Returns true if this device handles encrypted channels itself ++ ///< without VDR assistance. This can be e.g. when the device is a ++ ///< client that gets the stream from another VDR instance that has ++ ///< already decrypted the stream. In this case ProvidesChannel() ++ ///< shall check whether the channel can be decrypted. + void SetCamSlot(cCamSlot *CamSlot); + ///< Sets the given CamSlot to be used with this device. + cCamSlot *CamSlot(void) const { return camSlot; } + diff --git a/plugins/streamdev/streamdev-cvs/patches/vdr-cap_net_raw.diff b/plugins/streamdev/streamdev-cvs/patches/vdr-cap_net_raw.diff new file mode 100644 index 0000000..2f714b1 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/patches/vdr-cap_net_raw.diff @@ -0,0 +1,11 @@ +--- vdr.c.orig 2009-02-13 09:45:55.000000000 +0100 ++++ vdr.c 2009-02-13 09:46:24.000000000 +0100 +@@ -115,7 +115,7 @@ + static bool SetCapSysTime(void) + { + // drop all capabilities except cap_sys_time +- cap_t caps = cap_from_text("= cap_sys_time=ep"); ++ cap_t caps = cap_from_text("= cap_sys_time,cap_net_raw=ep"); + if (!caps) { + fprintf(stderr, "vdr: cap_from_text failed: %s\n", strerror(errno)); + return false; diff --git a/plugins/streamdev/streamdev-cvs/po/CVS/Entries b/plugins/streamdev/streamdev-cvs/po/CVS/Entries new file mode 100644 index 0000000..2b4c7f3 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/po/CVS/Entries @@ -0,0 +1,8 @@ +/de_DE.po/1.5/Fri Feb 13 10:39:21 2009// +/fi_FI.po/1.5/Tue Jul 7 10:47:36 2009// +/fr_FR.po/1.4/Fri Feb 13 10:39:21 2009// +/it_IT.po/1.3/Fri Feb 13 10:39:21 2009// +/lt_LT.po/1.1/Tue Dec 15 13:38:29 2009// +/ru_RU.po/1.3/Fri Feb 13 10:39:21 2009// +/sk_SK.po/1.1/Tue Nov 3 11:21:15 2009// +D diff --git a/plugins/streamdev/streamdev-cvs/po/CVS/Repository b/plugins/streamdev/streamdev-cvs/po/CVS/Repository new file mode 100644 index 0000000..40242d3 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/po/CVS/Repository @@ -0,0 +1 @@ +streamdev/po diff --git a/plugins/streamdev/streamdev-cvs/po/CVS/Root b/plugins/streamdev/streamdev-cvs/po/CVS/Root new file mode 100644 index 0000000..2c7f6ce --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/po/CVS/Root @@ -0,0 +1 @@ +:pserver:anoncvs@vdr-developer.org:/var/cvsroot diff --git a/plugins/streamdev/streamdev-cvs/po/de_DE.po b/plugins/streamdev/streamdev-cvs/po/de_DE.po new file mode 100644 index 0000000..82fa808 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/po/de_DE.po @@ -0,0 +1,118 @@ +# VDR streamdev plugin language source file. +# Copyright (C) 2008 streamdev development team. See http://streamdev.vdr-developer.org +# This file is distributed under the same license as the VDR streamdev package. +# Frank Schmirler <vdrdev@schmirler.de>, 2008 +# +msgid "" +msgstr "" +"Project-Id-Version: streamdev 0.5.0\n" +"Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n" +"POT-Creation-Date: 2009-02-13 11:53+0100\n" +"PO-Revision-Date: 2008-03-30 02:11+0200\n" +"Last-Translator: Frank Schmirler <vdrdev@schmirler.de>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-15\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "VTP Streaming Client" +msgstr "VTP Streaming Client" + +msgid "Suspend Server" +msgstr "Server pausieren" + +msgid "Server is suspended" +msgstr "Server ist pausiert" + +msgid "Couldn't suspend Server!" +msgstr "Konnte Server nicht pausieren!" + +msgid "Hide Mainmenu Entry" +msgstr "Hauptmenüeintrag verstecken" + +msgid "Start Client" +msgstr "Client starten" + +msgid "Remote IP" +msgstr "IP der Gegenseite" + +msgid "Remote Port" +msgstr "Port der Gegenseite" + +msgid "Filter Streaming" +msgstr "Filter-Daten streamen" + +msgid "Synchronize EPG" +msgstr "EPG synchronisieren" + +msgid "Minimum Priority" +msgstr "Minimale Priorität" + +msgid "Maximum Priority" +msgstr "Maximale Priorität" + +msgid "VDR Streaming Server" +msgstr "VDR Streaming Server" + +msgid "Streaming active" +msgstr "Streamen im Gange" + +msgid "Suspend Live TV" +msgstr "Live-TV pausieren" + +msgid "Common Settings" +msgstr "Allgemeines" + +msgid "Maximum Number of Clients" +msgstr "Maximalanzahl an Clients" + +msgid "Suspend behaviour" +msgstr "Pausierverhalten" + +msgid "Client may suspend" +msgstr "Client darf pausieren" + +msgid "VDR-to-VDR Server" +msgstr "VDR-zu-VDR Server" + +msgid "Start VDR-to-VDR Server" +msgstr "VDR-zu-VDR Server starten" + +msgid "VDR-to-VDR Server Port" +msgstr "Port des VDR-zu-VDR Servers" + +msgid "Bind to IP" +msgstr "Binde an IP" + +msgid "HTTP Server" +msgstr "HTTP Server" + +msgid "Start HTTP Server" +msgstr "HTTP Server starten" + +msgid "HTTP Server Port" +msgstr "Port des HTTP Servers" + +msgid "HTTP Streamtype" +msgstr "HTTP Streamtyp" + +msgid "Multicast Streaming Server" +msgstr "Multicast Streaming Server" + +msgid "Start IGMP Server" +msgstr "IGMP Server starten" + +msgid "Multicast Client Port" +msgstr "Port des Multicast Clients" + +msgid "Multicast Streamtype" +msgstr "Multicast Streamtyp" + +msgid "Offer suspend mode" +msgstr "Pausieren anbieten" + +msgid "Always suspended" +msgstr "Immer pausiert" + +msgid "Never suspended" +msgstr "Nie pausiert" diff --git a/plugins/streamdev/streamdev-cvs/po/fi_FI.po b/plugins/streamdev/streamdev-cvs/po/fi_FI.po new file mode 100644 index 0000000..1cd5566 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/po/fi_FI.po @@ -0,0 +1,118 @@ +# VDR streamdev plugin language source file. +# Copyright (C) 2008 streamdev development team. See http://streamdev.vdr-developer.org +# This file is distributed under the same license as the VDR streamdev package. +# Rolf Ahrenberg <rahrenbe@cc.hut.fi>, 2008 +# +msgid "" +msgstr "" +"Project-Id-Version: streamdev 0.5.0\n" +"Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n" +"POT-Creation-Date: 2009-02-13 11:53+0100\n" +"PO-Revision-Date: 2008-03-30 02:11+0200\n" +"Last-Translator: Rolf Ahrenberg <rahrenbe@cc.hut.fi>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-15\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "VTP Streaming Client" +msgstr "VTP-suoratoistoasiakas" + +msgid "Suspend Server" +msgstr "Pysäytä palvelin" + +msgid "Server is suspended" +msgstr "Palvelin on pysäytetty" + +msgid "Couldn't suspend Server!" +msgstr "Palvelinta ei onnistuttu pysäyttämään!" + +msgid "Hide Mainmenu Entry" +msgstr "Piilota valinta päävalikosta" + +msgid "Start Client" +msgstr "Käynnistä VDR-asiakas" + +msgid "Remote IP" +msgstr "Etäkoneen IP-osoite" + +msgid "Remote Port" +msgstr "Etäkoneen portti" + +msgid "Filter Streaming" +msgstr "Suodatetun tiedon suoratoisto" + +msgid "Synchronize EPG" +msgstr "Päivitä ohjelmaopas" + +msgid "Minimum Priority" +msgstr "Pienin prioriteetti" + +msgid "Maximum Priority" +msgstr "Suurin prioriteetti" + +msgid "VDR Streaming Server" +msgstr "VDR-suoratoistopalvelin" + +msgid "Streaming active" +msgstr "Suoratoistopalvelin aktiivinen" + +msgid "Suspend Live TV" +msgstr "Pysäytä suora TV-lähetys" + +msgid "Common Settings" +msgstr "Yleiset asetukset" + +msgid "Maximum Number of Clients" +msgstr "Suurin sallittu asiakkaiden määrä" + +msgid "Suspend behaviour" +msgstr "Pysäytystoiminto" + +msgid "Client may suspend" +msgstr "Asiakas saa pysäyttää palvelimen" + +msgid "VDR-to-VDR Server" +msgstr "VDR-palvelin" + +msgid "Start VDR-to-VDR Server" +msgstr "Käynnistä VDR-palvelin" + +msgid "VDR-to-VDR Server Port" +msgstr "VDR-palvelimen portti" + +msgid "Bind to IP" +msgstr "Sido osoitteeseen" + +msgid "HTTP Server" +msgstr "HTTP-palvelin" + +msgid "Start HTTP Server" +msgstr "Käynnistä HTTP-palvelin" + +msgid "HTTP Server Port" +msgstr "HTTP-palvelimen portti" + +msgid "HTTP Streamtype" +msgstr "HTTP-lähetysmuoto" + +msgid "Multicast Streaming Server" +msgstr "Multicast-suoratoistopalvelin" + +msgid "Start IGMP Server" +msgstr "Käynnistä IGMP-palvelin" + +msgid "Multicast Client Port" +msgstr "Multicast-portti" + +msgid "Multicast Streamtype" +msgstr "Multicast-lähetysmuoto" + +msgid "Offer suspend mode" +msgstr "tyrkytä" + +msgid "Always suspended" +msgstr "aina" + +msgid "Never suspended" +msgstr "ei koskaan" diff --git a/plugins/streamdev/streamdev-cvs/po/fr_FR.po b/plugins/streamdev/streamdev-cvs/po/fr_FR.po new file mode 100644 index 0000000..3ba78c0 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/po/fr_FR.po @@ -0,0 +1,118 @@ +# VDR streamdev plugin language source file. +# Copyright (C) 2008 streamdev development team. See http://streamdev.vdr-developer.org +# This file is distributed under the same license as the VDR streamdev package. +# Frank Schmirler <vdrdev@schmirler.de>, 2008 +# +msgid "" +msgstr "" +"Project-Id-Version: streamdev 0.5.0\n" +"Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n" +"POT-Creation-Date: 2009-02-13 11:53+0100\n" +"PO-Revision-Date: 2008-03-30 02:11+0200\n" +"Last-Translator: micky979 <micky979@free.fr>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-15\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "VTP Streaming Client" +msgstr "Client de streaming VTP" + +msgid "Suspend Server" +msgstr "Suspendre le serveur" + +msgid "Server is suspended" +msgstr "Le serveur est suspendu" + +msgid "Couldn't suspend Server!" +msgstr "Impossible de suspendre le serveur!" + +msgid "Hide Mainmenu Entry" +msgstr "Masquer dans le menu principal" + +msgid "Start Client" +msgstr "Démarrage du client" + +msgid "Remote IP" +msgstr "Adresse IP du serveur" + +msgid "Remote Port" +msgstr "Port du serveur" + +msgid "Filter Streaming" +msgstr "Filtre streaming" + +msgid "Synchronize EPG" +msgstr "Synchroniser l'EPG" + +msgid "Minimum Priority" +msgstr "" + +msgid "Maximum Priority" +msgstr "" + +msgid "VDR Streaming Server" +msgstr "Serveur de streaming VDR" + +msgid "Streaming active" +msgstr "Streaming actif" + +msgid "Suspend Live TV" +msgstr "Suspendre Live TV" + +msgid "Common Settings" +msgstr "Paramètres communs" + +msgid "Maximum Number of Clients" +msgstr "Nombre maximun de clients" + +msgid "Suspend behaviour" +msgstr "Suspendre" + +msgid "Client may suspend" +msgstr "Le client peut suspendre" + +msgid "VDR-to-VDR Server" +msgstr "VDR-to-VDR Serveur" + +msgid "Start VDR-to-VDR Server" +msgstr "Démarrer le serveur VDR-to-VDR" + +msgid "VDR-to-VDR Server Port" +msgstr "Port du serveur VDR-to-VDR" + +msgid "Bind to IP" +msgstr "Attacher aux IP" + +msgid "HTTP Server" +msgstr "Serveur HTTP" + +msgid "Start HTTP Server" +msgstr "Démarrer le serveur HTTP" + +msgid "HTTP Server Port" +msgstr "Port du serveur HTTP" + +msgid "HTTP Streamtype" +msgstr "Type de Streaming HTTP" + +msgid "Multicast Streaming Server" +msgstr "" + +msgid "Start IGMP Server" +msgstr "" + +msgid "Multicast Client Port" +msgstr "" + +msgid "Multicast Streamtype" +msgstr "" + +msgid "Offer suspend mode" +msgstr "Offrir le mode suspendre" + +msgid "Always suspended" +msgstr "Toujours suspendre" + +msgid "Never suspended" +msgstr "Jamais suspendre" diff --git a/plugins/streamdev/streamdev-cvs/po/it_IT.po b/plugins/streamdev/streamdev-cvs/po/it_IT.po new file mode 100755 index 0000000..4db80ed --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/po/it_IT.po @@ -0,0 +1,120 @@ +# VDR streamdev plugin language source file. +# Copyright (C) 2008 streamdev development team. See http://streamdev.vdr-developer.org +# This file is distributed under the same license as the VDR streamdev package. +# Alberto Carraro <bertocar@tin.it>, 2001 +# Antonio Ospite <ospite@studenti.unina.it>, 2003 +# Sean Carlos <seanc@libero.it>, 2005 +# +msgid "" +msgstr "" +"Project-Id-Version: streamdev 0.5.0\n" +"Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n" +"POT-Creation-Date: 2009-02-13 11:53+0100\n" +"PO-Revision-Date: 2008-04-13 23:42+0100\n" +"Last-Translator: Diego Pierotto <vdr-italian@tiscali.it>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-15\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "VTP Streaming Client" +msgstr "Client trasmissione VTP" + +msgid "Suspend Server" +msgstr "Sospendi Server" + +msgid "Server is suspended" +msgstr "Server sospeso" + +msgid "Couldn't suspend Server!" +msgstr "Impossibile sospendere il server!" + +msgid "Hide Mainmenu Entry" +msgstr "Nascondi voce menu principale" + +msgid "Start Client" +msgstr "Avvia Client" + +msgid "Remote IP" +msgstr "Indirizzo IP del Server" + +msgid "Remote Port" +msgstr "Porta Server Remoto" + +msgid "Filter Streaming" +msgstr "Filtra trasmissione" + +msgid "Synchronize EPG" +msgstr "Sincronizza EPG" + +msgid "Minimum Priority" +msgstr "" + +msgid "Maximum Priority" +msgstr "" + +msgid "VDR Streaming Server" +msgstr "Server trasmissione VDR" + +msgid "Streaming active" +msgstr "Trasmissione attiva" + +msgid "Suspend Live TV" +msgstr "Sospendi TV dal vivo" + +msgid "Common Settings" +msgstr "Impostazioni comuni" + +msgid "Maximum Number of Clients" +msgstr "Numero massimo di Client" + +msgid "Suspend behaviour" +msgstr "Tipo sospensione" + +msgid "Client may suspend" +msgstr "Permetti sospensione al Client" + +msgid "VDR-to-VDR Server" +msgstr "Server VDR-a-VDR" + +msgid "Start VDR-to-VDR Server" +msgstr "Avvia Server VDR-a-VDR" + +msgid "VDR-to-VDR Server Port" +msgstr "Porta Server VDR-a-VDR" + +msgid "Bind to IP" +msgstr "IP associati" + +msgid "HTTP Server" +msgstr "Server HTTP" + +msgid "Start HTTP Server" +msgstr "Avvia Server HTTP" + +msgid "HTTP Server Port" +msgstr "Porta Server HTTP" + +msgid "HTTP Streamtype" +msgstr "Tipo flusso HTTP" + +msgid "Multicast Streaming Server" +msgstr "" + +msgid "Start IGMP Server" +msgstr "" + +msgid "Multicast Client Port" +msgstr "" + +msgid "Multicast Streamtype" +msgstr "" + +msgid "Offer suspend mode" +msgstr "Offri mod. sospensione" + +msgid "Always suspended" +msgstr "Sempre sospeso" + +msgid "Never suspended" +msgstr "Mai sospeso" diff --git a/plugins/streamdev/streamdev-cvs/po/lt_LT.po b/plugins/streamdev/streamdev-cvs/po/lt_LT.po new file mode 100644 index 0000000..7ba3b5c --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/po/lt_LT.po @@ -0,0 +1,118 @@ +# VDR streamdev plugin language source file. +# Copyright (C) 2008 streamdev development team. See http://streamdev.vdr-developer.org +# This file is distributed under the same license as the VDR streamdev package. +# Frank Schmirler <vdrdev@schmirler.de>, 2008 +# +msgid "" +msgstr "" +"Project-Id-Version: streamdev 0.5.0\n" +"Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n" +"POT-Creation-Date: 2009-02-13 11:53+0100\n" +"PO-Revision-Date: 2009-11-26 21:57+0200\n" +"Last-Translator: Valdemaras Pipiras <varas@ambernet.lt>\n" +"Language-Team: Lietuvių\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "VTP Streaming Client" +msgstr "VTP transliavimo standartas" + +msgid "Suspend Server" +msgstr "Sustabdyti serverį" + +msgid "Server is suspended" +msgstr "Serveris sustabdytas" + +msgid "Couldn't suspend Server!" +msgstr "Negali sustabdyti serverio!" + +msgid "Hide Mainmenu Entry" +msgstr "PaslÄ—pti pagrindinio meniu įrašą" + +msgid "Start Client" +msgstr "Paleisti klientÄ…" + +msgid "Remote IP" +msgstr "Nuotolinis IP adresas" + +msgid "Remote Port" +msgstr "Nuotolinis portas" + +msgid "Filter Streaming" +msgstr "Filtruoti transliavimÄ…" + +msgid "Synchronize EPG" +msgstr "Sinchronizuoti EPG" + +msgid "Minimum Priority" +msgstr "Minimalus prioritetas" + +msgid "Maximum Priority" +msgstr "Maksimalus prioritetas" + +msgid "VDR Streaming Server" +msgstr "VDR transliavimo serveris" + +msgid "Streaming active" +msgstr "Transliavimas vyksta" + +msgid "Suspend Live TV" +msgstr "Pristabdyti Live TV" + +msgid "Common Settings" +msgstr "Bendri nustatymai" + +msgid "Maximum Number of Clients" +msgstr "Maksimalus klientų skaiÄius" + +msgid "Suspend behaviour" +msgstr "Pristabdyti veikimÄ…" + +msgid "Client may suspend" +msgstr "Klientas gali pristabdyti" + +msgid "VDR-to-VDR Server" +msgstr "VDR-su-VDR Serveris" + +msgid "Start VDR-to-VDR Server" +msgstr "Paleisti VDR-su-VDR serverį" + +msgid "VDR-to-VDR Server Port" +msgstr "VDR-su-VDR Serverio portas" + +msgid "Bind to IP" +msgstr "PririÅ¡ti IP" + +msgid "HTTP Server" +msgstr "HTTP Serveris" + +msgid "Start HTTP Server" +msgstr "Paleisti HTTP serverį" + +msgid "HTTP Server Port" +msgstr "HTTP serverio portas" + +msgid "HTTP Streamtype" +msgstr "HTTP transliavimo tipas" + +msgid "Multicast Streaming Server" +msgstr "Multicast transliavimo serveris" + +msgid "Start IGMP Server" +msgstr "Paleisti IGMP serverį" + +msgid "Multicast Client Port" +msgstr "Multicast kliento portas" + +msgid "Multicast Streamtype" +msgstr "Multicast transliavimo tipas" + +msgid "Offer suspend mode" +msgstr "Klausti dÄ—l sustabdymo" + +msgid "Always suspended" +msgstr "Visada stabdyti" + +msgid "Never suspended" +msgstr "Niekada nestabdyti" diff --git a/plugins/streamdev/streamdev-cvs/po/ru_RU.po b/plugins/streamdev/streamdev-cvs/po/ru_RU.po new file mode 100644 index 0000000..b80fcd3 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/po/ru_RU.po @@ -0,0 +1,118 @@ +# VDR streamdev plugin language source file. +# Copyright (C) 2008 streamdev development team. See http://streamdev.vdr-developer.org +# This file is distributed under the same license as the VDR streamdev package. +# Frank Schmirler <vdrdev@schmirler.de>, 2008 +# +msgid "" +msgstr "" +"Project-Id-Version: streamdev 0.5.0\n" +"Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n" +"POT-Creation-Date: 2009-02-13 11:53+0100\n" +"PO-Revision-Date: 2008-06-26 15:36+0100\n" +"Last-Translator: Oleg Roitburd <oleg@roitburd.de>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-5\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "VTP Streaming Client" +msgstr "VTP Streaming ÚÛØÕÝâ" + +msgid "Suspend Server" +msgstr "¾áâÐÝÞÒØâì áÕàÒÕà" + +msgid "Server is suspended" +msgstr "ÁÕàÒÕà ÞáâÐÝÞÒÛÕÝ" + +msgid "Couldn't suspend Server!" +msgstr "ÝÕ ÜÞÓã ÞáâÐÝÞÒØâì áÕàÒÕà" + +msgid "Hide Mainmenu Entry" +msgstr "ÁßàïâÐâì Ò ÓÛÐÒÝÞÜ ÜÕÝî" + +msgid "Start Client" +msgstr "ÁâÐàâ ÚÛØÕÝâÐ" + +msgid "Remote IP" +msgstr "ÃÔÐÛÕÝÝëÙ IP" + +msgid "Remote Port" +msgstr "ÃÔÐÛÕÝÝëÙ ßÞàâ" + +msgid "Filter Streaming" +msgstr "ÄØÛìâà ßÞâÞÚÐ" + +msgid "Synchronize EPG" +msgstr "ÁØÝåàÞÝØ×ÐæØï EPG" + +msgid "Minimum Priority" +msgstr "" + +msgid "Maximum Priority" +msgstr "" + +msgid "VDR Streaming Server" +msgstr "VDR Streaming áÕàÒÕà" + +msgid "Streaming active" +msgstr "ÁâàØÜØÝÓ ÐÚâØÒÕÝ" + +msgid "Suspend Live TV" +msgstr "¾áâÐÝÞÒÚÐ Live TV" + +msgid "Common Settings" +msgstr "½ÐáâàÞÙÚØ" + +msgid "Maximum Number of Clients" +msgstr "¼ÐÚá. ÚÞÛØçÕáâÒÞ ÚÛØÕÝâÞÒ" + +msgid "Suspend behaviour" +msgstr "¿ÞÒÕÔÕÝØÕ ÞáâÐÝÞÒÚØ" + +msgid "Client may suspend" +msgstr "ºÛØÕÝâ ÜÞÖÕâ ÞáâÐÝÐÒÛØÒÐâì" + +msgid "VDR-to-VDR Server" +msgstr "VDR-to-VDR áÕàÒÕà" + +msgid "Start VDR-to-VDR Server" +msgstr "ÁâÐàâ VDR-to-VDR áÕàÒÕà" + +msgid "VDR-to-VDR Server Port" +msgstr "VDR-to-VDR ßÞàâ áÕàÒÕàÐ" + +msgid "Bind to IP" +msgstr "¿àØáÞÕÔØÝØâìáï Ú IP" + +msgid "HTTP Server" +msgstr "HTTP áÕàÒÕà" + +msgid "Start HTTP Server" +msgstr "ÁâÐàâ HTTP áÕàÒÕàÐ" + +msgid "HTTP Server Port" +msgstr "HTTP áÕàÒÕà ¿Þàâ" + +msgid "HTTP Streamtype" +msgstr "ÂØß HTTP ßÞâÞÚÐ" + +msgid "Multicast Streaming Server" +msgstr "" + +msgid "Start IGMP Server" +msgstr "" + +msgid "Multicast Client Port" +msgstr "" + +msgid "Multicast Streamtype" +msgstr "" + +msgid "Offer suspend mode" +msgstr "¿àÕÔÛÐÓÐâì ÞáâÐÝÞÒÚã" + +msgid "Always suspended" +msgstr "²áÕÓÔÐ ÞáâÐÝÞÒÛÕÝ" + +msgid "Never suspended" +msgstr "½ØÚÞÓÔÐ ÝÕ ÞáâÐÝÞÒÛÕÝ" diff --git a/plugins/streamdev/streamdev-cvs/po/sk_SK.po b/plugins/streamdev/streamdev-cvs/po/sk_SK.po new file mode 100644 index 0000000..9a7102e --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/po/sk_SK.po @@ -0,0 +1,121 @@ +# VDR streamdev plugin language source file. +# Copyright (C) 2009 streamdev development team. See http://streamdev.vdr-developer.org +# This file is distributed under the same license as the VDR streamdev package. +# Milan Hrala <hrala.milan@gmail.com>, 2009 +# +msgid "" +msgstr "" +"Project-Id-Version: streamdev_SK\n" +"Report-Msgid-Bugs-To: <http://www.vdr-developer.org/mantisbt/>\n" +"POT-Creation-Date: 2009-09-23 13:59+0200\n" +"PO-Revision-Date: \n" +"Last-Translator: Milan Hrala <hrala.milan@gmail.com>\n" +"Language-Team: Slovak <hrala.milan@gmail.com>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=iso-8859-2\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Slovak\n" +"X-Poedit-Country: SLOVAKIA\n" + +msgid "VTP Streaming Client" +msgstr "VTP prúdový klient" + +msgid "Suspend Server" +msgstr "Server pozastavený" + +msgid "Server is suspended" +msgstr "Server je doèasne preru¹ený" + +msgid "Couldn't suspend Server!" +msgstr "Nepodarilo sa pozastavi» Server!" + +msgid "Hide Mainmenu Entry" +msgstr "Schova» polo¾ku v hlavnom menu" + +msgid "Start Client" +msgstr "Spusti» Klienta" + +msgid "Remote IP" +msgstr "Vzdialená IP" + +msgid "Remote Port" +msgstr "Vzdialený port" + +msgid "Filter Streaming" +msgstr "filtrova» prúdy" + +msgid "Synchronize EPG" +msgstr "zosúladi» EPG" + +msgid "Minimum Priority" +msgstr "minimálna priorita" + +msgid "Maximum Priority" +msgstr "maximálna priorita" + +msgid "VDR Streaming Server" +msgstr "VDR prúdový server" + +msgid "Streaming active" +msgstr "streamovanie aktivne" + +msgid "Suspend Live TV" +msgstr "Pozastavenie ¾ivého vysielania" + +msgid "Common Settings" +msgstr "V¹eobecné nastavenia" + +msgid "Maximum Number of Clients" +msgstr "Maximály poèet klientov" + +msgid "Suspend behaviour" +msgstr "Správanie preru¹enia" + +msgid "Client may suspend" +msgstr "Klient mô¾e pozastavi»" + +msgid "VDR-to-VDR Server" +msgstr "VDR-do-VDR server" + +msgid "Start VDR-to-VDR Server" +msgstr "Spusti» VDR-do-VDR Server" + +msgid "VDR-to-VDR Server Port" +msgstr "Port serveru pre VDR-do-VDR" + +msgid "Bind to IP" +msgstr "viaza» na IP" + +msgid "HTTP Server" +msgstr "server HTTP" + +msgid "Start HTTP Server" +msgstr "Spusti» HTTP Server" + +msgid "HTTP Server Port" +msgstr "Port serveru HTTP" + +msgid "HTTP Streamtype" +msgstr "typ prúdu HTTP" + +msgid "Multicast Streaming Server" +msgstr "Multicast prúdový server" + +msgid "Start IGMP Server" +msgstr "Spusti» IGMP Server" + +msgid "Multicast Client Port" +msgstr "Port klienta Multicast" + +msgid "Multicast Streamtype" +msgstr "Multicast typ streamu" + +msgid "Offer suspend mode" +msgstr "Výber re¾ímu pozastavenia" + +msgid "Always suspended" +msgstr "V¾dy pozastavi»" + +msgid "Never suspended" +msgstr "Nikdy nepozastavi»" + diff --git a/plugins/streamdev/streamdev-cvs/remux/CVS/Entries b/plugins/streamdev/streamdev-cvs/remux/CVS/Entries new file mode 100644 index 0000000..3529acd --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/remux/CVS/Entries @@ -0,0 +1,11 @@ +/extern.c/1.11/Fri Jun 19 06:32:40 2009// +/extern.h/1.5/Fri Jun 19 06:32:40 2009// +/ts2es.c/1.4/Fri Jun 19 06:32:40 2009// +/ts2es.h/1.3/Fri Jun 19 06:32:40 2009// +/ts2pes.c/1.2/Tue Jun 30 06:04:33 2009// +/ts2pes.h/1.4/Mon Jul 6 06:11:11 2009// +/ts2ps.c/1.4/Fri Jun 19 06:32:40 2009// +/ts2ps.h/1.5/Mon Jul 6 06:14:21 2009// +/tsremux.c/1.5/Fri Jun 19 06:32:40 2009// +/tsremux.h/1.8/Thu Dec 3 07:26:13 2009// +D diff --git a/plugins/streamdev/streamdev-cvs/remux/CVS/Repository b/plugins/streamdev/streamdev-cvs/remux/CVS/Repository new file mode 100644 index 0000000..e57f4ec --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/remux/CVS/Repository @@ -0,0 +1 @@ +streamdev/remux diff --git a/plugins/streamdev/streamdev-cvs/remux/CVS/Root b/plugins/streamdev/streamdev-cvs/remux/CVS/Root new file mode 100644 index 0000000..2c7f6ce --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/remux/CVS/Root @@ -0,0 +1 @@ +:pserver:anoncvs@vdr-developer.org:/var/cvsroot diff --git a/plugins/streamdev/streamdev-cvs/remux/extern.c b/plugins/streamdev/streamdev-cvs/remux/extern.c new file mode 100644 index 0000000..3791d10 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/remux/extern.c @@ -0,0 +1,192 @@ +#include "remux/extern.h" +#include "server/server.h" +#include "server/streamer.h" +#include <vdr/tools.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <signal.h> +#include <unistd.h> + +namespace Streamdev { + +class cTSExt: public cThread { +private: + cRingBufferLinear *m_ResultBuffer; + bool m_Active; + int m_Process; + int m_Inpipe, m_Outpipe; + +protected: + virtual void Action(void); + +public: + cTSExt(cRingBufferLinear *ResultBuffer, std::string Parameter); + virtual ~cTSExt(); + + void Put(const uchar *Data, int Count); +}; + +} // namespace Streamdev +using namespace Streamdev; + +cTSExt::cTSExt(cRingBufferLinear *ResultBuffer, std::string Parameter): + m_ResultBuffer(ResultBuffer), + m_Active(false), + m_Process(-1), + m_Inpipe(0), + m_Outpipe(0) +{ + int inpipe[2]; + int outpipe[2]; + + if (pipe(inpipe) == -1) { + LOG_ERROR_STR("pipe failed"); + return; + } + + if (pipe(outpipe) == -1) { + LOG_ERROR_STR("pipe failed"); + close(inpipe[0]); + close(inpipe[1]); + return; + } + + if ((m_Process = fork()) == -1) { + LOG_ERROR_STR("fork failed"); + close(inpipe[0]); + close(inpipe[1]); + close(outpipe[0]); + close(outpipe[1]); + return; + } + + if (m_Process == 0) { + // child process + dup2(inpipe[0], STDIN_FILENO); + close(inpipe[1]); + dup2(outpipe[1], STDOUT_FILENO); + close(outpipe[0]); + + int MaxPossibleFileDescriptors = getdtablesize(); + for (int i = STDERR_FILENO + 1; i < MaxPossibleFileDescriptors; i++) + close(i); //close all dup'ed filedescriptors + + std::string cmd = std::string(opt_remux) + " " + Parameter; + if (execl("/bin/sh", "sh", "-c", cmd.c_str(), NULL) == -1) { + esyslog("streamdev-server: externremux script '%s' execution failed: %m", cmd.c_str()); + _exit(-1); + } + // should never be reached + _exit(0); + } + + close(inpipe[0]); + close(outpipe[1]); + m_Inpipe = inpipe[1]; + m_Outpipe = outpipe[0]; + Start(); +} + +cTSExt::~cTSExt() +{ + m_Active = false; + Cancel(3); + if (m_Process > 0) { + // close pipes + close(m_Outpipe); + close(m_Inpipe); + // signal and wait for termination + if (kill(m_Process, SIGINT) < 0) { + esyslog("streamdev-server: externremux SIGINT failed: %m"); + } + else { + int i = 0; + int retval; + while ((retval = waitpid(m_Process, NULL, WNOHANG)) == 0) { + + if ((++i % 20) == 0) { + esyslog("streamdev-server: externremux process won't stop - killing it"); + kill(m_Process, SIGKILL); + } + cCondWait::SleepMs(100); + } + + if (retval < 0) + esyslog("streamdev-server: externremux process waitpid failed: %m"); + else + Dprintf("streamdev-server: externremux child (%d) exited as expected\n", m_Process); + } + m_Process = -1; + } +} + +void cTSExt::Action(void) +{ + m_Active = true; + while (m_Active) { + fd_set rfds; + struct timeval tv; + + FD_ZERO(&rfds); + FD_SET(m_Outpipe, &rfds); + + while (FD_ISSET(m_Outpipe, &rfds)) { + tv.tv_sec = 2; + tv.tv_usec = 0; + if (select(m_Outpipe + 1, &rfds, NULL, NULL, &tv) == -1) { + LOG_ERROR_STR("poll failed"); + break;; + } + + if (FD_ISSET(m_Outpipe, &rfds)) { + int result; + //Read returns 0 if buffer full or EOF + bool bufferFull = m_ResultBuffer->Free() <= 0; //Free may be < 0 + while ((result = m_ResultBuffer->Read(m_Outpipe)) == 0 && bufferFull) + dsyslog("streamdev-server: buffer full while reading from externremux"); + + if (result == -1) { + if (errno != EINTR) { + LOG_ERROR_STR("read failed"); + m_Active = false; + } + break; + } + else if (result == 0) { + esyslog("streamdev-server: EOF reading from externremux"); + m_Active = false; + break; + } + } + } + } + m_Active = false; +} + + +void cTSExt::Put(const uchar *Data, int Count) +{ + if (safe_write(m_Inpipe, Data, Count) == -1) { + LOG_ERROR_STR("write failed"); + return; + } +} + +cExternRemux::cExternRemux(int VPid, const int *APids, const int *Dpids, const int *SPids, std::string Parameter): + m_ResultBuffer(new cRingBufferLinear(WRITERBUFSIZE, TS_SIZE * 2)), + m_Remux(new cTSExt(m_ResultBuffer, Parameter)) +{ + m_ResultBuffer->SetTimeouts(500, 100); +} + +cExternRemux::~cExternRemux() +{ + delete m_Remux; + delete m_ResultBuffer; +} + +int cExternRemux::Put(const uchar *Data, int Count) +{ + m_Remux->Put(Data, Count); + return Count; +} diff --git a/plugins/streamdev/streamdev-cvs/remux/extern.h b/plugins/streamdev/streamdev-cvs/remux/extern.h new file mode 100644 index 0000000..ff4ddec --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/remux/extern.h @@ -0,0 +1,28 @@ +#ifndef VDR_STREAMDEV_EXTERNREMUX_H +#define VDR_STREAMDEV_EXTERNREMUX_H + +#include "remux/tsremux.h" +#include <vdr/ringbuffer.h> +#include <string> + +namespace Streamdev { + +class cTSExt; + +class cExternRemux: public cTSRemux { +private: + cRingBufferLinear *m_ResultBuffer; + cTSExt *m_Remux; + +public: + cExternRemux(int VPid, const int *APids, const int *Dpids, const int *SPids, std::string Parameter); + virtual ~cExternRemux(); + + int Put(const uchar *Data, int Count); + uchar *Get(int &Count) { return m_ResultBuffer->Get(Count); } + void Del(int Count) { m_ResultBuffer->Del(Count); } +}; + +} // namespace Streamdev + +#endif // VDR_STREAMDEV_EXTERNREMUX_H diff --git a/plugins/streamdev/streamdev-cvs/remux/ts2es.c b/plugins/streamdev/streamdev-cvs/remux/ts2es.c new file mode 100644 index 0000000..6ff4e87 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/remux/ts2es.c @@ -0,0 +1,146 @@ +#include "remux/ts2es.h" +#include "server/streamer.h" +#include "common.h" +#include <vdr/device.h> + +// from VDR's remux.c +#define MAXNONUSEFULDATA (10*1024*1024) + +namespace Streamdev { + +class cTS2ES: public ipack { + friend void PutES(uint8_t *Buffer, int Size, void *Data); + +private: + cRingBufferLinear *m_ResultBuffer; + +public: + cTS2ES(cRingBufferLinear *ResultBuffer); + ~cTS2ES(); + + void PutTSPacket(const uint8_t *Buffer); +}; + +void PutES(uint8_t *Buffer, int Size, void *Data) +{ + cTS2ES *This = (cTS2ES*)Data; + uint8_t payl = Buffer[8] + 9 + This->start - 1; + int count = Size - payl; + + int n = This->m_ResultBuffer->Put(Buffer + payl, count); + if (n != count) + esyslog("ERROR: result buffer overflow, dropped %d out of %d byte", count - n, count); + This->start = 1; +} + +} // namespace Streamdev +using namespace Streamdev; + +cTS2ES::cTS2ES(cRingBufferLinear *ResultBuffer) +{ + m_ResultBuffer = ResultBuffer; + + init_ipack(this, IPACKS, PutES, 0); + data = (void*)this; +} + +cTS2ES::~cTS2ES() +{ + free_ipack(this); +} + +void cTS2ES::PutTSPacket(const uint8_t *Buffer) { + if (!Buffer) + return; + + if (Buffer[1] & 0x80) { // ts error + // TODO + } + + if (Buffer[1] & 0x40) { // payload start + if (plength == MMAX_PLENGTH - 6) { + plength = found - 6; + found = 0; + send_ipack(this); + reset_ipack(this); + } + } + + uint8_t off = 0; + + if (Buffer[3] & 0x20) { // adaptation field? + off = Buffer[4] + 1; + if (off + 4 > TS_SIZE - 1) + return; + } + + instant_repack((uint8_t*)(Buffer + 4 + off), TS_SIZE - 4 - off, this); +} + +cTS2ESRemux::cTS2ESRemux(int Pid): + m_Pid(Pid), + m_ResultBuffer(new cStreamdevBuffer(WRITERBUFSIZE, IPACKS)), + m_Remux(new cTS2ES(m_ResultBuffer)) +{ + m_ResultBuffer->SetTimeouts(100, 100); +} + +cTS2ESRemux::~cTS2ESRemux() +{ + delete m_Remux; + delete m_ResultBuffer; +} + +int cTS2ESRemux::Put(const uchar *Data, int Count) +{ + int used = 0; + + // Make sure we are looking at a TS packet: + + while (Count > TS_SIZE) { + if (Data[0] == TS_SYNC_BYTE && Data[TS_SIZE] == TS_SYNC_BYTE) + break; + Data++; + Count--; + used++; + } + + if (used) + esyslog("ERROR: skipped %d byte to sync on TS packet", used); + + // Convert incoming TS data into ES: + + for (int i = 0; i < Count; i += TS_SIZE) { + if (Count - i < TS_SIZE) + break; + if (Data[i] != TS_SYNC_BYTE) + break; + if (m_ResultBuffer->Free() < 2 * IPACKS) { + m_ResultBuffer->WaitForPut(); + break; // A cTS2ES might write one full packet and also a small rest + } + int pid = cTSRemux::GetPid(Data + i + 1); + if (Data[i + 3] & 0x10) { // got payload + if (m_Pid == pid) + m_Remux->PutTSPacket(Data + i); + } + used += TS_SIZE; + } + +/* + // Check if we're getting anywhere here: + if (!synced && skipped >= 0) { + if (skipped > MAXNONUSEFULDATA) { + esyslog("ERROR: no useful data seen within %d byte of video stream", skipped); + skipped = -1; + if (exitOnFailure) + cThread::EmergencyExit(true); + } + else + skipped += used; + } +*/ + + return used; +} + diff --git a/plugins/streamdev/streamdev-cvs/remux/ts2es.h b/plugins/streamdev/streamdev-cvs/remux/ts2es.h new file mode 100644 index 0000000..95eceb9 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/remux/ts2es.h @@ -0,0 +1,28 @@ +#ifndef VDR_STREAMDEV_TS2ESREMUX_H +#define VDR_STREAMDEV_TS2ESREMUX_H + +#include "remux/tsremux.h" +#include "server/streamer.h" + +namespace Streamdev { + +class cTS2ES; + +class cTS2ESRemux: public cTSRemux { +private: + int m_Pid; + cStreamdevBuffer *m_ResultBuffer; + cTS2ES *m_Remux; + +public: + cTS2ESRemux(int Pid); + virtual ~cTS2ESRemux(); + + int Put(const uchar *Data, int Count); + uchar *Get(int &Count) { return m_ResultBuffer->Get(Count); } + void Del(int Count) { m_ResultBuffer->Del(Count); } +}; + +} // namespace Streamdev + +#endif // VDR_STREAMDEV_TS2ESREMUX_H diff --git a/plugins/streamdev/streamdev-cvs/remux/ts2pes.c b/plugins/streamdev/streamdev-cvs/remux/ts2pes.c new file mode 100644 index 0000000..eeb56d5 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/remux/ts2pes.c @@ -0,0 +1,2017 @@ +/* + * ts2pes.c: A streaming MPEG2 remultiplexer + * + * This file is based on remux.c from Klaus Schmidinger's VDR, version 1.6.0. + * + * The parts of this code that implement cTS2PES have been taken from + * the Linux DVB driver's 'tuxplayer' example and were rewritten to suit + * VDR's needs. + * + * The cRepacker family's code was originally written by Reinhard Nissl <rnissl@gmx.de>, + * and adapted to the VDR coding style by Klaus.Schmidinger@cadsoft.de. + * + * $Id: ts2pes.c,v 1.2 2009/06/30 06:04:33 schmirl Exp $ + */ + +#include "remux/ts2pes.h" +#include <stdlib.h> +#include <vdr/channels.h> +#include <vdr/shutdown.h> + +namespace Streamdev { + +// --- cRepacker ------------------------------------------------------------- + +#define MIN_LOG_INTERVAL 10 // min. # of seconds between two consecutive log messages of a cRepacker +#define LOG(a...) (LogAllowed() && (esyslog(a), true)) + +class cRepacker { +protected: + bool initiallySyncing; + int maxPacketSize; + uint8_t subStreamId; + time_t lastLog; + int suppressedLogMessages; + bool LogAllowed(void); + void DroppedData(const char *Reason, int Count) { LOG("%s (dropped %d bytes)", Reason, Count); } +public: + static int Put(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count, int CapacityNeeded); + cRepacker(void); + virtual ~cRepacker() {} + virtual void Reset(void) { initiallySyncing = true; } + virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) = 0; + virtual int BreakAt(const uchar *Data, int Count) = 0; + virtual int QuerySnoopSize(void) { return 0; } + void SetMaxPacketSize(int MaxPacketSize) { maxPacketSize = MaxPacketSize; } + void SetSubStreamId(uint8_t SubStreamId) { subStreamId = SubStreamId; } + }; + +cRepacker::cRepacker(void) +{ + initiallySyncing = true; + maxPacketSize = 6 + 65535; + subStreamId = 0; + suppressedLogMessages = 0;; + lastLog = 0; +} + +bool cRepacker::LogAllowed(void) +{ + bool Allowed = time(NULL) - lastLog >= MIN_LOG_INTERVAL; + lastLog = time(NULL); + if (Allowed) { + if (suppressedLogMessages) { + esyslog("%d cRepacker messages suppressed", suppressedLogMessages); + suppressedLogMessages = 0; + } + } + else + suppressedLogMessages++; + return Allowed; +} + +int cRepacker::Put(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count, int CapacityNeeded) +{ + if (CapacityNeeded >= Count && ResultBuffer->Free() < CapacityNeeded) { + esyslog("ERROR: possible result buffer overflow, dropped %d out of %d byte", CapacityNeeded, CapacityNeeded); + return 0; + } + int n = ResultBuffer->Put(Data, Count); + if (n != Count) + esyslog("ERROR: result buffer overflow, dropped %d out of %d byte", Count - n, Count); + return n; +} + +// --- cCommonRepacker ------------------------------------------------------- + +class cCommonRepacker : public cRepacker { +protected: + int skippedBytes; + int packetTodo; + uchar fragmentData[6 + 65535 + 3]; + int fragmentLen; + uchar pesHeader[6 + 3 + 255 + 3]; + int pesHeaderLen; + uchar pesHeaderBackup[6 + 3 + 255]; + int pesHeaderBackupLen; + uint32_t scanner; + uint32_t localScanner; + int localStart; + bool PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); + virtual int QuerySnoopSize() { return 4; } + virtual void Reset(void); + }; + +void cCommonRepacker::Reset(void) +{ + cRepacker::Reset(); + skippedBytes = 0; + packetTodo = 0; + fragmentLen = 0; + pesHeaderLen = 0; + pesHeaderBackupLen = 0; + localStart = -1; +} + +bool cCommonRepacker::PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) +{ + // enter packet length into PES header ... + if (fragmentLen > 0) { // ... which is contained in the fragment buffer + // determine PES packet payload + int PacketLen = fragmentLen + Count - 6; + fragmentData[ 4 ] = PacketLen >> 8; + fragmentData[ 5 ] = PacketLen & 0xFF; + // just skip packets with no payload + int PesPayloadOffset = 0; + if (AnalyzePesHeader(fragmentData, fragmentLen, PesPayloadOffset) <= phInvalid) + LOG("cCommonRepacker: invalid PES packet encountered in fragment buffer!"); + else if (6 + PacketLen <= PesPayloadOffset) { + fragmentLen = 0; + return true; // skip empty packet + } + // amount of data to put into result buffer: a negative Count value means + // to strip off any partially contained start code. + int Bite = fragmentLen + (Count >= 0 ? 0 : Count); + // put data into result buffer + int n = Put(ResultBuffer, fragmentData, Bite, 6 + PacketLen); + fragmentLen = 0; + if (n != Bite) + return false; + } + else if (pesHeaderLen > 0) { // ... which is contained in the PES header buffer + int PacketLen = pesHeaderLen + Count - 6; + pesHeader[ 4 ] = PacketLen >> 8; + pesHeader[ 5 ] = PacketLen & 0xFF; + // just skip packets with no payload + int PesPayloadOffset = 0; + if (AnalyzePesHeader(pesHeader, pesHeaderLen, PesPayloadOffset) <= phInvalid) + LOG("cCommonRepacker: invalid PES packet encountered in header buffer!"); + else if (6 + PacketLen <= PesPayloadOffset) { + pesHeaderLen = 0; + return true; // skip empty packet + } + // amount of data to put into result buffer: a negative Count value means + // to strip off any partially contained start code. + int Bite = pesHeaderLen + (Count >= 0 ? 0 : Count); + // put data into result buffer + int n = Put(ResultBuffer, pesHeader, Bite, 6 + PacketLen); + pesHeaderLen = 0; + if (n != Bite) + return false; + } + // append further payload + if (Count > 0) { + // amount of data to put into result buffer + int Bite = Count; + // put data into result buffer + int n = Put(ResultBuffer, Data, Bite, Bite); + if (n != Bite) + return false; + } + // we did it ;-) + return true; +} + +// --- cVideoRepacker -------------------------------------------------------- + +class cVideoRepacker : public cCommonRepacker { +private: + enum eState { + syncing, + findPicture, + scanPicture + }; + int state; + void HandleStartCode(const uchar *const Data, cRingBufferLinear *const ResultBuffer, const uchar *&Payload, const uchar StreamID, const ePesHeader MpegLevel); + inline bool ScanDataForStartCodeSlow(const uchar *const Data); + inline bool ScanDataForStartCodeFast(const uchar *&Data, const uchar *Limit); + inline bool ScanDataForStartCode(const uchar *&Data, int &Done, int &Todo); + inline void AdjustCounters(const int Delta, int &Done, int &Todo); + inline bool ScanForEndOfPictureSlow(const uchar *&Data); + inline bool ScanForEndOfPictureFast(const uchar *&Data, const uchar *Limit); + inline bool ScanForEndOfPicture(const uchar *&Data, const uchar *Limit); +public: + cVideoRepacker(void); + virtual void Reset(void); + virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); + virtual int BreakAt(const uchar *Data, int Count); + }; + +cVideoRepacker::cVideoRepacker(void) +{ + Reset(); +} + +void cVideoRepacker::Reset(void) +{ + cCommonRepacker::Reset(); + scanner = 0xFFFFFFFF; + state = syncing; +} + +void cVideoRepacker::HandleStartCode(const uchar *const Data, cRingBufferLinear *const ResultBuffer, const uchar *&Payload, const uchar StreamID, const ePesHeader MpegLevel) +{ + // synchronisation is detected some bytes after frame start. + const int SkippedBytesLimit = 4; + + // which kind of start code have we got? + switch (*Data) { + case 0xB9 ... 0xFF: // system start codes + LOG("cVideoRepacker: found system start code: stream seems to be scrambled or not demultiplexed"); + break; + case 0xB0 ... 0xB1: // reserved start codes + case 0xB6: + LOG("cVideoRepacker: found reserved start code: stream seems to be scrambled"); + break; + case 0xB4: // sequence error code + LOG("cVideoRepacker: found sequence error code: stream seems to be damaged"); + case 0xB2: // user data start code + case 0xB5: // extension start code + break; + case 0xB7: // sequence end code + case 0xB3: // sequence header code + case 0xB8: // group start code + case 0x00: // picture start code + if (state == scanPicture) { + // the above start codes indicate that the current picture is done. So + // push out the packet to start a new packet for the next picuture. If + // the byte count get's negative then the current buffer ends in a + // partitial start code that must be stripped off, as it shall be put + // in the next packet. + PushOutPacket(ResultBuffer, Payload, Data - 3 - Payload); + // go on with syncing to the next picture + state = syncing; + } + if (state == syncing) { + if (initiallySyncing) // omit report for the typical initial case + initiallySyncing = false; + else if (skippedBytes > SkippedBytesLimit) // report that syncing dropped some bytes + LOG("cVideoRepacker: skipped %d bytes to sync on next picture", skippedBytes - SkippedBytesLimit); + skippedBytes = 0; + // if there is a PES header available, then use it ... + if (pesHeaderBackupLen > 0) { + // ISO 13818-1 says: + // In the case of video, if a PTS is present in a PES packet header + // it shall refer to the access unit containing the first picture start + // code that commences in this PES packet. A picture start code commences + // in PES packet if the first byte of the picture start code is present + // in the PES packet. + memcpy(pesHeader, pesHeaderBackup, pesHeaderBackupLen); + pesHeaderLen = pesHeaderBackupLen; + pesHeaderBackupLen = 0; + } + else { + // ... otherwise create a continuation PES header + pesHeaderLen = 0; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x01; + pesHeader[pesHeaderLen++] = StreamID; // video stream ID + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + + if (MpegLevel == phMPEG2) { + pesHeader[pesHeaderLen++] = 0x80; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + } + else + pesHeader[pesHeaderLen++] = 0x0F; + } + // append the first three bytes of the start code + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x01; + // the next packet's payload will begin with the fourth byte of + // the start code (= the actual code) + Payload = Data; + // as there is no length information available, assume the + // maximum we can hold in one PES packet + packetTodo = maxPacketSize - pesHeaderLen; + // go on with finding the picture data + state++; + } + break; + case 0x01 ... 0xAF: // slice start codes + if (state == findPicture) { + // go on with scanning the picture data + state++; + } + break; + } +} + +bool cVideoRepacker::ScanDataForStartCodeSlow(const uchar *const Data) +{ + scanner <<= 8; + bool FoundStartCode = (scanner == 0x00000100); + scanner |= *Data; + return FoundStartCode; +} + +bool cVideoRepacker::ScanDataForStartCodeFast(const uchar *&Data, const uchar *Limit) +{ + Limit--; + + while (Data < Limit && (Data = (const uchar *)memchr(Data, 0x01, Limit - Data))) { + if (Data[-2] || Data[-1]) + Data += 3; + else { + scanner = 0x00000100 | *++Data; + return true; + } + } + + Data = Limit; + uint32_t *Scanner = (uint32_t *)(Data - 3); + scanner = ntohl(*Scanner); + return false; +} + +bool cVideoRepacker::ScanDataForStartCode(const uchar *&Data, int &Done, int &Todo) +{ + const uchar *const DataOrig = Data; + const int MinDataSize = 4; + + if (Todo < MinDataSize || (state != syncing && packetTodo < MinDataSize)) + return ScanDataForStartCodeSlow(Data); + + int Limit = Todo; + if (state != syncing && Limit > packetTodo) + Limit = packetTodo; + + if (ScanDataForStartCodeSlow(Data)) + return true; + + if (ScanDataForStartCodeSlow(++Data)) { + AdjustCounters(1, Done, Todo); + return true; + } + ++Data; + + bool FoundStartCode = ScanDataForStartCodeFast(Data, DataOrig + Limit); + AdjustCounters(Data - DataOrig, Done, Todo); + return FoundStartCode; +} + +void cVideoRepacker::AdjustCounters(const int Delta, int &Done, int &Todo) +{ + Done += Delta; + Todo -= Delta; + + if (state <= syncing) + skippedBytes += Delta; + else + packetTodo -= Delta; +} + +void cVideoRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) +{ + // synchronisation is detected some bytes after frame start. + const int SkippedBytesLimit = 4; + + // reset local scanner + localStart = -1; + + int pesPayloadOffset = 0; + bool continuationHeader = false; + ePesHeader mpegLevel = AnalyzePesHeader(Data, Count, pesPayloadOffset, &continuationHeader); + if (mpegLevel <= phInvalid) { + DroppedData("cVideoRepacker: no valid PES packet header found", Count); + return; + } + if (!continuationHeader) { + // backup PES header + pesHeaderBackupLen = pesPayloadOffset; + memcpy(pesHeaderBackup, Data, pesHeaderBackupLen); + } + + // skip PES header + int done = pesPayloadOffset; + int todo = Count - done; + const uchar *data = Data + done; + // remember start of the data + const uchar *payload = data; + + while (todo > 0) { + // collect number of skipped bytes while syncing + if (state <= syncing) + skippedBytes++; + // did we reach a start code? + if (ScanDataForStartCode(data, done, todo)) + HandleStartCode(data, ResultBuffer, payload, Data[3], mpegLevel); + // move on + data++; + done++; + todo--; + // do we have to start a new packet as there is no more space left? + if (state != syncing && --packetTodo <= 0) { + // we connot start a new packet here if the current might end in a start + // code and this start code shall possibly be put in the next packet. So + // overfill the current packet until we can safely detect that we won't + // break a start code into pieces: + // + // A) the last four bytes were a start code. + // B) the current byte introduces a start code. + // C) the last three bytes begin a start code. + // + // Todo : Data : Rule : Result + // -----:-------------------------------:------:------- + // : XX 00 00 00 01 YY|YY YY YY YY : : + // 0 : ^^| : A : push + // -----:-------------------------------:------:------- + // : XX XX 00 00 00 01|YY YY YY YY : : + // 0 : ^^| : B : wait + // -1 : |^^ : A : push + // -----:-------------------------------:------:------- + // : XX XX XX 00 00 00|01 YY YY YY : : + // 0 : ^^| : C : wait + // -1 : |^^ : B : wait + // -2 : | ^^ : A : push + // -----:-------------------------------:------:------- + // : XX XX XX XX 00 00|00 01 YY YY : : + // 0 : ^^| : C : wait + // -1 : |^^ : C : wait + // -2 : | ^^ : B : wait + // -3 : | ^^ : A : push + // -----:-------------------------------:------:------- + // : XX XX XX XX XX 00|00 00 01 YY : : + // 0 : ^^| : C : wait + // -1 : |^^ : C : wait + // -2 : | ^^ : : push + // -----:-------------------------------:------:------- + bool A = ((scanner & 0xFFFFFF00) == 0x00000100); + bool B = ((scanner & 0xFFFFFF) == 0x000001); + bool C = ((scanner & 0xFF) == 0x00) && (packetTodo >= -1); + if (A || (!B && !C)) { + // actually we cannot push out an overfull packet. So we'll have to + // adjust the byte count and payload start as necessary. If the byte + // count get's negative we'll have to append the excess from fragment's + // tail to the next PES header. + int bite = data + packetTodo - payload; + const uchar *excessData = fragmentData + fragmentLen + bite; + // a negative byte count means to drop some bytes from the current + // fragment's tail, to not exceed the maximum packet size. + PushOutPacket(ResultBuffer, payload, bite); + // create a continuation PES header + pesHeaderLen = 0; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x01; + pesHeader[pesHeaderLen++] = Data[3]; // video stream ID + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + + if (mpegLevel == phMPEG2) { + pesHeader[pesHeaderLen++] = 0x80; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + } + else + pesHeader[pesHeaderLen++] = 0x0F; + + // copy any excess data + while (bite++ < 0) { + // append the excess data here + pesHeader[pesHeaderLen++] = *excessData++; + packetTodo++; + } + // the next packet's payload will begin here + payload = data + packetTodo; + // as there is no length information available, assume the + // maximum we can hold in one PES packet + packetTodo += maxPacketSize - pesHeaderLen; + } + } + } + // the packet is done. Now store any remaining data into fragment buffer + // if we are no longer syncing. + if (state != syncing) { + // append the PES header ... + int bite = pesHeaderLen; + pesHeaderLen = 0; + if (bite > 0) { + memcpy(fragmentData + fragmentLen, pesHeader, bite); + fragmentLen += bite; + } + // append payload. It may contain part of a start code at it's end, + // which will be removed when the next packet gets processed. + bite = data - payload; + if (bite > 0) { + memcpy(fragmentData + fragmentLen, payload, bite); + fragmentLen += bite; + } + } + // report that syncing dropped some bytes + if (skippedBytes > SkippedBytesLimit) { + if (!initiallySyncing) // omit report for the typical initial case + LOG("cVideoRepacker: skipped %d bytes while syncing on next picture", skippedBytes - SkippedBytesLimit); + skippedBytes = SkippedBytesLimit; + } +} + +bool cVideoRepacker::ScanForEndOfPictureSlow(const uchar *&Data) +{ + localScanner <<= 8; + localScanner |= *Data++; + // check start codes which follow picture data + switch (localScanner) { + case 0x00000100: // picture start code + case 0x000001B8: // group start code + case 0x000001B3: // sequence header code + case 0x000001B7: // sequence end code + return true; + } + return false; +} + +bool cVideoRepacker::ScanForEndOfPictureFast(const uchar *&Data, const uchar *Limit) +{ + Limit--; + + while (Data < Limit && (Data = (const uchar *)memchr(Data, 0x01, Limit - Data))) { + if (Data[-2] || Data[-1]) + Data += 3; + else { + localScanner = 0x00000100 | *++Data; + // check start codes which follow picture data + switch (localScanner) { + case 0x00000100: // picture start code + case 0x000001B8: // group start code + case 0x000001B3: // sequence header code + case 0x000001B7: // sequence end code + Data++; + return true; + default: + Data += 3; + } + } + } + + Data = Limit + 1; + uint32_t *LocalScanner = (uint32_t *)(Data - 4); + localScanner = ntohl(*LocalScanner); + return false; +} + +bool cVideoRepacker::ScanForEndOfPicture(const uchar *&Data, const uchar *Limit) +{ + const uchar *const DataOrig = Data; + const int MinDataSize = 4; + bool FoundEndOfPicture; + + if (Limit - Data <= MinDataSize) { + FoundEndOfPicture = false; + while (Data < Limit) { + if (ScanForEndOfPictureSlow(Data)) { + FoundEndOfPicture = true; + break; + } + } + } + else { + FoundEndOfPicture = true; + if (!ScanForEndOfPictureSlow(Data)) { + if (!ScanForEndOfPictureSlow(Data)) { + if (!ScanForEndOfPictureFast(Data, Limit)) + FoundEndOfPicture = false; + } + } + } + + localStart += (Data - DataOrig); + return FoundEndOfPicture; +} + +int cVideoRepacker::BreakAt(const uchar *Data, int Count) +{ + if (initiallySyncing) + return -1; // fill the packet buffer completely until we have synced once + + int PesPayloadOffset = 0; + + if (AnalyzePesHeader(Data, Count, PesPayloadOffset) <= phInvalid) + return -1; // not enough data for test + + // just detect end of picture + if (state == scanPicture) { + // setup local scanner + if (localStart < 0) { + localScanner = scanner; + localStart = 0; + } + // start where we've stopped at the last run + const uchar *data = Data + PesPayloadOffset + localStart; + const uchar *limit = Data + Count; + // scan data + if (ScanForEndOfPicture(data, limit)) + return data - Data; + } + // just fill up packet and append next start code + return PesPayloadOffset + packetTodo + 4; +} + +// --- cAudioRepacker -------------------------------------------------------- + +class cAudioRepacker : public cCommonRepacker { +private: + static int bitRates[2][3][16]; + enum eState { + syncing, + scanFrame + }; + int state; + int frameTodo; + int frameSize; + int cid; + static bool IsValidAudioHeader(uint32_t Header, bool Mpeg2, int *FrameSize = NULL); +public: + cAudioRepacker(int Cid); + virtual void Reset(void); + virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); + virtual int BreakAt(const uchar *Data, int Count); + }; + +int cAudioRepacker::bitRates[2][3][16] = { // all values are specified as kbits/s + { + { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1 }, // MPEG 1, Layer I + { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1 }, // MPEG 1, Layer II + { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1 } // MPEG 1, Layer III + }, + { + { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1 }, // MPEG 2, Layer I + { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 }, // MPEG 2, Layer II/III + { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 } // MPEG 2, Layer II/III + } + }; + +cAudioRepacker::cAudioRepacker(int Cid) +{ + cid = Cid; + Reset(); +} + +void cAudioRepacker::Reset(void) +{ + cCommonRepacker::Reset(); + scanner = 0; + state = syncing; + frameTodo = 0; + frameSize = 0; +} + +bool cAudioRepacker::IsValidAudioHeader(uint32_t Header, bool Mpeg2, int *FrameSize) +{ + int syncword = (Header & 0xFFF00000) >> 20; + int id = (Header & 0x00080000) >> 19; + int layer = (Header & 0x00060000) >> 17; +//int protection_bit = (Header & 0x00010000) >> 16; + int bitrate_index = (Header & 0x0000F000) >> 12; + int sampling_frequency = (Header & 0x00000C00) >> 10; + int padding_bit = (Header & 0x00000200) >> 9; +//int private_bit = (Header & 0x00000100) >> 8; +//int mode = (Header & 0x000000C0) >> 6; +//int mode_extension = (Header & 0x00000030) >> 4; +//int copyright = (Header & 0x00000008) >> 3; +//int orignal_copy = (Header & 0x00000004) >> 2; + int emphasis = (Header & 0x00000003); + + if (syncword != 0xFFF) + return false; + + if (id == 0 && !Mpeg2) // reserved in MPEG 1 + return false; + + if (layer == 0) // reserved + return false; + + if (bitrate_index == 0xF) // forbidden + return false; + + if (sampling_frequency == 3) // reserved + return false; + + if (emphasis == 2) // reserved + return false; + + if (FrameSize) { + if (bitrate_index == 0) + *FrameSize = 0; + else { + static int samplingFrequencies[2][4] = { // all values are specified in Hz + { 44100, 48000, 32000, -1 }, // MPEG 1 + { 22050, 24000, 16000, -1 } // MPEG 2 + }; + + static int slots_per_frame[2][3] = { + { 12, 144, 144 }, // MPEG 1, Layer I, II, III + { 12, 144, 72 } // MPEG 2, Layer I, II, III + }; + + int mpegIndex = 1 - id; + int layerIndex = 3 - layer; + + // Layer I (i. e., layerIndex == 0) has a larger slot size + int slotSize = (layerIndex == 0) ? 4 : 1; // bytes + + int br = 1000 * bitRates[mpegIndex][layerIndex][bitrate_index]; // bits/s + int sf = samplingFrequencies[mpegIndex][sampling_frequency]; + + int N = slots_per_frame[mpegIndex][layerIndex] * br / sf; // slots + + *FrameSize = (N + padding_bit) * slotSize; // bytes + } + } + + return true; +} + +void cAudioRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) +{ + // synchronisation is detected some bytes after frame start. + const int SkippedBytesLimit = 4; + + // reset local scanner + localStart = -1; + + int pesPayloadOffset = 0; + bool continuationHeader = false; + ePesHeader mpegLevel = AnalyzePesHeader(Data, Count, pesPayloadOffset, &continuationHeader); + if (mpegLevel <= phInvalid) { + DroppedData("cAudioRepacker: no valid PES packet header found", Count); + return; + } + if (!continuationHeader) { + // backup PES header + pesHeaderBackupLen = pesPayloadOffset; + memcpy(pesHeaderBackup, Data, pesHeaderBackupLen); + } + + // skip PES header + int done = pesPayloadOffset; + int todo = Count - done; + const uchar *data = Data + done; + // remember start of the data + const uchar *payload = data; + + while (todo > 0) { + // collect number of skipped bytes while syncing + if (state <= syncing) + skippedBytes++; + // did we reach an audio frame header? + scanner <<= 8; + scanner |= *data; + if ((scanner & 0xFFF00000) == 0xFFF00000) { + if (frameTodo <= 0 && (frameSize == 0 || skippedBytes >= 4) && IsValidAudioHeader(scanner, mpegLevel == phMPEG2, &frameSize)) { + if (state == scanFrame) { + // As a new audio frame starts here, the previous one is done. So push + // out the packet to start a new packet for the next audio frame. If + // the byte count gets negative then the current buffer ends in a + // partitial audio frame header that must be stripped off, as it shall + // be put in the next packet. + PushOutPacket(ResultBuffer, payload, data - 3 - payload); + // go on with syncing to the next audio frame + state = syncing; + } + if (state == syncing) { + if (initiallySyncing) // omit report for the typical initial case + initiallySyncing = false; + else if (skippedBytes > SkippedBytesLimit) // report that syncing dropped some bytes + LOG("cAudioRepacker(0x%02X): skipped %d bytes to sync on next audio frame", cid, skippedBytes - SkippedBytesLimit); + skippedBytes = 0; + // if there is a PES header available, then use it ... + if (pesHeaderBackupLen > 0) { + // ISO 13818-1 says: + // In the case of audio, if a PTS is present in a PES packet header + // it shall refer to the access unit commencing in the PES packet. An + // audio access unit commences in a PES packet if the first byte of + // the audio access unit is present in the PES packet. + memcpy(pesHeader, pesHeaderBackup, pesHeaderBackupLen); + pesHeaderLen = pesHeaderBackupLen; + pesHeaderBackupLen = 0; + } + else { + // ... otherwise create a continuation PES header + pesHeaderLen = 0; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x01; + pesHeader[pesHeaderLen++] = Data[3]; // audio stream ID + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + + if (mpegLevel == phMPEG2) { + pesHeader[pesHeaderLen++] = 0x80; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + } + else + pesHeader[pesHeaderLen++] = 0x0F; + } + // append the first three bytes of the audio frame header + pesHeader[pesHeaderLen++] = 0xFF; + pesHeader[pesHeaderLen++] = (scanner >> 16) & 0xFF; + pesHeader[pesHeaderLen++] = (scanner >> 8) & 0xFF; + // the next packet's payload will begin with the fourth byte of + // the audio frame header (= the actual byte) + payload = data; + // maximum we can hold in one PES packet + packetTodo = maxPacketSize - pesHeaderLen; + // expected remainder of audio frame: so far we have read 3 bytes from the frame header + frameTodo = frameSize - 3; + // go on with collecting the frame's data + state++; + } + } + } + data++; + done++; + todo--; + // do we have to start a new packet as the current is done? + if (frameTodo > 0) { + if (--frameTodo == 0) { + // the current audio frame is is done now. So push out the packet to + // start a new packet for the next audio frame. + PushOutPacket(ResultBuffer, payload, data - payload); + // go on with syncing to the next audio frame + state = syncing; + } + } + // do we have to start a new packet as there is no more space left? + if (state != syncing && --packetTodo <= 0) { + // We connot start a new packet here if the current might end in an audio + // frame header and this header shall possibly be put in the next packet. So + // overfill the current packet until we can safely detect that we won't + // break an audio frame header into pieces: + // + // A) the last four bytes were an audio frame header. + // B) the last three bytes introduce an audio frame header. + // C) the last two bytes introduce an audio frame header. + // D) the last byte introduces an audio frame header. + // + // Todo : Data : Rule : Result + // -----:-------------------------------:------:------- + // : XX XX FF Fz zz zz|YY YY YY YY : : + // 0 : ^^| : A : push + // -----:-------------------------------:------:------- + // : XX XX XX FF Fz zz|zz YY YY YY : : + // 0 : ^^| : B : wait + // -1 : |^^ : A : push + // -----:-------------------------------:------:------- + // : XX XX XX XX FF Fz|zz zz YY YY : : + // 0 : ^^| : C : wait + // -1 : |^^ : B : wait + // -2 : | ^^ : A : push + // -----:-------------------------------:------:------- + // : XX XX XX XX XX FF|Fz zz zz YY : : + // 0 : ^^| : D : wait + // -1 : |^^ : C : wait + // -2 : | ^^ : B : wait + // -3 : | ^^ : A : push + // -----:-------------------------------:------:------- + bool A = ((scanner & 0xFFF00000) == 0xFFF00000); + bool B = ((scanner & 0xFFF000) == 0xFFF000); + bool C = ((scanner & 0xFFF0) == 0xFFF0); + bool D = ((scanner & 0xFF) == 0xFF); + if (A || (!B && !C && !D)) { + // Actually we cannot push out an overfull packet. So we'll have to + // adjust the byte count and payload start as necessary. If the byte + // count gets negative we'll have to append the excess from fragment's + // tail to the next PES header. + int bite = data + packetTodo - payload; + const uchar *excessData = fragmentData + fragmentLen + bite; + // A negative byte count means to drop some bytes from the current + // fragment's tail, to not exceed the maximum packet size. + PushOutPacket(ResultBuffer, payload, bite); + // create a continuation PES header + pesHeaderLen = 0; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x01; + pesHeader[pesHeaderLen++] = Data[3]; // audio stream ID + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + + if (mpegLevel == phMPEG2) { + pesHeader[pesHeaderLen++] = 0x80; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + } + else + pesHeader[pesHeaderLen++] = 0x0F; + + // copy any excess data + while (bite++ < 0) { + // append the excess data here + pesHeader[pesHeaderLen++] = *excessData++; + packetTodo++; + } + // the next packet's payload will begin here + payload = data + packetTodo; + // as there is no length information available, assume the + // maximum we can hold in one PES packet + packetTodo += maxPacketSize - pesHeaderLen; + } + } + } + // The packet is done. Now store any remaining data into fragment buffer + // if we are no longer syncing. + if (state != syncing) { + // append the PES header ... + int bite = pesHeaderLen; + pesHeaderLen = 0; + if (bite > 0) { + memcpy(fragmentData + fragmentLen, pesHeader, bite); + fragmentLen += bite; + } + // append payload. It may contain part of an audio frame header at it's + // end, which will be removed when the next packet gets processed. + bite = data - payload; + if (bite > 0) { + memcpy(fragmentData + fragmentLen, payload, bite); + fragmentLen += bite; + } + } + // report that syncing dropped some bytes + if (skippedBytes > SkippedBytesLimit) { + if (!initiallySyncing) // omit report for the typical initial case + LOG("cAudioRepacker(0x%02X): skipped %d bytes while syncing on next audio frame", cid, skippedBytes - SkippedBytesLimit); + skippedBytes = SkippedBytesLimit; + } +} + +int cAudioRepacker::BreakAt(const uchar *Data, int Count) +{ + if (initiallySyncing) + return -1; // fill the packet buffer completely until we have synced once + + int PesPayloadOffset = 0; + + ePesHeader MpegLevel = AnalyzePesHeader(Data, Count, PesPayloadOffset); + if (MpegLevel <= phInvalid) + return -1; // not enough data for test + + // determine amount of data to fill up packet and to append next audio frame header + int packetRemainder = PesPayloadOffset + packetTodo + 4; + + // just detect end of an audio frame + if (state == scanFrame) { + // when remaining audio frame size is known, then omit scanning + if (frameTodo > 0) { + // determine amount of data to fill up audio frame and to append next audio frame header + int remaining = PesPayloadOffset + frameTodo + 4; + if (remaining < packetRemainder) + return remaining; + return packetRemainder; + } + // setup local scanner + if (localStart < 0) { + localScanner = scanner; + localStart = 0; + } + // start where we've stopped at the last run + const uchar *data = Data + PesPayloadOffset + localStart; + const uchar *limit = Data + Count; + // scan data + while (data < limit) { + localStart++; + localScanner <<= 8; + localScanner |= *data++; + // check whether the next audio frame follows + if (((localScanner & 0xFFF00000) == 0xFFF00000) && IsValidAudioHeader(localScanner, MpegLevel == phMPEG2)) + return data - Data; + } + } + // just fill up packet and append next audio frame header + return packetRemainder; +} + +// --- cDolbyRepacker -------------------------------------------------------- + +class cDolbyRepacker : public cRepacker { +private: + static int frameSizes[]; + uchar fragmentData[6 + 65535]; + int fragmentLen; + int fragmentTodo; + uchar pesHeader[6 + 3 + 255 + 4 + 4]; + int pesHeaderLen; + uchar pesHeaderBackup[6 + 3 + 255]; + int pesHeaderBackupLen; + uchar chk1; + uchar chk2; + int ac3todo; + enum eState { + find_0b, + find_77, + store_chk1, + store_chk2, + get_length, + output_packet + }; + int state; + int skippedBytes; + void ResetPesHeader(bool ContinuationFrame = false); + void AppendSubStreamID(bool ContinuationFrame = false); + bool FinishRemainder(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite); + bool StartNewPacket(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite); +public: + cDolbyRepacker(void); + virtual void Reset(void); + virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); + virtual int BreakAt(const uchar *Data, int Count); + }; + +// frameSizes are in words, i. e. multiply them by 2 to get bytes +int cDolbyRepacker::frameSizes[] = { + // fs = 48 kHz + 64, 64, 80, 80, 96, 96, 112, 112, 128, 128, 160, 160, 192, 192, 224, 224, + 256, 256, 320, 320, 384, 384, 448, 448, 512, 512, 640, 640, 768, 768, 896, 896, + 1024, 1024, 1152, 1152, 1280, 1280, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // fs = 44.1 kHz + 69, 70, 87, 88, 104, 105, 121, 122, 139, 140, 174, 175, 208, 209, 243, 244, + 278, 279, 348, 349, 417, 418, 487, 488, 557, 558, 696, 697, 835, 836, 975, 976, + 1114, 1115, 1253, 1254, 1393, 1394, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // fs = 32 kHz + 96, 96, 120, 120, 144, 144, 168, 168, 192, 192, 240, 240, 288, 288, 336, 336, + 384, 384, 480, 480, 576, 576, 672, 672, 768, 768, 960, 960, 1152, 1152, 1344, 1344, + 1536, 1536, 1728, 1728, 1920, 1920, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + +cDolbyRepacker::cDolbyRepacker(void) +{ + pesHeader[0] = 0x00; + pesHeader[1] = 0x00; + pesHeader[2] = 0x01; + pesHeader[3] = 0xBD; + pesHeader[4] = 0x00; + pesHeader[5] = 0x00; + Reset(); +} + +void cDolbyRepacker::AppendSubStreamID(bool ContinuationFrame) +{ + if (subStreamId) { + pesHeader[pesHeaderLen++] = subStreamId; + // number of ac3 frames "starting" in this packet (1 by design). + pesHeader[pesHeaderLen++] = 0x01; + // offset to start of first ac3 frame (0 means "no ac3 frame starting" + // so 1 (by design) addresses the first byte after the next two bytes). + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = (ContinuationFrame ? 0x00 : 0x01); + } +} + +void cDolbyRepacker::ResetPesHeader(bool ContinuationFrame) +{ + pesHeader[6] = 0x80; + pesHeader[7] = 0x00; + pesHeader[8] = 0x00; + pesHeaderLen = 9; + AppendSubStreamID(ContinuationFrame); +} + +void cDolbyRepacker::Reset(void) +{ + cRepacker::Reset(); + ResetPesHeader(); + state = find_0b; + ac3todo = 0; + chk1 = 0; + chk2 = 0; + fragmentLen = 0; + fragmentTodo = 0; + pesHeaderBackupLen = 0; + skippedBytes = 0; +} + +bool cDolbyRepacker::FinishRemainder(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite) +{ + bool success = true; + // enough data available to put PES packet into buffer? + if (fragmentTodo <= Todo) { + // output a previous fragment first + if (fragmentLen > 0) { + Bite = fragmentLen; + int n = Put(ResultBuffer, fragmentData, Bite, fragmentLen + fragmentTodo); + if (Bite != n) + success = false; + fragmentLen = 0; + } + Bite = fragmentTodo; + if (success) { + int n = Put(ResultBuffer, Data, Bite, Bite); + if (Bite != n) + success = false; + } + fragmentTodo = 0; + // ac3 frame completely processed? + if (Bite >= ac3todo) + state = find_0b; // go on with finding start of next packet + } + else { + // copy the fragment into separate buffer for later processing + Bite = Todo; + memcpy(fragmentData + fragmentLen, Data, Bite); + fragmentLen += Bite; + fragmentTodo -= Bite; + } + return success; +} + +bool cDolbyRepacker::StartNewPacket(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite) +{ + bool success = true; + int packetLen = pesHeaderLen + ac3todo; + // limit packet to maximum size + if (packetLen > maxPacketSize) + packetLen = maxPacketSize; + pesHeader[4] = (packetLen - 6) >> 8; + pesHeader[5] = (packetLen - 6) & 0xFF; + Bite = pesHeaderLen; + // enough data available to put PES packet into buffer? + if (packetLen - pesHeaderLen <= Todo) { + int n = Put(ResultBuffer, pesHeader, Bite, packetLen); + if (Bite != n) + success = false; + Bite = packetLen - pesHeaderLen; + if (success) { + n = Put(ResultBuffer, Data, Bite, Bite); + if (Bite != n) + success = false; + } + // ac3 frame completely processed? + if (Bite >= ac3todo) + state = find_0b; // go on with finding start of next packet + } + else { + fragmentTodo = packetLen; + // copy the pesheader into separate buffer for later processing + memcpy(fragmentData + fragmentLen, pesHeader, Bite); + fragmentLen += Bite; + fragmentTodo -= Bite; + // copy the fragment into separate buffer for later processing + Bite = Todo; + memcpy(fragmentData + fragmentLen, Data, Bite); + fragmentLen += Bite; + fragmentTodo -= Bite; + } + return success; +} + +void cDolbyRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) +{ + // synchronisation is detected some bytes after frame start. + const int SkippedBytesLimit = 4; + + // check for MPEG 2 + if ((Data[6] & 0xC0) != 0x80) { + DroppedData("cDolbyRepacker: MPEG 2 PES header expected", Count); + return; + } + + // backup PES header + if (Data[6] != 0x80 || Data[7] != 0x00 || Data[8] != 0x00) { + pesHeaderBackupLen = 6 + 3 + Data[8]; + memcpy(pesHeaderBackup, Data, pesHeaderBackupLen); + } + + // skip PES header + int done = 6 + 3 + Data[8]; + int todo = Count - done; + const uchar *data = Data + done; + + // look for 0x0B 0x77 <chk1> <chk2> <frameSize> + while (todo > 0) { + switch (state) { + case find_0b: + if (*data == 0x0B) { + state++; + // copy header information once for later use + if (pesHeaderBackupLen > 0) { + pesHeaderLen = pesHeaderBackupLen; + pesHeaderBackupLen = 0; + memcpy(pesHeader, pesHeaderBackup, pesHeaderLen); + AppendSubStreamID(); + } + } + data++; + done++; + todo--; + skippedBytes++; // collect number of skipped bytes while syncing + continue; + case find_77: + if (*data != 0x77) { + state = find_0b; + continue; + } + data++; + done++; + todo--; + skippedBytes++; // collect number of skipped bytes while syncing + state++; + continue; + case store_chk1: + chk1 = *data++; + done++; + todo--; + skippedBytes++; // collect number of skipped bytes while syncing + state++; + continue; + case store_chk2: + chk2 = *data++; + done++; + todo--; + skippedBytes++; // collect number of skipped bytes while syncing + state++; + continue; + case get_length: + ac3todo = 2 * frameSizes[*data]; + // frameSizeCode was invalid => restart searching + if (ac3todo <= 0) { + // reset PES header instead of using a wrong one + ResetPesHeader(); + if (chk1 == 0x0B) { + if (chk2 == 0x77) { + state = store_chk1; + continue; + } + if (chk2 == 0x0B) { + state = find_77; + continue; + } + state = find_0b; + continue; + } + if (chk2 == 0x0B) { + state = find_77; + continue; + } + state = find_0b; + continue; + } + if (initiallySyncing) // omit report for the typical initial case + initiallySyncing = false; + else if (skippedBytes > SkippedBytesLimit) // report that syncing dropped some bytes + LOG("cDolbyRepacker: skipped %d bytes to sync on next AC3 frame", skippedBytes - SkippedBytesLimit); + skippedBytes = 0; + // append read data to header for common output processing + pesHeader[pesHeaderLen++] = 0x0B; + pesHeader[pesHeaderLen++] = 0x77; + pesHeader[pesHeaderLen++] = chk1; + pesHeader[pesHeaderLen++] = chk2; + ac3todo -= 4; + state++; + // fall through to output + case output_packet: { + int bite = 0; + // finish remainder of ac3 frame? + if (fragmentTodo > 0) + FinishRemainder(ResultBuffer, data, todo, bite); + else { + // start a new packet + StartNewPacket(ResultBuffer, data, todo, bite); + // prepare for next (continuation) packet + ResetPesHeader(state == output_packet); + } + data += bite; + done += bite; + todo -= bite; + ac3todo -= bite; + } + } + } + // report that syncing dropped some bytes + if (skippedBytes > SkippedBytesLimit) { + if (!initiallySyncing) // omit report for the typical initial case + LOG("cDolbyRepacker: skipped %d bytes while syncing on next AC3 frame", skippedBytes - 4); + skippedBytes = SkippedBytesLimit; + } +} + +int cDolbyRepacker::BreakAt(const uchar *Data, int Count) +{ + if (initiallySyncing) + return -1; // fill the packet buffer completely until we have synced once + // enough data for test? + if (Count < 6 + 3) + return -1; + // check for MPEG 2 + if ((Data[6] & 0xC0) != 0x80) + return -1; + int headerLen = Data[8] + 6 + 3; + // break after fragment tail? + if (ac3todo > 0) + return headerLen + ac3todo; + // enough data for test? + if (Count < headerLen + 5) + return -1; + const uchar *data = Data + headerLen; + // break after ac3 frame? + if (data[0] == 0x0B && data[1] == 0x77 && frameSizes[data[4]] > 0) + return headerLen + 2 * frameSizes[data[4]]; + return -1; +} + +// --- cTS2PES --------------------------------------------------------------- + +#include <netinet/in.h> + +//XXX TODO: these should really be available in some driver header file! +#define PROG_STREAM_MAP 0xBC +#ifndef PRIVATE_STREAM1 +#define PRIVATE_STREAM1 0xBD +#endif +#define PADDING_STREAM 0xBE +#ifndef PRIVATE_STREAM2 +#define PRIVATE_STREAM2 0xBF +#endif +#define AUDIO_STREAM_S 0xC0 +#define AUDIO_STREAM_E 0xDF +#define VIDEO_STREAM_S 0xE0 +#define VIDEO_STREAM_E 0xEF +#define ECM_STREAM 0xF0 +#define EMM_STREAM 0xF1 +#define DSM_CC_STREAM 0xF2 +#define ISO13522_STREAM 0xF3 +#define PROG_STREAM_DIR 0xFF + +//pts_dts flags +#define PTS_ONLY 0x80 + +#define TS_SIZE 188 +#define PID_MASK_HI 0x1F +#define CONT_CNT_MASK 0x0F + +// Flags: +#define PAY_LOAD 0x10 +#define ADAPT_FIELD 0x20 +#define PAY_START 0x40 +#define TS_ERROR 0x80 + +#define MAX_PLENGTH 0xFFFF // the maximum PES packet length (theoretically) +#define MMAX_PLENGTH (64*MAX_PLENGTH) // some stations send PES packets that are extremely large, e.g. DVB-T in Finland or HDTV 1920x1080 + +#define IPACKS 2048 + +// Start codes: +#define SC_SEQUENCE 0xB3 // "sequence header code" +#define SC_GROUP 0xB8 // "group start code" +#define SC_PICTURE 0x00 // "picture start code" + +#define MAXNONUSEFULDATA (10*1024*1024) +#define MAXNUMUPTERRORS 10 + +class cTS2PES { +private: + int pid; + int size; + int found; + int count; + uint8_t *buf; + uint8_t cid; + uint8_t rewriteCid; + uint8_t subStreamId; + int plength; + uint8_t plen[2]; + uint8_t flag1; + uint8_t flag2; + uint8_t hlength; + int mpeg; + uint8_t check; + int mpeg1_required; + int mpeg1_stuffing; + bool done; + cRingBufferLinear *resultBuffer; + int tsErrors; + int ccErrors; + int ccCounter; + cRepacker *repacker; + static uint8_t headr[]; + void store(uint8_t *Data, int Count); + void reset_ipack(void); + void send_ipack(void); + void write_ipack(const uint8_t *Data, int Count); + void instant_repack(const uint8_t *Buf, int Count); +public: + cTS2PES(int Pid, cRingBufferLinear *ResultBuffer, int Size, uint8_t RewriteCid = 0x00, uint8_t SubStreamId = 0x00, cRepacker *Repacker = NULL); + ~cTS2PES(); + int Pid(void) { return pid; } + void ts_to_pes(const uint8_t *Buf); // don't need count (=188) + void Clear(void); + }; + +uint8_t cTS2PES::headr[] = { 0x00, 0x00, 0x01 }; + +cTS2PES::cTS2PES(int Pid, cRingBufferLinear *ResultBuffer, int Size, uint8_t RewriteCid, uint8_t SubStreamId, cRepacker *Repacker) +{ + pid = Pid; + resultBuffer = ResultBuffer; + size = Size; + rewriteCid = RewriteCid; + subStreamId = SubStreamId; + repacker = Repacker; + if (repacker) { + repacker->SetMaxPacketSize(size); + repacker->SetSubStreamId(subStreamId); + size += repacker->QuerySnoopSize(); + } + + tsErrors = 0; + ccErrors = 0; + ccCounter = -1; + + if (!(buf = MALLOC(uint8_t, size))) + esyslog("Not enough memory for ts_transform"); + + reset_ipack(); +} + +cTS2PES::~cTS2PES() +{ + if (tsErrors || ccErrors) + dsyslog("cTS2PES got %d TS errors, %d TS continuity errors", tsErrors, ccErrors); + free(buf); + delete repacker; +} + +void cTS2PES::Clear(void) +{ + reset_ipack(); + if (repacker) + repacker->Reset(); +} + +void cTS2PES::store(uint8_t *Data, int Count) +{ + if (repacker) + repacker->Repack(resultBuffer, Data, Count); + else + cRepacker::Put(resultBuffer, Data, Count, Count); +} + +void cTS2PES::reset_ipack(void) +{ + found = 0; + cid = 0; + plength = 0; + flag1 = 0; + flag2 = 0; + hlength = 0; + mpeg = 0; + check = 0; + mpeg1_required = 0; + mpeg1_stuffing = 0; + done = false; + count = 0; +} + +void cTS2PES::send_ipack(void) +{ + if (count <= ((mpeg == 2) ? 9 : 7)) // skip empty packets + return; + buf[3] = rewriteCid ? rewriteCid : cid; + buf[4] = (uint8_t)(((count - 6) & 0xFF00) >> 8); + buf[5] = (uint8_t)((count - 6) & 0x00FF); + store(buf, count); + + switch (mpeg) { + case 2: + buf[6] = 0x80; + buf[7] = 0x00; + buf[8] = 0x00; + count = 9; + if (!repacker && subStreamId) { + buf[9] = subStreamId; + buf[10] = 1; + buf[11] = 0; + buf[12] = 1; + count = 13; + } + break; + case 1: + buf[6] = 0x0F; + count = 7; + break; + } +} + +void cTS2PES::write_ipack(const uint8_t *Data, int Count) +{ + if (count < 6) { + memcpy(buf, headr, 3); + count = 6; + } + + // determine amount of data to process + int bite = Count; + if (count + bite > size) + bite = size - count; + if (repacker) { + int breakAt = repacker->BreakAt(buf, count); + // avoid memcpy of data after break location + if (0 <= breakAt && breakAt < count + bite) { + bite = breakAt - count; + if (bite < 0) // should never happen + bite = 0; + } + } + + memcpy(buf + count, Data, bite); + count += bite; + + if (repacker) { + // determine break location + int breakAt = repacker->BreakAt(buf, count); + if (breakAt > size) // won't fit into packet? + breakAt = -1; + if (breakAt > count) // not enough data? + breakAt = -1; + // push out data before break location + if (breakAt > 0) { + // adjust bite if above memcpy was to large + bite -= count - breakAt; + count = breakAt; + send_ipack(); + // recurse for data after break location + if (Count - bite > 0) + write_ipack(Data + bite, Count - bite); + } + } + + // push out data when buffer is full + if (count >= size) { + send_ipack(); + // recurse for remaining data + if (Count - bite > 0) + write_ipack(Data + bite, Count - bite); + } +} + +void cTS2PES::instant_repack(const uint8_t *Buf, int Count) +{ + int c = 0; + + while (c < Count && (mpeg == 0 || (mpeg == 1 && found < mpeg1_required) || (mpeg == 2 && found < 9)) && (found < 5 || !done)) { + switch (found ) { + case 0: + case 1: + if (Buf[c] == 0x00) + found++; + else + found = 0; + c++; + break; + case 2: + if (Buf[c] == 0x01) + found++; + else if (Buf[c] != 0) + found = 0; + c++; + break; + case 3: + cid = 0; + switch (Buf[c]) { + case PROG_STREAM_MAP: + case PRIVATE_STREAM2: + case PROG_STREAM_DIR: + case ECM_STREAM : + case EMM_STREAM : + case PADDING_STREAM : + case DSM_CC_STREAM : + case ISO13522_STREAM: + done = true; + case PRIVATE_STREAM1: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + found++; + cid = Buf[c++]; + break; + default: + found = 0; + break; + } + break; + case 4: + if (Count - c > 1) { + unsigned short *pl = (unsigned short *)(Buf + c); + plength = ntohs(*pl); + c += 2; + found += 2; + mpeg1_stuffing = 0; + } + else { + plen[0] = Buf[c]; + found++; + return; + } + break; + case 5: { + plen[1] = Buf[c++]; + unsigned short *pl = (unsigned short *)plen; + plength = ntohs(*pl); + found++; + mpeg1_stuffing = 0; + } + break; + case 6: + if (!done) { + flag1 = Buf[c++]; + found++; + if (mpeg1_stuffing == 0) { // first stuffing iteration: determine MPEG level + if ((flag1 & 0xC0) == 0x80) + mpeg = 2; + else { + mpeg = 1; + mpeg1_required = 7; + } + } + if (mpeg == 1) { + if (flag1 == 0xFF) { // MPEG1 stuffing + if (++mpeg1_stuffing > 16) + found = 0; // invalid MPEG1 header + else { // ignore stuffing + found--; + if (plength > 0) + plength--; + } + } + else if ((flag1 & 0xC0) == 0x40) // STD_buffer_scale/size + mpeg1_required += 2; + else if (flag1 != 0x0F && (flag1 & 0xF0) != 0x20 && (flag1 & 0xF0) != 0x30) + found = 0; // invalid MPEG1 header + else { + flag2 = 0; + hlength = 0; + } + } + } + break; + case 7: + if (!done && (mpeg == 2 || mpeg1_required > 7)) { + flag2 = Buf[c++]; + found++; + } + break; + case 8: + if (!done && (mpeg == 2 || mpeg1_required > 7)) { + hlength = Buf[c++]; + found++; + if (mpeg == 1 && hlength != 0x0F && (hlength & 0xF0) != 0x20 && (hlength & 0xF0) != 0x30) + found = 0; // invalid MPEG1 header + } + break; + default: + break; + } + } + + if (!plength) + plength = MMAX_PLENGTH - 6; + + if (done || ((mpeg == 2 && found >= 9) || (mpeg == 1 && found >= mpeg1_required))) { + switch (cid) { + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + case PRIVATE_STREAM1: + + if (mpeg == 2 && found == 9 && count < found) { // make sure to not write the data twice by looking at count + write_ipack(&flag1, 1); + write_ipack(&flag2, 1); + write_ipack(&hlength, 1); + } + + if (mpeg == 1 && found == mpeg1_required && count < found) { // make sure to not write the data twice by looking at count + write_ipack(&flag1, 1); + if (mpeg1_required > 7) { + write_ipack(&flag2, 1); + write_ipack(&hlength, 1); + } + } + + if (mpeg == 2 && (flag2 & PTS_ONLY) && found < 14) { + while (c < Count && found < 14) { + write_ipack(Buf + c, 1); + c++; + found++; + } + if (c == Count) + return; + } + + if (!repacker && subStreamId) { + while (c < Count && found < (hlength + 9) && found < plength + 6) { + write_ipack(Buf + c, 1); + c++; + found++; + } + if (found == (hlength + 9)) { + uchar sbuf[] = { 0x01, 0x00, 0x00 }; + write_ipack(&subStreamId, 1); + write_ipack(sbuf, 3); + } + } + + while (c < Count && found < plength + 6) { + int l = Count - c; + if (l + found > plength + 6) + l = plength + 6 - found; + write_ipack(Buf + c, l); + found += l; + c += l; + } + + break; + } + + if (done) { + if (found + Count - c < plength + 6) { + found += Count - c; + c = Count; + } + else { + c += plength + 6 - found; + found = plength + 6; + } + } + + if (plength && found == plength + 6) { + if (plength == MMAX_PLENGTH - 6) + esyslog("ERROR: PES packet length overflow in remuxer (stream corruption)"); + send_ipack(); + reset_ipack(); + if (c < Count) + instant_repack(Buf + c, Count - c); + } + } + return; +} + +void cTS2PES::ts_to_pes(const uint8_t *Buf) // don't need count (=188) +{ + if (!Buf) + return; + + if (Buf[1] & TS_ERROR) + tsErrors++; + + if (!(Buf[3] & (ADAPT_FIELD | PAY_LOAD))) + return; // discard TS packet with adaption_field_control set to '00'. + + if ((Buf[3] & PAY_LOAD) && ((Buf[3] ^ ccCounter) & CONT_CNT_MASK)) { + // This should check duplicates and packets which do not increase the counter. + // But as the errors usually come in bursts this should be enough to + // show you there is something wrong with signal quality. + if (ccCounter != -1 && ((Buf[3] ^ (ccCounter + 1)) & CONT_CNT_MASK)) { + ccErrors++; + // Enable this if you are having problems with signal quality. + // These are the errors I used to get with Nova-T when antenna + // was not positioned correcly (not transport errors). //tvr + //dsyslog("TS continuity error (%d)", ccCounter); + } + ccCounter = Buf[3] & CONT_CNT_MASK; + } + + if (Buf[1] & PAY_START) { + if (found > 6) { + if (plength != MMAX_PLENGTH - 6 && plength != found - 6) + dsyslog("PES packet shortened to %d bytes (expected: %d bytes)", found, plength + 6); + plength = found - 6; + send_ipack(); + reset_ipack(); + } + found = 0; + } + + uint8_t off = 0; + + if (Buf[3] & ADAPT_FIELD) { // adaptation field? + off = Buf[4] + 1; + if (off + 4 > 187) + return; + } + + if (Buf[3] & PAY_LOAD) + instant_repack(Buf + 4 + off, TS_SIZE - 4 - off); +} + +// --- cRingBufferLinearPes -------------------------------------------------- + +class cRingBufferLinearPes : public cStreamdevBuffer { +protected: + virtual int DataReady(const uchar *Data, int Count); +public: + cRingBufferLinearPes(int Size, int Margin = 0, bool Statistics = false, const char *Description = NULL) + :cStreamdevBuffer(Size, Margin, Statistics, Description) {} + }; + +int cRingBufferLinearPes::DataReady(const uchar *Data, int Count) +{ + int c = cRingBufferLinear::DataReady(Data, Count); + if (!c && Count >= 6) { + if (!Data[0] && !Data[1] && Data[2] == 0x01) { + int Length = 6 + Data[4] * 256 + Data[5]; + if (Length <= Count) + return Length; + } + } + return c; +} + +// --- cTS2PESRemux ---------------------------------------------------------------- + +#define RESULTBUFFERSIZE KILOBYTE(256) + +cTS2PESRemux::cTS2PESRemux(int VPid, const int *APids, const int *DPids, const int *SPids) +{ + noVideo = VPid == 0 || VPid == 1 || VPid == 0x1FFF; + synced = false; + skipped = 0; + numTracks = 0; + resultSkipped = 0; + resultBuffer = new cRingBufferLinearPes(RESULTBUFFERSIZE, IPACKS, false, "Result"); + resultBuffer->SetTimeouts(100, 100); + if (VPid) +#define TEST_cVideoRepacker +#ifdef TEST_cVideoRepacker + ts2pes[numTracks++] = new cTS2PES(VPid, resultBuffer, IPACKS, 0xE0, 0x00, new cVideoRepacker); +#else + ts2pes[numTracks++] = new cTS2PES(VPid, resultBuffer, IPACKS, 0xE0); +#endif + if (APids) { + int n = 0; + while (*APids && numTracks < MAXTRACKS && n < MAXAPIDS) { +#define TEST_cAudioRepacker +#ifdef TEST_cAudioRepacker + ts2pes[numTracks++] = new cTS2PES(*APids++, resultBuffer, IPACKS, 0xC0 + n, 0x00, new cAudioRepacker(0xC0 + n)); + n++; +#else + ts2pes[numTracks++] = new cTS2PES(*APids++, resultBuffer, IPACKS, 0xC0 + n++); +#endif + } + } + if (DPids) { + int n = 0; + while (*DPids && numTracks < MAXTRACKS && n < MAXDPIDS) + ts2pes[numTracks++] = new cTS2PES(*DPids++, resultBuffer, IPACKS, 0x00, 0x80 + n++, new cDolbyRepacker); + } + if (SPids) { + int n = 0; + while (*SPids && numTracks < MAXTRACKS && n < MAXSPIDS) + ts2pes[numTracks++] = new cTS2PES(*SPids++, resultBuffer, IPACKS, 0x00, 0x20 + n++); + } +} + +cTS2PESRemux::~cTS2PESRemux() +{ + for (int t = 0; t < numTracks; t++) + delete ts2pes[t]; + delete resultBuffer; +} + +#define TS_SYNC_BYTE 0x47 + +int cTS2PESRemux::Put(const uchar *Data, int Count) +{ + int used = 0; + + // Make sure we are looking at a TS packet: + + while (Count > TS_SIZE) { + if (Data[0] == TS_SYNC_BYTE && Data[TS_SIZE] == TS_SYNC_BYTE) + break; + Data++; + Count--; + used++; + } + if (used) + esyslog("ERROR: skipped %d byte to sync on TS packet", used); + + // Convert incoming TS data into multiplexed PES: + + for (int i = 0; i < Count; i += TS_SIZE) { + if (Count - i < TS_SIZE) + break; + if (Data[i] != TS_SYNC_BYTE) + break; + if (resultBuffer->Free() < 2 * IPACKS) { + resultBuffer->WaitForPut(); + break; // A cTS2PES might write one full packet and also a small rest + } + int pid = cTSRemux::GetPid(Data + i + 1); + if (Data[i + 3] & 0x10) { // got payload + for (int t = 0; t < numTracks; t++) { + if (ts2pes[t]->Pid() == pid) { + ts2pes[t]->ts_to_pes(Data + i); + break; + } + } + } + used += TS_SIZE; + } + + // Check if we're getting anywhere here: + if (!synced && skipped >= 0) { + if (skipped > MAXNONUSEFULDATA) { + esyslog("ERROR: no useful data seen within %d byte of video stream", skipped); + skipped = -1; + } + else + skipped += used; + } + + return used; +} + +uchar *cTS2PESRemux::Get(int &Count) +{ + // Remove any previously skipped data from the result buffer: + + if (resultSkipped > 0) { + resultBuffer->Del(resultSkipped); + resultSkipped = 0; + } + + // Check for frame borders: + + Count = 0; + uchar *resultData = NULL; + int resultCount = 0; + uchar *data = resultBuffer->Get(resultCount); + if (data) { + for (int i = 0; i < resultCount - 3; i++) { + if (data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1) { + int l = 0; + uchar StreamType = data[i + 3]; + if (VIDEO_STREAM_S <= StreamType && StreamType <= VIDEO_STREAM_E) { + uchar pt = NO_PICTURE; + l = cTSRemux::ScanVideoPacket(data, resultCount, i, pt); + if (l < 0) + return resultData; + if (pt != NO_PICTURE) { + if (pt < I_FRAME || B_FRAME < pt) { + esyslog("ERROR: unknown picture type '%d'", pt); + } + else if (!synced) { + if (pt == I_FRAME) { + resultSkipped = i; // will drop everything before this position + cTSRemux::SetBrokenLink(data + i, l); + synced = true; + } + } + else if (Count) + return resultData; + } + } + else { //if (AUDIO_STREAM_S <= StreamType && StreamType <= AUDIO_STREAM_E || StreamType == PRIVATE_STREAM1) { + l = cTSRemux::GetPacketLength(data, resultCount, i); + if (l < 0) + return resultData; + if (noVideo) { + if (!synced) { + resultSkipped = i; // will drop everything before this position + synced = true; + } + else if (Count) + return resultData; + } + } + if (synced) { + if (!Count) + resultData = data + i; + Count += l; + } + else + resultSkipped = i + l; + if (l > 0) + i += l - 1; // the loop increments, too + } + } + } + return resultData; +} + +void cTS2PESRemux::Del(int Count) +{ + resultBuffer->Del(Count); +} + +void cTS2PESRemux::Clear(void) +{ + for (int t = 0; t < numTracks; t++) + ts2pes[t]->Clear(); + resultBuffer->Clear(); + synced = false; + skipped = 0; + resultSkipped = 0; +} + +} // namespace Streamdev diff --git a/plugins/streamdev/streamdev-cvs/remux/ts2pes.h b/plugins/streamdev/streamdev-cvs/remux/ts2pes.h new file mode 100644 index 0000000..e9d7237 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/remux/ts2pes.h @@ -0,0 +1,56 @@ +/* + * ts2pes.h: A streaming MPEG2 remultiplexer + * + * This file is based on a copy of remux.h from Klaus Schmidinger's + * VDR, version 1.6.0. + * + * $Id: ts2pes.h,v 1.4 2009/07/06 06:11:11 schmirl Exp $ + */ + +#ifndef VDR_STREAMDEV_TS2PES_H +#define VDR_STREAMDEV_TS2PES_H + +#include "remux/tsremux.h" +#include "server/streamer.h" + +#define MAXTRACKS 64 + +namespace Streamdev { + +class cTS2PES; + +class cTS2PESRemux: public cTSRemux { +private: + bool noVideo; + bool synced; + int skipped; + cTS2PES *ts2pes[MAXTRACKS]; + int numTracks; + cStreamdevBuffer *resultBuffer; + int resultSkipped; +public: + cTS2PESRemux(int VPid, const int *APids, const int *DPids, const int *SPids); + ///< Creates a new remuxer for the given PIDs. VPid is the video PID, while + ///< APids, DPids and SPids are pointers to zero terminated lists of audio, + ///< dolby and subtitle PIDs (the pointers may be NULL if there is no such + ///< PID). + virtual ~cTS2PESRemux(); + int Put(const uchar *Data, int Count); + ///< Puts at most Count bytes of Data into the remuxer. + ///< \return Returns the number of bytes actually consumed from Data. + uchar *Get(int &Count); + ///< Gets all currently available data from the remuxer. + ///< \return Count contains the number of bytes the result points to, and + void Del(int Count); + ///< Deletes Count bytes from the remuxer. Count must be the number returned + ///< from a previous call to Get(). Several calls to Del() with fractions of + ///< a previously returned Count may be made, but the total sum of all Count + ///< values must be exactly what the previous Get() has returned. + void Clear(void); + ///< Clears the remuxer of all data it might still contain, keeping the PID + ///< settings as they are. + }; + +} // namespace Streamdev + +#endif // VDR_STREAMDEV_TS2PES_H diff --git a/plugins/streamdev/streamdev-cvs/remux/ts2ps.c b/plugins/streamdev/streamdev-cvs/remux/ts2ps.c new file mode 100644 index 0000000..2a97dee --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/remux/ts2ps.c @@ -0,0 +1,218 @@ +#include "remux/ts2ps.h" +#include "server/streamer.h" +#include <vdr/channels.h> +#include <vdr/device.h> + +namespace Streamdev { + +class cTS2PS { + friend void PutPES(uint8_t *Buffer, int Size, void *Data); + +private: + ipack m_Ipack; + int m_Pid; + cRingBufferLinear *m_ResultBuffer; + +public: + cTS2PS(cRingBufferLinear *ResultBuffer, int Pid, uint8_t AudioCid = 0x00); + ~cTS2PS(); + + void PutTSPacket(const uint8_t *Buffer); + + int Pid(void) const { return m_Pid; } +}; + +void PutPES(uint8_t *Buffer, int Size, void *Data) +{ + cTS2PS *This = (cTS2PS*)Data; + int n = This->m_ResultBuffer->Put(Buffer, Size); + if (n != Size) + esyslog("ERROR: result buffer overflow, dropped %d out of %d byte", Size - n, Size); +} + +} // namespace Streamdev +using namespace Streamdev; + +cTS2PS::cTS2PS(cRingBufferLinear *ResultBuffer, int Pid, uint8_t AudioCid) +{ + m_ResultBuffer = ResultBuffer; + m_Pid = Pid; + + init_ipack(&m_Ipack, IPACKS, PutPES, false); + m_Ipack.cid = AudioCid; + m_Ipack.data = (void*)this; +} + +cTS2PS::~cTS2PS() +{ + free_ipack(&m_Ipack); +} + +void cTS2PS::PutTSPacket(const uint8_t *Buffer) +{ + if (!Buffer) + return; + + if (Buffer[1] & 0x80) { // ts error + // TODO + } + + if (Buffer[1] & 0x40) { // payload start + if (m_Ipack.plength == MMAX_PLENGTH - 6 && m_Ipack.found > 6) { + m_Ipack.plength = m_Ipack.found - 6; + m_Ipack.found = 0; + send_ipack(&m_Ipack); + reset_ipack(&m_Ipack); + } + } + + uint8_t off = 0; + + if (Buffer[3] & 0x20) { // adaptation field? + off = Buffer[4] + 1; + if (off + 4 > TS_SIZE - 1) + return; + } + + instant_repack((uint8_t*)(Buffer + 4 + off), TS_SIZE - 4 - off, &m_Ipack); +} + +cTS2PSRemux::cTS2PSRemux(int VPid, const int *APids, const int *DPids, const int *SPids): + m_NumTracks(0), + m_ResultBuffer(new cStreamdevBuffer(WRITERBUFSIZE, IPACKS)), + m_ResultSkipped(0), + m_Skipped(0), + m_Synced(false), + m_IsRadio(VPid == 0 || VPid == 1 || VPid == 0x1FFF) +{ + m_ResultBuffer->SetTimeouts(100, 100); + + if (VPid) + m_Remux[m_NumTracks++] = new cTS2PS(m_ResultBuffer, VPid); + if (APids) { + int n = 0; + while (*APids && m_NumTracks < MAXTRACKS && n < MAXAPIDS) + m_Remux[m_NumTracks++] = new cTS2PS(m_ResultBuffer, *APids++, 0xC0 + n++); + } + if (DPids) { + int n = 0; + while (*DPids && m_NumTracks < MAXTRACKS && n < MAXDPIDS) + m_Remux[m_NumTracks++] = new cTS2PS(m_ResultBuffer, *DPids++, 0x80 + n++); + } +} + +cTS2PSRemux::~cTS2PSRemux() { + for (int i = 0; i < m_NumTracks; ++i) + delete m_Remux[i]; + delete m_ResultBuffer; +} + +int cTS2PSRemux::Put(const uchar *Data, int Count) +{ + int used = 0; + + // Make sure we are looking at a TS packet: + while (Count > TS_SIZE) { + if (Data[0] == TS_SYNC_BYTE && Data[TS_SIZE] == TS_SYNC_BYTE) + break; + Data++; + Count--; + used++; + } + if (used) + esyslog("ERROR: m_Skipped %d byte to sync on TS packet", used); + + // Convert incoming TS data into multiplexed PS: + + for (int i = 0; i < Count; i += TS_SIZE) { + if (Count - i < TS_SIZE) + break; + if (Data[i] != TS_SYNC_BYTE) + break; + if (m_ResultBuffer->Free() < 2 * IPACKS) { + m_ResultBuffer->WaitForPut(); + break; // A cTS2PS might write one full packet and also a small rest + } + int pid = GetPid(Data + i + 1); + if (Data[i + 3] & 0x10) { // got payload + for (int t = 0; t < m_NumTracks; t++) { + if (m_Remux[t]->Pid() == pid) { + m_Remux[t]->PutTSPacket(Data + i); + break; + } + } + } + used += TS_SIZE; + } + + // Check if we're getting anywhere here: + if (!m_Synced && m_Skipped >= 0) + m_Skipped += used; + + return used; +} + +uchar *cTS2PSRemux::Get(int &Count) +{ + // Remove any previously skipped data from the result buffer: + + if (m_ResultSkipped > 0) { + m_ResultBuffer->Del(m_ResultSkipped); + m_ResultSkipped = 0; + } + + // Special VPID case to enable recording radio channels: + if (m_IsRadio) { + // Force syncing of radio channels to avoid "no useful data" error + m_Synced = true; + return m_ResultBuffer->Get(Count); + } + + // Check for frame borders: + Count = 0; + uchar *resultData = NULL; + int resultCount = 0; + uchar *data = m_ResultBuffer->Get(resultCount); + if (data) { + for (int i = 0; i < resultCount - 3; i++) { + if (data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1) { + int l = 0; + uchar StreamType = data[i + 3]; + if (VIDEO_STREAM_S <= StreamType && StreamType <= VIDEO_STREAM_E) { + uchar pt = NO_PICTURE; + l = ScanVideoPacket(data, resultCount, i, pt); + if (l < 0) + return resultData; + if (pt != NO_PICTURE) { + if (pt < I_FRAME || B_FRAME < pt) { + esyslog("ERROR: unknown picture type '%d'", pt); + } + else if (!m_Synced) { + if (pt == I_FRAME) { + m_ResultSkipped = i; // will drop everything before this position + SetBrokenLink(data + i, l); + m_Synced = true; + } + } + else if (Count) + return resultData; + } + } else { + l = GetPacketLength(data, resultCount, i); + if (l < 0) + return resultData; + } + if (m_Synced) { + if (!Count) + resultData = data + i; + Count += l; + } else + m_ResultSkipped = i + l; + if (l > 0) + i += l - 1; // the loop increments, too + } + } + } + return resultData; +} + diff --git a/plugins/streamdev/streamdev-cvs/remux/ts2ps.h b/plugins/streamdev/streamdev-cvs/remux/ts2ps.h new file mode 100644 index 0000000..2380c15 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/remux/ts2ps.h @@ -0,0 +1,36 @@ +#ifndef VDR_STREAMDEV_TS2PSREMUX_H +#define VDR_STREAMDEV_TS2PSREMUX_H + +#include "remux/tsremux.h" +#include "server/streamer.h" + +#ifndef MAXTRACKS +#define MAXTRACKS 64 +#endif + +namespace Streamdev { + +class cTS2PS; + +class cTS2PSRemux: public cTSRemux { +private: + int m_NumTracks; + cTS2PS *m_Remux[MAXTRACKS]; + cStreamdevBuffer *m_ResultBuffer; + int m_ResultSkipped; + int m_Skipped; + bool m_Synced; + bool m_IsRadio; + +public: + cTS2PSRemux(int VPid, const int *Apids, const int *Dpids, const int *Spids); + virtual ~cTS2PSRemux(); + + int Put(const uchar *Data, int Count); + uchar *Get(int &Count); + void Del(int Count) { m_ResultBuffer->Del(Count); } +}; + +} // namespace Streamdev + +#endif // VDR_STREAMDEV_TS2PSREMUX_H diff --git a/plugins/streamdev/streamdev-cvs/remux/tsremux.c b/plugins/streamdev/streamdev-cvs/remux/tsremux.c new file mode 100644 index 0000000..a503ed0 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/remux/tsremux.c @@ -0,0 +1,87 @@ +#include "remux/tsremux.h" + +#define SC_PICTURE 0x00 // "picture header" +#define PID_MASK_HI 0x1F +#define VIDEO_STREAM_S 0xE0 + +using namespace Streamdev; + +void cTSRemux::SetBrokenLink(uchar *Data, int Length) +{ + int PesPayloadOffset = 0; + if (AnalyzePesHeader(Data, Length, PesPayloadOffset) >= phMPEG1 && (Data[3] & 0xF0) == VIDEO_STREAM_S) { + for (int i = PesPayloadOffset; i < Length - 7; i++) { + if (Data[i] == 0 && Data[i + 1] == 0 && Data[i + 2] == 1 && Data[i + 3] == 0xB8) { + if (!(Data[i + 7] & 0x40)) // set flag only if GOP is not closed + Data[i + 7] |= 0x20; + return; + } + } + dsyslog("SetBrokenLink: no GOP header found in video packet"); + } + else + dsyslog("SetBrokenLink: no video packet in frame"); +} + +int cTSRemux::GetPid(const uchar *Data) +{ + return (((uint16_t)Data[0] & PID_MASK_HI) << 8) | (Data[1] & 0xFF); +} + +int cTSRemux::GetPacketLength(const uchar *Data, int Count, int Offset) +{ + // Returns the length of the packet starting at Offset, or -1 if Count is + // too small to contain the entire packet. + int Length = (Offset + 5 < Count) ? (Data[Offset + 4] << 8) + Data[Offset + 5] + 6 : -1; + if (Length > 0 && Offset + Length <= Count) + return Length; + return -1; +} + +int cTSRemux::ScanVideoPacket(const uchar *Data, int Count, int Offset, uchar &PictureType) +{ + // Scans the video packet starting at Offset and returns its length. + // If the return value is -1 the packet was not completely in the buffer. + int Length = GetPacketLength(Data, Count, Offset); + if (Length > 0) { + int PesPayloadOffset = 0; + if (AnalyzePesHeader(Data + Offset, Length, PesPayloadOffset) >= phMPEG1) { + const uchar *p = Data + Offset + PesPayloadOffset + 2; + const uchar *pLimit = Data + Offset + Length - 3; +#ifdef TEST_cVideoRepacker + // cVideoRepacker ensures that a new PES packet is started for a new sequence, + // group or picture which allows us to easily skip scanning through a huge + // amount of video data. + if (p < pLimit) { + if (p[-2] || p[-1] || p[0] != 0x01) + pLimit = 0; // skip scanning: packet doesn't start with 0x000001 + else { + switch (p[1]) { + case SC_SEQUENCE: + case SC_GROUP: + case SC_PICTURE: + break; + default: // skip scanning: packet doesn't start a new sequence, group or picture + pLimit = 0; + } + } + } +#endif + while (p < pLimit && (p = (const uchar *)memchr(p, 0x01, pLimit - p))) { + if (!p[-2] && !p[-1]) { // found 0x000001 + switch (p[1]) { + case SC_PICTURE: PictureType = (p[3] >> 3) & 0x07; + return Length; + } + p += 4; // continue scanning after 0x01ssxxyy + } + else + p += 3; // continue scanning after 0x01xxyy + } + } + PictureType = NO_PICTURE; + return Length; + } + return -1; +} + diff --git a/plugins/streamdev/streamdev-cvs/remux/tsremux.h b/plugins/streamdev/streamdev-cvs/remux/tsremux.h new file mode 100644 index 0000000..8f22b1d --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/remux/tsremux.h @@ -0,0 +1,28 @@ +#ifndef VDR_STREAMDEV_TSREMUX_H +#define VDR_STREAMDEV_TSREMUX_H + +#include "libdvbmpeg/transform.h" +#include <vdr/remux.h> + +// Picture types: +#define NO_PICTURE 0 + +namespace Streamdev { + +class cTSRemux { +public: + virtual ~cTSRemux() {}; + + virtual int Put(const uchar *Data, int Count) = 0; + virtual uchar *Get(int &Count) = 0; + virtual void Del(int Count) = 0; + + static void SetBrokenLink(uchar *Data, int Length); + static int GetPid(const uchar *Data); + static int GetPacketLength(const uchar *Data, int Count, int Offset); + static int ScanVideoPacket(const uchar *Data, int Count, int Offset, uchar &PictureType); +}; + +} // namespace Streamdev + +#endif // VDR_STREAMDEV_TSREMUX_H diff --git a/plugins/streamdev/streamdev-cvs/server/CVS/Entries b/plugins/streamdev/streamdev-cvs/server/CVS/Entries new file mode 100644 index 0000000..1a9cebe --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/CVS/Entries @@ -0,0 +1,34 @@ +/component.c/1.4/Fri Feb 13 10:39:22 2009// +/component.h/1.3/Fri Feb 13 10:39:22 2009// +/componentHTTP.c/1.2/Mon May 9 20:22:29 2005// +/componentHTTP.h/1.2/Mon May 9 20:22:29 2005// +/componentIGMP.c/1.2/Fri Jul 3 21:44:19 2009// +/componentIGMP.h/1.1/Fri Feb 13 10:39:22 2009// +/componentVTP.c/1.2/Mon May 9 20:22:29 2005// +/componentVTP.h/1.2/Mon May 9 20:22:29 2005// +/connection.c/1.13/Fri Sep 18 10:43:26 2009// +/connection.h/1.8/Fri Sep 18 10:43:26 2009// +/connectionHTTP.c/1.17/Fri Jun 19 06:32:45 2009// +/connectionHTTP.h/1.6/Tue Oct 14 11:05:48 2008// +/connectionIGMP.c/1.1/Fri Feb 13 10:39:22 2009// +/connectionIGMP.h/1.1/Fri Feb 13 10:39:22 2009// +/connectionVTP.c/1.27/Fri Jan 29 12:03:02 2010// +/connectionVTP.h/1.11/Wed Jul 1 10:46:16 2009// +/livefilter.c/1.7/Fri Feb 13 13:02:40 2009// +/livefilter.h/1.5/Mon Apr 7 14:27:31 2008// +/livestreamer.c/1.30/Sat Feb 20 23:02:10 2010// +/livestreamer.h/1.20/Mon Jul 6 06:23:29 2009// +/menuHTTP.c/1.6/Sat Feb 20 22:18:14 2010// +/menuHTTP.h/1.3/Tue Sep 15 10:39:18 2009// +/recplayer.c/1.1/Wed Jul 1 11:00:49 2009// +/recplayer.h/1.1/Wed Jul 1 11:00:49 2009// +/server.c/1.10/Fri Feb 13 10:39:22 2009// +/server.h/1.6/Wed Oct 22 11:59:32 2008// +/setup.c/1.9/Tue Oct 13 06:38:47 2009// +/setup.h/1.3/Fri Sep 18 10:43:26 2009// +/streamer.c/1.19/Fri Jun 19 06:32:45 2009// +/streamer.h/1.11/Fri Jun 19 06:32:45 2009// +/suspend.c/1.3/Wed Oct 22 11:59:32 2008// +/suspend.dat/1.1.1.1/Thu Dec 30 22:44:26 2004// +/suspend.h/1.2/Wed Oct 22 11:59:32 2008// +D diff --git a/plugins/streamdev/streamdev-cvs/server/CVS/Entries.Log b/plugins/streamdev/streamdev-cvs/server/CVS/Entries.Log new file mode 100644 index 0000000..5674fc6 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/CVS/Entries.Log @@ -0,0 +1 @@ +A D/http//// diff --git a/plugins/streamdev/streamdev-cvs/server/CVS/Repository b/plugins/streamdev/streamdev-cvs/server/CVS/Repository new file mode 100644 index 0000000..785255b --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/CVS/Repository @@ -0,0 +1 @@ +streamdev/server diff --git a/plugins/streamdev/streamdev-cvs/server/CVS/Root b/plugins/streamdev/streamdev-cvs/server/CVS/Root new file mode 100644 index 0000000..2c7f6ce --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/CVS/Root @@ -0,0 +1 @@ +:pserver:anoncvs@vdr-developer.org:/var/cvsroot diff --git a/plugins/streamdev/streamdev-cvs/server/component.c b/plugins/streamdev/streamdev-cvs/server/component.c new file mode 100644 index 0000000..70d861a --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/component.c @@ -0,0 +1,49 @@ +/* + * $Id: component.c,v 1.4 2009/02/13 10:39:22 schmirl Exp $ + */ + +#include "server/component.h" +#include "server/connection.h" + +cServerComponent::cServerComponent(const char *Protocol, const char *ListenIp, + uint ListenPort, int Type, int IpProto): + m_Protocol(Protocol), + m_Listen(Type, IpProto), + m_ListenIp(ListenIp), + m_ListenPort(ListenPort) +{ +} + +cServerComponent::~cServerComponent() +{ +} + +bool cServerComponent::Initialize(void) +{ + if (!m_Listen.Listen(m_ListenIp, m_ListenPort, 5)) { + esyslog("Streamdev: Couldn't listen (%s) %s:%d: %m", + m_Protocol, m_ListenIp, m_ListenPort); + return false; + } + isyslog("Streamdev: Listening (%s) on port %d", m_Protocol, m_ListenPort); + return true; +} + +void cServerComponent::Destruct(void) +{ + m_Listen.Close(); +} + +cServerConnection *cServerComponent::Accept(void) +{ + cServerConnection *client = NewClient(); + if (client->Accept(m_Listen)) { + isyslog("Streamdev: Accepted new client (%s) %s:%d", m_Protocol, + client->RemoteIp().c_str(), client->RemotePort()); + return client; + } else { + esyslog("Streamdev: Couldn't accept (%s): %m", m_Protocol); + delete client; + } + return NULL; +} diff --git a/plugins/streamdev/streamdev-cvs/server/component.h b/plugins/streamdev/streamdev-cvs/server/component.h new file mode 100644 index 0000000..7efd4ba --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/component.h @@ -0,0 +1,51 @@ +/* + * $Id: component.h,v 1.3 2009/02/13 10:39:22 schmirl Exp $ + */ + +#ifndef VDR_STREAMDEV_SERVERS_COMPONENT_H +#define VDR_STREAMDEV_SERVERS_COMPONENT_H + +#include "tools/socket.h" +#include "tools/select.h" + +#include <vdr/tools.h> + +class cServerConnection; + +/* Basic TCP listen server, all functions virtual if a derivation wants to do + things different */ + +class cServerComponent: public cListObject { +private: + const char *m_Protocol; + cTBSocket m_Listen; + const char *m_ListenIp; + uint m_ListenPort; + +protected: + /* Returns a new connection object for Accept() */ + virtual cServerConnection *NewClient(void) = 0; + +public: + cServerComponent(const char *Protocol, const char *ListenIp, uint ListenPort, int Type = SOCK_STREAM, int IpProto = 0); + virtual ~cServerComponent(); + + /* Starts listening on the specified Port, override if you want to do things + different */ + virtual bool Initialize(void); + + /* Stops listening, override if you want to do things different */ + virtual void Destruct(void); + + /* Get the listening socket's file number */ + virtual int Socket(void) const { return (int)m_Listen; } + + /* Adds the listening socket to the Select object */ + virtual void Add(cTBSelect &Select) const { Select.Add(m_Listen); } + + /* Accepts the connection on a NewClient() object and calls the + Welcome() on it, override if you want to do things different */ + virtual cServerConnection *Accept(void); +}; + +#endif // VDR_STREAMDEV_SERVERS_COMPONENT_H diff --git a/plugins/streamdev/streamdev-cvs/server/componentHTTP.c b/plugins/streamdev/streamdev-cvs/server/componentHTTP.c new file mode 100644 index 0000000..70e95e9 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/componentHTTP.c @@ -0,0 +1,18 @@ +/* + * $Id: componentHTTP.c,v 1.2 2005/05/09 20:22:29 lordjaxom Exp $ + */ + +#include "server/componentHTTP.h" +#include "server/connectionHTTP.h" +#include "server/setup.h" + +cComponentHTTP::cComponentHTTP(void): + cServerComponent("HTTP", StreamdevServerSetup.HTTPBindIP, + StreamdevServerSetup.HTTPServerPort) +{ +} + +cServerConnection *cComponentHTTP::NewClient(void) +{ + return new cConnectionHTTP; +} diff --git a/plugins/streamdev/streamdev-cvs/server/componentHTTP.h b/plugins/streamdev/streamdev-cvs/server/componentHTTP.h new file mode 100644 index 0000000..29dc087 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/componentHTTP.h @@ -0,0 +1,18 @@ +/* + * $Id: componentHTTP.h,v 1.2 2005/05/09 20:22:29 lordjaxom Exp $ + */ + +#ifndef VDR_STREAMDEV_HTTPSERVER_H +#define VDR_STREAMDEV_HTTPSERVER_H + +#include "server/component.h" + +class cComponentHTTP: public cServerComponent { +protected: + virtual cServerConnection *NewClient(void); + +public: + cComponentHTTP(void); +}; + +#endif // VDR_STREAMDEV_HTTPSERVER_H diff --git a/plugins/streamdev/streamdev-cvs/server/componentIGMP.c b/plugins/streamdev/streamdev-cvs/server/componentIGMP.c new file mode 100644 index 0000000..d9f8811 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/componentIGMP.c @@ -0,0 +1,447 @@ +/* + * $Id: componentIGMP.c,v 1.2 2009/07/03 21:44:19 schmirl Exp $ + */ +#include <netinet/ip.h> +#include <netinet/igmp.h> + +#include "server/componentIGMP.h" +#include "server/connectionIGMP.h" +#include "server/setup.h" + +#ifndef IGMP_ALL_HOSTS +#define IGMP_ALL_HOSTS htonl(0xE0000001L) +#endif +#ifndef IGMP_ALL_ROUTER +#define IGMP_ALL_ROUTER htonl(0xE0000002L) +#endif + +// IGMP parameters according to RFC2236. All time values in seconds. +#define IGMP_ROBUSTNESS 2 +#define IGMP_QUERY_INTERVAL 125 +#define IGMP_QUERY_RESPONSE_INTERVAL 10 +#define IGMP_GROUP_MEMBERSHIP_INTERVAL (2 * IGMP_QUERY_INTERVAL + IGMP_QUERY_RESPONSE_INTERVAL) +#define IGMP_OTHER_QUERIER_PRESENT_INTERVAL (2 * IGMP_QUERY_INTERVAL + IGMP_QUERY_RESPONSE_INTERVAL / 2) +#define IGMP_STARTUP_QUERY_INTERVAL (IGMP_QUERY_INTERVAL / 4) +#define IGMP_STARTUP_QUERY_COUNT IGMP_ROBUSTNESS +// This value is 1/10 sec. RFC default is 10. Reduced to minimum to free unused channels ASAP +#define IGMP_LAST_MEMBER_QUERY_INTERVAL_TS 1 +#define IGMP_LAST_MEMBER_QUERY_COUNT IGMP_ROBUSTNESS + +// operations on struct timeval +#define TV_CMP(a, cmp, b) (a.tv_sec == b.tv_sec ? a.tv_usec cmp b.tv_usec : a.tv_sec cmp b.tv_sec) +#define TV_SET(tv) (tv.tv_sec || tv.tv_usec) +#define TV_CLR(tv) memset(&tv, 0, sizeof(tv)) +#define TV_CPY(dst, src) memcpy(&dst, &src, sizeof(dst)) +#define TV_ADD(dst, ts) dst.tv_sec += ts / 10; dst.tv_usec += (ts % 10) * 100000; if (dst.tv_usec >= 1000000) { dst.tv_usec -= 1000000; dst.tv_sec++; } + +class cMulticastGroup: public cListObject +{ +public: + cConnectionIGMP *connection; + in_addr_t group; + in_addr_t reporter; + struct timeval timeout; + struct timeval v1timer; + struct timeval retransmit; + + cMulticastGroup(in_addr_t Group); +}; + +cMulticastGroup::cMulticastGroup(in_addr_t Group) : + connection(NULL), + group(Group), + reporter(0) +{ + TV_CLR(timeout); + TV_CLR(v1timer); + TV_CLR(retransmit); +} + +void logIGMP(uint8_t type, struct in_addr Src, struct in_addr Dst, struct in_addr Grp) +{ + const char* msg; + switch (type) { + case IGMP_MEMBERSHIP_QUERY: msg = "membership query"; break; + case IGMP_V1_MEMBERSHIP_REPORT: msg = "V1 membership report"; break; + case IGMP_V2_MEMBERSHIP_REPORT: msg = "V2 membership report"; break; + case IGMP_V2_LEAVE_GROUP: msg = "leave group"; break; + default: msg = "unknown"; break; + } + char* s = strdup(inet_ntoa(Src)); + char* d = strdup(inet_ntoa(Dst)); + dsyslog("streamdev-server IGMP: Received %s from %s (dst %s) for %s", msg, s, d, inet_ntoa(Grp)); + free(s); + free(d); +} + +/* Taken from http://tools.ietf.org/html/rfc1071 */ +uint16_t inetChecksum(uint16_t *addr, int count) +{ + uint32_t sum = 0; + while (count > 1) { + sum += *addr++; + count -= 2; + } + + if( count > 0 ) + sum += * (uint8_t *) addr; + + while (sum>>16) + sum = (sum & 0xffff) + (sum >> 16); + + return ~sum; +} + +cComponentIGMP::cComponentIGMP(void): + cServerComponent("IGMP", "0.0.0.0", 0, SOCK_RAW, IPPROTO_IGMP), + cThread("IGMP timeout handler"), + m_BindIp(inet_addr(StreamdevServerSetup.IGMPBindIP)), + m_MaxChannelNumber(0), + m_StartupQueryCount(IGMP_STARTUP_QUERY_COUNT), + m_Querier(true) +{ +} + +cComponentIGMP::~cComponentIGMP(void) +{ +} + +cMulticastGroup* cComponentIGMP::FindGroup(in_addr_t Group) const +{ + cMulticastGroup *group = m_Groups.First(); + while (group && group->group != Group) + group = m_Groups.Next(group); + return group; +} + +bool cComponentIGMP::Initialize(void) +{ + if (cServerComponent::Initialize() && IGMPMembership(IGMP_ALL_ROUTER)) + { + for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) + { + if (channel->GroupSep()) + continue; + int num = channel->Number(); + if (!IGMPMembership(htonl(MULTICAST_PRIV_MIN + num))) + break; + m_MaxChannelNumber = num; + } + if (m_MaxChannelNumber == 0) + { + IGMPMembership(IGMP_ALL_ROUTER, false); + esyslog("streamdev-server IGMP: no multicast group joined"); + } + else + { + Start(); + } + } + return m_MaxChannelNumber > 0; +} + +void cComponentIGMP::Destruct(void) +{ + if (m_MaxChannelNumber > 0) + { + Cancel(3); + for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) + { + if (channel->GroupSep()) + continue; + int num = channel->Number(); + if (num > m_MaxChannelNumber) + break; + IGMPMembership(htonl(MULTICAST_PRIV_MIN + num), false); + } + IGMPMembership(IGMP_ALL_ROUTER, false); + } + m_MaxChannelNumber = 0; + cServerComponent::Destruct(); +} + +cServerConnection *cComponentIGMP::NewClient(void) +{ + return new cConnectionIGMP("IGMP", StreamdevServerSetup.IGMPClientPort, (eStreamType) StreamdevServerSetup.IGMPStreamType); +} + +cServerConnection* cComponentIGMP::Accept(void) +{ + ssize_t recv_len; + int ip_hdrlen, ip_datalen; + struct ip *ip; + struct igmp *igmp; + + while ((recv_len = ::recvfrom(Socket(), m_ReadBuffer, sizeof(m_ReadBuffer), 0, NULL, NULL)) < 0 && errno == EINTR) + errno = 0; + + if (recv_len < 0) { + esyslog("streamdev-server IGMP: read failed: %m"); + return NULL; + } + else if (recv_len < (ssize_t) sizeof(struct ip)) { + esyslog("streamdev-server IGMP: IP packet too short"); + return NULL; + } + + ip = (struct ip*) m_ReadBuffer; + + // filter out my own packets + if (ip->ip_src.s_addr == m_BindIp) + return NULL; + + ip_hdrlen = ip->ip_hl << 2; +#ifdef __FreeBSD__ + ip_datalen = ip->ip_len; +#else + ip_datalen = ntohs(ip->ip_len) - ip_hdrlen; +#endif + if (ip->ip_p != IPPROTO_IGMP) { + esyslog("streamdev-server IGMP: Unexpected protocol %hhu", ip->ip_p); + return NULL; + } + if (recv_len < ip_hdrlen + IGMP_MINLEN) { + esyslog("streamdev-server IGMP: packet too short"); + return NULL; + } + igmp = (struct igmp*) (m_ReadBuffer + ip_hdrlen); + uint16_t chksum = igmp->igmp_cksum; + igmp->igmp_cksum = 0; + if (chksum != inetChecksum((uint16_t *)igmp, ip_datalen)) + { + esyslog("INVALID CHECKSUM %d %d %d %lu 0x%x 0x%x", (int) ntohs(ip->ip_len), ip_hdrlen, ip_datalen, (unsigned long int) recv_len, chksum, inetChecksum((uint16_t *)igmp, ip_datalen)); + return NULL; + } + logIGMP(igmp->igmp_type, ip->ip_src, ip->ip_dst, igmp->igmp_group); + return ProcessMessage(igmp, igmp->igmp_group.s_addr, ip->ip_src.s_addr); +} + +cServerConnection* cComponentIGMP::ProcessMessage(struct igmp *Igmp, in_addr_t Group, in_addr_t Sender) +{ + cServerConnection* conn = NULL; + cMulticastGroup* group; + LOCK_THREAD; + switch (Igmp->igmp_type) { + case IGMP_MEMBERSHIP_QUERY: + if (ntohl(Sender) < ntohl(m_BindIp)) + IGMPStartOtherQuerierPresentTimer(); + break; + case IGMP_V1_MEMBERSHIP_REPORT: + case IGMP_V2_MEMBERSHIP_REPORT: + group = FindGroup(Group); + if (!group) { + group = new cMulticastGroup(Group); + m_Groups.Add(group); + } + if (!group->connection) { + IGMPStartMulticast(group); + conn = group->connection; + } + IGMPStartTimer(group, Sender); + if (Igmp->igmp_type == IGMP_V1_MEMBERSHIP_REPORT) + IGMPStartV1HostTimer(group); + break; + case IGMP_V2_LEAVE_GROUP: + group = FindGroup(Group); + if (group && !TV_SET(group->v1timer)) { + if (group->reporter == Sender) { + IGMPStartTimerAfterLeave(group, m_Querier ? IGMP_LAST_MEMBER_QUERY_INTERVAL_TS : Igmp->igmp_code); + if (m_Querier) + IGMPSendGroupQuery(group); + IGMPStartRetransmitTimer(group); + } + m_CondWait.Signal(); + } + break; + default: + break; + } + return conn; +} + +void cComponentIGMP::Action() +{ + while (Running()) { + struct timeval now; + struct timeval next; + + gettimeofday(&now, NULL); + TV_CPY(next, now); + next.tv_sec += IGMP_QUERY_INTERVAL; + + cMulticastGroup *del = NULL; + { + LOCK_THREAD; + if (TV_CMP(m_GeneralQueryTimer, <, now)) { + dsyslog("General Query"); + IGMPSendGeneralQuery(); + IGMPStartGeneralQueryTimer(); + } + if (TV_CMP(next, >, m_GeneralQueryTimer)) + TV_CPY(next, m_GeneralQueryTimer); + + for (cMulticastGroup *group = m_Groups.First(); group; group = m_Groups.Next(group)) { + if (TV_CMP(group->timeout, <, now)) { + IGMPStopMulticast(group); + IGMPClearRetransmitTimer(group); + if (del) + m_Groups.Del(del); + del = group; + } + else if (m_Querier && TV_SET(group->retransmit) && TV_CMP(group->retransmit, <, now)) { + IGMPSendGroupQuery(group); + IGMPStartRetransmitTimer(group); + if (TV_CMP(next, >, group->retransmit)) + TV_CPY(next, group->retransmit); + } + else if (TV_SET(group->v1timer) && TV_CMP(group->v1timer, <, now)) { + TV_CLR(group->v1timer); + } + else { + if (TV_CMP(next, >, group->timeout)) + TV_CPY(next, group->timeout); + if (TV_SET(group->retransmit) && TV_CMP(next, >, group->retransmit)) + TV_CPY(next, group->retransmit); + if (TV_SET(group->v1timer) && TV_CMP(next, >, group->v1timer)) + TV_CPY(next, group->v1timer); + } + } + if (del) + m_Groups.Del(del); + } + + int sleep = (next.tv_sec - now.tv_sec) * 1000; + sleep += (next.tv_usec - now.tv_usec) / 1000; + if (next.tv_usec < now.tv_usec) + sleep += 1000; + dsyslog("Sleeping %d ms", sleep); + m_CondWait.Wait(sleep); + } +} + +bool cComponentIGMP::IGMPMembership(in_addr_t Group, bool Add) +{ + struct ip_mreqn mreq; + mreq.imr_multiaddr.s_addr = Group; + mreq.imr_address.s_addr = INADDR_ANY; + mreq.imr_ifindex = 0; + if (setsockopt(Socket(), IPPROTO_IP, Add ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) + { + esyslog("streamdev-server IGMP: unable to %s %s: %m", Add ? "join" : "leave", inet_ntoa(mreq.imr_multiaddr)); + if (errno == ENOBUFS) + esyslog("consider increasing sys.net.ipv4.igmp_max_memberships"); + return false; + } + return true; +} + +void cComponentIGMP::IGMPSendQuery(in_addr_t Group, int Timeout) +{ + struct sockaddr_in dst; + struct igmp query; + + dst.sin_family = AF_INET; + dst.sin_port = IPPROTO_IGMP; + dst.sin_addr.s_addr = Group; + query.igmp_type = IGMP_MEMBERSHIP_QUERY; + query.igmp_code = Timeout * 10; + query.igmp_cksum = 0; + query.igmp_group.s_addr = (Group == IGMP_ALL_HOSTS) ? 0 : Group; + query.igmp_cksum = inetChecksum((uint16_t *) &query, sizeof(query)); + + for (int i = 0; i < 5 && ::sendto(Socket(), &query, sizeof(query), 0, (sockaddr*)&dst, sizeof(dst)) == -1; i++) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + esyslog("streamdev-server IGMP: unable to query group %s: %m", inet_ntoa(dst.sin_addr)); + break; + } + cCondWait::SleepMs(10); + } +} + +// Querier state actions +void cComponentIGMP::IGMPStartGeneralQueryTimer() +{ + m_Querier = true; + if (m_StartupQueryCount) { + gettimeofday(&m_GeneralQueryTimer, NULL); + m_GeneralQueryTimer.tv_sec += IGMP_STARTUP_QUERY_INTERVAL; + m_StartupQueryCount--; + } + else { + gettimeofday(&m_GeneralQueryTimer, NULL); + m_GeneralQueryTimer.tv_sec += IGMP_QUERY_INTERVAL; + } +} + +void cComponentIGMP::IGMPStartOtherQuerierPresentTimer() +{ + m_Querier = false; + m_StartupQueryCount = 0; + gettimeofday(&m_GeneralQueryTimer, NULL); + m_GeneralQueryTimer.tv_sec += IGMP_OTHER_QUERIER_PRESENT_INTERVAL; +} + +void cComponentIGMP::IGMPSendGeneralQuery() +{ + IGMPSendQuery(IGMP_ALL_HOSTS, IGMP_QUERY_RESPONSE_INTERVAL); +} + +// Group state actions +void cComponentIGMP::IGMPStartTimer(cMulticastGroup* Group, in_addr_t Member) +{ + gettimeofday(&Group->timeout, NULL); + Group->timeout.tv_sec += IGMP_GROUP_MEMBERSHIP_INTERVAL; + TV_CLR(Group->retransmit); + Group->reporter = Member; + +} + +void cComponentIGMP::IGMPStartV1HostTimer(cMulticastGroup* Group) +{ + gettimeofday(&Group->v1timer, NULL); + Group->v1timer.tv_sec += IGMP_GROUP_MEMBERSHIP_INTERVAL; +} + +void cComponentIGMP::IGMPStartTimerAfterLeave(cMulticastGroup* Group, unsigned int MaxResponseTimeTs) +{ + //Group->Update(time(NULL) + MaxResponseTime * IGMP_LAST_MEMBER_QUERY_COUNT / 10); + MaxResponseTimeTs *= IGMP_LAST_MEMBER_QUERY_COUNT; + gettimeofday(&Group->timeout, NULL); + TV_ADD(Group->timeout, MaxResponseTimeTs); + TV_CLR(Group->retransmit); + Group->reporter = 0; +} + +void cComponentIGMP::IGMPStartRetransmitTimer(cMulticastGroup* Group) +{ + gettimeofday(&Group->retransmit, NULL); + TV_ADD(Group->retransmit, IGMP_LAST_MEMBER_QUERY_INTERVAL_TS); +} + +void cComponentIGMP::IGMPClearRetransmitTimer(cMulticastGroup* Group) +{ + TV_CLR(Group->retransmit); +} + +void cComponentIGMP::IGMPSendGroupQuery(cMulticastGroup* Group) +{ + IGMPSendQuery(Group->group, IGMP_LAST_MEMBER_QUERY_INTERVAL_TS); +} + +void cComponentIGMP::IGMPStartMulticast(cMulticastGroup* Group) +{ + in_addr_t g = ntohl(Group->group); + if (g > MULTICAST_PRIV_MIN && g <= MULTICAST_PRIV_MAX) { + cChannel *channel = Channels.GetByNumber(g - MULTICAST_PRIV_MIN); + Group->connection = (cConnectionIGMP*) NewClient(); + if (!Group->connection->Start(channel, Group->group)) { + DELETENULL(Group->connection); + } + } +} + +void cComponentIGMP::IGMPStopMulticast(cMulticastGroup* Group) +{ + if (Group->connection) + Group->connection->Stop(); +} diff --git a/plugins/streamdev/streamdev-cvs/server/componentIGMP.h b/plugins/streamdev/streamdev-cvs/server/componentIGMP.h new file mode 100644 index 0000000..09d8fde --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/componentIGMP.h @@ -0,0 +1,62 @@ +/* + * $Id: componentIGMP.h,v 1.1 2009/02/13 10:39:22 schmirl Exp $ + */ + +#ifndef VDR_STREAMDEV_IGMPSERVER_H +#define VDR_STREAMDEV_IGMPSERVER_H + +#include <sys/time.h> +#include <time.h> +#include <vdr/thread.h> +#include "server/component.h" + +class cConnectionIGMP; +class cMulticastGroup; + +class cComponentIGMP: public cServerComponent, public cThread { +private: + char m_ReadBuffer[2048]; + cList<cMulticastGroup> m_Groups; + in_addr_t m_BindIp; + int m_MaxChannelNumber; + struct timeval m_GeneralQueryTimer; + int m_StartupQueryCount; + bool m_Querier; + cCondWait m_CondWait; + + cMulticastGroup* FindGroup(in_addr_t Group) const; + + /* Add or remove local host to multicast group */ + bool IGMPMembership(in_addr_t Group, bool Add = true); + void IGMPSendQuery(in_addr_t Group, int Timeout); + + cServerConnection* ProcessMessage(struct igmp *Igmp, in_addr_t Group, in_addr_t Sender); + + void IGMPStartGeneralQueryTimer(); + void IGMPStartOtherQuerierPresentTimer(); + void IGMPSendGeneralQuery(); + + void IGMPStartTimer(cMulticastGroup* Group, in_addr_t Member); + void IGMPStartV1HostTimer(cMulticastGroup* Group); + void IGMPStartTimerAfterLeave(cMulticastGroup* Group, unsigned int MaxResponseTime); + void IGMPStartRetransmitTimer(cMulticastGroup* Group); + void IGMPClearRetransmitTimer(cMulticastGroup* Group); + void IGMPSendGroupQuery(cMulticastGroup* Group); + void IGMPStartMulticast(cMulticastGroup* Group); + void IGMPStopMulticast(cMulticastGroup* Group); + + virtual void Action(); + +protected: + virtual cServerConnection *NewClient(void); + +public: + virtual bool Initialize(void); + virtual void Destruct(void); + virtual cServerConnection* Accept(void); + + cComponentIGMP(void); + ~cComponentIGMP(void); +}; + +#endif // VDR_STREAMDEV_IGMPSERVER_H diff --git a/plugins/streamdev/streamdev-cvs/server/componentVTP.c b/plugins/streamdev/streamdev-cvs/server/componentVTP.c new file mode 100644 index 0000000..ed2df1c --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/componentVTP.c @@ -0,0 +1,18 @@ +/* + * $Id: componentVTP.c,v 1.2 2005/05/09 20:22:29 lordjaxom Exp $ + */ + +#include "server/componentVTP.h" +#include "server/connectionVTP.h" +#include "server/setup.h" + +cComponentVTP::cComponentVTP(void): + cServerComponent("VTP", StreamdevServerSetup.VTPBindIP, + StreamdevServerSetup.VTPServerPort) +{ +} + +cServerConnection *cComponentVTP::NewClient(void) +{ + return new cConnectionVTP; +} diff --git a/plugins/streamdev/streamdev-cvs/server/componentVTP.h b/plugins/streamdev/streamdev-cvs/server/componentVTP.h new file mode 100644 index 0000000..4f61eb5 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/componentVTP.h @@ -0,0 +1,18 @@ +/* + * $Id: componentVTP.h,v 1.2 2005/05/09 20:22:29 lordjaxom Exp $ + */ + +#ifndef VDR_STREAMDEV_SERVERS_SERVERVTP_H +#define VDR_STREAMDEV_SERVERS_SERVERVTP_H + +#include "server/component.h" + +class cComponentVTP: public cServerComponent { +protected: + virtual cServerConnection *NewClient(void); + +public: + cComponentVTP(void); +}; + +#endif // VDR_STREAMDEV_SERVERS_SERVERVTP_H diff --git a/plugins/streamdev/streamdev-cvs/server/connection.c b/plugins/streamdev/streamdev-cvs/server/connection.c new file mode 100644 index 0000000..eccb3c4 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/connection.c @@ -0,0 +1,255 @@ +/* + * $Id: connection.c,v 1.13 2009/09/18 10:43:26 schmirl Exp $ + */ + +#include "server/connection.h" +#include "server/setup.h" +#include "server/suspend.h" +#include "common.h" + +#include <vdr/tools.h> +#include <string.h> +#include <stdarg.h> +#include <errno.h> + +cServerConnection::cServerConnection(const char *Protocol, int Type): + cTBSocket(Type), + m_Protocol(Protocol), + m_DeferClose(false), + m_Pending(false), + m_ReadBytes(0), + m_WriteBytes(0), + m_WriteIndex(0) +{ +} + +cServerConnection::~cServerConnection() +{ +} + +const cChannel* cServerConnection::ChannelFromString(const char *String, int *Apid) { + const cChannel *channel = NULL; + char *string = strdup(String); + char *ptr, *end; + int apididx = 0; + + if ((ptr = strrchr(string, '+')) != NULL) { + *(ptr++) = '\0'; + apididx = strtoul(ptr, &end, 10); + Dprintf("found apididx: %d\n", apididx); + } + + if (isnumber(string)) { + int temp = strtol(String, NULL, 10); + if (temp >= 1 && temp <= Channels.MaxNumber()) + channel = Channels.GetByNumber(temp); + } else { + channel = Channels.GetByChannelID(tChannelID::FromString(string)); + + if (channel == NULL) { + int i = 1; + while ((channel = Channels.GetByNumber(i, 1)) != NULL) { + if (String == channel->Name()) + break; + + i = channel->Number() + 1; + } + } + } + + if (channel != NULL && apididx > 0) { + int apid = 0, index = 1; + + for (int i = 0; channel->Apid(i) != 0; ++i, ++index) { + if (index == apididx) { + apid = channel->Apid(i); + break; + } + } + + if (apid == 0) { + for (int i = 0; channel->Dpid(i) != 0; ++i, ++index) { + if (index == apididx) { + apid = channel->Dpid(i); + break; + } + } + } + + if (Apid != NULL) + *Apid = apid; + } + + free(string); + return channel; +} + +bool cServerConnection::Read(void) +{ + int b; + if ((b = cTBSocket::Read(m_ReadBuffer + m_ReadBytes, + sizeof(m_ReadBuffer) - m_ReadBytes - 1)) < 0) { + esyslog("ERROR: read from client (%s) %s:%d failed: %m", + m_Protocol, RemoteIp().c_str(), RemotePort()); + return false; + } + + if (b == 0) { + isyslog("client (%s) %s:%d has closed connection", + m_Protocol, RemoteIp().c_str(), RemotePort()); + return false; + } + + m_ReadBytes += b; + m_ReadBuffer[m_ReadBytes] = '\0'; + + char *end; + bool result = true; + while ((end = strchr(m_ReadBuffer, '\012')) != NULL) { + *end = '\0'; + if (end > m_ReadBuffer && *(end - 1) == '\015') + *(end - 1) = '\0'; + + if (!Command(m_ReadBuffer)) + return false; + + m_ReadBytes -= ++end - m_ReadBuffer; + if (m_ReadBytes > 0) + memmove(m_ReadBuffer, end, m_ReadBytes); + } + + if (m_ReadBytes == sizeof(m_ReadBuffer) - 1) { + esyslog("ERROR: streamdev: input buffer overflow (%s) for %s:%d", + m_Protocol, RemoteIp().c_str(), RemotePort()); + return false; + } + + return result; +} + +bool cServerConnection::Write(void) +{ + int b; + if ((b = cTBSocket::Write(m_WriteBuffer + m_WriteIndex, + m_WriteBytes - m_WriteIndex)) < 0) { + esyslog("ERROR: streamdev: write to client (%s) %s:%d failed: %m", + m_Protocol, RemoteIp().c_str(), RemotePort()); + return false; + } + + m_WriteIndex += b; + if (m_WriteIndex == m_WriteBytes) { + m_WriteIndex = 0; + m_WriteBytes = 0; + if (m_Pending) + Command(NULL); + if (m_DeferClose) + return false; + Flushed(); + } + return true; +} + +bool cServerConnection::Respond(const char *Message, bool Last, ...) +{ + char *buffer; + int length; + va_list ap; + va_start(ap, Last); + length = vasprintf(&buffer, Message, ap); + va_end(ap); + + if (length < 0) { + esyslog("ERROR: streamdev: buffer allocation failed (%s) for %s:%d", + m_Protocol, RemoteIp().c_str(), RemotePort()); + return false; + } + + if (m_WriteBytes + length + 2 > sizeof(m_WriteBuffer)) { + esyslog("ERROR: streamdev: output buffer overflow (%s) for %s:%d", + m_Protocol, RemoteIp().c_str(), RemotePort()); + free(buffer); + return false; + } + Dprintf("OUT: |%s|\n", buffer); + memcpy(m_WriteBuffer + m_WriteBytes, buffer, length); + free(buffer); + + m_WriteBytes += length; + m_WriteBuffer[m_WriteBytes++] = '\015'; + m_WriteBuffer[m_WriteBytes++] = '\012'; + m_Pending = !Last; + return true; +} + +cDevice *cServerConnection::GetDevice(const cChannel *Channel, int Priority) +{ + cDevice *device = NULL; + + /*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");*/ + + 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() + && !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); + } + } + + return device; +} diff --git a/plugins/streamdev/streamdev-cvs/server/connection.h b/plugins/streamdev/streamdev-cvs/server/connection.h new file mode 100644 index 0000000..73cb3d5 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/connection.h @@ -0,0 +1,99 @@ +/* + * $Id: connection.h,v 1.8 2009/09/18 10:43:26 schmirl Exp $ + */ + +#ifndef VDR_STREAMDEV_SERVER_CONNECTION_H +#define VDR_STREAMDEV_SERVER_CONNECTION_H + +#include "tools/socket.h" +#include "common.h" + +class cChannel; +class cDevice; + +/* Basic capabilities of a straight text-based protocol, most functions + virtual to support more complicated protocols */ + +class cServerConnection: public cListObject, public cTBSocket +{ +private: + const char *m_Protocol; + bool m_DeferClose; + bool m_Pending; + + char m_ReadBuffer[MAXPARSEBUFFER]; + uint m_ReadBytes; + + char m_WriteBuffer[MAXPARSEBUFFER]; + uint m_WriteBytes; + uint m_WriteIndex; + +protected: + /* Will be called when a command terminated by a newline has been + received */ + virtual bool Command(char *Cmd) = 0; + + /* Will put Message into the response queue, which will be sent in the next + server cycle. Note that Message will be line-terminated by Respond. + Only one line at a time may be sent. If there are lines to follow, set + Last to false. Command(NULL) will be called in the next cycle, so you can + post the next line. */ + virtual bool Respond(const char *Message, bool Last = true, ...); + //__attribute__ ((format (printf, 2, 4))); + + static const cChannel *ChannelFromString(const char *String, int *Apid = NULL); + +public: + /* If you derive, specify a short string such as HTTP for Protocol, which + will be displayed in error messages */ + cServerConnection(const char *Protocol, int Type = SOCK_STREAM); + virtual ~cServerConnection(); + + /* If true, any client IP will be accepted */ + virtual bool CanAuthenticate(void) { return false; } + + /* Gets called if the client has been accepted by the core */ + virtual void Welcome(void) { } + + /* Gets called if the client has been rejected by the core */ + virtual void Reject(void) { DeferClose(); } + + /* Get the client socket's file number */ + virtual int Socket(void) const { return (int)*this; } + + /* Determine if there is data to send or any command pending */ + virtual bool HasData(void) const; + + /* Gets called by server when the socket can accept more data. Writes + the buffer filled up by Respond(). Calls Command(NULL) if there is a + command pending. Returns false in case of an error */ + virtual bool Write(void); + + /* Gets called by server when there is incoming data to read. Calls + Command() for each line. Returns false in case of an error, or if + the connection shall be closed and removed by the server */ + virtual bool Read(void); + + /* Is polled regularely by the server. Returns true if the connection + needs to be terminated. */ + virtual bool Abort(void) const = 0; + + /* 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 + cDevice in a following call to StartTransfer */ + cDevice *GetDevice(const cChannel *Channel, int Priority); + + virtual void Flushed(void) {} + + virtual void Detach(void) = 0; + virtual void Attach(void) = 0; +}; + +inline bool cServerConnection::HasData(void) const +{ + return m_WriteBytes > 0 || m_Pending || m_DeferClose; +} + +#endif // VDR_STREAMDEV_SERVER_CONNECTION_H diff --git a/plugins/streamdev/streamdev-cvs/server/connectionHTTP.c b/plugins/streamdev/streamdev-cvs/server/connectionHTTP.c new file mode 100644 index 0000000..83e568d --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/connectionHTTP.c @@ -0,0 +1,296 @@ +/* + * $Id: connectionHTTP.c,v 1.17 2009/06/19 06:32:45 schmirl Exp $ + */ + +#include <ctype.h> + +#include "server/connectionHTTP.h" +#include "server/menuHTTP.h" +#include "server/server.h" +#include "server/setup.h" + +cConnectionHTTP::cConnectionHTTP(void): + cServerConnection("HTTP"), + m_Status(hsRequest), + m_LiveStreamer(NULL), + m_StreamerParameter(""), + m_Channel(NULL), + m_Apid(0), + m_StreamType((eStreamType)StreamdevServerSetup.HTTPStreamType), + m_ChannelList(NULL) +{ + Dprintf("constructor hsRequest\n"); +} + +cConnectionHTTP::~cConnectionHTTP() +{ + delete m_LiveStreamer; +} + +bool cConnectionHTTP::CanAuthenticate(void) +{ + return opt_auth != NULL; +} + +bool cConnectionHTTP::Command(char *Cmd) +{ + Dprintf("command %s\n", Cmd); + switch (m_Status) { + case hsRequest: + Dprintf("Request\n"); + m_Request = Cmd; + m_Status = hsHeaders; + return true; + + case hsHeaders: + if (*Cmd == '\0') { + m_Status = hsBody; + return ProcessRequest(); + } + if (strncasecmp(Cmd, "Host:", 5) == 0) { + Dprintf("Host-Header\n"); + m_Host = (std::string) skipspace(Cmd + 5); + return true; + } + else if (strncasecmp(Cmd, "Authorization:", 14) == 0) { + Cmd = skipspace(Cmd + 14); + if (strncasecmp(Cmd, "Basic", 5) == 0) { + Dprintf("'Authorization Basic'-Header\n"); + m_Authorization = (std::string) skipspace(Cmd + 5); + return true; + } + } + Dprintf("header\n"); + return true; + default: + // skip additional blank lines + if (*Cmd == '\0') + return true; + break; + } + return false; // ??? shouldn't happen +} + +bool cConnectionHTTP::ProcessRequest(void) +{ + Dprintf("process\n"); + if (!StreamdevHosts.Acceptable(RemoteIpAddr())) + { + if (!opt_auth || m_Authorization.empty() || m_Authorization.compare(opt_auth) != 0) { + isyslog("streamdev-server: HTTP authorization required"); + DeferClose(); + return Respond("HTTP/1.0 401 Authorization Required") + && Respond("WWW-authenticate: basic Realm=\"Streamdev-Server\")") + && Respond(""); + } + } + if (m_Request.substr(0, 4) == "GET " && CmdGET(m_Request.substr(4))) { + switch (m_Job) { + case hjListing: + if (m_ChannelList) + return Respond("%s", true, m_ChannelList->HttpHeader().c_str()); + break; + + case hjTransfer: + if (m_Channel == NULL) { + DeferClose(); + return Respond("HTTP/1.0 404 not found"); + } + + m_LiveStreamer = new cStreamdevLiveStreamer(0, m_StreamerParameter); + cDevice *device = GetDevice(m_Channel, 0); + if (device != NULL) { + device->SwitchChannel(m_Channel, false); + if (m_LiveStreamer->SetChannel(m_Channel, m_StreamType, m_Apid)) { + m_LiveStreamer->SetDevice(device); + if (!SetDSCP()) + LOG_ERROR_STR("unable to set DSCP sockopt"); + if (m_StreamType == stES && (m_Apid != 0 || ISRADIO(m_Channel))) { + return Respond("HTTP/1.0 200 OK") + && Respond("Content-Type: audio/mpeg") + && Respond("icy-name: %s", true, m_Channel->Name()) + && Respond(""); + } else { + return Respond("HTTP/1.0 200 OK") + && Respond("Content-Type: video/mpeg") + && Respond(""); + } + } + } + DELETENULL(m_LiveStreamer); + DeferClose(); + return Respond("HTTP/1.0 409 Channel not available") + && Respond(""); + } + } + + DeferClose(); + return Respond("HTTP/1.0 400 Bad Request") + && Respond(""); +} + +void cConnectionHTTP::Flushed(void) +{ + std::string line; + + if (m_Status != hsBody) + return; + + switch (m_Job) { + case hjListing: + if (m_ChannelList) { + if (m_ChannelList->HasNext()) { + if (!Respond("%s", true, m_ChannelList->Next().c_str())) + DeferClose(); + } + else { + DELETENULL(m_ChannelList); + m_Status = hsFinished; + DeferClose(); + } + return; + } + // should never be reached + esyslog("streamdev-server cConnectionHTTP::Flushed(): no channel list"); + m_Status = hsFinished; + break; + + case hjTransfer: + Dprintf("streamer start\n"); + m_LiveStreamer->Start(this); + m_Status = hsFinished; + break; + } +} + +bool cConnectionHTTP::CmdGET(const std::string &Opts) +{ + const char *ptr, *sp, *pp, *fp, *xp, *qp, *ep; + const cChannel *chan; + int apid = 0; + + ptr = Opts.c_str(); + + // find begin of URL + sp = skipspace(ptr); + // find end of URL (\0 or first space character) + for (ep = sp; *ep && !isspace(*ep); ep++) + ; + // find begin of query string (first ?) + for (qp = sp; qp < ep && *qp != '?'; qp++) + ; + // find begin of filename (last /) + for (fp = qp; fp > sp && *fp != '/'; --fp) + ; + // find begin of section params (first ;) + for (pp = sp; pp < fp && *pp != ';'; pp++) + ; + // find filename extension (first .) + for (xp = fp; xp < qp && *xp != '.'; xp++) + ; + if (qp - xp > 5) // too long for a filename extension + xp = qp; + + std::string type, filespec, fileext, query; + // Streamtype with leading / stripped off + if (pp > sp) + type = Opts.substr(sp - ptr + 1, pp - sp - 1); + // Section parameters with leading ; stripped off + if (fp > pp) + m_StreamerParameter = Opts.substr(pp - ptr + 1, fp - pp - 1); + // file basename with leading / stripped off + if (xp > fp) + filespec = Opts.substr(fp - ptr + 1, xp - fp - 1); + // file extension including leading . + fileext = Opts.substr(xp - ptr, qp - xp); + // query string including leading ? + query = Opts.substr(qp - ptr, ep - qp); + + Dprintf("before channelfromstring: type(%s) param(%s) filespec(%s) fileext(%s) query(%s)\n", type.c_str(), m_StreamerParameter.c_str(), filespec.c_str(), fileext.c_str(), query.c_str()); + + const char* pType = type.c_str(); + if (strcasecmp(pType, "PS") == 0) { + m_StreamType = stPS; + } else if (strcasecmp(pType, "PES") == 0) { + m_StreamType = stPES; + } else if (strcasecmp(pType, "TS") == 0) { + m_StreamType = stTS; + } else if (strcasecmp(pType, "ES") == 0) { + m_StreamType = stES; + } else if (strcasecmp(pType, "Extern") == 0) { + m_StreamType = stExtern; + } + + std::string groupTarget; + cChannelIterator *iterator = NULL; + + if (filespec.compare("tree") == 0) { + const cChannel* c = NULL; + size_t groupIndex = query.find("group="); + if (groupIndex != std::string::npos) + c = cChannelList::GetGroup(atoi(query.c_str() + groupIndex + 6)); + iterator = new cListTree(c); + groupTarget = filespec + fileext; + } else if (filespec.compare("groups") == 0) { + iterator = new cListGroups(); + groupTarget = (std::string) "group" + fileext; + } else if (filespec.compare("group") == 0) { + const cChannel* c = NULL; + size_t groupIndex = query.find("group="); + if (groupIndex != std::string::npos) + c = cChannelList::GetGroup(atoi(query.c_str() + groupIndex + 6)); + iterator = new cListGroup(c); + } else if (filespec.compare("channels") == 0) { + iterator = new cListChannels(); + } else if (filespec.compare("all") == 0 || + (filespec.empty() && fileext.empty())) { + iterator = new cListAll(); + } + + if (iterator) { + if (filespec.empty() || fileext.compare(".htm") == 0 || fileext.compare(".html") == 0) { + m_ChannelList = new cHtmlChannelList(iterator, m_StreamType, (filespec + fileext + query).c_str(), groupTarget.c_str()); + m_Job = hjListing; + } else if (fileext.compare(".m3u") == 0) { + std::string base; + if (*(m_Host.c_str())) + base = "http://" + m_Host + "/"; + else + base = (std::string) "http://" + LocalIp() + ":" + + (const char*) itoa(StreamdevServerSetup.HTTPServerPort) + "/"; + if (type.empty()) + { + switch (m_StreamType) + { + case stTS: base += "TS/"; break; + case stPS: base += "PS/"; break; + case stPES: base += "PES/"; break; + case stES: base += "ES/"; break; + case stExtern: base += "Extern/"; break; + default: break; + + } + } else { + base += type; + if (!m_StreamerParameter.empty()) + base += ";" + m_StreamerParameter; + base += "/"; + } + m_ChannelList = new cM3uChannelList(iterator, base.c_str()); + m_Job = hjListing; + } else { + delete iterator; + return false; + } + } else if ((chan = ChannelFromString(filespec.c_str(), &apid)) != NULL) { + m_Channel = chan; + m_Apid = apid; + Dprintf("Apid is %d\n", apid); + m_Job = hjTransfer; + } else + return false; + + Dprintf("after channelfromstring\n"); + return true; +} + diff --git a/plugins/streamdev/streamdev-cvs/server/connectionHTTP.h b/plugins/streamdev/streamdev-cvs/server/connectionHTTP.h new file mode 100644 index 0000000..0548959 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/connectionHTTP.h @@ -0,0 +1,70 @@ +/* + * $Id: connectionHTTP.h,v 1.6 2008/10/14 11:05:48 schmirl Exp $ + */ + +#ifndef VDR_STREAMDEV_SERVERS_CONNECTIONHTTP_H +#define VDR_STREAMDEV_SERVERS_CONNECTIONHTTP_H + +#include "connection.h" +#include "server/livestreamer.h" + +#include <tools/select.h> + +class cChannel; +class cStreamdevLiveStreamer; +class cChannelList; + +class cConnectionHTTP: public cServerConnection { +private: + enum eHTTPStatus { + hsRequest, + hsHeaders, + hsBody, + hsFinished, + }; + + enum eHTTPJob { + hjTransfer, + hjListing, + }; + + std::string m_Request; + std::string m_Host; + std::string m_Authorization; + //std::map<std::string,std::string> m_Headers; TODO: later? + eHTTPStatus m_Status; + eHTTPJob m_Job; + // job: transfer + cStreamdevLiveStreamer *m_LiveStreamer; + std::string m_StreamerParameter; + const cChannel *m_Channel; + int m_Apid; + eStreamType m_StreamType; + // job: listing + cChannelList *m_ChannelList; + +protected: + bool ProcessRequest(void); + +public: + cConnectionHTTP(void); + virtual ~cConnectionHTTP(); + + virtual void Attach(void) { if (m_LiveStreamer != NULL) m_LiveStreamer->Attach(); } + virtual void Detach(void) { if (m_LiveStreamer != NULL) m_LiveStreamer->Detach(); } + + virtual bool CanAuthenticate(void); + + virtual bool Command(char *Cmd); + bool CmdGET(const std::string &Opts); + + virtual bool Abort(void) const; + virtual void Flushed(void); +}; + +inline bool cConnectionHTTP::Abort(void) const +{ + return m_LiveStreamer && m_LiveStreamer->Abort(); +} + +#endif // VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H diff --git a/plugins/streamdev/streamdev-cvs/server/connectionIGMP.c b/plugins/streamdev/streamdev-cvs/server/connectionIGMP.c new file mode 100644 index 0000000..dc08798 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/connectionIGMP.c @@ -0,0 +1,64 @@ +/* + * $Id: connectionIGMP.c,v 1.1 2009/02/13 10:39:22 schmirl Exp $ + */ + +#include <ctype.h> + +#include "server/connectionIGMP.h" +#include "server/server.h" +#include "server/setup.h" +#include <vdr/channels.h> + +cConnectionIGMP::cConnectionIGMP(const char* Name, int ClientPort, eStreamType StreamType) : + cServerConnection(Name, SOCK_DGRAM), + m_LiveStreamer(NULL), + m_ClientPort(ClientPort), + m_StreamType(StreamType) +{ +} + +cConnectionIGMP::~cConnectionIGMP() +{ + delete m_LiveStreamer; +} + +bool cConnectionIGMP::Start(cChannel *Channel, in_addr_t Dst) +{ + if (Channel != NULL) { + cDevice *device = GetDevice(Channel, 0); + if (device != NULL) { + device->SwitchChannel(Channel, false); + struct in_addr ip; + ip.s_addr = Dst; + if (Connect(inet_ntoa(ip), m_ClientPort)) { + m_LiveStreamer = new cStreamdevLiveStreamer(0); + if (m_LiveStreamer->SetChannel(Channel, m_StreamType)) { + m_LiveStreamer->SetDevice(device); + if (!SetDSCP()) + LOG_ERROR_STR("unable to set DSCP sockopt"); + Dprintf("streamer start\n"); + m_LiveStreamer->Start(this); + return true; + } + else + esyslog("streamdev-server IGMP: SetDevice failed"); + DELETENULL(m_LiveStreamer); + } + else + esyslog("streamdev-server IGMP: Connect failed: %m"); + } + else + esyslog("streamdev-server IGMP: GetDevice failed"); + } + else + esyslog("streamdev-server IGMP: Channel not found"); + return false; +} + +void cConnectionIGMP::Stop() +{ + if (m_LiveStreamer) { + m_LiveStreamer->Stop(); + DELETENULL(m_LiveStreamer); + } +} diff --git a/plugins/streamdev/streamdev-cvs/server/connectionIGMP.h b/plugins/streamdev/streamdev-cvs/server/connectionIGMP.h new file mode 100644 index 0000000..90abd58 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/connectionIGMP.h @@ -0,0 +1,45 @@ +/* + * $Id: connectionIGMP.h,v 1.1 2009/02/13 10:39:22 schmirl Exp $ + */ + +#ifndef VDR_STREAMDEV_SERVERS_CONNECTIONIGMP_H +#define VDR_STREAMDEV_SERVERS_CONNECTIONIGMP_H + +#include "connection.h" +#include "server/livestreamer.h" + +#include <tools/select.h> + +#define MULTICAST_PRIV_MIN ((uint32_t) 0xefff0000) +#define MULTICAST_PRIV_MAX ((uint32_t) 0xeffffeff) + +class cStreamdevLiveStreamer; + +class cConnectionIGMP: public cServerConnection { +private: + cStreamdevLiveStreamer *m_LiveStreamer; + int m_ClientPort; + eStreamType m_StreamType; + +public: + cConnectionIGMP(const char* Name, int ClientPort, eStreamType StreamType); + virtual ~cConnectionIGMP(); + + bool Start(cChannel *Channel, in_addr_t Dst); + void Stop(); + + /* Not used here */ + virtual bool Command(char *Cmd) { return false; } + + virtual void Attach(void) { if (m_LiveStreamer != NULL) m_LiveStreamer->Attach(); } + virtual void Detach(void) { if (m_LiveStreamer != NULL) m_LiveStreamer->Detach(); } + + virtual bool Abort(void) const; +}; + +inline bool cConnectionIGMP::Abort(void) const +{ + return !m_LiveStreamer || m_LiveStreamer->Abort(); +} + +#endif // VDR_STREAMDEV_SERVERS_CONNECTIONIGMP_H diff --git a/plugins/streamdev/streamdev-cvs/server/connectionVTP.c b/plugins/streamdev/streamdev-cvs/server/connectionVTP.c new file mode 100644 index 0000000..7ff60e5 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/connectionVTP.c @@ -0,0 +1,1768 @@ +/* + * $Id: connectionVTP.c,v 1.27 2010/01/29 12:03:02 schmirl Exp $ + */ + +#include "server/connectionVTP.h" +#include "server/livestreamer.h" +#include "server/suspend.h" +#include "setup.h" + +#include <vdr/tools.h> +#include <vdr/videodir.h> +#include <vdr/menu.h> +#include <tools/select.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <stdarg.h> + +/* VTP Response codes: + 220: Service ready + 221: Service closing connection + 451: Requested action aborted: try again + 500: Syntax error or Command unrecognized + 501: Wrong parameters or missing parameters + 550: Requested action not taken + 551: Data connection not accepted + 560: Channel not available currently + 561: Capability not known + 562: Pid not available currently + 563: Recording not available (currently?) +*/ + +enum eDumpModeStreamdev { dmsdAll, dmsdPresent, dmsdFollowing, dmsdAtTime, dmsdFromToTime }; + +// --- cLSTEHandler ----------------------------------------------------------- + +class cLSTEHandler +{ +private: +#if defined(USE_PARENTALRATING) || defined(PARENTALRATINGCONTENTVERSNUM) + enum eStates { Channel, Event, Title, Subtitle, Description, Vps, Content, + EndEvent, EndChannel, EndEPG }; +#elif APIVERSNUM >= 10711 + enum eStates { Channel, Event, Title, Subtitle, Description, Vps, Content, Rating, + EndEvent, EndChannel, EndEPG }; +#else + enum eStates { Channel, Event, Title, Subtitle, Description, Vps, + EndEvent, EndChannel, EndEPG }; +#endif /* PARENTALRATING */ + cConnectionVTP *m_Client; + cSchedulesLock *m_SchedulesLock; + const cSchedules *m_Schedules; + const cSchedule *m_Schedule; + const cEvent *m_Event; + int m_Errno; + cString m_Error; + eStates m_State; + bool m_Traverse; + time_t m_ToTime; +public: + cLSTEHandler(cConnectionVTP *Client, const char *Option); + ~cLSTEHandler(); + bool Next(bool &Last); +}; + +cLSTEHandler::cLSTEHandler(cConnectionVTP *Client, const char *Option): + m_Client(Client), + m_SchedulesLock(new cSchedulesLock(false, 500)), + m_Schedules(cSchedules::Schedules(*m_SchedulesLock)), + m_Schedule(NULL), + m_Event(NULL), + m_Errno(0), + m_State(Channel), + m_Traverse(false), + m_ToTime(0) +{ + eDumpModeStreamdev dumpmode = dmsdAll; + time_t attime = 0; + time_t fromtime = 0; + + if (m_Schedules != NULL && *Option) { + char buf[strlen(Option) + 1]; + strcpy(buf, Option); + const char *delim = " \t"; + char *strtok_next; + char *p = strtok_r(buf, delim, &strtok_next); + while (p && dumpmode == dmsdAll) { + if (strcasecmp(p, "NOW") == 0) + dumpmode = dmsdPresent; + else if (strcasecmp(p, "NEXT") == 0) + dumpmode = dmsdFollowing; + else if (strcasecmp(p, "AT") == 0) { + dumpmode = dmsdAtTime; + if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) { + if (isnumber(p)) + attime = strtol(p, NULL, 10); + else { + m_Errno = 501; + m_Error = "Invalid time"; + break; + } + } else { + m_Errno = 501; + m_Error = "Missing time"; + break; + } + } + else if (strcasecmp(p, "FROM") == 0) { + dumpmode = dmsdFromToTime; + if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) { + if (isnumber(p)) + fromtime = strtol(p, NULL, 10); + else { + m_Errno = 501; + m_Error = "Invalid time"; + break; + } + if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) { + if (strcasecmp(p, "TO") == 0) { + if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) { + if (isnumber(p)) + m_ToTime = strtol(p, NULL, 10); + else { + m_Errno = 501; + m_Error = "Invalid time"; + break; + } + } else { + m_Errno = 501; + m_Error = "Missing time"; + break; + } + } + } + } else { + m_Errno = 501; + m_Error = "Missing time"; + break; + } + } else if (!m_Schedule) { + cChannel* Channel = NULL; + if (isnumber(p)) + Channel = Channels.GetByNumber(strtol(Option, NULL, 10)); + else + Channel = Channels.GetByChannelID(tChannelID::FromString( + Option)); + if (Channel) { + m_Schedule = m_Schedules->GetSchedule(Channel->GetChannelID()); + if (!m_Schedule) { + m_Errno = 550; + m_Error = "No schedule found"; + break; + } + } else { + m_Errno = 550; + m_Error = cString::sprintf("Channel \"%s\" not defined", p); + break; + } + } else { + m_Errno = 501; + m_Error = cString::sprintf("Unknown option: \"%s\"", p); + break; + } + p = strtok_r(NULL, delim, &strtok_next); + } + } else if (m_Schedules == NULL) { + m_Errno = 451; + m_Error = "EPG data is being modified, try again"; + } + + if (*m_Error == NULL) { + if (m_Schedule != NULL) + m_Schedules = NULL; + else if (m_Schedules != NULL) + m_Schedule = m_Schedules->First(); + + if (m_Schedule != NULL && m_Schedule->Events() != NULL) { + switch (dumpmode) { + case dmsdAll: m_Event = m_Schedule->Events()->First(); + m_Traverse = true; + break; + case dmsdPresent: m_Event = m_Schedule->GetPresentEvent(); + break; + case dmsdFollowing: m_Event = m_Schedule->GetFollowingEvent(); + break; + case dmsdAtTime: m_Event = m_Schedule->GetEventAround(attime); + break; + case dmsdFromToTime: + if (m_Schedule->Events()->Count() <= 1) { + m_Event = m_Schedule->Events()->First(); + break; + } + if (fromtime < m_Schedule->Events()->First()->StartTime()) { + fromtime = m_Schedule->Events()->First()->StartTime(); + } + if (m_ToTime > m_Schedule->Events()->Last()->EndTime()) { + m_ToTime = m_Schedule->Events()->Last()->EndTime(); + } + m_Event = m_Schedule->GetEventAround(fromtime); + m_Traverse = true; + break; + } + } + } +} + +cLSTEHandler::~cLSTEHandler() +{ + delete m_SchedulesLock; +} + +bool cLSTEHandler::Next(bool &Last) +{ + if (*m_Error != NULL) { + Last = true; + cString str(m_Error); + m_Error = NULL; + return m_Client->Respond(m_Errno, "%s", *str); + } + + Last = false; + switch (m_State) { + case Channel: + if (m_Schedule != NULL) { + cChannel *channel = Channels.GetByChannelID(m_Schedule->ChannelID(), + true); + if (channel != NULL) { + m_State = Event; + return m_Client->Respond(-215, "C %s %s", + *channel->GetChannelID().ToString(), + channel->Name()); + } else { + esyslog("ERROR: vdr streamdev: unable to find channel %s by ID", + *m_Schedule->ChannelID().ToString()); + m_State = EndChannel; + return Next(Last); + } + } else { + m_State = EndEPG; + return Next(Last); + } + break; + + case Event: + if (m_Event != NULL) { + m_State = Title; +#ifdef __FreeBSD__ + return m_Client->Respond(-215, "E %u %d %d %X", m_Event->EventID(), +#else + return m_Client->Respond(-215, "E %u %ld %d %X", m_Event->EventID(), +#endif + m_Event->StartTime(), m_Event->Duration(), + m_Event->TableID()); + } else { + m_State = EndChannel; + return Next(Last); + } + break; + + case Title: + m_State = Subtitle; + if (!isempty(m_Event->Title())) + return m_Client->Respond(-215, "T %s", m_Event->Title()); + else + return Next(Last); + break; + + case Subtitle: + m_State = Description; + if (!isempty(m_Event->ShortText())) + return m_Client->Respond(-215, "S %s", m_Event->ShortText()); + else + return Next(Last); + break; + + case Description: + m_State = Vps; + if (!isempty(m_Event->Description())) { + char *copy = strdup(m_Event->Description()); + cString cpy(copy, true); + strreplace(copy, '\n', '|'); + return m_Client->Respond(-215, "D %s", copy); + } else + return Next(Last); + break; + + case Vps: +#if defined(USE_PARENTALRATING) || defined(PARENTALRATINGCONTENTVERSNUM) || APIVERSNUM >= 10711 + m_State = Content; +#else + m_State = EndEvent; +#endif /* PARENTALRATING */ + if (m_Event->Vps()) +#ifdef __FreeBSD__ + return m_Client->Respond(-215, "V %d", m_Event->Vps()); +#else + return m_Client->Respond(-215, "V %ld", m_Event->Vps()); +#endif + else + return Next(Last); + break; + +#if defined(USE_PARENTALRATING) || defined(PARENTALRATINGCONTENTVERSNUM) + case Content: + m_State = EndEvent; + if (!isempty(m_Event->GetContentsString())) { + char *copy = strdup(m_Event->GetContentsString()); + cString cpy(copy, true); + strreplace(copy, '\n', '|'); + return m_Client->Respond(-215, "G %i %i %s", m_Event->Contents() & 0xF0, m_Event->Contents() & 0x0F, copy); + } else + return Next(Last); + break; +#elif APIVERSNUM >= 10711 + case Content: + m_State = Rating; + if (!isempty(m_Event->ContentToString(m_Event->Contents()))) { + char *copy = strdup(m_Event->ContentToString(m_Event->Contents())); + cString cpy(copy, true); + strreplace(copy, '\n', '|'); + return m_Client->Respond(-215, "G %i %i %s", m_Event->Contents() & 0xF0, m_Event->Contents() & 0x0F, copy); + } else + return Next(Last); + break; + + case Rating: + m_State = EndEvent; + if (m_Event->ParentalRating()) + return m_Client->Respond(-215, "R %d", m_Event->ParentalRating()); + else + return Next(Last); + break; +#endif + + case EndEvent: + if (m_Traverse) { + m_Event = m_Schedule->Events()->Next(m_Event); + if ((m_Event != NULL) && (m_ToTime != 0) && (m_Event->StartTime() > m_ToTime)) { + m_Event = NULL; + } + } + else + m_Event = NULL; + + if (m_Event != NULL) + m_State = Event; + else + m_State = EndChannel; + + return m_Client->Respond(-215, "e"); + + case EndChannel: + if (m_Schedules != NULL) { + m_Schedule = m_Schedules->Next(m_Schedule); + if (m_Schedule != NULL) { + if (m_Schedule->Events() != NULL) + m_Event = m_Schedule->Events()->First(); + m_State = Channel; + } + } + + if (m_Schedules == NULL || m_Schedule == NULL) + m_State = EndEPG; + + return m_Client->Respond(-215, "c"); + + case EndEPG: + Last = true; + return m_Client->Respond(215, "End of EPG data"); + } + return false; +} + +// --- cLSTCHandler ----------------------------------------------------------- + +class cLSTCHandler +{ +private: + cConnectionVTP *m_Client; + const cChannel *m_Channel; + char *m_Option; + int m_Errno; + cString m_Error; + bool m_Traverse; +public: + cLSTCHandler(cConnectionVTP *Client, const char *Option); + ~cLSTCHandler(); + bool Next(bool &Last); +}; + +cLSTCHandler::cLSTCHandler(cConnectionVTP *Client, const char *Option): + m_Client(Client), + m_Channel(NULL), + m_Option(NULL), + m_Errno(0), + m_Traverse(false) +{ + if (!Channels.Lock(false, 500)) { + m_Errno = 451; + m_Error = "Channels are being modified - try again"; + } else if (*Option) { + if (isnumber(Option)) { + m_Channel = Channels.GetByNumber(strtol(Option, NULL, 10)); + if (m_Channel == NULL) { + m_Errno = 501; + m_Error = cString::sprintf("Channel \"%s\" not defined", Option); + return; + } + } else { + int i = 1; + m_Traverse = true; + m_Option = strdup(Option); + while (i <= Channels.MaxNumber()) { + m_Channel = Channels.GetByNumber(i, 1); + if (strcasestr(m_Channel->Name(), Option) != NULL) + break; + i = m_Channel->Number() + 1; + } + + if (i > Channels.MaxNumber()) { + m_Errno = 501; + m_Error = cString::sprintf("Channel \"%s\" not defined", Option); + return; + } + } + } else if (Channels.MaxNumber() >= 1) { + m_Channel = Channels.GetByNumber(1, 1); + m_Traverse = true; + } else { + m_Errno = 550; + m_Error = "No channels defined"; + } +} + +cLSTCHandler::~cLSTCHandler() +{ + Channels.Unlock(); + if (m_Option != NULL) + free(m_Option); +} + +bool cLSTCHandler::Next(bool &Last) +{ + if (*m_Error != NULL) { + Last = true; + cString str(m_Error); + m_Error = NULL; + return m_Client->Respond(m_Errno, "%s", *str); + } + + int number; + char *buffer; + + number = m_Channel->Number(); + buffer = strdup(*m_Channel->ToText()); + buffer[strlen(buffer) - 1] = '\0'; // remove \n + cString str(buffer, true); + + Last = true; + if (m_Traverse) { + int i = m_Channel->Number() + 1; + while (i <= Channels.MaxNumber()) { + m_Channel = Channels.GetByNumber(i, 1); + if (m_Channel != NULL) { + if (m_Option == NULL || strcasestr(m_Channel->Name(), + m_Option) != NULL) + break; + i = m_Channel->Number() + 1; + } else { + m_Errno = 501; + m_Error = cString::sprintf("Channel \"%d\" not found", i); + } + } + + if (i < Channels.MaxNumber() + 1) + Last = false; + } + + return m_Client->Respond(Last ? 250 : -250, "%d %s", number, buffer); +} + +// --- cLSTTHandler ----------------------------------------------------------- + +class cLSTTHandler +{ +private: + cConnectionVTP *m_Client; + cTimer *m_Timer; + int m_Index; + int m_Errno; + cString m_Error; + bool m_Traverse; +public: + cLSTTHandler(cConnectionVTP *Client, const char *Option); + ~cLSTTHandler(); + bool Next(bool &Last); +}; + +cLSTTHandler::cLSTTHandler(cConnectionVTP *Client, const char *Option): + m_Client(Client), + m_Timer(NULL), + m_Index(0), + m_Errno(0), + m_Traverse(false) +{ + if (*Option) { + if (isnumber(Option)) { + m_Timer = Timers.Get(strtol(Option, NULL, 10) - 1); + if (m_Timer == NULL) { + m_Errno = 501; + m_Error = cString::sprintf("Timer \"%s\" not defined", Option); + } + } else { + m_Errno = 501; + m_Error = cString::sprintf("Error in timer number \"%s\"", Option); + } + } else if (Timers.Count()) { + m_Traverse = true; + m_Index = 0; + m_Timer = Timers.Get(m_Index); + if (m_Timer == NULL) { + m_Errno = 501; + m_Error = cString::sprintf("Timer \"%d\" not found", m_Index + 1); + } + } else { + m_Errno = 550; + m_Error = "No timers defined"; + } +} + +cLSTTHandler::~cLSTTHandler() +{ +} + +bool cLSTTHandler::Next(bool &Last) +{ + if (*m_Error != NULL) { + Last = true; + cString str(m_Error); + m_Error = NULL; + return m_Client->Respond(m_Errno, "%s", *str); + } + + bool result; + char *buffer; + Last = !m_Traverse || m_Index >= Timers.Count() - 1; + buffer = strdup(*m_Timer->ToText()); + buffer[strlen(buffer) - 1] = '\0'; // strip \n + result = m_Client->Respond(Last ? 250 : -250, "%d %s", m_Timer->Index() + 1, + buffer); + free(buffer); + + if (m_Traverse && !Last) { + m_Timer = Timers.Get(++m_Index); + if (m_Timer == NULL) { + m_Errno = 501; + m_Error = cString::sprintf("Timer \"%d\" not found", m_Index + 1); + } + } + return result; +} + +// --- cLSTRHandler ----------------------------------------------------------- + +class cLSTRHandler +{ +private: + enum eStates { Recording, Event, Title, Subtitle, Description, Components, Vps, + EndRecording }; + cConnectionVTP *m_Client; + cRecording *m_Recording; + const cEvent *m_Event; + int m_Index; + int m_Errno; + cString m_Error; + bool m_Traverse; + bool m_Info; + eStates m_State; + int m_CurrentComponent; +public: + cLSTRHandler(cConnectionVTP *Client, const char *Option); + ~cLSTRHandler(); + bool Next(bool &Last); +}; + +cLSTRHandler::cLSTRHandler(cConnectionVTP *Client, const char *Option): + m_Client(Client), + m_Recording(NULL), + m_Event(NULL), + m_Index(0), + m_Errno(0), + m_Traverse(false), + m_Info(false), + m_State(Recording), + m_CurrentComponent(0) +{ + if (*Option) { + if (isnumber(Option)) { + m_Recording = Recordings.Get(strtol(Option, NULL, 10) - 1); +#if defined(USE_STREAMDEVEXT) || APIVERSNUM >= 10705 + m_Event = m_Recording->Info()->GetEvent(); +#endif + m_Info = true; + if (m_Recording == NULL) { + m_Errno = 501; + m_Error = cString::sprintf("Recording \"%s\" not found", Option); + } + } + else { + m_Errno = 501; + m_Error = cString::sprintf("Error in Recording number \"%s\"", Option); + } + } + else if (Recordings.Count()) { + m_Traverse = true; + m_Index = 0; + m_Recording = Recordings.Get(m_Index); + if (m_Recording == NULL) { + m_Errno = 501; + m_Error = cString::sprintf("Recording \"%d\" not found", m_Index + 1); + } + } + else { + m_Errno = 550; + m_Error = "No recordings available"; + } +} + +cLSTRHandler::~cLSTRHandler() +{ +} + +bool cLSTRHandler::Next(bool &Last) +{ + if (*m_Error != NULL) { + Last = true; + cString str(m_Error); + m_Error = NULL; + return m_Client->Respond(m_Errno, "%s", *str); + } + + if (m_Info) { + Last = false; + switch (m_State) { + case Recording: + if (m_Recording != NULL) { + m_State = Event; + return m_Client->Respond(-215, "C %s%s%s", + *m_Recording->Info()->ChannelID().ToString(), + m_Recording->Info()->ChannelName() ? " " : "", + m_Recording->Info()->ChannelName() ? m_Recording->Info()->ChannelName() : ""); + } + else { + m_State = EndRecording; + return Next(Last); + } + break; + + case Event: + m_State = Title; + if (m_Event != NULL) { + return m_Client->Respond(-215, "E %u %ld %d %X %X", (unsigned int) m_Event->EventID(), + m_Event->StartTime(), m_Event->Duration(), + m_Event->TableID(), m_Event->Version()); + } + return Next(Last); + + case Title: + m_State = Subtitle; + return m_Client->Respond(-215, "T %s", m_Recording->Info()->Title()); + + case Subtitle: + m_State = Description; + if (!isempty(m_Recording->Info()->ShortText())) { + return m_Client->Respond(-215, "S %s", m_Recording->Info()->ShortText()); + } + return Next(Last); + + case Description: + m_State = Components; + if (!isempty(m_Recording->Info()->Description())) { + m_State = Components; + char *copy = strdup(m_Recording->Info()->Description()); + cString cpy(copy, true); + strreplace(copy, '\n', '|'); + return m_Client->Respond(-215, "D %s", copy); + } + return Next(Last); + + case Components: + if (m_Recording->Info()->Components()) { + if (m_CurrentComponent < m_Recording->Info()->Components()->NumComponents()) { + tComponent *p = m_Recording->Info()->Components()->Component(m_CurrentComponent); + m_CurrentComponent++; + if (!Setup.UseDolbyDigital && p->stream == 0x02 && p->type == 0x05) + return Next(Last); + + return m_Client->Respond(-215, "X %s", *p->ToString()); + } + } + m_State = Vps; + return Next(Last); + + case Vps: + m_State = EndRecording; + if (m_Event != NULL) { + if (m_Event->Vps()) { + return m_Client->Respond(-215, "V %ld", m_Event->Vps()); + } + } + return Next(Last); + + case EndRecording: + Last = true; + return m_Client->Respond(215, "End of recording information"); + } + } + else { + bool result; + Last = !m_Traverse || m_Index >= Recordings.Count() - 1; + result = m_Client->Respond(Last ? 250 : -250, "%d %s", m_Recording->Index() + 1, m_Recording->Title(' ', true)); + + if (m_Traverse && !Last) { + m_Recording = Recordings.Get(++m_Index); + if (m_Recording == NULL) { + m_Errno = 501; + m_Error = cString::sprintf("Recording \"%d\" not found", m_Index + 1); + } + } + return result; + } + return false; +} + +// --- cConnectionVTP --------------------------------------------------------- + +cConnectionVTP::cConnectionVTP(void): + cServerConnection("VTP"), + m_LiveSocket(NULL), + m_LiveStreamer(NULL), + m_FilterSocket(NULL), + m_FilterStreamer(NULL), + m_RecSocket(NULL), + m_DataSocket(NULL), + m_LastCommand(NULL), + m_StreamType(stTSPIDS), + m_FiltersSupport(false), + m_RecPlayer(NULL), + m_LSTEHandler(NULL), + m_LSTCHandler(NULL), + m_LSTTHandler(NULL), + m_LSTRHandler(NULL) +{ +} + +cConnectionVTP::~cConnectionVTP() +{ + if (m_LastCommand != NULL) + free(m_LastCommand); + delete m_LiveStreamer; + delete m_LiveSocket; + delete m_RecSocket; + delete m_FilterStreamer; + delete m_FilterSocket; + delete m_DataSocket; + delete m_LSTTHandler; + delete m_LSTCHandler; + delete m_LSTEHandler; + delete m_LSTRHandler; + delete m_RecPlayer; +} + +inline bool cConnectionVTP::Abort(void) const +{ + return m_LiveStreamer && m_LiveStreamer->Abort(); +} + +void cConnectionVTP::Welcome(void) +{ + Respond(220, "Welcome to Video Disk Recorder (VTP)"); +} + +void cConnectionVTP::Reject(void) +{ + Respond(221, "Too many clients or client not allowed to connect"); + cServerConnection::Reject(); +} + +void cConnectionVTP::Detach(void) +{ + if (m_LiveStreamer) m_LiveStreamer->Detach(); + if (m_FilterStreamer) m_FilterStreamer->Detach(); +} + +void cConnectionVTP::Attach(void) +{ + if (m_LiveStreamer) m_LiveStreamer->Attach(); + if (m_FilterStreamer) m_FilterStreamer->Attach(); +} + +bool cConnectionVTP::Command(char *Cmd) +{ + char *param = NULL; + + if (Cmd != NULL) { + if (m_LastCommand != NULL) { + esyslog("ERROR: streamdev: protocol violation (VTP) from %s:%d", + RemoteIp().c_str(), RemotePort()); + return false; + } + + if ((param = strchr(Cmd, ' ')) != NULL) + *(param++) = '\0'; + else + param = Cmd + strlen(Cmd); + m_LastCommand = strdup(Cmd); + } else { + Cmd = m_LastCommand; + param = NULL; + } + + if (strcasecmp(Cmd, "LSTE") == 0) return CmdLSTE(param); + else if (strcasecmp(Cmd, "LSTR") == 0) return CmdLSTR(param); + else if (strcasecmp(Cmd, "LSTT") == 0) return CmdLSTT(param); + else if (strcasecmp(Cmd, "LSTC") == 0) return CmdLSTC(param); + + if (param == NULL) { + esyslog("ERROR: streamdev: this seriously shouldn't happen at %s:%d", + __FILE__, __LINE__); + return false; + } + + if (strcasecmp(Cmd, "CAPS") == 0) return CmdCAPS(param); + else if (strcasecmp(Cmd, "PROV") == 0) return CmdPROV(param); + else if (strcasecmp(Cmd, "PORT") == 0) return CmdPORT(param); + 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, "ADDP") == 0) return CmdADDP(param); + else if (strcasecmp(Cmd, "DELP") == 0) return CmdDELP(param); + else if (strcasecmp(Cmd, "ADDF") == 0) return CmdADDF(param); + else if (strcasecmp(Cmd, "DELF") == 0) return CmdDELF(param); + else if (strcasecmp(Cmd, "ABRT") == 0) return CmdABRT(param); + else if (strcasecmp(Cmd, "QUIT") == 0) return CmdQUIT(); + else if (strcasecmp(Cmd, "SUSP") == 0) return CmdSUSP(); + // Commands adopted from SVDRP + else if (strcasecmp(Cmd, "STAT") == 0) return CmdSTAT(param); + else if (strcasecmp(Cmd, "MODT") == 0) return CmdMODT(param); + else if (strcasecmp(Cmd, "NEWT") == 0) return CmdNEWT(param); + else if (strcasecmp(Cmd, "DELT") == 0) return CmdDELT(param); + else if (strcasecmp(Cmd, "NEXT") == 0) return CmdNEXT(param); + else if (strcasecmp(Cmd, "NEWC") == 0) return CmdNEWC(param); + else if (strcasecmp(Cmd, "MODC") == 0) return CmdMODC(param); + else if (strcasecmp(Cmd, "MOVC") == 0) return CmdMOVC(param); + else if (strcasecmp(Cmd, "DELC") == 0) return CmdDELC(param); + else if (strcasecmp(Cmd, "DELR") == 0) return CmdDELR(param); + else if (strcasecmp(Cmd, "RENR") == 0) return CmdRENR(param); + else + return Respond(500, "Unknown Command \"%s\"", Cmd); +} + +bool cConnectionVTP::CmdCAPS(char *Opts) +{ + if (strcasecmp(Opts, "TS") == 0) { + m_StreamType = stTS; + return Respond(220, "Capability \"%s\" accepted", Opts); + } + + if (strcasecmp(Opts, "TSPIDS") == 0) { + m_StreamType = stTSPIDS; + return Respond(220, "Capability \"%s\" accepted", Opts); + } + + if (strcasecmp(Opts, "PS") == 0) { + m_StreamType = stPS; + return Respond(220, "Capability \"%s\" accepted", Opts); + } + + if (strcasecmp(Opts, "PES") == 0) { + m_StreamType = stPES; + return Respond(220, "Capability \"%s\" accepted", Opts); + } + + if (strcasecmp(Opts, "EXTERN") == 0) { + m_StreamType = stExtern; + return Respond(220, "Capability \"%s\" accepted", Opts); + } + + // + // Deliver section filters data in separate, channel-independent data stream + // + if (strcasecmp(Opts, "FILTERS") == 0) { + m_FiltersSupport = true; + return Respond(220, "Capability \"%s\" accepted", Opts); + } + + return Respond(561, "Capability \"%s\" not known", Opts); +} + +bool cConnectionVTP::CmdPROV(char *Opts) +{ + const cChannel *chan; + int prio; + char *ep; + + prio = strtol(Opts, &ep, 10); + if (ep == Opts || !isspace(*ep)) + return Respond(501, "Use: PROV Priority Channel"); + + Opts = skipspace(ep); + 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"); +} + +bool cConnectionVTP::CmdPORT(char *Opts) +{ + uint id, dataport = 0; + char dataip[20]; + char *ep, *ipoffs; + int n; + + id = strtoul(Opts, &ep, 10); + if (ep == Opts || !isspace(*ep)) + return Respond(500, "Use: PORT Id Destination"); + + if (id >= si_Count) + return Respond(501, "Wrong connection id %d", id); + + Opts = skipspace(ep); + n = 0; + ipoffs = dataip; + while ((ep = strchr(Opts, ',')) != NULL) { + if (n < 4) { + memcpy(ipoffs, Opts, ep - Opts); + ipoffs += ep - Opts; + if (n < 3) *(ipoffs++) = '.'; + } else if (n == 4) { + *ep = 0; + dataport = strtoul(Opts, NULL, 10) << 8; + } else + break; + Opts = ep + 1; + ++n; + } + *ipoffs = '\0'; + + if (n != 5) + return Respond(501, "Argument count invalid (must be 6 values)"); + + dataport |= strtoul(Opts, NULL, 10); + + isyslog("Streamdev: Setting data connection to %s:%d", dataip, dataport); + + switch (id) { + case siLiveFilter: + m_FiltersSupport = true; + if(m_FilterStreamer) + m_FilterStreamer->Stop(); + delete m_FilterSocket; + + m_FilterSocket = new cTBSocket(SOCK_STREAM); + if (!m_FilterSocket->Connect(dataip, dataport)) { + esyslog("ERROR: Streamdev: Couldn't open data connection to %s:%d: %s", + dataip, dataport, strerror(errno)); + DELETENULL(m_FilterSocket); + return Respond(551, "Couldn't open data connection"); + } + + if(!m_FilterStreamer) + m_FilterStreamer = new cStreamdevFilterStreamer; + m_FilterStreamer->Start(m_FilterSocket); + m_FilterStreamer->Activate(true); + + return Respond(220, "Port command ok, data connection opened"); + break; + + case siLive: + if(m_LiveSocket && m_LiveStreamer) + m_LiveStreamer->Stop(); + delete m_LiveSocket; + + m_LiveSocket = new cTBSocket(SOCK_STREAM); + if (!m_LiveSocket->Connect(dataip, dataport)) { + esyslog("ERROR: Streamdev: Couldn't open data connection to %s:%d: %s", + dataip, dataport, strerror(errno)); + DELETENULL(m_LiveSocket); + return Respond(551, "Couldn't open data connection"); + } + + if (!m_LiveSocket->SetDSCP()) + LOG_ERROR_STR("unable to set DSCP sockopt"); + if (m_LiveStreamer) + m_LiveStreamer->Start(m_LiveSocket); + + return Respond(220, "Port command ok, data connection opened"); + break; + + case siReplay: + delete m_RecSocket; + + m_RecSocket = new cTBSocket(SOCK_STREAM); + if (!m_RecSocket->Connect(dataip, dataport)) { + esyslog("ERROR: Streamdev: Couldn't open data connection to %s:%d: %s", + dataip, dataport, strerror(errno)); + DELETENULL(m_RecSocket); + return Respond(551, "Couldn't open data connection"); + } + + if (!m_RecSocket->SetDSCP()) + LOG_ERROR_STR("unable to set DSCP sockopt"); + + return Respond(220, "Port command ok, data connection opened"); + break; + + case siDataRespond: + delete m_DataSocket; + + m_DataSocket = new cTBSocket(SOCK_STREAM); + if (!m_DataSocket->Connect(dataip, dataport)) { + esyslog("ERROR: Streamdev: Couldn't open data connection to %s:%d: %s", + dataip, dataport, strerror(errno)); + DELETENULL(m_DataSocket); + return Respond(551, "Couldn't open data connection"); + } + return Respond(220, "Port command ok, data connection opened"); + break; + + default: + return Respond(501, "No handler for id %u", id); + } +} + +bool cConnectionVTP::CmdREAD(char *Opts) +{ + if (*Opts) { + char *tail; + uint64_t position = strtoll(Opts, &tail, 10); + if (tail && tail != Opts) { + tail = skipspace(tail); + if (tail && tail != Opts) { + int size = strtol(tail, NULL, 10); + uint8_t* data = (uint8_t*)malloc(size+4); + unsigned long count_readed = m_RecPlayer->getBlock(data, position, size); + unsigned long count_written = m_RecSocket->SysWrite(data, count_readed); + + free(data); + return Respond(220, "%lu Bytes submitted", count_written); + } + else { + return Respond(501, "Missing position"); + } + } + else { + return Respond(501, "Missing size"); + } + } + else { + return Respond(501, "Missing position"); + } +} + +bool cConnectionVTP::CmdTUNE(char *Opts) +{ + const cChannel *chan; + cDevice *dev; + + if ((chan = ChannelFromString(Opts)) == NULL) + return Respond(550, "Undefined channel \"%s\"", Opts); + + if ((dev = GetDevice(chan, 0)) == 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); + m_LiveStreamer->SetChannel(chan, m_StreamType); + m_LiveStreamer->SetDevice(dev); + if(m_LiveSocket) + m_LiveStreamer->Start(m_LiveSocket); + + if(m_FiltersSupport) { + if(!m_FilterStreamer) + m_FilterStreamer = new cStreamdevFilterStreamer; + m_FilterStreamer->SetDevice(dev); + //m_FilterStreamer->SetChannel(chan); + } + + return Respond(220, "Channel tuned"); +} + +bool cConnectionVTP::CmdPLAY(char *Opts) +{ + if (*Opts) { + if (isnumber(Opts)) { + cRecording *recording = Recordings.Get(strtol(Opts, NULL, 10) - 1); + if (recording) { + if (m_RecPlayer) { + delete m_RecPlayer; + } + m_RecPlayer = new RecPlayer(recording); + return Respond(220, "%llu (Bytes), %u (Frames)", (long long unsigned int) m_RecPlayer->getLengthBytes(), (unsigned int) m_RecPlayer->getLengthFrames()); + } + else { + return Respond(550, "Recording \"%s\" not found", Opts); + } + } + else { + return Respond(500, "Use: PLAY record"); + } + } + else { + return Respond(500, "Use: PLAY record"); + } +} + +bool cConnectionVTP::CmdADDP(char *Opts) +{ + int pid; + char *end; + + pid = strtoul(Opts, &end, 10); + if (end == Opts || (*end != '\0' && *end != ' ')) + return Respond(500, "Use: ADDP Pid"); + + return m_LiveStreamer && m_LiveStreamer->SetPid(pid, true) + ? Respond(220, "Pid %d available", pid) + : Respond(560, "Pid %d not available", pid); +} + +bool cConnectionVTP::CmdDELP(char *Opts) +{ + int pid; + char *end; + + pid = strtoul(Opts, &end, 10); + if (end == Opts || (*end != '\0' && *end != ' ')) + return Respond(500, "Use: DELP Pid"); + + return m_LiveStreamer && m_LiveStreamer->SetPid(pid, false) + ? Respond(220, "Pid %d stopped", pid) + : Respond(560, "Pid %d not transferring", pid); +} + +bool cConnectionVTP::CmdADDF(char *Opts) +{ + int pid, tid, mask; + char *ep; + + if (m_FilterStreamer == NULL) + return Respond(560, "Can't set filters without a filter stream"); + + pid = strtol(Opts, &ep, 10); + if (ep == Opts || (*ep != ' ')) + return Respond(500, "Use: ADDF Pid Tid Mask"); + Opts = skipspace(ep); + tid = strtol(Opts, &ep, 10); + if (ep == Opts || (*ep != ' ')) + return Respond(500, "Use: ADDF Pid Tid Mask"); + Opts = skipspace(ep); + mask = strtol(Opts, &ep, 10); + if (ep == Opts || (*ep != '\0' && *ep != ' ')) + return Respond(500, "Use: ADDF Pid Tid Mask"); + + return m_FilterStreamer->SetFilter(pid, tid, mask, true) + ? Respond(220, "Filter %d transferring", pid) + : Respond(560, "Filter %d not available", pid); +} + +bool cConnectionVTP::CmdDELF(char *Opts) +{ + int pid, tid, mask; + char *ep; + + if (m_FilterStreamer == NULL) + return Respond(560, "Can't delete filters without a stream"); + + pid = strtol(Opts, &ep, 10); + if (ep == Opts || (*ep != ' ')) + return Respond(500, "Use: DELF Pid Tid Mask"); + Opts = skipspace(ep); + tid = strtol(Opts, &ep, 10); + if (ep == Opts || (*ep != ' ')) + return Respond(500, "Use: DELF Pid Tid Mask"); + Opts = skipspace(ep); + mask = strtol(Opts, &ep, 10); + if (ep == Opts || (*ep != '\0' && *ep != ' ')) + return Respond(500, "Use: DELF Pid Tid Mask"); + + m_FilterStreamer->SetFilter(pid, tid, mask, false); + return Respond(220, "Filter %d stopped", pid); +} + +bool cConnectionVTP::CmdABRT(char *Opts) +{ + uint id; + char *ep; + + id = strtoul(Opts, &ep, 10); + if (ep == Opts || (*ep != '\0' && *ep != ' ')) + return Respond(500, "Use: ABRT Id"); + + switch (id) { + case siLive: + DELETENULL(m_LiveStreamer); + DELETENULL(m_LiveSocket); + break; + case siLiveFilter: + DELETENULL(m_FilterStreamer); + DELETENULL(m_FilterSocket); + break; + case siReplay: + DELETENULL(m_RecPlayer); + DELETENULL(m_RecSocket); + break; + case siDataRespond: + DELETENULL(m_DataSocket); + break; + default: + return Respond(501, "Wrong connection id %d", id); + break; + + } + + return Respond(220, "Data connection closed"); +} + +bool cConnectionVTP::CmdQUIT(void) +{ + DeferClose(); + return Respond(221, "Video Disk Recorder closing connection"); +} + +bool cConnectionVTP::CmdSUSP(void) +{ + if (StreamdevServerSetup.SuspendMode == smAlways || cSuspendCtl::IsActive()) + return Respond(220, "Server is suspended"); + else if (StreamdevServerSetup.SuspendMode == smOffer + && StreamdevServerSetup.AllowSuspend) { + cControl::Launch(new cSuspendCtl); + return Respond(220, "Server is suspended"); + } else + return Respond(550, "Client may not suspend server"); +} + +// Functions extended from SVDRP + +template<class cHandler> +bool cConnectionVTP::CmdLSTX(cHandler *&Handler, char *Option) +{ + if (Option != NULL) { + delete Handler; + Handler = new cHandler(this, Option); + } + + bool last = false; + bool result = false; + if (Handler != NULL) + result = Handler->Next(last); + else + esyslog("ERROR: vdr streamdev: Handler in LSTX command is NULL"); + if (!result || last) + DELETENULL(Handler); + + return result; +} + +bool cConnectionVTP::CmdLSTE(char *Option) +{ + return CmdLSTX(m_LSTEHandler, Option); +} + +bool cConnectionVTP::CmdLSTC(char *Option) +{ + return CmdLSTX(m_LSTCHandler, Option); +} + +bool cConnectionVTP::CmdLSTT(char *Option) +{ + return CmdLSTX(m_LSTTHandler, Option); +} + +bool cConnectionVTP::CmdLSTR(char *Option) +{ + return CmdLSTX(m_LSTRHandler, Option); +} + +// Functions adopted from SVDRP +#define INIT_WRAPPER() bool _res +#define Reply(c,m...) _res = Respond(c,m) +#define EXIT_WRAPPER() return _res + +bool cConnectionVTP::CmdSTAT(const char *Option) +{ + INIT_WRAPPER(); + if (*Option) { + if (strcasecmp(Option, "DISK") == 0) { + int FreeMB, UsedMB; + int Percent = VideoDiskSpace(&FreeMB, &UsedMB); + Reply(250, "%dMB %dMB %d%%", FreeMB + UsedMB, FreeMB, Percent); + } + else if (strcasecmp(Option, "NAME") == 0) { + Reply(250, "vdr - The Video Disk Recorder with Streamdev-Server"); + } + else if (strcasecmp(Option, "VERSION") == 0) { + Reply(250, "VDR: %s | Streamdev: %s", VDRVERSION, VERSION); + } + else if (strcasecmp(Option, "RECORDS") == 0) { + bool recordings = Recordings.Load(); + Recordings.Sort(); + if (recordings) { + cRecording *recording = Recordings.Last(); + Reply(250, "%d", recording->Index() + 1); + } + else { + Reply(250, "0"); + } + } + else if (strcasecmp(Option, "CHANNELS") == 0) { + Reply(250, "%d", Channels.MaxNumber()); + } + else if (strcasecmp(Option, "TIMERS") == 0) { + Reply(250, "%d", Timers.Count()); + } + else if (strcasecmp(Option, "CHARSET") == 0) { + Reply(250, "%s", cCharSetConv::SystemCharacterTable()); + } + else if (strcasecmp(Option, "TIME") == 0) { + time_t timeNow = time(NULL); + struct tm* timeStruct = localtime(&timeNow); + int timeOffset = timeStruct->tm_gmtoff; + + Reply(250, "%lu %i", (unsigned long) timeNow, timeOffset); + } + else + Reply(501, "Invalid Option \"%s\"", Option); + } + else + Reply(501, "No option given"); + EXIT_WRAPPER(); +} + +bool cConnectionVTP::CmdMODT(const char *Option) +{ + INIT_WRAPPER(); + if (*Option) { + char *tail; + int n = strtol(Option, &tail, 10); + if (tail && tail != Option) { + tail = skipspace(tail); + cTimer *timer = Timers.Get(n - 1); + if (timer) { + cTimer t = *timer; + if (strcasecmp(tail, "ON") == 0) + t.SetFlags(tfActive); + else if (strcasecmp(tail, "OFF") == 0) + t.ClrFlags(tfActive); + else if (!t.Parse(tail)) { + Reply(501, "Error in timer settings"); + EXIT_WRAPPER(); + } + *timer = t; + Timers.SetModified(); + isyslog("timer %s modified (%s)", *timer->ToDescr(), + timer->HasFlags(tfActive) ? "active" : "inactive"); + Reply(250, "%d %s", timer->Index() + 1, *timer->ToText()); + } else + Reply(501, "Timer \"%d\" not defined", n); + } else + Reply(501, "Error in timer number"); + } else + Reply(501, "Missing timer settings"); + EXIT_WRAPPER(); +} + +bool cConnectionVTP::CmdNEWT(const char *Option) +{ + INIT_WRAPPER(); + if (*Option) { + cTimer *timer = new cTimer; + if (timer->Parse(Option)) { + cTimer *t = Timers.GetTimer(timer); + if (!t) { + Timers.Add(timer); + Timers.SetModified(); + isyslog("timer %s added", *timer->ToDescr()); + Reply(250, "%d %s", timer->Index() + 1, *timer->ToText()); + EXIT_WRAPPER(); + } else + Reply(550, "Timer already defined: %d %s", t->Index() + 1, + *t->ToText()); + } else + Reply(501, "Error in timer settings"); + delete timer; + } else + Reply(501, "Missing timer settings"); + EXIT_WRAPPER(); +} + +bool cConnectionVTP::CmdDELT(const char *Option) +{ + INIT_WRAPPER(); + if (*Option) { + int number = 0; + bool force = false; + char buf[strlen(Option) + 1]; + strcpy(buf, Option); + const char *delim = " \t"; + char *strtok_next; + char *p = strtok_r(buf, delim, &strtok_next); + + if (isnumber(p)) { + number = strtol(p, NULL, 10) - 1; + } + else if (strcasecmp(p, "FORCE") == 0) { + force = true; + } + if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) { + if (isnumber(p)) { + number = strtol(p, NULL, 10) - 1; + } + else if (strcasecmp(p, "FORCE") == 0) { + force = true; + } + else { + Reply(501, "Timer not found or wrong syntax"); + } + } + + cTimer *timer = Timers.Get(number); + if (timer) { + if (timer->Recording()) { + if (force) { + timer->Skip(); + cRecordControls::Process(time(NULL)); + } + else { + Reply(550, "Timer \"%i\" is recording", number); + EXIT_WRAPPER(); + } + } + isyslog("deleting timer %s", *timer->ToDescr()); + Timers.Del(timer); + Timers.SetModified(); + Reply(250, "Timer \"%i\" deleted", number); + } else + Reply(501, "Timer \"%i\" not defined", number); + } else + Reply(501, "Missing timer option"); + EXIT_WRAPPER(); +} + +bool cConnectionVTP::CmdNEXT(const char *Option) +{ + INIT_WRAPPER(); + cTimer *t = Timers.GetNextActiveTimer(); + if (t) { + time_t Start = t->StartTime(); + int Number = t->Index() + 1; + if (!*Option) + Reply(250, "%d %s", Number, *TimeToString(Start)); + else if (strcasecmp(Option, "ABS") == 0) + Reply(250, "%d %ld", Number, Start); + else if (strcasecmp(Option, "REL") == 0) + Reply(250, "%d %ld", Number, Start - time(NULL)); + else + Reply(501, "Unknown option: \"%s\"", Option); + } + else + Reply(550, "No active timers"); + EXIT_WRAPPER(); +} + +bool cConnectionVTP::CmdNEWC(const char *Option) +{ + INIT_WRAPPER(); + if (*Option) { + cChannel ch; + if (ch.Parse(Option)) { + if (Channels.HasUniqueChannelID(&ch)) { + cChannel *channel = new cChannel; + *channel = ch; + Channels.Add(channel); + Channels.ReNumber(); + Channels.SetModified(true); + isyslog("new channel %d %s", channel->Number(), *channel->ToText()); + Reply(250, "%d %s", channel->Number(), *channel->ToText()); + } + else { + Reply(501, "Channel settings are not unique"); + } + } + else { + Reply(501, "Error in channel settings"); + } + } + else { + Reply(501, "Missing channel settings"); + } + EXIT_WRAPPER(); +} + +bool cConnectionVTP::CmdMODC(const char *Option) +{ + INIT_WRAPPER(); + if (*Option) { + char *tail; + int n = strtol(Option, &tail, 10); + if (tail && tail != Option) { + tail = skipspace(tail); + if (!Channels.BeingEdited()) { + cChannel *channel = Channels.GetByNumber(n); + if (channel) { + cChannel ch; + if (ch.Parse(tail)) { + if (Channels.HasUniqueChannelID(&ch, channel)) { + *channel = ch; + Channels.ReNumber(); + Channels.SetModified(true); + isyslog("modifed channel %d %s", channel->Number(), *channel->ToText()); + Reply(250, "%d %s", channel->Number(), *channel->ToText()); + } + else { + Reply(501, "Channel settings are not unique"); + } + } + else { + Reply(501, "Error in channel settings"); + } + } + else { + Reply(501, "Channel \"%d\" not defined", n); + } + } + else { + Reply(550, "Channels are being edited - try again later"); + } + } + else { + Reply(501, "Error in channel number"); + } + } + else { + Reply(501, "Missing channel settings"); + } + EXIT_WRAPPER(); +} + +bool cConnectionVTP::CmdMOVC(const char *Option) +{ + INIT_WRAPPER(); + if (*Option) { + if (!Channels.BeingEdited() && !Timers.BeingEdited()) { + char *tail; + int From = strtol(Option, &tail, 10); + if (tail && tail != Option) { + tail = skipspace(tail); + if (tail && tail != Option) { + int To = strtol(tail, NULL, 10); + int CurrentChannelNr = cDevice::CurrentChannel(); + cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr); + cChannel *FromChannel = Channels.GetByNumber(From); + if (FromChannel) { + cChannel *ToChannel = Channels.GetByNumber(To); + if (ToChannel) { + int FromNumber = FromChannel->Number(); + int ToNumber = ToChannel->Number(); + if (FromNumber != ToNumber) { + Channels.Move(FromChannel, ToChannel); + Channels.ReNumber(); + Channels.SetModified(true); + if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) { + if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) { + Channels.SwitchTo(CurrentChannel->Number()); + } + else { + cDevice::SetCurrentChannel(CurrentChannel); + } + } + isyslog("channel %d moved to %d", FromNumber, ToNumber); + Reply(250,"Channel \"%d\" moved to \"%d\"", From, To); + } + else { + Reply(501, "Can't move channel to same postion"); + } + } + else { + Reply(501, "Channel \"%d\" not defined", To); + } + } + else { + Reply(501, "Channel \"%d\" not defined", From); + } + } + else { + Reply(501, "Error in channel number"); + } + } + else { + Reply(501, "Error in channel number"); + } + } + else { + Reply(550, "Channels or timers are being edited - try again later"); + } + } + else { + Reply(501, "Missing channel number"); + } + EXIT_WRAPPER(); +} + +bool cConnectionVTP::CmdDELC(const char *Option) +{ + INIT_WRAPPER(); + if (*Option) { + if (isnumber(Option)) { + if (!Channels.BeingEdited()) { + cChannel *channel = Channels.GetByNumber(strtol(Option, NULL, 10)); + if (channel) { + for (cTimer *timer = Timers.First(); timer; timer = Timers.Next(timer)) { + if (timer->Channel() == channel) { + Reply(550, "Channel \"%s\" is in use by timer %d", Option, timer->Index() + 1); + return false; + } + } + int CurrentChannelNr = cDevice::CurrentChannel(); + cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr); + if (CurrentChannel && channel == CurrentChannel) { + int n = Channels.GetNextNormal(CurrentChannel->Index()); + if (n < 0) + n = Channels.GetPrevNormal(CurrentChannel->Index()); + CurrentChannel = Channels.Get(n); + CurrentChannelNr = 0; // triggers channel switch below + } + Channels.Del(channel); + Channels.ReNumber(); + Channels.SetModified(true); + isyslog("channel %s deleted", Option); + if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) { + if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) + Channels.SwitchTo(CurrentChannel->Number()); + else + cDevice::SetCurrentChannel(CurrentChannel); + } + Reply(250, "Channel \"%s\" deleted", Option); + } + else + Reply(501, "Channel \"%s\" not defined", Option); + } + else + Reply(550, "Channels are being edited - try again later"); + } + else + Reply(501, "Error in channel number \"%s\"", Option); + } + else { + Reply(501, "Missing channel number"); + } + EXIT_WRAPPER(); +} + +bool cConnectionVTP::CmdDELR(const char *Option) +{ + INIT_WRAPPER(); + if (*Option) { + if (isnumber(Option)) { + cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1); + if (recording) { + cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName()); + if (!rc) { + if (recording->Delete()) { + Reply(250, "Recording \"%s\" deleted", Option); + ::Recordings.DelByName(recording->FileName()); + } + else + Reply(554, "Error while deleting recording!"); + } + else + Reply(550, "Recording \"%s\" is in use by timer %d", Option, rc->Timer()->Index() + 1); + } + else + Reply(550, "Recording \"%s\" not found%s", Option, Recordings.Count() ? "" : " (use LSTR before deleting)"); + } + else + Reply(501, "Error in recording number \"%s\"", Option); + } + else + Reply(501, "Missing recording number"); + EXIT_WRAPPER(); +} + +bool cConnectionVTP::CmdRENR(const char *Option) +{ + INIT_WRAPPER(); +#if defined(LIEMIKUUTIO) + bool recordings = Recordings.Update(true); + if (recordings) { + if (*Option) { + char *tail; + int n = strtol(Option, &tail, 10); + cRecording *recording = Recordings.Get(n - 1); + if (recording && tail && tail != Option) { +#if APIVERSNUM < 10704 + int priority = recording->priority; + int lifetime = recording->lifetime; +#endif + char *oldName = strdup(recording->Name()); + tail = skipspace(tail); +#if APIVERSNUM < 10704 + if (recording->Rename(tail, &priority, &lifetime)) { +#else + if (recording->Rename(tail)) { +#endif + Reply(250, "Renamed \"%s\" to \"%s\"", oldName, recording->Name()); + Recordings.ChangeState(); + Recordings.TouchUpdate(); + } + else { + Reply(501, "Renaming \"%s\" to \"%s\" failed", oldName, tail); + } + free(oldName); + } + else { + Reply(501, "Recording not found or wrong syntax"); + } + } + else { + Reply(501, "Missing Input settings"); + } + } + else { + Reply(550, "No recordings available"); + } +#else + Reply(501, "Rename not supported, please use LIEMIEXT"); +#endif /* LIEMIKUUTIO */ + EXIT_WRAPPER(); +} + +bool cConnectionVTP::Respond(int Code, const char *Message, ...) +{ + va_list ap; + va_start(ap, Message); +#if APIVERSNUM < 10515 + char *buffer; + if (vasprintf(&buffer, Message, ap) < 0) + buffer = strdup("???"); + cString str(buffer, true); +#else + cString str = cString::sprintf(Message, ap); +#endif + va_end(ap); + + if (Code >= 0 && m_LastCommand != NULL) { + free(m_LastCommand); + m_LastCommand = NULL; + } + + return cServerConnection::Respond("%03d%c%s", Code >= 0, + Code < 0 ? -Code : Code, + Code < 0 ? '-' : ' ', *str); +} diff --git a/plugins/streamdev/streamdev-cvs/server/connectionVTP.h b/plugins/streamdev/streamdev-cvs/server/connectionVTP.h new file mode 100644 index 0000000..b938fe6 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/connectionVTP.h @@ -0,0 +1,93 @@ +#ifndef VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H +#define VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H + +#include "server/connection.h" +#include "server/recplayer.h" + +class cTBSocket; +class cStreamdevLiveStreamer; +class cStreamdevFilterStreamer; +class cLSTEHandler; +class cLSTCHandler; +class cLSTTHandler; +class cLSTRHandler; + +class cConnectionVTP: public cServerConnection { + friend class cLSTEHandler; +#if !defined __GNUC__ || __GNUC__ >= 3 + using cServerConnection::Respond; +#endif + +private: + cTBSocket *m_LiveSocket; + cStreamdevLiveStreamer *m_LiveStreamer; + cTBSocket *m_FilterSocket; + cStreamdevFilterStreamer *m_FilterStreamer; + cTBSocket *m_RecSocket; + cTBSocket *m_DataSocket; + + char *m_LastCommand; + eStreamType m_StreamType; + bool m_FiltersSupport; + RecPlayer *m_RecPlayer; + + // Members adopted for SVDRP + cLSTEHandler *m_LSTEHandler; + cLSTCHandler *m_LSTCHandler; + cLSTTHandler *m_LSTTHandler; + cLSTRHandler *m_LSTRHandler; + +protected: + template<class cHandler> + bool CmdLSTX(cHandler *&Handler, char *Option); + +public: + cConnectionVTP(void); + virtual ~cConnectionVTP(); + + virtual void Welcome(void); + virtual void Reject(void); + + virtual bool Abort(void) const; + virtual void Detach(void); + virtual void Attach(void); + + virtual bool Command(char *Cmd); + bool CmdCAPS(char *Opts); + bool CmdPROV(char *Opts); + bool CmdPORT(char *Opts); + bool CmdREAD(char *Opts); + bool CmdTUNE(char *Opts); + bool CmdPLAY(char *Opts); + bool CmdADDP(char *Opts); + bool CmdDELP(char *Opts); + bool CmdADDF(char *Opts); + bool CmdDELF(char *Opts); + bool CmdABRT(char *Opts); + bool CmdQUIT(void); + bool CmdSUSP(void); + + // Thread-safe implementations of SVDRP commands + bool CmdLSTE(char *Opts); + bool CmdLSTC(char *Opts); + bool CmdLSTT(char *Opts); + bool CmdLSTR(char *Opts); + + // Commands adopted from SVDRP + bool CmdSTAT(const char *Option); + bool CmdMODT(const char *Option); + bool CmdNEWT(const char *Option); + bool CmdDELT(const char *Option); + bool CmdNEXT(const char *Option); + bool CmdNEWC(const char *Option); + bool CmdMODC(const char *Option); + bool CmdMOVC(const char *Option); + bool CmdDELC(const char *Option); + bool CmdDELR(const char *Option); + bool CmdRENR(const char *Option); + + bool Respond(int Code, const char *Message, ...) + __attribute__ ((format (printf, 3, 4))); +}; + +#endif // VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H diff --git a/plugins/streamdev/streamdev-cvs/server/http/CVS/Entries b/plugins/streamdev/streamdev-cvs/server/http/CVS/Entries new file mode 100644 index 0000000..1784810 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/http/CVS/Entries @@ -0,0 +1 @@ +D diff --git a/plugins/streamdev/streamdev-cvs/server/http/CVS/Repository b/plugins/streamdev/streamdev-cvs/server/http/CVS/Repository new file mode 100644 index 0000000..49fadb8 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/http/CVS/Repository @@ -0,0 +1 @@ +streamdev/server/http diff --git a/plugins/streamdev/streamdev-cvs/server/http/CVS/Root b/plugins/streamdev/streamdev-cvs/server/http/CVS/Root new file mode 100644 index 0000000..2c7f6ce --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/http/CVS/Root @@ -0,0 +1 @@ +:pserver:anoncvs@vdr-developer.org:/var/cvsroot diff --git a/plugins/streamdev/streamdev-cvs/server/livefilter.c b/plugins/streamdev/streamdev-cvs/server/livefilter.c new file mode 100644 index 0000000..67b0e37 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/livefilter.c @@ -0,0 +1,39 @@ +/* + * $Id: livefilter.c,v 1.7 2009/02/13 13:02:40 schmirl Exp $ + */ + +#include "server/livefilter.h" +#include "server/streamer.h" +#include "common.h" + +#ifndef TS_SYNC_BYTE +# define TS_SYNC_BYTE 0x47 +#endif + +cStreamdevLiveFilter::cStreamdevLiveFilter(cStreamdevStreamer *Streamer) { + m_Streamer = Streamer; +} + +void cStreamdevLiveFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length) +{ + uchar buffer[TS_SIZE]; + int length = Length; + int pos = 0; + + while (length > 0) { + int chunk = min(length, TS_SIZE - 5); + buffer[0] = TS_SYNC_BYTE; + buffer[1] = ((Pid >> 8) & 0x3f) | (pos==0 ? 0x40 : 0); /* bit 6: payload unit start indicator (PUSI) */ + buffer[2] = Pid & 0xff; + buffer[3] = Tid; + // this makes it a proprietary stream + buffer[4] = (uchar)chunk; + memcpy(buffer + 5, Data + pos, chunk); + length -= chunk; + pos += chunk; + + int p = m_Streamer->Put(buffer, TS_SIZE); + if (p != TS_SIZE) + m_Streamer->ReportOverflow(TS_SIZE - p); + } +} diff --git a/plugins/streamdev/streamdev-cvs/server/livefilter.h b/plugins/streamdev/streamdev-cvs/server/livefilter.h new file mode 100644 index 0000000..13e8956 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/livefilter.h @@ -0,0 +1,32 @@ +/* + * $Id: livefilter.h,v 1.5 2008/04/07 14:27:31 schmirl Exp $ + */ + +#ifndef VDR_STREAMEV_LIVEFILTER_H +#define VDR_STREAMEV_LIVEFILTER_H + +#include <vdr/config.h> + +#include <vdr/filter.h> + +class cStreamdevStreamer; + +class cStreamdevLiveFilter: public cFilter { +private: + cStreamdevStreamer *m_Streamer; + +protected: + virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length); + +public: + cStreamdevLiveFilter(cStreamdevStreamer *Streamer); + + void Set(u_short Pid, u_char Tid, u_char Mask) { + cFilter::Set(Pid, Tid, Mask); + } + void Del(u_short Pid, u_char Tid, u_char Mask) { + cFilter::Del(Pid, Tid, Mask); + } +}; + +#endif // VDR_STREAMEV_LIVEFILTER_H diff --git a/plugins/streamdev/streamdev-cvs/server/livestreamer.c b/plugins/streamdev/streamdev-cvs/server/livestreamer.c new file mode 100644 index 0000000..3162fa2 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/livestreamer.c @@ -0,0 +1,689 @@ +#include <assert.h> + +#include <libsi/section.h> +#include <libsi/descriptor.h> + +#include "remux/ts2ps.h" +#include "remux/ts2pes.h" +#include "remux/ts2es.h" +#include "remux/extern.h" + +#include <vdr/ringbuffer.h> + +#include "server/livestreamer.h" +#include "server/livefilter.h" +#include "common.h" + +using namespace Streamdev; + +// --- cStreamdevLiveReceiver ------------------------------------------------- + +class cStreamdevLiveReceiver: public cReceiver { + friend class cStreamdevStreamer; + +private: + cStreamdevStreamer *m_Streamer; + +protected: + virtual void Activate(bool On); + virtual void Receive(uchar *Data, int Length); + +public: + cStreamdevLiveReceiver(cStreamdevStreamer *Streamer, tChannelID ChannelID, int Priority, const int *Pids); + virtual ~cStreamdevLiveReceiver(); +}; + +cStreamdevLiveReceiver::cStreamdevLiveReceiver(cStreamdevStreamer *Streamer, tChannelID ChannelID, + int Priority, const int *Pids): + cReceiver(ChannelID, Priority, 0, Pids), + m_Streamer(Streamer) +{ +} + +cStreamdevLiveReceiver::~cStreamdevLiveReceiver() +{ + Dprintf("Killing live receiver\n"); + Detach(); +} + +void cStreamdevLiveReceiver::Receive(uchar *Data, int Length) { + int p = m_Streamer->Receive(Data, Length); + if (p != Length) + m_Streamer->ReportOverflow(Length - p); +} + +inline void cStreamdevLiveReceiver::Activate(bool On) +{ + Dprintf("LiveReceiver->Activate(%d)\n", On); + m_Streamer->Activate(On); +} + +// --- cStreamdevPatFilter ---------------------------------------------------- + +class cStreamdevPatFilter : public cFilter { +private: + int pmtPid; + int pmtSid; + int pmtVersion; + uchar tspat_buf[TS_SIZE]; + cStreamdevBuffer siBuffer; + + const cChannel *m_Channel; + cStreamdevLiveStreamer *m_Streamer; + + virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length); + + int GetPid(SI::PMT::Stream& stream); +public: + cStreamdevPatFilter(cStreamdevLiveStreamer *Streamer, const cChannel *Channel); + uchar* Get(int &Count) { return siBuffer.Get(Count); } + void Del(int Count) { return siBuffer.Del(Count); } +}; + +cStreamdevPatFilter::cStreamdevPatFilter(cStreamdevLiveStreamer *Streamer, const cChannel *Channel): siBuffer(10 * TS_SIZE, TS_SIZE) +{ + Dprintf("cStreamdevPatFilter(\"%s\")\n", Channel->Name()); + assert(Streamer); + m_Channel = Channel; + m_Streamer = Streamer; + pmtPid = 0; + pmtSid = 0; + pmtVersion = -1; + Set(0x00, 0x00); // PAT + // initialize PAT buffer. Only some values are dynamic (see comments) + memset(tspat_buf, 0xff, TS_SIZE); + tspat_buf[0] = TS_SYNC_BYTE; // Transport packet header sunchronization byte (1000011 = 0x47h) + tspat_buf[1] = 0x40; // Set payload unit start indicator bit + tspat_buf[2] = 0x0; // PID + tspat_buf[3] = 0x10; // Set payload flag, DYNAMIC: Continuity counter + tspat_buf[4] = 0x0; // SI pointer field + tspat_buf[5] = 0x0; // PAT table id + tspat_buf[6] = 0xb0; // Section syntax indicator bit and reserved bits set + tspat_buf[7] = 12 + 1; // Section length (12 bit): PAT_TABLE_LEN + 1 + tspat_buf[8] = 0; // DYNAMIC: Transport stream ID (bits 8-15) + tspat_buf[9] = 0; // DYNAMIC: Transport stream ID (bits 0-7) + tspat_buf[10] = 0xc0; // Reserved, DYNAMIC: Version number, DYNAMIC: Current next indicator + tspat_buf[11] = 0x0; // Section number + tspat_buf[12] = 0x0; // Last section number + tspat_buf[13] = 0; // DYNAMIC: Program number (bits 8-15) + tspat_buf[14] = 0; // DYNAMIC: Program number (bits 0-7) + tspat_buf[15] = 0xe0; // Reserved, DYNAMIC: Network ID (bits 8-12) + tspat_buf[16] = 0; // DYNAMIC: Network ID (bits 0-7) + tspat_buf[17] = 0; // DYNAMIC: Checksum + tspat_buf[18] = 0; // DYNAMIC: Checksum + tspat_buf[19] = 0; // DYNAMIC: Checksum + tspat_buf[20] = 0; // DYNAMIC: Checksum +} + +static const char * const psStreamTypes[] = { + "UNKNOWN", + "ISO/IEC 11172 Video", + "ISO/IEC 13818-2 Video", + "ISO/IEC 11172 Audio", + "ISO/IEC 13818-3 Audio", + "ISO/IEC 13818-1 Privete sections", + "ISO/IEC 13818-1 Private PES data", + "ISO/IEC 13512 MHEG", + "ISO/IEC 13818-1 Annex A DSM CC", + "0x09", + "ISO/IEC 13818-6 Multiprotocol encapsulation", + "ISO/IEC 13818-6 DSM-CC U-N Messages", + "ISO/IEC 13818-6 Stream Descriptors", + "ISO/IEC 13818-6 Sections (any type, including private data)", + "ISO/IEC 13818-1 auxiliary", + "ISO/IEC 13818-7 Audio with ADTS transport sytax", + "ISO/IEC 14496-2 Visual (MPEG-4)", + "ISO/IEC 14496-3 Audio with LATM transport syntax", + "0x12", "0x13", "0x14", "0x15", "0x16", "0x17", "0x18", "0x19", "0x1a", + "ISO/IEC 14496-10 Video (MPEG-4 part 10/AVC, aka H.264)", + "", +}; + +int cStreamdevPatFilter::GetPid(SI::PMT::Stream& stream) +{ + SI::Descriptor *d; + + if (!stream.getPid()) + return 0; + + switch (stream.getStreamType()) { + case 0x01: // ISO/IEC 11172 Video + case 0x02: // ISO/IEC 13818-2 Video + case 0x03: // ISO/IEC 11172 Audio + case 0x04: // ISO/IEC 13818-3 Audio +#if 0 + case 0x07: // ISO/IEC 13512 MHEG + case 0x08: // ISO/IEC 13818-1 Annex A DSM CC + case 0x0a: // ISO/IEC 13818-6 Multiprotocol encapsulation + case 0x0b: // ISO/IEC 13818-6 DSM-CC U-N Messages + case 0x0c: // ISO/IEC 13818-6 Stream Descriptors + case 0x0d: // ISO/IEC 13818-6 Sections (any type, including private data) + case 0x0e: // ISO/IEC 13818-1 auxiliary +#endif + case 0x0f: // ISO/IEC 13818-7 Audio with ADTS transport syntax + case 0x10: // ISO/IEC 14496-2 Visual (MPEG-4) + case 0x11: // ISO/IEC 14496-3 Audio with LATM transport syntax + case 0x1b: // ISO/IEC 14496-10 Video (MPEG-4 part 10/AVC, aka H.264) + Dprintf("cStreamdevPatFilter PMT scanner adding PID %d (%s)\n", + stream.getPid(), psStreamTypes[stream.getStreamType()]); + return stream.getPid(); + case 0x05: // ISO/IEC 13818-1 private sections + case 0x06: // ISO/IEC 13818-1 PES packets containing private data + for (SI::Loop::Iterator it; (d = stream.streamDescriptors.getNext(it)); ) { + switch (d->getDescriptorTag()) { + case SI::AC3DescriptorTag: + Dprintf("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s\n", + stream.getPid(), psStreamTypes[stream.getStreamType()], "AC3"); + delete d; + return stream.getPid(); + case SI::TeletextDescriptorTag: + Dprintf("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s\n", + stream.getPid(), psStreamTypes[stream.getStreamType()], "Teletext"); + delete d; + return stream.getPid(); + case SI::SubtitlingDescriptorTag: + Dprintf("cStreamdevPatFilter PMT scanner: adding PID %d (%s) %s\n", + stream.getPid(), psStreamTypes[stream.getStreamType()], "DVBSUB"); + delete d; + return stream.getPid(); + default: + Dprintf("cStreamdevPatFilter PMT scanner: NOT adding PID %d (%s) %s\n", + stream.getPid(), psStreamTypes[stream.getStreamType()], "UNKNOWN"); + break; + } + delete d; + } + break; + default: + /* This following section handles all the cases where the audio track + * info is stored in PMT user info with stream id >= 0x80 + * we check the registration format identifier to see if it + * holds "AC-3" + */ + if (stream.getStreamType() >= 0x80) { + bool found = false; + for (SI::Loop::Iterator it; (d = stream.streamDescriptors.getNext(it)); ) { + switch (d->getDescriptorTag()) { + case SI::RegistrationDescriptorTag: + /* unfortunately libsi does not implement RegistrationDescriptor */ + if (d->getLength() >= 4) { + found = true; + SI::CharArray rawdata = d->getData(); + if (/*rawdata[0] == 5 && rawdata[1] >= 4 && */ + rawdata[2] == 'A' && rawdata[3] == 'C' && + rawdata[4] == '-' && rawdata[5] == '3') { + isyslog("cStreamdevPatFilter PMT scanner:" + "Adding pid %d (type 0x%x) RegDesc len %d (%c%c%c%c)", + stream.getPid(), stream.getStreamType(), + d->getLength(), rawdata[2], rawdata[3], + rawdata[4], rawdata[5]); + delete d; + return stream.getPid(); + } + } + break; + default: + break; + } + delete d; + } + if(!found) { + isyslog("Adding pid %d (type 0x%x) RegDesc not found -> assume AC-3", + stream.getPid(), stream.getStreamType()); + return stream.getPid(); + } + } + Dprintf("cStreamdevPatFilter PMT scanner: NOT adding PID %d (%s) %s\n", + stream.getPid(), psStreamTypes[stream.getStreamType()<0x1c?stream.getStreamType():0], "UNKNOWN"); + break; + } + return 0; +} + +void cStreamdevPatFilter::Process(u_short Pid, u_char Tid, const u_char *Data, int Length) +{ + if (Pid == 0x00) { + if (Tid == 0x00) { + SI::PAT pat(Data, false); + if (!pat.CheckCRCAndParse()) + return; + SI::PAT::Association assoc; + for (SI::Loop::Iterator it; pat.associationLoop.getNext(assoc, it); ) { + if (!assoc.isNITPid()) { + const cChannel *Channel = Channels.GetByServiceID(Source(), Transponder(), assoc.getServiceId()); + if (Channel && (Channel == m_Channel)) { + int prevPmtPid = pmtPid; + if (0 != (pmtPid = assoc.getPid())) { + Dprintf("cStreamdevPatFilter: PMT pid for channel %s: %d\n", Channel->Name(), pmtPid); + pmtSid = assoc.getServiceId(); + // repack PAT to TS frame and send to client + int ts_id; + unsigned int crc, i, len; + uint8_t *tmp; + static uint8_t ccounter = 0; + ccounter = (ccounter + 1) % 16; + ts_id = Channel->Tid(); // Get transport stream id of the channel + tspat_buf[3] = 0x10 | ccounter; // Set payload flag, Continuity counter + tspat_buf[8] = (ts_id >> 8); // Transport stream ID (bits 8-15) + tspat_buf[9] = (ts_id & 0xff); // Transport stream ID (bits 0-7) + tspat_buf[10] = 0xc0 | ((pat.getVersionNumber() << 1) & 0x3e) | + pat.getCurrentNextIndicator();// Version number, Current next indicator + tspat_buf[13] = (pmtSid >> 8); // Program number (bits 8-15) + tspat_buf[14] = (pmtSid & 0xff); // Program number (bits 0-7) + tspat_buf[15] = 0xe0 | (pmtPid >> 8); // Network ID (bits 8-12) + tspat_buf[16] = (pmtPid & 0xff); // Network ID (bits 0-7) + crc = 0xffffffff; + len = 12; // PAT_TABLE_LEN + tmp = &tspat_buf[4 + 1]; // TS_HDR_LEN + 1 + while (len--) { + crc ^= *tmp++ << 24; + for (i = 0; i < 8; i++) + crc = (crc << 1) ^ ((crc & 0x80000000) ? 0x04c11db7 : 0); // CRC32POLY + } + tspat_buf[17] = crc >> 24 & 0xff; // Checksum + tspat_buf[18] = crc >> 16 & 0xff; // Checksum + tspat_buf[19] = crc >> 8 & 0xff; // Checksum + tspat_buf[20] = crc & 0xff; // Checksum + int written = siBuffer.PutTS(tspat_buf, TS_SIZE); + if (written != TS_SIZE) + siBuffer.ReportOverflow(TS_SIZE - written); + if (pmtPid != prevPmtPid) { + m_Streamer->SetPids(pmtPid); + Add(pmtPid, 0x02); + pmtVersion = -1; + } + return; + } + } + } + } + } + } else if (Pid == pmtPid && Tid == SI::TableIdPMT && Source() && Transponder()) { + SI::PMT pmt(Data, false); + if (!pmt.CheckCRCAndParse()) + return; + if (pmt.getServiceId() != pmtSid) + return; // skip broken PMT records + if (pmtVersion != -1) { + if (pmtVersion != pmt.getVersionNumber()) { + Dprintf("cStreamdevPatFilter: PMT version changed, detaching all pids\n"); + cFilter::Del(pmtPid, 0x02); + pmtPid = 0; // this triggers PAT scan + } + return; + } + pmtVersion = pmt.getVersionNumber(); + + SI::PMT::Stream stream; + int pids[MAXRECEIVEPIDS + 1], npids = 0; + pids[npids++] = pmtPid; +#if 0 + pids[npids++] = 0x10; // pid 0x10, tid 0x40: NIT + pids[npids++] = 0x11; // pid 0x11, tid 0x42: SDT + pids[npids++] = 0x14; // pid 0x14, tid 0x70: TDT +#endif + pids[npids++] = 0x12; // pid 0x12, tid 0x4E...0x6F: EIT + for (SI::Loop::Iterator it; pmt.streamLoop.getNext(stream, it); ) + if (0 != (pids[npids] = GetPid(stream)) && npids < MAXRECEIVEPIDS) + npids++; + + pids[npids] = 0; + m_Streamer->SetPids(pmt.getPCRPid(), pids); + } +} + +// --- cStreamdevLiveStreamer ------------------------------------------------- + +cStreamdevLiveStreamer::cStreamdevLiveStreamer(int Priority, std::string Parameter): + cStreamdevStreamer("streamdev-livestreaming"), + m_Priority(Priority), + m_Parameter(Parameter), + m_NumPids(0), + m_StreamType(stTSPIDS), + m_Channel(NULL), + m_Device(NULL), + m_Receiver(NULL), + m_PatFilter(NULL), + m_Remux(NULL) +{ +} + +cStreamdevLiveStreamer::~cStreamdevLiveStreamer() +{ + Dprintf("Desctructing Live streamer\n"); + Stop(); + if(m_PatFilter) { + Detach(); + DELETENULL(m_PatFilter); + } + DELETENULL(m_Receiver); + delete m_Remux; +} + +bool cStreamdevLiveStreamer::HasPid(int Pid) +{ + int idx; + for (idx = 0; idx < m_NumPids; ++idx) + if (m_Pids[idx] == Pid) + return true; + return false; +} + +bool cStreamdevLiveStreamer::SetPid(int Pid, bool On) +{ + int idx; + + if (Pid == 0) + return true; + + if (On) { + for (idx = 0; idx < m_NumPids; ++idx) { + if (m_Pids[idx] == Pid) + return true; // No change needed + } + + if (m_NumPids == MAXRECEIVEPIDS) { + esyslog("ERROR: Streamdev: No free slot to receive pid %d\n", Pid); + return false; + } + + m_Pids[m_NumPids++] = Pid; + m_Pids[m_NumPids] = 0; + } else { + for (idx = 0; idx < m_NumPids; ++idx) { + if (m_Pids[idx] == Pid) { + --m_NumPids; + memmove(&m_Pids[idx], &m_Pids[idx + 1], sizeof(int) * (m_NumPids - idx)); + } + } + } + + StartReceiver(); + return true; +} + +bool cStreamdevLiveStreamer::SetPids(int Pid, const int *Pids1, const int *Pids2, const int *Pids3) +{ + m_NumPids = 0; + + if (Pid) + m_Pids[m_NumPids++] = Pid; + + if (Pids1) + for ( ; *Pids1 && m_NumPids < MAXRECEIVEPIDS; Pids1++) + if (!HasPid(*Pids1)) + m_Pids[m_NumPids++] = *Pids1; + + if (Pids2) + for ( ; *Pids2 && m_NumPids < MAXRECEIVEPIDS; Pids2++) + if (!HasPid(*Pids2)) + m_Pids[m_NumPids++] = *Pids2; + + if (Pids3) + for ( ; *Pids3 && m_NumPids < MAXRECEIVEPIDS; Pids3++) + if (!HasPid(*Pids3)) + m_Pids[m_NumPids++] = *Pids3; + + if (m_NumPids >= MAXRECEIVEPIDS) { + esyslog("ERROR: Streamdev: No free slot to receive pid %d\n", Pid); + return false; + } + + m_Pids[m_NumPids] = 0; + StartReceiver(); + return true; +} + +void cStreamdevLiveStreamer::StartReceiver(void) +{ + DELETENULL(m_Receiver); + if (m_NumPids > 0) { + Dprintf("Creating Receiver to respect changed pids\n"); + m_Receiver = new cStreamdevLiveReceiver(this, m_Channel->GetChannelID(), m_Priority, m_Pids); + if (IsRunning() && m_Device != NULL) { + Dprintf("Attaching new receiver\n"); + Attach(); + } + } +} + +bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType StreamType, int Apid) +{ + Dprintf("Initializing Remuxer for full channel transfer\n"); + //printf("ca pid: %d\n", Channel->Ca()); + m_Channel = Channel; + m_StreamType = StreamType; + + int apid[2] = { Apid, 0 }; + const int *Apids = Apid ? apid : m_Channel->Apids(); + const int *Dpids = Apid ? NULL : m_Channel->Dpids(); + + switch (m_StreamType) { + case stES: + { + int pid = ISRADIO(m_Channel) ? m_Channel->Apid(0) : m_Channel->Vpid(); + if (Apid != 0) + pid = Apid; + m_Remux = new cTS2ESRemux(pid); + return SetPids(pid); + } + + case stPES: + m_Remux = new cTS2PESRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(), + m_Channel->Spids()); + return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids()); + + case stPS: + m_Remux = new cTS2PSRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(), + m_Channel->Spids()); + return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids()); + + case stExtern: + m_Remux = new cExternRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(), + m_Channel->Spids(), m_Parameter); + // fall through + case stTS: + // This should never happen, but ... + if (m_PatFilter) { + Detach(); + DELETENULL(m_PatFilter); + } + // Set pids from cChannel + SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids()); + if (m_Channel->Vpid() != m_Channel->Ppid()) + SetPid(m_Channel->Ppid(), true); + // Set pids from PMT + m_PatFilter = new cStreamdevPatFilter(this, m_Channel); + return true; + + case stTSPIDS: + Dprintf("pid streaming mode\n"); + return true; + default: + return false; + } +} + +int cStreamdevLiveStreamer::Put(const uchar *Data, int Count) +{ + // insert si data + if (m_PatFilter) { + int siCount; + uchar *siData = m_PatFilter->Get(siCount); + if (siData) { + if (m_Remux) + siCount = m_Remux->Put(siData, siCount); + else + siCount = cStreamdevStreamer::Put(siData, siCount); + if (siCount) + m_PatFilter->Del(siCount); + } + } + if (m_Remux) + return m_Remux->Put(Data, Count); + else + return cStreamdevStreamer::Put(Data, Count); +} + +uchar *cStreamdevLiveStreamer::Get(int &Count) +{ + if (m_Remux) + return m_Remux->Get(Count); + else + return cStreamdevStreamer::Get(Count); +} + +void cStreamdevLiveStreamer::Del(int Count) +{ + if (m_Remux) + m_Remux->Del(Count); + else + cStreamdevStreamer::Del(Count); +} + +void cStreamdevLiveStreamer::Attach(void) +{ + Dprintf("cStreamdevLiveStreamer::Attach()\n"); + if (m_Device) { + if (m_Receiver) { + m_Device->Detach(m_Receiver); + m_Device->AttachReceiver(m_Receiver); + } + if (m_PatFilter) { + m_Device->Detach(m_PatFilter); + m_Device->AttachFilter(m_PatFilter); + } + } +} + +void cStreamdevLiveStreamer::Detach(void) +{ + Dprintf("cStreamdevLiveStreamer::Detach()\n"); + if (m_Device) { + if (m_Receiver) + m_Device->Detach(m_Receiver); + if (m_PatFilter) + m_Device->Detach(m_PatFilter); + } +} + +std::string cStreamdevLiveStreamer::Report(void) +{ + std::string result; + + if (m_Device != NULL) + result += (std::string)"+- Device is " + (const char*)itoa(m_Device->CardIndex()) + "\n"; + if (m_Receiver != NULL) + result += "+- Receiver is allocated\n"; + + result += "+- Pids are "; + for (int i = 0; i < MAXRECEIVEPIDS; ++i) + if (m_Pids[i] != 0) + result += (std::string)(const char*)itoa(m_Pids[i]) + ", "; + result += "\n"; + return result; +} + +// --- cStreamdevFilterStreamer ------------------------------------------------- + +cStreamdevFilterStreamer::cStreamdevFilterStreamer(): + cStreamdevStreamer("streamdev-filterstreaming"), + m_Device(NULL), + m_Filter(NULL)/*, + m_Channel(NULL)*/ +{ +} + +cStreamdevFilterStreamer::~cStreamdevFilterStreamer() +{ + Dprintf("Desctructing Filter streamer\n"); + Detach(); + m_Device = NULL; + DELETENULL(m_Filter); + Stop(); +} + +void cStreamdevFilterStreamer::Attach(void) +{ + Dprintf("cStreamdevFilterStreamer::Attach()\n"); + LOCK_THREAD; + if(m_Device && m_Filter) + m_Device->AttachFilter(m_Filter); +} + +void cStreamdevFilterStreamer::Detach(void) +{ + Dprintf("cStreamdevFilterStreamer::Detach()\n"); + LOCK_THREAD; + if(m_Device && m_Filter) + m_Device->Detach(m_Filter); +} + +#if 0 +void cStreamdevFilterStreamer::SetChannel(const cChannel *Channel) +{ + LOCK_THREAD; + Dprintf("cStreamdevFilterStreamer::SetChannel(%s : %s)", Channel?Channel->Name():"<null>", + Channel ? *Channel->GetChannelID().ToString() : ""); + m_Channel = Channel; +} +#endif + +void cStreamdevFilterStreamer::SetDevice(cDevice *Device) +{ + Dprintf("cStreamdevFilterStreamer::SetDevice()\n"); + LOCK_THREAD; + if(Device != m_Device) { + Detach(); + m_Device = Device; + //m_Channel = NULL; + Attach(); + } +} + +bool cStreamdevFilterStreamer::SetFilter(u_short Pid, u_char Tid, u_char Mask, bool On) +{ + Dprintf("cStreamdevFilterStreamer::SetFilter(%u,0x%x,0x%x,%s)\n", Pid, Tid, Mask, On?"On":"Off"); + + if(!m_Device) + return false; + + if (On) { + if (m_Filter == NULL) { + m_Filter = new cStreamdevLiveFilter(this); + Dprintf("attaching filter to device\n"); + Attach(); + } + m_Filter->Set(Pid, Tid, Mask); + } else if (m_Filter != NULL) + m_Filter->Del(Pid, Tid, Mask); + + return true; +} + +#if 0 +void cStreamdevFilterStreamer::ChannelSwitch(const cDevice *Device, int ChannelNumber) { + LOCK_THREAD; + if(Device == m_Device) { + if(ChannelNumber > 0) { + cChannel *ch = Channels.GetByNumber(ChannelNumber); + if(ch != NULL) { + if(m_Filter != NULL && + m_Channel != NULL && + (! TRANSPONDER(ch, m_Channel))) { + + isyslog("***** LiveFilterStreamer: transponder changed ! %s", + *ch->GetChannelID().ToString()); + + uchar buffer[TS_SIZE] = {TS_SYNC_BYTE, 0xff, 0xff, 0xff, 0x7f, 0}; + strcpy((char*)(buffer + 5), ch->GetChannelID().ToString()); + int p = Put(buffer, TS_SIZE); + if (p != TS_SIZE) + ReportOverflow(TS_SIZE - p); + } + m_Channel = ch; + } + } + } +} +#endif diff --git a/plugins/streamdev/streamdev-cvs/server/livestreamer.h b/plugins/streamdev/streamdev-cvs/server/livestreamer.h new file mode 100644 index 0000000..92448bb --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/livestreamer.h @@ -0,0 +1,82 @@ +#ifndef VDR_STREAMDEV_LIVESTREAMER_H +#define VDR_STREAMDEV_LIVESTREAMER_H + +#include <vdr/config.h> +#include <vdr/receiver.h> + +#include "server/streamer.h" +#include "common.h" + +namespace Streamdev { + class cTSRemux; +} +class cStreamdevPatFilter; +class cStreamdevLiveReceiver; + +// --- cStreamdevLiveStreamer ------------------------------------------------- + +class cStreamdevLiveStreamer: public cStreamdevStreamer { +private: + int m_Priority; + std::string m_Parameter; + int m_Pids[MAXRECEIVEPIDS + 1]; + int m_NumPids; + eStreamType m_StreamType; + const cChannel *m_Channel; + cDevice *m_Device; + cStreamdevLiveReceiver *m_Receiver; + cStreamdevPatFilter *m_PatFilter; + Streamdev::cTSRemux *m_Remux; + + void StartReceiver(void); + bool HasPid(int Pid); + +public: + cStreamdevLiveStreamer(int Priority, std::string Parameter = ""); + 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, int Apid = 0); + + virtual int Put(const uchar *Data, int Count); + virtual uchar *Get(int &Count); + virtual void Del(int Count); + + virtual void Attach(void); + virtual void Detach(void); + + // Statistical purposes: + virtual std::string Report(void); +}; + + +// --- cStreamdevFilterStreamer ------------------------------------------------- + +//#include <vdr/status.h> + +class cStreamdevLiveFilter; + +class cStreamdevFilterStreamer: public cStreamdevStreamer /*, public cStatus*/ { +private: + cDevice *m_Device; + cStreamdevLiveFilter *m_Filter; + //const cChannel *m_Channel; + +public: + cStreamdevFilterStreamer(); + virtual ~cStreamdevFilterStreamer(); + + void SetDevice(cDevice *Device); + //void SetChannel(const cChannel *Channel); + bool SetFilter(u_short Pid, u_char Tid, u_char Mask, bool On); + + virtual void Attach(void); + virtual void Detach(void); + + // cStatus message handlers + //virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber); +}; + +#endif // VDR_STREAMDEV_LIVESTREAMER_H diff --git a/plugins/streamdev/streamdev-cvs/server/menuHTTP.c b/plugins/streamdev/streamdev-cvs/server/menuHTTP.c new file mode 100644 index 0000000..84a4fb7 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/menuHTTP.c @@ -0,0 +1,429 @@ +#include <vdr/channels.h> +#include "server/menuHTTP.h" + +//**************************** cChannelIterator ************** +cChannelIterator::cChannelIterator(cChannel *First): channel(First) +{} + +const cChannel* cChannelIterator::Next() +{ + const cChannel *current = channel; + channel = NextChannel(channel); + return current; +} + +//**************************** cListAll ************** +cListAll::cListAll(): cChannelIterator(Channels.First()) +{} + +const cChannel* cListAll::NextChannel(const cChannel *Channel) +{ + if (Channel) + Channel = Channels.Next(Channel); + return Channel; +} + +//**************************** cListChannels ************** +cListChannels::cListChannels(): cChannelIterator(Channels.Get(Channels.GetNextNormal(-1))) +{} + +const cChannel* cListChannels::NextChannel(const cChannel *Channel) +{ + if (Channel) + Channel = Channels.Get(Channels.GetNextNormal(Channel->Index())); + return Channel; +} + +// ********************* cListGroups **************** +cListGroups::cListGroups(): cChannelIterator(Channels.Get(Channels.GetNextGroup(-1))) +{} + +const cChannel* cListGroups::NextChannel(const cChannel *Channel) +{ + if (Channel) + Channel = Channels.Get(Channels.GetNextGroup(Channel->Index())); + return Channel; +} +// +// ********************* cListGroup **************** +cListGroup::cListGroup(const cChannel *Group): cChannelIterator((Group && Group->GroupSep() && Channels.Next(Group) && !Channels.Next(Group)->GroupSep()) ? Channels.Next(Group) : NULL) +{} + +const cChannel* cListGroup::NextChannel(const cChannel *Channel) +{ + if (Channel) + Channel = Channels.Next(Channel); + return (Channel && !Channel->GroupSep()) ? Channel : NULL; +} +// +// ********************* cListTree **************** +cListTree::cListTree(const cChannel *SelectedGroup): cChannelIterator(Channels.Get(Channels.GetNextGroup(-1))) +{ + selectedGroup = SelectedGroup; + currentGroup = Channels.Get(Channels.GetNextGroup(-1)); +} + +const cChannel* cListTree::NextChannel(const cChannel *Channel) +{ + if (currentGroup == selectedGroup) + { + if (Channel) + Channel = Channels.Next(Channel); + if (Channel && Channel->GroupSep()) + currentGroup = Channel; + } + else + { + if (Channel) + Channel = Channels.Get(Channels.GetNextGroup(Channel->Index())); + currentGroup = Channel; + } + return Channel; +} + +// ******************** cChannelList ****************** +cChannelList::cChannelList(cChannelIterator *Iterator) : iterator(Iterator) +{} + +cChannelList::~cChannelList() +{ + delete iterator; +} + +int cChannelList::GetGroupIndex(const cChannel *Group) +{ + int index = 0; + for (int curr = Channels.GetNextGroup(-1); curr >= 0; curr = Channels.GetNextGroup(curr)) + { + if (Channels.Get(curr) == Group) + return index; + index++; + } + return -1; +} + +const cChannel* cChannelList::GetGroup(int Index) +{ + int group = Channels.GetNextGroup(-1); + while (Index-- && group >= 0) + group = Channels.GetNextGroup(group); + return group >= 0 ? Channels.Get(group) : NULL; +} + +// ******************** cHtmlChannelList ****************** +const char* cHtmlChannelList::menu = + "[<a href=\"/\">Home</a> (<a href=\"all.html\" tvid=\"RED\">no script</a>)] " + "[<a href=\"tree.html\" tvid=\"GREEN\">Tree View</a>] " + "[<a href=\"groups.html\" tvid=\"YELLOW\">Groups</a> (<a href=\"groups.m3u\">Playlist</a>)] " + "[<a href=\"channels.html\" tvid=\"BLUE\">Channels</a> (<a href=\"channels.m3u\">Playlist</a>)] "; + +const char* cHtmlChannelList::css = + "<style type=\"text/css\">\n" + "<!--\n" + "a:link, a:visited, a:hover, a:active, a:focus { color:#333399; }\n" + "body { font:100% Verdana, Arial, Helvetica, sans-serif; background-color:#9999FF; margin:1em; }\n" + ".menu { position:fixed; top:0px; left:1em; right:1em; height:3em; text-align:center; background-color:white; border:inset 2px #9999ff; }\n" + "h2 { font-size:150%; margin:0em; padding:0em 1.5em; }\n" + ".contents { margin-top:5em; background-color:white; }\n" + ".group { background:url(data:image/gif;base64,R0lGODdhAQAeAIQeAJub/5yc/6Cf/6Oj/6am/6qq/66u/7Gx/7S0/7i4/7u8/7+//8LD/8bG/8nK/83N/9HQ/9TU/9fX/9va/97e/+Lh/+Xl/+no/+3t//Dw//Pz//b3//v7//7+/////////ywAAAAAAQAeAAAFGCAQCANRGAeSKAvTOA8USRNVWReWaRvXhQA7) repeat-x; border:inset 2px #9999ff; }\n" + ".items { border-top:dashed 1px; margin-top:0px; margin-bottom:0px; padding:0.7em 5em; }\n" + ".apid { padding-left:28px; margin:0.5em; background:url(data:image/gif;base64,R0lGODlhGwASAKEBAAAAAP///////////yH5BAEKAAEALAAAAAAbABIAAAJAjI+pywj5WgPxVAmpNRcHqnGb94FPhE7m+okts7JvusSmSys2iLv6TstldjMfhhUkcXi+zjFUVFo6TiVVij0UAAA7) no-repeat; }\n" + ".dpid { padding-left:28px; margin:0.5em; background:url(data:image/gif;base64,R0lGODlhGwASAKEBAAAAAP///////////yH5BAEKAAEALAAAAAAbABIAAAJFjI+py+0BopwAUoqivRvr83UaZ4RWMnVoBbLZaJbuqcCLGcv0+t5Vvgu2hLrh6pfDzVSpnlGEbAZhnIutZaVmH9yuV1EAADs=) no-repeat; }\n" + "button { width:2em; margin:0.2em 0.5em; vertical-align:top; }\n" + "-->\n" + "</style>"; + +const char* cHtmlChannelList::js = + "<script language=\"JavaScript\">\n" + "<!--\n" + + "function eventTarget(evt) {\n" + " if (!evt) evt = window.event;\n" + " if (evt.target) return evt.target;\n" + " else if (evt.srcElement) return evt.srcElement;\n" + " else return null;\n" + "}\n" + + // toggle visibility of a group + "function clickHandler(evt) {\n" + " var button = eventTarget(evt);\n" + " if (button) {\n" + " var group = document.getElementById('c' + button.id);\n" + " if (group) {\n" + " button.removeChild(button.firstChild);\n" + " if (group.style.display == 'block') {\n" + " button.appendChild(document.createTextNode(\"+\"));\n" + " group.style.display = 'none';\n" + " } else {\n" + " button.appendChild(document.createTextNode(\"-\"));\n" + " group.style.display = 'block';\n" + " }\n" + " }\n" + " }\n" + "}\n" + + // insert a click button infront of each h2 and an id to the corresponding list + "function init() {\n" + " var titles = document.getElementsByTagName('h2');\n" + " for (var i = 0; i < titles.length; i++) {\n" + " var button = document.createElement('button');\n" + " button.id = 'g' + i;\n" + " button.onclick = clickHandler;\n" + " button.appendChild(document.createTextNode('+'));\n" + " titles[i].insertBefore(button, titles[i].firstChild);\n" + " var group = titles[i].nextSibling;\n" + " while (group) {\n" + " if (group.className && group.className == 'items') {\n" + " group.id = 'cg' + i;\n" + " break;\n" + " }\n" + " group = group.nextSibling;\n" + " }\n" + " }\n" + "}\n" + + "window.onload = init;\n" + + // hide lists before the browser renders it + "if (document.styleSheets[0].insertRule)\n" + " document.styleSheets[0].insertRule('.items { display:none }', 0);\n" + "else if (document.styleSheets[0].addRule)\n" + " document.styleSheets[0].addRule('.items', 'display:none');\n" + + "//-->\n" + "</script>"; + + +std::string cHtmlChannelList::StreamTypeMenu() +{ + std::string typeMenu; + typeMenu += (streamType == stTS ? (std::string) "[TS] " : + (std::string) "[<a href=\"/TS/" + self + "\">TS</a>] "); + typeMenu += (streamType == stPS ? (std::string) "[PS] " : + (std::string) "[<a href=\"/PS/" + self + "\">PS</a>] "); + typeMenu += (streamType == stPES ? (std::string) "[PES] " : + (std::string) "[<a href=\"/PES/" + self + "\">PES</a>] "); + typeMenu += (streamType == stES ? (std::string) "[ES] " : + (std::string) "[<a href=\"/ES/" + self + "\">ES</a>] "); + typeMenu += (streamType == stExtern ? (std::string) "[Extern] " : + (std::string) "[<a href=\"/Extern/" + self + "\">Extern</a>] "); + return typeMenu; +} + +cHtmlChannelList::cHtmlChannelList(cChannelIterator *Iterator, eStreamType StreamType, const char *Self, const char *GroupTarget): cChannelList(Iterator) +{ + streamType = StreamType; + self = strdup(Self); + groupTarget = (GroupTarget && *GroupTarget) ? strdup(GroupTarget) : NULL; + htmlState = hsRoot; + current = NULL; +} + +cHtmlChannelList::~cHtmlChannelList() +{ + free((void *) self); + free((void *) groupTarget); +} + +bool cHtmlChannelList::HasNext() +{ + return htmlState != hsPageBottom; +} + +std::string cHtmlChannelList::Next() +{ + switch (htmlState) + { + case hsRoot: + htmlState = hsHtmlHead; + break; + case hsHtmlHead: + htmlState = hsCss; + break; + case hsCss: + htmlState = *self ? hsPageTop : hsJs; + break; + case hsJs: + htmlState = hsPageTop; + break; + case hsPageTop: + current = NextChannel(); + htmlState = current ? (current->GroupSep() ? hsGroupTop : hsPlainTop) : hsPageBottom; + break; + case hsPlainTop: + htmlState = hsPlainItem; + break; + case hsPlainItem: + current = NextChannel(); + htmlState = current && !current->GroupSep() ? hsPlainItem : hsPlainBottom; + break; + case hsPlainBottom: + htmlState = current ? hsGroupTop : hsPageBottom; + break; + case hsGroupTop: + current = NextChannel(); + htmlState = current && !current->GroupSep() ? hsItemsTop : hsGroupBottom; + break; + case hsItemsTop: + htmlState = hsItem; + break; + case hsItem: + current = NextChannel(); + htmlState = current && !current->GroupSep() ? hsItem : hsItemsBottom; + break; + case hsItemsBottom: + htmlState = hsGroupBottom; + break; + case hsGroupBottom: + htmlState = current ? hsGroupTop : hsPageBottom; + break; + case hsPageBottom: + default: + esyslog("streamdev-server cHtmlChannelList: invalid call to Next()"); + break; + } + switch (htmlState) + { + // NOTE: JavaScript requirements: + // Group title is identified by <h2> tag + // Channel list must be a sibling of <h2> with class "items" + case hsHtmlHead: return "<html><head>" + HtmlHead(); + case hsCss: return css; + case hsJs: return js; + case hsPageTop: return "</head><body>" + PageTop() + "<div class=\"contents\">"; + case hsGroupTop: return "<div class=\"group\"><h2>" + GroupTitle() + "</h2>"; + case hsItemsTop: + case hsPlainTop: return "<ol class=\"items\">"; + case hsItem: + case hsPlainItem: return ItemText(); + case hsItemsBottom: + case hsPlainBottom: return "</ol>"; + case hsGroupBottom: return "</div>"; + case hsPageBottom: return "</div>" + PageBottom() + "</body></html>"; + default: return ""; + } +} + +std::string cHtmlChannelList::HtmlHead() +{ + return (std::string) ""; +} + +std::string cHtmlChannelList::PageTop() +{ + return (std::string) "<div class=\"menu\"><div>" + menu + "</div><div>" + StreamTypeMenu() + "</div></div>"; +} + +std::string cHtmlChannelList::PageBottom() +{ + return (std::string) ""; +} + +std::string cHtmlChannelList::GroupTitle() +{ + if (groupTarget) + { + return (std::string) "<a href=\"" + groupTarget + "?group=" + + (const char*) itoa(cChannelList::GetGroupIndex(current)) + + "\">" + current->Name() + "</a>"; + } + else + { + return (std::string) current->Name(); + } +} + +std::string cHtmlChannelList::ItemText() +{ + std::string line; + std::string suffix; + + switch (streamType) { + case stTS: suffix = (std::string) ".ts"; break; + case stPS: suffix = (std::string) ".vob"; break; + // for Network Media Tank + case stPES: suffix = (std::string) ".vdr"; break; + default: suffix = ""; + } + line += (std::string) "<li value=\"" + (const char*) itoa(current->Number()) + "\">"; + line += (std::string) "<a href=\"" + (std::string) current->GetChannelID().ToString() + suffix + "\""; + + // for Network Media Tank + line += (std::string) " vod "; + if (current->Number() < 1000) + line += (std::string) " tvid=\"" + (const char*) itoa(current->Number()) + "\""; + + line += (std::string) ">" + current->Name() + "</a>"; + + int count = 0; + for (int i = 0; current->Apid(i) != 0; ++i, ++count) + ; + for (int i = 0; current->Dpid(i) != 0; ++i, ++count) + ; + + if (count > 1) + { + int index = 1; + for (int i = 0; current->Apid(i) != 0; ++i, ++index) { + line += (std::string) " <a href=\"" + (std::string) current->GetChannelID().ToString() + + "+" + (const char*)itoa(index) + suffix + "\" class=\"apid\" vod>" + current->Alang(i) + "</a>"; + } + for (int i = 0; current->Dpid(i) != 0; ++i, ++index) { + line += (std::string) " <a href=\"" + (std::string) current->GetChannelID().ToString() + + "+" + (const char*)itoa(index) + suffix + "\" class=\"dpid\" vod>" + current->Dlang(i) + "</a>"; + } + } + line += "</li>"; + return line; +} + +// ******************** cM3uChannelList ****************** +cM3uChannelList::cM3uChannelList(cChannelIterator *Iterator, const char* Base) +: cChannelList(Iterator), + m_IConv(cCharSetConv::SystemCharacterTable(), "UTF-8") +{ + base = strdup(Base); + m3uState = msFirst; +} + +cM3uChannelList::~cM3uChannelList() +{ + free(base); +} + +bool cM3uChannelList::HasNext() +{ + return m3uState != msLast; +} + +std::string cM3uChannelList::Next() +{ + if (m3uState == msFirst) + { + m3uState = msContinue; + return "#EXTM3U"; + } + + const cChannel *channel = NextChannel(); + if (!channel) + { + m3uState = msLast; + return ""; + } + + std::string name = (std::string) m_IConv.Convert(channel->Name()); + + if (channel->GroupSep()) + { + return (std::string) "#EXTINF:-1," + name + "\r\n" + + base + "group.m3u?group=" + + (const char*) itoa(cChannelList::GetGroupIndex(channel)); + } + else + { + return (std::string) "#EXTINF:-1," + + (const char*) itoa(channel->Number()) + " " + name + "\r\n" + + base + (std::string) channel->GetChannelID().ToString(); + } +} + diff --git a/plugins/streamdev/streamdev-cvs/server/menuHTTP.h b/plugins/streamdev/streamdev-cvs/server/menuHTTP.h new file mode 100644 index 0000000..cbd7b59 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/menuHTTP.h @@ -0,0 +1,143 @@ +#ifndef VDR_STREAMDEV_SERVERS_MENUHTTP_H +#define VDR_STREAMDEV_SERVERS_MENUHTTP_H + +#include <string> +#include "../common.h" + +class cChannel; + +// ******************** cChannelIterator ****************** +class cChannelIterator +{ + private: + const cChannel *channel; + protected: + virtual const cChannel* NextChannel(const cChannel *Channel) = 0; + public: + const cChannel* Next(); + cChannelIterator(cChannel *First); + virtual ~cChannelIterator() {}; +}; + +class cListAll: public cChannelIterator +{ + protected: + virtual const cChannel* NextChannel(const cChannel *Channel); + public: + cListAll(); + virtual ~cListAll() {}; +}; + +class cListChannels: public cChannelIterator +{ + protected: + virtual const cChannel* NextChannel(const cChannel *Channel); + public: + cListChannels(); + virtual ~cListChannels() {}; +}; + +class cListGroups: public cChannelIterator +{ + protected: + virtual const cChannel* NextChannel(const cChannel *Channel); + public: + cListGroups(); + virtual ~cListGroups() {}; +}; + +class cListGroup: public cChannelIterator +{ + protected: + virtual const cChannel* NextChannel(const cChannel *Channel); + public: + cListGroup(const cChannel *Group); + virtual ~cListGroup() {}; +}; + +class cListTree: public cChannelIterator +{ + private: + const cChannel* selectedGroup; + const cChannel* currentGroup; + protected: + virtual const cChannel* NextChannel(const cChannel *Channel); + public: + cListTree(const cChannel *SelectedGroup); + virtual ~cListTree() {}; +}; + +// ******************** cChannelList ****************** +class cChannelList +{ + private: + cChannelIterator *iterator; + protected: + const cChannel* NextChannel() { return iterator->Next(); } + public: + // Helper which returns the group index + static int GetGroupIndex(const cChannel* Group); + // Helper which returns the group by its index + static const cChannel* GetGroup(int Index); + + virtual std::string HttpHeader() { return "HTTP/1.0 200 OK\r\n"; }; + virtual bool HasNext() = 0; + virtual std::string Next() = 0; + cChannelList(cChannelIterator *Iterator); + virtual ~cChannelList(); +}; + +class cHtmlChannelList: public cChannelList +{ + private: + static const char* menu; + static const char* css; + static const char* js; + + enum eHtmlState { + hsRoot, hsHtmlHead, hsCss, hsJs, hsPageTop, hsPageBottom, + hsGroupTop, hsGroupBottom, + hsPlainTop, hsPlainItem, hsPlainBottom, + hsItemsTop, hsItem, hsItemsBottom + }; + eHtmlState htmlState; + const cChannel *current; + eStreamType streamType; + const char* self; + const char* groupTarget; + + std::string StreamTypeMenu(); + std::string HtmlHead(); + std::string PageTop(); + std::string GroupTitle(); + std::string ItemText(); + std::string PageBottom(); + public: + virtual std::string HttpHeader() { + return cChannelList::HttpHeader() + + "Content-type: text/html; charset=" + + (cCharSetConv::SystemCharacterTable() ? cCharSetConv::SystemCharacterTable() : "UTF-8") + + "\r\n"; + } + virtual bool HasNext(); + virtual std::string Next(); + cHtmlChannelList(cChannelIterator *Iterator, eStreamType StreamType, const char *Self, const char *GroupTarget); + virtual ~cHtmlChannelList(); +}; + +class cM3uChannelList: public cChannelList +{ + private: + char *base; + enum eM3uState { msFirst, msContinue, msLast }; + eM3uState m3uState; + cCharSetConv m_IConv; + public: + virtual std::string HttpHeader() { return cChannelList::HttpHeader() + "Content-type: audio/x-mpegurl; charset=UTF-8\r\n"; }; + virtual bool HasNext(); + virtual std::string Next(); + cM3uChannelList(cChannelIterator *Iterator, const char* Base); + virtual ~cM3uChannelList(); +}; + +#endif diff --git a/plugins/streamdev/streamdev-cvs/server/recplayer.c b/plugins/streamdev/streamdev-cvs/server/recplayer.c new file mode 100644 index 0000000..f45d8c3 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/recplayer.c @@ -0,0 +1,288 @@ +/* + Copyright 2004-2005 Chris Tallon + + This file is part of VOMP. + and adopted for streamdev to play recordings + + VOMP is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + VOMP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with VOMP; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "recplayer.h" + +#define _XOPEN_SOURCE 600 +#include <fcntl.h> + +RecPlayer::RecPlayer(cRecording* rec) +{ + file = NULL; + fileOpen = 0; + lastPosition = 0; + recording = rec; + for(int i = 1; i < 1000; i++) segments[i] = NULL; + + // FIXME find out max file path / name lengths + +#if VDRVERSNUM >= 10703 + indexFile = new cIndexFile(recording->FileName(), false, rec->IsPesRecording()); +#else + indexFile = new cIndexFile(recording->FileName(), false); +#endif + if (!indexFile) esyslog("ERROR: Streamdev: Failed to create indexfile!"); + + scan(); +} + +void RecPlayer::scan() +{ + if (file) fclose(file); + totalLength = 0; + fileOpen = 0; + totalFrames = 0; + + int i = 1; + while(segments[i++]) delete segments[i]; + + char fileName[2048]; + for(i = 1; i < 1000; i++) + { + +#if APIVERSNUM < 10703 + snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), i); + //log->log("RecPlayer", Log::DEBUG, "FILENAME: %s", fileName); + file = fopen(fileName, "r"); +#else + snprintf(fileName, 2047, "%s/%05i.ts", recording->FileName(), i); + file = fopen(fileName, "r"); + if (!file) { + snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), i); + file = fopen(fileName, "r"); + } +#endif + if (!file) break; + + segments[i] = new Segment(); + segments[i]->start = totalLength; + fseek(file, 0, SEEK_END); + totalLength += ftell(file); + totalFrames = indexFile->Last(); + //log->log("RecPlayer", Log::DEBUG, "File %i found, totalLength now %llu, numFrames = %lu", i, totalLength, totalFrames); + segments[i]->end = totalLength; + fclose(file); + } + + file = NULL; +} + +RecPlayer::~RecPlayer() +{ + //log->log("RecPlayer", Log::DEBUG, "destructor"); + int i = 1; + while(segments[i++]) delete segments[i]; + if (file) fclose(file); +} + +int RecPlayer::openFile(int index) +{ + if (file) fclose(file); + + char fileName[2048]; + +#if APIVERSNUM >= 10703 + snprintf(fileName, 2047, "%s/%05i.ts", recording->FileName(), index); + isyslog("openFile called for index %i string:%s", index, fileName); + + file = fopen(fileName, "r"); + if (file) + { + fileOpen = index; + return 1; + } +#endif + + snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), index); + isyslog("openFile called for index %i string:%s", index, fileName); + //log->log("RecPlayer", Log::DEBUG, "openFile called for index %i string:%s", index, fileName); + + file = fopen(fileName, "r"); + if (file) + { + fileOpen = index; + return 1; + } + + //log->log("RecPlayer", Log::DEBUG, "file failed to open"); + fileOpen = 0; + return 0; +} + +uint64_t RecPlayer::getLengthBytes() +{ + return totalLength; +} + +uint32_t RecPlayer::getLengthFrames() +{ + return totalFrames; +} + +unsigned long RecPlayer::getBlock(unsigned char* buffer, uint64_t position, unsigned long amount) +{ + if ((amount > totalLength) || (amount > 500000)) + { + //log->log("RecPlayer", Log::DEBUG, "Amount %lu requested and rejected", amount); + return 0; + } + + if (position >= totalLength) + { + //log->log("RecPlayer", Log::DEBUG, "Client asked for data starting past end of recording!"); + return 0; + } + + if ((position + amount) > totalLength) + { + //log->log("RecPlayer", Log::DEBUG, "Client asked for some data past the end of recording, adjusting amount"); + amount = totalLength - position; + } + + // work out what block position is in + int segmentNumber; + for(segmentNumber = 1; segmentNumber < 1000; segmentNumber++) + { + if ((position >= segments[segmentNumber]->start) && (position < segments[segmentNumber]->end)) break; + // position is in this block + } + + // we could be seeking around + if (segmentNumber != fileOpen) + { + if (!openFile(segmentNumber)) return 0; + } + + uint64_t currentPosition = position; + uint32_t yetToGet = amount; + uint32_t got = 0; + uint32_t getFromThisSegment = 0; + uint32_t filePosition; + + while(got < amount) + { + if (got) + { + // if(got) then we have already got some and we are back around + // advance the file pointer to the next file + if (!openFile(++segmentNumber)) return 0; + } + + // is the request completely in this block? + if ((currentPosition + yetToGet) <= segments[segmentNumber]->end) + getFromThisSegment = yetToGet; + else + getFromThisSegment = segments[segmentNumber]->end - currentPosition; + + filePosition = currentPosition - segments[segmentNumber]->start; + fseek(file, filePosition, SEEK_SET); + if (fread(&buffer[got], getFromThisSegment, 1, file) != 1) return 0; // umm, big problem. + + // Tell linux not to bother keeping the data in the FS cache + posix_fadvise(file->_fileno, filePosition, getFromThisSegment, POSIX_FADV_DONTNEED); + + got += getFromThisSegment; + currentPosition += getFromThisSegment; + yetToGet -= getFromThisSegment; + } + + lastPosition = position; + return got; +} + +uint64_t RecPlayer::getLastPosition() +{ + return lastPosition; +} + +cRecording* RecPlayer::getCurrentRecording() +{ + return recording; +} + +uint64_t RecPlayer::positionFromFrameNumber(uint32_t frameNumber) +{ + if (!indexFile) return 0; + +#if VDRVERSNUM >= 10703 + uint16_t retFileNumber; + off_t retFileOffset; +#else + uchar retFileNumber; + int retFileOffset; +#endif + + if (!indexFile->Get((int)frameNumber, &retFileNumber, &retFileOffset)) + { + return 0; + } + +// log->log("RecPlayer", Log::DEBUG, "FN: %u FO: %i", retFileNumber, retFileOffset); + if (!segments[retFileNumber]) return 0; + uint64_t position = segments[retFileNumber]->start + retFileOffset; +// log->log("RecPlayer", Log::DEBUG, "Pos: %llu", position); + + return position; +} + +uint32_t RecPlayer::frameNumberFromPosition(uint64_t position) +{ + if (!indexFile) return 0; + + if (position >= totalLength) + { + //log->log("RecPlayer", Log::DEBUG, "Client asked for data starting past end of recording!"); + return 0; + } + + uint8_t segmentNumber; + for(segmentNumber = 1; segmentNumber < 255; segmentNumber++) + { + if ((position >= segments[segmentNumber]->start) && (position < segments[segmentNumber]->end)) break; + // position is in this block + } + uint32_t askposition = position - segments[segmentNumber]->start; + return indexFile->Get((int)segmentNumber, askposition); + +} + + +bool RecPlayer::getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength) +{ + // 0 = backwards + // 1 = forwards + + if (!indexFile) return false; + + int iframeLength; + int indexReturnFrameNumber; + + indexReturnFrameNumber = (uint32_t)indexFile->GetNextIFrame(frameNumber, (direction==1 ? true : false), NULL, NULL, &iframeLength); + //log->log("RecPlayer", Log::DEBUG, "GNIF input framenumber:%lu, direction=%lu, output:framenumber=%i, framelength=%i", frameNumber, direction, indexReturnFrameNumber, iframeLength); + + if (indexReturnFrameNumber == -1) return false; + + *rfilePosition = positionFromFrameNumber(indexReturnFrameNumber); + *rframeNumber = (uint32_t)indexReturnFrameNumber; + *rframeLength = (uint32_t)iframeLength; + + return true; +} diff --git a/plugins/streamdev/streamdev-cvs/server/recplayer.h b/plugins/streamdev/streamdev-cvs/server/recplayer.h new file mode 100644 index 0000000..3da6c89 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/recplayer.h @@ -0,0 +1,63 @@ +/* + Copyright 2004-2005 Chris Tallon + + This file is part of VOMP. + + VOMP is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + VOMP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with VOMP; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef RECPLAYER_H +#define RECPLAYER_H + +#include <stdio.h> +#include <vdr/recording.h> + +#include "server/streamer.h" + +class Segment +{ + public: + uint64_t start; + uint64_t end; +}; + +class RecPlayer +{ + public: + RecPlayer(cRecording* rec); + ~RecPlayer(); + uint64_t getLengthBytes(); + uint32_t getLengthFrames(); + unsigned long getBlock(unsigned char* buffer, uint64_t position, unsigned long amount); + int openFile(int index); + uint64_t getLastPosition(); + cRecording* getCurrentRecording(); + void scan(); + uint64_t positionFromFrameNumber(uint32_t frameNumber); + uint32_t frameNumberFromPosition(uint64_t position); + bool getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength); + + private: + cRecording* recording; + cIndexFile* indexFile; + FILE* file; + int fileOpen; + Segment* segments[1000]; + uint64_t totalLength; + uint64_t lastPosition; + uint32_t totalFrames; +}; + +#endif diff --git a/plugins/streamdev/streamdev-cvs/server/server.c b/plugins/streamdev/streamdev-cvs/server/server.c new file mode 100644 index 0000000..1bdb20a --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/server.c @@ -0,0 +1,173 @@ +/* + * $Id: server.c,v 1.10 2009/02/13 10:39:22 schmirl Exp $ + */ + +#include "server/server.h" +#include "server/componentVTP.h" +#include "server/componentHTTP.h" +#include "server/componentIGMP.h" +#include "server/setup.h" + +#include <vdr/tools.h> +#include <tools/select.h> +#include <string.h> +#include <errno.h> + +cSVDRPhosts StreamdevHosts; +char *opt_auth = NULL; +char *opt_remux = NULL; + +cStreamdevServer *cStreamdevServer::m_Instance = NULL; +cList<cServerComponent> cStreamdevServer::m_Servers; +cList<cServerConnection> cStreamdevServer::m_Clients; + +cStreamdevServer::cStreamdevServer(void): + cThread("streamdev server") +{ + Start(); +} + +cStreamdevServer::~cStreamdevServer() +{ + Stop(); +} + +void cStreamdevServer::Initialize(void) +{ + if (m_Instance == NULL) { + if (StreamdevServerSetup.StartVTPServer) Register(new cComponentVTP); + if (StreamdevServerSetup.StartHTTPServer) Register(new cComponentHTTP); + if (StreamdevServerSetup.StartIGMPServer) { + if (strcmp(StreamdevServerSetup.IGMPBindIP, "0.0.0.0") == 0) + esyslog("streamdev-server: Not starting IGMP. IGMP must be bound to a local IP"); + else + Register(new cComponentIGMP); + } + + m_Instance = new cStreamdevServer; + } +} + +void cStreamdevServer::Destruct(void) +{ + DELETENULL(m_Instance); +} + +void cStreamdevServer::Stop(void) +{ + if (Running()) + Cancel(3); +} + +void cStreamdevServer::Register(cServerComponent *Server) +{ + m_Servers.Add(Server); +} + +void cStreamdevServer::Action(void) +{ + /* Initialize Server components, deleting those that failed */ + for (cServerComponent *c = m_Servers.First(); c;) { + cServerComponent *next = m_Servers.Next(c); + if (!c->Initialize()) + m_Servers.Del(c); + c = next; + } + + if (m_Servers.Count() == 0) { + esyslog("ERROR: no streamdev server activated, exiting"); + Cancel(-1); + } + + cTBSelect select; + while (Running()) { + select.Clear(); + + /* Ask all Server components to register to the selector */ + for (cServerComponent *c = m_Servers.First(); c; c = m_Servers.Next(c)) + select.Add(c->Socket(), false); + + /* Ask all Client connections to register to the selector */ + for (cServerConnection *s = m_Clients.First(); s; s = m_Clients.Next(s)) + { + select.Add(s->Socket(), false); + if (s->HasData()) + select.Add(s->Socket(), true); + } + + int sel; + do + { + sel = select.Select(400); + if (sel < 0 && errno == ETIMEDOUT) { + // check for aborted clients + for (cServerConnection *s = m_Clients.First(); s; s = m_Clients.Next(s)) { + if (s->Abort()) + sel = 0; + } + } + } while (sel < 0 && errno == ETIMEDOUT && Running()); + + if (!Running()) + break; + if (sel < 0) { + esyslog("fatal error, server exiting: %m"); + break; + } + + /* Ask all Server components to act on signalled sockets */ + for (cServerComponent *c = m_Servers.First(); c; c = m_Servers.Next(c)){ + if (sel && select.CanRead(c->Socket())) { + cServerConnection *client = c->Accept(); + if (!client) + continue; + m_Clients.Add(client); + + if (m_Clients.Count() > StreamdevServerSetup.MaxClients) { + esyslog("streamdev: too many clients, rejecting %s:%d", + client->RemoteIp().c_str(), client->RemotePort()); + client->Reject(); + } else if (!client->CanAuthenticate() && !StreamdevHosts.Acceptable(client->RemoteIpAddr())) { + esyslog("streamdev: client %s:%d not allowed to connect", + client->RemoteIp().c_str(), client->RemotePort()); + client->Reject(); + } else + client->Welcome(); + } + } + + /* Ask all Client connections to act on signalled sockets */ + for (cServerConnection *s = m_Clients.First(); s;) { + bool result = true; + + if (sel && select.CanWrite(s->Socket())) + result = s->Write(); + + if (sel && result && select.CanRead(s->Socket())) + result = s->Read(); + + result &= !s->Abort(); + + cServerConnection *next = m_Clients.Next(s); + if (!result) { + isyslog("streamdev: closing streamdev connection to %s:%d", + s->RemoteIp().c_str(), s->RemotePort()); + s->Close(); + m_Clients.Del(s); + } + s = next; + } + } + + while (m_Clients.Count() > 0) { + cServerConnection *s = m_Clients.First(); + s->Close(); + m_Clients.Del(s); + } + + while (m_Servers.Count() > 0) { + cServerComponent *c = m_Servers.First(); + c->Destruct(); + m_Servers.Del(c); + } +} diff --git a/plugins/streamdev/streamdev-cvs/server/server.h b/plugins/streamdev/streamdev-cvs/server/server.h new file mode 100644 index 0000000..a44df1c --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/server.h @@ -0,0 +1,49 @@ +/* + * $Id: server.h,v 1.6 2008/10/22 11:59:32 schmirl Exp $ + */ + +#ifndef VDR_STREAMDEV_SERVER_H +#define VDR_STREAMDEV_SERVER_H + +#include <vdr/thread.h> + +#include "server/component.h" +#include "server/connection.h" + +#define DEFAULT_EXTERNREMUX (*AddDirectory(cPlugin::ConfigDirectory(PLUGIN_NAME_I18N), "externremux.sh")) +#define STREAMDEVHOSTSPATH (*AddDirectory(cPlugin::ConfigDirectory(PLUGIN_NAME_I18N), "streamdevhosts.conf")) + +extern char *opt_auth; +extern char *opt_remux; + +class cStreamdevServer: public cThread { +private: + static cStreamdevServer *m_Instance; + static cList<cServerComponent> m_Servers; + static cList<cServerConnection> m_Clients; + +protected: + void Stop(void); + + virtual void Action(void); + + static void Register(cServerComponent *Server); + +public: + cStreamdevServer(void); + virtual ~cStreamdevServer(); + + static void Initialize(void); + static void Destruct(void); + static bool Active(void); +}; + +inline bool cStreamdevServer::Active(void) +{ + return m_Instance != NULL + && m_Instance->m_Clients.Count() > 0; +} + +extern cSVDRPhosts StreamdevHosts; + +#endif // VDR_STREAMDEV_SERVER_H diff --git a/plugins/streamdev/streamdev-cvs/server/setup.c b/plugins/streamdev/streamdev-cvs/server/setup.c new file mode 100644 index 0000000..db709db --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/setup.c @@ -0,0 +1,141 @@ +/* + * $Id: setup.c,v 1.9 2009/10/13 06:38:47 schmirl Exp $ + */ + +#include <vdr/menuitems.h> + +#include "server/setup.h" +#include "server/server.h" + +cStreamdevServerSetup StreamdevServerSetup; + +cStreamdevServerSetup::cStreamdevServerSetup(void) { + MaxClients = 5; + StartVTPServer = true; + VTPServerPort = 2004; + StartHTTPServer = true; + HTTPServerPort = 3000; + HTTPStreamType = stTS; + StartIGMPServer = false; + IGMPClientPort = 1234; + IGMPStreamType = stTS; + SuspendMode = smAlways; + AllowSuspend = false; + strcpy(VTPBindIP, "0.0.0.0"); + strcpy(HTTPBindIP, "0.0.0.0"); + strcpy(IGMPBindIP, "0.0.0.0"); +} + +bool cStreamdevServerSetup::SetupParse(const char *Name, const char *Value) { + if (strcmp(Name, "MaxClients") == 0) MaxClients = atoi(Value); + else if (strcmp(Name, "StartServer") == 0) StartVTPServer = atoi(Value); + else if (strcmp(Name, "ServerPort") == 0) VTPServerPort = atoi(Value); + else if (strcmp(Name, "VTPBindIP") == 0) strcpy(VTPBindIP, Value); + else if (strcmp(Name, "StartHTTPServer") == 0) StartHTTPServer = atoi(Value); + else if (strcmp(Name, "HTTPServerPort") == 0) HTTPServerPort = atoi(Value); + else if (strcmp(Name, "HTTPStreamType") == 0) HTTPStreamType = atoi(Value); + else if (strcmp(Name, "HTTPBindIP") == 0) strcpy(HTTPBindIP, Value); + else if (strcmp(Name, "StartIGMPServer") == 0) StartIGMPServer = atoi(Value); + else if (strcmp(Name, "IGMPClientPort") == 0) IGMPClientPort = atoi(Value); + else if (strcmp(Name, "IGMPStreamType") == 0) IGMPStreamType = atoi(Value); + else if (strcmp(Name, "IGMPBindIP") == 0) strcpy(IGMPBindIP, Value); + else if (strcmp(Name, "SuspendMode") == 0) SuspendMode = atoi(Value); + else if (strcmp(Name, "AllowSuspend") == 0) AllowSuspend = atoi(Value); + else return false; + return true; +} + +const char* cStreamdevServerMenuSetupPage::StreamTypes[st_Count - 1] = { + "TS", + "PES", + "PS", + "ES", + "Extern" +}; + +const char* cStreamdevServerMenuSetupPage::SuspendModes[sm_Count] = { + trNOOP("Offer suspend mode"), + trNOOP("Always suspended"), + trNOOP("Never suspended") +}; + +cStreamdevServerMenuSetupPage::cStreamdevServerMenuSetupPage(void) { + m_NewSetup = StreamdevServerSetup; + + static const char* modes[sm_Count]; + for (int i = 0; i < sm_Count; i++) + modes[i] = tr(SuspendModes[i]); + + AddCategory (tr("Common Settings")); + Add(new cMenuEditIntItem (tr("Maximum Number of Clients"), &m_NewSetup.MaxClients, 0, 100)); + + Add(new cMenuEditStraItem(tr("Suspend behaviour"), &m_NewSetup.SuspendMode, sm_Count, modes)); + Add(new cMenuEditBoolItem(tr("Client may suspend"), &m_NewSetup.AllowSuspend)); + + AddCategory (tr("VDR-to-VDR Server")); + Add(new cMenuEditBoolItem(tr("Start VDR-to-VDR Server"), &m_NewSetup.StartVTPServer)); + Add(new cMenuEditIntItem (tr("VDR-to-VDR Server Port"), &m_NewSetup.VTPServerPort, 0, 65535)); + Add(new cMenuEditIpItem (tr("Bind to IP"), m_NewSetup.VTPBindIP)); + + AddCategory (tr("HTTP Server")); + Add(new cMenuEditBoolItem(tr("Start HTTP Server"), &m_NewSetup.StartHTTPServer)); + Add(new cMenuEditIntItem (tr("HTTP Server Port"), &m_NewSetup.HTTPServerPort, 0, 65535)); + Add(new cMenuEditStraItem(tr("HTTP Streamtype"), &m_NewSetup.HTTPStreamType, st_Count - 1, StreamTypes)); + Add(new cMenuEditIpItem (tr("Bind to IP"), m_NewSetup.HTTPBindIP)); + AddCategory (tr("Multicast Streaming Server")); + Add(new cMenuEditBoolItem(tr("Start IGMP Server"), &m_NewSetup.StartIGMPServer)); + Add(new cMenuEditIntItem (tr("Multicast Client Port"), &m_NewSetup.IGMPClientPort, 0, 65535)); + Add(new cMenuEditStraItem(tr("Multicast Streamtype"), &m_NewSetup.IGMPStreamType, st_Count - 1, StreamTypes)); + Add(new cMenuEditIpItem (tr("Bind to IP"), m_NewSetup.IGMPBindIP)); + SetCurrent(Get(1)); +} + +cStreamdevServerMenuSetupPage::~cStreamdevServerMenuSetupPage() { +} + +void cStreamdevServerMenuSetupPage::AddCategory(const char *Title) { + + cString str = cString::sprintf("--- %s -------------------------------------------------" + "---------------", Title ); + + cOsdItem *item = new cOsdItem(*str); + item->SetSelectable(false); + Add(item); +} + +void cStreamdevServerMenuSetupPage::Store(void) { + bool restart = false; + if (m_NewSetup.StartVTPServer != StreamdevServerSetup.StartVTPServer + || m_NewSetup.VTPServerPort != StreamdevServerSetup.VTPServerPort + || strcmp(m_NewSetup.VTPBindIP, StreamdevServerSetup.VTPBindIP) != 0 + || m_NewSetup.StartHTTPServer != StreamdevServerSetup.StartHTTPServer + || m_NewSetup.HTTPServerPort != StreamdevServerSetup.HTTPServerPort + || strcmp(m_NewSetup.HTTPBindIP, StreamdevServerSetup.HTTPBindIP) != 0 + || m_NewSetup.StartIGMPServer != StreamdevServerSetup.StartIGMPServer + || m_NewSetup.IGMPClientPort != StreamdevServerSetup.IGMPClientPort + || strcmp(m_NewSetup.IGMPBindIP, StreamdevServerSetup.IGMPBindIP) != 0) { + restart = true; + cStreamdevServer::Destruct(); + } + + SetupStore("MaxClients", m_NewSetup.MaxClients); + SetupStore("StartServer", m_NewSetup.StartVTPServer); + SetupStore("ServerPort", m_NewSetup.VTPServerPort); + SetupStore("VTPBindIP", m_NewSetup.VTPBindIP); + SetupStore("StartHTTPServer", m_NewSetup.StartHTTPServer); + SetupStore("HTTPServerPort", m_NewSetup.HTTPServerPort); + SetupStore("HTTPStreamType", m_NewSetup.HTTPStreamType); + SetupStore("HTTPBindIP", m_NewSetup.HTTPBindIP); + SetupStore("StartIGMPServer", m_NewSetup.StartIGMPServer); + SetupStore("IGMPClientPort", m_NewSetup.IGMPClientPort); + SetupStore("IGMPStreamType", m_NewSetup.IGMPStreamType); + SetupStore("IGMPBindIP", m_NewSetup.IGMPBindIP); + SetupStore("SuspendMode", m_NewSetup.SuspendMode); + SetupStore("AllowSuspend", m_NewSetup.AllowSuspend); + + StreamdevServerSetup = m_NewSetup; + + if (restart) + cStreamdevServer::Initialize(); +} + diff --git a/plugins/streamdev/streamdev-cvs/server/setup.h b/plugins/streamdev/streamdev-cvs/server/setup.h new file mode 100644 index 0000000..d22ab34 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/setup.h @@ -0,0 +1,48 @@ +/* + * $Id: setup.h,v 1.3 2009/09/18 10:43:26 schmirl Exp $ + */ + +#ifndef VDR_STREAMDEV_SETUPSERVER_H +#define VDR_STREAMDEV_SETUPSERVER_H + +#include "common.h" + +struct cStreamdevServerSetup { + cStreamdevServerSetup(void); + + bool SetupParse(const char *Name, const char *Value); + + int MaxClients; + int StartVTPServer; + int VTPServerPort; + char VTPBindIP[20]; + int StartHTTPServer; + int HTTPServerPort; + int HTTPStreamType; + char HTTPBindIP[20]; + int StartIGMPServer; + int IGMPClientPort; + int IGMPStreamType; + char IGMPBindIP[20]; + int SuspendMode; + int AllowSuspend; +}; + +extern cStreamdevServerSetup StreamdevServerSetup; + +class cStreamdevServerMenuSetupPage: public cMenuSetupPage { +private: + static const char* StreamTypes[]; + static const char* SuspendModes[]; + cStreamdevServerSetup m_NewSetup; + + void AddCategory(const char *Title); +protected: + virtual void Store(void); + +public: + cStreamdevServerMenuSetupPage(void); + virtual ~cStreamdevServerMenuSetupPage(); +}; + +#endif // VDR_STREAMDEV_SETUPSERVER_H diff --git a/plugins/streamdev/streamdev-cvs/server/streamer.c b/plugins/streamdev/streamdev-cvs/server/streamer.c new file mode 100644 index 0000000..42e7efa --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/streamer.c @@ -0,0 +1,162 @@ +/* + * $Id: streamer.c,v 1.19 2009/06/19 06:32:45 schmirl Exp $ + */ + +#include <vdr/ringbuffer.h> +#include <vdr/device.h> +#include <sys/types.h> +#include <unistd.h> + +#include "server/streamer.h" +#include "server/suspend.h" +#include "server/setup.h" +#include "tools/socket.h" +#include "tools/select.h" +#include "common.h" + +// --- cStreamdevBuffer ------------------------------------------------------- + +cStreamdevBuffer::cStreamdevBuffer(int Size, int Margin, bool Statistics, const char *Description): + cRingBufferLinear(Size, Margin, Statistics, Description) +{ +} + +// --- cStreamdevWriter ------------------------------------------------------- + +cStreamdevWriter::cStreamdevWriter(cTBSocket *Socket, + cStreamdevStreamer *Streamer): + cThread("streamdev-writer"), + m_Streamer(Streamer), + m_Socket(Socket) +{ +} + +cStreamdevWriter::~cStreamdevWriter() +{ + Dprintf("destructing writer\n"); + if (Running()) + Cancel(3); +} + +void cStreamdevWriter::Action(void) +{ + cTBSelect sel; + Dprintf("Writer start\n"); + int max = 0; + uchar *block = NULL; + int count, offset = 0; + + sel.Clear(); + sel.Add(*m_Socket, true); + while (Running()) { + if (block == NULL) { + block = m_Streamer->Get(count); + offset = 0; + } + + if (block != NULL) { + if (sel.Select(15000) == -1) { + esyslog("ERROR: streamdev-server: couldn't send data: %m"); + break; + } + + if (sel.CanWrite(*m_Socket)) { + int written; + int pkgsize = count; + // SOCK_DGRAM indicates multicast + if (m_Socket->Type() == SOCK_DGRAM) { + // don't fragment multicast packets + // max. payload on standard local ethernet is 1416 to 1456 bytes + // and some STBs expect complete TS packets + // so let's always limit to 7 * TS_SIZE = 1316 + if (pkgsize > 7 * TS_SIZE) + pkgsize = 7 * TS_SIZE; + else + pkgsize -= pkgsize % TS_SIZE; + } + if ((written = m_Socket->Write(block + offset, pkgsize)) == -1) { + esyslog("ERROR: streamdev-server: couldn't send %d bytes: %m", pkgsize); + break; + } + + // statistics + if (count > max) + max = count; + + offset += written; + count -= written; + + // less than one TS packet left: + // delete what we've written so far and get next chunk + if (count < TS_SIZE) { + m_Streamer->Del(offset); + block = NULL; + } + } + } + } + Dprintf("Max. Transmit Blocksize was: %d\n", max); +} + +// --- cStreamdevStreamer ----------------------------------------------------- + +cStreamdevStreamer::cStreamdevStreamer(const char *Name): + cThread(Name), + m_Writer(NULL), + m_RingBuffer(new cStreamdevBuffer(STREAMERBUFSIZE, TS_SIZE * 2, + true, "streamdev-streamer")), + m_SendBuffer(new cStreamdevBuffer(WRITERBUFSIZE, TS_SIZE * 2)) +{ + m_RingBuffer->SetTimeouts(0, 100); + m_SendBuffer->SetTimeouts(100, 100); +} + +cStreamdevStreamer::~cStreamdevStreamer() +{ + Dprintf("Desctructing streamer\n"); + delete m_RingBuffer; + delete m_SendBuffer; +} + +void cStreamdevStreamer::Start(cTBSocket *Socket) +{ + Dprintf("start streamer\n"); + m_Writer = new cStreamdevWriter(Socket, this); + Attach(); +} + +void cStreamdevStreamer::Activate(bool On) +{ + if (On && !Active()) { + Dprintf("activate streamer\n"); + m_Writer->Start(); + cThread::Start(); + } +} + +void cStreamdevStreamer::Stop(void) +{ + if (Running()) { + Dprintf("stopping streamer\n"); + Cancel(3); + } + if (m_Writer) { + Detach(); + DELETENULL(m_Writer); + } +} + +void cStreamdevStreamer::Action(void) +{ + while (Running()) { + int got; + uchar *block = m_RingBuffer->Get(got); + + if (block) { + int count = Put(block, got); + if (count) + m_RingBuffer->Del(count); + } + } +} + diff --git a/plugins/streamdev/streamdev-cvs/server/streamer.h b/plugins/streamdev/streamdev-cvs/server/streamer.h new file mode 100644 index 0000000..6561bc2 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/streamer.h @@ -0,0 +1,102 @@ +/* + * $Id: streamer.h,v 1.11 2009/06/19 06:32:45 schmirl Exp $ + */ + +#ifndef VDR_STREAMDEV_STREAMER_H +#define VDR_STREAMDEV_STREAMER_H + +#include <vdr/thread.h> +#include <vdr/ringbuffer.h> +#include <vdr/tools.h> + +class cTBSocket; +class cStreamdevStreamer; + +#ifndef TS_SIZE +#define TS_SIZE 188 +#endif + +#define STREAMERBUFSIZE (20000 * TS_SIZE) +#define WRITERBUFSIZE (5000 * TS_SIZE) + +// --- cStreamdevBuffer ------------------------------------------------------- + +class cStreamdevBuffer: public cRingBufferLinear { +public: + // make public + void WaitForPut(void) { cRingBuffer::WaitForPut(); } + // Always write complete TS packets + // (assumes Count is a multiple of TS_SIZE) + int PutTS(const uchar *Data, int Count); + cStreamdevBuffer(int Size, int Margin = 0, bool Statistics = false, const char *Description = NULL); +}; + +inline int cStreamdevBuffer::PutTS(const uchar *Data, int Count) +{ + int free = Free(); + if (free < Count) + Count = free; + + Count -= Count % TS_SIZE; + if (Count) + Count = Put(Data, Count); + else + WaitForPut(); + return Count; +} + +// --- cStreamdevWriter ------------------------------------------------------- + +class cStreamdevWriter: public cThread { +private: + cStreamdevStreamer *m_Streamer; + cTBSocket *m_Socket; + +protected: + virtual void Action(void); + +public: + cStreamdevWriter(cTBSocket *Socket, cStreamdevStreamer *Streamer); + virtual ~cStreamdevWriter(); +}; + +// --- cStreamdevStreamer ----------------------------------------------------- + +class cStreamdevStreamer: public cThread { +private: + cStreamdevWriter *m_Writer; + cStreamdevBuffer *m_RingBuffer; + cStreamdevBuffer *m_SendBuffer; + +protected: + virtual void Action(void); + + bool IsRunning(void) const { return m_Writer; } + +public: + cStreamdevStreamer(const char *Name); + virtual ~cStreamdevStreamer(); + + virtual void Start(cTBSocket *Socket); + virtual void Stop(void); + bool Abort(void); + + void Activate(bool On); + int Receive(uchar *Data, int Length) { return m_RingBuffer->PutTS(Data, Length); } + void ReportOverflow(int Bytes) { m_RingBuffer->ReportOverflow(Bytes); } + + virtual int Put(const uchar *Data, int Count) { return m_SendBuffer->PutTS(Data, Count); } + virtual uchar *Get(int &Count) { return m_SendBuffer->Get(Count); } + virtual void Del(int Count) { m_SendBuffer->Del(Count); } + + virtual void Detach(void) {} + virtual void Attach(void) {} +}; + +inline bool cStreamdevStreamer::Abort(void) +{ + return Active() && !m_Writer->Active(); +} + +#endif // VDR_STREAMDEV_STREAMER_H + diff --git a/plugins/streamdev/streamdev-cvs/server/suspend.c b/plugins/streamdev/streamdev-cvs/server/suspend.c new file mode 100644 index 0000000..b6e1382 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/suspend.c @@ -0,0 +1,57 @@ +/* + * $Id: suspend.c,v 1.3 2008/10/22 11:59:32 schmirl Exp $ + */ + +#include "server/suspend.h" +#include "server/suspend.dat" +#include "common.h" + +cSuspendLive::cSuspendLive(void) + : cThread("Streamdev: server suspend") +{ +} + +cSuspendLive::~cSuspendLive() { + Stop(); + Detach(); +} + +void cSuspendLive::Activate(bool On) { + Dprintf("Activate cSuspendLive %d\n", On); + if (On) + Start(); + else + Stop(); +} + +void cSuspendLive::Stop(void) { + if (Running()) + Cancel(3); +} + +void cSuspendLive::Action(void) { + while (Running()) { + DeviceStillPicture(suspend_mpg, sizeof(suspend_mpg)); + cCondWait::SleepMs(100); + } +} + +bool cSuspendCtl::m_Active = false; + +cSuspendCtl::cSuspendCtl(void): + cControl(m_Suspend = new cSuspendLive) { + m_Active = true; +} + +cSuspendCtl::~cSuspendCtl() { + m_Active = false; + DELETENULL(m_Suspend); +} + +eOSState cSuspendCtl::ProcessKey(eKeys Key) { + if (!m_Suspend->Active() || Key == kBack) { + DELETENULL(m_Suspend); + return osEnd; + } + return osContinue; +} diff --git a/plugins/streamdev/streamdev-cvs/server/suspend.dat b/plugins/streamdev/streamdev-cvs/server/suspend.dat new file mode 100644 index 0000000..7b1b890 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/suspend.dat @@ -0,0 +1,1206 @@ +const unsigned char suspend_mpg[] = { + 0x00, 0x00, 0x01, 0xb3, 0x2d, 0x02, 0x40, 0x83, 0x02, 0xd0, 0x20, 0xa0, + 0x00, 0x00, 0x01, 0xb2, 0x4d, 0x50, 0x45, 0x47, 0x0a, 0x00, 0x00, 0x01, + 0xb8, 0x00, 0x08, 0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0a, 0xbb, + 0x58, 0x00, 0x00, 0x01, 0x01, 0x52, 0x97, 0xe6, 0x54, 0xa5, 0x2f, 0xdc, + 0xaf, 0x9a, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x45, 0xe2, + 0x2c, 0x52, 0x67, 0x98, 0x6c, 0x9a, 0x2e, 0xb9, 0x9e, 0xb4, 0x6c, 0xdd, + 0x34, 0x8c, 0x8b, 0xab, 0xa6, 0x61, 0xb5, 0xbd, 0x33, 0x8d, 0xa7, 0x22, + 0x6f, 0x75, 0x74, 0xcc, 0x36, 0xb4, 0x6c, 0xfd, 0x34, 0x64, 0x4d, 0xee, + 0x91, 0xb3, 0xf4, 0xd6, 0xf4, 0xcd, 0xd3, 0x46, 0x44, 0xde, 0xea, 0x1b, + 0x37, 0x4d, 0x77, 0x4c, 0xdd, 0x34, 0xe4, 0x43, 0xdd, 0x43, 0x66, 0xe9, + 0xad, 0x1b, 0x38, 0xda, 0x32, 0x26, 0xf7, 0x4f, 0x4c, 0xe3, 0x6b, 0x46, + 0xce, 0x36, 0x8c, 0x89, 0xbd, 0xd3, 0xd3, 0x3f, 0x4d, 0x68, 0xd9, 0x86, + 0xd3, 0x91, 0x37, 0xba, 0x86, 0xcc, 0x36, 0xbb, 0xa6, 0x6e, 0x9a, 0x32, + 0x26, 0xf7, 0x4f, 0x4c, 0xfd, 0x35, 0xbd, 0x33, 0xf4, 0xd1, 0x91, 0x37, + 0xba, 0x46, 0xcf, 0xd3, 0x5a, 0x36, 0x7e, 0x9a, 0x72, 0x26, 0xf7, 0x4f, + 0x4c, 0xfd, 0x35, 0xa3, 0x66, 0x1b, 0x46, 0x44, 0xde, 0xea, 0xe9, 0x9c, + 0x6d, 0x67, 0x4c, 0xfd, 0x34, 0x64, 0x43, 0xdd, 0x43, 0x66, 0xe9, 0xad, + 0xe9, 0x9c, 0x6d, 0x39, 0x13, 0x7b, 0xa7, 0xa6, 0x7e, 0x9a, 0xd1, 0xb3, + 0x0d, 0xa3, 0x22, 0x6f, 0x75, 0x74, 0xcd, 0xd3, 0x5c, 0x36, 0x61, 0xb4, + 0x0c, 0x9b, 0xdd, 0x43, 0x66, 0x1b, 0x5c, 0x36, 0x6e, 0x9a, 0x72, 0x26, + 0xf7, 0x50, 0xd9, 0xba, 0x6b, 0x86, 0xcd, 0xd3, 0x46, 0x44, 0xde, 0xea, + 0x1b, 0x30, 0xda, 0xd1, 0xb3, 0x8d, 0xa3, 0x22, 0x6f, 0x74, 0x8d, 0x9c, + 0x6d, 0x68, 0xd9, 0xba, 0x69, 0xc8, 0x87, 0xba, 0xba, 0x66, 0x1b, 0x5c, + 0x36, 0x6e, 0x9a, 0x32, 0x26, 0xf7, 0x57, 0x4c, 0xdd, 0x35, 0xdd, 0x33, + 0x74, 0xd1, 0x91, 0x37, 0xba, 0x9e, 0x6e, 0x9a, 0xde, 0x99, 0xfa, 0x68, + 0xc8, 0x9b, 0xdd, 0x23, 0x67, 0xe9, 0xad, 0x79, 0xc6, 0xd3, 0x91, 0x37, + 0xba, 0x7a, 0x67, 0xe9, 0xad, 0x1b, 0x30, 0xda, 0x32, 0x26, 0xf7, 0x50, + 0xd9, 0xba, 0x6b, 0x7a, 0x67, 0x1b, 0x46, 0x44, 0x3d, 0xd4, 0x36, 0x61, + 0xb5, 0xbd, 0x33, 0xbd, 0x39, 0x13, 0x7b, 0xa7, 0xa6, 0x71, 0xb5, 0xbf, + 0xcc, 0x36, 0x8c, 0x89, 0xbd, 0xd4, 0xf3, 0x0d, 0xae, 0xe9, 0x9b, 0xa6, + 0x9c, 0x89, 0xbd, 0xd4, 0xf3, 0x74, 0xd7, 0x74, 0xcc, 0x36, 0x8c, 0x89, + 0xbd, 0xd2, 0x36, 0x7e, 0x9a, 0xd1, 0xb3, 0x0d, 0xa3, 0x22, 0x6f, 0x75, + 0x74, 0xcf, 0xd3, 0x5b, 0xd3, 0x37, 0x4d, 0x19, 0x10, 0xf7, 0x53, 0xcc, + 0x36, 0xb7, 0xa6, 0x71, 0xb4, 0xe4, 0x4d, 0xee, 0x91, 0xb3, 0xf4, 0xd6, + 0x8d, 0x98, 0x6d, 0x03, 0x26, 0xf0, 0x00, 0x00, 0x01, 0x02, 0x2b, 0xf9, + 0x95, 0x29, 0x4b, 0xf7, 0x2b, 0xe6, 0xae, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x45, 0xe2, 0x2c, 0x52, 0x71, 0xb3, 0x74, 0xc9, 0xa2, + 0xeb, 0x79, 0x86, 0xd6, 0xf4, 0xcc, 0x36, 0x9c, 0x88, 0xba, 0xba, 0x66, + 0xe9, 0xad, 0x1b, 0x38, 0xda, 0x32, 0x26, 0xf7, 0x50, 0xd9, 0xba, 0x6b, + 0x7a, 0x67, 0xe9, 0xa7, 0x22, 0x6f, 0x74, 0x8d, 0x9f, 0xa6, 0xb4, 0x6c, + 0xc3, 0x68, 0xc8, 0x9b, 0xdd, 0x5d, 0x33, 0x0d, 0xae, 0x1b, 0x37, 0x4d, + 0x19, 0x13, 0x7b, 0xab, 0xa6, 0x61, 0xb5, 0xc3, 0x66, 0xe9, 0xa3, 0x22, + 0x1e, 0xea, 0x1b, 0x30, 0xda, 0xde, 0x99, 0xfa, 0x69, 0xc8, 0x9b, 0xdd, + 0x23, 0x67, 0x1b, 0x5b, 0xd3, 0x37, 0x4d, 0x19, 0x13, 0x7b, 0xa8, 0x6c, + 0xc3, 0x6b, 0x86, 0xcd, 0xd3, 0x46, 0x44, 0xde, 0xea, 0xe9, 0x9b, 0xa6, + 0xb7, 0xa6, 0x7e, 0x9a, 0x72, 0x26, 0xf7, 0x4f, 0x4c, 0xfd, 0x35, 0xa3, + 0x67, 0xe9, 0xa3, 0x22, 0x6f, 0x74, 0xf4, 0xce, 0x36, 0xb7, 0xa6, 0x67, + 0xa3, 0x22, 0x6f, 0x75, 0x74, 0xcc, 0xf5, 0xc3, 0x66, 0x1b, 0x4e, 0x44, + 0xde, 0xea, 0xe9, 0x9b, 0xa6, 0xb4, 0x6c, 0xfd, 0x34, 0x0c, 0x87, 0xba, + 0x46, 0xcf, 0xd3, 0x5a, 0x36, 0x61, 0xb4, 0x64, 0x4d, 0xee, 0xa1, 0xb3, + 0xf4, 0xd6, 0x8d, 0x9b, 0xa6, 0x9c, 0x89, 0xbd, 0xd5, 0xd3, 0x30, 0xda, + 0xee, 0x99, 0x86, 0xd1, 0x91, 0x37, 0xba, 0x86, 0xcc, 0x36, 0xb8, 0x6c, + 0xc3, 0x68, 0xc8, 0x9b, 0xdd, 0x5d, 0x33, 0x74, 0xd6, 0x8d, 0x9c, 0x6d, + 0x39, 0x13, 0x7b, 0xa4, 0x6c, 0xe3, 0x6b, 0x7a, 0x66, 0xe9, 0xa3, 0x22, + 0x6f, 0x75, 0x3c, 0xdd, 0x35, 0xdd, 0x33, 0x0d, 0xa3, 0x22, 0x1e, 0xea, + 0xe9, 0x9b, 0xa6, 0xb8, 0x6c, 0xdd, 0x34, 0xe4, 0x4d, 0xee, 0xa1, 0xb3, + 0x0d, 0xad, 0x1b, 0x3f, 0x4d, 0x19, 0x13, 0x7b, 0xa4, 0x6c, 0xe3, 0x6b, + 0x7a, 0x67, 0x1b, 0x46, 0x44, 0xde, 0xe9, 0x1b, 0x38, 0xda, 0xd1, 0xb3, + 0x3d, 0x39, 0x13, 0x7b, 0xab, 0xa6, 0x61, 0xb5, 0xdd, 0x33, 0x74, 0xd1, + 0x91, 0x37, 0xba, 0x5e, 0x7e, 0x9a, 0xee, 0x99, 0x9e, 0x8c, 0x88, 0x7b, + 0xa4, 0x6c, 0xfd, 0x35, 0xbf, 0xcc, 0x36, 0x9c, 0x89, 0xbd, 0xd5, 0xd3, + 0x33, 0xd7, 0x0d, 0x9b, 0xa6, 0x8c, 0x89, 0xbd, 0xd5, 0xd3, 0x37, 0x4d, + 0x68, 0xd9, 0xc6, 0xd1, 0x91, 0x37, 0xba, 0xba, 0x66, 0x1b, 0x5b, 0xd3, + 0x33, 0xd3, 0x91, 0x37, 0xba, 0x86, 0xce, 0x36, 0xb7, 0xa6, 0x61, 0xb4, + 0x64, 0x4d, 0xee, 0xa1, 0xb3, 0x74, 0xd7, 0x0d, 0x98, 0x6d, 0x19, 0x13, + 0x7b, 0xab, 0xa6, 0x6e, 0x9a, 0xd1, 0xb3, 0xf4, 0xd3, 0x91, 0x0f, 0x74, + 0x8d, 0x9f, 0xa6, 0xb4, 0x6c, 0xe3, 0x68, 0xc8, 0x9b, 0xdd, 0x23, 0x66, + 0x1b, 0x5c, 0x36, 0x6e, 0x9a, 0x32, 0x26, 0xf7, 0x57, 0x4c, 0xc3, 0x6b, + 0xba, 0x66, 0x1b, 0x4e, 0x44, 0xde, 0x00, 0x00, 0x01, 0x03, 0x2b, 0xf9, + 0x95, 0x29, 0x4b, 0xf7, 0x2b, 0xe6, 0xae, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x28, 0xf3, 0x3c, 0x9b, 0xf8, 0xb5, 0xa5, 0x67, 0x4c, + 0xe3, 0x64, 0xd1, 0x75, 0x3c, 0xc3, 0x6b, 0x7a, 0x66, 0x1b, 0x4e, 0x44, + 0x3d, 0xd5, 0xd3, 0x30, 0xda, 0xe1, 0xb3, 0x74, 0xd1, 0x91, 0x37, 0xba, + 0x86, 0xcc, 0x36, 0xb4, 0x6c, 0xfd, 0x34, 0x64, 0x4d, 0xee, 0x9e, 0x99, + 0xc6, 0xd6, 0x8d, 0x9c, 0x6d, 0x38, 0xa9, 0xbd, 0xd2, 0x36, 0x71, 0xb5, + 0xbd, 0x33, 0x0d, 0xa3, 0x22, 0x6f, 0x75, 0x74, 0xcc, 0x36, 0xbb, 0xa6, + 0x6e, 0x9a, 0x31, 0x53, 0x7b, 0xab, 0xa6, 0x6e, 0x9a, 0xd1, 0xb3, 0x8d, + 0xa7, 0x15, 0x37, 0xba, 0xba, 0x66, 0x1b, 0x5a, 0x36, 0x6e, 0x9a, 0x06, + 0x43, 0xdd, 0x5d, 0x33, 0x0d, 0xae, 0x1b, 0x30, 0xda, 0x72, 0x26, 0xf7, + 0x57, 0x4c, 0xc3, 0x6b, 0x86, 0xcd, 0xd3, 0x46, 0x44, 0xde, 0xea, 0x1b, + 0x37, 0x4d, 0x6f, 0x4c, 0xe3, 0x68, 0x19, 0x37, 0xba, 0x86, 0xcd, 0xd3, + 0x5a, 0x36, 0x61, 0xb4, 0xe4, 0x4d, 0xee, 0xae, 0x99, 0x86, 0xd7, 0x74, + 0xcd, 0xd3, 0x46, 0x44, 0xde, 0xea, 0x1b, 0x30, 0xda, 0xde, 0x99, 0xfa, + 0x68, 0xc8, 0x9b, 0xdd, 0x5d, 0x33, 0x0d, 0xad, 0x1b, 0x3f, 0x4d, 0x23, + 0x26, 0xf7, 0x4f, 0x4c, 0xfd, 0x35, 0xbd, 0x33, 0x74, 0xd1, 0x91, 0x0f, + 0x75, 0x0d, 0x9f, 0xa6, 0xb7, 0xa6, 0x71, 0xb4, 0x64, 0x4d, 0xee, 0x9e, + 0x99, 0xba, 0x6b, 0x86, 0xcd, 0xd3, 0x4e, 0x44, 0xde, 0xea, 0x1b, 0x37, + 0x4d, 0x6f, 0x4c, 0xfd, 0x34, 0x64, 0x4d, 0xee, 0xa1, 0xb3, 0x0d, 0xad, + 0x1b, 0x38, 0xda, 0x32, 0x26, 0xf7, 0x4f, 0x4c, 0xe3, 0x6b, 0x46, 0xcd, + 0xd3, 0x4e, 0x44, 0xde, 0xea, 0xe9, 0x98, 0x6d, 0x70, 0xd9, 0xba, 0x68, + 0xc8, 0x87, 0xba, 0x86, 0xcd, 0xd3, 0x5d, 0xd3, 0x30, 0xda, 0x32, 0x26, + 0xf7, 0x57, 0x4c, 0xdd, 0x35, 0xa3, 0x67, 0x1b, 0x4e, 0x44, 0xde, 0xe9, + 0x1b, 0x38, 0xda, 0xde, 0x99, 0x86, 0xd1, 0x91, 0x37, 0xba, 0xba, 0x66, + 0x1b, 0x5c, 0x36, 0x6e, 0x9a, 0x32, 0x26, 0xf7, 0x57, 0x4c, 0xdd, 0x35, + 0xc3, 0x66, 0xe9, 0xa7, 0x22, 0x6f, 0x75, 0x74, 0xcc, 0x36, 0xb5, 0xe6, + 0xe9, 0xa3, 0x22, 0x1e, 0xea, 0xe9, 0x99, 0xeb, 0xba, 0x66, 0xe9, 0xa3, + 0x22, 0x6f, 0x75, 0x0d, 0x98, 0x6d, 0x70, 0xd9, 0x86, 0xd3, 0x91, 0x37, + 0xba, 0x86, 0xcd, 0xd3, 0x5a, 0x36, 0x61, 0xb4, 0x64, 0x4e, 0xea, 0xe9, + 0x9b, 0xa6, 0xbb, 0xa6, 0x6e, 0x9a, 0x32, 0x26, 0xf7, 0x53, 0xcc, 0x36, + 0xbb, 0xa6, 0x6e, 0x9a, 0x72, 0x26, 0xf7, 0x57, 0x4c, 0xc3, 0x6b, 0x46, + 0xcf, 0xd3, 0x46, 0x44, 0xde, 0xea, 0x1b, 0x37, 0x4d, 0x6f, 0x4c, 0xfd, + 0x34, 0x64, 0x43, 0xdd, 0x23, 0x67, 0xe9, 0xad, 0xe9, 0x98, 0x6d, 0x39, + 0x13, 0x7b, 0xab, 0xa6, 0x6e, 0x9a, 0xe1, 0xb3, 0x74, 0xd1, 0x91, 0x37, + 0xba, 0xba, 0x66, 0xe9, 0xad, 0x1b, 0x3f, 0x4d, 0x19, 0x13, 0x7b, 0xa4, + 0x6c, 0xfd, 0x35, 0xa3, 0x67, 0x1b, 0x4e, 0x44, 0xde, 0xe9, 0xe9, 0x9c, + 0x6d, 0x6f, 0x4c, 0xdd, 0x34, 0x64, 0x4d, 0xe0, 0x00, 0x00, 0x01, 0x04, + 0x2b, 0xf9, 0x95, 0x22, 0xf3, 0x74, 0xdf, 0xb8, 0xb5, 0xf3, 0x56, 0x91, + 0x7a, 0xce, 0x99, 0xc6, 0xc8, 0x64, 0x5d, 0x23, 0x67, 0xe9, 0xad, 0xe9, + 0x9b, 0xa6, 0x9c, 0x89, 0xdd, 0xe0, 0x19, 0xf4, 0x80, 0x54, 0x5f, 0x41, + 0x2c, 0x20, 0x61, 0x7f, 0x14, 0x4b, 0x25, 0xa5, 0x05, 0x27, 0x25, 0x25, + 0x20, 0x95, 0xd0, 0x58, 0xd2, 0xd1, 0xba, 0x33, 0x73, 0x4d, 0xb1, 0xe0, + 0x0c, 0x00, 0x60, 0x1a, 0x8d, 0xf2, 0x40, 0xaf, 0xc9, 0xcf, 0xbe, 0x01, + 0x88, 0x0e, 0xce, 0xe6, 0xe0, 0x0a, 0x6d, 0x1d, 0x32, 0x00, 0x36, 0xf8, + 0xa2, 0x62, 0x40, 0x74, 0xb0, 0x42, 0x08, 0x49, 0x29, 0x0f, 0xbe, 0x00, + 0xa3, 0xe1, 0xa1, 0x8c, 0xff, 0xff, 0x90, 0x8e, 0x06, 0x06, 0xba, 0xd2, + 0x57, 0xbe, 0x6c, 0x01, 0xa2, 0x51, 0xd0, 0x58, 0x67, 0xdb, 0xfd, 0xc9, + 0x79, 0xf6, 0xea, 0x3b, 0x91, 0x2f, 0x54, 0x03, 0x1c, 0x9d, 0x90, 0x5a, + 0x3f, 0xc6, 0x75, 0xde, 0xc0, 0x13, 0x10, 0xd2, 0x94, 0x16, 0x50, 0x6f, + 0x1a, 0x1a, 0x05, 0x0b, 0x49, 0x30, 0x37, 0xb3, 0x3f, 0x43, 0x74, 0x24, + 0x90, 0x84, 0x01, 0x72, 0xba, 0x0b, 0xe3, 0x30, 0xcd, 0xd2, 0xc6, 0x59, + 0x7a, 0x6d, 0x20, 0x21, 0xc0, 0x07, 0xc0, 0x19, 0xe4, 0xac, 0x60, 0x09, + 0xca, 0x18, 0x49, 0xed, 0x89, 0xa4, 0x27, 0xea, 0x43, 0x30, 0xd2, 0xc3, + 0x3f, 0x7e, 0xa5, 0x13, 0x31, 0xf0, 0x7b, 0xbc, 0x03, 0x10, 0x29, 0x88, + 0x40, 0x55, 0x39, 0x19, 0x90, 0x90, 0x94, 0x08, 0xe4, 0x0f, 0x72, 0x00, + 0xe8, 0x34, 0x0c, 0x86, 0x23, 0x9a, 0x77, 0xba, 0xba, 0x66, 0xe9, 0xad, + 0xe9, 0x9f, 0xa6, 0xec, 0xc8, 0xb9, 0xde, 0xe9, 0xe9, 0xb4, 0x13, 0x12, + 0x1a, 0x02, 0x14, 0x96, 0x80, 0x28, 0xdb, 0x06, 0x27, 0x20, 0xb2, 0x19, + 0x78, 0x68, 0x66, 0xfb, 0xf2, 0x59, 0x45, 0x6c, 0x94, 0x24, 0xb2, 0xd0, + 0xcc, 0x96, 0xf9, 0x08, 0xfd, 0x69, 0xe9, 0x47, 0x45, 0x79, 0x0d, 0x25, + 0xfc, 0x82, 0x83, 0x3e, 0xc1, 0xa4, 0xd2, 0xd0, 0x18, 0x57, 0x7d, 0xbe, + 0xe9, 0xeb, 0x2d, 0x19, 0x3b, 0xe4, 0xf0, 0xc2, 0x86, 0x86, 0x8c, 0x6e, + 0xf9, 0x1e, 0xf2, 0xc8, 0x41, 0x40, 0x3a, 0x0c, 0xdb, 0x29, 0x05, 0x63, + 0x7b, 0x87, 0xff, 0xd7, 0xff, 0x37, 0xf5, 0xfe, 0xbf, 0x78, 0x80, 0x0f, + 0x80, 0x34, 0x40, 0x6e, 0x03, 0x25, 0xa0, 0x6e, 0x76, 0x46, 0x1f, 0xf1, + 0xbc, 0x7e, 0xbd, 0x10, 0x10, 0x80, 0xef, 0x86, 0x32, 0x10, 0x7f, 0x27, + 0x55, 0x3d, 0x0c, 0x02, 0x02, 0x94, 0x35, 0x21, 0xa5, 0xf6, 0x67, 0xe5, + 0xa5, 0x0e, 0x02, 0x20, 0xce, 0x52, 0x1c, 0x5b, 0x55, 0x80, 0x1d, 0x13, + 0x07, 0x16, 0x09, 0x3f, 0x81, 0xa0, 0x9b, 0xfb, 0x64, 0x09, 0x4a, 0xdc, + 0x65, 0xeb, 0x50, 0x03, 0xb0, 0x1d, 0x97, 0x9c, 0x96, 0x56, 0x5a, 0x73, + 0x12, 0xbf, 0x5a, 0x3f, 0xdd, 0x60, 0x65, 0x6b, 0xc7, 0xaa, 0xf0, 0x53, + 0x8a, 0x2b, 0x3a, 0x7a, 0x36, 0x38, 0xe2, 0x05, 0xe9, 0xa0, 0x96, 0x5f, + 0x57, 0xe1, 0x75, 0x9f, 0x9d, 0x01, 0xb2, 0x05, 0x80, 0x46, 0x92, 0x0d, + 0x6f, 0x21, 0x15, 0xc0, 0x98, 0x24, 0xfe, 0x96, 0x00, 0x31, 0xbb, 0x24, + 0xb0, 0xd0, 0x26, 0x80, 0x08, 0xb8, 0x00, 0xc6, 0x80, 0x64, 0x37, 0x1a, + 0x8e, 0x1d, 0xf1, 0x17, 0xdb, 0x0d, 0x28, 0x7e, 0x0a, 0xb1, 0x18, 0x9b, + 0xce, 0x1a, 0x05, 0x06, 0xaf, 0x06, 0x96, 0x80, 0xa6, 0xaf, 0x46, 0x42, + 0x3e, 0x41, 0x7b, 0xef, 0xce, 0xe3, 0x52, 0xdc, 0x9e, 0x78, 0xab, 0x22, + 0x08, 0x60, 0x12, 0xe6, 0x17, 0xbb, 0xe2, 0x25, 0xc5, 0x2c, 0xac, 0x5a, + 0x3e, 0xeb, 0xde, 0xe6, 0xcc, 0x29, 0xae, 0x8e, 0xf8, 0xfb, 0xd2, 0x03, + 0x62, 0x85, 0x80, 0x46, 0x92, 0x0d, 0x00, 0x6c, 0x50, 0xee, 0x01, 0x1a, + 0x48, 0x37, 0xad, 0x1a, 0x43, 0x43, 0xa0, 0x97, 0x94, 0x3d, 0x0b, 0xf8, + 0xce, 0x49, 0x02, 0x34, 0x25, 0x93, 0x40, 0x25, 0x40, 0x04, 0x5c, 0x00, + 0x63, 0x69, 0xc6, 0x63, 0x70, 0x55, 0x8b, 0x26, 0xfc, 0x98, 0x0d, 0x8a, + 0x1d, 0xc0, 0x23, 0x49, 0x06, 0x80, 0x36, 0x28, 0x77, 0x00, 0x8d, 0x24, + 0x1b, 0xd7, 0x92, 0xc8, 0x60, 0x12, 0xa0, 0x02, 0x2e, 0x00, 0x31, 0x89, + 0x2c, 0x86, 0x01, 0x2a, 0x00, 0x22, 0x00, 0x17, 0xd4, 0xe5, 0x0f, 0xc1, + 0x54, 0x8c, 0x4d, 0xf9, 0x40, 0x0b, 0x0a, 0x30, 0x6e, 0x01, 0x1f, 0xe4, + 0x7f, 0xf5, 0xd9, 0x2c, 0x31, 0x25, 0x2f, 0x04, 0xe0, 0x08, 0x44, 0xa9, + 0x5c, 0x39, 0xab, 0xb1, 0x0c, 0x35, 0x07, 0xb2, 0x44, 0x2b, 0x91, 0xfd, + 0xe3, 0xd0, 0x9c, 0x5e, 0xdf, 0x1b, 0x0c, 0x8e, 0x3b, 0xd6, 0x12, 0x8a, + 0x9f, 0xdd, 0xe2, 0xf2, 0x76, 0xfb, 0xef, 0xbe, 0x7d, 0xf2, 0xf7, 0xdc, + 0xdd, 0xe2, 0xf5, 0x00, 0x1a, 0x86, 0x00, 0x1f, 0x00, 0xef, 0xa3, 0x12, + 0x03, 0x77, 0x0d, 0x28, 0x53, 0x7d, 0x8b, 0x46, 0x51, 0xdb, 0xfd, 0xf3, + 0xf0, 0x3e, 0xd9, 0x5a, 0xdc, 0xf7, 0xb6, 0x1a, 0x18, 0x06, 0x13, 0xf0, + 0x50, 0x8b, 0xc9, 0x28, 0xb2, 0x5a, 0x37, 0xba, 0x9e, 0x77, 0xad, 0x79, + 0x9e, 0xd1, 0xa8, 0xba, 0x9e, 0x67, 0xae, 0x79, 0x9e, 0x86, 0x9d, 0xd4, + 0xf3, 0x3d, 0x6b, 0xce, 0xf4, 0x34, 0xee, 0x97, 0x9d, 0xeb, 0x5e, 0x77, + 0xa1, 0xa7, 0x74, 0xbc, 0xef, 0x5a, 0xf3, 0xbd, 0x2d, 0x17, 0x4b, 0xce, + 0xf5, 0xaf, 0x33, 0xd0, 0xd3, 0xba, 0x9e, 0x67, 0xad, 0x79, 0xde, 0x86, + 0x9d, 0xd4, 0xf3, 0x3d, 0x6b, 0xce, 0xf4, 0xb4, 0xee, 0x97, 0x9d, 0xeb, + 0x5e, 0x67, 0xa1, 0xa7, 0x75, 0x3c, 0xef, 0x5a, 0xf3, 0x3d, 0x0d, 0x3b, + 0xa9, 0xe6, 0x7a, 0xe7, 0x99, 0xe9, 0x69, 0xdd, 0x4f, 0x33, 0xd6, 0xbc, + 0xef, 0x43, 0x45, 0xd4, 0xf3, 0x3d, 0x6b, 0xcc, 0xf4, 0x34, 0xee, 0xa7, + 0x99, 0xeb, 0x9e, 0x67, 0xa5, 0xa7, 0x75, 0x3c, 0xcf, 0x5c, 0xf3, 0x3d, + 0x0d, 0x3b, 0xa9, 0xe6, 0x7a, 0xd7, 0x9d, 0xe8, 0x69, 0xdd, 0x2f, 0x33, + 0xd7, 0x3c, 0xcf, 0x4b, 0x4e, 0xea, 0x79, 0x9e, 0xb9, 0xe6, 0x7a, 0x1a, + 0x2e, 0xa7, 0x99, 0xeb, 0x5e, 0x67, 0xa1, 0xa7, 0x75, 0x3c, 0xef, 0x5a, + 0xf3, 0x3d, 0x2d, 0x3b, 0xa9, 0xe6, 0x7a, 0xe7, 0x99, 0xe8, 0x69, 0xdd, + 0x4f, 0x33, 0xd7, 0x3c, 0xcf, 0x43, 0x4e, 0xea, 0x79, 0x9e, 0xb5, 0xe7, + 0x7a, 0x5a, 0x77, 0x4b, 0xce, 0xf5, 0xaf, 0x33, 0xd0, 0xd1, 0x75, 0x3c, + 0xef, 0x5a, 0xf3, 0x3d, 0x0d, 0x3b, 0xa9, 0xe6, 0x7a, 0xe7, 0x99, 0xe9, + 0x69, 0xdd, 0x2f, 0x3b, 0xd6, 0xbc, 0xef, 0x43, 0x4e, 0xe9, 0x79, 0xde, + 0xb5, 0xe6, 0x7a, 0x1a, 0x77, 0x53, 0xce, 0xf5, 0xaf, 0x33, 0xd2, 0xd3, + 0xba, 0x9e, 0x67, 0xac, 0x8d, 0x2d, 0x16, 0xaf, 0x17, 0x8d, 0x29, 0x9c, + 0x00, 0x00, 0x01, 0x05, 0x3b, 0xf9, 0xd3, 0xce, 0xf5, 0xaf, 0x33, 0xdf, + 0xb5, 0x35, 0xf3, 0x97, 0x53, 0xcc, 0xf5, 0xcf, 0x33, 0xd2, 0xd1, 0x75, + 0x3c, 0xcf, 0x5a, 0xf3, 0xbd, 0x0d, 0x3b, 0xa9, 0xe7, 0x28, 0xb0, 0x1b, + 0x00, 0xec, 0x31, 0x26, 0x06, 0xb9, 0x31, 0x21, 0x06, 0x9e, 0x57, 0xe9, + 0x1d, 0xbb, 0xa3, 0x61, 0xe1, 0xb5, 0x8f, 0x68, 0x26, 0xee, 0x59, 0x49, + 0xdb, 0xed, 0xc0, 0xf7, 0xf9, 0xc6, 0x27, 0x2d, 0x09, 0x61, 0xe8, 0xed, + 0xf0, 0xbd, 0x71, 0x01, 0x44, 0x81, 0x80, 0xd4, 0x24, 0xc3, 0xb8, 0x8f, + 0x6e, 0x1a, 0x82, 0x5a, 0x3e, 0xab, 0x78, 0x80, 0x3b, 0x47, 0x48, 0x67, + 0xf9, 0x7d, 0x01, 0x38, 0xc7, 0x63, 0x03, 0xf3, 0xe0, 0xfb, 0xab, 0xbe, + 0xfb, 0x7d, 0xdf, 0x73, 0x6f, 0x3a, 0x52, 0x49, 0x68, 0xdc, 0x7e, 0x5e, + 0x1d, 0xcd, 0xc1, 0xdd, 0x57, 0xae, 0xc0, 0x5b, 0xa4, 0xa0, 0x25, 0xc5, + 0x12, 0x3a, 0xd0, 0x1c, 0xca, 0x0e, 0xf7, 0x8a, 0x43, 0x43, 0xa3, 0x71, + 0xc4, 0x6b, 0xd2, 0x26, 0x76, 0x7d, 0x53, 0xe3, 0x79, 0x9e, 0xb9, 0xeb, + 0x12, 0x9c, 0x8d, 0xff, 0xfb, 0x6c, 0xbf, 0xfa, 0xb6, 0xc3, 0xfd, 0xef, + 0x64, 0x3d, 0xe4, 0xbd, 0x6d, 0xce, 0x2b, 0x3a, 0x5c, 0x09, 0x62, 0x7f, + 0x20, 0x62, 0x3c, 0x84, 0x34, 0xb3, 0x6c, 0x69, 0x1b, 0x00, 0xfa, 0xe6, + 0x86, 0x66, 0x46, 0x73, 0xfa, 0xf8, 0x07, 0xc7, 0x5e, 0xb5, 0x3c, 0x6a, + 0x53, 0xc7, 0x2b, 0x11, 0x40, 0x3e, 0xbe, 0x47, 0x04, 0x6b, 0xdd, 0x3f, + 0xd4, 0xfe, 0x02, 0xfa, 0x09, 0x33, 0xa8, 0xb0, 0x47, 0xfb, 0x21, 0x82, + 0x5f, 0xd9, 0x77, 0xcf, 0xaf, 0xa3, 0x06, 0x70, 0x1c, 0x82, 0x3f, 0xd9, + 0x0c, 0x12, 0xfe, 0xd3, 0x74, 0x80, 0x9c, 0x10, 0xc1, 0x81, 0x23, 0xf7, + 0x11, 0xbd, 0x00, 0x30, 0x24, 0xf1, 0x17, 0x8d, 0xee, 0x70, 0x60, 0x05, + 0xc0, 0x21, 0x26, 0x95, 0x83, 0x71, 0x41, 0x85, 0x15, 0xf3, 0x80, 0x52, + 0x5a, 0x50, 0x5b, 0x0f, 0x19, 0x83, 0x0b, 0x65, 0x2f, 0x64, 0xf4, 0x73, + 0x3e, 0xd7, 0x7c, 0x9a, 0x9d, 0x8a, 0x2d, 0xd7, 0x86, 0x77, 0x10, 0xd7, + 0x9b, 0x21, 0x80, 0x27, 0x2c, 0xa4, 0x96, 0x05, 0x39, 0x34, 0x07, 0x41, + 0x84, 0xde, 0x4d, 0x6c, 0x94, 0xe2, 0xf9, 0x41, 0x85, 0x37, 0x7c, 0x5e, + 0x35, 0x39, 0x38, 0x61, 0xed, 0xdd, 0xec, 0x00, 0x1e, 0x00, 0x60, 0x80, + 0x18, 0xe2, 0xfa, 0x51, 0xd2, 0x30, 0xcc, 0x47, 0xb9, 0x08, 0x64, 0xd2, + 0x53, 0x2d, 0xab, 0x4e, 0xe7, 0x37, 0x7c, 0x1d, 0xc7, 0x72, 0x27, 0xb8, + 0x84, 0x24, 0x9e, 0x81, 0xc7, 0x08, 0x1c, 0x1c, 0x40, 0xaf, 0x21, 0xfe, + 0x94, 0x10, 0x8b, 0x3b, 0xa7, 0x37, 0x0a, 0x7b, 0xa0, 0x19, 0xd8, 0x35, + 0x02, 0xdf, 0x13, 0x9c, 0x89, 0x57, 0x41, 0x29, 0x2c, 0x67, 0xb5, 0xeb, + 0xd5, 0x47, 0x28, 0x01, 0xe9, 0x4d, 0x83, 0x09, 0xb8, 0x0f, 0x24, 0x94, + 0x93, 0xc9, 0x05, 0xf6, 0xc1, 0x45, 0xb1, 0xe7, 0x6f, 0xee, 0xfb, 0xde, + 0x34, 0x03, 0x32, 0x80, 0xc8, 0x0c, 0x08, 0x68, 0xca, 0xe4, 0xa4, 0xb7, + 0xc7, 0xef, 0xb3, 0xef, 0x83, 0xef, 0x6f, 0xb2, 0x53, 0xb7, 0xc8, 0x4f, + 0xdf, 0x3e, 0x57, 0x36, 0xe9, 0x02, 0xa5, 0x38, 0xc4, 0xe1, 0x1c, 0x3a, + 0x60, 0x51, 0x2a, 0xfb, 0xda, 0x3c, 0xef, 0x02, 0x51, 0x60, 0x49, 0x21, + 0x5e, 0x7e, 0x50, 0x6a, 0xc0, 0xb1, 0x7d, 0x87, 0x21, 0x3f, 0x85, 0x6d, + 0xff, 0xbb, 0xc9, 0x49, 0x1d, 0xee, 0x21, 0xb8, 0x7e, 0xb8, 0xf7, 0x97, + 0x00, 0x70, 0xe0, 0x60, 0x9a, 0xee, 0x08, 0x7f, 0x6a, 0x70, 0x47, 0x04, + 0x28, 0xe1, 0x44, 0x57, 0xbd, 0x08, 0x60, 0x09, 0x80, 0x76, 0x43, 0x61, + 0x85, 0x72, 0xb7, 0x3d, 0x21, 0xbb, 0xa1, 0x81, 0x20, 0x0f, 0xb1, 0x7f, + 0x8a, 0x6d, 0x85, 0xeb, 0xc2, 0x10, 0x88, 0x48, 0x02, 0x81, 0x88, 0x0c, + 0x41, 0xd8, 0xd1, 0xff, 0xf7, 0xff, 0xb3, 0x67, 0xfd, 0x7e, 0xf3, 0x08, + 0x41, 0x9d, 0x0f, 0xfa, 0x9f, 0x33, 0x5e, 0x81, 0x0c, 0xbd, 0xf3, 0x65, + 0xdf, 0xa4, 0x00, 0x66, 0xa0, 0x03, 0xee, 0x00, 0x29, 0x4e, 0x13, 0xb9, + 0x49, 0xec, 0x17, 0xb7, 0x0e, 0xbd, 0x2b, 0xdc, 0xa0, 0x07, 0x64, 0xdc, + 0x1a, 0x43, 0x28, 0xac, 0xbc, 0x4c, 0xdd, 0x3c, 0xf2, 0x4a, 0x73, 0x61, + 0x44, 0x87, 0x71, 0x3a, 0xee, 0xbc, 0xc0, 0x77, 0x82, 0x76, 0x1c, 0x85, + 0xd8, 0x10, 0x94, 0x55, 0xd4, 0xf3, 0x3d, 0x6b, 0xce, 0xf7, 0x6b, 0x5c, + 0xd7, 0x4b, 0xce, 0xf5, 0xaf, 0x3b, 0xd0, 0xd3, 0xba, 0x5e, 0x77, 0xad, + 0x79, 0x9e, 0x86, 0x9d, 0xd4, 0xf3, 0x3d, 0x6b, 0xce, 0xf4, 0xb4, 0x5d, + 0x4f, 0x33, 0xd6, 0xbc, 0xef, 0x43, 0x4e, 0xe9, 0x79, 0xde, 0xb5, 0xe7, + 0x7a, 0x1a, 0x77, 0x4b, 0xce, 0xf5, 0xaf, 0x33, 0xd2, 0xd3, 0xba, 0x9e, + 0x67, 0xae, 0x79, 0x9e, 0x86, 0x9d, 0xd4, 0xf3, 0x3d, 0x6b, 0xce, 0xf4, + 0x34, 0xee, 0xa7, 0x99, 0xeb, 0x5e, 0x67, 0xa5, 0xa2, 0xea, 0x79, 0x9e, + 0xb9, 0xe7, 0x7a, 0x1a, 0x77, 0x4b, 0xcc, 0xf5, 0xcf, 0x33, 0xd0, 0xd3, + 0xba, 0x9e, 0x67, 0xad, 0x79, 0xde, 0x96, 0x9d, 0xd2, 0xf3, 0x3d, 0x73, + 0xcc, 0xf4, 0x34, 0xee, 0xa7, 0x9d, 0xeb, 0x5e, 0x67, 0xa1, 0xa7, 0x75, + 0x3c, 0xcf, 0x5a, 0xf3, 0xbd, 0x2d, 0x17, 0x4b, 0xce, 0xf5, 0xaf, 0x3b, + 0xd0, 0xd3, 0xba, 0x5e, 0x77, 0xad, 0x79, 0x9e, 0x86, 0x9d, 0xd4, 0xf3, + 0x3d, 0x73, 0xcc, 0xf4, 0xb4, 0xee, 0xa7, 0x99, 0xeb, 0x5e, 0x77, 0xa1, + 0xa7, 0x74, 0xbc, 0xef, 0x5a, 0xf3, 0x3d, 0x0d, 0x3b, 0xa9, 0xe7, 0x7a, + 0xd7, 0x99, 0xe9, 0x69, 0xdd, 0x4f, 0x33, 0xd7, 0x3c, 0xcf, 0x43, 0x45, + 0xd4, 0xf3, 0x3d, 0x6b, 0xce, 0xf4, 0x34, 0xee, 0x97, 0x9d, 0xeb, 0x5e, + 0x77, 0xa5, 0xa7, 0x74, 0xbc, 0xef, 0x5a, 0xf3, 0x3d, 0x0d, 0x3b, 0xa9, + 0xe6, 0x7a, 0xca, 0x52, 0xd3, 0xb5, 0x79, 0xa9, 0x49, 0x45, 0xca, 0x52, + 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x00, 0x00, 0x00, + 0x01, 0x06, 0x43, 0xf1, 0x4f, 0x33, 0xd6, 0xbc, 0xef, 0x7e, 0xc4, 0xd7, + 0xcf, 0x5d, 0x2f, 0x3b, 0xd6, 0xbc, 0xcf, 0x43, 0x4e, 0xea, 0x79, 0xde, + 0xb5, 0xe6, 0x7a, 0x1a, 0x77, 0x53, 0xcc, 0xf5, 0xaf, 0x3b, 0xd2, 0xd3, + 0xad, 0xf9, 0x04, 0xce, 0xdf, 0xe0, 0xfc, 0x45, 0x27, 0xac, 0x3e, 0xb4, + 0x9b, 0x99, 0x3b, 0x8f, 0x57, 0x23, 0xe2, 0x2d, 0xd3, 0x71, 0x9f, 0x71, + 0xc4, 0x63, 0x48, 0x21, 0xd7, 0x94, 0x0d, 0x40, 0xd4, 0x7c, 0x69, 0x82, + 0x71, 0xd7, 0xcd, 0x60, 0x9c, 0x4e, 0xbd, 0x9e, 0xaf, 0x54, 0x7a, 0x97, + 0xa1, 0x48, 0x00, 0xb4, 0x9a, 0x18, 0x18, 0x3f, 0x8d, 0x4a, 0xd8, 0x20, + 0xa6, 0xfc, 0x4e, 0xfd, 0x91, 0xf0, 0xe4, 0xda, 0xd0, 0x58, 0xd4, 0x64, + 0xe7, 0x5e, 0xdd, 0x67, 0xae, 0xf3, 0xe0, 0x0f, 0x8a, 0x72, 0x59, 0x34, + 0x7f, 0x25, 0x21, 0x6a, 0x64, 0x3b, 0xfd, 0x8c, 0xfd, 0x2f, 0xb8, 0xf1, + 0x9a, 0xf4, 0x40, 0xa0, 0x60, 0x17, 0x58, 0x1e, 0x58, 0x7d, 0xc4, 0x4d, + 0x2d, 0x8a, 0x13, 0x7c, 0xa0, 0x13, 0x93, 0x40, 0x2c, 0x21, 0x16, 0x9c, + 0x7b, 0x32, 0x30, 0xf4, 0xf5, 0x6c, 0xb1, 0xfe, 0xf7, 0x28, 0xc4, 0xa0, + 0xcc, 0x83, 0xfe, 0xcb, 0xec, 0xcf, 0xcd, 0xec, 0x2f, 0xaf, 0xdc, 0x80, + 0x07, 0xa1, 0xa4, 0xb2, 0x68, 0x61, 0x33, 0x1c, 0x80, 0x14, 0xa5, 0x4e, + 0x29, 0x18, 0x52, 0xc2, 0x77, 0x01, 0xe5, 0xd8, 0xfe, 0xf3, 0xc0, 0xaf, + 0x59, 0x45, 0x00, 0x8f, 0x89, 0xf7, 0x41, 0x31, 0x0c, 0x57, 0xe3, 0xef, + 0xe6, 0x97, 0xd3, 0x11, 0xc0, 0x72, 0x08, 0xff, 0x84, 0xd0, 0x4b, 0xfc, + 0x4d, 0xf3, 0x4b, 0xea, 0x3b, 0xac, 0xb0, 0x47, 0xfc, 0x26, 0x82, 0x5f, + 0xe2, 0x68, 0x02, 0xab, 0x0c, 0x01, 0x1e, 0x23, 0x7b, 0x60, 0x1d, 0x12, + 0x78, 0x8b, 0xcf, 0xca, 0x01, 0xbe, 0x4a, 0x3b, 0xe4, 0x0c, 0x4f, 0x0d, + 0x4e, 0x01, 0x21, 0x5b, 0x71, 0xa9, 0x16, 0xd8, 0xec, 0xcb, 0xff, 0x6b, + 0xe9, 0xc0, 0x54, 0x01, 0xb9, 0x41, 0xa9, 0x0d, 0x0d, 0xc4, 0xc0, 0x2e, + 0x92, 0xd3, 0xf3, 0x99, 0xbf, 0xff, 0x28, 0xe7, 0x61, 0xd7, 0x89, 0x00, + 0x6e, 0x05, 0x4b, 0x00, 0xd0, 0x01, 0xd0, 0x40, 0x42, 0x40, 0x35, 0x4b, + 0x3e, 0x3c, 0x02, 0xc2, 0x97, 0x97, 0xbf, 0x24, 0xfe, 0xfb, 0x81, 0x24, + 0xdf, 0x6b, 0xbe, 0x60, 0x03, 0x04, 0x24, 0x06, 0x01, 0x8a, 0xff, 0x74, + 0xaf, 0xc1, 0x35, 0x8b, 0xeb, 0x32, 0xbc, 0xfc, 0xd6, 0x4c, 0xe0, 0x16, + 0x90, 0x80, 0x7a, 0x82, 0x21, 0x07, 0x5b, 0xca, 0x01, 0xca, 0x04, 0xf2, + 0x37, 0xbd, 0x80, 0x06, 0x45, 0xf0, 0x32, 0x30, 0x7e, 0x23, 0x5f, 0x1c, + 0x49, 0x4c, 0x1a, 0xe6, 0x33, 0x87, 0x9c, 0x46, 0xba, 0x13, 0x9d, 0x70, + 0x65, 0x63, 0x8d, 0x24, 0x8e, 0x23, 0xda, 0x03, 0x3e, 0xe5, 0x21, 0xbe, + 0x3b, 0x4c, 0x43, 0x48, 0xc0, 0xc2, 0xf8, 0xed, 0xef, 0xaa, 0x64, 0x6d, + 0x99, 0x6a, 0x1d, 0xfe, 0x22, 0xde, 0xab, 0xed, 0x96, 0xf6, 0xe8, 0x43, + 0xd5, 0x3f, 0x1c, 0x7c, 0x3f, 0x49, 0x7d, 0x98, 0x95, 0xb0, 0x1f, 0x0d, + 0x04, 0x9f, 0xb0, 0xd0, 0x01, 0x8d, 0xc2, 0x01, 0xd0, 0x05, 0xe5, 0x80, + 0xe9, 0x3b, 0x06, 0xfe, 0x59, 0x33, 0x9e, 0x9e, 0x49, 0x0c, 0x16, 0x2d, + 0x03, 0xcd, 0xc7, 0xae, 0xac, 0x92, 0x92, 0xad, 0xfe, 0xe2, 0xec, 0xd2, + 0x4c, 0xfb, 0x56, 0xfe, 0x09, 0x25, 0xa1, 0x07, 0xf6, 0xbd, 0x59, 0x60, + 0x55, 0xd4, 0x80, 0xbe, 0x03, 0x5b, 0xc9, 0xa5, 0x18, 0xbc, 0xde, 0xfa, + 0x71, 0x68, 0xe5, 0x76, 0xe1, 0xdc, 0x78, 0x0b, 0xe8, 0x76, 0xab, 0x35, + 0x53, 0xab, 0x29, 0xcd, 0xff, 0xbf, 0xfc, 0xff, 0xc7, 0xfa, 0x3d, 0x6b, + 0xcc, 0xf7, 0xab, 0xdf, 0xf7, 0xf7, 0x93, 0x75, 0x3c, 0xef, 0x5a, 0xf3, + 0x3c, 0x9a, 0x77, 0x53, 0xcc, 0xf5, 0xcf, 0x33, 0xd0, 0xd1, 0x75, 0x3c, + 0xcf, 0x5c, 0xf3, 0x3d, 0x2d, 0x3b, 0xa5, 0xe7, 0x7a, 0xd7, 0x9d, 0xe8, + 0x69, 0xdd, 0x2f, 0x3b, 0xd6, 0xbc, 0xcf, 0x43, 0x4e, 0xea, 0x79, 0xde, + 0xb1, 0xe7, 0x7a, 0x5a, 0x77, 0x53, 0xcc, 0xf5, 0xaf, 0x3b, 0xd0, 0xd3, + 0xba, 0x5e, 0x67, 0xae, 0x79, 0x9e, 0x86, 0x8b, 0xa9, 0xe7, 0x7a, 0xd7, + 0x99, 0xe9, 0x69, 0xdd, 0x4f, 0x33, 0xd6, 0xbc, 0xef, 0x43, 0x4e, 0xe9, + 0x79, 0xde, 0xb5, 0xe7, 0x7a, 0x1a, 0x77, 0x4b, 0xce, 0xf5, 0xaf, 0x33, + 0xd2, 0xd3, 0xba, 0x9e, 0x67, 0xad, 0x79, 0xde, 0x86, 0x9d, 0xd2, 0xf3, + 0xbd, 0x6b, 0xce, 0xf4, 0x34, 0x5d, 0x2f, 0x3b, 0xd6, 0xbc, 0xcf, 0x4b, + 0x4e, 0xea, 0x79, 0xde, 0xb5, 0xe6, 0x7a, 0x1a, 0x77, 0x53, 0xcc, 0xf5, + 0xcf, 0x33, 0xd0, 0xd3, 0xba, 0x9e, 0x67, 0xad, 0x79, 0xde, 0x96, 0x9d, + 0xd2, 0xf3, 0xbd, 0x6b, 0xcc, 0xf4, 0x34, 0xee, 0xa7, 0x99, 0xeb, 0x9e, + 0x67, 0xa1, 0xa7, 0x75, 0x3c, 0xcf, 0x5c, 0xf3, 0x3d, 0x2d, 0x17, 0x53, + 0xcc, 0xf5, 0xaf, 0x3b, 0xd0, 0xd3, 0xba, 0x5e, 0x77, 0xad, 0x78, 0xd2, + 0xd3, 0xba, 0xde, 0x67, 0x9e, 0x94, 0xce, 0xc4, 0x69, 0x49, 0x45, 0xca, + 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, + 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x40, 0x00, 0x00, + 0x01, 0x07, 0x4b, 0xf3, 0x2f, 0x33, 0xd6, 0xbc, 0xef, 0x7e, 0xb2, 0xd7, + 0x83, 0x74, 0xbc, 0xef, 0x5a, 0xf3, 0xbd, 0x0d, 0x3b, 0xa5, 0xe7, 0x7a, + 0xd7, 0x9d, 0xe9, 0x69, 0xdd, 0x2f, 0x3b, 0xd6, 0xbc, 0xcf, 0x43, 0x4e, + 0xd3, 0xb7, 0xdf, 0x76, 0xfb, 0xab, 0xee, 0x77, 0xbc, 0x80, 0x06, 0x41, + 0xa0, 0x64, 0x85, 0xb7, 0x3c, 0xae, 0x4a, 0xef, 0xf7, 0xea, 0x3b, 0xf6, + 0x35, 0x28, 0x6e, 0x1f, 0x7b, 0x47, 0xb9, 0x40, 0x19, 0x06, 0x92, 0x88, + 0x63, 0x3f, 0x50, 0x6e, 0x6d, 0x80, 0x7a, 0x87, 0xea, 0x12, 0x87, 0x71, + 0x1a, 0xf3, 0x40, 0xa7, 0x6f, 0xf0, 0xf2, 0x3e, 0xbb, 0x48, 0x68, 0x74, + 0x38, 0xea, 0xe3, 0x7a, 0x70, 0xc4, 0xfd, 0x87, 0xbe, 0x0f, 0x03, 0xb7, + 0x83, 0xd8, 0x62, 0x36, 0x1d, 0xad, 0xa9, 0x28, 0x69, 0x68, 0x48, 0xe7, + 0x0f, 0xb4, 0x90, 0xd2, 0xc8, 0x17, 0xd5, 0x79, 0x3b, 0x5e, 0x8b, 0xbb, + 0x55, 0x3e, 0xc3, 0x86, 0x80, 0xdc, 0xb2, 0xc3, 0x40, 0x25, 0x4f, 0x65, + 0x01, 0xec, 0xac, 0x27, 0x75, 0xa7, 0x09, 0x8b, 0xd7, 0xab, 0x66, 0xca, + 0xd8, 0xc8, 0x1f, 0x7a, 0xff, 0x92, 0xcb, 0x71, 0xfe, 0xf2, 0x19, 0x90, + 0xc2, 0xaf, 0xe4, 0xf7, 0xde, 0x40, 0x4e, 0x43, 0x0d, 0x03, 0x21, 0xa1, + 0x85, 0x27, 0x14, 0x12, 0x5f, 0x4a, 0x59, 0xd2, 0xb0, 0xd2, 0xd4, 0x83, + 0x31, 0xc7, 0xf1, 0xea, 0xbe, 0x0f, 0x7d, 0x30, 0x10, 0x46, 0x8b, 0x48, + 0x15, 0x26, 0x96, 0x94, 0x95, 0x90, 0x1b, 0xdd, 0x5d, 0x3d, 0x2e, 0x37, + 0xf5, 0xee, 0x8d, 0xb2, 0xbb, 0x2b, 0x99, 0x7c, 0xb0, 0x09, 0x80, 0x2c, + 0xe1, 0xb8, 0x02, 0xd2, 0x46, 0xef, 0x7b, 0xa3, 0x7a, 0x32, 0x58, 0xfa, + 0xf3, 0xd6, 0xb0, 0x0d, 0xd9, 0x09, 0xe0, 0x2a, 0xe3, 0x80, 0xe5, 0xf4, + 0x94, 0xa3, 0xa3, 0xfd, 0xcc, 0x55, 0xf0, 0xa0, 0x03, 0xce, 0x3f, 0x5c, + 0x20, 0x03, 0xc2, 0x11, 0xfd, 0x0f, 0xc4, 0xf6, 0xbe, 0x5c, 0xbe, 0x51, + 0x9e, 0xd3, 0x57, 0xff, 0x58, 0x01, 0xd7, 0x25, 0x7c, 0x5f, 0x17, 0xaf, + 0x90, 0xa0, 0x87, 0x8b, 0x4f, 0xc7, 0x0e, 0x89, 0x34, 0x98, 0x35, 0x86, + 0x66, 0xd8, 0xca, 0xdc, 0x4c, 0x28, 0x31, 0x69, 0x5f, 0xc1, 0x5a, 0xcc, + 0x0b, 0x74, 0x0b, 0xad, 0x6f, 0x54, 0xa0, 0x03, 0x72, 0x68, 0xc2, 0x90, + 0x19, 0xba, 0xcb, 0x4e, 0x70, 0x15, 0x21, 0x7c, 0x2c, 0xe0, 0xfb, 0xdb, + 0xbc, 0xa4, 0x32, 0x1f, 0x01, 0xd9, 0x37, 0x93, 0x7b, 0xe7, 0x52, 0x37, + 0x03, 0xc8, 0x20, 0xf3, 0xf8, 0x76, 0x32, 0xd2, 0xf7, 0xe8, 0xe4, 0xdf, + 0xbf, 0x6c, 0x72, 0x15, 0x78, 0x41, 0x88, 0x19, 0xae, 0xa7, 0x99, 0xeb, + 0x9e, 0x67, 0xba, 0x5a, 0xc2, 0xb4, 0xf1, 0x00, 0x3a, 0x0d, 0x01, 0x27, + 0x01, 0x11, 0x00, 0x81, 0x79, 0xc2, 0x68, 0x60, 0x19, 0x43, 0x1a, 0x67, + 0x17, 0x7d, 0x75, 0xcd, 0xc7, 0xe5, 0xe1, 0x37, 0xc8, 0x09, 0x81, 0xb8, + 0x35, 0x0e, 0x1d, 0xc4, 0xde, 0x7d, 0xea, 0x27, 0x35, 0x55, 0xf5, 0xbe, + 0xf9, 0xf7, 0xdf, 0x7c, 0xaf, 0xb9, 0xfb, 0xe3, 0xf4, 0xef, 0x78, 0xd0, + 0x07, 0x20, 0x3a, 0x19, 0xc0, 0x2d, 0x46, 0x3d, 0xb1, 0x6d, 0x8d, 0x3b, + 0x24, 0x89, 0xc3, 0x85, 0xde, 0xe9, 0xed, 0xfa, 0x1a, 0xe1, 0x42, 0x46, + 0xeb, 0xa5, 0xe7, 0x7a, 0xd7, 0x9d, 0xe6, 0xd6, 0x77, 0x4b, 0xce, 0xf5, + 0xaf, 0x33, 0xd2, 0xd3, 0xba, 0x9e, 0x67, 0xae, 0x79, 0x9e, 0x86, 0x9d, + 0xd4, 0xf3, 0x3d, 0x6b, 0xce, 0xf4, 0x34, 0xee, 0x97, 0x9d, 0xeb, 0x5e, + 0x77, 0xa5, 0xa7, 0x74, 0xbc, 0xef, 0x5a, 0xf3, 0x3d, 0x0d, 0x17, 0x53, + 0xcc, 0xf5, 0xcf, 0x33, 0xd0, 0xd3, 0xba, 0x5e, 0x77, 0xad, 0x79, 0x9e, + 0x96, 0x9d, 0xd4, 0xf3, 0xbd, 0x6b, 0xcc, 0xf4, 0x34, 0xee, 0xa7, 0x99, + 0xeb, 0x5e, 0x77, 0xa1, 0xa7, 0x75, 0x3c, 0xcf, 0x5a, 0xf3, 0xbd, 0x2d, + 0x3b, 0xa5, 0xe7, 0x7a, 0xd7, 0x99, 0xe8, 0x69, 0xdd, 0x4f, 0x3b, 0xd6, + 0xbc, 0xcf, 0x43, 0x45, 0xd4, 0xf3, 0x3d, 0x73, 0xcc, 0xf4, 0xb4, 0xee, + 0xa7, 0x99, 0xeb, 0x9e, 0x67, 0xa1, 0xa7, 0x74, 0xbc, 0xef, 0x5a, 0xf3, + 0x3d, 0x0d, 0x3b, 0xa9, 0xe6, 0x7a, 0xe7, 0x99, 0xe9, 0x69, 0xdd, 0x4f, + 0x33, 0xd7, 0x3c, 0xcf, 0x43, 0x4e, 0xea, 0x79, 0x9e, 0xb5, 0xe7, 0x7a, + 0x1a, 0x77, 0x53, 0xcc, 0xf5, 0xb1, 0xa5, 0xa2, 0xeb, 0x79, 0x9e, 0x6a, + 0x4a, 0x77, 0x29, 0x4a, 0x4a, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0x80, 0x00, 0x00, 0x01, 0x08, 0x53, 0xe4, 0x3c, 0xef, + 0x5a, 0xf3, 0x3d, 0xfa, 0x83, 0x5e, 0x25, 0xd4, 0xf3, 0x3d, 0x6b, 0xce, + 0xf4, 0xb4, 0xee, 0xa7, 0x99, 0xeb, 0x5e, 0x77, 0xa1, 0xa7, 0x74, 0xbc, + 0xef, 0x69, 0x49, 0xc1, 0x41, 0xf9, 0xd7, 0x8c, 0x67, 0xca, 0x73, 0x2d, + 0x3c, 0x6a, 0x0b, 0x42, 0x3a, 0x73, 0xaf, 0xf0, 0x1e, 0xa0, 0xed, 0xc7, + 0x39, 0xc9, 0x53, 0x29, 0x2c, 0x65, 0xc4, 0x51, 0x30, 0x6a, 0x3f, 0x7c, + 0xa3, 0xed, 0x7a, 0x5b, 0xb5, 0xf3, 0xde, 0x61, 0xbd, 0x1f, 0x7c, 0x7e, + 0xfb, 0xe3, 0x17, 0x96, 0xb1, 0x43, 0xed, 0x43, 0x06, 0x97, 0xca, 0x61, + 0xa4, 0x95, 0x30, 0xd7, 0x00, 0xa3, 0xa4, 0xef, 0xbe, 0xed, 0xce, 0x54, + 0xe4, 0x81, 0x9b, 0xab, 0x8c, 0xef, 0x8e, 0x61, 0x9d, 0x95, 0x9b, 0x33, + 0xee, 0x3b, 0x65, 0x3e, 0xcb, 0xb9, 0x30, 0x14, 0xf9, 0x6e, 0xbd, 0xed, + 0x49, 0x6e, 0xaa, 0xf7, 0xe7, 0x00, 0x37, 0x28, 0x7e, 0xbd, 0x70, 0x6a, + 0x72, 0x52, 0xa5, 0xec, 0x47, 0xba, 0xe9, 0xcc, 0x93, 0x42, 0xb6, 0x23, + 0x4e, 0xc4, 0x63, 0xc3, 0x88, 0x26, 0x5e, 0x5b, 0x5d, 0x95, 0x55, 0x2f, + 0x11, 0xac, 0xbb, 0x5a, 0x12, 0x35, 0x1b, 0x8f, 0xdb, 0x85, 0xd4, 0x96, + 0x8e, 0x82, 0x5f, 0xe8, 0x6c, 0x4a, 0x42, 0x50, 0x96, 0xc8, 0x74, 0xed, + 0x94, 0xf8, 0xa0, 0x97, 0xcf, 0x97, 0xff, 0x37, 0x1d, 0x7a, 0xa1, 0x84, + 0xce, 0x97, 0x15, 0x71, 0x25, 0x2c, 0x1f, 0x5c, 0x7c, 0xd8, 0x02, 0xd0, + 0xc0, 0x27, 0x82, 0xf4, 0xa0, 0x37, 0x0c, 0x02, 0x78, 0x2f, 0x5f, 0x54, + 0xea, 0xce, 0x24, 0x89, 0x64, 0x4e, 0x6c, 0xe7, 0x2c, 0xdc, 0xc0, 0x6c, + 0x4d, 0xe5, 0x30, 0xab, 0xd0, 0x6a, 0xbf, 0xe6, 0xc0, 0x07, 0xa4, 0x20, + 0x45, 0xfc, 0x40, 0x5e, 0xad, 0xe0, 0x27, 0x21, 0x60, 0x12, 0xa3, 0x88, + 0xee, 0x46, 0xbe, 0x9b, 0xca, 0x01, 0x42, 0x3e, 0x4a, 0xfa, 0x0a, 0xe9, + 0x14, 0xfb, 0x2f, 0x61, 0xce, 0x78, 0x1b, 0xb5, 0xa7, 0xab, 0x88, 0xab, + 0xb2, 0x19, 0x44, 0xfa, 0x72, 0x37, 0xaf, 0xbe, 0x7b, 0xc0, 0xae, 0x33, + 0x7e, 0x4e, 0xbd, 0x8a, 0x53, 0xd2, 0x78, 0x55, 0x52, 0x76, 0xce, 0xc6, + 0x71, 0xd9, 0x41, 0x56, 0xaf, 0x82, 0x1d, 0x66, 0x99, 0x87, 0x8f, 0x3e, + 0xcd, 0x74, 0x55, 0x57, 0xbc, 0xcf, 0x56, 0x82, 0x93, 0xd0, 0x35, 0x08, + 0x25, 0x81, 0x76, 0xfb, 0xed, 0xce, 0xdb, 0xe3, 0xd1, 0xd8, 0xd5, 0xe6, + 0xe2, 0xd5, 0x5e, 0x19, 0x89, 0x68, 0x2d, 0xb3, 0xfd, 0xbb, 0x65, 0x25, + 0xf0, 0xd6, 0x01, 0xee, 0xea, 0x1e, 0xea, 0xff, 0xb2, 0xae, 0xc0, 0x0b, + 0x0e, 0x59, 0xf7, 0x38, 0xd3, 0xee, 0x3c, 0xcf, 0x63, 0x42, 0x30, 0x0d, + 0xc0, 0xa2, 0x3a, 0x18, 0x97, 0xf0, 0xd2, 0x94, 0xa6, 0x2f, 0x20, 0xe3, + 0xb6, 0xe8, 0x6e, 0x31, 0xa6, 0x26, 0x94, 0x18, 0x51, 0x48, 0x25, 0x7d, + 0xc0, 0xba, 0x50, 0xf9, 0x05, 0x21, 0x94, 0x48, 0xf8, 0xa5, 0x3a, 0x9f, + 0x7e, 0x22, 0x90, 0x0b, 0x0f, 0x15, 0x50, 0xd3, 0xaf, 0x63, 0xdc, 0x80, + 0x10, 0x00, 0x9c, 0x00, 0xf0, 0x35, 0xff, 0x02, 0xc9, 0x51, 0x62, 0x87, + 0x24, 0x38, 0xd1, 0x42, 0x2b, 0x80, 0xc0, 0x46, 0x74, 0x36, 0xe4, 0x20, + 0xc4, 0xe4, 0xb6, 0x40, 0x17, 0x25, 0x6e, 0x78, 0xc7, 0x4b, 0x9d, 0xdf, + 0xf3, 0xff, 0x32, 0x25, 0x13, 0x06, 0x90, 0x89, 0x79, 0x09, 0xfc, 0x6a, + 0x70, 0x40, 0x61, 0x49, 0x03, 0xc8, 0x35, 0x39, 0xf0, 0x75, 0x8e, 0x26, + 0x32, 0x4f, 0xaf, 0xfd, 0xf5, 0x77, 0xf3, 0x2e, 0x1a, 0x19, 0xf3, 0x0d, + 0xc1, 0x7a, 0xfa, 0xef, 0x6d, 0xfe, 0xdd, 0xf0, 0xfa, 0xb2, 0xb6, 0xfb, + 0xbf, 0x02, 0x4b, 0x6d, 0xcf, 0x71, 0xf6, 0x84, 0xa9, 0x3e, 0x8f, 0x95, + 0x5e, 0xaa, 0xae, 0x79, 0x9e, 0xb9, 0xe6, 0x7b, 0xb5, 0xae, 0x5b, 0xa5, + 0xe7, 0x7a, 0xd7, 0x99, 0xe8, 0x69, 0xdd, 0x4f, 0x3b, 0xd6, 0xbc, 0xcf, + 0x43, 0x4e, 0xea, 0x79, 0x9e, 0xb9, 0xe6, 0x7a, 0x5a, 0x2e, 0xa7, 0x99, + 0xeb, 0x5e, 0x77, 0xa1, 0xa7, 0x75, 0x3c, 0xcf, 0x5a, 0xf3, 0x3d, 0x0d, + 0x3b, 0xa9, 0xe6, 0x7a, 0xe7, 0x99, 0xe9, 0x69, 0xdd, 0x4f, 0x33, 0xd6, + 0xbc, 0xef, 0x43, 0x4e, 0xea, 0x79, 0x9e, 0xb5, 0xe7, 0x7a, 0x5a, 0x77, + 0x4b, 0xce, 0xf5, 0xaf, 0x33, 0xd0, 0xd3, 0xba, 0x9e, 0x67, 0xae, 0x79, + 0x9e, 0x86, 0x8b, 0xa9, 0xe6, 0x7a, 0xd7, 0x9d, 0xe8, 0x69, 0xdd, 0x2f, + 0x3b, 0xd6, 0xbc, 0xef, 0x4b, 0x4e, 0xe9, 0x79, 0xde, 0xb5, 0xe6, 0x7a, + 0x1a, 0x77, 0x53, 0xcc, 0xf5, 0xcf, 0x33, 0xd0, 0xd3, 0xba, 0x9e, 0x67, + 0xad, 0x79, 0xde, 0x96, 0x9d, 0xd4, 0xf3, 0x3d, 0x64, 0x69, 0x68, 0xb4, + 0x79, 0xa3, 0x4a, 0x67, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, + 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, + 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, + 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, + 0x11, 0x72, 0x94, 0xa4, 0x44, 0x00, 0x00, 0x01, 0x09, 0x53, 0xa1, 0xe6, + 0x7a, 0xd7, 0x9d, 0xef, 0xd2, 0x1a, 0xf1, 0xee, 0x97, 0x9d, 0xeb, 0x5e, + 0x67, 0xa1, 0xa7, 0x75, 0x3c, 0xef, 0x5a, 0xf3, 0x3d, 0x0d, 0x3b, 0xa9, + 0xed, 0x08, 0x46, 0x41, 0xc1, 0x46, 0x6c, 0x3b, 0x0a, 0xa3, 0xcc, 0xf6, + 0x59, 0xf0, 0x7c, 0xed, 0xf1, 0x5b, 0x66, 0xca, 0x5b, 0x88, 0x1c, 0xc6, + 0x28, 0xea, 0xc4, 0x14, 0x84, 0x66, 0x3c, 0xf3, 0xf6, 0x34, 0xfc, 0xdb, + 0x6c, 0xb3, 0x54, 0xba, 0xa7, 0x9d, 0xec, 0x46, 0xa5, 0x99, 0xfa, 0xeb, + 0x6d, 0x76, 0xe2, 0x99, 0x23, 0x46, 0xff, 0xc4, 0x21, 0x62, 0x7e, 0x46, + 0xe4, 0xf8, 0xf2, 0xb7, 0xee, 0xfc, 0x42, 0x8d, 0x3d, 0x6c, 0xf8, 0x3b, + 0xea, 0xb7, 0x9d, 0xec, 0xc9, 0x69, 0x77, 0x7e, 0xe7, 0x4d, 0xb5, 0xd4, + 0xf6, 0xb2, 0x56, 0x0e, 0x66, 0x66, 0x43, 0x30, 0xe3, 0x9d, 0x85, 0x3d, + 0x5b, 0xcc, 0xf1, 0xbf, 0x41, 0xdd, 0x9e, 0x47, 0xd8, 0x65, 0xbd, 0x68, + 0xcc, 0xe3, 0xce, 0xcc, 0xa3, 0x0e, 0x5b, 0x3f, 0x61, 0x7e, 0x0f, 0x33, + 0xd1, 0x92, 0x9c, 0x6f, 0xa9, 0xb5, 0xbf, 0x2b, 0x2f, 0xaf, 0x7e, 0xa7, + 0x14, 0xb5, 0x08, 0x5f, 0xa9, 0x7a, 0xe7, 0x99, 0xe4, 0xff, 0x9f, 0xe2, + 0xd4, 0x3f, 0x81, 0x17, 0x62, 0x75, 0x6a, 0x50, 0xea, 0x75, 0x2d, 0x4c, + 0x8e, 0x79, 0xe7, 0xa3, 0xc1, 0xe6, 0x7b, 0x2c, 0x94, 0xe3, 0x66, 0xda, + 0xd7, 0x81, 0xb3, 0xbb, 0x89, 0x7d, 0xe2, 0x1f, 0xc4, 0x89, 0xac, 0x79, + 0x9e, 0x4f, 0xc7, 0x9d, 0x3b, 0x53, 0x18, 0x0e, 0x11, 0x9b, 0x4c, 0x1f, + 0xcf, 0x7e, 0x2f, 0xaf, 0xac, 0x3f, 0xce, 0xf3, 0x3d, 0x0f, 0xc7, 0xf9, + 0xdd, 0x7f, 0xf1, 0xe2, 0x8e, 0xc7, 0x0e, 0x76, 0x72, 0x7c, 0xcf, 0xce, + 0x75, 0x0e, 0x64, 0x75, 0x8f, 0x5e, 0xf5, 0x8f, 0x3b, 0xd0, 0xe9, 0x1f, + 0x36, 0xd6, 0x86, 0x35, 0x1e, 0xb5, 0xe6, 0x79, 0x2e, 0x2e, 0xa7, 0x99, + 0xeb, 0x9e, 0x67, 0x86, 0x8b, 0xa9, 0xe6, 0x7a, 0xd7, 0x9d, 0xe9, 0x69, + 0xdd, 0x4f, 0x33, 0xd6, 0xbc, 0xcf, 0x43, 0x45, 0xd4, 0xf3, 0x3d, 0x73, + 0xcc, 0xf4, 0x34, 0xee, 0xa7, 0x99, 0xeb, 0x9e, 0x67, 0xa5, 0xa7, 0x75, + 0x3c, 0xcf, 0x5a, 0xf3, 0xbd, 0x0d, 0x3b, 0xa9, 0xe6, 0x7a, 0xd7, 0x99, + 0xe8, 0x69, 0xdd, 0x4f, 0x3b, 0xd6, 0xbc, 0xcf, 0x43, 0x4e, 0xea, 0x79, + 0x9e, 0xb9, 0xe6, 0x7a, 0x5a, 0x2e, 0xa7, 0x99, 0xeb, 0x5e, 0x77, 0xa1, + 0xa7, 0x74, 0xbc, 0xef, 0x5a, 0xf3, 0xbd, 0x0d, 0x3b, 0xa5, 0xe7, 0x7a, + 0xd7, 0x99, 0xe9, 0x69, 0xdd, 0x4f, 0x33, 0xd7, 0x3c, 0x68, 0x69, 0xdd, + 0x6f, 0x33, 0xd6, 0x52, 0x99, 0xda, 0xbc, 0xd4, 0xa4, 0xa2, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, + 0x01, 0x0a, 0x53, 0xd0, 0xf3, 0xbd, 0x6b, 0xcc, 0xf7, 0xdf, 0xb5, 0xe5, + 0x5d, 0x4f, 0x33, 0xd6, 0xbc, 0xef, 0x43, 0x45, 0xd2, 0xf3, 0xbd, 0x6b, + 0xce, 0xf4, 0xb4, 0xee, 0x97, 0x9d, 0xeb, 0x5e, 0x67, 0xa1, 0xa7, 0x75, + 0x3c, 0xcf, 0x5c, 0xf3, 0x3d, 0x0d, 0x3b, 0xa9, 0xe6, 0x7a, 0xe7, 0x99, + 0xe9, 0x69, 0xdd, 0x4f, 0x33, 0xd6, 0xbc, 0xcf, 0x43, 0x4e, 0xea, 0x79, + 0xde, 0xb5, 0xe6, 0x7a, 0x1a, 0x77, 0x53, 0xcc, 0xf5, 0xcf, 0x33, 0xd2, + 0xd1, 0x75, 0x3c, 0xcf, 0x5a, 0xf3, 0x3d, 0x0d, 0x3b, 0xad, 0xe6, 0x7a, + 0xd7, 0x99, 0xe8, 0x69, 0xdd, 0x4f, 0x33, 0xd7, 0x3c, 0xcf, 0x4b, 0x4e, + 0xea, 0x79, 0x9e, 0xb5, 0xe7, 0x7a, 0x1a, 0x77, 0x53, 0xcc, 0xf5, 0xaf, + 0x33, 0xd0, 0xd3, 0xba, 0x9e, 0x77, 0xad, 0x79, 0x9e, 0x96, 0x8b, 0xa9, + 0xe6, 0x7a, 0xe7, 0x99, 0xe8, 0x69, 0xdd, 0x4f, 0x33, 0xd6, 0xbc, 0xef, + 0x43, 0x4e, 0xea, 0x79, 0x9e, 0xb5, 0xe7, 0x7a, 0x5a, 0x77, 0x4b, 0xce, + 0xf5, 0xaf, 0x33, 0xd0, 0xd3, 0xba, 0x9e, 0x67, 0xad, 0x79, 0xde, 0x86, + 0x9d, 0xd4, 0xf3, 0x3d, 0x6b, 0xce, 0xf4, 0xb4, 0xee, 0x97, 0x9d, 0xeb, + 0x5e, 0x77, 0xa1, 0xa2, 0xe9, 0x79, 0xde, 0xb5, 0xe6, 0x7a, 0x1a, 0x77, + 0x53, 0xcc, 0xf5, 0xcf, 0x1a, 0x5a, 0x77, 0x5b, 0xcc, 0xf3, 0xd2, 0x99, + 0xd8, 0x8d, 0x29, 0x28, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x20, 0x00, 0x00, 0x01, 0x0b, 0x53, 0xeb, 0xbc, 0xef, + 0x5a, 0xf3, 0xbd, 0xf6, 0xcd, 0x79, 0x97, 0x4b, 0xce, 0xf5, 0xaf, 0x33, + 0xd2, 0xd3, 0xba, 0x9e, 0x67, 0xae, 0x79, 0x9e, 0x86, 0x9d, 0xd4, 0xf3, + 0x3d, 0x6b, 0xce, 0xf4, 0x34, 0xee, 0xa7, 0x99, 0xeb, 0x5e, 0x67, 0xa5, + 0xa7, 0x75, 0x3c, 0xcf, 0x5c, 0xf3, 0x3d, 0x0d, 0x3b, 0xa9, 0xe6, 0x7a, + 0xd7, 0x9d, 0xe8, 0x68, 0xba, 0x9e, 0x67, 0xad, 0x79, 0xde, 0x96, 0x9d, + 0xd2, 0xf3, 0xbd, 0x6b, 0xcc, 0xf4, 0x34, 0xee, 0xa7, 0x99, 0xeb, 0x5e, + 0x77, 0xa1, 0xa7, 0x75, 0x3c, 0xcf, 0x5a, 0xf3, 0xbd, 0x2d, 0x3b, 0xa5, + 0xe7, 0x7a, 0xd7, 0x9d, 0xe8, 0x69, 0xdd, 0x2f, 0x3b, 0xd6, 0xbc, 0xcf, + 0x43, 0x45, 0xd4, 0xf3, 0x3d, 0x73, 0xcc, 0xf4, 0xb4, 0xee, 0xa7, 0x99, + 0xeb, 0x5e, 0x77, 0xa1, 0xa7, 0x74, 0xbc, 0xef, 0x5a, 0xf3, 0x3d, 0x0d, + 0x3b, 0xa9, 0xe7, 0x7a, 0xd7, 0x99, 0xe9, 0x69, 0xdd, 0x4f, 0x33, 0xd6, + 0xbc, 0xef, 0x43, 0x4e, 0xea, 0x79, 0x9e, 0xb5, 0xe7, 0x7a, 0x1a, 0x77, + 0x4b, 0xce, 0xf5, 0xaf, 0x33, 0xd2, 0xd3, 0xba, 0x9e, 0x77, 0xac, 0x9a, + 0x86, 0x8b, 0xad, 0xe6, 0x79, 0xa9, 0x29, 0xd8, 0x8d, 0x29, 0x28, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x88, 0x00, 0x00, + 0x01, 0x0c, 0x53, 0xf4, 0xef, 0x33, 0xd6, 0xbc, 0xef, 0x7d, 0x73, 0x5e, + 0x7d, 0xd2, 0xf3, 0xbd, 0x6b, 0xce, 0xf4, 0x34, 0xee, 0x97, 0x99, 0xeb, + 0x9e, 0x67, 0xa1, 0xa7, 0x75, 0x3c, 0xef, 0x58, 0xf3, 0xbd, 0x2d, 0x3b, + 0xa9, 0xe6, 0x7a, 0xd7, 0x9d, 0xe8, 0x68, 0xba, 0x5e, 0x77, 0xad, 0x79, + 0x9e, 0x86, 0x9d, 0xd4, 0xf3, 0xbd, 0x6b, 0xcc, 0xf4, 0xb4, 0xee, 0xa7, + 0x99, 0xeb, 0x5e, 0x77, 0xa1, 0xa7, 0x74, 0xbc, 0xef, 0x5a, 0xf3, 0xbd, + 0x0d, 0x3b, 0xa5, 0xe7, 0x7a, 0xd7, 0x99, 0xe9, 0x69, 0xdd, 0x4f, 0x33, + 0xd7, 0x3c, 0xcf, 0x43, 0x45, 0xd4, 0xf3, 0x3d, 0x6b, 0xce, 0xf4, 0x34, + 0xee, 0x97, 0x9d, 0xeb, 0x5e, 0x67, 0xa5, 0xa7, 0x75, 0x3c, 0xef, 0x5a, + 0xf3, 0x3d, 0x0d, 0x3b, 0xa9, 0xe6, 0x7a, 0xe7, 0x99, 0xe8, 0x69, 0xdd, + 0x4f, 0x33, 0xd6, 0xbc, 0xef, 0x4b, 0x4e, 0xea, 0x79, 0x9e, 0xb5, 0xe6, + 0x7a, 0x1a, 0x77, 0x53, 0xce, 0xf5, 0x91, 0xa5, 0xa2, 0xd1, 0xe6, 0x8d, + 0x29, 0x9d, 0xca, 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, + 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, + 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, + 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, + 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, + 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, + 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, + 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, + 0x29, 0x4a, 0x44, 0x40, 0x00, 0x00, 0x01, 0x0d, 0x53, 0xf6, 0xcf, 0x3b, + 0xd6, 0x3c, 0xef, 0x7d, 0x2b, 0x5c, 0x57, 0x53, 0xcc, 0xf5, 0xaf, 0x3b, + 0xd0, 0xd3, 0xba, 0x5e, 0x77, 0xad, 0x79, 0x9e, 0x96, 0x8b, 0xa9, 0xe7, + 0x7a, 0xd7, 0x99, 0xe8, 0x69, 0xdd, 0x4f, 0x33, 0xd6, 0xbc, 0xef, 0x43, + 0x4e, 0xe9, 0x79, 0xde, 0xb5, 0xe7, 0x7a, 0x5a, 0x77, 0x4b, 0xce, 0xf5, + 0xaf, 0x33, 0xd0, 0xd3, 0xba, 0x9e, 0x67, 0xae, 0x79, 0x9e, 0x86, 0x9d, + 0xd4, 0xf3, 0x3d, 0x73, 0xcc, 0xf4, 0xb4, 0xee, 0xa7, 0x99, 0xeb, 0x5e, + 0x77, 0xa1, 0xa2, 0xe9, 0x79, 0xde, 0xb5, 0xe6, 0x7a, 0x1a, 0x77, 0x53, + 0xcc, 0xf5, 0xcf, 0x33, 0xd2, 0xd3, 0xba, 0x9e, 0x67, 0xae, 0x79, 0x9e, + 0x86, 0x9d, 0xd4, 0xf3, 0x3d, 0x6b, 0xcc, 0xf4, 0x34, 0xee, 0xa7, 0x9d, + 0xeb, 0x29, 0x4c, 0xed, 0x5e, 0x31, 0xa4, 0xa2, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x88, 0x00, 0x00, 0x01, + 0x0e, 0x53, 0xfa, 0x2b, 0xce, 0xf5, 0xaf, 0x33, 0xde, 0xe3, 0x5c, 0xb7, + 0x53, 0xcc, 0xf5, 0xcf, 0x33, 0xd2, 0xd1, 0x75, 0x3c, 0xcf, 0x5a, 0xf3, + 0xbd, 0x0d, 0x3b, 0xa9, 0xe6, 0x7a, 0xd7, 0x9d, 0xe8, 0x69, 0xdd, 0x2f, + 0x3b, 0xd6, 0xbc, 0xcf, 0x4b, 0x4e, 0xea, 0x79, 0x9e, 0xb9, 0xe6, 0x7a, + 0x1a, 0x77, 0x53, 0xcc, 0xf5, 0xaf, 0x3b, 0xd0, 0xd3, 0xba, 0x5e, 0x77, + 0xad, 0x79, 0x9e, 0x96, 0x8b, 0xa9, 0xe7, 0x7a, 0xd7, 0x99, 0xe8, 0x69, + 0xdd, 0x4f, 0x33, 0xd7, 0x3c, 0xcf, 0x43, 0x4e, 0xea, 0x79, 0x9e, 0xb5, + 0xe6, 0xa5, 0xa7, 0x75, 0x3c, 0xef, 0x3d, 0x29, 0x9d, 0x88, 0xd2, 0x92, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x88, 0x00, 0x00, 0x01, 0x0f, 0x53, 0xfa, 0xab, 0xcc, 0xf5, 0xaf, 0x3b, + 0xde, 0xa3, 0x5c, 0xf7, 0x4b, 0xce, 0xf5, 0xaf, 0x33, 0xd0, 0xd3, 0xba, + 0x9e, 0x67, 0xae, 0x79, 0x9e, 0x86, 0x9d, 0xd4, 0xf3, 0x3d, 0x6b, 0xce, + 0xf4, 0xb4, 0xee, 0xa7, 0x99, 0xeb, 0x5e, 0x77, 0xa1, 0xa7, 0x74, 0xbc, + 0xef, 0x5a, 0xf3, 0x3d, 0x0d, 0x3b, 0xa9, 0xe7, 0x7a, 0xd7, 0x99, 0xe9, + 0x68, 0xba, 0x9e, 0x67, 0xad, 0x78, 0xd0, 0xd3, 0xb4, 0x79, 0x9e, 0x6a, + 0x4a, 0x76, 0x23, 0x4a, 0x4a, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x97, 0xcf, + 0xca, 0x00, 0x58, 0x90, 0xd0, 0x1d, 0x00, 0x1f, 0xa0, 0x0a, 0x86, 0x01, + 0x81, 0x89, 0x4b, 0xb1, 0x6e, 0xc5, 0x3b, 0xa9, 0x9c, 0xcf, 0x6e, 0x58, + 0x6a, 0x03, 0x18, 0x6f, 0x67, 0xed, 0x84, 0xd3, 0xb7, 0xf7, 0xf4, 0x0a, + 0x5c, 0x22, 0x10, 0x0c, 0x0a, 0x21, 0x93, 0x40, 0x6c, 0x82, 0x1a, 0x1f, + 0xf4, 0x21, 0x1c, 0x61, 0x6c, 0x9e, 0x90, 0xe5, 0x31, 0xaf, 0x54, 0x01, + 0xa8, 0x06, 0x98, 0x03, 0x44, 0x92, 0xb9, 0x34, 0xa2, 0xcb, 0xcc, 0xe8, + 0x40, 0xd5, 0x38, 0xff, 0xf3, 0x07, 0x1e, 0xb9, 0x12, 0xd0, 0x34, 0x61, + 0xac, 0xec, 0xdc, 0xe8, 0x6e, 0x7d, 0xfa, 0x2a, 0x52, 0x96, 0x35, 0x5c, + 0xa5, 0x29, 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, 0xf1, 0xa0, 0x18, + 0x00, 0x38, 0x48, 0x05, 0xe8, 0x4f, 0x28, 0x9a, 0x56, 0xe8, 0x61, 0xa8, + 0xf9, 0x2a, 0x77, 0x65, 0xb3, 0x73, 0xad, 0x8b, 0x2d, 0x01, 0x98, 0x6a, + 0x7b, 0x61, 0xf4, 0xec, 0xf7, 0xec, 0xa9, 0x72, 0x80, 0x62, 0x05, 0x10, + 0x03, 0xa2, 0xf9, 0x40, 0x67, 0x16, 0x94, 0x25, 0x28, 0x4b, 0x64, 0xe6, + 0xe8, 0xec, 0x6e, 0xce, 0x1d, 0x71, 0x09, 0x80, 0x0f, 0x31, 0x34, 0x07, + 0x5c, 0x07, 0x60, 0x15, 0x86, 0x20, 0xa4, 0x29, 0x23, 0x72, 0x5f, 0x25, + 0xb7, 0x18, 0x72, 0xfa, 0xcc, 0x3e, 0x7c, 0xb4, 0x6d, 0xbf, 0xea, 0xe7, + 0x2b, 0x9b, 0x26, 0xf7, 0xec, 0x29, 0x78, 0x00, 0x13, 0x00, 0x2e, 0x48, + 0x06, 0xa1, 0x9c, 0x34, 0x98, 0x43, 0x0c, 0xfb, 0x71, 0x9d, 0xf1, 0x79, + 0x6d, 0xb8, 0xe1, 0x76, 0xde, 0x00, 0xe9, 0xdb, 0x13, 0x00, 0x62, 0x03, + 0xa2, 0x6a, 0x7f, 0x24, 0x62, 0xdb, 0x9d, 0xfb, 0x84, 0xb6, 0xb5, 0x00, + 0xb4, 0x31, 0xfe, 0x1a, 0xbc, 0x7a, 0xba, 0x84, 0xaa, 0x43, 0x3d, 0xfb, + 0x2a, 0x5c, 0xd0, 0x1d, 0x80, 0xc0, 0x9a, 0x02, 0x62, 0xc9, 0x44, 0xd2, + 0xba, 0x39, 0x8e, 0x77, 0x5b, 0x3e, 0xe6, 0xeb, 0x20, 0x06, 0x80, 0x26, + 0x00, 0x37, 0x0d, 0xc4, 0xcc, 0x5a, 0x0a, 0x4f, 0x24, 0x6f, 0xba, 0xdb, + 0x7f, 0xd7, 0xb2, 0x9b, 0x63, 0x6c, 0x39, 0x68, 0xc8, 0x1a, 0xea, 0x67, + 0x73, 0xd5, 0x53, 0x2d, 0xaf, 0xbb, 0x4b, 0xe7, 0x25, 0x80, 0x2a, 0xe1, + 0x81, 0xa8, 0x0c, 0x48, 0x0c, 0x40, 0xa2, 0x0b, 0x4b, 0x0c, 0x47, 0x1a, + 0xfd, 0xfb, 0x66, 0x51, 0xbe, 0xf5, 0x60, 0x27, 0x00, 0xc8, 0x0a, 0x80, + 0x65, 0xd1, 0x89, 0xa8, 0x61, 0xbb, 0xfd, 0xf7, 0xdf, 0x76, 0x3d, 0x78, + 0xdd, 0x6c, 0x82, 0x18, 0x61, 0x7f, 0x6c, 0x9d, 0xdd, 0x78, 0x58, 0x81, + 0xf2, 0xe8, 0xfa, 0xfb, 0x74, 0xa5, 0xe4, 0x00, 0x60, 0x00, 0xd8, 0x0a, + 0x80, 0x9c, 0x0a, 0x16, 0x43, 0x43, 0x64, 0x23, 0x76, 0xdc, 0xf2, 0xc6, + 0x65, 0xec, 0xc1, 0x4b, 0xb1, 0x49, 0x68, 0x2b, 0x3a, 0x7b, 0x63, 0xaa, + 0xd9, 0xef, 0xd7, 0xd2, 0x94, 0xb2, 0x9d, 0xca, 0x52, 0x91, 0x17, 0x29, + 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, + 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, 0xb8, + 0x40, 0x1a, 0x00, 0x98, 0x00, 0xdc, 0x37, 0x13, 0x31, 0x68, 0x29, 0x3c, + 0x91, 0xbe, 0xeb, 0x6d, 0xff, 0x5e, 0xca, 0x6d, 0x8d, 0xad, 0x29, 0xc8, + 0xdf, 0xf5, 0x6c, 0x7c, 0x8f, 0xbe, 0xed, 0x2f, 0x22, 0x00, 0xec, 0x03, + 0x04, 0x00, 0x5c, 0x90, 0xc2, 0x80, 0xcf, 0x2c, 0xb4, 0xa0, 0x6a, 0x73, + 0x36, 0xc6, 0x66, 0xe6, 0xbf, 0xdc, 0x2a, 0xf5, 0xd5, 0x14, 0x5e, 0x2d, + 0x19, 0x28, 0x5e, 0xe7, 0x4b, 0xe6, 0xb9, 0x4a, 0x52, 0xca, 0xab, 0x94, + 0xbe, 0x6a, 0x03, 0xa0, 0x05, 0x88, 0x26, 0x90, 0xb8, 0x0e, 0xd2, 0x05, + 0x70, 0x0d, 0x86, 0x25, 0x2e, 0x84, 0x24, 0x68, 0x16, 0xfd, 0xd4, 0x96, + 0x1d, 0xef, 0xa5, 0x5d, 0x25, 0x06, 0xa0, 0xbe, 0x9c, 0x8e, 0xe3, 0x3b, + 0x71, 0x23, 0xa8, 0xfb, 0xeb, 0x94, 0xa5, 0x2e, 0x6a, 0xee, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x88, + 0x00, 0x00, 0x01, 0x10, 0x53, 0xfb, 0x23, 0xcc, 0xf5, 0xcf, 0x33, 0xdd, + 0x6d, 0x65, 0x75, 0x3c, 0xcf, 0x5a, 0xf3, 0xbd, 0x2d, 0x3b, 0xa5, 0xe7, + 0x7a, 0xd7, 0x9d, 0xe8, 0x69, 0xdd, 0x2f, 0x3b, 0xd6, 0xbc, 0xcf, 0x43, + 0x4e, 0xea, 0x79, 0x9e, 0xb6, 0x34, 0xb4, 0x5a, 0x3c, 0xd1, 0xa5, 0x33, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x7c, 0x24, 0x00, 0xb8, 0x37, + 0x06, 0x80, 0xe8, 0x9a, 0x4c, 0xf9, 0xc6, 0x25, 0xd6, 0xbc, 0x30, 0xbc, + 0xcc, 0xc0, 0x22, 0xee, 0x26, 0xfb, 0xb5, 0xf2, 0xb0, 0x02, 0xac, 0x90, + 0x03, 0xf2, 0x6f, 0x58, 0xc0, 0x30, 0xdc, 0x73, 0xf4, 0xe6, 0x71, 0x6c, + 0x35, 0x83, 0xef, 0x64, 0x03, 0x54, 0x38, 0x14, 0x2c, 0x53, 0x7c, 0x27, + 0x7b, 0x00, 0xce, 0xc9, 0xd5, 0xff, 0x41, 0x00, 0x84, 0x0c, 0x8e, 0x9e, + 0x49, 0xef, 0x91, 0x8d, 0xc3, 0xfd, 0xea, 0x4a, 0x29, 0x21, 0xa9, 0x6c, + 0x1f, 0xee, 0x70, 0x15, 0x4f, 0x48, 0x6a, 0x45, 0x76, 0x0a, 0xbe, 0x58, + 0x03, 0xad, 0x8a, 0x26, 0x72, 0xc7, 0xbf, 0xe1, 0xd6, 0xbf, 0x8c, 0xce, + 0x6f, 0xa2, 0xfd, 0xa0, 0x05, 0x48, 0xdf, 0x23, 0x96, 0x13, 0xcc, 0x65, + 0x0a, 0xbd, 0x29, 0x64, 0xce, 0x18, 0x96, 0x4f, 0x0f, 0xb9, 0x64, 0x2d, + 0xc3, 0x49, 0xbb, 0xf7, 0xeb, 0x7f, 0xc0, 0x3f, 0xbc, 0x88, 0x0e, 0xd8, + 0xb0, 0xd2, 0x8a, 0x0b, 0x0f, 0x6a, 0x9f, 0xe4, 0xec, 0xee, 0xa7, 0xe2, + 0xa2, 0xfd, 0x29, 0x60, 0x50, 0x94, 0x50, 0xde, 0x91, 0xcc, 0x2e, 0x72, + 0x60, 0x14, 0xc1, 0xa8, 0xe9, 0xfd, 0xb6, 0xbc, 0x58, 0x09, 0xfb, 0x92, + 0x8a, 0xc6, 0xf7, 0x20, 0x11, 0x6e, 0x71, 0x60, 0x28, 0x02, 0x8d, 0xbb, + 0xbe, 0x61, 0x2e, 0x7b, 0x13, 0xee, 0xd1, 0xbc, 0x66, 0x1f, 0xf9, 0x3a, + 0xcd, 0xaf, 0xec, 0xa1, 0x85, 0x01, 0x94, 0xb3, 0xb5, 0xe3, 0x80, 0x0f, + 0x00, 0xbb, 0x6e, 0xac, 0x7f, 0xe4, 0x6c, 0x1d, 0x56, 0x02, 0x62, 0x80, + 0xbf, 0xff, 0x12, 0x09, 0xdc, 0x07, 0xf7, 0x64, 0x0a, 0x86, 0xfe, 0x1b, + 0xdb, 0x92, 0x15, 0x88, 0xb7, 0x07, 0xff, 0xa5, 0xb7, 0x32, 0xd0, 0xca, + 0xb3, 0xd4, 0x00, 0xc4, 0x99, 0x80, 0xa1, 0x0c, 0xb0, 0xcc, 0x9c, 0x5e, + 0x47, 0x61, 0xb8, 0xd0, 0x3b, 0x8e, 0xe2, 0xef, 0x00, 0x00, 0x91, 0x3c, + 0x02, 0xb0, 0xd4, 0x3a, 0xb8, 0x63, 0xfc, 0x2f, 0x27, 0x31, 0x1f, 0xdf, + 0x53, 0xbe, 0x7e, 0x00, 0xb0, 0x34, 0x94, 0x02, 0x1c, 0x86, 0x01, 0xca, + 0x12, 0x81, 0x0d, 0x86, 0x80, 0x0c, 0xae, 0x50, 0x2b, 0x9c, 0x84, 0x5e, + 0x3c, 0x23, 0x93, 0xad, 0x10, 0x49, 0xd5, 0xc6, 0xf0, 0x1d, 0x94, 0x08, + 0x5f, 0x29, 0xd8, 0xdf, 0x82, 0xfa, 0xc8, 0xfe, 0xa4, 0x0a, 0xa7, 0x23, + 0x20, 0x99, 0x8e, 0x28, 0xfe, 0x3d, 0x26, 0x85, 0x1d, 0x78, 0x52, 0x1a, + 0x00, 0xc0, 0x69, 0x64, 0x3e, 0x3b, 0x0c, 0x01, 0x5f, 0xcc, 0x4e, 0x67, + 0xbe, 0xd8, 0x7f, 0xb4, 0x28, 0x27, 0x61, 0x5b, 0x05, 0xd3, 0xea, 0xdf, + 0x81, 0x00, 0x98, 0x10, 0x82, 0x40, 0x1d, 0x72, 0xc0, 0x70, 0x9e, 0x51, + 0x69, 0x70, 0x1e, 0xf7, 0xc8, 0xe1, 0xe1, 0x0d, 0x7e, 0x81, 0xbe, 0x00, + 0x00, 0xec, 0x10, 0x81, 0xc0, 0x30, 0x41, 0x30, 0xf7, 0x25, 0x06, 0x39, + 0xac, 0x8c, 0xee, 0xc0, 0x22, 0x27, 0xdf, 0x68, 0x01, 0x80, 0x14, 0xe0, + 0x3b, 0x26, 0x94, 0x19, 0x80, 0xff, 0x5f, 0x1d, 0x8d, 0xe1, 0x57, 0x50, + 0x0e, 0xbe, 0x18, 0x1a, 0x1b, 0xc0, 0xfa, 0x7f, 0x71, 0x5a, 0x84, 0x9f, + 0xb5, 0x7f, 0xf2, 0x32, 0x18, 0x49, 0x09, 0x1f, 0x9e, 0x11, 0xc2, 0xf5, + 0xe9, 0xc0, 0x40, 0x87, 0x2f, 0xf6, 0x3b, 0x13, 0xc0, 0x7f, 0x79, 0x80, + 0x13, 0xa7, 0x64, 0x64, 0x63, 0x56, 0x1d, 0xa0, 0x02, 0x0c, 0x10, 0x9e, + 0x3f, 0xb0, 0x50, 0x1b, 0xbd, 0x27, 0x01, 0x47, 0x16, 0x70, 0x7d, 0x91, + 0xb7, 0xeb, 0xc8, 0x63, 0x4b, 0x21, 0xfc, 0x85, 0xb3, 0x6c, 0x28, 0x08, + 0xde, 0x4c, 0x04, 0x29, 0xc3, 0x36, 0x35, 0xdc, 0x3a, 0x50, 0x28, 0x4d, + 0xe1, 0xa5, 0xf5, 0x6c, 0xaf, 0xd5, 0x96, 0x7e, 0x36, 0xfa, 0x81, 0x0d, + 0x03, 0x03, 0x53, 0x9f, 0xf1, 0x22, 0x2e, 0x3d, 0x82, 0x76, 0x38, 0x66, + 0xae, 0xfb, 0xbb, 0x3f, 0x1d, 0x7c, 0x9f, 0x86, 0x24, 0x30, 0x99, 0xf9, + 0x2d, 0x5c, 0xcc, 0x77, 0x7b, 0xea, 0x48, 0x40, 0xd4, 0xec, 0x3f, 0xb5, + 0xf1, 0x90, 0x13, 0xf5, 0x21, 0x1b, 0x0b, 0x11, 0xb6, 0xb8, 0x53, 0xd6, + 0x8d, 0x6b, 0xaf, 0xe8, 0x85, 0x13, 0x1d, 0x04, 0xb4, 0xf6, 0x45, 0x9c, + 0xb2, 0x16, 0x42, 0x31, 0x68, 0x48, 0xdd, 0x79, 0x50, 0x28, 0x5e, 0xd8, + 0x0c, 0x17, 0x8f, 0x39, 0xc5, 0x99, 0x7a, 0xa0, 0x2a, 0x18, 0x03, 0x6e, + 0xdb, 0xf6, 0xeb, 0xad, 0xf8, 0xd6, 0x0a, 0x1d, 0xf4, 0xaf, 0xd9, 0x6c, + 0xfd, 0xb5, 0xf3, 0xc0, 0x0a, 0x91, 0xbe, 0x41, 0x69, 0x64, 0xf1, 0x4a, + 0x0b, 0xbd, 0xda, 0x1d, 0xd4, 0x7e, 0x58, 0x55, 0xf0, 0xa0, 0x2c, 0x03, + 0x14, 0x0e, 0x4e, 0x0b, 0xc0, 0x72, 0x82, 0xfb, 0x08, 0x34, 0x54, 0xf5, + 0xfd, 0x40, 0x02, 0xc6, 0x2c, 0x95, 0xfe, 0xdc, 0xc3, 0xd7, 0x12, 0x19, + 0x33, 0x13, 0x06, 0x67, 0x7f, 0xcf, 0xb8, 0x60, 0x3a, 0x47, 0xe4, 0xd5, + 0xbb, 0xa3, 0x72, 0x05, 0x8c, 0x37, 0x00, 0xdc, 0x86, 0xcf, 0x9b, 0x36, + 0x03, 0x82, 0x5f, 0xda, 0x3f, 0x1a, 0x7a, 0xb6, 0x7f, 0x4d, 0xf6, 0x52, + 0x05, 0x32, 0x03, 0x31, 0x7d, 0x2b, 0xec, 0xa1, 0x20, 0x72, 0xe8, 0x94, + 0x4c, 0x74, 0x12, 0xd3, 0xd9, 0x17, 0xcf, 0x00, 0x4f, 0xff, 0x18, 0x8c, + 0x2f, 0xf0, 0xe0, 0x23, 0x74, 0x40, 0xa1, 0x7b, 0x60, 0x30, 0x5e, 0x3c, + 0xe7, 0x16, 0x65, 0xd1, 0x92, 0x91, 0x8e, 0x2c, 0x9d, 0x49, 0xb7, 0xea, + 0x8b, 0x21, 0x64, 0x23, 0x16, 0x84, 0x8d, 0xd7, 0x19, 0x00, 0x3a, 0xdc, + 0xa1, 0xbd, 0x2a, 0x5f, 0x56, 0x33, 0x5d, 0x90, 0x2a, 0x18, 0x03, 0x6f, + 0xb6, 0xfd, 0xba, 0xef, 0x98, 0x80, 0x4c, 0x03, 0xac, 0x23, 0x70, 0xff, + 0xdc, 0x0d, 0x8a, 0x8e, 0x9e, 0xd9, 0xb9, 0xfe, 0x2f, 0xe8, 0xc1, 0x80, + 0x50, 0x96, 0x7f, 0xdb, 0xb3, 0xec, 0x68, 0x1a, 0xbb, 0xc0, 0x36, 0x49, + 0x2f, 0xa3, 0x67, 0xf8, 0x07, 0xf7, 0xce, 0x00, 0x4d, 0x9f, 0x96, 0x5f, + 0x0b, 0x27, 0xfb, 0xc6, 0x06, 0x77, 0xc9, 0x2d, 0x41, 0x3f, 0xf7, 0x15, + 0x95, 0x9e, 0xd3, 0x97, 0xf7, 0xe6, 0x9c, 0x17, 0xe4, 0xd7, 0xf5, 0xa0, + 0x0a, 0xb0, 0xd2, 0x51, 0x2f, 0x0d, 0x30, 0x70, 0x7d, 0xaf, 0x10, 0x80, + 0xb9, 0x2b, 0xa7, 0x6e, 0xe4, 0xe6, 0x7b, 0x86, 0x18, 0x92, 0x8a, 0xef, + 0x9c, 0xb4, 0xfb, 0x92, 0x03, 0xa7, 0x2c, 0x34, 0xb4, 0xa4, 0xfc, 0xa0, + 0x0e, 0x2c, 0x3f, 0xfb, 0x7e, 0x28, 0x9f, 0x55, 0xfa, 0xa2, 0xc0, 0xa0, + 0xc4, 0xb6, 0x49, 0x8f, 0xff, 0x56, 0xcb, 0x69, 0x49, 0xa0, 0x50, 0x06, + 0xc8, 0xdd, 0xdc, 0xcf, 0x72, 0x12, 0x02, 0x90, 0x2a, 0x30, 0xc7, 0x58, + 0xb6, 0x01, 0xf5, 0x84, 0x06, 0x2e, 0x5a, 0x72, 0x31, 0x87, 0x89, 0xfd, + 0x96, 0x02, 0xf9, 0xef, 0xdd, 0x19, 0xcc, 0x7f, 0x2b, 0xf7, 0x01, 0x84, + 0x24, 0x86, 0xbf, 0x3b, 0x63, 0x7d, 0xe1, 0x48, 0x41, 0x85, 0x12, 0xdd, + 0x09, 0xfc, 0x63, 0xba, 0x83, 0xae, 0x78, 0x0e, 0xf8, 0x18, 0x0c, 0xc5, + 0x63, 0x54, 0x1d, 0xae, 0x68, 0x0e, 0x91, 0x8a, 0x2b, 0xf3, 0x4f, 0xe2, + 0x80, 0x82, 0xee, 0xd4, 0xa7, 0xf4, 0x7e, 0x3d, 0x5b, 0x50, 0xd7, 0xf4, + 0xd2, 0x1b, 0x86, 0x93, 0x7e, 0xcf, 0xd9, 0x41, 0x47, 0x93, 0xe4, 0x00, + 0xab, 0x23, 0xec, 0x94, 0xed, 0xf9, 0xdb, 0x2d, 0x62, 0xef, 0x0c, 0x03, + 0x04, 0x70, 0x92, 0xf9, 0xe9, 0x02, 0x37, 0xa4, 0xc9, 0x0c, 0x41, 0x7c, + 0xbf, 0xfa, 0x52, 0xe7, 0x63, 0x8d, 0x1e, 0x7d, 0xc6, 0xe6, 0x63, 0x35, + 0x77, 0xec, 0x0b, 0x26, 0x24, 0xa4, 0x8c, 0x3f, 0xf3, 0x9a, 0xf3, 0xdc, + 0x0a, 0x62, 0xb0, 0x17, 0x4b, 0x39, 0x99, 0x77, 0xcf, 0x88, 0x43, 0x4b, + 0x2d, 0x25, 0xa7, 0x37, 0xc1, 0x1b, 0x0e, 0x36, 0xfa, 0x2e, 0x04, 0x2f, + 0xa2, 0x6b, 0xf3, 0x9c, 0x9c, 0x66, 0x0f, 0xa4, 0x6f, 0xe1, 0xd9, 0x73, + 0xab, 0x7c, 0x28, 0x0e, 0xf0, 0x01, 0xb0, 0x03, 0xac, 0x18, 0x37, 0xbf, + 0xd9, 0x0e, 0xcc, 0x03, 0x9e, 0x35, 0x39, 0x8c, 0x53, 0xeb, 0xef, 0x77, + 0xc4, 0x40, 0x16, 0xf4, 0xa4, 0x06, 0xc1, 0x88, 0x5a, 0x8b, 0x2d, 0x28, + 0xc3, 0xb6, 0x52, 0x78, 0x70, 0xdb, 0xec, 0x25, 0x93, 0x03, 0x43, 0x18, + 0x6a, 0x3a, 0x1f, 0x6f, 0x9f, 0x1f, 0xbd, 0x90, 0x09, 0x80, 0x70, 0x37, + 0x9e, 0x5f, 0xe9, 0xa8, 0xb3, 0xc4, 0x5e, 0xda, 0x58, 0xf9, 0x30, 0x35, + 0x08, 0xc5, 0xf4, 0x27, 0x21, 0x27, 0x76, 0x57, 0x68, 0x00, 0xd8, 0x34, + 0x94, 0x4b, 0x19, 0xc6, 0x3b, 0x0d, 0x65, 0x3d, 0xe2, 0x3e, 0x1f, 0x74, + 0x5e, 0xda, 0x52, 0x94, 0x4e, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x20, 0x00, 0x00, 0x01, 0x11, 0x6b, 0xfb, 0x9b, 0xce, + 0xf5, 0xaf, 0x35, 0x4d, 0x4d, 0xd4, 0xf3, 0x3d, 0x64, 0x65, 0x16, 0xaf, + 0x18, 0xd2, 0x99, 0xdc, 0xa5, 0x29, 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, + 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, + 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, + 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, 0x2f, 0x99, 0x00, + 0x64, 0x01, 0x98, 0x0e, 0x80, 0x34, 0xc5, 0xb2, 0x09, 0xa9, 0xe5, 0xfc, + 0xff, 0x8c, 0xc7, 0x1c, 0x68, 0xbf, 0x7d, 0x36, 0x97, 0x49, 0x65, 0x60, + 0xdc, 0xdc, 0xea, 0x37, 0x6b, 0xc4, 0x1b, 0xd1, 0xd2, 0xe8, 0xe9, 0xee, + 0x7f, 0x4b, 0x3b, 0x87, 0x7f, 0x79, 0x60, 0x13, 0x00, 0x5f, 0xc0, 0xa1, + 0x31, 0x24, 0x24, 0xa0, 0x96, 0x51, 0x6e, 0xeb, 0x6f, 0xb2, 0x73, 0x25, + 0x8c, 0x10, 0x2e, 0xf6, 0xd4, 0xa5, 0x25, 0x64, 0xe6, 0xeb, 0x9b, 0xb5, + 0xfc, 0xd4, 0x30, 0x01, 0xf9, 0x08, 0x04, 0xc8, 0x4e, 0x01, 0xd1, 0x31, + 0x2e, 0x93, 0xf9, 0x7d, 0xce, 0x38, 0xe1, 0xcb, 0xbd, 0x38, 0x14, 0x01, + 0x3a, 0x49, 0x80, 0x31, 0x40, 0x15, 0x2d, 0xcb, 0xef, 0xdc, 0x7f, 0x7d, + 0xf1, 0x15, 0x57, 0xa3, 0xa5, 0xae, 0x26, 0x23, 0x0d, 0x51, 0x25, 0x5f, + 0xf3, 0xf8, 0x7d, 0x2f, 0xc2, 0xaf, 0xc0, 0x80, 0x6a, 0x00, 0xff, 0x00, + 0xef, 0xf2, 0x50, 0x66, 0x43, 0xa0, 0x60, 0xde, 0x49, 0x18, 0xee, 0xc3, + 0xb2, 0xdc, 0x4e, 0xf7, 0x2c, 0x86, 0x00, 0xcc, 0x0c, 0x86, 0x21, 0x08, + 0x26, 0x64, 0x27, 0x2f, 0x73, 0xf1, 0x5c, 0xed, 0xd1, 0x87, 0xb7, 0xe7, + 0x9d, 0xef, 0xa9, 0x52, 0xd0, 0x02, 0xc2, 0x99, 0x19, 0x89, 0x07, 0x2f, + 0xc8, 0x6f, 0xbf, 0x9c, 0x80, 0x64, 0x00, 0xfc, 0xa0, 0x13, 0x06, 0x06, + 0xb9, 0x49, 0x48, 0x69, 0x7d, 0xc6, 0xb9, 0xea, 0x56, 0xe1, 0xee, 0x17, + 0x7d, 0x01, 0x29, 0xe5, 0xa7, 0xa5, 0x3d, 0xfa, 0x7f, 0xff, 0x8f, 0xfe, + 0xba, 0x96, 0x18, 0xa4, 0x23, 0xa5, 0x3b, 0x2d, 0x27, 0x87, 0x54, 0xf7, + 0x2e, 0x00, 0x06, 0x80, 0x57, 0x00, 0xc0, 0x0a, 0x94, 0x1a, 0x9d, 0xfe, + 0x40, 0xd5, 0x63, 0xd6, 0x8f, 0x88, 0xb7, 0x7e, 0x96, 0x5f, 0x64, 0xef, + 0xb9, 0xdd, 0x55, 0x1d, 0x7e, 0x18, 0x01, 0xf8, 0x06, 0x20, 0x06, 0xfc, + 0xac, 0x8d, 0xd0, 0x84, 0x63, 0xb8, 0x13, 0xe6, 0xa8, 0x51, 0xc1, 0xf7, + 0x7c, 0x06, 0x05, 0x86, 0x06, 0x06, 0xa0, 0xb2, 0xc6, 0x81, 0x64, 0xa1, + 0x3b, 0x23, 0x65, 0xb7, 0x75, 0xbf, 0xd7, 0x6a, 0x96, 0xa8, 0x29, 0x08, + 0x40, 0xcc, 0xad, 0x4b, 0xbf, 0x7b, 0xf9, 0x28, 0x0c, 0x40, 0x1f, 0x80, + 0x80, 0x04, 0xf9, 0x18, 0x34, 0x98, 0x4d, 0x08, 0xfd, 0xbe, 0xfb, 0x75, + 0x9f, 0x76, 0x4a, 0x00, 0x7e, 0x50, 0x0c, 0x32, 0x70, 0x0e, 0x88, 0x59, + 0x0e, 0xc0, 0x5c, 0xbe, 0xe9, 0x1e, 0xb3, 0x02, 0xaf, 0xa0, 0xd2, 0xe9, + 0xc0, 0x50, 0xad, 0x86, 0x2d, 0x87, 0x3f, 0x6a, 0x06, 0xfb, 0xe7, 0x16, + 0x4d, 0x49, 0x0c, 0xb1, 0xa4, 0xd4, 0xf4, 0x9c, 0xfc, 0xd6, 0x14, 0x65, + 0xf3, 0xa2, 0x10, 0x03, 0xf0, 0x28, 0x42, 0x01, 0xd7, 0x21, 0x21, 0x01, + 0xab, 0xc9, 0xc4, 0xb0, 0x8d, 0xdd, 0x2e, 0xb3, 0xcf, 0xc4, 0x4b, 0xea, + 0xd4, 0xb1, 0x48, 0x62, 0x0b, 0xdb, 0x73, 0xb2, 0xf6, 0xca, 0x3a, 0xad, + 0xfe, 0xbf, 0x2e, 0x02, 0x00, 0x13, 0x80, 0x1c, 0x10, 0xc9, 0x7b, 0x0d, + 0x47, 0x74, 0x7d, 0xcf, 0x19, 0xf9, 0x1d, 0x55, 0x60, 0x19, 0x81, 0x52, + 0x60, 0x0c, 0x43, 0x78, 0x69, 0x58, 0xb4, 0x27, 0xb3, 0xee, 0xcc, 0xac, + 0xcf, 0x9b, 0x88, 0xbd, 0x65, 0x28, 0x25, 0x20, 0x69, 0x2c, 0xd6, 0x60, + 0xe9, 0xba, 0x9a, 0xf8, 0x80, 0x20, 0x02, 0xa0, 0x07, 0x80, 0x54, 0xa0, + 0xd6, 0x71, 0x8e, 0xf8, 0x57, 0xdf, 0x11, 0x6f, 0x2e, 0x02, 0x10, 0x07, + 0x7c, 0x86, 0x90, 0x14, 0x20, 0x94, 0x76, 0xe9, 0xeb, 0x4a, 0x76, 0x58, + 0xce, 0x63, 0x3b, 0xa8, 0xd3, 0x6f, 0xa1, 0xd2, 0xae, 0x18, 0x8e, 0x9c, + 0x9f, 0x97, 0xb3, 0x49, 0xfb, 0x35, 0xfc, 0xcc, 0x34, 0x02, 0xf0, 0x28, + 0x02, 0x74, 0x20, 0x00, 0xfc, 0x98, 0x92, 0x5a, 0x00, 0xf9, 0x5f, 0x65, + 0x30, 0xb1, 0x66, 0x5e, 0x98, 0x03, 0x50, 0x0d, 0x4b, 0x00, 0xd5, 0x3b, + 0xa7, 0x64, 0x64, 0x12, 0x11, 0x8c, 0x73, 0xd6, 0x2e, 0xf4, 0xd4, 0xa9, + 0x04, 0xc4, 0x23, 0xef, 0xb1, 0xce, 0xc2, 0x9c, 0xf6, 0x8f, 0xff, 0xbf, + 0x36, 0x01, 0xa0, 0x08, 0x52, 0x01, 0xa1, 0x7d, 0x28, 0xd9, 0x3b, 0xe6, + 0xfb, 0x90, 0x4f, 0xd7, 0x97, 0x00, 0x13, 0x81, 0x42, 0x11, 0x65, 0x93, + 0x40, 0xa1, 0x31, 0x28, 0x2c, 0xbd, 0xf0, 0x4f, 0xc4, 0xac, 0xeb, 0x7d, + 0xb0, 0xbd, 0x95, 0x7d, 0x32, 0x92, 0x49, 0x31, 0x09, 0xef, 0xf1, 0xf9, + 0xba, 0xcf, 0xc2, 0xe1, 0xfd, 0xf8, 0x00, 0x0c, 0x00, 0x1f, 0x80, 0x1e, + 0x16, 0x4d, 0x48, 0x6e, 0xe5, 0x81, 0x96, 0xff, 0xe6, 0x77, 0xdf, 0x91, + 0xdf, 0xda, 0x80, 0x04, 0xe0, 0x85, 0xf3, 0x89, 0xb8, 0x98, 0x56, 0xd9, + 0x29, 0x2c, 0x62, 0xd6, 0x94, 0xe6, 0x7f, 0xcd, 0x6e, 0xbc, 0xa5, 0x5e, + 0xd6, 0x93, 0x01, 0xb1, 0x4f, 0xd2, 0xcb, 0x4e, 0x6e, 0x22, 0x06, 0xfb, + 0xf9, 0xe0, 0x08, 0x40, 0x1d, 0xf2, 0x1a, 0x40, 0x50, 0x82, 0x51, 0xdb, + 0xa7, 0xad, 0x29, 0xd9, 0x63, 0x39, 0x8c, 0xee, 0xa3, 0x4d, 0xb3, 0x06, + 0x80, 0x5e, 0x05, 0x00, 0x4e, 0x84, 0x00, 0x1f, 0x93, 0x12, 0x4b, 0x40, + 0x1f, 0x2b, 0xec, 0xa6, 0x16, 0x2c, 0xcb, 0xe9, 0x94, 0xac, 0x02, 0xc2, + 0xb6, 0xcd, 0x98, 0x61, 0xdf, 0x7e, 0xe1, 0x72, 0x1b, 0xeb, 0xff, 0x1a, + 0x58, 0x6a, 0x40, 0xa9, 0x68, 0xc7, 0x19, 0x83, 0xef, 0x97, 0x00, 0xc5, + 0x28, 0xc3, 0x32, 0x77, 0xfb, 0xbf, 0x51, 0xea, 0x03, 0x77, 0xdb, 0x8a, + 0x49, 0x29, 0x39, 0x2c, 0xec, 0x65, 0xcd, 0x26, 0x86, 0x80, 0xdd, 0x2e, + 0xcc, 0xa6, 0xbb, 0x3a, 0x17, 0x87, 0x67, 0xb3, 0x7a, 0xbb, 0x92, 0x1a, + 0x01, 0x7b, 0x13, 0x00, 0x2c, 0x02, 0x85, 0x01, 0x82, 0x99, 0xbb, 0xad, + 0x3d, 0xcf, 0x71, 0x2b, 0x0e, 0xaa, 0x01, 0x00, 0x05, 0xe0, 0x16, 0x06, + 0x24, 0x98, 0x1a, 0x8e, 0x59, 0x5f, 0x76, 0xd8, 0xfc, 0xe7, 0xe1, 0x1e, + 0xfa, 0x15, 0x2e, 0x20, 0x1b, 0x15, 0xf3, 0x31, 0xae, 0x2d, 0xc7, 0xb8, + 0xfb, 0x47, 0xf7, 0xe5, 0xb8, 0x15, 0x0c, 0x0c, 0xe9, 0x00, 0xb0, 0x33, + 0xa3, 0xf5, 0x64, 0xa5, 0x2e, 0x9d, 0xba, 0x3e, 0xec, 0xcd, 0xb7, 0x59, + 0xeb, 0xbc, 0xf0, 0x06, 0x80, 0x17, 0x86, 0x00, 0x80, 0x99, 0xd0, 0x4a, + 0x4a, 0x53, 0xff, 0xee, 0xad, 0x8e, 0x56, 0x73, 0xd8, 0x83, 0x7d, 0x2a, + 0x94, 0x24, 0x31, 0x09, 0xcd, 0xd6, 0x1e, 0x3a, 0x6f, 0xf5, 0xf9, 0x20, + 0x0c, 0x80, 0x4e, 0x00, 0x78, 0x43, 0xe5, 0xa7, 0x74, 0x8d, 0xdf, 0x71, + 0xeb, 0xfb, 0xba, 0xc5, 0xeb, 0x30, 0x0c, 0x40, 0x2f, 0x49, 0x34, 0xb7, + 0x2b, 0x01, 0x8c, 0x37, 0x25, 0x47, 0xa7, 0xa0, 0x91, 0xd2, 0x23, 0x6c, + 0xd8, 0xeb, 0xdb, 0x52, 0x40, 0x61, 0x03, 0x46, 0xb2, 0xd9, 0xa1, 0xfb, + 0x35, 0xfc, 0xbc, 0x07, 0x60, 0x0e, 0xca, 0x21, 0xe2, 0x81, 0x08, 0x1b, + 0x74, 0xa1, 0x6e, 0x95, 0xed, 0xd4, 0x76, 0x34, 0xe3, 0x6b, 0xc0, 0xa0, + 0x03, 0xdc, 0x4c, 0x02, 0x9c, 0x98, 0x90, 0xc2, 0x5f, 0xee, 0xb7, 0x24, + 0x8d, 0xfd, 0xc6, 0x9a, 0x72, 0xaf, 0xa4, 0xd2, 0xdc, 0x02, 0xc2, 0x86, + 0x0c, 0x65, 0x3b, 0x9f, 0xe9, 0x1b, 0xef, 0xe7, 0x00, 0x30, 0x00, 0x72, + 0x1a, 0x4c, 0x2d, 0x3c, 0xb0, 0x94, 0x24, 0xcc, 0xa1, 0x8a, 0x50, 0xc1, + 0x46, 0x35, 0xd6, 0xe0, 0x3b, 0x02, 0x85, 0x16, 0x4b, 0x21, 0x13, 0x3e, + 0xe7, 0xe2, 0x5f, 0x7c, 0x8e, 0xee, 0xad, 0x89, 0xcf, 0xef, 0x63, 0x4b, + 0x20, 0x30, 0x8d, 0xff, 0xd9, 0x9c, 0xf6, 0x36, 0xa7, 0xf7, 0xf3, 0xe0, + 0x0c, 0x00, 0x2f, 0x00, 0x3c, 0x25, 0xe2, 0xf2, 0x4b, 0xc9, 0xe3, 0x45, + 0x8d, 0x37, 0xac, 0x79, 0x12, 0xe8, 0xa0, 0x03, 0x5c, 0x51, 0x34, 0xa2, + 0xd0, 0x4c, 0x29, 0x08, 0x47, 0xfc, 0x95, 0x9d, 0x7f, 0xa1, 0x9f, 0xfe, + 0x7b, 0x1f, 0x87, 0x5e, 0xc2, 0x90, 0x06, 0x10, 0xff, 0xb1, 0x24, 0xf8, + 0x7f, 0x7f, 0x36, 0x01, 0xd8, 0x03, 0xb0, 0x32, 0x5e, 0xe5, 0x15, 0xb6, + 0xc3, 0x06, 0xb9, 0xe5, 0xa3, 0x77, 0x19, 0xc7, 0x67, 0x77, 0x1f, 0x6a, + 0x00, 0xcc, 0x01, 0xf8, 0x01, 0xd9, 0x45, 0x15, 0x90, 0x33, 0x66, 0x4f, + 0x01, 0x5b, 0x32, 0x85, 0x2e, 0xf7, 0xf4, 0xab, 0x13, 0x0a, 0xc3, 0x1b, + 0x12, 0x0e, 0x90, 0xdf, 0x7f, 0x36, 0x01, 0x88, 0x03, 0xb0, 0x0a, 0xc0, + 0xa6, 0x26, 0x62, 0xb1, 0x4e, 0x49, 0x1c, 0xdb, 0xbe, 0xc8, 0xa0, 0x00, + 0x84, 0x10, 0x82, 0x80, 0x2c, 0xc5, 0x6d, 0xc6, 0x12, 0x9c, 0xc1, 0x8c, + 0xdf, 0x89, 0xf7, 0xd2, 0x69, 0x30, 0x0b, 0x0a, 0xdb, 0x66, 0x3b, 0x8d, + 0x34, 0xf8, 0x1b, 0xee, 0x52, 0x94, 0xbc, 0xfb, 0x5b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0x80, 0x00, 0x00, 0x01, 0x12, + 0x73, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4b, 0x31, 0x5b, 0x25, 0x3b, 0x7f, 0xf3, 0x8e, 0xce, + 0x45, 0xd7, 0x9c, 0x0d, 0x01, 0x32, 0x03, 0x08, 0x60, 0x16, 0x01, 0x90, + 0x97, 0xd9, 0xfa, 0x19, 0x0b, 0xdb, 0x9f, 0xbf, 0xe6, 0x81, 0x2b, 0x5e, + 0x9d, 0xbb, 0x07, 0xd2, 0xd7, 0xeb, 0x29, 0x63, 0x0d, 0x0c, 0x49, 0x30, + 0xbe, 0x52, 0x73, 0x74, 0x76, 0xf6, 0xcb, 0x33, 0xe7, 0x72, 0x94, 0xa4, + 0xa7, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x5e, 0x5c, 0x06, 0x00, 0x3a, 0x28, + 0x9a, 0x51, 0x34, 0xb4, 0xa5, 0x38, 0x0c, 0x21, 0xdb, 0x6c, 0xb1, 0xcb, + 0xfc, 0x2d, 0x57, 0xa8, 0xb4, 0xdf, 0x7f, 0xf0, 0xea, 0x6e, 0x52, 0x94, + 0xb3, 0xaa, 0xe5, 0x29, 0x79, 0x70, 0x1d, 0x00, 0xe8, 0xa2, 0x69, 0x44, + 0xd2, 0xd2, 0x94, 0xe0, 0x30, 0x87, 0x6d, 0xb2, 0xc7, 0x2f, 0xf0, 0xbb, + 0x4f, 0xf2, 0x33, 0xfa, 0x4f, 0xbf, 0x51, 0x4a, 0x52, 0xce, 0xab, 0x94, + 0xa9, 0x4a, 0x3a, 0x0f, 0xcc, 0xa7, 0x0e, 0xf5, 0xd1, 0x17, 0x29, 0x4a, + 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, 0x91, + 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x97, 0x9a, 0x00, + 0x3e, 0x40, 0x01, 0xe7, 0x21, 0x8d, 0xe4, 0x9c, 0x8d, 0x98, 0xfe, 0x69, + 0xbc, 0xc1, 0x16, 0xe0, 0x1b, 0x60, 0x0b, 0x30, 0x69, 0x2d, 0x0e, 0x34, + 0x61, 0x28, 0x0f, 0x89, 0xb6, 0x1b, 0x89, 0xec, 0x1f, 0x4b, 0x5f, 0x5e, + 0x94, 0xa5, 0x8d, 0x57, 0x29, 0x4b, 0x20, 0x61, 0x5c, 0x34, 0xb4, 0x23, + 0x01, 0xff, 0xb4, 0xe2, 0xf5, 0xd2, 0xf2, 0x21, 0x80, 0x26, 0x28, 0x9b, + 0xcb, 0x18, 0x1b, 0xd8, 0x33, 0x7f, 0xd2, 0x4a, 0xdb, 0x36, 0x1c, 0x05, + 0xdd, 0x66, 0x2a, 0xb0, 0x98, 0x03, 0xa0, 0xc2, 0x19, 0xc4, 0xdf, 0x82, + 0x50, 0x53, 0x72, 0x8d, 0x49, 0xc7, 0x27, 0x8e, 0xba, 0x09, 0x79, 0x62, + 0x85, 0x7a, 0x1a, 0xfd, 0x55, 0x2e, 0x50, 0x15, 0x26, 0x62, 0x10, 0x6a, + 0x4a, 0x1a, 0x37, 0xa3, 0xb7, 0xec, 0xcb, 0x14, 0xbf, 0x64, 0x26, 0x81, + 0x44, 0x13, 0x0a, 0x42, 0x49, 0x7d, 0xcb, 0xcf, 0x91, 0xd0, 0xdd, 0x9d, + 0x9f, 0x84, 0xf6, 0x39, 0x47, 0xd9, 0x8d, 0xca, 0x16, 0x74, 0xda, 0xfd, + 0x05, 0x29, 0x4b, 0x2a, 0xae, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x20, 0x00, + 0x00, 0x01, 0x13, 0x73, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xad, 0xfc, 0xc8, 0x0e, 0x90, 0x34, 0x9a, + 0x50, 0xae, 0xe1, 0x4b, 0xb8, 0x5c, 0xaf, 0xc8, 0x7c, 0xb4, 0x84, 0x20, + 0x6b, 0xbf, 0x22, 0x0f, 0xb7, 0x80, 0xeb, 0xa0, 0xa1, 0xa9, 0xff, 0x6e, + 0x33, 0xe4, 0x09, 0xbd, 0x87, 0xbd, 0x13, 0xd3, 0xc4, 0x35, 0x8d, 0x7f, + 0xf3, 0x07, 0x41, 0x35, 0x25, 0xa7, 0xec, 0xdb, 0x1f, 0xdc, 0x89, 0xee, + 0xea, 0x32, 0x0b, 0x3b, 0x36, 0x46, 0x74, 0xf3, 0x78, 0x1d, 0xb4, 0xe4, + 0xec, 0x4c, 0xf9, 0xd6, 0x94, 0x93, 0xb5, 0x90, 0x84, 0x5e, 0xd8, 0xb4, + 0x7e, 0x30, 0x6e, 0xcd, 0xc5, 0xeb, 0xa1, 0x98, 0x89, 0x45, 0xb8, 0xcc, + 0x4a, 0xfd, 0x38, 0x60, 0x12, 0x1e, 0x06, 0xaa, 0xba, 0x10, 0x8c, 0x8f, + 0xf0, 0xd4, 0x7c, 0x79, 0xaf, 0x76, 0xc2, 0x1f, 0x3f, 0x75, 0xaf, 0x90, + 0x2e, 0x08, 0xd4, 0x21, 0x05, 0xa1, 0xc6, 0xee, 0x71, 0x06, 0xca, 0xaa, + 0xdf, 0x6a, 0x0a, 0xc0, 0x60, 0x97, 0xb0, 0xd6, 0xfc, 0x97, 0xb9, 0x98, + 0xc0, 0x37, 0x7a, 0x42, 0xc6, 0x74, 0xf1, 0x9b, 0xec, 0x66, 0xf7, 0x88, + 0x28, 0xb0, 0xc2, 0x62, 0x72, 0x50, 0x97, 0x47, 0x40, 0x4a, 0x39, 0xee, + 0xc7, 0x01, 0xdb, 0xe8, 0x1e, 0xc3, 0x1f, 0xea, 0xaf, 0xfe, 0x7e, 0x8c, + 0xc4, 0x2f, 0xf8, 0x1f, 0xe4, 0xec, 0x38, 0x3e, 0xd6, 0x33, 0x24, 0x98, + 0x37, 0x6d, 0xb8, 0xd6, 0x53, 0xac, 0x2f, 0xd4, 0x27, 0xa1, 0x21, 0x85, + 0x23, 0x76, 0x18, 0x7b, 0x8b, 0x97, 0x06, 0xa3, 0x24, 0xac, 0x07, 0x90, + 0x4f, 0xe3, 0xda, 0xec, 0x59, 0x3e, 0xce, 0xf7, 0x96, 0x8c, 0x19, 0xc6, + 0x6c, 0x35, 0x26, 0x89, 0x1d, 0xae, 0x70, 0x68, 0x66, 0x03, 0x38, 0xa4, + 0x36, 0x52, 0xc8, 0xff, 0xda, 0xb7, 0x42, 0x03, 0x77, 0x1a, 0xeb, 0x3c, + 0x70, 0x59, 0xf7, 0x3f, 0x06, 0xa3, 0x13, 0x7f, 0x77, 0xdd, 0xdf, 0xb0, + 0xf9, 0xac, 0x9c, 0x4f, 0x8b, 0xf4, 0x19, 0x25, 0x6f, 0xf0, 0xc7, 0xc6, + 0x0f, 0x12, 0x7d, 0xe6, 0x83, 0x0a, 0xfc, 0x85, 0xc0, 0x7a, 0x95, 0x88, + 0xcb, 0x70, 0xb5, 0x5e, 0xcd, 0x39, 0x08, 0x3b, 0x8d, 0xbc, 0x40, 0x0d, + 0xf1, 0x40, 0x63, 0x65, 0xed, 0x82, 0x43, 0xcf, 0xa3, 0x8a, 0x01, 0xf4, + 0xef, 0xda, 0xa5, 0x25, 0x33, 0x21, 0x01, 0xff, 0xac, 0x81, 0x02, 0x66, + 0x1a, 0x5e, 0xc3, 0xfc, 0x4b, 0xc5, 0x23, 0x84, 0x71, 0xa2, 0xee, 0x58, + 0x14, 0x4e, 0x42, 0x7f, 0xfd, 0x95, 0xd8, 0x4d, 0xcc, 0x65, 0x77, 0xcd, + 0x04, 0x3f, 0x90, 0xde, 0x73, 0x1e, 0xe2, 0xb5, 0x68, 0x62, 0x10, 0x4d, + 0x4f, 0x46, 0xcd, 0x9b, 0xac, 0xd1, 0x35, 0xa1, 0x85, 0xa1, 0x1d, 0x38, + 0xc4, 0xa3, 0xa0, 0x69, 0x15, 0xa7, 0x42, 0x50, 0x18, 0x84, 0x9a, 0x94, + 0xa8, 0x46, 0xba, 0x96, 0x46, 0x6b, 0x3a, 0xc3, 0xda, 0xde, 0x19, 0x08, + 0x0c, 0x40, 0x0c, 0x30, 0x6a, 0x4a, 0x43, 0xf2, 0xdb, 0x12, 0x5d, 0x3b, + 0x61, 0xef, 0xf6, 0x16, 0x4e, 0xbd, 0xd5, 0xe4, 0xc0, 0x62, 0x1a, 0x84, + 0xa0, 0x84, 0x52, 0x3b, 0xe2, 0x8a, 0x58, 0x0a, 0xf9, 0xf7, 0x12, 0x5f, + 0xb6, 0x16, 0xbb, 0x5d, 0x5f, 0xe3, 0x19, 0xbe, 0xe5, 0xe4, 0x1f, 0xd9, + 0x08, 0x23, 0xd1, 0x28, 0x46, 0x25, 0x6e, 0xaf, 0xc9, 0x2c, 0x66, 0x20, + 0xd9, 0x72, 0x72, 0x49, 0x99, 0x03, 0x46, 0x74, 0x7e, 0x3d, 0x7b, 0x88, + 0xbb, 0xc8, 0xc5, 0x7e, 0xdf, 0xfc, 0xa5, 0x71, 0x63, 0xfd, 0xd6, 0x16, + 0xab, 0x3b, 0xca, 0x5e, 0xc5, 0x3a, 0x12, 0x86, 0xe6, 0x30, 0xe2, 0x2c, + 0x03, 0x72, 0x32, 0x0a, 0x53, 0x7f, 0x9f, 0x91, 0x0f, 0x90, 0xb2, 0xf7, + 0x41, 0x48, 0xeb, 0xf8, 0xd1, 0x43, 0xae, 0x68, 0x6a, 0x4a, 0xe5, 0x8d, + 0x1a, 0xe7, 0x32, 0x8e, 0x85, 0xa8, 0x3e, 0x2f, 0xa2, 0x43, 0x46, 0x28, + 0x66, 0x65, 0xb3, 0xfb, 0x0a, 0x50, 0x8c, 0x18, 0x57, 0x66, 0xfd, 0x64, + 0x03, 0x42, 0xed, 0xc4, 0x34, 0xa0, 0xb2, 0xdf, 0x77, 0x16, 0x2a, 0x62, + 0x6a, 0x50, 0x92, 0xf3, 0x70, 0x8c, 0xda, 0x79, 0xd4, 0x17, 0xe2, 0xf9, + 0xa1, 0xa8, 0x42, 0x32, 0x3b, 0x33, 0xa0, 0xe7, 0x34, 0x8f, 0x10, 0xd0, + 0xcd, 0xd2, 0x52, 0x11, 0x9b, 0xfd, 0x9c, 0x73, 0xdb, 0x8b, 0x2f, 0x16, + 0x4d, 0x70, 0x3d, 0x94, 0x1d, 0xae, 0x20, 0x19, 0x42, 0x08, 0x63, 0x5d, + 0xf2, 0xed, 0x16, 0x4e, 0xf4, 0xd6, 0x1b, 0x0a, 0x26, 0x06, 0x0d, 0x4f, + 0xe8, 0x62, 0x12, 0x1f, 0x72, 0xfb, 0x8d, 0x4a, 0x94, 0xc2, 0xdd, 0xba, + 0xb3, 0x5f, 0x42, 0xbc, 0xd1, 0x34, 0x94, 0x8f, 0xc0, 0x52, 0x03, 0x7e, + 0xb6, 0x3c, 0xd3, 0x7f, 0xe7, 0xf1, 0x43, 0xef, 0x47, 0x8f, 0x5d, 0xe7, + 0xa0, 0x7a, 0xed, 0xbd, 0x7f, 0xe1, 0xcb, 0xe3, 0x03, 0x03, 0x10, 0x02, + 0x3f, 0x69, 0x0d, 0xc8, 0x42, 0x0a, 0x6c, 0xdf, 0xe7, 0xe1, 0xe7, 0x59, + 0x88, 0x69, 0x29, 0x29, 0xcd, 0xdf, 0x2b, 0x08, 0xd3, 0x90, 0xd2, 0x84, + 0x96, 0xfb, 0xb9, 0x19, 0xae, 0xcc, 0x61, 0xe6, 0xfb, 0x2b, 0xb8, 0x9b, + 0xb6, 0xc5, 0x6d, 0xb6, 0xec, 0xfc, 0xfb, 0x9b, 0xc3, 0x10, 0xe2, 0x90, + 0x8c, 0x34, 0x48, 0xea, 0xe2, 0x1a, 0x51, 0xcb, 0x1b, 0xbb, 0x9c, 0xd8, + 0x74, 0xc4, 0xc2, 0xf0, 0xc2, 0xd1, 0xd2, 0x33, 0xa5, 0x97, 0x52, 0xdb, + 0x45, 0x69, 0xa5, 0x25, 0x23, 0x13, 0x3f, 0x2d, 0x19, 0x09, 0xff, 0x12, + 0xb6, 0x3c, 0x5b, 0x9e, 0xa6, 0x1f, 0x7b, 0x17, 0x6b, 0x9c, 0x58, 0xcc, + 0xad, 0xc6, 0x80, 0xf4, 0xe0, 0xec, 0x41, 0xba, 0x37, 0x9b, 0xad, 0xaf, + 0xc0, 0x93, 0x00, 0xb0, 0x0c, 0x08, 0x69, 0x02, 0x7f, 0x23, 0x11, 0x49, + 0xd7, 0xad, 0x26, 0x24, 0x61, 0x68, 0xe3, 0x93, 0xb8, 0x57, 0xbc, 0xa8, + 0x6f, 0x46, 0x02, 0x85, 0x14, 0x82, 0xd1, 0xfb, 0xf4, 0xa7, 0x0b, 0x22, + 0xde, 0xef, 0x2a, 0xe9, 0x42, 0xc6, 0x8b, 0xfc, 0x81, 0x47, 0x32, 0xb0, + 0xf0, 0x00, 0x3b, 0x26, 0x20, 0x00, 0xe4, 0x34, 0x6f, 0x08, 0xc0, 0x5d, + 0x22, 0x3e, 0xdd, 0x82, 0xfb, 0x5e, 0xc4, 0x9a, 0x1b, 0xc9, 0xa5, 0xf4, + 0xf3, 0x49, 0xc2, 0xaf, 0x1a, 0x01, 0x98, 0x15, 0x28, 0x37, 0xb1, 0x4d, + 0xc6, 0xe4, 0x72, 0xd2, 0x6f, 0xfb, 0x33, 0x08, 0x22, 0xdf, 0x42, 0x90, + 0x63, 0x8c, 0xdf, 0x8e, 0x69, 0xfb, 0x94, 0xa5, 0x2e, 0x0b, 0x5b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x88, 0x00, 0x00, 0x01, 0x14, 0x83, 0xfb, 0xfd, + 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0x00, 0x00, 0x01, 0x15, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, 0x16, 0x83, + 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0x00, 0x00, 0x01, 0x17, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, + 0x18, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, 0x19, 0x83, 0xfb, 0xfd, 0x29, + 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, + 0x00, 0x01, 0x1a, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, 0x1b, 0x83, 0xfb, + 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0x00, 0x00, 0x01, 0x1c, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, 0x1d, + 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, 0x1e, 0x83, 0xfb, 0xfd, 0x29, 0x49, + 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, + 0x01, 0x1f, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, 0x20, 0x83, 0xfb, 0xfd, + 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0x00, 0x00, 0x01, 0x21, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, 0x22, 0x83, + 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0x00, 0x00, 0x01, 0x23, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, + 0x24, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, 0xb7, +}; diff --git a/plugins/streamdev/streamdev-cvs/server/suspend.h b/plugins/streamdev/streamdev-cvs/server/suspend.h new file mode 100644 index 0000000..bea25ee --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/server/suspend.h @@ -0,0 +1,36 @@ +/* + * $Id: suspend.h,v 1.2 2008/10/22 11:59:32 schmirl Exp $ + */ + +#ifndef VDR_STREAMDEV_SUSPEND_H +#define VDR_STREAMDEV_SUSPEND_H + +#include <vdr/player.h> + +class cSuspendLive: public cPlayer, public cThread { +protected: + virtual void Activate(bool On); + virtual void Action(void); + + void Stop(void); + +public: + cSuspendLive(void); + virtual ~cSuspendLive(); +}; + +class cSuspendCtl: public cControl { +private: + cSuspendLive *m_Suspend; + static bool m_Active; + +public: + cSuspendCtl(void); + virtual ~cSuspendCtl(); + virtual void Hide(void) {} + virtual eOSState ProcessKey(eKeys Key); + + static bool IsActive(void) { return m_Active; } +}; + +#endif // VDR_STREAMDEV_SUSPEND_H diff --git a/plugins/streamdev/streamdev-cvs/streamdev-client.c b/plugins/streamdev/streamdev-cvs/streamdev-client.c new file mode 100644 index 0000000..bc9403c --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/streamdev-client.c @@ -0,0 +1,60 @@ +/* + * streamdev.c: A plugin for the Video Disk Recorder + * + * See the README file for copyright information and how to reach the author. + * + * $Id: streamdev-client.c,v 1.6 2008/04/08 14:18:15 schmirl Exp $ + */ + +#include "streamdev-client.h" +#include "client/device.h" +#include "client/setup.h" + +#if !defined(APIVERSNUM) || APIVERSNUM < 10509 +#error "VDR-1.5.9 API version or greater is required!" +#endif + +const char *cPluginStreamdevClient::DESCRIPTION = trNOOP("VTP Streaming Client"); + +cPluginStreamdevClient::cPluginStreamdevClient(void) { +} + +cPluginStreamdevClient::~cPluginStreamdevClient() { +} + +const char *cPluginStreamdevClient::Description(void) { + return tr(DESCRIPTION); +} + +bool cPluginStreamdevClient::Start(void) { + I18nRegister(PLUGIN_NAME_I18N); + cStreamdevDevice::Init(); + return true; +} + +void cPluginStreamdevClient::Housekeeping(void) { + if (StreamdevClientSetup.StartClient && StreamdevClientSetup.SyncEPG) + ClientSocket.SynchronizeEPG(); +} + +const char *cPluginStreamdevClient::MainMenuEntry(void) { + return StreamdevClientSetup.StartClient && !StreamdevClientSetup.HideMenuEntry ? tr("Suspend Server") : NULL; +} + +cOsdObject *cPluginStreamdevClient::MainMenuAction(void) { + if (ClientSocket.SuspendServer()) + Skins.Message(mtInfo, tr("Server is suspended")); + else + Skins.Message(mtError, tr("Couldn't suspend Server!")); + return NULL; +} + +cMenuSetupPage *cPluginStreamdevClient::SetupMenu(void) { + return new cStreamdevClientMenuSetupPage; +} + +bool cPluginStreamdevClient::SetupParse(const char *Name, const char *Value) { + return StreamdevClientSetup.SetupParse(Name, Value); +} + +VDRPLUGINCREATOR(cPluginStreamdevClient); // Don't touch this! diff --git a/plugins/streamdev/streamdev-cvs/streamdev-client.h b/plugins/streamdev/streamdev-cvs/streamdev-client.h new file mode 100644 index 0000000..b01e0d7 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/streamdev-client.h @@ -0,0 +1,29 @@ +/* + * $Id: streamdev-client.h,v 1.1.1.1 2004/12/30 22:43:59 lordjaxom Exp $ + */ + +#ifndef VDR_STREAMDEVCLIENT_H +#define VDR_STREAMDEVCLIENT_H + +#include "common.h" + +#include <vdr/plugin.h> + +class cPluginStreamdevClient : public cPlugin { +private: + static const char *DESCRIPTION; + +public: + cPluginStreamdevClient(void); + virtual ~cPluginStreamdevClient(); + virtual const char *Version(void) { return VERSION; } + virtual const char *Description(void); + virtual bool Start(void); + virtual void Housekeeping(void); + virtual const char *MainMenuEntry(void); + virtual cOsdObject *MainMenuAction(void); + virtual cMenuSetupPage *SetupMenu(void); + virtual bool SetupParse(const char *Name, const char *Value); +}; + +#endif // VDR_STREAMDEVCLIENT_H diff --git a/plugins/streamdev/streamdev-cvs/streamdev-server.c b/plugins/streamdev/streamdev-cvs/streamdev-server.c new file mode 100644 index 0000000..3593d9f --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/streamdev-server.c @@ -0,0 +1,143 @@ +/* + * streamdev.c: A plugin for the Video Disk Recorder + * + * See the README file for copyright information and how to reach the author. + * + * $Id: streamdev-server.c,v 1.12 2009/06/19 06:32:38 schmirl Exp $ + */ + +#include <getopt.h> +#include <vdr/tools.h> +#include "streamdev-server.h" +#include "server/setup.h" +#include "server/server.h" +#include "server/suspend.h" + +#if !defined(APIVERSNUM) || APIVERSNUM < 10509 +#error "VDR-1.5.9 API version or greater is required!" +#endif + +const char *cPluginStreamdevServer::DESCRIPTION = trNOOP("VDR Streaming Server"); + +cPluginStreamdevServer::cPluginStreamdevServer(void) +{ +} + +cPluginStreamdevServer::~cPluginStreamdevServer() +{ + free(opt_auth); + free(opt_remux); +} + +const char *cPluginStreamdevServer::Description(void) +{ + return tr(DESCRIPTION); +} + +const char *cPluginStreamdevServer::CommandLineHelp(void) +{ + // return a string that describes all known command line options. + return + " -a <LOGIN:PASSWORD>, --auth=<LOGIN:PASSWORD> Credentials for HTTP authentication.\n" + " -r <CMD>, --remux=<CMD> Define an external command for remuxing.\n" + ; +} + +bool cPluginStreamdevServer::ProcessArgs(int argc, char *argv[]) +{ + // implement command line argument processing here if applicable. + static const struct option long_options[] = { + { "auth", required_argument, NULL, 'a' }, + { "remux", required_argument, NULL, 'r' }, + { NULL, 0, NULL, 0 } + }; + + int c; + while((c = getopt_long(argc, argv, "a:r:", long_options, NULL)) != -1) { + switch (c) { + case 'a': + { + if (opt_auth) + free(opt_auth); + int l = strlen(optarg); + cBase64Encoder Base64((uchar*) optarg, l, l * 4 / 3 + 3); + const char *s = Base64.NextLine(); + if (s) + opt_auth = strdup(s); + } + break; + case 'r': + if (opt_remux) + free(opt_remux); + opt_remux = strdup(optarg); + break; + default: + return false; + } + } + return true; +} + +bool cPluginStreamdevServer::Start(void) +{ + I18nRegister(PLUGIN_NAME_I18N); + if (!StreamdevHosts.Load(STREAMDEVHOSTSPATH, true, true)) { + esyslog("streamdev-server: error while loading %s", STREAMDEVHOSTSPATH); + fprintf(stderr, "streamdev-server: error while loading %s\n", STREAMDEVHOSTSPATH); + if (access(STREAMDEVHOSTSPATH, F_OK) != 0) { + fprintf(stderr, " Please install streamdevhosts.conf into the path " + "printed above. Without it\n" + " no client will be able to access your streaming-" + "server. An example can be\n" + " found together with this plugin's sources.\n"); + } + return false; + } + if (!opt_remux) + opt_remux = strdup(DEFAULT_EXTERNREMUX); + + cStreamdevServer::Initialize(); + + return true; +} + +void cPluginStreamdevServer::Stop(void) +{ + cStreamdevServer::Destruct(); +} + +cString cPluginStreamdevServer::Active(void) +{ + if (cStreamdevServer::Active()) + { + static const char *Message = NULL; + if (!Message) Message = tr("Streaming active"); + return Message; + } + return NULL; +} + +const char *cPluginStreamdevServer::MainMenuEntry(void) +{ + if (StreamdevServerSetup.SuspendMode == smOffer && !cSuspendCtl::IsActive()) + return tr("Suspend Live TV"); + return NULL; +} + +cOsdObject *cPluginStreamdevServer::MainMenuAction(void) +{ + cControl::Launch(new cSuspendCtl); + return NULL; +} + +cMenuSetupPage *cPluginStreamdevServer::SetupMenu(void) +{ + return new cStreamdevServerMenuSetupPage; +} + +bool cPluginStreamdevServer::SetupParse(const char *Name, const char *Value) +{ + return StreamdevServerSetup.SetupParse(Name, Value); +} + +VDRPLUGINCREATOR(cPluginStreamdevServer); // Don't touch this! diff --git a/plugins/streamdev/streamdev-cvs/streamdev-server.h b/plugins/streamdev/streamdev-cvs/streamdev-server.h new file mode 100644 index 0000000..8149e4b --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/streamdev-server.h @@ -0,0 +1,33 @@ +/* + * $Id: streamdev-server.h,v 1.4 2007/02/19 12:08:16 schmirl Exp $ + */ + +#ifndef VDR_STREAMDEVSERVER_H +#define VDR_STREAMDEVSERVER_H + +#include "common.h" + +#include <vdr/plugin.h> + +class cPluginStreamdevServer : public cPlugin { +private: + static const char *DESCRIPTION; + +public: + cPluginStreamdevServer(void); + virtual ~cPluginStreamdevServer(); + + virtual const char *Version(void) { return VERSION; } + virtual const char *Description(void); + virtual const char *CommandLineHelp(void); + virtual bool ProcessArgs(int argc, char *argv[]); + virtual bool Start(void); + virtual void Stop(void); + virtual cString Active(void); + virtual const char *MainMenuEntry(void); + virtual cOsdObject *MainMenuAction(void); + virtual cMenuSetupPage *SetupMenu(void); + virtual bool SetupParse(const char *Name, const char *Value); +}; + +#endif // VDR_STREAMDEVSERVER_H diff --git a/plugins/streamdev/streamdev-cvs/streamdev/CVS/Entries b/plugins/streamdev/streamdev-cvs/streamdev/CVS/Entries new file mode 100644 index 0000000..15bfaed --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/streamdev/CVS/Entries @@ -0,0 +1,3 @@ +/externremux.sh/1.1/Mon Apr 7 14:50:36 2008// +/streamdevhosts.conf/1.2/Fri Feb 13 10:39:24 2009// +D diff --git a/plugins/streamdev/streamdev-cvs/streamdev/CVS/Repository b/plugins/streamdev/streamdev-cvs/streamdev/CVS/Repository new file mode 100644 index 0000000..90fe6a1 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/streamdev/CVS/Repository @@ -0,0 +1 @@ +streamdev/streamdev diff --git a/plugins/streamdev/streamdev-cvs/streamdev/CVS/Root b/plugins/streamdev/streamdev-cvs/streamdev/CVS/Root new file mode 100644 index 0000000..2c7f6ce --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/streamdev/CVS/Root @@ -0,0 +1 @@ +:pserver:anoncvs@vdr-developer.org:/var/cvsroot diff --git a/plugins/streamdev/streamdev-cvs/streamdev/externremux.sh b/plugins/streamdev/streamdev-cvs/streamdev/externremux.sh new file mode 100755 index 0000000..e2b4156 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/streamdev/externremux.sh @@ -0,0 +1,48 @@ +#!/bin/sh +# +# externremux.sh - sample remux script using mencoder for remuxing. +# +# Install this script as VDRCONFDIR/plugins/streamdev/externremux.sh +# +# The parameter STREAMQUALITY selects the default remux parameters. Adjust +# to your needs and point your web browser to http://servername:3000/extern/ +# To select different remux parameters on the fly, insert a semicolon and +# the name of the requested quality: http://servername:3000/extern;WLAN11/ + +# CONFIG START + STREAMQUALITY="DSL6000" # DSL{1,2,3,6}000, LAN10, WLAN{11,54}, IPAQ + TMP=/tmp/externremux-${RANDOM:-$$} + MENCODER=mencoder +# CONFIG END + +mkdir -p $TMP +mkfifo $TMP/out.avi +(trap "rm -rf $TMP" EXIT HUP INT TERM ABRT; cat $TMP/out.avi) & + +case ${1:-$STREAMQUALITY} in + DSL1000) exec $MENCODER -ovc lavc -lavcopts vcodec=mpeg4:vbitrate=100 \ + -oac mp3lame -lameopts preset=15:mode=3 -vf scale=160:104 \ + -o $TMP/out.avi -- - &>$TMP/out.log ;; + DSL2000) exec $MENCODER -ovc lavc -lavcopts vcodec=mpeg4:vbitrate=128 \ + -oac mp3lame -lameopts preset=15:mode=3 -vf scale=160:104 \ + -o $TMP/out.avi -- - &>$TMP/out.log ;; + DSL3000) exec $MENCODER -ovc lavc -lavcopts vcodec=mpeg4:vbitrate=250 \ + -oac mp3lame -lameopts preset=15:mode=3 -vf scale=320:208 \ + -o $TMP/out.avi -- - &>$TMP/out.log ;; + DSL6000) exec $MENCODER -ovc lavc -lavcopts vcodec=mpeg4:vbitrate=350 \ + -oac mp3lame -lameopts preset=15:mode=3 -vf scale=320:208 \ + -o $TMP/out.avi -- - &>$TMP/out.log ;; + LAN10) exec $MENCODER -ovc lavc -lavcopts vcodec=mpeg4:vbitrate=4096 \ + -oac mp3lame -lameopts preset=standard \ + -o $TMP/out.avi -- - &>$TMP/out.log ;; + WLAN11) exec $MENCODER -ovc lavc -lavcopts vcodec=mpeg4:vbitrate=768 \ + -oac mp3lame -lameopts preset=standard -vf scale=640:408 \ + -o $TMP/out.avi -- - &>$TMP/out.log ;; + WLAN54) exec $MENCODER -ovc lavc -lavcopts vcodec=mpeg4:vbitrate=2048 \ + -oac mp3lame -lameopts preset=standard \ + -o $TMP/out.avi -- - &>$TMP/out.log ;; + IPAQ) exec $MENCODER -ovc lavc -lavcopts vcodec=mpeg4:vbitrate=350 \ + -oac mp3lame -lameopts preset=15:mode=3 -vf scale=320:208 \ + -o $TMP/out.avi -- - &>$TMP/out.log ;; + *) touch $TMP/out.avi ;; +esac diff --git a/plugins/streamdev/streamdev-cvs/streamdev/streamdevhosts.conf b/plugins/streamdev/streamdev-cvs/streamdev/streamdevhosts.conf new file mode 100644 index 0000000..49882a2 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/streamdev/streamdevhosts.conf @@ -0,0 +1,14 @@ +# +# streamdevhosts This file describes a number of host addresses that +# are allowed to connect to the streamdev server running +# with the Video Disk Recorder (VDR) on this system. +# Syntax: +# +# IP-Address[/Netmask] +# + +127.0.0.1 # always accept localhost +#192.168.100.0/24 # any host on the local net +#204.152.189.113 # a specific host +#239.255.0.0/16 # uncomment for IGMP multicast streaming +#0.0.0.0/0 # any host on any net (USE THIS WITH CARE!) diff --git a/plugins/streamdev/streamdev-cvs/tools/CVS/Entries b/plugins/streamdev/streamdev-cvs/tools/CVS/Entries new file mode 100644 index 0000000..b27688c --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/tools/CVS/Entries @@ -0,0 +1,9 @@ +/select.c/1.4/Thu Mar 13 15:58:24 2008// +/select.h/1.2/Mon Apr 2 10:32:35 2007// +/socket.c/1.7/Fri Sep 4 13:24:30 2009// +/socket.h/1.4/Fri Feb 13 10:39:24 2009// +/source.c/1.7/Fri Jul 20 06:54:03 2007// +/source.h/1.3/Tue Feb 8 19:54:52 2005// +/tools.c/1.1.1.1/Thu Dec 30 22:44:30 2004// +/tools.h/1.1.1.1/Thu Dec 30 22:44:30 2004// +D diff --git a/plugins/streamdev/streamdev-cvs/tools/CVS/Repository b/plugins/streamdev/streamdev-cvs/tools/CVS/Repository new file mode 100644 index 0000000..ba0320d --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/tools/CVS/Repository @@ -0,0 +1 @@ +streamdev/tools diff --git a/plugins/streamdev/streamdev-cvs/tools/CVS/Root b/plugins/streamdev/streamdev-cvs/tools/CVS/Root new file mode 100644 index 0000000..2c7f6ce --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/tools/CVS/Root @@ -0,0 +1 @@ +:pserver:anoncvs@vdr-developer.org:/var/cvsroot diff --git a/plugins/streamdev/streamdev-cvs/tools/select.c b/plugins/streamdev/streamdev-cvs/tools/select.c new file mode 100644 index 0000000..2a3abe5 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/tools/select.c @@ -0,0 +1,51 @@ +#include "tools/select.h" + +#include <vdr/tools.h> +#include <sys/time.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> +#include <errno.h> + +cTBSelect::cTBSelect(void) { + Clear(); +} + +cTBSelect::~cTBSelect() { +} + +int cTBSelect::Select(uint TimeoutMs) { + struct timeval tv; + ssize_t res = 0; + int ms; + + tv.tv_usec = (TimeoutMs % 1000) * 1000; + tv.tv_sec = TimeoutMs / 1000; + memcpy(m_FdsResult, m_FdsQuery, sizeof(m_FdsResult)); + + if (TimeoutMs == 0) + return ::select(m_MaxFiled + 1, &m_FdsResult[0], &m_FdsResult[1], NULL, &tv); + + cTimeMs starttime; + ms = TimeoutMs; + while (ms > 0 && (res = ::select(m_MaxFiled + 1, &m_FdsResult[0], &m_FdsResult[1], NULL, + &tv)) == -1 && errno == EINTR) { + ms = TimeoutMs - starttime.Elapsed(); + tv.tv_usec = (ms % 1000) * 1000; + tv.tv_sec = ms / 1000; + memcpy(m_FdsResult, m_FdsQuery, sizeof(m_FdsResult)); + } + if (ms <= 0 || res == 0) { + errno = ETIMEDOUT; + return -1; + } + return res; +} + +int cTBSelect::Select(void) { + ssize_t res; + do { + memcpy(m_FdsResult, m_FdsQuery, sizeof(m_FdsResult)); + } while ((res = ::select(m_MaxFiled + 1, &m_FdsResult[0], &m_FdsResult[1], NULL, NULL)) == -1 && errno == EINTR); + return res; +} diff --git a/plugins/streamdev/streamdev-cvs/tools/select.h b/plugins/streamdev/streamdev-cvs/tools/select.h new file mode 100644 index 0000000..a3622fd --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/tools/select.h @@ -0,0 +1,75 @@ +#ifndef TOOLBOX_SELECT_H +#define TOOLBOX_SELECT_H + +#include "tools/tools.h" + +#include <sys/types.h> + +/* cTBSelect provides an interface for polling UNIX-like file descriptors. */ + +class cTBSelect { +private: + int m_MaxFiled; + + fd_set m_FdsQuery[2]; + fd_set m_FdsResult[2]; + +public: + cTBSelect(void); + virtual ~cTBSelect(); + + /* Clear() resets the object for use in a new Select() call. All file + descriptors and their previous states are invalidated. */ + virtual void Clear(void); + + /* Add() adds a file descriptor to be polled in the next Select() call. + That call polls if the file is readable if Output is set to false, + writeable otherwise. */ + virtual bool Add(int Filed, bool Output = false); + + /* Select() polls all descriptors added by Add() and returns as soon as + one of those changes state (gets readable/writeable), or after + TimeoutMs milliseconds, whichever happens first. It returns the number + of filedescriptors that have changed state. On error, -1 is returned + and errno is set appropriately. */ + virtual int Select(uint TimeoutMs); + + /* Select() polls all descriptors added by Add() and returns as soon as + one of those changes state (gets readable/writeable). It returns the + number of filedescriptors that have changed state. On error, -1 is + returned and errno is set appropriately. */ + virtual int Select(void); + + /* CanRead() returns true if the descriptor has changed to readable during + the last Select() call. Otherwise false is returned. */ + virtual bool CanRead(int FileNo) const; + + /* CanWrite() returns true if the descriptor has changed to writeable + during the last Select() call. Otherwise false is returned. */ + virtual bool CanWrite(int FileNo) const; +}; + +inline void cTBSelect::Clear(void) { + FD_ZERO(&m_FdsQuery[0]); + FD_ZERO(&m_FdsQuery[1]); + m_MaxFiled = -1; +} + +inline bool cTBSelect::Add(int Filed, bool Output /* = false */) { + if (Filed < 0) return false; + FD_SET(Filed, &m_FdsQuery[Output ? 1 : 0]); + if (Filed > m_MaxFiled) m_MaxFiled = Filed; + return true; +} + +inline bool cTBSelect::CanRead(int FileNo) const { + if (FileNo < 0) return false; + return FD_ISSET(FileNo, &m_FdsResult[0]); +} + +inline bool cTBSelect::CanWrite(int FileNo) const { + if (FileNo < 0) return false; + return FD_ISSET(FileNo, &m_FdsResult[1]); +} + +#endif // TOOLBOX_SELECT_H diff --git a/plugins/streamdev/streamdev-cvs/tools/socket.c b/plugins/streamdev/streamdev-cvs/tools/socket.c new file mode 100644 index 0000000..5dde45a --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/tools/socket.c @@ -0,0 +1,169 @@ +#include "tools/socket.h" + +#include <vdr/tools.h> +#include <string.h> +#include <arpa/inet.h> +#include <unistd.h> +#include <errno.h> +#include <fcntl.h> + +// default class: best effort +#define DSCP_BE 0 +// gold class (video): assured forwarding 4 with lowest drop precedence +#define DSCP_AF41 34 << 2 +// premium class (voip): expedited forwarding +#define DSCP_EF 46 << 2 +// actual DSCP value used +#define STREAMDEV_DSCP DSCP_AF41 + +cTBSocket::cTBSocket(int Type, int Protocol) { + memset(&m_LocalAddr, 0, sizeof(m_LocalAddr)); + memset(&m_RemoteAddr, 0, sizeof(m_RemoteAddr)); + m_Type = Type; + m_Protocol = Protocol; +} + +cTBSocket::~cTBSocket() { + if (IsOpen()) Close(); +} + +bool cTBSocket::Connect(const std::string &Host, unsigned int Port) { + socklen_t len; + int socket; + + if (IsOpen()) Close(); + + if ((socket = ::socket(PF_INET, m_Type, m_Protocol)) == -1) + return false; + + m_LocalAddr.sin_family = AF_INET; + m_LocalAddr.sin_port = 0; + m_LocalAddr.sin_addr.s_addr = INADDR_ANY; + if (::bind(socket, (struct sockaddr*)&m_LocalAddr, sizeof(m_LocalAddr)) + == -1) { + ::close(socket); + return false; + } + + m_RemoteAddr.sin_family = AF_INET; + m_RemoteAddr.sin_port = htons(Port); + m_RemoteAddr.sin_addr.s_addr = inet_addr(Host.c_str()); + if (::connect(socket, (struct sockaddr*)&m_RemoteAddr, + sizeof(m_RemoteAddr)) == -1) { + ::close(socket); + return false; + } + + if (m_Type == SOCK_STREAM) { + len = sizeof(struct sockaddr_in); + if (::getpeername(socket, (struct sockaddr*)&m_RemoteAddr, &len) == -1) { + ::close(socket); + return false; + } + } + + len = sizeof(struct sockaddr_in); + if (::getsockname(socket, (struct sockaddr*)&m_LocalAddr, &len) == -1) { + ::close(socket); + return false; + } + + if (!cTBSource::Open(socket)) { + ::close(socket); + return false; + } + return true; +} + +bool cTBSocket::Listen(const std::string &Ip, unsigned int Port, int BackLog) { + int val; + socklen_t len; + int socket; + + if (IsOpen()) Close(); + + if ((socket = ::socket(PF_INET, m_Type, m_Protocol)) == -1) + return false; + + val = 1; + if (::setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) == -1) + return false; + + m_LocalAddr.sin_family = AF_INET; + m_LocalAddr.sin_port = htons(Port); + m_LocalAddr.sin_addr.s_addr = inet_addr(Ip.c_str()); + if (::bind(socket, (struct sockaddr*)&m_LocalAddr, sizeof(m_LocalAddr)) + == -1) + return false; + + len = sizeof(struct sockaddr_in); + if (::getsockname(socket, (struct sockaddr*)&m_LocalAddr, &len) == -1) + return false; + + if (m_Type == SOCK_STREAM && ::listen(socket, BackLog) == -1) + return false; + + if (!cTBSource::Open(socket)) + return false; + + return true; +} + +bool cTBSocket::Accept(const cTBSocket &Listener) { + socklen_t addrlen; + int socket; + + if (IsOpen()) Close(); + + addrlen = sizeof(struct sockaddr_in); + if ((socket = ::accept(Listener, (struct sockaddr*)&m_RemoteAddr, + &addrlen)) == -1) + return false; + + addrlen = sizeof(struct sockaddr_in); + if (::getsockname(socket, (struct sockaddr*)&m_LocalAddr, &addrlen) == -1) + return false; + + int sol=1; + // Ignore possible errors here, proceed as usual + ::setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, &sol, sizeof(sol)); + + if (!cTBSource::Open(socket)) + return false; + + return true; +} + +RETURNS(cTBSocket, cTBSocket::Accept(void) const, ret) + ret.Accept(*this); +RETURN(ret) + +bool cTBSocket::Close(void) { + bool ret = true; + + if (!IsOpen()) + ERRNUL(EBADF); + + if (::close(*this) == -1) + ret = false; + + if (!cTBSource::Close()) + ret = false; + + memset(&m_LocalAddr, 0, sizeof(m_LocalAddr)); + memset(&m_RemoteAddr, 0, sizeof(m_RemoteAddr)); + + return ret; +} + +bool cTBSocket::Shutdown(int how) { + if (!IsOpen()) + ERRNUL(EBADF); + + return ::shutdown(*this, how) != -1; +} + +bool cTBSocket::SetDSCP(void) { + int dscp = STREAMDEV_DSCP; + return ::setsockopt(*this, IPPROTO_IP, IP_TOS, &dscp, sizeof(dscp)) != -1; +} diff --git a/plugins/streamdev/streamdev-cvs/tools/socket.h b/plugins/streamdev/streamdev-cvs/tools/socket.h new file mode 100644 index 0000000..3dc7a33 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/tools/socket.h @@ -0,0 +1,119 @@ +#ifndef TOOLBOX_SOCKET_H +#define TOOLBOX_SOCKET_H + +#include "tools/tools.h" +#include "tools/source.h" + +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <string> + +/* cTBSocket provides a cTBSource-derived interface for input and output on + TCP/IPv4-sockets. */ + +class cTBSocket: public cTBSource { +private: + struct sockaddr_in m_LocalAddr; + struct sockaddr_in m_RemoteAddr; + + int m_Type; + int m_Protocol; + +public: + cTBSocket(int Type = SOCK_STREAM, int Protocol = 0); + virtual ~cTBSocket(); + + /* See cTBSource::SysRead() + Reimplemented for TCP/IPv4 sockets. */ + virtual ssize_t SysRead(void *Buffer, size_t Length) const; + + /* See cTBSource::SysWrite() + Reimplemented for TCP/IPv4 sockets. */ + virtual ssize_t SysWrite(const void *Buffer, size_t Length) const; + + /* Connect() tries to connect an available local socket to the port given + by Port of the target host given by Host in numbers-and-dots notation + (i.e. "212.43.45.21"). Returns true if the connection attempt was + successful and false otherwise, setting errno appropriately. */ + virtual bool Connect(const std::string &Host, uint Port); + + /* Shutdown() shuts down one or both ends of a socket. If called with How + set to SHUT_RD, further reads on this socket will be denied. If called + with SHUT_WR, all writes are denied. Called with SHUT_RDWR, all firther + action on this socket will be denied. Returns true on success and false + otherwise, setting errno appropriately. */ + virtual bool Shutdown(int How); + + /* Close() closes the associated socket and releases all structures. + Returns true on success and false otherwise, setting errno + appropriately. The object is in the closed state afterwards, regardless + of any errors. */ + virtual bool Close(void); + + /* Listen() listens on the local port Port for incoming connections. The + BackLog parameter defines the maximum length the queue of pending + connections may grow to. Returns true if the object is listening on + the specified port and false otherwise, setting errno appropriately. */ + virtual bool Listen(const std::string &Ip, uint Port, int BackLog); + + /* Accept() returns a newly created cTBSocket, which is connected to the + first connection request on the queue of pending connections of a + listening socket. If no connection request was pending, or if any other + error occured, the resulting cTBSocket is closed. */ + virtual cTBSocket Accept(void) const; + + /* Accept() extracts the first connection request on the queue of pending + connections of the listening socket Listener and connects it to this + object. Returns true on success and false otherwise, setting errno to + an appropriate value. */ + virtual bool Accept(const cTBSocket &Listener); + + /* Sets DSCP sockopt */ + bool SetDSCP(void); + + /* LocalPort() returns the port number this socket is connected to locally. + The result is undefined for a non-open socket. */ + int LocalPort(void) const { return ntohs(m_LocalAddr.sin_port); } + + /* RemotePort() returns the port number this socket is connected to on the + remote side. The result is undefined for a non-open socket. */ + int RemotePort(void) const { return ntohs(m_RemoteAddr.sin_port); } + + /* LocalIp() returns the internet address in numbers-and-dots notation of + the interface this socket is connected to locally. This can be + "0.0.0.0" for a listening socket listening to all interfaces. If the + socket is in its closed state, the result is undefined. */ + std::string LocalIp(void) const { return inet_ntoa(m_LocalAddr.sin_addr); } + + /* RemoteIp() returns the internet address in numbers-and-dots notation of + the interface this socket is connected to on the remote side. If the + socket is in its closed state, the result is undefined. */ + std::string RemoteIp(void) const { return inet_ntoa(m_RemoteAddr.sin_addr); } + + in_addr_t LocalIpAddr(void) const { return m_LocalAddr.sin_addr.s_addr; } + in_addr_t RemoteIpAddr(void) const { return m_RemoteAddr.sin_addr.s_addr; } + + int Type(void) const { return m_Type; } +}; + +inline ssize_t cTBSocket::SysRead(void *Buffer, size_t Length) const { + if (m_Type == SOCK_STREAM) + return ::recv(*this, Buffer, Length, 0); + else { + socklen_t len = sizeof(m_RemoteAddr); + return ::recvfrom(*this, Buffer, Length, 0, (sockaddr*)&m_RemoteAddr, &len); + } +} + +inline ssize_t cTBSocket::SysWrite(const void *Buffer, size_t Length) const { + return ::send(*this, Buffer, Length, 0); + if (m_Type == SOCK_STREAM) + return ::send(*this, Buffer, Length, 0); + else { + socklen_t len = sizeof(m_RemoteAddr); + return ::sendto(*this, Buffer, Length, 0, (sockaddr*)&m_RemoteAddr, len); + } +} + +#endif // TOOLBOX_SOCKET_H diff --git a/plugins/streamdev/streamdev-cvs/tools/source.c b/plugins/streamdev/streamdev-cvs/tools/source.c new file mode 100644 index 0000000..8a6b5f5 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/tools/source.c @@ -0,0 +1,171 @@ +#include "tools/source.h" +#include "tools/select.h" +#include "common.h" + +#include <vdr/tools.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +cTBSource::cTBSource(void) { + m_BytesRead = 0; + m_BytesWritten = 0; + m_Filed = -1; +} + +bool cTBSource::Open(int Filed, bool IsUnixFd) { + if (IsOpen()) + Close(); + + m_Filed = Filed; + if (IsUnixFd && ::fcntl(m_Filed, F_SETFL, O_NONBLOCK) == -1) + return false; + + return true; +} + +cTBSource::~cTBSource() { +} + +bool cTBSource::Close(void) { + if (!IsOpen()) { + errno = EBADF; + return false; + } + + m_Filed = -1; + return true; +} + +ssize_t cTBSource::Read(void *Buffer, size_t Length) { + ssize_t res; + while ((res = SysRead(Buffer, Length)) < 0 && errno == EINTR) + errno = 0; + if (res > 0) m_BytesRead += res; + return res; +} + +ssize_t cTBSource::Write(const void *Buffer, size_t Length) { + ssize_t res; + while ((res = SysWrite(Buffer, Length)) < 0 && errno == EINTR) + errno = 0; + if (res > 0) m_BytesWritten += res; + return res; +} + +bool cTBSource::TimedWrite(const void *Buffer, size_t Length, uint TimeoutMs) { + cTBSelect sel; + int ms, offs; + + cTimeMs starttime; + ms = TimeoutMs; + offs = 0; + sel.Clear(); + sel.Add(m_Filed, true); + while (Length > 0) { + int b; + + if (sel.Select(ms) == -1) + return false; + + if (sel.CanWrite(m_Filed)) { + if ((b = Write((char*)Buffer + offs, Length)) == -1) + return false; + offs += b; + Length -= b; + } + + ms = TimeoutMs - starttime.Elapsed(); + if (ms <= 0) { + errno = ETIMEDOUT; + return false; + } + } + return true; +} + +bool cTBSource::SafeWrite(const void *Buffer, size_t Length) { + cTBSelect sel; + int offs; + + offs = 0; + sel.Clear(); + sel.Add(m_Filed, true); + while (Length > 0) { + int b; + + if (sel.Select() == -1) + return false; + + if (sel.CanWrite(m_Filed)) { + if ((b = Write((char*)Buffer + offs, Length)) == -1) + return false; + offs += b; + Length -= b; + } + } + return true; +} + +ssize_t cTBSource::ReadUntil(void *Buffer, size_t Length, const char *Seq, + uint TimeoutMs) { + int ms; + size_t len; + cTBSelect sel; + + if ((len = m_LineBuffer.find(Seq)) != (size_t)-1) { + if (len > Length) { + errno = ENOBUFS; + return -1; + } + memcpy(Buffer, m_LineBuffer.data(), len); + m_LineBuffer.erase(0, len + strlen(Seq)); + Dprintf("ReadUntil: Served from Linebuffer: %d, |%.*s|\n", len, len - 1, + (char*)Buffer); + return len; + } + + cTimeMs starttime; + ms = TimeoutMs; + sel.Clear(); + sel.Add(m_Filed, false); + while (m_LineBuffer.size() < BUFSIZ) { + + if (sel.Select(ms) == -1) + return -1; + + if (sel.CanRead(m_Filed)) { + int b; + + len = m_LineBuffer.size(); + m_LineBuffer.resize(BUFSIZ); + if ((b = Read((char*)m_LineBuffer.data() + len, BUFSIZ - len)) == -1) { + m_LineBuffer.resize(len); + return -1; + } + m_LineBuffer.resize(len + b); + + if ((len = m_LineBuffer.find(Seq)) != (size_t)-1) { + if (len > Length) { + errno = ENOBUFS; + return -1; + } + memcpy(Buffer, m_LineBuffer.data(), len); + m_LineBuffer.erase(0, len + strlen(Seq)); + Dprintf("ReadUntil: Served from Linebuffer: %d, |%.*s|\n", len, len - 1, + (char*)Buffer); + return len; + } + } + + ms = TimeoutMs - starttime.Elapsed(); + if (ms <= 0) { + errno = ETIMEDOUT; + return -1; + } + } + errno = ENOBUFS; + return -1; +} + diff --git a/plugins/streamdev/streamdev-cvs/tools/source.h b/plugins/streamdev/streamdev-cvs/tools/source.h new file mode 100644 index 0000000..09c4bf3 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/tools/source.h @@ -0,0 +1,109 @@ +#ifndef TOOLBOX_SOURCE_H +#define TOOLBOX_SOURCE_H + +#include "tools/tools.h" + +#include <sys/types.h> +#include <string> + +/* cTBSource provides an abstract interface for input and output. It can + be used to have common access to different types of UNIX-files. */ + +class cTBSource { +private: + int m_Filed; + + size_t m_BytesRead; + size_t m_BytesWritten; + + std::string m_LineBuffer; + +public: + cTBSource(void); + virtual ~cTBSource(); + + /* SysRead() implements the low-level read on the source. It will store + data into the area pointed to by Buffer, which is at least Length + bytes in size. It will return the exact number of bytes read (which + can be fewer than requested). On error, -1 is returned, and errno + is set to an appropriate value. */ + virtual ssize_t SysRead(void *Buffer, size_t Length) const = 0; + + /* SysWrite() implements the low-level write on the source. It will write + at most Length bytes of the data pointed to by Buffer. It will return + the exact number of bytes written (which can be fewer than requested). + On error, -1 is returned, and errno is set to an appropriate value. */ + virtual ssize_t SysWrite(const void *Buffer, size_t Length) const = 0; + + /* IsOpen() returns true, if this source refers to a valid descriptor. + It is not checked whether this source is really open, so only if + opened by the appropriate Methods this function will return the + correct value */ + virtual bool IsOpen(void) const { return m_Filed != -1; } + + /* Open() associates this source with the descriptor Filed, setting it + to non-blocking mode if IsUnixFd in true. Returns true on success, + and false on error, setting errno to appropriately. + If you want to implement sources that can't be represented by UNIX + filedescriptors, you can use Filed to store any useful information + about the source. + This must be called by any derivations in an appropriate Method (like + open for files, connect for sockets). */ + virtual bool Open(int Filed, bool IsUnixFd = true); + + /* Close() resets the source to the uninitialized state (IsOpen() == false) + and must be called by any derivations after really closing the source. + Returns true on success and false on error, setting errno appropriately. + The object is in closed state afterwards, even if an error occured. */ + virtual bool Close(void); + + /* Read() reads at most Length bytes into the storage pointed to by Buffer, + which must be at least Length bytes in size, using the SysRead()- + Interface. It retries if an EINTR occurs (i.e. the low-level call was + interrupted). It returns the exact number of bytes read (which can be + fewer than requested). On error, -1 is returned, and errno is set + appropriately. */ + ssize_t Read(void *Buffer, size_t Length); + + /* Write() writes at most Length bytes from the storage pointed to by + Buffer, using the SysWrite()-Interface. It retries if EINTR occurs + (i.e. the low-level call was interrupted). It returns the exact number + of bytes written (which can be fewer than requested). On error, -1 is + returned and errno is set appropriately. */ + ssize_t Write(const void *Buffer, size_t Length); + + /* TimedWrite() tries to write Length bytes from the storage pointed to by + Buffer within the time specified by TimeoutMs, using the Write()- + Interface. On success, true is returned. On error, false is returned + and errno is set appropriately. TimedRead only works on UNIX file + descriptor sources. */ + bool TimedWrite(const void *Buffer, size_t Length, uint TimeoutMs); + + bool SafeWrite(const void *Buffer, size_t Length); + + /* ReadUntil() tries to read at most Length bytes into the storage pointed + to by Buffer, which must be at least Length bytes in size, within the + time specified by TimeoutMs, using the Read()-Interface. Reading stops + after the character sequence Seq has been read and on end-of-file. + Returns the number of bytes read (if that is equal to Length, you have + to check if the buffer ends with Seq), or -1 on error, in which case + errno is set appropriately. */ + ssize_t ReadUntil(void *Buffer, size_t Length, const char *Seq, + uint TimeoutMs); + + /* BytesRead() returns the exact number of bytes read through the Read() + method since Close() has been called on this source (or since its + creation). */ + size_t BytesRead(void) const { return m_BytesRead; } + + /* BytesWritten() returns the exact number of bytes written through the + Write() method since Close() has been called on this source (or since + its creation). */ + size_t BytesWritten(void) const { return m_BytesWritten; } + + /* operator int() returns the descriptor (or informative number) associated + with this source. */ + operator int() const { return m_Filed; } +}; + +#endif // TOOLBOX_SOURCE_H diff --git a/plugins/streamdev/streamdev-cvs/tools/tools.c b/plugins/streamdev/streamdev-cvs/tools/tools.c new file mode 100644 index 0000000..fa813fa --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/tools/tools.c @@ -0,0 +1,12 @@ +#include "tools/tools.h" + +#include <sys/time.h> +#include <time.h> +#include <sys/types.h> +#include <unistd.h> +#include <stdarg.h> + +void *operator new(size_t nSize, void *p) throw () { + return p; +} + diff --git a/plugins/streamdev/streamdev-cvs/tools/tools.h b/plugins/streamdev/streamdev-cvs/tools/tools.h new file mode 100644 index 0000000..ab00c60 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs/tools/tools.h @@ -0,0 +1,67 @@ +#ifndef TOOLBOX_TOOLS_H +#define TOOLBOX_TOOLS_H + +//#include <stdio.h> +//#include <iostream> +#include <sys/types.h> + +//#define KILOBYTE(x) ((x)*1024) +//#define MEGABYTE(x) (KILOBYTE(x)*1024) + +//typedef unsigned int uint; +//typedef unsigned long ulong; +typedef unsigned char uchar; +//typedef unsigned short ushort; + +// Special constructor for CreateElements +void *operator new(size_t, void*) throw (); + +#ifdef TOOLBOX_DEBUG +# define ASSERT(x) if ((x)) cerr << "Warning: ASSERT failed At " << __FILE__ << ":" << __LINE__ << " ["#x"]" << endl +# define CHECK_PTR(x) if (!(x)) cerr << "Warning: Pointer is NULL At " << __FILE__ << ":" << __LINE__ << endl; +# define CHECK_NEXT_ALLOC() _checkNextAlloc() +# define DPRINT(x...) LOGi(x) +#else +# define ASSERT(x) +# define CHECK_PTR(x) +# define CHECK_NEXT_ALLOC() +# define DPRINT(x...) +#endif + +#define ERRNUL(e) {errno=e;return 0;} +#define ERRSYS(e) {errno=e;return -1;} + +/* RETURNS() and RETURN() are macros that can be used if a class object is + being returned. They make use of the GNU C-Compiler's named return value + feature, if available. In this case, the class object isn't returned and + copied, but the result itself is filled. + + RETURNS(ReturnType, FunctionDeclaration, Result) + ... function-body working on Result ... + RETURN(Result) + + A function like this (cXYZ is a class type): + + cXYZ myfunction(int a, char *b) { + cXYZ result; + ... something happens with result ... + return result; + } + + can be written like this: + + RETURNS(cXYZ, myfunction(int a, char *b), result) + ... something happens with result ... + RETURN(result) + + DISABLED SINCE GCC 3.x +*/ +//#ifdef __GNUC__ +//# define RETURNS(t,x,r) t x return r { +//# define RETURN(x) } +//#else +# define RETURNS(t,x,r) t x { t r; +# define RETURN(x) return x; } +//#endif + +#endif // TOOLBOX_TOOLS_H diff --git a/plugins/streamdev/streamdev-cvs100210-ReplaceRecordingStreaming.patch b/plugins/streamdev/streamdev-cvs100210-ReplaceRecordingStreaming.patch new file mode 100644 index 0000000..a5cc5d6 --- /dev/null +++ b/plugins/streamdev/streamdev-cvs100210-ReplaceRecordingStreaming.patch @@ -0,0 +1,581 @@ +diff -NaurwB streamdev/Makefile streamdev-patched/Makefile +--- streamdev/Makefile 2009-11-04 12:12:20.000000000 +0100 ++++ streamdev-patched/Makefile 2010-02-09 15:58:13.000000000 +0100 +@@ -65,7 +65,7 @@ + server/server.o server/component.o server/connection.o \ + server/componentVTP.o server/componentHTTP.o server/componentIGMP.o \ + server/connectionVTP.o server/connectionHTTP.o server/connectionIGMP.o \ +- server/streamer.o server/livestreamer.o server/livefilter.o \ ++ server/streamer.o server/livestreamer.o server/livefilter.o server/recordingstreamer.o \ + server/suspend.o server/setup.o server/menuHTTP.o server/recplayer.o \ + remux/tsremux.o remux/ts2pes.o remux/ts2ps.o remux/ts2es.o remux/extern.o + +diff -NaurwB streamdev/server/connectionVTP.c streamdev-patched/server/connectionVTP.c +--- streamdev/server/connectionVTP.c 2010-01-29 13:03:02.000000000 +0100 ++++ streamdev-patched/server/connectionVTP.c 2010-02-10 08:56:22.000000000 +0100 +@@ -4,6 +4,7 @@ + + #include "server/connectionVTP.h" + #include "server/livestreamer.h" ++#include "server/recordingstreamer.h" + #include "server/suspend.h" + #include "setup.h" + +@@ -741,11 +742,11 @@ + m_FilterSocket(NULL), + m_FilterStreamer(NULL), + m_RecSocket(NULL), ++ m_RecStreamer(NULL), + m_DataSocket(NULL), + m_LastCommand(NULL), + m_StreamType(stTSPIDS), + m_FiltersSupport(false), +- m_RecPlayer(NULL), + m_LSTEHandler(NULL), + m_LSTCHandler(NULL), + m_LSTTHandler(NULL), +@@ -759,6 +760,7 @@ + free(m_LastCommand); + delete m_LiveStreamer; + delete m_LiveSocket; ++ delete m_RecStreamer; + delete m_RecSocket; + delete m_FilterStreamer; + delete m_FilterSocket; +@@ -767,7 +769,6 @@ + delete m_LSTCHandler; + delete m_LSTEHandler; + delete m_LSTRHandler; +- delete m_RecPlayer; + } + + inline bool cConnectionVTP::Abort(void) const +@@ -833,9 +834,10 @@ + if (strcasecmp(Cmd, "CAPS") == 0) return CmdCAPS(param); + else if (strcasecmp(Cmd, "PROV") == 0) return CmdPROV(param); + else if (strcasecmp(Cmd, "PORT") == 0) return CmdPORT(param); +- 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, "SEEK") == 0) return CmdSEEK(param); ++ else if (strcasecmp(Cmd, "SIZE") == 0) return CmdSIZE(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); +@@ -1012,6 +1014,8 @@ + + if (!m_RecSocket->SetDSCP()) + LOG_ERROR_STR("unable to set DSCP sockopt"); ++ if(m_RecSocket) ++ m_RecStreamer->Start(m_RecSocket); + + return Respond(220, "Port command ok, data connection opened"); + break; +@@ -1034,35 +1038,6 @@ + } + } + +-bool cConnectionVTP::CmdREAD(char *Opts) +-{ +- if (*Opts) { +- char *tail; +- uint64_t position = strtoll(Opts, &tail, 10); +- if (tail && tail != Opts) { +- tail = skipspace(tail); +- if (tail && tail != Opts) { +- int size = strtol(tail, NULL, 10); +- uint8_t* data = (uint8_t*)malloc(size+4); +- unsigned long count_readed = m_RecPlayer->getBlock(data, position, size); +- unsigned long count_written = m_RecSocket->SysWrite(data, count_readed); +- +- free(data); +- return Respond(220, "%lu Bytes submitted", count_written); +- } +- else { +- return Respond(501, "Missing position"); +- } +- } +- else { +- return Respond(501, "Missing size"); +- } +- } +- else { +- return Respond(501, "Missing position"); +- } +-} +- + bool cConnectionVTP::CmdTUNE(char *Opts) + { + const cChannel *chan; +@@ -1096,27 +1071,40 @@ + + bool cConnectionVTP::CmdPLAY(char *Opts) + { +- if (*Opts) { +- if (isnumber(Opts)) { +- cRecording *recording = Recordings.Get(strtol(Opts, NULL, 10) - 1); +- if (recording) { +- if (m_RecPlayer) { +- delete m_RecPlayer; +- } +- m_RecPlayer = new RecPlayer(recording); +- return Respond(220, "%llu (Bytes), %u (Frames)", (long long unsigned int) m_RecPlayer->getLengthBytes(), (unsigned int) m_RecPlayer->getLengthFrames()); +- } +- else { +- return Respond(550, "Recording \"%s\" not found", Opts); +- } +- } +- else { +- return Respond(500, "Use: PLAY record"); ++ const cRecording *recording; ++ cDevice *dev; ++ ++ if ((recording = Recordings.Get(strtol(Opts, NULL, 10) - 1)) == NULL) ++ return Respond(550, "Undefined recording \"%s\"", Opts); ++ ++ delete m_RecStreamer; ++ m_RecStreamer = new cStreamdevRecStreamer(recording); ++ if(m_RecSocket) ++ m_RecStreamer->Start(m_RecSocket); ++ ++ return Respond(220, "Recording opened"); + } ++ ++bool cConnectionVTP::CmdSEEK(char *Opts) ++{ ++ if (m_RecStreamer) ++ { ++ uint64_t TotalBytesWritten = m_RecStreamer->getTotalBytesWritten(); ++ uint64_t newPosition = atoll(Opts); ++ m_RecStreamer->seekPosition(newPosition); ++ ++ return Respond(220, "%llu (TCP Stack Position) %llu (New Position)", TotalBytesWritten, newPosition); + } +- else { +- return Respond(500, "Use: PLAY record"); ++ ++ return Respond(550, "Curretly no record playing"); + } ++ ++bool cConnectionVTP::CmdSIZE(char *Opts) ++{ ++ if (m_RecStreamer) ++ return Respond(220, "%llu (Bytes), %u (Frames)", (long long unsigned int) m_RecStreamer->getLengthBytes(), (unsigned int) m_RecStreamer->getLengthFrames()); ++ ++ return Respond(550, "Curretly no record playing"); + } + + bool cConnectionVTP::CmdADDP(char *Opts) +@@ -1215,7 +1203,7 @@ + DELETENULL(m_FilterSocket); + break; + case siReplay: +- DELETENULL(m_RecPlayer); ++ DELETENULL(m_RecStreamer); + DELETENULL(m_RecSocket); + break; + case siDataRespond: +diff -NaurwB streamdev/server/connectionVTP.h streamdev-patched/server/connectionVTP.h +--- streamdev/server/connectionVTP.h 2009-07-01 12:46:16.000000000 +0200 ++++ streamdev-patched/server/connectionVTP.h 2010-02-09 18:08:35.000000000 +0100 +@@ -7,6 +7,7 @@ + class cTBSocket; + class cStreamdevLiveStreamer; + class cStreamdevFilterStreamer; ++class cStreamdevRecStreamer; + class cLSTEHandler; + class cLSTCHandler; + class cLSTTHandler; +@@ -24,12 +25,12 @@ + cTBSocket *m_FilterSocket; + cStreamdevFilterStreamer *m_FilterStreamer; + cTBSocket *m_RecSocket; ++ cStreamdevRecStreamer *m_RecStreamer; + cTBSocket *m_DataSocket; + + char *m_LastCommand; + eStreamType m_StreamType; + bool m_FiltersSupport; +- RecPlayer *m_RecPlayer; + + // Members adopted for SVDRP + cLSTEHandler *m_LSTEHandler; +@@ -56,9 +57,10 @@ + bool CmdCAPS(char *Opts); + bool CmdPROV(char *Opts); + bool CmdPORT(char *Opts); +- bool CmdREAD(char *Opts); + bool CmdTUNE(char *Opts); + bool CmdPLAY(char *Opts); ++ bool CmdSEEK(char *Opts); ++ bool CmdSIZE(char *Opts); + bool CmdADDP(char *Opts); + bool CmdDELP(char *Opts); + bool CmdADDF(char *Opts); +diff -NaurwB streamdev/server/recordingstreamer.c streamdev-patched/server/recordingstreamer.c +--- streamdev/server/recordingstreamer.c 1970-01-01 01:00:00.000000000 +0100 ++++ streamdev-patched/server/recordingstreamer.c 2010-02-10 08:58:01.000000000 +0100 +@@ -0,0 +1,89 @@ ++#include <vdr/recording.h> ++ ++#include "tools/socket.h" ++#include "tools/select.h" ++ ++#include "server/recordingstreamer.h" ++#include "recplayer.h" ++#include "common.h" ++ ++// --- cStreamdevRecStreamer ------------------------------------------------- ++ ++cStreamdevRecStreamer::cStreamdevRecStreamer(const cRecording *Recording): ++ cThread("streamdev-recordingstreaming"), ++ m_Recording(Recording), ++ m_RecPlayer(new cRecPlayer(Recording)), ++ m_Position(0), ++ m_TotalBytesWritten(0) ++{ ++} ++ ++cStreamdevRecStreamer::~cStreamdevRecStreamer() ++{ ++ Dprintf("Desctructing Recording streamer\n"); ++ Stop(); ++ ++ DELETENULL(m_RecPlayer); ++} ++ ++void cStreamdevRecStreamer::Start(cTBSocket *Socket) ++{ ++ Dprintf("start streamer\n"); ++ m_Socket = Socket; ++ cThread::Start(); ++} ++ ++void cStreamdevRecStreamer::Stop(void) ++{ ++ if (Running()) { ++ Dprintf("stopping streamer\n"); ++ Cancel(3); ++ } ++} ++ ++uint64_t cStreamdevRecStreamer::getLengthBytes() ++{ ++ return m_RecPlayer->getLengthBytes(); ++} ++ ++uint32_t cStreamdevRecStreamer::getLengthFrames() ++{ ++ return m_RecPlayer->getLengthFrames(); ++} ++ ++void cStreamdevRecStreamer::seekPosition(uint64_t position) ++{ ++ m_Position = position; ++} ++ ++void cStreamdevRecStreamer::Action(void) ++{ ++ cTBSelect sel; ++ uint8_t data[32768]; ++ Dprintf("Writer start\n"); ++ ++ sel.Clear(); ++ sel.Add(*m_Socket, true); ++ while (Running()) { ++ ++ if (sel.Select(15000) == -1) { ++ esyslog("ERROR: streamdev-server: couldn't send recording data: %m"); ++ continue; /* Continue here instead of break, the recording playback can be paused */ ++ } ++ ++ if (sel.CanWrite(*m_Socket)) { ++ int written; ++ unsigned long readed = m_RecPlayer->getBlock(&*data, m_Position, sizeof(data)); ++ if (readed <= 0) ++ continue; ++ ++ if ((written = m_Socket->Write(&*data, readed)) == -1) { ++ esyslog("ERROR: streamdev-server: couldn't send %d bytes: %m", written); ++ break; ++ } ++ ++ m_Position += written; ++ m_TotalBytesWritten += written; ++ } ++ } ++} +diff -NaurwB streamdev/server/recordingstreamer.h streamdev-patched/server/recordingstreamer.h +--- streamdev/server/recordingstreamer.h 1970-01-01 01:00:00.000000000 +0100 ++++ streamdev-patched/server/recordingstreamer.h 2010-02-10 08:58:13.000000000 +0100 +@@ -0,0 +1,38 @@ ++#ifndef VDR_STREAMDEV_RECORDINGSTREAMER_H ++#define VDR_STREAMDEV_RECORDINGSTREAMER_H ++ ++#include <vdr/thread.h> ++#include <vdr/config.h> ++#include <vdr/receiver.h> ++ ++#include "common.h" ++ ++class cRecording; ++class cRecPlayer; ++ ++// --- cStreamdevRecStreamer ------------------------------------------------- ++ ++class cStreamdevRecStreamer: public cThread { ++private: ++ const cRecording *m_Recording; ++ cRecPlayer *m_RecPlayer; ++ cTBSocket *m_Socket; ++ uint64_t m_Position; ++ uint64_t m_TotalBytesWritten; ++ ++protected: ++ virtual void Action(void); ++ ++public: ++ cStreamdevRecStreamer(const cRecording *Recording); ++ virtual ~cStreamdevRecStreamer(); ++ ++ void Start(cTBSocket *Socket); ++ void Stop(void); ++ uint64_t getTotalBytesWritten() { return m_TotalBytesWritten; } ++ uint64_t getLengthBytes(); ++ uint32_t getLengthFrames(); ++ void seekPosition(uint64_t position); ++}; ++ ++#endif // VDR_STREAMDEV_RECORDINGSTREAMER_H +diff -NaurwB streamdev/server/recplayer.c streamdev-patched/server/recplayer.c +--- streamdev/server/recplayer.c 2009-07-01 13:00:49.000000000 +0200 ++++ streamdev-patched/server/recplayer.c 2010-02-10 08:24:01.000000000 +0100 +@@ -24,7 +24,7 @@ + #define _XOPEN_SOURCE 600 + #include <fcntl.h> + +-RecPlayer::RecPlayer(cRecording* rec) ++cRecPlayer::cRecPlayer(const cRecording* rec) + { + file = NULL; + fileOpen = 0; +@@ -44,7 +44,7 @@ + scan(); + } + +-void RecPlayer::scan() ++void cRecPlayer::scan() + { + if (file) fclose(file); + totalLength = 0; +@@ -60,7 +60,7 @@ + + #if APIVERSNUM < 10703 + snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), i); +- //log->log("RecPlayer", Log::DEBUG, "FILENAME: %s", fileName); ++ //log->log("cRecPlayer", Log::DEBUG, "FILENAME: %s", fileName); + file = fopen(fileName, "r"); + #else + snprintf(fileName, 2047, "%s/%05i.ts", recording->FileName(), i); +@@ -77,7 +77,7 @@ + fseek(file, 0, SEEK_END); + totalLength += ftell(file); + totalFrames = indexFile->Last(); +- //log->log("RecPlayer", Log::DEBUG, "File %i found, totalLength now %llu, numFrames = %lu", i, totalLength, totalFrames); ++ //log->log("cRecPlayer", Log::DEBUG, "File %i found, totalLength now %llu, numFrames = %lu", i, totalLength, totalFrames); + segments[i]->end = totalLength; + fclose(file); + } +@@ -85,15 +85,15 @@ + file = NULL; + } + +-RecPlayer::~RecPlayer() ++cRecPlayer::~cRecPlayer() + { +- //log->log("RecPlayer", Log::DEBUG, "destructor"); ++ //log->log("cRecPlayer", Log::DEBUG, "destructor"); + int i = 1; + while(segments[i++]) delete segments[i]; + if (file) fclose(file); + } + +-int RecPlayer::openFile(int index) ++int cRecPlayer::openFile(int index) + { + if (file) fclose(file); + +@@ -113,7 +113,7 @@ + + snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), index); + isyslog("openFile called for index %i string:%s", index, fileName); +- //log->log("RecPlayer", Log::DEBUG, "openFile called for index %i string:%s", index, fileName); ++ //log->log("cRecPlayer", Log::DEBUG, "openFile called for index %i string:%s", index, fileName); + + file = fopen(fileName, "r"); + if (file) +@@ -122,38 +122,58 @@ + return 1; + } + +- //log->log("RecPlayer", Log::DEBUG, "file failed to open"); ++ //log->log("cRecPlayer", Log::DEBUG, "file failed to open"); + fileOpen = 0; + return 0; + } + +-uint64_t RecPlayer::getLengthBytes() ++uint64_t cRecPlayer::getLengthBytes() + { ++ int totalLength = 0; ++ char fileName[2048]; ++ struct stat st; ++ ++ for(int i = 1; i < 1000; i++) ++ { ++#if APIVERSNUM < 10703 ++ snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), i); ++#else ++ snprintf(fileName, 2047, "%s/%05i.ts", recording->FileName(), i); ++ if (stat(fileName, &st) < 0) { ++ snprintf(fileName, 2047, "%s/%03i.vdr", recording->FileName(), i); ++ } ++#endif ++ ++ if (stat(fileName, &st) == 0) { ++ totalLength += st.st_size; ++ } ++ } ++ + return totalLength; + } + +-uint32_t RecPlayer::getLengthFrames() ++uint32_t cRecPlayer::getLengthFrames() + { + return totalFrames; + } + +-unsigned long RecPlayer::getBlock(unsigned char* buffer, uint64_t position, unsigned long amount) ++unsigned long cRecPlayer::getBlock(uint8_t* buffer, uint64_t position, unsigned long amount) + { + if ((amount > totalLength) || (amount > 500000)) + { +- //log->log("RecPlayer", Log::DEBUG, "Amount %lu requested and rejected", amount); ++ //log->log("cRecPlayer", Log::DEBUG, "Amount %lu requested and rejected", amount); + return 0; + } + + if (position >= totalLength) + { +- //log->log("RecPlayer", Log::DEBUG, "Client asked for data starting past end of recording!"); ++ //log->log("cRecPlayer", Log::DEBUG, "Client asked for data starting past end of recording!"); + return 0; + } + + if ((position + amount) > totalLength) + { +- //log->log("RecPlayer", Log::DEBUG, "Client asked for some data past the end of recording, adjusting amount"); ++ //log->log("cRecPlayer", Log::DEBUG, "Client asked for some data past the end of recording, adjusting amount"); + amount = totalLength - position; + } + +@@ -208,17 +228,17 @@ + return got; + } + +-uint64_t RecPlayer::getLastPosition() ++uint64_t cRecPlayer::getLastPosition() + { + return lastPosition; + } + +-cRecording* RecPlayer::getCurrentRecording() ++const cRecording* cRecPlayer::getCurrentRecording() + { + return recording; + } + +-uint64_t RecPlayer::positionFromFrameNumber(uint32_t frameNumber) ++uint64_t cRecPlayer::positionFromFrameNumber(uint32_t frameNumber) + { + if (!indexFile) return 0; + +@@ -235,21 +255,21 @@ + return 0; + } + +-// log->log("RecPlayer", Log::DEBUG, "FN: %u FO: %i", retFileNumber, retFileOffset); ++// log->log("cRecPlayer", Log::DEBUG, "FN: %u FO: %i", retFileNumber, retFileOffset); + if (!segments[retFileNumber]) return 0; + uint64_t position = segments[retFileNumber]->start + retFileOffset; +-// log->log("RecPlayer", Log::DEBUG, "Pos: %llu", position); ++// log->log("cRecPlayer", Log::DEBUG, "Pos: %llu", position); + + return position; + } + +-uint32_t RecPlayer::frameNumberFromPosition(uint64_t position) ++uint32_t cRecPlayer::frameNumberFromPosition(uint64_t position) + { + if (!indexFile) return 0; + + if (position >= totalLength) + { +- //log->log("RecPlayer", Log::DEBUG, "Client asked for data starting past end of recording!"); ++ //log->log("cRecPlayer", Log::DEBUG, "Client asked for data starting past end of recording!"); + return 0; + } + +@@ -265,7 +285,7 @@ + } + + +-bool RecPlayer::getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength) ++bool cRecPlayer::getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength) + { + // 0 = backwards + // 1 = forwards +@@ -276,7 +296,7 @@ + int indexReturnFrameNumber; + + indexReturnFrameNumber = (uint32_t)indexFile->GetNextIFrame(frameNumber, (direction==1 ? true : false), NULL, NULL, &iframeLength); +- //log->log("RecPlayer", Log::DEBUG, "GNIF input framenumber:%lu, direction=%lu, output:framenumber=%i, framelength=%i", frameNumber, direction, indexReturnFrameNumber, iframeLength); ++ //log->log("cRecPlayer", Log::DEBUG, "GNIF input framenumber:%lu, direction=%lu, output:framenumber=%i, framelength=%i", frameNumber, direction, indexReturnFrameNumber, iframeLength); + + if (indexReturnFrameNumber == -1) return false; + +diff -NaurwB streamdev/server/recplayer.h streamdev-patched/server/recplayer.h +--- streamdev/server/recplayer.h 2009-07-01 13:00:49.000000000 +0200 ++++ streamdev-patched/server/recplayer.h 2010-02-09 17:18:23.000000000 +0100 +@@ -33,24 +33,24 @@ + uint64_t end; + }; + +-class RecPlayer ++class cRecPlayer + { + public: +- RecPlayer(cRecording* rec); +- ~RecPlayer(); ++ cRecPlayer(const cRecording* rec); ++ ~cRecPlayer(); + uint64_t getLengthBytes(); + uint32_t getLengthFrames(); +- unsigned long getBlock(unsigned char* buffer, uint64_t position, unsigned long amount); ++ unsigned long getBlock(uint8_t* buffer, uint64_t position, unsigned long amount); + int openFile(int index); + uint64_t getLastPosition(); +- cRecording* getCurrentRecording(); ++ const cRecording* getCurrentRecording(); + void scan(); + uint64_t positionFromFrameNumber(uint32_t frameNumber); + uint32_t frameNumberFromPosition(uint64_t position); + bool getNextIFrame(uint32_t frameNumber, uint32_t direction, uint64_t* rfilePosition, uint32_t* rframeNumber, uint32_t* rframeLength); + + private: +- cRecording* recording; ++ const cRecording* recording; + cIndexFile* indexFile; + FILE* file; + int fileOpen; |