diff options
author | Tobias Grimm <etobi@debian.org> | 2015-10-07 19:30:05 +0200 |
---|---|---|
committer | Tobias Grimm <etobi@debian.org> | 2015-10-07 19:30:05 +0200 |
commit | 7092a2d6a6b2f988c86aea447edf553d0048cb4e (patch) | |
tree | 57aac6ae7d5467527e3b07e898504b3d86ef48c8 | |
download | vdr-plugin-xine-7092a2d6a6b2f988c86aea447edf553d0048cb4e.tar.gz vdr-plugin-xine-7092a2d6a6b2f988c86aea447edf553d0048cb4e.tar.bz2 |
Imported v0.9.4v0.9.4
-rw-r--r-- | COPYING | 340 | ||||
-rw-r--r-- | HISTORY | 893 | ||||
-rw-r--r-- | INSTALL | 393 | ||||
-rw-r--r-- | MANUAL | 471 | ||||
-rw-r--r-- | Makefile | 159 | ||||
-rw-r--r-- | README | 19 | ||||
-rwxr-xr-x | data/mkNoSignal.sh | 20 | ||||
-rw-r--r-- | data/noSignal16x9-completelyBlack.mpg | bin | 0 -> 8811 bytes | |||
-rw-r--r-- | data/noSignal16x9-completelyBlack.png | bin | 0 -> 1367 bytes | |||
-rw-r--r-- | data/noSignal16x9.mpg | bin | 0 -> 36539 bytes | |||
-rw-r--r-- | data/noSignal16x9.png | bin | 0 -> 27461 bytes | |||
-rw-r--r-- | data/noSignal4x3-completelyBlack.mpg | bin | 0 -> 8811 bytes | |||
-rw-r--r-- | data/noSignal4x3-completelyBlack.png | bin | 0 -> 1367 bytes | |||
-rw-r--r-- | data/noSignal4x3-old.mpg | bin | 0 -> 25207 bytes | |||
-rw-r--r-- | data/noSignal4x3-old.png | bin | 0 -> 13205 bytes | |||
-rw-r--r-- | data/noSignal4x3-smallTextAtBottom.mpg | bin | 0 -> 25324 bytes | |||
-rw-r--r-- | data/noSignal4x3.mpg | bin | 0 -> 42278 bytes | |||
-rw-r--r-- | data/noSignal4x3.png | bin | 0 -> 35383 bytes | |||
-rw-r--r-- | patches/xine-lib.patch | 4992 | ||||
-rw-r--r-- | patches/xine-ui.patch | 0 | ||||
-rw-r--r-- | po/ca_ES.po | 141 | ||||
-rw-r--r-- | po/cs_CZ.po | 139 | ||||
-rw-r--r-- | po/da_DK.po | 139 | ||||
-rw-r--r-- | po/de_DE.po | 139 | ||||
-rw-r--r-- | po/el_GR.po | 139 | ||||
-rw-r--r-- | po/es_ES.po | 139 | ||||
-rw-r--r-- | po/et_EE.po | 142 | ||||
-rw-r--r-- | po/fi_FI.po | 145 | ||||
-rw-r--r-- | po/fr_FR.po | 142 | ||||
-rw-r--r-- | po/hr_HR.po | 140 | ||||
-rw-r--r-- | po/hu_HU.po | 140 | ||||
-rw-r--r-- | po/it_IT.po | 147 | ||||
-rw-r--r-- | po/lt_LT.po | 139 | ||||
-rw-r--r-- | po/nl_NL.po | 144 | ||||
-rw-r--r-- | po/nn_NO.po | 140 | ||||
-rw-r--r-- | po/pl_PL.po | 139 | ||||
-rw-r--r-- | po/pt_PT.po | 139 | ||||
-rw-r--r-- | po/ro_RO.po | 140 | ||||
-rw-r--r-- | po/ru_RU.po | 142 | ||||
-rw-r--r-- | po/sk_SK.po | 141 | ||||
-rw-r--r-- | po/sl_SI.po | 140 | ||||
-rw-r--r-- | po/sv_SE.po | 143 | ||||
-rw-r--r-- | po/tr_TR.po | 139 | ||||
-rw-r--r-- | po/zh_CN.po | 139 | ||||
-rw-r--r-- | po/zh_TW.po | 139 | ||||
-rw-r--r-- | vdr172h264parser.c | 475 | ||||
-rw-r--r-- | vdr172h264parser.h | 406 | ||||
-rw-r--r-- | vdr172remux.c | 3453 | ||||
-rw-r--r-- | vdr172remux.h | 297 | ||||
-rw-r--r-- | xine.c | 343 | ||||
-rw-r--r-- | xineCommon.h | 63 | ||||
-rw-r--r-- | xineDevice.c | 4409 | ||||
-rw-r--r-- | xineDevice.h | 197 | ||||
-rw-r--r-- | xineExternal.c | 258 | ||||
-rw-r--r-- | xineExternal.h | 74 | ||||
-rw-r--r-- | xineI18n.h | 1561 | ||||
-rw-r--r-- | xineLib.c | 4596 | ||||
-rw-r--r-- | xineLib.h | 415 | ||||
-rw-r--r-- | xineOsd.c | 674 | ||||
-rw-r--r-- | xineOsd.h | 139 | ||||
-rw-r--r-- | xineRemote.c | 281 | ||||
-rw-r--r-- | xineSettings.c | 791 | ||||
-rw-r--r-- | xineSettings.h | 294 | ||||
-rw-r--r-- | xineSetupPage.c | 106 | ||||
-rw-r--r-- | xineSetupPage.h | 43 | ||||
-rw-r--r-- | xineplayer.c | 128 |
66 files changed, 29806 insertions, 0 deletions
@@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. @@ -0,0 +1,893 @@ +VDR Plugin 'xine' Revision History +---------------------------------- + +2011-03-16: Version 0.9.4 + +- The contained xine-lib-1.1 patch is outdated but might still + work. The patch will get updated in 0.9.5. +- A couple of xine-lib-1.2 patches is still waiting to get + commited. vdr-xine-0.9.4 should be able to work without them. +- Fixed a couple of things in xine-lib-1.2 and VDPAU. +- Fixed compilation for VDR-1.2.x and VDR-1.6.x. +- Updated Makefile to VDR-1.7.17. +- Added support for VDR-1.7.17s TrueColor OSD. +- Added support for VDR-1.7.12s changed PCR handling. +- Added support for VDR-1.7.11s semantic of MakePrimaryDevice. +- Resolved a segfault regarding VDR-1.7.x and cutting marks in + radio recordings (thanks to Joachim Wilke for providing a + fix). +- Included Lithuanian translation (thanks to Valdemaras + Pipiras). +- Included Slovak translation (thanks to Milan Hrala). +- Included Chinese and Taiwanese translations (thanks to + NanFeng). +- Fixed MANUAL about centre_cut_out_mode (thanks to Vorg on + #xine-vdpau). + +2009-06-17: Version 0.9.3 + +- Updated MANUAL accordingly (e. g. some information about + xine engine buffer sizes). +- Implemented VDR-1.7.8s cDevice::GetOsdSize() and changed + cDevice::GetVideoSize() accordingly. +- Implemented video window support while showing an OSD for + VDPAU to support plugins like yaepghd. +- Clipped settings in setup page to reasonable values while + editing to prevent rendering an unreadable OSD. +- Introduced separate live TV buffer values for SD and HD video + as well as audio to allow faster "zapping" on SD channels + while still having large buffers for HD video. +- Fixed setup page display while changing OSD extent. +- Fixed a memory leak in processing TS still images. +- Fixed a bug in processing TS still images which caused the + tail of the image to get lost and hence the image not to get + shown. +- Updated it_IT.po (thanks to Diego Pierotto for providing the + translations). + +2009-05-03: Version 0.9.2 + +- Updated MANUAL accordingly. +- Added setup options to define OSD extent. +- Implemented VDR-1.7.7s cDevice::GetVideoSize() to return an + OSDs maximum extent. + +2009-04-12: Version 0.9.1 + +- Adapted PlayTs() to semantic of VDR-1.7.5. +- Fixed compilation for VDR 1.2.x, 1.4.x, 1.6.x and 1.7.x. +- Added support for tIndex being uint16_t (not tested yet). +- Changed trick speed modes to match GetSTC(). +- Fixed buffer calculation after discontinuity which caused + endless slow motion. +- Changed GetSTC() to match VDR 1.7.5's requirements. +- Implemented TS support to StillPicture(). +- Fixed crash due to calling PlayTs() from different threads. +- Fixed crashes due to negative OSD coordinates caused by some + skins. +- Fixed INSTALL regarding xine-ui cvs repository and how to + apply the patches as they have been created differently since + release 0.9.0. + +2009-01-12: Version 0.9.0 + +- Updated INSTALL and MANUAL accordingly. +- Fixed several multithreading issues which got triggered on + SMP systems (thanks to Edgar (gimli) Hucek for reporting + issues and testing fixes). +- Added support for VDR-1.7.3 besides TS still pictures. +- Pulled patched VDR-1.7.2's remux.[ch] for a quick adaption to + to VDR-1.7.3 (thanks to Klaus Schmidinger for his approval). +- Fixed PTS handling in xine-lib's FFmpeg decoder for recent + FFmpeg versions which should provide better A/V sync now. +- Added patch sets to input_vdr.c which allow to omit parts of + the MRL, e. g. vdr:// should be sufficient now (thanks to + Ludwig Nussel for providing them). +- Make use of new OSD functionality in xine-lib-1.2. For now + an OSD implementation must provide both new features to have + vdr-xine make use of it. +- Extended OSD support in xine-lib-1.2 to allow truecolor OSDs + as well as hardware scaled OSDs. The new functionality is + currently only provided by VDPAU enabled installations. +- Added frame grabbing support for accelerated frame formats to + xine-lib. Previously, xine-lib exitted the app when frame + grabbing was requested for accelerated frame formats like + xxmc. You'll now get a green image in that case. Reading back + the content of an accelerated frame is currently only + provided by VDPAU enabled installations. +- Hacked support for VDR-1.7.1's new cDevice::PlayTs() method. +- Grab image nolonger needs pnmcut to select the relevant area + of the TV image. Besides that, grab image respects the aspect + ratio you select in VDR's setup menu, i. e. a 16:9 setup and + a 16:9 broadcast will grab you a image without black borders. + A mismatch of aspect ratios will add black borders to the + image to match the visual impression you get on your TV set. + Additionally, the grabbing of field pictures was corrected + with regard to color upsampling. +- A script mkNoSignal.sh is provided to create custom logos. + Just provide PNG files named noSignal16x9*.png (and ...4x3... + respectively) and have the tools mentioned in INSTALL in your + path. You'll have to move your custom logos then over the + default files. +- The vdr-xine logo is now available in two aspect ratios (4:3 + and 16:9) as more channels provide 16:9 content. Then vdr-xine + selects the logo which matches VDR's DVB setup option video + format. +- Added the ability to detect discontinuities in live TV and + to start a new buffering period in such a case. This will + help to resume to fluent playback from bad weather conditions + or when disconnecting and reattaching the antenna cable. +- Updated it_IT.po (thanks to Diego Pierotto for providing the + translations). + +2008-03-10: Version 0.8.2 + +- Fixed optional parameter processing for parameter -p. As + getopt() requires that optional arguments must follow the + option switch without any delimiters, I've updated MANUAL + accordingly, i. e. all options are documented without + delimiters. Furthermore you'll get an error message when + specifying the argument for -p incorrectly. +- Fixed compilation of patched xine-lib-1.1 (thanks to Darren + Salt and Fabian Foerg for contributing fixes for this issue). +- Provided xine_get_current_frame_alloc() to xine-lib which + allows retrieving the current frame image without the need + to pause the stream for a second call to the function after + allocating sufficient memory. +- Fixed retrieving only valid PTS from xine. xine's metronom is + now intercepted to monitor PTS changes after which PTS are + considered to be valid. +- Fixed handling VDR's clear command. Adding sync points to the + stream makes sure that all data on the way to xine up to sync + point gets properly dropped. +- Added Russian translation (thanks to Oleg Roitburd for + providing ru_RU.po). + +2008-01-01: Version 0.8.1 + +- Updated documentation mentioning binary and devel packages + for xine-lib-1.2, so compiling xine can be ommited for some + distributions. +- Updated INSTALL regarding XINE VDR VERSION MISMATCH. +- The xine-lib.patch for xine-lib-1.1.x nolonger contains any + FFmpeg fixes for H.264 decoding. It is recommended to install + a recent FFmpeg and to add --with-external-ffmpeg when + configuring xine-lib. +- Contributed some xine-lib options for H.264 decoding. +- Adopted name and files changes of vdr plugin in xine. +- Updated it_IT.po and xineI18n.h (thanks to Diego Pierotto for + providing the translations). +- Implemented makefile switch VDR_XINE_VERIFY_BITMAP_DIRTY to + turn off dirty bitmap verification and hence safing calls to + memcpy() and memcmp() for OSD operations. +- Removed TCP_NODELAY for the video stream socket to reduce + network load. +- Fixed OSD scaling for HD subtitles. The scaler nolonger + allocates a fixed size buffer for a 720x576 OSD but guesses + typical maximum OSD sizes, i. e. 720x576, 1280x720, 1920x1080. + +2007-10-24: Version 0.8.0 + +- Frame duration was not set correctly in xine-lib for H.264 + frames, causing frame drops an non fluent playback. +- Added network patch (thanks to Tobias Grimm for supplying it). + Although I had planned to optimize it to just use one port and + only two sockets, I decided to release it in its current state + instead of waiting further years. I've just modified it to use + a more xine-like MRL netvdr://host:port#demux:mpeg_pes where + port is optional (defaults to 18701). The fifo MRL should now + be written vdr://tmp/vdr-xine/stream#demux:mpeg_pes, although + a single slash will still work, too. +- Updated et_EE.po (thanks to Arthur Konovalov for supplying + the file). +- Fixed a bug in vdr-xine's buffer handling for live TV mode + which got most noticeable when switching back from a H.264 + channel to a MPEG2 channel, especially when the machine was + not capable of playing the H.264 stream in real time. As the + buffer was not cleared when switching channels, old H.264 + data was sent to xine instead of new MPEG2 data, which caused + xine to incorrectly switch to H.264 mode. This behavior could + lead to a crash in FFmpeg later. +- Fixed OSD handling in vdr-xine as certain subtitles could cause + a memory corruption in xine (thanks to Arthur Konovalov for + providing sample recordings). + +2007-10-17: Version 0.7.12 + +- Updated INSTALL and MANUAL accordingly. +- Added support for VDR-1.5.10's new key kSubtitles and fixed a + deadlock due to setting audio volume when changing the primary + device. +- Added a new configuration option "Connection interacts with EIT + scanner" as requested by Boguslaw Juza (see MANUAL for details). +- Added some patches to xine-lib regarding H.264 support, as + FFmpeg's decoder improved very much during the past weeks. It + supports now multithreaded decoding (you need to enable at + least 2 threads in xine-ui to make use of it) and almost + complete PAFF support which is used for interlaced images. + These new features are only available, when xine-lib is + configured to use an external FFmpeg installation. +- Added support for VDR-1.5.9's OSD levels. +- Fixed compiler warnings when compiled with gcc-4.2.x (thanks + to Joerg Bornkessel for suppling the patch). +- Exchanged noSignal.pes (thanks to Markus Nissl for supplying + the new image). + +2007-08-30: Version 0.7.11 + +- Added preliminary support for OSD changes in VDR-1.5.9. +- Added preliminary support for H.264 streams. xine uses FFmpeg for + H.264 decoding and as the decoder isn't rock stable at the moment + and doesn't support all H.264 features like interlacing, it is + likely that some frames do not get decoded or moreover that xine + crashes. +- Removed some unnecessary calls to clear xine's buffer when + switching between pause and play. +- Spent a lot of time fixing some issues in xine-lib. Integrated + a big bunch of my xine-lib patch into xine-lib-1.1.8. All patches + are now part of xine-lib-1.2. +- Added the opportunity to change xine's volume in software, like + when changing xine's config option gui.audio_mixer_method to + Software (thanks to Halim Sahin for the request). +- Added the possibility to configure different zoom settings for + 4:3 and 16:9 images, which are automatically activated when xine + signals a format change (thanks to Detlef Nebermann for the + request). + +2006-12-10: Version 0.7.10 + +- Updated xine patches to current CVS (should be compatible with + xine release 1.1.3). +- Fixed a segfault in SHQ OSD scaling code due to uninitialized + variables (thanks to Gregorie Favre for reporting this issue and + testing the fixes). +- Added noSignal-smallTextAtBottom.mpg to directory data (thanks to + John for supplying the file). +- Fixed a segfault in xine when there is no demuxer available (e. g. + the SuSE 10.1 xine-lib binary lacks support for MPEG2). +- Fixed compiler warnings on x86_64 architectures (thanks to Ville + Skyttä for compilation assistance). +- Added support to xine for the 3 new keys of VDR-1.3.47. +- Updated MANUAL accordingly. + +2006-04-17: Version 0.7.9 + +- Updated Finnish translation (thanks to Rolf Ahrenberg for + supplying the patch). +- Adapted vdr-xine to VDR-1.3.45. +- Adapted vdr-xine to VDR-1.3.47. + +2006-03-20: Version 0.7.8 + +- Fixed HISTORY as it mentioned future releases of VDR (thanks to + Klaus Schmidinger for reporing this issue). +- Fixed sending video data: the first packet after a Clear() got + dropped which made editing recordings impossible (thanks to + Gregoire Favre for reporting this issue). +- Fixed implementation of func_wait as it didn't synchronize VDR and + xine as expected. +- Adapted vdr-xine to VDR-1.3.44 cOsd::DrawBitmap() (thanks to C.Y.M. + for reporting this issue). +- Replaced 'prebuffer' by 'buffer' in vdr-xine's setup menu. +- Added new setup parameter "buffer hysteresis" which is added to + "Live-TV buffer" for determining the initial buffer size which + must be achieved before replaying switches to normal speed. It is + recommended to reduce your current buffer setting by the value + for hysteresis. +- Added PTS prediction to stop prebuffer phase much earlier on + channels which rarely add PTS to their stream's PES packets. +- Changed buffering to monitor the buffer size during a configureable + time after the configured buffer size (Live-TV buffer + hysteresis) + was initially established. + Whenever the buffer size drops below the configured value (of Live- + TV buffer frames), hysteresis is increased by an additional frame + and the new buffer is established. +- The above changes to buffering allow minimizing the buffer for + faster zapping on regular channels while not loosing the ability + to watch some special (mosaic) channels which required larger + buffer settings. +- Implemented continuous monitoring to reestablish the buffer once + it drops below the configured value. This allows watching the HDTV + demo loop on ASTRA HD forever. This feature can be enabled on + demand in vdr-xine's setup menu, as it might be a performance issue + on less powerful machines. +- As VDR-1.2.6 lacks c*Repackers, the above changes to buffering do + not work that precisely with this old VDR version. Just increase + the buffer values a bit as necessary. +- Fixed an out of range access in OSD scaling code by clipping away + negative coordinate areas. The segfault was triggered by opening + the audio track menu on channels with at least 20 tracks while + VDRs default skins where selected. +- Patched xine to speed up OSD operation when switching audio tracks + in live TV mode or when showing still frames (e. g. when editing + cutting marks). This change most likely breaks binary compatibilty + to other xine plugins. +- Failed to speed up OSD operation in trick speed modes: it would + require to copy each frame at least while OSD is active, which + might be a performance issue. So for now, the worst timing for a + single OSD operation is about 2500 ms in VDR's first slow reverse + mode. A possible solution might be X11 OSD overlay, as it is + independent of the video frame, so there is no copying involved. + But that's still to investigate. +- Implemented OSD scaling in regard to xine's video zoom factor. The + OSD will now be scaled accordingly for zoom factors > 100 %. As + determining the zoom factors is an "expensive" operation in xine, + they are only retrieved when some drawing to the OSD is happening. + So you won't see an open OSD to immediately change it's size. +- Fixed a segfault in SHQ OSD scaling code by setting the allocated + memory to 0. +- Updated INSTALL to mention a workaround for xine CVS access, as + cvs.sourceforge.net was quite unreliable recently. +- Fixed compilation for VDR version 1.2.6 which was only possible by + dropping auto primary device feature for VDR versions < 1.3.32. +- Updated MANUAL accordingly. + +2006-02-14: Version 0.7.7 + +- Updated MANUAL (thanks to Ville Skyttä for supplying the patch). +- Added Dutch translation (thanks to Maarten Wisse for supplying the + patch). +- Fixed auto primary device functionality concerning ActualDevice + (thanks to Luca Olivetti for reporting this issue). +- Shutting down cOsdProvider when vdr-xine is nolonger primary device + to fix an issue where the new primary device doesn't register it's + own cOsdProvider instance. +- Fixed cXineDevice::SetDigitalAudioDevice() for radio channels with + multiple audio tracks (e. g. RADIO INT2). These tracks are not + related to each other and therefore syncing these tracks by PTS is + impossible and causes huge delays otherwise. +- Adopted cXineDevice::GrabImage() to VDR-1.3.38 (based on a patch of + Darren Salt). +- Fixed some issues with FIFO_DIR containing spaces (thanks to Darren + Salt for supplying the patch). +- Fixed some warnings about strict-aliasing issues when compiling with + gcc-4.1 (thanks to Ville Skyttä for reporting this issue). +- Added two new command line arguments (-X / -Y) to change the default + image size for GrabImage(). Internally, they are predefined as + -X 720 and -Y 576. +- Dropped the Audio-Mode setup option for VDR >= 1.3.18 as VDR's audio + menu and PlayPes() took over sending just a single audio stream to + the output device. +- Fixed implementation of cXineDevice::Clear(), which speeds up jumping + back and forth in recordings. A stresstest showed that from time to + time a deadlock happened in libasound (ALSA) when xine is opening the + audio device. You may experience a problem too if you stay on the + GREEN button at the beginning of a recording. On my system it caused + segfaults, asserts and deadlocks while xine is opening the ALSA + audio device. I'd be glad if someone could fix this issue. + Anyway, I hope that this change partitially fixes the delay on some + systems which happened when switching to trickspeed modes while + replaying a recording. +- Added support for VDR's new info key. +- Tried to get xine's ffmpeg decoder to work with vdr-xine, but failed. +- Had xine CVS take over a couple of my xine patches. +- Tuned xine-lib's startcode scanner in libmpeg2. +- Adapted vdr-xine to VDR-1.3.41 changed detection of transfer mode. +- Adapted vdr-xine to VDR-1.3.42 changed cDevice::PlayAudio(). +- Replaced noSignal*.pes with noSignal*.mpg. vdr-xine will complain + about a missing noSignal.mpg. Just copy the new files from the source + directory to the mentioned location as mentioned in INSTALL. +- Changed buffering completely. When VDR switches to a channel, xine + starts replaying at 12.5 % of normal speed. Then vdr-xine monitors + the stream's PTS values and compares them to the value which xine + reports. The difference between VDR's and xine's value makes up the + currently gained buffer. When the buffer reaches the configured size + then xine switches to 100 % speed. + ATTENTION: this change requires you to reduce the configured + prebuffer in vdr-xine's setup page to about 8 frames! +- Updated MANUAL and INSTALL accordingly. + +2005-09-11: Version 0.7.6 + +- Fixed vdr-xine to update xine's audio stream bitrate and decoder + after xine has connected to vdr-xine. Before that, xine showed + the information of the NOSIGNAL stream. +- Several MPEG1 related fixes. +- Several fixes concerning playing of externally generated VDR + recordings (thanks to Fabian Wolter for supplying a recording). +- Fixed compilation for VDR-1.3.32. +- Playing a still image could make vdr-xine wait for further video + packets (this issue showed up from time to time when using the + vdr-radio plugin). + +2005-08-14: Version 0.7.5 + +- Added documentation about gxine's VDR support to MANUAL (thanks to + Darren Salt for supplying the patch). +- Fixed xine-plugin to update audio stream bitrate and decoder. +- Removed singleton enforcement of xine-plugin as it caused problems + with xine's post plugin management (suggested by Darren Salt). +- Added support to select left / right audio channel by audio post + plugin 'vdr_audio'. +- Added audio post plugin support to xine-ui and fbxine which was needed + for the new audio post plugin vdr_audio. +- Renamed video post plugin 'vdr' to 'vdr_video' to match naming scheme, + but it also registered as 'vdr' to maintain compatibility. +- Created audio post plugin 'upmix_mono' to turn a mono stream into a + stereo one with identical data on both sides (requested by Lucian + Muresan). +- Fixed some endianness problems in OSD code (thanks to Carsten + Rietzschel for reporting this issue). +- Fixed some compiler warnings with gcc-4.0.1 and gcc-4.1 (thanks to + Eric OUEDRAOGO for reporting this issue). +- Updated plugin to match interface changes in VDR-1.3.27 (thanks to + C.Y.M for reporting this issue). +- Fixed implementation of cDevice::Clear() which didn't ensure that + really all data (i. e. the data which is just in the FIFO or socket) + get's cleared. This might improve switching to trickspeed mode. +- Updated MANUAL about using vdr_audio post plugin. + +2005-05-08: Version 0.7.4 + +- Fixed compilation issue with gcc-4.1 (thanks to ollo at vdr-portal for + reporting this issue). +- Fixed audio output when used with muggle (thanks to Der_Olli at + vdr-portal for testing). +- Fixed input_vdr for proper "C" coding (thanks to Philip Lawatsch for + reporting this issue). +- Increased xine-lib's demux_mpeg_pes.c's WRAP_THRESHOLD to 270000 to + fix detection of false discontinuities (thanks to Jouni Karvo for + reporting this issue). +- Fixed xine-lib's xxmc decoder to properly decode radio-plugin's + background images. +- Updated dvbplayer patch for VDR-1.3.24. + +2005-04-11: Version 0.7.3 + +- Started detection of AFD header in xine to automatically crop out the + interesting part of the image later. +- Adopted implementation of cXineDevice::SetDigitialAudioDevice() to + different calling order in VDR 1.3.23. +- Improved cXineDevice::SetDigitalAudioDevice() when replaying recordings. +- Added setup option to automatically make vdr-xine the primary device + while xine is connected to vdr-xine (requested by Der_Olli at vdr-portal). +- Added setup option to consider all semi transparent colors as opaque to + make the menu more readable. +- Added commandline option '-s' to switch to skin 'curses' while xine is + not connected to vdr-xine (requested by Rantanen Teemu). +- Added commandline option '-q' to suppress debug messages (useful in + combination with option '-s'). +- Moved disconnect to cXineDevice::Stop() to get the new options to work. +- Fixed all (?) deadlock situations in RPC command processing (e. g. + stopping replay while switching a channel). +- Fixed deadlocks in vdr-xine's xread(). A possible drawback is that now + a disconnect might happen in such a case. +- Fixed VDR's I-frame processing which caused disconnects e. g. while + moving cut marks in HDTV recordings. vdr-1.2.23-dvbplayer3.patch is + highly recommended for proper operation of vdr-xine. +- Improved cXineDevice::StillImage() implementation to immediate display + the frame (improves moving cut marks). +- Fixed cXineDevice::StillImage() to work properly in combination with + the plugin vdr-radio. + BUG: xine's driver xxmc shows just a black screen on my EPIA MII-6000E. +- Reintroduced usleeps() in input_vdr.c for flush, OSD flush and reset + audio. sched_yield() simply caused to much CPU load while waiting + about 40 ms to reach the expected state. Improves number of dropped + frames when switching channels. +- Optimized OSD display: VDR's channel display repeatedly sends a dirty + OSD which doesn't differ from the previous one. Improves number of + dropped frames while switching channel. + BUG: it's still unclear whether this causes some OSD artefacts. +- Fixed demux_mpeg_pes' discontinuity detection. Previously, when a PTS + wrap happend, xine stopped replay for about 26.5 hours. +- vdr-xine now nolonger set's xine's metronom directly but tells it's + demuxer to do the job. Improves switching channels. +- Optimized implementation of cXineDevice::Clear() in input_vdr.c. + BUG: it may happen that xine's audio driver ALSA might get into a state + of "silence" where it doesn't recover from until you stop replaying the + recording. I still didn't find a way to reproduce this but it has to do + with trickspeed, pause, play, and probably cut marks. +- Fixed post_vdr.c to detect MRL changes for discovering streams sent from + VDR, e. g. when xine is not started with the MRL specified on it's + command line. + BUG: It's possible that xine crashes due to this detection. xine doesn't + allocate a different stream for a different MRL, but maybe other players + do. I'm not sure whether I managed to increase the streams usage counter + properly (by allocating an event queue) until I detect the new stream + respectively MRL. + BUG: post_vdr doesn't operate when xine's driver xxmc is used due to some + limitation/incomplete implementation in xine's plugin interface. +- Fixed xine's deinterlacer interface to take care of cropping. Previously + the OSD was resizing like mad e. g. between 1920x1080 and 1920x1088. +- Added support for VDR's new AUDIO key in xine (thanks to Darren Salt for + reporting this issue). + +2005-02-27: Version 0.7.2 + +- Fixed cXineDevice::StillPicture() to support three times larger still + image data than before. This was necessary to properly support mp3 + plugin's cover image feature (thanks to burki at vdr-portal for reporting + this issue). +- Removed cXineStatus as vdr-xine now uses a different method of detecting + LiveTV. This one works better in combination with mp3 plugin (thanks to + Klaus Schmidinger for suggesting the new solution). +- Updated xine-lib patch for current CVS (thanks to Grégoire Favre for + reporting this issue). + +2005-02-13: Version 0.7.1 + +- Updated finish translation (thanks to Rolf Ahrenberg for supplying the + update). +- Fixed a typo in checking for VDR versions before 1.3.18. This broke + successful compilation for versions between 1.3.8 and 1.3.18 (thanks to + Andreas Urban for reporting this issue). +- Fixed processing of cDevice::SetDigitalAudio() as it skipped displaying + of NO SIGNAL when switching channels. + +2005-01-29: Version 0.7.0 + +- Implemented centre-cut-out mode: this feature will crop away the black bars + around a 4:3 image which is broadcast in a 16:9 stream and is displayed on + a 4:3 monitor. See MANUAL how to enable this mode. +- Fixed OSD scaling for the case where xine cropped the image for displaying. +- Integrated patch for image grabbing. You can now specify the absolute path + of the utilities for grabbing an image. Furthermore, the patch fixes a + possible security issue when the filename for the GRAB command contained + special shell characters (thanks to Darren Salt for supplying the patch). +- Image grabbing now also requires pnmcut to support cropped images. +- Prepended VDR_XINE_ to Makefile variables FIFO_DIR and SET_VIDEO_WINDOW to + avoid collisions when vdr-xine's variables are set in VDR's Make.conf. +- Added syslog messages when VDR_XINE_FIFO_DIR or any fifo cannot be created. +- Added a further config option to vdr-xine's setup menu: you may now specify + how VDR's mute requests will be processed in xine: + a) ignore + b) simulate (set volume to 0 / restore volume) + c) execute +- Fixed xine part to update UI when volume / muting is changed. +- Updated INSTALL, MANUAL and xineI18n.h accordingly. + +2005-01-23: Version 0.6.5 + +- Adapted vdr-xine to the recent changes in VDR-1.3.19. +- With VDR-1.3.19, the HDTV patch for VDR is obsolete now. See the file + vdr-patches-README.txt on my homepage for details. + +2005-01-10: Version 0.6.4 + +- Adapted vdr-xine to the recent changes in VDR-1.3.18. The xine part has also + changed and therefore a rebuild of xine is required. +- With VDR-1.3.18 a lot of my patches for VDR are obsolete now. See the file + vdr-patches-README.txt on my homepage for details. +- Removed the necessity of changing POLLTIMEOUTS_BEFORE_DEVICECLEAR. +- Updated INSTALL appropriately. + +2004-12-31: Version 0.6.3 + +- Fixed uninitialized variable in xineRemote.c. Release 0.6.2 showed high CPU + load of XineRemote control thread when switching between channels with + different resolution. + +2004-12-30: Version 0.6.2 + +- Integrated i18n updates (thanks to Rolf Ahrenberg for supplying the patch). +- Fixed processing of MPEG1 streams. vdr-xine should now be able to work + together with vdr-analogtv. +- Fixed race-condition at shutdown when at client-disconnect vdr-xine accessed + the already destructed xineRemote. +- Fixed invalid pointer access in input_vdr.c (thanks to Darren Salt for + supplying the patch). +- Fixed some OSD update issues that happend when xine asked for an update and + VDR was drawing on the OSD at the same time. +- Prepared xine part for integration into xine-lib-1.0.x. +- Synced SPU processing. DVD subtitles should now nolonger be several seconds + ahead of their expected presentation time. +- Updated MANUAL to reflect name changes in xine's config file. + +2004-12-10: Version 0.6.1 + +- Added vdr-xine option "-i NUM" which can be used to get two or more vdr-xine + instances running on the same machine (in different VDR instances). The same + option is available as "--vdr-instance=NUM" to xineplayer but there it must + be the first argument to not conflict with any other options from mplayer. +- Fixed cXineDevice::StillPicture() to automatically add an PES video header + if the given data doesn't contain one. As a result vdr-streamdev-* is now + able to show an image while pausing Live-TV (thanks to Darren Salt for some + hints on optimized coding). +- Fixed some special cases of dolby audio processing introduced in 0.5.3 + (thanks to Markus Maierhofer for reporting this issue). +- Moved option OSD_SCALING_MODE and DONT_CHANGE_XINE_VOLUME from Makefile to + vdr-xine's setup. Therefore the same binary package can now be run on less + powerful machines too. +- Integrated a large piece of xine-lib.patch into xine-lib-cvs. As a result + the configure option --disable-exact-blending is gone and is now available + as runtime configureable option "video.disable_exact_osd_alpha_blending". +- Updated xineI18n.h. Feel free to supply updates for other languages too. +- Updated INSTALL and MANUAL accordingly. +- Introduced xineCommon.h to handle poisend VDR source. +- Prepared xineI18n.h for additional languages. +- Added a setup option for adjusting OSD gamma correction when OSD scaling + is necessary. +- Fixed xine part to no longer freeze when one connects to stale vdr-xine + FIFOs. +- Fixed xine part to immediately react on the "stop" button in case where + VDR doesn't send a stream to xine. +- The error message about missing noSignal.pes is now also logged via syslog + as there was otherwise no indicater found in the logfile why VDR terminated + that early. + +2004-11-02: Version 0.6.0 + +- Implemented playback of external sources. This is done using the well known + mplayer plugin in combination with my mplayer wrapper called "xineplayer". + Just setup mplayer plugin as it's documentation states. Then edit it's + "mplayer.sh" and replace "mplayer" by "xineplayer". xineplayer get's built + into vdr-xine's source directory. You'll have to copy it to a "bin" + directory which is part of your PATH or you might want to specifiy it's full + path in mplayer.sh. For this first release you'll have to select mplayer + plugin's "TRADITIONAL" mode to get it working with vdr-xine. +- Exact but CPU intensive OSD blending can now be disabled in xine-lib by + giving the configure switch "--disable-exact-blending" to "./autogen.sh". +- Fixed high CPU load of vdr-xine when xine was disconnected while a currently + replayed recording was paused. +- Removed DATA_DIR. "noSignal.pes" is now expected below VDR's config files + directory, i. e. .../plugins/xine/noSignal.pes. +- Added OSD scaling mode "auto SHQ" which automatically chooses SHQ mode for + low resolution streams (width < 360 respectively height < 288). This mode is + now the one which is preselected in Makefile. +- Updated INSTALL and MANUAL accordingly. + +2004-10-24: Version 0.5.3 + +*** NO NEED TO RECOMPILE xine-lib / xine-ui THIS TIME *** + +- Fixed dolby audio processing to get recordings (and former live stream) of + channel "ProSiebenHD" to play properly. +- Fixed extraction of PTS values (ignore PTS of value 0) resulting in better + prebuffering (thanks to Cristiano Bozzi for reporting this issue). +- Changed data processing to comply with VDR's new buffer handling since + release 1.3.13 (thanks to Klaus Schmidinger for assistance). +- Scrambled flag on PES packets is now cleared to not have xine terminate + playback due to bad reception quality respectively early data transmission + while device is still tuneing (thanks to mvdbeek at vdr-portal for reporting + this issue). +- Replaced all usleep() calls by cMutex / cCondVar substitutes. +- Added paragraph to INSTALL (BUILDING XINE) about required versions of + autoconf suite for building xine (requested by Frank99 at vdr-portal). + +2004-10-09: Version 0.5.2 + +- Fixed xine-lib/src/vdr/input_vdr.c for compliance with C coding. It should + now compile properly with gcc-2.95.3 (thanks to Jouni Karvo for reporting + this issue). +- Implemented cPlugin::CommandLineHelp() (suggested by ronnykornexl at + vdr-portal). +- Updated INSTALL and pointed to xine-cvs archives on my homepage. +- Updated INSTALL and documented "ERROR: remote control XineRemote not ready!" + which appears in VDR's logfile. +- Updated INSTALL and mentioned delay of about 10 seconds until VDR enters + "Phase 2" of remote learning mode after pressing a remote key in "Phase 1". +- Fixed compile error of xine-lib's video_out_xvmc.c which was caused by my + OSD patches (thanks to Kron at vdr-portal for reporting this issue). +- Fixed a buffer overrun in my patches to xine-lib which caused xine to die + occasionally when VDR's OSD was active and/or operated (thanks to Joerg + Knitter for intensively testing vdr-xine). +- Tried to create a patch for xine-lib that complains when applied to RC6 but + all rejects should only address the newly added driver xxmc. Therefore they + can simply be ignored. +- Currently, xine-ui.patch is empty but I didn't want to remove this file as + doing so might break some people's build scripts. + +2004-10-04: Version 0.5.1 + +- Updated documentation (thanks to Darren Salt for supplying the patch). +- OSD scaling is now using frame resolution instead of stream resolution. This + should make the OSD show up more often already at the right resoltion. For + almost immediate response to resolution changes it is required to run xine + with --post vdr. +- Completed implementation of cDevice::GrabImage() method. It now supports + xine's different image formats and honors the different aspect ratio of + 16:9 broadcasts. +- Added paragraph to INSTALL about additional programs which are needed to + successfully grab images. +- Added paragraph to INSTALL about remote learning mode. +- Synchronized OSD output. Hideing an OSD and grabing an image immediately + afterwards leaded to a snapshot which still showed the OSD. +- Probably fixed audio replay on channels with still video. Prebuffer time is + now extended dynamically by the difference between audio and video time + stamps on the channel in question. +- Fixed prebuffering after toggleing audio channel. +- Reorganized directory structure of the xine part once again. xine-ui now + needs the configure switch "--enable-vdr-keys" to support vdr-xine's remote. +- Improved OSD scaling which took over month to optimize. See Makefile for + OSD_SCALING_HQ. One might also choose OSD_SCALING_SHQ but be warned about + the CPU load it causes. +- xine-lib-1-RC6 should be sufficient for this release but my patches will + complain about the recently added driver xxmc. + +2004-08-08: Version 0.5.0 + +- Added paragraph to INSTALL about using the correct xine-config in Makefile + (suggested by Luca Olivetti). +- Fixed SEGV (in remote control code) which happend when VDR was exiting + (thanks to Mattias Grönlund for supplying the patch). +- Added a Makefile switch DONT_CHANGE_XINE_VOLUME to have vdr-xine never + change xine's volume control (e. g. when connecting xine to VDR), as this + might lead to (un)muting the wrong audio channels (requested by Jouni + Karvo). +- Fixed quickly switching between play / pause. There is still a lockup left + to be fixed that appears on some machines when jumping between cutting marks + (thanks to peter_weber69 at vdr-portal for testing the fix). +- Added video scaling by xine post plugin 'vdr' to support yaepg plugin. One + will have to add "--post vdr" to the command line or select the post plugin + via the user interface. As yaepg patches VDR, you'll have to tell vdr-xine + whether you want to support yaepg by enabling SET_VIDEO_WINDOW in Makefile. +- Looking forward to having the xine part integrated into CVS, the locations + of my plugins in xine-lib's source tree have changed. INSTALL was updated + to the new patch procedure. A current CVS source tree is required (can be + found on my home page, too). + +2004-07-25: Version 0.4.3 + +- Fixed xineI18n.h for compatibility with VDR-1.3.[01]. The Russian translation + entries were introduced with VDR-1.3.2 (reported by Rolf Ahrenberg). +- Implemented OSD scaling. Currently, the scaled OSD is of "bad" quality, but + as a start, it's better than nothing :-) +- Tried to fix the issues which were recently introduced by soft-start, but + it's likely that the fixes still don't work for less powerful machines. In + such a case, locate softStartCalcSpeed() in xineDevice.c and try differnt + methods (0..3). Please drop me a line on success respectively failure. + +2004-07-04: Version 0.4.2 + +- Integrated swedish translation supplied by Tomas Prybil. +- Fixed xineI18n.h for compatibility with VDR-1.3.6 (thanks to ronnykornexl at + vdr-portal for reporting this issue). +- Followed http://www.vdr-wiki.de/wiki/index.php/MANUAL-DE#Patches for creating + respectively naming xine patches (thanks to ronnykornexl at vdr-portal). +- New feature for Live-TV mode: "soft-start" replays the stream in slow motion + while buffering. Useful for zapping, where you'll see an image of the current + program quite early. The stream speed starts now at 50 %, slows down to 25 % + and then speeds up to 100 %. This happens within the prebuffer time, so you + should increase it (e. g. to 50) if you experience problems. + +2004-06-27: Version 0.4.1 + +- Fixed audio handling of the previous release as it broke playing mp3s (thanks + to Jouni Karvo for reporting this issue). +- Fixed xine's linear PCM decoder to support more sample rates. Previously, mp3 + files did often play to fast. +- DVDs should play properly now, independent of the dolby setting. +- Disabled debug code. Please remove all /tmp/frame* files which might have + been created by release 0.4.0. +- Integrated remote control patch for fbxine (provided by Dirk Meyer), but I'm + still waiting for an example on how to bind the keys in fbxine. +- Added a section to MANUAL about xine's command line and useful options + (suggested by Jouni Karvo). +- VDR: Improved performance. Please apply the vdr-1.3.11 patches which are + available on my homepage. +- VDR: Fixed recording replay when reaching the end of a recording in + trickspeed mode. VDR switched to play mode instead of ending replay. + +2004-06-21: Version 0.4.0 + +- Integrated patch which implements cDevice::GetSPUDecoder() (thanks to Sven + Goethel for supplying the patch). +- Fixed INSTALL again for correct quoting (thanks to Johannes Schoeller for + reporting this issue). +- Updated xineI18n.h for now 18 languages in VDR 1.3.x (thanks to Achim + Tuffentsammer for the hint). +- Added a paragraph to INSTALL for users of full featured cards (suggested by + Tuomas Jormola). +- Rewrote INSTALL in various places, to contain much helpful information + discussed previously on the mailing list. +- Integrated patch for supplying xine the default MRL of VDR (thanks to Darren + Salt). +- Added cDevice::Flush() to work around xine's large buffers. +- xine-lib-1-rc5 should be sufficient to get the plugin working. +- Improved a lot regarding interoperability with plugin vdr-dvd. To make use + of these changes, you'll have to adjust your '.xine/config'. See MANUAL for + more information. +- It is recommended to enable dolby when using vdr-dvd. + +2004-05-27: Version 0.3.4 + +- Fixed INSTALL to suggest a correctly quoted 'runvdr' command for running the + plugin for the first time (thanks to peter_weber69 and anonymous (aka Ronny) + on VDR Portal for pointing this out). +- Fixed xineOsd.c to show OSD in initial remote key learning mode (thanks to + chris281080 on VDR Portal for determining this issue). +- Added a warning message to xineLib.c, in the case where 'noSignal.pes' can't + be opened, e. g. if DATA_DIR is not set correctly in Makefile. +- Fixed Makefile to use the default DATA_DIR, not my personal one. + +2004-05-22: Version 0.3.3 + +- Fixed input_plugin/input_vdr.c to return proper values for cDevice::GetSTC() + (thanks to Pekka Virtanen for supplying a test plugin). + +2004-05-22: Version 0.3.2 + +- Fixed xineRemote.c to be compatible with API of VDR 1.2.6. + +2004-05-21: Version 0.3.1 + +- Updated xine-lib.patch to support all VDR remote keys. +- Patch file renamed to xine.patch, as xine-ui is modified too. +- Updated INSTALL to reflect new patch file name and patching of xine-ui. +- Updated MANUAL to tell command line args and xine's keybindings. + +2004-05-21: Version 0.3.0 + +- Changed code sequence to follow the C language properly (reported by + Jouni Karvo). +- Updated Finnish translation and added empty Russian translations while + maintaining compatibility with VDR 1.2.x (reported by Rolf Ahrenberg). +- Dropped (later maybe configurable) sending MUTE to xine, as it's not + really necessary for normal operation of VDR (requested by Jouni Karvo). +- Fixed sending dolby audio to xine (requires up-to-date xine-lib CVS). +- Added support for VDR-1.3.7's new OSD interface. +- Integrated patch from Simon Truss to send keystrokes from xine to VDR + for controlling VDR from within xine. +- Enhanced compatibility of xine part to support xine's older API for + VIA-enhanced xine (thanks to Vincenzo Memeo). + +2004-05-05: Version 0.2.2 + +- Added define _GNU_SOURCE to Makefile (just like for new plugins). +- DATA_DIR in Makefile contained path of my system. +- OSD update in xine is now optimized to VDR's dirty OSD area. +- Implemented cDevice::GetSTC(). +- Removed some unnecessary PES packet requirements for trickspeed mode. +- Moved image conversion from xine to VDR for cDevice::GrabImage() + implementation. +- Added support for dolby audio (i. e. cDevice::PlayAudio(): untested due + to lack of hardware). +- Added configuration option to enable dolby audio (default: off). +- When xine connects to VDR, volume and muting are adjusted to fit VDR's + values. + +2004-04-23: Version 0.2.1 + +- Corrected some FIXMEs for certain recordings. +- Audio is now unmuted when leaving a recording in trickmode speed. + +2004-04-22: Version 0.2.0 + +- Added trickspeed mode. +- Added image grabbing (requires y4mscaler, y4mtoppm and pnmtojpeg). +- Added automatic selection of prebuffer mode. +- Dropped config option 'xine.modeReplay.prebufferFrames'. +- Dropped config option 'xine.usageModeDefault'. +- Dropped main menu entry. +- Partly integrated Makefile patch from Darren Salt. +- Fixed some stuff regarding latest CVS xine. + +2004-02-03: Version 0.1.2 + +- Fixed a compile error in xine +- Fixed a compile error in VDR + +2003-12-21: Version 0.1.1 + +- Changed version numbering for easier update recognition. +- Updated to latest xine API version number. +- Fixed VDR's high CPU load when no xine connected. +- Added Finnish translation supplied by Rolf Ahrenberg. +- Supplied a completely black 'noSignal.pes' to prevent the monitor from + damage while listening to radio stations for a long time. + +2003-12-17: Version 0.0.3 (aka 0.1.0) + +- Improved channel switching by using xine's prebuffering. +- Prebuffer settings configurable for Live-TV and Replay. +- Changed directories for plugin's files as suggested on ML. +- Supports xine's unscaled OSD (configurable). +- Shows a currently open OSD on connect with xine. +- Shows an OSD in remote key learning mode. +- Addresses some show stoppers due to recent xine changes. +- Supports i18n. + +2003-09-08: Version 0.0.2 + +- Minor fixes. +- Improved channel switching. +- Dropped dependencies to X11. + +2003-09-07: Version 0.0.1 + +- Initial release. + +2003-06-23: Version 0.0.0 + +- Announcement. + @@ -0,0 +1,393 @@ + +OVERVIEW +~~~~~~~~ +Introduction +A note about VDPAU +Getting a recent xine +Patching xine's source +Building xine +Testing xine +Patching VDR +Makefile settings +Building vdr-xine +Xine VDR version mismatch +Testing vdr-xine +Using budget cards +Remote learning mode +Using full featured cards +Additional software +Last but not least + + +INTRODUCTION +~~~~~~~~~~~~ +As you can read these lines, you've successfully extracted the tar archive of +VDR plugin xine. The plugin supplies VDR with an output device in software by +using xine. It's most useful if you don't own a DVB card which can be directly +connected to a TV set, or if you want to watch HDTV (MPEG2/H.264) content which +typically cannot be displayed by such a card. + +Usually, the contents of this package should be below the PLUGINS subdirectory +of VDR. I've installed the source of VDR in directory "/soft/src/VDR", so the +files of this package reside in directory "/soft/src/VDR/PLUGINS/src/xine". + + +A NOTE ABOUT VDPAU +~~~~~~~~~~~~~~~~~~ +VDPAU is a new API for hardware assisted video decoding. If you plan to use +VDPAU, you will need to choose different sources for xine-lib. To stay with +xine-lib-1.1 you can choose the VDPAU development fork named xine-vdpau which +is available via svn and go on with patching it for vdr-xine: + + svn co svn://jusst.de/xine-vdpau + +In case you would like to use xine-lib-1.2 then you need to patch in VDPAU +support. You'll find updated patches against xine-lib-1.2 here: + + http://jusst.de/vdpau + +The patches are named like xine-lib-1.2-vdpau-r157.diff.bz2 where r157 for +example indicates the vdpau svn revision this patch is based on. + + +GETTING A RECENT XINE +~~~~~~~~~~~~~~~~~~~~~ +To compile and use this plugin, you must get a recent Hg/CVS version of xine. +When you are going to use xine-lib-1.2 then you can most often find binary +packages for your distribution. Additionally you'll have to install a suitable +devel-package too. After installing these packages, just read on in section +PATCHING VDR. + +To retrieve xine via Hg/CVS, please go to the project homepage of xine (which +is http://xine.sf.net). On the main page, look for downloads and then for CVS +or Mercurial (Hg) respectively. +Follow the instructions on this page and checkout module "xine-lib". You also +need a recent version of a GUI for xine, so checkout module "xine-ui". I've +checked out both modules and they reside in directory "/soft/src/xine-lib" +and "/soft/src/xine-ui" respectively. +My Hg/CVS commands were: + + hg clone http://hg.debian.org/hg/xine-lib/xine-lib + + cvs -d:pserver:anonymous@xine.cvs.sourceforge.net:/cvsroot/xine login + cvs -z9 -d:pserver:anonymous@xine.cvs.sourceforge.net:/cvsroot/xine co xine-ui + +NOTE: As space permits you'll also find archives of both modules on my homepage + which you might use if you don't have access to CVS servers. Just choose + the most recent archives for the most recent vdr-xine plugin. + +NOTE: The above command and my archives on the homepage supply xine-lib-1.1.x, + which needs to be patched as mentioned below. If you like, you may want + to try xine-lib-1.2, which contains already everything regarding vdr-xine + and therefore doesn't need to be patched. + + +PATCHING XINE'S SOURCE +~~~~~~~~~~~~~~~~~~~~~~ +Next, you have to extend xine-lib to support a media resource location (mrl) +type named "vdr:". Change to the parent directory of module "xine-lib", e. g. +in my case "/soft/src". Then apply the patches supplied with this distribution +(in case you are using xine-lib-1.2, there is no need for patching). + +The patches will add new source files to xine-lib's source tree and will add +keybindings to xine-ui for supporting my plugin's remote functionality. I use +the following commands for patching: + + patch -dxine-lib -p1 < /soft/src/VDR/PLUGINS/src/xine/patches/xine-lib.patch + patch -dxine-ui -p1 < /soft/src/VDR/PLUGINS/src/xine/patches/xine-ui.patch + +NOTE: It might well be that some of the patch files are empty if my changes + have been ported back into xine's CVS repository. But I didn't want to + remove those empty files as it might break some build scripts which + people created to build xine. + + +BUILDING XINE +~~~~~~~~~~~~~ +Now, it's time to build xine-lib. Change back to the directory of module +"xine-lib", e. g. "/soft/src/xine-lib", and run "./autogen.sh". + +Please don't call "configure" directly at this stage, even if you don't use +the CVS source. Doing so will miss necessary modifications to the Makefiles +and finally lead to not building the new plugins! + +"./autogen.sh" will need some additional tools which must have at least a +certain minimum version to successfully build xine. If you look into the +head of "./autogen.sh" then you'll find the following information: + + # Minimum value required to build + WANT_AUTOMAKE_1_8=1 export WANT_AUTOMAKE_1_8 + WANT_AUTOMAKE=1.8 export WANT_AUTOMAKE + AUTOMAKE_MIN=1.8.0 + AUTOCONF_MIN=2.59 + LIBTOOL_MIN=1.4.0 + +On my openSUSE 11.1 I use the following versions: + + automake-1.10.1-4.286 + autoconf-2.63-1.136 + libtool-2.2.6-1.35 + +If you have trouble running "./autogen.sh" respectively the hereby called +configure please consider upgrading the above mentioned tools. + +Calling "./autogen.sh" will create all the files which configure needs, and +will finally run configure. If you need special options for configure, you can +specify them as command arguments to autogen.sh. + +If you intend to use vdr-xine for watching H.264 channels, I highly recommend +to install a recent FFmpeg and to add the switch "--with-external-ffmpeg" when +compiling xine-lib (it's the default since xine-lib-1.1.15). + +When building xine-ui I suggest the following configure options: + + --enable-vdr-keys + + Adds VDR's commands to xine's keybinding dialog. You're then able to assign + keys in xine to VDR's commands and therefore control VDR by pressing the + appropriate keys in xine's window. + +For example I run the following command for building xine-ui: + + ./autogen.sh --prefix=/soft/xine-ui-cvs --enable-vdr-keys + +As xine supports a lot of media, it also requires a lot of libraries to compile +all modules. Please see the file "config.log" and the xine documentation to get +all the required components for the plugins you want to build. After configure +has been run (automatically from autogen.sh), the commands "make" and "make +install" should provide you with a recent version of module "xine-lib". + +After that, you can go to the directory of module "xine-ui" and repeat the +steps for building the standard GUI for xine. + + +TESTING XINE +~~~~~~~~~~~~ +Please test the newly compiled xine with a sample MPEG file, to see whether it +works. Be careful, that not any previously versions of xine respectively +plugins get loaded. Use the option "--verbose=2" and verify that the output +of xine contains a line which indicates loading "xineplug_vdr.so". + + +PATCHING VDR +~~~~~~~~~~~~ +There may be further patches available on my homepage that enhance VDR for best +cooperation with vdr-xine. For further details about the patches please have a +look into the corresponding README file on my homepage. I highly recommend to +apply at least some of them. + + +MAKEFILE SETTINGS +~~~~~~~~~~~~~~~~~ +Before you go on to compile vdr-xine, please make sure that you've installed +xine-lib already. Otherwise you'll get a lot of errors due to missing header +files respectively missing structure members if there are already old versions +of some header files available! + +Then please have a look at my plugin's "Makefile" and locate "INCLUDES", +"VDR_XINE_FIFO_DIR" and "VDR_XINE_SET_VIDEO_WINDOW". + +As the plugin depends on xine's data structures of it's companion, it is +necessary to include xine's header files. For this to work, INCLUDES must +contain the include path of xine's header files. Therefore, pkg-config is +asked to supply the path as it was set when xine was compiled. Make sure +that pkg-config reports the include directory of your previously installed +xine-lib by running the following command in a shell: + + pkg-config --cflags libxine + +Depending on your setup, you may need to adapt the environment variable +PKG_CONFIG_PATH to change the order in which pkgconfig directories are +queried to achive the goal. One reason for getting a wrong directory +reported is that a xine-lib-devel package is still installed. Simply +uninstall it and try building vdr-xine again. + +The plugin will create its fifos (which are used for data exchange with xine) +below directory "VDR_XINE_FIFO_DIR" (e. g. "/tmp/vdr-xine"). It's up to +you to create and maintain any parent directories of the directory you specify +here (in this case "/tmp"); the plugin will itself try to create and remove +the final directory (in this case "vdr-xine"). So you may need to create the +vdr-xine directy too, if the plugin is not allowed to do it on its own at +runtime. + +NOTE: xine's autoscan button "VDR" currently expects "VDR_XINE_FIFO_DIR" to + be set to directory "/tmp/vdr-xine"! + +If you are using the yaepg plugin and have patched VDR to support it, then you +might also enable VDR_XINE_SET_VIDEO_WINDOW. vdr-xine will then access the new +OSD member vidWin to extract position and size of the video window and ask xine +to position the video accordingly within the original frame. + + +BUILDING VDR-XINE +~~~~~~~~~~~~~~~~~ +Just go to VDR's source directory and type "make plugins". This should compile +my plugin and "xineplayer". For more information about xineplayer please have +a look into MANUAL. + + +XINE VDR VERSION MISMATCH +~~~~~~~~~~~~~~~~~~~~~~~~~ +In the case you've got a compilation error which asked you to read this +section, please try the following to solve this issue. Otherwise skip this +section. + +- Make sure that the above mentioned pkg-config command shows you the + correct location where xine-lib's include files reside. +- Make sure that there exists xine/vdr.h below this directory. In the + case that it does not exist and you are building xine-lib-1.1.x please + check whether you've applied the xine-lib.patch as mentioned above. +- When the file exists and you still get this error then you're most likely + trying to mix different versions of vdr-xine and xine-lib. Make sure that + you've installed a matching version of xine-lib. You may also need to + upgrade vdr-xine to use a recent version of xine-lib. +- Try to clean the build if the error persists after any changes like that: + make plugins-clean + + +TESTING VDR-XINE +~~~~~~~~~~~~~~~~ +For a first run, go to the VDR directory (e. g. "/soft/src/VDR") and issue the +following command: + + ./runvdr "'-Pxine -r'" + +This should start VDR and have it load my plugin, but if this is the first time +that you use my plugin it will also abort immediately with an error message +like this: + + vdr-xine: error: couldn't open '/video/plugins/xine/noSignal4x3.mpg'! + vdr-xine: error: couldn't open '/video/plugins/xine/noSignal.mpg'! + vdr-xine: error: couldn't open '/video/plugins/xine/noSignal16x9.mpg'! + vdr-xine: error: couldn't open '/video/plugins/xine/noSignal.mpg'! + +The exact path depends on VDR's directory for config files (typically '/video' +but see VDR's manual). To get vdr-xine working just create the directory +"xine" below "plugins" and copy the contents of the directory "data" (located +in my plugin's source directory) into the new directory. After that retry +starting VDR again as mentioned above. + +If no errors are reported, six fifos should now exist below "VDR_XINE_FIFO_DIR" +(e. g. "/tmp/vdr-xine"), named "stream", "stream.control", "stream.result" +and "stream.event" as well as "external.control" and "external.result". All of +them have a size of 0 bytes. + +At any time, you can use xine to connect to the above mentioned fifo "stream" +(while the "external" fifos are used by xineplayer). The MRL may look like +this, depending on the "VDR_XINE_FIFO_DIR" at compile time: + + vdr://tmp/vdr-xine/stream + +So either type something like + + xine vdr://tmp/vdr-xine/stream + +or just hit the autoscan button "VDR" in xine. + +You should now see a beautiful "vdr-xine" logo on screen! + + +USING BUDGET CARDS +~~~~~~~~~~~~~~~~~~ +If your system only has budget DVB cards (i. e. cards that don't supply an +MPEG decoder and rely on the CPU to decode the MPEG stream), then loading +vdr-xine will supply VDR with a "software device" with a software MPEG decoder. +VDR will choose this device as output device by default. + +Depending on your previous usage of VDR, it might well be that VDR has entered +remote learning mode and you may see an OSD which invites you to press keys. +Please see the VDR manual for more information. + +Where VDR's configuration suits your receiving equipment, it is most likely +that VDR enters transfer mode and you'll see live TV in xine! + + +REMOTE LEARNING MODE +~~~~~~~~~~~~~~~~~~~~ +vdr-xine's internal remote (enabled by the "-r" switch) behaves different to +VDR's other remotes as there is no key learning necessary within VDR as the +keys have to be assinged in xine's keybinding dialog. + +NOTE: Please don't worry about entries like the following in VDR's logfile + as there is currently no other way to skip key learning for a still + unknown remote: "ERROR: remote control XineRemote not ready!" + +Any other remote (e. g. VDR's built in keyboard remote) will make VDR start in +remote learning mode if there are still no keys learnt in remote.conf for that +kind of remote or when no remote.conf exists so far. + +To successfully enter remote learning mode, you'll have to connect xine to VDR +immediately after starting VDR. Otherwise you'll miss VDR's first prompt (which +is visible for about 10 seconds per remote) and VDR will either abort learning +mode or go on to learn the next remote. + +NOTE: It may take about 10 seconds after pressing a remote key in "Phase 1" of + remote learning mode until VDR goes on to "Phase 2". + + +USING FULL FEATURED CARDS +~~~~~~~~~~~~~~~~~~~~~~~~~ +If your system is equipped with a full featured card (i. e. a card that has +its own MPEG decoder), you'll see in xine nothing more than "vdr-xine". This +is because VDR has chosen the hardware MPEG decoder over the software MPEG +decoder, so the output device is your full featured card. To get the output to +xine, you'll have to go to VDR's OSD setup menu and choose the highest device +available, which should be vdr-xine's software device. + + +ADDITIONAL SOFTWARE +~~~~~~~~~~~~~~~~~~~ +To be able to have VDR grab the image currently displayed in xine, you'll +need the programs "y4mscaler", "y4mtoppm" and "pnmtojpeg". + +On my system I use the following versions: + + y4mscaler 9.0 + y4mtoppm part of mjpegtools-1.8.0 + pnmtojpeg part of netpbm-10.26.44-98.16 + +By default, vdr-xine assumes to find these utilities on your PATH. But you can +specify the absolute path to them by setting the Makefile variables +VDR_XINE_Y4MSCALER, VDR_XINE_Y4MTOPPM and VDR_XINE_PNMTOJPEG. + + +LAST BUT NOT LEAST +~~~~~~~~~~~~~~~~~~ +After you can see VDR's OSD in xine, you've done everything well. Now go on +and setup xine and my plugin to suit your needs. See the file "MANUAL" for more +information, e. g. how to bind keys in xine to VDR commands. + +As mentioned above you've copied some data files to ".../plugins/xine". Some of +them were named "noSignal*x*.mpg" and is responsible for displaying "vdr-xine" +on screen. After you've setup everything properly you might be annoyed of this +message whenever you switch channels or when listening to radio stations. + +Therefore I've supplied additional files "noSignal*x*-completelyBlack.mpg" +which show a completely black screen instead. If you prefer a black screen +instead of "vdr-xine" just move "noSignal*x*-completelyBlack.mpg" over the +files named "noSignal*x*.mpg". + +Alternatively you may try files like "noSignal4x3-smallTextAtBottom.mpg" or +"noSignal4x3-old.mpg". + +BTW: the ...4x3... or ...16x9... indicate which aspect ratio should be assumed + for the files. vdr-xine tries to load both specific files and falls back + to the file name "noSignal.mpg" when a specific file does not exist. + vdr-xine will then choose the file to display depending on VDR's DVB setup + option video format. + +In case none of the files suits, you may create your own files. Just provide +a suitable PNG image named noSignal*x*.png and run mkNoSignal.sh. Keep in mind +that the PNG image must be true color even if it just shows grey content and +it should be of a size which matches your TV channels e. g. something like +720x576 or 704x576. Furthermore it shouldn't be a too complex image as the +resulting MPG files must not exceed 64 kB. + +BTW: mkNoSignal.sh requires the following tools on your path: + + pngtopnm part of netpbm-10.26.44-98.16 + ppmtoy4m part of mjpegtools-1.8.0 + mpeg2enc part of mjpegtools-1.8.0 + +Have fun! + @@ -0,0 +1,471 @@ + +Please read the file "INSTALL" for getting the stuff working a first time :-) + + +SETUP-MENU +========== + +Live-TV SD video buffer [frames]: 4 +Live-TV HD video buffer [frames]: 4 +Live-TV audio buffer [frames]: 4 + +Playing live-TV requires a buffer for having data ready when xine needs it for +decoding. Without such a buffer or when it is not large enough, replaying live +TV may not be fluently and may degrade into a slide show without sound. On the +other hand, as buffering takes place before replaying, a too large buffer +slows down zapping as it takes longer before replaying is started. So one may +need to play with this value to find a suitable setting. The buffer size is +specified in video frames, where 25 video frames make up a buffer which can +hold one second of audio and video. Please note that this buffer is provided +by VDR and xine so a too large setting may cause an overflow (check VDR's +logfile for buffer usage). It is therefore recommended to increase xine's +input buffer settings in ~/.xine/config. See engine.buffers.video_num_buffers +and maybe engine.buffers.audio_num_buffers. A simple but stupid rule is to +increase buffers by multipling the default numbers by 10 -- some HD channels +require at least 1500 video buffers. As mentioned earlier, choosing a suitable +Live-TV buffer is a compromise, which is easier to achieve when there can be +separate values for different services like SD / HD video or radio (audio). + + +Buffer hysteresis [frames]: 4 + +Buffer monitoring is a feature of vdr-xine which tries to dynamically increase +the buffer for certain channels on demand, i. e. whenever the buffer size +drops below the configured value of "Live-TV buffer" frames. As a result, a +new buffer will be established which is of size "Live-TV buffer + hysteresis" +frames, and in the case, where this buffer is still not large enough, vdr-xine +will internally increase hysteresis each time by one frame so that finally a +buffer is established which perfectly fits for the current channel. + + +Buffer monitoring duration [s]: 10 + +Typically, the above mentioned buffer monitoring is only necessary for a +certain amount of time after switching the channel, because once the buffer +is established, it should stay constant as the amount of data put into the +buffer by VDR and the amout of data taken from the buffer by xine should be +equal. A value of 0 disables buffer monitoring. + + +Buffer monitoring mode: Once + +Lets you choose how buffer monitoring is applied. + +* Once + After the above mentioned time, buffer monitoring will be bypassed (which + reduces CPU and memory load) until a channel switch or audio track selection + occurs. + +* Continuous + Buffer monitoring will never be bypassed. After the above mentioned time the + internal hysteresis value will be reset to the configured one, to be ready + for the next buffering cycle which starts when the buffer size falls below + the above mentioned "Live-TV buffer" value. This mode is useful for channels + which degrade into a slide show after a certain amout of time. + + +OSD display mode: Blend scaled AUTO + +Lets you choose among several processing options for VDR's OSD. + +* X11 overlay + Tells xine to use a method for displaying the OSD that is independent of the + stream's video resolution. In this so called "unscaled" mode, xine uses a + X11 window to overlay the OSD on the video window. The advantage of mapping + a single OSD pixel to a single pixel on screen has the disadvantage of not + being able to support semi-transparent areas which appear totally opaque in + this mode. + + NOTE: You won't see any OSD in this mode if X11 is not available! + +* Blend clipped +* Blend scaled LQ +* Blend scaled HQ +* Blend scaled SHQ +* Blend scaled AUTO + For these modes xine uses the CPU to blend the OSD into each video frame. As + the result depends on the stream's video resolution you may choose among + several modes which require a different amount of CPU time. + The first mode simply cuts off all parts of the OSD that do not fit into the + video frame. If for example an OSD of size 720x576 is to be blended into a + frame of size 480x576 (e. g. VIVA broadcasts at this resolution) then almost + one halve of the OSD will be dropped at the right and the OSD will be quite + stretched in horizontal direction. + All other modes scale the OSD to fit into the video frame. The difference + among them is the scaling quality (Low, High and Super High) which also + leads to increasing CPU load and slows down e. g. navigation in the channels + list, etc. + The last mode automatically chooses between HQ and SHQ depending on the + stream's video resolution. SHQ will be chosen if either width or height + are below 360x288. + + NOTE: Blend scaled is only implemented for VDR 1.3.x (1.3.7 and higher). + + +OSD gamma correction [ 123 => 1.23 ]: 123 + +When OSD scaling is performed then multiple pixels (or parts of them for SHQ) +have to be combined into a single one. During this process a so called gamma +correction is applied in order to give the resulting pixels the correct visual +representation of the original ones. +You may adjust this correction within the range 1.00 to 2.50 (by entering 100 +to 250 respectively) to get the best visual representation on your monitor. +Changing this value is most noticeable in SHQ scaling mode so you need to +watch a channel that doesn't broadcast at 720x576 in order to activate OSD +scaling and to be able to see any change concerning gamma correction. + + +OSD extent X: 720 ***** REQUIRES VDR >= 1.7.7 ***** +OSD extent Y: 576 ***** REQUIRES VDR >= 1.7.7 ***** + +For a crisp OSD on HD displays increase these values to match your displays +resolution. +Valid extents are in the range from 320 x 240 up to 1920 x 1080. + + +4:3 image zoom X [%]: 100 +4:3 image zoom Y [%]: 100 +16:9 image zoom X [%]: 100 +16:9 image zoom Y [%]: 100 + +These options may be useful to stretch the video image to fill the screen. +A value of 133 for example will remove the black borders when showing a 4:3 +image on a 16:9 screen. The drawback is that a part of the images is cut +away on top and bottom for example. So it may be useful to have different +values for X and Y (e. g. 133 and 115). + + +Audio mode: Dolby on ***** OBSOLETE FOR VDR >= 1.3.18 ***** + +With this option you can control feeding dolby audio data to xine. You may +want to use this option if you don't have the necessary replay equipment +connected to your computer. In that way you can force xine to use a +differently coded audio source among the supplied e. g. mp2 or pcm. + + +Control xine's volume: Yes (by hardware) + +Allows you to control whether xine shall honor VDR's set volume requests. You +might need this if you have a special setup (e. g. external audio decoder or +amplifier) where changing the volume in xine might mute external audio: + +* No + xine isn't instructed to change the volume. +* Yes (by hardware) + xine will change the volume by changing the hardware mixer (won't work + when using a digital audio output). +* Yes (by software) + xine will change the volume by using it's internal software mixer (should + work even when using a digital audio output). + + +Muting: Simulate + +Lets you choose among several options how xine shall process VDR's muting +requests: + +* Ignore + Muting respectively unmuting requests will be ignored. +* Execute + Muting respectively unmuting requests will be executed. +* Simulate + Muting is simulated by setting volume to 0. Unmuting is simulated by + restoring the previous volume. + + NOTE: This happens even if "Control xine's volume" is set to "No"! + + +Get primary device when xine connects: Yes ***** REQUIRES VDR >= 1.3.32 ***** + +This option is especially useful for owners of full featured DVB cards which +usually run the OSD on a full featured card (i. e. the full featured card is +the primary device). +With this option set to Yes, vdr-xine automatically makes itself the primary +device while xine is connected to it. In that way the OSD and live TV are +automatically available via vdr-xine without the need to manually switch the +primary device via remote control nor SVDRP interface. + + +Support semi transparent colors: Yes + +Depending on the currently broadcast image the displayed OSD might be hardly +readable when the OSD is blended semi transparently into the TV image. +If this option is set to No, vdr-xine simply ignores the semi-transparent +component of the color and therefore makes such colors opaque which typically +makes the OSD easier to read. + + +Connection interacts with EIT scanner: No + +This option may be useful for users of single card systems. When xine connects +to vdr-xine, a currently running EPG scan will be interrupted to get into live +TV mode immediately. On the other hand, when VDR starts an EPG scan, vdr-xine +will shutdown the connection to xine. + + +COMMAND LINE ARGS +================= + +There are currently two optional arguments. + + -r Enable remote control. + +This argument enables Simon Truss' patch which allows controlling VDR by +pressing buttons in xine. It will also allow control from any other suitably +patched front end. + + -iN Instance number for FIFO directory + +If you want to run two instances of vdr-xine (in different VDR processes) on +the same computer then you have to use a unique FIFO dir for each instance. +For example "-i3" will append "3" to the FIFO_DIR given in Makefile. The MRL +will then have to be like that: "vdr://tmp/vdr-xine3/stream#demux:mpeg_pes". + + -q Suppress debug messages on console + +This option is useful if VDR's console is to be used for other output e. g. +for the OSD implementation of VDR's skin 'curses'. + + -s Switch to curses skin while xine is not connected + +Use this switch if it is useful to control VDR via it's controlling terminal +while xine is not connected to vdr-xine. Requires VDR >= 1.3.20. + + -XN default 'SizeX' for GRAB command (720, 1..4096) + -YN default 'SizeY' for GRAB command (576, 1..4096) + +With these switches you may change the default image size for the grabbing +the current video frame (see VDR's SVDRP command GRAB for details). + + -p[N] use socket connections on port N (18701) + -bIP ip address to bind for socket connections (see -p) + +These options control whether vdr-xine shall use sockets instead of fifos, +which allows you to run xine on a different computer than vdr-xine. To enable +socket connections, supply the option -p. This will use the default TCP ports +18701, 18702, 18703 and 18704. You may optionally provide a different base +port number and vdr-xine will use the next three ports too. +By default, vdr-xine will listen on all network interfaces. If you want to +limit connections to a certain inferface, specify option -b with the IP +address this interface. +The MRL for xine looks then like that (:port is optional): + netvdr://host:port + + +XINE KEYBINDINGS +================ + +To make use of the remote control feature, you'll have to assign keys in xine +to VDR's commands. Therefore, you'll find 36 keybindings in xine's key binding +editor, named 'VDR ...', which control the specified action in VDR. Besides +those, the following entries are also used for VDR: + + 'enter the number 0' .. 'enter the number 9' + 'jump to media menu' + 'menu navigate up' + 'menu navigate down' + 'menu navigate left' + 'menu navigate right' + 'menu select' + 'jump to next chapter' + 'jump to previous chapter' + + +GXINE KEYBINDINGS +================= + +And similarly for gxine... + +You'll find several VDR-specific keybindings in gxine's key binding editor: + + Used for VDR VDR-specific + + "Play" Menu bindings "Red" + "Pause" Number key bindings "Green" + "Stop" "Volume +" "Yellow" + "Up" "Volume -" "Blue" + "Down" "Mute" "Record" + "Left" "Power" + "Right" "Select/OK" "Back" + "Rewind / Back 1 min" "Audio" + "Fast forward / Forward 1 min" + +You'll notice that the menu bindings have their VDR functions in [brackets]. + +If not all of these bindings are present, try "Add new default bindings" +from the Reload menu; if they still aren't, you're running an older version +of gxine. + +The volume control bindings assume that VDR is passing volume control events +back to gxine - in the xine plugin's configuration, you need + Control xine's volume Yes + Muting Execute +for these bindings to work. + + +XINE-LIB CONFIG +=============== + +This applies whether you're using xine, gxine or some other xine-lib front +end. + +xine uses large buffers to gain smooth playback. The drawback is that VDR puts +a lot of data into xine's buffers and therefore VDR's progress indicator is +way ahead of the position at which xine is currently playing. For a recording +of a radio channel, xine's default buffers will cause an offset of about 16 +seconds. But this can easily be improved (with almost no noticeable effects) +to less than 2 seconds by adjusting xine's number of audio buffers. Just edit +your xine config "~/.xine/config" and add or change the following entry: + + engine.buffers.audio_num_buffers:4 + +The value '4' is the smallest possible value. You may increase it, in case that +you experience noticeable distortions in audio playback. Another interesting +option might be the following: + + audio.synchronization.av_sync_method:resample + +It should totally avoid distortions by adjusting audio data to fill any gaps. +But it's only usable if your amplifier is not connected via SPDIF. + +Another useful option on less powerful machines is the following: + + video.output.disable_exact_alphablend:1 + +The result will be that a less exact algorithm is used for blending the OSD +into each video frame and thus CPU time is saved. + + + +XINE COMMAND LINE OPTIONS +========================= + +This section is not intended as a replacement for xine's documentation, but it +may be useful to have a starting point for further reading. So, some useful +command line options are listed below. + +Options for xine (X11 required): + + -V vdpau (use hardware mpeg2/h264 video decoding acceleration) + -V xxmc (use hardware mpeg2 video decoding acceleration) + -V xv (use video overlay -- the usual case) + -V xshm (should always provide a picture) + -V vidix (for normal TV on my Matrox G550) + + -A alsa (use alsa sound system) + + --post vdr_video (highly recommended for correct and immediate OSD + scaling especially when using "xineplayer". It + further enables video scaling and positioning as + used within yaepg as well as applying the preconfigured + zoom values for 16:9 and 4:3 content) + + --post vdr_audio (highly recommended for selecting left / right stereo + channel) + + -D (enable selected deinterlacer) + -Dtvtime:method=Greedy2Frame,cheap_mode=0,pulldown=0,use_progressive_frame_flag=1 + (use specified deinterlacer) + -L (disable LIRC support) + +Options for gxine (X11 required): + + -V vdpau (use hardware mpeg2/h264 video decoding acceleration) + -V xxmc (use hardware mpeg2 video decoding acceleration) + -V xv (use video overlay -- the usual case) + -V xshm (should always provide a picture) + -V vidix (for normal TV on my Matrox G550) + + -A alsa (use alsa sound system) + + Other options must (as of 0.4.4) be configured from within gxine. + +Options for fbxine (no X11 required): + + -V vidixfb (for normal TV on my Matrox G550) + -V fb (for watching HDTV) + -V dxr3 (to use a DXR3/Hollywood+ hardware MPEG decoder) + + -A alsa (use alsa sound system) + + --post vdr_video (highly recommended for correct and immediate OSD + scaling especially when using "xineplayer". It + further enables video scaling and positioning as + used within yaepg as well as applying the preconfigured + zoom values for 16:9 and 4:3 content) + + --post vdr_audio (highly recommended for selecting left / right stereo + channel) + + -D (enable selected deinterlacer) + -Dtvtime:method=Greedy2Frame,cheap_mode=0,pulldown=0,use_progressive_frame_flag=1 + (use specified deinterlacer) + -L (disable LIRC support) + +The default deinterlacer doesn't take too much CPU time, but on the other hand +it doesn't deinterlace in all situations (e. g. when there is only a small area +with significant movement on the screen). + +The specified deinterlacer does a good job, but requires much CPU time. + +If you don't like VDR's OSD to be stretched when playing 16:9 material you +might also use xine's "expand" post processing plugin. It simply puts the +16:9 images into an 4:3 image (adding a black bar on top and bottom) and +therefore the OSD will keep it's aspect ratio. To make use of the plugin +add the following options in this order on (fb)xine's command line: + + xine ... --post expand --post vdr_video ... + +The expand plugin now also supports a feature called "centre cut out" which +crops away the black bars around the image when 4:3 material is broadcast in +a 16:9 stream and displayed on a 4:3 monitor. The command line looks then like +the following: + + xine ... --post expand:centre_cut_out_mode=1 --post vdr_video ... + +If you want to listen to mono audio streams in stereo, I'd suggest to use +the xine's upmix_mono audio post plugin like that: + + xine ... --post vdr_audio --post upmix_mono ... + + + +XINEPLAYER +========== +xineplayer is a companion of vdr-xine and is used to get the beloved mplayer +plugin working with vdr-xine. I. e. you'll be able to replay DivX movies +directly through xine without the need for CPU expensive recoding. And you'll +still be able to continue using VDR's OSD while the external file is playing. + +To get it working just install the mplayer plugin. Then edit it's "mplayer.sh" +and replace + + # where to find mplayer + MPLAYER="mplayer" + +with + + # where to find mplayer + MPLAYER="xineplayer" + +and now you'll only have to make sure that xineplayer is found by your shell. +xineplayer was built in vdr-xine's source directory so you'll either have to +copy it to a directory which is contained in your environment variable PATH or +just enter the absolute path to xineplayer into mplayer.sh as mentioned above. +That's it. + +NOTE: xineplayer is still under development and currently only supports + mplayer plugin's TRADITIONAL mode. Furthermore it ignores any parameter + given on the command line besides the last one and expects this to be a + MRL recognizable by xine (e. g. a filename). If xine doesn't know how + to play the given MRL you'll only see an error message on xine's console. + +As vdr-xine supports an instance number to create an unique FIFO directory it +will also necessary to tell this number to xineplayer to have it control the +right instance of vdr-xine. xineplayer's command line looks like that: + + xineplayer [ --vdr-xine-instance=N ] [ options ] mrl + +NOTE: "--vdr-xine-instance" must be given as the first argument as it might + otherwise collide with further options originally intended for mplayer. + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..674f4fb --- /dev/null +++ b/Makefile @@ -0,0 +1,159 @@ +# +# Makefile for a Video Disk Recorder plugin +# +# $Id$ + +# The official name of this plugin. +# This name will be used in the '-P...' option of VDR to load the plugin. +# By default the main source file also carries this name. +# IMPORTANT: the presence of this macro is important for the Make.config +# file. So it must be defined, even if it is not used here! +# +PLUGIN = xine + +### The version number of this plugin (taken from the main source file): + +VERSION = $(shell grep 'static const char \*VERSION *=' $(PLUGIN).c | awk '{ print $$6 }' | sed -e 's/[";]//g') + +### The C++ compiler and options: + +CXX ?= g++ +#CXX = /soft/gcc-4.1-20060113/bin/g++ -I/soft/include -L/soft/lib +CXXFLAGS ?= -g -O3 -Wall -Woverloaded-virtual +#CXXFLAGS ?= -g3 -O0 -Wall -Woverloaded-virtual +#CXXFLAGS ?= -g3 -O0 -Wall -Woverloaded-virtual -Wformat=2 -Wextra + +### The directory environment: + +VDRDIR = ../../.. +LIBDIR = ../../lib +TMPDIR = /tmp + +### Make sure that necessary options are included: + +-include $(VDRDIR)/Make.global + +### Allow user defined options to overwrite defaults: + +-include $(VDRDIR)/Make.config + +INCLUDES += `pkg-config --cflags libxine` + +# where to create fifos (xine expects them at /tmp/vdr-xine) +VDR_XINE_FIFO_DIR ?= /tmp/vdr-xine + +# can be used to detect inefficient OSD drawing +# 0 - do not verify whether the dirty area of a bitmap is really dirty +# 1 - verify that bitmap is really dirty and print a message on console when it is not +# 2 - additionally fail with a core dump +VDR_XINE_VERIFY_BITMAP_DIRTY ?= 0 + +# enable to fully support yaepg plugin +#VDR_XINE_SET_VIDEO_WINDOW = 1 + +# where are these utilities for image grabbing? (default: anywhere on your PATH) +#VDR_XINE_Y4MSCALER = /usr/bin/y4mscaler +#VDR_XINE_Y4MTOPPM = /usr/bin/y4mtoppm +#VDR_XINE_PNMTOJPEG = /usr/bin/pnmtojpeg + +### The version number of VDR's plugin API (taken from VDR's "config.h"): + +APIVERSION = $(shell (sed -ne '/define APIVERSION/s/^.*"\(.*\)".*$$/\1/p' $(VDRDIR)/config.h ; sed -ne '/define VDRVERSION/s/^.*"\(.*\)".*$$/\1/p' $(VDRDIR)/config.h) | sed -ne 1p) + +### The name of the distribution archive: + +ARCHIVE = $(PLUGIN)-$(VERSION) +PACKAGE = vdr-$(ARCHIVE) + +### Includes and Defines (add further entries here): + +INCLUDES += -I$(VDRDIR)/include + +DEFINES += -D_GNU_SOURCE -DPLUGIN_NAME_I18N='"$(PLUGIN)"' + +DEFINES += -DFIFO_DIR=\"$(VDR_XINE_FIFO_DIR)\" + +DEFINES += -DVERIFY_BITMAP_DIRTY=$(VDR_XINE_VERIFY_BITMAP_DIRTY) + +ifdef VDR_XINE_SET_VIDEO_WINDOW +DEFINES += -DSET_VIDEO_WINDOW +endif + +ifdef VDR_XINE_Y4MSCALER +DEFINES += -DY4MSCALER=\"$(VDR_XINE_Y4MSCALER)\" +endif + +ifdef VDR_XINE_Y4MTOPPM +DEFINES += -DY4MTOPPM=\"$(VDR_XINE_Y4MTOPPM)\" +endif + +ifdef VDR_XINE_PNMTOJPEG +DEFINES += -DPNMTOJPEG=\"$(VDR_XINE_PNMTOJPEG)\" +endif + +### The object files (add further files here): + +OBJS = $(PLUGIN).o xineDevice.o xineLib.o xineOsd.o xineSettings.o xineSetupPage.o xineRemote.o xineExternal.o vdr172remux.o vdr172h264parser.o + +### The main target: + +all: libvdr-$(PLUGIN).so i18n xineplayer + +### Implicit rules: + +%.o: %.c Makefile + $(CXX) $(CXXFLAGS) -c $(DEFINES) $(INCLUDES) $< + +### Dependencies: + +MAKEDEP = $(CXX) -MM -MG +DEPFILE = .dependencies +$(DEPFILE): Makefile + @$(MAKEDEP) $(DEFINES) $(INCLUDES) $(OBJS:%.o=%.c) xineplayer.c > $@ + +-include $(DEPFILE) + +### Internationalization (I18N): + +PODIR = po +LOCALEDIR = $(VDRDIR)/locale +I18Npo = $(wildcard $(PODIR)/*.po) +I18Nmsgs = $(addprefix $(LOCALEDIR)/, $(addsuffix /LC_MESSAGES/vdr-$(PLUGIN).mo, $(notdir $(foreach file, $(I18Npo), $(basename $(file)))))) +I18Npot = $(PODIR)/$(PLUGIN).pot + +%.mo: %.po + msgfmt -c -o $@ $< + +$(I18Npot): $(wildcard *.c) + xgettext -C -cTRANSLATORS --no-wrap --no-location -k -ktr -ktrNOOP --package-name=vdr-$(PLUGIN) --package-version=$(VERSION) --msgid-bugs-address='Reinhard Nissl <rnissl@gmx.de>' -o $@ $^ + +%.po: $(I18Npot) + msgmerge -U --no-wrap --no-location --backup=none -q $@ $< + @touch $@ + +$(I18Nmsgs): $(LOCALEDIR)/%/LC_MESSAGES/vdr-$(PLUGIN).mo: $(PODIR)/%.mo + @mkdir -p $(dir $@) + cp $< $@ + +.PHONY: i18n +i18n: $(I18Nmsgs) $(I18Npot) + +### Targets: + +libvdr-$(PLUGIN).so: $(OBJS) Makefile + $(CXX) $(CXXFLAGS) $(LDFLAGS) -shared $(OBJS) -o $@ + @cp --remove-destination $@ $(LIBDIR)/$@.$(APIVERSION) + +xineplayer: xineplayer.o Makefile + $(CXX) $(CXXFLAGS) $(LDFLAGS) xineplayer.o -o $@ + +dist: $(I18Npo) clean + @-rm -rf $(TMPDIR)/$(ARCHIVE) + @mkdir $(TMPDIR)/$(ARCHIVE) + @cp -a * $(TMPDIR)/$(ARCHIVE) + @tar czf $(PACKAGE).tgz -C $(TMPDIR) $(ARCHIVE) + @-rm -rf $(TMPDIR)/$(ARCHIVE) + @echo Distribution package created as $(PACKAGE).tgz + +clean: + @-rm -f $(OBJS) $(DEPFILE) *.so *.tgz core* *~ $(PODIR)/*.mo $(PODIR)/*.pot xineplayer xineplayer.o @@ -0,0 +1,19 @@ +This is a "plugin" for the Video Disk Recorder (VDR). + +Written by: Reinhard Nißl <rnissl@gmx.de> + +Project's homepage: http://home.vr-web.de/~rnissl/ + +Latest version available at: http://home.vr-web.de/~rnissl/ + +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. +See the file COPYING for more information. + +Description: +Plugin for "software only" playback using xine. + +For more information see INSTALL. + diff --git a/data/mkNoSignal.sh b/data/mkNoSignal.sh new file mode 100755 index 0000000..add133d --- /dev/null +++ b/data/mkNoSignal.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +for inFile in noSignal4x3*.png ; do + outFile="$(basename "$inFile" .png).mpg" + cat "$inFile" \ + | pngtopnm \ + | ppmtoy4m -F 25:1 -A 4:3 -I p -r -S 420mpeg2 -v 2 -n 1 \ + | mpeg2enc -f 8 -a 2 -q 1 -n p -I 0 -T 120 -R 2 -g 10 -G 12 -o /dev/stdout \ + | cat >"$outFile" +done + +for inFile in noSignal16x9*.png ; do + outFile="$(basename "$inFile" .png).mpg" + cat "$inFile" \ + | pngtopnm \ + | ppmtoy4m -F 25:1 -A 16:9 -I p -r -S 420mpeg2 -v 2 -n 1 \ + | mpeg2enc -f 8 -a 3 -q 1 -n p -I 0 -T 120 -R 2 -g 10 -G 12 -o /dev/stdout \ + | cat >"$outFile" +done + diff --git a/data/noSignal16x9-completelyBlack.mpg b/data/noSignal16x9-completelyBlack.mpg Binary files differnew file mode 100644 index 0000000..b7fc48a --- /dev/null +++ b/data/noSignal16x9-completelyBlack.mpg diff --git a/data/noSignal16x9-completelyBlack.png b/data/noSignal16x9-completelyBlack.png Binary files differnew file mode 100644 index 0000000..929c032 --- /dev/null +++ b/data/noSignal16x9-completelyBlack.png diff --git a/data/noSignal16x9.mpg b/data/noSignal16x9.mpg Binary files differnew file mode 100644 index 0000000..87983d8 --- /dev/null +++ b/data/noSignal16x9.mpg diff --git a/data/noSignal16x9.png b/data/noSignal16x9.png Binary files differnew file mode 100644 index 0000000..198c699 --- /dev/null +++ b/data/noSignal16x9.png diff --git a/data/noSignal4x3-completelyBlack.mpg b/data/noSignal4x3-completelyBlack.mpg Binary files differnew file mode 100644 index 0000000..75adc88 --- /dev/null +++ b/data/noSignal4x3-completelyBlack.mpg diff --git a/data/noSignal4x3-completelyBlack.png b/data/noSignal4x3-completelyBlack.png Binary files differnew file mode 100644 index 0000000..929c032 --- /dev/null +++ b/data/noSignal4x3-completelyBlack.png diff --git a/data/noSignal4x3-old.mpg b/data/noSignal4x3-old.mpg Binary files differnew file mode 100644 index 0000000..ebf1ff1 --- /dev/null +++ b/data/noSignal4x3-old.mpg diff --git a/data/noSignal4x3-old.png b/data/noSignal4x3-old.png Binary files differnew file mode 100644 index 0000000..deaf7b1 --- /dev/null +++ b/data/noSignal4x3-old.png diff --git a/data/noSignal4x3-smallTextAtBottom.mpg b/data/noSignal4x3-smallTextAtBottom.mpg Binary files differnew file mode 100644 index 0000000..2370131 --- /dev/null +++ b/data/noSignal4x3-smallTextAtBottom.mpg diff --git a/data/noSignal4x3.mpg b/data/noSignal4x3.mpg Binary files differnew file mode 100644 index 0000000..b99720e --- /dev/null +++ b/data/noSignal4x3.mpg diff --git a/data/noSignal4x3.png b/data/noSignal4x3.png Binary files differnew file mode 100644 index 0000000..6948a8a --- /dev/null +++ b/data/noSignal4x3.png diff --git a/patches/xine-lib.patch b/patches/xine-lib.patch new file mode 100644 index 0000000..cfda09c --- /dev/null +++ b/patches/xine-lib.patch @@ -0,0 +1,4992 @@ +diff --git a/configure.ac b/configure.ac +--- a/configure.ac ++++ b/configure.ac +@@ -2801,6 +2801,7 @@ src/video_out/vidix/drivers/Makefile + src/video_out/vidix/drivers/Makefile + src/xine-utils/Makefile + src/xine-engine/Makefile ++src/vdr/Makefile + win32/Makefile + win32/include/Makefile]) + AC_CONFIG_COMMANDS([default],[[chmod +x ./misc/SlackBuild ./misc/build_rpms.sh ./misc/relchk.sh]],[[]]) +@@ -2843,7 +2844,7 @@ echo " - stdin_fifo - rtp" + echo " - stdin_fifo - rtp" + echo " - http - mms" + echo " - pnm - rtsp" +-echo " - dvb" ++echo " - dvb - vdr" + if test "x$external_dvdnav" = "xyes"; then + echo " - dvd (external libs)" + else +@@ -3048,6 +3049,7 @@ echo " - eq - eq2" + echo " - eq - eq2" + echo " - boxblur - denoise3d" + echo " - unsharp - tvtime" ++echo " - vdr" + echo " * SFX:" + echo " - goom - oscope" + echo " - fftscope - mosaico" +diff --git a/src/Makefile.am b/src/Makefile.am +--- a/src/Makefile.am ++++ b/src/Makefile.am +@@ -27,4 +27,5 @@ SUBDIRS = \ + libfaad \ + libmusepack \ + post \ +- combined ++ combined \ ++ vdr +diff --git a/src/vdr/Makefile.am b/src/vdr/Makefile.am +new file mode 100644 +--- /dev/null ++++ b/src/vdr/Makefile.am +@@ -0,0 +1,13 @@ ++include $(top_srcdir)/misc/Makefile.common ++ ++AM_CFLAGS = -D_LARGEFILE64_SOURCE ++ ++xineplug_LTLIBRARIES = \ ++ xineplug_vdr.la ++ ++xineplug_vdr_la_SOURCES = combined_vdr.c input_vdr.c post_vdr_video.c post_vdr_audio.c ++xineplug_vdr_la_LIBADD = $(XINE_LIB) ++xineplug_vdr_la_LDFLAGS = -avoid-version -module @IMPURE_TEXT_LDFLAGS@ ++ ++xineinclude_HEADERS = vdr.h ++noinst_HEADERS = combined_vdr.h +diff --git a/src/vdr/combined_vdr.c b/src/vdr/combined_vdr.c +new file mode 100644 +--- /dev/null ++++ b/src/vdr/combined_vdr.c +@@ -0,0 +1,44 @@ ++/* ++ * Copyright (C) 2000-2004 the xine project ++ * ++ * This file is part of xine, a free video player. ++ * ++ * xine 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. ++ * ++ * xine is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA ++ */ ++ ++/* ++ * plugins for VDR ++ */ ++ ++#include "xine_internal.h" ++#include "post.h" ++#include "combined_vdr.h" ++ ++ ++ ++static const post_info_t vdr_video_special_info = { XINE_POST_TYPE_VIDEO_FILTER }; ++static const post_info_t vdr_audio_special_info = { XINE_POST_TYPE_AUDIO_FILTER }; ++ ++/* exported plugin catalog entry */ ++const plugin_info_t xine_plugin_info[] EXPORTED = ++{ ++ /* type , API, "name" , version , special_info , init_function */ ++ { PLUGIN_INPUT, 17, "VDR" , XINE_VERSION_CODE, NULL , &vdr_input_init_plugin }, ++ { PLUGIN_POST , 9, "vdr" , XINE_VERSION_CODE, &vdr_video_special_info, &vdr_video_init_plugin }, ++ { PLUGIN_POST , 9, "vdr_video", XINE_VERSION_CODE, &vdr_video_special_info, &vdr_video_init_plugin }, ++ { PLUGIN_POST , 9, "vdr_audio", XINE_VERSION_CODE, &vdr_audio_special_info, &vdr_audio_init_plugin }, ++ { PLUGIN_NONE , 0, "" , 0 , NULL , NULL } ++}; ++ +diff --git a/src/vdr/combined_vdr.h b/src/vdr/combined_vdr.h +new file mode 100644 +--- /dev/null ++++ b/src/vdr/combined_vdr.h +@@ -0,0 +1,92 @@ ++/* ++ * Copyright (C) 2000-2004 the xine project ++ * ++ * This file is part of xine, a free video player. ++ * ++ * xine 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. ++ * ++ * xine is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA ++ */ ++ ++#ifndef __COMBINED_VDR_H ++#define __COMBINED_VDR_H ++ ++ ++ ++typedef struct vdr_set_video_window_data_s { ++ int32_t x; ++ int32_t y; ++ int32_t w; ++ int32_t h; ++ int32_t w_ref; ++ int32_t h_ref; ++ ++} vdr_set_video_window_data_t; ++ ++ ++ ++typedef struct vdr_frame_size_changed_data_s { ++ int32_t x; ++ int32_t y; ++ int32_t w; ++ int32_t h; ++ double r; ++ ++} vdr_frame_size_changed_data_t; ++ ++ ++ ++typedef struct vdr_select_audio_data_s { ++ uint8_t channels; ++ ++} vdr_select_audio_data_t; ++ ++ ++ ++inline static int vdr_is_vdr_stream(xine_stream_t *stream) ++{ ++ if (!stream ++ || !stream->input_plugin ++ || !stream->input_plugin->input_class) ++ { ++ return 0; ++ } ++ ++ { ++ input_class_t *input_class = stream->input_plugin->input_class; ++ ++ if (input_class->get_identifier) ++ { ++ const char *identifier = input_class->get_identifier(input_class); ++ if (identifier ++ && 0 == strcmp(identifier, "VDR")) ++ { ++ return 1; ++ } ++ } ++ } ++ ++ return 0; ++} ++ ++ ++ ++/* plugin class initialization function */ ++void *vdr_input_init_plugin(xine_t *xine, void *data); ++void *vdr_video_init_plugin(xine_t *xine, void *data); ++void *vdr_audio_init_plugin(xine_t *xine, void *data); ++ ++ ++ ++#endif /* __COMBINED_VDR_H */ ++ +diff --git a/src/vdr/input_vdr.c b/src/vdr/input_vdr.c +new file mode 100644 +--- /dev/null ++++ b/src/vdr/input_vdr.c +@@ -0,0 +1,2665 @@ ++/* ++ * Copyright (C) 2003-2004 the xine project ++ * ++ * This file is part of xine, a free video player. ++ * ++ * xine 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. ++ * ++ * xine is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA ++ */ ++ ++#ifdef HAVE_CONFIG_H ++#include "config.h" ++#endif ++ ++#include <stdio.h> ++#include <stdlib.h> ++#include <string.h> ++#include <fcntl.h> ++#include <unistd.h> ++#include <sys/stat.h> ++#include <sys/poll.h> ++#include <errno.h> ++#include <pthread.h> ++ ++#include <sys/socket.h> ++#include <resolv.h> ++#include <netdb.h> ++ ++#define LOG_MODULE "input_vdr" ++#define LOG_VERBOSE ++/* ++#define LOG ++*/ ++#include "xine_internal.h" ++#include "xineutils.h" ++#include "input_plugin.h" ++ ++#include "vdr.h" ++#include "combined_vdr.h" ++ ++ ++ ++#define VDR_MAX_NUM_WINDOWS 16 ++#define VDR_ABS_FIFO_DIR "/tmp/vdr-xine" ++ ++#define BUF_SIZE 1024 ++ ++#define LOG_OSD(x) ++/* ++#define LOG_OSD(x) x ++*/ ++ ++ ++typedef struct vdr_input_plugin_s vdr_input_plugin_t; ++ ++typedef struct ++{ ++ metronom_t metronom; ++ metronom_t *stream_metronom; ++ vdr_input_plugin_t *input; ++} ++vdr_metronom_t; ++ ++ ++typedef struct vdr_osd_s ++{ ++ xine_osd_t *window; ++ uint8_t *argb_buffer; ++ int width; ++ int height; ++} ++vdr_osd_t; ++ ++ ++typedef struct vdr_vpts_offset_s vdr_vpts_offset_t; ++ ++struct vdr_vpts_offset_s ++{ ++ vdr_vpts_offset_t *next; ++ int64_t vpts; ++ int64_t offset; ++}; ++ ++ ++struct vdr_input_plugin_s ++{ ++ input_plugin_t input_plugin; ++ ++ xine_stream_t *stream; ++ xine_stream_t *stream_external; ++ ++ int fh; ++ int fh_control; ++ int fh_result; ++ int fh_event; ++ ++ char *mrl; ++ ++ off_t curpos; ++ char seek_buf[ BUF_SIZE ]; ++ ++ char *preview; ++ off_t preview_size; ++ ++ enum funcs cur_func; ++ off_t cur_size; ++ off_t cur_done; ++ ++ vdr_osd_t osd[ VDR_MAX_NUM_WINDOWS ]; ++ uint8_t *osd_buffer; ++ uint32_t osd_buffer_size; ++ uint8_t osd_unscaled_blending; ++ uint8_t osd_supports_custom_extent; ++ uint8_t osd_supports_argb_layer; ++ ++ uint8_t audio_channels; ++ uint8_t trick_speed_mode; ++ uint8_t mute_mode; ++ uint8_t volume_mode; ++ int last_volume; ++ vdr_frame_size_changed_data_t frame_size; ++ ++ pthread_t rpc_thread; ++ int rpc_thread_shutdown; ++ pthread_mutex_t rpc_thread_shutdown_lock; ++ pthread_cond_t rpc_thread_shutdown_cond; ++ int startup_phase; ++ ++ pthread_t metronom_thread; ++ pthread_mutex_t metronom_thread_lock; ++ int64_t metronom_thread_request; ++ int metronom_thread_reply; ++ pthread_cond_t metronom_thread_request_cond; ++ pthread_cond_t metronom_thread_reply_cond; ++ pthread_mutex_t metronom_thread_call_lock; ++ ++ xine_event_queue_t *event_queue; ++ xine_event_queue_t *event_queue_external; ++ ++ pthread_mutex_t adjust_zoom_lock; ++ uint16_t image4_3_zoom_x; ++ uint16_t image4_3_zoom_y; ++ uint16_t image16_9_zoom_x; ++ uint16_t image16_9_zoom_y; ++ ++ uint8_t find_sync_point; ++ pthread_mutex_t find_sync_point_lock; ++ ++ vdr_metronom_t metronom; ++ int last_disc_type; ++ ++ vdr_vpts_offset_t *vpts_offset_queue; ++ vdr_vpts_offset_t *vpts_offset_queue_tail; ++ pthread_mutex_t vpts_offset_queue_lock; ++ pthread_cond_t vpts_offset_queue_changed_cond; ++ int vpts_offset_queue_changes; ++ ++ int video_window_active; ++ vdr_set_video_window_data_t video_window_event_data; ++}; ++ ++ ++typedef struct ++{ ++ input_class_t input_class; ++ xine_t *xine; ++ const char *mrls[ 2 ]; ++} ++vdr_input_class_t; ++ ++ ++ ++static int vdr_write(int f, void *b, int n) ++{ ++ int t = 0, r; ++ ++ while (t < n) ++ { ++ /* ++ * System calls are not a thread cancellation point in Linux ++ * pthreads. However, the RT signal sent to cancel the thread ++ * will cause recv() to return with EINTR, and we can manually ++ * check cancellation. ++ */ ++ pthread_testcancel(); ++ r = write(f, ((char *)b) + t, n - t); ++ pthread_testcancel(); ++ ++ if (r < 0 ++ && (errno == EINTR ++ || errno == EAGAIN)) ++ { ++ continue; ++ } ++ ++ if (r < 0) ++ return r; ++ ++ t += r; ++ } ++ ++ return t; ++} ++ ++ ++ ++static int internal_write_event_play_external(vdr_input_plugin_t *this, uint32_t key); ++ ++static void event_handler_external(void *user_data, const xine_event_t *event) ++{ ++ vdr_input_plugin_t *this = (vdr_input_plugin_t *)user_data; ++ uint32_t key = key_none; ++/* ++ printf("event_handler_external(): event->type: %d\n", event->type); ++*/ ++ switch (event->type) ++ { ++ case XINE_EVENT_UI_PLAYBACK_FINISHED: ++ break; ++ ++ default: ++ return; ++ } ++ ++ if (0 != internal_write_event_play_external(this, key)) ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, ++ _("%s: input event write: %s.\n"), LOG_MODULE, strerror(errno)); ++} ++ ++static void external_stream_stop(vdr_input_plugin_t *this) ++{ ++ if (this->stream_external) ++ { ++ xine_stop(this->stream_external); ++ xine_close(this->stream_external); ++ ++ if (this->event_queue_external) ++ { ++ xine_event_dispose_queue(this->event_queue_external); ++ this->event_queue_external = 0; ++ } ++ ++ _x_demux_flush_engine(this->stream_external); ++ ++ xine_dispose(this->stream_external); ++ this->stream_external = 0; ++ } ++} ++ ++static void external_stream_play(vdr_input_plugin_t *this, char *file_name) ++{ ++ external_stream_stop(this); ++ ++ this->stream_external = xine_stream_new(this->stream->xine, this->stream->audio_out, this->stream->video_out); ++ ++ this->event_queue_external = xine_event_new_queue(this->stream_external); ++ ++ xine_event_create_listener_thread(this->event_queue_external, event_handler_external, this); ++ ++ if (!xine_open(this->stream_external, file_name) ++ || !xine_play(this->stream_external, 0, 0)) ++ { ++ uint32_t key = key_none; ++ ++ if ( 0 != internal_write_event_play_external(this, key)) ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, ++ _("%s: input event write: %s.\n"), LOG_MODULE, strerror(errno)); ++ } ++} ++ ++static off_t vdr_read_abort(xine_stream_t *stream, int fd, char *buf, off_t todo) ++{ ++ off_t ret; ++ ++ while (1) ++ { ++ /* ++ * System calls are not a thread cancellation point in Linux ++ * pthreads. However, the RT signal sent to cancel the thread ++ * will cause recv() to return with EINTR, and we can manually ++ * check cancellation. ++ */ ++ pthread_testcancel(); ++ ret = _x_read_abort(stream, fd, buf, todo); ++ pthread_testcancel(); ++ ++ if (ret < 0 ++ && (errno == EINTR ++ || errno == EAGAIN)) ++ { ++ continue; ++ } ++ ++ break; ++ } ++ ++ return ret; ++} ++ ++#define READ_DATA_OR_FAIL(kind, log) \ ++ data_##kind##_t *data = &data_union.kind; \ ++ { \ ++ log; \ ++ n = vdr_read_abort(this->stream, this->fh_control, (char *)data + sizeof (data->header), sizeof (*data) - sizeof (data->header)); \ ++ if (n != sizeof (*data) - sizeof (data->header)) \ ++ return -1; \ ++ \ ++ this->cur_size -= n; \ ++ } ++ ++static double _now() ++{ ++ struct timeval tv; ++ ++ gettimeofday(&tv, 0); ++ ++ return (tv.tv_sec * 1000000.0 + tv.tv_usec) / 1000.0; ++} ++ ++static void adjust_zoom(vdr_input_plugin_t *this) ++{ ++ pthread_mutex_lock(&this->adjust_zoom_lock); ++ ++ if (this->image4_3_zoom_x && this->image4_3_zoom_y ++ && this->image16_9_zoom_x && this->image16_9_zoom_y) ++ { ++ int ratio = (int)(10000 * this->frame_size.r + 0.5); ++ /* fprintf(stderr, "ratio: %d\n", ratio); */ ++ if (13332 <= ratio && ratio <= 13334) ++ { ++ xine_set_param(this->stream, XINE_PARAM_VO_ZOOM_X, this->image4_3_zoom_x); ++ xine_set_param(this->stream, XINE_PARAM_VO_ZOOM_Y, this->image4_3_zoom_y); ++ } ++ else /* if (17777 <= ratio && ratio <= 17779) */ ++ { ++ xine_set_param(this->stream, XINE_PARAM_VO_ZOOM_X, this->image16_9_zoom_x); ++ xine_set_param(this->stream, XINE_PARAM_VO_ZOOM_Y, this->image16_9_zoom_y); ++ } ++ } ++ ++ pthread_mutex_unlock(&this->adjust_zoom_lock); ++} ++ ++ ++static void vdr_vpts_offset_queue_process(vdr_input_plugin_t *this, int64_t vpts) ++{ ++ while (this->vpts_offset_queue ++ && this->vpts_offset_queue->vpts <= vpts) ++ { ++ vdr_vpts_offset_t *curr = this->vpts_offset_queue; ++ this->vpts_offset_queue = curr->next; ++ ++ free(curr); ++ } ++ ++ if (!this->vpts_offset_queue) ++ this->vpts_offset_queue_tail = 0; ++} ++ ++ ++static void vdr_vpts_offset_queue_purge(vdr_input_plugin_t *this) ++{ ++ vdr_vpts_offset_queue_process(this, 1ll << 62); ++} ++ ++ ++static off_t vdr_execute_rpc_command(vdr_input_plugin_t *this) ++{ ++ data_union_t data_union; ++ off_t n; ++ ++ n = vdr_read_abort(this->stream, this->fh_control, (char *)&data_union, sizeof (data_union.header)); ++ if (n != sizeof (data_union.header)) ++ return -1; ++ ++ this->cur_func = data_union.header.func; ++ this->cur_size = data_union.header.len - sizeof (data_union.header); ++ this->cur_done = 0; ++ ++ switch (this->cur_func) ++ { ++ case func_nop: ++ { ++ READ_DATA_OR_FAIL(nop, lprintf("got NOP\n")); ++ } ++ break; ++ ++ case func_osd_new: ++ { ++ READ_DATA_OR_FAIL(osd_new, LOG_OSD(lprintf("got OSDNEW\n"))); ++/* ++ LOG_OSD(lprintf("... (%d,%d)-(%d,%d)@(%d,%d)\n", data->x, data->y, data->width, data->height, data->w_ref, data->h_ref)); ++ ++ fprintf(stderr, "vdr: osdnew %d\n", data->window); ++*/ ++ if (data->window >= VDR_MAX_NUM_WINDOWS) ++ return -1; ++ ++ if (0 != this->osd[ data->window ].window) ++ return -1; ++ ++ this->osd[ data->window ].window = xine_osd_new(this->stream ++ , data->x ++ , data->y ++ , data->width ++ , data->height); ++ ++ this->osd[ data->window ].width = data->width; ++ this->osd[ data->window ].height = data->height; ++ ++ if (0 == this->osd[ data->window ].window) ++ return -1; ++ ++#ifdef XINE_OSD_CAP_CUSTOM_EXTENT ++ if (this->osd_supports_custom_extent && data->w_ref > 0 && data->h_ref > 0) ++ xine_osd_set_extent(this->osd[ data->window ].window, data->w_ref, data->h_ref); ++#endif ++ } ++ break; ++ ++ case func_osd_free: ++ { ++ READ_DATA_OR_FAIL(osd_free, LOG_OSD(lprintf("got OSDFREE\n"))); ++/* ++ fprintf(stderr, "vdr: osdfree %d\n", data->window); ++*/ ++ if (data->window >= VDR_MAX_NUM_WINDOWS) ++ return -1; ++ ++ if (0 != this->osd[ data->window ].window) ++ xine_osd_free(this->osd[ data->window ].window); ++ ++ this->osd[ data->window ].window = 0; ++ ++ free(this->osd[ data->window ].argb_buffer); ++ this->osd[ data->window ].argb_buffer = 0; ++ } ++ break; ++ ++ case func_osd_show: ++ { ++ READ_DATA_OR_FAIL(osd_show, LOG_OSD(lprintf("got OSDSHOW\n"))); ++/* ++ fprintf(stderr, "vdr: osdshow %d\n", data->window); ++*/ ++ if (data->window >= VDR_MAX_NUM_WINDOWS) ++ return -1; ++ ++ if (0 != this->osd[ data->window ].window) ++ { ++#ifdef XINE_OSD_CAP_VIDEO_WINDOW ++ xine_osd_set_video_window(this->osd[ data->window ].window ++ , this->video_window_active ? this->video_window_event_data.x : 0 ++ , this->video_window_active ? this->video_window_event_data.y : 0 ++ , this->video_window_active ? this->video_window_event_data.w : 0 ++ , this->video_window_active ? this->video_window_event_data.h : 0); ++#endif ++ if (this->osd_unscaled_blending) ++ xine_osd_show_unscaled(this->osd[ data->window ].window, 0); ++ else ++ xine_osd_show(this->osd[ data->window ].window, 0); ++ } ++ } ++ break; ++ ++ case func_osd_hide: ++ { ++ READ_DATA_OR_FAIL(osd_hide, LOG_OSD(lprintf("got OSDHIDE\n"))); ++/* ++ fprintf(stderr, "vdr: osdhide %d\n", data->window); ++*/ ++ if (data->window >= VDR_MAX_NUM_WINDOWS) ++ return -1; ++ ++ if (0 != this->osd[ data->window ].window) ++ xine_osd_hide(this->osd[ data->window ].window, 0); ++ } ++ break; ++ ++ case func_osd_flush: ++ { ++ double _t1, _t2; ++ int _n = 0; ++ int _to = 0; ++ int r = 0; ++ ++ READ_DATA_OR_FAIL(osd_flush, LOG_OSD(lprintf("got OSDFLUSH\n"))); ++/* ++ fprintf(stderr, "vdr: osdflush +\n"); ++*/ ++ _t1 = _now(); ++ ++ while ((r = _x_query_unprocessed_osd_events(this->stream))) ++ { ++ if ((_now() - _t1) > 200) ++ { ++ _to = 1; ++ break; ++ } ++/* ++ fprintf(stderr, "redraw_needed: 1\n"); ++*/ ++/* sched_yield(); */ ++ xine_usec_sleep(5000); ++ _n++; ++ } ++ ++ _t2 = _now(); ++ fprintf(stderr, "vdr: osdflush: n: %d, %.1lf, timeout: %d, result: %d\n", _n, _t2 - _t1, _to, r); ++/* ++ fprintf(stderr, "redraw_needed: 0\n"); ++ ++ fprintf(stderr, "vdr: osdflush -\n"); ++*/ ++ } ++ break; ++ ++ case func_osd_set_position: ++ { ++ READ_DATA_OR_FAIL(osd_set_position, LOG_OSD(lprintf("got OSDSETPOSITION\n"))); ++/* ++ fprintf(stderr, "vdr: osdsetposition %d\n", data->window); ++*/ ++ if (data->window >= VDR_MAX_NUM_WINDOWS) ++ return -1; ++ ++ if (0 != this->osd[ data->window ].window) ++ xine_osd_set_position(this->osd[ data->window ].window, data->x, data->y); ++ } ++ break; ++ ++ case func_osd_draw_bitmap: ++ { ++ READ_DATA_OR_FAIL(osd_draw_bitmap, LOG_OSD(lprintf("got OSDDRAWBITMAP\n"))); ++/* ++ fprintf(stderr, "vdr: osddrawbitmap %d, %d, %d, %d, %d, %d\n", data->window, data->x, data->y, data->width, data->height, data->argb); ++*/ ++ if (this->osd_buffer_size < this->cur_size) ++ { ++ if (this->osd_buffer) ++ free(this->osd_buffer); ++ ++ this->osd_buffer_size = 0; ++ ++ this->osd_buffer = xine_xmalloc(this->cur_size); ++ if (!this->osd_buffer) ++ return -1; ++ ++ this->osd_buffer_size = this->cur_size; ++ } ++ ++ n = vdr_read_abort (this->stream, this->fh_control, (char *)this->osd_buffer, this->cur_size); ++ if (n != this->cur_size) ++ return -1; ++ ++ this->cur_size -= n; ++ ++ if (data->window >= VDR_MAX_NUM_WINDOWS) ++ return -1; ++ ++ if (0 != this->osd[ data->window ].window) ++ { ++ vdr_osd_t *osd = &this->osd[ data->window ]; ++ ++ if (data->argb) ++ { ++ if (!osd->argb_buffer) ++ osd->argb_buffer = calloc(4 * osd->width, osd->height); ++ ++ { ++ int src_stride = 4 * data->width; ++ int dst_stride = 4 * osd->width; ++ ++ uint8_t *src = this->osd_buffer; ++ uint8_t *dst = osd->argb_buffer + data->y * dst_stride + data->x * 4; ++ int y; ++ ++ if (src_stride == dst_stride) ++ xine_fast_memcpy(dst, src, src_stride * data->height); ++ else ++ { ++ for (y = 0; y < data->height; y++) ++ { ++ xine_fast_memcpy(dst, src, src_stride); ++ dst += dst_stride; ++ src += src_stride; ++ } ++ } ++ } ++ ++#ifdef XINE_OSD_CAP_ARGB_LAYER ++ xine_osd_set_argb_buffer(osd->window, (uint32_t *)osd->argb_buffer, data->x, data->y, data->width, data->height); ++#endif ++ } ++ else ++ xine_osd_draw_bitmap(osd->window, this->osd_buffer, data->x, data->y, data->width, data->height, 0); ++ } ++ } ++ break; ++ ++ case func_set_color: ++ { ++ uint32_t vdr_color[ 256 ]; ++ ++ READ_DATA_OR_FAIL(set_color, lprintf("got SETCOLOR\n")); ++ ++ if (((data->num + 1) * sizeof (uint32_t)) != this->cur_size) ++ return -1; ++ ++ n = vdr_read_abort (this->stream, this->fh_control, (char *)&vdr_color[ data->index ], this->cur_size); ++ if (n != this->cur_size) ++ return -1; ++ ++ this->cur_size -= n; ++ ++ if (data->window >= VDR_MAX_NUM_WINDOWS) ++ return -1; ++ ++ if (0 != this->osd[ data->window ].window) ++ { ++ uint32_t color[ 256 ]; ++ uint8_t trans[ 256 ]; ++ ++ xine_osd_get_palette(this->osd[ data->window ].window, color, trans); ++ ++ { ++ int i; ++ ++ for (i = data->index; i <= (data->index + data->num); i++) ++ { ++ int a = (vdr_color[ i ] & 0xff000000) >> 0x18; ++ int r = (vdr_color[ i ] & 0x00ff0000) >> 0x10; ++ int g = (vdr_color[ i ] & 0x0000ff00) >> 0x08; ++ int b = (vdr_color[ i ] & 0x000000ff) >> 0x00; ++ ++ int y = (( 66 * r + 129 * g + 25 * b + 128) >> 8) + 16; ++ int cr = ((112 * r - 94 * g - 18 * b + 128) >> 8) + 128; ++ int cb = ((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128; ++ ++ uint8_t *dst = (uint8_t *)&color[ i ]; ++ *dst++ = cb; ++ *dst++ = cr; ++ *dst++ = y; ++ *dst++ = 0; ++ ++ trans[ i ] = a >> 4; ++ } ++ } ++ ++ xine_osd_set_palette(this->osd[ data->window ].window, color, trans); ++ } ++ } ++ break; ++ ++ case func_play_external: ++ { ++ char file_name[ 1024 ]; ++ int file_name_len = 0; ++ ++ READ_DATA_OR_FAIL(play_external, lprintf("got PLAYEXTERNAL\n")); ++ ++ file_name_len = this->cur_size; ++ ++ if (0 != file_name_len) ++ { ++ if (file_name_len <= 1 ++ || file_name_len > sizeof (file_name)) ++ { ++ return -1; ++ } ++ ++ n = vdr_read_abort (this->stream, this->fh_control, file_name, file_name_len); ++ if (n != file_name_len) ++ return -1; ++ ++ if (file_name[ file_name_len - 1 ] != '\0') ++ return -1; ++ ++ this->cur_size -= n; ++ } ++ ++ lprintf((file_name_len > 0) ? "----------- play external: %s\n" : "---------- stop external\n", file_name); ++ ++ if (file_name_len > 0) ++ external_stream_play(this, file_name); ++ else ++ external_stream_stop(this); ++ } ++ break; ++ ++ case func_clear: ++ { ++ READ_DATA_OR_FAIL(clear, lprintf("got CLEAR\n")); ++ ++ { ++ int orig_speed = xine_get_param(this->stream, XINE_PARAM_FINE_SPEED); ++ if (orig_speed <= 0) ++ xine_set_param(this->stream, XINE_PARAM_FINE_SPEED, XINE_FINE_SPEED_NORMAL); ++/* fprintf(stderr, "+++ CLEAR(%d%c): sync point: %02x\n", data->n, data->s ? 'b' : 'a', data->i); */ ++ if (!data->s) ++ { ++ pthread_mutex_lock(&this->find_sync_point_lock); ++ this->find_sync_point = data->i; ++ pthread_mutex_unlock(&this->find_sync_point_lock); ++ } ++/* ++ if (!this->dont_change_xine_volume) ++ xine_set_param(this->stream, XINE_PARAM_AUDIO_VOLUME, 0); ++*/ ++ _x_demux_flush_engine(this->stream); ++/* fprintf(stderr, "=== CLEAR(%d.1)\n", data->n); */ ++ _x_demux_control_start(this->stream); ++/* fprintf(stderr, "=== CLEAR(%d.2)\n", data->n); */ ++ _x_demux_seek(this->stream, 0, 0, 0); ++/* fprintf(stderr, "=== CLEAR(%d.3)\n", data->n); */ ++ ++ _x_stream_info_reset(this->stream, XINE_STREAM_INFO_AUDIO_BITRATE); ++/* fprintf(stderr, "=== CLEAR(%d.4)\n", data->n); */ ++ _x_meta_info_reset(this->stream, XINE_META_INFO_AUDIOCODEC); ++/* fprintf(stderr, "=== CLEAR(%d.5)\n", data->n); */ ++ ++ _x_trigger_relaxed_frame_drop_mode(this->stream); ++/* _x_reset_relaxed_frame_drop_mode(this->stream); */ ++/* ++ if (!this->dont_change_xine_volume) ++ xine_set_param(this->stream, XINE_PARAM_AUDIO_VOLUME, this->last_volume); ++*/ ++/* fprintf(stderr, "--- CLEAR(%d%c)\n", data->n, data->s ? 'b' : 'a'); */ ++ if (orig_speed <= 0) ++ xine_set_param(this->stream, XINE_PARAM_FINE_SPEED, orig_speed); ++ } ++ } ++ break; ++ ++ case func_first_frame: ++ { ++ READ_DATA_OR_FAIL(first_frame, lprintf("got FIRST FRAME\n")); ++ ++ _x_trigger_relaxed_frame_drop_mode(this->stream); ++/* _x_reset_relaxed_frame_drop_mode(this->stream); */ ++ } ++ break; ++ ++ case func_still_frame: ++ { ++ READ_DATA_OR_FAIL(still_frame, lprintf("got STILL FRAME\n")); ++ ++ _x_reset_relaxed_frame_drop_mode(this->stream); ++ } ++ break; ++ ++ case func_set_video_window: ++ { ++ READ_DATA_OR_FAIL(set_video_window, lprintf("got SET VIDEO WINDOW\n")); ++/* ++ fprintf(stderr, "svw: (%d, %d)x(%d, %d), (%d, %d)\n", data->x, data->y, data->w, data->h, data->wRef, data->hRef); ++*/ ++ { ++ xine_event_t event; ++ ++ this->video_window_active = (data->x != 0 ++ || data->y != 0 ++ || data->w != data->w_ref ++ || data->h != data->h_ref); ++ ++ this->video_window_event_data.x = data->x; ++ this->video_window_event_data.y = data->y; ++ this->video_window_event_data.w = data->w; ++ this->video_window_event_data.h = data->h; ++ this->video_window_event_data.w_ref = data->w_ref; ++ this->video_window_event_data.h_ref = data->h_ref; ++ ++ event.type = XINE_EVENT_VDR_SETVIDEOWINDOW; ++ event.data = &this->video_window_event_data; ++ event.data_length = sizeof (this->video_window_event_data); ++ ++ xine_event_send(this->stream, &event); ++ } ++ } ++ break; ++ ++ case func_select_audio: ++ { ++ READ_DATA_OR_FAIL(select_audio, lprintf("got SELECT AUDIO\n")); ++ ++ this->audio_channels = data->channels; ++ ++ { ++ xine_event_t event; ++ vdr_select_audio_data_t event_data; ++ ++ event_data.channels = this->audio_channels; ++ ++ event.type = XINE_EVENT_VDR_SELECTAUDIO; ++ event.data = &event_data; ++ event.data_length = sizeof (event_data); ++ ++ xine_event_send(this->stream, &event); ++ } ++ } ++ break; ++ ++ case func_trick_speed_mode: ++ { ++ READ_DATA_OR_FAIL(trick_speed_mode, lprintf("got TRICK SPEED MODE\n")); ++ ++ if (this->trick_speed_mode != data->on) ++ { ++ this->trick_speed_mode = data->on; ++ ++ _x_demux_seek(this->stream, 0, 0, 0); ++ ++ { ++ xine_event_t event; ++ ++ event.type = XINE_EVENT_VDR_TRICKSPEEDMODE; ++ event.data = 0; ++ event.data_length = 0; /* this->trick_speed_mode; */ ++/* fprintf(stderr, "************************: %p, %d\n", event.data, event.data_length); */ ++ xine_event_send(this->stream, &event); ++ } ++ } ++ } ++ break; ++ ++ case func_flush: ++ { ++ READ_DATA_OR_FAIL(flush, lprintf("got FLUSH\n")); ++ ++ if (!data->just_wait) ++ { ++ if (this->stream->video_fifo) ++ { ++ buf_element_t *buf = this->stream->video_fifo->buffer_pool_alloc(this->stream->video_fifo); ++ if (!buf) ++ { ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, _("%s: buffer_pool_alloc() failed!\n"), LOG_MODULE); ++ return -1; ++ } ++ ++ buf->type = BUF_CONTROL_FLUSH_DECODER; ++ ++ this->stream->video_fifo->put(this->stream->video_fifo, buf); ++ } ++ } ++ ++ { ++ double _t1, _t2; ++ int _n = 0; ++ ++ int vb = -1, ab = -1, vf = -1, af = -1; ++ ++ uint8_t timed_out = 0; ++ ++ struct timeval now, then; ++ ++ if (data->ms_timeout >= 0) ++ { ++ gettimeofday(&now, 0); ++ ++ then = now; ++ then.tv_usec += (data->ms_timeout % 1000) * 1000; ++ then.tv_sec += (data->ms_timeout / 1000); ++ ++ if (then.tv_usec >= 1000000) ++ { ++ then.tv_usec -= 1000000; ++ then.tv_sec += 1; ++ } ++ } ++ else ++ { ++ then.tv_usec = 0; ++ then.tv_sec = 0; ++ } ++ ++ _t1 = _now(); ++ ++ while (1) ++ { ++ _x_query_buffer_usage(this->stream, &vb, &ab, &vf, &af); ++ ++ if (vb <= 0 && ab <= 0 && vf <= 0 && af <= 0) ++ break; ++ ++ if (data->ms_timeout >= 0 ++ && timercmp(&now, &then, >=)) ++ { ++ timed_out++; ++ break; ++ } ++ ++/* sched_yield(); */ ++ xine_usec_sleep(5000); ++ _n++; ++ ++ if (data->ms_timeout >= 0) ++ gettimeofday(&now, 0); ++ } ++ ++ _t2 = _now(); ++ /* fprintf(stderr, "vdr: flush: n: %d, %.1lf\n", _n, _t2 - _t1); */ ++ ++ xprintf(this->stream->xine ++ , XINE_VERBOSITY_LOG ++ , _("%s: flush buffers (vb: %d, ab: %d, vf: %d, af: %d) %s.\n") ++ , LOG_MODULE, vb, ab, vf, af ++ , (timed_out ? "timed out" : "done")); ++ ++ { ++ result_flush_t result_flush; ++ result_flush.header.func = data->header.func; ++ result_flush.header.len = sizeof (result_flush); ++ ++ result_flush.timed_out = timed_out; ++ ++ if (sizeof (result_flush) != vdr_write(this->fh_result, &result_flush, sizeof (result_flush))) ++ return -1; ++ } ++ } ++ } ++ break; ++ ++ case func_mute: ++ { ++ READ_DATA_OR_FAIL(mute, lprintf("got MUTE\n")); ++ ++ { ++ int param_mute = (this->volume_mode == XINE_VDR_VOLUME_CHANGE_SW) ? XINE_PARAM_AUDIO_AMP_MUTE : XINE_PARAM_AUDIO_MUTE; ++ xine_set_param(this->stream, param_mute, data->mute); ++ } ++ } ++ break; ++ ++ case func_set_volume: ++ { ++double t3, t2, t1, t0; ++ READ_DATA_OR_FAIL(set_volume, lprintf("got SETVOLUME\n")); ++t0 = _now(); ++ { ++ int change_volume = (this->volume_mode != XINE_VDR_VOLUME_IGNORE); ++ int do_mute = (this->last_volume != 0 && 0 == data->volume); ++ int do_unmute = (this->last_volume <= 0 && 0 != data->volume); ++ int report_change = 0; ++ ++ int param_mute = (this->volume_mode == XINE_VDR_VOLUME_CHANGE_SW) ? XINE_PARAM_AUDIO_AMP_MUTE : XINE_PARAM_AUDIO_MUTE; ++ int param_volume = (this->volume_mode == XINE_VDR_VOLUME_CHANGE_SW) ? XINE_PARAM_AUDIO_AMP_LEVEL : XINE_PARAM_AUDIO_VOLUME; ++ ++ this->last_volume = data->volume; ++ ++ if (do_mute || do_unmute) ++ { ++ switch (this->mute_mode) ++ { ++ case XINE_VDR_MUTE_EXECUTE: ++ report_change = 1; ++ xine_set_param(this->stream, param_mute, do_mute); ++ ++ case XINE_VDR_MUTE_IGNORE: ++ if (do_mute) ++ change_volume = 0; ++ break; ++ ++ case XINE_VDR_MUTE_SIMULATE: ++ change_volume = 1; ++ break; ++ ++ default: ++ return -1; ++ }; ++ } ++t1 = _now(); ++ ++ if (change_volume) ++ { ++ report_change = 1; ++ xine_set_param(this->stream, param_volume, this->last_volume); ++ } ++t2 = _now(); ++ ++ if (report_change && this->volume_mode != XINE_VDR_VOLUME_CHANGE_SW) ++ { ++ xine_event_t event; ++ xine_audio_level_data_t data; ++ ++ data.left ++ = data.right ++ = xine_get_param(this->stream, param_volume); ++ data.mute ++ = xine_get_param(this->stream, param_mute); ++t3 = _now(); ++ ++ event.type = XINE_EVENT_AUDIO_LEVEL; ++ event.data = &data; ++ event.data_length = sizeof (data); ++ ++ xine_event_send(this->stream, &event); ++ } ++ } ++/* fprintf(stderr, "volume: %6.3lf ms, %6.3lf ms, %6.3lf ms\n", t1 - t0, t2 - t1, t3 - t2); */ ++ } ++ break; ++ ++ case func_set_speed: ++ { ++ READ_DATA_OR_FAIL(set_speed, lprintf("got SETSPEED\n")); ++ ++ lprintf("... got SETSPEED %d\n", data->speed); ++ ++ if (data->speed != xine_get_param(this->stream, XINE_PARAM_FINE_SPEED)) ++ xine_set_param(this->stream, XINE_PARAM_FINE_SPEED, data->speed); ++ } ++ break; ++ ++ case func_set_prebuffer: ++ { ++ READ_DATA_OR_FAIL(set_prebuffer, lprintf("got SETPREBUFFER\n")); ++ ++ xine_set_param(this->stream, XINE_PARAM_METRONOM_PREBUFFER, data->prebuffer); ++ } ++ break; ++ ++ case func_metronom: ++ { ++ READ_DATA_OR_FAIL(metronom, lprintf("got METRONOM\n")); ++ ++ _x_demux_control_newpts(this->stream, data->pts, data->flags); ++ } ++ break; ++ ++ case func_start: ++ { ++ READ_DATA_OR_FAIL(start, lprintf("got START\n")); ++ ++ _x_demux_control_start(this->stream); ++ _x_demux_seek(this->stream, 0, 0, 0); ++ } ++ break; ++ ++ case func_wait: ++ { ++ READ_DATA_OR_FAIL(wait, lprintf("got WAIT\n")); ++ ++ { ++ result_wait_t result_wait; ++ result_wait.header.func = data->header.func; ++ result_wait.header.len = sizeof (result_wait); ++ ++ if (sizeof (result_wait) != vdr_write(this->fh_result, &result_wait, sizeof (result_wait))) ++ return -1; ++ ++ if (data->id == 1) ++ this->startup_phase = 0; ++ } ++ } ++ break; ++ ++ case func_setup: ++ { ++ READ_DATA_OR_FAIL(setup, lprintf("got SETUP\n")); ++ ++ this->osd_unscaled_blending = data->osd_unscaled_blending; ++ this->volume_mode = data->volume_mode; ++ this->mute_mode = data->mute_mode; ++ this->image4_3_zoom_x = data->image4_3_zoom_x; ++ this->image4_3_zoom_y = data->image4_3_zoom_y; ++ this->image16_9_zoom_x = data->image16_9_zoom_x; ++ this->image16_9_zoom_y = data->image16_9_zoom_y; ++ ++ adjust_zoom(this); ++ } ++ break; ++ ++ case func_grab_image: ++ { ++ READ_DATA_OR_FAIL(grab_image, lprintf("got GRABIMAGE\n")); ++ ++ { ++ off_t ret_val = -1; ++ ++ xine_current_frame_data_t frame_data; ++ memset(&frame_data, 0, sizeof (frame_data)); ++ ++ if (xine_get_current_frame_data(this->stream, &frame_data, XINE_FRAME_DATA_ALLOCATE_IMG)) ++ { ++ if (frame_data.ratio_code == XINE_VO_ASPECT_SQUARE) ++ frame_data.ratio_code = 10000; ++ else if (frame_data.ratio_code == XINE_VO_ASPECT_4_3) ++ frame_data.ratio_code = 13333; ++ else if (frame_data.ratio_code == XINE_VO_ASPECT_ANAMORPHIC) ++ frame_data.ratio_code = 17778; ++ else if (frame_data.ratio_code == XINE_VO_ASPECT_DVB) ++ frame_data.ratio_code = 21100; ++ } ++ ++ if (!frame_data.img) ++ memset(&frame_data, 0, sizeof (frame_data)); ++ ++ { ++ result_grab_image_t result_grab_image; ++ result_grab_image.header.func = data->header.func; ++ result_grab_image.header.len = sizeof (result_grab_image) + frame_data.img_size; ++ ++ result_grab_image.width = frame_data.width; ++ result_grab_image.height = frame_data.height; ++ result_grab_image.ratio = frame_data.ratio_code; ++ result_grab_image.format = frame_data.format; ++ result_grab_image.interlaced = frame_data.interlaced; ++ result_grab_image.crop_left = frame_data.crop_left; ++ result_grab_image.crop_right = frame_data.crop_right; ++ result_grab_image.crop_top = frame_data.crop_top; ++ result_grab_image.crop_bottom = frame_data.crop_bottom; ++ ++ if (sizeof (result_grab_image) == vdr_write(this->fh_result, &result_grab_image, sizeof (result_grab_image))) ++ { ++ if (!frame_data.img_size || (frame_data.img_size == vdr_write(this->fh_result, frame_data.img, frame_data.img_size))) ++ ret_val = 0; ++ } ++ } ++ ++ free(frame_data.img); ++ ++ if (ret_val != 0) ++ return ret_val; ++ } ++ } ++ break; ++ ++ case func_get_pts: ++ { ++ READ_DATA_OR_FAIL(get_pts, lprintf("got GETPTS\n")); ++ ++ { ++ result_get_pts_t result_get_pts; ++ result_get_pts.header.func = data->header.func; ++ result_get_pts.header.len = sizeof (result_get_pts); ++ ++ pthread_mutex_lock(&this->vpts_offset_queue_lock); ++ ++ while (this->vpts_offset_queue_changes) ++ pthread_cond_wait(&this->vpts_offset_queue_changed_cond, &this->vpts_offset_queue_lock); ++ ++ if (this->last_disc_type == DISC_STREAMSTART ++ && data->ms_timeout > 0) ++ { ++ struct timespec abstime; ++ { ++ struct timeval now; ++ gettimeofday(&now, 0); ++ ++ abstime.tv_sec = now.tv_sec + data->ms_timeout / 1000; ++ abstime.tv_nsec = now.tv_usec * 1000 + (data->ms_timeout % 1000) * 1e6; ++ ++ if (abstime.tv_nsec > 1e9) ++ { ++ abstime.tv_nsec -= 1e9; ++ abstime.tv_sec++; ++ } ++ } ++ ++ while (this->last_disc_type == DISC_STREAMSTART ++ || this->vpts_offset_queue_changes) ++ { ++ if (0 != pthread_cond_timedwait(&this->vpts_offset_queue_changed_cond, &this->vpts_offset_queue_lock, &abstime)) ++ break; ++ } ++ } ++ ++ if (this->last_disc_type == DISC_STREAMSTART ++ || this->vpts_offset_queue_changes) ++ { ++ result_get_pts.pts = -1; ++ result_get_pts.queued = 0; ++ } ++ else ++ { ++ int64_t offset, vpts = xine_get_current_vpts(this->stream); ++ ++ vdr_vpts_offset_queue_process(this, vpts); ++ ++/* if(this->vpts_offset_queue) */ ++if(0) ++ { ++fprintf(stderr, "C ---------------------------------------------\n"); ++ vdr_vpts_offset_t *p = this->vpts_offset_queue; ++ while (p) ++ { ++ fprintf(stderr, "C now: %12"PRId64", vpts: %12"PRId64", offset: %12"PRId64"\n", xine_get_current_vpts(this->stream), p->vpts, p->offset); ++ p = p->next; ++ } ++fprintf(stderr, "C =============================================\n"); ++ } ++ ++ if (this->vpts_offset_queue) ++ offset = this->vpts_offset_queue->offset; ++ else ++ offset = this->stream->metronom->get_option(this->stream->metronom, METRONOM_VPTS_OFFSET); ++ ++ result_get_pts.pts = (vpts - offset) & ((1ll << 33) - 1); ++ result_get_pts.queued = !!this->vpts_offset_queue; ++/* fprintf(stderr, "vpts: %12ld, stc: %12ld, offset: %12ld\n", vpts, result_get_pts.pts, offset); */ ++ } ++ ++ pthread_mutex_unlock(&this->vpts_offset_queue_lock); ++ ++ if (sizeof (result_get_pts) != vdr_write(this->fh_result, &result_get_pts, sizeof (result_get_pts))) ++ return -1; ++ } ++ } ++ break; ++ ++ case func_get_version: ++ { ++ READ_DATA_OR_FAIL(get_version, lprintf("got GETVERSION\n")); ++ ++ { ++ result_get_version_t result_get_version; ++ result_get_version.header.func = data->header.func; ++ result_get_version.header.len = sizeof (result_get_version); ++ ++ result_get_version.version = XINE_VDR_VERSION; ++ ++ if (sizeof (result_get_version) != vdr_write(this->fh_result, &result_get_version, sizeof (result_get_version))) ++ return -1; ++ } ++ } ++ break; ++ ++ case func_video_size: ++ { ++ READ_DATA_OR_FAIL(video_size, lprintf("got VIDEO SIZE\n")); ++ ++ { ++ int format; ++ ++ result_video_size_t result_video_size; ++ result_video_size.header.func = data->header.func; ++ result_video_size.header.len = sizeof (result_video_size); ++ ++ result_video_size.top = -1; ++ result_video_size.left = -1; ++ result_video_size.width = -1; ++ result_video_size.height = -1; ++ result_video_size.ratio = 0; ++ ++ xine_get_current_frame(this->stream, &result_video_size.width, &result_video_size.height, &result_video_size.ratio, &format, 0); ++ ++ if (result_video_size.ratio == XINE_VO_ASPECT_SQUARE) ++ result_video_size.ratio = 10000; ++ else if (result_video_size.ratio == XINE_VO_ASPECT_4_3) ++ result_video_size.ratio = 13333; ++ else if (result_video_size.ratio == XINE_VO_ASPECT_ANAMORPHIC) ++ result_video_size.ratio = 17778; ++ else if (result_video_size.ratio == XINE_VO_ASPECT_DVB) ++ result_video_size.ratio = 21100; ++ ++ if (0 != this->frame_size.x ++ || 0 != this->frame_size.y ++ || 0 != this->frame_size.w ++ || 0 != this->frame_size.h) ++ { ++ result_video_size.left = this->frame_size.x; ++ result_video_size.top = this->frame_size.y; ++ result_video_size.width = this->frame_size.w; ++ result_video_size.height = this->frame_size.h; ++ } ++/* fprintf(stderr, "EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE\n"); */ ++ result_video_size.zoom_x = xine_get_param(this->stream, XINE_PARAM_VO_ZOOM_X); ++ result_video_size.zoom_y = xine_get_param(this->stream, XINE_PARAM_VO_ZOOM_Y); ++/* fprintf(stderr, "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF\n"); */ ++ if (sizeof (result_video_size) != vdr_write(this->fh_result, &result_video_size, sizeof (result_video_size))) ++ return -1; ++/* fprintf(stderr, "GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG\n"); */ ++ } ++ } ++ break; ++ ++ case func_reset_audio: ++ { ++ double _t1, _t2; ++ int _n = 0; ++ ++ READ_DATA_OR_FAIL(reset_audio, lprintf("got RESET AUDIO\n")); ++ ++ if (this->stream->audio_fifo) ++ { ++ xine_set_param(this->stream, XINE_PARAM_IGNORE_AUDIO, 1); ++ xine_set_param(this->stream, XINE_PARAM_AUDIO_CHANNEL_LOGICAL, -2); ++ ++ _t1 = _now(); ++ ++ while (1) ++ { ++ int n = xine_get_stream_info(this->stream, XINE_STREAM_INFO_MAX_AUDIO_CHANNEL); ++ if (n <= 0) ++ break; ++ ++ /* keep the decoder running */ ++ if (this->stream->audio_fifo) ++ { ++ buf_element_t *buf = this->stream->audio_fifo->buffer_pool_alloc(this->stream->audio_fifo); ++ if (!buf) ++ { ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, _("%s: buffer_pool_alloc() failed!\n"), LOG_MODULE); ++ return -1; ++ } ++ ++ buf->type = BUF_CONTROL_RESET_TRACK_MAP; ++ ++ this->stream->audio_fifo->put(this->stream->audio_fifo, buf); ++ } ++ ++/* sched_yield(); */ ++ xine_usec_sleep(5000); ++ _n++; ++ } ++ ++ _t2 = _now(); ++ /* fprintf(stderr, "vdr: reset_audio: n: %d, %.1lf\n", _n, _t2 - _t1); */ ++ ++ xine_set_param(this->stream, XINE_PARAM_AUDIO_CHANNEL_LOGICAL, -1); ++ ++ _x_stream_info_reset(this->stream, XINE_STREAM_INFO_AUDIO_BITRATE); ++ _x_meta_info_reset(this->stream, XINE_META_INFO_AUDIOCODEC); ++ ++ xine_set_param(this->stream, XINE_PARAM_IGNORE_AUDIO, 0); ++ } ++ } ++ break; ++ ++ case func_query_capabilities: ++ { ++ READ_DATA_OR_FAIL(query_capabilities, lprintf("got QUERYCAPABILITIES\n")); ++ ++ { ++ result_query_capabilities_t result_query_capabilities; ++ result_query_capabilities.header.func = data->header.func; ++ result_query_capabilities.header.len = sizeof (result_query_capabilities); ++ ++ result_query_capabilities.osd_max_num_windows = MAX_SHOWING; ++ result_query_capabilities.osd_palette_max_depth = 8; ++ result_query_capabilities.osd_palette_is_shared = 0; ++ result_query_capabilities.osd_supports_argb_layer = this->osd_supports_argb_layer; ++ result_query_capabilities.osd_supports_custom_extent = this->osd_supports_custom_extent; ++ ++ if (sizeof (result_query_capabilities) != vdr_write(this->fh_result, &result_query_capabilities, sizeof (result_query_capabilities))) ++ return -1; ++ } ++ } ++ break; ++ ++ default: ++ lprintf("unknown function: %d\n", this->cur_func); ++ } ++ ++ if (this->cur_size != this->cur_done) ++ { ++ off_t skip = this->cur_size - this->cur_done; ++ ++ lprintf("func: %d, skipping: %lld\n", this->cur_func, skip); ++ ++ while (skip > BUF_SIZE) ++ { ++ n = vdr_read_abort(this->stream, this->fh_control, this->seek_buf, BUF_SIZE); ++ if (n != BUF_SIZE) ++ return -1; ++ ++ skip -= BUF_SIZE; ++ } ++ ++ n = vdr_read_abort(this->stream, this->fh_control, this->seek_buf, skip); ++ if (n != skip) ++ return -1; ++ ++ this->cur_done = this->cur_size; ++ ++ return -1; ++ } ++ ++ return 0; ++} ++ ++static void *vdr_rpc_thread_loop(void *arg) ++{ ++ vdr_input_plugin_t *this = (vdr_input_plugin_t *)arg; ++ int frontend_lock_failures = 0; ++ int failed = 0; ++ int was_startup_phase = this->startup_phase; ++ ++ while (!failed ++ && !this->rpc_thread_shutdown ++ && was_startup_phase == this->startup_phase) ++ { ++ struct timeval timeout; ++ fd_set rset; ++ ++ FD_ZERO(&rset); ++ FD_SET(this->fh_control, &rset); ++ ++ timeout.tv_sec = 0; ++ timeout.tv_usec = 50000; ++ ++ if (select(this->fh_control + 1, &rset, NULL, NULL, &timeout) > 0) ++ { ++ if (!_x_lock_frontend(this->stream, 100)) ++ { ++ if (++frontend_lock_failures > 50) ++ { ++ failed = 1; ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, ++ LOG_MODULE ": locking frontend for rpc command execution failed, exiting ...\n"); ++ } ++ } ++ else ++ { ++ frontend_lock_failures = 0; ++ ++ if (_x_lock_port_rewiring(this->stream->xine, 100)) ++ { ++ if (vdr_execute_rpc_command(this) < 0) ++ { ++ failed = 1; ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, ++ LOG_MODULE ": execution of rpc command %d (%s) failed, exiting ...\n", this->cur_func, ""); ++ } ++ ++ _x_unlock_port_rewiring(this->stream->xine); ++ } ++ ++ _x_unlock_frontend(this->stream); ++ } ++ } ++ } ++ ++ if (!failed && was_startup_phase) ++ return (void *)1; ++ ++ /* close control and result channel here to have vdr-xine initiate a disconnect for the above error case ... */ ++ close(this->fh_control); ++ this->fh_control = -1; ++ ++ close(this->fh_result); ++ this->fh_result = -1; ++ ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, ++ LOG_MODULE ": rpc thread done.\n"); ++ ++ pthread_mutex_lock(&this->rpc_thread_shutdown_lock); ++ this->rpc_thread_shutdown = -1; ++ pthread_cond_broadcast(&this->rpc_thread_shutdown_cond); ++ pthread_mutex_unlock(&this->rpc_thread_shutdown_lock); ++ ++ return 0; ++} ++ ++static int internal_write_event_key(vdr_input_plugin_t *this, uint32_t key) ++{ ++ event_key_t event; ++ event.header.func = func_key; ++ event.header.len = sizeof (event); ++ ++ event.key = key; ++ ++ if (sizeof (event) != vdr_write(this->fh_event, &event, sizeof (event))) ++ return -1; ++ ++ return 0; ++} ++ ++static int internal_write_event_frame_size(vdr_input_plugin_t *this) ++{ ++ event_frame_size_t event; ++ event.header.func = func_frame_size; ++ event.header.len = sizeof (event); ++ ++ event.left = this->frame_size.x; ++ event.top = this->frame_size.y; ++ event.width = this->frame_size.w, ++ event.height = this->frame_size.h; ++ event.zoom_x = xine_get_param(this->stream, XINE_PARAM_VO_ZOOM_X); ++ event.zoom_y = xine_get_param(this->stream, XINE_PARAM_VO_ZOOM_Y); ++ ++ if (sizeof (event) != vdr_write(this->fh_event, &event, sizeof (event))) ++ return -1; ++ ++ return 0; ++} ++ ++static int internal_write_event_play_external(vdr_input_plugin_t *this, uint32_t key) ++{ ++ event_play_external_t event; ++ event.header.func = func_play_external; ++ event.header.len = sizeof (event); ++ ++ event.key = key; ++ ++ if (sizeof (event) != vdr_write(this->fh_event, &event, sizeof (event))) ++ return -1; ++ ++ return 0; ++} ++ ++static int internal_write_event_discontinuity(vdr_input_plugin_t *this, int32_t type) ++{ ++ event_discontinuity_t event; ++ event.header.func = func_discontinuity; ++ event.header.len = sizeof (event); ++ ++ event.type = type; ++ ++ if (sizeof (event) != vdr_write(this->fh_event, &event, sizeof (event))) ++ return -1; ++ ++ return 0; ++} ++ ++static off_t vdr_plugin_read(input_plugin_t *this_gen, ++ char *buf_gen, off_t len) ++{ ++ vdr_input_plugin_t *this = (vdr_input_plugin_t *) this_gen; ++ uint8_t *buf = (uint8_t *)buf_gen; ++ off_t n, total; ++#ifdef LOG_READ ++ lprintf ("reading %lld bytes...\n", len); ++#endif ++ total=0; ++ if (this->curpos < this->preview_size) ++ { ++ n = this->preview_size - this->curpos; ++ if (n > (len - total)) ++ n = len - total; ++#ifdef LOG_READ ++ lprintf ("%lld bytes from preview (which has %lld bytes)\n", ++ n, this->preview_size); ++#endif ++ memcpy (&buf[total], &this->preview[this->curpos], n); ++ this->curpos += n; ++ total += n; ++ } ++ ++ if( (len-total) > 0 ) ++ { ++ int retries = 0; ++ do ++ { ++ n = vdr_read_abort (this->stream, this->fh, (char *)&buf[total], len-total); ++ if (0 == n) ++ lprintf("read 0, retries: %d\n", retries); ++ } ++ while (0 == n ++ && !this->stream_external ++ && _x_continue_stream_processing(this->stream) ++ && 200 > retries++); /* 200 * 50ms */ ++#ifdef LOG_READ ++ lprintf ("got %lld bytes (%lld/%lld bytes read)\n", ++ n,total,len); ++#endif ++ if (n < 0) ++ { ++ _x_message(this->stream, XINE_MSG_READ_ERROR, NULL); ++ return 0; ++ } ++ ++ this->curpos += n; ++ total += n; ++ } ++ ++ if (this->find_sync_point ++ && total == 6) ++ { ++ pthread_mutex_lock(&this->find_sync_point_lock); ++ ++ while (this->find_sync_point ++ && total == 6 ++ && buf[0] == 0x00 ++ && buf[1] == 0x00 ++ && buf[2] == 0x01) ++ { ++ int l, sp; ++ ++ if (buf[3] == 0xbe ++ && buf[4] == 0xff) ++ { ++/* fprintf(stderr, "------- seen sync point: %02x, waiting for: %02x\n", buf[5], this->find_sync_point); */ ++ if (buf[5] == this->find_sync_point) ++ { ++ this->find_sync_point = 0; ++ break; ++ } ++ } ++ ++ if ((buf[3] & 0xf0) != 0xe0 ++ && (buf[3] & 0xe0) != 0xc0 ++ && buf[3] != 0xbd ++ && buf[3] != 0xbe) ++ { ++ break; ++ } ++ ++ l = buf[4] * 256 + buf[5]; ++ if (l <= 0) ++ break; ++ ++ sp = this->find_sync_point; ++ this->find_sync_point = 0; ++ this_gen->seek(this_gen, l, SEEK_CUR); ++ total = this_gen->read(this_gen, buf, 6); ++ this->find_sync_point = sp; ++ } ++ ++ pthread_mutex_unlock(&this->find_sync_point_lock); ++ } ++ ++ return total; ++} ++ ++static buf_element_t *vdr_plugin_read_block(input_plugin_t *this_gen, fifo_buffer_t *fifo, ++ off_t todo) ++{ ++ off_t total_bytes; ++ buf_element_t *buf = fifo->buffer_pool_alloc(fifo); ++ ++ buf->content = buf->mem; ++ buf->type = BUF_DEMUX_BLOCK; ++ ++ total_bytes = vdr_plugin_read(this_gen, (char *)buf->content, todo); ++ ++ if (total_bytes != todo) ++ { ++ buf->free_buffer(buf); ++ return NULL; ++ } ++ ++ buf->size = total_bytes; ++ ++ return buf; ++} ++ ++/* forward reference */ ++static off_t vdr_plugin_get_current_pos(input_plugin_t *this_gen); ++ ++static off_t vdr_plugin_seek(input_plugin_t *this_gen, off_t offset, int origin) ++{ ++ vdr_input_plugin_t *this = (vdr_input_plugin_t *)this_gen; ++ ++ lprintf("seek %lld offset, %d origin...\n", ++ offset, origin); ++ ++ if ((origin == SEEK_CUR) && (offset >= 0)) ++ { ++ for ( ; ((int)offset) - BUF_SIZE > 0; offset -= BUF_SIZE) ++ { ++ if (!this_gen->read(this_gen, this->seek_buf, BUF_SIZE)) ++ return this->curpos; ++ } ++ ++ this_gen->read (this_gen, this->seek_buf, offset); ++ } ++ ++ if (origin == SEEK_SET) ++ { ++ if (offset < this->curpos) ++ { ++ if (this->curpos <= this->preview_size) ++ this->curpos = offset; ++ else ++ lprintf("cannot seek back! (%lld > %lld)\n", this->curpos, offset); ++ } ++ else ++ { ++ offset -= this->curpos; ++ ++ for ( ; ((int)offset) - BUF_SIZE > 0; offset -= BUF_SIZE) ++ { ++ if (!this_gen->read(this_gen, this->seek_buf, BUF_SIZE)) ++ return this->curpos; ++ } ++ ++ this_gen->read(this_gen, this->seek_buf, offset); ++ } ++ } ++ ++ return this->curpos; ++} ++ ++static off_t vdr_plugin_get_length(input_plugin_t *this_gen) ++{ ++ return 0; ++} ++ ++static uint32_t vdr_plugin_get_capabilities(input_plugin_t *this_gen) ++{ ++ return INPUT_CAP_PREVIEW; ++} ++ ++static uint32_t vdr_plugin_get_blocksize(input_plugin_t *this_gen) ++{ ++ return 0; ++} ++ ++static off_t vdr_plugin_get_current_pos(input_plugin_t *this_gen) ++{ ++ vdr_input_plugin_t *this = (vdr_input_plugin_t *)this_gen; ++ ++ return this->curpos; ++} ++ ++static const char *vdr_plugin_get_mrl(input_plugin_t *this_gen) ++{ ++ vdr_input_plugin_t *this = (vdr_input_plugin_t *)this_gen; ++ ++ return this->mrl; ++} ++ ++static void vdr_plugin_dispose(input_plugin_t *this_gen) ++{ ++ vdr_input_plugin_t *this = (vdr_input_plugin_t *)this_gen; ++ int i; ++ ++ external_stream_stop(this); ++ ++ if (this->event_queue) ++ xine_event_dispose_queue(this->event_queue); ++ ++ if (this->rpc_thread) ++ { ++ struct timespec abstime; ++ int ms_to_time_out = 10000; ++ ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, _("%s: shutting down rpc thread (timeout: %d ms) ...\n"), LOG_MODULE, ms_to_time_out); ++ ++ pthread_mutex_lock(&this->rpc_thread_shutdown_lock); ++ ++ if (this->rpc_thread_shutdown > -1) ++ { ++ this->rpc_thread_shutdown = 1; ++ ++ { ++ struct timeval now; ++ gettimeofday(&now, 0); ++ ++ abstime.tv_sec = now.tv_sec + ms_to_time_out / 1000; ++ abstime.tv_nsec = now.tv_usec * 1000 + (ms_to_time_out % 1000) * 1e6; ++ ++ if (abstime.tv_nsec > 1e9) ++ { ++ abstime.tv_nsec -= 1e9; ++ abstime.tv_sec++; ++ } ++ } ++ ++ if (0 != pthread_cond_timedwait(&this->rpc_thread_shutdown_cond, &this->rpc_thread_shutdown_lock, &abstime)) ++ { ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, _("%s: cancelling rpc thread in function %d...\n"), LOG_MODULE, this->cur_func); ++ pthread_cancel(this->rpc_thread); ++ } ++ } ++ ++ pthread_mutex_unlock(&this->rpc_thread_shutdown_lock); ++ ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, _("%s: joining rpc thread ...\n"), LOG_MODULE); ++ pthread_join(this->rpc_thread, 0); ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, _("%s: rpc thread joined.\n"), LOG_MODULE); ++ } ++ ++ pthread_cond_destroy(&this->rpc_thread_shutdown_cond); ++ pthread_mutex_destroy(&this->rpc_thread_shutdown_lock); ++ ++ if (this->metronom_thread) ++ { ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, _("%s: joining metronom thread ...\n"), LOG_MODULE); ++ ++ pthread_mutex_lock(&this->metronom_thread_call_lock); ++ ++ pthread_mutex_lock(&this->metronom_thread_lock); ++ this->metronom_thread_request = -1; ++ this->metronom_thread_reply = 0; ++ pthread_cond_broadcast(&this->metronom_thread_request_cond); ++/* ++ pthread_mutex_unlock(&this->metronom_thread_lock); ++ ++ pthread_mutex_lock(&this->metronom_thread_lock); ++ if (!this->metronom_thread_reply) ++*/ ++ pthread_cond_wait(&this->metronom_thread_reply_cond, &this->metronom_thread_lock); ++ pthread_mutex_unlock(&this->metronom_thread_lock); ++ ++ pthread_mutex_unlock(&this->metronom_thread_call_lock); ++ ++ pthread_join(this->metronom_thread, 0); ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, _("%s: metronom thread joined.\n"), LOG_MODULE); ++ } ++ ++ pthread_mutex_destroy(&this->metronom_thread_lock); ++ pthread_cond_destroy(&this->metronom_thread_request_cond); ++ pthread_cond_destroy(&this->metronom_thread_reply_cond); ++ ++ pthread_mutex_destroy(&this->find_sync_point_lock); ++ pthread_mutex_destroy(&this->adjust_zoom_lock); ++ ++ if (this->fh_result != -1) ++ close(this->fh_result); ++ ++ if (this->fh_control != -1) ++ close(this->fh_control); ++ ++ if (this->fh_event != -1) ++ close(this->fh_event); ++ ++ for (i = 0; i < VDR_MAX_NUM_WINDOWS; i++) ++ { ++ if (0 == this->osd[ i ].window) ++ continue; ++ ++ xine_osd_hide(this->osd[ i ].window, 0); ++ xine_osd_free(this->osd[ i ].window); ++ ++ free(this->osd[ i ].argb_buffer); ++ } ++ ++ if (this->osd_buffer) ++ free(this->osd_buffer); ++ ++ if ((this->fh != STDIN_FILENO) && (this->fh != -1)) ++ close(this->fh); ++ ++ free(this->mrl); ++ ++ this->stream->metronom = this->metronom.stream_metronom; ++ this->metronom.stream_metronom = 0; ++ ++ vdr_vpts_offset_queue_purge(this); ++ pthread_cond_destroy(&this->vpts_offset_queue_changed_cond); ++ pthread_mutex_destroy(&this->vpts_offset_queue_lock); ++ ++ free(this); ++} ++ ++static int vdr_plugin_get_optional_data(input_plugin_t *this_gen, ++ void *data, int data_type) ++{ ++ vdr_input_plugin_t *this = (vdr_input_plugin_t *)this_gen; ++ (void)this; ++ switch (data_type) ++ { ++ case INPUT_OPTIONAL_DATA_PREVIEW: ++ /* just fake what mpeg_pes demuxer expects */ ++ memcpy (data, "\x00\x00\x01\xe0\x00\x03\x80\x00\x00", 9); ++ return 9; ++ } ++ return INPUT_OPTIONAL_UNSUPPORTED; ++} ++ ++static inline const char *mrl_to_fifo (const char *mrl) ++{ ++ /* vdr://foo -> /foo */ ++ return mrl + 3 + strspn (mrl + 4, "/"); ++} ++ ++static inline const char *mrl_to_host (const char *mrl) ++{ ++ /* netvdr://host:port -> host:port */ ++ return strrchr (mrl, '/') + 1; ++} ++ ++static int vdr_plugin_open_fifo_mrl(input_plugin_t *this_gen) ++{ ++ vdr_input_plugin_t *this = (vdr_input_plugin_t *)this_gen; ++ char *filename = (char *)mrl_to_fifo (this->mrl); ++ ++ if(!strcmp(filename, "/")) { ++ filename = (char *)VDR_ABS_FIFO_DIR "/stream"; ++ } ++ ++ filename = strdup(filename); ++ ++ _x_mrl_unescape (filename); ++ this->fh = open(filename, O_RDONLY | O_NONBLOCK); ++ ++ lprintf("filename '%s'\n", filename); ++ ++ if (this->fh == -1) ++ { ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, ++ _("%s: failed to open '%s' (%s)\n"), LOG_MODULE, ++ filename, ++ strerror(errno)); ++ free (filename); ++ return 0; ++ } ++ ++ { ++ struct pollfd poll_fh = { this->fh, POLLIN, 0 }; ++ ++ int r = poll(&poll_fh, 1, 300); ++ if (1 != r) ++ { ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, ++ _("%s: failed to open '%s' (%s)\n"), LOG_MODULE, ++ filename, ++ _("timeout expired during setup phase")); ++ free (filename); ++ return 0; ++ } ++ } ++ ++ fcntl(this->fh, F_SETFL, ~O_NONBLOCK & fcntl(this->fh, F_GETFL, 0)); ++ ++ /* eat initial handshake byte */ ++ { ++ char b; ++ read(this->fh, &b, 1); ++ } ++ ++ { ++ char *filename_control = 0; ++ asprintf(&filename_control, "%s.control", filename); ++ ++ this->fh_control = open(filename_control, O_RDONLY); ++ ++ if (this->fh_control == -1) { ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, ++ _("%s: failed to open '%s' (%s)\n"), LOG_MODULE, ++ filename_control, ++ strerror(errno)); ++ ++ free(filename_control); ++ free (filename); ++ return 0; ++ } ++ ++ free(filename_control); ++ } ++ ++ { ++ char *filename_result = 0; ++ asprintf(&filename_result, "%s.result", filename); ++ ++ this->fh_result = open(filename_result, O_WRONLY); ++ ++ if (this->fh_result == -1) { ++ perror("failed"); ++ ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, ++ _("%s: failed to open '%s' (%s)\n"), LOG_MODULE, ++ filename_result, ++ strerror(errno)); ++ ++ free(filename_result); ++ free (filename); ++ return 0; ++ } ++ ++ free(filename_result); ++ } ++ ++ { ++ char *filename_event = 0; ++ asprintf(&filename_event, "%s.event", filename); ++ ++ this->fh_event = open(filename_event, O_WRONLY); ++ ++ if (this->fh_event == -1) { ++ perror("failed"); ++ ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, ++ _("%s: failed to open '%s' (%s)\n"), LOG_MODULE, ++ filename_event, ++ strerror(errno)); ++ ++ free(filename_event); ++ free (filename); ++ return 0; ++ } ++ ++ free(filename_event); ++ } ++ ++ free (filename); ++ return 1; ++} ++ ++static int vdr_plugin_open_socket(vdr_input_plugin_t *this, struct hostent *host, unsigned short port) ++{ ++ int fd; ++ struct sockaddr_in sain; ++ struct in_addr iaddr; ++ ++ if ((fd = socket(PF_INET, SOCK_STREAM, 0)) == -1) ++ { ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, ++ _("%s: failed to create socket for port %d (%s)\n"), LOG_MODULE, ++ port, strerror(errno)); ++ return -1; ++ } ++ ++ iaddr.s_addr = *((unsigned int *)host->h_addr_list[0]); ++ ++ sain.sin_port = htons(port); ++ sain.sin_family = AF_INET; ++ sain.sin_addr = iaddr; ++ ++ if (connect(fd, (struct sockaddr *)&sain, sizeof (sain)) < 0) ++ { ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, ++ _("%s: failed to connect to port %d (%s)\n"), LOG_MODULE, port, ++ strerror(errno)); ++ ++ return -1; ++ } ++ ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, ++ _("%s: socket opening (port %d) successful, fd = %d\n"), LOG_MODULE, port, fd); ++ ++ return fd; ++} ++ ++static int vdr_plugin_open_sockets(vdr_input_plugin_t *this) ++{ ++ struct hostent *host; ++ char *mrl_host = strdup (mrl_to_host (this->mrl)); ++ char *mrl_port; ++ int port = 18701; ++ ++ mrl_port = strchr(mrl_host, '#'); ++ if (mrl_port) ++ *mrl_port = 0; /* strip off things like '#demux:mpeg_pes' */ ++ ++ _x_mrl_unescape (mrl_host); ++ ++ mrl_port = strchr(mrl_host, ':'); ++ if (mrl_port) ++ { ++ port = atoi(mrl_port + 1); ++ *mrl_port = 0; ++ } ++ ++ host = gethostbyname(mrl_host); ++ ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, ++ _("%s: connecting to vdr.\n"), LOG_MODULE); ++ ++ if (!host) ++ { ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, ++ _("%s: failed to resolve hostname '%s' (%s)\n"), LOG_MODULE, ++ mrl_host, ++ strerror(errno)); ++ free (mrl_host); ++ return 0; ++ } ++ free (mrl_host); ++ ++ if ((this->fh = vdr_plugin_open_socket(this, host, port + 0)) == -1) ++ return 0; ++ ++ fcntl(this->fh, F_SETFL, ~O_NONBLOCK & fcntl(this->fh, F_GETFL, 0)); ++ ++ if ((this->fh_control = vdr_plugin_open_socket(this, host, port + 1)) == -1) ++ return 0; ++ ++ if ((this->fh_result = vdr_plugin_open_socket(this, host, port + 2)) == -1) ++ return 0; ++ ++ if ((this->fh_event = vdr_plugin_open_socket(this, host, port + 3)) == -1) ++ return 0; ++ ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, ++ _("%s: connecting to all sockets (port %d .. %d) was successful.\n"), LOG_MODULE, port, port + 3); ++ ++ return 1; ++} ++ ++static int vdr_plugin_open_socket_mrl(input_plugin_t *this_gen) ++{ ++ vdr_input_plugin_t *this = (vdr_input_plugin_t *)this_gen; ++ ++ lprintf("input_vdr: connecting to vdr-xine-server...\n"); ++ ++ if (!vdr_plugin_open_sockets(this)) ++ return 0; ++ ++ return 1; ++} ++ ++static void *vdr_metronom_thread_loop(void *arg) ++{ ++ vdr_input_plugin_t *this = (vdr_input_plugin_t *)arg; ++ int run = 1; ++ ++ pthread_mutex_lock(&this->metronom_thread_lock); ++ ++ while (run) ++ { ++ if (this->metronom_thread_request == 0) ++ pthread_cond_wait(&this->metronom_thread_request_cond, &this->metronom_thread_lock); ++ ++ if (this->metronom_thread_request == -1) ++ run = 0; ++ else ++ this->metronom.metronom.handle_audio_discontinuity(&this->metronom.metronom, DISC_ABSOLUTE, this->metronom_thread_request); ++ ++ this->metronom_thread_request = 0; ++ this->metronom_thread_reply = 1; ++ pthread_cond_broadcast(&this->metronom_thread_reply_cond); ++ } ++ ++ pthread_mutex_unlock(&this->metronom_thread_lock); ++ ++ return 0; ++} ++ ++static int vdr_plugin_open(input_plugin_t *this_gen) ++{ ++ vdr_input_plugin_t *this = (vdr_input_plugin_t *)this_gen; ++ ++ lprintf("trying to open '%s'...\n", this->mrl); ++ ++ if (this->fh == -1) ++ { ++ int err = 0; ++ ++ if (!strncasecmp(&this->mrl[0], "vdr:/", 5)) ++ { ++ if (!vdr_plugin_open_fifo_mrl(this_gen)) ++ return 0; ++ } ++ else if (!strncasecmp(&this->mrl[0], "netvdr:/", 8)) ++ { ++ if (!vdr_plugin_open_socket_mrl(this_gen)) ++ return 0; ++ } ++ else ++ { ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, ++ _("%s: MRL (%s) invalid! MRL should start with vdr://path/to/fifo/stream or netvdr://host:port where ':port' is optional.\n"), LOG_MODULE, ++ strerror(err)); ++ return 0; ++ } ++ ++ if ((err = pthread_create(&this->metronom_thread, NULL, ++ vdr_metronom_thread_loop, (void *)this)) != 0) ++ { ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, ++ _("%s: can't create new thread (%s)\n"), LOG_MODULE, ++ strerror(err)); ++ ++ return 0; ++ } ++ ++ this->rpc_thread_shutdown = 0; ++ ++ /* let this thread handle rpc commands in startup phase */ ++ this->startup_phase = 1; ++ if (0 == vdr_rpc_thread_loop(this)) ++ return 0; ++/* fprintf(stderr, "####################################################\n"); */ ++ if ((err = pthread_create(&this->rpc_thread, NULL, ++ vdr_rpc_thread_loop, (void *)this)) != 0) ++ { ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, ++ _("%s: can't create new thread (%s)\n"), LOG_MODULE, ++ strerror(err)); ++ ++ return 0; ++ } ++ } ++ ++ /* ++ * mrl accepted and opened successfully at this point ++ * ++ * => create plugin instance ++ */ ++ ++ this->preview = NULL; ++ this->preview_size = 0; ++ this->curpos = 0; ++ ++ return 1; ++} ++ ++static void event_handler(void *user_data, const xine_event_t *event) ++{ ++ vdr_input_plugin_t *this = (vdr_input_plugin_t *)user_data; ++ uint32_t key = key_none; ++ ++ lprintf("eventHandler(): event->type: %d\n", event->type); ++ ++ if (XINE_EVENT_VDR_FRAMESIZECHANGED == event->type) ++ { ++ memcpy(&this->frame_size, event->data, event->data_length); ++ ++ if (0 != internal_write_event_frame_size(this)) ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, ++ _("%s: input event write: %s.\n"), LOG_MODULE, strerror(errno)); ++ ++ adjust_zoom(this); ++ return; ++ } ++ ++ if (XINE_EVENT_VDR_DISCONTINUITY == event->type) ++ { ++ if (0 != internal_write_event_discontinuity(this, event->data_length)) ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, ++ _("%s: input event write: %s.\n"), LOG_MODULE, strerror(errno)); ++ ++ return; ++ } ++ ++ if (XINE_EVENT_VDR_PLUGINSTARTED == event->type) ++ { ++ if (0 == event->data_length) /* vdr_video */ ++ { ++ xine_event_t event; ++ ++ event.type = XINE_EVENT_VDR_TRICKSPEEDMODE; ++ event.data = 0; ++ event.data_length = 0; /* this->trick_speed_mode; */ ++ ++ xine_event_send(this->stream, &event); ++ } ++ else if (1 == event->data_length) /* vdr_audio */ ++ { ++ xine_event_t event; ++ vdr_select_audio_data_t event_data; ++ ++ event_data.channels = this->audio_channels; ++ ++ event.type = XINE_EVENT_VDR_SELECTAUDIO; ++ event.data = &event_data; ++ event.data_length = sizeof (event_data); ++ ++ xine_event_send(this->stream, &event); ++ } ++ else ++ { ++ fprintf(stderr, "input_vdr: illegal XINE_EVENT_VDR_PLUGINSTARTED: %d\n", event->data_length); ++ } ++ ++ return; ++ } ++ ++ switch (event->type) ++ { ++ case XINE_EVENT_INPUT_UP: key = key_up; break; ++ case XINE_EVENT_INPUT_DOWN: key = key_down; break; ++ case XINE_EVENT_INPUT_LEFT: key = key_left; break; ++ case XINE_EVENT_INPUT_RIGHT: key = key_right; break; ++ case XINE_EVENT_INPUT_SELECT: key = key_ok; break; ++ case XINE_EVENT_VDR_BACK: key = key_back; break; ++ case XINE_EVENT_VDR_CHANNELPLUS: key = key_channel_plus; break; ++ case XINE_EVENT_VDR_CHANNELMINUS: key = key_channel_minus; break; ++ case XINE_EVENT_VDR_RED: key = key_red; break; ++ case XINE_EVENT_VDR_GREEN: key = key_green; break; ++ case XINE_EVENT_VDR_YELLOW: key = key_yellow; break; ++ case XINE_EVENT_VDR_BLUE: key = key_blue; break; ++ case XINE_EVENT_VDR_PLAY: key = key_play; break; ++ case XINE_EVENT_VDR_PAUSE: key = key_pause; break; ++ case XINE_EVENT_VDR_STOP: key = key_stop; break; ++ case XINE_EVENT_VDR_RECORD: key = key_record; break; ++ case XINE_EVENT_VDR_FASTFWD: key = key_fast_fwd; break; ++ case XINE_EVENT_VDR_FASTREW: key = key_fast_rew; break; ++ case XINE_EVENT_VDR_POWER: key = key_power; break; ++ case XINE_EVENT_VDR_SCHEDULE: key = key_schedule; break; ++ case XINE_EVENT_VDR_CHANNELS: key = key_channels; break; ++ case XINE_EVENT_VDR_TIMERS: key = key_timers; break; ++ case XINE_EVENT_VDR_RECORDINGS: key = key_recordings; break; ++ case XINE_EVENT_INPUT_MENU1: key = key_menu; break; ++ case XINE_EVENT_VDR_SETUP: key = key_setup; break; ++ case XINE_EVENT_VDR_COMMANDS: key = key_commands; break; ++ case XINE_EVENT_INPUT_NUMBER_0: key = key_0; break; ++ case XINE_EVENT_INPUT_NUMBER_1: key = key_1; break; ++ case XINE_EVENT_INPUT_NUMBER_2: key = key_2; break; ++ case XINE_EVENT_INPUT_NUMBER_3: key = key_3; break; ++ case XINE_EVENT_INPUT_NUMBER_4: key = key_4; break; ++ case XINE_EVENT_INPUT_NUMBER_5: key = key_5; break; ++ case XINE_EVENT_INPUT_NUMBER_6: key = key_6; break; ++ case XINE_EVENT_INPUT_NUMBER_7: key = key_7; break; ++ case XINE_EVENT_INPUT_NUMBER_8: key = key_8; break; ++ case XINE_EVENT_INPUT_NUMBER_9: key = key_9; break; ++ case XINE_EVENT_VDR_USER1: key = key_user1; break; ++ case XINE_EVENT_VDR_USER2: key = key_user2; break; ++ case XINE_EVENT_VDR_USER3: key = key_user3; break; ++ case XINE_EVENT_VDR_USER4: key = key_user4; break; ++ case XINE_EVENT_VDR_USER5: key = key_user5; break; ++ case XINE_EVENT_VDR_USER6: key = key_user6; break; ++ case XINE_EVENT_VDR_USER7: key = key_user7; break; ++ case XINE_EVENT_VDR_USER8: key = key_user8; break; ++ case XINE_EVENT_VDR_USER9: key = key_user9; break; ++ case XINE_EVENT_VDR_VOLPLUS: key = key_volume_plus; break; ++ case XINE_EVENT_VDR_VOLMINUS: key = key_volume_minus; break; ++ case XINE_EVENT_VDR_MUTE: key = key_mute; break; ++ case XINE_EVENT_VDR_AUDIO: key = key_audio; break; ++ case XINE_EVENT_VDR_INFO: key = key_info; break; ++ case XINE_EVENT_VDR_CHANNELPREVIOUS: key = key_channel_previous; break; ++ case XINE_EVENT_INPUT_NEXT: key = key_next; break; ++ case XINE_EVENT_INPUT_PREVIOUS: key = key_previous; break; ++ case XINE_EVENT_VDR_SUBTITLES: key = key_subtitles; break; ++ default: ++ return; ++ } ++ ++ if (0 != internal_write_event_key(this, key)) ++ xprintf(this->stream->xine, XINE_VERBOSITY_LOG, ++ _("%s: input event write: %s.\n"), LOG_MODULE, strerror(errno)); ++} ++ ++ ++static int64_t vdr_vpts_offset_queue_change_begin(vdr_input_plugin_t *this, int type) ++{ ++ pthread_mutex_lock(&this->vpts_offset_queue_lock); ++ this->vpts_offset_queue_changes++; ++ pthread_mutex_unlock(&this->vpts_offset_queue_lock); ++ ++ return this->metronom.metronom.get_option(&this->metronom.metronom, METRONOM_VPTS_OFFSET); ++} ++ ++static void vdr_vpts_offset_queue_change_end(vdr_input_plugin_t *this, int type, int64_t disc_off, int64_t vpts_offset) ++{ ++ pthread_mutex_lock(&this->vpts_offset_queue_lock); ++ ++if(0) ++ { ++fprintf(stderr, "A ---------------------------------------------\n"); ++ vdr_vpts_offset_t *p = this->vpts_offset_queue; ++ while (p) ++ { ++ fprintf(stderr, "A now: %12"PRId64", vpts: %12"PRId64", offset: %12"PRId64"\n", xine_get_current_vpts(this->stream), p->vpts, p->offset); ++ p = p->next; ++ } ++fprintf(stderr, "A =============================================\n"); ++ } ++ ++ if (type == DISC_ABSOLUTE) ++ { ++ int64_t vpts = this->stream->metronom->get_option(this->stream->metronom, METRONOM_VPTS_OFFSET) + disc_off; ++ ++ if (!this->vpts_offset_queue ++ || this->vpts_offset_queue_tail->vpts < vpts) ++ { ++ vdr_vpts_offset_t *curr = (vdr_vpts_offset_t *)calloc(1, sizeof (vdr_vpts_offset_t)); ++ curr->vpts = vpts; ++ curr->offset = vpts_offset; ++ ++ if (!this->vpts_offset_queue) ++ this->vpts_offset_queue = this->vpts_offset_queue_tail = curr; ++ else ++ { ++ this->vpts_offset_queue_tail->next = curr; ++ this->vpts_offset_queue_tail = curr; ++ } ++ } ++ } ++ else ++ vdr_vpts_offset_queue_purge(this); ++ ++if(0) ++ { ++fprintf(stderr, "B ---------------------------------------------\n"); ++ vdr_vpts_offset_t *p = this->vpts_offset_queue; ++ while (p) ++ { ++ fprintf(stderr, "B now: %12"PRId64", vpts: %12"PRId64", offset: %12"PRId64"\n", xine_get_current_vpts(this->stream), p->vpts, p->offset); ++ p = p->next; ++ } ++fprintf(stderr, "B =============================================\n"); ++ } ++ ++ this->vpts_offset_queue_changes--; ++ pthread_cond_broadcast(&this->vpts_offset_queue_changed_cond); ++ ++ this->last_disc_type = type; ++ ++ pthread_mutex_unlock(&this->vpts_offset_queue_lock); ++ ++ if (!this->trick_speed_mode) ++ { ++ xine_event_t event; ++ ++ event.type = XINE_EVENT_VDR_DISCONTINUITY; ++ event.data = 0; ++ event.data_length = type; ++ ++ xine_event_send(this->stream, &event); ++ } ++} ++ ++static void vdr_metronom_handle_audio_discontinuity(metronom_t *self, int type, int64_t disc_off) ++{ ++ vdr_metronom_t *this = (vdr_metronom_t *)self; ++ int64_t vpts_offset = vdr_vpts_offset_queue_change_begin(this->input, type); ++/* fprintf(stderr, "had A: %d, %"PRId64", %"PRId64", %"PRId64"\n", type, disc_off, xine_get_current_vpts(this->input->stream), this->stream_metronom->get_option(this->stream_metronom, METRONOM_VPTS_OFFSET)); */ ++ this->stream_metronom->handle_audio_discontinuity(this->stream_metronom, type, disc_off); ++/* fprintf(stderr, "had B: %d, %"PRId64", %"PRId64", %"PRId64"\n", type, disc_off, xine_get_current_vpts(this->input->stream), this->stream_metronom->get_option(this->stream_metronom, METRONOM_VPTS_OFFSET)); */ ++ vdr_vpts_offset_queue_change_end(this->input, type, disc_off, vpts_offset); ++} ++ ++static void vdr_metronom_handle_video_discontinuity(metronom_t *self, int type, int64_t disc_off) ++{ ++ vdr_metronom_t *this = (vdr_metronom_t *)self; ++ int64_t vpts_offset = vdr_vpts_offset_queue_change_begin(this->input, type); ++/* fprintf(stderr, "hvd A: %d, %"PRId64", %"PRId64", %"PRId64"\n", type, disc_off, xine_get_current_vpts(this->input->stream), this->stream_metronom->get_option(this->stream_metronom, METRONOM_VPTS_OFFSET)); */ ++ this->stream_metronom->handle_video_discontinuity(this->stream_metronom, type, disc_off); ++/* fprintf(stderr, "hvd B: %d, %"PRId64", %"PRId64", %"PRId64"\n", type, disc_off, xine_get_current_vpts(this->input->stream), this->stream_metronom->get_option(this->stream_metronom, METRONOM_VPTS_OFFSET)); */ ++ vdr_vpts_offset_queue_change_end(this->input, type, disc_off, vpts_offset); ++} ++ ++static void vdr_metronom_got_video_frame(metronom_t *self, vo_frame_t *frame) ++{ ++ vdr_metronom_t *this = (vdr_metronom_t *)self; ++ ++ if (this->input->trick_speed_mode && frame->pts) ++ { ++ pthread_mutex_lock(&this->input->metronom_thread_call_lock); ++ ++ pthread_mutex_lock(&this->input->metronom_thread_lock); ++ this->input->metronom_thread_request = frame->pts; ++ this->input->metronom_thread_reply = 0; ++ pthread_cond_broadcast(&this->input->metronom_thread_request_cond); ++ pthread_mutex_unlock(&this->input->metronom_thread_lock); ++ ++ vdr_metronom_handle_video_discontinuity(self, DISC_ABSOLUTE, frame->pts); ++ ++ pthread_mutex_lock(&this->input->metronom_thread_lock); ++ if (!this->input->metronom_thread_reply) ++ pthread_cond_wait(&this->input->metronom_thread_reply_cond, &this->input->metronom_thread_lock); ++ pthread_mutex_unlock(&this->input->metronom_thread_lock); ++ ++ pthread_mutex_unlock(&this->input->metronom_thread_call_lock); ++ } ++ ++ this->stream_metronom->got_video_frame(this->stream_metronom, frame); ++ ++ if (this->input->trick_speed_mode && frame->pts) ++ { ++/* fprintf(stderr, "vpts: %12ld, pts: %12ld, offset: %12ld\n", frame->vpts, frame->pts, this->stream_metronom->get_option(this->stream_metronom, METRONOM_VPTS_OFFSET)); */ ++ } ++} ++ ++static int64_t vdr_metronom_got_audio_samples(metronom_t *self, int64_t pts, int nsamples) ++{ ++ vdr_metronom_t *this = (vdr_metronom_t *)self; ++ return this->stream_metronom->got_audio_samples(this->stream_metronom, pts, nsamples); ++} ++ ++static int64_t vdr_metronom_got_spu_packet(metronom_t *self, int64_t pts) ++{ ++ vdr_metronom_t *this = (vdr_metronom_t *)self; ++ return this->stream_metronom->got_spu_packet(this->stream_metronom, pts); ++} ++ ++static void vdr_metronom_set_audio_rate(metronom_t *self, int64_t pts_per_smpls) ++{ ++ vdr_metronom_t *this = (vdr_metronom_t *)self; ++ this->stream_metronom->set_audio_rate(this->stream_metronom, pts_per_smpls); ++} ++ ++static void vdr_metronom_set_option(metronom_t *self, int option, int64_t value) ++{ ++ vdr_metronom_t *this = (vdr_metronom_t *)self; ++ this->stream_metronom->set_option(this->stream_metronom, option, value); ++} ++ ++static int64_t vdr_metronom_get_option(metronom_t *self, int option) ++{ ++ vdr_metronom_t *this = (vdr_metronom_t *)self; ++ return this->stream_metronom->get_option(this->stream_metronom, option); ++} ++ ++static void vdr_metronom_set_master(metronom_t *self, metronom_t *master) ++{ ++ vdr_metronom_t *this = (vdr_metronom_t *)self; ++ this->stream_metronom->set_master(this->stream_metronom, master); ++} ++ ++static void vdr_metronom_exit(metronom_t *self) ++{ ++ _x_abort(); ++} ++ ++ ++static input_plugin_t *vdr_class_get_instance(input_class_t *cls_gen, xine_stream_t *stream, ++ const char *data) ++{ ++ vdr_input_plugin_t *this; ++ char *mrl = strdup(data); ++ ++ if (!strncasecmp(mrl, "vdr:/", 5)) ++ lprintf("filename '%s'\n", mrl_to_path (mrl)); ++ else if (!strncasecmp(mrl, "netvdr:/", 5)) ++ lprintf("host '%s'\n", mrl_to_socket (mrl)); ++ else ++ { ++ free(mrl); ++ return NULL; ++ } ++ ++ /* ++ * mrl accepted and opened successfully at this point ++ * ++ * => create plugin instance ++ */ ++ ++ this = (vdr_input_plugin_t *)xine_xmalloc(sizeof (vdr_input_plugin_t)); ++ ++ this->stream = stream; ++ this->curpos = 0; ++ this->mrl = mrl; ++ this->fh = -1; ++ this->fh_control = -1; ++ this->fh_result = -1; ++ this->fh_event = -1; ++ ++ this->input_plugin.open = vdr_plugin_open; ++ this->input_plugin.get_capabilities = vdr_plugin_get_capabilities; ++ this->input_plugin.read = vdr_plugin_read; ++ this->input_plugin.read_block = vdr_plugin_read_block; ++ this->input_plugin.seek = vdr_plugin_seek; ++ this->input_plugin.get_current_pos = vdr_plugin_get_current_pos; ++ this->input_plugin.get_length = vdr_plugin_get_length; ++ this->input_plugin.get_blocksize = vdr_plugin_get_blocksize; ++ this->input_plugin.get_mrl = vdr_plugin_get_mrl; ++ this->input_plugin.dispose = vdr_plugin_dispose; ++ this->input_plugin.get_optional_data = vdr_plugin_get_optional_data; ++ this->input_plugin.input_class = cls_gen; ++ ++ this->cur_func = func_unknown; ++ this->cur_size = 0; ++ this->cur_done = 0; ++ ++ memset(this->osd, 0, sizeof (this->osd)); ++ ++ { ++ xine_osd_t *osd = xine_osd_new(this->stream, 0, 0, 16, 16); ++ uint32_t caps = xine_osd_get_capabilities(osd); ++ xine_osd_free(osd); ++ ++#ifdef XINE_OSD_CAP_ARGB_LAYER ++ this->osd_supports_argb_layer = !!(caps & XINE_OSD_CAP_ARGB_LAYER); ++#endif ++#ifdef XINE_OSD_CAP_CUSTOM_EXTENT ++ this->osd_supports_custom_extent = !!(caps & XINE_OSD_CAP_CUSTOM_EXTENT); ++#endif ++ } ++ ++ this->osd_buffer = 0; ++ this->osd_buffer_size = 0; ++ this->osd_unscaled_blending = 0; ++ this->trick_speed_mode = 0; ++ this->audio_channels = 0; ++ this->mute_mode = XINE_VDR_MUTE_SIMULATE; ++ this->volume_mode = XINE_VDR_VOLUME_CHANGE_HW; ++ this->last_volume = -1; ++ this->frame_size.x = 0; ++ this->frame_size.y = 0; ++ this->frame_size.w = 0; ++ this->frame_size.h = 0; ++ this->frame_size.r = 0; ++ ++ this->stream_external = 0; ++ this->event_queue_external = 0; ++ ++ pthread_mutex_init(&this->rpc_thread_shutdown_lock, 0); ++ pthread_cond_init(&this->rpc_thread_shutdown_cond, 0); ++ ++ pthread_mutex_init(&this->metronom_thread_lock, 0); ++ pthread_cond_init(&this->metronom_thread_request_cond, 0); ++ pthread_cond_init(&this->metronom_thread_reply_cond, 0); ++ pthread_mutex_init(&this->metronom_thread_call_lock, 0); ++ ++ pthread_mutex_init(&this->find_sync_point_lock, 0); ++ pthread_mutex_init(&this->adjust_zoom_lock, 0); ++ this->image4_3_zoom_x = 0; ++ this->image4_3_zoom_y = 0; ++ this->image16_9_zoom_x = 0; ++ this->image16_9_zoom_y = 0; ++ ++ this->event_queue = xine_event_new_queue(this->stream); ++ if (this->event_queue) ++ xine_event_create_listener_thread(this->event_queue, event_handler, this); ++ ++ this->metronom.input = this; ++ this->metronom.metronom.set_audio_rate = vdr_metronom_set_audio_rate; ++ this->metronom.metronom.got_video_frame = vdr_metronom_got_video_frame; ++ this->metronom.metronom.got_audio_samples = vdr_metronom_got_audio_samples; ++ this->metronom.metronom.got_spu_packet = vdr_metronom_got_spu_packet; ++ this->metronom.metronom.handle_audio_discontinuity = vdr_metronom_handle_audio_discontinuity; ++ this->metronom.metronom.handle_video_discontinuity = vdr_metronom_handle_video_discontinuity; ++ this->metronom.metronom.set_option = vdr_metronom_set_option; ++ this->metronom.metronom.get_option = vdr_metronom_get_option; ++ this->metronom.metronom.set_master = vdr_metronom_set_master; ++ this->metronom.metronom.exit = vdr_metronom_exit; ++ ++ this->metronom.stream_metronom = stream->metronom; ++ stream->metronom = &this->metronom.metronom; ++ ++ pthread_mutex_init(&this->vpts_offset_queue_lock, 0); ++ pthread_cond_init(&this->vpts_offset_queue_changed_cond, 0); ++ ++ return &this->input_plugin; ++} ++ ++/* ++ * vdr input plugin class stuff ++ */ ++ ++static const char *vdr_class_get_description(input_class_t *this_gen) ++{ ++ return _("VDR display device plugin"); ++} ++ ++static const char *vdr_class_get_identifier (input_class_t *this_gen) ++{ ++ return "VDR"; ++} ++ ++static void vdr_class_dispose (input_class_t *this_gen) ++{ ++ vdr_input_class_t *this = (vdr_input_class_t *)this_gen; ++ ++ free(this); ++} ++ ++static char **vdr_class_get_autoplay_list(input_class_t *this_gen, ++ int *num_files) ++{ ++ vdr_input_class_t *class = (vdr_input_class_t *)this_gen; ++ ++ *num_files = 1; ++ return (char **)class->mrls; ++} ++ ++void *vdr_input_init_plugin(xine_t *xine, void *data) ++{ ++ vdr_input_class_t *this; ++ ++ lprintf("init_class\n"); ++ ++ this = (vdr_input_class_t *)xine_xmalloc(sizeof (vdr_input_class_t)); ++ ++ this->xine = xine; ++ ++ this->mrls[ 0 ] = "vdr:/" VDR_ABS_FIFO_DIR "/stream#demux:mpeg_pes"; ++ this->mrls[ 1 ] = 0; ++ ++ this->input_class.get_instance = vdr_class_get_instance; ++ this->input_class.get_identifier = vdr_class_get_identifier; ++ this->input_class.get_description = vdr_class_get_description; ++ this->input_class.get_dir = NULL; ++ this->input_class.get_autoplay_list = vdr_class_get_autoplay_list; ++ this->input_class.dispose = vdr_class_dispose; ++ this->input_class.eject_media = NULL; ++ ++ return this; ++} +diff --git a/src/vdr/post_vdr_audio.c b/src/vdr/post_vdr_audio.c +new file mode 100644 +--- /dev/null ++++ b/src/vdr/post_vdr_audio.c +@@ -0,0 +1,285 @@ ++/* ++ * Copyright (C) 2000-2004 the xine project ++ * ++ * This file is part of xine, a free video player. ++ * ++ * xine 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. ++ * ++ * xine is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA ++ */ ++ ++/* ++ * select audio channel plugin for VDR ++ */ ++ ++#define LOG_MODULE "vdr_audio" ++#define LOG_VERBOSE ++/* ++#define LOG ++*/ ++ ++#include "xine_internal.h" ++#include "post.h" ++#include "combined_vdr.h" ++ ++ ++ ++typedef struct vdr_audio_post_plugin_s ++{ ++ post_plugin_t post_plugin; ++ ++ xine_event_queue_t *event_queue; ++ xine_stream_t *vdr_stream; ++ ++ uint8_t audio_channels; ++ int num_channels; ++ ++} ++vdr_audio_post_plugin_t; ++ ++ ++static void vdr_audio_select_audio(vdr_audio_post_plugin_t *this, uint8_t channels) ++{ ++ this->audio_channels = channels; ++} ++ ++ ++/* plugin class functions */ ++static post_plugin_t *vdr_audio_open_plugin(post_class_t *class_gen, int inputs, ++ xine_audio_port_t **audio_target, ++ xine_video_port_t **video_target); ++static char *vdr_audio_get_identifier(post_class_t *class_gen); ++static char *vdr_audio_get_description(post_class_t *class_gen); ++static void vdr_audio_class_dispose(post_class_t *class_gen); ++ ++/* plugin instance functions */ ++static void vdr_audio_dispose(post_plugin_t *this_gen); ++ ++/* replaced ao_port functions */ ++static int vdr_audio_port_open(xine_audio_port_t *port_gen, xine_stream_t *stream, ++ uint32_t bits, uint32_t rate, int mode); ++static void vdr_audio_port_put_buffer(xine_audio_port_t *port_gen, audio_buffer_t *buf, xine_stream_t *stream); ++ ++ ++ ++void *vdr_audio_init_plugin(xine_t *xine, void *data) ++{ ++ post_class_t *class = (post_class_t *)xine_xmalloc(sizeof (post_class_t)); ++ ++ if (!class) ++ return NULL; ++ ++ class->open_plugin = vdr_audio_open_plugin; ++ class->get_identifier = vdr_audio_get_identifier; ++ class->get_description = vdr_audio_get_description; ++ class->dispose = vdr_audio_class_dispose; ++ ++ return class; ++} ++ ++static post_plugin_t *vdr_audio_open_plugin(post_class_t *class_gen, int inputs, ++ xine_audio_port_t **audio_target, ++ xine_video_port_t **video_target) ++{ ++ vdr_audio_post_plugin_t *this = (vdr_audio_post_plugin_t *)xine_xmalloc(sizeof (vdr_audio_post_plugin_t)); ++ post_in_t *input; ++ post_out_t *output; ++ post_audio_port_t *port; ++/* ++fprintf(stderr, "~~~~~~~~~~ vdr open plugin\n"); ++*/ ++ if (!this || !audio_target || !audio_target[ 0 ]) ++ { ++ free(this); ++ return NULL; ++ } ++ ++ _x_post_init(&this->post_plugin, 1, 0); ++ this->post_plugin.dispose = vdr_audio_dispose; ++ ++ port = _x_post_intercept_audio_port(&this->post_plugin, audio_target[ 0 ], &input, &output); ++ port->new_port.open = vdr_audio_port_open; ++ port->new_port.put_buffer = vdr_audio_port_put_buffer; ++ ++ this->post_plugin.xine_post.audio_input[ 0 ] = &port->new_port; ++ ++ ++ ++ this->audio_channels = 0; ++ ++ return &this->post_plugin; ++} ++ ++static char *vdr_audio_get_identifier(post_class_t *class_gen) ++{ ++ return "vdr_audio"; ++} ++ ++static char *vdr_audio_get_description(post_class_t *class_gen) ++{ ++ return "modifies every audio frame as requested by VDR"; ++} ++ ++static void vdr_audio_class_dispose(post_class_t *class_gen) ++{ ++ free(class_gen); ++} ++ ++ ++static void vdr_audio_dispose(post_plugin_t *this_gen) ++{ ++/* ++fprintf(stderr, "~~~~~~~~~~ vdr dispose\n"); ++*/ ++ if (_x_post_dispose(this_gen)) ++ { ++ vdr_audio_post_plugin_t *this = (vdr_audio_post_plugin_t *)this_gen; ++ ++ if (this->vdr_stream) ++ xine_event_dispose_queue(this->event_queue); ++ ++ free(this_gen); ++ } ++} ++ ++static int vdr_audio_port_open(xine_audio_port_t *port_gen, xine_stream_t *stream, ++ uint32_t bits, uint32_t rate, int mode) { ++ ++ post_audio_port_t *port = (post_audio_port_t *)port_gen; ++ vdr_audio_post_plugin_t *this = (vdr_audio_post_plugin_t *)port->post; ++ ++ _x_post_rewire(&this->post_plugin); ++ _x_post_inc_usage(port); ++/* ++fprintf(stderr, "~~~~~~~~~~ vdr port open\n"); ++*/ ++ port->stream = stream; ++ port->bits = bits; ++ port->rate = rate; ++ port->mode = mode; ++ ++ this->num_channels = _x_ao_mode2channels(mode); ++ ++ return (port->original_port->open) (port->original_port, stream, bits, rate, mode ); ++} ++ ++ ++static void vdr_audio_port_put_buffer(xine_audio_port_t *port_gen, audio_buffer_t *buf, xine_stream_t *stream) ++{ ++ post_audio_port_t *port = (post_audio_port_t *)port_gen; ++ vdr_audio_post_plugin_t *this = (vdr_audio_post_plugin_t *)port->post; ++ xine_event_t *event; ++/* ++fprintf(stderr, "~~~~~~ vdr_audio\n"); ++*/ ++ if (this->vdr_stream ++ && !_x_continue_stream_processing(this->vdr_stream)) ++ { ++ this->vdr_stream = 0; ++ ++ xine_event_dispose_queue(this->event_queue); ++ this->event_queue = 0; ++ ++ this->audio_channels = 0; ++ } ++ ++ if (!this->vdr_stream ++ && vdr_is_vdr_stream(stream)) ++ { ++ this->event_queue = xine_event_new_queue(stream); ++ if (this->event_queue) ++ { ++ this->vdr_stream = stream; ++ ++ { ++ xine_event_t event; ++ ++ event.type = XINE_EVENT_VDR_PLUGINSTARTED; ++ event.data = 0; ++ event.data_length = 1; /* vdr_audio */ ++ ++ xine_event_send(this->vdr_stream, &event); ++ } ++ } ++ } ++ ++ if (this->event_queue) ++ { ++ while ((event = xine_event_get(this->event_queue))) ++ { ++ if (event->type == XINE_EVENT_VDR_SELECTAUDIO) ++ { ++ vdr_select_audio_data_t *data = (vdr_select_audio_data_t *)event->data; ++ ++ vdr_audio_select_audio(this, data->channels); ++ } ++ ++ xine_event_free(event); ++ } ++ } ++ ++ if (this->num_channels == 2 ++ && this->audio_channels != 0 ++ && this->audio_channels != 3) ++ { ++ audio_buffer_t *vdr_buf = port->original_port->get_buffer(port->original_port); ++ vdr_buf->num_frames = buf->num_frames; ++ vdr_buf->vpts = buf->vpts; ++ vdr_buf->frame_header_count = buf->frame_header_count; ++ vdr_buf->first_access_unit = buf->first_access_unit; ++ /* FIXME: The audio buffer should contain this info. ++ * We should not have to get it from the open call. ++ */ ++ vdr_buf->format.bits = buf->format.bits; ++ vdr_buf->format.rate = buf->format.rate; ++ vdr_buf->format.mode = buf->format.mode; ++ _x_extra_info_merge(vdr_buf->extra_info, buf->extra_info); ++ ++ { ++ int step = buf->format.bits / 8; ++ uint8_t *src = (uint8_t *)buf->mem; ++ uint8_t *dst = (uint8_t *)vdr_buf->mem; ++ ++ if (this->audio_channels == 2) ++ src += step; ++/* ++ fprintf(stderr, "~~~~~~~~~~ vdr port put buffer: channels: %d, %d\n" ++ , this->audio_channels ++ , buf->format.bits); ++*/ ++ int i, k; ++ for (i = 0; i < buf->num_frames; i++) ++ { ++ for (k = 0; k < step; k++) ++ *dst++ = *src++; ++ ++ src -= step; ++ ++ for (k = 0; k < step; k++) ++ *dst++ = *src++; ++ ++ src += step; ++ } ++ } ++ ++ /* pass data to original port */ ++ port->original_port->put_buffer(port->original_port, vdr_buf, stream); ++ ++ /* free data from origial buffer */ ++ buf->num_frames = 0; /* UNDOCUMENTED, but hey, it works! Force old audio_out buffer free. */ ++ } ++ ++ port->original_port->put_buffer(port->original_port, buf, stream); ++ ++ return; ++} +diff --git a/src/vdr/post_vdr_video.c b/src/vdr/post_vdr_video.c +new file mode 100644 +--- /dev/null ++++ b/src/vdr/post_vdr_video.c +@@ -0,0 +1,502 @@ ++/* ++ * Copyright (C) 2000-2004 the xine project ++ * ++ * This file is part of xine, a free video player. ++ * ++ * xine 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. ++ * ++ * xine is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA ++ */ ++ ++/* ++ * frame scaler plugin for VDR ++ */ ++ ++#define LOG_MODULE "vdr_video" ++/* ++#define LOG ++#define LOG_VERBOSE ++*/ ++ ++#include "xine_internal.h" ++#include "post.h" ++#include "combined_vdr.h" ++ ++ ++ ++typedef struct vdr_video_post_plugin_s ++{ ++ post_plugin_t post_plugin; ++ ++ xine_event_queue_t *event_queue; ++ xine_stream_t *vdr_stream; ++ ++ int8_t trick_speed_mode; ++ int8_t enabled; ++ ++ int32_t x; ++ int32_t y; ++ int32_t w; ++ int32_t h; ++ int32_t w_ref; ++ int32_t h_ref; ++ ++ int32_t old_frame_left; ++ int32_t old_frame_top; ++ int32_t old_frame_width; ++ int32_t old_frame_height; ++ double old_frame_ratio; ++ ++} ++vdr_video_post_plugin_t; ++ ++ ++static void vdr_video_set_video_window(vdr_video_post_plugin_t *this, int32_t x, int32_t y, int32_t w, int32_t h, int32_t w_ref, int32_t h_ref) ++{ ++ this->enabled = 0; ++ ++ this->x = x; ++ this->y = y; ++ this->w = w; ++ this->h = h; ++ this->w_ref = w_ref; ++ this->h_ref = h_ref; ++ ++ if (w != w_ref || h != h_ref) ++ this->enabled = 1; ++} ++ ++ ++/* plugin class functions */ ++static post_plugin_t *vdr_video_open_plugin(post_class_t *class_gen, int inputs, ++ xine_audio_port_t **audio_target, ++ xine_video_port_t **video_target); ++static char *vdr_video_get_identifier(post_class_t *class_gen); ++static char *vdr_video_get_description(post_class_t *class_gen); ++static void vdr_video_class_dispose(post_class_t *class_gen); ++ ++/* plugin instance functions */ ++static void vdr_video_dispose(post_plugin_t *this_gen); ++ ++/* route preprocessing functions check */ ++static int vdr_video_route_preprocessing_procs(post_video_port_t *port, vo_frame_t *frame); ++ ++/* replaced vo_frame functions */ ++static int vdr_video_draw(vo_frame_t *frame, xine_stream_t *stream); ++ ++ ++void *vdr_video_init_plugin(xine_t *xine, void *data) ++{ ++ post_class_t *class = (post_class_t *)xine_xmalloc(sizeof (post_class_t)); ++ ++ if (!class) ++ return NULL; ++ ++ class->open_plugin = vdr_video_open_plugin; ++ class->get_identifier = vdr_video_get_identifier; ++ class->get_description = vdr_video_get_description; ++ class->dispose = vdr_video_class_dispose; ++ ++ return class; ++} ++ ++static post_plugin_t *vdr_video_open_plugin(post_class_t *class_gen, int inputs, ++ xine_audio_port_t **audio_target, ++ xine_video_port_t **video_target) ++{ ++ vdr_video_post_plugin_t *this = (vdr_video_post_plugin_t *)xine_xmalloc(sizeof (vdr_video_post_plugin_t)); ++ post_in_t *input; ++ post_out_t *output; ++ post_video_port_t *port; ++ ++ if (!this || !video_target || !video_target[ 0 ]) ++ { ++ free(this); ++ return NULL; ++ } ++ ++ _x_post_init(&this->post_plugin, 0, 1); ++ this->post_plugin.dispose = vdr_video_dispose; ++ ++ port = _x_post_intercept_video_port(&this->post_plugin, video_target[ 0 ], &input, &output); ++ port->route_preprocessing_procs = vdr_video_route_preprocessing_procs; ++ port->new_frame->draw = vdr_video_draw; ++ this->post_plugin.xine_post.video_input[ 0 ] = &port->new_port; ++ ++ this->enabled = 0; ++ this->vdr_stream = 0; ++ this->event_queue = 0; ++ this->old_frame_left = 0; ++ this->old_frame_top = 0; ++ this->old_frame_width = 0; ++ this->old_frame_height = 0; ++ this->old_frame_ratio = 0; ++ this->trick_speed_mode = 0; ++ ++ return &this->post_plugin; ++} ++ ++static char *vdr_video_get_identifier(post_class_t *class_gen) ++{ ++ return "vdr"; ++} ++ ++static char *vdr_video_get_description(post_class_t *class_gen) ++{ ++ return "modifies every video frame as requested by VDR"; ++} ++ ++static void vdr_video_class_dispose(post_class_t *class_gen) ++{ ++ free(class_gen); ++} ++ ++ ++static void vdr_video_dispose(post_plugin_t *this_gen) ++{ ++ if (_x_post_dispose(this_gen)) ++ { ++ vdr_video_post_plugin_t *this = (vdr_video_post_plugin_t *)this_gen; ++ ++ if (this->vdr_stream) ++ { ++ xine_event_t event; ++ vdr_frame_size_changed_data_t event_data; ++ ++ event_data.x = 0; ++ event_data.y = 0; ++ event_data.w = 0; ++ event_data.h = 0; ++ ++ event.type = XINE_EVENT_VDR_FRAMESIZECHANGED; ++ event.data = &event_data; ++ event.data_length = sizeof (event_data); ++ ++ xine_event_send(this->vdr_stream, &event); ++ ++ xine_event_dispose_queue(this->event_queue); ++ } ++ ++ free(this_gen); ++ } ++} ++ ++static int vdr_video_route_preprocessing_procs(post_video_port_t *port, vo_frame_t *frame) ++{ ++ vdr_video_post_plugin_t *this = (vdr_video_post_plugin_t *)port->post; ++ return !this->enabled ++ || (frame->format != XINE_IMGFMT_YUY2 ++ && frame->format != XINE_IMGFMT_YV12); ++} ++ ++ ++static inline void vdr_video_scale(uint8_t *src, uint8_t *dst, int y_inc, int x_inc, int w_dst, int h_dst, int x, int y, int w, int h, int w_ref, int h_ref, int init) ++{ ++ int x0 = x * w_dst / w_ref; ++ int y0 = y * h_dst / h_ref; ++ ++ int x1 = ((x + w) * w_dst - 1 + w_ref) / w_ref; ++ int y1 = ((y + h) * h_dst - 1 + h_ref) / h_ref; ++ ++ int dx = x1 - x0; ++ int dy = y1 - y0; ++ ++ int yy, xx; ++ ++ int dy2 = dy + dy; ++ int h_dst2 = h_dst + h_dst; ++ int y_eps = h_dst - dy2; ++ ++ int dx2 = dx + dx; ++ int w_dst2 = w_dst + w_dst; ++ int x_eps0 = w_dst - dx2; ++ ++ for (yy = 0; yy < y0; yy++) ++ { ++ uint8_t *dst0 = dst; ++ ++ for (xx = 0; xx < w_dst; xx++) ++ { ++ *dst0 = init; ++ dst0 += x_inc; ++ } ++ ++ dst += y_inc; ++ } ++ ++ for (yy = y0; yy < y1; yy++) ++ { ++ uint8_t *dst0 = dst; ++ uint8_t *src0 = src; ++ ++ int x_eps = x_eps0; ++ ++ for (xx = 0; xx < x0; xx++) ++ { ++ *dst0 = init; ++ dst0 += x_inc; ++ } ++ ++ for (xx = x0; xx < x1; xx++) ++ { ++ *dst0 = *src0; ++ dst0 += x_inc; ++ ++ x_eps += w_dst2; ++ while (x_eps >= 0) ++ { ++ src0 += x_inc; ++ x_eps -= dx2; ++ } ++ } ++ ++ for (xx = x1; xx < w_dst; xx++) ++ { ++ *dst0 = init; ++ dst0 += x_inc; ++ } ++ ++ dst += y_inc; ++ ++ y_eps += h_dst2; ++ while (y_eps >= 0) ++ { ++ src += y_inc; ++ y_eps -= dy2; ++ } ++ } ++ ++ for (yy = y1; yy < h_dst; yy++) ++ { ++ uint8_t *dst0 = dst; ++ ++ for (xx = 0; xx < w_dst; xx++) ++ { ++ *dst0 = init; ++ dst0 += x_inc; ++ } ++ ++ dst += y_inc; ++ } ++} ++ ++static void vdr_video_scale_YUY2(vdr_video_post_plugin_t *this, vo_frame_t *src, vo_frame_t *dst) ++{ ++ int w = dst->width - dst->crop_left - dst->crop_right; ++ int h = dst->height - dst->crop_top - dst->crop_bottom; ++ int offset; ++ ++ if (w < 0) ++ w = 0; ++ ++ if (h < 0) ++ h = 0; ++ ++ offset = dst->pitches[ 0 ] * dst->crop_top + 2 * dst->crop_left; ++ vdr_video_scale(&src->base[ 0 ][ 0 ] + offset, &dst->base[ 0 ][ 0 ] + offset, dst->pitches[ 0 ], 2, w , h, this->x, this->y, this->w, this->h, this->w_ref, this->h_ref, 0x00); ++ offset = dst->pitches[ 0 ] * dst->crop_top + 4 * ((dst->crop_left + 1) / 2); ++ vdr_video_scale(&src->base[ 0 ][ 1 ] + offset, &dst->base[ 0 ][ 1 ] + offset, dst->pitches[ 0 ], 4, (w + 1) / 2, h, this->x, this->y, this->w, this->h, this->w_ref, this->h_ref, 0x80); ++ offset = dst->pitches[ 0 ] * dst->crop_top + 4 * ((dst->crop_left + 1) / 2); ++ vdr_video_scale(&src->base[ 0 ][ 3 ] + offset, &dst->base[ 0 ][ 3 ] + offset, dst->pitches[ 0 ], 4, (w + 1) / 2, h, this->x, this->y, this->w, this->h, this->w_ref, this->h_ref, 0x80); ++} ++ ++static void vdr_video_scale_YV12(vdr_video_post_plugin_t *this, vo_frame_t *src, vo_frame_t *dst) ++{ ++ int w = dst->width - dst->crop_left - dst->crop_right; ++ int h = dst->height - dst->crop_top - dst->crop_bottom; ++ int offset; ++ ++ if (w < 0) ++ w = 0; ++ ++ if (h < 0) ++ h = 0; ++ ++ offset = dst->pitches[ 0 ] * dst->crop_top + 1 * dst->crop_left; ++ vdr_video_scale(&src->base[ 0 ][ 0 ] + offset, &dst->base[ 0 ][ 0 ] + offset, dst->pitches[ 0 ], 1, w , h , this->x, this->y, this->w, this->h, this->w_ref, this->h_ref, 0x00); ++ offset = dst->pitches[ 1 ] * ((dst->crop_top + 1) / 2) + 1 * ((dst->crop_left + 1) / 2); ++ vdr_video_scale(&src->base[ 1 ][ 0 ] + offset, &dst->base[ 1 ][ 0 ] + offset, dst->pitches[ 1 ], 1, (w + 1) / 2, (h + 1) / 2, this->x, this->y, this->w, this->h, this->w_ref, this->h_ref, 0x80); ++ offset = dst->pitches[ 2 ] * ((dst->crop_top + 1) / 2) + 1 * ((dst->crop_left + 1) / 2); ++ vdr_video_scale(&src->base[ 2 ][ 0 ] + offset, &dst->base[ 2 ][ 0 ] + offset, dst->pitches[ 2 ], 1, (w + 1) / 2, (h + 1) / 2, this->x, this->y, this->w, this->h, this->w_ref, this->h_ref, 0x80); ++} ++ ++ ++static int vdr_video_draw(vo_frame_t *frame, xine_stream_t *stream) ++{ ++ post_video_port_t *port = (post_video_port_t *)frame->port; ++ vdr_video_post_plugin_t *this = (vdr_video_post_plugin_t *)port->post; ++ vo_frame_t *vdr_frame; ++ xine_event_t *event; ++ int skip; ++ ++ if (this->vdr_stream ++ && !_x_continue_stream_processing(this->vdr_stream)) ++ { ++ this->vdr_stream = 0; ++ ++ xine_event_dispose_queue(this->event_queue); ++ this->event_queue = 0; ++ ++ this->old_frame_left = 0; ++ this->old_frame_top = 0; ++ this->old_frame_width = 0; ++ this->old_frame_height = 0; ++ this->old_frame_ratio = 0; ++ } ++ ++ if (!this->vdr_stream ++ && vdr_is_vdr_stream(stream)) ++ { ++ this->event_queue = xine_event_new_queue(stream); ++ if (this->event_queue) ++ { ++ this->vdr_stream = stream; ++ ++ { ++ xine_event_t event; ++ ++ event.type = XINE_EVENT_VDR_PLUGINSTARTED; ++ event.data = 0; ++ event.data_length = 0; /* vdr_video */ ++ ++ xine_event_send(this->vdr_stream, &event); ++ } ++ } ++ } ++ ++ if (this->event_queue) ++ { ++ while ((event = xine_event_get(this->event_queue))) ++ { ++ if (event->type == XINE_EVENT_VDR_SETVIDEOWINDOW) ++ { ++ vdr_set_video_window_data_t *data = (vdr_set_video_window_data_t *)event->data; ++ ++ vdr_video_set_video_window(this, data->x, data->y, data->w, data->h, data->w_ref, data->h_ref); ++ } ++ else if (event->type == XINE_EVENT_VDR_TRICKSPEEDMODE) ++ { ++/* ++ fprintf(stderr, "###############################: %p, %d\n", event->data, event->data_length); ++ this->trick_speed_mode = (0 != event->data_length); ++*/ ++ } ++ ++ xine_event_free(event); ++ } ++ } ++ ++ { ++ int32_t frame_left = frame->crop_left; ++ int32_t frame_width = frame->width - frame->crop_left - frame->crop_right; ++ int32_t frame_top = frame->crop_top; ++ int32_t frame_height = frame->height - frame->crop_top - frame->crop_bottom; ++ double frame_ratio = frame->ratio; ++ ++ if (frame_left < 0) ++ frame_left = 0; ++ if (frame_width > frame->width) ++ frame_width = frame->width; ++ if (frame_top < 0) ++ frame_top = 0; ++ if (frame_height > frame->height) ++ frame_height = frame->height; ++ ++ if (this->vdr_stream ++ && frame_width != 0 ++ && frame_height != 0 ++ && (this->old_frame_left != frame_left ++ || this->old_frame_top != frame_top ++ || this->old_frame_width != frame_width ++ || this->old_frame_height != frame_height ++ || this->old_frame_ratio != frame_ratio)) ++ { ++ xine_event_t event; ++ vdr_frame_size_changed_data_t event_data; ++ ++ event_data.x = frame_left; ++ event_data.y = frame_top; ++ event_data.w = frame_width; ++ event_data.h = frame_height; ++ event_data.r = frame_ratio; ++ ++ xprintf(this->vdr_stream->xine, XINE_VERBOSITY_LOG, ++ _(LOG_MODULE ": osd: (%d, %d)-(%d, %d)@%lg\n"), frame_left, frame_top, frame_width, frame_height, frame_ratio); ++ ++ event.type = XINE_EVENT_VDR_FRAMESIZECHANGED; ++ event.data = &event_data; ++ event.data_length = sizeof (event_data); ++ ++ xine_event_send(this->vdr_stream, &event); ++ ++ this->old_frame_left = frame_left; ++ this->old_frame_top = frame_top; ++ this->old_frame_width = frame_width; ++ this->old_frame_height = frame_height; ++ this->old_frame_ratio = frame_ratio; ++ } ++ } ++/* ++ fprintf(stderr, "~~~~~~~~~~~~ trickspeedmode: %d\n", this->trick_speed_mode); ++ ++ if (this->vdr_stream ++ && this->trick_speed_mode) ++ { ++ frame->pts = 0; ++ frame->next->pts = 0; ++ } ++*/ ++#if defined(LOG) && defined(LOG_VERBOSE) ++ { ++ int a = 0, b = 0, c = 0, d = 0; ++ if (stream) ++ _x_query_buffer_usage(stream, &a, &b, &c, &d); ++ lprintf("buffer usage: %3d, %2d, %2d, %2d, %p\n", a, b, c, d, stream); ++ } ++#endif ++ ++ if (!this->enabled ++ || frame->bad_frame ++ || (frame->format != XINE_IMGFMT_YUY2 ++ && frame->format != XINE_IMGFMT_YV12) ++ || frame->proc_frame ++ || frame->proc_slice) ++ { ++ _x_post_frame_copy_down(frame, frame->next); ++ skip = frame->next->draw(frame->next, stream); ++ _x_post_frame_copy_up(frame, frame->next); ++ return skip; ++ } ++ ++ vdr_frame = port->original_port->get_frame(port->original_port, ++ frame->width, frame->height, frame->ratio, frame->format, frame->flags | VO_BOTH_FIELDS); ++ ++ _x_post_frame_copy_down(frame, vdr_frame); ++ ++ switch (vdr_frame->format) ++ { ++ case XINE_IMGFMT_YUY2: ++ vdr_video_scale_YUY2(this, frame, vdr_frame); ++ break; ++ ++ case XINE_IMGFMT_YV12: ++ vdr_video_scale_YV12(this, frame, vdr_frame); ++ break; ++ } ++ ++ skip = vdr_frame->draw(vdr_frame, stream); ++ _x_post_frame_copy_up(frame, vdr_frame); ++ vdr_frame->free(vdr_frame); ++ ++ return skip; ++} +diff --git a/src/vdr/vdr.h b/src/vdr/vdr.h +new file mode 100644 +--- /dev/null ++++ b/src/vdr/vdr.h +@@ -0,0 +1,665 @@ ++/* ++ * Copyright (C) 2000-2004 the xine project ++ * ++ * This file is part of xine, a free video player. ++ * ++ * xine 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. ++ * ++ * xine is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA ++ */ ++ ++#ifndef __VDR_H ++#define __VDR_H ++ ++ ++#define XINE_VDR_VERSION 901 ++ ++ ++enum funcs ++{ ++ func_unknown = -1 ++ , func_nop ++ , func_osd_new ++ , func_osd_free ++ , func_osd_show ++ , func_osd_hide ++ , func_osd_set_position ++ , func_osd_draw_bitmap ++ , func_set_color ++ , func_clear ++ , func_mute ++ , func_set_volume ++ , func_set_speed ++ , func_set_prebuffer ++ , func_metronom ++ , func_start ++ , func_wait ++ , func_setup ++ , func_grab_image ++ , func_get_pts ++ , func_flush ++ , func_first_frame ++ , func_still_frame ++ , func_video_size ++ , func_set_video_window ++ , func_osd_flush ++ , func_play_external ++ , func_key ++ , func_frame_size ++ , func_reset_audio ++ , func_select_audio ++ , func_trick_speed_mode ++ , func_get_version ++ , func_discontinuity ++ , func_query_capabilities ++}; ++ ++enum keys ++{ ++ key_none, ++ key_up, ++ key_down, ++ key_menu, ++ key_ok, ++ key_back, ++ key_left, ++ key_right, ++ key_red, ++ key_green, ++ key_yellow, ++ key_blue, ++ key_0, ++ key_1, ++ key_2, ++ key_3, ++ key_4, ++ key_5, ++ key_6, ++ key_7, ++ key_8, ++ key_9, ++ key_play, ++ key_pause, ++ key_stop, ++ key_record, ++ key_fast_fwd, ++ key_fast_rew, ++ key_power, ++ key_channel_plus, ++ key_channel_minus, ++ key_volume_plus, ++ key_volume_minus, ++ key_mute, ++ key_schedule, ++ key_channels, ++ key_timers, ++ key_recordings, ++ key_setup, ++ key_commands, ++ key_user1, ++ key_user2, ++ key_user3, ++ key_user4, ++ key_user5, ++ key_user6, ++ key_user7, ++ key_user8, ++ key_user9, ++ key_audio, ++ key_info, ++ key_channel_previous, ++ key_next, ++ key_previous, ++ key_subtitles, ++}; ++ ++ ++ ++typedef struct __attribute__((packed)) data_header_s ++{ ++ uint32_t func:8; ++ uint32_t len:24; ++} ++data_header_t; ++ ++ ++ ++typedef data_header_t result_header_t; ++typedef data_header_t event_header_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_nop_s ++{ ++ data_header_t header; ++} ++data_nop_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_osd_new_s ++{ ++ data_header_t header; ++ ++ uint8_t window; ++ int16_t x; ++ int16_t y; ++ uint16_t width; ++ uint16_t height; ++ uint16_t w_ref; ++ uint16_t h_ref; ++} ++data_osd_new_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_osd_free_s ++{ ++ data_header_t header; ++ ++ uint8_t window; ++} ++data_osd_free_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_osd_show_s ++{ ++ data_header_t header; ++ ++ uint8_t window; ++} ++data_osd_show_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_osd_hide_s ++{ ++ data_header_t header; ++ ++ uint8_t window; ++} ++data_osd_hide_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_osd_flush_s ++{ ++ data_header_t header; ++} ++data_osd_flush_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_play_external_s ++{ ++ data_header_t header; ++} ++data_play_external_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_osd_set_position_s ++{ ++ data_header_t header; ++ ++ uint8_t window; ++ int16_t x; ++ int16_t y; ++} ++data_osd_set_position_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_osd_draw_bitmap_s ++{ ++ data_header_t header; ++ ++ uint8_t window; ++ int16_t x; ++ int16_t y; ++ uint16_t width; ++ uint16_t height; ++ uint8_t argb; ++} ++data_osd_draw_bitmap_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_set_color_s ++{ ++ data_header_t header; ++ ++ uint8_t window; ++ uint8_t index; ++ uint8_t num; ++} ++data_set_color_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_flush_s ++{ ++ data_header_t header; ++ ++ int32_t ms_timeout; ++ uint8_t just_wait; ++} ++data_flush_t; ++ ++ ++ ++typedef struct __attribute__((packed)) result_flush_s ++{ ++ result_header_t header; ++ ++ uint8_t timed_out; ++} ++result_flush_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_clear_s ++{ ++ data_header_t header; ++ ++ int32_t n; ++ int8_t s; ++ uint8_t i; ++} ++data_clear_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_mute_s ++{ ++ data_header_t header; ++ ++ uint8_t mute; ++} ++data_mute_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_set_volume_s ++{ ++ data_header_t header; ++ ++ uint8_t volume; ++} ++data_set_volume_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_set_speed_s ++{ ++ data_header_t header; ++ ++ int32_t speed; ++} ++data_set_speed_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_set_prebuffer_s ++{ ++ data_header_t header; ++ ++ uint32_t prebuffer; ++} ++data_set_prebuffer_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_metronom_s ++{ ++ data_header_t header; ++ ++ int64_t pts; ++ uint32_t flags; ++} ++data_metronom_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_start_s ++{ ++ data_header_t header; ++} ++data_start_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_wait_s ++{ ++ data_header_t header; ++ uint8_t id; ++} ++data_wait_t; ++ ++ ++ ++typedef struct __attribute__((packed)) result_wait_s ++{ ++ result_header_t header; ++} ++result_wait_t; ++ ++ ++ ++#define XINE_VDR_VOLUME_IGNORE 0 ++#define XINE_VDR_VOLUME_CHANGE_HW 1 ++#define XINE_VDR_VOLUME_CHANGE_SW 2 ++ ++#define XINE_VDR_MUTE_IGNORE 0 ++#define XINE_VDR_MUTE_EXECUTE 1 ++#define XINE_VDR_MUTE_SIMULATE 2 ++ ++typedef struct __attribute__((packed)) data_setup_s ++{ ++ data_header_t header; ++ ++ uint8_t osd_unscaled_blending; ++ uint8_t volume_mode; ++ uint8_t mute_mode; ++ uint16_t image4_3_zoom_x; ++ uint16_t image4_3_zoom_y; ++ uint16_t image16_9_zoom_x; ++ uint16_t image16_9_zoom_y; ++} ++data_setup_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_first_frame_s ++{ ++ data_header_t header; ++} ++data_first_frame_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_still_frame_s ++{ ++ data_header_t header; ++} ++data_still_frame_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_set_video_window_s ++{ ++ data_header_t header; ++ ++ uint32_t x; ++ uint32_t y; ++ uint32_t w; ++ uint32_t h; ++ uint32_t w_ref; ++ uint32_t h_ref; ++} ++data_set_video_window_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_grab_image_s ++{ ++ data_header_t header; ++} ++data_grab_image_t; ++ ++ ++ ++typedef struct __attribute__((packed)) result_grab_image_s ++{ ++ result_header_t header; ++ ++ int32_t width; ++ int32_t height; ++ int32_t ratio; ++ int32_t format; ++ int32_t interlaced; ++ int32_t crop_left; ++ int32_t crop_right; ++ int32_t crop_top; ++ int32_t crop_bottom; ++} ++result_grab_image_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_get_pts_s ++{ ++ data_header_t header; ++ int32_t ms_timeout; ++} ++data_get_pts_t; ++ ++ ++ ++typedef struct __attribute__((packed)) result_get_pts_s ++{ ++ result_header_t header; ++ ++ int64_t pts; ++ int8_t queued; ++} ++result_get_pts_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_get_version_s ++{ ++ data_header_t header; ++} ++data_get_version_t; ++ ++ ++ ++typedef struct __attribute__((packed)) result_get_version_s ++{ ++ result_header_t header; ++ ++ int32_t version; ++} ++result_get_version_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_video_size_s ++{ ++ data_header_t header; ++} ++data_video_size_t; ++ ++ ++ ++typedef struct __attribute__((packed)) result_video_size_s ++{ ++ result_header_t header; ++ ++ int32_t left; ++ int32_t top; ++ int32_t width; ++ int32_t height; ++ int32_t ratio; ++ int32_t zoom_x; ++ int32_t zoom_y; ++} ++result_video_size_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_reset_audio_s ++{ ++ data_header_t header; ++} ++data_reset_audio_t; ++ ++ ++ ++typedef struct __attribute__((packed)) event_key_s ++{ ++ event_header_t header; ++ ++ uint32_t key; ++} ++event_key_t; ++ ++ ++ ++typedef struct __attribute__((packed)) event_frame_size_s ++{ ++ event_header_t header; ++ ++ int32_t left; ++ int32_t top; ++ int32_t width; ++ int32_t height; ++ int32_t zoom_x; ++ int32_t zoom_y; ++} ++event_frame_size_t; ++ ++ ++ ++typedef struct __attribute__((packed)) event_play_external_s ++{ ++ event_header_t header; ++ ++ uint32_t key; ++} ++event_play_external_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_select_audio_s ++{ ++ data_header_t header; ++ ++ uint8_t channels; ++} ++data_select_audio_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_trick_speed_mode_s ++{ ++ data_header_t header; ++ ++ uint8_t on; ++} ++data_trick_speed_mode_t; ++ ++ ++ ++typedef struct __attribute__((packed)) event_discontinuity_s ++{ ++ event_header_t header; ++ ++ int32_t type; ++} ++event_discontinuity_t; ++ ++ ++ ++typedef struct __attribute__((packed)) data_query_capabilities_s ++{ ++ data_header_t header; ++} ++data_query_capabilities_t; ++ ++ ++ ++typedef struct __attribute__((packed)) result_query_capabilities_s ++{ ++ result_header_t header; ++ ++ uint8_t osd_max_num_windows; ++ uint8_t osd_palette_max_depth; ++ uint8_t osd_palette_is_shared; ++ uint8_t osd_supports_argb_layer; ++ uint8_t osd_supports_custom_extent; ++} ++result_query_capabilities_t; ++ ++ ++ ++typedef union __attribute__((packed)) data_union_u ++{ ++ data_header_t header; ++ data_nop_t nop; ++ data_osd_new_t osd_new; ++ data_osd_free_t osd_free; ++ data_osd_show_t osd_show; ++ data_osd_hide_t osd_hide; ++ data_osd_set_position_t osd_set_position; ++ data_osd_draw_bitmap_t osd_draw_bitmap; ++ data_set_color_t set_color; ++ data_flush_t flush; ++ data_clear_t clear; ++ data_mute_t mute; ++ data_set_volume_t set_volume; ++ data_set_speed_t set_speed; ++ data_set_prebuffer_t set_prebuffer; ++ data_metronom_t metronom; ++ data_start_t start; ++ data_wait_t wait; ++ data_setup_t setup; ++ data_grab_image_t grab_image; ++ data_get_pts_t get_pts; ++ data_first_frame_t first_frame; ++ data_still_frame_t still_frame; ++ data_video_size_t video_size; ++ data_set_video_window_t set_video_window; ++ data_osd_flush_t osd_flush; ++ data_play_external_t play_external; ++ data_reset_audio_t reset_audio; ++ data_select_audio_t select_audio; ++ data_trick_speed_mode_t trick_speed_mode; ++ data_get_version_t get_version; ++ data_query_capabilities_t query_capabilities; ++} ++data_union_t; ++ ++ ++ ++typedef union __attribute__((packed)) result_union_u ++{ ++ result_header_t header; ++ result_grab_image_t grab_image; ++ result_get_pts_t get_pts; ++ result_flush_t flush; ++ result_video_size_t video_size; ++ result_get_version_t get_version; ++ result_wait_t wait; ++ result_query_capabilities_t query_capabilities; ++} ++result_union_t; ++ ++ ++ ++typedef union __attribute__((packed)) event_union_u ++{ ++ event_header_t header; ++ event_key_t key; ++ event_frame_size_t frame_size; ++ event_play_external_t play_external; ++ event_discontinuity_t discontinuity; ++} ++event_union_t; ++ ++ ++ ++#endif /* __VDR_H */ ++ +diff --git a/src/video_out/video_out_xvmc.c b/src/video_out/video_out_xvmc.c +--- a/src/video_out/video_out_xvmc.c ++++ b/src/video_out/video_out_xvmc.c +@@ -486,9 +486,9 @@ static void xvmc_render_macro_blocks(vo_ + int second_field, + xvmc_macroblocks_t *macroblocks) { + xvmc_driver_t *this = (xvmc_driver_t *) current_image->driver; +- xvmc_frame_t *current_frame = (xvmc_frame_t *) current_image; +- xvmc_frame_t *forward_frame = (xvmc_frame_t *) forward_ref_image; +- xvmc_frame_t *backward_frame = (xvmc_frame_t *) backward_ref_image; ++ xvmc_frame_t *current_frame = XVMC_FRAME(current_image); ++ xvmc_frame_t *forward_frame = XVMC_FRAME(forward_ref_image); ++ xvmc_frame_t *backward_frame = XVMC_FRAME(backward_ref_image); + int flags; + + lprintf ("xvmc_render_macro_blocks\n"); +@@ -561,6 +561,7 @@ static vo_frame_t *xvmc_alloc_frame (vo_ + return NULL; + + frame->vo_frame.accel_data = &frame->xvmc_data; ++ frame->xvmc_data.vo_frame = &frame->vo_frame; + + /* keep track of frames and how many frames alocated. */ + this->frames[this->num_frame_buffers++] = frame; +diff --git a/src/video_out/video_out_xxmc.c b/src/video_out/video_out_xxmc.c +--- a/src/video_out/video_out_xxmc.c ++++ b/src/video_out/video_out_xxmc.c +@@ -365,15 +365,15 @@ static int xxmc_lock_and_validate_surfac + + switch(pc_type) { + case XINE_PICT_B_TYPE: +- frame = (xxmc_frame_t *) bw_frame; ++ frame = XXMC_FRAME(bw_frame); + if (!xxmc_xvmc_surface_valid( driver, frame->xvmc_surf)) break; + /* fall through */ + case XINE_PICT_P_TYPE: +- frame = (xxmc_frame_t *) fw_frame; ++ frame = XXMC_FRAME(fw_frame); + if (!xxmc_xvmc_surface_valid( driver, frame->xvmc_surf)) break; + /* fall through */ + default: +- frame = (xxmc_frame_t *) cur_frame; ++ frame = XXMC_FRAME(cur_frame); + if (!xxmc_xvmc_surface_valid( driver, frame->xvmc_surf)) break; + return 0; + } +@@ -404,7 +404,7 @@ static void xvmc_flush(vo_frame_t *this_ + { + + xxmc_frame_t +- *frame = (xxmc_frame_t *) this_gen; ++ *frame = XXMC_FRAME(this_gen); + xxmc_driver_t + *driver = (xxmc_driver_t *) this_gen->driver; + +@@ -452,6 +452,7 @@ static void xxmc_duplicate_frame_data(vo + return; + } + this->xxmc_data = *xxmc; ++ this->xxmc_data.xvmc.vo_frame = &this->vo_frame; + this->width = original->width; + this->height = original->height; + this->format = original->format; +@@ -566,6 +567,7 @@ static vo_frame_t *xxmc_alloc_frame (vo_ + frame->vo_frame.driver = this_gen; + frame->last_sw_format = 0; + frame->vo_frame.accel_data = &frame->xxmc_data; ++ frame->xxmc_data.xvmc.vo_frame = &frame->vo_frame; + frame->image = NULL; + + xprintf (this->xine, XINE_VERBOSITY_DEBUG, "Allocating frame\n"); +@@ -1213,10 +1215,17 @@ static void xxmc_do_update_frame(vo_driv + double ratio, int format, int flags) { + + xxmc_driver_t *this = (xxmc_driver_t *) this_gen; +- xxmc_frame_t *frame = (xxmc_frame_t *) frame_gen; ++ xxmc_frame_t *frame = XXMC_FRAME(frame_gen); + + if ( XINE_IMGFMT_XXMC == format ) { + xine_xxmc_t *xxmc = &frame->xxmc_data; ++ vo_frame_t orig_frame_content; ++ ++ if (frame_gen != &frame->vo_frame) { ++ /* this is an intercepted frame, so we need to detect and propagate any ++ * changes on the original vo_frame to all the intercepted frames */ ++ xine_fast_memcpy(&orig_frame_content, &frame->vo_frame, sizeof (vo_frame_t)); ++ } + + xvmc_context_writer_lock( &this->xvmc_lock); + if (xxmc_accel_update(this, this->last_accel_request, xxmc->acceleration) || +@@ -1232,7 +1241,7 @@ static void xxmc_do_update_frame(vo_driv + if (this->contextActive) + xxmc_frame_updates(this, frame, 1); + +- xxmc_do_update_frame_xv(this_gen, frame_gen, width, height, ratio, ++ xxmc_do_update_frame_xv(this_gen, &frame->vo_frame, width, height, ratio, + xxmc->fallback_format, flags); + + if (!this->contextActive) { +@@ -1246,6 +1255,33 @@ static void xxmc_do_update_frame(vo_driv + + xvmc_context_writer_unlock( &this->xvmc_lock); + ++ if (frame_gen != &frame->vo_frame) { ++ /* this is an intercepted frame, so we need to detect and propagate any ++ * changes on the original vo_frame to all the intercepted frames */ ++ unsigned char *p0 = (unsigned char *)&orig_frame_content; ++ unsigned char *p1 = (unsigned char *)&frame->vo_frame; ++ int i; ++ for (i = 0; i < sizeof (vo_frame_t); i++) { ++ if (*p0 != *p1) { ++ /* propagate the change */ ++ vo_frame_t *f = frame_gen; ++ while (f->next) { ++ /* serveral restrictions apply when intercepting XXMC frames. So let's check ++ * the intercepted frames before modifing them and fail otherwise. */ ++ unsigned char *p = (unsigned char *)f + i; ++ if (*p != *p0) { ++ xprintf(this->xine, XINE_VERBOSITY_DEBUG, "xxmc_do_update_frame: a post plugin violates the restrictions on intercepting XXMC frames\n"); ++ _x_abort(); ++ } ++ ++ *p = *p1; ++ f = f->next; ++ } ++ } ++ p0++; ++ p1++; ++ } ++ } + } else { + /* switch back to an unaccelerated context */ + if (this->last_accel_request != 0xFFFFFFFF) { +@@ -1253,7 +1289,7 @@ static void xxmc_do_update_frame(vo_driv + xxmc_xvmc_update_context(this, frame, width, height, 0); + } + frame->vo_frame.proc_duplicate_frame_data = NULL; +- xxmc_do_update_frame_xv(this_gen, frame_gen, width, height, ratio, ++ xxmc_do_update_frame_xv(this_gen, &frame->vo_frame, width, height, ratio, + format, flags); + } + } +diff --git a/src/video_out/xvmc_mocomp.c b/src/video_out/xvmc_mocomp.c +--- a/src/video_out/xvmc_mocomp.c ++++ b/src/video_out/xvmc_mocomp.c +@@ -70,9 +70,9 @@ static void xvmc_render_macro_blocks(vo_ + int second_field, + xvmc_macroblocks_t *macroblocks) { + xxmc_driver_t *this = (xxmc_driver_t *) current_image->driver; +- xxmc_frame_t *current_frame = (xxmc_frame_t *) current_image; +- xxmc_frame_t *forward_frame = (xxmc_frame_t *) forward_ref_image; +- xxmc_frame_t *backward_frame = (xxmc_frame_t *) backward_ref_image; ++ xxmc_frame_t *current_frame = XXMC_FRAME(current_image); ++ xxmc_frame_t *forward_frame = XXMC_FRAME(forward_ref_image); ++ xxmc_frame_t *backward_frame = XXMC_FRAME(backward_ref_image); + int flags; + + lprintf ("xvmc_render_macro_blocks\n"); +diff --git a/src/video_out/xvmc_vld.c b/src/video_out/xvmc_vld.c +--- a/src/video_out/xvmc_vld.c ++++ b/src/video_out/xvmc_vld.c +@@ -32,12 +32,12 @@ void xvmc_vld_frame(struct vo_frame_s *t + { + vo_frame_t *this = (vo_frame_t *) this_gen; + xxmc_frame_t +- *cf = (xxmc_frame_t *) this; ++ *cf = XXMC_FRAME(this); + xine_vld_frame_t + *vft = &(cf->xxmc_data.vld_frame); + xxmc_frame_t +- *ff = (xxmc_frame_t *) vft->forward_reference_frame, +- *bf = (xxmc_frame_t *) vft->backward_reference_frame; ++ *ff = XXMC_FRAME(vft->forward_reference_frame), ++ *bf = XXMC_FRAME(vft->backward_reference_frame); + XvMCMpegControl ctl; + xxmc_driver_t + *driver = (xxmc_driver_t *) cf->vo_frame.driver; +@@ -104,7 +104,7 @@ void xvmc_vld_slice(vo_frame_t *this_gen + void xvmc_vld_slice(vo_frame_t *this_gen) + { + xxmc_frame_t +- *cf = (xxmc_frame_t *) this_gen; ++ *cf = XXMC_FRAME(this_gen); + xxmc_driver_t + *driver = (xxmc_driver_t *) cf->vo_frame.driver; + +diff --git a/src/xine-engine/accel_xvmc.h b/src/xine-engine/accel_xvmc.h +--- a/src/xine-engine/accel_xvmc.h ++++ b/src/xine-engine/accel_xvmc.h +@@ -65,6 +65,7 @@ typedef struct xine_vld_frame_s { + + + typedef struct xine_xvmc_s { ++ vo_frame_t *vo_frame; + xine_macroblocks_t *macroblocks; + void (*proc_macro_block)(int x,int y,int mb_type, + int motion_type,int (*mv_field_sel)[2], +@@ -73,6 +74,9 @@ typedef struct xine_xvmc_s { + vo_frame_t *backward_ref_frame,int picture_structure, + int second_field,int (*f_mot_pmv)[2],int (*b_mot_pmv)[2]); + } xine_xvmc_t ; ++ ++#define XVMC_DATA(frame_gen) ((frame_gen) ? (xine_xvmc_t *)(frame_gen)->accel_data : (xine_xvmc_t *)0) ++#define XVMC_FRAME(frame_gen) ((frame_gen) ? (xvmc_frame_t *)XVMC_DATA(frame_gen)->vo_frame : (xvmc_frame_t *)0) + + typedef struct xine_xxmc_s { + +@@ -108,6 +112,9 @@ typedef struct xine_xxmc_s { + void (*proc_xxmc_unlock) (vo_driver_t *this_gen); + } xine_xxmc_t; + ++#define XXMC_DATA(frame_gen) ((frame_gen) ? (xine_xxmc_t *)(frame_gen)->accel_data : (xine_xxmc_t *)0) ++#define XXMC_FRAME(frame_gen) ((frame_gen) ? (xxmc_frame_t *)XXMC_DATA(frame_gen)->xvmc.vo_frame : (xxmc_frame_t *)0) ++ + /* + * Register XvMC stream types here. + */ +diff --git a/src/xine-engine/post.c b/src/xine-engine/post.c +--- a/src/xine-engine/post.c ++++ b/src/xine-engine/post.c +@@ -144,6 +144,14 @@ static void post_video_flush(xine_video_ + if (port->port_lock) pthread_mutex_unlock(port->port_lock); + } + ++static void post_video_trigger_drawing(xine_video_port_t *port_gen) { ++ post_video_port_t *port = (post_video_port_t *)port_gen; ++ ++ if (port->port_lock) pthread_mutex_lock(port->port_lock); ++ port->original_port->trigger_drawing(port->original_port); ++ if (port->port_lock) pthread_mutex_unlock(port->port_lock); ++} ++ + static int post_video_status(xine_video_port_t *port_gen, xine_stream_t *stream, + int *width, int *height, int64_t *img_duration) { + post_video_port_t *port = (post_video_port_t *)port_gen; +@@ -187,6 +195,7 @@ static int post_video_rewire(xine_post_o + if (!new_port) + return 0; + ++ this->running_ticket->lock_port_rewiring(this->running_ticket, -1); + this->running_ticket->revoke(this->running_ticket, 1); + + if (input_port->original_port->status(input_port->original_port, input_port->stream, +@@ -197,6 +206,7 @@ static int post_video_rewire(xine_post_o + input_port->original_port = new_port; + + this->running_ticket->issue(this->running_ticket, 1); ++ this->running_ticket->unlock_port_rewiring(this->running_ticket); + + return 1; + } +@@ -218,6 +228,7 @@ post_video_port_t *_x_post_intercept_vid + port->new_port.exit = post_video_exit; + port->new_port.get_overlay_manager = post_video_get_overlay_manager; + port->new_port.flush = post_video_flush; ++ port->new_port.trigger_drawing = post_video_trigger_drawing; + port->new_port.status = post_video_status; + port->new_port.get_property = post_video_get_property; + port->new_port.set_property = post_video_set_property; +@@ -377,10 +388,11 @@ vo_frame_t *_x_post_intercept_video_fram + port->new_frame->free ? port->new_frame->free : post_frame_free; + new_frame->dispose = + port->new_frame->dispose ? port->new_frame->dispose : post_frame_dispose; +- +- if (!port->new_frame->draw) { ++ ++ if (!port->new_frame->draw || (port->route_preprocessing_procs && port->route_preprocessing_procs(port, frame))) { + /* draw will most likely modify the frame, so the decoder +- * should only request preprocessing when there is no new draw */ ++ * should only request preprocessing when there is no new draw ++ * but route_preprocessing_procs() can override this decision */ + if (frame->proc_frame && !new_frame->proc_frame) + new_frame->proc_frame = post_frame_proc_frame; + if (frame->proc_slice && !new_frame->proc_slice) +@@ -697,6 +709,7 @@ static int post_audio_rewire(xine_post_o + if (!new_port) + return 0; + ++ this->running_ticket->lock_port_rewiring(this->running_ticket, -1); + this->running_ticket->revoke(this->running_ticket, 1); + + if (input_port->original_port->status(input_port->original_port, input_port->stream, +@@ -707,6 +720,7 @@ static int post_audio_rewire(xine_post_o + input_port->original_port = new_port; + + this->running_ticket->issue(this->running_ticket, 1); ++ this->running_ticket->unlock_port_rewiring(this->running_ticket); + + return 1; + } +diff --git a/src/xine-engine/post.h b/src/xine-engine/post.h +--- a/src/xine-engine/post.h ++++ b/src/xine-engine/post.h +@@ -177,6 +177,13 @@ struct post_video_port_s { + /* the new frame function pointers */ + vo_frame_t *new_frame; + ++ /* if you want to decide yourself, whether the preprocessing functions ++ * should still be routed when draw is intercepted, fill in this ++ * function; _x_post_intercept_video_frame() acts as a template method ++ * and asks your function; return a boolean; the default is _not_ to ++ * route preprocessing functions when draw is intercepted */ ++ int (*route_preprocessing_procs)(post_video_port_t *self, vo_frame_t *frame); ++ + /* if you want to decide yourself, whether the overlay manager should + * be intercepted, fill in this function; get_overlay_manager() acts as + * a template method and asks your function; return a boolean; +diff --git a/src/xine-engine/video_out.c b/src/xine-engine/video_out.c +--- a/src/xine-engine/video_out.c ++++ b/src/xine-engine/video_out.c +@@ -132,6 +132,9 @@ typedef struct { + int frame_drop_cpt; + int frame_drop_suggested; + int crop_left, crop_right, crop_top, crop_bottom; ++ pthread_mutex_t trigger_drawing_mutex; ++ pthread_cond_t trigger_drawing_cond; ++ int trigger_drawing; + } vos_t; + + +@@ -1068,6 +1071,32 @@ static void check_redraw_needed (vos_t * + this->redraw_needed = 1; + } + ++static int interruptable_sleep(vos_t *this, int usec_to_sleep) ++{ ++ int timedout = 0; ++ ++ struct timeval now; ++ gettimeofday(&now, 0); ++ ++ pthread_mutex_lock (&this->trigger_drawing_mutex); ++ if (!this->trigger_drawing) { ++ struct timespec abstime; ++ abstime.tv_sec = now.tv_sec + usec_to_sleep / 1000000; ++ abstime.tv_nsec = now.tv_usec * 1000 + (usec_to_sleep % 1000000) * 1000; ++ ++ if (abstime.tv_nsec > 1000000000) { ++ abstime.tv_nsec -= 1000000000; ++ abstime.tv_sec++; ++ } ++ ++ timedout = pthread_cond_timedwait(&this->trigger_drawing_cond, &this->trigger_drawing_mutex, &abstime); ++ } ++ this->trigger_drawing = 0; ++ pthread_mutex_unlock (&this->trigger_drawing_mutex); ++ ++ return timedout; ++} ++ + /* special loop for paused mode + * needed to update screen due overlay changes, resize, window + * movement, brightness adjusting etc. +@@ -1113,7 +1142,7 @@ static void paused_loop( vos_t *this, in + } + + pthread_mutex_unlock( &this->free_img_buf_queue->mutex ); +- xine_usec_sleep (20000); ++ interruptable_sleep(this, 20000); + pthread_mutex_lock( &this->free_img_buf_queue->mutex ); + } + +@@ -1243,7 +1272,10 @@ static void *video_out_loop (void *this_ + "video_out: vpts/clock error, next_vpts=%" PRId64 " cur_vpts=%" PRId64 "\n", next_frame_vpts,vpts); + + if (usec_to_sleep > 0) +- xine_usec_sleep (usec_to_sleep); ++ { ++ if (0 == interruptable_sleep(this, usec_to_sleep)) ++ break; ++ } + + if (this->discard_frames) + break; +@@ -1626,6 +1658,9 @@ static void vo_exit (xine_video_port_t * + free (this->free_img_buf_queue); + free (this->display_img_buf_queue); + ++ pthread_cond_destroy(&this->trigger_drawing_cond); ++ pthread_mutex_destroy(&this->trigger_drawing_mutex); ++ + free (this); + } + +@@ -1693,6 +1728,15 @@ static void vo_flush (xine_video_port_t + this->discard_frames--; + pthread_mutex_unlock(&this->display_img_buf_queue->mutex); + } ++} ++ ++static void vo_trigger_drawing (xine_video_port_t *this_gen) { ++ vos_t *this = (vos_t *) this_gen; ++ ++ pthread_mutex_lock (&this->trigger_drawing_mutex); ++ this->trigger_drawing = 1; ++ pthread_cond_signal (&this->trigger_drawing_cond); ++ pthread_mutex_unlock (&this->trigger_drawing_mutex); + } + + /* crop_frame() will allocate a new frame to copy in the given image +@@ -1790,6 +1834,7 @@ xine_video_port_t *_x_vo_new_port (xine_ + this->vo.enable_ovl = vo_enable_overlay; + this->vo.get_overlay_manager = vo_get_overlay_manager; + this->vo.flush = vo_flush; ++ this->vo.trigger_drawing = vo_trigger_drawing; + this->vo.get_property = vo_get_property; + this->vo.set_property = vo_set_property; + this->vo.status = vo_status; +@@ -1883,6 +1928,9 @@ xine_video_port_t *_x_vo_new_port (xine_ + "were not scheduled for display in time, xine sends a notification."), + 20, NULL, NULL); + ++ pthread_mutex_init(&this->trigger_drawing_mutex, NULL); ++ pthread_cond_init(&this->trigger_drawing_cond, NULL); ++ this->trigger_drawing = 0; + + if (grabonly) { + +diff --git a/src/xine-engine/video_out.h b/src/xine-engine/video_out.h +--- a/src/xine-engine/video_out.h ++++ b/src/xine-engine/video_out.h +@@ -198,6 +198,9 @@ struct xine_video_port_s { + /* flush video_out fifo */ + void (*flush) (xine_video_port_t *self); + ++ /* trigger immediate drawing */ ++ void (*trigger_drawing) (xine_video_port_t *self); ++ + /* Get/Set video property + * + * See VO_PROP_* bellow +diff --git a/src/xine-engine/video_overlay.h b/src/xine-engine/video_overlay.h +--- a/src/xine-engine/video_overlay.h ++++ b/src/xine-engine/video_overlay.h +@@ -35,7 +35,7 @@ + + #define MAX_OBJECTS 50 + #define MAX_EVENTS 50 +-#define MAX_SHOWING 16 ++#define MAX_SHOWING (5 + 16) + + #define OVERLAY_EVENT_NULL 0 + #define OVERLAY_EVENT_SHOW 1 +diff --git a/src/xine-engine/xine.c b/src/xine-engine/xine.c +--- a/src/xine-engine/xine.c ++++ b/src/xine-engine/xine.c +@@ -294,8 +294,37 @@ static void ticket_revoke(xine_ticket_t + pthread_mutex_unlock(&this->revoke_lock); + } + ++static int ticket_lock_port_rewiring(xine_ticket_t *this, int ms_timeout) { ++ ++ if (ms_timeout >= 0) { ++ struct timespec abstime; ++ ++ struct timeval now; ++ gettimeofday(&now, 0); ++ ++ abstime.tv_sec = now.tv_sec + ms_timeout / 1000; ++ abstime.tv_nsec = now.tv_usec * 1000 + (ms_timeout % 1000) * 1e6; ++ ++ if (abstime.tv_nsec > 1e9) { ++ abstime.tv_nsec -= 1e9; ++ abstime.tv_sec++; ++ } ++ ++ return (0 == pthread_mutex_timedlock(&this->port_rewiring_lock, &abstime)); ++ } ++ ++ pthread_mutex_lock(&this->port_rewiring_lock); ++ return 1; ++} ++ ++static void ticket_unlock_port_rewiring(xine_ticket_t *this) { ++ ++ pthread_mutex_unlock(&this->port_rewiring_lock); ++} ++ + static void ticket_dispose(xine_ticket_t *this) { + ++ pthread_mutex_destroy(&this->port_rewiring_lock); + pthread_mutex_destroy(&this->lock); + pthread_mutex_destroy(&this->revoke_lock); + pthread_cond_destroy(&this->issued); +@@ -316,12 +345,15 @@ static xine_ticket_t *XINE_MALLOC ticket + port_ticket->renew = ticket_renew; + port_ticket->issue = ticket_issue; + port_ticket->revoke = ticket_revoke; ++ port_ticket->lock_port_rewiring = ticket_lock_port_rewiring; ++ port_ticket->unlock_port_rewiring = ticket_unlock_port_rewiring; + port_ticket->dispose = ticket_dispose; + port_ticket->holder_thread_count = XINE_MAX_TICKET_HOLDER_THREADS; + port_ticket->holder_threads = calloc(XINE_MAX_TICKET_HOLDER_THREADS,sizeof(*port_ticket->holder_threads)); + + pthread_mutex_init(&port_ticket->lock, NULL); + pthread_mutex_init(&port_ticket->revoke_lock, NULL); ++ pthread_mutex_init(&port_ticket->port_rewiring_lock, NULL); + pthread_cond_init(&port_ticket->issued, NULL); + pthread_cond_init(&port_ticket->revoked, NULL); + +@@ -523,6 +555,7 @@ static int stream_rewire_audio(xine_post + if (!data) + return 0; + ++ stream->xine->port_ticket->lock_port_rewiring(stream->xine->port_ticket, -1); + stream->xine->port_ticket->revoke(stream->xine->port_ticket, 1); + + if (stream->audio_out->status(stream->audio_out, stream, &bits, &rate, &mode)) { +@@ -533,6 +566,7 @@ static int stream_rewire_audio(xine_post + stream->audio_out = new_port; + + stream->xine->port_ticket->issue(stream->xine->port_ticket, 1); ++ stream->xine->port_ticket->unlock_port_rewiring(stream->xine->port_ticket); + + return 1; + } +@@ -547,6 +581,7 @@ static int stream_rewire_video(xine_post + if (!data) + return 0; + ++ stream->xine->port_ticket->lock_port_rewiring(stream->xine->port_ticket, -1); + stream->xine->port_ticket->revoke(stream->xine->port_ticket, 1); + + if (stream->video_out->status(stream->video_out, stream, &width, &height, &img_duration)) { +@@ -557,6 +592,7 @@ static int stream_rewire_video(xine_post + stream->video_out = new_port; + + stream->xine->port_ticket->issue(stream->xine->port_ticket, 1); ++ stream->xine->port_ticket->unlock_port_rewiring(stream->xine->port_ticket); + + return 1; + } +@@ -2339,3 +2375,83 @@ int _x_query_buffer_usage(xine_stream_t + + return ticket_acquired != 0; + } ++ ++int _x_lock_port_rewiring(xine_t *xine, int ms_timeout) ++{ ++ return xine->port_ticket->lock_port_rewiring(xine->port_ticket, ms_timeout); ++} ++ ++void _x_unlock_port_rewiring(xine_t *xine) ++{ ++ xine->port_ticket->unlock_port_rewiring(xine->port_ticket); ++} ++ ++int _x_lock_frontend(xine_stream_t *stream, int ms_to_time_out) ++{ ++ if (ms_to_time_out >= 0) { ++ struct timespec abstime; ++ ++ struct timeval now; ++ gettimeofday(&now, 0); ++ ++ abstime.tv_sec = now.tv_sec + ms_to_time_out / 1000; ++ abstime.tv_nsec = now.tv_usec * 1000 + (ms_to_time_out % 1000) * 1e6; ++ ++ if (abstime.tv_nsec > 1e9) { ++ abstime.tv_nsec -= 1e9; ++ abstime.tv_sec++; ++ } ++ ++ return (0 == pthread_mutex_timedlock(&stream->frontend_lock, &abstime)); ++ } ++ ++ pthread_mutex_lock(&stream->frontend_lock); ++ return 1; ++} ++ ++void _x_unlock_frontend(xine_stream_t *stream) ++{ ++ pthread_mutex_unlock(&stream->frontend_lock); ++} ++ ++int _x_query_unprocessed_osd_events(xine_stream_t *stream) ++{ ++ video_overlay_manager_t *ovl; ++ int redraw_needed; ++ ++ if (!stream->xine->port_ticket->acquire_nonblocking(stream->xine->port_ticket, 1)) ++ return -1; ++ ++ ovl = stream->video_out->get_overlay_manager(stream->video_out); ++ redraw_needed = ovl->redraw_needed(ovl, 0); ++ ++ if (redraw_needed) ++ stream->video_out->trigger_drawing(stream->video_out); ++ ++ stream->xine->port_ticket->release_nonblocking(stream->xine->port_ticket, 1); ++ ++ return redraw_needed; ++} ++ ++int _x_demux_seek(xine_stream_t *stream, off_t start_pos, int start_time, int playing) ++{ ++ if (!stream->demux_plugin) ++ return -1; ++ return stream->demux_plugin->seek(stream->demux_plugin, start_pos, start_time, playing); ++} ++ ++int _x_continue_stream_processing(xine_stream_t *stream) ++{ ++ return stream->status != XINE_STATUS_STOP ++ && stream->status != XINE_STATUS_QUIT; ++} ++ ++void _x_trigger_relaxed_frame_drop_mode(xine_stream_t *stream) ++{ ++ stream->first_frame_flag = 2; ++} ++ ++void _x_reset_relaxed_frame_drop_mode(xine_stream_t *stream) ++{ ++ stream->first_frame_flag = 1; ++} +diff --git a/src/xine-engine/xine_internal.h b/src/xine-engine/xine_internal.h +--- a/src/xine-engine/xine_internal.h ++++ b/src/xine-engine/xine_internal.h +@@ -169,6 +169,9 @@ struct xine_ticket_s { + * be used in combination with acquire_nonblocking() */ + void (*release_nonblocking)(xine_ticket_t *self, int irrevocable); + ++ int (*lock_port_rewiring)(xine_ticket_t *self, int ms_timeout); ++ void (*unlock_port_rewiring)(xine_ticket_t *self); ++ + void (*dispose)(xine_ticket_t *self); + + pthread_mutex_t lock; +@@ -185,6 +188,7 @@ struct xine_ticket_s { + pthread_t holder; + } *holder_threads; + unsigned holder_thread_count; ++ pthread_mutex_t port_rewiring_lock; + #endif + }; + +@@ -375,6 +379,15 @@ struct xine_stream_s { + */ + + int _x_query_buffer_usage(xine_stream_t *stream, int *num_video_buffers, int *num_audio_buffers, int *num_video_frames, int *num_audio_frames) XINE_PROTECTED; ++int _x_lock_port_rewiring(xine_t *xine, int ms_to_time_out) XINE_PROTECTED; ++void _x_unlock_port_rewiring(xine_t *xine) XINE_PROTECTED; ++int _x_lock_frontend(xine_stream_t *stream, int ms_to_time_out) XINE_PROTECTED; ++void _x_unlock_frontend(xine_stream_t *stream) XINE_PROTECTED; ++int _x_query_unprocessed_osd_events(xine_stream_t *stream) XINE_PROTECTED; ++int _x_demux_seek(xine_stream_t *stream, off_t start_pos, int start_time, int playing) XINE_PROTECTED; ++int _x_continue_stream_processing(xine_stream_t *stream) XINE_PROTECTED; ++void _x_trigger_relaxed_frame_drop_mode(xine_stream_t *stream) XINE_PROTECTED; ++void _x_reset_relaxed_frame_drop_mode(xine_stream_t *stream) XINE_PROTECTED; + + void _x_handle_stream_end (xine_stream_t *stream, int non_user) XINE_PROTECTED; + diff --git a/patches/xine-ui.patch b/patches/xine-ui.patch new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/patches/xine-ui.patch diff --git a/po/ca_ES.po b/po/ca_ES.po new file mode 100644 index 0000000..1bc29c3 --- /dev/null +++ b/po/ca_ES.po @@ -0,0 +1,141 @@ +# VDR plugin language source file. +# Copyright (C) 2007 Klaus Schmidinger <kls@cadsoft.de> +# This file is distributed under the same license as the VDR package. +# Marc Rovira Vall <tm05462@salleURL.edu>, 2003 +# Ramon Roca <ramon.roca@xcombo.com>, 2003 +# Jordi Vilà <jvila@tinet.org>, 2003 +# +msgid "" +msgstr "" +"Project-Id-Version: VDR 1.5.7\n" +"Report-Msgid-Bugs-To: Reinhard Nissl <rnissl@gmx.de>\n" +"POT-Creation-Date: 2011-03-13 14:22+0100\n" +"PO-Revision-Date: 2007-08-29 21:10+0200\n" +"Last-Translator: Jordi Vilà <jvila@tinet.org>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-1\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "X11 overlay" +msgstr "" + +msgid "Blend clipped" +msgstr "" + +msgid "Blend scaled LQ" +msgstr "" + +msgid "Blend scaled HQ" +msgstr "" + +msgid "Blend scaled SHQ" +msgstr "" + +msgid "Blend scaled Auto" +msgstr "" + +msgid "No" +msgstr "" + +msgid "Yes (by hardware)" +msgstr "" + +msgid "Yes (by software)" +msgstr "" + +msgid "Ignore" +msgstr "" + +msgid "Execute" +msgstr "" + +msgid "Simulate" +msgstr "" + +msgid "Live-TV SD video buffer [frames]" +msgstr "" + +msgid "Live-TV HD video buffer [frames]" +msgstr "" + +msgid "Live-TV audio buffer [frames]" +msgstr "" + +msgid "Buffer hysteresis [frames]" +msgstr "" + +msgid "Buffer monitoring duration [s]" +msgstr "" + +msgid "Buffer monitoring mode" +msgstr "" + +msgid "Once" +msgstr "" + +msgid "Continuous" +msgstr "" + +msgid "OSD display mode" +msgstr "" + +msgid "OSD gamma correction [ 123 => 1.23 ]" +msgstr "" + +msgid "OSD extent X" +msgstr "" + +msgid "OSD extent Y" +msgstr "" + +msgid "4:3 image zoom X [%]" +msgstr "" + +msgid "4:3 image zoom Y [%]" +msgstr "" + +msgid "16:9 image zoom X [%]" +msgstr "" + +msgid "16:9 image zoom Y [%]" +msgstr "" + +msgid "Audio mode" +msgstr "" + +msgid "Dolby off" +msgstr "" + +msgid "Dolby on" +msgstr "" + +msgid "Control xine's volume" +msgstr "" + +msgid "Muting" +msgstr "" + +msgid "Get primary device when xine connects" +msgstr "" + +msgid "Yes" +msgstr "" + +msgid "Support semi transparent colors" +msgstr "" + +msgid "Connection interacts with EIT scanner" +msgstr "" + +#, c-format +msgid "Switching primary DVB to %s..." +msgstr "" + +#, c-format +msgid "Switched primary DVB back from %s" +msgstr "" + +msgid "Software based playback using xine" +msgstr "" diff --git a/po/cs_CZ.po b/po/cs_CZ.po new file mode 100644 index 0000000..9aa8204 --- /dev/null +++ b/po/cs_CZ.po @@ -0,0 +1,139 @@ +# VDR plugin language source file. +# Copyright (C) 2007 Klaus Schmidinger <kls@cadsoft.de> +# This file is distributed under the same license as the VDR package. +# Vladimír Bárta <vladimir.barta@k2atmitec.cz>, 2006 +# +msgid "" +msgstr "" +"Project-Id-Version: VDR 1.5.7\n" +"Report-Msgid-Bugs-To: Reinhard Nissl <rnissl@gmx.de>\n" +"POT-Creation-Date: 2011-03-13 14:22+0100\n" +"PO-Revision-Date: 2007-08-29 21:10+0200\n" +"Last-Translator: Vladimír Bárta <vladimir.barta@k2atmitec.cz>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-2\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "X11 overlay" +msgstr "" + +msgid "Blend clipped" +msgstr "" + +msgid "Blend scaled LQ" +msgstr "" + +msgid "Blend scaled HQ" +msgstr "" + +msgid "Blend scaled SHQ" +msgstr "" + +msgid "Blend scaled Auto" +msgstr "" + +msgid "No" +msgstr "" + +msgid "Yes (by hardware)" +msgstr "" + +msgid "Yes (by software)" +msgstr "" + +msgid "Ignore" +msgstr "" + +msgid "Execute" +msgstr "" + +msgid "Simulate" +msgstr "" + +msgid "Live-TV SD video buffer [frames]" +msgstr "" + +msgid "Live-TV HD video buffer [frames]" +msgstr "" + +msgid "Live-TV audio buffer [frames]" +msgstr "" + +msgid "Buffer hysteresis [frames]" +msgstr "" + +msgid "Buffer monitoring duration [s]" +msgstr "" + +msgid "Buffer monitoring mode" +msgstr "" + +msgid "Once" +msgstr "" + +msgid "Continuous" +msgstr "" + +msgid "OSD display mode" +msgstr "" + +msgid "OSD gamma correction [ 123 => 1.23 ]" +msgstr "" + +msgid "OSD extent X" +msgstr "" + +msgid "OSD extent Y" +msgstr "" + +msgid "4:3 image zoom X [%]" +msgstr "" + +msgid "4:3 image zoom Y [%]" +msgstr "" + +msgid "16:9 image zoom X [%]" +msgstr "" + +msgid "16:9 image zoom Y [%]" +msgstr "" + +msgid "Audio mode" +msgstr "" + +msgid "Dolby off" +msgstr "" + +msgid "Dolby on" +msgstr "" + +msgid "Control xine's volume" +msgstr "" + +msgid "Muting" +msgstr "" + +msgid "Get primary device when xine connects" +msgstr "" + +msgid "Yes" +msgstr "" + +msgid "Support semi transparent colors" +msgstr "" + +msgid "Connection interacts with EIT scanner" +msgstr "" + +#, c-format +msgid "Switching primary DVB to %s..." +msgstr "" + +#, c-format +msgid "Switched primary DVB back from %s" +msgstr "" + +msgid "Software based playback using xine" +msgstr "" diff --git a/po/da_DK.po b/po/da_DK.po new file mode 100644 index 0000000..d097434 --- /dev/null +++ b/po/da_DK.po @@ -0,0 +1,139 @@ +# VDR plugin language source file. +# Copyright (C) 2007 Klaus Schmidinger <kls@cadsoft.de> +# This file is distributed under the same license as the VDR package. +# Mogens Elneff <mogens@elneff.dk>, 2004 +# +msgid "" +msgstr "" +"Project-Id-Version: VDR 1.5.7\n" +"Report-Msgid-Bugs-To: Reinhard Nissl <rnissl@gmx.de>\n" +"POT-Creation-Date: 2011-03-13 14:22+0100\n" +"PO-Revision-Date: 2007-08-29 21:10+0200\n" +"Last-Translator: Mogens Elneff <mogens@elneff.dk>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-15\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "X11 overlay" +msgstr "" + +msgid "Blend clipped" +msgstr "" + +msgid "Blend scaled LQ" +msgstr "" + +msgid "Blend scaled HQ" +msgstr "" + +msgid "Blend scaled SHQ" +msgstr "" + +msgid "Blend scaled Auto" +msgstr "" + +msgid "No" +msgstr "" + +msgid "Yes (by hardware)" +msgstr "" + +msgid "Yes (by software)" +msgstr "" + +msgid "Ignore" +msgstr "" + +msgid "Execute" +msgstr "" + +msgid "Simulate" +msgstr "" + +msgid "Live-TV SD video buffer [frames]" +msgstr "" + +msgid "Live-TV HD video buffer [frames]" +msgstr "" + +msgid "Live-TV audio buffer [frames]" +msgstr "" + +msgid "Buffer hysteresis [frames]" +msgstr "" + +msgid "Buffer monitoring duration [s]" +msgstr "" + +msgid "Buffer monitoring mode" +msgstr "" + +msgid "Once" +msgstr "" + +msgid "Continuous" +msgstr "" + +msgid "OSD display mode" +msgstr "" + +msgid "OSD gamma correction [ 123 => 1.23 ]" +msgstr "" + +msgid "OSD extent X" +msgstr "" + +msgid "OSD extent Y" +msgstr "" + +msgid "4:3 image zoom X [%]" +msgstr "" + +msgid "4:3 image zoom Y [%]" +msgstr "" + +msgid "16:9 image zoom X [%]" +msgstr "" + +msgid "16:9 image zoom Y [%]" +msgstr "" + +msgid "Audio mode" +msgstr "" + +msgid "Dolby off" +msgstr "" + +msgid "Dolby on" +msgstr "" + +msgid "Control xine's volume" +msgstr "" + +msgid "Muting" +msgstr "" + +msgid "Get primary device when xine connects" +msgstr "" + +msgid "Yes" +msgstr "" + +msgid "Support semi transparent colors" +msgstr "" + +msgid "Connection interacts with EIT scanner" +msgstr "" + +#, c-format +msgid "Switching primary DVB to %s..." +msgstr "" + +#, c-format +msgid "Switched primary DVB back from %s" +msgstr "" + +msgid "Software based playback using xine" +msgstr "" diff --git a/po/de_DE.po b/po/de_DE.po new file mode 100644 index 0000000..8555f4a --- /dev/null +++ b/po/de_DE.po @@ -0,0 +1,139 @@ +# VDR plugin language source file. +# Copyright (C) 2007 Klaus Schmidinger <kls@cadsoft.de> +# This file is distributed under the same license as the VDR package. +# Klaus Schmidinger <kls@cadsoft.de>, 2000 +# +msgid "" +msgstr "" +"Project-Id-Version: VDR 1.5.7\n" +"Report-Msgid-Bugs-To: Reinhard Nissl <rnissl@gmx.de>\n" +"POT-Creation-Date: 2011-03-13 14:22+0100\n" +"PO-Revision-Date: 2007-08-29 21:10+0200\n" +"Last-Translator: Klaus Schmidinger <kls@cadsoft.de>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-15\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "X11 overlay" +msgstr "Überlagern (X11)" + +msgid "Blend clipped" +msgstr "Abschneiden" + +msgid "Blend scaled LQ" +msgstr "Einpassen LQ" + +msgid "Blend scaled HQ" +msgstr "Einpassen HQ" + +msgid "Blend scaled SHQ" +msgstr "Einpassen SHQ" + +msgid "Blend scaled Auto" +msgstr "Einpassen Auto" + +msgid "No" +msgstr "Nein" + +msgid "Yes (by hardware)" +msgstr "Ja (in Hardware)" + +msgid "Yes (by software)" +msgstr "Ja (in Software)" + +msgid "Ignore" +msgstr "Ignorieren" + +msgid "Execute" +msgstr "Ausführen" + +msgid "Simulate" +msgstr "Simulieren" + +msgid "Live-TV SD video buffer [frames]" +msgstr "SD-Video Puffer für Live-TV [Bilder]" + +msgid "Live-TV HD video buffer [frames]" +msgstr "HD-Video Puffer für Live-TV [Bilder]" + +msgid "Live-TV audio buffer [frames]" +msgstr "Audio Puffer für Live-TV [Bilder]" + +msgid "Buffer hysteresis [frames]" +msgstr "Puffer-Hysterese [Bilder]" + +msgid "Buffer monitoring duration [s]" +msgstr "Puffer-Überwachungsdauer [s]" + +msgid "Buffer monitoring mode" +msgstr "Puffer-Überwachungsmodus" + +msgid "Once" +msgstr "Einmal" + +msgid "Continuous" +msgstr "Durchgehend" + +msgid "OSD display mode" +msgstr "Anzeigeart für OSD" + +msgid "OSD gamma correction [ 123 => 1.23 ]" +msgstr "OSD Gamma Korrektur [ 123 => 1.23 ]" + +msgid "OSD extent X" +msgstr "OSD Ausdehnung X" + +msgid "OSD extent Y" +msgstr "OSD Ausdehnung Y" + +msgid "4:3 image zoom X [%]" +msgstr "4:3 Bilder X-Skalierung [%]" + +msgid "4:3 image zoom Y [%]" +msgstr "4:3 Bilder Y-Skalierung [%]" + +msgid "16:9 image zoom X [%]" +msgstr "16:9 Bilder X-Skalierung [%]" + +msgid "16:9 image zoom Y [%]" +msgstr "16:9 Bilder Y-Skalierung [%]" + +msgid "Audio mode" +msgstr "Audio-Modus" + +msgid "Dolby off" +msgstr "Dolby aus" + +msgid "Dolby on" +msgstr "Dolby ein" + +msgid "Control xine's volume" +msgstr "Lautstärke in xine steuern" + +msgid "Muting" +msgstr "Stummschalten" + +msgid "Get primary device when xine connects" +msgstr "Automatisch zum primären Interface werden" + +msgid "Yes" +msgstr "Ja" + +msgid "Support semi transparent colors" +msgstr "Unterstützung für halbtransparente Farben" + +msgid "Connection interacts with EIT scanner" +msgstr "Verbindung interagiert mit EIT-Scanner" + +#, c-format +msgid "Switching primary DVB to %s..." +msgstr "Primäres Interface wird zu %s umgeschaltet..." + +#, c-format +msgid "Switched primary DVB back from %s" +msgstr "Primäres Interface wurde von %s zurückgeschaltet" + +msgid "Software based playback using xine" +msgstr "Software-basierte Wiedergabe mittels xine" diff --git a/po/el_GR.po b/po/el_GR.po new file mode 100644 index 0000000..09ed243 --- /dev/null +++ b/po/el_GR.po @@ -0,0 +1,139 @@ +# VDR plugin language source file. +# Copyright (C) 2007 Klaus Schmidinger <kls@cadsoft.de> +# This file is distributed under the same license as the VDR package. +# Dimitrios Dimitrakos <mail@dimitrios.de>, 2002 +# +msgid "" +msgstr "" +"Project-Id-Version: VDR 1.5.7\n" +"Report-Msgid-Bugs-To: Reinhard Nissl <rnissl@gmx.de>\n" +"POT-Creation-Date: 2011-03-13 14:22+0100\n" +"PO-Revision-Date: 2007-08-29 21:10+0200\n" +"Last-Translator: Dimitrios Dimitrakos <mail@dimitrios.de>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-7\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "X11 overlay" +msgstr "" + +msgid "Blend clipped" +msgstr "" + +msgid "Blend scaled LQ" +msgstr "" + +msgid "Blend scaled HQ" +msgstr "" + +msgid "Blend scaled SHQ" +msgstr "" + +msgid "Blend scaled Auto" +msgstr "" + +msgid "No" +msgstr "" + +msgid "Yes (by hardware)" +msgstr "" + +msgid "Yes (by software)" +msgstr "" + +msgid "Ignore" +msgstr "" + +msgid "Execute" +msgstr "" + +msgid "Simulate" +msgstr "" + +msgid "Live-TV SD video buffer [frames]" +msgstr "" + +msgid "Live-TV HD video buffer [frames]" +msgstr "" + +msgid "Live-TV audio buffer [frames]" +msgstr "" + +msgid "Buffer hysteresis [frames]" +msgstr "" + +msgid "Buffer monitoring duration [s]" +msgstr "" + +msgid "Buffer monitoring mode" +msgstr "" + +msgid "Once" +msgstr "" + +msgid "Continuous" +msgstr "" + +msgid "OSD display mode" +msgstr "" + +msgid "OSD gamma correction [ 123 => 1.23 ]" +msgstr "" + +msgid "OSD extent X" +msgstr "" + +msgid "OSD extent Y" +msgstr "" + +msgid "4:3 image zoom X [%]" +msgstr "" + +msgid "4:3 image zoom Y [%]" +msgstr "" + +msgid "16:9 image zoom X [%]" +msgstr "" + +msgid "16:9 image zoom Y [%]" +msgstr "" + +msgid "Audio mode" +msgstr "" + +msgid "Dolby off" +msgstr "" + +msgid "Dolby on" +msgstr "" + +msgid "Control xine's volume" +msgstr "" + +msgid "Muting" +msgstr "" + +msgid "Get primary device when xine connects" +msgstr "" + +msgid "Yes" +msgstr "" + +msgid "Support semi transparent colors" +msgstr "" + +msgid "Connection interacts with EIT scanner" +msgstr "" + +#, c-format +msgid "Switching primary DVB to %s..." +msgstr "" + +#, c-format +msgid "Switched primary DVB back from %s" +msgstr "" + +msgid "Software based playback using xine" +msgstr "" diff --git a/po/es_ES.po b/po/es_ES.po new file mode 100644 index 0000000..878a30e --- /dev/null +++ b/po/es_ES.po @@ -0,0 +1,139 @@ +# VDR plugin language source file. +# Copyright (C) 2007 Klaus Schmidinger <kls@cadsoft.de> +# This file is distributed under the same license as the VDR package. +# Ruben Nunez Francisco <ruben.nunez@tang-it.com>, 2002 +# +msgid "" +msgstr "" +"Project-Id-Version: VDR 1.5.7\n" +"Report-Msgid-Bugs-To: Reinhard Nissl <rnissl@gmx.de>\n" +"POT-Creation-Date: 2011-03-13 14:22+0100\n" +"PO-Revision-Date: 2007-08-29 21:10+0200\n" +"Last-Translator: Ruben Nunez Francisco <ruben.nunez@tang-it.com>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-15\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "X11 overlay" +msgstr "" + +msgid "Blend clipped" +msgstr "" + +msgid "Blend scaled LQ" +msgstr "" + +msgid "Blend scaled HQ" +msgstr "" + +msgid "Blend scaled SHQ" +msgstr "" + +msgid "Blend scaled Auto" +msgstr "" + +msgid "No" +msgstr "" + +msgid "Yes (by hardware)" +msgstr "" + +msgid "Yes (by software)" +msgstr "" + +msgid "Ignore" +msgstr "" + +msgid "Execute" +msgstr "" + +msgid "Simulate" +msgstr "" + +msgid "Live-TV SD video buffer [frames]" +msgstr "" + +msgid "Live-TV HD video buffer [frames]" +msgstr "" + +msgid "Live-TV audio buffer [frames]" +msgstr "" + +msgid "Buffer hysteresis [frames]" +msgstr "" + +msgid "Buffer monitoring duration [s]" +msgstr "" + +msgid "Buffer monitoring mode" +msgstr "" + +msgid "Once" +msgstr "" + +msgid "Continuous" +msgstr "" + +msgid "OSD display mode" +msgstr "" + +msgid "OSD gamma correction [ 123 => 1.23 ]" +msgstr "" + +msgid "OSD extent X" +msgstr "" + +msgid "OSD extent Y" +msgstr "" + +msgid "4:3 image zoom X [%]" +msgstr "" + +msgid "4:3 image zoom Y [%]" +msgstr "" + +msgid "16:9 image zoom X [%]" +msgstr "" + +msgid "16:9 image zoom Y [%]" +msgstr "" + +msgid "Audio mode" +msgstr "" + +msgid "Dolby off" +msgstr "" + +msgid "Dolby on" +msgstr "" + +msgid "Control xine's volume" +msgstr "" + +msgid "Muting" +msgstr "" + +msgid "Get primary device when xine connects" +msgstr "" + +msgid "Yes" +msgstr "" + +msgid "Support semi transparent colors" +msgstr "" + +msgid "Connection interacts with EIT scanner" +msgstr "" + +#, c-format +msgid "Switching primary DVB to %s..." +msgstr "" + +#, c-format +msgid "Switched primary DVB back from %s" +msgstr "" + +msgid "Software based playback using xine" +msgstr "" diff --git a/po/et_EE.po b/po/et_EE.po new file mode 100644 index 0000000..a9dabd8 --- /dev/null +++ b/po/et_EE.po @@ -0,0 +1,142 @@ +# VDR plugin language source file. +# Copyright (C) 2007 Klaus Schmidinger <kls@cadsoft.de> +# This file is distributed under the same license as the VDR package. +# Arthur Konovalov <kasjas@hot.ee>, 2004 +# +msgid "" +msgstr "" +"Project-Id-Version: VDR 1.5.7\n" +"Report-Msgid-Bugs-To: Reinhard Nissl <rnissl@gmx.de>\n" +"POT-Creation-Date: 2011-03-13 14:22+0100\n" +"PO-Revision-Date: 2007-08-29 21:10+0200\n" +"Last-Translator: Arthur Konovalov <kasjas@hot.ee>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-13\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "X11 overlay" +msgstr "overlay (X11)" + +msgid "Blend clipped" +msgstr "lõigatud" + +msgid "Blend scaled LQ" +msgstr "skaleeritud (LQ)" + +msgid "Blend scaled HQ" +msgstr "skaleeritud (HQ)" + +msgid "Blend scaled SHQ" +msgstr "skaleeritud (SHQ)" + +msgid "Blend scaled Auto" +msgstr "skaleeritud (auto)" + +msgid "No" +msgstr "ei" + +msgid "Yes (by hardware)" +msgstr "jah (raualine)" + +msgid "Yes (by software)" +msgstr "jah (tarkvaraline)" + +msgid "Ignore" +msgstr "eira" + +msgid "Execute" +msgstr "soorita" + +msgid "Simulate" +msgstr "simuleeri" + +#, fuzzy +msgid "Live-TV SD video buffer [frames]" +msgstr "Live-TV puhver [freimi]" + +#, fuzzy +msgid "Live-TV HD video buffer [frames]" +msgstr "Live-TV puhver [freimi]" + +#, fuzzy +msgid "Live-TV audio buffer [frames]" +msgstr "Live-TV puhver [freimi]" + +msgid "Buffer hysteresis [frames]" +msgstr "Puhvri hüsterees [freimi]" + +msgid "Buffer monitoring duration [s]" +msgstr "Puhvri monitooringu kestus [s]" + +msgid "Buffer monitoring mode" +msgstr "Puhvri monitooringu moodus" + +msgid "Once" +msgstr "üks kord" + +msgid "Continuous" +msgstr "pidevalt" + +msgid "OSD display mode" +msgstr "Ekraanimenüü moodus" + +msgid "OSD gamma correction [ 123 => 1.23 ]" +msgstr "Gammakorrektsioon [123=>1.23]" + +msgid "OSD extent X" +msgstr "" + +msgid "OSD extent Y" +msgstr "" + +msgid "4:3 image zoom X [%]" +msgstr "4:3 kujutuse suum X [%]" + +msgid "4:3 image zoom Y [%]" +msgstr "4:3 kujutuse suum Y [%]" + +msgid "16:9 image zoom X [%]" +msgstr "16:9 kujutuse suum X [%]" + +msgid "16:9 image zoom Y [%]" +msgstr "16:9 kujutuse suum Y [%]" + +msgid "Audio mode" +msgstr "Dolbyheli" + +msgid "Dolby off" +msgstr "väljas" + +msgid "Dolby on" +msgstr "sees" + +msgid "Control xine's volume" +msgstr "Helitugevuse kontroll" + +msgid "Muting" +msgstr "Vaigistus" + +msgid "Get primary device when xine connects" +msgstr "Esmase seadme automaatne ühendamine" + +msgid "Yes" +msgstr "jah" + +msgid "Support semi transparent colors" +msgstr "Poolläbipaistvate värvide tugi" + +msgid "Connection interacts with EIT scanner" +msgstr "Ühenduse koosoime EIT skanneriga" + +#, c-format +msgid "Switching primary DVB to %s..." +msgstr "Esmase DVB seadme %s ümberlülitamine..." + +#, c-format +msgid "Switched primary DVB back from %s" +msgstr "Esmane DVB seade lülitatud tagasi %s-st" + +msgid "Software based playback using xine" +msgstr "Xine baasil taasesitusseade" diff --git a/po/fi_FI.po b/po/fi_FI.po new file mode 100644 index 0000000..7f7fad7 --- /dev/null +++ b/po/fi_FI.po @@ -0,0 +1,145 @@ +# VDR plugin language source file. +# Copyright (C) 2007 Klaus Schmidinger <kls@cadsoft.de> +# This file is distributed under the same license as the VDR package. +# Hannu Savolainen <hannu@opensound.com>, 2002 +# Jaakko Hyvätti <jaakko@hyvatti.iki.fi>, 2002 +# Niko Tarnanen <niko.tarnanen@hut.fi>, 2003 +# Rolf Ahrenberg <rahrenbe@cc.hut.fi>, 2003 +# +msgid "" +msgstr "" +"Project-Id-Version: VDR 1.5.7\n" +"Report-Msgid-Bugs-To: Reinhard Nissl <rnissl@gmx.de>\n" +"POT-Creation-Date: 2011-03-13 14:22+0100\n" +"PO-Revision-Date: 2007-08-29 21:10+0200\n" +"Last-Translator: Rolf Ahrenberg <rahrenbe@cc.hut.fi>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-15\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "X11 overlay" +msgstr "overlay (X11)" + +msgid "Blend clipped" +msgstr "leikattu" + +msgid "Blend scaled LQ" +msgstr "skaalattu (LQ)" + +msgid "Blend scaled HQ" +msgstr "skaalattu (HQ)" + +msgid "Blend scaled SHQ" +msgstr "skaalattu (SHQ)" + +msgid "Blend scaled Auto" +msgstr "skaalattu (auto)" + +msgid "No" +msgstr "ei" + +msgid "Yes (by hardware)" +msgstr "" + +msgid "Yes (by software)" +msgstr "" + +msgid "Ignore" +msgstr "sivuuta" + +msgid "Execute" +msgstr "suorita" + +msgid "Simulate" +msgstr "simuloi" + +#, fuzzy +msgid "Live-TV SD video buffer [frames]" +msgstr "TV-lähetyksen puskurointi [ruutua]" + +#, fuzzy +msgid "Live-TV HD video buffer [frames]" +msgstr "TV-lähetyksen puskurointi [ruutua]" + +#, fuzzy +msgid "Live-TV audio buffer [frames]" +msgstr "TV-lähetyksen puskurointi [ruutua]" + +msgid "Buffer hysteresis [frames]" +msgstr "Puskurin hystereesis [ruutua]" + +msgid "Buffer monitoring duration [s]" +msgstr "Puskurin monitoroinnin kesto [s]" + +msgid "Buffer monitoring mode" +msgstr "Puskurin monitorointitapa" + +msgid "Once" +msgstr "kerran" + +msgid "Continuous" +msgstr "jatkuva" + +msgid "OSD display mode" +msgstr "Kuvaruutunäytön moodi" + +msgid "OSD gamma correction [ 123 => 1.23 ]" +msgstr "Kuvaruutunäytön gammakorjaus [123 => 1.23]" + +msgid "OSD extent X" +msgstr "" + +msgid "OSD extent Y" +msgstr "" + +msgid "4:3 image zoom X [%]" +msgstr "" + +msgid "4:3 image zoom Y [%]" +msgstr "" + +msgid "16:9 image zoom X [%]" +msgstr "" + +msgid "16:9 image zoom Y [%]" +msgstr "" + +msgid "Audio mode" +msgstr "Monikanavaäänet (Dolby)" + +msgid "Dolby off" +msgstr "pois" + +msgid "Dolby on" +msgstr "päällä" + +msgid "Control xine's volume" +msgstr "Käytä Xinen äänenvoimakkuussäätöä" + +msgid "Muting" +msgstr "Mykistys" + +msgid "Get primary device when xine connects" +msgstr "Käytä automaattisesti ensisijaisena sovittimena" + +msgid "Yes" +msgstr "kyllä" + +msgid "Support semi transparent colors" +msgstr "Tue puoliläpinäkyviä värejä" + +msgid "Connection interacts with EIT scanner" +msgstr "" + +#, c-format +msgid "Switching primary DVB to %s..." +msgstr "Vaihdetaan ensisijaiseksi DVB-sovittimeksi %s..." + +#, c-format +msgid "Switched primary DVB back from %s" +msgstr "Ensisijainen DVB-sovitin vaihdettu takaisin %s:stä" + +msgid "Software based playback using xine" +msgstr "Xine-näyttölaite" diff --git a/po/fr_FR.po b/po/fr_FR.po new file mode 100644 index 0000000..703c705 --- /dev/null +++ b/po/fr_FR.po @@ -0,0 +1,142 @@ +# VDR plugin language source file. +# Copyright (C) 2007 Klaus Schmidinger <kls@cadsoft.de> +# This file is distributed under the same license as the VDR package. +# Jean-Claude Repetto <jc@repetto.org>, 2001 +# Olivier Jacques <jacquesolivier@hotmail.com>, 2003 +# Gregoire Favre <greg@magma.unil.ch>, 2003 +# Nicolas Huillard <nhuillard@e-dition.fr>, 2005 +# +msgid "" +msgstr "" +"Project-Id-Version: VDR 1.5.7\n" +"Report-Msgid-Bugs-To: Reinhard Nissl <rnissl@gmx.de>\n" +"POT-Creation-Date: 2011-03-13 14:22+0100\n" +"PO-Revision-Date: 2007-08-29 21:10+0200\n" +"Last-Translator: Nicolas Huillard <nhuillard@e-dition.fr>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-1\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "X11 overlay" +msgstr "" + +msgid "Blend clipped" +msgstr "" + +msgid "Blend scaled LQ" +msgstr "" + +msgid "Blend scaled HQ" +msgstr "" + +msgid "Blend scaled SHQ" +msgstr "" + +msgid "Blend scaled Auto" +msgstr "" + +msgid "No" +msgstr "" + +msgid "Yes (by hardware)" +msgstr "" + +msgid "Yes (by software)" +msgstr "" + +msgid "Ignore" +msgstr "" + +msgid "Execute" +msgstr "" + +msgid "Simulate" +msgstr "" + +msgid "Live-TV SD video buffer [frames]" +msgstr "" + +msgid "Live-TV HD video buffer [frames]" +msgstr "" + +msgid "Live-TV audio buffer [frames]" +msgstr "" + +msgid "Buffer hysteresis [frames]" +msgstr "" + +msgid "Buffer monitoring duration [s]" +msgstr "" + +msgid "Buffer monitoring mode" +msgstr "" + +msgid "Once" +msgstr "" + +msgid "Continuous" +msgstr "" + +msgid "OSD display mode" +msgstr "" + +msgid "OSD gamma correction [ 123 => 1.23 ]" +msgstr "" + +msgid "OSD extent X" +msgstr "" + +msgid "OSD extent Y" +msgstr "" + +msgid "4:3 image zoom X [%]" +msgstr "" + +msgid "4:3 image zoom Y [%]" +msgstr "" + +msgid "16:9 image zoom X [%]" +msgstr "" + +msgid "16:9 image zoom Y [%]" +msgstr "" + +msgid "Audio mode" +msgstr "" + +msgid "Dolby off" +msgstr "" + +msgid "Dolby on" +msgstr "" + +msgid "Control xine's volume" +msgstr "" + +msgid "Muting" +msgstr "" + +msgid "Get primary device when xine connects" +msgstr "" + +msgid "Yes" +msgstr "" + +msgid "Support semi transparent colors" +msgstr "" + +msgid "Connection interacts with EIT scanner" +msgstr "" + +#, c-format +msgid "Switching primary DVB to %s..." +msgstr "" + +#, c-format +msgid "Switched primary DVB back from %s" +msgstr "" + +msgid "Software based playback using xine" +msgstr "" diff --git a/po/hr_HR.po b/po/hr_HR.po new file mode 100644 index 0000000..777e5df --- /dev/null +++ b/po/hr_HR.po @@ -0,0 +1,140 @@ +# VDR plugin language source file. +# Copyright (C) 2007 Klaus Schmidinger <kls@cadsoft.de> +# This file is distributed under the same license as the VDR package. +# Drazen Dupor <drazen.dupor@dupor.com>, 2004 +# Dino Ravnic <dino.ravnic@fer.hr>, 2004 +# +msgid "" +msgstr "" +"Project-Id-Version: VDR 1.5.7\n" +"Report-Msgid-Bugs-To: Reinhard Nissl <rnissl@gmx.de>\n" +"POT-Creation-Date: 2011-03-13 14:22+0100\n" +"PO-Revision-Date: 2007-08-29 21:10+0200\n" +"Last-Translator: Drazen Dupor <drazen.dupor@dupor.com>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-2\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "X11 overlay" +msgstr "" + +msgid "Blend clipped" +msgstr "" + +msgid "Blend scaled LQ" +msgstr "" + +msgid "Blend scaled HQ" +msgstr "" + +msgid "Blend scaled SHQ" +msgstr "" + +msgid "Blend scaled Auto" +msgstr "" + +msgid "No" +msgstr "" + +msgid "Yes (by hardware)" +msgstr "" + +msgid "Yes (by software)" +msgstr "" + +msgid "Ignore" +msgstr "" + +msgid "Execute" +msgstr "" + +msgid "Simulate" +msgstr "" + +msgid "Live-TV SD video buffer [frames]" +msgstr "" + +msgid "Live-TV HD video buffer [frames]" +msgstr "" + +msgid "Live-TV audio buffer [frames]" +msgstr "" + +msgid "Buffer hysteresis [frames]" +msgstr "" + +msgid "Buffer monitoring duration [s]" +msgstr "" + +msgid "Buffer monitoring mode" +msgstr "" + +msgid "Once" +msgstr "" + +msgid "Continuous" +msgstr "" + +msgid "OSD display mode" +msgstr "" + +msgid "OSD gamma correction [ 123 => 1.23 ]" +msgstr "" + +msgid "OSD extent X" +msgstr "" + +msgid "OSD extent Y" +msgstr "" + +msgid "4:3 image zoom X [%]" +msgstr "" + +msgid "4:3 image zoom Y [%]" +msgstr "" + +msgid "16:9 image zoom X [%]" +msgstr "" + +msgid "16:9 image zoom Y [%]" +msgstr "" + +msgid "Audio mode" +msgstr "" + +msgid "Dolby off" +msgstr "" + +msgid "Dolby on" +msgstr "" + +msgid "Control xine's volume" +msgstr "" + +msgid "Muting" +msgstr "" + +msgid "Get primary device when xine connects" +msgstr "" + +msgid "Yes" +msgstr "" + +msgid "Support semi transparent colors" +msgstr "" + +msgid "Connection interacts with EIT scanner" +msgstr "" + +#, c-format +msgid "Switching primary DVB to %s..." +msgstr "" + +#, c-format +msgid "Switched primary DVB back from %s" +msgstr "" + +msgid "Software based playback using xine" +msgstr "" diff --git a/po/hu_HU.po b/po/hu_HU.po new file mode 100644 index 0000000..80c260e --- /dev/null +++ b/po/hu_HU.po @@ -0,0 +1,140 @@ +# VDR plugin language source file. +# Copyright (C) 2007 Klaus Schmidinger <kls@cadsoft.de> +# This file is distributed under the same license as the VDR package. +# Istvan Koenigsberger <istvnko@hotmail.com>, 2002 +# Guido Josten <guido.josten@t-online.de>, 2002 +# +msgid "" +msgstr "" +"Project-Id-Version: VDR 1.5.7\n" +"Report-Msgid-Bugs-To: Reinhard Nissl <rnissl@gmx.de>\n" +"POT-Creation-Date: 2011-03-13 14:22+0100\n" +"PO-Revision-Date: 2007-08-29 21:10+0200\n" +"Last-Translator: Istvan Koenigsberger <istvnko@hotmail.com>, Guido Josten <guido.josten@t-online.de>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-2\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "X11 overlay" +msgstr "" + +msgid "Blend clipped" +msgstr "" + +msgid "Blend scaled LQ" +msgstr "" + +msgid "Blend scaled HQ" +msgstr "" + +msgid "Blend scaled SHQ" +msgstr "" + +msgid "Blend scaled Auto" +msgstr "" + +msgid "No" +msgstr "" + +msgid "Yes (by hardware)" +msgstr "" + +msgid "Yes (by software)" +msgstr "" + +msgid "Ignore" +msgstr "" + +msgid "Execute" +msgstr "" + +msgid "Simulate" +msgstr "" + +msgid "Live-TV SD video buffer [frames]" +msgstr "" + +msgid "Live-TV HD video buffer [frames]" +msgstr "" + +msgid "Live-TV audio buffer [frames]" +msgstr "" + +msgid "Buffer hysteresis [frames]" +msgstr "" + +msgid "Buffer monitoring duration [s]" +msgstr "" + +msgid "Buffer monitoring mode" +msgstr "" + +msgid "Once" +msgstr "" + +msgid "Continuous" +msgstr "" + +msgid "OSD display mode" +msgstr "" + +msgid "OSD gamma correction [ 123 => 1.23 ]" +msgstr "" + +msgid "OSD extent X" +msgstr "" + +msgid "OSD extent Y" +msgstr "" + +msgid "4:3 image zoom X [%]" +msgstr "" + +msgid "4:3 image zoom Y [%]" +msgstr "" + +msgid "16:9 image zoom X [%]" +msgstr "" + +msgid "16:9 image zoom Y [%]" +msgstr "" + +msgid "Audio mode" +msgstr "" + +msgid "Dolby off" +msgstr "" + +msgid "Dolby on" +msgstr "" + +msgid "Control xine's volume" +msgstr "" + +msgid "Muting" +msgstr "" + +msgid "Get primary device when xine connects" +msgstr "" + +msgid "Yes" +msgstr "" + +msgid "Support semi transparent colors" +msgstr "" + +msgid "Connection interacts with EIT scanner" +msgstr "" + +#, c-format +msgid "Switching primary DVB to %s..." +msgstr "" + +#, c-format +msgid "Switched primary DVB back from %s" +msgstr "" + +msgid "Software based playback using xine" +msgstr "" diff --git a/po/it_IT.po b/po/it_IT.po new file mode 100644 index 0000000..9252ab9 --- /dev/null +++ b/po/it_IT.po @@ -0,0 +1,147 @@ +# VDR plugin language source file. +# Copyright (C) 2007 Klaus Schmidinger <kls@cadsoft.de> +# This file is distributed under the same license as the VDR package. +# Alberto Carraro <bertocar@tin.it>, 2001 +# Antonio Ospite <ospite@studenti.unina.it>, 2003 +# Sean Carlos <seanc@libero.it>, 2005 +# +msgid "" +msgstr "" +"Project-Id-Version: VDR 1.5.7\n" +"Report-Msgid-Bugs-To: Reinhard Nissl <rnissl@gmx.de>\n" +"POT-Creation-Date: 2011-03-13 14:22+0100\n" +"PO-Revision-Date: 2009-05-06 21:05+0100\n" +"Last-Translator: Diego Pierotto <vdr-italian@tiscali.it>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Italian\n" +"X-Poedit-Country: ITALY\n" +"X-Poedit-SourceCharset: utf-8\n" + +msgid "X11 overlay" +msgstr "Overlay (X11)" + +msgid "Blend clipped" +msgstr "Trasparenza fissa" + +msgid "Blend scaled LQ" +msgstr "Trasparenza scalata LQ" + +msgid "Blend scaled HQ" +msgstr "Trasparenza scalata HQ" + +msgid "Blend scaled SHQ" +msgstr "Trasparenza scalata SHQ" + +msgid "Blend scaled Auto" +msgstr "Trasparenza scalata in automatico" + +msgid "No" +msgstr "No" + +msgid "Yes (by hardware)" +msgstr "Sì (tramite hardware)" + +msgid "Yes (by software)" +msgstr "Sì (tramite software)" + +msgid "Ignore" +msgstr "Ignora" + +msgid "Execute" +msgstr "Esegui" + +msgid "Simulate" +msgstr "Simula" + +#, fuzzy +msgid "Live-TV SD video buffer [frames]" +msgstr "Buffer TV dal vivo [frames]" + +#, fuzzy +msgid "Live-TV HD video buffer [frames]" +msgstr "Buffer TV dal vivo [frames]" + +#, fuzzy +msgid "Live-TV audio buffer [frames]" +msgstr "Buffer TV dal vivo [frames]" + +msgid "Buffer hysteresis [frames]" +msgstr "Buffer isteresi [frames]" + +msgid "Buffer monitoring duration [s]" +msgstr "Durata monitoraggio buffer [s]" + +msgid "Buffer monitoring mode" +msgstr "Modalità monitoraggio buffer" + +msgid "Once" +msgstr "Una volta" + +msgid "Continuous" +msgstr "Continuo" + +msgid "OSD display mode" +msgstr "Modalità visualizzazione OSD" + +msgid "OSD gamma correction [ 123 => 1.23 ]" +msgstr "Correzione gamma OSD [ 123 => 1.23 ]" + +msgid "OSD extent X" +msgstr "Limite OSD X" + +msgid "OSD extent Y" +msgstr "Limite OSD Y" + +msgid "4:3 image zoom X [%]" +msgstr "Ingrandimento X [%] immagine 4:3" + +msgid "4:3 image zoom Y [%]" +msgstr "Ingrandimento Y [%] immagine 4:3" + +msgid "16:9 image zoom X [%]" +msgstr "Ingrandimento X [%] immagine 16:9" + +msgid "16:9 image zoom Y [%]" +msgstr "Ingrandimento Y [%] immagine 16:9" + +msgid "Audio mode" +msgstr "Modalità audio" + +msgid "Dolby off" +msgstr "Dolby disattivo" + +msgid "Dolby on" +msgstr "Dolby attivo" + +msgid "Control xine's volume" +msgstr "Regolazione volume xine" + +msgid "Muting" +msgstr "Muto" + +msgid "Get primary device when xine connects" +msgstr "Mostra interfaccia primaria all'avvio di xine" + +msgid "Yes" +msgstr "Sì" + +msgid "Support semi transparent colors" +msgstr "Supporto colori semi trasparenti" + +msgid "Connection interacts with EIT scanner" +msgstr "La connessione interagisce con lo scanner EIT" + +#, c-format +msgid "Switching primary DVB to %s..." +msgstr "Cambio scheda DVB primaria a %s..." + +#, c-format +msgid "Switched primary DVB back from %s" +msgstr "Ritorno a scheda DVD primaria da %s" + +msgid "Software based playback using xine" +msgstr "Software per riproduzione tramite xine" diff --git a/po/lt_LT.po b/po/lt_LT.po new file mode 100644 index 0000000..5a2339f --- /dev/null +++ b/po/lt_LT.po @@ -0,0 +1,139 @@ +# VDR plugin language source file. +# Copyright (C) 2007 Klaus Schmidinger <kls@cadsoft.de> +# This file is distributed under the same license as the VDR package. +# Valdemaras Pipiras <varas@ambernet.lt>, 2009 +# +msgid "" +msgstr "" +"Project-Id-Version: VDR 1.7.10\n" +"Report-Msgid-Bugs-To: Reinhard Nissl <rnissl@gmx.de>\n" +"POT-Creation-Date: 2011-03-13 14:22+0100\n" +"PO-Revision-Date: 2009-11-26 00:10+0200\n" +"Last-Translator: Valdemaras Pipiras <varas@ambernet.lt>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "X11 overlay" +msgstr "X11 perdengimas (overlay)" + +msgid "Blend clipped" +msgstr "Nukirpti" + +msgid "Blend scaled LQ" +msgstr "Pereiti į žemÄ… kokybÄ™ (LQ)" + +msgid "Blend scaled HQ" +msgstr "Pereiti į aukÅ¡tÄ… kokybÄ™ (HQ)" + +msgid "Blend scaled SHQ" +msgstr "Pereiti į labai aukÅ¡tÄ… kokybÄ™ (SHQ)" + +msgid "Blend scaled Auto" +msgstr "Automatinis perÄ—jimas" + +msgid "No" +msgstr "Ne" + +msgid "Yes (by hardware)" +msgstr "Taip (aparatÅ«riÅ¡kai)" + +msgid "Yes (by software)" +msgstr "Taip (programiÅ¡kai)" + +msgid "Ignore" +msgstr "Ignoruoti" + +msgid "Execute" +msgstr "Ä®vykdyti" + +msgid "Simulate" +msgstr "Simuliuoti" + +msgid "Live-TV SD video buffer [frames]" +msgstr "SD-Video buferis Live-TV [kadrais]" + +msgid "Live-TV HD video buffer [frames]" +msgstr "HD-Video buferis Live-TV [kadrais]" + +msgid "Live-TV audio buffer [frames]" +msgstr "Audio buferis Live-TV [kadrais]" + +msgid "Buffer hysteresis [frames]" +msgstr "Burerio histerezÄ— [kadrais]" + +msgid "Buffer monitoring duration [s]" +msgstr "Buferio stebÄ—jimo trukmÄ— [s]" + +msgid "Buffer monitoring mode" +msgstr "Buferio stebÄ—jimo bÅ«sena" + +msgid "Once" +msgstr "VienÄ… kartÄ…" + +msgid "Continuous" +msgstr "BesitÄ™siantis" + +msgid "OSD display mode" +msgstr "Ekrano meniu (OSD) bÅ«sena" + +msgid "OSD gamma correction [ 123 => 1.23 ]" +msgstr "OSD gama korekcija [ 123 => 1.23 ]" + +msgid "OSD extent X" +msgstr "OSD pagal X ašį" + +msgid "OSD extent Y" +msgstr "OSD pagal Y ašį" + +msgid "4:3 image zoom X [%]" +msgstr "4:3 vaizdo X aÅ¡ies iÅ¡plÄ—timas [%]" + +msgid "4:3 image zoom Y [%]" +msgstr "4:3 vaizdo Y aÅ¡ies iÅ¡plÄ—timas [%]" + +msgid "16:9 image zoom X [%]" +msgstr "16:9 vaizdo X aÅ¡ies iÅ¡plÄ—timas [%]" + +msgid "16:9 image zoom Y [%]" +msgstr "16:9 vaizdo Y aÅ¡ies iÅ¡plÄ—timas [%]" + +msgid "Audio mode" +msgstr "Audio ręžimas" + +msgid "Dolby off" +msgstr "Dolby iÅ¡jungta" + +msgid "Dolby on" +msgstr "Dolby įjungta" + +msgid "Control xine's volume" +msgstr "Valdyti xine garsÄ…" + +msgid "Muting" +msgstr "IÅ¡jungiamas garsas" + +msgid "Get primary device when xine connects" +msgstr "Xine prisijungus įjungti pirmÄ… įrenginį" + +msgid "Yes" +msgstr "Taip" + +msgid "Support semi transparent colors" +msgstr "Pusiau permatomų spalvų palaikymas" + +msgid "Connection interacts with EIT scanner" +msgstr "Sujungimas sÄ…veikauja su EIT skaneriu" + +#, c-format +msgid "Switching primary DVB to %s..." +msgstr "PirminÄ— DVB korta perjungiama į %s..." + +#, c-format +msgid "Switched primary DVB back from %s" +msgstr "PirminÄ— DVB korta perjungta atgal iÅ¡ %s" + +msgid "Software based playback using xine" +msgstr "Programinis iÅ¡vedimas naudojant xine programÄ…" diff --git a/po/nl_NL.po b/po/nl_NL.po new file mode 100644 index 0000000..f70ab44 --- /dev/null +++ b/po/nl_NL.po @@ -0,0 +1,144 @@ +# VDR plugin language source file. +# Copyright (C) 2007 Klaus Schmidinger <kls@cadsoft.de> +# This file is distributed under the same license as the VDR package. +# Arnold Niessen <niessen@iae.nl> <arnold.niessen@philips.com>, 2001 +# Hans Dingemans <hans.dingemans@tacticalops.nl>, 2003 +# Maarten Wisse <Maarten.Wisse@urz.uni-hd.de>, 2005 +# +msgid "" +msgstr "" +"Project-Id-Version: VDR 1.5.7\n" +"Report-Msgid-Bugs-To: Reinhard Nissl <rnissl@gmx.de>\n" +"POT-Creation-Date: 2011-03-13 14:22+0100\n" +"PO-Revision-Date: 2007-08-29 21:10+0200\n" +"Last-Translator: Maarten Wisse <Maarten.Wisse@urz.uni-hd.de>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-15\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "X11 overlay" +msgstr "X11 overlay" + +msgid "Blend clipped" +msgstr "Samenvoegen met afsnijden" + +msgid "Blend scaled LQ" +msgstr "Samenvoegen met lage kwaliteit" + +msgid "Blend scaled HQ" +msgstr "Samenvoegen met hoge kwaliteit" + +msgid "Blend scaled SHQ" +msgstr "Samenvoegen met superhoge kwaliteit" + +msgid "Blend scaled Auto" +msgstr "Automatische samenvoeg-modus" + +msgid "No" +msgstr "Nee" + +msgid "Yes (by hardware)" +msgstr "" + +msgid "Yes (by software)" +msgstr "" + +msgid "Ignore" +msgstr "Negeren" + +msgid "Execute" +msgstr "Uitvoeren" + +msgid "Simulate" +msgstr "Simuleren" + +#, fuzzy +msgid "Live-TV SD video buffer [frames]" +msgstr "Live-TV buffer [frames]" + +#, fuzzy +msgid "Live-TV HD video buffer [frames]" +msgstr "Live-TV buffer [frames]" + +#, fuzzy +msgid "Live-TV audio buffer [frames]" +msgstr "Live-TV buffer [frames]" + +msgid "Buffer hysteresis [frames]" +msgstr "" + +msgid "Buffer monitoring duration [s]" +msgstr "" + +msgid "Buffer monitoring mode" +msgstr "" + +msgid "Once" +msgstr "" + +msgid "Continuous" +msgstr "" + +msgid "OSD display mode" +msgstr "Weergave-type van de OSD" + +msgid "OSD gamma correction [ 123 => 1.23 ]" +msgstr "OSD gamma correctie [ 123 => 1.23 ]" + +msgid "OSD extent X" +msgstr "" + +msgid "OSD extent Y" +msgstr "" + +msgid "4:3 image zoom X [%]" +msgstr "" + +msgid "4:3 image zoom Y [%]" +msgstr "" + +msgid "16:9 image zoom X [%]" +msgstr "" + +msgid "16:9 image zoom Y [%]" +msgstr "" + +msgid "Audio mode" +msgstr "Audio-modus" + +msgid "Dolby off" +msgstr "Dolby uitgeschakeld" + +msgid "Dolby on" +msgstr "Dolby ingeschakeld" + +msgid "Control xine's volume" +msgstr "Xine's volume via VDR besturen" + +msgid "Muting" +msgstr "Geluid dempen" + +msgid "Get primary device when xine connects" +msgstr "Xine als primair dvb-apparaat gebruiken" + +msgid "Yes" +msgstr "Ja" + +msgid "Support semi transparent colors" +msgstr "Ondersteun semi-transparante kleuren" + +msgid "Connection interacts with EIT scanner" +msgstr "" + +#, c-format +msgid "Switching primary DVB to %s..." +msgstr "" + +#, c-format +msgid "Switched primary DVB back from %s" +msgstr "" + +msgid "Software based playback using xine" +msgstr "Software-gebaseerde weergave met behulp van Xine" diff --git a/po/nn_NO.po b/po/nn_NO.po new file mode 100644 index 0000000..3f8d820 --- /dev/null +++ b/po/nn_NO.po @@ -0,0 +1,140 @@ +# VDR plugin language source file. +# Copyright (C) 2007 Klaus Schmidinger <kls@cadsoft.de> +# This file is distributed under the same license as the VDR package. +# Jørgen Tvedt <pjtvedt@online.no>, 2001 +# Truls Slevigen <truls@slevigen.no>, 2002 +# +msgid "" +msgstr "" +"Project-Id-Version: VDR 1.5.7\n" +"Report-Msgid-Bugs-To: Reinhard Nissl <rnissl@gmx.de>\n" +"POT-Creation-Date: 2011-03-13 14:22+0100\n" +"PO-Revision-Date: 2007-08-29 21:10+0200\n" +"Last-Translator: Truls Slevigen <truls@slevigen.no>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-1\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "X11 overlay" +msgstr "" + +msgid "Blend clipped" +msgstr "" + +msgid "Blend scaled LQ" +msgstr "" + +msgid "Blend scaled HQ" +msgstr "" + +msgid "Blend scaled SHQ" +msgstr "" + +msgid "Blend scaled Auto" +msgstr "" + +msgid "No" +msgstr "" + +msgid "Yes (by hardware)" +msgstr "" + +msgid "Yes (by software)" +msgstr "" + +msgid "Ignore" +msgstr "" + +msgid "Execute" +msgstr "" + +msgid "Simulate" +msgstr "" + +msgid "Live-TV SD video buffer [frames]" +msgstr "" + +msgid "Live-TV HD video buffer [frames]" +msgstr "" + +msgid "Live-TV audio buffer [frames]" +msgstr "" + +msgid "Buffer hysteresis [frames]" +msgstr "" + +msgid "Buffer monitoring duration [s]" +msgstr "" + +msgid "Buffer monitoring mode" +msgstr "" + +msgid "Once" +msgstr "" + +msgid "Continuous" +msgstr "" + +msgid "OSD display mode" +msgstr "" + +msgid "OSD gamma correction [ 123 => 1.23 ]" +msgstr "" + +msgid "OSD extent X" +msgstr "" + +msgid "OSD extent Y" +msgstr "" + +msgid "4:3 image zoom X [%]" +msgstr "" + +msgid "4:3 image zoom Y [%]" +msgstr "" + +msgid "16:9 image zoom X [%]" +msgstr "" + +msgid "16:9 image zoom Y [%]" +msgstr "" + +msgid "Audio mode" +msgstr "" + +msgid "Dolby off" +msgstr "" + +msgid "Dolby on" +msgstr "" + +msgid "Control xine's volume" +msgstr "" + +msgid "Muting" +msgstr "" + +msgid "Get primary device when xine connects" +msgstr "" + +msgid "Yes" +msgstr "" + +msgid "Support semi transparent colors" +msgstr "" + +msgid "Connection interacts with EIT scanner" +msgstr "" + +#, c-format +msgid "Switching primary DVB to %s..." +msgstr "" + +#, c-format +msgid "Switched primary DVB back from %s" +msgstr "" + +msgid "Software based playback using xine" +msgstr "" diff --git a/po/pl_PL.po b/po/pl_PL.po new file mode 100644 index 0000000..a76f215 --- /dev/null +++ b/po/pl_PL.po @@ -0,0 +1,139 @@ +# VDR plugin language source file. +# Copyright (C) 2007 Klaus Schmidinger <kls@cadsoft.de> +# This file is distributed under the same license as the VDR package. +# Michael Rakowski <mrak@gmx.de>, 2002 +# +msgid "" +msgstr "" +"Project-Id-Version: VDR 1.5.7\n" +"Report-Msgid-Bugs-To: Reinhard Nissl <rnissl@gmx.de>\n" +"POT-Creation-Date: 2011-03-13 14:22+0100\n" +"PO-Revision-Date: 2007-08-29 21:10+0200\n" +"Last-Translator: Michael Rakowski <mrak@gmx.de>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-2\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "X11 overlay" +msgstr "" + +msgid "Blend clipped" +msgstr "" + +msgid "Blend scaled LQ" +msgstr "" + +msgid "Blend scaled HQ" +msgstr "" + +msgid "Blend scaled SHQ" +msgstr "" + +msgid "Blend scaled Auto" +msgstr "" + +msgid "No" +msgstr "" + +msgid "Yes (by hardware)" +msgstr "" + +msgid "Yes (by software)" +msgstr "" + +msgid "Ignore" +msgstr "" + +msgid "Execute" +msgstr "" + +msgid "Simulate" +msgstr "" + +msgid "Live-TV SD video buffer [frames]" +msgstr "" + +msgid "Live-TV HD video buffer [frames]" +msgstr "" + +msgid "Live-TV audio buffer [frames]" +msgstr "" + +msgid "Buffer hysteresis [frames]" +msgstr "" + +msgid "Buffer monitoring duration [s]" +msgstr "" + +msgid "Buffer monitoring mode" +msgstr "" + +msgid "Once" +msgstr "" + +msgid "Continuous" +msgstr "" + +msgid "OSD display mode" +msgstr "" + +msgid "OSD gamma correction [ 123 => 1.23 ]" +msgstr "" + +msgid "OSD extent X" +msgstr "" + +msgid "OSD extent Y" +msgstr "" + +msgid "4:3 image zoom X [%]" +msgstr "" + +msgid "4:3 image zoom Y [%]" +msgstr "" + +msgid "16:9 image zoom X [%]" +msgstr "" + +msgid "16:9 image zoom Y [%]" +msgstr "" + +msgid "Audio mode" +msgstr "" + +msgid "Dolby off" +msgstr "" + +msgid "Dolby on" +msgstr "" + +msgid "Control xine's volume" +msgstr "" + +msgid "Muting" +msgstr "" + +msgid "Get primary device when xine connects" +msgstr "" + +msgid "Yes" +msgstr "" + +msgid "Support semi transparent colors" +msgstr "" + +msgid "Connection interacts with EIT scanner" +msgstr "" + +#, c-format +msgid "Switching primary DVB to %s..." +msgstr "" + +#, c-format +msgid "Switched primary DVB back from %s" +msgstr "" + +msgid "Software based playback using xine" +msgstr "" diff --git a/po/pt_PT.po b/po/pt_PT.po new file mode 100644 index 0000000..293b85e --- /dev/null +++ b/po/pt_PT.po @@ -0,0 +1,139 @@ +# VDR plugin language source file. +# Copyright (C) 2007 Klaus Schmidinger <kls@cadsoft.de> +# This file is distributed under the same license as the VDR package. +# Paulo Lopes <pmml@netvita.pt>, 2001 +# +msgid "" +msgstr "" +"Project-Id-Version: VDR 1.5.7\n" +"Report-Msgid-Bugs-To: Reinhard Nissl <rnissl@gmx.de>\n" +"POT-Creation-Date: 2011-03-13 14:22+0100\n" +"PO-Revision-Date: 2007-08-29 21:10+0200\n" +"Last-Translator: Paulo Lopes <pmml@netvita.pt>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-1\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "X11 overlay" +msgstr "" + +msgid "Blend clipped" +msgstr "" + +msgid "Blend scaled LQ" +msgstr "" + +msgid "Blend scaled HQ" +msgstr "" + +msgid "Blend scaled SHQ" +msgstr "" + +msgid "Blend scaled Auto" +msgstr "" + +msgid "No" +msgstr "" + +msgid "Yes (by hardware)" +msgstr "" + +msgid "Yes (by software)" +msgstr "" + +msgid "Ignore" +msgstr "" + +msgid "Execute" +msgstr "" + +msgid "Simulate" +msgstr "" + +msgid "Live-TV SD video buffer [frames]" +msgstr "" + +msgid "Live-TV HD video buffer [frames]" +msgstr "" + +msgid "Live-TV audio buffer [frames]" +msgstr "" + +msgid "Buffer hysteresis [frames]" +msgstr "" + +msgid "Buffer monitoring duration [s]" +msgstr "" + +msgid "Buffer monitoring mode" +msgstr "" + +msgid "Once" +msgstr "" + +msgid "Continuous" +msgstr "" + +msgid "OSD display mode" +msgstr "" + +msgid "OSD gamma correction [ 123 => 1.23 ]" +msgstr "" + +msgid "OSD extent X" +msgstr "" + +msgid "OSD extent Y" +msgstr "" + +msgid "4:3 image zoom X [%]" +msgstr "" + +msgid "4:3 image zoom Y [%]" +msgstr "" + +msgid "16:9 image zoom X [%]" +msgstr "" + +msgid "16:9 image zoom Y [%]" +msgstr "" + +msgid "Audio mode" +msgstr "" + +msgid "Dolby off" +msgstr "" + +msgid "Dolby on" +msgstr "" + +msgid "Control xine's volume" +msgstr "" + +msgid "Muting" +msgstr "" + +msgid "Get primary device when xine connects" +msgstr "" + +msgid "Yes" +msgstr "" + +msgid "Support semi transparent colors" +msgstr "" + +msgid "Connection interacts with EIT scanner" +msgstr "" + +#, c-format +msgid "Switching primary DVB to %s..." +msgstr "" + +#, c-format +msgid "Switched primary DVB back from %s" +msgstr "" + +msgid "Software based playback using xine" +msgstr "" diff --git a/po/ro_RO.po b/po/ro_RO.po new file mode 100644 index 0000000..df6e168 --- /dev/null +++ b/po/ro_RO.po @@ -0,0 +1,140 @@ +# VDR plugin language source file. +# Copyright (C) 2007 Klaus Schmidinger <kls@cadsoft.de> +# This file is distributed under the same license as the VDR package. +# Paul Lacatus <paul@campina.iiruc.ro>, 2002 +# Lucian Muresan <lucianm@users.sourceforge.net>, 2004 +# +msgid "" +msgstr "" +"Project-Id-Version: VDR 1.5.7\n" +"Report-Msgid-Bugs-To: Reinhard Nissl <rnissl@gmx.de>\n" +"POT-Creation-Date: 2011-03-13 14:22+0100\n" +"PO-Revision-Date: 2007-08-29 21:10+0200\n" +"Last-Translator: Lucian Muresan <lucianm@users.sourceforge.net>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-2\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "X11 overlay" +msgstr "" + +msgid "Blend clipped" +msgstr "" + +msgid "Blend scaled LQ" +msgstr "" + +msgid "Blend scaled HQ" +msgstr "" + +msgid "Blend scaled SHQ" +msgstr "" + +msgid "Blend scaled Auto" +msgstr "" + +msgid "No" +msgstr "" + +msgid "Yes (by hardware)" +msgstr "" + +msgid "Yes (by software)" +msgstr "" + +msgid "Ignore" +msgstr "" + +msgid "Execute" +msgstr "" + +msgid "Simulate" +msgstr "" + +msgid "Live-TV SD video buffer [frames]" +msgstr "" + +msgid "Live-TV HD video buffer [frames]" +msgstr "" + +msgid "Live-TV audio buffer [frames]" +msgstr "" + +msgid "Buffer hysteresis [frames]" +msgstr "" + +msgid "Buffer monitoring duration [s]" +msgstr "" + +msgid "Buffer monitoring mode" +msgstr "" + +msgid "Once" +msgstr "" + +msgid "Continuous" +msgstr "" + +msgid "OSD display mode" +msgstr "" + +msgid "OSD gamma correction [ 123 => 1.23 ]" +msgstr "" + +msgid "OSD extent X" +msgstr "" + +msgid "OSD extent Y" +msgstr "" + +msgid "4:3 image zoom X [%]" +msgstr "" + +msgid "4:3 image zoom Y [%]" +msgstr "" + +msgid "16:9 image zoom X [%]" +msgstr "" + +msgid "16:9 image zoom Y [%]" +msgstr "" + +msgid "Audio mode" +msgstr "" + +msgid "Dolby off" +msgstr "" + +msgid "Dolby on" +msgstr "" + +msgid "Control xine's volume" +msgstr "" + +msgid "Muting" +msgstr "" + +msgid "Get primary device when xine connects" +msgstr "" + +msgid "Yes" +msgstr "" + +msgid "Support semi transparent colors" +msgstr "" + +msgid "Connection interacts with EIT scanner" +msgstr "" + +#, c-format +msgid "Switching primary DVB to %s..." +msgstr "" + +#, c-format +msgid "Switched primary DVB back from %s" +msgstr "" + +msgid "Software based playback using xine" +msgstr "" diff --git a/po/ru_RU.po b/po/ru_RU.po new file mode 100644 index 0000000..f8bf72b --- /dev/null +++ b/po/ru_RU.po @@ -0,0 +1,142 @@ +# VDR plugin language source file. +# Copyright (C) 2007 Klaus Schmidinger <kls@cadsoft.de> +# This file is distributed under the same license as the VDR package. +# Vyacheslav Dikonov <sdiconov@mail.ru>, 2004 +# +msgid "" +msgstr "" +"Project-Id-Version: VDR 1.5.7\n" +"Report-Msgid-Bugs-To: Reinhard Nissl <rnissl@gmx.de>\n" +"POT-Creation-Date: 2011-03-13 14:22+0100\n" +"PO-Revision-Date: 2008-01-29 13:00+0100\n" +"Last-Translator: Oleg Roitburd <oleg@roitburd.de>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-5\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "X11 overlay" +msgstr "X11 ½ÐÛÞÖÕÝØÕ" + +msgid "Blend clipped" +msgstr "¾ÑàÕ×Ðâì" + +msgid "Blend scaled LQ" +msgstr "¿ÞÔÓÞÝÚÐ LQ" + +msgid "Blend scaled HQ" +msgstr "¿ÞÔÓÞÝÚÐ HQ" + +msgid "Blend scaled SHQ" +msgstr "¿ÞÔÓÞÝÚÐ SHQ" + +msgid "Blend scaled Auto" +msgstr "°ÒâÞÜÐâØçÕáÚÐï ßÞÔÓÞÝÚÐ" + +msgid "No" +msgstr "½Õâ" + +msgid "Yes (by hardware)" +msgstr "´Ð (ÚÞÜßìîâÕàÞÜ)" + +msgid "Yes (by software)" +msgstr "´Ð (ßàÞÓàÐÜÜÝÞ)" + +msgid "Ignore" +msgstr "¸ÓÝÞàØàÞÒÐâì" + +msgid "Execute" +msgstr "²ëßÞÛÝØâì" + +msgid "Simulate" +msgstr "ÁØÜãÛØàÞÒÐâì" + +#, fuzzy +msgid "Live-TV SD video buffer [frames]" +msgstr "Live-TV ÑãääÕàë [äàíÙÜë]" + +#, fuzzy +msgid "Live-TV HD video buffer [frames]" +msgstr "Live-TV ÑãääÕàë [äàíÙÜë]" + +#, fuzzy +msgid "Live-TV audio buffer [frames]" +msgstr "Live-TV ÑãääÕàë [äàíÙÜë]" + +msgid "Buffer hysteresis [frames]" +msgstr "³ØáâÕàÕרá ÑãäÕàÐ [äàÕÙÜë]" + +msgid "Buffer monitoring duration [s]" +msgstr "´ÛØâÕÛìÝÞáâì ÜÞÝØâÞàØÝÓÐ ÑãäÕàÐ [s]" + +msgid "Buffer monitoring mode" +msgstr "ÀÕÖØÜ ÜÞÝØâÞàØÝÓÐ ÑãäÕàÐ" + +msgid "Once" +msgstr "¾ÔÝÐÖÔë" + +msgid "Continuous" +msgstr "¿ÞáâÞïÝÝëÙ" + +msgid "OSD display mode" +msgstr "ÀÕÖØÜ ÞâÞÑàÐÖÕÝØï OSD" + +msgid "OSD gamma correction [ 123 => 1.23 ]" +msgstr "¸×ÜÕÝÕÝØÕ ÓÐÜÜë OSD [ 123 => 1.23 ]" + +msgid "OSD extent X" +msgstr "" + +msgid "OSD extent Y" +msgstr "" + +msgid "4:3 image zoom X [%]" +msgstr "4:3 áÚÐÛØàÞÒÐÝØÕ Ø×ÞÑàÐÖÕÝØï X [%]" + +msgid "4:3 image zoom Y [%]" +msgstr "4:3 áÚÐÛØàÞÒÐÝØÕ Ø×ÞÑàÐÖÕÝØï Y [%" + +msgid "16:9 image zoom X [%]" +msgstr "16:9 áÚÐÛØàÞÒÐÝØÕ Ø×ÞÑàÐÖÕÝØï X [%]" + +msgid "16:9 image zoom Y [%]" +msgstr "16:9 áÚÐÛØàÞÒÐÝØÕ Ø×ÞÑàÐÖÕÝØï Y [%]" + +msgid "Audio mode" +msgstr "°ãÔØÞ àÕÖØÜ" + +msgid "Dolby off" +msgstr "Dolby ÒëÚÛ." + +msgid "Dolby on" +msgstr "Dolby ÒÚÛ." + +msgid "Control xine's volume" +msgstr "ÃßàÐÒÛÕÝØÕ xine ×ÒãÚÞÜ" + +msgid "Muting" +msgstr "±Õ× ×ÒãÚÐ" + +msgid "Get primary device when xine connects" +msgstr "¿ÞÛãçØâì ßÕàÒØçÝÞÕ ãáâàÞÙáâÒÞ ÚÞÓÔÐ xine áÞÕÔØÝÕÝÞ" + +msgid "Yes" +msgstr "´Ð" + +msgid "Support semi transparent colors" +msgstr "¿ÞÔÔÕàÖÚÐ ßÞÛãßàÞ×àÐçÝëå ÚàÐáÞÚ" + +msgid "Connection interacts with EIT scanner" +msgstr "¸ÝâÕàÐÚâØÒÝÞÕ ßÞÔÚÛîçÕÝØÕ á EIT áÚÐÝÕàÞÜ" + +#, c-format +msgid "Switching primary DVB to %s..." +msgstr "¿ÕàÕÚÛîçÕÝØÕ ßÕàÒØçÝÞÓÞ DVB ãáâàÞÙáâÒÐ ÝÐ %s..." + +#, c-format +msgid "Switched primary DVB back from %s" +msgstr "¿ÕàÒØçÝÞÕ ãáâàÞÙáâÒÞ DVB ßÕàÕÚÛîçÕÝÞ ÝÐ×ÐÔ á %s" + +msgid "Software based playback using xine" +msgstr "¿àÞÓàÐÜÜÝÞÕ ÒÞáßàÞØ×ÒÕÔÕÝØÕ, ØáßÞÛì×ãï xine" diff --git a/po/sk_SK.po b/po/sk_SK.po new file mode 100644 index 0000000..4d22e77 --- /dev/null +++ b/po/sk_SK.po @@ -0,0 +1,141 @@ +# VDR plugin language source file. +# Copyright (C) 2007 Klaus Schmidinger <kls@cadsoft.de> +# This file is distributed under the same license as the VDR package. +# Vladimír Bárta <vladimir.barta@k2atmitec.cz>, 2006 +# +msgid "" +msgstr "" +"Project-Id-Version: xine_SK\n" +"Report-Msgid-Bugs-To: Reinhard Nissl <rnissl@gmx.de>\n" +"POT-Creation-Date: 2011-03-13 14:22+0100\n" +"PO-Revision-Date: 2009-11-02 10:43+0100\n" +"Last-Translator: Milan Hrala <hrala.milan@gmail.com>\n" +"Language-Team: Slovak <hrala.milan@gmail.com>\n" +"Language: sk\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=iso-8859-2\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Poedit-Language: Slovak\n" +"X-Poedit-Country: SLOVAKIA\n" + +msgid "X11 overlay" +msgstr "X11 prekrývanie" + +msgid "Blend clipped" +msgstr "Zastrihnuté zmie¹a»" + +msgid "Blend scaled LQ" +msgstr "Zastrihnuté zmie¹a» LQ" + +msgid "Blend scaled HQ" +msgstr "Zastrihnuté zmie¹a» HQ" + +msgid "Blend scaled SHQ" +msgstr "Zastrihnuté zmie¹a» SHQ" + +msgid "Blend scaled Auto" +msgstr "Automatické splynutie merítka" + +msgid "No" +msgstr "Nie" + +msgid "Yes (by hardware)" +msgstr "Áno (podµa hardware)" + +msgid "Yes (by software)" +msgstr "Áno (podµa software)" + +msgid "Ignore" +msgstr "Ignorova»" + +msgid "Execute" +msgstr "Vykona»" + +msgid "Simulate" +msgstr "Simulova»" + +msgid "Live-TV SD video buffer [frames]" +msgstr "Live-TV SD video vyrovnávacia pamä» [rámcov]" + +msgid "Live-TV HD video buffer [frames]" +msgstr "Live-TV HD video vyrovnávacia pamä» [rámcov]" + +msgid "Live-TV audio buffer [frames]" +msgstr "Live-TV audio vyrovnávacia pamä» [rámcov]" + +msgid "Buffer hysteresis [frames]" +msgstr "Spozdenie vyrovnávacej pamäte [rámce]" + +msgid "Buffer monitoring duration [s]" +msgstr "Doba sledovania vyrovnávacej pamäte [s]" + +msgid "Buffer monitoring mode" +msgstr "Re¾im monitorovania vyrovnávacej pamäti" + +msgid "Once" +msgstr "iba jeden raz" + +msgid "Continuous" +msgstr "Na pokraèovanie" + +msgid "OSD display mode" +msgstr "Re¾im zobrazenia OSD" + +msgid "OSD gamma correction [ 123 => 1.23 ]" +msgstr "OSD gama korekcia [123 => 1,23]" + +msgid "OSD extent X" +msgstr "OSD rozsah X" + +msgid "OSD extent Y" +msgstr "OSD rozsah Y" + +msgid "4:3 image zoom X [%]" +msgstr "obraz 4:3 zväè¹ený v X [%]" + +msgid "4:3 image zoom Y [%]" +msgstr "obraz 4:3 zväè¹ený v Y [%]" + +msgid "16:9 image zoom X [%]" +msgstr "obraz 16:9 zväè¹ený v X [%]" + +msgid "16:9 image zoom Y [%]" +msgstr "obraz 16:9 zväè¹ený v Y [%]" + +msgid "Audio mode" +msgstr "Re¾im zvuku" + +msgid "Dolby off" +msgstr "Dolby vypnuté" + +msgid "Dolby on" +msgstr "Dolby zapnuté" + +msgid "Control xine's volume" +msgstr "Ovládanie xine hlasitosti" + +msgid "Muting" +msgstr "Ticho" + +msgid "Get primary device when xine connects" +msgstr "Získanie spojenia primárneho ovládaèa xine" + +msgid "Yes" +msgstr "Áno" + +msgid "Support semi transparent colors" +msgstr "Podpora priesvitných farieb" + +msgid "Connection interacts with EIT scanner" +msgstr "Spojenie s ETI snímaèom" + +#, c-format +msgid "Switching primary DVB to %s..." +msgstr "Prepnutie na primárne DVB %s..." + +#, c-format +msgid "Switched primary DVB back from %s" +msgstr "Prepína hlavné DVB spä» z %s" + +msgid "Software based playback using xine" +msgstr "Softvér na prehrávanie pomocou xine" diff --git a/po/sl_SI.po b/po/sl_SI.po new file mode 100644 index 0000000..6340746 --- /dev/null +++ b/po/sl_SI.po @@ -0,0 +1,140 @@ +# VDR plugin language source file. +# Copyright (C) 2007 Klaus Schmidinger <kls@cadsoft.de> +# This file is distributed under the same license as the VDR package. +# Miha Setina <mihasetina@softhome.net>, 2000 +# Matjaz Thaler <matjaz.thaler@guest.arnes.si>, 2003 +# +msgid "" +msgstr "" +"Project-Id-Version: VDR 1.5.7\n" +"Report-Msgid-Bugs-To: Reinhard Nissl <rnissl@gmx.de>\n" +"POT-Creation-Date: 2011-03-13 14:22+0100\n" +"PO-Revision-Date: 2007-08-29 21:10+0200\n" +"Last-Translator: Matjaz Thaler <matjaz.thaler@guest.arnes.si>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-2\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "X11 overlay" +msgstr "" + +msgid "Blend clipped" +msgstr "" + +msgid "Blend scaled LQ" +msgstr "" + +msgid "Blend scaled HQ" +msgstr "" + +msgid "Blend scaled SHQ" +msgstr "" + +msgid "Blend scaled Auto" +msgstr "" + +msgid "No" +msgstr "" + +msgid "Yes (by hardware)" +msgstr "" + +msgid "Yes (by software)" +msgstr "" + +msgid "Ignore" +msgstr "" + +msgid "Execute" +msgstr "" + +msgid "Simulate" +msgstr "" + +msgid "Live-TV SD video buffer [frames]" +msgstr "" + +msgid "Live-TV HD video buffer [frames]" +msgstr "" + +msgid "Live-TV audio buffer [frames]" +msgstr "" + +msgid "Buffer hysteresis [frames]" +msgstr "" + +msgid "Buffer monitoring duration [s]" +msgstr "" + +msgid "Buffer monitoring mode" +msgstr "" + +msgid "Once" +msgstr "" + +msgid "Continuous" +msgstr "" + +msgid "OSD display mode" +msgstr "" + +msgid "OSD gamma correction [ 123 => 1.23 ]" +msgstr "" + +msgid "OSD extent X" +msgstr "" + +msgid "OSD extent Y" +msgstr "" + +msgid "4:3 image zoom X [%]" +msgstr "" + +msgid "4:3 image zoom Y [%]" +msgstr "" + +msgid "16:9 image zoom X [%]" +msgstr "" + +msgid "16:9 image zoom Y [%]" +msgstr "" + +msgid "Audio mode" +msgstr "" + +msgid "Dolby off" +msgstr "" + +msgid "Dolby on" +msgstr "" + +msgid "Control xine's volume" +msgstr "" + +msgid "Muting" +msgstr "" + +msgid "Get primary device when xine connects" +msgstr "" + +msgid "Yes" +msgstr "" + +msgid "Support semi transparent colors" +msgstr "" + +msgid "Connection interacts with EIT scanner" +msgstr "" + +#, c-format +msgid "Switching primary DVB to %s..." +msgstr "" + +#, c-format +msgid "Switched primary DVB back from %s" +msgstr "" + +msgid "Software based playback using xine" +msgstr "" diff --git a/po/sv_SE.po b/po/sv_SE.po new file mode 100644 index 0000000..44f3d06 --- /dev/null +++ b/po/sv_SE.po @@ -0,0 +1,143 @@ +# VDR plugin language source file. +# Copyright (C) 2007 Klaus Schmidinger <kls@cadsoft.de> +# This file is distributed under the same license as the VDR package. +# Tomas Prybil <tomas@prybil.se>, 2002 +# Jan Ekholm <chakie@infa.abo.fi>, 2003 +# +msgid "" +msgstr "" +"Project-Id-Version: VDR 1.5.7\n" +"Report-Msgid-Bugs-To: Reinhard Nissl <rnissl@gmx.de>\n" +"POT-Creation-Date: 2011-03-13 14:22+0100\n" +"PO-Revision-Date: 2007-08-29 21:10+0200\n" +"Last-Translator: Tomas Prybil <tomas@prybil.se>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-1\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "X11 overlay" +msgstr "" + +msgid "Blend clipped" +msgstr "" + +msgid "Blend scaled LQ" +msgstr "" + +msgid "Blend scaled HQ" +msgstr "" + +msgid "Blend scaled SHQ" +msgstr "" + +msgid "Blend scaled Auto" +msgstr "" + +msgid "No" +msgstr "" + +msgid "Yes (by hardware)" +msgstr "" + +msgid "Yes (by software)" +msgstr "" + +msgid "Ignore" +msgstr "" + +msgid "Execute" +msgstr "" + +msgid "Simulate" +msgstr "" + +#, fuzzy +msgid "Live-TV SD video buffer [frames]" +msgstr "Live-TV bufferstorlek [bildrutor]" + +#, fuzzy +msgid "Live-TV HD video buffer [frames]" +msgstr "Live-TV bufferstorlek [bildrutor]" + +#, fuzzy +msgid "Live-TV audio buffer [frames]" +msgstr "Live-TV bufferstorlek [bildrutor]" + +msgid "Buffer hysteresis [frames]" +msgstr "" + +msgid "Buffer monitoring duration [s]" +msgstr "" + +msgid "Buffer monitoring mode" +msgstr "" + +msgid "Once" +msgstr "" + +msgid "Continuous" +msgstr "" + +msgid "OSD display mode" +msgstr "OSD läge" + +msgid "OSD gamma correction [ 123 => 1.23 ]" +msgstr "" + +msgid "OSD extent X" +msgstr "" + +msgid "OSD extent Y" +msgstr "" + +msgid "4:3 image zoom X [%]" +msgstr "" + +msgid "4:3 image zoom Y [%]" +msgstr "" + +msgid "16:9 image zoom X [%]" +msgstr "" + +msgid "16:9 image zoom Y [%]" +msgstr "" + +msgid "Audio mode" +msgstr "Ljud läge" + +msgid "Dolby off" +msgstr "Dolby av" + +msgid "Dolby on" +msgstr "Dolby på" + +msgid "Control xine's volume" +msgstr "" + +msgid "Muting" +msgstr "" + +msgid "Get primary device when xine connects" +msgstr "" + +msgid "Yes" +msgstr "" + +msgid "Support semi transparent colors" +msgstr "" + +msgid "Connection interacts with EIT scanner" +msgstr "" + +#, c-format +msgid "Switching primary DVB to %s..." +msgstr "" + +#, c-format +msgid "Switched primary DVB back from %s" +msgstr "" + +msgid "Software based playback using xine" +msgstr "Mjukvarubaserad uppspelning med xine" diff --git a/po/tr_TR.po b/po/tr_TR.po new file mode 100644 index 0000000..b82a947 --- /dev/null +++ b/po/tr_TR.po @@ -0,0 +1,139 @@ +# VDR plugin language source file. +# Copyright (C) 2007 Klaus Schmidinger <kls@cadsoft.de> +# This file is distributed under the same license as the VDR package. +# Oktay Yolgeçen <oktay_73@yahoo.de>, 2007 +# +msgid "" +msgstr "" +"Project-Id-Version: VDR 1.5.7\n" +"Report-Msgid-Bugs-To: Reinhard Nissl <rnissl@gmx.de>\n" +"POT-Creation-Date: 2011-03-13 14:22+0100\n" +"PO-Revision-Date: 2007-08-29 21:10+0200\n" +"Last-Translator: Oktay Yolgeçen <oktay_73@yahoo.de>\n" +"Language-Team: <vdr@linuxtv.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ISO-8859-9\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "X11 overlay" +msgstr "" + +msgid "Blend clipped" +msgstr "" + +msgid "Blend scaled LQ" +msgstr "" + +msgid "Blend scaled HQ" +msgstr "" + +msgid "Blend scaled SHQ" +msgstr "" + +msgid "Blend scaled Auto" +msgstr "" + +msgid "No" +msgstr "" + +msgid "Yes (by hardware)" +msgstr "" + +msgid "Yes (by software)" +msgstr "" + +msgid "Ignore" +msgstr "" + +msgid "Execute" +msgstr "" + +msgid "Simulate" +msgstr "" + +msgid "Live-TV SD video buffer [frames]" +msgstr "" + +msgid "Live-TV HD video buffer [frames]" +msgstr "" + +msgid "Live-TV audio buffer [frames]" +msgstr "" + +msgid "Buffer hysteresis [frames]" +msgstr "" + +msgid "Buffer monitoring duration [s]" +msgstr "" + +msgid "Buffer monitoring mode" +msgstr "" + +msgid "Once" +msgstr "" + +msgid "Continuous" +msgstr "" + +msgid "OSD display mode" +msgstr "" + +msgid "OSD gamma correction [ 123 => 1.23 ]" +msgstr "" + +msgid "OSD extent X" +msgstr "" + +msgid "OSD extent Y" +msgstr "" + +msgid "4:3 image zoom X [%]" +msgstr "" + +msgid "4:3 image zoom Y [%]" +msgstr "" + +msgid "16:9 image zoom X [%]" +msgstr "" + +msgid "16:9 image zoom Y [%]" +msgstr "" + +msgid "Audio mode" +msgstr "" + +msgid "Dolby off" +msgstr "" + +msgid "Dolby on" +msgstr "" + +msgid "Control xine's volume" +msgstr "" + +msgid "Muting" +msgstr "" + +msgid "Get primary device when xine connects" +msgstr "" + +msgid "Yes" +msgstr "" + +msgid "Support semi transparent colors" +msgstr "" + +msgid "Connection interacts with EIT scanner" +msgstr "" + +#, c-format +msgid "Switching primary DVB to %s..." +msgstr "" + +#, c-format +msgid "Switched primary DVB back from %s" +msgstr "" + +msgid "Software based playback using xine" +msgstr "" diff --git a/po/zh_CN.po b/po/zh_CN.po new file mode 100644 index 0000000..1c18f75 --- /dev/null +++ b/po/zh_CN.po @@ -0,0 +1,139 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: Reinhard Nissl <rnissl@gmx.de>\n" +"POT-Creation-Date: 2011-03-13 14:22+0100\n" +"PO-Revision-Date: 2009-09-21 22:36+0800\n" +"Last-Translator: NanFeng <nfgx@21cn.com>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "X11 overlay" +msgstr "X11的覆盖" + +msgid "Blend clipped" +msgstr "æ··åˆå‰ªè¾‘" + +msgid "Blend scaled LQ" +msgstr "æ··åˆç¼©æ”¾LQ" + +msgid "Blend scaled HQ" +msgstr "æ··åˆç¼©æ”¾HQ" + +msgid "Blend scaled SHQ" +msgstr "æ··åˆç¼©æ”¾SHQ" + +msgid "Blend scaled Auto" +msgstr "自动混åˆç¼©æ”¾" + +msgid "No" +msgstr "å¦" + +msgid "Yes (by hardware)" +msgstr "确认(硬件模å¼)" + +msgid "Yes (by software)" +msgstr "确认(软件模å¼)" + +msgid "Ignore" +msgstr "忽略" + +msgid "Execute" +msgstr "执行" + +msgid "Simulate" +msgstr "仿真" + +msgid "Live-TV SD video buffer [frames]" +msgstr "ç›´æ’ç”µè§†æ ‡æ¸…è§†é¢‘ç¼“å†²å™¨[框架]" + +msgid "Live-TV HD video buffer [frames]" +msgstr "ç›´æ’电视高清视频缓冲器[框架]" + +msgid "Live-TV audio buffer [frames]" +msgstr "ç›´æ’电视音频缓冲区[框架]" + +msgid "Buffer hysteresis [frames]" +msgstr "缓冲区滞åŽ[框架]" + +msgid "Buffer monitoring duration [s]" +msgstr "缓冲区监测时间[s]" + +msgid "Buffer monitoring mode" +msgstr "缓冲区监控模å¼" + +msgid "Once" +msgstr "一次" + +msgid "Continuous" +msgstr "连ç»" + +msgid "OSD display mode" +msgstr "èœå•显示模å¼" + +msgid "OSD gamma correction [ 123 => 1.23 ]" +msgstr "èœå•ä¼½çŽ›æ ¡æ£ [ 123 => 1.23 ]" + +msgid "OSD extent X" +msgstr "èœå•广度 X" + +msgid "OSD extent Y" +msgstr "èœå•广度 Y" + +msgid "4:3 image zoom X [%]" +msgstr "4:3 å½±åƒå˜ç„¦ X [%]" + +msgid "4:3 image zoom Y [%]" +msgstr "4:3 å½±åƒå˜ç„¦ Y [%]" + +msgid "16:9 image zoom X [%]" +msgstr "16:9 å½±åƒå˜ç„¦ X [%]" + +msgid "16:9 image zoom Y [%]" +msgstr "16:9 å½±åƒå˜ç„¦ Y [%]" + +msgid "Audio mode" +msgstr "音频模å¼" + +msgid "Dolby off" +msgstr "æœæ¯”å…³é—" + +msgid "Dolby on" +msgstr "æœæ¯”打开" + +msgid "Control xine's volume" +msgstr "控制XINE的音é‡" + +msgid "Muting" +msgstr "é™éŸ³" + +msgid "Get primary device when xine connects" +msgstr "主è¦è®¾å¤‡ï¼Œåªè¦èŽ·å¾—xine的连接" + +msgid "Yes" +msgstr "是" + +msgid "Support semi transparent colors" +msgstr "支æŒåŠé€æ˜Žé¢œè‰²" + +msgid "Connection interacts with EIT scanner" +msgstr "与转型期扫æä»ªè¿žæŽ¥äº¤äº’" + +#, c-format +msgid "Switching primary DVB to %s..." +msgstr "切æ¢ä¸»çš„DVB到 %s..." + +#, c-format +msgid "Switched primary DVB back from %s" +msgstr "切æ¢ä¸»DVB回到 %s" + +msgid "Software based playback using xine" +msgstr "基于软件回放使用的XINEæ’æ”¾å™¨" diff --git a/po/zh_TW.po b/po/zh_TW.po new file mode 100644 index 0000000..2b68899 --- /dev/null +++ b/po/zh_TW.po @@ -0,0 +1,139 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: Reinhard Nissl <rnissl@gmx.de>\n" +"POT-Creation-Date: 2011-03-13 14:22+0100\n" +"PO-Revision-Date: 2009-09-21 22:36+0800\n" +"Last-Translator: NanFeng <nfgx@21cn.com>\n" +"Language-Team: LANGUAGE <LL@li.org>\n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "X11 overlay" +msgstr "X11的覆蓋" + +msgid "Blend clipped" +msgstr "æ··åˆå‰ªè¼¯" + +msgid "Blend scaled LQ" +msgstr "æ··åˆç¸®æ”¾LQ" + +msgid "Blend scaled HQ" +msgstr "æ··åˆç¸®æ”¾HQ" + +msgid "Blend scaled SHQ" +msgstr "æ··åˆç¸®æ”¾SHQ" + +msgid "Blend scaled Auto" +msgstr "自動混åˆç¸®æ”¾" + +msgid "No" +msgstr "å¦" + +msgid "Yes (by hardware)" +msgstr "確èª(硬件模å¼)" + +msgid "Yes (by software)" +msgstr "確èª(軟件模å¼)" + +msgid "Ignore" +msgstr "忽略" + +msgid "Execute" +msgstr "執行" + +msgid "Simulate" +msgstr "仿真" + +msgid "Live-TV SD video buffer [frames]" +msgstr "ç›´æ’é›»è¦–æ¨™æ¸…è¦–é »ç·©è¡å™¨[框架]" + +msgid "Live-TV HD video buffer [frames]" +msgstr "ç›´æ’é›»è¦–é«˜æ¸…è¦–é »ç·©è¡å™¨[框架]" + +msgid "Live-TV audio buffer [frames]" +msgstr "ç›´æ’é›»è¦–éŸ³é »ç·©è¡å€[框架]" + +msgid "Buffer hysteresis [frames]" +msgstr "ç·©è¡å€æ»¯å¾Œ[框架]" + +msgid "Buffer monitoring duration [s]" +msgstr "ç·©è¡å€ç›£æ¸¬æ™‚é–“[s]" + +msgid "Buffer monitoring mode" +msgstr "ç·©è¡å€ç›£æŽ§æ¨¡å¼" + +msgid "Once" +msgstr "一次" + +msgid "Continuous" +msgstr "連續" + +msgid "OSD display mode" +msgstr "èœå–®é¡¯ç¤ºæ¨¡å¼" + +msgid "OSD gamma correction [ 123 => 1.23 ]" +msgstr "èœå–®ä¼½ç‘ªæ ¡æ£[ 123 => 1.23 ]" + +msgid "OSD extent X" +msgstr "èœå–®å»£åº¦X" + +msgid "OSD extent Y" +msgstr "èœå–®å»£åº¦Y" + +msgid "4:3 image zoom X [%]" +msgstr "4:3å½±åƒè®Šç„¦X [%]" + +msgid "4:3 image zoom Y [%]" +msgstr "4:3å½±åƒè®Šç„¦Y [%]" + +msgid "16:9 image zoom X [%]" +msgstr "16:9å½±åƒè®Šç„¦X [%]" + +msgid "16:9 image zoom Y [%]" +msgstr "16:9å½±åƒè®Šç„¦Y [%]" + +msgid "Audio mode" +msgstr "éŸ³é »æ¨¡å¼" + +msgid "Dolby off" +msgstr "æœæ¯”關閉" + +msgid "Dolby on" +msgstr "æœæ¯”打開" + +msgid "Control xine's volume" +msgstr "控制XINE的音é‡" + +msgid "Muting" +msgstr "éœéŸ³" + +msgid "Get primary device when xine connects" +msgstr "主è¦è¨å‚™ï¼Œåªè¦ç²å¾—xine的連接" + +msgid "Yes" +msgstr "是" + +msgid "Support semi transparent colors" +msgstr "支æŒåŠé€æ˜Žé¡è‰²" + +msgid "Connection interacts with EIT scanner" +msgstr "與轉型期掃æå„€é€£æŽ¥äº¤äº’" + +#, c-format +msgid "Switching primary DVB to %s..." +msgstr "切æ›ä¸»çš„DVB到%s..." + +#, c-format +msgid "Switched primary DVB back from %s" +msgstr "切æ›ä¸»DVB回到%s" + +msgid "Software based playback using xine" +msgstr "基於軟件回放使用的XINEæ’æ”¾å™¨" diff --git a/vdr172h264parser.c b/vdr172h264parser.c new file mode 100644 index 0000000..8cb67b3 --- /dev/null +++ b/vdr172h264parser.c @@ -0,0 +1,475 @@ + +#include <vdr/config.h> + +#ifndef APIVERSNUM +#define APIVERSNUM VDRVERSNUM +#endif + +#if APIVERSNUM >= 10703 + +/* + * h264parser.c: a minimalistic H.264 video stream parser + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * The code was originally written by Reinhard Nissl <rnissl@gmx.de>, + * and adapted to the VDR coding style by Klaus.Schmidinger@cadsoft.de. + */ + +#include <vdr/tools.h> +#include "vdr172h264parser.h" + +namespace vdr172 +{ +namespace H264 +{ + // --- cContext ------------------------------------------------------------ + + int cContext::GetFramesPerSec(void) const + { + const cSequenceParameterSet *SPS = ActiveSPS(); + const cSliceHeader *SH = CurrentSlice(); + if (!SH || !SPS->timing_info_present_flag || !SPS->time_scale || !SPS->num_units_in_tick) + return -1; + uint32_t DeltaTfiDivisor; + if (SPS->pic_struct_present_flag) { + if (!SPS->pic_timing_sei.Defined()) + return -1; + switch (SPS->pic_timing_sei.pic_struct) { + case 1: + case 2: + DeltaTfiDivisor = 1; + break; + case 0: + case 3: + case 4: + DeltaTfiDivisor = 2; + break; + case 5: + case 6: + DeltaTfiDivisor = 3; + break; + case 7: + DeltaTfiDivisor = 4; + break; + case 8: + DeltaTfiDivisor = 6; + break; + default: + return -1; + } + } + else if (!SH->field_pic_flag) + DeltaTfiDivisor = 2; + else + DeltaTfiDivisor = 1; + + double FPS = (double)SPS->time_scale / SPS->num_units_in_tick / DeltaTfiDivisor / (SH->field_pic_flag ? 2 : 1); + int FramesPerSec = (int)FPS; + if ((FPS - FramesPerSec) >= 0.5) + FramesPerSec++; + return FramesPerSec; + } + + // --- cSimpleBuffer ------------------------------------------------------- + + cSimpleBuffer::cSimpleBuffer(int Size) + { + size = Size; + data = new uchar[size]; + avail = 0; + gotten = 0; + } + + cSimpleBuffer::~cSimpleBuffer() + { + delete [] data; + } + + int cSimpleBuffer::Put(const uchar *Data, int Count) + { + if (Count < 0) { + if (avail + Count < 0) + Count = 0 - avail; + if (avail + Count < gotten) + Count = gotten - avail; + avail += Count; + return Count; + } + if (avail + Count > size) + Count = size - avail; + memcpy(data + avail, Data, Count); + avail += Count; + return Count; + } + + uchar *cSimpleBuffer::Get(int &Count) + { + Count = gotten = avail; + return data; + } + + void cSimpleBuffer::Del(int Count) + { + if (Count < 0) + return; + if (Count > gotten) { + esyslog("ERROR: invalid Count in H264::cSimpleBuffer::Del: %d (limited to %d)", Count, gotten); + Count = gotten; + } + if (Count < avail) + memmove(data, data + Count, avail - Count); + avail -= Count; + gotten = 0; + } + + void cSimpleBuffer::Clear(void) + { + avail = gotten = 0; + } + + // --- cParser ------------------------------------------------------------- + + cParser::cParser(bool OmitPicTiming) + : nalUnitDataBuffer(1000) + { + // the above buffer size of 1000 bytes wont hold a complete NAL unit but + // should be sufficient for the relevant part used for parsing. + omitPicTiming = OmitPicTiming; // only necessary to determine frames per second + Reset(); + } + + void cParser::Reset(void) + { + context = cContext(); + nalUnitDataBuffer.Clear(); + syncing = true; + } + + void cParser::ParseSequenceParameterSet(uint8_t *Data, int Count) + { + cSequenceParameterSet SPS; + + cBitReader br(Data + 1, Count - 1); + uint32_t profile_idc = br.u(8); + /* uint32_t constraint_set0_flag = */ br.u(1); + /* uint32_t constraint_set1_flag = */ br.u(1); + /* uint32_t constraint_set2_flag = */ br.u(1); + /* uint32_t constraint_set3_flag = */ br.u(1); + /* uint32_t reserved_zero_4bits = */ br.u(4); + /* uint32_t level_idc = */ br.u(8); + SPS.seq_parameter_set_id = br.ue(); + if (profile_idc == 100 || profile_idc == 110 || profile_idc == 122 || profile_idc == 144) { + uint32_t chroma_format_idc = br.ue(); + if (chroma_format_idc == 3) { + /* uint32_t residual_colour_transform_flag = */ br.u(1); + } + /* uint32_t bit_depth_luma_minus8 = */ br.ue(); + /* uint32_t bit_depth_chroma_minus8 = */ br.ue(); + /* uint32_t qpprime_y_zero_transform_bypass_flag = */ br.u(1); + uint32_t seq_scaling_matrix_present_flag = br.u(1); + if (seq_scaling_matrix_present_flag) { + for (int i = 0; i < 8; i++) { + uint32_t seq_scaling_list_present_flag = br.u(1); + if (seq_scaling_list_present_flag) { + int sizeOfScalingList = (i < 6) ? 16 : 64; + int lastScale = 8; + int nextScale = 8; + for (int j = 0; j < sizeOfScalingList; j++) { + if (nextScale != 0) { + int32_t delta_scale = br.se(); + nextScale = (lastScale + delta_scale + 256) % 256; + } + lastScale = (nextScale == 0) ? lastScale : nextScale; + } + } + } + } + } + SPS.log2_max_frame_num_minus4(br.ue()); + SPS.pic_order_cnt_type = br.ue(); + if (SPS.pic_order_cnt_type == 0) + SPS.log2_max_pic_order_cnt_lsb_minus4(br.ue()); + else if (SPS.pic_order_cnt_type == 1) { + SPS.delta_pic_order_always_zero_flag = br.u(1); + /* int32_t offset_for_non_ref_pic = */ br.se(); + /* int32_t offset_for_top_to_bottom_field = */ br.se(); + uint32_t num_ref_frames_in_pic_order_cnt_cycle = br.ue(); + for (uint32_t i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++) { + /* int32_t offset_for_ref_frame = */ br.se(); + } + } + /* uint32_t num_ref_frames = */ br.ue(); + /* uint32_t gaps_in_frame_num_value_allowed_flag = */ br.u(1); + /* uint32_t pic_width_in_mbs_minus1 = */ br.ue(); + /* uint32_t pic_height_in_map_units_minus1 = */ br.ue(); + SPS.frame_mbs_only_flag = br.u(1); + + if (!omitPicTiming) { + if (!SPS.frame_mbs_only_flag) { + /* uint32_t mb_adaptive_frame_field_flag = */ br.u(1); + } + /* uint32_t direct_8x8_inference_flag = */ br.u(1); + uint32_t frame_cropping_flag = br.u(1); + if (frame_cropping_flag) { + /* uint32_t frame_crop_left_offset = */ br.ue(); + /* uint32_t frame_crop_right_offset = */ br.ue(); + /* uint32_t frame_crop_top_offset = */ br.ue(); + /* uint32_t frame_crop_bottom_offset = */ br.ue(); + } + uint32_t vui_parameters_present_flag = br.u(1); + if (vui_parameters_present_flag) { + uint32_t aspect_ratio_info_present_flag = br.u(1); + if (aspect_ratio_info_present_flag) { + uint32_t aspect_ratio_idc = br.u(8); + const uint32_t Extended_SAR = 255; + if (aspect_ratio_idc == Extended_SAR) { + /* uint32_t sar_width = */ br.u(16); + /* uint32_t sar_height = */ br.u(16); + } + } + uint32_t overscan_info_present_flag = br.u(1); + if (overscan_info_present_flag) { + /* uint32_t overscan_appropriate_flag = */ br.u(1); + } + uint32_t video_signal_type_present_flag = br.u(1); + if (video_signal_type_present_flag) { + /* uint32_t video_format = */ br.u(3); + /* uint32_t video_full_range_flag = */ br.u(1); + uint32_t colour_description_present_flag = br.u(1); + if (colour_description_present_flag) { + /* uint32_t colour_primaries = */ br.u(8); + /* uint32_t transfer_characteristics = */ br.u(8); + /* uint32_t matrix_coefficients = */ br.u(8); + } + } + uint32_t chroma_loc_info_present_flag = br.u(1); + if (chroma_loc_info_present_flag) { + /* uint32_t chroma_sample_loc_type_top_field = */ br.ue(); + /* uint32_t chroma_sample_loc_type_bottom_field = */ br.ue(); + } + SPS.timing_info_present_flag = br.u(1); + if (SPS.timing_info_present_flag) { + SPS.num_units_in_tick = br.u(32); + SPS.time_scale = br.u(32); + SPS.fixed_frame_rate_flag = br.u(1); + } + SPS.nal_hrd_parameters_present_flag = br.u(1); + if (SPS.nal_hrd_parameters_present_flag) + hrd_parameters(SPS, br); + SPS.vcl_hrd_parameters_present_flag = br.u(1); + if (SPS.vcl_hrd_parameters_present_flag) + hrd_parameters(SPS, br); + if (SPS.nal_hrd_parameters_present_flag || SPS.vcl_hrd_parameters_present_flag) { + /* uint32_t low_delay_hrd_flag = */ br.u(1); + } + SPS.pic_struct_present_flag = br.u(1); + } + } + + context.Define(SPS); + } + + void cParser::hrd_parameters(cSequenceParameterSet &SPS, cBitReader &br) + { + uint32_t cpb_cnt_minus1 = br.ue(); + /* uint32_t bit_rate_scale = */ br.u(4); + /* uint32_t cpb_size_scale = */ br.u(4); + for (uint32_t i = 0; i <= cpb_cnt_minus1; i++) { + /* uint32_t bit_rate_value_minus1 = */ br.ue(); + /* uint32_t cpb_size_value_minus1 = */ br.ue(); + /* uint32_t cbr_flag = */ br.u(1); + } + /* uint32_t initial_cpb_removal_delay_length_minus1 = */ br.u(5); + SPS.cpb_removal_delay_length_minus1(br.u(5)); + SPS.dpb_output_delay_length_minus1(br.u(5)); + /* uint32_t time_offset_length = */ br.u(5); + } + + void cParser::ParsePictureParameterSet(uint8_t *Data, int Count) + { + cPictureParameterSet PPS; + + cBitReader br(Data + 1, Count - 1); + PPS.pic_parameter_set_id = br.ue(); + PPS.seq_parameter_set_id = br.ue(); + /* uint32_t entropy_coding_mode_flag = */ br.u(1); + PPS.pic_order_present_flag = br.u(1); + + context.Define(PPS); + } + + void cParser::ParseSlice(uint8_t *Data, int Count) + { + cSliceHeader SH; + + cBitReader br(Data + 1, Count - 1); + SH.nal_ref_idc(Data[0] >> 5); + SH.nal_unit_type(Data[0] & 0x1F); + /* uint32_t first_mb_in_slice = */ br.ue(); + SH.slice_type = br.ue(); + SH.pic_parameter_set_id = br.ue(); + + context.ActivatePPS(SH.pic_parameter_set_id); + const cSequenceParameterSet *SPS = context.ActiveSPS(); + + SH.frame_num = br.u(SPS->log2_max_frame_num()); + if (!SPS->frame_mbs_only_flag) { + SH.field_pic_flag = br.u(1); + if (SH.field_pic_flag) + SH.bottom_field_flag = br.u(1); + } + if (SH.nal_unit_type() == 5) + SH.idr_pic_id = br.ue(); + if (SPS->pic_order_cnt_type == 0) { + SH.pic_order_cnt_lsb = br.u(SPS->log2_max_pic_order_cnt_lsb()); + const cPictureParameterSet *PPS = context.ActivePPS(); + if (PPS->pic_order_present_flag && !SH.field_pic_flag) + SH.delta_pic_order_cnt_bottom = br.se(); + } + if (SPS->pic_order_cnt_type == 1 && !SPS->delta_pic_order_always_zero_flag) { + SH.delta_pic_order_cnt[0] = br.se(); + const cPictureParameterSet *PPS = context.ActivePPS(); + if (PPS->pic_order_present_flag && !SH.field_pic_flag) + SH.delta_pic_order_cnt[1] = br.se(); + } + + context.Define(SH); + } + + void cParser::ParseSEI(uint8_t *Data, int Count) + { + // currently only used to determine frames per second + if (omitPicTiming) + return; + cBitReader br(Data + 1, Count - 1); + do + sei_message(br); + while (br.GetBytesAvail()); + } + + void cParser::sei_message(cBitReader &br) + { + uint32_t payloadType = 0; + while (1) { + uint32_t last_payload_type_byte = br.u(8); + payloadType += last_payload_type_byte; + if (last_payload_type_byte != 0xFF) + break; + } + uint32_t payloadSize = 0; + while (1) { + uint32_t last_payload_size_byte = br.u(8); + payloadSize += last_payload_size_byte; + if (last_payload_size_byte != 0xFF) + break; + } + sei_payload(payloadType, payloadSize, br); + } + + void cParser::sei_payload(uint32_t payloadType, uint32_t payloadSize, cBitReader &br) + { + const cBitReader::cBookMark BookMark = br.BookMark(); + switch (payloadType) { + case 0: + buffering_period(payloadSize, br); + break; + case 1: + pic_timing(payloadSize, br); + break; + } + // instead of dealing with trailing bits in each message + // go back to start of message and skip it completely + br.BookMark(BookMark); + reserved_sei_message(payloadSize, br); + } + + void cParser::buffering_period(uint32_t payloadSize, cBitReader &br) + { + uint32_t seq_parameter_set_id = br.ue(); + + context.ActivateSPS(seq_parameter_set_id); + } + + void cParser::pic_timing(uint32_t payloadSize, cBitReader &br) + { + cPictureTiming PT; + + const cSequenceParameterSet *SPS = context.ActiveSPS(); + if (!SPS) + return; + uint32_t CpbDpbDelaysPresentFlag = SPS->nal_hrd_parameters_present_flag || SPS->vcl_hrd_parameters_present_flag; + if (CpbDpbDelaysPresentFlag) { + /* uint32_t cpb_removal_delay = */ br.u(SPS->cpb_removal_delay_length()); + /* uint32_t dpb_output_delay = */ br.u(SPS->dpb_output_delay_length()); + } + if (SPS->pic_struct_present_flag) { + PT.pic_struct = br.u(4); + } + + context.Define(PT); + } + + void cParser::reserved_sei_message(uint32_t payloadSize, cBitReader &br) + { + for (uint32_t i = 0; i < payloadSize; i++) { + /* uint32_t reserved_sei_message_payload_byte = */ br.u(8); + } + } + + void cParser::PutNalUnitData(const uchar *Data, int Count) + { + int n = nalUnitDataBuffer.Put(Data, Count); + // typically less than a complete NAL unit are needed for parsing the + // relevant data, so simply ignore the overflow condition. + if (false && n != Count) + esyslog("ERROR: H264::cParser::PutNalUnitData(): NAL unit data buffer overflow"); + } + + void cParser::Process() + { + // nalUnitDataBuffer contains the head of the current NAL unit -- let's parse it + int Count = 0; + uchar *Data = nalUnitDataBuffer.Get(Count); + if (Data && Count >= 4) { + if (Data[0] == 0x00 && Data[1] == 0x00 && Data[2] == 0x01) { + int nal_unit_type = Data[3] & 0x1F; + try { + switch (nal_unit_type) { + case 1: // coded slice of a non-IDR picture + case 2: // coded slice data partition A + case 5: // coded slice of an IDR picture + ParseSlice(Data + 3, Count - 3); + break; + case 6: // supplemental enhancement information (SEI) + ParseSEI(Data + 3, Count - 3); + break; + case 7: // sequence parameter set + syncing = false; // from now on, we should get reliable results + ParseSequenceParameterSet(Data + 3, Count - 3); + break; + case 8: // picture parameter set + ParsePictureParameterSet(Data + 3, Count - 3); + break; + } + } + catch (cException *e) { + if (!syncing) // suppress typical error messages while syncing + esyslog(e->Message()); + delete e; + } + } + else if (!syncing) + esyslog("ERROR: H264::cParser::Process(): NAL unit data buffer content is invalid"); + } + else if (!syncing) + esyslog("ERROR: H264::cParser::Process(): NAL unit data buffer content is too short"); + // reset the buffer for the next NAL unit + nalUnitDataBuffer.Clear(); + } +} +} + +#endif + diff --git a/vdr172h264parser.h b/vdr172h264parser.h new file mode 100644 index 0000000..c7d0dca --- /dev/null +++ b/vdr172h264parser.h @@ -0,0 +1,406 @@ + +#if APIVERSNUM >= 10703 + +/* + * h264parser.h: a minimalistic H.264 video stream parser + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + */ + +#ifndef __VDR172H264PARSER_H +#define __VDR172H264PARSER_H + +namespace vdr172 +{ + +namespace H264 +{ + // --- cException ---------------------------------------------------------- + + class cException { + private: + cString message; + public: + cException(const cString &Message) { message = Message; } + const cString &Message(void) const { return message; } + }; + + // --- cBitReader ---------------------------------------------------------- + + class cBitReader { + public: + class cBookMark { + private: + uint8_t *data; + int count; + uint32_t bits; + uint32_t bitsAvail; + int countZeros; + cBookMark(void) {} + friend class cBitReader; + }; + private: + cBookMark bm; + uint8_t NextByte(void); + uint32_t ReadBits(uint32_t n); + public: + cBitReader(uint8_t *Data, int Count); + uint32_t u(uint32_t n) { return ReadBits(n); } // read n bits as unsigned number + uint32_t ue(void); // read Exp-Golomb coded unsigned number + int32_t se(void); // read Exp-Golomb coded signed number + uint32_t GetBitsAvail(void) { return (bm.bitsAvail & 0x07); } + bool GetBytesAvail(void) { return (bm.count > 0); } + const cBookMark BookMark(void) const { return bm; } + void BookMark(const cBookMark &b) { bm = b; } + }; + + inline cBitReader::cBitReader(unsigned char *Data, int Count) + { + bm.data = Data; + bm.count = Count; + bm.bitsAvail = 0; + bm.countZeros = 0; + } + + inline uint8_t cBitReader::NextByte(void) + { + if (bm.count < 1) // there is no more data left in this NAL unit + throw new cException("ERROR: H264::cBitReader::NextByte(): premature end of data"); + // detect 00 00 00, 00 00 01 and 00 00 03 and handle them + if (*bm.data == 0x00) { + if (bm.countZeros >= 3) // 00 00 00: the current NAL unit should have been terminated already before this sequence + throw new cException("ERROR: H264::cBitReader::NextByte(): premature end of data"); + // increase the zero counter as we have a zero byte + bm.countZeros++; + } + else { + if (bm.countZeros >= 2) { + if (*bm.data == 0x01) // 00 00 01: the current NAL unit should have been terminated already before this sequence + throw new cException("ERROR: H264::cBitReader::NextByte(): premature end of data"); + if (*bm.data == 0x03) { + // 00 00 03 xx: the emulation prevention byte 03 needs to be removed and xx must be returned + if (bm.count < 2) + throw new cException("ERROR: H264::cBitReader::NextByte(): premature end of data"); + // drop 03 and xx will be returned below + bm.count--; + bm.data++; + } + } + // reset the zero counter as we had a non zero byte + bm.countZeros = 0; + } + bm.count--; + return *bm.data++; + } + + inline uint32_t cBitReader::ReadBits(uint32_t n) + { + // fill the "shift register" bits with sufficient data + while (n > bm.bitsAvail) { + bm.bits <<= 8; + bm.bits |= NextByte(); + bm.bitsAvail += 8; + if (bm.bitsAvail > 24) { // a further turn will overflow bitbuffer + if (n <= bm.bitsAvail) + break; // service non overflowing request + if (n <= 32) // split overflowing reads into concatenated reads + return (ReadBits(16) << 16) | ReadBits(n - 16); + // cannot read more than 32 bits at once + throw new cException("ERROR: H264::cBitReader::ReadBits(): bitbuffer overflow"); + } + } + // return n most significant bits + bm.bitsAvail -= n; + return (bm.bits >> bm.bitsAvail) & (((uint32_t)1 << n) - 1); + } + + inline uint32_t cBitReader::ue(void) + { + // read and decode an Exp-Golomb coded unsigned number + // + // bitstring resulting number + // 1 0 + // 0 1 x 1 ... 2 + // 0 0 1 x y 3 ... 6 + // 0 0 0 1 x y z 7 ... 14 + // ... + int LeadingZeroBits = 0; + while (ReadBits(1) == 0) + LeadingZeroBits++; + if (LeadingZeroBits == 0) + return 0; + if (LeadingZeroBits >= 32) + throw new cException("ERROR: H264::cBitReader::ue(): overflow"); + return ((uint32_t)1 << LeadingZeroBits) - 1 + ReadBits(LeadingZeroBits); + } + + inline int32_t cBitReader::se(void) + { + // read and decode an Exp-Golomb coded signed number + // + // unsigned value resulting signed value + // 0 0 + // 1 +1 + // 2 -1 + // 3 +2 + // 4 -2 + // ... + uint32_t r = ue(); + if (r > 0xFFFFFFFE) + throw new cException("ERROR: H264::cBitReader::se(): overflow"); + return (1 - 2 * (r & 1)) * ((r + 1) / 2); + } + + // --- cPictureTiming ------------------------------------------------------ + + class cPictureTiming { + private: + friend class cContext; + bool defined; + public: + cPictureTiming(void) { memset(this, 0, sizeof (*this)); } + bool Defined(void) const { return defined; } + uint32_t pic_struct; + }; + + // --- cSequenceParameterSet ----------------------------------------------- + + class cSequenceParameterSet { + private: + friend class cContext; + bool defined; + uint32_t log2MaxFrameNum; + uint32_t log2MaxPicOrderCntLsb; + uint32_t cpbRemovalDelayLength; + uint32_t dpbOutputDelayLength; + public: + cSequenceParameterSet(void); + bool Defined(void) { return defined; } + void log2_max_frame_num_minus4(uint32_t Value) { log2MaxFrameNum = Value + 4; } + uint32_t log2_max_frame_num_minus4(void) const { return log2MaxFrameNum - 4; } + uint32_t log2_max_frame_num(void) const { return log2MaxFrameNum; } + void log2_max_pic_order_cnt_lsb_minus4(uint32_t Value) { log2MaxPicOrderCntLsb = Value + 4; } + uint32_t log2_max_pic_order_cnt_lsb_minus4(void) const { return log2MaxPicOrderCntLsb - 4; } + uint32_t log2_max_pic_order_cnt_lsb(void) const { return log2MaxPicOrderCntLsb; } + void cpb_removal_delay_length_minus1(uint32_t Value) { cpbRemovalDelayLength = Value + 1; } + uint32_t cpb_removal_delay_length_minus1(void) const { return cpbRemovalDelayLength - 1; } + uint32_t cpb_removal_delay_length(void) const { return cpbRemovalDelayLength; } + void dpb_output_delay_length_minus1(uint32_t Value) { dpbOutputDelayLength = Value + 1; } + uint32_t dpb_output_delay_length_minus1(void) const { return dpbOutputDelayLength - 1; } + uint32_t dpb_output_delay_length(void) const { return dpbOutputDelayLength; } + uint32_t seq_parameter_set_id; + uint32_t pic_order_cnt_type; + uint32_t delta_pic_order_always_zero_flag; + uint32_t frame_mbs_only_flag; + uint32_t timing_info_present_flag; + uint32_t num_units_in_tick; + uint32_t time_scale; + uint32_t fixed_frame_rate_flag; + uint32_t nal_hrd_parameters_present_flag; + uint32_t vcl_hrd_parameters_present_flag; + uint32_t pic_struct_present_flag; + cPictureTiming pic_timing_sei; + }; + + inline cSequenceParameterSet::cSequenceParameterSet(void) + { + memset(this, 0, sizeof (*this)); + log2_max_frame_num_minus4(0); + log2_max_pic_order_cnt_lsb_minus4(0); + cpb_removal_delay_length_minus1(23); + dpb_output_delay_length_minus1(23); + } + + // --- cPictureParameterSet ------------------------------------------------ + + class cPictureParameterSet { + private: + friend class cContext; + bool defined; + public: + cPictureParameterSet(void) { memset(this, 0, sizeof (*this)); } + bool Defined(void) { return defined; } + uint32_t pic_parameter_set_id; + uint32_t seq_parameter_set_id; + uint32_t pic_order_present_flag; + }; + + // --- cSliceHeader -------------------------------------------------------- + + class cSliceHeader { + private: + friend class cContext; + bool defined; + bool isFirstSliceOfCurrentAccessUnit; + uint32_t picOrderCntType; + uint32_t nalRefIdc; + uint32_t nalUnitType; + public: + cSliceHeader(void) { memset(this, 0, sizeof (*this)); } + bool Defined(void) const { return defined; } + bool IsFirstSliceOfCurrentAccessUnit(void) const { return isFirstSliceOfCurrentAccessUnit; } + void nal_ref_idc(uint32_t Value) { nalRefIdc = Value; } + uint32_t nal_ref_idc(void) const { return nalRefIdc; } + void nal_unit_type(uint32_t Value) { nalUnitType = Value; } + uint32_t nal_unit_type(void) const { return nalUnitType; } + uint32_t slice_type; + uint32_t pic_parameter_set_id; + uint32_t frame_num; + uint32_t field_pic_flag; + uint32_t bottom_field_flag; + uint32_t idr_pic_id; + uint32_t pic_order_cnt_lsb; + int32_t delta_pic_order_cnt_bottom; + int32_t delta_pic_order_cnt[2]; + enum eAccessUnitType { + Frame = 0, + TopField, + BottomField + }; + eAccessUnitType GetAccessUnitType() const { return (eAccessUnitType)(field_pic_flag + bottom_field_flag); } + }; + + // --- cContext ------------------------------------------------------------ + + class cContext { + private: + cSequenceParameterSet spsStore[32]; + cPictureParameterSet ppsStore[256]; + cSequenceParameterSet *sps; // active Sequence Parameter Set + cPictureParameterSet *pps; // active Picture Parameter Set + cSliceHeader sh; + public: + cContext(void) { sps = 0; pps = 0; } + void Define(cSequenceParameterSet &SPS); + void Define(cPictureParameterSet &PPS); + void Define(cSliceHeader &SH); + void Define(cPictureTiming &PT); + void ActivateSPS(uint32_t ID); + void ActivatePPS(uint32_t ID); + const cSequenceParameterSet *ActiveSPS(void) const { return sps; } + const cPictureParameterSet *ActivePPS(void) const { return pps; } + const cSliceHeader *CurrentSlice(void) const { return sh.Defined() ? &sh : 0; } + int GetFramesPerSec(void) const; + }; + + inline void cContext::ActivateSPS(uint32_t ID) + { + if (ID >= (sizeof (spsStore) / sizeof (*spsStore))) + throw new cException("ERROR: H264::cContext::ActivateSPS(): id out of range"); + if (!spsStore[ID].Defined()) + throw new cException("ERROR: H264::cContext::ActivateSPS(): requested SPS is undefined"); + sps = &spsStore[ID]; + } + + inline void cContext::ActivatePPS(uint32_t ID) + { + if (ID >= (sizeof (ppsStore) / sizeof (*ppsStore))) + throw new cException("ERROR: H264::cContext::ActivatePPS(): id out of range"); + if (!ppsStore[ID].Defined()) + throw new cException("ERROR: H264::cContext::ActivatePPS(): requested PPS is undefined"); + pps = &ppsStore[ID]; + ActivateSPS(pps->seq_parameter_set_id); + } + + inline void cContext::Define(cSequenceParameterSet &SPS) + { + if (SPS.seq_parameter_set_id >= (sizeof (spsStore) / sizeof (*spsStore))) + throw new cException("ERROR: H264::cContext::DefineSPS(): id out of range"); + SPS.defined = true; + spsStore[SPS.seq_parameter_set_id] = SPS; + } + + inline void cContext::Define(cPictureParameterSet &PPS) + { + if (PPS.pic_parameter_set_id >= (sizeof (ppsStore) / sizeof (*ppsStore))) + throw new cException("ERROR: H264::cContext::DefinePPS(): id out of range"); + PPS.defined = true; + ppsStore[PPS.pic_parameter_set_id] = PPS; + } + + inline void cContext::Define(cSliceHeader &SH) + { + SH.defined = true; + SH.picOrderCntType = ActiveSPS()->pic_order_cnt_type; + + // ITU-T Rec. H.264 (03/2005): 7.4.1.2.4 + SH.isFirstSliceOfCurrentAccessUnit = !sh.Defined() + || (sh.frame_num != SH.frame_num) + || (sh.pic_parameter_set_id != SH.pic_parameter_set_id) + || (sh.field_pic_flag != SH.field_pic_flag) + || (sh.bottom_field_flag != SH.bottom_field_flag) + || (sh.nalRefIdc != SH.nalRefIdc + && (sh.nalRefIdc == 0 || SH.nalRefIdc == 0)) + || (sh.picOrderCntType == 0 && SH.picOrderCntType == 0 + && (sh.pic_order_cnt_lsb != SH.pic_order_cnt_lsb + || sh.delta_pic_order_cnt_bottom != SH.delta_pic_order_cnt_bottom)) + || (sh.picOrderCntType == 1 && SH.picOrderCntType == 1 + && (sh.delta_pic_order_cnt[0] != SH.delta_pic_order_cnt[0] + || sh.delta_pic_order_cnt[1] != SH.delta_pic_order_cnt[1])) + || (sh.nalUnitType != SH.nalUnitType + && (sh.nalUnitType == 5 || SH.nalUnitType == 5)) + || (sh.nalUnitType == 5 && SH.nalUnitType == 5 + && sh.idr_pic_id != SH.idr_pic_id); + + sh = SH; + } + + inline void cContext::Define(cPictureTiming &PT) + { + PT.defined = true; + ((cSequenceParameterSet *)ActiveSPS())->pic_timing_sei = PT; + } + + // --- cSimpleBuffer ------------------------------------------------------- + + class cSimpleBuffer { + private: + uchar *data; + int size; + int avail; + int gotten; + public: + cSimpleBuffer(int Size); + ~cSimpleBuffer(); + int Size(void) { return size; } + int Available(void) { return avail; } + int Free(void) { return size - avail; } + int Put(const uchar *Data, int Count); + uchar *Get(int &Count); + void Del(int Count); + void Clear(void); + }; + + // --- cParser ------------------------------------------------------------- + + class cParser { + private: + bool syncing; + bool omitPicTiming; + cContext context; + cSimpleBuffer nalUnitDataBuffer; + void hrd_parameters(cSequenceParameterSet &SPS, cBitReader &br); + void ParseSequenceParameterSet(uint8_t *Data, int Count); + void ParsePictureParameterSet(uint8_t *Data, int Count); + void ParseSlice(uint8_t *Data, int Count); + void reserved_sei_message(uint32_t payloadSize, cBitReader &br); + void pic_timing(uint32_t payloadSize, cBitReader &br); + void buffering_period(uint32_t payloadSize, cBitReader &br); + void sei_payload(uint32_t payloadType, uint32_t payloadSize, cBitReader &br); + void sei_message(cBitReader &br); + void ParseSEI(uint8_t *Data, int Count); + public: + cParser(bool OmitPicTiming = true); + const cContext &Context(void) const { return context; } + void PutNalUnitData(const uchar *Data, int Count); + void Reset(void); + void Process(void); + }; +} +} + +#endif // __VDR172H264PARSER_H + +#endif + diff --git a/vdr172remux.c b/vdr172remux.c new file mode 100644 index 0000000..43804be --- /dev/null +++ b/vdr172remux.c @@ -0,0 +1,3453 @@ + +#include <vdr/config.h> + +#ifndef APIVERSNUM +#define APIVERSNUM VDRVERSNUM +#endif + +#if APIVERSNUM >= 10703 + +/* + * remux.c: A streaming MPEG2 remultiplexer + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * The parts of this code that implement cTS2PES have been taken from + * the Linux DVB driver's 'tuxplayer' example and were rewritten to suit + * VDR's needs. + * + * The cRepacker family's code was originally written by Reinhard Nissl <rnissl@gmx.de>, + * and adapted to the VDR coding style by Klaus.Schmidinger@cadsoft.de. + * + * $Id: remux.c 2.2 2008/12/13 14:30:15 kls Exp $ + */ + +#include "vdr172remux.h" +#include <stdlib.h> +#include <vdr/channels.h> +#include <vdr/device.h> +#if 0 +#include "libsi/si.h" +#include "libsi/section.h" +#include "libsi/descriptor.h" +#endif +#include <vdr/shutdown.h> +#include <vdr/tools.h> +#include <vdr/recording.h> +#include "vdr172h264parser.h" + +#if APIVERSNUM >= 10703 +#define FRAMESPERSEC ((int)(DEFAULTFRAMESPERSECOND)) +#endif + +namespace vdr172 +{ +#if 0 +// Set this to 'true' for debug output: +static bool DebugPatPmt = false; + +#define dbgpatpmt(a...) if (DebugPatPmt) fprintf(stderr, a) +#endif + +ePesHeader AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, bool *ContinuationHeader) +{ + if (Count < 7) + return phNeedMoreData; // too short + + if ((Data[6] & 0xC0) == 0x80) { // MPEG 2 + if (Count < 9) + return phNeedMoreData; // too short + + PesPayloadOffset = 6 + 3 + Data[8]; + if (Count < PesPayloadOffset) + return phNeedMoreData; // too short + + if (ContinuationHeader) + *ContinuationHeader = ((Data[6] == 0x80) && !Data[7] && !Data[8]); + + return phMPEG2; // MPEG 2 + } + + // check for MPEG 1 ... + PesPayloadOffset = 6; + + // skip up to 16 stuffing bytes + for (int i = 0; i < 16; i++) { + if (Data[PesPayloadOffset] != 0xFF) + break; + + if (Count <= ++PesPayloadOffset) + return phNeedMoreData; // too short + } + + // skip STD_buffer_scale/size + if ((Data[PesPayloadOffset] & 0xC0) == 0x40) { + PesPayloadOffset += 2; + + if (Count <= PesPayloadOffset) + return phNeedMoreData; // too short + } + + if (ContinuationHeader) + *ContinuationHeader = false; + + if ((Data[PesPayloadOffset] & 0xF0) == 0x20) { + // skip PTS only + PesPayloadOffset += 5; + } + else if ((Data[PesPayloadOffset] & 0xF0) == 0x30) { + // skip PTS and DTS + PesPayloadOffset += 10; + } + else if (Data[PesPayloadOffset] == 0x0F) { + // continuation header + PesPayloadOffset++; + + if (ContinuationHeader) + *ContinuationHeader = true; + } + else + return phInvalid; // unknown + + if (Count < PesPayloadOffset) + return phNeedMoreData; // too short + + return phMPEG1; // MPEG 1 +} + +// --- cRepacker ------------------------------------------------------------- + +#define MIN_LOG_INTERVAL 10 // min. # of seconds between two consecutive log messages of a cRepacker +#define LOG(a...) (LogAllowed() && (esyslog(a), true)) + +class cRepacker { +protected: + bool initiallySyncing; + int maxPacketSize; + uint8_t subStreamId; + time_t lastLog; + int suppressedLogMessages; + bool LogAllowed(void); + void DroppedData(const char *Reason, int Count) { LOG("%s (dropped %d bytes)", Reason, Count); } + virtual int Put(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count, int CapacityNeeded); +public: + static int PutAllOrNothing(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count, int CapacityNeeded); + cRepacker(void); + virtual ~cRepacker() {} + virtual void Reset(void) { initiallySyncing = true; } + virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) = 0; + virtual int BreakAt(const uchar *Data, int Count) = 0; + virtual int QuerySnoopSize(void) { return 0; } + void SetMaxPacketSize(int MaxPacketSize) { maxPacketSize = MaxPacketSize; } + void SetSubStreamId(uint8_t SubStreamId) { subStreamId = SubStreamId; } + }; + +cRepacker::cRepacker(void) +{ + initiallySyncing = true; + maxPacketSize = 6 + 65535; + subStreamId = 0; + suppressedLogMessages = 0;; + lastLog = 0; +} + +bool cRepacker::LogAllowed(void) +{ + bool Allowed = time(NULL) - lastLog >= MIN_LOG_INTERVAL; + lastLog = time(NULL); + if (Allowed) { + if (suppressedLogMessages) { + esyslog("%d cRepacker messages suppressed", suppressedLogMessages); + suppressedLogMessages = 0; + } + } + else + suppressedLogMessages++; + return Allowed; +} + +int cRepacker::Put(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count, int CapacityNeeded) +{ + return PutAllOrNothing(ResultBuffer, Data, Count, CapacityNeeded); +} + +int cRepacker::PutAllOrNothing(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count, int CapacityNeeded) +{ + if (CapacityNeeded >= Count && ResultBuffer->Free() < CapacityNeeded) { + esyslog("ERROR: possible result buffer overflow, dropped %d out of %d byte", CapacityNeeded, CapacityNeeded); + return 0; + } + int n = ResultBuffer->Put(Data, Count); + if (n != Count) + esyslog("ERROR: result buffer overflow, dropped %d out of %d byte", Count - n, Count); + return n; +} + +// --- cCommonRepacker ------------------------------------------------------- + +class cCommonRepacker : public cRepacker { +protected: + int skippedBytes; + int packetTodo; + uchar fragmentData[6 + 65535 + 3]; + int fragmentLen; + uchar pesHeader[6 + 3 + 255 + 5 + 3]; // 5: H.264 AUD + int pesHeaderLen; + uchar pesHeaderBackup[6 + 3 + 255]; + int pesHeaderBackupLen; + uint32_t scanner; + uint32_t localScanner; + int localStart; + bool PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); + virtual int QuerySnoopSize(void) { return 4; } + virtual void Reset(void); + }; + +void cCommonRepacker::Reset(void) +{ + cRepacker::Reset(); + skippedBytes = 0; + packetTodo = 0; + fragmentLen = 0; + pesHeaderLen = 0; + pesHeaderBackupLen = 0; + localStart = -1; +} + +bool cCommonRepacker::PushOutPacket(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) +{ + // enter packet length into PES header ... + if (fragmentLen > 0) { // ... which is contained in the fragment buffer + // determine PES packet payload + int PacketLen = fragmentLen + Count - 6; + fragmentData[ 4 ] = PacketLen >> 8; + fragmentData[ 5 ] = PacketLen & 0xFF; + // just skip packets with no payload + int PesPayloadOffset = 0; + if (AnalyzePesHeader(fragmentData, fragmentLen, PesPayloadOffset) <= phInvalid) + LOG("cCommonRepacker: invalid PES packet encountered in fragment buffer!"); + else if (6 + PacketLen <= PesPayloadOffset) { + fragmentLen = 0; + return true; // skip empty packet + } + // amount of data to put into result buffer: a negative Count value means + // to strip off any partially contained start code. + int Bite = fragmentLen + (Count >= 0 ? 0 : Count); + // put data into result buffer + int n = Put(ResultBuffer, fragmentData, Bite, 6 + PacketLen); + fragmentLen = 0; + if (n != Bite) + return false; + } + else if (pesHeaderLen > 0) { // ... which is contained in the PES header buffer + int PacketLen = pesHeaderLen + Count - 6; + pesHeader[ 4 ] = PacketLen >> 8; + pesHeader[ 5 ] = PacketLen & 0xFF; + // just skip packets with no payload + int PesPayloadOffset = 0; + if (AnalyzePesHeader(pesHeader, pesHeaderLen, PesPayloadOffset) <= phInvalid) + LOG("cCommonRepacker: invalid PES packet encountered in header buffer!"); + else if (6 + PacketLen <= PesPayloadOffset) { + pesHeaderLen = 0; + return true; // skip empty packet + } + // amount of data to put into result buffer: a negative Count value means + // to strip off any partially contained start code. + int Bite = pesHeaderLen + (Count >= 0 ? 0 : Count); + // put data into result buffer + int n = Put(ResultBuffer, pesHeader, Bite, 6 + PacketLen); + pesHeaderLen = 0; + if (n != Bite) + return false; + } + // append further payload + if (Count > 0) { + // amount of data to put into result buffer + int Bite = Count; + // put data into result buffer + int n = Put(ResultBuffer, Data, Bite, Bite); + if (n != Bite) + return false; + } + // we did it ;-) + return true; +} + +// --- cAudGenerator --------------------------------------------------------- + +class cAudGenerator { +private: + H264::cSimpleBuffer buffer; + int overflowByteCount; + H264::cSliceHeader::eAccessUnitType accessUnitType; + int sliceTypes; +public: + cAudGenerator(void); + void CollectSliceType(const H264::cSliceHeader *SH); + int CollectData(const uchar *Data, int Count); + void Generate(cRingBufferLinear *const ResultBuffer); +}; + +cAudGenerator::cAudGenerator() + : buffer(MAXFRAMESIZE) +{ + overflowByteCount = 0; + accessUnitType = H264::cSliceHeader::Frame; + sliceTypes = 0; +} + +int cAudGenerator::CollectData(const uchar *Data, int Count) +{ + // buffer frame data until AUD can be generated + int n = buffer.Put(Data, Count); + overflowByteCount += (Count - n); + // always report "success" as an error message will be shown in Generate() + return Count; +} + +void cAudGenerator::CollectSliceType(const H264::cSliceHeader *SH) +{ + if (!SH) + return; + // remember type of current access unit + accessUnitType = SH->GetAccessUnitType(); + // translate slice_type into part of primary_pic_type and merge them + switch (SH->slice_type) { + case 2: // I + case 7: // I only => I + sliceTypes |= 0x10000; + break; + case 0: // P + case 5: // P only => I, P + sliceTypes |= 0x11000; + break; + case 1: // B + case 6: // B only => I, P, B + sliceTypes |= 0x11100; + break; + case 4: // SI + case 9: // SI only => SI + sliceTypes |= 0x00010; + break; + case 3: // SP + case 8: // SP only => SI, SP + sliceTypes |= 0x00011; + break; + } +} + +void cAudGenerator::Generate(cRingBufferLinear *const ResultBuffer) +{ + int primary_pic_type; + // translate the merged primary_pic_type parts into primary_pic_type + switch (sliceTypes) { + case 0x10000: // I + primary_pic_type = 0; + break; + case 0x11000: // I, P + primary_pic_type = 1; + break; + case 0x11100: // I, P, B + primary_pic_type = 2; + break; + case 0x00010: // SI + primary_pic_type = 3; + break; + case 0x00011: // SI, SP + primary_pic_type = 4; + break; + case 0x10010: // I, SI + primary_pic_type = 5; + break; + case 0x11011: // I, SI, P, SP + case 0x10011: // I, SI, SP + case 0x11010: // I, SI, P + primary_pic_type = 6; + break; + case 0x11111: // I, SI, P, SP, B + case 0x11110: // I, SI, P, B + primary_pic_type = 7; + break; + default: + primary_pic_type = -1; // frame without slices? + } + // drop an incorrect frame + if (primary_pic_type < 0) + esyslog("ERROR: cAudGenerator::Generate(): dropping frame without slices"); + else { + // drop a partitial frame + if (overflowByteCount > 0) + esyslog("ERROR: cAudGenerator::Generate(): frame exceeds MAXFRAMESIZE bytes (required size: %d bytes), dropping frame", buffer.Size() + overflowByteCount); + else { + int Count; + uchar *Data = buffer.Get(Count); + int PesPayloadOffset = 0; + AnalyzePesHeader(Data, Count, PesPayloadOffset); + // enter primary_pic_type into AUD + Data[ PesPayloadOffset + 4 ] |= primary_pic_type << 5; + // mangle the "start code" to pass the information that this access unit is a + // bottom field to ScanVideoPacket() where this modification will be reverted. + if (accessUnitType == H264::cSliceHeader::BottomField) + Data[ PesPayloadOffset + 3 ] |= 0x80; + // store the buffered frame + cRepacker::PutAllOrNothing(ResultBuffer, Data, Count, Count); + } + } + // prepare for next run + buffer.Clear(); + overflowByteCount = 0; + sliceTypes = 0; +} + +// --- cVideoRepacker -------------------------------------------------------- + +#define IPACKS 2048 +#define SC_SEQUENCE 0xB3 // "sequence header code" +#define SC_GROUP 0xB8 // "group start code" +#define SC_PICTURE 0x00 // "picture start code" + +class cVideoRepacker : public cCommonRepacker { +private: + enum eState { + syncing, + findPicture, + scanPicture + }; + int state; + bool framePicture; + bool pictureExtensionAhead; + bool collectChunkData; + H264::cSimpleBuffer chunkData; + H264::cParser *h264Parser; + bool &h264; + int sliceSeen; + bool audSeen; + cAudGenerator *audGenerator; + const uchar *startCodeLocations[(IPACKS + 3) / 4]; + int startCodeLocationCount; + int startCodeLocationIndex; + bool startCodeLocationsPrepared; + void CheckAudGeneration(bool SliceNalUnitType, bool SyncPoint, const uchar *const Data, cRingBufferLinear *const ResultBuffer, const uchar *&Payload, const uchar StreamID, const ePesHeader MpegLevel); + void PushOutCurrentFrameAndStartNewPacket(const uchar *const Data, cRingBufferLinear *const ResultBuffer, const uchar *&Payload, const uchar StreamID, const ePesHeader MpegLevel); + void HandleNalUnit(const uchar *const Data, cRingBufferLinear *const ResultBuffer, const uchar *&Payload, const uchar StreamID, const ePesHeader MpegLevel, const uchar *&NalPayload); + void HandleStartCode(const uchar *const Data, cRingBufferLinear *const ResultBuffer, const uchar *&Payload, const uchar StreamID, const ePesHeader MpegLevel, const uchar *&ChunkPayload); + inline bool ScanDataForStartCodeSlow(const uchar *const Data); + inline bool ScanDataForStartCodeFast(const uchar *&Data, const uchar *Limit); + inline bool ScanDataForStartCode(const uchar *&Data, int &Done, int &Todo, int PesPayloadOffset); + inline void AdjustCounters(const int Delta, int &Done, int &Todo); + inline bool ScanForEndOfPictureSlow(const uchar *&Data); + inline bool ScanForEndOfPictureFast(const uchar *&Data, const uchar *Limit); + inline bool ScanForEndOfPicture(const uchar *&Data, const uchar *Limit); + inline void PushStartCodeLocation(const uchar *Data); + inline const uchar *PeekStartCodeLocation(void); + inline const uchar *PullStartCodeLocation(void); + void CollectData(const uchar *Data, int Count); + void BeginCollectingPictureExtension(void); + void EndCollectingPictureExtension(void); + bool DetermineFramePicture(void); + void GenerateFieldPicturesHint(bool FramePicture, const uchar *const Data, const uchar AndMask, const uchar OrMask); + void SwitchToMpeg12(void); +protected: + virtual int Put(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count, int CapacityNeeded); +public: + cVideoRepacker(bool &H264); + ~cVideoRepacker(); + virtual void Reset(void); + virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); + virtual int BreakAt(const uchar *Data, int Count); + }; + +cVideoRepacker::cVideoRepacker(bool &H264) +: chunkData(1024) +, h264(H264) +{ + // assume H.264 -- we'll fallback to MPEG1/2 when necessary + h264 = true; + h264Parser = (H264 ? new H264::cParser() : 0); + audGenerator = 0; + Reset(); +} + +cVideoRepacker::~cVideoRepacker() +{ + delete h264Parser; + delete audGenerator; +} + +void cVideoRepacker::Reset(void) +{ + cCommonRepacker::Reset(); + if (h264Parser) + h264Parser->Reset(); + scanner = 0xFFFFFFFF; + state = syncing; + framePicture = true; + pictureExtensionAhead = false; + collectChunkData = false; + chunkData.Clear(); + sliceSeen = -1; + audSeen = false; + delete audGenerator; + audGenerator = 0; + startCodeLocationCount = 0; + startCodeLocationsPrepared = false; +} + +void cVideoRepacker::SwitchToMpeg12(void) +{ + if (!h264Parser) + return; + dsyslog("cVideoRepacker: switching to MPEG1/2 mode"); + delete h264Parser; + h264Parser = 0; + delete audGenerator; + audGenerator = 0; + h264 = false; +} + +int cVideoRepacker::Put(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count, int CapacityNeeded) +{ + if (!audGenerator) + return cCommonRepacker::Put(ResultBuffer, Data, Count, CapacityNeeded); + + return audGenerator->CollectData(Data, Count); +} + +void cVideoRepacker::CollectData(const uchar *Data, int Count) +{ + if (h264Parser) + h264Parser->PutNalUnitData(Data, Count); + else if (collectChunkData) + chunkData.Put(Data, Count); +} + +void cVideoRepacker::HandleNalUnit(const uchar *const Data, cRingBufferLinear *const ResultBuffer, const uchar *&Payload, const uchar StreamID, const ePesHeader MpegLevel, const uchar *&NalPayload) +{ + // check whether we need to fall back to MPEG1/2 + if (initiallySyncing) { + switch (*Data) { + case SC_SEQUENCE: + case SC_GROUP: + case SC_PICTURE: + // the above start codes do not appear in H.264 so let's switch to MPEG1/2 + SwitchToMpeg12(); + // delegate startcode to appropriate handler + HandleStartCode(Data, ResultBuffer, Payload, StreamID, MpegLevel, NalPayload); + return; + } + } + + // valid NAL units start with a zero bit + if (*Data & 0x80) { + LOG("cVideoRepacker: found invalid NAL unit: stream seems to be scrambled or not demultiplexed"); + return; + } + + // collect NAL unit's remaining data and process it + CollectData(NalPayload, Data - 3 - NalPayload); + h264Parser->Process(); + + // which kind of NAL unit have we got? + const int nal_unit_type = *Data & 0x1F; + switch (nal_unit_type) { + case 1: // coded slice of a non-IDR picture + case 2: // coded slice data partition A + case 5: // coded slice of an IDR picture + CheckAudGeneration(true, false, Data, ResultBuffer, Payload, StreamID, MpegLevel); + break; + case 3: // coded slice data partition B + case 4: // coded slice data partition C + case 19: // coded slice of an auxiliary coded picture without partitioning + break; + case 6: // supplemental enhancement information (SEI) + case 7: // sequence parameter set + case 8: // picture parameter set + case 10: // end of sequence + case 11: // end of stream + case 13: // sequence parameter set extension + CheckAudGeneration(false, nal_unit_type == 7, Data, ResultBuffer, Payload, StreamID, MpegLevel); + break; + case 12: // filler data + break; + case 14 ... 18: // reserved + CheckAudGeneration(false, false, Data, ResultBuffer, Payload, StreamID, MpegLevel); + case 20 ... 23: // reserved + LOG("cVideoRepacker: found reserved NAL unit type: stream seems to be scrambled"); + break; + case 0: // unspecified + case 24 ... 31: // unspecified + LOG("cVideoRepacker: found unspecified NAL unit type: stream seems to be scrambled"); + break; + case 9: { // access unit delimiter + audSeen = true; + CheckAudGeneration(false, true, Data, ResultBuffer, Payload, StreamID, MpegLevel); + // mangle the "start code" to pass the information "the next access unit will be the + // second field of the current frame" to ScanVideoPacket() where this modification + // will be reverted. + const H264::cSliceHeader *SH = h264Parser->Context().CurrentSlice(); + GenerateFieldPicturesHint(!SH || SH->GetAccessUnitType() == H264::cSliceHeader::Frame, Data, 0xFF, 0x80); + } + break; + } + + // collect 0x00 0x00 0x01 for current NAL unit + static const uchar InitPayload[3] = { 0x00, 0x00, 0x01 }; + CollectData(InitPayload, sizeof (InitPayload)); + NalPayload = Data; +} + +void cVideoRepacker::GenerateFieldPicturesHint(bool FramePicture, const uchar *const Data, const uchar AndMask, const uchar OrMask) +{ + if (FramePicture) + framePicture = true; + else { + framePicture ^= true; // toggle between frame/first field and second field + if (!framePicture) { + // the last picture was a field so set a hint for this second field + *(uchar *)Data &= AndMask; + *(uchar *)Data |= OrMask; + } + } +} + +void cVideoRepacker::CheckAudGeneration(bool SliceNalUnitType, bool SyncPoint, const uchar *const Data, cRingBufferLinear *const ResultBuffer, const uchar *&Payload, const uchar StreamID, const ePesHeader MpegLevel) +{ + // we cannot generate anything until we have reached the synchronisation point + if (sliceSeen < 0 && !SyncPoint) + return; + // detect transition from slice to non-slice NAL units + const bool WasSliceSeen = (sliceSeen != false); + const bool IsSliceSeen = SliceNalUnitType; + sliceSeen = IsSliceSeen; + // collect slice types for AUD generation + if (WasSliceSeen && audGenerator) + audGenerator->CollectSliceType(h264Parser->Context().CurrentSlice()); + // handle access unit delimiter at the transition from slice to non-slice NAL units + if (WasSliceSeen && !IsSliceSeen) { + // an Access Unit Delimiter indicates that the current picture is done. So let's + // push out the current frame to start a new packet for the next picture. + PushOutCurrentFrameAndStartNewPacket(Data, ResultBuffer, Payload, StreamID, MpegLevel); + if (state == findPicture) { + // go on with scanning the picture data + state++; + } + // generate the AUD and push out the buffered frame + if (audGenerator) { + audGenerator->Generate(ResultBuffer); + if (audSeen) { + // we nolonger need to generate AUDs as they are part of the stream + delete audGenerator; + audGenerator = 0; + } + } + else if (!audSeen) // we do need to generate AUDs + audGenerator = new cAudGenerator; + } +} + +void cVideoRepacker::HandleStartCode(const uchar *const Data, cRingBufferLinear *const ResultBuffer, const uchar *&Payload, const uchar StreamID, const ePesHeader MpegLevel, const uchar *&ChunkPayload) +{ + // collect chunk's remaining data + CollectData(ChunkPayload, Data - 3 - ChunkPayload); + // currently, only picture extension is collected, so end collecting now + EndCollectingPictureExtension(); + + // which kind of start code have we got? + switch (*Data) { + case 0xB9 ... 0xFF: // system start codes + LOG("cVideoRepacker: found system start code: stream seems to be scrambled or not demultiplexed"); + break; + case 0xB0 ... 0xB1: // reserved start codes + case 0xB6: + LOG("cVideoRepacker: found reserved start code: stream seems to be scrambled"); + break; + case 0xB4: // sequence error code + LOG("cVideoRepacker: found sequence error code: stream seems to be damaged"); + case 0xB2: // user data start code + break; + case 0xB5: // extension start code + if (pictureExtensionAhead) { + pictureExtensionAhead = false; + // mangle the "start code" to pass the information "the next access unit will be the + // second field of the current frame" to ScanVideoPacket() where this modification + // will be reverted. + GenerateFieldPicturesHint(DetermineFramePicture(), Data, 0x00, 0xB9); + BeginCollectingPictureExtension(); + } + break; + case 0x00: // picture start code + pictureExtensionAhead = true; + case 0xB8: // group start code + case 0xB3: // sequence header code + case 0xB7: // sequence end code + // the above start codes indicate that the current picture is done. So let's + // push out the current frame to start a new packet for the next picture. + PushOutCurrentFrameAndStartNewPacket(Data, ResultBuffer, Payload, StreamID, MpegLevel); + break; + case 0x01 ... 0xAF: // slice start codes + if (state == findPicture) { + // go on with scanning the picture data + state++; + } + break; + } + + // collect 0x00 0x00 0x01 for current chunk + static const uchar InitPayload[3] = { 0x00, 0x00, 0x01 }; + CollectData(InitPayload, sizeof (InitPayload)); + ChunkPayload = Data; +} + +void cVideoRepacker::BeginCollectingPictureExtension(void) +{ + chunkData.Clear(); + collectChunkData = true; +} + +void cVideoRepacker::EndCollectingPictureExtension(void) +{ + collectChunkData = false; +} + +bool cVideoRepacker::DetermineFramePicture(void) +{ + bool FieldPicture = false; + int Count; + uchar *Data = chunkData.Get(Count); + if (Data && Count >= 7) { + if (!Data[0] && !Data[1] && Data[2] == 0x01 && (Data[3] == 0xB5 || Data[3] == 0xB9)) { // extension startcode or hint + if ((Data[4] & 0xF0) == 0x80) { // picture coding extension + int picture_structure = Data[6] & 0x03; + FieldPicture = picture_structure == 0x01 || picture_structure == 0x02; + } + } + } +if (FieldPicture) fprintf(stderr, "----- MPEG2 field picture detected ---------------------------\n"); + return !FieldPicture; +} + +void cVideoRepacker::PushOutCurrentFrameAndStartNewPacket(const uchar *const Data, cRingBufferLinear *const ResultBuffer, const uchar *&Payload, const uchar StreamID, const ePesHeader MpegLevel) +{ + // synchronisation is detected some bytes after frame start. + const int SkippedBytesLimit = 4; + + if (state == scanPicture) { + // picture data has been found so let's push out the current frame. + // If the byte count get's negative then the current buffer ends in a + // partitial start code that must be stripped off, as it shall be put + // in the next packet. + PushOutPacket(ResultBuffer, Payload, Data - 3 - Payload); + // go on with syncing to the next picture + state = syncing; + } + // when already synced to a picture, just go on collecting data + if (state != syncing) + return; + // we're synced to a picture so prepare a new packet + if (initiallySyncing) { // omit report for the typical initial case + initiallySyncing = false; + isyslog("cVideoRepacker: operating in %s mode", h264Parser ? "H.264" : "MPEG1/2"); + } + else if (skippedBytes > SkippedBytesLimit) // report that syncing dropped some bytes + LOG("cVideoRepacker: skipped %d bytes to sync on next picture", skippedBytes - SkippedBytesLimit); + skippedBytes = 0; + // if there is a PES header available, then use it ... + if (pesHeaderBackupLen > 0) { + // ISO 13818-1 says: + // In the case of video, if a PTS is present in a PES packet header + // it shall refer to the access unit containing the first picture start + // code that commences in this PES packet. A picture start code commences + // in PES packet if the first byte of the picture start code is present + // in the PES packet. + memcpy(pesHeader, pesHeaderBackup, pesHeaderBackupLen); + pesHeaderLen = pesHeaderBackupLen; + pesHeaderBackupLen = 0; + } + else { + // ... otherwise create a continuation PES header + pesHeaderLen = 0; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x01; + pesHeader[pesHeaderLen++] = StreamID; // video stream ID + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + + if (MpegLevel == phMPEG2) { + pesHeader[pesHeaderLen++] = 0x80; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + } + else + pesHeader[pesHeaderLen++] = 0x0F; + } + // add an AUD in H.264 mode when not present in stream + if (h264Parser && !audSeen) { + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x01; + pesHeader[pesHeaderLen++] = 0x09; // access unit delimiter + pesHeader[pesHeaderLen++] = 0x10; // will be filled later + } + // append the first three bytes of the start code + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x01; + // the next packet's payload will begin with the fourth byte of + // the start code (= the actual code) + Payload = Data; + // as there is no length information available, assume the + // maximum we can hold in one PES packet + packetTodo = maxPacketSize - pesHeaderLen; + // go on with finding the picture data + state++; +} + +bool cVideoRepacker::ScanDataForStartCodeSlow(const uchar *const Data) +{ + scanner <<= 8; + bool FoundStartCode = (scanner == 0x00000100); + scanner |= *Data; + return FoundStartCode; +} + +bool cVideoRepacker::ScanDataForStartCodeFast(const uchar *&Data, const uchar *Limit) +{ + // We enter here when it is safe to access at least 3 bytes before Data (e. g. + // the tail of a previous run) and one byte after Data (xx yy zz [aa] bb {cc}). + // On return, Data shall either point to the last valid byte of the block or to + // the byte qualifying the start code (00 00 01 [ss]). + // As we are searching for 0x01, we've to move pointers (xx yy [zz] aa {bb} cc) + // to find start code "aa" for example after a packet boundery. + Data--; + Limit--; + + while (Data < Limit && (Data = (const uchar *)memchr(Data, 0x01, Limit - Data))) { + if (Data[-2] || Data[-1]) + Data += 3; + else { + scanner = 0x00000100 | *++Data; + return true; + } + } + + Data = Limit; + uint32_t *Scanner = (uint32_t *)(Data - 3); + scanner = ntohl(*Scanner); + return false; +} + +bool cVideoRepacker::ScanDataForStartCode(const uchar *&Data, int &Done, int &Todo, int PesPayloadOffset) +{ + const int ReasonableDataSizeForFastScanning = 12; + + if (!startCodeLocationsPrepared) { + // use slow scanning until it is safe to use fast scanning + if (Done < PesPayloadOffset + 3) + return ScanDataForStartCodeSlow(Data); + + // process available data but not more than needed for the current packet + int Limit = Todo; + if (state != syncing && Limit > packetTodo) + Limit = packetTodo; + + // use slow scanning when there is not enough data left + if (Limit < ReasonableDataSizeForFastScanning) + return ScanDataForStartCodeSlow(Data); + + // it's reasonable to use fast scanning + const uchar *const DataOrig = Data; + bool FoundStartCode = ScanDataForStartCodeFast(Data, Data + Limit); + AdjustCounters(Data - DataOrig, Done, Todo); + return FoundStartCode; + } + + // process available data but not more than needed for the current packet + const uchar *Limit = Data + Todo - 1; + if (state != syncing && Todo > packetTodo) { + if (packetTodo <= 0) // overfill phase + Limit = Data; // do a single ScanDataForStartCodeSlow() below + else + Limit = Data + packetTodo - 1; + } + + // prepare the "not found" case + bool FoundStartCode = false; + const uchar *const DataOrig = Data; + Data = Limit; + + // get the next start code location which fits into the limit + const uchar *p = PeekStartCodeLocation(); + if (p && p <= Limit) { + Data = PullStartCodeLocation(); + FoundStartCode = true; + } + + // setup the scanner variable + int bite = Data - DataOrig; + if (bite <= 3) { + // to few data, need to do byte shifting + for (int i = 0; i <= bite; i++) + ScanDataForStartCodeSlow(DataOrig + i); + } + else { + // it's safe to access the last 4 bytes directly + uint32_t *Scanner = (uint32_t *)(Data - 3); + scanner = ntohl(*Scanner); + } + + AdjustCounters(bite, Done, Todo); + return FoundStartCode; +} + +void cVideoRepacker::AdjustCounters(const int Delta, int &Done, int &Todo) +{ + Done += Delta; + Todo -= Delta; + + if (state <= syncing) + skippedBytes += Delta; + else + packetTodo -= Delta; +} + +void cVideoRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) +{ + // synchronisation is detected some bytes after frame start. + const int SkippedBytesLimit = 4; + + // reset local scanner + localStart = -1; + + int pesPayloadOffset = 0; + bool continuationHeader = false; + ePesHeader mpegLevel = AnalyzePesHeader(Data, Count, pesPayloadOffset, &continuationHeader); + if (mpegLevel <= phInvalid) { + DroppedData("cVideoRepacker: no valid PES packet header found", Count); + return; + } + if (!continuationHeader) { + // backup PES header + pesHeaderBackupLen = pesPayloadOffset; + memcpy(pesHeaderBackup, Data, pesHeaderBackupLen); + } + + // skip PES header + int done = pesPayloadOffset; + int todo = Count - done; + const uchar *data = Data + done; + // remember start of the data + const uchar *payload = data; + const uchar *ChunkPayload = payload; + startCodeLocationIndex = 0; + while (todo > 0) { + // collect number of skipped bytes while syncing + if (state <= syncing) + skippedBytes++; + // did we reach a start code? + if (ScanDataForStartCode(data, done, todo, pesPayloadOffset)) { + if (h264Parser) + HandleNalUnit(data, ResultBuffer, payload, Data[3], mpegLevel, ChunkPayload); + else + HandleStartCode(data, ResultBuffer, payload, Data[3], mpegLevel, ChunkPayload); + } + // move on + data++; + done++; + todo--; + // do we have to start a new packet as there is no more space left? + if (state != syncing && --packetTodo <= 0) { + // we connot start a new packet here if the current might end in a start + // code and this start code shall possibly be put in the next packet. So + // overfill the current packet until we can safely detect that we won't + // break a start code into pieces: + // + // A) the last four bytes were a start code. + // B) the current byte introduces a start code. + // C) the last three bytes begin a start code. + // + // Todo : Data : Rule : Result + // -----:-------------------------------:------:------- + // : XX 00 00 00 01 YY|YY YY YY YY : : + // 0 : ^^| : A : push + // -----:-------------------------------:------:------- + // : XX XX 00 00 00 01|YY YY YY YY : : + // 0 : ^^| : B : wait + // -1 : |^^ : A : push + // -----:-------------------------------:------:------- + // : XX XX XX 00 00 00|01 YY YY YY : : + // 0 : ^^| : C : wait + // -1 : |^^ : B : wait + // -2 : | ^^ : A : push + // -----:-------------------------------:------:------- + // : XX XX XX XX 00 00|00 01 YY YY : : + // 0 : ^^| : C : wait + // -1 : |^^ : C : wait + // -2 : | ^^ : B : wait + // -3 : | ^^ : A : push + // -----:-------------------------------:------:------- + // : XX XX XX XX XX 00|00 00 01 YY : : + // 0 : ^^| : C : wait + // -1 : |^^ : C : wait + // -2 : | ^^ : : push + // -----:-------------------------------:------:------- + bool A = ((scanner & 0xFFFFFF00) == 0x00000100); + bool B = ((scanner & 0xFFFFFF) == 0x000001); + bool C = ((scanner & 0xFF) == 0x00) && (packetTodo >= -1); + if (A || (!B && !C)) { + // actually we cannot push out an overfull packet. So we'll have to + // adjust the byte count and payload start as necessary. If the byte + // count get's negative we'll have to append the excess from fragment's + // tail to the next PES header. + int bite = data + packetTodo - payload; + const uchar *excessData = fragmentData + fragmentLen + bite; + // a negative byte count means to drop some bytes from the current + // fragment's tail, to not exceed the maximum packet size. + PushOutPacket(ResultBuffer, payload, bite); + // create a continuation PES header + pesHeaderLen = 0; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x01; + pesHeader[pesHeaderLen++] = Data[3]; // video stream ID + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + + if (mpegLevel == phMPEG2) { + pesHeader[pesHeaderLen++] = 0x80; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + } + else + pesHeader[pesHeaderLen++] = 0x0F; + + // copy any excess data + while (bite++ < 0) { + // append the excess data here + pesHeader[pesHeaderLen++] = *excessData++; + packetTodo++; + } + // the next packet's payload will begin here + payload = data + packetTodo; + // as there is no length information available, assume the + // maximum we can hold in one PES packet + packetTodo += maxPacketSize - pesHeaderLen; + } + } + } + // the packet is done. Now store any remaining data into fragment buffer + // if we are no longer syncing. + if (state != syncing) { + // append the PES header ... + int bite = pesHeaderLen; + pesHeaderLen = 0; + if (bite > 0) { + memcpy(fragmentData + fragmentLen, pesHeader, bite); + fragmentLen += bite; + } + // append payload. It may contain part of a start code at it's end, + // which will be removed when the next packet gets processed. + bite = data - payload; + if (bite > 0) { + memcpy(fragmentData + fragmentLen, payload, bite); + fragmentLen += bite; + } + } + // collect data as needed + CollectData(ChunkPayload, data - ChunkPayload); + // report that syncing dropped some bytes + if (skippedBytes > SkippedBytesLimit) { + if (!initiallySyncing) // omit report for the typical initial case + LOG("cVideoRepacker: skipped %d bytes while syncing on next picture", skippedBytes - SkippedBytesLimit); + skippedBytes = SkippedBytesLimit; + } + startCodeLocationCount = 0; +} + +void cVideoRepacker::PushStartCodeLocation(const uchar *Data) +{ + startCodeLocations[startCodeLocationCount++] = Data; +} + +const uchar *cVideoRepacker::PeekStartCodeLocation() +{ + if (startCodeLocationIndex < startCodeLocationCount) + return startCodeLocations[startCodeLocationIndex]; + return 0; +} + +const uchar *cVideoRepacker::PullStartCodeLocation() +{ + const uchar *p = PeekStartCodeLocation(); + if (p != 0) + startCodeLocationIndex++; + return p; +} + +bool cVideoRepacker::ScanForEndOfPictureSlow(const uchar *&Data) +{ + localScanner <<= 8; + if (localScanner != 0x00000100) { + localScanner |= *Data++; + return false; + } + PushStartCodeLocation(Data); + localScanner |= *Data++; + // check start codes which follow picture data + if (h264Parser) { + int nal_unit_type = localScanner & 0x1F; + switch (nal_unit_type) { + case 9: // access unit delimiter + return true; + } + } + else { + switch (localScanner) { + case 0x00000100: // picture start code + case 0x000001B8: // group start code + case 0x000001B3: // sequence header code + case 0x000001B7: // sequence end code + return true; + } + } + return false; +} + +bool cVideoRepacker::ScanForEndOfPictureFast(const uchar *&Data, const uchar *Limit) +{ + // We enter here when it is safe to access at least 3 bytes before Data (e. g. + // the tail of a previous run) and one byte after Data (xx yy zz [aa] bb {cc}). + // On return, Data shall either point to the first byte outside of the block or + // to the byte following the start code (00 00 01 ss [tt]). + // As we are searching for 0x01, we've to move pointers (xx yy [zz] aa {bb} cc) + // to find start code "aa" for example after a packet boundery. + Data--; + Limit--; + + while (Data < Limit && (Data = (const uchar *)memchr(Data, 0x01, Limit - Data))) { + if (Data[-2] || Data[-1]) + Data += 3; + else { + localScanner = 0x00000100 | *++Data; + PushStartCodeLocation(Data); + // check start codes which follow picture data + if (h264Parser) { + int nal_unit_type = localScanner & 0x1F; + switch (nal_unit_type) { + case 9: // access unit delimiter + Data++; + return true; + default: + Data += 3; + } + } + else { + switch (localScanner) { + case 0x00000100: // picture start code + case 0x000001B8: // group start code + case 0x000001B3: // sequence header code + case 0x000001B7: // sequence end code + Data++; + return true; + default: + Data += 3; + } + } + } + } + + Data = Limit + 1; + uint32_t *LocalScanner = (uint32_t *)(Data - 4); + localScanner = ntohl(*LocalScanner); + return false; +} + +bool cVideoRepacker::ScanForEndOfPicture(const uchar *&Data, const uchar *Limit) +{ + const int ReasonableDataSizeForFastScanning = 12; + + const uchar *const ReasonableLimit = Limit - ReasonableDataSizeForFastScanning; + bool FoundEndOfPicture = false; + + while (Data < Limit) { + // use slow scanning until it is safe or reasonable to use fast scanning + if (localStart < 3 || Data >= ReasonableLimit) { + localStart++; + if (ScanForEndOfPictureSlow(Data)) { + FoundEndOfPicture = true; + break; + } + } + else { + const uchar *const DataOrig = Data; + FoundEndOfPicture = ScanForEndOfPictureFast(Data, Limit); + localStart += (Data - DataOrig); + break; + } + } + + return FoundEndOfPicture; +} + +int cVideoRepacker::BreakAt(const uchar *Data, int Count) +{ + int PesPayloadOffset = 0; + + if (AnalyzePesHeader(Data, Count, PesPayloadOffset) <= phInvalid) + return -1; // not enough data for test + + // setup local scanner + if (localStart < 0) { + localScanner = scanner; + localStart = 0; + } + // start where we've stopped at the last run + const uchar *data = Data + PesPayloadOffset + localStart; + const uchar *limit = Data + Count; + // scan data + startCodeLocationsPrepared = true; + if (ScanForEndOfPicture(data, limit)) { + // just detect end of picture + if (state == scanPicture && !initiallySyncing) + return data - Data; + } + if (initiallySyncing) + return -1; // fill the packet buffer completely until we have synced once + // just fill up packet and append next start code + return PesPayloadOffset + packetTodo + 4; +} + +// --- cAudioRepacker -------------------------------------------------------- + +class cAudioRepacker : public cCommonRepacker { +private: + static int bitRates[2][3][16]; + enum eState { + syncing, + scanFrame + }; + int state; + int frameTodo; + int frameSize; + int cid; + static bool IsValidAudioHeader(uint32_t Header, bool Mpeg2, int *FrameSize = NULL, int *FrameDuration = NULL); +public: + cAudioRepacker(int Cid); + virtual void Reset(void); + virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); + virtual int BreakAt(const uchar *Data, int Count); + static int GetFrameDuration(const uchar *Data, int Count, int *TrackIndex = NULL); + }; + +int cAudioRepacker::bitRates[2][3][16] = { // all values are specified as kbits/s + { + { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1 }, // MPEG 1, Layer I + { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1 }, // MPEG 1, Layer II + { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1 } // MPEG 1, Layer III + }, + { + { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, -1 }, // MPEG 2, Layer I + { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 }, // MPEG 2, Layer II/III + { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1 } // MPEG 2, Layer II/III + } + }; + +int cAudioRepacker::GetFrameDuration(const uchar *Data, int Count, int *TrackIndex) +{ + int PesPayloadOffset = 0; + ePesHeader PH = AnalyzePesHeader(Data, Count, PesPayloadOffset); + if (PH < phMPEG1) + return -1; + + const uchar *Payload = Data + PesPayloadOffset; + const int PayloadCount = Count - PesPayloadOffset; + + int FrameDuration = -1; + if ((Data[3] & 0xE0) == 0xC0 && PayloadCount >= 4) { + if (IsValidAudioHeader(((Payload[0] << 8 | Payload[1]) << 8 | Payload[2]) << 8 | Payload[3], PH == phMPEG2, NULL, &FrameDuration) && TrackIndex) + *TrackIndex = Data[3] - 0xC0; + } + + return FrameDuration; +} + +cAudioRepacker::cAudioRepacker(int Cid) +{ + cid = Cid; + Reset(); +} + +void cAudioRepacker::Reset(void) +{ + cCommonRepacker::Reset(); + scanner = 0; + state = syncing; + frameTodo = 0; + frameSize = 0; +} + +bool cAudioRepacker::IsValidAudioHeader(uint32_t Header, bool Mpeg2, int *FrameSize, int *FrameDuration) +{ + int syncword = (Header & 0xFFF00000) >> 20; + int id = (Header & 0x00080000) >> 19; + int layer = (Header & 0x00060000) >> 17; +//int protection_bit = (Header & 0x00010000) >> 16; + int bitrate_index = (Header & 0x0000F000) >> 12; + int sampling_frequency = (Header & 0x00000C00) >> 10; + int padding_bit = (Header & 0x00000200) >> 9; +//int private_bit = (Header & 0x00000100) >> 8; +//int mode = (Header & 0x000000C0) >> 6; +//int mode_extension = (Header & 0x00000030) >> 4; +//int copyright = (Header & 0x00000008) >> 3; +//int orignal_copy = (Header & 0x00000004) >> 2; + int emphasis = (Header & 0x00000003); + + if (syncword != 0xFFF) + return false; + + if (id == 0 && !Mpeg2) // reserved in MPEG 1 + return false; + + if (layer == 0) // reserved + return false; + + if (bitrate_index == 0xF) // forbidden + return false; + + if (sampling_frequency == 3) // reserved + return false; + + if (emphasis == 2) // reserved + return false; + + if (FrameSize || FrameDuration) { + static int samplingFrequencies[2][4] = { // all values are specified in Hz + { 44100, 48000, 32000, -1 }, // MPEG 1 + { 22050, 24000, 16000, -1 } // MPEG 2 + }; + + static int slots_per_frame[2][3] = { + { 12, 144, 144 }, // MPEG 1, Layer I, II, III + { 12, 144, 72 } // MPEG 2, Layer I, II, III + }; + + int mpegIndex = 1 - id; + int layerIndex = 3 - layer; + + // Layer I (i. e., layerIndex == 0) has a larger slot size + int slotSize = (layerIndex == 0) ? 4 : 1; // bytes + int sf = samplingFrequencies[mpegIndex][sampling_frequency]; + + if (FrameDuration) + *FrameDuration = 90000 * 8 * slotSize * slots_per_frame[mpegIndex][layerIndex] / sf; + + if (FrameSize) { + if (bitrate_index == 0) + *FrameSize = 0; + else { + int br = 1000 * bitRates[mpegIndex][layerIndex][bitrate_index]; // bits/s + int N = slots_per_frame[mpegIndex][layerIndex] * br / sf; // slots + + *FrameSize = (N + padding_bit) * slotSize; // bytes + } + } + } + + return true; +} + +void cAudioRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) +{ + // synchronisation is detected some bytes after frame start. + const int SkippedBytesLimit = 4; + + // reset local scanner + localStart = -1; + + int pesPayloadOffset = 0; + bool continuationHeader = false; + ePesHeader mpegLevel = AnalyzePesHeader(Data, Count, pesPayloadOffset, &continuationHeader); + if (mpegLevel <= phInvalid) { + DroppedData("cAudioRepacker: no valid PES packet header found", Count); + return; + } + if (!continuationHeader) { + // backup PES header + pesHeaderBackupLen = pesPayloadOffset; + memcpy(pesHeaderBackup, Data, pesHeaderBackupLen); + } + + // skip PES header + int done = pesPayloadOffset; + int todo = Count - done; + const uchar *data = Data + done; + // remember start of the data + const uchar *payload = data; + + while (todo > 0) { + // collect number of skipped bytes while syncing + if (state <= syncing) + skippedBytes++; + // did we reach an audio frame header? + scanner <<= 8; + scanner |= *data; + if ((scanner & 0xFFF00000) == 0xFFF00000) { + if (frameTodo <= 0 && (frameSize == 0 || skippedBytes >= 4) && IsValidAudioHeader(scanner, mpegLevel == phMPEG2, &frameSize)) { + if (state == scanFrame) { + // As a new audio frame starts here, the previous one is done. So push + // out the packet to start a new packet for the next audio frame. If + // the byte count gets negative then the current buffer ends in a + // partitial audio frame header that must be stripped off, as it shall + // be put in the next packet. + PushOutPacket(ResultBuffer, payload, data - 3 - payload); + // go on with syncing to the next audio frame + state = syncing; + } + if (state == syncing) { + if (initiallySyncing) // omit report for the typical initial case + initiallySyncing = false; + else if (skippedBytes > SkippedBytesLimit) // report that syncing dropped some bytes + LOG("cAudioRepacker(0x%02X): skipped %d bytes to sync on next audio frame", cid, skippedBytes - SkippedBytesLimit); + skippedBytes = 0; + // if there is a PES header available, then use it ... + if (pesHeaderBackupLen > 0) { + // ISO 13818-1 says: + // In the case of audio, if a PTS is present in a PES packet header + // it shall refer to the access unit commencing in the PES packet. An + // audio access unit commences in a PES packet if the first byte of + // the audio access unit is present in the PES packet. + memcpy(pesHeader, pesHeaderBackup, pesHeaderBackupLen); + pesHeaderLen = pesHeaderBackupLen; + pesHeaderBackupLen = 0; + } + else { + // ... otherwise create a continuation PES header + pesHeaderLen = 0; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x01; + pesHeader[pesHeaderLen++] = Data[3]; // audio stream ID + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + + if (mpegLevel == phMPEG2) { + pesHeader[pesHeaderLen++] = 0x80; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + } + else + pesHeader[pesHeaderLen++] = 0x0F; + } + // append the first three bytes of the audio frame header + pesHeader[pesHeaderLen++] = 0xFF; + pesHeader[pesHeaderLen++] = (scanner >> 16) & 0xFF; + pesHeader[pesHeaderLen++] = (scanner >> 8) & 0xFF; + // the next packet's payload will begin with the fourth byte of + // the audio frame header (= the actual byte) + payload = data; + // maximum we can hold in one PES packet + packetTodo = maxPacketSize - pesHeaderLen; + // expected remainder of audio frame: so far we have read 3 bytes from the frame header + frameTodo = frameSize - 3; + // go on with collecting the frame's data + state++; + } + } + } + data++; + done++; + todo--; + // do we have to start a new packet as the current is done? + if (frameTodo > 0) { + // try to skip most loops for continuous memory + int bite = frameTodo; // jump to next audio frame + if (bite > packetTodo) + bite = packetTodo; // jump only to next output packet + if (--bite > todo) + bite = todo; // jump only to next input packet + // is there enough payload available to load the scanner? + if (bite > 0 && done + bite - pesPayloadOffset >= 4) { + data += bite; + done += bite; + todo -= bite; + frameTodo -= bite; + packetTodo -= bite; + uint32_t *Scanner = (uint32_t *)(data - 4); + scanner = ntohl(*Scanner); + } + if (--frameTodo == 0) { + // the current audio frame is is done now. So push out the packet to + // start a new packet for the next audio frame. + PushOutPacket(ResultBuffer, payload, data - payload); + // go on with syncing to the next audio frame + state = syncing; + } + } + // do we have to start a new packet as there is no more space left? + if (state != syncing && --packetTodo <= 0) { + // We connot start a new packet here if the current might end in an audio + // frame header and this header shall possibly be put in the next packet. So + // overfill the current packet until we can safely detect that we won't + // break an audio frame header into pieces: + // + // A) the last four bytes were an audio frame header. + // B) the last three bytes introduce an audio frame header. + // C) the last two bytes introduce an audio frame header. + // D) the last byte introduces an audio frame header. + // + // Todo : Data : Rule : Result + // -----:-------------------------------:------:------- + // : XX XX FF Fz zz zz|YY YY YY YY : : + // 0 : ^^| : A : push + // -----:-------------------------------:------:------- + // : XX XX XX FF Fz zz|zz YY YY YY : : + // 0 : ^^| : B : wait + // -1 : |^^ : A : push + // -----:-------------------------------:------:------- + // : XX XX XX XX FF Fz|zz zz YY YY : : + // 0 : ^^| : C : wait + // -1 : |^^ : B : wait + // -2 : | ^^ : A : push + // -----:-------------------------------:------:------- + // : XX XX XX XX XX FF|Fz zz zz YY : : + // 0 : ^^| : D : wait + // -1 : |^^ : C : wait + // -2 : | ^^ : B : wait + // -3 : | ^^ : A : push + // -----:-------------------------------:------:------- + bool A = ((scanner & 0xFFF00000) == 0xFFF00000); + bool B = ((scanner & 0xFFF000) == 0xFFF000); + bool C = ((scanner & 0xFFF0) == 0xFFF0); + bool D = ((scanner & 0xFF) == 0xFF); + if (A || (!B && !C && !D)) { + // Actually we cannot push out an overfull packet. So we'll have to + // adjust the byte count and payload start as necessary. If the byte + // count gets negative we'll have to append the excess from fragment's + // tail to the next PES header. + int bite = data + packetTodo - payload; + const uchar *excessData = fragmentData + fragmentLen + bite; + // A negative byte count means to drop some bytes from the current + // fragment's tail, to not exceed the maximum packet size. + PushOutPacket(ResultBuffer, payload, bite); + // create a continuation PES header + pesHeaderLen = 0; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x01; + pesHeader[pesHeaderLen++] = Data[3]; // audio stream ID + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + pesHeader[pesHeaderLen++] = 0x00; // length still unknown + + if (mpegLevel == phMPEG2) { + pesHeader[pesHeaderLen++] = 0x80; + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = 0x00; + } + else + pesHeader[pesHeaderLen++] = 0x0F; + + // copy any excess data + while (bite++ < 0) { + // append the excess data here + pesHeader[pesHeaderLen++] = *excessData++; + packetTodo++; + } + // the next packet's payload will begin here + payload = data + packetTodo; + // as there is no length information available, assume the + // maximum we can hold in one PES packet + packetTodo += maxPacketSize - pesHeaderLen; + } + } + } + // The packet is done. Now store any remaining data into fragment buffer + // if we are no longer syncing. + if (state != syncing) { + // append the PES header ... + int bite = pesHeaderLen; + pesHeaderLen = 0; + if (bite > 0) { + memcpy(fragmentData + fragmentLen, pesHeader, bite); + fragmentLen += bite; + } + // append payload. It may contain part of an audio frame header at it's + // end, which will be removed when the next packet gets processed. + bite = data - payload; + if (bite > 0) { + memcpy(fragmentData + fragmentLen, payload, bite); + fragmentLen += bite; + } + } + // report that syncing dropped some bytes + if (skippedBytes > SkippedBytesLimit) { + if (!initiallySyncing) // omit report for the typical initial case + LOG("cAudioRepacker(0x%02X): skipped %d bytes while syncing on next audio frame", cid, skippedBytes - SkippedBytesLimit); + skippedBytes = SkippedBytesLimit; + } +} + +int cAudioRepacker::BreakAt(const uchar *Data, int Count) +{ + if (initiallySyncing) + return -1; // fill the packet buffer completely until we have synced once + + int PesPayloadOffset = 0; + + ePesHeader MpegLevel = AnalyzePesHeader(Data, Count, PesPayloadOffset); + if (MpegLevel <= phInvalid) + return -1; // not enough data for test + + // determine amount of data to fill up packet and to append next audio frame header + int packetRemainder = PesPayloadOffset + packetTodo + 4; + + // just detect end of an audio frame + if (state == scanFrame) { + // when remaining audio frame size is known, then omit scanning + if (frameTodo > 0) { + // determine amount of data to fill up audio frame and to append next audio frame header + int remaining = PesPayloadOffset + frameTodo + 4; + if (remaining < packetRemainder) + return remaining; + return packetRemainder; + } + // setup local scanner + if (localStart < 0) { + localScanner = scanner; + localStart = 0; + } + // start where we've stopped at the last run + const uchar *data = Data + PesPayloadOffset + localStart; + const uchar *limit = Data + Count; + // scan data + while (data < limit) { + localStart++; + localScanner <<= 8; + localScanner |= *data++; + // check whether the next audio frame follows + if (((localScanner & 0xFFF00000) == 0xFFF00000) && IsValidAudioHeader(localScanner, MpegLevel == phMPEG2)) + return data - Data; + } + } + // just fill up packet and append next audio frame header + return packetRemainder; +} + +// --- cDolbyRepacker -------------------------------------------------------- + +class cDolbyRepacker : public cRepacker { +private: + static int frameSizes[]; + uchar fragmentData[6 + 65535]; + int fragmentLen; + int fragmentTodo; + uchar pesHeader[6 + 3 + 255 + 4 + 4]; + int pesHeaderLen; + uchar pesHeaderBackup[6 + 3 + 255]; + int pesHeaderBackupLen; + uchar chk1; + uchar chk2; + int ac3todo; + enum eState { + find_0b, + find_77, + store_chk1, + store_chk2, + get_length, + output_packet + }; + int state; + int skippedBytes; + void ResetPesHeader(bool ContinuationFrame = false); + void AppendSubStreamID(bool ContinuationFrame = false); + bool FinishRemainder(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite); + bool StartNewPacket(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite); +public: + cDolbyRepacker(void); + virtual void Reset(void); + virtual void Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count); + virtual int BreakAt(const uchar *Data, int Count); + static int GetFrameDuration(const uchar *Data, int Count, int *TrackIndex = NULL); + }; + +// frameSizes are in words, i. e. multiply them by 2 to get bytes +int cDolbyRepacker::frameSizes[] = { + // fs = 48 kHz + 64, 64, 80, 80, 96, 96, 112, 112, 128, 128, 160, 160, 192, 192, 224, 224, + 256, 256, 320, 320, 384, 384, 448, 448, 512, 512, 640, 640, 768, 768, 896, 896, + 1024, 1024, 1152, 1152, 1280, 1280, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // fs = 44.1 kHz + 69, 70, 87, 88, 104, 105, 121, 122, 139, 140, 174, 175, 208, 209, 243, 244, + 278, 279, 348, 349, 417, 418, 487, 488, 557, 558, 696, 697, 835, 836, 975, 976, + 1114, 1115, 1253, 1254, 1393, 1394, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // fs = 32 kHz + 96, 96, 120, 120, 144, 144, 168, 168, 192, 192, 240, 240, 288, 288, 336, 336, + 384, 384, 480, 480, 576, 576, 672, 672, 768, 768, 960, 960, 1152, 1152, 1344, 1344, + 1536, 1536, 1728, 1728, 1920, 1920, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + // + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + }; + +int cDolbyRepacker::GetFrameDuration(const uchar *Data, int Count, int *TrackIndex) +{ + int PesPayloadOffset = 0; + ePesHeader PH = AnalyzePesHeader(Data, Count, PesPayloadOffset); + if (PH < phMPEG1) + return -1; + + const uchar *Payload = Data + PesPayloadOffset; + const int PayloadCount = Count - PesPayloadOffset; + + if (Data[3] == 0xBD && PayloadCount >= 9 && ((Payload[0] & 0xF0) == 0x80) && Payload[4] == 0x0B && Payload[5] == 0x77 && frameSizes[Payload[8]] > 0) { + if (TrackIndex) + *TrackIndex = Payload[0] - 0x80; + + static int samplingFrequencies[4] = { // all values are specified in Hz + 48000, 44100, 32000, -1 + }; + + return 90000 * 1536 / samplingFrequencies[Payload[8] >> 6]; + } + + return -1; +} + +cDolbyRepacker::cDolbyRepacker(void) +{ + pesHeader[0] = 0x00; + pesHeader[1] = 0x00; + pesHeader[2] = 0x01; + pesHeader[3] = 0xBD; + pesHeader[4] = 0x00; + pesHeader[5] = 0x00; + Reset(); +} + +void cDolbyRepacker::AppendSubStreamID(bool ContinuationFrame) +{ + if (subStreamId) { + pesHeader[pesHeaderLen++] = subStreamId; + // number of ac3 frames "starting" in this packet (1 by design). + pesHeader[pesHeaderLen++] = 0x01; + // offset to start of first ac3 frame (0 means "no ac3 frame starting" + // so 1 (by design) addresses the first byte after the next two bytes). + pesHeader[pesHeaderLen++] = 0x00; + pesHeader[pesHeaderLen++] = (ContinuationFrame ? 0x00 : 0x01); + } +} + +void cDolbyRepacker::ResetPesHeader(bool ContinuationFrame) +{ + pesHeader[6] = 0x80; + pesHeader[7] = 0x00; + pesHeader[8] = 0x00; + pesHeaderLen = 9; + AppendSubStreamID(ContinuationFrame); +} + +void cDolbyRepacker::Reset(void) +{ + cRepacker::Reset(); + ResetPesHeader(); + state = find_0b; + ac3todo = 0; + chk1 = 0; + chk2 = 0; + fragmentLen = 0; + fragmentTodo = 0; + pesHeaderBackupLen = 0; + skippedBytes = 0; +} + +bool cDolbyRepacker::FinishRemainder(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite) +{ + bool success = true; + // enough data available to put PES packet into buffer? + if (fragmentTodo <= Todo) { + // output a previous fragment first + if (fragmentLen > 0) { + Bite = fragmentLen; + int n = Put(ResultBuffer, fragmentData, Bite, fragmentLen + fragmentTodo); + if (Bite != n) + success = false; + fragmentLen = 0; + } + Bite = fragmentTodo; + if (success) { + int n = Put(ResultBuffer, Data, Bite, Bite); + if (Bite != n) + success = false; + } + fragmentTodo = 0; + // ac3 frame completely processed? + if (Bite >= ac3todo) + state = find_0b; // go on with finding start of next packet + } + else { + // copy the fragment into separate buffer for later processing + Bite = Todo; + memcpy(fragmentData + fragmentLen, Data, Bite); + fragmentLen += Bite; + fragmentTodo -= Bite; + } + return success; +} + +bool cDolbyRepacker::StartNewPacket(cRingBufferLinear *ResultBuffer, const uchar *const Data, const int Todo, int &Bite) +{ + bool success = true; + int packetLen = pesHeaderLen + ac3todo; + // limit packet to maximum size + if (packetLen > maxPacketSize) + packetLen = maxPacketSize; + pesHeader[4] = (packetLen - 6) >> 8; + pesHeader[5] = (packetLen - 6) & 0xFF; + Bite = pesHeaderLen; + // enough data available to put PES packet into buffer? + if (packetLen - pesHeaderLen <= Todo) { + int n = Put(ResultBuffer, pesHeader, Bite, packetLen); + if (Bite != n) + success = false; + Bite = packetLen - pesHeaderLen; + if (success) { + n = Put(ResultBuffer, Data, Bite, Bite); + if (Bite != n) + success = false; + } + // ac3 frame completely processed? + if (Bite >= ac3todo) + state = find_0b; // go on with finding start of next packet + } + else { + fragmentTodo = packetLen; + // copy the pesheader into separate buffer for later processing + memcpy(fragmentData + fragmentLen, pesHeader, Bite); + fragmentLen += Bite; + fragmentTodo -= Bite; + // copy the fragment into separate buffer for later processing + Bite = Todo; + memcpy(fragmentData + fragmentLen, Data, Bite); + fragmentLen += Bite; + fragmentTodo -= Bite; + } + return success; +} + +void cDolbyRepacker::Repack(cRingBufferLinear *ResultBuffer, const uchar *Data, int Count) +{ + // synchronisation is detected some bytes after frame start. + const int SkippedBytesLimit = 4; + + // check for MPEG 2 + if ((Data[6] & 0xC0) != 0x80) { + DroppedData("cDolbyRepacker: MPEG 2 PES header expected", Count); + return; + } + + // backup PES header + if (Data[6] != 0x80 || Data[7] != 0x00 || Data[8] != 0x00) { + pesHeaderBackupLen = 6 + 3 + Data[8]; + memcpy(pesHeaderBackup, Data, pesHeaderBackupLen); + } + + // skip PES header + int done = 6 + 3 + Data[8]; + int todo = Count - done; + const uchar *data = Data + done; + + // look for 0x0B 0x77 <chk1> <chk2> <frameSize> + while (todo > 0) { + switch (state) { + case find_0b: + if (*data == 0x0B) { + state++; + // copy header information once for later use + if (pesHeaderBackupLen > 0) { + pesHeaderLen = pesHeaderBackupLen; + pesHeaderBackupLen = 0; + memcpy(pesHeader, pesHeaderBackup, pesHeaderLen); + AppendSubStreamID(); + } + } + data++; + done++; + todo--; + skippedBytes++; // collect number of skipped bytes while syncing + continue; + case find_77: + if (*data != 0x77) { + state = find_0b; + continue; + } + data++; + done++; + todo--; + skippedBytes++; // collect number of skipped bytes while syncing + state++; + continue; + case store_chk1: + chk1 = *data++; + done++; + todo--; + skippedBytes++; // collect number of skipped bytes while syncing + state++; + continue; + case store_chk2: + chk2 = *data++; + done++; + todo--; + skippedBytes++; // collect number of skipped bytes while syncing + state++; + continue; + case get_length: + ac3todo = 2 * frameSizes[*data]; + // frameSizeCode was invalid => restart searching + if (ac3todo <= 0) { + // reset PES header instead of using a wrong one + ResetPesHeader(); + if (chk1 == 0x0B) { + if (chk2 == 0x77) { + state = store_chk1; + continue; + } + if (chk2 == 0x0B) { + state = find_77; + continue; + } + state = find_0b; + continue; + } + if (chk2 == 0x0B) { + state = find_77; + continue; + } + state = find_0b; + continue; + } + if (initiallySyncing) // omit report for the typical initial case + initiallySyncing = false; + else if (skippedBytes > SkippedBytesLimit) // report that syncing dropped some bytes +{ +//{ char cmd[500]; sprintf(cmd, "ddd /soft/vdr-" APIVERSION "/bin/vdr %d", getpid()); system(cmd); sleep(10); } //should never happen! + + LOG("cDolbyRepacker: skipped %d bytes to sync on next AC3 frame", skippedBytes - SkippedBytesLimit); +} + skippedBytes = 0; + // append read data to header for common output processing + pesHeader[pesHeaderLen++] = 0x0B; + pesHeader[pesHeaderLen++] = 0x77; + pesHeader[pesHeaderLen++] = chk1; + pesHeader[pesHeaderLen++] = chk2; + ac3todo -= 4; + state++; + // fall through to output + case output_packet: { + int bite = 0; + // finish remainder of ac3 frame? + if (fragmentTodo > 0) + FinishRemainder(ResultBuffer, data, todo, bite); + else { + // start a new packet + StartNewPacket(ResultBuffer, data, todo, bite); + // prepare for next (continuation) packet + ResetPesHeader(state == output_packet); + } + data += bite; + done += bite; + todo -= bite; + ac3todo -= bite; + } + } + } + // report that syncing dropped some bytes + if (skippedBytes > SkippedBytesLimit) { + if (!initiallySyncing) // omit report for the typical initial case + LOG("cDolbyRepacker: skipped %d bytes while syncing on next AC3 frame", skippedBytes - 4); + skippedBytes = SkippedBytesLimit; + } +} + +int cDolbyRepacker::BreakAt(const uchar *Data, int Count) +{ + if (initiallySyncing) + return -1; // fill the packet buffer completely until we have synced once + // enough data for test? + if (Count < 6 + 3) + return -1; + // check for MPEG 2 + if ((Data[6] & 0xC0) != 0x80) + return -1; + int headerLen = Data[8] + 6 + 3; + // break after fragment tail? + if (ac3todo > 0) + return headerLen + ac3todo; + // enough data for test? + if (Count < headerLen + 5) + return -1; + const uchar *data = Data + headerLen; + // break after ac3 frame? + if (data[0] == 0x0B && data[1] == 0x77 && frameSizes[data[4]] > 0) + return headerLen + 2 * frameSizes[data[4]]; + return -1; +} + +// --- cTS2PES --------------------------------------------------------------- + +#include <netinet/in.h> + +//XXX TODO: these should really be available in some driver header file! +#define PROG_STREAM_MAP 0xBC +#ifndef PRIVATE_STREAM1 +#define PRIVATE_STREAM1 0xBD +#endif +#define PADDING_STREAM 0xBE +#ifndef PRIVATE_STREAM2 +#define PRIVATE_STREAM2 0xBF +#endif +#define AUDIO_STREAM_S 0xC0 +#define AUDIO_STREAM_E 0xDF +#define VIDEO_STREAM_S 0xE0 +#define VIDEO_STREAM_E 0xEF +#define ECM_STREAM 0xF0 +#define EMM_STREAM 0xF1 +#define DSM_CC_STREAM 0xF2 +#define ISO13522_STREAM 0xF3 +#define PROG_STREAM_DIR 0xFF + +//pts_dts flags +#define PTS_ONLY 0x80 + +#define PID_MASK_HI 0x1F +#define CONT_CNT_MASK 0x0F + +// Flags: +#define PAY_LOAD 0x10 +#define ADAPT_FIELD 0x20 +#define PAY_START 0x40 +#define TS_ERROR 0x80 + +#define MAX_PLENGTH 0xFFFF // the maximum PES packet length (theoretically) +#define MMAX_PLENGTH (64*MAX_PLENGTH) // some stations send PES packets that are extremely large, e.g. DVB-T in Finland or HDTV 1920x1080 + +#define IPACKS 2048 + +// Start codes: +#define SC_SEQUENCE 0xB3 // "sequence header code" +#define SC_GROUP 0xB8 // "group start code" +#define SC_PICTURE 0x00 // "picture start code" + +#define MAXNONUSEFULDATA (10*1024*1024) +#define MAXNUMUPTERRORS 10 + +class cTS2PES { +private: + int pid; + int size; + int found; + int count; + uint8_t *buf; + uint8_t cid; + uint8_t rewriteCid; + uint8_t subStreamId; + int plength; + uint8_t plen[2]; + uint8_t flag1; + uint8_t flag2; + uint8_t hlength; + int mpeg; + uint8_t check; + int mpeg1_required; + int mpeg1_stuffing; + bool done; + cRingBufferLinear *resultBuffer; + int tsErrors; + int ccErrors; + int ccCounter; + cRepacker *repacker; + static uint8_t headr[]; + void store(uint8_t *Data, int Count); + void reset_ipack(void); + void send_ipack(void); + void write_ipack(const uint8_t *Data, int Count); + void instant_repack(const uint8_t *Buf, int Count); +public: + cTS2PES(int Pid, cRingBufferLinear *ResultBuffer, int Size, uint8_t RewriteCid = 0x00, uint8_t SubStreamId = 0x00, cRepacker *Repacker = NULL); + ~cTS2PES(); + int Pid(void) { return pid; } + void ts_to_pes(const uint8_t *Buf); // don't need count (=188) + void Clear(void); + }; + +uint8_t cTS2PES::headr[] = { 0x00, 0x00, 0x01 }; + +cTS2PES::cTS2PES(int Pid, cRingBufferLinear *ResultBuffer, int Size, uint8_t RewriteCid, uint8_t SubStreamId, cRepacker *Repacker) +{ + pid = Pid; + resultBuffer = ResultBuffer; + size = Size; + rewriteCid = RewriteCid; + subStreamId = SubStreamId; + repacker = Repacker; + if (repacker) { + repacker->SetMaxPacketSize(size); + repacker->SetSubStreamId(subStreamId); + size += repacker->QuerySnoopSize(); + } + + tsErrors = 0; + ccErrors = 0; + ccCounter = -1; + + if (!(buf = MALLOC(uint8_t, size))) + esyslog("Not enough memory for ts_transform"); + + reset_ipack(); +} + +cTS2PES::~cTS2PES() +{ + if (tsErrors || ccErrors) + dsyslog("cTS2PES got %d TS errors, %d TS continuity errors", tsErrors, ccErrors); + free(buf); + delete repacker; +} + +void cTS2PES::Clear(void) +{ + reset_ipack(); + if (repacker) + repacker->Reset(); +} + +void cTS2PES::store(uint8_t *Data, int Count) +{ + if (repacker) + repacker->Repack(resultBuffer, Data, Count); + else + cRepacker::PutAllOrNothing(resultBuffer, Data, Count, Count); +} + +void cTS2PES::reset_ipack(void) +{ + found = 0; + cid = 0; + plength = 0; + flag1 = 0; + flag2 = 0; + hlength = 0; + mpeg = 0; + check = 0; + mpeg1_required = 0; + mpeg1_stuffing = 0; + done = false; + count = 0; +} + +void cTS2PES::send_ipack(void) +{ + if (count <= ((mpeg == 2) ? 9 : 7)) // skip empty packets + return; + buf[3] = rewriteCid ? rewriteCid : cid; + buf[4] = (uint8_t)(((count - 6) & 0xFF00) >> 8); + buf[5] = (uint8_t)((count - 6) & 0x00FF); + store(buf, count); + + switch (mpeg) { + case 2: + buf[6] = 0x80; + buf[7] = 0x00; + buf[8] = 0x00; + count = 9; + if (!repacker && subStreamId) { + buf[9] = subStreamId; + buf[10] = 1; + buf[11] = 0; + buf[12] = 1; + count = 13; + } + break; + case 1: + buf[6] = 0x0F; + count = 7; + break; + } +} + +void cTS2PES::write_ipack(const uint8_t *Data, int Count) +{ + if (count < 6) { + memcpy(buf, headr, 3); + count = 6; + } + + // determine amount of data to process + int bite = Count; + if (count + bite > size) + bite = size - count; + if (repacker) { + int breakAt = repacker->BreakAt(buf, count); + // avoid memcpy of data after break location + if (0 <= breakAt && breakAt < count + bite) { + bite = breakAt - count; + if (bite < 0) // should never happen + bite = 0; + } + } + + memcpy(buf + count, Data, bite); + count += bite; + + if (repacker) { + // determine break location + int breakAt = repacker->BreakAt(buf, count); + if (breakAt > size) // won't fit into packet? + breakAt = -1; + if (breakAt > count) // not enough data? + breakAt = -1; + // push out data before break location + if (breakAt > 0) { + // adjust bite if above memcpy was to large + bite -= count - breakAt; + count = breakAt; + send_ipack(); + // recurse for data after break location + if (Count - bite > 0) + write_ipack(Data + bite, Count - bite); + } + } + + // push out data when buffer is full + if (count >= size) { + send_ipack(); + // recurse for remaining data + if (Count - bite > 0) + write_ipack(Data + bite, Count - bite); + } +} + +void cTS2PES::instant_repack(const uint8_t *Buf, int Count) +{ + int c = 0; + + while (c < Count && (mpeg == 0 || (mpeg == 1 && found < mpeg1_required) || (mpeg == 2 && found < 9)) && (found < 5 || !done)) { + switch (found ) { + case 0: + case 1: + if (Buf[c] == 0x00) + found++; + else + found = 0; + c++; + break; + case 2: + if (Buf[c] == 0x01) + found++; + else if (Buf[c] != 0) + found = 0; + c++; + break; + case 3: + cid = 0; + switch (Buf[c]) { + case PROG_STREAM_MAP: + case PRIVATE_STREAM2: + case PROG_STREAM_DIR: + case ECM_STREAM : + case EMM_STREAM : + case PADDING_STREAM : + case DSM_CC_STREAM : + case ISO13522_STREAM: + done = true; + case PRIVATE_STREAM1: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + found++; + cid = Buf[c++]; + break; + default: + found = 0; + break; + } + break; + case 4: + if (Count - c > 1) { + unsigned short *pl = (unsigned short *)(Buf + c); + plength = ntohs(*pl); + c += 2; + found += 2; + mpeg1_stuffing = 0; + } + else { + plen[0] = Buf[c]; + found++; + return; + } + break; + case 5: { + plen[1] = Buf[c++]; + unsigned short *pl = (unsigned short *)plen; + plength = ntohs(*pl); + found++; + mpeg1_stuffing = 0; + } + break; + case 6: + if (!done) { + flag1 = Buf[c++]; + found++; + if (mpeg1_stuffing == 0) { // first stuffing iteration: determine MPEG level + if ((flag1 & 0xC0) == 0x80) + mpeg = 2; + else { + mpeg = 1; + mpeg1_required = 7; + } + } + if (mpeg == 1) { + if (flag1 == 0xFF) { // MPEG1 stuffing + if (++mpeg1_stuffing > 16) + found = 0; // invalid MPEG1 header + else { // ignore stuffing + found--; + if (plength > 0) + plength--; + } + } + else if ((flag1 & 0xC0) == 0x40) // STD_buffer_scale/size + mpeg1_required += 2; + else if (flag1 != 0x0F && (flag1 & 0xF0) != 0x20 && (flag1 & 0xF0) != 0x30) + found = 0; // invalid MPEG1 header + else { + flag2 = 0; + hlength = 0; + } + } + } + break; + case 7: + if (!done && (mpeg == 2 || mpeg1_required > 7)) { + flag2 = Buf[c++]; + found++; + } + break; + case 8: + if (!done && (mpeg == 2 || mpeg1_required > 7)) { + hlength = Buf[c++]; + found++; + if (mpeg == 1 && hlength != 0x0F && (hlength & 0xF0) != 0x20 && (hlength & 0xF0) != 0x30) + found = 0; // invalid MPEG1 header + } + break; + default: + break; + } + } + + if (!plength) + plength = MMAX_PLENGTH - 6; + + if (done || ((mpeg == 2 && found >= 9) || (mpeg == 1 && found >= mpeg1_required))) { + switch (cid) { + case AUDIO_STREAM_S ... AUDIO_STREAM_E: + case VIDEO_STREAM_S ... VIDEO_STREAM_E: + case PRIVATE_STREAM1: + + if (mpeg == 2 && found == 9 && count < found) { // make sure to not write the data twice by looking at count + write_ipack(&flag1, 1); + write_ipack(&flag2, 1); + write_ipack(&hlength, 1); + } + + if (mpeg == 1 && found == mpeg1_required && count < found) { // make sure to not write the data twice by looking at count + write_ipack(&flag1, 1); + if (mpeg1_required > 7) { + write_ipack(&flag2, 1); + write_ipack(&hlength, 1); + } + } + + if (mpeg == 2 && (flag2 & PTS_ONLY) && found < 14) { + while (c < Count && found < 14) { + write_ipack(Buf + c, 1); + c++; + found++; + } + if (c == Count) + return; + } + + if (!repacker && subStreamId) { + while (c < Count && found < (hlength + 9) && found < plength + 6) { + write_ipack(Buf + c, 1); + c++; + found++; + } + if (found == (hlength + 9)) { + uchar sbuf[] = { 0x01, 0x00, 0x00 }; + write_ipack(&subStreamId, 1); + write_ipack(sbuf, 3); + } + } + + while (c < Count && found < plength + 6) { + int l = Count - c; + if (l + found > plength + 6) + l = plength + 6 - found; + write_ipack(Buf + c, l); + found += l; + c += l; + } + + break; + } + + if (done) { + if (found + Count - c < plength + 6) { + found += Count - c; + c = Count; + } + else { + c += plength + 6 - found; + found = plength + 6; + } + } + + if (plength && found == plength + 6) { + if (plength == MMAX_PLENGTH - 6) + esyslog("ERROR: PES packet length overflow in remuxer (stream corruption)"); + send_ipack(); + reset_ipack(); + if (c < Count) + instant_repack(Buf + c, Count - c); + } + } + return; +} + +void cTS2PES::ts_to_pes(const uint8_t *Buf) // don't need count (=188) +{ + if (!Buf) + return; + + if (Buf[1] & TS_ERROR) + tsErrors++; + + if (!(Buf[3] & (ADAPT_FIELD | PAY_LOAD))) + return; // discard TS packet with adaption_field_control set to '00'. + + if ((Buf[3] & PAY_LOAD) && ((Buf[3] ^ ccCounter) & CONT_CNT_MASK)) { + // This should check duplicates and packets which do not increase the counter. + // But as the errors usually come in bursts this should be enough to + // show you there is something wrong with signal quality. + if (ccCounter != -1 && ((Buf[3] ^ (ccCounter + 1)) & CONT_CNT_MASK)) { + ccErrors++; + // Enable this if you are having problems with signal quality. + // These are the errors I used to get with Nova-T when antenna + // was not positioned correcly (not transport errors). //tvr + dsyslog("TS continuity error (%d)", ccCounter); + } + ccCounter = Buf[3] & CONT_CNT_MASK; + } + + if (Buf[1] & PAY_START) { + if (found > 6) { + if (plength != MMAX_PLENGTH - 6 && plength != found - 6) + dsyslog("PES packet shortened to %d bytes (expected: %d bytes)", found, plength + 6); + plength = found - 6; + send_ipack(); + } + reset_ipack(); + } + + uint8_t off = 0; + + if (Buf[3] & ADAPT_FIELD) { // adaptation field? + off = Buf[4] + 1; + if (off + 4 > 187) + return; + } + + if (Buf[3] & PAY_LOAD) + instant_repack(Buf + 4 + off, TS_SIZE - 4 - off); +} + +// --- cAudioIndexer --------------------------------------------------------- + +class cAudioIndexer { +private: + int frameTrack; + int frameDuration; + int64_t trackTime[MAXAPIDS + MAXDPIDS]; + int64_t nextIndexTime; + +public: + cAudioIndexer(void); + void Clear(void); + void PrepareFrame(const uchar *Data, int Count, int Offset, uchar &PictureType); + void ProcessFrame(void); + }; + +cAudioIndexer::cAudioIndexer(void) +{ + Clear(); +} + +void cAudioIndexer::Clear(void) +{ + memset(trackTime, 0, sizeof (trackTime)); + nextIndexTime = 0; + frameTrack = -1; +} + +void cAudioIndexer::PrepareFrame(const uchar *Data, int Count, int Offset, uchar &PictureType) +{ + frameDuration = cRemux::GetAudioFrameDuration(Data + Offset, Count - Offset, &frameTrack); + if (frameDuration <= 0) + return; + + if (Data[Offset + 3] == 0xBD) + frameTrack += MAXAPIDS; + + PictureType = (trackTime[frameTrack] >= nextIndexTime) ? I_FRAME : NO_PICTURE; +} + +void cAudioIndexer::ProcessFrame(void) +{ + if (frameTrack < 0) + return; + + if (trackTime[frameTrack] >= nextIndexTime) + nextIndexTime += 90000 / FRAMESPERSEC; + + trackTime[frameTrack] += frameDuration; + frameTrack = -1; +} + +// --- cRingBufferLinearPes -------------------------------------------------- + +class cRingBufferLinearPes : public cRingBufferLinear { +protected: + virtual int DataReady(const uchar *Data, int Count); +public: + cRingBufferLinearPes(int Size, int Margin = 0, bool Statistics = false, const char *Description = NULL) + :cRingBufferLinear(Size, Margin, Statistics, Description) {} + }; + +int cRingBufferLinearPes::DataReady(const uchar *Data, int Count) +{ + int c = cRingBufferLinear::DataReady(Data, Count); + if (!c && Count >= 6) { + if (!Data[0] && !Data[1] && Data[2] == 0x01) { + int Length = 6 + Data[4] * 256 + Data[5]; + if (Length <= Count) + return Length; + } + } + return c; +} + +// --- cRemux ---------------------------------------------------------------- + +#define RESULTBUFFERSIZE KILOBYTE(256) + +cRemux::cRemux(int VPid, const int *APids, const int *DPids, const int *SPids, bool ExitOnFailure, bool SyncEarly) +{ + h264 = false; + exitOnFailure = ExitOnFailure; + noVideo = VPid == 0 || VPid == 1 || VPid == 0x1FFF; + numUPTerrors = 0; + synced = false; + syncEarly = SyncEarly; + skipped = 0; + numTracks = 0; + resultSkipped = 0; + resultBuffer = new cRingBufferLinearPes(RESULTBUFFERSIZE, IPACKS, false, "Result"); + resultBuffer->SetTimeouts(0, 100); + if (VPid) +#define TEST_cVideoRepacker +#ifdef TEST_cVideoRepacker + ts2pes[numTracks++] = new cTS2PES(VPid, resultBuffer, IPACKS, 0xE0, 0x00, new cVideoRepacker(h264)); +#else + ts2pes[numTracks++] = new cTS2PES(VPid, resultBuffer, IPACKS, 0xE0); +#endif + if (APids) { + int n = 0; + while (*APids && numTracks < MAXTRACKS && n < MAXAPIDS) { +#define TEST_cAudioRepacker +#ifdef TEST_cAudioRepacker + ts2pes[numTracks++] = new cTS2PES(*APids++, resultBuffer, IPACKS, 0xC0 + n, 0x00, new cAudioRepacker(0xC0 + n)); + n++; +#else + ts2pes[numTracks++] = new cTS2PES(*APids++, resultBuffer, IPACKS, 0xC0 + n++); +#endif + } + } + if (DPids) { + int n = 0; + while (*DPids && numTracks < MAXTRACKS && n < MAXDPIDS) + ts2pes[numTracks++] = new cTS2PES(*DPids++, resultBuffer, IPACKS, 0x00, 0x80 + n++, new cDolbyRepacker); + } + if (SPids) { + int n = 0; + while (*SPids && numTracks < MAXTRACKS && n < MAXSPIDS) + ts2pes[numTracks++] = new cTS2PES(*SPids++, resultBuffer, IPACKS, 0x00, 0x20 + n++); + } + audioIndexer = (noVideo ? new cAudioIndexer : NULL); +} + +cRemux::~cRemux() +{ + for (int t = 0; t < numTracks; t++) + delete ts2pes[t]; + delete resultBuffer; + delete audioIndexer; +} + +int cRemux::GetAudioFrameDuration(const uchar *Data, int Count, int *TrackIndex) +{ + if (Count <= 4) + return -1; + + if (Data[3] == 0xBD) + return cDolbyRepacker::GetFrameDuration(Data, Count, TrackIndex); + + return cAudioRepacker::GetFrameDuration(Data, Count, TrackIndex); +} + +int cRemux::GetPid(const uchar *Data) +{ + return (((uint16_t)Data[0] & PID_MASK_HI) << 8) | (Data[1] & 0xFF); +} + +int cRemux::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; +} + +bool cRemux::IsFrameH264(const uchar *Data, int Length) +{ + int PesPayloadOffset; + const uchar *limit = Data + Length; + if (AnalyzePesHeader(Data, Length, PesPayloadOffset) <= phInvalid) + return false; // neither MPEG1 nor MPEG2 + + Data += PesPayloadOffset + 3; // move to video payload and skip 00 00 01 + if (Data < limit) { + // cVideoRepacker ensures that in case of H264 we will see an access unit delimiter here + if (0x01 == Data[-1] && 9 == Data[0] && 0x00 == Data[-2] && 0x00 == Data[-3]) + return true; + } + + return false; +} + +int cRemux::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) { +#ifdef TEST_cVideoRepacker + bool FoundPicture = false; + bool FoundPictureCodingExtension = false; +#endif + int PesPayloadOffset = 0; + if (AnalyzePesHeader(Data + Offset, Length, PesPayloadOffset) >= phMPEG1) { + const uchar *p = Data + Offset + PesPayloadOffset + 2; + const uchar *pLimit = Data + Offset + Length - 3; +#ifdef TEST_cVideoRepacker + // cVideoRepacker ensures that a new PES packet is started for a new sequence, + // group or picture which allows us to easily skip scanning through a huge + // amount of video data. + if (p < pLimit) { + if (p[-2] || p[-1] || p[0] != 0x01) + pLimit = 0; // skip scanning: packet doesn't start with 0x000001 + else { + if (h264) { + int nal_unit_type = p[1] & 0x1F; + switch (nal_unit_type) { + case 9: // access unit delimiter + // when the MSB in p[1] is set (which violates H.264) then this is a hint + // from cVideoRepacker that this second field of the current frame shall + // not be reported as picture. + if (p[1] & 0x80) + ((uchar *)p)[1] &= ~0x80; // revert the hint and fall through + else + break; + default: // skip scanning: packet doesn't start a new picture + pLimit = 0; + } + } + else { + switch (p[1]) { + case SC_SEQUENCE: + case SC_GROUP: + case SC_PICTURE: + break; + default: // skip scanning: packet doesn't start a new sequence, group or picture + pLimit = 0; + } + } + } + } +#endif + while (p < pLimit && (p = (const uchar *)memchr(p, 0x01, pLimit - p))) { + if (!p[-2] && !p[-1]) { // found 0x000001 + if (h264) { + int nal_unit_type = p[1] & 0x1F; + switch (nal_unit_type) { + case 9: { // access unit delimiter + int primary_pic_type = p[2] >> 5; + switch (primary_pic_type) { + case 0: // I + case 3: // SI + case 5: // I, SI + PictureType = I_FRAME; + break; + case 1: // I, P + case 4: // SI, SP + case 6: // I, SI, P, SP + PictureType = P_FRAME; + break; + case 2: // I, P, B + case 7: // I, SI, P, SP, B + PictureType = B_FRAME; + break; + } + return Length; + } + } + } + else { + switch (p[1]) { + case SC_PICTURE: PictureType = (p[3] >> 3) & 0x07; +#ifndef TEST_cVideoRepacker + return Length; +#else + FoundPicture = true; + break; + case 0x01 ... 0xAF: // slice startcodes + break; + case 0xB5: // extension startcode + case 0xB9: // hint from cVideoRepacker + if (FoundPicture && p + 2 < pLimit && (p[2] & 0xF0) == 0x80) { // picture coding extension + FoundPictureCodingExtension = true; + // using 0xB9 instead of 0xB5 for an expected picture coding extension + // is a hint from cVideoRepacker that this second field of the current + // frame shall not be reported as picture. + if (p[1] == 0xB9) { + ((uchar *)p)[1] = 0xB5; // revert the hint + pLimit = 0; // return with NO_PICTURE below + } + else + return Length; + } + break; +#endif + } + } + p += 4; // continue scanning after 0x01ssxxyy + } + else + p += 3; // continue scanning after 0x01xxyy + } + } +#ifdef TEST_cVideoRepacker + if (!FoundPicture || FoundPictureCodingExtension) +#endif + PictureType = NO_PICTURE; + return Length; + } + return -1; +} + +int cRemux::Put(const uchar *Data, int Count) +{ + int used = 0; + + // Make sure we are looking at a TS packet: + + while (Count > TS_SIZE) { + if (Data[0] == TS_SYNC_BYTE && Data[TS_SIZE] == TS_SYNC_BYTE) + break; + Data++; + Count--; + used++; + } + if (used) + esyslog("ERROR: skipped %d byte to sync on TS packet", used); + + // Convert incoming TS data into multiplexed PES: + + for (int i = 0; i < Count; i += TS_SIZE) { + if (Count - i < TS_SIZE) + break; + if (Data[i] != TS_SYNC_BYTE) + break; + if (resultBuffer->Free() < 2 * IPACKS) + break; // A cTS2PES 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 < numTracks; t++) { + if (ts2pes[t]->Pid() == pid) { + ts2pes[t]->ts_to_pes(Data + i); + break; + } + } + } + used += TS_SIZE; + } + + // Check if we're getting anywhere here: + if (!synced && skipped >= 0) { + if (skipped > MAXNONUSEFULDATA) { + esyslog("ERROR: no useful data seen within %d byte of video stream", skipped); + skipped = -1; + if (exitOnFailure) + ShutdownHandler.RequestEmergencyExit(); + } + else + skipped += used; + } + + return used; +} + +uchar *cRemux::Get(int &Count, uchar *PictureType) +{ + // Remove any previously skipped data from the result buffer: + + if (resultSkipped > 0) { + resultBuffer->Del(resultSkipped); + resultSkipped = 0; + } + +#if 0 + // Test recording without determining the real frame borders: + if (PictureType) + *PictureType = I_FRAME; + return resultBuffer->Get(Count); +#endif + + // Check for frame borders: + + if (PictureType) + *PictureType = NO_PICTURE; + + Count = 0; + uchar *resultData = NULL; + int resultCount = 0; + uchar *data = resultBuffer->Get(resultCount); + if (data) { + for (int i = 0; i < resultCount - 3; i++) { + if (data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1) { + int l = 0; + uchar StreamType = data[i + 3]; + if (VIDEO_STREAM_S <= StreamType && StreamType <= VIDEO_STREAM_E) { + uchar pt = NO_PICTURE; + l = 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); + if (++numUPTerrors > MAXNUMUPTERRORS && exitOnFailure) { + ShutdownHandler.RequestEmergencyExit(); + numUPTerrors = 0; + } + } + else if (!synced) { + if (pt == I_FRAME || syncEarly) { + if (PictureType) + *PictureType = pt; + resultSkipped = i; // will drop everything before this position + synced = true; + if (pt == I_FRAME) // syncEarly: it's ok but there is no need to call SetBrokenLink() + SetBrokenLink(data + i, l); +else fprintf(stderr, "video: synced early\n"); + } + } + else if (Count) + return resultData; + else if (PictureType) + *PictureType = pt; + } + } + else { //if (AUDIO_STREAM_S <= StreamType && StreamType <= AUDIO_STREAM_E || StreamType == PRIVATE_STREAM1) { + l = GetPacketLength(data, resultCount, i); + if (l < 0) + return resultData; + if (noVideo || (!synced && syncEarly)) { + uchar pt = NO_PICTURE; + if (audioIndexer && !Count) + audioIndexer->PrepareFrame(data, resultCount, i, pt); + if (!synced) { + if (PictureType && noVideo) + *PictureType = pt; + resultSkipped = i; // will drop everything before this position + synced = true; +if (!noVideo) fprintf(stderr, "audio: synced early\n"); + } + else if (Count) + return resultData; + else if (PictureType) + *PictureType = pt; + } + } + if (synced) { + if (!Count) + resultData = data + i; + Count += l; + } + else + resultSkipped = i + l; + if (l > 0) + i += l - 1; // the loop increments, too + } + } + } + return resultData; +} + +void cRemux::Del(int Count) +{ + resultBuffer->Del(Count); + if (audioIndexer && Count > 0) + audioIndexer->ProcessFrame(); +} + +void cRemux::Clear(void) +{ + for (int t = 0; t < numTracks; t++) + ts2pes[t]->Clear(); + resultBuffer->Clear(); + if (audioIndexer) + audioIndexer->Clear(); + synced = false; + skipped = 0; + resultSkipped = 0; +} + +void cRemux::SetBrokenLink(uchar *Data, int Length) +{ + int PesPayloadOffset = 0; + if (AnalyzePesHeader(Data, Length, PesPayloadOffset) >= phMPEG1 && (Data[3] & 0xF0) == VIDEO_STREAM_S) { + for (int i = PesPayloadOffset; i < Length - 7; i++) { + if (Data[i] == 0 && Data[i + 1] == 0 && Data[i + 2] == 1 && Data[i + 3] == 0xB8) { + if (!(Data[i + 7] & 0x40)) // set flag only if GOP is not closed + Data[i + 7] |= 0x20; + return; + } + } + dsyslog("SetBrokenLink: no GOP header found in video packet"); + } + else + dsyslog("SetBrokenLink: no video packet in frame"); +} + +#if 0 + +// --- cPatPmtGenerator ------------------------------------------------------ + +cPatPmtGenerator::cPatPmtGenerator(void) +{ + numPmtPackets = 0; + patCounter = pmtCounter = 0; + patVersion = pmtVersion = 0; + esInfoLength = NULL; + GeneratePat(); +} + +void cPatPmtGenerator::IncCounter(int &Counter, uchar *TsPacket) +{ + TsPacket[3] = (TsPacket[3] & 0xF0) | Counter; + if (++Counter > 0x0F) + Counter = 0x00; +} + +void cPatPmtGenerator::IncVersion(int &Version) +{ + if (++Version > 0x1F) + Version = 0x00; +} + +void cPatPmtGenerator::IncEsInfoLength(int Length) +{ + if (esInfoLength) { + Length += ((*esInfoLength & 0x0F) << 8) | *(esInfoLength + 1); + *esInfoLength = 0xF0 | (Length >> 8); + *(esInfoLength + 1) = Length; + } +} + +int cPatPmtGenerator::MakeStream(uchar *Target, uchar Type, int Pid) +{ + int i = 0; + Target[i++] = Type; // stream type + Target[i++] = 0xE0 | (Pid >> 8); // dummy (3), pid hi (5) + Target[i++] = Pid; // pid lo + esInfoLength = &Target[i]; + Target[i++] = 0xF0; // dummy (4), ES info length hi + Target[i++] = 0x00; // ES info length lo + return i; +} + +int cPatPmtGenerator::MakeAC3Descriptor(uchar *Target) +{ + int i = 0; + Target[i++] = SI::AC3DescriptorTag; + Target[i++] = 0x01; // length + Target[i++] = 0x00; + IncEsInfoLength(i); + return i; +} + +int cPatPmtGenerator::MakeSubtitlingDescriptor(uchar *Target, const char *Language) +{ + int i = 0; + Target[i++] = SI::SubtitlingDescriptorTag; + Target[i++] = 0x08; // length + Target[i++] = *Language++; + Target[i++] = *Language++; + Target[i++] = *Language++; + Target[i++] = 0x00; // subtitling type + Target[i++] = 0x00; // composition page id hi + Target[i++] = 0x01; // composition page id lo + Target[i++] = 0x00; // ancillary page id hi + Target[i++] = 0x01; // ancillary page id lo + IncEsInfoLength(i); + return i; +} + +int cPatPmtGenerator::MakeLanguageDescriptor(uchar *Target, const char *Language) +{ + int i = 0; + Target[i++] = SI::ISO639LanguageDescriptorTag; + Target[i++] = 0x04; // length + Target[i++] = *Language++; + Target[i++] = *Language++; + Target[i++] = *Language++; + Target[i++] = 0x01; // audio type + IncEsInfoLength(i); + return i; +} + +int cPatPmtGenerator::MakeCRC(uchar *Target, const uchar *Data, int Length) +{ + int crc = SI::CRC32::crc32((const char *)Data, Length, 0xFFFFFFFF); + int i = 0; + Target[i++] = crc >> 24; + Target[i++] = crc >> 16; + Target[i++] = crc >> 8; + Target[i++] = crc; + return i; +} + +#define P_TSID 0x8008 // pseudo TS ID +#define P_PNR 0x0084 // pseudo Program Number +#define P_PMT_PID 0x0084 // pseudo PMT pid + +void cPatPmtGenerator::GeneratePat(void) +{ + memset(pat, 0xFF, sizeof(pat)); + uchar *p = pat; + int i = 0; + p[i++] = 0x47; // TS indicator + p[i++] = 0x40; // flags (3), pid hi (5) + p[i++] = 0x00; // pid lo + p[i++] = 0x10; // flags (4), continuity counter (4) + int PayloadStart = i; + p[i++] = 0x00; // table id + p[i++] = 0xB0; // section syntax indicator (1), dummy (3), section length hi (4) + int SectionLength = i; + p[i++] = 0x00; // section length lo (filled in later) + p[i++] = P_TSID >> 8; // TS id hi + p[i++] = P_TSID & 0xFF; // TS id lo + p[i++] = 0xC1 | (patVersion << 1); // dummy (2), version number (5), current/next indicator (1) + p[i++] = 0x00; // section number + p[i++] = 0x00; // last section number + p[i++] = P_PNR >> 8; // program number hi + p[i++] = P_PNR & 0xFF; // program number lo + p[i++] = 0xE0 | (P_PMT_PID >> 8); // dummy (3), PMT pid hi (5) + p[i++] = P_PMT_PID & 0xFF; // PMT pid lo + pat[SectionLength] = i - SectionLength - 1 + 4; // -2 = SectionLength storage, +4 = length of CRC + MakeCRC(pat + i, pat + PayloadStart, i - PayloadStart); + IncVersion(patVersion); +} + +void cPatPmtGenerator::GeneratePmt(tChannelID ChannelID) +{ + // generate the complete PMT section: + uchar buf[MAX_SECTION_SIZE]; + memset(buf, 0xFF, sizeof(buf)); + numPmtPackets = 0; + cChannel *Channel = Channels.GetByChannelID(ChannelID); + if (Channel) { + int Vpid = Channel->Vpid(); + uchar *p = buf; + int i = 0; + p[i++] = 0x02; // table id + int SectionLength = i; + p[i++] = 0xB0; // section syntax indicator (1), dummy (3), section length hi (4) + p[i++] = 0x00; // section length lo (filled in later) + p[i++] = P_PNR >> 8; // program number hi + p[i++] = P_PNR & 0xFF; // program number lo + p[i++] = 0xC1 | (pmtVersion << 1); // dummy (2), version number (5), current/next indicator (1) + p[i++] = 0x00; // section number + p[i++] = 0x00; // last section number + p[i++] = 0xE0 | (Vpid >> 8); // dummy (3), PCR pid hi (5) + p[i++] = Vpid; // PCR pid lo + p[i++] = 0xF0; // dummy (4), program info length hi (4) + p[i++] = 0x00; // program info length lo + + if (Vpid) + i += MakeStream(buf + i, Channel->Vtype(), Vpid); + for (int n = 0; Channel->Apid(n); n++) { + i += MakeStream(buf + i, 0x04, Channel->Apid(n)); + const char *Alang = Channel->Alang(n); + i += MakeLanguageDescriptor(buf + i, Alang); + if (Alang[3] == '+') + i += MakeLanguageDescriptor(buf + i, Alang + 3); + } + for (int n = 0; Channel->Dpid(n); n++) { + i += MakeStream(buf + i, 0x06, Channel->Dpid(n)); + i += MakeAC3Descriptor(buf + i); + i += MakeLanguageDescriptor(buf + i, Channel->Dlang(n)); + } + for (int n = 0; Channel->Spid(n); n++) { + i += MakeStream(buf + i, 0x06, Channel->Spid(n)); + i += MakeSubtitlingDescriptor(buf + i, Channel->Slang(n)); + } + + int sl = i - SectionLength - 2 + 4; // -2 = SectionLength storage, +4 = length of CRC + buf[SectionLength] |= (sl >> 8) & 0x0F; + buf[SectionLength + 1] = sl; + MakeCRC(buf + i, buf, i); + // split the PMT section into several TS packets: + uchar *q = buf; + while (i > 0) { + uchar *p = pmt[numPmtPackets++]; + int j = 0; + p[j++] = 0x47; // TS indicator + p[j++] = 0x40 | (P_PNR >> 8); // flags (3), pid hi (5) + p[j++] = P_PNR & 0xFF; // pid lo + p[j++] = 0x10; // flags (4), continuity counter (4) + int l = TS_SIZE - j; + memcpy(p + j, q, l); + q += l; + i -= l; + } + IncVersion(pmtVersion); + } + else + esyslog("ERROR: can't find channel %s", *ChannelID.ToString()); +} + +uchar *cPatPmtGenerator::GetPat(void) +{ + IncCounter(patCounter, pat); + return pat; +} + +uchar *cPatPmtGenerator::GetPmt(int &Index) +{ + if (Index < numPmtPackets) { + IncCounter(patCounter, pmt[Index]); + return pmt[Index++]; + } + return NULL; +} + +// --- cPatPmtParser --------------------------------------------------------- + +cPatPmtParser::cPatPmtParser(void) +{ + pmtSize = 0; + pmtPid = -1; + vpid = vtype = 0; +} + +void cPatPmtParser::ParsePat(const uchar *Data, int Length) +{ + // The PAT is always assumed to fit into a single TS packet + SI::PAT Pat(Data, false); + if (Pat.CheckCRCAndParse()) { + dbgpatpmt("PAT: TSid = %d, c/n = %d, v = %d, s = %d, ls = %d\n", Pat.getTransportStreamId(), Pat.getCurrentNextIndicator(), Pat.getVersionNumber(), Pat.getSectionNumber(), Pat.getLastSectionNumber()); + SI::PAT::Association assoc; + for (SI::Loop::Iterator it; Pat.associationLoop.getNext(assoc, it); ) { + dbgpatpmt(" isNITPid = %d\n", assoc.isNITPid()); + if (!assoc.isNITPid()) { + pmtPid = assoc.getPid(); + dbgpatpmt(" service id = %d, pid = %d\n", assoc.getServiceId(), assoc.getPid()); + } + } + } + else + esyslog("ERROR: can't parse PAT"); +} + +void cPatPmtParser::ParsePmt(const uchar *Data, int Length) +{ + // The PMT may extend over several TS packets, so we need to assemble them + if (pmtSize == 0) { + // this is the first packet + if (SectionLength(Data, Length) > Length) { + if (Length <= int(sizeof(pmt))) { + memcpy(pmt, Data, Length); + pmtSize = Length; + } + else + esyslog("ERROR: PMT packet length too big (%d byte)!", Length); + return; + } + // the packet contains the entire PMT section, so we run into the actual parsing + } + else { + // this is a following packet, so we add it to the pmt storage + if (Length <= int(sizeof(pmt)) - pmtSize) { + memcpy(pmt + pmtSize, Data, Length); + pmtSize += Length; + } + else { + esyslog("ERROR: PMT section length too big (%d byte)!", pmtSize + Length); + pmtSize = 0; + } + if (SectionLength(pmt, pmtSize) > pmtSize) + return; // more packets to come + // the PMT section is now complete, so we run into the actual parsing + Data = pmt; + } + SI::PMT Pmt(Data, false); + if (Pmt.CheckCRCAndParse()) { + dbgpatpmt("PMT: sid = %d, c/n = %d, v = %d, s = %d, ls = %d\n", Pmt.getServiceId(), Pmt.getCurrentNextIndicator(), Pmt.getVersionNumber(), Pmt.getSectionNumber(), Pmt.getLastSectionNumber()); + dbgpatpmt(" pcr = %d\n", Pmt.getPCRPid()); + cDevice::PrimaryDevice()->ClrAvailableTracks(false, true); + int NumApids = 0; + int NumDpids = 0; + int NumSpids = 0; + vpid = vtype = 0; + SI::PMT::Stream stream; + for (SI::Loop::Iterator it; Pmt.streamLoop.getNext(stream, it); ) { + dbgpatpmt(" stream type = %02X, pid = %d", stream.getStreamType(), stream.getPid()); + switch (stream.getStreamType()) { + case 0x02: // STREAMTYPE_13818_VIDEO + case 0x1B: // MPEG4 + vpid = stream.getPid(); + vtype = stream.getStreamType(); + break; + case 0x04: // STREAMTYPE_13818_AUDIO + { + if (NumApids < MAXAPIDS) { + char ALangs[MAXLANGCODE2] = ""; + SI::Descriptor *d; + for (SI::Loop::Iterator it; (d = stream.streamDescriptors.getNext(it)); ) { + switch (d->getDescriptorTag()) { + case SI::ISO639LanguageDescriptorTag: { + SI::ISO639LanguageDescriptor *ld = (SI::ISO639LanguageDescriptor *)d; + SI::ISO639LanguageDescriptor::Language l; + char *s = ALangs; + int n = 0; + for (SI::Loop::Iterator it; ld->languageLoop.getNext(l, it); ) { + if (*ld->languageCode != '-') { // some use "---" to indicate "none" + dbgpatpmt(" '%s'", l.languageCode); + if (n > 0) + *s++ = '+'; + strn0cpy(s, I18nNormalizeLanguageCode(l.languageCode), MAXLANGCODE1); + s += strlen(s); + if (n++ > 1) + break; + } + } + } + break; + default: ; + } + delete d; + } + cDevice::PrimaryDevice()->SetAvailableTrack(ttAudio, NumApids, stream.getPid(), ALangs); + NumApids++; + } + } + break; + case 0x06: // STREAMTYPE_13818_PES_PRIVATE + { + int dpid = 0; + char lang[MAXLANGCODE1] = ""; + SI::Descriptor *d; + for (SI::Loop::Iterator it; (d = stream.streamDescriptors.getNext(it)); ) { + switch (d->getDescriptorTag()) { + case SI::AC3DescriptorTag: + dbgpatpmt(" AC3"); + dpid = stream.getPid(); + break; + case SI::SubtitlingDescriptorTag: + dbgpatpmt(" subtitling"); + if (NumSpids < MAXSPIDS) { + SI::SubtitlingDescriptor *sd = (SI::SubtitlingDescriptor *)d; + SI::SubtitlingDescriptor::Subtitling sub; + char SLangs[MAXLANGCODE2] = ""; + char *s = SLangs; + int n = 0; + for (SI::Loop::Iterator it; sd->subtitlingLoop.getNext(sub, it); ) { + if (sub.languageCode[0]) { + dbgpatpmt(" '%s'", sub.languageCode); + if (n > 0) + *s++ = '+'; + strn0cpy(s, I18nNormalizeLanguageCode(sub.languageCode), MAXLANGCODE1); + s += strlen(s); + if (n++ > 1) + break; + } + } + cDevice::PrimaryDevice()->SetAvailableTrack(ttSubtitle, NumSpids, stream.getPid(), SLangs); + NumSpids++; + } + break; + case SI::ISO639LanguageDescriptorTag: { + SI::ISO639LanguageDescriptor *ld = (SI::ISO639LanguageDescriptor *)d; + dbgpatpmt(" '%s'", ld->languageCode); + strn0cpy(lang, I18nNormalizeLanguageCode(ld->languageCode), MAXLANGCODE1); + } + break; + default: ; + } + delete d; + } + if (dpid) { + if (NumDpids < MAXDPIDS) { + cDevice::PrimaryDevice()->SetAvailableTrack(ttDolby, NumDpids, dpid, lang); + NumDpids++; + } + } + } + break; + } + dbgpatpmt("\n"); + cDevice::PrimaryDevice()->EnsureAudioTrack(true); + cDevice::PrimaryDevice()->EnsureSubtitleTrack(); + } + } + else + esyslog("ERROR: can't parse PMT"); + pmtSize = 0; +} + +#endif + +// --- cTsToPes -------------------------------------------------------------- + +cTsToPes::cTsToPes(void) +{ + data = NULL; + size = length = offset = 0; + synced = false; +} + +cTsToPes::~cTsToPes() +{ + free(data); +} + +void cTsToPes::PutTs(const uchar *Data, int Length) +{ + if (TsPayloadStart(Data)) + Reset(); + else if (!size) + return; // skip everything before the first payload start + Length = TsGetPayload(&Data); + if (length + Length > size) { + size = max(KILOBYTE(2), length + Length); + data = (uchar *)realloc(data, size); + } + memcpy(data + length, Data, Length); + length += Length; +} + +#define MAXPESLENGTH 0xFFF0 + +const uchar *cTsToPes::GetPes(int &Length) +{ + if (offset < length && PesLongEnough(length)) { + if (!PesHasLength(data)) // this is a video PES packet with undefined length + offset = 6; // trigger setting PES length for initial slice + if (offset) { + uchar *p = data + offset - 6; + if (p != data) { + p -= 3; + memmove(p, data, 4); + } + int l = min(length - offset, MAXPESLENGTH); + offset += l; + if (p != data) { + l += 3; + p[6] = 0x80; + p[7] = 0x00; + p[8] = 0x00; + } + p[4] = l / 256; + p[5] = l & 0xFF; + Length = l + 6; + return p; + } + else { + Length = PesLength(data); + offset = Length; // to make sure we break out in case of garbage data + return data; + } + } + return NULL; +} + +void cTsToPes::Reset(void) +{ + length = offset = 0; +} + +// --- Some helper functions for debugging ----------------------------------- + +void BlockDump(const char *Name, const u_char *Data, int Length) +{ + printf("--- %s\n", Name); + for (int i = 0; i < Length; i++) { + if (i && (i % 16) == 0) + printf("\n"); + printf(" %02X", Data[i]); + } + printf("\n"); +} + +void TsDump(const char *Name, const u_char *Data, int Length) +{ + printf("%s: %04X", Name, Length); + int n = min(Length, 20); + for (int i = 0; i < n; i++) + printf(" %02X", Data[i]); + if (n < Length) { + printf(" ..."); + n = max(n, Length - 10); + for (n = max(n, Length - 10); n < Length; n++) + printf(" %02X", Data[n]); + } + printf("\n"); +} + +void PesDump(const char *Name, const u_char *Data, int Length) +{ + TsDump(Name, Data, Length); +} + +} + +#endif + diff --git a/vdr172remux.h b/vdr172remux.h new file mode 100644 index 0000000..fffebc0 --- /dev/null +++ b/vdr172remux.h @@ -0,0 +1,297 @@ + +#if APIVERSNUM >= 10703 + +/* + * remux.h: A streaming MPEG2 remultiplexer + * + * See the main source file 'vdr.c' for copyright information and + * how to reach the author. + * + * $Id: remux.h 2.3 2008/12/13 13:55:07 kls Exp $ + */ + +#ifndef __VDR172REMUX_H +#define __VDR172REMUX_H + +#include <vdr/channels.h> +#include <vdr/ringbuffer.h> +#include <vdr/tools.h> + +namespace vdr172 +{ +enum ePesHeader { + phNeedMoreData = -1, + phInvalid = 0, + phMPEG1 = 1, + phMPEG2 = 2 + }; + +ePesHeader AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, bool *ContinuationHeader = NULL); + +// Picture types: +#define NO_PICTURE 0 +#define I_FRAME 1 +#define P_FRAME 2 +#define B_FRAME 3 + +#define MAXTRACKS 64 + +class cTS2PES; +class cAudioIndexer; + +class cRemux { +private: + bool exitOnFailure; + bool noVideo; + bool h264; + int numUPTerrors; + bool synced; + bool syncEarly; + int skipped; + cTS2PES *ts2pes[MAXTRACKS]; + int numTracks; + cRingBufferLinear *resultBuffer; + int resultSkipped; + cAudioIndexer *audioIndexer; + int GetPid(const uchar *Data); + int ScanVideoPacket(const uchar *Data, int Count, int Offset, uchar &PictureType); +public: + cRemux(int VPid, const int *APids, const int *DPids, const int *SPids, bool ExitOnFailure = false, bool SyncEarly = false); + ///< Creates a new remuxer for the given PIDs. VPid is the video PID, while + ///< APids, DPids and SPids are pointers to zero terminated lists of audio, + ///< dolby and subtitle PIDs (the pointers may be NULL if there is no such + ///< PID). If ExitOnFailure is true, the remuxer will initiate an "emergency + ///< exit" in case of problems with the data stream. SyncEarly causes cRemux + ///< to sync as soon as a video or audio frame is seen. + ~cRemux(); + void SetTimeouts(int PutTimeout, int GetTimeout) { resultBuffer->SetTimeouts(PutTimeout, GetTimeout); } + ///< By default cRemux assumes that Put() and Get() are called from different + ///< threads, and uses a timeout in the Get() function in case there is no + ///< data available. SetTimeouts() can be used to modify these timeouts. + ///< Especially if Put() and Get() are called from the same thread, setting + ///< both timeouts to 0 is recommended. + int Put(const uchar *Data, int Count); + ///< Puts at most Count bytes of Data into the remuxer. + ///< \return Returns the number of bytes actually consumed from Data. + uchar *Get(int &Count, uchar *PictureType = NULL); + ///< Gets all currently available data from the remuxer. + ///< \return Count contains the number of bytes the result points to, and + ///< PictureType (if not NULL) will contain one of NO_PICTURE, I_FRAME, P_FRAME + ///< or B_FRAME. + void Del(int Count); + ///< Deletes Count bytes from the remuxer. Count must be the number returned + ///< from a previous call to Get(). Several calls to Del() with fractions of + ///< a previously returned Count may be made, but the total sum of all Count + ///< values must be exactly what the previous Get() has returned. + void Clear(void); + ///< Clears the remuxer of all data it might still contain, keeping the PID + ///< settings as they are. + static void SetBrokenLink(uchar *Data, int Length); + static int GetPacketLength(const uchar *Data, int Count, int Offset); + static int GetAudioFrameDuration(const uchar *Data, int Count, int *TrackIndex = NULL); + static bool IsFrameH264(const uchar *Data, int Length); + }; + +// Some TS handling tools. +// The following functions all take a pointer to one complete TS packet. + +#define TS_SYNC_BYTE 0x47 +#define TS_SIZE 188 +#define TS_ADAPT_FIELD_EXISTS 0x20 +#define TS_PAYLOAD_EXISTS 0x10 +#define TS_CONT_CNT_MASK 0x0F +#define TS_PAYLOAD_START 0x40 +#define TS_ERROR 0x80 +#define TS_PID_MASK_HI 0x1F + +inline int TsHasPayload(const uchar *p) +{ + return p[3] & TS_PAYLOAD_EXISTS; +} + +inline int TsPayloadStart(const uchar *p) +{ + return p[1] & TS_PAYLOAD_START; +} + +inline int TsError(const uchar *p) +{ + return p[1] & TS_ERROR; +} + +inline int TsPid(const uchar *p) +{ + return (p[1] & TS_PID_MASK_HI) * 256 + p[2]; +} + +inline int TsPayloadOffset(const uchar *p) +{ + return (p[3] & TS_ADAPT_FIELD_EXISTS) ? p[4] + 5 : 4; +} + +inline int TsGetPayload(const uchar **p) +{ + int o = TsPayloadOffset(*p); + *p += o; + return TS_SIZE - o; +} + +inline int TsContinuityCounter(const uchar *p) +{ + return p[3] & TS_CONT_CNT_MASK; +} + +// Some PES handling tools: +// The following functions that take a pointer to PES data all assume that +// there is enough data so that PesLongEnough() returns true. + +inline bool PesLongEnough(int Length) +{ + return Length >= 6; +} + +inline bool PesHasLength(const uchar *p) +{ + return p[4] | p[5]; +} + +inline int PesLength(const uchar *p) +{ + return 6 + p[4] * 256 + p[5]; +} + +inline int PesPayloadOffset(const uchar *p) +{ + return 9 + p[8]; +} + +inline int64_t PesGetPts(const uchar *p) +{ + if ((p[7] & 0x80) && p[8] >= 5) { + return ((((int64_t)p[ 9]) & 0x0E) << 29) | + (( (int64_t)p[10]) << 22) | + ((((int64_t)p[11]) & 0xFE) << 14) | + (( (int64_t)p[12]) << 7) | + ((((int64_t)p[13]) & 0xFE) >> 1); + } + return 0; +} + +#if 0 + +// PAT/PMT Generator: + +#define MAX_SECTION_SIZE 4096 // maximum size of an SI section +#define MAX_PMT_TS (MAX_SECTION_SIZE / TS_SIZE + 1) + +class cPatPmtGenerator { +private: + uchar pat[TS_SIZE]; // the PAT always fits into a single TS packet + uchar pmt[MAX_PMT_TS][TS_SIZE]; // the PMT may well extend over several TS packets + int numPmtPackets; + int patCounter; + int pmtCounter; + int patVersion; + int pmtVersion; + uchar *esInfoLength; + void IncCounter(int &Counter, uchar *TsPacket); + void IncVersion(int &Version); + void IncEsInfoLength(int Length); +protected: + int MakeStream(uchar *Target, uchar Type, int Pid); + int MakeAC3Descriptor(uchar *Target); + int MakeSubtitlingDescriptor(uchar *Target, const char *Language); + int MakeLanguageDescriptor(uchar *Target, const char *Language); + int MakeCRC(uchar *Target, const uchar *Data, int Length); +public: + cPatPmtGenerator(void); + void GeneratePat(void); + ///< Generates a PAT section for later use with GetPat(). + ///< This function is called by default from the constructor. + void GeneratePmt(tChannelID ChannelID); + ///< Generates a PMT section for the given ChannelId, for later use + ///< with GetPmt(). + uchar *GetPat(void); + ///< Returns a pointer to the PAT section, which consist of exactly + ///< one TS packet. + uchar *GetPmt(int &Index); + ///< Returns a pointer to the Index'th TS packet of the PMT section. + ///< Index must be initialized to 0 and will be incremented by each + ///< call to GetPmt(). Returns NULL is all packets of the PMT section + ///< have been fetched.. + }; + +// PAT/PMT Parser: + +class cPatPmtParser { +private: + uchar pmt[MAX_SECTION_SIZE]; + int pmtSize; + int pmtPid; + int vpid; + int vtype; +protected: + int SectionLength(const uchar *Data, int Length) { return (Length >= 3) ? ((int(Data[1]) & 0x0F) << 8)| Data[2] : 0; } +public: + cPatPmtParser(void); + void ParsePat(const uchar *Data, int Length); + ///< Parses the given PAT Data, which is the payload of a single TS packet + ///< from the PAT stream. The PAT may consist only of a single TS packet. + void ParsePmt(const uchar *Data, int Length); + ///< Parses the given PMT Data, which is the payload of a single TS packet + ///< from the PMT stream. The PMT may consist of several TS packets, which + ///< are delivered to the parser through several subsequent calls to + ///< ParsePmt(). The whole PMT data will be processed once the last packet + ///< has been received. + int PmtPid(void) { return pmtPid; } + ///< Returns the PMT pid as defined by the current PAT. + ///< If no PAT has been received yet, -1 will be returned. + int Vpid(void) { return vpid; } + ///< Returns the video pid as defined by the current PMT. + int Vtype(void) { return vtype; } + }; + +#endif + +// TS to PES converter: +// Puts together the payload of several TS packets that form one PES +// packet. + +class cTsToPes { +private: + uchar *data; + int size; + int length; + int offset; + bool synced; +public: + cTsToPes(void); + ~cTsToPes(); + void PutTs(const uchar *Data, int Length); + ///< Puts the payload data of the single TS packet at Data into the converter. + ///< Length is always 188. + ///< If the given TS packet starts a new PES payload packet, the converter + ///< will be automatically reset. Any packets before the first one that starts + ///< a new PES payload packet will be ignored. + const uchar *GetPes(int &Length); + ///< Gets a pointer to the complete PES packet, or NULL if the packet + ///< is not complete yet. If the packet is complete, Length will contain + ///< the total packet length. The returned pointer is only valid until + ///< the next call to PutTs() or Reset(), or until this object is destroyed. + void Reset(void); + ///< Resets the converter. This needs to be called after a PES packet has + ///< been fetched by a call to GetPes(), and before the next call to + ///< PutTs(). + }; + +// Some helper functions for debugging: + +void BlockDump(const char *Name, const u_char *Data, int Length); +void TsDump(const char *Name, const u_char *Data, int Length); +void PesDump(const char *Name, const u_char *Data, int Length); +} + +#endif // __VDR172REMUX_H + +#endif + @@ -0,0 +1,343 @@ +/* + * xine.c: A plugin for the Video Disk Recorder + * + * See the README file for copyright information and how to reach the author. + * + * $Id$ + */ + +#include "xineCommon.h" + +#include <vdr/plugin.h> + +#include "xineDevice.h" +#include "xineSettings.h" +#include "xineSetupPage.h" +#include "xineI18n.h" + + + +static const char *VERSION = "0.9.4"; +static const char *DESCRIPTION = tr("Software based playback using xine"); +//static const char *MAINMENUENTRY = "xine - Toggle prebuffer setting"; + + + +class cPluginXine : public cPlugin { +private: + // Add any member variables or functions you may need here. + PluginXine::cXineSettings m_settings; + + PluginXine::cXineRemote *m_remote; + bool m_remoteOn; + +public: + PluginXine::cXineLib *m_xineLib; + int m_instanceNo; + in_addr_t m_bindIp; + int m_bindPort; + + cPluginXine(void); + virtual ~cPluginXine(); + virtual const char *Version(void) { return VERSION; } + virtual const char *Description(void) { return tr(DESCRIPTION); } + virtual const char *CommandLineHelp(void); + virtual bool ProcessArgs(int argc, char *argv[]); + virtual bool Initialize(void); + virtual bool Start(void); + virtual void Stop(void); + virtual void Housekeeping(void); +#if APIVERSNUM >= 10347 + virtual void MainThreadHook(void); + virtual cString Active(void); + virtual time_t WakeupTime(void); +#endif + virtual const char *MainMenuEntry(void);// { return tr(MAINMENUENTRY); } + virtual cOsdObject *MainMenuAction(void); + virtual cMenuSetupPage *SetupMenu(void); + virtual bool SetupParse(const char *Name, const char *Value); +#if APIVERSNUM >= 10330 + virtual bool Service(const char *Id, void *Data = NULL); + virtual const char **SVDRPHelpPages(void); + virtual cString SVDRPCommand(const char *Command, const char *Option, int &ReplyCode); +#endif + }; + +cPluginXine::cPluginXine(void) + : cPlugin() + , m_remote(0) + , m_remoteOn(false) + , m_xineLib(0) + , m_instanceNo(-1) + , m_bindIp(0) + , m_bindPort(0) +{ + // Initialize any member variables here. + // DON'T DO ANYTHING ELSE THAT MAY HAVE SIDE EFFECTS, REQUIRE GLOBAL + // VDR OBJECTS TO EXIST OR PRODUCE ANY OUTPUT! +} + +cPluginXine::~cPluginXine() +{ + // Clean up after yourself! +} + +const char *cPluginXine::CommandLineHelp(void) +{ + //Return a string that describes all known command line options. + //" - -- x \n" + return + " -bIP ip address to bind for socket connections (see -p)\n" + " -iN instance number to append to FIFO directory\n" + " -p[N] use socket connections on port N (18701)\n" + " -q turn off debug messages on console\n" + " -r turn on remote (pressing keys in xine controls VDR)\n" +#if APIVERSNUM >= 10320 + " -s switch to curses skin, while xine is disconnected\n" +#endif + " -XN default 'SizeX' for GRAB command (720, 1..4096)\n" + " -YN default 'SizeY' for GRAB command (576, 1..4096)\n" + ; +} + +bool cPluginXine::ProcessArgs(int argc, char *argv[]) +{ + // Implement command line argument processing here if applicable. + + ::optind = 0; + ::opterr = 0; + +#define INVALID_ARG(fmt, args...) do { esyslog(fmt, ##args); fprintf(stderr, fmt "\n", ##args); } while (false) + + for (int r = -1; (r = ::getopt(argc, argv, ":b:i:p::qrsX:Y:")) >= 0; ) + { + switch (r) + { + case ':': + INVALID_ARG("vdr-xine: missing argument for option -%c", ::optopt); + return false; + + case 'b': + m_bindIp = ::inet_addr(::optarg); + if (m_bindIp == INADDR_NONE) + { + INVALID_ARG("vdr-xine: invalid argument '%s' for option -%c", ::optarg, r); + return false; + } + break; + + case 'i': + m_instanceNo = ::atoi(::optarg); + if (m_instanceNo < 0) + { + INVALID_ARG("vdr-xine: invalid argument '%s' for option -%c", ::optarg, r); + return false; + } + break; + + case 'p': + if (!::optarg) + m_bindPort = 18701; + else + { + m_bindPort = ::atoi(::optarg); + if (m_bindPort <= 0) + { + INVALID_ARG("vdr-xine: invalid argument '%s' for option -%c", ::optarg, r); + return false; + } + } + break; + + case 'r': + m_remoteOn = true; + break; + +#if APIVERSNUM >= 10320 + case 's': + m_settings.SetSwitchSkin(true); + break; +#endif + + case 'q': + m_settings.SetBeQuiet(true); + break; + + case 'X': + { + const int X = ::atoi(::optarg); + if (X < 1 || X > 4096) + { + INVALID_ARG("vdr-xine: invalid argument '%s' for option -%c", ::optarg, r); + return false; + } + + m_settings.SetDefaultGrabSizeX(X); + } + break; + + case 'Y': + { + const int Y = ::atoi(::optarg); + if (Y < 1 || Y > 4096) + { + INVALID_ARG("vdr-xine: invalid argument '%s' for option -%c", ::optarg, r); + return false; + } + + m_settings.SetDefaultGrabSizeY(Y); + } + break; + + default: + INVALID_ARG("vdr-xine: invalid option -%c", r); + return false; + } + } + + if (argv[::optind]) + { + INVALID_ARG("vdr-xine: invalid argument '%s'", argv[::optind]); + return false; + } + +#undef INVALID_ARG + + return true; +} + +bool cPluginXine::Initialize(void) +{ +#if APIVERSNUM < 10507 + RegisterI18n(PluginXine::Phrases); +#endif + + // Initialize any background activities the plugin shall perform. + m_remote = new PluginXine::cXineRemote(m_remoteOn); + if (!m_remote) + return false; + + if (!PluginXine::cXineDevice::Create(this, m_settings, m_remote)) + return false; + + return true; +} + +bool cPluginXine::Start(void) +{ + // Start any background activities the plugin shall perform. + if (!PluginXine::cXineDevice::Open()) + return false; + + return true; +} + +void cPluginXine::Stop(void) +{ + PluginXine::cXineDevice::Stop(); +} + +void cPluginXine::Housekeeping(void) +{ + // Perform any cleanup or other regular tasks. +} + +#if APIVERSNUM >= 10347 + +void cPluginXine::MainThreadHook(void) +{ + // Perform actions in the context of the main program thread. + // WARNING: Use with great care - see PLUGINS.html! + + PluginXine::cXineDevice::MainMenuTrampoline(); +} + +cString cPluginXine::Active(void) +{ + // Return a message string if shutdown should be postponed + return NULL; +} + +time_t cPluginXine::WakeupTime(void) +{ + // Return custom wakeup time for shutdown script + return 0; +} + +#endif + +cOsdObject *cPluginXine::MainMenuAction(void) +{ + // Perform the action when selected from the main VDR menu. +#if APIVERSNUM < 10347 + PluginXine::cXineDevice::MainMenuTrampoline(); +#endif + return NULL; +} + +cMenuSetupPage *cPluginXine::SetupMenu(void) +{ + // Return a setup menu in case the plugin supports one. + return new PluginXine::cXineSetupPage(m_xineLib, m_settings); +} + +bool cPluginXine::SetupParse(const char *Name, const char *Value) +{ + // Parse your own setup parameters and store their values. + return m_settings.SetupParse(Name, Value); +} + +#if APIVERSNUM >= 10330 + +bool cPluginXine::Service(const char *Id, void *Data) +{ + // Handle custom service requests from other plugins + return cPlugin::Service(Id, Data); +} + +const char **cPluginXine::SVDRPHelpPages(void) +{ + // Return help text for SVDRP commands this plugin implements + return cPlugin::SVDRPHelpPages(); +} + +cString cPluginXine::SVDRPCommand(const char *Command, const char *Option, int &ReplyCode) +{ + // Process SVDRP commands this plugin implements + return cPlugin::SVDRPCommand(Command, Option, ReplyCode); +} + +#endif + +const char *cPluginXine::MainMenuEntry(void) +{ +// return m_settings.GetMainMenuEntry(); + return 0; +} + +namespace PluginXine +{ + int GetBindIp(cPlugin *const plugin) + { + return ((cPluginXine *)plugin)->m_bindIp; + } + + int GetBindPort(cPlugin *const plugin) + { + return ((cPluginXine *)plugin)->m_bindPort; + } + + int GetInstanceNo(cPlugin *const plugin) + { + return ((cPluginXine *)plugin)->m_instanceNo; + } + + cXineLib *&GetXineLib(cPlugin *const plugin) + { + return ((cPluginXine *)plugin)->m_xineLib; + } +} + + + +VDRPLUGINCREATOR(cPluginXine); // Don't touch this! diff --git a/xineCommon.h b/xineCommon.h new file mode 100644 index 0000000..ed5fe9d --- /dev/null +++ b/xineCommon.h @@ -0,0 +1,63 @@ + +#ifndef __XINECOMMON_H +#define __XINECOMMON_H + + + +#include <assert.h> +#include <math.h> +#include <signal.h> + +#define __STDC_FORMAT_MACROS +#include <inttypes.h> + +#include <string> +using namespace std; + +#include <xine.h> +#include <xine/vdr.h> + +#define MIN_XINE_VDR_VERSION 901 + +#if !defined(XINE_VDR_VERSION) || XINE_VDR_VERSION < MIN_XINE_VDR_VERSION +#error xine/vdr.h does not match. Please solve this issue by reading section XINE VDR VERSION MISMATCH in INSTALL! +#endif + +#include <vdr/config.h> // poisened + +#ifndef APIVERSNUM +#define APIVERSNUM VDRVERSNUM +#endif + + + +namespace PluginXine +{ + extern bool beQuiet; + + template <class DST_TYPE, class SRC_TYPE> + DST_TYPE &alias_cast(SRC_TYPE &rhs) + { + union hlp + { + SRC_TYPE src; + DST_TYPE dst; + }; + + return ((hlp &)rhs).dst; + } +}; + +#define xfprintf(fh, fmt, args...) \ + while (!PluginXine::beQuiet) \ + { \ + fprintf(fh, fmt, ##args); \ + /* char xfmt[ 500 ]; */ \ + /* sprintf(xfmt, "%s", fmt); */ \ + /* fprintf(fh, xfmt, ##args); */ \ + break; \ + } + + + +#endif //__XINECOMMON_H diff --git a/xineDevice.c b/xineDevice.c new file mode 100644 index 0000000..bf752d5 --- /dev/null +++ b/xineDevice.c @@ -0,0 +1,4409 @@ + +#include "xineCommon.h" + +#include <vdr/remux.h> +#include <vdr/transfer.h> + +#if APIVERSNUM >= 10703 +#include "vdr172remux.h" +#define VDR172 ::vdr172 +#else +#define VDR172 +#endif + +#include "xineDevice.h" +#include "xineOsd.h" +#include "xineSettings.h" + + +//#define LOG_ME(x) x +#define LOG_ME(x) + + +namespace PluginXine +{ + void cXineSpuDecoder::ptsAdjust(uint32_t &pts) + { + if (0 == pts + || -1 == (int32_t)pts) + { + return; + } + + const int64_t ptsXine = m_xineDevice->GetSTC(); + + if (-1 == ptsXine) + return; + +// ::fprintf(stderr, "ptsAdjust: %ld, %lld, %lld\n", pts, ptsXine, pts - ptsXine); + + pts = (uint32_t)ptsXine; + } + + int cXineSpuDecoder::setTime(uint32_t pts) + { + ptsAdjust(pts); + + return cDvbSpuDecoder::setTime(pts); + } + + static cXineDevice *theXineDevice = 0; + + bool cXineDevice::HasDecoder(void) const + { + return true; + } + + cSpuDecoder *cXineDevice::GetSpuDecoder(void) + { + if (!m_spuDecoder + && IsPrimaryDevice()) + { + m_spuDecoder = new cXineSpuDecoder(this); + } + + return m_spuDecoder; + } + + bool cXineDevice::CanReplay(void) const + { + return true; + } + + static bool findVideo = false; + static bool foundVideo = false; + static int ts = 0; + static bool f = false; + static bool np = false; + static bool muted = false; + static int jumboPESsize = 0; + static int jumboPEStail = 0; + static ePlayMode pm = pmNone; + static bool audioSeen = false; + + static int64_t ptsV = -1, ptsA = -1, ptsP = -1, ptsD = -1; + + static cMutex softStartMutex; + static enum { sstNone = 0, sstNormal, sstNoMetronom } softStartTrigger = sstNone; + static enum + { + sIdle + , sInitiateSequence + , sStartSequence + , sContinueSequence + } + softStartState = sIdle; + + double tNow() + { + timeval tv; + ::gettimeofday(&tv, 0); + return tv.tv_sec + tv.tv_usec / 1.0e+6; + } + + static double ttt0 = tNow(), ttt1 = tNow(), ttt2 = 0, ttt3 = 0, ttt4 = 0, ttt5 = 0, ttt6 = 0; + +// static int streams[ 256 ]; + static bool doClear = false; + + bool cXineDevice::SetPlayMode(ePlayMode PlayMode) + { + if (pmNone == PlayMode) + { + doClear = true; + ttt0 = tNow(); + } + else + ttt2 = tNow(); +/* + timeval tv0; + ::gettimeofday(&tv0, 0); +*/ + if (0) + { + time_t t1 = time(0); + static time_t t0 = t1; + + if (0 == PlayMode && (t1 - t0) > (30 * 60)) + *(char *)0 = 0; + + t0 = t1; + } + + bool playModeSupported = false; + + switch (PlayMode) + { + case pmNone: + case pmAudioVideo: + case pmAudioOnlyBlack: + case pmExtern_THIS_SHOULD_BE_AVOIDED: + playModeSupported = true; + break; + case pmAudioOnly: +#if APIVERSNUM >= 10308 + case pmVideoOnly: +#endif + break; + } + + ptsV = ptsA = ptsP = ptsD = -1; + + ts = 0; + np = 0; + f = 0; + + xfprintf(stderr, "SetPlayMode: %d\n", PlayMode); + + if (pmExtern_THIS_SHOULD_BE_AVOIDED == pm + && pmNone == PlayMode) + { + m_xineLib.enableExternal(false); + } + + m_xineLib.pause(false); + m_xineLib.execFuncTrickSpeedMode(false); + m_xineLib.execFuncSetSpeed(100.0); + + if (muted) + { + muted = false; + m_xineLib.execFuncMute(false); + } + + if (pmNone == PlayMode) + { + pm = PlayMode; + + jumboPESsize = 0; + jumboPEStail = 0; +/* + for (unsigned int i = 0; i < sizeof (streams) / sizeof (*streams); i++) + { + if (streams[ i ]) + fprintf(stderr, "stream: 0x%02x\n", i); + } +*/ + + m_xineLib.ignore(); +// m_xineLib.execFuncMute(); +// m_xineLib.execFuncSetPrebuffer(0); + m_xineLib.execFuncClear(-2); +// m_xineLib.execFuncStart(); +// m_xineLib.execFuncMetronom(0); + m_xineLib.execFuncStillFrame(); + m_xineLib.execFuncWait(); + +// for (int i = 0; i < 2; i++) + m_xineLib.showNoSignal(); + + PushOut(); + m_xineLib.execFuncFlush(); + + { + cMutexLock lock(&softStartMutex); + softStartTrigger = sstNone; + softStartState = sIdle; + } + + foundVideo = false; + findVideo = false; + } + else + { + audioSeen = false; + { + cMutexLock lock(&softStartMutex); + softStartTrigger = sstNone; + softStartState = sIdle; + } +// ::memset(&streams, 0, sizeof (streams)); + + m_xineLib.freeze(); + m_xineLib.ignore(false); + + m_xineLib.freeze(false); +/* + PushOut(); + m_xineLib.execFuncFlush(); + m_xineLib.execFuncWait(); +*/ +// m_xineLib.execFuncSetPrebuffer(m_settings.GetModeParams()->m_prebufferFrames); +// m_xineLib.execFuncSetPrebuffer(0); + m_xineLib.execFuncClear(-4); +// m_xineLib.execFuncStart(); + m_xineLib.execFuncWait(); + +#if APIVERSNUM < 10342 + m_settings.SelectReplayPrebufferMode(0 == cTransferControl::ReceiverDevice()); +#else + m_settings.SelectReplayPrebufferMode(!Transferring()); +#endif + + if (m_settings.LiveTV()) + { + cMutexLock lock(&softStartMutex); +// ::fprintf(stderr, "LiveTV\n"); + softStartTrigger = sstNormal; + } + else + np = true; + + foundVideo = false; + findVideo = true; + + cMutexLock pmMutexLock(&m_pmMutex); + pm = PlayMode; + m_pmCondVar.Broadcast(); + } + + if (pmExtern_THIS_SHOULD_BE_AVOIDED == PlayMode) + m_xineLib.enableExternal(); +/* + timeval tv1; + ::gettimeofday(&tv1, 0); + + fprintf(stderr, "spm: %.3lf ms\n", 1000 * ((tv1.tv_sec + tv1.tv_usec / 1.0e+6) - (tv0.tv_sec + tv0.tv_usec / 1.0e+6))); +*/ + if (pmNone == PlayMode) { + ttt1 = tNow(); ttt4 = 0; ttt5 = 0; } + else + ttt3 = tNow(); + return playModeSupported; + } + + void cXineDevice::DiscontinuityDetected() + { + if (m_settings.LiveTV()) + { + cMutexLock lock(&softStartMutex); + if (softStartState == sIdle + && softStartTrigger == sstNone + && pm != pmNone) + { + softStartTrigger = sstNormal; + xfprintf(stderr, "DiscontinuityDetected: triggering soft start\n"); + } + } + } + + static bool lastCmdWasClear = false; + + bool cXineDevice::HasIBPTrickSpeed(void) + { +/* +#if APIVERSNUM >= 10706 + return true; +#else +*/ + return false; +//#endif + } + + void cXineDevice::TrickSpeed(int Speed) + { + TrickSpeed(Speed, false); + } + + void cXineDevice::TrickSpeed(int Speed, bool IBP) + { + f = false; + ts = Speed; + + xfprintf(stderr, "TrickSpeed: %d\n", Speed); + m_xineLib.execFuncTrickSpeedMode(lastCmdWasClear); + m_xineLib.execFuncSetSpeed(100.0 / Speed * (IBP ? 12 : 1)); + m_xineLib.execFuncWait(); + m_xineLib.freeze(false); + m_xineLib.pause(false); + } + + void cXineDevice::Clear(void) + { + lastCmdWasClear = true; + + doClear = true; + ptsV = ptsA = ptsP = ptsD = -1; + + static int cntClear = 0; + + xfprintf(stderr, "Clear(%d)", cntClear); + + m_xineLib.pause(); + + jumboPESsize = 0; + jumboPEStail = 0; + + if (f) + m_xineLib.execFuncSetSpeed(100.0); + + m_xineLib.execFuncClear(cntClear++); +// m_xineLib.execFuncStart(); + np = true; + + if (f) + m_xineLib.execFuncSetSpeed(0.0); + + m_xineLib.execFuncWait(); + m_xineLib.pause(false); + xfprintf(stderr, "!\n"); + if (m_settings.LiveTV()) + { + cMutexLock lock(&softStartMutex); + softStartTrigger = sstNoMetronom; + } + + cDevice::Clear(); + } + + void cXineDevice::Play(void) + { + lastCmdWasClear = false; + + f = false; + ts = 0; + + xfprintf(stderr, "Play\n"); + m_xineLib.execFuncTrickSpeedMode(false); + m_xineLib.execFuncSetSpeed(100.0); + + if (muted) + { + muted = false; + m_xineLib.execFuncMute(false); + } + + m_xineLib.execFuncWait(); + m_xineLib.freeze(false); + m_xineLib.pause(false); + LOG_ME(::fprintf(stderr, "----\n");) + } + + void cXineDevice::Freeze(void) + { + lastCmdWasClear = false; + + f = true; + + xfprintf(stderr, "Freeze\n"); + m_xineLib.freeze(); + m_xineLib.pause(); + m_xineLib.execFuncSetSpeed(0.0); + m_xineLib.execFuncWait(); + LOG_ME(::fprintf(stderr, "------\n");) + } + + void cXineDevice::Mute(void) + { + xfprintf(stderr, "Mute\n"); + m_xineLib.execFuncMute(true); + + muted = true; + } + + static void store_frame(const unsigned char *buf, int len, int line) + { + if (0) + { + static int cnt = 0; + + char name[ 100 ]; + ::sprintf(name, "/tmp/frame_%05d_%05d", line, cnt++); + + FILE *f = fopen(name, "wb"); + size_t r = fwrite(buf, 1, len, f); + (void)r; + fclose(f); + } + } + +#define VERBOSE_NOP() do{ xfprintf(stderr, "FIXME: %s:%d\n", __FILE__, __LINE__); } while (0) +#define VERBOSE_NOP1() do{ store_frame(Data, Length, __LINE__); xfprintf(stderr, "FIXME: %s:%d\n", __FILE__, __LINE__); } while (0) +#define VERBOSE_RETURN0(x) do{ xfprintf(stderr, "FIXME: %s:%d\n", __FILE__, __LINE__); return x; } while (0) +#define VERBOSE_RETURN1(x) do{ store_frame(buf0, len0, __LINE__); xfprintf(stderr, "FIXME: %s:%d\n", __FILE__, __LINE__); return x; } while (0) +#define VERBOSE_RETURN2(x) do{ store_frame(buf, len, __LINE__); xfprintf(stderr, "FIXME: %s:%d\n", __FILE__, __LINE__); return x; } while (0) +#define VERBOSE_RETURN3(x) do{ store_frame(Data, Length, __LINE__); xfprintf(stderr, "FIXME: %s:%d\n", __FILE__, __LINE__); return x; } while (0) + +#if APIVERSNUM < 10331 + +enum ePesHeader { + phNeedMoreData = -1, + phInvalid = 0, + phMPEG1 = 1, + phMPEG2 = 2 + }; + +static ePesHeader AnalyzePesHeader(const uchar *Data, int Count, int &PesPayloadOffset, bool *ContinuationHeader = NULL) +{ + if (Count < 7) + return phNeedMoreData; // too short + + if ((Data[6] & 0xC0) == 0x80) { // MPEG 2 + if (Count < 9) + return phNeedMoreData; // too short + + PesPayloadOffset = 6 + 3 + Data[8]; + if (Count < PesPayloadOffset) + return phNeedMoreData; // too short + + if (ContinuationHeader) + *ContinuationHeader = ((Data[6] == 0x80) && !Data[7] && !Data[8]); + + return phMPEG2; // MPEG 2 + } + + // check for MPEG 1 ... + PesPayloadOffset = 6; + + // skip up to 16 stuffing bytes + for (int i = 0; i < 16; i++) { + if (Data[PesPayloadOffset] != 0xFF) + break; + + if (Count <= ++PesPayloadOffset) + return phNeedMoreData; // too short + } + + // skip STD_buffer_scale/size + if ((Data[PesPayloadOffset] & 0xC0) == 0x40) { + PesPayloadOffset += 2; + + if (Count <= PesPayloadOffset) + return phNeedMoreData; // too short + } + + if (ContinuationHeader) + *ContinuationHeader = false; + + if ((Data[PesPayloadOffset] & 0xF0) == 0x20) { + // skip PTS only + PesPayloadOffset += 5; + } + else if ((Data[PesPayloadOffset] & 0xF0) == 0x30) { + // skip PTS and DTS + PesPayloadOffset += 10; + } + else if (Data[PesPayloadOffset] == 0x0F) { + // continuation header + PesPayloadOffset++; + + if (ContinuationHeader) + *ContinuationHeader = true; + } + else + return phInvalid; // unknown + + if (Count < PesPayloadOffset) + return phNeedMoreData; // too short + + return phMPEG1; // MPEG 1 +} + +#endif + +//#if APIVERSNUM < 10345 + + namespace cRemux + { +// Start codes: +#define SC_SEQUENCE 0xB3 // "sequence header code" +#define SC_GROUP 0xB8 // "group start code" +#define SC_PICTURE 0x00 // "picture start code" + + int 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; + } + +bool IsFrameH264(const uchar *Data, int Length) +{ + int PesPayloadOffset; + const uchar *limit = Data + Length; + if (AnalyzePesHeader(Data, Length, PesPayloadOffset) <= phInvalid) + return false; // neither MPEG1 nor MPEG2 + + Data += PesPayloadOffset + 3; // move to video payload and skip 00 00 01 + if (Data < limit) { + // cVideoRepacker ensures that in case of H264 we will see an access unit delimiter here + if (0x01 == Data[-1] && 9 == Data[0] && 0x00 == Data[-2] && 0x00 == Data[-3]) + return true; + } + + return false; +} + +int 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) { + const bool h264 = IsFrameH264(Data + Offset, Length); + int PesPayloadOffset = 0; + if (AnalyzePesHeader(Data + Offset, Length, PesPayloadOffset) >= phMPEG1) { + const uchar *p = Data + Offset + PesPayloadOffset + 2; + const uchar *pLimit = Data + Offset + Length - 3; +#if APIVERSNUM >= 10326 + // cVideoRepacker ensures that a new PES packet is started for a new sequence, + // group or picture which allows us to easily skip scanning through a huge + // amount of video data. + if (p < pLimit) { + if (p[-2] || p[-1] || p[0] != 0x01) + pLimit = 0; // skip scanning: packet doesn't start with 0x000001 + else { + if (h264) { + int nal_unit_type = p[1] & 0x1F; + switch (nal_unit_type) { + case 9: // access unit delimiter + // when the MSB in p[1] is set (which violates H.264) then this is a hint + // from cVideoRepacker::HandleNalUnit() that this bottom field shall not + // be reported as picture. + if (p[1] & 0x80) + ((uchar *)p)[1] &= ~0x80; // revert the hint and fall through + else + break; + default: // skip scanning: packet doesn't start a new picture + pLimit = 0; + } + } + else { + switch (p[1]) { + case SC_SEQUENCE: + case SC_GROUP: + case SC_PICTURE: + break; + default: // skip scanning: packet doesn't start a new sequence, group or picture + pLimit = 0; + } + } + } + } +#endif + while (p < pLimit && (p = (const uchar *)memchr(p, 0x01, pLimit - p))) { + if (!p[-2] && !p[-1]) { // found 0x000001 + if (h264) { + int nal_unit_type = p[1] & 0x1F; + switch (nal_unit_type) { + case 9: { // access unit delimiter + int primary_pic_type = p[2] >> 5; + switch (primary_pic_type) { + case 0: // I + case 3: // SI + case 5: // I, SI + PictureType = I_FRAME; + break; + case 1: // I, P + case 4: // SI, SP + case 6: // I, SI, P, SP + PictureType = P_FRAME; + break; + case 2: // I, P, B + case 7: // I, SI, P, SP, B + PictureType = B_FRAME; + break; + } + return Length; + } + } + } + else { + switch (p[1]) { + case SC_PICTURE: PictureType = (p[3] >> 3) & 0x07; + return Length; + } + } + p += 4; // continue scanning after 0x01ssxxyy + } + else + p += 3; // continue scanning after 0x01xxyy + } + } + PictureType = NO_PICTURE; + return Length; + } + return -1; +} + } + +//#endif + +/* + static bool IsNotVideoIorPframe(const uchar *buf, int len) + { + if (0xe0 != (0xf0 & buf[ 3 ])) // not video + return true; + + uchar pt = NO_PICTURE; + cRemux::ScanVideoPacket(buf, len, 0, pt); + + return (I_FRAME == pt || P_FRAME == pt); + } +*/ +/* + static char *getFrameType(const uchar *buf, int len) + { + if (0xe0 != (0xf0 & buf[ 3 ])) // not video + return ""; + + static char *frameTypes[ 8 ] = + { + "", + "i", + "p", + "b", + "4", + "5", + "6", + "7" + }; + + uchar pt = NO_PICTURE; + cRemux::ScanVideoPacket(buf, len, 0, pt); + + return frameTypes[ pt ]; + } +*/ + static bool getPTS(const unsigned char *buf0, int len0, int64_t &pts) + { + while (len0 > 0) + { + while (len0 > 3 + && 0x00 == buf0[ 0 ] + && 0x00 == buf0[ 1 ] + && 0x00 == buf0[ 2 ]) + { + buf0++; + len0--; + } + + if (3 == len0 + && 0x00 == buf0[ 0 ] + && 0x00 == buf0[ 1 ] + && 0x00 == buf0[ 2 ]) + { + break; + } + + if (len0 < 6) + VERBOSE_RETURN1(false); + + if (0x00 != buf0[ 0 ] + || 0x00 != buf0[ 1 ] + || 0x01 != buf0[ 2 ]) + { + VERBOSE_RETURN1(false); + } + + if (0xe0 != (0xf0 & buf0[ 3 ]) // video + && 0xc0 != (0xe0 & buf0[ 3 ]) // audio + && 0xbd != (0xff & buf0[ 3 ]) // dolby + && 0xbe != (0xff & buf0[ 3 ])) // padding (DVD) + { + VERBOSE_RETURN1(false); + } + + int len = (6 + buf0[ 4 ] * 0x100 + buf0[ 5 ]); + if (len > len0) + VERBOSE_RETURN1(false); + + const unsigned char *buf = buf0; + buf0 += len; + len0 -= len; + +// if (len0 != 0) +// VERBOSE_NOP(); + + if (0xbe == (0xff & buf[ 3 ])) // padding (DVD) + continue; + + if (len < (6 + 3)) + VERBOSE_RETURN2(false); + + if (0x80 != (0xc0 & buf[ 6 ])) // MPEG1 + { + do // ... while (false); + { + int o = 0; + + for (int i = 0; i < 16; i++) + { + if (buf[ 6 + o ] != 0xff) + break; + + if (len < (6 + ++o)) + VERBOSE_RETURN2(false); + } + + if (0x40 == (0xc0 & buf[ 6 + o ])) + o += 2; + + if (len < (6 + o)) + VERBOSE_RETURN2(false); + + if (0x31 == (0xf1 & buf[ 6 + o + 0 ])) + { + if (len < (6 + o + 5 + 5)) + VERBOSE_RETURN2(false); + + if (0x01 != (0x01 & buf[ 6 + o + 2 ])) + VERBOSE_RETURN2(false); + + if (0x01 != (0x01 & buf[ 6 + o + 4 ])) + VERBOSE_RETURN2(false); + + if (0x11 != (0xf1 & buf[ 6 + o + 5 + 0 ])) + VERBOSE_RETURN2(false); + + if (0x01 != (0x01 & buf[ 6 + o + 5 + 2 ])) + VERBOSE_RETURN2(false); + + if (0x01 != (0x01 & buf[ 6 + o + 5 + 4 ])) + VERBOSE_RETURN2(false); + + int64_t _pts = ((int64_t)(0x0e & buf[ 6 + o + 0 ])) << 29 + | (0xff & buf[ 6 + o + 1 ]) << 22 + | (0xfe & buf[ 6 + o + 2 ]) << 14 + | (0xff & buf[ 6 + o + 3 ]) << 7 + | (0xfe & buf[ 6 + o + 4 ]) >> 1; + +// ::fprintf(stderr, "pts: %lld\n", _pts); + + if (0 == _pts) + break; + +// if (!IsNotVideoIorPframe(buf, len)) // only PTS of I and P frames are progressive in time +// break; + + pts = _pts; + + return true; + } + else if (0x21 == (0xf1 & buf[ 6 + o + 0 ])) + { + if (len < (6 + o + 5)) + VERBOSE_RETURN2(false); + + if (0x01 != (0x01 & buf[ 6 + o + 2 ])) + VERBOSE_RETURN2(false); + + if (0x01 != (0x01 & buf[ 6 + o + 4 ])) + VERBOSE_RETURN2(false); + + int64_t _pts = ((int64_t)(0x0e & buf[ 6 + o + 0 ])) << 29 + | (0xff & buf[ 6 + o + 1 ]) << 22 + | (0xfe & buf[ 6 + o + 2 ]) << 14 + | (0xff & buf[ 6 + o + 3 ]) << 7 + | (0xfe & buf[ 6 + o + 4 ]) >> 1; + +// ::fprintf(stderr, "pts: %lld\n", _pts); + + if (0 == _pts) + break; + +// if (!IsNotVideoIorPframe(buf, len)) // only PTS of I and P frames are progressive in time +// break; + + pts = _pts; + + return true; + } + else if (0x0f == (0xff & buf[ 6 + o + 0 ])) + { + break; + } + + for (int i = 0; i < 30; i++) + xfprintf(stderr, "%02x ", buf[ i ]); + xfprintf(stderr, "\n"); + + VERBOSE_RETURN2(false); + } + while (false); + + continue; + } + + if (0x40 == (0xc0 & buf[ 7 ])) + VERBOSE_RETURN2(false); + + if (0x00 == (0xc0 & buf[ 7 ])) + continue; + +// ignore +// if (0x00 != (0x3f & buf[ 7 ])) +// VERBOSE_RETURN2(false); + + bool hasPTS = (0 != (0x80 & buf[ 7 ])); + bool hasDTS = (0 != (0x40 & buf[ 7 ])); + + unsigned char hdl = buf[ 8 ]; + + if (hdl < ((hasPTS + hasDTS) * 5)) + VERBOSE_RETURN2(false); + + if (len < (6 + 3 + hdl)) + VERBOSE_RETURN2(false); + + if ((0x20 * hasPTS + 0x10 * hasDTS + 0x01) != (0xf1 & buf[ 9 ])) + { + if ((0x20 * hasPTS + 0x00 * hasDTS + 0x01) != (0xf1 & buf[ 9 ])) + { + // accept streams, that start with '00X0' instead of '00X1'. + } + else if ((0x00 * hasPTS + 0x10 * hasDTS + 0x01) != (0xf1 & buf[ 9 ])) + { + // accept streams, that start with '000X' instead of '001X'. + } + else + { +fprintf(stderr, "buf:"); for (int i = 0; i < 6 + 3 + hdl; i++) fprintf(stderr, " %02x", buf[i]); fprintf(stderr, "\n"); + VERBOSE_RETURN2(false); + } + } + + if (0x01 != (0x01 & buf[ 11 ])) + VERBOSE_RETURN2(false); + + if (0x01 != (0x01 & buf[ 13 ])) + VERBOSE_RETURN2(false); + + if (hasDTS) + { + if (0x11 != (0xf1 & buf[ 14 ])) + { + if (0x21 == (0xf1 & buf[ 14 ])) + { + // accept streams, that start with '0010' instead of '0001'. + } + else if (0xa1 == (0xf1 & buf[ 14 ])) + { + // accept streams, that start with '1010' instead of '0001'. + } + else + { +fprintf(stderr, "buf:"); for (int i = 0; i < 6 + 3 + hdl; i++) fprintf(stderr, " %02x", buf[i]); fprintf(stderr, "\n"); + VERBOSE_RETURN2(false); + } + } + +// accept streams that have no marker bits set +// if (0x01 != (0x01 & buf[ 16 ])) +// VERBOSE_RETURN2(false); + +// if (0x01 != (0x01 & buf[ 18 ])) +// VERBOSE_RETURN2(false); + } +/* + fprintf(stderr, " %02x %02x %02x %02x %02x\n" + , buf[ 9 ] + , buf[ 10 ] + , buf[ 11 ] + , buf[ 12 ] + , buf[ 13 ]); +*/ + int64_t _pts = ((int64_t)(0x0e & buf[ 9 ])) << 29 + | (0xff & buf[ 10 ]) << 22 + | (0xfe & buf[ 11 ]) << 14 + | (0xff & buf[ 12 ]) << 7 + | (0xfe & buf[ 13 ]) >> 1; + + if (0 == _pts) + return false; + +// if (!IsNotVideoIorPframe(buf, len)) // only PTS of I and P frames are progressive in time +// return false; + + pts = _pts; + + return true; + } + +// VERBOSE_RETURN2(false); + return false; + } +/* + static bool stripPTSandDTS(unsigned char *buf0, int len0) + { + while (len0 > 0) + { + while (len0 > 3 + && 0x00 == buf0[ 0 ] + && 0x00 == buf0[ 1 ] + && 0x00 == buf0[ 2 ]) + { + buf0++; + len0--; + } + + if (3 == len0 + && 0x00 == buf0[ 0 ] + && 0x00 == buf0[ 1 ] + && 0x00 == buf0[ 2 ]) + { + break; + } + + if (len0 < 6) + VERBOSE_RETURN1(false); + + if (0x00 != buf0[ 0 ] + || 0x00 != buf0[ 1 ] + || 0x01 != buf0[ 2 ]) + { + VERBOSE_RETURN1(false); + } + + if (0xe0 != (0xf0 & buf0[ 3 ]) // video + && 0xc0 != (0xe0 & buf0[ 3 ]) // audio + && 0xbd != (0xff & buf0[ 3 ]) // dolby + && 0xbe != (0xff & buf0[ 3 ]) // padding (DVD) + && 0xba != (0xff & buf0[ 3 ]) // system layer: pack header + && 0xbb != (0xff & buf0[ 3 ]) // system layer: system header + && 0xb9 != (0xff & buf0[ 3 ])) // system layer: stream end + { +//fprintf(stderr, "buf0[ 3 ]: %02x\n", buf0[ 3 ]); + VERBOSE_RETURN1(false); + } + + int len = (6 + buf0[ 4 ] * 0x100 + buf0[ 5 ]); + if (0xba == buf0[ 3 ]) // pack header has fixed length + { + if (0x00 == (0xc0 & buf0[ 4 ])) // MPEG 1 + len = 12; + else // MPEG 2 + len = 14 + (buf0[ 13 ] & 0x07); + } + else if (0xb9 == buf0[ 3 ]) // stream end has fixed length + { + len = 4; + } + + if (len > len0) + VERBOSE_RETURN1(false); + + unsigned char *buf = buf0; + buf0 += len; + len0 -= len; + +// if (len0 != 0) +// VERBOSE_NOP(); + + if (0xbe == (0xff & buf[ 3 ]) // padding (DVD) + || 0xba == (0xff & buf[ 3 ]) // system layer: pack header + || 0xbb == (0xff & buf[ 3 ]) // system layer: system header + || 0xb9 == (0xff & buf[ 3 ])) // system layer: stream end + { + continue; + } + + if (len < (6 + 3)) + VERBOSE_RETURN2(false); + + if (0x80 != (0xc0 & buf[ 6 ])) // MPEG1 + { + bool ok = false; + + do // ... while (false); + { + int o = 0; + + for (int i = 0; i < 16; i++) + { + if (buf[ 6 + o ] != 0xff) + break; + + if (len < (6 + ++o)) + VERBOSE_RETURN2(false); + } + + if (0x40 == (0xc0 & buf[ 6 + o ])) + o += 2; + + if (len < (6 + o)) + VERBOSE_RETURN2(false); + + if (0x31 == (0xf1 & buf[ 6 + o + 0 ])) + { + if (0x01 != (0x01 & buf[ 6 + o + 2 ])) + VERBOSE_RETURN2(false); + + if (0x01 != (0x01 & buf[ 6 + o + 4 ])) + VERBOSE_RETURN2(false); + + if (0x11 != (0xf1 & buf[ 6 + o + 5 + 0 ])) + VERBOSE_RETURN2(false); + + if (0x01 != (0x01 & buf[ 6 + o + 5 + 2 ])) + VERBOSE_RETURN2(false); + + if (0x01 != (0x01 & buf[ 6 + o + 5 + 4 ])) + VERBOSE_RETURN2(false); + + buf[ 6 + o + 0 ] = 0xff; + buf[ 6 + o + 1 ] = 0xff; + buf[ 6 + o + 2 ] = 0xff; + buf[ 6 + o + 3 ] = 0xff; + buf[ 6 + o + 4 ] = 0xff; + + buf[ 6 + o + 5 + 0 ] = 0xff; + buf[ 6 + o + 5 + 1 ] = 0xff; + buf[ 6 + o + 5 + 2 ] = 0xff; + buf[ 6 + o + 5 + 3 ] = 0xff; + buf[ 6 + o + 5 + 4 ] = 0x0f; + + ok = true; + } + else if (0x21 == (0xf1 & buf[ 6 + o + 0 ])) + { + if (0x01 != (0x01 & buf[ 6 + o + 2 ])) + VERBOSE_RETURN2(false); + + if (0x01 != (0x01 & buf[ 6 + o + 4 ])) + VERBOSE_RETURN2(false); + + buf[ 6 + o + 0 ] = 0xff; + buf[ 6 + o + 1 ] = 0xff; + buf[ 6 + o + 2 ] = 0xff; + buf[ 6 + o + 3 ] = 0xff; + buf[ 6 + o + 4 ] = 0x0f; + + ok = true; + } + else if (0x0f == (0xff & buf[ 6 + o + 0 ])) + { + ok = true; + } + + if (ok) + break; + + for (int i = 0; i < 30; i++) + xfprintf(stderr, "%02x ", buf[ i ]); + xfprintf(stderr, "\n"); + + VERBOSE_RETURN2(false); + } + while (false); + + if (ok) + continue; + } + + if (0x40 == (0xc0 & buf[ 7 ])) + VERBOSE_RETURN2(false); + + if (0x00 == (0xc0 & buf[ 7 ])) + continue; + +// ignore +// if (0x00 != (0x3f & buf[ 7 ])) +// VERBOSE_RETURN2(false); + + bool hasPTS = (0 != (0x80 & buf[ 7 ])); + bool hasDTS = (0 != (0x40 & buf[ 7 ])); + + unsigned char hdl = buf[ 8 ]; + + if (hdl < ((hasPTS + hasDTS) * 5)) + VERBOSE_RETURN2(false); + + if (len < (6 + 3 + hdl)) + VERBOSE_RETURN2(false); + + if ((0x20 * hasPTS + 0x10 * hasDTS + 0x01) != (0xf1 & buf[ 9 ])) + { + if ((0x20 * hasPTS + 0x00 * hasDTS + 0x01) != (0xf1 & buf[ 9 ])) + { + // accept streams, that start with '00X0' instead of '00X1'. + } + else if ((0x00 * hasPTS + 0x10 * hasDTS + 0x01) != (0xf1 & buf[ 9 ])) + { + // accept streams, that start with '000X' instead of '001X'. + } + else + { + VERBOSE_RETURN2(false); + } + } + + if (0x01 != (0x01 & buf[ 11 ])) + VERBOSE_RETURN2(false); + + if (0x01 != (0x01 & buf[ 13 ])) + VERBOSE_RETURN2(false); + + if (hasDTS) + { + if (0x11 != (0xf1 & buf[ 14 ])) + VERBOSE_RETURN2(false); + + if (0x01 != (0x01 & buf[ 16 ])) + VERBOSE_RETURN2(false); + + if (0x01 != (0x01 & buf[ 18 ])) + VERBOSE_RETURN2(false); + } + + buf[ 7 ] &= 0x3f; + + for (int i = 9; i < (9 + ((hasPTS + hasDTS) * 5)); i++) + buf[ i ] = 0xff; + } + + return true; + } +*/ + static uchar padding[ 6 + 0xffff ] = + { + 0x00 + , 0x00 + , 0x01 + , 0xbe + , 0xff + , 0xff + }; + + int cXineDevice::PushOut() + { + uchar *Data = padding; + int Length = sizeof (padding); + + return PlayCommon3(Data, Length, -1); + } + +//static bool blahblah = false; + + void cXineDevice::StillPicture(const uchar *Data, int Length) + { +#if APIVERSNUM >= 10701 + if (Length > 0 && Data[0] == 0x47) + { +const uchar *tsData = Data; +const int tsLength = Length; +uchar *pesData = (uchar *)malloc(Length); +int pesLength = 0; +uchar tsHead[4] = { 0xff, 0xff, 0xff, 0xff }; +VDR172::cRemux *vRemux = 0; +cPatPmtParser patPmtParser; +while (Length >= TS_SIZE) +{ + if (TsHasPayload(Data)) + { + int plo = TsPayloadOffset(Data); + if (plo < Length) + { + int pid = TsPid(Data); + if (pid == 0) + patPmtParser.ParsePat(Data, TS_SIZE); + else if (pid == patPmtParser.PmtPid()) + patPmtParser.ParsePmt(Data, TS_SIZE); + else if (pid == patPmtParser.Vpid()) + { + if (!vRemux) + { + vRemux = new VDR172::cRemux(patPmtParser.Vpid(), 0, 0, 0); + vRemux->SetTimeouts(0, 0); + } + + memcpy(tsHead, Data, sizeof (tsHead)); + vRemux->Put(Data, TS_SIZE); + + int n = 0; + uchar *p = vRemux->Get(n); + if (p) + { + memcpy(pesData + pesLength, p, n); + pesLength += n; + vRemux->Del(n); + } + } + } + } + +Data += TS_SIZE; +Length -= TS_SIZE; +} + +if (!vRemux) +{ + free(pesData); + return; +} + +uchar tsTail[TS_SIZE]; +memset(tsTail + sizeof (tsHead), 0xff, sizeof (tsTail) - sizeof (tsHead)); +memcpy(tsTail, tsHead, sizeof (tsHead)); +tsTail[1] &= ~TS_ERROR; +tsTail[1] |= TS_PAYLOAD_START; +tsTail[3] &= ~TS_SCRAMBLING_CONTROL; +tsTail[3] |= TS_ADAPT_FIELD_EXISTS; +tsTail[3] |= TS_PAYLOAD_EXISTS; +tsTail[3] = (tsTail[3] & 0xf0) | (((tsTail[3] & TS_CONT_CNT_MASK) + 1) & TS_CONT_CNT_MASK); +tsTail[4] = TS_SIZE - 5 - 13; +tsTail[5] = 0x00; +tsTail[TS_SIZE - 13] = 0x00; +tsTail[TS_SIZE - 12] = 0x00; +tsTail[TS_SIZE - 11] = 0x01; +tsTail[TS_SIZE - 10] = pesData[3]; +tsTail[TS_SIZE - 9] = 0x00; +tsTail[TS_SIZE - 8] = 0x07; +tsTail[TS_SIZE - 7] = 0x80; +tsTail[TS_SIZE - 6] = 0x00; +tsTail[TS_SIZE - 5] = 0x00; +tsTail[TS_SIZE - 4] = 0x00; +tsTail[TS_SIZE - 3] = 0x00; +tsTail[TS_SIZE - 2] = 0x01; +tsTail[TS_SIZE - 1] = VDR172::cRemux::IsFrameH264(pesData, pesLength) ? 10 : 0xb7; + +if (0) +{ +FILE *ff = fopen("/tmp/still.ts", "wb"); +fwrite(tsData, 1, tsLength, ff); +fwrite(tsTail, 1, TS_SIZE, ff); +fclose(ff); +} + + vRemux->Put(tsTail, TS_SIZE); + + int n = 0; + uchar *p = vRemux->Get(n); + if (p) + { + memcpy(pesData + pesLength, p, n); + pesLength += n; + vRemux->Del(n); + } + +if (0) +{ +FILE *ff = fopen("/tmp/still.pes", "wb"); +fwrite(pesData, 1, pesLength, ff); +fclose(ff); +} + +delete vRemux; + +StillPicture(pesData, pesLength); +free(pesData); + return; + } +#endif + + xfprintf(stderr, "StillPicture: %p, %d\n", Data, Length); + + if (0) + { + for (int i = 0; i < Length - 3; i++) + { + if (i != 0 + && Data[ i + 0 ] == 0x00 + && Data[ i + 1 ] == 0x00 + && Data[ i + 2 ] == 0x01) + { + xfprintf(stderr, "\n"); + } + + xfprintf(stderr, "%02x ", Data[ i ]); + } + + for (int i = Length - 3; i < Length; i++) + { + xfprintf(stderr, "%02x ", Data[ i ]); + } + + xfprintf(stderr, "\n"); + } + + const int maxPackets = 3; + uchar pes[ maxPackets * (6 + 0xffff) ]; + static const uchar header[ 6 + 3 ] = { 0x00, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x80, 0x00, 0x00 }; + + do // ... while (false); + { + if (Length < 6) + { + VERBOSE_NOP(); + break; + } + + if (0x00 != Data[ 0 ] + || 0x00 != Data[ 1 ] + || 0x01 != Data[ 2 ]) + { + VERBOSE_NOP(); + break; + } + + if (0xe0 != (0xf0 & Data[ 3 ]) // video + && 0xc0 != (0xe0 & Data[ 3 ]) // audio + && 0xbd != (0xff & Data[ 3 ]) // dolby + && 0xbe != (0xff & Data[ 3 ])) // padding (DVD) + { + if (Length > maxPackets * (0xffff - 3)) + { + VERBOSE_NOP(); + break; + } + + int todo = Length; + const uchar *src = Data; + uchar *dst = pes; + + while (todo > 0) + { + ::memcpy(dst, header, sizeof (header)); + + int bite = todo; + + if (bite > 0xffff - 3) + bite = 0xffff - 3; + + todo -= bite; + + dst[ 4 ] = 0xff & ((bite + 3) >> 8); + dst[ 5 ] = 0xff & (bite + 3); + + ::memcpy(dst + sizeof (header), src, bite); + + Length += sizeof (header); + dst += sizeof (header) + bite; + src += bite; + } + + Data = pes; + } + } + while (false); + +//NNN stripPTSandDTS((uchar *)Data, Length); + + ts = 0; + + m_xineLib.execFuncTrickSpeedMode(false); + m_xineLib.execFuncSetSpeed(100.0); + m_xineLib.execFuncStillFrame(); + m_xineLib.execFuncWait(); + + f = 0; + + m_xineLib.pause(false); + +// PushOut(); + +//blahblah = true; +#if APIVERSNUM >= 10704 +uchar pesTail[] = { 0x00, 0x00, 0x01, Data[3], 0x00, 3 + 4, +0x80, 0x00, 0x00, +0x00, 0x00, 0x01, VDR172::cRemux::IsFrameH264(Data, Length) ? 10 : 0xb7 +}; +#endif + + for (int i = 0; i < 1 /* 4 */; i++) + { +//fprintf(stderr, " (%d) ", i); + int r = PlayVideo1(Data, Length, true); + if (r < 0) + return; +#if APIVERSNUM >= 10704 + r = PlayVideo1(pesTail, sizeof (pesTail), true); + if (r < 0) + return; +#endif + } +/* +FILE *ff = fopen("/tmp/still.pes", "wb"); +fwrite(Data, 1, Length, ff); +fclose(ff); +*/ + PushOut(); +// m_xineLib.execFuncFlush(0); + m_xineLib.execFuncFlush(); + +// ::fprintf(stderr, "------------\n"); + LOG_ME(::fprintf(stderr, "------------\n");) + } + + static bool softStartPoll(cXineLib &xineLib, cPoller &poller, const int timeout, const bool result); + + bool cXineDevice::Poll(cPoller &Poller, int TimeoutMs /* = 0 */) + { + cMutexLock lock(&softStartMutex); + + if (softStartState != sIdle) + return true; + + if (m_xineLib.Poll(Poller, TimeoutMs)) + return softStartPoll(m_xineLib, Poller, TimeoutMs, true); + + return softStartPoll(m_xineLib, Poller, TimeoutMs, false); + } + + static bool jw = false; + + bool cXineDevice::Flush(int TimeoutMs /* = 0 */) + { + const bool jw0 = jw; + + m_xineLib.pause(false); + + if (!jw0) + { + int r = PushOut(); + if (r < 0) + return true; + } + + bool retVal = m_xineLib.execFuncFlush(TimeoutMs, jw0); + + if (!retVal) + xfprintf(stderr, jw0 ? "f" : "F"); + + jw = true; + + return retVal; + } + + static bool dumpAudio(const char *proc, const uchar *Data, int Length) + { + return false; + + nextPacket: + if (Length == 0) + return true; +/* + fprintf(stderr + , "%s: " + , proc); +*/ + if (Length < 6) + VERBOSE_RETURN0(false); + + if (0x00 != Data[ 0 ] + || 0x00 != Data[ 1 ] + || 0x01 != Data[ 2 ]) + { + VERBOSE_RETURN3(false); + } + + int l = Data[ 4 ] * 0x0100 + Data[ 5 ]; + if (Length < (6 + l)) + VERBOSE_RETURN0(false); + + if (0xe0 == (Data[ 3 ] & 0xf0) //video + || 0xc0 == (Data[ 3 ] & 0xe0) //audio + || 0xbe == Data[ 3 ]) //padding + { + Data += (6 + l); + Length -= (6 + l); + goto nextPacket; + } + + // if (0xbd != Data[ 3 ]) //private (dolby, pcm) + // VERBOSE_RETURN0(false); + +// fprintf(stderr, "private "); + + if (l < (3 + 0 + 2)) + VERBOSE_RETURN0(false); + + int h = Data[ 8 ]; + if (l < (3 + h + 2)) + VERBOSE_RETURN0(false); + + xfprintf(stderr + , "%s: " + , proc); + + xfprintf(stderr + , "0x%02x 0x%02x\n" + , Data[ 6 + 3 + h + 0 ] + , Data[ 6 + 3 + h + 1 ]); + + Data += (6 + l); + Length -= (6 + l); + goto nextPacket; + } + + static bool IsVideo(const uchar *Data, int Length) + { + return (Length >= 4 + && 0x00 == Data[ 0 ] + && 0x00 == Data[ 1 ] + && 0x01 == Data[ 2 ] + && 0xe0 == (0xf0 & Data[ 3 ])); + } + +#if APIVERSNUM >= 10701 + static int m_tsVideoPid = 0; +#endif + + int cXineDevice::PlayTsVideo(const uchar *Data, int Length) + { +#if APIVERSNUM >= 10701 + if (Length >= TS_SIZE) + { +#if APIVERSNUM >= 10712 + m_tsVideoPid = PatPmtParser()->Vpid(); +#else + m_tsVideoPid = TsPid(Data); +#endif + } +#endif + return Length; + } + + int cXineDevice::PlayTsAudio(const uchar *Data, int Length) + { + return Length; + } + +#if APIVERSNUM >= 10701 + +static bool m_trampoline = false; + +struct sTrampoline +{ + uchar Header[6]; + const uchar *Data; + int Length; + bool VideoOnly; +}; + static VDR172::cRemux *m_tsVideoRemux = 0; + static VDR172::cRemux *m_tsAudioRemux = 0; +#endif + int cXineDevice::PlayTs(const uchar *Data, int Length, bool VideoOnly /* = false */) + { +#if APIVERSNUM >= 10701 + + cMutexLock lock(&m_playTsMutex); + + int ret = PlayTsImpl(Data, Length, VideoOnly); + +// if (Length > 0 && ret <= 0) { char cmd[500]; sprintf(cmd, "ddd /soft/vdr-" APIVERSION "/bin/vdr %d", getpid()); system(cmd); sleep(10); } //should never happen! + + return ret; + +#endif + return -1; + } + + int cXineDevice::PlayTsImpl(const uchar *Data, int Length, bool VideoOnly /* = false */) + { + if (!Data) + return PlaySingleTs(Data, Length, VideoOnly); + + int ret = 0; + while (Length >= TS_SIZE) + { + int r = PlaySingleTs(Data, TS_SIZE, VideoOnly); + if (r < 0) + { + if (!ret) + return r; + + return ret; + } + + ret += r; + Length -= r; + Data += r; + } + + if (ret) + return ret; + + if (Length > 0) + return PlaySingleTs(Data, Length, VideoOnly); + + return ret; + } + + int cXineDevice::PlaySingleTs(const uchar *Data, int Length, bool VideoOnly /* = false */) + { +#if APIVERSNUM >= 10701 + + if (!Data) + { + m_tsVideoPid = 0; + delete m_tsVideoRemux; + delete m_tsAudioRemux; + m_tsVideoRemux = 0; + m_tsAudioRemux = 0; + } + + int ret = cDevice::PlayTs(Data, Length, VideoOnly); + if (ret <= 0) + return ret; + + if (!TsHasPayload(Data)) + return ret; // silently ignore TS packets w/o payload +if (TsIsScrambled(Data)) fprintf(stderr, "TS is scrambled **************\n"); + int payloadOffset = TsPayloadOffset(Data); + if (payloadOffset >= Length) + return ret; + + int pid = TsPid(Data); + if (pid == 0) + { + m_tsVideoPid = 0; + return ret; + } + + m_trampoline = true; + sTrampoline trampoline = { { 0x00, 0x00, 0x01, 0xe0, 0x00, sizeof (trampoline) - 6 }, Data, Length, VideoOnly }; + PlayPesPacket(trampoline.Header, sizeof (trampoline)); + + return ret; + +#endif + return -1; + } + +//static int aaz = 0; + void cXineDevice::PlayTsTrampoline(const uchar *Data, int Length, bool VideoOnly /* = false */) + { +#if APIVERSNUM >= 10701 +m_trampoline = false; + const tTrackId *track = 0; + int pid = TsPid(Data); + if (pid == m_tsVideoPid) + goto remux; + else if ((track = GetTrack(GetCurrentAudioTrack())) && pid == track->id) + { + if (!VideoOnly || HasIBPTrickSpeed()) + goto remux; + } + return; + +remux: + static int vpid = 0; + static int apid[2] = { 0, 0 }; + static int dpid[2] = { 0, 0 }; + + int v = 0; + int a = 0; + int d = 0; + + v = m_tsVideoPid; + if (!m_tsVideoRemux + || vpid != v) + { +//fprintf(stderr, "vpid: %d, v: %d\n", vpid, v); + + delete m_tsVideoRemux; + + vpid = v; + + m_tsVideoRemux = new VDR172::cRemux(vpid, 0, 0, 0); + m_tsVideoRemux->SetTimeouts(0, 0); + } + + if (ttAudioFirst <= GetCurrentAudioTrack() && GetCurrentAudioTrack() <= ttAudioLast) + a = (track = GetTrack(GetCurrentAudioTrack())) ? track->id : 0; + else + d = (track = GetTrack(GetCurrentAudioTrack())) ? track->id : 0; +/* +if (a || d) +{ +if (aaz) +if (aaz != (d ? 1 : 2)) *(char *)0 =0 ; +} +*/ + if (!m_tsAudioRemux + || *apid != a + || *dpid != d) + { +//fprintf(stderr, "a: %d, d: %d\n", a, d); + + delete m_tsAudioRemux; + + *apid = a; + *dpid = d; + + m_tsAudioRemux = new VDR172::cRemux(0, apid, dpid, 0); + m_tsAudioRemux->SetTimeouts(0, 0); + } + + if (pid == vpid) + { + m_tsVideoRemux->Put(Data, Length); + + int n = 0; + uchar pt; + uchar *p = m_tsVideoRemux->Get(n, &pt); + if (p) + { + if (m_xineLib.isTrickSpeedMode()) + { + if (pt == I_FRAME && VDR172::cRemux::IsFrameH264(p, n)) + { +fprintf(stderr, "TrickSpeedMode: push H.264 SequenceEndCode\n"); + uchar sequenceEndCode[] = { 0x00, 0x00, 0x01, p[3], 0x00, 0x07, 0x80, 0x00, 0x00, 0x00, 0x00, 0x01, 10 }; + PlayVideo(sequenceEndCode, sizeof (sequenceEndCode)); + } + } + int w = PlayVideo(p, n); + m_tsVideoRemux->Del(w); + } + } + else + { + m_tsAudioRemux->Put(Data, Length); + + int n = 0; + uchar *p = m_tsAudioRemux->Get(n); + if (p) + { + int w = PlayAudio(p, n, 0); + m_tsAudioRemux->Del(w); + } + } +#endif + } + + int cXineDevice::PlayVideo(const uchar *Data, int Length) + { +#if APIVERSNUM >= 10701 +if (m_trampoline) +{ + sTrampoline *t = (sTrampoline *)Data; + PlayTsTrampoline(t->Data, t->Length, t->VideoOnly); + return Length; +} +#endif + if (ttt4 == 0) ttt4 = tNow(); +// static FILE *f = fopen("/tmp/egon1", "wb"); +// fwrite(Data, Length, 1, f); + + return PlayVideo1(Data, Length, false); + } + + int cXineDevice::PlayVideo1(const uchar *Data, int Length, const bool stillImageData) + { + LOG_ME(::fprintf(stderr, "V");) + + if (f) + { + LOG_ME(::fprintf(stderr, "<");) + return 0; + } + + if (pmNone == pm) + { + cMutexLock pmMutexLock(&m_pmMutex); + + if (pmNone == pm) + m_pmCondVar.Wait(m_pmMutex); + } + + int retVal = PlayVideo2(Data, Length, stillImageData); + + LOG_ME(::fprintf(stderr, "v");) + + return retVal; + } + + int cXineDevice::PlayCommon2(const uchar *Data, int Length, int64_t ptsForce) + { +/* +if (blahblah) +{ + fprintf(stderr, "blahblah C2"); + for (int i = 0; i < 50 && i < Length; i++) + fprintf(stderr, " %02x", Data[ i ]); + fprintf(stderr, "\n"); +} +*/ + do // ... while (false); + { + if (Length < 6) + { + VERBOSE_NOP(); + break; + } + + if (0x00 != Data[ 0 ] + || 0x00 != Data[ 1 ] + || 0x01 != Data[ 2 ]) + { + VERBOSE_NOP(); + break; + } + + int l = 6 + Data[ 4 ] * 0x0100 + Data[ 5 ]; + if (Length < l) + { + VERBOSE_NOP(); + break; + } + + if (0xe0 != (Data[ 3 ] & 0xf0) //video + && 0xc0 != (Data[ 3 ] & 0xe0) //audio + && 0xbd != Data[ 3 ]) //private (dolby, pcm) + { + VERBOSE_NOP(); + break; + } + + int payloadOffset = 0; + if (AnalyzePesHeader(Data, Length, payloadOffset) <= phInvalid) + { + VERBOSE_NOP(); + break; + } + + if (l <= payloadOffset) + { + // drop short frames +// ::fprintf(stderr, "i"); + return Length; + } + +// if (0xc0 == (Data[ 3 ] & 0xe0)) //audio +// return Length; + } + while (false); + + return PlayCommon3(Data, Length, ptsForce); + } + +typedef long long int lld_t; + +static int xzabc = 0; + + int cXineDevice::PlayCommon3(const uchar *Data, int Length, int64_t ptsForce) + { +// if (!m_settings.LiveTV()) +// { +// VERBOSE_NOP1(); +// return Length; +// } + +// if (xzabc) + if (0) + { + if (0xe0 == (Data[ 3 ] & 0xf0)) //video + { + uchar pt = NO_PICTURE; + cRemux::ScanVideoPacket(Data, Length, 0, pt); + + if (pt != NO_PICTURE) + { + static int64_t last = -1; + int64_t pts = -1; + getPTS(Data, Length, pts); + fprintf(stderr, "** %c ** %lld ** %lld ** %lld **\n", "0IPB4567"[pt], (lld_t)ptsForce, (lld_t)(ptsForce - last), (lld_t)pts); + last = ptsForce; + } + } + if (0xc0 == (Data[ 3 ] & 0xe0)) //audio + { + { + static int64_t last = -1; + int64_t pts = -1; + getPTS(Data, Length, pts); + fprintf(stderr, "\t\t\t\t\t\t\t** %c ** %lld ** %lld ** %lld **\n", 'A', (lld_t)ptsForce, (lld_t)(ptsForce - last), (lld_t)pts); + last = ptsForce; + } + } + } + if (0) + { + int64_t pts = ptsForce; + if (ptsForce > -1 + || getPTS(Data, Length, pts)) + { + xzabc = 0; + int64_t *pPTS = 0; + + if (0xe0 == (Data[ 3 ] & 0xf0)) //video + { + pPTS = &ptsV; + } + else if (0xc0 == (Data[ 3 ] & 0xe0)) //audio + { + pPTS = &ptsA; + } + else if (0xbd == Data[ 3 ]) //private (dolby, pcm) + { + int h = Data[ 6 + 2 ]; + + if (0xa0 == (0xf0 & Data[ 6 + 3 + h + 0 ])) // pcm? + pPTS = &ptsP; + else + pPTS = &ptsD; + } + else + { + xfprintf(stderr, "0x%02x\t", Data[ 3 ]); + VERBOSE_NOP(); + } + + if (pPTS + && *pPTS != pts) + { + *pPTS = pts; + + int64_t ptsX = -1; + m_xineLib.execFuncGetPTS(ptsX); + + int64_t dV = (ptsV != -1 && ptsX != -1) ? ptsV - ptsX : 0; + int64_t dA = (ptsA != -1 && ptsX != -1) ? ptsA - ptsX : 0; + int64_t dP = (ptsP != -1 && ptsX != -1) ? ptsP - ptsX : 0; + int64_t dD = (ptsD != -1 && ptsX != -1) ? ptsD - ptsX : 0; + + int64_t dVA = (ptsV != -1 && ptsA != -1) ? ptsA - ptsV : 0; + int64_t dVD = (ptsV != -1 && ptsD != -1) ? ptsD - ptsV : 0; + + fprintf(stderr, "ptsVideo: %lld, ptsAudio: %lld, ptsPCM: %lld, ptsDolby: %lld, ptsXine: %lld, dVA: %lld, dVD: %lld, dV: %lld, dA: %lld, dP: %lld, dD: %lld\n", (lld_t)ptsV, (lld_t)ptsA, (lld_t)ptsP, (lld_t)ptsD, (lld_t)ptsX, (lld_t)dVA, (lld_t)dVD, (lld_t)dV, (lld_t)dA, (lld_t)dP, (lld_t)dD); + } + } + } + +/* +if (blahblah) +{ + blahblah = false; + fprintf(stderr, "blahblah C3"); + for (int i = 0; i < 50 && i < Length; i++) + fprintf(stderr, " %02x", Data[ i ]); + fprintf(stderr, "\n"); +} +*/ +/* +if (aaz) +{ + if (Data[3] == 0xbd || (Data[3] & 0xe0) == 0xc0) { +fprintf(stderr, "===== %c =====\n", Data[3] == 0xbd ? 'D' : 'A'); +if (aaz != (Data[3] == 0xbd ? 1 : 2)) *(char *)0 = 0; +aaz = 0; + } +} +*/ + int done = 0; + + while (done < Length) + { + int r = m_xineLib.execFuncStream1(Data + done, Length - done); + if (r < 0) + return r; + + done += r; + } + + return done; + } + + int cXineDevice::PlayVideo2(const uchar *Data, int Length, const bool stillImageData) + { +// fprintf(stderr, "D"); + + int done = 0; + + while (done < Length) + { + char ch = 'X'; + + int todo = Length - done; + + if (todo >= 6) + { + if (0x00 == Data[ done + 0 ] + && 0x00 == Data[ done + 1 ] + && 0x01 == Data[ done + 2 ]) + { + int id = Data[ done + 3 ]; + int len = 6 + Data[ done + 4 ] * 0x0100 + Data[ done + 5 ]; + + if (0xba == id) // pack header has fixed length + { + if (0x00 == (0xc0 & Data[ done + 4 ])) // MPEG 1 + len = 12; + else // MPEG 2 + len = 14 + (Data[ done + 13 ] & 0x07); + } + else if (0xb9 == id) // stream end has fixed length + { + len = 4; + } + + if (todo >= len) + { + todo = len; + + if (0xbe == id // padding + || 0xba == id // system layer: pack header + || 0xbb == id // system layer: system header + || 0xb9 == id) // system layer: stream end + { + done += todo; + +// fprintf(stderr, "x"); + continue; + } + + ch = '.'; + } + else + { +// ::fprintf(stderr, "todo: %d, len: %d\t", todo, len); + VERBOSE_NOP(); + ch = '3'; + +// break; + } + } + else + { + VERBOSE_NOP(); + ch = '2'; + } + } + else + { + VERBOSE_NOP(); + ch = '1'; + } + +// fprintf(stderr, "%c", ch); + + int r = PlayVideo3(Data + done, todo, stillImageData); + if (r < 0) + return r; + + done += r; + } + + return done; + } + + static void resetScramblingControl(const uchar *Data, int Length) + { + if (Length < 6) + { + VERBOSE_NOP(); + return; + } + + if (0x00 != Data[ 0 ] + || 0x00 != Data[ 1 ] + || 0x01 != Data[ 2 ]) + { + VERBOSE_NOP1(); + return; + } + + if (0xe0 != (0xf0 & Data[ 3 ]) // video + && 0xc0 != (0xe0 & Data[ 3 ]) // audio + && 0xbd != (0xff & Data[ 3 ]) // dolby + && 0xbe != (0xff & Data[ 3 ]) // padding (DVD) + && 0xba != (0xff & Data[ 3 ]) // system layer: pack header + && 0xbb != (0xff & Data[ 3 ]) // system layer: system header + && 0xb9 != (0xff & Data[ 3 ])) // system layer: stream end + { + VERBOSE_NOP(); + return; + } + + if (0xbe == (0xff & Data[ 3 ]) // padding (DVD) + || 0xba == (0xff & Data[ 3 ]) // system layer: pack header + || 0xbb == (0xff & Data[ 3 ]) // system layer: system header + || 0xb9 == (0xff & Data[ 3 ])) // system layer: stream end + { + return; + } + + int len = (6 + Data[ 4 ] * 0x100 + Data[ 5 ]); + if (len < 6 + 1 + || Length < 6 + 1) + { + VERBOSE_NOP(); + return; + } + + if (0x00 != (0x30 & Data[ 6 ])) + { + if (0x80 == (0xc0 & Data[ 6 ])) // only touch MPEG2 + { + xfprintf(stderr, "reseting PES_scrambling_control: 0x%02x\n", Data[ 6 ]); + + ((uchar *)Data)[ 6 ] &= ~0x30; + } + } + } + + int cXineDevice::PlayVideo3(const uchar *Data, int Length, const bool stillImageData) + { +/* +if (blahblah) +{ + fprintf(stderr, "blahblah V3"); + for (int i = 0; i < 50 && i < Length; i++) + fprintf(stderr, " %02x", Data[ i ]); + fprintf(stderr, "\n"); +} +*/ + resetScramblingControl(Data, Length); + + dumpAudio("Video", Data, Length); + + return PlayCommon(Data, Length, stillImageData); + } + + static double videoFrameDuration(const uchar *Data, int Length) + { + int PesPayloadOffset = 0; + ePesHeader ph = AnalyzePesHeader(Data, Length, PesPayloadOffset); + if (ph < phMPEG1) + return -1; + + if ((Data[ 3 ] & 0xf0) != 0xe0) + return -1; + + if (Length < PesPayloadOffset + 8) + return -1; + + const uchar *p = Data + PesPayloadOffset; + + if (*p++ != 0x00) + return -1; + + if (*p++ != 0x00) + return -1; + + if (*p++ != 0x01) + return -1; + + if (*p++ != 0xb3) + return -1; + + p += 3; + + static const double frameRates[ 16 ] = + { + -1, + 24000/1001.0, + 24, + 25, + 30000/1001.0, + 30, + 50, + 60000/1001.0, + 60, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + }; + + int frameRateIndex = *p & 0x0f; + + if (frameRates[ frameRateIndex ] < 0) + return -1; + + long n = 0, d = 0; + + if (ph == phMPEG2) + { + const uchar *const limit = Data + Length - 10 + 3; + while (p < limit) + { + if (p[ 0 ] != 0x01 || p[ -1 ] != 0x00 || p[ -2 ] != 0x00) + { + p++; + continue; + } + + if (p[ 1 ] != 0xb5) // extension start code + break; + + if ((p[ 2 ] & 0xf0) != 0x10) // sequence extension + break; + + p += 7; + + n = (*p & 0x60) >> 5; + d = (*p & 0x1f); + break; + } + } + + return 90000 / (frameRates[ frameRateIndex ] * (n + 1) / (d + 1)); + } + + static int frameSizes[ 256 ]; + + static double audioFrameDuration(const uchar *Data, int Length) + { + int PesPayloadOffset = 0; + ePesHeader ph = AnalyzePesHeader(Data, Length, PesPayloadOffset); + if (ph < phMPEG1) + return -1; + + if ((Data[ 3 ] & 0xff) == 0xbd) + { + static int samplingFrequencies[ 4 ] = // all values are specified in Hz + { + 48000, 44100, 32000, -1 + }; + + if (PesPayloadOffset + 5 <= Length + && Data[ PesPayloadOffset + 0 ] == 0x0b + && Data[ PesPayloadOffset + 1 ] == 0x77 + && frameSizes[ Data[ PesPayloadOffset + 4 ] ] > 0) + { + if (samplingFrequencies[ Data[ PesPayloadOffset + 4 ] >> 6 ] < 0) + return -1; + + return 90000.0 * 1536 / samplingFrequencies[ Data[ PesPayloadOffset + 4 ] >> 6 ]; + } + else if (PesPayloadOffset + 4 + 5 <= Length + && Data[ PesPayloadOffset + 4 + 0 ] == 0x0b + && Data[ PesPayloadOffset + 4 + 1 ] == 0x77 + && frameSizes[ Data[ PesPayloadOffset + 4 + 4 ] ] > 0) + { + if (samplingFrequencies[ Data[ PesPayloadOffset + 4 + 4 ] >> 6 ] < 0) + return -1; + + return 90000.0 * 1536 / samplingFrequencies[ Data[ PesPayloadOffset + 4 + 4 ] >> 6 ]; + } + else + return -1; + } + + if ((Data[ 3 ] & 0xe0) != 0xc0) + return -1; + + if (Length < PesPayloadOffset + 4) + return -1; + + ulong Header = 0; + Header |= Data[ PesPayloadOffset + 0 ]; + Header <<= 8; + Header |= Data[ PesPayloadOffset + 1 ]; + Header <<= 8; + Header |= Data[ PesPayloadOffset + 2 ]; + Header <<= 8; + Header |= Data[ PesPayloadOffset + 3 ]; + + bool Mpeg2 = (ph == phMPEG2); + + int syncword = (Header & 0xFFF00000) >> 20; + int id = (Header & 0x00080000) >> 19; + int layer = (Header & 0x00060000) >> 17; +// int protection_bit = (Header & 0x00010000) >> 16; + int bitrate_index = (Header & 0x0000F000) >> 12; + int sampling_frequency = (Header & 0x00000C00) >> 10; +// int padding_bit = (Header & 0x00000200) >> 9; +// int private_bit = (Header & 0x00000100) >> 8; +// int mode = (Header & 0x000000C0) >> 6; +// int mode_extension = (Header & 0x00000030) >> 4; +// int copyright = (Header & 0x00000008) >> 3; +// int orignal_copy = (Header & 0x00000004) >> 2; + int emphasis = (Header & 0x00000003); + + if (syncword != 0xFFF) + return -1; + + if (id == 0 && !Mpeg2) // reserved in MPEG 1 + return -1; + + if (layer == 0) // reserved + return -1; + + if (bitrate_index == 0xF) // forbidden + return -1; + + if (sampling_frequency == 3) // reserved + return -1; + + if (emphasis == 2) // reserved + return -1; + + static int samplingFrequencies[ 2 ][ 4 ] = // all values are specified in Hz + { + { 44100, 48000, 32000, -1 }, // MPEG 1 + { 22050, 24000, 16000, -1 } // MPEG 2 + }; + + static int slots_per_frame[ 2 ][ 3 ] = + { + { 12, 144, 144 }, // MPEG 1, Layer I, II, III + { 12, 144, 72 } // MPEG 2, Layer I, II, III + }; + + int mpegIndex = 1 - id; + int layerIndex = 3 - layer; + + // Layer I (i. e., layerIndex == 0) has a larger slot size + int slotSize = (layerIndex == 0) ? 4 : 1; // bytes + + int sf = samplingFrequencies[ mpegIndex ][ sampling_frequency ]; + +//fprintf(stderr, "afd: %.3f ms, PES-Length: %d\n", 1000.0 * 8 * slotSize * slots_per_frame[ mpegIndex ][ layerIndex ] / sf, Length); + + return 90000.0 * 8 * slotSize * slots_per_frame[ mpegIndex ][ layerIndex ] / sf; + } + + static double softStartTime = 0; + static const double softStartSpeedStart = 0.75; + static const double softStartSpeedMin = 0.15; + static double softStartDurationVideoSD = 31 / 25.0; + static double softStartDurationVideoHD = 31 / 25.0; + static double softStartDurationAudio = 31 / 25.0; + static const int softStartMaxSpeedChanges = 20; + static double softStartLastSpeed = -1; + static double softStartSpeedChangeTime = -1; + static int64_t softStartPtsVdr = -1; + static const int64_t softStartLogPtsDelta = 4 * 90000; + static int softStartHitPoll = 0; + static bool softStartNoMetronom = false; + static const double pi = 4 * ::atan(1); + static bool softStartHasVideo = true; + static bool softStartHasAudio = true; + static bool softStartIsVideoHD = false; + + inline double getSoftStartDuration(const bool forVideo) + { + if (forVideo) + return softStartHasVideo ? (softStartIsVideoHD ? softStartDurationVideoHD : softStartDurationVideoSD) : 0; + + return softStartHasAudio ? softStartDurationAudio : 0; + } + + inline double getSoftStartDuration() + { + double dV = getSoftStartDuration(true); + double dA = getSoftStartDuration(false); + return (dV > dA) ? dV : dA; + } + + static double softStartCalcSpeed0(const double /* t */) + { + return 0; + } + + static double softStartCalcSpeed1(const double t) + { + const double p = (1 + ::cos(2 * pi * t)) / 2; + + return softStartSpeedMin + p * ((softStartSpeedStart * (1 - t) + 1.0 * t) - softStartSpeedMin); + } + + static double softStartCalcSpeed2(const double t) + { + double p = 2 * t - 1; + if (p < 0) + p = -p; + +// p = p * p * p; + p = p * p; + + return softStartSpeedMin + p * ((softStartSpeedStart * (1 - t) + 1.0 * t) - softStartSpeedMin); + } + + static double softStartCalcSpeed3(const double t) + { + if (t < 0.25) + return softStartSpeedStart; + + if (t < 0.50) + return softStartSpeedMin + (1 - softStartSpeedMin) * 0 / 3; + + if (t < 0.75) + return softStartSpeedMin + (1 - softStartSpeedMin) * 1 / 3; + + return softStartSpeedMin + (1 - softStartSpeedMin) * 2 / 3; + } + + static double softStartCalcSpeed4(const double t) + { + const double p = (1 + ::cos(pi * (1 + t))) / 2; + + return softStartSpeedMin + (1 - softStartSpeedMin) * p; + } + + static double softStartCalcSpeed(const double t) + { + if (t >= 1) + return 1; + + return softStartCalcSpeed0(t); // choose a method + + (void)softStartCalcSpeed0; + (void)softStartCalcSpeed1; + (void)softStartCalcSpeed2; + (void)softStartCalcSpeed3; + (void)softStartCalcSpeed4; + } + + bool softStartPoll(cXineLib &xineLib, cPoller &poller, const int timeout, bool result) + { + if (softStartState > sIdle) + { + if (result) + { + softStartHitPoll = 0; + } + else if (++softStartHitPoll >= 2) + { + do + { + softStartState = sIdle; + + xineLib.execFuncFirstFrame(); + xineLib.execFuncSetSpeed(100.0); + xineLib.execFuncWait(); + } + while (!xineLib.Poll(poller, timeout, true)); + + softStartHitPoll = 0; + result = true; + } + +// ::fprintf(stderr, "softStartHitPoll: %d, %d\n", result, softStartHitPoll); + } + + return result; + } + + static int64_t vpts, apts, extra0, extra; + static bool useApts; + static bool seenAudio = false; + static bool seenVideo = false; + static bool seenApts = false; + static bool seenVpts = false; + + static const int64_t extra_max = 50 * 90000 / 25; + + static bool oldMode = false; + + static inline int64_t calcPtsDelta(int64_t lhs, int64_t rhs) + { + int64_t delta = lhs - rhs; + + if (delta < -(1ll << 32)) + delta += 1ll << 33; + else if (delta > +(1ll << 32)) + delta -= 1ll << 33; + + return delta; + } + + static inline double softStartCalcQ0(int64_t vdr, int64_t xine, bool queued, bool forVideo, int64_t hyst) + { + if (vdr < 0 || xine < 0) + return 0; + + double delta = calcPtsDelta(vdr, xine) / 90000.0; + if (delta <= 0) + return 0; + + if (delta >= softStartLogPtsDelta / 90000.0) + return queued ? 0 : 1; + + if (delta >= (getSoftStartDuration(forVideo) + hyst / 90000.0)) + return 1; + + return delta / (getSoftStartDuration(forVideo) + hyst / 90000.0); + } + + static bool gotQ1 = false; + static int64_t hystQ = 0; + static int64_t hystQ1 = 0; + static bool vdrTime100reload = false; + + static inline double softStartCalcQ(int64_t vdr, int64_t xine, bool queued, bool forVideo) + { + return softStartCalcQ0(vdr, xine, queued, forVideo, hystQ); + } + + static inline double softStartCalcQ(int64_t vdrVideo, int64_t vdrAudio, int64_t xine, bool queued) + { + double qV = softStartHasVideo ? softStartCalcQ(vdrVideo, xine, queued, true) : 1; + double qA = softStartHasAudio ? softStartCalcQ(vdrAudio, xine, queued, false) : 1; + double q = (qV < qA) ? qV : qA; + + bool gQ1 = (q >= 1); + if (gQ1 != gotQ1) + { + gotQ1 = gQ1; + + if (gotQ1) + { + hystQ = 0; + } + else + { + vdrTime100reload = true; + hystQ = (hystQ1 += 90000 / 25); + } + } + +//fprintf(stderr, "q: %.3lf, gq1: %d, hystQ: %lld, hystQ1: %lld, vdr: %lld, xine: %lld, d: %lld\n", q, gotQ1, hystQ, hystQ1, vdr, xine, vdr - xine); + return q; + } + + static int64_t vdrPTSLast = -2; + static int64_t vdrAptsLast = -1; + static int64_t vdrVptsLast = -1; + + static double vdrVptsCalced = -1; + static double vdrAptsCalced = -1; + static double vdrVduration = -1; + static double vdrAduration = -1; + + static double vdrVptsBuffered[ 2 ] = { -1, -1 }; + + static bool getPTS(const uchar *Data, int Length, int64_t &pts, bool isVideo, bool isAudio, double &vptsCalced, double &aptsCalced, double &vDuration, double &aDuration, double vptsBuffered[2]) + { + uchar pt = NO_PICTURE; + + if (isVideo) + { + cRemux::ScanVideoPacket(Data, Length, 0, pt); + + if (pt != NO_PICTURE && vDuration > 0 && vptsBuffered[ 1 ] > 0) + vptsBuffered[ 1 ] += vDuration; + } + + bool retVal = false; + + if (getPTS(Data, Length, pts)) + { + if (isAudio) + { + aptsCalced = pts; + + double duration = audioFrameDuration(Data, Length); + if (duration >= 0) + aDuration = duration; + } + + if (isVideo) + { + if (pt == B_FRAME) + vptsCalced = pts; + else if (pt != NO_PICTURE) + vptsBuffered[ 1 ] = pts; + + double duration = videoFrameDuration(Data, Length); + if (duration >= 0) + vDuration = duration; + + if (pt != B_FRAME) + goto other; + } + + return true; + } + else + { + if (isAudio) + { + double duration = audioFrameDuration(Data, Length); + + bool frameStart = (duration > -1); + + if (aptsCalced > -1 && aDuration > -1 && frameStart) + { + aptsCalced += aDuration; + while (aptsCalced >= 0x200000000ull) + aptsCalced -= 0x200000000ull; + + pts = (int64_t)aptsCalced; + + retVal = true; + } + + if (duration >= 0) + aDuration = duration; + } + + if (isVideo) + { +other: + double dur = vDuration; + + if (pt != B_FRAME && pt != NO_PICTURE) + { + vptsCalced = vptsBuffered[ 0 ]; + vptsBuffered[ 0 ] = vptsBuffered[ 1 ]; + dur = 0; + } + + bool frameStart = (pt != NO_PICTURE); + + if (vptsCalced > -1 && dur > -1 && frameStart) + { + vptsCalced += dur; + while (vptsCalced >= 0x200000000ull) + vptsCalced -= 0x200000000ull; + + pts = (int64_t)vptsCalced; + + retVal = true; + } + + double duration = videoFrameDuration(Data, Length); + if (duration >= 0) + vDuration = duration; + } + + return retVal; + } + } + + static double vdrTime100 = -1; + + int cXineDevice::PlayCommon(const uchar *Data, int Length, const bool stillImageData) + { + const bool isAudio = !IsVideo(Data, Length); + const bool isVideo = !isAudio && !stillImageData; +//if (isAudio && ts) fprintf(stderr, "A"); + if ((stillImageData || m_xineLib.isTrickSpeedMode()) + && isAudio) + { +// ::fprintf(stderr, "x"); + return Length; + } +/* + if (stillImageData) + fprintf(stderr, "writing: %d\n", Length - 6); +*/ + if (findVideo && (isVideo || isAudio)) + { + findVideo = false; + foundVideo = isVideo; +//xfprintf(stderr, "foundVideo: %d\n", foundVideo); + } + +if (0 && isAudio) { +static int64_t opts = -1; +static int64_t xpts = -1; +getPTS(Data, Length, xpts, isVideo, isAudio, vdrVptsCalced, vdrAptsCalced, vdrVduration, vdrAduration, vdrVptsBuffered); +//fprintf(stderr, "audio: %.3lf, %d, %lld, %lld\n", audioFrameDuration(Data, Length), Length, xpts, xpts - opts); +opts = xpts; +} +if (0 && isVideo) { +static int64_t opts = -1; +static int64_t xpts = -1; +getPTS(Data, Length, xpts, isVideo, isAudio, vdrVptsCalced, vdrAptsCalced, vdrVduration, vdrAduration, vdrVptsBuffered); +//fprintf(stderr, "video: %.3lf, %d, %lld, %lld\n", videoFrameDuration(Data, Length), Length, xpts, xpts - opts); +opts = xpts; +} + + int64_t ptsForce = -1; + + { + cMutexLock lock(&softStartMutex); + + if (softStartTrigger + && !stillImageData) + { + softStartNoMetronom = (sstNoMetronom == softStartTrigger); + softStartTrigger = sstNone; + + softStartState = sInitiateSequence; +//fprintf(stderr, "/////////////////////////////\n"); +// ::fprintf(stderr, "#(%d,%d)", ::getpid(), pthread_self()); + } + + if (stillImageData) + { + softStartLastSpeed = -1; + } + else if (softStartState > sIdle) + { + timeval tv; + ::gettimeofday(&tv, 0); + + const double now = (tv.tv_sec + tv.tv_usec / 1.0e+6); + + if (softStartState == sInitiateSequence) + { + xfprintf(stderr, "["); + + softStartDurationVideoSD = m_settings.GetModeParams()->m_prebufferFramesVideoSD / 25.0; + softStartDurationVideoHD = m_settings.GetModeParams()->m_prebufferFramesVideoHD / 25.0; + softStartDurationAudio = m_settings.GetModeParams()->m_prebufferFramesAudio / 25.0; + softStartHasVideo = false; + softStartHasAudio = false; + softStartIsVideoHD = false; + + softStartTime = now; + + softStartSpeedChangeTime = -1; + softStartLastSpeed = -1; + softStartPtsVdr = -1; + softStartHitPoll = 0; + + softStartState = sStartSequence; + + vpts = -1; + apts = -1; + extra0 = 0; + extra = 0; + + useApts = 1; + + seenAudio = false; + seenVideo = false; + seenApts = false; + seenVpts = false; + + vdrPTSLast = -2; + vdrAptsLast = -1; + vdrVptsLast = -1; + + vdrAptsCalced = -1; + vdrVptsCalced = -1; + vdrAduration = -1; + vdrVduration = -1; + + vdrVptsBuffered[ 0 ] = -1; + vdrVptsBuffered[ 1 ] = -1; + + vdrTime100 = -1; + vdrTime100reload = false; + + gotQ1 = false; + hystQ = hystQ1 = 90000 * m_settings.GetModeParams()->m_prebufferHysteresis / 25; + } + + { + char packetType = 0; + char packetTypeOnce = 0; + + if (isVideo) + { + if (!seenVideo) + { + softStartHasVideo = true; + softStartIsVideoHD = cRemux::IsFrameH264(Data, Length); +//fprintf(stderr, "\nH264: %d\n", softStartIsVideoHD); + + seenVideo = true; +// ::fprintf(stderr, "seen video\n"); +// xfprintf(stderr, "v"); + packetTypeOnce = 'v'; + } + + packetType = 'v'; + } + else if (isAudio) + { + if (!seenAudio) + { + softStartHasAudio = true; + + audioSeen = true; + seenAudio = true; +// ::fprintf(stderr, "seen audio\n"); +// xfprintf(stderr, "a"); + packetTypeOnce = 'a'; + } + + packetType = 'a'; + } + + int64_t pts = 0; + if (getPTS(Data, Length, pts, isVideo, isAudio, vdrVptsCalced, vdrAptsCalced, vdrVduration, vdrAduration, vdrVptsBuffered)) + { + ptsForce = pts; + + if (isVideo) + { + if (!seenVpts) + { + seenVpts = true; +// ::fprintf(stderr, "seen video pts\n"); +// xfprintf(stderr, "V"); + packetTypeOnce = 'V'; + } + + packetType = 'V'; + + vpts = pts; + + if (apts > -1) + { + int64_t delta = calcPtsDelta(vpts, apts); + if (delta < 0) + delta = - delta; + + if (extra0 < delta) + { + extra0 = delta; + +// ::fprintf(stderr, "max. A/V delta: %lld pts => total extra buffering: %d frames", extra0, (int)(extra0 * 25 / 90000)); + + extra = extra0; + + if (extra > extra_max) + { + extra = extra_max; + +// ::fprintf(stderr, ", limited to %d frames", (int)(extra * 25 / 90000)); + } + +// ::fprintf(stderr, "\n"); + if (oldMode) + xfprintf(stderr, "+%d", (int)(extra * 25 / 90000)); + } + } + +// ::fprintf(stderr, "video: v: %lld, a: %lld, d: %lld, e: %lld\n", vpts, apts, vpts - apts, extra); + } + else if (isAudio) + { + if (!seenApts) + { + seenApts = true; +// ::fprintf(stderr, "seen audio pts\n"); +// xfprintf(stderr, "A"); + packetTypeOnce = 'A'; + } + + packetType = 'A'; + + apts = pts; + + if (vpts > -1) + { + int64_t delta = calcPtsDelta(vpts, apts); + if (delta < 0) + delta = - delta; + + if (extra0 < delta) + { + extra0 = delta; + +// ::fprintf(stderr, "max. A/V delta: %lld pts => total extra buffering: %d frames", extra0, (int)(extra0 * 25 / 90000)); + + extra = extra0; + + if (extra > extra_max) + { + extra = extra_max; + +// ::fprintf(stderr, ", limited to %d frames", (int)(extra * 25 / 90000)); + } + +// ::fprintf(stderr, "\n"); + if (oldMode) + xfprintf(stderr, "+%d", (int)(extra * 25 / 90000)); + } + } + +// ::fprintf(stderr, "audio: v: %lld, a: %lld, d: %lld, e: %lld\n", vpts, apts, vpts - apts, extra); + } + } + +//xfprintf(stderr, "%s%c", getFrameType(Data, Length), packetType); packetTypeOnce = 0; //ZZZ +// + if (packetTypeOnce) + xfprintf(stderr, "%c", packetTypeOnce); + } + + if ((seenVideo && !seenVpts) + || (seenAudio && !seenApts)) + { + softStartTime = now; + } + + if (softStartState >= sStartSequence) + { + if (isVideo) + useApts = false; + + if (useApts + || isVideo) + { + softStartPtsVdr = ptsForce; +// getPTS(Data, Length, softStartPtsVdr); + + if (softStartPtsVdr != -1) + { + bool queued = false; + int64_t ptsXine = -1; + m_xineLib.execFuncGetPTS(ptsXine, 0, &queued); + + const int64_t delta = (ptsXine >= 0) ? calcPtsDelta(softStartPtsVdr, ptsXine) : 0; + if (softStartState == sStartSequence + || delta < -softStartLogPtsDelta + || (delta > +softStartLogPtsDelta && queued)) + { +// if (softStartState != sStartSequence) +// ::fprintf(stderr, "SoftStart: ptsVdr: %12"PRId64", ptsXine: %12"PRId64", delta: %12"PRId64", queued: %d\n", softStartPtsVdr, ptsXine, delta, queued); + +//AAA m_xineLib.execFuncStart(); +//AAA m_xineLib.execFuncWait(); + + if (!softStartNoMetronom) + { + xfprintf(stderr, "M"); +// xfprintf(stderr, " %12" PRId64 ", %12" PRId64 ", %12" PRId64 "\n", delta, softStartPtsVdr, ptsXine); + +//ZZZ m_xineLib.execFuncMetronom(softStartPtsVdr); +//ZZZ m_xineLib.execFuncWait(); + } + + softStartTime = now; + + softStartState = sContinueSequence; + } + } + } + } + +// if (softStartState <= sStartSequence) +// stripPTSandDTS((uchar *)Data, Length); + + m_xineLib.execFuncFirstFrame(); + + int64_t vdrPTS = -1; + + if ((seenVideo && vpts <= -1) + || (seenAudio && apts <= -1)) + { + } + else if (vpts > -1) + { + if (apts > -1 && vpts > apts) + vdrPTS = apts; + else + vdrPTS = vpts; + } + else if (apts > -1) + vdrPTS = apts; + + bool queued = false; + int64_t xinePTS = -1; + m_xineLib.execFuncGetPTS(xinePTS, 0, &queued); + + const double totalDuration = (getSoftStartDuration() + extra / 90000.0); + const double q = oldMode ? ((now - softStartTime) / totalDuration) : softStartCalcQ(vpts, apts, xinePTS, queued); + double p = softStartCalcSpeed(q); + + if (!oldMode) + { + if (vdrPTSLast == vdrPTS || vdrVptsLast > vpts || vdrAptsLast > apts) + p = softStartLastSpeed / 100; + else + vdrPTSLast = vdrPTS; + + if (apts != vdrAptsLast || vpts != vdrVptsLast) + { +// xfprintf(stderr, "p: %.3lf, DA: %lld, DV: %lld, DC: %lld\n", p, (apts > -1) ? (apts - xinePTS) : -1, (vpts > -1) ? (vpts - xinePTS) : -1, (vdrPTS > - 1) ? (vdrPTS - xinePTS) : -1); +// xfprintf(stderr, "(%.1lf|%.1lf)", 25 * ((apts > -1) ? ((apts - xinePTS) / 90000.0) : 0.0), 25 * ((vpts > -1) ? ((vpts - xinePTS) / 90000.0) : 0.0)); + } + + vdrVptsLast = vpts; + vdrAptsLast = apts; + } + + double speed = (p > 0) ? (100.0 * p) : 12.5; + + if (speed >= 100.0 || xinePTS == -1) + { + bool newlineRequired = false; + + if (vdrTime100 < 0) + { +ttt6 = tNow(); +/* +fprintf(stderr, "+++++ %.3lf ms, %.3lf ms, %.3lf ms, (%.3lf ms, %.3lf ms, %.3lf ms) +++++\n" +, (ttt1 - ttt0) * 1000.0 +, (ttt2 - ttt1) * 1000.0 +, (ttt3 - ttt2) * 1000.0 +, (ttt4 - ttt3) * 1000.0 +, (ttt5 - ttt3) * 1000.0 +, (ttt6 - ttt3) * 1000.0 +); +*/ + vdrTime100 = now; + + xfprintf(stderr, "]"); + newlineRequired = true; + } + + speed = 100.0; + + if (vdrTime100reload) + { + vdrTime100reload = false; + + vdrTime100 = now; + } + + if (xinePTS < 0) + softStartState = sIdle; + else if ((now - vdrTime100) >= m_settings.GetModeParams()->m_monitoringDuration) + { + if (m_settings.GetModeParams()->MonitoringContinuous()) + hystQ1 = 90000 * (m_settings.GetModeParams()->m_prebufferHysteresis - 1) / 25; + else + softStartState = sIdle; + } + + if ((softStartState == sIdle || speed != softStartLastSpeed) && vdrPTS > -1 && xinePTS > -1) + { + xfprintf(stderr, "buffered %.1lf frames (v:%.1lf, a:%.1lf)", calcPtsDelta(vdrPTS, xinePTS) / 90000.0 * 25, (vpts > -1) ? (calcPtsDelta(vpts, xinePTS) / 90000.0 * 25) : 0.0, (apts > -1) ? (calcPtsDelta(apts, xinePTS) / 90000.0 * 25) : 0.0); + if (softStartState == sIdle) + xfprintf(stderr, " <<<<<"); + newlineRequired = true; + } + + if (newlineRequired) + xfprintf(stderr, "\n"); + } + + if (queued) + speed = 100; + + if (100.0 == speed + || (speed != softStartLastSpeed + && (!oldMode || (now - softStartSpeedChangeTime) > (totalDuration / softStartMaxSpeedChanges)))) + { + softStartSpeedChangeTime = now; + +// fprintf(stderr, "slowstart: %lg, %lg\n", speed, p); + + m_xineLib.execFuncSetSpeed(speed); + m_xineLib.execFuncWait(); + +// if (100.0 == speed) +// m_xineLib.execFuncSetPrebuffer(m_settings.GetModeParams()->m_prebufferFrames); + + softStartLastSpeed = speed; + } + } + } + +//if (jw) *(char *)0 = 0; + jw = false; + +// fprintf(stderr, "v"); + + if (ts) + { +//NNN stripPTSandDTS((uchar *)Data, Length); + } + else if (0) //ZZZ + { + int64_t pts = 0; + + if (np && getPTS(Data, Length, pts)) + { + np = false; + +// fprintf(stderr, "M %lld %llx\n", pts); + m_xineLib.execFuncMetronom(pts); + m_xineLib.execFuncWait(); + } + } + + int r = PlayCommon1(Data, Length, ptsForce); + +// fprintf(stderr, "V"); + + return r; + } + + int cXineDevice::PlayCommon1(const uchar *Data, int Length, int64_t ptsForce) + { +/* +if (blahblah) +{ + fprintf(stderr, "blahblah C1"); + for (int i = 0; i < 50 && i < Length; i++) + fprintf(stderr, " %02x", Data[ i ]); + fprintf(stderr, "\n"); +} +*/ + if (oldMode) + return PlayCommon2(Data, Length, ptsForce); + + struct sBuffer + { + sBuffer *next; + int length; + int64_t ptsForce; + uchar data[1]; + }; + + static sBuffer *pHead = 0; + static sBuffer *pTail = 0; + static int bufCount = 0; + + while (!doClear && pHead) + { + if (sIdle != softStartState) + { + cPoller p; + if (!m_xineLib.Poll(p, -1)) + break; + } + + int r = PlayCommon2(pHead->data, pHead->length, pHead->ptsForce); + if (r > 0) + { + sBuffer *p = pHead; + pHead = pHead->next; + ::free(p); +//fprintf(stderr, "bufCount: %d\n", --bufCount); + } + else if (r < 0) + { + while (pHead) + { + sBuffer *p = pHead; + pHead = pHead->next; + ::free(p); + } + + bufCount = 0; + pTail = 0; +//fprintf(stderr, "--- bufCount: %d\n", bufCount); + return r; + } + else + break; + } + + if (doClear) + { +//fprintf(stderr, "----- clearing -----\n"); + while (pHead) + { + sBuffer *p = pHead; + pHead = pHead->next; + ::free(p); + } + + bufCount = 0; + pTail = 0; + doClear = false; +//fprintf(stderr, "=== bufCount: %d\n", bufCount); + +// return Length; + } + + if (!pHead) + { + pTail = 0; + + do // ... while (false); + { + if (sIdle != softStartState) + { + cPoller p; + if (!m_xineLib.Poll(p, -1)) + break; +//fprintf(stderr, "### bufCount: %d\n", bufCount); + } + + return PlayCommon2(Data, Length, ptsForce); + } + while (false); + } + + sBuffer *p = (sBuffer *)::malloc(sizeof (sBuffer) - sizeof (p->data) + Length); + if (!p) + return 0; + + p->next = 0; + p->length = Length; + p->ptsForce = ptsForce; + memcpy(&p->data, Data, Length); + + if (!pTail) + pHead = pTail = p; + else + { + pTail->next = p; + pTail = p; + } + +//fprintf(stderr, "*** bufCount: %d\n", ++bufCount); + return Length; + } + + static uchar jumboPESdata[ 6 + 0xffff ]; + static uchar *jumboPEStailData = 0; + + static bool mkJumboPES(const uchar *Data, int Length) + { + int origJumboPESsize = jumboPESsize; + jumboPESsize = 0; + + if (Length < 9) + VERBOSE_RETURN0(false); + + if (0x00 != Data[ 0 ]) + VERBOSE_RETURN0(false); + + if (0x00 != Data[ 1 ]) + VERBOSE_RETURN0(false); + + if (0x01 != Data[ 2 ]) + VERBOSE_RETURN0(false); + + if (0xbd != Data[ 3 ]) + VERBOSE_RETURN0(false); + + int l = Data[ 4 ] * 256 + Data[ 5 ]; + if ((6 + l) != Length) + { + const uchar *data = Data + (6 + l); + int length = Length - (6 + l); + + if (length < 6) + VERBOSE_RETURN3(false); + + if (0x00 != data[ 0 ]) + VERBOSE_RETURN3(false); + + if (0x00 != data[ 1 ]) + VERBOSE_RETURN3(false); + + if (0x01 != data[ 2 ]) + VERBOSE_RETURN3(false); + + if (0xbe != data[ 3 ]) + VERBOSE_RETURN3(false); + + int L = data[ 4 ] * 256 + data[ 5 ]; + if ((6 + L) != length) + VERBOSE_RETURN3(false); + + // ignore padding + Length -= length; + } +/* + for (int i = 0; i < 20; i++) + fprintf(stderr, "%02x ", Data[ i ]); + fprintf(stderr, "\n"); +*/ + bool cont = (0x80 == Data[ 6 ] + && 0x00 == Data[ 7 ] + && 0x00 == Data[ 8 ]); + + if (cont + && Length >= 6 + 3 + 5 + && Data[ 9 ] == 0x0b + && Data[ 10 ] == 0x77 + && frameSizes[ Data[ 13 ] ] > 0) + { + cont = false; + } + + if (!cont + || 0 == origJumboPESsize) + { + if (0 != origJumboPESsize) + VERBOSE_RETURN0(false); + + if ((origJumboPESsize + Length - 0) > (6 + 0xffff)) + VERBOSE_RETURN0(false); + + if (jumboPEStail > 0) + { + int headerSize = 6 + 3 + Data[ 8 ]; + ::memcpy(&jumboPESdata[ origJumboPESsize ], &Data[ 0 ], headerSize); + + ::memmove(&jumboPESdata[ origJumboPESsize + headerSize ], jumboPEStailData, jumboPEStail); + + ::memcpy(&jumboPESdata[ origJumboPESsize + headerSize + jumboPEStail ], &Data[ headerSize ], Length - headerSize); + + origJumboPESsize += headerSize + jumboPEStail + Length - headerSize; + + jumboPEStail = 0; + jumboPEStailData = 0; + + //FIXME: PTS should be adjusted to take care of jumboPEStail's duration. + // Otherwise there is a certain jitter on audio duration <=> PTS. + } + else + { + ::memcpy(&jumboPESdata[ origJumboPESsize ], &Data[ 0 ], Length - 0); + origJumboPESsize += Length - 0; + } + } + else + { + if (0 == origJumboPESsize) + VERBOSE_RETURN0(false); + + if ((origJumboPESsize + Length - 9) > (6 + 0xffff)) + VERBOSE_RETURN0(false); + + ::memcpy(&jumboPESdata[ origJumboPESsize ], &Data[ 9 ], Length - 9); + origJumboPESsize += Length - 9; + } + + if (0 == origJumboPESsize) + VERBOSE_RETURN0(false); + + jumboPESsize = origJumboPESsize; + + if (2048 == Length) + { +// fprintf(stderr, " b %d", jumboPESsize); + return false; + } + + jumboPESdata[ 4 ] = (jumboPESsize - 6) >> 8; + jumboPESdata[ 5 ] = (jumboPESsize - 6) & 0xff; + +// fprintf(stderr, " B %d", jumboPESsize); + return true; + } + + static int initFrameSizes() + { + ::memset(frameSizes, 0, sizeof (frameSizes)); + + // fs = 48 kHz + frameSizes[ 0x00 ] = 64; + frameSizes[ 0x01 ] = 64; + frameSizes[ 0x02 ] = 80; + frameSizes[ 0x03 ] = 80; + frameSizes[ 0x04 ] = 96; + frameSizes[ 0x05 ] = 96; + frameSizes[ 0x06 ] = 112; + frameSizes[ 0x07 ] = 112; + frameSizes[ 0x08 ] = 128; + frameSizes[ 0x09 ] = 128; + frameSizes[ 0x0a ] = 160; + frameSizes[ 0x0b ] = 160; + frameSizes[ 0x0c ] = 192; + frameSizes[ 0x0d ] = 192; + frameSizes[ 0x0e ] = 224; + frameSizes[ 0x0f ] = 224; + frameSizes[ 0x10 ] = 256; + frameSizes[ 0x11 ] = 256; + frameSizes[ 0x12 ] = 320; + frameSizes[ 0x13 ] = 320; + frameSizes[ 0x14 ] = 384; + frameSizes[ 0x15 ] = 384; + frameSizes[ 0x16 ] = 448; + frameSizes[ 0x17 ] = 448; + frameSizes[ 0x18 ] = 512; + frameSizes[ 0x19 ] = 512; + frameSizes[ 0x1a ] = 640; + frameSizes[ 0x1b ] = 640; + frameSizes[ 0x1c ] = 768; + frameSizes[ 0x1d ] = 768; + frameSizes[ 0x1e ] = 896; + frameSizes[ 0x1f ] = 896; + frameSizes[ 0x20 ] = 1024; + frameSizes[ 0x21 ] = 1024; + frameSizes[ 0x22 ] = 1152; + frameSizes[ 0x23 ] = 1152; + frameSizes[ 0x24 ] = 1280; + frameSizes[ 0x25 ] = 1280; + + // fs = 44.1 kHz + frameSizes[ 0x40 ] = 69; + frameSizes[ 0x41 ] = 70; + frameSizes[ 0x42 ] = 87; + frameSizes[ 0x43 ] = 88; + frameSizes[ 0x44 ] = 104; + frameSizes[ 0x45 ] = 105; + frameSizes[ 0x46 ] = 121; + frameSizes[ 0x47 ] = 122; + frameSizes[ 0x48 ] = 139; + frameSizes[ 0x49 ] = 140; + frameSizes[ 0x4a ] = 174; + frameSizes[ 0x4b ] = 175; + frameSizes[ 0x4c ] = 208; + frameSizes[ 0x4d ] = 209; + frameSizes[ 0x4e ] = 243; + frameSizes[ 0x4f ] = 244; + frameSizes[ 0x50 ] = 278; + frameSizes[ 0x51 ] = 279; + frameSizes[ 0x52 ] = 348; + frameSizes[ 0x53 ] = 349; + frameSizes[ 0x54 ] = 417; + frameSizes[ 0x55 ] = 418; + frameSizes[ 0x56 ] = 487; + frameSizes[ 0x57 ] = 488; + frameSizes[ 0x58 ] = 557; + frameSizes[ 0x59 ] = 558; + frameSizes[ 0x5a ] = 696; + frameSizes[ 0x5b ] = 697; + frameSizes[ 0x5c ] = 835; + frameSizes[ 0x5d ] = 836; + frameSizes[ 0x5e ] = 975; + frameSizes[ 0x5f ] = 976; + frameSizes[ 0x60 ] = 1114; + frameSizes[ 0x61 ] = 1115; + frameSizes[ 0x62 ] = 1253; + frameSizes[ 0x63 ] = 1254; + frameSizes[ 0x64 ] = 1393; + frameSizes[ 0x65 ] = 1394; + + // fs = 32 kHz + frameSizes[ 0x80 ] = 96; + frameSizes[ 0x81 ] = 96; + frameSizes[ 0x82 ] = 120; + frameSizes[ 0x83 ] = 120; + frameSizes[ 0x84 ] = 144; + frameSizes[ 0x85 ] = 144; + frameSizes[ 0x86 ] = 168; + frameSizes[ 0x87 ] = 168; + frameSizes[ 0x88 ] = 192; + frameSizes[ 0x89 ] = 192; + frameSizes[ 0x8a ] = 240; + frameSizes[ 0x8b ] = 240; + frameSizes[ 0x8c ] = 288; + frameSizes[ 0x8d ] = 288; + frameSizes[ 0x8e ] = 336; + frameSizes[ 0x8f ] = 336; + frameSizes[ 0x90 ] = 384; + frameSizes[ 0x91 ] = 384; + frameSizes[ 0x92 ] = 480; + frameSizes[ 0x93 ] = 480; + frameSizes[ 0x94 ] = 576; + frameSizes[ 0x95 ] = 576; + frameSizes[ 0x96 ] = 672; + frameSizes[ 0x97 ] = 672; + frameSizes[ 0x98 ] = 768; + frameSizes[ 0x99 ] = 768; + frameSizes[ 0x9a ] = 960; + frameSizes[ 0x9b ] = 960; + frameSizes[ 0x9c ] = 1152; + frameSizes[ 0x9d ] = 1152; + frameSizes[ 0x9e ] = 1344; + frameSizes[ 0x9f ] = 1344; + frameSizes[ 0xa0 ] = 1536; + frameSizes[ 0xa1 ] = 1536; + frameSizes[ 0xa2 ] = 1728; + frameSizes[ 0xa3 ] = 1728; + frameSizes[ 0xa4 ] = 1920; + frameSizes[ 0xa5 ] = 1920; + + return 0; + }; + +#if APIVERSNUM < 10318 + + void cXineDevice::PlayAudio(const uchar *Data, int Length) + { + cDevice::PlayAudio(Data, Length); + + PlayAudioCommon(Data, Length); + } + +#else + + int cXineDevice::GetAudioChannelDevice(void) + { + return m_audioChannel; + } +static int m_acd = -1; + void cXineDevice::SetAudioChannelDevice(int AudioChannel) + { +#if APIVERSNUM >= 10701 +if (m_acd == AudioChannel) + return; +#endif +m_acd = AudioChannel; + + xfprintf(stderr, "SetAudioChannelDevice: %d\n", AudioChannel); + + m_audioChannel = AudioChannel; + + m_xineLib.execFuncSelectAudio(m_audioChannel); + } +static int m_dad = -1; + void cXineDevice::SetDigitalAudioDevice(bool On) + { +#if APIVERSNUM >= 10701 +if (m_dad == On) + return; +#endif +m_dad = On; + + xfprintf(stderr, "SetDigitalAudioDevice: %d\n", On); +//aaz = On ? 1 : 2; + m_xineLib.execFuncSelectAudio(On ? -1 : m_audioChannel); + + if (pmNone == pm) + return; + + doClear = true; + + if (m_settings.LiveTV() + && !audioSeen) + { + cMutexLock lock(&softStartMutex); + if (softStartState == sIdle) + softStartTrigger = sstNoMetronom; + + return; + } + + m_xineLib.pause(); +//xzabc = 1; + jumboPESsize = 0; + jumboPEStail = 0; + + if (f) + m_xineLib.execFuncSetSpeed(100.0); +//double t0 = tNow(); + if (m_settings.LiveTV() + || !foundVideo) // radio recording: audio channels are not related + { + ptsV = ptsA = ptsP = ptsD = -1; + + m_xineLib.execFuncClear(-3); + +// if (!foundVideo) +// m_xineLib.execFuncStart(); +// np = true; + } + + m_xineLib.execFuncResetAudio(); + + if (f) + m_xineLib.execFuncSetSpeed(0.0); + + m_xineLib.execFuncWait(); +//xzabc = 2; + m_xineLib.pause(false); +//double t1 = tNow(); fprintf(stderr, "!!!!!!! %.3lf ms\n", (t1 - t0) * 1000); + if (m_settings.LiveTV()) + { + cMutexLock lock(&softStartMutex); + softStartTrigger = sstNoMetronom; + } +//fprintf(stderr, "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"); + } + +#if APIVERSNUM < 10342 + + int cXineDevice::PlayAudio(const uchar *Data, int Length) + { +// fprintf(stderr, " 0x%02x ", Data[ 3 ]); + return PlayAudioCommon(Data, Length); + } + +#else + + int cXineDevice::PlayAudio(const uchar *Data, int Length, uchar /* Id */) + { +// fprintf(stderr, " 0x%02x ", Data[ 3 ]); + return PlayAudioCommon(Data, Length); + } + +#endif +#endif + + int cXineDevice::PlayAudioCommon(const uchar *Data, int Length) + { + if (ttt5 == 0) ttt5 = tNow(); +// fprintf(stderr, " 0x%02x: %d ", Data[ 3 ], Data[4] * 256 + Data[5]); +/* +if (Data[ 3 ] == 0xd0) { + FILE *f = fopen("/tmp/d0", "ab"); + fwrite(Data, Length, 1, f); + fclose(f); +} +*/ + { + static int i = initFrameSizes(); + (void)i; + } + +store_frame(Data, Length, __LINE__); + + LOG_ME(::fprintf(stderr, "A");) + + if (f) + { + LOG_ME(::fprintf(stderr, "<");) + return Length; + } + + if (pmNone == pm) + { + cMutexLock pmMutexLock(&m_pmMutex); + + if (pmNone == pm) + m_pmCondVar.Wait(m_pmMutex); + } + + int retVal = PlayAudio2(Data, Length); + + LOG_ME(::fprintf(stderr, "a")); + + return retVal; + } + + int cXineDevice::PlayAudio2(const uchar *Data, int Length) + { +// fprintf(stderr, "D"); + + int done = 0; + + while (done < Length) + { + char ch = 'X'; + + int id = 0x00; + + int todo = Length - done; + if (todo >= 6) + { + if (0x00 == Data[ done + 0 ] + && 0x00 == Data[ done + 1 ] + && 0x01 == Data[ done + 2 ]) + { + id = Data[ done + 3 ]; + int len = Data[ done + 4 ] * 0x0100 + Data[ done + 5 ]; + + if (todo >= (6 + len)) + { + todo = (6 + len); + + if (0xbe == id) + { + done += todo; + +// ::fprintf(stderr, "x"); + continue; + } + + ch = '.'; + } + else + { + VERBOSE_NOP(); + ch = '3'; + } + } + else + { + VERBOSE_NOP(); + ch = '2'; + } + } + else + { + VERBOSE_NOP(); + ch = '1'; + } + +// ::fprintf(stderr, "%c", ch); + + int r; + + if (0xbd == id) + r = PlayAudio3(Data + done, todo); + else + r = PlayVideo3(Data + done, todo, false); + + if (r < 0) + return r; + + if (r != todo) + VERBOSE_NOP(); + + done += r; + } + + return done; + } + + int cXineDevice::PlayAudio3(const uchar *Data, int Length) + { + resetScramblingControl(Data, Length); + +store_frame(Data, Length, __LINE__); +/* + ::fprintf(stderr, "l: %d\t", Length); + for (int i = 0; i < 20; i++) + ::fprintf(stderr, "%02x ", Data[ i ]); + ::fprintf(stderr, "\n"); +*/ +// fprintf(stderr, "A"); + + if (mkJumboPES(Data, Length)) + { + int todo = jumboPESsize; + jumboPESsize = 0; + + dumpAudio("Audio", jumboPESdata, todo); + + bool dolby = false; + bool pcm = false; + + do + { + if (todo < (6 + 3 + 0 + 2)) + break; + + if (0x00 != jumboPESdata[ 0 ] + || 0x00 != jumboPESdata[ 1 ] + || 0x01 != jumboPESdata[ 2 ] + || 0xbd != jumboPESdata[ 3 ]) + { + break; + } + + int l = jumboPESdata[ 4 ] * 0x0100 + jumboPESdata[ 5 ]; + if (l < (3 + 0 + 2)) + break; + + if (todo < (6 + l)) + break; + + int h = jumboPESdata[ 8 ]; + if (l < (3 + h + 2)) + break; + + if (0x0b == jumboPESdata[ 6 + 3 + h + 0 ] + && 0x77 == jumboPESdata[ 6 + 3 + h + 1 ]) + { + if (l < (3 + h + 2 + 2 + 1)) + { + VERBOSE_NOP(); + break; + } + + int frameStart = 6 + 3 + h; + bool failed = false; + + while (true) + { + int frameSize = 2 * frameSizes[ jumboPESdata[ frameStart + 4 ] ]; + if (frameSize <= 0) + { + failed = true; + + xfprintf(stderr, "frame_size_code: 0x%02x\n", jumboPESdata[ frameStart + 4 ]); + VERBOSE_NOP(); + break; + } + + if (frameStart + frameSize > todo) + break; + + frameStart += frameSize; + + if (frameStart + 2 + 2 + 1 > todo) + break; + + if (0x0b != jumboPESdata[ frameStart + 0 ] + || 0x77 != jumboPESdata[ frameStart + 1 ]) + { + failed = true; + + VERBOSE_NOP(); + break; + } + } + + if (failed) + break; + + jumboPEStail = todo - frameStart; + jumboPEStailData = jumboPESdata + frameStart; + + todo = frameStart; + + jumboPESdata[ 4 + 0 ] = (todo - 6) >> 8; + jumboPESdata[ 4 + 1 ] = (todo - 6) & 0xff; + + dolby = true; +store_frame(jumboPESdata, todo, __LINE__); + break; + } + + if (0x80 == (0xf0 & jumboPESdata[ 6 + 3 + h + 0 ])) + { + dolby = true; + break; + } + + if (0xa0 == (0xf0 & jumboPESdata[ 6 + 3 + h + 0 ])) + { + pcm = true; + break; + } + + for (int i = 6 + 3 + h; i < todo - 2 - 2 - 1; i++) + { + if (0x0b == jumboPESdata[ i + 0 ] + && 0x77 == jumboPESdata[ i + 1 ] + && 0 != frameSizes[ jumboPESdata[ i + 4 ] ]) + { + jumboPEStail = todo - i; + jumboPEStailData = jumboPESdata + i; + } + } + } + while (false); + + if (pcm + || (dolby && m_settings.AudioDolbyOn())) + { + int done = 0; + + while (done < todo) + { + int r = PlayCommon(jumboPESdata + done, todo - done, false); + if (r < 0) + return r; + +// fprintf(stderr, "."); + + done += r; + } + +// Don't return done here as the remaining bytes were buffered elsewhere! +// return done; + } + } + else if (jumboPESsize == Length) + { + int todo = jumboPESsize; + + bool dolby = false; + bool pcm = false; + + do + { + if (todo < (6 + 3 + 0 + 2)) + break; + + if (0x00 != jumboPESdata[ 0 ] + || 0x00 != jumboPESdata[ 1 ] + || 0x01 != jumboPESdata[ 2 ] + || 0xbd != jumboPESdata[ 3 ]) + { + break; + } + + int l = jumboPESdata[ 4 ] * 0x0100 + jumboPESdata[ 5 ]; + if (l < (3 + 0 + 2)) + break; + + if (todo < (6 + l)) + break; + + int h = jumboPESdata[ 8 ]; + if (l < (3 + h + 2)) + break; + + if (0x80 == (0xf0 & jumboPESdata[ 6 + 3 + h + 0 ])) + { + dolby = true; + break; + } + + if (0xa0 == (0xf0 & jumboPESdata[ 6 + 3 + h + 0 ])) + { + pcm = true; + break; + } + } + while (false); + + if (dolby || pcm) + { + dumpAudio("Audio", jumboPESdata, todo); + + jumboPESsize = 0; + } + + if (pcm + || (dolby && m_settings.AudioDolbyOn())) + { + int done = 0; + + while (done < todo) + { + int r = PlayCommon(jumboPESdata + done, todo - done, false); + if (r < 0) + return r; + +// fprintf(stderr, "."); + + done += r; + } + +// Don't return done here as the remaining bytes were buffered elsewhere! +// return done; + } + } + +// fprintf(stderr, "\n"); + + return Length; + } + +#if APIVERSNUM >= 10338 + uchar *cXineDevice::GrabImage(int &Size, bool Jpeg /* = true */, int Quality /* = -1 */, int SizeX /* = -1 */, int SizeY /* = -1 */) + { + const char *const FileName = 0; +#else + bool cXineDevice::GrabImage(const char *FileName, bool Jpeg /* = true */, int Quality /* = -1 */, int SizeX /* = -1 */, int SizeY /* = -1 */) + { + int Size = 0; +#endif + xfprintf(stderr, "GrabImage ...\n\n"); + + if (-1 == Quality) + Quality = 100; + + if (-1 == SizeX) + SizeX = m_settings.DefaultGrabSizeX(); + + if (-1 == SizeY) + SizeY = m_settings.DefaultGrabSizeY(); + + uchar *result = m_xineLib.execFuncGrabImage(FileName, Size, Jpeg, Quality, SizeX, SizeY); + + xfprintf(stderr, result ? "\nGrabImage succeeded.\n" : "\nGrabImage failed.\n"); + return result; + } + + int64_t cXineDevice::GetSTC(void) + { +// ::fprintf(stderr, "GetSTC: "); + + int64_t pts = -1; + + if (!m_xineLib.execFuncGetPTS(pts, 100) || pts < 0) + pts = cDevice::GetSTC(); + +// ::fprintf(stderr, "%lld\n", pts); + + return pts; + } + + void cXineDevice::SetVideoFormat(bool VideoFormat16_9) + { + xfprintf(stderr, "SetVideoFormat: %d\n", VideoFormat16_9); + cDevice::SetVideoFormat(VideoFormat16_9); + + m_is16_9 = VideoFormat16_9; + m_xineLib.selectNoSignalStream(VideoFormat16_9); + } + +#if APIVERSNUM >= 10707 +#if APIVERSNUM >= 10708 + void cXineDevice::GetVideoSize(int &Width, int &Height, double &VideoAspect) + { + int x, y, zx, zy; + m_xineLib.execFuncVideoSize(x, y, Width, Height, zx, zy, &VideoAspect); + } + + void cXineDevice::GetOsdSize(int &Width, int &Height, double &PixelAspect) +#else + void cXineDevice::GetVideoSize(int &Width, int &Height, eVideoAspect &Aspect) + // function name misleading -- max osd extent shall be returned +#endif + { + Width = m_settings.OsdExtentParams().GetOsdExtentWidth(); + Height = m_settings.OsdExtentParams().GetOsdExtentHeight(); +#if APIVERSNUM >= 10708 + PixelAspect = (m_is16_9 ? 16.0 : 4.0) * Height / ((m_is16_9 ? 9.0 : 3.0) * Width); +//fprintf(stderr, "GetOsdSize(%d, %d, %lg)\n", Width, Height, PixelAspect); +#else + Aspect = m_is16_9 ? va16_9 : va4_3; +#endif + } +#endif + + static bool firstCallToSetVolume = true; + static void switchSkin(const bool restore); + + void cXineDevice::SetVolumeDevice(int Volume) + { + if (firstCallToSetVolume) + { + firstCallToSetVolume = false; + + if (m_settings.ShallSwitchSkin()) + switchSkin(false); + } + + xfprintf(stderr, "SetVolumeDevice: %d\n", Volume); + m_xineLib.execFuncSetVolume(Volume); + } + +#if APIVERSNUM < 10307 + cOsdBase *cXineDevice::NewOsd(int w, int h, int x, int y) +#elif APIVERSNUM < 10509 + cOsd *cXineDevice::NewOsd(int w, int h, int x, int y) +#else + cOsd *cXineDevice::NewOsd(int w, int h, int x, int y, uint Level, bool TrueColor /* = false */) +#endif + { +// ::fprintf(stderr, "NewOsd ---: %s\n", ::ctime(&(const time_t &)::time(0))); + cXineOsdMutexLock osdLock(&m_osdMutex); +// ::fprintf(stderr, "NesOsd +++: %s\n", ::ctime(&(const time_t &)::time(0))); +#if APIVERSNUM < 10509 + if (m_currentOsd) + { + esyslog("vdr-xine: new OSD(%d, %d) requested while another OSD is active", x, y); + xfprintf(stderr, "vdr-xine: new OSD(%d, %d) requested while another OSD is active", x, y); + return 0; + } +#endif + if (x < 0 || y < 0) + { + esyslog("vdr-xine: new OSD(%d, %d) requested with coordinates out of range", x, y); + xfprintf(stderr, "vdr-xine: new OSD(%d, %d) requested with coordinates out of range", x, y); + } + + if (w <= 0 || h <= 0) + { + w = m_settings.OsdExtentParams().GetOsdExtentWidth(); + h = m_settings.OsdExtentParams().GetOsdExtentHeight(); + } + +#if APIVERSNUM < 10509 + m_currentOsd = new cXineOsd(*this, w, h, x, y); +#else + return new cXineOsd(*this, w, h, x, y, Level); +#endif + + return m_currentOsd; + } + +#if APIVERSNUM < 10307 + void cXineDevice::OnFreeOsd(cOsdBase *const osd) +#else + void cXineDevice::OnFreeOsd(cOsd *const osd) +#endif + { +#if APIVERSNUM < 10509 + cXineOsdMutexLock osdLock(&m_osdMutex); + + assert(osd == m_currentOsd); + + m_currentOsd = 0; +#endif + } + + bool cXineDevice::ChangeCurrentOsd(cXineOsd *const osd, bool on) + { + cXineOsdMutexLock osdLock(&m_osdMutex); + + if (m_currentOsd) + { + if (on) + { + esyslog("vdr-xine: OSD activation requested while another OSD is active -- ignoring request"); + xfprintf(stderr, "vdr-xine: OSD activation requested while another OSD is active -- ignoring request"); + return false; + } + else if (m_currentOsd != osd) + { + esyslog("vdr-xine: OSD deactivation requested for an OSD which is not active -- ignoring request"); + xfprintf(stderr, "vdr-xine: OSD deactivation requested for an OSD which is not active -- ignoring request"); + return false; + } + else + { + m_currentOsd = 0; + } + } + else + { + if (!on) + { + esyslog("vdr-xine: OSD deactivation requested while no OSD is active -- ignoring request"); + xfprintf(stderr, "vdr-xine: OSD deactivation requested while no OSD is active -- ignoring request"); + return false; + } + else + { + m_currentOsd = osd; + } + } + + return true; + } + + + + static cDevice *originalPrimaryDevice = 0; + +#if APIVERSNUM >= 10307 + + void cXineDevice::MakePrimaryDevice(bool On) + { + cDevice::MakePrimaryDevice(On); + + xfprintf(stderr, "-------------------------\n"); + xfprintf(stderr, "MakePrimaryDevice: %d\n", On); + xfprintf(stderr, "=========================\n"); + + if (On) + new cXineOsdProvider(*this); + else + cOsdProvider::Shutdown(); + + originalPrimaryDevice = 0; + } + +#endif + + + +#if APIVERSNUM >= 10320 + static cSkin *origSkin = 0; +#endif + + static void switchSkin(const bool restore) + { +#if APIVERSNUM >= 10320 + if (restore) + { + Skins.SetCurrent(origSkin->Name()); + cThemes::Load(Skins.Current()->Name(), Setup.OSDTheme, Skins.Current()->Theme()); + } + else + { + origSkin = Skins.Current(); + Skins.SetCurrent("curses"); + cThemes::Load(Skins.Current()->Name(), Setup.OSDTheme, Skins.Current()->Theme()); + } +#else +#warning vdr-xine: switching skins is only available for VDR versions >= 1.3.20 + isyslog("vdr-xine: switching skins is only available for VDR versions >= 1.3.20"); +#endif + } + + void cXineDevice::reshowCurrentOsd(const bool dontOptimize /* = true */, const int frameLeft /* = -1 */, const int frameTop /* = -1 */, const int frameWidth /* = -1 */, const int frameHeight /* = -1 */, const int frameZoomX /* = -1 */, const int frameZoomY /* = -1 */, const bool unlockOsdUpdate /* = false */) + { + cXineOsdMutexLock osdLock(&m_osdMutex); + + if (unlockOsdUpdate) + m_osdUpdateLocked = false; + + if (m_currentOsd) + m_currentOsd->ReshowCurrentOsd(dontOptimize, frameLeft, frameTop, frameWidth, frameHeight, frameZoomX, frameZoomY); + } + + void cXineDevice::LockOsdUpdate() + { + cXineOsdMutexLock osdLock(&m_osdMutex); + + m_osdUpdateLocked = true; + } + + bool cXineDevice::OsdUpdateLocked() + { + cXineOsdMutexLock osdLock(&m_osdMutex); + + return m_osdUpdateLocked; + } + + void cXineDevice::mainMenuTrampoline() + { +#if APIVERSNUM >= 10332 + cMutexLock switchPrimaryDeviceLock(&m_switchPrimaryDeviceMutex); + if (m_switchPrimaryDeviceDeviceNo < 0) + return; + + cControl::Shutdown(); + + if (m_switchPrimaryDeviceDeviceNo == (1 + DeviceNumber())) + { + char *msg = 0; + ::asprintf(&msg, tr("Switching primary DVB to %s..."), m_plugin->Name()); + + Skins.Message(mtInfo, msg); + ::free(msg); + } + + SetPrimaryDevice(m_switchPrimaryDeviceDeviceNo); + + if (m_switchPrimaryDeviceDeviceNo != (1 + DeviceNumber())) + { + char *msg = 0; + ::asprintf(&msg, tr("Switched primary DVB back from %s"), m_plugin->Name()); + + Skins.Message(mtInfo, msg); + ::free(msg); + } + + m_switchPrimaryDeviceDeviceNo = -1; + + m_switchPrimaryDeviceCond.Broadcast(); +#endif + } + + void cXineDevice::switchPrimaryDevice(const int deviceNo, const bool waitForExecution) + { +#if APIVERSNUM >= 10332 +#if APIVERSNUM < 10347 + while (cRemote::HasKeys()) + cCondWait::SleepMs(10); +#endif + + cMutexLock switchPrimaryDeviceLock(&m_switchPrimaryDeviceMutex); + m_switchPrimaryDeviceDeviceNo = deviceNo; + +#if APIVERSNUM < 10347 + cRemote::CallPlugin(m_plugin->Name()); +#endif + + if (waitForExecution) + m_switchPrimaryDeviceCond.Wait(m_switchPrimaryDeviceMutex); +#else +#warning vdr-xine: switching primary device is no longer supported for VDR versions < 1.3.32 + isyslog("vdr-xine: switching primary device is no longer supported for VDR versions < 1.3.32"); +#endif + } + + void cXineDevice::OnClientConnect() + { + reshowCurrentOsd(); + + if (m_settings.LiveTV()) + { + cMutexLock lock(&softStartMutex); + softStartTrigger = sstNormal; + } + + if (m_settings.AutoPrimaryDevice()) + { + cDevice *primaryDevice = cDevice::PrimaryDevice(); + if (this != primaryDevice) + switchPrimaryDevice(1 + DeviceNumber(), true); + + originalPrimaryDevice = primaryDevice; + } + + if (m_settings.ShallSwitchSkin()) + switchSkin(true); + } + + void cXineDevice::OnClientDisconnect() + { + if (m_settings.ShallSwitchSkin()) + switchSkin(false); + + if (m_settings.AutoPrimaryDevice() + && originalPrimaryDevice) + { + if (this != originalPrimaryDevice) + switchPrimaryDevice(1 + originalPrimaryDevice->DeviceNumber(), false); + } + } + + void cXineDevice::ReshowCurrentOSD(const int frameLeft, const int frameTop, const int frameWidth, const int frameHeight, const int frameZoomX, const int frameZoomY, const bool unlockOsdUpdate) + { +// ::fprintf(stderr, ">>> cXineDevice::ReshowCurrentOSD()\n"); + reshowCurrentOsd(false, frameLeft, frameTop, frameWidth, frameHeight, frameZoomX, frameZoomY, unlockOsdUpdate); +// ::fprintf(stderr, "<<< cXineDevice::ReshowCurrentOSD()\n"); + } + + bool cXineDevice::DeviceReplayingOrTransferring() + { +#if APIVERSNUM >= 10341 + return Replaying() || Transferring(); +#else + return Replaying() /* || Transferring() */; +#endif + } + + bool cXineDevice::open() + { + if (!m_xineLib.Open()) + return false; + + if (m_settings.ShallSwitchSkin()) + { +#if APIVERSNUM >= 10320 + Skins.SetCurrent(Setup.OSDSkin); + cThemes::Load(Skins.Current()->Name(), Setup.OSDTheme, Skins.Current()->Theme()); +#endif + switchSkin(false); + } + + return true; + } + + void cXineDevice::close() + { + m_xineLib.Close(); + + if (m_settings.ShallSwitchSkin()) + switchSkin(true); + } + + void cXineDevice::Stop() + { + if (!theXineDevice) + return; + + theXineDevice->close(); + } + + cXineDevice::cXineDevice(cPlugin *const plugin, cXineSettings &settings, cXineRemote *remote) + : cDevice() + , m_settings(settings) + , m_osdUpdateLocked(false) + , m_currentOsd(0) + , m_spuDecoder(0) + , m_is16_9(true) + , m_audioChannel(0) + , m_plugin(plugin) + , m_switchPrimaryDeviceDeviceNo(-1) + , m_xineLib(plugin, settings, m_osdMutex, remote) + { + m_xineLib.SetEventSink(this); + } + + cXineDevice::~cXineDevice() + { +#if APIVERSNUM < 10320 + close(); +#endif + if (m_spuDecoder) + delete m_spuDecoder; + } + + bool cXineDevice::Create(cPlugin *const plugin, cXineSettings &settings, cXineRemote *remote) + { + if (theXineDevice) + return false; + + theXineDevice = new cXineDevice(plugin, settings, remote); + + return 0 != theXineDevice + && theXineDevice->hasNoSignalStream(); + } + + bool cXineDevice::Open() + { + if (!theXineDevice) + return false; + + return theXineDevice->open(); + } + + cXineDevice *cXineDevice::GetDevice() + { + return theXineDevice; + } + +}; diff --git a/xineDevice.h b/xineDevice.h new file mode 100644 index 0000000..9a98837 --- /dev/null +++ b/xineDevice.h @@ -0,0 +1,197 @@ + +#ifndef __XINEDEVICE_H +#define __XINEDEVICE_H + + + +#include "xineCommon.h" + +#include <vdr/dvbspu.h> +#include <vdr/device.h> + +#include "xineLib.h" + + + +class cPlugin; + +namespace PluginXine +{ + class cXineSettings; + class cXineOsd; + class cXineDevice; + + class cXineSpuDecoder : public cDvbSpuDecoder + { + cXineDevice *const m_xineDevice; + + void ptsAdjust(uint32_t &pts); + + public: + cXineSpuDecoder(cXineDevice *const xineDevice) + : cDvbSpuDecoder() + , m_xineDevice(xineDevice) + { + } + + virtual int setTime(uint32_t pts); + }; + + class cXineDevice : public cDevice, public cXineLibEvents + { + cXineSettings &m_settings; + + bool m_osdUpdateLocked; + cXineOsd *m_currentOsd; + cXineSpuDecoder *m_spuDecoder; + + virtual bool HasDecoder(void) const; + virtual cSpuDecoder *GetSpuDecoder(void); + virtual bool CanReplay(void) const; + virtual bool SetPlayMode(ePlayMode PlayMode); + virtual bool HasIBPTrickSpeed(void); + virtual void TrickSpeed(int Speed, bool IBP); + virtual void TrickSpeed(int Speed); + virtual void Clear(void); + virtual void Play(void); + virtual void Freeze(void); + virtual void Mute(void); + virtual void StillPicture(const uchar *Data, int Length); + virtual bool Poll(cPoller &Poller, int TimeoutMs = 0); + virtual bool Flush(int TimeoutMs = 0); + virtual int PlayTs(const uchar *Data, int Length, bool VideoOnly = false); + virtual int PlayTsImpl(const uchar *Data, int Length, bool VideoOnly = false); + virtual int PlaySingleTs(const uchar *Data, int Length, bool VideoOnly = false); + virtual void PlayTsTrampoline(const uchar *Data, int Length, bool VideoOnly = false); + virtual int PlayTsVideo(const uchar *Data, int Length); + virtual int PlayTsAudio(const uchar *Data, int Length); + virtual int PlayVideo(const uchar *Data, int Length); + int PlayVideo1(const uchar *Data, int Length, const bool stillImageData); + int PlayVideo2(const uchar *Data, int Length, const bool stillImageData); + int PlayVideo3(const uchar *Data, int Length, const bool stillImageData); + int PlayAudio2(const uchar *Data, int Length); + int PlayAudio3(const uchar *Data, int Length); + int PlayCommon(const uchar *Data, int Length, const bool stillImageData); + int PlayCommon1(const uchar *Data, int Length, int64_t ptsForce); + int PlayCommon2(const uchar *Data, int Length, int64_t ptsForce); + int PlayCommon3(const uchar *Data, int Length, int64_t ptsForce); + +#if APIVERSNUM >= 10338 + virtual uchar *GrabImage(int &Size, bool Jpeg = true, int Quality = -1, int SizeX = -1, int SizeY = -1); +#else + virtual bool GrabImage(const char *FileName, bool Jpeg = true, int Quality = -1, int SizeX = -1, int SizeY = -1); +#endif + + bool m_is16_9; + virtual void SetVideoFormat(bool VideoFormat16_9); + virtual void SetVolumeDevice(int Volume); +#if APIVERSNUM >= 10707 +#if APIVERSNUM >= 10708 + virtual void GetVideoSize(int &Width, int &Height, double &VideoAspect); + virtual void GetOsdSize(int &Width, int &Height, double &PixelAspect); +#else + virtual void GetVideoSize(int &Width, int &Height, eVideoAspect &Aspect); +#endif +#endif + +#if APIVERSNUM >= 10307 + virtual void MakePrimaryDevice(bool On); +#endif + + int m_audioChannel; + +#if APIVERSNUM < 10318 + virtual void PlayAudio(const uchar *Data, int Length); +#else + virtual int GetAudioChannelDevice(void); + virtual void SetAudioChannelDevice(int AudioChannel); + virtual void SetDigitalAudioDevice(bool On); +#if APIVERSNUM < 10342 + virtual int PlayAudio(const uchar *Data, int Length); +#else + virtual int PlayAudio(const uchar *Data, int Length, uchar Id); +#endif +#endif + + int PlayAudioCommon(const uchar *Data, int Length); + + bool open(); + void close(); + + int PushOut(); + void initStream(); + void reshowCurrentOsd(const bool dontOptimize = true, const int frameLeft = -1, const int frameTop = -1, const int frameWidth = -1, const int frameHeight = -1, const int frameZoomX = -1, const int frameZoomY = -1, const bool unlockOsdUpdate = false); + virtual void OnClientConnect(); + virtual void OnClientDisconnect(); + virtual void ReshowCurrentOSD(const int frameLeft, const int frameTop, const int frameWidth, const int frameHeight, const int frameZoomX, const int frameZoomY, const bool unlockOsdUpdate); + virtual void LockOsdUpdate(); + virtual bool OsdUpdateLocked(); + virtual void DiscontinuityDetected(); + virtual bool DeviceReplayingOrTransferring(); + + cPlugin *const m_plugin; + int m_switchPrimaryDeviceDeviceNo; + cMutex m_switchPrimaryDeviceMutex; + cCondVar m_switchPrimaryDeviceCond; + void switchPrimaryDevice(const int deviceNo, const bool waitForExecution); + void mainMenuTrampoline(); + cMutex m_playTsMutex; + + public: + const cXineSettings &Settings() const + { + return m_settings; + } + + virtual int64_t GetSTC(void); + +#if APIVERSNUM < 10307 + virtual cOsdBase *NewOsd(int w, int h, int x, int y); +#elif APIVERSNUM < 10509 + virtual cOsd *NewOsd(int w, int h, int x, int y); +#else + virtual cOsd *NewOsd(int w, int h, int x, int y, uint Level, bool TrueColor = false); +#endif + bool ChangeCurrentOsd(cXineOsd *osd, bool on); + + cXineDevice(cPlugin *const plugin, cXineSettings &settings, cXineRemote *remote); + virtual ~cXineDevice(); + +#if APIVERSNUM < 10307 + void OnFreeOsd(cOsdBase *const osd); +#else + void OnFreeOsd(cOsd *const osd); +#endif + + cXineLib m_xineLib; + cMutex m_osdMutex; + + cMutex m_pmMutex; + cCondVar m_pmCondVar; + + static bool Create(cPlugin *const plugin, cXineSettings &settings, cXineRemote *remote); + static bool Open(); + static void Stop(); + static cXineDevice *GetDevice(); + + static void MainMenuTrampoline() + { + GetDevice()->mainMenuTrampoline(); + } + + bool hasNoSignalStream() const + { + return m_xineLib.hasNoSignalStream(); + } + + bool SupportsTrueColorOSD() const + { + return m_xineLib.SupportsTrueColorOSD(); + } + }; + +}; + + + +#endif //__XINEDEVICE_H diff --git a/xineExternal.c b/xineExternal.c new file mode 100644 index 0000000..b0ea701 --- /dev/null +++ b/xineExternal.c @@ -0,0 +1,258 @@ + +#include "xineCommon.h" + +#include "xineExternal.h" +#include "xineLib.h" + + + +namespace PluginXine +{ + + cXineExternal::cXineExternal() + : cThread() + , m_fdControl(-1) + , m_fdResult(-1) + , m_shutdown(false) + , m_connected(false) + , m_xineLib(0) + , m_enabled(false) + { + } + + cXineExternal::~cXineExternal() + { + StopExternal(); + } + + void cXineExternal::StartExternal() + { + cThread::Start(); + } + + void cXineExternal::StopExternal() + { + cMutexLock shutdownMutexLock(&m_shutdownMutex); + m_shutdown = true; + m_shutdownCondVar.Broadcast(); + + disconnect(); + } + + bool cXineExternal::readCommand() + { + int len = 0; + + while (!m_shutdown + && len < EXTERNAL_COMMAND_MAX_LEN) + { + cPoller poller(m_fdControl); + if (poller.Poll(100)) + { + void (* const sigPipeHandler)(int) = ::signal(SIGPIPE, SIG_IGN); + + int r = ::read(m_fdControl, &m_command[ len ], 1); + int myErrno = errno; + + ::signal(SIGPIPE, sigPipeHandler); + + if (r < 0) + { + if (EAGAIN == myErrno) + return false; + + if (m_connected) + { + errno = myErrno; + ::perror("vdr-xine: reading external command failed"); + } + } + + if (r <= 0) + { + disconnect(); + return false; + } + + if ('\n' == m_command[ len ]) + break; + + len += r; + } + } + + if (m_shutdown) + return false; + + if (len >= EXTERNAL_COMMAND_MAX_LEN + && m_command[ EXTERNAL_COMMAND_MAX_LEN - 1 ] != '\n') + { + ::fprintf(stderr, "vdr-xine: external command length exceeded!\n"); + disconnect(); + return false; + } + + m_command[ len ] = '\0'; + + return (len > 0); + } + + bool cXineExternal::writeResult(const char *result) + { + int l = ::strlen(result); + + while (l > 0) + { + void (* const sigPipeHandler)(int) = ::signal(SIGPIPE, SIG_IGN); + + int r = ::write(m_fdResult, &result, l); + int myErrno = errno; + + ::signal(SIGPIPE, sigPipeHandler); + + if (r < 0) + { + if (EAGAIN == myErrno) + continue; + + errno = myErrno; + ::perror("vdr-xine: writing external result failed"); + } + + if (r <= 0) + { + disconnect(); + return false; + } + + result += r; + l -= r; + } + + return true; + } + + void cXineExternal::cmdPlay(const char *const mrl) + { + if (m_xineLib) + { + m_xineLib->execFuncPlayExternal(mrl); + m_xineLib->execFuncWait(); + } + } + + void cXineExternal::enable(const bool enable) + { + cMutexLock lock(&m_enabledMutex); + + if (m_enabled && !enable) + cmdPlay(0); + + m_enabled = enable; + } + + void cXineExternal::externalStreamFinished() + { + ::fprintf(stderr, "vdr-xine: external stream finished\n"); + + cmdPlay(0); + + writeResult("play finished\n"); + } + + void cXineExternal::Action(void) + { + while (!m_shutdown) + { + if (!isConnected()) + { +// ::fprintf(stderr, "?"); + checkConnect(); + } + else + { +// ::fprintf(stderr, "!"); + + if (readCommand()) + { + cMutexLock lock(&m_enabledMutex); + + if (!m_enabled) + { + ::fprintf(stderr, "vdr-xine: external commands not allowed!\n"); + disconnect(); + } + else + { + if (0 == strncmp(m_command, "play ", 5)) + { + cmdPlay(m_command + 5); + } + else + { + ::fprintf(stderr, "vdr-xine: external command '%s' unknown!\n", m_command); + disconnect(); + } + } + } + } + + if (!m_shutdown) + { + cMutexLock shutdownMutexLock(&m_shutdownMutex); + if (!m_shutdown) + m_shutdownCondVar.TimedWait(m_shutdownMutex, 100); + } + } + } + + bool cXineExternal::isConnected() + { + return (-1 != m_fdControl); + } + + bool cXineExternal::checkConnect() + { + m_fdResult = ::open(m_xineLib->m_fifoNameExtResult.c_str() , O_WRONLY | O_NONBLOCK); + if (-1 == m_fdResult) + return false; + + ::fprintf(stderr, "vdr-xine: external connecting ...\n"); + + m_fdControl = ::open(m_xineLib->m_fifoNameExtControl.c_str(), O_RDONLY); + + ::fcntl(m_fdResult, F_SETFL, ~O_NONBLOCK & ::fcntl(m_fdResult, F_GETFL, 0)); + + if (isConnected()) + { + ::fprintf(stderr, "vdr-xine: external connected!\n"); + + m_connected = true; + return true; + } + + ::fprintf(stderr, "vdr-xine: external connect failed!\n"); + + return false; + } + + void cXineExternal::disconnect() + { + m_connected = false; + + cmdPlay(0); + + if (-1 != m_fdControl) + { + ::close(m_fdControl); + m_fdControl = -1; + } + + if (-1 != m_fdResult) + { + ::close(m_fdResult); + m_fdResult = -1; + } + } + +}; diff --git a/xineExternal.h b/xineExternal.h new file mode 100644 index 0000000..e450cae --- /dev/null +++ b/xineExternal.h @@ -0,0 +1,74 @@ + +#ifndef __XINEEXTERNAL_H +#define __XINEEXTERNAL_H + + + +#include "xineCommon.h" + +#include <vdr/thread.h> + + + +#define FIFO_NAME_EXT_CONTROL "/external.control" +#define FIFO_NAME_EXT_RESULT "/external.result" + +#define EXTERNAL_COMMAND_MAX_LEN (1000) + + + +namespace PluginXine +{ + + class cXineLib; + + class cXineExternal : public cThread + { + int m_fdControl; + int m_fdResult; + + bool m_shutdown; + cMutex m_shutdownMutex; + cCondVar m_shutdownCondVar; + + bool m_connected; + + cXineLib *m_xineLib; + + cMutex m_enabledMutex; + bool m_enabled; + + char m_command[ EXTERNAL_COMMAND_MAX_LEN ]; + + virtual void Action(void); + + bool checkConnect(); + bool isConnected(); + + bool readCommand(); + + void cmdPlay(const char *const mrl); + bool writeResult(const char *result); + + public: + cXineExternal(); + virtual ~cXineExternal(); + + void setXineLib(cXineLib *const xineLib) + { + m_xineLib = xineLib; + } + + void enable(const bool enable); + void externalStreamFinished(); + void disconnect(); + + void StartExternal(); + void StopExternal(); + }; + +}; + + + +#endif //__XINEEXTERNAL_H diff --git a/xineI18n.h b/xineI18n.h new file mode 100644 index 0000000..10a0740 --- /dev/null +++ b/xineI18n.h @@ -0,0 +1,1561 @@ + +#ifndef __XINEI18N_H +#define __XINEI18N_H + + + +#include "xineCommon.h" + + + +namespace PluginXine +{ + + static const tI18nPhrase Phrases[] = { + { "Software based playback using xine", + "Software-basierte Wiedergabe mittels xine", + "",// TODO + "Software per riproduzione tramite xine", + "Software-gebaseerde weergave met behulp van Xine", + "",// TODO + "",// TODO + "",// TODO + "Xine-näyttölaite", + "",// TODO + "",// TODO + "",// TODO + "Mjukvarubaserad uppspelning med xine", + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Live-TV SD video buffer [frames]", + "SD-Video Puffer für Live-TV [Bilder]", + "",// TODO + "Buffer TV dal vivo [frames]", + "Live-TV buffer [frames]", + "",// TODO + "",// TODO + "",// TODO + "TV-lähetyksen puskurointi [ruutua]", + "",// TODO + "",// TODO + "",// TODO + "Live-TV bufferstorlek [bildrutor]", + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Live-TV HD video buffer [frames]", + "HD-Video Puffer für Live-TV [Bilder]", + "",// TODO + "Buffer TV dal vivo [frames]", + "Live-TV buffer [frames]", + "",// TODO + "",// TODO + "",// TODO + "TV-lähetyksen puskurointi [ruutua]", + "",// TODO + "",// TODO + "",// TODO + "Live-TV bufferstorlek [bildrutor]", + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Live-TV audio buffer [frames]", + "Audio Puffer für Live-TV [Bilder]", + "",// TODO + "Buffer TV dal vivo [frames]", + "Live-TV buffer [frames]", + "",// TODO + "",// TODO + "",// TODO + "TV-lähetyksen puskurointi [ruutua]", + "",// TODO + "",// TODO + "",// TODO + "Live-TV bufferstorlek [bildrutor]", + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Buffer hysteresis [frames]", + "Puffer-Hysterese [Bilder]", + "",// TODO + "Buffer isteresi [frames]", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "Puskurin hystereesis [ruutua]", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Buffer monitoring duration [s]", + "Puffer-Überwachungsdauer [s]", + "",// TODO + "Durata monitoraggio buffer [s]", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "Puskurin monitoroinnin kesto [s]", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Buffer monitoring mode", + "Puffer-Überwachungsmodus", + "",// TODO + "Modalità monitoraggio buffer", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "Puskurin monitorointitapa", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Once", + "Einmal", + "",// TODO + "Una volta", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "kerran", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Continuous", + "Durchgehend", + "",// TODO + "Continuo", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "jatkuva", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "OSD display mode", + "Anzeigeart für OSD", + "",// TODO + "Modalità visualizzazione OSD", + "Weergave-type van de OSD", + "",// TODO + "",// TODO + "",// TODO + "Kuvaruutunäytön moodi", + "",// TODO + "",// TODO + "",// TODO + "OSD läge", + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "X11 overlay", + "Überlagern (X11)", + "",// TODO + "Overlay (X11)", + "X11 overlay", + "",// TODO + "",// TODO + "",// TODO + "overlay (X11)", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Blend clipped", + "Abschneiden", + "",// TODO + "Trasparenza fissa", + "Samenvoegen met afsnijden", + "",// TODO + "",// TODO + "",// TODO + "leikattu", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Blend scaled LQ", + "Einpassen LQ", + "",// TODO + "Trasparenza scalata LQ", + "Samenvoegen met lage kwaliteit", + "",// TODO + "",// TODO + "",// TODO + "skaalattu (LQ)", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Blend scaled HQ", + "Einpassen HQ", + "",// TODO + "Trasparenza scalata HQ", + "Samenvoegen met hoge kwaliteit", + "",// TODO + "",// TODO + "",// TODO + "skaalattu (HQ)", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Blend scaled SHQ", + "Einpassen SHQ", + "",// TODO + "Trasparenza scalata SHQ", + "Samenvoegen met superhoge kwaliteit", + "",// TODO + "",// TODO + "",// TODO + "skaalattu (SHQ)", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Blend scaled Auto", + "Einpassen Auto", + "",// TODO + "Trasparenza scalata in automatico", + "Automatische samenvoeg-modus", + "",// TODO + "",// TODO + "",// TODO + "skaalattu (auto)", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "OSD gamma correction [ 123 => 1.23 ]", + "OSD Gamma Korrektur [ 123 => 1.23 ]", + "",// TODO + "Correzione gamma OSD [ 123 => 1.23 ]", + "OSD gamma correctie [ 123 => 1.23 ]", + "",// TODO + "",// TODO + "",// TODO + "Kuvaruutunäytön gammakorjaus [123 => 1.23]", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Audio mode", + "Audio-Modus", + "",// TODO + "Modalità audio", + "Audio-modus", + "",// TODO + "",// TODO + "",// TODO + "Monikanavaäänet (Dolby)", + "",// TODO + "",// TODO + "",// TODO + "Ljud läge", + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Dolby off", + "Dolby aus", + "",// TODO + "Dolby disattivo", + "Dolby uitgeschakeld", + "",// TODO + "",// TODO + "",// TODO + "pois", + "",// TODO + "",// TODO + "",// TODO + "Dolby av", + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Dolby on", + "Dolby ein", + "",// TODO + "Dolby attivo", + "Dolby ingeschakeld", + "",// TODO + "",// TODO + "",// TODO + "päällä", + "",// TODO + "",// TODO + "",// TODO + "Dolby på", + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Control xine's volume", + "Lautstärke in xine steuern", + "",// TODO + "Regolazione volume xine", + "Xine's volume via VDR besturen", + "",// TODO + "",// TODO + "",// TODO + "Käytä Xinen äänenvoimakkuussäätöä", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "No", + "Nein", + "",// TODO + "No", + "Nee", + "",// TODO + "",// TODO + "",// TODO + "ei", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Yes", + "Ja", + "",// TODO + "Sì", + "Ja", + "",// TODO + "",// TODO + "",// TODO + "kyllä", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Muting", + "Stummschalten", + "",// TODO + "Muto", + "Geluid dempen", + "",// TODO + "",// TODO + "",// TODO + "Mykistys", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Ignore", + "Ignorieren", + "",// TODO + "Ignora", + "Negeren", + "",// TODO + "",// TODO + "",// TODO + "sivuuta", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Simulate", + "Simulieren", + "",// TODO + "Simula", + "Simuleren", + "",// TODO + "",// TODO + "",// TODO + "simuloi", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Execute", + "Ausführen", + "",// TODO + "Esegui", + "Uitvoeren", + "",// TODO + "",// TODO + "",// TODO + "suorita", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Get primary device when xine connects", + "Automatisch zum primären Interface werden", + "",// TODO + "Mostra interfaccia primaria all'avvio di xine", + "Xine als primair dvb-apparaat gebruiken", + "",// TODO + "",// TODO + "",// TODO + "Käytä automaattisesti ensisijaisena sovittimena", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Support semi transparent colors", + "Unterstützung für halbtransparente Farben", + "",// TODO + "Supporto colori semi trasparenti", + "Ondersteun semi-transparante kleuren", + "",// TODO + "",// TODO + "",// TODO + "Tue puoliläpinäkyviä värejä", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Switching primary DVB to %s...", // %s will be substituted by the plugin name, i. e. 'xine'. + "Primäres Interface wird zu %s umgeschaltet...", + "", // TODO "Preklapljanje primarne DVB naprave...", + "Cambio scheda DVB primaria a %s...", + "", // TODO "Eerste DVB-kaart wordt omgeschakeld...", + "", // TODO "A mudar interface DVB primário...", + "", // TODO "Changement de carte DVB primaire...", + "", // TODO "Bytter første DVB-enhet...", + "Vaihdetaan ensisijaiseksi DVB-sovittimeksi %s...", + "", // TODO "Przê³±czam na pierwszy interfejs DVB...", + "", // TODO "Cambiando el interfaz DVB primario...", + "", // TODO "Ç êýñéá DVB êÜñôá áëëÜæåé...", + "", // TODO "Byter primär DVB enhet...", + "", // TODO "Comut dispozitiv DVB primar...", + "", // TODO "Primér Interface átkapcsolva...", + "", // TODO "Canviant a la interfície DVB primària...", +#if APIVERSNUM >= 10302 + "", // TODO "ÁÜÕÝÐ ÞáÝÞÒÝÞÓÞ DVB-ãáâàÞÙáâÒÐ...", +#if APIVERSNUM >= 10307 + "", // TODO "Preklapanje primarnog DVB ureðaja...", +#if APIVERSNUM >= 10313 + "", // TODO "Esmase DVB seadme ümberlülitus...", +#if APIVERSNUM >= 10316 + "", // TODO "Skifter primær DVB enhed...", +#if APIVERSNUM >= 10342 + "", // TODO "Pøepnout primární DVB...", +#endif +#endif +#endif +#endif +#endif + }, + { "Switched primary DVB back from %s", // %s will be substituted by the plugin name, i. e. 'xine'. + "Primäres Interface wurde von %s zurückgeschaltet", + "", // TODO "Preklapljanje primarne DVB naprave", + "Ritorno a scheda DVD primaria da %s", + "", // TODO "Eerste DVB-kaart wordt omgeschakeld", + "", // TODO "A mudar interface DVB primário", + "", // TODO "Changement de carte DVB primaire", + "", // TODO "Bytter første DVB-enhet", + "Ensisijainen DVB-sovitin vaihdettu takaisin %s:stä", + "", // TODO "Przê³±czam na pierwszy interfejs DVB", + "", // TODO "Cambiando el interfaz DVB primario", + "", // TODO "Ç êýñéá DVB êÜñôá áëëÜæåé", + "", // TODO "Byter primär DVB enhet", + "", // TODO "Comut dispozitiv DVB primar", + "", // TODO "Primér Interface átkapcsolva", + "", // TODO "Canviant a la interfície DVB primària", +#if APIVERSNUM >= 10302 + "", // TODO "ÁÜÕÝÐ ÞáÝÞÒÝÞÓÞ DVB-ãáâàÞÙáâÒÐ", +#if APIVERSNUM >= 10307 + "", // TODO "Preklapanje primarnog DVB ureðaja", +#if APIVERSNUM >= 10313 + "", // TODO "Esmase DVB seadme ümberlülitus", +#if APIVERSNUM >= 10316 + "", // TODO "Skifter primær DVB enhed", +#if APIVERSNUM >= 10342 + "", // TODO "Pøepnout primární DVB", +#endif +#endif +#endif +#endif +#endif + }, + { "Yes (by hardware)", + "Ja (in Hardware)", + "",// TODO + "Sì (tramite hardware)", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Yes (by software)", + "Ja (in Software)", + "",// TODO + "Sì (tramite software)", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "4:3 image zoom X [%]", + "4:3 Bilder X-Skalierung [%]", + "",// TODO + "Ingrandimento X [%] immagine 4:3", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "4:3 image zoom Y [%]", + "4:3 Bilder Y-Skalierung [%]", + "",// TODO + "Ingrandimento Y [%] immagine 4:3", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "16:9 image zoom X [%]", + "16:9 Bilder X-Skalierung [%]", + "",// TODO + "Ingrandimento X [%] immagine 16:9", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "16:9 image zoom Y [%]", + "16:9 Bilder Y-Skalierung [%]", + "",// TODO + "Ingrandimento Y [%] immagine 16:9", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Connection interacts with EIT scanner", + "Verbindung interagiert mit EIT-Scanner", + "",// TODO + "La connessione interagisce con lo scanner EIT", + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, +/* + { "Replay prebuffer [frames]", + "Vorpuffer für Wiedergabe [Bilder]", + "",// TODO + "Riproduci prebuffer [frames]", + "Pre-buffer voor afspelen van opnamen", + "",// TODO + "",// TODO + "",// TODO + "Toiston puskurointi [ruutua]", + "",// TODO + "",// TODO + "",// TODO + "Uppspelningsbuffer [bildrutor]", + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Default settings optimized for", + "Standard-Einstellungen optimiert für", + "",// TODO + "Impostazioni predefinite ottimizzate per", + "Standaard instellingen geoptimaliseerd voor", + "",// TODO + "",// TODO + "",// TODO + "Oletusasetukset optimoitu", + "",// TODO + "",// TODO + "",// TODO + "Standardinställning optimerad för", + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Live-TV", + "Live-TV", + "",// TODO + "TV dal vivo", + "Live-TV", + "",// TODO + "",// TODO + "",// TODO + "TV-lähetykselle", + "",// TODO + "",// TODO + "",// TODO + "Live-TV", + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Replay", + "Wiedergabe", + "",// TODO + "Riproduci", + "Opnamen afspelen", + "",// TODO + "",// TODO + "",// TODO + "toistolle", + "",// TODO + "",// TODO + "",// TODO + "Uppspelning", + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Scaled", + "Skaliert", + "",// TODO + "Scalato", + "Geschaald", + "",// TODO + "",// TODO + "",// TODO + "skaalattu", + "",// TODO + "",// TODO + "",// TODO + "Skalad", + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Unscaled", + "Unskaliert", + "",// TODO + "Non scalato", + "Ongeschaald", + "",// TODO + "",// TODO + "",// TODO + "skaalamaton", + "",// TODO + "",// TODO + "",// TODO + "Normal", + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Settings are now optimized for 'Replay'", + "Einstellungen nun optimiert für 'Wiedergabe'", + "",// TODO + "Ora le impostazioni sono ottimizzate per la 'Riproduzione'", + "Instellingen zijn nu geoptimaliseerd voor 'Opnamen afspelen'", + "",// TODO + "",// TODO + "",// TODO + "Asetukset optimoitu toistolle", + "",// TODO + "",// TODO + "",// TODO + "Inställningarna är nu optimerade för 'Uppspelning'", + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "Settings are now optimized for 'Live-TV'", + "Einstellungen nun optimiert für 'Live-TV'", + "",// TODO + "Ora le impostazioni sono ottimizzate per la 'TV dal vivo'", + "Instellingen zijn nu geoptimaliseerd voor 'Live-TV'", + "",// TODO + "",// TODO + "",// TODO + "Asetukset optimoitu TV-lähetykselle", + "",// TODO + "",// TODO + "",// TODO + "Inställningarna är nu optimerade för 'Live-TV'", + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "xine - Choose optimized settings for 'Live-TV'", + "xine - Wähle optimierte Einstellungen für 'Live-TV'", + "",// TODO + "xine - Scegli impostazioni ottimizzate per la 'TV dal vivo'", + "xine - Kies optimale instellingen voor 'Live-TV'", + "",// TODO + "",// TODO + "",// TODO + "xine - Valitse optimoidut asetukset TV-lähetykselle", + "",// TODO + "",// TODO + "",// TODO + "xine - Välj optimerade inställningar för 'Live-TV'", + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, + { "xine - Choose optimized settings for 'Replay'", + "xine - Wähle optimierte Einstellungen für 'Wiedergabe'", + "",// TODO + "xine - Scegli impostazioni ottimizzate per la 'Riproduzione'", + "xine - Kies optimale instellingen voor 'Opnamen afspelen'", + "",// TODO + "",// TODO + "",// TODO + "xine - Valitse optimoidut asetukset toistolle", + "",// TODO + "",// TODO + "",// TODO + "xine - Välj optimerade inställningar för 'Live-TV'", + "",// TODO + "",// TODO + "",// TODO +#if APIVERSNUM >= 10302 + "",// TODO +#if APIVERSNUM >= 10307 + "",// TODO +#if APIVERSNUM >= 10313 + "",// TODO +#if APIVERSNUM >= 10316 + "",// TODO +#if APIVERSNUM >= 10342 + "",// TODO +#endif +#endif +#endif +#endif +#endif + }, +*/ + { NULL } + }; + +}; + + + +#endif //__XINEI18N_H + diff --git a/xineLib.c b/xineLib.c new file mode 100644 index 0000000..176a173 --- /dev/null +++ b/xineLib.c @@ -0,0 +1,4596 @@ + +#include "xineCommon.h" +#include <endian.h> + +#include <vdr/plugin.h> +#include <vdr/eitscan.h> + +#include "xineLib.h" +#include "xineOsd.h" +#include "xineSettings.h" + +#include <netinet/tcp.h> + + + +#define ASSERT_PALETTE(x) +//#define ASSERT_PALETTE(x) x + +#define PROFILE_SCALING(x) +//#define PROFILE_SCALING(x) x + +#define DLOG(x) +//#define DLOG(x) cDlog dlog(x) + + + +namespace PluginXine +{ + class cDlog + { + char m_s[ 100 ]; + + public: + cDlog(const char *const s) + { + ::strncpy(m_s, s, sizeof(m_s)); + m_s[sizeof(m_s) - 1] = 0; + + ::fprintf(stderr, ">>> %s\n", m_s); + } + + ~cDlog() + { + ::fprintf(stderr, "<<< %s\n", m_s); + } + }; + + + + int cXineBitmapAdapter::X0(void) const + { +#if APIVERSNUM < 10307 + return 0; +#else + return m_pBitmap->X0(); +#endif + } + + int cXineBitmapAdapter::Y0(void) const + { +#if APIVERSNUM < 10307 + return 0; +#else + return m_pBitmap->Y0(); +#endif + } + + int cXineBitmapAdapter::Width(void) const + { + return m_pBitmap->Width(); + } + + int cXineBitmapAdapter::Height(void) const + { + return m_pBitmap->Height(); + } + + bool cXineBitmapAdapter::Dirty(int &x1, int &y1, int &x2, int &y2) + { + return m_pBitmap->Dirty(x1, y1, x2, y2); + } + + void cXineBitmapAdapter::Clean(void) + { + m_pBitmap->Clean(); + } + + const tColor *cXineBitmapAdapter::Colors(int &NumColors) const + { +#if APIVERSNUM < 10307 + return m_pBitmap->AllColors(NumColors); +#else + return m_pBitmap->Colors(NumColors); +#endif + } + + const uint8_t *cXineBitmapAdapter::Data(int x, int y) const + { + return (uint8_t *)m_pBitmap->Data(x, y); + } + + int cXineBitmapAdapter::SizeOfPixel() const + { + return sizeof(tIndex); + } + + int cXineBitmapAdapter::SizeOfStride() const + { + return m_pBitmap->Width() * SizeOfPixel(); + } + + + +#if APIVERSNUM >= 10717 + + cXinePixmapMemoryAdapter::cXinePixmapMemoryAdapter(const cPixmapMemory *const pPixmapMemory) + : m_pPixmapMemory((cPixmapMemory *)pPixmapMemory) + { + } + + int cXinePixmapMemoryAdapter::X0(void) const + { + return m_pPixmapMemory->ViewPort().X() + m_pPixmapMemory->DrawPort().X(); + } + + int cXinePixmapMemoryAdapter::Y0(void) const + { + return m_pPixmapMemory->ViewPort().Y() + m_pPixmapMemory->DrawPort().Y(); + } + + int cXinePixmapMemoryAdapter::Width(void) const + { + return m_pPixmapMemory->DrawPort().Width(); + } + + int cXinePixmapMemoryAdapter::Height(void) const + { + return m_pPixmapMemory->DrawPort().Height(); + } + + bool cXinePixmapMemoryAdapter::Dirty(int &x1, int &y1, int &x2, int &y2) + { + const cRect &dirty = m_pPixmapMemory->DirtyViewPort(); + if (dirty.IsEmpty()) + return false; + + x1 = dirty.Left() - X0(); + x2 = dirty.Right() - X0(); + y1 = dirty.Top() - Y0(); + y2 = dirty.Bottom() - Y0(); + return true; + } + + void cXinePixmapMemoryAdapter::Clean(void) + { + //m_pPixmapMemory->SetClean(); + } + + const tColor *cXinePixmapMemoryAdapter::Colors(int &NumColors) const + { + NumColors = 0; + return 0; + } + + const uint8_t *cXinePixmapMemoryAdapter::Data(int x, int y) const + { + return m_pPixmapMemory->Data() + y * SizeOfStride() + x * SizeOfPixel(); + } + + int cXinePixmapMemoryAdapter::SizeOfPixel() const + { + return sizeof(tColor); + } + + int cXinePixmapMemoryAdapter::SizeOfStride() const + { + return m_pPixmapMemory->DrawPort().Width() * SizeOfPixel(); + } + +#endif + + + +#if APIVERSNUM < 10307 + + bool cXineLib::OpenWindow(cXineOsd *const xineOsd, cWindow *Window, const int maxOsdWidth, const int maxOsdHeight) + { + execFuncOsdNew(maxOsdWidth, maxOsdHeight, Window->Handle(), xineOsd->X0() + Window->X0(), xineOsd->Y0() + Window->Y0(), Window->Width(), Window->Height()); + + CommitWindow(xineOsd, Window, false); + + return true; + } + + void cXineLib::CommitWindow(cXineOsd *const xineOsd, cWindow *Window, const bool optimize /* = true */) + { + int firstColorIndex = -1; + int lastColorIndex = -1; + const eDvbColor *modifiedColors = (optimize ? Window->NewColors(firstColorIndex, lastColorIndex) : Window->AllColors(lastColorIndex)); + + if (!optimize) + { + firstColorIndex = 0; + lastColorIndex--; + } + + if (modifiedColors) + { + do + { + for (int colorIndex = firstColorIndex + ; colorIndex <= lastColorIndex + ; colorIndex++, modifiedColors++) + { + execFuncSetColor(Window->Handle(), colorIndex, filterAlpha(*modifiedColors)); + } + } + while (0 != (modifiedColors = Window->NewColors(firstColorIndex, lastColorIndex))); + } + +// xine_osd_draw_bitmap(m_osdWindow[ Window->Handle() ], (uint8_t *)Window->Data(0, 0), 0, 0, Window->Width(), Window->Height(), &m_mapToXinePalette[ Window->Handle() ][ 0 ]); + + int x1 = 0, y1 = 0, x2 = Window->Width() - 1, y2 = Window->Height() - 1; + + if (!optimize + || modifiedColors + || Window->Dirty(x1, y1, x2, y2)) + { +// fprintf(stderr, "dirty area: (%d, %d) - (%d, %d), window size: (%d, %d)\n", x1, y1, x2, y2, Window->Width(), Window->Height()); + + execFuncOsdDrawBitmap(Window->Handle(), (const uchar *)Window->Data(x1, y1), 1, x1, y1, x2 - x1 + 1, y2 - y1 + 1, Window->Width(), 0); + + if (m_osdWindowVisible[ Window->Handle() ]) + execFuncOsdShow(Window->Handle()); + } + } + + void cXineLib::ShowWindow(cXineOsd *const xineOsd, cWindow *Window) + { + execFuncOsdShow(Window->Handle()); + + m_osdWindowVisible[ Window->Handle() ] = true; + } + + void cXineLib::HideWindow(cXineOsd *const xineOsd, cWindow *Window, bool Hide) + { + if (Hide) + { + execFuncOsdHide(Window->Handle()); + } + else + { + execFuncOsdShow(Window->Handle()); + } + + m_osdWindowVisible[ Window->Handle() ] = !Hide; + } + + void cXineLib::MoveWindow(cXineOsd *const xineOsd, cWindow *Window, int x, int y) + { + execFuncOsdSetPosition(Window->Handle(), xineOsd->X0() + x, xineOsd->Y0() + y); + + if (m_osdWindowVisible[ Window->Handle() ]) + execFuncOsdShow(Window->Handle()); + } + + void cXineLib::CloseWindow(cXineOsd *const xineOsd, cWindow *Window) + { + CloseWindow(xineOsd, Window->Handle()); + } + + void cXineLib::CloseWindow(cXineOsd *const xineOsd, int Window) + { + if (m_osdWindowVisible[ Window ]) + execFuncOsdHide(Window); + + execFuncOsdFree(Window); + + m_osdWindowVisible[ Window ] = false; + } + +#else + + bool cXineLib::execFuncOsdNew(const int maxOsdWidth, const int maxOsdHeight, const eNeedsScaling needsScaling, const int videoLeft, const int videoTop, const int videoWidth, const int videoHeight, int window, int x, int y, int width, int height) + { + m_osdFlushRequired = true; + + if (!needsScaling) + return execFuncOsdNew(window, videoLeft + x, videoTop + y, width, height, maxOsdWidth, maxOsdHeight); + + int x0 = x * videoWidth / maxOsdWidth; + int w0 = ((x + width) * videoWidth - 1 + maxOsdWidth) / maxOsdWidth - x0; + int y0 = y * videoHeight / maxOsdHeight; + int h0 = ((y + height) * videoHeight - 1 + maxOsdHeight) / maxOsdHeight - y0; + + return execFuncOsdNew(window, videoLeft + x0, videoTop + y0, w0, h0, maxOsdWidth, maxOsdHeight); + } + + static inline int intersection(int a0, int a1, int b0, int b1) + { + if (a0 < b0) + a0 = b0; + + if (a1 > b1) + a1 = b1; + + int d = a1 - a0; + if (d <= 0) + return 0; + + return d; + } + +//#define CRT_GAMMA (2.2) +#define FIX_POINT_BITS (6) // * 3 + 8 + ceil(ld(ceil(1/scale_x) * ceil(1/scale_y))) < 32 !!! +#define FIX_POINT_FACTOR (1 << FIX_POINT_BITS) + + static uint16_t data_delinearize[ 255 * FIX_POINT_FACTOR + 1 ]; + + static int init_delinearize(int crtGamma) + { + for (int i = 0; i <= 255 * FIX_POINT_FACTOR; i++) + { + data_delinearize[ i ] = (int)(::exp(::log(i / (255.0 * FIX_POINT_FACTOR)) / (crtGamma / (double)cXineSettings::monitorGammaBase)) * (255.0 * FIX_POINT_FACTOR) + .5); + +// ::fprintf(stderr, "de_lin: %d => %d\n", i, data_delinearize[ i ]); + + ASSERT_PALETTE(assert(data_delinearize[ i ] <= 255 * FIX_POINT_FACTOR);) + } + + return 0; + } + + static inline uint16_t delinearize(uint16_t i) + { +// static int init = init_delinearize(); +// (void)init; + + ASSERT_PALETTE(assert(i <= 255 * FIX_POINT_FACTOR);) + + return data_delinearize[ i ]; + } + + static uint16_t data_linearize[ 255 * FIX_POINT_FACTOR + 1 ]; + + static int init_linearize(int crtGamma) + { + for (int i = 0; i <= 255 * FIX_POINT_FACTOR; i++) + { + data_linearize[ i ] = (int)(::exp(::log(i / (255.0 * FIX_POINT_FACTOR)) * (crtGamma / (double)cXineSettings::monitorGammaBase)) * (255.0 * FIX_POINT_FACTOR) + .5); + +// ::fprintf(stderr, "lin: %d => %d\n", i, data_linearize[ i ]); + + ASSERT_PALETTE(assert(data_linearize[ i ] <= 255 * FIX_POINT_FACTOR);) + } + + return 0; + } + + static inline uint16_t linearize(uint16_t i) + { +// static int init = init_linearize(); +// (void)init; + + ASSERT_PALETTE(assert(i <= 255 * FIX_POINT_FACTOR);) + + return data_linearize[ i ]; + } + + typedef uint64_t tColor16; + + static inline void mkLinearColor(const tColor &color, tColor16 &color16) + { + const uint8_t *src = (const uint8_t *)&color; + uint16_t *dst = (uint16_t *)&color16; + +#if __BYTE_ORDER == LITTLE_ENDIAN + *dst++ = linearize(FIX_POINT_FACTOR * *src++); + *dst++ = linearize(FIX_POINT_FACTOR * *src++); + *dst++ = linearize(FIX_POINT_FACTOR * *src++); + *dst++ = (FIX_POINT_FACTOR * *src++); +#else + *dst++ = (FIX_POINT_FACTOR * *src++); + *dst++ = linearize(FIX_POINT_FACTOR * *src++); + *dst++ = linearize(FIX_POINT_FACTOR * *src++); + *dst++ = linearize(FIX_POINT_FACTOR * *src++); +#endif +// ::fprintf(stderr, "c: %08x, c16: %016llx\n", color, color16); + } + + static inline void mkDelinearColor(const tColor16 &color16, tColor &color) + { + const uint16_t *src = (const uint16_t *)&color16; + uint8_t *dst = (uint8_t *)&color; + +#if __BYTE_ORDER == LITTLE_ENDIAN + *dst++ = delinearize(*src++) / FIX_POINT_FACTOR; + *dst++ = delinearize(*src++) / FIX_POINT_FACTOR; + *dst++ = delinearize(*src++) / FIX_POINT_FACTOR; + *dst++ = (*src++) / FIX_POINT_FACTOR; +#else + *dst++ = (*src++) / FIX_POINT_FACTOR; + *dst++ = delinearize(*src++) / FIX_POINT_FACTOR; + *dst++ = delinearize(*src++) / FIX_POINT_FACTOR; + *dst++ = delinearize(*src++) / FIX_POINT_FACTOR; +#endif +// ::fprintf(stderr, "c16: %016llx, c: %08x\n", color16, color); + } + + class cXinePalette + { +#define maxSize 65521 + public: + class cEntry + { + friend class cXinePalette; + + int m_index; + public: + tColor m_color; + uint32_t m_count; + private: + + cEntry *m_prev, *m_next; + + static int comparePointersByCountDesc(const void *lhs, const void *rhs) + { + const cEntry *cLhs = *(const cEntry **)lhs; + const cEntry *cRhs = *(const cEntry **)rhs; + + if (cLhs->m_count < cRhs->m_count) + return 1; + + if (cLhs->m_count > cRhs->m_count) + return -1; + + return 0; + } + + static long colorDistance(const tColor c1, const tColor c2) + { + int c1a = (c1 & 0xff000000) >> 0x18; + int c1r = (c1 & 0x00ff0000) >> 0x10; + int c1g = (c1 & 0x0000ff00) >> 0x08; + int c1b = (c1 & 0x000000ff) >> 0x00; + + int c2a = (c2 & 0xff000000) >> 0x18; + int c2r = (c2 & 0x00ff0000) >> 0x10; + int c2g = (c2 & 0x0000ff00) >> 0x08; + int c2b = (c2 & 0x000000ff) >> 0x00; + + long r,g,b,a; + long rmean; + + rmean = (c1r + c2r) / 2; + r = c1r - c2r; + g = c1g - c2g; + b = c1b - c2b; + a = c1a - c2a; + + return (((512 + rmean) * r * r) >> 8) + 4 * g * g + (((767 - rmean) * b * b) >> 8) + a * a; + } + + public: + int getIndex() const + { + return m_index; + } + }; + + private: + cEntry m_palette[ maxSize ]; + int m_numEntries; + + cEntry *m_lut[ 257 ][ 257 ]; + + bool m_warned; + + cEntry *m_lookup[ maxSize ]; + const cEntry *const *const m_lookupLimit; + + const int m_numPalColors; + + cEntry m_head, m_tail; + + cEntry *(cXinePalette::*m_add)(const tColor &color); + + cEntry *addHash(const tColor &color) + { + cEntry **lookup = m_lookup + (color % maxSize); + cEntry *entry = *lookup; + + while (entry) + { + if (color == entry->m_color) + { + entry->m_count++; + return entry; + } + + if (++lookup >= m_lookupLimit) + lookup = m_lookup; + + entry = *lookup; + } + +// ::fprintf(stderr, "i: %d, c: %08x\n", m_numEntries, color); + + entry = m_palette + m_numEntries++; + *lookup = entry; + + entry->m_color = color; + entry->m_count = 1; + + return entry; + } + + void addHead(cEntry *const node) + { + node->m_next = m_head.m_next; + node->m_prev = &m_head; + + node->m_next->m_prev = node; + node->m_prev->m_next = node; + } + + void remNode(cEntry *const node) + { + node->m_next->m_prev = node->m_prev; + node->m_prev->m_next = node->m_next; + } + + cEntry *addLru(const tColor &color) + { + cEntry *entry = m_head.m_next; + + while (entry != &m_tail) + { + if (color == entry->m_color) + { + if (entry->m_prev != &m_head) + { + remNode(entry); + addHead(entry); + } + + entry->m_count++; + return entry; + } + + entry = entry->m_next; + } + +// ::fprintf(stderr, "i: %d, c: %08x\n", m_numEntries, color); + + entry = m_palette + m_numEntries++; + addHead(entry); + + entry->m_color = color; + entry->m_count = 1; + + return entry; + } + + public: + uint8_t m_vdrMapping[ 256 ]; + + cXinePalette(cXineLib *xineLib, const int numVdrColors, const tColor *const colors = 0, const int numPalColors = 0, const tColor *const palColors = 0, const int transparentIndex = -1) + : m_numEntries(0) + , m_warned(false) + , m_lookupLimit(m_lookup + sizeof (m_lookup) / sizeof (*m_lookup)) + , m_numPalColors(numPalColors) + { +// fprintf(stderr, "numVdrColors: %d, numPalColors: %d\n", numVdrColors, numPalColors); + + if (numVdrColors >= 16) + { + PROFILE_SCALING(::fprintf(stderr, "hash: ");) + + ::memset(m_lookup, 0, sizeof (m_lookup)); + + m_add = &cXinePalette::addHash; + } + else + { + PROFILE_SCALING(::fprintf(stderr, "lru: ");) + + m_head.m_prev = 0; + m_head.m_next = &m_tail; + + m_tail.m_prev = &m_head; + m_tail.m_next = 0; + + m_add = &cXinePalette::addLru; + } + + ::memset(m_lut, 0, sizeof (m_lut)); + + if (colors) + { + for (int i = 0; i < numVdrColors; i++) + { + cEntry *e = addUnused(xineLib->filterAlpha(colors[ i ])); + m_vdrMapping[ i ] = e - m_palette; + } + } + + if (transparentIndex != -1) + { + ASSERT_PALETTE(assert(transparentIndex == m_numEntries);) + + addUnused(clrTransparent); + } + + if (palColors) + { + for (int i = 0; i < numPalColors; i++) + addUnused(palColors[ i ]); + } + } + + cEntry *add(const tColor &color) + { + if (m_numEntries < maxSize) + return (this->*m_add)(color); + + ASSERT_PALETTE( + { + if (!m_warned) + { + m_warned = true; + + ::fprintf(stderr, "warning: cXinePalette is full!\n"); + } + }) + + return 0; + } + + void addPersistent(const tColor &color) + { + cEntry *const entry = add(color); + assert(entry); + + entry->m_count = 0x7fffffff; + } + + cEntry *addUnused(const tColor &color) + { + cEntry *const entry = add(color); + assert(entry); + + entry->m_count = 0; + + return entry; + } + + cEntry *add(const uint16_t lutIndex) + { + ASSERT_PALETTE(assert(lutIndex <= 256);) + ASSERT_PALETTE(assert(lutIndex < m_numEntries);) + + cEntry *const entry = &m_palette[ lutIndex ]; + entry->m_count++; + + return entry; + } + + cEntry *add(const uint16_t lutIndexA, const uint16_t lutIndexB) + { + ASSERT_PALETTE(assert(lutIndexA <= 256);) + ASSERT_PALETTE(assert(lutIndexB <= 256);) + + cEntry *&entry = m_lut[ lutIndexA ][ lutIndexB ]; + + if (!entry) + { + if (lutIndexA == lutIndexB) + { + entry = add(lutIndexA); + return entry; + } + + tColor16 colorA, colorB; + + ASSERT_PALETTE(assert(lutIndexA < m_numEntries);) + ASSERT_PALETTE(assert(lutIndexB < m_numEntries);) + + mkLinearColor(m_palette[ lutIndexA ].m_color, colorA); + mkLinearColor(m_palette[ lutIndexB ].m_color, colorB); + + tColor16 colorC; + tColor color; + + //colorC = (((colorA ^ colorB) & 0xfffefffefffefffeULL) >> 1) + (colorA & colorB); // doesn't work with ULL and -O2: buggy compiler??? + { + uint16_t *pA = &alias_cast<uint16_t>(colorA), *pB = &alias_cast<uint16_t>(colorB), *pC = &alias_cast<uint16_t>(colorC); + for (int i = 0; i < 4; i++) + { + *pC = (((*pA ^ *pB) & 0xfffe) >> 1) + (*pA & *pB); + + pA++; + pB++; + pC++; + } + } + +// fprintf(stderr, "a: 0x%016llx, b: 0x%016llx, c: 0x%016llx\n", colorA, colorB, colorC); + + ASSERT_PALETTE( + { + uint16_t *c = (uint16_t *)&colorC; + + if (c[ 0 ] > (255 * FIX_POINT_FACTOR) + || c[ 1 ] > (255 * FIX_POINT_FACTOR) + || c[ 2 ] > (255 * FIX_POINT_FACTOR) + || c[ 3 ] > (255 * FIX_POINT_FACTOR)) + { + ::fprintf(stderr, "%d, 0x%08x, %d, 0x%08x\n, A: 0x%016llx\n, B: 0x%016llx\n, C: 0x%016llx\n, %d\n", lutIndexA, m_palette[ lutIndexA ].m_color, lutIndexB, m_palette[ lutIndexB ].m_color, colorA, colorB, colorC, -1); + } + }) + + mkDelinearColor(colorC, color); +/* + fprintf(stderr, "a: 0x%08lx, b: 0x%08lx, c: 0x%08lx\n" + , m_palette[ lutIndexA ].m_color + , m_palette[ lutIndexB ].m_color + , color); +*/ + m_lut[ lutIndexB ][ lutIndexA ] = entry = add(color); + return entry; + } + + entry->m_count++; + return entry; + } + + int getNum() const + { + return m_numEntries; + } + + tColor *getVdrColors(int &numColors) + { + int usedColors = numColors = m_numEntries; + int usedIndex = numColors - 1; + + if (numColors > 256) + { + usedColors = 0; + usedIndex = 0; + + { + cEntry *src = m_palette; + + for (int i = 0; i < m_numEntries; i++) + { + if ((src++)->m_count > 0) + { + usedColors++; + usedIndex = i; + } + } + } + + if (usedColors > 256) + { + numColors = 256; + } + else + { + numColors = usedColors; + + if (usedIndex < 256) + { + if (numColors <= usedIndex) + numColors = usedIndex + 1; + } + } + } + + if (0 == numColors) + return 0; + + tColor *const colors = new tColor[ numColors ]; + +// ASSERT_PALETTE(::fprintf(stderr, "used: %d, nc: %d, index: %d, ", usedColors, numColors, usedIndex);) + + if (usedColors > numColors) + { + cEntry *sorted[ maxSize ]; + + { + cEntry *src = m_palette; + cEntry **dst = sorted; + + for (int i = 0; i < m_numEntries; i++) + { + if (src->m_count > 0) + *dst++ = src; + + src++; + } + + ASSERT_PALETTE(assert((dst - sorted) == usedColors);) + } + + ::qsort(sorted, usedColors, sizeof (*sorted), cEntry::comparePointersByCountDesc); + + cEntry **src = sorted; + + { + tColor *dst = colors; + + for (int i = 0; i < numColors; i++) + { + *dst++ = (*src)->m_color; + + (*src++)->m_index = i; + } + } + + for (int i = numColors; i < usedColors; i++) + { + cEntry *const entry = *src++; + + { + cEntry **src = sorted; + + tColor bestMatchDelta = cEntry::colorDistance((*src)->m_color, entry->m_color); + entry->m_index = 0; + + for (int k = 1; k < numColors; k++) + { + ++src; + + tColor delta = cEntry::colorDistance((*src)->m_color, entry->m_color); + + if (bestMatchDelta > delta) + { + bestMatchDelta = delta; + entry->m_index = k; + } + } + } + } + } + else + { + cEntry *src = m_palette; + tColor *dst = colors; + + int index = 0; + + if (usedIndex > m_numPalColors) + { + int num = m_numEntries; + if (num > 256) + num = usedIndex + 1; + + for (int i = 0; i < num; i++) + { + if (src->m_count > 0 + || m_numEntries < 256) + { + *dst++ = src->m_color; + src->m_index = index++; + +// fprintf(stderr, "%3d: 0x%08x\n", index - 1, src->m_color); + + ASSERT_PALETTE(assert(index <= numColors);) + } + + src++; + } + } + else + { + ASSERT_PALETTE(assert(usedIndex < numColors);) + + for (int i = 0; i < numColors; i++) + { + *dst++ = src->m_color; + (src++)->m_index = i; + } + } + } + + return colors; + } + }; + + static inline double tt(const timeval &tb, const timeval &ta) + { + return (tb.tv_sec + tb.tv_usec * 1e-6) - (ta.tv_sec + ta.tv_usec * 1e-6); + } + + template <class T = int> + class cBresenham + { + const int m_yInc; + const int m_dx; + const int m_dy; + + int m_eps; + T m_y; + + public: + cBresenham(const int dy, const int dx, const int eps, T const y0 = 0) + : m_yInc(1) + , m_dx(dx) + , m_dy(dy) + , m_eps(eps - m_dx) + , m_y(y0) + { + } + + cBresenham(const int yInc, const int dy, const int dx, const int eps, T const y0 = 0) + : m_yInc(yInc) + , m_dx(dx) + , m_dy(dy) + , m_eps(eps - m_dx) + , m_y(y0) + { + } + + int eps() const + { + return m_eps; + } + + T step() + { + m_eps += m_dy; + + while (m_eps >= 0) + { + m_eps -= m_dx; + + m_y += m_yInc; + } + + return m_y; + } + + T step(int n) + { + if (n <= 0) + return m_y; + + while (--n > 0) + step(); + + return step(); + } + + T stepRelative(int n = 1) + { + T const y = m_y; + + return step(n) - y; + } + }; + + static tIndex *ScaleBitmapSHQ(const int maxOsdWidth, const int maxOsdHeight, const tIndex *src, int x0, int y0, int w, int h, int stride, int ws, int hs, int x1, int y1, int w1, int h1, const uint8_t /* transparentIndex */, const tColor *const colors, const int numColors, tColor *&palette2, int &paletteSize, cXineLib *xineLib) + { + timeval t0, t1, t2, t3, t4; + (void)t0, (void)t1, (void)t2, (void)t3, (void)t4; + + PROFILE_SCALING(::gettimeofday(&t0, 0);) + + tColor16 *const screen2 = new tColor16[ maxOsdHeight * maxOsdWidth ]; + { + tColor16 *const colors16 = new tColor16[ numColors ]; + + for (int i = 0; i < numColors; i++) + mkLinearColor(xineLib->filterAlpha(colors[ i ]), colors16[ i ]); + + tColor16 clrTransparent16; + mkLinearColor(clrTransparent, clrTransparent16); + + int x1 = x0 + w; + int y1 = y0 + h; + int x2 = maxOsdWidth; + int y2 = maxOsdHeight; + + if (x1 > x2) + x1 = x2; + + if (y1 > y2) + y1 = y2; + + tColor16 *dst = screen2; + + for (int y = 0; y < y0; y++) + { + for (int x = 0; x < x2; x++) + *dst++ = clrTransparent16; + } + + for (int y = y0; y < y1; y++) + { + for (int x = 0; x < x0; x++) + *dst++ = clrTransparent16; + + for (int x = x0; x < x1; x++) + *dst++ = colors16[ *src++ ]; + src += stride - w; + + for (int x = x1; x < x2; x++) + *dst++ = clrTransparent16; + } + + for (int y = y1; y < y2; y++) + { + for (int x = 0; x < x2; x++) + *dst++ = clrTransparent16; + } + + delete [] colors16; + } + + tIndex *const scaled = new tIndex[ hs * ws ]; + + PROFILE_SCALING(::gettimeofday(&t1, 0);) + +// cXinePalette xinePalette(numColors); // fails in getVdrColors(): stack??? + cXinePalette *const pXinePalette = new cXinePalette(xineLib, numColors); + cXinePalette &xinePalette = *pXinePalette; + xinePalette.addPersistent(clrTransparent); + xinePalette.addPersistent(clrGray50); + xinePalette.addPersistent(clrBlack); + xinePalette.addPersistent(clrRed); + xinePalette.addPersistent(clrGreen); + xinePalette.addPersistent(clrYellow); + xinePalette.addPersistent(clrMagenta); + xinePalette.addPersistent(clrBlue); + xinePalette.addPersistent(clrCyan); + xinePalette.addPersistent(clrWhite); + + cXinePalette::cEntry **const scaled2 = new cXinePalette::cEntry*[ hs * ws ]; + { + memset(scaled2, 0, sizeof (*scaled2) * hs * ws); + + int x2 = x1 + w1; + int y2 = y1 + h1; + + if (x2 > ws) + { + x2 = ws; + + w1 = x2 - x1; + if (w1 < 0) + w1 = 0; + } + + if (y2 > hs) + { + y2 = hs; + + h1 = y2 - y1; + if (h1 < 0) + h1 = 0; + } + + cXinePalette::cEntry **scaled2dst0 = scaled2 + y1 * ws + x1; + + cBresenham<> yBh(FIX_POINT_FACTOR * maxOsdHeight, hs, 0); + int yf0 = yBh.step(y1); //FIX_POINT_FACTOR * y1 * maxOsdHeight / hs; + + cBresenham<> xBh0(FIX_POINT_FACTOR * maxOsdWidth, ws, 0); + xBh0.step(x1); //FIX_POINT_FACTOR * x1 * maxOsdWidth / ws; + + cBresenham<tColor16 *> yyBh(maxOsdWidth, maxOsdHeight, hs, 0, screen2); + tColor16 *screen20 = yyBh.step(y1); //y1 * maxOsdHeight / hs; + + cBresenham<> xxBh0(maxOsdWidth, ws, 0); + xxBh0.step(x1); //x1 * maxOsdWidth / ws; + + for (int y = y1; y < y2; y++) + { + const int yf1 = yBh.step(); //FIX_POINT_FACTOR * (y + 1) * maxOsdHeight / hs; + const int yi00 = yf0 & ~(FIX_POINT_FACTOR - 1); + const int yi10 = yi00 + FIX_POINT_FACTOR; + + cXinePalette::cEntry **scaled2dst = scaled2dst0; + + cBresenham<> xBh(xBh0); + int xf0 = xBh.step(0); //FIX_POINT_FACTOR * x1 * maxOsdWidth / ws; + + cBresenham<> xxBh(xxBh0); + int xxx = xxBh.step(0); //x1 * maxOsdWidth / ws; + + for (int x = x1; x < x2; x++) + { + const int xf1 = xBh.step(); //FIX_POINT_FACTOR * (x + 1) * maxOsdWidth / ws; + const int xi00 = xf0 & ~(FIX_POINT_FACTOR - 1); + const int xi10 = xi00 + FIX_POINT_FACTOR; + +// ::fprintf(stderr, "(%d, %d) => (%d, %d)\n", x, y, xf0, yf0); + + tColor16 *screen2src0 = screen20 + xxx; //&screen2[ (yi00 / FIX_POINT_FACTOR) * maxOsdWidth + (xi00 / FIX_POINT_FACTOR) ]; + + int sai = 0; + int c0 = 0; + int c1 = 0; + int c2 = 0; + int c3 = 0; + + for (int yi0 = yi00, yi1 = yi10; yi0 < yf1; yi0 = yi1, yi1 += FIX_POINT_FACTOR) + { + int aiy = intersection(yi0, yi1, yf0, yf1); + + int saix = 0; + int c0x = 0; + int c1x = 0; + int c2x = 0; + int c3x = 0; + + tColor16 *screen2src = screen2src0; + + for (int xi0 = xi00, xi1 = xi10; xi0 < xf1; xi0 = xi1, xi1 += FIX_POINT_FACTOR) + { +// ::fprintf(stderr, "-(%d, %d)\n", xi / 256, yi / 256); + + int aix = intersection(xi0, xi1, xf0, xf1); + + saix += aix; + + uint16_t *c = (uint16_t *)screen2src++; + + c0x += aix * *c++; + c1x += aix * *c++; + c2x += aix * *c++; + c3x += aix * *c++; + } + + screen2src0 += maxOsdWidth; + + sai += aiy * saix; + + c0 += aiy * c0x; + c1 += aiy * c1x; + c2 += aiy * c2x; + c3 += aiy * c3x; + } + + tColor color; + uint8_t *c = (uint8_t *)&color; + +#if __BYTE_ORDER == LITTLE_ENDIAN + *c++ = delinearize(c0 / sai) / FIX_POINT_FACTOR; + *c++ = delinearize(c1 / sai) / FIX_POINT_FACTOR; + *c++ = delinearize(c2 / sai) / FIX_POINT_FACTOR; + *c++ = (c3 / sai) / FIX_POINT_FACTOR; +#else + *c++ = (c0 / sai) / FIX_POINT_FACTOR; + *c++ = delinearize(c1 / sai) / FIX_POINT_FACTOR; + *c++ = delinearize(c2 / sai) / FIX_POINT_FACTOR; + *c++ = delinearize(c3 / sai) / FIX_POINT_FACTOR; +#endif + *scaled2dst++ = xinePalette.add(color); + + xxx = xxBh.step(); //x1 * maxOsdWidth / ws; + + xf0 = xf1; + } + + scaled2dst0 += ws; + + screen20 = yyBh.step(); //y1 * maxOsdHeight / hs; + + yf0 = yf1; + } + + PROFILE_SCALING(::gettimeofday(&t2, 0);) + + palette2 = xinePalette.getVdrColors(paletteSize); + + PROFILE_SCALING(::gettimeofday(&t3, 0);) + + { + cXinePalette::cEntry **src = scaled2; + tIndex *dst = scaled; + + for (int y = 0; y < hs; y++) + { + for (int x = 0; x < ws; x++) + { + *dst++ = (*src) ? (*src)->getIndex() : 0; + src++; + } + } + } + + PROFILE_SCALING( + { + ::gettimeofday(&t4, 0); + + ::fprintf(stderr, "num: %d, new: %d, ", numColors, xinePalette.getNum()); + + ::fprintf(stderr, "t1: %.5lf, t2: %.5lf, t3: %.5lf, t4: %.5lf, to: %.5lf, sc: %.5lf %%\n", tt(t1, t0), tt(t2, t1), tt(t3, t2), tt(t4, t3), tt(t4, t0), tt(t2, t1) / tt(t4, t0) * 100); + }) + } + +/* + char fName[ 50 ]; + static int osdCnt = 0; + + ::sprintf(fName, "/tmp/osd_%05da.ppm", osdCnt); + + FILE *f = ::fopen(fName, "wb"); + + ::fprintf(f, "P6\n%d %d\n255\n", maxOsdWidth, maxOsdHeight); + + for (int y = 0; y < maxOsdHeight; y++) + { + for (int x = 0; x < maxOsdWidth; x++) + { + ::fwrite(&((uint8_t *)&screen2[ y * maxOsdWidth + x ])[ 2 ], 1, 1, f); + ::fwrite(&((uint8_t *)&screen2[ y * maxOsdWidth + x ])[ 1 ], 1, 1, f); + ::fwrite(&((uint8_t *)&screen2[ y * maxOsdWidth + x ])[ 0 ], 1, 1, f); + } + } + + ::fclose(f); + + ::sprintf(fName, "/tmp/osd_%05db.ppm", osdCnt++); + + f = ::fopen(fName, "wb"); + + ::fprintf(f, "P6\n%d %d\n255\n", ws, hs); + + for (int y = 0; y < hs; y++) + { + for (int x = 0; x < ws; x++) + { + ::fwrite(&((uint8_t *)&scaled2[ y * ws + x ])[ 2 ], 1, 1, f); + ::fwrite(&((uint8_t *)&scaled2[ y * ws + x ])[ 1 ], 1, 1, f); + ::fwrite(&((uint8_t *)&scaled2[ y * ws + x ])[ 0 ], 1, 1, f); + } + } + + ::fclose(f); +*/ + delete pXinePalette; + + delete [] screen2; + delete [] scaled2; + + return scaled; + } + + static int deduceEps(const int dst, const int src) + { + int eps = (int)(dst * (2 - ((dst > src) ? 1 : -1) * ::log(dst / src) / ::log(1.5)) / 2); + + if (eps < (dst / 2)) + eps = dst / 2; + + return -eps; + } + + static tIndex *ScaleBitmapHQ(const int maxOsdWidth, const int maxOsdHeight, const tIndex *src, int x0, int y0, int w, int h, int stride, int ws, int hs, int x1, int y1, int w1, int h1, const uint16_t transparentIndex, const tColor *const colors, const int numColors, tColor *&palette2, int &paletteSize, int currentPaletteSize, tColor *currentPalette, cXineLib *xineLib) + { + timeval t0, t1, t2, t3, t4; + (void)t0, (void)t1, (void)t2, (void)t3, (void)t4; + + PROFILE_SCALING(::gettimeofday(&t0, 0);) + +// cXinePalette xinePalette(numColors, colors, currentPaletteSize, currentPalette); // fails in getVdrColors(): stack??? + cXinePalette *const pXinePalette = new cXinePalette(xineLib, numColors, colors, currentPaletteSize, currentPalette, (transparentIndex < numColors) ? -1 : transparentIndex); + cXinePalette &xinePalette = *pXinePalette; + + uint16_t *const screen = new uint16_t[ maxOsdHeight * maxOsdWidth ]; + { + int x1 = x0 + w; + int y1 = y0 + h; + int x2 = maxOsdWidth; + int y2 = maxOsdHeight; + + if (x1 > x2) + x1 = x2; + + if (y1 > y2) + y1 = y2; + + uint16_t *dst = screen; + + for (int y = 0; y < y0; y++) + { + for (int x = 0; x < x2; x++) + *dst++ = transparentIndex; + } + + for (int y = y0; y < y1; y++) + { + for (int x = 0; x < x0; x++) + *dst++ = transparentIndex; + + for (int x = x0; x < x1; x++) + *dst++ = xinePalette.m_vdrMapping[ *src++ ]; + src += stride - w; + + for (int x = x1; x < x2; x++) + *dst++ = transparentIndex; + } + + for (int y = y1; y < y2; y++) + { + for (int x = 0; x < x2; x++) + *dst++ = transparentIndex; + } + } + + tIndex *const scaled = new tIndex[ hs * ws ]; + + PROFILE_SCALING(::gettimeofday(&t1, 0);) + + cXinePalette::cEntry **const scaled2 = new cXinePalette::cEntry*[ hs * ws ]; + { + memset(scaled2, 0, sizeof (*scaled2) * hs * ws); + + int x2 = x1 + w1; + int y2 = y1 + h1; + + if (x2 > ws) + { + x2 = ws; + + w1 = x2 - x1; + if (w1 < 0) + w1 = 0; + } + + if (y2 > hs) + { + y2 = hs; + + h1 = y2 - y1; + if (h1 < 0) + h1 = 0; + } + + cXinePalette::cEntry **scaled2dst0 = scaled2 + y1 * ws + x1; + + cBresenham<uint16_t *> yyBh(maxOsdWidth, maxOsdHeight, hs, 0, screen); + uint16_t *screen0 = yyBh.step(y1); //y1 * maxOsdHeight / hs; + + const int eps0 = deduceEps(ws, maxOsdWidth); + const int eps1 = 0; + + cBresenham<> xxBh0(maxOsdWidth, ws, 0); + xxBh0.step(x1); //x1 * maxOsdWidth / ws; + + const int xLimit = x2 - 1; + for (int y = y1; y < y2; y++) + { + cXinePalette::cEntry **scaled2dst = scaled2dst0; + + cBresenham<> xxBh(xxBh0); + uint16_t *screen2src0 = screen0 + xxBh.step(0); //&screen2[ (yi00 / FIX_POINT_FACTOR) * maxOsdWidth + (xi00 / FIX_POINT_FACTOR) ]; + + for (int x = x1; x < x2; x++) + { +// ::fprintf(stderr, "eps: %d\n", xxBh.eps()); + + if (eps0 <= xxBh.eps() && xxBh.eps() <= eps1 && x != xLimit) + *scaled2dst++ = xinePalette.add(screen2src0[ 0 ], screen2src0[ 1 ]); + else + *scaled2dst++ = xinePalette.add(*screen2src0); + + screen2src0 += xxBh.stepRelative(); //x1 * maxOsdWidth / ws; + } + + scaled2dst0 += ws; + + screen0 = yyBh.step(); //y1 * maxOsdHeight / hs; + } + + PROFILE_SCALING(::gettimeofday(&t2, 0);) + + palette2 = xinePalette.getVdrColors(paletteSize); + + PROFILE_SCALING(::gettimeofday(&t3, 0);) + + { + cXinePalette::cEntry **src = scaled2; + tIndex *dst = scaled; + + for (int y = 0; y < hs; y++) + { + for (int x = 0; x < ws; x++) + { + uint8_t idx = (*src) ? (*src)->getIndex() : 0; + +// fprintf(stderr, "%02x ", idx); + + *dst++ = idx; + src++; + } + +// fprintf(stderr, "\n"); + } + } + + PROFILE_SCALING( + { + ::gettimeofday(&t4, 0); + + ::fprintf(stderr, "num: %d, new: %d, ", numColors, xinePalette.getNum()); + + ::fprintf(stderr, "t1: %.5lf, t2: %.5lf, t3: %.5lf, t4: %.5lf, to: %.5lf, sc: %.5lf %%\n", tt(t1, t0), tt(t2, t1), tt(t3, t2), tt(t4, t3), tt(t4, t0), tt(t2, t1) / tt(t4, t0) * 100); + }) + } + +/* + char fName[ 50 ]; + static int osdCnt = 0; +*/ +/* + { + ::sprintf(fName, "/tmp/osd_%05da.ppm", osdCnt); + + FILE *f = ::fopen(fName, "wb"); + + ::fprintf(f, "P6\n%d %d\n255\n", maxOsdWidth, maxOsdHeight); + + for (int y = 0; y < maxOsdHeight; y++) + { + for (int x = 0; x < maxOsdWidth; x++) + { + ::fwrite(&((uint8_t *)&screen2[ y * maxOsdWidth + x ])[ 2 ], 1, 1, f); + ::fwrite(&((uint8_t *)&screen2[ y * maxOsdWidth + x ])[ 1 ], 1, 1, f); + ::fwrite(&((uint8_t *)&screen2[ y * maxOsdWidth + x ])[ 0 ], 1, 1, f); + } + } + + ::fclose(f); + } + + { + ::sprintf(fName, "/tmp/osd_%05db.ppm", osdCnt++); + + FILE *f = ::fopen(fName, "wb"); + + ::fprintf(f, "P6\n%d %d\n255\n", ws, hs); + + for (int y = 0; y < hs; y++) + { + for (int x = 0; x < ws; x++) + { + ::fwrite(&((uint8_t *)&scaled2[ y * ws + x ])[ 2 ], 1, 1, f); + ::fwrite(&((uint8_t *)&scaled2[ y * ws + x ])[ 1 ], 1, 1, f); + ::fwrite(&((uint8_t *)&scaled2[ y * ws + x ])[ 0 ], 1, 1, f); + } + } + + ::fclose(f); + } +*/ +/* + { + ::sprintf(fName, "/tmp/osd_%05db.ppm", osdCnt++); + + FILE *f = ::fopen(fName, "wb"); + + ::fprintf(f, "P6\n%d %d\n255\n", ws, hs); + + for (int y = 0; y < hs; y++) + { + for (int x = 0; x < ws; x++) + { + uint8_t *c = (uint8_t *)&palette2[ scaled[ y * ws + x ] ]; + + ::fwrite(&c[ 2 ], 1, 1, f); + ::fwrite(&c[ 1 ], 1, 1, f); + ::fwrite(&c[ 0 ], 1, 1, f); + } + } + + ::fclose(f); + } +*/ + delete pXinePalette; + + delete [] screen; + delete [] scaled2; + + return scaled; + } + + static tIndex *ScaleBitmapLQ(const int maxOsdWidth, const int maxOsdHeight, const tIndex *src, int x0, int y0, int w, int h, int stride, int ws, int hs, int x1, int y1, int w1, int h1, const uint8_t transparentIndex) + { + uint8_t *const screen = new uint8_t[ maxOsdHeight * maxOsdWidth ]; + { + int x1 = x0 + w; + int y1 = y0 + h; + int x2 = maxOsdWidth; + int y2 = maxOsdHeight; + + if (x1 > x2) + x1 = x2; + + if (y1 > y2) + y1 = y2; + + uint8_t *dst = screen; + + for (int y = 0; y < y0; y++) + { + for (int x = 0; x < x2; x++) + *dst++ = transparentIndex; + } + + for (int y = y0; y < y1; y++) + { + for (int x = 0; x < x0; x++) + *dst++ = transparentIndex; + + for (int x = x0; x < x1; x++) + *dst++ = *src++; + src += stride - w; + + for (int x = x1; x < x2; x++) + *dst++ = transparentIndex; + } + + for (int y = y1; y < y2; y++) + { + for (int x = 0; x < x2; x++) + *dst++ = transparentIndex; + } + } + + tIndex *const scaled = new tIndex[ hs * ws ]; + { + int x2 = x1 + w1; + int y2 = y1 + h1; + + if (x2 > ws) + { + x2 = ws; + + w1 = x2 - x1; + if (w1 < 0) + w1 = 0; + } + + if (y2 > hs) + { + y2 = hs; + + h1 = y2 - y1; + if (h1 < 0) + h1 = 0; + } + + cBresenham<uint8_t *> yyBh(maxOsdWidth, 2 * maxOsdHeight, 2 * hs, hs, screen); + uint8_t *screen0 = yyBh.step(y1); //(((2 * y1 + 1) * maxOsdHeight / hs) / 2); + + cBresenham<> xxBh0(2 * maxOsdWidth, 2 * ws, ws); + xxBh0.step(x1); //(((2 * x1 + 1) * maxOsdWidth / ws) / 2); + + tIndex *scaled0 = scaled + y1 * ws; + + for (int y = y1; y < y2; y++) + { + cBresenham<> xxBh(xxBh0); + int xxx = xxBh.step(0); //(((2 * x1 + 1) * maxOsdWidth / ws) / 2); + + uint8_t *screen00 = screen0 + xxx; + + tIndex *scaled00 = scaled0 + x1; + + for (int x = x1; x < x2; x++) + { + *scaled00++ = *screen00; + + screen00 += xxBh.stepRelative(); + } + + scaled0 += ws; + + screen0 = yyBh.step(); + } + } + + delete [] screen; + + return scaled; + } + + bool cXineLib::execFuncOsdDrawBitmap(const int maxOsdWidth, const int maxOsdHeight, const eNeedsScaling needsScaling, const int videoWidth, const int videoHeight, cXineOsd *const xineOsd, int window, cXineAdapter *const bitmap, int x, int y, int width, int height, int stride) + { + if (!needsScaling) + { + // offset destination location by clipped area + int xClip = 0; + int yClip = 0; + int xo = xineOsd->Left() + bitmap->X0(); + int yo = xineOsd->Top() + bitmap->Y0(); + + if (xo < 0) + xClip += xo; + + if (yo < 0) + yClip += yo; + + int numColors = 0; + return execFuncOsdDrawBitmap(window, bitmap->Data(x, y), bitmap->SizeOfPixel(), x + xClip, y + yClip, width, height, stride, bitmap->Colors(numColors)); + } + + int numColors = 0; + const tColor *colors = bitmap->Colors(numColors); + + if (!numColors) + return true; + + + static int crtGamma = 0; + if (crtGamma != m_settings.GetCrtGamma()) + { + crtGamma = m_settings.GetCrtGamma(); + init_linearize(crtGamma); + init_delinearize(crtGamma); + } + + int transparentIndex = numColors; + + for (int i = 0; i < numColors; i++) + { + if (clrTransparent == colors[ i ]) + { + transparentIndex = i; + break; + } + } + +// ::fprintf(stderr, "ti: %d\t", transparentIndex); + + if (m_settings.OsdMode() == cXineSettings::osdBlendScaledLQ) + { + if (transparentIndex == numColors + && numColors < 256) + { + execFuncSetColor(window, transparentIndex, clrTransparent); + } + } + + // calculate position and size of source bitmap on screen + int xClip = 0; + int yClip = 0; + int xo = xineOsd->Left() + bitmap->X0(); + int yo = xineOsd->Top() + bitmap->Y0(); + int wo = bitmap->Width(); + int ho = bitmap->Height(); + + if (xo < 0) + { + xClip += xo; + wo += xo; + xo = 0; + } + + if (wo < 0) + wo = 0; + + if (yo < 0) + { + yClip += yo; + ho += yo; + yo = 0; + } + + if (ho < 0) + ho = 0; + + // calculate position and size of destination bitmap on screen + int xs = xo * videoWidth / maxOsdWidth; + int ws = ((xo + wo) * videoWidth - 1 + maxOsdWidth) / maxOsdWidth - xs; + int ys = yo * videoHeight / maxOsdHeight; + int hs = ((yo + ho) * videoHeight - 1 + maxOsdHeight) / maxOsdHeight - ys; + + if (m_settings.OsdMode() > cXineSettings::osdBlendScaledLQ + || numColors > 256) + { + if (numColors > 22 + || shq == needsScaling) + { + // consider complete bitmap area, not just the dirty one + x = 0; + y = 0; + width = x + bitmap->Width() - 1; + height = y + bitmap->Height() - 1; + + if (clipBitmap(xineOsd, bitmap, x, y, width, height, xo, yo, wo, ho)) + { + width = 0; + height = 0; + } + else + { + width = width - x + 1; + height = height - y + 1; + } + } + } + + // calculate position and size of dirty destination area on screen + int x0 = (xo + xClip + x) * videoWidth / maxOsdWidth; + int w0 = (((xo + xClip + x) + width) * videoWidth - 1 + maxOsdWidth) / maxOsdWidth - x0; + int y0 = (yo + yClip + y) * videoHeight / maxOsdHeight; + int h0 = (((yo + yClip + y) + height) * videoHeight - 1 + maxOsdHeight) / maxOsdHeight - y0; + + // calculate position and size of dirty destination area in destination bitmap + x0 -= xs; + y0 -= ys; + (void)ws; + (void)hs; + + // scale dirty source area into destination screen ... + tIndex *scaledScreen = 0; + + if (m_settings.OsdMode() > cXineSettings::osdBlendScaledLQ + || numColors > 256) + { + tColor *palette = 0; + int paletteSize = 0; + + scaledScreen = (shq == needsScaling) + ? ScaleBitmapSHQ(maxOsdWidth, maxOsdHeight, bitmap->Data(-xClip, -yClip), xo, yo, wo, ho, bitmap->Width(), videoWidth, videoHeight, x0 + xs, y0 + ys, w0, h0, transparentIndex, colors, numColors, palette, paletteSize, this) + : ScaleBitmapHQ(maxOsdWidth, maxOsdHeight, bitmap->Data(-xClip, -yClip), xo, yo, wo, ho, bitmap->Width(), videoWidth, videoHeight, x0 + xs, y0 + ys, w0, h0, transparentIndex, colors, numColors, palette, paletteSize, m_scaledWindowColorsNum[ window ], &m_scaledWindowColors[ window ][ 0 ], this); + + if (shq == needsScaling) + { + execFuncSetColor(window, 0, palette, paletteSize); + } + else + { +// ::fprintf(stderr, "cached: "); + + if (m_scaledWindowColorsNum[ window ] < paletteSize + || 0 != ::memcmp(&m_scaledWindowColors[ window ][ 0 ], palette, paletteSize * sizeof (*palette))) + { + m_scaledWindowColorsNum[ window ] = paletteSize; + ::memcpy(&m_scaledWindowColors[ window ][ 0 ], palette, paletteSize * sizeof (*palette)); + + execFuncSetColor(window, 0, palette, paletteSize); + +// ::fprintf(stderr, "no\n"); + } + else + { +// ::fprintf(stderr, "yes\n"); + } + } + + delete [] palette; + } + else + { + scaledScreen = ScaleBitmapLQ(maxOsdWidth, maxOsdHeight, bitmap->Data(-xClip, -yClip), xo, yo, wo, ho, bitmap->Width(), videoWidth, videoHeight, x0 + xs, y0 + ys, w0, h0, transparentIndex); + } + +// ::fprintf(stderr, "N: (%d,%d):(%d,%d)-(%d,%d)\n", xo, yo, x , y , width, height); +// ::fprintf(stderr, "S: (%d,%d):(%d,%d)-(%d,%d)\n", xs, ys, x0, y0, w0 , h0 ); + + // clip dirty destination area's size to stay on screen ... + if (w0 > videoWidth - (x0 + xs)) + { + w0 = videoWidth - (x0 + xs); + + if (w0 < 0) + w0 = 0; + } + + if (h0 > videoHeight - (y0 + ys)) + { + h0 = videoHeight - (y0 + ys); + + if (h0 < 0) + h0 = 0; + } + + // send dirty destination area to xine + bool retVal = execFuncOsdDrawBitmap(window, scaledScreen + (y0 + ys) * videoWidth + (x0 + xs), bitmap->SizeOfPixel(), x0, y0, w0, h0, videoWidth, 0); + + delete [] scaledScreen; + + return retVal; + } + + bool cXineLib::SupportsTrueColorOSD() const + { + return m_settings.OsdMode() != cXineSettings::osdOverlay && m_capabilities.osd_supports_argb_layer && m_capabilities.osd_supports_custom_extent; + } + + cXineLib::eNeedsScaling cXineLib::NeedsScaling(const int maxOsdWidth, const int maxOsdHeight, const int videoWidth, const int videoHeight) + { + if (m_settings.OsdMode() < cXineSettings::osdBlendScaledLQ) + return cXineLib::no; + + bool ignoreScaling = m_capabilities.osd_supports_argb_layer && m_capabilities.osd_supports_custom_extent; + if (!ignoreScaling + && videoWidth > 0 + && videoHeight > 0 + && (videoWidth != maxOsdWidth + || videoHeight != maxOsdHeight)) + { + if (m_settings.OsdMode() == cXineSettings::osdBlendScaledAuto) + { + if ((2 * videoWidth) < maxOsdWidth + || (2 * videoHeight) < maxOsdHeight) + { + return cXineLib::shq; + } + } + + if (m_settings.OsdMode() == cXineSettings::osdBlendScaledSHQ) + return cXineLib::shq; + + return cXineLib::yes; + } + + return cXineLib::no; + } + + void cXineLib::SendWindow(const int maxOsdWidth, const int maxOsdHeight, cXineOsd *const xineOsd, const int windowNum, cXineAdapter *bitmap /* = 0 */, const int videoLeft /* = -1 */, const int videoTop /* = -1 */, const int videoWidth /* = -1 */, const int videoHeight /* = -1 */, const int videoZoomX /* = -1 */, const int videoZoomY /* = -1 */, const bool dontOptimize /* = false */) + { + int vl = videoLeft; + int vt = videoTop; + int vw = videoWidth; + int vh = videoHeight; + int zx = videoZoomX; + int zy = videoZoomY; + bool ignoreZoom = (m_settings.OsdMode() != cXineSettings::osdOverlay) && m_capabilities.osd_supports_argb_layer && m_capabilities.osd_supports_custom_extent; + + if (zx <= 100 || ignoreZoom) + zx = 100; + else + { + int nvl = (2 * vl + vw - vw * 100 / zx) / 2; + int nvw = (2 * vl + vw + vw * 100 / zx) / 2 - nvl; + + vl = nvl; + vw = nvw; + } + + if (zy <= 100 || ignoreZoom) + zy = 100; + else + { + int nvt = (2 * vt + vh - vh * 100 / zy) / 2; + int nvh = (2 * vt + vh + vh * 100 / zy) / 2 - nvt; + + vt = nvt; + vh = nvh; + } + + sendWindow(maxOsdWidth, maxOsdHeight, xineOsd, windowNum, bitmap, vl, vt, vw, vh, zx, zy, dontOptimize); + } + + void cXineLib::sendWindow(const int maxOsdWidth, const int maxOsdHeight, cXineOsd *const xineOsd, const int windowNum, cXineAdapter *bitmap /* = 0 */, const int videoLeft /* = -1 */, const int videoTop /* = -1 */, const int videoWidth /* = -1 */, const int videoHeight /* = -1 */, const int videoZoomX /* = -1 */, const int videoZoomY /* = -1 */, const bool dontOptimize /* = false */) + { + const cXineSettings::eOsdMode mode = m_settings.OsdMode(); + const bool supportTransparency = m_settings.SupportTransparency(); + const eNeedsScaling needsScaling = NeedsScaling(maxOsdWidth, maxOsdHeight, videoWidth, videoHeight); + const bool videoMatters = !(m_capabilities.osd_supports_argb_layer && m_capabilities.osd_supports_custom_extent); + const int crtGamma = m_settings.GetCrtGamma(); + + int osdX = xineOsd->Left() + (bitmap ? bitmap->X0() : 0); + int osdY = xineOsd->Top() + (bitmap ? bitmap->Y0() : 0); + int osdW = (bitmap ? bitmap->Width() : 0); + int osdH = (bitmap ? bitmap->Height() : 0); + + if (osdX < 0) + { + osdW += osdX; + osdX = 0; + } + + if (osdW < 0) + osdW = 0; + + if (osdY < 0) + { + osdH += osdY; + osdY = 0; + } + + if (osdH < 0) + osdH = 0; + + assert(0 <= windowNum && windowNum < MAXNUMWINDOWS); + + if (!bitmap + || dontOptimize + || (videoMatters && m_osdWindowVideoLeft[ windowNum ] != videoLeft) + || (videoMatters && m_osdWindowVideoTop[ windowNum ] != videoTop) + || (videoMatters && m_osdWindowVideoWidth[ windowNum ] != videoWidth) + || (videoMatters && m_osdWindowVideoHeight[ windowNum ] != videoHeight) + || (videoMatters && m_osdWindowVideoZoomX[ windowNum ] != videoZoomX) + || (videoMatters && m_osdWindowVideoZoomY[ windowNum ] != videoZoomY) + || m_osdWindowLeft[ windowNum ] != osdX + || m_osdWindowTop[ windowNum ] != osdY + || m_osdWindowWidth[ windowNum ] != osdW + || m_osdWindowHeight[ windowNum ] != osdH + || m_osdWindowMode[ windowNum ] != mode + || m_osdWindowSupportTransparency[ windowNum ] != supportTransparency + || m_osdWindowGamma[ windowNum ] != crtGamma) + { + if (m_osdWindowVisible[ windowNum ] + || dontOptimize) + { + m_osdWindowVisible[ windowNum ] = false; + m_osdWindowVideoLeft[ windowNum ] = videoLeft; + m_osdWindowVideoTop[ windowNum ] = videoTop; + m_osdWindowVideoWidth[ windowNum ] = videoWidth; + m_osdWindowVideoHeight[ windowNum ] = videoHeight; + m_osdWindowVideoZoomX[ windowNum ] = videoZoomX; + m_osdWindowVideoZoomY[ windowNum ] = videoZoomY; + m_osdWindowLeft[ windowNum ] = osdX; + m_osdWindowTop[ windowNum ] = osdY; + m_osdWindowWidth[ windowNum ] = osdW; + m_osdWindowHeight[ windowNum ] = osdH; + m_osdWindowMode[ windowNum ] = mode; + m_osdWindowSupportTransparency[ windowNum ] = supportTransparency; + m_osdWindowGamma[ windowNum ] = crtGamma; + + execFuncOsdHide(windowNum); + execFuncOsdFree(windowNum); + } + + if (!bitmap) + return; + } + + bool colorsModified = dontOptimize; + + if (!m_osdWindowVisible[ windowNum ] + || dontOptimize) + { + m_osdWindowVideoLeft[ windowNum ] = videoLeft; + m_osdWindowVideoTop[ windowNum ] = videoTop; + m_osdWindowVideoWidth[ windowNum ] = videoWidth; + m_osdWindowVideoHeight[ windowNum ] = videoHeight; + m_osdWindowVideoZoomX[ windowNum ] = videoZoomX; + m_osdWindowVideoZoomY[ windowNum ] = videoZoomY; + m_osdWindowLeft[ windowNum ] = osdX; + m_osdWindowTop[ windowNum ] = osdY; + m_osdWindowWidth[ windowNum ] = osdW; + m_osdWindowHeight[ windowNum ] = osdH; + m_osdWindowMode[ windowNum ] = mode; + m_osdWindowSupportTransparency[ windowNum ] = supportTransparency; + m_osdWindowGamma[ windowNum ] = crtGamma; + + m_scaledWindowColorsNum[ windowNum ] = 0; +/* + fprintf(stderr, "Bitmap[ %d ]: (%d,%d)-(%d,%d)\n" + , windowNum + , bitmap->X0() + , bitmap->Y0() + , bitmap->Width() + , bitmap->Height()); +*/ + assert(0x0000 <= bitmap->Width() && bitmap->Width() <= 0x0fff); + assert(0x0000 <= bitmap->Height() && bitmap->Height() <= 0x0fff); + + int vx = m_osdWindowVideoLeft[ windowNum ]; + int vy = m_osdWindowVideoTop[ windowNum ]; + + if (mode < cXineSettings::osdBlendScaledLQ) + { + vx = 0; + vy = 0; + } + else + { + + if (vx < 0) + vx = 0; + + if (vy < 0) + vy = 0; + } + +// fprintf(stderr, "n: %d: f: %d: v: (%d, %d) - (%d, %d): (%d, %d)\n", windowNum, dontOptimize, videoLeft, videoTop, videoWidth, videoHeight, vx, vy); + + execFuncOsdNew(maxOsdWidth, maxOsdHeight, needsScaling, vx, vy, videoWidth, videoHeight, windowNum, osdX, osdY, osdW, osdH); + colorsModified = true; + } + + int numColors = 0; + const tColor *colors = bitmap->Colors(numColors); + + int &numWindowColors = m_osdWindowColorsNum[ windowNum ]; + tColor *windowColors = &m_osdWindowColors[ windowNum ][ 0 ]; + + const bool setColor = !((m_settings.OsdMode() != cXineSettings::osdOverlay) && m_capabilities.osd_supports_argb_layer) + && (!needsScaling + || (m_settings.OsdMode() <= cXineSettings::osdBlendScaledLQ)); + + for (int i = 0; i < numColors; i++) + { + if (dontOptimize + || !m_osdWindowVisible[ windowNum ] + || numWindowColors <= i + || *windowColors != *colors) + { + if (setColor) + execFuncSetColor(windowNum, i, filterAlpha(*colors)); + + *windowColors = *colors; + colorsModified = true; + } + + windowColors++; + colors++; + } + + numWindowColors = numColors; + + int x1 = 0, y1 = 0, x2 = bitmap->Width() - 1, y2 = bitmap->Height() - 1; + if (clipBitmap(xineOsd, bitmap, x1, y1, x2, y2, osdX, osdY, osdW, osdH)) + { + if (!m_osdWindowVisible[ windowNum ]) + execFuncOsdShow(windowNum); + + bitmap->Clean(); + } + else if (colorsModified + || bitmapDiffers(windowNum, xineOsd, bitmap, x1, y1, x2, y2, osdX, osdY, osdW, osdH)) + { +// fprintf(stderr, "windowNum: %d, dirty area: (%d, %d) - (%d, %d), bitmap size: (%d, %d) %d %d\n", windowNum, x1, y1, x2, y2, bitmap->Width(), bitmap->Height(), colorsModified, !m_osdWindowVisible[ windowNum ]); + + cloneBitmap(windowNum, bitmap, x1, y1, x2, y2); + execFuncOsdDrawBitmap(maxOsdWidth, maxOsdHeight, needsScaling, videoWidth, videoHeight, xineOsd, windowNum, bitmap, x1, y1, x2 - x1 + 1, y2 - y1 + 1, bitmap->Width()); + + execFuncOsdShow(windowNum); + + bitmap->Clean(); + } + else + { +// fprintf(stderr, "not dirty\n"); + } + + m_osdWindowVisible[ windowNum ] = true; + } + + void cXineLib::cloneBitmap(const int windowNum, cXineAdapter *bitmap, int x1, int y1, int x2, int y2) + { +#if VERIFY_BITMAP_DIRTY < 1 + return; +#endif + + int w = bitmap->Width(); + int h = bitmap->Height(); + int stride = w * bitmap->SizeOfPixel(); + int s = h * stride; + + if (m_osdWindowBufferSize[ windowNum ] < s) + { + if (m_osdWindowBuffer[ windowNum ]) + delete [] m_osdWindowBuffer[ windowNum ]; + + m_osdWindowBufferSize[ windowNum ] = 0; + + m_osdWindowBuffer[ windowNum ] = new uint8_t[ s ]; + if (!m_osdWindowBuffer[ windowNum ]) + return; + + m_osdWindowBufferSize[ windowNum ] = s; + + x1 = 0; + y1 = 0; + x2 = w - 1; + y2 = h - 1; + } + + const uint8_t *src = bitmap->Data(0, 0) + bitmap->SizeOfStride() * y1 + bitmap->SizeOfPixel() * x1; + uint8_t *dst = m_osdWindowBuffer[ windowNum ] + stride * y1 + bitmap->SizeOfPixel() * x1; + + int n = (x2 - x1 + 1) * bitmap->SizeOfPixel(); + + for (int y = y1; y <= y2; y++) + { + memcpy(dst, src, n); + src += bitmap->SizeOfStride(); + dst += stride; + } + } + + bool cXineLib::clipBitmap(cXineOsd *xineOsd, cXineAdapter *bitmap, int &x1, int &y1, int &x2, int &y2, const int osdX, const int osdY, const int osdW, const int osdH) + { + int osdX1 = xineOsd->Left() + bitmap->X0() + x1; + int osdY1 = xineOsd->Top() + bitmap->Y0() + y1; + int osdX2 = xineOsd->Left() + bitmap->X0() + x2; + int osdY2 = xineOsd->Top() + bitmap->Y0() + y2; + + if (osdX1 < osdX) + x1 -= osdX1 - osdX; + + if (osdY1 < osdY) + y1 -= osdY1 - osdY; + + if (osdX2 > (osdX + osdW - 1)) + x2 -= osdX2 - (osdX + osdW - 1); + + if (osdY2 > (osdY + osdH - 1)) + y2 -= osdY2 - (osdY + osdH - 1); + + return (x1 > x2) || (y1 > y2); + } + + bool cXineLib::bitmapDiffers(const int windowNum, cXineOsd *xineOsd, cXineAdapter *bitmap, int &x1, int &y1, int &x2, int &y2, const int osdX, const int osdY, const int osdW, const int osdH) + { + if (!bitmap->Dirty(x1, y1, x2, y2)) + return false; + + if (clipBitmap(xineOsd, bitmap, x1, y1, x2, y2, osdX, osdY, osdW, osdH)) + { + // dirty area clipped is clipped away + bitmap->Clean(); + return false; + } + +#if VERIFY_BITMAP_DIRTY < 1 + return true; +#endif + + int w = bitmap->Width(); + int h = bitmap->Height(); + int stride = w * bitmap->SizeOfPixel(); + int s = h * stride; + + if (m_osdWindowBufferSize[ windowNum ] < s) + return true; + + if (!m_osdWindowBuffer[ windowNum ]) + return true; + + const uint8_t *src = bitmap->Data(0, 0) + bitmap->SizeOfStride() * y1 + bitmap->SizeOfPixel() * x1; + const uint8_t *dst = m_osdWindowBuffer[ windowNum ] + stride * y1 + bitmap->SizeOfPixel() * x1; + + int n = (x2 - x1 + 1) * bitmap->SizeOfPixel(); + + for (int y = y1; y <= y2; y++) + { + if (0 != memcmp(dst, src, n)) + return true; + + src += bitmap->SizeOfStride(); + dst += stride; + } + + struct timeval tv; + ::gettimeofday(&tv, 0); + double tt = tv.tv_sec + tv.tv_usec * 1e-6; + fprintf(stderr, "%.3lf OPTIMIZE OSD DRAWING: bitmap dirty, but no difference!\n", tt); + +#if VERIFY_BITMAP_DIRTY > 1 + *(char *)0 = 0; // fail with a core dump +#endif + + bitmap->Clean(); + return false; + } + + void cXineLib::SetVideoWindow(const int maxOsdWidth, const int maxOsdHeight, tArea vidWin, const bool dontOptimize /* = false */) + { + if (0 == vidWin.bpp) + { + vidWin.x1 = 0; + vidWin.y1 = 0; + vidWin.x2 = -1 + maxOsdWidth; + vidWin.y2 = -1 + maxOsdHeight; + } + else + { + m_vidWin.bpp = vidWin.bpp; + } + + if (dontOptimize + || (0 != ::memcmp(&m_vidWin, &vidWin, sizeof (m_vidWin)))) + { + m_vidWin = vidWin; + + execFuncSetVideoWindow(m_vidWin.x1, m_vidWin.y1, m_vidWin.Width(), m_vidWin.Height(), maxOsdWidth, maxOsdHeight); + } + } + +#endif + + extern int GetBindIp(cPlugin *const plugin); + extern int GetBindPort(cPlugin *const plugin); + extern int GetInstanceNo(cPlugin *const plugin); + extern cXineLib *&GetXineLib(cPlugin *const plugin); + + cXineLib::cXineLib(cPlugin *const plugin, const cXineSettings &settings, cMutex &osdMutex, cXineRemote *const remote) + : cThread() + , m_plugin(plugin) + , m_settings(settings) + , m_osdFlushRequired(false) + , fd_fifo0_serv(-1) + , fd_result_serv(-1) + , fd_control_serv(-1) + , fd_remote_serv(-1) + , fd_fifo0(-1) + , fd_result(-1) + , fd_control(-1) + , fd_remote(-1) + , m_osdMutex(osdMutex) + , m_paused(false) + , m_frozen(false) + , m_ignore(false) + , m_shutdown(false) + , m_muted(false) + , m_volume(0) + , m_audioChannel(0) + , m_trickSpeedMode(false) + , m_noSignalStream16x9(false) + , m_eventSink(0) + { + memset(&m_capabilities, 0, sizeof (m_capabilities)); + + m_noSignalStreamSize[0] = 0; + m_noSignalStreamSize[1] = 0; + + m_fifoDir = FIFO_DIR; + + if (GetInstanceNo(plugin) >= 0) + { + char s[ 20 ]; + ::sprintf(s, "%d", GetInstanceNo(plugin)); + + m_fifoDir += s; + } + + m_bindIp = GetBindIp(plugin); + m_bindPort = GetBindPort(plugin); + + m_fifoNameControl = m_fifoDir + "/stream.control"; + m_fifoNameResult = m_fifoDir + "/stream.result"; + m_fifoNameRemote = m_fifoDir + "/stream.event"; + m_fifoNameStream = m_fifoDir + "/stream"; + m_fifoNameExtControl = m_fifoDir + FIFO_NAME_EXT_CONTROL; + m_fifoNameExtResult = m_fifoDir + FIFO_NAME_EXT_RESULT; + + m_external.setXineLib(this); + + ::memset(m_osdWindowVisible, 0, sizeof (m_osdWindowVisible)); + +#if APIVERSNUM >= 10307 + ::memset(m_osdWindowBufferSize, 0, sizeof (m_osdWindowBufferSize)); + ::memset(m_osdWindowBuffer, 0, sizeof (m_osdWindowBuffer)); + ::memset(m_osdWindowColorsNum, 0, sizeof (m_osdWindowColorsNum)); + ::memset(m_osdWindowColors, 0, sizeof (m_osdWindowColors)); + ::memset(m_scaledWindowColorsNum, 0, sizeof (m_scaledWindowColorsNum)); + ::memset(m_scaledWindowColors, 0, sizeof (m_scaledWindowColors)); + + ::memset(&m_vidWin, 0, sizeof (m_vidWin)); + + ::memset(m_osdWindowVideoLeft, 0, sizeof (m_osdWindowVideoLeft)); + ::memset(m_osdWindowVideoTop, 0, sizeof (m_osdWindowVideoTop)); + ::memset(m_osdWindowVideoWidth, 0, sizeof (m_osdWindowVideoWidth)); + ::memset(m_osdWindowVideoHeight, 0, sizeof (m_osdWindowVideoHeight)); + ::memset(m_osdWindowVideoZoomX, 0, sizeof (m_osdWindowVideoZoomX)); + ::memset(m_osdWindowVideoZoomY, 0, sizeof (m_osdWindowVideoZoomY)); + ::memset(m_osdWindowLeft, 0, sizeof (m_osdWindowLeft)); + ::memset(m_osdWindowTop, 0, sizeof (m_osdWindowTop)); + ::memset(m_osdWindowWidth, 0, sizeof (m_osdWindowWidth)); + ::memset(m_osdWindowHeight, 0, sizeof (m_osdWindowHeight)); + + ::memset(m_osdWindowMode, 0, sizeof (m_osdWindowMode)); + ::memset(m_osdWindowGamma, 0, sizeof (m_osdWindowGamma)); + ::memset(m_osdWindowSupportTransparency, 0, sizeof (m_osdWindowSupportTransparency)); +#endif + + readNoSignalStream(0, "4x3") || readNoSignalStream(0, ""); + readNoSignalStream(1, "16x9") || readNoSignalStream(1, ""); + + assert(remote); + remote->setXineLib(this); + GetXineLib(m_plugin) = this; + } + + bool cXineLib::readNoSignalStream(const int index, const string &suffix) + { + string noSignalFileName = m_plugin->ConfigDirectory(PLUGIN_NAME_I18N); +// noSignalFileName += "/noSignal.pes"; + noSignalFileName += "/noSignal" + suffix + ".mpg"; + + FILE *const f = ::fopen(noSignalFileName.c_str(), "rb"); + if (f) + { + m_noSignalStreamSize[index] = ::fread(&m_noSignalStreamData[index][0] + 9, 1, sizeof (m_noSignalStreamData[index]) - 9 - 9 - 4, f); + if (m_noSignalStreamSize[index] == sizeof (m_noSignalStreamData[index]) - 9 - 9 - 4) + { + ::fprintf(stderr, "vdr-xine: error: '%s' exeeds limit of %ld bytes!\n", noSignalFileName.c_str(), (long)(sizeof (m_noSignalStreamData[index]) - 9 - 9 - 4 - 1)); + esyslog("vdr-xine: error: '%s' exeeds limit of %ld bytes!\n", noSignalFileName.c_str(), (long)(sizeof (m_noSignalStreamData[index]) - 9 - 9 - 4 - 1)); + } + else if (m_noSignalStreamSize[index] > 0) + { + m_noSignalStreamData[index][ 0 ] = 0x00; + m_noSignalStreamData[index][ 1 ] = 0x00; + m_noSignalStreamData[index][ 2 ] = 0x01; + m_noSignalStreamData[index][ 3 ] = 0xe0; + m_noSignalStreamData[index][ 4 ] = (m_noSignalStreamSize[index] + 3) >> 8; + m_noSignalStreamData[index][ 5 ] = (m_noSignalStreamSize[index] + 3) & 0xff; + m_noSignalStreamData[index][ 6 ] = 0x80; + m_noSignalStreamData[index][ 7 ] = 0x00; + m_noSignalStreamData[index][ 8 ] = 0x00; + m_noSignalStreamSize[index] += 9; + m_noSignalStreamData[index][ m_noSignalStreamSize[index]++ ] = 0x00; + m_noSignalStreamData[index][ m_noSignalStreamSize[index]++ ] = 0x00; + m_noSignalStreamData[index][ m_noSignalStreamSize[index]++ ] = 0x01; + m_noSignalStreamData[index][ m_noSignalStreamSize[index]++ ] = 0xe0; + m_noSignalStreamData[index][ m_noSignalStreamSize[index]++ ] = 0x00; + m_noSignalStreamData[index][ m_noSignalStreamSize[index]++ ] = 0x07; + m_noSignalStreamData[index][ m_noSignalStreamSize[index]++ ] = 0x80; + m_noSignalStreamData[index][ m_noSignalStreamSize[index]++ ] = 0x00; + m_noSignalStreamData[index][ m_noSignalStreamSize[index]++ ] = 0x00; + m_noSignalStreamData[index][ m_noSignalStreamSize[index]++ ] = 0x00; + m_noSignalStreamData[index][ m_noSignalStreamSize[index]++ ] = 0x00; + m_noSignalStreamData[index][ m_noSignalStreamSize[index]++ ] = 0x01; + m_noSignalStreamData[index][ m_noSignalStreamSize[index]++ ] = 0xb7; + } + ::fclose(f); + return true; + } + else + { + ::fprintf(stderr, "vdr-xine: error: couldn't open '%s'!\n", noSignalFileName.c_str()); + esyslog("vdr-xine: error: couldn't open '%s'!\n", noSignalFileName.c_str()); + } + + return false; + } + + cXineLib::~cXineLib() + { + Close(); + + GetXineLib(m_plugin) = 0; + +#if APIVERSNUM >= 10307 + for (int i = 0; i < MAXNUMWINDOWS; i++) + { + if (m_osdWindowBuffer[ i ]) + delete [] m_osdWindowBuffer[ i ]; + } +#endif + } + + void cXineLib::SetEventSink(cXineLibEvents *const eventSink) + { + m_eventSink = eventSink; + } + + int cXineLib::CreateServerSocket(unsigned short port) + { + int fd; + int onoff = 1; + struct sockaddr_in sain; + + if ((fd = ::socket(PF_INET, SOCK_STREAM, 0)) < 0) + { + perror("socket failed."); + return -1; + } + + memset(&sain, 0, sizeof (sain)); + sain.sin_addr.s_addr = m_bindIp; + sain.sin_port = htons(port); + + ::setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &onoff, sizeof (int)); + + onoff = (port != m_bindPort); + ::setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &onoff, sizeof(int)); + + if (::bind(fd, (struct sockaddr *)&sain, sizeof (sain)) != 0) + { + perror("bind failed."); + return -1; + } + + if (::listen(fd, 1) != 0) + { + printf("listen failed."); + return -1; + } + + return fd; + } + + bool cXineLib::Open() + { + ::unlink(m_fifoNameExtControl.c_str()); + ::unlink(m_fifoNameExtResult.c_str()); + ::unlink(m_fifoNameControl.c_str()); + ::unlink(m_fifoNameResult.c_str()); + ::unlink(m_fifoNameRemote.c_str()); + ::unlink(m_fifoNameStream.c_str()); + ::rmdir(m_fifoDir.c_str()); + + const mode_t origUmask = ::umask(0); + +#define MkFifo(String, Mask) \ + do { if (::mknod((String).c_str(), (Mask) | S_IFIFO, 0) < 0) \ + { \ + string msg = "vdr-xine: error: couldn't create fifo '" + (String) + "'"; \ + perror(msg.c_str()); \ + esyslog("%s", msg.c_str()); \ + ::umask(origUmask); \ + return false; \ + } } while (0) + + struct stat stat_buf; + if (::stat(m_fifoDir.c_str(), &stat_buf) < 0) + { + if (::mkdir(m_fifoDir.c_str(), 0755) < 0) + { + string msg = "vdr-xine: error: couldn't create directory '" + m_fifoDir + "'"; + perror(msg.c_str()); + esyslog("%s", msg.c_str()); + ::umask(origUmask); + return false; + } + } + + MkFifo(m_fifoNameExtControl, 0666); + MkFifo(m_fifoNameExtResult, 0644); + + if (m_bindPort <= 0) + { + MkFifo(m_fifoNameControl, 0644); + MkFifo(m_fifoNameResult, 0666); + MkFifo(m_fifoNameRemote, 0666); + MkFifo(m_fifoNameStream, 0644); + } + else + { + if ((fd_fifo0_serv = CreateServerSocket(m_bindPort + 0)) == -1) + return false; + + if ((fd_control_serv = CreateServerSocket(m_bindPort + 1)) == -1) + return false; + + if ((fd_result_serv = CreateServerSocket(m_bindPort + 2)) == -1) + return false; + + if ((fd_remote_serv = CreateServerSocket(m_bindPort + 3)) == -1) + return false; + } + +#undef MkFifo + + ::umask(origUmask); + + if (!Start()) + return false; + + m_external.StartExternal(); + + return true; + } + + void cXineLib::Close() + { + m_external.StopExternal(); + + { + cMutexLock shutdownMutexLock(&m_shutdownMutex); + m_shutdown = true; + m_shutdownCondVar.Broadcast(); + } + + { + DLOG("cXineLib::Close"); + cMutexLock ioLock(&m_ioMutex); + disconnect(); + } + + ::unlink(m_fifoNameExtControl.c_str()); + ::unlink(m_fifoNameExtResult.c_str()); + + if (m_bindPort <= 0) + { + ::unlink(m_fifoNameControl.c_str()); + ::unlink(m_fifoNameResult.c_str()); + ::unlink(m_fifoNameRemote.c_str()); + ::unlink(m_fifoNameStream.c_str()); + } + else + { + ::close(fd_remote_serv); + ::close(fd_result_serv); + ::close(fd_control_serv); + ::close(fd_fifo0_serv); + } + + ::rmdir(m_fifoDir.c_str()); + } + + void cXineLib::internalPaused(const bool paused) + { + cMutexLock pausedMutexLock(&m_pausedMutex); + + m_paused = paused; + + if (!paused) + m_pausedCondVar.Broadcast(); + } + + bool cXineLib::Poll(cPoller &Poller, int TimeoutMs /* = 0 */, const bool special /* = false */) + { + if (m_paused) + { + if (TimeoutMs > 0) + { + cMutexLock pausedMutexLock(&m_pausedMutex); + + if (m_paused) + m_pausedCondVar.TimedWait(m_pausedMutex, TimeoutMs); + } + +// fprintf(stderr, "p"); + return false; + } + + if (-1 == fd_fifo0) + return true; + + Poller.Add(fd_fifo0, true); + + if (Poller.Poll(TimeoutMs < 0 ? 0 : TimeoutMs)) + { +// fprintf(stderr, "_"); + return true; + } + + if (TimeoutMs >= 0) + xfprintf(stderr, (special ? "p" : "P")); + return false; + } + + void cXineLib::flush() + { + ::fsync(fd_fifo0); + } + + int cXineLib::xread(int f, void *b, int n) + { + int t = 0; + + while (t < n) + { + void (* const sigPipeHandler)(int) = ::signal(SIGPIPE, SIG_IGN); + + errno = 0; + int r = ::read(f, ((char *)b) + t, n - t); + int myErrno = errno; + + ::signal(SIGPIPE, sigPipeHandler); + + if (r <= 0) + { + if (EAGAIN == myErrno || EINTR == myErrno) + continue; + + xfprintf(stderr, "read(%d) returned %d, error %d: ", n, r, myErrno); + errno = myErrno; + if (!m_settings.ShallBeQuiet()) + perror(""); + + disconnect(); + + return r; + } + + t += r; + } + + return t; + } + + int cXineLib::xwrite(int f, const void *b, int n) + { + const char *yyy[] = { "i", "I", "d", "D" }; +// char *yyy[] = { "", "", "d", "D" }; + const char **xxx = yyy; + if (f == fd_fifo0) + xxx += 2; + + int t = 0; + + while (t < n) + { +// fprintf(stderr, "%s", xxx[ 0 ]); + +// fprintf(stderr, " xwrite "); + + void (* const sigPipeHandler)(int) = ::signal(SIGPIPE, SIG_IGN); + + errno = 0; + int r = ::write(f, ((char *)b) + t, n - t); + int myErrno = errno; + + ::signal(SIGPIPE, sigPipeHandler); + + if (r <= 0) + { + if (EAGAIN == myErrno || EINTR == myErrno) + break; + + xfprintf(stderr, "::write(%d) returned %d, error %d: ", n, r, myErrno); + errno = myErrno; + if (!m_settings.ShallBeQuiet()) + perror(""); + + disconnect(); + +// fprintf(stderr, "%s", xxx[ 1 ]); + return r; + } +/* + if (t != 0 || r != n) + fprintf(stderr, "::write(%d): %d\n", n - t, r); +*/ + t += r; + } + +// fprintf(stderr, "%s", xxx[ 1 ]); + return t; + } + + bool cXineLib::showNoSignal() + { +// fprintf(stderr, "showNoSignal: enter\n"); + int index = m_noSignalStream16x9; + bool x = execFuncStream0((uchar *)&m_noSignalStreamData[index][0], m_noSignalStreamSize[index]); +// fprintf(stderr, "showNoSignal: leave\n"); + + return x; + } + + static tThreadId limit_thread = -1; + + static tThreadId GetThreadID() + { +#if APIVERSNUM >= 10337 + return cThread::ThreadId(); +#else + return syscall(__NR_gettid); +#endif + } + + bool cXineLib::isConnected() + { +// return (-1 != fd_fifo0); + if (-1 == fd_fifo0) + return false; + + if (-1 == limit_thread) + return true; + + return limit_thread == GetThreadID(); + } + + static bool m_connectionEstablished = false; + + void cXineLib::disconnect() + { + cMutexLock disconnectLock(&m_disconnectMutex); + + const bool wasConnected = m_connectionEstablished; + m_connectionEstablished = false; + + if (-1 != fd_control) + { +// ::fprintf(stderr, "close: fd_control\n"); + ::close(fd_control); + fd_control = -1; + } + + if (-1 != fd_result) + { +// ::fprintf(stderr, "close: fd_result\n"); + ::close(fd_result); + fd_result = -1; + } + + if (-1 != fd_remote) + { +// ::fprintf(stderr, "close: fd_remote\n"); + ::close(fd_remote); + fd_remote = -1; + } + + if (-1 != fd_fifo0) + { +// ::fprintf(stderr, "close: fd_fifo0\n"); + ::close(fd_fifo0); + fd_fifo0 = -1; + } + + m_external.disconnect(); + + if (wasConnected) + { + if (m_eventSink) + m_eventSink->OnClientDisconnect(); + + xfprintf(stderr, "vdr-xine: Client disconnected!\n"); + } + + memset(&m_capabilities, 0, sizeof (m_capabilities)); + } + + bool cXineLib::execFuncNop() + { + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncNop"); + cMutexLock ioLock(&m_ioMutex); + if (!isConnected()) + return false; + + data_nop_t data; + data.header.func = func_nop; + data.header.len = sizeof (data); + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + return true; + + return false; + } + + void cXineLib::Action(void) + { + int delayConnect = 0; + + while (!m_shutdown) + { + if (!isConnected()) + { +// fprintf(stderr, "a"); + + DLOG("cXineLib::Action"); + + if (delayConnect > 0) + delayConnect--; + else + { + cXineOsdMutexLock osdLock(&m_osdMutex); + cMutexLock ioLock(&m_ioMutex); + cMutexLock dataLock(&m_dataMutex); + + checkConnect(); + } + } + else + { +// fprintf(stderr, "A"); + + if (m_settings.InteractWithEitScanner() && EITScanner.Active() && m_eventSink && !m_eventSink->DeviceReplayingOrTransferring()) + { + cMutexLock ioLock(&m_ioMutex); + disconnect(); + delayConnect = 50; + } + else + execFuncNop(); + +// fprintf(stderr, "n"); + } + + if (!m_shutdown) + { + cMutexLock shutdownMutexLock(&m_shutdownMutex); + if (!m_shutdown) + m_shutdownCondVar.TimedWait(m_shutdownMutex, 100); + } + } + +// fprintf(stderr, "Action done\n"); + } + + int cXineLib::SocketAcceptHelper(int fd) + { + // use cPoller for checking server socket for incoming requests + cPoller poller(fd,0); /* POLLIN */ + struct sockaddr sain; + socklen_t len = sizeof (sain); + int client; + +// ::fprintf(stderr, "vdr-xine: polling for connection on %d ...\n", fd); + if (!poller.Poll(100)) + return -1; + +// ::fprintf(stderr, "vdr-xine: incoming requests on %d\n", fd); + if ((client = ::accept(fd, (struct sockaddr *)&sain, &len)) == -1) { + ::fprintf(stderr, "vdr-xine: socket failed to accept ...\n"); + return -1; + } + +// ::fprintf(stderr, "vdr-xine: successful request on %d (client: %d)\n", fd, client); + return client; + } + + + bool cXineLib::checkXineVersion() + { + int32_t version = 0; + execFuncGetVersion(version); + + if (MIN_XINE_VDR_VERSION <= version /* && version <= MAX_XINE_VDR_VERSION */) + return true; + + xfprintf(stderr, "vdr-xine: Client reports unsupported version %d => disconnecting!\n", version); + + disconnect(); + return false; + } + + bool cXineLib::checkConnect() + { + limit_thread = GetThreadID(); + + if (m_bindPort <= 0) + { + fd_fifo0 = ::open(m_fifoNameStream.c_str(), O_WRONLY | O_NONBLOCK); + if (-1 == fd_fifo0) + return false; + + xfprintf(stderr, "vdr-xine: Client connecting ...\n"); + + char zero = 0; + xwrite(fd_fifo0, &zero, sizeof (zero)); + + fd_remote = ::open(m_fifoNameRemote.c_str(), O_RDONLY | O_NONBLOCK); + fd_control = ::open(m_fifoNameControl.c_str(), O_WRONLY); + fd_result = ::open(m_fifoNameResult.c_str() , O_RDONLY); + + ::fcntl(fd_fifo0 , F_SETFL, ~O_NONBLOCK & ::fcntl(fd_fifo0 , F_GETFL, 0)); + ::fcntl(fd_remote, F_SETFL, ~O_NONBLOCK & ::fcntl(fd_remote, F_GETFL, 0)); + } + else + { + if (fd_fifo0_serv == -1) + return false; + + if ((fd_fifo0 = SocketAcceptHelper(fd_fifo0_serv)) == -1) + return false; + + xfprintf(stderr, "vdr-xine: Client connecting ...\n"); + + if ((fd_control = SocketAcceptHelper(fd_control_serv)) == -1) + return false; + + if ((fd_result = SocketAcceptHelper(fd_result_serv)) == -1) + return false; + + if ((fd_remote = SocketAcceptHelper(fd_remote_serv)) == -1) + return false; + } + + internalPaused(false); + + m_frozen = false; + m_ignore = false; +// reinit = true; +// doBufferAndStart(); +// } + +// if (reinit) +// { + + if (checkXineVersion()) + { + execFuncQueryCapabilities(m_capabilities); + execFuncSetup(); +// execFuncMute(m_muted); +// execFuncSetVolume(m_volume); + execFuncSelectAudio(m_audioChannel); + execFuncTrickSpeedMode(m_trickSpeedMode); + execFuncSetPrebuffer(0); + execFuncClear(-1); +// execFuncStart(); + execFuncStillFrame(); + execFuncWait(true); + + for (int i = 0; i < 25; i++) + showNoSignal(); + + pushOut(); + execFuncFlush(); +// execFuncResetAudio(); +// execFuncWait(); +// execFuncSetPrebuffer(0); //m_settings.GetModeParams()->m_prebufferFrames); + execFuncClear(-5); + execFuncWait(); + +// m_setPrebufferRequired = true; +/* + if (m_setPrebufferRequired) + { + fprintf(stderr, "Prebuffer required\n"); + + m_setPrebufferRequired = false; + + if (!isConnected()) + return false; + } +*/ + + if (m_eventSink + && isConnected()) + { + m_eventSink->OnClientConnect(); + + if (m_settings.InteractWithEitScanner() && EITScanner.Active() && !m_eventSink->DeviceReplayingOrTransferring()) + EITScanner.Activity(); + } + + execFuncSetVolume(m_volume); + + limit_thread = -1; + m_connectionEstablished = true; + } + +// execFuncWait(); + +// execFuncSetSpeed(-1); +// execFuncWait(); + + { +// if (!execFuncSetSpeed(-1)) +// return false; + +#if 0 + if (execFuncOsdNew(0xf, 320, 16, 16, 16, 0, 0, false)) + { + if (execFuncSetColor(0xf, 0, clrBlack)) + { + if (execFuncSetColor(0xf, 1, clrCyan)) + { + if (execFuncSetColor(0xf, 2, clrRed)) + { + uint8_t data[ 16 ][ 16 ] = + { + { 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2 }, + { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, + { 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1 }, + { 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1 }, + + { 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1 }, + { 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, + + { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, + { 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1 }, + { 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1 }, + + { 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1 }, + { 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1 }, + { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }, + { 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2 }, + }; + + if (execFuncOsdDrawBitmap(0xf, &data[ 0 ][ 0 ], 0, 0, 16, 16, 16)) + { + if (execFuncOsdShow(0xf)) + { + return true; + } + } + } + } + } + } +#endif + } +// execFuncSetPrebuffer(31); +// execFuncStart(); +// execFuncWait(); + + if (isConnected()) + { + xfprintf(stderr, "vdr-xine: Client connected!\n"); + return true; + } + +// } +// else +// { +// dataNop_t dataNop; +// dataNop.header.func = funcNop; +// dataNop.header.len = sizeof (dataNop); +// +// if (sizeof (dataNop) == xwrite(fd_control, &dataNop, sizeof (dataNop))) +// return true; +// } + +// ::close(fd_control); +// ::close(fd_fifo0); +// fd_fifo0 = -1; + + xfprintf(stderr, "vdr-xine: Client connect failed!\n"); + + return false; + } +/* + void cXineLib::doBufferAndStart() + { + m_doBufferAndStart = 64000; + } +*/ + + bool cXineLib::execFuncSetPrebuffer(int frames) + { + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncSetPrebuffer"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + +// if (!checkConnect()) +// return false; + + data_set_prebuffer_t data; + data.header.func = func_set_prebuffer; + data.header.len = sizeof (data); + data.prebuffer = frames * 90000 / 25; + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + return true; + +// ::close(fd_control); +// ::close(fd_fifo0); +// fd_fifo0 = -1; + + return false; + } + + int cXineLib::execFuncStream1(const uchar *Data, int Length) + { + if (!m_connectionEstablished) + return Length; + + return execFuncStream(Data, Length); + } + + int cXineLib::execFuncStream(const uchar *Data, int Length) + { + if (!isConnected()) + return Length; + +// return true; + +// { +// cMutexLock ioLock(&m_ioMutex); +// +// if (!checkConnect()) +// return false; +// } + + if (m_paused) + return 0; + + if (m_frozen) + return -1; + + if (m_ignore) + return Length; + + cMutexLock dataLock(&m_dataMutex); + + if (!isConnected()) + return Length; + + int r = xwrite(fd_fifo0, Data, Length); + if (Length == r) +// fprintf(stderr, "."); + + if (r < 0) + xfprintf(stderr, "X"); + + return r; + +#if 0 + cMutexLock ioLock(&m_ioMutex); + + if (!checkConnect()) + return false; + + if (m_frozen) + return false; + + if (m_ignore) + return true; + + dataStream_t dataStream; + dataStream.header.func = funcStream; + dataStream.header.len = sizeof (dataStream) + Length; + + assert((sizeof (dataStream) + Length) == dataStream.header.len); + + if (sizeof (dataStream) == xwrite(fd_fifo, &dataStream, sizeof (dataStream))) + { + if (Length == xwrite(fd_fifo, Data, Length)) + { +/* if (m_doBufferAndStart > 0) + { + m_doBufferAndStart -= Length; + + if (m_doBufferAndStart <= 0) + { + if (!execFuncSetSpeed(0) + || !execFuncMute(false)) + { + return false; + } + } + } +*/ + return true; + } + } + + ::close(fd_control); + ::close(fd_fifo0); + fd_fifo0 = -1; + + return false; +#endif + } + + bool cXineLib::execFuncStream0(const uchar *Data, int Length) + { + if (!isConnected()) + return true; + +// return true; + +// { +// cMutexLock ioLock(&m_ioMutex); +// +// if (!checkConnect()) +// return false; +// } + + cMutexLock dataLock(&m_dataMutex); + + int done = 0; + + while (done < Length) + { + if (!isConnected()) + return true; + + int r = xwrite(fd_fifo0, Data + done, Length - done); + if (r < 0) + return false; + + done += r; + } + + return Length == done; + } + + bool cXineLib::execFuncStart() + { + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncStart"); + cMutexLock ioLock(&m_ioMutex); + +// if (!checkConnect()) +// return false; + + if (!isConnected()) + return false; + + data_start_t data; + data.header.func = func_start; + data.header.len = sizeof (data); + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + return true; + +// ::close(fd_control); +// ::close(fd_fifo0); +// fd_fifo0 = -1; + + return false; + } + + bool cXineLib::osdUpdateLocked(const char *const funcName) + { + if (m_eventSink + && m_eventSink->OsdUpdateLocked()) + { +// fprintf(stderr, "=== cXineLib::%s -> osdUpdateLocked\n", funcName); + return true; + } + + return false; + } + + bool cXineLib::execFuncOsdNew(int window, int x, int y, int width, int height, int w_ref, int h_ref) + { + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncOsdNew"); + cMutexLock ioLock(&m_ioMutex); + +// if (!checkConnect()) +// return false; + + if (!isConnected()) + return false; + + data_osd_new_t data; + data.header.func = func_osd_new; + data.header.len = sizeof (data); + data.window = window; + data.x = x; + data.y = y; + data.width = width; + data.height = height; + data.w_ref = w_ref; + data.h_ref = h_ref; + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + return true; + +// ::close(fd_control); +// ::close(fd_fifo0); +// fd_fifo0 = -1; + + return false; + } + + + +#if APIVERSNUM < 10307 + + bool cXineLib::execFuncSetColor(int window, int index, eDvbColor color) + { + return execFuncSetColor(window, index, &color, 1); + } + + bool cXineLib::execFuncSetColor(int window, int index, eDvbColor *const colors, int numColors) + { + for (int i = 0; i < numColors; i++) + { + uint8_t *const c = (uint8_t *)&colors[ i ]; + + uint8_t h = c[ 0 ]; + c[ 0 ] = c[ 2 ]; + c[ 2 ] = h; + } + + return execFuncSetColor(window, index, numColors, (uint32_t *)colors); + } + + eDvbColor cXineLib::filterAlpha(eDvbColor color) + { + if (!m_settings.SupportTransparency()) + { + uint8_t *const c = (uint8_t *)&color; + if (c[ 3 ]) + c[ 3 ] = 0xff; + } + return color; + } + +#else + + bool cXineLib::execFuncSetColor(int window, int index, tColor color) + { + return execFuncSetColor(window, index, &color, 1); + } + + bool cXineLib::execFuncSetColor(int window, int index, tColor *const colors, int numColors) + { + return execFuncSetColor(window, index, numColors, (uint32_t *)colors); + } + + tColor cXineLib::filterAlpha(tColor color) + { +// fprintf(stderr, "color: 0x%08x", color); + + if (!m_settings.SupportTransparency()) + { + uint8_t *const c = (uint8_t *)&color; + if (c[ 3 ]) + c[ 3 ] = 0xff; + } + +// fprintf(stderr, ", 0x%08x\n", color); + + return color; + } + +#endif + + + bool cXineLib::execFuncSetColor(int window, int index, int numColors, uint32_t *const colors) + { + assert(0 <= index && index < 256); + assert(0 < numColors && numColors <= 256); + assert((index + numColors) <= 256); + + m_osdFlushRequired = true; + + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncSetColor"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + + const int sizeofColors = sizeof (*colors) * numColors; + + data_set_color_t data; + data.header.func = func_set_color; + data.header.len = sizeof (data) + sizeofColors; + data.window = window; + data.index = index; + data.num = numColors - 1; + + if (sizeof (data) != xwrite(fd_control, &data, sizeof (data))) + return false; + + if (sizeofColors != xwrite(fd_control, colors, sizeofColors)) + return false; + + return true; + } + + bool cXineLib::execFuncOsdDrawBitmap(int window, const uint8_t *const pData, const int sizeOfPixel, int x, int y, int width, int height, int stride, const tColor *const colors) + { + m_osdFlushRequired = true; + + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncOsdDrawBitmap"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + +// if (!checkConnect()) +// return false; + + int bitmapLen = width * height * (((m_settings.OsdMode() != cXineSettings::osdOverlay) && m_capabilities.osd_supports_argb_layer) ? 4 : 1); + + data_osd_draw_bitmap_t data; + data.header.func = func_osd_draw_bitmap; + data.header.len = sizeof (data) + bitmapLen; + data.window = window; + data.x = x; + data.y = y; + data.width = width; + data.height = height; + data.argb = (m_settings.OsdMode() != cXineSettings::osdOverlay) && m_capabilities.osd_supports_argb_layer; + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + { + bool failed = false; + const uint8_t *row = pData; + + assert(width <= 4096); + uint32_t data[4096]; + + for (int y = 0; !failed && y < height; y++) + { + if ((m_settings.OsdMode() != cXineSettings::osdOverlay) && m_capabilities.osd_supports_argb_layer) + { + uint32_t *pData = &data[0]; + + if (4 == sizeOfPixel) + pData = (uint32_t *)row; + else if (!colors) + { + if (y == 0) + memset(data, 0, sizeof (data)); + } + else + { + for (int i = 0; i < width; i++) + data[i] = filterAlpha(colors[row[i]]); + } + + if (4 * width != xwrite(fd_control, pData, 4 * width)) + { + failed = true; + break; + } + } + else if (1 == sizeOfPixel) + { + if (width != xwrite(fd_control, row, width)) + { + failed = true; + break; + } + } + else + { + if (y == 0) + memset(data, 0, sizeof (data)); + + if (width != xwrite(fd_control, data, width)) + { + failed = true; + break; + } + } + + row += stride * sizeOfPixel; + } + + if (!failed) + return true; + } + +// ::close(fd_control); +// ::close(fd_fifo0); +// fd_fifo0 = -1; + + return false; + } + + bool cXineLib::execFuncOsdSetPosition(int window, int x, int y) + { + m_osdFlushRequired = true; + + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncOsdSetPosition"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + +// if (!checkConnect()) +// return false; + + data_osd_set_position_t data; + data.header.func = func_osd_set_position; + data.header.len = sizeof (data); + data.window = window; + data.x = x; + data.y = y; + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + return true; + +// ::close(fd_control); +// ::close(fd_fifo0); +// fd_fifo0 = -1; + + return false; + } + + bool cXineLib::execFuncOsdShow(int window) + { + m_osdFlushRequired = true; + + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncOsdShow"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + +// if (!checkConnect()) +// return false; + + data_osd_show_t data; + data.header.func = func_osd_show; + data.header.len = sizeof (data); + data.window = window; + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + return true; + +// ::close(fd_control); +// ::close(fd_fifo0); +// fd_fifo0 = -1; + + return false; + } + + bool cXineLib::execFuncOsdHide(int window) + { + m_osdFlushRequired = true; + + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncOsdHide"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + +// if (!checkConnect()) +// return false; + + data_osd_hide_t data; + data.header.func = func_osd_hide; + data.header.len = sizeof (data); + data.window = window; + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + return true; + +// ::close(fd_control); +// ::close(fd_fifo0); +// fd_fifo0 = -1; + + return false; + } + + bool cXineLib::execFuncOsdFlush() + { + if (osdUpdateLocked("execFuncOsdFlush")) + return true; + + if (!m_osdFlushRequired) + return true; + + m_osdFlushRequired = false; + + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncOsdFlush"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + + data_osd_flush_t data; + data.header.func = func_osd_flush; + data.header.len = sizeof (data); + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + return true; + + return false; + } + + bool cXineLib::execFuncOsdFree(int window) + { + m_osdFlushRequired = true; + + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncOsdFree"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + +// if (!checkConnect()) +// return false; + + data_osd_free_t data; + data.header.func = func_osd_free; + data.header.len = sizeof (data); + data.window = window; + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + return true; + +// ::close(fd_control); +// ::close(fd_fifo0); +// fd_fifo0 = -1; + + return false; + } + + bool cXineLib::pushOut(const uchar id /* = 0xff */) + { + if (!isConnected()) + return false; + + cMutexLock dataLock(&m_dataMutex); + + if (!isConnected()) + return false; + + static uchar dataPadding[ 6 + 0xffff ] = { 0x00, 0x00, 0x01, 0xbe, 0xff, 0xff, 0x00 }; + + dataPadding[ 5 ] = id; + int l = 6 + 0xff00 + id; + + if (l == xwrite(fd_fifo0, &dataPadding, l)) + return true; + + return false; + } + + bool cXineLib::execFuncClear(int n) + { + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncClear"); + + cMutexLock dataLock(&m_dataMutex); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + + static uchar id = 0x00; + if (id == 0xff) + id += 2; + else + id++; + + data_clear_t data; + data.header.func = func_clear; + data.header.len = sizeof (data); + data.n = n; + data.s = 0; + data.i = id; + + // first clear will discard xine's buffers to make some available for pushOut() + if (sizeof (data) != xwrite(fd_control, &data, sizeof (data))) + return false; + + if (!execFuncWait()) + return false; + + if (!pushOut(id)) + return false; + + data.s = 1; + + // second clear will discard xine's buffers which contain data from pushOut() + if (sizeof (data) != xwrite(fd_control, &data, sizeof (data))) + return false; + + return true; + } + + bool cXineLib::execFuncFirstFrame() + { + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncFirstFrame"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + + data_first_frame_t data; + data.header.func = func_first_frame; + data.header.len = sizeof (data); + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + return true; + +// ::close(fd_control); +// ::close(fd_fifo0); +// fd_fifo0 = -1; + + return false; + } + + bool cXineLib::execFuncStillFrame() + { + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncStillFrame"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + + data_still_frame_t data; + data.header.func = func_still_frame; + data.header.len = sizeof (data); + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + return true; + +// ::close(fd_control); +// ::close(fd_fifo0); +// fd_fifo0 = -1; + + return false; + } + + bool cXineLib::execFuncResetAudio() + { + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncResetAudio"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + + data_reset_audio_t data; + data.header.func = func_reset_audio; + data.header.len = sizeof (data); + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + return true; + + return false; + } + + bool cXineLib::execFuncFlush(int TimeoutMs /* = -1 */, const bool justWait /* = false */) + { + if (!isConnected()) + return true; + + DLOG("cXineLib::execFuncFlush"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return true; + +// if (!checkConnect()) +// return false; + + data_flush_t data; + data.header.func = func_flush; + data.header.len = sizeof (data); + data.ms_timeout = TimeoutMs; + data.just_wait = justWait; + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + { + result_union_t resultUnion; + + off_t n = xread(fd_result, (char *)&resultUnion, sizeof (resultUnion.header)); + if (n != sizeof (resultUnion.header)) + return true; + + if (data.header.func != resultUnion.header.func) + return true; + + result_flush_t *result = &resultUnion.flush; + + n = xread(fd_result, (char *)result + sizeof (result->header), sizeof (*result) - sizeof (result->header)); + if (n != sizeof (*result) - sizeof (result->header)) + return true; + + return !result->timed_out; + } + + return true; + } + + bool cXineLib::execFuncMute(bool mute /* = true */) + { + m_muted = mute; + + return true; + + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncMute"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + +// if (!checkConnect()) +// return false; + + data_mute_t data; + data.header.func = func_mute; + data.header.len = sizeof (data); + data.mute = mute; + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + return true; + +// ::close(fd_control); +// ::close(fd_fifo0); +// fd_fifo0 = -1; + + return false; + } + + bool cXineLib::execFuncSetVolume(int volume) + { + m_volume = volume; + + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncSetVolume"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + +// if (!checkConnect()) +// return false; + + data_set_volume_t data; + data.header.func = func_set_volume; + data.header.len = sizeof (data); + data.volume = volume * 100 / 0xff; + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + return true; + +// ::close(fd_control); +// ::close(fd_fifo0); +// fd_fifo0 = -1; + + return false; + } + + bool cXineLib::execFuncSelectAudio(int audioChannel) + { + m_audioChannel = audioChannel; + + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncSelectAudio"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + +// if (!checkConnect()) +// return false; + + data_select_audio_t data; + data.header.func = func_select_audio; + data.header.len = sizeof (data); + data.channels = audioChannel; + + switch (audioChannel) + { + case -1: + data.channels = 0; // don't touch audio data + break; + + case 0: + data.channels = 3; // left + right channel + break; + } + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + return true; + +// ::close(fd_control); +// ::close(fd_fifo0); +// fd_fifo0 = -1; + + return false; + } + + bool cXineLib::execFuncSetSpeed(double speed) + { + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncSetSpeed"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + + data_set_speed_t data; + data.header.func = func_set_speed; + data.header.len = sizeof (data); + data.speed = (int)(XINE_FINE_SPEED_NORMAL * speed / 100.0 + 0.5); + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + return true; + + return false; + } + + bool cXineLib::execFuncTrickSpeedMode(bool on) + { + m_trickSpeedMode = on; + + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncTrickSpeedMode"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + + data_trick_speed_mode_t data; + data.header.func = func_trick_speed_mode; + data.header.len = sizeof (data); + data.on = on; + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + return true; + + return false; + } +/* + bool cXineLib::execFuncDelay(int usdelay) + { + cMutexLock ioLock(&m_ioMutex); + + if (!checkConnect()) + return false; + + dataDelay_t dataDelay; + dataDelay.header.func = funcDelay; + dataDelay.header.len = sizeof (dataDelay); + dataDelay.usdelay = usdelay; + + if (sizeof (dataDelay) == xwrite(fd_control, &dataDelay, sizeof (dataDelay))) + return true; + + ::close(fd_control); + ::close(fd_fifo0); + fd_fifo0 = -1; + + return false; + } +*/ + bool cXineLib::execFuncMetronom(int64_t pts, uint32_t flags /* = 0 */) + { + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncMetronom"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + +// if (!checkConnect()) +// return false; + + data_metronom_t data; + data.header.func = func_metronom; + data.header.len = sizeof (data); + data.pts = pts; + data.flags = flags; + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + return true; + +// ::close(fd_control); +// ::close(fd_fifo0); +// fd_fifo0 = -1; + + return false; + } + + bool cXineLib::execFuncWait(const bool id /* = false */) + { + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncWait"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + +// if (!checkConnect()) +// return false; + + data_wait_t data; + data.header.func = func_wait; + data.header.len = sizeof (data); + data.id = id; + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + { + result_union_t resultUnion; + + off_t n = xread(fd_result, (char *)&resultUnion, sizeof (resultUnion.header)); + if (n != sizeof (resultUnion.header)) + return false; + + if (data.header.func != resultUnion.header.func) + return false; + + result_wait_t *result = &resultUnion.wait; + + n = xread(fd_result, (char *)result + sizeof (result->header), sizeof (*result) - sizeof (result->header)); + if (n != sizeof (*result) - sizeof (result->header)) + return false; + + return true; + } + + return false; + } + + bool cXineLib::execFuncQueryCapabilities(result_query_capabilities_t &capabilities) + { + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncQueryCapabilities"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + + data_query_capabilities_t data; + data.header.func = func_query_capabilities; + data.header.len = sizeof (data); + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + { + result_union_t resultUnion; + + off_t n = xread(fd_result, (char *)&resultUnion, sizeof (resultUnion.header)); + if (n != sizeof (resultUnion.header)) + return false; + + if (data.header.func != resultUnion.header.func) + return false; + + result_query_capabilities_t *result = &resultUnion.query_capabilities; + + n = xread(fd_result, (char *)result + sizeof (result->header), sizeof (*result) - sizeof (result->header)); + if (n != sizeof (*result) - sizeof (result->header)) + return false; + + memcpy(&capabilities, result, sizeof (capabilities)); + + return true; + } + + return false; + } + + bool cXineLib::execFuncSetup() + { + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncSetup"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + +// if (!checkConnect()) +// return false; + + data_setup_t data; + data.header.func = func_setup; + data.header.len = sizeof (data); + + data.osd_unscaled_blending = (m_settings.OsdMode() == cXineSettings::osdOverlay); + + switch (m_settings.VolumeMode()) + { + case cXineSettings::volumeIgnore: data.volume_mode = XINE_VDR_VOLUME_IGNORE; break; + case cXineSettings::volumeChangeHW: data.volume_mode = XINE_VDR_VOLUME_CHANGE_HW; break; + case cXineSettings::volumeChangeSW: data.volume_mode = XINE_VDR_VOLUME_CHANGE_SW; break; + default: + assert(false); + } + + switch (m_settings.MuteMode()) + { + case cXineSettings::muteIgnore: data.mute_mode = XINE_VDR_MUTE_IGNORE; break; + case cXineSettings::muteExecute: data.mute_mode = XINE_VDR_MUTE_EXECUTE; break; + case cXineSettings::muteSimulate: data.mute_mode = XINE_VDR_MUTE_SIMULATE; break; + default: + assert(false); + } + + data.image4_3_zoom_x = m_settings.ZoomParams(cXineSettings::image4_3).GetZoomX(); + data.image4_3_zoom_y = m_settings.ZoomParams(cXineSettings::image4_3).GetZoomY(); + data.image16_9_zoom_x = m_settings.ZoomParams(cXineSettings::image16_9).GetZoomX(); + data.image16_9_zoom_y = m_settings.ZoomParams(cXineSettings::image16_9).GetZoomY(); + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + return true; + +// ::close(fd_control); +// ::close(fd_fifo0); +// fd_fifo0 = -1; + + return false; + } + + void cXineLib::freeze(bool /* doFreeze */ /* = true */) + { +// m_frozen = doFreeze; + } + + void cXineLib::ignore(bool /* doIgnore */ /* = true */) + { +// m_ignore = doIgnore; + } + + void cXineLib::pause(bool doPause /* = true */) + { + cMutexLock dataLock(&m_dataMutex); + + internalPaused(doPause); + } + +#ifndef Y4MSCALER +#define Y4MSCALER "y4mscaler" +#endif + +#ifndef Y4MTOPPM +#define Y4MTOPPM "y4mtoppm" +#endif + +#ifndef PNMTOJPEG +#define PNMTOJPEG "pnmtojpeg" +#endif + + uchar *cXineLib::execFuncGrabImage(const char *FileName, int &Size, bool Jpeg, int Quality, int SizeX, int SizeY) + { + if (!isConnected()) + return NULL; + + DLOG("cXineLib::execFuncGrabImage"); + cMutexLock ioLock(&m_ioMutex); + + int videoX = -1; + int videoY = -1; + int videoW = -1; + int videoH = -1; + int zoomX = 100; + int zoomY = 100; + execFuncVideoSize(videoX, videoY, videoW, videoH, zoomX, zoomY); + + if (!isConnected()) + return NULL; + + data_grab_image_t data; + data.header.func = func_grab_image; + data.header.len = sizeof (data); + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + { + result_union_t resultUnion; + + off_t n = xread(fd_result, (char *)&resultUnion, sizeof (resultUnion.header)); + if (n != sizeof (resultUnion.header)) + return NULL; + + if (data.header.func != resultUnion.header.func) + return NULL; + + result_grab_image_t *result = &resultUnion.grab_image; + + n = xread(fd_result, (char *)result + sizeof (result->header), sizeof (*result) - sizeof (result->header)); + if (n != sizeof (*result) - sizeof (result->header)) + return NULL; + + const size_t frameSize = result->header.len - sizeof (*result); + +// ::fprintf(stderr, "frameSize: %d\n", frameSize); + + if (frameSize <= 0) + return NULL; + + uint8_t *img = (uint8_t *)::malloc(frameSize); + if (!img) + return NULL; + + if (frameSize != (size_t)xread(fd_result, img, frameSize)) + { + ::free(img); + return NULL; + } + + if (XINE_IMGFMT_YUY2 == result->format) + { + uint8_t *img2 = (uint8_t *)::malloc(frameSize); + if (!img2) + { + ::free(img); + return NULL; + } + + ::memset(img2, 0x80, frameSize); + + uint8_t *src = img; + uint8_t *dstY = img2; + uint8_t *dstU = dstY + result->height * result->width; + uint8_t *dstV = dstU + result->height * ((result->width + 1) / 2); + + for (int y = 0; y < result->height; y++) + { + for (int x = 0; x < (result->width + 1) / 2; x++) + { + *dstY++ = *src++; + *dstU++ = *src++; + *dstY++ = *src++; + *dstV++ = *src++; + } + } + + ::free(img); + + img = img2; + } + + FILE *tmpFile = 0; + int outfd = -1; + + if (FileName) + { + outfd = ::open(FileName, O_CREAT /* | O_EXCL */ | O_TRUNC | O_RDWR, 0644); + } + else + { + tmpFile = ::tmpfile(); + if (tmpFile) + outfd = fileno(tmpFile); + } + + if (-1 == outfd) + { + if (tmpFile) + ::fclose(tmpFile); // removes file, too + } + else + { +/* + if (-1 == videoX || -1 == videoY || videoW <= 0 || videoH <= 0) + { + videoX = 0; + videoY = 0; + videoW = result->width; + videoH = result->height; + } +*/ + videoX = result->crop_left; + videoY = result->crop_top; + videoW = result->width - result->crop_left - result->crop_right; + videoH = result->height - result->crop_top - result->crop_bottom; + + int iRn; + int iRd; + + if (videoW > videoH) + { + iRn = videoH; + iRd = (videoW * 20000 + result->ratio) / (2 * result->ratio); + } + else + { + iRn = (videoH * 2 * result->ratio + 10000) / 20000; + iRd = videoW; + } + + int oRn = SizeY * (m_noSignalStream16x9 ? 16 : 4); + int oRd = SizeX * (m_noSignalStream16x9 ? 9 : 3); + + const char *const chromass = (XINE_IMGFMT_YV12 == result->format) ? "420_MPEG2" : "422"; + char *cmd = 0; + + if (Jpeg) + { + ::asprintf(&cmd, Y4MSCALER " -I chromass=%s -I active=%dx%d+%d+%d -O chromass=444 -O size=%dx%d -O sar=%d:%d " + "| " Y4MTOPPM " -L " + "| " PNMTOJPEG " -quality=%d " + ">&%d" + , chromass, videoW, videoH, videoX, videoY, SizeX, SizeY, oRn, oRd + , Quality + , outfd); + } + else + { + ::asprintf(&cmd, Y4MSCALER " -I chromass=%s -I active=%dx%d+%d+%d -O chromass=444 -O size=%dx%d -O sar=%d:%d " + "| " Y4MTOPPM " -L " + ">&%d" + , chromass, videoW, videoH, videoX, videoY, SizeX, SizeY, oRn, oRd + , outfd); + } + +// ::fprintf(stderr, "ratio: %d\n", result->ratio); + xfprintf(stderr, "cmd: %s\n", cmd); + + FILE *f = ::popen(cmd, "w"); + if (f) + { + ::fprintf(f, "YUV4MPEG2 W%d H%d F%d:%d I%c A%d:%d\nFRAME\n" + , result->width, result->height + , 25, 1 + , "ptb"[result->interlaced] + , iRn, iRd); + + if (frameSize == ::fwrite(img, 1, frameSize, f)) + { + ::pclose(f); // close the pipe here + ::free(img); + img = NULL; + + if (tmpFile) // grab the image in one go + { + Size = (int) lseek (outfd, 0, SEEK_END); + lseek (outfd, 0, SEEK_SET); + + if (Size != -1) + { + img = (uint8_t *)::malloc(Size); + if (img && Size != ::read(outfd, img, Size)) + { + ::free(img); + img = NULL; + } + } + } + else // file is persistent so don't care about it's content + { + Size = -1; + img = (uint8_t *)-1; + } + } + else + { + ::pclose(f); + ::free(img); + img = NULL; + } + } + else + { + ::free(img); + img = NULL; + } + + ::free(cmd); + + if (tmpFile) + ::fclose(tmpFile); // removes file, too + else + ::close(outfd); + + return (uchar *)img; + } + + ::free(img); + } + + return NULL; + } + + bool cXineLib::execFuncGetVersion(int32_t &version) + { + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncGetVersion"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + + data_get_version_t data; + data.header.func = func_get_version; + data.header.len = sizeof (data); + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + { + result_union_t resultUnion; + + off_t n = xread(fd_result, (char *)&resultUnion, sizeof (resultUnion.header)); + if (n != sizeof (resultUnion.header)) + return false; + + if (data.header.func != resultUnion.header.func) + return false; + + result_get_version_t *result = &resultUnion.get_version; + + n = xread(fd_result, (char *)result + sizeof (result->header), sizeof (*result) - sizeof (result->header)); + if (n != sizeof (*result) - sizeof (result->header)) + return false; + + version = result->version; + + return true; + } + + return false; + } + + bool cXineLib::execFuncGetPTS(int64_t &pts, const int TimeoutMs /* = 0 */, bool *const pQueued /* = 0 */) + { + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncGetPTS"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + + data_get_pts_t data; + data.header.func = func_get_pts; + data.header.len = sizeof (data); + data.ms_timeout = TimeoutMs; + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + { + result_union_t resultUnion; + + off_t n = xread(fd_result, (char *)&resultUnion, sizeof (resultUnion.header)); + if (n != sizeof (resultUnion.header)) + return false; + + if (data.header.func != resultUnion.header.func) + return false; + + result_get_pts_t *result = &resultUnion.get_pts; + + n = xread(fd_result, (char *)result + sizeof (result->header), sizeof (*result) - sizeof (result->header)); + if (n != sizeof (*result) - sizeof (result->header)) + return false; + + pts = result->pts; + if (pQueued) + *pQueued = result->queued; + + return true; + } + + return false; + } + + bool cXineLib::execFuncVideoSize(int &videoLeft, int &videoTop, int &videoWidth, int &videoHeight, int &videoZoomX, int &videoZoomY, double *const pVideoAspect /* = 0 */) + { + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncVideoSize"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + + data_video_size_t data; + data.header.func = func_video_size; + data.header.len = sizeof (data); + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + { + result_union_t resultUnion; + + off_t n = xread(fd_result, (char *)&resultUnion, sizeof (resultUnion.header)); + if (n != sizeof (resultUnion.header)) + return false; + + if (data.header.func != resultUnion.header.func) + return false; + + result_video_size_t *result = &resultUnion.video_size; + + n = xread(fd_result, (char *)result + sizeof (result->header), sizeof (*result) - sizeof (result->header)); + if (n != sizeof (*result) - sizeof (result->header)) + return false; + + videoLeft = result->left; + videoTop = result->top; + videoWidth = result->width; + videoHeight = result->height; + videoZoomX = result->zoom_x; + videoZoomY = result->zoom_y; + + if (pVideoAspect) + *pVideoAspect = result->ratio / 10000.0; + + return true; + } + + return false; + } + + bool cXineLib::execFuncSetVideoWindow(int x, int y, int w, int h, int wRef, int hRef) + { + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncSetVideoWindow"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + + data_set_video_window_t data; + data.header.func = func_set_video_window; + data.header.len = sizeof (data); + data.x = x; + data.y = y; + data.w = w; + data.h = h; + data.w_ref = wRef; + data.h_ref = hRef; + + if (sizeof (data) == xwrite(fd_control, &data, sizeof (data))) + return true; + + return false; + } + + void cXineLib::ReshowCurrentOSD(const int frameLeft, const int frameTop, const int frameWidth, const int frameHeight, const int frameZoomX, const int frameZoomY, const bool unlockOsdUpdate /* = false */) + { + if (m_eventSink) + m_eventSink->ReshowCurrentOSD(frameLeft, frameTop, frameWidth, frameHeight, frameZoomX, frameZoomY, unlockOsdUpdate); + } + + void cXineLib::LockOsdUpdate() + { + if (m_eventSink) + m_eventSink->LockOsdUpdate(); + } + + void cXineLib::DiscontinuityDetected() + { + if (m_eventSink) + m_eventSink->DiscontinuityDetected(); + } + + bool cXineLib::execFuncPlayExternal(const char *const fileName /* = 0 */) + { + if (!isConnected()) + return false; + + DLOG("cXineLib::execFuncPlayExternal"); + cMutexLock ioLock(&m_ioMutex); + + if (!isConnected()) + return false; + + int dataSize = (fileName ? (1 + ::strlen(fileName)) : 0); + + data_play_external_t data; + data.header.func = func_play_external; + data.header.len = sizeof (data) + dataSize; + + if (sizeof (data) != xwrite(fd_control, &data, sizeof (data))) + return false; + + if (dataSize != xwrite(fd_control, fileName, dataSize)) + return false; + + return true; + } + + void cXineLib::enableExternal(const bool enable /* = true */) + { + m_external.enable(enable); + } + + void cXineLib::ExternalStreamFinished() + { + m_external.externalStreamFinished(); + } + +}; diff --git a/xineLib.h b/xineLib.h new file mode 100644 index 0000000..73e0e09 --- /dev/null +++ b/xineLib.h @@ -0,0 +1,415 @@ + +#ifndef __XINELIB_H +#define __XINELIB_H + + + +#include "xineCommon.h" + +#include <vdr/remote.h> + +#if APIVERSNUM < 10307 + +#include <vdr/osdbase.h> + +typedef uchar tIndex; +typedef eDvbColor tColor; +typedef pid_t tThreadId; +#include <sys/syscall.h> + +#else + +#include <vdr/osd.h> + +#define MAXNUMWINDOWS MAXOSDAREAS + +#endif + +#include <vdr/plugin.h> + +#include "xineExternal.h" +#include "xineSettings.h" + + + +namespace PluginXine +{ + + class cXineLibEvents + { + protected: + virtual ~cXineLibEvents() {} + public: + virtual void OnClientConnect() = 0; + virtual void OnClientDisconnect() = 0; + virtual void ReshowCurrentOSD(const int frameLeft, const int frameTop, const int frameWidth, const int frameHeight, const int frameZoomX, const int frameZoomY, const bool unlockOsdUpdate) = 0; + virtual void LockOsdUpdate() = 0; + virtual bool OsdUpdateLocked() = 0; + virtual void DiscontinuityDetected() = 0; + virtual bool DeviceReplayingOrTransferring() = 0; + }; + + + + class cXineLib; + +// This class performs the adaption from Xine events +// to VDR's remote control events. + class cXineRemote + : public cRemote + , private cThread + { + private: + bool m_active; + cMutex m_activeMutex; + cCondVar m_activeCondVar; + const bool m_remoteOn; + cXineLib *m_xineLib; + + virtual void Action (void); + bool Ready(void) { return false; }; + bool isConnected(); + + public: + cXineRemote(const bool remoteOn); + virtual ~cXineRemote(); + + void setXineLib(cXineLib *const xineLib); + }; + + + class cXineOsd; + class cXineSettings; + + class cXineAdapter + { + public: + virtual int X0(void) const = 0; + virtual int Y0(void) const = 0; + virtual int Width(void) const = 0; + virtual int Height(void) const = 0; + virtual bool Dirty(int &x1, int &y1, int &x2, int &y2) = 0; + virtual void Clean(void) = 0; + virtual const tColor *Colors(int &NumColors) const = 0; + virtual const uint8_t *Data(int x, int y) const = 0; + virtual int SizeOfPixel() const = 0; + virtual int SizeOfStride() const = 0; + }; + + class cXineBitmapAdapter : public cXineAdapter + { + cBitmap *const m_pBitmap; + + public: + cXineBitmapAdapter(cBitmap *const pBitmap) + : m_pBitmap(pBitmap) + { + } + + virtual int X0(void) const; + virtual int Y0(void) const; + virtual int Width(void) const; + virtual int Height(void) const; + virtual bool Dirty(int &x1, int &y1, int &x2, int &y2); + virtual void Clean(void); + virtual const tColor *Colors(int &NumColors) const; + virtual const uint8_t *Data(int x, int y) const; + virtual int SizeOfPixel() const; + virtual int SizeOfStride() const; + + operator cXineAdapter *() + { + if (m_pBitmap) + return this; + + return 0; + } + }; + +#if APIVERSNUM >= 10717 + + class cXinePixmapMemoryAdapter : public cXineAdapter + { + cPixmapMemory *const m_pPixmapMemory; + + public: + cXinePixmapMemoryAdapter(const cPixmapMemory *const pPixmapMemory); + virtual int X0(void) const; + virtual int Y0(void) const; + virtual int Width(void) const; + virtual int Height(void) const; + virtual bool Dirty(int &x1, int &y1, int &x2, int &y2); + virtual void Clean(void); + virtual const tColor *Colors(int &NumColors) const; + virtual const uint8_t *Data(int x, int y) const; + virtual int SizeOfPixel() const; + virtual int SizeOfStride() const; + + operator cXineAdapter *() + { + if (m_pPixmapMemory) + return this; + + return 0; + } + }; + +#endif + + class cXineLib : public cThread + { + public: + enum eNeedsScaling + { + no = 0 + , yes + , shq + }; + + string m_fifoDir; + string m_fifoNameControl; + string m_fifoNameResult; + string m_fifoNameRemote; + string m_fifoNameStream; + string m_fifoNameExtControl; + string m_fifoNameExtResult; + in_addr_t m_bindIp; + int m_bindPort; + + private: + cPlugin *const m_plugin; + cXineExternal m_external; + + void internalPaused(const bool paused); + cMutex m_pausedMutex; + cCondVar m_pausedCondVar; + cMutex m_shutdownMutex; + cCondVar m_shutdownCondVar; + + const cXineSettings &m_settings; + + bool osdUpdateLocked(const char *const funcName); + bool m_osdWindowVisible[ MAXNUMWINDOWS ]; + +#if APIVERSNUM >= 10307 + int m_osdWindowVideoLeft[ MAXNUMWINDOWS ]; + int m_osdWindowVideoTop[ MAXNUMWINDOWS ]; + int m_osdWindowVideoWidth[ MAXNUMWINDOWS ]; + int m_osdWindowVideoHeight[ MAXNUMWINDOWS ]; + int m_osdWindowVideoZoomX[ MAXNUMWINDOWS ]; + int m_osdWindowVideoZoomY[ MAXNUMWINDOWS ]; + int m_osdWindowLeft[ MAXNUMWINDOWS ]; + int m_osdWindowTop[ MAXNUMWINDOWS ]; + int m_osdWindowWidth[ MAXNUMWINDOWS ]; + int m_osdWindowHeight[ MAXNUMWINDOWS ]; + + int m_osdWindowColorsNum[ MAXNUMWINDOWS ]; + tColor m_osdWindowColors[ MAXNUMWINDOWS ][ MAXNUMCOLORS ]; + + int m_scaledWindowColorsNum[ MAXNUMWINDOWS ]; + tColor m_scaledWindowColors[ MAXNUMWINDOWS ][ MAXNUMCOLORS ]; + + cXineSettings::eOsdMode m_osdWindowMode[ MAXNUMWINDOWS ]; + int m_osdWindowGamma[ MAXNUMWINDOWS ]; + bool m_osdWindowSupportTransparency[ MAXNUMWINDOWS ]; + + int m_osdWindowBufferSize[ MAXNUMWINDOWS ]; + uint8_t *m_osdWindowBuffer[ MAXNUMWINDOWS ]; + + tArea m_vidWin; +#endif + + result_query_capabilities_t m_capabilities; + + void cloneBitmap(const int windowNum, cXineAdapter *bitmap, int x1, int y1, int x2, int y2); + bool bitmapDiffers(const int windowNum, cXineOsd *xineOsd, cXineAdapter *bitmap, int &x1, int &y1, int &x2, int &y2, const int osdX, const int osdY, const int osdW, const int osdH); + bool clipBitmap(cXineOsd *xineOsd, cXineAdapter *bitmap, int &x1, int &y1, int &x2, int &y2, const int osdX, const int osdY, const int osdW, const int osdH); + + bool m_osdFlushRequired; + + public: + int getRemoteFD() const + { + return fd_remote; + } + + private: + int CreateServerSocket(unsigned short port); + int SocketAcceptHelper(int fd); + + int fd_fifo0_serv, fd_result_serv, fd_control_serv, fd_remote_serv; + int fd_fifo0, fd_result, fd_control, fd_remote; + + cMutex m_ioMutex, m_dataMutex, m_disconnectMutex; + cMutex &m_osdMutex; + + bool m_paused; + bool m_frozen; + bool m_ignore; + bool m_shutdown; + + bool m_muted; + int m_volume; + int m_audioChannel; + bool m_trickSpeedMode; + + public: + bool isTrickSpeedMode() const + { + return m_trickSpeedMode; + } + + bool isConnected(); + void disconnect(); + + int xwrite(int f, const void *b, int n); + int xread(int f, void *b, int n); + + private: + bool m_noSignalStream16x9; + char m_noSignalStreamData[2][ 6 + 0xffff ]; + long m_noSignalStreamSize[2]; + + bool readNoSignalStream(const int index, const string &suffix); + + cXineLibEvents *m_eventSink; + + eNeedsScaling NeedsScaling(const int maxOsdWidth, const int maxOsdHeight, const int videoWidth, const int videoHeight); + + bool pushOut(const uchar id = 0xff); + + public: + bool hasNoSignalStream() const + { + return m_noSignalStreamSize[0] > 0 + && m_noSignalStreamSize[1] > 0; + } + + bool SupportsTrueColorOSD() const; + + void selectNoSignalStream(const bool stream16x9) + { + m_noSignalStream16x9 = stream16x9; + } + + virtual void Action(void); + + bool checkXineVersion(); + bool checkConnect(); + + void enableExternal(const bool enable = true); + void ExternalStreamFinished(); + + public: + cXineLib(cPlugin *const plugin, const cXineSettings &settings, cMutex &osdMutex, cXineRemote *const remote); + virtual ~cXineLib(); + + void SetEventSink(cXineLibEvents *const eventSink); + + bool Open(); + void Close(); + + bool Poll(cPoller &Poller, int TimeoutMs = 0, const bool special = false); + +#if APIVERSNUM < 10307 + + bool OpenWindow(cXineOsd *const xineOsd, cWindow *Window, const int maxOsdWidth, const int maxOsdHeight); + void CommitWindow(cXineOsd *const xineOsd, cWindow *Window, const bool optimize = true); + void ShowWindow(cXineOsd *const xineOsd, cWindow *Window); + void HideWindow(cXineOsd *const xineOsd, cWindow *Window, bool Hide); + void MoveWindow(cXineOsd *const xineOsd, cWindow *Window, int x, int y); + void CloseWindow(cXineOsd *const xineOsd, cWindow *Window); + void CloseWindow(cXineOsd *const xineOsd, int Window); + +#else + + void sendWindow(const int maxOsdWidth, const int maxOsdHeight, cXineOsd *const xineOsd, const int windowNum, cXineAdapter *bitmap = 0, const int videoLeft = -1, const int videoTop = -1, const int videoWidth = -1, const int videoHeight = -1, const int videoZoomX = -1, const int videoZoomY = -1, const bool dontOptimize = false); + void SendWindow(const int maxOsdWidth, const int maxOsdHeight, cXineOsd *const xineOsd, const int windowNum, cXineAdapter *bitmap = 0, const int videoLeft = -1, const int videoTop = -1, const int videoWidth = -1, const int videoHeight = -1, const int videoZoomX = -1, const int videoZoomY = -1, const bool dontOptimize = false); + + bool execFuncOsdNew(const int maxOsdWidth, const int maxOsdHeight, const eNeedsScaling needsScaling, const int videoLeft, const int videoTop, const int videoWidth, const int videoHeight, int window, int x, int y, int width, int height); + bool execFuncOsdDrawBitmap(const int maxOsdWidth, const int maxOsdHeight, const eNeedsScaling needsScaling, const int videoWidth, const int videoHeight, cXineOsd *const xineOsd, int window, cXineAdapter *const bitmap, int x, int y, int width, int height, int stride); + + void SetVideoWindow(const int maxOsdWidth, const int maxOsdHeight, tArea vidWin, const bool dontOptimize = false); + +#endif + + int execFuncStream1(const uchar *Data, int Length); + int execFuncStream(const uchar *Data, int Length); + bool execFuncStream0(const uchar *Data, int Length); + bool execFuncStart(); + bool execFuncEnd(); + bool execFuncWait(const bool id = false); + bool execFuncSetup(); + bool execFuncDiscontinuity(); + + bool execFuncOsdFlush(); + bool execFuncOsdNew(int window, int x, int y, int width, int height, int w_ref, int h_ref); + bool execFuncOsdFree(int window); + bool execFuncOsdShow(int window); + bool execFuncOsdHide(int window); + bool execFuncOsdSetPosition(int window, int x, int y); + bool execFuncOsdDrawBitmap(int window, const uint8_t *const bitmap, const int sizeOfPixel, int x, int y, int width, int height, int stride, const tColor *const colors); + +#if APIVERSNUM < 10307 + bool execFuncSetColor(int window, int index, eDvbColor color); + bool execFuncSetColor(int window, int index, eDvbColor *const colors, int numColors); + eDvbColor filterAlpha(eDvbColor color); +#else + bool execFuncSetColor(int window, int index, tColor color); + bool execFuncSetColor(int window, int index, tColor *const colors, int numColors); + tColor filterAlpha(tColor color); +#endif + + bool execFuncSetColor(int window, int index, int numColors, uint32_t *const colors); + + bool execFuncMute(bool mute = true); + bool execFuncClear(int n); + bool execFuncResetAudio(); + bool execFuncSelectAudio(int channel); + bool execFuncFirstFrame(); + bool execFuncStillFrame(); + bool execFuncFlush(int TimeoutMs = -1, const bool justWait = false); + bool execFuncSetVolume(int volume); + bool execFuncSetSpeed(double speed); + bool execFuncTrickSpeedMode(bool on); + bool execFuncDelay(int usDelay); + bool execFuncMetronom(int64_t pts, uint32_t flags = 0); + bool execFuncNop(); + bool execFuncSetPrebuffer(int frames); + bool execFuncGetPTS(int64_t &pts, const int TimeoutMs = 0, bool *const pQueued = 0); + bool execFuncVideoSize(int &videoLeft, int &videoTop, int &videoWidth, int &videoHeight, int &videoZoomX, int &videoZoomY, double *const pVideoAspect = 0); + + bool execFuncGetVersion(int32_t &version); + bool execFuncQueryCapabilities(result_query_capabilities_t &capabilities); + + uchar *execFuncGrabImage(const char *FileName, int &Size, bool Jpeg, int Quality, int SizeX, int SizeY); + + bool execFuncSetVideoWindow(int x, int y, int w, int h, int wRef, int hRef); + + bool execFuncPlayExternal(const char *const fileName = 0); + + bool showNoSignal(); + + void pause(bool doPause = true); + void freeze(bool doFreeze = true); + void ignore(bool doIgnore = true); + + void flush(); + + void LockOsdUpdate(); + + void ReshowCurrentOSD(const int frameLeft, const int frameTop, const int frameWidth, const int frameHeight, const int frameZoomX, const int frameZoomY, const bool unlockOsdUpdate = false); + + void ReshowCurrentOSD(const bool unlockOsdUpdate = false) + { + ReshowCurrentOSD(-1, -1, -1, -1, -1, -1, unlockOsdUpdate); + } + + void DiscontinuityDetected(); + }; + +}; + + + +#endif //__XINELIB_H diff --git a/xineOsd.c b/xineOsd.c new file mode 100644 index 0000000..38ba2e7 --- /dev/null +++ b/xineOsd.c @@ -0,0 +1,674 @@ + +#include "xineCommon.h" + +#include "xineOsd.h" +#include "xineDevice.h" +#include "xineLib.h" + + + +namespace PluginXine +{ +#if APIVERSNUM < 10307 + + bool cXineOsd::OpenWindow(cWindow *Window) + { + cXineOsdMutexLock osdLock(&m_osdMutex); + + m_windowVisible[ Window->Handle() ] = false; + + int maxOsdWidth, maxOsdHeight; + GetMaxOsdSize(maxOsdWidth, maxOsdHeight); + + return m_xineLib.OpenWindow(this, Window, maxOsdWidth, maxOsdHeight); + } + + void cXineOsd::CommitWindow(cWindow *Window) + { + cXineOsdMutexLock osdLock(&m_osdMutex); + + m_xineLib.CommitWindow(this, Window); + } + + void cXineOsd::ShowWindow(cWindow *Window) + { + cXineOsdMutexLock osdLock(&m_osdMutex); + + m_windowVisible[ Window->Handle() ] = true; + + m_xineLib.ShowWindow(this, Window); + } + + void cXineOsd::HideWindow(cWindow *Window, bool Hide) + { + cXineOsdMutexLock osdLock(&m_osdMutex); + + m_windowVisible[ Window->Handle() ] = !Hide; + + m_xineLib.HideWindow(this, Window, Hide); + } + + void cXineOsd::MoveWindow(cWindow *Window, int x, int y) + { + cXineOsdMutexLock osdLock(&m_osdMutex); + + m_xineLib.MoveWindow(this, Window, x, y); + } + + void cXineOsd::CloseWindow(cWindow *Window) + { + cXineOsdMutexLock osdLock(&m_osdMutex); + + m_windowVisible[ Window->Handle() ] = false; + + m_xineLib.CloseWindow(this, Window); + } + +#endif + + + static int vl = -1, vt = -1, vw = -1, vh = -1, zx = -1, zy = -1; + + + void cXineOsd::ReshowCurrentOsd(const bool dontOptimize, const int frameLeft /* = -1 */, const int frameTop /* = -1 */, const int frameWidth /* = -1 */, const int frameHeight /* = -1 */, const int frameZoomX /* = -1 */, const int frameZoomY /* = -1 */) + { + cXineOsdMutexLock osdLock(&m_osdMutex); + + int maxOsdWidth, maxOsdHeight; + GetMaxOsdSize(maxOsdWidth, maxOsdHeight); + +#if APIVERSNUM < 10307 + + (void)vl; + (void)vt; + (void)vw; + (void)vh; + (void)zx; + (void)zy; + + for (int i = 0; i < NumWindows(); i++) + { + cWindow *const window = GetWindowNr(i); + if (!window) + continue; + + if (dontOptimize) + m_xineLib.OpenWindow(this, window, maxOsdWidth, maxOsdHeight); + + m_xineLib.CommitWindow(this, window, !dontOptimize); + + if (m_windowVisible[ i ]) + Show(window->Handle()); + else + Hide(window->Handle()); + } + +#else + +#ifdef SET_VIDEO_WINDOW + + m_xineLib.SetVideoWindow(maxOsdWidth, maxOsdHeight, vidWin, dontOptimize); + +#endif + + int videoLeft = frameLeft; + int videoTop = frameTop; + int videoWidth = frameWidth; + int videoHeight = frameHeight; + int videoZoomX = frameZoomX; + int videoZoomY = frameZoomY; + + if (frameLeft < 0 + || frameTop < 0 + || frameWidth < 0 + || frameHeight < 0 + || frameZoomX < 0 + || frameZoomY < 0) + { + m_xineLib.execFuncVideoSize(videoLeft, videoTop, videoWidth, videoHeight, videoZoomX, videoZoomY); + } + +// ::fprintf(stderr, "frame: %d x %d, %d x %d\n", videoLeft, videoTop, videoWidth, videoHeight, videoZoomX, videoZoomY); + + vl = videoLeft; + vt = videoTop; + vw = videoWidth; + vh = videoHeight; + zx = videoZoomX; + zy = videoZoomY; + + callSendWindow(maxOsdWidth, maxOsdHeight, videoLeft, videoTop, videoWidth, videoHeight, videoZoomX, videoZoomY, dontOptimize); +#endif + + m_xineLib.execFuncOsdFlush(); + } + +#if APIVERSNUM < 10509 + cXineOsd::cXineOsd(cXineDevice &xineDevice, int w, int h, int x, int y) +#else + cXineOsd::cXineOsd(cXineDevice &xineDevice, int w, int h, int x, int y, uint Level) +#endif +#if APIVERSNUM < 10307 + : cOsdBase(x, y) +#elif APIVERSNUM < 10509 + : cOsd(x, y) +#else + : cOsd(x, y, Level) +#endif + , m_xineDevice(xineDevice) + , m_xineLib(xineDevice.m_xineLib) + , m_osdMutex(xineDevice.m_osdMutex) + , m_extentWidth(w) + , m_extentHeight(h) +#if APIVERSNUM >= 10717 + , m_pRawOsd(0) +#endif + { +//static int cnt = 0; +//fprintf(stderr, "x: %d, y: %d, cnt: %d\n", x, y, cnt); +// if (x == 58 && y == 46 && cnt++ == 5) { char cmd[500]; sprintf(cmd, "ddd /soft/vdr-" APIVERSION "/bin/vdr %d", getpid()); system(cmd); sleep(10); } //should never happen! + +#if APIVERSNUM < 10307 + ::memset(m_windowVisible, 0, sizeof (m_windowVisible)); +#endif + } + + cXineOsd::~cXineOsd() + { +#if APIVERSNUM < 10509 + HideOsd(); +#else + cXineOsdMutexLock osdLock(&m_osdMutex); + + if (Active()) + SetActive(false); +#endif +#if APIVERSNUM >= 10717 + delete m_pRawOsd; +#endif + } + + void cXineOsd::HideOsd() + { + cXineOsdMutexLock osdLock(&m_osdMutex); + +#if APIVERSNUM < 10307 + + for (int i = 0; i < MAXNUMWINDOWS; i++) + m_xineLib.CloseWindow(this, i); + +#else + + int maxOsdWidth, maxOsdHeight; + GetMaxOsdSize(maxOsdWidth, maxOsdHeight); + + tArea defaultWindow; + defaultWindow.x1 = 0; + defaultWindow.y1 = 0; + defaultWindow.x2 = 0; + defaultWindow.y2 = 0; + defaultWindow.bpp = 0; + + m_xineLib.SetVideoWindow(maxOsdWidth, maxOsdHeight, defaultWindow); + + for (int i = 0; i < MAXNUMWINDOWS; i++) + m_xineLib.SendWindow(maxOsdWidth, maxOsdHeight, this, i); + +#endif + + m_xineDevice.OnFreeOsd(this); + + m_xineLib.execFuncOsdFlush(); + +// m_xineLib.execFuncGrabImage("/tmp/grab.jpg", true, 50, 1000, 1000); + } + +#if APIVERSNUM >= 10307 + + void cXineOsd::SetActive(bool On) + { +#if APIVERSNUM >= 10509 + + cXineOsdMutexLock osdLock(&m_osdMutex); + + if (On == Active()) + return; + + cOsd::SetActive(On); + + if (!m_xineDevice.ChangeCurrentOsd(this, On)) + return; + + if (On) + m_xineLib.ReshowCurrentOSD(); + else + HideOsd(); + +#endif + } + +#endif + +#if APIVERSNUM >= 10717 + + cPixmap *cXineOsd::CreatePixmap(int Layer, const cRect &ViewPort, const cRect &DrawPort /* = cRect::Null */) + { + cXineOsdMutexLock osdLock(&m_osdMutex); + return cOsd::CreatePixmap(Layer, ViewPort, DrawPort); + } + + void cXineOsd::DestroyPixmap(cPixmap *Pixmap) + { + cXineOsdMutexLock osdLock(&m_osdMutex); + cOsd::DestroyPixmap(Pixmap); + } + + void cXineOsd::DrawImage(const cPoint &Point, const cImage &Image) + { + cXineOsdMutexLock osdLock(&m_osdMutex); + cOsd::DrawImage(Point, Image); + } + + void cXineOsd::DrawImage(const cPoint &Point, int ImageHandle) + { + cXineOsdMutexLock osdLock(&m_osdMutex); + cOsd::DrawImage(Point, ImageHandle); + } + +#endif + +#if APIVERSNUM >= 10307 + + eOsdError cXineOsd::CanHandleAreas(const tArea *Areas, int NumAreas) + { +// fprintf(stderr, "cXineOsd::CanHandleAreas(%p, %d)\n", Areas, NumAreas); + + const eOsdError retVal = cOsd::CanHandleAreas(Areas, NumAreas); + if (oeOk != retVal) + return retVal; + + for (int i = 0; i < NumAreas; i++) + { + const tArea &a = Areas[ i ]; +/* + fprintf(stderr, "Areas[ %d ]: (%d,%d)-(%d,%d)@%d\n" + , i + , a.x1 + , a.y1 + , a.x2 + , a.y2 + , a.bpp); +*/ + assert(a.x1 <= a.x2); + assert(a.y1 <= a.y2); + + if (1 != a.bpp + && 2 != a.bpp + && 4 != a.bpp + && 8 != a.bpp +#if APIVERSNUM >= 10717 + && (32 != a.bpp || !m_xineDevice.SupportsTrueColorOSD()) +#endif + ) + { + return oeBppNotSupported; + } + } + + return oeOk; + } + + eOsdError cXineOsd::SetAreas(const tArea *Areas, int NumAreas) + { + cXineOsdMutexLock osdLock(&m_osdMutex); + return cOsd::SetAreas(Areas, NumAreas); + } + + void cXineOsd::SaveRegion(int x1, int y1, int x2, int y2) + { + cXineOsdMutexLock osdLock(&m_osdMutex); + cOsd::SaveRegion(x1, y1, x2, y2); + } + + void cXineOsd::RestoreRegion(void) + { + cXineOsdMutexLock osdLock(&m_osdMutex); + cOsd::RestoreRegion(); + } + + eOsdError cXineOsd::SetPalette(const cPalette &Palette, int Area) + { + cXineOsdMutexLock osdLock(&m_osdMutex); + return cOsd::SetPalette(Palette, Area); + } + + void cXineOsd::DrawPixel(int x, int y, tColor Color) + { + cXineOsdMutexLock osdLock(&m_osdMutex); + cOsd::DrawPixel(x, y, Color); + } + +#if APIVERSNUM < 10327 + void cXineOsd::DrawBitmap(int x, int y, const cBitmap &Bitmap, tColor ColorFg /* = 0 */, tColor ColorBg /* = 0 */) + { + cXineOsdMutexLock osdLock(&m_osdMutex); + cOsd::DrawBitmap(x, y, Bitmap, ColorFg, ColorBg); +// fprintf(stderr, "drawbitmap\n"); + } +#elif APIVERSNUM < 10344 + void cXineOsd::DrawBitmap(int x, int y, const cBitmap &Bitmap, tColor ColorFg /* = 0 */, tColor ColorBg /* = 0 */, bool ReplacePalette /* = false */) + { + cXineOsdMutexLock osdLock(&m_osdMutex); + cOsd::DrawBitmap(x, y, Bitmap, ColorFg, ColorBg, ReplacePalette); +// fprintf(stderr, "drawbitmap\n"); + } +#else + void cXineOsd::DrawBitmap(int x, int y, const cBitmap &Bitmap, tColor ColorFg /* = 0 */, tColor ColorBg /* = 0 */, bool ReplacePalette /* = false */, bool Overlay /* = false */) + { + cXineOsdMutexLock osdLock(&m_osdMutex); + cOsd::DrawBitmap(x, y, Bitmap, ColorFg, ColorBg, ReplacePalette, Overlay); +// fprintf(stderr, "drawbitmap: (%d x %d) at (%d, %d) in (%d x %d)\n", Bitmap.Width(), Bitmap.Height(), x, y, Width(), Height()); + } +#endif + + void cXineOsd::DrawText(int x, int y, const char *s, tColor ColorFg, tColor ColorBg, const cFont *Font, int Width /* = 0 */, int Height /* = 0 */, int Alignment /* = taDefault */) + { + cXineOsdMutexLock osdLock(&m_osdMutex); + cOsd::DrawText(x, y, s, ColorFg, ColorBg, Font, Width, Height, Alignment); + } + + void cXineOsd::DrawRectangle(int x1, int y1, int x2, int y2, tColor Color) + { + cXineOsdMutexLock osdLock(&m_osdMutex); + cOsd::DrawRectangle(x1, y1, x2, y2, Color); + } + + void cXineOsd::DrawEllipse(int x1, int y1, int x2, int y2, tColor Color, int Quadrants /* = 0 */) + { + cXineOsdMutexLock osdLock(&m_osdMutex); + cOsd::DrawEllipse(x1, y1, x2, y2, Color, Quadrants); + } + + void cXineOsd::DrawSlope(int x1, int y1, int x2, int y2, tColor Color, int Type) + { + cXineOsdMutexLock osdLock(&m_osdMutex); + cOsd::DrawSlope(x1, y1, x2, y2, Color, Type); + } + + void cXineOsd::Flush(void) + { +// ::fprintf(stderr, "Flush ---: %s\n", ::ctime(&(const time_t &)::time(0))); + cXineOsdMutexLock osdLock(&m_osdMutex); +// ::fprintf(stderr, "Flush +++: %s\n", ::ctime(&(const time_t &)::time(0))); +#if APIVERSNUM >= 10509 + if (!Active()) + return; +#endif +// static int cnt = 0; +// fprintf(stderr, "cXineOsd::Flush() %d\n", cnt++); + + int maxOsdWidth, maxOsdHeight; + GetMaxOsdSize(maxOsdWidth, maxOsdHeight); + +#ifdef SET_VIDEO_WINDOW + + m_xineLib.SetVideoWindow(maxOsdWidth, maxOsdHeight, vidWin); + +#endif + + int videoLeft = -1; + int videoTop = -1; + int videoWidth = -1; + int videoHeight = -1; + int videoZoomX = -1; + int videoZoomY = -1; + + m_xineLib.execFuncVideoSize(videoLeft, videoTop, videoWidth, videoHeight, videoZoomX, videoZoomY); + + if (videoLeft < 0) videoLeft = 0; + if (videoTop < 0) videoTop = 0; + if (videoZoomX < 0) videoZoomX = 100; + if (videoZoomY < 0) videoZoomY = 100; + + if (vl != videoLeft + || vt != videoTop + || vw != videoWidth + || vh != videoHeight + || zx != videoZoomX + || zy != videoZoomY) + { + xfprintf(stderr, "frame: (%d, %d)-(%d, %d), zoom: (%.2lf, %.2lf)\n", videoLeft, videoTop, videoWidth, videoHeight, videoZoomX / 100.0, videoZoomY / 100.0); +// ::fprintf(stderr, "video: %d x %d, %d x %d @ %d %% x %d %%\n", videoLeft, videoTop, videoWidth, videoHeight, videoZoomX, videoZoomY); + + vl = videoLeft; + vt = videoTop; + vw = videoWidth; + vh = videoHeight; + zx = videoZoomX; + zy = videoZoomY; + } + + callSendWindow(maxOsdWidth, maxOsdHeight, videoLeft, videoTop, videoWidth, videoHeight, videoZoomX, videoZoomY); + + m_xineLib.execFuncOsdFlush(); + } + + void cXineOsd::callSendWindow(const int maxOsdWidth, const int maxOsdHeight, const int videoLeft, const int videoTop, const int videoWidth, const int videoHeight, const int videoZoomX, const int videoZoomY, const bool dontOptimize /* = false */) + { +#if APIVERSNUM >= 10717 +bool head = false; + if (IsTrueColor()) + { + if (m_pRawOsd) + { + if (m_pRawOsd->DrawPort().Width() != Width() + || m_pRawOsd->DrawPort().Height() != Height()) + { + delete m_pRawOsd; + m_pRawOsd = 0; + } + } + + if (!m_pRawOsd) + { + m_pRawOsd = new cPixmapMemory(0, cRect(0, 0, Width(), Height())); + m_pRawOsd->Clear(); +if (!head) +{ + head = true; +// fprintf(stderr, "OSD: -c-c-c-c-c-c-c-c-c-c-c-c-c-c-c-c-c-c-c-c-c-c-c-c-c\n"); +} + } + + while (cPixmapMemory *pm = RenderPixmaps()) + { +if (!head) +{ + head = true; +// fprintf(stderr, "OSD: --------------------------------------------------\n"); +} +/* + fprintf(stderr, "OSD: X: %4d, Y: %4d, W: %4d, H: %4d, X: %4d, Y: %4d, W: %4d, H: %4d\n" +, pm->ViewPort().X() +, pm->ViewPort().Y() +, pm->ViewPort().Width() +, pm->ViewPort().Height() +, pm->DrawPort().X() +, pm->DrawPort().Y() +, pm->DrawPort().Width() +, pm->DrawPort().Height() +); +*/ + m_pRawOsd->Copy(pm, pm->DrawPort().Shifted(-pm->DrawPort().Point()), pm->ViewPort().Point()); + delete pm; + } + } + else if (m_pRawOsd) + { + delete m_pRawOsd; + m_pRawOsd = 0; +/* +if (head) + fprintf(stderr, "OSD: =d=d=d=d=d=d=d=d=d=d=d=d=d=d=d=d=d=d=d=d=d=d=d=d=d\n"); +*/ + } + + if (m_pRawOsd) + { + cXinePixmapMemoryAdapter adapter(m_pRawOsd); +/* +cXineAdapter *bm = adapter; +int x0, y0, x1, y1; +bool d = bm->Dirty(x0, y0, x1, y1); +if (head) + fprintf(stderr, d ? "OSD: x: %4d, y: %4d, w: %4d, h: %4d, x0: %4d, y0: %4d, x1: %4d, y1: %4d\n" : "OSD: x: %4d, y: %4d, w: %4d, h: %4d\n" +, bm->X0() +, bm->Y0() +, bm->Width() +, bm->Height() +, x0 +, y0 +, x1 +, y1 +); +*/ + m_xineLib.SendWindow(maxOsdWidth, maxOsdHeight, this, 0, adapter, videoLeft, videoTop, videoWidth, videoHeight, videoZoomX, videoZoomY, dontOptimize); + + for (int i = 1; i < MAXNUMWINDOWS; i++) + m_xineLib.SendWindow(maxOsdWidth, maxOsdHeight, this, i); +/* +if (head) + fprintf(stderr, "OSD: ==================================================\n"); +*/ + } + else +#endif + { + for (int i = 0; i < MAXNUMWINDOWS; i++) + { + cXineBitmapAdapter adapter(GetBitmap(i)); + m_xineLib.SendWindow(maxOsdWidth, maxOsdHeight, this, i, adapter, videoLeft, videoTop, videoWidth, videoHeight, videoZoomX, videoZoomY, dontOptimize); + } + } + } + + void cXineOsd::GetMaxOsdSize(int &maxOsdWidth, int &maxOsdHeight) + { + maxOsdWidth = m_extentWidth; + maxOsdHeight = m_extentHeight; + return; +/* + maxOsdWidth = 0; + maxOsdHeight = 0; + + for (int i = 0; i < MAXNUMWINDOWS; i++) + { + cBitmap *const p = GetBitmap(i); + if (!p) + continue; + + int w = Left() + p->X0() + p->Width(); + int h = Top() + p->Y0() + p->Height(); + + if (maxOsdWidth < w) + maxOsdWidth = w; + + if (maxOsdHeight < h) + maxOsdHeight = h; + } +//fprintf(stderr, "GetMaxOsdSize(%d, %d)\n", maxOsdWidth, maxOsdHeight); +#if APIVERSNUM >= 10707 + if (maxOsdWidth <= m_xineDevice.Settings().OsdExtentParams().GetOsdExtentWidth() + && maxOsdHeight <= m_xineDevice.Settings().OsdExtentParams().GetOsdExtentHeight()) + { + maxOsdWidth = m_xineDevice.Settings().OsdExtentParams().GetOsdExtentWidth(); + maxOsdHeight = m_xineDevice.Settings().OsdExtentParams().GetOsdExtentHeight(); + } + else +#endif + if (maxOsdWidth > 1920 || maxOsdHeight > 1080) + { + if (maxOsdWidth < 1920) + maxOsdWidth = 1920; + + if (maxOsdHeight < 1080) + maxOsdHeight = 1080; + } + else if (maxOsdWidth > 1280 || maxOsdHeight > 720) + { + maxOsdWidth = 1920; + maxOsdHeight = 1080; + } + else if (maxOsdWidth > 720 || maxOsdHeight > 576) + { + maxOsdWidth = 1280; + maxOsdHeight = 720; + } + else + { + maxOsdWidth = 720; + maxOsdHeight = 576; + } +*/ + } + + + + cXineOsdMutexLock::cXineOsdMutexLock(cMutex *const pOsdMutex) + : m_pOsdLock(0) +#if APIVERSNUM >= 10717 + , m_pPixmapMutexLock(0) +#endif + { +#if APIVERSNUM >= 10717 + m_pPixmapMutexLock = new cPixmapMutexLock; +#endif + m_pOsdLock = new cMutexLock(pOsdMutex); + } + + cXineOsdMutexLock::~cXineOsdMutexLock() + { + delete m_pOsdLock; + +#if APIVERSNUM >= 10717 + delete m_pPixmapMutexLock; +#endif + } + + + + cXineOsdProvider::cXineOsdProvider(cXineDevice &xineDevice) + : cOsdProvider() + , m_xineDevice(xineDevice) + { + } + +#if APIVERSNUM < 10509 + cOsd *cXineOsdProvider::CreateOsd(int Left, int Top) + { + return m_xineDevice.NewOsd(0, 0, Left, Top); + } +#else + cOsd *cXineOsdProvider::CreateOsd(int Left, int Top, uint Level) + { + return m_xineDevice.NewOsd(0, 0, Left, Top, Level); + } + + cOsd *cXineOsdProvider::CreateOsd(int ExtentWidth, int ExtentHeight, int Left, int Top, uint Level) + { + return m_xineDevice.NewOsd(ExtentWidth, ExtentHeight, Left, Top, Level); + } +#endif + +#if APIVERSNUM >= 10717 + bool cXineOsdProvider::ProvidesTrueColor() + { + return true; + } +#endif + +#endif + + + +}; diff --git a/xineOsd.h b/xineOsd.h new file mode 100644 index 0000000..9bf58ae --- /dev/null +++ b/xineOsd.h @@ -0,0 +1,139 @@ + +#ifndef __XINEOSD_H +#define __XINEOSD_H + + + +#include "xineCommon.h" + +#if APIVERSNUM < 10307 +#include <vdr/osdbase.h> +#else +#include <vdr/osd.h> +#endif + +#include <vdr/thread.h> + + + +namespace PluginXine +{ + + class cXineDevice; + class cXineLib; + +#if APIVERSNUM < 10307 + class cXineOsd : public cOsdBase +#else + class cXineOsd : public cOsd +#endif + { + cXineDevice &m_xineDevice; + cXineLib &m_xineLib; + cMutex &m_osdMutex; + const int m_extentWidth; + const int m_extentHeight; + +#if APIVERSNUM < 10307 + bool m_windowVisible[ MAXNUMWINDOWS ]; +#endif +#if APIVERSNUM >= 10717 + cPixmapMemory *m_pRawOsd; +#endif + + void callSendWindow(const int maxOsdWidth, const int maxOsdHeight, const int videoLeft, const int videoTop, const int videoWidth, const int videoHeight, const int videoZoomX, const int videoZoomY, const bool dontOptimize = false); + +#if APIVERSNUM < 10307 + virtual bool OpenWindow(cWindow *Window); + virtual void CommitWindow(cWindow *Window); + virtual void ShowWindow(cWindow *Window); + virtual void HideWindow(cWindow *Window, bool Hide); + virtual void MoveWindow(cWindow *Window, int x, int y); + virtual void CloseWindow(cWindow *Window); +#else + virtual void SetActive(bool On); +#if APIVERSNUM >= 10717 + virtual cPixmap *CreatePixmap(int Layer, const cRect &ViewPort, const cRect &DrawPort = cRect::Null); + virtual void DestroyPixmap(cPixmap *Pixmap); + virtual void DrawImage(const cPoint &Point, const cImage &Image); + virtual void DrawImage(const cPoint &Point, int ImageHandle); +#endif + virtual eOsdError CanHandleAreas(const tArea *Areas, int NumAreas); + virtual eOsdError SetAreas(const tArea *Areas, int NumAreas); + virtual void SaveRegion(int x1, int y1, int x2, int y2); + virtual void RestoreRegion(void); + virtual eOsdError SetPalette(const cPalette &Palette, int Area); + virtual void DrawPixel(int x, int y, tColor Color); +#if APIVERSNUM < 10327 + virtual void DrawBitmap(int x, int y, const cBitmap &Bitmap, tColor ColorFg = 0, tColor ColorBg = 0); +#elif APIVERSNUM < 10344 + virtual void DrawBitmap(int x, int y, const cBitmap &Bitmap, tColor ColorFg = 0, tColor ColorBg = 0, bool ReplacePalette = false); +#else + virtual void DrawBitmap(int x, int y, const cBitmap &Bitmap, tColor ColorFg = 0, tColor ColorBg = 0, bool ReplacePalette = false, bool Overlay = false); +#endif + virtual void DrawText(int x, int y, const char *s, tColor ColorFg, tColor ColorBg, const cFont *Font, int Width = 0, int Height = 0, int Alignment = taDefault); + virtual void DrawRectangle(int x1, int y1, int x2, int y2, tColor Color); + virtual void DrawEllipse(int x1, int y1, int x2, int y2, tColor Color, int Quadrants = 0); + virtual void DrawSlope(int x1, int y1, int x2, int y2, tColor Color, int Type); + virtual void Flush(void); +#endif + void HideOsd(); + void GetMaxOsdSize(int &maxOsdWidth, int &maxOsdHeight); + + public: +#if APIVERSNUM < 10509 + cXineOsd(cXineDevice &device, int w, int h, int x, int y); +#else + cXineOsd(cXineDevice &device, int w, int h, int x, int y, uint Level); +#endif + virtual ~cXineOsd(); + + void ReshowCurrentOsd(const bool dontOptimize, const int frameLeft = -1, const int frameTop = -1, const int frameWidth = -1, const int frameHeight = -1, const int frameZoomX = -1, const int frameZoomY = -1); + + friend class cXineLib; + }; + + + + class cXineOsdMutexLock + { + cMutexLock *m_pOsdLock; +#if APIVERSNUM >= 10717 + cPixmapMutexLock *m_pPixmapMutexLock; +#endif + + public: + cXineOsdMutexLock(cMutex *const pOsdMutex); + ~cXineOsdMutexLock(); + }; + + + +#if APIVERSNUM >= 10307 + + class cXineOsdProvider : public cOsdProvider + { + cXineDevice &m_xineDevice; + + public: + cXineOsdProvider(cXineDevice &xineDevice); +#if APIVERSNUM < 10509 + virtual cOsd *CreateOsd(int Left, int Top); +#else + virtual cOsd *CreateOsd(int Left, int Top, uint Level); + virtual cOsd *CreateOsd(int ExtentWidth, int ExtentHeight, int Left, int Top, uint Level); +#endif +#if APIVERSNUM >= 10717 + virtual bool ProvidesTrueColor(void); +#endif + }; + +#endif + + + +}; + + + +#endif //__XINEOSD_H diff --git a/xineRemote.c b/xineRemote.c new file mode 100644 index 0000000..cfe2935 --- /dev/null +++ b/xineRemote.c @@ -0,0 +1,281 @@ + +#include "xineCommon.h" + +#include "xineLib.h" + + + +namespace PluginXine +{ + + static eKeys KeyMap[] = + { + kNone, + kUp, + kDown, + kMenu, + kOk, + kBack, + kLeft, + kRight, + kRed, + kGreen, + kYellow, + kBlue, + k0, k1, k2, k3, k4, k5, k6, k7, k8, k9, + kPlay, + kPause, + kStop, + kRecord, + kFastFwd, + kFastRew, + kPower, + kChanUp, + kChanDn, + kVolUp, + kVolDn, + kMute, + kSchedule, + kChannels, + kTimers, + kRecordings, + kSetup, + kCommands, + kUser1, kUser2, kUser3, kUser4, kUser5, kUser6, kUser7, kUser8, kUser9, +#if APIVERSNUM >= 10318 + kAudio, +#else + kNone, +#endif +#if APIVERSNUM >= 10338 + kInfo, +#else + kNone, +#endif +#if APIVERSNUM >= 10347 + kChanPrev, + kNext, + kPrev, +#else + kNone, + kNone, + kNone, +#endif +#if APIVERSNUM >= 10510 + kSubtitles, +#else + kNone, +#endif +#if APIVERSNUM >= 10715 + kUser0, +#else + kNone, +#endif + }; + + cXineRemote::cXineRemote(const bool remoteOn) + : cRemote("XineRemote") + , cThread() + , m_remoteOn(remoteOn) + , m_xineLib(0) + { +#if APIVERSNUM >= 10300 + SetDescription("XineRemote control"); +#endif + + // ::fprintf( stderr, "XineRemote constructor\n"); + m_active = false; + Start (); // calls Action + } + + cXineRemote::~cXineRemote() + { + { + cMutexLock activeMutexLock(&m_activeMutex); + m_active = false; + m_activeCondVar.Broadcast(); + } + + // Close(); + Cancel(3); + } + + void cXineRemote::setXineLib(cXineLib *const xineLib) + { + m_xineLib = xineLib; + } + + bool cXineRemote::isConnected() + { + if (!m_xineLib) + return false; + + return m_xineLib->isConnected(); + } + + void cXineRemote::Action (void) + { + dsyslog ("Entering cXineRemote thread\n"); + + bool externalStreamFinished = false; + bool osdReshowRequired = false; + bool discontinuityDetected = false; + int frameLeft = 0; + int frameTop = 0; + int frameWidth = 0; + int frameHeight = 0; + int frameZoomX = 0; + int frameZoomY = 0; + + m_active = true; + + while (m_active) + { + if (isConnected()) + { + if (osdReshowRequired) + { + osdReshowRequired = false; + + if (m_xineLib) + m_xineLib->ReshowCurrentOSD(frameLeft, frameTop, frameWidth, frameHeight, frameZoomX, frameZoomY); + } + + if (externalStreamFinished) + { + externalStreamFinished = false; + + if (m_xineLib) + m_xineLib->ExternalStreamFinished(); + } + + if (discontinuityDetected) + { + discontinuityDetected = false; + + if (m_xineLib) + m_xineLib->DiscontinuityDetected(); + } + + cPoller Poller(m_xineLib->getRemoteFD()); + errno = 0; + if (Poller.Poll(100) + && 0 == errno) + { + event_union_t event_union; + + //printf( "fd_fifo read: %d\n", fd_fifo0); + + if (isConnected()) + { + int n = m_xineLib->xread(m_xineLib->getRemoteFD(), (char *)&event_union.header, sizeof (event_union.header)); + if (n == sizeof(event_union.header)) + { + // could check that we have header we expected + //::fprintf(stderr, "got Key: %d\n", userInput.key); + + if (func_key == event_union.header.func) + { + event_key_t *event = &event_union.key; + int n = m_xineLib->xread(m_xineLib->getRemoteFD(), (char *)event + sizeof (event->header), sizeof (*event) - sizeof (event->header)); + + if (n == sizeof (*event) - sizeof (event->header)) + { + if (m_remoteOn) + { + if (event->key < (sizeof (KeyMap) / sizeof (*KeyMap))) + cRemote::Put( KeyMap[ event->key ]); +// ::fprintf(stderr, "Putting Key: 0x%08x\n", KeyMap[ event->key ]); + } + } + else + { + m_xineLib->disconnect(); + } + } + else if (func_frame_size == event_union.header.func) + { + event_frame_size_t *event = &event_union.frame_size; + int n = m_xineLib->xread(m_xineLib->getRemoteFD(), (char *)event + sizeof (event->header), sizeof (*event) - sizeof (event->header)); + + if (n == sizeof (*event) - sizeof (event->header)) + { + osdReshowRequired = true; + + frameLeft = event->left; + frameTop = event->top; + frameWidth = event->width; + frameHeight = event->height; + frameZoomX = event->zoom_x; + frameZoomY = event->zoom_y; + + if (frameLeft < 0) frameLeft = 0; + if (frameTop < 0) frameTop = 0; + if (frameZoomX < 0) frameZoomX = 100; + if (frameZoomY < 0) frameZoomY = 100; + + xfprintf(stderr, "frame: (%d, %d)-(%d, %d), zoom: (%.2lf, %.2lf)\n", frameLeft, frameTop, frameWidth, frameHeight, frameZoomX / 100.0, frameZoomY / 100.0); + } + else + { + m_xineLib->disconnect(); + } + } + else if (func_discontinuity == event_union.header.func) + { + event_discontinuity_t *event = &event_union.discontinuity; + int n = m_xineLib->xread(m_xineLib->getRemoteFD(), (char *)event + sizeof (event->header), sizeof (*event) - sizeof (event->header)); + + if (n == sizeof (*event) - sizeof (event->header)) + { + discontinuityDetected = true; + } + else + { + m_xineLib->disconnect(); + } + } + else if (func_play_external == event_union.header.func) + { + event_play_external_t *event = &event_union.play_external; + int n = m_xineLib->xread(m_xineLib->getRemoteFD(), (char *)event + sizeof (event->header), sizeof (*event) - sizeof (event->header)); + + if (n == sizeof (*event) - sizeof (event->header)) + { + externalStreamFinished = true; + } + else + { + m_xineLib->disconnect(); + } + } + else + { + ::fprintf(stderr, "vdr-xine: unknown event %d!\n", event_union.header.func); + m_xineLib->disconnect(); + } + } + else + { + m_xineLib->disconnect(); + } + } + } + } + else + { + if (m_active) + { + cMutexLock activeMutexLock(&m_activeMutex); + if (m_active) + m_activeCondVar.TimedWait(m_activeMutex, 100); + } + } + } + + dsyslog ("Leaving cXineRemote thread\n"); + } + +}; // namepsace PluginXine + + diff --git a/xineSettings.c b/xineSettings.c new file mode 100644 index 0000000..8796995 --- /dev/null +++ b/xineSettings.c @@ -0,0 +1,791 @@ + +#include "xineCommon.h" + +#include <vdr/interface.h> +#include <vdr/i18n.h> + +#include "xineSettings.h" +#include "xineSetupPage.h" +#include "xineDevice.h" + + + +namespace PluginXine +{ + + cXineSettings::cModeParams::cModeParams() + : m_prebufferFramesVideoSD(4) + , m_prebufferFramesVideoHD(4) + , m_prebufferFramesAudio(4) + , m_prebufferHysteresis(4) + , m_monitoringDuration(10) + , m_monitoringMode(monitoringOnce) + { + } + + bool cXineSettings::cModeParams::SetupParse(const char *optionName, int &optionValue, const char *Name, const char *Value) + { + if (0 != ::strcasecmp(optionName, Name)) + return false; + + char c; + int v = 0; + if (1 != ::sscanf(Value, "%d%c", &v, &c)) + return false; + + if (0 <= v && v <= 100) + { + optionValue = v; + return true; + } + + return false; + } + + bool cXineSettings::cModeParams::SetupParse(const char *optionName, eMonitoringMode &optionValue, const char *Name, const char *Value) + { + if (0 != ::strcasecmp(optionName, Name)) + return false; + + if (0 == ::strcasecmp("monitoringOnce", Value)) + { + optionValue = monitoringOnce; + return true; + } + + if (0 == ::strcasecmp("monitoringContinuous", Value)) + { + optionValue = monitoringContinuous; + return true; + } + + return false; + } + + bool cXineSettings::cModeParams::SetupParse(const char *prefix, const char *Name, const char *Value) + { + const int prefixLen = ::strlen(prefix); + + if (0 != ::strncasecmp(prefix, Name, prefixLen)) + return false; + + Name += prefixLen; + + if (SetupParse("prebufferFrames", m_prebufferFramesVideoSD, Name, Value) + && SetupParse("prebufferFrames", m_prebufferFramesVideoHD, Name, Value) + && SetupParse("prebufferFrames", m_prebufferFramesAudio, Name, Value)) + { + return true; + } + + if (SetupParse("prebufferFramesVideoSD", m_prebufferFramesVideoSD, Name, Value)) + return true; + + if (SetupParse("prebufferFramesVideoHD", m_prebufferFramesVideoHD, Name, Value)) + return true; + + if (SetupParse("prebufferFramesAudio", m_prebufferFramesAudio, Name, Value)) + return true; + + if (SetupParse("prebufferHysteresis", m_prebufferHysteresis, Name, Value)) + return true; + + if (SetupParse("monitoringDuration", m_monitoringDuration, Name, Value)) + return true; + + if (SetupParse("monitoringMode", m_monitoringMode, Name, Value)) + return true; + + return false; + } + + bool cXineSettings::cModeParams::MonitoringContinuous() const + { + return monitoringContinuous == m_monitoringMode; + } + + + cXineSettings::cZoomParams::cZoomParams() + : m_zoomX(imageZoomDefault) + , m_zoomY(imageZoomDefault) + { + } + + bool cXineSettings::cZoomParams::SetupParse(const char *optionName, eImageZoom &optionValue, const char *Name, const char *Value) + { + if (0 != ::strcasecmp(optionName, Name)) + return false; + + char c; + int v = 0; + if (1 != ::sscanf(Value, "%d%c", &v, &c)) + return false; + + if (imageZoomMin <= v && v <= imageZoomMax) + { + optionValue = (eImageZoom)v; + return true; + } + + return false; + } + + bool cXineSettings::cZoomParams::SetupParse(const char *prefix, const char *Name, const char *Value) + { + const int prefixLen = ::strlen(prefix); + + if (0 != ::strncasecmp(prefix, Name, prefixLen)) + return false; + + Name += prefixLen; + + if (SetupParse("zoomX", m_zoomX, Name, Value)) + return true; + + if (SetupParse("zoomY", m_zoomY, Name, Value)) + return true; + + return false; + } + + bool cXineSettings::cZoomParams::operator !=(const cXineSettings::cZoomParams &rhs) const + { + return GetZoomX() != rhs.GetZoomX() + || GetZoomY() != rhs.GetZoomY(); + } + + + cXineSettings::cOsdExtentParams::cOsdExtentParams() + : m_osdExtentWidth(osdExtentWidthDefault) + , m_osdExtentHeight(osdExtentHeightDefault) + { + } + + bool cXineSettings::cOsdExtentParams::SetupParse(const char *optionName, eOsdExtentWidth &optionValue, const char *Name, const char *Value) + { + if (0 != ::strcasecmp(optionName, Name)) + return false; + + char c; + int v = 0; + if (1 != ::sscanf(Value, "%d%c", &v, &c)) + return false; + + if (osdExtentWidthMin <= v && v <= osdExtentWidthMax) + { + optionValue = (eOsdExtentWidth)v; + return true; + } + + return false; + } + + bool cXineSettings::cOsdExtentParams::SetupParse(const char *optionName, eOsdExtentHeight &optionValue, const char *Name, const char *Value) + { + if (0 != ::strcasecmp(optionName, Name)) + return false; + + char c; + int v = 0; + if (1 != ::sscanf(Value, "%d%c", &v, &c)) + return false; + + if (osdExtentHeightMin <= v && v <= osdExtentHeightMax) + { + optionValue = (eOsdExtentHeight)v; + return true; + } + + return false; + } + bool cXineSettings::cOsdExtentParams::SetupParse(const char *prefix, const char *Name, const char *Value) + { + const int prefixLen = ::strlen(prefix); + + if (0 != ::strncasecmp(prefix, Name, prefixLen)) + return false; + + Name += prefixLen; + + if (SetupParse("X", m_osdExtentWidth, Name, Value)) + return true; + + if (SetupParse("Y", m_osdExtentHeight, Name, Value)) + return true; + + return false; + } + + bool cXineSettings::cOsdExtentParams::operator !=(const cXineSettings::cOsdExtentParams &rhs) const + { + return GetOsdExtentWidth() != rhs.GetOsdExtentWidth() + || GetOsdExtentHeight() != rhs.GetOsdExtentHeight(); + } + + + + cXineSettings::cXineSettings() + : m_switchSkin(false) + , m_beQuiet(false) + , m_defaultGrabSizeX(720) + , m_defaultGrabSizeY(576) + , m_osdMode(osdBlendScaledAuto) + , m_usageMode(modeLiveTV) +// , m_usageModeDefault(modeLiveTV) + , m_audioMode(audioDolbyOff) + , m_volumeMode(volumeChangeHW) + , m_muteMode(muteSimulate) + , m_crtGamma(monitorGammaDefault) + , m_autoPrimaryDeviceMode(autoPrimaryDeviceOn) + , m_transparencyMode(transparencyOn) + , m_interactWithEitScannerMode(interactWithEitScannerOff) + { +// m_modeParams[ modeLiveTV ].m_prebufferFrames = 4; //2 * 15 + 1; +// m_modeParams[ modeLiveTV ].m_prebufferHysteresis = 4; //2 * 15 + 1; + } + + bool cXineSettings::SetupParse(const char *optionName, eUsageMode &optionValue, const char *Name, const char *Value) + { + if (0 != ::strcasecmp(optionName, Name)) + return false; + + if (0 == ::strcasecmp("modeLiveTV", Value)) + { + optionValue = modeLiveTV; + return true; + } + + if (0 == ::strcasecmp("modeReplay", Value)) + { + optionValue = modeReplay; + return true; + } + + return false; + } + + bool cXineSettings::SetupParse(const char *optionName, eVolumeMode &optionValue, const char *Name, const char *Value) + { + if (0 != ::strcasecmp(optionName, Name)) + return false; + + if (0 == ::strcasecmp("volumeChangeHW", Value)) + { + optionValue = volumeChangeHW; + return true; + } + + if (0 == ::strcasecmp("volumeChangeSW", Value)) + { + optionValue = volumeChangeSW; + return true; + } + + if (0 == ::strcasecmp("volumeIgnore", Value)) + { + optionValue = volumeIgnore; + return true; + } + + // backward compatibilty + if (0 == ::strcasecmp("volumeChange", Value)) + { + optionValue = volumeChangeHW; + return true; + } + + if (0 == ::strcasecmp("volumeDontTouch", Value)) + { + optionValue = volumeIgnore; + return true; + } + + return false; + } + + bool cXineSettings::SetupParse(const char *optionName, eMuteMode &optionValue, const char *Name, const char *Value) + { + if (0 != ::strcasecmp(optionName, Name)) + return false; + + if (0 == ::strcasecmp("muteExecute", Value)) + { + optionValue = muteExecute; + return true; + } + + if (0 == ::strcasecmp("muteSimulate", Value)) + { + optionValue = muteSimulate; + return true; + } + + if (0 == ::strcasecmp("muteIgnore", Value)) + { + optionValue = muteIgnore; + return true; + } + + return false; + } + + bool cXineSettings::SetupParse(const char *optionName, eOsdMode &optionValue, const char *Name, const char *Value) + { + if (0 != ::strcasecmp(optionName, Name)) + return false; + + if (0 == ::strcasecmp("osdOverlay", Value)) + { + optionValue = osdOverlay; + return true; + } + + if (0 == ::strcasecmp("osdBlendClipped", Value)) + { + optionValue = osdBlendClipped; + return true; + } + + if (0 == ::strcasecmp("osdBlendScaledLQ", Value)) + { + optionValue = osdBlendScaledLQ; + return true; + } + + if (0 == ::strcasecmp("osdBlendScaledHQ", Value)) + { + optionValue = osdBlendScaledHQ; + return true; + } + + if (0 == ::strcasecmp("osdBlendScaledSHQ", Value)) + { + optionValue = osdBlendScaledSHQ; + return true; + } + + if (0 == ::strcasecmp("osdBlendScaledAuto", Value)) + { + optionValue = osdBlendScaledAuto; + return true; + } + + // backward compatibilty + if (0 == ::strcasecmp("osdScaled", Value)) + { + optionValue = osdBlendScaledAuto; + return true; + } + + if (0 == ::strcasecmp("osdUnscaled", Value)) + { + optionValue = osdOverlay; + return true; + } + + return false; + } + + bool cXineSettings::SetupParse(const char *optionName, eMonitorGamma &optionValue, const char *Name, const char *Value) + { + if (0 != ::strcasecmp(optionName, Name)) + return false; + + char c; + int v = 0; + if (1 != ::sscanf(Value, "%d%c", &v, &c)) + return false; + + if (monitorGammaMin <= v && v <= monitorGammaMax) + { + optionValue = (eMonitorGamma)v; + return true; + } + + return false; + } + + bool cXineSettings::SetupParse(const char *optionName, eAudioMode &optionValue, const char *Name, const char *Value) + { + if (0 != ::strcasecmp(optionName, Name)) + return false; + + if (0 == ::strcasecmp("audioDolbyOff", Value)) + { + optionValue = audioDolbyOff; + return true; + } + + if (0 == ::strcasecmp("audioDolbyOn", Value)) + { + optionValue = audioDolbyOn; + return true; + } + + return false; + } + + bool cXineSettings::SetupParse(const char *optionName, eAutoPrimaryDeviceMode &optionValue, const char *Name, const char *Value) + { + if (0 != ::strcasecmp(optionName, Name)) + return false; + + if (0 == ::strcasecmp("autoPrimaryDeviceOff", Value)) + { + optionValue = autoPrimaryDeviceOff; + return true; + } + + if (0 == ::strcasecmp("autoPrimaryDeviceOn", Value)) + { + optionValue = autoPrimaryDeviceOn; + return true; + } + + return false; + } + + bool cXineSettings::SetupParse(const char *optionName, eInteractWithEitScannerMode &optionValue, const char *Name, const char *Value) + { + if (0 != ::strcasecmp(optionName, Name)) + return false; + + if (0 == ::strcasecmp("interactWithEitScannerOff", Value)) + { + optionValue = interactWithEitScannerOff; + return true; + } + + if (0 == ::strcasecmp("interactWithEitScannerOn", Value)) + { + optionValue = interactWithEitScannerOn; + return true; + } + + return false; + } + + bool cXineSettings::SetupParse(const char *optionName, eTransparencyMode &optionValue, const char *Name, const char *Value) + { + if (0 != ::strcasecmp(optionName, Name)) + return false; + + if (0 == ::strcasecmp("transparencyOff", Value)) + { + optionValue = transparencyOff; + return true; + } + + if (0 == ::strcasecmp("transparencyOn", Value)) + { + optionValue = transparencyOn; + return true; + } + + return false; + } + + bool cXineSettings::SetupParse(const char *Name, const char *Value) + { + if (m_modeParams[ modeLiveTV ].SetupParse("modeLiveTV.", Name, Value)) + return true; +/* + if (m_modeParams[ modeReplay ].SetupParse("modeReplay.", Name, Value)) + return true; + + if (SetupParse("usageModeDefault", m_usageModeDefault, Name, Value)) + { + m_usageMode = m_usageModeDefault; + return true; + } +*/ + if (m_zoomParams[ image4_3 ].SetupParse("image4:3.", Name, Value)) + return true; + + if (m_zoomParams[ image16_9 ].SetupParse("image16:9.", Name, Value)) + return true; + + if (SetupParse("osdMode", m_osdMode, Name, Value)) + return true; + + if (SetupParse("osdGammaCorrection", m_crtGamma, Name, Value)) + return true; + +#if APIVERSNUM >= 10707 + if (m_osdExtentParams.SetupParse("osdExtent.", Name, Value)) + return true; +#endif + +#if APIVERSNUM < 10318 + if (SetupParse("audioMode", m_audioMode, Name, Value)) + return true; +#endif + + if (SetupParse("volumeMode", m_volumeMode, Name, Value)) + return true; + + if (SetupParse("muteMode", m_muteMode, Name, Value)) + return true; + +#if APIVERSNUM >= 10332 + if (SetupParse("autoPrimaryDeviceMode", m_autoPrimaryDeviceMode, Name, Value)) + return true; +#endif + + if (SetupParse("transparencyMode", m_transparencyMode, Name, Value)) + return true; + + if (SetupParse("interactWithEitScannerMode", m_interactWithEitScannerMode, Name, Value)) + return true; + + return false; + } + + const cXineSettings::cModeParams *cXineSettings::GetModeParams() const + { + return &m_modeParams[ m_usageMode ]; + } + + cXineSettings::eOsdMode cXineSettings::OsdMode() const + { + return m_osdMode; + } + + cXineSettings::eVolumeMode cXineSettings::VolumeMode() const + { + return m_volumeMode; + } + + cXineSettings::eMuteMode cXineSettings::MuteMode() const + { + return m_muteMode; + } + + bool cXineSettings::AudioDolbyOn() const + { +#if APIVERSNUM >= 10318 + return true; +#else + return audioDolbyOn == m_audioMode; +#endif + } + + void cXineSettings::Create(cXineSetupPage *const setupPage) + { + static const char *osdModes[ 6 ]; + osdModes[ osdOverlay ] = tr("X11 overlay"); + osdModes[ osdBlendClipped ] = tr("Blend clipped"); + osdModes[ osdBlendScaledLQ ] = tr("Blend scaled LQ"); + osdModes[ osdBlendScaledHQ ] = tr("Blend scaled HQ"); + osdModes[ osdBlendScaledSHQ ] = tr("Blend scaled SHQ"); + osdModes[ osdBlendScaledAuto ] = tr("Blend scaled Auto"); + + static const char *volumeModes[ 3 ]; + volumeModes[ volumeIgnore ] = tr("No"); + volumeModes[ volumeChangeHW ] = tr("Yes (by hardware)"); + volumeModes[ volumeChangeSW ] = tr("Yes (by software)"); + + static const char *muteModes[ 3 ]; + muteModes[ muteIgnore ] = tr("Ignore"); + muteModes[ muteExecute ] = tr("Execute"); + muteModes[ muteSimulate ] = tr("Simulate"); + + setupPage->Add(new cMenuEditIntItem(tr("Live-TV SD video buffer [frames]"), &m_modeParams[ modeLiveTV ].m_prebufferFramesVideoSD, 0, 50)); + setupPage->Add(new cMenuEditIntItem(tr("Live-TV HD video buffer [frames]"), &m_modeParams[ modeLiveTV ].m_prebufferFramesVideoHD, 0, 50)); + setupPage->Add(new cMenuEditIntItem(tr("Live-TV audio buffer [frames]"), &m_modeParams[ modeLiveTV ].m_prebufferFramesAudio, 0, 50)); +// setupPage->Add(new cMenuEditIntItem(tr("Replay prebuffer [frames]"), &m_modeParams[ modeReplay ].m_prebufferFrames, 0, 50)); +// setupPage->Add(new cMenuEditBoolItem(tr("Default settings optimized for"), (int *)&m_usageModeDefault, tr("Live-TV"), tr("Replay"))); + setupPage->Add(new cMenuEditIntItem(tr("Buffer hysteresis [frames]"), &m_modeParams[ modeLiveTV ].m_prebufferHysteresis, 0, 50)); + setupPage->Add(new cMenuEditIntItem(tr("Buffer monitoring duration [s]"), &m_modeParams[ modeLiveTV ].m_monitoringDuration, 0, 300)); + setupPage->Add(new cMenuEditBoolItem(tr("Buffer monitoring mode"), &alias_cast<int>(m_modeParams[ modeLiveTV ].m_monitoringMode), tr("Once"), tr("Continuous"))); + setupPage->Add(new cMenuEditStraItem(tr("OSD display mode"), &alias_cast<int>(m_osdMode), sizeof (osdModes) / sizeof (*osdModes), osdModes)); + setupPage->Add(new cMenuEditIntItem(tr("OSD gamma correction [ 123 => 1.23 ]"), &alias_cast<int>(m_crtGamma), monitorGammaMin, monitorGammaMax)); + +#if APIVERSNUM >= 10707 + setupPage->Add(new cMenuEditIntItem(tr("OSD extent X"), &alias_cast<int>(m_osdExtentParams.m_osdExtentWidth), osdExtentWidthMin, osdExtentWidthMax)); + setupPage->Add(new cMenuEditIntItem(tr("OSD extent Y"), &alias_cast<int>(m_osdExtentParams.m_osdExtentHeight), osdExtentHeightMin, osdExtentHeightMax)); +#endif + + setupPage->Add(new cMenuEditIntItem(tr("4:3 image zoom X [%]"), &alias_cast<int>(m_zoomParams[ image4_3 ].m_zoomX), imageZoomMin, imageZoomMax)); + setupPage->Add(new cMenuEditIntItem(tr("4:3 image zoom Y [%]"), &alias_cast<int>(m_zoomParams[ image4_3 ].m_zoomY), imageZoomMin, imageZoomMax)); + setupPage->Add(new cMenuEditIntItem(tr("16:9 image zoom X [%]"), &alias_cast<int>(m_zoomParams[ image16_9 ].m_zoomX), imageZoomMin, imageZoomMax)); + setupPage->Add(new cMenuEditIntItem(tr("16:9 image zoom Y [%]"), &alias_cast<int>(m_zoomParams[ image16_9 ].m_zoomY), imageZoomMin, imageZoomMax)); +#if APIVERSNUM < 10318 + setupPage->Add(new cMenuEditBoolItem(tr("Audio mode"), &alias_cast<int>(m_audioMode), tr("Dolby off"), tr("Dolby on"))); +#endif + setupPage->Add(new cMenuEditStraItem(tr("Control xine's volume"), &alias_cast<int>(m_volumeMode), sizeof (volumeModes) / sizeof (*volumeModes), volumeModes)); + setupPage->Add(new cMenuEditStraItem(tr("Muting"), &alias_cast<int>(m_muteMode), sizeof (muteModes) / sizeof (*muteModes), muteModes)); + setupPage->Add(new cMenuEditBoolItem(tr("Get primary device when xine connects"), &alias_cast<int>(m_autoPrimaryDeviceMode), tr("No"), tr("Yes"))); + setupPage->Add(new cMenuEditBoolItem(tr("Support semi transparent colors"), &alias_cast<int>(m_transparencyMode), tr("No"), tr("Yes"))); + setupPage->Add(new cMenuEditBoolItem(tr("Connection interacts with EIT scanner"), &alias_cast<int>(m_interactWithEitScannerMode), tr("No"), tr("Yes"))); + } + + void cXineSettings::Store(cXineSetupPage *const setupPage) + { + setupPage->SetupStore("modeLiveTV.prebufferFramesVideoSD", m_modeParams[ modeLiveTV ].m_prebufferFramesVideoSD); + setupPage->SetupStore("modeLiveTV.prebufferFramesVideoHD", m_modeParams[ modeLiveTV ].m_prebufferFramesVideoHD); + setupPage->SetupStore("modeLiveTV.prebufferFramesAudio", m_modeParams[ modeLiveTV ].m_prebufferFramesAudio); +// setupPage->SetupStore("modeReplay.prebufferFrames", m_modeParams[ modeReplay ].m_prebufferFrames); +// setupPage->SetupStore("usageModeDefault", (m_usageModeDefault ? "modeReplay" : "modeLiveTV")); + setupPage->SetupStore("modeLiveTV.prebufferHysteresis", m_modeParams[ modeLiveTV ].m_prebufferHysteresis); + setupPage->SetupStore("modeLiveTV.monitoringDuration", m_modeParams[ modeLiveTV ].m_monitoringDuration); + setupPage->SetupStore("modeLiveTV.monitoringMode", (m_modeParams[ modeLiveTV ].m_monitoringMode ? "monitoringContinuous" : "monitoringOnce")); + + setupPage->SetupStore("image4:3.zoomX", m_zoomParams[ image4_3 ].m_zoomX); + setupPage->SetupStore("image4:3.zoomY", m_zoomParams[ image4_3 ].m_zoomY); + setupPage->SetupStore("image16:9.zoomX", m_zoomParams[ image16_9 ].m_zoomX); + setupPage->SetupStore("image16:9.zoomY", m_zoomParams[ image16_9 ].m_zoomY); + + { + const char *mode = 0; + + switch (m_osdMode) + { + case osdOverlay: mode = "osdOverlay"; break; + case osdBlendClipped: mode = "osdBlendClipped"; break; + case osdBlendScaledLQ: mode = "osdBlendScaledLQ"; break; + case osdBlendScaledHQ: mode = "osdBlendScaledHQ"; break; + case osdBlendScaledSHQ: mode = "osdBlendScaledSHQ"; break; + case osdBlendScaledAuto: mode = "osdBlendScaledAuto"; break; + default: + assert(false); + } + + setupPage->SetupStore("osdMode", mode); + } + + setupPage->SetupStore("osdGammaCorrection", (int)m_crtGamma); + +#if APIVERSNUM >= 10707 + setupPage->SetupStore("osdExtent.X", m_osdExtentParams.m_osdExtentWidth); + setupPage->SetupStore("osdExtent.Y", m_osdExtentParams.m_osdExtentHeight); +#endif + +#if APIVERSNUM < 10318 + setupPage->SetupStore("audioMode", (m_audioMode ? "audioDolbyOn" : "audioDolbyOff")); +#endif + + { + const char *mode = 0; + + switch (m_volumeMode) + { + case volumeIgnore: mode = "volumeIgnore"; break; + case volumeChangeHW: mode = "volumeChangeHW"; break; + case volumeChangeSW: mode = "volumeChangeSW"; break; + } + + setupPage->SetupStore("volumeMode", mode); + } + + { + const char *mode = 0; + + switch (m_muteMode) + { + case muteIgnore: mode = "muteIgnore"; break; + case muteExecute: mode = "muteExecute"; break; + case muteSimulate: mode = "muteSimulate"; break; + default: + assert(false); + } + + setupPage->SetupStore("muteMode", mode); + } + + setupPage->SetupStore("autoPrimaryDeviceMode", (m_autoPrimaryDeviceMode ? "autoPrimaryDeviceOn" : "autoPrimaryDeviceOff")); + setupPage->SetupStore("transparencyMode", (m_transparencyMode ? "transparencyOn" : "transparencyOff")); + setupPage->SetupStore("interactWithEitScannerMode", (m_interactWithEitScannerMode ? "interactWithEitScannerOn" : "interactWithEitScannerOff")); + } + + void cXineSettings::SelectReplayPrebufferMode(const bool select /* = true */) + { + m_usageMode = (!select ? modeLiveTV : modeReplay); +/* + if (Interface) + Interface->Info(m_usageMode ? tr("Settings are now optimized for 'Replay'") : tr("Settings are now optimized for 'Live-TV'")); +*/ +// cXineDevice::GetDevice()->AdjustPrebufferMode(); + } + + void cXineSettings::TogglePrebufferMode() + { + m_usageMode = (m_usageMode ? modeLiveTV : modeReplay); +/* + if (Interface) + Interface->Info(m_usageMode ? tr("Settings are now optimized for 'Replay'") : tr("Settings are now optimized for 'Live-TV'")); +*/ +// cXineDevice::GetDevice()->AdjustPrebufferMode(); + } + + const char *cXineSettings::GetMainMenuEntry() + { + return 0; +// return (m_usageMode ? tr("xine - Choose optimized settings for 'Live-TV'") : tr("xine - Choose optimized settings for 'Replay'")); + } + + bool cXineSettings::LiveTV() const + { + return (m_usageMode == modeLiveTV); + } + + bool cXineSettings::AutoPrimaryDevice() const + { + return (m_autoPrimaryDeviceMode == autoPrimaryDeviceOn); + } + + bool cXineSettings::InteractWithEitScanner() const + { + return (m_interactWithEitScannerMode == interactWithEitScannerOn); + } + + bool cXineSettings::SupportTransparency() const + { + return (m_transparencyMode == transparencyOn); + } + + void cXineSettings::SetSwitchSkin(const bool switchSkin) + { + m_switchSkin = switchSkin; + } + + bool cXineSettings::ShallSwitchSkin() const + { + return m_switchSkin; + } + + bool beQuiet = false; + + void cXineSettings::SetBeQuiet(const bool beQuiet) + { + m_beQuiet = beQuiet; + + PluginXine::beQuiet = m_beQuiet; + } + + bool cXineSettings::ShallBeQuiet() const + { + return m_beQuiet; + } + + void cXineSettings::SetDefaultGrabSizeX(const int defaultGrabSizeX) + { + m_defaultGrabSizeX = defaultGrabSizeX; + } + + int cXineSettings::DefaultGrabSizeX() const + { + return m_defaultGrabSizeX; + } + + void cXineSettings::SetDefaultGrabSizeY(const int defaultGrabSizeY) + { + m_defaultGrabSizeY = defaultGrabSizeY; + } + + int cXineSettings::DefaultGrabSizeY() const + { + return m_defaultGrabSizeY; + } + +}; diff --git a/xineSettings.h b/xineSettings.h new file mode 100644 index 0000000..4942fa0 --- /dev/null +++ b/xineSettings.h @@ -0,0 +1,294 @@ + +#ifndef __XINESETTINGS_H +#define __XINESETTINGS_H + + + +#include "xineCommon.h" + + + +namespace PluginXine +{ + + class cXineSetupPage; + + class cXineSettings + { + public: + enum eUsageMode + { + modeLiveTV + , modeReplay + }; + + enum eOsdMode + { + osdOverlay + , osdBlendClipped + , osdBlendScaledLQ + , osdBlendScaledHQ + , osdBlendScaledSHQ + , osdBlendScaledAuto + }; + + enum eAudioMode + { + audioDolbyOff + , audioDolbyOn + }; + + enum eVolumeMode + { + volumeIgnore + , volumeChangeHW + , volumeChangeSW + }; + + enum eMuteMode + { + muteIgnore + , muteExecute + , muteSimulate + }; + + enum eOsdExtentWidth + { + osdExtentWidthMin = 320 + , osdExtentWidthDefault = 720 + , osdExtentWidthMax = 1920 + }; + + enum eOsdExtentHeight + { + osdExtentHeightMin = 240 + , osdExtentHeightDefault = 576 + , osdExtentHeightMax = 1080 + }; + + enum eMonitorGamma + { + monitorGammaBase = 100 + , monitorGammaMin = 100 + , monitorGammaDefault = 123 + , monitorGammaMax = 250 + }; + + enum eImage + { + image4_3 = 0 + , image16_9 = 1 + }; + + enum eImageZoom + { + imageZoomBase = 100 + , imageZoomMin = 25 + , imageZoomDefault = 100 + , imageZoomMax = 400 + }; + + enum eAutoPrimaryDeviceMode + { + autoPrimaryDeviceOff + , autoPrimaryDeviceOn + }; + + enum eTransparencyMode + { + transparencyOff + , transparencyOn + }; + + enum eMonitoringMode + { + monitoringOnce + , monitoringContinuous + }; + + enum eInteractWithEitScannerMode + { + interactWithEitScannerOff + , interactWithEitScannerOn + }; + + private: + bool m_switchSkin; + bool m_beQuiet; + int m_defaultGrabSizeX; + int m_defaultGrabSizeY; + + eOsdMode m_osdMode; + eUsageMode m_usageMode /* , m_usageModeDefault */; + eAudioMode m_audioMode; + eVolumeMode m_volumeMode; + eMuteMode m_muteMode; + eMonitorGamma m_crtGamma; + eAutoPrimaryDeviceMode m_autoPrimaryDeviceMode; + eTransparencyMode m_transparencyMode; + eInteractWithEitScannerMode m_interactWithEitScannerMode; + + template <class T> + static T clip(T a, T x, T b) + { + if (x < a) + return a; + + if (x > b) + return b; + + return x; + } + + class cModeParams + { + bool SetupParse(const char *optionName, int &optionValue, const char *Name, const char *Value); + bool SetupParse(const char *optionName, eMonitoringMode &optionValue, const char *Name, const char *Value); + + public: + cModeParams(); + + int m_prebufferFramesVideoSD; + int m_prebufferFramesVideoHD; + int m_prebufferFramesAudio; + int m_prebufferHysteresis; + int m_monitoringDuration; + eMonitoringMode m_monitoringMode; + + bool SetupParse(const char *prefix, const char *Name, const char *Value); + bool MonitoringContinuous() const; + } + m_modeParams[ 2 ]; + + class cZoomParams + { + bool SetupParse(const char *optionName, eImageZoom &optionValue, const char *Name, const char *Value); + + public: + cZoomParams(); + + eImageZoom m_zoomX; + eImageZoom m_zoomY; + + eImageZoom GetZoomX() const + { + return clip(imageZoomMin, m_zoomX, imageZoomMax); + } + + eImageZoom GetZoomY() const + { + return clip(imageZoomMin, m_zoomY, imageZoomMax); + } + + bool SetupParse(const char *prefix, const char *Name, const char *Value); + + bool operator !=(const cZoomParams &rhs) const; + } + m_zoomParams[ 2 ]; + + class cOsdExtentParams + { + bool SetupParse(const char *optionName, eOsdExtentWidth &optionValue, const char *Name, const char *Value); + bool SetupParse(const char *optionName, eOsdExtentHeight &optionValue, const char *Name, const char *Value); + + public: + cOsdExtentParams(); + + eOsdExtentWidth m_osdExtentWidth; + eOsdExtentHeight m_osdExtentHeight; + + eOsdExtentWidth GetOsdExtentWidth() const + { + return clip(osdExtentWidthMin, m_osdExtentWidth, osdExtentWidthMax); + } + + eOsdExtentHeight GetOsdExtentHeight() const + { + return clip(osdExtentHeightMin, m_osdExtentHeight, osdExtentHeightMax); + } + + bool SetupParse(const char *prefix, const char *Name, const char *Value); + + bool operator !=(const cOsdExtentParams &rhs) const; + } + m_osdExtentParams; + + bool SetupParse(const char *optionName, eUsageMode &optionValue, const char *Name, const char *Value); + bool SetupParse(const char *optionName, eOsdMode &optionValue, const char *Name, const char *Value); + bool SetupParse(const char *optionName, eAudioMode &optionValue, const char *Name, const char *Value); + bool SetupParse(const char *optionName, eVolumeMode &optionValue, const char *Name, const char *Value); + bool SetupParse(const char *optionName, eMuteMode &optionValue, const char *Name, const char *Value); + bool SetupParse(const char *optionName, eMonitorGamma &optionValue, const char *Name, const char *Value); + bool SetupParse(const char *optionName, eAutoPrimaryDeviceMode &optionValue, const char *Name, const char *Value); + bool SetupParse(const char *optionName, eTransparencyMode &optionValue, const char *Name, const char *Value); + bool SetupParse(const char *optionName, eInteractWithEitScannerMode &optionValue, const char *Name, const char *Value); + + public: + cXineSettings(); + + bool SetupParse(const char *Name, const char *Value); + + const cModeParams *GetModeParams() const; + + eOsdMode OsdMode() const; + bool AudioDolbyOn() const; + eVolumeMode VolumeMode() const; + bool SupportTransparency() const; + eMuteMode MuteMode() const; + + void Create(cXineSetupPage *const setupPage); + void Store(cXineSetupPage *const setupPage); + + void SelectReplayPrebufferMode(const bool select = true); + + void TogglePrebufferMode(); + const char *GetMainMenuEntry(); + + bool LiveTV() const; + bool AutoPrimaryDevice() const; + bool InteractWithEitScanner() const; + + bool setupDiffers(const cXineSettings &rhs) const + { + return m_osdMode != rhs.m_osdMode + || m_transparencyMode != rhs.m_transparencyMode + || m_volumeMode != rhs.m_volumeMode + || m_muteMode != rhs.m_muteMode + || m_zoomParams[ image4_3 ] != rhs.m_zoomParams[ image4_3 ] + || m_zoomParams[ image16_9 ] != rhs.m_zoomParams[ image16_9 ] + || m_osdExtentParams != rhs.m_osdExtentParams; + } + + int GetCrtGamma() const + { + return clip(monitorGammaMin, m_crtGamma, monitorGammaMax); + } + + const cZoomParams &ZoomParams(eImage image) const + { + return m_zoomParams[ image ]; + } + + const cOsdExtentParams &OsdExtentParams() const + { + return m_osdExtentParams; + } + + void SetSwitchSkin(const bool switchSkin); + bool ShallSwitchSkin() const; + + void SetBeQuiet(const bool beQuiet); + bool ShallBeQuiet() const; + + void SetDefaultGrabSizeX(const int defaultGrabSizeX); + int DefaultGrabSizeX() const; + + void SetDefaultGrabSizeY(const int defaultGrabSizeY); + int DefaultGrabSizeY() const; + }; + +}; + + + +#endif //__XINESETTINGS_H diff --git a/xineSetupPage.c b/xineSetupPage.c new file mode 100644 index 0000000..b1f69d5 --- /dev/null +++ b/xineSetupPage.c @@ -0,0 +1,106 @@ + +#include "xineCommon.h" + +#include "xineSetupPage.h" +#include "xineSettings.h" +#include "xineLib.h" + + + +namespace PluginXine +{ + + void cXineSetupPage::Store() + { + m_globalSettings.Store(this); + + bool setupDiffers = m_globalSettings.setupDiffers(m_localSettings); +#if APIVERSNUM >= 10707 + bool lockOsdUpdate = m_globalSettings.OsdExtentParams() != m_localSettings.OsdExtentParams(); +#endif + + m_localSettings = m_globalSettings; + + if (!setupDiffers) + return; + + m_xineLib->execFuncSetup(); +#if APIVERSNUM >= 10707 + if (lockOsdUpdate) + { + m_xineLib->LockOsdUpdate(); + cOsdProvider::UpdateOsdSize(true); + SetDisplayMenu(); + } + + m_xineLib->ReshowCurrentOSD(lockOsdUpdate); +#else + m_xineLib->ReshowCurrentOSD(); +#endif + } + + cXineSetupPage::cXineSetupPage(cXineLib *const xineLib, cXineSettings &settings) + : cMenuSetupPage() + , m_xineLib(xineLib) + , m_globalSettings(settings) + , m_localSettings(settings) + { + m_globalSettings.Create(this); + } + + cXineSetupPage::~cXineSetupPage() + { + bool setupDiffers = m_globalSettings.setupDiffers(m_localSettings); +#if APIVERSNUM >= 10707 + bool lockOsdUpdate = m_globalSettings.OsdExtentParams() != m_localSettings.OsdExtentParams(); +#endif + + m_globalSettings = m_localSettings; + + if (!setupDiffers) + return; + + m_xineLib->execFuncSetup(); +#if APIVERSNUM >= 10707 + if (lockOsdUpdate) + { + m_xineLib->LockOsdUpdate(); + cOsdProvider::UpdateOsdSize(true); + SetDisplayMenu(); + } + + m_xineLib->ReshowCurrentOSD(lockOsdUpdate); +#else + m_xineLib->ReshowCurrentOSD(); +#endif + } + + eOSState cXineSetupPage::ProcessKey(eKeys Key) + { + const cXineSettings prevSettings = m_globalSettings; + + eOSState state = cMenuSetupPage::ProcessKey(Key); + + if (prevSettings.setupDiffers(m_globalSettings)) + { + m_xineLib->execFuncSetup(); +#if APIVERSNUM >= 10707 + bool lockOsdUpdate = prevSettings.OsdExtentParams() != m_globalSettings.OsdExtentParams(); + if (lockOsdUpdate) + { + m_xineLib->LockOsdUpdate(); + cOsdProvider::UpdateOsdSize(true); + SetDisplayMenu(); + Display(); + } + + m_xineLib->ReshowCurrentOSD(lockOsdUpdate); +#else + m_xineLib->ReshowCurrentOSD(); +#endif + } + + return state; + } + +}; diff --git a/xineSetupPage.h b/xineSetupPage.h new file mode 100644 index 0000000..87792f9 --- /dev/null +++ b/xineSetupPage.h @@ -0,0 +1,43 @@ + +#ifndef __XINESETUPPAGE_H +#define __XINESETUPPAGE_H + + + +#include "xineCommon.h" + +#include <vdr/menuitems.h> + +#include "xineSettings.h" + + + +namespace PluginXine +{ + + class cXineLib; + + class cXineSetupPage : public cMenuSetupPage + { + cXineLib *const m_xineLib; + + cXineSettings &m_globalSettings; + cXineSettings m_localSettings; + + protected: + virtual void Store(); + + public: + cXineSetupPage(cXineLib *const xineLib, cXineSettings &settings); + virtual ~cXineSetupPage(); + + virtual eOSState ProcessKey(eKeys Key); + + friend class cXineSettings; + }; + +}; + + + +#endif //__XINESETUPPAGE_H diff --git a/xineplayer.c b/xineplayer.c new file mode 100644 index 0000000..b3b07cb --- /dev/null +++ b/xineplayer.c @@ -0,0 +1,128 @@ + +#include "xineCommon.h" + +#include "xineExternal.h" + +static int fdControl = -1; +static int fdResult = -1; + +bool writeString(const char *s) +{ + int l = ::strlen(s); + + while (l) + { + int r = ::write(fdControl, s, l); + if (r < 0) + { + ::perror("xineplayer: writeString() failed"); + return false; + } + + l -= r; + s += r; + } + + return true; +} + +bool cmdPlay(char *const mrl) +{ + if (!::writeString("play ")) + return false; + + if (!::writeString(mrl)) + return false; + + if (!::writeString("\n")) + return false; + + return true; +} + +bool waitResult() +{ + char s; + + int r = ::read(fdResult, &s, 1); + if (r < 0) + { + ::perror("xineplayer: waitResult() failed"); + return false; + } + + return (1 == r); +} + +bool communicate(char *const mrl) +{ + if (!::cmdPlay(mrl)) + return false; + + if (!::waitResult()) + return false; + + return true; +} + +#define ARG_VDR_XINE_INSTANCE "--vdr-xine-instance=" + +int main(int argc, char *argv[]) +{ + if (argc < 2) + { + usage: + ::fprintf(stderr, "usage: xineplayer [ " ARG_VDR_XINE_INSTANCE "N ] [ options ] mrl\n"); + return 1; + } + + int instanceNo = -1; + + if (0 == ::strncmp(argv[ 1 ], ARG_VDR_XINE_INSTANCE, ::strlen(ARG_VDR_XINE_INSTANCE))) + { + instanceNo = ::atoi(argv[ 1 ] + ::strlen(ARG_VDR_XINE_INSTANCE)); + + if (instanceNo < 0) + goto usage; + + if (argc < 3) + goto usage; + } + + string fifoDir = FIFO_DIR; + + if (instanceNo >= 0) + { + char s[ 20 ]; + ::sprintf(s, "%d", instanceNo); + + fifoDir += s; + } + + string fifoNameExtControl = fifoDir + FIFO_NAME_EXT_CONTROL; + string fifoNameExtResult = fifoDir + FIFO_NAME_EXT_RESULT; + + fdResult = ::open(fifoNameExtResult.c_str(), O_RDONLY); + if (-1 == fdResult) + { + ::perror(("xineplayer: opening '" + fifoNameExtResult + "' failed").c_str()); + + ::close(fdControl); + return 1; + } + + fdControl = ::open(fifoNameExtControl.c_str(), O_WRONLY); + if (-1 == fdControl) + { + ::perror(("xineplayer: opening '" + fifoNameExtControl + "' failed").c_str()); + return 1; + } + + bool result = ::communicate(argv[ argc - 1 ]); + + ::close(fdControl); + ::close(fdResult); + + return !result; +} + |