vdr-plugin-streamdev/0000755000175000017500000000000013276341255014471 5ustar tobiastobiasvdr-plugin-streamdev/COPYING0000644000175000017500000004325413276341255015534 0ustar tobiastobias GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser 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. Copyright (C) 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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. , 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 Lesser General Public License instead of this License. vdr-plugin-streamdev/CONTRIBUTORS0000644000175000017500000001742713276341255016364 0ustar tobiastobiasSpecial 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 for suggesting to change the URL path from EXTERN to EXT for suggesting increased thread priorities for cStreamdevWriter/Streamer for adding "Hide mainmenu entry" option for polishing po file headers for adding the special meaning "show current channel" to channel 0 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 for proper tsplay-0.2 patch detection 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 Oliver Wagner for pointing out a problem with the encrypted channel switching fix for suggesting use of SO_KEEPALIVE socket option to detect dead sockets for making cStatus::ChannelChange re-tune only if CA IDs changed 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 Eric Valette for adding support for EnhancedAC3 carel for reporting "plugin doesn't honor APIVERSION" error in new Makefile for helping to find a way to cleanly shutdown externremux with mencoder for reporting that GetClippedNumProvidedSystems is no longer up-to-date wolfi.m for reporting a typo in externremux quality parameter value Norman Thiel for reporting a wrong URL path in m3u playlists vel_tins for reporting that externremux x264 uses value of ABR for VBR for various suggestions to improve externremux.sh Matthias Prill for reporting a compiler error with older libstdc++ versions Timothy D. Lenz for reporting missing support for invisible channel groups in HTTP menu Rainer Blickle for reporting that channel switches may interrupt live TV on the server Gavin Hamill for reporting that ES/PS/PES no longer works Michal Novotny for reporting that switching away live TV fails when "always suspended" wtor for reporting that a client may interrupt replaying on FF cards for helping to debug channel switch issues on FF cards Javier Bradineras for providing Spanish language texts Pekko Tiitto for providing a git mirror of streamdev's lost CVS repository for suggesting to use mencoder params -alang and -msglevel in externremux Lubo¨ Dole¸el for suggesting higher buffer sizes to fix some ringbuffer overflows Ville Skyttä for updating the outdated COPYING file and FSF address for restricting VTP command RENR to liemikuutio patch < 1.32 for fixing memory and filedescriptor leaks in libdvbmpeg for code cleanup and optimization for correcting typos Methodus for suggesting to use HTTP code 503 for unavailable channels Uwe for reporting a compiler error in client/device.c with VDR < 1.7.22 Chris Tallon for his kind permission to use VOMP's recplayer for replaying recordings macmenot for adapting Makefiles to VDR 1.7.36+ thomasjfox for fixing cSuspendCtl preventing idle shutdown hivdr for adding the pos= parameter for replaying recordings from a certain position for suggesting to add the HTTP "Server" header hummel99 for reporting and helping to debug channel switch issues with priority > 0 for reporting a race condition when switching the server's LiveTV device Henrik Niehaus for fixing replay of large TS files on 32-bit systems Guy Martin for adding SVDRP commands to list and disconnect clients Martin1234 for suggesting a service call, returning the number of clients for implementing GetCurrentlyTunedTransponder() on client Toerless Eckert for converting suspend.dat into proper PES format for investigating and fixing problems caused by filter streaming for fixing TimedWrite() so it doesn't fail after a slow but successful write for suggesting to double the size of client's filter buffer Tomasz Maciej Nowak for providing Polish language texts Christopher Reimer for providing an initial compatibility patch for VDR 2.3.1 Matthias Senzel for refining the compatibility patch for VDR 2.3.1 David Binderman for fixing an lseek error check in libdvbmpeg Jasmin J for fixing some warnings in libdvbmpeg for adding .gitignore for fixing compilation for VDR 2.3.7 vdr-plugin-streamdev/server/0000755000175000017500000000000013276341255015777 5ustar tobiastobiasvdr-plugin-streamdev/server/connectionHTTP.c0000644000175000017500000005107613276341255021013 0ustar tobiastobias/* * $Id: connectionHTTP.c,v 1.21 2010/08/03 10:46:41 schmirl Exp $ */ #include #include #include #include #include #include #include #include #include #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_StreamType((eStreamType)StreamdevServerSetup.HTTPStreamType), m_Channel(NULL), m_RecPlayer(NULL), m_ReplayPos(0), m_ReplayFakeRange(false), m_MenuList(NULL) { Dprintf("constructor hsRequest\n"); m_Apid[0] = m_Apid[1] = 0; m_Dpid[0] = m_Dpid[1] = 0; } cConnectionHTTP::~cConnectionHTTP() { SetStreamer(NULL); delete m_RecPlayer; delete m_MenuList; } bool cConnectionHTTP::CanAuthenticate(void) { return opt_auth != NULL; } bool cConnectionHTTP::Command(char *Cmd) { Dprintf("command %s\n", Cmd); switch (m_Status) { case hsRequest: // parse METHOD PATH[?QUERY] VERSION { char *p, *q, *v; p = strchr(Cmd, ' '); if (p) { *p = 0; v = strchr(++p, ' '); if (v) { *v = 0; SetHeader("REQUEST_METHOD", Cmd); q = strchr(p, '?'); if (q) { *q = 0; SetHeader("QUERY_STRING", q + 1); while (q++) { char *n = strchr(q, '&'); if (n) *n = 0; char *e = strchr(q, '='); if (e) *e++ = 0; else e = n ? n : v; m_Params.insert(tStrStr(q, e)); q = n; } } else SetHeader("QUERY_STRING", ""); SetHeader("PATH_INFO", p); m_Status = hsHeaders; return true; } } } return false; case hsHeaders: if (*Cmd == '\0') { m_Status = hsBody; return ProcessRequest(); } else if (isspace(*Cmd)) { ; //TODO: multi-line header } else { // convert header name to CGI conventions: // uppercase, '-' replaced with '_', prefix "HTTP_" char *p; for (p = Cmd; *p != 0 && *p != ':'; p++) { if (*p == '-') *p = '_'; else *p = toupper(*p); } if (*p == ':') { *p = 0; p = skipspace(++p); // don't disclose Authorization header if (strcmp(Cmd, "AUTHORIZATION") == 0) { char *q; for (q = p; *q != 0 && *q != ' '; q++) *q = toupper(*q); if (p != q) { *q = 0; SetHeader("AUTH_TYPE", p); m_Authorization = (std::string) skipspace(++q); } } else SetHeader(Cmd, p, "HTTP_"); } } return true; default: // skip additional blank lines if (*Cmd == '\0') return true; break; } return false; // ??? shouldn't happen } bool cConnectionHTTP::ProcessRequest(void) { // keys for Headers() hash const static std::string AUTH_TYPE("AUTH_TYPE"); const static std::string REQUEST_METHOD("REQUEST_METHOD"); const static std::string PATH_INFO("PATH_INFO"); Dprintf("process\n"); if (!StreamdevHosts.Acceptable(RemoteIpAddr())) { bool authOk = opt_auth && !m_Authorization.empty(); if (authOk) { tStrStrMap::const_iterator it = Headers().find(AUTH_TYPE); if (it == Headers().end()) { // no authorization header present authOk = false; } else if (it->second.compare("BASIC") == 0) { // basic auth authOk &= m_Authorization.compare(opt_auth) == 0; } else { // unsupported auth type authOk = false; } } if (!authOk) { isyslog("streamdev-server: HTTP authorization required"); return HttpResponse(401, true, NULL, "WWW-authenticate: basic Realm=\"Streamdev-Server\""); } } tStrStrMap::const_iterator it; it = m_Params.find("apid"); if (it != m_Params.end()) m_Apid[0] = atoi(it->second.c_str()); it = m_Params.find("dpid"); if (it != m_Params.end()) m_Dpid[0] = atoi(it->second.c_str()); tStrStrMap::const_iterator it_method = Headers().find(REQUEST_METHOD); tStrStrMap::const_iterator it_pathinfo = Headers().find(PATH_INFO); if (it_method == Headers().end() || it_pathinfo == Headers().end()) { // should never happen esyslog("streamdev-server connectionHTTP: Missing method or pathinfo"); } else if (it_method->second.compare("GET") == 0 && ProcessURI(it_pathinfo->second)) { if (m_MenuList) return Respond("%s", true, m_MenuList->HttpHeader().c_str()); else if (m_Channel != NULL) { if (cStreamdevLiveStreamer::ProvidesChannel(m_Channel, StreamdevServerSetup.HTTPPriority)) { cStreamdevLiveStreamer* liveStreamer = new cStreamdevLiveStreamer(this, m_Channel, StreamdevServerSetup.HTTPPriority, m_StreamType, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL); if (liveStreamer->GetDevice()) { SetStreamer(liveStreamer); if (!SetDSCP()) LOG_ERROR_STR("unable to set DSCP sockopt"); if (m_StreamType == stEXT) { return Respond("HTTP/1.0 200 OK"); } else if (m_StreamType == stES && (m_Apid[0] || m_Dpid[0] || ISRADIO(m_Channel))) { return HttpResponse(200, false, "audio/mpeg", "icy-name: %s", m_Channel->Name()); } else if (ISRADIO(m_Channel)) { return HttpResponse(200, false, "audio/mpeg"); } else { return HttpResponse(200, false, "video/mpeg"); } } SetStreamer(NULL); delete liveStreamer; } return HttpResponse(503, true); } else if (m_RecPlayer != NULL) { Dprintf("GET recording\n"); bool isPes = m_RecPlayer->getCurrentRecording()->IsPesRecording(); // no remuxing for old PES recordings if (isPes && m_StreamType != stPES) return HttpResponse(503, true); int64_t from, to; bool hasRange = ParseRange(from, to); cStreamdevRecStreamer* recStreamer; if (from == 0 && hasRange && m_ReplayFakeRange) { recStreamer = new cStreamdevRecStreamer(this, m_RecPlayer, m_StreamType, (int64_t) 0L, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL); from += m_ReplayPos; if (to >= 0) to += m_ReplayPos; } else recStreamer = new cStreamdevRecStreamer(this, m_RecPlayer, m_StreamType, m_ReplayPos, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL); SetStreamer(recStreamer); if (m_StreamType == stEXT) return Respond("HTTP/1.0 200 OK"); else if (m_StreamType == stES && (m_Apid[0] || m_Dpid[0])) return HttpResponse(200, false, "audio/mpeg"); const char* contentType = (isPes || m_RecPlayer->getPatPmtData()->Vpid()) ? "video/mpeg" : "audio/mpeg"; // range not supported when remuxing if (m_StreamType != stTS && !isPes) return HttpResponse(200, false, contentType); uint64_t total = recStreamer->GetLength(); if (hasRange) { int64_t length = recStreamer->SetRange(from, to); Dprintf("range response: %lld-%lld/%lld, len %lld\n", (long long)from, (long long)to, (long long)total, (long long)length); if (length < 0L) return HttpResponse(416, true, contentType, "Accept-Ranges: bytes\r\nContent-Range: bytes */%llu", (unsigned long long) total); else return HttpResponse(206, false, contentType, "Accept-Ranges: bytes\r\nContent-Range: bytes %lld-%lld/%llu\r\nContent-Length: %lld", (long long) from, (long long) to, (unsigned long long) total, (long long) length); } else return HttpResponse(200, false, contentType, "Accept-Ranges: bytes"); } else { return HttpResponse(404, true); } } else if (it_method->second.compare("HEAD") == 0 && ProcessURI(it_pathinfo->second)) { if (m_MenuList) { DeferClose(); return Respond("%s", true, m_MenuList->HttpHeader().c_str()); } else if (m_Channel != NULL) { if (cStreamdevLiveStreamer::ProvidesChannel(m_Channel, StreamdevServerSetup.HTTPPriority)) { if (m_StreamType == stEXT) { cStreamdevLiveStreamer *liveStreamer = new cStreamdevLiveStreamer(this, m_Channel, IDLEPRIORITY, m_StreamType, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL); SetStreamer(liveStreamer); return Respond("HTTP/1.0 200 OK"); } else if (m_StreamType == stES && (m_Apid[0] || m_Dpid[0] || ISRADIO(m_Channel))) { return HttpResponse(200, true, "audio/mpeg", "icy-name: %s", m_Channel->Name()); } else if (ISRADIO(m_Channel)) { return HttpResponse(200, true, "audio/mpeg"); } else { return HttpResponse(200, true, "video/mpeg"); } } return HttpResponse(503, true); } else if (m_RecPlayer != NULL) { Dprintf("HEAD recording\n"); bool isPes = m_RecPlayer->getCurrentRecording()->IsPesRecording(); // no remuxing for old PES recordings if (isPes && m_StreamType != stPES) return HttpResponse(503, true); int64_t from, to; bool hasRange = ParseRange(from, to); cStreamdevRecStreamer* recStreamer; if (from == 0 && hasRange && m_ReplayFakeRange) { recStreamer = new cStreamdevRecStreamer(this, m_RecPlayer, m_StreamType, m_ReplayPos, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL); from += m_ReplayPos; if (to >= 0) to += m_ReplayPos; } else recStreamer = new cStreamdevRecStreamer(this, m_RecPlayer, m_StreamType, m_ReplayPos, m_Apid[0] ? m_Apid : NULL, m_Dpid[0] ? m_Dpid : NULL); SetStreamer(recStreamer); if (m_StreamType == stEXT) return Respond("HTTP/1.0 200 OK"); else if (m_StreamType == stES && (m_Apid[0] || m_Dpid[0])) return HttpResponse(200, true, "audio/mpeg"); const char* contentType = (isPes || m_RecPlayer->getPatPmtData()->Vpid()) ? "video/mpeg" : "audio/mpeg"; // range not supported when remuxing if (m_StreamType != stTS && !isPes) return HttpResponse(200, false, contentType); uint64_t total = recStreamer->GetLength(); if (hasRange) { int64_t length = recStreamer->SetRange(from, to); if (length < 0L) return HttpResponse(416, true, contentType, "Accept-Ranges: bytes\r\nContent-Range: bytes */%llu", (unsigned long long) total); else return HttpResponse(206, true, contentType, "Accept-Ranges: bytes\r\nContent-Range: bytes %lld-%lld/%llu\r\nContent-Length: %lld", (long long) from, (long long) to, (unsigned long long) total, (long long) length); } else return HttpResponse(200, true, contentType, "Accept-Ranges: bytes"); } else { return HttpResponse(404, true); } } return HttpResponse(400, true); } static const char *AAA[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; static const char *MMM[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; bool cConnectionHTTP::HttpResponse(int Code, bool Last, const char* ContentType, const char* Headers, ...) { va_list ap; va_start(ap, Headers); #if APIVERSNUM >= 10728 cString headers = cString::vsprintf(Headers, ap); #else cString headers = cString::sprintf(Headers, ap); #endif va_end(ap); bool rc; if (Last) DeferClose(); switch (Code) { case 200: rc = Respond("HTTP/1.1 200 OK"); break; case 206: rc = Respond("HTTP/1.1 206 Partial Content"); break; case 400: rc = Respond("HTTP/1.1 400 Bad Request"); break; case 401: rc = Respond("HTTP/1.1 401 Authorization Required"); break; case 404: rc = Respond("HTTP/1.1 404 Not Found"); break; case 416: rc = Respond("HTTP/1.1 416 Requested range not satisfiable"); break; case 503: rc = Respond("HTTP/1.1 503 Service Unavailable"); break; default: rc = Respond("HTTP/1.1 500 Internal Server Error"); } if (rc && ContentType) rc = Respond("Content-Type: %s", true, ContentType); if (rc) rc = Respond("Connection: close") && Respond("Pragma: no-cache") && Respond("Cache-Control: no-cache") && Respond("Server: VDR-%s / streamdev-server-%s", true, VDRVERSION, VERSION); time_t t = time(NULL); struct tm *gmt = gmtime(&t); if (rc && gmt) { char buf[] = "Date: AAA, DD MMM YYYY HH:MM:SS GMT"; if (snprintf(buf, sizeof(buf), "Date: %s, %.2d %s %.4d %.2d:%.2d:%.2d GMT", AAA[gmt->tm_wday], gmt->tm_mday, MMM[gmt->tm_mon], gmt->tm_year + 1900, gmt->tm_hour, gmt->tm_min, gmt->tm_sec) == sizeof(buf) - 1) rc = Respond(buf); } if (rc && strlen(Headers) > 0) rc = Respond(headers); tStrStrMap::iterator it = m_Params.begin(); while (rc && it != m_Params.end()) { static const char DLNA_POSTFIX[] = ".dlna.org"; if (it->first.rfind(DLNA_POSTFIX) + sizeof(DLNA_POSTFIX) - 1 == it->first.length()) rc = Respond("%s: %s", true, it->first.c_str(), it->second.c_str()); ++it; } return rc && Respond(""); } bool cConnectionHTTP::ParseRange(int64_t &From, int64_t &To) const { const static std::string RANGE("HTTP_RANGE"); From = To = 0L; tStrStrMap::const_iterator it = Headers().find(RANGE); if (it != Headers().end()) { size_t b = it->second.find("bytes="); if (b != std::string::npos) { char* e = NULL; const char* r = it->second.c_str() + b + sizeof("bytes=") - 1; if (strchr(r, ',') != NULL) esyslog("streamdev-server cConnectionHTTP::GetRange: Multi-ranges not supported"); From = strtol(r, &e, 10); if (r != e) { if (From < 0L) { To = -1L; return *e == 0 || *e == ','; } else if (*e == '-') { r = e + 1; if (*r == 0 || *e == ',') { To = -1L; return true; } To = strtol(r, &e, 10); return r != e && To >= From && (*e == 0 || *e == ','); } } } } return false; } void cConnectionHTTP::Flushed(void) { if (m_Status != hsBody) return; if (m_MenuList) { if (m_MenuList->HasNext()) { if (!Respond("%s", true, m_MenuList->Next().c_str())) DeferClose(); } else { DELETENULL(m_MenuList); m_Status = hsFinished; DeferClose(); } return; } else if (Streamer()) { Dprintf("streamer start\n"); Streamer()->Start(this); m_Status = hsFinished; } else { // should never be reached esyslog("streamdev-server cConnectionHTTP::Flushed(): no job to do"); m_Status = hsFinished; } } cMenuList* cConnectionHTTP::MenuListFromString(const std::string& Path, const std::string& Filebase, const std::string& Fileext) const { std::string groupTarget; cItemIterator *iterator = NULL; const static std::string GROUP("group"); if (Filebase.compare("tree") == 0) { tStrStrMap::const_iterator it = m_Params.find(GROUP); iterator = new cListTree(it == m_Params.end() ? NULL : it->second.c_str()); groupTarget = Filebase + Fileext; } else if (Filebase.compare("groups") == 0) { iterator = new cListGroups(); groupTarget = (std::string) "group" + Fileext; } else if (Filebase.compare("group") == 0) { tStrStrMap::const_iterator it = m_Params.find(GROUP); iterator = new cListGroup(it == m_Params.end() ? NULL : it->second.c_str()); } else if (Filebase.compare("channels") == 0) { iterator = new cListChannels(); } else if (Filebase.compare("all") == 0 || (Filebase.empty() && Fileext.empty())) { iterator = new cListAll(); } else if (Filebase.compare("recordings") == 0) { iterator = new cRecordingsIterator(m_StreamType); } if (iterator) { // assemble base url: http://host/path/ std::string base; const static std::string HOST("HTTP_HOST"); tStrStrMap::const_iterator it = Headers().find(HOST); if (it != Headers().end()) base = "http://" + it->second + "/"; else base = (std::string) "http://" + LocalIp() + ":" + (const char*) itoa(StreamdevServerSetup.HTTPServerPort) + "/"; base += Path; if (Filebase.empty() || Fileext.compare(".htm") == 0 || Fileext.compare(".html") == 0) { std::string self = Filebase + Fileext; std::string rss = Filebase + ".rss"; tStrStrMap::const_iterator it = Headers().find("QUERY_STRING"); if (it != Headers().end() && !it->second.empty()) { self += '?' + it->second; rss += '?' + it->second; } return new cHtmlMenuList(iterator, m_StreamType, self.c_str(), rss.c_str(), groupTarget.c_str()); } else if (Fileext.compare(".m3u") == 0) { return new cM3uMenuList(iterator, base.c_str()); } else if (Fileext.compare(".rss") == 0) { std::string html = Filebase + ".html"; tStrStrMap::const_iterator it = Headers().find("QUERY_STRING"); if (it != Headers().end() && !it->second.empty()) { html += '?' + it->second; } return new cRssMenuList(iterator, base.c_str(), html.c_str()); } else { delete iterator; } } return NULL; } RecPlayer* cConnectionHTTP::RecPlayerFromString(const char *FileBase, const char *FileExt) { RecPlayer *recPlayer = NULL; if (strcasecmp(FileExt, ".rec") != 0) return NULL; char *p = NULL; unsigned long l = strtoul(FileBase, &p, 0); if (p != FileBase && l > 0L) { if (*p == ':') { // get recording by dev:inode ino_t inode = (ino_t) strtoull(p + 1, &p, 0); if (*p == 0 && inode > 0) { struct stat st; #if APIVERSNUM >= 20300 LOCK_RECORDINGS_READ; for (const cRecording *rec = Recordings->First(); rec; rec = Recordings->Next(rec)) { #else cThreadLock RecordingsLock(&Recordings); for (cRecording *rec = Recordings.First(); rec; rec = Recordings.Next(rec)) { #endif if (stat(rec->FileName(), &st) == 0 && st.st_dev == (dev_t) l && st.st_ino == inode) recPlayer = new RecPlayer(rec->FileName()); } } } else if (*p == 0) { // get recording by index #if APIVERSNUM >= 20300 LOCK_RECORDINGS_READ; const cRecording *rec = Recordings->Get((int) l - 1); #else cThreadLock RecordingsLock(&Recordings); cRecording *rec = Recordings.Get((int) l - 1); #endif if (rec) recPlayer = new RecPlayer(rec->FileName()); } if (recPlayer) { const char *pos = NULL; tStrStrMap::const_iterator it = m_Params.begin(); while (it != m_Params.end()) { if (it->first == "pos") { pos = it->second.c_str(); break; } ++it; } if (pos) { // With prefix "full_" we try to fool players // by replying with a content range starting // at the requested position instead of 0. // This is a heavy violation of standards. // Use at your own risk! if (strncasecmp(pos, "full_", 5) == 0) { m_ReplayFakeRange = true; pos += 5; } if (strncasecmp(pos, "resume", 6) == 0) { int id = pos[6] == '.' ? atoi(pos + 7) : 0; m_ReplayPos = recPlayer->positionFromResume(id); } else if (strncasecmp(pos, "mark.", 5) == 0) { int index = atoi(pos + 5); m_ReplayPos = recPlayer->positionFromMark(index); } else if (strncasecmp(pos, "time.", 5) == 0) { int seconds = atoi(pos + 5); m_ReplayPos = recPlayer->positionFromTime(seconds); } else if (strncasecmp(pos, "frame.", 6) == 0) { int frame = atoi(pos + 6); m_ReplayPos = recPlayer->positionFromFrameNumber(frame); } else { m_ReplayPos = atol(pos); if (m_ReplayPos > 0L && m_ReplayPos < 100L) m_ReplayPos = recPlayer->positionFromPercent((int) m_ReplayPos); } } } } return recPlayer; } bool cConnectionHTTP::ProcessURI(const std::string& PathInfo) { std::string filespec, fileext; size_t file_pos = PathInfo.rfind('/'); if (file_pos != std::string::npos) { size_t ext_pos = PathInfo.rfind('.'); if (ext_pos != std::string::npos) { // file extension including leading . fileext = PathInfo.substr(ext_pos); const char *ext = fileext.c_str(); // ignore dummy file extensions if (strcasecmp(ext, ".ts") == 0 || strcasecmp(ext, ".vdr") == 0 || strcasecmp(ext, ".vob") == 0) { size_t ext_end = ext_pos; if (ext_pos > 0) ext_pos = PathInfo.rfind('.', ext_pos - 1); if (ext_pos == std::string::npos) ext_pos = ext_end; fileext = PathInfo.substr(ext_pos, ext_end - ext_pos); } } // file basename with leading / stripped off filespec = PathInfo.substr(file_pos + 1, ext_pos - file_pos - 1); } if (fileext.length() > 5) { //probably not an extension filespec += fileext; fileext.clear(); } // Streamtype with leading / stripped off std::string type = PathInfo.substr(1, PathInfo.find_first_of("/;", 1) - 1); const char* pType = type.c_str(); if (strcasecmp(pType, "PES") == 0) { m_StreamType = stPES; #ifdef STREAMDEV_PS } else if (strcasecmp(pType, "PS") == 0) { m_StreamType = stPS; #endif } else if (strcasecmp(pType, "TS") == 0) { m_StreamType = stTS; } else if (strcasecmp(pType, "ES") == 0) { m_StreamType = stES; } else if (strcasecmp(pType, "EXT") == 0) { m_StreamType = stEXT; } Dprintf("before channelfromstring: type(%s) filespec(%s) fileext(%s)\n", type.c_str(), filespec.c_str(), fileext.c_str()); if ((m_MenuList = MenuListFromString(PathInfo.substr(1, file_pos), filespec.c_str(), fileext.c_str())) != NULL) { Dprintf("Channel list requested\n"); return true; } else if ((m_RecPlayer = RecPlayerFromString(filespec.c_str(), fileext.c_str())) != NULL) { Dprintf("Recording %s found\n", m_RecPlayer->getCurrentRecording()->Name()); return true; } else if ((m_Channel = ChannelFromString(filespec.c_str(), &m_Apid[0], &m_Dpid[0])) != NULL) { Dprintf("Channel found. Apid/Dpid is %d/%d\n", m_Apid[0], m_Dpid[0]); return true; } else return false; } cString cConnectionHTTP::ToText(char Delimiter) const { cString str = cServerConnection::ToText(Delimiter); return Streamer() ? cString::sprintf("%s%c%s", *str, Delimiter, *Streamer()->ToText()) : str; } vdr-plugin-streamdev/server/setup.h0000644000175000017500000000221413276341255017307 0ustar tobiastobias/* * $Id: setup.h,v 1.4 2010/07/19 13:49:31 schmirl Exp $ */ #ifndef VDR_STREAMDEV_SETUPSERVER_H #define VDR_STREAMDEV_SETUPSERVER_H #include "common.h" enum eStartSuspended { ssNo, ssYes, ssAuto, ss_Count }; struct cStreamdevServerSetup { cStreamdevServerSetup(void); bool SetupParse(const char *Name, const char *Value); int HideMenuEntry; int MaxClients; int StartSuspended; int LiveBufferMs; int StartVTPServer; int VTPServerPort; char VTPBindIP[20]; int VTPPriority; int AllowSuspend; int LoopPrevention; int StartHTTPServer; int HTTPServerPort; int HTTPPriority; int HTTPStreamType; char HTTPBindIP[20]; int StartIGMPServer; int IGMPClientPort; int IGMPPriority; int IGMPStreamType; char IGMPBindIP[20]; }; extern cStreamdevServerSetup StreamdevServerSetup; class cStreamdevServerMenuSetupPage: public cMenuSetupPage { private: static const char* StreamTypes[]; cStreamdevServerSetup m_NewSetup; void AddCategory(const char *Title); void Set(); protected: virtual void Store(void); public: cStreamdevServerMenuSetupPage(void); virtual ~cStreamdevServerMenuSetupPage(); }; #endif // VDR_STREAMDEV_SETUPSERVER_H vdr-plugin-streamdev/server/connection.c0000644000175000017500000001164313276341255020307 0ustar tobiastobias/* * $Id: connection.c,v 1.16 2010/08/03 10:51:53 schmirl Exp $ */ #include "server/connection.h" #include "server/setup.h" #include "server/suspend.h" #include "common.h" #include #include #include #include #include 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), m_Streamer(NULL) { } cServerConnection::~cServerConnection() { delete(m_Streamer); } const cChannel* cServerConnection::ChannelFromString(const char *String, int *Apid, int *Dpid) { 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 == 0) temp = cDevice::CurrentChannel(); #if APIVERSNUM >= 20300 LOCK_CHANNELS_READ; if (temp >= 1 && temp <= Channels->MaxNumber()) channel = Channels->GetByNumber(temp); #else if (temp >= 1 && temp <= Channels.MaxNumber()) channel = Channels.GetByNumber(temp); #endif } else { #if APIVERSNUM >= 20300 LOCK_CHANNELS_READ; channel = Channels->GetByChannelID(tChannelID::FromString(string)); #else channel = Channels.GetByChannelID(tChannelID::FromString(string)); #endif if (channel == NULL) { int i = 1; #if APIVERSNUM >= 20300 while ((channel = Channels->GetByNumber(i, 1)) != NULL) { #else while ((channel = Channels.GetByNumber(i, 1)) != NULL) { #endif if (String == channel->Name()) break; i = channel->Number() + 1; } } } if (channel != NULL && apididx > 0) { int apid = 0, dpid = 0; int 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) { dpid = channel->Dpid(i); break; } } } if (Apid != NULL) *Apid = apid; if (Dpid != NULL) *Dpid = dpid; } 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; } bool cServerConnection::Close() { if (IsOpen()) isyslog("streamdev-server: closing %s connection to %s:%d", Protocol(), RemoteIp().c_str(), RemotePort()); return cTBSocket::Close(); } cString cServerConnection::ToText(char Delimiter) const { return cString::sprintf("%s%c%s:%d", Protocol(), Delimiter, RemoteIp().c_str(), RemotePort()); } vdr-plugin-streamdev/server/connectionIGMP.h0000644000175000017500000000244413276341255020770 0ustar tobiastobias/* * $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 #define MULTICAST_PRIV_MIN ((uint32_t) 0xefff0000) #define MULTICAST_PRIV_MAX ((uint32_t) 0xeffffeff) class cStreamdevLiveStreamer; class cConnectionIGMP: public cServerConnection { private: int m_ClientPort; eStreamType m_StreamType; #if APIVERSNUM >= 20300 const cChannel *m_Channel; #else cChannel *m_Channel; #endif public: cConnectionIGMP(const char* Name, int ClientPort, eStreamType StreamType); virtual ~cConnectionIGMP(); #if APIVERSNUM >= 20300 bool SetChannel(const cChannel *Channel, in_addr_t Dst); #else bool SetChannel(cChannel *Channel, in_addr_t Dst); #endif virtual void Welcome(void); virtual cString ToText(char Delimiter = ' ') const; /* Not used here */ virtual bool Command(char *Cmd) { return false; } virtual bool Close(void); virtual bool Abort(void) const; }; inline bool cConnectionIGMP::Abort(void) const { return !IsOpen() || !Streamer() || Streamer()->Abort(); } #endif // VDR_STREAMDEV_SERVERS_CONNECTIONIGMP_H vdr-plugin-streamdev/server/suspend.dat0000644000175000017500000027014713276341255020165 0ustar tobiastobiasconst unsigned char suspend_mpg[] = "\x47\x40\x11\x10\x00\x42\xf0\x24\x00\x01\xc1\x00\x00\x00\x01\xff\x00\x01\xfc\x80\x13" "\x48\x11\x01\x05\x4c\x69\x62\x61\x76\x09\x53\x65\x72\x76\x69\x63\x65\x30\x31\x68\xc5" "\xdb\x49\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x47" "\x40\x00\x10\x00\x00\xb0\x0d\x00\x01\xc1\x00\x00\x00\x01\xf0\x00\x2a\xb1\x04\xb2\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x47\x50" "\x00\x10\x00\x02\xb0\x12\x00\x01\xc1\x00\x00\xe1\x00\xf0\x00\x02\xe1\x00\xf0\x00\x9e" "\x8b\x23\xd1\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x47\x41\x00" "\x30\x07\x50\x00\x00\x74\x04\x7e\x00\x00\x00\x01\xe0\x4e\xd4\x80\xc0\x0a\x31\x00\x07" "\xd8\x61\x11\x00\x07\xbc\x41\x00\x00\x01\xb3\x2d\x02\x40\x23\xff\xff\xe0\x18\x00\x00" "\x01\xb5\x14\x8a\x00\x01\x00\x00\x00\x00\x01\xb8\x00\x08\x00\x00\x00\x00\x01\x00\x00" "\x0f\xff\xf8\x00\x00\x01\xb5\x8f\xff\xf3\x41\x80\x00\x00\x01\x01\x2b\xf9\x95\x29\x4b" "\xf7\x2b\xe6\xae\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94" "\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x91\x78\x8b\x14\x99\xe6\x1b" "\x26\x8b\xae\x67\xad\x1b\x37\x4d\x23\x22\xea\xe9\x98\x6d\x6f\x4c\xe3\x69\xc8\x9b\xdd" "\x5d\x33\x0d\xad\x1b\x3f\x4d\x19\x13\x7b\xa4\x6c\xfd\x35\xbd\x33\x74\xd1\x91\x37\xba" "\x86\xcd\xd3\x5d\xd3\x37\x4d\x39\x10\xf7\x50\xd9\xba\x6b\x46\xce\x36\x47\x01\x00\x11" "\x8c\x89\xbd\xd3\xd3\x38\xda\xd1\xb3\x8d\xa3\x22\x6f\x74\xf4\xcf\xd3\x5a\x36\x61\xb4" "\xe4\x4d\xee\xa1\xb3\x0d\xae\xe9\x9b\xa6\x8c\x89\xbd\xd3\xd3\x3f\x4d\x6f\x4c\xfd\x34" "\x64\x4d\xee\x91\xb3\xf4\xd6\x8d\x9f\xa6\x9c\x89\xbd\xd3\xd3\x3f\x4d\x68\xd9\x86\xd1" "\x91\x37\xba\xba\x67\x1b\x59\xd3\x3f\x4d\x19\x10\xf7\x50\xd9\xba\x6b\x7a\x67\x1b\x4e" "\x44\xde\xe9\xe9\x9f\xa6\xb4\x6c\xc3\x68\xc8\x9b\xdd\x5d\x33\x74\xd7\x0d\x98\x6d\x03" "\x26\xf7\x50\xd9\x86\xd7\x0d\x9b\xa6\x9c\x89\xbd\xd4\x36\x6e\x9a\xe1\xb3\x74\xd1\x91" "\x37\xba\x86\xcc\x36\xb4\x6c\xe3\x68\xc8\x9b\xdd\x23\x67\x1b\x5a\x36\x6e\x9a\x72\x21" "\xee\xae\x99\x86\xd7\x0d\x9b\xa6\x8c\x89\xbd\xd5\xd3\x37\x4d\x77\x4c\xdd\x34\x64\x4d" "\xee\xa7\x9b\xa6\xb7\xa6\x7e\x9a\x32\x26\xf7\x48\xd9\xfa\x6b\x5e\x47\x01\x00\x12\x71" "\xb4\xe4\x4d\xee\x9e\x99\xfa\x6b\x46\xcc\x36\x8c\x89\xbd\xd4\x36\x6e\x9a\xde\x99\xc6" "\xd1\x91\x0f\x75\x0d\x98\x6d\x6f\x4c\xef\x4e\x44\xde\xe9\xe9\x9c\x6d\x6f\xf3\x0d\xa3" "\x22\x6f\x75\x3c\xc3\x6b\xba\x66\xe9\xa7\x22\x6f\x75\x3c\xdd\x35\xdd\x33\x0d\xa3\x22" "\x6f\x74\x8d\x9f\xa6\xb4\x6c\xc3\x68\xc8\x9b\xdd\x5d\x33\xf4\xd6\xf4\xcd\xd3\x46\x44" "\x3d\xd4\xf3\x0d\xad\xe9\x9c\x6d\x39\x13\x7b\xa4\x6c\xfd\x35\xa3\x66\x1b\x40\xc9\xbc" "\x00\x00\x01\x02\x2b\xf9\x95\x29\x4b\xf7\x2b\xe6\xae\x52\x94\x88\xb9\x4a\x52\x22\xe5" "\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x45\xe2\x2c\x52\x71\xb3\x74\xc9\xa2" "\xeb\x79\x86\xd6\xf4\xcc\x36\x9c\x88\xba\xba\x66\xe9\xad\x1b\x38\xda\x32\x26\xf7\x50" "\xd9\xba\x6b\x7a\x67\xe9\xa7\x22\x6f\x74\x8d\x9f\xa6\xb4\x6c\x47\x01\x00\x13\xc3\x68" "\xc8\x9b\xdd\x5d\x33\x0d\xae\x1b\x37\x4d\x19\x13\x7b\xab\xa6\x61\xb5\xc3\x66\xe9\xa3" "\x22\x1e\xea\x1b\x30\xda\xde\x99\xfa\x69\xc8\x9b\xdd\x23\x67\x1b\x5b\xd3\x37\x4d\x19" "\x13\x7b\xa8\x6c\xc3\x6b\x86\xcd\xd3\x46\x44\xde\xea\xe9\x9b\xa6\xb7\xa6\x7e\x9a\x72" "\x26\xf7\x4f\x4c\xfd\x35\xa3\x67\xe9\xa3\x22\x6f\x74\xf4\xce\x36\xb7\xa6\x67\xa3\x22" "\x6f\x75\x74\xcc\xf5\xc3\x66\x1b\x4e\x44\xde\xea\xe9\x9b\xa6\xb4\x6c\xfd\x34\x0c\x87" "\xba\x46\xcf\xd3\x5a\x36\x61\xb4\x64\x4d\xee\xa1\xb3\xf4\xd6\x8d\x9b\xa6\x9c\x89\xbd" "\xd5\xd3\x30\xda\xee\x99\x86\xd1\x91\x37\xba\x86\xcc\x36\xb8\x6c\xc3\x68\xc8\x9b\xdd" "\x5d\x33\x74\xd6\x8d\x9c\x6d\x39\x13\x7b\xa4\x6c\xe3\x6b\x7a\x66\xe9\xa3\x22\x6f\x75" "\x3c\xdd\x35\xdd\x33\x0d\xa3\x22\x1e\xea\xe9\x9b\xa6\xb8\x47\x01\x00\x14\x6c\xdd\x34" "\xe4\x4d\xee\xa1\xb3\x0d\xad\x1b\x3f\x4d\x19\x13\x7b\xa4\x6c\xe3\x6b\x7a\x67\x1b\x46" "\x44\xde\xe9\x1b\x38\xda\xd1\xb3\x3d\x39\x13\x7b\xab\xa6\x61\xb5\xdd\x33\x74\xd1\x91" "\x37\xba\x5e\x7e\x9a\xee\x99\x9e\x8c\x88\x7b\xa4\x6c\xfd\x35\xbf\xcc\x36\x9c\x89\xbd" "\xd5\xd3\x33\xd7\x0d\x9b\xa6\x8c\x89\xbd\xd5\xd3\x37\x4d\x68\xd9\xc6\xd1\x91\x37\xba" "\xba\x66\x1b\x5b\xd3\x33\xd3\x91\x37\xba\x86\xce\x36\xb7\xa6\x61\xb4\x64\x4d\xee\xa1" "\xb3\x74\xd7\x0d\x98\x6d\x19\x13\x7b\xab\xa6\x6e\x9a\xd1\xb3\xf4\xd3\x91\x0f\x74\x8d" "\x9f\xa6\xb4\x6c\xe3\x68\xc8\x9b\xdd\x23\x66\x1b\x5c\x36\x6e\x9a\x32\x26\xf7\x57\x4c" "\xc3\x6b\xba\x66\x1b\x4e\x44\xde\x00\x00\x01\x03\x2b\xf9\x95\x29\x4b\xf7\x2b\xe6\xae" "\x52\x94\x88\xb9\x4a\x52\x22\xe5\x28\xf3\x3c\x9b\xf8\x47\x01\x00\x15\xb5\xa5\x67\x4c" "\xe3\x64\xd1\x75\x3c\xc3\x6b\x7a\x66\x1b\x4e\x44\x3d\xd5\xd3\x30\xda\xe1\xb3\x74\xd1" "\x91\x37\xba\x86\xcc\x36\xb4\x6c\xfd\x34\x64\x4d\xee\x9e\x99\xc6\xd6\x8d\x9c\x6d\x38" "\xa9\xbd\xd2\x36\x71\xb5\xbd\x33\x0d\xa3\x22\x6f\x75\x74\xcc\x36\xbb\xa6\x6e\x9a\x31" "\x53\x7b\xab\xa6\x6e\x9a\xd1\xb3\x8d\xa7\x15\x37\xba\xba\x66\x1b\x5a\x36\x6e\x9a\x06" "\x43\xdd\x5d\x33\x0d\xae\x1b\x30\xda\x72\x26\xf7\x57\x4c\xc3\x6b\x86\xcd\xd3\x46\x44" "\xde\xea\x1b\x37\x4d\x6f\x4c\xe3\x68\x19\x37\xba\x86\xcd\xd3\x5a\x36\x61\xb4\xe4\x4d" "\xee\xae\x99\x86\xd7\x74\xcd\xd3\x46\x44\xde\xea\x1b\x30\xda\xde\x99\xfa\x68\xc8\x9b" "\xdd\x5d\x33\x0d\xad\x1b\x3f\x4d\x23\x26\xf7\x4f\x4c\xfd\x35\xbd\x33\x74\xd1\x91\x0f" "\x75\x0d\x9f\xa6\xb7\xa6\x71\xb4\x64\x4d\xee\x9e\x47\x01\x00\x16\x99\xba\x6b\x86\xcd" "\xd3\x4e\x44\xde\xea\x1b\x37\x4d\x6f\x4c\xfd\x34\x64\x4d\xee\xa1\xb3\x0d\xad\x1b\x38" "\xda\x32\x26\xf7\x4f\x4c\xe3\x6b\x46\xcd\xd3\x4e\x44\xde\xea\xe9\x98\x6d\x70\xd9\xba" "\x68\xc8\x87\xba\x86\xcd\xd3\x5d\xd3\x30\xda\x32\x26\xf7\x57\x4c\xdd\x35\xa3\x67\x1b" "\x4e\x44\xde\xe9\x1b\x38\xda\xde\x99\x86\xd1\x91\x37\xba\xba\x66\x1b\x5c\x36\x6e\x9a" "\x32\x26\xf7\x57\x4c\xdd\x35\xc3\x66\xe9\xa7\x22\x6f\x75\x74\xcc\x36\xb5\xe6\xe9\xa3" "\x22\x1e\xea\xe9\x99\xeb\xba\x66\xe9\xa3\x22\x6f\x75\x0d\x98\x6d\x70\xd9\x86\xd3\x91" "\x37\xba\x86\xcd\xd3\x5a\x36\x61\xb4\x64\x4e\xea\xe9\x9b\xa6\xbb\xa6\x6e\x9a\x32\x26" "\xf7\x53\xcc\x36\xbb\xa6\x6e\x9a\x72\x26\xf7\x57\x4c\xc3\x6b\x46\xcf\xd3\x46\x44\xde" "\xea\x1b\x37\x4d\x6f\x4c\xfd\x34\x64\x43\xdd\x47\x01\x00\x17\x23\x67\xe9\xad\xe9\x98" "\x6d\x39\x13\x7b\xab\xa6\x6e\x9a\xe1\xb3\x74\xd1\x91\x37\xba\xba\x66\xe9\xad\x1b\x3f" "\x4d\x19\x13\x7b\xa4\x6c\xfd\x35\xa3\x67\x1b\x4e\x44\xde\xe9\xe9\x9c\x6d\x6f\x4c\xdd" "\x34\x64\x4d\xe0\x00\x00\x01\x04\x2b\xf9\x95\x22\xf3\x74\xdf\xb8\xb5\xf3\x56\x91\x7a" "\xce\x99\xc6\xc8\x64\x5d\x23\x67\xe9\xad\xe9\x9b\xa6\x9c\x89\xdd\xe0\x19\xf4\x80\x54" "\x5f\x41\x2c\x20\x61\x7f\x14\x4b\x25\xa5\x05\x27\x25\x25\x20\x95\xd0\x58\xd2\xd1\xba" "\x33\x73\x4d\xb1\xe0\x0c\x00\x60\x1a\x8d\xf2\x40\xaf\xc9\xcf\xbe\x01\x88\x0e\xce\xe6" "\xe0\x0a\x6d\x1d\x32\x00\x36\xf8\xa2\x62\x40\x74\xb0\x42\x00\x84\x92\x90\xfb\xe0\x0a" "\x3e\x1a\x18\xcf\xff\xf9\x08\xe0\x60\x6b\xad\x25\x7b\xe6\xc0\x1a\x25\x1d\x05\x86\x7d" "\xbf\xdc\x97\x9f\x6e\xa3\xb9\x12\xf5\x40\x47\x01\x00\x18\x31\xc9\xd9\x05\xa3\xfc\x67" "\x5d\xec\x01\x31\x0d\x29\x41\x65\x06\xf1\xa1\xa0\x50\xb4\x93\x03\x7b\x33\xf4\x37\x42" "\x49\x08\x40\x17\x2b\xa0\xbe\x33\x0c\xdd\x2c\x65\x97\xa6\xd2\x02\x1c\x00\x7c\x01\x9e" "\x4a\xc6\x00\x9c\xa1\x84\x9e\xd8\x9a\x42\x7e\xa4\x33\x0d\x2c\x33\xf7\xea\x51\x33\x1f" "\x07\xbb\xc0\x31\x02\x98\x84\x05\x53\x91\x99\x09\x09\x40\x8e\x40\xf7\x20\x0e\x83\x40" "\xc8\x62\x39\xa7\x7b\xab\xa6\x6e\x9a\xde\x99\xfa\x6e\xcc\x8b\x9d\xee\x9e\x9b\x41\x31" "\x21\xa0\x21\x49\x68\x02\x8d\xb0\x62\x72\x0b\x21\x97\x86\x86\x6f\xbf\x25\x94\x56\xc9" "\x42\x4b\x2d\x0c\xc9\x6f\x90\x8f\xd6\x9e\x94\x74\x57\x90\xd2\x5f\xc8\x28\x33\xec\x1a" "\x4d\x2d\x01\x85\x77\xdb\xee\x9e\xb2\xd1\x93\xbe\x4f\x0c\x28\x68\x68\xc6\xef\x91\xef" "\x2c\x84\x14\x03\xa0\xcd\xb2\x90\x56\x47\x01\x00\x19\x37\xb8\x7f\xfd\x7f\xf3\x7f\x5f" "\xeb\xf7\x88\x00\xf8\x03\x44\x06\xe0\x32\x5a\x06\xe7\x64\x61\xff\x1b\xc7\xeb\xd1\x01" "\x08\x0e\xf8\x63\x21\x07\xf2\x75\xf4\x10\x00\xd8\x98\x10\x4b\x2c\x0a\x93\x52\x30\x60" "\xd4\x93\x4b\x28\x68\x24\x00\x18\x14\x49\x30\xa1\xa0\x3d\x19\x56\x00\x20\x01\x80\x12" "\x26\x82\x4f\xfc\x81\x50\x4d\xff\xbb\x20\x17\x2c\x24\x69\x2a\xf5\xa5\x00\x62\x01\x89" "\x35\x03\x40\x6e\x4c\x40\x49\x68\x18\x03\x64\xa4\x24\xa4\xa5\x09\x09\x00\xac\x24\x25" "\x04\x90\x8b\xc1\x2d\x04\xc2\x62\x06\x96\x92\x90\x82\x41\x20\x00\x61\x7a\x65\x00\xdc" "\x9a\x90\x84\xa4\x02\x3b\xf3\xa0\x82\x02\x60\x50\x11\xbf\xf0\x12\x7f\xe4\x07\x60\x9b" "\xff\x95\xa1\xa0\x0c\x00\x40\x1a\x08\xbf\xf2\x09\x3f\xea\x02\x10\x4d\xff\x9b\xb2\x00" "\x72\x01\x78\x22\xff\xc8\x14\x04\x47\x01\x00\x1a\x90\x07\x0d\x04\xdf\xf9\xa0\x00\x9c" "\x02\xf0\xc0\x44\xff\xc0\x28\x1a\x09\x00\x08\x1a\x18\x09\x9f\xf8\x1b\x6c\x01\x58\x08" "\x01\x17\xff\x03\x01\x24\x01\x2c\x40\x2a\x02\xb7\x9c\x02\xf0\x05\x40\x15\x80\xa4\x30" "\x02\xf0\x10\x81\x40\x49\x00\x40\x31\x5e\x05\x03\x00\xa0\x14\x0d\x0c\x02\x80\x21\x0c" "\x0d\x0c\x0d\x0d\x00\x98\x34\x02\xb0\x2a\x06\x03\x41\x2b\xff\x00\x27\x04\x60\x04\xb2" "\x01\x40\x06\x20\x8b\xfe\xa1\x80\x60\x11\xbf\xf0\x30\x34\x0c\x86\x02\x60\x02\x5c\x50" "\x10\x80\x80\x30\x04\x20\x50\x34\x30\x34\x05\x21\x81\xb7\x30\x60\x18\x04\x60\x04\x03" "\x17\x40\x68\x19\x0c\x00\x9e\xf4\x82\x08\x09\x80\x80\x11\xbf\xf0\x12\x7f\xe0\x0a\x82" "\x6f\xfe\x50\x10\x40\x4c\x04\x00\x8a\x00\x81\xa0\x93\xff\x20\x54\x13\x7f\xf2\xf5\xa0" "\x15\x80\x31\x02\x80\x64\x0a\x47\x01\x00\x1b\x00\x1c\x86\x00\xa0\x11\x7f\xf0\x0a\x00" "\xa4\x34\x30\x11\x00\x10\x34\x10\xff\xe4\x14\x3f\xf2\x80\x07\x20\x0e\x41\x17\xfd\x40" "\xa0\x24\x80\x38\x68\x26\xff\xcd\xa0\x68\x05\x41\x80\x89\xff\x81\x80\x92\x00\x96\x20" "\x60\x0a\xdf\x93\x04\x10\x13\x01\x00\x22\x80\x20\x68\x24\xff\xc8\x15\x04\xdf\xfc\xa0" "\x20\x80\x98\x08\x01\x14\x01\x03\x41\x27\xfe\x40\xa8\x26\xff\xe5\xeb\xc0\x0e\x40\x18" "\x82\x2f\xfa\x81\x40\x49\x00\x70\xd0\x4d\xff\x98\x80\x1c\x80\x33\x04\x5f\xf5\x02\x80" "\x92\x00\xe0\x9d\xff\x35\x01\x90\x10\x02\x2f\xfe\x06\x02\x48\x02\x52\x01\x50\x15\xbf" "\x28\x08\x20\x2e\x02\x00\x44\x00\x40\x0a\xc3\x01\x23\xfe\x43\x43\x41\x2f\xff\x03\x43" "\x43\x2e\xc8\x08\x40\x2e\x02\xa0\x20\x01\x48\x60\x21\x7f\xc8\x60\x25\x7f\xc8\x23\xff" "\xe0\x0a\x00\x50\x1a\x09\x47\x01\x00\x1c\x00\x08\x06\x2b\x83\x00\x18\x80\x5e\x05\x00" "\x27\x03\x00\x54\x11\xc0\x10\x05\x01\xa0\x97\xff\x81\xb7\x8f\x02\x80\x54\x30\x04\x21" "\x81\x81\xa1\x80\x89\xff\x90\x06\x00\xa0\x68\x22\x80\x20\x6d\x60\x85\xff\x20\x52\xfd" "\xde\x0d\xc5\xed\xf7\xdf\x7c\xfb\xe5\xef\xb9\xbb\xc5\xea\x00\x1e\x93\x00\x0e\x00\x43" "\xca\xc0\x38\x26\xee\x4d\x0c\x14\xdf\x60\xd2\xb2\x8e\xdf\xef\x9f\x80\x95\xb2\xb5\xb9" "\xef\x6c\x9a\x4c\x01\xb1\x7f\x05\x08\xbc\x90\xc0\xd0\x32\x56\xf7\x53\xce\xf5\xaf\x33" "\xda\x35\x17\x53\xcc\xf5\xcf\x33\xd0\xd3\xba\x9e\x67\xad\x79\xde\x86\x9d\xd2\xf3\xbd" "\x6b\xce\xf4\x34\xee\x97\x9d\xeb\x5e\x77\xa5\xa2\xe9\x79\xde\xb5\xe6\x7a\x1a\x77\x53" "\xcc\xf5\xaf\x3b\xd0\xd3\xba\x9e\x67\xad\x79\xde\x96\x9d\xd2\xf3\xbd\x6b\xcc\xf4\x34" "\xee\xa7\x9d\xeb\x5e\x47\x01\x00\x1d\x67\xa1\xa7\x75\x3c\xcf\x5c\xf3\x3d\x2d\x3b\xa9" "\xe6\x7a\xd7\x9d\xe8\x68\xba\x9e\x67\xad\x79\x9e\x86\x9d\xd4\xf3\x3d\x73\xcc\xf4\xb4" "\xee\xa7\x99\xeb\x9e\x67\xa1\xa7\x75\x3c\xcf\x5a\xf3\xbd\x0d\x3b\xa5\xe6\x7a\xe7\x99" "\xe9\x69\xdd\x4f\x33\xd7\x3c\xcf\x43\x45\xd4\xf3\x3d\x6b\xcc\xf4\x34\xee\xa7\x9d\xeb" "\x5e\x67\xa5\xa7\x75\x3c\xcf\x5c\xf3\x3d\x0d\x3b\xa9\xe6\x7a\xe7\x99\xe8\x69\xdd\x4f" "\x33\xd6\xbc\xef\x4b\x4e\xe9\x79\xde\xb5\xe6\x7a\x1a\x2e\xa7\x9d\xeb\x5e\x67\xa1\xa7" "\x75\x3c\xcf\x5c\xf3\x3d\x2d\x3b\xa5\xe7\x7a\xd7\x9d\xe8\x69\xdd\x2f\x3b\xd6\xbc\xcf" "\x43\x4e\xea\x79\xde\xb5\xe6\x7a\x5a\x77\x53\xcc\xf5\x91\xa5\xa2\xd5\xe2\xf1\xa5\x33" "\x80\x00\x00\x01\x05\x2b\xf9\xd3\xce\xf5\xaf\x33\xdf\xb5\x35\xf3\x97\x53\xcc\xf5\xcf" "\x33\xd2\xd1\x75\x47\x01\x00\x1e\x3c\xcf\x5a\xf3\xbd\x0d\x3b\xa9\xe7\x0c\x0d\x00\xa8" "\x04\x24\xc2\xcc\x26\xb8\x14\x2c\x0b\x1a\x78\x67\xe5\x8e\xdd\xca\xd8\x79\x36\xb1\xed" "\x00\x57\x70\xd0\xc2\xf6\xfb\x70\x12\x7f\x9c\x94\x5e\x59\x45\xb0\xf2\xbb\x7c\x2f\x5c" "\x40\x26\x2c\x06\xc4\xd2\x8b\x30\xee\x23\xdb\x93\x4a\x03\x25\x7d\x7c\x40\x06\xa4\xc4" "\x93\x40\x4c\x94\xa0\x25\x24\xc0\x0a\x50\x07\x86\x8c\x03\xc0\x23\x40\xd4\x00\x8e\xea" "\xa1\x28\x4a\x10\x94\x24\x6a\x12\x07\xef\x3a\x03\xa2\x68\x01\xf9\x31\x09\x02\x68\x09" "\x40\x12\x48\x1f\x40\x08\x92\x11\x7a\xe4\x02\x10\x04\x24\x9a\x03\xa0\x45\x00\x54\x80" "\xf0\x10\xc0\x15\x21\x24\xc0\x11\x0c\x08\x01\x12\x6f\x14\x02\xf2\x60\xd2\x62\x12\x04" "\x80\x06\x97\xa4\x01\x92\x46\x0d\x45\xe3\x79\x9e\xb9\xeb\x0b\x2f\x15\xbf\xff\x6d\x97" "\xff\x56\xd8\x47\x01\x00\x1f\x7f\xbd\xec\x53\xde\x4b\xdf\x38\x03\x20\xc0\x32\x03\x10" "\x32\x08\xa0\x12\x18\x09\x5f\xf8\x1a\x09\xa0\x08\x18\x09\x7f\xf9\x20\x02\xb0\x18\x81" "\x80\x30\x18\x18\x08\x9f\xf8\x09\x60\x08\x18\x0a\x20\x09\x73\x40\x1e\x06\x01\x80\x18" "\x06\x01\x90\x09\xc3\x40\x52\x1a\x0a\xc0\x08\x01\x35\xeb\x40\x62\x1a\x00\x7a\x03\x10" "\x18\x86\x82\x28\x02\x00\xa0\x30\x13\x3f\xf0\x15\x80\x12\xf9\x10\xc0\x42\x00\x90\xcb" "\xdd\x00\x9c\x36\xfc\x05\xf4\x10\x29\xd4\x1a\x08\xff\xf2\x03\xb0\x4b\xff\x90\xdb\xe7" "\xd7\xd1\x89\x9c\x02\x70\x47\xff\x90\x1d\x82\x5f\xfc\x97\x74\x80\x62\x08\x60\x10\x09" "\x1f\xf9\x88\xde\x80\x0d\x00\x73\xc4\x5e\x37\xb9\xc4\xc0\x06\xc0\x17\x81\x50\xcc\x4d" "\xc1\x84\xc0\xc0\xcf\x9c\x10\xbf\xe4\x34\xb2\x83\x58\x79\x2b\x13\x03\x59\x4b\xd9\x3d" "\x1c\xcf\x47\x01\x00\x10\xb5\xdf\x02\xa5\xec\x18\x1a\xeb\xc4\xae\xe2\x1a\xf3\x80\x3b" "\x00\x25\x0d\x0c\x48\x68\x09\xb8\x15\x01\x01\x30\x0a\xf0\x2a\xd8\xb2\xf0\x6f\x0c\x26" "\x06\x37\x7c\x1b\x8d\x2f\x17\x89\x47\xb7\x77\xb0\x00\x5c\x00\xe0\xa0\x0d\x70\x6f\x2c" "\xae\x59\x28\xcc\x47\xb9\x00\x76\x05\x40\xc3\x2d\xaf\xb6\x1a\x08\x9f\xf8\x18\x1a\x18" "\x09\x00\x08\x1a\x08\xa0\x08\x1a\x09\x80\x08\x1b\x71\x00\x17\x00\xec\x02\x70\x1d\x02" "\x28\x02\x00\x4c\x08\xe0\x08\x08\xa0\x08\x09\x00\x08\x09\xa0\x09\x68\x00\x62\x1a\x1a" "\x03\xb0\x28\x00\xc0\x04\x20\x13\x06\x80\xec\x30\x94\x1a\x09\x20\x08\x06\x6d\xe0\x0f" "\x83\x40\xc0\x03\xf0\x1d\x02\x37\xfe\x01\x90\xc0\x4a\x00\x40\x32\x09\x80\x09\x50\x68" "\x0e\x80\x0d\x80\x76\x06\x01\x10\x01\x03\x6d\x43\x40\x52\x19\x7c\xa0\x05\x24\xc6\xc4" "\x20\x47\x01\x00\x11\x1d\xe0\x12\x16\x03\x62\xcf\x01\xc1\x37\xb6\x0a\x26\xb1\xe7\x6f" "\xee\xfb\xde\x34\x01\xa9\x30\x02\xd0\x0c\x00\x62\x56\x57\x01\xb1\x6d\xf1\xfb\xec\xfb" "\xe0\xfb\xdb\xec\x59\x68\xdf\x14\x5f\xdf\x3e\x57\x36\xe9\x01\x09\x31\xc9\x45\xe1\x1c" "\x3a\x60\x20\x2d\x5f\x7b\x47\x9d\xe0\x03\x62\x68\x04\x85\x85\x79\xf9\x30\x86\xb0\x0a" "\x09\xbd\x87\x14\x5f\x48\x56\xdf\xfb\xbc\x06\xc5\x8e\xf7\x11\x2f\x01\x3d\x71\xef\x2e" "\x00\x89\xc0\x2c\x01\xdb\xb8\x21\xff\xc0\x43\x82\x38\x03\x13\x0e\x14\x45\x7b\xd0\x90" "\x80\x1f\x00\x66\x03\x16\x25\x13\x39\x33\x73\xcb\x21\xee\x53\x02\x40\x02\xf6\x26\xfe" "\x29\xb6\x17\xaf\x08\x03\x00\x18\x14\x02\x02\x11\x44\x22\x8e\xc6\x8f\xff\xbf\xfd\x9b" "\x3f\xeb\xf7\x98\x03\x02\x17\x29\xff\x53\xe6\x6b\xd0\x01\x89\x35\x1f\x36\x5d\xfa\x40" "\x47\x01\x00\x12\x02\xb5\x00\x1a\x70\x46\xff\xb2\xf0\x9d\xc9\x85\xf1\x81\x7b\x70\xeb" "\xd2\xbd\xca\x00\x4a\x03\xbc\x43\x01\x89\x30\x99\x97\x80\x75\xb9\x7c\xf0\x1c\x97\x9b" "\x0a\x01\xc3\xb8\x9d\x77\x5e\x60\x19\xe0\x2f\xb0\xe2\x97\x60\x03\x05\x13\x2e\xa7\x99" "\xeb\x5e\x77\xbb\x5a\xe6\xba\x5e\x77\xad\x79\xde\x86\x9d\xd2\xf3\xbd\x6b\xcc\xf4\x34" "\xee\xa7\x99\xeb\x5e\x77\xa5\xa2\xea\x79\x9e\xb5\xe7\x7a\x1a\x77\x4b\xce\xf5\xaf\x3b" "\xd0\xd3\xba\x5e\x77\xad\x79\x9e\x96\x9d\xd4\xf3\x3d\x73\xcc\xf4\x34\xee\xa7\x99\xeb" "\x5e\x77\xa1\xa7\x75\x3c\xcf\x5a\xf3\x3d\x2d\x17\x53\xcc\xf5\xcf\x3b\xd0\xd3\xba\x5e" "\x67\xae\x79\x9e\x86\x9d\xd4\xf3\x3d\x6b\xce\xf4\xb4\xee\x97\x99\xeb\x9e\x67\xa1\xa7" "\x75\x3c\xef\x5a\xf3\x3d\x0d\x3b\xa9\xe6\x7a\xd7\x9d\xe9\x68\xba\x5e\x77\xad\x79\x47" "\x01\x00\x13\xde\x86\x9d\xd2\xf3\xbd\x6b\xcc\xf4\x34\xee\xa7\x99\xeb\x9e\x67\xa5\xa7" "\x75\x3c\xcf\x5a\xf3\xbd\x0d\x3b\xa5\xe7\x7a\xd7\x99\xe8\x69\xdd\x4f\x3b\xd6\xbc\xcf" "\x4b\x4e\xea\x79\x9e\xb9\xe6\x7a\x1a\x2e\xa7\x99\xeb\x5e\x77\xa1\xa7\x74\xbc\xef\x5a" "\xf3\xbd\x2d\x3b\xa5\xe7\x7a\xd7\x99\xe8\x69\xdd\x4f\x33\xd6\x52\x96\x9d\xab\xcd\x4a" "\x4a\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x88\x00\x00\x01\x06\x2b\xf1\x4f\x33" "\xd6\xbc\xef\x7e\xc4\xd7\xcf\x5d\x2f\x3b\xd6\xbc\xcf\x43\x4e\xea\x79\xde\xb5\xe6\x7a" "\x1a\x77\x53\xcc\xf5\xaf\x3b\xd2\xd3\xbe\x40\x03\x00\xd0\x30\x1a\x1a\x18\x09\x1f\xf8" "\x18\x09\x9f\xf8\x09\x5f\xf8\x02\x90\x48\xff\xca\xd0\x06\x21\x80\x60\x06\x21\x81\xa0" "\x8b\xff\x80\x28\x0d\x04\xbf\xfc\x0c\x04\xcf\xfc\xba\x60\x64\x00\xf0\x34\x30\x47\x01" "\x00\x14\x34\x11\x40\x10\x12\xc0\x10\x11\x3f\xf0\x13\x7f\xf0\x12\x00\x12\xf2\x80\x0e" "\xc0\x60\x00\x7a\x03\x00\xd0\xc0\x44\xff\xc0\x44\x00\x40\x47\xff\xc0\xc0\x09\xaf\x9a" "\x0c\x04\x2f\xf7\x0c\x04\xa0\x04\xbd\x90\xd0\x14\x06\xdf\xa9\x7a\x13\x0a\x00\x3d\x01" "\xd9\x08\x84\x3f\x92\xcb\x5b\x01\x62\x63\x7e\x27\x7e\xc5\x7c\x38\xbb\x59\x44\xd2\x59" "\x58\xbc\xeb\xdb\xac\xf5\xde\x7c\x01\x61\x31\xc0\x6e\x03\xb1\xfc\x06\xc5\x2d\x4c\x53" "\xbf\xd8\xcf\xcb\x7d\xc7\x92\xb5\xe8\x80\x80\x84\x01\x4a\xc0\x48\xb0\xfb\x88\x07\x64" "\xd6\x26\x09\xbe\x50\x03\xf0\x1d\x80\x1e\x00\xc0\x9a\x5e\x3d\x98\xac\x3c\xbe\xad\x96" "\x3f\xde\xe4\xac\x03\x62\x16\x28\xff\xb2\xfb\x33\xf3\x52\xc2\xfa\xfd\xc8\x00\x52\x43" "\x01\xb8\x0e\xc8\x40\x3a\xc7\x14\x08\x5f\xf4\x5a\x9c\x51\x58\x52\xc0\xbe\x47\x01\x00" "\x15\xe0\x02\x8b\xb1\xfd\xe7\x80\x87\xac\x98\x4c\x04\x8f\xfd\xe2\x7d\xd0\x03\xa2\x98" "\x99\xf8\xfb\xf9\xa5\xf4\xc2\xb8\x21\xff\xd8\x23\xff\xd0\x0e\xc1\x2f\xfe\x8b\xbe\x69" "\x7d\x47\x70\x92\x68\x23\xff\xd0\x0e\xc1\x2f\xfe\x8b\xa0\x04\x2b\x21\x02\x47\xfe\xe2" "\x37\xb6\x00\xc8\x07\x3c\x45\xe7\xe4\xc0\x03\xfc\x59\x5d\xf1\x44\xa2\xf9\x0c\xbc\x08" "\x80\x0a\x4c\xdb\x92\xcb\x16\xd8\xec\xcb\xff\x6b\xe9\xc0\x21\x00\x31\x26\x10\xcb\x21" "\x90\xd0\x03\xa0\x0a\x4b\x26\x97\xf3\x81\xe4\x25\x3f\xe5\x1c\xec\x3a\xf1\x20\x06\x20" "\x21\x26\x80\x38\x00\x48\x05\x80\xb1\x60\x0e\x4b\x67\xc4\x90\x03\xc2\x62\xf2\xf2\x78" "\x0e\x7f\x74\x70\x09\x0b\xbe\xd7\x7c\xc0\x03\x22\x8b\x00\xc0\x84\xaf\xf7\x2d\x7e\x00" "\x76\xc4\xde\xb3\x2f\xf3\x60\x02\x82\x18\x20\xff\x00\x01\x70\x23\x7f\x47\x01\x00\x16" "\xb0\x08\x01\x30\x01\x41\x37\xfe\xc9\x96\xe0\x07\xc0\x87\xfd\xe0\x20\x04\x7f\xfb\x26" "\x82\x58\x02\x93\x6f\x5e\x08\x00\x8e\x00\xfc\x9b\x80\x19\x80\x1d\x18\x4d\x26\x02\x58" "\x02\xdf\x1e\x01\x38\x03\xe0\x1b\x00\x33\x00\xb4\x11\x00\x14\x06\xc0\x37\x04\x8f\xfa" "\x04\x30\x05\x04\xb0\x05\xba\x00\x42\x4c\x01\xb8\x05\x30\x08\x80\x0b\x78\x80\xa8\x21" "\x80\x39\x60\x8e\x00\x65\xda\x00\x32\x2c\xa2\xc0\x4c\x05\x09\x45\x94\x03\x82\xa6\x00" "\x6e\x05\x40\x2a\x00\xc8\x04\xe5\x80\x48\x51\x77\xd5\x0a\x02\x85\x14\x51\x28\x0b\x81" "\x60\x09\x0b\x2c\xa0\x4c\xff\xdb\xd5\x25\x94\x51\x40\x5c\x97\x6e\x05\x00\xa1\x2e\xfe" "\x38\x35\x00\x4d\x12\x5f\x66\x01\xb2\x10\x02\x50\x2a\x09\x3f\xee\x05\x41\x37\xff\x6e" "\x10\x06\x00\x0a\x49\xa0\x18\x16\x84\x01\x54\xa4\x9a\x03\x04\x92\x47\x01\x00\x17\x4b" "\x48\x0e\x40\xa0\x0f\x40\x7a\x50\x13\x03\xe8\x24\x84\xd4\x82\xc9\x85\x84\x21\x29\x42" "\x40\x7b\x66\x58\x0c\x12\x84\x5f\x82\x01\x88\x06\x20\x30\x01\x80\x04\xe4\xd0\x31\x7a" "\xb0\x0c\x40\x0c\x40\xc8\x0a\x00\x60\x09\x3f\xf8\x1a\x0a\x5f\xf9\x79\x30\x18\x80\xc0" "\x30\x03\x10\xc0\x30\x1b\x7d\x38\x03\x10\x18\x06\x80\x60\x1a\x06\x03\x41\x20\x01\x03" "\x41\x17\xff\x01\x53\xff\x28\x03\x20\x62\xa0\xc0\x31\x7a\x90\x10\x34\x0f\xa5\x29\x1a" "\x94\xa4\x92\x94\x81\x34\xd0\x6d\x68\xd9\x86\xde\xaa\x46\xa5\x23\x53\x79\x37\x50\xd9" "\xc6\xd6\x8d\x98\x6c\x86\x4e\xea\x1b\x30\xda\xe1\xb3\x0d\xa0\x64\x5d\x43\x66\x1b\x5c" "\x36\x61\xb4\x8c\x9d\xd2\x36\x71\xb5\xa3\x67\x1b\x40\xc9\xdd\x23\x67\x1b\x5a\x36\x61" "\xb4\x0c\x9d\xd4\x36\x71\xb5\x83\x67\x1b\x48\xc9\xdd\x43\x66\x47\x01\x00\x18\x1b\x5a" "\x36\x71\xb4\x0c\x9d\xd2\x36\x61\xb5\xc3\x66\x1b\x40\xc8\xba\x86\xce\x36\xb4\x6c\xc3" "\x69\x19\x3b\xa8\x6c\xc3\x6b\x46\xce\x36\x81\x93\xba\x46\xce\x36\xb4\x6c\xe3\x68\x19" "\x3b\xa4\x6c\xe3\x6b\x46\xcc\x36\x91\x93\xba\x86\xcc\x36\xb4\x6c\xe3\x68\x19\x3b\xa4" "\x6c\xe3\x6b\x46\xce\x36\x81\x91\x74\x8d\x9c\x6d\x68\xd9\x86\xd2\x32\x77\x50\xd9\xc6" "\xd6\x8d\x98\x6d\x03\x27\x75\x0d\x98\x6d\x70\xd9\x86\xd0\x32\x77\x50\xd9\x86\xd6\x8d" "\x9c\x6d\x23\x27\x74\x8d\x9c\x6d\x68\xd9\x86\xd0\x32\x77\x50\xd9\x86\xd7\x0d\x98\x6d" "\x03\x27\x75\x0d\x98\x6d\x70\xd9\x86\xd2\x32\x2e\xa1\xb3\x0d\xad\x1b\x38\xda\x06\x4e" "\xe9\x1b\x38\xda\xd1\xb1\xa4\x64\xee\xb1\xb3\x0d\x9e\x94\xce\xc4\x69\x49\x45\xca\x52" "\x91\x17\x29\x4a\x44\x5c\xa5\x29\x11\x72\x94\xa4\x45\xca\x47\x01\x00\x19\x52\x91\x17" "\x29\x4a\x44\x40\x00\x00\x01\x07\x2b\xf3\x23\x66\x1b\x5a\x36\x71\xb7\xeb\x23\x2f\x06" "\xe9\x1b\x38\xda\xd1\xb3\x8d\xa0\x64\xee\x91\xb3\x8d\xad\x1b\x38\xda\x46\x4e\xe9\x1b" "\x38\xda\xd1\xb3\x0d\xa0\x64\xed\x12\x31\x28\x4a\x12\x31\x28\x48\x42\x50\x92\x42\x6f" "\x20\x00\xc0\x0a\x80\x56\x02\x04\x21\x24\x92\x62\x40\x6c\x91\xa9\x42\x52\x10\x48\x4a" "\x46\x01\xf2\xca\x18\x90\x11\xde\xd0\x6d\xca\x00\x60\x05\x40\x6c\x02\x12\x52\x52\x10" "\x05\x50\x31\x08\x00\x14\x94\x35\x21\x00\x2b\x28\x68\xd0\x15\x22\xf3\x40\x34\x48\xc4" "\xa5\x00\x4c\x00\x6a\x8b\xb4\x04\x25\x0d\x28\x68\x12\xbd\xe0\x31\x26\x00\x1e\x00\xc4" "\x9a\x4c\x26\x02\x2f\xfd\x80\xdc\x98\x09\x1f\xf6\x09\xdf\xf7\x78\x32\x61\x30\x00\xf0" "\x06\x04\xc2\x60\x22\x80\x29\x32\xda\x03\x10\x0c\x00\x47\x01\x00\x1a\x0f\x40\x31\x01" "\x80\x0c\x41\x14\x01\x40\x6e\x09\x1f\xf7\x69\x00\x4a\x03\x10\x1b\x00\xc0\x11\xbf\xec" "\x9a\x01\x45\xe4\x93\x09\x97\xa2\x03\x70\x1b\x80\xda\xfd\x82\x40\xa8\x01\xe9\x34\x9a" "\x05\x41\x17\xfe\xcb\x48\xc0\x80\x12\x20\x21\x00\x2b\x42\x42\x4b\x40\x0a\xe2\x36\xbc" "\x21\x08\x18\x80\x84\x20\x0f\x40\xfb\xd7\x4a\x40\x6e\x4d\x70\x26\x9b\xc8\x18\x30\xa1" "\x80\x3c\xbf\x93\xdf\x79\x00\x76\x02\x10\x2a\x01\x58\x15\x02\x84\xc2\xd0\x4c\x02\xe4" "\xd4\x96\x58\xc1\xa5\x84\x81\x52\x68\x41\x40\x79\x04\x82\x4a\x40\x98\x45\xf0\x7b\xe9" "\x80\x82\x05\xe4\xd2\xc0\x35\x01\x89\x34\xb2\xc9\x88\x28\x0a\xa4\x68\x42\x4b\x49\x63" "\x49\x69\x48\x4a\x12\x52\x10\x80\x84\x8c\x08\x48\x1e\xbe\x58\x01\xd0\x01\xc2\x40\xaa" "\x00\x0e\x40\x70\x84\x8d\xbd\xd2\x5a\x4a\x41\x63\x47\x01\x00\x1b\x09\x37\xeb\x40\x6c" "\x08\x3f\xca\x03\x60\x10\x00\x84\x86\x08\xe0\x16\x4d\x04\x50\x05\x04\xe0\x05\xbe\x90" "\x02\x10\x13\x13\x40\x40\x4d\x26\x93\x09\xa0\x88\x00\xa0\x14\x5f\x0b\x04\x10\x32\x26" "\x82\x2f\xfd\x13\x2e\x10\x20\x81\x88\x01\x86\x00\xa4\x9a\x02\x60\x1b\x93\x41\x1f\xfe" "\x88\x60\x36\xbe\x5c\x01\xe9\x34\x01\xe0\x22\x00\x29\x0e\xd0\x99\x7f\x56\x04\x00\x30" "\x26\xe0\x07\x00\x54\x84\x00\xec\x9a\x70\x05\x24\x2b\xe4\x60\x19\x00\x13\x90\x80\x1d" "\x80\x6a\x43\x21\x02\x18\x03\x02\x28\x03\x4e\x00\x94\x01\x07\x00\xd7\x01\x40\x03\x82" "\x60\x0d\x89\x84\x20\x44\x00\x56\x6a\xc0\x28\x00\x88\x01\xc0\x02\xe0\x42\xff\x90\x0d" "\x41\x0b\xfe\x88\x64\x20\x49\x00\x62\x15\x98\x21\x00\x91\x0c\x03\x40\x46\xff\xaa\xc1" "\x0b\xfe\x80\x2d\xba\xc0\x0a\x40\x62\x4a\x26\x47\x01\x00\x1c\x14\x05\x10\x90\x92\x69" "\x68\x1a\x08\xe0\x06\x50\x4a\x40\x23\x24\x00\x8e\xf6\xe3\x65\x01\x08\x08\x52\x01\x88" "\x0c\x52\x03\x14\x8d\x40\xd0\x82\x90\x90\x12\x06\x00\x0c\x52\x49\x48\x08\x90\x07\xad" "\x23\x6f\xd1\xc0\x62\x94\x25\x23\x10\x48\x28\x22\xf0\x80\xa1\x44\xa4\x5d\x43\x66\x1b" "\x5c\x36\x61\xb7\x48\xcb\x0b\xf1\x00\x06\x00\x0f\xc1\x10\x03\x03\x41\x20\x02\x01\x34" "\x01\x01\x34\x01\x2f\x38\x00\xdc\x01\xf0\x00\x94\x07\x40\x60\x11\x3f\xf0\x11\x00\x10" "\x34\x11\xbf\xf2\xfa\xe8\x19\x04\x4f\xfc\x0c\x00\x9c\x30\x05\x21\x80\x8f\xff\x97\xc8" "\x00\x1b\x00\x3f\x0c\x00\x7e\x03\xa0\x32\x09\x00\x08\x1a\x08\xff\xf9\x79\xf7\xa8\x03" "\xb0\xc0\x31\x7f\x5b\x48\xd4\x0d\x42\x50\x94\x25\x01\x09\x42\x49\x28\x4a\x09\x28\x9c" "\x6d\xe3\x40\x09\x40\x1f\x01\x84\x80\x1b\x47\x01\x00\x1d\x86\x20\x92\x31\x04\x31\x88" "\x03\xe4\x84\x06\x80\x0c\x92\x02\x20\x1e\xde\xe8\x6d\xba\x43\x06\x5c\x21\x81\xa0\x65" "\x17\x48\xd9\xc6\xd6\x8d\x9c\x6c\xc6\x59\xdd\x23\x67\x1b\x5a\x36\x61\xb4\x8c\x9d\xd4" "\x36\x61\xb5\xc3\x66\x1b\x40\xc9\xdd\x43\x66\x1b\x5a\x36\x71\xb4\x0c\x9d\xd2\x36\x71" "\xb5\xa3\x67\x1b\x48\xc9\xdd\x23\x67\x1b\x5a\x36\x61\xb4\x0c\x8b\xa8\x6c\xc3\x6b\x86" "\xcc\x36\x81\x93\xba\x46\xce\x36\xb4\x6c\xc3\x69\x19\x3b\xa8\x6c\xe3\x6b\x46\xcc\x36" "\x81\x93\xba\x86\xcc\x36\xb4\x6c\xe3\x68\x19\x3b\xa8\x6c\xc3\x6b\x46\xce\x36\x91\x93" "\xba\x46\xce\x36\xb4\x6c\xc3\x68\x19\x3b\xa8\x6c\xe3\x6b\x46\xcc\x36\x81\x91\x75\x0d" "\x98\x6d\x70\xd9\x86\xd2\x32\x77\x50\xd9\x86\xd7\x0d\x98\x6d\x03\x27\x74\x8d\x9c\x6d" "\x68\xd9\x86\xd0\x32\x77\x50\xd9\x86\x47\x01\x00\x1e\xd7\x0d\x98\x6d\x23\x27\x75\x0d" "\x98\x6d\x70\xd9\x86\xd0\x32\x77\x50\xd9\x86\xd6\x8d\x9c\x6d\x03\x27\x75\x0d\x98\x6d" "\x6c\x69\x19\x17\x58\xd9\x86\xcd\x49\x4e\xe5\x29\x49\x45\xca\x52\x91\x17\x29\x4a\x44" "\x5c\xa5\x29\x11\x72\x94\xa4\x45\xca\x52\x91\x17\x29\x4a\x44\x5c\xa5\x29\x11\x72\x94" "\xa4\x45\xca\x52\x91\x10\x00\x00\x01\x08\x2b\xe4\x0d\x9c\x6d\x68\xd9\x86\xdf\xa8\x0c" "\xbc\x4b\xa8\x6c\xc3\x6b\x46\xce\x36\x91\x93\xba\x86\xcc\x36\xb4\x6c\xe3\x68\x19\x3b" "\xa4\x6c\xe3\x6d\x21\xa4\x80\x08\x80\x46\x81\xa1\x28\x03\xc3\x06\xa0\x20\x68\x1e\xb4" "\xa4\x0c\x86\x10\xc3\x03\x12\x1a\x81\xa1\x29\x48\x23\x7f\xe0\x61\x21\x09\x02\x43\x49" "\x01\xa1\x03\x02\x03\x46\x01\xeb\x88\x84\x02\x60\x32\x18\x94\x8d\x40\x41\x26\xd5\x21" "\xa3\x12\x32\xf9\xe3\x66\x03\x29\x47\x01\x00\x1f\x0c\x4a\x12\x82\x4a\x12\x84\xa0\x0f" "\x04\xa0\x24\x24\x07\x80\x4e\xd4\x06\x00\xc9\x0d\x24\x21\x80\x64\x02\x70\x81\x80\x64" "\x68\x21\x00\x52\x43\x49\x09\x42\x50\x91\x89\x24\x04\x4e\x01\x30\x18\x42\x42\x12\x06" "\x12\x35\x04\x81\x80\x61\x23\x02\x10\x31\x03\x06\xa1\x20\x49\x08\x08\x1a\x84\x04\xdc" "\x88\x00\xc1\x28\x09\x1a\x12\x84\xda\x80\x5a\x34\x22\xfe\x70\x08\x3f\xc6\x00\xf0\x11" "\x7f\xe8\x85\x7a\xe0\x06\x20\x21\x26\x00\x84\x04\x20\x84\x00\xc0\x85\xff\x44\x22\x10" "\x25\xff\xd5\xd7\x01\x09\x08\x02\xc0\x10\x82\x27\xfd\x02\x48\x03\x10\x88\x40\x96\x00" "\xd3\x80\x58\x09\x60\x0c\x08\x7f\xf4\x09\x00\x0c\x09\xbf\xf4\x08\x80\x0d\x79\x60\x16" "\x5d\x97\x48\xd8\x81\x91\x81\x36\xb0\xc0\xd0\x32\x18\x84\x81\x34\x21\x20\x11\xd4\x90" "\xc3\x12\x18\x01\x6a\x52\x18\x47\x01\x00\x10\x31\x00\x16\x06\x06\x86\x06\x8c\x40\x60" "\xd0\xd4\x21\x01\x03\x50\x42\x01\x48\xd4\x0d\x40\x4a\x52\x90\x3e\x82\x45\xea\x80\xe8" "\x04\xc9\x0d\x1a\x03\xcb\x88\x34\x34\x60\x08\xef\xe6\xc0\x83\xfc\x60\x0e\x01\x17\xfd" "\xc9\x80\x93\xff\x64\xc9\x41\x07\xfa\x00\x1c\x02\x2f\xfb\x93\x01\x27\xfe\xc9\x97\xd5" "\x09\xa0\x14\x13\x00\x6e\x08\xff\xf6\x09\x80\x0b\x64\x01\x89\x30\x06\xc4\xc0\x1b\x82" "\x18\x02\x80\x52\x08\x9f\xf6\x4c\x01\xb0\x27\xff\xd8\x23\xff\xdd\xe5\x00\xd8\x11\x80" "\x16\xf4\x00\x6d\x7f\x36\x04\x1f\xe5\x00\x74\x08\xbf\xe8\x42\x04\x9f\xfd\x2a\xb4\xb0" "\x04\x60\x0e\x8a\x04\x4f\xf7\x21\x16\x08\xe0\x06\x59\x2c\x12\xc0\x0e\xfa\x69\x60\x30" "\x04\x20\x0c\x21\x16\x51\x0c\x0b\x96\x42\x01\x81\x64\x30\x01\x41\x2c\xa2\x80\xb9\x45" "\x00\x48\x4b\x01\xc8\x27\x47\x01\x00\x11\xff\xed\xac\x86\x58\x16\x2c\x11\xc0\x0e\xa2" "\xc9\x44\x22\x50\x16\x04\xaf\xfd\xa4\xa2\x11\x45\xdf\xcf\x48\x60\x81\xfa\xc4\x20\x44" "\x00\x62\x11\x0c\x86\x09\x40\x0d\x7b\x10\x0d\x40\x35\x21\x80\x6a\x08\x7f\xf4\x09\x20" "\x0d\x54\x01\xa9\x08\x84\x42\x00\xb4\x02\xc0\x44\x00\x62\x18\x22\x80\x31\x08\x10\x80" "\x18\x12\x40\x1a\xd4\x43\x21\x02\x10\x06\x00\x5a\x08\x5f\xf4\x08\x9f\xf4\x08\x80\x0c" "\x42\x04\x5f\xfa\x04\x5f\xfa\x04\x3f\xfa\xb3\x04\x2f\xfa\xa2\xef\x1b\x30\xda\xb0\xc2" "\x10\x6a\x43\x00\xc8\x60\x60\x05\xa0\x85\xff\x43\x12\x84\xa1\x09\x24\x21\x09\x41\x24" "\x31\x23\x00\xf8\x4a\x06\x24\x07\xa1\x15\xe0\x3a\x40\x05\xa1\x84\x31\x88\x1a\x94\x21" "\x23\x10\x10\x1a\x35\x00\x64\x60\x23\x7f\xe2\x12\x10\x04\xc6\x84\x25\x29\x18\x11\x76" "\x00\x1b\x12\x02\x49\x47\x01\x00\x12\x37\x38\x19\x24\xdc\x1b\x30\xdb\x18\x60\x62\x00" "\x0e\xc0\x30\x0c\x48\x60\xc0\x0b\x52\x80\x32\x42\x08\x08\x18\x43\x40\x61\x20\x90\x84" "\x24\x30\x62\x40\xc0\xc9\x80\x4e\x42\x01\xd1\x08\x84\x18\x01\x62\x50\x90\x42\xff\xa0" "\xd0\xc1\xa8\x0c\x21\x06\x0c\x08\x00\x99\x28\x21\x04\x0d\x08\x1a\x84\xa4\x05\x54\x80" "\x1b\x12\x40\x79\x50\x19\x24\x5e\xc1\xb7\x20\x01\x90\x03\x70\x01\x00\x0e\xc6\xa5\x20" "\x84\x00\xc1\xa1\x04\x30\x1e\x01\x20\xd0\x11\x01\xf0\x1e\x00\xaa\xb8\x00\xf8\x05\x08" "\x1a\x18\x31\x09\x00\xd0\x07\x41\xa8\x0d\x18\x80\xc0\x42\xff\xa0\x0b\x10\x92\x48\x18" "\x1a\x1a\x34\x90\x91\xa9\x49\x25\x29\x03\xd1\x21\x00\x98\x0c\x80\x68\x01\x6a\x03\x03" "\x52\x90\x32\x1a\x80\x14\x00\xe8\x84\x1a\x08\x80\x08\x18\x07\xc3\x50\x35\x00\x22\xb1" "\x40\x09\x86\x06\x47\x01\x00\x13\x92\x6b\x4a\x50\x94\x5f\xcc\x89\xa0\x0d\x40\x1a\x13" "\x49\x80\x36\x00\x39\x26\x02\x4f\xfd\x93\x2f\xae\x93\x40\x6c\x4c\x26\x93\x49\x84\xc2" "\x68\x0d\xc9\x80\x8b\xff\x75\x60\x0f\x89\x84\xc2\x69\x30\x9a\x03\x72\x68\x22\x80\x58" "\x05\x20\x36\x26\x13\x41\x0f\xfe\xc0\x6e\x08\xbf\xf7\x68\x01\x08\x05\x00\x21\x26\xd0" "\x4d\x26\x00\x51\x59\x32\xee\x1b\x30\xda\xe1\xb3\x0d\xbb\x46\x5c\xb7\x48\xd9\xc6\xd6" "\x8d\x98\x6d\x03\x27\x75\x0d\x9c\x6d\x68\xd9\x86\xd0\x32\x77\x50\xd9\x86\xd7\x0d\x98" "\x6d\x23\x22\xea\x1b\x30\xda\xd1\xb3\x8d\xa0\x64\xee\xa1\xb3\x0d\xad\x1b\x30\xda\x06" "\x4e\xea\x1b\x30\xda\xe1\xb3\x0d\xa4\x64\xee\xa1\xb3\x0d\xad\x1b\x38\xda\x06\x4e\xea" "\x1b\x30\xda\xd1\xb3\x8d\xa4\x64\xee\x91\xb3\x8d\xad\x1b\x30\xda\x06\x4e\xea\x1b\x30" "\xda\xe1\xb3\x47\x01\x00\x14\x0d\xa0\x64\x5d\x43\x66\x1b\x5a\x36\x71\xb4\x0c\x9d\xd2" "\x36\x71\xb5\xa3\x67\x1b\x48\xc9\xdd\x23\x67\x1b\x5a\x36\x61\xb4\x0c\x9d\xd4\x36\x61" "\xb5\xc3\x66\x1b\x40\xc9\xdd\x43\x66\x1b\x5a\x36\x71\xb4\x8c\x9d\xd4\x36\x61\xb5\x91" "\xa4\x64\x5a\x0d\x9a\x34\xa6\x77\x29\x4a\x44\x5c\xa5\x29\x11\x72\x94\xa4\x45\xca\x52" "\x91\x17\x29\x4a\x44\x5c\xa5\x29\x11\x72\x94\xa4\x45\xca\x52\x91\x17\x29\x4a\x44\x5c" "\xa5\x29\x11\x72\x94\xa4\x45\xca\x52\x91\x17\x29\x4a\x44\x40\x00\x00\x01\x09\x2b\xa0" "\x6c\xc3\x6b\x46\xce\x36\xfd\x20\x65\xe3\xdd\x23\x67\x1b\x5a\x36\x61\xb4\x0c\x9d\xd4" "\x36\x71\xb5\xa3\x66\x1b\x40\xc9\xdd\x43\x6d\x01\x81\x88\x0c\x24\x00\x44\x07\x90\x80" "\x24\x80\x1e\x50\x6c\xc3\x6c\x90\x35\x00\x23\x9d\xba\x08\x48\x42\x06\x20\x20\x24\x68" "\x0a\x80\x47\x01\x00\x15\x90\xc0\x3c\x10\x48\xac\x0c\x21\x06\x06\x20\x61\x24\x92\x49" "\x42\x00\xf9\x25\x03\x10\x84\x20\x24\x0f\x84\x04\xd5\x0d\x9c\x6d\x88\x19\x0d\x18\x30" "\x6a\x42\x6b\x18\x8b\xb5\x04\x21\x81\xa0\x64\x0c\xa5\x29\x01\x50\x60\x48\x0a\xd2\x80" "\xc4\x24\x02\x18\xa4\x84\x84\xa4\x68\xd4\x80\xa8\x20\x0f\x92\x42\x46\x0d\x40\x08\x92" "\x8a\xb1\xb3\x8d\xb3\x00\xb4\x34\x68\xd1\xa9\x1a\x48\x98\xc4\x5d\x43\x6d\x60\x16\x20" "\x04\x43\x06\x0c\x18\x18\x30\x60\x12\x24\x0d\x18\x03\xc1\xb5\x63\x66\x1b\x08\x4a\x43" "\x09\x09\x18\x36\x44\x9b\x03\x02\x46\xd6\x81\x84\x0d\x02\x64\x84\x0c\x08\x03\xc4\x80" "\x91\x83\x52\x30\x07\xa9\x80\xd9\x86\xd0\x80\xd0\xd4\x01\xf4\xd4\x31\x16\xe9\x21\x20" "\x25\x21\x28\x4a\x42\x06\x80\xf0\x24\x20\x05\x41\x29\xa9\x1b\x5c\x36\x61\xb2\x1a\x94" "\x92\x47\x01\x00\x16\x53\x16\xa0\x23\x48\x00\xac\x68\xc0\x08\x2b\x43\x43\x06\x84\x0d" "\x08\x09\x08\x18\x18\x92\x49\x24\x92\x18\x98\x0d\x98\x6d\x92\x03\x43\x50\x07\xe6\x31" "\x16\xa9\x00\x16\x92\x12\x34\x05\x63\x50\x98\x80\x8d\x20\x2b\x01\x5d\x60\xd9\x86\xc8" "\x6a\x40\x99\x22\x76\xa0\x78\x60\x00\xb8\x00\x68\x31\x13\x00\x8d\x24\x91\xa9\x01\xea" "\x42\x52\x12\x02\x34\xce\x36\x61\xb4\x0d\x48\x13\x4c\xee\xb4\xa5\x20\x4c\x07\x84\x84" "\x12\x00\x90\xd1\x83\x40\x21\x98\x6a\x49\x03\x42\x00\x90\xc0\xc4\x84\x81\x30\x94\x26" "\xb0\x6c\xe3\x68\x1a\x1a\x04\xe6\x31\x16\x80\x78\x65\x06\xd6\x8d\x98\x6c\x82\x62\xea" "\x1b\x30\xda\xe1\xb3\x0d\x81\x91\x75\x0d\x98\x6d\x68\xd9\xc6\xd2\x32\x77\x50\xd9\x86" "\xd6\x8d\x98\x6d\x03\x22\xea\x1b\x30\xda\xe1\xb3\x0d\xa0\x64\xee\xa1\xb3\x0d\xae\x1b" "\x47\x01\x00\x17\x30\xda\x46\x4e\xea\x1b\x30\xda\xd1\xb3\x8d\xa0\x64\xee\xa1\xb3\x0d" "\xad\x1b\x30\xda\x06\x4e\xea\x1b\x38\xda\xd1\xb3\x0d\xa0\x64\xee\xa1\xb3\x0d\xae\x1b" "\x30\xda\x46\x45\xd4\x36\x61\xb5\xa3\x67\x1b\x40\xc9\xdd\x23\x67\x1b\x5a\x36\x71\xb4" "\x0c\x9d\xd2\x36\x71\xb5\xa3\x66\x1b\x48\xc9\xdd\x43\x66\x1b\x5c\x36\x34\x0c\x9d\xd6" "\x36\x61\xb5\x94\xa6\x76\xa3\x66\xa5\x25\x17\x29\x4a\x44\x5c\xa5\x29\x11\x72\x94\xa4" "\x45\xca\x52\x91\x17\x29\x4a\x44\x5c\xa5\x29\x11\x72\x94\xa4\x45\xca\x52\x91\x17\x29" "\x4a\x44\x5c\xa5\x29\x11\x72\x94\xa4\x45\xca\x52\x91\x17\x29\x4a\x44\x5c\xa5\x29\x11" "\x72\x94\xa4\x45\xca\x52\x91\x10\x00\x00\x01\x0a\x2b\xd0\x36\x71\xb5\xa3\x66\x1b\x7d" "\xf8\xcb\xca\xba\x86\xcc\x36\xb4\x6c\xe3\x68\x19\x17\x48\xd9\xc6\xd6\x8d\x9c\x6d\x47" "\x40\x00\x11\x00\x00\xb0\x0d\x00\x01\xc1\x00\x00\x00\x01\xf0\x00\x2a\xb1\x04\xb2\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x47\x50" "\x00\x11\x00\x02\xb0\x12\x00\x01\xc1\x00\x00\xe1\x00\xf0\x00\x02\xe1\x00\xf0\x00\x9e" "\x8b\x23\xd1\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x47\x01\x00" "\x18\x23\x27\x74\x8d\x9c\x6d\x68\xd9\x86\xd0\x32\x77\x50\xd9\x86\xd7\x0d\x98\x6d\x03" "\x27\x75\x0d\x98\x6d\x70\xd9\x86\xd2\x32\x77\x50\xd9\x86\xd6\x8d\x98\x6d\x03\x27\x75" "\x0d\x9c\x6d\x68\xd9\x86\xd0\x32\x77\x50\xd9\x86\xd7\x0d\x98\x6d\x23\x22\xea\x1b\x30" "\xda\xd1\xb3\x0d\xa0\x64\xee\xb1\xb3\x0d\xad\x1b\x30\xda\x06\x4e\xea\x1b\x30\xda\xe1" "\xb3\x0d\xa4\x64\xee\xa1\xb3\x0d\xad\x1b\x38\xda\x06\x4e\xea\x1b\x30\xda\xd1\xb3\x0d" "\xa0\x64\xee\xa1\xb3\x8d\xad\x1b\x30\xda\x46\x45\xd4\x36\x61\xb5\xc3\x66\x1b\x40\xc9" "\xdd\x43\x66\x1b\x5a\x36\x71\xb4\x0c\x9d\xd4\x36\x61\xb5\xa3\x67\x1b\x48\xc9\xdd\x23" "\x67\x1b\x5a\x36\x61\xb4\x0c\x9d\xd4\x36\x61\xb5\xa3\x67\x1b\x40\xc9\xdd\x43\x66\x1b" "\x5a\x36\x71\xb4\x8c\x9d\xd2\x36\x71\xb5\xa3\x67\x1b\x40\xc8\xba\x46\x47\x01\x00\x19" "\xce\x36\xb4\x6c\xc3\x68\x19\x3b\xa8\x6c\xc3\x6b\x86\xc6\x91\x93\xba\xc6\xcc\x36\x7a" "\x53\x3b\x11\xa5\x25\x17\x29\x4a\x44\x5c\xa5\x29\x11\x72\x94\xa4\x45\xca\x52\x91\x17" "\x29\x4a\x44\x5c\xa5\x29\x11\x72\x94\xa4\x45\xca\x52\x91\x17\x29\x4a\x44\x5c\xa5\x29" "\x11\x72\x94\xa4\x45\xca\x52\x91\x17\x29\x4a\x44\x5c\xa5\x29\x11\x72\x94\xa4\x45\xca" "\x52\x91\x17\x29\x4a\x44\x5c\xa5\x29\x11\x72\x94\xa4\x44\x00\x00\x01\x0b\x2b\xeb\x8d" "\x9c\x6d\x68\xd9\xc6\xdf\x6c\x32\xf3\x2e\x91\xb3\x8d\xad\x1b\x30\xda\x46\x4e\xea\x1b" "\x30\xda\xe1\xb3\x0d\xa0\x64\xee\xa1\xb3\x0d\xad\x1b\x38\xda\x06\x4e\xea\x1b\x30\xda" "\xd1\xb3\x0d\xa4\x64\xee\xa1\xb3\x0d\xae\x1b\x30\xda\x06\x4e\xea\x1b\x30\xda\xd1\xb3" "\x8d\xa0\x64\x5d\x43\x66\x1b\x5a\x36\x71\xb4\x8c\x9d\xd2\x36\x71\x47\x01\x00\x1a\xb5" "\xa3\x66\x1b\x40\xc9\xdd\x43\x66\x1b\x5a\x36\x71\xb4\x0c\x9d\xd4\x36\x61\xb5\xa3\x67" "\x1b\x48\xc9\xdd\x23\x67\x1b\x5a\x36\x71\xb4\x0c\x9d\xd2\x36\x71\xb5\xa3\x66\x1b\x40" "\xc8\xba\x86\xcc\x36\xb8\x6c\xc3\x69\x19\x3b\xa8\x6c\xc3\x6b\x46\xce\x36\x81\x93\xba" "\x46\xce\x36\xb4\x6c\xc3\x68\x19\x3b\xa8\x6c\xe3\x6b\x46\xcc\x36\x91\x93\xba\x86\xcc" "\x36\xb4\x6c\xe3\x68\x19\x3b\xa8\x6c\xc3\x6b\x46\xce\x36\x81\x93\xba\x46\xce\x36\xb4" "\x6c\xc3\x69\x19\x3b\xa8\x6c\xe3\x6b\x26\xa0\x64\x5d\x63\x66\x1b\x35\x25\x3b\x11\xa5" "\x25\x17\x29\x4a\x44\x5c\xa5\x29\x11\x72\x94\xa4\x45\xca\x52\x91\x17\x29\x4a\x44\x5c" "\xa5\x29\x11\x72\x94\xa4\x45\xca\x52\x91\x17\x29\x4a\x44\x5c\xa5\x29\x11\x72\x94\xa4" "\x45\xca\x52\x91\x17\x29\x4a\x44\x5c\xa5\x29\x11\x72\x94\xa4\x47\x01\x00\x1b\x45\xca" "\x52\x91\x17\x29\x4a\x44\x5c\xa5\x29\x11\x72\x94\xa4\x45\xca\x52\x91\x17\x29\x4a\x44" "\x5c\xa5\x29\x11\x00\x00\x00\x01\x0c\x2b\xf4\xe3\x66\x1b\x5a\x36\x71\xb7\xd7\x0c\xbc" "\xfb\xa4\x6c\xe3\x6b\x46\xce\x36\x81\x93\xba\x46\xcc\x36\xb8\x6c\xc3\x68\x19\x3b\xa8" "\x6c\xe3\x6b\x06\xce\x36\x91\x93\xba\x86\xcc\x36\xb4\x6c\xe3\x68\x19\x17\x48\xd9\xc6" "\xd6\x8d\x98\x6d\x03\x27\x75\x0d\x9c\x6d\x68\xd9\x86\xd2\x32\x77\x50\xd9\x86\xd6\x8d" "\x9c\x6d\x03\x27\x74\x8d\x9c\x6d\x68\xd9\xc6\xd0\x32\x77\x48\xd9\xc6\xd6\x8d\x98\x6d" "\x23\x27\x75\x0d\x98\x6d\x70\xd9\x86\xd0\x32\x2e\xa1\xb3\x0d\xad\x1b\x38\xda\x06\x4e" "\xe9\x1b\x38\xda\xd1\xb3\x0d\xa4\x64\xee\xa1\xb3\x8d\xad\x1b\x30\xda\x06\x4e\xea\x1b" "\x30\xda\xe1\xb3\x0d\xa0\x64\xee\xa1\xb3\x0d\xad\x1b\x38\x47\x01\x00\x1c\xda\x46\x4e" "\xea\x1b\x30\xda\xd1\xb3\x0d\xa0\x64\xee\xa1\xb3\x8d\xac\x8d\x23\x22\xd0\x6c\xd1\xa5" "\x33\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5" "\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22" "\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a" "\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b" "\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x88\x00\x00\x01\x0d\x2b\xf6" "\xc3\x67\x1b\x58\x36\x71\xb7\xd2\x8c\xb8\xae\xa1\xb3\x0d\xad\x1b\x38\xda\x06\x4e\xe9" "\x1b\x38\xda\xd1\xb3\x0d\xa4\x64\x5d\x43\x67\x1b\x5a\x36\x61\xb4\x0c\x9d\xd4\x36\x61" "\xb5\xa3\x67\x1b\x40\xc9\xdd\x23\x67\x1b\x5a\x36\x71\x47\x01\x00\x1d\xb4\x8c\x9d\xd2" "\x36\x71\xb5\xa3\x66\x1b\x40\xc9\xdd\x43\x66\x1b\x5c\x36\x61\xb4\x0c\x9d\xd4\x36\x61" "\xb5\xc3\x66\x1b\x48\xc9\xdd\x43\x66\x1b\x5a\x36\x71\xb4\x0c\x8b\xa4\x6c\xe3\x6b\x46" "\xcc\x36\x81\x93\xba\x86\xcc\x36\xb8\x6c\xc3\x69\x19\x3b\xa8\x6c\xc3\x6b\x86\xcc\x36" "\x81\x93\xba\x86\xcc\x36\xb4\x6c\xc3\x68\x19\x3b\xa8\x6c\xe3\x6b\x29\x4c\xed\x46\xc6" "\x34\x94\x5c\xa5\x29\x11\x72\x94\xa4\x45\xca\x52\x91\x17\x29\x4a\x44\x5c\xa5\x29\x11" "\x72\x94\xa4\x45\xca\x52\x91\x17\x29\x4a\x44\x5c\xa5\x29\x11\x72\x94\xa4\x45\xca\x52" "\x91\x17\x29\x4a\x44\x5c\xa5\x29\x11\x72\x94\xa4\x45\xca\x52\x91\x17\x29\x4a\x44\x5c" "\xa5\x29\x11\x72\x94\xa4\x45\xca\x52\x91\x17\x29\x4a\x44\x5c\xa5\x29\x11\x72\x94\xa4" "\x45\xca\x52\x91\x17\x29\x4a\x44\x5c\xa5\x29\x11\x47\x01\x00\x1e\x72\x94\xa4\x45\xca" "\x52\x91\x17\x29\x4a\x44\x5c\xa5\x29\x11\x00\x00\x00\x01\x0e\x2b\xfa\x28\xd9\xc6\xd6" "\x8d\x98\x6d\xee\x0c\xb9\x6e\xa1\xb3\x0d\xae\x1b\x30\xda\x46\x45\xd4\x36\x61\xb5\xa3" "\x67\x1b\x40\xc9\xdd\x43\x66\x1b\x5a\x36\x71\xb4\x0c\x9d\xd2\x36\x71\xb5\xa3\x66\x1b" "\x48\xc9\xdd\x43\x66\x1b\x5c\x36\x61\xb4\x0c\x9d\xd4\x36\x61\xb5\xa3\x67\x1b\x40\xc9" "\xdd\x23\x67\x1b\x5a\x36\x61\xb4\x8c\x8b\xa8\x6c\xe3\x6b\x46\xcc\x36\x81\x93\xba\x86" "\xcc\x36\xb8\x6c\xc3\x68\x19\x3b\xa8\x6c\xc3\x6b\x46\xcd\x48\xc9\xdd\x43\x67\x1b\x3d" "\x29\x9d\x88\xd2\x92\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b" "\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94" "\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x47\x01\x00\x1f\x22\x2e\x52\x94\x88\xb9" "\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48" "\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52" "\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22" "\xe5\x29\x48\x88\x00\x00\x01\x0f\x2b\xfa\xa8\xd9\x86\xd6\x8d\x9c\x6d\xea\x0c\xb9\xee" "\x91\xb3\x8d\xad\x1b\x30\xda\x06\x4e\xea\x1b\x30\xda\xe1\xb3\x0d\xa0\x64\xee\xa1\xb3" "\x0d\xad\x1b\x38\xda\x46\x4e\xea\x1b\x30\xda\xd1\xb3\x8d\xa0\x64\xee\x91\xb3\x8d\xad" "\x1b\x30\xda\x06\x4e\xea\x1b\x38\xda\xd1\xb3\x0d\xa4\x64\x5d\x43\x66\x1b\x5a\x36\x34" "\x0c\x9d\xa0\xd9\x86\xcd\x49\x4e\xc4\x69\x49\x45\xca\x52\x91\x17\x29\x4a\x44\x5c\xa5" "\x29\x11\x72\x94\xa4\x45\xca\x52\xf9\xe9\x47\x01\x00\x10\x08\x10\x00\xc4\x34\x0a\x80" "\x5c\x00\x25\x28\x03\x30\x1d\x00\x1f\x01\x80\xd0\xd7\x18\x4d\x1a\xc4\xc1\xa3\x42\x06" "\x0d\x03\xc9\xb7\x21\x80\xec\x30\x07\x43\x00\xca\x46\x0d\x48\xc4\x00\xae\x94\x21\x29" "\xbf\xa1\x52\xe0\x00\x80\x01\xd1\x30\x04\x20\x27\x00\x3c\x0c\x00\xd4\x31\xcb\x48\x61" "\x41\x89\x03\x04\xd6\x0d\x49\x60\x22\x08\x18\x68\xda\xa0\x06\x20\x0c\x10\x00\xc0\x34" "\x02\xc4\x80\x9c\x84\x43\x21\xa0\x60\xd0\xc0\xcc\x1a\x10\xe0\x4d\x3d\x03\x00\x44\x49" "\x5c\x80\x2d\x0c\x03\x20\x60\x0f\x8c\x1a\x30\x62\x49\x10\x31\x24\x9b\xf4\x94\xa5\x2c" "\x6a\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\xe2\xc0\x14\x00\x13\x06\x80\x25\x28\x35" "\x20\x50\x04\xe4\xc4\x24\x30\x60\x19\x0c\x4a\x03\x42\x06\x8d\x18\x12\x30\x67\x24\x5b" "\x10\xc8\x61\x80\x3a\x40\x19\x0d\x48\x47\x01\x00\x11\xc4\x01\x3a\x50\x81\xb7\xed\x29" "\x71\xc0\x1e\x80\x64\x50\x05\xc4\x34\x90\x80\x2b\x41\x0c\xb0\xc0\xd0\xd0\xc0\xd1\x88" "\x0d\x40\xc4\x86\x24\x60\x09\x51\x9c\x3a\xe2\x80\xc0\x00\xb9\x00\x27\x00\x7c\x90\x07" "\xe0\x06\x60\x3a\x0c\x21\x06\x04\x06\x81\x94\x06\x8d\x40\x68\xc4\x24\x0c\x12\x02\x52" "\x12\x07\x8f\x9a\x48\x61\x88\x42\x12\x94\x84\x24\x90\x10\x90\x3f\x21\x89\xbf\x65\x4b" "\xe7\xe0\x0d\x81\x00\x0b\xc3\x40\x18\x80\xe9\x20\x54\x04\xc0\x1a\x80\xe9\x28\x42\x40" "\xc2\x46\xa0\x86\x80\x91\x88\x48\x12\x01\xed\xb1\x20\x05\x43\x46\x20\x06\x00\x0f\x40" "\x1f\x00\x9c\x35\x29\x00\x99\x04\x31\x89\x24\x74\x8d\x01\x48\xcd\x6a\x00\x6e\x03\xa1" "\xa9\x40\x19\x09\x41\x24\x21\x21\x00\x2b\x08\x90\x18\x4d\xfb\x4a\x5c\xa0\x0b\xc0\x1d" "\x00\x9c\x01\xc1\x0c\x02\xc0\x13\x47\x01\x00\x12\x93\x12\x18\x95\xb3\x92\x12\xb6\x1a" "\x8e\x07\xd1\x65\x00\x60\x00\xe0\x10\x7f\xa4\x07\x68\x01\x32\x08\x65\x13\x03\x52\x03" "\x8c\x94\x24\x24\x62\x12\x94\x84\xa1\x01\x03\x10\x80\x3f\x60\x92\x18\x62\x03\x00\xc8" "\xd0\x81\x83\x46\x92\x42\x2a\x18\x12\x32\xfb\xf4\xbe\x70\x43\x04\x00\x33\x48\x0e\x80" "\x76\x18\x03\xa0\xd0\x07\x60\x19\x06\x10\xc3\x46\x01\x80\xc4\x81\x91\xa9\x1a\x91\x88" "\x60\x80\x3e\x9b\xd4\x80\x39\x00\x54\x01\x88\x02\xe4\x86\x20\x04\xe1\x83\x00\xce\xe9" "\x47\x42\x72\x51\xc6\x12\x42\x71\xa8\xb6\x0c\x00\xd4\x07\x44\x34\xa1\x08\x0d\x42\x46" "\x84\xa0\x07\xa0\x2a\x02\x72\x48\x62\x51\x7d\xea\x52\xf1\xc0\x0e\x80\x09\x00\x31\x00" "\x72\x01\x91\x34\x03\x50\xc1\x88\x28\x31\x09\x19\x92\x49\x26\x81\x84\x04\xa1\x03\x02" "\x97\x62\x1a\x43\x0c\x21\x20\x47\x01\x00\x13\x68\x6a\x46\x20\x91\x52\x10\x36\xfd\x8d" "\x29\x4b\x29\xdc\xa5\x29\x11\x72\x94\xa4\x45\xca\x52\x91\x17\x29\x4a\x44\x5c\xa5\x29" "\x11\x72\x94\xa4\x45\xca\x52\x91\x17\x29\x4b\x80\x00\xc0\x01\xc0\x20\xff\x48\x0e\xd0" "\x02\x64\x10\xca\x26\x06\xa4\x07\x19\x28\x48\x48\xc4\x25\x29\x09\x42\x02\x06\x21\x00" "\x7e\xb0\xd0\xd4\x06\x21\x29\x48\x42\x10\x49\x91\x26\xfb\xf4\xbc\x78\x01\x58\x02\x60" "\xc0\x04\x81\xa0\x3a\x21\x00\x56\x92\x19\x0c\x34\x30\x0c\x86\xe1\x8c\x84\x01\xe4\x0c" "\x48\x1f\x74\xa1\x20\x11\x5e\xc2\xa2\x11\x0d\x04\x30\xc4\x06\x86\x04\xa1\x24\x89\x25" "\x03\x2e\x52\x94\xb2\xaa\xe5\x2f\x9a\x00\x3e\x04\x00\x31\x28\x04\xe0\x1a\x70\x07\xe1" "\xa0\x18\xa0\x00\xe8\x0c\x06\x86\x8d\x0c\x0c\x2c\x0c\x80\x51\xd2\x34\x20\x34\x60\x12" "\x4d\xf4\xbb\xa4\x84\x03\x47\x01\x00\x14\xb0\xc2\x1a\x43\x50\x18\x91\xa0\x61\x23\x12" "\x02\xb0\x25\x42\x50\x94\x5c\xa5\x29\x73\x57\x72\x94\xa4\x45\xca\x52\x91\x17\x29\x4a" "\x44\x5c\xa5\x29\x11\x72\x94\xa4\x45\xca\x52\x91\x17\x29\x4a\x44\x40\x00\x00\x01\x10" "\x2b\xfb\x20\xd9\x86\xd7\x0d\x98\x6d\xd6\x32\xca\xea\x1b\x30\xda\xd1\xb3\x8d\xa4\x64" "\xee\x91\xb3\x8d\xad\x1b\x38\xda\x06\x4e\xe9\x1b\x38\xda\xd1\xb3\x0d\xa0\x64\xee\xa1" "\xb3\x0d\xad\x8d\x23\x22\xd0\x6c\xd1\xa5\x33\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5" "\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9" "\x7c\x20\x10\x01\x04\x0a\xa0\x0a\x80\x5c\x02\x70\x13\x25\x03\x40\xc0\x68\xd0\x90\x94" "\x01\x82\x1a\x06\x0c\x18\x09\x00\x08\x91\xa0\x2b\xbe\xef\x7c\xa8\x10\x01\x17\x06\x80" "\x09\x40\x4e\x90\x90\x47\x01\x00\x15\x30\x00\x7c\x31\x20\x48\x6a\x43\x50\x31\xc0\x7a" "\x30\x0c\x8c\x01\x1d\xec\x80\x2f\x0c\x1a\x01\x81\x0c\x07\x83\x12\x80\x15\xa1\x36\x00" "\x3a\x48\xc0\xd4\x5e\xd0\x0c\x9c\x00\xb0\x03\x42\x18\x06\xa4\x30\x43\xff\x42\x18\x0d" "\xc8\x40\x20\x26\x02\x27\xfd\x90\x8c\x26\x90\xf8\xe1\xd7\xa8\x00\x74\x00\xe8\x04\xe0" "\x0b\xc0\x4e\x03\x62\x61\xe0\x89\xff\x81\xa6\x6c\x3f\xdc\xf0\x40\xfd\x60\x10\x93\x40" "\x42\x00\xbc\x03\x5f\xc1\x10\x01\x49\xbc\x85\x81\x20\x01\x9f\x5f\x2e\x04\x00\x2f\x26" "\x13\x00\x1e\x00\x22\x21\x80\x3b\xfd\xc0\x6e\x01\x69\x34\x9a\x20\x0a\x5a\x90\xc8\x60" "\x06\xc4\x20\x0b\x41\x13\xfe\x88\x74\x5f\xb3\xe0\x80\x05\x20\x1a\x10\x88\x64\x20\x0d" "\x09\xa0\x0e\xd8\x03\x32\x18\x22\x00\x28\x0d\xb8\x0d\x81\x18\x01\x56\x2e\xf4\xa0\x0e" "\x40\x12\x10\xc0\x47\x01\x00\x16\x18\x00\x6a\x01\x50\x08\x49\xac\x08\xdf\xf7\x9e\xe5" "\x80\x14\x10\x88\x60\x0b\xc0\x11\x81\x42\x19\x0c\x02\xd2\x18\x21\x7f\xd0\x05\xa4\x32" "\x69\x14\x12\x7f\xee\xf2\x20\x81\xfa\x00\x36\x00\x7a\x00\xbc\x01\xd0\x03\xec\xa6\xf8" "\x02\xd0\x48\xff\x90\x1b\x10\x2a\x00\xb4\x86\x42\x00\xd4\x84\x42\x00\xb4\x02\xd0\x42" "\x00\x60\x0b\x48\x60\x8c\x00\xd1\x7e\x94\x01\xd8\x20\x01\x50\x00\x80\x01\xe7\x00\xcc" "\x9a\x02\x1c\xb0\x1b\x00\x59\x98\x10\xff\xed\xc7\x08\x9c\x01\x00\x20\x01\x48\x60\x03" "\x10\x10\x01\x50\x0d\x48\x60\x54\x02\xc2\x61\x08\xfb\xc5\x82\x07\xe2\x10\xf9\x0c\x00" "\x44\x00\xf0\x0a\x02\x27\xfd\x93\x79\x0f\x88\xea\x21\x11\xc8\x76\x00\x07\xa0\x84\x03" "\x20\x80\x05\x40\x16\x10\x80\xa8\x0d\xc0\x2d\x26\x00\x58\x08\xff\xf6\x01\x68\x21\xff" "\xd0\x05\x80\x47\x01\x00\x17\x95\xff\x57\x68\x01\xb9\x0c\x00\xd8\x84\x08\xbf\xf4\x43" "\x21\x82\x50\x03\x59\x80\x59\x7f\x64\x00\x58\x00\xe8\x10\x7f\xa0\x04\x20\x16\x00\xdc" "\x02\xc7\x36\xf1\x7c\x10\x00\xd3\xf0\x07\xbc\x0a\x13\x00\xa8\x21\x00\x31\x30\x10\xff" "\xe8\x86\x4d\x56\xe0\x8c\x00\xa1\x83\xf8\x0d\xa4\x04\x00\x3c\x00\x7c\xe0\x0e\xc8\x64" "\x32\x69\x33\x82\x10\x06\x65\xb8\x22\x00\x29\x0c\xc2\x76\x26\xdd\x80\x40\xfd\x60\x06" "\x24\xd2\x18\x02\xf2\x68\x0d\x88\x7d\x80\x34\x72\x11\x31\x81\x2b\xfe\xc9\xef\x70\x10" "\xc8\x64\x32\x18\x06\xa0\x16\x10\x88\x60\x88\x00\xd6\x80\x88\x00\xd7\xea\x40\x1a\x80" "\x68\x80\x07\xc0\x19\x81\x50\x18\x20\x9a\x80\x2a\x82\x61\x63\x00\xca\x00\xf8\x00\xbd" "\x04\x84\x80\xf7\x5e\x08\x10\x01\x10\x35\x20\x02\x50\x18\x86\x0d\x02\xc9\x01\x80\xd4" "\xa0\x07\x47\x01\x00\x18\xa5\x13\x4a\x18\x00\x35\x4d\xf5\x5b\xe7\xa0\x80\x07\x60\x31" "\x00\xa8\x01\x5a\x03\x06\x02\x1f\xfc\x93\x09\xa1\x80\x2a\x18\x80\x1b\x82\x60\x02\x5c" "\xa0\x0f\x50\x34\x03\x00\x2a\x82\x48\x05\x09\x00\x82\xd0\x98\x08\x7f\xf6\x8b\xd8\x00" "\x4e\x02\x00\x42\xfe\x10\x18\x90\x89\x80\x89\xff\x84\xd2\xb8\x24\x7f\xe0\x15\xd8\x37" "\x82\x57\xfe\x06\xd5\x80\x17\x80\xc4\x30\x07\x41\x80\x30\x00\x60\x4c\x58\x60\x06\x0e" "\x03\x70\xd0\x45\xff\xb0\x13\x82\x27\xfe\x02\x48\x02\x00\x4c\x1d\x78\x70\x04\xe0\x30" "\xe0\x0e\x00\x1d\x80\x62\x00\x9c\x34\x11\x40\x10\x98\x00\x78\x08\xff\xec\x1a\x4c\x01" "\xb0\x25\x00\x28\x18\x03\x37\xda\xc0\x27\x0d\xb4\x00\xc0\x10\xbf\xdc\x98\x4c\x04\x60" "\x05\x26\x13\x01\x27\xfe\xe9\x26\xdf\xc0\xc0\x16\x02\x10\x0b\x82\x00\x17\x16\x03\xb0" "\x43\x47\x01\x00\x19\x00\x80\xd4\x81\x40\x1d\x93\x46\x82\x37\xfd\xa4\x96\x51\x31\x20" "\x23\x00\xa0\x65\xfa\x0a\xfc\xfe\x08\x1f\xa6\x08\x40\x26\x00\x54\x42\x00\xd0\x07\x23" "\x40\x0f\x80\x4c\x34\x0f\x8c\x26\x20\x96\x34\x60\x24\x00\x28\x04\x37\xd9\xc0\x18\x00" "\x3c\x2c\x01\xb8\x06\x60\x50\x04\xc5\x28\x06\xe9\x09\x48\x04\x85\x01\xf4\x80\x45\x75" "\x00\x34\x4a\x00\x6c\x02\x70\x13\xa4\x11\x3f\xec\x9a\x94\x8d\x01\xe2\x28\x26\x92\x50" "\x8b\xf9\x28\x01\x38\x21\x7f\xa0\x01\x30\x06\x84\x32\x18\x21\xff\xd0\x21\x00\x61\x0c" "\x12\x7f\xe8\x85\x7a\x20\x40\x04\x00\x10\x00\x5a\x00\xe4\x9a\x4d\x03\x1f\xe0\x28\x42" "\x7c\xe6\x10\xfa\xb0\x04\x18\x9b\x79\xb0\x40\xfc\x40\x10\x93\x09\x80\x1a\x10\x80\x34" "\x21\x60\x43\xff\xa0\x0a\x41\x20\x01\x49\x87\x40\x10\x01\x08\x0a\x30\x06\x80\x1a\x90" "\x47\x01\x00\x1a\xc1\x17\xfe\x89\xb8\x98\x09\x20\x0b\x9c\x13\x3f\xea\xf4\x40\x2d\x60" "\x05\xc4\x30\x46\xff\xa0\x43\x00\x60\x48\xff\xab\x20\x44\xff\xab\xf5\x80\x05\x20\x07" "\x20\x0e\xc0\x0b\x48\x64\xc0\x10\x39\x0c\x02\xce\x4c\x21\x10\x81\x18\x01\x58\x62\x85" "\x00\x53\xfd\xe5\x41\x03\xef\xc0\x42\x42\xe0\x20\x02\x80\x50\x11\x3f\xeb\x90\xc0\x6e" "\x09\x00\x0a\xca\x27\xc0\x10\x00\xa8\x01\x29\x0c\x01\x78\x03\xb0\x2a\x08\x40\x0c\x42" "\x21\x02\x10\x03\x10\xc8\x60\x84\x00\xc4\x20\x42\xff\xa0\x43\xff\xa2\x10\x22\x7f\xdd" "\xf4\xb0\x02\x90\x13\x00\x1b\x00\x31\x01\x39\x08\x0c\x93\x49\xa3\x00\xf9\x34\x93\xc0" "\x28\x34\x89\x72\x10\x88\x40\x85\xfe\x84\x22\x10\x21\x80\x30\x01\xb1\x0a\xbb\xf4\x04" "\xc0\xc0\x1b\x93\x59\xc0\xc1\x12\xf9\x50\x15\x00\x5c\x01\xa8\x02\xe0\x04\x44\x32\x47" "\x01\x00\x1b\x18\x00\x84\x10\x80\x18\x86\x08\x80\x0c\x42\x04\x30\x06\x21\x80\x5b\x7d" "\x38\x03\x20\x10\x70\x10\x80\x84\x84\x05\x01\x17\xfe\xc9\xb8\x30\x9f\x7c\x70\x10\x3f" "\x0c\x9b\x80\x2a\x01\x00\x06\x84\xc2\x63\x28\x02\xdc\x08\xc0\x0a\x4c\x21\x13\x0e\x22" "\xdc\x20\x1a\x90\xc1\x0b\xfe\x80\x34\x21\x5a\x90\xaf\xe8\x40\x0f\x00\x10\x00\x5a\x01" "\xa0\x00\x94\x04\x24\xd0\x0b\x00\x41\xbb\x3e\x3f\xfb\x30\x03\x90\x02\xa2\x60\x06\x40" "\x19\x13\x00\x1f\x80\x80\x04\x38\x04\x24\x2f\xd6\xa6\xf9\xaf\x2e\x08\x00\x54\x00\xec" "\x84\x4c\x26\x70\x05\xc0\x0e\xc8\x40\x87\xff\x40\x86\x00\xc0\x37\x04\x6f\xfb\x04\x40" "\x05\x0f\xbd\x48\x20\x7e\xb8\x02\xe0\x41\x01\xe0\x1d\xe2\x11\x08\x9a\x4d\xe4\x22\x18" "\x05\x39\xeb\x00\xb4\x86\x00\x6e\x01\x60\x24\x80\x30\x22\x80\x31\x0c\x85\x2b\x47\x01" "\x00\x1c\xf6\x21\x84\x2e\x05\x49\xa0\x60\x31\x6e\x75\xf3\xd0\x41\x02\x80\x10\x01\x40" "\x2a\x42\x00\xd0\x01\xf8\x06\x7c\x07\x40\x21\x21\xf3\x80\x6c\x08\x40\x0c\x09\x3f\xf5" "\x7b\xb0\x10\x60\x2a\x03\x77\x21\x00\x4e\x42\x00\xa4\x12\x40\x15\x77\xc3\x41\x08\x04" "\x81\x03\xf2\x40\x34\x04\x50\x06\x00\xd4\x84\x09\x3f\xf4\x4c\x04\xe0\x06\xa0\x01\xd9" "\x0c\x02\xc0\x47\x00\x60\x44\xff\xa0\x46\x00\x69\x90\xaf\xe9\xa0\x82\x04\x89\x0c\x00" "\x77\xc0\x1e\x10\xc8\x64\x22\x61\x0c\x11\x00\x17\xb1\x35\x89\xae\x46\x80\x01\x58\x02" "\x02\x60\x02\x40\x03\xa2\x60\x05\xa0\x37\x21\x86\xef\xc9\xac\x62\xee\x30\x20\x01\x88" "\x06\x84\x32\x68\x02\x50\x42\xff\xa0\x1b\x80\xdc\x04\x04\xc2\x69\x3b\x00\x58\x45\xb1" "\x80\x2f\x21\x02\x0f\xf1\x80\x14\x80\x59\xc9\xa4\xc0\x0b\x09\x98\x84\x42\x47\x01\x00" "\x1d\xdc\xe3\x41\x0c\x01\x45\x93\x40\x2d\x26\xfd\x68\x01\x69\x0c\x00\xdc\x10\xff\xe8" "\x10\x80\x18\x84\x42\x00\xb4\x87\x4d\xf6\x00\x35\x00\x20\x26\x00\x64\x00\xb0\x84\x00" "\xec\x86\x01\xa8\x05\x24\x30\x0b\x01\x08\x01\x81\x1f\xfe\x96\x78\x23\x80\x2a\xee\x88" "\x03\xc0\x04\x00\x16\x80\x68\x00\x25\x01\x09\x34\x02\xc0\x10\x6e\xcf\x8f\xfe\xf9\xf8" "\x20\x7e\x19\x0c\x9a\x43\x00\x36\x00\xd0\x98\x08\xdf\xf4\x43\x21\x82\x40\x03\x10\x01" "\x0b\xfe\xad\xe0\x80\x05\x40\x0e\xc8\x44\xc2\x67\x00\x5c\x00\xec\x84\x08\x7f\xf4\x08" "\x60\x0c\x03\x70\x46\xff\xb0\x44\x00\x50\xfb\xa0\x84\x01\xa8\x06\xa0\x06\xc0\x16\x82" "\x37\xfd\x02\x50\x03\x52\x08\x9f\xf5\x7e\xa0\x01\xc8\x01\x51\x30\x03\x20\x0c\x89\x80" "\x0f\xc0\x40\x02\x1c\x02\x12\x17\xeb\x53\x7c\xd7\x20\x04\x00\x80\x05\x47\x01\x00\x1e" "\xe4\x22\x18\x03\xc0\x03\x52\x68\x06\xa0\x14\x31\x0c\x86\x01\x41\x31\xc0\x28\x21\x1f" "\xee\xc0\x20\x7e\xb8\x02\xe0\x41\x01\xe2\x19\x30\x84\x4c\x26\x93\x79\x08\x87\x80\x6f" "\x9e\xf9\xa0\x21\x80\xb8\x20\x01\x81\x08\x11\xc0\x18\x84\x4d\x04\x8f\xfa\x26\x90\xc0" "\x2d\xd8\x13\x7f\xe8\x11\x80\x1a\x08\x60\x1a\x90\xc0\x2c\x21\x00\x58\x43\x04\x3f\xfa" "\x21\xc5\xfd\x0c\x01\x70\x01\x07\x00\x7a\xf8\x9a\x05\x49\x84\x22\x6f\x26\x00\x5a\x4c" "\x21\x02\x27\xfd\xf1\xdd\x81\x14\x01\x6e\xe8\x20\x80\xe0\x06\x60\x02\x32\x18\x06\x44" "\x22\x67\x21\x93\x49\x83\x87\x02\x2f\xfe\x08\xbe\x76\x08\x00\x7a\x42\x01\xb9\x34\x01" "\xe8\x03\xb2\x18\x79\x34\x77\x04\x3f\xfa\x21\x93\x9a\xf1\xa0\x0b\x88\x60\x16\x90\x80" "\x35\x00\x76\x08\x40\x0c\x08\x5f\xe8\x43\x21\x90\xc0\x2d\x04\x60\x47\x01\x00\x1f\x06" "\x21\x02\x10\x03\x10\x80\x2d\xb4\x21\x80\x3b\x21\x90\x88\x64\x30\x44\xff\xa0\x43\x00" "\x60\x49\xff\xa2\x1c\x80\x2c\xbf\xab\xe0\x40\x02\xa2\x16\x00\xd7\x80\x3c\xe0\x0f\xc8" "\x5c\x04\xff\x80\x50\xd8\x10\x80\x18\x7b\x13\x7b\x76\xf6\xb2\x60\x01\x43\x80\x3b\x00" "\x10\x10\xc0\x4e\x4c\x21\x10\xc0\x6e\xdc\xf6\x01\xb0\x05\x80\x16\xb8\x55\xc6\x00\x5c" "\x01\x98\x03\xa0\x07\x44\x30\x1b\x90\x80\x2d\x00\x76\x01\xa9\x36\xe3\x02\x00\x17\xe2" "\x68\x03\xd0\x05\xe0\x0f\x40\x35\x01\x0f\x72\x19\x33\x62\x67\x12\xb2\x30\x05\x96\x04" "\x32\x19\x0c\x84\x42\x21\x90\xc1\x18\x01\x81\x2b\xfe\xaa\xbf\x54\x00\xec\x00\x80\x00" "\xd8\x04\x38\x0a\x13\x00\x43\x94\x42\x00\xb4\x86\x43\x21\x82\x10\x03\x13\x09\x80\x85" "\xff\x40\x16\x1f\x28\x02\x10\x40\x02\x9e\x00\x90\x04\xc4\x20\x47\x01\x00\x10\x2a\x01" "\x68\x0d\xdf\x80\xd8\x9a\xc6\x71\x57\x28\x03\x5c\x08\x3f\xd0\x08\x1f\xac\x00\x70\x08" "\x80\x0c\x01\x6e\x00\xb4\x11\xbf\xec\x02\xc7\x3c\x12\xc0\x1a\xc2\x08\x1f\x94\x92\x18" "\x03\xd0\x0d\x48\x40\x1a\x10\x81\x10\x01\x81\x0f\xfe\xf8\x23\x7f\xd1\x0c\x86\x01\x60" "\x05\x20\xa9\xff\x53\x21\x10\xc8\x60\x16\x80\x68\x42\x00\xb4\x11\x00\x18\x02\xd2\x1c" "\xaf\xdb\x80\x30\x00\x28\x00\xd4\x01\x7e\x26\x90\xf7\x00\xb0\x84\x4c\x3c\x9a\x4d\x3a" "\xf0\xc0\x05\x00\x0b\x00\x1e\x60\x07\x60\x37\x01\x00\x06\xa4\x32\x18\x01\xb0\x0d\xc0" "\x6e\x08\x40\x0c\x67\x00\xa0\x8a\xf7\x3c\x10\x3f\x40\x86\x08\x20\x30\x00\xb8\x84\x00" "\xe8\x84\x08\x9f\xf4\x01\x40\x24\x00\x31\x0a\xe7\x02\x00\x18\x80\x80\x84\x00\xe8\x01" "\xd1\x0c\x86\x08\x9f\xf4\x08\x7f\xf4\x43\x04\x60\x06\x05\x47\x01\x00\x11\x00\x06\x04" "\x2f\xfa\xbb\x00\x35\x00\xd4\x86\x43\x00\xd0\x86\x43\x04\x5f\xfa\x04\x20\x06\x21\x10" "\xa8\x00\xb2\xfe\x94\x00\x52\x01\x68\x03\x10\x04\xa0\x54\x84\x42\x00\xb4\x86\x01\x60" "\x21\x00\x30\x24\x80\x28\x21\xff\xd8\x25\x7f\xdb\xc8\x08\x20\x50\x18\x02\x02\x69\x30" "\x84\x01\x98\x06\xa4\x22\x61\x0c\x87\x98\x84\x4c\x21\x02\x17\xfd\x31\x34\x71\x34\x76" "\xbc\x40\x20\x01\xb0\x08\x08\x60\x85\xfe\x80\x0e\xc8\x6b\x26\x80\x85\x58\x43\xfe\x43" "\xbd\x11\x08\x03\x30\x06\x00\x1a\x00\x3f\x26\x80\x3d\x21\x90\xc8\x60\x1a\x80\x87\x01" "\x5f\xc0\x2c\x21\x60\x42\x00\x65\x80\xdf\x1e\x43\x04\x3f\xfa\x32\xe4\x00\xb4\x11\x00" "\x18\x84\x08\x80\x0c\x42\xae\xfd\x80\x03\xd0\x04\x40\x21\x00\x74\x01\x9e\x00\xd1\x80" "\x2d\x26\x93\x41\x0c\x01\x40\x6c\x6a\xef\x3e\x43\x04\x47\x01\x00\x12\x00\x29\x26\x00" "\x38\x26\x6e\x00\xec\x03\x50\x0a\xb9\x34\xe2\x60\x14\x00\xa6\xf9\xf8\x01\x40\x01\xb8" "\x03\xb0\x07\x60\x1a\x80\x3b\x01\x09\x30\x06\xc4\x32\x10\x21\x00\x61\x30\x84\x08\xa0" "\x0c\x08\x9f\xf5\x7d\x10\x84\x08\x5f\xb8\x00\x8f\x93\x48\x60\x86\x00\xc0\x16\xe0\x49" "\x00\x50\x44\x00\x62\x60\x24\x7f\xd9\xd4\x80\x1b\x90\xc8\x60\x90\x00\xc4\x20\x42\xff" "\xa9\xdf\x86\x00\x6a\x80\x41\x01\xe0\x40\x02\xd4\x00\x98\x06\xe5\x92\xcb\x42\x09\x83" "\x46\x0c\x04\x3f\xf8\x2c\x06\xe4\xd2\x86\x01\xe0\x81\xa8\xbe\xfb\x7c\x40\x10\x3f\x00" "\xb2\x68\x68\x01\xa0\x09\x89\x81\x21\x00\x54\x07\x64\xd2\x62\x00\x91\x48\x02\xc4\xd4" "\x80\x88\x06\xf7\xd7\xc0\xa8\x06\x80\x31\x01\x00\xc0\x1b\x93\x0b\x0c\x25\xa1\x09\x40" "\xd4\x12\x50\x93\x6c\x80\x15\x02\x18\x04\x00\xdd\x47\x01\x00\x13\x24\x90\x1d\xa5\x24" "\xda\x80\x76\x49\x01\x55\xf4\x29\x60\x2c\x03\x40\x13\x93\x08\x45\x01\x52\xc9\x81\xa8" "\x26\x06\x80\xe0\xb1\x81\x09\x19\x40\x03\x60\x18\x80\x54\x00\x7e\x03\x62\xc0\x2c\x1a" "\x30\x0c\x8c\x08\x1b\x78\x83\x50\x04\xee\x8b\xdf\x4a\x52\x89\xdc\xa5\x29\x11\x72\x94" "\xa4\x45\xca\x52\x91\x17\x29\x4a\x44\x5c\xa5\x29\x11\x72\x94\xa4\x44\x00\x00\x01\x11" "\x2b\xfb\x98\xd9\xc6\xd6\x8d\x9a\xa1\x94\xdd\x43\x66\x1b\x59\x19\x45\xa8\xd8\xc6\x94" "\xce\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94" "\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88" "\xb9\x7c\xc0\x00\xb8\x00\xb4\x01\xb0\x02\x22\x80\xa8\xc2\x60\x06\x64\xd4\x81\x54\xa0" "\x6a\x52\x03\x64\x00\xe0\x90\x07\xc0\x7a\x5d\x47\x01\x00\x14\xf4\xfa\x5d\x20\x3b\x01" "\xd2\x00\x4e\x81\x89\x24\x50\x84\x8c\xbc\x00\x27\x49\x30\xb0\xd1\xa4\xc2\xc9\xa9\x1a" "\x49\x49\x34\x60\xd1\xa0\x22\x4a\x6f\x2a\x00\xb0\x00\x9d\x20\x0e\x80\x32\x21\x80\x5c" "\x4d\x26\x00\x56\x03\xa0\x2a\x4b\x1a\x13\xd0\x94\x14\x4d\x28\x61\x34\x94\x07\x80\x54" "\x03\xdb\xdc\xd2\x92\x68\x0e\x90\x4d\x40\xc4\x84\xcc\x68\xcb\xf9\x98\x08\x00\x08\x40" "\x2e\x00\x58\x4c\x26\xa0\x01\xb0\x06\x44\xd1\xa4\xd2\x4a\x40\x76\x58\xd2\x40\x0e\x09" "\x00\x48\x26\xf4\xa0\x0e\x80\x16\x93\x40\x40\x00\xcc\x98\x00\xf4\x0a\x8d\x01\xd9\x79" "\x25\xf4\xac\x25\x24\xb2\x92\x80\x01\x9a\xaf\x4f\x4b\x54\x00\x64\x4c\x40\x0d\xc2\x01" "\x0f\xfe\xc2\x12\x94\x92\x52\x02\x3a\x46\xa4\x02\x2b\xf9\xf0\x02\x10\x02\x14\x00\x37" "\x4a\x40\x2a\x01\x31\x44\xc1\xa4\xc0\x1b\x47\x01\x00\x15\x00\xdd\x20\x87\xff\x60\x36" "\x1a\x4b\x25\x01\x24\x04\x92\xc0\x56\x84\xdc\xc0\x0b\xc1\x03\xf2\x40\x0e\xc0\x4c\x18" "\x4c\x26\x00\x68\x51\x30\x9a\x50\x4a\x12\x03\x92\x80\xa2\x49\x08\x2c\x84\x80\x26\x4a" "\x4a\x49\x24\x84\xdf\x54\xa5\xa0\x00\x98\x07\x43\x09\x88\x18\x08\x60\x0a\x48\x09\x4c" "\x80\x6e\x9b\xf9\xb8\x01\x60\x01\x08\x14\x00\x58\x03\x00\x10\x8d\x02\x81\xa4\xd0\x13" "\x80\xec\xb1\xa0\x37\x25\x92\x42\x02\x10\x93\x49\x23\x40\x23\xbd\xf9\x34\x9a\x58\x0e" "\xc9\xa5\x93\x49\xa9\x1a\x58\x6a\x52\x94\xa4\x09\xa5\x36\x9a\x58\x20\x07\x44\xc2\x62" "\x49\xa4\xd4\x20\x24\x9a\x49\x01\x15\x43\x6e\x5e\x74\x01\x00\x03\xd2\x80\x19\x00\x3b" "\x01\xd0\x09\xc3\x50\x94\xa0\x98\x03\x70\x84\x12\x40\xb9\x31\x25\x00\x0c\xef\x45\x4b" "\x24\xa1\x04\xd4\x25\x09\x24\x24\x22\x47\x01\x00\x16\xa2\x45\xf8\x30\x02\x10\x02\xb0" "\x41\xfe\x24\x80\xeb\x06\x14\x92\x61\x08\x98\x80\x1c\x24\x11\x7f\xed\x20\x7c\x20\x07" "\x84\x80\x11\xde\x80\x01\x90\x0e\xc0\x4c\x02\x60\x13\x93\x00\x76\x03\xb0\x1b\x82\x10" "\x03\x93\x49\x84\xd4\x20\x98\x82\xb8\xd2\x51\x63\x42\x46\xa5\x17\x72\x96\xa4\xc0\x1d" "\x13\x09\x84\xc0\x1b\x20\x21\x14\x8d\x1a\x91\xb7\xf2\x30\x06\x20\x81\xfa\xe0\x0a\x00" "\x16\xa0\x98\x80\x13\x80\x64\x01\xa8\x05\x09\x48\xc4\x94\x59\x48\x48\x17\x24\xdd\x80" "\x1d\x00\x10\x81\x40\x06\x08\x26\x94\x00\xd8\x02\xe4\x13\x09\x63\x01\x0b\xfe\x40\xaa" "\x49\x64\x3e\x07\xc2\x40\xf0\x04\x4d\x7d\x12\x97\x4a\x00\x1d\x00\xe9\x08\x01\xb0\x48" "\xc0\x24\x35\x23\x28\x01\xba\x6f\x96\x05\x40\x35\x26\x80\x5e\x05\x40\x6e\x01\xa9\x34" "\xb2\x6f\x08\x1a\x90\x12\x8c\x01\x47\x01\x00\x17\xe0\x1e\xbe\x76\x01\x70\x01\x08\x03" "\xc0\x0b\x80\x1b\x16\x01\x71\x30\x84\x02\x10\x92\x89\xa8\x00\xac\x02\x84\x24\x69\x34" "\x68\x17\x24\x92\x50\x00\x32\xbe\xb1\x4b\x12\x68\x09\x89\x80\x3b\x42\x10\x92\x42\x02" "\x50\x84\x04\x12\x2a\x42\x52\x8b\xf2\x80\x09\x80\x17\x82\x08\x0e\x00\x5e\x00\x7e\x82" "\x80\x6e\x4c\x4e\x49\x31\x28\x49\x24\x06\xc9\x48\x00\xd4\x22\x40\x02\xf0\x07\x60\x1a" "\x00\x33\x01\x3a\x40\x42\x05\x0a\x02\xa4\xc0\xd4\x92\x89\x68\x49\x28\x94\x10\x81\x83" "\x50\x31\x22\x91\x7a\xea\x50\x00\x7c\x4c\x01\xb8\x01\xf8\x1f\x18\x30\x04\x53\x1a\x10" "\x32\xf8\x20\x09\x80\x1e\x82\x08\x0b\x80\x3d\x02\x80\x21\x18\x37\x13\x09\x63\x50\x03" "\xc2\xca\x49\x40\x03\x3b\xcc\x00\x29\x04\x0f\xd3\x2c\x01\xf9\x34\x10\x80\x28\x98\x00" "\x7c\x48\x41\x64\xd4\x84\x93\x47\x01\x00\x18\x43\x4a\x40\x48\x0d\x8b\x03\xc4\xa2\x58" "\xd0\x80\x12\x81\xfb\xe8\xb4\xa9\x20\x26\x26\x24\x9a\x82\x6a\x50\x12\x84\x0c\x90\xd4" "\x8c\x19\x7f\x31\x01\x08\x01\x38\x03\xa0\x05\x64\xc2\x60\x20\xff\x60\x06\x84\xde\x05" "\x49\x80\x89\xff\x60\x3a\x4a\x10\x10\x30\x07\xa0\x3d\x03\xd7\xa4\x00\x46\x00\x84\x07" "\x60\x08\xc9\xa8\x49\x35\x08\x26\x14\x18\x01\x31\x31\x07\x20\x69\x24\x24\x07\xb7\xaa" "\xa5\x44\xc0\x0c\x89\x84\xc4\xa1\x28\x41\x20\x68\xc0\x1e\x0d\x24\x8c\x84\xa5\x29\xbf" "\x30\x00\x88\x01\x39\x0c\x01\x10\x0e\xd2\x4d\x26\x14\x51\x35\x05\xa0\x62\x50\x93\x40" "\x46\x49\x45\xe6\x30\x01\x38\x03\xa0\x0b\x80\x76\x03\xb0\x0d\x40\x1d\x00\x64\x4d\x26" "\x00\xec\x0a\xa0\xb4\x00\x52\x94\x00\x54\x81\xa1\x23\x4a\x28\xa0\x1e\xa1\x01\x17\xd3" "\xa9\x22\x68\x06\x44\xc2\x47\x01\x00\x19\x6a\x46\xa5\x04\x94\x0c\x48\x49\x25\x00\x3d" "\x82\x5a\x6f\xe7\xa0\x05\x00\x04\x20\x82\x02\xe0\x54\x03\x52\x68\x09\xca\x48\x15\x00" "\x3b\x25\x25\x29\x40\xc1\xa4\xb4\x24\xb0\x01\xa9\x2d\x36\xb0\x41\xfd\xa0\x42\xfe\xf4" "\x00\x66\x80\x0c\x80\x74\x84\x14\x4d\x26\x81\x50\x1b\x04\x84\x93\x49\xa5\x0c\x1a\x5a" "\x40\xf8\xc4\x84\x94\x10\x11\x7b\x7a\x4c\x00\xd0\x07\x43\x52\x4d\x18\x12\x4d\x40\xc4" "\x80\xaa\x00\x6e\x9b\xf9\xd0\x02\x90\x40\xfd\x32\xc0\x1f\x93\x41\x08\x02\x89\x80\x07" "\xc4\x84\x16\x4d\x48\x49\x34\x34\xa4\x04\x80\xd8\xb0\x3c\x4a\x25\x8d\x08\x01\x28\x1f" "\xb3\x00\x84\x00\x9c\x01\xd0\x02\xb2\x61\x30\x10\x7f\xb0\x03\x42\x6f\x02\xa4\xc0\x44" "\xff\xb0\x1d\x25\x08\x08\x18\x03\xd0\x1e\x81\xeb\xe9\xd4\xac\x00\x4c\x03\xa4\x21\x03" "\x10\x30\x06\xc4\x84\x47\x01\x00\x1a\xa1\x29\x1a\x01\x1c\x80\x6e\x9b\xf1\x00\x0e\xc0" "\x18\x80\x6a\x08\x1f\xac\x00\xfc\x04\x04\xc5\x13\x38\x21\x80\x31\x0b\x0e\x01\xbf\x34" "\x4d\xf3\x00\x40\xfc\xa0\x13\x80\x80\x30\x00\xd8\x84\x01\xa9\x08\x9a\x43\x02\x84\x3e" "\x4d\x26\xb9\x33\xec\x4d\x72\x63\xb1\xe0\x8f\xff\x77\xdb\x40\x1c\x00\x66\x00\x22\x00" "\xd4\x84\x02\x70\x1b\x62\x68\x0d\xbb\x81\x8f\x73\x40\x12\x80\x2d\x04\x1f\xe4\x00\xd7" "\x01\x50\x1b\x00\x58\x01\x46\x26\x30\x8b\xb0\x86\x01\xa0\x21\x7f\xd1\x08\x11\x40\x18" "\x84\x01\x6d\x98\x05\xb7\xca\x01\x08\x01\x09\x28\x03\x00\x01\x20\x03\xa0\x1d\x00\x1c" "\x00\xc0\x94\x30\xb1\xa0\x5c\x9a\x58\xd0\x1c\x8d\xe0\x3d\x09\x01\x15\x58\x02\x20\x40" "\xfd\x70\x01\x20\x08\x09\xa0\x18\x00\x9c\x98\x90\x1d\x80\xe8\xb4\x16\x4a\x42\x09\x28" "\x25\x80\xe5\x00\x47\x01\x00\x1b\x8e\x00\x65\xdf\x44\xa5\xc4\x00\x64\x03\xa2\xca\x25" "\x12\x80\x4a\x4b\x00\x14\x92\xc0\x25\x25\x80\x4b\x68\x4b\x2e\xfc\x9a\x40\x1c\x80\x80" "\x04\x09\x26\x80\x09\x00\x4c\x92\x62\x52\x05\x8a\x21\x93\x49\xa4\xb2\x69\x45\x16\x4c" "\x49\x45\x92\x89\x44\xa4\x14\x58\x49\x24\x26\xf3\xe0\x05\xc0\x81\xfa\xe0\x26\x00\x44" "\x01\x91\x64\xc0\x03\xe2\x69\x34\x86\x5a\x52\x58\xd0\x82\x90\x48\x08\x28\x68\x0e\x46" "\x00\x0c\x6f\xa6\x52\x82\x68\x08\x09\x84\xd2\x89\x45\x81\x70\x48\xff\xd0\x09\x26\x4b" "\x2c\xab\xf1\xe0\x05\x40\x0a\x41\x04\x06\x40\x1f\xa4\x06\x24\xd2\x92\x4d\x01\xb9\x45" "\x94\x58\x13\x09\x2c\xa2\xc6\x84\x80\xf5\x16\x60\x05\xe0\x04\x24\x30\x0c\x40\x76\x4b" "\x01\x82\x30\x08\x0a\x01\xb9\x44\xd0\x82\x49\x35\x24\xc6\xc4\xc2\xc9\xa0\x8e\x00\x68" "\x41\x43\x0a\x47\x01\x00\x1c\x24\x5e\xe6\x92\x00\x38\x26\x00\xdc\x06\xe4\xa0\x2e\x4a" "\x25\x41\x2c\xb2\x51\x2a\xfe\x5c\x00\xcc\x10\x3f\x38\x07\x40\x0f\x4a\x01\xd0\x21\x00" "\x81\x49\x21\x93\x02\x46\x90\xc2\x50\x52\x40\xb1\x20\xa0\x12\x92\x00\x4b\x5c\x00\xe0" "\x10\x3f\x48\xa0\x0c\x00\x1b\x16\x01\x91\x34\x04\x00\x07\xe9\x49\x2c\x0b\x92\xc1\x0f" "\xfe\xc0\x6e\x94\x92\xc0\x6e\x07\xc9\x00\x5a\xfa\x5d\x2d\xc0\x04\x40\x3a\x01\xb0\x0d" "\x89\x40\x58\x96\x4b\x01\xc9\x74\x80\xdc\xbb\xf9\xa8\x02\xe0\x40\xfc\xd0\x10\x80\x60" "\x03\xb2\x6a\x40\x76\x01\x49\x30\x9a\x02\x42\x80\xb0\x0d\x82\x02\x00\x6c\x03\xc0\x3c" "\x4a\xba\xc5\x80\x35\x00\x74\x03\xa0\x1d\x80\x1f\x80\x3e\x00\xc9\x25\x16\x49\x28\x00" "\xfc\xb2\x59\x44\xc2\xc9\x63\x40\xb1\x48\x00\x80\x6a\x6f\x69\x4b\x20\x03\x82\x61\x45" "\x96\x59\x47\x01\x00\x1d\x45\x12\x89\x60\x39\x25\x00\x96\xa2\x59\x77\xf3\xb0\x02\x60" "\x40\xfd\x70\x41\x01\x90\x03\xf4\x00\xec\xa0\xd0\x1d\x94\x43\x2c\x06\xea\x24\x80\xdc" "\x0f\xa4\x24\x09\x9a\x02\xab\xa2\x42\x00\x31\x40\x0e\x80\x33\x01\xd0\x0e\xc9\x80\x18" "\x00\xe8\x98\x4c\x21\x24\xb2\xc0\x0f\x8a\x1a\x12\x94\x93\x09\x43\x4b\x4a\x49\x23\x09" "\x25\x01\x2b\xd9\xd2\x00\x0e\x09\x84\xb2\xcb\x25\x02\x1f\xfd\x80\xe6\x09\x65\xdf\xcc" "\xc0\x19\x82\x07\xe8\x00\x1c\x80\xec\xa2\xc0\x74\x03\xa4\x14\x52\x00\x6d\x89\xa3\x49" "\x20\x3b\x26\x14\x92\x58\x05\x85\x81\x22\x86\x8d\x25\xb1\x26\xd4\x00\x52\x08\x1f\xad" "\xc0\x13\x80\xe8\x07\x40\x3a\x41\x08\x06\xc5\x14\x30\x9a\x58\x23\xff\xd9\x2b\xa0\x20" "\x07\x81\x37\xd0\xa9\x51\x40\x18\x00\xe8\xa0\x1b\x12\x8a\x04\x30\x05\x01\xc4\x80\x6e" "\x5d\x47\x01\x00\x1e\xfc\xd0\x01\x78\x20\x7e\x7f\x00\x72\x00\xe1\x00\x19\x14\x03\xa4" "\x00\xe8\x68\x21\xff\xd8\x12\x18\x51\x63\x4a\x41\x32\x80\x83\xf9\xe0\x84\x03\x60\x02" "\x24\x00\xe8\xa4\x16\x01\x60\x01\xf1\x2c\x0f\x00\xd8\x94\x30\xb4\x82\x3f\xfe\xa6\xfa" "\x5d\x26\x00\x22\x01\xd1\x45\x14\x51\x28\x07\x05\x80\xdc\x04\xa0\x39\x80\x1b\x97\x72" "\x94\xa5\xe7\xda\xdc\xa5\x29\x11\x72\x94\xa4\x45\xca\x52\x91\x17\x29\x4a\x44\x5c\xa5" "\x29\x11\x72\x94\xa4\x44\x00\x00\x01\x12\x2b\xfb\xfd\x29\x49\x4a\xe5\x29\x48\x8b\x94" "\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88" "\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29" "\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4b\x20\x0e\x8a\x28\x9a\x4d\x42\x12\x92\xd0" "\x47\x01\x00\x1f\x37\x81\xe4\x0d\x27\x12\xd1\x79\xc0\x0d\x40\x14\x13\x00\x4c\x00\xf4" "\x00\x48\x00\x72\x01\x49\x2c\xa2\x89\x65\x93\x06\x10\x80\xb9\x45\x16\x03\x94\x16\x59" "\x60\x7c\x11\x40\x16\xd4\xb2\x69\x45\x16\x4a\x04\x8f\xfd\xa4\x95\x7e\xba\x96\x10\x10" "\x80\x80\x9a\x01\x90\x0e\xcb\x01\xd1\x0d\x03\x0b\x26\x24\x61\x76\xeb\x30\x1c\xce\xe5" "\x29\x49\x4e\xe5\x29\x48\x8b\x94\xbc\xa0\x03\x00\x06\x80\x3a\x00\xc4\x07\x40\x19\x80" "\xec\x9a\x4d\x26\x94\x00\x70\x4c\x25\x92\x8a\x28\xa0\x90\x24\x05\xd2\x58\x04\x60\x5a" "\xf5\x76\x85\x16\x51\x65\x96\x50\x04\x94\xdc\xa5\x29\x67\x55\xca\x52\xf2\x80\x0d\x00" "\x1a\x00\xe8\x03\x10\x18\x00\x66\x03\xb2\x69\x34\x9a\x50\x01\xc1\x30\x96\x4a\x28\xa2" "\x80\xb8\x12\x09\x49\x6c\x02\xbb\x42\xcb\x28\x98\x51\x2c\xba\x40\x73\x7e\xae\x94\x47" "\x40\x00\x12\x00\x00\xb0\x0d\x00\x01\xc1\x00\x00\x00\x01\xf0\x00\x2a\xb1\x04\xb2\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x47\x50" "\x00\x12\x00\x02\xb0\x12\x00\x01\xc1\x00\x00\xe1\x00\xf0\x00\x02\xe1\x00\xf0\x00\x9e" "\x8b\x23\xd1\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x47\x01\x00" "\x10\xa5\x9d\x57\x29\x66\x0d\x26\x06\x93\x38\x4a\x06\x3a\x06\x80\x89\x27\x5a\x62\x2e" "\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52" "\x22\xe5\x2f\x2e\x08\x20\x2a\x4c\x04\x10\x18\x48\x03\xf0\x1b\xa4\x10\xff\xed\x04\x22" "\x8a\x25\x30\xd2\xc0\xf8\x09\x52\x07\x80\x55\x6f\x00\x32\x40\x00\x91\x00\x21\x00\x3f" "\x26\x12\xc0\x6e\x03\x60\x03\xe0\x44\xff\xb0\x47\xff\xdb\x60\x1b\x94\x09\x5f\xfa\x4a" "\x04\x8f\xfd\xa4\x95\x7d\x9a\x52\x96\x35\x5c\xa5\x2c\x40\x20\x01\xd2\x40\x42\x03\xb2" "\x61\x09\x00\x89\xff\x65\xa0\xa9\xc5\xed\xa5\xe3\xc0\x40\x00\xa8\x07\x40\x18\x96\x03" "\x10\x1b\x00\x84\xb2\x50\x09\x90\x59\x65\x93\x40\x0f\x8a\x41\x43\x0a\x02\x40\x85\xff" "\x04\xb0\x90\x3c\x05\xab\x00\x30\x00\x64\x02\x60\x0b\xf0\x40\x06\x65\x47\x01\x00\x11" "\x94\xe4\x32\x10\x14\x18\x58\x0e\x80\xf9\x35\xf2\x00\x70\x43\x2c\x02\x4b\xa0\x00\xfc" "\xa0\x2e\x00\x28\x00\x14\x17\x41\x2a\xfd\x65\x2e\x38\x03\x90\x0c\x90\x00\xf0\x04\x24" "\x30\x1d\x00\xdc\x06\xe9\x26\x16\x30\xb4\x8c\x25\x04\x80\xf0\x25\x36\x40\x0c\x40\x1e" "\x13\x00\x32\x01\xd1\x08\x9a\x01\x59\x7d\x20\x3b\x28\x96\x51\x08\xb2\x11\x28\xb2\x51" "\x2f\xa0\x69\x60\x14\xa4\x61\x20\x20\x93\x66\x03\x72\x80\xb0\x00\xa4\x07\x13\x25\x5f" "\xa3\xa5\x29\x65\x55\xca\x52\x91\x17\x29\x4a\x44\x5c\xa5\x29\x11\x72\x94\xa4\x45\xca" "\x52\x91\x17\x29\x4a\x44\x5c\xa5\x29\x11\x72\x94\xa4\x44\x00\x00\x01\x13\x2b\xfb\xfd" "\x29\x49\x4a\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48" "\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x47\x01\x00\x12\x94" "\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88" "\xbf\x2e\x00\x50\x03\xae\x03\xb0\x06\x60\x19\x60\x45\x00\x40\xd0\x32\x09\x20\x0a\x02" "\x9b\x84\x1a\x01\x90\x68\x68\x02\xb0\xd0\x0c\xc0\x76\x08\x40\x12\x03\x00\x03\xd0\x33" "\xc9\xa1\xa6\xac\x11\x00\x10\x11\x7f\xf2\xde\x00\x54\x4d\x01\xd0\x06\x40\x07\xa0\x31" "\x26\x93\x4a\x0c\x0d\x00\x3c\x26\x94\x03\x07\x04\x5f\xfb\xbd\x88\x6d\xe8\x80\x4e\x03" "\x10\xd0\x47\x00\x40\x31\x63\x7f\x2f\x00\xb4\x04\x00\x08\xc0\x35\x00\x76\x01\xa9\x0c" "\x84\x42\x00\xb0\x98\x42\x04\x3f\xfb\x26\x80\x56\x09\x80\x0c\x43\xbb\x60\x26\x21\x00" "\x80\x01\xdb\x01\x82\x60\x05\x84\x20\x0d\x08\x40\x37\x01\x09\x0d\xc1\x0b\xfe\x89\xb8" "\x50\x23\x7f\xd8\xe6\xb4\x06\x00\x9c\x84\x05\x00\x11\x10\xc8\x47\x01\x00\x13\x40\x67" "\x80\x5a\x02\x10\x10\xa1\xc1\x1c\x01\x49\x9b\x11\x6c\x80\x05\x00\x0f\x48\x44\x20\x28" "\x00\xf4\x04\x04\xd0\x2a\x00\x74\x00\x72\x42\x26\x00\xd8\x9a\xc7\x13\x48\x5d\x4b\x16" "\xf7\x40\x05\x80\x16\x02\x60\x03\x51\x76\x80\x1c\x13\x00\x04\x44\x32\x18\x06\xa4\xc0" "\x03\xa7\x04\x30\x0c\x3c\x06\xff\x87\xac\x02\xca\x92\x18\x09\x80\x40\x01\x91\x30\x04" "\x00\x54\x9a\x42\x00\x3b\x01\x00\x15\x26\x02\x1f\xfd\xe5\x13\x40\x2d\x1c\x79\x3a\xee" "\x02\x10\x05\x80\x5a\x4c\x01\xb9\x0f\x93\x40\x29\x04\x2f\xfa\x26\xe1\xc0\x8c\x00\xb7" "\x0b\x00\x6a\x02\x00\x0c\x00\x40\x00\xec\x03\x40\x0b\x78\x08\x49\x84\xde\x08\x40\x0c" "\x24\x11\x7f\xea\xca\xab\xd8\x03\x00\x0c\x83\x00\x04\x40\x06\xa4\xc2\x60\x01\xe8\x18" "\x0d\x0d\x00\x37\x0c\x2c\x11\x00\x10\x30\x11\x00\x10\x2c\x47\x01\x00\x14\x11\x7f\xf2" "\xf4\x40\x19\xf0\x1d\x16\x03\xb0\xd0\x03\xc0\xc0\xd0\xc0\xcf\xc0\x50\x51\x64\x9d\x78" "\xa0\x0c\x80\x33\x00\x78\x00\xc8\x06\x24\xc0\x18\x80\xc0\x07\x7c\x9a\x03\xa2\x68\x0c" "\x01\x0b\xfe\x00\x60\x1a\x01\x38\x19\x03\x00\x86\x00\xa1\x60\x89\xff\x9a\xfa\x11\x67" "\xd8\x06\x00\x4e\x1b\x55\xfc\xf8\x04\x04\x20\x0b\x00\x0a\x08\x64\x32\x68\x22\x7f\xa1" "\x0c\xd3\xc0\x2c\x21\x02\x28\x02\x82\x47\xfd\x5a\x52\x01\x91\x08\x04\x20\x09\x00\x0e" "\x49\x84\x22\x61\x08\x86\x00\x72\x01\x60\x05\x00\x16\x80\x52\xf8\x11\xbf\xec\x87\xea" "\x40\x33\x02\xa0\x20\x00\xcc\x01\x80\x03\xc0\x10\x13\x09\xa0\x36\xe0\x20\x7e\x43\x01" "\xb8\xf2\x19\xb0\x21\x00\x2f\x00\xd0\x98\x02\x10\x07\x84\xc5\xf0\x0d\x00\x32\x0a\x21" "\x90\xc1\x17\xfe\xb9\x32\xec\x04\x2f\xfa\x04\xaf\xfa\x47\x01\x00\x15\xb3\xbd\xa0\x0f" "\x40\x34\x21\x00\x32\x02\xa8\x00\xc8\x30\x30\x00\xec\x04\x2a\xc0\x57\xb1\xc4\xd0\x45" "\x00\x52\x66\xc7\x91\xee\x70\x02\xf0\x05\xc4\xce\x00\xc4\x98\x00\xf0\x04\x00\x60\x98" "\x08\x40\x0d\xb1\x0f\x88\xc0\x37\x26\x93\x42\x8e\x11\x6c\x02\x84\xd0\x10\x00\x80\x01" "\x79\x30\x87\x80\x35\x00\xb5\x89\xaf\xc8\x60\x8a\x00\xc0\x93\xff\x40\x87\xff\x57\x3c" "\x84\x00\xc4\x04\x00\x50\x01\x29\x0c\x0a\x80\x5a\x01\x69\x08\x0a\x80\x5a\x03\x72\x18" "\x05\x80\x8b\xff\x53\x04\x2f\xfa\x04\xa0\x06\x04\xaf\xfa\x8b\xef\x10\x80\x42\x00\xf0" "\x98\x4d\x21\x86\x00\x1c\x00\x5a\x05\x0e\x26\x75\x80\xdd\xc1\x17\xff\x01\x0f\xfe\xef" "\x32\x00\xd0\x01\xf1\x0c\x0a\x80\x15\x10\xfe\x04\x4f\xf4\x01\x0e\xc4\xd0\x47\x00\x70" "\x29\x80\x6e\x01\x68\x24\xff\xd0\x21\x00\x35\xed\x47\x01\x00\x16\x80\x35\x21\x00\x68" "\x02\x6e\x08\x40\x0c\x43\xc0\x21\x23\x5e\x23\x00\x23\x02\x80\x0f\x01\x04\x06\x08\x44" "\x20\x0a\x48\x44\x22\x10\x21\x7f\xa8\xb0\x0b\x41\x0f\xfe\xb5\x04\x30\x46\x00\x60\x51" "\x00\x69\xdf\xb2\x01\x38\x09\xc0\x1d\x72\x60\x05\x80\x20\x00\xd3\xe0\x46\xff\xa2\x19" "\x37\xf2\x19\x38\x02\x88\x80\x26\x21\x00\x1b\x80\x3b\x02\x84\x20\x45\xff\xb2\x6e\xa0" "\x03\x92\x60\x03\xe0\x0d\x09\xa3\x00\x40\x05\x78\x08\x70\x22\xff\xdf\x51\x3a\xe6\x02" "\x00\x14\x80\x6a\x42\x00\xd0\x03\x52\x69\x0c\x86\x43\x00\xb0\x10\x80\x18\x9a\x03\x6e" "\x08\xdf\xf6\xd9\xae\x60\x44\x00\x6a\xef\x9b\xf7\x04\x0f\xd7\x00\x5a\x43\x04\x30\x05" "\xc4\xc5\x93\x79\x35\x99\x88\x44\x2a\xc0\x05\xc0\x1a\x00\x80\x01\x38\x06\x21\xa0\x20" "\x26\x10\xb1\x30\x99\x88\x44\xde\x03\x7e\xb2\x47\x01\x00\x17\x6a\x81\x13\xfe\xf9\x17" "\x56\x80\x30\x00\x7a\x01\xa0\x06\x04\x30\x0d\x40\xa7\x51\x30\x03\x50\x10\x10\xc0\x34" "\x00\x3b\x6e\xa7\x04\x4f\xfa\x01\xb5\x60\x06\x80\x19\x80\x80\x01\x80\x06\x40\x21\xc0" "\x87\xff\x60\x21\x01\x08\x21\x00\x30\x23\x80\x31\x30\x83\xee\xa0\x42\xff\xa0\x4b\x00" "\x60\x0b\x2c\xef\xdc\x5e\x10\x30\x84\x01\x91\x08\x01\x31\x40\x1a\x90\xc0\x60\x4c\x25" "\xa4\x04\xe0\x60\xa0\x43\xff\xa2\x59\x0c\xa2\x8a\x58\x49\x2c\xb2\x8a\x58\x0e\x41\x28" "\x00\xef\x79\x79\x10\x04\xe0\x19\x90\x88\x60\x50\x01\xd0\x0c\x08\x45\x92\xca\x01\x80" "\x0c\x00\xb8\x23\xff\xd1\x60\x39\xb8\x88\x64\xb2\xc9\x45\x00\x0a\x40\xbd\xa9\x57\x87" "\x00\x68\x42\x21\x93\x08\x60\x0f\xc9\x80\x26\x6c\x05\x40\xae\x21\x00\x80\x04\x0b\xe7" "\x60\x0a\x42\xa2\x02\x10\x0d\x00\x32\x21\x47\x01\x00\x18\x00\x08\x89\x84\x30\x42\x00" "\x62\x19\x34\x10\xff\xd4\x06\xcc\x01\x41\x31\xc7\x02\x2f\xfd\x35\x98\x84\x01\x99\x08" "\x04\x20\x08\x88\x40\x20\x00\x37\x00\x38\x21\x80\x68\x43\x21\x82\x2f\xfd\x02\x17\xfd" "\x13\x08\x60\x8e\x00\xaf\x77\x00\x34\x0c\x00\x78\x05\x40\xaf\x0c\x21\x93\x49\xa4\x2d" "\xc9\x80\x84\x00\xc4\xd3\x00\x2d\x3c\x06\xe4\xd3\x58\x45\xd6\x09\x3f\xf4\x08\x40\x0d" "\x67\x79\x40\x1e\x81\x40\xc0\x07\x9c\x86\x02\x00\x10\x80\x69\xc8\x44\x37\xc0\x36\x49" "\x33\x9c\x4c\xe3\xf8\x22\x7f\xd8\x9f\x00\x06\x24\x20\x10\x01\x40\x10\x00\x3a\xd8\x84" "\x01\x61\x0c\x86\x42\xe4\x32\x1b\x39\xc0\x88\x00\xa0\x13\x8a\x3c\x54\xa0\x0f\x40\x1e" "\x93\x00\xa8\x06\x40\x0f\x00\x4c\x4d\x04\x2f\xfa\x26\x80\xe8\x11\x3f\xe8\x11\x80\x1b" "\x82\x20\x03\x5c\xd0\x06\x20\x1a\x80\x47\x01\x00\x19\x3e\x21\x80\x3b\xe0\x21\xc0\x1a" "\xf2\x18\x21\x80\x28\x0d\x81\x08\x01\x81\x0c\x01\x59\x6b\x11\x00\x85\xff\x40\x84\x00" "\xc0\x91\xff\x51\x7d\x10\x02\xb0\x0c\x09\x80\x0f\x00\x0d\x88\x40\x36\x62\x18\x05\x98" "\x9a\x43\x0f\xb0\x80\x66\x01\xa0\x06\x80\x3a\x00\x64\x00\xf0\x86\x03\x6c\x4c\x21\x90" "\xdc\x9a\xf8\xef\x81\x10\x01\x99\xc9\xa0\x93\xff\x6a\xb6\x80\x13\x80\x6a\x02\x60\x07" "\xa0\x0f\x79\x34\x84\x43\xe4\xdc\xeb\x26\x82\x30\x02\x62\x2e\x98\x01\x28\x08\x40\x40" "\x01\xa8\x03\xd2\x60\x05\x81\xbf\x80\x80\x99\x88\x44\xcd\xc7\x6c\x7f\x99\x08\x02\xd0" "\x42\x00\x60\x49\xff\xa2\x1c\x5f\x3c\x01\x88\x06\x40\x1a\x00\x68\x42\x00\xd0\x86\x01" "\x50\x05\x80\x16\x80\x68\x08\x60\x0c\x03\x70\x44\xff\xa0\x4b\xff\xb8\x80\x31\x00\x60" "\x42\x26\x80\x6a\x00\xe8\x04\x00\x47\x01\x00\x1a\x1a\x10\x80\x2c\x21\x90\xc8\x44\xcc" "\x43\x04\x50\x05\x00\xb7\xda\xc0\x1c\x80\x3d\x02\x80\x0f\x40\x12\x80\xdf\xb8\x01\xc1" "\x31\x89\x98\x11\xc0\x1c\x85\x71\xc1\x07\xfa\x40\x34\x00\xd0\x00\xa4\x00\xd4\x06\xe0" "\x16\x90\x80\x29\xb4\x04\x2f\xfa\x04\xa0\x06\x21\xd3\x7b\x00\x60\x01\x70\x06\x40\x16" "\x90\xcb\x2c\x84\x4a\x00\x70\x42\x25\x94\x58\x0c\x52\x4b\x00\xb4\x86\x05\x80\xb1\x28" "\x00\x52\x4b\x18\x58\x16\x28\x95\x7d\x12\xf3\x00\x17\xe0\x1d\x13\x0b\x2c\x10\xbf\xd0" "\x00\x4a\x58\x17\x25\x00\xe4\x04\xa0\x25\x2c\xb2\xc0\x72\x58\x00\xa0\x02\x5b\xd1\xa0" "\x92\x13\x79\xe4\x20\x09\x40\xbd\xb1\x77\xe1\x40\x1d\x90\xf0\x08\x00\x18\x00\x30\x00" "\xd3\x63\xc1\x0b\xfd\x03\x70\x9e\x40\xb5\x00\x2f\x02\x80\x20\x01\x30\x08\x00\x1d\x00" "\x58\x42\x01\xb1\x0c\x9a\x42\x47\x01\x00\x1b\xe4\x32\x18\x82\x6a\xc9\x84\xeb\x28\x01" "\x58\x09\xc0\x1e\x00\x6a\x01\xa9\x0b\x90\x88\x60\x37\x21\x6c\x4c\x26\x60\x46\x00\x52" "\x16\x1d\x58\x00\x4e\x02\x10\x10\x00\x6a\x00\xf4\x02\xd2\x11\x34\x02\xd5\xb0\x23\x00" "\x28\x0d\xae\xc2\x10\x22\x00\x30\x21\xff\xd0\x22\x7f\xd1\x0e\xca\xed\x00\x4e\x42\x02" "\x84\x22\x10\x03\xc2\x60\x61\x08\x98\x42\x26\x80\x54\x03\x72\x1b\x00\xdc\xfe\x26\xe7" "\x90\xc0\x18\x00\x80\x02\xb7\x7e\x03\x60\x0d\x00\x34\x21\x70\x0d\x41\x1f\xfe\x41\x14" "\x01\x6b\x40\x0a\x40\x35\x01\x00\x15\x00\x7a\x00\x72\x05\x08\x60\x37\x04\x30\x05\x01" "\xb1\x30\x11\x40\x15\x96\xd1\x00\x44\x00\xf4\x99\x80\x34\x00\x7a\x02\x02\x18\x08\x40" "\x0e\x03\x40\x35\x01\xb3\x10\xf8\xed\xcd\x6a\x81\x0b\xfe\x80\x2c\x21\x45\xe9\x21\x80" "\x80\x84\x4c\x00\x70\x4d\x47\x01\x00\x1c\x0d\x01\x08\x14\x0c\x02\x80\x54\xb2\xc3\x00" "\x0e\x03\x03\x00\x72\xce\x02\x92\x58\x0e\x40\xb1\x28\x02\x53\x6f\x67\x93\xd1\x73\x00" "\x4e\x01\x50\x67\x03\x01\x81\xa0\x15\xaf\x02\x17\xfd\x31\x28\xe0\x12\x15\x81\x33\xff" "\x6e\xad\xe6\x86\x5b\x5f\xcf\xc0\x1b\x38\x08\x00\x0a\x80\x18\x81\x50\x45\xff\x90\xd0" "\xc0\x1d\x06\x02\x67\xfe\x02\x50\x01\xde\xb4\x01\xd0\x15\x00\x3c\x00\xd4\x07\x41\xa0" "\x8a\x00\x80\x54\x98\x1a\x09\x20\x06\x5d\xe5\x40\x2f\x0d\x02\x85\x00\x28\x00\xd0\x04" "\x00\x50\x03\x50\x28\x4d\x2c\x0c\x96\x05\x40\x76\x18\x00\x29\x12\x02\x5b\xdd\xa3\x8c" "\xf7\x48\x0e\x80\x52\x00\x7e\x08\xdf\xf8\x1a\x1a\x09\xa0\x09\x40\x68\x22\x00\x25\xf8" "\x00\x05\xa0\x17\x10\x81\x07\xf7\x80\x33\x00\xb4\xb0\x42\x00\x62\x81\x0b\xfd\x89\xa0" "\x8e\x00\x65\x94\x51\x47\x01\x00\x1d\x64\xa0\x49\xff\xd2\xc9\x57\xb0\x00\x7e\x01\xa9" "\x60\x17\x80\xc4\xb0\x2a\x58\x09\x4c\x02\x40\x3c\xbc\x70\x01\x08\x03\x30\x18\x00\x6a" "\x58\xc0\x13\x0c\x2c\x02\xd2\x88\x45\x80\xc4\x86\x02\x52\xcb\x0c\x28\x94\x4a\x04\x70" "\x03\x04\xcf\xfd\xbe\x89\x20\x0c\x89\x60\x16\x14\x59\x60\x12\x12\xa6\x5d\xca\x52\x97" "\x05\xad\xca\x52\x91\x17\x29\x4a\x44\x5c\xa5\x29\x11\x72\x94\xa4\x45\xca\x52\x91\x17" "\x29\x4a\x44\x5c\xa5\x29\x11\x72\x94\xa4\x44\x00\x00\x01\x14\x2b\xfb\xfd\x29\x49\x4a" "\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5" "\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9" "\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48" "\x8b\x94\xa5\x22\x47\x01\x00\x1e\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94" "\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88" "\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29" "\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e" "\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52" "\x22\x00\x00\x01\x15\x2b\xfb\xfd\x29\x49\x4a\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94" "\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5" "\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22" "\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a" "\x52\x22\xe5\x47\x01\x00\x1f\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22" "\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5" "\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9" "\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48" "\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\x00\x00\x01\x16\x2b\xfb\xfd\x29\x49" "\x4a\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94" "\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88" "\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29" "\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e" "\x52\x94\x47\x01\x00\x10\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94" "\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5" "\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22" "\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a" "\x52\x22\x00\x00\x01\x17\x2b\xfb\xfd\x29\x49\x4a\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52" "\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22" "\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5" "\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9" "\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48" "\x8b\x47\x01\x00\x11\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94" "\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88" "\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29" "\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\x00\x00\x01\x18\x2b\xfb\xfd\x29" "\x49\x4a\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b" "\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94" "\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5" "\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22" "\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a" "\x47\x01\x00\x12\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22" "\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5" "\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9" "\x4a\x52\x22\x00\x00\x01\x19\x2b\xfb\xfd\x29\x49\x4a\xe5\x29\x48\x8b\x94\xa5\x22\x2e" "\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52" "\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94" "\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88" "\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29" "\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x47" "\x01\x00\x13\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94" "\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5" "\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\x00\x00\x01\x1a\x2b\xfb\xfd" "\x29\x49\x4a\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48" "\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52" "\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22" "\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5" "\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9" "\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x47\x01" "\x00\x14\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94" "\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88" "\xb9\x4a\x52\x22\x00\x00\x01\x1b\x2b\xfb\xfd\x29\x49\x4a\xe5\x29\x48\x8b\x94\xa5\x22" "\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a" "\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b" "\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94" "\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5" "\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22" "\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x47\x01\x00" "\x15\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22" "\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\x00\x00\x01\x1c\x2b\xfb" "\xfd\x29\x49\x4a\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29" "\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e" "\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52" "\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94" "\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88" "\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29" "\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x47\x01\x00\x16" "\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94" "\x88\xb9\x4a\x52\x22\x00\x00\x01\x1d\x2b\xfb\xfd\x29\x49\x4a\xe5\x29\x48\x8b\x94\xa5" "\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9" "\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48" "\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52" "\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22" "\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5" "\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9" "\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x47\x01\x00\x17\x52" "\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\x00\x00\x01\x1e\x2b" "\xfb\xfd\x29\x49\x4a\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5" "\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22" "\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a" "\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b" "\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94" "\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5" "\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22" "\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x47\x01\x00\x18\x2e\x52" "\x94\x88\xb9\x4a\x52\x22\x00\x00\x01\x1f\x2b\xfb\xfd\x29\x49\x4a\xe5\x29\x48\x8b\x94" "\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88" "\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29" "\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e" "\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52" "\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94" "\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88" "\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29" "\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\x00\x47\x01\x00\x19\x00\x01\x20" "\x2b\xfb\xfd\x29\x49\x4a\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22" "\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5" "\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9" "\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48" "\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52" "\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22" "\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5" "\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9" "\x4a\x52\x22\x00\x00\x01\x21\x2b\xfb\xfd\x29\x49\x4a\x47\x01\x00\x1a\xe5\x29\x48\x8b" "\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94" "\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5" "\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22" "\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a" "\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b" "\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94" "\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5" "\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\x00\x00\x01\x22\x2b\xfb\xfd" "\x29\x49\x4a\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x47\x01\x00\x1b\x94\x88\xb9\x4a\x52" "\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94" "\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88" "\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29" "\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e" "\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52" "\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94" "\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88" "\xb9\x4a\x52\x22\x00\x00\x01\x23\x2b\xfb\xfd\x29\x49\x4a\xe5\x29\x48\x8b\x94\xa5\x22" "\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x47\x01\x00\x1c\x8b\x94\xa5\x22\x2e\x52" "\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22" "\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5" "\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9" "\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48" "\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52" "\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22" "\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\x00\x00\x01\x24\x2b\xfb" "\xfd\x29\x49\x4a\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29" "\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x47\x01\x00\x3d\x2d\x00\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x4a\x52\x22" "\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5" "\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9" "\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48" "\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52" "\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22" "\xe5\x29\x48\x8b\x94\xa5\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22\xe5\x29\x48\x8b\x94\xa5" "\x22\x2e\x52\x94\x88\xb9\x4a\x52\x22"; vdr-plugin-streamdev/server/livestreamer.c0000644000175000017500000005652413276341255020661 0ustar tobiastobias#include #include #include #include "remux/ts2ps.h" #include "remux/ts2pes.h" #include "remux/ts2es.h" #include "remux/extern.h" #include #include "server/livestreamer.h" #include "server/setup.h" #include "common.h" using namespace Streamdev; // device occupied timeout to prevent VDR main loop to immediately switch back // when streamdev switched the live TV channel. // Note that there is still a gap between the GetDevice() and SetOccupied() // calls where the VDR main loop could strike #define STREAMDEVTUNETIMEOUT 5 // --- cStreamdevLiveReceiver ------------------------------------------------- class cStreamdevLiveReceiver: public cReceiver { friend class cStreamdevStreamer; private: cStreamdevLiveStreamer *m_Streamer; protected: #if APIVERSNUM >= 20300 virtual void Receive(const uchar *Data, int Length); #else virtual void Receive(uchar *Data, int Length); #endif public: cStreamdevLiveReceiver(cStreamdevLiveStreamer *Streamer, const cChannel *Channel, int Priority, const int *Pids); virtual ~cStreamdevLiveReceiver(); }; cStreamdevLiveReceiver::cStreamdevLiveReceiver(cStreamdevLiveStreamer *Streamer, const cChannel *Channel, int Priority, const int *Pids): cReceiver(Channel, Priority), m_Streamer(Streamer) { // clears all PIDs but channel remains set SetPids(NULL); AddPids(Pids); } cStreamdevLiveReceiver::~cStreamdevLiveReceiver() { Dprintf("Killing live receiver\n"); Detach(); } #if APIVERSNUM >= 20300 void cStreamdevLiveReceiver::Receive(const uchar *Data, int Length) { #else void cStreamdevLiveReceiver::Receive(uchar *Data, int Length) { #endif m_Streamer->Receive(Data, Length); } // --- 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: case SI::EnhancedAC3DescriptorTag: 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()) { #if APIVERSNUM >= 20300 LOCK_CHANNELS_READ; const cChannel *Channel = Channels->GetByServiceID(Source(), Transponder(), assoc.getServiceId()); #else const cChannel *Channel = Channels.GetByServiceID(Source(), Transponder(), assoc.getServiceId()); #endif 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->SetPid(pmtPid, true); 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 #endif pids[npids++] = 0x11; // pid 0x11, tid 0x42: SDT pids[npids++] = 0x14; // pid 0x14, tid 0x70: TDT 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(const cServerConnection *Connection, const cChannel *Channel, int Priority, eStreamType StreamType, const int* Apid, const int *Dpid) : cStreamdevStreamer("streamdev-livestreaming", Connection), m_Priority(Priority), m_NumPids(0), m_Channel(Channel), m_Device(NULL), m_Receiver(NULL), m_PatFilter(NULL), m_SwitchLive(false) { m_ReceiveBuffer = new cStreamdevBuffer(LIVEBUFSIZE, TS_SIZE *2, true, "streamdev-livestreamer"), m_ReceiveBuffer->SetTimeouts(0, 100); if (Priority == IDLEPRIORITY) { SetChannel(StreamType, Apid, Dpid); } else { m_Device = SwitchDevice(Channel, Priority); if (m_Device) SetChannel(StreamType, Apid, Dpid); memcpy(m_Caids,Channel->Caids(),sizeof(m_Caids)); } } cStreamdevLiveStreamer::~cStreamdevLiveStreamer() { Dprintf("Desctructing Live streamer\n"); Stop(); DELETENULL(m_PatFilter); DELETENULL(m_Receiver); delete m_ReceiveBuffer; } 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::SetPriority(int Priority) { m_Priority = Priority; #if VDRVERSNUM >= 20104 cThreadLock ThreadLock(m_Device); if (m_Receiver) m_Receiver->SetPriority(Priority); else #endif StartReceiver(); } void cStreamdevLiveStreamer::GetSignal(int *DevNum, int *Strength, int *Quality) const { if (m_Device) { *DevNum = m_Device->DeviceNumber() + 1; *Strength = m_Device->SignalStrength(); *Quality = m_Device->SignalQuality(); } } cString cStreamdevLiveStreamer::ToText() const { if (m_Device && m_Channel) { return cString::sprintf("DVB%-2d %3d %s", m_Device->DeviceNumber() + 1, m_Channel->Number(), m_Channel->Name()); } return cString(""); } bool cStreamdevLiveStreamer::IsReceiving(void) const { cThreadLock ThreadLock(m_Device); return m_Receiver && m_Receiver->IsAttached(); } void cStreamdevLiveStreamer::StartReceiver(bool Force) { if (m_NumPids > 0 || Force) { Dprintf("Creating Receiver to respect changed pids\n"); cReceiver *current = m_Receiver; cThreadLock ThreadLock(m_Device); m_Receiver = new cStreamdevLiveReceiver(this, m_Channel, m_Priority, m_Pids); if (IsRunning()) Attach(); delete current; } else DELETENULL(m_Receiver); } bool cStreamdevLiveStreamer::SetChannel(eStreamType StreamType, const int* Apid, const int *Dpid) { Dprintf("Initializing Remuxer for full channel transfer\n"); //printf("ca pid: %d\n", Channel->Ca()); const int *Apids = Apid ? Apid : m_Channel->Apids(); const int *Dpids = Dpid ? Dpid : m_Channel->Dpids(); switch (StreamType) { case stES: { int pid = ISRADIO(m_Channel) ? m_Channel->Apid(0) : m_Channel->Vpid(); if (Apid && Apid[0]) pid = Apid[0]; else if (Dpid && Dpid[0]) pid = Dpid[0]; SetRemux(new cTS2ESRemux(pid)); return SetPids(pid); } case stPES: SetRemux(new cTS2PESRemux(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids())); return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids()); #ifdef STREAMDEV_PS case stPS: SetRemux(new cTS2PSRemux(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids())); return SetPids(m_Channel->Vpid(), Apids, Dpids, m_Channel->Spids()); #endif case stEXT: SetRemux(new cExternRemux(Connection(), m_Channel, Apids, Dpids)); // 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"); // No PIDs requested yet. Start receiver anyway to occupy device StartReceiver(true); return true; default: return false; } } #if APIVERSNUM >= 20300 void cStreamdevLiveStreamer::Receive(const uchar *Data, int Length) #else void cStreamdevLiveStreamer::Receive(uchar *Data, int Length) #endif { int p = m_ReceiveBuffer->PutTS(Data, Length); if (p != Length) m_ReceiveBuffer->ReportOverflow(Length - p); } void cStreamdevLiveStreamer::Action(void) { if (StreamdevServerSetup.LiveBufferMs) { // wait for first data block int count = 0; while (Running()) { if (m_ReceiveBuffer->Get(count) != NULL) { cCondWait::SleepMs(StreamdevServerSetup.LiveBufferMs); break; } } } cStreamdevStreamer::Action(); } int cStreamdevLiveStreamer::Put(const uchar *Data, int Count) { // insert si data if (m_PatFilter) { int siCount; uchar *siData = m_PatFilter->Get(siCount); if (siData) { siCount = cStreamdevStreamer::Put(siData, siCount); if (siCount) m_PatFilter->Del(siCount); } } return cStreamdevStreamer::Put(Data, Count); } void cStreamdevLiveStreamer::Attach(void) { Dprintf("cStreamdevLiveStreamer::Attach()\n"); if (m_Device) { if (m_Receiver) { if (m_Receiver->IsAttached()) 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); } } bool cStreamdevLiveStreamer::UsedByLiveTV(cDevice *device) { return device == cTransferControl::ReceiverDevice() || (device->IsPrimaryDevice() && device->HasDecoder() && !device->Replaying()); } cDevice *cStreamdevLiveStreamer::SwitchDevice(const cChannel *Channel, int Priority) { cDevice *device = cDevice::GetDevice(Channel, Priority, false); if (!device) { dsyslog("streamdev: GetDevice failed for channel %d (%s) at priority %d (PrimaryDevice=%d, ActualDevice=%d)", Channel->Number(), Channel->Name(), Priority, cDevice::PrimaryDevice()->CardIndex(), cDevice::ActualDevice()->CardIndex()); } else if (!device->IsTunedToTransponder(Channel) && UsedByLiveTV(device)) { // make sure VDR main loop doesn't switch back device->SetOccupied(STREAMDEVTUNETIMEOUT); if (device->SwitchChannel(Channel, false)) { // switched away live TV m_SwitchLive = true; } else { dsyslog("streamdev: SwitchChannel (live) failed for channel %d (%s) at priority %d (PrimaryDevice=%d, ActualDevice=%d, device=%d)", Channel->Number(), Channel->Name(), Priority, cDevice::PrimaryDevice()->CardIndex(), cDevice::ActualDevice()->CardIndex(), device->CardIndex()); device->SetOccupied(0); device = NULL; } } else if (!device->SwitchChannel(Channel, false)) { dsyslog("streamdev: SwitchChannel failed for channel %d (%s) at priority %d (PrimaryDevice=%d, ActualDevice=%d, device=%d)", Channel->Number(), Channel->Name(), Priority, cDevice::PrimaryDevice()->CardIndex(), cDevice::ActualDevice()->CardIndex(), device->CardIndex()); device = NULL; } return device; } bool cStreamdevLiveStreamer::ProvidesChannel(const cChannel *Channel, int Priority) { cDevice *device = cDevice::GetDevice(Channel, Priority, false, true); if (!device) dsyslog("streamdev: No device provides channel %d (%s) at priority %d", Channel->Number(), Channel->Name(), Priority); return device; } void cStreamdevLiveStreamer::ChannelChange(const cChannel *Channel) { if (Running() && m_Device && m_Channel == Channel) { // Check whether the Caids actually changed // If not, no need to re-tune, probably just an Audio PID update if (!memcmp(m_Caids, Channel->Caids(), sizeof(m_Caids))) { dsyslog("streamdev: channel %d (%s) changed, but caids remained the same, not re-tuning", Channel->Number(), Channel->Name()); } else { Detach(); if (m_Device->SwitchChannel(m_Channel, false)) { Attach(); dsyslog("streamdev: channel %d (%s) changed, re-tuned", Channel->Number(), Channel->Name()); memcpy(m_Caids, Channel->Caids(), sizeof(m_Caids)); } else isyslog("streamdev: failed to re-tune after channel %d (%s) changed", Channel->Number(), Channel->Name()); } } } void cStreamdevLiveStreamer::MainThreadHook() { if (!m_SwitchLive && Running() && m_Device && !m_Device->IsTunedToTransponder(m_Channel) && !IsReceiving()) { cDevice *dev = SwitchDevice(m_Channel, m_Priority); if (dev) { dsyslog("streamdev: Lost channel %d (%s) on device %d. Continuing on device %d.", m_Channel->Number(), m_Channel->Name(), m_Device->CardIndex(), dev->CardIndex()); m_Device = dev; StartReceiver(); } else { isyslog("streamdev: Lost channel %d (%s) on device %d.", m_Channel->Number(), m_Channel->Name(), m_Device->CardIndex()); Stop(); } } if (m_SwitchLive) { // switched away live TV. Try previous channel on other device first #if APIVERSNUM >= 20300 LOCK_CHANNELS_READ; if (!Channels->SwitchTo(cDevice::CurrentChannel())) { // switch to streamdev channel otherwise Channels->SwitchTo(m_Channel->Number()); #else if (!Channels.SwitchTo(cDevice::CurrentChannel())) { // switch to streamdev channel otherwise Channels.SwitchTo(m_Channel->Number()); #endif Skins.Message(mtInfo, tr("Streaming active")); } if (m_Device) m_Device->SetOccupied(0); m_SwitchLive = false; } } 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; } vdr-plugin-streamdev/server/menuHTTP.h0000644000175000017500000001471213276341255017621 0ustar tobiastobias#ifndef VDR_STREAMDEV_SERVERS_MENUHTTP_H #define VDR_STREAMDEV_SERVERS_MENUHTTP_H #include #include "../common.h" #include class cChannel; // ******************** cItemIterator ****************** class cItemIterator { public: virtual bool Next() = 0; virtual bool IsGroup() const = 0; virtual const cString ItemId() const = 0; virtual const char* ItemTitle() const = 0; virtual const cString ItemRessource() const = 0; virtual const char* Alang(int i) const = 0; virtual const char* Dlang(int i) const = 0; virtual ~cItemIterator() {}; }; class cRecordingsIterator: public cItemIterator { private: eStreamType streamType; const cRecording *first; const cRecording *current; cThreadLock RecordingsLock; protected: virtual const cRecording* NextSuitable(const cRecording *Recording); public: virtual bool Next(); virtual bool IsGroup() const { return false; } virtual const cString ItemId() const { return current ? itoa(current->Index() + 1) : "0"; } virtual const char* ItemTitle() const { return current ? current->Title() : ""; } virtual const cString ItemRessource() const; virtual const char* Alang(int i) const { return NULL; } virtual const char* Dlang(int i) const { return NULL; } cRecordingsIterator(eStreamType StreamType); virtual ~cRecordingsIterator() {}; }; class cChannelIterator: public cItemIterator { private: const cChannel *first; const cChannel *current; protected: virtual const cChannel* FirstChannel(); virtual const cChannel* NextNormal(); virtual const cChannel* NextGroup(); virtual const cChannel* NextChannel(const cChannel *Channel) = 0; static inline const cChannel* SkipFakeGroups(const cChannel *Channel); // Helper which returns the group by its index static const cChannel* GetGroup(const char* GroupId); public: virtual bool Next(); virtual bool IsGroup() const { return current && current->GroupSep(); } virtual const cString ItemId() const; virtual const char* ItemTitle() const { return current ? current->Name() : ""; } virtual const cString ItemRessource() const { return (current ? current->GetChannelID() : tChannelID::InvalidID).ToString(); } virtual const char* Alang(int i) const { return current && current->Apid(i) ? current->Alang(i) : NULL; } virtual const char* Dlang(int i) const { return current && current->Dpid(i) ? current->Dlang(i) : NULL; } cChannelIterator(const 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 { private: static const cChannel* GetNextChannelInGroup(const cChannel *Channel); protected: virtual const cChannel* NextChannel(const cChannel *Channel); public: cListGroup(const char *GroupId); virtual ~cListGroup() {}; }; class cListTree: public cChannelIterator { private: const cChannel* selectedGroup; const cChannel* currentGroup; protected: virtual const cChannel* NextChannel(const cChannel *Channel); public: cListTree(const char *SelectedGroupId); virtual ~cListTree() {}; }; // ******************** cMenuList ****************** class cMenuList { private: cItemIterator *iterator; protected: bool NextItem() { return iterator->Next(); } bool IsGroup() { return iterator->IsGroup(); } const cString ItemId() { return iterator->ItemId(); } const char* ItemTitle() { return iterator->ItemTitle(); } const cString ItemRessource() { return iterator->ItemRessource(); } const char* Alang(int i) { return iterator->Alang(i); } const char* Dlang(int i) { return iterator->Dlang(i); } public: virtual std::string HttpHeader() { return "HTTP/1.0 200 OK\r\n"; }; virtual bool HasNext() = 0; virtual std::string Next() = 0; cMenuList(cItemIterator *Iterator); virtual ~cMenuList(); }; class cHtmlMenuList: public cMenuList { 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; bool onItem; eStreamType streamType; const char* self; const char* rss; 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 cMenuList::HttpHeader() + "Content-type: text/html; charset=" + (cCharSetConv::SystemCharacterTable() ? cCharSetConv::SystemCharacterTable() : "UTF-8") + "\r\n"; } virtual bool HasNext(); virtual std::string Next(); cHtmlMenuList(cItemIterator *Iterator, eStreamType StreamType, const char *Self, const char *Rss, const char *GroupTarget); virtual ~cHtmlMenuList(); }; class cM3uMenuList: public cMenuList { private: char *base; enum eM3uState { msFirst, msContinue, msLast }; eM3uState m3uState; cCharSetConv m_IConv; public: virtual std::string HttpHeader() { return cMenuList::HttpHeader() + "Content-type: audio/x-mpegurl; charset=UTF-8\r\n"; }; virtual bool HasNext(); virtual std::string Next(); cM3uMenuList(cItemIterator *Iterator, const char* Base); virtual ~cM3uMenuList(); }; class cRssMenuList: public cMenuList { private: char *base; char *html; enum eRssState { msFirst, msContinue, msLast }; eRssState rssState; cCharSetConv m_IConv; public: virtual std::string HttpHeader() { return cMenuList::HttpHeader() + "Content-type: application/rss+xml\r\n"; }; virtual bool HasNext(); virtual std::string Next(); cRssMenuList(cItemIterator *Iterator, const char *Base, const char *Html); virtual ~cRssMenuList(); }; inline const cChannel* cChannelIterator::SkipFakeGroups(const cChannel* Group) { #if APIVERSNUM >= 20300 LOCK_CHANNELS_READ; #endif while (Group && Group->GroupSep() && !*Group->Name()) #if APIVERSNUM >= 20300 Group = Channels->Next(Group); #else Group = Channels.Next(Group); #endif return Group; } #endif vdr-plugin-streamdev/server/connectionVTP.c0000644000175000017500000015233713276341255020707 0ustar tobiastobias/* * $Id: connectionVTP.c,v 1.31 2010/08/18 10:26:54 schmirl Exp $ */ #include "server/connectionVTP.h" #include "server/livestreamer.h" #include "server/livefilter.h" #include "server/suspend.h" #include "setup.h" #include #include #include #include #include #include #include #include /* 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: enum eStates { Channel, Event, Title, Subtitle, Description, Vps, Content, Rating, EndEvent, EndChannel, EndEPG }; cConnectionVTP *m_Client; #if APIVERSNUM < 20300 cSchedulesLock *m_SchedulesLock; #endif 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), #if APIVERSNUM < 20300 m_SchedulesLock(new cSchedulesLock(false, 500)), m_Schedules(cSchedules::Schedules(*m_SchedulesLock)), #endif 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) { #if APIVERSNUM >= 20300 LOCK_CHANNELS_READ; const cChannel* Channel = NULL; if (isnumber(p)) Channel = Channels->GetByNumber(strtol(Option, NULL, 10)); else Channel = Channels->GetByChannelID(tChannelID::FromString( #else cChannel* Channel = NULL; if (isnumber(p)) Channel = Channels.GetByNumber(strtol(Option, NULL, 10)); else Channel = Channels.GetByChannelID(tChannelID::FromString( #endif 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() { #if APIVERSNUM < 20300 delete m_SchedulesLock; #endif } 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) { #if APIVERSNUM >= 20300 LOCK_CHANNELS_READ; const cChannel *channel = Channels->GetByChannelID(m_Schedule->ChannelID(), #else cChannel *channel = Channels.GetByChannelID(m_Schedule->ChannelID(), #endif 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: m_State = Content; 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; 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; 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 APIVERSNUM >= 20300 LOCK_CHANNELS_READ; if (!Channels) { #else if (!Channels.Lock(false, 500)) { #endif m_Errno = 451; m_Error = "Channels are being modified - try again"; } else if (*Option) { if (isnumber(Option)) { #if APIVERSNUM >= 20300 m_Channel = Channels->GetByNumber(strtol(Option, NULL, 10)); #else m_Channel = Channels.GetByNumber(strtol(Option, NULL, 10)); #endif 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); #if APIVERSNUM >= 20300 while (i <= Channels->MaxNumber()) { m_Channel = Channels->GetByNumber(i, 1); #else while (i <= Channels.MaxNumber()) { m_Channel = Channels.GetByNumber(i, 1); #endif if (strcasestr(m_Channel->Name(), Option) != NULL) break; i = m_Channel->Number() + 1; } #if APIVERSNUM >= 20300 if (i > Channels->MaxNumber()) { #else if (i > Channels.MaxNumber()) { #endif m_Errno = 501; m_Error = cString::sprintf("Channel \"%s\" not defined", Option); return; } } #if APIVERSNUM >= 20300 } else if (Channels->MaxNumber() >= 1) { m_Channel = Channels->GetByNumber(1, 1); #else } else if (Channels.MaxNumber() >= 1) { m_Channel = Channels.GetByNumber(1, 1); #endif m_Traverse = true; } else { m_Errno = 550; m_Error = "No channels defined"; } } cLSTCHandler::~cLSTCHandler() { #if APIVERSNUM < 20300 Channels.Unlock(); #endif 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; #if APIVERSNUM >= 20300 LOCK_CHANNELS_READ; while (i <= Channels->MaxNumber()) { m_Channel = Channels->GetByNumber(i, 1); #else while (i <= Channels.MaxNumber()) { m_Channel = Channels.GetByNumber(i, 1); #endif 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 APIVERSNUM >= 20300 if (i < Channels->MaxNumber() + 1) #else if (i < Channels.MaxNumber() + 1) #endif Last = false; } return m_Client->Respond(Last ? 250 : -250, "%d %s", number, buffer); } // --- cLSTTHandler ----------------------------------------------------------- class cLSTTHandler { private: cConnectionVTP *m_Client; #if APIVERSNUM >= 20300 const cTimer *m_Timer; #else cTimer *m_Timer; #endif 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 APIVERSNUM >= 20300 LOCK_TIMERS_READ; #endif if (*Option) { if (isnumber(Option)) { #if APIVERSNUM >= 20300 m_Timer = Timers->Get(strtol(Option, NULL, 10) - 1); #else m_Timer = Timers.Get(strtol(Option, NULL, 10) - 1); #endif 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); } #if APIVERSNUM >= 20300 } else if (Timers->Count()) { #else } else if (Timers.Count()) { #endif m_Traverse = true; m_Index = 0; #if APIVERSNUM >= 20300 m_Timer = Timers->Get(m_Index); #else m_Timer = Timers.Get(m_Index); #endif 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; #if APIVERSNUM >= 20300 LOCK_TIMERS_READ; Last = !m_Traverse || m_Index >= Timers->Count() - 1; #else Last = !m_Traverse || m_Index >= Timers.Count() - 1; #endif 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) { #if APIVERSNUM >= 20300 m_Timer = Timers->Get(++m_Index); #else m_Timer = Timers.Get(++m_Index); #endif 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; #if APIVERSNUM >= 20300 const cRecording *m_Recording; #else cRecording *m_Recording; #endif 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 APIVERSNUM >= 20300 LOCK_RECORDINGS_READ; #endif if (*Option) { if (isnumber(Option)) { #if APIVERSNUM >= 20300 m_Recording = Recordings->Get(strtol(Option, NULL, 10) - 1); #else m_Recording = Recordings.Get(strtol(Option, NULL, 10) - 1); #endif m_Event = m_Recording->Info()->GetEvent(); 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); } } #if APIVERSNUM >= 20300 else if (Recordings->Count()) { #else else if (Recordings.Count()) { #endif m_Traverse = true; m_Index = 0; #if APIVERSNUM >= 20300 m_Recording = Recordings->Get(m_Index); #else m_Recording = Recordings.Get(m_Index); #endif 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; #if APIVERSNUM >= 20300 LOCK_RECORDINGS_READ; Last = !m_Traverse || m_Index >= Recordings->Count() - 1; #else Last = !m_Traverse || m_Index >= Recordings.Count() - 1; #endif result = m_Client->Respond(Last ? 250 : -250, "%d %s", m_Recording->Index() + 1, m_Recording->Title(' ', true)); if (m_Traverse && !Last) { #if APIVERSNUM >= 20300 m_Recording = Recordings->Get(++m_Index); #else m_Recording = Recordings.Get(++m_Index); #endif if (m_Recording == NULL) { m_Errno = 501; m_Error = cString::sprintf("Recording \"%d\" not found", m_Index + 1); } } return result; } return false; } class cStreamdevLoopPrevention { private: bool Unlock; public: cStreamdevLoopPrevention(const cChannel* Channel, bool LoopPrevention): Unlock(LoopPrevention) { if (LoopPrevention) cPluginManager::CallAllServices(LOOP_PREVENTION_SERVICE, (void *)Channel); } ~cStreamdevLoopPrevention() { if (Unlock) cPluginManager::CallAllServices(LOOP_PREVENTION_SERVICE, NULL); } }; // --- cConnectionVTP --------------------------------------------------------- #define LOOP_PREVENTION(c) cStreamdevLoopPrevention LoopPrevention(c, m_LoopPrevention); cConnectionVTP::cConnectionVTP(void): cServerConnection("VTP"), m_LiveSocket(NULL), m_FilterSocket(NULL), m_FilterStreamer(NULL), m_RecSocket(NULL), m_DataSocket(NULL), m_LastCommand(NULL), m_StreamType(stTSPIDS), m_ClientVersion(0), m_FiltersSupport(false), m_RecPlayer(NULL), m_TuneChannel(NULL), m_TunePriority(0), m_LSTEHandler(NULL), m_LSTCHandler(NULL), m_LSTTHandler(NULL), m_LSTRHandler(NULL) { m_LoopPrevention = StreamdevServerSetup.LoopPrevention; if (m_LoopPrevention) // Loop prevention enabled - but is there anybody out there? m_LoopPrevention = cPluginManager::CallFirstService(LOOP_PREVENTION_SERVICE); } cConnectionVTP::~cConnectionVTP() { if (m_LastCommand != NULL) free(m_LastCommand); if (Streamer()) Streamer()->Stop(); 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; } bool cConnectionVTP::Abort(void) const { return !IsOpen() || (Streamer() && Streamer()->Abort()) || (!Streamer() && m_FilterStreamer && m_FilterStreamer->Abort()); } void cConnectionVTP::Welcome(void) { Respond(220, "VTP/1.0 Welcome to Video Disk Recorder"); } void cConnectionVTP::Reject(void) { Respond(221, "Too many clients or client not allowed to connect"); cServerConnection::Reject(); } void cConnectionVTP::Detach(void) { if (m_FilterStreamer) m_FilterStreamer->Detach(); if (m_LiveSocket && Streamer()) ((cStreamdevLiveStreamer *) Streamer())->Detach(); } void cConnectionVTP::Attach(void) { if (m_LiveSocket && Streamer()) ((cStreamdevLiveStreamer *) Streamer())->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, "VERS") == 0) return CmdVERS(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, "PRIO") == 0) return CmdPRIO(param); else if (strcasecmp(Cmd, "SGNL") == 0) return CmdSGNL(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); } #ifdef STREAMDEV_PS if (strcasecmp(Opts, "PS") == 0) { m_StreamType = stPS; return Respond(220, "Capability \"%s\" accepted", Opts); } #endif if (strcasecmp(Opts, "PES") == 0) { m_StreamType = stPES; return Respond(220, "Capability \"%s\" accepted", Opts); } if (strcasecmp(Opts, "EXT") == 0) { m_StreamType = stEXT; 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); } // Command PRIO is known if (strcasecmp(Opts, "PRIO") == 0) { return Respond(220, "Capability \"%s\" accepted", Opts); } return Respond(561, "Capability \"%s\" not known", Opts); } bool cConnectionVTP::CmdVERS(char *Opts) { unsigned int major, minor; if (sscanf(Opts, " %u.%u", &major, &minor) != 2) return Respond(501, "Use: VERS version (with version in format major.minor)"); m_ClientVersion = major * 100 + minor; m_FiltersSupport = true; return Respond(220, "Protocol version %u.%u accepted", major, minor); } 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); // legacy clients use priority 0 even if live TV has priority if (m_ClientVersion == 0 && prio == 0) prio = StreamdevServerSetup.VTPPriority; LOOP_PREVENTION(chan); if (cStreamdevLiveStreamer::ProvidesChannel(chan, prio)) { m_TuneChannel = chan; m_TunePriority = prio; return Respond(220, "Channel available"); } // legacy clients didn't lower priority when switching channels, // so get our own receiver temporarily out of the way if (m_ClientVersion == 0) { Detach(); bool provided = cStreamdevLiveStreamer::ProvidesChannel(chan, prio); Attach(); if (provided) { m_TuneChannel = chan; m_TunePriority = prio; return Respond(220, "Channel available"); } } m_TuneChannel = NULL; return 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); return Respond(220, "Port command ok, data connection opened"); break; case siLive: if(m_LiveSocket && Streamer()) Streamer()->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 (Streamer()) Streamer()->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; int prio = m_TunePriority; if ((chan = ChannelFromString(Opts)) == NULL) return Respond(550, "Undefined channel \"%s\"", Opts); LOOP_PREVENTION(chan); if (chan != m_TuneChannel) { isyslog("streamdev-server TUNE %s: Priority unknown - using 0", Opts); prio = 0; if (!cStreamdevLiveStreamer::ProvidesChannel(chan, prio)) return Respond(560, "Channel not available (ProvidesChannel)"); } cStreamdevLiveStreamer* liveStreamer = new cStreamdevLiveStreamer(this, chan, prio, m_StreamType); if ((dev = liveStreamer->GetDevice()) == NULL) { delete liveStreamer; return Respond(560, "Channel not available (SwitchDevice)"); } SetStreamer(liveStreamer); if(m_LiveSocket) 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)) { #if APIVERSNUM >= 20300 LOCK_RECORDINGS_READ; const cRecording *recording = Recordings->Get(strtol(Opts, NULL, 10) - 1); #else cRecording *recording = Recordings.Get(strtol(Opts, NULL, 10) - 1); #endif if (recording) { if (m_RecPlayer) { delete m_RecPlayer; } m_RecPlayer = new RecPlayer(recording->FileName()); 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::CmdPRIO(char *Opts) { int prio; char *end; prio = strtoul(Opts, &end, 10); if (end == Opts || (*end != '\0' && *end != ' ')) return Respond(500, "Use: PRIO Priority"); if (Streamer()) { ((cStreamdevLiveStreamer*) Streamer())->SetPriority(prio); return Respond(220, "Priority changed to %d", prio); } return Respond(550, "Priority not applicable"); } bool cConnectionVTP::CmdSGNL(char *Opts) { if (Streamer()) { int devnum = -1; int signal = -1; int quality = -1; ((cStreamdevLiveStreamer*) Streamer())->GetSignal(&devnum, &signal, &quality); return Respond(220, "%d %d:%d", devnum, signal, quality); } return Respond(550, "Signal not applicable"); } 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 Streamer() && ((cStreamdevLiveStreamer*) Streamer())->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 Streamer() && ((cStreamdevLiveStreamer*) Streamer())->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: if (Streamer()) Streamer()->Stop(); 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 (cSuspendCtl::IsActive()) return Respond(220, "Server is suspended"); else if (StreamdevServerSetup.AllowSuspend) { cControl::Launch(new cSuspendCtl); cControl::Attach(); return Respond(220, "Server is suspended"); } else return Respond(550, "Client may not suspend server"); } // Functions extended from SVDRP template 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; #if APIVERSNUM < 20102 int Percent = VideoDiskSpace(&FreeMB, &UsedMB); #else int Percent = cVideoDirectory::VideoDiskSpace(&FreeMB, &UsedMB); #endif 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) { #if APIVERSNUM >= 20300 LOCK_RECORDINGS_WRITE; Recordings->Sort(); if (Recordings) { cRecording *recording = Recordings->Last(); #else bool recordings = Recordings.Load(); Recordings.Sort(); if (recordings) { cRecording *recording = Recordings.Last(); #endif Reply(250, "%d", recording->Index() + 1); } else { Reply(250, "0"); } } else if (strcasecmp(Option, "CHANNELS") == 0) { #if APIVERSNUM >= 20300 LOCK_CHANNELS_READ; Reply(250, "%d", Channels->MaxNumber()); #else Reply(250, "%d", Channels.MaxNumber()); #endif } else if (strcasecmp(Option, "TIMERS") == 0) { #if APIVERSNUM >= 20300 LOCK_TIMERS_READ; Reply(250, "%d", Timers->Count()); #else Reply(250, "%d", Timers.Count()); #endif } 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); #if APIVERSNUM >= 20300 LOCK_TIMERS_WRITE; cTimer *timer = Timers->Get(n - 1); #else cTimer *timer = Timers.Get(n - 1); #endif 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; #if APIVERSNUM >= 20300 Timers->SetModified(); #else Timers.SetModified(); #endif 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)) { #if APIVERSNUM >= 20300 LOCK_TIMERS_WRITE; cTimer *t = Timers->GetTimer(timer); if (!t) { Timers->Add(timer); Timers->SetModified(); #else cTimer *t = Timers.GetTimer(timer); if (!t) { Timers.Add(timer); Timers.SetModified(); #endif 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"); } } #if APIVERSNUM >= 20300 LOCK_TIMERS_WRITE; cTimer *Timer = Timers->Get(number); if (Timer) { if (Timer->Recording()) { if (force) { if (!Timer->Remote()) { Timer->Skip(); cRecordControls::Process(Timers, time(NULL)); } #else cTimer *timer = Timers.Get(number); if (timer) { if (timer->Recording()) { if (force) { timer->Skip(); cRecordControls::Process(time(NULL)); #endif } else { Reply(550, "Timer \"%i\" is recording", number); EXIT_WRAPPER(); } } #if APIVERSNUM >= 20300 isyslog("deleting timer %s", *Timer->ToDescr()); Timers->Del(Timer); Timers->SetModified(); #else isyslog("deleting timer %s", *timer->ToDescr()); Timers.Del(timer); Timers.SetModified(); #endif 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(); #if APIVERSNUM >= 20300 LOCK_TIMERS_READ; const cTimer *t = Timers->GetNextActiveTimer(); #else cTimer *t = Timers.GetNextActiveTimer(); #endif 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 APIVERSNUM >= 20300 LOCK_CHANNELS_WRITE; if (Channels->HasUniqueChannelID(&ch)) { #else if (Channels.HasUniqueChannelID(&ch)) { #endif cChannel *channel = new cChannel; *channel = ch; #if APIVERSNUM >= 20300 Channels->Add(channel); Channels->ReNumber(); Channels->SetModified(); #else Channels.Add(channel); Channels.ReNumber(); Channels.SetModified(true); #endif 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 APIVERSNUM >= 20300 LOCK_CHANNELS_WRITE; Channels->SetExplicitModify(); cChannel *channel = Channels->GetByNumber(n); #else if (!Channels.BeingEdited()) { cChannel *channel = Channels.GetByNumber(n); #endif if (channel) { cChannel ch; if (ch.Parse(tail)) { #if APIVERSNUM >= 20300 if (Channels->HasUniqueChannelID(&ch, channel)) { *channel = ch; Channels->ReNumber(); Channels->SetModified(); #else if (Channels.HasUniqueChannelID(&ch, channel)) { *channel = ch; Channels.ReNumber(); Channels.SetModified(true); #endif 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); } #if APIVERSNUM < 20300 } else { Reply(550, "Channels are being edited - try again later"); } #endif } 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 APIVERSNUM >= 20300 LOCK_CHANNELS_WRITE; Channels->SetExplicitModify(); // LOCK_TIMERS_WRITE; // Timers->SetExplicitModify(); #else if (!Channels.BeingEdited() && !Timers.BeingEdited()) { #endif 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(); #if APIVERSNUM >= 20300 cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr); cChannel *FromChannel = Channels->GetByNumber(From); if (FromChannel) { cChannel *ToChannel = Channels->GetByNumber(To); #else cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr); cChannel *FromChannel = Channels.GetByNumber(From); if (FromChannel) { cChannel *ToChannel = Channels.GetByNumber(To); #endif if (ToChannel) { int FromNumber = FromChannel->Number(); int ToNumber = ToChannel->Number(); if (FromNumber != ToNumber) { #if APIVERSNUM >= 20300 Channels->Move(FromChannel, ToChannel); Channels->ReNumber(); Channels->SetModified(); #else Channels.Move(FromChannel, ToChannel); Channels.ReNumber(); Channels.SetModified(true); #endif if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) { if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) { #if APIVERSNUM >= 20300 Channels->SwitchTo(CurrentChannel->Number()); #else Channels.SwitchTo(CurrentChannel->Number()); #endif } 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 position"); } } 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"); } #if APIVERSNUM < 20300 } else { Reply(550, "Channels or timers are being edited - try again later"); } #endif } else { Reply(501, "Missing channel number"); } EXIT_WRAPPER(); } bool cConnectionVTP::CmdDELC(const char *Option) { INIT_WRAPPER(); if (*Option) { if (isnumber(Option)) { #if APIVERSNUM >= 20300 LOCK_CHANNELS_WRITE; Channels->SetExplicitModify(); cChannel *channel = Channels->GetByNumber(strtol(Option, NULL, 10)); #else if (!Channels.BeingEdited()) { cChannel *channel = Channels.GetByNumber(strtol(Option, NULL, 10)); #endif if (channel) { #if APIVERSNUM >= 20300 LOCK_TIMERS_READ; for (const cTimer *timer = Timers->First(); timer; timer = Timers->Next(timer)) { #else for (cTimer *timer = Timers.First(); timer; timer = Timers.Next(timer)) { #endif if (timer->Channel() == channel) { Reply(550, "Channel \"%s\" is in use by timer %d", Option, timer->Index() + 1); return false; } } int CurrentChannelNr = cDevice::CurrentChannel(); #if APIVERSNUM >= 20300 cChannel *CurrentChannel = Channels->GetByNumber(CurrentChannelNr); #else cChannel *CurrentChannel = Channels.GetByNumber(CurrentChannelNr); #endif if (CurrentChannel && channel == CurrentChannel) { #if APIVERSNUM >= 20300 int n = Channels->GetNextNormal(CurrentChannel->Index()); if (n < 0) n = Channels->GetPrevNormal(CurrentChannel->Index()); CurrentChannel = Channels->Get(n); #else int n = Channels.GetNextNormal(CurrentChannel->Index()); if (n < 0) n = Channels.GetPrevNormal(CurrentChannel->Index()); CurrentChannel = Channels.Get(n); #endif CurrentChannelNr = 0; // triggers channel switch below } #if APIVERSNUM >= 20300 Channels->Del(channel); Channels->ReNumber(); Channels->SetModified(); #else Channels.Del(channel); Channels.ReNumber(); Channels.SetModified(true); #endif isyslog("channel %s deleted", Option); if (CurrentChannel && CurrentChannel->Number() != CurrentChannelNr) { if (!cDevice::PrimaryDevice()->Replaying() || cDevice::PrimaryDevice()->Transferring()) #if APIVERSNUM >= 20300 Channels->SwitchTo(CurrentChannel->Number()); #else Channels.SwitchTo(CurrentChannel->Number()); #endif else cDevice::SetCurrentChannel(CurrentChannel); } Reply(250, "Channel \"%s\" deleted", Option); } else Reply(501, "Channel \"%s\" not defined", Option); } #if APIVERSNUM < 20300 else Reply(550, "Channels are being edited - try again later"); } #endif 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)) { #if APIVERSNUM >= 20300 LOCK_RECORDINGS_WRITE; cRecording *recording = Recordings->Get(strtol(Option, NULL, 10) - 1); #else cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1); #endif if (recording) { cRecordControl *rc = cRecordControls::GetRecordControl(recording->FileName()); if (!rc) { if (recording->Delete()) { Reply(250, "Recording \"%s\" deleted", Option); #if APIVERSNUM >= 20300 Recordings->DelByName(recording->FileName()); #else ::Recordings.DelByName(recording->FileName()); #endif } else Reply(554, "Error while deleting recording!"); } else Reply(550, "Recording \"%s\" is in use by timer %d", Option, rc->Timer()->Index() + 1); } else #if APIVERSNUM >= 20300 Reply(550, "Recording \"%s\" not found%s", Option, Recordings->Count() ? "" : " (use LSTR before deleting)"); #else Reply(550, "Recording \"%s\" not found%s", Option, Recordings.Count() ? "" : " (use LSTR before deleting)"); #endif } 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) && LIEMIKUUTIO < 132 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) { char *oldName = strdup(recording->Name()); tail = skipspace(tail); if (recording->Rename(tail)) { 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 LIEMIKUUTIO < 1.32"); #endif /* LIEMIKUUTIO */ EXIT_WRAPPER(); } bool cConnectionVTP::Respond(int Code, const char *Message, ...) { va_list ap; va_start(ap, Message); #if APIVERSNUM >= 10728 cString str = cString::vsprintf(Message, ap); #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); } cString cConnectionVTP::ToText(char Delimiter) const { cString str = cServerConnection::ToText(Delimiter); if (Streamer()) return cString::sprintf("%s%c%s", *str, Delimiter, *Streamer()->ToText()); else if (m_RecPlayer) return cString::sprintf("%s%c%s", *str, Delimiter, m_RecPlayer->getCurrentRecording()->Name()); else return str; } vdr-plugin-streamdev/server/streamdev-server.h0000644000175000017500000000304013276341255021443 0ustar tobiastobias/* * $Id: streamdev-server.h,v 1.2 2010/07/19 13:49:32 schmirl Exp $ */ #ifndef VDR_STREAMDEVSERVER_H #define VDR_STREAMDEVSERVER_H #include "common.h" #include #include class cMainThreadHookSubscriber: public cListObject { private: static cList m_Subscribers; static cMutex m_Mutex; public: #if APIVERSNUM >= 20300 static cList& Subscribers(cMutexLock& Lock); #else static const cList& Subscribers(cMutexLock& Lock); #endif virtual void MainThreadHook() = 0; cMainThreadHookSubscriber(); virtual ~cMainThreadHookSubscriber(); }; class cPluginStreamdevServer : public cPlugin { private: static const char *DESCRIPTION; bool m_Suspend; 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 void MainThreadHook(void); virtual cMenuSetupPage *SetupMenu(void); virtual bool SetupParse(const char *Name, const char *Value); virtual const char **SVDRPHelpPages(void); virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode); virtual bool Service(const char *Id, void *Data = NULL); }; #endif // VDR_STREAMDEVSERVER_H vdr-plugin-streamdev/server/livefilter.h0000644000175000017500000000161113276341255020314 0ustar tobiastobias/* * $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 "server/streamer.h" class cDevice; class cStreamdevLiveFilter; class cStreamdevFilterStreamer: public cStreamdevStreamer { private: cDevice *m_Device; cStreamdevLiveFilter *m_Filter; cStreamdevBuffer *m_ReceiveBuffer; protected: virtual uchar* GetFromReceiver(int &Count) { return m_ReceiveBuffer->Get(Count); } virtual void DelFromReceiver(int Count) { m_ReceiveBuffer->Del(Count); } public: cStreamdevFilterStreamer(); virtual ~cStreamdevFilterStreamer(); void SetDevice(cDevice *Device); bool SetFilter(u_short Pid, u_char Tid, u_char Mask, bool On); virtual bool IsReceiving(void) const; void Receive(uchar *Data); virtual void Attach(void); virtual void Detach(void); }; #endif // VDR_STREAMEV_LIVEFILTER_H vdr-plugin-streamdev/server/componentVTP.c0000644000175000017500000000062713276341255020544 0ustar tobiastobias/* * $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; } vdr-plugin-streamdev/server/componentVTP.h0000644000175000017500000000056513276341255020552 0ustar tobiastobias/* * $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 vdr-plugin-streamdev/server/streamer.h0000644000175000017500000000517013276341255017775 0ustar tobiastobias/* * $Id: streamer.h,v 1.12 2010/07/19 13:49:32 schmirl Exp $ */ #ifndef VDR_STREAMDEV_STREAMER_H #define VDR_STREAMDEV_STREAMER_H #include #include #include #include "remux/tsremux.h" class cTBSocket; class cStreamdevStreamer; class cServerConnection; #ifndef TS_SIZE #define TS_SIZE 188 #endif #define WRITERBUFSIZE (20000 * 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: const cServerConnection *m_Connection; Streamdev::cTSRemux *m_Remux; cStreamdevWriter *m_Writer; cStreamdevBuffer *m_SendBuffer; protected: virtual uchar* GetFromReceiver(int &Count) = 0; virtual void DelFromReceiver(int Count) = 0; virtual int Put(const uchar *Data, int Count); virtual void Action(void); bool IsRunning(void) const { return m_Writer; } void SetRemux(Streamdev::cTSRemux *Remux) { delete m_Remux; m_Remux = Remux; } public: cStreamdevStreamer(const char *Name, const cServerConnection *Connection = NULL); virtual ~cStreamdevStreamer(); const cServerConnection* Connection(void) const { return m_Connection; } virtual void Start(cTBSocket *Socket); virtual void Stop(void); virtual bool IsReceiving(void) const = 0; bool Abort(void); uchar *Get(int &Count) { return m_Remux->Get(Count); } void Del(int Count) { m_Remux->Del(Count); } virtual void Detach(void) {} virtual void Attach(void) {} virtual cString ToText() const { return ""; }; }; inline bool cStreamdevStreamer::Abort(void) { return Active() && !m_Writer->Active(); } #endif // VDR_STREAMDEV_STREAMER_H vdr-plugin-streamdev/server/componentHTTP.c0000644000175000017500000000064113276341255020646 0ustar tobiastobias/* * $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; } vdr-plugin-streamdev/server/po/0000755000175000017500000000000013276341255016415 5ustar tobiastobiasvdr-plugin-streamdev/server/po/es_ES.po0000644000175000017500000000402513276341255017754 0ustar tobiastobias# 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. # Javier Bradineras , 2011 # msgid "" msgstr "" "Project-Id-Version: streamdev 0.5.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2014-05-05 22:46+0200\n" "PO-Revision-Date: 2010-06-19 03:58+0100\n" "Last-Translator: Javier Bradineras \n" "Language-Team: Spanish \n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ISO-8859-15\n" "Content-Transfer-Encoding: 8bit\n" msgid "Streaming active" msgstr "Trasmisión activa" msgid "Streamdev Connections" msgstr "" msgid "Disconnect" msgstr "" msgid "Suspend" msgstr "Suspender" msgid "Common Settings" msgstr "Configuración común" msgid "Hide Mainmenu Entry" msgstr "" msgid "Start with Live TV suspended" msgstr "" msgid "Maximum Number of Clients" msgstr "Numero máximo de clientes" msgid "Live TV buffer delay (ms)" msgstr "" msgid "VDR-to-VDR Server" msgstr "Servidor VDR-a-VDR" msgid "Start VDR-to-VDR Server" msgstr "Iniciar Servidor VDR-a-VDR" msgid "VDR-to-VDR Server Port" msgstr "Puerto del Servidor VDR-a-VDR" msgid "Bind to IP" msgstr "IP asociada" msgid "Legacy Client Priority" msgstr "" msgid "Client may suspend" msgstr "Permitir suspender al cliente" msgid "Loop Prevention" msgstr "" msgid "HTTP Server" msgstr "Servidor HTTP" msgid "Start HTTP Server" msgstr "Iniciar Servidor HTTP" msgid "HTTP Server Port" msgstr "Puerto del Servidor HTTP" msgid "Priority" msgstr "" msgid "HTTP Streamtype" msgstr "Tipo de flujo HTTP" msgid "Multicast Streaming Server" msgstr "Servidor de transmisión Multicast" msgid "Start IGMP Server" msgstr "Iniciar Servidor IGMP" msgid "Multicast Client Port" msgstr "Puerto del Cliente Multicast" msgid "Multicast Streamtype" msgstr "Tipo de flujo Multicast" msgid "VDR Streaming Server" msgstr "Servidor de transmisiones del VDR" vdr-plugin-streamdev/server/po/pl_PL.po0000644000175000017500000000422513276341255017766 0ustar tobiastobias# VDR streamdev plugin language source file. # Copyright (C) 2008 streamdev development team. See # This file is distributed under the same license as the VDR streamdev package. # Tomasz Maciej Nowak, 2014 # msgid "" msgstr "" "Project-Id-Version: vdr-streamdev-server 0.6.1-git\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2014-11-20 14:11+0100\n" "PO-Revision-Date: 2014-11-24 18:02+0100\n" "Last-Translator: Tomasz Maciej Nowak \n" "Language-Team: Polish \n" "Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=iso-8859-2\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 1.6.10\n" msgid "Streaming active" msgstr "Strumieniowanie aktywne" msgid "Streamdev Connections" msgstr "Po³±czenia Streamdev" msgid "Disconnect" msgstr "Roz³±cz" msgid "Suspend" msgstr "Wstrzymaj" msgid "Common Settings" msgstr "Ustawienia ogólne" msgid "Hide Mainmenu Entry" msgstr "Ukryj pozycjê w g³ównym menu" msgid "Start with Live TV suspended" msgstr "Uruchom ze wstrzyman± telewizj±" msgid "Maximum Number of Clients" msgstr "Maksymalna liczba klientów" msgid "Live TV buffer delay (ms)" msgstr "Bufor opó¼nieñ telewizji (ms)" msgid "VDR-to-VDR Server" msgstr "Serwer VDR do VDR" msgid "Start VDR-to-VDR Server" msgstr "Uruchom serwer VDR do VDR" msgid "VDR-to-VDR Server Port" msgstr "Port serwera VDR do VDR" msgid "Bind to IP" msgstr "Przypisz do adresu" msgid "Legacy Client Priority" msgstr "Priorytet dla starego klienta" msgid "Client may suspend" msgstr "Klient mo¿e wstrzymaæ" msgid "Loop Prevention" msgstr "Zapobieganie pêtli" msgid "HTTP Server" msgstr "Serwer HTTP" msgid "Start HTTP Server" msgstr "Uruchom serwer HTTP" msgid "HTTP Server Port" msgstr "Port serwera HTTP" msgid "Priority" msgstr "Priorytet" msgid "HTTP Streamtype" msgstr "Typ strumienia HTTP" msgid "Multicast Streaming Server" msgstr "Serwer strumienia Multicast" msgid "Start IGMP Server" msgstr "Uruchom serwer Multicast" msgid "Multicast Client Port" msgstr "Port klienta Multicast" msgid "Multicast Streamtype" msgstr "Typ strumienia Multicast" msgid "VDR Streaming Server" msgstr "Serwer strumieniuj±cy VDR" vdr-plugin-streamdev/server/po/sk_SK.po0000755000175000017500000000426713276341255020003 0ustar tobiastobias# 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 , 2009 # msgid "" msgstr "" "Project-Id-Version: streamdev_SK\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2014-05-05 22:46+0200\n" "PO-Revision-Date: 2013-11-22 23:39+0100\n" "Last-Translator: Milan Hrala \n" "Language-Team: Slovak \n" "Language: sk\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 "Streaming active" msgstr "Streamovanie aktívne" msgid "Streamdev Connections" msgstr "Streamdev spojenia" msgid "Disconnect" msgstr "Odpoji»" msgid "Suspend" msgstr "Pozastavi»" msgid "Common Settings" msgstr "V¹eobecné nastavenia" msgid "Hide Mainmenu Entry" msgstr "Schova» v hlavnom menu" msgid "Start with Live TV suspended" msgstr "Pozastavi» Live TV pri ¹tarte" msgid "Maximum Number of Clients" msgstr "Maximály poèet klientov" msgid "Live TV buffer delay (ms)" msgstr "" msgid "VDR-to-VDR Server" msgstr "Prenos z VDR do VDR" msgid "Start VDR-to-VDR Server" msgstr "Spusti» prenos z VDR do VDR" msgid "VDR-to-VDR Server Port" msgstr "Port servera prenosu z VDR do VDR" msgid "Bind to IP" msgstr "Viaza» na IP" msgid "Legacy Client Priority" msgstr "Dodatoèná priorita klienta" msgid "Client may suspend" msgstr "Klient mô¾e server pozastavi»" msgid "Loop Prevention" msgstr "Prevencia sluèky" msgid "HTTP Server" msgstr "HTTP server " msgid "Start HTTP Server" msgstr "Spusti» HTTP Server" msgid "HTTP Server Port" msgstr "Port HTTP servera" msgid "Priority" msgstr "Priorita" msgid "HTTP Streamtype" msgstr "Typ HTTP streamu" msgid "Multicast Streaming Server" msgstr "Streamovanie Multicastového servera" msgid "Start IGMP Server" msgstr "Spusti» IGMP Server" msgid "Multicast Client Port" msgstr "Port Multicast klienta" msgid "Multicast Streamtype" msgstr "Typ Multicast streamu" msgid "VDR Streaming Server" msgstr "VDR server streamovania" vdr-plugin-streamdev/server/po/it_IT.po0000644000175000017500000000422713276341255017772 0ustar tobiastobias# 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 , 2001 # Antonio Ospite , 2003 # Sean Carlos , 2005 # msgid "" msgstr "" "Project-Id-Version: streamdev 0.5.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2014-05-05 22:46+0200\n" "PO-Revision-Date: 2012-06-12 19:57+0100\n" "Last-Translator: Diego Pierotto \n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "Streaming active" msgstr "Trasmissione attiva" msgid "Streamdev Connections" msgstr "Connessioni Streamdev" msgid "Disconnect" msgstr "Disconnetti" msgid "Suspend" msgstr "Sospendi" msgid "Common Settings" msgstr "Impostazioni comuni" msgid "Hide Mainmenu Entry" msgstr "Nascondi voce menu principale" msgid "Start with Live TV suspended" msgstr "" msgid "Maximum Number of Clients" msgstr "Numero massimo di Client" msgid "Live TV buffer delay (ms)" msgstr "" 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 "Legacy Client Priority" msgstr "Priorità nativa client" msgid "Client may suspend" msgstr "Permetti sospensione al Client" msgid "Loop Prevention" msgstr "Evita ciclo" msgid "HTTP Server" msgstr "Server HTTP" msgid "Start HTTP Server" msgstr "Avvia Server HTTP" msgid "HTTP Server Port" msgstr "Porta Server HTTP" msgid "Priority" msgstr "Priorità" msgid "HTTP Streamtype" msgstr "Tipo flusso HTTP" msgid "Multicast Streaming Server" msgstr "Server trasmissione Multicast" msgid "Start IGMP Server" msgstr "Avvia Server IGMP" msgid "Multicast Client Port" msgstr "Porta Client Multicast" msgid "Multicast Streamtype" msgstr "Tipo flusso Multicast" msgid "VDR Streaming Server" msgstr "Server trasmissione VDR" vdr-plugin-streamdev/server/po/fi_FI.po0000644000175000017500000000420113276341255017726 0ustar tobiastobias# 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, 2008- # msgid "" msgstr "" "Project-Id-Version: streamdev 0.5.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2014-05-05 22:46+0200\n" "PO-Revision-Date: 2008-03-30 02:11+0200\n" "Last-Translator: Rolf Ahrenberg\n" "Language-Team: Finnish \n" "Language: fi\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "Streaming active" msgstr "Suoratoistopalvelin aktiivinen" msgid "Streamdev Connections" msgstr "Suoratoistoyhteydet" msgid "Disconnect" msgstr "Katkaise" msgid "Suspend" msgstr "Pysäytä" msgid "Common Settings" msgstr "Yleiset asetukset" msgid "Hide Mainmenu Entry" msgstr "Piilota valinta päävalikosta" msgid "Start with Live TV suspended" msgstr "Käynnistä Live-katselu pysäytettynä" msgid "Maximum Number of Clients" msgstr "Suurin sallittu asiakkaiden määrä" msgid "Live TV buffer delay (ms)" msgstr "" 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 "Legacy Client Priority" msgstr "Legacy-asiakkaan prioriteetti" msgid "Client may suspend" msgstr "Asiakas saa pysäyttää palvelimen" msgid "Loop Prevention" msgstr "Estä asiakaslaitesilmukat" msgid "HTTP Server" msgstr "HTTP-palvelin" msgid "Start HTTP Server" msgstr "Käynnistä HTTP-palvelin" msgid "HTTP Server Port" msgstr "HTTP-palvelimen portti" msgid "Priority" msgstr "Prioriteetti" 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 "VDR Streaming Server" msgstr "VDR-suoratoistopalvelin" vdr-plugin-streamdev/server/po/fr_FR.po0000644000175000017500000000362513276341255017761 0ustar tobiastobias# 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 , 2008 # msgid "" msgstr "" "Project-Id-Version: streamdev 0.5.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2014-05-05 22:46+0200\n" "PO-Revision-Date: 2008-03-30 02:11+0200\n" "Last-Translator: micky979 \n" "Language-Team: French \n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ISO-8859-15\n" "Content-Transfer-Encoding: 8bit\n" msgid "Streaming active" msgstr "Streaming actif" msgid "Streamdev Connections" msgstr "" msgid "Disconnect" msgstr "" msgid "Suspend" msgstr "Suspendre" msgid "Common Settings" msgstr "Paramètres communs" msgid "Hide Mainmenu Entry" msgstr "" msgid "Start with Live TV suspended" msgstr "" msgid "Maximum Number of Clients" msgstr "Nombre maximun de clients" msgid "Live TV buffer delay (ms)" msgstr "" 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 "Legacy Client Priority" msgstr "" msgid "Client may suspend" msgstr "Le client peut suspendre" msgid "Loop Prevention" msgstr "" 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 "Priority" msgstr "" 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 "VDR Streaming Server" msgstr "Serveur de streaming VDR" vdr-plugin-streamdev/server/po/de_DE.po0000644000175000017500000000416313276341255017721 0ustar tobiastobias# 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 , 2008 # msgid "" msgstr "" "Project-Id-Version: streamdev\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2014-05-05 22:46+0200\n" "PO-Revision-Date: 2008-03-30 02:11+0200\n" "Last-Translator: Frank Schmirler \n" "Language-Team: German \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ISO-8859-15\n" "Content-Transfer-Encoding: 8bit\n" msgid "Streaming active" msgstr "Streamen im Gange" msgid "Streamdev Connections" msgstr "Streamdev Verbindungen" msgid "Disconnect" msgstr "Trennen" msgid "Suspend" msgstr "Pausieren" msgid "Common Settings" msgstr "Allgemeines" msgid "Hide Mainmenu Entry" msgstr "Hauptmenüeintrag verstecken" msgid "Start with Live TV suspended" msgstr "Live-TV beim Start pausieren" msgid "Maximum Number of Clients" msgstr "Maximalanzahl an Clients" msgid "Live TV buffer delay (ms)" msgstr "Live-TV Pufferdauer (ms)" 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 "Legacy Client Priority" msgstr "Priorität für alte Clients" msgid "Client may suspend" msgstr "Client darf pausieren" msgid "Loop Prevention" msgstr "Schleifen verhindern" msgid "HTTP Server" msgstr "HTTP Server" msgid "Start HTTP Server" msgstr "HTTP Server starten" msgid "HTTP Server Port" msgstr "Port des HTTP Servers" msgid "Priority" msgstr "Priorität" 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 "VDR Streaming Server" msgstr "VDR Streaming Server" vdr-plugin-streamdev/server/po/ru_RU.po0000644000175000017500000000356713276341255020024 0ustar tobiastobias# 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 , 2008 # msgid "" msgstr "" "Project-Id-Version: streamdev 0.5.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2014-05-05 22:46+0200\n" "PO-Revision-Date: 2008-06-26 15:36+0100\n" "Last-Translator: Oleg Roitburd \n" "Language-Team: Russian \n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ISO-8859-5\n" "Content-Transfer-Encoding: 8bit\n" msgid "Streaming active" msgstr "ÁâàØÜØÝÓ ÐÚâØÒÕÝ" msgid "Streamdev Connections" msgstr "" msgid "Disconnect" msgstr "" msgid "Suspend" msgstr "¾áâÐÝÞÒÚÐ" msgid "Common Settings" msgstr "½ÐáâàÞÙÚØ" msgid "Hide Mainmenu Entry" msgstr "" msgid "Start with Live TV suspended" msgstr "" msgid "Maximum Number of Clients" msgstr "¼ÐÚá. ÚÞÛØçÕáâÒÞ ÚÛØÕÝâÞÒ" msgid "Live TV buffer delay (ms)" 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 "Legacy Client Priority" msgstr "" msgid "Client may suspend" msgstr "ºÛØÕÝâ ÜÞÖÕâ ÞáâÐÝÐÒÛØÒÐâì" msgid "Loop Prevention" msgstr "" msgid "HTTP Server" msgstr "HTTP áÕàÒÕà" msgid "Start HTTP Server" msgstr "ÁâÐàâ HTTP áÕàÒÕàÐ" msgid "HTTP Server Port" msgstr "HTTP áÕàÒÕà ¿Þàâ" msgid "Priority" msgstr "" 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 "VDR Streaming Server" msgstr "VDR Streaming áÕàÒÕà" vdr-plugin-streamdev/server/po/lt_LT.po0000644000175000017500000000401713276341255017775 0ustar tobiastobias# 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 , 2008 # msgid "" msgstr "" "Project-Id-Version: streamdev 0.5.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2014-05-05 22:46+0200\n" "PO-Revision-Date: 2009-11-26 21:57+0200\n" "Last-Translator: Valdemaras Pipiras \n" "Language-Team: Lithuanian \n" "Language: lt\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "Streaming active" msgstr "Transliavimas vyksta" msgid "Streamdev Connections" msgstr "" msgid "Disconnect" msgstr "" msgid "Suspend" msgstr "Pristabdyti" msgid "Common Settings" msgstr "Bendri nustatymai" msgid "Hide Mainmenu Entry" msgstr "" msgid "Start with Live TV suspended" msgstr "" msgid "Maximum Number of Clients" msgstr "Maksimalus klientų skaiÄius" msgid "Live TV buffer delay (ms)" msgstr "" 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 "Legacy Client Priority" msgstr "" msgid "Client may suspend" msgstr "Klientas gali pristabdyti" msgid "Loop Prevention" msgstr "" msgid "HTTP Server" msgstr "HTTP Serveris" msgid "Start HTTP Server" msgstr "Paleisti HTTP serverį" msgid "HTTP Server Port" msgstr "HTTP serverio portas" msgid "Priority" msgstr "" 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 "VDR Streaming Server" msgstr "VDR transliavimo serveris" vdr-plugin-streamdev/server/menuHTTP.c0000644000175000017500000004362413276341255017620 0ustar tobiastobias#include #include #include #include #include "server/menuHTTP.h" //**************************** cRecordingIterator ************** #if APIVERSNUM >= 20300 cRecordingsIterator::cRecordingsIterator(eStreamType StreamType) #else cRecordingsIterator::cRecordingsIterator(eStreamType StreamType): RecordingsLock(&Recordings) #endif { streamType = StreamType; #if APIVERSNUM >= 20300 LOCK_RECORDINGS_READ; first = NextSuitable(Recordings->First()); #else first = NextSuitable(Recordings.First()); #endif current = NULL; } const cRecording* cRecordingsIterator::NextSuitable(const cRecording *Recording) { while (Recording) { bool isPes = Recording->IsPesRecording(); if (!isPes || (isPes && streamType == stPES)) break; #if APIVERSNUM >= 20300 LOCK_RECORDINGS_READ; Recording = Recordings->Next(Recording); #else Recording = Recordings.Next(Recording); #endif } return Recording; } bool cRecordingsIterator::Next() { #if APIVERSNUM >= 20300 LOCK_RECORDINGS_READ; #endif if (first) { current = first; first = NULL; } else #if APIVERSNUM >= 20300 current = NextSuitable(Recordings->Next(current)); #else current = NextSuitable(Recordings.Next(current)); #endif return current; } const cString cRecordingsIterator::ItemRessource() const { struct stat st; if (stat(current->FileName(), &st) == 0) return cString::sprintf("%lu:%llu.rec", (unsigned long) st.st_dev, (unsigned long long) st.st_ino); return ""; } //**************************** cChannelIterator ************** cChannelIterator::cChannelIterator(const cChannel *First) { first = First; current = NULL; } bool cChannelIterator::Next() { if (first) { current = first; first = NULL; } else current = NextChannel(current); return current; } const cString cChannelIterator::ItemId() const { if (current) { if (current->GroupSep()) { int index = 0; #if APIVERSNUM >= 20300 LOCK_CHANNELS_READ; for (int curr = Channels->GetNextGroup(-1); curr >= 0; curr = Channels->GetNextGroup(curr)) { if (Channels->Get(curr) == current) #else for (int curr = Channels.GetNextGroup(-1); curr >= 0; curr = Channels.GetNextGroup(curr)) { if (Channels.Get(curr) == current) #endif return itoa(index); index++; } } else { return itoa(current->Number()); } } return cString("-1"); } const cChannel* cChannelIterator::GetGroup(const char* GroupId) { int group = -1; #if APIVERSNUM >= 20300 LOCK_CHANNELS_READ; #endif if (GroupId) { int Index = atoi(GroupId); #if APIVERSNUM >= 20300 group = Channels->GetNextGroup(-1); while (Index-- && group >= 0) group = Channels->GetNextGroup(group); } return group >= 0 ? Channels->Get(group) : NULL; #else group = Channels.GetNextGroup(-1); while (Index-- && group >= 0) group = Channels.GetNextGroup(group); } return group >= 0 ? Channels.Get(group) : NULL; #endif } const cChannel* cChannelIterator::FirstChannel() { const cChannel *Channel; #if APIVERSNUM >= 20300 LOCK_CHANNELS_READ; Channel = Channels->First(); #else Channel = Channels.First(); #endif return Channel; } const cChannel* cChannelIterator::NextNormal() { const cChannel *Channel; #if APIVERSNUM >= 20300 LOCK_CHANNELS_READ; Channel = Channels->Get(Channels->GetNextNormal(-1)); #else Channel = Channels.Get(Channels.GetNextNormal(-1)); #endif return Channel; } const cChannel* cChannelIterator::NextGroup() { const cChannel *Channel; #if APIVERSNUM >= 20300 LOCK_CHANNELS_READ; Channel = Channels->Get(Channels->GetNextGroup(-1)); #else Channel = Channels.Get(Channels.GetNextGroup(-1)); #endif return Channel; } //**************************** cListAll ************** cListAll::cListAll(): cChannelIterator(FirstChannel()) {} const cChannel* cListAll::NextChannel(const cChannel *Channel) { #if APIVERSNUM >= 20300 LOCK_CHANNELS_READ; if (Channel) Channel = SkipFakeGroups(Channels->Next(Channel)); #else if (Channel) Channel = SkipFakeGroups(Channels.Next(Channel)); #endif return Channel; } //**************************** cListChannels ************** cListChannels::cListChannels(): cChannelIterator(NextNormal()) {} const cChannel* cListChannels::NextChannel(const cChannel *Channel) { #if APIVERSNUM >= 20300 LOCK_CHANNELS_READ; if (Channel) Channel = Channels->Get(Channels->GetNextNormal(Channel->Index())); #else if (Channel) Channel = Channels.Get(Channels.GetNextNormal(Channel->Index())); #endif return Channel; } // ********************* cListGroups **************** cListGroups::cListGroups(): cChannelIterator(NextGroup()) {} const cChannel* cListGroups::NextChannel(const cChannel *Channel) { #if APIVERSNUM >= 20300 LOCK_CHANNELS_READ; if (Channel) Channel = Channels->Get(Channels->GetNextGroup(Channel->Index())); #else if (Channel) Channel = Channels.Get(Channels.GetNextGroup(Channel->Index())); #endif return Channel; } // // ********************* cListGroup **************** cListGroup::cListGroup(const char *GroupId): cChannelIterator(GetNextChannelInGroup(GetGroup(GroupId))) {} const cChannel* cListGroup::GetNextChannelInGroup(const cChannel *Channel) { #if APIVERSNUM >= 20300 LOCK_CHANNELS_READ; if (Channel) Channel = SkipFakeGroups(Channels->Next(Channel)); #else if (Channel) Channel = SkipFakeGroups(Channels.Next(Channel)); #endif return Channel && !Channel->GroupSep() ? Channel : NULL; } const cChannel* cListGroup::NextChannel(const cChannel *Channel) { return GetNextChannelInGroup(Channel); } // // ********************* cListTree **************** cListTree::cListTree(const char *SelectedGroupId): cChannelIterator(NextGroup()) { selectedGroup = GetGroup(SelectedGroupId); #if APIVERSNUM >= 20300 LOCK_CHANNELS_READ; currentGroup = Channels->Get(Channels->GetNextGroup(-1)); #else currentGroup = Channels.Get(Channels.GetNextGroup(-1)); #endif } const cChannel* cListTree::NextChannel(const cChannel *Channel) { if (currentGroup == selectedGroup) { #if APIVERSNUM >= 20300 LOCK_CHANNELS_READ; if (Channel) Channel = SkipFakeGroups(Channels->Next(Channel)); #else if (Channel) Channel = SkipFakeGroups(Channels.Next(Channel)); #endif if (Channel && Channel->GroupSep()) currentGroup = Channel; } else { #if APIVERSNUM >= 20300 LOCK_CHANNELS_READ; if (Channel) Channel = Channels->Get(Channels->GetNextGroup(Channel->Index())); #else if (Channel) Channel = Channels.Get(Channels.GetNextGroup(Channel->Index())); #endif currentGroup = Channel; } return Channel; } // ******************** cMenuList ****************** cMenuList::cMenuList(cItemIterator *Iterator) : iterator(Iterator) {} cMenuList::~cMenuList() { delete iterator; } // ******************** cHtmlMenuList ****************** const char* cHtmlMenuList::menu = "[Home (no script)] " "[Tree View] " "[Groups (Playlist | RSS)] " "[Channels (Playlist | RSS)] " "[Recordings (Playlist | RSS)] "; const char* cHtmlMenuList::css = ""; const char* cHtmlMenuList::js = ""; std::string cHtmlMenuList::StreamTypeMenu() { std::string typeMenu; typeMenu += (streamType == stTS ? (std::string) "[TS] " : (std::string) "[TS] "); #ifdef STREAMDEV_PS typeMenu += (streamType == stPS ? (std::string) "[PS] " : (std::string) "[PS] "); #endif typeMenu += (streamType == stPES ? (std::string) "[PES] " : (std::string) "[PES] "); typeMenu += (streamType == stES ? (std::string) "[ES] " : (std::string) "[ES] "); typeMenu += (streamType == stEXT ? (std::string) "[EXT] " : (std::string) "[EXT] "); return typeMenu; } cHtmlMenuList::cHtmlMenuList(cItemIterator *Iterator, eStreamType StreamType, const char *Self, const char *Rss, const char *GroupTarget): cMenuList(Iterator) { streamType = StreamType; self = strdup(Self); rss = strdup(Rss); groupTarget = (GroupTarget && *GroupTarget) ? strdup(GroupTarget) : NULL; htmlState = hsRoot; onItem = true; } cHtmlMenuList::~cHtmlMenuList() { free((void *) self); free((void *) rss); free((void *) groupTarget); } bool cHtmlMenuList::HasNext() { return htmlState != hsPageBottom; } std::string cHtmlMenuList::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: onItem = NextItem(); htmlState = onItem ? (IsGroup() ? hsGroupTop : hsPlainTop) : hsPageBottom; break; case hsPlainTop: htmlState = hsPlainItem; break; case hsPlainItem: onItem = NextItem(); htmlState = onItem && !IsGroup() ? hsPlainItem : hsPlainBottom; break; case hsPlainBottom: htmlState = onItem ? hsGroupTop : hsPageBottom; break; case hsGroupTop: onItem = NextItem(); htmlState = onItem && !IsGroup() ? hsItemsTop : hsGroupBottom; break; case hsItemsTop: htmlState = hsItem; break; case hsItem: onItem = NextItem(); htmlState = onItem && !IsGroup() ? hsItem : hsItemsBottom; break; case hsItemsBottom: htmlState = hsGroupBottom; break; case hsGroupBottom: htmlState = onItem ? hsGroupTop : hsPageBottom; break; case hsPageBottom: default: esyslog("streamdev-server cHtmlMenuList: invalid call to Next()"); break; } switch (htmlState) { // NOTE: JavaScript requirements: // Group title is identified by

tag // Channel list must be a sibling of

with class "items" case hsHtmlHead: return "" + HtmlHead(); case hsCss: return css; case hsJs: return js; case hsPageTop: return "" + PageTop() + "
"; case hsGroupTop: return "

" + GroupTitle() + "

"; case hsItemsTop: case hsPlainTop: return "
    "; case hsItem: case hsPlainItem: return ItemText(); case hsItemsBottom: case hsPlainBottom: return "
"; case hsGroupBottom: return "
"; case hsPageBottom: return "
" + PageBottom() + ""; default: return ""; } } std::string cHtmlMenuList::HtmlHead() { return (std::string) ""; } std::string cHtmlMenuList::PageTop() { return (std::string) "
" + menu + "
" + StreamTypeMenu() + "
"; } std::string cHtmlMenuList::PageBottom() { return (std::string) ""; } std::string cHtmlMenuList::GroupTitle() { if (groupTarget) { return (std::string) "" + ItemTitle() + ""; } else { return (std::string) ItemTitle(); } } std::string cHtmlMenuList::ItemText() { std::string line; std::string suffix; switch (streamType) { case stTS: suffix = (std::string) ".ts"; break; #ifdef STREAMDEV_PS case stPS: suffix = (std::string) ".vob"; break; #endif // for Network Media Tank case stPES: suffix = (std::string) ".vdr"; break; default: suffix = ""; } line += (std::string) "
  • "; line += (std::string) "" + ItemTitle() + ""; // TS always streams all PIDs if (streamType != stTS) { int index = 1; const char* lang; std::string pids; for (int i = 0; (lang = Alang(i)) != NULL; ++i, ++index) { pids += (std::string) " " + (const char*) lang + ""; } for (int i = 0; (lang = Dlang(i)) != NULL; ++i, ++index) { pids += (std::string) " " + (const char*) lang + ""; } // always show audio PIDs for stES to select audio only if (index > 2 || streamType == stES) line += pids; } line += "
  • "; return line; } // ******************** cM3uMenuList ****************** cM3uMenuList::cM3uMenuList(cItemIterator *Iterator, const char* Base) : cMenuList(Iterator), m_IConv(cCharSetConv::SystemCharacterTable(), "UTF-8") { base = strdup(Base); m3uState = msFirst; } cM3uMenuList::~cM3uMenuList() { free(base); } bool cM3uMenuList::HasNext() { return m3uState != msLast; } std::string cM3uMenuList::Next() { if (m3uState == msFirst) { m3uState = msContinue; return "#EXTM3U"; } if (!NextItem()) { m3uState = msLast; return ""; } std::string name = (std::string) m_IConv.Convert(ItemTitle()); if (IsGroup()) { return (std::string) "#EXTINF:-1," + name + "\r\n" + base + "group.m3u?group=" + (const char*) ItemId(); } else { return (std::string) "#EXTINF:-1," + (const char*) ItemId() + " " + name + "\r\n" + base + (const char*) ItemRessource(); } } // ******************** cRssMenuList ****************** cRssMenuList::cRssMenuList(cItemIterator *Iterator, const char *Base, const char *Html) : cMenuList(Iterator), m_IConv(cCharSetConv::SystemCharacterTable(), "UTF-8") { base = strdup(Base); html = strdup(Html); rssState = msFirst; } cRssMenuList::~cRssMenuList() { free(base); free(html); } bool cRssMenuList::HasNext() { return rssState != msLast; } std::string cRssMenuList::Next() { std::string type_ext; if (rssState == msFirst) { rssState = msContinue; return (std::string) "\n\n\t\n" "\t\tVDR\n" "\t\t" + base + html + "\n" "\t\tVDR channel list\n" ; } if (!NextItem()) { rssState = msLast; return "\t\n\n"; } std::string name = (std::string) m_IConv.Convert(ItemTitle()); if (IsGroup()) { return (std::string) "\t\t\n\t\t\t" + name + "\n\t\t\t" + base + "group.rss?group=" + (const char*) ItemId() + "\n\t\t\n"; } else { return (std::string) "\t\t\n\t\t\t" + (const char*) ItemId() + " " + name + "\n\t\t\t" + base + (const char*) ItemRessource() + "\n\t\t\t\n\t\t\n"; } } vdr-plugin-streamdev/server/componentIGMP.h0000644000175000017500000000344313276341255020633 0ustar tobiastobias/* * $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 #include #include #include "server/component.h" #include "../common.h" class cMulticastGroup; class cComponentIGMP: public cServerComponent, public cThread { private: char m_ReadBuffer[2048]; cList m_Groups; in_addr_t m_BindIp; int m_MaxChannelNumber; struct timeval m_GeneralQueryTimer; int m_StartupQueryCount; bool m_Querier; cCondWait m_CondWait; #if APIVERSNUM >= 20300 cMulticastGroup* FindGroup(in_addr_t Group); #else cMulticastGroup* FindGroup(in_addr_t Group) const; #endif /* 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); cServerConnection* 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 vdr-plugin-streamdev/server/server.c0000644000175000017500000001061513276341255017454 0ustar tobiastobias/* * $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 #include #include #include cSVDRPhosts StreamdevHosts; char *opt_auth = NULL; char *opt_remux = NULL; cStreamdevServer *cStreamdevServer::m_Instance = NULL; cList cStreamdevServer::m_Servers; cList 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; Lock(); m_Clients.Add(client); Unlock(); 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) { if (s->IsOpen()) s->Close(); Lock(); m_Clients.Del(s); Unlock(); } s = next; } } Lock(); while (m_Clients.Count() > 0) { cServerConnection *s = m_Clients.First(); s->Close(); m_Clients.Del(s); } Unlock(); while (m_Servers.Count() > 0) { cServerComponent *c = m_Servers.First(); c->Destruct(); m_Servers.Del(c); } } #if APIVERSNUM >= 20300 cList& cStreamdevServer::Clients(cThreadLock& Lock) #else const cList& cStreamdevServer::Clients(cThreadLock& Lock) #endif { Lock.Lock(m_Instance); return m_Clients; } vdr-plugin-streamdev/server/component.c0000644000175000017500000000224613276341255020151 0ustar tobiastobias/* * $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; } vdr-plugin-streamdev/server/suspend.h0000644000175000017500000000124113276341255017627 0ustar tobiastobias/* * $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 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 vdr-plugin-streamdev/server/menu.c0000644000175000017500000000350413276341255017111 0ustar tobiastobias/* * $Id: menu.c,v 1.10 2010/07/19 13:49:31 schmirl Exp $ */ #include #include #include #include "server/menu.h" #include "server/setup.h" #include "server/server.h" #include "server/suspend.h" cStreamdevServerMenu::cStreamdevServerMenu(): cOsdMenu(tr("Streamdev Connections"), 4, 20) { cThreadLock lock; #if APIVERSNUM >= 20300 cList& clients = cStreamdevServer::Clients(lock); #else const cList& clients = cStreamdevServer::Clients(lock); #endif for (cServerConnection *s = clients.First(); s; s = clients.Next(s)) Add(new cOsdItem(s->ToText('\t'))); SetHelpKeys(); Display(); } cStreamdevServerMenu::~cStreamdevServerMenu() { } void cStreamdevServerMenu::SetHelpKeys() { SetHelp(Count() ? tr("Disconnect") : NULL, NULL, NULL, tr("Suspend")); } eOSState cStreamdevServerMenu::Disconnect() { cOsdItem *item = Get(Current()); if (item) { cThreadLock lock; #if APIVERSNUM >= 20300 cList& clients = cStreamdevServer::Clients(lock); #else const cList& clients = cStreamdevServer::Clients(lock); #endif const char *text = item->Text(); for (cServerConnection *s = clients.First(); s; s = clients.Next(s)) { if (!strcmp(text, s->ToText('\t'))) { s->Close(); Del(Current()); SetHelpKeys(); Display(); break; } } } return osContinue; } eOSState cStreamdevServerMenu::Suspend() { if (!cSuspendCtl::IsActive()) { cControl::Launch(new cSuspendCtl); return osBack; } return osContinue; } eOSState cStreamdevServerMenu::ProcessKey(eKeys Key) { eOSState state = cOsdMenu::ProcessKey(Key); if (state == osUnknown) { switch (Key) { case kRed: return Disconnect(); case kBlue: return Suspend(); case kOk: return osBack; default: break; } } return state; } vdr-plugin-streamdev/server/streamdev-server.c0000644000175000017500000001513213276341255021443 0ustar tobiastobias/* * 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.2 2010/07/19 13:49:32 schmirl Exp $ */ #include #include #include "streamdev-server.h" #include "server/menu.h" #include "server/setup.h" #include "server/server.h" #include "server/suspend.h" #if !defined(APIVERSNUM) || APIVERSNUM < 10725 #error "VDR-1.7.25 or greater required to compile server! Use 'make client' to compile client only." #endif cList cMainThreadHookSubscriber::m_Subscribers; cMutex cMainThreadHookSubscriber::m_Mutex; #if APIVERSNUM >= 20300 cList& cMainThreadHookSubscriber::Subscribers(cMutexLock& Lock) #else const cList& cMainThreadHookSubscriber::Subscribers(cMutexLock& Lock) #endif { Lock.Lock(&m_Mutex); return m_Subscribers; } cMainThreadHookSubscriber::cMainThreadHookSubscriber() { m_Mutex.Lock(); m_Subscribers.Add(this); m_Mutex.Unlock(); } cMainThreadHookSubscriber::~cMainThreadHookSubscriber() { m_Mutex.Lock(); m_Subscribers.Del(this, false); m_Mutex.Unlock(); } const char *cPluginStreamdevServer::DESCRIPTION = trNOOP("VDR Streaming Server"); cPluginStreamdevServer::cPluginStreamdevServer(void) { m_Suspend = false; } 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 , --auth= Credentials for HTTP authentication.\n" " -r , --remux= 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(); m_Suspend = StreamdevServerSetup.StartSuspended == ssAuto ? !cDevice::PrimaryDevice()->HasDecoder() : StreamdevServerSetup.StartSuspended; 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) { return !StreamdevServerSetup.HideMenuEntry ? tr("Streamdev Connections") : NULL; } cOsdObject *cPluginStreamdevServer::MainMenuAction(void) { return new cStreamdevServerMenu(); } void cPluginStreamdevServer::MainThreadHook(void) { if (m_Suspend) { cControl::Launch(new cSuspendCtl); m_Suspend = false; } cMutexLock lock; #if APIVERSNUM >= 20300 cList& subs = cMainThreadHookSubscriber::Subscribers(lock); #else const cList& subs = cMainThreadHookSubscriber::Subscribers(lock); #endif for (cMainThreadHookSubscriber *s = subs.First(); s; s = subs.Next(s)) s->MainThreadHook(); } cMenuSetupPage *cPluginStreamdevServer::SetupMenu(void) { return new cStreamdevServerMenuSetupPage; } bool cPluginStreamdevServer::SetupParse(const char *Name, const char *Value) { return StreamdevServerSetup.SetupParse(Name, Value); } const char **cPluginStreamdevServer::SVDRPHelpPages(void) { static const char *HelpPages[]= { "LSTC\n" " List connected clients\n", "DISC client_id\n" " Disconnect a client\n", NULL }; return HelpPages; } cString cPluginStreamdevServer::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode) { cString reply = NULL; if (!strcasecmp(Command, "LSTC")) { reply = ""; cThreadLock lock; #if APIVERSNUM >= 20300 cList& clients = cStreamdevServer::Clients(lock); #else const cList& clients = cStreamdevServer::Clients(lock); #endif cServerConnection *s = clients.First(); if (!s) { reply = "no client connected"; ReplyCode = 550; } else { for (; s; s = clients.Next(s)) { reply = cString::sprintf("%s%p: %s\n", (const char*) reply, s, (const char *) s->ToText()); } ReplyCode = 250; } } else if (!strcasecmp(Command, "DISC")) { void *client = NULL; if (sscanf(Option, "%p", &client) != 1 || !client) { reply = "invalid client handle"; ReplyCode = 501; } else { cThreadLock lock; #if APIVERSNUM >= 20300 cList& clients = cStreamdevServer::Clients(lock); #else const cList& clients = cStreamdevServer::Clients(lock); #endif cServerConnection *s = clients.First(); for (; s && s != client; s = clients.Next(s)); if (!s) { reply = "client not found"; ReplyCode = 501; } else { s->Close(); reply = "client disconnected"; ReplyCode = 250; } } } return reply; } bool cPluginStreamdevServer::Service(const char *Id, void *Data) { if (strcmp(Id, "StreamdevServer::ClientCount-v1.0") == 0) { if (Data) { int *count = (int *) Data; cThreadLock lock; *count = cStreamdevServer::Clients(lock).Count(); } return true; } return false; } VDRPLUGINCREATOR(cPluginStreamdevServer); // Don't touch this! vdr-plugin-streamdev/server/recplayer.c0000644000175000017500000002116313276341255020134 0ustar tobiastobias/* 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" // for TSPLAY patch detection #include "vdr/device.h" #undef _XOPEN_SOURCE #define _XOPEN_SOURCE 600 #include RecPlayer::RecPlayer(const char* FileName) { file = NULL; fileOpen = 0; lastPosition = 0; recording = new cRecording(FileName); for(int i = 1; i < 1000; i++) segments[i] = NULL; // FIXME find out max file path / name lengths indexFile = new cIndexFile(recording->FileName(), false, recording->IsPesRecording()); if (!indexFile) esyslog("ERROR: Streamdev: Failed to create indexfile!"); scan(); parser = new cPatPmtParser(); unsigned char buffer[2 * TS_SIZE]; unsigned long l = getBlock(buffer, 0UL, sizeof(buffer)); if (!l || !parser->ParsePatPmt(buffer, (int) l)) esyslog("ERROR: Streamdev: Failed to parse PAT/PMT"); } 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++) { 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"); } if (!file) break; segments[i] = new Segment(); segments[i]->start = totalLength; fseek(file, 0, SEEK_END); totalLength += ftello(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); delete indexFile; delete recording; delete parser; } int RecPlayer::openFile(int index) { if (file) fclose(file); char fileName[2048]; 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; } 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; uint64_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(fileno(file), 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; } #if VDRVERSNUM < 10732 #define ALIGNED_POS(x) (positionFromFrameNumber(indexFile->GetNextIFrame(x, 1))) #else #define ALIGNED_POS(x) (positionFromFrameNumber(indexFile->GetClosestIFrame(x))) #endif uint64_t RecPlayer::positionFromResume(int ResumeID) { int resumeBackup = Setup.ResumeID; Setup.ResumeID = ResumeID; cResumeFile resume(recording->FileName(), recording->IsPesRecording()); Setup.ResumeID = resumeBackup; return ALIGNED_POS(resume.Read()); } uint64_t RecPlayer::positionFromMark(int MarkIndex) { cMarks marks; if (marks.Load(recording->FileName(), recording->FramesPerSecond(), recording->IsPesRecording()) && marks.Count()) { cMark *mark = marks.cConfig::Get(MarkIndex); if (mark) return ALIGNED_POS(mark->Position()); } return 0; } uint64_t RecPlayer::positionFromTime(int Seconds) { return ALIGNED_POS(SecondsToFrames(Seconds, recording->FramesPerSecond())); } uint64_t RecPlayer::positionFromPercent(int Percent) { return ALIGNED_POS(getLengthFrames() * Percent / 100L); } uint64_t RecPlayer::positionFromFrameNumber(uint32_t frameNumber) { if (!indexFile) return 0; uint16_t retFileNumber; off_t retFileOffset; 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 } uint64_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; } vdr-plugin-streamdev/server/streamer.c0000644000175000017500000001025513276341255017770 0ustar tobiastobias/* * $Id: streamer.c,v 1.21 2010/07/30 10:01:11 schmirl Exp $ */ #include #include #include #include #include "server/streamer.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; int timeout = 0; SetPriority(-3); sel.Clear(); sel.Add(*m_Socket, true); while (Running()) { if (block == NULL) { block = m_Streamer->Get(count); offset = 0; // still no data - are we done? if (block == NULL && !m_Streamer->IsReceiving() && timeout++ > 20) { esyslog("streamdev-server: streamer done - writer exiting"); break; } } if (block != NULL) { if (sel.Select(600) == -1) { if (errno == ETIMEDOUT && timeout++ < 20) continue; // still Running()? esyslog("ERROR: streamdev-server: couldn't send data: %m"); break; } timeout = 0; 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; } } } } m_Socket->Close(); Dprintf("Max. Transmit Blocksize was: %d\n", max); } // --- cRemuxDummy ------------------------------------------------------------ class cRemuxDummy: public Streamdev::cTSRemux { private: cStreamdevBuffer m_Buffer; public: cRemuxDummy(); virtual int Put(const uchar *Data, int Count) { return m_Buffer.Put(Data, Count); } virtual uchar *Get(int& Count) { return m_Buffer.Get(Count); } virtual void Del(int Count) { return m_Buffer.Del(Count); } }; cRemuxDummy::cRemuxDummy(): m_Buffer(WRITERBUFSIZE, TS_SIZE * 2) { m_Buffer.SetTimeouts(100, 100); } // --- cStreamdevStreamer ----------------------------------------------------- cStreamdevStreamer::cStreamdevStreamer(const char *Name, const cServerConnection *Connection): cThread(Name), m_Connection(Connection), m_Remux(new cRemuxDummy()), m_Writer(NULL) { } cStreamdevStreamer::~cStreamdevStreamer() { Dprintf("Desctructing streamer\n"); delete m_Remux; } void cStreamdevStreamer::Start(cTBSocket *Socket) { Dprintf("start writer\n"); m_Writer = new cStreamdevWriter(Socket, this); m_Writer->Start(); if (!Active()) { Dprintf("start streamer\n"); cThread::Start(); } Attach(); } void cStreamdevStreamer::Stop(void) { Detach(); if (Running()) { Dprintf("stop streamer\n"); Cancel(3); } Dprintf("stop writer\n"); DELETENULL(m_Writer); } void cStreamdevStreamer::Action(void) { SetPriority(-3); while (Running()) { int got; uchar *block = GetFromReceiver(got); if (block) { int count = Put(block, got); if (count) DelFromReceiver(count); } } } int cStreamdevStreamer::Put(const uchar *Data, int Count) { return m_Remux->Put(Data, Count); } vdr-plugin-streamdev/server/connectionVTP.h0000644000175000017500000000530613276341255020705 0ustar tobiastobias#ifndef VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H #define VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H #include "server/connection.h" #include "server/recplayer.h" class cTBSocket; 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; cTBSocket *m_FilterSocket; cStreamdevFilterStreamer *m_FilterStreamer; cTBSocket *m_RecSocket; cTBSocket *m_DataSocket; char *m_LastCommand; eStreamType m_StreamType; unsigned int m_ClientVersion; bool m_FiltersSupport; bool m_LoopPrevention; RecPlayer *m_RecPlayer; // Priority is only known in PROV command // Store in here for later use in TUNE call const cChannel *m_TuneChannel; int m_TunePriority; // Members adopted for SVDRP cLSTEHandler *m_LSTEHandler; cLSTCHandler *m_LSTCHandler; cLSTTHandler *m_LSTTHandler; cLSTRHandler *m_LSTRHandler; protected: template bool CmdLSTX(cHandler *&Handler, char *Option); public: cConnectionVTP(void); virtual ~cConnectionVTP(); virtual void Welcome(void); virtual void Reject(void); virtual cString ToText(char Delimiter = ' ') const; virtual bool Abort(void) const; virtual void Detach(void); virtual void Attach(void); virtual bool Command(char *Cmd); bool CmdCAPS(char *Opts); bool CmdVERS(char *Opts); bool CmdPROV(char *Opts); bool CmdPORT(char *Opts); bool CmdREAD(char *Opts); bool CmdTUNE(char *Opts); bool CmdPLAY(char *Opts); bool CmdPRIO(char *Opts); bool CmdSGNL(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 vdr-plugin-streamdev/server/recstreamer.c0000644000175000017500000000441013276341255020456 0ustar tobiastobias#include "remux/ts2ps.h" #include "remux/ts2pes.h" #include "remux/ts2es.h" #include "remux/extern.h" #include #include "server/recstreamer.h" #include "server/connection.h" #include "common.h" using namespace Streamdev; // --- cStreamdevRecStreamer ------------------------------------------------- cStreamdevRecStreamer::cStreamdevRecStreamer(const cServerConnection *Connection, RecPlayer *RecPlayer, eStreamType StreamType, int64_t StartOffset, const int *Apid, const int *Dpid): cStreamdevStreamer("streamdev-recstreaming", Connection), m_RecPlayer(RecPlayer), m_StartOffset(StartOffset), m_From(0L) { Dprintf("New rec streamer\n"); m_To = (int64_t) m_RecPlayer->getLengthBytes() - StartOffset - 1; const cPatPmtParser *parser = RecPlayer->getPatPmtData(); const int *Apids = Apid ? Apid : parser->Apids(); const int *Dpids = Dpid ? Dpid : parser->Dpids(); switch (StreamType) { case stES: { int pid = parser->Vpid(); if (Apid && Apid[0]) pid = Apid[0]; else if (Dpid && Dpid[0]) pid = Dpid[0]; SetRemux(new cTS2ESRemux(pid)); } break; case stPES: if (!m_RecPlayer->getCurrentRecording()->IsPesRecording()) SetRemux(new cTS2PESRemux(parser->Vpid(), Apids, Dpids, parser->Spids())); break; #ifdef STREAMDEV_PS case stPS: SetRemux(new cTS2PSRemux(parser->Vpid(), Apids, Dpids, parser->Spids())); break; #endif case stEXT: SetRemux(new cExternRemux(Connection, parser, Apids, Dpids)); break; default: break; } } cStreamdevRecStreamer::~cStreamdevRecStreamer() { Dprintf("Desctructing rec streamer\n"); Stop(); } int64_t cStreamdevRecStreamer::SetRange(int64_t &From, int64_t &To) { int64_t l = (int64_t) GetLength(); if (From < 0L) { From += l; if (From < 0L) From = 0L; To = l - 1; } else { if (To < 0L) To += l; else if (To >= l) To = l - 1; if (From > To) { // invalid range - return whole content From = 0L; To = l - 1; } } m_From = From; m_To = To; return m_To - m_From + 1; } uchar* cStreamdevRecStreamer::GetFromReceiver(int &Count) { if (m_From <= m_To) { Count = (int) m_RecPlayer->getBlock(m_Buffer, m_StartOffset + m_From, sizeof(m_Buffer)); return m_Buffer; } return NULL; } cString cStreamdevRecStreamer::ToText() const { return "REPLAY"; } vdr-plugin-streamdev/server/component.h0000644000175000017500000000266413276341255020162 0ustar tobiastobias/* * $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 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 vdr-plugin-streamdev/server/componentHTTP.h0000644000175000017500000000054313276341255020654 0ustar tobiastobias/* * $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 vdr-plugin-streamdev/server/componentIGMP.c0000644000175000017500000003263213276341255020630 0ustar tobiastobias/* * $Id: componentIGMP.c,v 1.2 2009/07/03 21:44:19 schmirl Exp $ */ #include #include #include "server/componentIGMP.h" #include "server/connectionIGMP.h" #include "server/server.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: 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) : 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) { } #if APIVERSNUM >= 20300 cMulticastGroup* cComponentIGMP::FindGroup(in_addr_t Group) #else cMulticastGroup* cComponentIGMP::FindGroup(in_addr_t Group) const #endif { 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)) { #if APIVERSNUM >= 20300 LOCK_CHANNELS_READ; for (const cChannel *channel = Channels->First(); channel; channel = Channels->Next(channel)) #else for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) #endif { 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(-1); m_CondWait.Signal(); Cancel(2); #if APIVERSNUM >= 20300 LOCK_CHANNELS_READ; for (const cChannel *channel = Channels->First(); channel; channel = Channels->Next(channel)) #else for (cChannel *channel = Channels.First(); channel; channel = Channels.Next(channel)) #endif { 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); } conn = IGMPStartMulticast(group); 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); } cServerConnection* cComponentIGMP::IGMPStartMulticast(cMulticastGroup* Group) { cServerConnection *conn = NULL; in_addr_t g = ntohl(Group->group); if (g > MULTICAST_PRIV_MIN && g <= MULTICAST_PRIV_MAX) { cThreadLock lock; #if APIVERSNUM >= 20300 LOCK_CHANNELS_READ; const cChannel *channel = Channels->GetByNumber(g - MULTICAST_PRIV_MIN); #else cChannel *channel = Channels.GetByNumber(g - MULTICAST_PRIV_MIN); #endif const cList& clients = cStreamdevServer::Clients(lock); #if APIVERSNUM >= 20300 const cServerConnection *s = clients.First(); #else cServerConnection *s = clients.First(); #endif while (s) { if (s->RemoteIpAddr() == Group->group) break; s = clients.Next(s); } if (!s) { conn = NewClient(); if (!((cConnectionIGMP *)conn)->SetChannel(channel, Group->group)) { DELETENULL(conn); } } } return conn; } void cComponentIGMP::IGMPStopMulticast(cMulticastGroup* Group) { cThreadLock lock; #if APIVERSNUM >= 20300 cList& clients = cStreamdevServer::Clients(lock); #else const cList& clients = cStreamdevServer::Clients(lock); #endif for (cServerConnection *s = clients.First(); s; s = clients.Next(s)) { if (s->RemoteIpAddr() == Group->group) s->Close(); } } vdr-plugin-streamdev/server/connection.h0000644000175000017500000000755313276341255020321 0ustar tobiastobias/* * $Id: connection.h,v 1.10 2010/08/03 10:46:41 schmirl Exp $ */ #ifndef VDR_STREAMDEV_SERVER_CONNECTION_H #define VDR_STREAMDEV_SERVER_CONNECTION_H #include "tools/socket.h" #include "common.h" #include "server/streamer.h" #include #include typedef std::map tStrStrMap; typedef std::pair tStrStr; class cChannel; /* 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; cStreamdevStreamer *m_Streamer; tStrStrMap m_Headers; 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))); /* Add a request header */ void SetHeader(const char *Name, const char *Value, const char *Prefix = "") { m_Headers.insert(tStrStr(std::string(Prefix) + Name, Value)); } /* Set the streamer */ void SetStreamer(cStreamdevStreamer* Streamer) { delete m_Streamer; m_Streamer = Streamer; } /* Return the streamer */ cStreamdevStreamer *Streamer() const { return m_Streamer; } static const cChannel *ChannelFromString(const char *String, int *Apid = NULL, int *Dpid = 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; } /* Close the socket */ virtual bool Close(void); virtual void Flushed(void) {} /* This connections protocol name */ virtual const char* Protocol(void) const { return m_Protocol; } /* Text description of stream */ virtual cString ToText(char Delimiter = ' ') const; /* std::map with additional information */ const tStrStrMap& Headers(void) const { return m_Headers; } }; inline bool cServerConnection::HasData(void) const { return m_WriteBytes > 0 || m_Pending || m_DeferClose; } #endif // VDR_STREAMDEV_SERVER_CONNECTION_H vdr-plugin-streamdev/server/menu.h0000644000175000017500000000070213276341255017113 0ustar tobiastobias/* * $Id: menu.h,v 1.4 2010/07/19 13:49:31 schmirl Exp $ */ #ifndef VDR_STREAMDEV_MENU_H #define VDR_STREAMDEV_MENU_H #include #include "connection.h" class cStreamdevServerMenu: public cOsdMenu { private: void SetHelpKeys(); eOSState Disconnect(); eOSState Suspend(); protected: virtual eOSState ProcessKey(eKeys Key); public: cStreamdevServerMenu(); virtual ~cStreamdevServerMenu(); }; #endif // VDR_STREAMDEV_MENU_H vdr-plugin-streamdev/server/connectionHTTP.h0000644000175000017500000000423513276341255021013 0ustar tobiastobias/* * $Id: connectionHTTP.h,v 1.7 2010/07/19 13:49:31 schmirl Exp $ */ #ifndef VDR_STREAMDEV_SERVERS_CONNECTIONHTTP_H #define VDR_STREAMDEV_SERVERS_CONNECTIONHTTP_H #include "connection.h" #include "server/livestreamer.h" #include "server/recstreamer.h" #include #include class cChannel; class cMenuList; class cConnectionHTTP: public cServerConnection { private: enum eHTTPStatus { hsRequest, hsHeaders, hsBody, hsFinished, }; std::string m_Authorization; eHTTPStatus m_Status; tStrStrMap m_Params; eStreamType m_StreamType; // job: transfer const cChannel *m_Channel; int m_Apid[2]; int m_Dpid[2]; // job: replay RecPlayer *m_RecPlayer; int64_t m_ReplayPos; bool m_ReplayFakeRange; // job: listing cMenuList *m_MenuList; cMenuList* MenuListFromString(const std::string &PathInfo, const std::string &Filebase, const std::string &Fileext) const; RecPlayer* RecPlayerFromString(const char* FileBase, const char* FileExt); bool ProcessURI(const std::string &PathInfo); bool HttpResponse(int Code, bool Last, const char* ContentType = NULL, const char* Headers = "", ...); //__attribute__ ((format (printf, 5, 6))); /** * Extract byte range from HTTP Range header. Returns false if no valid * range is found. The contents of From and To are undefined in this * case. From may be negative in which case To is undefined. * TODO: support for multiple ranges. */ bool ParseRange(int64_t &From, int64_t &To) const; protected: bool ProcessRequest(void); public: cConnectionHTTP(void); virtual ~cConnectionHTTP(); virtual cString ToText(char Delimiter = ' ') const; virtual bool CanAuthenticate(void); virtual bool Command(char *Cmd); virtual bool Abort(void) const; virtual void Flushed(void); }; inline bool cConnectionHTTP::Abort(void) const { return !IsOpen() || (Streamer() && Streamer()->Abort()); } #endif // VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H vdr-plugin-streamdev/server/livestreamer.h0000644000175000017500000000536213276341255020660 0ustar tobiastobias#ifndef VDR_STREAMDEV_LIVESTREAMER_H #define VDR_STREAMDEV_LIVESTREAMER_H #include #include #include #include "server/streamer.h" #include "server/streamdev-server.h" #include "common.h" #define LIVEBUFSIZE (20000 * TS_SIZE) class cStreamdevPatFilter; class cStreamdevLiveReceiver; // --- cStreamdevLiveStreamer ------------------------------------------------- class cStreamdevLiveStreamer: public cStreamdevStreamer, public cMainThreadHookSubscriber #if VDRVERSNUM >= 20104 , public cStatus #endif { private: int m_Priority; int m_Pids[MAXRECEIVEPIDS + 1]; int m_NumPids; int m_Caids[MAXCAIDS + 1]; const cChannel *m_Channel; cDevice *m_Device; cStreamdevLiveReceiver *m_Receiver; cStreamdevBuffer *m_ReceiveBuffer; cStreamdevPatFilter *m_PatFilter; bool m_SwitchLive; void StartReceiver(bool Force = false); bool HasPid(int Pid); /* Test if device is in use as the transfer mode receiver device or a FF card, displaying live TV from internal tuner */ static bool UsedByLiveTV(cDevice *device); /* Find a suitable device and tune it to the requested channel. */ cDevice *SwitchDevice(const cChannel *Channel, int Priority); bool SetChannel(eStreamType StreamType, const int* Apid = NULL, const int* Dpid = NULL); protected: virtual uchar* GetFromReceiver(int &Count) { return m_ReceiveBuffer->Get(Count); } virtual void DelFromReceiver(int Count) { m_ReceiveBuffer->Del(Count); } virtual int Put(const uchar *Data, int Count); virtual void Action(void); virtual void ChannelChange(const cChannel *Channel); public: cStreamdevLiveStreamer(const cServerConnection *Connection, const cChannel *Channel, int Priority, eStreamType StreamType, const int* Apid = NULL, const int* Dpid = NULL); virtual ~cStreamdevLiveStreamer(); bool SetPid(int Pid, bool On); bool SetPids(int Pid, const int *Pids1 = NULL, const int *Pids2 = NULL, const int *Pids3 = NULL); void SetPriority(int Priority); void GetSignal(int *DevNum, int *Strength, int *Quality) const; virtual cString ToText() const; #if APIVERSNUM >= 20300 void Receive(const uchar *Data, int Length); #else void Receive(uchar *Data, int Length); #endif virtual bool IsReceiving(void) const; virtual void Attach(void); virtual void Detach(void); cDevice *GetDevice() const { return m_Device; } /* Test if a call to GetDevice would return a usable device. */ static bool ProvidesChannel(const cChannel *Channel, int Priority); /* Do things which must be done in VDR's main loop */ void MainThreadHook(); // Statistical purposes: virtual std::string Report(void); }; #endif // VDR_STREAMDEV_LIVESTREAMER_H vdr-plugin-streamdev/server/Makefile0000644000175000017500000000451313276341255017442 0ustar tobiastobias# # Makefile for a Video Disk Recorder plugin # # $Id: $ # 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-server ### The name of the shared object file: SOFILE = libvdr-$(PLUGIN).so ### Includes and Defines (add further entries here): DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"' ### The object files (add further files here): COMMONOBJS = ../common.o SERVEROBJS = $(PLUGIN).o \ server.o component.o connection.o \ componentVTP.o connectionVTP.o \ componentHTTP.o connectionHTTP.o menuHTTP.o \ componentIGMP.o connectionIGMP.o \ streamer.o livestreamer.o livefilter.o recstreamer.o recplayer.o \ menu.o suspend.o setup.o ### The main target: all: $(SOFILE) i18n ### Implicit rules: %.o: %.c $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $< ### Dependencies: MAKEDEP = $(CXX) -MM -MG DEPFILE = .dependencies $(DEPFILE): Makefile @$(MAKEDEP) $(CXXFLAGS) $(DEFINES) $(INCLUDES) $(SERVEROBJS:%.o=%.c) $(COMMONOBJS:%.o=%.c) > $@ -include $(DEPFILE) ### Internationalization (I18N): PODIR = po I18Npo = $(wildcard $(PODIR)/*.po) I18Nmo = $(addsuffix .mo, $(foreach file, $(I18Npo), $(basename $(file)))) I18Nmsgs = $(addprefix $(DESTDIR)$(LOCDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file)))))) I18Npot = $(PODIR)/$(PLUGIN).pot %.mo: %.po msgfmt -c -o $@ $< $(I18Npot): $(SERVEROBJS:%.o=%.c) xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --package-name=vdr-$(PLUGIN) --package-version=$(VERSION) --msgid-bugs-address='' -o $@ `ls $^` %.po: $(I18Npot) msgmerge -U --no-wrap --no-location --backup=none -q -N $@ $< @touch $@ $(I18Nmsgs): $(DESTDIR)$(LOCDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo install -D -m644 $< $@ .PHONY: i18n i18n: $(I18Nmo) $(I18Npot) install-i18n: $(I18Nmsgs) ### Targets: $(SOFILE): $(SERVEROBJS) $(COMMONOBJS) \ ../tools/sockettools.a ../remux/remux.a ../libdvbmpeg/libdvbmpegtools.a $(CXX) $(CXXFLAGS) $(LDFLAGS) -shared $^ -o $@ install-lib: $(SOFILE) install -D $^ $(DESTDIR)$(LIBDIR)/$^.$(APIVERSION) install: install-lib install-i18n clean: @-rm -f $(PODIR)/*.mo $(PODIR)/*.pot @-rm -f $(COMMONOBJS) $(SERVEROBJS) $(DEPFILE) *.so *.tgz core* *~ vdr-plugin-streamdev/server/setup.c0000644000175000017500000001650613276341255017313 0ustar tobiastobias/* * $Id: setup.c,v 1.10 2010/07/19 13:49:31 schmirl Exp $ */ #include #include "server/setup.h" #include "server/server.h" cStreamdevServerSetup StreamdevServerSetup; cStreamdevServerSetup::cStreamdevServerSetup(void) { HideMenuEntry = false; MaxClients = 5; StartSuspended = ssAuto; LiveBufferMs = 0; StartVTPServer = true; VTPServerPort = 2004; VTPPriority = 0; LoopPrevention = false; StartHTTPServer = true; HTTPServerPort = 3000; HTTPPriority = 0; HTTPStreamType = stTS; StartIGMPServer = false; IGMPClientPort = 1234; IGMPPriority = 0; IGMPStreamType = stTS; 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, "HideMenuEntry") == 0) HideMenuEntry = atoi(Value); else if (strcmp(Name, "MaxClients") == 0) MaxClients = atoi(Value); else if (strcmp(Name, "StartSuspended") == 0) StartSuspended = atoi(Value); else if (strcmp(Name, "LiveBufferMs") == 0) LiveBufferMs = atoi(Value); else if (strcmp(Name, "StartServer") == 0) StartVTPServer = atoi(Value); else if (strcmp(Name, "ServerPort") == 0) VTPServerPort = atoi(Value); else if (strcmp(Name, "VTPPriority") == 0) VTPPriority = atoi(Value); else if (strcmp(Name, "VTPBindIP") == 0) strcpy(VTPBindIP, Value); else if (strcmp(Name, "LoopPrevention") == 0) LoopPrevention = atoi(Value); else if (strcmp(Name, "StartHTTPServer") == 0) StartHTTPServer = atoi(Value); else if (strcmp(Name, "HTTPServerPort") == 0) HTTPServerPort = atoi(Value); else if (strcmp(Name, "HTTPPriority") == 0) HTTPPriority = 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, "IGMPPriority") == 0) IGMPPriority = atoi(Value); else if (strcmp(Name, "IGMPStreamType") == 0) IGMPStreamType = atoi(Value); else if (strcmp(Name, "IGMPBindIP") == 0) strcpy(IGMPBindIP, 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", "EXT" }; cStreamdevServerMenuSetupPage::cStreamdevServerMenuSetupPage(void) { m_NewSetup = StreamdevServerSetup; Set(); } cStreamdevServerMenuSetupPage::~cStreamdevServerMenuSetupPage() { } void cStreamdevServerMenuSetupPage::Set(void) { static const char *StartSuspendedItems[ss_Count] = { trVDR("no"), trVDR("yes"), trVDR("auto") }; int current = Current(); Clear(); AddCategory (tr("Common Settings")); Add(new cMenuEditBoolItem(tr("Hide Mainmenu Entry"), &m_NewSetup.HideMenuEntry)); Add(new cMenuEditStraItem(tr("Start with Live TV suspended"), &m_NewSetup.StartSuspended, ss_Count, StartSuspendedItems)); Add(new cMenuEditIntItem (tr("Maximum Number of Clients"), &m_NewSetup.MaxClients, 0, 100)); Add(new cMenuEditIntItem (tr("Live TV buffer delay (ms)"), &m_NewSetup.LiveBufferMs, 0, 1500)); 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)); Add(new cMenuEditIntItem (tr("Legacy Client Priority"), &m_NewSetup.VTPPriority, MINPRIORITY, MAXPRIORITY)); Add(new cMenuEditBoolItem(tr("Client may suspend"), &m_NewSetup.AllowSuspend)); if (cPluginManager::CallFirstService(LOOP_PREVENTION_SERVICE)) Add(new cMenuEditBoolItem(tr("Loop Prevention"), &m_NewSetup.LoopPrevention)); 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 cMenuEditIpItem (tr("Bind to IP"), m_NewSetup.HTTPBindIP)); Add(new cMenuEditIntItem (tr("Priority"), &m_NewSetup.HTTPPriority, MINPRIORITY, MAXPRIORITY)); Add(new cMenuEditStraItem(tr("HTTP Streamtype"), &m_NewSetup.HTTPStreamType, st_Count - 1, StreamTypes)); 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 cMenuEditIpItem (tr("Bind to IP"), m_NewSetup.IGMPBindIP)); Add(new cMenuEditIntItem (tr("Priority"), &m_NewSetup.IGMPPriority, MINPRIORITY, MAXPRIORITY)); Add(new cMenuEditStraItem(tr("Multicast Streamtype"), &m_NewSetup.IGMPStreamType, st_Count - 1, StreamTypes)); SetCurrent(Get(current)); Display(); } 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("HideMenuEntry", m_NewSetup.HideMenuEntry); SetupStore("MaxClients", m_NewSetup.MaxClients); SetupStore("StartSuspended", m_NewSetup.StartSuspended); SetupStore("LiveBufferMs", m_NewSetup.LiveBufferMs); SetupStore("StartServer", m_NewSetup.StartVTPServer); SetupStore("ServerPort", m_NewSetup.VTPServerPort); SetupStore("VTPBindIP", m_NewSetup.VTPBindIP); SetupStore("VTPPriority", m_NewSetup.VTPPriority); SetupStore("LoopPrevention", m_NewSetup.LoopPrevention); SetupStore("StartHTTPServer", m_NewSetup.StartHTTPServer); SetupStore("HTTPServerPort", m_NewSetup.HTTPServerPort); SetupStore("HTTPBindIP", m_NewSetup.HTTPBindIP); SetupStore("HTTPPriority", m_NewSetup.HTTPPriority); SetupStore("HTTPStreamType", m_NewSetup.HTTPStreamType); SetupStore("StartIGMPServer", m_NewSetup.StartIGMPServer); SetupStore("IGMPClientPort", m_NewSetup.IGMPClientPort); SetupStore("IGMPBindIP", m_NewSetup.IGMPBindIP); SetupStore("IGMPPriority", m_NewSetup.IGMPPriority); SetupStore("IGMPStreamType", m_NewSetup.IGMPStreamType); SetupStore("AllowSuspend", m_NewSetup.AllowSuspend); StreamdevServerSetup = m_NewSetup; if (restart) cStreamdevServer::Initialize(); } vdr-plugin-streamdev/server/Makefile-1.7.330000644000175000017500000000417113276341255020171 0ustar tobiastobias# # Makefile for a Video Disk Recorder plugin # # $Id: Makefile,v 1.2 2010/07/19 13:49:31 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-server ### Includes and Defines (add further entries here): DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"' ### The object files (add further files here): COMMONOBJS = ../common.o SERVEROBJS = $(PLUGIN).o \ server.o component.o connection.o \ componentVTP.o connectionVTP.o \ componentHTTP.o connectionHTTP.o menuHTTP.o \ componentIGMP.o connectionIGMP.o \ streamer.o livestreamer.o livefilter.o recstreamer.o recplayer.o \ menu.o suspend.o setup.o ### The main target: .PHONY: all i18n clean all: libvdr-$(PLUGIN).so i18n ### Implicit rules: %.o: %.c $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $< ### Dependencies: MAKEDEP = $(CXX) -MM -MG DEPFILE = .dependencies $(DEPFILE): Makefile @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(SERVEROBJS:%.o=%.c) $(COMMONOBJS:%.o=%.c) > $@ -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): $(SERVEROBJS:%.o=%.c) xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --msgid-bugs-address='' -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: libvdr-$(PLUGIN).so: $(SERVEROBJS) $(COMMONOBJS) \ ../tools/sockettools.a ../remux/remux.a ../libdvbmpeg/libdvbmpegtools.a %.so: $(CXX) $(CXXFLAGS) -shared $^ -o $@ @cp --remove-destination $@ $(LIBDIR)/$@.$(APIVERSION) clean: @-rm -f $(COMMONOBJS) $(SERVEROBJS) $(DEPFILE) $(PODIR)/*.mo $(PODIR)/*.pot *.so *.tgz core* *~ vdr-plugin-streamdev/server/recstreamer.h0000644000175000017500000000222713276341255020467 0ustar tobiastobias#ifndef VDR_STREAMDEV_RECSTREAMER_H #define VDR_STREAMDEV_RECSTREAMER_H #include "common.h" #include "server/streamer.h" #include "server/recplayer.h" #define RECBUFSIZE (174 * TS_SIZE) // --- cStreamdevRecStreamer ------------------------------------------------- class cStreamdevRecStreamer: public cStreamdevStreamer { private: //Streamdev::cTSRemux *m_Remux; RecPlayer *m_RecPlayer; int64_t m_StartOffset; int64_t m_From; int64_t m_To; uchar m_Buffer[RECBUFSIZE]; protected: virtual uchar* GetFromReceiver(int &Count); virtual void DelFromReceiver(int Count) { m_From += Count; }; public: virtual bool IsReceiving(void) const { return m_From <= m_To; }; uint64_t GetLength() { return m_RecPlayer->getLengthBytes() - m_StartOffset; } int64_t SetRange(int64_t &From, int64_t &To); virtual cString ToText() const; cStreamdevRecStreamer(const cServerConnection *Connection, RecPlayer *RecPlayer, eStreamType StreamType, int64_t StartOffset = 0L, const int *Apids = NULL, const int *Dpids = NULL); virtual ~cStreamdevRecStreamer(); }; #endif // VDR_STREAMDEV_RECSTREAMER_H vdr-plugin-streamdev/server/livefilter.c0000644000175000017500000000662213276341255020316 0ustar tobiastobias/* * $Id: livefilter.c,v 1.7 2009/02/13 13:02:40 schmirl Exp $ */ #include #include "server/livefilter.h" #include "common.h" #ifndef TS_SYNC_BYTE # define TS_SYNC_BYTE 0x47 #endif #define FILTERBUFSIZE (1000 * TS_SIZE) // --- cStreamdevLiveFilter ------------------------------------------------- class cStreamdevLiveFilter: public cFilter { private: cStreamdevFilterStreamer *m_Streamer; bool m_On; protected: virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length); virtual void SetStatus(bool On); public: cStreamdevLiveFilter(cStreamdevFilterStreamer *Streamer); virtual bool IsAttached(void) const { return m_On; }; 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); } }; cStreamdevLiveFilter::cStreamdevLiveFilter(cStreamdevFilterStreamer *Streamer) { m_On = false; m_Streamer = Streamer; } void cStreamdevLiveFilter::SetStatus(bool On) { m_On = On; cFilter::SetStatus(On); } 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; m_Streamer->Receive(buffer); } } // --- cStreamdevFilterStreamer ------------------------------------------------- cStreamdevFilterStreamer::cStreamdevFilterStreamer(): cStreamdevStreamer("streamdev-filterstreaming"), m_Device(NULL), m_Filter(NULL)/*, m_Channel(NULL)*/ { m_ReceiveBuffer = new cStreamdevBuffer(FILTERBUFSIZE, TS_SIZE); m_ReceiveBuffer->SetTimeouts(0, 500); } cStreamdevFilterStreamer::~cStreamdevFilterStreamer() { Dprintf("Desctructing Filter streamer\n"); Detach(); m_Device = NULL; DELETENULL(m_Filter); Stop(); delete m_ReceiveBuffer; } void cStreamdevFilterStreamer::Receive(uchar *Data) { int p = m_ReceiveBuffer->PutTS(Data, TS_SIZE); if (p != TS_SIZE) m_ReceiveBuffer->ReportOverflow(TS_SIZE - p); } 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); } void cStreamdevFilterStreamer::SetDevice(cDevice *Device) { Dprintf("cStreamdevFilterStreamer::SetDevice()\n"); LOCK_THREAD; Detach(); m_Device = Device; Attach(); } bool cStreamdevFilterStreamer::IsReceiving(void) const { return m_Filter && m_Filter->IsAttached(); } 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; } vdr-plugin-streamdev/server/connectionIGMP.c0000644000175000017500000000356213276341255020765 0ustar tobiastobias/* * $Id: connectionIGMP.c,v 1.3 2010/08/03 10:46:41 schmirl Exp $ */ #include #include "server/connectionIGMP.h" #include "server/server.h" #include "server/setup.h" #include cConnectionIGMP::cConnectionIGMP(const char* Name, int ClientPort, eStreamType StreamType) : cServerConnection(Name, SOCK_DGRAM), m_ClientPort(ClientPort), m_StreamType(StreamType), m_Channel(NULL) { } cConnectionIGMP::~cConnectionIGMP() { } #if APIVERSNUM >= 20300 bool cConnectionIGMP::SetChannel(const cChannel *Channel, in_addr_t Dst) #else bool cConnectionIGMP::SetChannel(cChannel *Channel, in_addr_t Dst) #endif { if (Channel) { m_Channel = Channel; struct in_addr ip; ip.s_addr = Dst; if (Connect(inet_ntoa(ip), m_ClientPort)) return true; else esyslog("streamdev-server IGMP: Connect failed: %m"); return false; } else esyslog("streamdev-server IGMP: Channel not found"); return false; } void cConnectionIGMP::Welcome() { if (cStreamdevLiveStreamer::ProvidesChannel(m_Channel, StreamdevServerSetup.IGMPPriority)) { cStreamdevLiveStreamer * liveStreamer = new cStreamdevLiveStreamer(this, m_Channel, StreamdevServerSetup.IGMPPriority, m_StreamType); if (liveStreamer->GetDevice()) { SetStreamer(liveStreamer); if (!SetDSCP()) LOG_ERROR_STR("unable to set DSCP sockopt"); Dprintf("streamer start\n"); liveStreamer->Start(this); } else { SetStreamer(NULL); delete liveStreamer; esyslog("streamdev-server IGMP: SetChannel failed"); } } else esyslog("streamdev-server IGMP: SwitchDevice failed"); } bool cConnectionIGMP::Close() { if (Streamer()) Streamer()->Stop(); return cServerConnection::Close(); } cString cConnectionIGMP::ToText(char Delimiter) const { cString str = cServerConnection::ToText(Delimiter); return Streamer() ? cString::sprintf("%s%c%s", *str, Delimiter, *Streamer()->ToText()) : str; } vdr-plugin-streamdev/server/suspend.c0000644000175000017500000000172713276341255017633 0ustar tobiastobias/* * $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, true) { 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; } vdr-plugin-streamdev/server/server.h0000644000175000017500000000241213276341255017455 0ustar tobiastobias/* * $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 #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 m_Servers; static cList 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); #if APIVERSNUM >= 20300 static cList& Clients(cThreadLock& Lock); #else static const cList& Clients(cThreadLock& Lock); #endif }; inline bool cStreamdevServer::Active(void) { return m_Instance != NULL && m_Instance->m_Clients.Count() > 0; } extern cSVDRPhosts StreamdevHosts; #endif // VDR_STREAMDEV_SERVER_H vdr-plugin-streamdev/server/recplayer.h0000644000175000017500000000405113276341255020136 0ustar tobiastobias/* 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 #include #include #include "server/streamer.h" class Segment { public: uint64_t start; uint64_t end; }; class RecPlayer { public: RecPlayer(const char* FileName); ~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(); const cPatPmtParser* getPatPmtData() { return parser; } void scan(); uint64_t positionFromResume(int ResumeID); uint64_t positionFromMark(int MarkIndex); uint64_t positionFromTime(int Seconds); uint64_t positionFromPercent(int Percent); 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; cPatPmtParser* parser; FILE* file; int fileOpen; Segment* segments[1000]; uint64_t totalLength; uint64_t lastPosition; uint32_t totalFrames; }; #endif vdr-plugin-streamdev/README0000644000175000017500000006467213276341255015370 0ustar tobiastobiasThis is a "plugin" for the Video Disk Recorder (VDR). Written by: Sascha Volkenandt Current maintainer: Frank Schmirler 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 Compatibility 2.2 Compiling 2.3 Updating 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. externremux.sh 6. 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.5.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-server folder into the "plugins/streamdev-server" 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 set your video directory to "/video0", the directory has to be copied to /video0/plugins/streamdev-server. 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 by default. 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 Compatibility: ------------------ This version is not compatible to VDR releases older than 1.7.25. Use one of the streamdev-0.5.x releases for older versions. 2.2 Compiling: -------------- The Makefiles are for VDR 1.7.36 and above. For VDR 1.7.33 and below, please replace the Makefiles in the main directory and in the client/ and server/ subdirectories with the corresponding Makefile-1.7.33 files. With VDR 1.7.34 and 1.7.35 YMMV ;) cd vdr-1.X.X/PLUGINS/src tar xvfz vdr-streamdev-0.5.0.tgz ln -s streamdev-0.5.0 streamdev cp -r streamdev/streamdev-server VDRCONFDIR/plugins/ cd ../.. make [options, if necessary] vdr make [options, if necessary] plugins To build only the plugin, change into the streamdev source folder and issue make To build only streamdev-server or only streamdev-client, use make server make client 2.3 Updating: -------------- If you are updating streamdev from an earlier release, you might have to perform some additional steps. Check which version you've been running before, then read below for the necessary changes. * Priorities: ------------- (Affected: 0.5.x and older) The server-side setting "Suspend behaviour" has been dropped in 0.6.0 in favour of priority based precedence. A priority of 0 and above means that clients have precedence. A negative priority gives precedence to local live TV on the server. So if "Suspend behaviour" was previously set to "Client may suspend" or "Never suspended", you will have to configure a negative priority. If the "Suspend behaviour" was set to "Always suspended", the default values should do. Configure the desired priorities for HTTP and IGMP Multicast streaming in the settings of streamdev-server. If you haven't updated all your streamdev-clients to at least 0.5.2, configure "Legacy Client Priority", too. In streamdev-client, you should set "Minimum Priority" to -99. Adjust "Live TV Priority" if necessary. * Location of files: -------------------- (Affected: 0.3.x, 0.4.x, 0.4.0pre, 0.5.0pre) Starting with streamdev 0.5.0, all additional files are kept in a directory called "streamdev-server" 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: streamdev 0.3.x: mv VDRCONFDIR/plugins/streamdevhosts.conf VDRCONFDIR/plugins/streamdev-server/ streamdev 0.4.x, 0.4.0pre and 0.5.0pre: mv VDRCONFDIR/plugins/streamdev VDRCONFDIR/plugins/streamdev-server/ 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. * Handling of externremux script: --------------------------------- (Affected: 0.3.x, 0.4.0pre, 0.5.0pre) Streamdev server's externremux script became responsible for emitting all HTTP headers. A quick and dirty extension to your current script would be: echo -ne 'Content-type: video/mpeg\r\n' echo -ne '\r\n' However I encourage you to try the new externremux.sh script shipped with the streamdev source distribution. To emphasize the required change in externremux, the URL path for passing the stream through externremux has changed from EXTERN to EXT. 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. Precedence between multiple clients and between client and server is controlled with priorities. For HTTP and IGMP Multicast, the priority is configured in streamdev-server's setup menu. A negative priority gives precedence to local live TV on the server. Zero and positive values give precedence to the client. The priority for VDR clients watching live TV is configured in the plugin setup of streamdev-client. For other client tasks (e.g. recording a client side timer) the same priority as on the client is used. With the parameter "Legacy client Priority" in streamdev-server's setup menu you can configure the priority for clients which cannot be configured to use negative priorities. It is used when an old client is detected an it requests priority "0". On the server, the main menu entry "Streamdev Connections" gives you a list of currently connected clients. Use the "red" key to terminate a connection. Note that depending on connection type and client, the client might re-connect sooner or later. The "blue" key in the server's main menu will suspend live TV on server. An image is displayed instead. This would allow a low priority client to switch to a different transponder. Enable "Client may suspend" in the server setup to allow VDR clients to suspend live TV remotely. In the server's setup there's also an option to suspend live TV when starting the server. The "auto" option will suspend live TV if there's no device with an MPEG decoder available which is typically the case on a headless server. NOTE: Precedence is mainly an issue on One-Card-Systems, since with multiple cards there is no need to switch transponders on the primary interface, if one of the other cards is idle (i.e. if it is not blocked by a recording). If all cards are in use (i.e. when something is recorded, or by multiple clients), this applies to Multiple-Card-Systems as well. If your client suffers from buffer underruns while watching live TV, you can configure buffering on the server side. Enter a reasonable value (e.g. 300ms) as "Live TV buffer delay (ms)" in the server setup. 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) ES Elementary Stream (only Video, if available, otherwise only Audio) EXT 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. Use the special channel number 0 to see the server's current live TV channel. 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', 'ES' and 'EXT'. 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 'EXT' you can also add parameters which are passed as arguments to the externremux script (e.g. http://hostname:3000/EXT;param1=value1;param2=value2/3) Check your externremux.sh script for the parameters it understands. For details on how to modify or write your own externremux.sh, please see the chapter upon externremux.sh further down. 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 in between. 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. 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 address "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 + 1)) You need to run the sysctl command *before* VDR is started. The setting is lost after the next reboot. Check the documentation of your Linux distro on how to make the setting persist (i.e. have your distro change the value for you as part of the boot procedure). Most likely /etc/sysctl.conf is your friend. 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. If both, streamdev-client and streamdev-server are installed, the additional option "Loop prevention" will show up in the streamdev-server setup. If enabled, streamdev-client won't be considered when streamdev-server is looking for a device which is able to receive some channel. This is required if two or more VDRs mutually share their DVB devices through streamdev. Otherwise you would end up in a loop. 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-Address-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 address, 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" on your remote. If you want to enter "192.168.1.12", type "192168112". 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. The client is disabled by default, because it wouldn't make much sense to start the client without specifying a server anyway. Activate the client by setting "Simultaneously used Devices" to at least 1. Streamdev-client will allocate as many VDR devices as you configure here. Each of these devices opens one connection to the server and becomes associated with one of the server's devices (typically a DVB card) on demand. 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 on the same transponder and use plugins that use receivers themselves (like osdteletext). So for viewing live TV a single device is sufficient. But if the client needs to receive channels from different transponders simultaneously (e.g. for PiP or client side recordings) a second device becomes necessary. To allocate additional devices, just increase the number and push the OK button. There's no need to restart VDR. Deleting VDR devices on-the-fly is not possible. However requests to switch channels will be refused by redundant devices. The default timeout of 2 seconds for network operations should be sufficient in most cases. Increase "Timeout" if you get frequent timeout errors in the log. 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. If you have TV programs with dynamically changing PIDs (such as some german regional programs like NDR), then you need to enable "Filter Streaming" to correctly receive them. You also need to set in VDRs DVB setup the option "Update channels" to at least "PIDs only" (or "names and PIDs") for this to work. "Filter streaming" uses internally a socketpair(2) to copy meta data to VDR. This socketpair may require larger than default buffering. If you see a mesage like the following in syslog, cStreamdevFilter::PutSection(Pid:18 Tid: 64): Dropped 2995 bytes, max queue: 328640 then you should increase the streamdev client "FilterSockBufSize" value. A good value is 3072000. You will need to first configure your linux to permit such a large buffer size: sysctl net.core.wmem_max=3072000 The precedence among multiple client VDRs receiving live TV from the same server is controlled with "Live TV Priority". With "Maximum Priority" 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 might 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". You can also configure the "Broadcast Systems / Cost" of the streamdev-client device. On a pure streamdev-client only system it doesn't matter what you configure here. But if your client is equipped with a DVB card, you should read on. VDR always prefers the cheapest device in terms of supported broadcast systems and modulations. A DVB-S2 card supports two broadcast systems (DVB-S and DVB-S2). The supported modulations are counted as well (QPSK, QAM32/64/128/256, VSB8/16, TURBO_FEC). So for a DVB-S2 card which does QPSK you'll get a total cost of three. A DVB-C card (one broadcast system) which can do QAM32, QAM64, QAM128, QAM256 would give you a total of five. Check your log for "frontend ... provides ... with ..." messages to find out the cost of your DVB cards. Then pick a suitable value for streamdev-client. With equal costs, VDR will usually prefer the DVB card and take streamdev for recordings. If streamdev's costs are higher, live TV will use your DVB card until a recordings kicks in. Then the recording will take the DVB card and live TV will be shifted to streamdev (you'll notice a short interruption of live TV). To receive channels from multiple servers, create additional instances of the streamdev-client plugin. Simply copy (don't link!) the binary to a different name (e.g. streamdev-client2): cd VDRPLUGINLIBDIR cp libvdr-streamdev-client.so.1.X.X libvdr-streamdev-client2.so.1.X.X Now add -Pstreamdev-client2 to the VDR commandline. In the VDR plugin setup a second streamdev-client entry should show up. Both instances have to be configured individually. 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. externremux.sh: ------------------ When selecting streamtype "EXT", the TS stream from VDR is passed through an external program for further processing. By default a script installed at VDRCONFDIR/plugins/streamdev/externremux.sh is expected, however you may specify a different location as parameter -r to the streamdev-server plugin (see chapter upon Installation above). The TS stream is passed to the script on stdin, the resulting stream is expected on stdout. The following parameters are passed to the script in the environment: * Information on the channel: REMUX_CHANNEL_ID VDR channel ID REMUX_CHANNEL_NAME Channel name REMUX_VTYPE Video type (2 for MPEG-2) REMUX_VPID Video PID (undefined if audio only) REMUX_PPID PCR PID (undefined if equal to VPID) REMUX_TPID Teletext PID (undefined if not available) REMUX_APID Space separated list of audio pids REMUX_ALANG Space separated list of audio languages REMUX_DPID Space separated list of dolby pids REMUX_DLANG Space separated list of dolby languages REMUX_SPID Space separated list of subtitle pids REMUX_SLANG Space separated list of subtitle languages REMUX_PARAM_* All (user supplied) parameters (e.g. REMUX_PARAM_x) * Information on the connection (CGI like) REMOTE_ADDR Client IP SERVER_NAME Local IP SERVER_PORT Local port SERVER_PROTOCOL Streamdev protocol (HTTP, VTP, IGMP) SERVER_SOFTWARE Streamdev version All HTTP headers converted to uppercase, '-' replaced by '_' (e.g. USER_AGENT) The script should perform the following steps (pseudocode): if (SERVER_PROTOCOL == HTTP) write headers (including Content-Type) to STDOUT write empty line to STDOUT if (REQUEST_METHOD == HEAD) exit endif endif while (read STDIN) remux to STDOUT wend onSIGINT/SIGKILL: cleanup and exit 6. Known Problems: ------------------ * In VDR before 1.7.30 viewing encrypted channels is an issue as 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-1.7.29-intcamdevices.patch" or patch "patches/vdr-1.6.0-1.7.29-ignore_missing_cam.diff" to your client VDR. Intcamdevices is the clean solution, but 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. vdr-plugin-streamdev/common.c0000644000175000017500000000557513276341255016141 0ustar tobiastobias/* * $Id: common.c,v 1.12 2010/07/19 13:49:24 schmirl Exp $ */ #include #include #include "common.h" #include "tools/select.h" using namespace std; const char *VERSION = "0.6.1-git"; 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; } vdr-plugin-streamdev/patches/0000755000175000017500000000000013276341255016120 5ustar tobiastobiasvdr-plugin-streamdev/patches/vdr-1.6.0-1.7.29-intcamdevices.patch0000644000175000017500000001315613276341255023752 0ustar tobiastobiasIndex: 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; } vdr-plugin-streamdev/patches/vdr-1.6.0-1.7.29-ignore_missing_cam.diff0000644000175000017500000000062213276341255024573 0ustar tobiastobias--- 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; vdr-plugin-streamdev/client/0000755000175000017500000000000013276341255015747 5ustar tobiastobiasvdr-plugin-streamdev/client/setup.h0000644000175000017500000000170013276341255017256 0ustar tobiastobias/* * $Id: setup.h,v 1.7 2010/06/08 05:55:17 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 Timeout; int StreamFilters; int FilterSockBufSize; int HideMenuEntry; int LivePriority; int MinPriority; int MaxPriority; #if APIVERSNUM >= 10700 int NumProvidedSystems; #endif }; extern cStreamdevClientSetup StreamdevClientSetup; class cPluginStreamdevClient; class cStreamdevClientMenuSetupPage: public cMenuSetupPage { private: cPluginStreamdevClient *m_Plugin; cStreamdevClientSetup m_NewSetup; protected: virtual void Store(void); public: cStreamdevClientMenuSetupPage(cPluginStreamdevClient *Plugin); virtual ~cStreamdevClientMenuSetupPage(); }; #endif // VDR_STREAMDEV_SETUPCLIENT_H vdr-plugin-streamdev/client/device.c0000644000175000017500000002137613276341255017363 0ustar tobiastobias/* * $Id: device.c,v 1.27 2010/08/18 10:26:55 schmirl Exp $ */ #include "client/device.h" #include "client/setup.h" #include "client/filter.h" #include "tools/select.h" #include #include #include #include #include #include #include using namespace std; #ifndef LIVEPRIORITY #define LIVEPRIORITY 0 #endif #ifndef TRANSFERPRIORITY #define TRANSFERPRIORITY -1 #endif #define VIDEOBUFSIZE MEGABYTE(3) const cChannel *cStreamdevDevice::m_DenyChannel = NULL; cStreamdevDevice::cStreamdevDevice(void) { m_Disabled = false; m_ClientSocket = new cClientSocket(); m_Channel = NULL; m_TSBuffer = NULL; m_Filters = new cStreamdevFilters(m_ClientSocket); StartSectionHandler(); isyslog("streamdev-client: got device number %d", CardIndex() + 1); m_Pids = 0; } cStreamdevDevice::~cStreamdevDevice() { Dprintf("Device gets destructed\n"); Lock(); m_Filters->SetConnection(-1); m_ClientSocket->Quit(); m_ClientSocket->Reset(); Unlock(); Cancel(3); StopSectionHandler(); DELETENULL(m_Filters); DELETENULL(m_TSBuffer); delete m_ClientSocket; } #if APIVERSNUM >= 10700 int cStreamdevDevice::NumProvidedSystems(void) const { return StreamdevClientSetup.NumProvidedSystems; } #endif 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; } #if APIVERSNUM >= 10722 bool cStreamdevDevice::IsTunedToTransponder(const cChannel *Channel) const #else bool cStreamdevDevice::IsTunedToTransponder(const cChannel *Channel) #endif { return m_ClientSocket->DataSocket(siLive) != NULL && m_Channel != NULL && Channel->Transponder() == m_Channel->Transponder(); } const cChannel *cStreamdevDevice::GetCurrentlyTunedTransponder(void) const { if (m_ClientSocket->DataSocket(siLive) != NULL) return m_Channel; return NULL; } bool cStreamdevDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers) const { if (m_Disabled || Channel == m_DenyChannel) return false; Dprintf("ProvidesChannel, Channel=%s, Priority=%d, SocketPrio=%d\n", Channel->Name(), Priority, m_ClientSocket->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; } int newPrio = Priority; if (Priority == LIVEPRIORITY) { if (m_ClientSocket->ServerVersion() >= 100 || StreamdevClientSetup.LivePriority >= 0) newPrio = StreamdevClientSetup.LivePriority; } #if APIVERSNUM >= 10725 bool prio = Priority == IDLEPRIORITY || newPrio >= m_ClientSocket->Priority(); #else bool prio = Priority < 0 || newPrio > m_ClientSocket->Priority(); #endif bool res = prio; bool ndr = false; #if APIVERSNUM >= 10722 if (IsTunedToTransponder(Channel)) { #else if (const_cast(this)->IsTunedToTransponder(Channel)) { #endif if (Channel->Ca() < CA_ENCRYPTED_MIN || (Channel->Vpid() && HasPid(Channel->Vpid())) || (Channel->Apid(0) && HasPid(Channel->Apid(0)))) res = true; else ndr = true; } else if (prio) { if (Priority == LIVEPRIORITY && m_ClientSocket->ServerVersion() >= 100) UpdatePriority(true); res = m_ClientSocket->ProvidesChannel(Channel, newPrio); ndr = Receiving(); if (m_ClientSocket->ServerVersion() >= 100) UpdatePriority(false); } if (NeedsDetachReceivers) *NeedsDetachReceivers = ndr; Dprintf("prov res = %d, ndr = %d\n", res, ndr); return res; } bool cStreamdevDevice::SetChannelDevice(const cChannel *Channel, bool LiveView) { bool res; Dprintf("SetChannelDevice Channel: %s, LiveView: %s\n", Channel->Name(), LiveView ? "true" : "false"); LOCK_THREAD; if (LiveView) return false; if (Receiving() && IsTunedToTransponder(Channel) && ( Channel->Ca() < CA_ENCRYPTED_MIN || (Channel->Vpid() && HasPid(Channel->Vpid())) || (Channel->Apid(0) && HasPid(Channel->Apid(0))))) { res = true; } else { DetachAllReceivers(); m_Channel = Channel; // Old servers delete cStreamdevLiveStreamer in ABRT. // Delete it now or it will happen after we tuned to new channel if (m_ClientSocket->ServerVersion() < 100) CloseDvr(); res = m_ClientSocket->SetChannelDevice(m_Channel); } Dprintf("setchanneldevice res=%d\n", res); return res; } 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; bool res = true; if (Handle->pid && (On || !Handle->used)) { res = m_ClientSocket->SetPid(Handle->pid, On); m_Pids += (!res) ? 0 : On ? 1 : -1; if (m_Pids < 0) m_Pids = 0; } return res; } bool cStreamdevDevice::OpenDvr(void) { Dprintf("OpenDvr\n"); LOCK_THREAD; CloseDvr(); if (m_ClientSocket->CreateDataConnection(siLive)) { m_TSBuffer = new cTSBuffer(*m_ClientSocket->DataSocket(siLive), MEGABYTE(2), CardIndex() + 1); } else { esyslog("cStreamdevDevice::OpenDvr(): DVR connection FAILED"); } return m_TSBuffer != NULL; } void cStreamdevDevice::CloseDvr(void) { Dprintf("CloseDvr\n"); LOCK_THREAD; m_ClientSocket->CloseDvr(); DELETENULL(m_TSBuffer); } bool cStreamdevDevice::GetTSPacket(uchar *&Data) { if (m_TSBuffer) { 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(!m_ClientSocket->DataSocket(siLive)) { return false; // triggers CloseDvr() + OpenDvr() in cDevice } cPoller Poller(*m_ClientSocket->DataSocket(siLive)); errno = 0; if (Poller.Poll() && !errno) { char tmp[1]; if (recv(*m_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; CloseDvr(); 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 (!m_ClientSocket->DataSocket(siLiveFilter)) { if (m_ClientSocket->CreateDataConnection(siLiveFilter)) { m_Filters->SetConnection(*m_ClientSocket->DataSocket(siLiveFilter)); } else { isyslog("cStreamdevDevice::OpenFilter: connect failed: %m"); return -1; } } if (m_ClientSocket->SetFilter(Pid, Tid, Mask, true)) return m_Filters->OpenFilter(Pid, Tid, Mask); return -1; } void cStreamdevDevice::CloseFilter(int Handle) { if(m_Filters) m_Filters->CloseFilter(Handle); else esyslog("cStreamdevDevice::CloseFilter called while m_Filters is null"); } bool cStreamdevDevice::ReInit(bool Disable) { LOCK_THREAD; m_Disabled = Disable; m_Filters->SetConnection(-1); m_Pids = 0; m_ClientSocket->Quit(); m_ClientSocket->Reset(); //DELETENULL(m_TSBuffer); return true; } void cStreamdevDevice::UpdatePriority(bool SwitchingChannels) const { if (!m_Disabled) { //LOCK_THREAD; const_cast(this)->Lock(); if (m_ClientSocket->SupportsPrio() && m_ClientSocket->DataSocket(siLive)) { int Priority = this->Priority(); // override TRANSFERPRIORITY (-1) with live TV priority from setup if (Priority == TRANSFERPRIORITY && this == cDevice::ActualDevice()) { Priority = StreamdevClientSetup.LivePriority; // temporarily lower priority if (SwitchingChannels) Priority--; if (Priority < 0 && m_ClientSocket->ServerVersion() < 100) Priority = 0; } m_ClientSocket->SetPriority(Priority); } const_cast(this)->Unlock(); } } cString cStreamdevDevice::DeviceName(void) const { return StreamdevClientSetup.RemoteIp; } cString cStreamdevDevice::DeviceType(void) const { static int dev = -1; static cString devType("STRDev"); int d = -1; if (m_ClientSocket->DataSocket(siLive) != NULL) m_ClientSocket->GetSignal(NULL, NULL, &d); if (d != dev) { dev = d; devType = d < 0 ? "STRDev" : *cString::sprintf("STRD%2d", d); } return devType; } int cStreamdevDevice::SignalStrength(void) const { int strength = -1; if (m_ClientSocket->DataSocket(siLive) != NULL) m_ClientSocket->GetSignal(&strength, NULL, NULL); return strength; } int cStreamdevDevice::SignalQuality(void) const { int quality = -1; if (m_ClientSocket->DataSocket(siLive) != NULL) m_ClientSocket->GetSignal(NULL, &quality, NULL); return quality; } vdr-plugin-streamdev/client/filter.h0000644000175000017500000000133513276341255017407 0ustar tobiastobias/* * $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 #include #include class cTSBuffer; class cStreamdevFilter; class cClientSocket; class cStreamdevFilters: public cList, public cThread { private: cClientSocket *m_ClientSocket; cTSBuffer *m_TSBuffer; protected: virtual void Action(void); bool ReActivateFilters(void); public: cStreamdevFilters(cClientSocket *ClientSocket); virtual ~cStreamdevFilters(); void SetConnection(int Handle); int OpenFilter(u_short Pid, u_char Tid, u_char Mask); void CloseFilter(int Handle); }; #endif // VDR_STREAMDEV_FILTER_H vdr-plugin-streamdev/client/po/0000755000175000017500000000000013276341255016365 5ustar tobiastobiasvdr-plugin-streamdev/client/po/es_ES.po0000644000175000017500000000271313276341255017726 0ustar tobiastobias# 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. # Javier Bradineras , 2011 # msgid "" msgstr "" "Project-Id-Version: streamdev 0.5.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-01-16 22:32+0100\n" "PO-Revision-Date: 2010-06-19 03:58+0100\n" "Last-Translator: Javier Bradineras \n" "Language-Team: Spanish \n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ISO-8859-15\n" "Content-Transfer-Encoding: 8bit\n" msgid "Hide Mainmenu Entry" msgstr "Ocultar entrada en menú principal" msgid "Simultaneously used Devices" msgstr "" msgid "Remote IP" msgstr "Indicar IP del Servidor" msgid "Remote Port" msgstr "Indicar puerto remoto del Servidor" msgid "Timeout (s)" msgstr "" msgid "Filter Streaming" msgstr "Filtrar transmisión" msgid "Filter SockBufSize" msgstr "" msgid "Live TV Priority" msgstr "" msgid "Minimum Priority" msgstr "Prioridad mínima" msgid "Maximum Priority" msgstr "Prioridad máxima" msgid "Broadcast Systems / Cost" msgstr "" msgid "VTP Streaming Client" msgstr "Cliente trasmisión VTP" msgid "Suspend Server" msgstr "Suspender servidor" msgid "Server is suspended" msgstr "Servidor en suspensión" msgid "Couldn't suspend Server!" msgstr "Imposible suspender el servidor!" vdr-plugin-streamdev/client/po/pl_PL.po0000644000175000017500000000300313276341255017727 0ustar tobiastobias# VDR streamdev plugin language source file. # Copyright (C) 2008 streamdev development team. See # This file is distributed under the same license as the VDR streamdev package. # Tomasz Maciej Nowak, 2014 # msgid "" msgstr "" "Project-Id-Version: vdr-streamdev-client 0.6.1-git\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-01-16 22:32+0100\n" "PO-Revision-Date: 2014-11-24 18:07+0100\n" "Last-Translator: Tomasz Maciej Nowak \n" "Language-Team: Polish \n" "Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=iso-8859-2\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 1.6.10\n" msgid "Hide Mainmenu Entry" msgstr "Ukryj pozycjê w g³ównym menu" msgid "Simultaneously used Devices" msgstr "Jednocze¶nie u¿ywane urz±dzenia" msgid "Remote IP" msgstr "Adres serwera" msgid "Remote Port" msgstr "Port serwera" msgid "Timeout (s)" msgstr "Czas oczekiwania (s)" msgid "Filter Streaming" msgstr "Filtruj strumieñ" msgid "Filter SockBufSize" msgstr "" msgid "Live TV Priority" msgstr "Priorytet lokalnych ur±dzeñ" msgid "Minimum Priority" msgstr "Minimalny priorytet" msgid "Maximum Priority" msgstr "Maksymalny priorytet" msgid "Broadcast Systems / Cost" msgstr "Koszt / systemy transmisji" msgid "VTP Streaming Client" msgstr "Klient strumieniowania VTP" msgid "Suspend Server" msgstr "Wstrzymaj serwer" msgid "Server is suspended" msgstr "Serwer jest wstrzymany" msgid "Couldn't suspend Server!" msgstr "Nie mo¿na wstrzymaæ serwera!" vdr-plugin-streamdev/client/po/sk_SK.po0000755000175000017500000000307213276341255017744 0ustar tobiastobias# 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 , 2009 # msgid "" msgstr "" "Project-Id-Version: streamdev_SK\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-01-16 22:32+0100\n" "PO-Revision-Date: \n" "Last-Translator: Milan Hrala \n" "Language-Team: Slovak \n" "Language: sk\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 "Hide Mainmenu Entry" msgstr "Schova» polo¾ku v hlavnom menu" msgid "Simultaneously used Devices" msgstr "Súbe¾ne pou¾íva» zariadenia" msgid "Remote IP" msgstr "Vzdialená IP" msgid "Remote Port" msgstr "Vzdialený port" msgid "Timeout (s)" msgstr "Èasový limit (s)" msgid "Filter Streaming" msgstr "Filtrova» dátový prúd" msgid "Filter SockBufSize" msgstr "" msgid "Live TV Priority" msgstr "Priorita ¾ivého vysielania" msgid "Minimum Priority" msgstr "Minimálna priorita" msgid "Maximum Priority" msgstr "Maximálna priorita" msgid "Broadcast Systems / Cost" msgstr "Systémy vysielania / Hodnota" msgid "VTP Streaming Client" msgstr "VDR klient streamovania" msgid "Suspend Server" msgstr "Pozastavi» server" msgid "Server is suspended" msgstr "Server je doèasne pozastavený" msgid "Couldn't suspend Server!" msgstr "Server sa nepodarilo pozastavi»!" vdr-plugin-streamdev/client/po/it_IT.po0000644000175000017500000000307313276341255017740 0ustar tobiastobias# 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 , 2001 # Antonio Ospite , 2003 # Sean Carlos , 2005 # msgid "" msgstr "" "Project-Id-Version: streamdev 0.5.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-01-16 22:32+0100\n" "PO-Revision-Date: 2012-06-10 20:34+0100\n" "Last-Translator: Diego Pierotto \n" "Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "Hide Mainmenu Entry" msgstr "Nascondi voce menu principale" msgid "Simultaneously used Devices" msgstr "" msgid "Remote IP" msgstr "Indirizzo IP del Server" msgid "Remote Port" msgstr "Porta Server Remoto" msgid "Timeout (s)" msgstr "Scadenza (s)" msgid "Filter Streaming" msgstr "Filtra trasmissione" msgid "Filter SockBufSize" msgstr "" msgid "Live TV Priority" msgstr "Priorità TV dal vivo" msgid "Minimum Priority" msgstr "Priorità minima" msgid "Maximum Priority" msgstr "Priorità massima" msgid "Broadcast Systems / Cost" msgstr "Costo / sistemi trasmissione" 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!" vdr-plugin-streamdev/client/po/fi_FI.po0000644000175000017500000000300313276341255017675 0ustar tobiastobias# 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, 2008- # msgid "" msgstr "" "Project-Id-Version: streamdev 0.5.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-01-16 22:32+0100\n" "PO-Revision-Date: 2008-03-30 02:11+0200\n" "Last-Translator: Rolf Ahrenberg\n" "Language-Team: Finnish \n" "Language: fi\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "Hide Mainmenu Entry" msgstr "Piilota valinta päävalikosta" msgid "Simultaneously used Devices" msgstr "Yhtäaikaiset laitteet" msgid "Remote IP" msgstr "Etäkoneen IP-osoite" msgid "Remote Port" msgstr "Etäkoneen portti" msgid "Timeout (s)" msgstr "Yhteyden aikakatkaisu (s)" msgid "Filter Streaming" msgstr "Suodatetun tiedon suoratoisto" msgid "Filter SockBufSize" msgstr "" msgid "Live TV Priority" msgstr "Live-katselun prioriteetti" msgid "Minimum Priority" msgstr "Pienin prioriteetti" msgid "Maximum Priority" msgstr "Suurin prioriteetti" msgid "Broadcast Systems / Cost" msgstr "Lähetysjärjestelmien suhdeluku" 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!" vdr-plugin-streamdev/client/po/fr_FR.po0000644000175000017500000000261213276341255017724 0ustar tobiastobias# 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 , 2008 # msgid "" msgstr "" "Project-Id-Version: streamdev 0.5.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-01-16 22:32+0100\n" "PO-Revision-Date: 2008-03-30 02:11+0200\n" "Last-Translator: micky979 \n" "Language-Team: French \n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ISO-8859-15\n" "Content-Transfer-Encoding: 8bit\n" msgid "Hide Mainmenu Entry" msgstr "Masquer dans le menu principal" msgid "Simultaneously used Devices" msgstr "" msgid "Remote IP" msgstr "Adresse IP du serveur" msgid "Remote Port" msgstr "Port du serveur" msgid "Timeout (s)" msgstr "" msgid "Filter Streaming" msgstr "Filtre streaming" msgid "Filter SockBufSize" msgstr "" msgid "Live TV Priority" msgstr "" msgid "Minimum Priority" msgstr "" msgid "Maximum Priority" msgstr "" msgid "Broadcast Systems / Cost" msgstr "" 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!" vdr-plugin-streamdev/client/po/de_DE.po0000644000175000017500000000302713276341255017667 0ustar tobiastobias# 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 , 2008 # msgid "" msgstr "" "Project-Id-Version: streamdev 0.5.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2014-10-20 22:57+0200\n" "PO-Revision-Date: 2008-03-30 02:11+0200\n" "Last-Translator: Frank Schmirler \n" "Language-Team: German \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ISO-8859-15\n" "Content-Transfer-Encoding: 8bit\n" msgid "Hide Mainmenu Entry" msgstr "Hauptmenüeintrag verstecken" msgid "Simultaneously used Devices" msgstr "Gleichzeitig genutzte DVB-Karten" msgid "Remote IP" msgstr "IP der Gegenseite" msgid "Remote Port" msgstr "Port der Gegenseite" msgid "Timeout (s)" msgstr "Timeout (s)" msgid "Filter Streaming" msgstr "Filter-Daten streamen" msgid "Filter SockBufSize" msgstr "Filter Socket Puffergröße" msgid "Live TV Priority" msgstr "Live TV Priorität" msgid "Minimum Priority" msgstr "Minimale Priorität" msgid "Maximum Priority" msgstr "Maximale Priorität" msgid "Broadcast Systems / Cost" msgstr "Empfangssysteme / Kosten" 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!" vdr-plugin-streamdev/client/po/ru_RU.po0000644000175000017500000000254513276341255017767 0ustar tobiastobias# 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 , 2008 # msgid "" msgstr "" "Project-Id-Version: streamdev 0.5.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-01-16 22:32+0100\n" "PO-Revision-Date: 2008-06-26 15:36+0100\n" "Last-Translator: Oleg Roitburd \n" "Language-Team: Russian \n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=ISO-8859-5\n" "Content-Transfer-Encoding: 8bit\n" msgid "Hide Mainmenu Entry" msgstr "ÁßàïâÐâì Ò ÓÛÐÒÝÞÜ ÜÕÝî" msgid "Simultaneously used Devices" msgstr "" msgid "Remote IP" msgstr "ÃÔÐÛÕÝÝëÙ IP" msgid "Remote Port" msgstr "ÃÔÐÛÕÝÝëÙ ßÞàâ" msgid "Timeout (s)" msgstr "" msgid "Filter Streaming" msgstr "ÄØÛìâà ßÞâÞÚÐ" msgid "Filter SockBufSize" msgstr "" msgid "Live TV Priority" msgstr "" msgid "Minimum Priority" msgstr "" msgid "Maximum Priority" msgstr "" msgid "Broadcast Systems / Cost" msgstr "" msgid "VTP Streaming Client" msgstr "VTP Streaming ÚÛØÕÝâ" msgid "Suspend Server" msgstr "¾áâÐÝÞÒØâì áÕàÒÕà" msgid "Server is suspended" msgstr "ÁÕàÒÕà ÞáâÐÝÞÒÛÕÝ" msgid "Couldn't suspend Server!" msgstr "ÝÕ ÜÞÓã ÞáâÐÝÞÒØâì áÕàÒÕà" vdr-plugin-streamdev/client/po/lt_LT.po0000644000175000017500000000270513276341255017747 0ustar tobiastobias# 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 , 2008 # msgid "" msgstr "" "Project-Id-Version: streamdev 0.5.0\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2015-01-16 22:32+0100\n" "PO-Revision-Date: 2009-11-26 21:57+0200\n" "Last-Translator: Valdemaras Pipiras \n" "Language-Team: Lithuanian \n" "Language: lt\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" msgid "Hide Mainmenu Entry" msgstr "PaslÄ—pti pagrindinio meniu įrašą" msgid "Simultaneously used Devices" msgstr "" msgid "Remote IP" msgstr "Nuotolinis IP adresas" msgid "Remote Port" msgstr "Nuotolinis portas" msgid "Timeout (s)" msgstr "" msgid "Filter Streaming" msgstr "Filtruoti transliavimÄ…" msgid "Filter SockBufSize" msgstr "" msgid "Live TV Priority" msgstr "" msgid "Minimum Priority" msgstr "Minimalus prioritetas" msgid "Maximum Priority" msgstr "Maksimalus prioritetas" msgid "Broadcast Systems / Cost" msgstr "" 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!" vdr-plugin-streamdev/client/filter.c0000644000175000017500000002205613276341255017405 0ustar tobiastobias/* * $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 #include #include #define PID_MASK_HI 0x1F // --- cStreamdevFilter ------------------------------------------------------ static int FilterSockBufSize_warn = 0; class cStreamdevFilter: public cListObject { private: uchar m_Buffer[8192]; int m_Used; int m_Pipe[2]; u_short m_Pid; u_char m_Tid; u_char m_Mask; #ifdef TIOCOUTQ unsigned long m_maxq; unsigned long m_flushed; #endif 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]; } 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 TIOCOUTQ m_flushed = 0; m_maxq = 0; #endif #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"); } // Set buffer for socketpair. During certain situations, such as startup, channel/transponder // change, VDR may lag in reading data. Instead of discarding it, we can buffer it. // Buffer size required may be up to 4MByte. if(StreamdevClientSetup.FilterSockBufSize) { int sbs = StreamdevClientSetup.FilterSockBufSize; int sbs2; unsigned int sbss = sizeof(sbs); int r; r = setsockopt(m_Pipe[1], SOL_SOCKET, SO_SNDBUF, (char *)&sbs, sbss); if(r < 0) { isyslog("streamdev-client: setsockopt(SO_SNDBUF, %d) = %s", sbs, strerror(errno)); } sbs2 = 0; r = getsockopt(m_Pipe[1], SOL_SOCKET, SO_SNDBUF, (char *)&sbs2, &sbss); if(r < 0 || !sbss || !sbs2) { isyslog("streamdev-client: getsockopt(SO_SNDBUF, &%d, &%d) = %s", sbs2, sbss, strerror(errno)); } else { // Linux actually returns double the requested size // if everything works fine. And it actually buffers up to that double amount // as can be seen from observing TIOCOUTQ (kernel 3.7/2014). if(sbs2 > sbs) sbs2 /= 2; if(sbs2 < sbs) { if(FilterSockBufSize_warn != sbs2) { isyslog("streamdev-client: ******************************************************"); isyslog("streamdev-client: getsockopt(SO_SNDBUF) = %d < %d (configured).", sbs2, sbs); isyslog("streamdev-client: Consider increasing system buffer size:"); isyslog("streamdev-client: 'sysctl net.core.wmem_max=%d'", sbs); isyslog("streamdev-client: ******************************************************"); FilterSockBufSize_warn = sbs2; } } } } 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); 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; #ifdef TIOCOUTQ // If we can determine the queue size of the socket, // we flush rather then let the socket drop random packets. // This ensures that we have more contiguous set of packets // on the receiver side. if(m_flushed) { unsigned long queue = 0; ioctl(m_Pipe[1], TIOCOUTQ, &queue); if(queue > m_maxq) m_maxq = queue; if(queue * 2 < m_maxq) { dsyslog("cStreamdevFilter::PutSection(Pid:%d Tid: %d): " "Flushed %ld bytes, max queue: %ld", m_Pid, m_Tid, m_flushed, m_maxq); m_flushed = m_maxq = 0; } else { m_flushed += length; } } if(!m_flushed) #endif if(write(m_Pipe[1], m_Buffer, length) < 0) { if(errno != EAGAIN && errno != EWOULDBLOCK) { dsyslog("cStreamdevFilter::PutSection(Pid:%d Tid: %d): error: %s", m_Pid, m_Tid, strerror(errno)); return false; } else { #ifdef TIOCOUTQ m_flushed += length; #else dsyslog("cStreamdevFilter::PutSection(Pid:%d Tid: %d): " "Dropping packet %ld bytes (queue overflow)", m_Pid, m_Tid, length); #endif } } } 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; } // --- cStreamdevFilters ----------------------------------------------------- cStreamdevFilters::cStreamdevFilters(cClientSocket *ClientSocket): cThread("streamdev-client: sections assembler") { m_ClientSocket = ClientSocket; m_TSBuffer = NULL; } cStreamdevFilters::~cStreamdevFilters() { SetConnection(-1); } int cStreamdevFilters::OpenFilter(u_short Pid, u_char Tid, u_char Mask) { cStreamdevFilter *f = new cStreamdevFilter(Pid, Tid, Mask); int fh = f->ReadPipe(); LOCK_THREAD; Add(f); return fh; } void cStreamdevFilters::CloseFilter(int Handle) { LOCK_THREAD; for (cStreamdevFilter *fi = First(); fi; fi = Next(fi)) { if(fi->ReadPipe() == Handle) { // isyslog("cStreamdevFilters::CloseFilter(%d): Pid %4d, Tid %3d, Mask %2x (%d filters left)\n", // Handle, (int)fi->Pid(), (int)fi->Tid(), fi->Mask(), Count()-1); Del(fi); return; } } esyslog("cStreamdevFilters::CloseFilter(%d): failed (%d filters left)\n", Handle, Count()-1); } bool cStreamdevFilters::ReActivateFilters(void) { LOCK_THREAD; bool res = true; for (cStreamdevFilter *fi = First(); fi; fi = Next(fi)) { res = m_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"); } 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 = *m_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 ?"); m_ClientSocket->CloseDataConnection(siLiveFilter); break; } } else { fails = 0; } } else { fails = 0; } cCondWait::SleepMs(10); #endif } } DELETENULL(m_TSBuffer); dsyslog("StreamdevFilters::Action() ended"); } vdr-plugin-streamdev/client/socket.c0000644000175000017500000002221213276341255017402 0ustar tobiastobias/* * $Id: socket.c,v 1.15 2010/08/18 10:26:55 schmirl Exp $ */ #include #include #include #include #include #include #define MINLOGREPEAT 10 //don't log connect failures too often (seconds) // timeout for writing to command socket #define WRITE_TIMEOUT_MS 200 #define QUIT_TIMEOUT_MS 500 #include "client/socket.h" #include "common.h" cClientSocket::cClientSocket(void) { memset(m_DataSockets, 0, sizeof(cTBSocket*) * si_Count); m_ServerVersion = 0; m_Priority = -100; m_Prio = false; m_Abort = false; m_LastSignalUpdate = 0; m_LastSignalStrength = -1; m_LastSignalQuality = -1; m_LastDev = -1; Reset(); } cClientSocket::~cClientSocket() { Reset(); if (IsOpen()) Quit(); } void cClientSocket::Reset(void) { for (int it = 0; it < si_Count; ++it) DELETENULL(m_DataSockets[it]); m_Priority = -100; } cTBSocket *cClientSocket::DataSocket(eSocketId Id) const { return m_DataSockets[Id]; } bool cClientSocket::Command(const std::string &Command, uint Expected) { uint code = 0; std::string buffer; if (Send(Command) && Receive(Command, &code, &buffer)) { if (code == Expected) return true; dsyslog("streamdev-client: Command '%s' rejected by %s:%d: %s", Command.c_str(), RemoteIp().c_str(), RemotePort(), buffer.c_str()); } return false; } bool cClientSocket::Send(const std::string &Command) { std::string pkt = Command + "\015\012"; Dprintf("OUT: |%s|\n", Command.c_str()); errno = 0; if (!TimedWrite(pkt.c_str(), pkt.size(), WRITE_TIMEOUT_MS)) { esyslog("ERROR: streamdev-client: Failed sending command '%s' to %s:%d: %s", Command.c_str(), RemoteIp().c_str(), RemotePort(), strerror(errno)); Close(); return false; } return true; } #define TIMEOUT_MS 1000 bool cClientSocket::Receive(const std::string &Command, uint *Code, std::string *Result, uint TimeoutMs) { int bufcount; do { errno = 0; bufcount = ReadUntil(m_Buffer, sizeof(m_Buffer) - 1, "\012", TimeoutMs < TIMEOUT_MS ? TimeoutMs : TIMEOUT_MS); if (bufcount == -1) { if (m_Abort) return false; if (errno != ETIMEDOUT || TimeoutMs <= TIMEOUT_MS) { esyslog("ERROR: streamdev-client: Failed reading reply to '%s' from %s:%d: %s", Command.c_str(), RemoteIp().c_str(), RemotePort(), strerror(errno)); Close(); return false; } TimeoutMs -= TIMEOUT_MS; } } while (bufcount == -1); if (m_Buffer[bufcount - 1] == '\015') --bufcount; m_Buffer[bufcount] = '\0'; Dprintf("IN: |%s|\n", m_Buffer); if (Result != NULL) *Result = m_Buffer; if (Code != NULL) *Code = strtoul(m_Buffer, NULL, 10); return true; } bool cClientSocket::CheckConnection(void) { CMD_LOCK; if (IsOpen()) { cTBSelect select; // 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) { return true; } Dprintf("closing connection (res was %d)\n", res); Close(); } if (!Connect(StreamdevClientSetup.RemoteIp, StreamdevClientSetup.RemotePort, StreamdevClientSetup.Timeout * 1000)){ static time_t lastTime = 0; if (time(NULL) - lastTime > MINLOGREPEAT) { esyslog("ERROR: streamdev-client: Couldn't connect to %s:%d: %s", (const char*)StreamdevClientSetup.RemoteIp, StreamdevClientSetup.RemotePort, strerror(errno)); lastTime = time(NULL); } return false; } uint code = 0; std::string buffer; if (!Receive("", &code, &buffer)) { Close(); return false; } if (code != 220) { esyslog("ERROR: streamdev-client: Didn't receive greeting from %s:%d: %s", RemoteIp().c_str(), RemotePort(), buffer.c_str()); Close(); return false; } unsigned int major, minor; if (sscanf(buffer.c_str(), "%*u VTP/%u.%u", &major, &minor) == 2) m_ServerVersion = major * 100 + minor; if (m_ServerVersion == 0) { if (!Command("CAPS TSPIDS", 220)) { Close(); return false; } const char *Filters = ""; if(Command("CAPS FILTERS", 220)) Filters = ",FILTERS"; const char *Prio = ""; if(Command("CAPS PRIO", 220)) { Prio = ",PRIO"; m_Prio = true; } isyslog("streamdev-client: Connected to server %s:%d using capabilities TSPIDS%s%s", RemoteIp().c_str(), RemotePort(), Filters, Prio); } else { if(!Command("VERS 1.0", 220)) { Close(); return false; } m_Prio = true; isyslog("streamdev-client: Connected to server %s:%d using protocol version %u.%u", RemoteIp().c_str(), RemotePort(), major, minor); } 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 (!Send(command)) return false; uint code; std::string buffer; if (!Receive(command, &code, &buffer)) return false; if (code != 220 && code != 560) { esyslog("streamdev-client: Unexpected reply to '%s' from %s:%d: %s", command.c_str(), RemoteIp().c_str(), RemotePort(), buffer.c_str()); return false; } return code == 220; } 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-client: 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)) 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-client: 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) { CMD_LOCK; if(Id == siLive || Id == siLiveFilter) if (m_DataSockets[Id] != NULL) { std::string command = (std::string)"ABRT " + (const char*)itoa(Id); Command(command, 220); 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)) return false; m_LastSignalUpdate = 0; return true; } bool cClientSocket::SetPriority(int Priority) { if (Priority == m_Priority) return true; if (!CheckConnection()) return false; CMD_LOCK; std::string command = (std::string)"PRIO " + (const char*)itoa(Priority); if (!Command(command, 220)) return false; m_Priority = Priority; return true; } bool cClientSocket::GetSignal(int *SignalStrength, int *SignalQuality, int *Dev) { if (!CheckConnection()) return -1; CMD_LOCK; if (m_LastSignalUpdate != time(NULL)) { uint code = 0; std::string buffer; std::string command("SGNL"); if (!Send(command) || !Receive(command, &code, &buffer) || code != 220 || sscanf(buffer.c_str(), "%*d %d %d:%d", &m_LastDev, &m_LastSignalStrength, &m_LastSignalQuality) != 3) { m_LastDev = -1; m_LastSignalStrength = -1; m_LastSignalQuality = -1; } m_LastSignalUpdate = time(NULL); } if (SignalStrength) *SignalStrength = m_LastSignalStrength; if (SignalQuality) *SignalQuality = m_LastSignalQuality; if (Dev) *Dev = m_LastDev; return 0; } 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); return Command(command, 220); } 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); return Command(command, 220); } 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)) return false; DELETENULL(m_DataSockets[siLive]); } return true; } bool cClientSocket::Quit(void) { m_Abort = true; if (!IsOpen()) return false; CMD_LOCK; std::string command("QUIT"); bool res = Send(command) && Receive(command, NULL, NULL, QUIT_TIMEOUT_MS); Close(); return res; } bool cClientSocket::SuspendServer(void) { if (!CheckConnection()) return false; CMD_LOCK; return Command("SUSP", 220); } vdr-plugin-streamdev/client/streamdev-client.h0000644000175000017500000000162613276341255021373 0ustar tobiastobias/* * $Id: streamdev-client.h,v 1.3 2010/08/18 10:26:56 schmirl Exp $ */ #ifndef VDR_STREAMDEVCLIENT_H #define VDR_STREAMDEVCLIENT_H #include "common.h" #include #define STREAMDEV_MAXDEVICES 8 class cStreamdevDevice; class cPluginStreamdevClient : public cPlugin { private: static const char *DESCRIPTION; cStreamdevDevice *m_Devices[STREAMDEV_MAXDEVICES]; public: cPluginStreamdevClient(void); virtual ~cPluginStreamdevClient(); virtual const char *Version(void) { return VERSION; } virtual const char *Description(void); virtual bool Initialize(void); virtual const char *MainMenuEntry(void); virtual cOsdObject *MainMenuAction(void); virtual cMenuSetupPage *SetupMenu(void); virtual bool SetupParse(const char *Name, const char *Value); virtual bool Service(const char *Id, void *Data = NULL); virtual void MainThreadHook(void); }; #endif // VDR_STREAMDEVCLIENT_H vdr-plugin-streamdev/client/streamdev-client.c0000644000175000017500000000406013276341255021361 0ustar tobiastobias/* * 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.3 2010/08/18 10:26:56 schmirl Exp $ */ #include "streamdev-client.h" #include "client/device.h" #include "client/setup.h" #if !defined(APIVERSNUM) || APIVERSNUM < 10516 #error "VDR-1.5.16 API version or greater is required!" #endif const char *cPluginStreamdevClient::DESCRIPTION = trNOOP("VTP Streaming Client"); cPluginStreamdevClient::cPluginStreamdevClient(void): m_Devices() { } cPluginStreamdevClient::~cPluginStreamdevClient() { } const char *cPluginStreamdevClient::Description(void) { return tr(DESCRIPTION); } bool cPluginStreamdevClient::Initialize(void) { for (int i = 0; i < STREAMDEV_MAXDEVICES; i++) { if (m_Devices[i]) m_Devices[i]->ReInit(i >= StreamdevClientSetup.StartClient); else if (i < StreamdevClientSetup.StartClient) m_Devices[i] = new cStreamdevDevice(); } return true; } const char *cPluginStreamdevClient::MainMenuEntry(void) { return StreamdevClientSetup.StartClient && !StreamdevClientSetup.HideMenuEntry ? tr("Suspend Server") : NULL; } cOsdObject *cPluginStreamdevClient::MainMenuAction(void) { if (StreamdevClientSetup.StartClient && m_Devices[0]->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(this); } bool cPluginStreamdevClient::SetupParse(const char *Name, const char *Value) { return StreamdevClientSetup.SetupParse(Name, Value); } bool cPluginStreamdevClient::Service(const char *Id, void *Data) { if (!strcmp(Id, LOOP_PREVENTION_SERVICE)) { cStreamdevDevice::DenyChannel((const cChannel*) Data); return true; } return false; } void cPluginStreamdevClient::MainThreadHook(void) { for (int i = 0; i < StreamdevClientSetup.StartClient; i++) m_Devices[i]->UpdatePriority(); } VDRPLUGINCREATOR(cPluginStreamdevClient); // Don't touch this! vdr-plugin-streamdev/client/device.h0000644000175000017500000000452213276341255017362 0ustar tobiastobias/* * $Id: device.h,v 1.10 2010/08/18 10:26:55 schmirl Exp $ */ #ifndef VDR_STREAMDEV_DEVICE_H #define VDR_STREAMDEV_DEVICE_H #include #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 { private: bool m_Disabled; cClientSocket *m_ClientSocket; const cChannel *m_Channel; cTSBuffer *m_TSBuffer; cStreamdevFilters *m_Filters; int m_Pids; static const cChannel *m_DenyChannel; protected: virtual bool SetChannelDevice(const cChannel *Channel, bool LiveView); #if APIVERSNUM >= 10738 virtual bool HasLock(int TimeoutMs) const #else virtual bool HasLock(int TimeoutMs) #endif { //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); virtual void CloseFilter(int Handle); 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; #endif #if APIVERSNUM >= 10719 virtual bool AvoidRecording(void) const { return true; } #endif #if APIVERSNUM >= 10722 virtual bool IsTunedToTransponder(const cChannel *Channel) const; #else virtual bool IsTunedToTransponder(const cChannel *Channel); #endif virtual const cChannel *GetCurrentlyTunedTransponder(void) const; virtual cString DeviceName(void) const; virtual cString DeviceType(void) const; virtual int SignalStrength(void) const; virtual int SignalQuality(void) const; bool ReInit(bool Disable); void UpdatePriority(bool SwitchingChannels = false) const; bool SuspendServer() { return m_ClientSocket->SuspendServer(); } static void DenyChannel(const cChannel *Channel) { m_DenyChannel = Channel; } }; #endif // VDR_STREAMDEV_DEVICE_H vdr-plugin-streamdev/client/Makefile0000644000175000017500000000410313276341255017405 0ustar tobiastobias# # Makefile for a Video Disk Recorder plugin # # $Id: $ # 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-client ### The name of the shared object file: SOFILE = libvdr-$(PLUGIN).so ### Includes and Defines (add further entries here): DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"' ### The object files (add further files here): COMMONOBJS = ../common.o CLIENTOBJS = $(PLUGIN).o \ device.o filter.o setup.o socket.o ### The main target: all: $(SOFILE) i18n ### Implicit rules: %.o: %.c $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $< ### Dependencies: MAKEDEP = $(CXX) -MM -MG DEPFILE = .dependencies $(DEPFILE): Makefile @$(MAKEDEP) $(CXXFLAGS) $(DEFINES) $(INCLUDES) $(CLIENTOBJS:%.o=%.c) $(COMMONOBJS:%.o=%.c) > $@ -include $(DEPFILE) ### Internationalization (I18N): PODIR = po I18Npo = $(wildcard $(PODIR)/*.po) I18Nmo = $(addsuffix .mo, $(foreach file, $(I18Npo), $(basename $(file)))) I18Nmsgs = $(addprefix $(DESTDIR)$(LOCDIR)/, $(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) xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --package-name=vdr-$(PLUGIN) --package-version=$(VERSION) --msgid-bugs-address='' -o $@ `ls $^` %.po: $(I18Npot) msgmerge -U --no-wrap --no-location --backup=none -q -N $@ $< @touch $@ $(I18Nmsgs): $(DESTDIR)$(LOCDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo install -D -m644 $< $@ .PHONY: i18n i18n: $(I18Nmo) $(I18Npot) install-i18n: $(I18Nmsgs) ### Targets: $(SOFILE): $(CLIENTOBJS) $(COMMONOBJS) ../tools/sockettools.a $(CXX) $(CXXFLAGS) $(LDFLAGS) -shared $^ -o $@ install-lib: $(SOFILE) install -D $^ $(DESTDIR)$(LIBDIR)/$^.$(APIVERSION) install: install-lib install-i18n clean: @-rm -f $(PODIR)/*.mo $(PODIR)/*.pot @-rm -f $(COMMONOBJS) $(CLIENTOBJS) $(DEPFILE) *.so *.tgz core* *~ vdr-plugin-streamdev/client/setup.c0000644000175000017500000000762313276341255017263 0ustar tobiastobias/* * $Id: setup.c,v 1.10 2010/06/08 05:55:17 schmirl Exp $ */ #include #include "client/setup.h" #include "client/streamdev-client.h" #ifndef MINPRIORITY #define MINPRIORITY -MAXPRIORITY #endif cStreamdevClientSetup StreamdevClientSetup; cStreamdevClientSetup::cStreamdevClientSetup(void) { StartClient = false; RemotePort = 2004; Timeout = 2; StreamFilters = false; HideMenuEntry = false; LivePriority = 0; MinPriority = MINPRIORITY; MaxPriority = MAXPRIORITY; #if APIVERSNUM >= 10700 NumProvidedSystems = 1; #endif strcpy(RemoteIp, ""); FilterSockBufSize = 0; } 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, "Timeout") == 0) Timeout = atoi(Value); else if (strcmp(Name, "StreamFilters") == 0) StreamFilters = atoi(Value); else if (strcmp(Name, "HideMenuEntry") == 0) HideMenuEntry = atoi(Value); else if (strcmp(Name, "LivePriority") == 0) LivePriority = atoi(Value); else if (strcmp(Name, "MinPriority") == 0) MinPriority = atoi(Value); else if (strcmp(Name, "MaxPriority") == 0) MaxPriority = atoi(Value); else if (strcmp(Name, "FilterSockBufSize") == 0) FilterSockBufSize = atoi(Value); #if APIVERSNUM >= 10700 else if (strcmp(Name, "NumProvidedSystems") == 0) NumProvidedSystems = atoi(Value); #endif else return false; return true; } cStreamdevClientMenuSetupPage::cStreamdevClientMenuSetupPage(cPluginStreamdevClient *Plugin) { m_Plugin = Plugin; m_NewSetup = StreamdevClientSetup; Add(new cMenuEditBoolItem(tr("Hide Mainmenu Entry"), &m_NewSetup.HideMenuEntry)); Add(new cMenuEditIntItem (tr("Simultaneously used Devices"), &m_NewSetup.StartClient, 0, STREAMDEV_MAXDEVICES)); Add(new cMenuEditIpItem (tr("Remote IP"), m_NewSetup.RemoteIp)); Add(new cMenuEditIntItem (tr("Remote Port"), &m_NewSetup.RemotePort, 0, 65535)); Add(new cMenuEditIntItem (tr("Timeout (s)"), &m_NewSetup.Timeout, 1, 15)); Add(new cMenuEditBoolItem(tr("Filter Streaming"), &m_NewSetup.StreamFilters)); if(m_NewSetup.StreamFilters) Add(new cMenuEditIntItem (tr("Filter SockBufSize"), &m_NewSetup.FilterSockBufSize, 0, 8192000)); Add(new cMenuEditIntItem (tr("Live TV Priority"), &m_NewSetup.LivePriority, MINPRIORITY, MAXPRIORITY)); Add(new cMenuEditIntItem (tr("Minimum Priority"), &m_NewSetup.MinPriority, MINPRIORITY, MAXPRIORITY)); Add(new cMenuEditIntItem (tr("Maximum Priority"), &m_NewSetup.MaxPriority, MINPRIORITY, MAXPRIORITY)); #if APIVERSNUM >= 10715 Add(new cMenuEditIntItem (tr("Broadcast Systems / Cost"), &m_NewSetup.NumProvidedSystems, 1, 15)); #elif APIVERSNUM >= 10700 Add(new cMenuEditIntItem (tr("Broadcast Systems / Cost"), &m_NewSetup.NumProvidedSystems, 1, 4)); #endif SetCurrent(Get(0)); } cStreamdevClientMenuSetupPage::~cStreamdevClientMenuSetupPage() { } void cStreamdevClientMenuSetupPage::Store(void) { 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("Timeout", m_NewSetup.Timeout); SetupStore("StreamFilters", m_NewSetup.StreamFilters); SetupStore("HideMenuEntry", m_NewSetup.HideMenuEntry); SetupStore("LivePriority", m_NewSetup.LivePriority); SetupStore("MinPriority", m_NewSetup.MinPriority); SetupStore("MaxPriority", m_NewSetup.MaxPriority); #if APIVERSNUM >= 10700 SetupStore("NumProvidedSystems", m_NewSetup.NumProvidedSystems); #endif SetupStore("FilterSockBufSize", m_NewSetup.FilterSockBufSize); StreamdevClientSetup = m_NewSetup; m_Plugin->Initialize(); } vdr-plugin-streamdev/client/Makefile-1.7.330000644000175000017500000000416213276341255020141 0ustar tobiastobias# # Makefile for a Video Disk Recorder plugin # # $Id: Makefile,v 1.2 2010/07/19 13:49:25 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-client ### Includes and Defines (add further entries here): DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"' ### The object files (add further files here): COMMONOBJS = ../common.o CLIENTOBJS = $(PLUGIN).o \ device.o filter.o setup.o socket.o ### The main target: .PHONY: all i18n dist clean all: libvdr-$(PLUGIN).so i18n ### Implicit rules: %.o: %.c $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $< ### Dependencies: MAKEDEP = $(CXX) -MM -MG DEPFILE = .dependencies $(DEPFILE): Makefile @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(CLIENTOBJS:%.o=%.c) $(COMMONOBJS:%.o=%.c) > $@ -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) xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --msgid-bugs-address='' -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: libvdr-$(PLUGIN).so: $(CLIENTOBJS) $(COMMONOBJS) ../tools/sockettools.a %.so: $(CXX) $(CXXFLAGS) -shared $^ -o $@ @cp --remove-destination $@ $(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) $(DEPFILE) $(PODIR)/*.mo $(PODIR)/*.pot *.so *.tgz core* *~ vdr-plugin-streamdev/client/socket.h0000644000175000017500000000424313276341255017413 0ustar tobiastobias/* * $Id: socket.h,v 1.8 2010/08/18 10:26:55 schmirl Exp $ */ #ifndef VDR_STREAMDEV_CLIENT_CONNECTION_H #define VDR_STREAMDEV_CLIENT_CONNECTION_H #include #include "common.h" #include "client/setup.h" #include #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 unsigned int m_ServerVersion; bool m_Prio; // server supports command PRIO int m_Priority; // current device priority bool m_Abort; // quit command pending time_t m_LastSignalUpdate; int m_LastSignalStrength; int m_LastSignalQuality; int m_LastDev; protected: /* Send Command, and return true if the command results in Expected. Returns false on failure. */ bool Command(const std::string &Command, uint Expected); /* Send the given command. Returns false on failure. */ bool Send(const std::string &Command); /* Fetch results from an ongoing Command. The status code and the buffer holding the server's response are stored in Code and Result if non-NULL. Returns false on failure. */ bool Receive(const std::string &Command, uint *Code = NULL, std::string *Result = NULL, uint TimeoutMs = StreamdevClientSetup.Timeout * 1000); 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 SupportsPrio() { return m_Prio; } unsigned int ServerVersion() { return m_ServerVersion; } int Priority() const { return m_Priority; } bool SetPriority(int Priority); bool SetPid(int Pid, bool On); bool SetFilter(ushort Pid, uchar Tid, uchar Mask, bool On); bool GetSignal(int *SignalStrength, int *SignalQuality, int *Dev); bool CloseDvr(void); bool SuspendServer(void); bool Quit(void); cTBSocket *DataSocket(eSocketId Id) const; }; #endif // VDR_STREAMDEV_CLIENT_CONNECTION_H vdr-plugin-streamdev/tools/0000755000175000017500000000000013276341255015631 5ustar tobiastobiasvdr-plugin-streamdev/tools/source.h0000644000175000017500000001133013276341255017300 0ustar tobiastobias#ifndef TOOLBOX_SOURCE_H #define TOOLBOX_SOURCE_H #include "tools/tools.h" #include #include /* 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 occurred. */ 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 vdr-plugin-streamdev/tools/tools.c0000644000175000017500000000030513276341255017133 0ustar tobiastobias#include "tools/tools.h" #include #include #include #include #include void *operator new(size_t nSize, void *p) throw () { return p; } vdr-plugin-streamdev/tools/tools.h0000644000175000017500000000346413276341255017151 0ustar tobiastobias#ifndef TOOLBOX_TOOLS_H #define TOOLBOX_TOOLS_H //#include //#include #include //#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 vdr-plugin-streamdev/tools/select.h0000644000175000017500000000443613276341255017270 0ustar tobiastobias#ifndef TOOLBOX_SELECT_H #define TOOLBOX_SELECT_H #include "tools/tools.h" #include /* 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 vdr-plugin-streamdev/tools/socket.c0000644000175000017500000001045213276341255017267 0ustar tobiastobias#include "tools/socket.h" #include "tools/select.h" #include #include #include #include #include #include // 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, unsigned int TimeoutMs) { 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; } if (TimeoutMs > 0 && ::fcntl(socket, F_SETFL, O_NONBLOCK) == -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) { if (TimeoutMs > 0 && errno == EINPROGRESS) { int so_error; socklen_t len = sizeof(so_error); cTBSelect select; select.Add(socket); if (select.Select(TimeoutMs) == -1 || ::getsockopt(socket, SOL_SOCKET, SO_ERROR, &so_error, &len) == -1) { ::close(socket); return false; } if (so_error) { errno = so_error; ::close(socket); return false; } } else { ::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; } vdr-plugin-streamdev/tools/Makefile0000644000175000017500000000113613276341255017272 0ustar tobiastobias# # Makefile for a Video Disk Recorder plugin # # $Id: Makefile,v 1.2 2010/07/19 13:49:44 schmirl Exp $ ### The object files (add further files here): OBJS = select.o socket.o source.o tools.o ### The main target: .PHONY: clean sockettools.a: $(OBJS) ar -rcs sockettools.a $(OBJS) ### Implicit rules: %.o: %.c $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $< ### Dependencies: MAKEDEP = $(CXX) -MM -MG DEPFILE = .dependencies $(DEPFILE): Makefile @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@ -include $(DEPFILE) ### Targets: clean: @-rm -f $(OBJS) $(DEPFILE) *.a core* *~ vdr-plugin-streamdev/tools/socket.h0000644000175000017500000001140413276341255017272 0ustar tobiastobias#ifndef TOOLBOX_SOCKET_H #define TOOLBOX_SOCKET_H #include "tools/tools.h" #include "tools/source.h" #include #include #include #include /* 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 within TimeoutMs milliseconds to the port given by Port of the target host given by Host in numbers-and-dots notation (i.e. "212.43.45.21"). A TimeoutMs of 0 will disable non-blocking IO for the connect call. Returns true if the connection attempt was successful and false otherwise, setting errno appropriately. */ virtual bool Connect(const std::string &Host, uint Port, uint TimeoutMs = 0); /* 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 occurred, 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 vdr-plugin-streamdev/tools/select.c0000644000175000017500000000223613276341255017257 0ustar tobiastobias#include "tools/select.h" #include #include #include #include #include #include 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; } vdr-plugin-streamdev/tools/source.c0000644000175000017500000000625113276341255017301 0ustar tobiastobias#include "tools/source.h" #include "tools/select.h" #include "common.h" #include #include #include #include #include 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; offs = 0; sel.Clear(); sel.Add(m_Filed, true); while (Length > 0) { int b; ms = TimeoutMs - starttime.Elapsed(); if (ms <= 0) { errno = ETIMEDOUT; return false; } 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; } } 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; } vdr-plugin-streamdev/.gitignore0000644000175000017500000000037213276341255016463 0ustar tobiastobias# Compiled Object files *.slo *.lo *.o # Compiled Dynamic libraries *.so *.dylib # Compiled Static libraries *.lai *.la *.a # Eclipse project files .project .cproject .settings # Generated dependency file .dependencies # translations *.mo *.pot vdr-plugin-streamdev/libdvbmpeg/0000755000175000017500000000000013276341255016604 5ustar tobiastobiasvdr-plugin-streamdev/libdvbmpeg/transform.h0000644000175000017500000001445613276341255021002 0ustar tobiastobias/* * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 #include #include #include #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_*/ vdr-plugin-streamdev/libdvbmpeg/remux.c0000644000175000017500000006552213276341255020122 0ustar tobiastobias/* * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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){ count++; init_pes(&pes); if (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; } 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; 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; 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; #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); } } vdr-plugin-streamdev/libdvbmpeg/ctools.c0000644000175000017500000011307113276341255020256 0ustar tobiastobias/* * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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;istuffing;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 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 ){ 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 == (off_t) -1) 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){ free(buf); 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 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); } 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; } } vdr-plugin-streamdev/libdvbmpeg/transform.c0000644000175000017500000016001613276341255020767 0ustar tobiastobias/* * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 #include #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 ); } vdr-plugin-streamdev/libdvbmpeg/remux.h0000644000175000017500000000624513276341255020124 0ustar tobiastobias/* * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * Or, point your browser to http://www.gnu.org/copyleft/gpl.html * * The author can be reached at mocm@metzlerbros.de, */ #include #include #include #include #include #include #include #include #include #include //#include #include #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_*/ vdr-plugin-streamdev/libdvbmpeg/Makefile0000644000175000017500000000126013276341255020243 0ustar tobiastobias# # Makefile for a Video Disk Recorder plugin # # $Id: Makefile,v 1.5 2010/07/30 10:49:28 schmirl Exp $ ### The object files (add further files here): OBJS = ctools.o remux.o ringbuffy.o transform.o ### Disable attribute warn_unused_result DEFINES += -U_FORTIFY_SOURCE ### The main target: .PHONY: clean libdvbmpegtools.a: $(OBJS) ar -rcs libdvbmpegtools.a $(OBJS) ### Implicit rules: %.o: %.c $(CC) $(CFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $< ### Dependencies: MAKEDEP = $(CC) -MM -MG DEPFILE = .dependencies $(DEPFILE): Makefile @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@ -include $(DEPFILE) ### Targets: clean: @-rm -f $(OBJS) $(DEPFILE) *.a core* *~ vdr-plugin-streamdev/libdvbmpeg/ctools.h0000644000175000017500000002264713276341255020273 0ustar tobiastobias/* * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * Or, point your browser to http://www.gnu.org/copyleft/gpl.html * * The author can be reached at mocm@metzlerbros.de */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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); ssize_t save_read(int fd, void *buf, size_t count); #ifdef __cplusplus } #endif /* __cplusplus */ #endif /*_CTOOLS_H_*/ vdr-plugin-streamdev/libdvbmpeg/ringbuffy.c0000644000175000017500000001054313276341255020746 0ustar tobiastobias/* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "ringbuffy.h" #include 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; pos = rbuf->read_pos; diff = rbuf->write_pos - pos; free = (diff >= 0) ? diff : rbuf->size+diff; return free; } vdr-plugin-streamdev/libdvbmpeg/ringbuffy.h0000644000175000017500000000310313276341255020745 0ustar tobiastobias/* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #ifndef RINGBUFFY_H #define RINGBUFFY_H #include #include #include #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 */ vdr-plugin-streamdev/libdvbmpeg/.cvsignore0000644000175000017500000000001013276341255020573 0ustar tobiastobias.depend vdr-plugin-streamdev/common.h0000644000175000017500000000263713276341255016142 0ustar tobiastobias/* * $Id: common.h,v 1.16 2010/07/19 13:49:24 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 #include #include "tools/socket.h" #ifdef DEBUG #include #include #define Dprintf(fmt, x...) {\ struct timespec ts;\ clock_gettime(CLOCK_MONOTONIC, &ts);\ fprintf(stderr, "%ld.%.3ld [%d] "fmt,\ ts.tv_sec, ts.tv_nsec / 1000000, cThread::ThreadId(), ##x);\ } #else #define Dprintf(x...) #endif #define MAXPARSEBUFFER KILOBYTE(16) /* Service ID for loop prevention */ #define LOOP_PREVENTION_SERVICE "StreamdevLoopPrevention" /* 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, stEXT, stTSPIDS, st_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 vdr-plugin-streamdev/remux/0000755000175000017500000000000013276341255015631 5ustar tobiastobiasvdr-plugin-streamdev/remux/ts2es.c0000644000175000017500000000566513276341255017051 0ustar tobiastobias#include "remux/ts2es.h" #include "server/streamer.h" #include "common.h" #include // 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; } vdr-plugin-streamdev/remux/tsremux.h0000644000175000017500000000124713276341255017515 0ustar tobiastobias#ifndef VDR_STREAMDEV_TSREMUX_H #define VDR_STREAMDEV_TSREMUX_H #include "libdvbmpeg/transform.h" #include // 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 vdr-plugin-streamdev/remux/extern.c0000644000175000017500000002317513276341255017312 0ustar tobiastobias#include "remux/extern.h" #include "server/server.h" #include "server/connection.h" #include "server/streamer.h" #include #include #include #include #include #include #include #include namespace Streamdev { #define MAXENV 63 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, const cServerConnection *Connection, const cChannel *Channel, const cPatPmtParser *PatPmt, const int *Apids, const int *Dpids); virtual ~cTSExt(); void Put(const uchar *Data, int Count); }; } // namespace Streamdev using namespace Streamdev; cTSExt::cTSExt(cRingBufferLinear *ResultBuffer, const cServerConnection *Connection, const cChannel *Channel, const cPatPmtParser *PatPmt, const int *Apids, const int *Dpids): 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 char *env[MAXENV + 1]; int i = 0; #define ADDENV(x...) if (asprintf(&env[i++], x) < 0) i-- // add channel ID, name and pids to environment if (Channel) { ADDENV("REMUX_CHANNEL_ID=%s", *Channel->GetChannelID().ToString()); ADDENV("REMUX_CHANNEL_NAME=%s", Channel->Name()); ADDENV("REMUX_VTYPE=%d", Channel->Vtype()); if (Channel->Vpid()) ADDENV("REMUX_VPID=%d", Channel->Vpid()); if (Channel->Ppid() != Channel->Vpid()) ADDENV("REMUX_PPID=%d", Channel->Ppid()); if (Channel->Tpid()) ADDENV("REMUX_TPID=%d", Channel->Tpid()); } else if (PatPmt) { ADDENV("REMUX_VTYPE=%d", PatPmt->Vtype()); if (PatPmt->Vpid()) ADDENV("REMUX_VPID=%d", PatPmt->Vpid()); if (PatPmt->Ppid() != PatPmt->Vpid()) ADDENV("REMUX_PPID=%d", PatPmt->Ppid()); } std::string buffer; if (Apids && *Apids) { for (const int *pid = Apids; *pid; pid++) (buffer += (const char *) itoa(*pid)) += (*(pid + 1) ? " " : ""); ADDENV("REMUX_APID=%s", buffer.c_str()); buffer.clear(); for (const int *pid = Apids; *pid; pid++) { int j; if (Channel) { for (j = 0; Channel->Apid(j) && Channel->Apid(j) != *pid; j++) ; (buffer += Channel->Alang(j)) += (*(pid + 1) ? " " : ""); } else if (PatPmt) { for (j = 0; PatPmt->Apid(j) && PatPmt->Apid(j) != *pid; j++) ; (buffer += PatPmt->Alang(j)) += (*(pid + 1) ? " " : ""); } } ADDENV("REMUX_ALANG=%s", buffer.c_str()); } if (Dpids && *Dpids) { buffer.clear(); for (const int *pid = Dpids; *pid; pid++) (buffer += (const char *) itoa(*pid)) += (*(pid + 1) ? " " : ""); ADDENV("REMUX_DPID=%s", buffer.c_str()); buffer.clear(); for (const int *pid = Dpids; *pid; pid++) { int j; if (Channel) { for (j = 0; Channel->Dpid(j) && Channel->Dpid(j) != *pid; j++) ; (buffer += Channel->Dlang(j)) += (*(pid + 1) ? " " : ""); } else if (PatPmt) { for (j = 0; PatPmt->Dpid(j) && PatPmt->Dpid(j) != *pid; j++) ; (buffer += PatPmt->Dlang(j)) += (*(pid + 1) ? " " : ""); } } ADDENV("REMUX_DLANG=%s", buffer.c_str()); } if (Channel && Channel->Spid(0)) { buffer.clear(); for (const int *pid = Channel->Spids(); *pid; pid++) (buffer += (const char *) itoa(*pid)) += (*(pid + 1) ? " " : ""); ADDENV("REMUX_SPID=%s", buffer.c_str()); buffer.clear(); for (int j = 0; Channel->Spid(j); j++) (buffer += Channel->Slang(j)) += (Channel->Spid(j + 1) ? " " : ""); ADDENV("REMUX_SLANG=%s", buffer.c_str()); } else if (PatPmt && PatPmt->Spid(0)) { buffer.clear(); for (const int *pid = PatPmt->Spids(); *pid; pid++) (buffer += (const char *) itoa(*pid)) += (*(pid + 1) ? " " : ""); ADDENV("REMUX_SPID=%s", buffer.c_str()); buffer.clear(); for (int j = 0; PatPmt->Spid(j); j++) (buffer += PatPmt->Slang(j)) += (PatPmt->Spid(j + 1) ? " " : ""); ADDENV("REMUX_SLANG=%s", buffer.c_str()); } if (Connection) { // add vars for a CGI like interface // the following vars are not implemented: // REMOTE_HOST, REMOTE_IDENT, REMOTE_USER // CONTENT_TYPE, CONTENT_LENGTH, // SCRIPT_NAME, PATH_TRANSLATED, GATEWAY_INTERFACE ADDENV("REMOTE_ADDR=%s", Connection->RemoteIp().c_str()); ADDENV("SERVER_NAME=%s", Connection->LocalIp().c_str()); ADDENV("SERVER_PORT=%d", Connection->LocalPort()); ADDENV("SERVER_PROTOCOL=%s", Connection->Protocol()); ADDENV("SERVER_SOFTWARE=%s", VERSION); for (tStrStrMap::const_iterator it = Connection->Headers().begin(); it != Connection->Headers().end(); ++it) { if (i >= MAXENV) { esyslog("streamdev-server: Too many headers for externremux.sh"); break; } ADDENV("%s=%s", it->first.c_str(), it->second.c_str()); } // look for section parameters: /path;param1=value1;param2=value2/ std::string::size_type begin, end; const static std::string PATH_INFO("PATH_INFO"); tStrStrMap::const_iterator it_pathinfo = Connection->Headers().find(PATH_INFO); const std::string& path = it_pathinfo == Connection->Headers().end() ? "/" : it_pathinfo->second; begin = path.find(';', 0); begin = path.find_first_not_of(';', begin); end = path.find_first_of(";/", begin); while (begin != std::string::npos && path[begin] != '/') { std::string param = path.substr(begin, end - begin); std::string::size_type e = param.find('='); if (i >= MAXENV) { esyslog("streamdev-server: Too many parameters for externremux.sh"); break; } else if (e > 0 && e != std::string::npos) { ADDENV("REMUX_PARAM_%s", param.c_str()); } else esyslog("streamdev-server: Invalid externremux.sh parameter %s", param.c_str()); begin = path.find_first_not_of(';', end); end = path.find_first_of(";/", begin); } } env[i] = NULL; 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 if (setpgid(0, 0) == -1) esyslog("streamdev-server: externremux setpgid failed: %m"); if (access(opt_remux, X_OK) == -1) { esyslog("streamdev-server %s: %m", opt_remux); _exit(-1); } if (execle("/bin/sh", "sh", "-c", opt_remux, NULL, env) == -1) { esyslog("streamdev-server: externremux script '%s' execution failed: %m", opt_remux); _exit(-1); } // 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 && errno != EAGAIN) { 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(const cServerConnection *Connection, const cChannel *Channel, const int *Apids, const int *Dpids): m_ResultBuffer(new cRingBufferLinear(WRITERBUFSIZE)), m_Remux(new cTSExt(m_ResultBuffer, Connection, Channel, NULL, Apids, Dpids)) { m_ResultBuffer->SetTimeouts(500, 100); } cExternRemux::cExternRemux(const cServerConnection *Connection, const cPatPmtParser *PatPmt, const int *Apids, const int *Dpids): m_ResultBuffer(new cRingBufferLinear(WRITERBUFSIZE)), m_Remux(new cTSExt(m_ResultBuffer, Connection, NULL, PatPmt, Apids, Dpids)) { 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; } vdr-plugin-streamdev/remux/ts2pes.c0000644000175000017500000021144613276341255017225 0ustar tobiastobias/* * 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 , * 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 #include #include 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 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 //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 vdr-plugin-streamdev/remux/extern.h0000644000175000017500000000152313276341255017310 0ustar tobiastobias#ifndef VDR_STREAMDEV_EXTERNREMUX_H #define VDR_STREAMDEV_EXTERNREMUX_H #include "remux/tsremux.h" #include #include class cChannel; class cPatPmtParser; class cServerConnection; namespace Streamdev { class cTSExt; class cExternRemux: public cTSRemux { private: cRingBufferLinear *m_ResultBuffer; cTSExt *m_Remux; public: cExternRemux(const cServerConnection *Connection, const cChannel *Channel, const int *APids, const int *Dpids); cExternRemux(const cServerConnection *Connection, const cPatPmtParser *PatPmt, const int *APids, const int *Dpids); 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 vdr-plugin-streamdev/remux/tsremux.c0000644000175000017500000000611013276341255017502 0ustar tobiastobias#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; } vdr-plugin-streamdev/remux/ts2ps.c0000644000175000017500000001204113276341255017046 0ustar tobiastobias#ifdef STREAMDEV_PS #include "remux/ts2ps.h" #include "server/streamer.h" #include #include 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; } #endif vdr-plugin-streamdev/remux/ts2es.h0000644000175000017500000000111213276341255017035 0ustar tobiastobias#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 vdr-plugin-streamdev/remux/Makefile0000644000175000017500000000112513276341255017270 0ustar tobiastobias# # Makefile for a Video Disk Recorder plugin # # $Id: Makefile,v 1.2 2010/07/19 13:49:28 schmirl Exp $ ### The object files (add further files here): OBJS = tsremux.o ts2es.o ts2pes.o ts2ps.o extern.o ### The main target: .PHONY: clean remux.a: $(OBJS) ar -rcs remux.a $^ ### Implicit rules: %.o: %.c $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $< ### Dependencies: MAKEDEP = $(CXX) -MM -MG DEPFILE = .dependencies $(DEPFILE): Makefile @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) > $@ -include $(DEPFILE) ### Targets: clean: @-rm -f $(OBJS) $(DEPFILE) *.a core* *~ vdr-plugin-streamdev/remux/ts2ps.h0000644000175000017500000000147113276341255017060 0ustar tobiastobias#ifdef STREAMDEV_PS #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 #endif vdr-plugin-streamdev/remux/ts2pes.h0000644000175000017500000000351213276341255017223 0ustar tobiastobias/* * 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 vdr-plugin-streamdev/streamdev-server/0000755000175000017500000000000013276341255017767 5ustar tobiastobiasvdr-plugin-streamdev/streamdev-server/streamdevhosts.conf0000644000175000017500000000103213276341255023705 0ustar tobiastobias# # 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 (DON'T DO THAT! USE AUTHENTICATION) vdr-plugin-streamdev/streamdev-server/externremux.sh0000755000175000017500000002137713276341255022726 0ustar tobiastobias#!/bin/bash # # externremux.sh - sample remux script using mencoder for remuxing. # # Install this script as VDRCONFDIR/plugins/streamdev-server/externremux.sh # # The parameter QUALITY selects the default remux parameters. Adjust # to your needs and point your web browser to http://servername:3000/ext/ # To select different remux parameters on the fly, insert a semicolon # followed by the name and value of the requested parameter, e.g: # e.g. http://servername:3000/ext;QUALITY=WLAN11;VBR=512/ # The following parameters are recognized: # # PROG actual remux program # VC video codec # VBR video bitrate (kbit) # VOPTS custom video options # WIDTH scale video to width # HEIGHT scale video to height # FPS output frames per second # AC audio codec # ABR audio bitrate (kbit) # AOPTS custom audio options # ########################################################################## ### GENERAL CONFIG START ### # Pick one of DSL1000/DSL2000/DSL3000/DSL6000/DSL16000/LAN10/WLAN11/WLAN54 QUALITY='DSL1000' # Program used for logging (logging disabled if empty) LOGGER=logger # Path and name of FIFO FIFO=/tmp/externremux-${RANDOM:-$$} # Default remux program (cat/mencoder/ogg) PROG=mencoder # Use mono if $ABR is lower than this value ABR_MONO=64 ### ### GENERAL CONFIG END ### MENCODER CONFIG START ### # mencoder binary MENCODER=mencoder # verbosity from all=-1 to all=9 (-msglevel ...) MENCODER_MSGLEVEL=all=1 ### video part # Default video codec (e.g. lavc/x264/copy) MENCODER_VC=lavc # Default video options if lavc is used (-ovc lavc -lavcopts ...) MENCODER_LAVC_VOPTS=vcodec=mpeg4 # Default video options if x264 is used (-ovc x264 -x264encopts ...) MENCODER_X264_VOPTS=threads=auto ### audio part # Audio language to use if several audio PIDs are available (-alang ...) MENCODER_ALANG=eng # Default audio codec (e.g. lavc/mp3lame/faac/copy) MENCODER_AC=mp3lame # Default audio options if lavc is used (-oac lavc -lavcopts ...) MENCODER_LAVC_AOPTS=acodec=mp2 # Default audio options if mp3lame is used (-oac mp3lame -lameopts ...) MENCODER_LAME_AOPTS= # Default audio options if faac is used (-oac faac -faacopts ...) MENCODER_FAAC_AOPTS= ### ### MENCODER CONFIG END ### OGG CONFIG START ### # ffmpeg2theora binary OGG=ffmpeg2theora # speedlevel - lower value gives better quality but is slower (0..2) OGG_SPEED=1 # videoquality - higher value gives better quality but is slower (0..10) OGG_VQUALITY=0 # audioquality - higher value gives better quality but is slower (0..10) OGG_AQUALITY=0 # aspect ratio used for scaling if only one of HEIGHT/WIDTH given (16/9 or 4/3) OGG_ASPECT='4 / 3' ### ### OGG CONFIG END ########################################################################## function hasOpt { echo "$1" | grep -q "\b${2}\b"; } # $1: concatenation of already set option=value pairs # $2-$n: option=value pairs to be echod if the option is not present in $1 function addOpts { local opts="$1" shift while [ $# -gt 0 ]; do hasOpt "$opts" ${1%%=*}= || echo $1 shift done } function isNumeric() { echo "$@" | grep -q '^-\?[0-9]\{1,\}$'; } function remux_cat { startReply exec 3<&0 cat 0<&3 >"$FIFO" & } function remux_mencoder { # lavc may be used for video and audio LAVCOPTS=() # Assemble video options VC=${REMUX_PARAM_VC:-$MENCODER_VC} VOPTS=${REMUX_PARAM_VOPTS} FPS=${REMUX_PARAM_FPS:-$FPS} # if only one of HEIGHT/WIDTH given: # have mencoder calculate other value depending on actual aspect ratio if [ "$HEIGHT" -a -z "$WIDTH" ]; then WIDTH=-3 elif [ "$WIDTH" -a -z "$HEIGHT" ]; then HEIGHT=-3 fi case "$VC" in lavc) LAVCOPTS=( ${VOPTS} $(IFS=$IFS:; addOpts "$VOPTS" $MENCODER_LAVC_VOPTS) ${VBR:+vbitrate=$VBR} ) [ ${#LAVCOPTS[*]} -gt 0 ] && VOPTS=$(IFS=:; echo -lavcopts "${LAVCOPTS[*]}") ;; x264) isNumeric "$HEIGHT" && [ $HEIGHT -lt 0 -a $HEIGHT -gt -8 ] && ((HEIGHT-=8)) isNumeric "$WIDTH" && [ $WIDTH -lt 0 -a $WIDTH -gt -8 ] && ((WIDTH-=8)) X264OPTS=( ${VOPTS} $(IFS=$IFS:; addOpts "$VOPTS" $MENCODER_X264_VOPTS) ${VBR:+bitrate=$VBR} ) [ ${#X264OPTS[*]} -gt 0 ] && VOPTS=$(IFS=:; echo -x264encopts "${X264OPTS[*]}") ;; copy) VOPTS= ;; *) error "Unknown video codec '$VC'" ;; esac # Assemble audio options AC=${REMUX_PARAM_AC:-$MENCODER_AC} AOPTS=${REMUX_PARAM_AOPTS} case "$AC" in lavc) LAVCOPTS=( ${LAVCOPTS[*]} ${AOPTS} $(IFS=$IFS:; addOpts "$AOPTS" $MENCODER_LAVC_AOPTS) ${ABR:+abitrate=$ABR} ) [ ${#LAVCOPTS[*]} -gt 0 ] && AOPTS=$(IFS=:; echo -lavcopts "${LAVCOPTS[*]}") # lavc used for video and audio decoding - wipe out VOPTS as video options became part of AOPTS [ "$VC" = lavc ] && VOPTS= ;; mp3lame) LAMEOPTS=( ${AOPTS} $(isNumeric "${ABR}" && [ "${ABR}" -lt "$ABR_MONO" ] && ! hasOpt "${AOPTS}" mode ] && echo 'mode=3') $(IFS=$IFS:; addOpts "$AOPTS" $MENCODER_LAME_AOPTS) ${ABR:+preset=$ABR} ) [ ${#LAMEOPTS[*]} -gt 0 ] && AOPTS=$(IFS=:; echo -lameopts "${LAMEOPTS[*]}") ;; faac) FAACOPTS=( ${AOPTS} $(IFS=$IFS:; addOpts "$AOPTS" $MENCODER_FAAC_AOPTS) ${ABR:+br=$ABR} ) [ ${#FAACOPTS[*]} -gt 0 ] && AOPTS=$(IFS=:; echo -faacopts "${FAACOPTS[*]}") ;; copy) AOPTS= ;; *) error "Unknown audio codec '$AC'" ;; esac startReply exec 3<&0 echo $MENCODER \ ${MENCODER_MSGLEVEL:+-msglevel $MENCODER_MSGLEVEL} \ -ovc $VC $VOPTS \ -oac $AC $AOPTS \ ${MENCODER_ALANG:+-alang $MENCODER_ALANG} \ ${WIDTH:+-vf scale=$WIDTH:$HEIGHT -zoom} \ ${FPS:+-ofps $FPS} \ -o "$FIFO" -- - >&2 $MENCODER \ ${MENCODER_MSGLEVEL:+-msglevel $MENCODER_MSGLEVEL} \ -ovc $VC $VOPTS \ -oac $AC $AOPTS \ ${MENCODER_ALANG:+-alang $MENCODER_ALANG} \ ${WIDTH:+-vf scale=$WIDTH:$HEIGHT -zoom} \ ${FPS:+-ofps $FPS} \ -o "$FIFO" -- - 0<&3 >/dev/null & } function remux_ogg { VOPTS=${REMUX_PARAM_VOPTS//[:=]/ } AOPTS=${REMUX_PARAM_AOPTS//[:=]/ } # if only one of HEIGHT/WIDTH given: # calculate other value depending on configured aspect ratio # trim to multiple of 8 if [ "$HEIGHT" -a -z "$WIDTH" ]; then WIDTH=$((HEIGHT * $OGG_ASPECT / 8 * 8)) elif [ "$WIDTH" -a -z "$HEIGHT" ]; then HEIGHT=$(($WIDTH * $( echo $OGG_ASPECT | sed 's#^\([0-9]\+\) */ *\([0-9]\+\)$#\2 / \1#') / 8 * 8)) fi OGGOPTS=( ${VOPTS} ${VBR:+--videobitrate $VBR} $(hasOpt "${VOPTS}" videoquality || echo "--videoquality $OGG_VQUALITY") $(hasOpt "${VOPTS}" speedlevel || echo "--speedlevel $OGG_SPEED") ${AOPTS} ${ABR:+--audiobitrate $ABR} $(isNumeric "${ABR}" && [ "${ABR}" -lt "$ABR_MONO" ] && ! hasOpt "${AOPTS}" channels ] && echo '--channels 1') $(hasOpt "${AOPTS}" audioquality || echo "--audioquality $OGG_AQUALITY") $(hasOpt "${AOPTS}" audiostream || echo '--audiostream 1') ) startReply exec 3<&0 echo $OGG --format ts \ ${OGGOPTS[*]} \ ${WIDTH:+--width $WIDTH --height $HEIGHT} \ --title "VDR Streamdev: ${REMUX_CHANNEL_NAME}" \ --output "$FIFO" -- - 0<&3 >&2 $OGG --format ts \ ${OGGOPTS[*]} \ ${WIDTH:+--width $WIDTH --height $HEIGHT} \ --title "VDR Streamdev: ${REMUX_CHANNEL_NAME}" \ --output "$FIFO" -- - 0<&3 >/dev/null & } function error { if [ "$SERVER_PROTOCOL" = HTTP ]; then echo -ne "Content-type: text/plain\r\n" echo -ne '\r\n' echo "$*" fi echo "$*" >&2 exit 1 } function startReply { if [ "$SERVER_PROTOCOL" = HTTP ]; then # send content-type and custom headers echo -ne "Content-type: ${CONTENTTYPE}\r\n" for header in "${HEADER[@]}"; do echo -ne "$header\r\n"; done echo -ne '\r\n' # abort after headers [ "$REQUEST_METHOD" = HEAD ] && exit 0 fi # create FIFO and read from it in the background mkfifo "$FIFO" trap "trap '' EXIT HUP INT TERM ABRT PIPE CHLD; kill -INT 0; sleep 1; fuser -k '$FIFO'; rm '$FIFO'" EXIT HUP INT TERM ABRT PIPE CHLD cat "$FIFO" <&- & } HEADER=() [ "$LOGGER" ] && exec 2> >($LOGGER -t "vdr: [$$] ${0##*/}" 2>&-) # set default content-types case "$REMUX_VPID" in ''|0|1) CONTENTTYPE='audio/mpeg';; *) CONTENTTYPE='video/mpeg';; esac QUALITY=${REMUX_PARAM_QUALITY:-$QUALITY} case "$QUALITY" in DSL1000|dsl1000) VBR=96; ABR=16; WIDTH=160;; DSL2000|dsl2000) VBR=128; ABR=16; WIDTH=160;; DSL3000|dsl3000) VBR=256; ABR=16; WIDTH=320;; DSL6000|dsl6000) VBR=378; ABR=32; WIDTH=320;; DSL16000|dsl16000) VBR=512; ABR=32; WIDTH=480;; WLAN11|wlan11) VBR=768; ABR=64; WIDTH=640;; WLAN54|wlan54) VBR=2048; ABR=128; WIDTH=;; LAN10|lan10) VBR=4096; ABR=; WIDTH=;; *) error "Unknown quality '$QUALITY'";; esac ABR=${REMUX_PARAM_ABR:-$ABR} VBR=${REMUX_PARAM_VBR:-$VBR} WIDTH=${REMUX_PARAM_WIDTH:-$WIDTH} HEIGHT=${REMUX_PARAM_HEIGHT:-$HEIGHT} PROG=${REMUX_PARAM_PROG:-$PROG} case "$PROG" in cat) remux_cat;; mencoder) remux_mencoder;; ogg) remux_ogg;; *) error "Unknown remuxer '$PROG'";; esac set -o monitor wait vdr-plugin-streamdev/Makefile0000644000175000017500000000474713276341255016145 0ustar tobiastobias# # Makefile for a Video Disk Recorder plugin # # $Id: $ # 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 directory environment: # Use package data if installed...otherwise assume we're under the VDR source directory: PKGCFG = $(if $(VDRDIR),$(shell pkg-config --variable=$(1) $(VDRDIR)/vdr.pc),$(shell pkg-config --variable=$(1) vdr || pkg-config --variable=$(1) ../../../vdr.pc)) LIBDIR = $(call PKGCFG,libdir) LOCDIR = $(call PKGCFG,locdir) PLGCFG = $(call PKGCFG,plgcfg) # TMPDIR ?= /tmp ### The compiler options: export CFLAGS = $(call PKGCFG,cflags) export CXXFLAGS = $(call PKGCFG,cxxflags) ### The version number of VDR's plugin API: APIVERSION = $(call PKGCFG,apiversion) ### Allow user defined options to overwrite defaults: -include $(PLGCFG) ### export all vars for sub-makes, using absolute paths LIBDIR := $(shell cd $(LIBDIR) >/dev/null 2>&1 && pwd) LOCDIR := $(shell cd $(LOCDIR) >/dev/null 2>&1 && pwd) export unexport PLUGIN ### The name of the distribution archive: ARCHIVE = $(PLUGIN)-$(VERSION) PACKAGE = vdr-$(ARCHIVE) ### Includes and Defines (add further entries here): INCLUDES += -I$(VDRDIR)/include -I.. export INCLUDES DEFINES += -D_GNU_SOURCE ifdef DEBUG DEFINES += -DDEBUG endif ifdef STREAMDEV_DEBUG DEFINES += -DDEBUG endif ### The main target: .PHONY: all client server install install-client install-server dist clean all: client server ### Targets: client: $(MAKE) -C ./tools $(MAKE) -C ./client server: $(MAKE) -C ./tools $(MAKE) -C ./libdvbmpeg $(MAKE) -C ./remux $(MAKE) -C ./server install-client: client $(MAKE) -C ./client install # installs to $(LIBDIR)/libvdr-streamdev-client.so.$(APIVERSION) install-server: server $(MAKE) -C ./server install # installs to $(LIBDIR)/libvdr-streamdev-server.so.$(APIVERSION) install: install-client install-server dist: clean @-rm -rf $(TMPDIR)/$(ARCHIVE) @mkdir $(TMPDIR)/$(ARCHIVE) @cp -a * $(TMPDIR)/$(ARCHIVE) @tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE) @-rm -rf $(TMPDIR)/$(ARCHIVE) @echo Distribution package created as $(PACKAGE).tgz clean: $(MAKE) -C ./tools clean $(MAKE) -C ./libdvbmpeg clean $(MAKE) -C ./remux clean $(MAKE) -C ./client clean $(MAKE) -C ./server clean vdr-plugin-streamdev/Makefile-1.7.330000644000175000017500000000512513276341255016663 0ustar tobiastobias# # Makefile for a Video Disk Recorder plugin # # $Id: Makefile,v 1.23 2010/08/02 10:36:59 schmirl Exp $ # The main source file name. # PLUGIN = streamdev ### The C/C++ compiler and options: CC ?= gcc CFLAGS ?= -g -O2 -Wall CXX ?= g++ CXXFLAGS ?= -g -O2 -Wall -Woverloaded-virtual -Wno-parentheses ### 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 directory environment: VDRDIR = ../../.. LIBDIR = ../../lib TMPDIR = /tmp ### 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') TSPLAYVERSNUM = $(shell grep 'define TSPLAY_PATCH_VERSION ' $(VDRDIR)/device.h | awk '{ print $$3 }') ### Allow user defined options to overwrite defaults: ifeq ($(shell test $(APIVERSNUM) -ge 10713; echo $$?),0) include $(VDRDIR)/Make.global else ifeq ($(shell test $(APIVERSNUM) -ge 10704 -o -n "$(TSPLAYVERSNUM)" ; echo $$?),0) DEFINES += -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE CFLAGS += -fPIC CXXFLAGS += -fPIC else CFLAGS += -fPIC CXXFLAGS += -fPIC endif endif -include $(VDRDIR)/Make.config ### export all vars for sub-makes, using absolute paths VDRDIR := $(shell cd $(VDRDIR) >/dev/null 2>&1 && pwd) LIBDIR := $(shell cd $(LIBDIR) >/dev/null 2>&1 && pwd) export unexport PLUGIN ### 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 ifdef DEBUG DEFINES += -DDEBUG endif ifdef STREAMDEV_DEBUG DEFINES += -DDEBUG endif ### The main target: .PHONY: all client server dist clean all: client server ### Targets: client: $(MAKE) -C ./tools $(MAKE) -C ./client # installs to $(LIBDIR)/libvdr-streamdev-client.so.$(APIVERSION) server: $(MAKE) -C ./tools $(MAKE) -C ./libdvbmpeg $(MAKE) -C ./remux $(MAKE) -C ./server # installs to $(LIBDIR)/libvdr-streamdev-server.so.$(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: $(MAKE) -C ./tools clean $(MAKE) -C ./libdvbmpeg clean $(MAKE) -C ./remux clean $(MAKE) -C ./client clean $(MAKE) -C ./server clean vdr-plugin-streamdev/HISTORY0000644000175000017500000007073713276341255015573 0ustar tobiastobiasVDR Plugin 'streamdev' Revision History --------------------------------------- - fixed compilation for VDR 2.3.7 (thanks to Jasmin J) - added .gitignore (thanks to Jasmin J) - fixed some warnings in libdvbmpeg (thanks to Jasmin J) - fixed lseek error check in libdvbmpeg (thanks to David Binderman) - server compatibility with VDR 2.3.1 (thanks to Christopher Reimer and Matthias Senzel) - client compatibility with VDR 2.3.1 - use cReceiver::SetPriority(...) in VDR 2.1.4+ - doubled size of client's filter buffer (suggested by Toerless Eckert) - make sure TimedWrite(...) doesn't return failure after a slow but successful write operation (thanks to Toerless Eckert) - fixed problems related to VTP filter streaming like ringbuffer overflows, stuttering or aborting video stream (thanks to Toerless Eckert) - added Polish translation (thanks to Tomasz Maciej Nowak) - converted suspend.dat into proper PES format (thanks to Toerless Eckert) - implemented GetCurrentlyTunedTransponder() on client (thanks to Martin1234) - added service call returning the number of clients (suggested by Martin1234) - added SVDRP commands to list and disconnect clients (thanks to Guy Martin) - fixed recplayer issues with large TS files (>4GB) - Don't abort externremux when internal read buffer is empty - Implemented remuxing of recordings - Make ChannelChange retune only if CA IDs changed (thanks to Oliver Wagner) - Implemented VDR 2.1.4 cStatus::ChannelChange(...) - Call detach only if receiver is attached - Try changing to other device when receiver got detached - In TSPIDS mode, create and attach receiver with empty pid list to occupy dev - Restructured server classes - New option for server side live TV buffer to prevent buffer underruns 2013-11-28: Version 0.6.1 - Updated Slovak translation (thanks to Milan Hrala) - Updated Finnish translation (thanks to Rolf Ahrenberg) - Disabled PS remuxer which is said to produce anything but PS - The patches intcamdevices and ignore_missing_cam are no longer required on VDR >= 1.7.30. The localchannelprovide patch became obsolete with VDR 1.7.21. - Added option to suspend live TV when the server starts - Set device occupied when streamdev switches away LiveTV on the server, to reduce the risk that the VDR main loop immediately switches back, resulting in a black screen on the client (reported by hummel99) - Fixed channel switch issues with priority > 0 (reported by hummel99) - Removed noisy debug messages - Fixed HTTP menu destruction - API change of VDR 2.1.2 - Fixed priority handling, messed up when adding multi-device support - Added HTTP "Server" header (suggested by hivdr) - Ignore dummy file extensions (.ts, .vob, .vdr) when parsing HTTP URIs - Select start position for replaying a recording by parameter pos=. Supported values are resume, mark.#, time.#, frame.# or a plain # representing a percentage if < 100 or a byte position otherwise (thanks to hivdr) - Start cSuspendCtl hidden or it will prevent idle shutdown (thanks to thomasjfox) - Fixed recordings menu inode numbers: ino_t is a long long on some systems - Updated Slovak translation (thanks to Milan Hrala) - Adapted Makefiles to VDR 1.7.36+ (thanks to macmenot). Old makefiles have been renamed to Makefile-1.7.33. - API changes of VDR 1.7.38 (thanks to mal@vdr-developer) - Added simple recordings menu in HTTP server - Restructured menuHTTP classes - Added RSS format for HTTP menus - Recordings can now also be selected by struct stat "st_dev:st_ino.rec" - Implemented multi-device support for streamdev client (suggested by johns) - Basic support for HTTP streaming of recordings - Close writer when streamer is finished - Don't abort VTP connection if filter stream is broken - Restructured cStreamdevStreamer: Moved inbound buffer into actual subclass. - In cStreamdevStreamer dropped Activate(bool) and moved its code into Start(). - Moved cStreamdevFilterStreamer to livefilter.[hc] - Return HTTP/1.1 compliant response headers plus some always useful headers - Return HTTP URL parameters ending with ".dlna.org" as response headers - Store HTTP URL parameters in a map - Support HTTP HEAD requests with external remuxer - Fixed always using priority 0 for HTTP HEAD requests - Start writer right after creating it - Corrected typos (thanks to Ville Skyttä) - Fixed compiler error in client/device.c with VDR < 1.7.22 (reported by Uwe@vdrportal) - Updated Italian translation (thanks to Diego Pierotto) - Added DeviceName() and DeviceType() to client device. The server IP and the number of the device used on the server are returned respectively. 2012-05-29: Version 0.6.0 - Reimplemented some client device methods - Proper fix for "client sends ABRT after TUNE". Obsoletes many hacks in client - Added CLOCK_MONOTONIC timestamp and thread id to Dprintf - Silenced warning (thanks to Rolf Ahrenberg) - Updated Finnish translation (thanks to Rolf Ahrenberg) - Replaced server-side suspend modes with priority based precedence handling - Client-side priority handling for VDR >= 1.7.25 and servers running VTP > 1.0 - Introduced VTP protocol version numbering for easier compatibility handling between different client and server versions. The server includes the protocol version in its greeting string, the client reports its version with the new command "VERS". - Dropped compatibility of streamdev-server with VDR < 1.7.25 2012-05-12: Version 0.5.2 - Use fileno() to retrieve the fd from a FILE structure (submitted by an anonymous user) - New special meaning "show current channel" when channel 0 is requested. Applies to HTTP streaming only (thanks to Rolf Ahrenberg) - Fixed ProvidesChannel() on client always returning true since the new timeout option has been added. - Updated Finnish translation (thanks to Rolf Ahrenberg) - With VDR 1.7.25 priorities down to -99 will be used. Please update "Minimum Priority" in streamdev-client setup. - Use the new streamdev-client setup option "Live TV Priority" to control precedence among multiple clients. The VDR option "Primary Limit" which has previouly been used for this purpose has been dropped in VDR 1.7.25. - Timout for network operations now configurable in streamdev-client setup - Added timeout to Connect() - Report the server-side HTTP status "503 Service unavailable" instead of the client-side error "409 Conflict" when a channel is unavailable (suggested by Methodus) - Update of po headers and Finnish translation (thanks to Rolf Ahrenberg) - support for non-cycle-free setups (e.g. where two VDRs mutually share their DVB cards through streamdev-client/-server). Must be enabled in streamdev-server setup. Obsoletes recursion patches. - API change of VDR 1.7.22 - VDR 1.7.22 obsoletes cap_net_raw patch. Added cap_net_raw patch for VDR 1.7.5 - 1.7.21. - Update and UTF-8 conversion of Finnish po files (thanks to Rolf Ahrenberg) - Added "Hide mainmenu entry" option on server (thanks to Rolf Ahrenberg) - Added server menu with list of clients. Connections can be terminated with the "red" key. The former main menu action of suspending live TV moved to the "blue" key. - code cleanup and optimization (thanks to Ville Skyttä) - properly shutdown IGMP timeout handler thread when the plugin is stopped. Fixes occasional segfaults on VDR exit. - fixed memory leak in libdvbmpeg read_pes (thanks to Ville Skyttä) - dropped several unused functions in libdvbmpeg - restricted VTP command RENR to liemikuutio patch < 1.32. Build fails with newer versions of this patch (thanks to Ville Skyttä) - updated outdated COPYING file and FSF address (thanks to Ville Skyttä) - include SDT and TDT in TS streams - the icy-name HTTP header sent with radio streams makes VLC pick the wrong demuxer. Send icy-name only for ES audio streams. - fixed regression of "live TV must be switched in VDR main thread" change: deadlock in IGMP streaming server when switching live TV. - streamdev-client returns true in its AvoidRecording() method introduced with VDR 1.7.19. Note however that the impact of NumProvidedSystems is higher. - updated device selection to code of VDR 1.7.19 - adaption to VDR 1.7.12 cReceiver API change - increased WRITERBUFSIZE. Has been reported to fix some ringbuffer overflows (thanks to Lubo¨ Dole¸el) - check availability of channel if VTP command TUNE is called without prior PROV call (e.g. client side EPG scan) - added support for VDR 1.7.19 SignalStrength/SignalQuality - analog video channels use the same transponder and pid for different channels, so streamdev-client must always issue TUNE command - server must close the VTP connection also if filter stream is broken - fixed missing #ifdefs for new NumProvidedSystems setup option - new externremux.sh mencoder config options: audio pid by language code (-alang) and verbosity (-msglevel) (thanks to Pekko Tiitto) - writer must not spend too much time waiting in select() without checking if the thread has been cancelled - added Spanish translation (thanks to Javier Bradineras) - live TV must be switched in VDR main thread - dropped compatibility with VDR < 1.5.16 - return value of streamdev-clients cDevice::NumProvidedSystems() now configurable in plugin setup 2011-02-11: Version 0.5.1 - updated copy of GetClippedNumProvidedSystems to the version used since VDR 1.7.15 (reported by carel@vdrportal) - fixed the code deciding if a device is in use for live TV or not. It did not work as expected for FF cards (reported by wtor@vdrportal) - increased client side timeout for TUNE command - more dsyslog messages to help troubleshouting channel switch issues - improved the channel switch code trying to move live TV to different card - make sure that a client doesn't interrupt replaying on server's FF card (reported by wtor@vdrportal) - switching away live TV failed even when "always suspended" (reported by Michal Novotny) - fixed regression: no receiver created for ES/PS/PES (reported by Gavin Hamill) - VTP no longer uses a static priority value for its server-side receivers. The server stores channel and priority requested with the PROV command and re-uses these values in a subsequent TUNE for the same channel. The new PRIO command is used to update the receiver's priority if necessary. - added parameter HEIGHT to externremux.sh - fixed syslog messages reporting local instead of remote IP and port - fixed regression of the GetDevice(...) change. Filter streaming to clients with a recent VDR version no longer worked. - log an error if externremux.sh is missing or not executable - since VDR 1.5.0 cDevice::GetDevice(...) is no longer a query only method. It detaches all receivers of the device it returns. So it is no longer suitable for testing the availability of a device. Added a copy of VDR's cDevice::GetDevice(...) without the detach receivers part as a workaround until a better solution is available - added dsyslog messages to help troubleshouting channel switch issues - VTP command SUSP didn't attach the player to the primary device - fixed incompatibilities with older make versions - replacing a connections receiver is now an atomic operation. Solves stuttering audio/video due to lost TS packets when adding/removing PIDs - disabled attribute warn_unused_result in libdvbmpeg - slightly increased thread priorities of cStreamdevWriter/Streamer (suggested by Rolf Ahrenberg) - fixed missing support for invisible channel groups (groups without name) in HTTP menu (reported by Timothy D. Lenz) - don't quote actual program call in externremux.sh, so you can run the program through e.g. nice or taskset just by extending the variable which holds the program name - in externremux.sh each mencoder audio and video codec has a dedicated variable for a default option string now. Still you can override each default option with an URL parameter - externremux.sh mencoder now uses scale parameter with negative height instead of -xy for scaling (suggested by vel_tins@vdrportal) - added FPS (frames per second) parameter to externremux.sh (suggested by vel_tins@vdrportal) - don't use std::map.at(). It's not available in older libstdc++ version (reported by Matthias Prill) - fixed extremux x264 using value of ABR for VBR (thanks to vel_tins@vdrportal) 2010-07-20: Version 0.5.0b - fixed wrong URL path in m3u playlists (reported by Norman Thiel) 2010-07-20: Version 0.5.0a - set externremux.sh executable in distribution archive - externremux quality value should be wlan54, not wlan45 (reported by wolfi.m@vdrportal) 2010-07-19: Version 0.5.0 - using SIGINT in externremux to kill mencoder works better than SIGTERM; especially x264 still needs a SIGKILL sometimes - added --remove-destination to cp commands installing plugins - fixed "plugin doesn't honor APIVERSION" (reported by carel@vdrportal) - updated Italian translation (thanks to Diego Pierotto) - config option "client may suspend" hidden if not applicable - updated and enhanced README - separated language resources of client and server - restructured build process - added support for HTTP method HEAD - rewrite of externremux.sh, including support for various URL parameters, logging and improved shutdown - start externremux script in a separate process group - changed HTTP URL path for externremux from EXTERN to EXT (suggested by Rolf Ahrenberg) - HTTP headers now have to be emitted by externremux script - pass channel related information and URL parameters to externremux script through environment - implement CGI like interface for externremux script - dropped "Synchronize EPG" feature. Please use epgsync-plugin instead (available from http://vdr.schmirler.de) - proper tsplay-0.2 patch detection. tsplay-0.1 is no longer recognized (thanks to Udo Richter) - added compatibility with VDR 1.6 tsplay-0.1 patch - added support for EnhancedAC3 (thanks to Eric Valette) - 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 - 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. vdr-plugin-streamdev/PROTOCOL0000644000175000017500000001447313276341255015666 0ustar tobiastobiasWritten by: Sascha Volkenandt 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 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 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
    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 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 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 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 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.