commit 5e30711bfdb28085234a5ef6da4f4e44305ac3e4 Author: Frank Schmirler Date: Thu Dec 2 08:53:01 2010 +0100 Snapshot 2007-03-20 diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 0000000..f872f9a --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1,21 @@ +Special thanks go to the following persons (if you think your name is missing +here, please send an email to sascha@akv-soft.de): + +The Metzler Brothers + because I took a whole lot of code 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 + +Rantanen Teemu + for providing vdr-incompletesections.diff + +Thomas Keil + for providing vdr-localchannelprovide.diff diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..5b6e7c6 --- /dev/null +++ b/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/HISTORY b/HISTORY new file mode 100644 index 0000000..5018176 --- /dev/null +++ b/HISTORY @@ -0,0 +1,146 @@ +VDR Plugin 'streamdev' Revision History +--------------------------------------- + +2004-??-??: Version 0.3.3 + +- dropped support for non-ts streaming in vdr-to-vdr clients +- implemented packet buffer that seems to improve distortions +- greatly re-worked device selection on server and client + (vdr-to-vdr clients should behave exactly like clients with one card, + can't test conditional access, though) +- now printing an error and exiting if streamdevhosts.conf is not existing +- increased client stream priority to 1 +- implemented remote schedule to program remote timers directly from schedule +- the servers are turned on by default now +- new setup parameters "Bind to IP" for both servers for binding to a specific + interface +- re-implemented section streaming (turned off by default, see setup menu) +- implemented a possibility to prevent a shutdown when clients are connected + (patch VDR with patches/vdr-pluginactivity.diff if you want this feature) +- implemented channel listing through channels.htm(l) URI + +????-??-??: Version 0.3.2 + +... has myteriously disappeared :-) + +2004-02-16: Version 0.3.1 (unstable) + +- Added finnish language texts (thanks to Rolf Ahrenberg) +- Increased all ringbuffer sizes to 3 MB +- Autodetecting VDR 1.2.x, 1.2.x with AutoPID and 1.3.x on compilation +- Server is only restarted if necessary after confirming setup +- Implemented PID-based streaming (only needed PIDs are transferred instead of + all PIDs of the requested channel) (configurable) +- Implemented an editor for remote timers +- Implemented manual EPG synchronization from client +- Implemented Server Suspend remotely from client (configurable) +- Implemented an IP-Editor for the setup menu +- Separated Client and Server into two PlugIns +- Increased initial number of clients to five +- Implemented host-based authorization (syntax is equal to svdrphosts.conf) +- Removed two irritating messages that appeared sometimes while exiting VDR +- Implemented "Choose, Always, Never" for Suspend Mode, so it can be configured + to behave like 0.2.0 (Always), 0.3.0 (Choose) or completely different (Never) +- Added missing translation entries +- Added PlugIn description to translation table +- Fully upgraded to VDR 1.3.X regarding threading (but still works with 1.2.6) +- Reworked manual (almost everything) + +2003-10-10: Version 0.3.0 (unstable) + +- Implemented "Suspend Live TV" in the VDR server (configurable) +- Reimplemented choice of device for live streaming (better for switching on + client, and server doesn't loose live-tv) +- Added missing translation entries +- Increased client's streaming buffer size from 1 to 3 MB +- Updated installation instructions (including a patch to VDR that is + recommended currently) +- Updated manual + +2003-10-04: Version 0.2.0 + +- Removed those silly warnings in the toolbox-headers +- Implemented intelligent buffer overflow logging (doesn't flood syslog) +- Implemented EPG synchronization in the VDR client (configurable) +- Station name is transmitted in radio streaming now (Shoutcast-format). + +2003-09-24: Version 0.1.1beta1 + +- Restructured remuxer code +- Added an ES-remuxer for radio channels (currently only manually) + +2003-09-20: Version 0.1.0 + +- Fixed thread-abortion timeout in server thread + +2003-08-31: Version 0.1.0beta4 + +- Added italian language texts (thanks to Angelus (DOm)) +- Added a missing i18n translation (thanks to DOm) +- Added an #ifdef so the setup menu is displayed correctly with ElchiAIO + (thanks to DOm for reporting this one) +- It's possible to select the HTTP streamtype remotely, specified in the + URL in addition to the old behaviour (thanks to Michal Novotny) +- Fixed creation ob remuxer objects in the server +- Fixed handling of timeout in cTBSelect + +2003-06-08: Version 0.1.0beta3 + +- Fixed setup menu - now the cursor starts at the first visible entry +- Added PS streaming for HTTP (should work with most players now) +- Debugging symbols are only compiled with DEBUG=1 set + +2003-06-06: Version 0.1.0beta2 + +- Added an #ifdef so this PlugIn will compile cleanly with the next + AUTOPID-patches +- Added categories to the menu +- Fixed segfault when closing the menu with OK +- Added an AnalogTV section to the README +- Added some missing i18n entries +- Corrected client reinitialization code (when changing client settings) +- Added PS streaming for HTTP (should work with most players now) +- Added -D_GNU_SOURCE to the Makefile (.......) + +2003-06-03: Version 0.1.0beta1 + +- Replaced the toolbox with a current version +- Rewrote the server core from scratch +- Rewrote the client core from scratch +- Reduced the size of blocks processed in a transceiver turn to 10 TS packets +- Added TS transmission for HTTP (configurable via setup) +- Most client settings can be done on-the-fly now +- MIME type for radio channels now "audio/mpeg" instead of "video/mpeg" + (still doesn't work really) + +2003-05-08: Version 0.0.3beta1 + +- Server stops correctly on VDR exit +- Fixed a race condition when several threads access the client device +- Made server code more modular +- Structured the directories +- Fixed a bug in informational log-message +- Added Apid2, Dpid1 and Ppid in TS mode (silly me;) ) + +2003-05-03: Version 0.0.2 + +- Device is not deactivated anymore, since VDR does that itself +- Server is correctly deactivated, so it can be faultlessly reactivated +- Did some major code cleanup +- Added new command to the PROTOCOL (to negotiate stream types) +- Added the possibility to stream TS between two VDR's (which adds the + possibility of having AC3, Teletext etc. on the client) - this is + autonegotiated +- Streamtype can be changed in the setup menu, if TS works too unreliable +- Fixed a bug in multi-threaded device operation +- Sharing an epg.data with a server will be possible even if there is no + DVB-Device present +- Added a basic HTTP daemon to the server code + +2003-03-17: Version 0.0.1a + +- Corrected some bugs in the README and on the homepage *g* + +2003-03-17: Version 0.0.1 + +- Initial revision. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..7be2627 --- /dev/null +++ b/Makefile @@ -0,0 +1,135 @@ +# +# Makefile for a Video Disk Recorder plugin +# +# $Id: Makefile,v 1.7 2006/09/14 10:30:16 schmirl Exp $ + +# The official name of this plugin. +# This name will be used in the '-P...' option of VDR to load the plugin. +# By default the main source file also carries this name. +# +PLUGIN = streamdev + +### The version number of this plugin (taken from the main source file): + +VERSION = $(shell grep 'const char \*VERSION *=' common.c | awk '{ print $$5 }' | sed -e 's/[";]//g') + +### The C++ compiler and options: + +CXX ?= g++ +CXXFLAGS ?= -fPIC -W -Woverloaded-virtual + +### The directory environment: + +DVBDIR = ../../../../DVB +VDRDIR = ../../.. +LIBDIR = ../../lib +TMPDIR = /tmp + +### Allow user defined options to overwrite defaults: + +-include $(VDRDIR)/Make.config + +### The version number of VDR (taken from VDR's "config.h"): + +APIVERSION = $(shell grep 'define APIVERSION ' $(VDRDIR)/config.h | awk '{ print $$3 }' | sed -e 's/"//g') + +### The name of the distribution archive: + +ARCHIVE = $(PLUGIN)-$(VERSION) +PACKAGE = vdr-$(ARCHIVE) + +### Includes and Defines (add further entries here): + +INCLUDES += -I$(VDRDIR)/include -I$(DVBDIR)/include -I. + +DEFINES += -D_GNU_SOURCE + +### The object files (add further files here): + +COMMONOBJS = common.o i18n.o \ + \ + tools/source.o tools/select.o tools/socket.o tools/tools.o + +CLIENTOBJS = $(PLUGIN)-client.o \ + \ + client/socket.o client/device.o client/setup.o \ + client/remote.o client/assembler.o client/filter.o + + +SERVEROBJS = $(PLUGIN)-server.o \ + \ + server/server.o server/connectionVTP.o server/connectionHTTP.o \ + server/componentHTTP.o server/componentVTP.o server/connection.o \ + server/component.o server/suspend.o server/setup.o server/streamer.o \ + server/livestreamer.o server/livefilter.o \ + \ + remux/tsremux.o remux/ts2ps.o remux/ts2es.o remux/extern.o + +ifdef DEBUG + DEFINES += -DDEBUG + CXXFLAGS += -g +else + CXXFLAGS += -O2 +endif + +ifeq ($(shell test -f $(VDRDIR)/fontsym.h ; echo $$?),0) + DEFINES += -DHAVE_BEAUTYPATCH +endif + +ifeq ($(shell test -f $(VDRDIR)/fontsym.c ; echo $$?),0) + DEFINES += -DHAVE_BEAUTYPATCH +endif + +# HAVE_AUTOPID only applies if VDRVERSNUM < 10300 +ifeq ($(shell test -f $(VDRDIR)/sections.c ; echo $$?),0) + DEFINES += -DHAVE_AUTOPID +endif + +libdvbmpeg/libdvbmpegtools.a: libdvbmpeg/*.c libdvbmpeg/*.cc libdvbmpeg/*.h libdvbmpeg/*.hh + make -C ./libdvbmpeg libdvbmpegtools.a + +### Implicit rules: + +%.o: %.c + $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) -o $@ $< + +# Dependencies: + +MAKEDEP = g++ -MM -MG +DEPFILE = .dependencies +ifdef GCC3 +$(DEPFILE): Makefile + @rm -f $@ + @for i in $(CLIENTOBJS:%.o=%.c) $(SERVEROBJS:%.o=%.c) $(COMMONOBJS:%.o=%.c) ; do \ + $(MAKEDEP) $(DEFINES) $(INCLUDES) -MT "`dirname $$i`/`basename $$i .c`.o" $$i >>$@ ; \ + done +else +$(DEPFILE): Makefile + @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(CLIENTOBJS:%.o=%.c) $(SERVEROBJS:%.o=%.c) \ + $(COMMONOBJS:%.o=%.c) > $@ +endif + +-include $(DEPFILE) + +### Targets: + +all: libvdr-$(PLUGIN)-client.so libvdr-$(PLUGIN)-server.so + +libvdr-$(PLUGIN)-client.so: $(CLIENTOBJS) $(COMMONOBJS) libdvbmpeg/libdvbmpegtools.a +libvdr-$(PLUGIN)-server.so: $(SERVEROBJS) $(COMMONOBJS) libdvbmpeg/libdvbmpegtools.a + +%.so: + $(CXX) $(CXXFLAGS) -shared $^ -o $@ + @cp $@ $(LIBDIR)/$@.$(APIVERSION) + +dist: clean + @-rm -rf $(TMPDIR)/$(ARCHIVE) + @mkdir $(TMPDIR)/$(ARCHIVE) + @cp -a * $(TMPDIR)/$(ARCHIVE) + @tar czf $(PACKAGE).tgz --exclude SCCS -C $(TMPDIR) $(ARCHIVE) + @-rm -rf $(TMPDIR)/$(ARCHIVE) + @echo Distribution package created as $(PACKAGE).tgz + +clean: + @-rm -f $(COMMONOBJS) $(CLIENTOBJS) $(SERVEROBJS) $(DEPFILE) *.so *.tgz core* *~ + make -C ./libdvbmpeg clean diff --git a/PROTOCOL b/PROTOCOL new file mode 100644 index 0000000..a0c999e --- /dev/null +++ b/PROTOCOL @@ -0,0 +1,139 @@ +Written 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. diff --git a/README b/README new file mode 100644 index 0000000..b69c5c5 --- /dev/null +++ b/README @@ -0,0 +1,299 @@ +This is a "plugin" for the Video Disk Recorder (VDR). + +Written by: Sascha Volkenandt + +Project's homepage: http://www.magoa.net/linux/ + +Latest version available at: http://www.magoa.net/linux/index.php?view=streamdev + +See the file COPYING for license information. + +Contents: +--------- + +1. Description +2. Installation +2.1 VDR 1.2.X +2.2 VDR 1.3.X +3. Usage +3.1 Usage VDR-to-VDR server +3.2 Usage HTTP server +3.3 Usage VDR-to-VDR client +3.4 General Usage Notes +4. VDR-to-VDR client notes (PLEASE READ IF YOU HAVE ONE) +4.1 EPG data [OUTDATED] +4.2 Teletext / OSD Teletext +4.3 AnalogTV [OUTDATED] +5. Known Problems + + +1. Description: +--------------- + +This PlugIn is a VDR implementation of the VTP (Video Transfer Protocol) +Version 0.0.3 (see file PROTOCOL) and a basic HTTP Streaming Protocol. + +It consists of a server and a client part, but both parts are compiled together +with the PlugIn source, but appear as separate PlugIns to VDR. + +The client part acts as a full Input Device, so it can be used in conjunction +with a DXR3-Card, XINE, SoftDevice or others to act as a working VDR +installation without any DVB-Hardware including EPG-Handling. + +The server part acts as a Receiver-Device and works transparently in the +background within your running VDR. It can serve multiple clients and it can +distribute multiple input streams (i.e. from multiple DVB-cards) to multiple +clients using the native VTP protocol (for VDR-clients), or using the HTTP +protocol supporting clients such as XINE, MPlayer and so on. With XMMS or +WinAMP, you can also listen to radio channels over a HTTP connection. + +It is possible to attach as many clients as the bus and network can handle, as +long as there is a device which can receive a specific channel. Multiple +channels homed on the same transponder (which is determined by it's frequency) +can be broadcasted with a single device. + +Additional clients can be programmed using the Protocol Instructions inside +the PROTOCOL file. + + +2. Installation: +---------------- + +Let's say streamdev's version is 0.3.1 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 (in case you use 1.2.X +with AutoPID or 1.3.X). + +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 put the provided streamdevhosts.conf.example +into the "plugins" subfolder of your config-directory (which is equal to your +video-directory if not specified otherwise), rename it to streamdevhosts.conf +and adjust it 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. For example, if you didn't specify a separate config-directory, +and specified your video directory as "/video0", the file has to be put to +/video0/plugins/streamdevhosts.conf. + + +2.1 VDR 1.2.X: +-------------- + +It is recommended that you apply a patch to VDR that improves thread +cancellation. You can work without it, but you _might_ have delays in switching +(especially when using VDR-to-VDR streaming) that are around three seconds. + +cd vdr-1.X.X/PLUGINS/src +tar xvfz vdr-streamdev-0.3.1.tgz +ln -s streamdev-0.3.1 streamdev +cd ../.. +patch -p1 " 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. Activate the client by +setting "Start Client" to yes. It is disabled by default, because it wouldn't +make much sense to start the client without specifying a server anyway. The +client is activated after you push the OK button, so there's no need to restart +VDR. Deactivation on-the-fly is not possible, so in order to deactivate the +client, you will have to restart VDR. All other settings can be changed without +restarting VDR. + +The client will try to connect to the server (in case it isn't yet) whenever +a remote channel is requested. Just activate the client and switch to a +channel that's not available by local devices. If anything goes wrong with the +connection between the two, you will see it in the logfile instantly. If you +now switch the client to a channel which isn't covered by it's own local +devices, it will ask the server for it. If the server can (currently) receive +that channel, the client will show it until you switch again, or until the +server needs that card (if no other is free) for a recording on a different +transponder. + +You can choose a remote streamtype in the setup. I'd suggest TS streaming as +it has a much shorter delay than PES streaming (compared to live-view of the +same channel on the server), and transmits more information such as AC3 and +teletext data. + +When setting the parameter "MultiPID streaming" to yes (the default) (only +applies if the streamtype is TS), only the needed PIDs are transferred, and +additional PIDs can be turned on during an active transfer. This makes it +possible to switch languages, receive additional channels (for recording on +the client) and use plugins that use receivers themselves (like osdteletext). + +The last parameter, "Synchronize EPG", will have the client synchronize it's +program table with the server every now and then, but not regularly. This +happens when starting the client, and everytime VDR does its housekeeping +tasks. The only thing that's guaranteed is, that there will be a minimum +interval of ten seconds between each EPG synchronization. + +The client has a Main Menu entry called "Streaming Control". This is used to +control various aspects of the remote server VDR. Inside, you will find +"Remote Timers", "Remote Recordings", "Suspend server" and "Synchronize EPG". + +The "Remote Timers" entry gives you the possibility to edit, create and delete +the server's timers remotely. Every timer is synchronized before the requested +action actually takes place. This only leaves a very short time-span (of a few +milliseconds) in which a race-condition could happen. + +"Remote Recordings" shows up all recordings that the server can access. Only +deleting recordings is implemented, yet. + +With "Suspend Server", you can send the server into suspend mode remotely, if +the server is set to "Offer suspend mode" and allows the client to suspend. + +Last but not least, "Synchronize EPG" starts a synchronization in case you +don't want to do it regularly, or in case you just activated it and can't wait +for the first synchronization to happen by itself. + +3.4 General Usage Notes: +------------------------ + +If there's still some debug output on stdout, please ignore it ;) + + +4. VDR-to-VDR client notes: +--------------------------- + +4.1 EPG data: +-------------- + +[ OUTDATED, see "Synchronize EPG" in 3.2 ] + +4.2 Teletext / OSD Teletext: +----------------------------- + +Usual teletext will probably not work on the client, if it has no DVB hardware. +I never tried, and probably I never will, so don't ask about it please ;) + +Osdteletext-0.3.1 (and later) definitely work when used in MultiPID Streaming +mode. + + +4.3 AnalogTV +------------ + +Works with ivtv and analogue cards according to Andreas Kool. + + +5. Known Problems: +------------------ + +- Recordings & Timers on the client side could endanger Timers & Recordings on + the server, as they will have the same priority (by default). Set the + default priority to i.e. 40 if you want the server to supersede the client. + +- Sometimes, if you reload VDR too often (for example while recompiling), the + driver can get "stuck" in some situations. Try a driver restart if anything + you think should work doesn't before sending a bug-report :-). + [ ADDITION ] + In the meantime I have discovered that this error is caused by the all- + mysterical UPT (unknown picture type) error :-(. + + diff --git a/client/assembler.c b/client/assembler.c new file mode 100644 index 0000000..a32b2ed --- /dev/null +++ b/client/assembler.c @@ -0,0 +1,125 @@ +/* + * $Id: assembler.c,v 1.2 2005/01/25 14:14:43 lordjaxom Exp $ + */ + +#include "client/assembler.h" +#include "common.h" + +#include "tools/socket.h" +#include "tools/select.h" + +#include +#include +#include + +#include + +cStreamdevAssembler::cStreamdevAssembler(cTBSocket *Socket) +#if VDRVERSNUM >= 10300 + :cThread("Streamdev: UDP-TS Assembler") +#endif +{ + m_Socket = Socket; + if (pipe(m_Pipe) != 0) { + esyslog("streamdev-client: Couldn't open assembler pipe: %m"); + return; + } + fcntl(m_Pipe[0], F_SETFL, O_NONBLOCK); + fcntl(m_Pipe[1], F_SETFL, O_NONBLOCK); + m_Mutex.Lock(); + Start(); +} + +cStreamdevAssembler::~cStreamdevAssembler() { + if (m_Active) { + m_Active = false; +/* WakeUp();*/ + Cancel(3); + } + close(m_Pipe[0]); + close(m_Pipe[1]); +} + +void cStreamdevAssembler::Action(void) { + cTBSelect sel; + uchar buffer[2048]; + bool fillup = true; + + const int rbsize = TS_SIZE * 5600; + const int rbmargin = TS_SIZE * 2; + const int rbminfill = rbmargin * 50; + cRingBufferLinear ringbuf(rbsize, rbmargin, true); + +#if VDRVERSNUM < 10300 + isyslog("streamdev-client: UDP-TS Assembler thread started (pid=%d)", + getpid()); +#endif + + m_Mutex.Lock(); + + m_Active = true; + while (m_Active) { + sel.Clear(); + + if (ringbuf.Available() < rbsize * 80 / 100) + sel.Add(*m_Socket, false); + if (ringbuf.Available() > rbminfill) { + if (fillup) { + Dprintf("giving signal\n"); + m_WaitFill.Broadcast(); + m_Mutex.Unlock(); + fillup = false; + } + sel.Add(m_Pipe[1], true); + } + + if (sel.Select(1500) < 0) { + if (!m_Active) // Exit was requested + break; + esyslog("streamdev-client: Fatal error: %m"); + Dprintf("streamdev-client: select failed (%m)\n"); + m_Active = false; + break; + } + + if (sel.CanRead(*m_Socket)) { + int b; + if ((b = m_Socket->Read(buffer, sizeof(buffer))) < 0) { + esyslog("streamdev-client: Couldn't read from server: %m"); + Dprintf("streamdev-client: read failed (%m)\n"); + m_Active = false; + break; + } + if (b == 0) + m_Active = false; + else + ringbuf.Put(buffer, b); + } + + if (sel.CanWrite(m_Pipe[1])) { + int recvd; + const uchar *block = ringbuf.Get(recvd); + if (block && recvd > 0) { + int result; + if (recvd > ringbuf.Available() - rbminfill) + recvd = ringbuf.Available() - rbminfill; + if ((result = write(m_Pipe[1], block, recvd)) == -1) { + esyslog("streamdev-client: Couldn't write to VDR: %m"); // TODO + Dprintf("streamdev-client: write failed (%m)\n"); + m_Active = false; + break; + } + ringbuf.Del(result); + } + } + } + +#if VDRVERSNUM < 10300 + isyslog("streamdev-client: UDP-TS Assembler thread stopped", getpid()); +#endif +} + +void cStreamdevAssembler::WaitForFill(void) { + m_WaitFill.Wait(m_Mutex); + m_Mutex.Unlock(); +} diff --git a/client/assembler.h b/client/assembler.h new file mode 100644 index 0000000..be8f88c --- /dev/null +++ b/client/assembler.h @@ -0,0 +1,32 @@ +/* + * $Id: assembler.h,v 1.1.1.1 2004/12/30 22:44:04 lordjaxom Exp $ + */ + +#ifndef VDR_STREAMDEV_ASSEMBLER_H +#define VDR_STREAMDEV_ASSEMBLER_H + +#include +#include + +class cTBSocket; + +class cStreamdevAssembler: public cThread { +private: + cTBSocket *m_Socket; + cMutex m_Mutex; + cCondVar m_WaitFill; + int m_Pipe[2]; + bool m_Active; +protected: + virtual void Action(void); + +public: + cStreamdevAssembler(cTBSocket *Socket); + virtual ~cStreamdevAssembler(); + + int ReadPipe(void) const { return m_Pipe[0]; } + void WaitForFill(void); +}; + +#endif // VDR_STREAMDEV_ASSEMBLER_H + diff --git a/client/device.c b/client/device.c new file mode 100644 index 0000000..a2e4580 --- /dev/null +++ b/client/device.c @@ -0,0 +1,188 @@ +/* + * $Id: device.c,v 1.8 2007/01/15 12:15:12 schmirl Exp $ + */ + +#include "client/device.h" +#include "client/setup.h" +#include "client/assembler.h" +#include "client/filter.h" + +#include "tools/select.h" + +#include +#include +#include +#include + +#include +#include + +using namespace std; + +#define VIDEOBUFSIZE MEGABYTE(3) + +cStreamdevDevice *cStreamdevDevice::m_Device = NULL; + +cStreamdevDevice::cStreamdevDevice(void) { + m_Channel = NULL; + m_TSBuffer = NULL; + m_Assembler = NULL; + +#if VDRVERSNUM < 10300 +# if defined(HAVE_AUTOPID) + (void)new cSIProcessor(new cSectionsScanner("")); +# else + (void)new cSIProcessor(""); +# endif + cSIProcessor::Read(); +#else + m_Filters = new cStreamdevFilters; + StartSectionHandler(); + cSchedules::Read(); +#endif + + m_Device = this; + + if (StreamdevClientSetup.SyncEPG) + ClientSocket.SynchronizeEPG(); +} + +cStreamdevDevice::~cStreamdevDevice() { + Dprintf("Device gets destructed\n"); + m_Device = NULL; + delete m_TSBuffer; + delete m_Assembler; +#if VDRVERSNUM >= 10300 + delete m_Filters; +#endif +} + +bool cStreamdevDevice::ProvidesSource(int Source) const { + Dprintf("ProvidesSource, Source=%d\n", Source); + return false; +} + +bool cStreamdevDevice::ProvidesTransponder(const cChannel *Channel) const +{ + Dprintf("ProvidesTransponder\n"); + return false; +} + +bool cStreamdevDevice::ProvidesChannel(const cChannel *Channel, int Priority, + bool *NeedsDetachReceivers) const { + bool res = false; + bool prio = Priority < 0 || Priority > this->Priority(); + bool ndr = false; + Dprintf("ProvidesChannel, Channel=%s, Prio=%d\n", Channel->Name(), Priority); + + if (ClientSocket.DataSocket(siLive) != NULL + && TRANSPONDER(Channel, m_Channel)) + res = true; + else { + res = prio && ClientSocket.ProvidesChannel(Channel, Priority); + ndr = true; + } + + if (NeedsDetachReceivers) + *NeedsDetachReceivers = ndr; + Dprintf("prov res = %d, ndr = %d\n", res, ndr); + return res; +} + +bool cStreamdevDevice::SetChannelDevice(const cChannel *Channel, + bool LiveView) { + Dprintf("SetChannelDevice Channel: %s, LiveView: %s\n", Channel->Name(), + LiveView ? "true" : "false"); + + if (LiveView) + return false; + + if (ClientSocket.DataSocket(siLive) != NULL + && TRANSPONDER(Channel, m_Channel)) + return true; + +#if VDRVERSNUM < 10338 + DetachAll(pidHandles[ptAudio].pid); + DetachAll(pidHandles[ptVideo].pid); + DetachAll(pidHandles[ptPcr].pid); + DetachAll(pidHandles[ptTeletext].pid); + DelPid(pidHandles[ptAudio].pid); + DelPid(pidHandles[ptVideo].pid); + DelPid(pidHandles[ptPcr].pid, ptPcr); + DelPid(pidHandles[ptTeletext].pid); + DelPid(pidHandles[ptDolby].pid); +#else + DetachAllReceivers(); +#endif + m_Channel = Channel; + bool r = ClientSocket.SetChannelDevice(m_Channel); + Dprintf("setchanneldevice r=%d\n", r); + return r; +} + +bool cStreamdevDevice::SetPid(cPidHandle *Handle, int Type, bool On) { + Dprintf("SetPid, Pid=%d, Type=%d, On=%d, used=%d\n", Handle->pid, Type, On, + Handle->used); + if (Handle->pid && (On || !Handle->used)) + return ClientSocket.SetPid(Handle->pid, On); + return true; +} + +bool cStreamdevDevice::OpenDvr(void) { + Dprintf("OpenDvr\n"); + CloseDvr(); + if (ClientSocket.CreateDataConnection(siLive)) { + //m_Assembler = new cStreamdevAssembler(ClientSocket.DataSocket(siLive)); + //m_TSBuffer = new cTSBuffer(m_Assembler->ReadPipe(), MEGABYTE(2), CardIndex() + 1); + m_TSBuffer = new cTSBuffer(*ClientSocket.DataSocket(siLive), MEGABYTE(2), CardIndex() + 1); + Dprintf("waiting\n"); + //m_Assembler->WaitForFill(); + Dprintf("resuming\n"); + return true; + } + return false; +} + +void cStreamdevDevice::CloseDvr(void) { + Dprintf("CloseDvr\n"); + + //DELETENULL(m_Assembler); + DELETENULL(m_TSBuffer); + ClientSocket.CloseDvr(); +} + +bool cStreamdevDevice::GetTSPacket(uchar *&Data) { + if (m_TSBuffer) { + Data = m_TSBuffer->Get(); + return true; + } + return false; +} + +#if VDRVERSNUM >= 10300 +int cStreamdevDevice::OpenFilter(u_short Pid, u_char Tid, u_char Mask) { + Dprintf("OpenFilter\n"); + if (StreamdevClientSetup.StreamFilters + && ClientSocket.SetFilter(Pid, Tid, Mask, true)) { + return m_Filters->OpenFilter(Pid, Tid, Mask); + } else + return -1; +} +#endif + +bool cStreamdevDevice::Init(void) { + if (m_Device == NULL && StreamdevClientSetup.StartClient) + new cStreamdevDevice; + return true; +} + +bool cStreamdevDevice::ReInit(void) { + ClientSocket.Quit(); + ClientSocket.Reset(); + if (m_Device != NULL) { + DELETENULL(m_Device->m_TSBuffer); + DELETENULL(m_Device->m_Assembler); + } + return StreamdevClientSetup.StartClient ? Init() : true; +} + diff --git a/client/device.h b/client/device.h new file mode 100644 index 0000000..bdfabb6 --- /dev/null +++ b/client/device.h @@ -0,0 +1,64 @@ +/* + * $Id: device.h,v 1.3 2005/02/08 15:21:19 lordjaxom Exp $ + */ + +#ifndef VDR_STREAMDEV_DEVICE_H +#define VDR_STREAMDEV_DEVICE_H + +#include + +#include "client/socket.h" +#include "client/assembler.h" +#include "client/filter.h" + +class cTBString; + +#define CMD_LOCK_OBJ(x) cMutexLock CmdLock((cMutex*)&(x)->m_Mutex) + +class cStreamdevDevice: public cDevice { + friend class cRemoteRecordings; + +private: + const cChannel *m_Channel; + cTSBuffer *m_TSBuffer; + cStreamdevAssembler *m_Assembler; +#if VDRVERSNUM >= 10307 + cStreamdevFilters *m_Filters; +#endif + + static cStreamdevDevice *m_Device; + +protected: + virtual bool SetChannelDevice(const cChannel *Channel, bool LiveView); + virtual bool HasLock(int TimeoutMs) + { + //printf("HasLock is %d\n", (ClientSocket.DataSocket(siLive) != NULL)); + //return ClientSocket.DataSocket(siLive) != NULL; + return true; + } + + virtual bool SetPid(cPidHandle *Handle, int Type, bool On); + virtual bool OpenDvr(void); + virtual void CloseDvr(void); + virtual bool GetTSPacket(uchar *&Data); + +#if VDRVERSNUM >= 10300 + virtual int OpenFilter(u_short Pid, u_char Tid, u_char Mask); +#endif + +public: + cStreamdevDevice(void); + virtual ~cStreamdevDevice(); + + 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; + + static bool Init(void); + static bool ReInit(void); + + static cStreamdevDevice *GetDevice(void) { return m_Device; } +}; + +#endif // VDR_STREAMDEV_DEVICE_H diff --git a/client/filter.c b/client/filter.c new file mode 100644 index 0000000..daf534a --- /dev/null +++ b/client/filter.c @@ -0,0 +1,119 @@ +/* + * $Id: filter.c,v 1.3 2005/11/06 16:43:58 lordjaxom Exp $ + */ + +#include "client/filter.h" +#include "client/socket.h" +#include "tools/select.h" +#include "common.h" + +#include +#include + +#if VDRVERSNUM >= 10300 + +cStreamdevFilter::cStreamdevFilter(u_short Pid, u_char Tid, u_char Mask) { + m_Used = 0; + m_Pid = Pid; + m_Tid = Tid; + m_Mask = Mask; + + if (pipe(m_Pipe) != 0 || fcntl(m_Pipe[0], F_SETFL, O_NONBLOCK) != 0) { + esyslog("streamev-client: coudln't open section filter pipe: %m"); + m_Pipe[0] = m_Pipe[1] = -1; + } +} + +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) { + if (m_Used + Length >= (int)sizeof(m_Buffer)) { + esyslog("ERROR: Streamdev: Section handler buffer overflow (%d bytes lost)", + Length); + m_Used = 0; + 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) { + if (write(m_Pipe[1], m_Buffer, length) < 0) + return false; + m_Used = 0; + } + } + return true; +} + +cStreamdevFilters::cStreamdevFilters(void): + cThread("streamdev-client: sections assembler") { + m_Active = false; + m_RingBuffer = new cRingBufferLinear(MEGABYTE(1), TS_SIZE * 2, true); + Start(); +} + +cStreamdevFilters::~cStreamdevFilters() { + if (m_Active) { + m_Active = false; + Cancel(3); + } + delete m_RingBuffer; +} + +int cStreamdevFilters::OpenFilter(u_short Pid, u_char Tid, u_char Mask) { + cStreamdevFilter *f = new cStreamdevFilter(Pid, Tid, Mask); + Add(f); + return f->ReadPipe(); +} + +cStreamdevFilter *cStreamdevFilters::Matches(u_short Pid, u_char Tid) { + for (cStreamdevFilter *f = First(); f; f = Next(f)) { + if (f->Matches(Pid, Tid)) + return f; + } + return NULL; +} + +void cStreamdevFilters::Put(const uchar *Data) { + int p = m_RingBuffer->Put(Data, TS_SIZE); + if (p != TS_SIZE) + m_RingBuffer->ReportOverflow(TS_SIZE - p); +} + +void cStreamdevFilters::Action(void) { + m_Active = true; + while (m_Active) { + int recvd; + const uchar *block = m_RingBuffer->Get(recvd); + + if (block && recvd > 0) { + cStreamdevFilter *f; + u_short pid = (((u_short)block[1] & PID_MASK_HI) << 8) | block[2]; + u_char tid = block[3]; + + if ((f = Matches(pid, tid)) != NULL) { + int len = block[4]; + if (!f->PutSection(block + 5, len)) { + if (errno != EPIPE) { + esyslog("streamdev-client: couldn't send section packet: %m"); + Dprintf("FATAL ERROR: %m\n"); + } + ClientSocket.SetFilter(f->Pid(), f->Tid(), f->Mask(), false); + Del(f); + } + } + m_RingBuffer->Del(TS_SIZE); + } else + usleep(1); + } +} + +#endif // VDRVERSNUM >= 10300 diff --git a/client/filter.h b/client/filter.h new file mode 100644 index 0000000..cb46bf0 --- /dev/null +++ b/client/filter.h @@ -0,0 +1,64 @@ +/* + * $Id: filter.h,v 1.1.1.1 2004/12/30 22:44:04 lordjaxom Exp $ + */ + +#ifndef VDR_STREAMDEV_FILTER_H +#define VDR_STREAMDEV_FILTER_H + +#include + +# if VDRVERSNUM >= 10300 + +#include +#include + +class cRingBufferFrame; +class cRingBufferLinear; + +class cStreamdevFilter: public cListObject { +private: + uchar m_Buffer[4096]; + int m_Used; + int m_Pipe[2]; + u_short m_Pid; + u_char m_Tid; + u_char m_Mask; + cRingBufferFrame *m_RingBuffer; + +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); + int ReadPipe(void) const { return m_Pipe[0]; } + + 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); +} + +class cStreamdevFilters: public cList, public cThread { +private: + bool m_Active; + cRingBufferLinear *m_RingBuffer; + +protected: + virtual void Action(void); + +public: + cStreamdevFilters(void); + virtual ~cStreamdevFilters(); + + int OpenFilter(u_short Pid, u_char Tid, u_char Mask); + cStreamdevFilter *Matches(u_short Pid, u_char Tid); + void Put(const uchar *Data); +}; + +# endif // VDRVERSNUM >= 10300 +#endif // VDR_STREAMDEV_FILTER_H diff --git a/client/menu.c b/client/menu.c new file mode 100644 index 0000000..268b3b2 --- /dev/null +++ b/client/menu.c @@ -0,0 +1,1045 @@ +/* + * $Id: menu.c,v 1.4 2005/03/12 12:54:19 lordjaxom Exp $ + */ + +#include +#include + +#include "client/menu.h" +#include "client/socket.h" +#include "i18n.h" + +#define CHNUMWIDTH (numdigits(Channels.MaxNumber()) + 1) + +// --- cMenuText ------------------------------------------------------------- + +class cMenuText : public cOsdMenu { +public: + cMenuText(const char *Title, const char *Text, eDvbFont Font = fontOsd); + virtual eOSState ProcessKey(eKeys Key); + }; + +// --- cStreamdevMenu -------------------------------------------------------- + +cStreamdevMenu::cStreamdevMenu(void): + cOsdMenu(tr("Streaming Control")) { + SetHasHotkeys(); + Add(new cOsdItem(hk(tr("Remote Schedule")), (eOSState)subSchedule)); + Add(new cOsdItem(hk(tr("Remote Timers")), (eOSState)subTimers)); + Add(new cOsdItem(hk(tr("Remote Recordings")), (eOSState)subRecordings)); + Add(new cOsdItem(hk(tr("Suspend Server")), (eOSState)subSuspend)); + Add(new cOsdItem(hk(tr("Synchronize EPG")), (eOSState)subSyncEPG)); +} + +cStreamdevMenu::~cStreamdevMenu() { +} + +eOSState cStreamdevMenu::ProcessKey(eKeys Key) { + eOSState state = cOsdMenu::ProcessKey(Key); + switch (state) { + case subSchedule: return AddSubMenu(new cStreamdevMenuSchedule); + case subTimers: return AddSubMenu(new cStreamdevMenuTimers); + case subRecordings: return AddSubMenu(new cStreamdevMenuRecordings); + case subSuspend: SuspendServer(); return osEnd; + case subSyncEPG: ClientSocket.SynchronizeEPG(); return osEnd; + default: return state; + } +} + +void cStreamdevMenu::SuspendServer(void) { + if (ClientSocket.SuspendServer()) + INFO(tr("Server is suspended")); + else + ERROR(tr("Couldn't suspend Server!")); +} + +#if VDRVERSNUM < 10307 +// --- cMenuEditChanItem ----------------------------------------------------- + +class cMenuEditChanItem : public cMenuEditIntItem { +protected: + virtual void Set(void); +public: + cMenuEditChanItem(const char *Name, int *Value); + virtual eOSState ProcessKey(eKeys Key); + }; + +// --- cMenuEditDateItem ----------------------------------------------------- + +class cMenuEditDateItem : public cMenuEditItem { +protected: + time_t *value; + virtual void Set(void); +public: + cMenuEditDateItem(const char *Name, time_t *Value); + virtual eOSState ProcessKey(eKeys Key); + }; + +// --- cMenuEditDayItem ------------------------------------------------------ + +class cMenuEditDayItem : public cMenuEditIntItem { +protected: + static int days[]; + int d; + virtual void Set(void); +public: + cMenuEditDayItem(const char *Name, int *Value); + virtual eOSState ProcessKey(eKeys Key); + }; + +// --- cMenuEditTimeItem ----------------------------------------------------- + +class cMenuEditTimeItem : public cMenuEditItem { +protected: + int *value; + int hh, mm; + int pos; + virtual void Set(void); +public: + cMenuEditTimeItem(const char *Name, int *Value); + virtual eOSState ProcessKey(eKeys Key); + }; +#endif // VDRVERSNUM < 10307 + +// --- cStreamdevMenuEditTimer ----------------------------------------------- + +class cStreamdevMenuEditTimer : public cOsdMenu { +private: + int m_Channel; + bool m_AddIfConfirmed; + cRemoteTimer *m_Timer; + cRemoteTimer m_Data; + cMenuEditDateItem *m_FirstDay; + +protected: + void SetFirstDayItem(void); + +public: + cStreamdevMenuEditTimer(cRemoteTimer *Timer, bool New = false); + virtual ~cStreamdevMenuEditTimer(); + + virtual eOSState ProcessKey(eKeys Key); +}; + +cStreamdevMenuEditTimer::cStreamdevMenuEditTimer(cRemoteTimer *Timer, bool New): + cOsdMenu(tr("Edit remote timer"), 12) { + m_FirstDay = NULL; + m_Timer = Timer; + m_AddIfConfirmed = New; + + if (m_Timer) { + m_Data = *m_Timer; + if (New) + m_Data.m_Active = 1; + m_Channel = m_Data.Channel()->Number(); +#if VDRVERSNUM < 10300 + Add(new cMenuEditBoolItem(tr("Active"), &m_Data.m_Active)); +#else + Add(new cMenuEditBitItem( tr("Active"), &m_Data.m_Active, tfActive)); +#endif + Add(new cMenuEditChanItem(tr("Channel"), &m_Channel)); + Add(new cMenuEditDayItem( tr("Day"), &m_Data.m_Day)); + Add(new cMenuEditTimeItem(tr("Start"), &m_Data.m_Start)); + Add(new cMenuEditTimeItem(tr("Stop"), &m_Data.m_Stop)); +#if VDRVERSNUM >= 10300 + Add(new cMenuEditBitItem( tr("VPS"), &m_Data.m_Active, tfVps)); +#endif + Add(new cMenuEditIntItem( tr("Priority"), &m_Data.m_Priority, 0, + MAXPRIORITY)); + Add(new cMenuEditIntItem( tr("Lifetime"), &m_Data.m_Lifetime, 0, + MAXLIFETIME)); + Add(new cMenuEditStrItem( tr("File"), m_Data.m_File, + sizeof(m_Data.m_File), tr(FileNameChars))); + SetFirstDayItem(); + } +} + +cStreamdevMenuEditTimer::~cStreamdevMenuEditTimer() { + if (m_Timer && m_AddIfConfirmed) { + Dprintf("SOMETHING GETS DELETED\n"); + delete m_Timer; // apparently it wasn't confirmed + } +} + +void cStreamdevMenuEditTimer::SetFirstDayItem(void) { + if (!m_FirstDay && !m_Data.IsSingleEvent()) { + Add(m_FirstDay = new cMenuEditDateItem(tr("First day"),&m_Data.m_FirstDay)); + Display(); + } else if (m_FirstDay && m_Data.IsSingleEvent()) { + Del(m_FirstDay->Index()); + m_FirstDay = NULL; + m_Data.m_FirstDay = 0; + Display(); + } +} + +eOSState cStreamdevMenuEditTimer::ProcessKey(eKeys Key) { + eOSState state = cOsdMenu::ProcessKey(Key); + + if (state == osUnknown) { + switch (Key) { + case kOk: + { + cChannel *ch = Channels.GetByNumber(m_Channel); + if (ch) + m_Data.m_Channel = ch; + else { + ERROR(tr("*** Invalid Channel ***")); + break; + } + if (!*m_Data.m_File) + strcpy(m_Data.m_File, m_Data.Channel()->Name()); + if (m_Timer) { + bool success = true; + if (m_Data != *m_Timer) { + // Timer has changed + if ((success = ClientSocket.SaveTimer(m_Timer, m_Data))) { + *m_Timer = m_Data; + if (m_Timer->m_Active) + m_Timer->m_Active = 1; + // allows external programs to mark active timers with + // values > 1 and recognize if the user has modified them + } + } + if (success) { + if (m_AddIfConfirmed) + RemoteTimers.Add(m_Timer); + isyslog("timer %d %s (%s)", m_Timer->Index() + 1, + m_AddIfConfirmed ? "added" : "modified", + m_Timer->m_Active ? "active" : "inactive"); + m_AddIfConfirmed = false; + } + } + } + return osBack; + + case kRed: + case kGreen: + case kYellow: + case kBlue: return osContinue; + default: break; + } + } + if (Key != kNone) + SetFirstDayItem(); + return state; +} + +// --- cMenuWhatsOnItem ------------------------------------------------------ + +#if VDRVERSNUM < 10300 +class cMenuWhatsOnItem : public cOsdItem { +public: + const cEventInfo *eventInfo; +# ifdef HAVE_BEAUTYPATCH + cMenuWhatsOnItem(const cEventInfo *EventInfo, bool ShowProgressBar); + ~cMenuWhatsOnItem(); + virtual void Display(int Offset= -1, eDvbColor FgColor = clrWhite, eDvbColor BgColor = clrBackground); +protected: + cBitmap *progressBar; + bool showProgressBar; + float percent; +private: + void DrawProgressBar(eDvbColor FgColor, eDvbColor BgColor); +# else + cMenuWhatsOnItem(const cEventInfo *EventInfo); +# endif +}; +#else +class cMenuWhatsOnItem : public cOsdItem { +public: + const cEvent *event; + const cChannel *channel; + cMenuWhatsOnItem(const cEvent *Event, cChannel *Channel); //, bool Now = false); +}; +#endif + +// --- cMenuEvent ------------------------------------------------------------ + +#if VDRVERSNUM < 10300 +class cMenuEvent : public cOsdMenu { +private: + const cEventInfo *eventInfo; +public: + cMenuEvent(const cEventInfo *EventInfo, bool CanSwitch = false); + cMenuEvent(bool Now); + virtual eOSState ProcessKey(eKeys Key); +}; +#elif VDRVERSNUM < 10307 +class cMenuEvent : public cOsdMenu { +private: + const cEvent *event; +public: + cMenuEvent(const cEvent *Event, bool CanSwitch = false); + cMenuEvent(bool Now); + virtual eOSState ProcessKey(eKeys Key); +}; +#else +class cMenuEvent : public cOsdMenu { +private: + const cEvent *event; +public: + cMenuEvent(const cEvent *Event, bool CanSwitch = false); + virtual void Display(void); + virtual eOSState ProcessKey(eKeys Key); +}; +#endif + +// --- cStreamdevMenuWhatsOn ------------------------------------------------- + +int cStreamdevMenuWhatsOn::m_CurrentChannel = 0; +#if VDRVERSNUM < 10300 +const cEventInfo *cStreamdevMenuWhatsOn::m_ScheduleEventInfo = NULL; +#else +const cEvent *cStreamdevMenuWhatsOn::m_ScheduleEventInfo = NULL; +#endif + +#if VDRVERSNUM < 10300 +static int CompareEventChannel(const void *p1, const void *p2) { + return (int)((*(const cEventInfo**)p1)->GetChannelNumber() + - (*(const cEventInfo**)p2)->GetChannelNumber()); +} +#endif + +cStreamdevMenuWhatsOn::cStreamdevMenuWhatsOn(const cSchedules *Schedules, + bool Now, int CurrentChannel): + cOsdMenu(Now ? tr("What's on now?") : tr("What's on next?"), CHNUMWIDTH, + 7, 6) { +#if VDRVERSNUM < 10300 + const cSchedule *Schedule = Schedules->First(); + const cEventInfo **pArray = NULL; + int num = 0; + + while (Schedule) { + pArray=(const cEventInfo**)realloc(pArray, (num + 1) * sizeof(cEventInfo*)); + pArray[num] = Now ? Schedule->GetPresentEvent() + : Schedule->GetFollowingEvent(); + if (pArray[num]) { + cChannel *channel + = Channels.GetByChannelID(pArray[num]->GetChannelID(), true); + if (channel) + pArray[num++]->SetChannelNumber(channel->Number()); + } + Schedule = Schedules->Next(Schedule); + } + + qsort(pArray, num, sizeof(cEventInfo*), CompareEventChannel); + for (int a = 0; a < num; ++a) { + int channelnr = pArray[a]->GetChannelNumber(); +# ifdef HAVE_BEAUTYPATCH + Add(new cMenuWhatsOnItem(pArray[a],Now), channelnr == CurrentChannel); +# else + Add(new cMenuWhatsOnItem(pArray[a]), channelnr == CurrentChannel); +# endif + } + + free(pArray); +#else + for (cChannel *Channel = Channels.First(); Channel; + Channel = Channels.Next(Channel)) { + if (!Channel->GroupSep()) { + const cSchedule *Schedule + = Schedules->GetSchedule(Channel->GetChannelID()); + if (Schedule) { + const cEvent *Event = Now ? Schedule->GetPresentEvent() + : Schedule->GetFollowingEvent(); + if (Event) + Add(new cMenuWhatsOnItem(Event, Channel), + Channel->Number() == CurrentChannel); + } + } + } +#endif + m_CurrentChannel = CurrentChannel; + SetHelp(Count() ? tr("Record") : NULL, Now ? tr("Next") : tr("Now"), + tr("Schedule"), tr("Switch")); +} + +#if VDRVERSNUM < 10300 +const cEventInfo *cStreamdevMenuWhatsOn::ScheduleEventInfo(void) { + const cEventInfo *ei = m_ScheduleEventInfo; + m_ScheduleEventInfo = NULL; + return ei; +} +#else +const cEvent *cStreamdevMenuWhatsOn::ScheduleEventInfo(void) { + const cEvent *ei = m_ScheduleEventInfo; + m_ScheduleEventInfo = NULL; + return ei; +} +#endif + +eOSState cStreamdevMenuWhatsOn::Switch(void) { + cMenuWhatsOnItem *item = (cMenuWhatsOnItem*)Get(Current()); + if (item) { + cChannel *channel +#if VDRVERSNUM < 10300 + = Channels.GetByChannelID(item->eventInfo->GetChannelID(), true); +#else + = Channels.GetByChannelID(item->event->ChannelID(), true); +#endif + if (channel && cDevice::PrimaryDevice()->SwitchChannel(channel, true)) + return osEnd; + } + ERROR(tr("Can't switch channel!")); + return osContinue; +} + +eOSState cStreamdevMenuWhatsOn::Record(void) { + cMenuWhatsOnItem *item = (cMenuWhatsOnItem*)Get(Current()); + if (item) { + cRemoteTimer *timer +#if VDRVERSNUM < 10300 + = new cRemoteTimer(item->eventInfo); +#else + = new cRemoteTimer(item->event); +#endif + return AddSubMenu(new cStreamdevMenuEditTimer(timer)); + // Load remote timers and see if timer exists before editing + } + return osContinue; +} + +eOSState cStreamdevMenuWhatsOn::ProcessKey(eKeys Key) { + eOSState state = cOsdMenu::ProcessKey(Key); + if (state == osUnknown) { + switch (Key) { + case kRecord: + case kRed: + return Record(); + + case kYellow: + state = osBack; + case kGreen: + { + cMenuWhatsOnItem *mi = (cMenuWhatsOnItem*)Get(Current()); + if (mi) { +#if VDRVERSNUM < 10300 + m_ScheduleEventInfo = mi->eventInfo; + m_CurrentChannel = mi->eventInfo->GetChannelNumber(); +#else + m_ScheduleEventInfo = mi->event; + m_CurrentChannel = mi->channel->Number(); +#endif + } + } + break; + + case kBlue: + return Switch(); + + case kOk: + if (Count()) +#if VDRVERSNUM < 10300 + return AddSubMenu(new cMenuEvent( + ((cMenuWhatsOnItem*)Get(Current()))->eventInfo, true)); +#else + return AddSubMenu(new cMenuEvent( + ((cMenuWhatsOnItem*)Get(Current()))->event, true)); +#endif + break; + + default: + break; + } + } + return state; +} + +// --- cMenuScheduleItem ----------------------------------------------------- + +#if VDRVERSNUM < 10300 +class cMenuScheduleItem : public cOsdItem { +public: + const cEventInfo *eventInfo; + cMenuScheduleItem(const cEventInfo *EventInfo); +}; +#else +class cMenuScheduleItem : public cOsdItem { +public: + const cEvent *event; + cMenuScheduleItem(const cEvent *Event); +}; +#endif + +// --- cStreamdevMenuSchedule ------------------------------------------------ + +cStreamdevMenuSchedule::cStreamdevMenuSchedule(void): +#if VDRVERSNUM < 10300 + cOsdMenu("", 6, 6) +#else + cOsdMenu("", 7, 6, 4) +#endif +{ + m_Now = false; + m_Next = false; + m_OtherChannel = -1; + m_Schedules = NULL; + + cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel()); + if (channel) { +#if VDRVERSNUM < 10300 + m_Schedules = cSIProcessor::Schedules(m_Lock); +#else + m_Schedules = cSchedules::Schedules(m_Lock); +#endif + PrepareSchedule(channel); + SetHelp(Count() ? tr("Record") : NULL, tr("Now"), tr("Next")); + } +} + +cStreamdevMenuSchedule::~cStreamdevMenuSchedule() { +} + +#if VDRVERSNUM < 10307 +static int CompareEventTime(const void *p1, const void *p2) { +#if VDRVERSNUM < 10300 + return (int)((*(cEventInfo **)p1)->GetTime() + - (*(cEventInfo **)p2)->GetTime()); +#else + return (int)((*(cEvent**)p1)->StartTime() + - (*(cEvent**)p2)->StartTime()); +#endif +} +#endif + +void cStreamdevMenuSchedule::PrepareSchedule(cChannel *Channel) { +#if VDRVERSNUM < 10300 + cTBString buffer; + Clear(); + buffer.Format(tr("Schedule - %s"), Channel->Name()); + SetTitle(buffer); + if (m_Schedules) { + const cSchedule *Schedule=m_Schedules->GetSchedule(Channel->GetChannelID()); + if (Schedule) { + int num = Schedule->NumEvents(); + const cEventInfo **pArray = MALLOC(const cEventInfo*, num); + if (pArray) { + time_t now = time(NULL); + int numreal = 0; + for (int a = 0; a < num; ++a) { + const cEventInfo *EventInfo = Schedule->GetEventNumber(a); + if (EventInfo->GetTime() + EventInfo->GetDuration() > now) + pArray[numreal++] = EventInfo; + } + + qsort(pArray, numreal, sizeof(cEventInfo*), CompareEventTime); + for (int a = 0; a < numreal; ++a) + Add(new cMenuScheduleItem(pArray[a])); + free(pArray); + } + } + } +#else + Clear(); + char *buffer = NULL; + asprintf(&buffer, tr("Schedule - %s"), Channel->Name()); + SetTitle(buffer); + free(buffer); + if (m_Schedules) { + const cSchedule *Schedule = m_Schedules->GetSchedule(Channel->GetChannelID()); + if (Schedule) { + const cEvent *PresentEvent = Schedule->GetPresentEvent(Channel->Number() == cDevice::CurrentChannel()); + time_t now = time(NULL) - Setup.EPGLinger * 60; + for (const cEvent *Event = Schedule->Events()->First(); Event; Event = Schedule->Events()->Next(Event)) { + if (Event->EndTime() > now || Event == PresentEvent) + Add(new cMenuScheduleItem(Event), Event == PresentEvent); + } + } + } +#endif +} + +eOSState cStreamdevMenuSchedule::Switch(void) { + if (m_OtherChannel) { + if (Channels.SwitchTo(m_OtherChannel)) + return osEnd; + } + ERROR(tr("Can't switch channel!")); + return osContinue; +} + +eOSState cStreamdevMenuSchedule::Record(void) { + cMenuScheduleItem *item = (cMenuScheduleItem*)Get(Current()); + if (item) { + cRemoteTimer *timer +#if VDRVERSNUM < 10300 + = new cRemoteTimer(item->eventInfo); +#else + = new cRemoteTimer(item->event); +#endif + return AddSubMenu(new cStreamdevMenuEditTimer(timer)); + // Load remote timers and see if timer exists before editing + } + return osContinue; +} + +eOSState cStreamdevMenuSchedule::ProcessKey(eKeys Key) { + eOSState state = cOsdMenu::ProcessKey(Key); + if (state == osUnknown) { + switch (Key) { + case kRecord: + case kRed: + return Record(); + + case kGreen: + if (m_Schedules) { + if (!m_Now && !m_Next) { + int channelnr = 0; + if (Count()) { + cChannel *channel +#if VDRVERSNUM < 10300 + = Channels.GetByChannelID( + ((cMenuScheduleItem*)Get(Current()))->eventInfo->GetChannelID(), + true); +#else + = Channels.GetByChannelID( + ((cMenuScheduleItem*)Get(Current()))->event->ChannelID(), true); +#endif + if (channel) + channelnr = channel->Number(); + } + m_Now = true; + return AddSubMenu(new cStreamdevMenuWhatsOn(m_Schedules, true, + channelnr)); + } + m_Now = !m_Now; + m_Next = !m_Next; + return AddSubMenu(new cStreamdevMenuWhatsOn(m_Schedules, m_Now, + cStreamdevMenuWhatsOn::CurrentChannel())); + } + + case kYellow: + if (m_Schedules) + return AddSubMenu(new cStreamdevMenuWhatsOn(m_Schedules, false, + cStreamdevMenuWhatsOn::CurrentChannel())); + break; + + case kBlue: + if (Count()) + return Switch(); + break; + + case kOk: + if (Count()) +#if VDRVERSNUM < 10300 + return AddSubMenu(new cMenuEvent( + ((cMenuScheduleItem*)Get(Current()))->eventInfo, m_OtherChannel)); +#else + return AddSubMenu(new cMenuEvent( + ((cMenuScheduleItem*)Get(Current()))->event, m_OtherChannel)); +#endif + break; + + default: + break; + } + } else if (!HasSubMenu()) { + m_Now = false; + m_Next = false; +#if VDRVERSNUM < 10300 + const cEventInfo *ei +#else + const cEvent *ei +#endif + = cStreamdevMenuWhatsOn::ScheduleEventInfo(); + if (ei) { + cChannel *channel +#if VDRVERSNUM < 10300 + = Channels.GetByChannelID(ei->GetChannelID(), true); +#else + = Channels.GetByChannelID(ei->ChannelID(), true); +#endif + if (channel) { + PrepareSchedule(channel); + if (channel->Number() != cDevice::CurrentChannel()) { + m_OtherChannel = channel->Number(); + SetHelp(Count() ? tr("Record") : NULL, tr("Now"), tr("Next"), + tr("Switch")); + } + Display(); + } + } + } + return state; +} + +// --- cStreamdevMenuRecordingItem ------------------------------------------- + +class cStreamdevMenuRecordingItem: public cOsdItem { +private: + int m_Total; + int m_New; + char *m_FileName; + char *m_Name; + +public: + cStreamdevMenuRecordingItem(cRemoteRecording *Recording, int Level); + virtual ~cStreamdevMenuRecordingItem(); + + void IncrementCounter(bool New); + const char *Name(void) const { return m_Name; } + const char *FileName(void) const { return m_FileName; } + bool IsDirectory(void) const { return m_Name != NULL; } +}; + +cStreamdevMenuRecordingItem::cStreamdevMenuRecordingItem( + cRemoteRecording *Recording, int Level) { + m_FileName = strdup(Recording->Name()); + m_Name = NULL; + m_Total = m_New = 0; + SetText(Recording->Title('\t', true, Level)); + if (*Text() == '\t') + m_Name = strdup(Text() + 2); +} + +cStreamdevMenuRecordingItem::~cStreamdevMenuRecordingItem() { +} + +void cStreamdevMenuRecordingItem::IncrementCounter(bool New) { + ++m_Total; + if (New) ++m_New; + char *buffer = NULL; + asprintf(&buffer, "%d\t%d\t%s", m_Total, m_New, m_Name); + SetText(buffer, false); +} + +// --- cStreamdevMenuRecordings ---------------------------------------------- + +cRemoteRecordings cStreamdevMenuRecordings::Recordings; +int cStreamdevMenuRecordings::HelpKeys = -1; + +cStreamdevMenuRecordings::cStreamdevMenuRecordings(const char *Base, int Level, + bool OpenSubMenus): + cOsdMenu(Base ? Base : tr("Remote Recordings"), 6, 6) { + m_Base = Base ? strdup(Base) : NULL; + m_Level = Setup.RecordingDirs ? Level : -1; + + Display(); // this keeps the higher level menus from showing up briefly when + // pressing 'Back' during replay + + if (!Base) { + STATUS(tr("Fetching recordings...")); + FLUSH(); + } + + if (Base || Recordings.Load()) { + cStreamdevMenuRecordingItem *LastItem = NULL; + char *LastItemText = NULL; + for (cRemoteRecording *r = Recordings.First(); r; r = Recordings.Next(r)) { + if (!Base || (strstr(r->Name(), Base) == r->Name() + && r->Name()[strlen(Base)] == '~')) { + cStreamdevMenuRecordingItem *Item = new cStreamdevMenuRecordingItem(r, + m_Level); + if (*Item->Text() && (!LastItem || strcmp(Item->Text(), LastItemText) + != 0)) { + Add(Item); + LastItem = Item; + free(LastItemText); + LastItemText = strdup(LastItem->Text()); + } else + delete Item; + + if (LastItem) { + if (LastItem->IsDirectory()) + LastItem->IncrementCounter(r->IsNew()); + } + } + } + free(LastItemText); + if (Current() < 0) + SetCurrent(First()); + else if (OpenSubMenus && Open(true)) + return; + } + +#if VDRVERSNUM >= 10307 + STATUS(NULL); + FLUSH(); +#endif + + SetHelpKeys(); +} + +cStreamdevMenuRecordings::~cStreamdevMenuRecordings() { + if (m_Base != NULL) free(m_Base); + HelpKeys = -1; +} + +void cStreamdevMenuRecordings::SetHelpKeys(void) { + cStreamdevMenuRecordingItem *ri =(cStreamdevMenuRecordingItem*)Get(Current()); + int NewHelpKeys = HelpKeys; + if (ri) { + if (ri->IsDirectory()) + NewHelpKeys = 1; + else { + NewHelpKeys = 2; + cRemoteRecording *recording = GetRecording(ri); + if (recording && recording->Summary()) + NewHelpKeys = 3; + } + } + if (NewHelpKeys != HelpKeys) { + switch (NewHelpKeys) { + case 0: SetHelp(NULL); break; + case 1: SetHelp(tr("Open")); break; + case 2: + case 3: SetHelp(NULL, NULL, tr("Delete"), NewHelpKeys == 3 ? tr("Summary") : NULL); + //SetHelp(tr("Play"), tr("Rewind"), tr("Delete"), NewHelpKeys == 3 ? tr("Summary") : NULL); XXX + } + HelpKeys = NewHelpKeys; + } +} + +cRemoteRecording *cStreamdevMenuRecordings::GetRecording( + cStreamdevMenuRecordingItem *Item) { + Dprintf("looking for %s\n", Item->FileName()); + cRemoteRecording *recording = Recordings.GetByName(Item->FileName()); + if (!recording) + ERROR(tr("Error while accessing recording!")); + return recording; +} + +bool cStreamdevMenuRecordings::Open(bool OpenSubMenus) { + cStreamdevMenuRecordingItem *ri + = (cStreamdevMenuRecordingItem*)Get(Current()); + + if (ri && ri->IsDirectory()) { + const char *t = ri->Name(); + char *buffer = NULL; + if (m_Base) { + asprintf(&buffer, "%s~%s", m_Base, t); + t = buffer; + } + AddSubMenu(new cStreamdevMenuRecordings(t, m_Level + 1, OpenSubMenus)); + if (buffer != NULL) free(buffer); + return true; + } + return false; +} + +eOSState cStreamdevMenuRecordings::Select(void) { + cStreamdevMenuRecordingItem *ri + = (cStreamdevMenuRecordingItem*)Get(Current()); + + if (ri) { + if (ri->IsDirectory()) + Open(); + /*else { + cControl::Launch(new cStreamdevPlayerControl(ri->FileName())); + return osEnd; + } XXX */ + } + return osContinue; +} + +eOSState cStreamdevMenuRecordings::Delete(void) { + if (HasSubMenu() || Count() == 0) + return osContinue; + cStreamdevMenuRecordingItem *ri + = (cStreamdevMenuRecordingItem*)Get(Current()); + if (ri && !ri->IsDirectory()) { + if (Interface->Confirm(tr("Delete recording?"))) { + cRemoteRecording *recording = GetRecording(ri); + if (recording) { + if (ClientSocket.DeleteRecording(recording)) { + cOsdMenu::Del(Current()); + Recordings.Del(recording); + Display(); + if (!Count()) + return osBack; + } + } + } + } + return osContinue; +} + +eOSState cStreamdevMenuRecordings::Summary(void) { + if (HasSubMenu() || Count() == 0) + return osContinue; + cStreamdevMenuRecordingItem *ri=(cStreamdevMenuRecordingItem *)Get(Current()); + if (ri && !ri->IsDirectory()) { + cRemoteRecording *recording = GetRecording(ri); + if (recording && recording->Summary() && *recording->Summary()) + return AddSubMenu(new cMenuText(tr("Summary"), recording->Summary())); + } + return osContinue; +} + +eOSState cStreamdevMenuRecordings::ProcessKey(eKeys Key) { + bool HadSubMenu = HasSubMenu(); + eOSState state = cOsdMenu::ProcessKey(Key); + + if (state == osUnknown) { + switch (Key) { + case kOk: + case kRed: return Select(); + case kYellow: return Delete(); + case kBlue: return Summary(); + default: break; + } + } + + if (Key == kYellow && HadSubMenu && !HasSubMenu()) { + cOsdMenu::Del(Current()); + if (!Count()) + return osBack; + Display(); + } + + if (!HasSubMenu() && Key != kNone) + SetHelpKeys(); + return state; +} + +// --- cStreamdevMenuTimerItem ----------------------------------------------- + +class cStreamdevMenuTimerItem: public cOsdItem { +private: + cRemoteTimer *m_Timer; + +public: + cStreamdevMenuTimerItem(cRemoteTimer *Timer); + virtual ~cStreamdevMenuTimerItem(); + + virtual void Set(void); + + cRemoteTimer *Timer(void) const { return m_Timer; } +}; + +cStreamdevMenuTimerItem::cStreamdevMenuTimerItem(cRemoteTimer *Timer) { + m_Timer = Timer; + Set(); +} + +cStreamdevMenuTimerItem::~cStreamdevMenuTimerItem() { +} + +void cStreamdevMenuTimerItem::Set(void) { + char *buffer = NULL; + asprintf(&buffer, "%c\t%d\t%s\t%02d:%02d\t%02d:%02d\t%s", + !m_Timer->Active() ? ' ' : + m_Timer->FirstDay() ? '!' : + /*m_Timer->Recording() ? '#' :*/ '>', + m_Timer->Channel()->Number(), + m_Timer->PrintDay(m_Timer->Day()), + m_Timer->Start() / 100, + m_Timer->Start() % 100, + m_Timer->Stop() / 100, + m_Timer->Stop() % 100, + m_Timer->File()); + SetText(buffer, false); +} + +// --- cStreamdevMenuTimers -------------------------------------------------- + +cStreamdevMenuTimers::cStreamdevMenuTimers(void): + cOsdMenu(tr("Remote Timers"), 2, CHNUMWIDTH, 10, 6, 6) { + Refresh(); + SetHelp(tr("Edit"), tr("New"), tr("Delete"), tr("On/Off")); +} + +cStreamdevMenuTimers::~cStreamdevMenuTimers() { +} + +eOSState cStreamdevMenuTimers::ProcessKey(eKeys Key) { + int timerNum = HasSubMenu() ? Count() : -1; + eOSState state = cOsdMenu::ProcessKey(Key); + + if (state == osUnknown) { + switch (Key) { + case kOk: return Summary(); + case kRed: return Edit(); + case kGreen: return New(); + case kYellow: return Delete(); + case kBlue: OnOff(); break; + default: break; + } + } + + if (timerNum >= 0 && !HasSubMenu()) { + Refresh(); + Display(); + } + return state; +} + +eOSState cStreamdevMenuTimers::Edit(void) { + if (HasSubMenu() || Count() == 0) + return osContinue; + isyslog("Streamdev: Editing remote timer %d", CurrentTimer()->Index() + 1); + return AddSubMenu(new cStreamdevMenuEditTimer(CurrentTimer())); +} + +eOSState cStreamdevMenuTimers::New(void) { + if (HasSubMenu()) + return osContinue; + return AddSubMenu(new cStreamdevMenuEditTimer(new cRemoteTimer, true)); +} + +eOSState cStreamdevMenuTimers::Delete(void) { + cRemoteTimer *ti = CurrentTimer(); + if (ti) { + if (Interface->Confirm(tr("Delete timer?"))) { + int idx = ti->Index(); + if (ClientSocket.DeleteTimer(ti)) { + RemoteTimers.Del(ti); + cOsdMenu::Del(Current()); + isyslog("Streamdev: Remote timer %d deleted", idx + 1); + } + Refresh(); + Display(); + } + } + return osContinue; +} + +eOSState cStreamdevMenuTimers::OnOff(void) { + cRemoteTimer *timer = CurrentTimer(); + if (timer) { + cRemoteTimer data = *timer; + data.OnOff(); + if (data.FirstDay()) + isyslog("Streamdev: Remote timer %d first day set to %s", + data.Index() + 1, data.PrintFirstDay()); + else + isyslog("Streamdev: Remote timer %d %sactivated", data.Index() + 1, + data.Active() ? "" : "de"); + + if (ClientSocket.SaveTimer(timer, data)) { + *timer = data; + RefreshCurrent(); + DisplayCurrent(true); + } else { + Refresh(); + Display(); + } + } + return osContinue; +} + +eOSState cStreamdevMenuTimers::Summary(void) { + if (HasSubMenu() || Count() == 0) + return osContinue; + + cRemoteTimer *ti = CurrentTimer(); + if (ti && ti->Summary() != "") + return AddSubMenu(new cMenuText(tr("Summary"), ti->Summary().c_str())); + + return osContinue; +} + +cRemoteTimer *cStreamdevMenuTimers::CurrentTimer(void) { + cStreamdevMenuTimerItem *item = (cStreamdevMenuTimerItem*)Get(Current()); + return item ? item->Timer() : NULL; +} + +void cStreamdevMenuTimers::Refresh(void) { + Clear(); + if (RemoteTimers.Load()) { + for (cRemoteTimer *t = RemoteTimers.First(); t; t = RemoteTimers.Next(t)) { + Add(new cStreamdevMenuTimerItem(t)); + } + } +} diff --git a/client/menu.h b/client/menu.h new file mode 100644 index 0000000..dc873d2 --- /dev/null +++ b/client/menu.h @@ -0,0 +1,144 @@ +/* + * $Id: menu.h,v 1.1.1.1 2004/12/30 22:44:02 lordjaxom Exp $ + */ + +#ifndef VDR_STREAMDEV_MENU_H +#define VDR_STREAMDEV_MENU_H + +#include + +#include "client/remote.h" + +class cStreamdevMenuRecordingItem; + +// --- cStreamdevMenu -------------------------------------------------------- + +class cStreamdevMenu: public cOsdMenu { +private: + enum eSubmenus { + sub_Start = os_User, + subSchedule, + subTimers, + subRecordings, + subSuspend, + subSyncEPG + }; + +protected: + void SuspendServer(void); + +public: + cStreamdevMenu(void); + virtual ~cStreamdevMenu(void); + + virtual eOSState ProcessKey(eKeys Key); +}; + +// --- cStreamdevMenuSchedule ------------------------------------------------ + +class cStreamdevMenuSchedule: public cOsdMenu { +private: + bool m_Now; + bool m_Next; + int m_OtherChannel; + const cSchedules *m_Schedules; +#if VDRVERSNUM < 10300 + cMutexLock m_Lock; +#else + cSchedulesLock m_Lock; +#endif + +protected: + void PrepareSchedule(cChannel *Channel); + + eOSState Switch(void); + eOSState Record(void); + +public: + cStreamdevMenuSchedule(void); + virtual ~cStreamdevMenuSchedule(void); + + virtual eOSState ProcessKey(eKeys Key); +}; + +// --- cStreamdevMenuWhatsOn ------------------------------------------------- + +class cStreamdevMenuWhatsOn: public cOsdMenu { +private: + static int m_CurrentChannel; +#if VDRVERSNUM < 10300 + static const cEventInfo *m_ScheduleEventInfo; +#else + static const cEvent *m_ScheduleEventInfo; +#endif + +protected: + eOSState Switch(void); + eOSState Record(void); + +public: + cStreamdevMenuWhatsOn(const cSchedules *Schedules, bool Now, + int CurrentChannel); + + static int CurrentChannel(void) { return m_CurrentChannel; } + static void SetCurrentChannel(int Channel) { m_CurrentChannel = Channel; } +#if VDRVERSNUM < 10300 + static const cEventInfo *ScheduleEventInfo(void); +#else + static const cEvent *ScheduleEventInfo(void); +#endif + + virtual eOSState ProcessKey(eKeys Key); +}; + +// --- cStreamdevMenuRecordings ---------------------------------------------- + +class cStreamdevMenuRecordings: public cOsdMenu { +private: + char *m_Base; + int m_Level; + + static int HelpKeys; + static cRemoteRecordings Recordings; + +protected: + bool Open(bool OpenSubMenus = false); + void SetHelpKeys(); + cRemoteRecording *cStreamdevMenuRecordings::GetRecording( + cStreamdevMenuRecordingItem *Item); + + eOSState Select(void); + eOSState Delete(void); + eOSState Summary(void); + +public: + cStreamdevMenuRecordings(const char *Base = NULL, int Level = 0, + bool OpenSubMenus = false); + virtual ~cStreamdevMenuRecordings(); + + virtual eOSState ProcessKey(eKeys Key); +}; + +// --- cStreamdevMenuTimers -------------------------------------------------- + +class cStreamdevMenuTimers: public cOsdMenu { +protected: + eOSState Edit(void); + eOSState New(void); + eOSState Delete(void); + eOSState OnOff(void); + eOSState Summary(void); + + cRemoteTimer *CurrentTimer(void); + + void Refresh(void); + +public: + cStreamdevMenuTimers(void); + virtual ~cStreamdevMenuTimers(); + + virtual eOSState ProcessKey(eKeys Key); +}; + +#endif // VDR_STREAMDEV_MENU_H + diff --git a/client/remote.c b/client/remote.c new file mode 100644 index 0000000..d5b1380 --- /dev/null +++ b/client/remote.c @@ -0,0 +1,476 @@ +/* + * $Id: remote.c,v 1.4 2005/04/24 16:26:14 lordjaxom Exp $ + */ + +#include + +#include "client/remote.h" +#include "client/device.h" +#include "common.h" + +cRemoteTimers RemoteTimers; + +// --- cRemoteRecording ------------------------------------------------------ + +cRemoteRecording::cRemoteRecording(const char *Text) { + m_IsValid = false; + m_Index = -1; + m_IsNew = false; + m_TitleBuffer = NULL; + + char *ptr; + char *timestr; + int idx; + + Dprintf("text: %s\n", Text); + + m_Index = strtoul(Text, &ptr, 10); + Dprintf("index: %d\n", m_Index); + if (*ptr == '\0' || *++ptr == '\0' ) return; + timestr = ptr; + while (*ptr != '\0' && !isspace(*ptr)) ++ptr; + if (*ptr == '\0' || *++ptr == '\0') return; + while (*ptr != '\0' && *ptr != '*' && !isspace(*ptr)) ++ptr; + if (*ptr == '*') m_IsNew = true; + Dprintf("new: %d\n", m_IsNew); + *(ptr++) = '\0'; + m_StartTime = timestr; + idx = -1; + while ((idx = m_StartTime.find(' ', idx + 1)) != -1) m_StartTime[idx] = '\t'; + Dprintf("m_Start: %s\n", m_StartTime.c_str()); + if (*ptr == 0) return; + if (isspace(*ptr)) ++ptr; + if (*ptr == 0) return; + m_Name = ptr; + Dprintf("file: %s\n", m_Name.c_str()); + m_IsValid = true; +} + +cRemoteRecording::~cRemoteRecording(void) { +} + +bool cRemoteRecording::operator==(const cRemoteRecording &Recording) { + return m_IsValid == Recording.m_IsValid + && m_Index == Recording.m_Index + && m_StartTime == Recording.m_StartTime + && m_Name == Recording.m_Name; +} + +void cRemoteRecording::ParseInfo(const char *Text) { + m_Summary = strreplace(strdup(Text), '|', '\n'); +} + +const char *cRemoteRecording::Title(char Delimiter, bool NewIndicator, + int Level) { + char New = NewIndicator && IsNew() ? '*' : ' '; + + if (m_TitleBuffer != NULL) { + free(m_TitleBuffer); + m_TitleBuffer = NULL; + } + + if (Level < 0 || Level == HierarchyLevels()) { + char *s; + const char *t; + if (Level > 0 && (t = strrchr(m_Name.c_str(), '~')) != NULL) + t++; + else + t = m_Name.c_str(); + + asprintf(&m_TitleBuffer, "%s%c%c%s", m_StartTime.c_str(), New, Delimiter, t); + // let's not display a trailing '~': + stripspace(m_TitleBuffer); + s = &m_TitleBuffer[strlen(m_TitleBuffer) - 1]; + if (*s == '~') + *s = 0; + } else if (Level < HierarchyLevels()) { + const char *s = m_Name.c_str(); + const char *p = s; + while (*++s) { + if (*s == '~') { + if (Level--) + p = s + 1; + else + break; + } + } + m_TitleBuffer = MALLOC(char, s - p + 3); + *m_TitleBuffer = Delimiter; + *(m_TitleBuffer + 1) = Delimiter; + strn0cpy(m_TitleBuffer + 2, p, s - p + 1); + } else + return ""; + return m_TitleBuffer; +} + +int cRemoteRecording::HierarchyLevels(void) +{ + const char *s = m_Name.c_str(); + int level = 0; + while (*++s) { + if (*s == '~') ++level; + } + return level; +} + +// --- cRemoteRecordings ----------------------------------------------------- + +bool cRemoteRecordings::Load(void) { + Clear(); + return ClientSocket.LoadRecordings(*this); +} + +cRemoteRecording *cRemoteRecordings::GetByName(const char *Name) { + for (cRemoteRecording *r = First(); r; r = Next(r)) + if (strcmp(r->Name(), Name) == 0) + return r; + return NULL; +} + +// --- cRemoteTimer ---------------------------------------------------------- + +cRemoteTimer::cRemoteTimer(const char *Text) { + m_IsValid = false; + m_Index = -1; + m_Active = -1; + m_Day = -1; + m_Start = -1; + m_Stop = -1; + m_StartTime = 0; + m_StopTime = 0; + m_Priority = -1; + m_Lifetime = -1; + m_File[0] = '\0'; + m_FirstDay = 0; + m_Buffer = NULL; + m_Channel = NULL; + + char *tmpbuf; + char *ptr; + + Dprintf("text: %s\n", Text); + + m_Index = strtoul(Text, &ptr, 10); + Dprintf("index: %d\n", m_Index); + if (*ptr == '\0' || *++ptr == '\0') return; + m_Active = strtoul(ptr, &ptr, 10); + Dprintf("m_Active: %d\n", m_Active); + if (*ptr == '\0' || *++ptr == '\0') return; + + tmpbuf = ptr; + while (*ptr != '\0' && *ptr != ':') ++ptr; + if (*ptr == '\0') return; + *(ptr++)= '\0'; + if (isnumber(tmpbuf)) + m_Channel = Channels.GetByNumber(strtoul(tmpbuf, NULL, 10)); + else + m_Channel = Channels.GetByChannelID(tChannelID::FromString(tmpbuf)); + Dprintf("channel no.: %d\n", m_Channel->Number()); + + tmpbuf = ptr; + while (*ptr != '\0' && *ptr != ':') ++ptr; + if (*ptr == '\0') return; + *(ptr++) = '\0'; + m_Day = ParseDay(tmpbuf, &m_FirstDay); + Dprintf("Day: %d\n", m_Day); + m_Start = strtoul(ptr, &ptr, 10); + Dprintf("Start: %d\n", m_Start); + if (*ptr == '\0' || *++ptr == '\0') return; + m_Stop = strtoul(ptr, &ptr, 10); + Dprintf("Stop: %d\n", m_Stop); + if (*ptr == '\0' || *++ptr == '\0') return; + m_Priority = strtoul(ptr, &ptr, 10); + Dprintf("Prio: %d\n", m_Priority); + if (*ptr == '\0' || *++ptr == '\0') return; + m_Lifetime = strtoul(ptr, &ptr, 10); + Dprintf("Lifetime: %d\n", m_Lifetime); + if (*ptr == '\0' || *++ptr == '\0') return; + tmpbuf = ptr; + while (*ptr != '\0' && *ptr != ':') ++ptr; + if (*ptr == '\0') return; + *(ptr++) = '\0'; + strncpy(m_File, tmpbuf, MaxFileName); + Dprintf("file: %s\n", m_File); + if (*ptr != '\0') m_Summary = ptr; + Dprintf("summary: %s\n", m_Summary.c_str()); + m_IsValid = true; +} + +#if VDRVERSNUM < 10300 +cRemoteTimer::cRemoteTimer(const cEventInfo *EventInfo) { + time_t tstart = EventInfo->GetTime(); + time_t tstop = tstart + EventInfo->GetDuration() + Setup.MarginStop * 60; + tstart -= Setup.MarginStart * 60; + struct tm tm_r; + struct tm *time = localtime_r(&tstart, &tm_r); + const char *title = EventInfo->GetTitle(); + cChannel *channel = Channels.GetByChannelID(EventInfo->GetChannelID(), true); +#else +cRemoteTimer::cRemoteTimer(const cEvent *Event) { + time_t tstart = Event->StartTime(); + time_t tstop = tstart + Event->Duration() + Setup.MarginStop * 60; + tstart -= Setup.MarginStart * 60; + struct tm tm_r; + struct tm *time = localtime_r(&tstart, &tm_r); + const char *title = Event->Title(); + cChannel *channel = Channels.GetByChannelID(Event->ChannelID(), true); +#endif + + m_IsValid = true; + m_Index = -1; + m_Active = true; + m_Day = time->tm_mday; + m_Start = time->tm_hour * 100 + time->tm_min; + time = localtime_r(&tstop, &tm_r); + m_Stop = time->tm_hour * 100 + time->tm_min; + m_StartTime = 0; + m_StopTime = 0; + if (m_Stop >= 2400) m_Stop -= 2400; + m_Priority = Setup.DefaultPriority; + m_Lifetime = Setup.DefaultLifetime; + m_File[0] = '\0'; + if (!isempty(title)) + strn0cpy(m_File, title, sizeof(m_File)); + m_FirstDay = 0; + m_Channel = channel; +} + +cRemoteTimer::cRemoteTimer(void) { + time_t t = time(NULL); + struct tm tm_r; + struct tm *now = localtime_r(&t, &tm_r); + + m_IsValid = true; + m_Index = -1; + m_Active = -1; + m_Day = now->tm_mday; + m_Start = now->tm_hour * 100 + now->tm_min; + m_Stop = now->tm_hour * 60 + now->tm_min + Setup.InstantRecordTime; + m_Stop = (m_Stop / 60) * 100 + (m_Stop % 60); + if (m_Stop >= 2400) m_Stop -= 2400; + m_StartTime = 0; + m_StopTime = 0; + m_Priority = Setup.DefaultPriority; + m_Lifetime = Setup.DefaultLifetime; + m_File[0] = '\0'; + m_FirstDay = 0; + m_Buffer = NULL; + m_Channel = Channels.GetByNumber(cDevice::CurrentChannel()); +} + +cRemoteTimer::~cRemoteTimer() { + if (m_Buffer != NULL) free(m_Buffer); +} + +cRemoteTimer &cRemoteTimer::operator=(const cRemoteTimer &Timer) { + Dprintf("\n\n\n\nOPÜERATHVBDÖLJVG\n\n\n"); + m_IsValid = Timer.m_IsValid; + m_Index = Timer.m_Index; + m_Active = Timer.m_Active; + m_Day = Timer.m_Day; + m_Start = Timer.m_Start; + m_Stop = Timer.m_Stop; + m_Priority = Timer.m_Priority; + m_Lifetime = Timer.m_Lifetime; + m_FirstDay = Timer.m_FirstDay; + m_Channel = Timer.m_Channel; + m_Summary = Timer.m_Summary; + return *this; +} + +bool cRemoteTimer::operator==(const cRemoteTimer &Timer) { + return m_IsValid == Timer.m_IsValid + && m_Index == Timer.m_Index + && m_Active == Timer.m_Active + && m_Day == Timer.m_Day + && m_Start == Timer.m_Start + && m_Stop == Timer.m_Stop + && m_Priority == Timer.m_Priority + && m_Lifetime == Timer.m_Lifetime + && m_FirstDay == Timer.m_FirstDay + && m_Channel == Timer.m_Channel + && strcmp(m_File, Timer.m_File) == 0 + && m_Summary == Timer.m_Summary; +} + +int cRemoteTimer::ParseDay(const char *s, time_t *FirstDay) { + char *tail; + int d = strtol(s, &tail, 10); + if (FirstDay) + *FirstDay = 0; + if (tail && *tail) { + d = 0; + if (tail == s) { + const char *first = strchr(s, '@'); + int l = first ? first - s : strlen(s); + if (l == 7) { + for (const char *p = s + 6; p >= s; p--) { + d <<= 1; + d |= (*p != '-'); + } + d |= 0x80000000; + } + if (FirstDay && first) { + ++first; + if (strlen(first) == 10) { + struct tm tm_r; + if (3 == sscanf(first, "%d-%d-%d", &tm_r.tm_year, &tm_r.tm_mon, &tm_r.tm_mday)) { + tm_r.tm_year -= 1900; + tm_r.tm_mon--; + tm_r.tm_hour = tm_r.tm_min = tm_r.tm_sec = 0; + tm_r.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting + *FirstDay = mktime(&tm_r); + } + } + else + d = 0; + } + } + } + else if (d < 1 || d > 31) + d = 0; + return d; +} + +const char *cRemoteTimer::PrintDay(int d, time_t FirstDay) { +#define DAYBUFFERSIZE 32 + static char buffer[DAYBUFFERSIZE]; + if ((d & 0x80000000) != 0) { + char *b = buffer; + const char *w = tr("MTWTFSS"); + while (*w) { + *b++ = (d & 1) ? *w : '-'; + d >>= 1; + w++; + } + if (FirstDay) { + struct tm tm_r; + localtime_r(&FirstDay, &tm_r); + b += strftime(b, DAYBUFFERSIZE - (b - buffer), "@%Y-%m-%d", &tm_r); + } + *b = 0; + } + else + sprintf(buffer, "%d", d); + return buffer; +} + +const char *cRemoteTimer::PrintFirstDay(void) const { + if (m_FirstDay) { + const char *s = PrintDay(m_Day, m_FirstDay); + if (strlen(s) == 18) + return s + 8; + } + return ""; // not NULL, so the caller can always use the result +} + +void cRemoteTimer::OnOff(void) { + if (IsSingleEvent()) + m_Active = !m_Active; + else if (m_FirstDay) { + m_FirstDay = 0; + m_Active = false; + } + else if (m_Active) + Skip(); + else + m_Active = true; + Matches(); // refresh m_Start and end time +} + +time_t cRemoteTimer::SetTime(time_t t, int SecondsFromMidnight) { + struct tm tm_r; + tm tm = *localtime_r(&t, &tm_r); + tm.tm_hour = SecondsFromMidnight / 3600; + tm.tm_min = (SecondsFromMidnight % 3600) / 60; + tm.tm_sec = SecondsFromMidnight % 60; + tm.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting + return mktime(&tm); +} + +bool cRemoteTimer::Matches(time_t t) { + m_StartTime = m_StopTime = 0; + if (t == 0) + t = time(NULL); + + int begin = TimeToInt(m_Start); // seconds from midnight + int length = TimeToInt(m_Stop) - begin; + if (length < 0) + length += SECSINDAY; + + int DaysToCheck = IsSingleEvent() ? 61 : 7; // 61 to handle months with 31/30/31 + for (int i = -1; i <= DaysToCheck; i++) { + time_t t0 = IncDay(t, i); + if (DayMatches(t0)) { + time_t a = SetTime(t0, begin); + time_t b = a + length; + if ((!m_FirstDay || a >= m_FirstDay) && t <= b) { + m_StartTime = a; + m_StopTime = b; + break; + } + } + } + if (!m_StartTime) + m_StartTime = m_FirstDay; // just to have something that's more than a week in the future + else if (t > m_StartTime || t > m_FirstDay + SECSINDAY + 3600) // +3600 in case of DST change + m_FirstDay = 0; + return m_Active && m_StartTime <= t && t < m_StopTime; // must m_Stop *before* m_StopTime to allow adjacent timers +} + +bool cRemoteTimer::DayMatches(time_t t) { + return IsSingleEvent() + ? GetMDay(t) == m_Day + : (m_Day & (1 << GetWDay(t))) != 0; +} + +int cRemoteTimer::GetMDay(time_t t) +{ + struct tm tm_r; + return localtime_r(&t, &tm_r)->tm_mday; +} + +int cRemoteTimer::GetWDay(time_t t) +{ + struct tm tm_r; + int weekday = localtime_r(&t, &tm_r)->tm_wday; + return weekday == 0 ? 6 : weekday - 1; // we start with monday==0! +} + +time_t cRemoteTimer::IncDay(time_t t, int Days) { + struct tm tm_r; + tm tm = *localtime_r(&t, &tm_r); + tm.tm_mday += Days; // now tm_mday may be out of its valid range + int h = tm.tm_hour; // save original hour to compensate for DST change + tm.tm_isdst = -1; // makes sure mktime() will determine the correct DST setting + t = mktime(&tm); // normalize all values + tm.tm_hour = h; // compensate for DST change + return mktime(&tm); // calculate final result +} + +const char *cRemoteTimer::ToText(void) { + char *summary = NULL; + + if (m_Buffer != NULL) free(m_Buffer); + + strreplace(m_File, ':', '|'); + if (m_Summary != "") + summary = strreplace(strdup(m_Summary.c_str()), ':', '|'); + + asprintf(&m_Buffer, "%d:%s:%s:%04d:%04d:%d:%d:%s:%s", m_Active, + (const char*)Channel()->GetChannelID().ToString(), PrintDay(m_Day, m_FirstDay), + m_Start, m_Stop, m_Priority, m_Lifetime, m_File, summary ? summary : ""); + + if (summary != NULL) + free(summary); + strreplace(m_File, '|', ':'); + return m_Buffer; +} + +// --- cRemoteTimers --------------------------------------------------------- + +bool cRemoteTimers::Load(void) { + Clear(); + return ClientSocket.LoadTimers(*this); +} + diff --git a/client/remote.h b/client/remote.h new file mode 100644 index 0000000..36a1b09 --- /dev/null +++ b/client/remote.h @@ -0,0 +1,131 @@ +/* + * $Id: remote.h,v 1.2 2005/02/08 17:22:35 lordjaxom Exp $ + */ + +#ifndef VDR_STREAMDEV_REMOTE_H +#define VDR_STREAMDEV_REMOTE_H + +#include +#include + +#if VDRVERSNUM < 10300 +class cEventInfo; +#else +class cEvent; +#endif +class cChannel; + +class cRemoteRecording: public cListObject { +private: + bool m_IsValid; + int m_Index; + bool m_IsNew; + char *m_TitleBuffer; + std::string m_StartTime; + std::string m_Name; + std::string m_Summary; + +public: + cRemoteRecording(const char *Text); + ~cRemoteRecording(); + + bool operator==(const cRemoteRecording &Recording); + bool operator!=(const cRemoteRecording &Recording); + + void ParseInfo(const char *Text); + + bool IsValid(void) const { return m_IsValid; } + int Index(void) const { return m_Index; } + const char *StartTime(void) const { return m_StartTime.c_str(); } + bool IsNew(void) const { return m_IsNew; } + const char *Name(void) const { return m_Name.c_str(); } + const char *Summary(void) const { return m_Summary.c_str(); } + const char *Title(char Delimiter, bool NewIndicator, int Level); + int HierarchyLevels(void); +}; + +inline bool cRemoteRecording::operator!=(const cRemoteRecording &Recording) { + return !operator==(Recording); +} + +class cRemoteRecordings: public cList { +public: + bool Load(void); + cRemoteRecording *GetByName(const char *Name); +}; + +class cRemoteTimer: public cListObject { + friend class cStreamdevMenuEditTimer; + +private: + bool m_IsValid; + int m_Index; + int m_Active; + int m_Day; + int m_Start; + int m_Stop; + time_t m_StartTime; + time_t m_StopTime; + int m_Priority; + int m_Lifetime; + char m_File[MaxFileName]; + time_t m_FirstDay; + std::string m_Summary; + char *m_Buffer; + const cChannel *m_Channel; + +public: + cRemoteTimer(const char *Text); +#if VDRVERSNUM < 10300 + cRemoteTimer(const cEventInfo *EventInfo); +#else + cRemoteTimer(const cEvent *Event); +#endif + cRemoteTimer(void); + ~cRemoteTimer(); + + cRemoteTimer &operator=(const cRemoteTimer &Timer); + bool operator==(const cRemoteTimer &Timer); + bool operator!=(const cRemoteTimer &Timer) { return !operator==(Timer); } + + static int ParseDay(const char *s, time_t *FirstDay); + static const char *PrintDay(int d, time_t FirstDay = 0); + static time_t SetTime(time_t t, int SecondsFromMidnight); + static time_t IncDay(time_t t, int Days); + static int TimeToInt(int t) { return (t / 100 * 60 + t % 100) * 60; } + + const char *PrintFirstDay(void) const; + void OnOff(void); + bool IsSingleEvent(void) const { return (m_Day & 0x80000000) == 0; } + void Skip(void) { m_FirstDay = IncDay(SetTime(StartTime(), 0), 1); } + bool Matches(time_t t = 0); + bool DayMatches(time_t t = 0); + int GetMDay(time_t t); + int GetWDay(time_t t); + + bool IsValid(void) const { return m_IsValid; } + int Index(void) const { return m_Index; } + int Active(void) const { return m_Active; } + int Day(void) const { return m_Day; } + int Start(void) const { return m_Start; } + int Stop(void) const { return m_Stop; } + time_t StartTime(void) { if (!m_StartTime) Matches(); return m_StartTime; } + time_t StopTime(void) { if (!m_StopTime) Matches(); return m_StopTime; } + int Priority(void) const { return m_Priority; } + int Lifetime(void) const { return m_Lifetime; } + const char *File(void) const { return m_File; } + time_t FirstDay(void) const { return m_FirstDay; } + const std::string &Summary(void) const { return m_Summary; } + const cChannel *Channel(void) const { return m_Channel; } + + const char *ToText(void); +}; + +class cRemoteTimers: public cList { +public: + bool Load(void); +}; + +extern cRemoteTimers RemoteTimers; + +#endif // VDR_STREAMDEV_REMOTE_H diff --git a/client/setup.c b/client/setup.c new file mode 100644 index 0000000..8d7b9cb --- /dev/null +++ b/client/setup.c @@ -0,0 +1,79 @@ +/* + * $Id: setup.c,v 1.2 2005/02/08 15:34:38 lordjaxom Exp $ + */ + +#include + +#include "client/setup.h" +#include "client/device.h" +#include "i18n.h" + +cStreamdevClientSetup StreamdevClientSetup; + +cStreamdevClientSetup::cStreamdevClientSetup(void) { + StartClient = false; + RemotePort = 2004; +#if VDRVERSNUM >= 10300 + StreamFilters = false; +#endif + SyncEPG = false; + strcpy(RemoteIp, ""); +} + +bool cStreamdevClientSetup::SetupParse(const char *Name, const char *Value) { + if (strcmp(Name, "StartClient") == 0) StartClient = atoi(Value); + else if (strcmp(Name, "RemoteIp") == 0) { + if (strcmp(Value, "-none-") == 0) + strcpy(RemoteIp, ""); + else + strcpy(RemoteIp, Value); + } + else if (strcmp(Name, "RemotePort") == 0) RemotePort = atoi(Value); +#if VDRVERSNUM >= 10300 + else if (strcmp(Name, "StreamFilters") == 0) StreamFilters = atoi(Value); +#endif + else if (strcmp(Name, "SyncEPG") == 0) SyncEPG = atoi(Value); + else return false; + return true; +} + +cStreamdevClientMenuSetupPage::cStreamdevClientMenuSetupPage(void) { + m_NewSetup = StreamdevClientSetup; + + AddBoolEdit (tr("Start Client"), m_NewSetup.StartClient); + AddIpEdit (tr("Remote IP"), m_NewSetup.RemoteIp); + AddShortEdit(tr("Remote Port"), m_NewSetup.RemotePort); +#if VDRVERSNUM >= 10300 + AddBoolEdit (tr("Filter Streaming"), m_NewSetup.StreamFilters); +#endif + AddBoolEdit (tr("Synchronize EPG"), m_NewSetup.SyncEPG); + SetCurrent(Get(0)); +} + +cStreamdevClientMenuSetupPage::~cStreamdevClientMenuSetupPage() { +} + +void cStreamdevClientMenuSetupPage::Store(void) { + if (m_NewSetup.StartClient != StreamdevClientSetup.StartClient) { + if (m_NewSetup.StartClient) + cStreamdevDevice::Init(); + else + INFO(tr("Please restart VDR to activate changes")); + } + + 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); +#if VDRVERSNUM >= 10300 + SetupStore("StreamFilters", m_NewSetup.StreamFilters); +#endif + SetupStore("SyncEPG", m_NewSetup.SyncEPG); + + StreamdevClientSetup = m_NewSetup; + + cStreamdevDevice::ReInit(); +} + diff --git a/client/setup.h b/client/setup.h new file mode 100644 index 0000000..b0a1867 --- /dev/null +++ b/client/setup.h @@ -0,0 +1,38 @@ +/* + * $Id: setup.h,v 1.2 2005/02/08 15:34:38 lordjaxom 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; +#if VDRVERSNUM >= 10300 + int StreamFilters; +#endif + int SyncEPG; +}; + +extern cStreamdevClientSetup StreamdevClientSetup; + +class cStreamdevClientMenuSetupPage: public cStreamdevMenuSetupPage { +private: + cStreamdevClientSetup m_NewSetup; + +protected: + virtual void Store(void); + +public: + cStreamdevClientMenuSetupPage(void); + virtual ~cStreamdevClientMenuSetupPage(); +}; + +#endif // VDR_STREAMDEV_SETUPCLIENT_H diff --git a/client/socket.c b/client/socket.c new file mode 100644 index 0000000..e59c705 --- /dev/null +++ b/client/socket.c @@ -0,0 +1,587 @@ +/* + * $Id: socket.c,v 1.7 2007/01/15 11:45:48 schmirl Exp $ + */ + +#include +#include +#include +#include +#include +#include + +#define MINLOGREPEAT 10 //don't log connect failures too often (seconds) + +#include "client/socket.h" +#include "client/setup.h" +#include "client/remote.h" +#include "common.h" +#include "i18n.h" + +cClientSocket ClientSocket; + +cClientSocket::cClientSocket(void) +{ + memset(m_DataSockets, 0, sizeof(cTBSocket*) * si_Count); + Reset(); +} + +cClientSocket::~cClientSocket() +{ + Reset(); + if (IsOpen()) Quit(); +} + +void cClientSocket::Reset(void) +{ + for (int it = 0; it < si_Count; ++it) { + if (m_DataSockets[it] != NULL) + DELETENULL(m_DataSockets[it]); + } +} + +cTBSocket *cClientSocket::DataSocket(eSocketId Id) const { + return m_DataSockets[Id]; +} + +bool cClientSocket::Command(const std::string &Command, uint Expected, uint TimeoutMs) +{ + errno = 0; + + std::string pkt = Command + "\015\012"; + Dprintf("OUT: |%s|\n", Command.c_str()); + + cTimeMs starttime; + if (!TimedWrite(pkt.c_str(), pkt.size(), TimeoutMs)) { + esyslog("Streamdev: Lost connection to %s:%d: %s", RemoteIp().c_str(), RemotePort(), + strerror(errno)); + Close(); + return false; + } + + uint64_t elapsed = starttime.Elapsed(); + if (Expected != 0) { // XXX+ What if elapsed > TimeoutMs? + TimeoutMs -= elapsed; + return Expect(Expected, NULL, TimeoutMs); + } + + return true; +} + +bool cClientSocket::Expect(uint Expected, std::string *Result, uint TimeoutMs) { + char *endptr; + int bufcount; + bool res; + + errno = 0; + + if ((bufcount = ReadUntil(m_Buffer, sizeof(m_Buffer) - 1, "\012", TimeoutMs)) == -1) { + esyslog("Streamdev: Lost connection to %s:%d: %s", RemoteIp().c_str(), RemotePort(), + strerror(errno)); + Close(); + return false; + } + if (m_Buffer[bufcount - 1] == '\015') + --bufcount; + m_Buffer[bufcount] = '\0'; + Dprintf("IN: |%s|\n", m_Buffer); + + if (Result != NULL) + *Result = m_Buffer; + + res = strtoul(m_Buffer, &endptr, 10) == Expected; + return res; +} + +bool cClientSocket::CheckConnection(void) { + CMD_LOCK; + + if (IsOpen()) { + cTBSelect select; + + Dprintf("connection open\n"); + + // XXX+ check if connection is still alive (is there a better way?) + // There REALLY shouldn't be anything readable according to PROTOCOL here + // If there is, assume it's an eof signal (subseq. read would return 0) + select.Add(*this, false); + int res; + if ((res = select.Select(0)) == 0) { + Dprintf("select said nothing happened\n"); + return true; + } + Dprintf("closing connection (res was %d)", res); + Close(); + } + + if (!Connect(StreamdevClientSetup.RemoteIp, StreamdevClientSetup.RemotePort)){ + static time_t lastTime = 0; + if (time(NULL) - lastTime > MINLOGREPEAT) { + esyslog("ERROR: Streamdev: Couldn't connect to %s:%d: %s", + (const char*)StreamdevClientSetup.RemoteIp, + StreamdevClientSetup.RemotePort, strerror(errno)); + lastTime = time(NULL); + } + return false; + } + + if (!Expect(220)) { + if (errno == 0) + esyslog("ERROR: Streamdev: Didn't receive greeting from %s:%d", + RemoteIp().c_str(), RemotePort()); + Close(); + return false; + } + + if (!Command("CAPS TSPIDS", 220)) { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't negotiate capabilities on %s:%d", + RemoteIp().c_str(), RemotePort()); + Close(); + return false; + } + + isyslog("Streamdev: Connected to server %s:%d using capabilities TSPIDS", + RemoteIp().c_str(), RemotePort()); + return true; +} + +bool cClientSocket::ProvidesChannel(const cChannel *Channel, int Priority) { + if (!CheckConnection()) return false; + + CMD_LOCK; + + std::string command = (std::string)"PROV " + (const char*)itoa(Priority) + " " + + (const char*)Channel->GetChannelID().ToString(); + if (!Command(command)) + return false; + + std::string buffer; + if (!Expect(220, &buffer)) { + if (buffer.substr(0, 3) != "560" && errno == 0) + esyslog("ERROR: Streamdev: Couldn't check if %s:%d provides channel %s", + RemoteIp().c_str(), RemotePort(), Channel->Name()); + return false; + } + return true; +} + +bool cClientSocket::CreateDataConnection(eSocketId Id) { + cTBSocket listen(SOCK_STREAM); + + if (!CheckConnection()) return false; + + if (m_DataSockets[Id] != NULL) + DELETENULL(m_DataSockets[Id]); + + if (!listen.Listen(LocalIp(), 0, 1)) { + esyslog("ERROR: Streamdev: Couldn't create data connection: %s", + strerror(errno)); + return false; + } + + std::string command = (std::string)"PORT " + (const char*)itoa(Id) + " " + + LocalIp().c_str() + "," + + (const char*)itoa((listen.LocalPort() >> 8) & 0xff) + "," + + (const char*)itoa(listen.LocalPort() & 0xff); + size_t idx = 4; + while ((idx = command.find('.', idx + 1)) != (size_t)-1) + command[idx] = ','; + + CMD_LOCK; + + if (!Command(command, 220)) { + Dprintf("error: %m\n"); + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't establish data connection to %s:%d", + RemoteIp().c_str(), RemotePort()); + return false; + } + + /* The server SHOULD do the following: + * - get PORT command + * - connect to socket + * - return 220 + */ + + m_DataSockets[Id] = new cTBSocket; + if (!m_DataSockets[Id]->Accept(listen)) { + esyslog("ERROR: Streamdev: Couldn't establish data connection to %s:%d%s%s", + RemoteIp().c_str(), RemotePort(), errno == 0 ? "" : ": ", + errno == 0 ? "" : strerror(errno)); + DELETENULL(m_DataSockets[Id]); + return false; + } + + return true; +} + +bool cClientSocket::SetChannelDevice(const cChannel *Channel) { + if (!CheckConnection()) return false; + + CMD_LOCK; + + std::string command = (std::string)"TUNE " + + (const char*)Channel->GetChannelID().ToString(); + if (!Command(command, 220)) { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't tune %s:%d to channel %s", + RemoteIp().c_str(), RemotePort(), Channel->Name()); + return false; + } + return true; +} + +bool cClientSocket::SetPid(int Pid, bool On) { + if (!CheckConnection()) return false; + + CMD_LOCK; + + std::string command = (std::string)(On ? "ADDP " : "DELP ") + (const char*)itoa(Pid); + if (!Command(command, 220)) { + if (errno == 0) + esyslog("Streamdev: Pid %d not available from %s:%d", Pid, LocalIp().c_str(), + LocalPort()); + return false; + } + return true; +} + +#if VDRVERSNUM >= 10300 +bool cClientSocket::SetFilter(ushort Pid, uchar Tid, uchar Mask, bool On) { + if (!CheckConnection()) return false; + + CMD_LOCK; + + std::string command = (std::string)(On ? "ADDF " : "DELF ") + (const char*)itoa(Pid) + + " " + (const char*)itoa(Tid) + " " + (const char*)itoa(Mask); + if (!Command(command, 220)) { + if (errno == 0) + esyslog("Streamdev: Filter %hu, %hhu, %hhu not available from %s:%d", + Pid, Tid, Mask, LocalIp().c_str(), LocalPort()); + return false; + } + return true; +} +#endif + +bool cClientSocket::CloseDvr(void) { + if (!CheckConnection()) return false; + + CMD_LOCK; + + if (m_DataSockets[siLive] != NULL) { + std::string command = (std::string)"ABRT " + (const char*)itoa(siLive); + if (!Command(command, 220)) { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't cleanly close data connection"); + return false; + } + + DELETENULL(m_DataSockets[siLive]); + } + return true; +} + +bool cClientSocket::SynchronizeEPG(void) { + std::string buffer; + bool result; + FILE *epgfd; + + if (!CheckConnection()) return false; + + isyslog("Streamdev: Synchronizing EPG from server\n"); + + CMD_LOCK; + + if (!Command("LSTE")) + return false; + + if ((epgfd = tmpfile()) == NULL) { + esyslog("ERROR: Streamdev: Error while processing EPG data: %s", + strerror(errno)); + return false; + } + + while ((result = Expect(215, &buffer))) { + if (buffer[3] == ' ') break; + fputs(buffer.c_str() + 4, epgfd); + fputc('\n', epgfd); + } + + if (!result) { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't fetch EPG data from %s:%d", + RemoteIp().c_str(), RemotePort()); + fclose(epgfd); + return false; + } + + rewind(epgfd); + if (cSchedules::Read(epgfd)) +#if VDRVERSNUM < 10300 + cSIProcessor::TriggerDump(); +#else + cSchedules::Cleanup(true); +#endif + else { + esyslog("ERROR: Streamdev: Parsing EPG data failed"); + fclose(epgfd); + return false; + } + fclose(epgfd); + return true; +} + +bool cClientSocket::Quit(void) { + bool res; + + if (!CheckConnection()) return false; + + if (!(res = Command("QUIT", 221))) { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't quit command connection to %s:%d", + RemoteIp().c_str(), RemotePort()); + } + Close(); + return res; +} + +bool cClientSocket::LoadRecordings(cRemoteRecordings &Recordings) { + bool res; + + if (!CheckConnection()) return false; + + CMD_LOCK; + + if (!Command("LSTR")) + return false; + + std::string buffer; + while ((res = Expect(250, &buffer))) { + cRemoteRecording *rec = new cRemoteRecording(buffer.c_str() + 4); + Dprintf("recording valid: %d\n", rec->IsValid()); + if (rec->IsValid()) + Recordings.Add(rec); + else + delete rec; + if (buffer[3] == ' ') break; + } + + if (!res && buffer.substr(0, 3) != "550") { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't fetch recordings from %s:%d", + RemoteIp().c_str(), RemotePort()); + return false; + } + + for (cRemoteRecording *r = Recordings.First(); r; r = Recordings.Next(r)) { + std::string command = (std::string)"LSTR " + (const char*)itoa(r->Index()); + if (!Command(command)) + return false; + + if (Expect(250, &buffer)) + r->ParseInfo(buffer.c_str() + 4); + else if (buffer.substr(0, 3) != "550") { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't fetch details for recording from %s:%d", + RemoteIp().c_str(), RemotePort()); + return false; + } + Dprintf("recording complete: %d\n", r->Index()); + } + return res; +} + +bool cClientSocket::StartReplay(const char *Filename) { + if (!CheckConnection()) return false; + + CMD_LOCK; + + std::string command = (std::string)"PLAY " + Filename; + if (!Command(command, 220)) { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't replay \"%s\" from %s:%d", + Filename, RemoteIp().c_str(), RemotePort()); + return false; + } + return true; +} + +bool cClientSocket::AbortReplay(void) { + if (!CheckConnection()) return false; + + CMD_LOCK; + + if (m_DataSockets[siReplay] != NULL) { + std::string command = (std::string)"ABRT " + (const char*)itoa(siReplay); + if (!Command(command, 220)) { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't cleanly close data connection"); + return false; + } + + DELETENULL(m_DataSockets[siReplay]); + } + return true; +} + +bool cClientSocket::DeleteRecording(cRemoteRecording *Recording) { + bool res; + cRemoteRecording *rec = NULL; + + if (!CheckConnection()) + return false; + + CMD_LOCK; + + if (!Command("LSTR")) + return false; + + std::string buffer; + while ((res = Expect(250, &buffer))) { + if (rec == NULL) { + rec = new cRemoteRecording(buffer.c_str() + 4); + if (!rec->IsValid() || rec->Index() != Recording->Index()) + DELETENULL(rec); + } + if (buffer[3] == ' ') break; + } + + if (!res && buffer.substr(0, 3) != "550") { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't fetch recordings from %s:%d", + RemoteIp().c_str(), RemotePort()); + if (rec != NULL) delete rec; + return false; + } + + if (rec == NULL || *rec != *Recording) { + ERROR(tr("Recordings not in sync! Try again...")); + return false; + } + + std::string command = (std::string)"DELR " + (const char*)itoa(Recording->Index()); + if (!Command(command, 250)) { + ERROR(tr("Couldn't delete recording! Try again...")); + return false; + } + return true; +} + +bool cClientSocket::SuspendServer(void) { + if (!CheckConnection()) return false; + + CMD_LOCK; + + if (!Command("SUSP", 220)) { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't suspend server"); + return false; + } + return true; +} + +bool cClientSocket::LoadTimers(cRemoteTimers &Timers) { + if (!CheckConnection()) return false; + + CMD_LOCK; + + if (!Command("LSTT")) + return false; + + bool res; + std::string buffer; + while ((res = Expect(250, &buffer))) { + cRemoteTimer *timer = new cRemoteTimer(buffer.c_str() + 4); + Dprintf("timer valid: %d\n", timer->IsValid()); + if (timer->IsValid()) + Timers.Add(timer); + if (buffer[3] == ' ') break; + } + + if (!res && buffer.substr(0, 3) != "550") { + if (errno == 0) + esyslog("ERROR: Streamdev: Couldn't fetch recordings from %s:%d", + RemoteIp().c_str(), RemotePort()); + return false; + } + return res; +} + +bool cClientSocket::SaveTimer(cRemoteTimer *Old, cRemoteTimer &New) { + if (!CheckConnection()) return false; + + CMD_LOCK; + + if (New.Index() == -1) { // New timer + std::string command = (std::string)"NEWT " + (const char*)New.ToText(); + if (!Command(command, 250)) { + ERROR(tr("Couldn't save timer! Try again...")); + return false; + } + } else { // Modified timer + std::string command = (std::string)"LSTT " + (const char*)itoa(New.Index()); + if (!Command(command)) + return false; + + std::string buffer; + if (!Expect(250, &buffer)) { + if (errno == 0) + ERROR(tr("Timers not in sync! Try again...")); + else + ERROR(tr("Server error! Try again...")); + return false; + } + + cRemoteTimer oldstate(buffer.c_str() + 4); + if (oldstate != *Old) { + /*Dprintf("old timer: %d,%d,%d,%d,%d,%d,%s,%d,%s,%d\n", oldstate.m_Index, + oldstate.m_Active,oldstate.m_Day,oldstate.m_Start,oldstate.m_StartTime,oldstate.m_Priority,oldstate.m_File,oldstate.m_FirstDay,(const char*)oldstate.m_Summary,oldstate.m_Channel->Number()); + Dprintf("new timer: %d,%d,%d,%d,%d,%d,%s,%d,%s,%d\n", Old->m_Index, + Old->m_Active,Old->m_Day,Old->m_Start,Old->m_StartTime,Old->m_Priority,Old->m_File,Old->m_FirstDay,(const char*)Old->m_Summary,Old->m_Channel->Number());*/ + ERROR(tr("Timers not in sync! Try again...")); + return false; + } + + + command = (std::string)"MODT " + (const char*)itoa(New.Index()) + " " + + (const char*)New.ToText(); + if (!Command(command, 250)) { + ERROR(tr("Couldn't save timer! Try again...")); + return false; + } + } + return true; +} + +bool cClientSocket::DeleteTimer(cRemoteTimer *Timer) { + if (!CheckConnection()) return false; + + CMD_LOCK; + + std::string command = (std::string)"LSTT " + (const char*)itoa(Timer->Index()); + if (!Command(command)) + return false; + + std::string buffer; + if (!Expect(250, &buffer)) { + if (errno == 0) + ERROR(tr("Timers not in sync! Try again...")); + else + ERROR(tr("Server error! Try again...")); + return false; + } + + cRemoteTimer oldstate(buffer.c_str() + 4); + if (oldstate != *Timer) { + ERROR(tr("Timers not in sync! Try again...")); + return false; + } + + command = (std::string)"DELT " + (const char*)itoa(Timer->Index()); + if (!Command(command, 250)) { + ERROR(tr("Couldn't delete timer! Try again...")); + return false; + } + return true; +} diff --git a/client/socket.h b/client/socket.h new file mode 100644 index 0000000..17478ce --- /dev/null +++ b/client/socket.h @@ -0,0 +1,72 @@ +/* + * $Id: socket.h,v 1.3 2005/02/08 17:22:35 lordjaxom Exp $ + */ + +#ifndef VDR_STREAMDEV_CLIENT_CONNECTION_H +#define VDR_STREAMDEV_CLIENT_CONNECTION_H + +#include + +#include "common.h" + +#include + +#define CMD_LOCK cMutexLock CmdLock((cMutex*)&m_Mutex) + +class cRemoteRecordings; +class cRemoteRecording; +class cRemoteTimers; +class cRemoteTimer; +class cPES2TSRemux; + +class cClientSocket: public cTBSocket { +private: + cTBSocket *m_DataSockets[si_Count]; + cMutex m_Mutex; + char m_Buffer[BUFSIZ + 1]; // various uses + +protected: + /* Send Command, and return true if the command results in Expected. + Returns false on failure, setting errno appropriately if it has been + a system failure. If Expected is zero, returns immediately after + sending the command. */ + bool Command(const std::string &Command, uint Expected = 0, uint TimeoutMs = 1500); + + /* Fetch results from an ongoing Command called with Expected == 0. Returns + true if the response has the code Expected, returning an internal buffer + in the array pointer pointed to by Result. Returns false on failure, + setting errno appropriately if it has been a system failure. */ + bool Expect(uint Expected, std::string *Result = NULL, uint TimeoutMs = 1500); + +public: + cClientSocket(void); + virtual ~cClientSocket(); + + void Reset(void); + + bool CheckConnection(void); + bool ProvidesChannel(const cChannel *Channel, int Priority); + bool CreateDataConnection(eSocketId Id); + bool SetChannelDevice(const cChannel *Channel); + bool SetPid(int Pid, bool On); +#if VDRVERSNUM >= 10300 + bool SetFilter(ushort Pid, uchar Tid, uchar Mask, bool On); +#endif + bool CloseDvr(void); + bool SynchronizeEPG(void); + bool LoadRecordings(cRemoteRecordings &Recordings); + bool StartReplay(const char *Filename); + bool AbortReplay(void); + bool DeleteRecording(cRemoteRecording *Recording); + bool LoadTimers(cRemoteTimers &Timers); + bool SaveTimer(cRemoteTimer *Old, cRemoteTimer &New); + bool DeleteTimer(cRemoteTimer *Timer); + bool SuspendServer(void); + bool Quit(void); + + cTBSocket *DataSocket(eSocketId Id) const; +}; + +extern class cClientSocket ClientSocket; + +#endif // VDR_STREAMDEV_CLIENT_CONNECTION_H diff --git a/common.c b/common.c new file mode 100644 index 0000000..cc5412e --- /dev/null +++ b/common.c @@ -0,0 +1,295 @@ +/* + * $Id: common.c,v 1.4 2005/02/11 16:44:14 lordjaxom Exp $ + */ + +#include +#include + +#include "common.h" +#include "tools/select.h" +#include "i18n.h" + +using namespace std; + +const char *VERSION = "0.3.3-20070320"; + +const char *StreamTypes[st_Count] = { + "TS", + "PES", + "PS", + "ES", + "Extern", + "", // used internally only +}; + +const char *SuspendModes[sm_Count] = { + "Offer suspend mode", + "Always suspended", + "Never suspended" +}; + +const char IpCharacters[] = "0123456789."; + +char *GetNextLine(char *String, uint Length, uint &Offset) { + char *last, *first; + + first = String + Offset; + for (last = first; last < String + Length; ++last) { + if (*last == '\012') { + if (*(last - 1) == '\015') + *(last - 1) = '\0'; + + *last++ = '\0'; + Dprintf("IN: |%s|\n", first); + Offset = last - String; + return first; + } + } + return NULL; +} + +const cChannel *ChannelFromString(const char *String, int *Apid) { + const cChannel *channel = NULL; + char *string = strdup(String); + char *ptr, *end; + int apididx = 0; + + if ((ptr = strrchr(string, '+')) != NULL) { + *(ptr++) = '\0'; + apididx = strtoul(ptr, &end, 10); + Dprintf("found apididx: %d\n", apididx); + } + + if (isnumber(string)) { + int temp = strtol(String, NULL, 10); + if (temp >= 1 && temp <= Channels.MaxNumber()) + channel = Channels.GetByNumber(temp); + } else { + channel = Channels.GetByChannelID(tChannelID::FromString(string)); + + if (channel == NULL) { + int i = 1; + while ((channel = Channels.GetByNumber(i, 1)) != NULL) { + if (String == channel->Name()) + break; + + i = channel->Number() + 1; + } + } + } + + if (channel != NULL && apididx > 0) { + int apid = 0, index = 1; + + for (int i = 0; channel->Apid(i) != 0; ++i, ++index) { + if (index == apididx) { + apid = channel->Apid(i); + break; + } + } + + if (apid == 0) { + for (int i = 0; channel->Dpid(i) != 0; ++i, ++index) { + if (index == apididx) { + apid = channel->Dpid(i); + break; + } + } + } + + if (Apid != NULL) + *Apid = apid; + } + + free(string); + return channel; +} + +void cStreamdevMenuSetupPage::AddCategory(const char *Title) { + char *buffer = NULL; + + asprintf(&buffer, "--- %s -------------------------------------------------" + "---------------", Title ); + + cOsdItem *item = new cOsdItem(buffer); + free(buffer); + +#if VDRVERSNUM < 10307 +# ifdef HAVE_BEAUTYPATCH + item->SetColor(clrScrolLine, clrBackground); +# else + item->SetColor(clrCyan, clrBackground); +# endif +#else + item->SetSelectable(false); +#endif + Add(item); +} + +void cStreamdevMenuSetupPage::AddBoolEdit(const char *Title, int &Value) { + Add(new cMenuEditBoolItem(Title, &Value)); +} + +void cStreamdevMenuSetupPage::AddIpEdit(const char *Title, char *Value) { + Add(new cMenuEditIpItem(Title, Value)); +} + +void cStreamdevMenuSetupPage::AddShortEdit(const char *Title, int &Value) { + AddRangeEdit(Title, Value, 0, 65535); +} + +void cStreamdevMenuSetupPage::AddRangeEdit(const char *Title, int &Value, + int Min, int Max) { + Add(new cMenuEditIntItem(Title, &Value, Min, Max)); +} + +void cStreamdevMenuSetupPage::AddSuspEdit(const char *Title, int &Value) { + static const char *SuspendModesTR[sm_Count] = { NULL }; + + if (SuspendModesTR[0] == NULL) { + for (int i = 0; i < sm_Count; ++i) + SuspendModesTR[i] = tr(SuspendModes[i]); + } + + Add(new cMenuEditStraItem(Title, &Value, sm_Count, SuspendModesTR)); +} +void cStreamdevMenuSetupPage::AddTypeEdit(const char *Title, int &Value) { + Add(new cMenuEditStraItem(Title, &Value, st_CountSetup, StreamTypes)); +} + +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 (!step && (curNum * 10 > 255) || (curNum == 0)) { + in_addr addr; + addr.s_addr = inet_addr(value); + if ((int)addr.s_addr == -1) + addr.s_addr = 0; + addr.s_addr &= ~(0xff << (pos * 8)); + addr.s_addr |= curNum << (pos * 8); + strcpy(value, inet_ntoa(addr)); + if (++pos == 4) + pos = 0; + curNum = (addr.s_addr >> (pos * 8)) & 0xff; + step = true; + } + break; + + default: + return cMenuEditItem::ProcessKey(Key); + } + + Set(); + return osContinue; +} + diff --git a/common.h b/common.h new file mode 100644 index 0000000..e5f143d --- /dev/null +++ b/common.h @@ -0,0 +1,124 @@ +/* + * $Id: common.h,v 1.7 2005/11/06 16:43:58 lordjaxom Exp $ + */ + +#ifndef VDR_STREAMDEV_COMMON_H +#define VDR_STREAMDEV_COMMON_H + +#include +#include + +#include "tools/socket.h" + +#ifdef DEBUG +# include +# define Dprintf(x...) fprintf(stderr, x) +#else +# define Dprintf(x...) +#endif + +#if VDRVERSNUM < 10300 +# define TRANSPONDER(c1, c2) (ISTRANSPONDER(c1->Frequency(), c2->Frequency())) +#else +# define TRANSPONDER(c1, c2) (c1->Transponder() == c2->Transponder()) +#endif + +#if VDRVERSNUM < 10307 +# define INFO(s) Interface->Info(s) +# define STATUS(s) Interface->Status(s) +# define ERROR(s) Interface->Status(s) +# define FLUSH() Interface->Flush() +#else +# define INFO(s) Skins.Message(mtInfo, s) +# define STATUS(s) Skins.Message(mtInfo, s) +# define ERROR(s) Skins.Message(mtStatus, s) +# define FLUSH() Skins.Flush() +#endif + +#if VDRVERSNUM >= 10336 +# define MAXPARSEBUFFER KILOBYTE(16) +#endif + +/* Check if a channel is a radio station. */ +#define ISRADIO(x) ((x)->Vpid()==0||(x)->Vpid()==1||(x)->Vpid()==0x1fff) + +class cChannel; + +char *GetNextLine(char *String, uint Length, uint &Offset); + +const cChannel *ChannelFromString(const char *String, int *Apid = NULL); + +/* Disable logging if BUFCOUNT buffer overflows occur within BUFOVERTIME + milliseconds. Enable logging again if there is no error within BUFOVERTIME + milliseconds. */ +#define BUFOVERTIME 5000 +#define BUFOVERCOUNT 100 + +#define POLLFAIL esyslog("Streamdev: Polling failed: %s", strerror(errno)) +#define WRITEFAIL esyslog("Streamdev: Writing failed: %s", strerror(errno)) +#define READFAIL esyslog("Streamdev: Reading failed: %s", strerror(errno)) +#define CHECKPOLL(x) if ((x)<0){POLLFAIL; return false;} +#define CHECKWRITE(x) if ((x)<0) { WRITEFAIL; return false; } +#define CHECKREAD(x) if ((x)<0) { READFAIL; return false; } + +enum eStreamType { + stTS, + stPES, + stPS, + stES, + stExtern, + stTSPIDS, + +#define st_CountSetup (stExtern+1) +#define st_Count (stTSPIDS+1) +}; + +enum eSuspendMode { + smOffer, + smAlways, + smNever, + sm_Count +}; + +enum eSocketId { + siLive, + siReplay, + si_Count +}; + +extern const char *VERSION; +extern const char *StreamTypes[st_Count]; +extern const char *SuspendModes[sm_Count]; +extern const char IpCharacters[]; + +class cStreamdevMenuSetupPage: public cMenuSetupPage { +protected: + void AddCategory(const char *Title); + virtual void Store(void) = 0; + + void AddBoolEdit(const char *Title, int &Value); + void AddIpEdit(const char *Title, char *Value); + void AddShortEdit(const char *Title, int &Value); + void AddRangeEdit(const char *Title, int &Value, int Min, int Max); + void AddSuspEdit(const char *Title, int &Value); + void AddTypeEdit(const char *Title, int &Value); +}; + +class cMenuEditIpItem: public cMenuEditItem { +private: + char *value; + int curNum; + int pos; + bool step; + +protected: + virtual void Set(void); + +public: + cMenuEditIpItem(const char *Name, char *Value); // Value must be 16 bytes + ~cMenuEditIpItem(); + + virtual eOSState ProcessKey(eKeys Key); +}; + +#endif // VDR_STREAMDEV_COMMON_H diff --git a/i18n.c b/i18n.c new file mode 100644 index 0000000..8864994 --- /dev/null +++ b/i18n.c @@ -0,0 +1,831 @@ +/* + * $Id: i18n.c,v 1.5 2006/08/17 09:26:00 thomas Exp $ + */ + +#include "i18n.h" + +const char *i18n_name = NULL; + +const tI18nPhrase Phrases[] = { + { "VDR Streaming Server", // English + "VDR Streaming Server", // Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "VDR-suoratoistopalvelin", // suomi + "", // Polski + "", // Español + "", // Ellinika / Greek + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "VTP Streaming Client", // English + "VTP Streaming Client", // Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "VTP-suoratoistoasiakas ", // suomi + "", // Polski + "", // Español + "", // Ellinika / Greek + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Start VDR-to-VDR Server",// English + "VDR-zu-VDR Server starten",// Deutsch + "", // Slovenski + "Avvia il Server VDR-toVDR",// Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Käynnistä VDR-palvelin", // suomi + "", // Polski + "", // Español + "", // Ellinika / Greek + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Start HTTP Server", // English + "HTTP Server starten", // Deutsch + "", // Slovenski + "Avvia il Server HTTP", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Käynnistä HTTP-palvelin", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "HTTP Streamtype", // English + "HTTP Streamtyp", // Deutsch + "", // Slovenski + "Tipo di Stream HTTP", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "HTTP-lähetysmuoto", // Suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Start Client", // English + "Client starten", // Deutsch + "", // Slovenski + "Avvia il Client", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Käynnistä VDR-asiakas", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "VDR-to-VDR Server Port",// English + "Port des VDR-zu-VDR Servers",// Deutsch + "", // Slovenski + "Porta del Server VDR-to-VDR",// Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "VDR-palvelimen portti", // Suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "HTTP Server Port", // English + "Port des HTTP Servers",// Deutsch + "", // Slovenski + "Porta del Server HTTP",// Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "HTTP-palvelimen portti", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Maximum Number of Clients",// English + "Maximalanzahl an Clients",// Deutsch + "", // Slovenski + "Numero Massimo di Client",// Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Suurin sallittu asiakkaiden määrä", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Remote IP", // English + "IP der Gegenseite", // Deutsch + "", // Slovenski + "Indirizzo IP del Server",// Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Etäkoneen IP-osoite", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Remote Port", // English + "Port der Gegenseite", // Deutsch + "", // Slovenski + "Porta del Server Remoto",// Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Etäkoneen portti", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Remote Streamtype", // English + "Streamtyp von Gegenseite",// Deutsch + "", // Slovenski + "Tipo di Stream", // Italiano (oppure Flusso ?) + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Etäkoneen lähetysmuoto", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Common Settings", // English + "Allgemeines", // Deutsch + "", // Slovenski + "Settaggi Comuni", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Yleiset asetukset", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "VDR-to-VDR Server", // English + "VDR-zu-VDR Server", // Deutsch + "", // Slovenski + "Server VDR-to-VDR", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "VDR-palvelin", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "HTTP Server", // English + "HTTP Server", // Deutsch + "", // Slovenski + "Server HTTP", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "HTTP-palvelin", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "VDR-to-VDR Client", // English + "VDR-zu-VDR Client", // Deutsch + "", // Slovenski + "Client VDR-to-VDR", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "VDR-asiakas", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Please restart VDR to activate changes",// English + "Bitte starten Sie für die Änderungen VDR neu",// Deutsch + "", // Slovenski + "Riavviare VDR per attivare i cambiamenti",// Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Aktivoi muutokset käynnistämällä VDR uudelleen", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Synchronize EPG", // English + "EPG synchronisieren", // Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Päivitä ohjelmaopas", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Suspend Live TV", // English + "Live-TV pausieren", // Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Pysäytä suora TV-lähetys", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Suspend behaviour", // English + "Pausierverhalten", // Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Pysäytystoiminto", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Offer suspend mode", // English + "Pausieren anbieten", // Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "tyrkytä", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Always suspended", // English + "Immer pausiert", // Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "aina", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Never suspended", // English + "Nie pausiert", // Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "ei koskaan", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Streaming Control", // English + "Streamkontrolle", // Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Suoratoiston hallinta", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Fetching recordings...",// English + "Hole Aufnahmen...", // Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Haetaan tallenteita...", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Remote Recordings", // English + "Entfernte Aufnahmen", // Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Etätallenteet", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Remote Timers", // English + "Entfernte Timer", // Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Etäajastimet", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Suspend Server", // English + "Server pausieren", // Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Pysäytä palvelin", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Server is suspended", // English + "Server ist pausiert", // Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Palvelin on pysäytetty", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Couldn't suspend Server!",// English + "Konnte Server nicht pausieren!",// Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Palvelinta ei onnistuttu pysäyttämään!", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Edit remote timer", // English + "Entfernten Timer editieren",// Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Muokkaa etäajastinta", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Timers not in sync! Try again...",// Englisch + "Timer nicht synchron! Bitte wiederholen...",//Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Ajastimet eivät täsmää! Yritä uudelleen...", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Couldn't save timer! Try again...",// English + "Konnte Timer nicht speichern! Bitte wiederholen...",// Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Ajastimen tallennus epäonnistui! Yritä uudelleen...", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Couldn't delete timer! Try again...",// English + "Konnte Timer nicht löschen! Bitte wiederholen...",// Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Ajastimen poistaminen epäonnistui! Yritä uudelleen...", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Server error! Try again...",// English + "Serverfehler! Bitte wiederholen...",// Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Palvelimessa virhe! Yritä uudelleen...", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "MultiPID Streaming", // English + "Multiple PIDs streamen",// Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Usean PID:in suoratoisto", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Client may suspend", // English + "Client darf pausieren",// Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Asiakas saa pysäyttää palvelimen", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Bind to IP", // English + "",// Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Sido osoitteeseen", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Remote Schedule", // English + "",// Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Etäkoneen ohjelmaopas", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Filter Streaming", // English + "",// Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Suodatetun tiedon suoratoisto", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { "Streaming active", // English + "Streamen im Gange",// Deutsch + "", // Slovenski + "", // Italiano + "", // Nederlands + "", // Português + "", // Français + "", // Norsk + "Suoratoistopalvelin aktiivinen", // suomi + "", // Polski + "", // Español + "", // Ellinika + "", // Svenska + "", // Romaneste + "", // Magyar + "", // Catala +#if VDRVERSNUM >= 10300 + "" // Russian +#endif + }, + { NULL } +}; diff --git a/i18n.h b/i18n.h new file mode 100644 index 0000000..9d90f97 --- /dev/null +++ b/i18n.h @@ -0,0 +1,16 @@ +/* + * $Id: i18n.h,v 1.1.1.1 2004/12/30 22:43:58 lordjaxom Exp $ + */ + +#ifndef VDR_STREAMDEV_I18N_H +#define VDR_STREAMDEV_I18N_H + +#include + +extern const char *i18n_name; +extern const tI18nPhrase Phrases[]; + +#undef tr +#define tr(s) I18nTranslate(s, i18n_name) + +#endif // VDR_STREAMDEV_I18N_H diff --git a/libdvbmpeg/.cvsignore b/libdvbmpeg/.cvsignore new file mode 100644 index 0000000..4671378 --- /dev/null +++ b/libdvbmpeg/.cvsignore @@ -0,0 +1 @@ +.depend diff --git a/libdvbmpeg/DVB.hh b/libdvbmpeg/DVB.hh new file mode 100644 index 0000000..e713bee --- /dev/null +++ b/libdvbmpeg/DVB.hh @@ -0,0 +1,446 @@ +#ifndef _DVB_DEV_HH_ +#define _DVB_DEV_HH_ + +extern "C" { +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define NEWSTRUCT +#include +} + +#include +#include +#include +using namespace std; + +#include +#include + +#ifndef MAXNAM +#define MAXNAM 80 +#endif + + + +#define FRONT_DVBS 1 +#define FRONT_DVBC 2 +#define FRONT_DVBT 3 + +#define VTXDIR "/var/vtx" + +#define DEC(N) dec << setw(N) << setfill('0') +#define HEX(N) hex << setw(N) << setfill('0') + +#define MAXSECSIZE 4096 + +#define NK 10 +enum {LNB=0,DIS,ROTOR,TRANS,CHAN,BOU,SAT,PICS,SWI,NTW}; +static const int nums[]={LNB,DIS,ROTOR,TRANS,CHAN,BOU,SAT,PICS,SWI,NTW}; +static const int maxs[]={ 32, 32, 32, 512,16384,512,100, 50, 10, 100}; + +#define MAX_TRANS_CHAN 1024 + +enum{DVB_ORIG=0, DVB_NOKIA, DVB_XML, DVB_SATCO}; + +typedef struct frontend_stat_s{ + fe_status_t status; + uint16_t snr; + uint16_t strength; + uint32_t ber; + uint32_t u_blocks; +} frontend_stat; + + +extern uint8_t hamtab[256]; +extern uint8_t invtab[256]; + +#define MAX_MAG 8 +typedef struct mag_struct_ { + int valid; + int magn; + uint8_t flags; + uint8_t lang; + int pnum,sub; + uint8_t pagebuf[25*40]; +} magazin_t; + + +class DVB { +public: + int no_open; + int fd_frontend; + int fd_demuxa; + int fd_demuxv; + int fd_demuxpcr; + int fd_demuxtt; + int fdvb; + + int minor; + int adapter; + int max_tpid; + int max_satid; + int max_chanid; + + frontend_stat festat; + + struct dvb_diseqc_master_cmd dcmd; + fe_sec_tone_mode_t tone; + fe_sec_voltage_t voltage; + int burst; + struct dmx_pes_filter_params pesFilterParamsV; + struct dmx_pes_filter_params pesFilterParamsA; + struct dmx_pes_filter_params pesFilterParamsP; + struct dmx_pes_filter_params pesFilterParamsTT; + struct dvb_frontend_parameters front_param; + int front_type; + int dvr_enabled; + OSD osd; + uint32_t transponder_freq; + char transponder_pol; + uint32_t transponder_srate; + + + + fe_status_t status; + uint32_t ber, uncorrected_blocks; + uint16_t snr, signal; + + + struct Lnb *lnbs; + struct DiSEqC *diseqcs; + struct Rotor *rotors; + struct Transponder *tps; + struct Channel *chans; + struct Bouquet *bouqs; + struct Sat *sats; + struct Picture *pics; + struct Switch *swis; + struct Network *ntws; + int num[NK]; + int oldsec; + int tryit; + int oldpol; + + char *vtxdir; + magazin_t magazin[MAX_MAG]; + + DVB(){ + no_open = 0; + max_tpid = 0; + max_satid = 0; + max_chanid = 0; + minor = 0; + + fd_frontend = -1; + fd_demuxa = -1; + fd_demuxpcr = -1; + fd_demuxv = -1; + fd_demuxtt = -1; + fdvb = -1; + vtxdir = NULL; + transponder_freq=0; + transponder_pol=0; + transponder_srate=0; + } + + DVB(int i){ + if (i >= 0) + no_open = 0; + else + no_open = 1; + max_tpid = 0; + max_satid = 0; + max_chanid = 0; + + fd_frontend = -1; + fd_demuxa = -1; + fd_demuxpcr = -1; + fd_demuxv = -1; + fd_demuxtt = -1; + fdvb = -1; + vtxdir = NULL; + transponder_freq=0; + transponder_pol=0; + transponder_srate=0; + + init("","",i); + } + + DVB(char *a, char *b) { + max_tpid = 0; + max_satid = 0; + max_chanid = 0; + + fd_frontend = -1; + fd_demuxa = -1; + fd_demuxpcr = -1; + fd_demuxv = -1; + fd_demuxtt = -1; + + fdvb = -1; + vtxdir = NULL; + init(a,b,0); + } + + ~DVB(); + + void use_osd(int fd = -1){ + char dvn[32]; + if (no_open) return; + if (fd < 0) fd = 0; + sprintf(dvn,OSD_DEV,adapter,fd); + fdvb = open(dvn, O_RDWR); + + if (fdvb >= 0){ + cerr << dvn << " for OSD" << endl; + osd.init(fdvb); + } else perror("osd"); + osd.Open(80, 500, 640, 540, 2, 0, 2); + osd.SetColor(0, 0, 0, 0, 255); + osd.SetColor(1, 240, 240, 240, 255); + osd.Show(); + } + + void set_vtxdir(char *newname){ + if (!newname) return; + if (vtxdir) free(vtxdir); + vtxdir = (char *) malloc(sizeof(char)*(strlen(newname)+1)); + if (vtxdir) + strncpy(vtxdir, newname, strlen(newname)); + } + + void close_osd(){ + osd.Close(fdvb); + close(fdvb); + } + + int DVR_enabled(){ + if (no_open) return -1; + return dvr_enabled; + } + + void enable_DVR(){ + if (no_open) return; + dvr_enabled = 1; + } + + void enable_DVR_other(){ + if (no_open) return; + dvr_enabled = 2; + } + + void disable_DVR(){ + if (no_open) return; + dvr_enabled = 0; + } + + void init(char *a="/dev/video0", char *b="/dev/vbi0",int adapt=0, + int minor = 0); + + + inline void init(char *a, char *b){ + if (no_open) return; + init(a,b,0,0); + } + + int check_frontend(); + + void set_apid(ushort apid); + void set_vpid(ushort vpid); + void set_pcrpid(ushort vpid); + void set_ttpid(ushort ttpid); + int set_apid_fd(ushort apid, int fd); + int set_vpid_fd(ushort vpid, int fd); + int set_ttpid_fd(ushort ttpid, int fd); + int set_pcrpid_fd(ushort pcrpid, int fd); + int set_otherpid_fd(ushort otherpid, int fd); + + + int set_lnb(int dis); + void set_diseqc_nb(int nr); + int set_front(void); + void get_front(void); + + void scan_pf_eit(int chnr, + int (*callback)(uint8_t *data, int l, int pnr, + int c_n, uint8_t *t)); + + void scan_pf_eit(Channel *chan, + int (*callback)(uint8_t *data, int l, int pnr, + int c_n, uint8_t *t)); + void scan_pf_eit(int chnr); + + + int search_in_TP(Transponder &tp, int show=1, int verbose=0); + int search_in_TP(uint16_t tpid, uint16_t satid, int show=1, + int verbose=0); + int scan_TP(uint16_t tpid, uint16_t satid, int timeout=-1, int verbose=0); + + int GetSection(uint8_t *buf, + uint16_t PID, uint8_t TID, uint16_t TIDExt, + uint16_t FilterTIDExt, + uint8_t secnum, uint8_t &msecnum); + int GetSection(uint8_t *buf, + uint16_t PID, uint8_t *filter, uint8_t *mask, + uint8_t secnum, uint8_t &msecnum); + int GetSection(uint8_t *buf, ushort PID, uint8_t sec, + uint8_t secnum, uint8_t &msecnum); + int SetFilter(uint16_t pid, uint8_t *filter, + uint8_t *mask, + uint32_t timeout, uint32_t flags); + uint16_t SetFilter(uint16_t pid, uint16_t section, uint16_t mode); + int CloseFilter(int h); + + + void bar2(int x, int y, int w, int h, int val, int col1, int col2); + + int SetTP(unsigned int, unsigned int); + int scan(void); + int scan_all_tps(void); + int scan_lnb(struct Lnb &); + int scan_cable(Sat &sat); + int scan_sat(struct Sat &); + int scan_tp(struct Transponder &); + + int AddLNB(int id, int t, uint l1, uint l2, uint sl, + int dnr, int dis, int sw); + int AddSat(Sat &sat); + int AddSat(int satid, unsigned int lnbid, char *name, uint fmin, uint fmax); + int AddTP(Transponder &tp); + int AddChannel(Channel &chan); + int parse_descriptor(Channel *chan, uint8_t *data, int length); + int parse_pmt(Channel *chan, uint8_t *data); + int parse_pat(Channel *chan, uint8_t *data); + + int check_pids(Channel *chan); + void check_all_pids(); + void scan_sdt(Channel *chan); + int scan_sdts(int *chs, int n); + + int channel_num(void) { + return num[CHAN]; + }; + + int channel_change(int n) { + return 0; + }; + int SetChannel(uint16_t, uint16_t, uint16_t, uint16_t); + int SetChannel(Channel *chan, char* apref=NULL, uint16_t *apidp=NULL, + uint16_t *vpidp=NULL) ; + int SetChannel(int chnr, char *apref=NULL, uint16_t *apidp=NULL, + uint16_t *vpidp=NULL); + int GetChannel(int chnr, struct channel *); + int NumChannel(void) { + return num[CHAN]; + } + int tune_it(struct dvb_frontend_parameters *qpsk); + void find_satid(Channel &chan); + int check_input_format(istream &ins); + void read_original(istream &ins); + int get_all_progs(uint16_t *progbuf, uint16_t *pnrbuf, int length); + uint16_t find_pnr(uint16_t vpid, uint16_t apid); + int get_pids(uint16_t prog_pid, uint16_t *vpid, uint16_t *apids, + uint16_t *ttpid, uint8_t *apids_name=NULL); + void AddECM(Channel *chan, uint8_t *data, int length); + int check_ecm(Channel *chan); + void add_vtx_line(magazin_t *mag, int line, uint8_t *data, int pnr); + + friend ostream &operator<<(ostream &stream, DVB &x); + friend istream &operator>>(istream &stream, DVB &x); + +}; + +#define NOKIA_MAX_SAT 4 +class nokiaconv{ +public: + DVB *dvb; + struct lnb_sat_l{ + int n; + int diseqc[NOKIA_MAX_SAT]; + char sat_names[NOKIA_MAX_SAT][MAXNAM+1]; + int satid[NOKIA_MAX_SAT]; + } lnb_sat; + + nokiaconv(DVB *d){ + dvb = d; + } + + friend istream &operator>>(istream &stream, nokiaconv &x); +}; + +#define XML_MAX_SAT 4 +class xmlconv{ +public: + DVB *dvb; + struct lnb_sat_l{ + int n; + int diseqc[XML_MAX_SAT]; + char sat_names[XML_MAX_SAT][MAXNAM+1]; + int satid[XML_MAX_SAT]; + } lnb_sat; + + xmlconv(DVB *d){ + dvb = d; + } + int read_stream(istream &ins, int nchan); + int read_desc(istream &ins, int nchan); + int read_serv(istream &ins, int ctp, int csat); + int read_trans(istream &ins, int satid); + int read_sat(istream &ins, int satid = -1); + int skip_tag(istream &ins, char *tag); + int read_iso639(istream &ins, int nchan, int apids); + + friend istream &operator>>(istream &stream, xmlconv &x); +}; + + + +#define SATCO_MAX_SAT 10 +class satcoconv{ +public: + DVB *dvb; + int nlnb; + + satcoconv(DVB *d){ + dvb = d; + } + + friend istream &operator>>(istream &stream, satcoconv &x); +}; + +void hdump(uint8_t *buf, int n); +int get_dvbrc(char *path, DVB &dv, int dev, int len); +int set_dvbrc(char *path, DVB &dv, int dev, int len); +void dvb2txt(char *out, char *in, int len); +int set_sfront(int fdf, uint32_t freq, uint32_t pol, uint32_t sr , int snum, fe_code_rate_t fec); +void set_pes_filt(int fd,uint16_t pes_pid); +void set_diseqc(int fdf, int snum, fe_sec_voltage_t v, fe_sec_tone_mode_t t); +int tune(int fdf, uint32_t freq, uint32_t sr, fe_code_rate_t fec); +int set_sfront(int fdf, uint32_t freq, uint32_t pol, uint32_t sr , int snum, + fe_code_rate_t fec); + + +struct in_addr getaddress (const char *name); +int tcp_client_connect(const char *hostname, int sckt); +int udp_client_connect(const char *filename); +void client_send_msg(int fd, uint8_t *msg, int size); +int chck_frontend (int fefd, frontend_stat *festat); + +uint8_t deham(uint8_t x, uint8_t y); + +#endif diff --git a/libdvbmpeg/Makefile b/libdvbmpeg/Makefile new file mode 100644 index 0000000..60eba44 --- /dev/null +++ b/libdvbmpeg/Makefile @@ -0,0 +1,33 @@ +INCS = -I. +CFLAGS = -g -Wall -O2 -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -fPIC +MFLAG = -M +OBJS = ctools.o ringbuffy.o remux.o transform.o cpptools.o +SRC = $(wildcard *.c) +CPPSRC = $(wildcard *.cpp) +CSRC = $(wildcard *.cc) + +DESTDIR = /usr/local + +.PHONY: depend clean install uninstall + +clean: + - rm -f *.o *~ *.a .depend + +libdvbmpegtools.a: $(OBJS) + ar -rcs libdvbmpegtools.a $(OBJS) + +%.o: %.cc + $(CXX) -c $(CFLAGS) $(INCS) $(DEFINES) $< + +%.o: %.cpp + $(CXX) -c $(CFLAGS) $(INCS) $(DEFINES) $< + +%.o: %.c + $(CC) -c $(CFLAGS) $(INCS) $(DEFINES) $< + +.depend: + $(CXX) $(DEFINES) $(MFLAG) $(SRC) $(CSRC) $(CPPSRC) $(INCS)> .depend + + + +-include .depend diff --git a/libdvbmpeg/OSD.h b/libdvbmpeg/OSD.h new file mode 100644 index 0000000..385ac78 --- /dev/null +++ b/libdvbmpeg/OSD.h @@ -0,0 +1,30 @@ +#ifndef _OSD_H_ +#define _OSD_H_ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ +int OSDClose(int dev); +int OSDOpen(int dev, int x0, int y0, int x1, int y1, int BitPerPixel, int mix); +int OSDShow(int dev); +int OSDHide(int dev); +int OSDClear(int dev); +int OSDFill(int dev, int color); +int OSDSetColor(int dev, int color, int r, int g, int b, int op); +int OSDText(int dev, int x, int y, int size, int color, const char *text); +int OSDSetPalette(int dev, int first, int last, unsigned char *data); +int OSDSetTrans(int dev, int trans); +int OSDSetPixel(int dev, int x, int y, unsigned int color); +int OSDGetPixel(int dev, int x, int y); +int OSDSetRow(int dev, int x, int y, int x1, unsigned char *data); +int OSDSetBlock(int dev, int x, int y, int x1, int y1, int inc, unsigned char *data); +int OSDFillRow(int dev, int x, int y, int x1, int color); +int OSDFillBlock(int dev, int x, int y, int x1, int y1, int color); +int OSDLine(int dev, int x, int y, int x1, int y1, int color); +int OSDQuery(int dev); +int OSDSetWindow(int dev, int win); +int OSDMoveWindow(int dev, int x, int y); +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif diff --git a/libdvbmpeg/channel.h b/libdvbmpeg/channel.h new file mode 100644 index 0000000..c4f62b4 --- /dev/null +++ b/libdvbmpeg/channel.h @@ -0,0 +1,58 @@ +#ifndef _CHANNEL_H +#define _CHANNEL_H + +#include + +struct channel { + int id; + char name[81]; + int type; + ushort pnr; + ushort vpid; + ushort apids[8]; + ushort apidnum; + ushort ac3pid; + ushort pcrpid; + + uint freq; + int pol; + int qam; + uint srate; + int fec; +}; + +#ifdef NEWSTRUCT + +#include +#include +#include +#include + +#define DVR_DEV "/dev/dvb/adapter%d/dvr%d" +#define VIDEO_DEV "/dev/dvb/adapter%d/video%d" +#define AUDIO_DEV "/dev/dvb/adapter%d/audio%d" +#define DEMUX_DEV "/dev/dvb/adapter%d/demux%d" +#define FRONT_DEV "/dev/dvb/adapter%d/frontend%d" +#define OSD_DEV "/dev/dvb/adapter%d/osd%d" +#define CA_DEV "/dev/dvb/adapter%d/ca%d" + +#else + +#include +#include +#include +#include +#include + +#define DVR_DEV "/dev/ost/dvr%d" +#define VIDEO_DEV "/dev/ost/video%d" +#define AUDIO_DEV "/dev/ost/audio%d" +#define DEMUX_DEV "/dev/ost/demux%d" +#define FRONT_DEV "/dev/ost/frontend%d" +#define OSD_DEV "/dev/ost/osd%d" +#define CA_DEV "/dev/ost/ca%d" + +#endif + + +#endif diff --git a/libdvbmpeg/ci.hh b/libdvbmpeg/ci.hh new file mode 100644 index 0000000..77e7684 --- /dev/null +++ b/libdvbmpeg/ci.hh @@ -0,0 +1,167 @@ +/* + * ci.hh: Common Interface + * + * Copyright (C) 2000 Klaus Schmidinger + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + * The author can be reached at kls@cadsoft.de + * + * The project's page is at http://www.cadsoft.de/people/kls/vdr + * + */ + +#ifndef __CI_H +#define __CI_H + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define MAXCASYSTEMIDS 16 + +class cMutex { + friend class cCondVar; +private: + pthread_mutex_t mutex; + pid_t lockingPid; + int locked; +public: + cMutex(void); + ~cMutex(); + void Lock(void); + void Unlock(void); + }; + +class cMutexLock { +private: + cMutex *mutex; + bool locked; +public: + cMutexLock(cMutex *Mutex = NULL); + ~cMutexLock(); + bool Lock(cMutex *Mutex); + }; + + +class cCiMMI; + +class cCiMenu { + friend class cCiMMI; +private: + enum { MAX_CIMENU_ENTRIES = 64 }; ///< XXX is there a specified maximum? + cCiMMI *mmi; + bool selectable; + char *titleText; + char *subTitleText; + char *bottomText; + char *entries[MAX_CIMENU_ENTRIES]; + int numEntries; + bool AddEntry(char *s); + cCiMenu(cCiMMI *MMI, bool Selectable); +public: + ~cCiMenu(); + const char *TitleText(void) { return titleText; } + const char *SubTitleText(void) { return subTitleText; } + const char *BottomText(void) { return bottomText; } + const char *Entry(int n) { return n < numEntries ? entries[n] : NULL; } + int NumEntries(void) { return numEntries; } + bool Selectable(void) { return selectable; } + bool Select(int Index); + bool Cancel(void); + }; + +class cCiEnquiry { + friend class cCiMMI; +private: + cCiMMI *mmi; + char *text; + bool blind; + int expectedLength; + cCiEnquiry(cCiMMI *MMI); +public: + ~cCiEnquiry(); + const char *Text(void) { return text; } + bool Blind(void) { return blind; } + int ExpectedLength(void) { return expectedLength; } + bool Reply(const char *s); + bool Cancel(void); + }; + +class cCiCaPmt { + friend class cCiConditionalAccessSupport; +private: + int length; + int esInfoLengthPos; + uint8_t capmt[2048]; ///< XXX is there a specified maximum? +public: + cCiCaPmt(int ProgramNumber); + void AddPid(int Pid); + void AddCaDescriptor(int Length, uint8_t *Data); + }; + +#define MAX_CI_SESSION 16 //XXX + +class cCiSession; +class cCiTransportLayer; +class cCiTransportConnection; + +class cCiHandler { +private: + cMutex mutex; + int numSlots; + bool newCaSupport; + bool hasUserIO; + cCiSession *sessions[MAX_CI_SESSION]; + cCiTransportLayer *tpl; + cCiTransportConnection *tc; + int ResourceIdToInt(const uint8_t *Data); + bool Send(uint8_t Tag, int SessionId, int ResourceId = 0, int Status = -1); + cCiSession *GetSessionBySessionId(int SessionId); + cCiSession *GetSessionByResourceId(int ResourceId, int Slot); + cCiSession *CreateSession(int ResourceId); + bool OpenSession(int Length, const uint8_t *Data); + bool CloseSession(int SessionId); + int CloseAllSessions(int Slot); + cCiHandler(int Fd, int NumSlots); +public: + ~cCiHandler(); + static cCiHandler *CreateCiHandler(const char *FileName); + int NumSlots(void) { return numSlots; } + bool Process(void); + bool HasUserIO(void) { return hasUserIO; } + bool EnterMenu(int Slot); + cCiMenu *GetMenu(void); + cCiEnquiry *GetEnquiry(void); + bool SetCaPmt(cCiCaPmt &CaPmt); + const unsigned short *GetCaSystemIds(int Slot); + bool SetCaPmt(cCiCaPmt &CaPmt, int Slot); + bool Reset(int Slot); + }; + +int tcp_listen(struct sockaddr_in *name,int sckt,unsigned long address=INADDR_ANY); +int accept_tcp(int ip_sock,struct sockaddr_in *ip_name); +int udp_listen(struct sockaddr_un *name,char const * const filename); +int accept_udp(int ip_sock,struct sockaddr_un *ip_name); + +#endif //__CI_H diff --git a/libdvbmpeg/cpptools.cc b/libdvbmpeg/cpptools.cc new file mode 100644 index 0000000..91808cc --- /dev/null +++ b/libdvbmpeg/cpptools.cc @@ -0,0 +1,946 @@ +/* + * dvb-mpegtools for the Siemens Fujitsu DVB PCI card + * + * Copyright (C) 2000, 2001 Marcus Metzler + * for convergence integrated media GmbH + * Copyright (C) 2002 Marcus Metzler + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + + * The author can be reached at mocm@metzlerbros.de + */ + +#include "cpptools.hh" + +#define HEX(N) "0x" << hex << setw(2) << setfill('0') \ +<< int(N) << " " << dec +#define HHEX(N,M) "0x" << hex << setw(M) << setfill('0') \ +<< int(N) << " " << dec +#define LHEX(N,M) "0x" << hex << setw(M) << setfill('0') \ +<< long(N) << " " << dec + +#define MAX_SEARCH 1024 * 1024 + +ostream & operator << (ostream & stream, PES_Packet & x){ + + if (x.info){ + cerr << "PES Packet: " ; + switch ( x.p.stream_id ) { + + case PROG_STREAM_MAP: + cerr << "Program Stream Map"; + break; + case PRIVATE_STREAM2: + cerr << "Private Stream 2"; + break; + case PROG_STREAM_DIR: + cerr << "Program Stream Directory"; + break; + case ECM_STREAM : + cerr << "ECM Stream"; + break; + case EMM_STREAM : + cerr << "EMM Stream"; + break; + case PADDING_STREAM : + cerr << "Padding Stream"; + break; + case DSM_CC_STREAM : + cerr << "DSM Stream"; + break; + case ISO13522_STREAM: + cerr << "ISO13522 Stream"; + break; + case PRIVATE_STREAM1: + cerr << "Private Stream 1"; + break; + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + cerr << "Audio Stream " << HEX(x.p.stream_id); + break; + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + cerr << "Video Stream " << HEX(x.p.stream_id); + break; + + } + cerr << " MPEG" << x.p.mpeg << endl; + if ( x.p.mpeg == 2 ){ + cerr << " FLAGS: "; + + if (x.p.flags1 & SCRAMBLE_FLAGS){ + cerr << " SCRAMBLE("; + cerr << ((x.p.flags1 & SCRAMBLE_FLAGS)>>4); + cerr << ")"; + } + if (x.p.flags1 & PRIORITY_FLAG) + cerr << " PRIORITY"; + if (x.p.flags1 & DATA_ALIGN_FLAG) + cerr << " DATA_ALIGN"; + if (x.p.flags1 & COPYRIGHT_FLAG) + cerr << " COPYRIGHT"; + if (x.p.flags1 & ORIGINAL_FLAG) + cerr << " ORIGINAL"; + + if (x.p.flags2 & PTS_DTS_FLAGS){ + cerr << " PTS_DTS("; + cerr << ((x.p.flags2 & PTS_DTS_FLAGS)>>6); + cerr << ")"; + } + if (x.p.flags2 & ESCR_FLAG) + cerr << " ESCR"; + if (x.p.flags2 & ES_RATE_FLAG) + cerr << " ES_RATE"; + if (x.p.flags2 & DSM_TRICK_FLAG) + cerr << " DSM_TRICK"; + if (x.p.flags2 & ADD_CPY_FLAG) + cerr << " ADD_CPY"; + if (x.p.flags2 & PES_CRC_FLAG) + cerr << " CRC"; + if (x.p.flags2 & PES_EXT_FLAG) + cerr << " EXT"; + + cerr << endl; + + if ((x.p.flags2 & PTS_DTS_FLAGS) == PTS_ONLY) + cerr << " PTS: " + << LHEX(ntohl(x.WPTS()),8) + << "(h" << int(x.high_pts()) << ")" + << endl; + else if ((x.p.flags2 & PTS_DTS_FLAGS) == PTS_DTS){ + cerr << " PTS: " + << LHEX(ntohl(x.WPTS()),8) + << "(h" << int(x.high_pts()) << ")"; + cerr << " DTS: " + << LHEX(ntohl(x.WDTS()),8) + << "(h" << int(x.high_dts()) << ")" + << endl; + } +/* + if (x.p.flags2 & ESCR_FLAG) + + + if (x.p.flags2 & ES_RATE_FLAG) + + + if (x.p.flags2 & DSM_TRICK_FLAG) + + + if (x.p.flags2 & ADD_CPY_FLAG) + + + if (x.p.flags2 & PES_CRC_FLAG) + + + if (x.p.flags2 & PES_EXT_FLAG){ + + if (x.p.priv_flags & PRIVATE_DATA) + stream.write(x.p.pes_priv_data,16); + + if (x.p.priv_flags & HEADER_FIELD){ + stream.write(&x.p.pack_field_length,1); + x.p.pack_header = new + uint8_t[x.p.pack_field_length]; + stream.write(x.p.pack_header, + x.p.pack_field_length); + } + + if ( x.p.priv_flags & PACK_SEQ_CTR){ + stream.write(&x.p.pck_sqnc_cntr,1); + stream.write(&x.p.org_stuff_length,1); + } + + if ( x.p.priv_flags & P_STD_BUFFER) + stream.write(x.p.p_std,2); + + if ( x.p.priv_flags & PES_EXT_FLAG2){ + stream.write(&x.p.pes_ext_lngth,1); + x.p.pes_ext = new + uint8_t[x.p.pes_ext_lngth]; + stream.write(x.p.pes_ext, + x.p.pes_ext_lngth); + } + } + } else { + if ((x.p.flags2 & PTS_DTS_FLAGS) == PTS_ONLY) + stream.write(x.p.pts,5); + else if ((x.p.flags2 & PTS_DTS_FLAGS) == + PTS_DTS){ + stream.write(x.p.pts,5); + stream.write(x.p.dts,5); + } +*/ + } + cerr << endl << endl; + return stream; + } + + int l = x.p.length+x.p.pes_hlength+9; + uint8_t buf[l]; + int length = cwrite_pes(buf,&(x.p),l); + stream.write((char *)buf,length); + + return stream; +} + +static unsigned int find_length(istream & stream){ + streampos p = 0; + streampos start = 0; + streampos q = 0; + int found = 0; + uint8_t sync4[4]; + + start = stream.tellg(); + start -=2; + stream.seekg(start); + while ( !stream.eof() && !found ){ + p = stream.tellg(); + stream.read((char *)&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 = stream.tellg(); + break; + } + } + } + q = stream.tellg(); + stream.seekg(streampos(2)+start); + if (found) return (unsigned int)(q-start)-4-2; + else return (unsigned int)(q-start)-2; + +} + +istream & operator >> (istream & stream, PES_Packet & x){ + + uint8_t sync4[4]; + int found=0; + int done=0; + streampos p = 0; + + while (!stream.eof() && !found) { + p = stream.tellg(); + stream.read((char *)&sync4,4); + if (sync4[0] == 0x00 && sync4[1] == 0x00 && sync4[2] == 0x01) { + x.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 : + found = 1; + stream.read((char *)x.p.llength,2); + x.setlength(); + if (!x.p.length){ + x.p.length = find_length(stream); + x.Nlength(); + } + stream.read((char *)x.p.pes_pckt_data,x.p.length); + done = 1; + break; + case PADDING_STREAM : + found = 1; + stream.read((char *)x.p.llength,2); + x.setlength(); + if (!x.p.length){ + x.p.length = find_length(stream); + x.Nlength(); + } + x.p.padding = x.p.length; + stream.read((char *)x.p.pes_pckt_data,x.p.length); + done = 1; + 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: + stream.read((char *)x.p.llength,2); + x.setlength(); + if (!x.p.length){ + x.p.length = find_length(stream); + x.Nlength(); + } + found = 1; + break; + + default: + stream.seekg(p+streampos(1)); + break; + } + } else stream.seekg(p+streampos(1)); + } + + if ( found && !done) { + p = stream.tellg(); + stream.read((char *)&x.p.flags1,1); + if ( (x.p.flags1 & 0xC0) == 0x80 ) + x.p.mpeg = 2; + else + x.p.mpeg = 1; + if ( x.p.mpeg == 2 ){ + stream.read((char *)&x.p.flags2,1); + stream.read((char *)&x.p.pes_hlength,1); + + if ((int)x.p.length > x.p.pes_hlength+3) + x.p.length -=x.p.pes_hlength+3; + else + return stream; + + uint8_t count = x.p.pes_hlength; + + if ((x.p.flags2 & PTS_DTS_FLAGS) == PTS_ONLY){ + stream.read((char *)x.p.pts,5); + count -=5; + } else + if ((x.p.flags2 & PTS_DTS_FLAGS) == PTS_DTS){ + stream.read((char *)x.p.pts,5); + stream.read((char *)x.p.dts,5); + count -= 10; + } + + if (x.p.flags2 & ESCR_FLAG){ + stream.read((char *)x.p.escr,6); + count -= 6; + } + + if (x.p.flags2 & ES_RATE_FLAG){ + stream.read((char *)x.p.es_rate,3); + count -= 6; + } + + if (x.p.flags2 & DSM_TRICK_FLAG){ + stream.read((char *)&x.p.trick,1); + count -= 1; + } + + if (x.p.flags2 & ADD_CPY_FLAG){ + stream.read((char *)&x.p.add_cpy,1); + count -= 1; + } + + if (x.p.flags2 & PES_CRC_FLAG){ + stream.read((char *)x.p.prev_pes_crc,2); + count -= 2; + } + + if (x.p.flags2 & PES_EXT_FLAG){ + stream.read((char *)&x.p.priv_flags,1); + count -= 1; + + if (x.p.priv_flags & PRIVATE_DATA){ + stream.read((char *)x.p.pes_priv_data,16); + count -= 16; + } + + if (x.p.priv_flags & HEADER_FIELD){ + stream.read((char *)&x.p.pack_field_length,1); + x.p.pack_header = new + uint8_t[x.p.pack_field_length]; + stream.read((char *)x.p.pack_header, + x.p.pack_field_length); + count -= 1+x.p.pack_field_length; + } + + if ( x.p.priv_flags & PACK_SEQ_CTR){ + stream.read((char *)&x.p.pck_sqnc_cntr,1); + stream.read((char *)&x.p.org_stuff_length,1); + count -= 2; + } + + if ( x.p.priv_flags & P_STD_BUFFER){ + stream.read((char *)x.p.p_std,2); + count -= 2; + } + + if ( x.p.priv_flags & PES_EXT_FLAG2){ + stream.read((char *)&x.p.pes_ext_lngth,1); + x.p.pes_ext = new + uint8_t[x.p.pes_ext_lngth]; + stream.read((char *)x.p.pes_ext, + x.p.pes_ext_lngth); + count -= 1+x.p.pes_ext_lngth; + } + } + x.p.stuffing = count; + uint8_t dummy; + for(int i = 0; i< count ;i++) + stream.read((char *)&dummy,1); + + } else { + uint8_t check; + x.p.mpeg1_pad = 1; + check = x.p.flags1; + while (check == 0xFF){ + stream.read((char *)&check,1); + x.p.mpeg1_pad++; + } + + if ( (check & 0xC0) == 0x40){ + stream.read((char *)&check,1); + x.p.mpeg1_pad++; + stream.read((char *)&check,1); + x.p.mpeg1_pad++; + } + x.p.flags2 = 0; + x.p.length -= x.p.mpeg1_pad; + + stream.seekg(p); + if ( (check & 0x30)){ + x.p.length ++; + x.p.mpeg1_pad --; + + if (check == x.p.flags1){ + x.p.pes_hlength = 0; + } else { + x.p.mpeg1_headr = + new uint8_t[x.p.mpeg1_pad]; + x.p.pes_hlength = x.p.mpeg1_pad; + stream.read((char *)x.p.mpeg1_headr, + x.p.mpeg1_pad); + } + + x.p.flags2 = (check & 0xF0) << 2; + if ((x.p.flags2 & PTS_DTS_FLAGS) == PTS_ONLY){ + stream.read((char *)x.p.pts,5); + x.p.length -= 5; + x.p.pes_hlength += 5; + } + else if ((x.p.flags2 & PTS_DTS_FLAGS) == + PTS_DTS){ + stream.read((char *)x.p.pts,5); + stream.read((char *)x.p.dts,5); + x.p.length -= 10; + x.p.pes_hlength += 10; + } + } else { + x.p.mpeg1_headr = new uint8_t[x.p.mpeg1_pad]; + x.p.pes_hlength = x.p.mpeg1_pad; + stream.read((char *)x.p.mpeg1_headr,x.p.mpeg1_pad); + } + } + stream.read((char *)x.p.pes_pckt_data,x.p.length); + } + return stream; +} + +ostream & operator << (ostream & stream, TS_Packet & x){ + + uint8_t buf[TS_SIZE]; + int length = cwrite_ts(buf,&(x.p),TS_SIZE); + stream.write((char *)buf,length); + + return stream; +} + +istream & operator >> (istream & stream, TS_Packet & x){ + uint8_t sync; + int found=0; + streampos p,q; + + sync=0; + while (!stream.eof() && !found) { + stream.read((char *)&sync,1); + if (sync == 0x47) + found = 1; + } + stream.read((char *)x.p.pid,2); + stream.read((char *)&x.p.flags,1); + x.p.count = x.p.flags & COUNT_MASK; + + if (!(x.p.flags & ADAPT_FIELD) && (x.p.flags & PAYLOAD)){ + //no adapt. field only payload + stream.read((char *)x.p.data,184); + x.p.rest = 184; + return stream; + } + + if ( x.p.flags & ADAPT_FIELD ) { + // adaption field + stream.read((char *)&x.p.adapt_length,1); + if (x.p.adapt_length){ + p = stream.tellg(); + stream.read((char *)&x.p.adapt_flags,1); + + if ( x.p.adapt_flags & PCR_FLAG ) + stream.read((char *) x.p.pcr,6); + + if ( x.p.adapt_flags & OPCR_FLAG ) + stream.read((char *) x.p.opcr,6); + + if ( x.p.adapt_flags & SPLICE_FLAG ) + stream.read((char *) &x.p.splice_count,1); + + if( x.p.adapt_flags & TRANS_PRIV){ + stream.read((char *)&x.p.priv_dat_len,1); + x.p.priv_dat = new uint8_t[x.p.priv_dat_len]; + stream.read((char *)x.p.priv_dat,x.p.priv_dat_len); + } + + if( x.p.adapt_flags & ADAP_EXT_FLAG){ + stream.read((char *)&x.p.adapt_ext_len,1); + stream.read((char *)&x.p.adapt_eflags,1); + if( x.p.adapt_eflags & LTW_FLAG) + stream.read((char *)x.p.ltw,2); + + if( x.p.adapt_eflags & PIECE_RATE) + stream.read((char *)x.p.piece_rate,3); + + if( x.p.adapt_eflags & SEAM_SPLICE) + stream.read((char *)x.p.dts,5); + } + q = stream.tellg(); + x.p.stuffing = x.p.adapt_length -(q-p); + x.p.rest = 183-x.p.adapt_length; + stream.seekg(q+streampos(x.p.stuffing)); + if (x.p.flags & PAYLOAD) // payload + stream.read((char *)x.p.data,x.p.rest); + else + stream.seekg(q+streampos(x.p.rest)); + } else { + x.p.rest = 182; + stream.read((char *)x.p.data, 183); + return stream; + } + + } + return stream; +} + + +ostream & operator << (ostream & stream, PS_Packet & x){ + + uint8_t buf[PS_MAX]; + int length = cwrite_ps(buf,&(x.p),PS_MAX); + stream.write((char *)buf,length); + + return stream; +} + +istream & operator >> (istream & stream, PS_Packet & x){ + uint8_t headr[4]; + int found=0; + streampos p = 0; + streampos q = 0; + int count = 0; + + p = stream.tellg(); + while (!stream.eof() && !found && count < MAX_SEARCH) { + stream.read((char *)&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 stream.seekg(p+streampos(1)); + count++; + } + + if (found){ + stream.read((char *)x.p.scr,6); + if (x.p.scr[0] & 0x40) + x.p.mpeg = 2; + else + x.p.mpeg = 1; + + if (x.p.mpeg == 2){ + stream.read((char *)x.p.mux_rate,3); + stream.read((char *)&x.p.stuff_length,1); + p = stream.tellg(); + stream.seekg(p+streampos(x.p.stuff_length & 3)); + } else { + x.p.mux_rate[0] = x.p.scr[5]; //mpeg1 scr is only 5 bytes + stream.read((char *)x.p.mux_rate+1,2); + } + + p=stream.tellg(); + stream.read((char *)headr,4); + if (headr[0] == 0x00 && headr[1] == 0x00 && + headr[2] == 0x01 && headr[3] == 0xBB ) { + stream.read((char *)x.p.sheader_llength,2); + x.setlength(); + if (x.p.mpeg == 2){ + stream.read((char *)x.p.rate_bound,3); + stream.read((char *)&x.p.audio_bound,1); + stream.read((char *)&x.p.video_bound,1); + stream.read((char *)&x.p.reserved,1); + } + stream.read((char *)x.p.data,x.p.sheader_length); + } else { + stream.seekg(p); + x.p.sheader_length = 0; + } + + int i = 0; + int done = 0; + q = stream.tellg(); + PES_Packet pes; + do { + p=stream.tellg(); + stream.read((char *)headr,4); + stream.seekg(p); + if ( headr[0] == 0x00 && headr[1] == 0x00 + && headr[2] == 0x01 && headr[3] != 0xBA){ + pes.init(); + stream >> pes; + i++; + } else done = 1; + } while (!stream.eof() && !done); + x.p.npes = i; + stream.seekg(q); + } + return stream; +} + +void extract_audio_from_PES(istream &in, ostream &out){ + PES_Packet pes; + + while(!in.eof()){ + pes.init(); + in >> pes ; + if (pes.Stream_ID() == 0xC0) + out << pes; + } +} + +void extract_video_from_PES(istream &in, ostream &out){ + PES_Packet pes; + + while(!in.eof()){ + pes.init(); + in >> pes ; + if (pes.Stream_ID() == 0xE0) + out << pes; + } +} + +void extract_es_audio_from_PES(istream &in, ostream &out){ + PES_Packet pes; + + while(!in.eof()){ + pes.init(); + in >> pes ; + if (pes.Stream_ID() == 0xC0) + out.write((char *)pes.Data(),pes.Length()); + } +} + +void extract_es_video_from_PES(istream &in, ostream &out){ + PES_Packet pes; + + while(!in.eof()){ + pes.init(); + in >> pes ; + if (pes.Stream_ID() == 0xE0) + out.write((char *)pes.Data(),pes.Length()); + } +} + + + +#define MAX_PID 20 +int TS_PIDS(istream &in, ostream &out){ + int pid[MAX_PID]; + TS_Packet ts; + int npid=0; + + for (int i=0 ; i> ts; + int j; + int found = 0; + for (j=0;j> 4 | headr[0] << 4; + vsize = (headr[1] &0x0F) << 8 | headr[2]; + cerr << "SIZE: " << hsize << "x" << vsize << endl; + + switch(((headr[3]&0xF0) >>4)){ + case 1: + cerr << "ASPECT: 1:1" << endl; + break; + case 2: + cerr << "ASPECT: 4:3" << endl; + break; + case 3: + cerr << "ASPECT: 16:9" << endl; + break; + case 4: + cerr << "ASPECT: 2.21:1" << endl; + break; + } + + switch (int(headr[3]&0x0F)){ + case 1: + cerr << "FRAMERATE: 23.976" << endl; + form = pDUNNO; + break; + case 2: + cerr << "FRAMERATE: 24" << endl; + form = pDUNNO; + break; + case 3: + cerr << "FRAMERATE: 25" << endl; + form = pPAL; + break; + case 4: + cerr << "FRAMERATE: 29.97" << endl; + form = pNTSC; + break; + case 5: + cerr << "FRAMERATE: 30" << endl; + form = pNTSC; + break; + case 6: + cerr << "FRAMERATE: 50" << endl; + form = pPAL; + break; + case 7: + cerr << "FRAMERATE: 59.94" << endl; + form = pNTSC; + break; + case 8: + cerr << "FRAMERATE: 60" << endl; + form = pNTSC; + break; + } + + int mpeg = 0; + found = 0; + while (!stream.eof() && !found) { + p = stream.tellg(); + stream.read((char *)headr,4); + if (headr[0] == 0x00 && headr[1] == 0x00 && headr[2] == 0x01) + if ( headr[3] == 0xB5 ){ + char *profile[] = {"reserved", "High", "Spatially Scalable", + "SNR Scalable", "Main", "Simple", "undef", + "undef"}; + char *level[] = {"res", "res", "res", "res", + "High","res", "High 1440", "res", + "Main","res", "Low", "res", + "res", "res", "res", "res"}; + char *chroma[] = {"res", "4:2:0", "4:2:2", "4:4:4:"}; + mpeg = 2; + stream.read((char *)headr,4); + cerr << "PROFILE: " << profile[headr[0] & 0x7] << endl; + cerr << "LEVEL: " << level[headr[1]>>4 & 0xF] << endl; + cerr << "CHROMA: " << chroma[headr[1]>>1 & 0x3] << endl; + found = 1; + } else { + mpeg = 1; + found = 1; + } + if (! found) stream.seekg(p+streampos(1)); + } + + stream.seekg(q); + return (form | mpeg << 4); +} + + + +int stream_type(istream &fin){ + uint8_t headr[4]; + streampos p=fin.tellg(); + + TS_Packet ts; + fin >> ts; + fin.read((char *)headr,1); + fin.seekg(p); + if(fin && headr[0] == 0x47){ + return TS_STREAM; + } + + PS_Packet ps; + fin >> ps; + PES_Packet pes; + for(int j=0;j < ps.NPES();j++){ + fin >> pes; + } + fin.read((char *)headr,4); + fin.seekg(p); + if (fin && headr[0] == 0x00 && headr[1] == 0x00 + && headr[2] == 0x01 && headr[3] == 0xBA){ + return PS_STREAM; + } + + fin >> pes; + fin.read((char *)!headr,4); + fin.seekg(p); + if (fin && headr[0] == 0x00 && headr[1] == 0x00 + && headr[2] == 0x01 ){ + int found = 0; + switch ( headr[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; + } + if (found){ + return PES_STREAM; + } + } + + + + return -1; +} + + +void analyze(istream &fin) +{ + PS_Packet ps; + PES_Packet pes; + int fc = 0; + char *frames[3] = {"I-Frame","P-Frame","B-Frame"}; + + while(fin){ + uint32_t pts; + fin >> ps; + cerr << "SCR base: " << hex << setw(5) + << setfill('0') \ + << ps.SCR_base() << " " << dec + << "ext : " << ps.SCR_ext(); + + cerr << " MUX rate: " << ps.MUX()*50*8/1000000.0 + << " Mbit/s "; + cerr << "RATE bound: " << ps.Rate()*50*8/1000000.0 + << " Mbit/s" << endl; + cerr << " Audio bound: " + << hex << "0x" + << int(ps.P()->audio_bound); + cerr << " Video bound: " << hex << "0x" + << int(ps.P()->video_bound) + << dec + << endl; + cerr << endl; + + for (int i=0; i < ps.NPES(); i++){ + pes.init(); + fin >> pes; + pts2pts((uint8_t *)&pts,pes.PTS()); + pes.Info() = 1; + cerr << pes; + + uint8_t *buf = pes.P()->pes_pckt_data; + int c = 0; + int l; + switch (pes.P()->stream_id){ + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + l=pes.P()->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] == 0xB8) { + c += 4; + cerr << "TIME hours: " + << int((buf[c]>>2)& 0x1F) + << " minutes: " + << int(((buf[c]<<4)& 0x30)| + ((buf[c+1]>>4)& 0x0F)) + << " seconds: " + << int(((buf[c+1]<<3)& 0x38)| + ((buf[c+2]>>5)& 0x0F)) + << endl; + } + + if ( buf[c] == 0x00 && + buf[c+1] == 0x00 && + buf[c+2] == 0x01 && + buf[c+3] == 0x00) { + fc++; + c += 4; + cerr << "picture: " + << fc + << " (" + << frames[((buf[c+1]&0x38) >>3)-1] + << ")" << endl << endl; + } else c++; + } + } + } +} + + diff --git a/libdvbmpeg/cpptools.hh b/libdvbmpeg/cpptools.hh new file mode 100644 index 0000000..49ea5de --- /dev/null +++ b/libdvbmpeg/cpptools.hh @@ -0,0 +1,330 @@ +/* + * dvb-mpegtools for the Siemens Fujitsu DVB PCI card + * + * Copyright (C) 2000, 2001 Marcus Metzler + * for convergence integrated media GmbH + * Copyright (C) 2002 Marcus Metzler + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + + * The author can be reached at mocm@metzlerbros.de, + */ + +#include +#include +#include +#include +using namespace std; + + +#ifndef _CPPTOOLS_HH_ +#define _CPPTOOLS_HH_ + +#include "ctools.h" + + +class PES_Packet{ + int info; + pes_packet p; +public: + PES_Packet(){ + info = 0; + init_pes(&p); + } + + ~PES_Packet(){ + if (p.pack_header) + delete [] p.pack_header; + if (p.pes_ext) + delete [] p.pes_ext; + if (p.pes_pckt_data) + delete [] p.pes_pckt_data; + if (p.mpeg1_headr) + delete [] p.mpeg1_headr; + } + + inline void init(){ + if (p.pack_header) + delete [] p.pack_header; + if (p.pes_ext) + delete [] p.pes_ext; + if (p.pes_pckt_data) + delete [] p.pes_pckt_data; + if (p.mpeg1_headr) + delete [] p.mpeg1_headr; + + info = 0; + init_pes(&p); + } + + inline pes_packet *P(){ + return &p; + } + + inline void setlength(){ + setlength_pes(&p); + if (p.length) + p.pes_pckt_data = new uint8_t[p.length]; + } + + inline void Nlength(){ + nlength_pes(&p); + p.pes_pckt_data = new uint8_t[p.length]; + } + + + inline uint8_t &Stream_ID(){ + return p.stream_id; + } + + inline uint8_t &Flags1(){ + return p.flags1; + } + + inline uint8_t &Flags2(){ + return p.flags2; + } + + inline uint32_t &Length(){ + return p.length; + } + + inline uint8_t &HLength(){ + return p.pes_hlength; + } + + inline uint8_t &Stuffing(){ + return p.stuffing; + } + + inline uint8_t *Data(){ + return p.pes_pckt_data; + } + + inline int has_pts(){ + return (p.flags2 & PTS_DTS); + } + + inline int &MPEG(){ + return p.mpeg; + } + inline uint8_t *PTS(){ + return p.pts; + } + + inline uint8_t *DTS(){ + return p.dts; + } + + inline int &Info(){ + return info; + } + + + + inline uint8_t high_pts(){ + if (has_pts()) + return ((p.pts[0] & 0x08)>>3); + else + return 0; + } + + inline uint8_t high_dts(){ + return ((p.dts[0] & 0x08)>>3); + } + + inline int WDTS(){ + int w_dts; + w_dts = (int)trans_pts_dts(p.dts); + return w_dts; + } + + inline int WPTS(){ + int w_dts; + w_dts = (int)trans_pts_dts(p.pts); + return w_dts; + } + + friend ostream & operator << (ostream & stream, PES_Packet & x); + friend istream & operator >> (istream & stream, PES_Packet & x); + +}; + + +class TS_Packet{ + ts_packet p; + int info; + +public: + TS_Packet(){ + init_ts(&p); + info = 0; + } + + ~TS_Packet(){ + if (p.priv_dat) + delete [] p.priv_dat; + } + + inline void init(){ + if (p.priv_dat) + delete [] p.priv_dat; + + init_ts(&p); + info = 0; + } + + inline ts_packet *P(){ + return &p; + } + + inline int &Rest(){ + return p.rest; + } + + inline uint8_t *Data(){ + return p.data; + } + + inline short PID(){ + return pid_ts(&p); + } + + inline uint8_t FLAG1(){ + return (p.pid[0] & ~PID_MASK_HI); + } + + inline int &Info(){ + return info; + } + + friend ostream & operator << (ostream & stream, TS_Packet & x); + friend istream & operator >> (istream & stream, TS_Packet & x); +}; + + +class PS_Packet{ + int info; + ps_packet p; +public: + + PS_Packet(){ + init_ps(&p); + info = 0; + } + + ~PS_Packet(){ + if (p.data) + delete [] p.data; + } + + inline void init(){ + if (p.data) + delete [] p.data; + + init_ps(&p); + info = 0; + } + + inline ps_packet *P(){ + return &p; + } + + inline int MUX(){ + return mux_ps(&p); + } + + inline int Rate(){ + return rate_ps(&p); + } + + inline void setlength(){ + setlength_ps(&p); + p.data = new uint8_t[p.sheader_length]; + } + + inline int Stuffing(){ + return p.stuff_length & PACK_STUFF_MASK; + } + + inline int NPES(){ + return p.npes; + } + + inline int &MPEG(){ + return p.mpeg; + } + + inline uint8_t &operator()(int l){ + return p.data[l]; + } + + inline char * Data() { + return (char *)p.data+p.stuff_length; + } + + inline int &SLENGTH(){ + return p.sheader_length; + } + + inline int &Info(){ + return info; + } + + uint32_t SCR_base(){ + return scr_base_ps(&p); + } + + uint16_t SCR_ext(){ + return scr_ext_ps(&p); + } + + friend ostream & operator << (ostream & stream, PS_Packet & x); + friend istream & operator >> (istream & stream, PS_Packet & x); +}; + + +typedef void (* FILTER)(istream &in, ostream &out); + +typedef struct thread_args_{ + FILTER function; + int *fd; + int in; + int out; +} thread_args; + + +void extract_audio_from_PES(istream &in, ostream &out); +void extract_video_from_PES(istream &in, ostream &out); +void extract_es_audio_from_PES(istream &in, ostream &out); +void extract_es_video_from_PES(istream &in, ostream &out); +int TS_PIDS(istream &in, ostream &out); +int ifilter (istream &in, FILTER function); +int ofilter (istream &in, FILTER function); +int itfilter (int in, FILTER function); +int otfilter (istream &in, FILTER function); +int stream_type(int fd); +int stream_type(istream &stream); +int tv_norm(istream &fin); + +void analyze(istream &fin); + + +#endif //_CPPTOOLS_HH_ + diff --git a/libdvbmpeg/ctools.c b/libdvbmpeg/ctools.c new file mode 100644 index 0000000..76ba48d --- /dev/null +++ b/libdvbmpeg/ctools.c @@ -0,0 +1,2379 @@ +/* + * dvb-mpegtools for the Siemens Fujitsu DVB PCI card + * + * Copyright (C) 2000, 2001 Marcus Metzler + * for convergence integrated media GmbH + * Copyright (C) 2002 Marcus Metzler + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + + * The author can be reached at mocm@metzlerbros.de, + */ + +#include "ctools.h" + +#define MAX_SEARCH 1024 * 1024 + + +/* + + PES + +*/ + +ssize_t save_read(int fd, void *buf, size_t count) +{ + ssize_t neof = 1; + size_t re = 0; + + while(neof >= 0 && re < count){ + neof = read(fd, buf+re, count - re); + if (neof > 0) re += neof; + else break; + } + + if (neof < 0 && re == 0) return neof; + else return re; +} + +void init_pes(pes_packet *p){ + p->stream_id = 0; + p->llength[0] = 0; + p->llength[1] = 0; + p->length = 0; + p->flags1 = 0x80; + p->flags2 = 0; + p->pes_hlength = 0; + p->trick = 0; + p->add_cpy = 0; + p->priv_flags = 0; + p->pack_field_length = 0; + p->pack_header = (uint8_t *) NULL; + p->pck_sqnc_cntr = 0; + p->org_stuff_length = 0; + p->pes_ext_lngth = 0; + p->pes_ext = (uint8_t *) NULL; + p->pes_pckt_data = (uint8_t *) NULL; + p->padding = 0; + p->mpeg = 2; // DEFAULT MPEG2 + p->mpeg1_pad = 0; + p->mpeg1_headr = NULL; + p->stuffing = 0; +} + +void kill_pes(pes_packet *p){ + if (p->pack_header) + free(p->pack_header); + if (p->pes_ext) + free(p->pes_ext); + if (p->pes_pckt_data) + free(p->pes_pckt_data); + if (p->mpeg1_headr) + free(p->mpeg1_headr); + init_pes(p); +} + +void setlength_pes(pes_packet *p){ + short *ll; + ll = (short *) p->llength; + p->length = ntohs(*ll); +} + +static void setl_pes(pes_packet *p){ + setlength_pes(p); + if (p->length) + p->pes_pckt_data = (uint8_t *)malloc(p->length); +} + +void nlength_pes(pes_packet *p){ + if (p->length <= 0xFFFF){ + short *ll = (short *) p->llength; + short l = p->length; + *ll = htons(l); + } else { + p->llength[0] =0x00; + p->llength[1] =0x00; + } +} + +static void nl_pes(pes_packet *p) +{ + nlength_pes(p); + p->pes_pckt_data = (uint8_t *) malloc(p->length); +} + +void pts2pts(uint8_t *av_pts, uint8_t *pts) +{ + + av_pts[0] = ((pts[0] & 0x06) << 5) | + ((pts[1] & 0xFC) >> 2); + av_pts[1] = ((pts[1] & 0x03) << 6) | + ((pts[2] & 0xFC) >> 2); + av_pts[2] = ((pts[2] & 0x02) << 6) | + ((pts[3] & 0xFE) >> 1); + av_pts[3] = ((pts[3] & 0x01) << 7) | + ((pts[4] & 0xFE) >> 1); + +} + + +int cwrite_pes(uint8_t *buf, pes_packet *p, long length){ + int count,i; + uint8_t dummy; + int more = 0; + uint8_t headr[3] = { 0x00, 0x00 , 0x01}; + + if (length < p->length+p->pes_hlength){ + fprintf(stderr,"Wrong buffer size in cwrite_pes\n"); + exit(1); + } + + + memcpy(buf,headr,3); + count = 3; + buf[count] = p->stream_id; + count++; + + switch ( p->stream_id ) { + + case PROG_STREAM_MAP: + case PRIVATE_STREAM2: + case PROG_STREAM_DIR: + case ECM_STREAM : + case EMM_STREAM : + case PADDING_STREAM : + buf[count] = p->llength[0]; + count++; + buf[count] = p->llength[1]; + count++; + memcpy(buf+count,p->pes_pckt_data,p->length); + count += p->length; + break; + case DSM_CC_STREAM : + case ISO13522_STREAM: + case PRIVATE_STREAM1: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + buf[count] = p->llength[0]; + count++; + buf[count] = p->llength[1]; + count++; + more = 1; + break; + } + + + if ( more ) { + if ( p->mpeg == 2 ){ + memcpy(buf+count,&p->flags1,1); + count++; + memcpy(buf+count,&p->flags2,1); + count++; + memcpy(buf+count,&p->pes_hlength,1); + count++; + + if ((p->flags2 & PTS_DTS_FLAGS) == PTS_ONLY){ + memcpy(buf+count,p->pts,5); + count += 5; + } else + if ((p->flags2 & PTS_DTS_FLAGS) == PTS_DTS){ + memcpy(buf+count,p->pts,5); + count += 5; + memcpy(buf+count,p->dts,5); + count += 5; + } + if (p->flags2 & ESCR_FLAG){ + memcpy(buf+count,p->escr,6); + count += 6; + } + if (p->flags2 & ES_RATE_FLAG){ + memcpy(buf+count,p->es_rate,3); + count += 3; + } + if (p->flags2 & DSM_TRICK_FLAG){ + memcpy(buf+count,&p->trick,1); + count++; + } + if (p->flags2 & ADD_CPY_FLAG){ + memcpy(buf+count,&p->add_cpy,1); + count++; + } + if (p->flags2 & PES_CRC_FLAG){ + memcpy(buf+count,p->prev_pes_crc,2); + count += 2; + } + if (p->flags2 & PES_EXT_FLAG){ + memcpy(buf+count,&p->priv_flags,1); + count++; + + if (p->priv_flags & PRIVATE_DATA){ + memcpy(buf+count,p->pes_priv_data,16); + count += 16; + } + if (p->priv_flags & HEADER_FIELD){ + memcpy(buf+count,&p->pack_field_length, + 1); + count++; + memcpy(buf+count,p->pack_header, + p->pack_field_length); + count += p->pack_field_length; + + } + + if ( p->priv_flags & PACK_SEQ_CTR){ + memcpy(buf+count,&p->pck_sqnc_cntr,1); + count++; + memcpy(buf+count,&p->org_stuff_length, + 1); + count++; + } + + if ( p->priv_flags & P_STD_BUFFER){ + memcpy(buf+count,p->p_std,2); + count += 2; + } + if ( p->priv_flags & PES_EXT_FLAG2){ + memcpy(buf+count,&p->pes_ext_lngth,1); + count++; + memcpy(buf+count,p->pes_ext, + p->pes_ext_lngth); + count += p->pes_ext_lngth; + } + } + dummy = 0xFF; + for (i=0;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 p = 0; + uint64_t start = 0; + uint64_t q = 0; + int found = 0; + uint8_t sync4[4]; + int neof = 1; + + start = lseek(f,0,SEEK_CUR); + start -=2; + lseek(f,start,SEEK_SET); + while ( neof > 0 && !found ){ + p = lseek(f,0,SEEK_CUR); + neof = save_read(f,&sync4,4); + if (sync4[0] == 0x00 && sync4[1] == 0x00 && sync4[2] == 0x01) { + switch ( sync4[3] ) { + + case PROG_STREAM_MAP: + case PRIVATE_STREAM2: + case PROG_STREAM_DIR: + case ECM_STREAM : + case EMM_STREAM : + case PADDING_STREAM : + case DSM_CC_STREAM : + case ISO13522_STREAM: + case PRIVATE_STREAM1: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + found = 1; + break; + default: + q = lseek(f,0,SEEK_CUR); + break; + } + } + } + q = lseek(f,0,SEEK_CUR); + lseek(f,start+2,SEEK_SET); + if (found) return (unsigned int)(q-start)-4-2; + else return (unsigned int)(q-start-2); + +} + + +void cread_pes(char *buf, pes_packet *p){ + + uint8_t count, dummy, check; + int i; + uint64_t po = 0; + int c=0; + + switch ( p->stream_id ) { + + case PROG_STREAM_MAP: + case PRIVATE_STREAM2: + case PROG_STREAM_DIR: + case ECM_STREAM : + case EMM_STREAM : + memcpy(p->pes_pckt_data,buf+c,p->length); + return; + break; + case PADDING_STREAM : + p->padding = p->length; + memcpy(p->pes_pckt_data,buf+c,p->length); + return; + break; + case DSM_CC_STREAM : + case ISO13522_STREAM: + case PRIVATE_STREAM1: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + break; + default: + return; + break; + } + + po = c; + memcpy(&p->flags1,buf+c,1); + c++; + if ( (p->flags1 & 0xC0) == 0x80 ) p->mpeg = 2; + else p->mpeg = 1; + + if ( p->mpeg == 2 ){ + memcpy(&p->flags2,buf+c,1); + c++; + memcpy(&p->pes_hlength,buf+c,1); + c++; + + p->length -=p->pes_hlength+3; + count = p->pes_hlength; + + if ((p->flags2 & PTS_DTS_FLAGS) == PTS_ONLY){ + memcpy(p->pts,buf+c,5); + c += 5; + count -=5; + } else + if ((p->flags2 & PTS_DTS_FLAGS) == PTS_DTS){ + memcpy(p->pts,buf+c,5); + c += 5; + memcpy(p->dts,buf+c,5); + c += 5; + count -= 10; + } + + if (p->flags2 & ESCR_FLAG){ + memcpy(p->escr,buf+c,6); + c += 6; + count -= 6; + } + + if (p->flags2 & ES_RATE_FLAG){ + memcpy(p->es_rate,buf+c,3); + c += 3; + count -= 3; + } + + if (p->flags2 & DSM_TRICK_FLAG){ + memcpy(&p->trick,buf+c,1); + c += 1; + count -= 1; + } + + if (p->flags2 & ADD_CPY_FLAG){ + memcpy(&p->add_cpy,buf+c,1); + c++; + count -= 1; + } + + if (p->flags2 & PES_CRC_FLAG){ + memcpy(p->prev_pes_crc,buf+c,2); + c += 2; + count -= 2; + } + + if (p->flags2 & PES_EXT_FLAG){ + memcpy(&p->priv_flags,buf+c,1); + c++; + count -= 1; + + if (p->priv_flags & PRIVATE_DATA){ + memcpy(p->pes_priv_data,buf+c,16); + c += 16; + count -= 16; + } + + if (p->priv_flags & HEADER_FIELD){ + memcpy(&p->pack_field_length,buf+c,1); + c++; + p->pack_header = (uint8_t *) + malloc(p->pack_field_length); + memcpy(p->pack_header,buf+c, + p->pack_field_length); + c += p->pack_field_length; + count -= 1+p->pack_field_length; + } + + if ( p->priv_flags & PACK_SEQ_CTR){ + memcpy(&p->pck_sqnc_cntr,buf+c,1); + c++; + memcpy(&p->org_stuff_length,buf+c,1); + c++; + count -= 2; + } + + if ( p->priv_flags & P_STD_BUFFER){ + memcpy(p->p_std,buf+c,2); + c += 2; + count -= 2; + } + + if ( p->priv_flags & PES_EXT_FLAG2){ + memcpy(&p->pes_ext_lngth,buf+c,1); + c++; + p->pes_ext = (uint8_t *) + malloc(p->pes_ext_lngth); + memcpy(p->pes_ext,buf+c, + p->pes_ext_lngth); + c += p->pes_ext_lngth; + count -= 1+p->pes_ext_lngth; + } + } + p->stuffing = count; + for(i = 0; i< count ;i++){ + memcpy(&dummy,buf+c,1); + c++; + } + } else { + p->mpeg1_pad = 1; + check = p->flags1; + while (check == 0xFF){ + memcpy(&check,buf+c,1); + c++; + p->mpeg1_pad++; + } + + if ( (check & 0xC0) == 0x40){ + memcpy(&check,buf+c,1); + c++; + p->mpeg1_pad++; + memcpy(&check,buf+c,1); + c++; + p->mpeg1_pad++; + } + p->flags2 = 0; + p->length -= p->mpeg1_pad; + + c = po; + if ( (check & 0x30)){ + p->length ++; + p->mpeg1_pad --; + + if (check == p->flags1){ + p->pes_hlength = 0; + } else { + p->mpeg1_headr = (uint8_t *) + malloc(p->mpeg1_pad); + p->pes_hlength = p->mpeg1_pad; + memcpy(p->mpeg1_headr,buf+c, + p->mpeg1_pad); + c += p->mpeg1_pad; + } + + p->flags2 = (check & 0xF0) << 2; + if ((p->flags2 & PTS_DTS_FLAGS) == PTS_ONLY){ + memcpy(p->pts,buf+c,5); + c += 5; + p->length -= 5; + p->pes_hlength += 5; + } + else if ((p->flags2 & PTS_DTS_FLAGS) == + PTS_DTS){ + memcpy(p->pts,buf+c,5); + c += 5; + memcpy(p->dts,buf+c,5); + c += 5; + p->length -= 10; + p->pes_hlength += 10; + } + } else { + p->mpeg1_headr = (uint8_t *) malloc(p->mpeg1_pad); + p->pes_hlength = p->mpeg1_pad; + memcpy(p->mpeg1_headr,buf+c, + p->mpeg1_pad); + c += p->mpeg1_pad; + } + } + memcpy(p->pes_pckt_data,buf+c,p->length); +} + + +int read_pes(int f, pes_packet *p){ + + uint8_t sync4[4]; + int found=0; + uint64_t po = 0; + int neof = 1; + uint8_t *buf; + + while (neof > 0 && !found) { + po = lseek(f,0,SEEK_CUR); + if (po < 0) return -1; + if ((neof = save_read(f,&sync4,4)) < 4) return -1; + if (sync4[0] == 0x00 && sync4[1] == 0x00 && sync4[2] == 0x01) { + p->stream_id = sync4[3]; + switch ( sync4[3] ) { + + case PROG_STREAM_MAP: + case PRIVATE_STREAM2: + case PROG_STREAM_DIR: + case ECM_STREAM : + case EMM_STREAM : + case PADDING_STREAM : + case DSM_CC_STREAM : + case ISO13522_STREAM: + case PRIVATE_STREAM1: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + if((neof = save_read(f,p->llength,2)) < 2) + return -1; + setl_pes(p); + if (!p->length){ + p->length = find_length(f); + nl_pes(p); + } + found = 1; + break; + + default: + if (lseek(f,po+1,SEEK_SET) < po+1) return -1; + break; + } + } else if(lseek(f,po+1,SEEK_SET) < po+1) return -1; + } + + if (!found || !p->length) return 0; + + if (p->length >0){ + buf = (uint8_t *) malloc(p->length); + if((neof = save_read(f,buf,p->length))< p->length) return -1; + cread_pes((char *)buf,p); + free(buf); + } else return 0; + + return neof; +} + +/* + + Transport Stream + +*/ + +void init_ts(ts_packet *p){ + p->pid[0] = 0; + p->pid[1] = 0; + p->flags = 0; + p->count = 0; + p->adapt_length = 0; + p->adapt_flags = 0; + p->splice_count = 0; + p->priv_dat_len = 0; + p->priv_dat = NULL; + p->adapt_ext_len = 0; + p->adapt_eflags = 0; + p->rest = 0; + p->stuffing = 0; +} + +void kill_ts(ts_packet *p){ + if (p->priv_dat) + free(p->priv_dat); + init_ts(p); +} + + + +unsigned short pid_ts(ts_packet *p) +{ + return get_pid(p->pid); +} + +int cwrite_ts(uint8_t *buf, ts_packet *p, long length){ + long count,i; + uint8_t sync,dummy; + + sync = 0x47; + memcpy(buf,&sync,1); + count = 1; + memcpy(buf+count,p->pid,2); + count += 2; + memcpy(buf+count,&p->flags,1); + count++; + + + if (! (p->flags & ADAPT_FIELD) && (p->flags & PAYLOAD)){ + memcpy(buf+count,p->data,184); + count += 184; + } else { + memcpy(buf+count,&p->adapt_length,1); + count++; + memcpy(buf+count,&p->adapt_flags,1); + count++; + + if ( p->adapt_flags & PCR_FLAG ){ + memcpy(buf+count, p->pcr,6); + count += 6; + } + if ( p->adapt_flags & OPCR_FLAG ){ + memcpy(buf+count, p->opcr,6); + count += 6; + } + if ( p->adapt_flags & SPLICE_FLAG ){ + memcpy(buf+count, &p->splice_count,1); + count++; + } + if( p->adapt_flags & TRANS_PRIV){ + memcpy(buf+count,&p->priv_dat_len,1); + count++; + memcpy(buf+count,p->priv_dat,p->priv_dat_len); + count += p->priv_dat_len; + } + + if( p->adapt_flags & ADAP_EXT_FLAG){ + memcpy(buf+count,&p->adapt_ext_len,1); + count++; + memcpy(buf+count,&p->adapt_eflags,1); + count++; + + if( p->adapt_eflags & LTW_FLAG){ + memcpy(buf+count,p->ltw,2); + count += 2; + } + if( p->adapt_eflags & PIECE_RATE){ + memcpy(buf+count,p->piece_rate,3); + count += 3; + } + if( p->adapt_eflags & SEAM_SPLICE){ + memcpy(buf+count,p->dts,5); + count += 5; + } + } + dummy = 0xFF; + for(i=0; i < p->stuffing ; i++){ + memcpy(buf+count,&dummy,1); + count++; + } + if (p->flags & PAYLOAD){ + memcpy(buf+count,p->data,p->rest); + count += p->rest; + } + } + + + return count; +} + +void write_ts(int fd, ts_packet *p){ + long length; + uint8_t buf[TS_SIZE]; + + length = cwrite_ts(buf,p,TS_SIZE); + write(fd,buf,length); +} + +int read_ts (int f, ts_packet *p){ + uint8_t sync; + int found=0; + uint64_t po,q; + int neof = 1; + + sync=0; + while (neof > 0 && !found) { + neof = save_read(f,&sync,1); + if (sync == 0x47) + found = 1; + } + neof = save_read(f,p->pid,2); + neof = save_read(f,&p->flags,1); + p->count = p->flags & COUNT_MASK; + + if (!(p->flags & ADAPT_FIELD) && (p->flags & PAYLOAD)){ + //no adapt. field only payload + neof = save_read(f,p->data,184); + p->rest = 184; + return neof; + } + + if ( p->flags & ADAPT_FIELD ) { + // adaption field + neof = save_read(f,&p->adapt_length,1); + po = lseek(f,0,SEEK_CUR); + neof = save_read(f,&p->adapt_flags,1); + + if ( p->adapt_flags & PCR_FLAG ) + neof = save_read(f, p->pcr,6); + + if ( p->adapt_flags & OPCR_FLAG ) + neof = save_read(f, p->opcr,6); + + if ( p->adapt_flags & SPLICE_FLAG ) + neof = save_read(f, &p->splice_count,1); + + if( p->adapt_flags & TRANS_PRIV){ + neof = save_read(f,&p->priv_dat_len,1); + p->priv_dat = (uint8_t *) malloc(p->priv_dat_len); + neof = save_read(f,p->priv_dat,p->priv_dat_len); + } + + if( p->adapt_flags & ADAP_EXT_FLAG){ + neof = save_read(f,&p->adapt_ext_len,1); + neof = save_read(f,&p->adapt_eflags,1); + if( p->adapt_eflags & LTW_FLAG) + neof = save_read(f,p->ltw,2); + + if( p->adapt_eflags & PIECE_RATE) + neof = save_read(f,p->piece_rate,3); + + if( p->adapt_eflags & SEAM_SPLICE) + neof = save_read(f,p->dts,5); + } + q = lseek(f,0,SEEK_CUR); + p->stuffing = p->adapt_length -(q-po); + p->rest = 183-p->adapt_length; + lseek(f,q+p->stuffing,SEEK_SET); + if (p->flags & PAYLOAD) // payload + neof = save_read(f,p->data,p->rest); + else + lseek(f,q+p->rest,SEEK_SET); + } + return neof; +} + +void cread_ts (char *buf, ts_packet *p, long length){ + uint8_t sync; + int found=0; + uint64_t po,q; + long count=0; + + sync=0; + while (count < length && !found) { + sync=buf[count]; + count++; + if (sync == 0x47) + found = 1; + } + memcpy(p->pid,buf+count,2); + count += 2; + p->flags = buf[count]; + count++; + p->count = p->flags & COUNT_MASK; + + if (!(p->flags & ADAPT_FIELD) && (p->flags & PAYLOAD)){ + //no adapt. field only payload + memcpy(p->data,buf+count,184); + p->rest = 184; + return; + } + + if ( p->flags & ADAPT_FIELD ) { + // adaption field + p->adapt_length = buf[count]; + count++; + po = count; + memcpy(&p->adapt_flags,buf+count,1); + count++; + + if ( p->adapt_flags & PCR_FLAG ){ + memcpy( p->pcr,buf+count,6); + count += 6; + } + if ( p->adapt_flags & OPCR_FLAG ){ + memcpy( p->opcr,buf+count,6); + count += 6; + } + if ( p->adapt_flags & SPLICE_FLAG ){ + memcpy( &p->splice_count,buf+count,1); + count++; + } + if( p->adapt_flags & TRANS_PRIV){ + memcpy(&p->priv_dat_len,buf+count,1); + count++; + p->priv_dat = (uint8_t *) malloc(p->priv_dat_len); + memcpy(p->priv_dat,buf+count,p->priv_dat_len); + count += p->priv_dat_len; + } + + if( p->adapt_flags & ADAP_EXT_FLAG){ + memcpy(&p->adapt_ext_len,buf+count,1); + count++; + memcpy(&p->adapt_eflags,buf+count,1); + count++; + if( p->adapt_eflags & LTW_FLAG){ + memcpy(p->ltw,buf+count,2); + count += 2; + } + if( p->adapt_eflags & PIECE_RATE){ + memcpy(p->piece_rate,buf+count,3); + count += 3; + } + if( p->adapt_eflags & SEAM_SPLICE){ + memcpy(p->dts,buf+count,5); + count += 5; + } + } + q = count; + p->stuffing = p->adapt_length -(q-po); + p->rest = 183-p->adapt_length; + count = q+p->stuffing; + if (p->flags & PAYLOAD){ // payload + memcpy(p->data,buf+count,p->rest); + count += p->rest; + } else + count = q+p->rest; + } +} + + +/* + + Program Stream + +*/ + + +void init_ps(ps_packet *p) +{ + p->stuff_length=0xF8; + p->data = NULL; + p->sheader_length = 0; + p->audio_bound = 0; + p->video_bound = 0; + p->npes = 0; + p->mpeg = 2; +} + +void kill_ps(ps_packet *p) +{ + if (p->data) + free(p->data); + init_ps(p); +} + +void setlength_ps(ps_packet *p) +{ + short *ll; + ll = (short *) p->sheader_llength; + if (p->mpeg == 2) + p->sheader_length = ntohs(*ll) - 6; + else + p->sheader_length = ntohs(*ll); +} + +static void setl_ps(ps_packet *p) +{ + setlength_ps(p); + p->data = (uint8_t *) malloc(p->sheader_length); +} + +int mux_ps(ps_packet *p) +{ + uint32_t mux = 0; + uint8_t *i = (uint8_t *)&mux; + + i[1] = p->mux_rate[0]; + i[2] = p->mux_rate[1]; + i[3] = p->mux_rate[2]; + mux = ntohl(mux); + mux = (mux >>2); + return mux; +} + +int rate_ps(ps_packet *p) +{ + uint32_t rate=0; + uint8_t *i= (uint8_t *) &rate; + + i[1] = p->rate_bound[0] & 0x7F; + i[2] = p->rate_bound[1]; + i[3] = p->rate_bound[2]; + + rate = ntohl(rate); + rate = (rate >> 1); + return rate; +} + + +uint32_t scr_base_ps(ps_packet *p) // only 32 bit!! +{ + uint32_t base = 0; + uint8_t *buf = (uint8_t *)&base; + + buf[0] |= (long int)((p->scr[0] & 0x18) << 3); + buf[0] |= (long int)((p->scr[0] & 0x03) << 4); + buf[0] |= (long int)((p->scr[1] & 0xF0) >> 4); + + buf[1] |= (long int)((p->scr[1] & 0x0F) << 4); + buf[1] |= (long int)((p->scr[2] & 0xF0) >> 4); + + buf[2] |= (long int)((p->scr[2] & 0x08) << 4); + buf[2] |= (long int)((p->scr[2] & 0x03) << 5); + buf[2] |= (long int)((p->scr[3] & 0xF8) >> 3); + + buf[3] |= (long int)((p->scr[3] & 0x07) << 5); + buf[3] |= (long int)((p->scr[4] & 0xF8) >> 3); + + base = ntohl(base); + return base; +} + +uint16_t scr_ext_ps(ps_packet *p) +{ + short ext = 0; + + ext = (short)(p->scr[5] >> 1); + ext += (short) (p->scr[4] & 0x03) * 128; + + return ext; +} + +int cwrite_ps(uint8_t *buf, ps_packet *p, long length) +{ + long count,i; + uint8_t headr1[4] = {0x00, 0x00, 0x01, 0xBA }; + uint8_t headr2[4] = {0x00, 0x00, 0x01, 0xBB }; + uint8_t buffy = 0xFF; + + + memcpy(buf,headr1,4); + count = 4; + if (p->mpeg == 2){ + memcpy(buf+count,p->scr,6); + count += 6; + memcpy(buf+count,p->mux_rate,3); + count += 3; + memcpy(buf+count,&p->stuff_length,1); + count++; + for(i=0; i< (p->stuff_length & 3); i++){ + memcpy(buf+count,&buffy,1); + count++; + } + } else { + memcpy(buf+count,p->scr,5); + count += 5; + memcpy(buf+count,p->mux_rate,3); + count += 3; + } + if (p->sheader_length){ + memcpy(buf+count,headr2,4); + count += 4; + memcpy(buf+count,p->sheader_llength,2); + count += 2; + if ( p->mpeg == 2){ + memcpy(buf+count,p->rate_bound,3); + count += 3; + memcpy(buf+count,&p->audio_bound,1); + count++; + memcpy(buf+count,&p->video_bound,1); + count++; + memcpy(buf+count,&p->reserved,1); + count++; + } + memcpy(buf+count,p->data,p->sheader_length); + count += p->sheader_length; + } + + return count; +} + +void write_ps(int fd, ps_packet *p){ + long length; + uint8_t buf[PS_MAX]; + + length = cwrite_ps(buf,p,PS_MAX); + write(fd,buf,length); +} + +int read_ps (int f, ps_packet *p){ + uint8_t headr[4]; + pes_packet pes; + int i,done; + int found=0; + uint64_t po = 0; + uint64_t q = 0; + long count = 0; + int neof = 1; + + po = lseek(f,0,SEEK_CUR); + while (neof > 0 && !found && count < MAX_SEARCH) { + neof = save_read(f,&headr,4); + if (headr[0] == 0x00 && headr[1] == 0x00 && headr[2] == 0x01){ + if ( headr[3] == 0xBA ) + found = 1; + else + if ( headr[3] == 0xB9 ) break; + else lseek(f,po+1,SEEK_SET); + } + count++; + } + + if (found){ + neof = save_read(f,p->scr,6); + if (p->scr[0] & 0x40) + p->mpeg = 2; + else + p->mpeg = 1; + + if (p->mpeg == 2){ + neof = save_read(f,p->mux_rate,3); + neof = save_read(f,&p->stuff_length,1); + po = lseek(f,0,SEEK_CUR); + lseek(f,po+(p->stuff_length & 3),SEEK_SET); + } else { + p->mux_rate[0] = p->scr[5]; //mpeg1 scr is only 5 bytes + neof = save_read(f,p->mux_rate+1,2); + } + + po = lseek(f,0,SEEK_CUR); + neof = save_read(f,headr,4); + if (headr[0] == 0x00 && headr[1] == 0x00 && + headr[2] == 0x01 && headr[3] == 0xBB ) { + neof = save_read(f,p->sheader_llength,2); + setl_ps(p); + if (p->mpeg == 2){ + neof = save_read(f,p->rate_bound,3); + neof = save_read(f,&p->audio_bound,1); + neof = save_read(f,&p->video_bound,1); + neof = save_read(f,&p->reserved,1); + } + neof = save_read(f,p->data,p->sheader_length); + } else { + lseek(f,po,SEEK_SET); + p->sheader_length = 0; + } + + i = 0; + done = 0; + q = lseek(f,0,SEEK_CUR); + do { + po = lseek(f,0,SEEK_CUR); + neof = save_read(f,headr,4); + lseek(f,po,SEEK_SET); + if ( headr[0] == 0x00 && headr[1] == 0x00 + && headr[2] == 0x01 && headr[3] != 0xBA){ + init_pes(&pes); + neof = read_pes(f,&pes); + i++; + } else done = 1; + kill_pes(&pes); + } while ( neof > 0 && !done); + p->npes = i; + lseek(f,q,SEEK_SET); + } + return neof; +} + +void cread_ps (char *buf, ps_packet *p, long length){ + uint8_t *headr; + pes_packet pes; + int i,done; + int found=0; + uint64_t po = 0; + uint64_t q = 0; + long count = 0; + long c = 0; + + po = c; + while ( count < length && !found && count < MAX_SEARCH) { + headr = (uint8_t *)buf+c; + c += 4; + if (headr[0] == 0x00 && headr[1] == 0x00 && headr[2] == 0x01){ + if ( headr[3] == 0xBA ) + found = 1; + else + if ( headr[3] == 0xB9 ) break; + else c = po+1; + } + count++; + } + + if (found){ + memcpy(p->scr,buf+c,6); + c += 6; + if (p->scr[0] & 0x40) + p->mpeg = 2; + else + p->mpeg = 1; + + if (p->mpeg == 2){ + memcpy(p->mux_rate,buf+c,3); + c += 3; + memcpy(&p->stuff_length,buf+c,1); + c++; + po = c; + c = po+(p->stuff_length & 3); + } else { + p->mux_rate[0] = p->scr[5]; //mpeg1 scr is only 5 bytes + memcpy(p->mux_rate+1,buf+c,2); + c += 2; + } + + po = c; + headr = (uint8_t *)buf+c; + c += 4; + if (headr[0] == 0x00 && headr[1] == 0x00 && + headr[2] == 0x01 && headr[3] == 0xBB ) { + memcpy(p->sheader_llength,buf+c,2); + c += 2; + setl_ps(p); + if (p->mpeg == 2){ + memcpy(p->rate_bound,buf+c,3); + c += 3; + memcpy(&p->audio_bound,buf+c,1); + c++; + memcpy(&p->video_bound,buf+c,1); + c++; + memcpy(&p->reserved,buf+c,1); + c++; + } + memcpy(p->data,buf+c,p->sheader_length); + c += p->sheader_length; + } else { + c = po; + p->sheader_length = 0; + } + + i = 0; + done = 0; + q = c; + do { + headr = (uint8_t *)buf+c; + if ( headr[0] == 0x00 && headr[1] == 0x00 + && headr[2] == 0x01 && headr[3] != 0xBA){ + init_pes(&pes); + // cread_pes(buf+c,&pes); + i++; + } else done = 1; + kill_pes(&pes); + } while (c < length && !done); + p->npes = i; + c = q; + } +} + + + + + + + +/* + conversion +*/ + +void init_trans(trans *p) +{ + int i; + + p->found = 0; + p->pes = 0; + p->is_full = 0; + p->pes_start = 0; + p->pes_started = 0; + p->set = 0; + + for (i = 0; i < MASKL*MAXFILT ; i++){ + p->mask[i] = 0; + p->filt[i] = 0; + } + for (i = 0; i < MAXFILT ; i++){ + p->sec[i].found = 0; + p->sec[i].length = 0; + } +} + +int set_trans_filt(trans *p, int filtn, uint16_t pid, uint8_t *mask, uint8_t *filt, int pes) +{ + int i; + int off; + + if ( filtn > MAXFILT-1 || filtn<0 ) return -1; + p->pid[filtn] = pid; + if (pes) p->pes |= (tflags)(1 << filtn); + else { + off = MASKL*filtn; + p->pes &= ~((tflags) (1 << filtn) ); + for (i = 0; i < MASKL ; i++){ + p->mask[off+i] = mask[i]; + p->filt[off+i] = filt[i]; + } + } + p->set |= (tflags) (1 << filtn); + return 0; +} + +void clear_trans_filt(trans *p,int filtn) +{ + int i; + + p->set &= ~((tflags) (1 << filtn) ); + p->pes &= ~((tflags) (1 << filtn) ); + p->is_full &= ~((tflags) (1 << filtn) ); + p->pes_start &= ~((tflags) (1 << filtn) ); + p->pes_started &= ~((tflags) (1 << filtn) ); + + for (i = MASKL*filtn; i < MASKL*(filtn+1) ; i++){ + p->mask[i] = 0; + p->filt[i] = 0; + } + p->sec[filtn].found = 0; + p->sec[filtn].length = 0; +} + +int filt_is_set(trans *p, int filtn) +{ + if (p->set & ((tflags)(1 << filtn))) return 1; + return 0; +} + +int pes_is_set(trans *p, int filtn) +{ + if (p->pes & ((tflags)(1 << filtn))) return 1; + return 0; +} + +int pes_is_started(trans *p, int filtn) +{ + if (p->pes_started & ((tflags)(1 << filtn))) return 1; + return 0; +} + +int pes_is_start(trans *p, int filtn) +{ + if (p->pes_start & ((tflags)(1 << filtn))) return 1; + return 0; +} + +int filt_is_ready(trans *p,int filtn) +{ + if (p->is_full & ((tflags)(1 << filtn))) return 1; + return 0; +} + +void trans_filt(uint8_t *buf, int count, trans *p) +{ + int c=0; + //fprintf(stderr,"trans_filt\n"); + + + while (c < count && p->found <1 ){ + if ( buf[c] == 0x47) p->found = 1; + c++; + p->packet[0] = 0x47; + } + if (c == count) return; + + while( c < count && p->found < 188 && p->found > 0 ){ + p->packet[p->found] = buf[c]; + c++; + p->found++; + } + if (p->found == 188){ + p->found = 0; + tfilter(p); + } + + if (c < count) trans_filt(buf+c,count-c,p); +} + + +void tfilter(trans *p) +{ + int l,c; + int tpid; + uint8_t flag,flags; + uint8_t adapt_length = 0; + uint8_t cpid[2]; + + + // fprintf(stderr,"tfilter\n"); + + cpid[0] = p->packet[1]; + cpid[1] = p->packet[2]; + tpid = get_pid(cpid); + + if ( p->packet[1]&0x80){ + fprintf(stderr,"Error in TS for PID: %d\n", + tpid); + } + + flag = cpid[0]; + flags = p->packet[3]; + + if ( flags & ADAPT_FIELD ) { + // adaption field + adapt_length = p->packet[4]; + } + + c = 5 + adapt_length - (int)(!(flags & ADAPT_FIELD)); + if (flags & PAYLOAD){ + for ( l = 0; l < MAXFILT ; l++){ + if ( filt_is_set(p,l) ) { + if ( p->pid[l] == tpid) { + if ( pes_is_set(p,l) ){ + if (cpid[0] & PAY_START){ + p->pes_started |= + (tflags) + (1 << l); + p->pes_start |= + (tflags) + (1 << l); + } else { + p->pes_start &= ~ + ((tflags) + (1 << l)); + } + pes_filter(p,l,c); + } else { + sec_filter(p,l,c); + } + } + } + } + } +} + + +void pes_filter(trans *p, int filtn, int off) +{ + int count,c; + uint8_t *buf; + + if (filtn < 0 || filtn >= MAXFILT) return; + + count = 188 - off; + c = 188*filtn; + buf = p->packet+off; + if (pes_is_started(p,filtn)){ + p->is_full |= (tflags) (1 << filtn); + memcpy(p->transbuf+c,buf,count); + p->transcount[filtn] = count; + } +} + +section *get_filt_sec(trans *p, int filtn) +{ + section *sec; + + sec = &p->sec[filtn]; + p->is_full &= ~((tflags) (1 << filtn) ); + return sec; +} + +int get_filt_buf(trans *p, int filtn,uint8_t **buf) +{ + *buf = p->transbuf+188*filtn; + p->is_full &= ~((tflags) (1 << filtn) ); + return p->transcount[filtn]; +} + + + + +void sec_filter(trans *p, int filtn, int off) +{ + int i,j; + int error; + int count,c; + uint8_t *buf, *secbuf; + section *sec; + + // fprintf(stderr,"sec_filter\n"); + + if (filtn < 0 || filtn >= MAXFILT) return; + + count = 188 - off; + c = 0; + buf = p->packet+off; + sec = &p->sec[filtn]; + secbuf = sec->payload; + if(!filt_is_ready(p,filtn)){ + p->is_full &= ~((tflags) (1 << filtn) ); + sec->found = 0; + sec->length = 0; + } + + if ( !sec->found ){ + c = buf[c]+1; + if (c >= count) return; + sec->id = buf[c]; + secbuf[0] = buf[c]; + c++; + sec->found++; + sec->length = 0; + } + + while ( c < count && sec->found < 3){ + secbuf[sec->found] = buf[c]; + c++; + sec->found++; + } + if (c == count) return; + + if (!sec->length && sec->found == 3){ + sec->length |= ((secbuf[1] & 0x0F) << 8); + sec->length |= (secbuf[2] & 0xFF); + } + + while ( c < count && sec->found < sec->length+3){ + secbuf[sec->found] = buf[c]; + c++; + sec->found++; + } + + if ( sec->length && sec->found == sec->length+3 ){ + error=0; + for ( i = 0; i < MASKL; i++){ + if (i > 0 ) j=2+i; + else j = 0; + error += (sec->payload[j]&p->mask[MASKL*filtn+i])^ + (p->filt[MASKL*filtn+i]& + p->mask[MASKL*filtn+i]); + } + if (!error){ + p->is_full |= (tflags) (1 << filtn); + } + if (buf[0]+1 < c ) c=count; + } + + if ( c < count ) sec_filter(p, filtn, off); + +} + +#define MULT 1024 + + +void write_ps_headr( ps_packet *p, uint8_t *pts,int fd) +{ + long muxr = 37500; + uint8_t audio_bound = 1; + uint8_t fixed = 0; + uint8_t CSPS = 0; + uint8_t audio_lock = 1; + uint8_t video_lock = 1; + uint8_t video_bound = 1; + uint8_t stream1 = 0XC0; + uint8_t buffer1_scale = 1; + uint32_t buffer1_size = 32; + uint8_t stream2 = 0xE0; + uint8_t buffer2_scale = 1; + uint32_t buffer2_size = 230; + + init_ps(p); + + p->mpeg = 2; +// SCR = 0 + p->scr[0] = 0x44; + p->scr[1] = 0x00; + p->scr[2] = 0x04; + p->scr[3] = 0x00; + p->scr[4] = 0x04; + p->scr[5] = 0x01; + +// SCR = PTS + p->scr[0] = 0x44 | ((pts[0] >> 3)&0x18) | ((pts[0] >> 4)&0x03); + p->scr[1] = 0x00 | ((pts[0] << 4)&0xF0) | ((pts[1] >> 4)&0x0F); + p->scr[2] = 0x04 | ((pts[1] << 4)&0xF0) | ((pts[2] >> 4)&0x08) + | ((pts[2] >> 5)&0x03); + p->scr[3] = 0x00 | ((pts[2] << 3)&0xF8) | ((pts[3] >> 5)&0x07); + p->scr[4] = 0x04 | ((pts[3] << 3)&0xF8); + p->scr[5] = 0x01; + + p->mux_rate[0] = (uint8_t)(muxr >> 14); + p->mux_rate[1] = (uint8_t)(0xff & (muxr >> 6)); + p->mux_rate[2] = (uint8_t)(0x03 | ((muxr & 0x3f) << 2)); + + p->stuff_length = 0xF8; + + p->sheader_llength[0] = 0x00; + p->sheader_llength[1] = 0x0c; + + setl_ps(p); + + p->rate_bound[0] = (uint8_t)(0x80 | (muxr >>15)); + p->rate_bound[1] = (uint8_t)(0xff & (muxr >> 7)); + p->rate_bound[2] = (uint8_t)(0x01 | ((muxr & 0x7f)<<1)); + + + p->audio_bound = (uint8_t)((audio_bound << 2)|(fixed << 1)|CSPS); + p->video_bound = (uint8_t)((audio_lock << 7)| + (video_lock << 6)|0x20|video_bound); + p->reserved = (uint8_t)(0xFF); + + p->data[0] = stream2; + p->data[1] = (uint8_t) (0xc0 | (buffer2_scale << 5) | + (buffer2_size >> 8)); + p->data[2] = (uint8_t) (buffer2_size & 0xff); + p->data[3] = stream1; + p->data[4] = (uint8_t) (0xc0 | (buffer1_scale << 5) | + (buffer1_size >> 8)); + p->data[5] = (uint8_t) (buffer1_size & 0xff); + + write_ps(fd, p); + kill_ps(p); +} + + + +void twrite(uint8_t const *buf) +{ + int l = TS_SIZE; + int c = 0; + int w; + + + while (l){ + w = write(STDOUT_FILENO,buf+c,l); + if (w>=0){ + l-=w; + c+=w; + } + } +} + +void init_p2t(p2t_t *p, void (*fkt)(uint8_t const *buf)) +{ + memset(p->pes,0,TS_SIZE); + p->counter = 0; + p->pos = 0; + p->frags = 0; + if (fkt) p->t_out = fkt; + else p->t_out = twrite; +} + +void clear_p2t(p2t_t *p) +{ + memset(p->pes,0,TS_SIZE); + p->counter = 0; + p->pos = 0; + p->frags = 0; +} + + +long int find_pes_header(uint8_t const *buf, long int length, int *frags) +{ + int c = 0; + int found = 0; + + *frags = 0; + + while (c < length-3 && !found) { + if (buf[c] == 0x00 && buf[c+1] == 0x00 && + buf[c+2] == 0x01) { + switch ( buf[c+3] ) { + case 0xBA: + case PROG_STREAM_MAP: + case PRIVATE_STREAM2: + case PROG_STREAM_DIR: + case ECM_STREAM : + case EMM_STREAM : + case PADDING_STREAM : + case DSM_CC_STREAM : + case ISO13522_STREAM: + case PRIVATE_STREAM1: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + found = 1; + break; + + default: + c++; + break; + } + } else c++; + } + if (c == length-3 && !found){ + if (buf[length-1] == 0x00) *frags = 1; + if (buf[length-2] == 0x00 && + buf[length-1] == 0x00) *frags = 2; + if (buf[length-3] == 0x00 && + buf[length-2] == 0x00 && + buf[length-1] == 0x01) *frags = 3; + return -1; + } + + return c; +} + +void pes_to_ts( uint8_t const *buf, long int length, uint16_t pid, p2t_t *p) +{ + int c,c2,l,add; + int check,rest; + + c = 0; + c2 = 0; + if (p->frags){ + check = 0; + switch(p->frags){ + case 1: + if ( buf[c] == 0x00 && buf[c+1] == 0x01 ){ + check = 1; + c += 2; + } + break; + case 2: + if ( buf[c] == 0x01 ){ + check = 1; + c++; + } + break; + case 3: + check = 1; + } + if(check){ + switch ( buf[c] ) { + + case PROG_STREAM_MAP: + case PRIVATE_STREAM2: + case PROG_STREAM_DIR: + case ECM_STREAM : + case EMM_STREAM : + case PADDING_STREAM : + case DSM_CC_STREAM : + case ISO13522_STREAM: + case PRIVATE_STREAM1: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + p->pes[0] = 0x00; + p->pes[1] = 0x00; + p->pes[2] = 0x01; + p->pes[3] = buf[c]; + p->pos=4; + memcpy(p->pes+p->pos,buf+c,TS_SIZE-4-p->pos); + c += TS_SIZE-4-p->pos; + p_to_t(p->pes,TS_SIZE-4,pid,&p->counter, + p->t_out); + clear_p2t(p); + break; + + default: + c=0; + break; + } + } + p->frags = 0; + } + + if (p->pos){ + c2 = find_pes_header(buf+c,length-c,&p->frags); + if (c2 >= 0 && c2 < TS_SIZE-4-p->pos){ + l = c2+c; + } else l = TS_SIZE-4-p->pos; + memcpy(p->pes+p->pos,buf,l); + c += l; + p->pos += l; + p_to_t(p->pes,p->pos,pid,&p->counter, + p->t_out); + clear_p2t(p); + } + + add = 0; + while (c < length){ + c2 = find_pes_header(buf+c+add,length-c-add,&p->frags); + if (c2 >= 0) { + c2 += c+add; + if (c2 > c){ + p_to_t(buf+c,c2-c,pid,&p->counter, + p->t_out); + c = c2; + clear_p2t(p); + add = 0; + } else add = 1; + } else { + l = length-c; + rest = l % (TS_SIZE-4); + l -= rest; + p_to_t(buf+c,l,pid,&p->counter, + p->t_out); + memcpy(p->pes,buf+c+l,rest); + p->pos = rest; + c = length; + } + } +} + + + +void p_to_t( uint8_t const *buf, long int length, uint16_t pid, uint8_t *counter, + void (*ts_write)(uint8_t const *)) +{ + + int l, pes_start; + uint8_t obuf[TS_SIZE]; + long int c = 0; + pes_start = 0; + if ( length > 3 && + buf[0] == 0x00 && buf[1] == 0x00 && buf[2] == 0x01 ) + switch (buf[3]){ + case PROG_STREAM_MAP: + case PRIVATE_STREAM2: + case PROG_STREAM_DIR: + case ECM_STREAM : + case EMM_STREAM : + case PADDING_STREAM : + case DSM_CC_STREAM : + case ISO13522_STREAM: + case PRIVATE_STREAM1: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + pes_start = 1; + break; + + default: + break; + } + + while ( c < length ){ + memset(obuf,0,TS_SIZE); + if (length - c >= TS_SIZE-4){ + l = write_ts_header(pid, counter, pes_start + , obuf, TS_SIZE-4); + memcpy(obuf+l, buf+c, TS_SIZE-l); + c += TS_SIZE-l; + } else { + l = write_ts_header(pid, counter, pes_start + , obuf, length-c); + memcpy(obuf+l, buf+c, TS_SIZE-l); + c = length; + } + ts_write(obuf); + pes_start = 0; + } +} + + +int write_ps_header(uint8_t *buf, + uint32_t SCR, + long muxr, + uint8_t audio_bound, + uint8_t fixed, + uint8_t CSPS, + uint8_t audio_lock, + uint8_t video_lock, + uint8_t video_bound, + uint8_t stream1, + uint8_t buffer1_scale, + uint32_t buffer1_size, + uint8_t stream2, + uint8_t buffer2_scale, + uint32_t buffer2_size) +{ + ps_packet p; + uint8_t *pts; + long lpts; + init_ps(&p); + + lpts = htonl(SCR); + pts = (uint8_t *) &lpts; + + + p.mpeg = 2; +// SCR = 0 + p.scr[0] = 0x44; + p.scr[1] = 0x00; + p.scr[2] = 0x04; + p.scr[3] = 0x00; + p.scr[4] = 0x04; + p.scr[5] = 0x01; + +// SCR = PTS + p.scr[0] = 0x44 | ((pts[0] >> 3)&0x18) | ((pts[0] >> 4)&0x03); + p.scr[1] = 0x00 | ((pts[0] << 4)&0xF0) | ((pts[1] >> 4)&0x0F); + p.scr[2] = 0x04 | ((pts[1] << 4)&0xF0) | ((pts[2] >> 4)&0x08) + | ((pts[2] >> 5)&0x03); + p.scr[3] = 0x00 | ((pts[2] << 3)&0xF8) | ((pts[3] >> 5)&0x07); + p.scr[4] = 0x04 | ((pts[3] << 3)&0xF8); + p.scr[5] = 0x01; + + p.mux_rate[0] = (uint8_t)(muxr >> 14); + p.mux_rate[1] = (uint8_t)(0xff & (muxr >> 6)); + p.mux_rate[2] = (uint8_t)(0x03 | ((muxr & 0x3f) << 2)); + + p.stuff_length = 0xF8; + + if (stream1 && stream2){ + p.sheader_llength[0] = 0x00; + p.sheader_llength[1] = 0x0c; + + setl_ps(&p); + + p.rate_bound[0] = (uint8_t)(0x80 | (muxr >>15)); + p.rate_bound[1] = (uint8_t)(0xff & (muxr >> 7)); + p.rate_bound[2] = (uint8_t)(0x01 | ((muxr & 0x7f)<<1)); + + + p.audio_bound = (uint8_t)((audio_bound << 2)|(fixed << 1)|CSPS); + p.video_bound = (uint8_t)((audio_lock << 7)| + (video_lock << 6)|0x20|video_bound); + p.reserved = (uint8_t)(0xFF >> 1); + + p.data[0] = stream2; + p.data[1] = (uint8_t) (0xc0 | (buffer2_scale << 5) | + (buffer2_size >> 8)); + p.data[2] = (uint8_t) (buffer2_size & 0xff); + p.data[3] = stream1; + p.data[4] = (uint8_t) (0xc0 | (buffer1_scale << 5) | + (buffer1_size >> 8)); + p.data[5] = (uint8_t) (buffer1_size & 0xff); + + cwrite_ps(buf, &p, PS_HEADER_L2); + kill_ps(&p); + return PS_HEADER_L2; + } else { + cwrite_ps(buf, &p, PS_HEADER_L1); + kill_ps(&p); + return PS_HEADER_L1; + } +} + + + +#define MAX_BASE 80 +#define MAX_PATH 256 +#define MAX_EXT 10 + +int break_up_filename(char *name, char *base_name, char *path, char *ext) +{ + int l,i,sstop,sstart; + + l = strlen(name); + sstop = l; + sstart = -1; + for( i= l-1; i >= 0; i--){ + if (sstop == l && name[i] == '.') sstop = i; + if (sstart<0 && name[i] == '/') sstart = i+1; + } + if (sstart < 0) sstart = 0; + if (sstop-sstart < MAX_BASE){ + strncpy(base_name, name+sstart, sstop-sstart); + base_name[sstop-sstart]=0; + if(sstart > 0){ + if( l - sstop + sstart < MAX_PATH){ + strncpy(path, name, sstart); + path[sstart] = 0; + } else { + fprintf(stderr,"PATH too long\n"); + return -1; + } + + } else { + strcpy(path, "./"); + } + + if(sstop < l){ + if( l - sstop -1 < MAX_EXT){ + strncpy(ext, name+sstop+1, l-sstop-1); + ext[l-sstop-1]=0; + } else { + fprintf(stderr,"Extension too long\n"); + return -1; + } + + } else { + strcpy(ext, ""); + } + + } else { + fprintf(stderr,"Name too long\n"); + return -1; + } +/* + printf("%d %d\n",sstart, sstop); + printf("%s %d\n",name, strlen(name)); + printf("%s %d\n",base_name, strlen(base_name)); + printf("%s %d\n",path,strlen(path)); + printf("%s %d\n",ext,strlen(ext)); +*/ + return 0; +} + + +int seek_mpg_start(uint8_t *buf, int size) +{ + int found = 0; + int c=0; + int seq = 0; + int mpeg = 0; + int mark = 0; + + while ( !seq ){ + while (found != 4){ + switch (found) { + case 0: + if ( buf[c] == 0x00 ) found++; + c++; + break; + case 1: + if ( buf[c] == 0x00 ) found++; + else found = 0; + c++; + break; + case 2: + if ( buf[c] == 0x01 ) found++; + else found = 0; + if ( buf[c] == 0x00 ) found = 2; + c++; + break; + + case 3: + if ( (buf[c] & 0xe0) == 0xe0 ) found++; + else found = 0; + c++; + break; + } + if (c >= size) return -1; + } + + if (found == 4){ + mark = c-4; + c+=2; + if (c >= size) return -1; + + if ( (buf[c] & 0xC0) == 0x80 ){ + mpeg = 2; + c += 2; + if (c >= size) return -1; + c += buf[c]+1; + if (c >= size) return -1; + } else { + mpeg = 1; + while( buf[c] == 0xFF ) { + c++; + if (c >= size) return -1; + } + if ( (buf[c] & 0xC0) == 0x40) c+=2; + if (c >= size) return -1; + if ( (buf[c] & 0x30) ){ + if ( (buf[c] & 0x30) == 0x20) c+=5; + else c+=10; + } else c++; + if (c >= size) return -1; + } + + if ( buf[c] == 0x00 && + buf[c+1] == 0x00 && + buf[c+2] == 0x01 && + buf[c+3] == 0xB3 ) + seq = 1; + } + found = 0; + } + + return size-mark; +} + + +void write_mpg(int fstart, uint64_t length, int fdin, int fdout) +{ +// uint8_t mpeg_end[4] = { 0x00, 0x00, 0x01, 0xB9 }; + uint8_t *buf; + uint64_t l=0; + uint64_t count = 0; + struct stat sb; + int buf_size; + + fstat (fdout, &sb); + buf_size = sb.st_blksize; + + buf = (uint8_t *) alloca (buf_size + sizeof (int)); + + lseek(fdin, fstart, SEEK_SET); + + while ( count < length && (l = read(fdin,buf,buf_size)) >= 0){ + if (l > 0) count+=l; + write(fdout,buf,l); + printf("written %02.2f%%\r",(100.*count)/length); + } + printf("\n"); + + //write( fdout, mpeg_end, 4); +} + + +#define CHECKBUF (1024*1024) +#define ONE_GIG (1024UL*1024UL*1024UL) +void split_mpg(char *name, uint64_t size) +{ + char base_name[MAX_BASE]; + char path[MAX_PATH]; + char ext[MAX_EXT]; + char new_name[256]; + uint8_t buf[CHECKBUF]; + int fdin; + int fdout; + uint64_t length = 0; + uint64_t last; + int i; + int mark, csize; + struct stat sb; + + if (break_up_filename(name,base_name,path,ext) < 0) exit(1); + + + if ( (fdin = open(name, O_RDONLY|O_LARGEFILE)) < 0){ + fprintf(stderr,"Can't open %s\n",name); + exit(1); + } + + fstat (fdin, &sb); + + length = sb.st_size; + if ( length < ONE_GIG ) + printf("Filelength = %2.2f MB\n", length/1024./1024.); + else + printf("Filelength = %2.2f GB\n", length/1024./1024./1024.); + + if ( length < size ) length = size; + + printf("Splitting %s into Files with size <= %2.2f MB\n",name, + size/1024./1024.); + + csize = CHECKBUF; + read(fdin, buf, csize); + if ( (mark = seek_mpg_start(buf,csize)) < 0){ + fprintf(stderr,"Couldn't find sequence header\n"); + exit(1); + } + + last = csize-mark; + + for ( i = 0 ; i < length/size; i++){ + csize = CHECKBUF; + + if (csize > length-last) csize = length-last; + lseek(fdin, last+size-csize, SEEK_SET); + read(fdin, buf, csize); + if ( (mark = seek_mpg_start(buf,csize)) < 0){ + fprintf(stderr,"Couldn't find sequence header\n"); + exit(1); + } + + sprintf(new_name,"%s-%03d.%s",base_name,i,ext); + printf("writing %s\n",new_name); + + if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC + |O_LARGEFILE, + S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP| + S_IROTH|S_IWOTH)) < 0){ + fprintf(stderr,"Can't open %s\n",new_name); + exit(1); + } + write_mpg(last, size-mark, fdin, fdout); + last = last + size - mark; + } + sprintf(new_name,"%s-%03d.%s",base_name,i,ext); + printf("writing %s\n",new_name); + + if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC + |O_LARGEFILE, + S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP| + S_IROTH|S_IWOTH)) < 0){ + fprintf(stderr,"Can't open %s\n",new_name); + exit(1); + } + write_mpg(last, length-last, fdin, fdout); +} + + + + +void cut_mpg(char *name, uint64_t size) +{ + char base_name[MAX_BASE]; + char path[MAX_PATH]; + char ext[MAX_EXT]; + char new_name[256]; + uint8_t buf[CHECKBUF]; + int fdin; + int fdout; + uint64_t length = 0; + uint64_t last; + int mark, csize; + struct stat sb; + + if (break_up_filename(name,base_name,path,ext) < 0) exit(1); + + + if ( (fdin = open(name, O_RDONLY|O_LARGEFILE)) < 0){ + fprintf(stderr,"Can't open %s\n",name); + exit(1); + } + + fstat (fdin, &sb); + + length = sb.st_size; + if ( length < ONE_GIG ) + printf("Filelength = %2.2f MB\n", length/1024./1024.); + else + printf("Filelength = %2.2f GB\n", length/1024./1024./1024.); + + if ( length < size ) length = size; + + printf("Splitting %s into 2 Files with length %.2f MB and %.2f MB\n", + name, size/1024./1024., (length-size)/1024./1024.); + + csize = CHECKBUF; + read(fdin, buf, csize); + if ( (mark = seek_mpg_start(buf,csize)) < 0){ + fprintf(stderr,"Couldn't find sequence header\n"); + exit(1); + } + + last = csize-mark; + + if (csize > length-last) csize = length-last; + lseek(fdin, last+size-csize, SEEK_SET); + read(fdin, buf, csize); + if ( (mark = seek_mpg_start(buf,csize)) < 0){ + fprintf(stderr,"Couldn't find sequence header\n"); + exit(1); + } + + sprintf(new_name,"%s-1.%s",base_name,ext); + printf("writing %s\n",new_name); + + if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC + |O_LARGEFILE, + S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP| + S_IROTH|S_IWOTH)) < 0){ + fprintf(stderr,"Can't open %s\n",new_name); + exit(1); + } + write_mpg(last, size-mark, fdin, fdout); + last = last + size - mark; + + sprintf(new_name,"%s-2.%s",base_name,ext); + printf("writing %s\n",new_name); + + if ( (fdout = open(new_name,O_WRONLY|O_CREAT|O_TRUNC + |O_LARGEFILE, + S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP| + S_IROTH|S_IWOTH)) < 0){ + fprintf(stderr,"Can't open %s\n",new_name); + exit(1); + } + write_mpg(last, length-last, fdin, fdout); +} + + + + +void write_all (int fd, const char *data, int length) +{ + int r; + + while (length) { + if ((r = write(fd, data, length)) > 0) { + data += r; + length -= r; + } + } +} + + + +void read_all (int fd, char *data, int length) +{ + int c = 0; + + while(1) { + if( read(fd, data+c, 1) == 1) { + c++; + if(data[c-1] == '\n') { + data[c] = 0; + break; + } + } + else { + fprintf (stderr, "Error reading socket\n"); + exit(1); + } + } +} + + + +char *url2host (char *url, char **name, uint32_t *ip, uint32_t *port) +{ + char *murl; + struct hostent *hoste; + struct in_addr haddr; + int found_ip = 1; + + if (!(strncmp(url, "http://", 7))) + url += 7; + + *name = strdup(url); + if (!(*name)) { + *name = NULL; + return (NULL); + } + + murl = url; + while (*murl && *murl != ':' && *murl != '/') { + if ((*murl < '0' || *murl > '9') && *murl != '.') + found_ip = 0; + murl++; + } + + (*name)[murl - url] = 0; + if (found_ip) { + if ((*ip = inet_addr(*name)) == INADDR_NONE) + return (NULL); + } else { + if (!(hoste = gethostbyname(*name))) + return (NULL); + memcpy (&haddr, hoste->h_addr, sizeof(haddr)); + *ip = haddr.s_addr; + } + + if (!*murl || *murl == '/') { + *port = 80; + return (murl); + } + *port = atoi(++murl); + + while (*murl && *murl != '/') + murl++; + return (murl); +} + +#define ACCEPT "Accept: video/mpeg, video/x-mpegurl, */*\r\n" + +int http_open (char *url) +{ + char purl[1024], *host, req[1024], *sptr; + uint32_t ip; + uint32_t port; + int sock; + int reloc, relocnum = 0; + struct sockaddr_in server; + int mfd; + + strncpy (purl, url, 1023); + purl[1023] = '\0'; + + do { + host = NULL; + strcpy (req, "GET "); + if (!(sptr = url2host(purl, &host, &ip, &port))) { + fprintf (stderr, "Unknown host\n"); + exit (1); + } + strcat (req, sptr); + sprintf (req + strlen(req), + " HTTP/1.0\r\nUser-Agent: %s/%s\r\n", + "whatever", "you want"); + if (host) { + sprintf(req + strlen(req), + "Host: %s:%u\r\n", host, port); + free (host); + } + + strcat (req, ACCEPT); + strcat (req, "\r\n"); + + server.sin_port = htons(port); + server.sin_family = AF_INET; + server.sin_addr.s_addr = ip; + + if ((sock = socket(PF_INET, SOCK_STREAM, 6)) < 0) { + perror ("socket"); + exit (1); + } + + if (connect(sock, (struct sockaddr *)&server, + sizeof(server))) { + perror ("connect"); + exit (1); + } + + write_all (sock, req, strlen(req)); + if (!(mfd = fileno(fdopen(sock, "rb")))) { + perror ("open"); + exit (1); + } + reloc = 0; + purl[0] = '\0'; + read_all (mfd, req, 1023); + if ((sptr = strchr(req, ' '))) { + switch (sptr[1]) { + case '2': + break; + case '3': + reloc = 1; + default: + fprintf (stderr, "HTTP req failed:%s", + sptr+1); + exit (1); + } + } + do { + read_all (mfd,req, 1023); + if (!strncmp(req, "Location:", 9)) + strncpy (purl, req+10, 1023); + } while (req[0] != '\r' && req[0] != '\n'); + } while (reloc && purl[0] && relocnum++ < 3); + if (reloc) { + fprintf (stderr, "Too many HTTP relocations.\n"); + exit (1); + } + + return sock; +} + +extern int errno; +const char * strerrno (void) +{ + return strerror(errno); +} diff --git a/libdvbmpeg/ctools.h b/libdvbmpeg/ctools.h new file mode 100644 index 0000000..a7b0271 --- /dev/null +++ b/libdvbmpeg/ctools.h @@ -0,0 +1,404 @@ +/* + * dvb-mpegtools for the Siemens Fujitsu DVB PCI card + * + * Copyright (C) 2000, 2001 Marcus Metzler + * for convergence integrated media GmbH + * Copyright (C) 2002, 2003 Marcus Metzler + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + + * The author can be reached at mocm@metzlerbros.de + + */ + +#include +#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); + + + int seek_mpg_start(uint8_t *buf, int size); + + + void split_mpg(char *name, uint64_t size); + void cut_mpg(char *name, uint64_t size); + int http_open (char *url); + ssize_t save_read(int fd, void *buf, size_t count); + + const char * strerrno(void); +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /*_CTOOLS_H_*/ diff --git a/libdvbmpeg/devices.hh b/libdvbmpeg/devices.hh new file mode 100644 index 0000000..02c62cd --- /dev/null +++ b/libdvbmpeg/devices.hh @@ -0,0 +1,310 @@ +#ifndef _channel_hh +#define _channel_hh + +using namespace std; +#include + +#include +#include +#include + +#include +#include +#include + +#include "DVB.hh" + +#define MAXNAM 80 +#define MAXKEY 15 + +const int maxname=80; +const int MAXAPIDS=32; +const uint32_t UNSET=0xffffffff; +const uint16_t NOID=0xffff; +const uint16_t NOPID=0xffff; + +class Transponder { +public: + uint16_t id; + uint16_t onid; + uint16_t satid; + int type; + char name[maxname+1]; + uint32_t freq; + int pol; + int qam; + uint32_t srate; + int fec; + int band; + int hp_rate; + int lp_rate; + int mod; + int transmode; + int guard; + int hierarchy; + + struct Sat *sat; + + Transponder() { + name[0]='\0'; + id = NOID; + onid = NOID; + satid = NOID; + type = 0; + } + + friend ostream &operator<<(ostream &stream, Transponder &x); + friend istream &operator>>(istream &stream, Transponder &x); +}; + +class Sat { +public: + uint16_t id; + char name[maxname+1]; + unsigned int lnbid; + struct Lnb *lnb; + unsigned int rotorid; + unsigned int fmin; + unsigned int fmax; + + Sat() { + id=NOID; + name[0]='\0'; + lnb=NULL; + rotorid=NOID; + lnbid=NOID; + fmin=fmax=0; + }; + int set(int sid, char *sname, int slnbid, int srotorid) { + return 0; + }; + + friend ostream &operator<<(ostream &stream, Sat &x); + friend istream &operator>>(istream &stream, Sat &x); +}; + + +class Lnb { +public: + Sat *sat; + uint16_t id; + struct DVB *dvbd; + char name[maxname+1]; + int type; + unsigned int lof1; + unsigned int lof2; + unsigned int slof; + int diseqcnr; + uint16_t diseqcid; + uint16_t swiid; + + + void cpy (const Lnb &olnb){ + this->id=olnb.id; + this->type=olnb.type; + this->lof1=olnb.lof1; + this->lof2=olnb.lof2; + this->slof=olnb.slof; + this->diseqcnr=olnb.diseqcnr; + this->diseqcid=olnb.diseqcid; + this->swiid=olnb.swiid; + strncpy(this->name,olnb.name,maxname); + } + + void init(int t, uint l1, uint l2, uint sl, + int dnr, int disid, int sw) { + type=t; + lof1=l1; + lof2=l2; + slof=sl; + diseqcnr=dnr; + diseqcid=disid; + swiid=sw; + dvbd=0; + name[0]='\0'; + } + + Lnb () { + lof1=lof2=slof=0; + swiid=NOID; + diseqcid=NOID; + diseqcnr=-1; + name[0]='\0'; + } + + Lnb (const Lnb &olnb){ + cpy(olnb); + } + + + + friend ostream &operator<<(ostream &stream, Lnb &x); + friend istream &operator>>(istream &stream, Lnb &x); +}; + +struct diseqcmsg { + int burst; + int len; + unsigned char msg[8]; +}; + +class DiSEqC { +public: + uint16_t id; + char name[maxname+1]; + diseqcmsg msgs[4]; + + friend ostream &operator<<(ostream &stream, DiSEqC &x); + friend istream &operator>>(istream &stream, DiSEqC &x); +}; + +class Rotor { +public: + uint16_t id; + char name[maxname+1]; + diseqcmsg msgs[4]; + + friend ostream &operator<<(ostream &stream, Rotor &x); + friend istream &operator>>(istream &stream, Rotor &x); +}; + +class Switch { +public: + uint16_t id; + int switchid; + char name[maxname+1]; + diseqcmsg msg; + + friend ostream &operator<<(ostream &stream, Switch &x); + friend istream &operator>>(istream &stream, Switch &x); +}; + +class Network { +public: + uint16_t id; + char name[maxname+1]; + + friend ostream &operator<<(ostream &stream, Network &x); + friend istream &operator>>(istream &stream, Network &x); +}; + +class Bouquet { +public: + uint16_t id; + char name[maxname+1]; + + friend ostream &operator<<(ostream &stream, Bouquet &x); + friend istream &operator>>(istream &stream, Bouquet &x); +}; + + +#define MAX_ECM 16 +#define MAX_ECM_DESC 256 +typedef struct ecm_struct { + int num; + uint16_t sysid[MAX_ECM]; + uint16_t pid[MAX_ECM]; + uint16_t length[MAX_ECM]; + uint8_t data[MAX_ECM*MAX_ECM_DESC]; +} ecm_t; + + + +class Channel{ +public: + Channel *next; + uint32_t id; + char name[maxname+1]; + int32_t type; + int checked; + + uint16_t pnr; + uint16_t vpid; + uint16_t apids[MAXAPIDS]; + char apids_name[MAXAPIDS*4]; + int32_t apidnum; + int last_apidn; + uint16_t ac3pid; + uint16_t ttpid; + uint16_t pmtpid; + uint16_t pcrpid; + uint16_t casystem; + uint16_t capid; + + ecm_t ecm; + int (*ecm_callback)(Channel *chan); + + int has_eit; + int pres_follow; + + uint16_t satid; + uint16_t tpid; + uint16_t onid; + uint16_t bid; + int8_t eit_ver_n; + int8_t eit_ver_c; + + void clearall(void) { + id=UNSET; + name[0]='\0'; + type=0; + checked = 0; + has_eit = -1; + pres_follow = -1; + eit_ver_c = -1; + eit_ver_n = -1; + + pnr=NOPID; + vpid=NOPID; + memset(apids, 0, sizeof(uint16_t)*MAXAPIDS); + memset(apids_name, 0, sizeof(char)*MAXAPIDS*4); + apidnum=0; + last_apidn=-1; + ac3pid=NOPID; + ttpid=NOPID; + pmtpid=NOPID; + pcrpid=NOPID; + capid=NOPID; + + satid=NOID; + tpid=NOID; + onid=NOID; + bid=NOID; + ecm_callback = NULL; + memset(&ecm,0, sizeof(ecm_t)); + }; + + Channel() { + clearall(); + } + + Channel(int cid, char *nam, int ty, int prognr, + int vid, int aid, int tid) { + int l=strlen(nam); + + clearall(); + if (l>maxname){ + cerr << "" << endl; + l=maxname; + } + strncpy(name, nam, l); + name[l]='\0'; + type=ty; + pnr=prognr; + vpid=vid; + apids[0]=aid; + } + +#ifdef DEBUG + ~Channel(){ + cerr <<"Channel " << name << " destroyed" << endl; + } +#endif + + friend ostream &operator<<(ostream &stream, Channel &x); + friend istream &operator>>(istream &stream, Channel &x); +}; + +int findkey(char *name, char *keys[]); +void getname(char *name,istream &ins); +#endif /*channel.h*/ diff --git a/libdvbmpeg/osd.hh b/libdvbmpeg/osd.hh new file mode 100644 index 0000000..9c6b530 --- /dev/null +++ b/libdvbmpeg/osd.hh @@ -0,0 +1,84 @@ +#ifndef _OSD_HH_ +#define _OSD_HH_ + +extern "C" { +#include "OSD.h" +} +struct OSD { + int dev; + + void init(int d) { + dev=d; + } + int Open(int x0, int y0, int x1, int y1, int BitPerPixel, int mix, int win) { + if (OSDSetWindow(dev, win)) + return -1; + return OSDOpen(dev, x0, y0, x1, y1, BitPerPixel, mix); + } + int Open(int x0, int y0, int x1, int y1, int BitPerPixel, int mix) { + return OSDOpen(dev, x0, y0, x1, y1, BitPerPixel, mix); + } + int Close(int win) { + if (OSDSetWindow(dev, win)) + return -1; + return OSDClose(dev); + } + int Close(void) { + return OSDClose(dev); + } + int Show(void) { + return OSDShow(dev); + } + int Hide(void) { + return OSDHide(dev); + } + int Clear(void) { + return OSDClear(dev); + } + int Fill(int color) { + return OSDFill(dev, color); + } + int SetColor(int color, int r, int g, int b, int op) { + return OSDSetColor(dev, color, r, g, b, op); + } + int Text(int x, int y, int size, int color, const char *text) { + return OSDText(dev, x, y, size, color, text); + } + int SetPalette(int first, int last, unsigned char *data) { + return OSDSetPalette(dev, first, last, data); + + } + int SetTrans(int trans) { + return OSDSetTrans(dev, trans); + + } + int SetPixel(int dev, int x, int y, unsigned int color) { + return OSDSetPixel(dev, x, y, color); + } + int GetPixel(int dev, int x, int y) { + return OSDGetPixel(dev, x, y); + } + int SetRow(int x, int y, int x1, unsigned char *data) { + return OSDSetRow(dev, x, y, x1, data); + } + int SetBlock(int x, int y, int x1, int y1, int inc, unsigned char *data) { + return OSDSetBlock(dev, x, y, x1, y1, inc, data); + } + int FillRow(int x, int y, int x1, int color) { + return OSDFillRow(dev, x, y, x1, color); + } + int FillBlock(int x, int y, int x1, int y1, int color) { + return OSDFillBlock(dev, x, y, x1, y1, color); + } + int Line(int x, int y, int x1, int y1, int color) { + return OSDLine(dev, x, y, x1, y1, color); + } + int Query() { + return OSDQuery(dev); + } + int SetWindow(int win) { + return OSDSetWindow(dev, win); + } +}; + +#endif diff --git a/libdvbmpeg/remux.c b/libdvbmpeg/remux.c new file mode 100644 index 0000000..6f8a44f --- /dev/null +++ b/libdvbmpeg/remux.c @@ -0,0 +1,1215 @@ +/* + * dvb-mpegtools for the Siemens Fujitsu DVB PCI card + * + * Copyright (C) 2000, 2001 Marcus Metzler + * for convergence integrated media GmbH + * Copyright (C) 2002 Marcus Metzler + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + + * The author can be reached at mocm@metzlerbros.de, + */ + +#include "remux.h" + +unsigned int bitrates[3][16] = +{{0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0}, + {0,32,48,56,64,80,96,112,128,160,192,224,256,320,384,0}, + {0,32,40,48,56,64,80,96,112,128,160,192,224,256,320,0}}; + +uint32_t freq[4] = {441, 480, 320, 0}; +static uint32_t samples[4] = { 384, 1152, 0, 0}; +char *frames[3] = {"I-Frame","P-Frame","B-Frame"}; + + +void copy_ptslm(PTS_List *a, PTS_List *b) +{ + a->pos = b->pos; + a->PTS = b->PTS; + a->dts = b->dts; + a->spos = b->spos; +} + +void clear_ptslm(PTS_List *a) +{ + a->pos = 0; + a->PTS = 0; + a->dts = 0; + a->spos = 0; +} + +void init_ptsl(PTS_List *ptsl) +{ + int i; + for (i=0;i< MAX_PTS;i++){ + clear_ptslm(&ptsl[i]); + } +} + +int del_pts(PTS_List *ptsl, int pos, int nr) +{ + int i; + int del = 0; + + for( i = 0; i < nr-1; i++){ + if(pos > ptsl[i].pos && pos >= ptsl[i+1].pos) del++; + } + + if(del) + for( i = 0; i < nr-del; i++){ + copy_ptslm(&ptsl[i], &ptsl[i+del]); + } + + return nr-del; +} + +int del_ptss(PTS_List *ptsl, int pts, int *nb) +{ + int i; + int del = 0; + int sum = 0; + int nr = *nb; + + for( i = 0; i < nr; i++){ + if(pts > ptsl[i].PTS){ + del++; + sum += ptsl[i].pos; + } + } + + if(del) + for( i = 0; i < nr-del; i++){ + copy_ptslm(&ptsl[i], &ptsl[i+del]); + } + + *nb = nr-del; + return sum; +} + +int add_pts(PTS_List *ptsl, uint32_t pts, int pos, int spos, int nr, uint32_t dts) +{ + int i; + + for ( i=0;i < nr; i++) if (spos && ptsl[i].pos == pos) return nr; + if (nr == MAX_PTS) { + nr = del_pts(ptsl, ptsl[1].pos+1, nr); + } else nr++; + i = nr-1; + + ptsl[i].pos = pos; + ptsl[i].spos = spos; + ptsl[i].PTS = pts; + ptsl[i].dts = dts; + return nr; +} + +void add_vpts(Remux *rem, uint8_t *pts) +{ + uint32_t PTS = trans_pts_dts(pts); + rem->vptsn = add_pts(rem->vpts_list, PTS, rem->vwrite, rem->awrite, + rem->vptsn, PTS); +} + +void add_apts(Remux *rem, uint8_t *pts) +{ + uint32_t PTS = trans_pts_dts(pts); + rem->aptsn = add_pts(rem->apts_list, PTS, rem->awrite, rem->vwrite, + rem->aptsn, PTS); +} + +void del_vpts(Remux *rem) +{ + rem->vptsn = del_pts(rem->vpts_list, rem->vread, rem->vptsn); +} + +void del_apts(Remux *rem) +{ + rem->aptsn = del_pts(rem->apts_list, rem->aread, rem->aptsn); +} + + +void copy_framelm(FRAME_List *a, FRAME_List *b) +{ + a->type = b->type; + a->pos = b->pos; + a->FRAME = b->FRAME; + a->time = b->time; + a->pts = b->pts; + a->dts = b->dts; +} + +void clear_framelm(FRAME_List *a) +{ + a->type = 0; + a->pos = 0; + a->FRAME = 0; + a->time = 0; + a->pts = 0; + a->dts = 0; +} + +void init_framel(FRAME_List *framel) +{ + int i; + for (i=0;i< MAX_FRAME;i++){ + clear_framelm(&framel[i]); + } +} + +int del_frame(FRAME_List *framel, int pos, int nr) +{ + int i; + int del = 0; + + for( i = 0; i < nr-1; i++){ + if(pos > framel[i].pos && pos >= framel[i+1].pos) del++; + } + + if(del) + for( i = 0; i < nr-del; i++){ + copy_framelm(&framel[i], &framel[i+del]); + } + + return nr-del; +} + +int add_frame(FRAME_List *framel, uint32_t frame, int pos, int type, int nr, + uint32_t time, uint32_t pts, uint32_t dts) +{ + int i; + + if (nr == MAX_FRAME) { + nr = del_frame(framel, framel[1].pos+1, nr); + } else nr++; + i = nr-1; + + framel[i].type = type; + framel[i].pos = pos; + framel[i].FRAME = frame; + framel[i].time = time; + framel[i].pts = pts; + framel[i].dts = dts; + return nr; +} + +void add_vframe(Remux *rem, uint32_t frame, long int pos, int type, int time, + uint32_t pts, uint32_t dts) +{ + rem->vframen = add_frame(rem->vframe_list, frame, pos, type, + rem->vframen, time, pts, dts); +} + +void add_aframe(Remux *rem, uint32_t frame, long int pos, uint32_t pts) +{ + rem->aframen = add_frame(rem->aframe_list, frame, pos, 0, + rem->aframen, 0, pts, pts); +} + +void del_vframe(Remux *rem) +{ + rem->vframen = del_frame(rem->vframe_list, rem->vread, rem->vframen); +} + +void del_aframe(Remux *rem) +{ + rem->aframen = del_frame(rem->aframe_list, rem->aread, rem->aframen); +} + + +void printpts(uint32_t pts) +{ + fprintf(stderr,"%2d:%02d:%02d.%03d", + (int)(pts/90000.)/3600, + ((int)(pts/90000.)%3600)/60, + ((int)(pts/90000.)%3600)%60, + (((int)(pts/90.)%3600000)%60000)%1000 + ); +} + + +void find_vframes( Remux *rem, uint8_t *buf, int l) +{ + int c = 0; + int type; + uint32_t time = 0; + int hour; + int min; + int sec; + uint64_t pts=0; + uint64_t dts=0; + uint32_t tempref = 0; + + while ( c < l - 6){ + if (buf[c] == 0x00 && + buf[c+1] == 0x00 && + buf[c+2] == 0x01 && + buf[c+3] == 0xB8) { + c += 4; + hour = (int)((buf[c]>>2)& 0x1F); + min = (int)(((buf[c]<<4)& 0x30)| + ((buf[c+1]>>4)& 0x0F)); + sec = (int)(((buf[c+1]<<3)& 0x38)| + ((buf[c+2]>>5)& 0x07)); + + time = 3600*hour + 60*min + sec; + if ( rem->time_off){ + time = (uint32_t)((uint64_t)time - rem->time_off); + hour = time/3600; + min = (time%3600)/60; + sec = (time%3600)%60; + /* + buf[c] |= (hour & 0x1F) << 2; + buf[c] |= (min & 0x30) >> 4; + buf[c+1] |= (min & 0x0F) << 4; + buf[c+1] |= (sec & 0x38) >> 3; + buf[c+2] |= (sec & 0x07) >> 5;*/ + } + rem->group++; + rem->groupframe = 0; + } + if ( buf[c] == 0x00 && + buf[c+1] == 0x00 && + buf[c+2] == 0x01 && + buf[c+3] == 0x00) { + c += 4; + tempref = (buf[c+1]>>6) & 0x03; + tempref |= buf[c] << 2; + + type = ((buf[c+1]&0x38) >>3); + if ( rem->video_info.framerate){ + pts = ((uint64_t)rem->vframe + tempref + 1 + - rem->groupframe ) * 90000ULL + /rem->video_info.framerate + + rem->vpts_off; + dts = (uint64_t)rem->vframe * 90000ULL/ + rem->video_info.framerate + + rem->vpts_off; + + +fprintf(stderr,"MYPTS:"); +printpts((uint32_t)pts-rem->vpts_off); + fprintf(stderr," REALPTS:"); + printpts(rem->vpts_list[rem->vptsn-1].PTS-rem->vpts_off); + fprintf(stderr," DIFF:"); + printpts(pts-(uint64_t)rem->vpts_list[rem->vptsn-1].PTS); +// fprintf(stderr," DIST: %4d",-rem->vpts_list[rem->vptsn-1].pos+(rem->vwrite+c-4)); + //fprintf(stderr," ERR: %3f",(double)(-rem->vpts_list[rem->vptsn-1].PTS+pts)/(rem->vframe+1)); + fprintf(stderr,"\r"); + + + + rem->vptsn = add_pts(rem->vpts_list,(uint32_t)pts + ,rem->vwrite+c-4, + rem->awrite, + rem->vptsn, + (uint32_t)dts); + + + + } + rem->vframe++; + rem->groupframe++; + add_vframe( rem, rem->vframe, rem->vwrite+c, type, + time, pts, dts); + } else c++; + } +} + +void find_aframes( Remux *rem, uint8_t *buf, int l) +{ + int c = 0; + uint64_t pts = 0; + int sam; + uint32_t fr; + + + while ( c < l - 2){ + if ( buf[c] == 0xFF && + (buf[c+1] & 0xF8) == 0xF8) { + c += 2; + if ( rem->audio_info.layer >= 0){ + sam = samples[3-rem->audio_info.layer]; + fr = freq[rem->audio_info.frequency] ; + + pts = ( (uint64_t)rem->aframe * sam * 900ULL)/fr + + rem->apts_off; + + +fprintf(stderr,"MYPTS:"); +printpts((uint32_t)pts-rem->apts_off); + fprintf(stderr," REALPTS:"); + printpts(rem->apts_list[rem->aptsn-1].PTS-rem->apts_off); + fprintf(stderr," DIFF:"); + printpts((uint32_t)((uint64_t)rem->apts_list[rem->aptsn-1].PTS-pts)); +// fprintf(stderr," DIST: %4d",-rem->apts_list[rem->aptsn-1].pos+(rem->awrite+c-2)); + fprintf(stderr,"\r"); + + rem->aptsn = add_pts(rem->apts_list,(uint32_t)pts + ,rem->awrite+c-2, + rem->vwrite, + rem->aptsn, + (uint32_t)pts); + } + + rem->aframe++; + add_aframe( rem, rem->aframe, rem->awrite+c, pts); + + } else c++; + } +} + +int refill_buffy(Remux *rem) +{ + pes_packet pes; + int count = 0; + int acount, vcount; + ringbuffy *vbuf = &rem->vid_buffy; + ringbuffy *abuf = &rem->aud_buffy; + int fin = rem->fin; + + acount = abuf->size-ring_rest(abuf); + vcount = vbuf->size-ring_rest(vbuf); + + + while ( acount > MAX_PLENGTH && vcount > MAX_PLENGTH && count < 10){ + int neof; + count++; + init_pes(&pes); + if ((neof = read_pes(fin,&pes)) <= 0) return -1; + switch(pes.stream_id){ + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + rem->apes++; + if( rem->audio_info.layer < 0 && + (pes.flags2 & PTS_DTS) ) + add_apts(rem, pes.pts); + find_aframes( rem, pes.pes_pckt_data, pes.length); + ring_write(abuf,(char *)pes.pes_pckt_data,pes.length); + rem->awrite += pes.length; + break; + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + rem->vpes++; + if( !rem->video_info.framerate && + (pes.flags2 & PTS_DTS) ) + add_vpts(rem, pes.pts); + + find_vframes( rem, pes.pes_pckt_data, pes.length); + + ring_write(vbuf,(char *)pes.pes_pckt_data,pes.length); + rem->vwrite += pes.length; + break; + } + acount = abuf->size-ring_rest(abuf); + vcount = vbuf->size-ring_rest(vbuf); + kill_pes(&pes); + } + if (count < 10) return 0; + return 1; +} + +int vring_read( Remux *rem, uint8_t *buf, int l) +{ + int c = 0; + int r = 0; + + if (ring_rest(&rem->vid_buffy) <= l) + r = refill_buffy(rem); + if (r) return -1; + + c = ring_read(&rem->vid_buffy, (char *) buf, l); + rem->vread += c; + del_vpts(rem); + del_vframe(rem); + return c; +} + +int aring_read( Remux *rem, uint8_t *buf, int l) +{ + int c = 0; + int r = 0; + + if (ring_rest(&rem->aud_buffy) <= l) + r = refill_buffy(rem); + if (r) return -1; + + c = ring_read(&rem->aud_buffy, (char *)buf, l); + rem->aread += c; + del_apts(rem); + del_aframe(rem); + return c; +} + +int vring_peek( Remux *rem, uint8_t *buf, int l, long off) +{ + int c = 0; + + if (ring_rest(&rem->vid_buffy) <= l) + refill_buffy(rem); + + c = ring_peek(&rem->vid_buffy, (char *) buf, l, off); + return c; +} + +int aring_peek( Remux *rem, uint8_t *buf, int l, long off) +{ + int c = 0; + + if (ring_rest(&rem->aud_buffy) <= l) + refill_buffy(rem); + + c = ring_peek(&rem->aud_buffy, (char *)buf, l, off); + return c; +} + + +int get_video_info(Remux *rem) +{ + uint8_t buf[12]; + uint8_t *headr; + int found = 0; + int sw; + long off = 0; + int form = -1; + ringbuffy *vid_buffy = &rem->vid_buffy; + VideoInfo *vi = &rem->video_info; + + while (found < 4 && ring_rest(vid_buffy)){ + uint8_t b[3]; + + vring_peek( rem, b, 4, 0); + if ( b[0] == 0x00 && b[1] == 0x00 && b[2] == 0x01 + && b[3] == 0xb3) found = 4; + else { + off++; + vring_read( rem, b, 1); + } + } + rem->vframe = rem->vframen-1; + + if (! found) return -1; + buf[0] = 0x00; buf[1] = 0x00; buf[2] = 0x01; buf[3] = 0xb3; + headr = buf+4; + if(vring_peek(rem, buf, 12, 0) < 12) return -1; + + vi->horizontal_size = ((headr[1] &0xF0) >> 4) | (headr[0] << 4); + vi->vertical_size = ((headr[1] &0x0F) << 8) | (headr[2]); + + sw = (int)((headr[3]&0xF0) >> 4) ; + + switch( sw ){ + case 1: + fprintf(stderr,"Videostream: ASPECT: 1:1"); + vi->aspect_ratio = 100; + break; + case 2: + fprintf(stderr,"Videostream: ASPECT: 4:3"); + vi->aspect_ratio = 133; + break; + case 3: + fprintf(stderr,"Videostream: ASPECT: 16:9"); + vi->aspect_ratio = 177; + break; + case 4: + fprintf(stderr,"Videostream: ASPECT: 2.21:1"); + vi->aspect_ratio = 221; + break; + + case 5 ... 15: + fprintf(stderr,"Videostream: ASPECT: reserved"); + vi->aspect_ratio = 0; + break; + + default: + vi->aspect_ratio = 0; + return -1; + } + + fprintf(stderr," Size = %dx%d",vi->horizontal_size,vi->vertical_size); + + sw = (int)(headr[3]&0x0F); + + switch ( sw ) { + case 1: + fprintf(stderr," FRate: 23.976 fps"); + vi->framerate = 24000/1001.; + form = -1; + break; + case 2: + fprintf(stderr," FRate: 24 fps"); + vi->framerate = 24; + form = -1; + break; + case 3: + fprintf(stderr," FRate: 25 fps"); + vi->framerate = 25; + form = VIDEO_MODE_PAL; + break; + case 4: + fprintf(stderr," FRate: 29.97 fps"); + vi->framerate = 30000/1001.; + form = VIDEO_MODE_NTSC; + break; + case 5: + fprintf(stderr," FRate: 30 fps"); + vi->framerate = 30; + form = VIDEO_MODE_NTSC; + break; + case 6: + fprintf(stderr," FRate: 50 fps"); + vi->framerate = 50; + form = VIDEO_MODE_PAL; + break; + case 7: + fprintf(stderr," FRate: 60 fps"); + vi->framerate = 60; + form = VIDEO_MODE_NTSC; + break; + } + + rem->dts_delay = (int)(7.0/vi->framerate/2.0*90000); + + vi->bit_rate = 400*(((headr[4] << 10) & 0x0003FC00UL) + | ((headr[5] << 2) & 0x000003FCUL) | + (((headr[6] & 0xC0) >> 6) & 0x00000003UL)); + + fprintf(stderr," BRate: %.2f Mbit/s",(vi->bit_rate)/1000000.); + + fprintf(stderr,"\n"); + vi->video_format = form; + + /* + marker_bit (&video_bs, 1); + vi->vbv_buffer_size = getbits (&video_bs, 10); + vi->CSPF = get1bit (&video_bs); + */ + return form; +} + + +int get_audio_info( Remux *rem) +{ + uint8_t *headr; + uint8_t buf[3]; + long off = 0; + int found = 0; + ringbuffy *aud_buffy = &rem->aud_buffy; + AudioInfo *ai = &rem->audio_info; + + while(!ring_rest(aud_buffy) && !refill_buffy(rem)); + while (found < 2 && ring_rest(aud_buffy)){ + uint8_t b[2]; + refill_buffy(rem); + aring_peek( rem, b, 2, 0); + + if ( b[0] == 0xff && (b[1] & 0xf8) == 0xf8) + found = 2; + else { + off++; + aring_read( rem, b, 1); + } + } + + if (!found) return -1; + rem->aframe = rem->aframen-1; + + if (aring_peek(rem, buf, 3, 0) < 1) return -1; + headr = buf+2; + + ai->layer = (buf[1] & 0x06) >> 1; + + fprintf(stderr,"Audiostream: Layer: %d", 4-ai->layer); + + + ai->bit_rate = bitrates[(3-ai->layer)][(headr[0] >> 4 )]*1000; + + if (ai->bit_rate == 0) + fprintf (stderr," Bit rate: free"); + else if (ai->bit_rate == 0xf) + fprintf (stderr," BRate: reserved"); + else + fprintf (stderr," BRate: %d kb/s", ai->bit_rate/1000); + + + ai->frequency = (headr[0] & 0x0c ) >> 2; + if (ai->frequency == 3) + fprintf (stderr, " Freq: reserved\n"); + else + fprintf (stderr," Freq: %2.1f kHz\n", + freq[ai->frequency]/10.); + + return 0; +} + + + +void init_remux(Remux *rem, int fin, int fout, int mult) +{ + rem->video_info.framerate = 0; + rem->audio_info.layer = -1; + rem->fin = fin; + rem->fout = fout; + ring_init(&rem->vid_buffy, 40*BUFFYSIZE*mult); + ring_init(&rem->aud_buffy, BUFFYSIZE*mult); + init_ptsl(rem->vpts_list); + init_ptsl(rem->apts_list); + init_framel(rem->vframe_list); + init_framel(rem->aframe_list); + + rem->vptsn = 0; + rem->aptsn = 0; + rem->vframen = 0; + rem->aframen = 0; + rem->vframe = 0; + rem->aframe = 0; + rem->vcframe = 0; + rem->acframe = 0; + rem->vpts = 0; + rem->vdts = 0; + rem->apts_off = 0; + rem->vpts_off = 0; + rem->apts_delay= 0; + rem->vpts_delay= 0; + rem->dts_delay = 0; + rem->apts = 0; + rem->vpes = 0; + rem->apes = 0; + rem->vpts_old = 0; + rem->apts_old = 0; + rem->SCR = 0; + rem->vwrite = 0; + rem->awrite = 0; + rem->vread = 0; + rem->aread = 0; + rem->group = 0; + rem->groupframe= 0; + rem->pack_size = 0; + rem->muxr = 0; + rem->time_off = 0; +} + +uint32_t bytes2pts(int bytes, int rate) +{ + if (bytes < 0xFFFFFFFFUL/720000UL) + return (uint32_t)(bytes*720000UL/rate); + else + return (uint32_t)(bytes/rate*720000UL); +} + +long pts2bytes( uint32_t pts, int rate) +{ + if (pts < 0xEFFFFFFFUL/rate) + return (pts*rate/720000); + else + return (pts* (rate/720000)); +} + +int write_audio_pes( Remux *rem, uint8_t *buf, int *alength) +{ + int add; + int pos = 0; + int p = 0; + uint32_t pts = 0; + int stuff = 0; + int length = *alength; + + if (!length) return 0; + p = PS_HEADER_L1+PES_H_MIN; + + if (rem->apts_old != rem->apts){ + pts = (uint32_t)((uint64_t)rem->apts + rem->apts_delay - rem->apts_off); + p += 5; + } + if ( length+p >= rem->pack_size){ + length = rem->pack_size; + } else { + if (rem->pack_size-length-p <= PES_MIN){ + stuff = rem->pack_size - length; + length = rem->pack_size; + } else + length = length+p; + } + pos = write_ps_header(buf,rem->SCR,rem->muxr, 1, 0, 0, 1, 1, 1, + 0, 0, 0, 0, 0, 0); + + pos += write_pes_header( 0xC0, length-pos, pts, buf+pos, stuff); + add = aring_read( rem, buf+pos, length-pos); + *alength = add; + if (add < 0) return -1; + pos += add; + rem->apts_old = rem->apts; + rem->apts = rem->apts_list[0].PTS; + + if (pos+PES_MIN < rem->pack_size){ + pos += write_pes_header( PADDING_STREAM, rem->pack_size-pos, 0, + buf+pos, 0); + pos = rem->pack_size; + } + if (pos != rem->pack_size) { + fprintf(stderr,"apos: %d\n",pos); + exit(1); + } + + return pos; +} + +int write_video_pes( Remux *rem, uint8_t *buf, int *vlength) +{ + int add; + int pos = 0; + int p = 0; + uint32_t pts = 0; + uint32_t dts = 0; + int stuff = 0; + int length = *vlength; + long diff = 0; + + if (! length) return 0; + p = PS_HEADER_L1+PES_H_MIN; + + if (rem->vpts_old != rem->vpts){ + pts = (uint32_t)((uint64_t)rem->vpts + rem->vpts_delay - rem->vpts_off); + p += 5; + } + if ( length+p >= rem->pack_size){ + length = rem->pack_size; + } else { + if (rem->pack_size - length - p <= PES_MIN){ + stuff = rem->pack_size - length; + length = rem->pack_size; + } else + length = length+p; + } + + pos = write_ps_header(buf,rem->SCR,rem->muxr, 1, 0, 0, 1, 1, 1, + 0, 0, 0, 0, 0, 0); + + pos += write_pes_header( 0xE0, length-pos, pts, buf+pos, stuff); + add = vring_read( rem, buf+pos, length-pos); + *vlength = add; + if (add < 0) return -1; + pos += add; + rem->vpts_old = rem->vpts; + dts = rem->vdts; + rem->vpts = rem->vpts_list[0].PTS; + rem->vdts = rem->vpts_list[0].dts; + if ( diff > 0) rem->SCR += diff; + if (pos+PES_MIN < rem->pack_size){ + // fprintf(stderr,"vstuffing: %d \n",rem->pack_size-pos); + pos += write_pes_header( PADDING_STREAM, rem->pack_size-pos, 0, + buf+pos, 0); + pos = rem->pack_size; + } + return pos; +} + +void print_info( Remux *rem , int ret) +{ + int newtime = 0; + static int time = 0; + int i = 0; + + while(! newtime && i < rem->vframen) { + if( (newtime = rem->vframe_list[i].time)) break; + i++; + } + if (newtime) time = newtime; + + fprintf(stderr,"SCR:"); + printpts(rem->SCR); + fprintf(stderr," VDTS:"); + printpts((uint32_t)((uint64_t)rem->vdts - rem->vpts_off + rem->vpts_delay)); + fprintf(stderr," APTS:"); + printpts((uint32_t)((uint64_t)rem->apts - rem->apts_off + rem->apts_delay)); + fprintf(stderr," TIME:%2d:", time/3600); + fprintf(stderr,"%02d:", (time%3600)/60); + fprintf(stderr,"%02d", (time%3600)%60); + if (ret) fprintf(stderr,"\n"); + else fprintf(stderr,"\r"); +} + +void remux(int fin, int fout, int pack_size, int mult) +{ + Remux rem; + long ptsdiff; + uint8_t buf[MAX_PACK_L]; + long pos = 0; + int r = 0; + int i, r1, r2; + long packets = 0; + uint8_t mpeg_end[4] = { 0x00, 0x00, 0x01, 0xB9 }; + uint32_t SCR_inc = 0; + int data_size; + long vbuf, abuf; + long vbuf_max, abuf_max; + PTS_List abufl[MAX_PTS]; + PTS_List vbufl[MAX_PTS]; + int abufn = 0; + int vbufn = 0; + uint64_t pts_d = 0; + int ok_audio; + int ok_video; + uint32_t apos = 0; + uint32_t vpos = 0; + int vpack_size = 0; + int apack_size = 0; + + init_ptsl(abufl); + init_ptsl(vbufl); + + if (mult < 0 || mult >1000){ + fprintf(stderr,"Multipler too large\n"); + exit(1); + } + init_remux(&rem, fin, fout, mult); + rem.pack_size = pack_size; + data_size = pack_size - MAX_H_SIZE; + fprintf(stderr,"pack_size: %d header_size: %d data size: %d\n", + pack_size, MAX_H_SIZE, data_size); + refill_buffy(&rem); + fprintf(stderr,"Package size: %d\n",pack_size); + + if ( get_video_info(&rem) < 0 ){ + fprintf(stderr,"ERROR: Can't find valid video stream\n"); + exit(1); + } + + i = 0; + while(! rem.time_off && i < rem.vframen) { + if( (rem.time_off = rem.vframe_list[i].time)) break; + i++; + } + + if ( get_audio_info(&rem) < 0 ){ + fprintf(stderr,"ERROR: Can't find valid audio stream\n"); + exit(1); + } + + rem.vpts = rem.vpts_list[0].PTS; + rem.vdts = rem.vpts; + rem.vpts_off = rem.vpts; + fprintf(stderr,"Video start PTS = %fs \n",rem.vpts_off/90000.); + rem.apts = rem.apts_list[0].PTS; + rem.apts_off = rem.apts; + ptsdiff = rem.vpts - rem.apts; + if (ptsdiff > 0) rem.vpts_off -= ptsdiff; + else rem.apts_off -= -ptsdiff; + fprintf(stderr,"Audio start PTS = %fs\n",rem.apts_off/90000.); + fprintf(stderr,"Difference Video - Audio = %fs\n",ptsdiff/90000.); + fprintf(stderr,"Time offset = %ds\n",rem.time_off); + + rem.muxr = (rem.video_info.bit_rate + + rem.audio_info.bit_rate)/400; + fprintf(stderr,"MUXRATE: %.2f Mb/sec\n",rem.muxr/2500.); + SCR_inc = 1800 * pack_size / rem.muxr; + + r = 0; + while ( rem.vptsn < 2 && !r) r = refill_buffy(&rem); + r = 0; + while ( rem.aptsn < 2 && !r) r = refill_buffy(&rem); + + //rem.vpts_delay = (uint32_t)(2*90000ULL* (uint64_t)pack_size/rem.muxr); + rem.vpts_delay = rem.dts_delay; + rem.apts_delay = rem.vpts_delay; + + vbuf_max = 29440; + abuf_max = 4096; + vbuf = 0; + abuf = 0; + pos = write_ps_header(buf,rem.SCR,rem.muxr, 1, 0, 0, 1, 1, 1, + 0xC0, 0, 32, 0xE0, 1, 230); + pos += write_pes_header( PADDING_STREAM, pack_size-pos, 0, buf+pos,0); + pos = rem.pack_size; + write( fout, buf, pos); + + apos = rem.aread; + vpos = rem.vread; + print_info( &rem, 1 ); + + while( ring_rest(&rem.aud_buffy) && ring_rest(&rem.vid_buffy) ){ + uint32_t next_apts; + uint32_t next_vdts; + int asize, vsize; + + r1 = 0; + r2 = 0; + while ( rem.aframen < 2 && !r1) + r1 = refill_buffy(&rem); + while ( rem.vframen < 2 && !r2) + r2 = refill_buffy(&rem); + if (r1 && r2) break; + + if ( !r1 && apos <= rem.aread) + apos = rem.aframe_list[1].pos; + if ( !r2 && vpos <= rem.vread) + vpos = rem.vframe_list[1].pos; + apack_size = apos - rem.aread; + vpack_size = vpos - rem.vread; + + + next_vdts = (uint32_t)((uint64_t)rem.vdts + rem.vpts_delay + - rem.vpts_off) ; + ok_video = ( rem.SCR < next_vdts); + + next_apts = (uint32_t)((uint64_t)rem.apts + rem.apts_delay + - rem.apts_off) ; + ok_audio = ( rem.SCR < next_apts); + + asize = (apack_size > data_size ? data_size: apack_size); + vsize = (vpack_size > data_size ? data_size: vpack_size); + + fprintf(stderr,"vframen: %d aframen: %d v_ok: %d a_ok: %d v_buf: %d a_buf: %d vpacks: %d apacks: %d\n",rem.vframen,rem.aframen, ok_video, ok_audio, (int)vbuf,(int)abuf,vsize, asize); + + + if( vbuf+vsize < vbuf_max && vsize && ok_audio ){ + fprintf(stderr,"1 "); + pos = write_video_pes( &rem, buf, &vpack_size); + write( fout, buf, pos); + vbuf += vpack_size; + vbufn = add_pts( vbufl, rem.vdts, vpack_size, + 0, vbufn, 0); + packets++; + } else if ( abuf+asize < abuf_max && asize && + ok_video ){ + fprintf(stderr,"2 "); + pos = write_audio_pes( &rem, buf, &apack_size); + write( fout, buf, pos); + abuf += apack_size; + abufn = add_pts( abufl, rem.apts, apack_size, + 0, abufn, 0); + packets++; + } else if ( abuf+asize < abuf_max && asize && + !ok_audio){ + fprintf(stderr,"3 "); + pos = write_audio_pes( &rem, buf, &apack_size); + write( fout, buf, pos); + abuf += apack_size; + abufn = add_pts( abufl, rem.apts, apack_size, + 0, abufn, 0); + packets++; + } else if (vbuf+vsize < vbuf_max && vsize && + !ok_video){ + fprintf(stderr,"4 "); + pos = write_video_pes( &rem, buf, &vpack_size); + write( fout, buf, pos); + vbuf += vpack_size; + vbufn = add_pts( vbufl, rem.vdts, vpack_size, + 0, vbufn, 0); + packets++; + } else { + fprintf(stderr,"5 "); + pos = write_ps_header(buf,rem.SCR,rem.muxr, 1, 0, 0, + 1, 1, 1, 0, 0, 0, 0, 0, 0); + + pos += write_pes_header( PADDING_STREAM, pack_size-pos, + 0, buf+pos, 0); + write( fout, buf, pos); + } + + + //fprintf(stderr,"vbufn: %d abufn: %d ", vbufn,abufn); + //fprintf(stderr,"vbuf: %5d abuf: %4d\n", vbuf,abuf); + + if (rem.SCR > rem.vdts+rem.vpts_off -rem.vpts_delay) + rem.SCR = rem.vdts-rem.vpts_off; + rem.SCR = (uint32_t)((uint64_t) rem.SCR + SCR_inc); + + if ( rem.apts_off + rem.SCR < rem.apts_delay ) pts_d = 0; + else pts_d = (uint64_t) rem.SCR + rem.apts_off - rem.apts_delay; + abuf -= del_ptss( abufl, (uint32_t) pts_d, &abufn); + + if ( rem.vpts_off + rem.SCR < rem.vpts_delay ) pts_d = 0; + else pts_d = (uint64_t) rem.SCR + rem.vpts_off - rem.vpts_delay; + vbuf -= del_ptss( vbufl, (uint32_t) pts_d, &vbufn); + + print_info( &rem, 1); + //fprintf(stderr,"vbufn: %d abufn: %d ", vbufn,abufn); + //fprintf(stderr,"vbuf: %5d abuf: %4d\n\n", vbuf,abuf); + + + } + pos = write_ps_header(buf,rem.SCR,rem.muxr, 1, 0, 0, 1, 1, 1, + 0, 0, 0, 0, 0, 0); + + pos += write_pes_header( PADDING_STREAM, pack_size-pos-4, 0, + buf+pos, 0); + pos = rem.pack_size-4; + write( fout, buf, pos); + + write( fout, mpeg_end, 4); + fprintf(stderr,"\ndone\n"); +} + + +typedef +struct pes_buffer_s{ + ringbuffy pes_buffy; + uint8_t type; + PTS_List pts_list[MAX_PTS]; + FRAME_List frame_list[MAX_FRAME]; + int pes_size; + uint64_t written; + uint64_t read; +} PESBuffer; + + +void init_PESBuffer(PESBuffer *pbuf, int pes_size, int buf_size, uint8_t type) +{ + init_framel( pbuf->frame_list); + init_ptsl( pbuf->pts_list); + ring_init( &pbuf->pes_buffy, buf_size); + pbuf->pes_size = pes_size; + pbuf->type = type; + pbuf->written = 0; + pbuf->read = 0; +} + + +#define MAX_PBUF 4 + +typedef +struct remux_s{ + PESBuffer pbuf_list[MAX_PBUF]; + int num_pbuf; +} REMUX; + + +void init_REMUX(REMUX *rem) +{ + rem->num_pbuf = 0; +} + + + +#define REPACK 2048 +#define ABUF_SIZE REPACK*1024 +#define VBUF_SIZE REPACK*10240 + +void remux_main(uint8_t *buf, int count, void *pr) +{ + int i, b; + int bufsize = 0; + p2p *p = (p2p *) pr; + PESBuffer *pbuf; + REMUX *rem = (REMUX *) p->data; + uint8_t type = buf[3]; + int *npbuf = &(rem->num_pbuf); + + switch ( type ){ + case PRIVATE_STREAM1: + bufsize = ABUF_SIZE; + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + if (!bufsize) bufsize = VBUF_SIZE; + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + if (!bufsize) bufsize = ABUF_SIZE; + b = -1; + for ( i = 0; i < *npbuf; i++){ + if ( type == rem->pbuf_list[i].type ){ + b = i; + break; + } + } + if (b < 0){ + if ( *npbuf < MAX_PBUF ){ + init_PESBuffer(&rem->pbuf_list[*npbuf], + p->repack+6, bufsize, type); + b = *npbuf; + (*npbuf)++; + } else { + fprintf(stderr,"Not enough PES buffers\n"); + exit(1); + } + } + break; + default: + return; + } + + pbuf = &(rem->pbuf_list[b]); + if (ring_write(&(pbuf->pes_buffy),(char *)buf,count) != count){ + fprintf(stderr,"buffer overflow type 0x%2x\n",type); + exit(1); + } else { + pbuf->written += count; + if ((p->flag2 & PTS_DTS_FLAGS)){ + uint32_t PTS = trans_pts_dts(p->pts); + add_pts(pbuf->pts_list, PTS, pbuf->written, + pbuf->written, 0, 0); + } + p->flag2 = 0; + } + +} + +void output_mux(p2p *p) +{ + int i, filling; + PESBuffer *pbuf; + ringbuffy *pes_buffy; + REMUX *rem = (REMUX *) p->data; + int repack = p->repack; + int npbuf = rem->num_pbuf; + + for ( i = 0; i < npbuf; i++){ + pbuf = &(rem->pbuf_list[i]); + pes_buffy = &pbuf->pes_buffy; + filling = pes_buffy->size - ring_rest(pes_buffy); + if (filling/(2 *repack)){ + pbuf->read += ring_read_file(pes_buffy, p->fd1, + (filling/repack)*repack); + } + } +} + + + +#define SIZE 32768 + +void remux2(int fdin, int fdout) +{ + p2p p; + int count = 1; + uint8_t buf[SIZE]; + uint64_t length = 0; + uint64_t l = 0; + int verb = 0; + REMUX rem; + + init_p2p(&p, remux_main, REPACK); + p.fd1 = fdout; + p.data = (void *) &rem; + + + if (fdin != STDIN_FILENO) verb = 1; + + if (verb) { + length = lseek(fdin, 0, SEEK_END); + lseek(fdin,0,SEEK_SET); + } + + while (count > 0){ + count = read(fdin,buf,SIZE); + l += count; + if (verb) + fprintf(stderr,"Writing %2.2f %%\r", + 100.*l/length); + + get_pes(buf,count,&p,pes_repack); + output_mux(&p); + } + +} diff --git a/libdvbmpeg/remux.h b/libdvbmpeg/remux.h new file mode 100644 index 0000000..76c128b --- /dev/null +++ b/libdvbmpeg/remux.h @@ -0,0 +1,149 @@ +/* + * dvb-mpegtools for the Siemens Fujitsu DVB PCI card + * + * Copyright (C) 2000, 2001 Marcus Metzler + * for convergence integrated media GmbH + * Copyright (C) 2002 Marcus Metzler + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + + * The author can be reached at mocm@metzlerbros.de, + */ + +#include +#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_*/ diff --git a/libdvbmpeg/ringbuffy.c b/libdvbmpeg/ringbuffy.c new file mode 100644 index 0000000..965e49a --- /dev/null +++ b/libdvbmpeg/ringbuffy.c @@ -0,0 +1,201 @@ +/* + Ringbuffer Implementation for gtvscreen + + Copyright (C) 2000 Marcus Metzler (mocm@metzlerbros.de) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include "ringbuffy.h" +#include + +int ring_init (ringbuffy *rbuf, int size) +{ + if (size > 0){ + rbuf->size = size; + if( !(rbuf->buffy = (char *) malloc(sizeof(char)*size)) ){ + fprintf(stderr,"Not enough memory for ringbuffy\n"); + return -1; + } + } else { + fprintf(stderr,"Wrong size for ringbuffy\n"); + return -1; + } + rbuf->read_pos = 0; + rbuf->write_pos = 0; + return 0; +} + + +void ring_destroy(ringbuffy *rbuf) +{ + free(rbuf->buffy); +} + + +int ring_write(ringbuffy *rbuf, char *data, int count) +{ + + int diff, free, pos, rest; + + if (count <=0 ) return 0; + pos = rbuf->write_pos; + rest = rbuf->size - pos; + diff = rbuf->read_pos - pos; + free = (diff > 0) ? diff-1 : rbuf->size+diff-1; + + if ( free <= 0 ) return FULL_BUFFER; + if ( free < count ) count = free; + + if (count >= rest){ + memcpy (rbuf->buffy+pos, data, rest); + if (count - rest) + memcpy (rbuf->buffy, data+rest, count - rest); + rbuf->write_pos = count - rest; + } else { + memcpy (rbuf->buffy+pos, data, count); + rbuf->write_pos += count; + } + + return count; +} + + + + +int ring_peek(ringbuffy *rbuf, char *data, int count, long off) +{ + + int diff, free, pos, rest; + + if (count <=0 ) return 0; + pos = rbuf->read_pos+off; + rest = rbuf->size - pos ; + diff = rbuf->write_pos - pos; + free = (diff >= 0) ? diff : rbuf->size+diff; + + if ( free <= 0 ) return FULL_BUFFER; + if ( free < count ) count = free; + + if ( count < rest ){ + memcpy(data, rbuf->buffy+pos, count); + } else { + memcpy(data, rbuf->buffy+pos, rest); + if ( count - rest) + memcpy(data+rest, rbuf->buffy, count - rest); + } + + return count; +} + +int ring_read(ringbuffy *rbuf, char *data, int count) +{ + + int diff, free, pos, rest; + + if (count <=0 ) return 0; + pos = rbuf->read_pos; + rest = rbuf->size - pos; + diff = rbuf->write_pos - pos; + free = (diff >= 0) ? diff : rbuf->size+diff; + + if ( rest <= 0 ) return 0; + if ( free < count ) count = free; + + if ( count < rest ){ + memcpy(data, rbuf->buffy+pos, count); + rbuf->read_pos += count; + } else { + memcpy(data, rbuf->buffy+pos, rest); + if ( count - rest) + memcpy(data+rest, rbuf->buffy, count - rest); + rbuf->read_pos = count - rest; + } + + return count; +} + + + +int ring_write_file(ringbuffy *rbuf, int fd, int count) +{ + + int diff, free, pos, rest, rr; + + if (count <=0 ) return 0; + pos = rbuf->write_pos; + rest = rbuf->size - pos; + diff = rbuf->read_pos - pos; + free = (diff > 0) ? diff-1 : rbuf->size+diff-1; + + if ( rest <= 0 ) return 0; + if ( free < count ) count = free; + + if (count >= rest){ + rr = read (fd, rbuf->buffy+pos, rest); + if (rr == rest && count - rest) + rr += read (fd, rbuf->buffy, count - rest); + if (rr >=0) + rbuf->write_pos = (pos + rr) % rbuf->size; + } else { + rr = read (fd, rbuf->buffy+pos, count); + if (rr >=0) + rbuf->write_pos += rr; + } + + return rr; +} + + + +int ring_read_file(ringbuffy *rbuf, int fd, int count) +{ + + int diff, free, pos, rest, rr; + + if (count <=0 ) return 0; + pos = rbuf->read_pos; + rest = rbuf->size - pos; + diff = rbuf->write_pos - pos; + free = (diff >= 0) ? diff : rbuf->size+diff; + + if ( free <= 0 ) return FULL_BUFFER; + if ( free < count ) count = free; + + if (count >= rest){ + rr = write (fd, rbuf->buffy+pos, rest); + if (rr == rest && count - rest) + rr += write (fd, rbuf->buffy, count - rest); + if (rr >=0) + rbuf->read_pos = (pos + rr) % rbuf->size; + } else { + rr = write (fd, rbuf->buffy+pos, count); + if (rr >=0) + rbuf->read_pos += rr; + } + + + return rr; +} + +int ring_rest(ringbuffy *rbuf){ + int diff, free, pos, rest; + pos = rbuf->read_pos; + rest = rbuf->size - pos; + diff = rbuf->write_pos - pos; + free = (diff >= 0) ? diff : rbuf->size+diff; + + return free; +} diff --git a/libdvbmpeg/ringbuffy.h b/libdvbmpeg/ringbuffy.h new file mode 100644 index 0000000..16011d7 --- /dev/null +++ b/libdvbmpeg/ringbuffy.h @@ -0,0 +1,52 @@ +/* + Ringbuffer Implementation for gtvscreen + + Copyright (C) 2000 Marcus Metzler (mocm@metzlerbros.de) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef RINGBUFFY_H +#define RINGBUFFY_H + +#include +#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 */ diff --git a/libdvbmpeg/transform.c b/libdvbmpeg/transform.c new file mode 100644 index 0000000..c53f1fb --- /dev/null +++ b/libdvbmpeg/transform.c @@ -0,0 +1,2681 @@ +/* + * dvb-mpegtools for the Siemens Fujitsu DVB PCI card + * + * Copyright (C) 2000, 2001 Marcus Metzler + * for convergence integrated media GmbH + * Copyright (C) 2002 Marcus Metzler + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + + * The author can be reached at marcus@convergence.de, + + * the project's page is at http://linuxtv.org/dvb/ + */ + + +#include "transform.h" +#include +#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 ); +} diff --git a/libdvbmpeg/transform.h b/libdvbmpeg/transform.h new file mode 100644 index 0000000..ad32706 --- /dev/null +++ b/libdvbmpeg/transform.h @@ -0,0 +1,250 @@ +/* + * dvb-mpegtools for the Siemens Fujitsu DVB PCI card + * + * Copyright (C) 2000, 2001 Marcus Metzler + * for convergence integrated media GmbH + * Copyright (C) 2002 Marcus Metzler + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + * + + * The author can be reached at mocm@metzlerbros.de, + + */ + +#ifndef _TS_TRANSFORM_H_ +#define _TS_TRANSFORM_H_ + +#include +#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 (8*MAX_PLENGTH) + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#define P2P_LENGTH 2048 + + enum{NOPES, AUDIO, VIDEO, AC3}; + + typedef struct p2pstruct { + int found; + uint8_t buf[MMAX_PLENGTH]; + uint8_t cid; + uint8_t subid; + uint32_t plength; + uint8_t plen[2]; + uint8_t flag1; + uint8_t flag2; + uint8_t hlength; + uint8_t pts[5]; + int mpeg; + uint8_t check; + int fd1; + int fd2; + int es; + int filter; + int which; + int done; + int repack; + uint16_t bigend_repack; + void (*func)(uint8_t *buf, int count, void *p); + int startv; + int starta; + int64_t apts; + int64_t vpts; + uint16_t pid; + uint16_t pida; + uint16_t pidv; + uint8_t acounter; + uint8_t vcounter; + uint8_t count0; + uint8_t count1; + void *data; + } p2p; + + + uint64_t trans_pts_dts(uint8_t *pts); + int write_ts_header(uint16_t pid, uint8_t *counter, int pes_start, + uint8_t *buf, uint8_t length); + uint16_t get_pid(uint8_t *pid); + void init_p2p(p2p *p, void (*func)(uint8_t *buf, int count, void *p), + int repack); + void get_pes (uint8_t *buf, int count, p2p *p, void (*func)(p2p *p)); + void get_pes (uint8_t *buf, int count, p2p *p, void (*func)(p2p *p)); + void pes_repack(p2p *p); + void setup_pes2ts( p2p *p, uint32_t pida, uint32_t pidv, + void (*ts_write)(uint8_t *buf, int count, void *p)); + void kpes_to_ts( p2p *p,uint8_t *buf ,int count ); + void setup_ts2pes( p2p *pa, p2p *pv, uint32_t pida, uint32_t pidv, + void (*pes_write)(uint8_t *buf, int count, void *p)); + void kts_to_pes( p2p *p, uint8_t *buf); + void pes_repack(p2p *p); + void extract_from_pes(int fdin, int fdout, uint8_t id, int es); + int64_t pes_dmx(int fdin, int fdouta, int fdoutv, int es); + void pes_to_ts2( int fdin, int fdout, uint16_t pida, uint16_t pidv); + void ts_to_pes( int fdin, uint16_t pida, uint16_t pidv, int pad); + int get_ainfo(uint8_t *mbuf, int count, AudioInfo *ai, int pr); + int get_vinfo(uint8_t *mbuf, int count, VideoInfo *vi, int pr); + int get_ac3info(uint8_t *mbuf, int count, AudioInfo *ai, int pr); + void filter_audio_from_pes(int fdin, int fdout, uint8_t id, + uint8_t subid); + + +//instant repack + + typedef struct ipack_s { + int size; + int size_orig; + int found; + int ps; + int has_ai; + int has_vi; + AudioInfo ai; + VideoInfo vi; + uint8_t *buf; + uint8_t cid; + uint32_t plength; + uint8_t plen[2]; + uint8_t flag1; + uint8_t flag2; + uint8_t hlength; + uint8_t pts[5]; + uint8_t last_pts[5]; + int mpeg; + uint8_t check; + int which; + int done; + void *data; + void *data2; + void (*func)(uint8_t *buf, int size, void *priv); + int count; + int start; + int fd; + int fd1; + int fd2; + int ffd; + int playing; + } ipack; + + void instant_repack (uint8_t *buf, int count, ipack *p); + void init_ipack(ipack *p, int size, + void (*func)(uint8_t *buf, int size, void *priv), + int pad); + void free_ipack(ipack * p); + void send_ipack(ipack *p); + void reset_ipack(ipack *p); + void ps_pes(ipack *p); + // use with ipack structure, repack size and callback func + + int64_t ts_demux(int fd_in, int fdv_out,int fda_out,uint16_t pida, + uint16_t pidv, int es); + + void ts2es(int fdin, uint16_t pidv); + void ts2es_opt(int fdin, uint16_t pidv, ipack *p, int verb); + void insert_pat_pmt( int fdin, int fdout); + void change_aspect(int fdin, int fdout, int aspect); + +// SV: all made non-static: + void pes_in_ts(p2p *p); + +// SV: moved from .c file: +#define IPACKS 2048 + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _TS_TRANSFORM_H_*/ + + + diff --git a/patches/respect_ca.diff b/patches/respect_ca.diff new file mode 100644 index 0000000..8240b19 --- /dev/null +++ b/patches/respect_ca.diff @@ -0,0 +1,43 @@ +# The cannels.conf ca field can be used to bind a channel to a specific +# device. The streamdev-client does not consider this information, so +# there's no way to keep VDR from using streamdev for a specific +# channel. Apply this patch if you need this feature. +# +# This fix should probably become part of streamdev. However as it +# changes the behaviour of streamdev, I decided to keep it as a separate +# patch until there is something like a new official streamdev release. +# +--- client/device.h.bak 2006-11-09 12:25:21.000000000 +0100 ++++ client/device.h 2006-11-09 12:26:57.000000000 +0100 +@@ -50,6 +50,7 @@ + cStreamdevDevice(void); + virtual ~cStreamdevDevice(); + ++ virtual int ProvidesCa(const cChannel *Channel) const; + virtual bool ProvidesSource(int Source) const; + virtual bool ProvidesTransponder(const cChannel *Channel) const; + virtual bool ProvidesChannel(const cChannel *Channel, int Priority = -1, +--- client/device.c.bak 2006-11-09 12:23:24.000000000 +0100 ++++ client/device.c 2006-11-09 12:35:48.000000000 +0100 +@@ -57,6 +57,12 @@ + #endif + } + ++int cStreamdevDevice::ProvidesCa(const cChannel *Channel) const ++{ ++ // Encrypted is acceptable for now. Will ask the server later. ++ return Channel->Ca() <= CA_DVB_MAX ? cDevice::ProvidesCa(Channel) : 1; ++} ++ + bool cStreamdevDevice::ProvidesSource(int Source) const { + Dprintf("ProvidesSource, Source=%d\n", Source); + return false; +@@ -78,7 +84,7 @@ + if (ClientSocket.DataSocket(siLive) != NULL + && TRANSPONDER(Channel, m_Channel)) + res = true; +- else { ++ else if (ProvidesCa(Channel)) { + res = prio && ClientSocket.ProvidesChannel(Channel, Priority); + ndr = true; + } diff --git a/patches/thread.c.diff b/patches/thread.c.diff new file mode 100644 index 0000000..a03ffac --- /dev/null +++ b/patches/thread.c.diff @@ -0,0 +1,29 @@ +--- vdr-vanilla/thread.c 2003-10-10 18:19:15.000000000 +0200 ++++ vdr-vanilla-thread/thread.c 2003-10-10 18:43:36.000000000 +0200 +@@ -158,12 +158,21 @@ + + bool cThread::Active(void) + { +- if (threadPid) { +- if (kill(threadPid, SIGIO) < 0) { // couldn't find another way of checking whether the thread is still running - any ideas? +- if (errno == ESRCH) +- threadPid = 0; +- else ++ if (thread) { ++ /* ++ * Single UNIX Spec v2 says: ++ * ++ * The pthread_kill() function is used to request ++ * that a signal be delivered to the specified thread. ++ * ++ * As in kill(), if sig is zero, error checking is ++ * performed but no signal is actually sent. ++ */ ++ int err; ++ if ((err = pthread_kill(thread, 0)) != 0) { ++ if (err != ESRCH) + LOG_ERROR; ++ thread = 0; + } + else + return true; diff --git a/patches/vdr-1.3.11-localchannelprovide.diff b/patches/vdr-1.3.11-localchannelprovide.diff new file mode 100644 index 0000000..448d7fc --- /dev/null +++ b/patches/vdr-1.3.11-localchannelprovide.diff @@ -0,0 +1,61 @@ +diff -u vdr-1.3.11/config.c vdr-1.3.11.LocalChannelProvide/config.c +--- vdr-1.3.11/config.c 2004-05-16 14:43:55.000000000 +0200 ++++ vdr-1.3.11.LocalChannelProvide/config.c 2004-08-29 17:55:59.000000000 +0200 +@@ -297,6 +297,7 @@ + ResumeID = 0; + CurrentChannel = -1; + CurrentVolume = MAXVOLUME; ++ LocalChannelProvide = 1; + } + + cSetup& cSetup::operator= (const cSetup &s) +@@ -450,6 +451,7 @@ + else if (!strcasecmp(Name, "ResumeID")) ResumeID = atoi(Value); + else if (!strcasecmp(Name, "CurrentChannel")) CurrentChannel = atoi(Value); + else if (!strcasecmp(Name, "CurrentVolume")) CurrentVolume = atoi(Value); ++ else if (!strcasecmp(Name, "LocalChannelProvide")) LocalChannelProvide = atoi(Value); + else + return false; + return true; +@@ -510,6 +512,7 @@ + Store("ResumeID", ResumeID); + Store("CurrentChannel", CurrentChannel); + Store("CurrentVolume", CurrentVolume); ++ Store("LocalChannelProvide",LocalChannelProvide); + + Sort(); + +diff -u vdr-1.3.11/config.h vdr-1.3.11.LocalChannelProvide/config.h +--- vdr-1.3.11/config.h 2004-06-10 15:18:50.000000000 +0200 ++++ vdr-1.3.11.LocalChannelProvide/config.h 2004-08-29 17:47:32.000000000 +0200 +@@ -251,6 +251,7 @@ + int ResumeID; + int CurrentChannel; + int CurrentVolume; ++ int LocalChannelProvide; + int __EndData__; + cSetup(void); + cSetup& operator= (const cSetup &s); +diff -u vdr-1.3.11/dvbdevice.c vdr-1.3.11.LocalChannelProvide/dvbdevice.c +--- vdr-1.3.11/dvbdevice.c 2004-06-19 11:33:42.000000000 +0200 ++++ vdr-1.3.11.LocalChannelProvide/dvbdevice.c 2004-08-29 18:00:37.000000000 +0200 +@@ -674,6 +674,8 @@ + + bool cDvbDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers) const + { ++ if (Setup.LocalChannelProvide != 1) ++ return false; + bool result = false; + bool hasPriority = Priority < 0 || Priority > this->Priority(); + bool needsDetachReceivers = false; +diff -u vdr-1.3.11/menu.c vdr-1.3.11.LocalChannelProvide/menu.c +--- vdr-1.3.11/menu.c 2004-06-13 22:26:51.000000000 +0200 ++++ vdr-1.3.11.LocalChannelProvide/menu.c 2004-08-29 17:52:31.000000000 +0200 +@@ -1878,6 +1878,7 @@ + Add(new cMenuEditIntItem( tr("Setup.DVB$Primary DVB interface"), &data.PrimaryDVB, 1, cDevice::NumDevices())); + Add(new cMenuEditBoolItem(tr("Setup.DVB$Video format"), &data.VideoFormat, "4:3", "16:9")); + Add(new cMenuEditStraItem(tr("Setup.DVB$Update channels"), &data.UpdateChannels, 5, updateChannelsTexts)); ++ Add(new cMenuEditBoolItem(tr("Channels available locally"), &data.LocalChannelProvide)); + } + + eOSState cMenuSetupDVB::ProcessKey(eKeys Key) diff --git a/patches/vdr-1.3.24.LocalChannelProvide.diff b/patches/vdr-1.3.24.LocalChannelProvide.diff new file mode 100644 index 0000000..830960a --- /dev/null +++ b/patches/vdr-1.3.24.LocalChannelProvide.diff @@ -0,0 +1,93 @@ +diff -Nu vdr-1.3.24/config.c vdr-1.3.24.LocalChannelProvide/config.c +--- vdr-1.3.24/config.c 2005-02-20 13:52:59.000000000 +0100 ++++ vdr-1.3.24.LocalChannelProvide/config.c 2005-05-12 19:23:58.000000000 +0200 +@@ -301,6 +301,7 @@ + CurrentChannel = -1; + CurrentVolume = MAXVOLUME; + CurrentDolby = 0; ++ LocalChannelProvide = 1; + } + + cSetup& cSetup::operator= (const cSetup &s) +@@ -458,6 +459,7 @@ + else if (!strcasecmp(Name, "CurrentChannel")) CurrentChannel = atoi(Value); + else if (!strcasecmp(Name, "CurrentVolume")) CurrentVolume = atoi(Value); + else if (!strcasecmp(Name, "CurrentDolby")) CurrentDolby = atoi(Value); ++ else if (!strcasecmp(Name, "LocalChannelProvide")) LocalChannelProvide = atoi(Value); + else + return false; + return true; +@@ -522,6 +524,7 @@ + Store("CurrentChannel", CurrentChannel); + Store("CurrentVolume", CurrentVolume); + Store("CurrentDolby", CurrentDolby); ++ Store("LocalChannelProvide",LocalChannelProvide); + + Sort(); + +diff -Nu vdr-1.3.24/config.h vdr-1.3.24.LocalChannelProvide/config.h +--- vdr-1.3.24/config.h 2005-05-05 13:04:18.000000000 +0200 ++++ vdr-1.3.24.LocalChannelProvide/config.h 2005-05-12 19:24:31.000000000 +0200 +@@ -255,6 +255,7 @@ + int CurrentChannel; + int CurrentVolume; + int CurrentDolby; ++ int LocalChannelProvide; + int __EndData__; + cSetup(void); + cSetup& operator= (const cSetup &s); +diff -Nu vdr-1.3.24/dvbdevice.c vdr-1.3.24.LocalChannelProvide/dvbdevice.c +--- vdr-1.3.24/dvbdevice.c 2005-03-20 11:10:38.000000000 +0100 ++++ vdr-1.3.24.LocalChannelProvide/dvbdevice.c 2005-05-12 19:19:29.000000000 +0200 +@@ -746,6 +746,8 @@ + + bool cDvbDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers) const + { ++ if (Setup.LocalChannelProvide != 1) ++ return false; + bool result = false; + bool hasPriority = Priority < 0 || Priority > this->Priority(); + bool needsDetachReceivers = false; +diff -Nu vdr-1.3.24/i18n.c vdr-1.3.24.LocalChannelProvide/i18n.c +--- vdr-1.3.24/i18n.c 2005-05-05 15:12:54.000000000 +0200 ++++ vdr-1.3.24.LocalChannelProvide/i18n.c 2005-05-12 19:30:50.000000000 +0200 +@@ -5325,6 +5325,27 @@ + "ST:TNG konsool", + "ST:TNG konsol", + }, ++ { "Channels available locally", ++ "Kanäle lokal beziehen", ++ "",// TODO ++ "", ++ "", ++ "",// TODO ++ "",// TODO ++ "",// TODO ++ "", ++ "",// TODO ++ "",// TODO ++ "",// TODO ++ "", ++ "", ++ "",// TODO ++ "",// TODO ++ "", ++ "", ++ "", ++ "", ++ }, + { NULL } + }; + +diff -Nu vdr-1.3.24/menu.c vdr-1.3.24.LocalChannelProvide/menu.c +--- vdr-1.3.24/menu.c 2005-03-20 16:14:51.000000000 +0100 ++++ vdr-1.3.24.LocalChannelProvide/menu.c 2005-05-12 19:26:57.000000000 +0200 +@@ -1968,7 +1968,7 @@ + Add(new cMenuEditIntItem( tr("Setup.DVB$Audio languages"), &numAudioLanguages, 0, I18nNumLanguages)); + for (int i = 0; i < numAudioLanguages; i++) + Add(new cMenuEditStraItem(tr("Setup.DVB$Audio language"), &data.AudioLanguages[i], I18nNumLanguages, I18nLanguages())); +- ++ Add(new cMenuEditBoolItem(tr("Channels available locally"), &data.LocalChannelProvide)); + SetCurrent(Get(current)); + Display(); + } diff --git a/patches/vdr-1.3.6-incompletesections.diff b/patches/vdr-1.3.6-incompletesections.diff new file mode 100644 index 0000000..a58a121 --- /dev/null +++ b/patches/vdr-1.3.6-incompletesections.diff @@ -0,0 +1,22 @@ +--- vdr-1.3.6/sections.c 2004-02-07 17:51:57.000000000 +0200 ++++ sections.c 2004-03-21 18:34:47.000000000 +0200 +@@ -185,11 +185,17 @@ + if (fh) { + // Read section data: + unsigned char buf[4096]; // max. allowed size for any EIT section +- int r = safe_read(fh->handle, buf, sizeof(buf)); ++ struct stat statbuf; ++ int st = fstat(fh->handle, &statbuf); ++ int ispipe = (st == 0 && !S_ISCHR(statbuf.st_mode)); ++ /*printf("ispipe %d\n", ispipe);*/ ++ int r = safe_read(fh->handle, buf, ispipe ? 3 : sizeof(buf)); + if (!DeviceHasLock) + continue; // we do the read anyway, to flush any data that might have come from a different transponder +- if (r > 3) { // minimum number of bytes necessary to get section length ++ if (r >= 3) { // minimum number of bytes necessary to get section length + int len = (((buf[1] & 0x0F) << 8) | (buf[2] & 0xFF)) + 3; ++ if (ispipe) ++ r += safe_read(fh->handle, buf+3, len-3); + if (len == r) { + // Distribute data to all attached filters: + int pid = fh->filterData.pid; diff --git a/patches/vdr-1.4.3-recursion.diff b/patches/vdr-1.4.3-recursion.diff new file mode 100644 index 0000000..7a06c92 --- /dev/null +++ b/patches/vdr-1.4.3-recursion.diff @@ -0,0 +1,85 @@ +# If you have two or more VDRs and you like them to mutually share +# there DVB cards you might need to apply this patch first. +# +# IMPORTANT: As this patch does not only modify streamdev-server but +# also an exported method of VDR, you will need to +# +# !!!!! RECOMPILE VDR AND ALL PLUGINS !!!!! +# +# Why do I need the patch? +# -------------------------- +# Before switching channels VDR will consider all of its devices to +# find the one with the least impact. This includes the device provided +# by the streamdev-client plugin. Streamdev-client will forward the +# request to its server which in turn checks all of its devices. Now if +# the server is running streamdev-client, too, the request will again +# be forwarded to its server and finally you will endup in a loop. +# +# What does the patch do? +# ----------------------- +# The patch adds the additional parameter "bool DVBCardsOnly" to VDR's +# device selection method cDevice::GetDevice(...). The parameter +# defaults to false which gives you the standard behaviour of GetDevice. +# When set to true, GetDevice will use only those devices with a card +# index < MAXDVBDEVICES, so only real DVB cards will be considered. +# Other devices like streamdev-client or DVB cards provided by plugin +# (Hauppauge PVR) won't be used. +# +# Author: Frank Schmirler (http://vdr.schmirler.de) +# +--- device.h.orig 2006-11-15 12:01:34.000000000 +0100 ++++ device.h 2006-11-15 12:02:15.000000000 +0100 +@@ -128,7 +128,7 @@ + ///< Gets the device with the given Index. + ///< \param Index must be in the range 0..numDevices-1. + ///< \return A pointer to the device, or NULL if the Index was invalid. +- static cDevice *GetDevice(const cChannel *Channel, int Priority = -1, bool *NeedsDetachReceivers = NULL); ++ static cDevice *GetDevice(const cChannel *Channel, int Priority = -1, bool *NeedsDetachReceivers = NULL, bool DVBCardsOnly = false); + ///< Returns a device that is able to receive the given Channel at the + ///< given Priority, with the least impact on active recordings and + ///< live viewing. +--- device.c.orig 2006-11-15 12:01:30.000000000 +0100 ++++ device.c 2006-11-22 12:28:05.000000000 +0100 +@@ -8,6 +8,7 @@ + */ + + #include "device.h" ++#include "dvbdevice.h" + #include + #include + #include +@@ -278,11 +279,13 @@ + return (0 <= Index && Index < numDevices) ? device[Index] : NULL; + } + +-cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers) ++cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers, bool DVBCardsOnly) + { + cDevice *d = NULL; + uint Impact = 0xFFFFFFFF; // we're looking for a device with the least impact + for (int i = 0; i < numDevices; i++) { ++ if (DVBCardsOnly && device[i]->CardIndex() >= MAXDVBDEVICES) ++ continue; + bool ndr; + if (device[i]->ProvidesChannel(Channel, Priority, &ndr)) { // this device is basicly able to do the job + // Put together an integer number that reflects the "impact" using +--- PLUGINS/src/streamdev/server/connection.c.orig 2006-11-15 12:10:11.000000000 +0100 ++++ PLUGINS/src/streamdev/server/connection.c 2006-11-15 12:10:59.000000000 +0100 +@@ -132,7 +132,7 @@ + Dprintf(" * GetDevice(const cChannel*, int)\n"); + Dprintf(" * -------------------------------\n"); + +- device = cDevice::GetDevice(Channel, Priority); ++ device = cDevice::GetDevice(Channel, Priority, NULL, true); + + Dprintf(" * Found following device: %p (%d)\n", device, + device ? device->CardIndex() + 1 : 0); +@@ -150,7 +150,7 @@ + const cChannel *current = Channels.GetByNumber(cDevice::CurrentChannel()); + isyslog("streamdev-server: Detaching current receiver"); + Detach(); +- device = cDevice::GetDevice(Channel, Priority); ++ device = cDevice::GetDevice(Channel, Priority, NULL, true); + Attach(); + Dprintf(" * Found following device: %p (%d)\n", device, + device ? device->CardIndex() + 1 : 0); diff --git a/patches/vdr-1.4.3-recursion_bigpatch.diff b/patches/vdr-1.4.3-recursion_bigpatch.diff new file mode 100644 index 0000000..5bb7854 --- /dev/null +++ b/patches/vdr-1.4.3-recursion_bigpatch.diff @@ -0,0 +1,88 @@ +# If you have two or more VDRs and you like them to mutually share +# there DVB cards you might need to apply this patch first. +# +# This is a modified version of the patch for VDRs with BIGPATCH. +# Thanks to p_body@vdrportal. +# +# IMPORTANT: As this patch does not only modify streamdev-server but +# also an exported method of VDR, you will need to +# +# !!!!! RECOMPILE VDR AND ALL PLUGINS !!!!! +# +# Why do I need the patch? +# -------------------------- +# Before switching channels VDR will consider all of its devices to +# find the one with the least impact. This includes the device provided +# by the streamdev-client plugin. Streamdev-client will forward the +# request to its server which in turn checks all of its devices. Now if +# the server is running streamdev-client, too, the request will again +# be forwarded to its server and finally you will endup in a loop. +# +# What does the patch do? +# ----------------------- +# The patch adds the additional parameter "bool DVBCardsOnly" to VDR's +# device selection method cDevice::GetDevice(...). The parameter +# defaults to false which gives you the standard behaviour of GetDevice. +# When set to true, GetDevice will use only those devices with a card +# index < MAXDVBDEVICES, so only real DVB cards will be considered. +# Other devices like streamdev-client or DVB cards provided by plugin +# (Hauppauge PVR) won't be used. +# +# Author: Frank Schmirler (http://vdr.schmirler.de) +# +--- device.h.orig 2006-11-15 12:01:34.000000000 +0100 ++++ device.h 2006-11-15 12:02:15.000000000 +0100 +@@ -128,7 +128,7 @@ + ///< Gets the device with the given Index. + ///< \param Index must be in the range 0..numDevices-1. + ///< \return A pointer to the device, or NULL if the Index was invalid. +- static cDevice *GetDevice(const cChannel *Channel, int Priority = -1, bool *NeedsDetachReceivers = NULL, bool LiveView = false); ++ static cDevice *GetDevice(const cChannel *Channel, int Priority = -1, bool *NeedsDetachReceivers = NULL, bool LiveView = false, bool DVBCardsOnly = false); + ///< Returns a device that is able to receive the given Channel at the + ///< given Priority, with the least impact on active recordings and + ///< live viewing. +--- device.c.orig 2006-11-15 12:01:30.000000000 +0100 ++++ device.c 2006-11-22 12:28:05.000000000 +0100 +@@ -8,6 +8,7 @@ + */ + + #include "device.h" ++#include "dvbdevice.h" + #include + #include + #include +@@ -278,11 +279,13 @@ + return (0 <= Index && Index < numDevices) ? device[Index] : NULL; + } + +-cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers, bool LiveView) ++cDevice *cDevice::GetDevice(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers, bool LiveView, bool DVBCardsOnly) + { + cDevice *d = NULL; + uint Impact = 0xFFFFFFFF; // we're looking for a device with the least impact + for (int i = 0; i < numDevices; i++) { ++ if (DVBCardsOnly && device[i]->CardIndex() >= MAXDVBDEVICES) ++ continue; + bool ndr; + if (device[i]->ProvidesChannel(Channel, Priority, &ndr)) { // this device is basicly able to do the job + // Put together an integer number that reflects the "impact" using +--- PLUGINS/src/streamdev/server/connection.c.orig 2006-11-15 12:10:11.000000000 +0100 ++++ PLUGINS/src/streamdev/server/connection.c 2006-11-15 12:10:59.000000000 +0100 +@@ -132,7 +132,7 @@ + Dprintf(" * GetDevice(const cChannel*, int)\n"); + Dprintf(" * -------------------------------\n"); + +- device = cDevice::GetDevice(Channel, Priority); ++ device = cDevice::GetDevice(Channel, Priority, NULL, NULL, true); + + Dprintf(" * Found following device: %p (%d)\n", device, + device ? device->CardIndex() + 1 : 0); +@@ -150,7 +150,7 @@ + const cChannel *current = Channels.GetByNumber(cDevice::CurrentChannel()); + isyslog("streamdev-server: Detaching current receiver"); + Detach(); +- device = cDevice::GetDevice(Channel, Priority); ++ device = cDevice::GetDevice(Channel, Priority, NULL, NULL, true); + Attach(); + Dprintf(" * Found following device: %p (%d)\n", device, + device ? device->CardIndex() + 1 : 0); diff --git a/patches/vdr-1.4.x-localchannelprovide.diff b/patches/vdr-1.4.x-localchannelprovide.diff new file mode 100644 index 0000000..857c1a2 --- /dev/null +++ b/patches/vdr-1.4.x-localchannelprovide.diff @@ -0,0 +1,102 @@ +# Apply this patch to VDR if you want to use a fullfeatured DVB card +# as pure output device. Infact the patch will keep VDR from using the +# tuner of any local DVB card (also budget cards). It will not affect +# other input devices like e.g. streamdev-client or DVB cards provided +# by plugins (e.g. Hauppauge PVR). +# +# By default the patch is DISABLED. There will be a new OSD menu entry +# in Setup->DVB which allows you to enable or disable the patch at any +# time. +diff -ru vdr-1.4.3.orig/config.c vdr-1.4.3/config.c +--- vdr-1.4.3.orig/config.c 2006-07-22 13:57:51.000000000 +0200 ++++ vdr-1.4.3/config.c 2006-11-16 08:16:37.000000000 +0100 +@@ -273,6 +273,7 @@ + CurrentChannel = -1; + CurrentVolume = MAXVOLUME; + CurrentDolby = 0; ++ LocalChannelProvide = 1; + InitialChannel = 0; + InitialVolume = -1; + } +@@ -434,6 +435,7 @@ + else if (!strcasecmp(Name, "CurrentChannel")) CurrentChannel = atoi(Value); + else if (!strcasecmp(Name, "CurrentVolume")) CurrentVolume = atoi(Value); + else if (!strcasecmp(Name, "CurrentDolby")) CurrentDolby = atoi(Value); ++ else if (!strcasecmp(Name, "LocalChannelProvide")) LocalChannelProvide = atoi(Value); + else if (!strcasecmp(Name, "InitialChannel")) InitialChannel = atoi(Value); + else if (!strcasecmp(Name, "InitialVolume")) InitialVolume = atoi(Value); + else +@@ -502,6 +504,7 @@ + Store("CurrentChannel", CurrentChannel); + Store("CurrentVolume", CurrentVolume); + Store("CurrentDolby", CurrentDolby); ++ Store("LocalChannelProvide",LocalChannelProvide); + Store("InitialChannel", InitialChannel); + Store("InitialVolume", InitialVolume); + +diff -ru vdr-1.4.3.orig/config.h vdr-1.4.3/config.h +--- vdr-1.4.3.orig/config.h 2006-09-23 15:56:08.000000000 +0200 ++++ vdr-1.4.3/config.h 2006-11-16 08:16:57.000000000 +0100 +@@ -250,6 +250,7 @@ + int CurrentChannel; + int CurrentVolume; + int CurrentDolby; ++ int LocalChannelProvide; + int InitialChannel; + int InitialVolume; + int __EndData__; +diff -ru vdr-1.4.3.orig/dvbdevice.c vdr-1.4.3/dvbdevice.c +--- vdr-1.4.3.orig/dvbdevice.c 2006-08-14 11:38:32.000000000 +0200 ++++ vdr-1.4.3/dvbdevice.c 2006-11-16 08:17:58.000000000 +0100 +@@ -766,6 +766,8 @@ + + bool cDvbDevice::ProvidesChannel(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers) const + { ++ if (Setup.LocalChannelProvide != 1) ++ return false; + bool result = false; + bool hasPriority = Priority < 0 || Priority > this->Priority(); + bool needsDetachReceivers = false; +diff -ru vdr-1.4.3.orig/i18n.c vdr-1.4.3/i18n.c +--- vdr-1.4.3.orig/i18n.c 2006-09-16 11:08:30.000000000 +0200 ++++ vdr-1.4.3/i18n.c 2006-11-16 08:36:53.000000000 +0100 +@@ -3546,6 +3546,28 @@ + "Foretrukket sprog", + "Preferovaný jazyk", + }, ++ { "Setup.DVB$Use DVB receivers", ++ "DVB Empfangsteile benutzen", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ "", ++ }, + { "Setup.DVB$Primary DVB interface", + "Primäres DVB-Interface", + "Primarna naprava", +diff -ru vdr-1.4.3.orig/menu.c vdr-1.4.3/menu.c +--- vdr-1.4.3.orig/menu.c 2006-07-23 11:23:11.000000000 +0200 ++++ vdr-1.4.3/menu.c 2006-11-16 08:37:27.000000000 +0100 +@@ -2354,6 +2354,7 @@ + + Clear(); + ++ Add(new cMenuEditBoolItem(tr("Setup.DVB$Use DVB receivers"), &data.LocalChannelProvide)); + Add(new cMenuEditIntItem( tr("Setup.DVB$Primary DVB interface"), &data.PrimaryDVB, 1, cDevice::NumDevices())); + Add(new cMenuEditBoolItem(tr("Setup.DVB$Video format"), &data.VideoFormat, "4:3", "16:9")); + if (data.VideoFormat == 0) diff --git a/patches/vdr-pluginactivity.diff b/patches/vdr-pluginactivity.diff new file mode 100644 index 0000000..0b173ae --- /dev/null +++ b/patches/vdr-pluginactivity.diff @@ -0,0 +1,113 @@ +diff -Nru -x PLUGINS vdr-1.3.12-orig/i18n.c vdr-1.3.12/i18n.c +--- vdr-1.3.12-orig/i18n.c 2004-05-28 15:19:29.000000000 +0200 ++++ vdr-1.3.12/i18n.c 2004-08-17 16:01:07.000000000 +0200 +@@ -1033,8 +1033,8 @@ + "´ÕÙáâÒØâÕÛìÝÞ ßÕàÕ×ÐßãáâØâì?", + "Zaista ponovo pokrenuti?", + }, +- { "Recording - restart anyway?", +- "Aufnahme läuft - trotzdem neu starten?", ++ { "Busy - restart anyway?", ++ "Beschäftigt - trotzdem neu starten?", + "Snemanje - zares ponoven zagon?", + "In registrazione - restart comunque?", + "Opname loopt - toch opnieuw starten?", +@@ -1052,8 +1052,8 @@ + "¸Ôñâ ×ÐßØáì - ÔÕÙáâÒØâÕÛìÝÞ ßÕàÕ×ÐßãáâØâì?", + "Snimanje traje - svejedno restart sistema?", + }, +- { "Recording - shut down anyway?", +- "Aufnahme läuft - trotzdem ausschalten?", ++ { "Busy - shut down anyway?", ++ "Beschäftigt - trotzdem ausschalten?", + "Snemanje - zares izklopi?", + "In registrazione - spengo comunque?", + "Opname loopt - toch uitschakelen?", +diff -Nru -x PLUGINS vdr-1.3.12-orig/menu.c vdr-1.3.12/menu.c +--- vdr-1.3.12-orig/menu.c 2004-06-13 22:26:51.000000000 +0200 ++++ vdr-1.3.12/menu.c 2004-08-17 16:00:07.000000000 +0200 +@@ -2201,7 +2201,7 @@ + + eOSState cMenuSetup::Restart(void) + { +- if (Interface->Confirm(cRecordControls::Active() ? tr("Recording - restart anyway?") : tr("Really restart?"))) { ++ if (Interface->Confirm((cRecordControls::Active() || cPluginManager::Active()) ? tr("Busy - restart anyway?") : tr("Really restart?"))) { + cThread::EmergencyExit(true); + return osEnd; + } +diff -Nru -x PLUGINS vdr-1.3.12-orig/plugin.c vdr-1.3.12/plugin.c +--- vdr-1.3.12-orig/plugin.c 2004-05-22 13:25:22.000000000 +0200 ++++ vdr-1.3.12/plugin.c 2004-08-17 15:57:52.000000000 +0200 +@@ -64,6 +64,11 @@ + { + } + ++bool cPlugin::Active(void) ++{ ++ return false; ++} ++ + const char *cPlugin::MainMenuEntry(void) + { + return NULL; +@@ -369,6 +374,18 @@ + return NULL; + } + ++bool cPluginManager::Active(void) ++{ ++ if (pluginManager) { ++ for (cDll *dll = pluginManager->dlls.First(); dll; dll = pluginManager->dlls.Next(dll)) { ++ cPlugin *p = dll->Plugin(); ++ if (p && p->Active()) ++ return true; ++ } ++ } ++ return false; ++} ++ + void cPluginManager::Shutdown(bool Log) + { + cDll *dll; +diff -Nru -x PLUGINS vdr-1.3.12-orig/plugin.h vdr-1.3.12/plugin.h +--- vdr-1.3.12-orig/plugin.h 2004-04-30 15:46:21.000000000 +0200 ++++ vdr-1.3.12/plugin.h 2004-08-17 15:56:51.000000000 +0200 +@@ -36,6 +36,7 @@ + virtual bool Initialize(void); + virtual bool Start(void); + virtual void Housekeeping(void); ++ virtual bool Active(void); + + virtual const char *MainMenuEntry(void); + virtual cOsdObject *MainMenuAction(void); +@@ -85,6 +86,7 @@ + static bool HasPlugins(void); + static cPlugin *GetPlugin(int Index); + static cPlugin *GetPlugin(const char *Name); ++ static bool Active(void); + void Shutdown(bool Log = false); + }; + +diff -Nru -x PLUGINS vdr-1.3.12-orig/vdr.c vdr-1.3.12/vdr.c +--- vdr-1.3.12-orig/vdr.c 2004-06-13 15:52:09.000000000 +0200 ++++ vdr-1.3.12/vdr.c 2004-08-17 15:59:18.000000000 +0200 +@@ -707,8 +707,8 @@ + Skins.Message(mtError, tr("Can't shutdown - option '-s' not given!")); + break; + } +- if (cRecordControls::Active()) { +- if (Interface->Confirm(tr("Recording - shut down anyway?"))) ++ if (cRecordControls::Active() || cPluginManager::Active()) { ++ if (Interface->Confirm(tr("Busy - shut down anyway?"))) + ForceShutdown = true; + } + LastActivity = 1; // not 0, see below! +@@ -821,7 +821,7 @@ + Skins.Message(mtInfo, tr("Editing process finished")); + } + } +- if (!Interact && ((!cRecordControls::Active() && !cCutter::Active() && (!Interface->HasSVDRPConnection() || UserShutdown)) || ForceShutdown)) { ++ if (!Interact && ((!cRecordControls::Active() && !cCutter::Active() && !cPluginManager::Active() && (!Interface->HasSVDRPConnection() || UserShutdown)) || ForceShutdown)) { + time_t Now = time(NULL); + if (Now - LastActivity > ACTIVITYTIMEOUT) { + // Shutdown: diff --git a/remux/extern.c b/remux/extern.c new file mode 100644 index 0000000..51c353c --- /dev/null +++ b/remux/extern.c @@ -0,0 +1,157 @@ +#include "remux/extern.h" +#include "server/streamer.h" +#include +#include +#include +#include +#include + +const char *g_ExternRemux = "/root/externremux.sh"; + +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); + virtual ~cTSExt(); + + void Put(const uchar *Data, int Count); +}; + +cTSExt::cTSExt(cRingBufferLinear *ResultBuffer): + m_ResultBuffer(ResultBuffer), + m_Active(false), + m_Process(0), + m_Inpipe(0), + m_Outpipe(0) +{ + int inpipe[2]; + int outpipe[2]; + + if (pipe(inpipe) == -1) { + LOG_ERROR_STR("pipe failed"); + return; + } + + if (pipe(outpipe) == -1) { + LOG_ERROR_STR("pipe failed"); + close(inpipe[0]); + close(inpipe[1]); + return; + } + + if ((m_Process = fork()) == -1) { + LOG_ERROR_STR("fork failed"); + close(inpipe[0]); + close(inpipe[1]); + close(outpipe[0]); + close(outpipe[1]); + return; + } + + if (m_Process == 0) { + // child process + dup2(inpipe[0], STDIN_FILENO); + close(inpipe[1]); + dup2(outpipe[1], STDOUT_FILENO); + close(outpipe[0]); + + int MaxPossibleFileDescriptors = getdtablesize(); + for (int i = STDERR_FILENO + 1; i < MaxPossibleFileDescriptors; i++) + close(i); //close all dup'ed filedescriptors + + //printf("starting externremux.sh\n"); + execl("/bin/sh", "sh", "-c", g_ExternRemux, NULL); + //printf("failed externremux.sh\n"); + _exit(-1); + } + + 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(m_Outpipe); + close(m_Inpipe); + kill(m_Process, SIGTERM); + for (int i = 0; waitpid(m_Process, NULL, WNOHANG) == 0; i++) { + if (i == 20) { + esyslog("streamdev-server: externremux process won't stop - killing it"); + kill(m_Process, SIGKILL); + } + cCondWait::SleepMs(100); + } + } +} + +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; + if ((result = m_ResultBuffer->Read(m_Outpipe)) == -1) { + LOG_ERROR_STR("read failed"); + break; + } + } + } + } + m_Active = false; +} + + +void cTSExt::Put(const uchar *Data, int Count) +{ + if (safe_write(m_Inpipe, Data, Count) == -1) { + LOG_ERROR_STR("write failed"); + return; + } +} + +cExternRemux::cExternRemux(int VPid, const int *APids, const int *Dpids, const int *SPids): + m_ResultBuffer(new cRingBufferLinear(WRITERBUFSIZE, TS_SIZE * 2)), + m_Remux(new cTSExt(m_ResultBuffer)) +{ + m_ResultBuffer->SetTimeouts(0, 100); +} + +cExternRemux::~cExternRemux() +{ + delete m_Remux; + delete m_ResultBuffer; +} + +int cExternRemux::Put(const uchar *Data, int Count) +{ + m_Remux->Put(Data, Count); + return Count; +} diff --git a/remux/extern.h b/remux/extern.h new file mode 100644 index 0000000..ae055ac --- /dev/null +++ b/remux/extern.h @@ -0,0 +1,25 @@ +#ifndef VDR_STREAMDEV_EXTERNREMUX_H +#define VDR_STREAMDEV_EXTERNREMUX_H + +#include "remux/tsremux.h" +#include + +extern const char *g_ExternRemux; + +class cTSExt; + +class cExternRemux: public cTSRemux { +private: + cRingBufferLinear *m_ResultBuffer; + cTSExt *m_Remux; + +public: + cExternRemux(int VPid, const int *APids, const int *Dpids, const int *SPids); + 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); } +}; + +#endif // VDR_STREAMDEV_EXTERNREMUX_H diff --git a/remux/ts2es.c b/remux/ts2es.c new file mode 100644 index 0000000..3476e24 --- /dev/null +++ b/remux/ts2es.c @@ -0,0 +1,140 @@ +#include "remux/ts2es.h" +#include "server/streamer.h" +#include "libdvbmpeg/transform.h" +#include "common.h" +#include + +// from VDR's remux.c +#define MAXNONUSEFULDATA (10*1024*1024) + +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; +} + +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 cRingBufferLinear(WRITERBUFSIZE, IPACKS)), + m_Remux(new cTS2ES(m_ResultBuffer)) +{ + m_ResultBuffer->SetTimeouts(0, 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) + break; // A cTS2ES might write one full packet and also a small rest + int pid = cTSRemux::GetPid(Data + i + 1); + if (Data[i + 3] & 0x10) { // got payload + if (m_Pid == pid) + m_Remux->PutTSPacket(Data + i); + } + used += TS_SIZE; + } + +/* + // Check if we're getting anywhere here: + if (!synced && skipped >= 0) { + if (skipped > MAXNONUSEFULDATA) { + esyslog("ERROR: no useful data seen within %d byte of video stream", skipped); + skipped = -1; + if (exitOnFailure) + cThread::EmergencyExit(true); + } + else + skipped += used; + } +*/ + + return used; +} + diff --git a/remux/ts2es.h b/remux/ts2es.h new file mode 100644 index 0000000..551df1d --- /dev/null +++ b/remux/ts2es.h @@ -0,0 +1,25 @@ +#ifndef VDR_STREAMDEV_TS2ESREMUX_H +#define VDR_STREAMDEV_TS2ESREMUX_H + +#include "remux/tsremux.h" +#include + +class cTS2ES; +class cRingBufferLinear; + +class cTS2ESRemux: public cTSRemux { +private: + int m_Pid; + cRingBufferLinear *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); } +}; + +#endif // VDR_STREAMDEV_TS2ESREMUX_H diff --git a/remux/ts2ps.c b/remux/ts2ps.c new file mode 100644 index 0000000..d0d08cf --- /dev/null +++ b/remux/ts2ps.c @@ -0,0 +1,211 @@ +#include "remux/ts2ps.h" +#include "server/streamer.h" +#include +#include + +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); +} + +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 cRingBufferLinear(WRITERBUFSIZE, IPACKS)), + m_ResultSkipped(0), + m_Skipped(0), + m_Synced(false), + m_IsRadio(VPid == 0 || VPid == 1 || VPid == 0x1FFF) +{ + m_ResultBuffer->SetTimeouts(0, 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) + break; // A cTS2PS might write one full packet and also a small rest + int pid = GetPid(Data + i + 1); + if (Data[i + 3] & 0x10) { // got payload + for (int t = 0; t < m_NumTracks; t++) { + if (m_Remux[t]->Pid() == pid) { + m_Remux[t]->PutTSPacket(Data + i); + break; + } + } + } + used += TS_SIZE; + } + + // Check if we're getting anywhere here: + if (!m_Synced && m_Skipped >= 0) + m_Skipped += used; + + return used; +} + +uchar *cTS2PSRemux::Get(int &Count) +{ + // Remove any previously skipped data from the result buffer: + + if (m_ResultSkipped > 0) { + m_ResultBuffer->Del(m_ResultSkipped); + m_ResultSkipped = 0; + } + + // Special VPID case to enable recording radio channels: + if (m_IsRadio) { + // Force syncing of radio channels to avoid "no useful data" error + m_Synced = true; + return m_ResultBuffer->Get(Count); + } + + // Check for frame borders: + Count = 0; + uchar *resultData = NULL; + int resultCount = 0; + uchar *data = m_ResultBuffer->Get(resultCount); + if (data) { + for (int i = 0; i < resultCount - 3; i++) { + if (data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1) { + int l = 0; + uchar StreamType = data[i + 3]; + if (VIDEO_STREAM_S <= StreamType && StreamType <= VIDEO_STREAM_E) { + uchar pt = NO_PICTURE; + l = ScanVideoPacket(data, resultCount, i, pt); + if (l < 0) + return resultData; + if (pt != NO_PICTURE) { + if (pt < I_FRAME || B_FRAME < pt) { + esyslog("ERROR: unknown picture type '%d'", pt); + } + else if (!m_Synced) { + if (pt == I_FRAME) { + m_ResultSkipped = i; // will drop everything before this position + SetBrokenLink(data + i, l); + m_Synced = true; + } + } + else if (Count) + return resultData; + } + } else { + l = GetPacketLength(data, resultCount, i); + if (l < 0) + return resultData; + } + if (m_Synced) { + if (!Count) + resultData = data + i; + Count += l; + } else + m_ResultSkipped = i + l; + if (l > 0) + i += l - 1; // the loop increments, too + } + } + } + return resultData; +} + diff --git a/remux/ts2ps.h b/remux/ts2ps.h new file mode 100644 index 0000000..334215a --- /dev/null +++ b/remux/ts2ps.h @@ -0,0 +1,29 @@ +#ifndef VDR_STREAMDEV_TS2PESREMUX_H +#define VDR_STREAMDEV_TS2PESREMUX_H + +#include "remux/tsremux.h" +#include +#include + +class cTS2PS; + +class cTS2PSRemux: public cTSRemux { +private: + int m_NumTracks; + cTS2PS *m_Remux[MAXTRACKS]; + cRingBufferLinear *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); } +}; + +#endif // VDR_STREAMDEV_TS2PESREMUX_H diff --git a/remux/tsremux.c b/remux/tsremux.c new file mode 100644 index 0000000..6be5245 --- /dev/null +++ b/remux/tsremux.c @@ -0,0 +1,59 @@ +#include "remux/tsremux.h" + +#define SC_PICTURE 0x00 // "picture header" + +void cTSRemux::SetBrokenLink(uchar *Data, int Length) +{ + if (Length > 9 && Data[0] == 0 && Data[1] == 0 && Data[2] == 1 && (Data[3] & 0xF0) == VIDEO_STREAM_S) { + for (int i = Data[8] + 9; i < Length - 7; i++) { // +9 to skip video packet header + 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) { + if (Length >= 8) { + int i = Offset + 8; // the minimum length of the video packet header + i += Data[i] + 1; // possible additional header bytes + for (; i < Offset + Length - 5; i++) { + if (Data[i] == 0 && Data[i + 1] == 0 && Data[i + 2] == 1) { + switch (Data[i + 3]) { + case SC_PICTURE: PictureType = (Data[i + 5] >> 3) & 0x07; + return Length; + } + } + } + } + PictureType = NO_PICTURE; + return Length; + } + return -1; +} + diff --git a/remux/tsremux.h b/remux/tsremux.h new file mode 100644 index 0000000..f7e4e09 --- /dev/null +++ b/remux/tsremux.h @@ -0,0 +1,33 @@ +#ifndef VDR_STREAMDEV_TSREMUX_H +#define VDR_STREAMDEV_TSREMUX_H + +#include "libdvbmpeg/transform.h" +#include + +#define RESULTBUFFERSIZE KILOBYTE(256) + +class cTSRemux { +protected: + /*uchar m_ResultBuffer[RESULTBUFFERSIZE]; + int m_ResultCount; + int m_ResultDelivered; + int m_Synced; + int m_Skipped; + int m_Sync; + + + virtual void PutTSPacket(int Pid, const uint8_t *Data) = 0; + +public: + cTSRemux(bool Sync = true); + virtual ~cTSRemux(); + + virtual uchar *Process(const uchar *Data, int &Count, int &Result);*/ + + 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); +}; + +#endif // VDR_STREAMDEV_TSREMUX_H diff --git a/server/component.c b/server/component.c new file mode 100644 index 0000000..1a584b5 --- /dev/null +++ b/server/component.c @@ -0,0 +1,48 @@ +/* + * $Id: component.c,v 1.3 2005/05/09 20:22:29 lordjaxom Exp $ + */ + +#include "server/component.h" +#include "server/connection.h" + +cServerComponent::cServerComponent(const char *Protocol, const char *ListenIp, + uint ListenPort): + m_Protocol(Protocol), + m_ListenIp(ListenIp), + m_ListenPort(ListenPort) +{ +} + +cServerComponent::~cServerComponent() +{ +} + +bool cServerComponent::Initialize(void) +{ + if (!m_Listen.Listen(m_ListenIp, m_ListenPort, 5)) { + esyslog("Streamdev: Couldn't listen (%s) %s:%d: %m", + m_Protocol, m_ListenIp, m_ListenPort); + return false; + } + isyslog("Streamdev: Listening (%s) on port %d", m_Protocol, m_ListenPort); + return true; +} + +void cServerComponent::Destruct(void) +{ + m_Listen.Close(); +} + +cServerConnection *cServerComponent::Accept(void) +{ + cServerConnection *client = NewClient(); + if (client->Accept(m_Listen)) { + isyslog("Streamdev: Accepted new client (%s) %s:%d", m_Protocol, + client->RemoteIp().c_str(), client->RemotePort()); + return client; + } else { + esyslog("Streamdev: Couldn't accept (%s): %m", m_Protocol); + delete client; + } + return NULL; +} diff --git a/server/component.h b/server/component.h new file mode 100644 index 0000000..8703348 --- /dev/null +++ b/server/component.h @@ -0,0 +1,51 @@ +/* + * $Id: component.h,v 1.2 2005/05/09 20:22:29 lordjaxom 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: + cTBSocket m_Listen; + const char *m_Protocol; + 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); + virtual ~cServerComponent(); + + /* Starts listening on the specified Port, override if you want to do things + different */ + virtual bool Initialize(void); + + /* Stops listening, override if you want to do things different */ + virtual void Destruct(void); + + /* Get the listening socket's file number */ + virtual int Socket(void) const { return (int)m_Listen; } + + /* Adds the listening socket to the Select object */ + virtual void Add(cTBSelect &Select) const { Select.Add(m_Listen); } + + /* Accepts the connection on a NewClient() object and calls the + Welcome() on it, override if you want to do things different */ + virtual cServerConnection *Accept(void); +}; + +#endif // VDR_STREAMDEV_SERVERS_COMPONENT_H diff --git a/server/componentHTTP.c b/server/componentHTTP.c new file mode 100644 index 0000000..70e95e9 --- /dev/null +++ b/server/componentHTTP.c @@ -0,0 +1,18 @@ +/* + * $Id: componentHTTP.c,v 1.2 2005/05/09 20:22:29 lordjaxom Exp $ + */ + +#include "server/componentHTTP.h" +#include "server/connectionHTTP.h" +#include "server/setup.h" + +cComponentHTTP::cComponentHTTP(void): + cServerComponent("HTTP", StreamdevServerSetup.HTTPBindIP, + StreamdevServerSetup.HTTPServerPort) +{ +} + +cServerConnection *cComponentHTTP::NewClient(void) +{ + return new cConnectionHTTP; +} diff --git a/server/componentHTTP.h b/server/componentHTTP.h new file mode 100644 index 0000000..29dc087 --- /dev/null +++ b/server/componentHTTP.h @@ -0,0 +1,18 @@ +/* + * $Id: componentHTTP.h,v 1.2 2005/05/09 20:22:29 lordjaxom Exp $ + */ + +#ifndef VDR_STREAMDEV_HTTPSERVER_H +#define VDR_STREAMDEV_HTTPSERVER_H + +#include "server/component.h" + +class cComponentHTTP: public cServerComponent { +protected: + virtual cServerConnection *NewClient(void); + +public: + cComponentHTTP(void); +}; + +#endif // VDR_STREAMDEV_HTTPSERVER_H diff --git a/server/componentVTP.c b/server/componentVTP.c new file mode 100644 index 0000000..ed2df1c --- /dev/null +++ b/server/componentVTP.c @@ -0,0 +1,18 @@ +/* + * $Id: componentVTP.c,v 1.2 2005/05/09 20:22:29 lordjaxom Exp $ + */ + +#include "server/componentVTP.h" +#include "server/connectionVTP.h" +#include "server/setup.h" + +cComponentVTP::cComponentVTP(void): + cServerComponent("VTP", StreamdevServerSetup.VTPBindIP, + StreamdevServerSetup.VTPServerPort) +{ +} + +cServerConnection *cComponentVTP::NewClient(void) +{ + return new cConnectionVTP; +} diff --git a/server/componentVTP.h b/server/componentVTP.h new file mode 100644 index 0000000..4f61eb5 --- /dev/null +++ b/server/componentVTP.h @@ -0,0 +1,18 @@ +/* + * $Id: componentVTP.h,v 1.2 2005/05/09 20:22:29 lordjaxom Exp $ + */ + +#ifndef VDR_STREAMDEV_SERVERS_SERVERVTP_H +#define VDR_STREAMDEV_SERVERS_SERVERVTP_H + +#include "server/component.h" + +class cComponentVTP: public cServerComponent { +protected: + virtual cServerConnection *NewClient(void); + +public: + cComponentVTP(void); +}; + +#endif // VDR_STREAMDEV_SERVERS_SERVERVTP_H diff --git a/server/connection.c b/server/connection.c new file mode 100644 index 0000000..dff1945 --- /dev/null +++ b/server/connection.c @@ -0,0 +1,198 @@ +/* + * $Id: connection.c,v 1.8 2007/01/15 12:00:19 schmirl Exp $ + */ + +#include "server/connection.h" +#include "server/setup.h" +#include "server/suspend.h" +#include "common.h" + +#include +#include +#include +#include + +cServerConnection::cServerConnection(const char *Protocol): + m_Protocol(Protocol), + m_DeferClose(false), + m_Pending(false), + m_ReadBytes(0), + m_WriteBytes(0), + m_WriteIndex(0) +{ +} + +cServerConnection::~cServerConnection() +{ +} + +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 (m_WriteBytes + length + 2 > sizeof(m_WriteBuffer)) { + esyslog("ERROR: streamdev: output buffer overflow (%s) for %s:%d", + m_Protocol, RemoteIp().c_str(), RemotePort()); + return false; + } + Dprintf("OUT: |%s|\n", buffer); + memcpy(m_WriteBuffer + m_WriteBytes, buffer, length); + free(buffer); + + m_WriteBytes += length; + m_WriteBuffer[m_WriteBytes++] = '\015'; + m_WriteBuffer[m_WriteBytes++] = '\012'; + m_Pending = !Last; + return true; +} + +cDevice *cServerConnection::GetDevice(const cChannel *Channel, int Priority) +{ + cDevice *device = NULL; + + /*Dprintf("+ Statistics:\n"); + Dprintf("+ Current Channel: %d\n", cDevice::CurrentChannel()); + Dprintf("+ Current Device: %d\n", cDevice::ActualDevice()->CardIndex()); + Dprintf("+ Transfer Mode: %s\n", cDevice::ActualDevice() + == cDevice::PrimaryDevice() ? "false" : "true"); + Dprintf("+ Replaying: %s\n", cDevice::PrimaryDevice()->Replaying() ? "true" + : "false");*/ + + Dprintf(" * GetDevice(const cChannel*, int)\n"); + Dprintf(" * -------------------------------\n"); + +#if VDRVERSNUM < 10500 + device = cDevice::GetDevice(Channel, Priority); +#else + device = cDevice::GetDevice(Channel, Priority, false); +#endif + + Dprintf(" * Found following device: %p (%d)\n", device, + device ? device->CardIndex() + 1 : 0); + if (device == cDevice::ActualDevice()) + Dprintf(" * is actual device\n"); + if (!cSuspendCtl::IsActive() && StreamdevServerSetup.SuspendMode != smAlways) + Dprintf(" * NOT suspended\n"); + + if (!device || (device == cDevice::ActualDevice() + && !cSuspendCtl::IsActive() + && StreamdevServerSetup.SuspendMode != smAlways)) { + // mustn't switch actual device + // maybe a device would be free if THIS connection did turn off its streams? + Dprintf(" * trying again...\n"); + const cChannel *current = Channels.GetByNumber(cDevice::CurrentChannel()); + isyslog("streamdev-server: Detaching current receiver"); + Detach(); +#if VDRVERSNUM < 10500 + device = cDevice::GetDevice(Channel, Priority); +#else + device = cDevice::GetDevice(Channel, Priority, false); +#endif + Attach(); + Dprintf(" * Found following device: %p (%d)\n", device, + device ? device->CardIndex() + 1 : 0); + if (device == cDevice::ActualDevice()) + Dprintf(" * is actual device\n"); + if (!cSuspendCtl::IsActive() + && StreamdevServerSetup.SuspendMode != smAlways) + Dprintf(" * NOT suspended\n"); + if (current && !TRANSPONDER(Channel, current)) + Dprintf(" * NOT same transponder\n"); + if (device && (device == cDevice::ActualDevice() + && !cSuspendCtl::IsActive() + && StreamdevServerSetup.SuspendMode != smAlways + && current != NULL + && !TRANSPONDER(Channel, current))) { + // now we would have to switch away live tv...let's see if live tv + // can be handled by another device + cDevice *newdev = NULL; + for (int i = 0; i < cDevice::NumDevices(); ++i) { + cDevice *dev = cDevice::GetDevice(i); + if (dev->ProvidesChannel(current, 0) && dev != device) { + newdev = dev; + break; + } + } + Dprintf(" * Found device for live tv: %p (%d)\n", newdev, + newdev ? newdev->CardIndex() + 1 : 0); + if (newdev == NULL || newdev == device) + // no suitable device to continue live TV, giving up... + device = NULL; + else + newdev->SwitchChannel(current, true); + } + } + + return device; +} diff --git a/server/connection.h b/server/connection.h new file mode 100644 index 0000000..f68f84a --- /dev/null +++ b/server/connection.h @@ -0,0 +1,90 @@ +/* + * $Id: connection.h,v 1.3 2005/05/09 20:22:29 lordjaxom Exp $ + */ + +#ifndef VDR_STREAMDEV_SERVER_CONNECTION_H +#define VDR_STREAMDEV_SERVER_CONNECTION_H + +#include "tools/socket.h" +#include "common.h" + +class cChannel; +class cDevice; + +/* Basic capabilities of a straight text-based protocol, most functions + virtual to support more complicated protocols */ + +class cServerConnection: public cListObject, public cTBSocket +{ +private: + const char *m_Protocol; + bool m_DeferClose; + bool m_Pending; + + char m_ReadBuffer[MAXPARSEBUFFER]; + uint m_ReadBytes; + + char m_WriteBuffer[MAXPARSEBUFFER]; + uint m_WriteBytes; + uint m_WriteIndex; + +protected: + /* Will be called when a command terminated by a newline has been + received */ + virtual bool Command(char *Cmd) = 0; + + /* Will put Message into the response queue, which will be sent in the next + server cycle. Note that Message will be line-terminated by Respond. + Only one line at a time may be sent. If there are lines to follow, set + Last to false. Command(NULL) will be called in the next cycle, so you can + post the next line. */ + virtual bool Respond(const char *Message, bool Last = true, ...) + __attribute__ ((format (printf, 2, 4))); + +public: + /* If you derive, specify a short string such as HTTP for Protocol, which + will be displayed in error messages */ + cServerConnection(const char *Protocol); + virtual ~cServerConnection(); + + /* 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); + + /* Will make the socket close after sending all queued output data */ + void DeferClose(void) { m_DeferClose = true; } + + /* Will retrieve an unused device for transmitting data. Use the returned + cDevice in a following call to StartTransfer */ + cDevice *GetDevice(const cChannel *Channel, int Priority); + + virtual void Flushed(void) {} + + virtual void Detach(void) = 0; + virtual void Attach(void) = 0; +}; + +inline bool cServerConnection::HasData(void) const +{ + return m_WriteBytes > 0 || m_Pending || m_DeferClose; +} + +#endif // VDR_STREAMDEV_SERVER_CONNECTION_H diff --git a/server/connectionHTTP.c b/server/connectionHTTP.c new file mode 100644 index 0000000..a526da9 --- /dev/null +++ b/server/connectionHTTP.c @@ -0,0 +1,200 @@ +/* + * $Id: connectionHTTP.c,v 1.10 2006/01/26 19:40:18 lordjaxom Exp $ + */ + +#include + +#include "server/connectionHTTP.h" +#include "server/setup.h" + +cConnectionHTTP::cConnectionHTTP(void): + cServerConnection("HTTP"), + m_Status(hsRequest), + m_LiveStreamer(NULL), + m_Channel(NULL), + m_Apid(0), + m_StreamType((eStreamType)StreamdevServerSetup.HTTPStreamType), + m_ListChannel(NULL) +{ + Dprintf("constructor hsRequest\n"); +} + +cConnectionHTTP::~cConnectionHTTP() +{ + delete m_LiveStreamer; +} + +bool cConnectionHTTP::Command(char *Cmd) +{ + Dprintf("command %s\n", Cmd); + switch (m_Status) { + case hsRequest: + Dprintf("Request\n"); + m_Request = Cmd; + m_Status = hsHeaders; + return true; + + case hsHeaders: + if (*Cmd == '\0') { + m_Status = hsBody; + return ProcessRequest(); + } + Dprintf("header\n"); + return true; + } + return false; // ??? shouldn't happen +} + +bool cConnectionHTTP::ProcessRequest(void) +{ + Dprintf("process\n"); + if (m_Request.substr(0, 4) == "GET " && CmdGET(m_Request.substr(4))) { + switch (m_Job) { + case hjListing: + return Respond("HTTP/1.0 200 OK") + && Respond("Content-Type: text/html") + && Respond("") + && Respond("VDR Channel Listing") + && Respond("
    "); + + case hjTransfer: + if (m_Channel == NULL) { + DeferClose(); + return Respond("HTTP/1.0 404 not found"); + } + + m_LiveStreamer = new cStreamdevLiveStreamer(0); + cDevice *device = GetDevice(m_Channel, 0); + if (device != NULL) { + device->SwitchChannel(m_Channel, false); + if (m_LiveStreamer->SetChannel(m_Channel, m_StreamType, m_Apid)) { + m_LiveStreamer->SetDevice(device); + if (m_StreamType == stES && (m_Apid != 0 || ISRADIO(m_Channel))) { + return Respond("HTTP/1.0 200 OK") + && Respond("Content-Type: audio/mpeg") + && Respond("icy-name: %s", true, m_Channel->Name()) + && Respond(""); + } else { + return Respond("HTTP/1.0 200 OK") + && Respond("Content-Type: video/mpeg") + && Respond(""); + } + } + } + DELETENULL(m_LiveStreamer); + DeferClose(); + return Respond("HTTP/1.0 409 Channel not available") + && Respond(""); + } + } + + DeferClose(); + return Respond("HTTP/1.0 400 Bad Request") + && Respond(""); +} + +void cConnectionHTTP::Flushed(void) +{ + std::string line; + + if (m_Status != hsBody) + return; + + switch (m_Job) { + case hjListing: + if (m_ListChannel == NULL) { + Respond("
"); + DeferClose(); + m_Status = hsFinished; + return; + } + + if (m_ListChannel->GroupSep()) + line = (std::string)"
  • --- " + m_ListChannel->Name() + "---
  • "; + else { + int index = 1; + line = (std::string)"
  • GetChannelID().ToString() + "\">" + + m_ListChannel->Name() + " "; + for (int i = 0; m_ListChannel->Apid(i) != 0; ++i, ++index) { + line += "GetChannelID().ToString() + "+" + + (const char*)itoa(index) + "\">(" + + m_ListChannel->Alang(i) + ") "; + } + for (int i = 0; m_ListChannel->Dpid(i) != 0; ++i, ++index) { + line += "GetChannelID().ToString() + "+" + + (const char*)itoa(index) + "\">(" + + m_ListChannel->Dlang(i) + ") "; + } + line += "
  • "; + } + if (!Respond(line.c_str())) + DeferClose(); + m_ListChannel = Channels.Next(m_ListChannel); + break; + + case hjTransfer: + Dprintf("streamer start\n"); + m_LiveStreamer->Start(this); + m_Status = hsFinished; + break; + } +} + +bool cConnectionHTTP::CmdGET(const std::string &Opts) +{ + const char *sp = Opts.c_str(), *ptr = sp, *ep; + const cChannel *chan; + int apid = 0, pos; + + ptr = skipspace(ptr); + while (*ptr == '/') + ++ptr; + + if (strncasecmp(ptr, "PS/", 3) == 0) { + m_StreamType = stPS; + ptr += 3; + } else if (strncasecmp(ptr, "PES/", 4) == 0) { + m_StreamType = stPES; + ptr += 4; + } else if (strncasecmp(ptr, "TS/", 3) == 0) { + m_StreamType = stTS; + ptr += 3; + } else if (strncasecmp(ptr, "ES/", 3) == 0) { + m_StreamType = stES; + ptr += 3; + } else if (strncasecmp(ptr, "Extern/", 3) == 0) { + m_StreamType = stExtern; + ptr += 7; + } + + while (*ptr == '/') + ++ptr; + for (ep = ptr + strlen(ptr); ep >= ptr && !isspace(*ep); --ep) + ; + + std::string filespec = Opts.substr(ptr - sp, ep - ptr); + Dprintf("substr: %s\n", filespec.c_str()); + + Dprintf("before channelfromstring\n"); + if (filespec == "" || filespec.substr(0, 12) == "channels.htm") { + m_ListChannel = Channels.First(); + m_Job = hjListing; + } else if ((chan = ChannelFromString(filespec.c_str(), &apid)) != NULL) { + m_Channel = chan; + m_Apid = apid; + Dprintf("Apid is %d\n", apid); + m_Job = hjTransfer; + } + Dprintf("after channelfromstring\n"); + return true; +} + diff --git a/server/connectionHTTP.h b/server/connectionHTTP.h new file mode 100644 index 0000000..d12c418 --- /dev/null +++ b/server/connectionHTTP.h @@ -0,0 +1,58 @@ +/* + * $Id: connectionHTTP.h,v 1.3 2005/02/11 16:44:15 lordjaxom Exp $ + */ + +#ifndef VDR_STREAMDEV_SERVERS_CONNECTIONHTTP_H +#define VDR_STREAMDEV_SERVERS_CONNECTIONHTTP_H + +#include "connection.h" +#include "server/livestreamer.h" + +#include + +class cChannel; +class cStreamdevLiveStreamer; + +class cConnectionHTTP: public cServerConnection { +private: + enum eHTTPStatus { + hsRequest, + hsHeaders, + hsBody, + hsFinished, + }; + + enum eHTTPJob { + hjTransfer, + hjListing, + }; + + std::string m_Request; + //std::map m_Headers; TODO: later? + eHTTPStatus m_Status; + eHTTPJob m_Job; + // job: transfer + cStreamdevLiveStreamer *m_LiveStreamer; + const cChannel *m_Channel; + int m_Apid; + eStreamType m_StreamType; + // job: listing + const cChannel *m_ListChannel; + +protected: + bool ProcessRequest(void); + +public: + cConnectionHTTP(void); + virtual ~cConnectionHTTP(); + + virtual void Attach(void) { if (m_LiveStreamer != NULL) m_LiveStreamer->Attach(); } + virtual void Detach(void) { if (m_LiveStreamer != NULL) m_LiveStreamer->Detach(); } + + virtual bool Command(char *Cmd); + bool CmdGET(const std::string &Opts); + + virtual void Flushed(void); +}; + +#endif // VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H diff --git a/server/connectionVTP.c b/server/connectionVTP.c new file mode 100644 index 0000000..18ea353 --- /dev/null +++ b/server/connectionVTP.c @@ -0,0 +1,990 @@ +/* + * $Id: connectionVTP.c,v 1.8 2007/03/02 15:27:07 schmirl Exp $ + */ + +#include "server/connectionVTP.h" +#include "server/livestreamer.h" +#include "server/suspend.h" +#include "setup.h" + +#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?) +*/ + +// --- cLSTEHandler ----------------------------------------------------------- + +class cLSTEHandler +{ +private: + enum eStates { Channel, Event, Title, Subtitle, Description, Vps, + EndEvent, EndChannel, EndEPG }; + cConnectionVTP *m_Client; + cSchedulesLock *m_SchedulesLock; + const cSchedules *m_Schedules; + const cSchedule *m_Schedule; + const cEvent *m_Event; + int m_Errno; + char *m_Error; + eStates m_State; + bool m_Traverse; +public: + cLSTEHandler(cConnectionVTP *Client, const char *Option); + ~cLSTEHandler(); + bool Next(bool &Last); +}; + +cLSTEHandler::cLSTEHandler(cConnectionVTP *Client, const char *Option): + m_Client(Client), + m_SchedulesLock(new cSchedulesLock(false, 500)), + m_Schedules(cSchedules::Schedules(*m_SchedulesLock)), + m_Schedule(NULL), + m_Event(NULL), + m_Errno(0), + m_Error(NULL), + m_State(Channel), + m_Traverse(false) +{ + eDumpMode dumpmode = dmAll; + time_t attime = 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 == dmAll) { + if (strcasecmp(p, "NOW") == 0) + dumpmode = dmPresent; + else if (strcasecmp(p, "NEXT") == 0) + dumpmode = dmFollowing; + else if (strcasecmp(p, "AT") == 0) { + dumpmode = dmAtTime; + if ((p = strtok_r(NULL, delim, &strtok_next)) != NULL) { + if (isnumber(p)) + attime = strtol(p, NULL, 10); + else { + m_Errno = 501; + m_Error = strdup("Invalid time"); + break; + } + } else { + m_Errno = 501; + m_Error = strdup("Missing time"); + break; + } + } else if (!m_Schedule) { + cChannel* Channel = NULL; + if (isnumber(p)) + Channel = Channels.GetByNumber(strtol(Option, NULL, 10)); + else + Channel = Channels.GetByChannelID(tChannelID::FromString( + Option)); + if (Channel) { + m_Schedule = m_Schedules->GetSchedule(Channel->GetChannelID()); + if (!m_Schedule) { + m_Errno = 550; + m_Error = strdup("No schedule found"); + break; + } + } else { + m_Errno = 550; + asprintf(&m_Error, "Channel \"%s\" not defined", p); + break; + } + } else { + m_Errno = 501; + asprintf(&m_Error, "Unknown option: \"%s\"", p); + break; + } + p = strtok_r(NULL, delim, &strtok_next); + } + } else if (m_Schedules == NULL) { + m_Errno = 451; + m_Error = strdup("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 dmAll: m_Event = m_Schedule->Events()->First(); + m_Traverse = true; + break; + case dmPresent: m_Event = m_Schedule->GetPresentEvent(); + break; + case dmFollowing: m_Event = m_Schedule->GetFollowingEvent(); + break; + case dmAtTime: m_Event = m_Schedule->GetEventAround(attime); + break; + + } + } + } +} + +cLSTEHandler::~cLSTEHandler() +{ + delete m_SchedulesLock; + if (m_Error != NULL) + free(m_Error); +} + +bool cLSTEHandler::Next(bool &Last) +{ + char *buffer; + + if (m_Error != NULL) { + Last = true; + cString str(m_Error, true); + m_Error = NULL; + return m_Client->Respond(m_Errno, *str); + } + + Last = false; + switch (m_State) { + case Channel: + if (m_Schedule != NULL) { + cChannel *channel = Channels.GetByChannelID(m_Schedule->ChannelID(), + true); + if (channel != NULL) { + m_State = Event; + return m_Client->Respond(-215, "C %s %s", + *channel->GetChannelID().ToString(), + channel->Name()); + } else { + esyslog("ERROR: vdr streamdev: unable to find channel %s by ID", + *m_Schedule->ChannelID().ToString()); + m_State = EndChannel; + return Next(Last); + } + } else { + m_State = EndEPG; + return Next(Last); + } + break; + + case Event: + if (m_Event != NULL) { + m_State = Title; + return m_Client->Respond(-215, "E %u %ld %d %X", m_Event->EventID(), + 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 = EndEvent; + if (m_Event->Vps()) + return m_Client->Respond(-215, "V %ld", m_Event->Vps()); + else + return Next(Last); + break; + + case EndEvent: + if (m_Traverse) + m_Event = m_Schedule->Events()->Next(m_Event); + 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; + char *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_Error(NULL), + m_Traverse(false) +{ + if (!Channels.Lock(false, 500)) { + m_Errno = 451; + m_Error = strdup("Channels are being modified - try again"); + } else if (*Option) { + if (isnumber(Option)) { + m_Channel = Channels.GetByNumber(strtol(Option, NULL, 10)); + if (m_Channel == NULL) { + m_Errno = 501; + asprintf(&m_Error, "Channel \"%s\" not defined", Option); + return; + } + } else { + int i = 1; + m_Traverse = true; + m_Option = strdup(Option); + while (i <= Channels.MaxNumber()) { + m_Channel = Channels.GetByNumber(i, 1); + if (strcasestr(m_Channel->Name(), Option) != NULL) + break; + i = m_Channel->Number() + 1; + } + + if (i > Channels.MaxNumber()) { + m_Errno = 501; + asprintf(&m_Error, "Channel \"%s\" not defined", Option); + return; + } + } + } else if (Channels.MaxNumber() >= 1) { + m_Channel = Channels.GetByNumber(1, 1); + m_Traverse = true; + } else { + m_Errno = 550; + m_Error = strdup("No channels defined"); + } +} + +cLSTCHandler::~cLSTCHandler() +{ + Channels.Unlock(); + if (m_Error != NULL) + free(m_Error); + if (m_Option != NULL) + free(m_Option); +} + +bool cLSTCHandler::Next(bool &Last) +{ + if (m_Error != NULL) { + Last = true; + cString str(m_Error, true); + m_Error = NULL; + return m_Client->Respond(m_Errno, *str); + } + + int number; + char *buffer; + + number = m_Channel->Number(); + buffer = strdup(*m_Channel->ToText()); + buffer[strlen(buffer) - 1] = '\0'; // remove \n + cString str(buffer, true); + + Last = true; + if (m_Traverse) { + int i = m_Channel->Number() + 1; + while (i <= Channels.MaxNumber()) { + m_Channel = Channels.GetByNumber(i, 1); + if (m_Channel != NULL) { + if (m_Option == NULL || strcasestr(m_Channel->Name(), + m_Option) != NULL) + break; + i = m_Channel->Number() + 1; + } else { + m_Errno = 501; + asprintf(&m_Error, "Channel \"%d\" not found", i); + } + } + + if (i < Channels.MaxNumber()) + Last = false; + } + + return m_Client->Respond(Last ? 250 : -250, "%d %s", number, buffer); +} + +// --- cLSTTHandler ----------------------------------------------------------- + +class cLSTTHandler +{ +private: + cConnectionVTP *m_Client; + cTimer *m_Timer; + int m_Index; + int m_Errno; + char *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_Error(NULL), + m_Traverse(false) +{ + if (*Option) { + if (isnumber(Option)) { + m_Timer = Timers.Get(strtol(Option, NULL, 10) - 1); + if (m_Timer == NULL) { + m_Errno = 501; + asprintf(&m_Error, "Timer \"%s\" not defined", Option); + } + } else { + m_Errno = 501; + asprintf(&m_Error, "Error in timer number \"%s\"", Option); + } + } else if (Timers.Count()) { + m_Traverse = true; + m_Index = 0; + m_Timer = Timers.Get(m_Index); + if (m_Timer == NULL) { + m_Errno = 501; + asprintf(&m_Error, "Timer \"%d\" not found", m_Index + 1); + } + } else { + m_Errno = 550; + m_Error = strdup("No timers defined"); + } +} + +cLSTTHandler::~cLSTTHandler() +{ + if (m_Error != NULL) + free(m_Error); +} + +bool cLSTTHandler::Next(bool &Last) +{ + if (m_Error != NULL) { + Last = true; + cString str(m_Error, true); + m_Error = NULL; + return m_Client->Respond(m_Errno, *str); + } + + bool result; + char *buffer; + Last = !m_Traverse || m_Index >= Timers.Count() - 1; + buffer = strdup(*m_Timer->ToText()); + buffer[strlen(buffer) - 1] = '\0'; // strip \n + result = m_Client->Respond(Last ? 250 : -250, "%d %s", m_Timer->Index() + 1, + buffer); + free(buffer); + + if (m_Traverse && !Last) { + m_Timer = Timers.Get(++m_Index); + if (m_Timer == NULL) { + m_Errno = 501; + asprintf(&m_Error, "Timer \"%d\" not found", m_Index + 1); + } + } + return result; +} + +// --- cConnectionVTP --------------------------------------------------------- + +cConnectionVTP::cConnectionVTP(void): + cServerConnection("VTP"), + m_LiveSocket(NULL), + m_LiveStreamer(NULL), + m_LastCommand(NULL), + m_NoTSPIDS(false), + m_LSTEHandler(NULL), + m_LSTCHandler(NULL), + m_LSTTHandler(NULL) +{ +} + +cConnectionVTP::~cConnectionVTP() +{ + if (m_LastCommand != NULL) + free(m_LastCommand); + delete m_LiveStreamer; + delete m_LiveSocket; + delete m_LSTTHandler; + delete m_LSTCHandler; + delete m_LSTEHandler; +} + +void cConnectionVTP::Welcome(void) +{ + Respond(220, "Welcome to Video Disk Recorder (VTP)"); +} + +void cConnectionVTP::Reject(void) +{ + Respond(221, "Too many clients or client not allowed to connect"); + cServerConnection::Reject(); +} + +void cConnectionVTP::Detach(void) +{ + if (m_LiveStreamer != NULL) m_LiveStreamer->Detach(); +} + +void cConnectionVTP::Attach(void) +{ + if (m_LiveStreamer != NULL) m_LiveStreamer->Attach(); +} + +bool cConnectionVTP::Command(char *Cmd) +{ + char *param = NULL; + + if (Cmd != NULL) { + if (m_LastCommand != NULL) { + esyslog("ERROR: streamdev: protocol violation (VTP) from %s:%d", + RemoteIp().c_str(), RemotePort()); + return false; + } + + if ((param = strchr(Cmd, ' ')) != NULL) + *(param++) = '\0'; + else + param = Cmd + strlen(Cmd); + m_LastCommand = strdup(Cmd); + } else { + Cmd = m_LastCommand; + param = NULL; + } + + if (strcasecmp(Cmd, "LSTE") == 0) return CmdLSTE(param); + //else if (strcasecmp(Cmd, "LSTR") == 0) return CmdLSTR(param); + else if (strcasecmp(Cmd, "LSTT") == 0) return CmdLSTT(param); + else if (strcasecmp(Cmd, "LSTC") == 0) return CmdLSTC(param); + + if (param == NULL) { + esyslog("ERROR: streamdev: this seriously shouldn't happen at %s:%d", + __FILE__, __LINE__); + return false; + } + + if (strcasecmp(Cmd, "CAPS") == 0) return CmdCAPS(param); + else if (strcasecmp(Cmd, "PROV") == 0) return CmdPROV(param); + else if (strcasecmp(Cmd, "PORT") == 0) return CmdPORT(param); + else if (strcasecmp(Cmd, "TUNE") == 0) return CmdTUNE(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(param); + else if (strcasecmp(Cmd, "SUSP") == 0) return CmdSUSP(param); + // Commands adopted from SVDRP + //else if (strcasecmp(Cmd, "DELR") == 0) return CmdDELR(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 + return Respond(500, "Unknown Command \"%s\"", Cmd); +} + +bool cConnectionVTP::CmdCAPS(char *Opts) +{ + char *buffer; + + if (strcasecmp(Opts, "TS") == 0) { + m_NoTSPIDS = true; + return Respond(220, "Ignored, capability \"%s\" accepted for " + "compatibility", Opts); + } + + if (strcasecmp(Opts, "TSPIDS") == 0) { + m_NoTSPIDS = false; + return Respond(220, "Capability \"%s\" accepted", Opts); + } + + return Respond(561, "Capability \"%s\" not known", Opts); +} + +bool cConnectionVTP::CmdPROV(char *Opts) +{ + const cChannel *chan; + int prio; + char *ep; + + prio = strtol(Opts, &ep, 10); + if (ep == Opts || !isspace(*ep)) + return Respond(501, "Use: PROV Priority Channel"); + + Opts = skipspace(ep); + if ((chan = ChannelFromString(Opts)) == NULL) + return Respond(550, "Undefined channel \"%s\"", Opts); + + return GetDevice(chan, prio) != NULL + ? Respond(220, "Channel available") + : Respond(560, "Channel not available"); +} + +bool cConnectionVTP::CmdPORT(char *Opts) +{ + uint id, dataport = 0; + char dataip[20]; + char *ep, *ipoffs; + int n; + + id = strtoul(Opts, &ep, 10); + if (ep == Opts || !isspace(*ep)) + return Respond(500, "Use: PORT Id Destination"); + + if (id != 0) + 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); + + 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 (id == siLive) + m_LiveStreamer->Start(m_LiveSocket); + + return Respond(220, "Port command ok, data connection opened"); +} + +bool cConnectionVTP::CmdTUNE(char *Opts) +{ + const cChannel *chan; + cDevice *dev; + + if ((chan = ChannelFromString(Opts)) == NULL) + return Respond(550, "Undefined channel \"%s\"", Opts); + + if ((dev = GetDevice(chan, 0)) == NULL) + return Respond(560, "Channel not available"); + + if (!dev->SwitchChannel(chan, false)) + return Respond(560, "Channel not available"); + + delete m_LiveStreamer; + m_LiveStreamer = new cStreamdevLiveStreamer(1); + m_LiveStreamer->SetChannel(chan, m_NoTSPIDS ? stTS : stTSPIDS); + m_LiveStreamer->SetDevice(dev); + + return Respond(220, "Channel tuned"); +} + +bool cConnectionVTP::CmdADDP(char *Opts) +{ + int pid; + char *end; + + pid = strtoul(Opts, &end, 10); + if (end == Opts || (*end != '\0' && *end != ' ')) + return Respond(500, "Use: ADDP Pid"); + + return m_LiveStreamer && m_LiveStreamer->SetPid(pid, true) + ? Respond(220, "Pid %d available", pid) + : Respond(560, "Pid %d not available", pid); +} + +bool cConnectionVTP::CmdDELP(char *Opts) +{ + int pid; + char *end; + + pid = strtoul(Opts, &end, 10); + if (end == Opts || (*end != '\0' && *end != ' ')) + return Respond(500, "Use: DELP Pid"); + + return m_LiveStreamer && m_LiveStreamer->SetPid(pid, false) + ? Respond(220, "Pid %d stopped", pid) + : Respond(560, "Pid %d not transferring", pid); +} + +bool cConnectionVTP::CmdADDF(char *Opts) +{ +#if VDRVERSNUM >= 10300 + int pid, tid, mask; + char *ep; + + if (m_LiveStreamer == NULL) + return Respond(560, "Can't set filters without a 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_LiveStreamer->SetFilter(pid, tid, mask, true) + ? Respond(220, "Filter %d transferring", pid) + : Respond(560, "Filter %d not available", pid); +#else + return Respond(500, "ADDF known but unimplemented with VDR < 1.3.0"); +#endif +} + +bool cConnectionVTP::CmdDELF(char *Opts) +{ +#if VDRVERSNUM >= 10307 + int pid, tid, mask; + char *ep; + + if (m_LiveStreamer == 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"); + + return m_LiveStreamer->SetFilter(pid, tid, mask, false) + ? Respond(220, "Filter %d stopped", pid) + : Respond(560, "Filter %d not transferring", pid); +#else + return Respond(500, "DELF known but unimplemented with VDR < 1.3.0"); +#endif +} + +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 0: DELETENULL(m_LiveStreamer); break; + } + + DELETENULL(m_LiveSocket); + return Respond(220, "Data connection closed"); +} + +bool cConnectionVTP::CmdQUIT(char *Opts) +{ + DeferClose(); + return Respond(221, "Video Disk Recorder closing connection"); +} + +bool cConnectionVTP::CmdSUSP(char *Opts) +{ + if (StreamdevServerSetup.SuspendMode == smAlways || cSuspendCtl::IsActive()) + return Respond(220, "Server is suspended"); + else if (StreamdevServerSetup.SuspendMode == smOffer + && StreamdevServerSetup.AllowSuspend) { + cControl::Launch(new cSuspendCtl); + return Respond(220, "Server is suspended"); + } else + return Respond(550, "Client may not suspend server"); +} + +// Functions extended from SVDRP + +template +bool cConnectionVTP::CmdLSTX(cHandler *&Handler, char *Option) +{ + if (Option != NULL) { + delete Handler; + Handler = new cHandler(this, Option); + } + + bool last, 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); +} + +// Functions adopted from SVDRP +#define INIT_WRAPPER() bool _res +#define Reply(c,m...) _res = Respond(c,m) +#define EXIT_WRAPPER() return _res + +bool cConnectionVTP::CmdMODT(const char *Option) +{ + INIT_WRAPPER(); + if (*Option) { + char *tail; + int n = strtol(Option, &tail, 10); + if (tail && tail != Option) { + tail = skipspace(tail); + cTimer *timer = Timers.Get(n - 1); + if (timer) { + cTimer t = *timer; + if (strcasecmp(tail, "ON") == 0) + t.SetFlags(tfActive); + else if (strcasecmp(tail, "OFF") == 0) + t.ClrFlags(tfActive); + else if (!t.Parse(tail)) { + Reply(501, "Error in timer settings"); + EXIT_WRAPPER(); + } + *timer = t; + Timers.SetModified(); + isyslog("timer %s modified (%s)", *timer->ToDescr(), + timer->HasFlags(tfActive) ? "active" : "inactive"); + Reply(250, "%d %s", timer->Index() + 1, *timer->ToText()); + } else + Reply(501, "Timer \"%d\" not defined", n); + } else + Reply(501, "Error in timer number"); + } else + Reply(501, "Missing timer settings"); + EXIT_WRAPPER(); +} + +bool cConnectionVTP::CmdNEWT(const char *Option) +{ + INIT_WRAPPER(); + if (*Option) { + cTimer *timer = new cTimer; + if (timer->Parse(Option)) { + cTimer *t = Timers.GetTimer(timer); + if (!t) { + Timers.Add(timer); + Timers.SetModified(); + isyslog("timer %s added", *timer->ToDescr()); + Reply(250, "%d %s", timer->Index() + 1, *timer->ToText()); + EXIT_WRAPPER(); + } else + Reply(550, "Timer already defined: %d %s", t->Index() + 1, + *t->ToText()); + } else + Reply(501, "Error in timer settings"); + delete timer; + } else + Reply(501, "Missing timer settings"); + EXIT_WRAPPER(); +} + +bool cConnectionVTP::CmdDELT(const char *Option) +{ + INIT_WRAPPER(); + if (*Option) { + if (isnumber(Option)) { + cTimer *timer = Timers.Get(strtol(Option, NULL, 10) - 1); + if (timer) { + if (!timer->Recording()) { + isyslog("deleting timer %s", *timer->ToDescr()); + Timers.Del(timer); + Timers.SetModified(); + Reply(250, "Timer \"%s\" deleted", Option); + } else + Reply(550, "Timer \"%s\" is recording", Option); + } else + Reply(501, "Timer \"%s\" not defined", Option); + } else + Reply(501, "Error in timer number \"%s\"", Option); + } else + Reply(501, "Missing timer number"); + EXIT_WRAPPER(); +} + +/*bool cConnectionVTP::CmdLSTR(char *Option) { + INIT_WRAPPER(); + bool recordings = Recordings.Load(); + Recordings.Sort(); + if (*Option) { + if (isnumber(Option)) { + cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1); + if (recording) { + if (recording->Summary()) { + char *summary = strdup(recording->Summary()); + Reply(250, "%s", strreplace(summary,'\n','|')); + free(summary); + } + else + Reply(550, "No summary availabe"); + } + else + Reply(550, "Recording \"%s\" not found", Option); + } + else + Reply(501, "Error in recording number \"%s\"", Option); + } + else if (recordings) { + cRecording *recording = Recordings.First(); + while (recording) { + Reply(recording == Recordings.Last() ? 250 : -250, "%d %s", recording->Index() + 1, recording->Title(' ', true)); + recording = Recordings.Next(recording); + } + } + else + Reply(550, "No recordings available"); + EXIT_WRAPPER(); +} + +bool cConnectionVTP::CmdDELR(char *Option) { + INIT_WRAPPER(); + if (*Option) { + if (isnumber(Option)) { + cRecording *recording = Recordings.Get(strtol(Option, NULL, 10) - 1); + if (recording) { + if (recording->Delete()) + Reply(250, "Recording \"%s\" deleted", Option); + else + Reply(554, "Error while deleting recording!"); + } + else + Reply(550, "Recording \"%s\" not found%s", Option, Recordings.Count() ? "" : " (use LSTR before deleting)"); + } + else + Reply(501, "Error in recording number \"%s\"", Option); + } + else + Reply(501, "Missing recording number"); + EXIT_WRAPPER(); +}*/ + +bool cConnectionVTP::Respond(int Code, const char *Message, ...) +{ + char *buffer; + va_list ap; + va_start(ap, Message); + vasprintf(&buffer, Message, ap); + va_end(ap); + cString str(buffer, true); + + 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 ? '-' : ' ', buffer); +} diff --git a/server/connectionVTP.h b/server/connectionVTP.h new file mode 100644 index 0000000..a8e76eb --- /dev/null +++ b/server/connectionVTP.h @@ -0,0 +1,75 @@ +#ifndef VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H +#define VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H + +#include "server/connection.h" + +class cTBSocket; +class cStreamdevLiveStreamer; +class cLSTEHandler; +class cLSTCHandler; +class cLSTTHandler; + +class cConnectionVTP: public cServerConnection { + friend class cLSTEHandler; + // if your compiler doesn't understand the following statement + // (e.g. gcc 2.x), simply remove it and try again ;-) + using cServerConnection::Respond; + +private: + cTBSocket *m_LiveSocket; + cStreamdevLiveStreamer *m_LiveStreamer; + + char *m_LastCommand; + bool m_NoTSPIDS; + + // Members adopted for SVDRP + cRecordings Recordings; + cLSTEHandler *m_LSTEHandler; + cLSTCHandler *m_LSTCHandler; + cLSTTHandler *m_LSTTHandler; + +protected: + template + bool CmdLSTX(cHandler *&Handler, char *Option); + +public: + cConnectionVTP(void); + virtual ~cConnectionVTP(); + + virtual void Welcome(void); + virtual void Reject(void); + + virtual void Detach(void); + virtual void Attach(void); + + virtual bool Command(char *Cmd); + bool CmdCAPS(char *Opts); + bool CmdPROV(char *Opts); + bool CmdPORT(char *Opts); + bool CmdTUNE(char *Opts); + bool CmdADDP(char *Opts); + bool CmdDELP(char *Opts); + bool CmdADDF(char *Opts); + bool CmdDELF(char *Opts); + bool CmdABRT(char *Opts); + bool CmdQUIT(char *Opts); + bool CmdSUSP(char *Opts); + + // Thread-safe implementations of SVDRP commands + bool CmdLSTE(char *Opts); + bool CmdLSTC(char *Opts); + bool CmdLSTT(char *Opts); + + // Commands adopted from SVDRP + bool CmdMODT(const char *Option); + bool CmdNEWT(const char *Option); + bool CmdDELT(const char *Option); + + //bool CmdLSTR(char *Opts); + //bool CmdDELR(char *Opts); + + bool Respond(int Code, const char *Message, ...) + __attribute__ ((format (printf, 3, 4))); +}; + +#endif // VDR_STREAMDEV_SERVERS_CONNECTIONVTP_H diff --git a/server/livefilter.c b/server/livefilter.c new file mode 100644 index 0000000..4524a88 --- /dev/null +++ b/server/livefilter.c @@ -0,0 +1,41 @@ +/* + * $Id: livefilter.c,v 1.2 2005/02/08 13:59:16 lordjaxom Exp $ + */ + +#include "server/livefilter.h" +#include "server/livestreamer.h" +#include "common.h" + +#if VDRVERSNUM >= 10300 + +cStreamdevLiveFilter::cStreamdevLiveFilter(cStreamdevLiveStreamer *Streamer) { + m_Streamer = Streamer; +} + +cStreamdevLiveFilter::~cStreamdevLiveFilter() { +} + +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) & 0xff; + buffer[2] = Pid & 0xff; + buffer[3] = Tid; + buffer[4] = (uchar)chunk; + memcpy(buffer + 5, Data + pos, chunk); + length -= chunk; + pos += chunk; + + int p = m_Streamer->Put(buffer, TS_SIZE); + if (p != TS_SIZE) + m_Streamer->ReportOverflow(TS_SIZE - p); + } +} + +#endif // VDRVERSNUM >= 10300 diff --git a/server/livefilter.h b/server/livefilter.h new file mode 100644 index 0000000..a30cba0 --- /dev/null +++ b/server/livefilter.h @@ -0,0 +1,31 @@ +/* + * $Id: livefilter.h,v 1.2 2005/11/07 19:28:41 lordjaxom Exp $ + */ + +#ifndef VDR_STREAMEV_LIVEFILTER_H +#define VDR_STREAMEV_LIVEFILTER_H + +#include + +# if VDRVERSNUM >= 10300 + +#include + +class cStreamdevLiveStreamer; + +class cStreamdevLiveFilter: public cFilter { + friend class cStreamdevLiveStreamer; + +private: + cStreamdevLiveStreamer *m_Streamer; + +protected: + virtual void Process(u_short Pid, u_char Tid, const u_char *Data, int Length); + +public: + cStreamdevLiveFilter(cStreamdevLiveStreamer *Streamer); + virtual ~cStreamdevLiveFilter(); +}; + +# endif // VDRVERSNUM >= 10300 +#endif // VDR_STREAMEV_LIVEFILTER_H diff --git a/server/livestreamer.c b/server/livestreamer.c new file mode 100644 index 0000000..6148720 --- /dev/null +++ b/server/livestreamer.c @@ -0,0 +1,298 @@ +#include + +#include "server/livestreamer.h" +#include "remux/ts2ps.h" +#include "remux/ts2es.h" +#include "remux/extern.h" +#include "common.h" + +// --- cStreamdevLiveReceiver ------------------------------------------------- + +#if VDRVERSNUM < 10500 +cStreamdevLiveReceiver::cStreamdevLiveReceiver(cStreamdevLiveStreamer *Streamer, int Ca, + int Priority, const int *Pids): + cReceiver(Ca, Priority, 0, Pids), +#else +cStreamdevLiveReceiver::cStreamdevLiveReceiver(cStreamdevLiveStreamer *Streamer, tChannelID ChannelID, + int Priority, const int *Pids): + cReceiver(ChannelID, Priority, 0, Pids), +#endif + m_Streamer(Streamer) +{ +} + +cStreamdevLiveReceiver::~cStreamdevLiveReceiver() +{ + Dprintf("Killing live receiver\n"); + Detach(); +} + +void cStreamdevLiveReceiver::Receive(uchar *Data, int Length) { + int p = m_Streamer->Receive(Data, Length); + if (p != Length) + m_Streamer->ReportOverflow(Length - p); +} + +// --- cStreamdevLiveStreamer ------------------------------------------------- + +cStreamdevLiveStreamer::cStreamdevLiveStreamer(int Priority): + cStreamdevStreamer("streamdev-livestreaming"), + m_Priority(Priority), + m_NumPids(0), + m_StreamType(stTSPIDS), + m_Channel(NULL), + m_Device(NULL), + m_Receiver(NULL), + m_PESRemux(NULL), + m_ESRemux(NULL), + m_PSRemux(NULL), + m_ExtRemux(NULL) +{ +} + +cStreamdevLiveStreamer::~cStreamdevLiveStreamer() +{ + Dprintf("Desctructing Live streamer\n"); + Stop(); + delete m_Receiver; + delete m_PESRemux; + delete m_ESRemux; + delete m_PSRemux; + delete m_ExtRemux; +#if VDRVERSNUM >= 10300 + //delete m_Filter; TODO +#endif +} + +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)); + } + } + } + + DELETENULL(m_Receiver); + if (m_NumPids > 0) { + Dprintf("Creating Receiver to respect changed pids\n"); +#if VDRVERSNUM < 10500 + m_Receiver = new cStreamdevLiveReceiver(this, m_Channel->Ca(), m_Priority, m_Pids); +#else + m_Receiver = new cStreamdevLiveReceiver(this, m_Channel->GetChannelID(), m_Priority, m_Pids); +#endif + if (IsRunning() && m_Device != NULL) { + Dprintf("Attaching new receiver\n"); + Attach(); + } + } + return true; +} + +bool cStreamdevLiveStreamer::SetChannel(const cChannel *Channel, eStreamType StreamType, int Apid) +{ + Dprintf("Initializing Remuxer for full channel transfer\n"); + printf("ca pid: %d\n", Channel->Ca()); + m_Channel = Channel; + m_StreamType = StreamType; + switch (m_StreamType) { + case stES: + { + int pid = ISRADIO(m_Channel) ? m_Channel->Apid(0) : m_Channel->Vpid(); + if (Apid != 0) + pid = Apid; + m_ESRemux = new cTS2ESRemux(pid); + return SetPid(pid, true); + } + + case stPES: + Dprintf("PES\n"); + m_PESRemux = new cRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(), + m_Channel->Spids(), false); + if (Apid != 0) + return SetPid(m_Channel->Vpid(), true) + && SetPid(Apid, true); + else + return SetPid(m_Channel->Vpid(), true) + && SetPid(m_Channel->Apid(0), true) + && SetPid(m_Channel->Dpid(0), true); + + case stPS: + m_PSRemux = new cTS2PSRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(), + m_Channel->Spids()); + if (Apid != 0) + return SetPid(m_Channel->Vpid(), true) + && SetPid(Apid, true); + else + return SetPid(m_Channel->Vpid(), true) + && SetPid(m_Channel->Apid(0), true) + && SetPid(m_Channel->Dpid(0), true); + + case stTS: + if (Apid != 0) + return SetPid(m_Channel->Vpid(), true) + && SetPid(Apid, true); + else + return SetPid(m_Channel->Vpid(), true) + && SetPid(m_Channel->Apid(0), true) + && SetPid(m_Channel->Dpid(0), true); + + case stExtern: + m_ExtRemux = new cExternRemux(m_Channel->Vpid(), m_Channel->Apids(), m_Channel->Dpids(), + m_Channel->Spids()); + if (Apid != 0) + return SetPid(m_Channel->Vpid(), true) + && SetPid(Apid, true); + else + return SetPid(m_Channel->Vpid(), true) + && SetPid(m_Channel->Apid(0), true) + && SetPid(m_Channel->Dpid(0), true); + + case stTSPIDS: + Dprintf("pid streaming mode\n"); + return true; + } + return false; +} + +bool cStreamdevLiveStreamer::SetFilter(u_short Pid, u_char Tid, u_char Mask, bool On) +{ +#if 0 + Dprintf("setting filter\n"); + if (On) { + if (m_Filter == NULL) { + m_Filter = new cStreamdevLiveFilter(this); + Dprintf("attaching filter to device\n"); + m_Device->AttachFilter(m_Filter); + } + m_Filter->Set(Pid, Tid, Mask); + } else if (m_Filter != NULL) + m_Filter->Del(Pid, Tid, Mask); + return true; +#else + return false; +#endif +} + +int cStreamdevLiveStreamer::Put(const uchar *Data, int Count) +{ + switch (m_StreamType) { + case stTS: + case stTSPIDS: + return cStreamdevStreamer::Put(Data, Count); + + case stPES: + return m_PESRemux->Put(Data, Count); + + case stES: + return m_ESRemux->Put(Data, Count); + + case stPS: + return m_PSRemux->Put(Data, Count); + + case stExtern: + return m_ExtRemux->Put(Data, Count); + + default: // shouldn't happen??? + return 0; + } +} + +uchar *cStreamdevLiveStreamer::Get(int &Count) +{ + switch (m_StreamType) { + case stTS: + case stTSPIDS: + return cStreamdevStreamer::Get(Count); + + case stPES: + return m_PESRemux->Get(Count); + + case stES: + return m_ESRemux->Get(Count); + + case stPS: + return m_PSRemux->Get(Count); + + case stExtern: + return m_ExtRemux->Get(Count); + + default: // shouldn't happen??? + return 0; + } +} + +void cStreamdevLiveStreamer::Del(int Count) +{ + switch (m_StreamType) { + case stTS: + case stTSPIDS: + cStreamdevStreamer::Del(Count); + break; + + case stPES: + m_PESRemux->Del(Count); + break; + + case stES: + m_ESRemux->Del(Count); + break; + + case stPS: + m_PSRemux->Del(Count); + break; + + case stExtern: + m_ExtRemux->Del(Count); + break; + } +} + +void cStreamdevLiveStreamer::Attach(void) +{ + printf("RIGHT ATTACH\n"); + m_Device->AttachReceiver(m_Receiver); +} + +void cStreamdevLiveStreamer::Detach(void) +{ + printf("RIGHT DETACH\n"); + m_Device->Detach(m_Receiver); +} + +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; +} diff --git a/server/livestreamer.h b/server/livestreamer.h new file mode 100644 index 0000000..0c525bf --- /dev/null +++ b/server/livestreamer.h @@ -0,0 +1,81 @@ +#ifndef VDR_STREAMDEV_LIVESTREAMER_H +#define VDR_STREAMDEV_LIVESTREAMER_H + +#include +#include + +#include "server/streamer.h" +#include "server/livefilter.h" +#include "common.h" + +class cTS2PSRemux; +class cTS2ESRemux; +class cExternRemux; +class cRemux; + +// --- cStreamdevLiveReceiver ------------------------------------------------- + +class cStreamdevLiveReceiver: public cReceiver { + friend class cStreamdevLiveStreamer; + +private: + cStreamdevLiveStreamer *m_Streamer; + +protected: + virtual void Activate(bool On); + virtual void Receive(uchar *Data, int Length); + +public: +#if VDRVERSNUM < 10500 + cStreamdevLiveReceiver(cStreamdevLiveStreamer *Streamer, int Ca, int Priority, const int *Pids); +#else + cStreamdevLiveReceiver(cStreamdevLiveStreamer *Streamer, tChannelID ChannelID, int Priority, const int *Pids); +#endif + virtual ~cStreamdevLiveReceiver(); +}; + +// --- cStreamdevLiveStreamer ------------------------------------------------- + +class cStreamdevLiveStreamer: public cStreamdevStreamer { +private: + int m_Priority; + int m_Pids[MAXRECEIVEPIDS + 1]; + int m_NumPids; + eStreamType m_StreamType; + const cChannel *m_Channel; + cDevice *m_Device; + cStreamdevLiveReceiver *m_Receiver; + cRemux *m_PESRemux; + cTS2ESRemux *m_ESRemux; + cTS2PSRemux *m_PSRemux; + cExternRemux *m_ExtRemux; + +public: + cStreamdevLiveStreamer(int Priority); + virtual ~cStreamdevLiveStreamer(); + + void SetDevice(cDevice *Device) { m_Device = Device; } + bool SetPid(int Pid, bool On); + bool SetChannel(const cChannel *Channel, eStreamType StreamType, int Apid = 0); + bool SetFilter(u_short Pid, u_char Tid, u_char Mask, bool On); + + virtual int Put(const uchar *Data, int Count); + virtual uchar *Get(int &Count); + virtual void Del(int Count); + + virtual void Attach(void); + virtual void Detach(void); + + // Statistical purposes: + virtual std::string Report(void); +}; + +// --- cStreamdevLiveReceiver reverse inlines --------------------------------- + +inline void cStreamdevLiveReceiver::Activate(bool On) +{ + Dprintf("LiveReceiver->Activate(%d)\n", On); + m_Streamer->Activate(On); +} + +#endif // VDR_STREAMDEV_LIVESTREAMER_H diff --git a/server/server.c b/server/server.c new file mode 100644 index 0000000..b00f70d --- /dev/null +++ b/server/server.c @@ -0,0 +1,158 @@ +/* + * $Id: server.c,v 1.4 2006/11/10 11:52:41 schmirl Exp $ + */ + +#include "server/server.h" +#include "server/componentVTP.h" +#include "server/componentHTTP.h" +#include "server/setup.h" + +#include +#include +#include +#include + +cSVDRPhosts StreamdevHosts; + +cStreamdevServer *cStreamdevServer::m_Instance = NULL; +cList cStreamdevServer::m_Servers; +cList cStreamdevServer::m_Clients; + +cStreamdevServer::cStreamdevServer(void): + cThread("streamdev server"), + m_Active(false) +{ + Start(); +} + +cStreamdevServer::~cStreamdevServer() +{ + Stop(); +} + +void cStreamdevServer::Initialize(void) +{ + if (m_Instance == NULL) { + if (StreamdevServerSetup.StartVTPServer) Register(new cComponentVTP); + if (StreamdevServerSetup.StartHTTPServer) Register(new cComponentHTTP); + + m_Instance = new cStreamdevServer; + } +} + +void cStreamdevServer::Destruct(void) +{ + DELETENULL(m_Instance); +} + +void cStreamdevServer::Stop(void) +{ + if (m_Active) { + m_Active = false; + Cancel(3); + } +} + +void cStreamdevServer::Register(cServerComponent *Server) +{ + m_Servers.Add(Server); +} + +void cStreamdevServer::Action(void) +{ + m_Active = true; + + /* 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"); + m_Active = false; + } + + cTBSelect select; + while (m_Active) { + 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 result; + while ((result = select.Select(100)) < 0 && errno == ETIMEDOUT) { + if (!m_Active) break; + } + + if (result < 0) { + if (m_Active) // no exit was requested while polling + 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 (select.CanRead(c->Socket())) { + cServerConnection *client = c->Accept(); + m_Clients.Add(client); + + if (m_Clients.Count() > StreamdevServerSetup.MaxClients) { + esyslog("streamdev: too many clients, rejecting %s:%d", + client->RemoteIp().c_str(), client->RemotePort()); + client->Reject(); + } else if (!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 (select.CanWrite(s->Socket())) + result = s->Write(); + + if (result && select.CanRead(s->Socket())) + result = s->Read(); + + cServerConnection *next = m_Clients.Next(s); + if (!result) { + isyslog("streamdev: closing streamdev connection to %s:%d", + s->RemoteIp().c_str(), s->RemotePort()); + s->Close(); + m_Clients.Del(s); + } + s = next; + } + } + + while (m_Clients.Count() > 0) { + cServerConnection *s = m_Clients.First(); + s->Close(); + m_Clients.Del(s); + } + + while (m_Servers.Count() > 0) { + cServerComponent *c = m_Servers.First(); + c->Destruct(); + m_Servers.Del(c); + } + + m_Active = false; +} diff --git a/server/server.h b/server/server.h new file mode 100644 index 0000000..af574f5 --- /dev/null +++ b/server/server.h @@ -0,0 +1,47 @@ +/* + * $Id: server.h,v 1.2 2005/05/09 20:22:29 lordjaxom Exp $ + */ + +#ifndef VDR_STREAMDEV_SERVER_H +#define VDR_STREAMDEV_SERVER_H + +#include + +#include "server/component.h" +#include "server/connection.h" + +#define STREAMDEVHOSTSPATH (*AddDirectory(cPlugin::ConfigDirectory(), "streamdevhosts.conf")) + +class cStreamdevServer: public cThread { +private: + bool m_Active; + + 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); +}; + +inline bool cStreamdevServer::Active(void) +{ + return m_Instance != NULL + && m_Instance->m_Clients.Count() > 0; +} + +extern cSVDRPhosts StreamdevHosts; + +#endif // VDR_STREAMDEV_SERVER_H diff --git a/server/setup.c b/server/setup.c new file mode 100644 index 0000000..2589fec --- /dev/null +++ b/server/setup.c @@ -0,0 +1,94 @@ +/* + * $Id: setup.c,v 1.2 2005/05/09 20:22:29 lordjaxom Exp $ + */ + +#include + +#include "server/setup.h" +#include "server/server.h" +#include "i18n.h" + +cStreamdevServerSetup StreamdevServerSetup; + +cStreamdevServerSetup::cStreamdevServerSetup(void) { + MaxClients = 5; + StartVTPServer = true; + VTPServerPort = 2004; + StartHTTPServer = true; + HTTPServerPort = 3000; + HTTPStreamType = stPES; + SuspendMode = smOffer; + AllowSuspend = false; + strcpy(VTPBindIP, "0.0.0.0"); + strcpy(HTTPBindIP, "0.0.0.0"); +} + +bool cStreamdevServerSetup::SetupParse(const char *Name, const char *Value) { + if (strcmp(Name, "MaxClients") == 0) MaxClients = atoi(Value); + else if (strcmp(Name, "StartServer") == 0) StartVTPServer = atoi(Value); + else if (strcmp(Name, "ServerPort") == 0) VTPServerPort = atoi(Value); + else if (strcmp(Name, "VTPBindIP") == 0) strcpy(VTPBindIP, Value); + else if (strcmp(Name, "StartHTTPServer") == 0) StartHTTPServer = atoi(Value); + else if (strcmp(Name, "HTTPServerPort") == 0) HTTPServerPort = atoi(Value); + else if (strcmp(Name, "HTTPStreamType") == 0) HTTPStreamType = atoi(Value); + else if (strcmp(Name, "HTTPBindIP") == 0) strcpy(HTTPBindIP, Value); + else if (strcmp(Name, "SuspendMode") == 0) SuspendMode = atoi(Value); + else if (strcmp(Name, "AllowSuspend") == 0) AllowSuspend = atoi(Value); + else return false; + return true; +} + +cStreamdevServerMenuSetupPage::cStreamdevServerMenuSetupPage(void) { + m_NewSetup = StreamdevServerSetup; + + AddCategory (tr("Common Settings")); + AddRangeEdit(tr("Maximum Number of Clients"), m_NewSetup.MaxClients, 0, 100); + AddSuspEdit (tr("Suspend behaviour"), m_NewSetup.SuspendMode); + AddBoolEdit (tr("Client may suspend"), m_NewSetup.AllowSuspend); + + AddCategory (tr("VDR-to-VDR Server")); + AddBoolEdit (tr("Start VDR-to-VDR Server"), m_NewSetup.StartVTPServer); + AddShortEdit(tr("VDR-to-VDR Server Port"), m_NewSetup.VTPServerPort); + AddIpEdit (tr("Bind to IP"), m_NewSetup.VTPBindIP); + + AddCategory (tr("HTTP Server")); + AddBoolEdit (tr("Start HTTP Server"), m_NewSetup.StartHTTPServer); + AddShortEdit(tr("HTTP Server Port"), m_NewSetup.HTTPServerPort); + AddTypeEdit (tr("HTTP Streamtype"), m_NewSetup.HTTPStreamType); + AddIpEdit (tr("Bind to IP"), m_NewSetup.HTTPBindIP); + + SetCurrent(Get(1)); +} + +cStreamdevServerMenuSetupPage::~cStreamdevServerMenuSetupPage() { +} + +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) { + restart = true; + cStreamdevServer::Destruct(); + } + + SetupStore("MaxClients", m_NewSetup.MaxClients); + SetupStore("StartServer", m_NewSetup.StartVTPServer); + SetupStore("ServerPort", m_NewSetup.VTPServerPort); + SetupStore("VTPBindIP", m_NewSetup.VTPBindIP); + SetupStore("StartHTTPServer", m_NewSetup.StartHTTPServer); + SetupStore("HTTPServerPort", m_NewSetup.HTTPServerPort); + SetupStore("HTTPStreamType", m_NewSetup.HTTPStreamType); + SetupStore("HTTPBindIP", m_NewSetup.HTTPBindIP); + SetupStore("SuspendMode", m_NewSetup.SuspendMode); + SetupStore("AllowSuspend", m_NewSetup.AllowSuspend); + + StreamdevServerSetup = m_NewSetup; + + if (restart) + cStreamdevServer::Initialize(); +} + diff --git a/server/setup.h b/server/setup.h new file mode 100644 index 0000000..840567f --- /dev/null +++ b/server/setup.h @@ -0,0 +1,41 @@ +/* + * $Id: setup.h,v 1.1.1.1 2004/12/30 22:44:21 lordjaxom Exp $ + */ + +#ifndef VDR_STREAMDEV_SETUPSERVER_H +#define VDR_STREAMDEV_SETUPSERVER_H + +#include "common.h" + +struct cStreamdevServerSetup { + cStreamdevServerSetup(void); + + bool SetupParse(const char *Name, const char *Value); + + int MaxClients; + int StartVTPServer; + int VTPServerPort; + char VTPBindIP[20]; + int StartHTTPServer; + int HTTPServerPort; + int HTTPStreamType; + char HTTPBindIP[20]; + int SuspendMode; + int AllowSuspend; +}; + +extern cStreamdevServerSetup StreamdevServerSetup; + +class cStreamdevServerMenuSetupPage: public cStreamdevMenuSetupPage { +private: + cStreamdevServerSetup m_NewSetup; + +protected: + virtual void Store(void); + +public: + cStreamdevServerMenuSetupPage(void); + virtual ~cStreamdevServerMenuSetupPage(); +}; + +#endif // VDR_STREAMDEV_SETUPSERVER_H diff --git a/server/streamer.c b/server/streamer.c new file mode 100644 index 0000000..582fc6a --- /dev/null +++ b/server/streamer.c @@ -0,0 +1,146 @@ +/* + * $Id: streamer.c,v 1.14 2005/05/09 20:22:29 lordjaxom Exp $ + */ + +#include +#include +#include +#include + +#include "server/streamer.h" +#include "server/suspend.h" +#include "server/setup.h" +#include "tools/socket.h" +#include "tools/select.h" +#include "common.h" + +// --- cStreamdevWriter ------------------------------------------------------- + +cStreamdevWriter::cStreamdevWriter(cTBSocket *Socket, + cStreamdevStreamer *Streamer): + cThread("streamdev-writer"), + m_Streamer(Streamer), + m_Socket(Socket), + m_Active(false) +{ +} + +cStreamdevWriter::~cStreamdevWriter() +{ + Dprintf("destructing writer\n"); + m_Active = false; + Cancel(3); +} + +void cStreamdevWriter::Action(void) +{ + cTBSelect sel; + Dprintf("Writer start\n"); + int max = 0; + uchar *block = NULL; + int count, offset = 0; + m_Active = true; + while (m_Active) { + if (block == NULL) { + block = m_Streamer->Get(count); + offset = 0; + } + + if (block != NULL) { + sel.Clear(); + sel.Add(*m_Socket, true); + if (sel.Select(500) == -1) { + esyslog("ERROR: streamdev-server: couldn't send data: %m"); + break; + } + + if (sel.CanWrite(*m_Socket)) { + int written; + if ((written = m_Socket->Write(block + offset, count)) == -1) { + esyslog("ERROR: streamdev-server: couldn't send data: %m"); + break; + } + if (count > max) + max = count; + + offset += written; + count -= written; + if (count == 0) { + m_Streamer->Del(offset); + block = NULL; + } + } + } + } + m_Active = false; + Dprintf("Max. Transmit Blocksize was: %d\n", max); +} + +// --- cStreamdevStreamer ----------------------------------------------------- + +cStreamdevStreamer::cStreamdevStreamer(const char *Name): + cThread(Name), + m_Active(false), + m_Running(false), + m_Writer(NULL), + m_RingBuffer(new cRingBufferLinear(STREAMERBUFSIZE, TS_SIZE * 2, + true, "streamdev-streamer")), + m_SendBuffer(new cRingBufferLinear(WRITERBUFSIZE, TS_SIZE * 2)) +{ + m_RingBuffer->SetTimeouts(0, 100); + m_SendBuffer->SetTimeouts(0, 100); +} + +cStreamdevStreamer::~cStreamdevStreamer() +{ + Dprintf("Desctructing streamer\n"); + delete m_RingBuffer; + delete m_SendBuffer; +} + +void cStreamdevStreamer::Start(cTBSocket *Socket) +{ + Dprintf("start streamer\n"); + m_Writer = new cStreamdevWriter(Socket, this); + m_Running = true; + Attach(); +} + +void cStreamdevStreamer::Activate(bool On) +{ + if (On && !m_Active) { + Dprintf("activate streamer\n"); + m_Writer->Start(); + cThread::Start(); + } +} + +void cStreamdevStreamer::Stop(void) +{ + if (m_Active) { + Dprintf("stopping streamer\n"); + m_Active = false; + Cancel(3); + } + if (m_Running) { + Detach(); + m_Running = false; + DELETENULL(m_Writer); + } +} + +void cStreamdevStreamer::Action(void) +{ + m_Active = true; + while (m_Active) { + int got; + uchar *block = m_RingBuffer->Get(got); + + if (block) { + int count = Put(block, got); + if (count) + m_RingBuffer->Del(count); + } + } +} + diff --git a/server/streamer.h b/server/streamer.h new file mode 100644 index 0000000..c27677c --- /dev/null +++ b/server/streamer.h @@ -0,0 +1,69 @@ +/* + * $Id: streamer.h,v 1.7 2005/03/12 12:54:19 lordjaxom Exp $ + */ + +#ifndef VDR_STREAMDEV_STREAMER_H +#define VDR_STREAMDEV_STREAMER_H + +#include +#include +#include + +class cTBSocket; +class cStreamdevStreamer; + +#define STREAMERBUFSIZE MEGABYTE(4) +#define WRITERBUFSIZE KILOBYTE(256) + +// --- cStreamdevWriter ------------------------------------------------------- + +class cStreamdevWriter: public cThread { +private: + cStreamdevStreamer *m_Streamer; + cTBSocket *m_Socket; + bool m_Active; + +protected: + virtual void Action(void); + +public: + cStreamdevWriter(cTBSocket *Socket, cStreamdevStreamer *Streamer); + virtual ~cStreamdevWriter(); +}; + +// --- cStreamdevStreamer ----------------------------------------------------- + +class cStreamdevStreamer: public cThread { +private: + bool m_Active; + bool m_Running; + cStreamdevWriter *m_Writer; + cRingBufferLinear *m_RingBuffer; + cRingBufferLinear *m_SendBuffer; + +protected: + virtual void Action(void); + + bool IsRunning(void) const { return m_Running; } + +public: + cStreamdevStreamer(const char *Name); + virtual ~cStreamdevStreamer(); + + virtual void Start(cTBSocket *Socket); + virtual void Stop(void); + + void Activate(bool On); + int Receive(uchar *Data, int Length) { return m_RingBuffer->Put(Data, Length); } + void ReportOverflow(int Bytes) { m_RingBuffer->ReportOverflow(Bytes); } + + virtual int Put(const uchar *Data, int Count) { return m_SendBuffer->Put(Data, Count); } + virtual uchar *Get(int &Count) { return m_SendBuffer->Get(Count); } + virtual void Del(int Count) { m_SendBuffer->Del(Count); } + + virtual void Detach(void) {} + virtual void Attach(void) {} +}; + +#endif // VDR_STREAMDEV_STREAMER_H + diff --git a/server/suspend.c b/server/suspend.c new file mode 100644 index 0000000..9ac9ac5 --- /dev/null +++ b/server/suspend.c @@ -0,0 +1,69 @@ +/* + * $Id: suspend.c,v 1.1.1.1 2004/12/30 22:44:21 lordjaxom Exp $ + */ + +#include "server/suspend.h" +#include "server/suspend.dat" +#include "common.h" + +cSuspendLive::cSuspendLive(void) +#if VDRVERSNUM >= 10300 + : cThread("Streamdev: server suspend") +#endif +{ +} + +cSuspendLive::~cSuspendLive() { + Detach(); +} + +void cSuspendLive::Activate(bool On) { + Dprintf("Activate cSuspendLive %d\n", On); + if (On) + Start(); + else + Stop(); +} + +void cSuspendLive::Stop(void) { + if (m_Active) { + m_Active = false; + Cancel(3); + } +} + +void cSuspendLive::Action(void) { +#if VDRVERSNUM < 10300 + isyslog("Streamdev: Suspend Live thread started (pid = %d)", getpid()); +#endif + + m_Active = true; + while (m_Active) { + DeviceStillPicture(suspend_mpg, sizeof(suspend_mpg)); + usleep(100000); + } + +#if VDRVERSNUM < 10300 + isyslog("Streamdev: Suspend Live thread stopped"); +#endif +} + +bool cSuspendCtl::m_Active = false; + +cSuspendCtl::cSuspendCtl(void): + cControl(m_Suspend = new cSuspendLive) { + m_Active = true; +} + +cSuspendCtl::~cSuspendCtl() { + m_Active = false; + DELETENULL(m_Suspend); +} + +eOSState cSuspendCtl::ProcessKey(eKeys Key) { + if (!m_Suspend->IsActive() || Key == kBack) { + DELETENULL(m_Suspend); + return osEnd; + } + return osContinue; +} diff --git a/server/suspend.dat b/server/suspend.dat new file mode 100644 index 0000000..7b1b890 --- /dev/null +++ b/server/suspend.dat @@ -0,0 +1,1206 @@ +const unsigned char suspend_mpg[] = { + 0x00, 0x00, 0x01, 0xb3, 0x2d, 0x02, 0x40, 0x83, 0x02, 0xd0, 0x20, 0xa0, + 0x00, 0x00, 0x01, 0xb2, 0x4d, 0x50, 0x45, 0x47, 0x0a, 0x00, 0x00, 0x01, + 0xb8, 0x00, 0x08, 0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0a, 0xbb, + 0x58, 0x00, 0x00, 0x01, 0x01, 0x52, 0x97, 0xe6, 0x54, 0xa5, 0x2f, 0xdc, + 0xaf, 0x9a, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x45, 0xe2, + 0x2c, 0x52, 0x67, 0x98, 0x6c, 0x9a, 0x2e, 0xb9, 0x9e, 0xb4, 0x6c, 0xdd, + 0x34, 0x8c, 0x8b, 0xab, 0xa6, 0x61, 0xb5, 0xbd, 0x33, 0x8d, 0xa7, 0x22, + 0x6f, 0x75, 0x74, 0xcc, 0x36, 0xb4, 0x6c, 0xfd, 0x34, 0x64, 0x4d, 0xee, + 0x91, 0xb3, 0xf4, 0xd6, 0xf4, 0xcd, 0xd3, 0x46, 0x44, 0xde, 0xea, 0x1b, + 0x37, 0x4d, 0x77, 0x4c, 0xdd, 0x34, 0xe4, 0x43, 0xdd, 0x43, 0x66, 0xe9, + 0xad, 0x1b, 0x38, 0xda, 0x32, 0x26, 0xf7, 0x4f, 0x4c, 0xe3, 0x6b, 0x46, + 0xce, 0x36, 0x8c, 0x89, 0xbd, 0xd3, 0xd3, 0x3f, 0x4d, 0x68, 0xd9, 0x86, + 0xd3, 0x91, 0x37, 0xba, 0x86, 0xcc, 0x36, 0xbb, 0xa6, 0x6e, 0x9a, 0x32, + 0x26, 0xf7, 0x4f, 0x4c, 0xfd, 0x35, 0xbd, 0x33, 0xf4, 0xd1, 0x91, 0x37, + 0xba, 0x46, 0xcf, 0xd3, 0x5a, 0x36, 0x7e, 0x9a, 0x72, 0x26, 0xf7, 0x4f, + 0x4c, 0xfd, 0x35, 0xa3, 0x66, 0x1b, 0x46, 0x44, 0xde, 0xea, 0xe9, 0x9c, + 0x6d, 0x67, 0x4c, 0xfd, 0x34, 0x64, 0x43, 0xdd, 0x43, 0x66, 0xe9, 0xad, + 0xe9, 0x9c, 0x6d, 0x39, 0x13, 0x7b, 0xa7, 0xa6, 0x7e, 0x9a, 0xd1, 0xb3, + 0x0d, 0xa3, 0x22, 0x6f, 0x75, 0x74, 0xcd, 0xd3, 0x5c, 0x36, 0x61, 0xb4, + 0x0c, 0x9b, 0xdd, 0x43, 0x66, 0x1b, 0x5c, 0x36, 0x6e, 0x9a, 0x72, 0x26, + 0xf7, 0x50, 0xd9, 0xba, 0x6b, 0x86, 0xcd, 0xd3, 0x46, 0x44, 0xde, 0xea, + 0x1b, 0x30, 0xda, 0xd1, 0xb3, 0x8d, 0xa3, 0x22, 0x6f, 0x74, 0x8d, 0x9c, + 0x6d, 0x68, 0xd9, 0xba, 0x69, 0xc8, 0x87, 0xba, 0xba, 0x66, 0x1b, 0x5c, + 0x36, 0x6e, 0x9a, 0x32, 0x26, 0xf7, 0x57, 0x4c, 0xdd, 0x35, 0xdd, 0x33, + 0x74, 0xd1, 0x91, 0x37, 0xba, 0x9e, 0x6e, 0x9a, 0xde, 0x99, 0xfa, 0x68, + 0xc8, 0x9b, 0xdd, 0x23, 0x67, 0xe9, 0xad, 0x79, 0xc6, 0xd3, 0x91, 0x37, + 0xba, 0x7a, 0x67, 0xe9, 0xad, 0x1b, 0x30, 0xda, 0x32, 0x26, 0xf7, 0x50, + 0xd9, 0xba, 0x6b, 0x7a, 0x67, 0x1b, 0x46, 0x44, 0x3d, 0xd4, 0x36, 0x61, + 0xb5, 0xbd, 0x33, 0xbd, 0x39, 0x13, 0x7b, 0xa7, 0xa6, 0x71, 0xb5, 0xbf, + 0xcc, 0x36, 0x8c, 0x89, 0xbd, 0xd4, 0xf3, 0x0d, 0xae, 0xe9, 0x9b, 0xa6, + 0x9c, 0x89, 0xbd, 0xd4, 0xf3, 0x74, 0xd7, 0x74, 0xcc, 0x36, 0x8c, 0x89, + 0xbd, 0xd2, 0x36, 0x7e, 0x9a, 0xd1, 0xb3, 0x0d, 0xa3, 0x22, 0x6f, 0x75, + 0x74, 0xcf, 0xd3, 0x5b, 0xd3, 0x37, 0x4d, 0x19, 0x10, 0xf7, 0x53, 0xcc, + 0x36, 0xb7, 0xa6, 0x71, 0xb4, 0xe4, 0x4d, 0xee, 0x91, 0xb3, 0xf4, 0xd6, + 0x8d, 0x98, 0x6d, 0x03, 0x26, 0xf0, 0x00, 0x00, 0x01, 0x02, 0x2b, 0xf9, + 0x95, 0x29, 0x4b, 0xf7, 0x2b, 0xe6, 0xae, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x45, 0xe2, 0x2c, 0x52, 0x71, 0xb3, 0x74, 0xc9, 0xa2, + 0xeb, 0x79, 0x86, 0xd6, 0xf4, 0xcc, 0x36, 0x9c, 0x88, 0xba, 0xba, 0x66, + 0xe9, 0xad, 0x1b, 0x38, 0xda, 0x32, 0x26, 0xf7, 0x50, 0xd9, 0xba, 0x6b, + 0x7a, 0x67, 0xe9, 0xa7, 0x22, 0x6f, 0x74, 0x8d, 0x9f, 0xa6, 0xb4, 0x6c, + 0xc3, 0x68, 0xc8, 0x9b, 0xdd, 0x5d, 0x33, 0x0d, 0xae, 0x1b, 0x37, 0x4d, + 0x19, 0x13, 0x7b, 0xab, 0xa6, 0x61, 0xb5, 0xc3, 0x66, 0xe9, 0xa3, 0x22, + 0x1e, 0xea, 0x1b, 0x30, 0xda, 0xde, 0x99, 0xfa, 0x69, 0xc8, 0x9b, 0xdd, + 0x23, 0x67, 0x1b, 0x5b, 0xd3, 0x37, 0x4d, 0x19, 0x13, 0x7b, 0xa8, 0x6c, + 0xc3, 0x6b, 0x86, 0xcd, 0xd3, 0x46, 0x44, 0xde, 0xea, 0xe9, 0x9b, 0xa6, + 0xb7, 0xa6, 0x7e, 0x9a, 0x72, 0x26, 0xf7, 0x4f, 0x4c, 0xfd, 0x35, 0xa3, + 0x67, 0xe9, 0xa3, 0x22, 0x6f, 0x74, 0xf4, 0xce, 0x36, 0xb7, 0xa6, 0x67, + 0xa3, 0x22, 0x6f, 0x75, 0x74, 0xcc, 0xf5, 0xc3, 0x66, 0x1b, 0x4e, 0x44, + 0xde, 0xea, 0xe9, 0x9b, 0xa6, 0xb4, 0x6c, 0xfd, 0x34, 0x0c, 0x87, 0xba, + 0x46, 0xcf, 0xd3, 0x5a, 0x36, 0x61, 0xb4, 0x64, 0x4d, 0xee, 0xa1, 0xb3, + 0xf4, 0xd6, 0x8d, 0x9b, 0xa6, 0x9c, 0x89, 0xbd, 0xd5, 0xd3, 0x30, 0xda, + 0xee, 0x99, 0x86, 0xd1, 0x91, 0x37, 0xba, 0x86, 0xcc, 0x36, 0xb8, 0x6c, + 0xc3, 0x68, 0xc8, 0x9b, 0xdd, 0x5d, 0x33, 0x74, 0xd6, 0x8d, 0x9c, 0x6d, + 0x39, 0x13, 0x7b, 0xa4, 0x6c, 0xe3, 0x6b, 0x7a, 0x66, 0xe9, 0xa3, 0x22, + 0x6f, 0x75, 0x3c, 0xdd, 0x35, 0xdd, 0x33, 0x0d, 0xa3, 0x22, 0x1e, 0xea, + 0xe9, 0x9b, 0xa6, 0xb8, 0x6c, 0xdd, 0x34, 0xe4, 0x4d, 0xee, 0xa1, 0xb3, + 0x0d, 0xad, 0x1b, 0x3f, 0x4d, 0x19, 0x13, 0x7b, 0xa4, 0x6c, 0xe3, 0x6b, + 0x7a, 0x67, 0x1b, 0x46, 0x44, 0xde, 0xe9, 0x1b, 0x38, 0xda, 0xd1, 0xb3, + 0x3d, 0x39, 0x13, 0x7b, 0xab, 0xa6, 0x61, 0xb5, 0xdd, 0x33, 0x74, 0xd1, + 0x91, 0x37, 0xba, 0x5e, 0x7e, 0x9a, 0xee, 0x99, 0x9e, 0x8c, 0x88, 0x7b, + 0xa4, 0x6c, 0xfd, 0x35, 0xbf, 0xcc, 0x36, 0x9c, 0x89, 0xbd, 0xd5, 0xd3, + 0x33, 0xd7, 0x0d, 0x9b, 0xa6, 0x8c, 0x89, 0xbd, 0xd5, 0xd3, 0x37, 0x4d, + 0x68, 0xd9, 0xc6, 0xd1, 0x91, 0x37, 0xba, 0xba, 0x66, 0x1b, 0x5b, 0xd3, + 0x33, 0xd3, 0x91, 0x37, 0xba, 0x86, 0xce, 0x36, 0xb7, 0xa6, 0x61, 0xb4, + 0x64, 0x4d, 0xee, 0xa1, 0xb3, 0x74, 0xd7, 0x0d, 0x98, 0x6d, 0x19, 0x13, + 0x7b, 0xab, 0xa6, 0x6e, 0x9a, 0xd1, 0xb3, 0xf4, 0xd3, 0x91, 0x0f, 0x74, + 0x8d, 0x9f, 0xa6, 0xb4, 0x6c, 0xe3, 0x68, 0xc8, 0x9b, 0xdd, 0x23, 0x66, + 0x1b, 0x5c, 0x36, 0x6e, 0x9a, 0x32, 0x26, 0xf7, 0x57, 0x4c, 0xc3, 0x6b, + 0xba, 0x66, 0x1b, 0x4e, 0x44, 0xde, 0x00, 0x00, 0x01, 0x03, 0x2b, 0xf9, + 0x95, 0x29, 0x4b, 0xf7, 0x2b, 0xe6, 0xae, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x28, 0xf3, 0x3c, 0x9b, 0xf8, 0xb5, 0xa5, 0x67, 0x4c, + 0xe3, 0x64, 0xd1, 0x75, 0x3c, 0xc3, 0x6b, 0x7a, 0x66, 0x1b, 0x4e, 0x44, + 0x3d, 0xd5, 0xd3, 0x30, 0xda, 0xe1, 0xb3, 0x74, 0xd1, 0x91, 0x37, 0xba, + 0x86, 0xcc, 0x36, 0xb4, 0x6c, 0xfd, 0x34, 0x64, 0x4d, 0xee, 0x9e, 0x99, + 0xc6, 0xd6, 0x8d, 0x9c, 0x6d, 0x38, 0xa9, 0xbd, 0xd2, 0x36, 0x71, 0xb5, + 0xbd, 0x33, 0x0d, 0xa3, 0x22, 0x6f, 0x75, 0x74, 0xcc, 0x36, 0xbb, 0xa6, + 0x6e, 0x9a, 0x31, 0x53, 0x7b, 0xab, 0xa6, 0x6e, 0x9a, 0xd1, 0xb3, 0x8d, + 0xa7, 0x15, 0x37, 0xba, 0xba, 0x66, 0x1b, 0x5a, 0x36, 0x6e, 0x9a, 0x06, + 0x43, 0xdd, 0x5d, 0x33, 0x0d, 0xae, 0x1b, 0x30, 0xda, 0x72, 0x26, 0xf7, + 0x57, 0x4c, 0xc3, 0x6b, 0x86, 0xcd, 0xd3, 0x46, 0x44, 0xde, 0xea, 0x1b, + 0x37, 0x4d, 0x6f, 0x4c, 0xe3, 0x68, 0x19, 0x37, 0xba, 0x86, 0xcd, 0xd3, + 0x5a, 0x36, 0x61, 0xb4, 0xe4, 0x4d, 0xee, 0xae, 0x99, 0x86, 0xd7, 0x74, + 0xcd, 0xd3, 0x46, 0x44, 0xde, 0xea, 0x1b, 0x30, 0xda, 0xde, 0x99, 0xfa, + 0x68, 0xc8, 0x9b, 0xdd, 0x5d, 0x33, 0x0d, 0xad, 0x1b, 0x3f, 0x4d, 0x23, + 0x26, 0xf7, 0x4f, 0x4c, 0xfd, 0x35, 0xbd, 0x33, 0x74, 0xd1, 0x91, 0x0f, + 0x75, 0x0d, 0x9f, 0xa6, 0xb7, 0xa6, 0x71, 0xb4, 0x64, 0x4d, 0xee, 0x9e, + 0x99, 0xba, 0x6b, 0x86, 0xcd, 0xd3, 0x4e, 0x44, 0xde, 0xea, 0x1b, 0x37, + 0x4d, 0x6f, 0x4c, 0xfd, 0x34, 0x64, 0x4d, 0xee, 0xa1, 0xb3, 0x0d, 0xad, + 0x1b, 0x38, 0xda, 0x32, 0x26, 0xf7, 0x4f, 0x4c, 0xe3, 0x6b, 0x46, 0xcd, + 0xd3, 0x4e, 0x44, 0xde, 0xea, 0xe9, 0x98, 0x6d, 0x70, 0xd9, 0xba, 0x68, + 0xc8, 0x87, 0xba, 0x86, 0xcd, 0xd3, 0x5d, 0xd3, 0x30, 0xda, 0x32, 0x26, + 0xf7, 0x57, 0x4c, 0xdd, 0x35, 0xa3, 0x67, 0x1b, 0x4e, 0x44, 0xde, 0xe9, + 0x1b, 0x38, 0xda, 0xde, 0x99, 0x86, 0xd1, 0x91, 0x37, 0xba, 0xba, 0x66, + 0x1b, 0x5c, 0x36, 0x6e, 0x9a, 0x32, 0x26, 0xf7, 0x57, 0x4c, 0xdd, 0x35, + 0xc3, 0x66, 0xe9, 0xa7, 0x22, 0x6f, 0x75, 0x74, 0xcc, 0x36, 0xb5, 0xe6, + 0xe9, 0xa3, 0x22, 0x1e, 0xea, 0xe9, 0x99, 0xeb, 0xba, 0x66, 0xe9, 0xa3, + 0x22, 0x6f, 0x75, 0x0d, 0x98, 0x6d, 0x70, 0xd9, 0x86, 0xd3, 0x91, 0x37, + 0xba, 0x86, 0xcd, 0xd3, 0x5a, 0x36, 0x61, 0xb4, 0x64, 0x4e, 0xea, 0xe9, + 0x9b, 0xa6, 0xbb, 0xa6, 0x6e, 0x9a, 0x32, 0x26, 0xf7, 0x53, 0xcc, 0x36, + 0xbb, 0xa6, 0x6e, 0x9a, 0x72, 0x26, 0xf7, 0x57, 0x4c, 0xc3, 0x6b, 0x46, + 0xcf, 0xd3, 0x46, 0x44, 0xde, 0xea, 0x1b, 0x37, 0x4d, 0x6f, 0x4c, 0xfd, + 0x34, 0x64, 0x43, 0xdd, 0x23, 0x67, 0xe9, 0xad, 0xe9, 0x98, 0x6d, 0x39, + 0x13, 0x7b, 0xab, 0xa6, 0x6e, 0x9a, 0xe1, 0xb3, 0x74, 0xd1, 0x91, 0x37, + 0xba, 0xba, 0x66, 0xe9, 0xad, 0x1b, 0x3f, 0x4d, 0x19, 0x13, 0x7b, 0xa4, + 0x6c, 0xfd, 0x35, 0xa3, 0x67, 0x1b, 0x4e, 0x44, 0xde, 0xe9, 0xe9, 0x9c, + 0x6d, 0x6f, 0x4c, 0xdd, 0x34, 0x64, 0x4d, 0xe0, 0x00, 0x00, 0x01, 0x04, + 0x2b, 0xf9, 0x95, 0x22, 0xf3, 0x74, 0xdf, 0xb8, 0xb5, 0xf3, 0x56, 0x91, + 0x7a, 0xce, 0x99, 0xc6, 0xc8, 0x64, 0x5d, 0x23, 0x67, 0xe9, 0xad, 0xe9, + 0x9b, 0xa6, 0x9c, 0x89, 0xdd, 0xe0, 0x19, 0xf4, 0x80, 0x54, 0x5f, 0x41, + 0x2c, 0x20, 0x61, 0x7f, 0x14, 0x4b, 0x25, 0xa5, 0x05, 0x27, 0x25, 0x25, + 0x20, 0x95, 0xd0, 0x58, 0xd2, 0xd1, 0xba, 0x33, 0x73, 0x4d, 0xb1, 0xe0, + 0x0c, 0x00, 0x60, 0x1a, 0x8d, 0xf2, 0x40, 0xaf, 0xc9, 0xcf, 0xbe, 0x01, + 0x88, 0x0e, 0xce, 0xe6, 0xe0, 0x0a, 0x6d, 0x1d, 0x32, 0x00, 0x36, 0xf8, + 0xa2, 0x62, 0x40, 0x74, 0xb0, 0x42, 0x08, 0x49, 0x29, 0x0f, 0xbe, 0x00, + 0xa3, 0xe1, 0xa1, 0x8c, 0xff, 0xff, 0x90, 0x8e, 0x06, 0x06, 0xba, 0xd2, + 0x57, 0xbe, 0x6c, 0x01, 0xa2, 0x51, 0xd0, 0x58, 0x67, 0xdb, 0xfd, 0xc9, + 0x79, 0xf6, 0xea, 0x3b, 0x91, 0x2f, 0x54, 0x03, 0x1c, 0x9d, 0x90, 0x5a, + 0x3f, 0xc6, 0x75, 0xde, 0xc0, 0x13, 0x10, 0xd2, 0x94, 0x16, 0x50, 0x6f, + 0x1a, 0x1a, 0x05, 0x0b, 0x49, 0x30, 0x37, 0xb3, 0x3f, 0x43, 0x74, 0x24, + 0x90, 0x84, 0x01, 0x72, 0xba, 0x0b, 0xe3, 0x30, 0xcd, 0xd2, 0xc6, 0x59, + 0x7a, 0x6d, 0x20, 0x21, 0xc0, 0x07, 0xc0, 0x19, 0xe4, 0xac, 0x60, 0x09, + 0xca, 0x18, 0x49, 0xed, 0x89, 0xa4, 0x27, 0xea, 0x43, 0x30, 0xd2, 0xc3, + 0x3f, 0x7e, 0xa5, 0x13, 0x31, 0xf0, 0x7b, 0xbc, 0x03, 0x10, 0x29, 0x88, + 0x40, 0x55, 0x39, 0x19, 0x90, 0x90, 0x94, 0x08, 0xe4, 0x0f, 0x72, 0x00, + 0xe8, 0x34, 0x0c, 0x86, 0x23, 0x9a, 0x77, 0xba, 0xba, 0x66, 0xe9, 0xad, + 0xe9, 0x9f, 0xa6, 0xec, 0xc8, 0xb9, 0xde, 0xe9, 0xe9, 0xb4, 0x13, 0x12, + 0x1a, 0x02, 0x14, 0x96, 0x80, 0x28, 0xdb, 0x06, 0x27, 0x20, 0xb2, 0x19, + 0x78, 0x68, 0x66, 0xfb, 0xf2, 0x59, 0x45, 0x6c, 0x94, 0x24, 0xb2, 0xd0, + 0xcc, 0x96, 0xf9, 0x08, 0xfd, 0x69, 0xe9, 0x47, 0x45, 0x79, 0x0d, 0x25, + 0xfc, 0x82, 0x83, 0x3e, 0xc1, 0xa4, 0xd2, 0xd0, 0x18, 0x57, 0x7d, 0xbe, + 0xe9, 0xeb, 0x2d, 0x19, 0x3b, 0xe4, 0xf0, 0xc2, 0x86, 0x86, 0x8c, 0x6e, + 0xf9, 0x1e, 0xf2, 0xc8, 0x41, 0x40, 0x3a, 0x0c, 0xdb, 0x29, 0x05, 0x63, + 0x7b, 0x87, 0xff, 0xd7, 0xff, 0x37, 0xf5, 0xfe, 0xbf, 0x78, 0x80, 0x0f, + 0x80, 0x34, 0x40, 0x6e, 0x03, 0x25, 0xa0, 0x6e, 0x76, 0x46, 0x1f, 0xf1, + 0xbc, 0x7e, 0xbd, 0x10, 0x10, 0x80, 0xef, 0x86, 0x32, 0x10, 0x7f, 0x27, + 0x55, 0x3d, 0x0c, 0x02, 0x02, 0x94, 0x35, 0x21, 0xa5, 0xf6, 0x67, 0xe5, + 0xa5, 0x0e, 0x02, 0x20, 0xce, 0x52, 0x1c, 0x5b, 0x55, 0x80, 0x1d, 0x13, + 0x07, 0x16, 0x09, 0x3f, 0x81, 0xa0, 0x9b, 0xfb, 0x64, 0x09, 0x4a, 0xdc, + 0x65, 0xeb, 0x50, 0x03, 0xb0, 0x1d, 0x97, 0x9c, 0x96, 0x56, 0x5a, 0x73, + 0x12, 0xbf, 0x5a, 0x3f, 0xdd, 0x60, 0x65, 0x6b, 0xc7, 0xaa, 0xf0, 0x53, + 0x8a, 0x2b, 0x3a, 0x7a, 0x36, 0x38, 0xe2, 0x05, 0xe9, 0xa0, 0x96, 0x5f, + 0x57, 0xe1, 0x75, 0x9f, 0x9d, 0x01, 0xb2, 0x05, 0x80, 0x46, 0x92, 0x0d, + 0x6f, 0x21, 0x15, 0xc0, 0x98, 0x24, 0xfe, 0x96, 0x00, 0x31, 0xbb, 0x24, + 0xb0, 0xd0, 0x26, 0x80, 0x08, 0xb8, 0x00, 0xc6, 0x80, 0x64, 0x37, 0x1a, + 0x8e, 0x1d, 0xf1, 0x17, 0xdb, 0x0d, 0x28, 0x7e, 0x0a, 0xb1, 0x18, 0x9b, + 0xce, 0x1a, 0x05, 0x06, 0xaf, 0x06, 0x96, 0x80, 0xa6, 0xaf, 0x46, 0x42, + 0x3e, 0x41, 0x7b, 0xef, 0xce, 0xe3, 0x52, 0xdc, 0x9e, 0x78, 0xab, 0x22, + 0x08, 0x60, 0x12, 0xe6, 0x17, 0xbb, 0xe2, 0x25, 0xc5, 0x2c, 0xac, 0x5a, + 0x3e, 0xeb, 0xde, 0xe6, 0xcc, 0x29, 0xae, 0x8e, 0xf8, 0xfb, 0xd2, 0x03, + 0x62, 0x85, 0x80, 0x46, 0x92, 0x0d, 0x00, 0x6c, 0x50, 0xee, 0x01, 0x1a, + 0x48, 0x37, 0xad, 0x1a, 0x43, 0x43, 0xa0, 0x97, 0x94, 0x3d, 0x0b, 0xf8, + 0xce, 0x49, 0x02, 0x34, 0x25, 0x93, 0x40, 0x25, 0x40, 0x04, 0x5c, 0x00, + 0x63, 0x69, 0xc6, 0x63, 0x70, 0x55, 0x8b, 0x26, 0xfc, 0x98, 0x0d, 0x8a, + 0x1d, 0xc0, 0x23, 0x49, 0x06, 0x80, 0x36, 0x28, 0x77, 0x00, 0x8d, 0x24, + 0x1b, 0xd7, 0x92, 0xc8, 0x60, 0x12, 0xa0, 0x02, 0x2e, 0x00, 0x31, 0x89, + 0x2c, 0x86, 0x01, 0x2a, 0x00, 0x22, 0x00, 0x17, 0xd4, 0xe5, 0x0f, 0xc1, + 0x54, 0x8c, 0x4d, 0xf9, 0x40, 0x0b, 0x0a, 0x30, 0x6e, 0x01, 0x1f, 0xe4, + 0x7f, 0xf5, 0xd9, 0x2c, 0x31, 0x25, 0x2f, 0x04, 0xe0, 0x08, 0x44, 0xa9, + 0x5c, 0x39, 0xab, 0xb1, 0x0c, 0x35, 0x07, 0xb2, 0x44, 0x2b, 0x91, 0xfd, + 0xe3, 0xd0, 0x9c, 0x5e, 0xdf, 0x1b, 0x0c, 0x8e, 0x3b, 0xd6, 0x12, 0x8a, + 0x9f, 0xdd, 0xe2, 0xf2, 0x76, 0xfb, 0xef, 0xbe, 0x7d, 0xf2, 0xf7, 0xdc, + 0xdd, 0xe2, 0xf5, 0x00, 0x1a, 0x86, 0x00, 0x1f, 0x00, 0xef, 0xa3, 0x12, + 0x03, 0x77, 0x0d, 0x28, 0x53, 0x7d, 0x8b, 0x46, 0x51, 0xdb, 0xfd, 0xf3, + 0xf0, 0x3e, 0xd9, 0x5a, 0xdc, 0xf7, 0xb6, 0x1a, 0x18, 0x06, 0x13, 0xf0, + 0x50, 0x8b, 0xc9, 0x28, 0xb2, 0x5a, 0x37, 0xba, 0x9e, 0x77, 0xad, 0x79, + 0x9e, 0xd1, 0xa8, 0xba, 0x9e, 0x67, 0xae, 0x79, 0x9e, 0x86, 0x9d, 0xd4, + 0xf3, 0x3d, 0x6b, 0xce, 0xf4, 0x34, 0xee, 0x97, 0x9d, 0xeb, 0x5e, 0x77, + 0xa1, 0xa7, 0x74, 0xbc, 0xef, 0x5a, 0xf3, 0xbd, 0x2d, 0x17, 0x4b, 0xce, + 0xf5, 0xaf, 0x33, 0xd0, 0xd3, 0xba, 0x9e, 0x67, 0xad, 0x79, 0xde, 0x86, + 0x9d, 0xd4, 0xf3, 0x3d, 0x6b, 0xce, 0xf4, 0xb4, 0xee, 0x97, 0x9d, 0xeb, + 0x5e, 0x67, 0xa1, 0xa7, 0x75, 0x3c, 0xef, 0x5a, 0xf3, 0x3d, 0x0d, 0x3b, + 0xa9, 0xe6, 0x7a, 0xe7, 0x99, 0xe9, 0x69, 0xdd, 0x4f, 0x33, 0xd6, 0xbc, + 0xef, 0x43, 0x45, 0xd4, 0xf3, 0x3d, 0x6b, 0xcc, 0xf4, 0x34, 0xee, 0xa7, + 0x99, 0xeb, 0x9e, 0x67, 0xa5, 0xa7, 0x75, 0x3c, 0xcf, 0x5c, 0xf3, 0x3d, + 0x0d, 0x3b, 0xa9, 0xe6, 0x7a, 0xd7, 0x9d, 0xe8, 0x69, 0xdd, 0x2f, 0x33, + 0xd7, 0x3c, 0xcf, 0x4b, 0x4e, 0xea, 0x79, 0x9e, 0xb9, 0xe6, 0x7a, 0x1a, + 0x2e, 0xa7, 0x99, 0xeb, 0x5e, 0x67, 0xa1, 0xa7, 0x75, 0x3c, 0xef, 0x5a, + 0xf3, 0x3d, 0x2d, 0x3b, 0xa9, 0xe6, 0x7a, 0xe7, 0x99, 0xe8, 0x69, 0xdd, + 0x4f, 0x33, 0xd7, 0x3c, 0xcf, 0x43, 0x4e, 0xea, 0x79, 0x9e, 0xb5, 0xe7, + 0x7a, 0x5a, 0x77, 0x4b, 0xce, 0xf5, 0xaf, 0x33, 0xd0, 0xd1, 0x75, 0x3c, + 0xef, 0x5a, 0xf3, 0x3d, 0x0d, 0x3b, 0xa9, 0xe6, 0x7a, 0xe7, 0x99, 0xe9, + 0x69, 0xdd, 0x2f, 0x3b, 0xd6, 0xbc, 0xef, 0x43, 0x4e, 0xe9, 0x79, 0xde, + 0xb5, 0xe6, 0x7a, 0x1a, 0x77, 0x53, 0xce, 0xf5, 0xaf, 0x33, 0xd2, 0xd3, + 0xba, 0x9e, 0x67, 0xac, 0x8d, 0x2d, 0x16, 0xaf, 0x17, 0x8d, 0x29, 0x9c, + 0x00, 0x00, 0x01, 0x05, 0x3b, 0xf9, 0xd3, 0xce, 0xf5, 0xaf, 0x33, 0xdf, + 0xb5, 0x35, 0xf3, 0x97, 0x53, 0xcc, 0xf5, 0xcf, 0x33, 0xd2, 0xd1, 0x75, + 0x3c, 0xcf, 0x5a, 0xf3, 0xbd, 0x0d, 0x3b, 0xa9, 0xe7, 0x28, 0xb0, 0x1b, + 0x00, 0xec, 0x31, 0x26, 0x06, 0xb9, 0x31, 0x21, 0x06, 0x9e, 0x57, 0xe9, + 0x1d, 0xbb, 0xa3, 0x61, 0xe1, 0xb5, 0x8f, 0x68, 0x26, 0xee, 0x59, 0x49, + 0xdb, 0xed, 0xc0, 0xf7, 0xf9, 0xc6, 0x27, 0x2d, 0x09, 0x61, 0xe8, 0xed, + 0xf0, 0xbd, 0x71, 0x01, 0x44, 0x81, 0x80, 0xd4, 0x24, 0xc3, 0xb8, 0x8f, + 0x6e, 0x1a, 0x82, 0x5a, 0x3e, 0xab, 0x78, 0x80, 0x3b, 0x47, 0x48, 0x67, + 0xf9, 0x7d, 0x01, 0x38, 0xc7, 0x63, 0x03, 0xf3, 0xe0, 0xfb, 0xab, 0xbe, + 0xfb, 0x7d, 0xdf, 0x73, 0x6f, 0x3a, 0x52, 0x49, 0x68, 0xdc, 0x7e, 0x5e, + 0x1d, 0xcd, 0xc1, 0xdd, 0x57, 0xae, 0xc0, 0x5b, 0xa4, 0xa0, 0x25, 0xc5, + 0x12, 0x3a, 0xd0, 0x1c, 0xca, 0x0e, 0xf7, 0x8a, 0x43, 0x43, 0xa3, 0x71, + 0xc4, 0x6b, 0xd2, 0x26, 0x76, 0x7d, 0x53, 0xe3, 0x79, 0x9e, 0xb9, 0xeb, + 0x12, 0x9c, 0x8d, 0xff, 0xfb, 0x6c, 0xbf, 0xfa, 0xb6, 0xc3, 0xfd, 0xef, + 0x64, 0x3d, 0xe4, 0xbd, 0x6d, 0xce, 0x2b, 0x3a, 0x5c, 0x09, 0x62, 0x7f, + 0x20, 0x62, 0x3c, 0x84, 0x34, 0xb3, 0x6c, 0x69, 0x1b, 0x00, 0xfa, 0xe6, + 0x86, 0x66, 0x46, 0x73, 0xfa, 0xf8, 0x07, 0xc7, 0x5e, 0xb5, 0x3c, 0x6a, + 0x53, 0xc7, 0x2b, 0x11, 0x40, 0x3e, 0xbe, 0x47, 0x04, 0x6b, 0xdd, 0x3f, + 0xd4, 0xfe, 0x02, 0xfa, 0x09, 0x33, 0xa8, 0xb0, 0x47, 0xfb, 0x21, 0x82, + 0x5f, 0xd9, 0x77, 0xcf, 0xaf, 0xa3, 0x06, 0x70, 0x1c, 0x82, 0x3f, 0xd9, + 0x0c, 0x12, 0xfe, 0xd3, 0x74, 0x80, 0x9c, 0x10, 0xc1, 0x81, 0x23, 0xf7, + 0x11, 0xbd, 0x00, 0x30, 0x24, 0xf1, 0x17, 0x8d, 0xee, 0x70, 0x60, 0x05, + 0xc0, 0x21, 0x26, 0x95, 0x83, 0x71, 0x41, 0x85, 0x15, 0xf3, 0x80, 0x52, + 0x5a, 0x50, 0x5b, 0x0f, 0x19, 0x83, 0x0b, 0x65, 0x2f, 0x64, 0xf4, 0x73, + 0x3e, 0xd7, 0x7c, 0x9a, 0x9d, 0x8a, 0x2d, 0xd7, 0x86, 0x77, 0x10, 0xd7, + 0x9b, 0x21, 0x80, 0x27, 0x2c, 0xa4, 0x96, 0x05, 0x39, 0x34, 0x07, 0x41, + 0x84, 0xde, 0x4d, 0x6c, 0x94, 0xe2, 0xf9, 0x41, 0x85, 0x37, 0x7c, 0x5e, + 0x35, 0x39, 0x38, 0x61, 0xed, 0xdd, 0xec, 0x00, 0x1e, 0x00, 0x60, 0x80, + 0x18, 0xe2, 0xfa, 0x51, 0xd2, 0x30, 0xcc, 0x47, 0xb9, 0x08, 0x64, 0xd2, + 0x53, 0x2d, 0xab, 0x4e, 0xe7, 0x37, 0x7c, 0x1d, 0xc7, 0x72, 0x27, 0xb8, + 0x84, 0x24, 0x9e, 0x81, 0xc7, 0x08, 0x1c, 0x1c, 0x40, 0xaf, 0x21, 0xfe, + 0x94, 0x10, 0x8b, 0x3b, 0xa7, 0x37, 0x0a, 0x7b, 0xa0, 0x19, 0xd8, 0x35, + 0x02, 0xdf, 0x13, 0x9c, 0x89, 0x57, 0x41, 0x29, 0x2c, 0x67, 0xb5, 0xeb, + 0xd5, 0x47, 0x28, 0x01, 0xe9, 0x4d, 0x83, 0x09, 0xb8, 0x0f, 0x24, 0x94, + 0x93, 0xc9, 0x05, 0xf6, 0xc1, 0x45, 0xb1, 0xe7, 0x6f, 0xee, 0xfb, 0xde, + 0x34, 0x03, 0x32, 0x80, 0xc8, 0x0c, 0x08, 0x68, 0xca, 0xe4, 0xa4, 0xb7, + 0xc7, 0xef, 0xb3, 0xef, 0x83, 0xef, 0x6f, 0xb2, 0x53, 0xb7, 0xc8, 0x4f, + 0xdf, 0x3e, 0x57, 0x36, 0xe9, 0x02, 0xa5, 0x38, 0xc4, 0xe1, 0x1c, 0x3a, + 0x60, 0x51, 0x2a, 0xfb, 0xda, 0x3c, 0xef, 0x02, 0x51, 0x60, 0x49, 0x21, + 0x5e, 0x7e, 0x50, 0x6a, 0xc0, 0xb1, 0x7d, 0x87, 0x21, 0x3f, 0x85, 0x6d, + 0xff, 0xbb, 0xc9, 0x49, 0x1d, 0xee, 0x21, 0xb8, 0x7e, 0xb8, 0xf7, 0x97, + 0x00, 0x70, 0xe0, 0x60, 0x9a, 0xee, 0x08, 0x7f, 0x6a, 0x70, 0x47, 0x04, + 0x28, 0xe1, 0x44, 0x57, 0xbd, 0x08, 0x60, 0x09, 0x80, 0x76, 0x43, 0x61, + 0x85, 0x72, 0xb7, 0x3d, 0x21, 0xbb, 0xa1, 0x81, 0x20, 0x0f, 0xb1, 0x7f, + 0x8a, 0x6d, 0x85, 0xeb, 0xc2, 0x10, 0x88, 0x48, 0x02, 0x81, 0x88, 0x0c, + 0x41, 0xd8, 0xd1, 0xff, 0xf7, 0xff, 0xb3, 0x67, 0xfd, 0x7e, 0xf3, 0x08, + 0x41, 0x9d, 0x0f, 0xfa, 0x9f, 0x33, 0x5e, 0x81, 0x0c, 0xbd, 0xf3, 0x65, + 0xdf, 0xa4, 0x00, 0x66, 0xa0, 0x03, 0xee, 0x00, 0x29, 0x4e, 0x13, 0xb9, + 0x49, 0xec, 0x17, 0xb7, 0x0e, 0xbd, 0x2b, 0xdc, 0xa0, 0x07, 0x64, 0xdc, + 0x1a, 0x43, 0x28, 0xac, 0xbc, 0x4c, 0xdd, 0x3c, 0xf2, 0x4a, 0x73, 0x61, + 0x44, 0x87, 0x71, 0x3a, 0xee, 0xbc, 0xc0, 0x77, 0x82, 0x76, 0x1c, 0x85, + 0xd8, 0x10, 0x94, 0x55, 0xd4, 0xf3, 0x3d, 0x6b, 0xce, 0xf7, 0x6b, 0x5c, + 0xd7, 0x4b, 0xce, 0xf5, 0xaf, 0x3b, 0xd0, 0xd3, 0xba, 0x5e, 0x77, 0xad, + 0x79, 0x9e, 0x86, 0x9d, 0xd4, 0xf3, 0x3d, 0x6b, 0xce, 0xf4, 0xb4, 0x5d, + 0x4f, 0x33, 0xd6, 0xbc, 0xef, 0x43, 0x4e, 0xe9, 0x79, 0xde, 0xb5, 0xe7, + 0x7a, 0x1a, 0x77, 0x4b, 0xce, 0xf5, 0xaf, 0x33, 0xd2, 0xd3, 0xba, 0x9e, + 0x67, 0xae, 0x79, 0x9e, 0x86, 0x9d, 0xd4, 0xf3, 0x3d, 0x6b, 0xce, 0xf4, + 0x34, 0xee, 0xa7, 0x99, 0xeb, 0x5e, 0x67, 0xa5, 0xa2, 0xea, 0x79, 0x9e, + 0xb9, 0xe7, 0x7a, 0x1a, 0x77, 0x4b, 0xcc, 0xf5, 0xcf, 0x33, 0xd0, 0xd3, + 0xba, 0x9e, 0x67, 0xad, 0x79, 0xde, 0x96, 0x9d, 0xd2, 0xf3, 0x3d, 0x73, + 0xcc, 0xf4, 0x34, 0xee, 0xa7, 0x9d, 0xeb, 0x5e, 0x67, 0xa1, 0xa7, 0x75, + 0x3c, 0xcf, 0x5a, 0xf3, 0xbd, 0x2d, 0x17, 0x4b, 0xce, 0xf5, 0xaf, 0x3b, + 0xd0, 0xd3, 0xba, 0x5e, 0x77, 0xad, 0x79, 0x9e, 0x86, 0x9d, 0xd4, 0xf3, + 0x3d, 0x73, 0xcc, 0xf4, 0xb4, 0xee, 0xa7, 0x99, 0xeb, 0x5e, 0x77, 0xa1, + 0xa7, 0x74, 0xbc, 0xef, 0x5a, 0xf3, 0x3d, 0x0d, 0x3b, 0xa9, 0xe7, 0x7a, + 0xd7, 0x99, 0xe9, 0x69, 0xdd, 0x4f, 0x33, 0xd7, 0x3c, 0xcf, 0x43, 0x45, + 0xd4, 0xf3, 0x3d, 0x6b, 0xce, 0xf4, 0x34, 0xee, 0x97, 0x9d, 0xeb, 0x5e, + 0x77, 0xa5, 0xa7, 0x74, 0xbc, 0xef, 0x5a, 0xf3, 0x3d, 0x0d, 0x3b, 0xa9, + 0xe6, 0x7a, 0xca, 0x52, 0xd3, 0xb5, 0x79, 0xa9, 0x49, 0x45, 0xca, 0x52, + 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x00, 0x00, 0x00, + 0x01, 0x06, 0x43, 0xf1, 0x4f, 0x33, 0xd6, 0xbc, 0xef, 0x7e, 0xc4, 0xd7, + 0xcf, 0x5d, 0x2f, 0x3b, 0xd6, 0xbc, 0xcf, 0x43, 0x4e, 0xea, 0x79, 0xde, + 0xb5, 0xe6, 0x7a, 0x1a, 0x77, 0x53, 0xcc, 0xf5, 0xaf, 0x3b, 0xd2, 0xd3, + 0xad, 0xf9, 0x04, 0xce, 0xdf, 0xe0, 0xfc, 0x45, 0x27, 0xac, 0x3e, 0xb4, + 0x9b, 0x99, 0x3b, 0x8f, 0x57, 0x23, 0xe2, 0x2d, 0xd3, 0x71, 0x9f, 0x71, + 0xc4, 0x63, 0x48, 0x21, 0xd7, 0x94, 0x0d, 0x40, 0xd4, 0x7c, 0x69, 0x82, + 0x71, 0xd7, 0xcd, 0x60, 0x9c, 0x4e, 0xbd, 0x9e, 0xaf, 0x54, 0x7a, 0x97, + 0xa1, 0x48, 0x00, 0xb4, 0x9a, 0x18, 0x18, 0x3f, 0x8d, 0x4a, 0xd8, 0x20, + 0xa6, 0xfc, 0x4e, 0xfd, 0x91, 0xf0, 0xe4, 0xda, 0xd0, 0x58, 0xd4, 0x64, + 0xe7, 0x5e, 0xdd, 0x67, 0xae, 0xf3, 0xe0, 0x0f, 0x8a, 0x72, 0x59, 0x34, + 0x7f, 0x25, 0x21, 0x6a, 0x64, 0x3b, 0xfd, 0x8c, 0xfd, 0x2f, 0xb8, 0xf1, + 0x9a, 0xf4, 0x40, 0xa0, 0x60, 0x17, 0x58, 0x1e, 0x58, 0x7d, 0xc4, 0x4d, + 0x2d, 0x8a, 0x13, 0x7c, 0xa0, 0x13, 0x93, 0x40, 0x2c, 0x21, 0x16, 0x9c, + 0x7b, 0x32, 0x30, 0xf4, 0xf5, 0x6c, 0xb1, 0xfe, 0xf7, 0x28, 0xc4, 0xa0, + 0xcc, 0x83, 0xfe, 0xcb, 0xec, 0xcf, 0xcd, 0xec, 0x2f, 0xaf, 0xdc, 0x80, + 0x07, 0xa1, 0xa4, 0xb2, 0x68, 0x61, 0x33, 0x1c, 0x80, 0x14, 0xa5, 0x4e, + 0x29, 0x18, 0x52, 0xc2, 0x77, 0x01, 0xe5, 0xd8, 0xfe, 0xf3, 0xc0, 0xaf, + 0x59, 0x45, 0x00, 0x8f, 0x89, 0xf7, 0x41, 0x31, 0x0c, 0x57, 0xe3, 0xef, + 0xe6, 0x97, 0xd3, 0x11, 0xc0, 0x72, 0x08, 0xff, 0x84, 0xd0, 0x4b, 0xfc, + 0x4d, 0xf3, 0x4b, 0xea, 0x3b, 0xac, 0xb0, 0x47, 0xfc, 0x26, 0x82, 0x5f, + 0xe2, 0x68, 0x02, 0xab, 0x0c, 0x01, 0x1e, 0x23, 0x7b, 0x60, 0x1d, 0x12, + 0x78, 0x8b, 0xcf, 0xca, 0x01, 0xbe, 0x4a, 0x3b, 0xe4, 0x0c, 0x4f, 0x0d, + 0x4e, 0x01, 0x21, 0x5b, 0x71, 0xa9, 0x16, 0xd8, 0xec, 0xcb, 0xff, 0x6b, + 0xe9, 0xc0, 0x54, 0x01, 0xb9, 0x41, 0xa9, 0x0d, 0x0d, 0xc4, 0xc0, 0x2e, + 0x92, 0xd3, 0xf3, 0x99, 0xbf, 0xff, 0x28, 0xe7, 0x61, 0xd7, 0x89, 0x00, + 0x6e, 0x05, 0x4b, 0x00, 0xd0, 0x01, 0xd0, 0x40, 0x42, 0x40, 0x35, 0x4b, + 0x3e, 0x3c, 0x02, 0xc2, 0x97, 0x97, 0xbf, 0x24, 0xfe, 0xfb, 0x81, 0x24, + 0xdf, 0x6b, 0xbe, 0x60, 0x03, 0x04, 0x24, 0x06, 0x01, 0x8a, 0xff, 0x74, + 0xaf, 0xc1, 0x35, 0x8b, 0xeb, 0x32, 0xbc, 0xfc, 0xd6, 0x4c, 0xe0, 0x16, + 0x90, 0x80, 0x7a, 0x82, 0x21, 0x07, 0x5b, 0xca, 0x01, 0xca, 0x04, 0xf2, + 0x37, 0xbd, 0x80, 0x06, 0x45, 0xf0, 0x32, 0x30, 0x7e, 0x23, 0x5f, 0x1c, + 0x49, 0x4c, 0x1a, 0xe6, 0x33, 0x87, 0x9c, 0x46, 0xba, 0x13, 0x9d, 0x70, + 0x65, 0x63, 0x8d, 0x24, 0x8e, 0x23, 0xda, 0x03, 0x3e, 0xe5, 0x21, 0xbe, + 0x3b, 0x4c, 0x43, 0x48, 0xc0, 0xc2, 0xf8, 0xed, 0xef, 0xaa, 0x64, 0x6d, + 0x99, 0x6a, 0x1d, 0xfe, 0x22, 0xde, 0xab, 0xed, 0x96, 0xf6, 0xe8, 0x43, + 0xd5, 0x3f, 0x1c, 0x7c, 0x3f, 0x49, 0x7d, 0x98, 0x95, 0xb0, 0x1f, 0x0d, + 0x04, 0x9f, 0xb0, 0xd0, 0x01, 0x8d, 0xc2, 0x01, 0xd0, 0x05, 0xe5, 0x80, + 0xe9, 0x3b, 0x06, 0xfe, 0x59, 0x33, 0x9e, 0x9e, 0x49, 0x0c, 0x16, 0x2d, + 0x03, 0xcd, 0xc7, 0xae, 0xac, 0x92, 0x92, 0xad, 0xfe, 0xe2, 0xec, 0xd2, + 0x4c, 0xfb, 0x56, 0xfe, 0x09, 0x25, 0xa1, 0x07, 0xf6, 0xbd, 0x59, 0x60, + 0x55, 0xd4, 0x80, 0xbe, 0x03, 0x5b, 0xc9, 0xa5, 0x18, 0xbc, 0xde, 0xfa, + 0x71, 0x68, 0xe5, 0x76, 0xe1, 0xdc, 0x78, 0x0b, 0xe8, 0x76, 0xab, 0x35, + 0x53, 0xab, 0x29, 0xcd, 0xff, 0xbf, 0xfc, 0xff, 0xc7, 0xfa, 0x3d, 0x6b, + 0xcc, 0xf7, 0xab, 0xdf, 0xf7, 0xf7, 0x93, 0x75, 0x3c, 0xef, 0x5a, 0xf3, + 0x3c, 0x9a, 0x77, 0x53, 0xcc, 0xf5, 0xcf, 0x33, 0xd0, 0xd1, 0x75, 0x3c, + 0xcf, 0x5c, 0xf3, 0x3d, 0x2d, 0x3b, 0xa5, 0xe7, 0x7a, 0xd7, 0x9d, 0xe8, + 0x69, 0xdd, 0x2f, 0x3b, 0xd6, 0xbc, 0xcf, 0x43, 0x4e, 0xea, 0x79, 0xde, + 0xb1, 0xe7, 0x7a, 0x5a, 0x77, 0x53, 0xcc, 0xf5, 0xaf, 0x3b, 0xd0, 0xd3, + 0xba, 0x5e, 0x67, 0xae, 0x79, 0x9e, 0x86, 0x8b, 0xa9, 0xe7, 0x7a, 0xd7, + 0x99, 0xe9, 0x69, 0xdd, 0x4f, 0x33, 0xd6, 0xbc, 0xef, 0x43, 0x4e, 0xe9, + 0x79, 0xde, 0xb5, 0xe7, 0x7a, 0x1a, 0x77, 0x4b, 0xce, 0xf5, 0xaf, 0x33, + 0xd2, 0xd3, 0xba, 0x9e, 0x67, 0xad, 0x79, 0xde, 0x86, 0x9d, 0xd2, 0xf3, + 0xbd, 0x6b, 0xce, 0xf4, 0x34, 0x5d, 0x2f, 0x3b, 0xd6, 0xbc, 0xcf, 0x4b, + 0x4e, 0xea, 0x79, 0xde, 0xb5, 0xe6, 0x7a, 0x1a, 0x77, 0x53, 0xcc, 0xf5, + 0xcf, 0x33, 0xd0, 0xd3, 0xba, 0x9e, 0x67, 0xad, 0x79, 0xde, 0x96, 0x9d, + 0xd2, 0xf3, 0xbd, 0x6b, 0xcc, 0xf4, 0x34, 0xee, 0xa7, 0x99, 0xeb, 0x9e, + 0x67, 0xa1, 0xa7, 0x75, 0x3c, 0xcf, 0x5c, 0xf3, 0x3d, 0x2d, 0x17, 0x53, + 0xcc, 0xf5, 0xaf, 0x3b, 0xd0, 0xd3, 0xba, 0x5e, 0x77, 0xad, 0x78, 0xd2, + 0xd3, 0xba, 0xde, 0x67, 0x9e, 0x94, 0xce, 0xc4, 0x69, 0x49, 0x45, 0xca, + 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, + 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x40, 0x00, 0x00, + 0x01, 0x07, 0x4b, 0xf3, 0x2f, 0x33, 0xd6, 0xbc, 0xef, 0x7e, 0xb2, 0xd7, + 0x83, 0x74, 0xbc, 0xef, 0x5a, 0xf3, 0xbd, 0x0d, 0x3b, 0xa5, 0xe7, 0x7a, + 0xd7, 0x9d, 0xe9, 0x69, 0xdd, 0x2f, 0x3b, 0xd6, 0xbc, 0xcf, 0x43, 0x4e, + 0xd3, 0xb7, 0xdf, 0x76, 0xfb, 0xab, 0xee, 0x77, 0xbc, 0x80, 0x06, 0x41, + 0xa0, 0x64, 0x85, 0xb7, 0x3c, 0xae, 0x4a, 0xef, 0xf7, 0xea, 0x3b, 0xf6, + 0x35, 0x28, 0x6e, 0x1f, 0x7b, 0x47, 0xb9, 0x40, 0x19, 0x06, 0x92, 0x88, + 0x63, 0x3f, 0x50, 0x6e, 0x6d, 0x80, 0x7a, 0x87, 0xea, 0x12, 0x87, 0x71, + 0x1a, 0xf3, 0x40, 0xa7, 0x6f, 0xf0, 0xf2, 0x3e, 0xbb, 0x48, 0x68, 0x74, + 0x38, 0xea, 0xe3, 0x7a, 0x70, 0xc4, 0xfd, 0x87, 0xbe, 0x0f, 0x03, 0xb7, + 0x83, 0xd8, 0x62, 0x36, 0x1d, 0xad, 0xa9, 0x28, 0x69, 0x68, 0x48, 0xe7, + 0x0f, 0xb4, 0x90, 0xd2, 0xc8, 0x17, 0xd5, 0x79, 0x3b, 0x5e, 0x8b, 0xbb, + 0x55, 0x3e, 0xc3, 0x86, 0x80, 0xdc, 0xb2, 0xc3, 0x40, 0x25, 0x4f, 0x65, + 0x01, 0xec, 0xac, 0x27, 0x75, 0xa7, 0x09, 0x8b, 0xd7, 0xab, 0x66, 0xca, + 0xd8, 0xc8, 0x1f, 0x7a, 0xff, 0x92, 0xcb, 0x71, 0xfe, 0xf2, 0x19, 0x90, + 0xc2, 0xaf, 0xe4, 0xf7, 0xde, 0x40, 0x4e, 0x43, 0x0d, 0x03, 0x21, 0xa1, + 0x85, 0x27, 0x14, 0x12, 0x5f, 0x4a, 0x59, 0xd2, 0xb0, 0xd2, 0xd4, 0x83, + 0x31, 0xc7, 0xf1, 0xea, 0xbe, 0x0f, 0x7d, 0x30, 0x10, 0x46, 0x8b, 0x48, + 0x15, 0x26, 0x96, 0x94, 0x95, 0x90, 0x1b, 0xdd, 0x5d, 0x3d, 0x2e, 0x37, + 0xf5, 0xee, 0x8d, 0xb2, 0xbb, 0x2b, 0x99, 0x7c, 0xb0, 0x09, 0x80, 0x2c, + 0xe1, 0xb8, 0x02, 0xd2, 0x46, 0xef, 0x7b, 0xa3, 0x7a, 0x32, 0x58, 0xfa, + 0xf3, 0xd6, 0xb0, 0x0d, 0xd9, 0x09, 0xe0, 0x2a, 0xe3, 0x80, 0xe5, 0xf4, + 0x94, 0xa3, 0xa3, 0xfd, 0xcc, 0x55, 0xf0, 0xa0, 0x03, 0xce, 0x3f, 0x5c, + 0x20, 0x03, 0xc2, 0x11, 0xfd, 0x0f, 0xc4, 0xf6, 0xbe, 0x5c, 0xbe, 0x51, + 0x9e, 0xd3, 0x57, 0xff, 0x58, 0x01, 0xd7, 0x25, 0x7c, 0x5f, 0x17, 0xaf, + 0x90, 0xa0, 0x87, 0x8b, 0x4f, 0xc7, 0x0e, 0x89, 0x34, 0x98, 0x35, 0x86, + 0x66, 0xd8, 0xca, 0xdc, 0x4c, 0x28, 0x31, 0x69, 0x5f, 0xc1, 0x5a, 0xcc, + 0x0b, 0x74, 0x0b, 0xad, 0x6f, 0x54, 0xa0, 0x03, 0x72, 0x68, 0xc2, 0x90, + 0x19, 0xba, 0xcb, 0x4e, 0x70, 0x15, 0x21, 0x7c, 0x2c, 0xe0, 0xfb, 0xdb, + 0xbc, 0xa4, 0x32, 0x1f, 0x01, 0xd9, 0x37, 0x93, 0x7b, 0xe7, 0x52, 0x37, + 0x03, 0xc8, 0x20, 0xf3, 0xf8, 0x76, 0x32, 0xd2, 0xf7, 0xe8, 0xe4, 0xdf, + 0xbf, 0x6c, 0x72, 0x15, 0x78, 0x41, 0x88, 0x19, 0xae, 0xa7, 0x99, 0xeb, + 0x9e, 0x67, 0xba, 0x5a, 0xc2, 0xb4, 0xf1, 0x00, 0x3a, 0x0d, 0x01, 0x27, + 0x01, 0x11, 0x00, 0x81, 0x79, 0xc2, 0x68, 0x60, 0x19, 0x43, 0x1a, 0x67, + 0x17, 0x7d, 0x75, 0xcd, 0xc7, 0xe5, 0xe1, 0x37, 0xc8, 0x09, 0x81, 0xb8, + 0x35, 0x0e, 0x1d, 0xc4, 0xde, 0x7d, 0xea, 0x27, 0x35, 0x55, 0xf5, 0xbe, + 0xf9, 0xf7, 0xdf, 0x7c, 0xaf, 0xb9, 0xfb, 0xe3, 0xf4, 0xef, 0x78, 0xd0, + 0x07, 0x20, 0x3a, 0x19, 0xc0, 0x2d, 0x46, 0x3d, 0xb1, 0x6d, 0x8d, 0x3b, + 0x24, 0x89, 0xc3, 0x85, 0xde, 0xe9, 0xed, 0xfa, 0x1a, 0xe1, 0x42, 0x46, + 0xeb, 0xa5, 0xe7, 0x7a, 0xd7, 0x9d, 0xe6, 0xd6, 0x77, 0x4b, 0xce, 0xf5, + 0xaf, 0x33, 0xd2, 0xd3, 0xba, 0x9e, 0x67, 0xae, 0x79, 0x9e, 0x86, 0x9d, + 0xd4, 0xf3, 0x3d, 0x6b, 0xce, 0xf4, 0x34, 0xee, 0x97, 0x9d, 0xeb, 0x5e, + 0x77, 0xa5, 0xa7, 0x74, 0xbc, 0xef, 0x5a, 0xf3, 0x3d, 0x0d, 0x17, 0x53, + 0xcc, 0xf5, 0xcf, 0x33, 0xd0, 0xd3, 0xba, 0x5e, 0x77, 0xad, 0x79, 0x9e, + 0x96, 0x9d, 0xd4, 0xf3, 0xbd, 0x6b, 0xcc, 0xf4, 0x34, 0xee, 0xa7, 0x99, + 0xeb, 0x5e, 0x77, 0xa1, 0xa7, 0x75, 0x3c, 0xcf, 0x5a, 0xf3, 0xbd, 0x2d, + 0x3b, 0xa5, 0xe7, 0x7a, 0xd7, 0x99, 0xe8, 0x69, 0xdd, 0x4f, 0x3b, 0xd6, + 0xbc, 0xcf, 0x43, 0x45, 0xd4, 0xf3, 0x3d, 0x73, 0xcc, 0xf4, 0xb4, 0xee, + 0xa7, 0x99, 0xeb, 0x9e, 0x67, 0xa1, 0xa7, 0x74, 0xbc, 0xef, 0x5a, 0xf3, + 0x3d, 0x0d, 0x3b, 0xa9, 0xe6, 0x7a, 0xe7, 0x99, 0xe9, 0x69, 0xdd, 0x4f, + 0x33, 0xd7, 0x3c, 0xcf, 0x43, 0x4e, 0xea, 0x79, 0x9e, 0xb5, 0xe7, 0x7a, + 0x1a, 0x77, 0x53, 0xcc, 0xf5, 0xb1, 0xa5, 0xa2, 0xeb, 0x79, 0x9e, 0x6a, + 0x4a, 0x77, 0x29, 0x4a, 0x4a, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0x80, 0x00, 0x00, 0x01, 0x08, 0x53, 0xe4, 0x3c, 0xef, + 0x5a, 0xf3, 0x3d, 0xfa, 0x83, 0x5e, 0x25, 0xd4, 0xf3, 0x3d, 0x6b, 0xce, + 0xf4, 0xb4, 0xee, 0xa7, 0x99, 0xeb, 0x5e, 0x77, 0xa1, 0xa7, 0x74, 0xbc, + 0xef, 0x69, 0x49, 0xc1, 0x41, 0xf9, 0xd7, 0x8c, 0x67, 0xca, 0x73, 0x2d, + 0x3c, 0x6a, 0x0b, 0x42, 0x3a, 0x73, 0xaf, 0xf0, 0x1e, 0xa0, 0xed, 0xc7, + 0x39, 0xc9, 0x53, 0x29, 0x2c, 0x65, 0xc4, 0x51, 0x30, 0x6a, 0x3f, 0x7c, + 0xa3, 0xed, 0x7a, 0x5b, 0xb5, 0xf3, 0xde, 0x61, 0xbd, 0x1f, 0x7c, 0x7e, + 0xfb, 0xe3, 0x17, 0x96, 0xb1, 0x43, 0xed, 0x43, 0x06, 0x97, 0xca, 0x61, + 0xa4, 0x95, 0x30, 0xd7, 0x00, 0xa3, 0xa4, 0xef, 0xbe, 0xed, 0xce, 0x54, + 0xe4, 0x81, 0x9b, 0xab, 0x8c, 0xef, 0x8e, 0x61, 0x9d, 0x95, 0x9b, 0x33, + 0xee, 0x3b, 0x65, 0x3e, 0xcb, 0xb9, 0x30, 0x14, 0xf9, 0x6e, 0xbd, 0xed, + 0x49, 0x6e, 0xaa, 0xf7, 0xe7, 0x00, 0x37, 0x28, 0x7e, 0xbd, 0x70, 0x6a, + 0x72, 0x52, 0xa5, 0xec, 0x47, 0xba, 0xe9, 0xcc, 0x93, 0x42, 0xb6, 0x23, + 0x4e, 0xc4, 0x63, 0xc3, 0x88, 0x26, 0x5e, 0x5b, 0x5d, 0x95, 0x55, 0x2f, + 0x11, 0xac, 0xbb, 0x5a, 0x12, 0x35, 0x1b, 0x8f, 0xdb, 0x85, 0xd4, 0x96, + 0x8e, 0x82, 0x5f, 0xe8, 0x6c, 0x4a, 0x42, 0x50, 0x96, 0xc8, 0x74, 0xed, + 0x94, 0xf8, 0xa0, 0x97, 0xcf, 0x97, 0xff, 0x37, 0x1d, 0x7a, 0xa1, 0x84, + 0xce, 0x97, 0x15, 0x71, 0x25, 0x2c, 0x1f, 0x5c, 0x7c, 0xd8, 0x02, 0xd0, + 0xc0, 0x27, 0x82, 0xf4, 0xa0, 0x37, 0x0c, 0x02, 0x78, 0x2f, 0x5f, 0x54, + 0xea, 0xce, 0x24, 0x89, 0x64, 0x4e, 0x6c, 0xe7, 0x2c, 0xdc, 0xc0, 0x6c, + 0x4d, 0xe5, 0x30, 0xab, 0xd0, 0x6a, 0xbf, 0xe6, 0xc0, 0x07, 0xa4, 0x20, + 0x45, 0xfc, 0x40, 0x5e, 0xad, 0xe0, 0x27, 0x21, 0x60, 0x12, 0xa3, 0x88, + 0xee, 0x46, 0xbe, 0x9b, 0xca, 0x01, 0x42, 0x3e, 0x4a, 0xfa, 0x0a, 0xe9, + 0x14, 0xfb, 0x2f, 0x61, 0xce, 0x78, 0x1b, 0xb5, 0xa7, 0xab, 0x88, 0xab, + 0xb2, 0x19, 0x44, 0xfa, 0x72, 0x37, 0xaf, 0xbe, 0x7b, 0xc0, 0xae, 0x33, + 0x7e, 0x4e, 0xbd, 0x8a, 0x53, 0xd2, 0x78, 0x55, 0x52, 0x76, 0xce, 0xc6, + 0x71, 0xd9, 0x41, 0x56, 0xaf, 0x82, 0x1d, 0x66, 0x99, 0x87, 0x8f, 0x3e, + 0xcd, 0x74, 0x55, 0x57, 0xbc, 0xcf, 0x56, 0x82, 0x93, 0xd0, 0x35, 0x08, + 0x25, 0x81, 0x76, 0xfb, 0xed, 0xce, 0xdb, 0xe3, 0xd1, 0xd8, 0xd5, 0xe6, + 0xe2, 0xd5, 0x5e, 0x19, 0x89, 0x68, 0x2d, 0xb3, 0xfd, 0xbb, 0x65, 0x25, + 0xf0, 0xd6, 0x01, 0xee, 0xea, 0x1e, 0xea, 0xff, 0xb2, 0xae, 0xc0, 0x0b, + 0x0e, 0x59, 0xf7, 0x38, 0xd3, 0xee, 0x3c, 0xcf, 0x63, 0x42, 0x30, 0x0d, + 0xc0, 0xa2, 0x3a, 0x18, 0x97, 0xf0, 0xd2, 0x94, 0xa6, 0x2f, 0x20, 0xe3, + 0xb6, 0xe8, 0x6e, 0x31, 0xa6, 0x26, 0x94, 0x18, 0x51, 0x48, 0x25, 0x7d, + 0xc0, 0xba, 0x50, 0xf9, 0x05, 0x21, 0x94, 0x48, 0xf8, 0xa5, 0x3a, 0x9f, + 0x7e, 0x22, 0x90, 0x0b, 0x0f, 0x15, 0x50, 0xd3, 0xaf, 0x63, 0xdc, 0x80, + 0x10, 0x00, 0x9c, 0x00, 0xf0, 0x35, 0xff, 0x02, 0xc9, 0x51, 0x62, 0x87, + 0x24, 0x38, 0xd1, 0x42, 0x2b, 0x80, 0xc0, 0x46, 0x74, 0x36, 0xe4, 0x20, + 0xc4, 0xe4, 0xb6, 0x40, 0x17, 0x25, 0x6e, 0x78, 0xc7, 0x4b, 0x9d, 0xdf, + 0xf3, 0xff, 0x32, 0x25, 0x13, 0x06, 0x90, 0x89, 0x79, 0x09, 0xfc, 0x6a, + 0x70, 0x40, 0x61, 0x49, 0x03, 0xc8, 0x35, 0x39, 0xf0, 0x75, 0x8e, 0x26, + 0x32, 0x4f, 0xaf, 0xfd, 0xf5, 0x77, 0xf3, 0x2e, 0x1a, 0x19, 0xf3, 0x0d, + 0xc1, 0x7a, 0xfa, 0xef, 0x6d, 0xfe, 0xdd, 0xf0, 0xfa, 0xb2, 0xb6, 0xfb, + 0xbf, 0x02, 0x4b, 0x6d, 0xcf, 0x71, 0xf6, 0x84, 0xa9, 0x3e, 0x8f, 0x95, + 0x5e, 0xaa, 0xae, 0x79, 0x9e, 0xb9, 0xe6, 0x7b, 0xb5, 0xae, 0x5b, 0xa5, + 0xe7, 0x7a, 0xd7, 0x99, 0xe8, 0x69, 0xdd, 0x4f, 0x3b, 0xd6, 0xbc, 0xcf, + 0x43, 0x4e, 0xea, 0x79, 0x9e, 0xb9, 0xe6, 0x7a, 0x5a, 0x2e, 0xa7, 0x99, + 0xeb, 0x5e, 0x77, 0xa1, 0xa7, 0x75, 0x3c, 0xcf, 0x5a, 0xf3, 0x3d, 0x0d, + 0x3b, 0xa9, 0xe6, 0x7a, 0xe7, 0x99, 0xe9, 0x69, 0xdd, 0x4f, 0x33, 0xd6, + 0xbc, 0xef, 0x43, 0x4e, 0xea, 0x79, 0x9e, 0xb5, 0xe7, 0x7a, 0x5a, 0x77, + 0x4b, 0xce, 0xf5, 0xaf, 0x33, 0xd0, 0xd3, 0xba, 0x9e, 0x67, 0xae, 0x79, + 0x9e, 0x86, 0x8b, 0xa9, 0xe6, 0x7a, 0xd7, 0x9d, 0xe8, 0x69, 0xdd, 0x2f, + 0x3b, 0xd6, 0xbc, 0xef, 0x4b, 0x4e, 0xe9, 0x79, 0xde, 0xb5, 0xe6, 0x7a, + 0x1a, 0x77, 0x53, 0xcc, 0xf5, 0xcf, 0x33, 0xd0, 0xd3, 0xba, 0x9e, 0x67, + 0xad, 0x79, 0xde, 0x96, 0x9d, 0xd4, 0xf3, 0x3d, 0x64, 0x69, 0x68, 0xb4, + 0x79, 0xa3, 0x4a, 0x67, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, + 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, + 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, + 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, + 0x11, 0x72, 0x94, 0xa4, 0x44, 0x00, 0x00, 0x01, 0x09, 0x53, 0xa1, 0xe6, + 0x7a, 0xd7, 0x9d, 0xef, 0xd2, 0x1a, 0xf1, 0xee, 0x97, 0x9d, 0xeb, 0x5e, + 0x67, 0xa1, 0xa7, 0x75, 0x3c, 0xef, 0x5a, 0xf3, 0x3d, 0x0d, 0x3b, 0xa9, + 0xed, 0x08, 0x46, 0x41, 0xc1, 0x46, 0x6c, 0x3b, 0x0a, 0xa3, 0xcc, 0xf6, + 0x59, 0xf0, 0x7c, 0xed, 0xf1, 0x5b, 0x66, 0xca, 0x5b, 0x88, 0x1c, 0xc6, + 0x28, 0xea, 0xc4, 0x14, 0x84, 0x66, 0x3c, 0xf3, 0xf6, 0x34, 0xfc, 0xdb, + 0x6c, 0xb3, 0x54, 0xba, 0xa7, 0x9d, 0xec, 0x46, 0xa5, 0x99, 0xfa, 0xeb, + 0x6d, 0x76, 0xe2, 0x99, 0x23, 0x46, 0xff, 0xc4, 0x21, 0x62, 0x7e, 0x46, + 0xe4, 0xf8, 0xf2, 0xb7, 0xee, 0xfc, 0x42, 0x8d, 0x3d, 0x6c, 0xf8, 0x3b, + 0xea, 0xb7, 0x9d, 0xec, 0xc9, 0x69, 0x77, 0x7e, 0xe7, 0x4d, 0xb5, 0xd4, + 0xf6, 0xb2, 0x56, 0x0e, 0x66, 0x66, 0x43, 0x30, 0xe3, 0x9d, 0x85, 0x3d, + 0x5b, 0xcc, 0xf1, 0xbf, 0x41, 0xdd, 0x9e, 0x47, 0xd8, 0x65, 0xbd, 0x68, + 0xcc, 0xe3, 0xce, 0xcc, 0xa3, 0x0e, 0x5b, 0x3f, 0x61, 0x7e, 0x0f, 0x33, + 0xd1, 0x92, 0x9c, 0x6f, 0xa9, 0xb5, 0xbf, 0x2b, 0x2f, 0xaf, 0x7e, 0xa7, + 0x14, 0xb5, 0x08, 0x5f, 0xa9, 0x7a, 0xe7, 0x99, 0xe4, 0xff, 0x9f, 0xe2, + 0xd4, 0x3f, 0x81, 0x17, 0x62, 0x75, 0x6a, 0x50, 0xea, 0x75, 0x2d, 0x4c, + 0x8e, 0x79, 0xe7, 0xa3, 0xc1, 0xe6, 0x7b, 0x2c, 0x94, 0xe3, 0x66, 0xda, + 0xd7, 0x81, 0xb3, 0xbb, 0x89, 0x7d, 0xe2, 0x1f, 0xc4, 0x89, 0xac, 0x79, + 0x9e, 0x4f, 0xc7, 0x9d, 0x3b, 0x53, 0x18, 0x0e, 0x11, 0x9b, 0x4c, 0x1f, + 0xcf, 0x7e, 0x2f, 0xaf, 0xac, 0x3f, 0xce, 0xf3, 0x3d, 0x0f, 0xc7, 0xf9, + 0xdd, 0x7f, 0xf1, 0xe2, 0x8e, 0xc7, 0x0e, 0x76, 0x72, 0x7c, 0xcf, 0xce, + 0x75, 0x0e, 0x64, 0x75, 0x8f, 0x5e, 0xf5, 0x8f, 0x3b, 0xd0, 0xe9, 0x1f, + 0x36, 0xd6, 0x86, 0x35, 0x1e, 0xb5, 0xe6, 0x79, 0x2e, 0x2e, 0xa7, 0x99, + 0xeb, 0x9e, 0x67, 0x86, 0x8b, 0xa9, 0xe6, 0x7a, 0xd7, 0x9d, 0xe9, 0x69, + 0xdd, 0x4f, 0x33, 0xd6, 0xbc, 0xcf, 0x43, 0x45, 0xd4, 0xf3, 0x3d, 0x73, + 0xcc, 0xf4, 0x34, 0xee, 0xa7, 0x99, 0xeb, 0x9e, 0x67, 0xa5, 0xa7, 0x75, + 0x3c, 0xcf, 0x5a, 0xf3, 0xbd, 0x0d, 0x3b, 0xa9, 0xe6, 0x7a, 0xd7, 0x99, + 0xe8, 0x69, 0xdd, 0x4f, 0x3b, 0xd6, 0xbc, 0xcf, 0x43, 0x4e, 0xea, 0x79, + 0x9e, 0xb9, 0xe6, 0x7a, 0x5a, 0x2e, 0xa7, 0x99, 0xeb, 0x5e, 0x77, 0xa1, + 0xa7, 0x74, 0xbc, 0xef, 0x5a, 0xf3, 0xbd, 0x0d, 0x3b, 0xa5, 0xe7, 0x7a, + 0xd7, 0x99, 0xe9, 0x69, 0xdd, 0x4f, 0x33, 0xd7, 0x3c, 0x68, 0x69, 0xdd, + 0x6f, 0x33, 0xd6, 0x52, 0x99, 0xda, 0xbc, 0xd4, 0xa4, 0xa2, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, + 0x01, 0x0a, 0x53, 0xd0, 0xf3, 0xbd, 0x6b, 0xcc, 0xf7, 0xdf, 0xb5, 0xe5, + 0x5d, 0x4f, 0x33, 0xd6, 0xbc, 0xef, 0x43, 0x45, 0xd2, 0xf3, 0xbd, 0x6b, + 0xce, 0xf4, 0xb4, 0xee, 0x97, 0x9d, 0xeb, 0x5e, 0x67, 0xa1, 0xa7, 0x75, + 0x3c, 0xcf, 0x5c, 0xf3, 0x3d, 0x0d, 0x3b, 0xa9, 0xe6, 0x7a, 0xe7, 0x99, + 0xe9, 0x69, 0xdd, 0x4f, 0x33, 0xd6, 0xbc, 0xcf, 0x43, 0x4e, 0xea, 0x79, + 0xde, 0xb5, 0xe6, 0x7a, 0x1a, 0x77, 0x53, 0xcc, 0xf5, 0xcf, 0x33, 0xd2, + 0xd1, 0x75, 0x3c, 0xcf, 0x5a, 0xf3, 0x3d, 0x0d, 0x3b, 0xad, 0xe6, 0x7a, + 0xd7, 0x99, 0xe8, 0x69, 0xdd, 0x4f, 0x33, 0xd7, 0x3c, 0xcf, 0x4b, 0x4e, + 0xea, 0x79, 0x9e, 0xb5, 0xe7, 0x7a, 0x1a, 0x77, 0x53, 0xcc, 0xf5, 0xaf, + 0x33, 0xd0, 0xd3, 0xba, 0x9e, 0x77, 0xad, 0x79, 0x9e, 0x96, 0x8b, 0xa9, + 0xe6, 0x7a, 0xe7, 0x99, 0xe8, 0x69, 0xdd, 0x4f, 0x33, 0xd6, 0xbc, 0xef, + 0x43, 0x4e, 0xea, 0x79, 0x9e, 0xb5, 0xe7, 0x7a, 0x5a, 0x77, 0x4b, 0xce, + 0xf5, 0xaf, 0x33, 0xd0, 0xd3, 0xba, 0x9e, 0x67, 0xad, 0x79, 0xde, 0x86, + 0x9d, 0xd4, 0xf3, 0x3d, 0x6b, 0xce, 0xf4, 0xb4, 0xee, 0x97, 0x9d, 0xeb, + 0x5e, 0x77, 0xa1, 0xa2, 0xe9, 0x79, 0xde, 0xb5, 0xe6, 0x7a, 0x1a, 0x77, + 0x53, 0xcc, 0xf5, 0xcf, 0x1a, 0x5a, 0x77, 0x5b, 0xcc, 0xf3, 0xd2, 0x99, + 0xd8, 0x8d, 0x29, 0x28, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x20, 0x00, 0x00, 0x01, 0x0b, 0x53, 0xeb, 0xbc, 0xef, + 0x5a, 0xf3, 0xbd, 0xf6, 0xcd, 0x79, 0x97, 0x4b, 0xce, 0xf5, 0xaf, 0x33, + 0xd2, 0xd3, 0xba, 0x9e, 0x67, 0xae, 0x79, 0x9e, 0x86, 0x9d, 0xd4, 0xf3, + 0x3d, 0x6b, 0xce, 0xf4, 0x34, 0xee, 0xa7, 0x99, 0xeb, 0x5e, 0x67, 0xa5, + 0xa7, 0x75, 0x3c, 0xcf, 0x5c, 0xf3, 0x3d, 0x0d, 0x3b, 0xa9, 0xe6, 0x7a, + 0xd7, 0x9d, 0xe8, 0x68, 0xba, 0x9e, 0x67, 0xad, 0x79, 0xde, 0x96, 0x9d, + 0xd2, 0xf3, 0xbd, 0x6b, 0xcc, 0xf4, 0x34, 0xee, 0xa7, 0x99, 0xeb, 0x5e, + 0x77, 0xa1, 0xa7, 0x75, 0x3c, 0xcf, 0x5a, 0xf3, 0xbd, 0x2d, 0x3b, 0xa5, + 0xe7, 0x7a, 0xd7, 0x9d, 0xe8, 0x69, 0xdd, 0x2f, 0x3b, 0xd6, 0xbc, 0xcf, + 0x43, 0x45, 0xd4, 0xf3, 0x3d, 0x73, 0xcc, 0xf4, 0xb4, 0xee, 0xa7, 0x99, + 0xeb, 0x5e, 0x77, 0xa1, 0xa7, 0x74, 0xbc, 0xef, 0x5a, 0xf3, 0x3d, 0x0d, + 0x3b, 0xa9, 0xe7, 0x7a, 0xd7, 0x99, 0xe9, 0x69, 0xdd, 0x4f, 0x33, 0xd6, + 0xbc, 0xef, 0x43, 0x4e, 0xea, 0x79, 0x9e, 0xb5, 0xe7, 0x7a, 0x1a, 0x77, + 0x4b, 0xce, 0xf5, 0xaf, 0x33, 0xd2, 0xd3, 0xba, 0x9e, 0x77, 0xac, 0x9a, + 0x86, 0x8b, 0xad, 0xe6, 0x79, 0xa9, 0x29, 0xd8, 0x8d, 0x29, 0x28, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x88, 0x00, 0x00, + 0x01, 0x0c, 0x53, 0xf4, 0xef, 0x33, 0xd6, 0xbc, 0xef, 0x7d, 0x73, 0x5e, + 0x7d, 0xd2, 0xf3, 0xbd, 0x6b, 0xce, 0xf4, 0x34, 0xee, 0x97, 0x99, 0xeb, + 0x9e, 0x67, 0xa1, 0xa7, 0x75, 0x3c, 0xef, 0x58, 0xf3, 0xbd, 0x2d, 0x3b, + 0xa9, 0xe6, 0x7a, 0xd7, 0x9d, 0xe8, 0x68, 0xba, 0x5e, 0x77, 0xad, 0x79, + 0x9e, 0x86, 0x9d, 0xd4, 0xf3, 0xbd, 0x6b, 0xcc, 0xf4, 0xb4, 0xee, 0xa7, + 0x99, 0xeb, 0x5e, 0x77, 0xa1, 0xa7, 0x74, 0xbc, 0xef, 0x5a, 0xf3, 0xbd, + 0x0d, 0x3b, 0xa5, 0xe7, 0x7a, 0xd7, 0x99, 0xe9, 0x69, 0xdd, 0x4f, 0x33, + 0xd7, 0x3c, 0xcf, 0x43, 0x45, 0xd4, 0xf3, 0x3d, 0x6b, 0xce, 0xf4, 0x34, + 0xee, 0x97, 0x9d, 0xeb, 0x5e, 0x67, 0xa5, 0xa7, 0x75, 0x3c, 0xef, 0x5a, + 0xf3, 0x3d, 0x0d, 0x3b, 0xa9, 0xe6, 0x7a, 0xe7, 0x99, 0xe8, 0x69, 0xdd, + 0x4f, 0x33, 0xd6, 0xbc, 0xef, 0x4b, 0x4e, 0xea, 0x79, 0x9e, 0xb5, 0xe6, + 0x7a, 0x1a, 0x77, 0x53, 0xce, 0xf5, 0x91, 0xa5, 0xa2, 0xd1, 0xe6, 0x8d, + 0x29, 0x9d, 0xca, 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, + 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, + 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, + 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, + 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, + 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, + 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, + 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, + 0x29, 0x4a, 0x44, 0x40, 0x00, 0x00, 0x01, 0x0d, 0x53, 0xf6, 0xcf, 0x3b, + 0xd6, 0x3c, 0xef, 0x7d, 0x2b, 0x5c, 0x57, 0x53, 0xcc, 0xf5, 0xaf, 0x3b, + 0xd0, 0xd3, 0xba, 0x5e, 0x77, 0xad, 0x79, 0x9e, 0x96, 0x8b, 0xa9, 0xe7, + 0x7a, 0xd7, 0x99, 0xe8, 0x69, 0xdd, 0x4f, 0x33, 0xd6, 0xbc, 0xef, 0x43, + 0x4e, 0xe9, 0x79, 0xde, 0xb5, 0xe7, 0x7a, 0x5a, 0x77, 0x4b, 0xce, 0xf5, + 0xaf, 0x33, 0xd0, 0xd3, 0xba, 0x9e, 0x67, 0xae, 0x79, 0x9e, 0x86, 0x9d, + 0xd4, 0xf3, 0x3d, 0x73, 0xcc, 0xf4, 0xb4, 0xee, 0xa7, 0x99, 0xeb, 0x5e, + 0x77, 0xa1, 0xa2, 0xe9, 0x79, 0xde, 0xb5, 0xe6, 0x7a, 0x1a, 0x77, 0x53, + 0xcc, 0xf5, 0xcf, 0x33, 0xd2, 0xd3, 0xba, 0x9e, 0x67, 0xae, 0x79, 0x9e, + 0x86, 0x9d, 0xd4, 0xf3, 0x3d, 0x6b, 0xcc, 0xf4, 0x34, 0xee, 0xa7, 0x9d, + 0xeb, 0x29, 0x4c, 0xed, 0x5e, 0x31, 0xa4, 0xa2, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x88, 0x00, 0x00, 0x01, + 0x0e, 0x53, 0xfa, 0x2b, 0xce, 0xf5, 0xaf, 0x33, 0xde, 0xe3, 0x5c, 0xb7, + 0x53, 0xcc, 0xf5, 0xcf, 0x33, 0xd2, 0xd1, 0x75, 0x3c, 0xcf, 0x5a, 0xf3, + 0xbd, 0x0d, 0x3b, 0xa9, 0xe6, 0x7a, 0xd7, 0x9d, 0xe8, 0x69, 0xdd, 0x2f, + 0x3b, 0xd6, 0xbc, 0xcf, 0x4b, 0x4e, 0xea, 0x79, 0x9e, 0xb9, 0xe6, 0x7a, + 0x1a, 0x77, 0x53, 0xcc, 0xf5, 0xaf, 0x3b, 0xd0, 0xd3, 0xba, 0x5e, 0x77, + 0xad, 0x79, 0x9e, 0x96, 0x8b, 0xa9, 0xe7, 0x7a, 0xd7, 0x99, 0xe8, 0x69, + 0xdd, 0x4f, 0x33, 0xd7, 0x3c, 0xcf, 0x43, 0x4e, 0xea, 0x79, 0x9e, 0xb5, + 0xe6, 0xa5, 0xa7, 0x75, 0x3c, 0xef, 0x3d, 0x29, 0x9d, 0x88, 0xd2, 0x92, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x88, 0x00, 0x00, 0x01, 0x0f, 0x53, 0xfa, 0xab, 0xcc, 0xf5, 0xaf, 0x3b, + 0xde, 0xa3, 0x5c, 0xf7, 0x4b, 0xce, 0xf5, 0xaf, 0x33, 0xd0, 0xd3, 0xba, + 0x9e, 0x67, 0xae, 0x79, 0x9e, 0x86, 0x9d, 0xd4, 0xf3, 0x3d, 0x6b, 0xce, + 0xf4, 0xb4, 0xee, 0xa7, 0x99, 0xeb, 0x5e, 0x77, 0xa1, 0xa7, 0x74, 0xbc, + 0xef, 0x5a, 0xf3, 0x3d, 0x0d, 0x3b, 0xa9, 0xe7, 0x7a, 0xd7, 0x99, 0xe9, + 0x68, 0xba, 0x9e, 0x67, 0xad, 0x78, 0xd0, 0xd3, 0xb4, 0x79, 0x9e, 0x6a, + 0x4a, 0x76, 0x23, 0x4a, 0x4a, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x97, 0xcf, + 0xca, 0x00, 0x58, 0x90, 0xd0, 0x1d, 0x00, 0x1f, 0xa0, 0x0a, 0x86, 0x01, + 0x81, 0x89, 0x4b, 0xb1, 0x6e, 0xc5, 0x3b, 0xa9, 0x9c, 0xcf, 0x6e, 0x58, + 0x6a, 0x03, 0x18, 0x6f, 0x67, 0xed, 0x84, 0xd3, 0xb7, 0xf7, 0xf4, 0x0a, + 0x5c, 0x22, 0x10, 0x0c, 0x0a, 0x21, 0x93, 0x40, 0x6c, 0x82, 0x1a, 0x1f, + 0xf4, 0x21, 0x1c, 0x61, 0x6c, 0x9e, 0x90, 0xe5, 0x31, 0xaf, 0x54, 0x01, + 0xa8, 0x06, 0x98, 0x03, 0x44, 0x92, 0xb9, 0x34, 0xa2, 0xcb, 0xcc, 0xe8, + 0x40, 0xd5, 0x38, 0xff, 0xf3, 0x07, 0x1e, 0xb9, 0x12, 0xd0, 0x34, 0x61, + 0xac, 0xec, 0xdc, 0xe8, 0x6e, 0x7d, 0xfa, 0x2a, 0x52, 0x96, 0x35, 0x5c, + 0xa5, 0x29, 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, 0xf1, 0xa0, 0x18, + 0x00, 0x38, 0x48, 0x05, 0xe8, 0x4f, 0x28, 0x9a, 0x56, 0xe8, 0x61, 0xa8, + 0xf9, 0x2a, 0x77, 0x65, 0xb3, 0x73, 0xad, 0x8b, 0x2d, 0x01, 0x98, 0x6a, + 0x7b, 0x61, 0xf4, 0xec, 0xf7, 0xec, 0xa9, 0x72, 0x80, 0x62, 0x05, 0x10, + 0x03, 0xa2, 0xf9, 0x40, 0x67, 0x16, 0x94, 0x25, 0x28, 0x4b, 0x64, 0xe6, + 0xe8, 0xec, 0x6e, 0xce, 0x1d, 0x71, 0x09, 0x80, 0x0f, 0x31, 0x34, 0x07, + 0x5c, 0x07, 0x60, 0x15, 0x86, 0x20, 0xa4, 0x29, 0x23, 0x72, 0x5f, 0x25, + 0xb7, 0x18, 0x72, 0xfa, 0xcc, 0x3e, 0x7c, 0xb4, 0x6d, 0xbf, 0xea, 0xe7, + 0x2b, 0x9b, 0x26, 0xf7, 0xec, 0x29, 0x78, 0x00, 0x13, 0x00, 0x2e, 0x48, + 0x06, 0xa1, 0x9c, 0x34, 0x98, 0x43, 0x0c, 0xfb, 0x71, 0x9d, 0xf1, 0x79, + 0x6d, 0xb8, 0xe1, 0x76, 0xde, 0x00, 0xe9, 0xdb, 0x13, 0x00, 0x62, 0x03, + 0xa2, 0x6a, 0x7f, 0x24, 0x62, 0xdb, 0x9d, 0xfb, 0x84, 0xb6, 0xb5, 0x00, + 0xb4, 0x31, 0xfe, 0x1a, 0xbc, 0x7a, 0xba, 0x84, 0xaa, 0x43, 0x3d, 0xfb, + 0x2a, 0x5c, 0xd0, 0x1d, 0x80, 0xc0, 0x9a, 0x02, 0x62, 0xc9, 0x44, 0xd2, + 0xba, 0x39, 0x8e, 0x77, 0x5b, 0x3e, 0xe6, 0xeb, 0x20, 0x06, 0x80, 0x26, + 0x00, 0x37, 0x0d, 0xc4, 0xcc, 0x5a, 0x0a, 0x4f, 0x24, 0x6f, 0xba, 0xdb, + 0x7f, 0xd7, 0xb2, 0x9b, 0x63, 0x6c, 0x39, 0x68, 0xc8, 0x1a, 0xea, 0x67, + 0x73, 0xd5, 0x53, 0x2d, 0xaf, 0xbb, 0x4b, 0xe7, 0x25, 0x80, 0x2a, 0xe1, + 0x81, 0xa8, 0x0c, 0x48, 0x0c, 0x40, 0xa2, 0x0b, 0x4b, 0x0c, 0x47, 0x1a, + 0xfd, 0xfb, 0x66, 0x51, 0xbe, 0xf5, 0x60, 0x27, 0x00, 0xc8, 0x0a, 0x80, + 0x65, 0xd1, 0x89, 0xa8, 0x61, 0xbb, 0xfd, 0xf7, 0xdf, 0x76, 0x3d, 0x78, + 0xdd, 0x6c, 0x82, 0x18, 0x61, 0x7f, 0x6c, 0x9d, 0xdd, 0x78, 0x58, 0x81, + 0xf2, 0xe8, 0xfa, 0xfb, 0x74, 0xa5, 0xe4, 0x00, 0x60, 0x00, 0xd8, 0x0a, + 0x80, 0x9c, 0x0a, 0x16, 0x43, 0x43, 0x64, 0x23, 0x76, 0xdc, 0xf2, 0xc6, + 0x65, 0xec, 0xc1, 0x4b, 0xb1, 0x49, 0x68, 0x2b, 0x3a, 0x7b, 0x63, 0xaa, + 0xd9, 0xef, 0xd7, 0xd2, 0x94, 0xb2, 0x9d, 0xca, 0x52, 0x91, 0x17, 0x29, + 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, + 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, 0xb8, + 0x40, 0x1a, 0x00, 0x98, 0x00, 0xdc, 0x37, 0x13, 0x31, 0x68, 0x29, 0x3c, + 0x91, 0xbe, 0xeb, 0x6d, 0xff, 0x5e, 0xca, 0x6d, 0x8d, 0xad, 0x29, 0xc8, + 0xdf, 0xf5, 0x6c, 0x7c, 0x8f, 0xbe, 0xed, 0x2f, 0x22, 0x00, 0xec, 0x03, + 0x04, 0x00, 0x5c, 0x90, 0xc2, 0x80, 0xcf, 0x2c, 0xb4, 0xa0, 0x6a, 0x73, + 0x36, 0xc6, 0x66, 0xe6, 0xbf, 0xdc, 0x2a, 0xf5, 0xd5, 0x14, 0x5e, 0x2d, + 0x19, 0x28, 0x5e, 0xe7, 0x4b, 0xe6, 0xb9, 0x4a, 0x52, 0xca, 0xab, 0x94, + 0xbe, 0x6a, 0x03, 0xa0, 0x05, 0x88, 0x26, 0x90, 0xb8, 0x0e, 0xd2, 0x05, + 0x70, 0x0d, 0x86, 0x25, 0x2e, 0x84, 0x24, 0x68, 0x16, 0xfd, 0xd4, 0x96, + 0x1d, 0xef, 0xa5, 0x5d, 0x25, 0x06, 0xa0, 0xbe, 0x9c, 0x8e, 0xe3, 0x3b, + 0x71, 0x23, 0xa8, 0xfb, 0xeb, 0x94, 0xa5, 0x2e, 0x6a, 0xee, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x88, + 0x00, 0x00, 0x01, 0x10, 0x53, 0xfb, 0x23, 0xcc, 0xf5, 0xcf, 0x33, 0xdd, + 0x6d, 0x65, 0x75, 0x3c, 0xcf, 0x5a, 0xf3, 0xbd, 0x2d, 0x3b, 0xa5, 0xe7, + 0x7a, 0xd7, 0x9d, 0xe8, 0x69, 0xdd, 0x2f, 0x3b, 0xd6, 0xbc, 0xcf, 0x43, + 0x4e, 0xea, 0x79, 0x9e, 0xb6, 0x34, 0xb4, 0x5a, 0x3c, 0xd1, 0xa5, 0x33, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x7c, 0x24, 0x00, 0xb8, 0x37, + 0x06, 0x80, 0xe8, 0x9a, 0x4c, 0xf9, 0xc6, 0x25, 0xd6, 0xbc, 0x30, 0xbc, + 0xcc, 0xc0, 0x22, 0xee, 0x26, 0xfb, 0xb5, 0xf2, 0xb0, 0x02, 0xac, 0x90, + 0x03, 0xf2, 0x6f, 0x58, 0xc0, 0x30, 0xdc, 0x73, 0xf4, 0xe6, 0x71, 0x6c, + 0x35, 0x83, 0xef, 0x64, 0x03, 0x54, 0x38, 0x14, 0x2c, 0x53, 0x7c, 0x27, + 0x7b, 0x00, 0xce, 0xc9, 0xd5, 0xff, 0x41, 0x00, 0x84, 0x0c, 0x8e, 0x9e, + 0x49, 0xef, 0x91, 0x8d, 0xc3, 0xfd, 0xea, 0x4a, 0x29, 0x21, 0xa9, 0x6c, + 0x1f, 0xee, 0x70, 0x15, 0x4f, 0x48, 0x6a, 0x45, 0x76, 0x0a, 0xbe, 0x58, + 0x03, 0xad, 0x8a, 0x26, 0x72, 0xc7, 0xbf, 0xe1, 0xd6, 0xbf, 0x8c, 0xce, + 0x6f, 0xa2, 0xfd, 0xa0, 0x05, 0x48, 0xdf, 0x23, 0x96, 0x13, 0xcc, 0x65, + 0x0a, 0xbd, 0x29, 0x64, 0xce, 0x18, 0x96, 0x4f, 0x0f, 0xb9, 0x64, 0x2d, + 0xc3, 0x49, 0xbb, 0xf7, 0xeb, 0x7f, 0xc0, 0x3f, 0xbc, 0x88, 0x0e, 0xd8, + 0xb0, 0xd2, 0x8a, 0x0b, 0x0f, 0x6a, 0x9f, 0xe4, 0xec, 0xee, 0xa7, 0xe2, + 0xa2, 0xfd, 0x29, 0x60, 0x50, 0x94, 0x50, 0xde, 0x91, 0xcc, 0x2e, 0x72, + 0x60, 0x14, 0xc1, 0xa8, 0xe9, 0xfd, 0xb6, 0xbc, 0x58, 0x09, 0xfb, 0x92, + 0x8a, 0xc6, 0xf7, 0x20, 0x11, 0x6e, 0x71, 0x60, 0x28, 0x02, 0x8d, 0xbb, + 0xbe, 0x61, 0x2e, 0x7b, 0x13, 0xee, 0xd1, 0xbc, 0x66, 0x1f, 0xf9, 0x3a, + 0xcd, 0xaf, 0xec, 0xa1, 0x85, 0x01, 0x94, 0xb3, 0xb5, 0xe3, 0x80, 0x0f, + 0x00, 0xbb, 0x6e, 0xac, 0x7f, 0xe4, 0x6c, 0x1d, 0x56, 0x02, 0x62, 0x80, + 0xbf, 0xff, 0x12, 0x09, 0xdc, 0x07, 0xf7, 0x64, 0x0a, 0x86, 0xfe, 0x1b, + 0xdb, 0x92, 0x15, 0x88, 0xb7, 0x07, 0xff, 0xa5, 0xb7, 0x32, 0xd0, 0xca, + 0xb3, 0xd4, 0x00, 0xc4, 0x99, 0x80, 0xa1, 0x0c, 0xb0, 0xcc, 0x9c, 0x5e, + 0x47, 0x61, 0xb8, 0xd0, 0x3b, 0x8e, 0xe2, 0xef, 0x00, 0x00, 0x91, 0x3c, + 0x02, 0xb0, 0xd4, 0x3a, 0xb8, 0x63, 0xfc, 0x2f, 0x27, 0x31, 0x1f, 0xdf, + 0x53, 0xbe, 0x7e, 0x00, 0xb0, 0x34, 0x94, 0x02, 0x1c, 0x86, 0x01, 0xca, + 0x12, 0x81, 0x0d, 0x86, 0x80, 0x0c, 0xae, 0x50, 0x2b, 0x9c, 0x84, 0x5e, + 0x3c, 0x23, 0x93, 0xad, 0x10, 0x49, 0xd5, 0xc6, 0xf0, 0x1d, 0x94, 0x08, + 0x5f, 0x29, 0xd8, 0xdf, 0x82, 0xfa, 0xc8, 0xfe, 0xa4, 0x0a, 0xa7, 0x23, + 0x20, 0x99, 0x8e, 0x28, 0xfe, 0x3d, 0x26, 0x85, 0x1d, 0x78, 0x52, 0x1a, + 0x00, 0xc0, 0x69, 0x64, 0x3e, 0x3b, 0x0c, 0x01, 0x5f, 0xcc, 0x4e, 0x67, + 0xbe, 0xd8, 0x7f, 0xb4, 0x28, 0x27, 0x61, 0x5b, 0x05, 0xd3, 0xea, 0xdf, + 0x81, 0x00, 0x98, 0x10, 0x82, 0x40, 0x1d, 0x72, 0xc0, 0x70, 0x9e, 0x51, + 0x69, 0x70, 0x1e, 0xf7, 0xc8, 0xe1, 0xe1, 0x0d, 0x7e, 0x81, 0xbe, 0x00, + 0x00, 0xec, 0x10, 0x81, 0xc0, 0x30, 0x41, 0x30, 0xf7, 0x25, 0x06, 0x39, + 0xac, 0x8c, 0xee, 0xc0, 0x22, 0x27, 0xdf, 0x68, 0x01, 0x80, 0x14, 0xe0, + 0x3b, 0x26, 0x94, 0x19, 0x80, 0xff, 0x5f, 0x1d, 0x8d, 0xe1, 0x57, 0x50, + 0x0e, 0xbe, 0x18, 0x1a, 0x1b, 0xc0, 0xfa, 0x7f, 0x71, 0x5a, 0x84, 0x9f, + 0xb5, 0x7f, 0xf2, 0x32, 0x18, 0x49, 0x09, 0x1f, 0x9e, 0x11, 0xc2, 0xf5, + 0xe9, 0xc0, 0x40, 0x87, 0x2f, 0xf6, 0x3b, 0x13, 0xc0, 0x7f, 0x79, 0x80, + 0x13, 0xa7, 0x64, 0x64, 0x63, 0x56, 0x1d, 0xa0, 0x02, 0x0c, 0x10, 0x9e, + 0x3f, 0xb0, 0x50, 0x1b, 0xbd, 0x27, 0x01, 0x47, 0x16, 0x70, 0x7d, 0x91, + 0xb7, 0xeb, 0xc8, 0x63, 0x4b, 0x21, 0xfc, 0x85, 0xb3, 0x6c, 0x28, 0x08, + 0xde, 0x4c, 0x04, 0x29, 0xc3, 0x36, 0x35, 0xdc, 0x3a, 0x50, 0x28, 0x4d, + 0xe1, 0xa5, 0xf5, 0x6c, 0xaf, 0xd5, 0x96, 0x7e, 0x36, 0xfa, 0x81, 0x0d, + 0x03, 0x03, 0x53, 0x9f, 0xf1, 0x22, 0x2e, 0x3d, 0x82, 0x76, 0x38, 0x66, + 0xae, 0xfb, 0xbb, 0x3f, 0x1d, 0x7c, 0x9f, 0x86, 0x24, 0x30, 0x99, 0xf9, + 0x2d, 0x5c, 0xcc, 0x77, 0x7b, 0xea, 0x48, 0x40, 0xd4, 0xec, 0x3f, 0xb5, + 0xf1, 0x90, 0x13, 0xf5, 0x21, 0x1b, 0x0b, 0x11, 0xb6, 0xb8, 0x53, 0xd6, + 0x8d, 0x6b, 0xaf, 0xe8, 0x85, 0x13, 0x1d, 0x04, 0xb4, 0xf6, 0x45, 0x9c, + 0xb2, 0x16, 0x42, 0x31, 0x68, 0x48, 0xdd, 0x79, 0x50, 0x28, 0x5e, 0xd8, + 0x0c, 0x17, 0x8f, 0x39, 0xc5, 0x99, 0x7a, 0xa0, 0x2a, 0x18, 0x03, 0x6e, + 0xdb, 0xf6, 0xeb, 0xad, 0xf8, 0xd6, 0x0a, 0x1d, 0xf4, 0xaf, 0xd9, 0x6c, + 0xfd, 0xb5, 0xf3, 0xc0, 0x0a, 0x91, 0xbe, 0x41, 0x69, 0x64, 0xf1, 0x4a, + 0x0b, 0xbd, 0xda, 0x1d, 0xd4, 0x7e, 0x58, 0x55, 0xf0, 0xa0, 0x2c, 0x03, + 0x14, 0x0e, 0x4e, 0x0b, 0xc0, 0x72, 0x82, 0xfb, 0x08, 0x34, 0x54, 0xf5, + 0xfd, 0x40, 0x02, 0xc6, 0x2c, 0x95, 0xfe, 0xdc, 0xc3, 0xd7, 0x12, 0x19, + 0x33, 0x13, 0x06, 0x67, 0x7f, 0xcf, 0xb8, 0x60, 0x3a, 0x47, 0xe4, 0xd5, + 0xbb, 0xa3, 0x72, 0x05, 0x8c, 0x37, 0x00, 0xdc, 0x86, 0xcf, 0x9b, 0x36, + 0x03, 0x82, 0x5f, 0xda, 0x3f, 0x1a, 0x7a, 0xb6, 0x7f, 0x4d, 0xf6, 0x52, + 0x05, 0x32, 0x03, 0x31, 0x7d, 0x2b, 0xec, 0xa1, 0x20, 0x72, 0xe8, 0x94, + 0x4c, 0x74, 0x12, 0xd3, 0xd9, 0x17, 0xcf, 0x00, 0x4f, 0xff, 0x18, 0x8c, + 0x2f, 0xf0, 0xe0, 0x23, 0x74, 0x40, 0xa1, 0x7b, 0x60, 0x30, 0x5e, 0x3c, + 0xe7, 0x16, 0x65, 0xd1, 0x92, 0x91, 0x8e, 0x2c, 0x9d, 0x49, 0xb7, 0xea, + 0x8b, 0x21, 0x64, 0x23, 0x16, 0x84, 0x8d, 0xd7, 0x19, 0x00, 0x3a, 0xdc, + 0xa1, 0xbd, 0x2a, 0x5f, 0x56, 0x33, 0x5d, 0x90, 0x2a, 0x18, 0x03, 0x6f, + 0xb6, 0xfd, 0xba, 0xef, 0x98, 0x80, 0x4c, 0x03, 0xac, 0x23, 0x70, 0xff, + 0xdc, 0x0d, 0x8a, 0x8e, 0x9e, 0xd9, 0xb9, 0xfe, 0x2f, 0xe8, 0xc1, 0x80, + 0x50, 0x96, 0x7f, 0xdb, 0xb3, 0xec, 0x68, 0x1a, 0xbb, 0xc0, 0x36, 0x49, + 0x2f, 0xa3, 0x67, 0xf8, 0x07, 0xf7, 0xce, 0x00, 0x4d, 0x9f, 0x96, 0x5f, + 0x0b, 0x27, 0xfb, 0xc6, 0x06, 0x77, 0xc9, 0x2d, 0x41, 0x3f, 0xf7, 0x15, + 0x95, 0x9e, 0xd3, 0x97, 0xf7, 0xe6, 0x9c, 0x17, 0xe4, 0xd7, 0xf5, 0xa0, + 0x0a, 0xb0, 0xd2, 0x51, 0x2f, 0x0d, 0x30, 0x70, 0x7d, 0xaf, 0x10, 0x80, + 0xb9, 0x2b, 0xa7, 0x6e, 0xe4, 0xe6, 0x7b, 0x86, 0x18, 0x92, 0x8a, 0xef, + 0x9c, 0xb4, 0xfb, 0x92, 0x03, 0xa7, 0x2c, 0x34, 0xb4, 0xa4, 0xfc, 0xa0, + 0x0e, 0x2c, 0x3f, 0xfb, 0x7e, 0x28, 0x9f, 0x55, 0xfa, 0xa2, 0xc0, 0xa0, + 0xc4, 0xb6, 0x49, 0x8f, 0xff, 0x56, 0xcb, 0x69, 0x49, 0xa0, 0x50, 0x06, + 0xc8, 0xdd, 0xdc, 0xcf, 0x72, 0x12, 0x02, 0x90, 0x2a, 0x30, 0xc7, 0x58, + 0xb6, 0x01, 0xf5, 0x84, 0x06, 0x2e, 0x5a, 0x72, 0x31, 0x87, 0x89, 0xfd, + 0x96, 0x02, 0xf9, 0xef, 0xdd, 0x19, 0xcc, 0x7f, 0x2b, 0xf7, 0x01, 0x84, + 0x24, 0x86, 0xbf, 0x3b, 0x63, 0x7d, 0xe1, 0x48, 0x41, 0x85, 0x12, 0xdd, + 0x09, 0xfc, 0x63, 0xba, 0x83, 0xae, 0x78, 0x0e, 0xf8, 0x18, 0x0c, 0xc5, + 0x63, 0x54, 0x1d, 0xae, 0x68, 0x0e, 0x91, 0x8a, 0x2b, 0xf3, 0x4f, 0xe2, + 0x80, 0x82, 0xee, 0xd4, 0xa7, 0xf4, 0x7e, 0x3d, 0x5b, 0x50, 0xd7, 0xf4, + 0xd2, 0x1b, 0x86, 0x93, 0x7e, 0xcf, 0xd9, 0x41, 0x47, 0x93, 0xe4, 0x00, + 0xab, 0x23, 0xec, 0x94, 0xed, 0xf9, 0xdb, 0x2d, 0x62, 0xef, 0x0c, 0x03, + 0x04, 0x70, 0x92, 0xf9, 0xe9, 0x02, 0x37, 0xa4, 0xc9, 0x0c, 0x41, 0x7c, + 0xbf, 0xfa, 0x52, 0xe7, 0x63, 0x8d, 0x1e, 0x7d, 0xc6, 0xe6, 0x63, 0x35, + 0x77, 0xec, 0x0b, 0x26, 0x24, 0xa4, 0x8c, 0x3f, 0xf3, 0x9a, 0xf3, 0xdc, + 0x0a, 0x62, 0xb0, 0x17, 0x4b, 0x39, 0x99, 0x77, 0xcf, 0x88, 0x43, 0x4b, + 0x2d, 0x25, 0xa7, 0x37, 0xc1, 0x1b, 0x0e, 0x36, 0xfa, 0x2e, 0x04, 0x2f, + 0xa2, 0x6b, 0xf3, 0x9c, 0x9c, 0x66, 0x0f, 0xa4, 0x6f, 0xe1, 0xd9, 0x73, + 0xab, 0x7c, 0x28, 0x0e, 0xf0, 0x01, 0xb0, 0x03, 0xac, 0x18, 0x37, 0xbf, + 0xd9, 0x0e, 0xcc, 0x03, 0x9e, 0x35, 0x39, 0x8c, 0x53, 0xeb, 0xef, 0x77, + 0xc4, 0x40, 0x16, 0xf4, 0xa4, 0x06, 0xc1, 0x88, 0x5a, 0x8b, 0x2d, 0x28, + 0xc3, 0xb6, 0x52, 0x78, 0x70, 0xdb, 0xec, 0x25, 0x93, 0x03, 0x43, 0x18, + 0x6a, 0x3a, 0x1f, 0x6f, 0x9f, 0x1f, 0xbd, 0x90, 0x09, 0x80, 0x70, 0x37, + 0x9e, 0x5f, 0xe9, 0xa8, 0xb3, 0xc4, 0x5e, 0xda, 0x58, 0xf9, 0x30, 0x35, + 0x08, 0xc5, 0xf4, 0x27, 0x21, 0x27, 0x76, 0x57, 0x68, 0x00, 0xd8, 0x34, + 0x94, 0x4b, 0x19, 0xc6, 0x3b, 0x0d, 0x65, 0x3d, 0xe2, 0x3e, 0x1f, 0x74, + 0x5e, 0xda, 0x52, 0x94, 0x4e, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x20, 0x00, 0x00, 0x01, 0x11, 0x6b, 0xfb, 0x9b, 0xce, + 0xf5, 0xaf, 0x35, 0x4d, 0x4d, 0xd4, 0xf3, 0x3d, 0x64, 0x65, 0x16, 0xaf, + 0x18, 0xd2, 0x99, 0xdc, 0xa5, 0x29, 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, + 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, + 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, + 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, 0x91, 0x17, 0x2f, 0x99, 0x00, + 0x64, 0x01, 0x98, 0x0e, 0x80, 0x34, 0xc5, 0xb2, 0x09, 0xa9, 0xe5, 0xfc, + 0xff, 0x8c, 0xc7, 0x1c, 0x68, 0xbf, 0x7d, 0x36, 0x97, 0x49, 0x65, 0x60, + 0xdc, 0xdc, 0xea, 0x37, 0x6b, 0xc4, 0x1b, 0xd1, 0xd2, 0xe8, 0xe9, 0xee, + 0x7f, 0x4b, 0x3b, 0x87, 0x7f, 0x79, 0x60, 0x13, 0x00, 0x5f, 0xc0, 0xa1, + 0x31, 0x24, 0x24, 0xa0, 0x96, 0x51, 0x6e, 0xeb, 0x6f, 0xb2, 0x73, 0x25, + 0x8c, 0x10, 0x2e, 0xf6, 0xd4, 0xa5, 0x25, 0x64, 0xe6, 0xeb, 0x9b, 0xb5, + 0xfc, 0xd4, 0x30, 0x01, 0xf9, 0x08, 0x04, 0xc8, 0x4e, 0x01, 0xd1, 0x31, + 0x2e, 0x93, 0xf9, 0x7d, 0xce, 0x38, 0xe1, 0xcb, 0xbd, 0x38, 0x14, 0x01, + 0x3a, 0x49, 0x80, 0x31, 0x40, 0x15, 0x2d, 0xcb, 0xef, 0xdc, 0x7f, 0x7d, + 0xf1, 0x15, 0x57, 0xa3, 0xa5, 0xae, 0x26, 0x23, 0x0d, 0x51, 0x25, 0x5f, + 0xf3, 0xf8, 0x7d, 0x2f, 0xc2, 0xaf, 0xc0, 0x80, 0x6a, 0x00, 0xff, 0x00, + 0xef, 0xf2, 0x50, 0x66, 0x43, 0xa0, 0x60, 0xde, 0x49, 0x18, 0xee, 0xc3, + 0xb2, 0xdc, 0x4e, 0xf7, 0x2c, 0x86, 0x00, 0xcc, 0x0c, 0x86, 0x21, 0x08, + 0x26, 0x64, 0x27, 0x2f, 0x73, 0xf1, 0x5c, 0xed, 0xd1, 0x87, 0xb7, 0xe7, + 0x9d, 0xef, 0xa9, 0x52, 0xd0, 0x02, 0xc2, 0x99, 0x19, 0x89, 0x07, 0x2f, + 0xc8, 0x6f, 0xbf, 0x9c, 0x80, 0x64, 0x00, 0xfc, 0xa0, 0x13, 0x06, 0x06, + 0xb9, 0x49, 0x48, 0x69, 0x7d, 0xc6, 0xb9, 0xea, 0x56, 0xe1, 0xee, 0x17, + 0x7d, 0x01, 0x29, 0xe5, 0xa7, 0xa5, 0x3d, 0xfa, 0x7f, 0xff, 0x8f, 0xfe, + 0xba, 0x96, 0x18, 0xa4, 0x23, 0xa5, 0x3b, 0x2d, 0x27, 0x87, 0x54, 0xf7, + 0x2e, 0x00, 0x06, 0x80, 0x57, 0x00, 0xc0, 0x0a, 0x94, 0x1a, 0x9d, 0xfe, + 0x40, 0xd5, 0x63, 0xd6, 0x8f, 0x88, 0xb7, 0x7e, 0x96, 0x5f, 0x64, 0xef, + 0xb9, 0xdd, 0x55, 0x1d, 0x7e, 0x18, 0x01, 0xf8, 0x06, 0x20, 0x06, 0xfc, + 0xac, 0x8d, 0xd0, 0x84, 0x63, 0xb8, 0x13, 0xe6, 0xa8, 0x51, 0xc1, 0xf7, + 0x7c, 0x06, 0x05, 0x86, 0x06, 0x06, 0xa0, 0xb2, 0xc6, 0x81, 0x64, 0xa1, + 0x3b, 0x23, 0x65, 0xb7, 0x75, 0xbf, 0xd7, 0x6a, 0x96, 0xa8, 0x29, 0x08, + 0x40, 0xcc, 0xad, 0x4b, 0xbf, 0x7b, 0xf9, 0x28, 0x0c, 0x40, 0x1f, 0x80, + 0x80, 0x04, 0xf9, 0x18, 0x34, 0x98, 0x4d, 0x08, 0xfd, 0xbe, 0xfb, 0x75, + 0x9f, 0x76, 0x4a, 0x00, 0x7e, 0x50, 0x0c, 0x32, 0x70, 0x0e, 0x88, 0x59, + 0x0e, 0xc0, 0x5c, 0xbe, 0xe9, 0x1e, 0xb3, 0x02, 0xaf, 0xa0, 0xd2, 0xe9, + 0xc0, 0x50, 0xad, 0x86, 0x2d, 0x87, 0x3f, 0x6a, 0x06, 0xfb, 0xe7, 0x16, + 0x4d, 0x49, 0x0c, 0xb1, 0xa4, 0xd4, 0xf4, 0x9c, 0xfc, 0xd6, 0x14, 0x65, + 0xf3, 0xa2, 0x10, 0x03, 0xf0, 0x28, 0x42, 0x01, 0xd7, 0x21, 0x21, 0x01, + 0xab, 0xc9, 0xc4, 0xb0, 0x8d, 0xdd, 0x2e, 0xb3, 0xcf, 0xc4, 0x4b, 0xea, + 0xd4, 0xb1, 0x48, 0x62, 0x0b, 0xdb, 0x73, 0xb2, 0xf6, 0xca, 0x3a, 0xad, + 0xfe, 0xbf, 0x2e, 0x02, 0x00, 0x13, 0x80, 0x1c, 0x10, 0xc9, 0x7b, 0x0d, + 0x47, 0x74, 0x7d, 0xcf, 0x19, 0xf9, 0x1d, 0x55, 0x60, 0x19, 0x81, 0x52, + 0x60, 0x0c, 0x43, 0x78, 0x69, 0x58, 0xb4, 0x27, 0xb3, 0xee, 0xcc, 0xac, + 0xcf, 0x9b, 0x88, 0xbd, 0x65, 0x28, 0x25, 0x20, 0x69, 0x2c, 0xd6, 0x60, + 0xe9, 0xba, 0x9a, 0xf8, 0x80, 0x20, 0x02, 0xa0, 0x07, 0x80, 0x54, 0xa0, + 0xd6, 0x71, 0x8e, 0xf8, 0x57, 0xdf, 0x11, 0x6f, 0x2e, 0x02, 0x10, 0x07, + 0x7c, 0x86, 0x90, 0x14, 0x20, 0x94, 0x76, 0xe9, 0xeb, 0x4a, 0x76, 0x58, + 0xce, 0x63, 0x3b, 0xa8, 0xd3, 0x6f, 0xa1, 0xd2, 0xae, 0x18, 0x8e, 0x9c, + 0x9f, 0x97, 0xb3, 0x49, 0xfb, 0x35, 0xfc, 0xcc, 0x34, 0x02, 0xf0, 0x28, + 0x02, 0x74, 0x20, 0x00, 0xfc, 0x98, 0x92, 0x5a, 0x00, 0xf9, 0x5f, 0x65, + 0x30, 0xb1, 0x66, 0x5e, 0x98, 0x03, 0x50, 0x0d, 0x4b, 0x00, 0xd5, 0x3b, + 0xa7, 0x64, 0x64, 0x12, 0x11, 0x8c, 0x73, 0xd6, 0x2e, 0xf4, 0xd4, 0xa9, + 0x04, 0xc4, 0x23, 0xef, 0xb1, 0xce, 0xc2, 0x9c, 0xf6, 0x8f, 0xff, 0xbf, + 0x36, 0x01, 0xa0, 0x08, 0x52, 0x01, 0xa1, 0x7d, 0x28, 0xd9, 0x3b, 0xe6, + 0xfb, 0x90, 0x4f, 0xd7, 0x97, 0x00, 0x13, 0x81, 0x42, 0x11, 0x65, 0x93, + 0x40, 0xa1, 0x31, 0x28, 0x2c, 0xbd, 0xf0, 0x4f, 0xc4, 0xac, 0xeb, 0x7d, + 0xb0, 0xbd, 0x95, 0x7d, 0x32, 0x92, 0x49, 0x31, 0x09, 0xef, 0xf1, 0xf9, + 0xba, 0xcf, 0xc2, 0xe1, 0xfd, 0xf8, 0x00, 0x0c, 0x00, 0x1f, 0x80, 0x1e, + 0x16, 0x4d, 0x48, 0x6e, 0xe5, 0x81, 0x96, 0xff, 0xe6, 0x77, 0xdf, 0x91, + 0xdf, 0xda, 0x80, 0x04, 0xe0, 0x85, 0xf3, 0x89, 0xb8, 0x98, 0x56, 0xd9, + 0x29, 0x2c, 0x62, 0xd6, 0x94, 0xe6, 0x7f, 0xcd, 0x6e, 0xbc, 0xa5, 0x5e, + 0xd6, 0x93, 0x01, 0xb1, 0x4f, 0xd2, 0xcb, 0x4e, 0x6e, 0x22, 0x06, 0xfb, + 0xf9, 0xe0, 0x08, 0x40, 0x1d, 0xf2, 0x1a, 0x40, 0x50, 0x82, 0x51, 0xdb, + 0xa7, 0xad, 0x29, 0xd9, 0x63, 0x39, 0x8c, 0xee, 0xa3, 0x4d, 0xb3, 0x06, + 0x80, 0x5e, 0x05, 0x00, 0x4e, 0x84, 0x00, 0x1f, 0x93, 0x12, 0x4b, 0x40, + 0x1f, 0x2b, 0xec, 0xa6, 0x16, 0x2c, 0xcb, 0xe9, 0x94, 0xac, 0x02, 0xc2, + 0xb6, 0xcd, 0x98, 0x61, 0xdf, 0x7e, 0xe1, 0x72, 0x1b, 0xeb, 0xff, 0x1a, + 0x58, 0x6a, 0x40, 0xa9, 0x68, 0xc7, 0x19, 0x83, 0xef, 0x97, 0x00, 0xc5, + 0x28, 0xc3, 0x32, 0x77, 0xfb, 0xbf, 0x51, 0xea, 0x03, 0x77, 0xdb, 0x8a, + 0x49, 0x29, 0x39, 0x2c, 0xec, 0x65, 0xcd, 0x26, 0x86, 0x80, 0xdd, 0x2e, + 0xcc, 0xa6, 0xbb, 0x3a, 0x17, 0x87, 0x67, 0xb3, 0x7a, 0xbb, 0x92, 0x1a, + 0x01, 0x7b, 0x13, 0x00, 0x2c, 0x02, 0x85, 0x01, 0x82, 0x99, 0xbb, 0xad, + 0x3d, 0xcf, 0x71, 0x2b, 0x0e, 0xaa, 0x01, 0x00, 0x05, 0xe0, 0x16, 0x06, + 0x24, 0x98, 0x1a, 0x8e, 0x59, 0x5f, 0x76, 0xd8, 0xfc, 0xe7, 0xe1, 0x1e, + 0xfa, 0x15, 0x2e, 0x20, 0x1b, 0x15, 0xf3, 0x31, 0xae, 0x2d, 0xc7, 0xb8, + 0xfb, 0x47, 0xf7, 0xe5, 0xb8, 0x15, 0x0c, 0x0c, 0xe9, 0x00, 0xb0, 0x33, + 0xa3, 0xf5, 0x64, 0xa5, 0x2e, 0x9d, 0xba, 0x3e, 0xec, 0xcd, 0xb7, 0x59, + 0xeb, 0xbc, 0xf0, 0x06, 0x80, 0x17, 0x86, 0x00, 0x80, 0x99, 0xd0, 0x4a, + 0x4a, 0x53, 0xff, 0xee, 0xad, 0x8e, 0x56, 0x73, 0xd8, 0x83, 0x7d, 0x2a, + 0x94, 0x24, 0x31, 0x09, 0xcd, 0xd6, 0x1e, 0x3a, 0x6f, 0xf5, 0xf9, 0x20, + 0x0c, 0x80, 0x4e, 0x00, 0x78, 0x43, 0xe5, 0xa7, 0x74, 0x8d, 0xdf, 0x71, + 0xeb, 0xfb, 0xba, 0xc5, 0xeb, 0x30, 0x0c, 0x40, 0x2f, 0x49, 0x34, 0xb7, + 0x2b, 0x01, 0x8c, 0x37, 0x25, 0x47, 0xa7, 0xa0, 0x91, 0xd2, 0x23, 0x6c, + 0xd8, 0xeb, 0xdb, 0x52, 0x40, 0x61, 0x03, 0x46, 0xb2, 0xd9, 0xa1, 0xfb, + 0x35, 0xfc, 0xbc, 0x07, 0x60, 0x0e, 0xca, 0x21, 0xe2, 0x81, 0x08, 0x1b, + 0x74, 0xa1, 0x6e, 0x95, 0xed, 0xd4, 0x76, 0x34, 0xe3, 0x6b, 0xc0, 0xa0, + 0x03, 0xdc, 0x4c, 0x02, 0x9c, 0x98, 0x90, 0xc2, 0x5f, 0xee, 0xb7, 0x24, + 0x8d, 0xfd, 0xc6, 0x9a, 0x72, 0xaf, 0xa4, 0xd2, 0xdc, 0x02, 0xc2, 0x86, + 0x0c, 0x65, 0x3b, 0x9f, 0xe9, 0x1b, 0xef, 0xe7, 0x00, 0x30, 0x00, 0x72, + 0x1a, 0x4c, 0x2d, 0x3c, 0xb0, 0x94, 0x24, 0xcc, 0xa1, 0x8a, 0x50, 0xc1, + 0x46, 0x35, 0xd6, 0xe0, 0x3b, 0x02, 0x85, 0x16, 0x4b, 0x21, 0x13, 0x3e, + 0xe7, 0xe2, 0x5f, 0x7c, 0x8e, 0xee, 0xad, 0x89, 0xcf, 0xef, 0x63, 0x4b, + 0x20, 0x30, 0x8d, 0xff, 0xd9, 0x9c, 0xf6, 0x36, 0xa7, 0xf7, 0xf3, 0xe0, + 0x0c, 0x00, 0x2f, 0x00, 0x3c, 0x25, 0xe2, 0xf2, 0x4b, 0xc9, 0xe3, 0x45, + 0x8d, 0x37, 0xac, 0x79, 0x12, 0xe8, 0xa0, 0x03, 0x5c, 0x51, 0x34, 0xa2, + 0xd0, 0x4c, 0x29, 0x08, 0x47, 0xfc, 0x95, 0x9d, 0x7f, 0xa1, 0x9f, 0xfe, + 0x7b, 0x1f, 0x87, 0x5e, 0xc2, 0x90, 0x06, 0x10, 0xff, 0xb1, 0x24, 0xf8, + 0x7f, 0x7f, 0x36, 0x01, 0xd8, 0x03, 0xb0, 0x32, 0x5e, 0xe5, 0x15, 0xb6, + 0xc3, 0x06, 0xb9, 0xe5, 0xa3, 0x77, 0x19, 0xc7, 0x67, 0x77, 0x1f, 0x6a, + 0x00, 0xcc, 0x01, 0xf8, 0x01, 0xd9, 0x45, 0x15, 0x90, 0x33, 0x66, 0x4f, + 0x01, 0x5b, 0x32, 0x85, 0x2e, 0xf7, 0xf4, 0xab, 0x13, 0x0a, 0xc3, 0x1b, + 0x12, 0x0e, 0x90, 0xdf, 0x7f, 0x36, 0x01, 0x88, 0x03, 0xb0, 0x0a, 0xc0, + 0xa6, 0x26, 0x62, 0xb1, 0x4e, 0x49, 0x1c, 0xdb, 0xbe, 0xc8, 0xa0, 0x00, + 0x84, 0x10, 0x82, 0x80, 0x2c, 0xc5, 0x6d, 0xc6, 0x12, 0x9c, 0xc1, 0x8c, + 0xdf, 0x89, 0xf7, 0xd2, 0x69, 0x30, 0x0b, 0x0a, 0xdb, 0x66, 0x3b, 0x8d, + 0x34, 0xf8, 0x1b, 0xee, 0x52, 0x94, 0xbc, 0xfb, 0x5b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0x80, 0x00, 0x00, 0x01, 0x12, + 0x73, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4b, 0x31, 0x5b, 0x25, 0x3b, 0x7f, 0xf3, 0x8e, 0xce, + 0x45, 0xd7, 0x9c, 0x0d, 0x01, 0x32, 0x03, 0x08, 0x60, 0x16, 0x01, 0x90, + 0x97, 0xd9, 0xfa, 0x19, 0x0b, 0xdb, 0x9f, 0xbf, 0xe6, 0x81, 0x2b, 0x5e, + 0x9d, 0xbb, 0x07, 0xd2, 0xd7, 0xeb, 0x29, 0x63, 0x0d, 0x0c, 0x49, 0x30, + 0xbe, 0x52, 0x73, 0x74, 0x76, 0xf6, 0xcb, 0x33, 0xe7, 0x72, 0x94, 0xa4, + 0xa7, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x5e, 0x5c, 0x06, 0x00, 0x3a, 0x28, + 0x9a, 0x51, 0x34, 0xb4, 0xa5, 0x38, 0x0c, 0x21, 0xdb, 0x6c, 0xb1, 0xcb, + 0xfc, 0x2d, 0x57, 0xa8, 0xb4, 0xdf, 0x7f, 0xf0, 0xea, 0x6e, 0x52, 0x94, + 0xb3, 0xaa, 0xe5, 0x29, 0x79, 0x70, 0x1d, 0x00, 0xe8, 0xa2, 0x69, 0x44, + 0xd2, 0xd2, 0x94, 0xe0, 0x30, 0x87, 0x6d, 0xb2, 0xc7, 0x2f, 0xf0, 0xbb, + 0x4f, 0xf2, 0x33, 0xfa, 0x4f, 0xbf, 0x51, 0x4a, 0x52, 0xce, 0xab, 0x94, + 0xa9, 0x4a, 0x3a, 0x0f, 0xcc, 0xa7, 0x0e, 0xf5, 0xd1, 0x17, 0x29, 0x4a, + 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x94, 0xa4, 0x45, 0xca, 0x52, 0x91, + 0x17, 0x29, 0x4a, 0x44, 0x5c, 0xa5, 0x29, 0x11, 0x72, 0x97, 0x9a, 0x00, + 0x3e, 0x40, 0x01, 0xe7, 0x21, 0x8d, 0xe4, 0x9c, 0x8d, 0x98, 0xfe, 0x69, + 0xbc, 0xc1, 0x16, 0xe0, 0x1b, 0x60, 0x0b, 0x30, 0x69, 0x2d, 0x0e, 0x34, + 0x61, 0x28, 0x0f, 0x89, 0xb6, 0x1b, 0x89, 0xec, 0x1f, 0x4b, 0x5f, 0x5e, + 0x94, 0xa5, 0x8d, 0x57, 0x29, 0x4b, 0x20, 0x61, 0x5c, 0x34, 0xb4, 0x23, + 0x01, 0xff, 0xb4, 0xe2, 0xf5, 0xd2, 0xf2, 0x21, 0x80, 0x26, 0x28, 0x9b, + 0xcb, 0x18, 0x1b, 0xd8, 0x33, 0x7f, 0xd2, 0x4a, 0xdb, 0x36, 0x1c, 0x05, + 0xdd, 0x66, 0x2a, 0xb0, 0x98, 0x03, 0xa0, 0xc2, 0x19, 0xc4, 0xdf, 0x82, + 0x50, 0x53, 0x72, 0x8d, 0x49, 0xc7, 0x27, 0x8e, 0xba, 0x09, 0x79, 0x62, + 0x85, 0x7a, 0x1a, 0xfd, 0x55, 0x2e, 0x50, 0x15, 0x26, 0x62, 0x10, 0x6a, + 0x4a, 0x1a, 0x37, 0xa3, 0xb7, 0xec, 0xcb, 0x14, 0xbf, 0x64, 0x26, 0x81, + 0x44, 0x13, 0x0a, 0x42, 0x49, 0x7d, 0xcb, 0xcf, 0x91, 0xd0, 0xdd, 0x9d, + 0x9f, 0x84, 0xf6, 0x39, 0x47, 0xd9, 0x8d, 0xca, 0x16, 0x74, 0xda, 0xfd, + 0x05, 0x29, 0x4b, 0x2a, 0xae, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x20, 0x00, + 0x00, 0x01, 0x13, 0x73, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xad, 0xfc, 0xc8, 0x0e, 0x90, 0x34, 0x9a, + 0x50, 0xae, 0xe1, 0x4b, 0xb8, 0x5c, 0xaf, 0xc8, 0x7c, 0xb4, 0x84, 0x20, + 0x6b, 0xbf, 0x22, 0x0f, 0xb7, 0x80, 0xeb, 0xa0, 0xa1, 0xa9, 0xff, 0x6e, + 0x33, 0xe4, 0x09, 0xbd, 0x87, 0xbd, 0x13, 0xd3, 0xc4, 0x35, 0x8d, 0x7f, + 0xf3, 0x07, 0x41, 0x35, 0x25, 0xa7, 0xec, 0xdb, 0x1f, 0xdc, 0x89, 0xee, + 0xea, 0x32, 0x0b, 0x3b, 0x36, 0x46, 0x74, 0xf3, 0x78, 0x1d, 0xb4, 0xe4, + 0xec, 0x4c, 0xf9, 0xd6, 0x94, 0x93, 0xb5, 0x90, 0x84, 0x5e, 0xd8, 0xb4, + 0x7e, 0x30, 0x6e, 0xcd, 0xc5, 0xeb, 0xa1, 0x98, 0x89, 0x45, 0xb8, 0xcc, + 0x4a, 0xfd, 0x38, 0x60, 0x12, 0x1e, 0x06, 0xaa, 0xba, 0x10, 0x8c, 0x8f, + 0xf0, 0xd4, 0x7c, 0x79, 0xaf, 0x76, 0xc2, 0x1f, 0x3f, 0x75, 0xaf, 0x90, + 0x2e, 0x08, 0xd4, 0x21, 0x05, 0xa1, 0xc6, 0xee, 0x71, 0x06, 0xca, 0xaa, + 0xdf, 0x6a, 0x0a, 0xc0, 0x60, 0x97, 0xb0, 0xd6, 0xfc, 0x97, 0xb9, 0x98, + 0xc0, 0x37, 0x7a, 0x42, 0xc6, 0x74, 0xf1, 0x9b, 0xec, 0x66, 0xf7, 0x88, + 0x28, 0xb0, 0xc2, 0x62, 0x72, 0x50, 0x97, 0x47, 0x40, 0x4a, 0x39, 0xee, + 0xc7, 0x01, 0xdb, 0xe8, 0x1e, 0xc3, 0x1f, 0xea, 0xaf, 0xfe, 0x7e, 0x8c, + 0xc4, 0x2f, 0xf8, 0x1f, 0xe4, 0xec, 0x38, 0x3e, 0xd6, 0x33, 0x24, 0x98, + 0x37, 0x6d, 0xb8, 0xd6, 0x53, 0xac, 0x2f, 0xd4, 0x27, 0xa1, 0x21, 0x85, + 0x23, 0x76, 0x18, 0x7b, 0x8b, 0x97, 0x06, 0xa3, 0x24, 0xac, 0x07, 0x90, + 0x4f, 0xe3, 0xda, 0xec, 0x59, 0x3e, 0xce, 0xf7, 0x96, 0x8c, 0x19, 0xc6, + 0x6c, 0x35, 0x26, 0x89, 0x1d, 0xae, 0x70, 0x68, 0x66, 0x03, 0x38, 0xa4, + 0x36, 0x52, 0xc8, 0xff, 0xda, 0xb7, 0x42, 0x03, 0x77, 0x1a, 0xeb, 0x3c, + 0x70, 0x59, 0xf7, 0x3f, 0x06, 0xa3, 0x13, 0x7f, 0x77, 0xdd, 0xdf, 0xb0, + 0xf9, 0xac, 0x9c, 0x4f, 0x8b, 0xf4, 0x19, 0x25, 0x6f, 0xf0, 0xc7, 0xc6, + 0x0f, 0x12, 0x7d, 0xe6, 0x83, 0x0a, 0xfc, 0x85, 0xc0, 0x7a, 0x95, 0x88, + 0xcb, 0x70, 0xb5, 0x5e, 0xcd, 0x39, 0x08, 0x3b, 0x8d, 0xbc, 0x40, 0x0d, + 0xf1, 0x40, 0x63, 0x65, 0xed, 0x82, 0x43, 0xcf, 0xa3, 0x8a, 0x01, 0xf4, + 0xef, 0xda, 0xa5, 0x25, 0x33, 0x21, 0x01, 0xff, 0xac, 0x81, 0x02, 0x66, + 0x1a, 0x5e, 0xc3, 0xfc, 0x4b, 0xc5, 0x23, 0x84, 0x71, 0xa2, 0xee, 0x58, + 0x14, 0x4e, 0x42, 0x7f, 0xfd, 0x95, 0xd8, 0x4d, 0xcc, 0x65, 0x77, 0xcd, + 0x04, 0x3f, 0x90, 0xde, 0x73, 0x1e, 0xe2, 0xb5, 0x68, 0x62, 0x10, 0x4d, + 0x4f, 0x46, 0xcd, 0x9b, 0xac, 0xd1, 0x35, 0xa1, 0x85, 0xa1, 0x1d, 0x38, + 0xc4, 0xa3, 0xa0, 0x69, 0x15, 0xa7, 0x42, 0x50, 0x18, 0x84, 0x9a, 0x94, + 0xa8, 0x46, 0xba, 0x96, 0x46, 0x6b, 0x3a, 0xc3, 0xda, 0xde, 0x19, 0x08, + 0x0c, 0x40, 0x0c, 0x30, 0x6a, 0x4a, 0x43, 0xf2, 0xdb, 0x12, 0x5d, 0x3b, + 0x61, 0xef, 0xf6, 0x16, 0x4e, 0xbd, 0xd5, 0xe4, 0xc0, 0x62, 0x1a, 0x84, + 0xa0, 0x84, 0x52, 0x3b, 0xe2, 0x8a, 0x58, 0x0a, 0xf9, 0xf7, 0x12, 0x5f, + 0xb6, 0x16, 0xbb, 0x5d, 0x5f, 0xe3, 0x19, 0xbe, 0xe5, 0xe4, 0x1f, 0xd9, + 0x08, 0x23, 0xd1, 0x28, 0x46, 0x25, 0x6e, 0xaf, 0xc9, 0x2c, 0x66, 0x20, + 0xd9, 0x72, 0x72, 0x49, 0x99, 0x03, 0x46, 0x74, 0x7e, 0x3d, 0x7b, 0x88, + 0xbb, 0xc8, 0xc5, 0x7e, 0xdf, 0xfc, 0xa5, 0x71, 0x63, 0xfd, 0xd6, 0x16, + 0xab, 0x3b, 0xca, 0x5e, 0xc5, 0x3a, 0x12, 0x86, 0xe6, 0x30, 0xe2, 0x2c, + 0x03, 0x72, 0x32, 0x0a, 0x53, 0x7f, 0x9f, 0x91, 0x0f, 0x90, 0xb2, 0xf7, + 0x41, 0x48, 0xeb, 0xf8, 0xd1, 0x43, 0xae, 0x68, 0x6a, 0x4a, 0xe5, 0x8d, + 0x1a, 0xe7, 0x32, 0x8e, 0x85, 0xa8, 0x3e, 0x2f, 0xa2, 0x43, 0x46, 0x28, + 0x66, 0x65, 0xb3, 0xfb, 0x0a, 0x50, 0x8c, 0x18, 0x57, 0x66, 0xfd, 0x64, + 0x03, 0x42, 0xed, 0xc4, 0x34, 0xa0, 0xb2, 0xdf, 0x77, 0x16, 0x2a, 0x62, + 0x6a, 0x50, 0x92, 0xf3, 0x70, 0x8c, 0xda, 0x79, 0xd4, 0x17, 0xe2, 0xf9, + 0xa1, 0xa8, 0x42, 0x32, 0x3b, 0x33, 0xa0, 0xe7, 0x34, 0x8f, 0x10, 0xd0, + 0xcd, 0xd2, 0x52, 0x11, 0x9b, 0xfd, 0x9c, 0x73, 0xdb, 0x8b, 0x2f, 0x16, + 0x4d, 0x70, 0x3d, 0x94, 0x1d, 0xae, 0x20, 0x19, 0x42, 0x08, 0x63, 0x5d, + 0xf2, 0xed, 0x16, 0x4e, 0xf4, 0xd6, 0x1b, 0x0a, 0x26, 0x06, 0x0d, 0x4f, + 0xe8, 0x62, 0x12, 0x1f, 0x72, 0xfb, 0x8d, 0x4a, 0x94, 0xc2, 0xdd, 0xba, + 0xb3, 0x5f, 0x42, 0xbc, 0xd1, 0x34, 0x94, 0x8f, 0xc0, 0x52, 0x03, 0x7e, + 0xb6, 0x3c, 0xd3, 0x7f, 0xe7, 0xf1, 0x43, 0xef, 0x47, 0x8f, 0x5d, 0xe7, + 0xa0, 0x7a, 0xed, 0xbd, 0x7f, 0xe1, 0xcb, 0xe3, 0x03, 0x03, 0x10, 0x02, + 0x3f, 0x69, 0x0d, 0xc8, 0x42, 0x0a, 0x6c, 0xdf, 0xe7, 0xe1, 0xe7, 0x59, + 0x88, 0x69, 0x29, 0x29, 0xcd, 0xdf, 0x2b, 0x08, 0xd3, 0x90, 0xd2, 0x84, + 0x96, 0xfb, 0xb9, 0x19, 0xae, 0xcc, 0x61, 0xe6, 0xfb, 0x2b, 0xb8, 0x9b, + 0xb6, 0xc5, 0x6d, 0xb6, 0xec, 0xfc, 0xfb, 0x9b, 0xc3, 0x10, 0xe2, 0x90, + 0x8c, 0x34, 0x48, 0xea, 0xe2, 0x1a, 0x51, 0xcb, 0x1b, 0xbb, 0x9c, 0xd8, + 0x74, 0xc4, 0xc2, 0xf0, 0xc2, 0xd1, 0xd2, 0x33, 0xa5, 0x97, 0x52, 0xdb, + 0x45, 0x69, 0xa5, 0x25, 0x23, 0x13, 0x3f, 0x2d, 0x19, 0x09, 0xff, 0x12, + 0xb6, 0x3c, 0x5b, 0x9e, 0xa6, 0x1f, 0x7b, 0x17, 0x6b, 0x9c, 0x58, 0xcc, + 0xad, 0xc6, 0x80, 0xf4, 0xe0, 0xec, 0x41, 0xba, 0x37, 0x9b, 0xad, 0xaf, + 0xc0, 0x93, 0x00, 0xb0, 0x0c, 0x08, 0x69, 0x02, 0x7f, 0x23, 0x11, 0x49, + 0xd7, 0xad, 0x26, 0x24, 0x61, 0x68, 0xe3, 0x93, 0xb8, 0x57, 0xbc, 0xa8, + 0x6f, 0x46, 0x02, 0x85, 0x14, 0x82, 0xd1, 0xfb, 0xf4, 0xa7, 0x0b, 0x22, + 0xde, 0xef, 0x2a, 0xe9, 0x42, 0xc6, 0x8b, 0xfc, 0x81, 0x47, 0x32, 0xb0, + 0xf0, 0x00, 0x3b, 0x26, 0x20, 0x00, 0xe4, 0x34, 0x6f, 0x08, 0xc0, 0x5d, + 0x22, 0x3e, 0xdd, 0x82, 0xfb, 0x5e, 0xc4, 0x9a, 0x1b, 0xc9, 0xa5, 0xf4, + 0xf3, 0x49, 0xc2, 0xaf, 0x1a, 0x01, 0x98, 0x15, 0x28, 0x37, 0xb1, 0x4d, + 0xc6, 0xe4, 0x72, 0xd2, 0x6f, 0xfb, 0x33, 0x08, 0x22, 0xdf, 0x42, 0x90, + 0x63, 0x8c, 0xdf, 0x8e, 0x69, 0xfb, 0x94, 0xa5, 0x2e, 0x0b, 0x5b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x88, 0x00, 0x00, 0x01, 0x14, 0x83, 0xfb, 0xfd, + 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0x00, 0x00, 0x01, 0x15, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, 0x16, 0x83, + 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0x00, 0x00, 0x01, 0x17, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, + 0x18, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, 0x19, 0x83, 0xfb, 0xfd, 0x29, + 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, + 0x00, 0x01, 0x1a, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, 0x1b, 0x83, 0xfb, + 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0x00, 0x00, 0x01, 0x1c, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, 0x1d, + 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, 0x1e, 0x83, 0xfb, 0xfd, 0x29, 0x49, + 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, + 0x01, 0x1f, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, 0x20, 0x83, 0xfb, 0xfd, + 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0x00, 0x00, 0x01, 0x21, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, 0x22, 0x83, + 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, + 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, + 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, + 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, + 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, + 0x52, 0x22, 0x00, 0x00, 0x01, 0x23, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, + 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, + 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, + 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, + 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, + 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, + 0x24, 0x83, 0xfb, 0xfd, 0x29, 0x49, 0x4a, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, + 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, + 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, 0x22, 0xe5, 0x29, + 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, 0xb9, 0x4a, 0x52, + 0x22, 0xe5, 0x29, 0x48, 0x8b, 0x94, 0xa5, 0x22, 0x2e, 0x52, 0x94, 0x88, + 0xb9, 0x4a, 0x52, 0x22, 0x00, 0x00, 0x01, 0xb7, +}; diff --git a/server/suspend.h b/server/suspend.h new file mode 100644 index 0000000..f60e6ec --- /dev/null +++ b/server/suspend.h @@ -0,0 +1,41 @@ +/* + * $Id: suspend.h,v 1.1.1.1 2004/12/30 22:44:26 lordjaxom Exp $ + */ + +#ifndef VDR_STREAMDEV_SUSPEND_H +#define VDR_STREAMDEV_SUSPEND_H + +#include + +class cSuspendLive: public cPlayer, cThread { +private: + bool m_Active; + +protected: + virtual void Activate(bool On); + virtual void Action(void); + + void Stop(void); + +public: + cSuspendLive(void); + virtual ~cSuspendLive(); + + bool IsActive(void) const { return m_Active; } +}; + +class cSuspendCtl: public cControl { +private: + cSuspendLive *m_Suspend; + static bool m_Active; + +public: + cSuspendCtl(void); + virtual ~cSuspendCtl(); + virtual void Hide(void) {} + virtual eOSState ProcessKey(eKeys Key); + + static bool IsActive(void) { return m_Active; } +}; + +#endif // VDR_STREAMDEV_SUSPEND_H diff --git a/streamdev-client.c b/streamdev-client.c new file mode 100644 index 0000000..fe6a21f --- /dev/null +++ b/streamdev-client.c @@ -0,0 +1,59 @@ +/* + * 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.2 2005/04/24 16:19:44 lordjaxom Exp $ + */ + +#include "streamdev-client.h" +#include "client/device.h" +#include "client/setup.h" +//#include "client/menu.h" +#include "i18n.h" + +const char *cPluginStreamdevClient::DESCRIPTION = "VTP Streaming Client"; + +cPluginStreamdevClient::cPluginStreamdevClient(void) { +} + +cPluginStreamdevClient::~cPluginStreamdevClient() { +} + +const char *cPluginStreamdevClient::Description(void) { + return tr(DESCRIPTION); +} + +bool cPluginStreamdevClient::Start(void) { + i18n_name = Name(); + RegisterI18n(Phrases); + + cStreamdevDevice::Init(); + + return true; +} + +void cPluginStreamdevClient::Housekeeping(void) { + if (StreamdevClientSetup.StartClient && StreamdevClientSetup.SyncEPG) + ClientSocket.SynchronizeEPG(); +} + +const char *cPluginStreamdevClient::MainMenuEntry(void) { + return NULL; + //return StreamdevClientSetup.StartClient ? tr("Streaming Control") : NULL; +} + +cOsdObject *cPluginStreamdevClient::MainMenuAction(void) { + return NULL; + //return StreamdevClientSetup.StartClient ? new cStreamdevMenu : NULL; +} + +cMenuSetupPage *cPluginStreamdevClient::SetupMenu(void) { + return new cStreamdevClientMenuSetupPage; +} + +bool cPluginStreamdevClient::SetupParse(const char *Name, const char *Value) { + return StreamdevClientSetup.SetupParse(Name, Value); +} + +VDRPLUGINCREATOR(cPluginStreamdevClient); // Don't touch this! diff --git a/streamdev-client.h b/streamdev-client.h new file mode 100644 index 0000000..b01e0d7 --- /dev/null +++ b/streamdev-client.h @@ -0,0 +1,29 @@ +/* + * $Id: streamdev-client.h,v 1.1.1.1 2004/12/30 22:43:59 lordjaxom Exp $ + */ + +#ifndef VDR_STREAMDEVCLIENT_H +#define VDR_STREAMDEVCLIENT_H + +#include "common.h" + +#include + +class cPluginStreamdevClient : public cPlugin { +private: + static const char *DESCRIPTION; + +public: + cPluginStreamdevClient(void); + virtual ~cPluginStreamdevClient(); + virtual const char *Version(void) { return VERSION; } + virtual const char *Description(void); + virtual bool Start(void); + virtual void Housekeeping(void); + virtual const char *MainMenuEntry(void); + virtual cOsdObject *MainMenuAction(void); + virtual cMenuSetupPage *SetupMenu(void); + virtual bool SetupParse(const char *Name, const char *Value); +}; + +#endif // VDR_STREAMDEVCLIENT_H diff --git a/streamdev-server.c b/streamdev-server.c new file mode 100644 index 0000000..af5f104 --- /dev/null +++ b/streamdev-server.c @@ -0,0 +1,121 @@ +/* + * 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.5 2007/02/19 12:08:16 schmirl Exp $ + */ + +#include +#include "streamdev-server.h" +#include "server/setup.h" +#include "server/server.h" +#include "server/suspend.h" +#include "remux/extern.h" +#include "i18n.h" + +const char *cPluginStreamdevServer::DESCRIPTION = "VDR Streaming Server"; + +cPluginStreamdevServer::cPluginStreamdevServer(void) +{ +} + +cPluginStreamdevServer::~cPluginStreamdevServer() +{ +} + +const char *cPluginStreamdevServer::Description(void) +{ + return tr(DESCRIPTION); +} + +const char *cPluginStreamdevServer::CommandLineHelp(void) +{ + // return a string that describes all known command line options. + return " -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[] = { + { "remux", required_argument, NULL, 'r' }, + { NULL, 0, NULL, 0 } + }; + + int c; + while((c = getopt_long(argc, argv, "r:", long_options, NULL)) != -1) { + switch (c) { + case 'r': + g_ExternRemux = optarg; + break; + default: + return false; + } + } + return true; +} + +bool cPluginStreamdevServer::Start(void) +{ + i18n_name = Name(); + RegisterI18n(Phrases); + + if (!StreamdevHosts.Load(STREAMDEVHOSTSPATH, true, true)) { + esyslog("streamdev-server: error while loading %s", STREAMDEVHOSTSPATH); + fprintf(stderr, "streamdev-server: error while loading %s\n"); + 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; + } + + cStreamdevServer::Initialize(); + + return true; +} + +void cPluginStreamdevServer::Stop(void) +{ + cStreamdevServer::Destruct(); +} + +cString cPluginStreamdevServer::Active(void) +{ + if (cStreamdevServer::Active()) + { + static const char *Message = NULL; + if (!Message) Message = tr("Streaming active"); + return Message; + } + return NULL; +} + +const char *cPluginStreamdevServer::MainMenuEntry(void) +{ + if (StreamdevServerSetup.SuspendMode == smOffer && !cSuspendCtl::IsActive()) + return tr("Suspend Live TV"); + return NULL; +} + +cOsdObject *cPluginStreamdevServer::MainMenuAction(void) +{ + cControl::Launch(new cSuspendCtl); + return NULL; +} + +cMenuSetupPage *cPluginStreamdevServer::SetupMenu(void) +{ + return new cStreamdevServerMenuSetupPage; +} + +bool cPluginStreamdevServer::SetupParse(const char *Name, const char *Value) +{ + return StreamdevServerSetup.SetupParse(Name, Value); +} + +VDRPLUGINCREATOR(cPluginStreamdevServer); // Don't touch this! diff --git a/streamdev-server.h b/streamdev-server.h new file mode 100644 index 0000000..8149e4b --- /dev/null +++ b/streamdev-server.h @@ -0,0 +1,33 @@ +/* + * $Id: streamdev-server.h,v 1.4 2007/02/19 12:08:16 schmirl Exp $ + */ + +#ifndef VDR_STREAMDEVSERVER_H +#define VDR_STREAMDEVSERVER_H + +#include "common.h" + +#include + +class cPluginStreamdevServer : public cPlugin { +private: + static const char *DESCRIPTION; + +public: + cPluginStreamdevServer(void); + virtual ~cPluginStreamdevServer(); + + virtual const char *Version(void) { return VERSION; } + virtual const char *Description(void); + virtual const char *CommandLineHelp(void); + virtual bool ProcessArgs(int argc, char *argv[]); + virtual bool Start(void); + virtual void Stop(void); + virtual cString Active(void); + virtual const char *MainMenuEntry(void); + virtual cOsdObject *MainMenuAction(void); + virtual cMenuSetupPage *SetupMenu(void); + virtual bool SetupParse(const char *Name, const char *Value); +}; + +#endif // VDR_STREAMDEVSERVER_H diff --git a/streamdevhosts.conf.example b/streamdevhosts.conf.example new file mode 100644 index 0000000..6c13598 --- /dev/null +++ b/streamdevhosts.conf.example @@ -0,0 +1,13 @@ +# +# 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 +#0.0.0.0/0 # any host on any net (USE THIS WITH CARE!) diff --git a/tools/select.c b/tools/select.c new file mode 100644 index 0000000..0ab5f9b --- /dev/null +++ b/tools/select.c @@ -0,0 +1,49 @@ +#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; + int ms; + + tv.tv_usec = (TimeoutMs % 1000) * 1000; + tv.tv_sec = TimeoutMs / 1000; + + if (TimeoutMs == 0) + return ::select(m_MaxFiled + 1, &m_Rfds, &m_Wfds, NULL, &tv); + + cTimeMs starttime; + ms = TimeoutMs; + while (ms > 0 && (res = ::select(m_MaxFiled + 1, &m_Rfds, &m_Wfds, NULL, + &tv)) == -1 && errno == EINTR) { + ms = TimeoutMs - starttime.Elapsed(); + tv.tv_usec = (ms % 1000) * 1000; + tv.tv_sec = ms / 1000; + } + if (ms <= 0) { + errno = ETIMEDOUT; + return -1; + } + return res; +} + +int cTBSelect::Select(void) { + ssize_t res; + while ((res = ::select(m_MaxFiled + 1, &m_Rfds, &m_Wfds, NULL, NULL)) == -1 + && errno == EINTR) + ; + return res; +} diff --git a/tools/select.h b/tools/select.h new file mode 100644 index 0000000..7e873e2 --- /dev/null +++ b/tools/select.h @@ -0,0 +1,75 @@ +#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_Rfds; + fd_set m_Wfds; + +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_Rfds); + FD_ZERO(&m_Wfds); + m_MaxFiled = -1; +} + +inline bool cTBSelect::Add(int Filed, bool Output /* = false */) { + if (Filed < 0) return false; + FD_SET(Filed, Output ? &m_Wfds : &m_Rfds); + 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_Rfds); +} + +inline bool cTBSelect::CanWrite(int FileNo) const { + if (FileNo < 0) return false; + return FD_ISSET(FileNo, &m_Wfds); +} + +#endif // TOOLBOX_SELECT_H diff --git a/tools/socket.c b/tools/socket.c new file mode 100644 index 0000000..4b5167d --- /dev/null +++ b/tools/socket.c @@ -0,0 +1,143 @@ +#include "tools/socket.h" + +#include +#include +#include +#include +#include + +cTBSocket::cTBSocket(int Type) { + memset(&m_LocalAddr, 0, sizeof(m_LocalAddr)); + memset(&m_RemoteAddr, 0, sizeof(m_RemoteAddr)); + m_Type = Type; +} + +cTBSocket::~cTBSocket() { + if (IsOpen()) Close(); +} + +bool cTBSocket::Connect(const std::string &Host, unsigned int Port) { + socklen_t len; + int socket; + + if (IsOpen()) Close(); + + if ((socket = ::socket(PF_INET, m_Type, IPPROTO_IP)) == -1) + return false; + + m_LocalAddr.sin_family = AF_INET; + m_LocalAddr.sin_port = 0; + m_LocalAddr.sin_addr.s_addr = INADDR_ANY; + if (::bind(socket, (struct sockaddr*)&m_LocalAddr, sizeof(m_LocalAddr)) + == -1) { + ::close(socket); + return false; + } + + m_RemoteAddr.sin_family = AF_INET; + m_RemoteAddr.sin_port = htons(Port); + m_RemoteAddr.sin_addr.s_addr = inet_addr(Host.c_str()); + if (::connect(socket, (struct sockaddr*)&m_RemoteAddr, + sizeof(m_RemoteAddr)) == -1) { + ::close(socket); + return false; + } + + 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; + } + + return cTBSource::Open(socket); +} + +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, IPPROTO_IP)) == -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; + + 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; +} diff --git a/tools/socket.h b/tools/socket.h new file mode 100644 index 0000000..d1a7d62 --- /dev/null +++ b/tools/socket.h @@ -0,0 +1,108 @@ +#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; + +public: + cTBSocket(int Type = SOCK_STREAM); + virtual ~cTBSocket(); + + /* See cTBSource::SysRead() + Reimplemented for TCP/IPv4 sockets. */ + virtual ssize_t SysRead(void *Buffer, size_t Length) const; + + /* See cTBSource::SysWrite() + Reimplemented for TCP/IPv4 sockets. */ + virtual ssize_t SysWrite(const void *Buffer, size_t Length) const; + + /* Connect() tries to connect an available local socket to the port given + by Port of the target host given by Host in numbers-and-dots notation + (i.e. "212.43.45.21"). Returns true if the connection attempt was + successful and false otherwise, setting errno appropriately. */ + virtual bool Connect(const std::string &Host, uint Port); + + /* Shutdown() shuts down one or both ends of a socket. If called with How + set to SHUT_RD, further reads on this socket will be denied. If called + with SHUT_WR, all writes are denied. Called with SHUT_RDWR, all firther + action on this socket will be denied. Returns true on success and false + otherwise, setting errno appropriately. */ + virtual bool Shutdown(int How); + + /* Close() closes the associated socket and releases all structures. + Returns true on success and false otherwise, setting errno + appropriately. The object is in the closed state afterwards, regardless + of any errors. */ + virtual bool Close(void); + + /* Listen() listens on the local port Port for incoming connections. The + BackLog parameter defines the maximum length the queue of pending + connections may grow to. Returns true if the object is listening on + the specified port and false otherwise, setting errno appropriately. */ + virtual bool Listen(const std::string &Ip, uint Port, int BackLog); + + /* Accept() returns a newly created cTBSocket, which is connected to the + first connection request on the queue of pending connections of a + listening socket. If no connection request was pending, or if any other + error occured, the resulting cTBSocket is closed. */ + virtual cTBSocket Accept(void) const; + + /* Accept() extracts the first connection request on the queue of pending + connections of the listening socket Listener and connects it to this + object. Returns true on success and false otherwise, setting errno to + an appropriate value. */ + virtual bool Accept(const cTBSocket &Listener); + + /* 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_DGRAM) { + socklen_t len = sizeof(m_RemoteAddr); + return ::recvfrom(*this, Buffer, Length, 0, (sockaddr*)&m_RemoteAddr, &len); + } else + return ::recv(*this, Buffer, Length, 0); +} + +inline ssize_t cTBSocket::SysWrite(const void *Buffer, size_t Length) const { + return ::send(*this, Buffer, Length, 0); +} + +#endif // TOOLBOX_SOCKET_H diff --git a/tools/source.c b/tools/source.c new file mode 100644 index 0000000..c832e2f --- /dev/null +++ b/tools/source.c @@ -0,0 +1,169 @@ +#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; + ms = TimeoutMs; + offs = 0; + while (Length > 0) { + int b; + + sel.Clear(); + sel.Add(m_Filed, true); + if (sel.Select(ms) == -1) + return false; + + if (sel.CanWrite(m_Filed)) { + if ((b = Write((char*)Buffer + offs, Length)) == -1) + return false; + offs += b; + Length -= b; + } + + ms = TimeoutMs - starttime.Elapsed(); + if (ms <= 0) { + errno = ETIMEDOUT; + return false; + } + } + return true; +} + +bool cTBSource::SafeWrite(const void *Buffer, size_t Length) { + cTBSelect sel; + int offs; + + offs = 0; + while (Length > 0) { + int b; + + sel.Clear(); + sel.Add(m_Filed, true); + 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 seqlen, 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; + while (m_LineBuffer.size() < BUFSIZ) { + sel.Clear(); + sel.Add(m_Filed, false); + + 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) + return -1; + m_LineBuffer.resize(len + b); + + if ((len = m_LineBuffer.find(Seq)) != (size_t)-1) { + if (len > Length) { + errno = ENOBUFS; + return -1; + } + memcpy(Buffer, m_LineBuffer.data(), len); + m_LineBuffer.erase(0, len + strlen(Seq)); + Dprintf("ReadUntil: Served from Linebuffer: %d, |%.*s|\n", len, len - 1, + (char*)Buffer); + return len; + } + } + + ms = TimeoutMs - starttime.Elapsed(); + if (ms <= 0) { + errno = ETIMEDOUT; + return -1; + } + } + errno = ENOBUFS; + return -1; +} + diff --git a/tools/source.h b/tools/source.h new file mode 100644 index 0000000..09c4bf3 --- /dev/null +++ b/tools/source.h @@ -0,0 +1,109 @@ +#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 occured. */ + virtual bool Close(void); + + /* Read() reads at most Length bytes into the storage pointed to by Buffer, + which must be at least Length bytes in size, using the SysRead()- + Interface. It retries if an EINTR occurs (i.e. the low-level call was + interrupted). It returns the exact number of bytes read (which can be + fewer than requested). On error, -1 is returned, and errno is set + appropriately. */ + ssize_t Read(void *Buffer, size_t Length); + + /* Write() writes at most Length bytes from the storage pointed to by + Buffer, using the SysWrite()-Interface. It retries if EINTR occurs + (i.e. the low-level call was interrupted). It returns the exact number + of bytes written (which can be fewer than requested). On error, -1 is + returned and errno is set appropriately. */ + ssize_t Write(const void *Buffer, size_t Length); + + /* TimedWrite() tries to write Length bytes from the storage pointed to by + Buffer within the time specified by TimeoutMs, using the Write()- + Interface. On success, true is returned. On error, false is returned + and errno is set appropriately. TimedRead only works on UNIX file + descriptor sources. */ + bool TimedWrite(const void *Buffer, size_t Length, uint TimeoutMs); + + bool SafeWrite(const void *Buffer, size_t Length); + + /* ReadUntil() tries to read at most Length bytes into the storage pointed + to by Buffer, which must be at least Length bytes in size, within the + time specified by TimeoutMs, using the Read()-Interface. Reading stops + after the character sequence Seq has been read and on end-of-file. + Returns the number of bytes read (if that is equal to Length, you have + to check if the buffer ends with Seq), or -1 on error, in which case + errno is set appropriately. */ + ssize_t ReadUntil(void *Buffer, size_t Length, const char *Seq, + uint TimeoutMs); + + /* BytesRead() returns the exact number of bytes read through the Read() + method since Close() has been called on this source (or since its + creation). */ + size_t BytesRead(void) const { return m_BytesRead; } + + /* BytesWritten() returns the exact number of bytes written through the + Write() method since Close() has been called on this source (or since + its creation). */ + size_t BytesWritten(void) const { return m_BytesWritten; } + + /* operator int() returns the descriptor (or informative number) associated + with this source. */ + operator int() const { return m_Filed; } +}; + +#endif // TOOLBOX_SOURCE_H diff --git a/tools/tools.c b/tools/tools.c new file mode 100644 index 0000000..fa813fa --- /dev/null +++ b/tools/tools.c @@ -0,0 +1,12 @@ +#include "tools/tools.h" + +#include +#include +#include +#include +#include + +void *operator new(size_t nSize, void *p) throw () { + return p; +} + diff --git a/tools/tools.h b/tools/tools.h new file mode 100644 index 0000000..ab00c60 --- /dev/null +++ b/tools/tools.h @@ -0,0 +1,67 @@ +#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