diff options
| author | Andreas Auras <yak54@gmx.net> | 2010-02-08 16:53:24 +0100 |
|---|---|---|
| committer | Andreas Auras <yak54@gmx.net> | 2010-02-08 16:53:24 +0100 |
| commit | 82687e40db5d89aaff644fe7a34ae3c058b80255 (patch) | |
| tree | 0df3b9e872779a4e4f98d48e67ac72d34e658eb6 | |
| download | xine-lib-atmolight-82687e40db5d89aaff644fe7a34ae3c058b80255.tar.gz xine-lib-atmolight-82687e40db5d89aaff644fe7a34ae3c058b80255.tar.bz2 | |
Import of unmanaged version 0.3 of plugin
Signed-off-by: Andreas Auras <yak54@gmx.net>
| -rw-r--r-- | .cproject | 144 | ||||
| -rw-r--r-- | .project | 77 | ||||
| -rw-r--r-- | .settings/org.eclipse.cdt.core.prefs | 3 | ||||
| -rw-r--r-- | COPYING | 340 | ||||
| -rw-r--r-- | Makefile | 33 | ||||
| -rw-r--r-- | README | 177 | ||||
| -rw-r--r-- | debian/changelog | 25 | ||||
| -rw-r--r-- | debian/compat | 1 | ||||
| -rw-r--r-- | debian/control | 14 | ||||
| -rw-r--r-- | debian/copyright | 26 | ||||
| -rw-r--r-- | debian/docs | 1 | ||||
| -rw-r--r-- | debian/rules | 91 | ||||
| -rw-r--r-- | df10ch_usb_proto.h | 116 | ||||
| -rw-r--r-- | output_driver.h | 1025 | ||||
| -rw-r--r-- | xine_post_atmo.c | 1272 |
15 files changed, 3345 insertions, 0 deletions
diff --git a/.cproject b/.cproject new file mode 100644 index 0000000..3e3e00d --- /dev/null +++ b/.cproject @@ -0,0 +1,144 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<?fileVersion 4.0.0?> + +<cproject> +<storageModule moduleId="org.eclipse.cdt.core.settings"> +<cconfiguration id="cdt.managedbuild.toolchain.gnu.base.522923725"> +<storageModule buildSystemId="org.eclipse.cdt.managedbuilder.core.configurationDataProvider" id="cdt.managedbuild.toolchain.gnu.base.522923725" moduleId="org.eclipse.cdt.core.settings" name="Linux GCC"> +<externalSettings/> +<extensions> +<extension id="org.eclipse.cdt.core.ELF" point="org.eclipse.cdt.core.BinaryParser"/> +<extension id="org.eclipse.cdt.core.MakeErrorParser" point="org.eclipse.cdt.core.ErrorParser"/> +<extension id="org.eclipse.cdt.core.GCCErrorParser" point="org.eclipse.cdt.core.ErrorParser"/> +<extension id="org.eclipse.cdt.core.GASErrorParser" point="org.eclipse.cdt.core.ErrorParser"/> +<extension id="org.eclipse.cdt.core.GLDErrorParser" point="org.eclipse.cdt.core.ErrorParser"/> +</extensions> +</storageModule> +<storageModule moduleId="cdtBuildSystem" version="4.0.0"> +<configuration artifactName="atmo" buildProperties="" id="cdt.managedbuild.toolchain.gnu.base.522923725" name="Linux GCC" parent="org.eclipse.cdt.build.core.emptycfg"> +<folderInfo id="cdt.managedbuild.toolchain.gnu.base.522923725.1299807297" name="/" resourcePath=""> +<toolChain id="cdt.managedbuild.toolchain.gnu.base.1765852131" name="cdt.managedbuild.toolchain.gnu.base" superClass="cdt.managedbuild.toolchain.gnu.base"> +<targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="cdt.managedbuild.target.gnu.platform.base.1949347644" name="Debug Platform" osList="linux,hpux,aix,qnx" superClass="cdt.managedbuild.target.gnu.platform.base"/> +<builder id="cdt.managedbuild.target.gnu.builder.base.1108692234" managedBuildOn="false" name="Gnu Make Builder.Linux GCC" superClass="cdt.managedbuild.target.gnu.builder.base"/> +<tool id="cdt.managedbuild.tool.gnu.archiver.base.1982832721" name="GCC Archiver" superClass="cdt.managedbuild.tool.gnu.archiver.base"/> +<tool id="cdt.managedbuild.tool.gnu.cpp.compiler.base.2064905689" name="GCC C++ Compiler" superClass="cdt.managedbuild.tool.gnu.cpp.compiler.base"/> +<tool id="cdt.managedbuild.tool.gnu.c.compiler.base.1226506188" name="GCC C Compiler" superClass="cdt.managedbuild.tool.gnu.c.compiler.base"> +<inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.758137729" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/> +</tool> +<tool id="cdt.managedbuild.tool.gnu.c.linker.base.314775202" name="GCC C Linker" superClass="cdt.managedbuild.tool.gnu.c.linker.base"> +<inputType id="cdt.managedbuild.tool.gnu.c.linker.input.2084718394" superClass="cdt.managedbuild.tool.gnu.c.linker.input"> +<additionalInput kind="additionalinputdependency" paths="$(USER_OBJS)"/> +<additionalInput kind="additionalinput" paths="$(LIBS)"/> +</inputType> +</tool> +<tool id="cdt.managedbuild.tool.gnu.cpp.linker.base.1209638415" name="GCC C++ Linker" superClass="cdt.managedbuild.tool.gnu.cpp.linker.base"/> +<tool id="cdt.managedbuild.tool.gnu.assembler.base.891915511" name="GCC Assembler" superClass="cdt.managedbuild.tool.gnu.assembler.base"> +<inputType id="cdt.managedbuild.tool.gnu.assembler.input.1190003687" superClass="cdt.managedbuild.tool.gnu.assembler.input"/> +</tool> +</toolChain> +</folderInfo> +</configuration> +</storageModule> +<storageModule moduleId="scannerConfiguration"> +<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId="org.eclipse.cdt.make.core.GCCStandardMakePerProjectProfile"/> +<profile id="org.eclipse.cdt.make.core.GCCStandardMakePerProjectProfile"> +<buildOutputProvider> +<openAction enabled="true" filePath=""/> +<parser enabled="true"/> +</buildOutputProvider> +<scannerInfoProvider id="specsFile"> +<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/> +<parser enabled="true"/> +</scannerInfoProvider> +</profile> +<profile id="org.eclipse.cdt.make.core.GCCStandardMakePerFileProfile"> +<buildOutputProvider> +<openAction enabled="true" filePath=""/> +<parser enabled="true"/> +</buildOutputProvider> +<scannerInfoProvider id="makefileGenerator"> +<runAction arguments="-f ${project_name}_scd.mk" command="make" useDefault="true"/> +<parser enabled="true"/> +</scannerInfoProvider> +</profile> +<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfile"> +<buildOutputProvider> +<openAction enabled="true" filePath=""/> +<parser enabled="true"/> +</buildOutputProvider> +<scannerInfoProvider id="specsFile"> +<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/> +<parser enabled="true"/> +</scannerInfoProvider> +</profile> +<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileCPP"> +<buildOutputProvider> +<openAction enabled="true" filePath=""/> +<parser enabled="true"/> +</buildOutputProvider> +<scannerInfoProvider id="specsFile"> +<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.cpp" command="g++" useDefault="true"/> +<parser enabled="true"/> +</scannerInfoProvider> +</profile> +<profile id="org.eclipse.cdt.managedbuilder.core.GCCManagedMakePerProjectProfileC"> +<buildOutputProvider> +<openAction enabled="true" filePath=""/> +<parser enabled="true"/> +</buildOutputProvider> +<scannerInfoProvider id="specsFile"> +<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.c" command="gcc" useDefault="true"/> +<parser enabled="true"/> +</scannerInfoProvider> +</profile> +<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfile"> +<buildOutputProvider> +<openAction enabled="true" filePath=""/> +<parser enabled="true"/> +</buildOutputProvider> +<scannerInfoProvider id="specsFile"> +<runAction arguments="-E -P -v -dD ${plugin_state_location}/${specs_file}" command="gcc" useDefault="true"/> +<parser enabled="true"/> +</scannerInfoProvider> +</profile> +<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfileCPP"> +<buildOutputProvider> +<openAction enabled="true" filePath=""/> +<parser enabled="true"/> +</buildOutputProvider> +<scannerInfoProvider id="specsFile"> +<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.cpp" command="g++" useDefault="true"/> +<parser enabled="true"/> +</scannerInfoProvider> +</profile> +<profile id="org.eclipse.cdt.managedbuilder.core.GCCWinManagedMakePerProjectProfileC"> +<buildOutputProvider> +<openAction enabled="true" filePath=""/> +<parser enabled="true"/> +</buildOutputProvider> +<scannerInfoProvider id="specsFile"> +<runAction arguments="-E -P -v -dD ${plugin_state_location}/specs.c" command="gcc" useDefault="true"/> +<parser enabled="true"/> +</scannerInfoProvider> +</profile> +</storageModule> +<storageModule moduleId="org.eclipse.cdt.core.externalSettings"/> +<storageModule moduleId="org.eclipse.cdt.internal.ui.text.commentOwnerProjectMappings"/> +<storageModule moduleId="org.eclipse.cdt.make.core.buildtargets"> +<buildTargets> +<target name="install" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder"> +<buildCommand>make</buildCommand> +<buildArguments/> +<buildTarget>install</buildTarget> +<stopOnError>true</stopOnError> +<useDefaultCommand>true</useDefaultCommand> +<runAllBuilders>false</runAllBuilders> +</target> +</buildTargets> +</storageModule> +</cconfiguration> +</storageModule> +<storageModule moduleId="cdtBuildSystem" version="4.0.0"> +<project id="atmo.null.2145325042" name="atmo"/> +</storageModule> +</cproject> diff --git a/.project b/.project new file mode 100644 index 0000000..54774f4 --- /dev/null +++ b/.project @@ -0,0 +1,77 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>xine-lib-atmolight</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.cdt.managedbuilder.core.genmakebuilder</name> + <triggers>clean,full,incremental,</triggers> + <arguments> + <dictionary> + <key>?name?</key> + <value></value> + </dictionary> + <dictionary> + <key>org.eclipse.cdt.make.core.append_environment</key> + <value>true</value> + </dictionary> + <dictionary> + <key>org.eclipse.cdt.make.core.autoBuildTarget</key> + <value>all</value> + </dictionary> + <dictionary> + <key>org.eclipse.cdt.make.core.buildArguments</key> + <value></value> + </dictionary> + <dictionary> + <key>org.eclipse.cdt.make.core.buildCommand</key> + <value>make</value> + </dictionary> + <dictionary> + <key>org.eclipse.cdt.make.core.cleanBuildTarget</key> + <value>clean</value> + </dictionary> + <dictionary> + <key>org.eclipse.cdt.make.core.contents</key> + <value>org.eclipse.cdt.make.core.activeConfigSettings</value> + </dictionary> + <dictionary> + <key>org.eclipse.cdt.make.core.enableAutoBuild</key> + <value>false</value> + </dictionary> + <dictionary> + <key>org.eclipse.cdt.make.core.enableCleanBuild</key> + <value>true</value> + </dictionary> + <dictionary> + <key>org.eclipse.cdt.make.core.enableFullBuild</key> + <value>true</value> + </dictionary> + <dictionary> + <key>org.eclipse.cdt.make.core.fullBuildTarget</key> + <value>all</value> + </dictionary> + <dictionary> + <key>org.eclipse.cdt.make.core.stopOnError</key> + <value>true</value> + </dictionary> + <dictionary> + <key>org.eclipse.cdt.make.core.useDefaultBuildCmd</key> + <value>true</value> + </dictionary> + </arguments> + </buildCommand> + <buildCommand> + <name>org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.cdt.managedbuilder.core.ScannerConfigNature</nature> + <nature>org.eclipse.cdt.managedbuilder.core.managedBuildNature</nature> + <nature>org.eclipse.cdt.core.cnature</nature> + </natures> +</projectDescription> diff --git a/.settings/org.eclipse.cdt.core.prefs b/.settings/org.eclipse.cdt.core.prefs new file mode 100644 index 0000000..9d7778c --- /dev/null +++ b/.settings/org.eclipse.cdt.core.prefs @@ -0,0 +1,3 @@ +#Tue Jul 07 22:40:27 CEST 2009 +eclipse.preferences.version=1 +environment/project/cdt.managedbuild.toolchain.gnu.base.522923725=<?xml version\="1.0" encoding\="UTF-8" standalone\="no"?>\n<environment append\="true" appendContributed\="true">\n<variable delimiter\=";" name\="PKG_CONFIG_PATH" operation\="append" value\="/home/andy/build/lib/pkgconfig"/>\n</environment>\n @@ -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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..44db1ba --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +# +# Makefile for xine atmo post plugin +# +INSTALL ?= install +XINEPLUGINDIR ?= $(shell pkg-config --variable=plugindir libxine) +CFLAGS_XINE ?= $(shell pkg-config --cflags libxine ) +LIBS_XINE ?= $(shell pkg-config --libs libxine) +CFLAGS_USB ?= $(shell pkg-config --cflags libusb-1.0) +LIBS_USB ?= $(shell pkg-config --libs libusb-1.0) +LDFLAGS_SO ?= -shared -fvisibility=hidden +CFLAGS ?= -O3 -pipe -Wall -fPIC -g + +XINEPOSTATMO = xineplug_post_atmo.so + +.PHONY: all install clean + +all: $(XINEPOSTATMO) + +install: all + @echo Installing $(XINEPLUGINDIR)/post/$(XINEPOSTATMO) + @-rm -rf $(XINEPLUGINDIR)/post/$(XINEPOSTATMO) + @$(INSTALL) -m 0644 $(XINEPOSTATMO) $(XINEPLUGINDIR)/post/$(XINEPOSTATMO) + +clean: + @-rm -f *.so* *.o + +xine_post_atmo.o: xine_post_atmo.c output_driver.h + $(CC) $(CFLAGS) $(CFLAGS_XINE) $(CFLAGS_USB) -c -o $@ $< + +$(XINEPOSTATMO): xine_post_atmo.o + $(CC) $(CFLAGS) $(LDFLAGS_SO) $(LIBS_XINE) $(LIBS_USB) -lm -o $@ $< + + @@ -0,0 +1,177 @@ +This is a "post plugin" for xine based media players. + +Written by: Andreas Auras (yak54@gmx.net) + +See the file COPYING for license information. + +Many ideas and algorithm for this plugin had been derived from the fantastic +Atmolight-plugin for the Video Disk Recorder (VDR) that was developed by +Eike Edener. + + +Description: +------------ +The "atmo" post plugin analyze the video picture and generates +output data for so called "atmolight" controllers. + +For detailed informations about "atmolight" visit the VDR-Wiki: +http://www.vdr-wiki.de/wiki/index.php/Atmo-plugin + +!!!NOTE!!!! +Currently this plugin supports only grabbing of video from VDPAU +enabled devices! + + +Requirements: +------------- + +xine-lib-vdpau or xine-lib-1.2 with vdpau support +Both version must have the vdpau extension patches from here: +http://www.vdr-portal.de/board/thread.php?threadid=86804 + +For compiling the plugin the xine-lib and libusb-1.0 development +files have to be installed. On debian based systems these are +packages libxine-dev and libusb-1.0-0-dev. + + +Compiling and installation: +--------------------------- + +make +make install + + +Configuration: +-------------- + +Load this plugin as a post plugin in your xine based media player. +For the VDR xineliboutput player or xine-ui player add a command line option like this: + +--post=atmo:driver=classic,driver_param=/dev/ttyS0,top=1,bottom=1,left=1,right=1,center=1 + +When using DF10CH controller(s): + +--post atmo:driver=df10ch + + +Parameters understood by this plugin: +------------------------------------- + +Parameter Default Description + +driver none Selects output driver. Currently supported are: + + file Write output data to file. Usefull for + testing and debuging. + + classic Send output data to serial port. Used + Protokoll is for "classic" Atmolight + 2 channel controller. See: + http://www.vdr-wiki.de/wiki/index.php/Atmo-plugin + + df4ch Send output data to serial port. Used + Protokoll is for my own designed 4 channel + controller. + + df10ch Send output data via libusb to my own designed + DF10CH 10ch Controller(s). + +driver_param Parameter for output driver: + + file: File name of output file. If not specified + "xine_atmo_data.out" is used. + + classic, df4ch: + Path of serial device e.g. /dev/ttyS0 + For users with usb-to-serial converters the + device could be specified as a regular expression + that is looked up in /proc/tty/driver/usbserial to + evaluate the /dev/ttyUSB device. The parameter must + start with "usb:" followed by the regular expression + e.g.: usb:pl2303.*usb-.*-[^6] + + df10ch: + All connected controllers are scanned automatically. No + parameter required here. + +top +bottom +left +right +center +top_left +top_right +bottom_left +bottom_right 0 Number of RGB channel groups per Area. + For top, bottom, left and right area more then one group could be + specified. This will be usefull when "next generation" controllers + like DF10CH are available. See the "aurora" project at: + http://www.vdr-portal.de/board/thread.php?threadid=71741&hilight=aurora + Valid values: 0 ... 25 for top, bottom, left, right + Valid values: 0 ... 1 for center, top_left, top_right, bottom_left, bottom_right + + NOTE!!!: Starting from plugin version 0.3 for "classic" controllers you must define one + group for area top, bottom, left, right and center as plugin parameter!!! + + For DF10CH controller you do not have to specify these parameters here because they are read + from the controller configuration data. Use the DF10CH setup program to configure your + desired layout. + + +analyze_rate 40 Rate of frame grabbing and video analysis. Unit milliseconds. + Valid values: 10 ... 500 + +analyze_size 1 Size of analyze window. The window width is calculated by (analyze_size+1)*64 pixel. + The window height is calcuated aspect correct according to the grabbed video + window size. So a analyze size of 1 for a 16:9 video will give us a 128x72 pixel + analyze window. + Valid values: 0 ... 3 + +overscan 30 Ignored overscan border of grabbed video frame. + Unit is percentage of 1000. e.g. 30 -> 3% + Valid values: 0 ... 200 + +darkness_limit 50 Minimum brightness of pixel. Value's below are interpreted as black pixel. + Used to detect and skip "black borders" in video. + Valid values are 0 ... 100 + +edge_weighting 8 Power of edge weighting. Valid values 1 ... 30 + +hue_win_size 3 Windowing size for HUE. Valid values 0 ... 5 + +sat_win_size 3 Windowing size for saturation. Valid values 0 ... 5 + +filter combined Select's smoothness filter. Currently there are two filters + supported: percentage and combined. + Valid values: off, percentage, combined + +filter_smoothness 50 Controls filter smoothness of percentage filter. + Unit percentage of 100. Valid values 1 ... 100 + +filter_length 500 Controls filter length of combined filter. + Unit milliseconds. Valid values 300 ... 5000 + +filter_threshold 40 Controls filter threshold of combined filter. + Unit percentage of 100. Valid values 1 ... 100 + +brightness 100 Controls brightness of generated color values. + Unit percentage of 100. Valid values 50 ... 300 + Note: When using DF10CH controller(s) it is better to use the white calibration or + common brightness feature of the DF10CH setup program instead of this + parameter because you will lose lesser or no PWM resolution. + +wc_red +wc_green +wc_blue 255 White calibration values for red, green and blue channel. + Valid values 0 ... 255. + Note: When using DF10CH controller(s) it is better to use the white calibration + feature of the DF10CH setup program instead of this parameter because you will + lose lesser PWM resolution. + +gamma 0 Gamma correction factor for red, green and blue channel. + Value is divided by 10 e.g. 25 -> gamma 2.5 + Valid values 0 ... 50. 0 means no gamma correction + Note: When using DF10CH controller(s) it is better to use the gamma calibration + feature of the DF10CH setup program instead of this parameter because you will + lose no PWM resolution. +
\ No newline at end of file diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..ce329aa --- /dev/null +++ b/debian/changelog @@ -0,0 +1,25 @@ +libxine-atmo-plugin (0.3-0) karmic; urgency=low + + * New upstream version 0.3 + * added dependency for libusb-1.0 + * control file fixed so that compilation against libxine1 or libxine2 is possible + + -- durchflieger <yak54@gmx.net> Thu, 04 Feb 2010 11:49:15 +0100 + +libxine-atmo-plugin (0.2-3tvt1) karmic; urgency=low + + * changed debian/control+copyright + + -- Holger Schvestka <hotzenplotz5@gmx.de> Mon, 28 Dec 2009 21:09:18 +0100 + +libxine-atmo-plugin (0.2-2tvt1) karmic; urgency=low + + * recompile with -fPIC + + -- Gerald Dachs <gda@dachsweb.de> Mon, 28 Dec 2009 12:45:24 +0100 + +libxine-atmo-plugin (0.2-1tvt1) karmic; urgency=low + + * Initial release + + -- Gerald Dachs <gda@dachsweb.de> Mon, 28 Dec 2009 00:08:17 +0100 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..7f8f011 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +7 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..59bc583 --- /dev/null +++ b/debian/control @@ -0,0 +1,14 @@ +Source: libxine-atmo-plugin +Section: libs +Priority: extra +Maintainer: Gerald Dachs <gda@dachsweb.de> +Build-Depends: debhelper (>= 7), libxine-dev, libusb-1.0-0-dev +Standards-Version: 3.8.3 +Homepage: http://www.vdrportal.de/board/thread.php?threadid=88205&hilightuser=18772 + +Package: libxine-atmo-plugin +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: xine post plugin needed for atmolight controllers + The "atmo" post plugin analyze the video picture and generates + output data for so called "atmolight" controllers. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..71ac9ee --- /dev/null +++ b/debian/copyright @@ -0,0 +1,26 @@ +This package was debianized by Gerald Dachs <Gerald Dachs <gda@dachsweb.de>> on +Mon, 28 Dec 2009 00:08:17 +0100. + + It was downloaded from http://www.vdrportal.de/board/thread.php?threadid=88205&hilightuser=18772 + +Upstream Author: + + Andreas Auras <yak54@gmx.net> + +Copyright: + + (C) 2009 Andreas Auras + +License: + + and is licensed under the GPL version 2, + see `/usr/share/common-licenses/GPL-2'. + +The Debian packaging is: + + Copyright (C) 2009 Gerald Dachs <Gerald Dachs <gda@dachsweb.de>> + + and is licensed under the GPL version 2, + see `/usr/share/common-licenses/GPL-2'. + + diff --git a/debian/docs b/debian/docs new file mode 100644 index 0000000..e845566 --- /dev/null +++ b/debian/docs @@ -0,0 +1 @@ +README diff --git a/debian/rules b/debian/rules new file mode 100644 index 0000000..75b5e91 --- /dev/null +++ b/debian/rules @@ -0,0 +1,91 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 + +CFLAGS+=-O3 -fPIC +XINEPLUGINDIR = $(shell pkg-config --variable=plugindir libxine) + + + +configure: configure-stamp +configure-stamp: + dh_testdir + # Add here commands to configure the package. + + touch configure-stamp + + +build: build-stamp + +build-stamp: configure-stamp + dh_testdir + + # Add here commands to compile the package. + $(MAKE) + #docbook-to-man debian/atmo.sgml > atmo.1 + + touch $@ + +clean: + dh_testdir + dh_testroot + rm -f build-stamp configure-stamp + + # Add here commands to clean up after the build process. + $(MAKE) clean + + dh_clean + +install: build + dh_testdir + dh_testroot + dh_prep + dh_installdirs + + # Add here commands to install the package into debian/atmo. + dh_install xineplug_post_atmo.so $(XINEPLUGINDIR)/post/ + +# Build architecture-independent files here. +binary-indep: install +# We have nothing to do by default. + +# Build architecture-dependent files here. +binary-arch: install + dh_testdir + dh_testroot + dh_installchangelogs + dh_installdocs + dh_installexamples +# dh_install +# dh_installmenu +# dh_installdebconf +# dh_installlogrotate +# dh_installemacsen +# dh_installpam +# dh_installmime +# dh_python +# dh_installinit +# dh_installcron +# dh_installinfo + dh_installman + dh_link + dh_strip + dh_compress + dh_fixperms +# dh_perl +# dh_makeshlibs + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install configure diff --git a/df10ch_usb_proto.h b/df10ch_usb_proto.h new file mode 100644 index 0000000..d1b7310 --- /dev/null +++ b/df10ch_usb_proto.h @@ -0,0 +1,116 @@ +/*
+ * Copyright (C) 2010 Andreas Auras
+ *
+ * This file is part of the atmo post plugin, a plugin for the free xine video player.
+ *
+ * atmo post plugin 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.
+ *
+ * atmo post plugin 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
+ *
+ */
+
+/* ---
+ * Communication Protokoll related defines for DF10CH 10 channel RGB Controller.
+ */
+
+#define REQ_USB_START 0 // Start of usb controller requests
+#define REQ_USB_BL_START 64 // Start of usb controller boot loader requests
+#define REQ_PWM_START 128 // Start of pwm controller requests
+#define REQ_PWM_BL_START 192 // Start of pwm controller boot loader requests
+
+enum
+{
+ // usb controller requests
+ REQ_START_BOOTLOADER = REQ_USB_START, // start boot loader of usb controller
+ REQ_READ_EE_DATA, // read eeprom data (wLength: number of bytes, wIndex: eeprom start address)
+ REQ_WRITE_EE_DATA, // write eeprom data (wLength: number of bytes, wIndex: eeprom start address)
+
+ REQ_STOP_PWM_CTRL, // stop PWM controller
+ REQ_RESET_PWM_CTRL, // reset PWM controller
+ REQ_BOOTLOADER_RESET_PWM_CTRL, // reset PWM controller and signal bootloader start
+
+ REQ_SET_REPLY_TIMEOUT, // set reply timeout values (wValue: start timeout [ms], wIndex: timeout [ms])
+
+ REQ_GET_REPLY_ERR_STATUS, // get reply error status (COMM_ERR_...)
+
+ // usb controller boot loader requests
+ BL_REQ_WRITE_PAGE = REQ_USB_BL_START, // write flash page
+ BL_REQ_LEAVE_BOOT, // leave boot loader and start application
+ BL_REQ_GET_PAGE_SIZE, // return flash page size of device
+ BL_REQ_READ_FLASH, // return flash contents
+
+ // pwm controller requests
+ PWM_REQ_GET_VERSION = REQ_PWM_START, // Get firmware version (returns 1 byte)
+ PWM_REQ_SET_BRIGHTNESS, // Set channel brightness values (wLenght: number of bytes, wIndex: start channel)
+ PWM_REQ_SET_BRIGHTNESS_SYNCED, // Same as above but wait until buffer flip
+ PWM_REQ_GET_BRIGHTNESS,
+
+ PWM_REQ_SET_CHANNEL_MAP, // Set channel to port mapping (wLength: number of bytes, wIndex: start channel)
+ PWM_REQ_GET_CHANNEL_MAP,
+
+ PWM_REQ_SET_COMMON_PWM, // Set common pwm value (wValue.low: pwm value)
+ PWM_REQ_GET_COMMON_PWM,
+
+ PWM_REQ_STORE_SETUP, // Store actual calibration values
+ PWM_REQ_RESET_SETUP, // Reset calibration values to default
+
+ PWM_REQ_GET_REQUEST_ERR_STATUS, // Get request error status (COMM_ERR_...)
+
+ PWM_REQ_GET_MAX_PWM, // Get maximum internal PWM value
+
+ PWM_REQ_SET_PWM_FREQ, // Set pwm frequency (wValue: frequency [hz])
+ PWM_REQ_GET_PWM_FREQ, // Get pwm frequency (returns word)
+
+ PWM_REQ_ECHO_TEST, // Reply 8 byte header
+
+ // pwm controller boot loader requests
+ BL_PWM_REQ_WRITE_PAGE = REQ_PWM_BL_START, // write flash page
+ BL_PWM_REQ_GET_PAGE_SIZE, // return flash page size of device
+ BL_PWM_REQ_READ_FLASH, // return flash contents
+ BL_PWM_REQ_GET_REQUEST_ERR_STATUS // Get request error status (COMM_ERR_...)
+};
+
+// Data payload related
+#define MAX_REQ_PAYLOAD_SIZE 128
+#define MAX_REPLY_PAYLOAD_SIZE 128
+
+// Error flag definition for communication error's of usb and pwm controller
+#define COMM_ERR_OVERRUN 0
+#define COMM_ERR_FRAME 1
+#define COMM_ERR_TIMEOUT 2
+#define COMM_ERR_START 3
+#define COMM_ERR_OVERFLOW 4
+#define COMM_ERR_CRC 5
+#define COMM_ERR_DUPLICATE 6
+#define COMM_ERR_DEBUG 7
+
+// Port channel mapping related
+#define NCHANNELS 30 // Number of supported Channels
+
+#define NPORTS 4
+#define PA_IDX 0
+#define PB_IDX 1
+#define PC_IDX 2
+#define PD_IDX 3
+
+#define CM_CODE(port, channel) (((channel) << 2) | (port))
+#define CM_CHANNEL(code) ((code) >> 2)
+#define CM_PORT(code) ((code) & 0x03)
+
+// PWM frequency related
+#define MIN_PWM_FREQ 50
+#define MAX_PWM_FREQ 400
+
+// PWM controller version request related
+#define PWM_VERS_APPL 0 // Is application firmware
+#define PWM_VERS_BOOT 1 // Is bootloader firmware
diff --git a/output_driver.h b/output_driver.h new file mode 100644 index 0000000..3d88b4f --- /dev/null +++ b/output_driver.h @@ -0,0 +1,1025 @@ +/* + * Copyright (C) 2009, 2010 Andreas Auras + * + * This file is part of the atmo post plugin, a plugin for the free xine video player. + * + * atmo post plugin 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. + * + * atmo post plugin 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 + * + */ + +#include <termios.h> +#include <regex.h> +#include <fcntl.h> +#include <math.h> + +#define NUM_AREAS 9 // Number of different areas (top, bottom ...) + +typedef struct { int sum, top, bottom, left, right, center, top_left, top_right, bottom_left, bottom_right; } num_channels_t; + + +/* + * abstraction for output drivers + */ +typedef struct output_driver_s output_driver_t; +struct output_driver_s { + /* open device and configure for number of channels */ + int (*open)(output_driver_t *this, const char *param, num_channels_t *channels); + + /* configure device for number of channels */ + int (*configure)(output_driver_t *this, num_channels_t *channels); + + /* close device */ + void (*close)(output_driver_t *this); + + /* + * send RGB color values to device + * last_colors is NULL when first initial color packet is send + * order for 'colors' is: top 1,2,3..., bottom 1,2,3..., left 1,2,3..., right 1,2,3..., center, top left, top right, bottom left, bottom right + */ + void (*output_colors)(output_driver_t *this, rgb_color_t *new_colors, rgb_color_t *last_colors); + + /* provide detailed error message here if open of device fails */ + char errmsg[128]; +}; + + + +/********************************************************************************************************** + * File output driver + **********************************************************************************************************/ + +typedef struct { + output_driver_t output_driver; + num_channels_t num_channels; + FILE *fd; + int id; +} file_output_driver_t; + + +static int file_driver_open(output_driver_t *this_gen, const char *param, num_channels_t *n) { + file_output_driver_t *this = (file_output_driver_t *) this_gen; + + this->num_channels = *n; + this->id = 0; + + if (param && strlen(param)) + this->fd = fopen(param, "a"); + else + this->fd = fopen("xine_atmo_data.out", "a"); + + if (this->fd == NULL) { + strerror_r(errno, this->output_driver.errmsg, sizeof(this->output_driver.errmsg)); + return -1; + } + return 0; +} + +static int file_driver_configure(output_driver_t *this_gen, num_channels_t *n) { + file_output_driver_t *this = (file_output_driver_t *) this_gen; + this->num_channels = *n; + return 0; +} + +static void file_driver_close(output_driver_t *this_gen) { + file_output_driver_t *this = (file_output_driver_t *) this_gen; + + if (this->fd) { + fclose(this->fd); + this->fd = NULL; + } +} + + +static void file_driver_output_colors(output_driver_t *this_gen, rgb_color_t *colors, rgb_color_t *last_colors) { + file_output_driver_t *this = (file_output_driver_t *) this_gen; + FILE *fd = this->fd; + struct timeval tvnow; + int c; + + if (fd) { + gettimeofday(&tvnow, NULL); + fprintf(fd, "%d: %ld.%03ld ---\n", this->id++, tvnow.tv_sec, tvnow.tv_usec / 1000); + + for (c = 1; c <= this->num_channels.top; ++c, ++colors) + fprintf(fd," top %2d: %3d %3d %3d\n", c, colors->r, colors->g, colors->b); + + for (c = 1; c <= this->num_channels.bottom; ++c, ++colors) + fprintf(fd," bottom %2d: %3d %3d %3d\n", c, colors->r, colors->g, colors->b); + + for (c = 1; c <= this->num_channels.left; ++c, ++colors) + fprintf(fd," left %2d: %3d %3d %3d\n", c, colors->r, colors->g, colors->b); + + for (c = 1; c <= this->num_channels.right; ++c, ++colors) + fprintf(fd," right %2d: %3d %3d %3d\n", c, colors->r, colors->g, colors->b); + + if (this->num_channels.center) { + fprintf(fd," center: %3d %3d %3d\n", colors->r, colors->g, colors->b); + ++colors; + } + if (this->num_channels.top_left) { + fprintf(fd," top left: %3d %3d %3d\n", colors->r, colors->g, colors->b); + ++colors; + } + if (this->num_channels.top_right) { + fprintf(fd," top right: %3d %3d %3d\n", colors->r, colors->g, colors->b); + ++colors; + } + if (this->num_channels.bottom_left) { + fprintf(fd," bottom left: %3d %3d %3d\n", colors->r, colors->g, colors->b); + ++colors; + } + if (this->num_channels.bottom_right) { + fprintf(fd," bottom right: %3d %3d %3d\n", colors->r, colors->g, colors->b); + } + fflush(fd); + } +} + + + +/************************************************************************************************* + * Serial output driver + *************************************************************************************************/ + +typedef struct { + output_driver_t output_driver; + num_channels_t num_channels; + int devfd; +} serial_output_driver_t; + + +static int serial_driver_open(output_driver_t *this_gen, const char *param, num_channels_t *n) { + serial_output_driver_t *this = (serial_output_driver_t *) this_gen; + char buf[256], buf1[64], *s; + const char *devname = NULL; + regex_t preg; + int rc; + + this->num_channels = *n; + this->devfd = -1; + + if (!param || !strlen(param)) + { + strcpy(this->output_driver.errmsg, "no device parameter"); + return -1; + } + + if (strncmp(param, "usb:", 4) == 0) { + /* Lookup serial USB device name */ + rc = regcomp(&preg, param + 4, REG_EXTENDED | REG_NOSUB); + if (rc) { + regerror(rc, &preg, buf, sizeof(buf)); + snprintf(this->output_driver.errmsg, sizeof(this->output_driver.errmsg), "illegal device identification pattern '%s': %s", param + 4, buf); + regfree(&preg); + return -1; + } + + FILE *procfd = fopen("/proc/tty/driver/usbserial", "r"); + if (!procfd) { + strerror_r(errno, buf, sizeof(buf)); + snprintf(this->output_driver.errmsg, sizeof(this->output_driver.errmsg), "could not open /proc/tty/driver/usbserial: %s", buf); + regfree(&preg); + return -1; + } + + while (fgets(buf, sizeof(buf), procfd)) { + if (!regexec(&preg, buf, 0, NULL, 0) && (s = index(buf, ':'))) { + *s = 0; + snprintf(buf1, sizeof(buf1), "/dev/ttyUSB%s", buf); + devname = buf1; + break; + } + } + fclose(procfd); + regfree(&preg); + if (!devname) { + strcpy(this->output_driver.errmsg, "could not find usb device in /proc/tty/driver/usbserial"); + return -1; + } + llprintf(LOG_1, "USB tty device for '%s' is '%s'\n", param + 4, devname); + } else { + devname = param; + } + + /* open serial port */ + int devfd = open(devname, O_RDWR | O_NOCTTY); + if (devfd < 0) { + strerror_r(errno, buf, sizeof(buf)); + snprintf(this->output_driver.errmsg, sizeof(this->output_driver.errmsg), "could not open serial port: %s", buf); + return -1; + } + + /* configure serial port */ + const int bconst = B38400; + struct termios tio; + memset(&tio, 0, sizeof(tio)); + tio.c_cflag = (CS8 | CREAD | CLOCAL); + tio.c_cc[VMIN] = 0; + tio.c_cc[VTIME] = 1; + cfsetispeed(&tio, bconst); + cfsetospeed(&tio, bconst); + if (tcsetattr(devfd, TCSANOW, &tio)) { + strerror_r(errno, buf, sizeof(buf)); + snprintf(this->output_driver.errmsg, sizeof(this->output_driver.errmsg), "configuration of serial port failed: %s", buf); + close(devfd); + return -1; + } + tcflush(devfd, TCIOFLUSH); + + this->devfd = devfd; + return 0; +} + + +static int serial_driver_configure(output_driver_t *this_gen, num_channels_t *n) { + serial_output_driver_t *this = (serial_output_driver_t *) this_gen; + this->num_channels = *n; + return 0; +} + + +static void serial_driver_close(output_driver_t *this_gen) { + serial_output_driver_t *this = (serial_output_driver_t *) this_gen; + + if (this->devfd < 0) + return; + + close(this->devfd); + this->devfd = -1; +} + + + +/************************************************************************************** + * Protocol for "classic" atmolight serial 2ch controller + **************************************************************************************/ + +static void classic_driver_output_colors(output_driver_t *this_gen, rgb_color_t *colors, rgb_color_t *last_colors) { + serial_output_driver_t *this = (serial_output_driver_t *) this_gen; + uint8_t msg[19]; + + if (this->devfd < 0) + return; + + /* create command */ + memset(msg, 0, sizeof(msg)); + msg[0] = 0xFF; /* start byte */ + msg[1] = msg[2] = 0; /* start channel */ + msg[3] = 15; /* number of channels (5 * RGB) */ + + /* top channel */ + if (this->num_channels.top) + { + msg[13] = colors->r; + msg[14] = colors->g; + msg[15] = colors->b; + colors += this->num_channels.top; + } + + /* bottom channel */ + if (this->num_channels.bottom) + { + msg[16] = colors->r; + msg[17] = colors->g; + msg[18] = colors->b; + colors += this->num_channels.bottom; + } + + /* left channel */ + if (this->num_channels.left) + { + msg[7] = colors->r; + msg[8] = colors->g; + msg[9] = colors->b; + colors += this->num_channels.left; + } + + /* right channel */ + if (this->num_channels.right) + { + msg[10] = colors->r; + msg[11] = colors->g; + msg[12] = colors->b; + colors += this->num_channels.right; + } + + /* center channel */ + if (this->num_channels.center) + { + msg[4] = colors->r; + msg[5] = colors->g; + msg[6] = colors->b; + } + + /* send data to target */ + if (write(this->devfd, msg, sizeof(msg)) > 0) + tcdrain(this->devfd); /* flush buffer */ +} + + + +/******************************************************************************** + * Protocol for my own designed 4ch serial controller + ********************************************************************************/ + +static void df4ch_driver_output_colors(output_driver_t *this_gen, rgb_color_t *colors, rgb_color_t *last_colors) { + serial_output_driver_t *this = (serial_output_driver_t *) this_gen; + uint8_t msg[15]; + + if (this->devfd < 0) + return; + + /* create command */ + memset(msg, 0, sizeof(msg)); + msg[0] = 0xFF; /* start byte */ + msg[1] = 0; /* start channel */ + msg[2] = 12; /* number of channels (4 * RGB) */ + + /* top channel */ + if (this->num_channels.top) + { + msg[9] = colors->r; + msg[10] = colors->g; + msg[11] = colors->b; + colors += this->num_channels.top; + } + + /* bottom channel */ + if (this->num_channels.bottom) + { + msg[12] = colors->r; + msg[13] = colors->g; + msg[14] = colors->b; + colors += this->num_channels.bottom; + } + + /* left channel */ + if (this->num_channels.left) + { + msg[3] = colors->r; + msg[4] = colors->g; + msg[5] = colors->b; + colors += this->num_channels.left; + } + + /* right channel */ + if (this->num_channels.right) + { + msg[6] = colors->r; + msg[7] = colors->g; + msg[8] = colors->b; + } + + /* send data to target */ + if (write(this->devfd, msg, sizeof(msg)) > 0) + tcdrain(this->devfd); /* flush buffer */ +} + + + +/*************************************************************************************************** + * DF10CH output driver for my own designed "next generation" 10ch RGB Controller + ***************************************************************************************************/ + +#include <libusb.h> +#include "df10ch_usb_proto.h" + +#define DF10CH_USB_CFG_VENDOR_ID 0x16c0 +#define DF10CH_USB_CFG_PRODUCT_ID 0x05dc +#define DF10CH_USB_CFG_VENDOR_NAME "yak54@gmx.net" +#define DF10CH_USB_CFG_PRODUCT "DF10CH" +#define DF10CH_USB_CFG_SERIAL "AP" +#define DF10CH_USB_DEFAULT_TIMEOUT 100 + +#define DF10CH_MAX_CHANNELS 30 +#define DF10CH_SIZE_CONFIG (14 + DF10CH_MAX_CHANNELS * 6) +#define DF10CH_CONFIG_VALID_ID 0xA0A1 + +enum { DF10CH_AREA_TOP, DF10CH_AREA_BOTTOM, DF10CH_AREA_LEFT, DF10CH_AREA_RIGHT, DF10CH_AREA_CENTER, DF10CH_AREA_TOP_LEFT, DF10CH_AREA_TOP_RIGHT, DF10CH_AREA_BOTTOM_LEFT, DF10CH_AREA_BOTTOM_RIGHT }; + +typedef struct df10ch_gamma_tab_s { + struct df10ch_gamma_tab_s *next; + uint8_t gamma; + uint16_t white_cal; + uint16_t tab[256]; +} df10ch_gamma_tab_t; + +typedef struct { + int req_channel; // Channel number in request + int area; // Source area + int area_num; // Source area number + int color; // Source color + df10ch_gamma_tab_t *gamma_tab; // Corresponding gamma table +} df10ch_channel_config_t; + +typedef struct df10ch_ctrl_s { + struct df10ch_ctrl_s *next; + libusb_device_handle *dev; + int idx_serial_number; + uint16_t config_version; // Version number of configuration data + uint16_t pwm_res; // PWM resolution + int num_req_channels; // Number of channels in request + df10ch_channel_config_t *channel_config; // List of channel configurations + char id[32]; // ID of Controller + struct libusb_transfer *transfer; + uint8_t *transfer_data; + int pending_submit; +} df10ch_ctrl_t; + +typedef struct { + output_driver_t output_driver; + libusb_context *ctx; + num_channels_t num_channels; // Global channel layout + df10ch_ctrl_t *ctrls; // List of found controllers + df10ch_gamma_tab_t *gamma_tabs; // List of calculated gamma tables + int max_transmit_latency; + int avg_transmit_latency; +} df10ch_output_driver_t; + + +static const char *df10ch_usb_errmsg(int rc) { + switch (rc) { + case LIBUSB_SUCCESS: + return ("Success (no error)"); + case LIBUSB_ERROR_IO: + return ("Input/output error"); + case LIBUSB_ERROR_INVALID_PARAM: + return ("Invalid parameter"); + case LIBUSB_ERROR_ACCESS: + return ("Access denied (insufficient permissions)"); + case LIBUSB_ERROR_NO_DEVICE: + return ("No such device (it may have been disconnected)"); + case LIBUSB_ERROR_NOT_FOUND: + return ("Entity not found"); + case LIBUSB_ERROR_BUSY: + return ("Resource busy"); + case LIBUSB_ERROR_TIMEOUT: + return ("Operation timed out"); + case LIBUSB_ERROR_OVERFLOW: + return ("Overflow"); + case LIBUSB_ERROR_PIPE: + return ("Pipe error"); + case LIBUSB_ERROR_INTERRUPTED: + return ("System call interrupted (perhaps due to signal)"); + case LIBUSB_ERROR_NO_MEM: + return ("Insufficient memory"); + case LIBUSB_ERROR_NOT_SUPPORTED: + return ("Operation not supported or unimplemented on this platform"); + case LIBUSB_ERROR_OTHER: + return ("Other error"); + } + return ("?"); +} + + +static const char * df10ch_usb_transfer_errmsg(int s) { + switch (s) { + case LIBUSB_TRANSFER_COMPLETED: + return ("Transfer completed without error"); + case LIBUSB_TRANSFER_ERROR: + return ("Transfer failed"); + case LIBUSB_TRANSFER_TIMED_OUT: + return ("Transfer timed out"); + case LIBUSB_TRANSFER_CANCELLED: + return ("Transfer was cancelled"); + case LIBUSB_TRANSFER_STALL: + return ("Control request stalled"); + case LIBUSB_TRANSFER_NO_DEVICE: + return ("Device was disconnected"); + case LIBUSB_TRANSFER_OVERFLOW: + return ("Device sent more data than requested"); + } + return ("?"); +} + + +static int df10ch_control_in_transfer(df10ch_ctrl_t *ctrl, uint8_t req, uint16_t val, uint16_t index, unsigned int timeout, uint8_t *buf, uint16_t buflen) +{ + // Use a return buffer always so that the controller is able to send a USB reply status + // This is special for VUSB at controller side + unsigned char rcbuf[1]; + int len = buflen; + if (!len) + { + buf = rcbuf; + len = 1; + } + + // Because VUSB at controller sends ACK reply before CRC check of received data we have to retry sending request our self if data is corrupted + int n = 0, retrys = 0; + while (retrys < 3) + { + n = libusb_control_transfer(ctrl->dev, LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, req, val, index, buf, len, timeout); + if (n != LIBUSB_ERROR_INTERRUPTED) + { + if (n >= 0 || n != LIBUSB_ERROR_PIPE) + break; + ++retrys; + } + } + + if (n < 0) + { + llprintf(LOG_1, "%s: sending USB control transfer message %d failed: %s\n", ctrl->id, req, df10ch_usb_errmsg(n)); + return -1; + } + + if (n != buflen) + { + llprintf(LOG_1, "%s: sending USB control transfer message %d failed: read %d bytes but expected %d bytes\n", ctrl->id, req, n, buflen); + return -1; + } + + return 0; +} + + +static void df10ch_dispose(df10ch_output_driver_t *this) { + df10ch_ctrl_t *ctrl = this->ctrls; + while (ctrl) { + libusb_free_transfer(ctrl->transfer); + libusb_release_interface(ctrl->dev, 0); + libusb_close(ctrl->dev); + + df10ch_ctrl_t *next = ctrl->next; + free(ctrl->transfer_data); + free(ctrl->channel_config); + free(ctrl); + ctrl = next; + } + + if (this->ctx) + libusb_exit(this->ctx); + + df10ch_gamma_tab_t *gt = this->gamma_tabs; + while (gt) { + df10ch_gamma_tab_t *next = gt->next; + free(gt); + gt = next; + } + + this->ctrls = NULL; + this->ctx = NULL; + this->gamma_tabs = NULL; +} + + +static void df10ch_wait_for_replys(df10ch_output_driver_t *this) { + // wait for end of all pending transfers + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = (DF10CH_USB_DEFAULT_TIMEOUT + 50) * 1000; + df10ch_ctrl_t *ctrl = this->ctrls; + while (ctrl) { + if (ctrl->pending_submit) { + int rc = libusb_handle_events_timeout(this->ctx, &timeout); + if (rc && rc != LIBUSB_ERROR_INTERRUPTED) { + llprintf(LOG_1, "handling USB events failed: %s\n", df10ch_usb_errmsg(rc)); + break; + } + } + else + ctrl = ctrl->next; + } +} + + +static void df10ch_reply_cb(struct libusb_transfer *transfer) { + df10ch_ctrl_t *ctrl = (df10ch_ctrl_t *) transfer->user_data; + ctrl->pending_submit = 0; + if (transfer->status != LIBUSB_TRANSFER_COMPLETED && transfer->status != LIBUSB_TRANSFER_CANCELLED) + llprintf(LOG_1, "%s: submitting USB control transfer message failed: %s\n", ctrl->id, df10ch_usb_transfer_errmsg(transfer->status)); +} + + +static int df10ch_driver_open(output_driver_t *this_gen, const char *param, num_channels_t *num_channels) { + df10ch_output_driver_t *this = (df10ch_output_driver_t *) this_gen; + + if (libusb_init(&this->ctx) < 0) { + strcpy(this->output_driver.errmsg, "can't initialize USB library"); + return -1; + } + + libusb_device **list = NULL; + size_t cnt = libusb_get_device_list(this->ctx, &list); + if (cnt < 0) { + snprintf(this->output_driver.errmsg, sizeof(this->output_driver.errmsg), "getting list of USB devices failed: %s", df10ch_usb_errmsg(cnt)); + df10ch_dispose(this); + return -1; + } + + // Note: Because controller uses obdev's free USB product/vendor ID's we have to do special lookup for finding + // the controllers. See file "USB-IDs-for-free.txt" of VUSB distribution. + int rc; + size_t i; + for (i = 0; i < cnt; i++) { + libusb_device *d = list[i]; + struct libusb_device_descriptor desc; + + int busnum = libusb_get_bus_number(d); + int devnum = libusb_get_device_address(d); + + rc = libusb_get_device_descriptor(d, &desc); + if (rc < 0) + llprintf(LOG_1, "USB[%d,%d]: getting USB device descriptor failed: %s\n", busnum, devnum, df10ch_usb_errmsg(rc)); + else if (desc.idVendor == DF10CH_USB_CFG_VENDOR_ID && desc.idProduct == DF10CH_USB_CFG_PRODUCT_ID) { + libusb_device_handle *hdl = NULL; + rc = libusb_open(d, &hdl); + if (rc < 0) + llprintf(LOG_1, "USB[%d,%d]: open of USB device failed: %s\n", busnum, devnum, df10ch_usb_errmsg(rc)); + else { + unsigned char buf[256]; + rc = libusb_get_string_descriptor_ascii(hdl, desc.iManufacturer, buf, sizeof(buf)); + if (rc < 0) + llprintf(LOG_1, "USB[%d,%d]: getting USB manufacturer string failed: %s\n", busnum, devnum, df10ch_usb_errmsg(rc)); + else if (rc == sizeof(DF10CH_USB_CFG_VENDOR_NAME) - 1 && !memcmp(buf, DF10CH_USB_CFG_VENDOR_NAME, rc)) { + rc = libusb_get_string_descriptor_ascii(hdl, desc.iProduct, buf, sizeof(buf)); + if (rc < 0) + llprintf(LOG_1, "USB[%d,%d]: getting USB product string failed: %s\n", busnum, devnum, df10ch_usb_errmsg(rc)); + else if (rc == sizeof(DF10CH_USB_CFG_PRODUCT) - 1 && !memcmp(buf, DF10CH_USB_CFG_PRODUCT, rc)) { + char id[32]; + snprintf(id, sizeof(id), "DF10CH[%d,%d]", busnum, devnum); + rc = libusb_set_configuration(hdl, 1); + if (rc < 0) + llprintf(LOG_1, "%s: setting USB configuration failed: %s\n", id, df10ch_usb_errmsg(rc)); + else { + rc = libusb_claim_interface(hdl, 0); + if (rc < 0) + llprintf(LOG_1, "%s: claiming USB interface failed: %s\n", id, df10ch_usb_errmsg(rc)); + else { + df10ch_ctrl_t *ctrl = (df10ch_ctrl_t *) calloc(1, sizeof(df10ch_ctrl_t)); + ctrl->next = this->ctrls; + this->ctrls = ctrl; + ctrl->dev = hdl; + ctrl->idx_serial_number = desc.iSerialNumber; + strcpy(ctrl->id, id); + llprintf(LOG_1, "%s: device opened\n", id); + continue; + } + } + } + } + libusb_close(hdl); + } + } + } + + libusb_free_device_list(list, 1); + + if (!this->ctrls) { + strcpy(this->output_driver.errmsg, "USB: no DF10CH devices found!"); + df10ch_dispose(this); + return -1; + } + + // Read controller configuration + df10ch_ctrl_t *ctrl = this->ctrls; + while (ctrl) { + uint8_t data[256]; + + // Check that USB controller is running application firmware and not bootloader + rc = libusb_get_string_descriptor_ascii(ctrl->dev, ctrl->idx_serial_number, data, sizeof(data) - 1); + if (rc < 0) { + snprintf(this->output_driver.errmsg, sizeof(this->output_driver.errmsg), "%s: getting USB serial number string failed: %s", ctrl->id, df10ch_usb_errmsg(rc)); + df10ch_dispose(this); + return -1; + } + if (rc != sizeof(DF10CH_USB_CFG_SERIAL) - 1 || memcmp(data, DF10CH_USB_CFG_SERIAL, rc)) { + data[rc] = 0; + snprintf(this->output_driver.errmsg, sizeof(this->output_driver.errmsg), "%s: application firmware of USB controller is not running! Current mode is: %s", ctrl->id, data); + df10ch_dispose(this); + return -1; + } + + // check that PWM controller is running application firmware and not bootloader + if (df10ch_control_in_transfer(ctrl, PWM_REQ_GET_VERSION, 0, 0, DF10CH_USB_DEFAULT_TIMEOUT, data, 2)) { + snprintf(this->output_driver.errmsg, sizeof(this->output_driver.errmsg), "%s: reading PWM controller version fails!", ctrl->id); + df10ch_dispose(this); + return -1; + } + if (data[0] != PWM_VERS_APPL) { + snprintf(this->output_driver.errmsg, sizeof(this->output_driver.errmsg), "%s: application firmware of PWM controller is not running! Current mode is: %d", ctrl->id, data[0]); + df10ch_dispose(this); + return -1; + } + + // read eeprom configuration data + uint8_t eedata[DF10CH_SIZE_CONFIG]; + if (df10ch_control_in_transfer(ctrl, REQ_READ_EE_DATA, 0, 1, DF10CH_USB_DEFAULT_TIMEOUT, eedata, sizeof(eedata))) { + snprintf(this->output_driver.errmsg, sizeof(this->output_driver.errmsg), "%s: reading eeprom config data fails!", ctrl->id); + df10ch_dispose(this); + return -1; + } + + // check that configuration data is valid + int cfg_valid_id = eedata[0] + (eedata[1] << 8); + if (cfg_valid_id != DF10CH_CONFIG_VALID_ID) { + snprintf(this->output_driver.errmsg, sizeof(this->output_driver.errmsg), "%s: controller is not configured! Please run setup program first", ctrl->id); + df10ch_dispose(this); + return -1; + } + + ctrl->config_version = eedata[2] + (eedata[3] << 8); + + // Determine channel layout + int n; + n = eedata[4 + DF10CH_AREA_TOP]; + if (n > num_channels->top) + num_channels->top = n; + n = eedata[4 + DF10CH_AREA_BOTTOM]; + if (n > num_channels->bottom) + num_channels->bottom = n; + n = eedata[4 + DF10CH_AREA_LEFT]; + if (n > num_channels->left) + num_channels->left = n; + n = eedata[4 + DF10CH_AREA_RIGHT]; + if (n > num_channels->right) + num_channels->right = n; + n = eedata[4 + DF10CH_AREA_CENTER]; + if (n > num_channels->center) + num_channels->center = n; + n = eedata[4 + DF10CH_AREA_TOP_LEFT]; + if (n > num_channels->top_left) + num_channels->top_left = n; + n = eedata[4 + DF10CH_AREA_TOP_RIGHT]; + if (n > num_channels->top_right) + num_channels->top_right = n; + n = eedata[4 + DF10CH_AREA_BOTTOM_LEFT]; + if (n > num_channels->bottom_left) + num_channels->bottom_left = n; + n = eedata[4 + DF10CH_AREA_BOTTOM_RIGHT]; + if (n > num_channels->bottom_right) + num_channels->bottom_right = n; + + ctrl->num_req_channels = eedata[13]; + if (ctrl->num_req_channels > DF10CH_MAX_CHANNELS) + ctrl->num_req_channels = DF10CH_MAX_CHANNELS; + + // Read PWM resolution + if (df10ch_control_in_transfer(ctrl, PWM_REQ_GET_MAX_PWM, 0, 0, DF10CH_USB_DEFAULT_TIMEOUT, data, 2)) { + snprintf(this->output_driver.errmsg, sizeof(this->output_driver.errmsg), "%s: reading PWM resolution data fails!", ctrl->id); + df10ch_dispose(this); + return -1; + } + ctrl->pwm_res = data[0] + (data[1] << 8); + + // Build channel configuration list + int nch = ctrl->num_req_channels; + df10ch_channel_config_t *ccfg = (df10ch_channel_config_t *) calloc(nch, sizeof(df10ch_channel_config_t)); + ctrl->channel_config = ccfg; + int eei = 14; + while (nch) { + ccfg->req_channel = eedata[eei]; + ccfg->area = eedata[eei + 1] >> 2; + ccfg->color = eedata[eei + 1] & 0x03; + ccfg->area_num = eedata[eei + 2]; + uint8_t gamma = eedata[eei + 3]; + if (gamma < 10) + gamma = 10; + uint16_t white_cal = eedata[eei + 4] + (eedata[eei + 5] << 8); + + // Lookup gamma table for gamma and white calibration value + df10ch_gamma_tab_t *gt = this->gamma_tabs; + while (gt && gamma != gt->gamma && white_cal != gt->white_cal) + gt = gt->next; + if (!gt) { + // Calculate new gamma table + gt = (df10ch_gamma_tab_t *) calloc(1, sizeof(df10ch_gamma_tab_t)); + gt->next = this->gamma_tabs; + this->gamma_tabs = gt; + gt->gamma = gamma; + gt->white_cal = white_cal; + double dgamma = gamma / 10.0; + double dwhite_cal = white_cal; + int v; + for (v = 0; v < 256; ++v) { + gt->tab[v] = (uint16_t) (lround(pow((v / 255.0), dgamma) * dwhite_cal)); + if (gt->tab[v] > ctrl->pwm_res) + gt->tab[v] = ctrl->pwm_res; + } + } + ccfg->gamma_tab = gt; + + ++ccfg; + eei += 6; + --nch; + } + + // Prepare USB request for sending brightness values + ctrl->transfer_data = calloc(1, (LIBUSB_CONTROL_SETUP_SIZE + ctrl->num_req_channels * 2)); + libusb_fill_control_setup(ctrl->transfer_data, LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE, PWM_REQ_SET_BRIGHTNESS, 0, 0, ctrl->num_req_channels * 2); + ctrl->transfer = libusb_alloc_transfer(0); + libusb_fill_control_transfer(ctrl->transfer, ctrl->dev, ctrl->transfer_data, df10ch_reply_cb, ctrl, DF10CH_USB_DEFAULT_TIMEOUT); + ctrl->pending_submit = 0; + + ctrl = ctrl->next; + } + + this->num_channels = *num_channels; + this->max_transmit_latency = 0; + this->avg_transmit_latency = 0; + return 0; +} + + +static int df10ch_driver_configure(output_driver_t *this_gen, num_channels_t *num_channels) { + df10ch_output_driver_t *this = (df10ch_output_driver_t *) this_gen; + + if (num_channels->top < this->num_channels.top) + num_channels->top = this->num_channels.top; + if (num_channels->bottom < this->num_channels.bottom) + num_channels->bottom = this->num_channels.bottom; + if (num_channels->left < this->num_channels.left) + num_channels->left = this->num_channels.left; + if (num_channels->right < this->num_channels.right) + num_channels->right = this->num_channels.right; + if (num_channels->center < this->num_channels.center) + num_channels->center = this->num_channels.center; + if (num_channels->top_left < this->num_channels.top_left) + num_channels->top_left = this->num_channels.top_left; + if (num_channels->top_right < this->num_channels.top_right) + num_channels->top_right = this->num_channels.top_right; + if (num_channels->bottom_left < this->num_channels.bottom_left) + num_channels->bottom_left = this->num_channels.bottom_left; + if (num_channels->bottom_right < this->num_channels.bottom_right) + num_channels->bottom_right = this->num_channels.bottom_right; + + this->num_channels = *num_channels; + return 0; +} + + +static void df10ch_driver_close(output_driver_t *this_gen) { + df10ch_output_driver_t *this = (df10ch_output_driver_t *) this_gen; + + llprintf(LOG_1, "average transmit latency: %d [us]\n", this->avg_transmit_latency); + + // Cancel all pending requests + df10ch_ctrl_t *ctrl = this->ctrls; + while (ctrl) { + if (ctrl->pending_submit) + libusb_cancel_transfer(ctrl->transfer); + ctrl = ctrl->next; + } + + df10ch_wait_for_replys(this); + df10ch_dispose(this); +} + + +static void df10ch_driver_output_colors(output_driver_t *this_gen, rgb_color_t *colors, rgb_color_t *last_colors) { + df10ch_output_driver_t *this = (df10ch_output_driver_t *) this_gen; + struct timeval tvnow, tvlast, tvdiff; + + // Build area mapping table + rgb_color_t *area_map[9]; + rgb_color_t *c = colors; + area_map[DF10CH_AREA_TOP] = c; + c += this->num_channels.top; + area_map[DF10CH_AREA_BOTTOM] = c; + c += this->num_channels.bottom; + area_map[DF10CH_AREA_LEFT] = c; + c += this->num_channels.left; + area_map[DF10CH_AREA_RIGHT] = c; + c += this->num_channels.right; + area_map[DF10CH_AREA_CENTER] = c; + c += this->num_channels.center; + area_map[DF10CH_AREA_TOP_LEFT] = c; + c += this->num_channels.top_left; + area_map[DF10CH_AREA_TOP_RIGHT] = c; + c += this->num_channels.top_right; + area_map[DF10CH_AREA_BOTTOM_LEFT] = c; + c += this->num_channels.bottom_left; + area_map[DF10CH_AREA_BOTTOM_RIGHT] = c; + + if (LOG_1) + gettimeofday(&tvlast, NULL); + + // Generate transfer messages and send it to controllers + df10ch_ctrl_t *ctrl = this->ctrls; + while (ctrl) { + // Generate payload data (brightness values) + int do_submit = 0; + uint8_t *payload = ctrl->transfer_data + LIBUSB_CONTROL_SETUP_SIZE; + df10ch_channel_config_t *cfg = ctrl->channel_config; + int nch = ctrl->num_req_channels; + while (nch) { + c = area_map[cfg->area] + cfg->area_num; + rgb_color_t *lc = last_colors ? last_colors + (c - colors): NULL; + int v = 0; + switch (cfg->color) { + case 0: // Red + v = c->r; + if (!lc || v != lc->r) + do_submit = 1; + break; + case 1: // Green + v = c->g; + if (!lc || v != lc->g) + do_submit = 1; + break; + case 2: // Blue + v = c->b; + if (!lc || v != lc->b) + do_submit = 1; + } + + // gamma and white calibration correction + uint16_t bv = cfg->gamma_tab->tab[v]; + payload[cfg->req_channel * 2] = bv; + payload[cfg->req_channel * 2 + 1] = bv >> 8; + + ++cfg; + --nch; + } + + // initiate asynchron data transfer to controller + if (do_submit) { + int rc = libusb_submit_transfer(ctrl->transfer); + if (rc) + llprintf(LOG_1, "%s: submitting USB control transfer message failed: %s\n", ctrl->id, df10ch_usb_errmsg(rc)); + else + ctrl->pending_submit = 1; + } + + ctrl = ctrl->next; + } + + // wait for end of all pending transfers + df10ch_wait_for_replys(this); + + if (LOG_1) { + gettimeofday(&tvnow, NULL); + timersub(&tvnow, &tvlast, &tvdiff); + this->avg_transmit_latency = (this->avg_transmit_latency + tvdiff.tv_usec) / 2; + if (tvdiff.tv_usec > this->max_transmit_latency) { + this->max_transmit_latency = tvdiff.tv_usec; + llprintf(LOG_1, "max/avg transmit latency: %d/%d [us]\n", this->max_transmit_latency, this->avg_transmit_latency); + } + } +} + + +/* + * Output Drivers + */ + +#define NUM_DRIVERS 4 /* Number of registered drivers */ +static char *driver_enum[NUM_DRIVERS+1] = { "none", "file", "classic", "df4ch", "df10ch" }; + +typedef union { + output_driver_t output_driver; + file_output_driver_t file_output_driver; + serial_output_driver_t serial_output_driver; + df10ch_output_driver_t df10ch_output_driver; +} output_drivers_t; + + +static output_driver_t * get_output_driver(output_drivers_t *output_drivers, int driver) { + memset(output_drivers, 0, sizeof(output_drivers_t)); + + output_driver_t *output_driver = &output_drivers->output_driver; + switch(driver) { + case 1: /* file */ + output_driver->open = file_driver_open; + output_driver->configure = file_driver_configure; + output_driver->close = file_driver_close; + output_driver->output_colors = file_driver_output_colors; + break; + case 2: /* classic */ + output_driver->open = serial_driver_open; + output_driver->configure = serial_driver_configure; + output_driver->close = serial_driver_close; + output_driver->output_colors = classic_driver_output_colors; + output_drivers->serial_output_driver.devfd = -1; + break; + case 3: /* df4ch */ + output_driver->open = serial_driver_open; + output_driver->configure = serial_driver_configure; + output_driver->close = serial_driver_close; + output_driver->output_colors = df4ch_driver_output_colors; + output_drivers->serial_output_driver.devfd = -1; + break; + case 4: /* df10ch */ + output_driver->open = df10ch_driver_open; + output_driver->configure = df10ch_driver_configure; + output_driver->close = df10ch_driver_close; + output_driver->output_colors = df10ch_driver_output_colors; + break; + default: /* none */ + output_driver = NULL; + } + return output_driver; +} diff --git a/xine_post_atmo.c b/xine_post_atmo.c new file mode 100644 index 0000000..622c1bc --- /dev/null +++ b/xine_post_atmo.c @@ -0,0 +1,1272 @@ +/* + * Copyright (C) 2009, 2010 Andreas Auras + * + * This file is part of the atmo post plugin, a plugin for the free xine video player. + * + * atmo post plugin 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. + * + * atmo post plugin 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 + * + * + * Many ideas and algorithm in this module had been derived from the fantastic + * Atmolight-plugin for the Video Disk Recorder (VDR) that was developed by + * Eike Edener. + * + */ + +#include <stdio.h> +#include <stdint.h> +#include <unistd.h> +#include <pthread.h> +#include <math.h> +#include <errno.h> +#include <sys/time.h> + +#include <xine/post.h> + +#undef LOG_MODULE +#define LOG_MODULE "atmo" +#define LOG_1 1 +#define LOG_2 0 + + +#define OUTPUT_RATE 20 /* rate of output loop [ms] */ +#define OUTPUT_START_DELAY 1000 /* delay after video open for first send output packet [ms] */ +#define GRAB_TIMEOUT 100 /* max. time waiting for next grab image [ms] */ + + +/* accuracy of color calculation */ +#define h_MAX 255 +#define s_MAX 255 +#define v_MAX 255 + +/* macros */ +#define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) +#define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) +#define POS_DIV(a, b) ( (a)/(b) + ( ((a)%(b) >= (b)/2 ) ? 1 : 0) ) + + +typedef struct { uint8_t h, s, v; } hsv_color_t; +typedef struct { uint8_t r, g, b; } rgb_color_t; +typedef struct { uint64_t r, g, b; } rgb_color_sum_t; + +#include "output_driver.h" + +/* + * Plugin + */ + +typedef struct { + int driver; + char driver_param[256]; + int top; + int bottom; + int left; + int right; + int center; + int top_left; + int top_right; + int bottom_left; + int bottom_right; + int analyze_rate; + int analyze_size; + int overscan; + int darkness_limit; + int edge_weighting; + int hue_win_size; + int sat_win_size; + int brightness; + int filter; + int filter_smoothness; + int filter_length; + int filter_threshold; + int wc_red; + int wc_green; + int wc_blue; + int gamma; +} atmo_parameters_t; + + +#define NUM_FILTERS 2 +static char *filter_enum[NUM_FILTERS+1] = { "off", "percentage", "combined" }; + + +START_PARAM_DESCR(atmo_parameters_t) +PARAM_ITEM(POST_PARAM_TYPE_INT, driver, driver_enum, 0, NUM_DRIVERS, 0, + "output driver") +PARAM_ITEM(POST_PARAM_TYPE_CHAR, driver_param, NULL, 0, 0, 0, + "parameters for output driver") +PARAM_ITEM(POST_PARAM_TYPE_INT, top, NULL, 0, 25, 0, + "number of areas at top border") +PARAM_ITEM(POST_PARAM_TYPE_INT, bottom, NULL, 0, 25, 0, + "number of areas at bottom border") +PARAM_ITEM(POST_PARAM_TYPE_INT, left, NULL, 0, 25, 0, + "number of areas at left border") +PARAM_ITEM(POST_PARAM_TYPE_INT, right, NULL, 0, 25, 0, + "number of areas at right border") +PARAM_ITEM(POST_PARAM_TYPE_INT, center, NULL, 0, 1, 0, + "activate center area") +PARAM_ITEM(POST_PARAM_TYPE_INT, top_left, NULL, 0, 1, 0, + "activate top_left area") +PARAM_ITEM(POST_PARAM_TYPE_INT, top_right, NULL, 0, 1, 0, + "activate top_right area") +PARAM_ITEM(POST_PARAM_TYPE_INT, bottom_left, NULL, 0, 1, 0, + "activate bottom_left area") +PARAM_ITEM(POST_PARAM_TYPE_INT, bottom_right, NULL, 0, 1, 0, + "activate bottom right area") +PARAM_ITEM(POST_PARAM_TYPE_INT, analyze_rate, NULL, 10, 500, 0, + "analyze rate [ms]") +PARAM_ITEM(POST_PARAM_TYPE_INT, analyze_size, NULL, 0, 3, 0, + "size of analyze image") +PARAM_ITEM(POST_PARAM_TYPE_INT, overscan, NULL, 0, 200, 0, + "ignored overscan border of grabbed image [%1000]") +PARAM_ITEM(POST_PARAM_TYPE_INT, darkness_limit, NULL, 0, 100, 0, + "limit for black pixel") +PARAM_ITEM(POST_PARAM_TYPE_INT, edge_weighting, NULL, 1, 30, 0, + "power of edge weighting") +PARAM_ITEM(POST_PARAM_TYPE_INT, hue_win_size, NULL, 0, 5, 0, + "hue windowing size") +PARAM_ITEM(POST_PARAM_TYPE_INT, sat_win_size, NULL, 0, 5, 0, + "saturation windowing size") +PARAM_ITEM(POST_PARAM_TYPE_INT, brightness, NULL, 50, 300, 0, + "brightness [%]") +PARAM_ITEM(POST_PARAM_TYPE_INT, filter, filter_enum, 0, NUM_FILTERS, 0, + "filter mode") +PARAM_ITEM(POST_PARAM_TYPE_INT, filter_smoothness, NULL, 1, 100, 0, + "filter smoothness [%]") +PARAM_ITEM(POST_PARAM_TYPE_INT, filter_length, NULL, 300, 5000, 0, + "filter length [ms]") +PARAM_ITEM(POST_PARAM_TYPE_INT, filter_threshold, NULL, 1, 100, 0, + "filter threshold [%]") +PARAM_ITEM(POST_PARAM_TYPE_INT, wc_red, NULL, 0, 255, 0, + "white calibration correction factor of red color channel") +PARAM_ITEM(POST_PARAM_TYPE_INT, wc_green, NULL, 0, 255, 0, + "white calibration correction factor of green color channel") +PARAM_ITEM(POST_PARAM_TYPE_INT, wc_blue, NULL, 0, 255, 0, + "white calibration correction factor of blue color channel") +PARAM_ITEM(POST_PARAM_TYPE_INT, gamma, NULL, 0, 30, 0, + "gamma correction factor") +END_PARAM_DESCR(atmo_param_descr) + + +typedef struct atmo_post_plugin_s +{ + /* xine related */ + post_plugin_t post_plugin; + xine_post_in_t parameter_input; + atmo_parameters_t parm; + void (*port_open)(xine_video_port_t *port_gen, xine_stream_t *stream); + void (*port_close)(xine_video_port_t *port_gen, xine_stream_t *stream); + + /* channel configuration related */ + num_channels_t num_channels; + + /* thread related */ + int *grab_running, *output_running; + pthread_t grab_thread, output_thread; + pthread_mutex_t lock; + pthread_cond_t thread_started; + + /* output related */ + output_driver_t *output_driver; + output_drivers_t output_drivers; + int driver_opened; + int driver; + char driver_param[256]; + rgb_color_t *output_colors, *last_output_colors; + + /* analyze related */ + uint64_t *hue_hist, *sat_hist; + uint64_t *w_hue_hist, *w_sat_hist; + int *most_used_hue, *last_most_used_hue, *most_used_sat; + rgb_color_t *analyzed_colors; + + /* filter related */ + rgb_color_t *filtered_colors; + rgb_color_t *mean_filter_values; + rgb_color_sum_t *mean_filter_sum_values; + int old_mean_length; +} atmo_post_plugin_t; + + +static inline void rgb_to_hsv(hsv_color_t *hsv, int r, int g, int b) { + int min, max, delta; + int h = 0; + + min = MIN(MIN(r, g), b); + max = MAX(MAX(r, g), b); + + delta = max - min; + + hsv->v = (uint8_t) POS_DIV(max * v_MAX, 255); + + if (delta == 0) { + h = 0; + hsv->s = 0; + } else { + hsv->s = (uint8_t) POS_DIV((delta * s_MAX) ,max); + + int dr = (max - r) + 3 * delta; + int dg = (max - g) + 3 * delta; + int db = (max - b) + 3 * delta; + int divisor = 6 * delta; + + if (r == max) { + h = POS_DIV(( (db - dg) * h_MAX ) , divisor); + } else if (g == max) { + h = POS_DIV( ((dr - db) * h_MAX) , divisor) + (h_MAX/3); + } else if (b == max) { + h = POS_DIV(( (dg - dr) * h_MAX) , divisor) + (h_MAX/3) * 2; + } + + if (h < 0) { + h += h_MAX; + } + if (h > h_MAX) { + h -= h_MAX; + } + } + hsv->h = (uint8_t) h; +} + + +static void calc_hsv_image(hsv_color_t *hsv, uint8_t *rgb, int img_size) { + while (img_size--) { + rgb_to_hsv(hsv, rgb[0], rgb[1], rgb[2]); + ++hsv; + rgb += 3; + } +} + + +static void calc_weight(atmo_post_plugin_t *this, int *weight, const int width, const int height, const double edge_weighting) { + int row, col, c; + + const int top_channels = this->num_channels.top; + const int bottom_channels = this->num_channels.bottom; + const int left_channels = this->num_channels.left; + const int right_channels = this->num_channels.right; + const int center_channel = this->num_channels.center; + const int top_left_channel = this->num_channels.top_left; + const int top_right_channel = this->num_channels.top_right; + const int bottom_left_channel = this->num_channels.bottom_left; + const int bottom_right_channel = this->num_channels.bottom_right; + + c = top_channels + top_left_channel + top_right_channel; + const int top_chunk = c ? width / c: 0; + + c = bottom_channels + bottom_left_channel + bottom_right_channel; + const int bottom_chunk = c ? width / c: 0; + + c = left_channels + bottom_left_channel + top_left_channel; + const int left_chunk = c ? height / c: 0; + + c = right_channels + bottom_right_channel + top_right_channel; + const int right_chunk = c ? height / c: 0; + + for (row = 0; row < height; ++row) + { + double row_norm = (double)row / (double)(height - 1); + int top = (int) (255.0 * pow(1.0 - row_norm, edge_weighting)); + int bottom = (int) (255.0 * pow(row_norm, edge_weighting)); + + for (col = 0; col < width; ++col) + { + double col_norm = (double)col / (double)(width - 1); + int left = (int) (255.0 * pow((1.0 - col_norm), edge_weighting)); + int right = (int) (255.0 * pow(col_norm, edge_weighting)); + + for (c = top_left_channel; c < (top_channels + top_left_channel); ++c) + *weight++ = (col >= (c * top_chunk) && col < ((c + 1) * top_chunk)) ? top: 0; + for (c = bottom_left_channel; c < (bottom_channels + bottom_left_channel); ++c) + *weight++ = (col >= (c * bottom_chunk) && col < ((c + 1) * bottom_chunk)) ? bottom: 0; + for (c = top_left_channel; c < (left_channels + top_left_channel); ++c) + *weight++ = (row >= (c * left_chunk) && row < ((c + 1) * left_chunk)) ? left: 0; + for (c = top_right_channel; c < (right_channels + top_right_channel); ++c) + *weight++ = (row >= (c * right_chunk) && row < ((c + 1) * right_chunk)) ? right: 0; + if (center_channel) + *weight++ = 255; + if (top_left_channel) + *weight++ = (top + left) / 2; + if (top_right_channel) + *weight++ = (top + right) / 2; + if (bottom_left_channel) + *weight++ = (bottom + left) / 2; + if (bottom_right_channel) + *weight++ = (bottom + right) / 2; + } + } +} + + +static void calc_hue_hist(atmo_post_plugin_t *this, hsv_color_t *hsv, int *weight, int img_size) { + const int n = this->num_channels.sum; + uint64_t * const hue_hist = this->hue_hist; + const int darkness_limit = this->parm.darkness_limit; + + memset(hue_hist, 0, (n * (h_MAX+1) * sizeof(uint64_t))); + + while (img_size--) { + if (hsv->v > darkness_limit) { + int c; + for (c = 0; c < n; ++c) { + hue_hist[c * (h_MAX+1) + hsv->h] += *weight * hsv->v; + ++weight; + } + } else + weight += n; + ++hsv; + } +} + + +static void calc_windowed_hue_hist(atmo_post_plugin_t *this) { + int i, c, w; + const int n = this->num_channels.sum; + uint64_t * const hue_hist = this->hue_hist; + uint64_t * const w_hue_hist = this->w_hue_hist; + const int hue_win_size = this->parm.hue_win_size; + + memset(w_hue_hist, 0, (n * (h_MAX+1) * sizeof(uint64_t))); + + for (i = 0; i < (h_MAX+1); ++i) + { + for (w = -hue_win_size; w <= hue_win_size; w++) + { + int iw = i + w; + + if (iw < 0) + iw = iw + h_MAX + 1; + if (iw > h_MAX) + iw = iw - h_MAX - 1; + + uint64_t win_weight = (hue_win_size + 1) - abs(w); + + for (c = 0; c < n; ++c) + w_hue_hist[c * (h_MAX+1) + i] += hue_hist[c * (h_MAX+1) + iw] * win_weight; + } + } +} + + +static void calc_most_used_hue(atmo_post_plugin_t *this) { + int i, c; + + const int n = this->num_channels.sum; + uint64_t * const w_hue_hist = this->w_hue_hist; + int * const most_used_hue = this->most_used_hue; + int * const last_most_used_hue = this->last_most_used_hue; + + memset(most_used_hue, 0, (n * sizeof(int))); + + for (c = 0; c < n; ++c) { + uint64_t v = 0; + for (i = 0; i < (h_MAX + 1); ++i) { + if (w_hue_hist[c * (h_MAX+1) + i] > v) { + v = w_hue_hist[c * (h_MAX+1) + i]; + most_used_hue[c] = i; + } + } + if (((double) w_hue_hist[c * (h_MAX+1) + last_most_used_hue[c]] / (double) v) > 0.93) + most_used_hue[c] = last_most_used_hue[c]; + else + last_most_used_hue[c] = most_used_hue[c]; + } +} + + +static void calc_sat_hist(atmo_post_plugin_t *this, hsv_color_t *hsv, int *weight, int img_size) { + const int n = this->num_channels.sum; + uint64_t * const sat_hist = this->sat_hist; + int * const most_used_hue = this->most_used_hue; + const int darkness_limit = this->parm.darkness_limit; + const int hue_win_size = this->parm.hue_win_size; + + memset(sat_hist, 0, (n * (s_MAX+1) * sizeof(uint64_t))); + + while (img_size--) { + if (hsv->v > darkness_limit) { + int h = hsv->h; + int c; + for (c = 0; c < n; ++c) { + if (h > (most_used_hue[c] - hue_win_size) && h < (most_used_hue[c] + hue_win_size)) + sat_hist[c * (s_MAX+1) + hsv->s] += *weight * hsv->v; + ++weight; + } + } else + weight += n; + ++hsv; + } +} + + +static void calc_windowed_sat_hist(atmo_post_plugin_t *this) { + int i, c, w; + const int n = this->num_channels.sum; + uint64_t * const sat_hist = this->sat_hist; + uint64_t * const w_sat_hist = this->w_sat_hist; + const int sat_win_size = this->parm.sat_win_size; + + memset(w_sat_hist, 0, (n * (s_MAX+1) * sizeof(uint64_t))); + + for (i = 0; i < (s_MAX+1); ++i) + { + for (w = -sat_win_size; w <= sat_win_size; w++) + { + int iw = i + w; + + if (iw < 0) + iw = iw + s_MAX + 1; + if (iw > s_MAX) + iw = iw - s_MAX - 1; + + uint64_t win_weight = (sat_win_size + 1) - abs(w); + + for (c = 0; c < n; ++c) + w_sat_hist[c * (s_MAX+1) + i] += sat_hist[c * (s_MAX+1) + iw] * win_weight; + } + } +} + + +static void calc_most_used_sat(atmo_post_plugin_t *this) { + int i, c; + const int n = this->num_channels.sum; + uint64_t * const w_sat_hist = this->w_sat_hist; + int * const most_used_sat = this->most_used_sat; + + memset(most_used_sat, 0, (n * sizeof(int))); + + for (c = 0; c < n; ++c) { + uint64_t v = 0; + for (i = 0; i < (s_MAX + 1); ++i) { + if (w_sat_hist[c * (s_MAX+1) + i] > v) { + v = w_sat_hist[c * (s_MAX+1) + i]; + most_used_sat[c] = i; + } + } + } +} + + +static int calc_average_brightness(hsv_color_t *hsv, int img_size, const int darkness_limit) { + int n = 0; + uint64_t v_avg = 0; + + while (img_size--) { + if (hsv->v > darkness_limit) { + v_avg += hsv->v; + ++n; + } + ++hsv; + } + + return (n ? (int)(v_avg / n): darkness_limit); +} + + +static void hsv_to_rgb(rgb_color_t *rgb, double h, double s, double v) { + rgb->r = rgb->g = rgb->b = 0; + + h /= h_MAX; + s /= s_MAX; + v /= v_MAX; + + if (s == 0.0) { + rgb->r = (uint8_t) (v * 255.0 + 0.5); + rgb->g = rgb->r; + rgb->b = rgb->r; + } else { + h = h * 6.0; + if (h == 6.0) + h = 0.0; + int i = (int) h; + + double f = h - i; + double p = v * (1.0 - s); + double q = v * (1.0 - (s * f)); + double t = v * (1.0 - (s * (1.0 - f))); + + if (i == 0) { + rgb->r = (uint8_t) (v * 255.0 + 0.5); + rgb->g = (uint8_t) (t * 255.0 + 0.5); + rgb->b = (uint8_t) (p * 255.0 + 0.5); + } else if (i == 1) { + rgb->r = (uint8_t) (q * 255.0 + 0.5); + rgb->g = (uint8_t) (v * 255.0 + 0.5); + rgb->b = (uint8_t) (p * 255.0 + 0.5); + } else if (i == 2) { + rgb->r = (uint8_t) (p * 255.0 + 0.5); + rgb->g = (uint8_t) (v * 255.0 + 0.5); + rgb->b = (uint8_t) (t * 255.0 + 0.5); + } else if (i == 3) { + rgb->r = (uint8_t) (p * 255.0 + 0.5); + rgb->g = (uint8_t) (q * 255.0 + 0.5); + rgb->b = (uint8_t) (v * 255.0 + 0.5); + } else if (i == 4) { + rgb->r = (uint8_t) (t * 255.0 + 0.5); + rgb->g = (uint8_t) (p * 255.0 + 0.5); + rgb->b = (uint8_t) (v * 255.0 + 0.5); + } else { + rgb->r = (uint8_t) (v * 255.0 + 0.5); + rgb->g = (uint8_t) (p * 255.0 + 0.5); + rgb->b = (uint8_t) (q * 255.0 + 0.5); + } + } +} + + +static void calc_rgb_values(atmo_post_plugin_t *this, int v_avg) +{ + int c; + const int n = this->num_channels.sum; + double v = (v_avg * this->parm.brightness) / 100.0; + if (v > 255.0) + v = 255.0; + + for (c = 0; c < n; ++c) + hsv_to_rgb(&this->analyzed_colors[c], this->most_used_hue[c], this->most_used_sat[c], v); +} + + +static void *atmo_grab_loop (void *port_gen) { + post_video_port_t *port = (post_video_port_t *) port_gen; + atmo_post_plugin_t *this = (atmo_post_plugin_t *) port->post; + xine_video_port_t *video_port = port->original_port; + xine_ticket_t *ticket = this->post_plugin.running_ticket; + xine_grab_frame_t *frame = NULL; + int rc; + int grab_width, grab_height, analyze_width, analyze_height, overscan, img_size; + int last_analyze_width = 0, last_analyze_height = 0; + int alloc_img_size = 0; + int edge_weighting = 0; + int running = 1; + hsv_color_t *hsv_img = NULL; + int *weight = NULL; + struct timeval tvnow, tvlast, tvdiff; + useconds_t analyze_rate; + + _x_post_inc_usage(port); + + pthread_mutex_lock(&this->lock); + this->grab_running = &running; + pthread_mutex_unlock(&this->lock); + pthread_cond_broadcast(&this->thread_started); + + llprintf(LOG_1, "grab thread running\n"); + + ticket->acquire(ticket, 0); + + while (running) { + + /* allocate grab frame */ + if (!frame && xine_port_send_gui_data(video_port, XINE_GUI_SEND_ALLOC_GRAB_FRAME, &frame)) { + xine_log(this->post_plugin.xine, XINE_LOG_PLUGIN, "atmo: frame grabbing not supported by this video driver!\n"); + break; + } + + if (ticket->ticket_revoked) { + /* free grab frame */ + if (frame) { + xine_port_send_gui_data(video_port, XINE_GUI_SEND_FREE_GRAB_FRAME, frame); + frame = NULL; + } + llprintf(LOG_1, "grab thread waiting for new ticket\n"); + ticket->renew(ticket, 0); + llprintf(LOG_1, "grab thread got new ticket\n"); + continue; + } + + gettimeofday(&tvlast, NULL); + + /* get actual displayed image size */ + grab_width = video_port->get_property(video_port, VO_PROP_WINDOW_WIDTH); + grab_height = video_port->get_property(video_port, VO_PROP_WINDOW_HEIGHT); + if (grab_width > 0 && grab_height > 0) { + + /* calculate size of analyze image */ + analyze_width = (this->parm.analyze_size + 1) * 64; + analyze_height = (analyze_width * grab_height) / grab_width; + + /* calculate size of grab (sub) window */ + overscan = this->parm.overscan; + if (overscan) { + frame->crop_left = frame->crop_right = grab_width * overscan / 1000; + frame->crop_top = frame->crop_bottom = grab_height * overscan / 1000; + grab_width = grab_width - frame->crop_left - frame->crop_right; + grab_height = grab_height - frame->crop_top - frame->crop_bottom; + } else { + frame->crop_bottom = 0; + frame->crop_top = 0; + frame->crop_left = 0; + frame->crop_right = 0; + } + + /* grab displayed video frame */ + frame->timeout = GRAB_TIMEOUT; + frame->width = analyze_width; + frame->height = analyze_height; + if (!(rc = xine_port_send_gui_data(video_port, XINE_GUI_SEND_GRAB_FRAME, frame))) { + if (frame->width == analyze_width && frame->height == analyze_height) { + img_size = analyze_width * analyze_height; + + /* allocate hsv and weight images */ + if (img_size > alloc_img_size) { + free(hsv_img); + free(weight); + alloc_img_size = img_size; + hsv_img = (hsv_color_t *) malloc(img_size * sizeof(hsv_color_t)); + weight = (int *) malloc(img_size * this->num_channels.sum * sizeof(int)); + if (!hsv_img || !weight) + break; + last_analyze_width = 0; + last_analyze_height = 0; + edge_weighting = 0; + } + + /* calculate weight image */ + if (analyze_width != last_analyze_width || analyze_height != last_analyze_height || edge_weighting != this->parm.edge_weighting) { + edge_weighting = this->parm.edge_weighting; + last_analyze_width = analyze_width; + last_analyze_height = analyze_height; + calc_weight(this, weight, analyze_width, analyze_height, edge_weighting); + llprintf(LOG_1, "analyze size %dx%d, grab %dx%d@%d,%d\n", analyze_width, analyze_height, grab_width, grab_height, frame->crop_left, frame->crop_top); + } + + /* analyze grabbed image */ + calc_hsv_image(hsv_img, frame->img, img_size); + calc_hue_hist(this, hsv_img, weight, img_size); + calc_windowed_hue_hist(this); + calc_most_used_hue(this); + calc_sat_hist(this, hsv_img, weight, img_size); + calc_windowed_sat_hist(this); + calc_most_used_sat(this); + int v_avg = calc_average_brightness(hsv_img, img_size, this->parm.darkness_limit); + pthread_mutex_lock(&this->lock); + calc_rgb_values(this, v_avg); + pthread_mutex_unlock(&this->lock); + + llprintf(LOG_2, "grab %ld.%03ld: vpts=%ld\n", tvlast.tv_sec, tvlast.tv_usec / 1000, frame->vpts); + } + } else { + if (rc < 0) + llprintf(LOG_1, "grab failed!\n"); + if (rc > 0) + llprintf(LOG_2, "grab timed out!\n"); + } + } + + /* loop with analyze rate duration */ + analyze_rate = this->parm.analyze_rate * 1000; + gettimeofday(&tvnow, NULL); + timersub(&tvnow, &tvlast, &tvdiff); + if (tvdiff.tv_sec == 0 && tvdiff.tv_usec < analyze_rate) + usleep(analyze_rate - tvdiff.tv_usec); + } + + /* free grab frame */ + if (frame) + xine_port_send_gui_data(video_port, XINE_GUI_SEND_FREE_GRAB_FRAME, frame); + + ticket->release(ticket, 0); + + free(hsv_img); + free(weight); + + pthread_mutex_lock(&this->lock); + if (running) + this->grab_running = NULL; + pthread_mutex_unlock(&this->lock); + + _x_post_dec_usage(port); + + llprintf(LOG_1, "grab thread terminated\n"); + + return NULL; +} + + +static void reset_filters(atmo_post_plugin_t *this) { + this->old_mean_length = 0; +} + + +static void percent_filter(atmo_post_plugin_t *this) { + rgb_color_t *act = this->analyzed_colors; + rgb_color_t *out = this->filtered_colors; + const int old_p = this->parm.filter_smoothness; + const int new_p = 100 - old_p; + int n = this->num_channels.sum; + + while (n--) { + out->r = (act->r * new_p + out->r * old_p) / 100; + out->g = (act->g * new_p + out->g * old_p) / 100; + out->b = (act->b * new_p + out->b * old_p) / 100; + ++act; + ++out; + } +} + + +static void mean_filter(atmo_post_plugin_t *this) { + rgb_color_t *act = this->analyzed_colors; + rgb_color_t *out = this->filtered_colors; + rgb_color_t *mean_values = this->mean_filter_values; + rgb_color_sum_t *mean_sums = this->mean_filter_sum_values; + const int64_t mean_threshold = (int64_t) ((double) this->parm.filter_threshold * 3.6); + const int old_p = this->parm.filter_smoothness; + const int new_p = 100 - old_p; + int n = this->num_channels.sum; + const int64_t mean_length = (this->parm.filter_length < OUTPUT_RATE) ? 1: this->parm.filter_length / OUTPUT_RATE; + const int reinitialize = ((int)mean_length != this->old_mean_length); + this->old_mean_length = (int)mean_length; + + while (n--) { + mean_sums->r += (act->r - mean_values->r); + mean_values->r = (uint8_t) (mean_sums->r / mean_length); + + mean_sums->g += (act->g - mean_values->g); + mean_values->g = (uint8_t) (mean_sums->g / mean_length); + + mean_sums->b += (act->b - mean_values->b); + mean_values->b = (uint8_t) (mean_sums->b / mean_length); + + /* + * check, if there is a jump -> check if differences between actual values and filter values are too big + */ + int64_t dist = (int64_t)(mean_values->r - act->r) * (int64_t)(mean_values->r - act->r) + + (int64_t)(mean_values->g - act->g) * (int64_t)(mean_values->g - act->g) + + (int64_t)(mean_values->b - act->b) * (int64_t)(mean_values->b - act->b); + + if (dist > 0) + dist = (int64_t) sqrt((double) dist); + + /* compare calculated distance with the filter threshold */ + if (dist > mean_threshold || reinitialize) { + /* filter jump detected -> set the long filters to the result of the short filters */ + *out = *act; + *mean_values = *act; + mean_sums->r = act->r * mean_length; + mean_sums->g = act->g * mean_length; + mean_sums->b = act->b * mean_length; + } + else + { + /* apply an additional percent filter */ + out->r = (mean_values->r * new_p + out->r * old_p) / 100; + out->g = (mean_values->g * new_p + out->g * old_p) / 100; + out->b = (mean_values->b * new_p + out->b * old_p) / 100; + } + + ++act; + ++out; + ++mean_sums; + ++mean_values; + } +} + + +static void apply_white_calibration(atmo_post_plugin_t *this) { + const int wc_red = this->parm.wc_red; + const int wc_green = this->parm.wc_green; + const int wc_blue = this->parm.wc_blue; + if (wc_red == 255 && wc_green == 255 && wc_blue == 255) + return; + + rgb_color_t *out = this->output_colors; + int n = this->num_channels.sum; + while (n--) { + out->r = (uint8_t)((int)out->r * wc_red / 255); + out->g = (uint8_t)((int)out->g * wc_green / 255); + out->b = (uint8_t)((int)out->b * wc_blue / 255); + ++out; + } +} + + +static void apply_gamma_correction(atmo_post_plugin_t *this) { + if (!this->parm.gamma) + return; + + const double gamma = (double)this->parm.gamma / 10.0; + rgb_color_t *out = this->output_colors; + int n = this->num_channels.sum; + while (n--) { + out->r = (uint8_t)(pow((double)out->r / 255.0, gamma) * 255.0); + out->g = (uint8_t)(pow((double)out->g / 255.0, gamma) * 255.0); + out->b = (uint8_t)(pow((double)out->b / 255.0, gamma) * 255.0); + ++out; + } +} + + +static void *atmo_output_loop (void *port_gen) { + post_video_port_t *port = (post_video_port_t *) port_gen; + atmo_post_plugin_t *this = (atmo_post_plugin_t *) port->post; + xine_ticket_t *ticket = this->post_plugin.running_ticket; + output_driver_t *output_driver = this->output_driver; + int colors_size = this->num_channels.sum * sizeof(rgb_color_t); + int running = 1; + struct timeval tvnow, tvlast, tvdiff, tvfirst; + + _x_post_inc_usage(port); + + pthread_mutex_lock(&this->lock); + this->output_running = &running; + pthread_mutex_unlock(&this->lock); + pthread_cond_broadcast(&this->thread_started); + + llprintf(LOG_1, "output thread running\n"); + + ticket->acquire(ticket, 0); + reset_filters(this); + + gettimeofday(&tvfirst, NULL); + + while (running) { + + if (ticket->ticket_revoked) { + reset_filters(this); + llprintf(LOG_1, "output thread waiting for new ticket\n"); + ticket->renew(ticket, 0); + llprintf(LOG_1, "output thread got new ticket\n"); + continue; + } + + gettimeofday(&tvlast, NULL); + + /* Transfer analyzed colors into filtered colors */ + pthread_mutex_lock(&this->lock); + switch (this->parm.filter) { + case 1: + percent_filter(this); + break; + case 2: + mean_filter(this); + break; + default: + /* no filtering */ + memcpy(this->filtered_colors, this->analyzed_colors, colors_size); + } + pthread_mutex_unlock(&this->lock); + + /* Transfer filtered colors to output colors */ + memcpy(this->output_colors, this->filtered_colors, colors_size); + apply_gamma_correction(this); + apply_white_calibration(this); + + /* Output colors */ + gettimeofday(&tvnow, NULL); + timersub(&tvnow, &tvfirst, &tvdiff); + if ((tvdiff.tv_sec * 1000 + tvdiff.tv_usec / 1000) >= OUTPUT_START_DELAY) { + if (memcmp(this->output_colors, this->last_output_colors, colors_size)) { + output_driver->output_colors(output_driver, this->output_colors, this->last_output_colors); + memcpy(this->last_output_colors, this->output_colors, colors_size); + } + } + + /* Loop with output rate duration */ + const useconds_t output_rate = OUTPUT_RATE * 1000; + gettimeofday(&tvnow, NULL); + timersub(&tvnow, &tvlast, &tvdiff); + if (tvdiff.tv_sec == 0 && tvdiff.tv_usec < output_rate) + usleep(output_rate - tvdiff.tv_usec); + } + + /* Turn off Light */ + memset(this->output_colors, 0, colors_size); + if (memcmp(this->output_colors, this->last_output_colors, colors_size)) + output_driver->output_colors(output_driver, this->output_colors, this->last_output_colors); + memset(this->last_output_colors, 0, colors_size); + + ticket->release(ticket, 0); + + pthread_mutex_lock(&this->lock); + if (running) + this->output_running = NULL; + pthread_mutex_unlock(&this->lock); + + _x_post_dec_usage(port); + + llprintf(LOG_1, "output thread terminated\n"); + + return NULL; +} + + +static void config_channels(atmo_post_plugin_t *this) { + int n = this->num_channels.top + this->num_channels.bottom + this->num_channels.left + this->num_channels.right + + this->num_channels.center + + this->num_channels.top_left + this->num_channels.top_right + this->num_channels.bottom_left + this->num_channels.bottom_right; + this->num_channels.sum = n; + + if (n) + { + this->hue_hist = (uint64_t *) calloc(n * (h_MAX + 1), sizeof(uint64_t)); + this->w_hue_hist = (uint64_t *) calloc(n * (h_MAX + 1), sizeof(uint64_t)); + this->most_used_hue = (int *) calloc(n, sizeof(int)); + this->last_most_used_hue = (int *) calloc(n, sizeof(int)); + + this->sat_hist = (uint64_t *) calloc(n * (s_MAX + 1), sizeof(uint64_t)); + this->w_sat_hist = (uint64_t *) calloc(n * (s_MAX + 1), sizeof(uint64_t)); + this->most_used_sat = (int *) calloc(n, sizeof(int)); + + this->analyzed_colors = (rgb_color_t *) calloc(n, sizeof(rgb_color_t)); + this->filtered_colors = (rgb_color_t *) calloc(n, sizeof(rgb_color_t)); + this->output_colors = (rgb_color_t *) calloc(n, sizeof(rgb_color_t)); + this->last_output_colors = (rgb_color_t *) calloc(n, sizeof(rgb_color_t)); + this->mean_filter_values = (rgb_color_t *) calloc(n, sizeof(rgb_color_t)); + this->mean_filter_sum_values = (rgb_color_sum_t *) calloc(n, sizeof(rgb_color_sum_t)); + } + + llprintf(LOG_1, "configure channels top %d, bottom %d, left %d, right %d, center %d, topLeft %d, topRight %d, bottomLeft %d, bottomRight %d\n", + this->num_channels.top, this->num_channels.bottom, this->num_channels.left, this->num_channels.right, this->num_channels.center, + this->num_channels.top_left, this->num_channels.top_right, this->num_channels.bottom_left, this->num_channels.bottom_right); +} + + +static void free_channels(atmo_post_plugin_t *this) { + if (this->num_channels.sum) + { + free(this->hue_hist); + free(this->w_hue_hist); + free(this->most_used_hue); + free(this->last_most_used_hue); + + free(this->sat_hist); + free(this->w_sat_hist); + free(this->most_used_sat); + + free(this->analyzed_colors); + free(this->filtered_colors); + free(this->output_colors); + free(this->last_output_colors); + free(this->mean_filter_values); + free(this->mean_filter_sum_values); + } +} + + +static void start_threads(atmo_post_plugin_t *this, post_video_port_t *port) { + int err; + pthread_attr_t pth_attrs; + + pthread_attr_init(&pth_attrs); + pthread_attr_setscope(&pth_attrs, PTHREAD_SCOPE_SYSTEM); + pthread_attr_setdetachstate(&pth_attrs, PTHREAD_CREATE_DETACHED); + + pthread_mutex_lock(&this->lock); + if (this->grab_running == NULL) { + if ((err = pthread_create (&this->grab_thread, &pth_attrs, atmo_grab_loop, port))) + xprintf(this->post_plugin.xine, XINE_VERBOSITY_LOG, "atmo: can't create grab thread (%s)\n", strerror(err)); + else + pthread_cond_wait(&this->thread_started, &this->lock); + } + + if (this->output_running == NULL) { + if ((err = pthread_create (&this->output_thread, &pth_attrs, atmo_output_loop, port))) + xprintf(this->post_plugin.xine, XINE_VERBOSITY_LOG, "atmo: can't create output thread (%s)\n", strerror(err)); + else + pthread_cond_wait(&this->thread_started, &this->lock); + } + pthread_mutex_unlock(&this->lock); + + pthread_attr_destroy(&pth_attrs); +} + + +static void stop_threads(atmo_post_plugin_t *this) { + pthread_mutex_lock(&this->lock); + if (this->grab_running) { + *this->grab_running = 0; + this->grab_running = NULL; + } + if (this->output_running) { + *this->output_running = 0; + this->output_running = NULL; + } + pthread_mutex_unlock(&this->lock); +} + + +static void close_output_driver(atmo_post_plugin_t *this) { + + if (this->driver_opened) { + /* Switch all channels off */ + int colors_size = this->num_channels.sum * sizeof(rgb_color_t); + memset(this->output_colors, 0, colors_size); + if (memcmp(this->output_colors, this->last_output_colors, colors_size)) + this->output_driver->output_colors(this->output_driver, this->output_colors, this->last_output_colors); + + this->output_driver->close(this->output_driver); + this->driver_opened = 0; + llprintf(LOG_1, "output driver closed\n"); + } +} + + +/* + * Open/Close video port + */ + +static void atmo_video_open(xine_video_port_t *port_gen, xine_stream_t *stream) { + post_video_port_t *port = (post_video_port_t *)port_gen; + atmo_post_plugin_t *this = (atmo_post_plugin_t *) port->post; + num_channels_t num_channels; + int start = 1; + + this->port_open(port_gen, stream); + + num_channels.top = this->parm.top; + num_channels.bottom = this->parm.bottom; + num_channels.left = this->parm.left; + num_channels.right = this->parm.right; + num_channels.center = this->parm.center; + num_channels.top_left = this->parm.top_left; + num_channels.top_right = this->parm.top_right; + num_channels.bottom_left = this->parm.bottom_left; + num_channels.bottom_right = this->parm.bottom_right; + + if (this->driver != this->parm.driver || strcmp(this->driver_param, this->parm.driver_param)) + close_output_driver(this); + + /* open output driver */ + if (!this->driver_opened) { + this->driver = this->parm.driver; + strcpy(this->driver_param, this->parm.driver_param); + + if ((this->output_driver = get_output_driver(&this->output_drivers, this->driver)) == NULL) { + xine_log(this->post_plugin.xine, XINE_LOG_PLUGIN, "atmo: no valid output driver selected!\n"); + start = 0; + } else if (this->output_driver->open(this->output_driver, this->driver_param, &num_channels)) { + xine_log(this->post_plugin.xine, XINE_LOG_PLUGIN, "atmo: can't open output driver: %s!\n", this->output_driver->errmsg); + start = 0; + } else { + this->driver_opened = 1; + llprintf(LOG_1, "output driver opened\n"); + } + } else if (this->output_driver->configure(this->output_driver, &num_channels)) { + xine_log(this->post_plugin.xine, XINE_LOG_PLUGIN, "atmo: can't configure output driver: %s!\n", this->output_driver->errmsg); + start = 0; + } + + if (this->num_channels.top != num_channels.top || + this->num_channels.bottom != num_channels.bottom || + this->num_channels.left != num_channels.left || + this->num_channels.right != num_channels.right || + this->num_channels.center != num_channels.center || + this->num_channels.top_left != num_channels.top_left || + this->num_channels.top_right != num_channels.top_right || + this->num_channels.bottom_left != num_channels.bottom_left || + this->num_channels.bottom_right != num_channels.bottom_right) { + free_channels(this); + this->num_channels = num_channels; + config_channels(this); + } + + if (!this->num_channels.sum) + start = 0; + + if (start) { + /* send first initial color packet */ + this->output_driver->output_colors(this->output_driver, this->output_colors, NULL); + + start_threads(this, port); + } +} + + +static void atmo_video_close(xine_video_port_t *port_gen, xine_stream_t *stream) { + post_video_port_t *port = (post_video_port_t *)port_gen; + atmo_post_plugin_t *this = (atmo_post_plugin_t *) port->post; + + stop_threads(this); + + this->port_close(port_gen, stream); +} + + +/* + * Parameter functions + */ + +static xine_post_api_descr_t *atmo_get_param_descr(void) +{ + return &atmo_param_descr; +} + + +static int atmo_set_parameters(xine_post_t *this_gen, void *parm_gen) +{ + atmo_post_plugin_t *this = (atmo_post_plugin_t *)this_gen; + atmo_parameters_t *parm = (atmo_parameters_t *)parm_gen; + + this->parm = *parm; + llprintf(LOG_1, "set parameters\n"); + return 1; +} + + +static int atmo_get_parameters(xine_post_t *this_gen, void *parm_gen) +{ + atmo_post_plugin_t *this = (atmo_post_plugin_t *)this_gen; + atmo_parameters_t *parm = (atmo_parameters_t *)parm_gen; + + *parm = this->parm; + return 1; +} + + +static char *atmo_get_help(void) { + return _("The xine atmolight post plugin\n" + "Analyze video picture and generate output data for atmolight controllers\n" + "\n" + ); +} + + +/* + * open/close plugin + */ + +static void atmo_dispose(post_plugin_t *this_gen) +{ + if (_x_post_dispose(this_gen)) { + atmo_post_plugin_t *this = (atmo_post_plugin_t *) this_gen; + + close_output_driver(this); + free_channels(this); + pthread_mutex_destroy(&this->lock); + pthread_cond_destroy(&this->thread_started); + free(this); + } +} + + +static post_plugin_t *atmo_open_plugin(post_class_t *class_gen, + int inputs, + xine_audio_port_t **audio_target, + xine_video_port_t **video_target) +{ + post_in_t *input; + post_out_t *output; + post_video_port_t *port; + xine_post_in_t *input_param; + static xine_post_api_t post_api = + { atmo_set_parameters, atmo_get_parameters, + atmo_get_param_descr, atmo_get_help }; + + if (!video_target || !video_target[0]) + return NULL; + + atmo_post_plugin_t *this = (atmo_post_plugin_t *) calloc(1, sizeof(atmo_post_plugin_t)); + if (!this) + return NULL; + + _x_post_init(&this->post_plugin, 0, 1); + + port = _x_post_intercept_video_port(&this->post_plugin, video_target[0], &input, &output); + + input->xine_in.name = "video in"; + output->xine_out.name = "video out"; + + this->post_plugin.dispose = atmo_dispose; + + this->port_open = port->new_port.open; + this->port_close = port->new_port.close; + port->new_port.open = atmo_video_open; + port->new_port.close = atmo_video_close; + + this->post_plugin.xine_post.video_input[0] = &port->new_port; + + input_param = &this->parameter_input; + input_param->name = "parameters"; + input_param->type = XINE_POST_DATA_PARAMETERS; + input_param->data = &post_api; + xine_list_push_back(this->post_plugin.input, input_param); + + this->driver = -1; + pthread_mutex_init(&this->lock, NULL); + pthread_cond_init(&this->thread_started, NULL); + + /* Set default values for parameters */ + this->parm.overscan = 30; + this->parm.analyze_rate = 40; + this->parm.analyze_size = 1; + this->parm.brightness = 100; + this->parm.darkness_limit = 50; + this->parm.edge_weighting = 8; + this->parm.filter = 2; + this->parm.filter_length = 500; + this->parm.filter_smoothness = 50; + this->parm.filter_threshold = 40; + this->parm.hue_win_size = 3; + this->parm.sat_win_size = 3; + this->parm.wc_red = 255; + this->parm.wc_green = 255; + this->parm.wc_blue = 255; + this->parm.gamma = 0; + + return &this->post_plugin; +} + + +/* + * Plugin class + */ + +#if POST_PLUGIN_IFACE_VERSION < 10 +static char *atmo_get_identifier(post_class_t *class_gen) +{ + return "atmo"; +} + +static char *atmo_get_description(post_class_t *class_gen) +{ + return "Analyze video picture and generate output data for atmolight controllers"; +} + +static void atmo_class_dispose(post_class_t *class_gen) +{ + free(class_gen); +} +#endif + +static void *atmo_init_plugin(xine_t *xine, void *data) +{ + post_class_t *class = (post_class_t *) calloc(1, sizeof(post_class_t)); + + if(class) { + class->open_plugin = atmo_open_plugin; +#if POST_PLUGIN_IFACE_VERSION < 10 + class->get_identifier = atmo_get_identifier; + class->get_description = atmo_get_description; + class->dispose = atmo_class_dispose; +#else + class->identifier = "atmo"; + class->description = N_("Analyze video picture and generate output data for atmolight controllers"); + class->dispose = default_post_class_dispose; +#endif + } + return class; +} + + +static post_info_t info = { XINE_POST_TYPE_VIDEO_FILTER }; + +const plugin_info_t xine_plugin_info[] __attribute__((visibility("default"))) = +{ + /* type, API, "name", version, special_info, init_function */ + { PLUGIN_POST, POST_PLUGIN_IFACE_VERSION, "atmo", XINE_VERSION_CODE, &info, &atmo_init_plugin }, + { PLUGIN_NONE, 0, "", 0, NULL, NULL } +}; |
