From: Andre Noll Date: Sun, 20 Feb 2011 11:44:05 +0000 (+0100) Subject: Merge branch 'maint' X-Git-Tag: v0.4.6~41 X-Git-Url: http://git.tuebingen.mpg.de/?p=paraslash.git;a=commitdiff_plain;h=9417eae5ca2b9f10d25f769221e8fd91048bc68a;hp=4d5a9b90c3e9cbdff019776d05792beb57fbfa31 Merge branch 'maint' Conflicts: configure.ac --- diff --git a/.gitignore b/.gitignore index 0000ba41..d3a1fc5e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -*.[oa] +objects foo* bar* para_* @@ -30,4 +30,4 @@ web_sync confdefs.h conftest conftest.c - +GIT-VERSION-FILE diff --git a/COPYING.LIB b/COPYING.LIB new file mode 100644 index 00000000..cf9b6b99 --- /dev/null +++ b/COPYING.LIB @@ -0,0 +1,510 @@ + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations +below. + + When we speak of free software, we are referring to freedom of use, +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 and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. +^L + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it +becomes a de-facto standard. To achieve this, non-free programs must +be allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. +^L + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, 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 library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete 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 distribute a copy of this License along with the +Library. + + 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 Library or any portion +of it, thus forming a work based on the Library, 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) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +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 Library, 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 Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. +^L + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you 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. + + If distribution of 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 satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. +^L + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least + three years, to give the same user the materials specified in + Subsection 6a, above, for a charge no more than the cost of + performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be 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. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. +^L + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library 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. + + 9. 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 Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +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 with +this License. +^L + 11. 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 Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library 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 Library. + +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. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library 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. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser 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 Library +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 Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. +^L + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +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 + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. 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 LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS +^L + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms +of the ordinary General Public License). + + To apply these terms, attach the following notices to the library. +It is safest to attach them to the start of each source file to most +effectively convey the exclusion of warranty; and each file should +have at least the "copyright" line and a pointer to where the full +notice is found. + + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or +your school, if any, to sign a "copyright disclaimer" for the library, +if necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James + Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/CREDITS b/CREDITS index e5f12ac4..1d8ad0ce 100644 --- a/CREDITS +++ b/CREDITS @@ -14,6 +14,8 @@ Karl Bartel (SFont) Gerd Becker (MacOs testing) +Fabrice Bellard (FFmpeg) + Lorenzo Bettini (gengetopt) Ricardo Cerqueira (mp3info) @@ -34,12 +36,16 @@ Robert Leslie (libmad) Ian McDonald (dccp example code) +Loren Merritt (FFmpeg) + Simon Morlat (ortp) Christof Müller (bug reports) M. Hari Nezumi (AudioCompress) +Michael Niedermayer (FFmpeg) + Manuel Odendahl (poc) Guillaume Outters (mosx-mpg123) @@ -56,3 +62,5 @@ Cedric Tefft (mp3info) Linus Torvalds (for giving us one hell of an operating system [quote taken from README.linux for DOOM v1.666]) + +Jean-Marc Valin (speex) diff --git a/Doxyfile b/Doxyfile index 43209a52..fcfe4043 100644 --- a/Doxyfile +++ b/Doxyfile @@ -1,4 +1,4 @@ -# Doxyfile 1.5.3 +# Doxyfile 1.6.3 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project @@ -14,198 +14,215 @@ # Project related configuration options #--------------------------------------------------------------------------- -# This tag specifies the encoding used for all characters in the config file that -# follow. The default is UTF-8 which is also the encoding used for all text before -# the first occurrence of this tag. Doxygen uses libiconv (or the iconv built into -# libc) for the transcoding. See http://www.gnu.org/software/libiconv for the list of -# possible encodings. +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. DOXYFILE_ENCODING = UTF-8 -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. PROJECT_NAME = paraslash -# The PROJECT_NUMBER tag can be used to enter a project or revision number. -# This could be handy for archiving the generated documentation or +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or # if some version control system is used. -PROJECT_NUMBER = +PROJECT_NUMBER = -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) -# base path where the generated documentation will be put. -# If a relative path is entered, it will be relative to the location +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = web_sync/doxygen -# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create -# 4096 sub-directories (in 2 levels) under the output directory of each output -# format and will distribute the generated files over these directories. -# Enabling this option can be useful when feeding doxygen a huge amount of -# source files, where putting all generated files in the same directory would +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# The default language is English, other supported languages are: -# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, -# Croatian, Czech, Danish, Dutch, Finnish, French, German, Greek, Hungarian, -# Italian, Japanese, Japanese-en (Japanese with English messages), Korean, -# Korean-en, Lithuanian, Norwegian, Polish, Portuguese, Romanian, Russian, -# Serbian, Slovak, Slovene, Spanish, Swedish, and Ukrainian. +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. OUTPUT_LANGUAGE = English -# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will -# include brief member descriptions after the members that are listed in -# the file and class documentation (similar to JavaDoc). +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES -# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend -# the brief description of a member or function before the detailed description. -# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES -# This tag implements a quasi-intelligent brief description abbreviator -# that is used to form the text in various listings. Each string -# in this list, if found as the leading text of the brief description, will be -# stripped from the text and the result after processing the whole list, is -# used as the annotated text. Otherwise, the brief description is used as-is. -# If left blank, the following values are used ("$name" is automatically -# replaced with the name of the entity): "The $name class" "The $name widget" -# "The $name file" "is" "provides" "specifies" "contains" +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" -ABBREVIATE_BRIEF = +ABBREVIATE_BRIEF = -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# Doxygen will generate a detailed section even if there is only a brief +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all -# inherited members of a class in the documentation of that class as if those -# members were ordinary class members. Constructors, destructors and assignment +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = NO -# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full -# path before files name in the file list and in the header files. If set +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = YES -# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag -# can be used to strip a user-defined part of the path. Stripping is -# only done if one of the specified strings matches the left-hand part of -# the path. The tag can be used to show relative paths in the file list. -# If left blank the directory from which doxygen is run is used as the +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the # path to strip. -STRIP_FROM_PATH = +STRIP_FROM_PATH = -# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of -# the path mentioned in the documentation of a class, which tells -# the reader which header file to include in order to use a class. -# If left blank only the name of the header file containing the class -# definition is used. Otherwise one should specify the include paths that +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. -STRIP_FROM_INC_PATH = +STRIP_FROM_INC_PATH = -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter -# (but less readable) file names. This can be useful is your file systems +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful is your file systems # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO -# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen -# will interpret the first line (until the first dot) of a JavaDoc-style -# comment as the brief description. If set to NO, the JavaDoc -# comments will behave just like regular Qt-style comments +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments # (thus requiring an explicit @brief command for a brief description.) JAVADOC_AUTOBRIEF = YES -# If the QT_AUTOBRIEF tag is set to YES then Doxygen will -# interpret the first line (until the first dot) of a Qt-style -# comment as the brief description. If set to NO, the comments -# will behave just like regular Qt-style comments (thus requiring +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring # an explicit \brief command for a brief description.) QT_AUTOBRIEF = NO -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen -# treat a multi-line C++ special comment block (i.e. a block of //! or /// -# comments) as a brief description. This used to be the default behaviour. -# The new default is to treat a multi-line C++ comment block as a detailed +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO -# If the DETAILS_AT_TOP tag is set to YES then Doxygen -# will output the detailed description near the top, like JavaDoc. -# If set to NO, the detailed description appears after the member -# documentation. - -DETAILS_AT_TOP = NO - -# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented -# member inherits the documentation from any documented member that it +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES -# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce -# a new page for each member. If set to NO, the documentation of a member will +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO -# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 8 -# This tag can be used to specify a number of aliases that acts -# as commands in the documentation. An alias has the form "name=value". -# For example adding "sideeffect=\par Side Effects:\n" will allow you to -# put the command \sideeffect (or @sideeffect) in the documentation, which -# will result in a user-defined paragraph with heading "Side Effects:". +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. -ALIASES = +ALIASES = -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C -# sources only. Doxygen will then generate output that is more tailored for C. -# For instance, some of the names that are used will be different. The list +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = YES -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java -# sources only. Doxygen will then generate output that is more tailored for Java. -# For instance, namespaces will be presented as packages, qualified scopes -# will look different, etc. +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO -# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to -# include (a tag file for) the STL sources as input, then you should -# set this tag to YES in order to let doxygen match functions declarations and -# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. -# func(std::string) {}). This also make the inheritance and collaboration +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it parses. +# With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this tag. +# The format is ext=language, where ext is a file extension, and language is one of +# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP, +# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat +# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. Note that for custom extensions you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = NO @@ -215,309 +232,390 @@ BUILTIN_STL_SUPPORT = NO CPP_CLI_SUPPORT = NO -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES, then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen to replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO -# Set the SUBGROUPING tag to YES (the default) to allow class member groups of -# the same type (for instance a group of public functions) to be put as a -# subgroup of that type (e.g. under the Public Functions section). Set it to -# NO to prevent subgrouping. Alternatively, this can be done per class using +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penality. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will rougly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- -# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in -# documentation are documented, even if no documentation was available. -# Private class members and static file members will be hidden unless +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = YES -# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = NO -# If the EXTRACT_STATIC tag is set to YES all static members of a file +# If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = NO -# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) -# defined locally in source files will be included in the documentation. +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. -EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_CLASSES = NO -# This flag is only useful for Objective-C code. When set to YES local -# methods, which are defined in the implementation section but not in -# the interface are included in the documentation. +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO -# If this flag is set to YES, the members of anonymous namespaces will be extracted -# and appear in the documentation as a namespace called 'anonymous_namespace{file}', -# where file will be replaced with the base name of the file that contains the anonymous -# namespace. By default anonymous namespace are hidden. +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespace are hidden. EXTRACT_ANON_NSPACES = NO -# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all -# undocumented members of documented classes, files or namespaces. -# If set to NO (the default) these members will be included in the -# various overviews, but no documentation section is generated. +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = NO -# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. -# If set to NO (the default) these classes will be included in the various +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = NO -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all -# friend (class|struct|union) declarations. -# If set to NO (the default) these declarations will be included in the +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO -# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any -# documentation blocks found inside the body of a function. -# If set to NO (the default) these blocks will be appended to the +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO -# The INTERNAL_DOCS tag determines if documentation -# that is typed after a \internal command is included. If the tag is set -# to NO (the default) then the documentation will be excluded. +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO -# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate -# file names in lower-case letters. If set to YES upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = YES -# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen -# will show members with their full class and namespace scopes in the +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = YES -# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen -# will put a list of the files that are included by a file in the documentation +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES -# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES -# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen -# will sort the (detailed) documentation of file and class members -# alphabetically by member name. If set to NO the members will appear in +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = NO -# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the -# brief documentation of file, namespace and class members alphabetically -# by member name. If set to NO (the default) the members will appear in +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = NO -# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be -# sorted by fully-qualified names, including namespaces. If set to -# NO (the default), the class list will be sorted only by class name, -# not including the namespace part. +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the (brief and detailed) documentation of class members so that constructors and destructors are listed first. If set to NO (the default) the constructors will appear in the respective orders defined by SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. -# Note: This option applies only to the class list, not to the +# Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO -# The GENERATE_TODOLIST tag can be used to enable (YES) or -# disable (NO) the todo list. This list is created by putting \todo +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = YES -# The GENERATE_TESTLIST tag can be used to enable (YES) or -# disable (NO) the test list. This list is created by putting \test +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES -# The GENERATE_BUGLIST tag can be used to enable (YES) or -# disable (NO) the bug list. This list is created by putting \bug +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or -# disable (NO) the deprecated list. This list is created by putting +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST= YES -# The ENABLED_SECTIONS tag can be used to enable conditional +# The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. -ENABLED_SECTIONS = +ENABLED_SECTIONS = -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines -# the initial value of a variable or define consists of for it to appear in -# the documentation. If the initializer consists of more lines than specified -# here it will be hidden. Use a value of 0 to hide initializers completely. -# The appearance of the initializer of individual variables and defines in the -# documentation can be controlled using \showinitializer or \hideinitializer +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or define consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and defines in the +# documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated -# at the bottom of the documentation of classes and structs. If set to YES the +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES -# If the sources in your project are distributed over multiple directories -# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy # in the documentation. The default is NO. SHOW_DIRECTORIES = NO -# The FILE_VERSION_FILTER tag can be used to specify a program or script that -# doxygen should invoke to get the current version for each file (typically from the -# version control system). Doxygen will invoke the program by executing (via -# popen()) the command , where is the value of -# the FILE_VERSION_FILTER tag, and is the name of an input file -# provided by doxygen. Whatever the program writes to standard output +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. -FILE_VERSION_FILTER = +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by +# doxygen. The layout file controls the global structure of the generated output files +# in an output format independent way. The create the layout file that represents +# doxygen's defaults, run doxygen with the -l option. You can optionally specify a +# file name after the option, if omitted DoxygenLayout.xml will be used as the name +# of the layout file. + +LAYOUT_FILE = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- -# The QUIET tag can be used to turn on/off the messages that are generated +# The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = YES -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated by doxygen. Possible values are YES and NO. If left blank +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES -# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings -# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = YES -# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some -# parameters in a documented function, or documenting parameters that +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES -# This WARN_NO_PARAMDOC option can be abled to get warnings for -# functions that are documented, but have no documentation for their parameters -# or return value. If set to NO (the default) doxygen will only warn about -# wrong or incomplete parameter documentation, but not about the absence of +# This WARN_NO_PARAMDOC option can be abled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = YES -# The WARN_FORMAT tag determines the format of the warning messages that -# doxygen can produce. The string should contain the $file, $line, and $text -# tags, which will be replaced by the file and line number from which the -# warning originated and the warning text. Optionally the format may contain -# $version, which will be replaced by the version of the file (if it could +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text " -# The WARN_LOGFILE tag can be used to specify a file to which warning -# and error messages should be written. If left blank the output is written +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written # to stderr. -WARN_LOGFILE = +WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- -# The INPUT tag can be used to specify the files and/or directories that contain -# documented source files. You may enter file names like "myfile.cpp" or -# directories like "/usr/src/myproject". Separate the files or directories +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories # with spaces. INPUT = . -# This tag can be used to specify the character encoding of the source files that -# doxygen parses. Internally doxygen uses the UTF-8 encoding, which is also the default -# input encoding. Doxygen uses libiconv (or the iconv built into libc) for the transcoding. -# See http://www.gnu.org/software/libiconv for the list of possible encodings. +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. INPUT_ENCODING = UTF-8 -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank the following patterns are tested: -# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx -# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 FILE_PATTERNS = *.c \ *.h -# The RECURSIVE tag can be used to turn specify whether or not subdirectories -# should be searched for input files as well. Possible values are YES and NO. +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = NO -# The EXCLUDE tag can be used to specify files and/or directories that should -# excluded from the INPUT source files. This way you can easily exclude a +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. EXCLUDE = error2.h -# The EXCLUDE_SYMLINKS tag can be used select whether or not files or -# directories that are symbolic links (a Unix filesystem feature) are excluded +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem feature) are excluded # from the input. EXCLUDE_SYMLINKS = NO -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. Note that the wildcards are matched -# against the file with absolute path, so to exclude all test directories +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* EXCLUDE_PATTERNS = *.cmdline.* \ @@ -526,60 +624,64 @@ EXCLUDE_PATTERNS = *.cmdline.* \ fade.c \ config.h -# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names -# (namespaces, classes, functions, etc.) that should be excluded from the output. -# The symbol name can be a fully qualified name, a word, or if the wildcard * is used, -# a substring. Examples: ANamespace, AClass, AClass::ANamespace, ANamespace::*Test +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test -EXCLUDE_SYMBOLS = +EXCLUDE_SYMBOLS = -# The EXAMPLE_PATH tag can be used to specify one or more files or -# directories that contain example code fragments that are included (see +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see # the \include command). -EXAMPLE_PATH = +EXAMPLE_PATH = -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left # blank all files are included. -EXAMPLE_PATTERNS = +EXAMPLE_PATTERNS = -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude -# commands irrespective of the value of the RECURSIVE tag. +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO -# The IMAGE_PATH tag can be used to specify one or more files or -# directories that contain image that are included in the documentation (see +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see # the \image command). -IMAGE_PATH = +IMAGE_PATH = -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command , where -# is the value of the INPUT_FILTER tag, and is the name of an -# input file. Doxygen will then use the output that the filter program writes -# to standard output. If FILTER_PATTERNS is specified, this tag will be +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be # ignored. -INPUT_FILTER = +INPUT_FILTER = -# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern -# basis. Doxygen will compare the file name with each pattern and apply the -# filter if there is a match. The filters are a list of the form: -# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further -# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER # is applied to all files. -FILTER_PATTERNS = +FILTER_PATTERNS = -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will be used to filter the input files when producing source +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO @@ -588,34 +690,32 @@ FILTER_SOURCE_FILES = NO # configuration options related to source browsing #--------------------------------------------------------------------------- -# If the SOURCE_BROWSER tag is set to YES then a list of source files will -# be generated. Documented entities will be cross-referenced with these sources. -# Note: To get rid of all source code in the generated output, make sure also -# VERBATIM_HEADERS is set to NO. If you have enabled CALL_GRAPH or CALLER_GRAPH -# then you must also enable this option. If you don't then doxygen will produce -# a warning and turn it on anyway +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = YES -# Setting the INLINE_SOURCES tag to YES will include the body +# Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO -# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct -# doxygen to hide any special comment blocks from generated source code +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code # fragments. Normal C and C++ comments will always remain visible. STRIP_CODE_COMMENTS = YES -# If the REFERENCED_BY_RELATION tag is set to YES (the default) -# then for each documented function all documented +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = YES -# If the REFERENCES_RELATION tag is set to YES (the default) -# then for each documented function all documented entities +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = YES @@ -623,20 +723,21 @@ REFERENCES_RELATION = YES # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will -# link to the source code. Otherwise they will link to the documentstion. +# link to the source code. +# Otherwise they will link to the documentation. REFERENCES_LINK_SOURCE = YES -# If the USE_HTAGS tag is set to YES then the references to source code -# will point to the HTML generated by the htags(1) tool instead of doxygen -# built-in source browser. The htags tool is part of GNU's global source -# tagging system (see http://www.gnu.org/software/global/global.html). You +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = YES -# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen -# will generate a verbatim copy of the header file for each class for +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES @@ -645,287 +746,424 @@ VERBATIM_HEADERS = YES # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index -# of all compounds will be generated. Enable this if the project +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project # contains a lot of classes, structs, unions or interfaces. ALPHABETICAL_INDEX = NO -# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then -# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 -# In case all classes in a project start with a common prefix, all -# classes will be put under the same header in the alphabetical index. -# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. -IGNORE_PREFIX = +IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- -# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for -# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html -# The HTML_HEADER tag can be used to specify a personal HTML header for -# each generated HTML page. If it is left blank doxygen will generate a +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a # standard header. HTML_HEADER = web/header2.html -# The HTML_FOOTER tag can be used to specify a personal HTML footer for -# each generated HTML page. If it is left blank doxygen will generate a +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = web/footer.html -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading -# style sheet that is used by each HTML page. It can be used to -# fine-tune the look of the HTML output. If the tag is left blank doxygen -# will generate a default style sheet. Note that doxygen will try to copy -# the style sheet file to the HTML output directory, so don't put your own +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own # stylesheet in the HTML output directory as well, or it will be erased! -HTML_STYLESHEET = +HTML_STYLESHEET = -# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, -# files or namespaces will be aligned in HTML using tables. If set to -# NO a bullet list will be used. +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. -HTML_ALIGN_MEMBERS = YES +HTML_TIMESTAMP = YES -# If the GENERATE_HTMLHELP tag is set to YES, additional index files -# will be generated that can be used as input for tools like the -# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) -# of the generated HTML documentation. +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. -GENERATE_HTMLHELP = NO +HTML_ALIGN_MEMBERS = YES -# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML -# documentation will contain sections that can be hidden and shown after the -# page has loaded. For this to work a browser that supports -# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox # Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). HTML_DYNAMIC_SECTIONS = NO -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can -# be used to specify the file name of the resulting .chm file. You -# can add a path in front of the file if the result should not be +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be # written to the html output directory. -CHM_FILE = +CHM_FILE = -# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can -# be used to specify the location (absolute path including file name) of -# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. -HHC_LOCATION = +HHC_LOCATION = -# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag -# controls if a separate .chi index file is generated (YES) or that +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO -# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag -# controls whether a binary table of contents is generated (YES) or a +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO -# The TOC_EXPAND flag can be set to YES to add extra items for group members +# The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO -# The DISABLE_INDEX tag can be used to turn on/off the condensed index at -# top of each HTML page. The value NO (the default) enables the index and +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER +# are set, an additional index file will be generated that can be used as input for +# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated +# HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add. +# For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's +# filter section matches. +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. DISABLE_INDEX = NO -# This tag can be used to set the number of enum values (range [1..20]) +# This tag can be used to set the number of enum values (range [1..20]) # that doxygen will group on one line in the generated HTML documentation. ENUM_VALUES_PER_LINE = 4 -# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be -# generated containing a tree-like index structure (just like the one that -# is generated for HTML Help). For this to work a browser that supports -# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, -# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are -# probably better off using the HTML help feature. +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. GENERATE_TREEVIEW = NO -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be -# used to set the initial width (in pixels) of the frame in which the tree +# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list. + +USE_INLINE_TREES = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be implemented using a PHP enabled web server instead of at the web client using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server based approach is that it scales better to large projects and allows full text search. The disadvances is that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- -# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = NO -# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex -# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. LATEX_CMD_NAME = latex -# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to -# generate index for LaTeX. If left blank `makeindex' will be used as the +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex -# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact -# LaTeX documents. This may be useful for small projects and may help to +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO -# The PAPER_TYPE tag can be used to set the paper type that is used -# by the printer. Possible values are: a4, a4wide, letter, legal and +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, a4wide, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = a4wide -# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. -EXTRA_PACKAGES = +EXTRA_PACKAGES = -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for -# the generated latex document. The header should contain everything until -# the first chapter. If it is left blank doxygen will generate a +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! -LATEX_HEADER = +LATEX_HEADER = -# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated -# is prepared for conversion to pdf (using ps2pdf). The pdf file will -# contain links (just like the HTML output) instead of page references +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = NO -# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of -# plain latex in the generated Makefile. Set this option to YES to get a +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = NO -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. -# command to the generated LaTeX files. This will instruct LaTeX to keep -# running if errors occur, instead of asking the user for help. +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO -# If LATEX_HIDE_INDICES is set to YES then doxygen will not -# include the index chapters (such as File Index, Compound Index, etc.) +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO +# If LATEX_SOURCE_CODE is set to YES then doxygen will include source code with syntax highlighting in the LaTeX output. Note that which sources are shown also depends on other settings such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- -# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output -# The RTF output is optimized for Word 97 and may not look very pretty with +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO -# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf -# If the COMPACT_RTF tag is set to YES Doxygen generates more compact -# RTF documents. This may be useful for small projects and may help to +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO -# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated -# will contain hyperlink fields. The RTF file will -# contain links (just like the HTML output) instead of page references. -# This makes the output suitable for online browsing using WORD or other -# programs which support those fields. +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO -# Load stylesheet definitions from file. Syntax is similar to doxygen's -# config file, i.e. a series of assignments. You only have to provide +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. -RTF_STYLESHEET_FILE = +RTF_STYLESHEET_FILE = -# Set optional variables used in the generation of an rtf document. +# Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. -RTF_EXTENSIONS_FILE = +RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- -# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO -# The MAN_OUTPUT tag is used to specify where the man pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man -# The MAN_EXTENSION tag determines the extension that is added to +# The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 -# If the MAN_LINKS tag is set to YES and Doxygen generates man output, -# then it will generate one additional man file for each entity -# documented in the real man page(s). These additional files -# only source the real man page, but without them the man command +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO @@ -934,33 +1172,33 @@ MAN_LINKS = NO # configuration options related to the XML output #--------------------------------------------------------------------------- -# If the GENERATE_XML tag is set to YES Doxygen will -# generate an XML file that captures the structure of +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO -# The XML_OUTPUT tag is used to specify where the XML pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml -# The XML_SCHEMA tag can be used to specify an XML schema, -# which can be used by a validating XML parser to check the +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the # syntax of the XML files. -XML_SCHEMA = +XML_SCHEMA = -# The XML_DTD tag can be used to specify an XML DTD, -# which can be used by a validating XML parser to check the +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the # syntax of the XML files. -XML_DTD = +XML_DTD = -# If the XML_PROGRAMLISTING tag is set to YES Doxygen will -# dump the program listings (including syntax highlighting -# and cross-referencing information) to the XML output. Note that +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES @@ -969,10 +1207,10 @@ XML_PROGRAMLISTING = YES # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- -# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will -# generate an AutoGen Definitions (see autogen.sf.net) file -# that captures the structure of the code including all -# documentation. Note that this feature is still experimental +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO @@ -981,323 +1219,343 @@ GENERATE_AUTOGEN_DEF = NO # configuration options related to the Perl module output #--------------------------------------------------------------------------- -# If the GENERATE_PERLMOD tag is set to YES Doxygen will -# generate a Perl module file that captures the structure of -# the code including all documentation. Note that this -# feature is still experimental and incomplete at the +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO -# If the PERLMOD_LATEX tag is set to YES Doxygen will generate -# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO -# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be -# nicely formatted so it can be parsed by a human reader. This is useful -# if you want to understand what is going on. On the other hand, if this -# tag is set to NO the size of the Perl module output will be much smaller +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES -# The names of the make variables in the generated doxyrules.make file -# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. -# This is useful so different doxyrules.make files included by the same +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. -PERLMOD_MAKEVAR_PREFIX = +PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- -# Configuration options related to the preprocessor +# Configuration options related to the preprocessor #--------------------------------------------------------------------------- -# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will -# evaluate all C-preprocessor directives found in the sources and include +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES -# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro -# names in the source code. If set to NO (the default) only conditional -# compilation will be performed. Macro expansion can be done in a controlled +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = NO -# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES -# then the macro expansion is limited to the macros specified with the +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO -# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # in the INCLUDE_PATH (see below) will be search if a #include is found. SEARCH_INCLUDES = YES -# The INCLUDE_PATH tag can be used to specify one or more directories that -# contain include files that are not input files but should be processed by +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by # the preprocessor. -INCLUDE_PATH = +INCLUDE_PATH = -# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard -# patterns (like *.h and *.hpp) to filter out the header-files in the -# directories. If left blank, the patterns specified with FILE_PATTERNS will +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. -INCLUDE_FILE_PATTERNS = +INCLUDE_FILE_PATTERNS = -# The PREDEFINED tag can be used to specify one or more macro names that -# are defined before the preprocessor is started (similar to the -D option of -# gcc). The argument of the tag is a list of macros of the form: name -# or name=definition (no spaces). If the definition and the = are -# omitted =1 is assumed. To prevent a macro definition from being -# undefined via #undef or recursively expanded use the := operator +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator # instead of the = operator. PREDEFINED = HAVE_MAD \ - HAVE_FAAD \ + HAVE_FAAD \ HAVE_OGGVORBIS \ __GNUC__=4 \ - __GNUC_MINOR__=4 + __GNUC_MINOR__=4 \ + HAVE_UCRED -# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then -# this tag can be used to specify a list of macro names that should be expanded. -# The macro definition that is found in the sources will be used. +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition. -EXPAND_AS_DEFINED = +EXPAND_AS_DEFINED = -# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then -# doxygen's preprocessor will remove all function-like macros that are alone -# on a line, have an all uppercase name, and do not end with a semicolon. Such -# function macros are typically used for boiler-plate code, and will confuse +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and will confuse # the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- -# Configuration::additions related to external references +# Configuration::additions related to external references #--------------------------------------------------------------------------- -# The TAGFILES option can be used to specify one or more tagfiles. -# Optionally an initial location of the external documentation -# can be added for each tagfile. The format of a tag file without -# this location is as follows: -# TAGFILES = file1 file2 ... -# Adding location for the tag files is done as follows: -# TAGFILES = file1=loc1 "file2 = loc2" ... -# where "loc1" and "loc2" can be relative or absolute paths or -# URLs. If a location is present for each tag, the installdox tool +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool # does not have to be run to correct the links. # Note that each tag file must have a unique name # (where the name does NOT include the path) -# If a tag file is not located in the directory in which doxygen +# If a tag file is not located in the directory in which doxygen # is run, you must also specify the path to the tagfile here. -TAGFILES = +TAGFILES = -# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. -GENERATE_TAGFILE = +GENERATE_TAGFILE = -# If the ALLEXTERNALS tag is set to YES all external classes will be listed -# in the class index. If set to NO only the inherited external classes +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO -# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES -# The PERL_PATH should be the absolute path and name of the perl script +# The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- -# Configuration options related to the dot tool +# Configuration options related to the dot tool #--------------------------------------------------------------------------- -# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will -# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base -# or super classes. Setting the tag to NO turns the diagrams off. Note that -# this option is superseded by the HAVE_DOT option below. This is only a -# fallback. It is recommended to install and use dot, since it yields more +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option is superseded by the HAVE_DOT option below. This is only a +# fallback. It is recommended to install and use dot, since it yields more # powerful graphs. CLASS_DIAGRAMS = YES -# You can define message sequence charts within doxygen comments using the \msc -# command. Doxygen will then run the mscgen tool (see http://www.mcternan.me.uk/mscgen/) to -# produce the chart and insert it in the documentation. The MSCGEN_PATH tag allows you to -# specify the directory where the mscgen tool resides. If left empty the tool is assumed to -# be found in the default search path. +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. -MSCGEN_PATH = +MSCGEN_PATH = -# If set to YES, the inheritance and collaboration graphs will hide -# inheritance and usage relations if the target is undocumented +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES -# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is -# available from the path. This tool is part of Graphviz, a graph visualization -# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = NO -# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect inheritance relations. Setting this tag to YES will force the +# By default doxygen will write a font called FreeSans.ttf to the output +# directory and reference it in all dot files that doxygen generates. This +# font does not include all possible unicode characters however, so when you need +# these (or just want a differently looking font) you can specify the font name +# using DOT_FONTNAME. You need need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the # the CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES -# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect implementation dependencies (inheritance, containment, and +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES -# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES -# If the UML_LOOK tag is set to YES doxygen will generate inheritance and -# collaboration diagrams in a style similar to the OMG's Unified Modeling +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO -# If set to YES, the inheritance and collaboration graphs will show the +# If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT -# tags are set to YES then doxygen will generate a graph for each documented -# file showing the direct and indirect include dependencies of the file with +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and -# HAVE_DOT tags are set to YES then doxygen will generate a graph for each -# documented header file showing the documented files that directly or +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES -# If the CALL_GRAPH, SOURCE_BROWSER and HAVE_DOT tags are set to YES then doxygen will -# generate a call dependency graph for every global function or class method. -# Note that enabling this option will significantly increase the time of a run. -# So in most cases it will be better to enable call graphs for selected -# functions only using the \callgraph command. +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. CALL_GRAPH = NO -# If the CALLER_GRAPH, SOURCE_BROWSER and HAVE_DOT tags are set to YES then doxygen will -# generate a caller dependency graph for every global function or class method. -# Note that enabling this option will significantly increase the time of a run. -# So in most cases it will be better to enable caller graphs for selected -# functions only using the \callergraph command. +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. CALLER_GRAPH = NO -# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES -# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES -# then doxygen will show the dependencies a directory has on other directories +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES -# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are png, jpg, or gif # If left blank png will be used. DOT_IMAGE_FORMAT = png -# The tag DOT_PATH can be used to specify the path where the dot tool can be +# The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. -DOT_PATH = +DOT_PATH = -# The DOTFILE_DIRS tag can be used to specify one or more directories that -# contain dot files that are included in the documentation (see the +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the # \dotfile command). -DOTFILE_DIRS = +DOTFILE_DIRS = -# The MAX_DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of -# nodes that will be shown in the graph. If the number of nodes in a graph -# becomes larger than this value, doxygen will truncate the graph, which is -# visualized by representing a node as a red box. Note that doxygen if the number -# of direct children of the root node in a graph is already larger than -# MAX_DOT_GRAPH_NOTES then the graph will not be shown at all. Also note +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note # that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. DOT_GRAPH_MAX_NODES = 50 -# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the -# graphs generated by dot. A depth value of 3 means that only nodes reachable -# from the root by following a path via at most 3 edges will be shown. Nodes -# that lay further from the root node will be omitted. Note that setting this -# option to 1 or 2 may greatly reduce the computation time needed for large -# code bases. Also note that the size of a graph can be further restricted by +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. MAX_DOT_GRAPH_DEPTH = 0 -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, which results in a white background. -# Warning: Depending on the platform used, enabling this option may lead to -# badly anti-aliased labels on the edges of a graph (i.e. they become hard to -# read). +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). DOT_TRANSPARENT = NO -# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output -# files in one run (i.e. multiple -o and -T options on the command line). This -# makes dot run faster, but since only newer versions of dot (>1.8.10) +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = NO -# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will -# generate a legend page explaining the meaning of the various boxes and +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES -# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will -# remove the intermediate dot files that are used to generate +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES - -#--------------------------------------------------------------------------- -# Configuration::additions related to the search engine -#--------------------------------------------------------------------------- - -# The SEARCHENGINE tag specifies whether or not a search engine should be -# used. If set to NO the values of all tags below this one will be ignored. - -SEARCHENGINE = YES diff --git a/FEATURES b/FEATURES index d054dfd1..5b5f4699 100644 --- a/FEATURES +++ b/FEATURES @@ -5,7 +5,7 @@ Features * Runs on Linux, Mac OS, FreeBSD, NetBSD, Solaris and probably other Unixes - * Mp3, ogg vorbis, aac(m4a) support + * Mp3, ogg/vorbis, ogg/speex, aac (m4a) and wma support * Local or remote http, dccp, and udp network audio streaming * IPv6 support * Forward error correction allows receivers to recover from packet losses diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN new file mode 100755 index 00000000..a64d7961 --- /dev/null +++ b/GIT-VERSION-GEN @@ -0,0 +1,45 @@ +#!/bin/sh + +if test $# -ne 1; then + echo >&2 "usage: $0 " + exit 1 +fi + +GVF="$1" +DEF_VER="unnamed_version" + +LF=' +' + +# First see if there is a version file (included in release tarballs), +# then try git-describe, then default. +if test -f VERSION +then + VN=$(cat VERSION) || VN="$DEF_VER" +elif test -d .git -o -f .git && + VN=$(git describe --abbrev=4 HEAD 2>/dev/null) && + case "$VN" in + *$LF*) (exit 1) ;; + v[0-9]*) + git update-index -q --refresh + test -z "$(git diff-index --name-only HEAD --)" || + VN="$VN-dirty" ;; + esac +then + VN=$(echo "$VN" | sed -e 's/-/./g'); +else + VN="$DEF_VER" +fi + +VN=$(expr "$VN" : v*'\(.*\)') + +if test -r $GVF +then + VC=$(sed -e 's/^GIT_VERSION = //' <$GVF) +else + VC=unset +fi +test "$VN" = "$VC" || { + echo >&2 "GIT_VERSION = $VN" + echo "GIT_VERSION = $VN" >$GVF +} diff --git a/INSTALL b/INSTALL index 09a2616f..0f4c18b6 100644 --- a/INSTALL +++ b/INSTALL @@ -1,255 +1,13 @@ -INSTALL -======= - ----- Any knowledge of how to work with mouse and icons is not required. ---------------------------- -Install all needed packages ---------------------------- -See -<< - REQUIREMENTS ->> -for a list of required software. You don't need everything listed -there. In particular, mp3, ogg vorbis and aac support are all -optional. The configure script will detect what is installed on your -system and will only try to build those executables that can be built -with your setup. - -Note that no special library (not even the mp3 decoding library libmad) -is needed for para_server if you only want to stream mp3 files. -Also, it's fine to use para_server on a box without sound card as -para_server only sends the audio stream to connected clients. - -------------------------- -Install server and client -------------------------- - -Install the package on all machines, you'd like this software to run on: - - (./configure && make) > /dev/null - -There should be no errors but probably some warnings about missing -software packages which usually implies that not all audio formats will -be supported. If headers or libs are installed at unusual locations -you might need to tell the configure script where to find them. Try - - ./configure --help - -to see a list of options. If the paraslash package was compiled -successfully, execute as root, - - make install - ------------------------------------ -Setup user list and create rsa keys ------------------------------------ - -If you already have your rsa keys, skip this step. If you are new -to paraslash, you have to generate an rsa key pair for each user you -want to allow to connect. You need at least one user. - -Let's assume that you'd like to run the server on host server_host -as user foo, and that you want to connect from client_host as user bar. - -As foo@server_host, create ~/.paraslash/server.users by typing the -following commands: - - user=bar - target=~/.paraslash/server.users - key=~/.paraslash/key.pub.$user - perms=AFS_READ,AFS_WRITE,VSS_READ,VSS_WRITE - mkdir -p ~/.paraslash - echo "user $user $key $perms" >> $target - -This gives "bar" the full privileges. - -Change to the "bar" account on client_host and generate the key-pair -with the commands - - key=~/.paraslash/key.$LOGNAME - mkdir -p ~/.paraslash - (umask 077 && openssl genrsa -out $key) - -Next, extract its public part: - - pubkey=~/.paraslash/key.pub.$LOGNAME - openssl rsa -in $key -pubout -out $pubkey - -and copy the public key just created to server_host (you may -skip this step for a single-user setup, i.e. if foo=bar and -server_host=client_host): - - scp $pubkey foo@server_host:.paraslash/ - -Finally, tell para_client to connect to server_host: - - conf=~/.paraslash/client.conf - echo 'hostname server_host' > $conf - ------------------ -Start para_server ------------------ - -Before starting the server make sure you have write permissions to -the directory /var/paraslash. - - sudo chown $LOGNAME /var/paraslash - -Alternatively, use the --afs_socket Option to specify a different -location for the afs command socket. - -For this first try, we'll use the info loglevel to make the output -of para_server more verbose. - - para_server -l info - -Now you can use para_client to connect to the server and issue -commands. Open a new shell (as "bar" on "client_host" in the above -example) and try - - para_client help - para_client si - -to retrieve the list of available commands and some server info. -Don't proceed if this doesn't work. - -------------------- -Create the database -------------------- - - para_client init - -This creates some empty tables under ~/.paraslash/afs_database. -You normally don't need to look at these tables, but it's good -to know that you can start from scratch with - - rm -rf ~/.paraslash/afs_database - -in case something went wrong. - -Next, you need to fill the audio file table of that database with -contents so that para_server knows about your audio files. Choose an -absolute path to a directory containing some audio files and add them -to the audio file table: - - para_client add /my/mp3/dir - -This might take a while, so it is a good idea to start with a directory -containing not too many audio files. Note that the table only contains -data about the audio files found, not the files themselves. - -Print a list of all audio files found with - - para_client ls - ------------------------- -Start streaming manually ------------------------- - - para_client play - para_client stat 2 - -This starts streaming and dumps some information about the current -audio file to stdout. - -You should now be able to receive the stream and listen to it. If -you have mpg123 or xmms handy, execute on client_host - - mpg123 http://server_host:8000/ -or - xmms http://server_host:8000/ - -Paraslash comes with its own receiving and playing software, which -will be described next. Try the following on client_host (assuming -Linux/ALSA and an mp3 stream): - - para_recv -l info -r 'http -i server_host' > file.mp3 - # (interrupt with CTRL+C after a few seconds) - ls -l file.mp3 # should not be empty - para_filter -f mp3dec -f wav < file.mp3 > file.wav - ls -l file.wav # should be much bigger than file.mp3 - para_write -w alsa < file.wav - -If this works, proceed. Otherwise double check what is logged by -para_server and use the --loglevel option of para_recv, para_filter -and para_write to increase verbosity. - -Next, put the pieces together: - - para_recv -r 'http -i server_host' \ - | para_filter -f mp3dec -f wav \ - | para_write -w alsa - ---------------------- -Configure para_audiod ---------------------- - -In order to automatically start the right decoder at the right time -and to offer to the clients some information on the current audio -stream and on paraslash's internal state, you should run the local -audio daemon, para_audiod, on every machine in your network which is -supposed to play the audio stream. Try - - para_audiod -h - -for help. Usually you have to specify only server_host as the receiver -specifier for each supported audio format, like this: - - para_audiod -l info -r 'mp3:http -i server_host' - -The preferred way to use para_audiod is to run it once at system start -as an unprivileged user. para_audiod needs to create a "well-known" -socket for the clients to connect to. The default path for this -socket is - - /var/paraslash/audiod_socket.$HOSTNAME - -so the /var/paraslash directory should be writable for the user who -runs para_audiod. - -If you want to change the location of the socket, use the --socket -option for para_audiod or the config file ~/.paraslash/audiod.conf -to change the default. Note that in this case you'll also have to -specify the same value for para_audioc's --socket option. - -If para_server is playing, you should be able to listen to the audio -stream as soon as para_audiod is started. Once it is running, try - - para_audioc stat - -That should dump some information to stdout. Other commands include +From tarball: - para_audioc off - para_audioc on - para_audioc sb - para_audioc term - para_audioc cycle + ./configure && make && sudo make install --------------- -Start para_gui --------------- +From git: -para_gui reads the output of "para_audioc stat" and displays that -information in a curses window. It also allows you to bind keys to -arbitrary commands. There are several flavours of key-bindings: + ./autogen.sh && sudo make install - - internal: These are the built-in commands that can not be - changed (help, quit, loglevel, version...). - - external: Shutdown curses before launching the given command. - Useful for starting other ncurses programs from within - para_gui, e.g. aumix or dialog scripts. Or, use the mbox - output format to write a mailbox containing one mail for each - (admissible) file the audio file selector knows about. Then - start mutt from within para_gui to browse your collection! - - display: Launch the command and display its stdout in - para_gui's bottom window. - - para: Like display, but start "para_client " instead of "". +For details see the user manual: -This concludes the installation notes. Next thing you might to have a look -at is how to use paraslash's audio file selector. See -<< - README.afs ->> + http://paraslash.systemlinux.org/manual.html diff --git a/Makefile.in b/Makefile.in index d04d0ea3..3fbbce21 100644 --- a/Makefile.in +++ b/Makefile.in @@ -8,12 +8,13 @@ MANDIR := @datarootdir@/man/man1 PACKAGE_VERSION := @PACKAGE_VERSION@ PACKAGE_STRING := @PACKAGE_STRING@ install_sh := @install_sh@ +cmdline_dir := @cmdline_dir@ build_date := $(shell date) uname_s := $(shell uname -s 2>/dev/null || echo "UNKNOWN_OS") uname_rs := $(shell uname -rs) cc_version := $(shell $(CC) --version | head -n 1) -codename := imaginary radiation +codename := deterministic entropy DEBUG_CPPFLAGS += -Wno-sign-compare -g -Wunused -Wundef -W DEBUG_CPPFLAGS += -Wredundant-decls @@ -55,151 +56,253 @@ CPPFLAGS += -DMAIN_INPUT_FILE_IS_$(*F) CPPFLAGS += @SSL_CPPFLAGS@ CPPFLAGS += @ncurses_cppflags@ CPPFLAGS += @arch_cppflags@ +CPPFLAGS += -I/usr/local/include +CPPFLAGS += -I$(cmdline_dir) +CPPFLAGS += @osl_cppflags@ +CPPFLAGS += -DGIT_VERSION='"$(GIT_VERSION)"' -BINARIES = para_server para_client para_audioc para_recv \ - para_filter para_write para_fsck para_afh @extra_binaries@ -man_binaries := $(BINARIES) -man_pages := $(patsubst %, man/man1/%.1, $(man_binaries)) -man_pages_in := $(patsubst %, web/%.man.in.html, $(man_binaries)) +man_pages := $(patsubst %, man/man1/%.1, @executables@) +man_pages_in := $(patsubst %, web/%.man.in.html, @executables@) ggo_dir := ggo +object_dir := objects +man_dir := man/man1 -m4_ggos := afh audioc audiod client filter fsck gui recv server write -all_ggos := $(m4_ggos) dccp_recv oggdec_filter alsa_write oss_write fade http_recv \ +m4_ggos := afh audioc audiod client filter gui recv server write +all_ggos := $(m4_ggos) dccp_recv alsa_write oss_write fade http_recv \ osx_write udp_recv amp_filter compress_filter file_write \ - grab_client mp3dec_filter -ggo_generated := $(addsuffix .cmdline.c, $(all_ggos)) $(addsuffix .cmdline.h, $(all_ggos)) \ - $(addsuffix .ggo, $(addprefix $(ggo_dir)/,$(m4_ggos))) + mp3dec_filter prebuffer_filter +ggo_generated := $(addsuffix .ggo, $(addprefix $(ggo_dir)/,$(m4_ggos))) +cmdline_generated := $(addprefix $(cmdline_dir)/,$(addsuffix .cmdline.c, $(all_ggos)) \ + $(addsuffix .cmdline.h, $(all_ggos))) autocrap := config.h.in configure tarball_pfx := @PACKAGE_TARNAME@-$(PACKAGE_VERSION) -tarball_delete = web versions pics .changelog_before_cvs .changelog_cvs .gitignore -tarball_delete := $(patsubst %,$(tarball_pfx)/%,$(tarball_delete)) -tarball_add := $(ggo_generated) $(autocrap) +tarball_delete := $(addprefix $(tarball_pfx)/,\ + web versions .changelog_before_cvs .changelog_cvs .gitignore\ + $(ggo_dir) skencil) tarball := @PACKAGE_TARNAME@-$(PACKAGE_VERSION).tar.bz2 -.PHONY: clean distclean maintainer-clean install man tarball -all: $(BINARIES) $(man_pages) +# To put more focus on warnings, be less verbose as default +# Use 'make V=1' to see the full commands +ifdef V + ifeq ("$(origin V)", "command line") + BUILD_VERBOSE = $(V) + endif +endif +ifndef BUILD_VERBOSE + BUILD_VERBOSE = 0 +endif +ifeq ($(BUILD_VERBOSE),1) + Q = +else + Q = @ +endif + +.PHONY: all clean distclean maintainer-clean install man tarball\ + .FORCE-GIT-VERSION-FILE +all: @executables@ $(man_pages) man: $(man_pages) tarball: $(tarball) -*.o: para.h config.h gcc-compat.h +GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE + @./GIT-VERSION-GEN GIT-VERSION-FILE +-include GIT-VERSION-FILE +@executables@: GIT-VERSION-FILE -include Makefile.deps -include $(ggo_dir)/makefile - -%_command_list.c %_command_list.h: %.cmd - ./command_util.sh c < $< >$@ - ./command_util.sh h < $< >$(@:%.c=%.h) +-include $(ggo_dir)/makefile +%_command_list.c: %.cmd + @[ -z "$(Q)" ] || echo 'GEN $@' + $(Q) ./command_util.sh c < $< >$@ +%_command_list.h: %.cmd + @[ -z "$(Q)" ] || echo 'GEN $@' + $(Q) ./command_util.sh h < $< >$@ %_command_list.man: %.cmd - ./command_util.sh man < $< > $@ - -server_command_lists = server_command_list.man afs_command_list.man -man/man1/para_server.1: para_server $(server_command_lists) - mkdir -p man/man1 - opts="-h --detailed-help -N `for i in $(server_command_lists); do printf "%s\n" "-i $$i"; done`"; \ + @[ -z "$(Q)" ] || echo 'GEN $@' + $(Q) ./command_util.sh man < $< > $@ + +server_command_lists_ch = server_command_list.c afs_command_list.c \ + server_command_list.h afs_command_list.h +server_command_lists_man = server_command_list.man afs_command_list.man +man/man1/para_server.1: para_server $(server_command_lists_man) | $(man_dir) + @[ -z "$(Q)" ] || echo 'HELP2MAN $<' + $(Q) opts="-h --detailed-help -N `for i in $(server_command_lists_man); do printf "%s\n" "-i $$i"; done`"; \ help2man $$opts ./para_server > $@ -man/man1/para_audiod.1: para_audiod audiod_command_list.man - mkdir -p man/man1 - help2man -h --detailed-help -N -i audiod_command_list.man ./para_audiod > $@ +man/man1/para_audiod.1: para_audiod audiod_command_list.man | $(man_dir) + @[ -z "$(Q)" ] || echo 'HELP2MAN $<' + $(Q) help2man -h --detailed-help -N -i audiod_command_list.man ./para_audiod > $@ -man/man1/%.1: % - mkdir -p man/man1 - help2man -h --detailed-help -N ./$< > $@ +man/man1/%.1: % | $(man_dir) + @[ -z "$(Q)" ] || echo 'HELP2MAN $<' + $(Q) help2man -h --detailed-help -N ./$< > $@ man/html/%.html: man/man1/%.1 - mkdir -p man/html - man2html $< > $@ + @[ -z "$(Q)" ] || echo 'MAN2HTML $<' + $(Q) mkdir -p man/html + $(Q) man2html $< > $@ web/%.man.in.html: man/man1/%.1 - man2html $< | sed -e '/^<\/BODY>/,$$d' -e '1,/<\/HEAD>/d' > $@ - - -oggdec_filter.o: oggdec_filter.c - $(CC) -c $(CPPFLAGS) $(DEBUG_CPPFLAGS) @oggvorbis_cppflags@ $< -ogg_afh.o: ogg_afh.c - $(CC) -c $(CPPFLAGS) $(DEBUG_CPPFLAGS) @oggvorbis_cppflags@ $< - -mp3dec_filter.o: mp3dec_filter.c - $(CC) -c $(CPPFLAGS) $(DEBUG_CPPFLAGS) @mad_cppflags@ $< - -aacdec_filter.o: aacdec_filter.c - $(CC) -c $(CPPFLAGS) $(DEBUG_CPPFLAGS) @faad_cppflags@ $< - -aac_common.o: aac_common.c - $(CC) -c $(CPPFLAGS) $(DEBUG_CPPFLAGS) @faad_cppflags@ $< - -aac_afh.o: aac_afh.c - $(CC) -c $(CPPFLAGS) $(DEBUG_CPPFLAGS) @faad_cppflags@ $< - -%.cmdline.o: %.cmdline.c - $(CC) -c $(CPPFLAGS) $< + @[ -z "$(Q)" ] || echo 'MAN2HTML $<' + $(Q) mkdir -p man/html + $(Q) man2html $< | sed -e '/^<\/BODY>/,$$d' -e '1,/<\/HEAD>/d' > $@ + +$(object_dir): + mkdir -p $@ +$(man_dir): + mkdir -p $@ + +$(object_dir)/spx_common.o: spx_common.c | $(object_dir) + @[ -z "$(Q)" ] || echo 'CC $<' + $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @ogg_cppflags@ $< + +$(object_dir)/spxdec_filter.o: spxdec_filter.c | $(object_dir) + @[ -z "$(Q)" ] || echo 'CC $<' + $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @ogg_cppflags@ $< + +$(object_dir)/spx_afh.o: spx_afh.c | $(object_dir) + @[ -z "$(Q)" ] || echo 'CC $<' + $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @ogg_cppflags@ $< + +$(object_dir)/oggdec_filter.o: oggdec_filter.c | $(object_dir) + @[ -z "$(Q)" ] || echo 'CC $<' + $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @ogg_cppflags@ $< + +$(object_dir)/ogg_afh.o: ogg_afh.c | $(object_dir) + @[ -z "$(Q)" ] || echo 'CC $<' + $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @ogg_cppflags@ $< + +$(object_dir)/ogg_afh_common.o: ogg_afh_common.c | $(object_dir) + @[ -z "$(Q)" ] || echo 'CC $<' + $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @ogg_cppflags@ $< + +$(object_dir)/mp3dec_filter.o: mp3dec_filter.c | $(object_dir) + @[ -z "$(Q)" ] || echo 'CC $<' + $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @mad_cppflags@ $< + +$(object_dir)/aacdec_filter.o: aacdec_filter.c | $(object_dir) + @[ -z "$(Q)" ] || echo 'CC $<' + $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @faad_cppflags@ $< + +$(object_dir)/aac_common.o: aac_common.c | $(object_dir) + @[ -z "$(Q)" ] || echo 'CC $<' + $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @faad_cppflags@ $< + +$(object_dir)/aac_afh.o: aac_afh.c | $(object_dir) + @[ -z "$(Q)" ] || echo 'CC $<' + $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) @faad_cppflags@ $< + +$(object_dir)/%.cmdline.o: $(cmdline_dir)/%.cmdline.c $(cmdline_dir)/%.cmdline.h | $(object_dir) + @[ -z "$(Q)" ] || echo 'CC $<' + $(Q) $(CC) -c $(CPPFLAGS) -Wno-unused-function -o $@ $< + +$(object_dir)/%.o: %.c | $(object_dir) + @[ -z "$(Q)" ] || echo 'CC $<' + $(Q) $(CC) -c -o $@ $(CPPFLAGS) $(DEBUG_CPPFLAGS) $< + +$(object_dir)/%.cmdline.d: $(cmdline_dir)/%.cmdline.c | $(object_dir) + @[ -z "$(Q)" ] || echo 'DEP $<' + $(Q) ./depend.sh $(object_dir) $(cmdline_dir) $(CPPFLAGS) $< > $@ + +$(object_dir)/%.d: %.c | $(object_dir) + @[ -z "$(Q)" ] || echo 'DEP $<' + $(Q) ./depend.sh $(object_dir) $(cmdline_dir) $(CPPFLAGS) $< > $@ + +recv_objs := $(addprefix $(object_dir)/, @recv_objs@) +filter_objs := $(addprefix $(object_dir)/, @filter_objs@) +client_objs := $(addprefix $(object_dir)/, @client_objs@) +gui_objs := $(addprefix $(object_dir)/, @gui_objs@) +audiod_objs := $(addprefix $(object_dir)/, @audiod_objs@) +audioc_objs := $(addprefix $(object_dir)/, @audioc_objs@) +fade_objs := $(addprefix $(object_dir)/, @fade_objs@) +server_objs := $(addprefix $(object_dir)/, @server_objs@) +write_objs := $(addprefix $(object_dir)/, @write_objs@) +afh_objs := $(addprefix $(object_dir)/, @afh_objs@) + +all_objs := $(recv_objs) $(filter_objs) $(client_objs) $(gui_objs) \ + $(audiod_objs) $(audioc_objs) $(fade_objs) $(server_objs) \ + $(write_objs) $(afh_objs) + +ifeq ($(findstring clean, $(MAKECMDGOALS)),) +-include $(all_objs:.o=.d) +endif -%.o: %.c - $(CC) -c $(CPPFLAGS) $(DEBUG_CPPFLAGS) $< +para_recv: $(recv_objs) + @[ -z "$(Q)" ] || echo 'LD $@' + $(Q) $(CC) $(LDFLAGS) $(recv_objs) -o $@ @recv_ldflags@ -para_recv: @recv_objs@ - $(CC) $(LDFLAGS) @recv_objs@ -o $@ @recv_ldflags@ +para_filter: $(filter_objs) + @[ -z "$(Q)" ] || echo 'LD $@' + $(Q) $(CC) $(LDFLAGS) $(filter_objs) -o $@ @filter_ldflags@ -para_filter: @filter_objs@ - $(CC) $(LDFLAGS) @filter_objs@ -o $@ @filter_ldflags@ +para_client: $(client_objs) + @[ -z "$(Q)" ] || echo 'LD $@' + $(Q) $(CC) $(LDFLAGS) -o $@ $(client_objs) @client_ldflags@ -para_client: @client_objs@ - $(CC) $(LDFLAGS) -o $@ @client_objs@ @client_ldflags@ +para_gui: $(gui_objs) + @[ -z "$(Q)" ] || echo 'LD $@' + $(Q) $(CC) $(LDFLAGS) -o $@ $(gui_objs) -lncurses -para_gui: @gui_objs@ - $(CC) $(LDFLAGS) -o $@ @gui_objs@ -lncurses +para_audiod: audiod_command_list.c audiod_command_list.h $(audiod_objs) + @[ -z "$(Q)" ] || echo 'LD $@' + $(Q) $(CC) $(LDFLAGS) -o $@ $(audiod_objs) @audiod_ldflags@ -para_audiod: @audiod_objs@ - $(CC) $(LDFLAGS) -o $@ @audiod_objs@ @audiod_ldflags@ +para_audioc: $(audioc_objs) + @[ -z "$(Q)" ] || echo 'LD $@' + $(Q) $(CC) $(LDFLAGS) -o $@ $(audioc_objs) @audioc_ldflags@ -para_audioc: @audioc_objs@ - $(CC) $(LDFLAGS) -o $@ @audioc_objs@ @audioc_ldflags@ +para_fade: $(fade_objs) + @[ -z "$(Q)" ] || echo 'LD $@' + $(Q) $(CC) $(LDFLAGS) -o $@ $(fade_objs) @fade_ldflags@ -para_fade: @fade_objs@ - $(CC) $(LDFLAGS) -o $@ @fade_objs@ @fade_ldflags@ +para_server: $(server_command_lists_ch) $(server_objs) + @[ -z "$(Q)" ] || echo 'LD $@' + $(Q) $(CC) $(LDFLAGS) -o $@ $(server_objs) @server_ldflags@ -para_server: @server_objs@ - $(CC) $(LDFLAGS) -o $@ @server_objs@ @server_ldflags@ +para_write: $(write_objs) + @[ -z "$(Q)" ] || echo 'LD $@' + $(Q) $(CC) $(LDFLAGS) -o $@ $(write_objs) @write_ldflags@ -para_fsck: @fsck_objs@ - $(CC) $(LDFLAGS) -o $@ @fsck_objs@ @fsck_ldflags@ +para_afh: $(afh_objs) + @[ -z "$(Q)" ] || echo 'LD $@' + $(Q) $(CC) $(LDFLAGS) -o $@ $(afh_objs) @afh_ldflags@ -para_write: @write_objs@ - $(CC) $(LDFLAGS) -o $@ @write_objs@ @write_ldflags@ +clean: + @[ -z "$(Q)" ] || echo 'CLEAN' + $(Q) rm -f @executables@ $(object_dir)/*.o -para_afh: @afh_objs@ - $(CC) $(LDFLAGS) -o $@ @afh_objs@ @afh_ldflags@ +clean2: clean + @[ -z "$(Q)" ] || echo 'CLEAN2' + $(Q) rm -rf man $(object_dir) + $(Q) rm -f *_command_list.* -clean: - rm -f *.o $(BINARIES) - rm -rf man -distclean: clean - rm -f Makefile autoscan.log config.status config.log && \ - rm -rf web/sync/* autom4te.cache aclocal.m4 - rm -f GPATH GRTAGS GSYMS GTAGS +distclean: clean2 + @[ -z "$(Q)" ] || echo 'DISTCLEAN' + $(Q) rm -f Makefile autoscan.log config.status config.log + $(Q) rm -rf autom4te.cache aclocal.m4 + $(Q) rm -f GPATH GRTAGS GSYMS GTAGS maintainer-clean: distclean rm -f $(ggo_generated) *.tar.bz2 \ config.h configure \ config.h.in skencil/*.pdf skencil/*.ps - rm -f *_command_list.* *.man man/man1/* - rm -rf web_sync + rm -rf web_sync $(cmdline_dir) install: all man mkdir -p $(BINDIR) $(MANDIR) - $(install_sh) -s -m 755 $(BINARIES) $(BINDIR) + $(install_sh) -s -m 755 @executables@ $(BINDIR) $(install_sh) -m 644 $(man_pages) $(MANDIR) mkdir -p $(VARDIR) >/dev/null 2>&1 || true # not fatal, so don't complain -@PACKAGE_TARNAME@-$(PACKAGE_VERSION).tar.bz2: $(tarball_add) +$(tarball): $(cmdline_generated) rm -rf $(tarball_pfx).tar.bz2 $(tarball_pfx) git archive --format=tar --prefix=$(tarball_pfx)/ HEAD \ | tar --delete $(tarball_delete) > $(tarball_pfx).tar - mkdir $(tarball_pfx) - cp -r $(tarball_add) $(tarball_pfx) + mkdir -p $(tarball_pfx)/$(cmdline_dir) + echo $(GIT_VERSION) > $(tarball_pfx)/VERSION + cp -r $(autocrap) $(tarball_pfx) + cp -r $(cmdline_generated) $(tarball_pfx)/$(cmdline_dir) tar rf $(tarball_pfx).tar $(tarball_pfx)/* rm -rf $(tarball_pfx) bzip2 -9 $(tarball_pfx).tar diff --git a/NEWS b/NEWS index 66ce1f3f..149f4e40 100644 --- a/NEWS +++ b/NEWS @@ -1,5 +1,97 @@ -NEWS -==== +----------------------------------------------- +0.4.6 (to be announced) "deterministic entropy" +----------------------------------------------- + + - For DCCP/OGG streams the audio file header is only sent once + at the beginning of the stream rather than periodically + every five seconds. This reduces network traffic and the + FEC group size. + - aacdec error message cleanups + - simplified color error handling + +-------------------------------------------- +0.4.5 (2010-12-17) "symmetric randomization" +-------------------------------------------- + +Bug fixes, internal cleanups and variable-sized FEC slices. + + - Contains a fix for an invalid-free-bug in the ogg audio format + handler code. + - Switching off the DCCP sender works again. + - para_audiod handles crashes of para_server more robustly. + - Internal scheduler and writer cleanups. + - Reduced latency due to variable-sized FEC slices. + - Improved documentation and error diagnostics. + - The build of para_server is now optional, allowing the build + to succeed in case libosl is not installed. + +------------------------------------------ +0.4.4 (2010-08-06) "persistent regularity" +------------------------------------------ + +Support for yet another audio format, para_write improvements and +bug fixes. + + - Support for the speex codec. + - Support for sample formats other than 16 bit little endian. + - error2.h is now created by a perl script which speeds up configure + considerably. + - Fix a bug in the aac decoder which could lead to segfaults in + para_filter/para_audiod. + - Fixes for autoconf-2.66. + +---------------------------------------- +0.4.3 (2010-07-05) "imaginary radiation" +---------------------------------------- + +Many improvements for the DCCP and the UDP transport, the new user +manual and the usual mix of bug fixes and internal improvements. + + - FEC support for the DCCP sender (Gerrit Renker). The new + --dccp_max_slice_size, --dccp_data_slices_per_group and + --dccp_slices_per_group options can be used to set the FEC + parameters for the DCCP transport. + - DNS lookups for UDP targets (Gerrit Renker). + - The new user manual replaces the README, README.afs, REQUIREMENTS + and INSTALL documents. + - Fix an end-of-file detection bug in the oggdec filter. + - The new nonblock API. + - Both options of the oggdec filter have been removed. + - New debug mode for the internal scheduler. + +------------------------------------------ +0.4.2 (2010-04-23) "associative expansion" +------------------------------------------ + +It's been some time since the last release, but finally here is +paraslash-0.4.2. The bulk of the changes comes from the new buffer +tree API, but there are changes all over the tree. Mainly performance +and usability improvements, but also quite some bug fixes. + + - The new buffer tree API. + - DCCP: Support for CCID negotiation (Gerrit Renker). + - UDP robustness fixes. + - The --bufsize option for mp3dec is gone as it no longer makes sense + for the new buffer tree API. + - Fix audible buffer underruns for wma streams. + - The alsa writer no longer prints meaningless underrun durations. + - audiod: Defaults work also for udp streams. If no filter is + given for an audio format that is received via upd, fecdec is + automatically added as the first filter (along with the decoder). + +--------------------------------------- +0.4.1 (2009-12-22) "concurrent horizon" +--------------------------------------- + +Support for another audio format, minor feature enhancements and lots of bug +fixes. All fixes that have been accumulated in the maint branch (in particular +those mentionened in the 0.3.6 release notes) appear in this release as well. + + - wma support. + - new afh option: --human to activate human-readable output. + - new server/audiod option: --log-timing to print timing information. + - build system improvements. + - source code documentation updates. ------------------------------------- 0.3.6 (2009-12-07) "cubic continuity" @@ -19,6 +111,28 @@ another 0.3.x release. No new features. - http/dccp: Do not send the audio file header twice. - FEC: Timing improvements. +---------------------------------------------- +0.4.0 (2009-11-10) "simultaneous independence" +---------------------------------------------- + +Two significant changes which require the new version number: The +improved authentication dialog and the fact that the database code +has been moved to a library, libosl. To use the new version, you have +to generate new RSA keys, see INSTALL for details. A shell script is +provided for conversion of the 0.3 database to the new 0.4 format. + + - stronger crypto for client authentication + - the database code has been moved to a library + - improved status item handling + - cleanup of the build system + - The "-V" option now also prints the git version + - the new parser-friendly listing mode for the ls and stat commands + - mandatory rc4 encryption + - major audio format handler cleanups + - (id3,...) tags are no longer stored as a combined string in the database + - new mood methods: artist_matches, title_matches, comment_matches, + album_matches, year_maches, year. + -------------------------------------------- 0.3.5 (2009-09-21) "symplectic separability" -------------------------------------------- diff --git a/README b/README index 6cf34f27..e991b734 100644 --- a/README +++ b/README @@ -6,166 +6,10 @@ Paraslash is an acronym for _Play, archive, rate and stream large audio sets happily_ -It contains the following programs: +The paraslash package contains server and client software for +network streaming as well as stand-alone utilities for decoding +mp3, ogg, aac and wma files. See the user manual for details. ------------ -para_server ------------ - -para_server streams binary audio data (mp3/oggvorbis/m4a files) -over local and/or remote networks. It listens on a tcp port and -accepts commands such as play, stop, pause, next from authenticated -clients. However, there are many more commands. - -It supports three built-in network streaming methods (senders): http, dccp, -or udp. - - * The http sender is recommended for public streams that can be played - by any player like mpg123, xmms, itunes, winamp... - - * The dccp sender requires kernel support for the datagram congestion - control protocol. - - * The udp sender is recommended for multicast LAN streaming. - -It is possible to activate more than one sender simultaneously. - -The built-in audio file selector of paraslash is used to manage your -audio files. It maintains statistics on the usage of all available audio -files such as last played time, and the number of times each file was -selected. - -Its features include - - * attributes. Allow fine-grained audio file selection. - - * image table. For storing e.g. album cover art. - - * lyrics table. For storing lyrics. - - * playlist table. Stores arbitrary many playlists for later use. - - * mood mode. Audio file selection works by specifying mood - methods involving attributes, pattern matching for file names - and more. This allows rather sophisticated configurations - and is explained in more detail in -<< - README.afs ->> - - * rename detection. If files are moved or renamed, afs will - recognize them despite of this change. - -Despite of all these features, paraslash is lightweight. The -stripped binary of para_server with all its features compiled in -mp3/ogg/aac support, http/dccp/udp support) is about 160K on i386 -under Linux. para_audiod (see below) is even smaller. - ------------ -para_client ------------ - -The client program to connect to para_server. paraslash commands -are sent to para_server and the response is dumped to stdout. This -can be used by any scripting language to produce user interfaces with -little programming effort. - -All connections between para_server and para_client are encrypted by -default. For each user of paraslash you must create a public/secret -RSA key pair for authentication. The authenticated connection is -encrypted with a symmetric rc4 session key. - ---------- -para_recv ---------- - -A command line http/dccp/udp stream grabber. The http mode of this -tool can be used to receive data from any http streaming source. - ------------ -para_filter ------------ - -A filter program that converts from stdin and writes to stdout. - -para_filter combines several decoders (mp3, oggvorbis, aac) and a -volume normalizer. New filters can be added easily. It is possible -to "chain" any number of filters, like UNIX pipes. - -para_filter does not depend on other parts of paraslash, so it can -be used as a stand-alone command line tool for audio decoding and -volume normalization. - --------- -para_afh --------- - -A small stand-alone program that prints tech info about the given -audio file to stdout. It can be instructed to print a "chunk table", -an array of offsets within the audio file or to write the content of -the audio file in complete chunks 'just in time'. - -This allows third-party streaming software that is unaware of -the particular audio format to send complete frames in real -time. Currently, mp3, ogg vorbis and aac are supported. - ----------- -para_write ----------- - -A modular audio stream writer. It supports a simple file writer -output plug-in and optional wav/raw players for ALSA (Linux) and for -coreaudio (Mac OS). para_write can also be used as a stand-alone wav -or raw audio player. - ------------ -para_audiod ------------ - -The local daemon that collects information from para_server. - -It runs on the client side and connects to para_server. As soon as -para_server announces the availability of an audio stream, para_audiod -starts an appropriate receiver, any number of filters and a paraslash -writer to play the stream. It is possible to capture the stream at -any position in the filter chain. - -Moreover, para_audiod listens on a local socket and sends status -information about para_server and para_audiod to local clients on -request. Access via this local socket may be restricted by using Unix -socket credentials, if available. - ------------ -para_audioc ------------ - -The client program which talks to para_audiod. Used to control -para_audiod, to receive status info, or to grab the stream at any -point in the filter chain. - -para_audioc (hence para_audiod) is needed by para_gui see below. - --------- -para_gui --------- - -Themable ncurses-based gui. It calls para_audioc and presents -the obtained information in an ncurses window. para_gui provides -key-bindings for the most common commands and new key-bindings can -be added easily. - ---------- -para_fade ---------- - -A (oss-only) alarm clock and volume-fader. - ---------------- -bash_completion ---------------- - -A small bash script for inclusion in ~/.bashrc. It gives you command -line completion for some paraslash commands. ------- LICENSE diff --git a/README.afs b/README.afs deleted file mode 100644 index ca01df6a..00000000 --- a/README.afs +++ /dev/null @@ -1,256 +0,0 @@ -The audio file selector -======================= - -Paraslash comes with a sophisticated audio file selector called *afs*. -In the -<< -installation notes, ->> -only the "dummy" mode of afs was used which gets activated automatically if -nothing else was specified. In this section the various features of afs are -described. - ----------- -Attributes -~~~~~~~~~~ - -An attribute is simply a bit which can be set for each audio -file individually. Up to 64 different attributes may be -defined. For example, "pop", "rock", "blues", "jazz", "instrumental", -"german_lyrics", "speech", whatever. It's up to you how many attributes -you define and how you call them. - -A new attribute "test" is created by - - para_client addatt test -and - para_client lsatt - -lists all available attributes. You can set the "test" attribute for -an audio file by executing - - para_client setatt test+ /path/to/the/audio/file - -Similarly, the "test" bit can be removed from an audio file with - - para_client setatt test- /path/to/the/audio/file - -Instead of a path you may use a shell wildcard pattern. The attribute -is applied to all audio files matching that pattern: - - para_client setatt test+ '/test/directory/*' - -The command - - para_client -- ls -lv - -gives you a verbose listing of your audio files which contains also -which attributes are set. - -In case you wonder why the double-dash in the above command is needed: -It tells para_client to not interpret the options after the dashes. If -you find this annoying, just say - - alias para='para_client --' - -and be happy. In what follows we shall use this alias. - -The "test" attribute can be dropped from the database with - - para rmatt test - -Read the output of - - para help ls - para help setatt - -for more information and a complete list of command line options to -these commands. - - ----------------------- -Abstract mood nonsense -~~~~~~~~~~~~~~~~~~~~~~ - -[skip this part if you don't like formal definitions] - -A mood consists of a unique name and its *mood definition*, which is -a set of *mood lines* containing expressions in terms of attributes -and other data contained in the database. - -A mood defines a subset of audio files called the *admissible audio -files* for that mood. At any time, at most one mood can be *active* -which means that para_server is going to select only files from that -subset of admissible files. - -So in order to create a mood definition one has to write a set of -mood lines. Mood lines come in three flavours: Accept lines, deny -lines and score lines. - -The general syntax of the three types of mood lines is - - - accept [with score ] [if] [not] [options] - deny [with score ] [if] [not] [options] - score [if] [not] [options] - - -Here is either an integer or the string "random" which assigns -a random score to all matching files. The score value changes the -order in which admissible files are going to be selected, but is of -minor importance for this introduction. - -So we concentrate on the first two forms, i.e. accept and deny -lines. As usual, everything in square brackets is optional, i.e. -accept/deny lines take the following form when ignoring scores: - - accept [if] [not] [options] - -and analogously for the deny case. The "if" keyword is purely cosmetic -and has no function. The "not" keyword just inverts the result, so -the essence of a mood line is the mood method part and the options -following thereafter. - -A *mood method* is realized as a function which takes an audio file -and computes a number from the data contained in the database. -If this number is non-negative, we say the file *matches* the mood -method. The file matches the full mood line if it either - - - matches the mood method and the "not" keyword is not given, -or - - does not match the mood method, but the "not" keyword is given. - -The set of admissible files for the whole mood is now defined as those -files which match at least one accept mood line, but no deny mood line. -More formally, an audio file F is admissible if and only if - - (F ~ AL1 or F ~ AL2...) and not (F ~ DL1 or F ~ DN2 ...) - -where AL1, AL2... are the accept lines, DL1, DL2... are the deny -lines and "~" means "matches". - -The cases where no mood lines of accept/deny type are defined need -special treatment: - - - Neither accept nor deny lines: This treats all files as admissible - (in fact, that is the definition of the dummy mood which is activated - automatically if no moods are available). - - - Only accept lines: A file is admissible iff it matches at least one - accept line: - - F ~ AL1 or F ~ AL2 or ... - - - Only deny lines: A file is admissible iff it matches no deny line: - - not (F ~ DL1 or F ~ DN2 ...) - - - --------------------- -List of mood_methods -~~~~~~~~~~~~~~~~~~~~ - - no_attributes_set - -Takes no arguments and matches an audio file if and only if no -attributes are set. - - played_rarely - -Takes no arguments and matches all audio files where the number of -times this audio file was selected is below the average. - - is_set attribute_name - -Takes the name of an attribute and matches iff that attribute is set. - - path_matches pattern - -Takes a filename pattern and matches iff the path of the audio file -matches the pattern. - - ----------- -Mood usage -~~~~~~~~~~ - -To create a new mood called "my_mood", write its definition into -some temporary file, say "tmpfile", and add it to the mood table -by executing - - para addmood my_mood < tmpfile - -If the mood definition is really short, you may just pipe it to the -client instead of using temporary files. Like this: - - echo "$MOOD_DEFINITION" | para addmood my_mood - -There is no need to keep the temporary file since you can always use -the catmood command to get it back: - - para catmood my_mood - -A mood can be activated by executing - - para select m/my_mood - -Once active, the list of admissible files is shown by the ls command -if the "-a" switch is given: - - para ls -a - ------------------------ -Example mood definition -~~~~~~~~~~~~~~~~~~~~~~~ - -Suppose you have defined attributes "punk" and "rock" and want to define -a mood containing only Punk-Rock songs. That is, an audio file should be -admissible if and only if both attributes are set. Since - - punk and rock - -is obviously the same as - - not (not punk or not rock) - -(de Morgan's rule), a mood definition that selects only Punk-Rock -songs is - - deny if not is_set punk - deny if not is_set rock - - ---------- -Troubles? ---------- - -Use the debug loglevel (option -l debug for most commands) to show -debugging info. Almost all paraslash executables have a brief online -help which is displayed by using the -h switch. The --detailed-help -option prints the full help text. - -para_fsck tries to fix your database. Use --force (even if your name -isn't Luke) to clean up after a crash. However, first make sure -para_server isn't running before executing para_fsck if para_fsck -complains about busy (dirty) tables. para_fsck also contains an option -to dump the contents of the database to the file system. - -If you don't mind to recreate your database you can start -from scratch by removing the entire database directory, i.e. - - rm -rf ~/.paraslash/afs_database - -Note that this removes all tables, in particular attribute definitions -and data, and all playlist and mood definitions. - -para_fsck operates on the osl-layer, i.e. it fixes inconsistencies -in the database but doesn't know about the contents of the tables -contained therein. Use - - para_client check - -to print out bad entries, e.g.missing audio files or invalid mood -definitions. - -Still having problems? mailto: Andre Noll diff --git a/REQUIREMENTS b/REQUIREMENTS deleted file mode 100644 index c1743fd2..00000000 --- a/REQUIREMENTS +++ /dev/null @@ -1,56 +0,0 @@ -Requirements -============ - -In any case you need - - - gcc, the gnu compiler collection (shipped with distro): gcc-3.3 - or newer is required. - - gnu make (shipped with disto, might be called gmake) - - bash (most likely already installed) - - A decent version of grep. Solaris' /bin/grep is not good enough, - /usr/xpg4/bin/grep is fine though. - - openssl (needed by server, client): usually shipped with - distro, but you might have to install the "development package" - (called libssl-dev on debian systems) as well: - http://www.openssl.org/ - - help2man (for man page creation) ftp://ftp.gnu.org/pub/gnu/help2man - - software mixing, e.g. ALSA and the direct mixing plugin (dmix) - -Optional features: - - - *mp3*: The mp3 decoder of para_filter is based on libmad: - http://www.underbit.com/products/mad/. If you prefer to - use the libmad package provided by your distributor, make - sure to install the corresponding development package as - well. It is called libmad0-dev on debian-based systems. - Note that libmad is not necessary for the server side, - i.e. for sending mp3 files. - - - *id3 tags*: - For version-2 id3 tag support, you'll need libid3tag which - is also available through the above link (alternatively: - install package libid3tag0-dev). Without libid3tag, only - version one tags are recognized. - - - *ogg vorbis*: For ogg vorbis streams you'll need libogg, - libvorbis, libvorbisfile: http://www.xiph.org/downloads/. - The corresponding Debian packages are called libogg-dev - libvorbis-dev, other distributors chose similar names. - - - *aac*: - For aac files (m4a) you'll need libfaad. Get it at - http://www.audiocoding.com/. - Debian package: libfaad-dev. - - - On Linux, you'll need to have ALSA's development package - installed. The Debian package is called libasound2-dev. - -Hacking the source: - - - gengetopt: ftp://ftp.gnu.org/pub/gnu/gengetopt/ - - autoconf: ftp://ftp.gnu.org/pub/gnu/autoconf/ - - git http://git.or.cz/ - - grutatxt http://www.triptico.com/software/grutatxt.html - - doxygen http://www.stack.nl/~dimitri/doxygen/ - - global ftp://ftp.gnu.org/pub/gnu/global - - m4: ftp://ftp.gnu.org/pub/gnu/m4/ diff --git a/aac.h b/aac.h index 1efe75ac..0653d35a 100644 --- a/aac.h +++ b/aac.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ diff --git a/aac_afh.c b/aac_afh.c index 80b50a4d..94b0f8b3 100644 --- a/aac_afh.c +++ b/aac_afh.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2008 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -10,12 +10,12 @@ /** \file aac_afh.c para_server's aac audio format handler */ +#include + #include "para.h" #include "error.h" -#include "string.h" #include "afh.h" -#include "afs.h" -#include "server.h" +#include "string.h" #include "aac.h" static int aac_find_stsz(unsigned char *buf, size_t buflen, off_t *skip) @@ -91,11 +91,9 @@ static char *get_tag(unsigned char *p, int size) return buf; } -static char *read_tags(unsigned char *buf, size_t buflen) +static void read_tags(unsigned char *buf, size_t buflen, struct afh_info *afhi) { unsigned char *p = buf; - char *title = NULL, *artist = NULL, *album = NULL, *year = NULL, - *comment = NULL, *result; while (p + 32 < buf + buflen) { unsigned char *q, type1[5], type2[5]; @@ -114,27 +112,20 @@ static char *read_tags(unsigned char *buf, size_t buflen) if (q + size2 > buf + buflen) break; if (!atom_cmp(type1, "©ART")) - artist = get_tag(q, size2); + afhi->tags.artist = get_tag(q, size2); else if (!atom_cmp(type1, "©alb")) - album = get_tag(q, size2); + afhi->tags.album = get_tag(q, size2); else if (!atom_cmp(type1, "©nam")) - title = get_tag(q, size2); + afhi->tags.title = get_tag(q, size2); else if (!atom_cmp(type1, "©cmt")) - comment = get_tag(q, size2); + afhi->tags.comment = get_tag(q, size2); else if (!atom_cmp(type1, "©day")) - year = get_tag(q, size2); + afhi->tags.year = get_tag(q, size2); p += size1; } - result = make_taginfo(title, artist, album, year, comment); - free(title); - free(artist); - free(album); - free(year); - free(comment); - return result; } -static char *read_meta(unsigned char *buf, size_t buflen) +static void read_meta(unsigned char *buf, size_t buflen, struct afh_info *afhi) { unsigned char *p = buf; @@ -145,12 +136,12 @@ static char *read_meta(unsigned char *buf, size_t buflen) continue; } p += 4; - return read_tags(p, buflen - (p - buf)); + return read_tags(p, buflen - (p - buf), afhi); } - return make_taginfo(NULL, NULL, NULL, NULL, NULL); } -static char *aac_get_taginfo(unsigned char *buf, size_t buflen) +static void aac_get_taginfo(unsigned char *buf, size_t buflen, + struct afh_info *afhi) { int i; uint64_t subsize; @@ -165,10 +156,9 @@ static char *aac_get_taginfo(unsigned char *buf, size_t buflen) p = buf + i; i += read_atom_header(p, &subsize, type); p = buf + i; - return read_meta(p, buflen - i); + return read_meta(p, buflen - i, afhi); } PARA_INFO_LOG("no meta data\n"); - return make_taginfo(NULL, NULL, NULL, NULL, NULL); } static ssize_t aac_compute_chunk_table(struct afh_info *afhi, @@ -232,12 +222,11 @@ static int aac_get_file_info(char *map, size_t numbytes, __a_unused int fd, mp4AudioSpecificConfig mp4ASC; NeAACDecHandle handle = NULL; unsigned char *umap = (unsigned char *) map; - char *taginfo; ret = aac_find_esds(umap, numbytes, &skip, &decoder_len); if (ret < 0) goto out; - taginfo = aac_get_taginfo(umap, numbytes); + aac_get_taginfo(umap, numbytes, afhi); handle = aac_open(); ret = -E_AAC_AFH_INIT; if (NeAACDecInit(handle, umap + skip, decoder_len, &rate, &channels)) @@ -268,11 +257,6 @@ static int aac_get_file_info(char *map, size_t numbytes, __a_unused int fd, ret = (afhi->chunk_table[afhi->chunks_total] - afhi->chunk_table[0]) * 8; /* bits */ ret += (channels * afhi->seconds_total * 500); /* avoid rounding error */ afhi->bitrate = ret / (channels * afhi->seconds_total * 1000); - afhi->info_string = make_message("%s:\n%s", - status_item_list[SI_AUDIO_FILE_INFO], - taginfo); - free(taginfo); - tv_scale(20, &afhi->chunk_tv, &afhi->eof_tv); ret = 1; out: if (handle) diff --git a/aac_common.c b/aac_common.c index 2235236c..14662fdd 100644 --- a/aac_common.c +++ b/aac_common.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ diff --git a/aacdec_filter.c b/aacdec_filter.c index 1ceca1fc..95c13305 100644 --- a/aacdec_filter.c +++ b/aacdec_filter.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -10,20 +10,20 @@ /** \file aacdec_filter.c paraslash's aac (m4a) decoder. */ -#include "para.h" +#include +#include +#include "para.h" #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "filter.h" #include "error.h" #include "string.h" #include "aac.h" -/** the output buffer size */ -#define AAC_OUTBUF_SIZE (32 * 1024) - -/** give up decoding after that many errors */ +/** Give up decoding after that many errors. */ #define MAX_ERRORS 20 /** @@ -49,25 +49,59 @@ struct private_aacdec_data { size_t consumed_total; /** return value of aac_find_entry_point */ size_t entry; + /** The number of channels of the current stream. */ + unsigned int channels; + /** Current sample rate in Hz. */ + unsigned int sample_rate; }; -static ssize_t aacdec(char *input_buffer, size_t len, struct filter_node *fn) +static int aacdec_execute(struct btr_node *btrn, const char *cmd, char **result) +{ + struct filter_node *fn = btr_context(btrn); + struct private_aacdec_data *padd = fn->private_data; + + return decoder_execute(cmd, padd->sample_rate, padd->channels, result); +} + +static void aacdec_open(struct filter_node *fn) +{ + struct private_aacdec_data *padd = para_calloc(sizeof(*padd)); + + fn->private_data = padd; + fn->min_iqs = 2048; + padd->handle = aac_open(); +} + +static void aacdec_close(struct filter_node *fn) { struct private_aacdec_data *padd = fn->private_data; - struct filter_chain *fc = fn->fc; + + NeAACDecClose(padd->handle); + free(padd); + fn->private_data = NULL; +} + +static void aacdec_post_select(__a_unused struct sched *s, struct task *t) +{ + struct filter_node *fn = container_of(t, struct filter_node, task); + struct btr_node *btrn = fn->btrn; + struct private_aacdec_data *padd = fn->private_data; int i, ret; - unsigned char *p, *outbuffer; - unsigned char *inbuf = (unsigned char*)input_buffer; - size_t skip, consumed = 0; + unsigned char *p, *inbuf, *outbuffer; + char *btr_buf; + size_t len, skip, consumed, loaded; - if (fn->loaded > fn->bufsize * 3 / 5) - return 0; - ret = *fc->input_error; +next_buffer: + t->error = 0; + ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL); if (ret < 0) - return ret; - if (len < 2048) - return 0; - + goto err; + if (ret == 0) + return; + btr_merge(btrn, fn->min_iqs); + len = btr_next_buffer(btrn, (char **)&inbuf); + len = PARA_MIN(len, (size_t)8192); + consumed = 0; if (!padd->initialized) { unsigned long rate = 0; unsigned char channels = 0; @@ -93,10 +127,10 @@ static ssize_t aacdec(char *input_buffer, size_t len, struct filter_node *fn) &channels) < 0) goto out; } - fc->samplerate = rate; - fc->channels = channels; + padd->sample_rate = rate; + padd->channels = channels; PARA_INFO_LOG("rate: %u, channels: %d\n", - fc->samplerate, fc->channels); + padd->sample_rate, padd->channels); padd->initialized = 1; } if (padd->decoder_length > 0) { @@ -124,72 +158,68 @@ static ssize_t aacdec(char *input_buffer, size_t len, struct filter_node *fn) if (consumed >= len) goto success; p = inbuf + consumed; + //PARA_CRIT_LOG("consumed: %zu (%zu + %zu), have: %zu\n", padd->consumed_total + consumed, + // padd->consumed_total, consumed, len - consumed); outbuffer = NeAACDecDecode(padd->handle, &padd->frame_info, p, len - consumed); if (padd->frame_info.error) { + int err = padd->frame_info.error; ret = -E_AAC_DECODE; if (padd->error_count++ > MAX_ERRORS) - goto out; - PARA_ERROR_LOG("frame_error: %d, consumed: %zu + %zd + %lu\n", - padd->frame_info.error, padd->consumed_total, - consumed, padd->frame_info.bytesconsumed); - PARA_ERROR_LOG("%s\n", NeAACDecGetErrorMessage( - padd->frame_info.error)); - consumed++; /* catch 21 */ + goto err; + /* Suppress non-fatal bitstream error message at BOF/EOF */ + if (len < fn->min_iqs || padd->consumed_total == 0) { + consumed = len; + goto success; + } + PARA_ERROR_LOG("%s\n", NeAACDecGetErrorMessage(err)); + PARA_ERROR_LOG("consumed: %zu + %zd + %lu\n", + padd->consumed_total, consumed, + padd->frame_info.bytesconsumed); + if (consumed < len) + consumed++; /* catch 21 */ goto success; } padd->error_count = 0; + //PARA_CRIT_LOG("decoder ate %lu\n", padd->frame_info.bytesconsumed); consumed += padd->frame_info.bytesconsumed; ret = consumed; if (!padd->frame_info.samples) goto out; - ret = -E_AAC_OVERRUN; - if (padd->frame_info.samples * 2 + fn->loaded > fn->bufsize) - goto out; + btr_buf = para_malloc(2 * padd->frame_info.samples); + loaded = 0; for (i = 0; i < padd->frame_info.samples; i++) { - short *s = (short *)outbuffer; - write_int16_host_endian(fn->buf + fn->loaded, s[i]); - fn->loaded += 2; + short sh = ((short *)outbuffer)[i]; + write_int16_host_endian(btr_buf + loaded, sh); + loaded += 2; } + btr_add_output(btr_buf, loaded, btrn); success: ret = consumed; out: - if (ret > 0) + if (ret >= 0) { padd->consumed_total += ret; - return ret; -} - -static void aacdec_open(struct filter_node *fn) -{ - struct private_aacdec_data *padd = para_calloc(sizeof(*padd)); - - fn->private_data = padd; - fn->bufsize = AAC_OUTBUF_SIZE; - fn->buf = para_calloc(fn->bufsize); - padd->handle = aac_open(); -} - -static void aacdec_close(struct filter_node *fn) -{ - struct private_aacdec_data *padd = fn->private_data; - - NeAACDecClose(padd->handle); - free(fn->buf); - fn->buf = NULL; - free(padd); - fn->private_data = NULL; + btr_consume(btrn, ret); + goto next_buffer; + } +err: + assert(ret < 0); + t->error = ret; + btr_remove_node(btrn); } /** - * the init function of the aacdec filter + * The init function of the aacdec filter. * - * \param f pointer to the filter struct to initialize + * \param f Pointer to the filter struct to initialize. * * \sa filter::init */ void aacdec_filter_init(struct filter *f) { f->open = aacdec_open; - f->convert = aacdec; f->close = aacdec_close; + f->pre_select = generic_filter_pre_select; + f->post_select = aacdec_post_select; + f->execute = aacdec_execute; } diff --git a/acl.c b/acl.c index ffcd1685..132350dd 100644 --- a/acl.c +++ b/acl.c @@ -1,11 +1,13 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file acl.c Access control lists for paraslash senders. */ +#include + #include "para.h" #include "error.h" #include "string.h" diff --git a/acl.h b/acl.h index e6090a88..14950b36 100644 --- a/acl.h +++ b/acl.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2009 Andre Noll + * Copyright (C) 2008-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ diff --git a/afh.c b/afh.c index 8e1e6b31..5ce6cfc5 100644 --- a/afh.c +++ b/afh.c @@ -1,11 +1,12 @@ /* - * Copyright (C) 2008-2009 Andre Noll + * Copyright (C) 2008-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file afh.c Paraslash's standalone audio format handler tool. */ +#include #include #include @@ -32,18 +33,28 @@ static void print_info(int audio_format_num, struct afh_info *afhi) "%s: %dHz\n" /* frequency */ "%s: %d\n" /* channels */ "%s: %lu\n" /* seconds total */ - "%s" /* tag info */ "%s: %lu: %lu\n" /* chunk time */ - "%s: %lu\n", /* num chunks */ + "%s: %lu\n" /* num chunks */ + "%s: %s\n" /* techinfo */ + "%s: %s\n" /* artist */ + "%s: %s\n" /* title */ + "%s: %s\n" /* year */ + "%s: %s\n" /* album */ + "%s: %s\n", /* comment */ status_item_list[SI_BITRATE], afhi->bitrate, status_item_list[SI_FORMAT], audio_format_name(audio_format_num), status_item_list[SI_FREQUENCY], afhi->frequency, status_item_list[SI_CHANNELS], afhi->channels, status_item_list[SI_SECONDS_TOTAL], afhi->seconds_total, - afhi->info_string, status_item_list[SI_CHUNK_TIME], (long unsigned)afhi->chunk_tv.tv_sec, (long unsigned)afhi->chunk_tv.tv_usec, - status_item_list[SI_NUM_CHUNKS], afhi->chunks_total + status_item_list[SI_NUM_CHUNKS], afhi->chunks_total, + status_item_list[SI_TECHINFO], afhi->techinfo? afhi->techinfo : "", + status_item_list[SI_ARTIST], afhi->tags.artist? afhi->tags.artist : "", + status_item_list[SI_TITLE], afhi->tags.title? afhi->tags.title : "", + status_item_list[SI_YEAR], afhi->tags.year? afhi->tags.year : "", + status_item_list[SI_ALBUM], afhi->tags.album? afhi->tags.album : "", + status_item_list[SI_COMMENT], afhi->tags.comment? afhi->tags.comment : "" ); } @@ -51,10 +62,25 @@ static void print_chunk_table(struct afh_info *afhi) { int i; - printf("chunk_table: "); - for (i = 0; i <= afhi->chunks_total; i++) - printf("%u ", afhi->chunk_table[i]); - printf("\n"); + if (!conf.human_given) { + printf("chunk_table: "); + for (i = 0; i <= afhi->chunks_total; i++) + printf("%u ", afhi->chunk_table[i]); + printf("\n"); + return; + } + for (i = 1; i <= afhi->chunks_total; i++) { + struct timeval tv; + long unsigned from, to; + tv_scale(i - 1, &afhi->chunk_tv, &tv); + from = tv2ms(&tv); + tv_scale(i, &afhi->chunk_tv, &tv); + to = tv2ms(&tv); + printf("%d [%lu.%03lu - %lu.%03lu] %u - %u (%u)\n", i - 1, + from / 1000, from % 1000, to / 1000, to % 1000, + afhi->chunk_table[i - 1], afhi->chunk_table[i], + afhi->chunk_table[i] - afhi->chunk_table[i - 1]); + } } static int cat_file(void *audio_file_data, struct afh_info *afhi) @@ -166,10 +192,15 @@ int main(int argc, char **argv) print_info(audio_format_num, &afhi); if (conf.chunk_table_given) print_chunk_table(&afhi); + free(afhi.techinfo); + free(afhi.tags.artist); + free(afhi.tags.title); + free(afhi.tags.year); + free(afhi.tags.album); + free(afhi.tags.comment); + free(afhi.chunk_table); printf("\n"); } - free(afhi.chunk_table); - free(afhi.info_string); ret2 = para_munmap(audio_file_data, audio_file_size); if (ret2 < 0 && ret >= 0) ret = ret2; diff --git a/afh.h b/afh.h index 35cc7ef1..8f74e236 100644 --- a/afh.h +++ b/afh.h @@ -1,27 +1,29 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file afh.h structures for audio format handling (para_server) */ -/** \cond */ -#ifdef HAVE_OGGVORBIS -#define OV_AUDIO_FORMAT " ogg" -#else -#define OV_AUDIO_FORMAT "" -#endif - -#ifdef HAVE_FAAD -#define AAC_AUDIO_FORMAT " aac" -#else -#define AAC_AUDIO_FORMAT "" -#endif - -#define SUPPORTED_AUDIO_FORMATS "mp3" OV_AUDIO_FORMAT AAC_AUDIO_FORMAT - -/** \endcond */ +/** + * The tags used by all audio format handlers. + * + * Paraslash only uses the more common tags. These are recognized + * for all supported audio formats. + */ +struct taginfo { + /** TPE1 (id3v2) / ARTIST (vorbis) / ©ART (aac) */ + char *artist; + /** TIT2/TITLE/©nam */ + char *title; + /** TDRC/YEAR/©day */ + char *year; + /** TALB/ALBUM/©alb */ + char *album; + /** COMM/COMMENT/©cmt */ + char *comment; +}; /** Audio format dependent information. */ struct afh_info { @@ -29,8 +31,10 @@ struct afh_info { long unsigned chunks_total; /** The length of the audio file in seconds. */ long unsigned seconds_total; - /** A string that gets filled in by the audio format handler. */ - char *info_string; + /** Audio handler specific info about the file. */ + char *techinfo; + /** Id3 tags, vorbis comments, aac tags. */ + struct taginfo tags; /** * The table that specifies the offset of the individual pieces in * the current audio file. @@ -38,8 +42,6 @@ struct afh_info { uint32_t *chunk_table; /** Period of time between sending data chunks. */ struct timeval chunk_tv; - /** End of file timeout - Do not load new audio file until this time. */ - struct timeval eof_tv; /** * The position of the header within the audio file. Ignored if \a * header_len equals zero. @@ -102,7 +104,4 @@ int compute_afhi(const char *path, char *data, size_t size, const char *audio_format_name(int); void afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi, void *map, const char **buf, size_t *len); -uint32_t afh_get_largest_chunk_size(struct afh_info *afhi); void afh_get_header(struct afh_info *afhi, void *map, const char **buf, size_t *len); -char *make_taginfo(char *title, char *artist, char *album, char *year, - char *comment); diff --git a/afh_common.c b/afh_common.c index 7d4ab089..dd2ae650 100644 --- a/afh_common.c +++ b/afh_common.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 1997-2009 Andre Noll + * Copyright (C) 1997-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -10,6 +10,7 @@ #include /* gettimeofday */ #include #include +#include #include "para.h" #include "error.h" @@ -25,7 +26,11 @@ void mp3_init(struct audio_format_handler *); #ifdef HAVE_FAAD void aac_afh_init(struct audio_format_handler *); #endif +#ifdef HAVE_SPEEX + void spx_afh_init(struct audio_format_handler *); +#endif +void wma_afh_init(struct audio_format_handler *); /** * The list of supported audio formats. * @@ -53,6 +58,16 @@ static struct audio_format_handler afl[] = { .name = "aac", #ifdef HAVE_FAAD .init = aac_afh_init, +#endif + }, + { + .name = "wma", + .init = wma_afh_init, + }, + { + .name = "spx", +#ifdef HAVE_SPEEX + .init = spx_afh_init, #endif }, { @@ -83,7 +98,7 @@ void afh_init(void) int i; PARA_INFO_LOG("supported audio formats: %s\n", - SUPPORTED_AUDIO_FORMATS); + SERVER_AUDIO_FORMATS); FOR_EACH_AUDIO_FORMAT(i) { PARA_NOTICE_LOG("initializing %s handler\n", audio_format_name(i)); @@ -121,39 +136,6 @@ int guess_audio_format(const char *name) return -E_AUDIO_FORMAT; } -/** - * Pretty-print the given meta-info. - * - * \param title The title of the audio file. - * \param artist The artist. - * \param album The name of the album. - * \param year Year of release. - * \param comment Further comments. - * - * This function is called by each audio format handler to produce the tag info - * status items. Usually, the audio format handlers read this info from the - * audio file (id3 tags, vorbis comments, ...). - * - * It is OK to pass \p NULL pointers for any argument in which case a suitable - * string is inserted which indicates that this information is not available. - * - * \return The status item string. It must be freed by the caller. - */ -char *make_taginfo(char *title, char *artist, char *album, char *year, - char *comment) -{ - return make_message("%s: %s, by %s\n" /* taginfo1 */ - "%s: A: %s, Y: %s, C: %s\n", /* taginfo2 */ - status_item_list[SI_TAGINFO1], - (title && *title)? title : "(title tag not set)", - (artist && *artist)? artist : "(artist tag not set)", - status_item_list[SI_TAGINFO2], - (album && *album)? album : "(album tag not set)", - (year && *year)? year : "????", - (comment && *comment)? comment : "(comment tag not set)" - ); -} - /** * Call get_file_info() to obtain an afhi structure. * @@ -180,22 +162,52 @@ int compute_afhi(const char *path, char *data, size_t size, int fd, afhi->header_offset = 0; afhi->header_len = 0; + afhi->techinfo = NULL; + afhi->tags.artist = NULL; + afhi->tags.title = NULL; + afhi->tags.year = NULL; + afhi->tags.album = NULL; + afhi->tags.comment = NULL; format = guess_audio_format(path); if (format >= 0) { ret = afl[format].get_file_info(data, size, fd, afhi); - if (ret >= 0) - return format; + if (ret >= 0) { + ret = format; + goto success; + } } FOR_EACH_AUDIO_FORMAT(i) { if (i == format) /* we already tried this one to no avail */ continue; ret = afl[i].get_file_info(data, size, fd, afhi); - if (ret >= 0) - return i; + if (ret >= 0) { + ret = i; + goto success; + } PARA_WARNING_LOG("%s\n", para_strerror(-ret)); } return -E_AUDIO_FORMAT; +success: + if (!afhi->techinfo) + afhi->techinfo = para_strdup(NULL); + if (!afhi->tags.artist) + afhi->tags.artist = para_strdup(NULL); + if (!afhi->tags.title) + afhi->tags.title = para_strdup(NULL); + if (!afhi->tags.year) + afhi->tags.year = para_strdup(NULL); + if (!afhi->tags.album) + afhi->tags.album = para_strdup(NULL); + if (!afhi->tags.comment) + afhi->tags.comment = para_strdup(NULL); + PARA_DEBUG_LOG("techinfo: %s\n", afhi->techinfo); + PARA_DEBUG_LOG("artist: %s\n", afhi->tags.artist); + PARA_DEBUG_LOG("title: %s\n", afhi->tags.title); + PARA_DEBUG_LOG("year: %s\n", afhi->tags.year); + PARA_DEBUG_LOG("album: %s\n", afhi->tags.album); + PARA_DEBUG_LOG("comment: %s\n", afhi->tags.comment); + return ret; } /** @@ -233,15 +245,6 @@ void afh_get_chunk(long unsigned chunk_num, struct afh_info *afhi, *len = afhi->chunk_table[chunk_num + 1] - pos; } -uint32_t afh_get_largest_chunk_size(struct afh_info *afhi) -{ - uint32_t n, largest = 0, *ct = afhi->chunk_table; - - for (n = 1; n <= afhi->chunks_total; n++) - largest = PARA_MAX(largest, ct[n] - ct[n - 1]); - return largest; -} - /** * Get the header of an audio file. * @@ -252,7 +255,7 @@ uint32_t afh_get_largest_chunk_size(struct afh_info *afhi) * * This function sets \a buf to \p NULL and \a len to zero if \a map or \a * afhi is \p NULL, or if the current audio format does not need special - * header treamtment. + * header treatment. */ void afh_get_header(struct afh_info *afhi, void *map, const char **buf, size_t *len) { diff --git a/afs.c b/afs.c index ccda7cda..2fb28b71 100644 --- a/afs.c +++ b/afs.c @@ -1,16 +1,21 @@ /* - * Copyright (C) 2007-2009 Andre Noll + * Copyright (C) 2007-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file afs.c Paraslash's audio file selector. */ +#include #include #include +#include +#include + #include "server.cmdline.h" #include "para.h" #include "error.h" +#include "crypt.h" #include "string.h" #include "afh.h" #include "afs.h" @@ -24,6 +29,7 @@ #include "sched.h" #include "signal.h" #include "fd.h" +#include "mood.h" /** The osl tables used by afs. \sa blob.c. */ enum afs_table_num { @@ -85,7 +91,6 @@ static struct signal_task signal_task_struct; static enum play_mode current_play_mode; static char *current_mop; /* mode or playlist specifier. NULL means dummy mooe */ - /** * A random number used to "authenticate" the connection. * @@ -228,7 +233,7 @@ int send_callback_request(callback_function *f, struct osl_object *query, *(uint32_t *) buf = afs_socket_cookie; *(int *) (buf + sizeof(afs_socket_cookie)) = query_shmid; - ret = create_remote_socket(conf.afs_socket_arg); + ret = connect_local_socket(conf.afs_socket_arg); if (ret < 0) goto out; fd = ret; @@ -342,7 +347,7 @@ static int action_if_pattern_matches(struct osl_row *row, void *data) struct pattern_match_data *pmd = data; struct osl_object name_obj; const char *p, *name; - int ret = osl_get_object(pmd->table, row, pmd->match_col_num, &name_obj); + int ret = osl(osl_get_object(pmd->table, row, pmd->match_col_num, &name_obj)); const char *pattern_txt = (const char *)pmd->patterns.data; if (ret < 0) @@ -369,16 +374,15 @@ static int action_if_pattern_matches(struct osl_row *row, void *data) * * \param pmd Describes what to match and how. * - * \return The return value of the underlying call to osl_rbtree_loop() - * or osl_rbtree_loop_reverse(). + * \return Standard. */ int for_each_matching_row(struct pattern_match_data *pmd) { if (pmd->pm_flags & PM_REVERSE_LOOP) - return osl_rbtree_loop_reverse(pmd->table, pmd->loop_col_num, pmd, - action_if_pattern_matches); - return osl_rbtree_loop(pmd->table, pmd->loop_col_num, pmd, - action_if_pattern_matches); + return osl(osl_rbtree_loop_reverse(pmd->table, pmd->loop_col_num, pmd, + action_if_pattern_matches)); + return osl(osl_rbtree_loop(pmd->table, pmd->loop_col_num, pmd, + action_if_pattern_matches)); } /** @@ -403,80 +407,6 @@ int string_compare(const struct osl_object *obj1, const struct osl_object *obj2) return strncmp(str1, str2, PARA_MIN(obj1->size, obj2->size)); } -/* - * write input from fd to dynamically allocated buffer, - * but maximal max_size byte. - */ -static int fd2buf(int fd, unsigned max_size, struct osl_object *obj) -{ - const size_t chunk_size = 1024; - size_t size = 2048, received = 0; - int ret; - char *buf = para_malloc(size); - - for (;;) { - ret = recv_bin_buffer(fd, buf + received, chunk_size); - if (ret <= 0) - break; - received += ret; - if (received + chunk_size >= size) { - size *= 2; - ret = -E_INPUT_TOO_LARGE; - if (size > max_size) - break; - buf = para_realloc(buf, size); - } - } - obj->data = buf; - obj->size = received; - if (ret < 0) - free(buf); - return ret; -} - -/** - * Read data from a file descriptor, and send it to the afs process. - * - * \param fd File descriptor to read data from. - * \param arg_obj Pointer to the arguments to \a f. - * \param f The callback function. - * \param max_len Don't read more than that many bytes from stdin. - * \param result_handler See \ref send_callback_request. - * \param private_result_data See \ref send_callback_request. - * - * This function is used by commands that wish to let para_server store - * arbitrary data specified by the user (for instance the add_blob family of - * commands). First, at most \a max_len bytes are read from \a fd, the result - * is concatenated with the buffer given by \a arg_obj, and the combined buffer - * is made available to the afs process via the callback method. See \ref - * send_callback_request for details. - * - * \return Negative on errors, the return value of the underlying call to - * send_callback_request() otherwise. - */ -int stdin_command(int fd, struct osl_object *arg_obj, callback_function *f, - unsigned max_len, callback_result_handler *result_handler, - void *private_result_data) -{ - struct osl_object query, stdin_obj; - int ret; - - ret = send_buffer(fd, AWAITING_DATA_MSG); - if (ret < 0) - return ret; - ret = fd2buf(fd, max_len, &stdin_obj); - if (ret < 0) - return ret; - query.size = arg_obj->size + stdin_obj.size; - query.data = para_malloc(query.size); - memcpy(query.data, arg_obj->data, arg_obj->size); - memcpy((char *)query.data + arg_obj->size, stdin_obj.data, stdin_obj.size); - free(stdin_obj.data); - ret = send_callback_request(f, &query, result_handler, private_result_data); - free(query.data); - return ret; -} - static int pass_afd(int fd, char *buf, size_t size) { struct msghdr msg = {.msg_iov = NULL}; @@ -652,21 +582,22 @@ out: * Result handler for sending data to the para_client process. * * \param result The data to be sent. - * \param fd_ptr Pointer to the file descriptor. + * \param private Pointer to rc4 context. * - * \return The return value of the underlying call to send_bin_buffer(). + * \return The return value of the underlying call to rc4_send_bin_buffer(). * - * \sa \ref callback_result_handler. + * \sa \ref callback_result_handler, \ref rc4_send_bin_buffer(). */ -int send_result(struct osl_object *result, void *fd_ptr) +int rc4_send_result(struct osl_object *result, void *private) { - int fd = *(int *)fd_ptr; + struct rc4_context *rc4c = private; + if (!result->size) return 1; - return send_bin_buffer(fd, result->data, result->size); + return rc4_send_bin_buffer(rc4c, result->data, result->size); } -int com_select(int fd, int argc, char * const * const argv) +int com_select(struct rc4_context *rc4c, int argc, char * const * const argv) { struct osl_object query; @@ -675,7 +606,7 @@ int com_select(int fd, int argc, char * const * const argv) query.data = argv[1]; query.size = strlen(argv[1]) + 1; return send_callback_request(com_select_callback, &query, - &send_result, &fd); + &rc4_send_result, rc4c); } static void init_admissible_files(char *arg) @@ -730,7 +661,7 @@ static void get_database_dir(void) else { char *home = para_homedir(); database_dir = make_message( - "%s/.paraslash/afs_database", home); + "%s/.paraslash/afs_database-0.4", home); free(home); } } @@ -778,15 +709,16 @@ static void signal_pre_select(struct sched *s, struct task *t) static void afs_signal_post_select(struct sched *s, struct task *t) { - struct signal_task *st = container_of(t, struct signal_task, task); + int signum; + if (getppid() == 1) { PARA_EMERG_LOG("para_server died\n"); goto shutdown; } - if (!FD_ISSET(st->fd, &s->rfds)) + signum = para_next_signal(&s->rfds); + if (signum == 0) return; - st->signum = para_next_signal(); - if (st->signum == SIGHUP) { + if (signum == SIGHUP) { close_afs_tables(); parse_config_or_die(1); t->error = open_afs_tables(); @@ -795,7 +727,7 @@ static void afs_signal_post_select(struct sched *s, struct task *t) init_admissible_files(current_mop); return; } - PARA_EMERG_LOG("terminating on signal %d\n", st->signum); + PARA_EMERG_LOG("terminating on signal %d\n", signum); shutdown: sched_shutdown(); t->error = -E_AFS_SIGNAL; @@ -911,57 +843,56 @@ static int call_callback(int fd, int query_shmid) return shm_detach(query_shm); } -static int execute_server_command(void) +static int execute_server_command(fd_set *rfds) { char buf[8]; - int ret = recv_bin_buffer(server_socket, buf, sizeof(buf) - 1); + size_t n; + int ret = read_nonblock(server_socket, buf, sizeof(buf) - 1, rfds, &n); - if (ret <= 0) { - if (!ret) - ret = -ERRNO_TO_PARA_ERROR(ECONNRESET); - goto err; - } - buf[ret] = '\0'; - PARA_DEBUG_LOG("received: %s\n", buf); - ret = -E_BAD_CMD; + if (ret < 0 || n == 0) + return ret; + buf[n] = '\0'; if (strcmp(buf, "new")) - goto err; - ret = open_next_audio_file(); -err: - return ret; + return -E_BAD_CMD; + return open_next_audio_file(); } -static void execute_afs_command(int fd, uint32_t expected_cookie) +/* returns 0 if no data available, 1 else */ +static int execute_afs_command(int fd, fd_set *rfds, uint32_t expected_cookie) { uint32_t cookie; int query_shmid; char buf[sizeof(cookie) + sizeof(query_shmid)]; - int ret = recv_bin_buffer(fd, buf, sizeof(buf)); + size_t n; + int ret = read_nonblock(fd, buf, sizeof(buf), rfds, &n); if (ret < 0) goto err; - if (ret != sizeof(buf)) { + if (n == 0) + return 0; + if (n != sizeof(buf)) { PARA_NOTICE_LOG("short read (%d bytes, expected %lu)\n", ret, (long unsigned) sizeof(buf)); - return; + return 1; } cookie = *(uint32_t *)buf; if (cookie != expected_cookie) { - PARA_NOTICE_LOG("received invalid cookie(got %u, expected %u)\n", + PARA_NOTICE_LOG("received invalid cookie (got %u, expected %u)\n", (unsigned)cookie, (unsigned)expected_cookie); - return; + return 1; } query_shmid = *(int *)(buf + sizeof(cookie)); if (query_shmid < 0) { PARA_WARNING_LOG("received invalid query shmid %d)\n", query_shmid); - return; + return 1; } ret = call_callback(fd, query_shmid); if (ret >= 0) - return; + return 1; err: PARA_NOTICE_LOG("%s\n", para_strerror(-ret)); + return 1; } /** Shutdown connection if query has not arrived until this many seconds. */ @@ -974,20 +905,16 @@ static void command_post_select(struct sched *s, struct task *t) struct afs_client *client, *tmp; int fd, ret; - if (FD_ISSET(server_socket, &s->rfds)) { - ret = execute_server_command(); - if (ret < 0) { - PARA_EMERG_LOG("%s\n", para_strerror(-ret)); - sched_shutdown(); - return; - } + ret = execute_server_command(&s->rfds); + if (ret < 0) { + PARA_EMERG_LOG("%s\n", para_strerror(-ret)); + sched_shutdown(); + return; } - /* Check the list of connected clients. */ list_for_each_entry_safe(client, tmp, &afs_client_list, node) { - if (FD_ISSET(client->fd, &s->rfds)) - execute_afs_command(client->fd, ct->cookie); - else { /* prevent bogus connection flooding */ + ret = execute_afs_command(client->fd, &s->rfds, ct->cookie); + if (ret == 0) { /* prevent bogus connection flooding */ struct timeval diff; tv_diff(now, &client->connect_time, &diff); if (diff.tv_sec < AFS_CLIENT_TIMEOUT) @@ -999,14 +926,11 @@ static void command_post_select(struct sched *s, struct task *t) free(client); } /* Accept connections on the local socket. */ - if (!FD_ISSET(ct->fd, &s->rfds)) - return; - ret = para_accept(ct->fd, &unix_addr, sizeof(unix_addr)); - if (ret < 0) { + ret = para_accept(ct->fd, &s->rfds, &unix_addr, sizeof(unix_addr), &fd); + if (ret < 0) PARA_NOTICE_LOG("%s\n", para_strerror(-ret)); + if (ret <= 0) return; - } - fd = ret; ret = mark_fd_nonblocking(fd); if (ret < 0) { PARA_NOTICE_LOG("%s\n", para_strerror(-ret)); @@ -1096,7 +1020,7 @@ out: free(pb.buf); } -int com_init(int fd, int argc, char * const * const argv) +int com_init(struct rc4_context *rc4c, int argc, char * const * const argv) { int i, j, ret; uint32_t table_mask = (1 << (NUM_AFS_TABLES + 1)) - 1; @@ -1121,9 +1045,10 @@ int com_init(int fd, int argc, char * const * const argv) return -E_BAD_TABLE_NAME; } } - ret = send_callback_request(create_tables_callback, &query, &send_result, &fd); + ret = send_callback_request(create_tables_callback, &query, + rc4_send_result, rc4c); if (ret < 0) - return send_va_buffer(fd, "%s\n", para_strerror(-ret)); + return rc4_send_va_buffer(rc4c, "%s\n", para_strerror(-ret)); return ret; } @@ -1141,7 +1066,7 @@ enum com_check_flags { CHECK_PLAYLISTS = 4 }; -int com_check(int fd, int argc, char * const * const argv) +int com_check(struct rc4_context *rc4c, int argc, char * const * const argv) { unsigned flags = 0; int i, ret; @@ -1173,17 +1098,20 @@ int com_check(int fd, int argc, char * const * const argv) if (!flags) flags = ~0U; if (flags & CHECK_AFT) { - ret = send_callback_request(aft_check_callback, NULL, send_result, &fd); + ret = send_callback_request(aft_check_callback, NULL, + rc4_send_result, rc4c); if (ret < 0) return ret; } if (flags & CHECK_PLAYLISTS) { - ret = send_callback_request(playlist_check_callback, NULL, send_result, &fd); + ret = send_callback_request(playlist_check_callback, + NULL, rc4_send_result, rc4c); if (ret < 0) return ret; } if (flags & CHECK_MOODS) { - ret = send_callback_request(mood_check_callback, NULL, send_result, &fd); + ret = send_callback_request(mood_check_callback, NULL, + rc4_send_result, rc4c); if (ret < 0) return ret; } @@ -1216,12 +1144,30 @@ void afs_event(enum afs_events event, struct para_buffer *pb, } } +/** + * Dummy event handler for the images table. + * + * \param event Unused. + * \param pb Unused. + * \param data Unused. + * + * This table does not honor events. + */ int images_event_handler(__a_unused enum afs_events event, __a_unused struct para_buffer *pb, __a_unused void *data) { return 1; } +/** + * Dummy event handler for the lyrics table. + * + * \param event Unused. + * \param pb Unused. + * \param data Unused. + * + * This table does not honor events. + */ int lyrics_event_handler(__a_unused enum afs_events event, __a_unused struct para_buffer *pb, __a_unused void *data) { diff --git a/afs.cmd b/afs.cmd index 0af45113..f0551998 100644 --- a/afs.cmd +++ b/afs.cmd @@ -3,7 +3,8 @@ SF: afs.c aft.c attribute.c HC: Prototypes for the commands of the audio file selector. CC: Array of commands for the audio file selector. AT: server_command -IN: para error string afh afs server list user_list +SI: openssl/rc4 osl regex +IN: para error crypt command string afh afs server list user_list SN: list of afs commands TM: mood lyr img pl --- @@ -54,6 +55,8 @@ H: -ll: long listing mode (equivalent to -l) H: H: -lv: verbose listing mode H: +H: -lp: parser-friendly mode +H: H: -lm: mbox listing mode H: H: -lc: chunk-table listing mode @@ -94,7 +97,7 @@ H: -sa: sort by audio format. --- N: lsatt P: AFS_READ -D: List attributes +D: List attributes. U: lsatt [-i] [-l] [-r] [pattern] H: Print the list of all defined attributes which match the H: given pattern. If no pattern is given, the full list is @@ -184,7 +187,7 @@ H: a slash (see fnmatch(3)). N: touch P: AFS_READ | AFS_WRITE D: Manipulate the afs data for all audio files matching a pattern. -U: touch [-n numplayed] [-l lastplayed] [-y lyrics_id] [-i image_id] [-a amp] [-v] [-p] pattern +U: touch [-n=numplayed] [-l=lastplayed] [-y=lyrics_id] [-i=image_id] [-a=amp] [-v] [-p] pattern H: If no option is given, lastplayed is set to the current time H: and numplayed is increased by one. Otherwise, only the given H: options are taken into account. @@ -197,7 +200,7 @@ H: H: -l Set lastplayed time. The last time this audio file was selected. H: Must be given as the number of seconds since the epoch. Example: H: -H: touch -l $(date +%s) file +H: touch -l=$(date +%s) file H: H: sets the lastplayed time of 'file' to the current time. H: @@ -257,7 +260,7 @@ H: loads the mood named 'foo'. --- T: add N: add@member@ -O: int com_add@member@(int fd, int argc, char * const * const argv); +O: int com_add@member@(struct rc4_context *rc4c, int argc, char * const * const argv); P: AFS_READ | AFS_WRITE D: Read data from stdin and add it as a blob to the @member@ table. U: add@member@ @member@_name @@ -270,7 +273,7 @@ H: given name already exists, its contents are replaced by the new data. --- T: cat N: cat@member@ -O: int com_cat@member@(int fd, int argc, char * const * const argv); +O: int com_cat@member@(struct rc4_context *rc4c, int argc, char * const * const argv); P: AFS_READ D: Dump the contents of a blob of type @member@ to stdout. U: cat@member@ @member@_name @@ -280,7 +283,7 @@ H: they were previously added. --- T: ls N: ls@member@ -O: int com_ls@member@(int fd, int argc, char * const * const argv); +O: int com_ls@member@(struct rc4_context *rc4c, int argc, char * const * const argv); P: AFS_READ D: List blobs of type @member@ matching a pattern. U: ls@member@ [-i] [-l] [-r] [pattern] @@ -300,7 +303,7 @@ H: -r Reverse sort order. --- T: rm N: rm@member@ -O: int com_rm@member@(int fd, int argc, char * const * const argv); +O: int com_rm@member@(struct rc4_context *rc4c, int argc, char * const * const argv); P: AFS_READ | AFS_WRITE D: Remove blob(s) of type @member@ from the @member@ table. U: rm@member@ pattern... @@ -309,7 +312,7 @@ H: any given pattern. --- T: mv N: mv@member@ -O: int com_mv@member@(int fd, int argc, char * const * const argv); +O: int com_mv@member@(struct rc4_context *rc4c, int argc, char * const * const argv); P: AFS_READ | AFS_WRITE D: Rename a blob of type @member@. U: mv@member@ old_@member@_name new_@member@_name diff --git a/afs.h b/afs.h index bcc36309..699998b0 100644 --- a/afs.h +++ b/afs.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007-2009 Andre Noll + * Copyright (C) 2007-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -7,7 +7,6 @@ /** \file afs.h Exported symbols of the audio file selector. */ #include -#include "osl.h" #include "hash.h" /** Audio file selector data stored in the audio file table. */ @@ -98,7 +97,13 @@ struct afs_table { void *data); }; -enum play_mode {PLAY_MODE_MOOD, PLAY_MODE_PLAYLIST}; +/** How audio files are selected by afs. */ +enum play_mode { + /** Admissible files are determined by a mood definition. */ + PLAY_MODE_MOOD, + /** All listed files are admissible. */ + PLAY_MODE_PLAYLIST, +}; /** * Data about one audio file. @@ -118,25 +123,35 @@ struct ls_data { HASH_TYPE *hash; }; -void make_empty_status_items(char *buf); - -/** At most that many bytes will be passed from afs to para_server. */ -#define VERBOSE_LS_OUTPUT_SIZE 4096 - /** Data about the current audio file, passed from afs to server. */ struct audio_file_data { - /** Same info as ls -lv -p current audio_file. */ - char verbose_ls_output[VERBOSE_LS_OUTPUT_SIZE]; /** The open file descriptor to the current audio file. */ int fd; /** Vss needs this for streaming. */ struct afh_info afhi; + /** Size of the largest chunk. */ + uint32_t max_chunk_size; }; +/** + * Codes used for communication between the server and the afs process. + * + * Before forking the afs child, para_server creates a bidirectional pipe + * through which both processes communicate. Usually para_server requests a new + * audio in order to start streaming or when the end of the current audio file + * has been reached. The afs process responds to such a request by sending + * back an eight byte buffer. The first four bytes is the uint32_t + * representation of the code, usually \p NEXT_AUDIO_FILE if an admissible + * audio file was found, successfully opened and verified. The other four bytes + * represent the shared memory id of the shared memory area that contains + * details about the audio file to be streamed next. The open file descriptor + * of that file is also passed from afs to para_server through the same pipe. + */ enum afs_server_code { + /** An audio file was successfully opened. */ NEXT_AUDIO_FILE, + /** No admissible audio file was found. */ NO_ADMISSIBLE_FILES, - AFD_CHANGE }; /** Flags passed to for_each_matching_row(). */ @@ -188,7 +203,7 @@ typedef void callback_function(int fd, const struct osl_object *); * \sa \ref send_callback_request(). */ typedef int callback_result_handler(struct osl_object *result, void *private); -int send_result(struct osl_object *result, void *fd_ptr); +int rc4_send_result(struct osl_object *result, void *private); int pass_buffer_as_shm(char *buf, size_t size, void *fd_ptr); __noreturn void afs_init(uint32_t cookie, int socket_fd); @@ -204,9 +219,6 @@ int send_option_arg_callback_request(struct osl_object *options, int send_standard_callback_request(int argc, char * const * const argv, callback_function *f, callback_result_handler *result_handler, void *private_result_data); -int stdin_command(int fd, struct osl_object *arg_obj, callback_function *f, - unsigned max_len, callback_result_handler *result_handler, - void *private_result_data); int string_compare(const struct osl_object *obj1, const struct osl_object *obj2); int for_each_matching_row(struct pattern_match_data *pmd); @@ -245,13 +257,6 @@ int get_afsi_object_of_row(const struct osl_row *row, struct osl_object *obj); int audio_file_loop(void *private_data, osl_rbtree_loop_func *func); void aft_check_callback(int fd, __a_unused const struct osl_object *query); -/* mood */ -int change_current_mood(char *mood_name); -void close_current_mood(void); -int reload_current_mood(void); -void mood_check_callback(int fd, __a_unused const struct osl_object *query); - - /* playlist */ int playlist_open(char *name); void playlist_close(void); @@ -289,21 +294,3 @@ enum blob_table_columns { /** A blob table has that many columns. */ NUM_BLOB_COLUMNS }; - -/** Define an osl table description for a blob table. */ -#define DEFINE_BLOB_TABLE_DESC(table_name) \ - struct osl_table_description table_name ## _table_desc = { \ - .name = #table_name, \ - .num_columns = NUM_BLOB_COLUMNS, \ - .flags = OSL_LARGE_TABLE, \ - .column_descriptions = blob_cols \ - }; - -/** Define a pointer to an osl blob table with a canonical name. */ -#define DEFINE_BLOB_TABLE_PTR(table_name) struct osl_table *table_name ## _table; - -/** Define a blob table. */ -#define INIT_BLOB_TABLE(table_name) \ - DEFINE_BLOB_TABLE_DESC(table_name); \ - DEFINE_BLOB_TABLE_PTR(table_name); - diff --git a/aft.c b/aft.c index 5fb04807..412318b2 100644 --- a/aft.c +++ b/aft.c @@ -1,28 +1,33 @@ /* - * Copyright (C) 2007-2009 Andre Noll + * Copyright (C) 2007-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file aft.c Audio file table functions. */ +#include #include /* readdir() */ -#include "para.h" -#include "error.h" -#include "string.h" +#include #include #include #include +#include +#include "para.h" +#include "error.h" +#include "crypt.h" +#include "string.h" #include "afh.h" #include "afs.h" #include "net.h" -#include "vss.h" #include "fd.h" #include "ipc.h" #include "portable_io.h" static struct osl_table *audio_file_table; +static char *status_items; +static char *parser_friendly_status_items; /** The different sorting methods of the ls command. */ enum ls_sorting_method { @@ -63,7 +68,9 @@ enum ls_listing_mode { /** -lm */ LS_MODE_MBOX, /** -lc */ - LS_MODE_CHUNKS + LS_MODE_CHUNKS, + /** -lp */ + LS_MODE_PARSER, }; /** The flags accepted by the ls command. */ @@ -220,12 +227,27 @@ enum audio_file_table_columns { NUM_AFT_COLUMNS }; +/** + * Compare two osl objects pointing to hash values. + * + * \param obj1 Pointer to the first hash object. + * \param obj2 Pointer to the second hash object. + * + * \return The values required for an osl compare function. + * + * \sa osl_compare_func, uint32_compare(). + */ +static int aft_hash_compare(const struct osl_object *obj1, const struct osl_object *obj2) +{ + return hash_compare((HASH_TYPE *)obj1->data, (HASH_TYPE *)obj2->data); +} + static struct osl_column_description aft_cols[] = { [AFTCOL_HASH] = { .storage_type = OSL_MAPPED_STORAGE, .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE, .name = "hash", - .compare_function = osl_hash_compare, + .compare_function = aft_hash_compare, .data_size = HASH_SIZE }, [AFTCOL_PATH] = { @@ -338,23 +360,29 @@ enum afhi_offsets { CHUNK_TV_TV_USEC_OFFSET = 36, /** Number of channels is stored here. (1 byte) */ AFHI_CHANNELS_OFFSET = 40, - /** EOF timeout in ms. (2 byte) */ - AFHI_EOF_OFFSET = 41, /** The tag info position. */ - AFHI_INFO_STRING_OFFSET = 43, + AFHI_INFO_STRING_OFFSET = 41, /** Minimal on-disk size of a valid afhi struct. */ - MIN_AFHI_SIZE = 44 + MIN_AFHI_SIZE = 47, /* at least 6 null bytes for techinfo/tags */ }; static unsigned sizeof_afhi_buf(const struct afh_info *afhi) { if (!afhi) return 0; - return strlen(afhi->info_string) + MIN_AFHI_SIZE; + return MIN_AFHI_SIZE + + strlen(afhi->techinfo) + + strlen(afhi->tags.artist) + + strlen(afhi->tags.title) + + strlen(afhi->tags.year) + + strlen(afhi->tags.album) + + strlen(afhi->tags.comment); } static void save_afhi(struct afh_info *afhi, char *buf) { + char *p; + if (!afhi) return; write_u32(buf + AFHI_SECONDS_TOTAL_OFFSET, afhi->seconds_total); @@ -368,8 +396,14 @@ static void save_afhi(struct afh_info *afhi, char *buf) write_u32(buf + HEADER_OFFSET_OFFSET, afhi->header_offset); write_u32(buf + CHUNK_TV_TV_SEC_OFFSET, afhi->chunk_tv.tv_sec); write_u32(buf + CHUNK_TV_TV_USEC_OFFSET, afhi->chunk_tv.tv_usec); - write_u16(buf + AFHI_EOF_OFFSET, tv2ms(&afhi->eof_tv)); - strcpy(buf + AFHI_INFO_STRING_OFFSET, afhi->info_string); /* OK */ + p = buf + AFHI_INFO_STRING_OFFSET; + /* The sprintf's below are OK as our caller made sure that buf is large enough */ + p += sprintf(p, "%s", afhi->techinfo) + 1; + p += sprintf(p, "%s", afhi->tags.artist) + 1; + p += sprintf(p, "%s", afhi->tags.title) + 1; + p += sprintf(p, "%s", afhi->tags.year) + 1; + p += sprintf(p, "%s", afhi->tags.album) + 1; + sprintf(p, "%s", afhi->tags.comment); } static void load_afhi(const char *buf, struct afh_info *afhi) @@ -385,8 +419,12 @@ static void load_afhi(const char *buf, struct afh_info *afhi) afhi->header_offset = read_u32(buf + HEADER_OFFSET_OFFSET); afhi->chunk_tv.tv_sec = read_u32(buf + CHUNK_TV_TV_SEC_OFFSET); afhi->chunk_tv.tv_usec = read_u32(buf + CHUNK_TV_TV_USEC_OFFSET); - ms2tv(read_u16(buf + AFHI_EOF_OFFSET), &afhi->eof_tv); - afhi->info_string = para_strdup(buf + AFHI_INFO_STRING_OFFSET); + afhi->techinfo = (char *)buf + AFHI_INFO_STRING_OFFSET; + afhi->tags.artist = afhi->techinfo + strlen(afhi->techinfo) + 1; + afhi->tags.title = afhi->tags.artist + strlen(afhi->tags.artist) + 1; + afhi->tags.year = afhi->tags.title + strlen(afhi->tags.title) + 1; + afhi->tags.album = afhi->tags.year + strlen(afhi->tags.year) + 1; + afhi->tags.comment = afhi->tags.album + strlen(afhi->tags.album) + 1; } static unsigned sizeof_chunk_table(struct afh_info *afhi) @@ -396,12 +434,26 @@ static unsigned sizeof_chunk_table(struct afh_info *afhi) return 4 * (afhi->chunks_total + 1); } -static void save_chunk_table(struct afh_info *afhi, char *buf) +static uint32_t save_chunk_table(struct afh_info *afhi, char *buf) { int i; - - for (i = 0; i <= afhi->chunks_total; i++) - write_u32(buf + 4 * i, afhi->chunk_table[i]); + uint32_t max = 0, old = 0; + + for (i = 0; i <= afhi->chunks_total; i++) { + uint32_t val = afhi->chunk_table[i]; + write_u32(buf + 4 * i, val); + /* + * If the first chunk is the header, do not consider it for the + * calculation of the largest chunk size. + */ + if (i == 0 || (i == 1 && afhi->header_len > 0)) { + old = val; + continue; + } + max = PARA_MAX(max, val - old); + old = val; + } + return max; } static void load_chunk_table(struct afh_info *afhi, char *buf) @@ -419,27 +471,27 @@ static void load_chunk_table(struct afh_info *afhi, char *buf) * \param path The full path of the audio file. * \param row Result pointer. * - * \return The return value of the underlying call to osl_get_row(). + * \return Standard. */ int aft_get_row_of_path(const char *path, struct osl_row **row) { struct osl_object obj = {.data = (char *)path, .size = strlen(path) + 1}; - return osl_get_row(audio_file_table, AFTCOL_PATH, &obj, row); + return osl(osl_get_row(audio_file_table, AFTCOL_PATH, &obj, row)); } /** * Get the row of the audio file table corresponding to the given hash value. * * \param hash The hash value of the desired audio file. - * \param row resul pointer. + * \param row Result pointer. * - * \return The return value of the underlying call to osl_get_row(). + * \return Standard. */ -int aft_get_row_of_hash(HASH_TYPE *hash, struct osl_row **row) +static int aft_get_row_of_hash(HASH_TYPE *hash, struct osl_row **row) { const struct osl_object obj = {.data = hash, .size = HASH_SIZE}; - return osl_get_row(audio_file_table, AFTCOL_HASH, &obj, row); + return osl(osl_get_row(audio_file_table, AFTCOL_HASH, &obj, row)); } /** @@ -448,11 +500,11 @@ int aft_get_row_of_hash(HASH_TYPE *hash, struct osl_row **row) * \param row Pointer to a row in the audio file table. * \param obj Result pointer. * - * \return The return value of the underlying call to osl_get_object(). + * \return Standard. */ int get_afsi_object_of_row(const struct osl_row *row, struct osl_object *obj) { - return osl_get_object(audio_file_table, row, AFTCOL_AFSI, obj); + return osl(osl_get_object(audio_file_table, row, AFTCOL_AFSI, obj)); } /** @@ -464,7 +516,7 @@ int get_afsi_object_of_row(const struct osl_row *row, struct osl_object *obj) * * \return Positive on success, negative on errors. */ -int get_afsi_object_of_path(const char *path, struct osl_object *obj) +static int get_afsi_object_of_path(const char *path, struct osl_object *obj) { struct osl_row *row; int ret = aft_get_row_of_path(path, &row); @@ -521,8 +573,8 @@ int get_afsi_of_path(const char *path, struct afs_info *afsi) int get_audio_file_path_of_row(const struct osl_row *row, char **path) { struct osl_object path_obj; - int ret = osl_get_object(audio_file_table, row, AFTCOL_PATH, - &path_obj); + int ret = osl(osl_get_object(audio_file_table, row, AFTCOL_PATH, + &path_obj)); if (ret < 0) return ret; *path = path_obj.data; @@ -539,9 +591,10 @@ int get_audio_file_path_of_row(const struct osl_row *row, char **path) * * \sa get_hash_of_row(). */ -static int get_hash_object_of_aft_row(const struct osl_row *row, struct osl_object *obj) +static int get_hash_object_of_aft_row(const struct osl_row *row, + struct osl_object *obj) { - return osl_get_object(audio_file_table, row, AFTCOL_HASH, obj); + return osl(osl_get_object(audio_file_table, row, AFTCOL_HASH, obj)); } /** @@ -579,8 +632,8 @@ static int get_hash_of_row(const struct osl_row *row, HASH_TYPE **hash) int get_afhi_of_row(const struct osl_row *row, struct afh_info *afhi) { struct osl_object obj; - int ret = osl_get_object(audio_file_table, row, AFTCOL_AFHI, - &obj); + int ret = osl(osl_get_object(audio_file_table, row, AFTCOL_AFHI, + &obj)); if (ret < 0) return ret; load_afhi(obj.data, afhi); @@ -601,10 +654,10 @@ static int save_afd(struct audio_file_data *afd) ret = shm_attach(shmid, ATTACH_RW, &shm_afd); if (ret < 0) goto err; - *(struct audio_file_data *)shm_afd = *afd; buf = shm_afd; buf += sizeof(*afd); - save_chunk_table(&afd->afhi, buf); + afd->max_chunk_size = save_chunk_table(&afd->afhi, buf); + *(struct audio_file_data *)shm_afd = *afd; shm_detach(shm_afd); return shmid; err: @@ -612,6 +665,18 @@ err: return ret; } +/** + * Extract a afd stored in a shared memory area. + * + * Attach the shared memory area given by \a shmid, load the audio file data + * stored therein and detach the area afterwards. Called by vss, after + * receiving a positive response to the request for the next audio file. + + + * \param shmid The identifier of the shared memory area containing the afd. + * \param afd Result pointer. + * + * \return Standard. + */ int load_afd(int shmid, struct audio_file_data *afd) { void *shm_afd; @@ -691,56 +756,68 @@ static void get_duration_buf(int seconds, char *buf, struct ls_options *opts) } } -static char *make_attribute_lines(const char *att_bitmap, struct afs_info *afsi) + +static int write_attribute_items(struct para_buffer *b, + const char *att_bitmap, struct afs_info *afsi) { - char *att_text, *att_lines; + char *att_text; + int ret; - get_attribute_text(&afsi->attributes, " ", &att_text); - if (!att_text) - return para_strdup(att_bitmap); - att_lines = make_message("%s: %s\n%s: %s", - status_item_list[SI_ATTRIBUTES_BITMAP], att_bitmap, - status_item_list[SI_ATTRIBUTES_TXT], att_text); + ret = WRITE_STATUS_ITEM(b, SI_ATTRIBUTES_BITMAP, "%s\n", att_bitmap); + if (ret < 0) + return ret; + ret = get_attribute_text(&afsi->attributes, " ", &att_text); + if (ret < 0) + return ret; + ret = WRITE_STATUS_ITEM(b, SI_ATTRIBUTES_TXT, "%s\n", att_text); free(att_text); - return att_lines; + return ret; } -static char *make_lyrics_lines(struct afs_info *afsi) +static int write_lyrics_items(struct para_buffer *b, struct afs_info *afsi) { char *lyrics_name; + int ret; + ret = WRITE_STATUS_ITEM(b, SI_LYRICS_ID, "%u\n", afsi->lyrics_id); + if (ret < 0) + return ret; lyr_get_name_by_id(afsi->lyrics_id, &lyrics_name); - return make_message("%s: %u\n%s: %s\n", - status_item_list[SI_LYRICS_ID], afsi->lyrics_id, - status_item_list[SI_LYRICS_NAME], lyrics_name? - lyrics_name : "(none)"); + return WRITE_STATUS_ITEM(b, SI_LYRICS_NAME, "%s\n", lyrics_name? + lyrics_name : "(none)"); } -static char *make_image_lines(struct afs_info *afsi) +static int write_image_items(struct para_buffer *b, struct afs_info *afsi) { char *image_name; + int ret; + + ret = WRITE_STATUS_ITEM(b, SI_IMAGE_ID, "%u\n", afsi->image_id); + if (ret < 0) + return ret; img_get_name_by_id(afsi->image_id, &image_name); - return make_message("%s: %u\n%s: %s\n", - status_item_list[SI_IMAGE_ID], afsi->image_id, - status_item_list[SI_IMAGE_NAME], image_name? - image_name : "(none)"); + return WRITE_STATUS_ITEM(b, SI_IMAGE_NAME, "%s\n", image_name? + image_name : "(none)"); } -static char *make_filename_lines(const char *path, unsigned flags) +static int write_filename_items(struct para_buffer *b, const char *path, + unsigned flags) { - char *dirname, *ret; - const char *basename; + char *val; + int ret; if (!(flags & LS_FLAG_FULL_PATH)) - return make_message("%s: %s\n", - status_item_list[SI_BASENAME], path); - basename = para_basename(path), - dirname = para_dirname(path); - ret = make_message("%s: %s\n%s: %s\n%s: %s\n", - status_item_list[SI_PATH], path, - status_item_list[SI_DIRECTORY], dirname? dirname : "?", - status_item_list[SI_BASENAME], basename? basename : "?"); - free(dirname); + return WRITE_STATUS_ITEM(b, SI_BASENAME, "%s\n", path); + ret = WRITE_STATUS_ITEM(b, SI_PATH, "%s\n", path); + if (ret < 0) + return ret; + val = para_basename(path); + ret = WRITE_STATUS_ITEM(b, SI_BASENAME, "%s\n", val? val : ""); + if (ret < 0) + return ret; + val = para_dirname(path); + ret = WRITE_STATUS_ITEM(b, SI_DIRECTORY, "%s\n", val? val : ""); + free(val); return ret; } @@ -778,6 +855,14 @@ out: return ret; } +static int write_score(struct para_buffer *b, struct ls_data *d, + struct ls_options *opts) +{ + if (!(opts->flags & LS_FLAG_ADMISSIBLE_ONLY)) /* no score*/ + return 0; + return WRITE_STATUS_ITEM(b, SI_SCORE, "%li\n", d->score); +} + static int print_list_item(struct ls_data *d, struct ls_options *opts, struct para_buffer *b, time_t current_time) { @@ -785,13 +870,9 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, char att_buf[65]; char last_played_time[30]; char duration_buf[30]; /* nobody has an audio file long enough to overflow this */ - char score_buf[30] = ""; struct afs_info *afsi = &d->afsi; struct afh_info *afhi = &d->afhi; - struct ls_widths *w = &opts->widths; - int have_score = opts->flags & LS_FLAG_ADMISSIBLE_ONLY; char asc_hash[2 * HASH_SIZE + 1]; - char *att_lines, *lyrics_lines, *image_lines, *filename_lines; if (opts->mode == LS_MODE_SHORT) { ret = para_printf(b, "%s\n", d->path); @@ -812,16 +893,15 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, goto out; } get_duration_buf(afhi->seconds_total, duration_buf, opts); - if (have_score) { - if (opts->mode == LS_MODE_LONG) - sprintf(score_buf, "%*li ", w->score_width, d->score); - else - sprintf(score_buf, "%li ", d->score); - } - if (opts->mode == LS_MODE_LONG) { + struct ls_widths *w = &opts->widths; + if (opts->flags & LS_FLAG_ADMISSIBLE_ONLY) { + ret = para_printf(b, "%*li ", + opts->widths.score_width, d->score); + if (ret < 0) + goto out; + } ret = para_printf(b, - "%s" /* score */ "%s " /* attributes */ "%*u " /* amp */ "%*d " /* image_id */ @@ -834,7 +914,6 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, "%*d " /* num_played */ "%s " /* last_played */ "%s\n", /* path */ - score_buf, att_buf, w->amp_width, afsi->amp, w->image_id_width, afsi->image_id, @@ -850,11 +929,6 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, ); goto out; } - hash_to_asc(d->hash, asc_hash); - att_lines = make_attribute_lines(att_buf, afsi); - lyrics_lines = make_lyrics_lines(afsi); - image_lines = make_image_lines(afsi); - filename_lines = make_filename_lines(d->path, opts->flags); if (opts->mode == LS_MODE_MBOX) { const char *bn = para_basename(d->path); ret = para_printf(b, @@ -866,47 +940,78 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, if (ret < 0) goto out; } - ret = para_printf(b, - "%s" /* filename stuff */ - "%s%s%s%s" /* score */ - "%s\n" /* attributes */ - "%s: %s\n" /* hash */ - "%s" /* image id, image name */ - "%s" /* lyrics */ - "%s: %dkbit/s\n" /* bitrate */ - "%s: %s\n" /* format */ - "%s: %dHz\n" /* frequency */ - "%s: %d\n" /* channels */ - "%s: %s\n" /* duration */ - "%s: %lu\n" /* seconds total */ - "%s: %s\n" /* last played time */ - "%s: %d\n" /* num_played */ - "%s: %u\n" /* ampplification */ - "%s" /* tag info */ - "%s: %lu\n" /* chunk time */ - "%s: %lu\n", /* num chunks */ - filename_lines, - have_score? status_item_list[SI_SCORE] : "", - have_score? ": " : "", - score_buf, - have_score? "\n" : "", - att_lines, - status_item_list[SI_HASH], asc_hash, - image_lines, - lyrics_lines, - status_item_list[SI_BITRATE], afhi->bitrate, - status_item_list[SI_FORMAT], audio_format_name(afsi->audio_format_id), - status_item_list[SI_FREQUENCY], afhi->frequency, - status_item_list[SI_CHANNELS], afhi->channels, - status_item_list[SI_DURATION], duration_buf, - status_item_list[SI_SECONDS_TOTAL], afhi->seconds_total, - status_item_list[SI_LAST_PLAYED], last_played_time, - status_item_list[SI_NUM_PLAYED], afsi->num_played, - status_item_list[SI_AMPLIFICATION], afsi->amp, - afhi->info_string, - status_item_list[SI_CHUNK_TIME], tv2ms(&afhi->chunk_tv), - status_item_list[SI_NUM_CHUNKS], afhi->chunks_total - ); + ret = write_filename_items(b, d->path, opts->flags); + if (ret < 0) + goto out; + ret = write_score(b, d, opts); + if (ret < 0) + goto out; + ret = write_attribute_items(b, att_buf, afsi); + if (ret < 0) + goto out; + ret = write_image_items(b, afsi); + if (ret < 0) + goto out; + ret = write_lyrics_items(b, afsi); + if (ret < 0) + goto out; + hash_to_asc(d->hash, asc_hash); + ret = WRITE_STATUS_ITEM(b, SI_HASH, "%s\n", asc_hash); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_BITRATE, "%dkbit/s\n", afhi->bitrate); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_FORMAT, "%s\n", + audio_format_name(afsi->audio_format_id)); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_FREQUENCY, "%dHz\n", afhi->frequency); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_CHANNELS, "%d\n", afhi->channels); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_DURATION, "%s\n", duration_buf); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_SECONDS_TOTAL, "%lu\n", + afhi->seconds_total); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_LAST_PLAYED, "%s\n", last_played_time); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_NUM_PLAYED, "%d\n", afsi->num_played); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_AMPLIFICATION, "%u\n", afsi->amp); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_CHUNK_TIME, "%lu\n", + tv2ms(&afhi->chunk_tv)); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_NUM_CHUNKS, "%lu\n", + afhi->chunks_total); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_TECHINFO, "%s\n", afhi->techinfo); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_ARTIST, "%s\n", afhi->tags.artist); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_TITLE, "%s\n", afhi->tags.title); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_YEAR, "%s\n", afhi->tags.year); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_ALBUM, "%s\n", afhi->tags.album); + if (ret < 0) + goto out; + ret = WRITE_STATUS_ITEM(b, SI_COMMENT, "%s\n", afhi->tags.comment); if (ret < 0) goto out; if (opts->mode == LS_MODE_MBOX) { @@ -918,129 +1023,10 @@ static int print_list_item(struct ls_data *d, struct ls_options *opts, osl_close_disk_object(&lyrics_def); } } - free(att_lines); - free(lyrics_lines); - free(image_lines); - free(filename_lines); out: - free(afhi->info_string); return ret; } -/** - * Write a list of audio-file related status items with empty values. - * - * \param buf Result pointer. - * - * This is used by vss when currently no audio file is open. - */ -void make_empty_status_items(char *buf) -{ - sprintf(buf, - "%s: \n" /* path */ - "%s: \n" /* dirname */ - "%s: \n" /* basename */ - "%s: \n" /* score */ - "%s: \n" /* attributes bitmap */ - "%s: \n" /* attributes txt */ - "%s: \n" /* hash */ - "%s: \n" /* image id */ - "%s: \n" /* image name */ - "%s: \n" /* lyrics id */ - "%s: \n" /* lyrics name */ - "%s: \n" /* bitrate */ - "%s: \n" /* format */ - "%s: \n" /* frequency */ - "%s: \n" /* channels */ - "%s: \n" /* duration */ - "%s: \n" /* seconds total */ - "%s: \n" /* num played */ - "%s: \n" /* last played */ - "%s: \n" /* audio file info */ - "%s: \n" /* taginfo1 */ - "%s: \n" /* taginfo2 */ - "%s: \n" /* amplification */ - , - status_item_list[SI_PATH], - status_item_list[SI_DIRECTORY], - status_item_list[SI_BASENAME], - status_item_list[SI_SCORE], - status_item_list[SI_ATTRIBUTES_BITMAP], - status_item_list[SI_ATTRIBUTES_TXT], - status_item_list[SI_HASH], - status_item_list[SI_IMAGE_ID], - status_item_list[SI_IMAGE_NAME], - status_item_list[SI_LYRICS_ID], - status_item_list[SI_LYRICS_NAME], - status_item_list[SI_BITRATE], - status_item_list[SI_FORMAT], - status_item_list[SI_FREQUENCY], - status_item_list[SI_CHANNELS], - status_item_list[SI_DURATION], - status_item_list[SI_SECONDS_TOTAL], - status_item_list[SI_NUM_PLAYED], - status_item_list[SI_LAST_PLAYED], - status_item_list[SI_AUDIO_FILE_INFO], - status_item_list[SI_TAGINFO1], - status_item_list[SI_TAGINFO2], - status_item_list[SI_AMPLIFICATION] - ); -} - -static void fixup_taginfo(char *begin, char *end) -{ - char *p = begin; - - for (;;) { - p = strchr(p, '\n'); - if (!p) - break; - if (p >= end - 1) - break; - *p = ' '; - p++; - } -} - -/* crap, remove this ASAP. */ -static int fixup_info_string(char *info_string) -{ - char *t1, *t2, *end; - - if (strncmp(info_string, "audio_file_info:", 16)) - return -ERRNO_TO_PARA_ERROR(EINVAL); - t1 = strstr(info_string, "\ntaginfo1:"); - if (!t1) - return -ERRNO_TO_PARA_ERROR(EINVAL); - t2 = strstr(info_string, "\ntaginfo2: "); - if (!t2) - return -ERRNO_TO_PARA_ERROR(EINVAL); - - end = t2 + strlen(t2); - fixup_taginfo(info_string + 16, t1); - fixup_taginfo(t1 + 10, t2); - fixup_taginfo(t2 + 10, end); - - if (t1 - info_string < 80 && t2 - t1 < 80 && end - t2 < 80) - return 0; - if (t1 - info_string >= 80) { - memmove(info_string + 80, t1, end - t1); - t1 = info_string + 80; - t2 -= t1 - info_string - 80; - end -= t1 - info_string - 80; - } - if (t2 - t1 >= 80) { - memmove(t1 + 80, t2, end - t2); - end -= t2 - t1 - 80; - t2 = t1 + 80; - } - if (end - t2 >= 80) { - t2[78] = '\n'; - t2[79] = '\0'; - } - return 1; -} - static int make_status_items(struct audio_file_data *afd, struct afs_info *afsi, char *path, long score, HASH_TYPE *hash) @@ -1056,26 +1042,28 @@ static int make_status_items(struct audio_file_data *afd, .flags = LS_FLAG_FULL_PATH | LS_FLAG_ADMISSIBLE_ONLY, .mode = LS_MODE_VERBOSE, }; - struct para_buffer pb = {.max_size = VERBOSE_LS_OUTPUT_SIZE - 1}; + struct para_buffer pb = {.max_size = SHMMAX - 1}; time_t current_time; int ret; - ret = fixup_info_string(afd->afhi.info_string); - if (ret < 0) { - PARA_WARNING_LOG("ignoring invalid tag info\n"); - afd->afhi.info_string[0] = '\0'; - } else if (ret) - PARA_NOTICE_LOG("truncated overlong tag info\n"); time(¤t_time); - ret = print_list_item(&d, &opts, &pb, current_time); /* frees info string */ - afd->afhi.info_string = NULL; + ret = print_list_item(&d, &opts, &pb, current_time); if (ret < 0) - goto out; - strncpy(afd->verbose_ls_output, pb.buf, VERBOSE_LS_OUTPUT_SIZE); - afd->verbose_ls_output[VERBOSE_LS_OUTPUT_SIZE - 1] = '\0'; -out: - free(pb.buf); - return ret; + return ret; + free(status_items); + status_items = pb.buf; + memset(&pb, 0, sizeof(pb)); + pb.max_size = SHMMAX - 1; + pb.flags = PBF_SIZE_PREFIX; + ret = print_list_item(&d, &opts, &pb, current_time); + if (ret < 0) { + free(status_items); + status_items = NULL; + return ret; + } + free(parser_friendly_status_items); + parser_friendly_status_items = pb.buf; + return 1; } /** @@ -1107,6 +1095,7 @@ int open_and_update_audio_file(struct osl_row *aft_row, long score, ret = get_audio_file_path_of_row(aft_row, &path); if (ret < 0) return ret; + PARA_NOTICE_LOG("%s\n", path); ret = get_afsi_object_of_row(aft_row, &afsi_obj); if (ret < 0) return ret; @@ -1147,7 +1136,6 @@ int open_and_update_audio_file(struct osl_row *aft_row, long score, ret = save_afd(afd); err: free(afd->afhi.chunk_table); - free(afd->afhi.info_string); osl_close_disk_object(&chunk_table_obj); return ret; } @@ -1350,7 +1338,6 @@ static int prepare_ls_row(struct osl_row *row, void *ls_opts) } return 1; err: - free(d->afhi.info_string); return ret; } @@ -1359,11 +1346,11 @@ static void com_ls_callback(int fd, const struct osl_object *query) struct ls_options *opts = query->data; char *p, *pattern_start = (char *)query->data + sizeof(*opts); struct para_buffer b = {.max_size = SHMMAX, + .flags = (opts->mode == LS_MODE_PARSER)? PBF_SIZE_PREFIX : 0, .max_size_handler = pass_buffer_as_shm, .private_data = &fd}; int i = 0, ret; time_t current_time; - if (opts->num_patterns) { opts->patterns = para_malloc(opts->num_patterns * sizeof(char *)); for (i = 0, p = pattern_start; i < opts->num_patterns; i++) { @@ -1375,8 +1362,8 @@ static void com_ls_callback(int fd, const struct osl_object *query) if (opts->flags & LS_FLAG_ADMISSIBLE_ONLY) ret = admissible_file_loop(opts, prepare_ls_row); else - ret = osl_rbtree_loop(audio_file_table, AFTCOL_PATH, opts, - prepare_ls_row); + ret = osl(osl_rbtree_loop(audio_file_table, AFTCOL_PATH, opts, + prepare_ls_row)); if (ret < 0) goto out; if (!opts->num_matching_paths) @@ -1409,7 +1396,7 @@ out: /* * TODO: flags -h (sort by hash) */ -int com_ls(int fd, int argc, char * const * const argv) +int com_ls(struct rc4_context *rc4c, int argc, char * const * const argv) { int i, ret; unsigned flags = 0; @@ -1449,6 +1436,9 @@ int com_ls(int fd, int argc, char * const * const argv) case 'c': mode = LS_MODE_CHUNKS; continue; + case 'p': + mode = LS_MODE_PARSER; + continue; default: return -E_AFT_SYNTAX; } @@ -1518,7 +1508,7 @@ int com_ls(int fd, int argc, char * const * const argv) opts.mode = mode; opts.num_patterns = argc - i; ret = send_option_arg_callback_request(&query, opts.num_patterns, - argv + i, com_ls_callback, send_result, &fd); + argv + i, com_ls_callback, rc4_send_result, rc4c); return ret; } @@ -1528,12 +1518,12 @@ int com_ls(int fd, int argc, char * const * const argv) * \param private_data An arbitrary data pointer, passed to \a func. * \param func The custom function to be called. * - * \return The return value of the underlying call to osl_rbtree_loop(). + * \return Standard. */ int audio_file_loop(void *private_data, osl_rbtree_loop_func *func) { - return osl_rbtree_loop(audio_file_table, AFTCOL_HASH, private_data, - func); + return osl(osl_rbtree_loop(audio_file_table, AFTCOL_HASH, private_data, + func)); } static struct osl_row *find_hash_sister(HASH_TYPE *hash) @@ -1693,7 +1683,7 @@ static void com_add_callback(int fd, const struct osl_object *query) PARA_INFO_LOG("request to add %s\n", path); hs = find_hash_sister(hash); ret = aft_get_row_of_path(path, &pb); - if (ret < 0 && ret != -E_RB_KEY_NOT_FOUND) + if (ret < 0 && ret != -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND)) goto out; if (hs && pb && hs == pb && !(flags & ADD_FLAG_FORCE)) { if (flags & ADD_FLAG_VERBOSE) @@ -1710,23 +1700,23 @@ static void com_add_callback(int fd, const struct osl_object *query) if (ret < 0) goto out; } - ret = osl_del_row(audio_file_table, pb); + ret = osl(osl_del_row(audio_file_table, pb)); if (ret < 0) goto out; pb = NULL; } /* file rename, update hs' path */ if (flags & ADD_FLAG_VERBOSE) { - ret = osl_get_object(audio_file_table, hs, - AFTCOL_PATH, &obj); + ret = osl(osl_get_object(audio_file_table, hs, + AFTCOL_PATH, &obj)); if (ret < 0) goto out; ret = para_printf(&msg, "renamed from %s\n", (char *)obj.data); if (ret < 0) goto out; } - ret = osl_update_object(audio_file_table, hs, AFTCOL_PATH, - &objs[AFTCOL_PATH]); + ret = osl(osl_update_object(audio_file_table, hs, AFTCOL_PATH, + &objs[AFTCOL_PATH])); if (ret < 0) goto out; afs_event(AUDIO_FILE_RENAME, &msg, hs); @@ -1770,12 +1760,12 @@ static void com_add_callback(int fd, const struct osl_object *query) if (ret < 0) goto out; } - ret = osl_update_object(audio_file_table, row, AFTCOL_AFHI, - &objs[AFTCOL_AFHI]); + ret = osl(osl_update_object(audio_file_table, row, AFTCOL_AFHI, + &objs[AFTCOL_AFHI])); if (ret < 0) goto out; - ret = osl_update_object(audio_file_table, row, AFTCOL_CHUNKS, - &objs[AFTCOL_CHUNKS]); + ret = osl(osl_update_object(audio_file_table, row, AFTCOL_CHUNKS, + &objs[AFTCOL_CHUNKS])); if (ret < 0) goto out; afs_event(AFHI_CHANGE, &msg, row); @@ -1793,7 +1783,7 @@ static void com_add_callback(int fd, const struct osl_object *query) objs[AFTCOL_AFSI].data = &afsi_buf; objs[AFTCOL_AFSI].size = AFSI_SIZE; save_afsi(&default_afsi, &objs[AFTCOL_AFSI]); - ret = osl_add_and_get_row(audio_file_table, objs, &aft_row); + ret = osl(osl_add_and_get_row(audio_file_table, objs, &aft_row)); afs_event(AUDIO_FILE_ADD, &msg, aft_row); out: if (ret < 0) @@ -1805,8 +1795,8 @@ out: /** Used by com_add(). */ struct private_add_data { - /** The socket file descriptor. */ - int fd; + /** The socket file descriptor, including rc4 keys. */ + struct rc4_context *rc4c; /** The given add flags. */ uint32_t flags; }; @@ -1856,12 +1846,13 @@ static int add_one_audio_file(const char *path, void *private_data) query.size = strlen(path) + 1; ret = send_callback_request(path_brother_callback, &query, get_row_pointer_from_result, &pb); - if (ret < 0 && ret != -E_RB_KEY_NOT_FOUND) + if (ret < 0 && ret != -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND)) goto out_free; ret = 1; if (pb && (pad->flags & ADD_FLAG_LAZY)) { /* lazy is really cheap */ if (pad->flags & ADD_FLAG_VERBOSE) - send_ret = send_va_buffer(pad->fd, "lazy-ignore: %s\n", path); + send_ret = rc4_send_va_buffer(pad->rc4c, + "lazy-ignore: %s\n", path); goto out_free; } /* We still want to add this file. Compute its hash. */ @@ -1881,7 +1872,7 @@ static int add_one_audio_file(const char *path, void *private_data) ret = 1; if (pb && hs && hs == pb && !(pad->flags & ADD_FLAG_FORCE)) { if (pad->flags & ADD_FLAG_VERBOSE) - send_ret = send_va_buffer(pad->fd, + send_ret = rc4_send_va_buffer(pad->rc4c, "%s exists, not forcing update\n", path); goto out_unmap; } @@ -1899,13 +1890,13 @@ static int add_one_audio_file(const char *path, void *private_data) munmap(map.data, map.size); close(fd); if (pad->flags & ADD_FLAG_VERBOSE) { - send_ret = send_va_buffer(pad->fd, "adding %s\n", path); + send_ret = rc4_send_va_buffer(pad->rc4c, "adding %s\n", path); if (send_ret < 0) goto out_free; } save_add_callback_buffer(hash, path, afhi_ptr, pad->flags, format_num, &obj); /* Ask afs to consider this entry for adding. */ - ret = send_callback_request(com_add_callback, &obj, send_result, &pad->fd); + ret = send_callback_request(com_add_callback, &obj, rc4_send_result, pad->rc4c); goto out_free; out_unmap: @@ -1913,21 +1904,26 @@ out_unmap: munmap(map.data, map.size); out_free: if (ret < 0 && send_ret >= 0) - send_ret = send_va_buffer(pad->fd, "failed to add %s (%s)\n", path, - para_strerror(-ret)); + send_ret = rc4_send_va_buffer(pad->rc4c, + "failed to add %s (%s)\n", path, para_strerror(-ret)); free(obj.data); if (afhi_ptr) { free(afhi_ptr->chunk_table); - free(afhi_ptr->info_string); + free(afhi_ptr->techinfo); + free(afhi_ptr->tags.artist); + free(afhi_ptr->tags.title); + free(afhi_ptr->tags.year); + free(afhi_ptr->tags.album); + free(afhi_ptr->tags.comment); } /* Stop adding files only on send errors. */ return send_ret; } -int com_add(int fd, int argc, char * const * const argv) +int com_add(struct rc4_context *rc4c, int argc, char * const * const argv) { int i, ret; - struct private_add_data pad = {.fd = fd, .flags = 0}; + struct private_add_data pad = {.rc4c = rc4c, .flags = 0}; struct stat statbuf; for (i = 1; i < argc; i++) { @@ -1961,7 +1957,7 @@ int com_add(int fd, int argc, char * const * const argv) char *path; ret = verify_path(argv[i], &path); if (ret < 0) { - ret = send_va_buffer(fd, "%s: %s\n", argv[i], + ret = rc4_send_va_buffer(rc4c, "%s: %s\n", argv[i], para_strerror(-ret)); if (ret < 0) return ret; @@ -1969,7 +1965,7 @@ int com_add(int fd, int argc, char * const * const argv) } ret = stat(path, &statbuf); if (ret < 0) { - ret = send_va_buffer(fd, "failed to stat %s (%s)\n", path, + ret = rc4_send_va_buffer(rc4c, "failed to stat %s (%s)\n", path, strerror(errno)); free(path); if (ret < 0) @@ -1982,7 +1978,7 @@ int com_add(int fd, int argc, char * const * const argv) else ret = add_one_audio_file(path, &pad); if (ret < 0) { - send_va_buffer(fd, "%s: %s\n", path, para_strerror(-ret)); + rc4_send_va_buffer(rc4c, "%s: %s\n", path, para_strerror(-ret)); free(path); return ret; } @@ -2114,7 +2110,7 @@ static void com_touch_callback(int fd, const struct osl_object *query) free(tad.pb.buf); } -int com_touch(int fd, int argc, char * const * const argv) +int com_touch(struct rc4_context *rc4c, int argc, char * const * const argv) { struct com_touch_options cto = { .num_played = -1, @@ -2135,33 +2131,33 @@ int com_touch(int fd, int argc, char * const * const argv) i++; break; } - if (!strncmp(arg, "-n", 2)) { - ret = para_atoi32(arg + 2, &cto.num_played); + if (!strncmp(arg, "-n=", 3)) { + ret = para_atoi32(arg + 3, &cto.num_played); if (ret < 0) return ret; continue; } - if (!strncmp(arg, "-l", 2)) { - ret = para_atoi64(arg + 2, &cto.last_played); + if (!strncmp(arg, "-l=", 3)) { + ret = para_atoi64(arg + 3, &cto.last_played); if (ret < 0) return ret; continue; } - if (!strncmp(arg, "-y", 2)) { - ret = para_atoi32(arg + 2, &cto.lyrics_id); + if (!strncmp(arg, "-y=", 3)) { + ret = para_atoi32(arg + 3, &cto.lyrics_id); if (ret < 0) return ret; continue; } - if (!strncmp(arg, "-i", 2)) { - ret = para_atoi32(arg + 2, &cto.image_id); + if (!strncmp(arg, "-i=", 3)) { + ret = para_atoi32(arg + 3, &cto.image_id); if (ret < 0) return ret; continue; } - if (!strncmp(arg, "-a", 2)) { + if (!strncmp(arg, "-a=", 3)) { int32_t val; - ret = para_atoi32(arg + 2, &val); + ret = para_atoi32(arg + 3, &val); if (ret < 0) return ret; if (val < 0 || val > 255) @@ -2182,9 +2178,9 @@ int com_touch(int fd, int argc, char * const * const argv) if (i >= argc) return -E_AFT_SYNTAX; ret = send_option_arg_callback_request(&query, argc - i, - argv + i, com_touch_callback, send_result, &fd); + argv + i, com_touch_callback, rc4_send_result, rc4c); if (ret < 0) - send_va_buffer(fd, "%s\n", para_strerror(-ret)); + rc4_send_va_buffer(rc4c, "%s\n", para_strerror(-ret)); return ret; } @@ -2220,7 +2216,7 @@ static int remove_audio_file(__a_unused struct osl_table *table, return ret; } afs_event(AUDIO_FILE_REMOVE, &crd->pb, row); - ret = osl_del_row(audio_file_table, row); + ret = osl(osl_del_row(audio_file_table, row)); if (ret < 0) para_printf(&crd->pb, "%s: %s\n", name, para_strerror(-ret)); else @@ -2266,7 +2262,7 @@ static void com_rm_callback(int fd, const struct osl_object *query) } /* TODO options: -r (recursive) */ -int com_rm(int fd, int argc, char * const * const argv) +int com_rm(struct rc4_context *rc4c, int argc, char * const * const argv) { uint32_t flags = 0; struct osl_object query = {.data = &flags, .size = sizeof(flags)}; @@ -2297,9 +2293,9 @@ int com_rm(int fd, int argc, char * const * const argv) if (i >= argc) return -E_AFT_SYNTAX; ret = send_option_arg_callback_request(&query, argc - i, argv + i, - com_rm_callback, send_result, &fd); + com_rm_callback, rc4_send_result, rc4c); if (ret < 0) - send_va_buffer(fd, "%s\n", para_strerror(-ret)); + rc4_send_va_buffer(rc4c, "%s\n", para_strerror(-ret)); return ret; } @@ -2414,7 +2410,7 @@ out: free(cad.pb.buf); } -int com_cpsi(int fd, int argc, char * const * const argv) +int com_cpsi(struct rc4_context *rc4c, int argc, char * const * const argv) { unsigned flags = 0; int i, ret; @@ -2454,17 +2450,48 @@ int com_cpsi(int fd, int argc, char * const * const argv) } break; } - if (i + 1 >= argc) /* need at least souce file and pattern */ + if (i + 1 >= argc) /* need at least source file and pattern */ return -E_AFT_SYNTAX; if (!(flags & ~CPSI_FLAG_VERBOSE)) /* no copy flags given */ flags = ~(unsigned)CPSI_FLAG_VERBOSE | flags; ret = send_option_arg_callback_request(&options, argc - i, argv + i, - com_cpsi_callback, send_result, &fd); + com_cpsi_callback, rc4_send_result, rc4c); if (ret < 0) - send_va_buffer(fd, "%s\n", para_strerror(-ret)); + rc4_send_va_buffer(rc4c, "%s\n", para_strerror(-ret)); return ret; } +static void afs_stat_callback(int fd, const struct osl_object *query) +{ + int *parser_friendly = query->data; + char *buf = *parser_friendly? + parser_friendly_status_items : status_items; + + if (!buf) + return; + pass_buffer_as_shm(buf, strlen(buf), &fd); +} + +/** + * Get the current afs status items from the afs process and send it using RC4. + * + * \param rc4c The rc4 context for data encryption. + * \param parser_friendly Whether parser-friendly output format should be used. + * + * As the contents of the afs status items change in time and the command + * handler only has a COW version created at fork time, it can not send + * up-to-date afs status items directly. Therefore the usual callback mechanism + * is used to pass the status items from the afs process to the command handler + * via a shared memory area and a pipe. + */ +int send_afs_status(struct rc4_context *rc4c, int parser_friendly) +{ + struct osl_object query = {.data = &parser_friendly, + .size = sizeof(parser_friendly)}; + + return send_callback_request(afs_stat_callback, &query, rc4_send_result, rc4c); +} + /* TODO: optionally fix problems by removing offending rows */ static int check_audio_file(struct osl_row *row, void *data) { @@ -2543,6 +2570,10 @@ static void aft_close(void) { osl_close_table(audio_file_table, OSL_MARK_CLEAN); audio_file_table = NULL; + free(status_items); + status_items = NULL; + free(parser_friendly_status_items); + parser_friendly_status_items = NULL; } /** @@ -2559,7 +2590,7 @@ static int aft_open(const char *dir) int ret; audio_file_table_desc.dir = dir; - ret = osl_open_table(&audio_file_table_desc, &audio_file_table); + ret = osl(osl_open_table(&audio_file_table_desc, &audio_file_table)); if (ret >= 0) { unsigned num; osl_get_num_rows(audio_file_table, &num); @@ -2568,7 +2599,7 @@ static int aft_open(const char *dir) } PARA_INFO_LOG("failed to open audio file table\n"); audio_file_table = NULL; - if (ret >= 0 || is_errno(-ret, ENOENT)) + if (ret >= 0 || ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_NOENT)) return 1; return ret; } @@ -2576,7 +2607,7 @@ static int aft_open(const char *dir) static int aft_create(const char *dir) { audio_file_table_desc.dir = dir; - return osl_create_table(&audio_file_table_desc); + return osl(osl_create_table(&audio_file_table_desc)); } static int clear_attribute(struct osl_row *row, void *data) @@ -2617,6 +2648,11 @@ static int aft_event_handler(enum afs_events event, struct para_buffer *pb, } } +/** + * Initialize the audio file table. + * + * \param t Pointer to the structure to be initialized. + */ void aft_init(struct afs_table *t) { t->open = aft_open; diff --git a/alsa_write.c b/alsa_write.c index 53c97384..d6493418 100644 --- a/alsa_write.c +++ b/alsa_write.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -12,9 +12,12 @@ * based on the vplay program by Michael Beck. */ +#include #include #include #include +#include +#include #include "para.h" #include "fd.h" @@ -22,45 +25,60 @@ #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "write.h" +#include "write_common.h" #include "alsa_write.cmdline.h" #include "error.h" -/** always use 16 bit little endian */ -#define FORMAT SND_PCM_FORMAT_S16_LE - /** Data specific to the alsa writer. */ struct private_alsa_write_data { /** The alsa handle */ snd_pcm_t *handle; - /** Determined and set by alsa_open(). */ + /** Determined and set by alsa_init(). */ int bytes_per_frame; /** The approximate maximum buffer duration in us. */ unsigned buffer_time; /* Number of frames that fit into the buffer. */ - unsigned buffer_frames; + snd_pcm_uframes_t buffer_frames; /** - * The samplerate given by command line option or the decoder + * The sample rate given by command line option or the decoder * of the writer node group. */ - unsigned samplerate; + unsigned sample_rate; + snd_pcm_format_t sample_format; /** * The number of channels, given by command line option or the * decoder of the writer node group. */ unsigned channels; + struct timeval drain_barrier; }; +static snd_pcm_format_t get_alsa_pcm_format(enum sample_format sf) +{ + switch (sf) { + case SF_S8: return SND_PCM_FORMAT_S8; + case SF_U8: return SND_PCM_FORMAT_U8; + case SF_S16_LE: return SND_PCM_FORMAT_S16_LE; + case SF_S16_BE: return SND_PCM_FORMAT_S16_BE; + case SF_U16_LE: return SND_PCM_FORMAT_U16_LE; + case SF_U16_BE: return SND_PCM_FORMAT_U16_BE; + default: return SND_PCM_FORMAT_S16_LE; + } +} + /* Install PCM software and hardware configuration. */ static int alsa_init(struct private_alsa_write_data *pad, struct alsa_write_args_info *conf) { snd_pcm_hw_params_t *hwparams; snd_pcm_sw_params_t *swparams; - snd_pcm_uframes_t buffer_size, start_threshold, stop_threshold; + snd_pcm_uframes_t start_threshold, stop_threshold; snd_pcm_uframes_t period_size; int err; + PARA_INFO_LOG("opening %s\n", conf->device_arg); err = snd_pcm_open(&pad->handle, conf->device_arg, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); if (err < 0) @@ -73,13 +91,14 @@ static int alsa_init(struct private_alsa_write_data *pad, if (snd_pcm_hw_params_set_access(pad->handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) return -E_ACCESS_TYPE; - if (snd_pcm_hw_params_set_format(pad->handle, hwparams, FORMAT) < 0) + if (snd_pcm_hw_params_set_format(pad->handle, hwparams, + pad->sample_format) < 0) return -E_SAMPLE_FORMAT; if (snd_pcm_hw_params_set_channels(pad->handle, hwparams, pad->channels) < 0) return -E_CHANNEL_COUNT; if (snd_pcm_hw_params_set_rate_near(pad->handle, hwparams, - &pad->samplerate, NULL) < 0) + &pad->sample_rate, NULL) < 0) return -E_SET_RATE; err = snd_pcm_hw_params_get_buffer_time_max(hwparams, &pad->buffer_time, NULL); @@ -92,70 +111,49 @@ static int alsa_init(struct private_alsa_write_data *pad, if (snd_pcm_hw_params(pad->handle, hwparams) < 0) return -E_HW_PARAMS; snd_pcm_hw_params_get_period_size(hwparams, &period_size, NULL); - snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size); - PARA_INFO_LOG("buffer size: %lu, period_size: %lu\n", buffer_size, + snd_pcm_hw_params_get_buffer_size(hwparams, &pad->buffer_frames); + PARA_INFO_LOG("buffer size: %lu, period_size: %lu\n", pad->buffer_frames, period_size); - if (period_size == buffer_size) + if (period_size == pad->buffer_frames) return -E_BAD_PERIOD; snd_pcm_sw_params_current(pad->handle, swparams); snd_pcm_sw_params_set_avail_min(pad->handle, swparams, period_size); - if (buffer_size < 1) + if (pad->buffer_frames < 1) start_threshold = 1; else - start_threshold = PARA_MIN(buffer_size, - (snd_pcm_uframes_t)pad->samplerate); + start_threshold = PARA_MIN(pad->buffer_frames, + (snd_pcm_uframes_t)pad->sample_rate); if (snd_pcm_sw_params_set_start_threshold(pad->handle, swparams, start_threshold) < 0) return -E_START_THRESHOLD; - stop_threshold = buffer_size; + stop_threshold = pad->buffer_frames; if (snd_pcm_sw_params_set_stop_threshold(pad->handle, swparams, stop_threshold) < 0) return -E_STOP_THRESHOLD; if (snd_pcm_sw_params(pad->handle, swparams) < 0) PARA_WARNING_LOG("unable to install sw params\n"); - pad->bytes_per_frame = snd_pcm_format_physical_width(FORMAT) + pad->bytes_per_frame = snd_pcm_format_physical_width(pad->sample_format) * pad->channels / 8; if (pad->bytes_per_frame <= 0) return -E_PHYSICAL_WIDTH; PARA_INFO_LOG("bytes per frame: %d\n", pad->bytes_per_frame); if (snd_pcm_nonblock(pad->handle, 1)) PARA_ERROR_LOG("failed to set nonblock mode\n"); - pad->buffer_frames = 1000 * pad->buffer_time / pad->samplerate; - PARA_INFO_LOG("max buffered frames: %d\n", pad->buffer_frames); return 1; } -/* Open an instance of the alsa writer. */ -static int alsa_open(struct writer_node *wn) -{ - struct alsa_write_args_info *conf = wn->conf; - struct writer_node_group *wng = wn->wng; - struct private_alsa_write_data *pad = para_calloc(sizeof(*pad)); - - wn->private_data = pad; - if (!conf->samplerate_given && wng->samplerate) - pad->samplerate = *wng->samplerate; - else - pad->samplerate = conf->samplerate_arg; - if (!conf->channels_given && wng->channels) - pad->channels = *wng->channels; - else - pad->channels = conf->channels_arg; - PARA_INFO_LOG("%d channel(s), %dHz\n", pad->channels, pad->samplerate); - return 1; -} - -static int alsa_write_pre_select(struct sched *s, struct writer_node *wn) +static void alsa_write_pre_select(struct sched *s, struct task *t) { + struct writer_node *wn = container_of(t, struct writer_node, task); struct private_alsa_write_data *pad = wn->private_data; - struct writer_node_group *wng = wn->wng; struct timeval tv; snd_pcm_sframes_t avail, underrun; + int ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF); - if (!pad->handle) - return 1; - if (*wng->loaded - wn->written < pad->bytes_per_frame) - return 1; + if (ret == 0) + return; + if (ret < 0 || !pad) + return sched_min_delay(s); /* * Data is available to be written to the alsa handle. Compute number * of milliseconds until next buffer underrun would occur. @@ -175,101 +173,137 @@ static int alsa_write_pre_select(struct sched *s, struct writer_node *wn) underrun = 50; underrun -= 50; ms2tv(underrun, &tv); - if (tv_diff(&s->timeout, &tv, NULL) > 0) - s->timeout = tv; - return 1; + sched_request_timeout(&tv, s); +} + +static void alsa_close(struct writer_node *wn) +{ + struct private_alsa_write_data *pad = wn->private_data; + PARA_INFO_LOG("closing writer node %p\n", wn); + + if (!pad) + return; + /* + * It's OK to have a blocking operation here because we already made + * sure that the PCM output buffer is (nearly) empty. + */ + snd_pcm_nonblock(pad->handle, 0); + snd_pcm_drain(pad->handle); + snd_pcm_close(pad->handle); + snd_config_update_free_global(); + free(pad); } -static int alsa_write_post_select(__a_unused struct sched *s, - struct writer_node *wn) +static void alsa_write_post_select(__a_unused struct sched *s, + struct task *t) { + struct writer_node *wn = container_of(t, struct writer_node, task); struct private_alsa_write_data *pad = wn->private_data; - struct writer_node_group *wng = wn->wng; - size_t bytes = *wng->loaded - wn->written; - unsigned char *data = (unsigned char*)*wng->bufp + wn->written; - snd_pcm_sframes_t ret, frames, avail; + struct btr_node *btrn = wn->btrn; + char *data; + size_t bytes; + snd_pcm_sframes_t frames; + int ret; - if (*wng->input_error < 0 && (!pad->handle || bytes < pad->bytes_per_frame)) { - wn->written = *wng->loaded; - return *wng->input_error; +again: + t->error = 0; + ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_LEAF); + if (ret == 0) + return; + btr_merge(btrn, wn->min_iqs); + bytes = btr_next_buffer(btrn, &data); + if (ret < 0 || bytes < wn->min_iqs) { /* eof */ + assert(btr_no_parent(btrn)); + ret = -E_ALSA_EOF; + if (!pad) + goto err; + /* wait until pending frames are played */ + if (pad->drain_barrier.tv_sec == 0) { + PARA_DEBUG_LOG("waiting for device to drain\n"); + tv_add(now, &(struct timeval)EMBRACE(0, 200 * 1000), + &pad->drain_barrier); + return; + } + if (tv_diff(now, &pad->drain_barrier, NULL) > 0) + goto err; + return; } - if (!bytes) /* no data available */ - return 0; - if (!pad->handle) { - int err = alsa_init(pad, wn->conf); - if (err < 0) - return err; + if (!pad) { + int32_t val; + + pad = para_calloc(sizeof(*pad)); + wn->private_data = pad; + if (bytes == 0) /* no data available */ + return; + get_btr_sample_rate(btrn, &val); + pad->sample_rate = val; + get_btr_channels(btrn, &val); + pad->channels = val; + get_btr_sample_format(btrn, &val); + pad->sample_format = get_alsa_pcm_format(val); + + PARA_INFO_LOG("%d channel(s), %dHz\n", pad->channels, + pad->sample_rate); + ret = alsa_init(pad, wn->conf); + if (ret < 0) + goto err; + wn->min_iqs = pad->bytes_per_frame; + goto again; } frames = bytes / pad->bytes_per_frame; - if (!frames) /* less than a single frame available */ - return 0; - avail = snd_pcm_avail_update(pad->handle); - if (avail <= 0) - return 0; - frames = PARA_MIN(frames, avail); - ret = snd_pcm_writei(pad->handle, data, frames); - if (ret >= 0) { - wn->written += ret * pad->bytes_per_frame; - return 1; + frames = snd_pcm_writei(pad->handle, data, frames); + if (frames >= 0) { + btr_consume(btrn, frames * pad->bytes_per_frame); + goto again; } - PARA_WARNING_LOG("%s\n", snd_strerror(-ret)); - if (ret == -EPIPE) { + if (frames == -EPIPE) { + PARA_WARNING_LOG("underrun (tried to write %zu bytes)\n", bytes); snd_pcm_prepare(pad->handle); - return 0; + return; } - if (ret == -EAGAIN) - return 0; - return -E_ALSA_WRITE; + if (frames == -EAGAIN) + return; + PARA_WARNING_LOG("%s\n", snd_strerror(-frames)); + ret = -E_ALSA_WRITE; +err: + assert(ret < 0); + btr_remove_node(btrn); + t->error = ret; } -static void alsa_close(struct writer_node *wn) +__malloc static void *alsa_parse_config_or_die(const char *options) { - struct private_alsa_write_data *pad = wn->private_data; - PARA_INFO_LOG("closing writer node %p\n", wn); + struct alsa_write_args_info *conf = para_calloc(sizeof(*conf)); - if (pad->handle) { - snd_pcm_drain(pad->handle); - snd_pcm_close(pad->handle); - snd_config_update_free_global(); - } - free(pad); + PARA_INFO_LOG("options: %s, %zd\n", options, strcspn(options, " \t")); + /* exits on errors */ + alsa_cmdline_parser_string(options, conf, "alsa_write"); + return conf; } -__malloc static void *alsa_parse_config(const char *options) +static void alsa_free_config(void *conf) { - int ret; - struct alsa_write_args_info *conf - = para_calloc(sizeof(struct alsa_write_args_info)); - - PARA_INFO_LOG("options: %s, %zd\n", options, strcspn(options, " \t")); - ret = alsa_cmdline_parser_string(options, conf, "alsa_write"); - if (ret) - goto err_out; - PARA_INFO_LOG("help given: %d\n", conf->help_given); - return conf; -err_out: - free(conf); - return NULL; + alsa_cmdline_parser_free(conf); } /** - * the init function of the alsa writer + * The init function of the alsa writer. * - * \param w pointer to the writer to initialize + * \param w Pointer to the writer to initialize. * - * \sa struct writer + * \sa struct \ref writer. */ void alsa_write_init(struct writer *w) { struct alsa_write_args_info dummy; alsa_cmdline_parser_init(&dummy); - w->open = alsa_open; w->close = alsa_close; w->pre_select = alsa_write_pre_select; w->post_select = alsa_write_post_select; - w->parse_config = alsa_parse_config; + w->parse_config_or_die = alsa_parse_config_or_die; w->shutdown = NULL; /* nothing to do */ + w->free_config = alsa_free_config; w->help = (struct ggo_help) { .short_help = alsa_write_args_info_help, .detailed_help = alsa_write_args_info_detailed_help diff --git a/amp_filter.c b/amp_filter.c index a011c411..21fd9fd1 100644 --- a/amp_filter.c +++ b/amp_filter.c @@ -1,23 +1,24 @@ /* - * Copyright (C) 2009 Andre Noll + * Copyright (C) 2009-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file amp_filter.c Paraslash's amplify filter. */ +#include +#include + #include "para.h" #include "amp_filter.cmdline.h" #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "filter.h" #include "string.h" #include "error.h" -/** The size of the output data buffer. */ -#define AMP_CHUNK_SIZE 40960 - extern char *stat_item_values[NUM_STAT_ITEMS]; /** Data specific to the amplify filter. */ @@ -28,27 +29,9 @@ struct private_amp_data { unsigned amp; }; -static ssize_t amp_convert(char *inbuf, size_t inbuf_len, struct filter_node *fn) -{ - size_t i, length = PARA_MIN((inbuf_len / 2) * 2, - (fn->bufsize - fn->loaded) / 2 * 2); - struct private_amp_data *pad = fn->private_data; - int16_t *ip = (int16_t *)inbuf, *op = (int16_t *)(fn->buf + fn->loaded); - - if (!length) - return 0; - for (i = 0; i < length / 2; i++) { - int x = (PARA_ABS(*ip) * (64 + pad->amp)) >> 6; - *op++ = *ip++ > 0? PARA_MIN(x, 32767) : PARA_MAX(-x, -32768); - } - fn->loaded += length; - return length; -} - static void amp_close(struct filter_node *fn) { free(fn->private_data); - free(fn->buf); } static int amp_parse_config(int argc, char **argv, void **config) @@ -61,8 +44,6 @@ static int amp_parse_config(int argc, char **argv, void **config) ret = -ERRNO_TO_PARA_ERROR(EINVAL); if (amp_conf->amp_arg < 0) goto err; - PARA_NOTICE_LOG("amplification: %u (scaling factor: %1.2f)\n", - amp_conf->amp_arg, amp_conf->amp_arg / 64.0 + 1.0); *config = amp_conf; return 1; err: @@ -76,14 +57,74 @@ static void amp_open(struct filter_node *fn) pad->conf = fn->conf; fn->private_data = pad; - if (!pad->conf->amp_given && stat_item_values[SI_AMPLIFICATION]) { - int i = SI_AMPLIFICATION; - char *s = stat_item_values[i] + strlen(status_item_list[i]) + 1; - sscanf(s, "%u", &pad->amp); - } else + fn->min_iqs = 2; + if (!pad->conf->amp_given && stat_item_values[SI_AMPLIFICATION]) + sscanf(stat_item_values[SI_AMPLIFICATION], "%u", &pad->amp); + else pad->amp = pad->conf->amp_arg; - fn->bufsize = AMP_CHUNK_SIZE; - fn->buf = para_malloc(fn->bufsize); + PARA_NOTICE_LOG("amplification: %u (scaling factor: %1.2f)\n", + pad->amp, pad->amp / 64.0 + 1.0); +} + +static void amp_post_select(__a_unused struct sched *s, struct task *t) +{ + struct filter_node *fn = container_of(t, struct filter_node, task); + struct private_amp_data *pad = fn->private_data; + struct btr_node *btrn = fn->btrn; + int ret, factor = 64 + pad->amp; + size_t i, in_bytes, len; + int16_t *in, *out; + bool inplace = btr_inplace_ok(btrn); + + if (pad->amp == 0) { /* no amplification */ + t->error = -E_AMP_ZERO_AMP; + btr_splice_out_node(btrn); + return; + } +next_buffer: + ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL); + if (ret < 0) + goto err; + if (ret == 0) + return; + btr_merge(btrn, fn->min_iqs); + in_bytes = btr_next_buffer(btrn, (char **)&in); + len = in_bytes / 2; + if (len == 0) { /* eof and in_bytes == 1 */ + ret = -E_AMP_EOF; + goto err; + } + + if (inplace) + out = in; + else + out = para_malloc(len * 2); + + for (i = 0; i < len; i++) { + int x = (in[i] * factor) >> 6; + + out[i] = x; + if (out[i] != x) /* overflow, clip */ + out[i] = (x >= 0)? 32767 : -32768; + } + + if (inplace) + btr_pushdown_one(btrn); + else { + btr_consume(btrn, len * 2); + btr_add_output((char *)out, len * 2, btrn); + } + t->error = 0; + goto next_buffer; +err: + assert(ret < 0); + t->error = ret; + btr_remove_node(btrn); +} + +static void amp_free_config(void *conf) +{ + amp_cmdline_parser_free(conf); } /** @@ -98,8 +139,10 @@ void amp_filter_init(struct filter *f) amp_cmdline_parser_init(&dummy); f->open = amp_open; f->close = amp_close; - f->convert = amp_convert; + f->pre_select = generic_filter_pre_select; + f->post_select = amp_post_select; f->parse_config = amp_parse_config; + f->free_config = amp_free_config; f->help = (struct ggo_help) { .short_help = amp_filter_args_info_help, .detailed_help = amp_filter_args_info_detailed_help diff --git a/attribute.c b/attribute.c index df66c9d0..24536911 100644 --- a/attribute.c +++ b/attribute.c @@ -1,12 +1,18 @@ /* - * Copyright (C) 1997-2009 Andre Noll + * Copyright (C) 1997-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file attribute.c Attribute handling functions. */ + +#include +#include +#include + #include "para.h" #include "error.h" +#include "crypt.h" #include "string.h" #include "afh.h" #include "afs.h" @@ -89,11 +95,11 @@ int get_attribute_bitnum_by_name(const char *att_name, unsigned char *bitnum) struct osl_object obj = {.data = (char *)att_name, .size = strlen(att_name) + 1}; struct osl_row *row; - int ret = osl_get_row(attribute_table, ATTCOL_NAME, &obj, &row); + int ret = osl(osl_get_row(attribute_table, ATTCOL_NAME, &obj, &row)); if (ret < 0) return ret; - ret = osl_get_object(attribute_table, row, ATTCOL_BITNUM, &obj); + ret = osl(osl_get_object(attribute_table, row, ATTCOL_BITNUM, &obj)); if (ret < 0) return ret; *bitnum = *(unsigned char *)obj.data; @@ -131,7 +137,7 @@ static int print_attribute(struct osl_table *table, struct osl_row *row, if (!(laad->flags & LSATT_FLAG_LONG)) return para_printf(&laad->pb, "%s\n", name); - ret = osl_get_object(table, row, ATTCOL_BITNUM, &bitnum_obj); + ret = osl(osl_get_object(table, row, ATTCOL_BITNUM, &bitnum_obj)); if (ret < 0) { para_printf(&laad->pb, "%s: %s\n", name, para_strerror(-ret)); return ret; @@ -171,7 +177,7 @@ static void com_lsatt_callback(int fd, const struct osl_object *query) free(laad.pb.buf); } -int com_lsatt(int fd, int argc, char * const * const argv) +int com_lsatt(struct rc4_context *rc4c, int argc, char * const * const argv) { unsigned flags = 0; struct osl_object options = {.data = &flags, .size = sizeof(flags)}; @@ -199,12 +205,12 @@ int com_lsatt(int fd, int argc, char * const * const argv) } } ret = send_option_arg_callback_request(&options, argc - i, argv + i, - com_lsatt_callback, send_result, &fd); + com_lsatt_callback, rc4_send_result, rc4c); if (!ret) { if (argc > 1) - ret = send_va_buffer(fd, "no matches\n"); + ret = rc4_send_va_buffer(rc4c, "no matches\n"); } else if (ret < 0) - send_va_buffer(fd, "%s\n", para_strerror(-ret)); + rc4_send_va_buffer(rc4c, "%s\n", para_strerror(-ret)); return ret; } @@ -230,11 +236,11 @@ static void com_setatt_callback(__a_unused int fd, const struct osl_object *quer p[len - 1] = '\0'; obj.data = p; obj.size = len + 1; - ret = osl_get_row(attribute_table, ATTCOL_NAME, &obj, &row); + ret = osl(osl_get_row(attribute_table, ATTCOL_NAME, &obj, &row)); if (ret < 0) goto out; - ret = osl_get_object(attribute_table, row, ATTCOL_BITNUM, - &obj); + ret = osl(osl_get_object(attribute_table, row, ATTCOL_BITNUM, + &obj)); if (ret < 0) goto out; if (c == '+') @@ -272,7 +278,7 @@ out: PARA_NOTICE_LOG("%s\n", para_strerror(-ret)); } -int com_setatt(__a_unused int fd, int argc, char * const * const argv) +int com_setatt(__a_unused struct rc4_context *rc4c, int argc, char * const * const argv) { if (argc < 3) return -E_ATTR_SYNTAX; @@ -317,15 +323,15 @@ static void com_addatt_callback(int fd, const struct osl_object *query) goto out; continue; } - if (ret != -E_RB_KEY_NOT_FOUND) /* error */ + if (ret != -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND)) /* error */ goto out; objs[ATTCOL_BITNUM].size = 1; /* find smallest unused attribute */ for (bitnum = 0; bitnum < 64; bitnum++) { objs[ATTCOL_BITNUM].data = &bitnum; - ret = osl_get_row(attribute_table, ATTCOL_BITNUM, - &objs[ATTCOL_BITNUM], &row); - if (ret == -E_RB_KEY_NOT_FOUND) + ret = osl(osl_get_row(attribute_table, ATTCOL_BITNUM, + &objs[ATTCOL_BITNUM], &row)); + if (ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND)) break; /* this bitnum is unused, use it */ if (ret < 0) /* error */ goto out; @@ -337,7 +343,7 @@ static void com_addatt_callback(int fd, const struct osl_object *query) } objs[ATTCOL_NAME].data = p; objs[ATTCOL_NAME].size = len + 1; - ret = osl_add_row(attribute_table, objs); + ret = osl(osl_add_row(attribute_table, objs)); if (ret < 0) goto out; aed.name = p; @@ -353,16 +359,16 @@ out: free(pb.buf); } -int com_addatt(int fd, int argc, char * const * const argv) +int com_addatt(struct rc4_context *rc4c, int argc, char * const * const argv) { int ret; if (argc < 2) return -E_ATTR_SYNTAX; ret = send_standard_callback_request(argc - 1, argv + 1, com_addatt_callback, - send_result, &fd); + rc4_send_result, rc4c); if (ret < 0) - send_va_buffer(fd, "%s\n", para_strerror(-ret)); + rc4_send_va_buffer(rc4c, "%s\n", para_strerror(-ret)); return ret; } @@ -380,12 +386,12 @@ static void com_mvatt_callback(int fd, const struct osl_object *query) }; int ret; - ret = osl_get_row(attribute_table, ATTCOL_NAME, &obj, &row); + ret = osl(osl_get_row(attribute_table, ATTCOL_NAME, &obj, &row)); if (ret < 0) goto out; obj.data = new; obj.size = strlen(new) + 1; - ret = osl_update_object(attribute_table, row, ATTCOL_NAME, &obj); + ret = osl(osl_update_object(attribute_table, row, ATTCOL_NAME, &obj)); out: if (ret < 0) para_printf(&pb, "%s\n", para_strerror(-ret)); @@ -396,16 +402,16 @@ out: free(pb.buf); } -int com_mvatt(int fd, int argc, char * const * const argv) +int com_mvatt(struct rc4_context *rc4c, int argc, char * const * const argv) { int ret; if (argc != 3) return -E_ATTR_SYNTAX; ret = send_standard_callback_request(argc - 1, argv + 1, com_mvatt_callback, - send_result, &fd); + rc4_send_result, rc4c); if (ret < 0) - send_va_buffer(fd, "%s\n", para_strerror(-ret)); + rc4_send_va_buffer(rc4c, "%s\n", para_strerror(-ret)); return ret; } @@ -429,7 +435,7 @@ static int remove_attribute(struct osl_table *table, struct osl_row *row, ret = get_attribute_bitnum_by_name(name, &red.bitnum); if (ret < 0) return para_printf(&raad->pb, "%s: %s\n", name, para_strerror(-ret)); - ret = osl_del_row(table, row); + ret = osl(osl_del_row(table, row)); if (ret < 0) return para_printf(&raad->pb, "%s: %s\n", name, para_strerror(-ret)); ret = para_printf(&raad->pb, "removed attribute %s\n", name); @@ -468,16 +474,16 @@ static void com_rmatt_callback(int fd, const struct osl_object *query) free(raad.pb.buf); } -int com_rmatt(int fd, int argc, char * const * const argv) +int com_rmatt(struct rc4_context *rc4c, int argc, char * const * const argv) { int ret; if (argc < 2) return -E_ATTR_SYNTAX; ret = send_standard_callback_request(argc - 1, argv + 1, com_rmatt_callback, - send_result, &fd); + rc4_send_result, rc4c); if (ret < 0) - send_va_buffer(fd, "%s\n", para_strerror(-ret)); + rc4_send_va_buffer(rc4c, "%s\n", para_strerror(-ret)); return ret; } @@ -533,10 +539,10 @@ int get_attribute_text(uint64_t *atts, const char *delim, char **text) if (!(*atts & (one << i))) continue; - ret = osl_get_row(attribute_table, ATTCOL_BITNUM, &obj, &row); + ret = osl(osl_get_row(attribute_table, ATTCOL_BITNUM, &obj, &row)); if (ret < 0) goto err; - ret = osl_get_object(attribute_table, row, ATTCOL_NAME, &obj); + ret = osl(osl_get_object(attribute_table, row, ATTCOL_NAME, &obj)); if (ret < 0) goto err; if (*text) { @@ -559,7 +565,7 @@ err: * * \sa osl_close_table(). */ -void attribute_close(void) +static void attribute_close(void) { osl_close_table(attribute_table, OSL_MARK_CLEAN); attribute_table = NULL; @@ -579,14 +585,14 @@ static int attribute_open(const char *dir) int ret; attribute_table_desc.dir = dir; - ret = osl_open_table(&attribute_table_desc, &attribute_table); + ret = osl(osl_open_table(&attribute_table_desc, &attribute_table)); greatest_att_bitnum = -1; /* no atts available */ if (ret >= 0) { find_greatest_att_bitnum(); return ret; } attribute_table = NULL; - if (ret >= 0 || is_errno(-ret, ENOENT)) + if (ret >= 0 || ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_NOENT)) return 1; return ret; } @@ -594,7 +600,7 @@ static int attribute_open(const char *dir) static int attribute_create(const char *dir) { attribute_table_desc.dir = dir; - return osl_create_table(&attribute_table_desc); + return osl(osl_create_table(&attribute_table_desc)); } /** diff --git a/audioc.c b/audioc.c index 5c081da5..74fb824c 100644 --- a/audioc.c +++ b/audioc.c @@ -1,11 +1,12 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file audioc.c the client program used to connect to para_audiod */ +#include #include #include @@ -18,8 +19,8 @@ INIT_AUDIOC_ERRLISTS; -/** the gengetopt structure containing command line args */ -struct audioc_args_info conf; +/** The gengetopt structure containing command line args. */ +static struct audioc_args_info conf; static int loglevel; INIT_STDERR_LOGGING(loglevel); @@ -70,7 +71,7 @@ int main(int argc, char *argv[]) { int ret = -E_AUDIOC_SYNTAX, fd; char *cf, *buf = NULL, *args; - size_t bufsize, loaded = 0; + size_t bufsize; if (audioc_cmdline_parser(argc, argv, &conf)) goto out; @@ -92,72 +93,33 @@ int main(int argc, char *argv[]) args = conf.inputs_num? concat_args(conf.inputs_num, conf.inputs) : para_strdup("stat"); - bufsize = conf.bufsize_arg; - buf = para_malloc(bufsize); - if (conf.socket_given) { - ret = create_remote_socket(conf.socket_arg); - } else { - char *hn = para_hostname(), - *socket_name = make_message("/var/paraslash/audiod_socket.%s", hn); - - ret = create_remote_socket(socket_name); + if (conf.socket_given) + ret = connect_local_socket(conf.socket_arg); + else { + char *hn = para_hostname(), *socket_name = make_message( + "/var/paraslash/audiod_socket.%s", hn); + ret = connect_local_socket(socket_name); free(hn); free(socket_name); } - if (ret < 0) + if (ret < 0) { + PARA_EMERG_LOG("failed to connect to local socket\n"); goto out; + } fd = ret; - ret = mark_fd_nonblocking(fd); - if (ret < 0) - goto out; - ret = mark_fd_nonblocking(STDOUT_FILENO); - if (ret < 0) - goto out; ret = send_cred_buffer(fd, args); if (ret < 0) goto out; - for (;;) { - int max_fileno = -1, check_write = 0; - ssize_t len; - fd_set rfd, wfd; - FD_ZERO(&rfd); - FD_ZERO(&wfd); - if (loaded < bufsize) - para_fd_set(fd, &rfd, &max_fileno); - if (loaded > 0) { - para_fd_set(STDOUT_FILENO, &wfd, &max_fileno); - check_write = 1; - } - ret = -E_AUDIOC_OVERRUN; - if (max_fileno < 0) - goto out; - ret = para_select(max_fileno + 1, &rfd, &wfd, NULL); - if (ret < 0) - goto out; - if (loaded < bufsize && FD_ISSET(fd, &rfd)) { - len = recv_bin_buffer(fd, buf + loaded, - bufsize - loaded); - if (len <= 0) { - ret = len < 0? -E_AUDIOC_READ : 0; - goto out; - } - loaded += len; - } - if (check_write && FD_ISSET(STDOUT_FILENO, &wfd)) { - ret = write(STDOUT_FILENO, buf, loaded); - if (ret < 0) { - ret = -E_AUDIOC_WRITE; - goto out; - } - loaded -= ret; - if (loaded && ret) - memmove(buf, buf + ret, loaded); - } - } + bufsize = conf.bufsize_arg; + buf = para_malloc(bufsize); + do { + size_t n = ret = recv_bin_buffer(fd, buf, bufsize); + if (ret <= 0) + break; + ret = write_all(STDOUT_FILENO, buf, &n); + } while (ret >= 0); out: - if (!ret && loaded && buf) - ret = write(STDOUT_FILENO, buf, loaded); if (ret < 0) PARA_ERROR_LOG("%s\n", para_strerror(-ret)); return ret < 0? EXIT_FAILURE : EXIT_SUCCESS; diff --git a/audiod.c b/audiod.c index 8fbd0a00..64ea8a51 100644 --- a/audiod.c +++ b/audiod.c @@ -1,23 +1,27 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file audiod.c the paraslash's audio daemon */ +#include #include #include #include +#include +#include #include "para.h" #include "error.h" +#include "crypt.h" #include "audiod.cmdline.h" #include "list.h" #include "sched.h" #include "ggo.h" #include "recv.h" +#include "buffer_tree.h" #include "filter.h" -#include "grab_client.cmdline.h" #include "grab_client.h" #include "client.cmdline.h" #include "client.h" @@ -88,6 +92,7 @@ struct status_task { struct timeval restart_barrier; /** Last time we received status data from para_server. */ struct timeval last_status_read; + size_t min_iqs; /** The offset value announced by para_server. */ int offset_seconds; /** The length of the current audio file as announced by para_server. */ @@ -106,6 +111,8 @@ struct status_task { struct timeval clock_diff_barrier; /** Number of the audio format as announced by para_server. */ int current_audio_format_num; + /* The status task btrn is the child of the client task. */ + struct btr_node *btrn; }; /** The array of status items sent by para_server. */ @@ -136,7 +143,6 @@ static struct status_task status_task_struct; * \sa struct status_task */ static struct status_task *stat_task = &status_task_struct; -static struct timeval initial_delay_barrier; /** * the task for handling audiod commands @@ -154,13 +160,14 @@ struct command_task { #define FOR_EACH_AUDIO_FORMAT(af) for (af = 0; af < NUM_AUDIO_FORMATS; af++) /** - * get the audio format number - * \param name the name of the audio format + * Get the audio format number. + * + * \param name The name of the audio format. * * \return The audio format number on success, -E_UNSUPPORTED_AUDIO_FORMAT if * \a name is not a supported audio format. */ -int get_audio_format_num(char *name) +int get_audio_format_num(const char *name) { int i; @@ -172,13 +179,37 @@ int get_audio_format_num(char *name) return -E_UNSUPPORTED_AUDIO_FORMAT; } +/** + * Compute the play time based on information of the given slot. + * + * \param slot_num The slot number (negative means: no slot). + * + * This computes a string of the form "0:07 [3:33] (3%/3:40)" using information + * from the status items received from para_server and the start time of the + * (first) writer of the given slot. + * + * It has to to take into account that probably the stream was not started at + * the beginning of the file, that the clock between the server and the client + * host may differ and that playback of the stream was delayed, e.g. because + * the prebuffer filter is used in the filter configuration of the given slot. + * + * If no writer is active in the given slot, or \a slot_num is negative + * (indicating that para_audiod runs in standby mode), an approximation based + * only on the status items is computed and the returned string is prefixed + * with "~". + * + * \return A string that must be freed by the caller. + */ char *get_time_string(int slot_num) { int ret, seconds = 0, length; struct timeval *tmp, sum, sss, /* server stream start */ + rstime, /* receiver start time */ + wstime, /* writer start time */ wtime, /* now - writer start */ rskip; /* receiver start - sss */ struct slot_info *s = slot_num < 0? NULL : &slot[slot_num]; + char *msg; if (audiod_status == AUDIOD_OFF) goto empty; @@ -189,27 +220,41 @@ char *get_time_string(int slot_num) } if (audiod_status == AUDIOD_ON && !s) goto empty; - /* valid status items and playing */ - if (s) { /* writer active in this slot */ - length = s->seconds_total; - tmp = &s->server_stream_start; - } else { /* standby mode, rely on status items */ - length = stat_task->length_seconds; - tmp = &stat_task->server_stream_start; + /* + * Valid status items and playing, set length and tmp to the stream + * start. We use the slot info and fall back to the info from current + * status items if no slot info is available. + */ + length = stat_task->length_seconds; + tmp = &stat_task->server_stream_start; + if (s && s->wns) { /* writer active in this slot */ + btr_get_node_start(s->wns[0].btrn, &wstime); + if (wstime.tv_sec != 0) { /* writer wrote something */ + if (s->server_stream_start.tv_sec == 0) { + /* copy status info to slot */ + s->server_stream_start = stat_task->server_stream_start; + s->offset_seconds = stat_task->offset_seconds; + s->seconds_total = stat_task->length_seconds; + } + length = s->seconds_total; + tmp = &s->server_stream_start; + } } if (stat_task->sa_time_diff_sign > 0) tv_diff(tmp, &stat_task->sa_time_diff, &sss); else tv_add(tmp, &stat_task->sa_time_diff, &sss); - if (!s) { + if (!s || !s->wns) { struct timeval diff; tv_diff(now, &sss, &diff); seconds = diff.tv_sec + stat_task->offset_seconds; goto out; } - tv_diff(now, &s->wstime, &wtime); + tv_diff(now, &wstime, &wtime); + //PARA_CRIT_LOG("offset %d\n", s->offset_seconds); seconds = s->offset_seconds; - ret = tv_diff(&s->rstime, &sss, &rskip); + btr_get_node_start(s->receiver_node->btrn, &rstime); + ret = tv_diff(&rstime, &sss, &rskip); if (ret > 0) { /* audiod was started in the middle of the stream */ tv_add(&wtime, &rskip, &sum); seconds += sum.tv_sec; @@ -218,9 +263,8 @@ char *get_time_string(int slot_num) out: seconds = PARA_MIN(seconds, length); seconds = PARA_MAX(seconds, 0); - return make_message( - "%s: %s%d:%02d [%d:%02d] (%d%%/%d:%02d)\n", - status_item_list[SI_PLAY_TIME], + msg = make_message( + "%s%d:%02d [%d:%02d] (%d%%/%d:%02d)", s? "" : "~", seconds / 60, seconds % 60, @@ -230,8 +274,10 @@ out: length / 60, length % 60 ); + PARA_DEBUG_LOG("slot %d: %s\n", slot_num, msg); + return msg; empty: - return make_message("%s:\n", status_item_list[SI_PLAY_TIME]); + return para_strdup(NULL); } static int want_colors(void) @@ -309,30 +355,98 @@ static void close_receiver(int slot_num) PARA_NOTICE_LOG("closing %s receiver in slot %d\n", audio_formats[s->format], slot_num); a->receiver->close(s->receiver_node); + btr_free_node(s->receiver_node->btrn); free(s->receiver_node); s->receiver_node = NULL; - stat_task->current_audio_format_num = -1; + tv_add(now, &(struct timeval)EMBRACE(0, 200 * 1000), + &a->restart_barrier); } -static void kill_all_decoders(int error) +static void writer_cleanup(struct writer_node *wn) +{ + struct writer *w; + + if (!wn) + return; + w = writers + wn->writer_num; + PARA_INFO_LOG("closing %s\n", writer_names[wn->writer_num]); + w->close(wn); + btr_free_node(wn->btrn); +} + +static void close_writers(struct slot_info *s) +{ + struct audio_format_info *a; + int i; + + if (s->format < 0) + return; + assert(s->wns); + a = afi + s->format; + if (a->num_writers == 0) + writer_cleanup(s->wns); + else { + for (i = 0; i < a->num_writers; i++) + writer_cleanup(s->wns + i); + } + free(s->wns); + s->wns = NULL; +} + +static void close_filters(struct slot_info *s) { int i; + struct audio_format_info *a = afi + s->format; + if (a->num_filters == 0) + return; + for (i = 0; i < a->num_filters; i++) { + struct filter_node *fn = s->fns + i; + struct filter *f; + + if (!fn) + continue; + f = filters + fn->filter_num; + if (f->close) + f->close(fn); + btr_free_node(fn->btrn); + } + free(s->fns); + s->fns = NULL; +} + +/* + * Whenever a task commits suicide by returning from post_select with t->error + * < 0, it also removes its btr node. We do exactly that to kill a running + * task. Note that the scheduler checks t->error also _before_ each pre/post + * select call, so the victim will never be scheduled again. + */ +static void kill_btrn(struct btr_node *btrn, struct task *t, int error) +{ + if (t->error < 0) + return; + t->error = error; + btr_remove_node(btrn); +} + +static void kill_all_decoders(int error) +{ + int i, j; FOR_EACH_SLOT(i) { struct slot_info *s = &slot[i]; - if (s->wng && s->wng->task.error >= 0) { - PARA_INFO_LOG("deactivating wng in slot %d\n", - i); - s->wng->task.error = error; - } - if (s->fc && s->fc->task.error >= 0) { - PARA_INFO_LOG("deactivatimg filter chain in slot %d\n", i); - s->fc->task.error = error; - } - if (s->receiver_node && s->receiver_node->task.error >= 0) { - PARA_INFO_LOG("deactivating receiver_node in slot %d\n", i); - s->receiver_node->task.error = error; - } + struct audio_format_info *a; + if (s->format < 0) + continue; + a = afi + s->format; + if (s->wns) + for (j = 0; j < a->num_writers; j++) + kill_btrn(s->wns[j].btrn, &s->wns[j].task, error); + if (s->fns) + for (j = 0; j < a->num_writers; j++) + kill_btrn(s->fns[j].btrn, &s->wns[j].task, error); + if (s->receiver_node) + kill_btrn(s->receiver_node->btrn, &s->receiver_node->task, + error); } } @@ -347,7 +461,7 @@ static int get_empty_slot(void) clear_slot(i); return i; } - if (s->wng || s->receiver_node || s->fc) + if (s->wns || s->receiver_node || s->fns) continue; clear_slot(i); return i; @@ -369,172 +483,173 @@ int num_filters(int audio_format_num) return afi[audio_format_num].num_filters; } -static void open_filters(int slot_num) +static void open_filters(struct slot_info *s) { - struct slot_info *s = &slot[slot_num]; - struct audio_format_info *a = &afi[s->format]; + struct audio_format_info *a = afi + s->format; struct filter_node *fn; int nf = a->num_filters; + struct btr_node *parent; int i; - s->fc = NULL; - if (!nf) + if (nf == 0) return; PARA_INFO_LOG("opening %s filters\n", audio_formats[s->format]); - s->fc = para_calloc(sizeof(struct filter_chain)); - s->fc->filter_nodes = para_malloc(nf * sizeof(struct filter_node)); - s->fc->inbufp = &s->receiver_node->buf; - s->fc->in_loaded = &s->receiver_node->loaded; - s->fc->input_error = &s->receiver_node->task.error; - s->fc->task.pre_select = NULL; - s->fc->task.post_select = filter_post_select; - s->fc->task.error = 0; - s->fc->num_filters = nf; - - s->receiver_node->output_error = &s->fc->task.error; - sprintf(s->fc->task.status, "filter chain"); - FOR_EACH_FILTER_NODE(fn, s->fc, i) { + assert(s->fns == NULL); + s->fns = para_calloc(nf * sizeof(struct filter_node)); + parent = s->receiver_node->btrn; + for (i = 0; i < nf; i++) { struct filter *f = filters + a->filter_nums[i]; + fn = s->fns + i; fn->filter_num = a->filter_nums[i]; fn->conf = a->filter_conf[i]; - fn->fc = s->fc; - fn->loaded = 0; - INIT_LIST_HEAD(&fn->callbacks); + fn->task.pre_select = f->pre_select; + fn->task.post_select = f->post_select; + + fn->btrn = btr_new_node(&(struct btr_node_description) + EMBRACE(.name = f->name, .parent = parent, + .handler = f->execute, .context = fn)); + f->open(fn); + register_task(&fn->task); + parent = fn->btrn; PARA_NOTICE_LOG("%s filter %d/%d (%s) started in slot %d\n", - audio_formats[s->format], i, nf, f->name, slot_num); - s->fc->outbufp = &fn->buf; - s->fc->out_loaded = &fn->loaded; + audio_formats[s->format], i, nf, f->name, (int)(s - slot)); + sprintf(fn->task.status, "%s (slot %d)", f->name, (int)(s - slot)); } - register_task(&s->fc->task); } -static void open_writers(int slot_num) +static void open_writers(struct slot_info *s) { - int ret, i; - struct slot_info *s = &slot[slot_num]; - struct audio_format_info *a = &afi[s->format]; - - PARA_INFO_LOG("opening %s writers\n", audio_formats[s->format]); - if (!a->num_writers) - s->wng = setup_default_wng(); - else - s->wng = wng_new(a->num_writers); - if (s->fc) { - s->wng->bufp = s->fc->outbufp; - s->wng->loaded = s->fc->out_loaded; - s->wng->input_error = &s->fc->task.error; - s->wng->channels = &s->fc->channels; - s->wng->samplerate = &s->fc->samplerate; - s->fc->output_error = &s->wng->task.error; - PARA_INFO_LOG("samplerate: %d\n", *s->wng->samplerate); - } else { - s->wng->bufp = &s->receiver_node->buf; - s->wng->loaded = &s->receiver_node->loaded; - s->wng->input_error = &s->receiver_node->task.error; - } - for (i = 0; i < a->num_writers; i++) { - s->wng->writer_nodes[i].conf = a->writer_conf[i]; - s->wng->writer_nodes[i].writer_num = a->writer_nums[i]; + int i; + struct audio_format_info *a = afi + s->format; + struct writer_node *wn; + struct btr_node *parent = s->fns[a->num_filters - 1].btrn; + + assert(s->wns == NULL); + s->wns = para_calloc(PARA_MAX(1U, a->num_writers) + * sizeof(struct writer_node)); + if (a->num_writers == 0) + setup_writer_node(NULL, parent, s->wns); + else { + PARA_INFO_LOG("opening %s writers\n", audio_formats[s->format]); + for (i = 0; i < a->num_writers; i++) { + wn = s->wns + i; + wn->conf = a->writer_conf[i]; + wn->writer_num = a->writer_nums[i]; + register_writer_node(wn, parent); + } } - ret = wng_open(s->wng); - if (ret < 0) - return; - s->wstime = *now; - s->server_stream_start = stat_task->server_stream_start.tv_sec? - stat_task->server_stream_start : *now; - s->offset_seconds = stat_task->offset_seconds; - s->seconds_total = stat_task->length_seconds; - activate_inactive_grab_clients(slot_num, s->format, s->fc); } +/* returns slot num on success */ static int open_receiver(int format) { struct audio_format_info *a = &afi[format]; struct slot_info *s; int ret, slot_num; + struct receiver *r = a->receiver; struct receiver_node *rn; - const struct timeval restart_delay = {2, 0}; + tv_add(now, &(struct timeval)EMBRACE(2, 0), &a->restart_barrier); ret = get_empty_slot(); if (ret < 0) - goto err; + return ret; slot_num = ret; - s = &slot[slot_num]; - s->format = format; - s->receiver_node = para_calloc(sizeof(struct receiver_node)); - rn = s->receiver_node; - rn->receiver = a->receiver; + rn = para_calloc(sizeof(*rn)); + rn->receiver = r; rn->conf = a->receiver_conf; - ret = a->receiver->open(s->receiver_node); + rn->btrn = btr_new_node(&(struct btr_node_description) + EMBRACE(.name = r->name, .context = rn)); + ret = r->open(rn); if (ret < 0) { - free(s->receiver_node); - s->receiver_node = NULL; - goto err; + btr_free_node(rn->btrn); + free(rn); + return ret; } + s = &slot[slot_num]; + s->format = format; + s->receiver_node = rn; PARA_NOTICE_LOG("started %s: %s receiver in slot %d\n", - audio_formats[s->format], a->receiver->name, slot_num); - rn->task.pre_select = a->receiver->pre_select; - rn->task.post_select = a->receiver->post_select; - s->rstime = *now; - sprintf(rn->task.status, "%s receiver node", rn->receiver->name); + audio_formats[format], r->name, slot_num); + rn->task.pre_select = r->pre_select; + rn->task.post_select = r->post_select; + sprintf(rn->task.status, "%s receiver node", r->name); register_task(&rn->task); - ret = 1; -err: - if (ret < 0) - PARA_ERROR_LOG("%s\n", para_strerror(-ret)); - tv_add(now, &restart_delay, &afi[format].restart_barrier); - return ret; + return slot_num; } -/* return: 0: Not running, 1: Running, -1: Running but eof (or error) */ -static int receiver_running(int format) +static bool receiver_running(void) { - int i, ret = 0; + int i; + long unsigned ss1 = stat_task->server_stream_start.tv_sec; FOR_EACH_SLOT(i) { struct slot_info *s = &slot[i]; - if (s->format != format) - continue; + long unsigned ss2 = s->server_stream_start.tv_sec; + if (!s->receiver_node) continue; if (s->receiver_node->task.error >= 0) - return 1; - ret = -1; + return true; + if (ss1 == ss2) + return true; } - return ret; + return false; } -static void open_current_receiver(struct sched *s) +/** + * Return the root node of the current buffer tree. + * + * This is only used for stream grabbing. + * + * \return \p NULL if no slot is currently active. If more than one buffer tree + * exists, the node corresponding to the most recently started receiver is + * returned. + */ +struct btr_node *audiod_get_btr_root(void) { - struct timeval diff; - int ret, cafn = stat_task->current_audio_format_num; + int i, newest_slot = -1; + struct timeval newest_rstime = {0, 0}; - if (cafn < 0 || !stat_task->ct) - return; - /* Do nothing if the 'N' flag is set or the 'P' flag is unset */ - if (stat_task->vss_status != VSS_STATUS_FLAG_PLAYING) - return; - ret = receiver_running(cafn); - if (ret > 0) /* already running and not eof */ - return; - if (ret < 0) { /* eof */ - /* - * para_server uses a zero start time during the announcement - * period, i.e. before it sends the first chunk. Wait until - * this period begins to avoid restarting the receiver that - * belongs to the file just completed. - */ - if (stat_task->server_stream_start.tv_sec) - return; - } - if (tv_diff(now, &afi[cafn].restart_barrier, &diff) < 0) { - /* avoid busy loop */ - s->timeout = diff; - return; + FOR_EACH_SLOT(i) { + struct slot_info *s = &slot[i]; + struct timeval rstime; + if (!s->receiver_node) + continue; + if (s->receiver_node->task.error < 0) + continue; + btr_get_node_start(s->receiver_node->btrn, &rstime); + if (newest_slot >= 0 && tv_diff(&rstime, &newest_rstime, NULL) < 0) + continue; + newest_rstime = rstime; + newest_slot = i; } - /* start a new receiver */ - open_receiver(cafn); + if (newest_slot == -1) + return NULL; + return slot[newest_slot].receiver_node->btrn; +} + +/* whether a new instance of a decoder should be started. */ +static bool must_start_decoder(void) +{ + int cafn = stat_task->current_audio_format_num; + unsigned vs = stat_task->vss_status; + + if (audiod_status != AUDIOD_ON) + return false; + if (cafn < 0) + return false; + if (!stat_task->ct) + return false; + if (vs & VSS_STATUS_FLAG_NEXT) + return false; + if (!(vs & VSS_STATUS_FLAG_PLAYING)) + return false; + if (receiver_running()) + return false; + if (tv_diff(now, &afi[cafn].restart_barrier, NULL) < 0) + return false; + return true; } static unsigned compute_time_diff(const struct timeval *status_time) @@ -569,9 +684,9 @@ static unsigned compute_time_diff(const struct timeval *status_time) &tmp); stat_task->sa_time_diff = tmp; PARA_INFO_LOG("time diff (cur/avg): %s%lums/%s%lums\n", - sign > 0? "+" : "-", + sign < 0? "-" : "+", tv2ms(&diff), - sa_time_diff_sign ? "+" : "-", + sa_time_diff_sign < 0? "-" : "+", tv2ms(&stat_task->sa_time_diff) ); out: @@ -579,70 +694,44 @@ out: return count; } -static int check_stat_line(char *line, __a_unused void *data) +static int update_item(int itemnum, char *buf) { - int itemnum; - size_t ilen = 0; long unsigned sec, usec; - char *tmp; - //PARA_INFO_LOG("line: %s\n", line); - if (!line) - return 1; - itemnum = stat_line_valid(line); - if (itemnum < 0) { - PARA_WARNING_LOG("invalid status line: %s\n", line); - return 1; - } if (stat_task->clock_diff_count && itemnum != SI_CURRENT_TIME) return 1; - tmp = make_message("%s\n", line); - stat_client_write(tmp, itemnum); - free(tmp); free(stat_item_values[itemnum]); - stat_item_values[itemnum] = para_strdup(line); - ilen = strlen(status_item_list[itemnum]); + stat_item_values[itemnum] = para_strdup(buf); + stat_client_write_item(itemnum); switch (itemnum) { case SI_STATUS_FLAGS: stat_task->vss_status = 0; - if (strchr(line, 'N')) + if (strchr(buf, 'N')) stat_task->vss_status |= VSS_STATUS_FLAG_NEXT; - if (strchr(line, 'P')) + if (strchr(buf, 'P')) stat_task->vss_status |= VSS_STATUS_FLAG_PLAYING; break; case SI_OFFSET: - stat_task->offset_seconds = atoi(line + ilen + 1); + stat_task->offset_seconds = atoi(buf); break; case SI_SECONDS_TOTAL: - stat_task->length_seconds = atoi(line + ilen + 1); + stat_task->length_seconds = atoi(buf); break; case SI_STREAM_START: - if (sscanf(line + ilen + 1, "%lu.%lu", &sec, &usec) == 2) { - struct timeval a_start, delay; - delay.tv_sec = conf.stream_delay_arg / 1000; - delay.tv_usec = (conf.stream_delay_arg % 1000) * 1000; + if (sscanf(buf, "%lu.%lu", &sec, &usec) == 2) { stat_task->server_stream_start.tv_sec = sec; stat_task->server_stream_start.tv_usec = usec; - if (compute_time_diff(NULL) > 2) { - if (stat_task->sa_time_diff_sign < 0) - tv_add(&stat_task->server_stream_start, - &stat_task->sa_time_diff, &a_start); - else - tv_diff(&stat_task->server_stream_start, - &stat_task->sa_time_diff, &a_start); - tv_add(&a_start, &delay, &initial_delay_barrier); - } } break; case SI_CURRENT_TIME: - if (sscanf(line + ilen + 1, "%lu.%lu", &sec, &usec) == 2) { + if (sscanf(buf, "%lu.%lu", &sec, &usec) == 2) { struct timeval tv = {sec, usec}; compute_time_diff(&tv); } break; case SI_FORMAT: - stat_task->current_audio_format_num = get_audio_format_num( - line + ilen + 1); + stat_task->current_audio_format_num + = get_audio_format_num(buf); } return 1; } @@ -772,6 +861,18 @@ static int init_default_filters(void) if (a->num_filters) continue; /* no default -- nothing to to */ + /* + * If udp is used to receive this audiod format, add fecdec as + * the first filter. + */ + if (strcmp(afi[i].receiver->name, "udp") == 0 || + strcmp(afi[i].receiver->name, "dccp") == 0) { + tmp = para_strdup("fecdec"); + add_filter(i, tmp); + free(tmp); + if (ret < 0) + goto out; + } /* add "dec" to audio format name */ tmp = make_message("%sdec", audio_formats[i]); for (j = 0; filters[j].name; j++) @@ -797,7 +898,7 @@ static int parse_filter_args(void) { int i, ret, nf; - nf = PARA_MAX(1U, conf.filter_given); + nf = PARA_MAX(2U, conf.filter_given); PARA_INFO_LOG("maximal number of filters: %d\n", nf); FOR_EACH_AUDIO_FORMAT(i) { afi[i].filter_conf = para_malloc(nf * sizeof(void *)); @@ -882,19 +983,16 @@ static void signal_pre_select(struct sched *s, struct task *t) para_fd_set(st->fd, &s->rfds, &s->max_fileno); } -static void signal_post_select(struct sched *s, struct task *t) +static void signal_post_select(struct sched *s, __a_unused struct task *t) { - struct signal_task *st = container_of(t, struct signal_task, task); + int signum; - if (!FD_ISSET(st->fd, &s->rfds)) - return; - - st->signum = para_next_signal(); - switch (st->signum) { + signum = para_next_signal(&s->rfds); + switch (signum) { case SIGINT: case SIGTERM: case SIGHUP: - PARA_EMERG_LOG("terminating on signal %d\n", st->signum); + PARA_EMERG_LOG("terminating on signal %d\n", signum); clean_exit(EXIT_FAILURE, "caught deadly signal"); } } @@ -916,13 +1014,19 @@ static void command_post_select(struct sched *s, struct task *t) { int ret; struct command_task *ct = container_of(t, struct command_task, task); + static struct timeval last_status_dump; + struct timeval tmp, delay = {0, 500 * 1000}; - audiod_status_dump(); - if (!FD_ISSET(ct->fd, &s->rfds)) - return; - ret = handle_connect(ct->fd); + tv_add(&last_status_dump, &delay, &tmp); + if (tv_diff(&tmp, now, NULL) < 0) { + audiod_status_dump(); + last_status_dump = *now; + } + + ret = handle_connect(ct->fd, &s->rfds); if (ret < 0) PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + audiod_status_dump(); } static void init_command_task(struct command_task *ct) @@ -936,17 +1040,12 @@ static void init_command_task(struct command_task *ct) static void close_stat_pipe(void) { - int i; - if (!stat_task->ct) return; + btr_free_node(stat_task->ct->btrn); client_close(stat_task->ct); stat_task->ct = NULL; - FOR_EACH_STATUS_ITEM(i) { - free(stat_item_values[i]); - stat_item_values[i] = NULL; - } - dump_empty_status(); + clear_and_dump_items(); stat_task->length_seconds = 0; stat_task->offset_seconds = 0; stat_task->vss_status = 0; @@ -985,19 +1084,27 @@ static void set_stat_task_restart_barrier(unsigned seconds) static void try_to_close_slot(int slot_num) { struct slot_info *s = &slot[slot_num]; + struct audio_format_info *a = afi + s->format; + int i; if (s->format < 0) return; if (s->receiver_node && s->receiver_node->task.error != -E_TASK_UNREGISTERED) return; - if (s->fc && s->fc->task.error != -E_TASK_UNREGISTERED) - return; - if (s->wng && s->wng->task.error != -E_TASK_UNREGISTERED) - return; + for (i = 0; i < a->num_filters; i++) + if (s->fns && s->fns[i].task.error != -E_TASK_UNREGISTERED) + return; + if (a->num_writers > 0) { + for (i = 0; i < a->num_writers; i++) + if (s->wns && s->wns[i].task.error != -E_TASK_UNREGISTERED) + return; + } else { + if (s->wns && s->wns[0].task.error != -E_TASK_UNREGISTERED) + return; + } PARA_INFO_LOG("closing slot %d\n", slot_num); - wng_close(s->wng); - close_filters(s->fc); - free(s->fc); + close_writers(s); + close_filters(s); close_receiver(slot_num); clear_slot(slot_num); } @@ -1006,54 +1113,63 @@ static void try_to_close_slot(int slot_num) * Check if any receivers/filters/writers need to be started and do so if * necessary. */ -static void start_stop_decoders(struct sched *s) +static void start_stop_decoders(void) { - int i; + int i, ret; + struct slot_info *sl; + struct audio_format_info *a; FOR_EACH_SLOT(i) try_to_close_slot(i); if (audiod_status != AUDIOD_ON || !(stat_task->vss_status & VSS_STATUS_FLAG_PLAYING)) return kill_all_decoders(-E_NOT_PLAYING); - open_current_receiver(s); - FOR_EACH_SLOT(i) { - struct slot_info *sl = &slot[i]; - struct audio_format_info *a; - struct timeval diff; - - if (sl->format < 0) - continue; - a = &afi[sl->format]; - if (!sl->receiver_node) - continue; - if ((!a->num_filters || sl->fc) && sl->wng) - continue; /* everything already started */ - if (!a->num_filters) { - if (sl->receiver_node->loaded && !sl->wng) { - open_writers(i); - } - continue; - } - if (sl->receiver_node->loaded && !sl->fc) { - open_filters(i); - continue; - } - if (sl->wng || !sl->fc || !*sl->fc->out_loaded) - continue; - if (tv_diff(now, &initial_delay_barrier, &diff) > 0) { - open_writers(i); - continue; - } - PARA_INFO_LOG("initial delay: %lu ms left\n", tv2ms(&diff)); - if (tv_diff(&s->timeout, &diff, NULL) > 0) { - s->timeout = diff; - } + if (!must_start_decoder()) + return; + ret = open_receiver(stat_task->current_audio_format_num); + if (ret < 0) { + PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + return; } + sl = slot + ret; + a = afi + sl->format; + if (a->num_filters) + open_filters(sl); + open_writers(sl); + activate_grab_clients(); + btr_log_tree(sl->receiver_node->btrn, LL_NOTICE); } +static void status_pre_select(struct sched *s, struct task *t) +{ + struct status_task *st = container_of(t, struct status_task, task); + int ret, cafn = stat_task->current_audio_format_num; + + if (must_start_decoder()) + goto min_delay; + ret = btr_node_status(st->btrn, 0, BTR_NT_LEAF); + if (ret > 0) + goto min_delay; + if (st->ct && audiod_status == AUDIOD_OFF) + goto min_delay; + if (!st->ct && audiod_status != AUDIOD_OFF) + sched_request_barrier_or_min_delay(&st->restart_barrier, s); + if (cafn >= 0) + sched_request_barrier(&afi[cafn].restart_barrier, s); + /* + * If para_server is playing we'd like to have a smooth time display + * even if we are running in standby mode. So we request a timeout that + * expires at the next full second. + */ + if (stat_task->vss_status & VSS_STATUS_FLAG_PLAYING) + sched_request_timeout_ms(1000 - now->tv_usec / 1000, s); + return; +min_delay: + sched_min_delay(s); +} /* restart the client task if necessary */ -static void status_pre_select(struct sched *s, struct task *t) +static void status_post_select(__a_unused struct sched *s, struct task *t) { struct status_task *st = container_of(t, struct status_task, task); @@ -1061,7 +1177,7 @@ static void status_pre_select(struct sched *s, struct task *t) if (!st->ct) goto out; if (st->ct->task.error >= 0) { - st->ct->task.error = -E_AUDIOD_OFF; + kill_btrn(st->ct->btrn, &st->ct->task, -E_AUDIOD_OFF); goto out; } if (st->ct->task.error != -E_TASK_UNREGISTERED) @@ -1071,7 +1187,9 @@ static void status_pre_select(struct sched *s, struct task *t) goto out; } if (st->ct) { - unsigned bytes_left; + char *buf; + size_t sz; + int ret; if (st->ct->task.error < 0) { if (st->ct->task.error != -E_TASK_UNREGISTERED) goto out; @@ -1080,54 +1198,68 @@ static void status_pre_select(struct sched *s, struct task *t) } if (st->ct->status != CL_RECEIVING) goto out; - bytes_left = for_each_line(st->ct->buf, st->ct->loaded, - &check_stat_line, NULL); - if (st->ct->loaded != bytes_left) { - st->last_status_read = *now; - st->ct->loaded = bytes_left; - } else { + ret = btr_node_status(st->btrn, st->min_iqs, BTR_NT_LEAF); + if (ret <= 0) { struct timeval diff; tv_diff(now, &st->last_status_read, &diff); if (diff.tv_sec > 61) - st->ct->task.error = -E_STATUS_TIMEOUT; + kill_btrn(st->ct->btrn, &st->ct->task, + -E_STATUS_TIMEOUT); + goto out; } + btr_merge(st->btrn, st->min_iqs); + sz = btr_next_buffer(st->btrn, &buf); + ret = for_each_stat_item(buf, sz, update_item); + if (ret < 0) { + kill_btrn(st->ct->btrn, &st->ct->task, ret); + goto out; + } + if (sz != ret) { + btr_consume(st->btrn, sz - ret); + st->last_status_read = *now; + st->min_iqs = 0; + } else /* current status item crosses buffers */ + st->min_iqs = sz + 1; goto out; } + btr_drain(st->btrn); + st->current_audio_format_num = -1; if (tv_diff(now, &st->restart_barrier, NULL) < 0) goto out; if (st->clock_diff_count) { /* get status only one time */ - char *argv[] = {"audiod", "stat", "1", NULL}; - int argc = 3; + char *argv[] = {"audiod", "--", "stat", "-p", "-n=1", NULL}; + int argc = 5; PARA_INFO_LOG("clock diff count: %d\n", st->clock_diff_count); st->clock_diff_count--; - client_open(argc, argv, &st->ct, NULL); + client_open(argc, argv, &st->ct, NULL, NULL, st->btrn); set_stat_task_restart_barrier(2); } else { - char *argv[] = {"audiod", "stat", NULL}; - int argc = 2; - client_open(argc, argv, &st->ct, NULL); + char *argv[] = {"audiod", "--", "stat", "-p", NULL}; + int argc = 4; + client_open(argc, argv, &st->ct, NULL, NULL, st->btrn); set_stat_task_restart_barrier(5); } free(stat_item_values[SI_BASENAME]); - stat_item_values[SI_BASENAME] = make_message( - "%s: no connection to para_server\n", - status_item_list[SI_BASENAME]); - stat_client_write(stat_item_values[SI_BASENAME], - SI_BASENAME); + stat_item_values[SI_BASENAME] = para_strdup( + "no connection to para_server"); + stat_client_write_item(SI_BASENAME); st->last_status_read = *now; out: - start_stop_decoders(s); + start_stop_decoders(); } static void init_status_task(struct status_task *st) { memset(st, 0, sizeof(struct status_task)); st->task.pre_select = status_pre_select; + st->task.post_select = status_post_select; st->sa_time_diff_sign = 1; st->clock_diff_count = conf.clock_diff_count_arg; st->current_audio_format_num = -1; - sprintf(st->task.status, "status task"); + sprintf(st->task.status, "stat"); + st->btrn = btr_new_node(&(struct btr_node_description) + EMBRACE(.name = "stat")); } static void set_initial_status(void) @@ -1166,17 +1298,14 @@ __noreturn static void print_help_and_die(void) static void init_colors_or_die(void) { - int ret, i; + int i; if (!want_colors()) return; daemon_set_default_log_colors(); daemon_set_flag(DF_COLOR_LOG); - for (i = 0; i < conf.log_color_given; i++) { - ret = daemon_set_log_color(conf.log_color_arg[i]); - if (ret < 0) - exit(EXIT_FAILURE); - } + for (i = 0; i < conf.log_color_given; i++) + daemon_set_log_color_or_die(conf.log_color_arg[i]); } /** @@ -1215,9 +1344,12 @@ int main(int argc, char *argv[]) drop_privileges_or_die(conf.user_arg, conf.group_arg); parse_config_or_die(); init_colors_or_die(); + init_random_seed_or_die(); daemon_set_flag(DF_LOG_TIME); daemon_set_flag(DF_LOG_HOSTNAME); daemon_set_flag(DF_LOG_LL); + if (conf.log_timing_given) + daemon_set_flag(DF_LOG_TIMING); if (conf.logfile_given) { daemon_set_logfile(conf.logfile_arg); daemon_open_log_or_die(); @@ -1232,7 +1364,6 @@ int main(int argc, char *argv[]) set_initial_status(); FOR_EACH_SLOT(i) clear_slot(i); - init_grabbing(); setup_signal_handling(); signal_setup_default(sig_task); @@ -1245,8 +1376,8 @@ int main(int argc, char *argv[]) register_task(&sig_task->task); register_task(&cmd_task->task); register_task(&stat_task->task); - s.default_timeout.tv_sec = 0; - s.default_timeout.tv_usec = 99 * 1000; + s.default_timeout.tv_sec = 2; + s.default_timeout.tv_usec = 999 * 1000; ret = schedule(&s); PARA_EMERG_LOG("%s\n", para_strerror(-ret)); diff --git a/audiod.cmd b/audiod.cmd index 5469476c..7ffe98e7 100644 --- a/audiod.cmd +++ b/audiod.cmd @@ -14,11 +14,32 @@ H: on -> standby -> off -> on N: grab D: grab the audio stream L: -U: -- grab [grab_options] -H: grab ('splice') the audio stream at any position in the filter -H: chain and send that data back to the client. Try -H: para_audioc -- grab -h -H: for the list of available options. +U: -- grab [-m[{s|p|a}]] [-p=] [-n=] [-o] +H: +H: grab ('splice') the audio stream at any position in the buffer +H: tree and send that data back to the client. +H: +H: Options: +H: +H: -m Change grab mode. Defaults to sloppy grab if not given. +H: +H: -ms: sloppy grab +H: +H: -mp: pedantic grab +H: +H: -ma: aggressive grab +H: +H: The various grab modes only differ in what happens if the +H: file descriptor to write the grabbed audio data to is not +H: ready for writing (i.e. would block). Sloppy mode ignores +H: the write, pedantic mode aborts and aggressive mode tries +H: to write anyway. +H: +H: -p Grab output of node of the buffer tree. +H: +H: -n Name of the new buffer tree node. Defaults to 'grab'. +H: +H: -o One-shot mode: Stop grabbing if audio file changes. --- N: help D: display command list or help for given command @@ -51,8 +72,9 @@ H: Stop all decoders but leave connection to para_server open. --- N: stat D: print status information -U: stat [item1 ...] -H: Dump given status items (all if none given) to stdout. +U: stat [-p] [item1 ...] +H: Dump given status items (all if none given) to stdout. If -p is given, use +H: parser-friendly mode. --- N: tasks D: list current tasks diff --git a/audiod.h b/audiod.h index 8b416ce5..94399602 100644 --- a/audiod.h +++ b/audiod.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -8,7 +8,7 @@ int num_filters(int audio_format_num); -int get_audio_format_num(char *name); +int get_audio_format_num(const char *name); /** enum of audio formats supported by para_audiod */ enum {AUDIOD_AUDIO_FORMATS_ENUM}; @@ -34,12 +34,6 @@ struct audiod_command { const char *name; /** pointer to the function that handles the command */ int (*handler)(int, int, char**); - /** - * if the command prefers to handle the full line (rather than the usual - * argv[] array), it stores a pointer to the corresponding line handling - * function here. In this case, the above \a handler pointer must be NULL. - */ - int (*line_handler)(int, char*); /** one-line description of the command */ const char *description; /** summary of the command line options */ @@ -51,16 +45,12 @@ struct audiod_command { /** * Describes one instance of a receiver-filter-writer chain. * - * \sa receiver_node, receiver, filter, filter_node, filter_chain, writer, - * writer_node, writer_node_group. + * \sa receiver_node, receiver, filter, filter_node, writer, writer_node, + * writer_node_group. */ struct slot_info { /** Number of the audio format in this slot. */ int format; - /** Receiver start time. */ - struct timeval rstime; - /** Writer start time. */ - struct timeval wstime; /** The stream_start status item announced by para_server. */ struct timeval server_stream_start; /** The offset status item announced by para_server. */ @@ -69,10 +59,10 @@ struct slot_info { unsigned seconds_total; /** The receiver info associated with this slot. */ struct receiver_node *receiver_node; - /** The active filter chain. */ - struct filter_chain *fc; - /** The active writer node group. */ - struct writer_node_group *wng; + /** The array of filter nodes. */ + struct filter_node *fns; + /** The array of writers attached to the last filter. */ + struct writer_node *wns; }; extern struct slot_info slot[MAX_STREAM_SLOTS]; @@ -80,10 +70,13 @@ extern struct audiod_args_info conf; extern int audiod_status; void __noreturn clean_exit(int status, const char *msg); -int handle_connect(int accept_fd); +int handle_connect(int accept_fd, fd_set *rfds); void audiod_status_dump(void); -void dump_empty_status(void); char *get_time_string(int slot_num); +struct btr_node *audiod_get_btr_root(void); + +void stat_client_write_item(int item_num); +void clear_and_dump_items(void); /** iterate over all slots */ #define FOR_EACH_SLOT(_slot) for (_slot = 0; _slot < MAX_STREAM_SLOTS; _slot++) diff --git a/audiod_command.c b/audiod_command.c index 4706131d..eed9fc15 100644 --- a/audiod_command.c +++ b/audiod_command.c @@ -1,41 +1,186 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file audiod_command.c commands for para_audiod */ +#include #include #include +#include #include "para.h" #include "audiod.cmdline.h" #include "list.h" -#include "close_on_fork.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "filter.h" -#include "grab_client.cmdline.h" #include "grab_client.h" - #include "error.h" #include "audiod.h" #include "net.h" #include "daemon.h" #include "string.h" +#include "write.h" #include "fd.h" #include "audiod_command_list.h" extern char *stat_item_values[NUM_STAT_ITEMS]; - -/** iterate over the array of all audiod commands */ +/** Iterate over the array of all audiod commands. */ #define FOR_EACH_COMMAND(c) for (c = 0; audiod_cmds[c].name; c++) +/** The maximal number of simultaneous connections. */ +#define MAX_STAT_CLIENTS 50 + +/** Flags used for the stat command of para_audiod. */ +enum stat_client_flags { + /** Enable parser-friendly output. */ + SCF_PARSER_FRIENDLY = 1, +}; + +/** + * Describes a status client of para_audiod. + * + * There's one such structure per audiod client that sent the 'stat' command. + * + * A status client is identified by its file descriptor. para_audiod + * keeps a list of connected status clients. + */ +struct stat_client { + /** The stat client's file descriptor. */ + int fd; + /** Bitmask of those status items the client is interested in. */ + uint64_t item_mask; + /** See \ref stat_client flags. s*/ + unsigned flags; + /** Its entry in the list of stat clients. */ + struct list_head node; +}; + +static INITIALIZED_LIST_HEAD(client_list); +static int num_clients; + +/** The list of all status items used by para_{server,audiod,gui}. */ +const char *status_item_list[] = {STATUS_ITEM_ARRAY}; + +static void dump_stat_client_list(void) +{ + struct stat_client *sc; + + list_for_each_entry(sc, &client_list, node) + PARA_INFO_LOG("stat client on fd %d\n", sc->fd); +} +/** + * Add a status client to the list. + * + * \param fd The file descriptor of the client. + * \param mask Bitfield of status items for this client. + * \param parser_friendly Enable parser-friendly output mode. + * + * Only those status items having the bit set in \a mask will be + * sent to the client. + * + * \return Positive value on success, or -E_TOO_MANY_CLIENTS if + * the number of connected clients exceeds #MAX_STAT_CLIENTS. + */ +static int stat_client_add(int fd, uint64_t mask, int parser_friendly) +{ + struct stat_client *new_client; + + if (num_clients >= MAX_STAT_CLIENTS) { + PARA_ERROR_LOG("maximal number of stat clients (%d) exceeded\n", + MAX_STAT_CLIENTS); + return -E_TOO_MANY_CLIENTS; + } + PARA_INFO_LOG("adding client on fd %d\n", fd); + new_client = para_calloc(sizeof(struct stat_client)); + new_client->fd = fd; + new_client->item_mask = mask; + if (parser_friendly) + new_client->flags = SCF_PARSER_FRIENDLY; + para_list_add(&new_client->node, &client_list); + dump_stat_client_list(); + num_clients++; + return 1; +} +/** + * Write a message to all connected status clients. + * + * \param item_num The number of the status item of \a msg. + * + * On write errors, remove the status client from the client list and close its + * file descriptor. + */ +void stat_client_write_item(int item_num) +{ + struct stat_client *sc, *tmp; + struct para_buffer pb = {.flags = 0}; + struct para_buffer pfpb = {.flags = PBF_SIZE_PREFIX}; + const uint64_t one = 1; + + list_for_each_entry_safe(sc, tmp, &client_list, node) { + int fd = sc->fd, ret; + + if (!((one << item_num) & sc->item_mask)) + continue; + if (write_ok(fd) > 0) { + struct para_buffer *b = + (sc->flags & SCF_PARSER_FRIENDLY)? &pfpb : &pb; + char *msg = stat_item_values[item_num]; + if (!b->buf) + WRITE_STATUS_ITEM(b, item_num, "%s\n", + msg? msg : ""); + ret = write(fd, b->buf, b->offset); + if (ret == b->offset) + continue; + } + /* write error or fd not ready for writing */ + close(fd); + num_clients--; + PARA_INFO_LOG("deleting client on fd %d\n", fd); + list_del(&sc->node); + free(sc); + dump_stat_client_list(); + } + free(pb.buf); + free(pfpb.buf); +// if (num_clients) +// PARA_DEBUG_LOG("%d client(s)\n", num_clients); +} + +/** + * Check if string is a known status item. + * + * \param item Buffer containing the text to check. + * + * \return If \a item is a valid status item, the number of that status item is + * returned. Otherwise, this function returns \p -E_UNKNOWN_STAT_ITEM. + */ +static int stat_item_valid(const char *item) +{ + int i; + if (!item || !*item) { + PARA_ERROR_LOG("%s\n", "no item"); + return -E_UNKNOWN_STAT_ITEM; + } + FOR_EACH_STATUS_ITEM(i) + if (!strcmp(status_item_list[i], item)) + return i; + PARA_ERROR_LOG("invalid stat item: %s\n", item); + return -E_UNKNOWN_STAT_ITEM; +} + static int client_write(int fd, const char *buf) { - size_t len = strlen(buf); + size_t len; + + if (!buf) + return 0; + len = strlen(buf); return write(fd, buf, len) != len? -E_CLIENT_WRITE: 1; } @@ -43,23 +188,27 @@ __malloc static char *audiod_status_string(void) { const char *status = (audiod_status == AUDIOD_ON)? "on" : (audiod_status == AUDIOD_OFF)? "off": "sb"; - return make_message("%s: %s\n", status_item_list[SI_AUDIOD_STATUS], status); + return para_strdup(status); } static int get_play_time_slot_num(void) { - int i, oldest = -1; + int i, oldest_slot = -1; + struct timeval oldest_wstime = {0, 0}; FOR_EACH_SLOT(i) { struct slot_info *s = &slot[i]; - if (!s->wng) + struct timeval wstime; + if (!s->wns) continue; - if (oldest >= 0 && tv_diff(&s->wstime, &slot[oldest].wstime, - NULL) > 0) + btr_get_node_start(s->wns[0].btrn, &wstime); + if (oldest_slot >= 0 && tv_diff(&wstime, &oldest_wstime, NULL) > 0) continue; - oldest = i; + oldest_wstime = wstime; + oldest_slot = i; } - return oldest; + //PARA_CRIT_LOG("oldest slot: %d\n", oldest_slot); + return oldest_slot; } __malloc static char *decoder_flags(void) @@ -72,13 +221,14 @@ __malloc static char *decoder_flags(void) char flag = '0'; if (s->receiver_node) flag += 1; - if (s->wng) + if (s->fns) flag += 2; + if (s->wns) + flag += 4; flags[i] = flag; } flags[MAX_STREAM_SLOTS] = '\0'; - return make_message("%s: %s\n", status_item_list[SI_DECODER_FLAGS], - flags); + return para_strdup(flags); } static int dump_commands(int fd) @@ -168,95 +318,50 @@ int com_kill(int fd, int argc, char **argv) int com_stat(int fd, int argc, char **argv) { - int i, ret; - char *buf = NULL; + int i, ret, parser_friendly = 0; uint64_t mask = 0; const uint64_t one = 1; + struct para_buffer b = {.flags = 0}; - if (argc > 1) { - for (i = 1; i < argc; i++) { - ret = stat_item_valid(argv[i]); - if (ret < 0) - return ret; - mask |= (one << ret); + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (arg[0] != '-') + break; + if (!strcmp(arg, "--")) { + i++; + break; } - } else + if (!strncmp(arg, "-p", 2)) { + parser_friendly = 1; + b.flags = PBF_SIZE_PREFIX; + continue; + } + } + if (i >= argc) mask--; /* set all bits */ + for (; i < argc; i++) { + ret = stat_item_valid(argv[i]); + if (ret < 0) + return ret; + mask |= (one << ret); + } PARA_INFO_LOG("mask: 0x%llx\n", (long long unsigned)mask); FOR_EACH_STATUS_ITEM(i) { - char *tmp, *v; + char *item = stat_item_values[i]; if (!((one << i) & mask)) continue; - v = stat_item_values[i]; - if (!v) - continue; - tmp = make_message("%s%s%s", buf? buf: "", v, - strrchr(v, '\n')? "" : "\n"); - free(buf); - buf = tmp; + WRITE_STATUS_ITEM(&b, i, "%s\n", item? item : ""); } - ret = client_write(fd, buf); - if (ret > 0) - ret = stat_client_add(fd, mask); - free(buf); + ret = client_write(fd, b.buf); + if (ret >= 0) + ret = stat_client_add(fd, mask, parser_friendly); + free(b.buf); return ret; } -static struct filter_node *find_filter_node(int slot_num, int format, int filternum) -{ - int i; - - FOR_EACH_SLOT(i) { - struct slot_info *s = &slot[i]; - if (s->format < 0 || !s->fc) - continue; - if (slot_num >= 0 && slot_num != i) - continue; - if (format >= 0 && s->format != format) - continue; - if (num_filters(i) <= filternum) - continue; - /* success */ - return s->fc->filter_nodes + filternum; - } - return NULL; -} - -int com_grab(int fd, char *cmdline) +int com_grab(int fd, int argc, char **argv) { - struct grab_client *gc; - struct filter_node *fn; - int i, err; - char *msg; - - gc = grab_client_new(fd, cmdline, &err); - if (!gc) - goto err_out; - fn = find_filter_node(gc->conf->slot_arg, gc->audio_format_num, gc->conf->filter_num_arg); - if (fn) - activate_grab_client(gc, fn); - return 1; -err_out: - if (err != -E_GC_HELP_GIVEN && err != -E_GC_VERSION_GIVEN) - return err; - if (err == -E_GC_HELP_GIVEN) { - msg = make_message("%s\n\n", grab_client_args_info_usage); - for (i = 0; grab_client_args_info_help[i]; i++) { - char *tmp = make_message("%s%s\n", msg, - grab_client_args_info_help[i]); - free(msg); - msg = tmp; - } - } else - msg = make_message("%s %s\n", - GRAB_CLIENT_CMDLINE_PARSER_PACKAGE, - GRAB_CLIENT_CMDLINE_PARSER_VERSION); - err = client_write(fd, msg); - free(msg); - if (err < 0) - return err; - close(fd); - return 1; + return grab_client_new(fd, argc, argv); } __noreturn int com_term(int fd, __a_unused int argc, __a_unused char **argv) @@ -316,32 +421,33 @@ static int check_perms(uid_t uid) } /** - * handle arriving connections on the local socket + * Handle arriving connections on the local socket. * - * \param accept_fd the fd to call accept() on + * \param accept_fd The fd to accept connections on. + * \param rfds If \a accept_fd is not set in \a rfds, do nothing. * - * This is called whenever para_audiod's main task detects an incoming - * connection by the readability of \a accept_fd. This function reads the - * command sent by the peer, checks the connecting user's permissions by using - * unix socket credentials (if supported by the OS) and calls the corresponding - * command handler if permissions are OK. + * This is called in each iteration of the select loop. If there is an incoming + * connection on \a accept_fd, this function reads the command sent by the peer, + * checks the connecting user's permissions by using unix socket credentials + * (if supported by the OS) and calls the corresponding command handler if + * permissions are OK. * - * \return positive on success, negative on errors + * \return Positive on success, negative on errors, zero if there was no + * connection to accept. * * \sa para_accept(), recv_cred_buffer() * */ -int handle_connect(int accept_fd) +int handle_connect(int accept_fd, fd_set *rfds) { - int i, argc, ret, clifd = -1; - char *cmd = NULL, *p, *buf = para_calloc(MAXLINE), **argv = NULL; + int i, argc, ret, clifd; + char buf[MAXLINE], **argv = NULL; struct sockaddr_un unix_addr; uid_t uid; - ret = para_accept(accept_fd, &unix_addr, sizeof(struct sockaddr_un)); - if (ret < 0) - goto out; - clifd = ret; - ret = recv_cred_buffer(clifd, buf, MAXLINE - 1); + ret = para_accept(accept_fd, rfds, &unix_addr, sizeof(struct sockaddr_un), &clifd); + if (ret <= 0) + return ret; + ret = recv_cred_buffer(clifd, buf, sizeof(buf) - 1); if (ret < 0) goto out; uid = ret; @@ -349,36 +455,20 @@ int handle_connect(int accept_fd) ret = check_perms(uid); if (ret < 0) goto out; - cmd = para_strdup(buf); - p = strchr(cmd, '\n'); - if (!p) - p = ""; - else { - *p = '\0'; - p++; - } - for (i = 0; audiod_cmds[i].name; i++) { - int j; - if (strcmp(audiod_cmds[i].name, cmd)) + ret = create_argv(buf, "\n", &argv); + if (ret < 0) + goto out; + argc = ret; + //PARA_INFO_LOG("argv[0]: %s, argc = %d\n", argv[0], argc); + FOR_EACH_COMMAND(i) { + if (strcmp(audiod_cmds[i].name, argv[0])) continue; - if (audiod_cmds[i].handler) { - argc = split_args(buf, &argv, "\n"); - PARA_INFO_LOG("argv[0]: %s, argc= %d\n", argv[0], argc); - ret = audiod_cmds[i].handler(clifd, argc, argv); - goto out; - } - for (j = 0; p[j]; j++) - if (p[j] == '\n') - p[j] = ' '; - PARA_INFO_LOG("cmd: %s, options: %s\n", cmd, p); - ret = audiod_cmds[i].line_handler(clifd, p); + ret = audiod_cmds[i].handler(clifd, argc, argv); goto out; } ret = -E_INVALID_AUDIOD_CMD; out: - free(cmd); - free(buf); - free(argv); + free_argv(argv); if (clifd > 0 && ret < 0 && ret != -E_CLIENT_WRITE) { char *tmp = make_message("%s\n", para_strerror(-ret)); client_write(clifd, tmp); @@ -387,34 +477,32 @@ out: } return ret; } + /** - * send the current audiod status to all connected stat clients + * Send the current audiod status to all connected stat clients. */ void audiod_status_dump(void) { int slot_num = get_play_time_slot_num(); - char *old, *new, *tmp; + char *old, *new; old = stat_item_values[SI_PLAY_TIME]; new = get_time_string(slot_num); if (new) { if (!old || strcmp(old, new)) { free(old); - stat_client_write(new, SI_PLAY_TIME); stat_item_values[SI_PLAY_TIME] = new; + stat_client_write_item(SI_PLAY_TIME); } else free(new); } - tmp = uptime_str(); - new = make_message("%s: %s\n", status_item_list[SI_AUDIOD_UPTIME], - tmp); - free(tmp); + new = uptime_str(); old = stat_item_values[SI_AUDIOD_UPTIME]; if (!old || strcmp(old, new)) { free(old); - stat_client_write(new, SI_AUDIOD_UPTIME); stat_item_values[SI_AUDIOD_UPTIME] = new; + stat_client_write_item(SI_AUDIOD_UPTIME); } else free(new); @@ -422,8 +510,8 @@ void audiod_status_dump(void) new = audiod_status_string(); if (!old || strcmp(old, new)) { free(old); - stat_client_write(new, SI_AUDIOD_STATUS); stat_item_values[SI_AUDIOD_STATUS] = new; + stat_client_write_item(SI_AUDIOD_STATUS); } else free(new); @@ -431,27 +519,25 @@ void audiod_status_dump(void) new = decoder_flags(); if (!old || strcmp(old, new)) { free(old); - stat_client_write(new, SI_DECODER_FLAGS); stat_item_values[SI_DECODER_FLAGS] = new; + stat_client_write_item(SI_DECODER_FLAGS); } else free(new); } /** - * send empty status list + * Flush and send all status items. * * Send to each connected client the full status item list * with empty values. */ -void dump_empty_status(void) +void clear_and_dump_items(void) { int i; FOR_EACH_STATUS_ITEM(i) { - char *tmp = make_message("%s:\n", status_item_list[i]); - stat_client_write(tmp, i); - free(tmp); free(stat_item_values[i]); stat_item_values[i] = NULL; + stat_client_write_item(i); } } diff --git a/autogen.sh b/autogen.sh index 5c3d931d..76eaef2f 100755 --- a/autogen.sh +++ b/autogen.sh @@ -5,8 +5,9 @@ if test -f Makefile; then fi aclocal -I . &> /dev/null autoconf -autoheader +autoheader echo configuring... ./configure $@ > /dev/null echo compiling... -make clean all > /dev/null +make clean2 &> /dev/null +make > /dev/null diff --git a/bash_completion b/bash_completion index cd647e20..1ab6205a 100644 --- a/bash_completion +++ b/bash_completion @@ -47,7 +47,7 @@ __para_complete_table() __para_complete_sender() { if test -z "$__para_sender_list"; then - __para_sender_list="$($PC si | grep "supported senders:" | sed -e 's/.*: //')" + __para_sender_list="$($PC si | grep '^.* sender:$' | sed -e 's/ sender://')" fi echo "$__para_sender_list" } diff --git a/bitstream.c b/bitstream.c new file mode 100644 index 00000000..1139edc0 --- /dev/null +++ b/bitstream.c @@ -0,0 +1,207 @@ +/* + * Common bit I/O utils. + * + * Extracted 2009 from mplayer 2009-02-10 libavcodec/bitstream.c. + * + * Copyright (c) 2000, 2001 Fabrice Bellard + * Copyright (c) 2002-2004 Michael Niedermayer + * alternative bitstream reader & writer by Michael Niedermayer + * + * Licensed under the GNU Lesser General Public License. + * For licencing details see COPYING.LIB. + */ + +/** + * \file bitstream.c Bitstream API. + */ + +#include +#include +#include +#include +#include + +#include "para.h" +#include "error.h" +#include "string.h" +#include "wma.h" +#include "bitstream.h" + +#define GET_DATA(v, table, i, size) \ +{\ + const uint8_t *ptr = (const uint8_t *)table + i * size; \ + switch (size) { \ + case 1: \ + v = *(const uint8_t *)ptr; \ + break; \ + case 2: \ + v = *(const uint16_t *)ptr; \ + break; \ + default: \ + v = *(const uint32_t *)ptr; \ + break; \ + } \ +} + +static void alloc_table(struct vlc *vlc, int size) +{ + vlc->table_size += size; + if (vlc->table_size > vlc->table_allocated) { + vlc->table_allocated += (1 << vlc->bits); + vlc->table = para_realloc(vlc->table, + sizeof(VLC_TYPE) * 2 * vlc->table_allocated); + } +} + +static int build_table(struct vlc *vlc, int table_nb_bits, int nb_codes, + const void *bits, const void *codes, int codes_size, + uint32_t code_prefix, int n_prefix) +{ + int i, j, k, n, table_size, table_index, nb, n1, idx, code_prefix2, + symbol; + uint32_t code; + VLC_TYPE(*table)[2]; + + table_size = 1 << table_nb_bits; + table_index = vlc->table_size; + alloc_table(vlc, table_size); + table = &vlc->table[table_index]; + + for (i = 0; i < table_size; i++) { + table[i][1] = 0; //bits + table[i][0] = -1; //codes + } + + /* map codes and compute auxillary table sizes */ + for (i = 0; i < nb_codes; i++) { + GET_DATA(n, bits, i, 1); + GET_DATA(code, codes, i, codes_size); + /* we accept tables with holes */ + if (n <= 0) + continue; + symbol = i; + /* if code matches the prefix, it is in the table */ + n -= n_prefix; + code_prefix2 = code >> n; + if (n <= 0 || code_prefix2 != code_prefix) + continue; + if (n <= table_nb_bits) { + /* no need to add another table */ + j = (code << (table_nb_bits - n)) & (table_size - 1); + nb = 1 << (table_nb_bits - n); + for (k = 0; k < nb; k++) { + if (table[j][1] /* bits */ != 0) { + PARA_EMERG_LOG("incorrect code\n"); + exit(EXIT_FAILURE); + } + table[j][1] = n; //bits + table[j][0] = symbol; + j++; + } + } else { + n -= table_nb_bits; + j = (code >> n) & ((1 << table_nb_bits) - 1); + /* compute table size */ + n1 = -table[j][1]; //bits + if (n > n1) + n1 = n; + table[j][1] = -n1; //bits + } + } + + /* fill auxillary tables recursively */ + for (i = 0; i < table_size; i++) { + n = table[i][1]; //bits + if (n < 0) { + n = -n; + if (n > table_nb_bits) { + n = table_nb_bits; + table[i][1] = -n; //bits + } + idx = build_table(vlc, n, nb_codes, bits, codes, + codes_size, (code_prefix << table_nb_bits) | i, + n_prefix + table_nb_bits); + /* vlc->table might have changed */ + table = &vlc->table[table_index]; + table[i][0] = idx; //code + } + } + return table_index; +} + +/** + * Build VLC decoding tables suitable for use with get_vlc(). + * + * \param vlc The structure to be initialized. + * + * \param nb_bits Set the decoding table size (2^nb_bits) entries. The bigger + * it is, the faster is the decoding. But it should not be too big to save + * memory and L1 cache. '9' is a good compromise. + * + * \param nb_codes Number of vlcs codes. + * + * \param bits Table which gives the size (in bits) of each vlc code. + * + * \param codes Table which gives the bit pattern of of each vlc code. + * + * \param codes_size The number of bytes of each entry of the \a codes tables. + * + * The wrap and size parameters allow to use any memory configuration and + * types (byte/word/long) to store the bits and codes tables. + */ +void init_vlc(struct vlc *vlc, int nb_bits, int nb_codes, const void *bits, + const void *codes, int codes_size) +{ + PARA_INFO_LOG("nb_codes: %d\n", nb_codes); + vlc->bits = nb_bits; + vlc->table = NULL; + vlc->table_allocated = 0; + vlc->table_size = 0; + build_table(vlc, nb_bits, nb_codes, bits, codes, codes_size, 0, 0); +} + +void free_vlc(struct vlc *vlc) +{ + freep(&vlc->table); +} + +/** + * Parse a vlc code. + * + * \param gbc The getbit context structure. + * + * \param table The vlc tables to use. + * + * \param bits The number of bits which will be read at once, must be + * identical to nb_bits in init_vlc(). + * + * \param max_depth The number of times bits bits must be read to completely + * read the longest vlc code = (max_vlc_length + bits - 1) / bits. + * + * \return The vlc code. + */ +int get_vlc(struct getbit_context *gbc, VLC_TYPE(*table)[2], int bits, + int max_depth) +{ + int n, idx, nb_bits, code; + + idx = show_bits(gbc, bits); + code = table[idx][0]; + n = table[idx][1]; + if (max_depth > 1 && n < 0) { + skip_bits(gbc, bits); + nb_bits = -n; + idx = show_bits(gbc, nb_bits) + code; + code = table[idx][0]; + n = table[idx][1]; + if (max_depth > 2 && n < 0) { + skip_bits(gbc, nb_bits); + nb_bits = -n; + idx = show_bits(gbc, nb_bits) + code; + code = table[idx][0]; + n = table[idx][1]; + } + } + skip_bits(gbc, n); + return code >= 0? code : -E_VLC; +} diff --git a/bitstream.h b/bitstream.h new file mode 100644 index 00000000..5d6be3be --- /dev/null +++ b/bitstream.h @@ -0,0 +1,91 @@ +/* + * Extracted 2009 from mplayer 2009-02-10 libavcodec/bitstream.h. + * + * copyright (c) 2004 Michael Niedermayer + * + * Licensed under the GNU Lesser General Public License. + * For licencing details see COPYING.LIB. + */ + +/** \file bitstream.h Bitstream structures and inline functions. */ + +/** Structure for bistream I/O. */ +struct getbit_context { + /* Start of the internal buffer. */ + const uint8_t *buffer; + /* End of the internal buffer. */ + const uint8_t *buffer_end; + /** Bit counter. */ + int index; +}; + +#define VLC_TYPE int16_t + +/** A variable length code table. */ +struct vlc { + /** Number of bits of the table. */ + int bits; + /** The code and the bits table. */ + VLC_TYPE(*table)[2]; + /** The size of the table. */ + int table_size; + /** Amount of memory allocated so far. */ + int table_allocated; +}; + +static inline uint32_t show_bits(struct getbit_context *gbc, int num) +{ + int idx = gbc->index; + const uint8_t *p = gbc->buffer + (idx >> 3); + uint32_t x = ((p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]); + return (x << (idx & 7)) >> (32 - num); +} + +static inline int get_bits_count(struct getbit_context *gbc) +{ + return gbc->index; +} + +static inline void skip_bits(struct getbit_context *gbc, int n) +{ + gbc->index += n; +} + +static inline unsigned int get_bits(struct getbit_context *gbc, int n) +{ + unsigned int ret = show_bits(gbc, n); + skip_bits(gbc, n); + return ret; +} + +/* This is rather hot, we can do better than get_bits(gbc, 1). */ +static inline unsigned int get_bit(struct getbit_context *gbc) +{ + int idx = gbc->index++; + uint8_t tmp = gbc->buffer[idx >> 3], mask = (1 << (7 - (idx & 7))); + return !!(tmp & mask); +} + +/** + * Initialize a getbit_context structure. + * + * \param buffer The bitstream buffer. It must be 4 bytes larger then the + * actual read bits because the bitstream reader might read 32 bits at once and + * could read over the end. + * + * \param bit_size The size of the buffer in bytes. + */ +static inline void init_get_bits(struct getbit_context *gbc, + const uint8_t *buffer, int size) +{ + gbc->buffer = buffer; + gbc->buffer_end = buffer + size; + gbc->index = 0; +} + +void init_vlc(struct vlc *vlc, int nb_bits, int nb_codes, const void *bits, + const void *codes, int codes_size); +void free_vlc(struct vlc *vlc); +int get_vlc(struct getbit_context *gbc, VLC_TYPE(*table)[2], int bits, + int max_depth); + diff --git a/blob.c b/blob.c index 5ab5c1ce..cbf2af49 100644 --- a/blob.c +++ b/blob.c @@ -1,19 +1,47 @@ /* - * Copyright (C) 2007-2009 Andre Noll + * Copyright (C) 2007-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file blob.c Macros and functions for blob handling. */ +#include #include +#include +#include + #include "para.h" #include "error.h" +#include "crypt.h" #include "string.h" #include "afh.h" #include "afs.h" #include "net.h" #include "ipc.h" +#include "portable_io.h" + +/** + * Compare two osl objects pointing to unsigned integers of 32 bit size. + * + * \param obj1 Pointer to the first integer. + * \param obj2 Pointer to the second integer. + * + * \return The values required for an osl compare function. + * + * \sa osl_compare_func, osl_hash_compare(). + */ +static int uint32_compare(const struct osl_object *obj1, const struct osl_object *obj2) +{ + uint32_t d1 = read_u32((const char *)obj1->data); + uint32_t d2 = read_u32((const char *)obj2->data); + + if (d1 < d2) + return 1; + if (d1 > d2) + return -1; + return 0; +} static struct osl_column_description blob_cols[] = { [BLOBCOL_ID] = { @@ -36,6 +64,24 @@ static struct osl_column_description blob_cols[] = { } }; +/** Define an osl table description for a blob table. */ +#define DEFINE_BLOB_TABLE_DESC(table_name) \ + struct osl_table_description table_name ## _table_desc = { \ + .name = #table_name, \ + .num_columns = NUM_BLOB_COLUMNS, \ + .flags = OSL_LARGE_TABLE, \ + .column_descriptions = blob_cols \ + }; + +/** Define a pointer to an osl blob table with a canonical name. */ +#define DEFINE_BLOB_TABLE_PTR(table_name) struct osl_table *table_name ## _table; + + +/** Define a blob table. */ +#define INIT_BLOB_TABLE(table_name) \ + DEFINE_BLOB_TABLE_DESC(table_name); \ + DEFINE_BLOB_TABLE_PTR(table_name); + /** \cond doxygen isn't smart enough to recognize these */ INIT_BLOB_TABLE(lyrics); INIT_BLOB_TABLE(images); @@ -71,7 +117,7 @@ static int print_blob(struct osl_table *table, struct osl_row *row, if (!(lbad->flags & BLOB_LS_FLAG_LONG)) return para_printf(&lbad->pb, "%s\n", name); - ret = osl_get_object(table, row, BLOBCOL_ID, &obj); + ret = osl(osl_get_object(table, row, BLOBCOL_ID, &obj)); if (ret < 0) { para_printf(&lbad->pb, "%s: %s\n", name, para_strerror(-ret)); return ret; @@ -116,7 +162,7 @@ static void com_lsblob_callback(struct osl_table *table, free(lbad.pb.buf); } -static int com_lsblob(callback_function *f, int fd, int argc, char * const * const argv) +static int com_lsblob(callback_function *f, struct rc4_context *rc4c, int argc, char * const * const argv) { uint32_t flags = 0; struct osl_object options = {.data = &flags, .size = sizeof(flags)}; @@ -147,7 +193,7 @@ static int com_lsblob(callback_function *f, int fd, int argc, char * const * con // if (argc > i) // return -E_BLOB_SYNTAX; return send_option_arg_callback_request(&options, argc - i, - argv + i, f, send_result, &fd); + argv + i, f, rc4_send_result, rc4c); } static int cat_blob(struct osl_table *table, struct osl_row *row, @@ -156,12 +202,12 @@ static int cat_blob(struct osl_table *table, struct osl_row *row, int ret = 0, ret2; struct osl_object obj; - ret = osl_open_disk_object(table, row, BLOBCOL_DEF, &obj); + ret = osl(osl_open_disk_object(table, row, BLOBCOL_DEF, &obj)); if (ret < 0) return ret; if (obj.size) ret = pass_buffer_as_shm(obj.data, obj.size, data); - ret2 = osl_close_disk_object(&obj); + ret2 = osl(osl_close_disk_object(&obj)); return (ret < 0)? ret : ret2; } @@ -180,12 +226,13 @@ static void com_catblob_callback(struct osl_table *table, int fd, for_each_matching_row(&pmd); } -static int com_catblob(callback_function *f, int fd, int argc, +static int com_catblob(callback_function *f, struct rc4_context *rc4c, int argc, char * const * const argv) { if (argc < 2) return -E_BLOB_SYNTAX; - return send_standard_callback_request(argc - 1, argv + 1, f, send_result, &fd); + return send_standard_callback_request(argc - 1, argv + 1, f, + rc4_send_result, rc4c); } /** Used for removing rows from a blob table. */ @@ -200,7 +247,7 @@ static int remove_blob(struct osl_table *table, struct osl_row *row, const char *name, void *data) { struct rmblob_data *rmbd = data; - int ret = osl_del_row(table, row); + int ret = osl(osl_del_row(table, row)); if (ret < 0) { para_printf(&rmbd->pb, "%s: %s\n", name, para_strerror(-ret)); return ret; @@ -248,13 +295,13 @@ out: free(rmbd.pb.buf); } -static int com_rmblob(callback_function *f, int fd, int argc, +static int com_rmblob(callback_function *f, struct rc4_context *rc4c, int argc, char * const * const argv) { if (argc < 2) return -E_MOOD_SYNTAX; return send_option_arg_callback_request(NULL, argc - 1, argv + 1, f, - send_result, &fd); + rc4_send_result, rc4c); } static void com_addblob_callback(struct osl_table *table, __a_unused int fd, @@ -267,7 +314,7 @@ static void com_addblob_callback(struct osl_table *table, __a_unused int fd, unsigned num_rows; int ret; - ret = osl_get_num_rows(table, &num_rows); + ret = osl(osl_get_num_rows(table, &num_rows)); if (ret < 0) goto out; if (!num_rows) { /* this is the first entry ever added */ @@ -279,34 +326,34 @@ static void com_addblob_callback(struct osl_table *table, __a_unused int fd, objs[BLOBCOL_NAME].size = 1; objs[BLOBCOL_DEF].data = ""; objs[BLOBCOL_DEF].size = 1; - ret = osl_add_row(table, objs); + ret = osl(osl_add_row(table, objs)); if (ret < 0) goto out; } else { /* check if name already exists */ struct osl_row *row; struct osl_object obj = {.data = name, .size = name_len}; - ret = osl_get_row(table, BLOBCOL_NAME, &obj, &row); - if (ret < 0 && ret != -E_RB_KEY_NOT_FOUND) + ret = osl(osl_get_row(table, BLOBCOL_NAME, &obj, &row)); + if (ret < 0 && ret != -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND)) goto out; if (ret >= 0) { /* we already have a blob with this name */ obj.data = name + name_len; obj.size = query->size - name_len; - ret = osl_update_object(table, row, BLOBCOL_DEF, &obj); + ret = osl(osl_update_object(table, row, BLOBCOL_DEF, &obj)); goto out; } /* new blob, get id of the dummy row and increment it */ obj.data = ""; obj.size = 1; - ret = osl_get_row(table, BLOBCOL_NAME, &obj, &row); + ret = osl(osl_get_row(table, BLOBCOL_NAME, &obj, &row)); if (ret < 0) goto out; - ret = osl_get_object(table, row, BLOBCOL_ID, &obj); + ret = osl(osl_get_object(table, row, BLOBCOL_ID, &obj)); if (ret < 0) goto out; id = *(uint32_t *)obj.data + 1; obj.data = &id; - ret = osl_update_object(table, row, BLOBCOL_ID, &obj); + ret = osl(osl_update_object(table, row, BLOBCOL_ID, &obj)); if (ret < 0) goto out; } @@ -317,7 +364,7 @@ static void com_addblob_callback(struct osl_table *table, __a_unused int fd, objs[BLOBCOL_NAME].size = name_len; objs[BLOBCOL_DEF].data = name + name_len; objs[BLOBCOL_DEF].size = query->size - name_len; - ret = osl_add_row(table, objs); + ret = osl(osl_add_row(table, objs)); if (ret < 0) goto out; afs_event(BLOB_ADD, NULL, table); @@ -326,7 +373,82 @@ out: PARA_NOTICE_LOG("%s\n", para_strerror(-ret)); } -static int com_addblob(callback_function *f, int fd, int argc, +/* + * write input from fd to dynamically allocated buffer, + * but maximal max_size byte. + */ +static int fd2buf(struct rc4_context *rc4c, unsigned max_size, struct osl_object *obj) +{ + const size_t chunk_size = 1024; + size_t size = 2048, received = 0; + int ret; + char *buf = para_malloc(size); + + for (;;) { + ret = rc4_recv_bin_buffer(rc4c, buf + received, chunk_size); + if (ret <= 0) + break; + received += ret; + if (received + chunk_size >= size) { + size *= 2; + ret = -E_INPUT_TOO_LARGE; + if (size > max_size) + break; + buf = para_realloc(buf, size); + } + } + obj->data = buf; + obj->size = received; + if (ret < 0) + free(buf); + return ret; +} + +/* + * Read data from a file descriptor, and send it to the afs process. + * + * \param rc4c crypt context containing the file descriptor to read data from. + * \param arg_obj Pointer to the arguments to \a f. + * \param f The callback function. + * \param max_len Don't read more than that many bytes from stdin. + * \param result_handler See \ref send_callback_request. + * \param private_result_data See \ref send_callback_request. + * + * This function is used by commands that wish to let para_server store + * arbitrary data specified by the user (for instance the add_blob family of + * commands). First, at most \a max_len bytes are read and decrypted from the + * file descriptor given by \a rc4c. The result is concatenated with the buffer + * given by \a arg_obj, and the combined buffer is made available to the afs + * process via the callback method. See \ref send_callback_request for details. + * + * \return Negative on errors, the return value of the underlying call to + * send_callback_request() otherwise. + */ +static int stdin_command(struct rc4_context *rc4c, struct osl_object *arg_obj, + callback_function *f, unsigned max_len, + callback_result_handler *result_handler, + void *private_result_data) +{ + struct osl_object query, stdin_obj; + int ret; + + ret = rc4_send_buffer(rc4c, AWAITING_DATA_MSG); + if (ret < 0) + return ret; + ret = fd2buf(rc4c, max_len, &stdin_obj); + if (ret < 0) + return ret; + query.size = arg_obj->size + stdin_obj.size; + query.data = para_malloc(query.size); + memcpy(query.data, arg_obj->data, arg_obj->size); + memcpy((char *)query.data + arg_obj->size, stdin_obj.data, stdin_obj.size); + free(stdin_obj.data); + ret = send_callback_request(f, &query, result_handler, private_result_data); + free(query.data); + return ret; +} + +static int com_addblob(callback_function *f, struct rc4_context *rc4c, int argc, char * const * const argv) { struct osl_object arg_obj; @@ -337,7 +459,7 @@ static int com_addblob(callback_function *f, int fd, int argc, return -E_BLOB_SYNTAX; arg_obj.size = strlen(argv[1]) + 1; arg_obj.data = (char *)argv[1]; - return stdin_command(fd, &arg_obj, f, 10 * 1024 * 1024, NULL, NULL); + return stdin_command(rc4c, &arg_obj, f, 10 * 1024 * 1024, NULL, NULL); } /* FIXME: Print output to client, not to log file */ @@ -348,13 +470,13 @@ static void com_mvblob_callback(struct osl_table *table, __a_unused int fd, struct osl_object obj = {.data = src, .size = strlen(src) + 1}; char *dest = src + obj.size; struct osl_row *row; - int ret = osl_get_row(table, BLOBCOL_NAME, &obj, &row); + int ret = osl(osl_get_row(table, BLOBCOL_NAME, &obj, &row)); if (ret < 0) goto out; obj.data = dest; obj.size = strlen(dest) + 1; - ret = osl_update_object(table, row, BLOBCOL_NAME, &obj); + ret = osl(osl_update_object(table, row, BLOBCOL_NAME, &obj)); if (ret < 0) goto out; afs_event(BLOB_RENAME, NULL, table); @@ -363,7 +485,7 @@ out: PARA_NOTICE_LOG("%s\n", para_strerror(-ret)); } -static int com_mvblob(callback_function *f, __a_unused int fd, +static int com_mvblob(callback_function *f, __a_unused struct rc4_context *rc4c, int argc, char * const * const argv) { if (argc != 3) @@ -377,9 +499,9 @@ static int com_mvblob(callback_function *f, __a_unused int fd, { \ return com_ ## cmd_name ## blob_callback(table_name ## _table, fd, query); \ } \ - int com_ ## cmd_name ## cmd_prefix(int fd, int argc, char * const * const argv) \ + int com_ ## cmd_name ## cmd_prefix(struct rc4_context *rc4c, int argc, char * const * const argv) \ { \ - return com_ ## cmd_name ## blob(com_ ## cmd_name ## cmd_prefix ## _callback, fd, argc, argv); \ + return com_ ## cmd_name ## blob(com_ ## cmd_name ## cmd_prefix ## _callback, rc4c, argc, argv); \ } static int blob_get_name_by_id(struct osl_table *table, uint32_t id, @@ -392,10 +514,10 @@ static int blob_get_name_by_id(struct osl_table *table, uint32_t id, *name = NULL; if (!id) return 1; - ret = osl_get_row(table, BLOBCOL_ID, &obj, &row); + ret = osl(osl_get_row(table, BLOBCOL_ID, &obj, &row)); if (ret < 0) return ret; - ret = osl_get_object(table, row, BLOBCOL_NAME, &obj); + ret = osl(osl_get_object(table, row, BLOBCOL_NAME, &obj)); if (ret < 0) return ret; *name = (char *)obj.data; @@ -420,10 +542,10 @@ static int blob_get_def_by_name(struct osl_table *table, char *name, def->data = NULL; if (!*name) return 1; - ret = osl_get_row(table, BLOBCOL_NAME, &obj, &row); + ret = osl(osl_get_row(table, BLOBCOL_NAME, &obj, &row)); if (ret < 0) return ret; - return osl_open_disk_object(table, row, BLOBCOL_DEF, def); + return osl(osl_open_disk_object(table, row, BLOBCOL_DEF, def)); } /** Define the \p get_def_by_id function for this blob type. */ @@ -443,10 +565,10 @@ static int blob_get_def_by_id(struct osl_table *table, uint32_t id, def->data = NULL; if (!id) return 1; - ret = osl_get_row(table, BLOBCOL_ID, &obj, &row); + ret = osl(osl_get_row(table, BLOBCOL_ID, &obj, &row)); if (ret < 0) return ret; - return osl_open_disk_object(table, row, BLOBCOL_DEF, def); + return osl(osl_open_disk_object(table, row, BLOBCOL_DEF, def)); } /** Define the \p get_def_by_id function for this blob type. */ @@ -460,11 +582,11 @@ static int blob_get_name_and_def_by_row(struct osl_table *table, const struct osl_row *row, char **name, struct osl_object *def) { struct osl_object obj; - int ret = osl_get_object(table, row, BLOBCOL_NAME, &obj); + int ret = osl(osl_get_object(table, row, BLOBCOL_NAME, &obj)); if (ret < 0) return ret; *name = obj.data; - return osl_open_disk_object(table, row, BLOBCOL_DEF, def); + return osl(osl_open_disk_object(table, row, BLOBCOL_DEF, def)); } /** Define the \p get_name_and_def_by_row function for this blob type. */ #define DEFINE_GET_NAME_AND_DEF_BY_ROW(table_name, cmd_prefix) \ @@ -497,11 +619,11 @@ static int blob_open(struct osl_table **table, { int ret; desc->dir = dir; - ret = osl_open_table(desc, table); + ret = osl(osl_open_table(desc, table)); if (ret >= 0) return ret; *table = NULL; - if (ret >= 0 || is_errno(-ret, ENOENT)) + if (ret >= 0 || ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_NOENT)) return 1; return ret; } diff --git a/buffer_tree.c b/buffer_tree.c new file mode 100644 index 00000000..0e460dd3 --- /dev/null +++ b/buffer_tree.c @@ -0,0 +1,1136 @@ +/* + * Copyright (C) 2009-2011 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file buffer_tree.c Buffer tree and buffer pool implementations. */ +#include +#include + +#include "para.h" +#include "list.h" +#include "string.h" +#include "buffer_tree.h" +#include "error.h" +#include "sched.h" + +/* whead = NULL means area full */ +struct btr_pool { + char *name; + char *area_start; + char *area_end; + char *rhead; + char *whead; +}; + +struct btr_buffer { + char *buf; + size_t size; + /** The number of references to this buffer. */ + int refcount; + /* NULL means no buffer pool but a malloced buffer. */ + struct btr_pool *pool; +}; + +struct btr_buffer_reference { + struct btr_buffer *btrb; + size_t consumed; + /* Each buffer reference belongs to the buffer queue list of some buffer tree node. */ + struct list_head node; + size_t wrap_count; +}; + +struct btr_node { + char *name; + struct btr_node *parent; + /* The position of this btr node in the buffer tree. */ + struct list_head node; + /* The children nodes of this btr node are linked together in a list. */ + struct list_head children; + /* Time of first data transfer. */ + struct timeval start; + /** + * The input queue is a list of references to btr buffers. Each item on + * the list represents an input buffer which has not been completely + * used by this btr node. + */ + struct list_head input_queue; + btr_command_handler execute; + void *context; +}; + +/** + * Create a new buffer pool. + * + * \param name The name of the new buffer pool. + * \param area_size The size in bytes of the pool area. + * + * \return An opaque pointer to the newly created buffer pool. It must be + * passed to btr_pool_free() after it is no longer used to deallocate all + * resources. + */ +struct btr_pool *btr_pool_new(const char *name, size_t area_size) +{ + struct btr_pool *btrp; + + PARA_INFO_LOG("%s, %zu bytes\n", name, area_size); + btrp = para_malloc(sizeof(*btrp)); + btrp->area_start = para_malloc(area_size); + btrp->area_end = btrp->area_start + area_size; + btrp->rhead = btrp->area_start; + btrp->whead = btrp->area_start; + btrp->name = para_strdup(name); + return btrp; +} + +/** + * Deallocate resources used by a buffer pool. + * + * \param btrp A pointer obtained via btr_pool_new(). + */ +void btr_pool_free(struct btr_pool *btrp) +{ + if (!btrp) + return; + free(btrp->area_start); + free(btrp->name); + free(btrp); +} + +/** + * Return the size of the buffer pool area. + * + * \param btrp The buffer pool. + * + * \return The same value which was passed during creation time to + * btr_pool_new(). + */ +size_t btr_pool_size(struct btr_pool *btrp) +{ + return btrp->area_end - btrp->area_start; +} + +static size_t btr_pool_filled(struct btr_pool *btrp) +{ + if (!btrp->whead) + return btr_pool_size(btrp); + if (btrp->rhead <= btrp->whead) + return btrp->whead - btrp->rhead; + return btr_pool_size(btrp) - (btrp->rhead - btrp->whead); +} + +/** + * Get the number of unused bytes in the buffer pool. + * + * \param btrp The pool. + * + * \return The number of bytes that can currently be allocated. + * + * Note that in general the returned number of bytes is not available as a + * single contiguous buffer. Use btr_pool_available() to obtain the length of + * the largest contiguous buffer that can currently be allocated from the + * buffer pool. + */ +size_t btr_pool_unused(struct btr_pool *btrp) +{ + return btr_pool_size(btrp) - btr_pool_filled(btrp); +} + +/* + * Return maximal size available for one read. This is + * smaller than the value returned by btr_pool_unused(). + */ +static size_t btr_pool_available(struct btr_pool *btrp) +{ + if (!btrp->whead) + return 0; + if (btrp->rhead <= btrp->whead) + return btrp->area_end - btrp->whead; + return btrp->rhead - btrp->whead; +} + +/** + * Obtain the current write head. + * + * \param btrp The buffer pool. + * \param result The write head is returned here. + * + * \return The maximal amount of bytes that may be written to the returned + * buffer. + */ +size_t btr_pool_get_buffer(struct btr_pool *btrp, char **result) +{ + if (result) + *result = btrp->whead; + return btr_pool_available(btrp); +} + +/** + * Get references to buffers pointing to free space of the buffer pool area. + * + * \param btrp The buffer pool. + * \param iov The scatter array. + * + * \return Zero if the buffer pool is full, one if the free space of the buffer + * pool area is available as a single contiguous buffer, two if the free space + * consists of two buffers. If this function returns the value n, then n + * elements of \a iov are initialized. + */ +int btr_pool_get_buffers(struct btr_pool *btrp, struct iovec iov[2]) +{ + size_t sz, unused; + char *buf; + + sz = btr_pool_get_buffer(btrp, &buf); + if (sz == 0) + return 0; + iov[0].iov_len = sz; + iov[0].iov_base = buf; + unused = btr_pool_unused(btrp); + if (sz == unused) + return 1; + iov[1].iov_len = unused - sz; + iov[1].iov_base = btrp->area_start; + return 2; +} + +/** + * Mark a part of the buffer pool area as allocated. + * + * \param btrp The buffer pool. + * \param size The amount of bytes to be allocated. + * + * This is usually called after the caller wrote to the buffer obtained by + * btr_pool_get_buffer(). + */ +static void btr_pool_allocate(struct btr_pool *btrp, size_t size) +{ + char *end; + + if (size == 0) + return; + assert(size <= btr_pool_available(btrp)); + end = btrp->whead + size; + assert(end <= btrp->area_end); + + if (end == btrp->area_end) { + PARA_DEBUG_LOG("%s: end of pool area reached\n", btrp->name); + end = btrp->area_start; + } + if (end == btrp->rhead) { + PARA_DEBUG_LOG("%s btrp buffer full\n", btrp->name); + end = NULL; /* buffer full */ + } + btrp->whead = end; +} + +static void btr_pool_deallocate(struct btr_pool *btrp, size_t size) +{ + char *end = btrp->rhead + size; + + if (size == 0) + return; + assert(end <= btrp->area_end); + assert(size <= btr_pool_filled(btrp)); + if (end == btrp->area_end) + end = btrp->area_start; + if (!btrp->whead) + btrp->whead = btrp->rhead; + btrp->rhead = end; + if (btrp->rhead == btrp->whead) + btrp->rhead = btrp->whead = btrp->area_start; +} + +#define FOR_EACH_CHILD(_tn, _btrn) list_for_each_entry((_tn), \ + &((_btrn)->children), node) +#define FOR_EACH_CHILD_SAFE(_tn, _tmp, _btrn) \ + list_for_each_entry_safe((_tn), (_tmp), &((_btrn)->children), node) + +#define FOR_EACH_BUFFER_REF(_br, _btrn) \ + list_for_each_entry((_br), &(_btrn)->input_queue, node) +#define FOR_EACH_BUFFER_REF_SAFE(_br, _tmp, _btrn) \ + list_for_each_entry_safe((_br), (_tmp), &(_btrn)->input_queue, node) + +/** + * Create a new buffer tree node. + * + * \param bnd Specifies how to create the new node. + * + * This function always succeeds (or calls exit()). The returned pointer must + * be freed using btr_free_node() after the node has been removed from the + * buffer tree via btr_remove_node(). + */ +struct btr_node *btr_new_node(struct btr_node_description *bnd) +{ + struct btr_node *btrn = para_malloc(sizeof(*btrn)); + + btrn->name = para_strdup(bnd->name); + btrn->parent = bnd->parent; + btrn->execute = bnd->handler; + btrn->context = bnd->context; + btrn->start.tv_sec = 0; + btrn->start.tv_usec = 0; + INIT_LIST_HEAD(&btrn->children); + INIT_LIST_HEAD(&btrn->input_queue); + if (!bnd->child) { + if (bnd->parent) { + list_add_tail(&btrn->node, &bnd->parent->children); + PARA_INFO_LOG("new leaf node: %s (child of %s)\n", + bnd->name, bnd->parent->name); + } else + PARA_INFO_LOG("added %s as btr root\n", bnd->name); + goto out; + } + if (!bnd->parent) { + assert(!bnd->child->parent); + PARA_INFO_LOG("new root: %s (was %s)\n", + bnd->name, bnd->child->name); + btrn->parent = NULL; + list_add_tail(&bnd->child->node, &btrn->children); + /* link it in */ + bnd->child->parent = btrn; + goto out; + } + PARA_EMERG_LOG("inserting internal nodes not yet supported.\n"); + exit(EXIT_FAILURE); + assert(bnd->child->parent == bnd->parent); +out: + return btrn; +} + +/* + * Allocate a new btr buffer. + * + * The freshly allocated buffer will have a zero refcount and will + * not be associated with a btr pool. + */ +static struct btr_buffer *new_btrb(char *buf, size_t size) +{ + struct btr_buffer *btrb = para_calloc(sizeof(*btrb)); + + btrb->buf = buf; + btrb->size = size; + return btrb; +} + +static void dealloc_buffer(struct btr_buffer *btrb) +{ + if (btrb->pool) + btr_pool_deallocate(btrb->pool, btrb->size); + else + free(btrb->buf); +} + +static struct btr_buffer_reference *get_first_input_br(struct btr_node *btrn) +{ + if (list_empty(&btrn->input_queue)) + return NULL; + return list_first_entry(&btrn->input_queue, + struct btr_buffer_reference, node); +} + +/* + * Deallocate the reference, release the resources if refcount drops to zero. + */ +static void btr_drop_buffer_reference(struct btr_buffer_reference *br) +{ + struct btr_buffer *btrb = br->btrb; + + list_del(&br->node); + free(br); + btrb->refcount--; + if (btrb->refcount == 0) { + dealloc_buffer(btrb); + free(btrb); + } +} + +static void add_btrb_to_children(struct btr_buffer *btrb, + struct btr_node *btrn, size_t consumed) +{ + struct btr_node *ch; + + if (btrn->start.tv_sec == 0) + btrn->start = *now; + FOR_EACH_CHILD(ch, btrn) { + struct btr_buffer_reference *br = para_calloc(sizeof(*br)); + br->btrb = btrb; + br->consumed = consumed; + list_add_tail(&br->node, &ch->input_queue); + btrb->refcount++; + if (ch->start.tv_sec == 0) + ch->start = *now; + } +} + +/** + * Insert a malloced buffer into the buffer tree. + * + * \param buf The buffer to insert. + * \param size The size of \a buf in bytes. + * \param btrn Position in the buffer tree to create the output. + * + * This creates references to \a buf and adds these references to each child of + * \a btrn. The buffer will be freed using standard free() once no buffer tree + * node is referencing it any more. + * + * Note that this function must not be used if \a buf was obtained from a + * buffer pool. Use btr_add_output_pool() in this case. + */ +void btr_add_output(char *buf, size_t size, struct btr_node *btrn) +{ + struct btr_buffer *btrb; + + assert(size != 0); + if (list_empty(&btrn->children)) { + free(buf); + return; + } + btrb = new_btrb(buf, size); + add_btrb_to_children(btrb, btrn, 0); +} + +/** + * Feed data to child nodes of a buffer tree node. + * + * \param btrp The buffer pool. + * \param size The number of bytes to be allocated and fed to each child. + * \param btrn The node whose children are to be fed. + * + * This function allocates the amount of bytes from the buffer pool area, + * starting at the current value of the write head, and creates buffer + * references to the resulting part of the buffer pool area, one for each child + * of \a btrn. The references are then fed into the input queue of each child. + */ +void btr_add_output_pool(struct btr_pool *btrp, size_t size, + struct btr_node *btrn) +{ + struct btr_buffer *btrb; + char *buf; + size_t avail; + + assert(size != 0); + if (list_empty(&btrn->children)) + return; + avail = btr_pool_get_buffer(btrp, &buf); + assert(avail >= size); + btr_pool_allocate(btrp, size); + btrb = new_btrb(buf, size); + btrb->pool = btrp; + add_btrb_to_children(btrb, btrn, 0); +} + +/** + * Copy data to write head of a buffer pool and feed it to all children nodes. + * + * \param src The source buffer. + * \param n The size of the source buffer in bytes. + * \param btrp The destination buffer pool. + * \param btrn Add the data as output of this node. + * + * This is expensive. The caller must make sure the data fits into the buffer + * pool area. + */ +void btr_copy(const void *src, size_t n, struct btr_pool *btrp, + struct btr_node *btrn) +{ + char *buf; + size_t sz, copy; + + if (n == 0) + return; + assert(n <= btr_pool_unused(btrp)); + sz = btr_pool_get_buffer(btrp, &buf); + copy = PARA_MIN(sz, n); + memcpy(buf, src, copy); + btr_add_output_pool(btrp, copy, btrn); + if (copy == n) + return; + sz = btr_pool_get_buffer(btrp, &buf); + assert(sz >= n - copy); + memcpy(buf, src + copy, n - copy); + btr_add_output_pool(btrp, n - copy, btrn); +} + +static void btr_pushdown_br(struct btr_buffer_reference *br, struct btr_node *btrn) +{ + add_btrb_to_children(br->btrb, btrn, br->consumed); + btr_drop_buffer_reference(br); +} + +/** + * Feed all buffer references of the input queue through the output channel. + * + * \param btrn The node whose buffer references should be pushed down. + * + * This function is useful for filters that do not change the contents of the + * buffers at all, like the wav filter or the amp filter if no amplification + * was specified. This function is rather cheap. + * + * \sa \ref btr_pushdown_one(). + */ +void btr_pushdown(struct btr_node *btrn) +{ + struct btr_buffer_reference *br, *tmp; + + FOR_EACH_BUFFER_REF_SAFE(br, tmp, btrn) + btr_pushdown_br(br, btrn); +} + +/** + * Feed the next buffer of the input queue through the output channel. + * + * \param btrn The node whose first input queue buffer should be pushed down. + * + * This works like \ref btr_pushdown() but pushes down only one buffer + * reference. + */ +void btr_pushdown_one(struct btr_node *btrn) +{ + struct btr_buffer_reference *br; + + if (list_empty(&btrn->input_queue)) + return; + br = list_first_entry(&btrn->input_queue, struct btr_buffer_reference, node); + btr_pushdown_br(br, btrn); +} + +/* + * Find out whether a node is a leaf node. + * + * \param btrn The node to check. + * + * \return True if this node has no children. False otherwise. + */ +static bool btr_no_children(struct btr_node *btrn) +{ + return list_empty(&btrn->children); +} + +/** + * Find out whether a node is an orphan node. + * + * \param btrn The buffer tree node. + * + * \return True if \a btrn has no parent. + * + * This function will always return true for the root node. However in case + * nodes have been removed from the tree, other nodes may become orphans too. + */ +bool btr_no_parent(struct btr_node *btrn) +{ + return !btrn->parent; +} + +/** + * Find out whether it is OK to change an input buffer. + * + * \param btrn The buffer tree node to check. + * + * This is used by filters that produce exactly the same amount of output as + * there is input. The amp filter which multiplies each sample by some number + * is an example of such a filter. If there are no other nodes in the buffer + * tree that read the same input stream (i.e. if \a btrn has no siblings), a + * node may modify its input buffer directly and push down the modified buffer + * to its children, thereby avoiding to allocate a possibly large additional + * buffer. + * + * Since the buffer tree may change at any time, this function should be called + * during each post_select call. + * + * \return True if \a btrn has no siblings. + */ +bool btr_inplace_ok(struct btr_node *btrn) +{ + if (!btrn->parent) + return true; + return list_is_singular(&btrn->parent->children); +} + +static inline size_t br_available_bytes(struct btr_buffer_reference *br) +{ + return br->btrb->size - br->consumed; +} + +static size_t btr_get_buffer_by_reference(struct btr_buffer_reference *br, char **buf) +{ + if (buf) + *buf = br->btrb->buf + br->consumed; + return br_available_bytes(br); +} + +/** + * Obtain the next buffer of the input queue of a buffer tree node. + * + * \param btrn The node whose input queue is to be queried. + * \param bufp Result pointer. + * + * \return The number of bytes that can be read from buf. Zero if the input + * buffer queue is empty. In this case the value of \a bufp is undefined. + */ +size_t btr_next_buffer(struct btr_node *btrn, char **bufp) +{ + struct btr_buffer_reference *br; + char *buf, *result = NULL; + size_t sz, rv = 0; + + FOR_EACH_BUFFER_REF(br, btrn) { + sz = btr_get_buffer_by_reference(br, &buf); + if (!result) { + result = buf; + rv = sz; + if (!br->btrb->pool) + break; + continue; + } + if (!br->btrb->pool) + break; + if (result + rv != buf) + break; + rv += sz; + } + if (bufp) + *bufp = result; + return rv; +} + +/** + * Deallocate the given number of bytes from the input queue. + * + * \param btrn The buffer tree node. + * \param numbytes The number of bytes to be deallocated. + * + * This function must be used to get rid of existing buffer references in the + * node's input queue. If no references to a buffer remain, the underlying + * buffers are either freed (in the non-buffer pool case) or the read head of + * the buffer pool is being advanced. + * + * Note that \a numbytes may be smaller than the buffer size. In this case the + * buffer is not deallocated and subsequent calls to btr_next_buffer() return + * the remaining part of the buffer. + */ +void btr_consume(struct btr_node *btrn, size_t numbytes) +{ + struct btr_buffer_reference *br, *tmp; + size_t sz; + + if (numbytes == 0) + return; + br = get_first_input_br(btrn); + assert(br); + + if (br->wrap_count == 0) { + /* + * No wrap buffer. Drop buffer references whose buffer + * has been fully used. */ + FOR_EACH_BUFFER_REF_SAFE(br, tmp, btrn) { + if (br->consumed + numbytes <= br->btrb->size) { + br->consumed += numbytes; + if (br->consumed == br->btrb->size) + btr_drop_buffer_reference(br); + return; + } + numbytes -= br->btrb->size - br->consumed; + btr_drop_buffer_reference(br); + } + assert(false); + } + /* + * We have a wrap buffer, consume from it. If in total, i.e. including + * previous calls to brt_consume(), less than wrap_count has been + * consumed, there's nothing more we can do. + * + * Otherwise we drop the wrap buffer and consume from subsequent + * buffers of the input queue the correct amount of bytes. This is the + * total number of bytes that have been consumed from the wrap buffer. + */ + PARA_DEBUG_LOG("consuming %zu/%zu bytes from wrap buffer\n", numbytes, + br_available_bytes(br)); + + assert(numbytes <= br_available_bytes(br)); + if (br->consumed + numbytes < br->wrap_count) { + br->consumed += numbytes; + return; + } + PARA_DEBUG_LOG("dropping wrap buffer (%zu bytes)\n", br->btrb->size); + /* get rid of the wrap buffer */ + sz = br->consumed + numbytes; + btr_drop_buffer_reference(br); + return btr_consume(btrn, sz); +} + +void btr_drain(struct btr_node *btrn) +{ + struct btr_buffer_reference *br, *tmp; + + FOR_EACH_BUFFER_REF_SAFE(br, tmp, btrn) + btr_drop_buffer_reference(br); +} + +/** + * Free all resources allocated by btr_new_node(). + * + * \param btrn Pointer to a btr node obtained by \ref btr_new_node(). + * + * Like free(3), it is OK to call this with a \p NULL pointer argument. + */ +void btr_free_node(struct btr_node *btrn) +{ + if (!btrn) + return; + free(btrn->name); + free(btrn); +} + +/** + * Remove a node from a buffer tree. + * + * \param btrn The node to remove. + * + * This makes all child nodes of \a btrn orphans and removes \a btrn from the + * list of children of its parent. Moreover, the input queue of \a btrn is + * flushed if it is not empty. + * + * \sa \ref btr_splice_out_node. + */ +void btr_remove_node(struct btr_node *btrn) +{ + struct btr_node *ch; + + if (!btrn) + return; + PARA_NOTICE_LOG("removing btr node %s from buffer tree\n", btrn->name); + FOR_EACH_CHILD(ch, btrn) + ch->parent = NULL; + btr_drain(btrn); + if (btrn->parent) + list_del(&btrn->node); +} + +/** + * Return the amount of available input bytes of a buffer tree node. + * + * \param btrn The node whose input size should be computed. + * + * \return The total number of bytes available in the node's input + * queue. + * + * This simply iterates over all buffer references in the input queue and + * returns the sum of the sizes of all references. + */ +size_t btr_get_input_queue_size(struct btr_node *btrn) +{ + struct btr_buffer_reference *br; + size_t size = 0, wrap_consumed = 0; + + FOR_EACH_BUFFER_REF(br, btrn) { + if (br->wrap_count != 0) { + wrap_consumed = br->consumed; + continue; + } + size += br_available_bytes(br); + } + assert(wrap_consumed <= size); + size -= wrap_consumed; + return size; +} + +/** + * Remove a node from the buffer tree, reconnecting parent and children. + * + * \param btrn The node to splice out. + * + * This function is used by buffer tree nodes that do not exist during the + * whole lifetime of the buffer tree. Unlike btr_remove_node(), calling + * btr_splice_out_node() does not split the tree into disconnected components + * but reconnects the buffer tree by making all child nodes of \a btrn children + * of the parent of \a btrn. + */ +void btr_splice_out_node(struct btr_node *btrn) +{ + struct btr_node *ch, *tmp; + + assert(btrn); + PARA_NOTICE_LOG("splicing out %s\n", btrn->name); + btr_pushdown(btrn); + if (btrn->parent) + list_del(&btrn->node); + FOR_EACH_CHILD_SAFE(ch, tmp, btrn) { + PARA_INFO_LOG("parent(%s): %s\n", ch->name, + btrn->parent? btrn->parent->name : "NULL"); + ch->parent = btrn->parent; + if (btrn->parent) + list_move(&ch->node, &btrn->parent->children); + } + assert(list_empty(&btrn->children)); +} + +/** + * Return number of queued output bytes of a buffer tree node. + * + * \param btrn The node whose output queue size should be computed. + * + * This function iterates over all children of the given node and returns the + * size of the largest input queue. + */ +size_t btr_get_output_queue_size(struct btr_node *btrn) +{ + size_t max_size = 0; + struct btr_node *ch; + + FOR_EACH_CHILD(ch, btrn) { + size_t size = btr_get_input_queue_size(ch); + max_size = PARA_MAX(max_size, size); + } + return max_size; +} + +/** + * Execute a inter-node command on a parent node. + * + * \param btrn The node to start looking. + * \param command The command to execute. + * \param value_result Additional arguments and result value. + * + * This function traverses the buffer tree upwards and looks for parent nodes + * of \a btrn that understands \a command. On the first such node the command + * is executed, and the result is stored in \a value_result. + * + * \return \p -ENOTSUP if no parent node of \a btrn understands \a command. + * Otherwise the return value of the command handler is returned. + */ +int btr_exec_up(struct btr_node *btrn, const char *command, char **value_result) +{ + int ret; + + for (; btrn; btrn = btrn->parent) { + struct btr_node *parent = btrn->parent; + if (!parent) + return -ERRNO_TO_PARA_ERROR(ENOTSUP); + if (!parent->execute) + continue; + PARA_INFO_LOG("parent: %s, cmd: %s\n", parent->name, command); + ret = parent->execute(parent, command, value_result); + if (ret == -ERRNO_TO_PARA_ERROR(ENOTSUP)) + continue; + if (ret < 0) + return ret; + if (value_result && *value_result) + PARA_NOTICE_LOG("%s(%s): %s\n", command, parent->name, + *value_result); + return 1; + } + return -ERRNO_TO_PARA_ERROR(ENOTSUP); +} + +/** + * Obtain the context of a buffer node tree. + * + * The returned pointer equals the context pointer used at creation time of the + * node. + * + * \sa btr_new_node(), struct \ref btr_node_description. + */ +void *btr_context(struct btr_node *btrn) +{ + return btrn->context; +} + +static bool need_buffer_pool_merge(struct btr_node *btrn) +{ + struct btr_buffer_reference *br = get_first_input_br(btrn); + + if (!br) + return false; + if (br->wrap_count != 0) + return true; + if (br->btrb->pool) + return true; + return false; +} + +static void merge_input_pool(struct btr_node *btrn, size_t dest_size) +{ + struct btr_buffer_reference *br, *wbr = NULL; + int num_refs; /* including wrap buffer */ + char *buf, *buf1 = NULL, *buf2 = NULL; + size_t sz, sz1 = 0, sz2 = 0, wb_consumed = 0; + + br = get_first_input_br(btrn); + if (!br || br_available_bytes(br) >= dest_size) + return; + num_refs = 0; + FOR_EACH_BUFFER_REF(br, btrn) { + num_refs++; + sz = btr_get_buffer_by_reference(br, &buf); + if (sz == 0) + break; + if (br->wrap_count != 0) { + assert(!wbr); + assert(num_refs == 1); + wbr = br; + if (sz >= dest_size) + return; + wb_consumed = br->consumed; + continue; + } + if (!buf1) { + buf1 = buf; + sz1 = sz; + goto next; + } + if (buf1 + sz1 == buf) { + sz1 += sz; + goto next; + } + if (!buf2) { + buf2 = buf; + sz2 = sz; + goto next; + } + assert(buf2 + sz2 == buf); + sz2 += sz; +next: + if (sz1 + sz2 >= dest_size + wb_consumed) + break; + } + if (!buf2) /* nothing to do */ + return; + assert(buf1 && sz2 > 0); + /* + * If the second buffer is large, we only take the first part of it to + * avoid having to memcpy() huge buffers. + */ + sz2 = PARA_MIN(sz2, (size_t)(64 * 1024)); + if (!wbr) { + /* Make a new wrap buffer combining buf1 and buf2. */ + sz = sz1 + sz2; + buf = para_malloc(sz); + PARA_DEBUG_LOG("merging input buffers: (%p:%zu, %p:%zu) -> %p:%zu\n", + buf1, sz1, buf2, sz2, buf, sz); + memcpy(buf, buf1, sz1); + memcpy(buf + sz1, buf2, sz2); + br = para_calloc(sizeof(*br)); + br->btrb = new_btrb(buf, sz); + br->btrb->refcount = 1; + br->consumed = 0; + /* This is a wrap buffer */ + br->wrap_count = sz1; + para_list_add(&br->node, &btrn->input_queue); + return; + } + /* + * We already have a wrap buffer, but it is too small. It might be + * partially used. + */ + if (wbr->wrap_count == sz1 && wbr->btrb->size >= sz1 + sz2) /* nothing we can do about it */ + return; + sz = sz1 + sz2 - wbr->btrb->size; /* amount of new data */ + PARA_DEBUG_LOG("increasing wrap buffer %zu -> %zu\n", wbr->btrb->size, + wbr->btrb->size + sz); + wbr->btrb->size += sz; + wbr->btrb->buf = para_realloc(wbr->btrb->buf, wbr->btrb->size); + /* copy the new data to the end of the reallocated buffer */ + assert(sz2 >= sz); + memcpy(wbr->btrb->buf + wbr->btrb->size - sz, buf2 + sz2 - sz, sz); +} + +/** + * Merge the first two input buffers into one. + * + * This is a quite expensive operation. + * + * \return The number of buffers that have been available (zero, one or two). + */ +static int merge_input(struct btr_node *btrn) +{ + struct btr_buffer_reference *brs[2], *br; + char *bufs[2], *buf; + size_t szs[2], sz; + int i; + + if (list_empty(&btrn->input_queue)) + return 0; + if (list_is_singular(&btrn->input_queue)) + return 1; + i = 0; + /* get references to the first two buffers */ + FOR_EACH_BUFFER_REF(br, btrn) { + brs[i] = br; + szs[i] = btr_get_buffer_by_reference(brs[i], bufs + i); + i++; + if (i == 2) + break; + } + assert(i == 2); + /* make a new btrb that combines the two buffers and a br to it. */ + sz = szs[0] + szs[1]; + buf = para_malloc(sz); + PARA_DEBUG_LOG("%s: memory merging input buffers: (%zu, %zu) -> %zu\n", + btrn->name, szs[0], szs[1], sz); + memcpy(buf, bufs[0], szs[0]); + memcpy(buf + szs[0], bufs[1], szs[1]); + + br = para_calloc(sizeof(*br)); + br->btrb = new_btrb(buf, sz); + br->btrb->refcount = 1; + + /* replace the first two refs by the new one */ + btr_drop_buffer_reference(brs[0]); + btr_drop_buffer_reference(brs[1]); + para_list_add(&br->node, &btrn->input_queue); + return 2; +} + +/** + * Combine input queue buffers. + * + * \param btrn The buffer tree node whose input should be merged. + * \param dest_size Stop merging if a buffer of at least this size exists. + * + * Used to combine as many buffers as needed into a single buffer whose size is + * at least \a dest_size. This function is rather cheap in case the parent node + * uses buffer pools and rather expensive otherwise. + * + * Note that if less than \a dest_size bytes are available in total, this + * function does nothing and subsequent calls to btr_next_buffer() will still + * return a buffer size less than \a dest_size. + */ +void btr_merge(struct btr_node *btrn, size_t dest_size) +{ + if (need_buffer_pool_merge(btrn)) + return merge_input_pool(btrn, dest_size); + for (;;) { + char *buf; + size_t len = btr_next_buffer(btrn, &buf); + if (len >= dest_size) + return; + PARA_DEBUG_LOG("input size = %zu < %zu = dest\n", len, dest_size); + if (merge_input(btrn) < 2) + return; + } +} + +static bool btr_eof(struct btr_node *btrn) +{ + char *buf; + size_t len = btr_next_buffer(btrn, &buf); + + return (len == 0 && btr_no_parent(btrn)); +} + +static void log_tree_recursively(struct btr_node *btrn, int loglevel, int depth) +{ + struct btr_node *ch; + const char spaces[] = " ", *space = spaces + 16 - depth; + + if (depth > 16) + return; + para_log(loglevel, "%s%s\n", space, btrn->name); + FOR_EACH_CHILD(ch, btrn) + log_tree_recursively(ch, loglevel, depth + 1); +} + +/** + * Write the current buffer (sub-)tree to the log. + * + * \param btrn Start logging at this node. + * \param loglevel Set severity with which the tree should be logged. + */ +void btr_log_tree(struct btr_node *btrn, int loglevel) +{ + return log_tree_recursively(btrn, loglevel, 0); +} + +/** + * Find the node with the given name in the buffer tree. + * + * \param name The name of the node to search. + * \param root Where to start the search. + * + * \return A pointer to the node with the given name on success. If \a name is + * \p NULL, the function returns \a root. If there is no node with the given + * name, \p NULL is returned. + */ +struct btr_node *btr_search_node(const char *name, struct btr_node *root) +{ + struct btr_node *ch; + + if (!name) + return root; + if (!strcmp(root->name, name)) + return root; + FOR_EACH_CHILD(ch, root) { + struct btr_node *result = btr_search_node(name, ch); + if (result) + return result; + } + return NULL; +} + +/** 640K ought to be enough for everybody ;) */ +#define BTRN_MAX_PENDING (96 * 1024) + +/** + * Return the current state of a buffer tree node. + * + * \param btrn The node whose state should be queried. + * \param min_iqs The minimal input queue size. + * \param type The supposed type of \a btrn. + * + * Most users of the buffer tree subsystem call this function from both + * their pre_select and the post_select methods. + * + * \return Negative if an error condition was detected, zero if there + * is nothing to do and positive otherwise. + * + * Examples: + * + * - If a non-root node has no parent and an empty input queue, the function + * returns \p -E_BTR_EOF. Similarly, if a non-leaf node has no children, \p + * -E_BTR_NO_CHILD is returned. + * + * - If less than \a min_iqs many bytes are available in the input queue and no + * EOF condition was detected, the function returns zero. + * + * - If there's plenty of data left in the input queue of the children of \a + * btrn, the function also returns zero in order to bound the memory usage of + * the buffer tree. + */ +int btr_node_status(struct btr_node *btrn, size_t min_iqs, + enum btr_node_type type) +{ + size_t iqs; + + assert(btrn); + if (type != BTR_NT_LEAF) { + if (btr_no_children(btrn)) + return -E_BTR_NO_CHILD; + if (btr_get_output_queue_size(btrn) > BTRN_MAX_PENDING) + return 0; + } + if (type != BTR_NT_ROOT) { + if (btr_eof(btrn)) + return -E_BTR_EOF; + iqs = btr_get_input_queue_size(btrn); + if (iqs == 0) /* we have a parent, because not eof */ + return 0; + if (iqs < min_iqs && !btr_no_parent(btrn)) + return 0; + } + return 1; +} + +/** + * Get the time of the first I/O for a buffer tree node. + * + * \param btrn The node whose I/O time should be obtained. + * \param tv Result pointer. + * + * Mainly useful for the time display of para_audiod. + */ +void btr_get_node_start(struct btr_node *btrn, struct timeval *tv) +{ + *tv = btrn->start; +} diff --git a/buffer_tree.h b/buffer_tree.h new file mode 100644 index 00000000..fcf9df62 --- /dev/null +++ b/buffer_tree.h @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2009-2011 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** + * \file buffer_tree.h Buffer tree management. + * + * \par Buffer trees and buffer tree nodes. + * The buffer tree API offers a more powerful method than standard unix pipes + * for managing the data flow from the producer of the data (e.g. the network + * receiver) to its consumer(s) (e.g. a sound card). + * + * A buffer tree consists of buffer tree nodes linked via certain parent/child + * relationships. + * + * Each data buffer starts its way from the root of the buffer tree. At each + * node the data is investigated and possibly changed. New data is then fed to + * each child. Everything happens within one single-treaded process. There are + * no file descriptors and no calls to read() or write(). + * + * Whenever a node in the buffer tree creates output, either by creating a new + * buffer or by pushing down buffers received from its parent, references to + * that buffer are created for all children of the node. The buffer tree code + * tries hard to avoid to copy buffer contents, but is forced to do so in case + * there are alignment constraints. + * + * Communication between nodes is possible via the btr_exec_up() function. + * For example, in para_audiod the alsa writer asks all parent nodes + * for for the number of channels and the sample rate of the current + * audio file. + * + * Buffer pools - An alternative to malloc/free buffer management. + * + * Non-leaf nodes usually create output to be processed by their children. The + * data must be fed through the output channel(s) of the node in order to make + * that data available to each child. + * + * The easiest way to do so is to malloc() a buffer, fill it, and then call + * btr_add_output(). This adds references to that buffer to all children. The + * buffer is automatically freed if no buffer tree node is using it any more. + * + * This approach, while being simple, has some drawbacks, especially affecting + * the root nodes of the buffer tree. Often the data source which is + * represented by a root node does not know in advance how much data will be + * available. Therefore the allocated buffer is either larger than what can + * currently be read, or is too small so that multiple buffers have to be used. + * + * While this could be worked around by using a large buffer and calling + * realloc() afterwards to shrink the buffer according to how much has been + * read, there is a second problem which comes from the alignment constraints + * of some filters, mainly the decoders like mp3dec. These need a minimal + * amount of data to proceed, and most of them even need this amount as one + * contiguous buffer, i.e. not spread out over two or more buffers. + * + * Although the buffer tree code handles this case just fine, it can be + * expensive because two or more buffers must be merged by copying buffer + * contents around in order to satisfy the constraint. + * + * This is where buffer pools come into play. Buffer pools try to satisfy + * alignment constraints without copying buffer content whenever possible. To + * avoid spreading out the input data over the address space like in the + * malloc/free approach, a fixed large contiguous buffer (the area) is used + * instead. A buffer pool consists basically of an area and two pointers, the + * read head and the write head. + * + * Once a buffer pool has been created, its node, e.g. a receiver, obtains the + * current value of the write head and writes new data to this location. Then + * it calls btr_add_output_pool() to tell much data it has written. This + * advances the write head accordingly, and it also creates references to the + * newly written part of the area for the children of the node to consume. + * + * Child nodes consume data by working through their input queue, which is a + * list of buffer references. Once the content of a buffer is no longer needed + * by a child node, the child calls btr_consume() to indicate the amount of + * data which can be dropped from the child's point of view. If no reference + * to some region of the buffer pool area remains, the read head of the buffer + * pool advances, making space available for the receiver node to fill. + * + * No matter if malloc() or a buffer pool is used, the buffer tree code takes + * care of alignment constraints imposed by the consumers. In the buffer pool + * case, automatic merging of references to contiguous buffers is performed. + * memcpy is only used if a constraint can not be satisfied by using the + * remaining part of the area only. This only happens when the end of the area + * is reached. + */ + +struct btr_node; +struct btr_pool; + +/** + * The three different types of buffer tree nodes. + * + * Usually, there is exactly one node in the buffer tree, the root node, which + * has no parent. Every node different from the root node has exactly one + * parent. The root node represents a data source. Root nodes are thus used by + * the receivers of paraslash. Also, reading from stdin is realized as the root + * node of a buffer tree. + * + * Each node may have arbitrary many children, including none. Nodes with no + * children are called leaf nodes. They represent a data sink, like the alsa or + * the file writer. + * + * Hence there are three different types of buffer tree nodes: The root node + * and the leaf nodes and nodes which have both a parent and at least one + * child. Such a node is called an internal node. + * + * Internal nodes represent filters through which data buffers flow, possibly + * while being altered on their way to the children of the node. Examples of + * internal nodes are audio file decoders (mp3dec, oggdec, ...), but also the + * check for a wav header is implemented as an internal buffer tree node. + */ +enum btr_node_type { + /* This node has no parent. */ + BTR_NT_ROOT, + /* Node has parent and at least one child. */ + BTR_NT_INTERNAL, + /* Node has no children. */ + BTR_NT_LEAF, +}; + +/** + * Per node handler used for inter node communication. + * + * Each node in the buffer tree may optionally provide a command handler for + * execution of commands by other nodes of the tree. + * + * It is dependent on the node in question which commands are supported and how + * they work. In any case, the input for the command handler is some string and + * its output is also a string which is returned via the \a result pointer of + * the handler. + * + * This mechanism is used in para_audiod e.g. by the alsa writer which needs to + * know the sample rate of its input known to e.g. the mp3dec node further up + * in the buffer tree. + */ +typedef int (*btr_command_handler)(struct btr_node *btrn, + const char *command, char **result); + +/** + * Structure for creating new buffer tree nodes. + * + * btr_new_node() takes a pointer to such a structure. + * + * There are four different combinations of \a parent and child: + * + * 1. both \p NULL. This creates a new buffer tree with a single isolated node. + * + * 2. \a parent != \p NULL, \a child == NULL. This creates a new leaf node by + * adding the new node to the list of children of the given parent node. + * + * 3. \a parent == NULL, \a child != NULL. The new node becomes the new root of + * the buffer tree. The child must be old root. + * + * 4. both != NULL. This creates a new internal node. \a child must be child of + * p. This mode of operation is currently not needed and is thus not yet + * implemented. + */ +struct btr_node_description { + /** Name of the new node. */ + const char *name; + /** Parent of the new node. */ + struct btr_node *parent; + /** Child of the new node. */ + struct btr_node *child; + /** Used for inter node communication. Optional. */ + btr_command_handler handler; + /** Points usually to the struct that contains the node pointer. */ + void *context; +}; + +size_t btr_pool_size(struct btr_pool *btrp); +struct btr_pool *btr_pool_new(const char *name, size_t area_size); +void btr_pool_free(struct btr_pool *btrp); +size_t btr_pool_get_buffer(struct btr_pool *btrp, char **result); +int btr_pool_get_buffers(struct btr_pool *btrp, struct iovec iov[2]); +void btr_add_output_pool(struct btr_pool *btrp, size_t size, + struct btr_node *btrn); +size_t btr_pool_unused(struct btr_pool *btrp); +void btr_copy(const void *src, size_t n, struct btr_pool *btrp, + struct btr_node *btrn); + +struct btr_node *btr_new_node(struct btr_node_description *bnd); +void btr_remove_node(struct btr_node *btrn); +void btr_free_node(struct btr_node *btrn); +void btr_add_output(char *buf, size_t size, struct btr_node *btrn); +size_t btr_get_input_queue_size(struct btr_node *btrn); +size_t btr_get_output_queue_size(struct btr_node *btrn); +bool btr_no_parent(struct btr_node *btrn); +size_t btr_next_buffer(struct btr_node *btrn, char **bufp); +void btr_consume(struct btr_node *btrn, size_t numbytes); +int btr_exec_up(struct btr_node *btrn, const char *command, char **value_result); +void btr_splice_out_node(struct btr_node *btrn); +void btr_pushdown(struct btr_node *btrn); +void *btr_context(struct btr_node *btrn); +void btr_merge(struct btr_node *btrn, size_t dest_size); +void btr_log_tree(struct btr_node *btrn, int loglevel); +void btr_pushdown_one(struct btr_node *btrn); +bool btr_inplace_ok(struct btr_node *btrn); +int btr_node_status(struct btr_node *btrn, size_t min_iqs, + enum btr_node_type type); +void btr_get_node_start(struct btr_node *btrn, struct timeval *tv); +struct btr_node *btr_search_node(const char *name, struct btr_node *root); +void btr_drain(struct btr_node *btrn); diff --git a/chunk_queue.c b/chunk_queue.c index d295b6c0..c0a8fb51 100644 --- a/chunk_queue.c +++ b/chunk_queue.c @@ -1,15 +1,16 @@ /* - * Copyright (C) 2007-2009 Andre Noll + * Copyright (C) 2007-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file chunk_queue.c Queuing functions for paraslash senders. */ +#include + #include "para.h" #include "list.h" #include "afh.h" -#include "vss.h" #include "string.h" #include "error.h" diff --git a/chunk_queue.h b/chunk_queue.h index 2bed999c..bcd43f8e 100644 --- a/chunk_queue.h +++ b/chunk_queue.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007-2009 Andre Noll + * Copyright (C) 2007-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ diff --git a/client.c b/client.c index 003c1e60..ec32cd41 100644 --- a/client.c +++ b/client.c @@ -1,19 +1,25 @@ /* - * Copyright (C) 1997-2009 Andre Noll + * Copyright (C) 1997-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file client.c the client program used to connect to para_server */ +#include +#include +#include + #include "para.h" #include "list.h" #include "sched.h" +#include "crypt.h" #include "client.cmdline.h" #include "string.h" #include "stdin.h" #include "stdout.h" #include "client.h" +#include "buffer_tree.h" #include "error.h" INIT_CLIENT_ERRLISTS; @@ -30,19 +36,12 @@ static void supervisor_post_select(__a_unused struct sched *s, struct task *t) } if (ct->status == CL_SENDING) { stdin_set_defaults(&sit); - sit.buf = para_malloc(sit.bufsize), register_task(&sit.task); - ct->inbuf = sit.buf; - ct->in_loaded = &sit.loaded; - ct->in_error = &sit.task.error; t->error = -E_TASK_STARTED; return; } if (ct->status == CL_RECEIVING) { stdout_set_defaults(&sot); - sot.bufp = &ct->buf; - sot.loaded = &ct->loaded; - sot.input_error = &ct->task.error; register_task(&sot.task); t->error = -E_TASK_STARTED; return; @@ -54,10 +53,9 @@ static struct task svt = { .status = "supervisor task" }; -static int client_loglevel; /* loglevel */ +static int client_loglevel = LL_ERROR; /* loglevel */ INIT_STDERR_LOGGING(client_loglevel); - /** * The client program to connect to para_server. * @@ -65,12 +63,16 @@ INIT_STDERR_LOGGING(client_loglevel); * \param argv Usual argument vector. * * It registers two tasks: The client task that communicates with para_server - * and the standard out task that writes any output produced by the client task - * to standard out. + * and the supervisor task that minitors whether the client task intends to + * read from stdin or write to stdout. + * + * Once it has been determined whether the client command corresponds to a + * stdin command (addmood, addimg, ..), either the stdin task or the stdout + * task is set up to replace the supervisor task. * * \return EXIT_SUCCESS or EXIT_FAILURE * - * \sa client_open(), stdout.c, stdout.h, para_client(1), para_server(1) + * \sa client_open(), stdin.c, stdout.c, para_client(1), para_server(1) */ int main(int argc, char *argv[]) { @@ -78,15 +80,31 @@ int main(int argc, char *argv[]) int ret; static struct sched s; + init_random_seed_or_die(); s.default_timeout.tv_sec = 1; s.default_timeout.tv_usec = 0; - ret = client_open(argc, argv, &ct, &client_loglevel); - if (ret < 0) /* can not use PARA_LOG here because ct is NULL */ - exit(EXIT_FAILURE); + /* + * We add buffer tree nodes for stdin and stdout even though + * only one of them will be needed. This simplifies the code + * a bit wrt. to the buffer tree setup. + */ + sit.btrn = btr_new_node(&(struct btr_node_description) + EMBRACE(.name = "stdin")); + ret = client_open(argc, argv, &ct, &client_loglevel, sit.btrn, NULL); + if (ret < 0) + goto out; + sot.btrn = btr_new_node(&(struct btr_node_description) + EMBRACE(.name = "stdout", .parent = ct->btrn)); register_task(&svt); ret = schedule(&s); - if (ret < 0) - PARA_ERROR_LOG("%s\n", para_strerror(-ret)); +out: client_close(ct); - return ret >= 0? EXIT_SUCCESS: EXIT_FAILURE; + btr_free_node(sit.btrn); + btr_free_node(sot.btrn); + if (ret < 0) { + /* can not use PARA_LOG here because ct is NULL */ + fprintf(stderr, "%s\n", para_strerror(-ret)); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; } diff --git a/client.h b/client.h index c6d5c75d..667607e2 100644 --- a/client.h +++ b/client.h @@ -1,80 +1,52 @@ /* - * Copyright (C) 1997-2009 Andre Noll + * Copyright (C) 1997-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ -/** \file client.h common client functions and exported symbols from client_common.c */ +/** \file client.h Common client functions and exported symbols from client_common.c. */ #include -/** - * the different states of a connection from the view of the client - */ +/** The different states of a connection from the view of the client. */ enum { - /** tcp connection is established */ + /** TCP connection is established. */ CL_CONNECTED, - /** server sends the welcome message */ + /** Server sends the welcome message. */ CL_RECEIVED_WELCOME, - /** client sends the authentification request */ + /** Client sends the authentification request. */ CL_SENT_AUTH, - /** server sends a challenge */ - CL_RECEIVED_CHALLENGE, - /** clientd solves the challenge and sends the result */ + /** Client solves the challenge and sends the result. */ CL_SENT_CH_RESPONSE, - /** server accepts this authentication */ + /** Server accepts this authentication. */ CL_RECEIVED_PROCEED, - /** client sends the command */ + /** Client sends the command. */ CL_SENT_COMMAND, - /** server expects data */ + /** Server expects data. */ CL_SENDING, - /** client expects data */ + /** Client expects data. */ CL_RECEIVING, }; -/** size of the receiving buffer */ -#define CLIENT_BUFSIZE 8192 - -/** - * data specific to a client task - */ +/** Data specific to a client task. */ struct client_task { - /** the state of the connection */ + /** The state of the connection. */ int status; - /** the file descriptor */ - int fd; - /** the configuration (including the command) */ + /** The file descriptor and the rc4 keys. */ + struct rc4_context rc4c; + /** The configuration (including the command). */ struct client_args_info conf; - /** the config file for client options */ + /** The config file for client options. */ char *config_file; - /** the RSA private key */ + /** The RSA private key. */ char *key_file; - /** paraslash user name */ + /** Paraslash user name. */ char *user; - /** session key for receiving data */ - RC4_KEY rc4_recv_key; - /** session key for sending data */ - RC4_KEY rc4_send_key; - /** the client task structure */ + /** The client task structure. */ struct task task; - /** the buffer used for handshake and receiving */ - char *buf; - /** number of bytes loaded in \p buf */ - size_t loaded; - /** non-zero if the pre_select hook added \p fd to the read fd set */ - int check_r; - /** non-zero if the pre_select hook added \p fd to the write fd set */ - int check_w; - /** the decrypted challenge */ - long unsigned challenge_nr; - /** pointer to the data to be sent to para_server */ - char *inbuf; - /** number of bytes loaded in \p inbuf */ - size_t *in_loaded; - /** Non-zero if input task encountered an eof or an error condition. */ - int *in_error; + struct btr_node *btrn; }; void client_close(struct client_task *ct); -int client_open(int argc, char *argv[], struct client_task **ct, - int *loglevel); +int client_open(int argc, char *argv[], struct client_task **ct_ptr, + int *loglevel, struct btr_node *parent, struct btr_node *child); diff --git a/client_common.c b/client_common.c index cf77acd8..a7115fd7 100644 --- a/client_common.c +++ b/client_common.c @@ -1,13 +1,15 @@ /* - * Copyright (C) 1997-2009 Andre Noll + * Copyright (C) 1997-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file client_common.c Common functions of para_client and para_audiod. */ +#include #include #include +#include #include "para.h" #include "error.h" @@ -21,33 +23,11 @@ #include "string.h" #include "client.cmdline.h" #include "client.h" +#include "hash.h" +#include "buffer_tree.h" -/* - * Rc4-encrypt data before sending. - * - * \param len The number of bytes to encrypt. - * \param indata Pointer to the input data of length \a len to be encrypted. - * \param outdata Result-pointer that holds the encrypted data. - * \param private_data Contains the rc4 key. - */ -static void rc4_send(unsigned long len, const unsigned char *indata, - unsigned char *outdata, void *private_data) -{ - struct client_task *ct = private_data; - RC4(&ct->rc4_send_key, len, indata, outdata); -} - -/* - * Rc4-decrypt received data. - * - * Parameters are identical to those of rc4_send. - */ -static void rc4_recv(unsigned long len, const unsigned char *indata, - unsigned char *outdata, void *private_data) -{ - struct client_task *ct = private_data; - RC4(&ct->rc4_recv_key, len, indata, outdata); -} +/** The size of the receiving buffer. */ +#define CLIENT_BUFSIZE 4000 /** * Close the connection to para_server and free all resources. @@ -60,11 +40,8 @@ void client_close(struct client_task *ct) { if (!ct) return; - if (ct->fd >= 0) { - disable_crypt(ct->fd); - close(ct->fd); - } - free(ct->buf); + if (ct->rc4c.fd >= 0) + close(ct->rc4c.fd); free(ct->user); free(ct->config_file); free(ct->key_file); @@ -88,62 +65,71 @@ void client_close(struct client_task *ct) */ static void client_pre_select(struct sched *s, struct task *t) { + int ret; struct client_task *ct = container_of(t, struct client_task, task); + struct btr_node *btrn = ct->btrn; - ct->check_r = 0; - ct->check_w = 0; - if (ct->fd < 0) + if (ct->rc4c.fd < 0) return; switch (ct->status) { case CL_CONNECTED: case CL_SENT_AUTH: case CL_SENT_CH_RESPONSE: case CL_SENT_COMMAND: - para_fd_set(ct->fd, &s->rfds, &s->max_fileno); - ct->check_r = 1; + para_fd_set(ct->rc4c.fd, &s->rfds, &s->max_fileno); return; case CL_RECEIVED_WELCOME: - case CL_RECEIVED_CHALLENGE: case CL_RECEIVED_PROCEED: - para_fd_set(ct->fd, &s->wfds, &s->max_fileno); - ct->check_w = 1; + para_fd_set(ct->rc4c.fd, &s->wfds, &s->max_fileno); return; case CL_RECEIVING: - if (ct->loaded < CLIENT_BUFSIZE - 1) { - para_fd_set(ct->fd, &s->rfds, &s->max_fileno); - ct->check_r = 1; + ret = btr_node_status(btrn, 0, BTR_NT_ROOT); + if (ret != 0) { + if (ret < 0) + sched_min_delay(s); + else + para_fd_set(ct->rc4c.fd, &s->rfds, + &s->max_fileno); } return; case CL_SENDING: - if (!ct->in_loaded) /* stdin task not yet started */ - return; - if (*ct->in_loaded) { - PARA_INFO_LOG("loaded: %zd\n", *ct->in_loaded); - para_fd_set(ct->fd, &s->wfds, &s->max_fileno); - ct->check_w = 1; - } else { - if (*ct->in_error) { - t->error = *ct->in_error; - s->timeout.tv_sec = 0; - s->timeout.tv_usec = 1; - } + ret = btr_node_status(btrn, 0, BTR_NT_LEAF); + if (ret != 0) { + if (ret < 0) + sched_min_delay(s); + else + para_fd_set(ct->rc4c.fd, &s->wfds, + &s->max_fileno); } return; } } -static ssize_t client_recv_buffer(struct client_task *ct) +static int client_recv_buffer(struct client_task *ct, fd_set *rfds, + char *buf, size_t sz, size_t *n) { - ssize_t ret = recv_buffer(ct->fd, ct->buf + ct->loaded, - CLIENT_BUFSIZE - ct->loaded); - if (!ret) - return -E_SERVER_EOF; - if (ret > 0) - ct->loaded += ret; - return ret; + int ret; + + if (ct->status < CL_SENT_CH_RESPONSE) + return read_nonblock(ct->rc4c.fd, buf, sz, rfds, n); + *n = 0; + ret = rc4_recv_buffer(&ct->rc4c, buf, sz); + /* + * rc4_recv_buffer is used with blocking fds elsewhere, so it + * does not use the nonblock-API. Therefore we need to + * check for EOF and EAGAIN. + */ + if (ret == 0) + return -E_SERVER_EOF; + if (ret == -ERRNO_TO_PARA_ERROR(EAGAIN)) + return 0; + if (ret < 0) + return ret; + *n = ret; + return 0; } /** @@ -162,87 +148,84 @@ static ssize_t client_recv_buffer(struct client_task *ct) static void client_post_select(struct sched *s, struct task *t) { struct client_task *ct = container_of(t, struct client_task, task); + struct btr_node *btrn = ct->btrn; + int ret = 0; + size_t n; + char buf[CLIENT_BUFSIZE]; t->error = 0; - if (ct->fd < 0) - return; - if (!ct->check_r && !ct->check_w) - return; - if (ct->check_r && !FD_ISSET(ct->fd, &s->rfds)) - return; - if (ct->check_w && !FD_ISSET(ct->fd, &s->wfds)) + if (ct->rc4c.fd < 0) return; switch (ct->status) { case CL_CONNECTED: /* receive welcome message */ - t->error = client_recv_buffer(ct); - if (t->error > 0) - ct->status = CL_RECEIVED_WELCOME; + ret = client_recv_buffer(ct, &s->rfds, buf, sizeof(buf), &n); + if (ret < 0 || n == 0) + goto out; + ct->status = CL_RECEIVED_WELCOME; return; case CL_RECEIVED_WELCOME: /* send auth command */ - sprintf(ct->buf, "auth %s%s", ct->conf.plain_given? - "" : "rc4 ", ct->user); - PARA_INFO_LOG("--> %s\n", ct->buf); - t->error = send_buffer(ct->fd, ct->buf); - if (t->error >= 0) - ct->status = CL_SENT_AUTH; - return; - case CL_SENT_AUTH: /* receive challenge number */ - ct->loaded = 0; - t->error = client_recv_buffer(ct); - if (t->error < 0) + sprintf(buf, AUTH_REQUEST_MSG "%s", ct->user); + PARA_INFO_LOG("--> %s\n", buf); + if (!FD_ISSET(ct->rc4c.fd, &s->wfds)) return; - if (t->error < 64) { - t->error = -E_INVALID_CHALLENGE; - PARA_ERROR_LOG("received the following: %s\n", ct->buf); - return; - } - PARA_INFO_LOG("<-- [challenge] (%d bytes)\n", t->error); - /* decrypt challenge number */ - t->error = para_decrypt_challenge(ct->key_file, &ct->challenge_nr, - (unsigned char *) ct->buf, t->error); - if (t->error > 0) - ct->status = CL_RECEIVED_CHALLENGE; + ret = send_buffer(ct->rc4c.fd, buf); + if (ret < 0) + goto out; + ct->status = CL_SENT_AUTH; return; - case CL_RECEIVED_CHALLENGE: /* send decrypted challenge */ - PARA_INFO_LOG("--> %lu\n", ct->challenge_nr); - t->error = send_va_buffer(ct->fd, "%s%lu", CHALLENGE_RESPONSE_MSG, - ct->challenge_nr); - if (t->error > 0) - ct->status = CL_SENT_CH_RESPONSE; + case CL_SENT_AUTH: + /* + * Receive challenge and rc4 keys, decrypt the challenge and + * send back the hash of the decrypted challenge. + */ + { + /* decrypted challenge/rc4 buffer */ + unsigned char crypt_buf[1024]; + /* the SHA1 of the decrypted challenge */ + unsigned char challenge_sha1[HASH_SIZE]; + + ret = client_recv_buffer(ct, &s->rfds, buf, sizeof(buf), &n); + if (ret < 0 || n == 0) + goto out; + PARA_INFO_LOG("<-- [challenge] (%zu bytes)\n", n); + ret = para_decrypt_buffer(ct->key_file, crypt_buf, + (unsigned char *)buf, n); + if (ret < 0) + goto out; + sha1_hash((char *)crypt_buf, CHALLENGE_SIZE, challenge_sha1); + RC4_set_key(&ct->rc4c.send_key, RC4_KEY_LEN, + crypt_buf + CHALLENGE_SIZE); + RC4_set_key(&ct->rc4c.recv_key, RC4_KEY_LEN, + crypt_buf + CHALLENGE_SIZE + RC4_KEY_LEN); + hash_to_asc(challenge_sha1, buf); + PARA_INFO_LOG("--> %s\n", buf); + ret = send_bin_buffer(ct->rc4c.fd, (char *)challenge_sha1, + HASH_SIZE); + if (ret < 0) + goto out; + ct->status = CL_SENT_CH_RESPONSE; return; + } case CL_SENT_CH_RESPONSE: /* read server response */ { - size_t bytes_received; - unsigned char rc4_buf[2 * RC4_KEY_LEN] = ""; - ct->loaded = 0; - t->error = client_recv_buffer(ct); - if (t->error < 0) - return; - bytes_received = t->error; - PARA_DEBUG_LOG("++++ server info ++++\n%s\n++++ end of server " - "info ++++\n", ct->buf); + ret = client_recv_buffer(ct, &s->rfds, buf, sizeof(buf), &n); + if (ret < 0 || n == 0) + goto out; /* check if server has sent "Proceed" message */ - t->error = -E_CLIENT_AUTH; - if (!strstr(ct->buf, PROCEED_MSG)) - return; - t->error = 0; + ret = -E_CLIENT_AUTH; + if (n < PROCEED_MSG_LEN) + goto out; + if (!strstr(buf, PROCEED_MSG)) + goto out; ct->status = CL_RECEIVED_PROCEED; - if (bytes_received < PROCEED_MSG_LEN + 32) - return; - PARA_INFO_LOG("decrypting session key\n"); - t->error = para_decrypt_buffer(ct->key_file, rc4_buf, - (unsigned char *)ct->buf + PROCEED_MSG_LEN + 1, - bytes_received - PROCEED_MSG_LEN - 1); - if (t->error < 0) - return; - RC4_set_key(&ct->rc4_send_key, RC4_KEY_LEN, rc4_buf); - RC4_set_key(&ct->rc4_recv_key, RC4_KEY_LEN, rc4_buf + RC4_KEY_LEN); - enable_crypt(ct->fd, rc4_recv, rc4_send, ct); + return; } case CL_RECEIVED_PROCEED: /* concat args and send command */ { int i; char *command = NULL; + if (!FD_ISSET(ct->rc4c.fd, &s->wfds)) + return; for (i = 0; i < ct->conf.inputs_num; i++) { char *tmp = command; command = make_message("%s\n%s", command? @@ -251,32 +234,79 @@ static void client_post_select(struct sched *s, struct task *t) } command = para_strcat(command, EOC_MSG "\n"); PARA_DEBUG_LOG("--> %s\n", command); - t->error = send_buffer(ct->fd, command); + ret = rc4_send_buffer(&ct->rc4c, command); free(command); - if (t->error > 0) - ct->status = CL_SENT_COMMAND; + if (ret < 0) + goto out; + ct->status = CL_SENT_COMMAND; return; } case CL_SENT_COMMAND: - ct->loaded = 0; - t->error = client_recv_buffer(ct); - if (t->error < 0) - return; - if (strstr(ct->buf, AWAITING_DATA_MSG)) - ct->status = CL_SENDING; - else + { + char *buf2; + /* can not use "buf" here because we need a malloced buffer */ + buf2 = para_malloc(CLIENT_BUFSIZE); + ret = client_recv_buffer(ct, &s->rfds, buf2, CLIENT_BUFSIZE, &n); + if (n > 0) { + if (strstr(buf2, AWAITING_DATA_MSG)) { + free(buf2); + ct->status = CL_SENDING; + return; + } ct->status = CL_RECEIVING; - return; - case CL_SENDING: /* FIXME: might block */ - PARA_INFO_LOG("loaded: %zd\n", *ct->in_loaded); - t->error = send_bin_buffer(ct->fd, ct->inbuf, *ct->in_loaded); - if (t->error < 0) + btr_add_output(buf2, n, btrn); + } else + free(buf2); + goto out; + } + case CL_SENDING: + { + char *buf2; + size_t sz; + ret = btr_node_status(btrn, 0, BTR_NT_LEAF); + if (ret < 0) + goto out; + if (ret == 0) return; - *ct->in_loaded = 0; + if (!FD_ISSET(ct->rc4c.fd, &s->wfds)) + return; + sz = btr_next_buffer(btrn, &buf2); + ret = rc4_send_bin_buffer(&ct->rc4c, buf2, sz); + if (ret < 0) + goto out; + btr_consume(btrn, sz); return; + } case CL_RECEIVING: - t->error = client_recv_buffer(ct); - return; + { + char *buf2; + ret = btr_node_status(btrn, 0, BTR_NT_ROOT); + if (ret < 0) + goto out; + if (ret == 0) + return; + /* + * The FD_ISSET() is not strictly necessary, but is allows us + * to skip the malloc below if there is nothing to read anyway. + */ + if (!FD_ISSET(ct->rc4c.fd, &s->rfds)) + return; + buf2 = para_malloc(CLIENT_BUFSIZE); + ret = client_recv_buffer(ct, &s->rfds, buf2, CLIENT_BUFSIZE, &n); + if (n > 0) { + buf2 = para_realloc(buf2, n); + btr_add_output(buf2, n, btrn); + } else + free(buf2); + goto out; + } + } +out: + t->error = ret; + if (ret < 0) { + if (ret != -E_SERVER_EOF && ret != -E_BTR_EOF) + PARA_ERROR_LOG("%s\n", para_strerror(-t->error)); + btr_remove_node(btrn); } } @@ -285,14 +315,14 @@ static int client_connect(struct client_task *ct) { int ret; - ct->fd = -1; - ret = makesock(AF_UNSPEC, IPPROTO_TCP, 0, ct->conf.hostname_arg, - ct->conf.server_port_arg); + ct->rc4c.fd = -1; + ret = para_connect_simple(IPPROTO_TCP, ct->conf.hostname_arg, + ct->conf.server_port_arg); if (ret < 0) return ret; - ct->fd = ret; + ct->rc4c.fd = ret; ct->status = CL_CONNECTED; - ret = mark_fd_nonblocking(ct->fd); + ret = mark_fd_nonblocking(ct->rc4c.fd); if (ret < 0) goto err_out; ct->task.pre_select = client_pre_select; @@ -301,8 +331,8 @@ static int client_connect(struct client_task *ct) register_task(&ct->task); return 1; err_out: - close(ct->fd); - ct->fd = -1; + close(ct->rc4c.fd); + ct->rc4c.fd = -1; return ret; } @@ -314,6 +344,8 @@ err_out: * \param ct_ptr Points to dynamically allocated and initialized client task * struct upon successful return. * \param loglevel If not \p NULL, the number of the loglevel is stored here. + * \param parent Add the new buffer tree node as a child of this node. + * \param child Add the new buffer tree node as a parent of this node. * * Check the command line options given by \a argc and argv, set default values * for user name and rsa key file, read further option from the config file. @@ -322,15 +354,16 @@ err_out: * \return Standard. */ int client_open(int argc, char *argv[], struct client_task **ct_ptr, - int *loglevel) + int *loglevel, struct btr_node *parent, struct btr_node *child) { char *home = para_homedir(); int ret; struct client_task *ct = para_calloc(sizeof(struct client_task)); - ct->buf = para_malloc(CLIENT_BUFSIZE); + ct->btrn = btr_new_node(&(struct btr_node_description) + EMBRACE(.name = "client", .parent = parent, .child = child)); *ct_ptr = ct; - ct->fd = -1; + ct->rc4c.fd = -1; ret = -E_CLIENT_SYNTAX; if (client_cmdline_parser(argc, argv, &ct->conf)) goto out; @@ -338,12 +371,6 @@ int client_open(int argc, char *argv[], struct client_task **ct_ptr, ret = -E_CLIENT_SYNTAX; if (!ct->conf.inputs_num) goto out; - ct->user = ct->conf.user_given? - para_strdup(ct->conf.user_arg) : para_logname(); - - ct->key_file = ct->conf.key_file_given? - para_strdup(ct->conf.key_file_arg) : - make_message("%s/.paraslash/key.%s", home, ct->user); ct->config_file = ct->conf.config_file_given? para_strdup(ct->conf.config_file_arg) : @@ -366,6 +393,13 @@ int client_open(int argc, char *argv[], struct client_task **ct_ptr, &ct->conf, ¶ms)) goto out; } + ct->user = ct->conf.user_given? + para_strdup(ct->conf.user_arg) : para_logname(); + + ct->key_file = ct->conf.key_file_given? + para_strdup(ct->conf.key_file_arg) : + make_message("%s/.paraslash/key.%s", home, ct->user); + if (loglevel) *loglevel = get_loglevel_by_name(ct->conf.loglevel_arg); PARA_INFO_LOG("loglevel: %s\n", ct->conf.loglevel_arg); @@ -378,6 +412,8 @@ out: free(home); if (ret < 0) { PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + btr_remove_node(ct->btrn); + btr_free_node(ct->btrn); client_close(ct); *ct_ptr = NULL; } diff --git a/close_on_fork.c b/close_on_fork.c index 378799c7..92517134 100644 --- a/close_on_fork.c +++ b/close_on_fork.c @@ -1,10 +1,13 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file close_on_fork.c Manage a list of fds that should be closed on fork. */ + +#include + #include "para.h" #include "list.h" #include "string.h" diff --git a/color.c b/color.c index ca5a32d3..0e2beeb0 100644 --- a/color.c +++ b/color.c @@ -47,11 +47,9 @@ static int parse_attr(const char *name, int len) * \param value Human-readable color spec. * \param dst Result pointer for the escape sequence. * - * \return -1 on errors, 1 on success. - * * Format of \a value: [fg [bg]] [attr]. */ -int color_parse(const char *value, char *dst) +void color_parse_or_die(const char *value, char *dst) { const char *ptr = value; int attr = -1; @@ -60,7 +58,7 @@ int color_parse(const char *value, char *dst) if (!strcasecmp(value, "reset")) { strcpy(dst, COLOR_RESET); - return 1; + return; } /* [fg [bg]] [attr] */ @@ -125,8 +123,8 @@ int color_parse(const char *value, char *dst) *dst++ = 'm'; } *dst = 0; - return 1; + return; bad: - PARA_ERROR_LOG("bad color value '%s'\n", value); - return -1; + PARA_EMERG_LOG("bad color value '%s'\n", value); + exit(EXIT_FAILURE); } diff --git a/color.h b/color.h index 12972b3a..167c9294 100644 --- a/color.h +++ b/color.h @@ -7,4 +7,4 @@ /** Switch back to normal colors. */ #define COLOR_RESET "\033[m" -int color_parse(const char *value, char *dst); +void color_parse_or_die(const char *value, char *dst); diff --git a/command.c b/command.c index 1810e5b1..f462016b 100644 --- a/command.c +++ b/command.c @@ -1,27 +1,32 @@ /* - * Copyright (C) 1997-2009 Andre Noll + * Copyright (C) 1997-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file command.c Client authentication and server commands. */ +#include #include #include #include #include #include +#include +#include #include "para.h" #include "error.h" +#include "crypt.h" +#include "command.h" #include "server.cmdline.h" #include "string.h" #include "afh.h" #include "afs.h" #include "server.h" -#include "vss.h" #include "list.h" #include "send.h" +#include "vss.h" #include "rc4.h" #include "net.h" #include "daemon.h" @@ -36,13 +41,12 @@ /** Commands including options must be shorter than this. */ #define MAX_COMMAND_LEN 32768 -static RC4_KEY rc4_recv_key; -static RC4_KEY rc4_send_key; -static unsigned char rc4_buf[2 * RC4_KEY_LEN]; - extern int mmd_mutex; extern struct misc_meta_data *mmd; extern struct sender senders[]; +int send_afs_status(struct rc4_context *rc4c, int parser_friendly); + +const char *status_item_list[] = {STATUS_ITEM_ARRAY}; static void dummy(__a_unused int s) { @@ -102,14 +106,15 @@ static char *vss_get_status_flags(unsigned int flags) return msg; } -static char *get_status(struct misc_meta_data *nmmd) +static char *get_status(struct misc_meta_data *nmmd, int parser_friendly) { - char *ret, mtime[30] = ""; + char mtime[30] = ""; char *status, *flags; /* vss status info */ char *ut = uptime_str(); long offset = (nmmd->offset + 500) / 1000; struct timeval current_time; struct tm mtime_tm; + struct para_buffer b = {.flags = parser_friendly? PBF_SIZE_PREFIX : 0}; /* report real status */ status = vss_status_tohuman(nmmd->vss_status_flags); @@ -119,38 +124,22 @@ static char *get_status(struct misc_meta_data *nmmd) strftime(mtime, 29, "%b %d %Y", &mtime_tm); } gettimeofday(¤t_time, NULL); - ret = make_message( - "%s: %zu\n" /* file size */ - "%s: %s\n" /* mtime */ - "%s: %s\n" /* status */ - "%s: %s\n" /* status flags */ - "%s: %li\n" /* offset */ - "%s: %s\n" /* afs mode */ - "%s: %lu.%lu\n" /* stream start */ - "%s: %lu.%lu\n" /* current server time */ - "%s", /* afs status info */ - status_item_list[SI_FILE_SIZE], nmmd->size / 1024, - status_item_list[SI_MTIME], mtime, - status_item_list[SI_STATUS], status, - status_item_list[SI_STATUS_FLAGS], flags, - - status_item_list[SI_OFFSET], offset, - status_item_list[SI_AFS_MODE], mmd->afs_mode_string, - - status_item_list[SI_STREAM_START], - (long unsigned)nmmd->stream_start.tv_sec, - (long unsigned)nmmd->stream_start.tv_usec, - status_item_list[SI_CURRENT_TIME], - (long unsigned)current_time.tv_sec, - (long unsigned)current_time.tv_usec, - - nmmd->afd.verbose_ls_output - - ); + WRITE_STATUS_ITEM(&b, SI_FILE_SIZE, "%zu\n", nmmd->size / 1024); + WRITE_STATUS_ITEM(&b, SI_MTIME, "%s\n", mtime); + WRITE_STATUS_ITEM(&b, SI_STATUS, "%s\n", status); + WRITE_STATUS_ITEM(&b, SI_STATUS_FLAGS, "%s\n", flags); + WRITE_STATUS_ITEM(&b, SI_OFFSET, "%li\n", offset); + WRITE_STATUS_ITEM(&b, SI_AFS_MODE, "%s\n", mmd->afs_mode_string); + WRITE_STATUS_ITEM(&b, SI_STREAM_START, "%lu.%lu\n", + (long unsigned)nmmd->stream_start.tv_sec, + (long unsigned)nmmd->stream_start.tv_usec); + WRITE_STATUS_ITEM(&b, SI_CURRENT_TIME, "%lu.%lu\n", + (long unsigned)current_time.tv_sec, + (long unsigned)current_time.tv_usec); free(flags); free(status); free(ut); - return ret; + return b.buf; } static int check_sender_args(int argc, char * const * argv, struct sender_command_data *scd) @@ -200,33 +189,42 @@ static int check_sender_args(int argc, char * const * argv, struct sender_comman return 1; } -int com_sender(int fd, int argc, char * const * argv) +int com_sender(struct rc4_context *rc4c, int argc, char * const * argv) { int i, ret; + char *msg = NULL; struct sender_command_data scd; if (argc < 2) { - char *msg = NULL; for (i = 0; senders[i].name; i++) { char *tmp = make_message("%s%s\n", msg? msg : "", senders[i].name); free(msg); msg = tmp; } - ret = send_buffer(fd, msg); + ret = rc4_send_buffer(rc4c, msg); free(msg); return ret; } ret = check_sender_args(argc, argv, &scd); if (ret < 0) { - char *msg; if (scd.sender_num < 0) return ret; msg = senders[scd.sender_num].help(); - ret = send_buffer(fd, msg); + ret = rc4_send_buffer(rc4c, msg); free(msg); return ret; } + + switch (scd.cmd_num) { + case SENDER_ADD: + case SENDER_DELETE: + assert(senders[scd.sender_num].resolve_target); + ret = senders[scd.sender_num].resolve_target(argv[3], &scd); + if (ret < 0) + return ret; + } + for (i = 0; i < 10; i++) { mutex_lock(mmd_mutex); if (mmd->sender_cmd_data.cmd_num >= 0) { @@ -242,11 +240,11 @@ int com_sender(int fd, int argc, char * const * argv) } /* server info */ -int com_si(int fd, int argc, __a_unused char * const * argv) +int com_si(struct rc4_context *rc4c, int argc, __a_unused char * const * argv) { int i, ret; char *ut; - char *sender_info = NULL, *sender_list = NULL; + char *sender_info = NULL; if (argc != 1) return -E_COMMAND_SYNTAX; @@ -255,17 +253,14 @@ int com_si(int fd, int argc, __a_unused char * const * argv) char *info = senders[i].info(); sender_info = para_strcat(sender_info, info); free(info); - sender_list = para_strcat(sender_list, senders[i].name); - sender_list = para_strcat(sender_list, " "); } ut = uptime_str(); - ret = send_va_buffer(fd, "up: %s\nplayed: %u\n" + ret = rc4_send_va_buffer(rc4c, "up: %s\nplayed: %u\n" "server_pid: %d\n" "afs_pid: %d\n" "connections (active/accepted/total): %u/%u/%u\n" "current loglevel: %s\n" "supported audio formats: %s\n" - "supported senders: %s\n" "%s", ut, mmd->num_played, (int)getppid(), @@ -274,54 +269,130 @@ int com_si(int fd, int argc, __a_unused char * const * argv) mmd->num_commands, mmd->num_connects, conf.loglevel_arg, - supported_audio_formats(), - sender_list, + SERVER_AUDIO_FORMATS, sender_info ); mutex_unlock(mmd_mutex); free(ut); - free(sender_list); free(sender_info); return ret; } /* version */ -int com_version(int fd, int argc, __a_unused char * const * argv) +int com_version(struct rc4_context *rc4c, int argc, __a_unused char * const * argv) { if (argc != 1) return -E_COMMAND_SYNTAX; - return send_buffer(fd, VERSION_TEXT("server") + return rc4_send_buffer(rc4c, VERSION_TEXT("server") "built: " BUILD_DATE "\n" UNAME_RS ", " CC_VERSION "\n" ); } +#define EMPTY_STATUS_ITEMS \ + ITEM(PATH) \ + ITEM(DIRECTORY) \ + ITEM(BASENAME) \ + ITEM(SCORE) \ + ITEM(ATTRIBUTES_BITMAP) \ + ITEM(ATTRIBUTES_TXT) \ + ITEM(HASH) \ + ITEM(IMAGE_ID) \ + ITEM(IMAGE_NAME) \ + ITEM(LYRICS_ID) \ + ITEM(LYRICS_NAME) \ + ITEM(BITRATE) \ + ITEM(FORMAT) \ + ITEM(FREQUENCY) \ + ITEM(CHANNELS) \ + ITEM(DURATION) \ + ITEM(SECONDS_TOTAL) \ + ITEM(NUM_PLAYED) \ + ITEM(LAST_PLAYED) \ + ITEM(TECHINFO) \ + ITEM(ARTIST) \ + ITEM(TITLE) \ + ITEM(YEAR) \ + ITEM(ALBUM) \ + ITEM(COMMENT) \ + ITEM(AMPLIFICATION) + +/** + * Write a list of audio-file related status items with empty values. + * + * This is used by vss when currently no audio file is open. + */ +static char *empty_status_items(int parser_friendly) +{ + if (parser_friendly) + return make_message( + #define ITEM(x) "0004 %02x:\n" + EMPTY_STATUS_ITEMS + #undef ITEM + #define ITEM(x) , SI_ ## x + EMPTY_STATUS_ITEMS + #undef ITEM + ); + return make_message( + #define ITEM(x) "%s:\n" + EMPTY_STATUS_ITEMS + #undef ITEM + #define ITEM(x) ,status_item_list[SI_ ## x] + EMPTY_STATUS_ITEMS + #undef ITEM + ); +} +#undef EMPTY_STATUS_ITEMS + /* stat */ -int com_stat(int fd, int argc, char * const * argv) +int com_stat(struct rc4_context *rc4c, int argc, char * const * argv) { - int ret, num = 0;/* status will be printed that many - * times. num <= 0 means: print forever - */ + int i, ret; struct misc_meta_data tmp, *nmmd = &tmp; char *s; + int32_t num = 0; + int parser_friendly = 0; para_sigaction(SIGUSR1, dummy); - if (argc > 2) + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (arg[0] != '-') + break; + if (!strcmp(arg, "--")) { + i++; + break; + } + if (!strncmp(arg, "-n=", 3)) { + ret = para_atoi32(arg + 3, &num); + if (ret < 0) + return ret; + continue; + } + if (!strcmp(arg, "-p")) { + parser_friendly = 1; + continue; + } return -E_COMMAND_SYNTAX; - if (argc > 1) { - ret = para_atoi32(argv[1], &num); - if (ret < 0) - goto out; } + if (i != argc) + return -E_COMMAND_SYNTAX; for (;;) { - mmd_dup(nmmd); - s = get_status(nmmd); - ret = send_buffer(fd, s); + s = get_status(nmmd, parser_friendly); + ret = rc4_send_buffer(rc4c, s); free(s); if (ret < 0) goto out; + if (nmmd->vss_status_flags & VSS_NEXT) { + static char *esi; + if (!esi) + esi = empty_status_items(parser_friendly); + ret = rc4_send_buffer(rc4c, esi); + if (ret < 0) + goto out; + } else + send_afs_status(rc4c, parser_friendly); ret = 1; if (num > 0 && !--num) goto out; @@ -333,14 +404,14 @@ out: return ret; } -static int send_list_of_commands(int fd, struct server_command *cmd, +static int send_list_of_commands(struct rc4_context *rc4c, struct server_command *cmd, const char *handler) { int ret, i; for (i = 1; cmd->name; cmd++, i++) { char *perms = cmd_perms_itohuman(cmd->perms); - ret = send_va_buffer(fd, "%s\t%s\t%s\t%s\n", cmd->name, + ret = rc4_send_va_buffer(rc4c, "%s\t%s\t%s\t%s\n", cmd->name, handler, perms, cmd->description); @@ -373,7 +444,7 @@ static struct server_command *get_cmd_ptr(const char *name, char **handler) } /* help */ -int com_help(int fd, int argc, char * const * argv) +int com_help(struct rc4_context *rc4c, int argc, char * const * argv) { struct server_command *cmd; char *perms, *handler; @@ -381,16 +452,16 @@ int com_help(int fd, int argc, char * const * argv) if (argc < 2) { /* no argument given, print list of commands */ - if ((ret = send_list_of_commands(fd, server_cmds, "server")) < 0) + if ((ret = send_list_of_commands(rc4c, server_cmds, "server")) < 0) return ret; - return send_list_of_commands(fd, afs_cmds, "afs"); + return send_list_of_commands(rc4c, afs_cmds, "afs"); } /* argument given for help */ cmd = get_cmd_ptr(argv[1], &handler); if (!cmd) return -E_BAD_CMD; perms = cmd_perms_itohuman(cmd->perms); - ret = send_va_buffer(fd, + ret = rc4_send_va_buffer(rc4c, "%s - %s\n\n" "handler: %s\n" "permissions: %s\n" @@ -409,7 +480,7 @@ int com_help(int fd, int argc, char * const * argv) } /* hup */ -int com_hup(__a_unused int fd, int argc, __a_unused char * const * argv) +int com_hup(__a_unused struct rc4_context *rc4c, int argc, __a_unused char * const * argv) { if (argc != 1) return -E_COMMAND_SYNTAX; @@ -418,7 +489,7 @@ int com_hup(__a_unused int fd, int argc, __a_unused char * const * argv) } /* term */ -int com_term(__a_unused int fd, int argc, __a_unused char * const * argv) +int com_term(__a_unused struct rc4_context *rc4c, int argc, __a_unused char * const * argv) { if (argc != 1) return -E_COMMAND_SYNTAX; @@ -426,7 +497,7 @@ int com_term(__a_unused int fd, int argc, __a_unused char * const * argv) return 1; } -int com_play(__a_unused int fd, int argc, __a_unused char * const * argv) +int com_play(__a_unused struct rc4_context *rc4c, int argc, __a_unused char * const * argv) { if (argc != 1) return -E_COMMAND_SYNTAX; @@ -439,7 +510,7 @@ int com_play(__a_unused int fd, int argc, __a_unused char * const * argv) } /* stop */ -int com_stop(__a_unused int fd, int argc, __a_unused char * const * argv) +int com_stop(__a_unused struct rc4_context *rc4c, int argc, __a_unused char * const * argv) { if (argc != 1) return -E_COMMAND_SYNTAX; @@ -452,7 +523,7 @@ int com_stop(__a_unused int fd, int argc, __a_unused char * const * argv) } /* pause */ -int com_pause(__a_unused int fd, int argc, __a_unused char * const * argv) +int com_pause(__a_unused struct rc4_context *rc4c, int argc, __a_unused char * const * argv) { if (argc != 1) return -E_COMMAND_SYNTAX; @@ -467,7 +538,7 @@ int com_pause(__a_unused int fd, int argc, __a_unused char * const * argv) } /* next */ -int com_next(__a_unused int fd, int argc, __a_unused char * const * argv) +int com_next(__a_unused struct rc4_context *rc4c, int argc, __a_unused char * const * argv) { if (argc != 1) return -E_COMMAND_SYNTAX; @@ -479,7 +550,7 @@ int com_next(__a_unused int fd, int argc, __a_unused char * const * argv) } /* nomore */ -int com_nomore(__a_unused int fd, int argc, __a_unused char * const * argv) +int com_nomore(__a_unused struct rc4_context *rc4c, int argc, __a_unused char * const * argv) { if (argc != 1) return -E_COMMAND_SYNTAX; @@ -491,7 +562,7 @@ int com_nomore(__a_unused int fd, int argc, __a_unused char * const * argv) } /* ff */ -int com_ff(__a_unused int fd, int argc, char * const * argv) +int com_ff(__a_unused struct rc4_context *rc4c, int argc, char * const * argv) { long promille; int ret, backwards = 0; @@ -530,7 +601,7 @@ out: } /* jmp */ -int com_jmp(__a_unused int fd, int argc, char * const * argv) +int com_jmp(__a_unused struct rc4_context *rc4c, int argc, char * const * argv) { long unsigned int i; int ret; @@ -584,32 +655,7 @@ static struct server_command *parse_cmd(const char *cmdstr) return get_cmd_ptr(buf, NULL); } -static void init_rc4_keys(void) -{ - int i; - - for (i = 0; i < 2 * RC4_KEY_LEN; i++) - rc4_buf[i] = para_random(256); - PARA_DEBUG_LOG("rc4 keys initialized (%u:%u)\n", - (unsigned char) rc4_buf[0], - (unsigned char) rc4_buf[RC4_KEY_LEN]); - RC4_set_key(&rc4_recv_key, RC4_KEY_LEN, rc4_buf); - RC4_set_key(&rc4_send_key, RC4_KEY_LEN, rc4_buf + RC4_KEY_LEN); -} - -static void rc4_recv(unsigned long len, const unsigned char *indata, - unsigned char *outdata, __a_unused void *private_data) -{ - RC4(&rc4_recv_key, len, indata, outdata); -} - -static void rc4_send(unsigned long len, const unsigned char *indata, - unsigned char *outdata, __a_unused void *private_data) -{ - RC4(&rc4_send_key, len, indata, outdata); -} - -static int read_command(int fd, char **result) +static int read_command(struct rc4_context *rc4c, char **result) { int ret; char buf[4096]; @@ -619,7 +665,7 @@ static int read_command(int fd, char **result) size_t numbytes; char *p; - ret = recv_buffer(fd, buf, sizeof(buf)); + ret = rc4_recv_buffer(rc4c, buf, sizeof(buf)); if (ret < 0) goto out; if (!ret) @@ -682,22 +728,22 @@ static void reset_signals(void) */ __noreturn void handle_connect(int fd, const char *peername) { - int ret, argc, use_rc4 = 0; + int ret, argc; char buf[4096]; - unsigned char crypt_buf[MAXLINE]; + unsigned char rand_buf[CHALLENGE_SIZE + 2 * RC4_KEY_LEN]; + unsigned char challenge_sha1[HASH_SIZE]; struct user *u; struct server_command *cmd = NULL; - long unsigned challenge_nr, chall_response; char **argv = NULL; char *p, *command = NULL; size_t numbytes; + struct rc4_context rc4c = {.fd = fd}; reset_signals(); /* we need a blocking fd here as recv() might return EAGAIN otherwise. */ ret = mark_fd_blocking(fd); if (ret < 0) goto err_out; - challenge_nr = random(); /* send Welcome message */ ret = send_va_buffer(fd, "This is para_server, version " PACKAGE_VERSION ".\n" ); @@ -707,65 +753,69 @@ __noreturn void handle_connect(int fd, const char *peername) ret = recv_buffer(fd, buf, sizeof(buf)); if (ret < 0) goto err_out; - if (ret <= 6) { - ret = -E_AUTH; + if (ret < 10) { + ret = -E_AUTH_REQUEST; goto err_out; } numbytes = ret; - ret = -E_AUTH; - if (strncmp(buf, "auth ", 5)) + ret = -E_AUTH_REQUEST; + if (strncmp(buf, AUTH_REQUEST_MSG, strlen(AUTH_REQUEST_MSG))) goto err_out; - - if (numbytes < 9 || strncmp(buf, "auth rc4 ", 9)) - p = buf + 5; /* client version < 0.2.6 */ - else { - p = buf + 9; /* client version >= 0.2.6 */ - use_rc4 = 1; - } - PARA_DEBUG_LOG("received %s request for user %s\n", - use_rc4? "rc4" : "auth", p); + p = buf + strlen(AUTH_REQUEST_MSG); + PARA_DEBUG_LOG("received auth request for user %s\n", p); ret = -E_BAD_USER; u = lookup_user(p); - if (!u) - goto err_out; - ret = para_encrypt_challenge(u->rsa, challenge_nr, crypt_buf); - if (ret <= 0) - goto err_out; - numbytes = ret; - PARA_DEBUG_LOG("sending %zu byte challenge\n", numbytes); - /* We can't use send_buffer here since buf may contain null bytes */ - ret = send_bin_buffer(fd,(char *) crypt_buf, numbytes); + if (u) { + get_random_bytes_or_die(rand_buf, sizeof(rand_buf)); + ret = para_encrypt_buffer(u->rsa, rand_buf, sizeof(rand_buf), + (unsigned char *)buf); + if (ret < 0) + goto err_out; + numbytes = ret; + } else { + /* + * We don't want to reveal our user names, so we send a + * challenge to the client even if the user does not exist, and + * fail the authentication later. + */ + numbytes = 256; + get_random_bytes_or_die((unsigned char *)buf, numbytes); + } + PARA_DEBUG_LOG("sending %u byte challenge + rc4 keys (%zu bytes)\n", + CHALLENGE_SIZE, numbytes); + ret = send_bin_buffer(fd, buf, numbytes); if (ret < 0) goto net_err; - /* recv decrypted number */ - ret = recv_buffer(fd, buf, sizeof(buf)); + /* recv challenge response */ + ret = recv_bin_buffer(fd, buf, HASH_SIZE); if (ret < 0) goto net_err; numbytes = ret; - ret = -E_AUTH; - if (!numbytes) + PARA_DEBUG_LOG("received %d bytes challenge response\n", ret); + ret = -E_BAD_USER; + if (!u) goto net_err; - if (sscanf(buf, CHALLENGE_RESPONSE_MSG "%lu", &chall_response) < 1 - || chall_response != challenge_nr) - goto err_out; - /* auth successful, send 'Proceed' message */ - PARA_INFO_LOG("good auth for %s (%lu)\n", u->name, challenge_nr); - sprintf(buf, "%s", PROCEED_MSG); - if (use_rc4) { - init_rc4_keys(); - ret = para_encrypt_buffer(u->rsa, rc4_buf, 2 * RC4_KEY_LEN, - (unsigned char *)buf + PROCEED_MSG_LEN + 1); - if (ret <= 0) - goto err_out; - numbytes = ret + strlen(PROCEED_MSG) + 1; - } else - numbytes = strlen(buf); - ret = send_bin_buffer(fd, buf, numbytes); + /* + * The correct response is the sha1 of the first CHALLENGE_SIZE bytes + * of the random data. + */ + ret = -E_BAD_AUTH; + if (numbytes != HASH_SIZE) + goto net_err; + sha1_hash((char *)rand_buf, CHALLENGE_SIZE, challenge_sha1); + if (memcmp(challenge_sha1, buf, HASH_SIZE)) + goto net_err; + /* auth successful */ + alarm(0); + PARA_INFO_LOG("good auth for %s\n", u->name); + /* init rc4 keys with the second part of the random buffer */ + RC4_set_key(&rc4c.recv_key, RC4_KEY_LEN, rand_buf + CHALLENGE_SIZE); + RC4_set_key(&rc4c.send_key, RC4_KEY_LEN, rand_buf + CHALLENGE_SIZE + + RC4_KEY_LEN); + ret = rc4_send_buffer(&rc4c, PROCEED_MSG); if (ret < 0) goto net_err; - if (use_rc4) - enable_crypt(fd, rc4_recv, rc4_send, NULL); - ret = read_command(fd, &command); + ret = read_command(&rc4c, &command); if (ret == -E_COMMAND_SYNTAX) goto err_out; if (ret < 0) @@ -779,23 +829,25 @@ __noreturn void handle_connect(int fd, const char *peername) if (ret < 0) goto err_out; /* valid command and sufficient perms */ - alarm(0); - argc = split_args(command, &argv, "\n"); + ret = create_argv(command, "\n", &argv); + if (ret < 0) + goto err_out; + argc = ret; PARA_NOTICE_LOG("calling com_%s() for %s@%s\n", cmd->name, u->name, peername); - ret = cmd->handler(fd, argc, argv); + ret = cmd->handler(&rc4c, argc, argv); + free_argv(argv); mutex_lock(mmd_mutex); mmd->num_commands++; mutex_unlock(mmd_mutex); if (ret >= 0) goto out; err_out: - send_va_buffer(fd, "%s\n", para_strerror(-ret)); + rc4_send_va_buffer(&rc4c, "%s\n", para_strerror(-ret)); net_err: PARA_NOTICE_LOG("%s\n", para_strerror(-ret)); out: free(command); - free(argv); mutex_lock(mmd_mutex); if (cmd && (cmd->perms & AFS_WRITE) && ret >= 0) mmd->events++; diff --git a/command.h b/command.h new file mode 100644 index 00000000..347dd81c --- /dev/null +++ b/command.h @@ -0,0 +1,19 @@ +/** \file command.h The structure of server and afs commands. */ + +/** + * Defines one command of para_server. + */ +struct server_command { + /** The name of the command. */ + const char *name; + /** Pointer to the function that handles the command. */ + int (*handler)(struct rc4_context *, int, char * const * const); + /** The privileges a user must have to execute this command. */ + unsigned int perms; + /** One-line description of the command. */ + const char *description; + /** Summary of the command line options. */ + const char *usage; + /** The long help text. */ + const char *help; +}; diff --git a/command_util.sh b/command_util.sh index 62ad9663..847f87e4 100755 --- a/command_util.sh +++ b/command_util.sh @@ -26,6 +26,12 @@ read_header() AT:) array_type="$value" ;; + SI:) + for i in $value; do + system_includes="$system_includes +#include <$i.h>" + done + ;; IN:) for i in $value; do includes="$includes @@ -57,7 +63,6 @@ read_one_command() usage_txt="" help_txt="" perms_txt="" - line_handler=0 template=0 template_name="" template_prototype="" @@ -84,9 +89,6 @@ read_one_command() D:) desc_txt="$value" ;; - L:) - line_handler=1 - ;; U:) usage_txt="$value" ;; @@ -198,13 +200,13 @@ dump_proto() echo '/**' echo " * $desc_txt" echo ' *' - echo ' * \param fd The file descriptor to send output to.' - if test $line_handler -eq 0; then - echo ' * \param argc The number of arguments.' - echo ' * \param argv The argument vector.' + if [[ "$system_includes" =~ openssl/rc4.h ]]; then + echo ' * \param rc4c The rc4 crypt context.' else - echo ' * \param cmdline The full command line.' + echo ' * \param fd The file descriptor to send output to.' fi + echo ' * \param argc The number of arguments.' + echo ' * \param argv The argument vector.' echo ' * ' echo " * Usage: $usage_txt" echo ' * ' @@ -227,21 +229,18 @@ dump_proto() dump_array_member() { + local TAB=' ' echo '{' echo ".name = \"$name_txt\"," - if test $line_handler -eq 0; then - echo ".handler = com_$name_txt," - else - echo ".handler = NULL," - echo ".line_handler = com_$name_txt," - fi + echo ".handler = com_$name_txt," if test -n "$perms_txt"; then echo ".perms = $perms_txt," fi echo ".description = \"$desc_txt\"," echo ".usage = \"$usage_txt\"," echo ".help = " - printf "%s\n" "$help_txt" | sed -e 's/^/\"/g' -e 's/$/\\n\"/g' + printf "%s\n" "$help_txt" | sed -e 's/^/\"/g' -e 's/$/\\n\"/g' \ + -e "s/$TAB/\\\t/g" echo '},' } @@ -273,6 +272,7 @@ template_loop() com_c_file() { echo "/** \file $output_file.c $c_file_comment */" + echo "$system_includes" echo "$includes" echo "struct $array_type $array_name[] = {" while : ; do diff --git a/compress_filter.c b/compress_filter.c index f853b633..d0816df4 100644 --- a/compress_filter.c +++ b/compress_filter.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -10,20 +10,19 @@ * Uses ideas of AudioCompress, (C) 2002-2004 M. Hari Nezumi */ +#include +#include + #include "para.h" #include "compress_filter.cmdline.h" #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "filter.h" #include "string.h" #include "error.h" -/** The size of the output data buffer. */ -#define COMPRESS_CHUNK_SIZE 40960 - -extern char *stat_item_values[NUM_STAT_ITEMS]; - /** Data specific to the compress filter. */ struct private_compress_data { /** The current multiplier. */ @@ -38,22 +37,48 @@ struct private_compress_data { int peak; }; -static ssize_t compress(char *inbuf, size_t inbuf_len, struct filter_node *fn) +static void compress_close(struct filter_node *fn) +{ + free(fn->private_data); +} + +static void compress_post_select(__a_unused struct sched *s, struct task *t) { - size_t i, length = PARA_MIN((inbuf_len / 2) * 2, - (fn->bufsize - fn->loaded) / 2 * 2); + struct filter_node *fn = container_of(t, struct filter_node, task); struct private_compress_data *pcd = fn->private_data; - int16_t *ip = (int16_t *)inbuf, *op = (int16_t *)(fn->buf + fn->loaded); + struct btr_node *btrn = fn->btrn; + bool inplace = btr_inplace_ok(btrn); + int ret; + char *inbuf; + size_t length, i; + int16_t *ip, *op; unsigned gain_shift = pcd->conf->inertia_arg + pcd->conf->damp_arg, mask = (1 << pcd->conf->blocksize_arg) - 1; - if (!length) - return 0; + //inplace = false; +next_buffer: + t->error = 0; + ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL); + if (ret < 0) + goto err; + if (ret == 0) + return; + btr_merge(btrn, fn->min_iqs); + length = btr_next_buffer(btrn, &inbuf) & ~(size_t)1; + if (length == 0) { /* eof and 1 byte available */ + ret = -E_COMPRESS_EOF; + goto err; + } + ip = (int16_t *)inbuf; + if (inplace) + op = ip; + else + op = para_malloc(length); for (i = 0; i < length / 2; i++) { /* be careful in that heat, my dear */ int sample = *ip++, adjusted_sample = (PARA_ABS(sample) * pcd->current_gain) >> gain_shift; - if (unlikely(adjusted_sample > 32767)) { /* clip */ + if (adjusted_sample > 32767) { /* clip */ PARA_NOTICE_LOG("clip: sample: %d, adjusted sample: %d\n", sample, adjusted_sample); adjusted_sample = 32767; @@ -62,8 +87,8 @@ static ssize_t compress(char *inbuf, size_t inbuf_len, struct filter_node *fn) pcd->peak = 0; } else pcd->peak = PARA_MAX(pcd->peak, adjusted_sample); - *op++ = sample >= 0? adjusted_sample : -adjusted_sample; - if (likely(++pcd->num_samples & mask)) + op[i] = sample >= 0? adjusted_sample : -adjusted_sample; + if (++pcd->num_samples & mask) continue; // PARA_DEBUG_LOG("gain: %u, peak: %u\n", pcd->current_gain, // pcd->peak); @@ -75,14 +100,17 @@ static ssize_t compress(char *inbuf, size_t inbuf_len, struct filter_node *fn) 1U << pcd->conf->inertia_arg); pcd->peak = 0; } - fn->loaded += length; - return length; -} - -static void close_compress(struct filter_node *fn) -{ - free(fn->private_data); - free(fn->buf); + if (inplace) + btr_pushdown_one(btrn); + else { + btr_consume(btrn, length); + btr_add_output((char *)op, length, btrn); + } + goto next_buffer; +err: + assert(ret < 0); + t->error = ret; + btr_remove_node(btrn); } /** TODO: Add sanity checks */ @@ -102,18 +130,22 @@ err: return ret; } -static void open_compress(struct filter_node *fn) +static void compress_open(struct filter_node *fn) { struct private_compress_data *pcd = para_calloc( sizeof(struct private_compress_data)); pcd->conf = fn->conf; fn->private_data = pcd; - fn->bufsize = COMPRESS_CHUNK_SIZE; - fn->buf = para_malloc(fn->bufsize); + fn->min_iqs = 2; /* 16 bit audio */ pcd->current_gain = 1 << pcd->conf->inertia_arg; pcd->max_gain = 1 << (pcd->conf->inertia_arg + pcd->conf->aggressiveness_arg); } +static void compress_free_config(void *conf) +{ + compress_cmdline_parser_free(conf); +} + /** * The init function of the compress filter. * @@ -124,10 +156,12 @@ void compress_filter_init(struct filter *f) struct compress_filter_args_info dummy; compress_cmdline_parser_init(&dummy); - f->open = open_compress; - f->close = close_compress; - f->convert = compress; + f->open = compress_open; + f->close = compress_close; + f->pre_select = generic_filter_pre_select; + f->post_select = compress_post_select; f->parse_config = compress_parse_config; + f->free_config = compress_free_config; f->help = (struct ggo_help) { .short_help = compress_filter_args_info_help, .detailed_help = compress_filter_args_info_detailed_help diff --git a/configure.ac b/configure.ac index f7b70c1c..efde761f 100644 --- a/configure.ac +++ b/configure.ac @@ -78,82 +78,200 @@ AC_CHECK_FUNCS([atexit dup2 memchr memmove memset \ strncasecmp strrchr strspn alarm mkdir rmdir], [], [AC_MSG_ERROR([function not found, cannot live without it])]) +cmdline_dir="cmdline" +AC_SUBST(cmdline_dir) +AC_DEFUN([add_cmdline],[$(for i in $@; do printf "${i}.cmdline "; done)]) + + all_errlist_objs="server mp3_afh afh_common vss command net string signal time daemon stat crypt http_send close_on_fork ipc acl afh fade amp_filter -dccp_send fd user_list chunk_queue afs osl aft mood score attribute blob ringbuffer -playlist sha1 rbtree sched audiod grab_client filter_common wav_filter compress_filter +dccp_send fd user_list chunk_queue afs aft mood score attribute blob ringbuffer +playlist sha1 sched audiod grab_client filter_common wav_filter compress_filter http_recv dccp_recv recv_common write_common file_write audiod_command -client_common recv stdout filter stdin audioc write client fsck exec send_common ggo -udp_recv udp_send color fec fecdec_filter prebuffer_filter" +client_common recv stdout filter stdin audioc write client exec send_common ggo +udp_recv udp_send color fec fecdec_filter prebuffer_filter mm +server_command_list afs_command_list audiod_command_list bitstream imdct wma_afh +wma_common wmadec_filter buffer_tree +" + +executables="recv filter audioc write client afh audiod" -all_executables="server recv filter audioc write client fsck afh" +recv_cmdline_objs="add_cmdline(recv http_recv dccp_recv udp_recv)" -recv_cmdline_objs="recv.cmdline http_recv.cmdline dccp_recv.cmdline udp_recv.cmdline" recv_errlist_objs="http_recv recv_common recv time string net dccp_recv - fd sched stdout ggo udp_recv fec" + fd sched stdout ggo udp_recv fec buffer_tree" recv_ldflags="" -receivers=" http dccp udp" -senders=" http dccp udp" - -filter_cmdline_objs="filter.cmdline compress_filter.cmdline amp_filter.cmdline - prebuffer_filter.cmdline" +filter_cmdline_objs="add_cmdline(filter compress_filter amp_filter prebuffer_filter)" filter_errlist_objs="filter_common wav_filter compress_filter filter string stdin stdout sched fd amp_filter ggo fecdec_filter fec - prebuffer_filter time" -filter_ldflags="" -filters=" compress wav amp fecdec prebuffer" + prebuffer_filter time bitstream imdct wma_common wmadec_filter buffer_tree" +filter_ldflags="-lm" +filters=" compress wav amp fecdec wmadec prebuffer" -audioc_cmdline_objs="audioc.cmdline" +audioc_cmdline_objs="add_cmdline(audioc)" audioc_errlist_objs="audioc string net fd" audioc_ldflags="" -audiod_cmdline_objs="audiod.cmdline grab_client.cmdline compress_filter.cmdline - http_recv.cmdline dccp_recv.cmdline file_write.cmdline client.cmdline - audiod_command_list amp_filter.cmdline udp_recv.cmdline - prebuffer_filter.cmdline" +audiod_cmdline_objs="add_cmdline(audiod compress_filter http_recv dccp_recv file_write client amp_filter udp_recv prebuffer_filter)" audiod_errlist_objs="audiod signal string daemon stat net time grab_client filter_common wav_filter compress_filter amp_filter http_recv dccp_recv recv_common fd sched write_common file_write audiod_command crypt fecdec_filter - client_common ggo udp_recv color fec prebuffer_filter" -audiod_ldflags="" -audiod_audio_formats="" + client_common ggo udp_recv color fec prebuffer_filter sha1 audiod_command_list + bitstream imdct wma_common wmadec_filter buffer_tree" +audiod_ldflags="-lm" +audiod_audio_formats="wma" -afh_cmdline_objs="afh.cmdline" -afh_errlist_objs="afh string fd mp3_afh afh_common time" +afh_cmdline_objs="add_cmdline(afh)" +afh_errlist_objs="afh string fd mp3_afh afh_common time wma_afh wma_common" afh_ldflags="" -server_cmdline_objs="server.cmdline server_command_list afs_command_list" +server_cmdline_objs="add_cmdline(server)" server_errlist_objs="server afh_common mp3_afh vss command net string signal - time daemon stat crypt http_send close_on_fork - ipc dccp_send fd user_list chunk_queue afs osl aft mood score attribute - blob playlist sha1 rbtree sched acl send_common udp_send color fec" -server_ldflags="" -server_audio_formats=" mp3" - -write_cmdline_objs="write.cmdline file_write.cmdline" -write_errlist_objs="write write_common file_write time fd string sched stdin ggo" + time daemon crypt http_send close_on_fork mm + ipc dccp_send fd user_list chunk_queue afs aft mood score attribute + blob playlist sha1 sched acl send_common udp_send color fec + server_command_list afs_command_list wma_afh wma_common" +server_ldflags="-losl" +server_audio_formats="mp3 wma" + +write_cmdline_objs="add_cmdline(write file_write)" +write_errlist_objs="write write_common file_write time fd string sched stdin + buffer_tree ggo" write_ldflags="" writers=" file" default_writer="FILE_WRITE" -client_cmdline_objs="client.cmdline" -client_errlist_objs="client net string crypt fd sched stdin stdout client_common" +client_cmdline_objs="add_cmdline(client)" +client_errlist_objs="client net string crypt fd sched stdin stdout time + client_common sha1 buffer_tree" client_ldflags="" -fsck_cmdline_objs="fsck.cmdline" -fsck_errlist_objs="osl rbtree fsck string sha1 fd" - -gui_cmdline_objs="gui.cmdline" +gui_cmdline_objs="add_cmdline(gui)" gui_errlist_objs="exec signal string stat ringbuffer fd" gui_other_objs="gui gui_theme" gui_objs="$gui_cmdline_objs $gui_errlist_objs $gui_other_objs" -fade_cmdline_objs="fade.cmdline" +fade_cmdline_objs="add_cmdline(fade)" fade_errlist_objs="fade exec string fd" +########################################################################### snprintf +# =========================================================================== +# http://www.nongnu.org/autoconf-archive/ax_func_snprintf.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_FUNC_SNPRINTF +# +# DESCRIPTION +# +# Checks for a fully C99 compliant snprintf, in particular checks whether +# it does bounds checking and returns the correct string length; does the +# same check for vsnprintf. If no working snprintf or vsnprintf is found, +# it prints an error message and aborts. +# +# LICENSE +# +# Copyright (c) 2008 Ruediger Kuhlmann +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. + +AU_ALIAS([AC_FUNC_SNPRINTF], [AX_FUNC_SNPRINTF]) +AC_DEFUN([AX_FUNC_SNPRINTF], +[AC_CHECK_FUNCS(snprintf vsnprintf) +AC_MSG_CHECKING(for working snprintf) +AC_CACHE_VAL(ac_cv_have_working_snprintf, +[AC_TRY_RUN( +[#include + +int main(void) +{ + char bufs[5] = { 'x', 'x', 'x', '\0', '\0' }; + char bufd[5] = { 'x', 'x', 'x', '\0', '\0' }; + int i; + i = snprintf (bufs, 2, "%s", "111"); + if (strcmp (bufs, "1")) exit (1); + if (i != 3) exit (1); + i = snprintf (bufd, 2, "%d", 111); + if (strcmp (bufd, "1")) exit (1); + if (i != 3) exit (1); + exit(0); +}], ac_cv_have_working_snprintf=yes, ac_cv_have_working_snprintf=no, ac_cv_have_working_snprintf=cross)]) +AC_MSG_RESULT([$ac_cv_have_working_snprintf]) +AC_MSG_CHECKING(for working vsnprintf) +AC_CACHE_VAL(ac_cv_have_working_vsnprintf, +[AC_TRY_RUN( +[#include +#include + +int my_vsnprintf (char *buf, const char *tmpl, ...) +{ + int i; + va_list args; + va_start (args, tmpl); + i = vsnprintf (buf, 2, tmpl, args); + va_end (args); + return i; +} + +int main(void) +{ + char bufs[5] = { 'x', 'x', 'x', '\0', '\0' }; + char bufd[5] = { 'x', 'x', 'x', '\0', '\0' }; + int i; + i = my_vsnprintf (bufs, "%s", "111"); + if (strcmp (bufs, "1")) exit (1); + if (i != 3) exit (1); + i = my_vsnprintf (bufd, "%d", 111); + if (strcmp (bufd, "1")) exit (1); + if (i != 3) exit (1); + exit(0); +}], ac_cv_have_working_vsnprintf=yes, ac_cv_have_working_vsnprintf=no, ac_cv_have_working_vsnprintf=cross)]) +AC_MSG_RESULT([$ac_cv_have_working_vsnprintf]) +if test x$ac_cv_have_working_snprintf$ac_cv_have_working_vsnprintf != "xyesyes"; then +AC_MSG_ERROR([fatal: buggy snprintf() detected]) +fi]) +AX_FUNC_SNPRINTF() +########################################################################### osl +have_osl=yes +OLD_CPPFLAGS="$CPPFLAGS" +OLD_LD_FLAGS="$LDFLAGS" +OLD_LIBS="$LIBS" +AC_ARG_WITH(osl_headers, [AC_HELP_STRING(--with-osl-headers=dir, + [look for osl.h also in dir])]) +if test -n "$with_osl_headers"; then + osl_cppflags="-I$with_osl_headers" + CPPFLAGS="$CPPFLAGS $osl_cppflags" +fi +AC_ARG_WITH(osl_libs, [AC_HELP_STRING(--with-osl-libs=dir, + [look for libosl also in dir])]) +if test -n "$with_osl_libs"; then + osl_libs="-L$with_osl_libs" + LDFLAGS="$LDFLAGS $osl_libs" +fi +AC_CHECK_HEADER(osl.h, [], have_osl=no) +AC_CHECK_LIB([osl], [osl_open_table], [], have_osl=no) +if test "$have_osl" = "no"; then + AC_MSG_WARN([libosl not found, can not build para_server. +Download libosl at + http://systemlinux.org/~maan/osl +or execute + git clone git://git.tuebingen.mpg.de/osl + ]) +else + extras="$extras server" + executables="$executables server" + AC_SUBST(osl_cppflags) + server_ldflags="$server_ldflags -L$with_osl_libs" +fi +CPPFLAGS="$OLD_CPPFLAGS" +LDFLAGS="$OLD_LDFLAGS" +LIBS="$OLD_LIBS" ########################################################################### ssl dnl @synopsis CHECK_SSL dnl @@ -184,19 +302,15 @@ AC_DEFUN([CHECK_SSL], SSL_LDFLAGS="-L$ssldir/lib"; fi AC_SUBST(SSL_CPPFLAGS) - AC_SUBST(SSL_CFLAGS) - AC_SUBST(SSL_LIBS) - AC_SUBST(SSL_LDFLAGS) ])dnl AC_ARG_ENABLE(ssldir, [AS_HELP_STRING(--enable-ssldir=path, [Search for openssl also in path.])]) if test "$enable_ssldir" = "yes"; then enable_ssldir=""; fi CHECK_SSL($enable_ssldir) -server_ldflags="$srver_ldflags $SSL_LDFLAGS $SSL_LIBS" +server_ldflags="$server_ldflags $SSL_LDFLAGS $SSL_LIBS" client_ldflags="$client_ldflags $SSL_LDFLAGS $SSL_LIBS" audiod_ldflags="$audiod_ldflags $SSL_LDFLAGS $SSL_LIBS" -fsck_ldflags="$fsck_ldflags $SSL_LDFLAGS $SSL_LIBS" ########################################################################### libsocket AC_CHECK_LIB([c], [socket], @@ -262,10 +376,9 @@ AC_CHECK_LIB([ncurses], [initscr], [], [ ]) if test "$have_ncurses" = "yes"; then AC_SUBST(ncurses_cppflags) - AC_SUBST(ncurses_libs) AC_DEFINE(HAVE_NCURSES, 1, [define to 1 to turn on ncurses support]) - extras="$extras para_gui" - all_executables="$all_executables gui" + extras="$extras gui" + executables="$executables gui" else AC_MSG_WARN([cannot build para_gui]) fi @@ -314,53 +427,88 @@ if test ${have_core_audio} = yes; then default_writer="OSX_WRITE" AC_DEFINE(HAVE_CORE_AUDIO, 1, define to 1 on Mac Os X) fi -########################################################################### ogg +########################################################### ogg/vorbis/speex have_ogg="yes" +have_vorbis="yes" +have_speex="yes" OLD_CPPFLAGS="$CPPFLAGS" OLD_LD_FLAGS="$LDFLAGS" OLD_LIBS="$LIBS" AC_ARG_WITH(oggvorbis_headers, [AC_HELP_STRING(--with-oggvorbis-headers=dir, - [look for vorbis/codec.h also in dir])]) + [look for ogg/vorbis/speex headers also in dir])]) if test -n "$with_oggvorbis_headers"; then - oggvorbis_cppflags="-I$with_oggvorbis_headers" - CPPFLAGS="$CPPFLAGS $oggvorbis_cppflags" + ogg_cppflags="-I$with_oggvorbis_headers" + CPPFLAGS="$CPPFLAGS $ogg_cppflags" fi AC_ARG_WITH(oggvorbis_libs, [AC_HELP_STRING(--with-oggvorbis-libs=dir, - [look for oggvorbis libs also in dir])]) + [look for ogg/vorbis/speex libs also in dir])]) if test -n "$with_oggvorbis_libs"; then - oggvorbis_libs="-L$with_oggvorbis_libs" - LDFLAGS="$LDFLAGS $oggvorbis_libs" + ogg_libs="-L$with_oggvorbis_libs" + LDFLAGS="$LDFLAGS $ogg_libs" fi AC_CHECK_LIB([ogg], [ogg_stream_init], [], [ have_ogg="no" ]) -AC_CHECK_LIB([vorbis], [vorbis_info_init], [], [ have_ogg="no" ]) -AC_CHECK_HEADERS([ogg/ogg.h vorbis/codec.h], [], [ have_ogg="no" ]) -if test "$have_ogg" = "yes"; then - all_errlist_objs="$all_errlist_objs oggdec_filter ogg_afh" - AC_DEFINE(HAVE_OGGVORBIS, 1, define to 1 to turn on ogg vorbis support) - filters="$filters oggdec" +AC_CHECK_LIB([vorbis], [vorbis_info_init], [], [ have_vorbis="no" ]) +AC_CHECK_LIB([speex], [speex_decoder_init], [], [ have_speex="no" ]) +AC_CHECK_HEADERS([ogg/ogg.h], [], [ have_ogg="no"; ]) +AC_CHECK_HEADERS([vorbis/codec.h], [], [ have_vorbis="no" ]) +AC_CHECK_HEADERS([speex/speex.h], [], [ have_speex="no" ]) +msg="support in para_server/para_filter/para_afh" +if test "$have_ogg" = "yes" && { test "$have_vorbis" = "yes" || test "$have_speex" = "yes"; }; then + AC_SUBST(ogg_cppflags) + ogg_libs="$ogg_libs -logg" if test "$OSTYPE" = "Darwin"; then - oggvorbis_libs="-Wl,-bind_at_load $oggvorbis_libs" + ogg_libs="-Wl,-bind_at_load $ogg_libs $ogg_libs" + fi + server_ldflags="$server_ldflags $ogg_libs" + filter_ldflags="$filter_ldflags $ogg_libs" + audiod_ldflags="$audiod_ldflags $ogg_libs" + all_errlist_objs="$all_errlist_objs ogg_afh_common" + afh_ldflags="$afh_ldflags $ogg_libs" + afh_errlist_objs="$afh_errlist_objs ogg_afh_common" + server_errlist_objs="$server_errlist_objs ogg_afh_common" + if test "$have_vorbis" = "yes"; then + all_errlist_objs="$all_errlist_objs oggdec_filter ogg_afh" + AC_DEFINE(HAVE_OGGVORBIS, 1, define to 1 to turn on ogg/vorbis support) + filters="$filters oggdec" + vorbis_libs="-lvorbis -lvorbisfile" + server_ldflags="$server_ldflags $vorbis_libs" + filter_ldflags="$filter_ldflags $vorbis_libs" + audiod_ldflags="$audiod_ldflags $vorbis_libs" + afh_ldflags="$afh_ldflags $vorbis_libs" + + server_errlist_objs="$server_errlist_objs ogg_afh" + filter_errlist_objs="$filter_errlist_objs oggdec_filter" + audiod_errlist_objs="$audiod_errlist_objs oggdec_filter" + afh_errlist_objs="$afh_errlist_objs ogg_afh" + + audiod_audio_formats="$audiod_audio_formats ogg" + server_audio_formats="$server_audio_formats ogg" + else + AC_MSG_WARN([no ogg/vorbis $msg]) + fi + if test "$have_speex" = "yes"; then + all_errlist_objs="$all_errlist_objs spxdec_filter spx_afh spx_common" + AC_DEFINE(HAVE_SPEEX, 1, define to 1 to turn on ogg/speex support) + filters="$filters spxdec" + speex_libs="-lspeex" + server_ldflags="$server_ldflags $speex_libs" + filter_ldflags="$filter_ldflags $speex_libs" + audiod_ldflags="$audiod_ldflags $speex_libs" + afh_ldflags="$afh_ldflags $speex_libs" + + server_errlist_objs="$server_errlist_objs spx_afh spx_common" + filter_errlist_objs="$filter_errlist_objs spxdec_filter spx_common" + audiod_errlist_objs="$audiod_errlist_objs spxdec_filter spx_common" + afh_errlist_objs="$afh_errlist_objs spx_afh spx_common" + + audiod_audio_formats="$audiod_audio_formats spx" + server_audio_formats="$server_audio_formats spx" + else + AC_MSG_WARN([no ogg/speex $msg]) fi - server_ldflags="$server_ldflags $oggvorbis_libs -logg -lvorbis -lvorbisfile" - filter_ldflags="$filter_ldflags $oggvorbis_libs -lvorbis -lvorbisfile" - audiod_ldflags="$audiod_ldflags $oggvorbis_libs -lvorbis -lvorbisfile" - afh_ldflags="$afh_ldflags $oggvorbis_libs -logg -lvorbis -lvorbisfile" - - filter_cmdline_objs="$filter_cmdline_objs oggdec_filter.cmdline" - audiod_cmdline_objs="$audiod_cmdline_objs oggdec_filter.cmdline" - - server_errlist_objs="$server_errlist_objs ogg_afh" - filter_errlist_objs="$filter_errlist_objs oggdec_filter" - audiod_errlist_objs="$audiod_errlist_objs oggdec_filter" - afh_errlist_objs="$afh_errlist_objs ogg_afh" - - audiod_audio_formats="ogg" - server_audio_formats="$server_audio_formats ogg" - AC_SUBST(oggvorbis_cppflags) - AC_SUBST(oggvorbis_libs) else - AC_MSG_WARN([no ogg vorbis support in para_server/para_filter]) + AC_MSG_WARN([no ogg/vorbis ogg/speex $msg]) fi CPPFLAGS="$OLD_CPPFLAGS" LDFLAGS="$OLD_LDFLAGS" @@ -399,7 +547,6 @@ if test "$have_faad" = "yes"; then server_audio_formats="$server_audio_formats aac" filters="$filters aacdec" AC_SUBST(faad_cppflags) - AC_SUBST(faad_libs) else AC_MSG_WARN([no aac support in para_audiod/para_filter]) fi @@ -432,8 +579,8 @@ AC_CHECK_LIB([mad], [mad_stream_init], [], [ ]) if test "$have_mad" = "yes"; then AC_DEFINE(HAVE_MAD, 1, define to 1 if you want to build the mp3dec filter) - filter_cmdline_objs="$filter_cmdline_objs mp3dec_filter.cmdline" - audiod_cmdline_objs="$audiod_cmdline_objs mp3dec_filter.cmdline" + filter_cmdline_objs="$filter_cmdline_objs add_cmdline(mp3dec_filter)" + audiod_cmdline_objs="$audiod_cmdline_objs add_cmdline(mp3dec_filter)" all_errlist_objs="$all_errlist_objs mp3dec_filter" filter_errlist_objs="$filter_errlist_objs mp3dec_filter" audiod_errlist_objs="$audiod_errlist_objs mp3dec_filter" @@ -442,16 +589,9 @@ if test "$have_mad" = "yes"; then audiod_audio_formats="$audiod_audio_formats mp3" filters="$filters mp3dec" AC_SUBST(mad_cppflags) - AC_SUBST(mad_libs) else AC_MSG_WARN([no mp3dec support in para_audiod/para_filter]) fi -if test -n "$audiod_audio_formats"; then - extras="$extras para_audiod" - all_executables="$all_executables audiod" -else - AC_MSG_WARN([can not build para_audiod (no supported audio formats)]) -fi CPPFLAGS="$OLD_CPPFLAGS" LDFLAGS="$OLD_LDFLAGS" LIBS="$OLD_LIBS" @@ -479,14 +619,14 @@ have_oss="yes" msg="=> will not build para_fade/oss writer" AC_CHECK_HEADER(sys/soundcard.h, [ - extras="$extras para_fade" - all_executables="$all_executables fade" + extras="$extras fade" + executables="$executables fade" all_errlist_objs="$all_errlist_objs oss_write" audiod_errlist_objs="$audiod_errlist_objs oss_write" - audiod_cmdline_objs="$audiod_cmdline_objs oss_write.cmdline" + audiod_cmdline_objs="$audiod_cmdline_objs add_cmdline(oss_write)" write_errlist_objs="$write_errlist_objs oss_write" - write_cmdline_objs="$write_cmdline_objs oss_write.cmdline" + write_cmdline_objs="$write_cmdline_objs add_cmdline(oss_write)" writers="$writers oss" default_writer="OSS_WRITE" AC_CHECK_LIB(ossaudio, _oss_ioctl, [ @@ -533,11 +673,11 @@ fi if test "$have_alsa" = "yes"; then all_errlist_objs="$all_errlist_objs alsa_write" audiod_errlist_objs="$audiod_errlist_objs alsa_write" - audiod_cmdline_objs="$audiod_cmdline_objs alsa_write.cmdline" + audiod_cmdline_objs="$audiod_cmdline_objs add_cmdline(alsa_write)" audiod_ldflags="$audiod_ldflags -lasound" write_errlist_objs="$write_errlist_objs alsa_write" - write_cmdline_objs="$write_cmdline_objs alsa_write.cmdline" + write_cmdline_objs="$write_cmdline_objs add_cmdline(alsa_write)" write_ldflags="$write_ldflags -lasound" writers="$writers alsa" default_writer="ALSA_WRITE" @@ -547,64 +687,22 @@ CPPFLAGS="$OLD_CPPFLAGS" LDFLAGS="$OLD_LDFLAGS" LIBS="$OLD_LIBS" -AC_SUBST(extra_binaries, [$extras]) -AC_SUBST(extra_filter_objs, [$extra_filter_objs]) -AC_SUBST(extra_filter_libs, [$extra_filter_libs]) AC_SUBST(install_sh, [$INSTALL]) AC_CONFIG_FILES([Makefile]) AC_DEFUN([add_dot_o],[$(for i in $@; do printf "$i.o "; done)]) +AC_DEFUN([add_para],[$(for i in $@; do printf "para_$i "; done)]) AC_DEFUN([objlist_to_errlist],[$(for i in $@; do printf "DEFINE_ERRLIST($(echo $i| tr 'a-z' 'A-Z'));"; done) [const char **para_errlist[[]]] = {$(for i in $@; do printf "PARA_ERRLIST($(echo $i | tr 'a-z' 'A-Z')), "; done) }]) - ############################################################# error2.h -AC_DEFUN([define_safe_error_enums], -[ - exe="" - for i in $all_executables; do -# eval echo checking if $1 is linked into $i - for j in $(eval echo \$${i}_errlist_objs); do - if test $j = $1; then - exe="$exe $i" - break; - fi - done - done - #echo "$1 gets linked into $exe" - safe_errlists="" - for i in $all_errlist_objs; do - for j in $exe; do - found=0 - for k in $(eval echo \$${j}_errlist_objs); do - if test $k = $i; then - found=1 - break; - fi - done - if test $found -eq 0; then - break; - fi - done - if test $found -eq 1; then - safe_errlists="$safe_errlists $i" - fi - done - #echo "safe errlists for $1: $safe_errlists" - ss_defs="" - for i in $safe_errlists; do - echo "SS_ENUM($(echo $i | tr 'a-z' 'A-Z'));" - done -] -) - - AC_MSG_NOTICE(creating error2.h) +for i in $executables; do + echo "$i: " + eval echo \$${i}_errlist_objs +done | ./error2.pl > error2.h for obj in $all_errlist_objs; do SS="$SS SS_$(echo $obj | tr 'a-z' 'A-Z')," - echo "#ifdef MAIN_INPUT_FILE_IS_$obj" - define_safe_error_enums($obj) - echo "#endif" -done > error2.h +done AC_DEFINE_UNQUOTED(DEFINE_ERRLIST_OBJECT_ENUM, [enum {$SS NUM_SS}], [list of all objects that use paraslash's error facility] @@ -613,11 +711,12 @@ AC_DEFINE_UNQUOTED(DEFINE_ERRLIST_OBJECT_ENUM, ################################################################## status items status_items="basename status num_played mtime bitrate frequency file_size -status_flags format score audio_file_info taginfo1 taginfo2 afs_mode +status_flags format score techinfo afs_mode attributes_txt decoder_flags audiod_status play_time attributes_bitmap offset seconds_total stream_start current_time audiod_uptime image_id lyrics_id duration directory lyrics_name image_name path hash channels -last_played num_chunks chunk_time amplification" +last_played num_chunks chunk_time amplification artist title year album +comment" result= for i in $status_items; do @@ -633,6 +732,10 @@ done AC_DEFINE_UNQUOTED(STATUS_ITEM_ARRAY, [$result], [char * array of all status items]) +AC_DEFINE_UNQUOTED(SERVER_AUDIO_FORMATS, "$server_audio_formats", + [formats supported by para_server and para_afh]) + +AC_SUBST(executables, add_para($executables)) recv_objs="$recv_cmdline_objs $recv_errlist_objs" filter_objs="$filter_cmdline_objs $filter_errlist_objs" @@ -640,7 +743,6 @@ audiod_objs="$audiod_cmdline_objs $audiod_errlist_objs" server_objs="$server_cmdline_objs $server_errlist_objs" write_objs="$write_cmdline_objs $write_errlist_objs" client_objs="$client_cmdline_objs $client_errlist_objs" -fsck_objs="$fsck_cmdline_objs $fsck_errlist_objs" audioc_objs="$audioc_cmdline_objs $audioc_errlist_objs" afh_objs="$afh_cmdline_objs $afh_errlist_objs" fade_objs="$fade_cmdline_objs $fade_errlist_objs" @@ -680,11 +782,6 @@ AC_SUBST(client_ldflags, $client_ldflags) AC_DEFINE_UNQUOTED(INIT_CLIENT_ERRLISTS, objlist_to_errlist($client_errlist_objs), errors used by para_client) -AC_SUBST(fsck_objs, add_dot_o($fsck_objs)) -AC_SUBST(fsck_ldflags, $fsck_ldflags) -AC_DEFINE_UNQUOTED(INIT_FSCK_ERRLISTS, - objlist_to_errlist($fsck_errlist_objs), errors used by para_fsck) - AC_SUBST(audioc_objs, add_dot_o($audioc_objs)) AC_SUBST(audioc_ldflags, $audioc_ldflags) AC_DEFINE_UNQUOTED(INIT_AUDIOC_ERRLISTS, @@ -726,16 +823,12 @@ names="$(for i in $audiod_audio_formats; do printf \"$i\",' ' ; done)" AC_DEFINE_UNQUOTED(AUDIOD_AUDIO_FORMAT_ARRAY, $names, array of audio formats supported by audiod) AC_OUTPUT -AC_MSG_NOTICE([creating Makefile.deps]) -gcc -MM -MG $faad_cppflags $mad_cppflags $oggvorbis_cppflags *.c > Makefile.deps AC_MSG_NOTICE([ paraslash configuration: ~~~~~~~~~~~~~~~~~~~~~~~~ unix socket credentials: $have_ucred audio formats supported by para_server/para_afh: $server_audio_formats id3 version2 support: $have_libid3tag -senders supported by para_server: $senders -receivers supported by para_audiod/para_recv: $receivers filters supported by para_audiod/para_filter: $filters writers supported by para_audiod/para_write: $writers optional executables: $extras diff --git a/convert_0.2-0.3.sh b/convert_0.2-0.3.sh deleted file mode 100755 index 24c937b9..00000000 --- a/convert_0.2-0.3.sh +++ /dev/null @@ -1,186 +0,0 @@ -#!/usr/bin/env bash - -#------------------------------------------------------------------------------- -## Script to convert the database of paraslash 0.2.x to version 0.3.x. -## -## Assumptions: -## - para_server 0.2.x is running and the mysql selector is active -## - para_server 0.3.x is running on another port -## - The database of paraslash 0.3.x has been initialized (i.e. -## para_client init has successfully been executed) -## - All audio files in the mysql database of paraslash 0.2.x. have -## already been added to the 0.3.x database (execute para_client add -## /my/audio/file/dir to do that) -## -## The script converts the attribute table, the set attributes for each audio -## file, the image table and all image ids, and finally the lastplayed and the -## numplayed data. -## -## However, it does not convert the paraslash stream definitions from 0.2.x to -## the moods of 0.3.x. You'll have to do this by hand. -#------------------------------------------------------------------------------- - -# Call this script without arguments to see usage info - -# How to connect to para_server 0.2.x. -client02=/usr/local/bin/para_client -port02=2991 -host02=localhost - -# How to connect to para_server 0.3.x. -client03=./para_client -port03=2990 -host03=localhost - -# Unset this to deactivate messages -debug=1 - - -client02_cmd="$client02 -p $port02 -i $host02" -client03_cmd="$client03 -p $port03 -i $host03" - -info_log() -{ - if test $debug -eq 1; then - echo "$@" - fi -} - -exec_client02_cmd() -{ - info_log "$client02_cmd -- $@" - result="$($client02_cmd -- "$@")" -} - -exec_client03_cmd() -{ - info_log "$client03_cmd -- $@" - result="$($client03_cmd -- "$@")" -} - -convert_attribute_table() -{ - local atts - exec_client02_cmd laa - atts="$result" - info_log "creating attributes: $atts" - exec_client03_cmd addatt $atts -} - -convert_attributes() -{ - local att atts current_atts cmd query="select dir.dir, dir.name" - exec_client02_cmd laa - atts="$result" - for att in $atts; do - query="$query, data.$att" - done - query="$query from dir,data where dir.name=data.name" - exec_client02_cmd verb "$query" - echo "$result" | while read dir name current_atts; do - cmd="setatt " - for att in $atts; do - if test "${current_atts#0}" = "$current_atts"; then - cmd="$cmd $att+" - current_atts=${current_atts#1 } - else - current_atts=${current_atts#0 } - fi - done - if test "$cmd" = "setatt "; then - continue - fi - exec_client03_cmd $cmd "$dir/$name" - done -} - -convert_lastplayed_numplayed() -{ - local query="select dir.dir, dir.name, unix_timestamp(data.lastplayed), data.numplayed from dir,data where data.name=dir.name" - local lp np data dir name - exec_client02_cmd verb "$query" - data="$result" - echo "$result" | while read dir name lp np; do - cmd="touch -n$np -l$lp $dir/$name" - exec_client03_cmd $cmd - done -} - -convert_image_table() -{ - local num size name - exec_client02_cmd piclist; - echo "$result" | while read num size name; do - info_log "converting $name" - $client02_cmd -- pic "#$num" | $client03_cmd -- addimg "$name" - done -} - -convert_image_ids() -{ - local query="select dir.dir, dir.name, pics.name from dir,data,pics where data.name=dir.name and pics.id=data.pic_id" - local img_ids_03 dir name img id - exec_client03_cmd lsimg -l - img_ids_03="$result" - exec_client02_cmd verb "$query" - echo "$result" | while read dir name img; do - id="$(echo "$img_ids_03" | grep " $img\$" | cut -f 1)" - exec_client03_cmd touch "-i$id" "$dir/$name" - done -} - - -usage() -{ - grep '^##' $0 | sed -e 's/^## *//' - echo ' -Usage: $0 command - -command is one of the following: - - attribute_table: create attributes - attributes: convert attributes for each audio file - lastplayed_numplayed: convert numplayed and lastplayed - data of each audio file - image_table: retrieve images from mysql and add them to the database - of paraslash-0.3.x - image_ids: convert image id of each audio file. - all: Do all of the above. - -Edit the top of the script to customize some options. -' -} - -if test $# -ne 1; then - usage - exit 1 -fi - -case "$1" in -attribute_table) - convert_attribute_table - ;; -attributes) - convert_attributes - ;; -lastplayed_numplayed) - convert_lastplayed_numplayed - ;; -image_table) - convert_image_table - ;; -image_ids) - convert_image_ids - ;; -all) - convert_attribute_table - convert_attributes - convert_lastplayed_numplayed - convert_image_table - convert_image_ids - ;; -*) - usage - exit 1 - ;; -esac diff --git a/convert_0.3-0.4.sh b/convert_0.3-0.4.sh new file mode 100755 index 00000000..aa0b239f --- /dev/null +++ b/convert_0.3-0.4.sh @@ -0,0 +1,221 @@ +#!/usr/bin/env bash + +#------------------------------------------------------------------------------- +## Script to convert the database of paraslash 0.3.5 to version 0.4.x. +## +## Assumptions: +## - para_server 0.3.5 is running +## - "para_client check" reports no errors +## - para_server 0.4.x is running, listens on another port and uses a +## different afs database and a different afs socket +## - The database of paraslash 0.4.x has been initialized (i.e. +## para_client init has successfully been executed) +## - All audio files in the 0.3.x database have already been added to +## the 0.4.x database (execute para_client add /my/audio/file/dir to +## do that) +## +#------------------------------------------------------------------------------- + +# Call this script without arguments to see usage info + +# How to connect to para_server 0.3.x. +client03=/usr/local/bin/para_client +port03=2991 +host03=localhost +database03=$HOME/.paraslash/afs_database + +# How to connect to para_server 0.4.x. +client04=$(pwd)/para_client +port04=2990 +host04=localhost +database04=$HOME/.paraslash/afs_database-0.4 + +# Any character that does not occur in any filename of an audio file +sep='|' + + +client03_cmd="$client03 -p $port03 -i $host03" +client04_cmd="$client04 -p $port04 -i $host04" + +exec_client03_cmd() +{ + result="$($client03_cmd -- "$@")" +} + +exec_client04_cmd() +{ + result="$($client04_cmd -- "$@")" +} + +convert_attribute_table() +{ + local atts + + echo "converting attribute table" + exec_client03_cmd lsatt + atts="$result" + exec_client04_cmd addatt $atts +} + +convert_attributes() +{ + local OIFS="$IFS" a p att atts + + printf "converting attributes: " + $client03_cmd -- ls -p -lv \ + | grep '^path:\|^attributes_txt:' \ + | sed -e "/^path:/N;s/\n/$sep/1" \ + | { + IFS="$sep" + while read p a; do + p=${p#path: } + a=${a#attributes_txt:} + IFS=" " + atts= + for att in $a; do + atts="$atts $att+" + done + IFS="$OIFS" + [[ -n "$atts" ]] && $client04_cmd -- setatt $atts "$p" + IFS="$sep" + printf "." + done + echo done + } + IFS="$OIFS" +} + +convert_lna() +{ + local OIFS="$IFS" p l n a + + printf "converting last_played, num_played, amplification values: " + $client03_cmd -- ls -p -d -lv \ + | grep '^path:\|^last_played: \|^num_played: \|^amplification: ' \ + | sed -e "/^path:/N;N;N;s/\n/$sep/g" \ + | { + IFS="$sep" + while read p l n a; do + #echo "p: $p, l:$l, n:$n a:$a" + p=${p#path: } + l=${l#last_played: } + n=${n#num_played: } + a=${a#amplification: } + IFS="$OIFS" + $client04_cmd -- touch "-l$l" "-n$n" "-a$a" "$p" + IFS="$sep" + printf "." + done + echo done + } + IFS="$OIFS" +} + +convert_blobs() +{ + local blob name + + for blob in img lyr mood pl; do + printf "converting $blob table: " + exec_client03_cmd ls$blob + echo "$result" | while read name; do + $client03_cmd -- cat$blob "$name" | $client04_cmd -- add$blob "$name" + printf "." + done + echo done + done +} + +convert_ids() +{ + local OIFS="$IFS" p i y iopts yopts + + printf "converting image and lyrics ids: " + $client03_cmd -- ls -p -lv \ + | grep '^path:\|^image_name: \|^lyrics_name: ' \ + | sed -e "/^path:/N;N;s/\n/$sep/g" \ + | { + IFS="$sep" + while read p i l; do + IFS="$OIFS" + iopts= + yopts= + p=${p#path: } + i=${i#image_name: } + l=${l#lyrics_name: } + if [[ "$i" != '(none)' ]]; then + exec_client04_cmd lsimg -l "$i" + iopts="-i${result%% *}" + fi + if [[ "$l" != '(none)' ]]; then + exec_client04_cmd lslyr -l "$l" + yopts="-y${result%% *}" + fi + if [[ -n "$iopts" && -n "$yopts" ]]; then + $client04_cmd -- touch "$iopts" "$yopts" "$p" + elif [[ -n "$iopts" ]]; then + $client04_cmd -- touch "$iopts" "$p" + elif [[ -n "$yopts" ]]; then + $client04_cmd -- touch "$yopts" "$p" + fi + printf "." + IFS="$sep" + done + echo done + } + IFS="$OIFS" +} + + +usage() +{ + grep '^##' $0 | sed -e 's/^## *//' + echo ' +Usage: $0 command + +command is one of the following: + + attribute_table: create attributes + attributes: convert attributes for each audio file + lna: convert the last_played/num_played/amplification values + blobs: convert the image/lyrics/moods/playlists tables + ids: convert image and the lyrics id of each audio file. + all: Do all of the above. + +Edit the top of the script to customize some options. +' +} + +if test $# -ne 1; then + usage + exit 1 +fi + +case "$1" in +attribute_table) + convert_attribute_table + ;; +attributes) + convert_attributes + ;; +lna) + convert_lna + ;; +blobs) + convert_blobs + ;; +ids) + convert_ids + ;; +all) + convert_attribute_table + convert_attributes + convert_lna + convert_blobs + convert_ids + ;; +*) + usage + exit 1 + ;; +esac diff --git a/crypt.c b/crypt.c index 73eebe18..917948c6 100644 --- a/crypt.c +++ b/crypt.c @@ -1,21 +1,93 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file crypt.c openssl-based RSA encryption/decryption routines */ +#include +#include +#include +#include +#include +#include +#include + #include "para.h" #include "error.h" #include "string.h" #include "crypt.h" +#include "fd.h" +/** + * Fill a buffer with random content. + * + * \param buf The buffer to fill. + * \param num The size of \a buf in bytes. + * + * This function puts \a num cryptographically strong pseudo-random bytes into + * buf. If libssl can not guarantee an unpredictable byte sequence (for example + * because the PRNG has not been seeded with enough randomness) the function + * logs an error message and calls exit(). + */ +void get_random_bytes_or_die(unsigned char *buf, int num) +{ + unsigned long err; + + /* RAND_bytes() returns 1 on success, 0 otherwise. */ + if (RAND_bytes(buf, num) == 1) + return; + err = ERR_get_error(); + PARA_EMERG_LOG("%s\n", ERR_reason_error_string(err)); + exit(EXIT_FAILURE); +} + +/** + * Seed pseudo random number generators. + * + * This function reads 64 bytes from /dev/urandom and adds them to the SSL + * PRNG. It also seeds the PRNG used by random() with a random seed obtained + * from SSL. If /dev/random could not be read, an error message is logged and + * the function calls exit(). + * + * \sa RAND_load_file(3), \ref get_random_bytes_or_die(), srandom(3), + * random(3), \ref para_random(). + */ +void init_random_seed_or_die(void) +{ + int seed, ret = RAND_load_file("/dev/urandom", 64); + + if (ret != 64) { + PARA_EMERG_LOG("could not seed PRNG (ret = %d)\n", ret); + exit(EXIT_FAILURE); + } + get_random_bytes_or_die((unsigned char *)&seed, sizeof(seed)); + srandom(seed); +} + +static int check_key_file(const char *file, int private) +{ + struct stat st; + + if (stat(file, &st) != 0) + return -ERRNO_TO_PARA_ERROR(errno); + if (private != LOAD_PRIVATE_KEY) + return 0; + if ((st.st_uid == getuid()) && (st.st_mode & 077) != 0) + return -E_KEY_PERM; + return 1; +} static EVP_PKEY *load_key(const char *file, int private) { BIO *key; EVP_PKEY *pkey = NULL; + int ret = check_key_file(file, private); + if (ret < 0) { + PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + return NULL; + } key = BIO_new(BIO_s_file()); if (!key) return NULL; @@ -92,35 +164,19 @@ int para_decrypt_buffer(char *key_file, unsigned char *outbuf, unsigned char *in ret = get_rsa_key(key_file, &rsa, LOAD_PRIVATE_KEY); if (ret < 0) return ret; - ret = RSA_private_decrypt(inlen, inbuf, outbuf, rsa, RSA_PKCS1_PADDING); + /* + * RSA is vulnerable to timing attacks. Generate a random blinding + * factor to protect against this kind of attack. + */ + ret = -E_BLINDING; + if (RSA_blinding_on(rsa, NULL) == 0) + goto out; + ret = RSA_private_decrypt(inlen, inbuf, outbuf, rsa, RSA_PKCS1_OAEP_PADDING); + RSA_blinding_off(rsa); + if (ret <= 0) + ret = -E_DECRYPT; +out: rsa_free(rsa); - return (ret > 0)? ret : -E_DECRYPT; -} - -/** - * decrypt the challenge number sent by para_server - * - * \param key_file full path of the rsa key - * \param challenge_nr result is stored here - * \param inbuf the input buffer - * \param rsa_inlen the length of \a inbuf - * - * \return positive on success, negative on errors - * - * \sa para_decrypt_buffer() - */ -int para_decrypt_challenge(char *key_file, long unsigned *challenge_nr, - unsigned char *inbuf, unsigned rsa_inlen) -{ - unsigned char *rsa_out = OPENSSL_malloc(rsa_inlen + 1); - int ret = para_decrypt_buffer(key_file, rsa_out, inbuf, rsa_inlen); - - if (ret >= 0) { - rsa_out[ret] = '\0'; - ret = sscanf((char *)rsa_out, "%lu", challenge_nr) == 1? - 1 : -E_CHALLENGE; - } - OPENSSL_free(rsa_out); return ret; } @@ -143,30 +199,114 @@ int para_encrypt_buffer(RSA *rsa, unsigned char *inbuf, if (flen < 0) return -E_ENCRYPT; - ret = RSA_public_encrypt(flen, inbuf, outbuf, rsa, RSA_PKCS1_PADDING); - return ret < 0? -E_ENCRYPT : ret; + ret = RSA_public_encrypt(flen, inbuf, outbuf, rsa, RSA_PKCS1_OAEP_PADDING); + return ret < 0? -E_ENCRYPT : ret; } /** - * encrypt the given challenge number + * Encrypt and send a buffer. * - * \param rsa: public rsa key - * \param challenge_nr the number to be encrypted - * \param outbuf the output buffer + * \param rc4c The rc4 crypt context. + * \param buf The buffer to send. + * \param len The size of \a buf in bytes. * - * \a outbuf must be at least 64 bytes long + * \return The return value of the underyling call to write_all(). * - * \return The size of the encrypted data on success, negative on errors + * \sa \ref write_all(), RC4(3). + */ +int rc4_send_bin_buffer(struct rc4_context *rc4c, const char *buf, size_t len) +{ + int ret; + unsigned char *tmp; + + assert(len); + tmp = para_malloc(len + 8); + RC4(&rc4c->send_key, len, (const unsigned char *)buf, tmp); + ret = write_all(rc4c->fd, (char *)tmp, &len); + free(tmp); + return ret; +} + +/** + * Encrypt and send a \p NULL-terminated buffer. + * + * \param rc4c The rc4 crypt context. + * \param buf The buffer to send. + * + * \return The return value of the underyling call to rc4_send_bin_buffer(). + */ +int rc4_send_buffer(struct rc4_context *rc4c, const char *buf) +{ + return rc4_send_bin_buffer(rc4c, buf, strlen(buf)); +} + +/** + * Format, encrypt and send a buffer. + * + * \param rc4c The rc4 crypt context. + * \param fmt A format string. * - * \sa para_encrypt_buffer() + * \return The return value of the underyling call to rc4_send_buffer(). + */ +__printf_2_3 int rc4_send_va_buffer(struct rc4_context *rc4c, const char *fmt, ...) +{ + char *msg; + int ret; + + PARA_VSPRINTF(fmt, msg); + ret = rc4_send_buffer(rc4c, msg); + free(msg); + return ret; +} + +/** + * Receive a buffer and decrypt it. + * + * \param rc4c The rc4 crypt context. + * \param buf The buffer to write the decrypted data to. + * \param size The size of \a buf. + * + * \return The number of bytes received on success, negative on errors, zero if + * the peer has performed an orderly shutdown. * + * \sa recv(2), RC4(3). */ -int para_encrypt_challenge(RSA* rsa, long unsigned challenge_nr, - unsigned char *outbuf) +int rc4_recv_bin_buffer(struct rc4_context *rc4c, char *buf, size_t size) { - unsigned char *inbuf = (unsigned char*) make_message("%lu", challenge_nr); - int ret = para_encrypt_buffer(rsa, inbuf, strlen((char *)inbuf), outbuf); - free(inbuf); + unsigned char *tmp = para_malloc(size); + ssize_t ret = recv(rc4c->fd, tmp, size, 0); + + if (ret > 0) + RC4(&rc4c->recv_key, ret, tmp, (unsigned char *)buf); + else if (ret < 0) + ret = -ERRNO_TO_PARA_ERROR(errno); + free(tmp); return ret; } +/** + * Receive a buffer, decrypt it and write terminating NULL byte. + * + * \param rc4c The rc4 crypt context. + * \param buf The buffer to write the decrypted data to. + * \param size The size of \a buf. + * + * Read at most \a size - 1 bytes from file descriptor given by \a rc4c, + * decrypt the received data and write a NULL byte at the end of the decrypted + * data. + * + * \return The return value of the underlying call to \ref + * rc4_recv_bin_buffer(). + */ +int rc4_recv_buffer(struct rc4_context *rc4c, char *buf, size_t size) +{ + int n; + + assert(size); + n = rc4_recv_bin_buffer(rc4c, buf, size - 1); + if (n >= 0) + buf[n] = '\0'; + else + *buf = '\0'; + return n; +} diff --git a/crypt.h b/crypt.h index 88af9e89..6bd8179b 100644 --- a/crypt.h +++ b/crypt.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -7,10 +7,6 @@ /** \file crypt.h prototypes for the RSA crypt functions */ #include -int para_decrypt_challenge(char *key_file, long unsigned *challenge_nr, - unsigned char *buf, unsigned rsa_inlen); -int para_encrypt_challenge(RSA* rsa, long unsigned challenge_nr, - unsigned char *outbuf); int para_encrypt_buffer(RSA* rsa, unsigned char *inbuf, unsigned len, unsigned char *outbuf); int para_decrypt_buffer(char *key_file, unsigned char *outbuf, unsigned char *inbuf, @@ -18,8 +14,32 @@ int para_decrypt_buffer(char *key_file, unsigned char *outbuf, unsigned char *in int get_rsa_key(char *key_file, RSA **rsa, int private); void rsa_free(RSA *rsa); +void get_random_bytes_or_die(unsigned char *buf, int num); +void init_random_seed_or_die(void); + +/** + * Used on the server-side for client-server communication encryption. + * + * The traffic between (the forked child of) para_server and the remote + * client process is crypted by a RC4 session key. This structure contains + * the RC4 keys and the file descriptor for which these keys should be used. + */ +struct rc4_context { + /** The socket file descriptor. */ + int fd; + /** Key used for sending data. */ + RC4_KEY recv_key; + /** Key used for receiving data. */ + RC4_KEY send_key; +}; +int rc4_send_bin_buffer(struct rc4_context *rc4c, const char *buf, size_t len); +int rc4_send_buffer(struct rc4_context *rc4c, const char *buf); +__printf_2_3 int rc4_send_va_buffer(struct rc4_context *rc4c, const char *fmt, ...); +int rc4_recv_bin_buffer(struct rc4_context *rcc, char *buf, size_t size); +int rc4_recv_buffer(struct rc4_context *rcc, char *buf, size_t size); /** \cond used to distinguish between loading of private/public key */ #define LOAD_PUBLIC_KEY 0 #define LOAD_PRIVATE_KEY 1 +#define CHALLENGE_SIZE 64 /** \endcond **/ diff --git a/daemon.c b/daemon.c index 011ece6d..b7a0a326 100644 --- a/daemon.c +++ b/daemon.c @@ -1,16 +1,20 @@ /* - * Copyright (C) 1997-2009 Andre Noll + * Copyright (C) 1997-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file daemon.c Some helpers for programs that detach from the console. */ -#include "para.h" -#include "daemon.h" + +#include #include #include /* getgrnam() */ #include +#include +#include +#include "para.h" +#include "daemon.h" #include "string.h" #include "color.h" @@ -52,10 +56,8 @@ void daemon_set_default_log_colors(void) [LL_CRIT] = "magenta bold", [LL_EMERG] = "red bold", }; - for (i = 0; i < NUM_LOGLEVELS; i++) { - int ret = color_parse(default_log_colors[i], me->log_colors[i]); - assert(ret >= 0); - } + for (i = 0; i < NUM_LOGLEVELS; i++) + color_parse_or_die(default_log_colors[i], me->log_colors[i]); } /** @@ -67,7 +69,7 @@ void daemon_set_default_log_colors(void) * * \return 1 On success, -1 on errors. */ -int daemon_set_log_color(char const *arg) +void daemon_set_log_color_or_die(char const *arg) { char *p = strchr(arg, ':'); int ret, ll; @@ -79,14 +81,11 @@ int daemon_set_log_color(char const *arg) goto err; ll = ret; p++; - ret = color_parse(p, me->log_colors[ll]); - if (ret < 0) - goto err; - return 1; + color_parse_or_die(p, me->log_colors[ll]); + return; err: - PARA_ERROR_LOG("%s: color syntax error\n", arg); - return -1; - + PARA_EMERG_LOG("%s: color syntax error\n", arg); + exit(EXIT_FAILURE); } /** @@ -139,7 +138,7 @@ void daemon_clear_flag(unsigned flag) me->flags &= ~flag; } -static unsigned daemon_test_flag(unsigned flag) +static bool daemon_test_flag(unsigned flag) { return me->flags & flag; } @@ -330,8 +329,9 @@ __printf_2_3 void para_log(int ll, const char* fmt,...) va_list argp; FILE *fp; struct tm *tm; - time_t t1; char *color; + bool log_time = daemon_test_flag(DF_LOG_TIME), log_timing = + daemon_test_flag(DF_LOG_TIMING); ll = PARA_MIN(ll, NUM_LOGLEVELS - 1); ll = PARA_MAX(ll, LL_DEBUG); @@ -342,12 +342,18 @@ __printf_2_3 void para_log(int ll, const char* fmt,...) color = daemon_test_flag(DF_COLOR_LOG)? me->log_colors[ll] : NULL; if (color) fprintf(fp, "%s", color); - if (daemon_test_flag(DF_LOG_TIME)) { /* print date and time */ - char str[100]; - time(&t1); - tm = localtime(&t1); - strftime(str, sizeof(str), "%b %d %H:%M:%S", tm); - fprintf(fp, "%s ", str); + if (log_time || log_timing) { + struct timeval tv; + gettimeofday(&tv, NULL); + if (daemon_test_flag(DF_LOG_TIME)) { /* print date and time */ + time_t t1 = tv.tv_sec; + char str[100]; + tm = localtime(&t1); + strftime(str, sizeof(str), "%b %d %H:%M:%S", tm); + fprintf(fp, "%s%s", str, log_timing? ":" : " "); + } + if (log_timing) /* print milliseconds */ + fprintf(fp, "%04lu ", (long unsigned)tv.tv_usec / 1000); } if (daemon_test_flag(DF_LOG_HOSTNAME)) { if (!me->hostname) diff --git a/daemon.h b/daemon.h index c8dd997f..4e803bdc 100644 --- a/daemon.h +++ b/daemon.h @@ -15,7 +15,7 @@ void daemon_set_flag(unsigned flag); void daemon_clear_flag(unsigned flag); void daemon_set_loglevel(char *loglevel); void daemon_set_default_log_colors(void); -int daemon_set_log_color(char const *arg); +void daemon_set_log_color_or_die(char const *arg); /** Daemon log configuration flags. */ enum daemon_flags { @@ -29,4 +29,6 @@ enum daemon_flags { DF_LOG_LL = 8, /** Use colored output. */ DF_COLOR_LOG = 16, + /** Include milliseconds in log output. */ + DF_LOG_TIMING = 32 }; diff --git a/dccp_recv.c b/dccp_recv.c index 95444588..41c2ee1a 100644 --- a/dccp_recv.c +++ b/dccp_recv.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -11,6 +11,7 @@ * (C) 2005 Ian McDonald */ +#include #include #include @@ -23,12 +24,10 @@ #include "string.h" #include "net.h" #include "fd.h" +#include "buffer_tree.h" #include "dccp_recv.cmdline.h" -/** the size of the output buffer */ -#define DCCP_BUFSIZE 40960 - /** * data specific to the dccp receiver * @@ -37,6 +36,7 @@ struct private_dccp_recv_data { /** the file descriptor for the dccp socket */ int fd; + struct btr_pool *btrp; }; @@ -47,8 +47,7 @@ static void dccp_recv_close(struct receiver_node *rn) if (pdd && pdd->fd > 0) close(pdd->fd); - free(rn->buf); - rn->buf = NULL; + btr_pool_free(pdd->btrp); free(rn->private_data); rn->private_data = NULL; } @@ -58,12 +57,25 @@ static int dccp_recv_open(struct receiver_node *rn) { struct private_dccp_recv_data *pdd; struct dccp_recv_args_info *conf = rn->conf; - int fd, ret = makesock(AF_UNSPEC, IPPROTO_DCCP, 0, conf->host_arg, - conf->port_arg); + struct flowopts *fo = NULL; + uint8_t *ccids = NULL; + int fd, ret, i; - if (ret < 0) - return ret; - fd = ret; + /* Copy CCID preference list (u8 array required) */ + if (conf->ccid_given) { + ccids = para_malloc(conf->ccid_given); + fo = flowopt_new(); + + for (i = 0; i < conf->ccid_given; i++) + ccids[i] = conf->ccid_arg[i]; + + OPT_ADD(fo, SOL_DCCP, DCCP_SOCKOPT_CCID, ccids, i); + } + + fd = makesock(IPPROTO_DCCP, 0, conf->host_arg, conf->port_arg, fo); + free(ccids); + if (fd < 0) + return fd; /* * Disable unused CCIDs: the receiver does not send any application * data to the server. By shutting down this unused path we reduce @@ -77,8 +89,8 @@ static int dccp_recv_open(struct receiver_node *rn) ret = mark_fd_nonblocking(fd); if (ret < 0) goto err; - rn->buf = para_calloc(DCCP_BUFSIZE); rn->private_data = pdd = para_calloc(sizeof(struct private_dccp_recv_data)); + pdd->btrp = btr_pool_new("dccp_recv", 320 * 1024); pdd->fd = fd; return 1; err: @@ -86,16 +98,38 @@ err: return ret; } -static void dccp_shutdown(void) +/** + * Check whether the host supports the requested 'ccid' arguments. + * \param conf DCCP receiver arguments. + * \return True if all CCIDs requested in \a conf are supported. + */ +static bool dccp_recv_ccid_support_check(struct dccp_recv_args_info *conf) { - ; /* nothing to do */ + uint8_t *ccids; + int i, j, nccids; + + nccids = dccp_available_ccids(&ccids); + if (nccids <= 0) + return false; + + for (i = 0; i < conf->ccid_given; i++) { + for (j = 0; j < nccids && ccids[j] != conf->ccid_arg[i]; j++) + ; + if (j == nccids) { + PARA_ERROR_LOG("'CCID-%d' not supported on this host.\n", + conf->ccid_arg[i]); + return false; + } + } + return true; } static void *dccp_recv_parse_config(int argc, char **argv) { - struct dccp_recv_args_info *tmp = para_calloc(sizeof(struct dccp_recv_args_info)); + struct dccp_recv_args_info *tmp = para_calloc(sizeof(*tmp)); - if (!dccp_recv_cmdline_parser(argc, argv, tmp)) + if (!dccp_recv_cmdline_parser(argc, argv, tmp) && + dccp_recv_ccid_support_check(tmp)) return tmp; free(tmp); return NULL; @@ -107,6 +141,8 @@ static void dccp_recv_pre_select(struct sched *s, struct task *t) struct private_dccp_recv_data *pdd = rn->private_data; t->error = 0; + if (generic_recv_pre_select(s, t) <= 0) + return; para_fd_set(pdd->fd, &s->rfds, &s->max_fileno); } @@ -114,25 +150,38 @@ static void dccp_recv_post_select(struct sched *s, struct task *t) { struct receiver_node *rn = container_of(t, struct receiver_node, task); struct private_dccp_recv_data *pdd = rn->private_data; - - if (rn->output_error && *rn->output_error < 0) { - t->error = *rn->output_error; - return; - } - if (!FD_ISSET(pdd->fd, &s->rfds)) - return; /* nothing to do */ - if (rn->loaded >= DCCP_BUFSIZE) { - t->error = -E_DCCP_OVERRUN; - return; + struct btr_node *btrn = rn->btrn; + struct iovec iov[2]; + int ret, iovcnt; + size_t num_bytes; + + ret = btr_node_status(btrn, 0, BTR_NT_ROOT); + if (ret <= 0) + goto out; + iovcnt = btr_pool_get_buffers(pdd->btrp, iov); + ret = -E_DCCP_OVERRUN; + if (iovcnt == 0) + goto out; + ret = readv_nonblock(pdd->fd, iov, iovcnt, &s->rfds, &num_bytes); + if (num_bytes == 0) + goto out; + if (num_bytes <= iov[0].iov_len) /* only the first buffer was filled */ + btr_add_output_pool(pdd->btrp, num_bytes, btrn); + else { /* both buffers contain data */ + btr_add_output_pool(pdd->btrp, iov[0].iov_len, btrn); + btr_add_output_pool(pdd->btrp, num_bytes - iov[0].iov_len, btrn); } - t->error = recv_bin_buffer(pdd->fd, rn->buf + rn->loaded, - DCCP_BUFSIZE - rn->loaded); - if (t->error > 0) { - rn->loaded += t->error; +out: + if (ret >= 0) return; - } - if (!t->error) - t->error = -E_RECV_EOF; + btr_remove_node(rn->btrn); + t->error = ret; +} + +static void dccp_recv_free_config(void *conf) +{ + dccp_recv_cmdline_parser_free(conf); + free(conf); } /** @@ -147,12 +196,12 @@ void dccp_recv_init(struct receiver *r) struct dccp_recv_args_info dummy; dccp_recv_cmdline_parser_init(&dummy); - r->shutdown = dccp_shutdown; r->open = dccp_recv_open; r->close = dccp_recv_close; r->pre_select = dccp_recv_pre_select; r->post_select = dccp_recv_post_select; r->parse_config = dccp_recv_parse_config; + r->free_config = dccp_recv_free_config; r->help = (struct ggo_help) { .short_help = dccp_recv_args_info_help, .detailed_help = dccp_recv_args_info_detailed_help diff --git a/dccp_send.c b/dccp_send.c index 9b72b7c9..304a42e7 100644 --- a/dccp_send.c +++ b/dccp_send.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -11,8 +11,10 @@ * (C) 2005 Ian McDonald */ +#include #include #include +#include #include "para.h" #include "error.h" @@ -22,19 +24,21 @@ #include "server.h" #include "net.h" #include "list.h" -#include "vss.h" #include "send.h" +#include "vss.h" #include "fd.h" #include "close_on_fork.h" #include "chunk_queue.h" #include "server.cmdline.h" #include "acl.h" -/** Do not write more than that many bytes at once. */ -#define DCCP_MAX_BYTES_PER_WRITE 1024 - static struct sender_status dccp_sender_status, *dss = &dccp_sender_status; +struct dccp_fec_client { + struct fec_client_parms fcp; + struct fec_client *fc; +}; + static void dccp_pre_select(int *max_fileno, fd_set *rfds, __a_unused fd_set *wfds) { @@ -42,40 +46,107 @@ static void dccp_pre_select(int *max_fileno, fd_set *rfds, para_fd_set(dss->listen_fd, rfds, max_fileno); } -static void dccp_post_select(fd_set *rfds, __a_unused fd_set *wfds) +/** + * Query the TX CCID used on the sender-client half connection. + * \param sockfd Server socket descriptor to query (after accept(2)). + * \return CCID number > 0, -1 on error/incompatibility. + * + * NB: This feature is only available on Linux > 2.6.30; on older kernels + * ENOPROTOOPT ("Protocol not available") will be returned. + */ +static int dccp_get_tx_ccid(int sockfd) { - struct sender_client *sc; + int tx_ccid; + socklen_t len = sizeof(tx_ccid); + + if (getsockopt(sockfd, SOL_DCCP, + DCCP_SOCKOPT_TX_CCID, &tx_ccid, &len) < 0) { + PARA_WARNING_LOG("DCCP_SOCKOPT_TX_CCID: %s\n", strerror(errno)); + return -1; + } + return tx_ccid; +} - if (dss->listen_fd < 0 || !FD_ISSET(dss->listen_fd, rfds)) - return; - sc = accept_sender_client(dss); - if (!sc) - return; - /* - * Bypass unused CCID paths: the sender does not receive application data - * from the client; by shutting down this unused communication path we can - * reduce processing costs a bit. See analogous comment in dccp_recv.c. - */ - if (shutdown(sc->fd, SHUT_RD) >= 0) - return; - PARA_WARNING_LOG("%s\n", strerror(errno)); +static void dccp_shutdown_client(struct sender_client *sc) +{ + struct dccp_fec_client *dfc = sc->private_data; + + vss_del_fec_client(dfc->fc); shutdown_client(sc, dss); } -static void dccp_send(long unsigned current_chunk, - __a_unused long unsigned chunks_sent, const char *buf, - size_t len, const char *header_buf, size_t header_len) +static void dccp_shutdown_clients(void) { struct sender_client *sc, *tmp; list_for_each_entry_safe(sc, tmp, &dss->client_list, node) - send_chunk(sc, dss, DCCP_MAX_BYTES_PER_WRITE, current_chunk, buf, - len, header_buf, header_len); + dccp_shutdown_client(sc); } -static void dccp_shutdown_clients(void) +/** * Obtain current MPS according to RFC 4340, sec. 14. */ +static int dccp_init_fec(struct sender_client *sc) { - shutdown_clients(dss); + int mps, ret; + socklen_t ml = sizeof(mps); + + /* If call fails, return some sensible minimum value */ + ret = getsockopt(sc->fd, SOL_DCCP, DCCP_SOCKOPT_GET_CUR_MPS, &mps, &ml); + if (ret < 0) { + PARA_NOTICE_LOG("can not determine MPS: %s\n", strerror(errno)); + mps = generic_max_transport_msg_size(sc->fd) - DCCP_MAX_HEADER; + } + PARA_INFO_LOG("current MPS = %d bytes\n", mps); + assert(mps > 0); + if (conf.dccp_max_slice_size_arg > 0) + mps = PARA_MIN(mps, conf.dccp_max_slice_size_arg); + return mps; +} + +static int dccp_send_fec(struct sender_client *sc, char *buf, size_t len) +{ + int ret = write_nonblock(sc->fd, buf, len); + + if (ret < 0) + dccp_shutdown_client(sc); + return ret; +} + +static void dccp_post_select(fd_set *rfds, __a_unused fd_set *wfds) +{ + struct sender_client *sc; + struct dccp_fec_client *dfc; + int tx_ccid; + + sc = accept_sender_client(dss, rfds); + if (!sc) + return; + + /* If CCID identifiable, present client as #~ */ + tx_ccid = dccp_get_tx_ccid(sc->fd); + if (tx_ccid != -1) { + char *tmp = make_message("%s~%d", sc->name, tx_ccid); + + free(sc->name); + sc->name = tmp; + } + /* + * Bypass unused CCID paths: the sender does not receive application data + * from the client; by shutting down this unused communication path we can + * reduce processing costs a bit. See analogous comment in dccp_recv.c. + */ + if (shutdown(sc->fd, SHUT_RD) < 0) { + PARA_WARNING_LOG("%s\n", strerror(errno)); + shutdown_client(sc, dss); + return; + } + dfc = para_calloc(sizeof(*dfc)); + sc->private_data = dfc; + dfc->fcp.data_slices_per_group = conf.dccp_data_slices_per_group_arg; + dfc->fcp.slices_per_group = conf.dccp_slices_per_group_arg; + dfc->fcp.init_fec = dccp_init_fec; + dfc->fcp.send_fec = dccp_send_fec; + dfc->fcp.need_periodic_header = false; + dfc->fc = vss_add_fec_client(sc, &dfc->fcp); } static int dccp_com_on(__a_unused struct sender_command_data *scd) @@ -85,6 +156,7 @@ static int dccp_com_on(__a_unused struct sender_command_data *scd) static int dccp_com_off(__a_unused struct sender_command_data *scd) { + dccp_shutdown_clients(); generic_com_off(dss); return 1; } @@ -102,9 +174,34 @@ static int dccp_com_allow(struct sender_command_data *scd) return 1; } +/** + * Return list of available CCIDs or warning, in static buffer. + */ +static const char *dccp_list_available_ccids(void) +{ + /* Worst case length: n * 3 digits + n-1 spaces + '\0' */ + static char list[DCCP_MAX_HOST_CCIDS * 4]; + uint8_t *ccids; + int i, len, nccids; + + nccids = dccp_available_ccids(&ccids); + if (nccids < 0) { + snprintf(list, sizeof(list), "Unable to query available CCIDs"); + } else { + for (i = len = 0; i < nccids; i++) + len += snprintf(list + len, sizeof(list) - len, + "%s%d", i ? " " : "", ccids[i]); + } + return list; +} + static char *dccp_info(void) { - return get_sender_info(dss, "dccp"); + char *info = get_sender_info(dss, "dccp"); + char *ret = make_message("%s" "\tsupported ccids: %s\n", + info, dccp_list_available_ccids()); + free(info); + return ret; } /** @@ -117,13 +214,14 @@ static char *dccp_info(void) */ void dccp_send_init(struct sender *s) { - int ret; + int ret, k, n; s->info = dccp_info; - s->send = dccp_send; + s->send = NULL; s->pre_select = dccp_pre_select; s->post_select = dccp_post_select; s->shutdown_clients = dccp_shutdown_clients; + s->resolve_target = NULL; s->help = generic_sender_help; s->client_cmds[SENDER_ON] = dccp_com_on; s->client_cmds[SENDER_OFF] = dccp_com_off; @@ -132,6 +230,15 @@ void dccp_send_init(struct sender *s) s->client_cmds[SENDER_ADD] = NULL; s->client_cmds[SENDER_DELETE] = NULL; + k = conf.dccp_data_slices_per_group_arg; + n = conf.dccp_slices_per_group_arg; + + if (k <= 0 || n <= 0 || k >= n) { + PARA_WARNING_LOG("invalid FEC parameters, using defaults\n"); + conf.dccp_data_slices_per_group_arg = 3; + conf.dccp_slices_per_group_arg = 4; + } + init_sender_status(dss, conf.dccp_access_arg, conf.dccp_access_given, conf.dccp_port_arg, conf.dccp_max_clients_arg, conf.dccp_default_deny_given); diff --git a/depend.sh b/depend.sh new file mode 100755 index 00000000..aad45a3c --- /dev/null +++ b/depend.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +# Call gcc to output a rule suitable for make describing the dependencies of +# the given input file and parse the output to add a *.d target with the same +# dependencies. + +# The first two arguments to that script are special: $1 is the object +# directory. This string is prefixed to both the .o and the .d target. $2 is +# the directory that contains the *.cmdline.h files generated by gengetopt. + +# As gcc outputs the dependencies on the *.cmdline.h files either as either +# foo.cmdline.h or as $cmdline_dir/foo,cmdline.h, depending on whether the +# latter file exists, we prefix the former with $2/ + +object_dir="$1" +cmdline_dir="$2" +shift +shift + +LC_ALL=C gcc -MM -MG "$@" \ + | sed -e "s@^\(.*\)\.o:@$object_dir/\1.d $object_dir/\1.o:@" \ + -e "s@[ ^]\([a-zA-Z0-9_]\+\.cmdline.h\)@ $cmdline_dir/\1@g" diff --git a/error.h b/error.h index 4ec9da85..3dfee0dd 100644 --- a/error.h +++ b/error.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -16,39 +16,105 @@ DEFINE_ERRLIST_OBJECT_ENUM; #define TIME_ERRORS #define CLOSE_ON_FORK_ERRORS #define DAEMON_ERRORS -#define UDP_SEND_ERRORS #define GUI_ERRORS #define RINGBUFFER_ERRORS #define SCORE_ERRORS #define SHA1_ERRORS #define RBTREE_ERRORS #define RECV_ERRORS -#define STDOUT_ERRORS #define IPC_ERRORS #define DCCP_SEND_ERRORS #define HTTP_SEND_ERRORS #define GGO_ERRORS #define COLOR_ERRORS #define SIGNAL_ERRORS - +#define FADE_ERRORS +#define SERVER_COMMAND_LIST_ERRORS +#define AFS_COMMAND_LIST_ERRORS +#define AUDIOD_COMMAND_LIST_ERRORS +#define STDOUT_ERRORS +#define FILE_WRITE_ERRORS +#define STDIN_ERRORS extern const char **para_errlist[]; +#define OGG_AFH_COMMON_ERRORS \ + PARA_ERROR(STREAM_PACKETOUT, "ogg stream packet-out error (first packet)"), \ + PARA_ERROR(SYNC_PAGEOUT, "ogg sync page-out error (no ogg file?)"), \ + PARA_ERROR(STREAM_PAGEIN, "ogg stream page-in error (first page)"), \ + PARA_ERROR(OGG_SYNC, "internal ogg storage overflow"), \ + PARA_ERROR(OGG_EMPTY, "no ogg pages found"), \ + + +#define SPX_AFH_ERRORS \ + PARA_ERROR(SPX_COMMENT, "invalid speex comment"), \ + + +#define SPX_COMMON_ERRORS \ + PARA_ERROR(SPX_HEADER, "can not read speex header"), \ + PARA_ERROR(SPX_HEADER_MODE, "invalid speex mode in header"), \ + PARA_ERROR(SPX_VERSION, "incompatible speex bit stream version"), \ + PARA_ERROR(SPX_DECODER_INIT, "speex decoder initialization failed"), \ + PARA_ERROR(SPX_CTL_BAD_RQ, "speex_decoder_ctl: invalid request"), \ + PARA_ERROR(SPX_CTL_INVAL, "speex_decoder_ctl: invalid argument"), \ + +#define SPXDEC_FILTER_ERRORS \ + PARA_ERROR(SPX_DECODE, "speex decoding error"), \ + PARA_ERROR(SPX_DECODE_OVERFLOW, "speex decoding overflow"), \ + PARA_ERROR(SPX_EOS, "speex: end of stream"), \ + +#define BUFFER_TREE_ERRORS \ + PARA_ERROR(BTR_EOF, "buffer tree: end of file"), \ + PARA_ERROR(BTR_NO_CHILD, "btr node has no children"), \ + PARA_ERROR(BTR_NAVAIL, "btr node: value currently unavailable"), \ + + +#define BITSTREAM_ERRORS \ + PARA_ERROR(VLC, "invalid vlc code"), \ + + +#define WMA_AFH_ERRORS \ + PARA_ERROR(NO_WMA, "asf/wma format not recognized"), \ + + +#define WMA_COMMON_ERRORS \ + PARA_ERROR(WMA_NO_GUID, "audio stream guid not found"), \ + + +#define WMADEC_FILTER_ERRORS \ + PARA_ERROR(WMA_BAD_PARAMS, "invalid WMA parameters"), \ + PARA_ERROR(WMA_OUTPUT_SPACE, "insufficient output space"), \ + PARA_ERROR(WMA_BAD_SUPERFRAME, "invalid superframe"), \ + PARA_ERROR(WMA_BLOCK_SIZE, "invalid block size"), \ + PARA_ERROR(INCOHERENT_BLOCK_LEN, "incoherent block length"), \ + PARA_ERROR(WMADEC_EOF, "wmadec: end of file"), \ + + +#define IMDCT_ERRORS \ + PARA_ERROR(FFT_BAD_PARAMS, "invalid params for fft"), \ + + #define PREBUFFER_FILTER_ERRORS \ PARA_ERROR(PREBUFFER_SYNTAX, "syntax error in prebuffer filter config"), \ + PARA_ERROR(PREBUFFER_SUCCESS, "prebuffering complete"), \ + #define OSS_WRITE_ERRORS \ PARA_ERROR(BAD_SAMPLE_FORMAT, "sample format not supported"), \ PARA_ERROR(BAD_CHANNEL_COUNT, "channel count not supported"), \ PARA_ERROR(BAD_SAMPLERATE, "sample rate not supported"), \ + PARA_ERROR(OSS_EOF, "oss: end of file"), \ #define COMPRESS_FILTER_ERRORS \ PARA_ERROR(COMPRESS_SYNTAX, "syntax error in compress filter config"), \ + PARA_ERROR(COMPRESS_EOF, "compress: end of file"), \ #define WAV_FILTER_ERRORS \ - PARA_ERROR(WAV_BAD_FC, "invalid filter chain configuration"), \ + PARA_ERROR(WAV_BAD_FC, "invalid filter configuration"), \ + PARA_ERROR(WAV_EOF, "wav filter: end of file"), \ + PARA_ERROR(WAV_SUCCESS, "successfully wrote wav header"), \ #define FEC_ERRORS \ @@ -68,16 +134,14 @@ extern const char **para_errlist[]; #define AMP_FILTER_ERRORS \ PARA_ERROR(AMP_SYNTAX, "syntax error in amp filter config"), \ + PARA_ERROR(AMP_ZERO_AMP, "no amplification necessary"), \ + PARA_ERROR(AMP_EOF, "amp: end of file"), \ #define SEND_COMMON_ERRORS \ PARA_ERROR(MAX_CLIENTS, "maximal number of clients exceeded"), \ -#define FADE_ERRORS \ - PARA_ERROR(FADE_SYNTAX, "fade syntax error"), \ - - #define CLIENT_ERRORS \ PARA_ERROR(TASK_STARTED, "task started"), \ @@ -95,66 +159,22 @@ extern const char **para_errlist[]; PARA_ERROR(ACL_PERM, "access denied by acl"), \ -#define FSCK_ERRORS \ - PARA_ERROR(FSCK_SYNTAX, "fsck syntax error"), \ - PARA_ERROR(RANGE_VIOLATION, "range violation detected, very bad"), \ - PARA_ERROR(NOT_A_REGULAR_FILE, "not a regular file"), \ - - -#define OSL_ERRORS \ - PARA_ERROR(BAD_DB_DIR, "invalid database directory"), \ - PARA_ERROR(NO_COLUMN_DESC, "missing column description"), \ - PARA_ERROR(BAD_NAME, "invalid name for a column/table"), \ - PARA_ERROR(BAD_STORAGE_TYPE, "invalid storage type"), \ - PARA_ERROR(BAD_STORAGE_FLAGS, "invalid storage flags"), \ - PARA_ERROR(NO_COLUMN_NAME, "missing column name"), \ - PARA_ERROR(NO_COLUMNS, "at least one column required"), \ - PARA_ERROR(BAD_COLUMN_NAME, "invalid name for a table column"), \ - PARA_ERROR(NO_UNIQUE_RBTREE_COLUMN, "need at least one mapped column with OSL_UNIQE and OSL_RBTREE OSL"), \ - PARA_ERROR(NO_RBTREE_COL, "at least one column needs an rbtree"), \ - PARA_ERROR(DUPLICATE_COL_NAME, "column name given twice"), \ - PARA_ERROR(BAD_STORAGE_SIZE, "invalid storage size"), \ - PARA_ERROR(NO_COMPARE_FUNC, "missing compare function"), \ - PARA_ERROR(BAD_DATA_SIZE, "wrong data size for fixed-size column"), \ - PARA_ERROR(NOT_MAPPED, "file not mapped"), \ - PARA_ERROR(ALREADY_MAPPED, "file already mapped"), \ - PARA_ERROR(BAD_SIZE, "invalid size specified"), \ - PARA_ERROR(TRUNC, "failed to truncate file"), \ - PARA_ERROR(BAD_TABLE, "table not open"), \ - PARA_ERROR(BAD_TABLE_DESC, "invalid table description"), \ - PARA_ERROR(RB_KEY_EXISTS, "key already exists in rbtree"), \ - PARA_ERROR(RB_KEY_NOT_FOUND, "key not found in rbtree"), \ - PARA_ERROR(BAD_ROW_NUM, "invalid row number"), \ - PARA_ERROR(INDEX_CORRUPTION, "index corruption detected"), \ - PARA_ERROR(INVALID_OBJECT, "reference to invalid object"), \ - PARA_ERROR(STAT, "can not stat file"), \ - PARA_ERROR(WRITE, "write error"), \ - PARA_ERROR(LSEEK, "lseek error"), \ - PARA_ERROR(BUSY, "table is busy"), \ - PARA_ERROR(SHORT_TABLE, "table too short"), \ - PARA_ERROR(NO_MAGIC, "missing table header magic"), \ - PARA_ERROR(VERSION_MISMATCH, "table version not supported"), \ - PARA_ERROR(BAD_COLUMN_NUM, "invalid column number"), \ - PARA_ERROR(BAD_TABLE_FLAGS, "invalid flags in table description"), \ - PARA_ERROR(BAD_ROW, "invalid row"), \ - - - #define AFS_ERRORS \ PARA_ERROR(BAD_TABLE_NAME, "invalid table"), \ - PARA_ERROR(INPUT_TOO_LARGE, "input too large for stdin command"), \ PARA_ERROR(AFS_SYNTAX, "afs syntax error"), \ PARA_ERROR(AFS_SIGNAL, "afs caught deadly signal"), \ PARA_ERROR(AFS_SOCKET, "afs socket not writable"), \ - PARA_ERROR(AFS_SHORT_READ, "short read from afs socket"), \ #define MOOD_ERRORS \ - PARA_ERROR(MOOD_SYNTAX, "mood syntax error"), \ PARA_ERROR(NO_MOOD, "no mood available"), \ PARA_ERROR(DUMMY_ROW, "attempted to access blob dummy object"), \ +#define MM_ERRORS \ + PARA_ERROR(MOOD_SYNTAX, "mood syntax error"), \ + + #define ATTRIBUTE_ERRORS \ PARA_ERROR(ATTR_SYNTAX, "attribute syntax error"), \ PARA_ERROR(NO_ATTRIBUTES, "no attributes defined yet"), \ @@ -163,6 +183,7 @@ extern const char **para_errlist[]; #define BLOB_ERRORS \ PARA_ERROR(BLOB_SYNTAX, "blob syntax error"), \ + PARA_ERROR(INPUT_TOO_LARGE, "input too large for stdin command"), \ #define PLAYLIST_ERRORS \ @@ -178,7 +199,6 @@ extern const char **para_errlist[]; PARA_ERROR(BAD_PATH, "invalid path"), \ PARA_ERROR(BAD_SORT, "invalid sorting method"), \ PARA_ERROR(FNMATCH, "fnmatch error"), \ - PARA_ERROR(NO_MATCH, "no matches"), \ PARA_ERROR(NO_AFHI, "audio format handler info required"), \ PARA_ERROR(AFT_SYNTAX, "audio file table syntax error"), \ PARA_ERROR(HASH_MISMATCH, "hash mismatch, consider re-add"), \ @@ -199,14 +219,10 @@ extern const char **para_errlist[]; #define AUDIOC_ERRORS \ PARA_ERROR(AUDIOC_SYNTAX, "audioc syntax error"), \ - PARA_ERROR(AUDIOC_READ, "audioc read error"), \ - PARA_ERROR(AUDIOC_WRITE, "audioc write error"), \ - PARA_ERROR(AUDIOC_OVERRUN, "audioc buffer overrun"), \ #define CLIENT_COMMON_ERRORS \ PARA_ERROR(CLIENT_SYNTAX, "syntax error"), \ - PARA_ERROR(INVALID_CHALLENGE, "did not receive valid challenge"), \ PARA_ERROR(NO_CONFIG, "config file not found"), \ PARA_ERROR(BAD_CONFIG, "syntax error in config file"), \ PARA_ERROR(CLIENT_AUTH, "authentication failed"), \ @@ -220,26 +236,24 @@ extern const char **para_errlist[]; PARA_ERROR(NOT_INITIALIZED, "scheduler not yet initialized"), \ -#define STDIN_ERRORS \ - PARA_ERROR(STDIN_EOF, "end of file"), \ - - - #define NET_ERRORS \ PARA_ERROR(NAME_TOO_LONG, "name too long for struct sockaddr_un"), \ - PARA_ERROR(ADDRESS_LOOKUP, "address lookup / socket creation failed"), \ + PARA_ERROR(ADDRESS_LOOKUP, "can not resolve requested address"),\ PARA_ERROR(CHMOD, "failed to set socket mode"), \ PARA_ERROR(SENDMSG, "sendmsg() failed"), \ PARA_ERROR(RECVMSG, "recvmsg() failed"), \ PARA_ERROR(SCM_CREDENTIALS, "did not receive SCM credentials"), \ - PARA_ERROR(RECV_PATTERN, "did not receive expected pattern"), \ + PARA_ERROR(MAKESOCK, "makesock error"), \ #define UDP_RECV_ERRORS \ PARA_ERROR(UDP_SYNTAX, "udp_recv syntax error"), \ - PARA_ERROR(UDP_BAD_HEADER, "invalid udp audio header"), \ PARA_ERROR(UDP_OVERRUN, "output buffer overrun"), \ - PARA_ERROR(UDP_BAD_STREAM_TYPE, "invalid stream type"), \ + + +#define UDP_SEND_ERRORS \ + PARA_ERROR(TARGET_EXISTS, "requested target is already present"),\ + PARA_ERROR(TARGET_NOT_FOUND, "requested target not found") #define HTTP_RECV_ERRORS \ @@ -265,17 +279,17 @@ extern const char **para_errlist[]; PARA_ERROR(AUDIOD_SYNTAX, "syntax error"), \ PARA_ERROR(UCRED_PERM, "permission denied"), \ PARA_ERROR(INVALID_AUDIOD_CMD, "invalid command"), \ + PARA_ERROR(TOO_MANY_CLIENTS, "maximal number of stat clients exceeded"), \ + PARA_ERROR(UNKNOWN_STAT_ITEM, "status item not recognized"), \ #define FILTER_COMMON_ERRORS \ - PARA_ERROR(UNSUPPORTED_FILTER, "given filter not supported"), \ PARA_ERROR(BAD_FILTER_OPTIONS, "invalid filter option given"), \ - PARA_ERROR(FC_EOF, "filter chain: eof"), \ + PARA_ERROR(UNSUPPORTED_FILTER, "given filter not supported"), \ #define STAT_ERRORS \ - PARA_ERROR(TOO_MANY_CLIENTS, "maximal number of stat clients exceeded"), \ - PARA_ERROR(UNKNOWN_STAT_ITEM, "status item not recognized"), \ + PARA_ERROR(STAT_ITEM_PARSE, "failed to parse status item"), \ #define OGGDEC_FILTER_ERRORS \ @@ -285,23 +299,17 @@ extern const char **para_errlist[]; PARA_ERROR(OGGDEC_BADHEADER, "invalid vorbis bitstream header"), \ PARA_ERROR(OGGDEC_FAULT, "bug or heap/stack corruption"), \ PARA_ERROR(OGGDEC_BADLINK, "invalid stream section or requested link corrupt"), \ - PARA_ERROR(OGGDEC_SYNTAX, "syntax error in oggdec config"), \ #define GRAB_CLIENT_ERRORS \ - PARA_ERROR(PEDANTIC_GRAB, "fd not ready and pedantic grab requested"), \ PARA_ERROR(GC_WRITE, "grab client write error"), \ - PARA_ERROR(BAD_GC_SLOT, "invalid slot requested by grab client"), \ - PARA_ERROR(BAD_GC_FILTER_NUM, "invalid filter number given"), \ PARA_ERROR(GC_SYNTAX, "grab client syntax error"), \ - PARA_ERROR(GC_HELP_GIVEN, ""), /* not really an error */ \ - PARA_ERROR(GC_VERSION_GIVEN, ""), /* not really an error */ \ #define MP3DEC_FILTER_ERRORS \ PARA_ERROR(MAD_FRAME_DECODE, "mad frame decode error"), \ - PARA_ERROR(MP3DEC_OVERRUN, "mp3 output buffer overrun"), \ PARA_ERROR(MP3DEC_SYNTAX, "syntax error in mp3dec config"), \ + PARA_ERROR(MP3DEC_EOF, "mp3dec: end of file"), \ #define FILTER_ERRORS \ @@ -314,6 +322,8 @@ extern const char **para_errlist[]; PARA_ERROR(STRTOLL, "unknown strtoll error"), \ PARA_ERROR(ATOI_NO_DIGITS, "no digits found in string"), \ PARA_ERROR(ATOI_JUNK_AT_END, "further characters after number"), \ + PARA_ERROR(SIZE_PREFIX, "bad size prefix"), \ + PARA_ERROR(REGEX, "regular expression error") \ #define EXEC_ERRORS \ @@ -340,17 +350,13 @@ extern const char **para_errlist[]; #define OGG_AFH_ERRORS \ - PARA_ERROR(SYNC_PAGEOUT, "ogg sync page-out error (no ogg file?)"), \ - PARA_ERROR(STREAM_PAGEIN, "ogg stream page-in error (first page)"), \ - PARA_ERROR(STREAM_PACKETOUT, "ogg stream packet-out error (first packet)"), \ PARA_ERROR(VORBIS, "vorbis synthesis header-in error (not vorbis?)"), \ - PARA_ERROR(OGG_SYNC, "internal ogg storage overflow"), \ - PARA_ERROR(OGG_EMPTY, "no ogg pages found"), \ #define VSS_ERRORS \ PARA_ERROR(NOFD, "did not receive open fd from afs"), \ - PARA_ERROR(BAD_CT, "invalid chunk table or bad FEC configuration") + PARA_ERROR(BAD_CT, "invalid chunk table or bad FEC configuration"), \ + PARA_ERROR(AFS_SHORT_READ, "short read from afs socket"), \ #define CRYPT_ERRORS \ @@ -359,19 +365,21 @@ extern const char **para_errlist[]; PARA_ERROR(RSA, "RSA error"), \ PARA_ERROR(ENCRYPT, "encrypt error"), \ PARA_ERROR(DECRYPT, "decrypt error"), \ - PARA_ERROR(CHALLENGE, "failed to read challenge"), \ + PARA_ERROR(BLINDING, "failed to activate key blinding"), \ + PARA_ERROR(KEY_PERM, "unprotected private key"), \ #define COMMAND_ERRORS \ PARA_ERROR(COMMAND_SYNTAX, "syntax error in command"), \ - PARA_ERROR(AUTH, "did not receive auth request"), \ + PARA_ERROR(AUTH_REQUEST, "did not receive auth request"), \ PARA_ERROR(NO_AUDIO_FILE, "no audio file"), \ PARA_ERROR(BAD_CMD, "invalid command"), \ PARA_ERROR(PERM, "permission denied"), \ PARA_ERROR(LOCK, "lock error"), \ PARA_ERROR(SENDER_CMD, "command not supported by this sender"), \ PARA_ERROR(SERVER_CRASH, "para_server crashed -- can not live without it"), \ - PARA_ERROR(BAD_USER, "you don't exist. Go away."), \ + PARA_ERROR(BAD_USER, "auth request for invalid user"), \ + PARA_ERROR(BAD_AUTH, "authentication failure"), \ #define DCCP_RECV_ERRORS \ @@ -380,14 +388,13 @@ extern const char **para_errlist[]; #define FD_ERRORS \ PARA_ERROR(FGETS, "fgets error"), \ + PARA_ERROR(EOF, "end of file"), \ + PARA_ERROR(READ_PATTERN, "did not read expected pattern"), \ #define WRITE_ERRORS \ PARA_ERROR(WRITE_SYNTAX, "para_write syntax error"), \ PARA_ERROR(NO_WAV_HEADER, "wave header not found"), \ - PARA_ERROR(WAV_HEADER_SUCCESS, "successfully read wave header"), \ - PARA_ERROR(NO_DELAY, "no initial delay"), \ - PARA_ERROR(DELAY_TIMEOUT, "initial delay timeout"), \ #define ALSA_WRITE_ERRORS \ @@ -405,13 +412,9 @@ extern const char **para_errlist[]; PARA_ERROR(SET_RATE, "snd_pcm_hw_params_set_rate_near failed"), \ PARA_ERROR(START_THRESHOLD, "snd_pcm_sw_params_set_start_threshold() failed"), \ PARA_ERROR(STOP_THRESHOLD, "snd_pcm_sw_params_set_stop_threshold() failed"), \ + PARA_ERROR(ALSA_EOF, "alsa: end of file"), \ -#define FILE_WRITE_ERRORS \ - PARA_ERROR(FW_WRITE, "file writer write error"), \ - PARA_ERROR(FW_OPEN, "file writer: can not open output file"), \ - PARA_ERROR(FW_NO_FILE, "task started without open file"), \ - #define WRITE_COMMON_ERRORS \ PARA_ERROR(WRITE_COMMON_SYNTAX, "syntax error in write option"), \ @@ -420,7 +423,6 @@ extern const char **para_errlist[]; #define AACDEC_FILTER_ERRORS \ PARA_ERROR(AACDEC_INIT, "failed to init aac decoder"), \ PARA_ERROR(AAC_DECODE, "aac decode error"), \ - PARA_ERROR(AAC_OVERRUN, "aac output buffer overrun"), \ #define CHUNK_QUEUE_ERRORS \ @@ -475,12 +477,23 @@ extern const char **para_errlist[]; */ #define SYSTEM_ERROR_BIT 30 +/** + * Like the SYSTEM_ERROR_BIT, but indicates an error from the osl library. + */ +#define OSL_ERROR_BIT 29 + /** Check whether the system error bit is set. */ #define IS_SYSTEM_ERROR(num) (!!((num) & (1 << SYSTEM_ERROR_BIT))) +/** Check whether the osl error bit is set. */ +#define IS_OSL_ERROR(num) (!!((num) & (1 << OSL_ERROR_BIT))) + /** Set the system error bit for the given number. */ #define ERRNO_TO_PARA_ERROR(num) ((num) | (1 << SYSTEM_ERROR_BIT)) +/** Set the osl error bit for the given number. */ +#define OSL_ERRNO_TO_PARA_ERROR(num) ((num) | (1 << OSL_ERROR_BIT)) + /** Check whether a given number is a system error number. * * \param num The value to be checked. @@ -505,11 +518,33 @@ _static_inline_ int is_errno(int num, int _errno) _static_inline_ const char *para_strerror(int num) { assert(num > 0); +#ifdef _OSL_H + if (IS_OSL_ERROR(num)) + return osl_strerror(num & ((1 << OSL_ERROR_BIT) - 1)); +#endif if (IS_SYSTEM_ERROR(num)) - return strerror((num) & ((1 << SYSTEM_ERROR_BIT) - 1)); - else - return para_errlist[ERRNUM_TO_SS(num)][ERRNUM_TO_INDEX(num)]; + return strerror(num & ((1 << SYSTEM_ERROR_BIT) - 1)); + return para_errlist[ERRNUM_TO_SS(num)][ERRNUM_TO_INDEX(num)]; } + +/** + * Wrapper for osl library calls. + * + * \param ret The return value of an osl library function. + * + * This should be used for all calls to osl functions that return an osl error + * code. It changes the return value appropriately so that it can be used for + * printing the correct error message vi para_strerror(). + * + * \return \a ret if \a ret >= 0, a paraslash error code otherwise. + */ +_static_inline_ int osl(int ret) +{ + if (ret >= 0) + return ret; + return -OSL_ERRNO_TO_PARA_ERROR(-ret); +} + /** * Define the error list for one subsystem. # diff --git a/error2.pl b/error2.pl new file mode 100755 index 00000000..78ff2c56 --- /dev/null +++ b/error2.pl @@ -0,0 +1,58 @@ +#!/usr/bin/env perl + +use warnings; +use strict; + +my %matrix; +my @executables; +my %objects; + +sub make_matrix +{ + my ($line, $e, @fields, $field); + + while (defined($line = <>)) { + chomp($line); + if ($line =~ "^ *\$") { + next; + } + @fields = split(" ", $line); + while (defined(($field = shift(@fields)))) { + if ($field =~ ":\$") { + $field =~ s/://; + $e = $field; + push(@executables, $e); + next; + } + $matrix{$e . ">" . $field} = 1; + $objects{$field} = 1; + } + } +} + +sub print_safe_objects +{ + my @objs = keys(%objects); + my ($o1, $o2, $e); + + foreach $o1 (@objs) { + print("#ifdef MAIN_INPUT_FILE_IS_$o1\n"); + O2: foreach $o2 (@objs) { + foreach $e (@executables) { + if (!defined($matrix{$e . ">" . $o1})) { + next; + } + if (defined($matrix{$e . ">" . $o2})) { + next; + } + next O2; + } + $_ = $o2; + tr/a-z/A-Z/; + printf("SS_ENUM(%s);\n", $_); + } + print("#endif\n"); + } +} +make_matrix; +print_safe_objects; diff --git a/exec.c b/exec.c index 86a483cf..b11b217b 100644 --- a/exec.c +++ b/exec.c @@ -1,16 +1,19 @@ /* - * Copyright (C) 2003-2009 Andre Noll + * Copyright (C) 2003-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file exec.c Helper functions for spawning new processes. */ + +#include #include + #include "para.h" #include "close_on_fork.h" #include "error.h" -#include "string.h" #include "fd.h" +#include "string.h" /** * Spawn a new process and redirect fd 0, 1, and 2. @@ -137,11 +140,11 @@ int para_exec_cmdline_pid(pid_t *pid, const char *cmdline, int *fds) { int ret; char **argv; - char *tmp = para_strdup(cmdline); - split_args(tmp, &argv, " \t"); + ret = create_argv(cmdline, " \t", &argv); + if (ret < 0) + return ret; ret = para_exec(pid, argv[0], argv, fds); - free(argv); - free(tmp); + free_argv(argv); return ret; } diff --git a/fade.c b/fade.c index 78903e5f..8eeb79e7 100644 --- a/fade.c +++ b/fade.c @@ -1,18 +1,14 @@ /* - * Copyright (C) 1998-2009 Andre Noll + * Copyright (C) 1998-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ -/** \file fade.c A volume fader and alarm clock for linux. */ +/** \file fade.c A volume fader and alarm clock for OSS. */ +#include #include #include - -#include "fade.cmdline.h" -#include "para.h" -#include "fd.h" - #include #include #include /* EXIT_SUCCESS */ @@ -21,12 +17,15 @@ #include #include #include + +#include "fade.cmdline.h" +#include "para.h" +#include "fd.h" #include "string.h" #include "error.h" - INIT_FADE_ERRLISTS; -struct fade_args_info conf; +static struct fade_args_info conf; __printf_2_3 void para_log(__a_unused int ll, const char *fmt, ...) { @@ -43,10 +42,11 @@ __printf_2_3 void para_log(__a_unused int ll, const char *fmt, ...) } /* - * open mixer device + * Open the mixer device. */ static int open_mixer(void) { + PARA_INFO_LOG("opening %s\n", conf.mixer_device_arg); return para_open(conf.mixer_device_arg, O_RDWR, 42); } @@ -131,7 +131,7 @@ static void fixup_mixer_channel_arg(void) /* * Open mixer, get volume, fade to new_vol in secs seconds and - * close mixer + * close mixer. */ static int fade(int new_vol, int fade_time) { @@ -217,14 +217,14 @@ static int sweet_dreams(void) unsigned int delay; struct tm *tm; int ret, min = conf.wake_min_arg; - char *fa_mode = conf.fa_mode_arg; - char *wake_mode = conf.wake_mode_arg; - char *sleep_mode = conf.sleep_mode_arg; - int wf = conf.wake_fade_arg; - int sf = conf.fa_fade_arg; - int wv = conf.wake_vol_arg; - int sv = conf.fa_vol_arg; - int iv = conf.sleep_ivol_arg; + char *fo_mood = conf.fo_mood_arg; + char *fi_mood = conf.fi_mood_arg; + char *sleep_mood = conf.sleep_mood_arg; + int fit = conf.fi_time_arg; + int fot = conf.fo_time_arg; + int fiv = conf.fi_vol_arg; + int fov = conf.fo_vol_arg; + int iv = conf.ivol_arg; /* calculate wake time */ time(&t1); @@ -247,38 +247,38 @@ static int sweet_dreams(void) PARA_INFO_LOG("waketime: %s", asctime(tm)); client_cmd("stop"); sleep(1); - if (sf) { + if (fot) { PARA_INFO_LOG("initial volume: %d\n", iv); ret = open_and_set_mixer_channel(iv); if (ret < 0) return ret; - change_afs_mode_and_play(fa_mode); - ret = fade(sv, sf); + change_afs_mode_and_play(fo_mood); + ret = fade(fov, fot); if (ret < 0) return ret; } else { - ret = open_and_set_mixer_channel(sf); + ret = open_and_set_mixer_channel(fov); if (ret < 0) return ret; } - if (conf.sleep_mode_given) - change_afs_mode_and_play(sleep_mode); + if (conf.sleep_mood_given) + change_afs_mode_and_play(sleep_mood); else client_cmd("stop"); - if (!wf) + if (!fit) return 1; for (;;) { time(&t1); - if (wake_time_epoch <= t1 + wf) + if (wake_time_epoch <= t1 + fit) break; - delay = wake_time_epoch - t1 - wf; + delay = wake_time_epoch - t1 - fit; PARA_INFO_LOG("sleeping %u seconds (%u:%02u)\n", delay, delay / 3600, (delay % 3600) / 60); sleep(delay); } - change_afs_mode_and_play(wake_mode); - ret = fade(wv, wf); + change_afs_mode_and_play(fi_mood); + ret = fade(fiv, fit); PARA_INFO_LOG("fade complete, returning\n"); return ret; } @@ -288,20 +288,20 @@ static int snooze(void) int ret; unsigned sleep_time; - if (conf.snooze_time_arg <= 0) + if (conf.so_time_arg <= 0) return 1; - sleep_time = conf.snooze_time_arg; - if (open_and_get_mixer_channel() < conf.snooze_out_vol_arg) - ret = open_and_set_mixer_channel(conf.snooze_out_vol_arg); + sleep_time = conf.so_time_arg; + if (open_and_get_mixer_channel() < conf.so_vol_arg) + ret = open_and_set_mixer_channel(conf.so_vol_arg); else - ret = fade(conf.snooze_out_vol_arg, conf.snooze_out_fade_arg); + ret = fade(conf.so_vol_arg, conf.so_time_arg); if (ret < 0) return ret; client_cmd("pause"); PARA_NOTICE_LOG("%d seconds snooze time...\n", conf.snooze_time_arg); sleep(sleep_time); client_cmd("play"); - return fade(conf.snooze_in_vol_arg, conf.snooze_in_fade_arg); + return fade(conf.si_vol_arg, conf.si_time_arg); } static int configfile_exists(void) @@ -336,26 +336,24 @@ int main(int argc, char *argv[]) .override = 0, .initialize = 0, .check_required = 0, - .check_ambiguity = 0 + .check_ambiguity = 0, + .print_errors = 1 }; fade_cmdline_parser_config_file(conf.config_file_arg, &conf, ¶ms); } fixup_mixer_channel_arg(); - if (!strcmp(conf.mode_arg, "sleep")) { + switch (conf.mode_arg) { + case mode_arg_sleep: ret = sweet_dreams(); - goto out; - } - if (!strcmp(conf.mode_arg, "fade")) { + break; + case mode_arg_fade: ret = fade(conf.fade_vol_arg, conf.fade_time_arg); - goto out; - } - if (!strcmp(conf.mode_arg, "snooze")) { + break; + case mode_arg_snooze: ret = snooze(); - goto out; + break; } - ret = -E_FADE_SYNTAX; -out: if (ret < 0) PARA_EMERG_LOG("%s\n", para_strerror(-ret)); return ret < 0? EXIT_FAILURE : EXIT_SUCCESS; diff --git a/fd.c b/fd.c index 31eb6388..a0969e3e 100644 --- a/fd.c +++ b/fd.c @@ -1,19 +1,22 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file fd.c Helper functions for file descriptor handling. */ +#include #include #include #include #include -#include +#include #include "para.h" #include "error.h" +#include "string.h" +#include "fd.h" /** * Write a buffer to a file descriptor, re-write on short writes. @@ -46,10 +49,6 @@ int write_all(int fd, const char *buf, size_t *len) * \param fd The file descriptor. * \param buf the buffer to write. * \param len the number of bytes of \a buf. - * \param max_bytes_per_write Do not write more than that many bytes at once. - * - * If \a max_bytes_per_write is non-zero, do not send more than that many bytes - * per write(). * * EAGAIN is not considered an error condition. For example CCID3 has a * sending wait queue which fills up and is emptied asynchronously. The EAGAIN @@ -58,8 +57,7 @@ int write_all(int fd, const char *buf, size_t *len) * * \return Negative on errors, number of bytes written else. */ -int write_nonblock(int fd, const char *buf, size_t len, - size_t max_bytes_per_write) +int write_nonblock(int fd, const char *buf, size_t len) { size_t written = 0; int ret = 0; @@ -67,8 +65,6 @@ int write_nonblock(int fd, const char *buf, size_t len, while (written < len) { size_t num = len - written; - if (max_bytes_per_write && max_bytes_per_write < num) - num = max_bytes_per_write; ret = write(fd, buf + written, num); if (ret < 0 && errno == EAGAIN) return written; @@ -79,6 +75,147 @@ int write_nonblock(int fd, const char *buf, size_t len, return written; } +/** + * Read from a non-blocking file descriptor into multiple buffers. + * + * \param fd The file descriptor to read from. + * \param iov Scatter/gather array used in readv(). + * \param iovcnt Number of elements in \a iov. + * \param rfds An optional fd set pointer. + * \param num_bytes Result pointer. Contains the number of bytes read from \a fd. + * + * If \a rfds is not \p NULL and the (non-blocking) file descriptor \a fd is + * not set in \a rfds, this function returns early without doing anything. + * Otherwise The function tries to read up to \a sz bytes from \a fd. As for + * write_nonblock(), EAGAIN is not considered an error condition. However, EOF + * is. + * + * \return Zero or a negative error code. If the underlying call to readv(2) + * returned zero (indicating an end of file condition) or failed for some + * reason other than \p EAGAIN, a negative return value is returned. + * + * In any case, \a num_bytes contains the number of bytes that have been + * successfully read from \a fd (zero if the first readv() call failed with + * EAGAIN). Note that even if the function returns negative, some data might + * have been read before the error occurred. In this case \a num_bytes is + * positive. + * + * \sa \ref write_nonblock(), read(2), readv(2). + */ +int readv_nonblock(int fd, struct iovec *iov, int iovcnt, fd_set *rfds, + size_t *num_bytes) +{ + int ret, i, j; + + *num_bytes = 0; + /* + * Avoid a shortcoming of select(): Reads from a non-blocking fd might + * return EAGAIN even if FD_ISSET() returns true. However, FD_ISSET() + * returning false definitely means that no data can currently be read. + * This is the common case, so it is worth to avoid the overhead of the + * read() system call in this case. + */ + if (rfds && !FD_ISSET(fd, rfds)) + return 0; + + for (i = 0, j = 0; i < iovcnt;) { + + /* fix up the first iov */ + assert(j < iov[i].iov_len); + iov[i].iov_base += j; + iov[i].iov_len -= j; + ret = readv(fd, iov + i, iovcnt - i); + iov[i].iov_base -= j; + iov[i].iov_len += j; + + if (ret == 0) + return -E_EOF; + if (ret < 0) { + if (errno == EAGAIN) + return 0; + return -ERRNO_TO_PARA_ERROR(errno); + } + *num_bytes += ret; + while (ret > 0) { + if (ret < iov[i].iov_len - j) { + j += ret; + break; + } + ret -= iov[i].iov_len - j; + j = 0; + if (++i >= iovcnt) + break; + } + } + return 0; +} + +/** + * Read from a non-blocking file descriptor into a single buffer. + * + * \param fd The file descriptor to read from. + * \param buf The buffer to read data to. + * \param sz The size of \a buf. + * \param rfds \see \ref readv_nonblock(). + * \param num_bytes \see \ref readv_nonblock(). + * + * This is a simple wrapper for readv_nonblock() which uses an iovec with a single + * buffer. + * + * \return The return value of the underlying call to readv_nonblock(). + */ +int read_nonblock(int fd, void *buf, size_t sz, fd_set *rfds, size_t *num_bytes) +{ + struct iovec iov = {.iov_base = buf, .iov_len = sz}; + return readv_nonblock(fd, &iov, 1, rfds, num_bytes); +} + +/** + * Read a buffer and check its content for a pattern. + * + * \param fd The file descriptor to receive from. + * \param pattern The expected pattern. + * \param bufsize The size of the internal buffer. + * \param rfds Passed to read_nonblock(). + * + * This function tries to read at most \a bufsize bytes from the non-blocking + * file descriptor \a fd. If at least \p strlen(\a pattern) bytes have been + * received, the beginning of the received buffer is compared with \a pattern, + * ignoring case. + * + * \return Positive if \a pattern was received, negative on errors, zero if no data + * was available to read. + * + * \sa \ref read_nonblock(), \sa strncasecmp(3). + */ +int read_pattern(int fd, const char *pattern, size_t bufsize, fd_set *rfds) +{ + size_t n, len; + char *buf = para_malloc(bufsize + 1); + int ret = read_nonblock(fd, buf, bufsize, rfds, &n); + + buf[n] = '\0'; + if (ret < 0) + goto out; + ret = 0; + if (n == 0) + goto out; + ret = -E_READ_PATTERN; + len = strlen(pattern); + if (n < len) + goto out; + if (strncasecmp(buf, pattern, len) != 0) + goto out; + ret = 1; +out: + if (ret < 0) { + PARA_NOTICE_LOG("%s\n", para_strerror(-ret)); + PARA_NOTICE_LOG("recvd %zu bytes: %s\n", n, buf); + } + free(buf); + return ret; +} + /** * Check whether a file exists. * @@ -114,11 +251,10 @@ int file_exists(const char *fn) int para_select(int n, fd_set *readfds, fd_set *writefds, struct timeval *timeout_tv) { - int ret, err; - do { + int ret; + do ret = select(n, readfds, writefds, NULL, timeout_tv); - err = errno; - } while (ret < 0 && err == EINTR); + while (ret < 0 && errno == EINTR); if (ret < 0) return -ERRNO_TO_PARA_ERROR(errno); return ret; @@ -446,18 +582,14 @@ int para_munmap(void *start, size_t length) int write_ok(int fd) { - struct timeval tv = {0, 0}; + struct timeval tv; fd_set wfds; - int ret; -again: + FD_ZERO(&wfds); FD_SET(fd, &wfds); tv.tv_sec = 0; tv.tv_usec = 0; - ret = select(fd + 1, NULL, &wfds, NULL, &tv); - if (ret < 0 && errno == EINTR) - goto again; - return ret; + return para_select(fd + 1, NULL, &wfds, &tv); } /** @@ -480,3 +612,65 @@ void valid_fd_012(void) } } } + +/** + * Traverse the given directory recursively. + * + * \param dirname The directory to traverse. + * \param func The function to call for each entry. + * \param private_data Pointer to an arbitrary data structure. + * + * For each regular file under \a dirname, the supplied function \a func is + * called. The full path of the regular file and the \a private_data pointer + * are passed to \a func. Directories for which the calling process has no + * permissions to change to are silently ignored. + * + * \return Standard. + */ +int for_each_file_in_dir(const char *dirname, + int (*func)(const char *, void *), void *private_data) +{ + DIR *dir; + struct dirent *entry; + int cwd_fd, ret2, ret = para_opendir(dirname, &dir, &cwd_fd); + + if (ret < 0) + return ret == -ERRNO_TO_PARA_ERROR(EACCES)? 1 : ret; + /* scan cwd recursively */ + while ((entry = readdir(dir))) { + mode_t m; + char *tmp; + struct stat s; + + if (!strcmp(entry->d_name, ".")) + continue; + if (!strcmp(entry->d_name, "..")) + continue; + if (lstat(entry->d_name, &s) == -1) + continue; + m = s.st_mode; + if (!S_ISREG(m) && !S_ISDIR(m)) + continue; + tmp = make_message("%s/%s", dirname, entry->d_name); + if (!S_ISDIR(m)) { + ret = func(tmp, private_data); + free(tmp); + if (ret < 0) + goto out; + continue; + } + /* directory */ + ret = for_each_file_in_dir(tmp, func, private_data); + free(tmp); + if (ret < 0) + goto out; + } + ret = 1; +out: + closedir(dir); + ret2 = para_fchdir(cwd_fd); + if (ret2 < 0 && ret >= 0) + ret = ret2; + close(cwd_fd); + return ret; +} diff --git a/fd.h b/fd.h index 7c364120..436622f0 100644 --- a/fd.h +++ b/fd.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -26,5 +26,10 @@ int mmap_full_file(const char *filename, int open_mode, void **map, int para_munmap(void *start, size_t length); int write_ok(int fd); void valid_fd_012(void); -int write_nonblock(int fd, const char *buf, size_t len, - size_t max_bytes_per_write); +int readv_nonblock(int fd, struct iovec *iov, int iovcnt, fd_set *rfds, + size_t *num_bytes); +int read_nonblock(int fd, void *buf, size_t sz, fd_set *rfds, size_t *num_bytes); +int read_pattern(int fd, const char *pattern, size_t bufsize, fd_set *rfds); +int write_nonblock(int fd, const char *buf, size_t len); +int for_each_file_in_dir(const char *dirname, + int (*func)(const char *, void *), void *private_data); diff --git a/fec.c b/fec.c index e4cdbaf3..dc6e7520 100644 --- a/fec.c +++ b/fec.c @@ -33,6 +33,7 @@ * OF SUCH DAMAGE. */ +#include #include "para.h" #include "error.h" @@ -40,20 +41,36 @@ #include "string.h" #include "fec.h" -#define GF_BITS 8 /* code over GF(256) */ +/** Code over GF(256). */ +#define GF_BITS 8 +/** The largest number in GF(256) */ #define GF_SIZE ((1 << GF_BITS) - 1) /* * To speed up computations, we have tables for logarithm, exponent and inverse - * of a number. We use a table for multiplication as well (it takes 64K, no big - * deal even on a PDA, especially because it can be pre-initialized an put into - * a ROM!). The macro gf_mul(x,y) takes care of multiplications. + * of a number. + */ + +/** Index->poly form conversion table. */ +static unsigned char gf_exp[2 * GF_SIZE]; + +/** Poly->index form conversion table. */ +static int gf_log[GF_SIZE + 1]; + +/** Inverse of a field element. */ +static unsigned char inverse[GF_SIZE + 1]; + +/** + * The multiplication table. + * + * We use a table for multiplication as well. It takes 64K, no big deal even on + * a PDA, especially because it can be pre-initialized and put into a ROM. + * + * \sa \ref gf_mul. */ -static unsigned char gf_exp[2 * GF_SIZE]; /* index->poly form conversion table */ -static int gf_log[GF_SIZE + 1]; /* Poly->index form conversion table */ -static unsigned char inverse[GF_SIZE + 1]; /* inverse of field elem. */ static unsigned char gf_mul_table[GF_SIZE + 1][GF_SIZE + 1]; -/* Multiply two numbers. */ + +/** Multiply two GF numbers. */ #define gf_mul(x,y) gf_mul_table[x][y] /* Compute x % GF_SIZE without a slow divide. */ @@ -153,13 +170,15 @@ static void generate_gf(void) inverse[i] = gf_exp[GF_SIZE - gf_log[i]]; } +/** How often the loop is unrolled. */ +#define UNROLL 16 + /* * Compute dst[] = dst[] + c * src[] * * This is used often, so better optimize it! Currently the loop is unrolled 16 * times. The case c=0 is also optimized, whereas c=1 is not. */ -#define UNROLL 16 static void addmul(unsigned char *dst1, const unsigned char const *src1, unsigned char c, int sz) { @@ -210,6 +229,7 @@ static void matmul(unsigned char *a, unsigned char *b, unsigned char *c, } } +/** Swap two numbers. */ #define FEC_SWAP(a,b) {typeof(a) tmp = a; a = b; b = tmp;} /* diff --git a/fec.h b/fec.h index 1e28e8a4..d09b035d 100644 --- a/fec.h +++ b/fec.h @@ -33,8 +33,10 @@ * OF SUCH DAMAGE. */ -#define FEC_MAGIC 0xFECC0DEC +/** Each FEC slice contains a FEC header of this size. */ #define FEC_HEADER_SIZE 32 +/** The FEC header starts with this magic value. */ +#define FEC_MAGIC 0xFECC0DEC struct fec_parms; diff --git a/fecdec_filter.c b/fecdec_filter.c index 8cb17ad6..4f3ba950 100644 --- a/fecdec_filter.c +++ b/fecdec_filter.c @@ -1,17 +1,21 @@ /* - * Copyright (C) 2009 Andre Noll + * Copyright (C) 2009-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file fecdec_filter.c A filter that fec-decodes an audio stream. */ +#include +#include + #include #include "para.h" #include "error.h" #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "filter.h" #include "string.h" #include "portable_io.h" @@ -27,18 +31,13 @@ */ #define NUM_FEC_GROUPS 3 -/** Default size of the output buffer of the fecdec filter. */ -#define FECDEC_DEFAULT_OUTBUF_SIZE (3 * 1024) -/** Maximal size of the output buffer of the fecdec filter. */ -#define FECDEC_MAX_OUTBUF_SIZE (1024 * 1024) - /** Data read from the header of a slice. */ struct fec_header { /** Total number of slices in this group. */ uint8_t slices_per_group; /** Number of slices needed to start decoding. */ uint8_t data_slices_per_group; - /** Size of the ogg vorbis header (zero for mp3, aac). */ + /** Size of the ogg vorbis/wma header (zero for mp3, aac). */ uint32_t audio_header_size; /** Number of the FEC group this slice belongs to. */ uint32_t group_num; @@ -62,6 +61,8 @@ struct fecdec_group { struct fec_header h; /** How many slices received so far. */ int num_received_slices; + /** Bitmap of received slices. */ + uint8_t received_slices[32]; /** The size of the \a idx and the \a data arrays below. */ int num_slices; /** Array of indices of the received slices. */ @@ -82,6 +83,7 @@ struct private_fecdec_data { int have_header; /** Points to the first received group. */ struct fecdec_group *first_complete_group; + struct btr_pool *btrp; }; /** Iterate over all fecdec groups. */ @@ -102,18 +104,11 @@ static void clear_group(struct fecdec_group *fg) { int i; - for (i = 0; i < fg->num_slices; i++) { + for (i = 0; i < fg->num_slices; i++) free(fg->data[i]); - fg->data[i] = NULL; - fg->idx[i] = -1; - } free(fg->data); - fg->data = NULL; free(fg->idx); - fg->idx = NULL; - fg->num_slices = 0; - memset(&fg->h, 0, sizeof(struct fec_header)); - fg->num_received_slices = 0; + memset(fg, 0, sizeof(*fg)); } static int find_group(struct fec_header *h, @@ -213,29 +208,40 @@ success: return ret; } +static bool test_and_set_slice_bit(struct fecdec_group *fg, uint8_t slice_num) +{ + uint8_t *p = fg->received_slices + slice_num / 8, old = *p; + + *p |= 1 << (slice_num % 8); + return old == *p; +} + /* * returns 1 if slice was added, zero otherwise (because the group was already - * complete). In any case the number of received slices is being increased by - * one. + * complete or a slice has been received twice). */ static int add_slice(char *buf, struct fecdec_group *fg) { - int r, slice_num; + int r; + uint8_t slice_num = fg->h.slice_num; if (group_complete(fg)) { PARA_DEBUG_LOG("group %d complete, ignoring slice %d\n", - fg->h.group_num, fg->h.slice_num); - fg->num_received_slices++; + fg->h.group_num, slice_num); return 0; } - slice_num = fg->h.slice_num; if (fg->num_slices == 0) { fg->num_slices = fg->h.slices_per_group; fg->idx = para_malloc(fg->num_slices * sizeof(int)); - fg->data = para_malloc(fg->num_slices * sizeof(unsigned char *)); - memset(fg->data, 0, fg->num_slices * sizeof(unsigned char *)); + fg->data = para_calloc(fg->num_slices * sizeof(unsigned char *)); } r = fg->num_received_slices; + /* Check if we already have this slice. */ + if (test_and_set_slice_bit(fg, slice_num)) { + PARA_INFO_LOG("ignoring duplicate slice %d:%d\n", fg->h.group_num, + slice_num); + return 0; + } fg->idx[r] = slice_num; fg->data[r] = para_malloc(fg->h.slice_bytes); memcpy(fg->data[r], buf, fg->h.slice_bytes); @@ -243,10 +249,21 @@ static int add_slice(char *buf, struct fecdec_group *fg) return 1; } +/** + * The different states of a complete FEC group. + * + * Even if a FEC group has been received successfully, it probably can not be + * used right away because some streams (ogg, wma) need to receive an audio + * file header before decoding can start. + */ enum fec_group_usability { + /** Drop the group (because we did not receive the header yet). */ FEC_GROUP_UNUSABLE, + /** Use all data in the group. */ FEC_GROUP_USABLE, + /** Use the group, but drop its audio file header. */ FEC_GROUP_USABLE_SKIP_HEADER, + /** Use the group, including its header. */ FEC_GROUP_USABLE_WITH_HEADER }; @@ -275,6 +292,7 @@ static int decode_group(struct fecdec_group *fg, struct filter_node *fn) size_t written, need; struct private_fecdec_data *pfd = fn->private_data; enum fec_group_usability u = group_is_usable(fg, pfd); + char *buf = NULL, *p; if (u == FEC_GROUP_UNUSABLE) { PARA_INFO_LOG("dropping unusable group %d\n", fg->h.group_num); @@ -288,21 +306,17 @@ static int decode_group(struct fecdec_group *fg, struct filter_node *fn) pfd->have_header = 1; i = 0; if (u == FEC_GROUP_USABLE_SKIP_HEADER) { - i = ROUND_UP(fg->h.audio_header_size, fg->h.slice_bytes) - / fg->h.slice_bytes; + i = DIV_ROUND_UP(fg->h.audio_header_size, fg->h.slice_bytes); PARA_DEBUG_LOG("skipping %d header slices\n", i); } PARA_DEBUG_LOG("writing group %d (%d/%d decoded data bytes)\n", fg->h.group_num, fg->h.group_bytes, fg->h.data_slices_per_group * sb); - need = fn->loaded + (fg->h.data_slices_per_group - i)* sb; - if (need > fn->bufsize) { - fn->bufsize = PARA_MAX(fn->bufsize * 2, need); - if (fn->bufsize > FECDEC_MAX_OUTBUF_SIZE) - return -E_FECDEC_OVERRUN; - PARA_INFO_LOG("increasing fec buf to %zu\n", fn->bufsize); - fn->buf = para_realloc(fn->buf, fn->bufsize); - } + need = (fg->h.data_slices_per_group - i) * sb; + if (need > btr_pool_unused(pfd->btrp)) + return -E_FECDEC_OVERRUN; + btr_pool_get_buffer(pfd->btrp, &buf); + p = buf; if (u == FEC_GROUP_USABLE_WITH_HEADER) { PARA_INFO_LOG("writing audio file header\n"); written = 0; @@ -312,20 +326,20 @@ static int decode_group(struct fecdec_group *fg, struct filter_node *fn) break; if (sb + written > fg->h.audio_header_size) n = fg->h.audio_header_size - written; - memcpy(fn->buf + fn->loaded, fg->data[i], n); - fn->loaded += n; + btr_copy(fg->data[i], n, pfd->btrp, fn->btrn); written += n; } + p += written; } written = 0; for (; i < fg->h.data_slices_per_group; i++) { size_t n = sb; if (n + written > fg->h.group_bytes) n = fg->h.group_bytes - written; - memcpy(fn->buf + fn->loaded, fg->data[i], n); - fn->loaded += n; + btr_copy(fg->data[i], n, pfd->btrp, fn->btrn); written += n; } + p += written; return 0; } @@ -370,8 +384,10 @@ static int dispatch_slice(char *buf, size_t len, struct fec_header *h, int ret, k, n; struct private_fecdec_data *pfd = fn->private_data; - if (h->slice_bytes > len) /* can not use the thing, try to read more */ + if (h->slice_bytes > len) { /* can not use the thing, try to read more */ + fn->min_iqs = h->slice_bytes + FEC_HEADER_SIZE; return 0; + } ret = get_group(h, pfd, &fg); if (ret < 0) return ret; @@ -398,6 +414,7 @@ static int dispatch_slice(char *buf, size_t len, struct fec_header *h, ret = fec_new(k, n, &pfd->fec); if (ret < 0) return ret; + pfd->btrp = btr_pool_new("fecdec", 64 * 1024); /* decode and clear the first group */ ret = decode_group(pfd->first_complete_group, fn); if (ret < 0) @@ -411,58 +428,73 @@ decode: return 1; } -static ssize_t fecdec(char *buf, size_t len, struct filter_node *fn) +static void fecdec_close(struct filter_node *fn) +{ + struct private_fecdec_data *pfd = fn->private_data; + struct fecdec_group *fg; + + FOR_EACH_FECDEC_GROUP(fg, pfd) + clear_group(fg); + fec_free(pfd->fec); + btr_pool_free(pfd->btrp); + free(fn->private_data); + fn->private_data = NULL; +} + +static void fecdec_post_select(__a_unused struct sched *s, struct task *t) { + struct filter_node *fn = container_of(t, struct filter_node, task); + struct btr_node *btrn = fn->btrn; int ret; struct fec_header h; + char *buf; + size_t len; +next_buffer: + ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL); + if (ret <= 0) + goto out; + btr_merge(btrn, fn->min_iqs); + len = btr_next_buffer(btrn, &buf); ret = read_fec_header(buf, len, &h); if (ret <= 0) - return ret; + goto out; + ret = -E_BAD_SLICE_SIZE; if (!h.slice_bytes) - return -E_BAD_SLICE_SIZE; + goto out; + ret = -E_BAD_SLICE_NUM; if (h.slice_num > h.slices_per_group) - return -E_BAD_SLICE_NUM; + goto out; ret = dispatch_slice(buf + FEC_HEADER_SIZE, len - FEC_HEADER_SIZE, &h, fn); //PARA_INFO_LOG("ret: %d, len: %d, slice_bytes: %d\n", ret, len, h.slice_bytes); if (ret <= 0) - return ret; - return FEC_HEADER_SIZE + h.slice_bytes; -} - -static void fecdec_close(struct filter_node *fn) -{ - struct private_fecdec_data *pfd = fn->private_data; - struct fecdec_group *fg; - - FOR_EACH_FECDEC_GROUP(fg, pfd) - clear_group(fg); - free(fn->buf); - fn->buf = NULL; - fec_free(pfd->fec); - free(fn->private_data); - fn->private_data = NULL; + goto out; + btr_consume(btrn, FEC_HEADER_SIZE + h.slice_bytes); + goto next_buffer; +out: + t->error = ret; + if (ret < 0) + btr_remove_node(btrn); } static void fecdec_open(struct filter_node *fn) { struct private_fecdec_data *pfd; - fn->bufsize = FECDEC_DEFAULT_OUTBUF_SIZE; - fn->buf = para_malloc(fn->bufsize); pfd = para_calloc(sizeof(*pfd)); fn->private_data = pfd; - fn->loaded = 0; + fn->min_iqs = FEC_HEADER_SIZE; } /** * The init function of the fecdec filter. * - * \param f struct to initialize. + * \param f Struct to initialize. */ void fecdec_filter_init(struct filter *f) { - f->convert = fecdec; f->close = fecdec_close; f->open = fecdec_open; + f->pre_select = generic_filter_pre_select; + f->post_select = fecdec_post_select; } diff --git a/file_write.c b/file_write.c index e03cf8f6..8cafbb68 100644 --- a/file_write.c +++ b/file_write.c @@ -1,110 +1,152 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file file_write.c simple output plugin for testing purposes */ +#include #include #include +#include +#include #include "para.h" #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "write.h" +#include "write_common.h" #include "string.h" #include "fd.h" #include "file_write.cmdline.h" #include "error.h" -/** data specific to the file writer */ +/** Data specific to the file writer. */ struct private_file_write_data { - /** the file descriptor of the output file */ + /** The file descriptor of the output file. */ int fd; - /** non-zero if \a fd was added to the write fd set */ - int check_fd; }; -static int file_write_open(struct writer_node *wn) +/* + * Get a random filename. + * + * This is by no means a secure way to create temporary files in a hostile + * directory like \p /tmp. However, we use it only for creating temp files in + * ~/.paraslash, for which it is OK. Result must be freed by the caller. + */ +__must_check __malloc static char *random_filename(void) +{ + char *result, *home = para_homedir(); + struct timeval tv; + + gettimeofday(&tv, NULL); + srandom(tv.tv_usec); + result = make_message("%s/.paraslash/%08lu", home, + para_random(99999999)); + free(home); + return result; +} + +static int prepare_output_file(struct writer_node *wn) { - struct private_file_write_data *pfwd = para_calloc( - sizeof(struct private_file_write_data)); struct file_write_args_info *conf = wn->conf; char *filename; + int ret; + struct private_file_write_data *pfwd = para_calloc(sizeof(*pfwd)); + if (conf->filename_given) filename = conf->filename_arg; - else { - char *tmp = para_tmpname(), *home = para_homedir(); - filename = make_message("%s/.paraslash/%s", home, tmp); - free(home); - free(tmp); - } - wn->private_data = pfwd; - pfwd->fd = open(filename, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); + else + filename = random_filename(); + ret = para_open(filename, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR); if (!conf->filename_given) free(filename); - if (pfwd->fd >= 0) - return 1; + if (ret < 0) + goto out; + pfwd->fd = ret; + ret = mark_fd_blocking(pfwd->fd); + if (ret < 0) + goto out_close; + wn->private_data = pfwd; + return 1; +out_close: + close(pfwd->fd); +out: free(pfwd); - return -E_FW_OPEN; + return ret; } -static int file_write_pre_select(struct sched *s, struct writer_node *wn) +static void file_write_pre_select(struct sched *s, struct task *t) { + struct writer_node *wn = container_of(t, struct writer_node, task); struct private_file_write_data *pfwd = wn->private_data; - struct writer_node_group *wng = wn->wng; + int ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF); - pfwd->check_fd = 0; - if (pfwd->fd <= 0) - return -E_FW_NO_FILE; - if (!*wng->loaded) - return 1; + if (ret == 0) + return; + if (ret < 0 || !pfwd) + return sched_min_delay(s); para_fd_set(pfwd->fd, &s->wfds, &s->max_fileno); - pfwd->check_fd = 1; - return 1; } -static int file_write_post_select(struct sched *s, struct writer_node *wn) +static void file_write_close(struct writer_node *wn) +{ + struct private_file_write_data *pfwd = wn->private_data; + + if (!pfwd) + return; + close(pfwd->fd); + free(pfwd); +} + +static void file_write_post_select(__a_unused struct sched *s, + struct task *t) { + struct writer_node *wn = container_of(t, struct writer_node, task); struct private_file_write_data *pfwd = wn->private_data; - struct writer_node_group *wng = wn->wng; + struct btr_node *btrn = wn->btrn; int ret; + char *buf; + size_t bytes; - if (!pfwd->check_fd) - return 1; - if (*wng->loaded <= wn->written) - return 1; + t->error = 0; + ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_LEAF); + if (ret <= 0) + goto out; + if (!pfwd) { + ret = prepare_output_file(wn); + goto out; + } if (!FD_ISSET(pfwd->fd, &s->wfds)) - return 1; -// PARA_INFO_LOG("writing %zd\n", *wng->loaded); - ret = write(pfwd->fd, *wng->bufp + wn->written, - *wng->loaded - wn->written); + return; + bytes = btr_next_buffer(btrn, &buf); + assert(bytes > 0); + //PARA_INFO_LOG("writing %zu\n", bytes); + ret = write(pfwd->fd, buf, bytes); if (ret < 0) - return -E_FW_WRITE; - wn->written += ret; - return 1; + goto out; + btr_consume(btrn, ret); +out: + if (ret < 0) + btr_remove_node(btrn); + t->error = ret; } -static void file_write_close(struct writer_node *wn) +__malloc static void *file_write_parse_config_or_die(const char *options) { - struct private_file_write_data *pfwd = wn->private_data; - close(pfwd->fd); - free(pfwd); + struct file_write_args_info *conf = para_calloc(sizeof(*conf)); + + /* exits on errors */ + file_cmdline_parser_string(options, conf, "file_write"); + return conf; } -__malloc static void *file_write_parse_config(const char *options) +static void file_write_free_config(void *conf) { - struct file_write_args_info *conf - = para_calloc(sizeof(struct file_write_args_info)); - int ret = file_cmdline_parser_string(options, conf, "file_write"); - - PARA_INFO_LOG("conf->filename_given: %d\n", conf->filename_given); - if (!ret) - return conf; - free(conf); - return NULL; + file_cmdline_parser_free(conf); } /** the init function of the file writer */ @@ -113,10 +155,10 @@ void file_write_init(struct writer *w) struct file_write_args_info dummy; file_cmdline_parser_init(&dummy); - w->open = file_write_open; w->pre_select = file_write_pre_select; w->post_select = file_write_post_select; - w->parse_config = file_write_parse_config; + w->parse_config_or_die = file_write_parse_config_or_die; + w->free_config = file_write_free_config; w->close = file_write_close; w->shutdown = NULL; /* nothing to do */ w->help = (struct ggo_help) { diff --git a/filter.c b/filter.c index 98cef2fa..1d938c45 100644 --- a/filter.c +++ b/filter.c @@ -1,17 +1,20 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file filter.c The stand-alone filter program. */ -#include "para.h" +#include +#include +#include "para.h" #include "filter.cmdline.h" #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "filter.h" #include "string.h" #include "stdin.h" @@ -31,11 +34,6 @@ static struct stdin_task stdin_task_struct; /** pointer to the stdin task. */ static struct stdin_task *sit = &stdin_task_struct; -/** The task that filters the data. */ -static struct filter_chain filter_chain_struct; -/** Pointer to the filter chain. */ -static struct filter_chain *fc = &filter_chain_struct; - /** The task that writes converted data to stdout. */ static struct stdout_task stdout_task_struct; /** Pointer to the stdout task. */ @@ -47,65 +45,6 @@ static struct filter_args_info conf; static int loglevel; INIT_STDERR_LOGGING(loglevel); -static void open_filters(void) -{ - int i; - struct filter_node *fn; - - FOR_EACH_FILTER_NODE(fn, fc, i) { - struct filter *f = filters + fn->filter_num; - f->open(fn); - PARA_INFO_LOG("opened %s filter\n", f->name); - fc->outbufp = &fn->buf; - fc->out_loaded = &fn->loaded; - } -} - -static void free_filter_confs(void) -{ - int i; - struct filter_node *fn; - - FOR_EACH_FILTER_NODE(fn, fc, i) - free(fn->conf); -} - -static int init_filter_chain(void) -{ - int i, ret; - struct filter_node *fn; - - if (!conf.filter_given) - return -E_NO_FILTERS; - fc->num_filters = conf.filter_given; - fc->filter_nodes = para_calloc(fc->num_filters * sizeof(struct filter_node)); - fc->inbufp = &sit->buf; - fc->in_loaded = &sit->loaded; - fc->input_error = &sit->task.error; - fc->task.error = 0; - fc->output_error = &sot->task.error; - fc->task.post_select = filter_post_select; - sprintf(fc->task.status, "filter chain"); - - FOR_EACH_FILTER_NODE(fn, fc, i) { - char *fa = conf.filter_arg[i]; - fn = fc->filter_nodes + i; - ret = check_filter_arg(fa, &fn->conf); - if (ret < 0) - goto err; - fn->filter_num = ret; - fn->fc = fc; - INIT_LIST_HEAD(&fn->callbacks); - PARA_DEBUG_LOG("filter #%d: %s\n", i, filters[fn->filter_num].name); - } - open_filters(); - return 1; -err: - free_filter_confs(); - free(fc->filter_nodes); - return ret; -} - __noreturn static void print_help_and_die(void) { int d = conf.detailed_help_given; @@ -148,6 +87,8 @@ static int parse_config(int argc, char *argv[]) if (filter_cmdline_parser_config_file(cf, &conf, ¶ms)) return -E_FILTER_SYNTAX; } + if (!conf.filter_given) + return -E_NO_FILTERS; return 1; } @@ -165,37 +106,70 @@ static int parse_config(int argc, char *argv[]) */ int main(int argc, char *argv[]) { - int ret; static struct sched s; - - stdin_set_defaults(sit); - sit->buf = para_malloc(sit->bufsize), + int i, ret; + struct filter *f; + struct btr_node *parent; + struct filter_node **fns; filter_init(); ret = parse_config(argc, argv); if (ret < 0) goto out; - ret = init_filter_chain(); - if (ret < 0) - goto out; - sit->output_error = &fc->task.error; + sit->btrn = btr_new_node(&(struct btr_node_description) + EMBRACE(.name = "stdin")); + stdin_set_defaults(sit); + register_task(&sit->task); - stdout_set_defaults(sot); - sot->bufp = fc->outbufp; - sot->loaded = fc->out_loaded; - sot->input_error = &fc->task.error; + fns = para_malloc(conf.filter_given * sizeof(*fns)); + for (i = 0, parent = sit->btrn; i < conf.filter_given; i++) { + char *fa = conf.filter_arg[i]; + struct filter_node *fn; - register_task(&sit->task); + fn = fns[i] = para_calloc(sizeof(*fn)); + ret = check_filter_arg(fa, &fn->conf); + if (ret < 0) { + free(fn); + goto out_cleanup; + } + fn->filter_num = ret; + f = filters + fn->filter_num; + sprintf(fn->task.status, "%s", f->name); + PARA_DEBUG_LOG("filter #%d: %s\n", i, f->name); + fn->btrn = btr_new_node(&(struct btr_node_description) + EMBRACE(.name = f->name, .parent = parent, + .handler = f->execute, .context = fn)); + fn->task.pre_select = f->pre_select; + fn->task.post_select = f->post_select; + f->open(fn); + register_task(&fn->task); + parent = fn->btrn; + } + sot->btrn = btr_new_node(&(struct btr_node_description) + EMBRACE(.name = "stdout", .parent = parent)); + stdout_set_defaults(sot); register_task(&sot->task); - register_task(&fc->task); + s.default_timeout.tv_sec = 1; s.default_timeout.tv_usec = 0; + btr_log_tree(sit->btrn, LL_INFO); ret = schedule(&s); - free_filter_confs(); - close_filters(fc); +out_cleanup: + for (i--; i >= 0; i--) { + struct filter_node *fn = fns[i]; + + f = filters + fn->filter_num; + if (f->close) + f->close(fn); + btr_free_node(fn->btrn); + free(fn->conf); + free(fn); + } + free(fns); + btr_free_node(sit->btrn); + btr_free_node(sot->btrn); out: - free(sit->buf); if (ret < 0) PARA_EMERG_LOG("%s\n", para_strerror(-ret)); - return ret < 0? EXIT_FAILURE : EXIT_SUCCESS; + exit(ret < 0? EXIT_FAILURE : EXIT_SUCCESS); } diff --git a/filter.h b/filter.h index b877b649..0808059d 100644 --- a/filter.h +++ b/filter.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -15,123 +15,20 @@ enum filter_enum {FILTER_ENUM}; struct filter_node { /** The number in the array of available filters. */ unsigned filter_num; - /** The filter chain this filter node belongs to. */ - struct filter_chain *fc; /** * Each filter may store any filter-specific information about the particular * instance of the filter here. */ void *private_data; - /** The output buffer. */ - char *buf; - /** The size of the output buffer. */ - size_t bufsize; - /** The number of bytes currently loaded in \a buf. */ - size_t loaded; /** The list of registered callbacks. */ struct list_head callbacks; /** A pointer to the configuration of this instance. */ void *conf; -}; - -/** Describes one running instance of a chain of filters */ -struct filter_chain { - /** The length of the filter chain. */ - unsigned int num_filters; - /** - * The number of channels of the current stream. - * - * Set by the decoding filter. - */ - unsigned int channels; - /** - * Current sample rate in Hz. - * - * Set by the decoding filter. - */ - unsigned int samplerate; - /** The list containing all filter nodes in this filter chain. */ - struct filter_node *filter_nodes; - /** - * The input buffer of the filter chain. - * - * This is set to point to the output buffer of the receiving application (the - * buffer used to read from stdin for para_filter; the output buffer of the - * current receiver for para_audiod). - */ - char **inbufp; - /** - * The output buffer of the filter chain. - * - * Points to the output buffer of the last filter in the filter chain. - */ - char **outbufp; - /** Contains the number of bytes loaded in the input buffer. */ - size_t *in_loaded; - /** Contains the number of bytes loaded in the output buffer. */ - size_t *out_loaded; - /** Pointer to the error variable of the receiving application. */ - int *input_error; - /** Pointer to the error variable of the writing application. */ - int *output_error; - /** The task associated with the filter chain. */ + struct btr_node *btrn; struct task task; + size_t min_iqs; }; -#define FOR_EACH_FILTER_NODE(fn, fc, i) for (i = 0; i < (fc)->num_filters \ - && (fn = (fc)->filter_nodes + i); i++) - - -/** - * Used to manage grab clients. - * - * An application using paraslash's filter subsystem may register any number of - * callbacks for each filter_node. It is possible to attach a filter callback - * while the filter is running. This is used for stream grabbing in - * para_audiod: Whenever a client sends the 'grab' command, para_audiod adds a - * filter callback to the list of callbacks for the filter node specified in - * the grab command. - */ -struct filter_callback { - /** All callbacks are organized in a doubly linked list. */ - struct list_head node; - /** - * Private data. - * - * May be initialized by the application before registering the callback. This - * pointer is not used by the filter subsystem. It is provided for use within - * the input/output/close callback functions. - */ - void *data; - /** - * The input callback. - * - * In not \p NULL, the filter subsystem calls this function whenever the filter - * consumed some or all of its input buffer. A pointer to the buffer of consumed - * data, its length and a pointer to the own \a filter_callback structure are passed - * to \a input_cb. The input callback is expected to return a negative value on errors. - */ - int (*input_cb)(char *buf, size_t len, struct filter_callback *fc); - /** - * The output callback. - * - * If not NULL, this is called whenever the filter produces output. A pointer - * to the output data, its length and a pointer to the own \a filter_callback - * structure are passed to \a output_cb. Like the input callback, the output - * callback is expected to return a negative value on errors. - */ - int (*output_cb)(char *buf, size_t len, struct filter_callback *fc); - /** - * The callback close function. - * - * This gets called whenever the input/output callback returned an error, or if - * the filter chain is going to be destroyed, e.g. because the end of the - * stream was encountered. It is assumed to succeed. - */ - void (*close)(struct filter_callback *fc); -}; - - /** * The structure associated with a paraslash filter. * @@ -163,24 +60,12 @@ struct filter { * of \a fn suitably. The open function is assumed to succeed. */ void (*open)(struct filter_node *fn); - /** - * Convert (filter) the given data. - * - * Pointer to the converting function of the filter. It should convert the - * given input buffer \a inbuf which is of length \a len to the previously - * reserved output buffer of \a fn. On success, it must return the number of - * bytes it consumed from \a inbuf. On errors, a negative number indicating the - * kind of the error must be returned. - * - * A zero return value just means that nothing was converted (probably because - * the input buffer was too small). This is not interpreted as an error. - */ - ssize_t (*convert)(char *inbuf, size_t len, struct filter_node *fn); /** * Close one instance of this filter. * * Free all resources of associated with \a fn that were previously allocated - * by the open() function. + * by the open() function. It's OK to leave this alone if the filter does not + * need any cleanups. */ void (*close)(struct filter_node *fn); /** @@ -196,16 +81,47 @@ struct filter { * argv. On failure, a negative paraslash error code must be returned. */ int (*parse_config)(int argc, char **argv, void **config); + /** + * Deallocate the memory for the configuration. + * + * This is called to free whatever ->parse_config() has allocated. + */ + void (*free_config)(void *conf); /** The help texts for this filter. */ struct ggo_help help; + /** + * Set scheduler timeout and add file descriptors to fd sets. + * + * This function is used to control the timeout value for select. It + * only allowed to decrease the current value. The second purpose of + * this function is to set file descriptors to be watched by the + * subsequent select call to the two fd sets. + */ + void (*pre_select)(struct sched *s, struct task *t); + /** + * Convert (filter) the given data. + * + * Pointer to the converting function of the filter. On errors, the + * post_select function is supposed to set t->error to a (negative) + * error code. + */ + void (*post_select)(struct sched *s, struct task *t); + /** + * Answer a buffer tree query. + * + * This optional function pointer is used for inter node communications + * of the buffer tree nodes. See \ref btr_command_handler for details. + */ + btr_command_handler execute; }; -void close_filters(struct filter_chain *fc); void filter_init(void); int check_filter_arg(char *filter_arg, void **conf); -void filter_post_select(__a_unused struct sched *s, struct task *t); void print_filter_helps(int detailed); +void generic_filter_pre_select(struct sched *s, struct task *t); +int decoder_execute(const char *cmd, unsigned sample_rate, unsigned channels, + char **result); static inline void write_int16_host_endian(char *buf, int val) { diff --git a/filter_common.c b/filter_common.c index 2fa774c0..269c4d6d 100644 --- a/filter_common.c +++ b/filter_common.c @@ -1,19 +1,22 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file filter_common.c Common helper functions for filter input/output. */ +#include #include #include +#include #include "para.h" #include "list.h" #include "sched.h" #include "fd.h" #include "ggo.h" +#include "buffer_tree.h" #include "filter.h" #include "error.h" #include "string.h" @@ -33,153 +36,6 @@ void filter_init(void) filters[i].init(filters + i); } -/** - * Close and destroy a filter callback. - * - * \param fcb The filter callback to close. - * - * This removes \a fcb from the list of filter callbacks and calls - * the close callback associated with \a fcb. - */ -static void close_filter_callback(struct filter_callback *fcb) -{ - PARA_NOTICE_LOG("closing filter_callback %p, data: %p\n", fcb, fcb->data); - list_del(&fcb->node); - fcb->close(fcb); -} - -/** - * Close all callbacks of a filter node. - * - * \param fn The filter node which contains the filter callbacks to be closed. - * - * Call close_filter_callback() for each entry in the filter callback list - * of \a fn. - */ -static void close_callbacks(struct filter_node *fn) -{ - struct filter_callback *fcb, *tmp; - - list_for_each_entry_safe(fcb, tmp, &fn->callbacks, node) { - PARA_INFO_LOG("closing %s filter callback\n", - filters[fn->filter_num].name); - close_filter_callback(fcb); - } -} - -static void call_callbacks(struct filter_node *fn, char *inbuf, size_t inlen, - char *outbuf, size_t outlen) -{ - struct filter_callback *fcb, *tmp; - list_for_each_entry_safe(fcb, tmp, &fn->callbacks, node) { - int ret; - if (inlen && fcb->input_cb) { - ret = fcb->input_cb(inbuf, inlen, fcb); - if (ret < 0) { - close_filter_callback(fcb); - continue; - } - } - if (!outlen || !fcb->output_cb) - continue; - ret = fcb->output_cb(outbuf, outlen, fcb); - if (ret < 0) - close_filter_callback(fcb); - } -} - -/** - * Call the convert function of each filter. - * - * \param s Unused. - * \param t The task identifying the filter chain. - * - * This is the core function of the filter subsystem. It loops over the list of - * filter nodes determined by \a t and calls the filter's convert function if - * there is input available for the filter node in question. If the convert - * function consumed some or all of its input data, all registered input - * callbacks are called. Similarly, if a convert function produced output, all - * registered output callbacks get called. - * - * On errors a (negative) error code is stored in t->error. - * - * \sa filter_node, filter#convert, filter_callback. - */ -void filter_post_select(__a_unused struct sched *s, struct task *t) -{ - struct filter_chain *fc = container_of(t, struct filter_chain, task); - struct filter_node *fn; - char *ib; - size_t *loaded; - int i, conv, conv_total = 0; - - if (fc->output_error && *fc->output_error < 0) { - t->error = *fc->output_error; - return; - } -again: - ib = *fc->inbufp; - loaded = fc->in_loaded; - conv = 0; - FOR_EACH_FILTER_NODE(fn, fc, i) { - struct filter *f = filters + fn->filter_num; - if (fn->loaded < fn->bufsize) { - size_t size, old_fn_loaded = fn->loaded; - t->error = f->convert(ib, *loaded, fn); - if (t->error < 0) - return; - size = t->error; - call_callbacks(fn, ib, size, fn->buf + old_fn_loaded, - fn->loaded - old_fn_loaded); - *loaded -= size; - conv += size + fn->loaded - old_fn_loaded; - if (*loaded && size) - memmove(ib, ib + size, *loaded); - } - ib = fn->buf; - loaded = &fn->loaded; - } - conv_total += conv; - if (conv) - goto again; - if (*fc->input_error >= 0) - return; - if (*fc->out_loaded) - return; - if (*fc->in_loaded && conv_total) - return; - t->error = -E_FC_EOF; -} - -/** - * Close all filter nodes and their callbacks. - * - * \param fc The filter chain to close. - * - * For each filter node determined by \a fc, call the close function of each - * registered filter callback as well as the close function of the - * corresponding filter. Free all resources and destroy all callback lists and - * the filter node list. - * - * \sa filter::close, filter_callback::close - */ -void close_filters(struct filter_chain *fc) -{ - struct filter_node *fn; - int i; - - if (!fc) - return; - PARA_NOTICE_LOG("closing filter chain %p\n", fc); - FOR_EACH_FILTER_NODE(fn, fc, i) { - struct filter *f = filters + fn->filter_num; - close_callbacks(fn); - PARA_INFO_LOG("closing %s filter\n", f->name); - f->close(fn); - } - free(fc->filter_nodes); -} - /* * If the filter has a command line parser and options is not NULL, run it. * Returns filter_num on success, negative on errors @@ -195,15 +51,18 @@ static int parse_filter_args(int filter_num, char *options, void **conf) if (!f->parse_config) return strlen(options)? -E_BAD_FILTER_OPTIONS : filter_num; // PARA_DEBUG_LOG("options: %s\n", options); - argc = split_args(options, &argv, " \t"); -// PARA_DEBUG_LOG("argc = %d, argv[0]: %s\n", argc, argv[0]); + argc = create_argv(options, " \t", &argv); + if (argc < 0) + return -E_BAD_FILTER_OPTIONS; + PARA_DEBUG_LOG("argc = %d, argv[0]: %s\n", argc, argv[0]); for (i = argc - 1; i >= 0; i--) argv[i + 1] = argv[i]; argv[0] = para_strdup(f->name); - argc += 1; + argc++; ret = f->parse_config(argc, argv, conf); - free(argv[0]); - free(argv); + free(argv[argc - 1]); + argv[argc - 1] = NULL; + free_argv(argv); return ret < 0? ret : filter_num; } @@ -267,5 +126,53 @@ void print_filter_helps(int detailed) printf_or_die("Options for %s:\n", f->name); ggo_print_help(&f->help, detailed); } +} + +void generic_filter_pre_select(struct sched *s, struct task *t) +{ + struct filter_node *fn = container_of(t, struct filter_node, task); + t->error = 0; + if (btr_node_status(fn->btrn, fn->min_iqs, BTR_NT_INTERNAL) != 0) + sched_min_delay(s); +} + +#ifdef WORDS_BIGENDIAN +#define DECODER_SAMPLE_FORMAT SF_S16_BE +#else +#define DECODER_SAMPLE_FORMAT SF_S16_LE +#endif + +/** + * Execute a btr command for a decoder. + * + * The buffer tree nodes of the writers ask the parent nodes about sample_rate, + * channels count and sample format. This function is called by all decoders to + * answer these queries. + * + * \param cmd The command to be executed by the child node. + * \param sample_rate Known to the decoder. + * \param channels Known to the decoder. + * \param result Ascii representation on the answer is stored here. + */ +int decoder_execute(const char *cmd, unsigned sample_rate, unsigned channels, + char **result) +{ + if (!strcmp(cmd, "sample_rate")) { + if (sample_rate == 0) + return -E_BTR_NAVAIL; + *result = make_message("%u", sample_rate); + return 1; + } + if (!strcmp(cmd, "channels")) { + if (channels == 0) + return -E_BTR_NAVAIL; + *result = make_message("%u", channels); + return 1; + } + if (!strcmp(cmd, "sample_format")) { + *result = make_message("%u", DECODER_SAMPLE_FORMAT); + return 1; + } + return -ERRNO_TO_PARA_ERROR(ENOTSUP); } diff --git a/fsck.c b/fsck.c deleted file mode 100644 index 3b605c2d..00000000 --- a/fsck.c +++ /dev/null @@ -1,989 +0,0 @@ -/* - * Copyright (C) 1997-2009 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ - -/** \file fsck.c The program used to check an osl table. */ - - -#include -#include - -#include "para.h" -#include "fd.h" -#include "error.h" -#include "osl_core.h" -#include "fsck.cmdline.h" - -static struct fsck_args_info conf; - -INIT_FSCK_ERRLISTS; - -static int loglevel; -INIT_STDERR_LOGGING(loglevel); - -/* taken from git */ -signed char hexval_table[256] = { - -1, -1, -1, -1, -1, -1, -1, -1, /* 00-07 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 08-0f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 10-17 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 18-1f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 20-27 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 28-2f */ - 0, 1, 2, 3, 4, 5, 6, 7, /* 30-37 */ - 8, 9, -1, -1, -1, -1, -1, -1, /* 38-3f */ - -1, 10, 11, 12, 13, 14, 15, -1, /* 40-47 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 48-4f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 50-57 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 58-5f */ - -1, 10, 11, 12, 13, 14, 15, -1, /* 60-67 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 68-67 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 70-77 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 78-7f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 80-87 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 88-8f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 90-97 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* 98-9f */ - -1, -1, -1, -1, -1, -1, -1, -1, /* a0-a7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* a8-af */ - -1, -1, -1, -1, -1, -1, -1, -1, /* b0-b7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* b8-bf */ - -1, -1, -1, -1, -1, -1, -1, -1, /* c0-c7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* c8-cf */ - -1, -1, -1, -1, -1, -1, -1, -1, /* d0-d7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* d8-df */ - -1, -1, -1, -1, -1, -1, -1, -1, /* e0-e7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* e8-ef */ - -1, -1, -1, -1, -1, -1, -1, -1, /* f0-f7 */ - -1, -1, -1, -1, -1, -1, -1, -1, /* f8-ff */ -}; - -int asc_to_hash(const char *asc_hash, int len, HASH_TYPE *hash) -{ - int i = 0; - const unsigned char *asc = (const unsigned char *) asc_hash; - - while (*asc && i++ < len) { - unsigned int val = (hexval_table[asc[0]] << 4) | hexval_table[asc[1]]; - if (val & ~0xff) - return -1; - *hash++ = val; - asc += 2; - - } - return 1; -} - -/* - * check for object boundary violations - * - * test whether the range pointed to by the index entry for a given cell is - * contained in mapped data file. This should always be the case. Otherwise - * we are in real trouble. - */ -static int check_range(struct osl_table *t, uint32_t row_num, uint32_t col_num) -{ - char *index_entry; - struct osl_object obj; - struct osl_column *col; - int ret; - char *map_start, *obj_start; - - ret = get_cell_index(t, row_num, col_num, &index_entry); - if (ret < 0) - return ret; - ret = get_mapped_object(t, col_num, row_num, &obj); - if (ret < 0) - return ret; - col = t->columns + col_num; - obj_start = obj.data; - map_start = col->data_map.data; -// PARA_INFO_LOG("obj: %p..%p\n", obj_start, obj_start + obj.size); -// PARA_INFO_LOG("map: %p..%p\n", map_start, map_start + col->data_map.size); - if (obj_start < map_start || obj_start + obj.size > map_start + col->data_map.size) { - PARA_CRIT_LOG("range violation in row %u, col %u\n", row_num, - col_num); - return -E_RANGE_VIOLATION; - } - PARA_DEBUG_LOG("col %u: ok\n", col_num); - return 1; -} - -/* - * check all cells of the given table for boundary violations - */ -static int check_index_ranges(struct osl_table *t) -{ - int i, j, ret; - - PARA_INFO_LOG("checking for range violations in index\n"); - //PARA_DEBUG_LOG("%d rows. %d columns\n", t->num_rows, t->desc->num_columns); - t->num_invalid_rows = 0; - for (i = 0; i < t->num_rows; i++) { - if (row_is_invalid(t, i)) { - t->num_invalid_rows++; - continue; - } - for (j = 0; j < t->desc->num_columns; j++) { /* FXIME */ - const struct osl_column_description *cd = - get_column_description(t->desc, j); - if (cd->storage_type != OSL_MAPPED_STORAGE) - continue; - ret = check_range(t, i, j); - if (ret < 0) { - if (ret != -E_INVALID_OBJECT && - ret != -E_RANGE_VIOLATION) - goto err; - if (ret == -E_INVALID_OBJECT) { - PARA_CRIT_LOG("row %d, col %d maps to an " - "invalid object\n", i, j); - } - ret = mark_row_invalid(t, i); - if (ret < 0) - goto err; - t->num_invalid_rows++; - break; - } - } - - } - if (t->num_invalid_rows) - PARA_NOTICE_LOG("ranges OK. %d invalid row(s) detected\n", - t->num_invalid_rows); - else - PARA_INFO_LOG("no invalid rows, no range violations, good\n"); - return 1; -err: - return ret; -} - -static int move_index_entry(struct osl_table *t, uint32_t dest, uint32_t src) -{ - char *dest_ie, *src_ie; - int ret = get_row_index(t, dest, &dest_ie); - - if (ret < 0) - return ret; - ret = get_row_index(t, src, &src_ie); - if (ret < 0) - return ret; - PARA_INFO_LOG("moving entry #%u to position %u\n", src, dest); - memcpy(dest_ie, src_ie, t->row_index_size); - return 1; -} - -static int map_index(const struct osl_table_description *desc, struct osl_object *map) -{ - char *filename = index_filename(desc); - int ret; - - ret = mmap_full_file(filename, O_RDWR, &map->data, &map->size, NULL); - PARA_DEBUG_LOG("mapping index %s: ret: %d, size: %zu\n", filename, ret, map->size); - free(filename); - return ret; -} - -static int prune_invalid_rows_from_index(struct osl_table *t) -{ - uint32_t top = 0, bottom; - char *filename; - int ret; - - if (!t->num_invalid_rows) { - PARA_INFO_LOG("all rows are valid, good\n"); - return 1; - } - PARA_NOTICE_LOG("deleting %u invalid row(s) (%d bytes) from index\n", - t->num_invalid_rows, t->row_index_size * t->num_invalid_rows); - bottom = t->num_rows - 1; - while (top < bottom) { - if (!row_is_invalid(t, top)) { - top++; - continue; - } - while (bottom > top) { - if (row_is_invalid(t, bottom)) { - bottom--; - continue; - } - /* move bottom index entry to top */ - move_index_entry(t, top, bottom); - bottom--; - top++; - break; - } - } - PARA_DEBUG_LOG("unmapping index\n"); - para_munmap(t->index_map.data, t->index_map.size); - filename = index_filename(t->desc); - ret = para_truncate(filename, t->row_index_size - * t->num_invalid_rows); - free(filename); - if (ret < 0) - return ret; - ret = map_index(t->desc, &t->index_map); - if (ret < 0) - return ret; - t->num_rows = table_num_rows(t); - return 1; -} - -static int check_for_invalid_objects(struct osl_table *t, uint32_t **lost_bytes) -{ - int i, j, ret; - const struct osl_column_description *cd; - uint32_t *loss = para_malloc(sizeof(uint32_t) * t->desc->num_columns); - - PARA_INFO_LOG("looking for mapped objects not contained in index\n"); - /* first count used bytes */ - FOR_EACH_MAPPED_COLUMN(i, t, cd) { - loss[i] = t->columns[i].data_map.size; - for (j = 0; j < t->num_rows; j++) { - struct osl_object obj; - ret = get_mapped_object(t, i, j, &obj); - if (ret >= 0) { - loss[i] -= obj.size + 1; /* add one for header byte */ - continue; - } - if (ret != -E_INVALID_OBJECT) - goto err; - PARA_CRIT_LOG("row %d, col %d points to an invalid " - "mapped object, bad\n", j, i); - } - } - ret = 0; - FOR_EACH_MAPPED_COLUMN(i, t, cd) { - if (loss[i]) { - PARA_NOTICE_LOG("column %u contains %u lost bytes\n", - i, loss[i]); - ret = 1; - } - } - if (!ret) - PARA_INFO_LOG("all mapped objects are valid, good\n"); - *lost_bytes = loss; - return ret; -err: - free(loss); - return ret; -} - -/* prune_invalid_rows() must be run on the table before calling this */ -static int prune_mapped_column(struct osl_table *t, uint32_t col_num, int fd) -{ - int i, ret; - uint32_t written = 0; - struct osl_column *col = t->columns + col_num; - - PARA_INFO_LOG("pruning col %u\n", col_num); - for (i = 0; i < t->num_rows; i++) { - struct osl_object obj; - char *index_entry; - - PARA_DEBUG_LOG("checking row %u/%u\n", i, t->num_rows); - ret = get_mapped_object(t, col_num, i, &obj); - if (ret < 0) - return ret; - ret = para_write_all(fd, (char *)(obj.data) - 1, obj.size + 1); - if (ret < 0) - return ret; - written += obj.size + 1; - ret = get_row_index(t, i, &index_entry); - if (ret < 0) - return ret; - update_cell_index(index_entry, col, written, obj.size); - } - return 1; -} - -static int prune_objects(struct osl_table *t, uint32_t *lost_bytes) -{ - int i, ret; - const struct osl_column_description *cd; - char **col_filenames = para_calloc(t->desc->num_columns * sizeof(char *)); - char **new_col_filenames = para_calloc(t->desc->num_columns * sizeof(char *)); - char *idx_filename = index_filename(t->desc); - char *old_idx_filename = make_message("%s.bak", idx_filename); - int fd; - - PARA_NOTICE_LOG("removing unreferenced objects from data files\n"); - /* first make a copy of the index */ - ret = para_open(old_idx_filename, O_WRONLY | O_CREAT | O_EXCL, 0644); - if (ret < 0) - goto out_free; - fd = ret; - ret = para_write_all(fd, t->index_map.data, t->index_map.size); - close(fd); - if (ret < 0) - goto out_free; - FOR_EACH_MAPPED_COLUMN(i, t, cd) { - if (!lost_bytes[i]) - continue; - col_filenames[i] = column_filename(t, i); - new_col_filenames[i] = make_message("%s.fsck", col_filenames[i]); - ret = para_open(new_col_filenames[i], O_WRONLY | O_CREAT | O_EXCL, 0644); - if (ret < 0) - goto out_unlink_data; - fd = ret; - ret = prune_mapped_column(t, i, fd); - close(fd); - if (ret < 0) - goto out_unlink_data; - } - ret = unmap_table(t, OSL_MARK_CLEAN); - if (ret < 0) - goto out_unlink_data; - FOR_EACH_MAPPED_COLUMN(i, t, cd) { - if (!lost_bytes[i]) - continue; - ret = para_rename(new_col_filenames[i], col_filenames[i]); - if (ret < 0) { /* we're kinda screwed here */ - PARA_CRIT_LOG("rename of col %i failed: %s\n", i, - strerror(errno)); - goto out_free; - } - } - unlink(old_idx_filename); - ret = map_table(t, 0); - goto out_free; -out_unlink_data: - FOR_EACH_MAPPED_COLUMN(i, t, cd) - unlink(new_col_filenames[i]); -out_free: - free(old_idx_filename); - free(idx_filename); - FOR_EACH_MAPPED_COLUMN(i, t, cd) { - free(col_filenames[i]); - free(new_col_filenames[i]); - } - free(col_filenames); - free(new_col_filenames); - return ret; -} - -static struct osl_column_description hash_tree_table_cols[] = { - { - .storage_type = OSL_NO_STORAGE, - .storage_flags = OSL_RBTREE | OSL_FIXED_SIZE | OSL_UNIQUE, - .name = "hash", - .compare_function = uint32_compare, - .data_size = HASH_SIZE - }, -}; - -static const struct osl_table_description hash_tree_table_desc = { - .dir = "/", /* irrelevant */ - .name = "hash_tree", - .num_columns = 1, - .flags = 0, - .column_descriptions = hash_tree_table_cols -}; - -/** - * The hash_tree table contains all hashes of the disk storage name column. - * of each row. It is used for checking if a disk storage file has a reference - * in the table. - */ -static struct osl_table *hash_tree_table; -static HASH_TYPE *hashes; - -static int check_disk_storage_column(struct osl_table *t, int row_num, - int col_num, char *ds_name, unsigned *num_missing_objects) -{ - int ret; - struct stat statbuf; - char *path = disk_storage_path(t, col_num, ds_name); - unsigned dsnc = t->disk_storage_name_column; - struct osl_object obj; - - PARA_DEBUG_LOG("checking if %s is a regular file\n", path); - ret = stat(path, &statbuf); - if (ret < 0 && errno == ENOENT) { - struct osl_row *row; - (*num_missing_objects)++; - PARA_ERROR_LOG("row %d: object %s is missing\n", row_num, path); - PARA_NOTICE_LOG("trying to delete row %d\n", row_num); - ret = osl_get_row(t, dsnc, &obj, &row); - if (ret < 0) { - PARA_CRIT_LOG("unable to get row %d\n", row_num); - mark_row_invalid(t, row_num); - PARA_CRIT_LOG("Please re-run fsck\n"); - goto out; - } - ret = osl_del_row(t, row); - if (ret < 0) - goto out; - } -out: - free(path); - if (ret < 0) - return ret; - ret = -E_NOT_A_REGULAR_FILE; - if (!(S_IFREG & statbuf.st_mode)) - return ret; - return 1; -} - -static int check_disk_storage_presence(struct osl_table *t) -{ - int ret, i, j; - struct osl_object obj, hash_obj = {.size = HASH_SIZE}; - char *ds_name; - const struct osl_column_description *cd; - unsigned dsnc = t->disk_storage_name_column, missing_objects = 0; - - if (!t->num_rows) - return 1; - hashes = para_malloc(t->num_rows * HASH_SIZE); - PARA_INFO_LOG("looking for missing disk storage objects\n"); - for (i = 0; i < t->num_rows; i++) { - if (row_is_invalid(t, i)) - continue; - ret = get_mapped_object(t, dsnc, i, &obj); - if (ret < 0) - return ret; - hash_object(&obj, hashes + i * HASH_SIZE); - hash_obj.data = hashes + i * HASH_SIZE; - osl_add_row(hash_tree_table, &hash_obj); - ds_name = disk_storage_name_of_hash(t, hashes + i * HASH_SIZE); - FOR_EACH_DISK_STORAGE_COLUMN(j, t, cd) { - ret = check_disk_storage_column(t, i, j, ds_name, - &missing_objects); - if (ret < 0) - goto err; - } - free(ds_name); - } - if (!missing_objects) - PARA_INFO_LOG("all referenced disk storage objects exist, good\n"); - else - PARA_NOTICE_LOG("%d missing object(s)\n", missing_objects); - return missing_objects; -err: - free(ds_name); - return ret; -} - -static int dummy_compare(const struct osl_object *obj1, const struct osl_object *obj2) -{ - if (obj1 < obj2) - return -1; - if (obj1 > obj2) - return 1; - return 0; -} - -static unsigned files_pruned; - -int prune_disk_storage_file(const char *path, void *private_data) -{ - HASH_TYPE hash[HASH_SIZE]; - unsigned flags = *(unsigned *)private_data; - struct osl_object obj = {.data = hash, .size = HASH_SIZE}; - struct osl_row *row; - int ret = -1; - size_t len = strlen(path); - - - PARA_DEBUG_LOG("path: %s\n", path); - if (flags & OSL_LARGE_TABLE) { - if (len < HASH_SIZE * 2 + 2) - goto invalid; -// PARA_NOTICE_LOG("p: %s\n", path + len - 2 * HASH_SIZE - 1); - ret = asc_to_hash(path + len - 2 * HASH_SIZE - 1, 1, hash); - if (ret < 0) - goto invalid; - ret = asc_to_hash(path + len - 2 * HASH_SIZE + 2, HASH_SIZE - 1, - hash + 1); - if (ret < 0) - goto invalid; -// PARA_INFO_LOG("high: %x, low: %x, hash: %x\n", high, low, hash); - } else { - if (len < 2 * HASH_SIZE + 1) - goto invalid; - ret = asc_to_hash(path + len - 2 * HASH_SIZE, 2 * HASH_SIZE, hash); - if (ret < 0) - goto invalid; -// PARA_INFO_LOG("hash: %x\n", hash); - } -#if 0 -{ - char asc[2 * HASH_SIZE + 1]; - hash_to_asc(hash, asc); - PARA_NOTICE_LOG("before: %s\nafter: %s\n", path, asc); -} -#endif - ret = osl_get_row(hash_tree_table, 0, &obj, &row); - if (ret >= 0) - return 1; - PARA_NOTICE_LOG("unreferenced file in hash dir: %s\n", path); - goto remove; -invalid: - PARA_ERROR_LOG("could not read hash value of %s\n", path); -remove: - PARA_NOTICE_LOG("removing %s\n", path); - unlink(path); - files_pruned++; - return 1; -} - -static int prune_disk_storage_files(struct osl_table *t) -{ - int i, ret = 1; - const struct osl_column_description *cd; - - PARA_INFO_LOG("looking for unreferenced disk storage files\n"); - FOR_EACH_DISK_STORAGE_COLUMN(i, t, cd) { - char *dirname = column_filename(t, i); - ret = for_each_file_in_dir(dirname, prune_disk_storage_file, - (unsigned *)&t->desc->flags); - free(dirname); - } - if (files_pruned) - PARA_NOTICE_LOG("%u disk storage files deleted\n", - files_pruned); - else - PARA_INFO_LOG("all files are are referenced, good\n"); - return ret; -} - -static int check_disk_storage_columns(struct osl_table *t) -{ - int ret, i; - const struct osl_column_description *cd; - - if (!t->num_disk_storage_columns) { - PARA_INFO_LOG("no disk storage columns in table '%s', " - "skipping checks\n", t->desc->name); - return 1; - } - FOR_EACH_COLUMN(i, t->desc, cd) - t->desc->column_descriptions[i].compare_function = dummy_compare; - ret = init_rbtrees(t); - if (ret < 0) - return ret; - PARA_INFO_LOG("creating rbtree for disk storage hash values\n"); - ret = osl_open_table(&hash_tree_table_desc, &hash_tree_table); - if (ret < 0) - goto out; - ret = check_disk_storage_presence(t); - if (ret < 0) - goto out_close_hash_tree; - ret = prune_disk_storage_files(t); -out_close_hash_tree: - osl_close_table(hash_tree_table, 0); - free(hashes); - hashes = NULL; -out: - clear_rbtrees(t); /* TODO why are we doing that here? Seems odd */ - return ret; -} - -static void set_dummy_contents(struct osl_table_description *desc) -{ - int i; - struct osl_column_description *cd; - - for (i = 0; i < desc->num_columns; i++) { - cd = get_column_description(desc, i); - cd->compare_function = dummy_compare; - } -} - -static int fsck_init(struct osl_table_description *desc, struct osl_table **t) -{ - struct osl_object map; - int ret = map_index(desc, &map); - - if (ret < 0) - goto out; - ret = read_table_desc(&map, desc); - if (ret < 0) { - para_munmap(map.data, map.size); - goto out; - } - set_dummy_contents(desc); - ret = init_table_structure(desc, t); - if (ret < 0) { - para_munmap(map.data, map.size); - goto out; - } - PARA_DEBUG_LOG("unmapping index\n"); - para_munmap(map.data, map.size); - if (conf.force_given) - ret = map_table(*t, (MAP_TBL_FL_IGNORE_DIRTY)); - else - ret = map_table(*t, 0); - if (ret >= 0) - (*t)->num_rows = table_num_rows(*t); -out: - return ret; -} - -static void fsck_cleanup(struct osl_table *t) -{ - int i; - - if (!t) - return; - if (t->desc->column_descriptions) { - struct osl_column_description *cd; - for (i = 0; i < t->desc->num_columns; i++) { - cd = get_column_description(t->desc, i); - free((char*)cd->name); - } - free(t->desc->column_descriptions); - } - free(t->columns); - free(t); - -} - -#define ST_CASE(st) case st: return #st - -const char *get_asc_storage_type(enum osl_storage_type st) -{ - switch (st) { - ST_CASE(OSL_MAPPED_STORAGE); - ST_CASE(OSL_DISK_STORAGE); - ST_CASE(OSL_NO_STORAGE); - } - return NULL; -} - -#define APPEND_ASC_SF(sf, flag, str) do { if (sf & flag) { \ - if (str) str = para_strcat(str, " | " # flag); \ - else str = para_strdup(#flag); }} while (0) - - -char *get_asc_storage_flags(enum osl_storage_type sf) -{ - char *asc_sf = NULL; - - APPEND_ASC_SF(sf, OSL_RBTREE, asc_sf); - APPEND_ASC_SF(sf, OSL_FIXED_SIZE, asc_sf); - APPEND_ASC_SF(sf, OSL_UNIQUE, asc_sf); - return asc_sf; -} - -static int dump_table_desc(struct osl_table *t, int fd) -{ - const struct osl_table_description *desc = t->desc; - int ret, i; - struct osl_column_description *cd; - char *msg = make_message("static struct osl_column_description cols[] = {\n"); - ret = para_write_all(fd, msg, strlen(msg)); - if (ret < 0) - return ret; - free(msg); - FOR_EACH_COLUMN(i, desc, cd) { - const char *asc_st; - msg = make_message("\t[%d] = {\n", i); - ret = para_write_all(fd, msg, strlen(msg)); - if (ret < 0) - return ret; - free(msg); - asc_st = get_asc_storage_type(cd->storage_type); - msg = make_message("\t\t.storage_type = %s,\n", asc_st); - ret = para_write_all(fd, msg, strlen(msg)); - if (ret < 0) - return ret; - free(msg); - if (cd->storage_flags) { - char *asc_sf = get_asc_storage_flags(cd->storage_flags); - msg = make_message("\t\t,storage_flags = %s,\n", asc_sf); - free(asc_sf); - ret = para_write_all(fd, msg, strlen(msg)); - if (ret < 0) - return ret; - free(msg); - } - if (cd->storage_flags & OSL_FIXED_SIZE) { - msg = make_message("\t\t.data_size = %u,\n", cd->data_size); - ret = para_write_all(fd, msg, strlen(msg)); - if (ret < 0) - return ret; - free(msg); - } - msg = make_message("\t\t.name = \"%s\",\n", cd->name); - ret = para_write_all(fd, msg, strlen(msg)); - if (ret < 0) - return ret; - free(msg); - if (cd->storage_flags & OSL_RBTREE) { - msg = make_message("\t\t.compare_function = compare_func,\n"); - ret = para_write_all(fd, msg, strlen(msg)); - if (ret < 0) - return ret; - free(msg); - } - msg = make_message("\t},\n"); - ret = para_write_all(fd, msg, strlen(msg)); - if (ret < 0) - return ret; - free(msg); - } - msg = make_message("};\n"); - ret = para_write_all(fd, msg, strlen(msg)); - if (ret < 0) - return ret; - free(msg); - return 1; -} - -static int dump_row(struct osl_table *t, unsigned row_num, const char *row_dir) -{ - int ret, i; - const struct osl_column_description *cd; - unsigned dsnc; - struct osl_object obj; - char *ds_name; - HASH_TYPE hash[HASH_SIZE]; - char *filename; - - FOR_EACH_MAPPED_COLUMN(i, t, cd) { - ret = get_mapped_object(t, i, row_num, &obj); - if (ret < 0) - return ret; - filename = make_message("%s/col_%03u", row_dir, i); - ret = para_write_file(filename, obj.data, obj.size); - free(filename); - if (ret < 0) - return ret; - } - if (!t->num_disk_storage_columns) - return 1; - dsnc = t->disk_storage_name_column; - ret = get_mapped_object(t, dsnc, row_num, &obj); - if (ret < 0) - return ret; - hash_object(&obj, hash); - ds_name = disk_storage_name_of_hash(t, hash); - FOR_EACH_DISK_STORAGE_COLUMN(i, t, cd) { - filename = disk_storage_path(t, i, ds_name); - ret = mmap_full_file(filename, O_RDONLY, &obj.data, &obj.size, NULL); - free(filename); - if (ret < 0) - goto out; - filename = make_message("%s/col_%03u", row_dir, i); - ret = para_write_file(filename, obj.data, obj.size); - free(filename); - if (ret < 0) - goto out; - } - ret = 1; -out: - free(ds_name); - return ret; -} - -static int dump_rows(char *dump_dir, struct osl_table *t) -{ - unsigned i; - char *current_dir = NULL; - int ret = 0; - - for (i = 0; i < t->num_rows; i++) { - char *row_dir; - if (row_is_invalid(t, i)) - continue; - if (!(i % 1000)) { - free(current_dir); - current_dir = make_message("%s/rows_%u-%u", dump_dir, i, i + 999); - PARA_NOTICE_LOG("dumping rows %u - %u\n", i, i + 999); - ret = para_mkdir(current_dir, 0777); - if (ret < 0 && !is_errno(-ret, EEXIST)) - goto out; - } - row_dir = make_message("%s/row_%03u", current_dir, i); - ret = para_mkdir(row_dir, 0777); - if (ret < 0 && !is_errno(-ret, EEXIST)) { - free(row_dir); - goto out; - } - ret = dump_row(t, i, row_dir); - free(row_dir); - if (ret < 0) - goto out; - } -out: - free(current_dir); - return ret; -} - -static int dump_table(char *dump_dir, struct osl_table_description *desc) -{ - struct osl_table *t = NULL; - int fd, ret = fsck_init(desc, &t); - char *desc_file; - char *table_dump_dir = NULL; - - if (ret < 0) - goto out; - ret = para_mkdir(dump_dir, 0777); - if (ret < 0 && !is_errno(-ret, EEXIST)) - goto out; - table_dump_dir = make_message("%s/%s", dump_dir, desc->name); - ret = para_mkdir(table_dump_dir, 0777); - if (ret < 0 && !is_errno(-ret, EEXIST)) - goto out; - desc_file = make_message("%s/table_description.c", table_dump_dir); - ret = para_open(desc_file, O_WRONLY | O_CREAT | O_EXCL, 0644); - free(desc_file); - if (ret < 0) - goto out; - fd = ret; - ret = dump_table_desc(t, fd); - close(fd); - if (ret < 0) - goto out; - ret = dump_rows(table_dump_dir, t); -out: - free(table_dump_dir); - fsck_cleanup(t); - return ret; -} - -static int fsck(struct osl_table_description *desc) -{ - int ret; - struct osl_table *t = NULL; - uint32_t *lost_bytes = NULL; - - ret = fsck_init(desc, &t); - if (ret < 0) - goto out; - ret = check_index_ranges(t); - if (ret < 0) - goto out_unmap; - ret = check_disk_storage_columns(t); - if (ret < 0) - goto out_unmap; - ret = prune_invalid_rows_from_index(t); - if (ret < 0) - goto out_unmap; - ret = check_for_invalid_objects(t, &lost_bytes); - if (ret < 0) - goto out_unmap; - if (ret > 0) { /* at least one mapped data file needs pruning */ - ret = prune_objects(t, lost_bytes); - if (ret < 0) - goto out_unmap; - } - free(lost_bytes); -out_unmap: - unmap_table(t, OSL_MARK_CLEAN); -out: - fsck_cleanup(t); - return ret; -} - -static int check_table(char *base_dir, char *table_name) -{ - struct osl_table_description desc = { - .column_descriptions = NULL, - .dir = base_dir, - .name = table_name - }; - int ret; - - PARA_INFO_LOG("checking table %s\n", table_name); - if (!conf.no_fsck_given) { - ret = fsck(&desc); - if (ret < 0) - goto out; - } - ret = 1; - if (!conf.dump_dir_given || !*conf.dump_dir_arg) - goto out; - ret = dump_table(conf.dump_dir_arg, &desc); -out: - if (ret < 0) - PARA_ERROR_LOG("failed to check table %s\n", table_name); - else - PARA_NOTICE_LOG("successfully checked table %s\n", table_name); - return ret; -} - -static int check_all_tables(char *base_dir) -{ - DIR *dir; - struct dirent *entry; - int cwd_fd, ret2, ret = para_opendir(base_dir, &dir, &cwd_fd); - - if (ret < 0) - return ret; - while ((entry = readdir(dir))) { - mode_t m; - struct stat s; - if (!strcmp(entry->d_name, ".")) - continue; - if (!strcmp(entry->d_name, "..")) - continue; - if (lstat(entry->d_name, &s) == -1) - continue; - m = s.st_mode; - if (!S_ISDIR(m)) - continue; - ret = check_table(base_dir, entry->d_name); - if (ret < 0) - break; - } - closedir(dir); - ret2 = para_fchdir(cwd_fd); - if (ret2 < 0 && ret >= 0) - ret = ret2; - close(cwd_fd); - return ret; -} - -/** - * The praslash database check program. - * - * \param argc Usual arg count. - * \param argv Usual arg vector. - * - * \return \p EXIT_SUCCESS or \p EXIT_FAILURE. - */ -int main(int argc, char **argv) -{ - int i, ret; - char *base_dir = NULL; - - ret = fsck_cmdline_parser(argc, argv, &conf); - if (ret < 0) { - ret = -E_FSCK_SYNTAX; - goto out; - } - HANDLE_VERSION_FLAG("fsck", conf); - loglevel = get_loglevel_by_name(conf.loglevel_arg); - if (conf.base_dir_given) - base_dir = para_strdup(conf.base_dir_arg); - else { - char *home = para_homedir(); - base_dir = make_message("%s/.paraslash/afs_database", home); - free(home); - } - if (!conf.inputs_num) { - ret = check_all_tables(base_dir); - goto out; - } - for (i = 0; i < conf.inputs_num; i++) { - ret = check_table(base_dir, conf.inputs[i]); - if (ret < 0) - break; - } -out: - if (ret < 0) { - PARA_ERROR_LOG("%s%s: %s\n", - base_dir? "base_dir: " : "", - base_dir? base_dir : "", - para_strerror(-ret) - ); - } else - PARA_NOTICE_LOG("success\n"); - if (base_dir) - free(base_dir); - return ret < 0? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/gcc-compat.h b/gcc-compat.h index 61c3c88d..ffd714f7 100644 --- a/gcc-compat.h +++ b/gcc-compat.h @@ -1,12 +1,12 @@ -# define inline inline __attribute__ ((always_inline)) -# define __noreturn __attribute__ ((noreturn)) -# define __malloc __attribute__ ((malloc)) -# define __a_unused __attribute__ ((unused)) -# define likely(x) __builtin_expect (!!(x), 1) -# define unlikely(x) __builtin_expect (!!(x), 0) -/* - * p is the number of the "format string" parameter, and q is - * the number of the first variadic parameter +#define inline inline __attribute__ ((always_inline)) +#define __noreturn __attribute__ ((noreturn)) +#define __malloc __attribute__ ((malloc)) +#define __a_unused __attribute__ ((unused)) +#define __a_aligned(alignment) __attribute__((__aligned__(alignment))) + +/* + * p is the number of the "format string" parameter, and q is + * the number of the first variadic parameter. */ # define __printf(p,q) __attribute__ ((format (printf, p, q))) /* diff --git a/ggo.c b/ggo.c index b9852b99..a9bc4c36 100644 --- a/ggo.c +++ b/ggo.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2009 Andre Noll + * Copyright (C) 2008-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ diff --git a/ggo.h b/ggo.h index 4d452b1d..88db8761 100644 --- a/ggo.h +++ b/ggo.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2009 Andre Noll + * Copyright (C) 2008-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ diff --git a/ggo/.gitignore b/ggo/.gitignore index a6d955c1..a0824cf8 100644 --- a/ggo/.gitignore +++ b/ggo/.gitignore @@ -8,4 +8,3 @@ gui.ggo recv.ggo server.ggo write.ggo - diff --git a/ggo/afh.m4 b/ggo/afh.m4 index 97b8f288..80df8948 100644 --- a/ggo/afh.m4 +++ b/ggo/afh.m4 @@ -62,6 +62,20 @@ option "chunk_table" c flag off dependon="info" +option "human" u +#~~~~~~~~~~~~~~~ +"use human-readable output format" +flag off +dependon = "info" +details = " + Currently this option only affects the format of the chunk table, + so it has no effect if --chunk_table is not given. + + The human-readable output consists of one output line per + chunk and the output contains also the chunk number, the + duration and the size of each chunk. +" + section "Options for stream mode" #================================ diff --git a/ggo/alsa_write.ggo b/ggo/alsa_write.ggo deleted file mode 100644 index 84f49d58..00000000 --- a/ggo/alsa_write.ggo +++ /dev/null @@ -1,34 +0,0 @@ -option "device" d -#~~~~~~~~~~~~~~~~ -"set PCM device" -string typestr="device" -default="default" -optional -details=" - On systems with dmix, a better choice than the default - value might be to use \"plug:swmix\". -" - -option "channels" c -#~~~~~~~~~~~~~~~~~~ -"specify number of channels" -int typestr="num" -default="2" -optional -details=" - This option is only necessary for playing raw audio with - para_write. In all other cases (plaing wav files with - para_write or using this writer with para_audiod), the number - of channels will be obtained from other resources. -" - -option "samplerate" s -#~~~~~~~~~~~~~~~~~~~~~ -"force given sample rate" -int typestr="num" -default="44100" -optional -details=" - Again, it is only necessary to specify this when playing raw - audio with para_write. -" diff --git a/ggo/alsa_write.m4 b/ggo/alsa_write.m4 new file mode 100644 index 00000000..d0cd4071 --- /dev/null +++ b/ggo/alsa_write.m4 @@ -0,0 +1,14 @@ +include(header.m4) + + +option "device" d +#~~~~~~~~~~~~~~~~ +"set PCM device" +string typestr="device" +default="default" +optional +details=" + On systems with dmix, a better choice than the default + value might be to use \"plug:swmix\". +" + diff --git a/ggo/audiod.m4 b/ggo/audiod.m4 index 29bce100..289917c3 100644 --- a/ggo/audiod.m4 +++ b/ggo/audiod.m4 @@ -12,6 +12,7 @@ include(loglevel.m4) include(color.m4) include(config_file.m4) include(logfile.m4) +include(log_timing.m4) include(daemon.m4) include(user.m4) include(group.m4) @@ -145,12 +146,12 @@ details=" the amp filter or the compress filter which are not activated by default. - Playing udp streams also requires manual filter configuration - because the output of the udp receiver must be fed into the - fecdec filter first to produce a stream which can be decoded - by the appropriate decoder (mp3dec, oggdec, aacdec). In other - words, the fecdec filter must be specified as the first filter - of the filter chain for any udp stream. + Playing udp streams also requires that the output of the udp + receiver must be fed into the fecdec filter first to produce + a stream which can be decoded by the appropriate decoder + (mp3dec, oggdec, aacdec, wmadec). In other words, the fecdec + filter should be specified as the first filter of the filter + configuration for udp streaming. " option "filter" f diff --git a/ggo/client.m4 b/ggo/client.m4 index 1ae5a38a..5770ff0a 100644 --- a/ggo/client.m4 +++ b/ggo/client.m4 @@ -11,7 +11,3 @@ option "key_file" k "(default='~/.paraslash/key.')" string typestr="filena include(loglevel.m4) include(config_file.m4) - - -option "plain" - "request an uncrypted session" flag off - diff --git a/ggo/dccp_recv.ggo b/ggo/dccp_recv.ggo index 9717329d..80d31233 100644 --- a/ggo/dccp_recv.ggo +++ b/ggo/dccp_recv.ggo @@ -11,3 +11,27 @@ option "port" p int default="8000" optional + +option "ccid" c +"CCID preference(s) for this connection" +int +# restrict the maximum number of times this option can be passed +optional multiple(-10) +# currently known CCIDs: +# - CCID-2 (RFC 4341), +# - CCID-3 (RFC 4342), +# - CCID-4 (RFC 5622), +# - CCID-248 ... CCID-254 are experimental (RFC 4340, 19.5) +values="2", "3", "4", "248", "249", "250", "251", "252", "253", "254" +details=" + When present exactly once, this option mandates the CCID for the + sender-receiver connection. If it is passed more than once, it sets + a preference list where the order of appearance signifies descending + priority. For example, passing 4, 2, 3 creates the preference list + (CCID-4, CCID-2, CCID-3), assigning CCID-4 highest preference. + + The request is reconciled with the CCIDs on the server through the + 'server-priority' mechanism of RFC 4340 6.3.1/10. The server CCIDs + can be listed by calling 'para_client si'. + +" diff --git a/ggo/fade.ggo b/ggo/fade.ggo index 01eb6aa9..80f8b73c 100644 --- a/ggo/fade.ggo +++ b/ggo/fade.ggo @@ -1,190 +1,200 @@ -section "general options" +section "General options" ######################### option "mode" o #~~~~~~~~~~~~~~ -"{sleep|fade|snooze}" -string default="sleep" -optional -details=" - para_fade knows these three different modes. +"how to fade volume" + enum typestr = "mode" + values = "sleep", "snooze", "fade" + default = "sleep" + optional + details=" + para_fade knows three different fading modes: - sleep mode: Change to the initial volume and an initial afs - mode, then fade the volume down until the fade out volume is - reached. Switch to the afs sleep mode until the wake time is - reached. Then switch to the afs wake mode and and fade in to - the wake volume. + sleep mode: Change to the initial volume and select + the initial afs mood/playlist. Then fade out until + the fade-out volume is reached. Switch to the + sleep mood/playlist until wake time minus fade-in + time. Finally switch to the wake mood/playlist and + fade to the fade-in volume. - fade: Fade the volume to the given value in the given time. + fade: Fade the volume to the given value in the + given time. - snooze: Fade out, sleep a bit and fade in. + snooze: Fade out, sleep a bit and fade in. " -option "config_file" c +option "config-file" c #~~~~~~~~~~~~~~~~~~~~~ - "(default='~/.paraslash/fade.conf')" - string typestr="filename" +"(default='~/.paraslash/fade.conf')" + string typestr = "filename" optional -option "mixer_device" m +option "mixer-device" m #~~~~~~~~~~~~~~~~~~~~~~ - "mixer device file" - string typestr="device" - default="/dev/mixer" +"mixer device file" + string typestr = "device" + default = "/dev/mixer" optional -option "mixer_channel" C +option "mixer-channel" C #~~~~~~~~~~~~~~~~~~~~~~~ "select the analog mixer channel" - enum typestr="channel" - values="volume", "bass", "treble", "synth", "pcm", "speaker", "line", + enum typestr = "channel" + values = "volume", "bass", "treble", "synth", "pcm", "speaker", "line", "mic", "cd", "imix", "altpcm", "reclev", "igain", "ogain" - default="volume" + default = "volume" optional - details=" - Not all listed channels might be supported on any particular hardware. + details = " + Not all listed channels might be supported on any + particular hardware. " -section "sleep options (only relevant in sleep mode)" -##################################################### +section "Options for sleep mode" +################################ -option "sleep_ivol" - -#~~~~~~~~~~~~~~~~~~~~ +option "ivol" - +#~~~~~~~~~~~~~~ "set initial volume" -int typestr="volume" -default="60" -optional -details=" - Used as the start volume, before fading out to the fade out volume. -" + int typestr = "volume" + default = "60" + optional + details = " + Used as the start volume, before fading out to the + fade out volume. + " -option "fa_mode" - +option "fo-mood" - #~~~~~~~~~~~~~~~~~ -"fall asleep afs mode." -string typestr="afs_mode" -default="m/fade" -optional -details=" - Select this mode right after setting the volume. Example: - --fa_mode m/sleep +"afs mood/playlist during fade out" + string typestr = "mood_spec" + default = "m/fade" + optional + details = " + Select this mood right after setting the + volume. Example: --fo-mood m/sleep " -option "fa_fade" - +option "fo-time" - #~~~~~~~~~~~~~~~~~ - "fall asleep fading time" -int typestr="seconds" -default="1800" -optional -details=" - No fading if set to 0. -" +"fall asleep fade out time" + int typestr = "seconds" + default = "1800" + optional + details = " + No fading if set to 0. + " -option "fa_vol" - +option "fo-vol" - #~~~~~~~~~~~~~~~~ - "volume to fade to" - int typestr="volume" - default="20" +"volume to fade out to" + int typestr = "volume" + default = "20" optional -option "sleep_mode" - -#~~~~~~~~~~~~~~~~~~~~~~ -"sleep time afs mode" -details = " - Select the given afs mode after the fade out is complete. If - unset, the \"stop\" command is sent to para_server. -" -string typestr="afs_mode" -default="m/sleep" -optional +option "sleep-mood" - +#~~~~~~~~~~~~~~~~~~~~ +"sleep time afs mood/playlist" + string typestr = "mood_spec" + default = "m/sleep" + optional + details = " + Select the given afs mood/playlist after the fade + out is complete. If unset, the \"stop\" command is + sent to para_server. + " -option "wake_hour" H +option "wake-hour" H #~~~~~~~~~~~~~~~~~~~ - "(0-23) (default: now + 9 hours)" - int typestr="hour" +"(0-23) (default: now + 9 hours)" + int typestr = "hour" optional -option "wake_min" M +option "wake-min" M #~~~~~~~~~~~~~~~~~~ "(0-59)" - int typestr="minutes" - default="0" + int typestr = "minutes" + default = "0" optional -option "wake_mode" - -#~~~~~~~~~~~~~~~~~~~ -"wake time afs mode" -string typestr="afs_mode" -default="m/wake" -optional -details=" - Change to this afs mode on waketime. -" +option "fi-mood" - +#~~~~~~~~~~~~~~~~~ +"afs mood/playlist during fade in" + string typestr = "mood_spec" + default = "m/wake" + optional + details = " + Change to this afs mood/playlist on wake time. + " -option "wake_fade" - -#~~~~~~~~~~~~~~~~~~~ - "no fading in if set to 0" +option "fi-time" - +#~~~~~~~~~~~~~~~~~ +"wake up fade in time" int typestr="seconds" default="1200" optional + details = " + No fading in if set to 0. + " -option "wake_vol" - -#~~~~~~~~~~~~~~~~~~ - "vol to fade to at waketime" - int typestr="volume" - default="80" +option "fi-vol" - +#~~~~~~~~~~~~~~~~ +"volume to fade to at wake time" + int typestr = "volume" + default = "80" optional -section "snooze options" -######################## +section "Options for snooze mode" +################################# -option "snooze_out_fade" - -#~~~~~~~~~~~~~~~~~~~~~~~~~ - "fade out time" - int typestr="seconds" - default="30" +option "so-time" - +#~~~~~~~~~~~~~~~~~ +"snooze-out time" + int typestr = "seconds" + default = "30" optional -option "snooze_out_vol" - -#~~~~~~~~~~~~~~~~~~~~~~~~ - "vol to fade to before snooze" - int typestr="volume" - default="20" +option "so-vol" - +#~~~~~~~~~~~~~~~~ +"volume to fade to before snooze" + int typestr = "volume" + default = "20" optional -option "snooze_time" - +option "snooze-time" - #~~~~~~~~~~~~~~~~~~~~~ - "delay" - int typestr="seconds" - default="600" +"delay" + int typestr = "seconds" + default = "600" optional -option "snooze_in_fade" - -#~~~~~~~~~~~~~~~~~~~~~~~~ - "fade in time" - int typestr="seconds" - default="180" +option "si-time" - +#~~~~~~~~~~~~~~~~~ +"snooze-in time" + int typestr = "seconds" + default = "180" optional -option "snooze_in_vol" - -#~~~~~~~~~~~~~~~~~~~~~~~ - "vol to fade to after snooze" - int typestr="volume" - default="80" +option "si-vol" - +#~~~~~~~~~~~~~~~~ +"volume to fade to after snooze" + int typestr = "volume" + default = "80" optional -section "fade options" -###################### +section "Options for fade mode" +############################### -option "fade_vol" f +option "fade-vol" f #~~~~~~~~~~~~~~~~~~ - "volume to fade to" - int typestr="volume" - default="50" +"volume to fade to" + int typestr = "volume" + default = "50" optional -option "fade_time" t +option "fade-time" t #~~~~~~~~~~~~~~~~~~~ - "time to fade in" - int typestr="seconds" - default="5" +"fading time" + int typestr = "seconds" + default = "5" optional diff --git a/ggo/filter.m4 b/ggo/filter.m4 index 4e991764..9df0f3f2 100644 --- a/ggo/filter.m4 +++ b/ggo/filter.m4 @@ -9,13 +9,13 @@ optional multiple details=" May be given multiple times to 'pipe' the stream through - arbitrary many filters in an efficient way. The same filter - may appear more than once, order matters. + arbitrary many filters (without copying the data). The same + filter may appear more than once, order matters. - Filter options may be specified for each '-f' option - separately. Note that you will have to quote these options - like this: + Options for a particular filter may be specified for each + given '--filter' option separately. You will have to quote + these options like this: - -f 'compress --inertia 5 --damp 2' + --filter 'compress --inertia 5 --damp 2' " diff --git a/ggo/grab_client.ggo b/ggo/grab_client.ggo deleted file mode 100644 index 5527143b..00000000 --- a/ggo/grab_client.ggo +++ /dev/null @@ -1,33 +0,0 @@ -option "filter_num" f - "point of filter chain to grab" - int typestr="num" - default="0" - optional - -option "slot" s - "only grab this slot; grab any slot if negative" - int typestr="num" - default="-1" - optional - -option "audio_format" a - "only grab this type of input stream; - grab any if empty" - string typestr="name" - default="" - optional - -option "input_grab" i - "grab the filter input instead of its output" - flag off - -option "one_shot" o - "stop grabbing if audio file changes" - flag off - -option "mode" m - "select grab mode" - typestr="grab_mode" - values="sloppy","aggressive","pedantic" - default="sloppy" - optional diff --git a/ggo/gui.m4 b/ggo/gui.m4 index dd1e2496..3f31dc1a 100644 --- a/ggo/gui.m4 +++ b/ggo/gui.m4 @@ -25,7 +25,7 @@ option "stat_cmd" s data from" string typestr="command" - default="para_audioc stat" + default="para_audioc -- stat -p" optional #--------------------------------- diff --git a/ggo/http_recv.ggo b/ggo/http_recv.ggo deleted file mode 100644 index 2f1b4d61..00000000 --- a/ggo/http_recv.ggo +++ /dev/null @@ -1,15 +0,0 @@ -option "host" i -#~~~~~~~~~~~~~~ -"ip or host" -string -default="localhost" -optional -details=" - Both IPv4 and IPv6 addresses are supported. -" - -option "port" p -#~~~~~~~~~~~~~~ -"tcp port to connect to" -int default="8000" -optional diff --git a/ggo/http_recv.m4 b/ggo/http_recv.m4 new file mode 100644 index 00000000..14ae99ab --- /dev/null +++ b/ggo/http_recv.m4 @@ -0,0 +1,19 @@ +include(header.m4) + + +option "host" i +#~~~~~~~~~~~~~~ +"ip or host" +string +default="localhost" +optional +details=" + Both IPv4 and IPv6 addresses are supported. +" + +option "port" p +#~~~~~~~~~~~~~~ +"tcp port to connect to" +int default="8000" +optional + diff --git a/ggo/log_timing.m4 b/ggo/log_timing.m4 new file mode 100644 index 00000000..ac0ea841 --- /dev/null +++ b/ggo/log_timing.m4 @@ -0,0 +1,12 @@ + +option "log-timing" T +#~~~~~~~~~~~~~~~~~~~~ +"show milliseconds in log messages" +flag off +details = " + Selecting this option causes milliseconds to be included in + the log message output. This allows to measure the interval + between log messages in milliseconds which is useful for + identifying timing problems. +" + diff --git a/ggo/makefile b/ggo/makefile index a7715208..d4f3a8cd 100644 --- a/ggo/makefile +++ b/ggo/makefile @@ -1,31 +1,28 @@ module_ggo_opts := --set-version="($(PACKAGE_STRING), $(codename))" +$(cmdline_dir): + mkdir -p $(cmdline_dir) -grab_client.cmdline.h grab_client.cmdline.c: $(ggo_dir)/grab_client.ggo - gengetopt $(module_ggo_opts) \ - -S \ - --set-package=grab \ - --no-handle-help \ - --no-handle-error \ - --no-handle-version \ - --arg-struct-name=grab_client_args_info \ - --file-name=$(subst .ggo,,$( $(@F) + @[ -z "$(Q)" ] || echo 'M4 $<' + $(Q) cd $(ggo_dir); m4 $( $(@F) diff --git a/ggo/mp3dec_filter.ggo b/ggo/mp3dec_filter.ggo deleted file mode 100644 index a34c30ae..00000000 --- a/ggo/mp3dec_filter.ggo +++ /dev/null @@ -1,22 +0,0 @@ -option "bufsize" b -#~~~~~~~~~~~~~~~~~ -"size of output buffer" -int typestr="kilobyte" -default="128" -optional -details=" - Increase this if you encounter output buffer overrun - errors. Smaller values make the mp3dec filter use less - memory. The minimal size is 32K. -" - -option "ignore-crc" i -#~~~~~~~~~~~~~~~~~~~~ -"ignore CRC information in the audio stream." -flag off -details=" - This causes frames with CRC errors to be decoded and played - anyway. This option is not recommended, but since some encoders - have been known to generate bad CRC information, this option - is a work-around to play streams from such encoders. -" diff --git a/ggo/mp3dec_filter.m4 b/ggo/mp3dec_filter.m4 new file mode 100644 index 00000000..c02f1583 --- /dev/null +++ b/ggo/mp3dec_filter.m4 @@ -0,0 +1,14 @@ +include(header.m4) + + +option "ignore-crc" i +#~~~~~~~~~~~~~~~~~~~~ +"ignore CRC information in the audio stream." +flag off +details=" + This causes frames with CRC errors to be decoded and played + anyway. This option is not recommended, but since some encoders + have been known to generate bad CRC information, this option + is a work-around to play streams from such encoders. +" + diff --git a/ggo/oggdec_filter.ggo b/ggo/oggdec_filter.ggo deleted file mode 100644 index e2d653d7..00000000 --- a/ggo/oggdec_filter.ggo +++ /dev/null @@ -1,21 +0,0 @@ -option "bufsize" b -#~~~~~~~~~~~~~~~~~ -"size of output buffer" -int typestr="kilobyte" -default="128" -optional -details=" - Increase this if you encounter output buffer overrun errors. Smaller - values make the oggdec filter use less memory. -" - -option "initial_buffer" i -#~~~~~~~~~~~~~~~~~~~~~~~~ -"size of initial input buffer" -int typestr="kilobyte" -default="16" -optional -details=" - On startup, defer decoding until that many kilobytes are - available in the input buffer. -" diff --git a/ggo/oss_write.ggo b/ggo/oss_write.ggo index 60682186..351561cd 100644 --- a/ggo/oss_write.ggo +++ b/ggo/oss_write.ggo @@ -4,27 +4,4 @@ option "device" d string typestr="device" default="/dev/dsp" optional - -option "channels" c -#~~~~~~~~~~~~~~~~~~ -"specify number of channels" -int typestr="num" -default="2" -optional -details=" - This option is only necessary for playing raw audio with - para_write. In all other cases (plaing wav files with - para_write or using this writer with para_audiod), the number - of channels will be obtained from other resources. -" - -option "samplerate" s -#~~~~~~~~~~~~~~~~~~~~~ -"force given sample rate" -int typestr="num" -default="44100" -optional -details=" - Again, it is only necessary to specify this when playing raw - audio with para_write. -" +details = "" diff --git a/ggo/osx_write.ggo b/ggo/osx_write.ggo index ce2792e5..bee16d9e 100644 --- a/ggo/osx_write.ggo +++ b/ggo/osx_write.ggo @@ -1,25 +1,6 @@ section "osx options" ##################### -option "channels" c -#~~~~~~~~~~~~~~~~~~ -"number of channels (only neccessary for raw -audio)" - - int typestr="num" - default="2" - optional - -option "samplerate" s -#~~~~~~~~~~~~~~~~~~~~~ - -"force given sample rate (only neccessary for -raw audio)" - - int typestr="num" - default="44100" - optional - option "numbuffers" n #~~~~~~~~~~~~~~~~~~~~~ @@ -27,5 +8,6 @@ option "numbuffers" n you get buffer underruns)" int typestr="num" - default="5" + default="20" optional + details = "" diff --git a/ggo/server.m4 b/ggo/server.m4 index f2738d37..7f2d9759 100644 --- a/ggo/server.m4 +++ b/ggo/server.m4 @@ -9,6 +9,7 @@ section "General options" include(loglevel.m4) +include(log_timing.m4) include(color.m4) include(daemon.m4) include(user.m4) @@ -104,7 +105,7 @@ option "afs_socket" s #~~~~~~~~~~~~~~~~~~~~ "Command socket for afs" string typestr="path" -default="/var/paraslash/afs_command_socket" +default="/var/paraslash/afs_command_socket-0.4" optional details=" For each server command that is handled by the audio file @@ -237,6 +238,56 @@ details=" See http_max_clients for details. " +option "dccp_max_slice_size" - +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +"Upper bound for the FEC slice size" +int typestr = "size" +optional +default = "0" +details = " + If this value is non-positive (the default) the dccp sender + uses the maximum packet size (MPS) of the connection as the + slice size. The MPS is a network parameter and depends on + the path maximum transmission unit (path MTU) of an incoming + connection, i.e. on the largest packet size that can be + transmitted without causing fragmentation. + + This option allows to use a value less than the MPS in order + to fine-tune application performance. Values greater than + the MPS of an incoming connection can not be set. +" + +option "dccp_data_slices_per_group" - +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +"The number of non-redundant slices per FEC group" +int typestr = "num" +optional +default = "3" +details = " + This determines the number of slices in each FEC group that are + necessary to decode the group. The given number must be smaller + than the value of the dccp_slices_per_group option below. + + Note that the duration of a FEC group is proportional to the + product dccp_max_slice_size * dccp_data_slices_per_group. +" + +option "dccp_slices_per_group" - +#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +"The total number of slices per FEC group" +int typestr = "num" +optional +default = "4" +details = " + This value must be larger than the value given for above + dccp_data_slices_per_group above. The difference being the + number of redundant slices per group, i.e. the number of + data packets that may be lost without causing interruptions + of the resulting audio stream. + + Increase this value if for lossy networks. +" + #################### section "udp sender" #################### diff --git a/ggo/write.m4 b/ggo/write.m4 index 41e44dd7..524d7248 100644 --- a/ggo/write.m4 +++ b/ggo/write.m4 @@ -1,13 +1,6 @@ include(header.m4) include(loglevel.m4) -option "bufsize" b -#~~~~~~~~~~~~~~~~~ -"input buffer size" -int typestr="kilobytes" -default="64" -optional - option "writer" w #~~~~~~~~~~~~~~~~ "select stream writer" @@ -20,12 +13,31 @@ details=" more than once. " -option "start_time" t -#~~~~~~~~~~~~~~~~~~~~ -"defer playback" -string typestr="timeval" -optional -details=" - Start playback at given time which must be in a:b format where - a denotes seconds and b denotes microseconds since the epoch. +text " + The following options are only necessary for raw audio. When + playing wav files this information is obtained from the + wave header. " + +option "channels" c +#~~~~~~~~~~~~~~~~~~ +"specify number of channels" +int typestr = "num" +default = "2" +optional + +option "sample-rate" s +#~~~~~~~~~~~~~~~~~~~~~ +"force given sample rate" +int typestr = "num" +default = "44100" +optional + +option "sample-format" f +#~~~~~~~~~~~~~~~~~~~~~~~ +"specify sample format" +# This must match the enum sample_format of para.h +values = "S8", "U8", "S16_LE", "S16_BE", "U16_LE", "U16_BE" enum +default = "S16_LE" +optional + diff --git a/grab_client.c b/grab_client.c index f3a0f484..8e671503 100644 --- a/grab_client.c +++ b/grab_client.c @@ -1,270 +1,295 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ -/** - * \file grab_client.c Functions for grabbing the stream at any position - * in a filter chain. - * - * \sa filter_chain filter_chain_info filter. - */ +/** \file grab_client.c Functions for grabbing the audio stream. */ +#include #include #include +#include #include "para.h" -#include "grab_client.cmdline.h" #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "filter.h" #include "grab_client.h" #include "audiod.h" #include "error.h" #include "string.h" #include "fd.h" -#include "crypt.h" -/** Grab clients that are not yet attached to a filter node. */ -struct list_head inactive_grab_client_list; +/** + * How to handle blocking writes for the grab client fds. + */ +enum grab_mode { + /** Ignore the data and do not write. */ + GM_SLOPPY, + /** Write anyway (default). */ + GM_AGGRESSIVE, + /** Close fd if write would block. */ + GM_PEDANTIC, +}; -static int max_num_filters(void) -{ - int i, ret = 0; - for (i = 0; audio_formats[i]; i++) { - PARA_INFO_LOG("%s filter chain length: %d\n", audio_formats[i], - num_filters(i)); - ret = PARA_MAX(ret, num_filters(i)); - } - PARA_INFO_LOG("maximal filter chain length: %d\n", ret); - return ret; -} +/** Flags specified as arguments to the grab command. */ +enum grab_flags { + /** Stop grabbing if audio file changes. */ + GF_ONE_SHOT = 1, +}; -static int gc_write(char *buf, size_t len, struct filter_callback *fcb) -{ - struct grab_client *gc = fcb->data; - struct timeval tv = {0, 100}; - int ret; +/** Describes one active grab client. */ +struct grab_client { + /* The value of the -p option. */ + char *parent; + /* The value of the -n option. */ + char *name; + /** The file descriptor to send the grabbed stream to. */ + int fd; + /** See \ref grab_mode. */ + enum grab_mode mode; + /** Flags given at the command line. */ + enum grab_flags flags; + /** The point of the grab client's node in the buffer tree. */ + struct btr_node *btrn; + /* The task of this grab client. */ + struct task task; + /** Belongs to either the active or the inactive list. */ + struct list_head node; +}; -// PARA_INFO_LOG("writing %d bytes to fd %d\n", len, gc->fd); - fd_set wfds; - FD_ZERO(&wfds); - FD_SET(gc->fd, &wfds); - ret = para_select(gc->fd + 1, NULL, &wfds, &tv); - if (ret <= 0) { - if (gc->mode == GRAB_PEDANTIC) - return -E_PEDANTIC_GRAB; - if (gc->mode == GRAB_SLOPPY) - return 1; - } -rewrite: - ret = write(gc->fd, buf, len); - if (ret < 0) { - ret = -E_GC_WRITE; - gc->error = E_GC_WRITE; - } else { - if (ret != len) { - if (gc->mode == GRAB_PEDANTIC) - return -E_PEDANTIC_GRAB; - if (gc->mode == GRAB_AGGRESSIVE) { - len -= ret; - memmove(buf, buf + ret, len); - goto rewrite; - } - } - } - return ret; -} +/* Grab clients that are attached to a btr node. */ +INITIALIZED_LIST_HEAD(active_grab_client_list); +/* Grab clients that are not currently attached any btr node. */ +INITIALIZED_LIST_HEAD(inactive_grab_client_list); -/* TODO: gengetopt can handle the grab client modes */ -static int check_gc_args(struct grab_client *gc) +static int gc_write(struct grab_client *gc, char *buf, size_t len) { - int i; - struct grab_client_args_info *c = gc->conf; - const char **mv = grab_client_cmdline_parser_mode_values; + int ret = write_ok(gc->fd); - PARA_INFO_LOG("filter_num: %d\n", c->filter_num_arg); - for (i = 0; mv[i]; i++) - if (!strcmp(c->mode_arg, mv[i])) - break; - if (!mv[i]) - return -E_GC_SYNTAX; - gc->mode = i; - gc->audio_format_num = -1; - if (c->audio_format_given) { - gc->audio_format_num = get_audio_format_num(c->audio_format_arg); - if (gc->audio_format_num < 0) - return gc->audio_format_num; + if (ret < 0) + goto err; + if (ret == 0) { /* fd not ready */ + if (gc->mode == GM_PEDANTIC) + goto err; + if (gc->mode == GM_SLOPPY) + return len; } - if (c->slot_arg >= MAX_STREAM_SLOTS) - return -E_BAD_GC_SLOT; - if (c->filter_num_arg < 0) - return -E_BAD_GC_FILTER_NUM; - if (c->audio_format_given) { - if (num_filters(gc->audio_format_num) <= c->filter_num_arg) - return -E_BAD_GC_FILTER_NUM; - } else - if (c->filter_num_arg >= max_num_filters()) - return -E_BAD_GC_FILTER_NUM; - - return 1; -} - -static void add_inactive_gc(struct grab_client *gc) -{ - PARA_INFO_LOG("adding grab client %p (fd %d) to inactive list\n", - gc, gc->fd); - para_list_add(&gc->node, &inactive_grab_client_list); -} - -static void gc_free(struct grab_client *gc) -{ - int i; - - for (i = 0; i < gc->argc; i++) - free(gc->argv[i]); - free(gc->argv); - free(gc->conf); - free(gc); - + ret = write_nonblock(gc->fd, buf, len); + if (ret < 0) + goto err; + if (ret > 0) + return ret; + if (ret == 0) { + if (gc->mode == GM_PEDANTIC) + goto err; + if (gc->mode == GM_SLOPPY) + return len; + } + return 0; +err: + return -E_GC_WRITE; } -static void gc_close(struct filter_callback *fcb) +static void gc_pre_select(struct sched *s, struct task *t) { - struct grab_client *gc = fcb->data; + struct grab_client *gc = container_of(t, struct grab_client, task); + int ret = btr_node_status(gc->btrn, 0, BTR_NT_LEAF); - if (gc->conf->one_shot_given || gc->error) { - PARA_INFO_LOG("closing fd %d (grab client %p)\n", gc->fd, gc); - close(gc->fd); - gc_free(gc); - /* close on fork ?*/ + if (ret == 0) return; - } - add_inactive_gc(gc); + if (ret < 0) + sched_min_delay(s); + para_fd_set(gc->fd, &s->wfds, &s->max_fileno); } +/* + * We need this forward declaration as post_select() needs + * activate_grab_client and vice versa. + */ +static void gc_post_select(struct sched *s, struct task *t); + /** - * Move a grab client from the inactive list to a filter node. + * Move a grab client to the active list and start it. * * \param gc The grab client to activate. - * \param fn The filter node \a gc gets attached to. - * - * \sa filter_node::callbacks, inactive_grab_client_list. */ -void activate_grab_client(struct grab_client *gc, struct filter_node *fn) +static void gc_activate(struct grab_client *gc) { - PARA_INFO_LOG("activating %p (fd %d, filter node: %p)\n", gc, gc->fd, fn); - list_del(&gc->node); - para_list_add(&gc->fcb.node, &fn->callbacks); + struct btr_node *root = audiod_get_btr_root(), *parent; + char *name = gc->name? gc->name : "grab"; + + if (!root) + return; + parent = btr_search_node(gc->parent, root); + if (!parent) + return; + PARA_INFO_LOG("activating fd %d\n", gc->fd); + list_move(&gc->node, &active_grab_client_list); + gc->btrn = btr_new_node(&(struct btr_node_description) + EMBRACE(.name = name, .parent = parent)); + if (!gc->task.pre_select) { + gc->task.pre_select = gc_pre_select; + gc->task.post_select = gc_post_select; + snprintf(gc->task.status, sizeof(gc->task.status) - 1, "%s", name); + gc->task.status[sizeof(gc->task.status) - 1] = '\0'; + register_task(&gc->task); + } } /** * Activate inactive grab clients if possible. * - * \param slot_num Audiod's slot for the new audio file. - * \param audio_format_num The number of the audio format of the new audio file. - * \param fc The filter chain containing the activated filters. - * * This is called from audiod.c when the current audio file changes. It loops * over all inactive grab clients and checks each grab client's configuration * to determine if the client in question wishes to grab the new stream. If - * yes, this grab client is moved from the inactive grab client list to an - * appropriate filter_node. + * yes, this grab client is moved from the inactive to the active grab client list. * - * \sa filter_chain_info::filters, inactive_grab_client_list, - * activate_grab_client. + * This function also garbage collects all grab clients whose tasks have been + * unscheduled. */ -void activate_inactive_grab_clients(int slot_num, int audio_format_num, - struct filter_chain *fc) +void activate_grab_clients(void) { struct grab_client *gc, *tmp; - int filter_num; - struct filter_node *fn; list_for_each_entry_safe(gc, tmp, &inactive_grab_client_list, node) { -// PARA_INFO_LOG("checking inactive grab client %p\n", gc); - if (gc->conf->slot_arg >= 0 && gc->conf->slot_arg != slot_num) + if (gc->task.error == -E_TASK_UNREGISTERED) { + list_del(&gc->node); + free(gc); continue; - if (gc->audio_format_num >= 0 && gc->audio_format_num != - audio_format_num) + } + gc_activate(gc); + } +} + +static int gc_close(struct grab_client *gc, int err) +{ + btr_remove_node(gc->btrn); + btr_free_node(gc->btrn); + gc->btrn = NULL; + PARA_INFO_LOG("closing gc: %s\n", para_strerror(-err)); + list_move(&gc->node, &inactive_grab_client_list); + if (err == -E_GC_WRITE || (gc->flags & GF_ONE_SHOT)) { + /* + * We must not free the gc structure here as it contains ->task + * which is still used because this function is called from + * post_select(). + */ + close(gc->fd); + free(gc->parent); + free(gc->name); + return 1; + } + gc_activate(gc); + return 0; +} + +static void gc_post_select(__a_unused struct sched *s, struct task *t) +{ + struct grab_client *gc = container_of(t, struct grab_client, task); + struct btr_node *btrn = gc->btrn; + int ret; + size_t sz; + char *buf; + + t->error = 0; + ret = btr_node_status(btrn, 0, BTR_NT_LEAF); + if (ret == 0) + return; + if (ret < 0) + goto err; + sz = btr_next_buffer(btrn, &buf); + assert(sz != 0); + ret = gc_write(gc, buf, sz); + if (ret < 0) + goto err; + if (ret > 0) + btr_consume(btrn, ret); + return; +err: + t->error = gc_close(gc, ret)? ret : 0; +} + +static int gc_check_args(int argc, char **argv, struct grab_client *gc) +{ + int i; + + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (arg[0] != '-') + break; + if (!strcmp(arg, "--")) { + i++; + break; + } + if (!strncmp(arg, "-m", 2)) { + if (*(arg + 3)) + return -E_GC_SYNTAX; + switch(*(arg + 2)) { + case 's': + gc->mode = GM_SLOPPY; + continue; + case 'a': + gc->mode = GM_AGGRESSIVE; + continue; + case 'p': + gc->mode = GM_PEDANTIC; + continue; + default: + return -E_GC_SYNTAX; + } + } + if (!strcmp(arg, "-o")) { + gc->flags |= GF_ONE_SHOT; continue; - filter_num = gc->conf->filter_num_arg; - if (filter_num >= num_filters(gc->audio_format_num)) + } + if (!strncmp(arg, "-p=", 3)) { + gc->parent = para_strdup(arg + 3); continue; - fn = fc->filter_nodes + filter_num; - activate_grab_client(gc, fn); + } + if (!strncmp(arg, "-n=", 3)) { + gc->name = para_strdup(arg + 3); + continue; + } + return -E_GC_SYNTAX; } + if (i != argc) + return -E_GC_SYNTAX; + return 1; } /** * Check the command line options and allocate a grab_client structure. * * \param fd The file descriptor of the client. - * \param line The command line. - * \param err Non-zero if an error occurred. + * \param argc Argument count. + * \param argv Argument vector. * * If the command line options given by \a argc and \a argv are valid. * allocate a struct grab_client and initialize it with this valid - * configuration. Moreover, add the new grab client to the inactive list. + * configuration. * - * \return On success, this function returns a pointer to the newly created - * struct. On errors, it returns NULL and sets \a err appropriately. + * If the new grab client can be added to an existing buffer tree, activate it. + * Otherwise, add it to the inactive list for later activation. * - * \sa grab_client, inactive_grab_client_list, activate_grab_client, - * filter_node::callbacks. + * \return Standard. */ -/* - * argc, argv get freed when com_grab() returns, so we have to make a - * copy. - */ -struct grab_client *grab_client_new(int fd, char *line, int *err) +int grab_client_new(int fd, int argc, char **argv) { int ret; struct grab_client *gc = para_calloc(sizeof(struct grab_client)); - gc->conf = para_calloc(sizeof(struct grab_client_args_info)); - - ret = grab_client_cmdline_parser_string(line, gc->conf, "grab"); - *err = -E_GC_SYNTAX; - if (ret) - goto err_out; - *err = -E_GC_HELP_GIVEN; - if (gc->conf->help_given) - goto err_out; - *err = -E_GC_VERSION_GIVEN; - if (gc->conf->version_given) + ret = gc_check_args(argc, argv, gc); + if (ret < 0) goto err_out; - *err = check_gc_args(gc); - if (*err < 0) - goto err_out; - if (gc->conf->input_grab_given) { - gc->fcb.input_cb = gc_write; - gc->fcb.output_cb = NULL; - } else { - gc->fcb.output_cb = gc_write; - gc->fcb.input_cb = NULL; - } gc->fd = fd; - gc->fcb.close = gc_close; - gc->fcb.data = gc; - add_inactive_gc(gc); - return gc; + para_list_add(&gc->node, &inactive_grab_client_list); + gc_activate(gc); + return 1; err_out: - free(gc->conf); free(gc); - return NULL; -} - -/** - * Initialize the grabbing subsystem. - * - * This has to be called once during startup before any other function from - * grab_client.c may be used. It initializes \a inactive_grab_client_list. - */ -void init_grabbing(void) -{ - PARA_INFO_LOG("grab init\n"); - INIT_LIST_HEAD(&inactive_grab_client_list); + return ret; } diff --git a/grab_client.h b/grab_client.h index 2832f093..88b95483 100644 --- a/grab_client.h +++ b/grab_client.h @@ -1,49 +1,10 @@ /* - * Copyright (C) 2006 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file grab_client.h exported symbols from grab_client.c */ -#include "config.h" -/** - * handle blocking writes for the grab client fds - * - * - pedantic: close fd if write would block - * - sloppy: ignore the data and do not write - * - aggressive: write anyway (default) - * - */ -enum grab_mode {GRAB_SLOPPY, GRAB_AGGRESSIVE, GRAB_PEDANTIC}; - -/** describes one active grab client - * - * \sa filter_callback, filter_node::callbacks - */ -struct grab_client { -/** the file descriptor to send the grabbed stream to */ - int fd; -/** the command line options for this grab client */ - struct grab_client_args_info *conf; -/** pedantic, sloppy, or aggressive, computed from command line */ - enum grab_mode mode; -/** non-zero if the write() to \a fd failed */ - int error; -/** the number of the desired audio format, computed from command line */ - int audio_format_num; -/** the callback data which gets attached to a suitable filter_node */ - struct filter_callback fcb; -/** all grab clients belong either to a filter node or to the inactive list */ - struct list_head node; -/** the number of command line options */ - int argc; -/** pointers to the command line options */ - char **argv; -}; - -__malloc struct grab_client *grab_client_new(int fd, char *line, int *err); -void activate_inactive_grab_clients(int slot_num, int audio_format_num, - struct filter_chain *fc); -void activate_grab_client(struct grab_client *gc, struct filter_node *fn); -void init_grabbing(void); +int grab_client_new(int fd, int argc, char **argv); +void activate_grab_clients(void); diff --git a/gui.c b/gui.c index 01cca6e6..8e70dd93 100644 --- a/gui.c +++ b/gui.c @@ -1,19 +1,21 @@ /* - * Copyright (C) 1998-2009 Andre Noll + * Copyright (C) 1998-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file gui.c Curses-based interface for paraslash. */ +#include #include #include #include +#include + #include "gui.cmdline.h" #include "para.h" #include "gui.h" #include "string.h" -#include #include "ringbuffer.h" #include "fd.h" #include "error.h" @@ -43,7 +45,7 @@ struct rb_entry { size_t len; int color; }; -struct ringbuffer *bot_win_rb; +static struct ringbuffer *bot_win_rb; #define NUM_LINES(len) (1 + (len) / bot.cols) static unsigned scroll_position; @@ -52,7 +54,7 @@ static int cmd_died, curses_active; static pid_t cmd_pid; static int command_pipe = -1; -static int audiod_pipe = -1; +static int stat_pipe = -1; static struct gui_args_info conf; enum {GETCH_MODE, COMMAND_MODE, EXTERNAL_MODE}; @@ -89,8 +91,8 @@ struct stat_item { static struct gui_theme theme; -int _argc; -char **_argv; +static int _argc; +static char **_argv; static void com_help(void); static void com_reread_conf(void); @@ -108,7 +110,7 @@ static void com_scroll_down(void); static void com_page_up(void); static void com_page_down(void); -struct gui_command command_list[] = { +static struct gui_command command_list[] = { { .key = "?", .name = "help", @@ -189,38 +191,6 @@ struct gui_command command_list[] = { } }; -static int para_open_audiod_pipe(char *cmd) -{ - int fds[3] = {0, 1, 0}; - pid_t pid; - int ret = para_exec_cmdline_pid(&pid, cmd, fds); - if (ret < 0) - return ret; - ret = mark_fd_nonblocking(fds[1]); - if (ret > 0) - return fds[1]; - close(fds[1]); - return ret; -} - -static int read_audiod_pipe(int fd, line_handler_t *line_handler) -{ - static char buf[4096]; - const ssize_t bufsize = sizeof(buf) - 1; - static ssize_t loaded; - ssize_t ret; - - if (loaded >= bufsize) - loaded = 0; - ret = read(fd, buf + loaded, bufsize - loaded); - if (ret > 0) { - loaded += ret; - buf[loaded] = '\0'; - loaded = for_each_line(buf, loaded, line_handler, NULL); - } - return ret; -} - static int find_cmd_byname(char *name) { int i; @@ -312,20 +282,23 @@ static void add_spaces(WINDOW* win, unsigned int num) * print aligned string to curses window. This function always prints * exactly len chars. */ -static int align_str(WINDOW* win, const char *string, unsigned int len, +static int align_str(WINDOW* win, char *str, unsigned int len, unsigned int align) { - int num; /* of spaces */ - char *str; + int i, num; /* of spaces */ - if (!win || !string) + if (!win || !str) return -1; - num = len - strlen(string); - str = para_strdup(string); + num = len - strlen(str); if (num < 0) { str[len] = '\0'; num = 0; } + /* replace control characters by spaces */ + for (i = 0; i < len && str[i]; i++) { + if (str[i] == '\n' || str[i] == '\r' || str[i] == '\f') + str[i] = ' '; + } if (align == LEFT) { waddstr(win, str); add_spaces(win, num); @@ -337,7 +310,6 @@ static int align_str(WINDOW* win, const char *string, unsigned int len, waddstr(win, str[0]? str: ""); add_spaces(win, num - num / 2); } - free(str); return 1; } @@ -360,10 +332,14 @@ __printf_2_3 static void print_in_bar(int color, const char *fmt,...) */ static void print_status_bar(void) { + char *tmp; + if (!curses_active) return; + tmp = para_strdup(STANDARD_STATUS_BAR); wmove(sb.win, 0, 0); - align_str(sb.win, STANDARD_STATUS_BAR, sb.cols, CENTER); + align_str(sb.win, tmp, sb.cols, CENTER); + free(tmp); wrefresh(sb.win); } @@ -687,6 +663,70 @@ static void print_stat_item(int i) wrefresh(top.win); } +static int update_item(int item_num, char *buf) +{ + char **c = stat_content + item_num; + + free(*c); + if (buf && buf[0]) + goto dup; + switch (item_num) { + case SI_ARTIST: + *c = para_strdup("(artist tag not set)"); + goto print; + case SI_TITLE: + *c = para_strdup("(title tag not set)"); + goto print; + case SI_YEAR: + *c = para_strdup("????"); + goto print; + case SI_ALBUM: + *c = para_strdup("(album tag not set)"); + goto print; + case SI_COMMENT: + *c = para_strdup("(comment tag not set)"); + goto print; + } +dup: + *c = para_strdup(buf); +print: + print_stat_item(item_num); + return 1; +} + +static int read_stat_pipe(fd_set *rfds) +{ + static char *buf; + static int bufsize, loaded; + int ret, ret2; + size_t sz; + + if (stat_pipe < 0) + return 0; + if (loaded >= bufsize) { + if (bufsize > 1000 * 1000) { + loaded = 0; + return 0; + } + bufsize += bufsize + 1000; + buf = para_realloc(buf, bufsize); + } + assert(loaded < bufsize); + ret = read_nonblock(stat_pipe, buf + loaded, bufsize - loaded, + rfds, &sz); + loaded += sz; + ret2 = for_each_stat_item(buf, loaded, update_item); + if (ret < 0 || ret2 < 0) { + loaded = 0; + return ret2 < 0? ret2 : ret; + } + sz = ret2; /* what is left */ + if (sz > 0 && sz < loaded) + memmove(buf, buf + loaded - sz, sz); + loaded = sz; + return 1; +} + static void print_all_items(void) { int i; @@ -769,26 +809,6 @@ reap_next_child: goto reap_next_child; } -/* - * print status line if line starts with known command. - */ -static int check_stat_line(char *line, __a_unused void *data) -{ - int i; - -// PARA_INFO_LOG("%s: checking: %s\n", __func__, line); - i = stat_line_valid(line); - if (i >= 0) { - line += strlen(status_item_list[i]) + 1; - if (*line == ' ') - line++; - free(stat_content[i]); - stat_content[i] = para_strdup(line); - print_stat_item(i); - } - return 1; -} - /* * This sucker modifies its first argument. *handler and *arg are * pointers to 0-terminated strings (inside line). Crap. @@ -859,8 +879,8 @@ static void handle_signal(int sig) return; case SIGINT: PARA_WARNING_LOG("caught SIGINT, reset"); - /* Nothing to do. SIGINT killed our child, para_client stat. - * This get noticed by do_select which resets everything + /* Nothing to do. SIGINT killed our child which gets noticed + * by do_select and resets everything. */ return; case SIGUSR1: @@ -873,9 +893,11 @@ static void handle_signal(int sig) } } -static int open_audiod_pipe(void) +static int open_stat_pipe(void) { static int init = 1; + int ret, fds[3] = {0, 1, 0}; + pid_t pid; if (init) init = 0; @@ -887,7 +909,14 @@ static int open_audiod_pipe(void) */ while (sleep(2)) ; /* nothing */ - return para_open_audiod_pipe(conf.stat_cmd_arg); + ret = para_exec_cmdline_pid(&pid, conf.stat_cmd_arg, fds); + if (ret < 0) + return ret; + ret = mark_fd_nonblocking(fds[1]); + if (ret >= 0) + return fds[1]; + close(fds[1]); + return ret; } /* @@ -908,7 +937,7 @@ static int do_select(int mode) { fd_set rfds; int ret; - int max_fileno, cp_numread = 1; + int max_fileno; char command_buf[4096] = ""; int cbo = 0; /* command buf offset */ struct timeval tv; @@ -918,11 +947,10 @@ repeat: // ret = refresh_status(); FD_ZERO(&rfds); max_fileno = 0; - /* audiod pipe */ - if (audiod_pipe < 0) - audiod_pipe = open_audiod_pipe(); - if (audiod_pipe >= 0) - para_fd_set(audiod_pipe, &rfds, &max_fileno); + if (stat_pipe < 0) + stat_pipe = open_stat_pipe(); + if (stat_pipe >= 0) + para_fd_set(stat_pipe, &rfds, &max_fileno); /* signal pipe */ para_fd_set(signal_pipe, &rfds, &max_fileno); /* command pipe only for COMMAND_MODE */ @@ -932,46 +960,41 @@ repeat: if (ret <= 0) goto check_return; /* skip fd checks */ /* signals */ - if (FD_ISSET(signal_pipe, &rfds)) { - int sig_nr = para_next_signal(); - if (sig_nr > 0) - handle_signal(sig_nr); - } + ret = para_next_signal(&rfds); + if (ret > 0) + handle_signal(ret); /* read command pipe if ready */ - if (command_pipe >= 0 && mode == COMMAND_MODE && - FD_ISSET(command_pipe, &rfds)) { - cp_numread = read(command_pipe, command_buf + cbo, - sizeof(command_buf) - 1 - cbo); - if (cp_numread >= 0) - cbo += cp_numread; - else { - if (cp_numread < 0) - PARA_ERROR_LOG("read error (%d)", cp_numread); + if (command_pipe >= 0 && mode == COMMAND_MODE) { + size_t sz; + ret = read_nonblock(command_pipe, command_buf + cbo, + sizeof(command_buf) - 1 - cbo, &rfds, &sz); + cbo += sz; + sz = cbo; + cbo = for_each_line(command_buf, cbo, &add_output_line, NULL); + if (sz != cbo) + wrefresh(bot.win); + if (ret < 0) { + PARA_NOTICE_LOG("closing command pipe: %s", + para_strerror(-ret)); close(command_pipe); command_pipe = -1; + return 0; } } - if (audiod_pipe >= 0 && FD_ISSET(audiod_pipe, &rfds)) - if (read_audiod_pipe(audiod_pipe, check_stat_line) <= 0) { - close(audiod_pipe); - audiod_pipe = -1; - clear_all_items(); - free(stat_content[SI_BASENAME]); - stat_content[SI_BASENAME] = - para_strdup("audiod not running!?"); - print_all_items(); - } + ret = read_stat_pipe(&rfds); + if (ret < 0) { + PARA_NOTICE_LOG("closing stat pipe: %s\n", para_strerror(-ret)); + close(stat_pipe); + stat_pipe = -1; + clear_all_items(); + free(stat_content[SI_BASENAME]); + stat_content[SI_BASENAME] = + para_strdup("stat command terminated!?"); + print_all_items(); + } check_return: switch (mode) { case COMMAND_MODE: - if (cp_numread <= 0 && !cbo) /* command complete */ - return 0; - if (cbo) - cbo = for_each_line(command_buf, cbo, - &add_output_line, NULL); - if (cp_numread <= 0) - cbo = 0; - wrefresh(bot.win); ret = wgetch(top.win); if (ret != ERR && ret != KEY_RESIZE) { if (command_pipe) { @@ -1327,31 +1350,30 @@ static void handle_command(int c) /* first check user's key bindings */ for (i = 0; i < conf.key_map_given; ++i) { - char tmp[MAXLINE], *handler, *arg; + char *tmp, *handler, *arg; - strcpy(tmp, conf.key_map_arg[i]); - if (!split_key_map(tmp, &handler, &arg)) + tmp = para_strdup(conf.key_map_arg[i]); + if (!split_key_map(tmp, &handler, &arg)) { + free(tmp); return; - if (!strcmp(tmp, km_keyname(c))) { - if (*handler == 'd') { - display_cmd(arg); - return; - } - if (*handler == 'x') { - external_cmd(arg); - return; - } - if (*handler == 'p') { - client_cmd_cmdline(arg); - return; - } - if (*handler == 'i') { - int num = find_cmd_byname(arg); - if (num >= 0) - command_list[num].handler(); - return; - } } + if (strcmp(tmp, km_keyname(c))) { + free(tmp); + continue; + } + if (*handler == 'd') + display_cmd(arg); + else if (*handler == 'x') + external_cmd(arg); + else if (*handler == 'p') + client_cmd_cmdline(arg); + else if (*handler == 'i') { + int num = find_cmd_byname(arg); + if (num >= 0) + command_list[num].handler(); + } + free(tmp); + return; } /* not found, check internal key bindings */ for (i = 0; command_list[i].handler; i++) { diff --git a/gui.h b/gui.h index abc3191b..f8c6712b 100644 --- a/gui.h +++ b/gui.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007-2009 Andre Noll + * Copyright (C) 2007-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ diff --git a/gui_theme.c b/gui_theme.c index 9345f0ed..ffb47d43 100644 --- a/gui_theme.c +++ b/gui_theme.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -281,32 +281,59 @@ static void init_theme_colorful_blackness(struct gui_theme *t) d[SI_AMPLIFICATION].y = 27; d[SI_AMPLIFICATION].len = 8; - d[SI_AUDIO_FILE_INFO].prefix = ""; - d[SI_AUDIO_FILE_INFO].postfix = ""; - d[SI_AUDIO_FILE_INFO].fg = COLOR_GREEN; - d[SI_AUDIO_FILE_INFO].bg = COLOR_BLACK; - d[SI_AUDIO_FILE_INFO].align = CENTER; - d[SI_AUDIO_FILE_INFO].x = 0; - d[SI_AUDIO_FILE_INFO].y = 43; - d[SI_AUDIO_FILE_INFO].len = 100; - - d[SI_TAGINFO1].prefix = ""; - d[SI_TAGINFO1].postfix = ""; - d[SI_TAGINFO1].fg = COLOR_GREEN; - d[SI_TAGINFO1].bg = COLOR_BLACK; - d[SI_TAGINFO1].align = CENTER; - d[SI_TAGINFO1].x = 0; - d[SI_TAGINFO1].y = 53; - d[SI_TAGINFO1].len = 100; - - d[SI_TAGINFO2].prefix = ""; - d[SI_TAGINFO2].postfix = ""; - d[SI_TAGINFO2].fg = COLOR_GREEN; - d[SI_TAGINFO2].bg = COLOR_BLACK; - d[SI_TAGINFO2].align = CENTER; - d[SI_TAGINFO2].x = 0; - d[SI_TAGINFO2].y = 63; - d[SI_TAGINFO2].len = 100; + d[SI_TECHINFO].prefix = ""; + d[SI_TECHINFO].postfix = ""; + d[SI_TECHINFO].fg = COLOR_GREEN; + d[SI_TECHINFO].bg = COLOR_BLACK; + d[SI_TECHINFO].align = CENTER; + d[SI_TECHINFO].x = 0; + d[SI_TECHINFO].y = 43; + d[SI_TECHINFO].len = 100; + + d[SI_TITLE].prefix = ""; + d[SI_TITLE].postfix = ","; + d[SI_TITLE].fg = COLOR_GREEN; + d[SI_TITLE].bg = COLOR_BLACK; + d[SI_TITLE].align = RIGHT; + d[SI_TITLE].x = 0; + d[SI_TITLE].y = 53; + d[SI_TITLE].len = 45; + + d[SI_ARTIST].prefix = " by "; + d[SI_ARTIST].postfix = ""; + d[SI_ARTIST].fg = COLOR_GREEN; + d[SI_ARTIST].bg = COLOR_BLACK; + d[SI_ARTIST].align = LEFT; + d[SI_ARTIST].x = 45; + d[SI_ARTIST].y = 53; + d[SI_ARTIST].len = 45; + + d[SI_YEAR].prefix = "("; + d[SI_YEAR].postfix = ")"; + d[SI_YEAR].fg = COLOR_GREEN; + d[SI_YEAR].bg = COLOR_BLACK; + d[SI_YEAR].align = RIGHT; + d[SI_YEAR].x = 90; + d[SI_YEAR].y = 53; + d[SI_YEAR].len = 10; + + d[SI_ALBUM].prefix = "A: "; + d[SI_ALBUM].postfix = ","; + d[SI_ALBUM].fg = COLOR_GREEN; + d[SI_ALBUM].bg = COLOR_BLACK; + d[SI_ALBUM].align = RIGHT; + d[SI_ALBUM].x = 0; + d[SI_ALBUM].y = 63; + d[SI_ALBUM].len = 50; + + d[SI_COMMENT].prefix = " C: "; + d[SI_COMMENT].postfix = ""; + d[SI_COMMENT].fg = COLOR_GREEN; + d[SI_COMMENT].bg = COLOR_BLACK; + d[SI_COMMENT].align = LEFT; + d[SI_COMMENT].x = 50; + d[SI_COMMENT].y = 63; + d[SI_COMMENT].len = 50; d[SI_AFS_MODE].prefix = ""; d[SI_AFS_MODE].postfix = ""; diff --git a/hash.h b/hash.h index 8a14e3a7..6dfddbed 100644 --- a/hash.h +++ b/hash.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007-2009 Andre Noll + * Copyright (C) 2007-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ diff --git a/http_recv.c b/http_recv.c index 7a751e4a..53ef8e60 100644 --- a/http_recv.c +++ b/http_recv.c @@ -1,11 +1,12 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file http_recv.c paraslash's http receiver */ +#include #include #include @@ -20,9 +21,7 @@ #include "net.h" #include "string.h" #include "fd.h" - -/** the output buffer size of the http receiver */ -#define BUFSIZE (32 * 1024) +#include "buffer_tree.h" /** * the possible states of a http receiver node @@ -32,48 +31,40 @@ enum http_recv_status {HTTP_CONNECTED, HTTP_SENT_GET_REQUEST, HTTP_STREAMING}; /** - * data specific to the http receiver + * Data specific to the http receiver. * * Each running instance of the http receiver reserves space for one such struct. */ struct private_http_recv_data { -/** - * - * - * the current status of the http receiver node - * - * It gets initialized to \p HTTP_CONNECTED by the open function of the - * http receiver. - * - * \sa receiver::open, receiver_node. - */ + /** + * The current status of the http receiver node. + * + * It gets initialized to \p HTTP_CONNECTED by the open function of the + * http receiver. + * + * \sa receiver::open, receiver_node. + */ enum http_recv_status status; -/** - * - * - * the file descriptor used for receiving the http stream - * - * The pre_select function of the http receiver adds this file descriptor to - * the set of file decriptors which are checked for reading/writing (depending - * on the current status) by the select loop of the application (para_audiod or - * para_recv). - * - * The post_select function of the http receiver uses \a fd, if ready, to - * establish the http connection, and updates \a status according to the new - * state of the connection. As soon as \a status is \p HTTP_STREAMING, \a fd is - * going to be only checked for reading. If data is available, it is read into - * the output buffer of the receiver node by post_select. - * - * \sa receiver::pre_select receiver::post_select receiver_node, http_recv_status - */ + /** + * The file descriptor used for receiving the http stream. + * + * The pre_select function of the http receiver adds this file descriptor to + * the set of file descriptors which are checked for reading/writing (depending + * on the current status) by the select loop of the application (para_audiod or + * para_recv). + * + * The post_select function of the http receiver uses \a fd, if ready, to + * establish the http connection, and updates \a status according to the new + * state of the connection. As soon as \a status is \p HTTP_STREAMING, \a fd is + * going to be only checked for reading. If data is available, it is read into + * the output buffer of the receiver node by post_select. + * + * \sa receiver::pre_select receiver::post_select receiver_node, http_recv_status + */ int fd; + struct btr_pool *btrp; }; -static void http_shutdown(void) -{ - return; -} - static char *make_request_msg(void) { char *ret, *hn = para_hostname(); @@ -89,63 +80,72 @@ static void http_recv_pre_select(struct sched *s, struct task *t) struct private_http_recv_data *phd = rn->private_data; t->error = 0; + if (generic_recv_pre_select(s, t) <= 0) + return; if (phd->status == HTTP_CONNECTED) para_fd_set(phd->fd, &s->wfds, &s->max_fileno); else para_fd_set(phd->fd, &s->rfds, &s->max_fileno); } - static void http_recv_post_select(struct sched *s, struct task *t) { struct receiver_node *rn = container_of(t, struct receiver_node, task); struct private_http_recv_data *phd = rn->private_data; + struct btr_node *btrn = rn->btrn; + int ret; + char *buf; + size_t sz, n; - if (rn->output_error && *rn->output_error < 0) { - t->error = *rn->output_error; + t->error = 0; + ret = btr_node_status(btrn, 0, BTR_NT_ROOT); + if (ret < 0) + goto err; + if (ret == 0) return; - } - if (phd->status == HTTP_CONNECTED) { + if (phd->status == HTTP_CONNECTED) { char *rq; if (!FD_ISSET(phd->fd, &s->wfds)) return; rq = make_request_msg(); PARA_INFO_LOG("sending http request\n"); - t->error = send_va_buffer(phd->fd, "%s", rq); + ret = send_va_buffer(phd->fd, "%s", rq); free(rq); - if (t->error >= 0) - phd->status = HTTP_SENT_GET_REQUEST; + if (ret < 0) + goto err; + phd->status = HTTP_SENT_GET_REQUEST; return; } - if (!FD_ISSET(phd->fd, &s->rfds)) - return; if (phd->status == HTTP_SENT_GET_REQUEST) { - t->error = recv_pattern(phd->fd, HTTP_OK_MSG, strlen(HTTP_OK_MSG)); - if (t->error >= 0) { - PARA_INFO_LOG("received ok msg, streaming\n"); - phd->status = HTTP_STREAMING; - } - return; - } - if (rn->loaded >= BUFSIZE) { - t->error = -E_HTTP_RECV_OVERRUN; + ret = read_pattern(phd->fd, HTTP_OK_MSG, strlen(HTTP_OK_MSG), &s->rfds); + if (ret < 0) + goto err; + if (ret == 0) + return; + PARA_INFO_LOG("received ok msg, streaming\n"); + phd->status = HTTP_STREAMING; return; } - t->error = recv_bin_buffer(phd->fd, rn->buf + rn->loaded, - BUFSIZE - rn->loaded); - if (t->error > 0) { - rn->loaded += t->error; + ret = -E_HTTP_RECV_OVERRUN; + sz = btr_pool_get_buffer(phd->btrp, &buf); + if (sz == 0) + goto err; + ret = read_nonblock(phd->fd, buf, sz, &s->rfds, &n); + if (n > 0) + btr_add_output_pool(phd->btrp, n, btrn); + if (ret >= 0) return; - } - if (!t->error) - t->error = -E_RECV_EOF; +err: + btr_remove_node(rn->btrn); + t->error = ret; } static void http_recv_close(struct receiver_node *rn) { struct private_http_recv_data *phd = rn->private_data; + close(phd->fd); - free(rn->buf); + btr_pool_free(phd->btrp); free(rn->private_data); } @@ -163,8 +163,8 @@ static int http_recv_open(struct receiver_node *rn) { struct private_http_recv_data *phd; struct http_recv_args_info *conf = rn->conf; - int fd, ret = makesock(AF_UNSPEC, IPPROTO_TCP, 0, conf->host_arg, - conf->port_arg); + int fd, ret = para_connect_simple(IPPROTO_TCP, conf->host_arg, + conf->port_arg); if (ret < 0) return ret; @@ -174,19 +174,25 @@ static int http_recv_open(struct receiver_node *rn) close(fd); return ret; } - rn->buf = para_calloc(BUFSIZE); rn->private_data = phd = para_calloc(sizeof(struct private_http_recv_data)); phd->fd = fd; phd->status = HTTP_CONNECTED; + phd->btrp = btr_pool_new("http_recv", 320 * 1024); return 1; } +static void http_recv_free_config(void *conf) +{ + http_recv_cmdline_parser_free(conf); + free(conf); +} + /** - * the init function of the http receiver + * The init function of the http receiver. * - * \param r pointer to the receiver struct to initialize + * \param r Pointer to the receiver struct to initialize. * - * Just initialize all function pointers of \a r. + * This initializes all function pointers of \a r. */ void http_recv_init(struct receiver *r) { @@ -197,8 +203,8 @@ void http_recv_init(struct receiver *r) r->close = http_recv_close; r->pre_select = http_recv_pre_select; r->post_select = http_recv_post_select; - r->shutdown = http_shutdown; r->parse_config = http_recv_parse_config; + r->free_config = http_recv_free_config; r->help = (struct ggo_help) { .short_help = http_recv_args_info_help, .detailed_help = http_recv_args_info_detailed_help diff --git a/http_send.c b/http_send.c index 0c48934a..086b600f 100644 --- a/http_send.c +++ b/http_send.c @@ -1,13 +1,16 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file http_send.c paraslash's http sender */ +#include #include #include +#include +#include #include "para.h" #include "error.h" @@ -17,9 +20,9 @@ #include "afs.h" #include "server.h" #include "http.h" -#include "vss.h" #include "list.h" #include "send.h" +#include "vss.h" #include "close_on_fork.h" #include "net.h" #include "fd.h" @@ -75,6 +78,66 @@ static void http_shutdown_clients(void) shutdown_clients(hss); } +static int queue_chunk_or_shutdown(struct sender_client *sc, + struct sender_status *ss, const char *buf, size_t num_bytes) +{ + int ret = cq_enqueue(sc->cq, buf, num_bytes); + if (ret < 0) + shutdown_client(sc, ss); + return ret; +} + +/** + * Send one chunk of audio data to a connected client. + * + * \param sc The client. + * \param ss The sender. + * \param current_chunk The number of the chunk to write. + * \param buf The data to write. + * \param len The number of bytes of \a buf. + * \param header_buf The audio file header. + * \param header_len The number of bytes of \a header_buf. + * + * On errors, the client is shut down. If only a part of the buffer could be + * written, the remainder is put into the chunk queue for that client. + */ +static void http_send_chunk(struct sender_client *sc, struct sender_status *ss, + long unsigned current_chunk, const char *buf, size_t len, + const char *header_buf, size_t header_len) +{ + int ret; + + if (!sc->header_sent && current_chunk) { + if (header_buf && header_len > 0) { + ret = queue_chunk_or_shutdown(sc, ss, header_buf, header_len); + if (ret < 0) + goto out; + } + } + sc->header_sent = 1; + ret = send_queued_chunks(sc->fd, sc->cq); + if (ret < 0) { + shutdown_client(sc, ss); + goto out; + } + if (!len) + goto out; + if (!ret) { /* still data left in the queue */ + ret = queue_chunk_or_shutdown(sc, ss, buf, len); + goto out; + } + ret = write_nonblock(sc->fd, buf, len); + if (ret < 0) { + shutdown_client(sc, ss); + goto out; + } + if (ret != len) + ret = queue_chunk_or_shutdown(sc, ss, buf + ret, len - ret); +out: + if (ret < 0) + PARA_NOTICE_LOG("%s\n", para_strerror(-ret)); +} + static void http_send(long unsigned current_chunk, __a_unused long unsigned chunks_sent, const char *buf, size_t len, const char *header_buf, size_t header_len) @@ -83,10 +146,10 @@ static void http_send(long unsigned current_chunk, list_for_each_entry_safe(sc, tmp, &hss->client_list, node) { struct private_http_sender_data *phsd = sc->private_data; - if (phsd->status != HTTP_STREAMING) - continue; - send_chunk(sc, hss, 0, current_chunk, buf, len, header_buf, - header_len); + + if (phsd->status == HTTP_STREAMING) + http_send_chunk(sc, hss, current_chunk, buf, len, + header_buf, header_len); } } @@ -94,23 +157,20 @@ static void http_post_select(fd_set *rfds, __a_unused fd_set *wfds) { struct sender_client *sc, *tmp; struct private_http_sender_data *phsd; + int ret; - if (hss->listen_fd < 0) - return; list_for_each_entry_safe(sc, tmp, &hss->client_list, node) { phsd = sc->private_data; switch (phsd->status) { case HTTP_STREAMING: /* nothing to do */ break; case HTTP_CONNECTED: /* need to recv get request */ - if (FD_ISSET(sc->fd, rfds)) { - if (recv_pattern(sc->fd, HTTP_GET_MSG, MAXLINE) - < 0) { - phsd->status = HTTP_INVALID_GET_REQUEST; - } else { - phsd->status = HTTP_GOT_GET_REQUEST; - PARA_INFO_LOG("received get request\n"); - } + ret = read_pattern(sc->fd, HTTP_GET_MSG, MAXLINE, rfds); + if (ret < 0) + phsd->status = HTTP_INVALID_GET_REQUEST; + else if (ret > 0) { + phsd->status = HTTP_GOT_GET_REQUEST; + PARA_INFO_LOG("received get request\n"); } break; case HTTP_GOT_GET_REQUEST: /* need to send ok msg */ @@ -123,9 +183,7 @@ static void http_post_select(fd_set *rfds, __a_unused fd_set *wfds) break; } } - if (!FD_ISSET(hss->listen_fd, rfds)) - return; - sc = accept_sender_client(hss); + sc = accept_sender_client(hss, rfds); if (!sc) return; phsd = para_malloc(sizeof(*phsd)); @@ -194,6 +252,7 @@ void http_send_init(struct sender *s) s->pre_select = http_pre_select; s->post_select = http_post_select; s->shutdown_clients = http_shutdown_clients; + s->resolve_target = NULL; s->help = generic_sender_help; s->client_cmds[SENDER_ON] = http_com_on; s->client_cmds[SENDER_OFF] = http_com_off; diff --git a/imdct.c b/imdct.c new file mode 100644 index 00000000..5f48ba44 --- /dev/null +++ b/imdct.c @@ -0,0 +1,409 @@ +/* + * FFT/IFFT transforms. + * + * Extracted 2009 from mplayer 2009-02-10 libavcodec/fft.c and libavcodec/mdct.c + * + * Copyright (c) 2008 Loren Merritt + * Copyright (c) 2002 Fabrice Bellard + * Partly based on libdjbfft by D. J. Bernstein + * + * Licensed under the GNU Lesser General Public License. + * For licencing details see COPYING.LIB. + */ + +/** + * \file imdct.c Inverse modified discrete cosine transform. + */ + +#include +#include +#include +#include +#include + +#include "para.h" +#include "error.h" +#include "string.h" +#include "imdct.h" +#include "wma.h" + +typedef float fftsample_t; + +/** Canonical representation of a complex number. */ +struct fft_complex { + /** Real part. */ + fftsample_t re; + /** Imaginary part. */ + fftsample_t im; +}; + +/** FFT Lookup table. */ +struct fft_context { + /** Number of bits of this instance of the FFT. */ + int nbits; + /** The lookup table for cosine values. */ + uint16_t *revtab; +}; + +struct mdct_context { + /** Size of MDCT (number of input data * 2). */ + int n; + /** n = 2^n bits. */ + int nbits; + /** Cosine table for pre/post rotation. */ + fftsample_t *tcos; + /** Sine table for pre/post rotation. */ + fftsample_t *tsin; + /** The context for the underlying fast Fourier transform. */ + struct fft_context fft; +}; + +/** cos(2 * pi * x / n) for 0 <= x <= n / 4, followed by its reverse */ +#define COSINE_TAB(n) fftsample_t cos_ ## n[n / 2] __a_aligned(16) + +COSINE_TAB(16); +COSINE_TAB(32); +COSINE_TAB(64); +COSINE_TAB(128); +COSINE_TAB(256); +COSINE_TAB(512); +COSINE_TAB(1024); +COSINE_TAB(2048); +COSINE_TAB(4096); +COSINE_TAB(8192); +COSINE_TAB(16384); +COSINE_TAB(32768); +COSINE_TAB(65536); + +static fftsample_t *cos_tabs[] = { + cos_16, cos_32, cos_64, cos_128, cos_256, cos_512, cos_1024, cos_2048, + cos_4096, cos_8192, cos_16384, cos_32768, cos_65536, +}; + +static int split_radix_permutation(int i, int n) +{ + int m; + if (n <= 2) + return i & 1; + m = n >> 1; + if ((i & m) == 0) + return split_radix_permutation(i, m) * 2; + m >>= 1; + if ((i & m) == 0) + return split_radix_permutation(i, m) * 4 + 1; + else + return split_radix_permutation(i, m) * 4 - 1; +} + +#define BF(x, y, a, b) {\ + x = a - b;\ + y = a + b;\ +} + +#define BUTTERFLIES(a0, a1, a2, a3) {\ + BF(t3, t5, t5, t1);\ + BF(a2.re, a0.re, a0.re, t5);\ + BF(a3.im, a1.im, a1.im, t3);\ + BF(t4, t6, t2, t6);\ + BF(a3.re, a1.re, a1.re, t4);\ + BF(a2.im, a0.im, a0.im, t6);\ +} + +/* + * Force loading all the inputs before storing any. This is slightly slower for + * small data, but avoids store->load aliasing for addresses separated by large + * powers of 2. + */ +#define BUTTERFLIES_BIG(a0, a1, a2, a3) {\ + fftsample_t r0 = a0.re, i0 = a0.im, r1 = a1.re, i1 = a1.im;\ + BF(t3, t5, t5, t1);\ + BF(a2.re, a0.re, r0, t5);\ + BF(a3.im, a1.im, i1, t3);\ + BF(t4, t6, t2, t6);\ + BF(a3.re, a1.re, r1, t4);\ + BF(a2.im, a0.im, i0, t6);\ +} + +#define TRANSFORM(a0, a1, a2, a3, wre,wim) {\ + t1 = a2.re * wre + a2.im * wim;\ + t2 = a2.im * wre - a2.re * wim;\ + t5 = a3.re * wre - a3.im * wim;\ + t6 = a3.im * wre + a3.re * wim;\ + BUTTERFLIES(a0, a1, a2, a3)\ +} + +#define TRANSFORM_ZERO(a0, a1, a2, a3) {\ + t1 = a2.re;\ + t2 = a2.im;\ + t5 = a3.re;\ + t6 = a3.im;\ + BUTTERFLIES(a0, a1, a2, a3)\ +} + +/* z[0...8n - 1], w[1...2n - 1] */ +#define PASS(name)\ +static void name(struct fft_complex *z, const fftsample_t *wre, unsigned int n)\ +{\ + fftsample_t t1, t2, t3, t4, t5, t6;\ + int o1 = 2 * n;\ + int o2 = 4 * n;\ + int o3 = 6 * n;\ + const fftsample_t *wim = wre + o1;\ + n--;\ +\ + TRANSFORM_ZERO(z[0], z[o1], z[o2], z[o3]);\ + TRANSFORM(z[1], z[o1 + 1], z[o2 + 1], z[o3 + 1], wre[1], wim[-1]);\ + do {\ + z += 2;\ + wre += 2;\ + wim -= 2;\ + TRANSFORM(z[0], z[o1], z[o2], z[o3], wre[0], wim[0]);\ + TRANSFORM(z[1], z[o1 + 1], z[o2 + 1], z[o3 + 1], wre[1], wim[-1]);\ + } while (--n);\ +} + +PASS(pass) +#undef BUTTERFLIES +#define BUTTERFLIES BUTTERFLIES_BIG + +#define DECL_FFT(n, n2, n4)\ +static void fft##n(struct fft_complex *z)\ +{\ + fft ## n2(z);\ + fft ## n4(z + n4 * 2);\ + fft ## n4(z + n4 * 3);\ + pass(z, cos_ ## n, n4 / 2);\ +} + +static void fft4(struct fft_complex *z) +{ + fftsample_t t1, t2, t3, t4, t5, t6, t7, t8; + + BF(t3, t1, z[0].re, z[1].re); + BF(t8, t6, z[3].re, z[2].re); + BF(z[2].re, z[0].re, t1, t6); + BF(t4, t2, z[0].im, z[1].im); + BF(t7, t5, z[2].im, z[3].im); + BF(z[3].im, z[1].im, t4, t8); + BF(z[3].re, z[1].re, t3, t7); + BF(z[2].im, z[0].im, t2, t5); +} + +static void fft8(struct fft_complex *z) +{ + fftsample_t t1, t2, t3, t4, t5, t6, t7, t8; + + fft4(z); + + BF(t1, z[5].re, z[4].re, -z[5].re); + BF(t2, z[5].im, z[4].im, -z[5].im); + BF(t3, z[7].re, z[6].re, -z[7].re); + BF(t4, z[7].im, z[6].im, -z[7].im); + BF(t8, t1, t3, t1); + BF(t7, t2, t2, t4); + BF(z[4].re, z[0].re, z[0].re, t1); + BF(z[4].im, z[0].im, z[0].im, t2); + BF(z[6].re, z[2].re, z[2].re, t7); + BF(z[6].im, z[2].im, z[2].im, t8); + + TRANSFORM(z[1], z[3], z[5], z[7], M_SQRT1_2, M_SQRT1_2); +} + +static void fft16(struct fft_complex *z) +{ + fftsample_t t1, t2, t3, t4, t5, t6; + + fft8(z); + fft4(z + 8); + fft4(z + 12); + + TRANSFORM_ZERO(z[0], z[4], z[8], z[12]); + TRANSFORM(z[2], z[6], z[10], z[14], M_SQRT1_2, M_SQRT1_2); + TRANSFORM(z[1], z[5], z[9], z[13], cos_16[1], cos_16[3]); + TRANSFORM(z[3], z[7], z[11], z[15], cos_16[3], cos_16[1]); +} + +DECL_FFT(32, 16, 8) +DECL_FFT(64, 32, 16) +DECL_FFT(128, 64, 32) +DECL_FFT(256, 128, 64) +DECL_FFT(512, 256, 128) + +DECL_FFT(1024, 512, 256) +DECL_FFT(2048, 1024, 512) +DECL_FFT(4096, 2048, 1024) +DECL_FFT(8192, 4096, 2048) +DECL_FFT(16384, 8192, 4096) +DECL_FFT(32768, 16384, 8192) +DECL_FFT(65536, 32768, 16384) + +static void (*fft_dispatch[]) (struct fft_complex *) = { + fft4, fft8, fft16, fft32, fft64, fft128, fft256, fft512, fft1024, + fft2048, fft4096, fft8192, fft16384, fft32768, fft65536, +}; + +static void fft(struct fft_context *s, struct fft_complex *z) +{ + fft_dispatch[s->nbits - 2] (z); +} + +/* complex multiplication: p = a * b */ +#define CMUL(pre, pim, are, aim, bre, bim) \ +{\ + fftsample_t _are = (are);\ + fftsample_t _aim = (aim);\ + fftsample_t _bre = (bre);\ + fftsample_t _bim = (bim);\ + (pre) = _are * _bre - _aim * _bim;\ + (pim) = _are * _bim + _aim * _bre;\ +} + +/** + * Compute the middle half of the inverse MDCT of size N = 2^nbits + * + * Thus excluding the parts that can be derived by symmetry. + * + * \param output N/2 samples. + * \param input N/2 samples. + */ +static void imdct_half(struct mdct_context *s, fftsample_t *output, + const fftsample_t *input) +{ + int k, n8, n4, n2, n, j; + const uint16_t *revtab = s->fft.revtab; + const fftsample_t *tcos = s->tcos; + const fftsample_t *tsin = s->tsin; + const fftsample_t *in1, *in2; + struct fft_complex *z = (struct fft_complex *)output; + + n = 1 << s->nbits; + n2 = n >> 1; + n4 = n >> 2; + n8 = n >> 3; + + /* pre rotation */ + in1 = input; + in2 = input + n2 - 1; + for (k = 0; k < n4; k++) { + j = revtab[k]; + CMUL(z[j].re, z[j].im, *in2, *in1, tcos[k], tsin[k]); + in1 += 2; + in2 -= 2; + } + fft(&s->fft, z); + + /* post rotation + reordering */ + output += n4; + for (k = 0; k < n8; k++) { + fftsample_t r0, i0, r1, i1; + CMUL(r0, i1, z[n8 - k - 1].im, z[n8 - k - 1].re, + tsin[n8 - k - 1], tcos[n8 - k - 1]); + CMUL(r1, i0, z[n8 + k].im, z[n8 + k].re, tsin[n8 + k], + tcos[n8 + k]); + z[n8 - k - 1].re = r0; + z[n8 - k - 1].im = i0; + z[n8 + k].re = r1; + z[n8 + k].im = i1; + } +} + +/** + * Compute the inverse MDCT. + * + * \param ctx The initialized context structure. + * \param output N samples. + * \param input N/2 samples. + * + * \sa \ref imdct_init(). + */ +void imdct(struct mdct_context *ctx, float *output, const float *input) +{ + int k; + int n = 1 << ctx->nbits; + int n2 = n >> 1; + int n4 = n >> 2; + + imdct_half(ctx, output + n4, input); + + for (k = 0; k < n4; k++) { + output[k] = -output[n2 - k - 1]; + output[n - k - 1] = output[n2 + k]; + } +} + +static int fft_init(struct fft_context *s, int nbits) +{ + int i, j, n; + + if (nbits < 2 || nbits > 16) + return -E_FFT_BAD_PARAMS; + s->nbits = nbits; + n = 1 << nbits; + + s->revtab = para_malloc(n * sizeof(uint16_t)); + for (j = 4; j <= nbits; j++) { + int k = 1 << j; + double freq = 2 * M_PI / k; + fftsample_t *tab = cos_tabs[j - 4]; + for (i = 0; i <= k / 4; i++) + tab[i] = cos(i * freq); + for (i = 1; i < k / 4; i++) + tab[k / 2 - i] = tab[i]; + } + for (i = 0; i < n; i++) + s->revtab[-split_radix_permutation(i, n) & (n - 1)] = i; + return 0; +} + +/** + * Initialize the inverse modified cosine transform. + * + * \param nbits The number of bits to use (4 <= \a nbits <= 18). + * + * \param result Opaque structure that must be passed to \ref imdct(). + * + * \return Standard. + */ +int imdct_init(int nbits, struct mdct_context **result) +{ + int ret, n, n4, i; + double alpha; + struct mdct_context *s; + + s = para_calloc(sizeof(*s)); + n = 1 << nbits; + s->nbits = nbits; + s->n = n; + n4 = n >> 2; + s->tcos = para_malloc(n4 * sizeof(fftsample_t)); + s->tsin = para_malloc(n4 * sizeof(fftsample_t)); + + for (i = 0; i < n4; i++) { + alpha = 2 * M_PI * (i + 1.0 / 8.0) / n; + s->tcos[i] = -cos(alpha); + s->tsin[i] = -sin(alpha); + } + ret = fft_init(&s->fft, s->nbits - 2); + if (ret < 0) + goto fail; + *result = s; + return 0; +fail: + freep(&s->tcos); + freep(&s->tsin); + free(s); + return ret; +} + +/** + * Deallocate imdct resources. + * + * \param ctx The pointer obtained by imdct_init(). + */ +void imdct_end(struct mdct_context *ctx) +{ + free(ctx->tcos); + free(ctx->tsin); + free(ctx->fft.revtab); + free(ctx); +} diff --git a/imdct.h b/imdct.h new file mode 100644 index 00000000..442b98bf --- /dev/null +++ b/imdct.h @@ -0,0 +1,7 @@ +/** \file imdct.h Exported symbols from imdct.c. */ + +struct mdct_context; + +int imdct_init(int nbits, struct mdct_context **result); +void imdct(struct mdct_context *s, float *output, const float *input); +void imdct_end(struct mdct_context *s); diff --git a/ipc.c b/ipc.c index c64cc89c..c1069ad9 100644 --- a/ipc.c +++ b/ipc.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ diff --git a/list.h b/list.h index de04ab9e..e4b5a1f6 100644 --- a/list.h +++ b/list.h @@ -33,6 +33,10 @@ struct list_head { struct list_head *prev; }; +/** Define an initialized list head. */ +#define INITIALIZED_LIST_HEAD(name) struct list_head name = { &(name), &(name) } + + /** must be called before using any other list functions */ #define INIT_LIST_HEAD(ptr) do { \ (ptr)->next = (ptr); (ptr)->prev = (ptr); \ @@ -200,3 +204,25 @@ static inline int list_empty(const struct list_head *head) n = list_entry(pos->member.prev, typeof(*pos), member); \ &pos->member != (head); \ pos = n, n = list_entry(n->member.prev, typeof(*n), member)) + +/** + * Get the first element from a list + * \param ptr the list head to take the element from. + * \param type The type of the struct this is embedded in. + * \param member The name of the list_struct within the struct. + * + * Note that list is expected to be not empty. + */ +#define list_first_entry(ptr, type, member) \ + list_entry((ptr)->next, type, member) + +/** + * Test whether a list has just one entry. + * + * \param head The list to test. + */ +static inline int list_is_singular(const struct list_head *head) +{ + return !list_empty(head) && (head->next == head->prev); +} + diff --git a/mm.c b/mm.c new file mode 100644 index 00000000..a9f4b9bd --- /dev/null +++ b/mm.c @@ -0,0 +1,356 @@ +/* + * Copyright (C) 2007-2011 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file mm.c Paraslash's mood methods. */ + +#include +#include +#include +#include + +#include "para.h" +#include "error.h" +#include "string.h" +#include "afh.h" +#include "afs.h" +#include "mm.h" + +#define MOOD_COMPARATORS \ + MC(LESS, <) \ + MC(LESS_OR_EQUAL, <=) \ + MC(EQUAL, =) \ + MC(EQUAL2, ==) \ + MC(NOT_EQUAL, !=) \ + MC(NOT_EQUAL2, <>) \ + MC(GREATER, >) \ + MC(GREATER_OR_EQUAL, >=) \ + +#define MC(a, b) MC_ ## a, +enum mood_comparator_id {MOOD_COMPARATORS NUM_MOOD_COMPARATORS}; +#undef MC +#define MC(a, b) # b, +static const char const *mood_comparators[] = {MOOD_COMPARATORS}; +#undef MC + +static int parse_mood_comparator(const char *word) +{ + int i; + + for (i = 0; i < NUM_MOOD_COMPARATORS; i++) + if (!strcmp(word, mood_comparators[i])) + return i; + return -E_MOOD_SYNTAX; +} + +struct mm_compare_num_data { + /** <, <=, =, !=, >=, or >. */ + enum mood_comparator_id id; + /** The value given at the mood line. */ + int32_t arg; +}; + +static int mm_compare_num_score_function(int32_t val, + const struct mm_compare_num_data *cnd) +{ + int res; + int32_t arg = cnd->arg; + + switch (cnd->id) { + case MC_LESS: + res = val < arg; break; + case MC_LESS_OR_EQUAL: + res = val <= arg; break; + case MC_EQUAL: + case MC_EQUAL2: + res = val == arg; break; + case MC_NOT_EQUAL: + case MC_NOT_EQUAL2: + res = val != arg; break; + case MC_GREATER: + res = val > arg; break; + case MC_GREATER_OR_EQUAL: + res = val >= arg; break; + default: + PARA_EMERG_LOG("BUG: invalid mood comparator\n"); + exit(EXIT_FAILURE); + } + return res? 100 : -100; +} + +static int mm_compare_num_parser(int argc, char **argv, void **private) +{ + int ret; + enum mood_comparator_id id; + int32_t arg; + struct mm_compare_num_data *cnd; + if (argc != 2) + return -E_MOOD_SYNTAX; + ret = parse_mood_comparator(argv[1]); + if (ret < 0) + return ret; + id = ret; + ret = para_atoi32(argv[2], &arg); + if (ret < 0) + return ret; + cnd = para_malloc(sizeof(struct mm_compare_num_data)); + cnd->id = id; + cnd->arg = arg; + *private = cnd; + return 1; +} + +static int mm_regex_parser(int argc, char **argv, void **private) +{ + regex_t *preg; + int ret; + + if (argc != 1) + return -E_MOOD_SYNTAX; + preg = para_malloc(sizeof(*preg)); + ret = para_regcomp(preg, argv[1], REG_EXTENDED | REG_NOSUB); + if (ret < 0) { + free(preg); + return ret; + } + *private = preg; + return 1; +} + +static int mm_regex_score_function(const regex_t *preg, const char *pattern) +{ + return regexec(preg, pattern, 0, NULL, 0) == 0? 100 : -100; +} + +static void mm_regex_cleanup(void *private) +{ + regex_t *preg = private; + regfree(preg); + free(preg); +} + +static int mm_artist_matches_score_function(__a_unused const char *path, + __a_unused const struct afs_info *afsi, + const struct afh_info *afhi, + const void *private) +{ + return mm_regex_score_function(private, afhi->tags.artist); +} + +static int mm_title_matches_score_function(__a_unused const char *path, + __a_unused const struct afs_info *afsi, + const struct afh_info *afhi, + const void *private) +{ + return mm_regex_score_function(private, afhi->tags.title); +} + +static int mm_album_matches_score_function(__a_unused const char *path, + __a_unused const struct afs_info *afsi, + const struct afh_info *afhi, + const void *private) +{ + return mm_regex_score_function(private, afhi->tags.album); +} + +static int mm_comment_matches_score_function(__a_unused const char *path, + __a_unused const struct afs_info *afsi, + const struct afh_info *afhi, + const void *private) +{ + return mm_regex_score_function(private, afhi->tags.comment); +} + +static int mm_bitrate_score_function(__a_unused const char *path, + __a_unused const struct afs_info *afsi, + const struct afh_info *afhi, + const void *private) +{ + return mm_compare_num_score_function(afhi->bitrate, private); +} + +static int mm_frequency_score_function(__a_unused const char *path, + __a_unused const struct afs_info *afsi, + const struct afh_info *afhi, + const void *private) +{ + return mm_compare_num_score_function(afhi->frequency, private); +} + +static int mm_channels_score_function(__a_unused const char *path, + __a_unused const struct afs_info *afsi, + const struct afh_info *afhi, + const void *private) +{ + return mm_compare_num_score_function(afhi->channels, private); +} + +static int mm_num_played_score_function(__a_unused const char *path, + const struct afs_info *afsi, + __a_unused const struct afh_info *afhi, + const void *private) +{ + return mm_compare_num_score_function(afsi->num_played, private); +} + +struct mm_year_data { + /** Comparator and year given at the mood line. */ + struct mm_compare_num_data *cnd; + /** Used to detect Y2K issues. */ + int32_t current_year; +}; + +static int mm_year_parser(int argc, char **argv, void **private) +{ + int ret = -E_MOOD_SYNTAX; + struct mm_year_data *mmyd = para_malloc(sizeof(*mmyd)); + time_t current_time; + struct tm *gmt; + + ret = mm_compare_num_parser(argc, argv, (void **)&mmyd->cnd); + if (ret < 0) + goto err; + current_time = time(NULL); + gmt = gmtime(¤t_time); + /* tm_year is the number of years since 1900 */ + mmyd->current_year = gmt->tm_year + 1900; + *private = mmyd; + return 1; +err: + free(mmyd); + return ret; +} + +static int mm_year_score_function(__a_unused const char *path, + __a_unused const struct afs_info *afsi, + const struct afh_info *afhi, + const void *private) +{ + const struct mm_year_data *mmyd = private; + int32_t tag_year; + int ret = para_atoi32(afhi->tags.year, &tag_year); + + if (ret < 0) /* year tag not present or not a number */ + return -100; + if (tag_year < 0) + return -100; + /* try to work around Y2K issues */ + if (tag_year < 100) { + tag_year += 1900; + if (tag_year + 100 <= mmyd->current_year) + tag_year += 100; /* assume tag_year >= 2000 */ + } + return mm_compare_num_score_function(tag_year, mmyd->cnd); +} + +static void mm_year_cleanup(void *private) +{ + struct mm_year_data *mmyd = private; + + free(mmyd->cnd); + free(mmyd); +} + +static int mm_no_attributes_set_parser(int argc, __a_unused char **argv, + __a_unused void **ignored) +{ + return argc? -E_MOOD_SYNTAX : 1; +} + +static int mm_no_attributes_set_score_function(__a_unused const char *path, + const struct afs_info *afsi, + __a_unused const struct afh_info *afhi, + __a_unused const void *data) +{ + if (!afsi->attributes) + return 100; + return -100; +} + +static int mm_path_matches_score_function(const char *path, + __a_unused const struct afs_info *afsi, + __a_unused const struct afh_info *afhi, + const void *data) +{ + if (fnmatch(data, path, 0)) + return -100; + return 100; +} + +static int mm_path_matches_parser(int argc, char **argv, void **data) +{ + if (argc != 1) + return -E_MOOD_SYNTAX; + *data = para_strdup(argv[1]); + return 1; +} + +static void mm_path_matches_cleanup(void *data) +{ + free(data); +} + +static int mm_is_set_parser(int argc, char **argv, void **bitnum) +{ + int ret; + unsigned char c, *res; + + if (argc != 1) + return -E_MOOD_SYNTAX; + ret = get_attribute_bitnum_by_name(argv[1], &c); + if (ret < 0) + return ret; + res = para_malloc(1); + *res = c; + *bitnum = res; + return 1; +} + +static int mm_is_set_score_function(__a_unused const char *path, + __a_unused const struct afs_info *afsi, + __a_unused const struct afh_info *afhi, + const void *data) +{ + const unsigned char *bn = data; + if (afsi->attributes & (1ULL << *bn)) + return 100; + return -100; +} + +#define DEFINE_MOOD_METHOD(_name) \ +.parser = mm_ ## _name ## _parser, \ +.score_function = mm_ ## _name ## _score_function, \ +.name = #_name + +#define DEFINE_MOOD_METHOD_WITH_CLEANUP(_name) \ + DEFINE_MOOD_METHOD(_name), \ + .cleanup = mm_ ## _name ## _cleanup + +#define DEFINE_REGEX_MOOD_METHOD(_name) \ + .name = #_name, \ + .parser = mm_regex_parser, \ + .score_function = mm_ ## _name ## _score_function, \ + .cleanup = mm_regex_cleanup + +#define DEFINE_COMPARE_NUM_MOOD_METHOD(_name) \ + .name = #_name, \ + .parser = mm_compare_num_parser, \ + .score_function = mm_ ## _name ## _score_function + +const struct mood_method mood_methods[] = { + {DEFINE_MOOD_METHOD(no_attributes_set)}, + {DEFINE_MOOD_METHOD(is_set)}, + {DEFINE_MOOD_METHOD_WITH_CLEANUP(path_matches)}, + {DEFINE_MOOD_METHOD_WITH_CLEANUP(year)}, + {DEFINE_REGEX_MOOD_METHOD(artist_matches)}, + {DEFINE_REGEX_MOOD_METHOD(title_matches)}, + {DEFINE_REGEX_MOOD_METHOD(album_matches)}, + {DEFINE_REGEX_MOOD_METHOD(comment_matches)}, + {DEFINE_COMPARE_NUM_MOOD_METHOD(bitrate)}, + {DEFINE_COMPARE_NUM_MOOD_METHOD(frequency)}, + {DEFINE_COMPARE_NUM_MOOD_METHOD(channels)}, + {DEFINE_COMPARE_NUM_MOOD_METHOD(num_played)}, + {.parser = NULL} +}; diff --git a/mm.h b/mm.h new file mode 100644 index 00000000..ebc968d3 --- /dev/null +++ b/mm.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2007-2011 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file mm.h Symbols and declarations for mood methods. */ + +/** + * Assign scores according to a mood_method. + * + * Each mood_method has its own mood_score_function. The first three parameters + * passed to that function are informations about the audio file whose score is + * to be computed. The data argument depends on the mood method this function + * is used for. It usually is the argument given at the end of a mood line. + * + * Mood score functions must return values between -100 and +100 inclusively. + * Boolean score functions should always return either -100 or +100. + * + * \sa struct mood_method, mood_parser. + */ +typedef int mood_score_function(const char *path, const struct afs_info *afsi, + const struct afh_info *afhi, const void *data); + +/** + * Pre-process a mood line. + * + * The mood_parser of a mood_method is called once at mood open time for each + * line of the current mood definition that contains the mood_method's name as + * a keyword. The line is passed to the mood_parser as the first argument. The + * mood_parser must determine whether the line is syntactically correct and + * return a positive value if so and a negative value otherwise. + * + * Some mood parsers pre-process the data given in the mood line to compute a + * structure which depends of the particular mood_method and which is used + * later in the mood_score_function of the mood_method. The mood_parser may + * store a pointer to its structure via the void** pointer. + * + * \sa mood_open(), mood_cleanup_function, mood_score_function. + */ +typedef int mood_parser(int, char **, void **); + +/** + * Deallocate resources which were allocated by the mood_parser. + * + * This optional function of a mood_method is used to free any resources + * allocated in mood_open() by the mood_parser. The argument passed is a + * pointer to the mood_method specific data structure that was returned by the + * mood_parser. + * + * \sa mood_parser. + */ +typedef void mood_cleanup_function(void *); + +/** + * Used for scoring and to determine whether a file is admissible. + */ +struct mood_method { + /** The name of the method. */ + const char *name; + /** Pointer to the mood parser. */ + mood_parser *parser; + /** Pointer to the score function */ + mood_score_function *score_function; + /** Optional cleanup function. */ + mood_cleanup_function *cleanup; +}; + +/** The array of available mood methods. */ +extern const struct mood_method mood_methods[]; diff --git a/mood.c b/mood.c index 9f1d7745..732df0a3 100644 --- a/mood.c +++ b/mood.c @@ -1,12 +1,14 @@ /* - * Copyright (C) 2007-2009 Andre Noll + * Copyright (C) 2007-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file mood.c Paraslash's mood handling functions. */ -#include +#include +#include + #include "para.h" #include "error.h" #include "string.h" @@ -14,6 +16,7 @@ #include "afs.h" #include "list.h" #include "ipc.h" +#include "mm.h" /** * Contains statistical data of the currently admissible audio files. @@ -32,67 +35,7 @@ struct afs_statistics { /** Number of admissible files */ unsigned num; }; -struct afs_statistics statistics; - -/** - * Assign scores according to a mood_method. - * - * Each mood_method has its own mood_score_function. The first three parameters - * passed to that function are informations about the audio file whose score is - * to be computed. The data argument depends on the mood method this function - * is used for. It usually is the argument given at the end of a mood line. - * - * Mood score functions must return values between -100 and +100 inclusively. - * Boolean score functions should always return either -100 or +100. - * - * \sa struct mood_method, mood_parser. - */ -typedef int mood_score_function(const char *path, const struct afs_info *afsi, - const struct afh_info *afhi, const void *data); - -/** - * Pre-process a mood line. - * - * The mood_parser of a mood_method is called once at mood open time for each - * line of the current mood definition that contains the mood_method's name as - * a keyword. The line is passed to the mood_parser as the first argument. The - * mood_parser must determine whether the line is syntactically correct and - * return a positive value if so and a negative value otherwise. - * - * Some mood parsers pre-process the data given in the mood line to compute a - * structure which depends of the particular mood_method and which is used - * later in the mood_score_function of the mood_method. The mood_parser may - * store a pointer to its structure via the second argument. - * - * \sa mood_open(), mood_cleanup_function, mood_score_function. - */ -typedef int mood_parser(const char *, void **); - -/** - * Deallocate resources which were allocated by the mood_parser. - * - * This optional function of a mood_method is used to free any resources - * allocated in mood_open() by the mood_parser. The argument passed is a - * pointer to the mood_method specific data structure that was returned by the - * mood_parser. - * - * \sa mood_parser. - */ -typedef void mood_cleanup_function(void *); - -/** - * Used for scoring and to determine whether a file is admissible. - */ -struct mood_method { - /** The name of the method. */ - const char *name; - /** Pointer to the mood parser. */ - mood_parser *parser; - /** Pointer to the score function */ - mood_score_function *score_function; - /** Optional cleanup function. */ - mood_cleanup_function *cleanup; -}; +static struct afs_statistics statistics; /** * Each line of the current mood corresponds to a mood_item. @@ -164,113 +107,17 @@ static uint64_t int_sqrt(uint64_t x) return res; } -static int mm_no_attributes_set_parser(const char *arg, __a_unused void **ignored) -{ - if (arg && *arg) - PARA_WARNING_LOG("ignored junk at eol: %s\n", arg); - return 1; -} - -static int mm_no_attributes_set_score_function(__a_unused const char *path, - const struct afs_info *afsi, - __a_unused const struct afh_info *afhi, - __a_unused const void *data) -{ - if (!afsi->attributes) - return 100; - return -100; -} - -static int mm_played_rarely_score_function(__a_unused const char *path, - const struct afs_info *afsi, - __a_unused const struct afh_info *afhi, - __a_unused const void *data) -{ - unsigned num; - int ret = get_num_admissible_files(&num); - - if (ret < 0) - return 0; - if (statistics.num_played_sum - num * afsi->num_played - > int_sqrt(statistics.num_played_qd * num)) - return 100; - return -100; -} - -static int mm_played_rarely_parser(const char *arg, __a_unused void **ignored) -{ - if (arg && *arg) - PARA_WARNING_LOG("ignored junk at eol: %s\n", arg); - return 1; -} - -static int mm_path_matches_score_function(const char *path, - __a_unused const struct afs_info *afsi, - __a_unused const struct afh_info *afhi, - const void *data) -{ - if (fnmatch(data, path, 0)) - return -100; - return 100; -} - -static int mm_path_matches_parser(const char *arg, void **data) -{ - *data = para_strdup(arg); - return 1; -} - -static void mm_path_matches_cleanup(void *data) -{ - free(data); -} - -static int mm_is_set_parser(const char *arg, void **bitnum) -{ - unsigned char *c = para_malloc(1); - int ret = get_attribute_bitnum_by_name(arg, c); - - if (ret >= 0) - *bitnum = c; - else - free(c); - return ret; -} - -static int mm_is_set_score_function(__a_unused const char *path, - __a_unused const struct afs_info *afsi, - __a_unused const struct afh_info *afhi, - const void *data) +/* returns 1 if row matches score item, 0 if not. */ +static int get_item_score(struct mood_item *item, const struct afs_info *afsi, + const struct afh_info *afhi, const char *path, long *score, + long *score_arg_sum) { - const unsigned char *bn = data; - if (afsi->attributes & (1ULL << *bn)) - return 100; - return -100; -} - -/* returns 1 if row matches score item, 0 if not, negative on errors */ -static int get_item_score(const struct osl_row *row, struct mood_item *item, - long *score, long *score_arg_sum) -{ - struct afs_info afsi; - struct afh_info afhi; - char *path; int ret, match = 1; *score_arg_sum += item->random_score? 100 : PARA_ABS(item->score_arg); ret = 100; if (item->method) { - ret = get_afsi_of_row(row, &afsi); - if (ret< 0) - return ret; - ret = get_afhi_of_row(row, &afhi); - if (ret< 0) - return ret; - free(afhi.info_string); /* don't need the tag info */ - ret = get_audio_file_path_of_row(row, &path); - if (ret< 0) - return ret; - ret = item->method->score_function(path, &afsi, &afhi, + ret = item->method->score_function(path, afsi, afhi, item->parser_data); if ((ret < 0 && !item->logical_not) || (ret >= 0 && item->logical_not)) match = 0; /* no match */ @@ -289,24 +136,32 @@ static int compute_mood_score(const struct osl_row *aft_row, struct mood *m, struct mood_item *item; int ret, match = 0; long score_arg_sum = 0, score = 0, item_score; + struct afs_info afsi; + struct afh_info afhi; + char *path; if (!m) return -E_NO_MOOD; + ret = get_afsi_of_row(aft_row, &afsi); + if (ret< 0) + return ret; + ret = get_afhi_of_row(aft_row, &afhi); + if (ret< 0) + return ret; + ret = get_audio_file_path_of_row(aft_row, &path); + if (ret< 0) + return ret; /* reject audio file if it matches any entry in the deny list */ list_for_each_entry(item, &m->deny_list, mood_item_node) { - ret = get_item_score(aft_row, item, &item_score, + ret = get_item_score(item, &afsi, &afhi, path, &item_score, &score_arg_sum); - if (ret < 0) - return ret; if (ret > 0) /* not admissible */ return 0; score += item_score; } list_for_each_entry(item, &m->accept_list, mood_item_node) { - ret = get_item_score(aft_row, item, &item_score, + ret = get_item_score(item, &afsi, &afhi, path, &item_score, &score_arg_sum); - if (ret < 0) - return ret; if (ret == 0) continue; match = 1; @@ -316,10 +171,8 @@ static int compute_mood_score(const struct osl_row *aft_row, struct mood *m, if (!match && !list_empty(&m->accept_list)) return 0; list_for_each_entry(item, &m->score_list, mood_item_node) { - ret = get_item_score(aft_row, item, &item_score, + ret = get_item_score(item, &afsi, &afhi, path, &item_score, &score_arg_sum); - if (ret < 0) - return ret; score += item_score; } if (score_arg_sum) @@ -328,23 +181,6 @@ static int compute_mood_score(const struct osl_row *aft_row, struct mood *m, return 1; } -#define DEFINE_MOOD_METHOD(_name) \ -.parser = mm_ ## _name ## _parser, \ -.score_function = mm_ ## _name ## _score_function, \ -.name = #_name - -#define DEFINE_MOOD_METHOD_WITH_CLEANUP(_name) \ - DEFINE_MOOD_METHOD(_name), \ - .cleanup = mm_ ## _name ## _cleanup - -static const struct mood_method mood_methods[] = { - {DEFINE_MOOD_METHOD(no_attributes_set)}, - {DEFINE_MOOD_METHOD(played_rarely)}, - {DEFINE_MOOD_METHOD(is_set)}, - {DEFINE_MOOD_METHOD_WITH_CLEANUP(path_matches)}, - {.parser = NULL} -}; - static void cleanup_list_entry(struct mood_item *item) { if (item->method && item->method->cleanup) @@ -412,17 +248,17 @@ static int parse_mood_line(char *mood_line, void *data) { struct mood_line_parser_data *mlpd = data; char **argv; - char *delim = " \t"; unsigned num_words; char **w; int i, ret; enum mood_line_type mlt = ML_INVALID; struct mood_item *mi = NULL; - char *buf = para_strdup(mood_line); mlpd->line_num++; - num_words = split_args(buf, &argv, delim); - ret = 1; + ret = create_argv(mood_line, " \t", &argv); + if (ret < 0) + return ret; + num_words = ret; if (!num_words) /* empty line */ goto out; w = argv; @@ -495,8 +331,8 @@ check_for_if: ret = -E_MOOD_SYNTAX; if (!mood_methods[i].parser) goto out; - w++; - ret = mood_methods[i].parser(*w, &mi->parser_data); + ret = mood_methods[i].parser(num_words - 1 - (w - argv), w, + &mi->parser_data); if (ret < 0) goto out; mi->method = &mood_methods[i]; @@ -513,8 +349,7 @@ success: (mlt == ML_DENY? "deny" : "score"), mi->method); ret = 1; out: - free(argv); - free(buf); + free_argv(argv); if (ret >= 0) return ret; if (mi) { @@ -961,7 +796,6 @@ void close_current_mood(void) memset(&statistics, 0, sizeof(statistics)); } - /** * Change the current mood. * @@ -993,7 +827,7 @@ int change_current_mood(char *mood_name) .data = mood_name, .size = strlen(mood_name) + 1 }; - ret = osl_get_row(moods_table, BLOBCOL_NAME, &obj, &row); + ret = osl(osl_get_row(moods_table, BLOBCOL_NAME, &obj, &row)); if (ret < 0) { PARA_NOTICE_LOG("no such mood: %s\n", mood_name); return ret; @@ -1039,7 +873,7 @@ out: * * \sa mood_open(), mood_close(). */ -int reload_current_mood(void) +static int reload_current_mood(void) { int ret; char *mood_name = NULL; @@ -1056,6 +890,17 @@ int reload_current_mood(void) return ret; } +/** + * Notification callback for the moods table. + * + * \param event Type of the event just occurred. + * \param pb Unused. + * \param data Its type depends on the event. + * + * This function performs actions required due to the occurrence of the given + * event. Possible actions include reload of the current mood and update of the + * score of an audio file. + */ int moods_event_handler(enum afs_events event, __a_unused struct para_buffer *pb, void *data) { @@ -1092,4 +937,3 @@ int moods_event_handler(enum afs_events event, __a_unused struct para_buffer *pb return 1; } } - diff --git a/mood.h b/mood.h new file mode 100644 index 00000000..e9f08111 --- /dev/null +++ b/mood.h @@ -0,0 +1,11 @@ +/* + * Copyright (C) 2007-2011 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file mood.h Functions exported by mood.h. */ + +int change_current_mood(char *mood_name); +void close_current_mood(void); +void mood_check_callback(int fd, __a_unused const struct osl_object *query); diff --git a/mp3_afh.c b/mp3_afh.c index 2d18d5e5..b83343ed 100644 --- a/mp3_afh.c +++ b/mp3_afh.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2009 Andre Noll + * Copyright (C) 2003-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -16,12 +16,12 @@ * Johannes Overmann */ +#include + #include "para.h" #include "error.h" #include "afh.h" #include "string.h" -#include "afs.h" -#include "server.h" /** \cond some defines and structs which are only used in this file */ @@ -126,62 +126,49 @@ static char *get_strings(struct id3_frame *fr) return NULL; } -static char *mp3_get_id3(__a_unused unsigned char *map, - __a_unused size_t numbytes, int fd) +static void mp3_get_id3(__a_unused unsigned char *map, + __a_unused size_t numbytes, int fd, struct taginfo *tags) { int i; struct id3_tag *id3_t; - char *title = NULL, *artist = NULL, *album = NULL, *year = NULL, - *comment = NULL, *result; struct id3_file *id3_f = id3_file_fdopen(fd, ID3_FILE_MODE_READONLY); if (!id3_f) - goto no_tag; + return; id3_t = id3_file_tag(id3_f); - if (!id3_t) - goto no_tag; + if (!id3_t) { + id3_file_close(id3_f); + return; + } for (i = 0; i < id3_t->nframes; i++) { struct id3_frame *fr = id3_t->frames[i]; if (!strcmp(fr->id, "TIT2")) { - if (!title) - title = get_strings(fr); + if (!tags->title) + tags->title = get_strings(fr); continue; } if (!strcmp(fr->id, "TPE1")) { - if (!artist) - artist = get_strings(fr); + if (!tags->artist) + tags->artist = get_strings(fr); continue; } if (!strcmp(fr->id, "TALB")) { - if (!album) - album = get_strings(fr); + if (!tags->album) + tags->album = get_strings(fr); continue; } if (!strcmp(fr->id, "TDRC")) { - if (!year) - year = get_strings(fr); + if (!tags->year) + tags->year = get_strings(fr); continue; } if (!strcmp(fr->id, "COMM")) { - if (!comment) - comment = get_strings(fr); + if (!tags->comment) + tags->comment = get_strings(fr); continue; } } id3_file_close(id3_f); - result = make_taginfo(title, artist, album, year, comment); - free(title); - free(artist); - free(album); - free(year); - free(comment); - return result; -no_tag: - if (id3_f) - id3_file_close(id3_f); - return make_message("%s: (no id3 v1/v2 tag)\n%s:\n", - status_item_list[SI_TAGINFO1], - status_item_list[SI_TAGINFO2]); } #else /* HAVE_LIBID3TAG */ @@ -197,16 +184,15 @@ static char *unpad(char *string) return string; } -static char *mp3_get_id3(unsigned char *map, size_t numbytes, __a_unused int fd) +static void mp3_get_id3(unsigned char *map, size_t numbytes, __a_unused int fd, + struct taginfo *tags) { char title[31], artist[31], album[31], year[5], comment[31]; off_t fpos; if (numbytes < 128 || strncmp("TAG", (char *)map + numbytes - 128, 3)) { PARA_DEBUG_LOG("no id3 v1 tag\n"); - return make_message("%s: (no id3 v1 tag)\n%s:\n", - status_item_list[SI_TAGINFO1], - status_item_list[SI_TAGINFO2]); + return; } fpos = numbytes - 125; memcpy(title, map + fpos, 30); @@ -228,7 +214,11 @@ static char *mp3_get_id3(unsigned char *map, size_t numbytes, __a_unused int fd) unpad(album); unpad(year); unpad(comment); - return make_taginfo(title, artist, album, year, comment); + tags->artist = para_strdup(artist); + tags->title = para_strdup(title); + tags->year = para_strdup(year); + tags->album = para_strdup(album); + tags->comment = para_strdup(comment); } #endif /* HAVE_LIBID3TAG */ @@ -400,11 +390,9 @@ static int mp3_read_info(unsigned char *map, size_t numbytes, int fd, unsigned chunk_table_size = 1000; /* gets increased on demand */ off_t fpos = 0; struct mp3header header; - char *taginfo; afhi->chunks_total = 0; afhi->chunk_table = para_malloc(chunk_table_size * sizeof(uint32_t)); - taginfo = mp3_get_id3(map, numbytes, fd); while (1) { int freq, br; struct timeval tmp, cct; /* current chunk time */ @@ -460,15 +448,11 @@ static int mp3_read_info(unsigned char *map, size_t numbytes, int fd, tv_divide(afhi->chunks_total, &total_time, &afhi->chunk_tv); PARA_DEBUG_LOG("%lu chunks, each %lums\n", afhi->chunks_total, tv2ms(&afhi->chunk_tv)); - tv_scale(3, &afhi->chunk_tv, &afhi->eof_tv); - PARA_DEBUG_LOG("eof timeout: %lu\n", tv2ms(&afhi->eof_tv)); - afhi->info_string = make_message("%s: %cbr, %s\n%s", - status_item_list[SI_AUDIO_FILE_INFO], vbr? 'v' : 'c', - header_mode(&header), taginfo); - free(taginfo); + afhi->techinfo = make_message("%cbr, %s", vbr? 'v' : 'c', + header_mode(&header)); + mp3_get_id3(map, numbytes, fd, &afhi->tags); return 1; err_out: - free(taginfo); PARA_ERROR_LOG("%s\n", para_strerror(-ret)); free(afhi->chunk_table); return ret; diff --git a/mp3dec_filter.c b/mp3dec_filter.c index 24123bc6..82f01418 100644 --- a/mp3dec_filter.c +++ b/mp3dec_filter.c @@ -1,19 +1,23 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file mp3dec_filter.c Paraslash's mp3 decoder. */ +#include +#include +#include + #include "para.h" #include "mp3dec_filter.cmdline.h" #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "filter.h" #include "error.h" -#include #include "string.h" /** Convert a sample value from libmad to a signed short. */ @@ -26,6 +30,7 @@ enum mp3dec_flags { MP3DEC_FLAG_BAD_DATA = 1, /** Some output has already been produced. */ MP3DEC_FLAG_DECODE_STARTED = 2, + MP3DEC_FLAG_NEED_MORE = 4, }; /** Data specific to the mp3dec filter. */ @@ -42,6 +47,10 @@ struct private_mp3dec_data { struct timeval stream_start_barrier; /** Wait until this many input bytes are available. */ size_t input_len_barrier; + /** The number of channels of the current stream. */ + unsigned int channels; + /** Current sample rate in Hz. */ + unsigned int sample_rate; }; static int need_bad_data_delay(struct private_mp3dec_data *pmd, @@ -64,6 +73,7 @@ static int need_bad_data_delay(struct private_mp3dec_data *pmd, */ static int handle_decode_error(struct private_mp3dec_data *pmd, size_t len) { + const struct timeval delay = {0, 60 * 1000}; if (!MAD_RECOVERABLE(pmd->stream.error) && pmd->stream.error != MAD_ERROR_BUFLEN) { PARA_ERROR_LOG("%s\n", mad_stream_errorstr(&pmd->stream)); @@ -80,86 +90,116 @@ static int handle_decode_error(struct private_mp3dec_data *pmd, size_t len) */ pmd->flags |= MP3DEC_FLAG_BAD_DATA; pmd->input_len_barrier = len; - tv_add(now, &(struct timeval){0, 60 * 1000}, - &pmd->stream_start_barrier); + tv_add(now, &delay, &pmd->stream_start_barrier); return 1; } -static ssize_t mp3dec(char *inbuffer, size_t len, struct filter_node *fn) +static size_t used_mad_buffer_bytes(struct mad_stream *s, size_t max) +{ + size_t rv; + + if (!s->next_frame) + return max; + /* we still have some data */ + rv = s->next_frame - s->buffer; + assert(rv <= max); + return rv; +} + +static void mp3dec_close(struct filter_node *fn) +{ + struct private_mp3dec_data *pmd = fn->private_data; + + mad_synth_finish(&pmd->synth); + mad_frame_finish(&pmd->frame); + mad_stream_finish(&pmd->stream); + + free(pmd); + fn->private_data = NULL; +} + +static void mp3dec_post_select(__a_unused struct sched *s, struct task *t) { + struct filter_node *fn = container_of(t, struct filter_node, task); int i, ret; struct private_mp3dec_data *pmd = fn->private_data; - size_t copy = PARA_MIN(len, (size_t)4096); + struct btr_node *btrn = fn->btrn; + size_t loaded, used, len, iqs; + char *inbuffer, *outbuffer; - if (fn->loaded + 16384 > fn->bufsize) - return 0; - if (need_bad_data_delay(pmd, len)) - return 0; - mad_stream_buffer(&pmd->stream, (unsigned char *) inbuffer, copy); +next_buffer: pmd->stream.error = 0; + t->error = 0; + iqs = btr_get_input_queue_size(btrn); + ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL); + if (ret < 0) + goto err; + if (need_bad_data_delay(pmd, iqs)) + return; + if (ret == 0) + return; + btr_merge(btrn, fn->min_iqs); + len = btr_next_buffer(btrn, &inbuffer); + /* + * Decode at most 8K in one go to give the post_select() functions of + * other buffer tree nodes a chance to run. This is necessary to avoid + * buffer underruns on slow machines. + */ + len = PARA_MIN(len, (size_t)8192); + mad_stream_buffer(&pmd->stream, (unsigned char *)inbuffer, len); next_frame: ret = mad_header_decode(&pmd->frame.header, &pmd->stream); if (ret < 0) { - if (pmd->stream.error != MAD_ERROR_BUFLEN && - pmd->stream.error != MAD_ERROR_LOSTSYNC) + used = used_mad_buffer_bytes(&pmd->stream, len); + btr_consume(btrn, used); + if (pmd->stream.error == MAD_ERROR_BUFLEN) { + if (len == iqs && btr_no_parent(btrn)) { + ret = -E_MP3DEC_EOF; + goto err; + } + fn->min_iqs += 100; + goto next_buffer; + } else if (pmd->stream.error != MAD_ERROR_LOSTSYNC) PARA_DEBUG_LOG("header decode: %s\n", mad_stream_errorstr(&pmd->stream)); - goto out; + goto next_buffer; } - fn->fc->samplerate = pmd->frame.header.samplerate; - fn->fc->channels = MAD_NCHANNELS(&pmd->frame.header); + fn->min_iqs = 0; + pmd->sample_rate = pmd->frame.header.samplerate; + pmd->channels = MAD_NCHANNELS(&pmd->frame.header); ret = mad_frame_decode(&pmd->frame, &pmd->stream); if (ret != 0) { - ret = handle_decode_error(pmd, len); + PARA_INFO_LOG("frame decode: %s\n", mad_stream_errorstr(&pmd->stream)); + used = used_mad_buffer_bytes(&pmd->stream, len); + ret = handle_decode_error(pmd, used); + btr_consume(btrn, used); if (ret < 0) - return ret; + goto err; if (ret == 0) - goto out; - ret = copy - (pmd->stream.bufend - pmd->stream.next_frame); - PARA_NOTICE_LOG("skipping %d input bytes\n", ret); - return ret; + goto next_buffer; + return; } mad_synth_frame(&pmd->synth, &pmd->frame); pmd->flags |= MP3DEC_FLAG_DECODE_STARTED; + outbuffer = para_malloc(pmd->synth.pcm.length * 4); + loaded = 0; for (i = 0; i < pmd->synth.pcm.length; i++) { - int s = MAD_TO_SHORT(pmd->synth.pcm.samples[0][i]); - write_int16_host_endian(fn->buf + fn->loaded, s); - fn->loaded += 2; + int sample = MAD_TO_SHORT(pmd->synth.pcm.samples[0][i]); + write_int16_host_endian(outbuffer + loaded, sample); + loaded += 2; if (MAD_NCHANNELS(&pmd->frame.header) == 2) { /* stereo */ - s = MAD_TO_SHORT(pmd->synth.pcm.samples[1][i]); - write_int16_host_endian(fn->buf + fn->loaded, s); - fn->loaded += 2; + sample = MAD_TO_SHORT(pmd->synth.pcm.samples[1][i]); + write_int16_host_endian(outbuffer + loaded, sample); + loaded += 2; } - if (fn->loaded != fn->bufsize) /* output buffer not full */ - continue; - PARA_ERROR_LOG("output buffer full: %zd\n", fn->loaded); - return -E_MP3DEC_OVERRUN; } - if (fn->loaded + 16384 <= fn->bufsize) - goto next_frame; -out: - if (pmd->stream.next_frame) { /* we still have some data */ - size_t off = pmd->stream.bufend - pmd->stream.next_frame; - if (fn->loaded + 16384 <= fn->bufsize && off > 2048) - goto next_frame; - return copy - off; - } - return copy; -} - -static void mp3dec_close(struct filter_node *fn) -{ - struct private_mp3dec_data *pmd = fn->private_data; - - mad_synth_finish(&pmd->synth); - mad_frame_finish(&pmd->frame); - mad_stream_finish(&pmd->stream); - - free(fn->buf); - fn->buf = NULL; - free(pmd); - fn->private_data = NULL; + btr_add_output(outbuffer, loaded, btrn); + goto next_frame; +err: + assert(ret < 0); + t->error = ret; + btr_remove_node(btrn); } static void mp3dec_open(struct filter_node *fn) @@ -171,9 +211,6 @@ static void mp3dec_open(struct filter_node *fn) mad_stream_init(&pmd->stream); mad_frame_init(&pmd->frame); mad_synth_init(&pmd->synth); - fn->loaded = 0; - fn->bufsize = mp3_conf->bufsize_arg * 1024; - fn->buf = para_calloc(fn->bufsize); if (mp3_conf->ignore_crc_given) mad_stream_options(&pmd->stream, MAD_OPTION_IGNORECRC); } @@ -187,11 +224,6 @@ static int mp3dec_parse_config(int argc, char **argv, void **config) ret = -E_MP3DEC_SYNTAX; if (mp3dec_cmdline_parser(argc, argv, mp3_conf)) goto err; - ret = -ERRNO_TO_PARA_ERROR(EINVAL); - if (mp3_conf->bufsize_arg < 32) - goto err; - if (mp3_conf->bufsize_arg >= INT_MAX / 1024) - goto err; *config = mp3_conf; return 1; err: @@ -199,6 +231,18 @@ err: return ret; } +static int mp3dec_execute(struct btr_node *btrn, const char *cmd, char **result) +{ + struct filter_node *fn = btr_context(btrn); + struct private_mp3dec_data *pmd = fn->private_data; + + return decoder_execute(cmd, pmd->sample_rate, pmd->channels, result); +} + +static void mp3dec_free_config(void *conf) +{ + mp3dec_cmdline_parser_free(conf); +} /** * The init function of the mp3dec filter. * @@ -212,9 +256,12 @@ void mp3dec_filter_init(struct filter *f) mp3dec_cmdline_parser_init(&dummy); f->open = mp3dec_open; - f->convert = mp3dec; f->close = mp3dec_close; f->parse_config = mp3dec_parse_config; + f->free_config = mp3dec_free_config; + f->pre_select = generic_filter_pre_select; + f->post_select = mp3dec_post_select; + f->execute = mp3dec_execute; f->help = (struct ggo_help) { .short_help = mp3dec_filter_args_info_help, .detailed_help = mp3dec_filter_args_info_detailed_help diff --git a/net.c b/net.c index ab6a9894..2369dc8c 100644 --- a/net.c +++ b/net.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -27,71 +27,16 @@ #include #include +#include #include "para.h" #include "error.h" +#include "crypt.h" #include "net.h" #include "string.h" +#include "list.h" #include "fd.h" - -/** Information about one encrypted connection. */ -struct crypt_data { - /** Function used to decrypt received data. */ - crypt_function *recv; - /** Function used to encrypt data to be sent. */ - crypt_function *send; - /** - * Context-dependent data (crypt keys), passed verbatim to the above - * crypt functions. - */ - void *private_data; -}; -/** Array holding per fd crypt data. */ -static struct crypt_data *crypt_data_array; -/** Current size of the crypt data array. */ -static unsigned cda_size = 0; - -/** - * Activate encryption for one file descriptor. - * - * \param fd The file descriptor. - * \param recv_f The function used for decrypting received data. - * \param send_f The function used for encrypting before sending. - * \param private_data User data supplied by the caller. - */ -void enable_crypt(int fd, crypt_function *recv_f, crypt_function *send_f, - void *private_data) -{ - if (fd + 1 > cda_size) { - crypt_data_array = para_realloc(crypt_data_array, - (fd + 1) * sizeof(struct crypt_data)); - memset(crypt_data_array + cda_size, 0, - (fd + 1 - cda_size) * sizeof(struct crypt_data)); - cda_size = fd + 1; - } - crypt_data_array[fd].recv = recv_f; - crypt_data_array[fd].send = send_f; - crypt_data_array[fd].private_data = private_data; - PARA_INFO_LOG("rc4 encryption activated for fd %d\n", fd); -} - -/** - * Deactivate encryption for a given fd. - * - * \param fd The file descriptor. - * - * This must be called if and only if \p fd was activated via enable_crypt(). - */ -void disable_crypt(int fd) -{ - if (cda_size < fd + 1) - return; - crypt_data_array[fd].recv = NULL; - crypt_data_array[fd].send = NULL; - crypt_data_array[fd].private_data = NULL; -} - /** * Parse and validate IPv4 address/netmask string. * @@ -144,7 +89,8 @@ static bool is_v4_dot_quad(const char *address) bool result; regex_t r; - assert(!regcomp(&r, "^([0-9]+\\.){3}[0-9]+$", REG_EXTENDED|REG_NOSUB)); + assert(para_regcomp(&r, "^([0-9]+\\.){3}[0-9]+$", + REG_EXTENDED | REG_NOSUB) >= 0); result = regexec(&r, address, 0, NULL, 0) == 0; regfree(&r); return result; @@ -242,6 +188,31 @@ failed: return NULL; } +/** + * Stringify port number, resolve into service name where defined. + * \param port 2-byte port number, in host-byte-order. + * \param transport Transport protocol name (e.g. "udp", "tcp"), or NULL. + * \return Pointer to static result buffer. + * + * \sa getservent(3), services(5), nsswitch.conf(5) + */ +const char *stringify_port(int port, const char *transport) +{ + static char service[NI_MAXSERV]; + + if (port < 0 || port > 0xFFFF) { + snprintf(service, sizeof(service), "undefined (%d)", port); + } else { + struct servent *se = getservbyport(htons(port), transport); + + if (se == NULL) + snprintf(service, sizeof(service), "%u", port); + else + snprintf(service, sizeof(service), "%s", se->s_name); + } + return service; +} + /** * Determine the socket type for a given layer-4 protocol. * @@ -272,14 +243,119 @@ static const char *layer4_name(const unsigned l4type) return "UNKNOWN PROTOCOL"; } +/** + * Flowopts: Transport-layer independent encapsulation of socket options. + * + * These collect individual socket options into a queue, which is disposed of + * directly after makesock(). The 'pre_conn_opt' structure is for internal use + * only and should not be visible elsewhere. + * + * \sa setsockopt(2), makesock() + */ +struct pre_conn_opt { + int sock_level; /**< Second argument to setsockopt() */ + int sock_option; /**< Third argument to setsockopt() */ + char *opt_name; /**< Stringified \a sock_option */ + void *opt_val; /**< Fourth argument to setsockopt() */ + socklen_t opt_len; /**< Fifth argument to setsockopt() */ + + struct list_head node; /**< FIFO, as sockopt order matters. */ +}; + +/** FIFO list of pre-connection socket options to be set */ +struct flowopts { + struct list_head sockopts; +}; + +struct flowopts *flowopt_new(void) +{ + struct flowopts *new = para_malloc(sizeof(*new)); + + INIT_LIST_HEAD(&new->sockopts); + return new; +} + +/** + * Append new socket option to flowopt queue. + * + * \param fo The flowopt queue to append to. + * \param lev Level at which \a opt resides. + * \param opt New option to add. + * \param name Stringified name of \a opt. + * \param val The value to set \a opt to. + * \param len Length of \a val. + * + * \sa setsockopt(2) + */ +void flowopt_add(struct flowopts *fo, int lev, int opt, + char *name, const void *val, int len) +{ + struct pre_conn_opt *new = para_malloc(sizeof(*new)); + + new->sock_option = opt; + new->sock_level = lev; + new->opt_name = para_strdup(name); + + if (val == NULL) { + new->opt_val = NULL; + new->opt_len = 0; + } else { + new->opt_val = para_malloc(len); + new->opt_len = len; + memcpy(new->opt_val, val, len); + } + + list_add_tail(&new->node, &fo->sockopts); +} + +void flowopt_add_bool(struct flowopts *fo, int lev, int opt, + char *optname, bool on_or_off) +{ + int on = on_or_off; /* kernel takes 'int' */ + + flowopt_add(fo, lev, opt, optname, &on, sizeof(on)); +} + +/** Set the entire bunch of pre-connection options at once. */ +static void flowopt_setopts(int sockfd, struct flowopts *fo) +{ + struct pre_conn_opt *pc; + + if (fo == NULL) + return; + + list_for_each_entry(pc, &fo->sockopts, node) + if (setsockopt(sockfd, pc->sock_level, pc->sock_option, + pc->opt_val, pc->opt_len) < 0) { + PARA_EMERG_LOG("Can not set %s socket option: %s", + pc->opt_name, strerror(errno)); + exit(EXIT_FAILURE); + } +} + +static void flowopt_cleanup(struct flowopts *fo) +{ + struct pre_conn_opt *cur, *next; + + if (fo == NULL) + return; + + list_for_each_entry_safe(cur, next, &fo->sockopts, node) { + free(cur->opt_name); + free(cur->opt_val); + free(cur); + } + free(fo); +} + /** * Resolve IPv4/IPv6 address and create a ready-to-use active or passive socket. * - * \param l3type The layer-3 type (\p AF_INET, \p AF_INET6, \p AF_UNSPEC). * \param l4type The layer-4 type (\p IPPROTO_xxx). * \param passive Whether this is a passive (1) or active (0) socket. * \param host Remote or local hostname or IPv/6 address string. * \param port_number Decimal port number. + * \param fo Socket options to be set before making the connection. * * This creates a ready-made IPv4/v6 socket structure after looking up the * necessary parameters. The interpretation of \a host depends on the value of @@ -293,18 +369,20 @@ static const char *layer4_name(const unsigned l4type) * * Furthermore, bind(2) is called on passive sockets, and connect(2) on active * sockets. The algorithm tries all possible address combinations until it - * succeeds. + * succeeds. If \a fo is supplied, options are set and cleanup is performed. * * \return This function returns 1 on success and \a -E_ADDRESS_LOOKUP when no * matching connection could be set up (with details in the error log). * * \sa ipv6(7), getaddrinfo(3), bind(2), connect(2). */ -int makesock(unsigned l3type, unsigned l4type, int passive, - const char *host, unsigned short port_number) +int makesock(unsigned l4type, bool passive, + const char *host, uint16_t port_number, + struct flowopts *fo) { - struct addrinfo *local = NULL, *src, - *remote = NULL, *dst, hints; + struct addrinfo *local = NULL, *src = NULL, *remote = NULL, + *dst = NULL, hints; + unsigned int l3type = AF_UNSPEC; int rc, on = 1, sockfd = -1, socktype = sock_type(l4type); char port[6]; /* port number has at most 5 digits */ @@ -336,7 +414,8 @@ int makesock(unsigned l3type, unsigned l4type, int passive, layer4_name(l4type), host? host : (passive? "[loopback]" : "[localhost]"), port, gai_strerror(rc)); - return -E_ADDRESS_LOOKUP; + rc = -E_ADDRESS_LOOKUP; + goto out; } /* Iterate over all src/dst combination, exhausting dst first */ @@ -351,16 +430,20 @@ int makesock(unsigned l3type, unsigned l4type, int passive, goto get_next_dst; /* - * Set those options that need to be set before establishing - * the connection. Reuse the address on passive (listening) - * sockets to avoid failure on restart. + * Reuse the address on passive sockets to avoid failure on + * restart (protocols using listen()) and when creating + * multiple listener instances (UDP multicast). */ if (passive && setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, - &on, sizeof(on)) == -1) { + &on, sizeof(on)) == -1) { + rc = errno; + close(sockfd); PARA_ERROR_LOG("can not set SO_REUSEADDR: %s\n", - strerror(errno)); - return -ERRNO_TO_PARA_ERROR(errno); + strerror(rc)); + rc = -ERRNO_TO_PARA_ERROR(rc); + break; } + flowopt_setopts(sockfd, fo); if (src) { if (bind(sockfd, src->ai_addr, src->ai_addrlen) < 0) { @@ -381,16 +464,20 @@ get_next_src: if (src && (src = src->ai_next)) /* restart inner loop */ dst = remote; } +out: if (local) freeaddrinfo(local); if (remote) freeaddrinfo(remote); + flowopt_cleanup(fo); if (src == NULL && dst == NULL) { + if (rc >= 0) + rc = -E_MAKESOCK; PARA_ERROR_LOG("can not create %s socket %s#%s.\n", layer4_name(l4type), host? host : (passive? "[loopback]" : "[localhost]"), port); - return -ERRNO_TO_PARA_ERROR(errno); + return rc; } return sockfd; } @@ -398,24 +485,25 @@ get_next_src: /** * Create a passive / listening socket. * - * \param l3type The network-layer type (\p AF_xxx). * \param l4type The transport-layer type (\p IPPROTO_xxx). * \param port The decimal port number to listen on. + * \param fo Flowopts (if any) to set before starting to listen. * * \return Positive integer (socket descriptor) on success, negative value * otherwise. * * \sa makesock(), ip(7), ipv6(7), bind(2), listen(2). */ -int para_listen(unsigned l3type, unsigned l4type, unsigned short port) +int para_listen(unsigned l4type, uint16_t port, struct flowopts *fo) { - int ret, fd = makesock(l3type, l4type, 1, NULL, port); + int ret, fd = makesock(l4type, 1, NULL, port, fo); if (fd > 0) { ret = listen(fd, BACKLOG); if (ret < 0) { + ret = errno; close(fd); - return -ERRNO_TO_PARA_ERROR(errno); + return -ERRNO_TO_PARA_ERROR(ret); } PARA_INFO_LOG("listening on %s port %u, fd %d\n", layer4_name(l4type), port, fd); @@ -423,28 +511,120 @@ int para_listen(unsigned l3type, unsigned l4type, unsigned short port) return fd; } +/** + * Determine IPv4/v6 socket address length. + * \param sa Container of IPv4 or IPv6 address. + * \return Address-family dependent address length. + */ +static socklen_t salen(const struct sockaddr *sa) +{ + assert(sa->sa_family == AF_INET || sa->sa_family == AF_INET6); + + return sa->sa_family == AF_INET6 + ? sizeof(struct sockaddr_in6) + : sizeof(struct sockaddr_in); +} + +/** True if @ss holds a v6-mapped-v4 address (RFC 4291, 2.5.5.2) */ +static bool SS_IS_ADDR_V4MAPPED(const struct sockaddr_storage *ss) +{ + const struct sockaddr_in6 *ia6 = (const struct sockaddr_in6 *)ss; + + return ss->ss_family == AF_INET6 && IN6_IS_ADDR_V4MAPPED(&ia6->sin6_addr); +} + +/** + * Process IPv4/v6 address, turn v6-mapped-v4 address into normal IPv4 address. + * \param ss Container of IPv4/6 address. + * \return Pointer to normalized address (may be static storage). + * + * \sa RFC 3493 + */ +static const struct sockaddr * +normalize_ip_address(const struct sockaddr_storage *ss) +{ + assert(ss->ss_family == AF_INET || ss->ss_family == AF_INET6); + + if (SS_IS_ADDR_V4MAPPED(ss)) { + const struct sockaddr_in6 *ia6 = (const struct sockaddr_in6 *)ss; + static struct sockaddr_in ia; + + ia.sin_family = AF_INET; + ia.sin_port = ia6->sin6_port; + memcpy(&ia.sin_addr.s_addr, &(ia6->sin6_addr.s6_addr[12]), 4); + return (const struct sockaddr *)&ia; + } + return (const struct sockaddr *)ss; +} + +/** + * Generic/fallback MTU values + * + * These are taken from RFC 1122, RFC 2460, and RFC 5405. + * - RFC 1122, 3.3.3 defines EMTU_S ("Effective MTU for sending") and recommends + * to use an EMTU_S size of of 576 bytes if the IPv4 path MTU is unknown; + * - RFC 2460, 5. requires a minimum IPv6 MTU of 1280 bytes; + * - RFC 5405, 3.2 recommends that if path MTU discovery is not done, + * UDP senders should use the respective minimum values of EMTU_S. + */ +static inline int generic_mtu(const int af_type) +{ + return af_type == AF_INET6 ? 1280 : 576; +} + +/** Crude approximation of IP header overhead - neglecting options. */ +static inline int estimated_header_overhead(const int af_type) +{ + return af_type == AF_INET6 ? 40 : 20; +} + +/** + * Maximum transport-layer message size (MMS_S) as per RFC 1122, 3.3.3 + * Socket must be connected. + */ +int generic_max_transport_msg_size(int sockfd) +{ + struct sockaddr_storage ss; + socklen_t sslen = sizeof(ss); + int af_type = AF_INET; + + if (getpeername(sockfd, (struct sockaddr *)&ss, &sslen) < 0) { + PARA_ERROR_LOG("can not determine remote address type: %s\n", + strerror(errno)); + } else if (!SS_IS_ADDR_V4MAPPED(&ss)) { + af_type = ss.ss_family; + } + return generic_mtu(af_type) - estimated_header_overhead(af_type); +} + /** * Print numeric host and port number (beware - uses static char). * * \param sa The IPv4/IPv6 socket address to use. - * \param len The length of \p sa. * - * \sa getnameinfo(3). + * \return Host string in numeric host:port format, \sa parse_url(). + * \sa getnameinfo(3), services(5), nsswitch.conf(5) */ -static char *host_and_port(struct sockaddr *sa, socklen_t len) +static char *host_and_port(const struct sockaddr_storage *ss) { - static char output[NI_MAXHOST + NI_MAXSERV + 2]; + const struct sockaddr *sa = normalize_ip_address(ss); char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV]; + static char output[sizeof(hbuf) + sizeof(sbuf) + 4]; int ret; - ret = getnameinfo(sa, len, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), - NI_NUMERICHOST | NI_NUMERICSERV); + ret = getnameinfo(sa, salen(sa), + hbuf, sizeof(hbuf), + sbuf, sizeof(sbuf), + NI_NUMERICHOST | NI_NUMERICSERV); if (ret) { + snprintf(output, sizeof(output), "(unknown)"); PARA_WARNING_LOG("hostname lookup error (%s).\n", - gai_strerror(ret)); - sprintf(output, "(unknown)"); - } else - sprintf(output, "%s#%s", hbuf, sbuf); + gai_strerror(ret)); + } else if (sa->sa_family == AF_INET6) { + snprintf(output, sizeof(output), "[%s]:%s", hbuf, sbuf); + } else { + snprintf(output, sizeof(output), "%s:%s", hbuf, sbuf); + } return output; } @@ -472,7 +652,7 @@ static char *__get_sock_name(int fd, int (*getname)(int, struct sockaddr*, fd, strerror(errno)); return dont_know; } - return host_and_port((struct sockaddr *)&ss, sslen); + return host_and_port(&ss); } /** @@ -515,54 +695,34 @@ char *remote_name(int sockfd) struct in_addr extract_v4_addr(const struct sockaddr_storage *ss) { struct in_addr ia = {.s_addr = 0}; + const struct sockaddr *sa = normalize_ip_address(ss); - if (ss->ss_family == AF_INET) - ia.s_addr = ((struct sockaddr_in *)ss)->sin_addr.s_addr; - if (ss->ss_family == AF_INET6) { - const struct in6_addr v6_addr = ((struct sockaddr_in6 *)ss)->sin6_addr; - - if (IN6_IS_ADDR_V4MAPPED(&v6_addr)) - memcpy(&ia.s_addr, &(v6_addr.s6_addr[12]), 4); - } + if (sa->sa_family == AF_INET) + ia = ((struct sockaddr_in *)sa)->sin_addr; return ia; } /** - * Encrypt and send a binary buffer. + * Send a binary buffer. * * \param fd The file descriptor. - * \param buf The buffer to be encrypted and sent. + * \param buf The buffer to be sent. * \param len The length of \a buf. * - * Check if encryption is available. If yes, encrypt the given buffer. Send - * out the buffer, encrypted or not, and try to resend the remaining part in - * case of short writes. + * Send out the buffer and try to resend the remaining part in case of short + * writes. * * \return Standard. */ int send_bin_buffer(int fd, const char *buf, size_t len) { - int ret; - crypt_function *cf = NULL; - if (!len) PARA_CRIT_LOG("len == 0\n"); - if (fd + 1 <= cda_size) - cf = crypt_data_array[fd].send; - if (cf) { - void *private = crypt_data_array[fd].private_data; - /* RC4 may write more than len to the output buffer */ - unsigned char *outbuf = para_malloc(ROUND_UP(len, 8)); - (*cf)(len, (unsigned char *)buf, outbuf, private); - ret = write_all(fd, (char *)outbuf, &len); - free(outbuf); - } else - ret = write_all(fd, buf, &len); - return ret; + return write_all(fd, buf, &len); } /** - * Encrypt and send null terminated buffer. + * Send a \p NULL-terminated buffer. * * \param fd The file descriptor. * \param buf The null-terminated buffer to be send. @@ -576,9 +736,8 @@ int send_buffer(int fd, const char *buf) return send_bin_buffer(fd, buf, strlen(buf)); } - /** - * Send and encrypt a buffer given by a format string. + * Send a buffer given by a format string. * * \param fd The file descriptor. * \param fmt A format string. @@ -597,51 +756,37 @@ __printf_2_3 int send_va_buffer(int fd, const char *fmt, ...) } /** - * Receive and decrypt. + * Receive data from a file descriptor. * * \param fd The file descriptor. - * \param buf The buffer to write the decrypted data to. + * \param buf The buffer to write the data to. * \param size The size of \a buf. * - * Receive at most \a size bytes from file descriptor \a fd. If encryption is - * available, decrypt the received buffer. + * Receive at most \a size bytes from file descriptor \a fd. * - * \return The number of bytes received on success, negative on errors. + * \return The number of bytes received on success, negative on errors, zero if + * the peer has performed an orderly shutdown. * - * \sa recv(2) + * \sa recv(2). */ __must_check int recv_bin_buffer(int fd, char *buf, size_t size) { ssize_t n; - crypt_function *cf = NULL; - - if (fd + 1 <= cda_size) - cf = crypt_data_array[fd].recv; - if (cf) { - unsigned char *tmp = para_malloc(size); - void *private = crypt_data_array[fd].private_data; - n = recv(fd, tmp, size, 0); - if (n > 0) { - size_t numbytes = n; - unsigned char *b = (unsigned char *)buf; - (*cf)(numbytes, tmp, b, private); - } - free(tmp); - } else - n = recv(fd, buf, size, 0); + + n = recv(fd, buf, size, 0); if (n == -1) return -ERRNO_TO_PARA_ERROR(errno); return n; } /** - * Receive, decrypt and write terminating NULL byte. + * Receive and write terminating NULL byte. * * \param fd The file descriptor. - * \param buf The buffer to write the decrypted data to. + * \param buf The buffer to write the data to. * \param size The size of \a buf. * - * Read and decrypt at most \a size - 1 bytes from file descriptor \a fd and + * Read at most \a size - 1 bytes from file descriptor \a fd and * write a NULL byte at the end of the received data. * * \return The return value of the underlying call to \a recv_bin_buffer(). @@ -665,23 +810,68 @@ int recv_buffer(int fd, char *buf, size_t size) * Wrapper around the accept system call. * * \param fd The listening socket. + * \param rfds An optional fd_set pointer. * \param addr Structure which is filled in with the address of the peer socket. * \param size Should contain the size of the structure pointed to by \a addr. + * \param new_fd Result pointer. * - * Accept incoming connections on \a addr. Retry if interrupted. + * Accept incoming connections on \a addr, retry if interrupted. If \a rfds is + * not \p NULL, return 0 if \a fd is not set in \a rfds without calling accept(). * - * \return The new file descriptor on success, negative on errors. + * \return Negative on errors, zero if no connections are present to be accepted, + * one otherwise. * * \sa accept(2). */ -int para_accept(int fd, void *addr, socklen_t size) +int para_accept(int fd, fd_set *rfds, void *addr, socklen_t size, int *new_fd) { - int new_fd; + int ret; + if (rfds && !FD_ISSET(fd, rfds)) + return 0; do - new_fd = accept(fd, (struct sockaddr *) addr, &size); - while (new_fd < 0 && errno == EINTR); - return new_fd < 0? -ERRNO_TO_PARA_ERROR(errno) : new_fd; + ret = accept(fd, (struct sockaddr *) addr, &size); + while (ret < 0 && errno == EINTR); + + if (ret >= 0) { + *new_fd = ret; + return 1; + } + if (errno == EAGAIN || errno == EWOULDBLOCK) + return 0; + return -ERRNO_TO_PARA_ERROR(errno); +} + +/** + * Probe the list of DCCP CCIDs configured on this host. + * \param ccid_array Pointer to return statically allocated array in. + * \return Number of elements returned in \a ccid_array or error. + * + * NB: This feature is only available on Linux > 2.6.30; on older kernels + * ENOPROTOOPT ("Protocol not available") will be returned. + */ +int dccp_available_ccids(uint8_t **ccid_array) +{ + static uint8_t ccids[DCCP_MAX_HOST_CCIDS]; + socklen_t nccids = sizeof(ccids); + int ret, fd; + + ret = fd = makesock(IPPROTO_DCCP, 1, NULL, 0, NULL); + if (ret < 0) + return ret; + + if (getsockopt(fd, SOL_DCCP, DCCP_SOCKOPT_AVAILABLE_CCIDS, + ccids, &nccids) < 0) { + ret = errno; + close(fd); + PARA_ERROR_LOG("No DCCP_SOCKOPT_AVAILABLE_CCIDS: %s\n", + strerror(ret)); + return -ERRNO_TO_PARA_ERROR(ret); + } + + close(fd); + *ccid_array = ccids; + return nccids; } /** @@ -755,15 +945,17 @@ err: * This function creates a local socket for sequenced, reliable, two-way, * connection-based byte streams. * - * \return The file descriptor, on success, negative on errors. + * \return The file descriptor of the connected socket on success, negative on + * errors. * * \sa create_local_socket(), unix(7), connect(2). */ -int create_remote_socket(const char *name) +int connect_local_socket(const char *name) { struct sockaddr_un unix_addr; int fd, ret; + PARA_DEBUG_LOG("connecting to %s\n", name); ret = init_unix_addr(&unix_addr, name); if (ret < 0) return ret; @@ -894,40 +1086,3 @@ int recv_cred_buffer(int fd, char *buf, size_t size) return result; } #endif /* HAVE_UCRED */ - -/** - * Receive a buffer and check for a pattern. - * - * \param fd The file descriptor to receive from. - * \param pattern The expected pattern. - * \param bufsize The size of the internal buffer. - * - * \return Positive if \a pattern was received, negative otherwise. - * - * This function tries to receive at most \a bufsize bytes from file descriptor - * \a fd. If at least \p strlen(\a pattern) bytes were received, the beginning - * of the received buffer is compared with \a pattern, ignoring case. - * - * \sa recv_buffer(), \sa strncasecmp(3). - */ -int recv_pattern(int fd, const char *pattern, size_t bufsize) -{ - size_t len = strlen(pattern); - char *buf = para_malloc(bufsize + 1); - int ret = -E_RECV_PATTERN, n = recv_buffer(fd, buf, bufsize + 1); - - if (n < len) - goto out; - if (strncasecmp(buf, pattern, len)) - goto out; - ret = 1; -out: - if (ret < 0) { - PARA_NOTICE_LOG("n = %d, did not receive pattern '%s'\n", n, - pattern); - if (n > 0) - PARA_NOTICE_LOG("recvd: %s\n", buf); - } - free(buf); - return ret; -} diff --git a/net.h b/net.h index 8b706178..a1af08ae 100644 --- a/net.h +++ b/net.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -17,14 +17,56 @@ #define UNIX_PATH_MAX 108 #endif -/** \cond Userland defines for Linux DCCP support. */ +/* Userland defines for Linux DCCP support. */ + #ifndef IPPROTO_DCCP -#define IPPROTO_DCCP 33 /**< IANA assigned value */ -#define SOCK_DCCP 6 /**< Linux socket type */ -#define SOL_DCCP 269 /**< Linux socket level */ +#define IPPROTO_DCCP 33 /**< IANA assigned value. */ +#endif + +#ifndef SOCK_DCCP +#define SOCK_DCCP 6 /**< Linux socket type. */ +#endif + +#ifndef DCCP_SOCKOPT_RX_CCID +/** Per-connection CCID support (set/get the RX CCID, since v2.6.30-rc1). */ +#define DCCP_SOCKOPT_RX_CCID 15 +#endif + +#ifndef SOL_DCCP +#define SOL_DCCP 269 /**< Linux socket level. */ +#endif + +#ifndef DCCP_SOCKOPT_GET_CUR_MPS +#define DCCP_SOCKOPT_GET_CUR_MPS 5 /**< Max packet size, RFC 4340, 14. */ #endif -/** \endcond */ +#ifndef DCCP_SOCKOPT_AVAILABLE_CCIDS +#define DCCP_SOCKOPT_AVAILABLE_CCIDS 12 /**< List of supported CCIDs. */ +#endif + +#ifndef DCCP_SOCKOPT_CCID +#define DCCP_SOCKOPT_CCID 13 /**< Sets both TX/RX CCID. */ +#endif + +#ifndef DCCP_SOCKOPT_TX_CCID +#define DCCP_SOCKOPT_TX_CCID 14 /**< Set/get the TX CCID. */ +#endif + +/** + * Flowopts: Transport-layer independent encapsulation of socket options + * that need to be registered prior to setting up a connection. + */ +struct flowopts; + +extern struct flowopts *flowopt_new(void); +extern void flowopt_add(struct flowopts *fo, int level, int opt, + char *name, const void *val, int len); +extern void flowopt_add_bool(struct flowopts *fo, int lev, int opt, + char *optname, bool on_or_off); +/** Flowopt shortcut macros */ +#define OPT_ADD(fo, lev, opt, val, len) flowopt_add(fo, lev, opt, #opt, val, len) +#define OPT_ENABLE(fo, lev, opt) flowopt_add_bool(fo, lev, opt, #opt, 1) +#define OPT_DISABLE(fo, lev, opt) flowopt_add_bool(fo, lev, opt, #opt, 0) /** * Functions to parse and validate (parts of) URLs. @@ -33,6 +75,7 @@ extern char *parse_cidr(const char *cidr, char *addr, ssize_t addrlen, int32_t *netmask); extern char *parse_url(const char *url, char *host, ssize_t hostlen, int32_t *port); +extern const char *stringify_port(int port, const char *transport); /** * Ensure that string conforms to the IPv4 address format. * @@ -65,8 +108,16 @@ _static_inline_ bool is_valid_ipv6_address(const char *address) /** * Generic socket creation (passive and active sockets). */ -extern int makesock(unsigned l3type, unsigned l4type, int passive, - const char *host, unsigned short port_number); +extern int makesock(unsigned l4type, bool passive, + const char *host, uint16_t port_number, + struct flowopts *fo); + +static inline int para_connect_simple(unsigned l4type, + const char *host, uint16_t port) +{ + return makesock(l4type, 0, host, port, NULL); +} + extern struct in_addr extract_v4_addr(const struct sockaddr_storage *ss); /** @@ -74,28 +125,41 @@ extern struct in_addr extract_v4_addr(const struct sockaddr_storage *ss); */ /** How many pending connections queue of a listening server will hold. */ #define BACKLOG 10 -extern int para_listen(unsigned l3type, unsigned l4type, unsigned short port); +extern int para_listen(unsigned l4type, uint16_t port, struct flowopts *fo); + +static inline int para_listen_simple(unsigned l4type, uint16_t port) +{ + return para_listen(l4type, port, NULL); +} /** Pretty-printing of IPv4/6 socket addresses */ extern char *local_name(int sockfd); extern char *remote_name(int sockfd); -/** used to crypt the communication between para_server and para_client */ -typedef void crypt_function(unsigned long len, - const unsigned char *indata, unsigned char *outdata, void *private_data); +/** + * Determining maximum payload (packet) size + */ +extern int generic_max_transport_msg_size(int sockfd); -int send_buffer(int, const char *); int send_bin_buffer(int, const char *, size_t); +int send_buffer(int, const char *); __printf_2_3 int send_va_buffer(int fd, const char *fmt, ...); -int recv_buffer(int fd, char *buf, size_t size); + int recv_bin_buffer(int fd, char *buf, size_t size); -int para_accept(int, void *addr, socklen_t size); +int recv_buffer(int fd, char *buf, size_t size); + +int para_accept(int fd, fd_set *rfds, void *addr, socklen_t size, int *new_fd); int create_local_socket(const char *name, struct sockaddr_un *unix_addr, mode_t mode); -int create_remote_socket(const char *name); +int connect_local_socket(const char *name); int recv_cred_buffer(int, char *, size_t); ssize_t send_cred_buffer(int, char*); -int recv_pattern(int fd, const char *pattern, size_t bufsize); -void enable_crypt(int fd, crypt_function *recv_f, crypt_function *send_f, - void *private_data); -void disable_crypt(int fd); + +/** + * Functions and definitions to support \p IPPROTO_DCCP + */ +/** Estimated worst-case length of a DCCP header including options. */ +#define DCCP_MAX_HEADER 128 +/** Hardcoded maximum number of separate CCID modules compiled into a host. */ +#define DCCP_MAX_HOST_CCIDS 20 +extern int dccp_available_ccids(uint8_t **ccid_array); diff --git a/ogg_afh.c b/ogg_afh.c index 11214c89..ef42c497 100644 --- a/ogg_afh.c +++ b/ogg_afh.c @@ -1,196 +1,67 @@ /* - * Copyright (C) 2004-2009 Andre Noll + * Copyright (C) 2004-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ -/** \file ogg_afh.c Audio format handler for ogg vorbis files. */ +/** \file ogg_afh.c Audio format handler for ogg/vorbis files. */ -#include -#include #include +#include #include "para.h" #include "afh.h" #include "error.h" #include "string.h" +#include "ogg_afh_common.h" -/* Taken from decoder_example.c of libvorbis-1.2.3. */ -static int read_vorbis_comment(ogg_sync_state *oss, ogg_stream_state *stream, - vorbis_info *vi, vorbis_comment *vc) -{ - ogg_page page; - ogg_packet packet; - int i = 0; - - while (i < 2) { - while (i < 2) { - int ret = ogg_sync_pageout(oss, &page); - if (ret == 0) - break; /* Need more data */ - if (ret != 1) - continue; - /* - * We can ignore any errors here as they'll also become - * apparent at packetout. - */ - ogg_stream_pagein(stream, &page); - while (i < 2) { - ret = ogg_stream_packetout(stream, &packet); - if (ret == 0) - break; - if (ret < 0) - return -E_STREAM_PACKETOUT; - ret = vorbis_synthesis_headerin(vi, vc, - &packet); - if (ret < 0) - return -E_VORBIS; - i++; - } - } - } - return 1; -} - -static int read_vorbis_info(ogg_sync_state *oss, struct afh_info *afhi) -{ - vorbis_comment vc; +struct private_vorbis_data { vorbis_info vi; - ogg_packet packet; - ogg_stream_state stream; - ogg_page page; - int ret; - char *taginfo; - - vorbis_info_init(&vi); - vorbis_comment_init(&vc); - - ret = -E_SYNC_PAGEOUT; - if (ogg_sync_pageout(oss, &page) != 1) - goto out; - - ret = ogg_page_serialno(&page); - ogg_stream_init(&stream, ret); - - ret = -E_STREAM_PAGEIN; - if (ogg_stream_pagein(&stream, &page) < 0) - goto out; - - ret = -E_STREAM_PACKETOUT; - if (ogg_stream_packetout(&stream, &packet) != 1) - goto out; - - ret = -E_VORBIS; - if (vorbis_synthesis_headerin(&vi, &vc, &packet) < 0) - goto out; - if (vi.rate == 0) - goto out; - afhi->channels = vi.channels; - afhi->frequency = vi.rate; - afhi->bitrate = vi.bitrate_nominal / 1000; - PARA_DEBUG_LOG("channels: %i, sampling rate: %i, bitrate: %i\n", - afhi->channels, afhi->frequency, afhi->bitrate); - ret = read_vorbis_comment(oss, &stream, &vi, &vc); - if (ret < 0) - goto out; - taginfo = make_taginfo( - vorbis_comment_query(&vc, "title", 0), - vorbis_comment_query(&vc, "artist", 0), - vorbis_comment_query(&vc, "album", 0), - vorbis_comment_query(&vc, "year", 0), - vorbis_comment_query(&vc, "comment", 0) - ); - PARA_DEBUG_LOG("tag info: %s\n", taginfo); - afhi->info_string = make_message("%s:\n%s", - status_item_list[SI_AUDIO_FILE_INFO], taginfo); - free(taginfo); - - afhi->header_offset = 0; - afhi->header_len = oss->returned; - ret = 1; -out: - vorbis_info_clear(&vi); - vorbis_comment_clear(&vc); - ogg_stream_clear(&stream); - return ret; -} + vorbis_comment vc; +}; -static void set_chunk_tv(int num_frames, int num_chunks, int frequency, - struct timeval *result) +static int vorbis_packet_callback(ogg_packet *packet, int packet_num, + struct afh_info *afhi, void *private_data) { - uint64_t x = (uint64_t)num_frames * 1000 * 1000 - / frequency / num_chunks; - - result->tv_sec = x / 1000 / 1000; - result->tv_usec = x % (1000 * 1000); - PARA_INFO_LOG("%d chunks, chunk time: %lums\n", num_chunks, - tv2ms(result)); + struct private_vorbis_data *pvd = private_data; + + if (vorbis_synthesis_headerin(&pvd->vi, &pvd->vc, packet) < 0) + return -E_VORBIS; + if (packet_num == 0) { + if (pvd->vi.rate == 0) + return -E_VORBIS; + afhi->channels = pvd->vi.channels; + afhi->frequency = pvd->vi.rate; + afhi->bitrate = pvd->vi.bitrate_nominal / 1000; + PARA_DEBUG_LOG("channels: %i, sampling rate: %i, bitrate: %i\n", + afhi->channels, afhi->frequency, afhi->bitrate); + return 1; + } + if (packet_num == 1) + return 1; /* we also want to have packet #2 */ + afhi->tags.artist = para_strdup(vorbis_comment_query(&pvd->vc, "artist", 0)); + afhi->tags.title = para_strdup(vorbis_comment_query(&pvd->vc, "title", 0)); + afhi->tags.album = para_strdup(vorbis_comment_query(&pvd->vc, "album", 0)); + afhi->tags.year = para_strdup(vorbis_comment_query(&pvd->vc, "year", 0)); + afhi->tags.comment = para_strdup(vorbis_comment_query(&pvd->vc, "comment", 0)); + return 0; } -/* Write tech data to given audio format handler struct. */ -static int ogg_get_file_info(char *map, size_t numbytes, __a_unused int fd, +static int ogg_vorbis_get_file_info(char *map, size_t numbytes, __a_unused int fd, struct afh_info *afhi) { - ogg_sync_state oss; - ogg_page op; - long len = numbytes; - char *buf; - int ret, i, j, frames_per_chunk, ct_size; - long long unsigned num_frames = 0; - - afhi->info_string = NULL; - ogg_sync_init(&oss); - ret = -E_OGG_SYNC; - buf = ogg_sync_buffer(&oss, len); - if (!buf) - goto out; - memcpy(buf, map, len); - ret = -E_OGG_SYNC; - if (ogg_sync_wrote(&oss, len) < 0) - goto out; - ret = read_vorbis_info(&oss, afhi); - if (ret < 0) - goto out; - oss.returned = 0; - oss.fill = numbytes; - /* count ogg packages and get duration of the file */ - for (i = 0; ogg_sync_pageseek(&oss, &op) > 0; i++) - num_frames = ogg_page_granulepos(&op); - PARA_INFO_LOG("%d pages, %llu frames\n", i, num_frames); - ret = -E_OGG_EMPTY; - if (i == 0) - goto out; - afhi->seconds_total = num_frames / afhi->frequency; - /* use roughly one page per chunk */ - frames_per_chunk = num_frames / i; - PARA_INFO_LOG("%lu seconds, %d frames/chunk\n", - afhi->seconds_total, frames_per_chunk); - ct_size = 250; - afhi->chunk_table = para_malloc(ct_size * sizeof(uint32_t)); - afhi->chunk_table[0] = 0; - afhi->chunk_table[1] = afhi->header_len; - oss.returned = afhi->header_len; - oss.fill = numbytes; - for (i = 0, j = 1; ogg_sync_pageseek(&oss, &op) > 0; i++) { - int granule = ogg_page_granulepos(&op); - - while (granule > j * frames_per_chunk) { - j++; - if (j >= ct_size) { - ct_size *= 2; - afhi->chunk_table = para_realloc( - afhi->chunk_table, - ct_size * sizeof(uint32_t)); - } - afhi->chunk_table[j] = oss.returned; - } - } - afhi->chunks_total = j; - set_chunk_tv(num_frames, j, afhi->frequency, &afhi->chunk_tv); - tv_scale(3, &afhi->chunk_tv, &afhi->eof_tv); - ret = 0; -out: - ogg_sync_clear(&oss); + int ret; + struct private_vorbis_data pvd; + struct ogg_afh_callback_info vorbis_callback_info = { + .packet_callback = vorbis_packet_callback, + .private_data = &pvd, + }; + + vorbis_info_init(&pvd.vi); + vorbis_comment_init(&pvd.vc); + ret = ogg_get_file_info(map, numbytes, afhi, &vorbis_callback_info); + vorbis_info_clear(&pvd.vi); + vorbis_comment_clear(&pvd.vc); return ret; } @@ -203,6 +74,6 @@ static const char* ogg_suffixes[] = {"ogg", NULL}; */ void ogg_init(struct audio_format_handler *afh) { - afh->get_file_info = ogg_get_file_info, + afh->get_file_info = ogg_vorbis_get_file_info, afh->suffixes = ogg_suffixes; } diff --git a/ogg_afh_common.c b/ogg_afh_common.c new file mode 100644 index 00000000..1deb5cda --- /dev/null +++ b/ogg_afh_common.c @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2004-2011 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file ogg_afh_common.c Functions common to ogg/vorbis and ogg/speex. */ + +#include +#include + +#include "para.h" +#include "afh.h" +#include "error.h" +#include "string.h" +#include "ogg_afh_common.h" + + +/* Taken from decoder_example.c of libvorbis-1.2.3. */ +static int process_packets_2_and_3(ogg_sync_state *oss, + ogg_stream_state *stream, struct afh_info *afhi, + struct ogg_afh_callback_info *ci) +{ + ogg_page page; + ogg_packet packet; + int i = 0; + + while (i < 2) { + while (i < 2) { + int ret = ogg_sync_pageout(oss, &page); + if (ret == 0) + break; /* Need more data */ + if (ret != 1) + continue; + /* + * We can ignore any errors here as they'll also become + * apparent at packetout. + */ + ogg_stream_pagein(stream, &page); + while (i < 2) { + ret = ogg_stream_packetout(stream, &packet); + if (ret == 0) + break; + if (ret < 0) + return -E_STREAM_PACKETOUT; + ret = ci->packet_callback(&packet, i + 1, afhi, + ci->private_data); + if (ret < 0) + return ret; + if (ret == 0) /* header complete */ + return 1; + i++; + } + } + } + return 1; +} + +static int process_ogg_packets(ogg_sync_state *oss, struct afh_info *afhi, + struct ogg_afh_callback_info *ci) +{ + ogg_packet packet; + ogg_stream_state stream; + ogg_page page; + int ret; + + if (ogg_sync_pageout(oss, &page) != 1) + return -E_SYNC_PAGEOUT; + + ret = ogg_page_serialno(&page); + ogg_stream_init(&stream, ret); + + ret = -E_STREAM_PAGEIN; + if (ogg_stream_pagein(&stream, &page) < 0) + goto out; + + ret = -E_STREAM_PACKETOUT; + if (ogg_stream_packetout(&stream, &packet) != 1) + goto out; + ret = ci->packet_callback(&packet, 0, afhi, ci->private_data); + if (ret < 0) + goto out; + ret = process_packets_2_and_3(oss, &stream, afhi, ci); + if (ret < 0) + goto out; + afhi->header_offset = 0; + afhi->header_len = oss->returned; + ret = 1; +out: + ogg_stream_clear(&stream); + return ret; +} + +static void set_chunk_tv(int num_frames, int num_chunks, int frequency, + struct timeval *result) +{ + uint64_t x = (uint64_t)num_frames * 1000 * 1000 + / frequency / num_chunks; + + result->tv_sec = x / 1000 / 1000; + result->tv_usec = x % (1000 * 1000); + PARA_INFO_LOG("%d chunks, chunk time: %lums\n", num_chunks, + tv2ms(result)); +} + +/** + * Pass first three ogg packets to callback and build the chunk table. + * + * This function extracts the first three ogg packets of the audio data + * given by \a map and \a numbytes and passes each packet to the callback + * defined by \a ci. + * + * If the packet callback indicates success, the chunk table is built. Chunk + * zero contains the first three ogg packets while all other chunks consist of + * exactly one ogg page. + * + * \param map Audio file data. + * \param numbytes The length of \a map. + * \param afhi Passed to the packet callback, contains chunk table. + * \param ci The callback structure. + * + * \return Standard. + */ +int ogg_get_file_info(char *map, size_t numbytes, struct afh_info *afhi, + struct ogg_afh_callback_info *ci) +{ + ogg_sync_state oss; + ogg_page op; + long len = numbytes; + char *buf; + int ret, i, j, frames_per_chunk, ct_size; + long long unsigned num_frames = 0; + + ogg_sync_init(&oss); + ret = -E_OGG_SYNC; + buf = ogg_sync_buffer(&oss, len); + if (!buf) + goto out; + memcpy(buf, map, len); + ret = -E_OGG_SYNC; + if (ogg_sync_wrote(&oss, len) < 0) + goto out; + ret = process_ogg_packets(&oss, afhi, ci); + if (ret < 0) + goto out; + oss.returned = 0; + oss.fill = numbytes; + /* count ogg packages and get duration of the file */ + for (i = 0; ogg_sync_pageseek(&oss, &op) > 0; i++) + num_frames = ogg_page_granulepos(&op); + PARA_INFO_LOG("%d pages, %llu frames\n", i, num_frames); + ret = -E_OGG_EMPTY; + if (i == 0) + goto out; + afhi->seconds_total = num_frames / afhi->frequency; + /* use roughly one page per chunk */ + frames_per_chunk = num_frames / i; + PARA_INFO_LOG("%lu seconds, %d frames/chunk\n", + afhi->seconds_total, frames_per_chunk); + ct_size = 250; + afhi->chunk_table = para_malloc(ct_size * sizeof(uint32_t)); + afhi->chunk_table[0] = 0; + afhi->chunk_table[1] = afhi->header_len; + oss.returned = afhi->header_len; + oss.fill = numbytes; + for (j = 1; ogg_sync_pageseek(&oss, &op) > 0; /* nothing */) { + int granule = ogg_page_granulepos(&op); + + while (granule > j * frames_per_chunk) { + j++; + if (j >= ct_size) { + ct_size *= 2; + afhi->chunk_table = para_realloc( + afhi->chunk_table, + ct_size * sizeof(uint32_t)); + } + afhi->chunk_table[j] = oss.returned; + } + } + afhi->chunks_total = j; + set_chunk_tv(num_frames, j, afhi->frequency, &afhi->chunk_tv); + ret = 0; +out: + ogg_sync_clear(&oss); + return ret; +} diff --git a/ogg_afh_common.h b/ogg_afh_common.h new file mode 100644 index 00000000..01f8d6c5 --- /dev/null +++ b/ogg_afh_common.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2010-2011 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** + * \file ogg_afh_common.h Structures and prototypes common to audio format + * handlers that use the ogg container format. + */ + +/** + * Callback structure provided by vorbis/speex audio format handlers. + * + * Both audio formats utilize the ogg container format. Meta info about + * the audio file is contained in the first three ogg packets. + */ +struct ogg_afh_callback_info { + /** + * ogg_get_file_info() calls this function for each of the three + * header packets. If this callback returns a negative value, the + * audio file is considered invalid and the chunk table is not + * created. If it returns zero, the end of the header has been + * reached and no further ogg packets should be processed. + */ + int (*packet_callback)(ogg_packet *packet, int packet_num, + struct afh_info *afhi, void *private_data); + /** Vorbis/speex specific data. */ + void *private_data; +}; + +int ogg_get_file_info(char *map, size_t numbytes, struct afh_info *afhi, + struct ogg_afh_callback_info *ci); diff --git a/oggdec_filter.c b/oggdec_filter.c index 7155a54f..1bc6d2f7 100644 --- a/oggdec_filter.c +++ b/oggdec_filter.c @@ -1,23 +1,24 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file oggdec_filter.c Paraslash's ogg vorbis decoder. */ -#include "para.h" +#include +#include +#include -#include "oggdec_filter.cmdline.h" +#include "para.h" #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "filter.h" #include "error.h" #include "string.h" -#include - /** Determine byte sex. */ #ifdef WORDS_BIGENDIAN #define ENDIAN 1 @@ -29,35 +30,37 @@ struct private_oggdec_data { /** Describes an ogg vorbis file. */ OggVorbis_File *vf; - /** The input buffer. */ - char *inbuf; - /** The length of \a inbuf. */ - size_t inbuf_len; /** The number of bytes consumed from the input buffer. */ size_t converted; - /** When to start producing output. */ + /** The number of channels of the current stream. */ + unsigned int channels; + /** Current sample rate in Hz. */ struct timeval stream_start; + unsigned int sample_rate; }; static size_t cb_read(void *buf, size_t size, size_t nmemb, void *datasource) { struct filter_node *fn = datasource; struct private_oggdec_data *pod = fn->private_data; - size_t ret, have = pod->inbuf_len - pod->converted; - char *p = pod->inbuf + pod->converted; - -// PARA_DEBUG_LOG("pod = %p\n", pod); -// PARA_DEBUG_LOG("vorbis requests %d bytes, have %d\n", size * nmemb, have); - if (pod->inbuf_len < size) { - if (*fn->fc->input_error) - return 0; - errno = EAGAIN; - return (size_t)-1; - } - ret = PARA_MIN(nmemb, have / size) * size; - memcpy(buf, p, ret); - pod->converted += ret; - return ret; + struct btr_node *btrn = fn->btrn; + char *btr_buf; + size_t nbytes = btr_next_buffer(btrn, &btr_buf), tmp; + + /** + * oggvorbis always uses size == 1. Other sizes would complicate the code + * for no real gain. So we simply don't support size != 1. + */ + assert(size == 1); + assert(pod->converted <= nbytes); + tmp = nbytes - pod->converted; + PARA_DEBUG_LOG("vorbis requests %zu bytes have %zu\n", nmemb, tmp); + tmp = PARA_MIN(tmp, nmemb); + if (tmp == 0) + return 0; + memcpy(buf, btr_buf + pod->converted, tmp); + pod->converted += tmp; + return tmp; } /* @@ -92,11 +95,9 @@ static void ogg_open(struct filter_node *fn) { struct private_oggdec_data *pod = para_calloc( sizeof(struct private_oggdec_data)); - struct oggdec_filter_args_info *conf = fn->conf; fn->private_data = pod; - fn->bufsize = conf->bufsize_arg * 1024; - fn->buf = para_malloc(fn->bufsize); + fn->min_iqs = 8000; } static void ogg_close(struct filter_node *fn) @@ -108,108 +109,137 @@ static void ogg_close(struct filter_node *fn) free(pod->vf); pod->vf = NULL; } else - PARA_DEBUG_LOG("nothing to close in fc %p, pod = %p\n", pod->vf, pod); - free(fn->buf); - fn->buf = NULL; + PARA_DEBUG_LOG("nothing to close\n"); free(fn->private_data); fn->private_data = NULL; } -static ssize_t ogg_convert(char *inbuffer, size_t len, struct filter_node *fn) +#define OGGDEC_OUTPUT_CHUNK_SIZE (64 * 1024) + +static int oggdec_execute(struct btr_node *btrn, const char *cmd, char **result) { - ssize_t ret; + struct filter_node *fn = btr_context(btrn); struct private_oggdec_data *pod = fn->private_data; - struct oggdec_filter_args_info *conf = fn->conf; - /* make the buffer known to the read callback cb_read() */ - pod->inbuf = inbuffer; - pod->inbuf_len = len; - pod->converted = 0; - if (!pod->vf) { - if (*fn->fc->input_error < 0) - return *fn->fc->input_error; - if (!len) - return 0; - pod->vf = para_malloc(sizeof(struct OggVorbis_File)); - PARA_NOTICE_LOG("input buffer: %zd, opening ov callbacks\n", len); - ret = ov_open_callbacks(fn, pod->vf, - NULL, /* no initial buffer */ - 0, /* no initial bytes */ - ovc); /* the ov_open_callbacks */ - if (ret == OV_ENOTVORBIS || ret == OV_EBADHEADER) { - /* this might be due to the input buffer being too small */ - int ib = 1024 * conf->initial_buffer_arg; /* initial buffer */ - if (len < ib) { - PARA_INFO_LOG("initial input buffer %zd/%d, " - "waiting for more data\n", len, ib); - free(pod->vf); - pod->vf = NULL; - return 0; - } - return ret == OV_ENOTVORBIS? - -E_OGGDEC_NOTVORBIS : -E_OGGDEC_BADHEADER; + return decoder_execute(cmd, pod->sample_rate, pod->channels, result); +} + +static int ogg_init(struct filter_node *fn) +{ + struct private_oggdec_data *pod = fn->private_data; + struct btr_node *btrn = fn->btrn; + int ret, oret; + size_t iqs; + + pod->vf = para_malloc(sizeof(struct OggVorbis_File)); + PARA_NOTICE_LOG("iqs: %zu, min_iqs: %zu, opening ov callbacks\n", + btr_get_input_queue_size(btrn), fn->min_iqs); +open: + oret = ov_open_callbacks(fn, pod->vf, + NULL, /* no initial buffer */ + 0, /* no initial bytes */ + ovc); /* the ov_open_callbacks */ + if (oret == OV_ENOTVORBIS || oret == OV_EBADHEADER) { + /* this might be due to the input buffer being too small */ + if (!btr_no_parent(btrn)) { + fn->min_iqs += 1000; + iqs = btr_get_input_queue_size(btrn); + ret = 0; + if (iqs < fn->min_iqs) + goto out; + PARA_CRIT_LOG("iqs: %zu\n", iqs); + btr_merge(btrn, fn->min_iqs); + pod->converted = 0; + goto open; } - if (ret == OV_EREAD) - return -E_OGGDEC_READ; - if (ret == OV_EVERSION) - return -E_OGGDEC_VERSION; - if (ret < 0) - return -E_OGGDEC_FAULT; - fn->fc->channels = ov_info(pod->vf, 0)->channels; - fn->fc->samplerate = ov_info(pod->vf, 0)->rate; - PARA_NOTICE_LOG("%d channels, %d Hz\n", fn->fc->channels, - fn->fc->samplerate); - /* wait a bit to avoid buffer underruns */ - tv_add(now, &(struct timeval){0, 500 * 1000}, &pod->stream_start); - return pod->converted; + ret = (oret == OV_ENOTVORBIS)? + -E_OGGDEC_NOTVORBIS : -E_OGGDEC_BADHEADER; + goto out; } - if (tv_diff(now, &pod->stream_start, NULL) < 0) { - PARA_DEBUG_LOG("initial delay..\n"); - return 0; - } - while (fn->loaded < fn->bufsize) { - int length = fn->bufsize - fn->loaded; - long read_ret = ov_read(pod->vf, fn->buf + fn->loaded, length, - ENDIAN, 2 /* 16 bit */, 1 /* signed */, NULL); - if (read_ret == 0) - return pod->converted; - if (read_ret == OV_HOLE) { - if (!fn->loaded) { - PARA_INFO_LOG("hole, delaying playback\n"); - tv_add(now, &(struct timeval){0, 500 * 1000}, &pod->stream_start); - } - return pod->converted; - } - if (read_ret < 0) - return -E_OGGDEC_BADLINK; - fn->loaded += read_ret; + ret = -E_OGGDEC_READ; + if (oret == OV_EREAD) + goto out; + ret = -E_OGGDEC_VERSION; + if (oret == OV_EVERSION) + goto out; + ret = -E_OGGDEC_FAULT; + if (oret < 0) + goto out; + pod->channels = ov_info(pod->vf, 0)->channels; + pod->sample_rate = ov_info(pod->vf, 0)->rate; + tv_add(now, &(struct timeval)EMBRACE(0, 300 * 1000), &pod->stream_start); + PARA_NOTICE_LOG("%d channels, %d Hz\n", pod->channels, + pod->sample_rate); + ret = 1; +out: + if (ret <= 0) { + free(pod->vf); + pod->vf = NULL; + } else { + btr_consume(btrn, pod->converted); + pod->converted = 0; + fn->min_iqs = 0; } - return pod->converted; + return ret; } -static int oggdec_parse_config(int argc, char **argv, void **config) +static void ogg_pre_select(__a_unused struct sched *s, struct task *t) { + struct filter_node *fn = container_of(t, struct filter_node, task); + struct private_oggdec_data *pod = fn->private_data; int ret; - struct oggdec_filter_args_info *ogg_conf; - - ogg_conf = para_calloc(sizeof(*ogg_conf)); - ret = -E_OGGDEC_SYNTAX; - if (oggdec_cmdline_parser(argc, argv, ogg_conf)) - goto err; - ret = -ERRNO_TO_PARA_ERROR(EINVAL); - if (ogg_conf->bufsize_arg < 0) - goto err; - if (ogg_conf->bufsize_arg >= INT_MAX / 1024) - goto err; - if (ogg_conf->initial_buffer_arg < 0) - goto err; - if (ogg_conf->initial_buffer_arg >= INT_MAX / 1024) - goto err; - *config = ogg_conf; - return 1; -err: - free(ogg_conf); - return ret; + + t->error = 0; + ret = btr_node_status(fn->btrn, fn->min_iqs, BTR_NT_INTERNAL); + if (ret < 0) + sched_min_delay(s); + else + sched_request_barrier(&pod->stream_start, s); +} + +static void ogg_post_select(__a_unused struct sched *s, struct task *t) +{ + struct filter_node *fn = container_of(t, struct filter_node, task); + struct private_oggdec_data *pod = fn->private_data; + struct btr_node *btrn = fn->btrn; + int ret, ns; + + if (tv_diff(&pod->stream_start, now, NULL) > 0) + return; + pod->converted = 0; + t->error = 0; + ret = ns = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL); + if (!pod->vf) { + if (ret <= 0) + goto out; + btr_merge(btrn, fn->min_iqs); + ret = ogg_init(fn); + if (ret <= 0) + goto out; + } + for (;;) { + char *out = para_malloc(OGGDEC_OUTPUT_CHUNK_SIZE); + ssize_t read_ret = ov_read(pod->vf, out, OGGDEC_OUTPUT_CHUNK_SIZE, + ENDIAN, 2 /* 16 bit */, 1 /* signed */, NULL); + btr_consume(btrn, pod->converted); + pod->converted = 0; + if (read_ret <= 0) + free(out); + ret = ns; + if (read_ret == 0 || read_ret == OV_HOLE) + goto out; + ret = -E_OGGDEC_BADLINK; + if (read_ret < 0) + goto out; + btr_add_output(out, read_ret, btrn); + if (btr_get_output_queue_size(btrn) > 128 * 1024) + return; /* enough data for the moment */ + } +out: + if (ret < 0) { + t->error = ret; + btr_remove_node(btrn); + } } /** @@ -219,15 +249,9 @@ err: */ void oggdec_filter_init(struct filter *f) { - struct oggdec_filter_args_info dummy; - - oggdec_cmdline_parser_init(&dummy); f->open = ogg_open; f->close = ogg_close; - f->convert = ogg_convert; - f->parse_config = oggdec_parse_config; - f->help = (struct ggo_help) { - .short_help = oggdec_filter_args_info_help, - .detailed_help = oggdec_filter_args_info_detailed_help - }; + f->pre_select = ogg_pre_select; + f->post_select = ogg_post_select; + f->execute = oggdec_execute; } diff --git a/osl.c b/osl.c deleted file mode 100644 index e2c1ef46..00000000 --- a/osl.c +++ /dev/null @@ -1,2082 +0,0 @@ -/* - * Copyright (C) 2007-2009 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ - -/** \file osl.c Object storage layer functions. */ -#include /* readdir() */ -#include - - -#include "para.h" -#include "error.h" -#include "fd.h" -#include "osl_core.h" -/** - * A wrapper for lseek(2). - * - * \param fd The file descriptor whose offset is to be to repositioned. - * \param offset A value-result parameter. - * \param whence Usual repositioning directive. - * - * Reposition the offset of the file descriptor \a fd to the argument \a offset - * according to the directive \a whence. Upon successful return, \a offset - * contains the resulting offset location as measured in bytes from the - * beginning of the file. - * - * \return Positive on success. Otherwise, the function returns \p -E_LSEEK. - * - * \sa lseek(2). - */ -int para_lseek(int fd, off_t *offset, int whence) -{ - int ret = -E_LSEEK; - - *offset = lseek(fd, *offset, whence); - if (*offset == -1) - return ret; - return 1; -} - -/** - * Wrapper for the write system call. - * - * \param fd The file descriptor to write to. - * \param buf The buffer to write. - * \param size The length of \a buf in bytes. - * - * This function writes out the given buffer and retries if an interrupt - * occurred during the write. - * - * \return On success, the number of bytes written is returned, otherwise, the - * function returns \p -E_WRITE. - * - * \sa write(2). - */ -ssize_t para_write(int fd, const void *buf, size_t size) -{ - ssize_t ret; - - for (;;) { - ret = write(fd, buf, size); - if ((ret < 0) && (errno == EAGAIN || errno == EINTR)) - continue; - return ret >= 0? ret : -E_WRITE; - } -} - -/** - * Write the whole buffer to a file descriptor. - * - * \param fd The file descriptor to write to. - * \param buf The buffer to write. - * \param size The length of \a buf in bytes. - * - * This function writes the given buffer and continues on short writes and - * when interrupted by a signal. - * - * \return Positive on success, negative on errors. Possible errors: any - * errors returned by para_write(). - * - * \sa para_write(). - */ -ssize_t para_write_all(int fd, const void *buf, size_t size) -{ -// PARA_DEBUG_LOG("writing %zu bytes\n", size); - const char *b = buf; - while (size) { - ssize_t ret = para_write(fd, b, size); -// PARA_DEBUG_LOG("ret: %zd\n", ret); - if (ret < 0) - return ret; - b += ret; - size -= ret; - } - return 1; -} -/** - * Open a file, write the given buffer and close the file. - * - * \param filename Full path to the file to open. - * \param buf The buffer to write to the file. - * \param size The size of \a buf. - * - * \return Positive on success, negative on errors. Possible errors include: - * any errors from para_open() or para_write(). - * - * \sa para_open(), para_write(). - */ -int para_write_file(const char *filename, const void *buf, size_t size) -{ - int ret, fd; - - ret = para_open(filename, O_WRONLY | O_CREAT | O_EXCL, 0644); - if (ret < 0) - return ret; - fd = ret; - ret = para_write_all(fd, buf, size); - if (ret < 0) - goto out; - ret = 1; -out: - close(fd); - return ret; -} - -static int append_file(const char *filename, char *header, size_t header_size, - char *data, size_t data_size, uint32_t *new_pos) -{ - int ret, fd; - -// PARA_DEBUG_LOG("appending %zu + %zu bytes\n", header_size, data_size); - ret = para_open(filename, O_WRONLY | O_CREAT | O_APPEND, 0644); - if (ret < 0) - return ret; - fd = ret; - if (header && header_size) { - ret = para_write_all(fd, header, header_size); - if (ret < 0) - goto out; - } - ret = para_write_all(fd, data, data_size); - if (ret < 0) - goto out; - if (new_pos) { - off_t offset = 0; - ret = para_lseek(fd, &offset, SEEK_END); - if (ret < 0) - goto out; -// PARA_DEBUG_LOG("new file size: " FMT_OFF_T "\n", offset); - *new_pos = offset; - } - ret = 1; -out: - close(fd); - return ret; -} - -/** - * Traverse the given directory recursively. - * - * \param dirname The directory to traverse. - * \param func The function to call for each entry. - * \param private_data Pointer to an arbitrary data structure. - * - * For each regular file under \a dirname, the supplied function \a func is - * called. The full path of the regular file and the \a private_data pointer - * are passed to \a func. Directories for which the calling process has no - * permissions to change to are silently ignored. - * - * \return Standard. - */ -int for_each_file_in_dir(const char *dirname, - int (*func)(const char *, void *), void *private_data) -{ - DIR *dir; - struct dirent *entry; - int cwd_fd, ret2, ret = para_opendir(dirname, &dir, &cwd_fd); - - if (ret < 0) - return ret == -ERRNO_TO_PARA_ERROR(EACCES)? 1 : ret; - /* scan cwd recursively */ - while ((entry = readdir(dir))) { - mode_t m; - char *tmp; - struct stat s; - - if (!strcmp(entry->d_name, ".")) - continue; - if (!strcmp(entry->d_name, "..")) - continue; - if (lstat(entry->d_name, &s) == -1) - continue; - m = s.st_mode; - if (!S_ISREG(m) && !S_ISDIR(m)) - continue; - tmp = make_message("%s/%s", dirname, entry->d_name); - if (!S_ISDIR(m)) { - ret = func(tmp, private_data); - free(tmp); - if (ret < 0) - goto out; - continue; - } - /* directory */ - ret = for_each_file_in_dir(tmp, func, private_data); - free(tmp); - if (ret < 0) - goto out; - } - ret = 1; -out: - closedir(dir); - ret2 = para_fchdir(cwd_fd); - if (ret2 < 0 && ret >= 0) - ret = ret2; - close(cwd_fd); - return ret; -} - -static int verify_name(const char *name) -{ - if (!name) - return -E_BAD_NAME; - if (!*name) - return -E_BAD_NAME; - if (strchr(name, '/')) - return -E_BAD_NAME; - if (!strcmp(name, "..")) - return -E_BAD_NAME; - if (!strcmp(name, ".")) - return -E_BAD_NAME; - return 1; -} - -/** - * Compare two osl objects pointing to unsigned integers of 32 bit size. - * - * \param obj1 Pointer to the first integer. - * \param obj2 Pointer to the second integer. - * - * \return The values required for an osl compare function. - * - * \sa osl_compare_func, osl_hash_compare(). - */ -int uint32_compare(const struct osl_object *obj1, const struct osl_object *obj2) -{ - uint32_t d1 = read_u32((const char *)obj1->data); - uint32_t d2 = read_u32((const char *)obj2->data); - - if (d1 < d2) - return 1; - if (d1 > d2) - return -1; - return 0; -} - -/** - * Compare two osl objects pointing to hash values. - * - * \param obj1 Pointer to the first hash object. - * \param obj2 Pointer to the second hash object. - * - * \return The values required for an osl compare function. - * - * \sa osl_compare_func, uint32_compare(). - */ -int osl_hash_compare(const struct osl_object *obj1, const struct osl_object *obj2) -{ - return hash_compare((HASH_TYPE *)obj1->data, (HASH_TYPE *)obj2->data); -} - -static char *disk_storage_dirname(const struct osl_table *t, unsigned col_num, - const char *ds_name) -{ - char *dirname, *column_name = column_filename(t, col_num); - - if (!(t->desc->flags & OSL_LARGE_TABLE)) - return column_name; - dirname = make_message("%s/%.2s", column_name, ds_name); - free(column_name); - return dirname; -} - -static char *disk_storage_name_of_object(const struct osl_table *t, - const struct osl_object *obj) -{ - HASH_TYPE hash[HASH_SIZE]; - hash_object(obj, hash); - return disk_storage_name_of_hash(t, hash); -} - -static int disk_storage_name_of_row(const struct osl_table *t, - const struct osl_row *row, char **name) -{ - struct osl_object obj; - int ret = osl_get_object(t, row, t->disk_storage_name_column, &obj); - - if (ret < 0) - return ret; - *name = disk_storage_name_of_object(t, &obj); - return 1; -} - -static void column_name_hash(const char *col_name, HASH_TYPE *hash) -{ - hash_function(col_name, strlen(col_name), hash); -} - -static int init_column_descriptions(struct osl_table *t) -{ - int i, j, ret; - const struct osl_column_description *cd; - - ret = verify_name(t->desc->name); - if (ret < 0) - goto err; - ret = -E_BAD_DB_DIR; - if (!t->desc->dir && (t->num_disk_storage_columns || t->num_mapped_columns)) - goto err; - /* the size of the index header without column descriptions */ - t->index_header_size = IDX_COLUMN_DESCRIPTIONS; - FOR_EACH_COLUMN(i, t->desc, cd) { - struct osl_column *col = t->columns + i; - if (cd->storage_flags & OSL_RBTREE) { - if (!cd->compare_function) - return -E_NO_COMPARE_FUNC; - } - if (cd->storage_type == OSL_NO_STORAGE) - continue; - ret = -E_NO_COLUMN_NAME; - if (!cd->name || !cd->name[0]) - goto err; - ret = verify_name(cd->name); - if (ret < 0) - goto err; - t->index_header_size += index_column_description_size(cd->name); - column_name_hash(cd->name, col->name_hash); - ret = -E_DUPLICATE_COL_NAME; - for (j = i + 1; j < t->desc->num_columns; j++) { - const char *name2 = get_column_description(t->desc, - j)->name; - if (cd->name && name2 && !strcmp(cd->name, name2)) - goto err; - } - } - return 1; -err: - return ret; -} - -/** - * Initialize a struct table from given table description. - * - * \param desc The description of the osl table. - * \param table_ptr Result is returned here. - * - * This function performs several sanity checks on \p desc and returns if any - * of these tests fail. On success, a struct \p osl_table is allocated and - * initialized with data derived from \p desc. - * - * \return Positive on success, negative on errors. Possible errors include: \p - * E_BAD_TABLE_DESC, \p E_NO_COLUMN_DESC, \p E_NO_COLUMNS, \p - * E_BAD_STORAGE_TYPE, \p E_BAD_STORAGE_FLAGS, \p E_BAD_STORAGE_SIZE, \p - * E_NO_UNIQUE_RBTREE_COLUMN, \p E_NO_RBTREE_COL. - * - * \sa struct osl_table. - */ -int init_table_structure(const struct osl_table_description *desc, - struct osl_table **table_ptr) -{ - const struct osl_column_description *cd; - struct osl_table *t = para_calloc(sizeof(*t)); - int i, ret = -E_BAD_TABLE_DESC, have_disk_storage_name_column = 0; - - if (!desc) - goto err; - PARA_DEBUG_LOG("creating table structure for '%s' from table " - "description\n", desc->name); - ret = -E_NO_COLUMN_DESC; - if (!desc->column_descriptions) - goto err; - ret = -E_NO_COLUMNS; - if (!desc->num_columns) - goto err; - t->columns = para_calloc(desc->num_columns * sizeof(struct osl_column)); - t->desc = desc; - FOR_EACH_COLUMN(i, t->desc, cd) { - enum osl_storage_type st = cd->storage_type; - enum osl_storage_flags sf = cd->storage_flags; - struct osl_column *col = &t->columns[i]; - - ret = -E_BAD_STORAGE_TYPE; - if (st != OSL_MAPPED_STORAGE && st != OSL_DISK_STORAGE - && st != OSL_NO_STORAGE) - goto err; - ret = -E_BAD_STORAGE_FLAGS; - if (st == OSL_DISK_STORAGE && sf & OSL_RBTREE) - goto err; - ret = -E_BAD_STORAGE_SIZE; - if (sf & OSL_FIXED_SIZE && !cd->data_size) - goto err; - switch (st) { - case OSL_DISK_STORAGE: - t->num_disk_storage_columns++; - break; - case OSL_MAPPED_STORAGE: - t->num_mapped_columns++; - col->index_offset = t->row_index_size; - t->row_index_size += 8; - break; - case OSL_NO_STORAGE: - col->volatile_num = t->num_volatile_columns; - t->num_volatile_columns++; - break; - } - if (sf & OSL_RBTREE) { - col->rbtree_num = t->num_rbtrees; - t->num_rbtrees++; - if ((sf & OSL_UNIQUE) && (st == OSL_MAPPED_STORAGE)) { - if (!have_disk_storage_name_column) - t->disk_storage_name_column = i; - have_disk_storage_name_column = 1; - } - } - } - ret = -E_NO_UNIQUE_RBTREE_COLUMN; - if (t->num_disk_storage_columns && !have_disk_storage_name_column) - goto err; - ret = -E_NO_RBTREE_COL; - if (!t->num_rbtrees) - goto err; - /* success */ - PARA_DEBUG_LOG("OK. Index entry size: %u\n", t->row_index_size); - ret = init_column_descriptions(t); - if (ret < 0) - goto err; - *table_ptr = t; - return 1; -err: - free(t->columns); - free(t); - return ret; -} - -/** - * Read the table description from index header. - * - * \param map The memory mapping of the index file. - * \param desc The values found in the index header are returned here. - * - * Read the index header, check for the paraslash magic string and the table version number. - * Read all information stored in the index header into \a desc. - * - * \return Positive on success, negative on errors. - * - * \sa struct osl_table_description, osl_create_table. - */ -int read_table_desc(struct osl_object *map, struct osl_table_description *desc) -{ - char *buf = map->data; - uint8_t version; - uint16_t header_size; - int ret, i; - unsigned offset; - struct osl_column_description *cd; - - if (map->size < MIN_INDEX_HEADER_SIZE(1)) - return -E_SHORT_TABLE; - if (strncmp(buf + IDX_PARA_MAGIC, PARA_MAGIC, strlen(PARA_MAGIC))) - return -E_NO_MAGIC; - version = read_u8(buf + IDX_VERSION); - if (version < MIN_TABLE_VERSION || version > MAX_TABLE_VERSION) - return -E_VERSION_MISMATCH; - desc->num_columns = read_u8(buf + IDX_TABLE_FLAGS); - desc->flags = read_u8(buf + IDX_TABLE_FLAGS); - desc->num_columns = read_u16(buf + IDX_NUM_COLUMNS); - PARA_DEBUG_LOG("%u columns\n", desc->num_columns); - if (!desc->num_columns) - return -E_NO_COLUMNS; - header_size = read_u16(buf + IDX_HEADER_SIZE); - if (map->size < header_size) - return -E_BAD_SIZE; - desc->column_descriptions = para_calloc(desc->num_columns - * sizeof(struct osl_column_description)); - offset = IDX_COLUMN_DESCRIPTIONS; - FOR_EACH_COLUMN(i, desc, cd) { - char *null_byte; - - ret = -E_SHORT_TABLE; - if (map->size < offset + MIN_IDX_COLUMN_DESCRIPTION_SIZE) { - PARA_ERROR_LOG("map size = %zu < %u = offset + min desc size\n", - map->size, offset + MIN_IDX_COLUMN_DESCRIPTION_SIZE); - goto err; - } - cd->storage_type = read_u16(buf + offset + IDX_CD_STORAGE_TYPE); - cd->storage_flags = read_u16(buf + offset + - IDX_CD_STORAGE_FLAGS); - cd->data_size = read_u32(buf + offset + IDX_CD_DATA_SIZE); - null_byte = memchr(buf + offset + IDX_CD_NAME, '\0', - map->size - offset - IDX_CD_NAME); - ret = -E_INDEX_CORRUPTION; - if (!null_byte) - goto err; - cd->name = para_strdup(buf + offset + IDX_CD_NAME); - offset += index_column_description_size(cd->name); - } - if (offset != header_size) { - ret = -E_INDEX_CORRUPTION; - PARA_ERROR_LOG("real header size = %u != %u = stored header size\n", - offset, header_size); - goto err; - } - return 1; -err: - FOR_EACH_COLUMN(i, desc, cd) - free(cd->name); - return ret; -} - -/* - * check whether the table description given by \p t->desc matches the on-disk - * table structure stored in the index of \a t. - */ -static int compare_table_descriptions(struct osl_table *t) -{ - int i, ret; - struct osl_table_description desc; - const struct osl_column_description *cd1, *cd2; - - /* read the on-disk structure into desc */ - ret = read_table_desc(&t->index_map, &desc); - if (ret < 0) - return ret; - ret = -E_BAD_TABLE_FLAGS; - if (desc.flags != t->desc->flags) - goto out; - ret = -E_BAD_COLUMN_NUM; - if (desc.num_columns != t->desc->num_columns) - goto out; - FOR_EACH_COLUMN(i, t->desc, cd1) { - cd2 = get_column_description(&desc, i); - ret = -E_BAD_STORAGE_TYPE; - if (cd1->storage_type != cd2->storage_type) - goto out; - ret = -E_BAD_STORAGE_FLAGS; - if (cd1->storage_flags != cd2->storage_flags) { - PARA_ERROR_LOG("sf1 = %u != %u = sf2\n", - cd1->storage_flags, cd2->storage_flags); - goto out; - } - ret = -E_BAD_DATA_SIZE; - if (cd1->storage_flags & OSL_FIXED_SIZE) - if (cd1->data_size != cd2->data_size) - goto out; - ret = -E_BAD_COLUMN_NAME; - if (strcmp(cd1->name, cd2->name)) - goto out; - } - PARA_DEBUG_LOG("table description of '%s' matches on-disk data, good\n", - t->desc->name); - ret = 1; -out: - FOR_EACH_COLUMN(i, &desc, cd1) - free(cd1->name); - free(desc.column_descriptions); - return ret; -} - -static int create_table_index(struct osl_table *t) -{ - char *buf, *filename; - int i, ret; - size_t size = t->index_header_size; - const struct osl_column_description *cd; - unsigned offset; - - PARA_INFO_LOG("creating %zu byte index for table %s\n", size, - t->desc->name); - buf = para_calloc(size); - sprintf(buf + IDX_PARA_MAGIC, "%s", PARA_MAGIC); - write_u8(buf + IDX_TABLE_FLAGS, t->desc->flags); - write_u8(buf + IDX_DIRTY_FLAG, 0); - write_u8(buf + IDX_VERSION, CURRENT_TABLE_VERSION); - write_u16(buf + IDX_NUM_COLUMNS, t->desc->num_columns); - write_u16(buf + IDX_HEADER_SIZE, t->index_header_size); - offset = IDX_COLUMN_DESCRIPTIONS; - FOR_EACH_COLUMN(i, t->desc, cd) { - write_u16(buf + offset + IDX_CD_STORAGE_TYPE, - cd->storage_type); - write_u16(buf + offset + IDX_CD_STORAGE_FLAGS, - cd->storage_flags); - if (cd->storage_flags & OSL_FIXED_SIZE) - write_u32(buf + offset + IDX_CD_DATA_SIZE, - cd->data_size); - strcpy(buf + offset + IDX_CD_NAME, cd->name); - offset += index_column_description_size(cd->name); - } - assert(offset = size); - filename = index_filename(t->desc); - ret = para_write_file(filename, buf, size); - free(buf); - free(filename); - return ret; -} - -/** - * Create a new osl table. - * - * \param desc Pointer to the table description. - * - * \return Standard. - */ -int osl_create_table(const struct osl_table_description *desc) -{ - const struct osl_column_description *cd; - char *table_dir = NULL, *filename; - struct osl_table *t; - int i, ret = init_table_structure(desc, &t); - - if (ret < 0) - return ret; - PARA_INFO_LOG("creating %s\n", desc->name); - FOR_EACH_COLUMN(i, t->desc, cd) { - if (cd->storage_type == OSL_NO_STORAGE) - continue; - if (!table_dir) { - ret = para_mkdir(desc->dir, 0777); - if (ret < 0 && !is_errno(-ret, EEXIST)) - goto out; - table_dir = make_message("%s/%s", desc->dir, - desc->name); - ret = para_mkdir(table_dir, 0777); - if (ret < 0) - goto out; - } - filename = column_filename(t, i); - PARA_INFO_LOG("filename: %s\n", filename); - if (cd->storage_type == OSL_MAPPED_STORAGE) { - ret = para_open(filename, O_RDWR | O_CREAT | O_EXCL, - 0644); - free(filename); - if (ret < 0) - goto out; - close(ret); - continue; - } - /* DISK STORAGE */ - ret = para_mkdir(filename, 0777); - free(filename); - if (ret < 0) - goto out; - } - if (t->num_mapped_columns) { - ret = create_table_index(t); - if (ret < 0) - goto out; - } - ret = 1; -out: - free(table_dir); - free(t->columns); - free(t); - return ret; -} - -static int table_is_dirty(struct osl_table *t) -{ - char *buf = (char *)t->index_map.data + IDX_DIRTY_FLAG; - uint8_t dirty = read_u8(buf) & 0x1; - return !!dirty; -} - -static void mark_table_dirty(struct osl_table *t) -{ - char *buf = (char *)t->index_map.data + IDX_DIRTY_FLAG; - write_u8(buf, read_u8(buf) | 1); -} - -static void mark_table_clean(struct osl_table *t) -{ - char *buf = (char *)t->index_map.data + IDX_DIRTY_FLAG; - write_u8(buf, read_u8(buf) & 0xfe); -} - -static void unmap_column(struct osl_table *t, unsigned col_num) -{ - struct osl_object map = t->columns[col_num].data_map; - int ret; - if (!map.data) - return; - ret = para_munmap(map.data, map.size); - assert(ret > 0); - map.data = NULL; -} - -/** - * Unmap all mapped files of an osl table. - * - * \param t Pointer to a mapped table. - * \param flags Options for unmapping. - * - * \return Positive on success, negative on errors. - * - * \sa map_table(), enum osl_close_flags, para_munmap(). - */ -int unmap_table(struct osl_table *t, enum osl_close_flags flags) -{ - unsigned i; - const struct osl_column_description *cd; - int ret; - - if (!t->num_mapped_columns) /* can this ever happen? */ - return 1; - PARA_DEBUG_LOG("unmapping table '%s'\n", t->desc->name); - if (!t->index_map.data) - return -E_NOT_MAPPED; - if (flags & OSL_MARK_CLEAN) - mark_table_clean(t); - ret = para_munmap(t->index_map.data, t->index_map.size); - if (ret < 0) - return ret; - t->index_map.data = NULL; - if (!t->num_rows) - return 1; - FOR_EACH_MAPPED_COLUMN(i, t, cd) - unmap_column(t, i); - return 1; -} - -static int map_column(struct osl_table *t, unsigned col_num) -{ - struct stat statbuf; - char *filename = column_filename(t, col_num); - int ret = -E_STAT; - if (stat(filename, &statbuf) < 0) { - free(filename); - return ret; - } - if (!(S_IFREG & statbuf.st_mode)) { - free(filename); - return ret; - } - ret = mmap_full_file(filename, O_RDWR, - &t->columns[col_num].data_map.data, - &t->columns[col_num].data_map.size, - NULL); - free(filename); - return ret; -} - -/** - * Map the index file and all columns of type \p OSL_MAPPED_STORAGE into memory. - * - * \param t Pointer to an initialized table structure. - * \param flags Mapping options. - * - * \return Negative return value on errors; on success the number of rows - * (including invalid rows) is returned. - * - * \sa unmap_table(), enum map_table_flags, osl_open_table(), mmap(2). - */ -int map_table(struct osl_table *t, enum map_table_flags flags) -{ - char *filename; - const struct osl_column_description *cd; - int i = 0, ret, num_rows = 0; - - if (!t->num_mapped_columns) - return 0; - if (t->index_map.data) - return -E_ALREADY_MAPPED; - filename = index_filename(t->desc); - PARA_DEBUG_LOG("mapping table '%s' (index: %s)\n", t->desc->name, filename); - ret = mmap_full_file(filename, flags & MAP_TBL_FL_MAP_RDONLY? - O_RDONLY : O_RDWR, &t->index_map.data, &t->index_map.size, NULL); - free(filename); - if (ret < 0) - return ret; - if (flags & MAP_TBL_FL_VERIFY_INDEX) { - ret = compare_table_descriptions(t); - if (ret < 0) - goto err; - } - ret = -E_BUSY; - if (!(flags & MAP_TBL_FL_IGNORE_DIRTY)) { - if (table_is_dirty(t)) { - PARA_ERROR_LOG("%s is dirty\n", t->desc->name); - goto err; - } - } - mark_table_dirty(t); - num_rows = table_num_rows(t); - if (!num_rows) - return num_rows; - /* map data files */ - FOR_EACH_MAPPED_COLUMN(i, t, cd) { - ret = map_column(t, i); - if (ret < 0) - goto err; - } - return num_rows; -err: /* unmap what is already mapped */ - for (i--; i >= 0; i--) { - struct osl_object map = t->columns[i].data_map; - para_munmap(map.data, map.size); - map.data = NULL; - } - para_munmap(t->index_map.data, t->index_map.size); - t->index_map.data = NULL; - return ret; -} - -/** - * Retrieve a mapped object by row and column number. - * - * \param t Pointer to an open osl table. - * \param col_num Number of the mapped column containing the object to retrieve. - * \param row_num Number of the row containing the object to retrieve. - * \param obj The result is returned here. - * - * It is considered an error if \a col_num does not refer to a column - * of storage type \p OSL_MAPPED_STORAGE. - * - * \return Positive on success, negative on errors. Possible errors include: - * \p E_BAD_ROW_NUM, \p E_INVALID_OBJECT. - * - * \sa osl_storage_type. - */ -int get_mapped_object(const struct osl_table *t, unsigned col_num, - uint32_t row_num, struct osl_object *obj) -{ - struct osl_column *col = &t->columns[col_num]; - uint32_t offset; - char *header; - char *cell_index; - int ret; - - if (t->num_rows <= row_num) - return -E_BAD_ROW_NUM; - ret = get_cell_index(t, row_num, col_num, &cell_index); - if (ret < 0) - return ret; - offset = read_u32(cell_index); - obj->size = read_u32(cell_index + 4) - 1; - header = col->data_map.data + offset; - obj->data = header + 1; - if (read_u8(header) == 0xff) { - PARA_ERROR_LOG("col %u, size %zu, offset %u\n", col_num, - obj->size, offset); - return -E_INVALID_OBJECT; - } - return 1; -} - -static int search_rbtree(const struct osl_object *obj, - const struct osl_table *t, unsigned col_num, - struct rb_node **result, struct rb_node ***rb_link) -{ - struct osl_column *col = &t->columns[col_num]; - struct rb_node **new = &col->rbtree.rb_node, *parent = NULL; - const struct osl_column_description *cd = - get_column_description(t->desc, col_num); - enum osl_storage_type st = cd->storage_type; - while (*new) { - struct osl_row *this_row = get_row_pointer(*new, - col->rbtree_num); - int ret; - struct osl_object this_obj; - parent = *new; - if (st == OSL_MAPPED_STORAGE) { - ret = get_mapped_object(t, col_num, this_row->num, - &this_obj); - if (ret < 0) - return ret; - } else - this_obj = this_row->volatile_objects[col->volatile_num]; - ret = cd->compare_function(obj, &this_obj); - if (!ret) { - if (result) - *result = get_rb_node_pointer(this_row, - col->rbtree_num); - return 1; - } - if (ret < 0) - new = &((*new)->rb_left); - else - new = &((*new)->rb_right); - } - if (result) - *result = parent; - if (rb_link) - *rb_link = new; - return -E_RB_KEY_NOT_FOUND; -} - -static int insert_rbtree(struct osl_table *t, unsigned col_num, - const struct osl_row *row, const struct osl_object *obj) -{ - struct rb_node *parent, **rb_link; - unsigned rbtree_num; - struct rb_node *n; - int ret = search_rbtree(obj, t, col_num, &parent, &rb_link); - - if (ret > 0) - return -E_RB_KEY_EXISTS; - rbtree_num = t->columns[col_num].rbtree_num; - n = get_rb_node_pointer(row, rbtree_num); - rb_link_node(n, parent, rb_link); - rb_insert_color(n, &t->columns[col_num].rbtree); - return 1; -} - -static void remove_rb_node(struct osl_table *t, unsigned col_num, - const struct osl_row *row) -{ - struct osl_column *col = &t->columns[col_num]; - const struct osl_column_description *cd = - get_column_description(t->desc, col_num); - enum osl_storage_flags sf = cd->storage_flags; - struct rb_node *victim, *splice_out_node, *tmp; - if (!(sf & OSL_RBTREE)) - return; - /* - * Which node is removed/spliced out actually depends on how many - * children the victim node has: If it has no children, it gets - * deleted. If it has one child, it gets spliced out. If it has two - * children, its successor (which has at most a right child) gets - * spliced out. - */ - victim = get_rb_node_pointer(row, col->rbtree_num); - if (victim->rb_left && victim->rb_right) - splice_out_node = rb_next(victim); - else - splice_out_node = victim; - /* Go up to the root and decrement the size of each node in the path. */ - for (tmp = splice_out_node; tmp; tmp = rb_parent(tmp)) - tmp->size--; - rb_erase(victim, &col->rbtree); -} - -static int add_row_to_rbtrees(struct osl_table *t, uint32_t row_num, - struct osl_object *volatile_objs, struct osl_row **row_ptr) -{ - unsigned i; - int ret; - struct osl_row *row = allocate_row(t->num_rbtrees); - const struct osl_column_description *cd; - - row->num = row_num; - row->volatile_objects = volatile_objs; - FOR_EACH_RBTREE_COLUMN(i, t, cd) { - if (cd->storage_type == OSL_MAPPED_STORAGE) { - struct osl_object obj; - ret = get_mapped_object(t, i, row_num, &obj); - if (ret < 0) - goto err; - ret = insert_rbtree(t, i, row, &obj); - } else { /* volatile */ - const struct osl_object *obj - = volatile_objs + t->columns[i].volatile_num; - ret = insert_rbtree(t, i, row, obj); - } - if (ret < 0) - goto err; - } - if (row_ptr) - *row_ptr = row; - return 1; -err: /* rollback changes, i.e. remove added entries from rbtrees */ - while (i) - remove_rb_node(t, i--, row); - free(row); - return ret; -} - -static void free_volatile_objects(const struct osl_table *t, - enum osl_close_flags flags) -{ - int i, j; - struct rb_node *n; - struct osl_column *rb_col; - const struct osl_column_description *cd; - - if (!t->num_volatile_columns) - return; - /* find the first rbtree column (any will do) */ - FOR_EACH_RBTREE_COLUMN(i, t, cd) - break; - rb_col = t->columns + i; - /* walk that rbtree and free all volatile objects */ - for (n = rb_first(&rb_col->rbtree); n; n = rb_next(n)) { - struct osl_row *r = get_row_pointer(n, rb_col->rbtree_num); - if (flags & OSL_FREE_VOLATILE) - FOR_EACH_VOLATILE_COLUMN(j, t, cd) { - if (cd->storage_flags & OSL_DONT_FREE) - continue; - free(r->volatile_objects[ - t->columns[j].volatile_num].data); - } -// for (j = 0; j < t->num_volatile_columns; j++) -// free(r->volatile_objects[j].data); - free(r->volatile_objects); - } -} - -/** - * Erase all rbtree nodes and free resources. - * - * \param t Pointer to an open osl table. - * - * This function is called by osl_close_table(). - */ -void clear_rbtrees(struct osl_table *t) -{ - const struct osl_column_description *cd; - unsigned i, rbtrees_cleared = 0; - - FOR_EACH_RBTREE_COLUMN(i, t, cd) { - struct osl_column *col = &t->columns[i]; - struct rb_node *n; - rbtrees_cleared++; - for (n = rb_first(&col->rbtree); n;) { - struct osl_row *r; - rb_erase(n, &col->rbtree); - if (rbtrees_cleared == t->num_rbtrees) { - r = get_row_pointer(n, col->rbtree_num); - n = rb_next(n); - free(r); - } else - n = rb_next(n); - } - } - -} - -/** - * Close an osl table. - * - * \param t Pointer to the table to be closed. - * \param flags Options for what should be cleaned up. - * - * If osl_open_table() succeeds, the resulting table pointer must later be - * passed to this function in order to flush all changes to the file system and - * to free the resources that were allocated by osl_open_table(). - * - * \return Positive on success, negative on errors. Possible errors: \p E_BAD_TABLE, - * errors returned by unmap_table(). - * - * \sa osl_open_table(), unmap_table(). - */ -int osl_close_table(struct osl_table *t, enum osl_close_flags flags) -{ - int ret; - - if (!t) - return -E_BAD_TABLE; - free_volatile_objects(t, flags); - clear_rbtrees(t); - ret = unmap_table(t, flags); - if (ret < 0) - PARA_ERROR_LOG("unmap_table failed: %d\n", ret); - free(t->columns); - free(t); - return ret; -} - -/** - * Find out whether the given row number corresponds to an invalid row. - * - * \param t Pointer to the osl table. - * \param row_num The number of the row in question. - * - * By definition, a row is considered invalid if all its index entries - * are invalid. - * - * \return Positive if \a row_num corresponds to an invalid row, - * zero if it corresponds to a valid row, negative on errors. - */ -int row_is_invalid(struct osl_table *t, uint32_t row_num) -{ - char *row_index; - int i, ret = get_row_index(t, row_num, &row_index); - - if (ret < 0) - return ret; - for (i = 0; i < t->row_index_size; i++) { - if ((unsigned char)row_index[i] != 0xff) - return 0; - } - PARA_INFO_LOG("row %d is invalid\n", row_num); - return 1; -} - -/** - * Invalidate a row of an osl table. - * - * \param t Pointer to an open osl table. - * \param row_num Number of the row to mark as invalid. - * - * This function marks each mapped object in the index entry of \a row as - * invalid. - * - * \return Positive on success, negative on errors. - */ -int mark_row_invalid(struct osl_table *t, uint32_t row_num) -{ - char *row_index; - int ret = get_row_index(t, row_num, &row_index); - - if (ret < 0) - return ret; - PARA_INFO_LOG("marking row %d as invalid\n", row_num); - memset(row_index, 0xff, t->row_index_size); - return 1; -} - -/** - * Initialize all rbtrees and compute number of invalid rows. - * - * \param t The table containing the rbtrees to be initialized. - * - * \return Positive on success, negative on errors. - */ -int init_rbtrees(struct osl_table *t) -{ - int i, ret; - const struct osl_column_description *cd; - - /* create rbtrees */ - FOR_EACH_RBTREE_COLUMN(i, t, cd) - t->columns[i].rbtree = RB_ROOT; - /* add valid rows to rbtrees */ - t->num_invalid_rows = 0; - for (i = 0; i < t->num_rows; i++) { - ret = row_is_invalid(t, i); - if (ret < 0) - return ret; - if (ret) { - t->num_invalid_rows++; - continue; - } - ret = add_row_to_rbtrees(t, i, NULL, NULL); - if (ret < 0) - return ret; - } - return 1; -} - -/** - * Open an osl table. - * - * Each osl table must be opened before its data can be accessed. - * - * \param table_desc Describes the table to be opened. - * \param result Contains a pointer to the open table on success. - * - * The table description given by \a desc should coincide with the - * description used at creation time. - * - * \return Standard. - */ -int osl_open_table(const struct osl_table_description *table_desc, - struct osl_table **result) -{ - int i, ret; - struct osl_table *t; - const struct osl_column_description *cd; - - PARA_INFO_LOG("opening table %s\n", table_desc->name); - ret = init_table_structure(table_desc, &t); - if (ret < 0) - return ret; - FOR_EACH_DISK_STORAGE_COLUMN(i, t, cd) { - /* check if directory exists */ - char *dirname = column_filename(t, i); - struct stat statbuf; - ret = stat(dirname, &statbuf); - free(dirname); - if (ret < 0) { - ret = -ERRNO_TO_PARA_ERROR(errno); - goto err; - } - ret = -ERRNO_TO_PARA_ERROR(ENOTDIR); - if (!S_ISDIR(statbuf.st_mode)) - goto err; - } - ret = map_table(t, MAP_TBL_FL_VERIFY_INDEX); - if (ret < 0) - goto err; - t->num_rows = ret; - PARA_DEBUG_LOG("num rows: %d\n", t->num_rows); - ret = init_rbtrees(t); - if (ret < 0) { - osl_close_table(t, OSL_MARK_CLEAN); /* ignore further errors */ - return ret; - } - *result = t; - return 1; -err: - free(t->columns); - free(t); - return ret; -} - -static int create_disk_storage_object_dir(const struct osl_table *t, - unsigned col_num, const char *ds_name) -{ - char *dirname; - int ret; - - if (!(t->desc->flags & OSL_LARGE_TABLE)) - return 1; - dirname = disk_storage_dirname(t, col_num, ds_name); - ret = para_mkdir(dirname, 0777); - free(dirname); - if (ret < 0 && !is_errno(-ret, EEXIST)) - return ret; - return 1; -} - -static int write_disk_storage_file(const struct osl_table *t, unsigned col_num, - const struct osl_object *obj, const char *ds_name) -{ - int ret; - char *filename; - - ret = create_disk_storage_object_dir(t, col_num, ds_name); - if (ret < 0) - return ret; - filename = disk_storage_path(t, col_num, ds_name); - ret = para_write_file(filename, obj->data, obj->size); - free(filename); - return ret; -} - -static int append_map_file(const struct osl_table *t, unsigned col_num, - const struct osl_object *obj, uint32_t *new_size) -{ - char *filename = column_filename(t, col_num); - int ret; - char header = 0; /* zero means valid object */ - -// PARA_DEBUG_LOG("appending %zu + 1 byte\n", obj->size); - ret = append_file(filename, &header, 1, obj->data, obj->size, - new_size); - free(filename); - return ret; -} - -static int append_row_index(const struct osl_table *t, char *row_index) -{ - char *filename; - int ret; - - if (!t->num_mapped_columns) - return 1; - filename = index_filename(t->desc); - ret = append_file(filename, NULL, 0, row_index, - t->row_index_size, NULL); - free(filename); - return ret; -} - -/** - * A wrapper for truncate(2) - * - * \param path Name of the regular file to truncate - * \param size Number of bytes to \b shave \b off - * - * Truncate the regular file named by \a path by \a size bytes. - * - * \return Positive on success, negative on errors. Possible errors include: \p - * E_STAT, \p E_BAD_SIZE, \p E_TRUNC. - * - * \sa truncate(2) - */ -int para_truncate(const char *path, off_t size) -{ - int ret; - struct stat statbuf; - - ret = -E_STAT; - if (stat(path, &statbuf) < 0) - goto out; - ret = -E_BAD_SIZE; - if (statbuf.st_size < size) - goto out; - ret = -E_TRUNC; - if (truncate(path, statbuf.st_size - size) < 0) - goto out; - ret = 1; -out: - return ret; -} - -static int truncate_mapped_file(const struct osl_table *t, unsigned col_num, - off_t size) -{ - char *filename = column_filename(t, col_num); - int ret = para_truncate(filename, size); - free(filename); - return ret; -} - -static int delete_disk_storage_file(const struct osl_table *t, unsigned col_num, - const char *ds_name) -{ - char *dirname, *filename = disk_storage_path(t, col_num, ds_name); - int ret = unlink(filename), err = errno; - - free(filename); - if (ret < 0) - return -ERRNO_TO_PARA_ERROR(err); - if (!(t->desc->flags & OSL_LARGE_TABLE)) - return 1; - dirname = disk_storage_dirname(t, col_num, ds_name); - rmdir(dirname); - free(dirname); - return 1; -} - -/** - * Add a new row to an osl table and retrieve this row. - * - * \param t Pointer to an open osl table. - * \param objects Array of objects to be added. - * \param row Result pointer. - * - * The \a objects parameter must point to an array containing one object per - * column. The order of the objects in the array is given by the table - * description of \a table. Several sanity checks are performed during object - * insertion and the function returns without modifying the table if any of - * these tests fail. In fact, it is atomic in the sense that it either - * succeeds or leaves the table unchanged (i.e. either all or none of the - * objects are added to the table). - * - * It is considered an error if an object is added to a column with associated - * rbtree if this object is equal to an object already contained in that column - * (i.e. the compare function for the column's rbtree returns zero). - * - * Possible errors include: \p E_RB_KEY_EXISTS, \p E_BAD_DATA_SIZE. - * - * \return Positive on success, negative on errors. - * - * \sa struct osl_table_description, osl_compare_func, osl_add_row(). - */ -int osl_add_and_get_row(struct osl_table *t, struct osl_object *objects, - struct osl_row **row) -{ - int i, ret; - char *ds_name = NULL; - struct rb_node **rb_parents = NULL, ***rb_links = NULL; - char *new_row_index = NULL; - struct osl_object *volatile_objs = NULL; - const struct osl_column_description *cd; - - if (!t) - return -E_BAD_TABLE; - rb_parents = para_malloc(t->num_rbtrees * sizeof(struct rn_node*)); - rb_links = para_malloc(t->num_rbtrees * sizeof(struct rn_node**)); - if (t->num_mapped_columns) - new_row_index = para_malloc(t->row_index_size); - /* pass 1: sanity checks */ -// PARA_DEBUG_LOG("sanity tests: %p:%p\n", objects[0].data, -// objects[1].data); - FOR_EACH_COLUMN(i, t->desc, cd) { - enum osl_storage_type st = cd->storage_type; - enum osl_storage_flags sf = cd->storage_flags; - -// ret = -E_NULL_OBJECT; -// if (!objects[i]) -// goto out; - if (st == OSL_DISK_STORAGE) - continue; - if (sf & OSL_RBTREE) { - unsigned rbtree_num = t->columns[i].rbtree_num; - ret = -E_RB_KEY_EXISTS; -// PARA_DEBUG_LOG("checking whether %p exists\n", -// objects[i].data); - if (search_rbtree(objects + i, t, i, - &rb_parents[rbtree_num], - &rb_links[rbtree_num]) > 0) - goto out; - } - if (sf & OSL_FIXED_SIZE) { -// PARA_DEBUG_LOG("fixed size. need: %zu, have: %d\n", -// objects[i].size, cd->data_size); - ret = -E_BAD_DATA_SIZE; - if (objects[i].size != cd->data_size) - goto out; - } - } - if (t->num_disk_storage_columns) - ds_name = disk_storage_name_of_object(t, - &objects[t->disk_storage_name_column]); - ret = unmap_table(t, OSL_MARK_CLEAN); - if (ret < 0) - goto out; -// PARA_DEBUG_LOG("sanity tests passed%s\n", ""); - /* pass 2: create data files, append map data */ - FOR_EACH_COLUMN(i, t->desc, cd) { - enum osl_storage_type st = cd->storage_type; - if (st == OSL_NO_STORAGE) - continue; - if (st == OSL_MAPPED_STORAGE) { - uint32_t new_size; - struct osl_column *col = &t->columns[i]; -// PARA_DEBUG_LOG("appending object of size %zu\n", -// objects[i].size); - ret = append_map_file(t, i, objects + i, &new_size); - if (ret < 0) - goto rollback; - update_cell_index(new_row_index, col, new_size, - objects[i].size); - continue; - } - /* DISK_STORAGE */ - ret = write_disk_storage_file(t, i, objects + i, ds_name); - if (ret < 0) - goto rollback; - } - ret = append_row_index(t, new_row_index); - if (ret < 0) - goto rollback; - ret = map_table(t, MAP_TBL_FL_VERIFY_INDEX); - if (ret < 0) { /* truncate index and rollback changes */ - char *filename = index_filename(t->desc); - para_truncate(filename, t->row_index_size); - free(filename); - goto rollback; - } - /* pass 3: add entry to rbtrees */ - if (t->num_volatile_columns) { - volatile_objs = para_calloc(t->num_volatile_columns - * sizeof(struct osl_object)); - FOR_EACH_VOLATILE_COLUMN(i, t, cd) - volatile_objs[t->columns[i].volatile_num] = objects[i]; - } - t->num_rows++; -// PARA_DEBUG_LOG("adding new entry as row #%d\n", t->num_rows - 1); - ret = add_row_to_rbtrees(t, t->num_rows - 1, volatile_objs, row); - if (ret < 0) - goto out; -// PARA_DEBUG_LOG("added new entry as row #%d\n", t->num_rows - 1); - ret = 1; - goto out; -rollback: /* rollback all changes made, ignore further errors */ - for (i--; i >= 0; i--) { - enum osl_storage_type st; - - cd = get_column_description(t->desc, i); - st = cd->storage_type; - if (st == OSL_NO_STORAGE) - continue; - - if (st == OSL_MAPPED_STORAGE) - truncate_mapped_file(t, i, objects[i].size); - else /* disk storage */ - delete_disk_storage_file(t, i, ds_name); - } - /* ignore error and return previous error value */ - map_table(t, MAP_TBL_FL_VERIFY_INDEX); -out: - free(new_row_index); - free(ds_name); - free(rb_parents); - free(rb_links); - return ret; -} - -/** - * Add a new row to an osl table. - * - * \param t Same meaning as osl_add_and_get_row(). - * \param objects Same meaning as osl_add_and_get_row(). - * - * \return The return value of the underlying call to osl_add_and_get_row(). - * - * This is equivalent to osl_add_and_get_row(t, objects, NULL). - */ -int osl_add_row(struct osl_table *t, struct osl_object *objects) -{ - return osl_add_and_get_row(t, objects, NULL); -} - -/** - * Retrieve an object identified by row and column - * - * \param t Pointer to an open osl table. - * \param r Pointer to the row. - * \param col_num The column number. - * \param object The result pointer. - * - * The column determined by \a col_num must be of type \p OSL_MAPPED_STORAGE - * or \p OSL_NO_STORAGE, i.e. no disk storage objects may be retrieved by this - * function. - * - * \return Positive if object was found, negative on errors. Possible errors - * include: \p E_BAD_TABLE, \p E_BAD_STORAGE_TYPE. - * - * \sa osl_storage_type, osl_open_disk_object(). - */ -int osl_get_object(const struct osl_table *t, const struct osl_row *r, - unsigned col_num, struct osl_object *object) -{ - const struct osl_column_description *cd; - - if (!t) - return -E_BAD_TABLE; - cd = get_column_description(t->desc, col_num); - /* col must not be disk storage */ - if (cd->storage_type == OSL_DISK_STORAGE) - return -E_BAD_STORAGE_TYPE; - if (cd->storage_type == OSL_MAPPED_STORAGE) - return get_mapped_object(t, col_num, r->num, object); - /* volatile */ - *object = r->volatile_objects[t->columns[col_num].volatile_num]; - return 1; -} - -static int mark_mapped_object_invalid(const struct osl_table *t, - uint32_t row_num, unsigned col_num) -{ - struct osl_object obj; - char *p; - int ret = get_mapped_object(t, col_num, row_num, &obj); - - if (ret < 0) - return ret; - p = obj.data; - p--; - *p = 0xff; - return 1; -} - -/** - * Delete a row from an osl table. - * - * \param t Pointer to an open osl table. - * \param row Pointer to the row to delete. - * - * This removes all disk storage objects, removes all rbtree nodes, and frees - * all volatile objects belonging to the given row. For mapped columns, the - * data is merely marked invalid and may be pruned from time to time by - * para_fsck. - * - * \return Positive on success, negative on errors. Possible errors include: - * \p E_BAD_TABLE, errors returned by osl_get_object(). - */ -int osl_del_row(struct osl_table *t, struct osl_row *row) -{ - struct osl_row *r = row; - int i, ret; - const struct osl_column_description *cd; - - if (!t) - return -E_BAD_TABLE; - PARA_INFO_LOG("deleting row %p\n", row); - - if (t->num_disk_storage_columns) { - char *ds_name; - ret = disk_storage_name_of_row(t, r, &ds_name); - if (ret < 0) - goto out; - FOR_EACH_DISK_STORAGE_COLUMN(i, t, cd) - delete_disk_storage_file(t, i, ds_name); - free(ds_name); - } - FOR_EACH_COLUMN(i, t->desc, cd) { - struct osl_column *col = t->columns + i; - enum osl_storage_type st = cd->storage_type; - remove_rb_node(t, i, r); - if (st == OSL_MAPPED_STORAGE) { - mark_mapped_object_invalid(t, r->num, i); - continue; - } - if (st == OSL_NO_STORAGE && !(cd->storage_flags & OSL_DONT_FREE)) - free(r->volatile_objects[col->volatile_num].data); - } - if (t->num_mapped_columns) { - ret = mark_row_invalid(t, r->num); - if (ret < 0) - goto out; - t->num_invalid_rows++; - } else - t->num_rows--; - ret = 1; -out: - free(r->volatile_objects); - free(r); - return ret; -} - -/* test if column has an rbtree */ -static int check_rbtree_col(const struct osl_table *t, unsigned col_num, - struct osl_column **col) -{ - if (!t) - return -E_BAD_TABLE; - if (!(get_column_description(t->desc, col_num)->storage_flags & OSL_RBTREE)) - return -E_BAD_STORAGE_FLAGS; - *col = t->columns + col_num; - return 1; -} - -/** - * Get the row that contains the given object. - * - * \param t Pointer to an open osl table. - * \param col_num The number of the column to be searched. - * \param obj The object to be looked up. - * \param result Points to the row containing \a obj. - * - * Lookup \a obj in \a t and return the row containing \a obj. The column - * specified by \a col_num must have an associated rbtree. - * - * \return Positive on success, negative on errors. If an error occurred, \a - * result is set to \p NULL. Possible errors include: \p E_BAD_TABLE, \p - * E_BAD_STORAGE_FLAGS, errors returned by get_mapped_object(), \p - * E_RB_KEY_NOT_FOUND. - * - * \sa osl_storage_flags - */ -int osl_get_row(const struct osl_table *t, unsigned col_num, - const struct osl_object *obj, struct osl_row **result) -{ - int ret; - struct rb_node *node; - struct osl_row *row; - struct osl_column *col; - - *result = NULL; - ret = check_rbtree_col(t, col_num, &col); - if (ret < 0) - return ret; - ret = search_rbtree(obj, t, col_num, &node, NULL); - if (ret < 0) - return ret; - row = get_row_pointer(node, t->columns[col_num].rbtree_num); - *result = row; - return 1; -} - -static int rbtree_loop(struct osl_column *col, void *private_data, - osl_rbtree_loop_func *func) -{ - struct rb_node *n, *tmp; - - /* this for-loop is safe against removal of an entry */ - for (n = rb_first(&col->rbtree), tmp = n? rb_next(n) : NULL; - n; - n = tmp, tmp = tmp? rb_next(tmp) : NULL) { - struct osl_row *r = get_row_pointer(n, col->rbtree_num); - int ret = func(r, private_data); - if (ret < 0) - return ret; - } - return 1; -} - -static int rbtree_loop_reverse(struct osl_column *col, void *private_data, - osl_rbtree_loop_func *func) -{ - struct rb_node *n, *tmp; - - /* safe against removal of an entry */ - for (n = rb_last(&col->rbtree), tmp = n? rb_prev(n) : NULL; - n; - n = tmp, tmp = tmp? rb_prev(tmp) : NULL) { - struct osl_row *r = get_row_pointer(n, col->rbtree_num); - int ret = func(r, private_data); - if (ret < 0) - return ret; - } - return 1; -} - -/** - * Loop over all nodes in an rbtree. - * - * \param t Pointer to an open osl table. - * \param col_num The column to use for iterating over the elements. - * \param private_data Pointer that gets passed to \a func. - * \param func The function to be called for each node in the rbtree. - * - * This function does an in-order walk of the rbtree associated with \a - * col_num. It is an error if the \p OSL_RBTREE flag is not set for this - * column. For each node in the rbtree, the given function \a func is called - * with two pointers as arguments: The first osl_row* argument points to the - * row that contains the object corresponding to the rbtree node currently - * traversed, and the \a private_data pointer is passed verbatim to \a func as the - * second argument. The loop terminates either if \a func returns a negative - * value, or if all nodes of the tree have been visited. - * - * - * \return Positive on success, negative on errors. If the termination of the - * loop was caused by \a func returning a negative value, this value is - * returned. - * - * \sa osl_storage_flags, osl_rbtree_loop_reverse(), osl_compare_func. - */ -int osl_rbtree_loop(const struct osl_table *t, unsigned col_num, - void *private_data, osl_rbtree_loop_func *func) -{ - struct osl_column *col; - - int ret = check_rbtree_col(t, col_num, &col); - if (ret < 0) - return ret; - return rbtree_loop(col, private_data, func); -} - -/** - * Loop over all nodes in an rbtree in reverse order. - * - * \param t Identical meaning as in \p osl_rbtree_loop(). - * \param col_num Identical meaning as in \p osl_rbtree_loop(). - * \param private_data Identical meaning as in \p osl_rbtree_loop(). - * \param func Identical meaning as in \p osl_rbtree_loop(). - * - * This function is identical to \p osl_rbtree_loop(), the only difference - * is that the tree is walked in reverse order. - * - * \return The same return value as \p osl_rbtree_loop(). - * - * \sa osl_rbtree_loop(). - */ -int osl_rbtree_loop_reverse(const struct osl_table *t, unsigned col_num, - void *private_data, osl_rbtree_loop_func *func) -{ - struct osl_column *col; - - int ret = check_rbtree_col(t, col_num, &col); - if (ret < 0) - return ret; - return rbtree_loop_reverse(col, private_data, func); -} - -/* TODO: Rollback changes on errors */ -static int rename_disk_storage_objects(struct osl_table *t, - struct osl_object *old_obj, struct osl_object *new_obj) -{ - int i, ret; - const struct osl_column_description *cd; - char *old_ds_name, *new_ds_name; - - if (!t->num_disk_storage_columns) - return 1; /* nothing to do */ - if (old_obj->size == new_obj->size && !memcmp(new_obj->data, - old_obj->data, new_obj->size)) - return 1; /* object did not change */ - old_ds_name = disk_storage_name_of_object(t, old_obj); - new_ds_name = disk_storage_name_of_object(t, new_obj); - FOR_EACH_DISK_STORAGE_COLUMN(i, t, cd) { - char *old_filename, *new_filename; - ret = create_disk_storage_object_dir(t, i, new_ds_name); - if (ret < 0) - goto out; - old_filename = disk_storage_path(t, i, old_ds_name); - new_filename = disk_storage_path(t, i, new_ds_name); - ret = para_rename(old_filename, new_filename); - free(old_filename); - free(new_filename); - if (ret < 0) - goto out; - } - ret = 1; -out: - free(old_ds_name); - free(new_ds_name); - return ret; - -} - -/** - * Change an object in an osl table. - * - * \param t Pointer to an open osl table. - * \param r Pointer to the row containing the object to be updated. - * \param col_num Number of the column containing the object to be updated. - * \param obj Pointer to the replacement object. - * - * This function gets rid of all references to the old object. This includes - * removal of the rbtree node in case there is an rbtree associated with \a - * col_num. It then inserts \a obj into the table and the rbtree if necessary. - * - * If the \p OSL_RBTREE flag is set for \a col_num, you \b MUST call this - * function in order to change the contents of an object, even for volatile or - * mapped columns of constant size (which may be updated directly if \p - * OSL_RBTREE is not set). Otherwise the rbtree might become corrupted. - * - * \return Standard - */ -int osl_update_object(struct osl_table *t, const struct osl_row *r, - unsigned col_num, struct osl_object *obj) -{ - struct osl_column *col; - const struct osl_column_description *cd; - int ret; - - if (!t) - return -E_BAD_TABLE; - col = &t->columns[col_num]; - cd = get_column_description(t->desc, col_num); - PARA_DEBUG_LOG("updating column %u of %s\n", col_num, t->desc->name); - if (cd->storage_flags & OSL_RBTREE) { - if (search_rbtree(obj, t, col_num, NULL, NULL) > 0) - return -E_RB_KEY_EXISTS; - } - if (cd->storage_flags & OSL_FIXED_SIZE) { - if (obj->size != cd->data_size) - return -E_BAD_DATA_SIZE; - } - remove_rb_node(t, col_num, r); - if (cd->storage_type == OSL_NO_STORAGE) { /* TODO: If fixed size, reuse object? */ - free(r->volatile_objects[col->volatile_num].data); - r->volatile_objects[col->volatile_num] = *obj; - } else if (cd->storage_type == OSL_DISK_STORAGE) { - char *ds_name; - ret = disk_storage_name_of_row(t, r, &ds_name); - if (ret < 0) - return ret; - ret = delete_disk_storage_file(t, col_num, ds_name); - if (ret < 0 && !is_errno(-ret, ENOENT)) { - free(ds_name); - return ret; - } - ret = write_disk_storage_file(t, col_num, obj, ds_name); - free(ds_name); - if (ret < 0) - return ret; - } else { /* mapped storage */ - struct osl_object old_obj; - ret = get_mapped_object(t, col_num, r->num, &old_obj); - if (ret < 0) - return ret; - /* - * If the updated column is the disk storage name column, the - * disk storage name changes, so we have to rename all disk - * storage objects accordingly. - */ - if (col_num == t->disk_storage_name_column) { - ret = rename_disk_storage_objects(t, &old_obj, obj); - if (ret < 0) - return ret; - } - if (cd->storage_flags & OSL_FIXED_SIZE) - memcpy(old_obj.data, obj->data, cd->data_size); - else { /* TODO: if the size doesn't change, use old space */ - uint32_t new_data_map_size; - char *row_index; - ret = get_row_index(t, r->num, &row_index); - if (ret < 0) - return ret; - ret = mark_mapped_object_invalid(t, r->num, col_num); - if (ret < 0) - return ret; - unmap_column(t, col_num); - ret = append_map_file(t, col_num, obj, - &new_data_map_size); - if (ret < 0) - return ret; - ret = map_column(t, col_num); - if (ret < 0) - return ret; - update_cell_index(row_index, col, new_data_map_size, - obj->size); - } - } - if (cd->storage_flags & OSL_RBTREE) { - ret = insert_rbtree(t, col_num, r, obj); - if (ret < 0) - return ret; - } - return 1; -} - -/** - * Retrieve an object of type \p OSL_DISK_STORAGE by row and column. - * - * \param t Pointer to an open osl table. - * \param r Pointer to the row containing the object. - * \param col_num The column number. - * \param obj Points to the result upon successful return. - * - * For columns of type \p OSL_DISK_STORAGE, this function must be used to - * retrieve one of its containing objects. Afterwards, osl_close_disk_object() - * must be called in order to deallocate the resources. - * - * \return Positive on success, negative on errors. Possible errors include: - * \p E_BAD_TABLE, \p E_BAD_STORAGE_TYPE, errors returned by osl_get_object(). - * - * \sa osl_get_object(), osl_storage_type, osl_close_disk_object(). - */ -int osl_open_disk_object(const struct osl_table *t, const struct osl_row *r, - unsigned col_num, struct osl_object *obj) -{ - const struct osl_column_description *cd; - char *ds_name, *filename; - int ret; - - if (!t) - return -E_BAD_TABLE; - cd = get_column_description(t->desc, col_num); - if (cd->storage_type != OSL_DISK_STORAGE) - return -E_BAD_STORAGE_TYPE; - - ret = disk_storage_name_of_row(t, r, &ds_name); - if (ret < 0) - return ret; - filename = disk_storage_path(t, col_num, ds_name); - free(ds_name); - PARA_DEBUG_LOG("filename: %s\n", filename); - ret = mmap_full_file(filename, O_RDONLY, &obj->data, &obj->size, NULL); - free(filename); - return ret; -} - -/** - * Free resources that were allocated during osl_open_disk_object(). - * - * \param obj Pointer to the object previously returned by open_disk_object(). - * - * \return The return value of the underlying call to para_munmap(). - * - * \sa para_munmap(). - */ -int osl_close_disk_object(struct osl_object *obj) -{ - return para_munmap(obj->data, obj->size); -} - -/** - * Get the number of rows of the given table. - * - * \param t Pointer to an open osl table. - * \param num_rows Result is returned here. - * - * The number of rows returned via \a num_rows excluding any invalid rows. - * - * \return Positive on success, \p -E_BAD_TABLE if \a t is \p NULL. - */ -int osl_get_num_rows(const struct osl_table *t, unsigned *num_rows) -{ - if (!t) - return -E_BAD_TABLE; - assert(t->num_rows >= t->num_invalid_rows); - *num_rows = t->num_rows - t->num_invalid_rows; - return 1; -} - -/** - * Get the rank of a row. - * - * \param t An open osl table. - * \param r The row to get the rank of. - * \param col_num The number of an rbtree column. - * \param rank Result pointer. - * - * The rank is, by definition, the position of the row in the linear order - * determined by an in-order tree walk of the rbtree associated with column - * number \a col_num of \a table. - * - * \return Positive on success, negative on errors. - * - * \sa osl_get_nth_row(). - */ -int osl_get_rank(const struct osl_table *t, struct osl_row *r, - unsigned col_num, unsigned *rank) -{ - struct osl_object obj; - struct osl_column *col; - struct rb_node *node; - int ret = check_rbtree_col(t, col_num, &col); - - if (ret < 0) - return ret; - ret = osl_get_object(t, r, col_num, &obj); - if (ret < 0) - return ret; - ret = search_rbtree(&obj, t, col_num, &node, NULL); - if (ret < 0) - return ret; - ret = rb_rank(node, rank); - if (ret < 0) - return -E_BAD_ROW; - return 1; -} - -/** - * Get the row with n-th greatest value. - * - * \param t Pointer to an open osl table. - * \param col_num The column number. - * \param n The rank of the desired row. - * \param result Row is returned here. - * - * Retrieve the n-th order statistic with respect to the compare function - * of the rbtree column \a col_num. In other words, get that row with - * \a n th greatest value in column \a col_num. It's an error if - * \a col_num is not a rbtree column, or if \a n is larger than the - * number of rows in the table. - * - * \return Positive on success, negative on errors. Possible errors: - * \p E_BAD_TABLE, \p E_BAD_STORAGE_FLAGS, \p E_RB_KEY_NOT_FOUND. - * - * \sa osl_storage_flags, osl_compare_func, osl_get_row(), - * osl_rbtree_last_row(), osl_rbtree_first_row(), osl_get_rank(). - */ -int osl_get_nth_row(const struct osl_table *t, unsigned col_num, - unsigned n, struct osl_row **result) -{ - struct osl_column *col; - struct rb_node *node; - unsigned num_rows; - int ret; - - if (n == 0) - return -E_RB_KEY_NOT_FOUND; - ret = osl_get_num_rows(t, &num_rows); - if (ret < 0) - return ret; - if (n > num_rows) - return -E_RB_KEY_NOT_FOUND; - ret = check_rbtree_col(t, col_num, &col); - if (ret < 0) - return ret; - node = rb_nth(col->rbtree.rb_node, n); - if (!node) - return -E_RB_KEY_NOT_FOUND; - *result = get_row_pointer(node, col->rbtree_num); - return 1; -} - -/** - * Get the row corresponding to the smallest rbtree node of a column. - * - * \param t An open rbtree table. - * \param col_num The number of the rbtree column. - * \param result A pointer to the first row is returned here. - * - * The rbtree node of the smallest object (with respect to the corresponding - * compare function) is selected and the row containing this object is - * returned. It is an error if \a col_num refers to a column without an - * associated rbtree. - * - * \return Positive on success, negative on errors. - * - * \sa osl_get_nth_row(), osl_rbtree_last_row(). - */ -int osl_rbtree_first_row(const struct osl_table *t, unsigned col_num, - struct osl_row **result) -{ - return osl_get_nth_row(t, col_num, 1, result); -} - -/** - * Get the row corresponding to the greatest rbtree node of a column. - * - * \param t The same meaning as in \p osl_rbtree_first_row(). - * \param col_num The same meaning as in \p osl_rbtree_first_row(). - * \param result The same meaning as in \p osl_rbtree_first_row(). - * - * This function works just like osl_rbtree_first_row(), the only difference - * is that the row containing the greatest rather than the smallest object is - * returned. - * - * \return Positive on success, negative on errors. - * - * \sa osl_get_nth_row(), osl_rbtree_first_row(). - */ -int osl_rbtree_last_row(const struct osl_table *t, unsigned col_num, - struct osl_row **result) -{ - unsigned num_rows; - int ret = osl_get_num_rows(t, &num_rows); - - if (ret < 0) - return ret; - return osl_get_nth_row(t, col_num, num_rows, result); -} diff --git a/osl.h b/osl.h deleted file mode 100644 index 3c35fd4e..00000000 --- a/osl.h +++ /dev/null @@ -1,187 +0,0 @@ -#include -/* - * Copyright (C) 2007-2009 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ - -/** \file osl.h User interface for the object storage layer. */ - -/** describes an object of the object storage layer (osl) */ -struct osl_object { - /** Pointer to the data of the object. */ - void *data; - /** The object's size. */ - size_t size; -}; - -/** Flags that change the internal handling of osl tables. */ -enum osl_table_flags { - /** This table will have many rows. */ - OSL_LARGE_TABLE = 1 -}; - -/** The three different types of storage for an osl column */ -enum osl_storage_type { - /** - * All data for this column is stored in one file which gets mmapped by - * osl_open_table(). This is suitable for columns that do not hold much - * data. - */ - OSL_MAPPED_STORAGE, - /** - * Each entry is stored on disk and is loaded on demand by - * open_disk_object(). This is the preferable storage type for large - * objects that need not be in memory all the time. - */ - OSL_DISK_STORAGE, - /** - * Objects for columns of this type are volatile: They are only stored - * in memory and are discarded once the table gets closed. - */ - OSL_NO_STORAGE -}; - -/** - * Additional per-column flags - */ -enum osl_storage_flags { - /** - * Build an rbtree for this column. This is only possible if the - * storage type of the column is either \a OSL_MAPPED_STORAGE or \a - * OSL_NO_STORAGE. In order to lookup objects in the table by using \a - * osl_get_row(), the lookup column must have an associated rbtree. - * - * \sa osl_storage_type, osl_get_row() - */ - OSL_RBTREE = 1, - /** The data for this column will have constant size. */ - OSL_FIXED_SIZE = 2, - /** All values of this column will be different. */ - OSL_UNIQUE = 4, - /** Do not free the data for this column (\p OSL_NO_STORAGE). */ - OSL_DONT_FREE = 8 -}; - -struct osl_table; -struct osl_row; - -/** - * In order to build up an rbtree a compare function for the objects must be - * specified. Such a function always takes pointers to the two objects to be - * compared. It must return -1, zero, or 1, if the first argument is considered - * to be respectively less than, equal to, or greater than the second. If two - * members compare as equal, their order in the sorted array is undefined. - */ -typedef int osl_compare_func(const struct osl_object *obj1, - const struct osl_object *obj2); -typedef int osl_rbtree_loop_func(struct osl_row *row, void *data); - -osl_compare_func osl_hash_compare, uint32_compare; - -/** - * Describes one column of a osl table. - */ -struct osl_column_description { - /** One of zje tree possible types of storage */ - enum osl_storage_type storage_type; - /** Specifies further properties of the column */ - enum osl_storage_flags storage_flags; - /** - * The column name determines the name of the directory where all data - * for this column will be stored. Its hash is stored in the table - * header. This field is ignored if the storage type is \a NO_STORAGE - */ - char *name; - /** - * For columns with an associated rbtree, this must point to a function - * that compares the values of two objects, either a built-in function - * or a function defined by the application may be supplied. This - * field is ignored if the column does not have an associated rbtree. - * - * \sa osl_storage_flags, osl_compare_func - */ - osl_compare_func *compare_function; - /** - * If the \a OSL_FIXED_SIZE flag is set for this column, this value - * determines the fixed size of all objects of this column. It is - * ignored, if \a OSL_FIXED_SIZE is not set. - */ - uint32_t data_size; -}; - -/** - * Describes one osl table. - */ -struct osl_table_description { - /** The directory which contains all files of this table. */ - const char *dir; - /** - * The table name. A subdirectory of \a dir called \a name is created - * at table creation time. It must be a valid name for a subdirectory. - * In particular, no slashes are allowed for \a name. - */ - const char *name; - /** The number of columns of this table. */ - uint16_t num_columns; - /** Further table-wide information. */ - enum osl_table_flags flags; - /** The array describing the individual columns of the table. */ - struct osl_column_description *column_descriptions; -}; - -/** Flags to be passed to \a osl_close_table(). */ -enum osl_close_flags { - /** - * The table header contains a "dirty" flag which specifies whether - * the table is currently open by another process. This flag specifies - * that the dirty flag should be cleared before closing the table. - */ - OSL_MARK_CLEAN = 1, - /** - * If the table contains columns of type \a OSL_NO_STORAGE and this - * flag is passed to osl_close_table(), free(3) is called for each - * object of each column of type \a OSL_NO_STORAGE. - */ - OSL_FREE_VOLATILE = 2 -}; - - - -int osl_create_table(const struct osl_table_description *desc); -int osl_open_table(const struct osl_table_description *desc, - struct osl_table **result); -int osl_close_table(struct osl_table *t, enum osl_close_flags flags); -int osl_get_row(const struct osl_table *t, unsigned col_num, - const struct osl_object *obj, struct osl_row **result); -int osl_get_object(const struct osl_table *t, const struct osl_row *row, - unsigned col_num, struct osl_object *object); -int osl_open_disk_object(const struct osl_table *t, - const struct osl_row *r, unsigned col_num, struct osl_object *obj); -int osl_close_disk_object(struct osl_object *obj); -int osl_add_and_get_row(struct osl_table *t, struct osl_object *objects, - struct osl_row **row); -int osl_add_row(struct osl_table *t, struct osl_object *objects); -int osl_del_row(struct osl_table *t, struct osl_row *row); -int osl_rbtree_loop(const struct osl_table *t, unsigned col_num, - void *private_data, osl_rbtree_loop_func *func); -int osl_rbtree_loop_reverse(const struct osl_table *t, unsigned col_num, - void *private_data, osl_rbtree_loop_func *func); -int osl_update_object(struct osl_table *t, const struct osl_row *r, - unsigned col_num, struct osl_object *obj); -int osl_get_num_rows(const struct osl_table *t, unsigned *num_rows); -int osl_rbtree_first_row(const struct osl_table *t, unsigned col_num, - struct osl_row **result); -int osl_rbtree_last_row(const struct osl_table *t, unsigned col_num, - struct osl_row **result); -int osl_get_nth_row(const struct osl_table *t, unsigned col_num, - unsigned n, struct osl_row **result); -int osl_get_rank(const struct osl_table *t, struct osl_row *r, - unsigned col_num, unsigned *rank); - -int for_each_file_in_dir(const char *dirname, - int (*func)(const char *, void *), void *private_data); -ssize_t para_write_all(int fd, const void *buf, size_t size); -int para_lseek(int fd, off_t *offset, int whence); -int para_write_file(const char *filename, const void *buf, size_t size); - diff --git a/osl_core.h b/osl_core.h deleted file mode 100644 index de53cdd1..00000000 --- a/osl_core.h +++ /dev/null @@ -1,591 +0,0 @@ -/* - * Copyright (C) 2007-2009 Andre Noll - * - * Licensed under the GPL v2. For licencing details see COPYING. - */ - -/** \file osl_core.h Object storage layer details, not visible to users. */ - -#include "rbtree.h" -#include "osl.h" -#include "string.h" -#include "portable_io.h" -#include "hash.h" - -/** Internal representation of a column of an osl table. */ -struct osl_column { - /** The memory mapping of this comumn (only used for mapped columns). */ - struct osl_object data_map; - /** The root of the rbtree (only used for columns with rbtrees). */ - struct rb_root rbtree; - /** The index in the array of rb nodes (only used for columns with rbtrees). */ - unsigned rbtree_num; - /** Index for volatile_objects of struct osl_row. */ - unsigned volatile_num; - /** - * Starting point of the data for this column within the index - * (only used for mapped columns). - */ - uint16_t index_offset; - /** - * The hash value of the name of this column. - * - * This is only used for mapped and disk storage columns). - */ - HASH_TYPE name_hash[HASH_SIZE]; -}; - -/** Internal representation of an osl table */ -struct osl_table { - /** Pointer to the table description */ - const struct osl_table_description *desc; - /** The size of the index header of this table. */ - uint16_t index_header_size; - /** Contains the mapping of the table's index file */ - struct osl_object index_map; - /** Total number of rows, including invalid rows. */ - unsigned num_rows; - /** Keeps track of the number of invalid rows in the table. */ - uint32_t num_invalid_rows; - /** Number of columns of type \p OSL_MAPPED_STORAGE. */ - unsigned num_mapped_columns; - /** Number of columns of type \p OSL_NO_STORAGE. */ - unsigned num_volatile_columns; - /** Number of columns of type \p OSL_DISK_STORAGE. */ - unsigned num_disk_storage_columns; - /** Number of columns with associated rbtree. */ - unsigned num_rbtrees; - /** - * The number of the column that determines the name of the disk - * storage objects. - */ - unsigned disk_storage_name_column; - /** The number of bytes of an index entry of a row. */ - unsigned row_index_size; - /** Pointer to the internal representation of the columns. */ - struct osl_column *columns; -}; - -/** Internal representation of a row of an osl table */ -struct osl_row { - /** - * The row number. - * - * It is only used if there is at least one mapped column. - */ - off_t num; - /** Array of size \a num_volatile_columns. */ - struct osl_object *volatile_objects; -}; - -int read_table_desc(struct osl_object *map, struct osl_table_description *desc); -int init_table_structure(const struct osl_table_description *desc, - struct osl_table **table_ptr); -int row_is_invalid(struct osl_table *t, uint32_t row_num); -int get_mapped_object(const struct osl_table *t, unsigned col_num, - uint32_t row_num, struct osl_object *obj); -int para_truncate(const char *filename, off_t size); -int unmap_table(struct osl_table *t, enum osl_close_flags flags); -int init_rbtrees(struct osl_table *t); - -/** - * Flags to be specified for map_table(). - * - * \sa map_table(). - */ -enum map_table_flags { - /** - * Check whether the entries in the table index match the entries in - * the table description. - */ - MAP_TBL_FL_VERIFY_INDEX = 1, - /** Do not complain even if the dirty flag is set. */ - MAP_TBL_FL_IGNORE_DIRTY = 2, - /** Use read-only mappings. */ - MAP_TBL_FL_MAP_RDONLY = 4 -}; - -int map_table(struct osl_table *t, enum map_table_flags flags); -void clear_rbtrees(struct osl_table *t); -int mark_row_invalid(struct osl_table *t, uint32_t row_num); - - -/** - * Get the description of a column by column number - * - * \param d Pointer to the table description. - * \param col_num The number of the column to get the description for. - * - * \return The table description. - * - * \sa struct osl_column_description. - */ -_static_inline_ struct osl_column_description *get_column_description( - const struct osl_table_description *d, unsigned col_num) -{ - return &d->column_descriptions[col_num]; -} - -/** - * Format of the header of the index file of an osl table. - * - * Bytes 16-31 are currently unused. - * - * \sa enum index_column_desc_offsets, HASH_SIZE, osl_table_flags. - */ -enum index_header_offsets { - /** Bytes 0-8: PARASLASH. */ - IDX_PARA_MAGIC = 0, - /** Byte 9: Dirty flag (nonzero if table is mapped). */ - IDX_DIRTY_FLAG = 9, - /** Byte 10: osl table version number. */ - IDX_VERSION = 10, - /** Byte 11: Table flags.*/ - IDX_TABLE_FLAGS = 11, - /** Byte 12,13: Number of columns. */ - IDX_NUM_COLUMNS, - /** Byte 14,15 Size of the index header. */ - IDX_HEADER_SIZE = 14, - /** Column descriptions start here. */ - IDX_COLUMN_DESCRIPTIONS = 32 -}; - -/** - * Format of the column description in the index header. - * - * \sa index_header_offsets. - */ -enum index_column_desc_offsets { - /** Bytes 0,1: Storage_type. */ - IDX_CD_STORAGE_TYPE = 0, - /** Bytes 2,3: Storage_flags. */ - IDX_CD_STORAGE_FLAGS = 2, - /** Bytes 4 - 7: Data_size (only used for fixed size columns). */ - IDX_CD_DATA_SIZE = 4, - /** Bytes 8 - ... Name of the column. */ - IDX_CD_NAME = 8, -}; - -/** Magic string contained in the header of the index file of each osl table. */ -#define PARA_MAGIC "PARASLASH" - -/** - * The minimal number of bytes for a column in the index header. - * - * The column name starts at byte IDX_CD_NAME and must at least contain one - * character, plus the terminating NULL byte. - */ -#define MIN_IDX_COLUMN_DESCRIPTION_SIZE (IDX_CD_NAME + 2) - -/** - * Get the number of bytes used for a column in the index header. - * - * \param name The name of the column. - * - * The amount of space used for a column in the index header of a table depends - * on the (length of the) name of the column. - * - * \return The total number of bytes needed to store one column of this name. - */ -_static_inline_ size_t index_column_description_size(const char *name) -{ - return MIN_IDX_COLUMN_DESCRIPTION_SIZE + strlen(name) - 1; -} - -#define CURRENT_TABLE_VERSION 1 -#define MIN_TABLE_VERSION 1 -#define MAX_TABLE_VERSION 1 -/** An index header must be at least that many bytes long. */ -#define MIN_INDEX_HEADER_SIZE(num_cols) (MIN_IDX_COLUMN_DESCRIPTION_SIZE \ - * num_cols + IDX_COLUMN_DESCRIPTIONS) - -/** - * Get the number of rows from the size of the memory mapping. - * - * \param t Pointer to an open table. - * - * \return The number of rows, including invalid rows. - */ -_static_inline_ unsigned table_num_rows(const struct osl_table *t) -{ - return (t->index_map.size - t->index_header_size) - / t->row_index_size; -} - -/** - * Get the path of the index file from a table description. - * - * \param d The table description. - * - * \return The full path of the index file. Result must be freed by - * the caller. - */ -_static_inline_ char *index_filename(const struct osl_table_description *d) -{ - return make_message("%s/%s/index", d->dir, d->name); -} - -/** - * Get the path of storage of a column. - * - * \param t Pointer to an initialized table. - * \param col_num The number of the column to get the path for. - * - * \return The full path of the mapped data file (mapped storage) or the - * data directory (disk storage). Result must be freed by the caller. - * - * \sa osl_storage_type. - */ -_static_inline_ char *column_filename(const struct osl_table *t, unsigned col_num) -{ - char asc[2 * HASH_SIZE + 1]; - hash_to_asc(t->columns[col_num].name_hash, asc); - return make_message("%s/%s/%s", t->desc->dir, t->desc->name, asc); -} - -/** - * Get the start of an index entry - * - * \param t Pointer to a table which has been mapped. - * \param row_num The number of the row whose index entry should be retrieved. - * \param row_index Result pointer. - * - * \return Positive on success, \p -E_INDEX_CORRUPTION otherwise. - * - * \sa get_cell_index(). - */ -_static_inline_ int get_row_index(const struct osl_table *t, uint32_t row_num, - char **row_index) -{ - uint32_t index_offset; - index_offset = t->index_header_size + t->row_index_size * row_num; - if (index_offset + 8 > t->index_map.size) { - *row_index = NULL; - return -E_INDEX_CORRUPTION; - } - *row_index = (char *)(t->index_map.data) + index_offset; - return 1; -} - -/** - * Get the index entry of a row/column pair. - * - * \param t Pointer to a table which has been mapped. - * \param row_num The number of the row whose index entry should be retrieved. - * \param col_num The number of the column whose index entry should be retrieved. - * \param cell_index Result pointer. - * - * \return Positive on success, \p -E_INDEX_CORRUPTION otherwise. - * - * \sa get_row_index(). - */ -_static_inline_ int get_cell_index(const struct osl_table *t, uint32_t row_num, - uint32_t col_num, char **cell_index) -{ - int ret = get_row_index(t, row_num, cell_index); - if (ret < 0) - return ret; - *cell_index += t->columns[col_num].index_offset; - return ret; -} - -/** - * Change an index entry of a column after object was added. - * - * \param row_index Pointer to the index of the row to update. - * \param col Pointer to the column. - * \param map_size The new size of the data file. - * \param object_size The size of the object just appended to the data file. - * - * This is called right after an object was appended to the data file for a - * mapped column. - * - * \sa get_row_index(). - */ -_static_inline_ void update_cell_index(char *row_index, struct osl_column *col, - uint32_t map_size, uint32_t object_size) -{ - write_u32(row_index + col->index_offset, map_size - object_size - 1); - write_u32(row_index + col->index_offset + 4, object_size + 1); -} - -/** - * Get the full path of a disk storage object - * - * \param t Pointer to an initialized table. - * \param col_num The number of the column the disk storage object belongs to. - * \param ds_name The disk storage name of the object. - * - * \return Pointer to the full path which must be freed by the caller. - * - * \sa column_filename(), disk_storage_name_of_row(). - */ -_static_inline_ char *disk_storage_path(const struct osl_table *t, - unsigned col_num, const char *ds_name) -{ - char *dirname = column_filename(t, col_num); - char *filename = make_message("%s/%s", dirname, ds_name); - free(dirname); - return filename; -} - -/** - * Get the column description of the next column of a given type. - * - * \param type the desired storage type. - * \param col_num the column to start the search. - * \param t the osl table. - * \param cd result is returned here. - * - * \return On success, \a cd contains the column description of the next column - * of type \a type, and the number of that column is returned. Otherwise, \a - * cd is set to \p NULL and the function returns t->num_columns + 1. - * - * \sa FOR_EACH_COLUMN_OF_TYPE, FOR_EACH_MAPPED_COLUMN, FOR_EACH_RBTREE_COLUMN, - * FOR_EACH_DISK_STORAGE_COLUMN, FOR_EACH_VOLATILE_COLUMN, osl_storage_type. - */ -_static_inline_ int next_column_of_type(enum osl_storage_type type, int col_num, - const struct osl_table *t, - const struct osl_column_description **cd) -{ - *cd = NULL; - while (col_num < t->desc->num_columns) { - *cd = get_column_description(t->desc, col_num); - if ((*cd)->storage_type == type) - break; - col_num++; - } - return col_num; -} - -/** - * Find the next column with an associated rbtree. - * - * \param col_num The column to start the search. - * \param t The osl table. - * \param cd Result is returned here. - - * \return On success, \a cd contains the column description of the next column - * with associated rbtree, and the number of that column is returned. - * Otherwise, \a cd is set to \p NULL and the function returns t->num_columns + - * 1. - * - * \sa FOR_EACH_COLUMN_OF_TYPE, FOR_EACH_MAPPED_COLUMN, FOR_EACH_RBTREE_COLUMN, - * FOR_EACH_DISK_STORAGE_COLUMN, FOR_EACH_VOLATILE_COLUMN, osl_storage_flags. - */ -_static_inline_ int next_rbtree_column(int col_num, const struct osl_table *t, - const struct osl_column_description **cd) -{ - *cd = NULL; - while (col_num < t->desc->num_columns) { - *cd = get_column_description(t->desc, col_num); - if ((*cd)->storage_flags & OSL_RBTREE) - break; - col_num++; - } - return col_num; -} - - -/* quite some dirty hacks */ - -/** Round up size of struct osl_row to multiple of 8 */ -#define RB_NODES_OFFSET ((sizeof(struct osl_row) + 7) / 8 * 8) - -/** - * Allocate a new osl row. - * - * \param num_rbtrees The number of rbtrees for this row. - * - * \return A pointer to a zeroed-out area suitable for holding an osl row - * with \a num_rbtrees rbtree columns. - */ -_static_inline_ struct osl_row *allocate_row(unsigned num_rbtrees) -{ - size_t s = RB_NODES_OFFSET + num_rbtrees * sizeof(struct rb_node); - return para_calloc(s); -} - -/** - * Compute the pointer to a rbtree node embedded in a osl row. - * - * \param row The row to extract the rbtree pointer from. - * \param rbtree_num The number of the rbtree node to extract. - * - * \return A pointer to the \a rbtree_num th node contained in \a row. - */ -_static_inline_ struct rb_node *get_rb_node_pointer(const struct osl_row *row, uint32_t rbtree_num) -{ - return ((struct rb_node *)(((char *)row) + RB_NODES_OFFSET)) + rbtree_num; -} - -/** - * Get a pointer to the struct containing the given rbtree node. - * - * \param node Pointer to the rbtree node. - * \param rbtree_num Number of \a node in the array of rbtree nodes. - * - * \return A pointer to the row containing \a node. - */ -_static_inline_ struct osl_row *get_row_pointer(const struct rb_node *node, - unsigned rbtree_num) -{ - node -= rbtree_num; - return (struct osl_row *)(((char *)node) - RB_NODES_OFFSET); -} - -/** - * Compute a cryptographic hash of an osl object. - * - * \param obj the Object to compute the hash value from. - * \param hash Result is returned here. - */ -_static_inline_ void hash_object(const struct osl_object *obj, HASH_TYPE *hash) -{ - hash_function(obj->data, obj->size, hash); -} - -/** - * Get the relative path of an object, given the hash value. - * - * \param t Pointer to an initialized osl table. - * \param hash An arbitrary hash value. - * - * This function is typically called with \a hash being the hash of the object - * stored in the disk storage name column of a row. If the OSL_LARGE_TABLE - * flag is set, the first two hex digits get separated with a slash from the - * remaining digits. - * - * \return The relative path for all disk storage objects corresponding to \a - * hash. - * - * \sa struct osl_table:disk_storage_name_column. - */ -_static_inline_ char *disk_storage_name_of_hash(const struct osl_table *t, HASH_TYPE *hash) -{ - char asc[2 * HASH_SIZE + 2]; - - hash_to_asc(hash, asc); - if (t->desc->flags & OSL_LARGE_TABLE) - return make_message("%.2s/%s", asc, asc + 2); - return para_strdup(asc); -} - -/** - * A wrapper for rename(2). - * - * \param old_path The source path. - * \param new_path The destination path. - * - * \return Standard. - * - * \sa rename(2). - */ -_static_inline_ int para_rename(const char *old_path, const char *new_path) -{ - if (rename(old_path, new_path) < 0) - return -ERRNO_TO_PARA_ERROR(errno); - return 1; -} - -/** - * Iterate over each column of an initialized table. - * - * \param col A pointer to a struct osl_column. - * \param desc Pointer to the table description. - * \param cd Pointer to struct osl_column_description. - * - * On each iteration, \a col will point to the next column of the table and \a - * cd will point to the column description of this column. - * - * \sa struct osl_column FOR_EACH_RBTREE_COLUMN, FOR_EACH_COLUMN_OF_TYPE, - * FOR_EACH_MAPPED_COLUMN, FOR_EACH_DISK_STORAGE_COLUMN, - * FOR_EACH_VOLATILE_COLUMN. - */ -#define FOR_EACH_COLUMN(col, desc, cd) \ - for (col = 0; col < (desc)->num_columns && \ - (cd = get_column_description(desc, col)); col++) - -/** - * Iterate over each column with associated rbtree. - * - * \param col Same meaning as in FOR_EACH_COLUMN(). - * \param table Same meaning as in FOR_EACH_COLUMN(). - * \param cd Same meaning as in FOR_EACH_COLUMN(). - * - * \sa osl_storage_flags::OSL_RBTREE, FOR_EACH_COLUMN, FOR_EACH_COLUMN_OF_TYPE, - * FOR_EACH_MAPPED_COLUMN, FOR_EACH_DISK_STORAGE_COLUMN, - * FOR_EACH_VOLATILE_COLUMN. - */ -#define FOR_EACH_RBTREE_COLUMN(col, table, cd) \ - for (col = next_rbtree_column(0, table, &cd); \ - col < table->desc->num_columns; \ - col = next_rbtree_column(++col, table, &cd)) - -/** - * Iterate over each column of given storage type. - * - * \param type The osl_storage_type to iterate over. - * \param col Same meaning as in FOR_EACH_COLUMN(). - * \param table Same meaning as in FOR_EACH_COLUMN(). - * \param cd Same meaning as in FOR_EACH_COLUMN(). - * - * \sa osl_storage_type, FOR_EACH_COLUMN, FOR_EACH_RBTREE_COLUMN, - * FOR_EACH_MAPPED_COLUMN, FOR_EACH_DISK_STORAGE_COLUMN, - * FOR_EACH_VOLATILE_COLUMN. - */ -#define FOR_EACH_COLUMN_OF_TYPE(type, col, table, cd) \ - for (col = next_column_of_type(type, 0, table, &cd); \ - col < table->desc->num_columns; \ - col = next_column_of_type(type, ++col, table, &cd)) - -/** - * Iterate over each mapped column. - * - * \param col Same meaning as in FOR_EACH_COLUMN(). - * \param table Same meaning as in FOR_EACH_COLUMN(). - * \param cd Same meaning as in FOR_EACH_COLUMN(). - * - * Just like FOR_EACH_COLUMN(), but skip columns which are - * not of type \p OSL_MAPPED_STORAGE. - * - * \sa osl_storage_flags::OSL_MAPPED_STORAGE, FOR_EACH_COLUMN, - * FOR_EACH_RBTREE_COLUMN, FOR_EACH_COLUMN_OF_TYPE, - * FOR_EACH_DISK_STORAGE_COLUMN, FOR_EACH_VOLATILE_COLUMN. - */ -#define FOR_EACH_MAPPED_COLUMN(col, table, cd) \ - FOR_EACH_COLUMN_OF_TYPE(OSL_MAPPED_STORAGE, col, table, cd) - -/** - * Iterate over each disk storage column. - * - * \param col Same meaning as in FOR_EACH_COLUMN(). - * \param table Same meaning as in FOR_EACH_COLUMN(). - * \param cd Same meaning as in FOR_EACH_COLUMN(). - * - * Just like FOR_EACH_COLUMN(), but skip columns which are - * not of type \p OSL_DISK_STORAGE. - * - * \sa osl_storage_flags::OSL_DISK_STORAGE, FOR_EACH_COLUMN, - * FOR_EACH_RBTREE_COLUMN, FOR_EACH_COLUMN_OF_TYPE, FOR_EACH_MAPPED_COLUMN, - * FOR_EACH_VOLATILE_COLUMN. - */ -#define FOR_EACH_DISK_STORAGE_COLUMN(col, table, cd) \ - FOR_EACH_COLUMN_OF_TYPE(OSL_DISK_STORAGE, col, table, cd) - -/** - * Iterate over each volatile column. - * - * \param col Same meaning as in FOR_EACH_COLUMN(). - * \param table Same meaning as in FOR_EACH_COLUMN(). - * \param cd Same meaning as in FOR_EACH_COLUMN(). - * - * Just like FOR_EACH_COLUMN(), but skip columns which are - * not of type \p OSL_NO_STORAGE. - * - * \sa osl_storage_flags::OSL_NO_STORAGE, FOR_EACH_COLUMN, - * FOR_EACH_RBTREE_COLUMN, FOR_EACH_COLUMN_OF_TYPE, FOR_EACH_MAPPED_COLUMN, - * FOR_EACH_DISK_STORAGE_COLUMN. - */ -#define FOR_EACH_VOLATILE_COLUMN(col, table, cd) \ - FOR_EACH_COLUMN_OF_TYPE(OSL_NO_STORAGE, col, table, cd) diff --git a/oss_write.c b/oss_write.c index 48545630..df9608a8 100644 --- a/oss_write.c +++ b/oss_write.c @@ -1,15 +1,17 @@ /* - * Copyright (C) 2009 Andre Noll + * Copyright (C) 2009-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file oss_write.c Paraslash's oss output plugin. */ +#include #include #include #include #include +#include #include "para.h" #include "fd.h" @@ -17,73 +19,53 @@ #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "write.h" +#include "write_common.h" #include "oss_write.cmdline.h" #include "error.h" -/** Always use 16 bit little endian. */ -#define FORMAT AFMT_S16_LE - /** Data specific to the oss writer. */ struct private_oss_write_data { /** The file handle of the device. */ int fd; - /** - * The samplerate given by command line option or the decoder - * of the writer node group. - */ - int samplerate; - /** - * The number of channels, given by command line option or the - * decoder of the writer node group. - */ - int channels; /** Four bytes for stereo streams, two bytes for mono streams. */ int bytes_per_frame; }; -static int oss_pre_select(struct sched *s, struct writer_node *wn) +static int get_oss_format(enum sample_format sf) { - struct private_oss_write_data *powd = wn->private_data; - struct writer_node_group *wng = wn->wng; - - if (*wng->loaded - wn->written < powd->bytes_per_frame) - return 0; - para_fd_set(powd->fd, &s->wfds, &s->max_fileno); - return 1; + switch (sf) { + case SF_S8: return AFMT_S8; + case SF_U8: return AFMT_U8; + case SF_S16_LE: return AFMT_S16_LE; + case SF_S16_BE: return AFMT_S16_BE; + case SF_U16_LE: return AFMT_U16_LE; + case SF_U16_BE: return AFMT_U16_BE; + default: return AFMT_S16_LE; + } } -static int oss_post_select(struct sched *s, struct writer_node *wn) +static void oss_pre_select(struct sched *s, struct task *t) { - int ret; + struct writer_node *wn = container_of(t, struct writer_node, task); struct private_oss_write_data *powd = wn->private_data; - struct writer_node_group *wng = wn->wng; - size_t frames, bytes = *wng->loaded - wn->written; - char *data = *wng->bufp + wn->written; + int ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF); - if (*wng->input_error < 0 && bytes < powd->bytes_per_frame) { - wn->written = *wng->loaded; - return *wng->input_error; - } - frames = bytes / powd->bytes_per_frame; - if (!frames) /* less than a single frame available */ - goto out; - if (!FD_ISSET(powd->fd, &s->wfds)) - goto out; - ret = write_nonblock(powd->fd, data, frames * powd->bytes_per_frame, 0); - if (ret < 0) - return ret; - wn->written += ret; -out: - return 1; + if (ret == 0) + return; + if (ret < 0 || !powd) + return sched_min_delay(s); + para_fd_set(powd->fd, &s->wfds, &s->max_fileno); } static void oss_close(struct writer_node *wn) { struct private_oss_write_data *powd = wn->private_data; - if (powd->fd >= 0) - close(powd->fd); + if (!powd) + return; + close(powd->fd); free(powd); } @@ -98,49 +80,47 @@ static void oss_close(struct writer_node *wn) * incorrectly believe that the device is still in 44.1 kHz mode when actually * the speed is decreased to 22.05 kHz. */ -static int oss_open(struct writer_node *wn) +static int oss_init(struct writer_node *wn, unsigned sample_rate, + unsigned channels, int sample_format) { - int ret, format = FORMAT, channels, samplerate; + int ret, format; + unsigned ch, rate; struct oss_write_args_info *conf = wn->conf; - struct writer_node_group *wng = wn->wng; - struct private_oss_write_data *powd; + struct private_oss_write_data *powd = para_calloc(sizeof(*powd)); + wn->private_data = powd; PARA_INFO_LOG("opening %s\n", conf->device_arg); ret = para_open(conf->device_arg, O_WRONLY, 0); if (ret < 0) - return ret; - powd = para_calloc(sizeof(*powd)); - wn->private_data = powd; + goto err_free; powd->fd = ret; ret = mark_fd_nonblocking(powd->fd); if (ret < 0) goto err; /* set PCM format */ + sample_format = format = get_oss_format(sample_format); ret = ioctl(powd->fd, SNDCTL_DSP_SETFMT, &format); if (ret < 0) { ret = -ERRNO_TO_PARA_ERROR(errno); goto err; } ret = -E_BAD_SAMPLE_FORMAT; - if (format != FORMAT) + if (format != sample_format) goto err; /* set number of channels */ - if (!conf->channels_given && wng->channels) - channels = *wng->channels; - else - channels = conf->channels_arg; - ret = -E_BAD_CHANNEL_COUNT; - if (channels == 0) - goto err; - powd->channels = channels; - ret = ioctl(powd->fd, SNDCTL_DSP_CHANNELS, &channels); + ch = channels; + ret = ioctl(powd->fd, SNDCTL_DSP_CHANNELS, &ch); if (ret < 0) { ret = -ERRNO_TO_PARA_ERROR(errno); goto err; } - if (powd->channels != channels) + ret = -E_BAD_CHANNEL_COUNT; + if (ch != channels) goto err; - powd->bytes_per_frame = channels * 2; + if (format == SF_U8 || format == SF_S8) + powd->bytes_per_frame = ch; + else + powd->bytes_per_frame = ch * 2; /* * Set sampling rate @@ -149,19 +129,15 @@ static int oss_open(struct writer_node *wn) * device, the the highest possible speed is automatically used. The * value actually used is returned as the new value of the argument. */ - if (!conf->samplerate_given && wng->samplerate) - samplerate = *wng->samplerate; - else - samplerate = conf->samplerate_arg; - powd->samplerate = samplerate; - ret = ioctl(powd->fd, SNDCTL_DSP_SPEED, &samplerate); + rate = sample_rate; + ret = ioctl(powd->fd, SNDCTL_DSP_SPEED, &rate); if (ret < 0) { ret = -ERRNO_TO_PARA_ERROR(errno); goto err; } - if (samplerate != powd->samplerate) { - int min = PARA_MIN(samplerate, powd->samplerate), - max = PARA_MAX(samplerate, powd->samplerate); + if (rate != sample_rate) { + unsigned min = PARA_MIN(rate, sample_rate), + max = PARA_MAX(rate, sample_rate); /* * Check whether the returned sample rate differs significantly * from the requested one. @@ -169,30 +145,75 @@ static int oss_open(struct writer_node *wn) ret = -E_BAD_SAMPLERATE; if (100 * max > 110 * min) /* more than 10% deviation */ goto err; - PARA_NOTICE_LOG("using %dHz rather than %dHz\n", samplerate, - powd->samplerate); + PARA_NOTICE_LOG("using %dHz rather than %dHz\n", rate, + sample_rate); } - + wn->min_iqs = powd->bytes_per_frame; return 1; err: close(powd->fd); - powd->fd = -1; +err_free: + free(powd); return ret; } -__malloc static void *oss_parse_config(const char *options) +static void oss_post_select(__a_unused struct sched *s, + struct task *t) +{ + struct writer_node *wn = container_of(t, struct writer_node, task); + struct private_oss_write_data *powd = wn->private_data; + struct btr_node *btrn = wn->btrn; + size_t frames, bytes; + int ret = btr_node_status(btrn, wn->min_iqs, BTR_NT_LEAF); + char *data; + + if (ret < 0) + goto out; + if (ret == 0) + return; + if (!powd) { + int32_t rate, ch, format; + get_btr_sample_rate(btrn, &rate); + get_btr_channels(btrn, &ch); + get_btr_sample_format(btrn, &format); + ret = oss_init(wn, rate, ch, format); + if (ret < 0) + goto out; + return; + } + btr_merge(btrn, wn->min_iqs); + bytes = btr_next_buffer(btrn, &data); + frames = bytes / powd->bytes_per_frame; + if (!frames) { /* eof and less than a single frame available */ + ret = -E_OSS_EOF; + goto out; + } + ret = 0; + if (!FD_ISSET(powd->fd, &s->wfds)) + goto out; + ret = write_nonblock(powd->fd, data, frames * powd->bytes_per_frame); + if (ret < 0) + goto out; + btr_consume(btrn, ret); + ret = 0; +out: + t->error = ret; + if (ret < 0) + btr_remove_node(btrn); +} + +__malloc static void *oss_parse_config_or_die(const char *options) { - int ret; struct oss_write_args_info *conf = para_calloc(sizeof(*conf)); - PARA_INFO_LOG("options: %s, %zd\n", options, strcspn(options, " \t")); - ret = oss_cmdline_parser_string(options, conf, "oss_write"); - if (ret) - goto err_out; + /* exits on errors */ + oss_cmdline_parser_string(options, conf, "oss_write"); return conf; -err_out: - free(conf); - return NULL; +} + +static void oss_free_config(void *conf) +{ + oss_cmdline_parser_free(conf); } /** @@ -207,11 +228,11 @@ void oss_write_init(struct writer *w) struct oss_write_args_info dummy; oss_cmdline_parser_init(&dummy); - w->open = oss_open; w->close = oss_close; w->pre_select = oss_pre_select; w->post_select = oss_post_select; - w->parse_config = oss_parse_config; + w->parse_config_or_die = oss_parse_config_or_die; + w->free_config = oss_free_config; w->shutdown = NULL; w->help = (struct ggo_help) { .short_help = oss_write_args_info_help, diff --git a/osx_write.c b/osx_write.c index c4042889..f27a49b0 100644 --- a/osx_write.c +++ b/osx_write.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -11,8 +11,10 @@ * */ +#include #include #include +#include #include "para.h" #include "fd.h" @@ -20,7 +22,9 @@ #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "write.h" +#include "write_common.h" #include "osx_write.cmdline.h" #include "error.h" @@ -53,7 +57,9 @@ struct private_osx_write_data { /** the post_select writes audio data here */ struct osx_buffer *to; /** sample rate of the current audio stream */ - unsigned samplerate; + unsigned sample_rate; + /** Sample format of the current audio stream */ + unsigned sample_format; /** number of channels of the current audio stream */ unsigned channels; }; @@ -90,19 +96,41 @@ static void init_buffers(struct writer_node *wn) *ptrptr = powd->from = powd->to; } -static void fill_buffer(struct osx_buffer *b, short *source, long size) +static void fill_buffer(struct private_osx_write_data *powd, char *data, long bytes) { + struct osx_buffer *b = powd->to; float *dest; + long samples; + enum sample_format sf = powd->sample_format; - if (b->remaining) /* Non empty buffer, must still be playing */ - return; - if (b->size != size) { - b->buffer = para_realloc(b->buffer, size * sizeof(float)); - b->size = size; + samples = (sf == SF_S8 || sf == SF_U8)? bytes : bytes / 2; + assert(b->remaining == 0 || samples > 0); + if (b->size != samples) { + b->buffer = para_realloc(b->buffer, samples * sizeof(float)); + b->size = samples; } dest = b->buffer; - while (size--) - *dest++ = (*source++) / 32768.0; + switch (powd->sample_format) { + case SF_U8: { + uint8_t *src = (uint8_t *)data; + while (samples--) { + *dest++ = (*src++) / 256.0; + } + break; + } + case SF_S8: { + int8_t *src = (int8_t *)data; + while (samples--) { + *dest++ = ((*src++) + 128) / 256.0; + } + break; + } + default: { + short *src = (short *)data; + while (samples--) + *dest++ = (*src++) / 32768.0; + } + } b->ptr = b->buffer; b->remaining = b->size; } @@ -125,9 +153,14 @@ static OSStatus osx_callback(void * inClientData, m = outOutputData->mBuffers[i].mDataByteSize / sizeof(float); dest = outOutputData->mBuffers[i].mData; while (m > 0) { - if ((n = powd->from->remaining) <= 0) { - PARA_INFO_LOG("buffer underrun\n"); - return 0; + n = powd->from->remaining; + if (n <= 0) { + n = powd->from->next->remaining; + if (n <= 0) { + PARA_INFO_LOG("buffer underrun\n"); + return 0; + } + powd->from = powd->from->next; } // PARA_INFO_LOG("buf %p: n = %ld, m= %ld\n", powd->from->buffer, n, m); /* @@ -154,19 +187,19 @@ static OSStatus osx_callback(void * inClientData, #define ENDIAN_FLAGS 0 #endif -static int osx_write_open(struct writer_node *wn) +static int core_audio_init(struct writer_node *wn) { - struct private_osx_write_data *powd = para_calloc( - sizeof(struct private_osx_write_data)); + struct private_osx_write_data *powd = para_calloc(sizeof(*powd)); ComponentDescription desc; Component comp; AURenderCallbackStruct inputCallback = {osx_callback, powd}; AudioStreamBasicDescription format; int ret; - struct writer_node_group *wng = wn->wng; - struct osx_write_args_info *conf = wn->conf; + struct btr_node *btrn = wn->btrn; + int32_t val; wn->private_data = powd; + init_buffers(wn); /* where did that default audio output go? */ desc.componentType = kAudioUnitType_Output; desc.componentSubType = kAudioUnitSubType_DefaultOutput; @@ -185,39 +218,44 @@ static int osx_write_open(struct writer_node *wn) if (AudioUnitInitialize(powd->audio_unit)) goto e1; powd->play = 0; - /* Hmmm, let's choose PCM format */ - /* We tell the Output Unit what format we're going to supply data to it. - * This is necessary if you're providing data through an input callback - * AND you want the DefaultOutputUnit to do any format conversions - * necessary from your format to the device's format. + get_btr_sample_rate(btrn, &val); + powd->sample_rate = val; + get_btr_channels(btrn, &val); + powd->channels = val; + get_btr_sample_format(btrn, &val); + powd->sample_format = val; + /* + * Choose PCM format. We tell the Output Unit what format we're going + * to supply data to it. This is necessary if you're providing data + * through an input callback AND you want the DefaultOutputUnit to do + * any format conversions necessary from your format to the device's + * format. */ - if (!conf->samplerate_given && wng->samplerate) - powd->samplerate = *wng->samplerate; - else - powd->samplerate = conf->samplerate_arg; - format.mSampleRate = powd->samplerate; - /* The specific encoding type of audio stream*/ format.mFormatID = kAudioFormatLinearPCM; + format.mFramesPerPacket = 1; + format.mSampleRate = powd->sample_rate; /* flags specific to each format */ format.mFormatFlags = kLinearPCMFormatFlagIsFloat | kLinearPCMFormatFlagIsPacked | ENDIAN_FLAGS; - if (!conf->channels_given && wng->channels) - powd->channels = *wng->channels; - else - powd->channels = conf->channels_arg; + switch (powd->sample_format) { + case SF_S8: + case SF_U8: + wn->min_iqs = powd->channels; + break; + default: + wn->min_iqs = powd->channels * 2; + } + format.mBitsPerChannel = 8 * sizeof(float); + format.mBytesPerPacket = powd->channels * sizeof(float); + format.mBytesPerFrame = format.mBytesPerPacket; format.mChannelsPerFrame = powd->channels; - format.mFramesPerPacket = 1; - format.mBytesPerPacket = format.mChannelsPerFrame * sizeof(float); - format.mBytesPerFrame = format.mFramesPerPacket * format.mBytesPerPacket; - /* one of the most constant constants of the whole computer history */ - format.mBitsPerChannel = sizeof(float) * 8; + ret = -E_STREAM_FORMAT; if (AudioUnitSetProperty(powd->audio_unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, sizeof(AudioStreamBasicDescription))) goto e2; - init_buffers(wn); ret = -E_ADD_CALLBACK; if (AudioUnitSetProperty(powd->audio_unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &inputCallback, @@ -231,28 +269,31 @@ e2: e1: CloseComponent(powd->audio_unit); e0: + free(powd); + wn->private_data = NULL; return ret; } -__malloc static void *osx_write_parse_config(const char *options) +__malloc static void *osx_write_parse_config_or_die(const char *options) { - struct osx_write_args_info *conf - = para_calloc(sizeof(struct osx_write_args_info)); - PARA_INFO_LOG("options: %s\n", options); - int ret = osx_cmdline_parser_string(options, conf, "osx_write"); - if (ret) - goto err_out; + struct osx_write_args_info *conf = para_calloc(sizeof(*conf)); + + /* exits on errors */ + osx_cmdline_parser_string(options, conf, "osx_write"); return conf; -err_out: - free(conf); - return NULL; +} +static void osx_free_config(void *conf) +{ + osx_cmdline_parser_free(conf); } static void osx_write_close(struct writer_node *wn) { struct private_osx_write_data *powd = wn->private_data; + if (!powd) + return; PARA_INFO_LOG("closing writer node %p\n", wn); AudioOutputUnitStop(powd->audio_unit); AudioUnitUninitialize(powd->audio_unit); @@ -261,72 +302,80 @@ static void osx_write_close(struct writer_node *wn) free(powd); } -static int need_new_buffer(struct writer_node *wn) -{ - struct writer_node_group *wng = wn->wng; - struct private_osx_write_data *powd = wn->private_data; - - if (*wng->loaded < sizeof(short)) - return 0; - if (powd->to->remaining) /* Non empty buffer, must still be playing */ - return 0; - return 1; -} - -static int osx_write_post_select(__a_unused struct sched *s, - struct writer_node *wn) +static void osx_write_post_select(__a_unused struct sched *s, struct task *t) { + struct writer_node *wn = container_of(t, struct writer_node, task); struct private_osx_write_data *powd = wn->private_data; - struct writer_node_group *wng = wn->wng; - short *data = (short*)*wng->bufp; + struct btr_node *btrn = wn->btrn; + char *data; + size_t bytes; + int ret = 0; - if (!need_new_buffer(wn)) - return 1; - fill_buffer(powd->to, data, *wng->loaded / sizeof(short)); - powd->to = powd->to->next; - wn->written = *wng->loaded; - if (!powd->play) { - if (AudioOutputUnitStart(powd->audio_unit)) - return -E_UNIT_START; - powd->play = 1; + while (!powd || powd->to->remaining <= 0) { + ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF); + if (ret <= 0) + break; + if (!powd) { + ret = core_audio_init(wn); + if (ret < 0) + break; + powd = wn->private_data; + } + btr_merge(btrn, 8192); + bytes = btr_next_buffer(btrn, &data); + //PARA_CRIT_LOG("have: %zu\n", bytes); + fill_buffer(powd, data, bytes); + btr_consume(btrn, bytes); + if (!powd->play) { + ret = -E_UNIT_START; + if (AudioOutputUnitStart(powd->audio_unit)) + break; + powd->play = 1; + } + powd->to = powd->to->next; + } + if (ret < 0 && (!powd || powd->from->remaining <= 0)) { + btr_remove_node(btrn); + t->error = ret; } - return 1; } -static int osx_write_pre_select(struct sched *s, __a_unused struct writer_node *wn) +static void osx_write_pre_select(struct sched *s, struct task *t) { + struct writer_node *wn = container_of(t, struct writer_node, task); struct private_osx_write_data *powd = wn->private_data; - struct writer_node_group *wng = wn->wng; - size_t numbytes = powd->to->remaining * sizeof(short); struct timeval tmp = {.tv_sec = 1, .tv_usec = 0}, delay = tmp; - unsigned long divisor; + unsigned long factor; + size_t numbytes; + int ret = btr_node_status(wn->btrn, wn->min_iqs, BTR_NT_LEAF); - if (!numbytes && *wng->loaded >= sizeof(short)) - goto min_delay; /* there's a buffer to fill */ - if (!numbytes) - return 1; - divisor = powd->samplerate * powd->channels * 2 / numbytes; - if (divisor) - tv_divide(divisor, &tmp, &delay); - if (tv_diff(&s->timeout, &delay, NULL) > 0) - s->timeout = delay; -// PARA_DEBUG_LOG("delay: %lu:%lu\n", (long unsigned) s->timeout.tv_sec, -// (long unsigned) s->timeout.tv_usec); - return 1; -min_delay: - PARA_DEBUG_LOG("%s\n", "minimal delay"); - s->timeout.tv_sec = 0; - s->timeout.tv_usec = 1; - return 1; + if (ret == 0) + return; + if (ret < 0 || !powd) + return sched_min_delay(s); + assert(powd->sample_rate > 0); + assert(wn->min_iqs > 0); + numbytes = powd->to->remaining * sizeof(short); + factor = numbytes / powd->sample_rate / wn->min_iqs; + tv_scale(factor, &tmp, &delay); + sched_request_timeout(&delay, s); } /** the init function of the osx writer */ void osx_write_init(struct writer *w) { - w->open = osx_write_open; + struct osx_write_args_info dummy; + + osx_cmdline_parser_init(&dummy); w->close = osx_write_close; w->pre_select = osx_write_pre_select; w->post_select = osx_write_post_select; - w->parse_config = osx_write_parse_config; + w->parse_config_or_die = osx_write_parse_config_or_die; + w->free_config = osx_free_config; w->shutdown = NULL; /* nothing to do */ + w->help = (struct ggo_help) { + .short_help = osx_write_args_info_help, + .detailed_help = osx_write_args_info_detailed_help + }; + osx_cmdline_parser_free(&dummy); } diff --git a/para.h b/para.h index e4e9a53d..c5e12fe6 100644 --- a/para.h +++ b/para.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 1997-2009 Andre Noll + * Copyright (C) 1997-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -49,72 +49,8 @@ typeof(x) _x = (x); \ _x > 0? _x : -_x; }) -/** Debug loglevel, gets really noisy. */ -#define LL_DEBUG 0 -/** Still noisy, but won't fill your disk. */ -#define LL_INFO 1 -/** Normal, but significant event. */ -#define LL_NOTICE 2 -/** Unexpected event that can be handled. */ -#define LL_WARNING 3 -/** Unhandled error condition. */ -#define LL_ERROR 4 -/** System might be unreliable. */ -#define LL_CRIT 5 -/** Last message before exit. */ -#define LL_EMERG 6 -/** Number of all loglevels. */ -#define NUM_LOGLEVELS 7 - -/** Log messages with lower priority than that will not be compiled in. */ -#define COMPILE_TIME_LOGLEVEL 0 - -/** \cond */ -#if LL_DEBUG >= COMPILE_TIME_LOGLEVEL -#define PARA_DEBUG_LOG(f,...) para_log(LL_DEBUG, "%s: " f, __FUNCTION__, ## __VA_ARGS__) -#else -#define PARA_DEBUG_LOG(...) do {;} while (0) -#endif - -#if LL_INFO >= COMPILE_TIME_LOGLEVEL -#define PARA_INFO_LOG(f,...) para_log(LL_INFO, "%s: " f, __FUNCTION__, ## __VA_ARGS__) -#else -#define PARA_INFO_LOG(...) do {;} while (0) -#endif - -#if LL_NOTICE >= COMPILE_TIME_LOGLEVEL -#define PARA_NOTICE_LOG(f,...) para_log(LL_NOTICE, "%s: " f, __FUNCTION__, ## __VA_ARGS__) -#else -#define PARA_NOTICE_LOG(...) do {;} while (0) -#endif - -#if LL_WARNING >= COMPILE_TIME_LOGLEVEL -#define PARA_WARNING_LOG(f,...) para_log(LL_WARNING, "%s: " f, __FUNCTION__, ## __VA_ARGS__) -#else -#define PARA_WARNING_LOG(...) do {;} while (0) -#endif - -#if LL_ERROR >= COMPILE_TIME_LOGLEVEL -#define PARA_ERROR_LOG(f,...) para_log(LL_ERROR, "%s: " f, __FUNCTION__, ## __VA_ARGS__) -#else -#define PARA_ERROR_LOG(...) do {;} while (0) -#endif - -#if LL_CRIT >= COMPILE_TIME_LOGLEVEL -#define PARA_CRIT_LOG(f,...) para_log(LL_CRIT, "%s: " f, __FUNCTION__, ## __VA_ARGS__) -#else -#define PARA_CRIT_LOG(...) do {;} while (0) -#endif - -#if LL_EMERG >= COMPILE_TIME_LOGLEVEL -#define PARA_EMERG_LOG(f,...) para_log(LL_EMERG, "%s: " f, __FUNCTION__, ## __VA_ARGS__) -#else -#define PARA_EMERG_LOG(...) -#endif -/** \endcond */ - /** - * define a standard log function that always writes to stderr + * Define a standard log function that always writes to stderr. * * \param loglevel_barrier If the loglevel of the current message * is less than that, the message is going to be ignored. @@ -131,30 +67,32 @@ va_end(argp); \ } -/** version text used by various commands if -V switch was given */ -#define VERSION_TEXT(prefix) "para_" prefix " " PACKAGE_VERSION " (" CODENAME ")" "\n" \ - "Copyright (C) 2009 Andre Noll\n" \ +/** Version text used by various commands if -V switch was given. */ +#define VERSION_TEXT(prefix) "para_" prefix " " PACKAGE_VERSION \ + " (" GIT_VERSION ": " CODENAME ")" "\n" \ + "Copyright (C) 2011 Andre Noll\n" \ "This is free software with ABSOLUTELY NO WARRANTY." \ " See COPYING for details.\n" \ "Written by Andre Noll.\n" \ "Report bugs to .\n" -/** print out \p VERSION_TEXT and exit if version flag was given */ +/** Print out \p VERSION_TEXT and exit if version flag was given. */ #define HANDLE_VERSION_FLAG(_prefix, _args_info_struct) \ if (_args_info_struct.version_given) { \ printf("%s", VERSION_TEXT(_prefix)); \ exit(EXIT_SUCCESS); \ } -/** sent by para_server for commands that expect a data file */ + +/** Sent by para_client to initiate the authentication procedure. */ +#define AUTH_REQUEST_MSG "auth rsa " +/** Sent by para_server for commands that expect a data file. */ #define AWAITING_DATA_MSG "\nAwaiting Data." -/** sent by para_server if authentication was successful */ -#define PROCEED_MSG "\nProceed.\n" -/** length of the \p PROCEED_MSG string */ +/** Sent by para_server if authentication was successful. */ +#define PROCEED_MSG "Proceed." +/** Length of the \p PROCEED_MSG string. */ #define PROCEED_MSG_LEN strlen(PROCEED_MSG) -/** sent by para_client to indicate the end of the command line */ +/** Sent by para_client to indicate the end of the command line. */ #define EOC_MSG "\nEnd of Command." -/** sent by para_client, followed by the decrypted challenge number */ -#define CHALLENGE_RESPONSE_MSG "challenge_response:" /* exec */ int para_exec_cmdline_pid(pid_t *pid, const char *cmdline, int *fds); @@ -180,10 +118,8 @@ enum status_items {STATUS_ITEM_ENUM NUM_STAT_ITEMS}; extern const char *status_item_list[]; /** Loop over each status item. */ #define FOR_EACH_STATUS_ITEM(i) for (i = 0; i < NUM_STAT_ITEMS; i++) -int stat_item_valid(const char *item); -int stat_line_valid(const char *); -void stat_client_write(const char *msg, int itemnum); -int stat_client_add(int fd, uint64_t mask); +int for_each_stat_item(char *item_buf, size_t num_bytes, + int (*item_handler)(int, char *)); __printf_2_3 void para_log(int, const char*, ...); @@ -224,14 +160,15 @@ __printf_2_3 void para_log(int, const char*, ...); * * \return An integer between zero and \p max - 1, inclusively. */ -static inline long int para_random(unsigned max) +_static_inline_ long int para_random(unsigned max) { return ((max + 0.0) * (random() / (RAND_MAX + 1.0))); } -/** Round up x to a multiple of y */ -#define ROUND_UP(x, y) ((((x) + ((y) - 1)) / (y)) * (y)) - +/** Divide and round up to next integer. */ +#define DIV_ROUND_UP(x, y) ({ \ + typeof(y) _divisor = y; \ + ((x) + _divisor - 1) / _divisor; }) /** Get the size of an array */ #define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) @@ -255,3 +192,95 @@ static inline long int para_random(unsigned max) #define FEC_EOF_PACKET "\xec\x0d\xcc\xfe\0\0\0\0" \ "\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0" "\0\0\0\0\0\0\0\0" #define FEC_EOF_PACKET_LEN 32 + +/** Used to avoid a shortcoming in vim's syntax highlighting. */ +#define EMBRACE(...) { __VA_ARGS__} + +/** + * The sample formats supported by paraslash. + * + * It may be determined by one of the following sources: + * + * 1. The decoding filter (para_audiod only). In this case, it is always + * \p SF_S16_LE which is the canonical format used within decoders. + * + * 2. The wav header (para_write only). + * + * 3. The --format option of para_write. + */ +#define SAMPLE_FORMATS \ + SAMPLE_FORMAT(SF_S8, "8 bit signed"), \ + SAMPLE_FORMAT(SF_U8, "8 bit unsigned"), \ + SAMPLE_FORMAT(SF_S16_LE, "16 bit signed, little endian"), \ + SAMPLE_FORMAT(SF_S16_BE, "16 bit signed, big endian"), \ + SAMPLE_FORMAT(SF_U16_LE, "16 bit unsigned, little endian"), \ + SAMPLE_FORMAT(SF_U16_BE, "16 bit unsigned, big endian"), \ + +#define SAMPLE_FORMAT(a, b) a +enum sample_format {SAMPLE_FORMATS}; +#undef SAMPLE_FORMAT +#define SAMPLE_FORMAT(a, b) b + +/** Debug loglevel, gets really noisy. */ +#define LL_DEBUG 0 +/** Still noisy, but won't fill your disk. */ +#define LL_INFO 1 +/** Normal, but significant event. */ +#define LL_NOTICE 2 +/** Unexpected event that can be handled. */ +#define LL_WARNING 3 +/** Unhandled error condition. */ +#define LL_ERROR 4 +/** System might be unreliable. */ +#define LL_CRIT 5 +/** Last message before exit. */ +#define LL_EMERG 6 +/** Number of all loglevels. */ +#define NUM_LOGLEVELS 7 + +/** Log messages with lower priority than that will not be compiled in. */ +#define COMPILE_TIME_LOGLEVEL 0 + +/** \cond */ +#if LL_DEBUG >= COMPILE_TIME_LOGLEVEL +#define PARA_DEBUG_LOG(f,...) para_log(LL_DEBUG, "%s: " f, __FUNCTION__, ## __VA_ARGS__) +#else +#define PARA_DEBUG_LOG(...) do {;} while (0) +#endif + +#if LL_INFO >= COMPILE_TIME_LOGLEVEL +#define PARA_INFO_LOG(f,...) para_log(LL_INFO, "%s: " f, __FUNCTION__, ## __VA_ARGS__) +#else +#define PARA_INFO_LOG(...) do {;} while (0) +#endif + +#if LL_NOTICE >= COMPILE_TIME_LOGLEVEL +#define PARA_NOTICE_LOG(f,...) para_log(LL_NOTICE, "%s: " f, __FUNCTION__, ## __VA_ARGS__) +#else +#define PARA_NOTICE_LOG(...) do {;} while (0) +#endif + +#if LL_WARNING >= COMPILE_TIME_LOGLEVEL +#define PARA_WARNING_LOG(f,...) para_log(LL_WARNING, "%s: " f, __FUNCTION__, ## __VA_ARGS__) +#else +#define PARA_WARNING_LOG(...) do {;} while (0) +#endif + +#if LL_ERROR >= COMPILE_TIME_LOGLEVEL +#define PARA_ERROR_LOG(f,...) para_log(LL_ERROR, "%s: " f, __FUNCTION__, ## __VA_ARGS__) +#else +#define PARA_ERROR_LOG(...) do {;} while (0) +#endif + +#if LL_CRIT >= COMPILE_TIME_LOGLEVEL +#define PARA_CRIT_LOG(f,...) para_log(LL_CRIT, "%s: " f, __FUNCTION__, ## __VA_ARGS__) +#else +#define PARA_CRIT_LOG(...) do {;} while (0) +#endif + +#if LL_EMERG >= COMPILE_TIME_LOGLEVEL +#define PARA_EMERG_LOG(f,...) para_log(LL_EMERG, "%s: " f, __FUNCTION__, ## __VA_ARGS__) +#else +#define PARA_EMERG_LOG(...) +#endif +/** \endcond */ diff --git a/pics/paraslash/default.jpg b/pics/paraslash/default.jpg deleted file mode 100644 index 451b9164..00000000 Binary files a/pics/paraslash/default.jpg and /dev/null differ diff --git a/pics/screenshots/audiod.log b/pics/screenshots/audiod.log deleted file mode 100644 index c9e43663..00000000 --- a/pics/screenshots/audiod.log +++ /dev/null @@ -1,549 +0,0 @@ -Dec 08 19:23:27 meins 2 log_welcome: welcome to para_audiod git (Sat Dec 8 13:42:52 MET 2007) -Dec 08 19:23:27 meins 2 init_writers: maximal number of writers: 3 -Dec 08 19:23:27 meins 2 check_writer_arg: checking alsa -d plug:swmix -Dec 08 19:23:27 meins 2 alsa_parse_config: options: -d plug:swmix, 2 -Dec 08 19:23:27 meins 2 alsa_parse_config: help given: 0 -Dec 08 19:23:27 meins 2 init_writers: mp3 writer #0: alsa -Dec 08 19:23:27 meins 2 check_writer_arg: checking alsa -d plug:swmix -Dec 08 19:23:27 meins 2 alsa_parse_config: options: -d plug:swmix, 2 -Dec 08 19:23:27 meins 2 alsa_parse_config: help given: 0 -Dec 08 19:23:27 meins 2 init_writers: ogg writer #0: alsa -Dec 08 19:23:27 meins 2 check_writer_arg: checking alsa -d plug:swmix -Dec 08 19:23:27 meins 2 alsa_parse_config: options: -d plug:swmix, 2 -Dec 08 19:23:27 meins 2 alsa_parse_config: help given: 0 -Dec 08 19:23:27 meins 2 init_writers: aac writer #0: alsa -Dec 08 19:23:27 meins 2 init_receivers: initializing http receiver -Dec 08 19:23:27 meins 2 init_receivers: initializing dccp receiver -Dec 08 19:23:27 meins 2 init_receivers: initializing ortp receiver -Dec 08 19:23:27 meins 2 init_filters: maximal number of filters: 6 -Dec 08 19:23:27 meins 2 add_filter: mp3 filter 1: mp3dec -Dec 08 19:23:27 meins 2 add_filter: mp3 filter 2: compress -Dec 08 19:23:27 meins 2 add_filter: ogg filter 1: oggdec -Dec 08 19:23:27 meins 2 add_filter: ogg filter 2: compress -Dec 08 19:23:27 meins 2 add_filter: aac filter 1: aacdec -Dec 08 19:23:27 meins 2 add_filter: aac filter 2: compress -Dec 08 19:23:27 meins 2 clear_slot: clearing slot 0 -Dec 08 19:23:27 meins 2 clear_slot: clearing slot 1 -Dec 08 19:23:27 meins 2 clear_slot: clearing slot 2 -Dec 08 19:23:27 meins 2 clear_slot: clearing slot 3 -Dec 08 19:23:27 meins 2 clear_slot: clearing slot 4 -Dec 08 19:23:27 meins 2 init_grabbing: grab init -Dec 08 19:23:27 meins 2 setup_signal_handling: signal pipe: fd 4 -Dec 08 19:23:27 meins 3 audiod_get_socket: local socket: /var/paraslash/audiod_socket.meins -Dec 08 19:23:27 meins 2 daemon_init: daemonizing -Dec 08 19:23:27 meins 2 init_sched: initializing scheduler -Dec 08 19:23:27 meins 2 register_task: registering signal task (0x80631bc) -Dec 08 19:23:27 meins 2 register_task: registering command task (0xbfec3ec4) -Dec 08 19:23:27 meins 2 register_task: registering status task (0x80632e8) -Dec 08 19:23:27 meins 2 register_task: registering audiod task (0xbfec3fe8) -Dec 08 19:23:27 meins 2 status_pre_select: clock diff count: 5 -Dec 08 19:23:27 meins 2 client_open: loglevel: 5 -Dec 08 19:23:27 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf -Dec 08 19:23:27 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan -Dec 08 19:23:27 meins 3 client_open: connecting localhost:2990 -Dec 08 19:23:27 meins 2 register_task: registering client (0x8067878) -Dec 08 19:23:27 meins 2 client_post_select: --> auth rc4 maan -Dec 08 19:23:27 meins 2 client_post_select: <-- [challenge] -Dec 08 19:23:27 meins 2 client_post_select: --> 1499570993 -Dec 08 19:23:27 meins 2 client_post_select: decrypting session key -Dec 08 19:23:27 meins 2 enable_crypt: rc4 encryption activated for fd 7 -Dec 08 19:23:27 meins 2 unregister_task: unregistering client (0x8067878) -Dec 08 19:23:28 meins 2 handle_connect: connection from user 409, buf: stat -Dec 08 19:23:28 meins 2 handle_connect: argv[0]: stat, argc= 1 -Dec 08 19:23:28 meins 2 com_stat: mask: 0xffffffff -Dec 08 19:23:28 meins 2 stat_client_add: adding client on fd 7 -Dec 08 19:23:28 meins 2 dump_stat_client_list: stat client on fd 7 -Dec 08 19:23:28 meins 2 status_pre_select: clock diff count: 4 -Dec 08 19:23:28 meins 2 client_open: loglevel: 5 -Dec 08 19:23:28 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf -Dec 08 19:23:28 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan -Dec 08 19:23:28 meins 3 client_open: connecting localhost:2990 -Dec 08 19:23:28 meins 2 register_task: registering client (0x8067878) -Dec 08 19:23:28 meins 2 client_post_select: --> auth rc4 maan -Dec 08 19:23:28 meins 2 client_post_select: <-- [challenge] -Dec 08 19:23:28 meins 2 client_post_select: --> 1400991892 -Dec 08 19:23:28 meins 2 client_post_select: decrypting session key -Dec 08 19:23:28 meins 2 enable_crypt: rc4 encryption activated for fd 8 -Dec 08 19:23:28 meins 2 compute_time_diff: time diff (cur/avg): -1ms/+1ms -Dec 08 19:23:28 meins 2 unregister_task: unregistering client (0x8067878) -Dec 08 19:23:29 meins 2 status_pre_select: clock diff count: 3 -Dec 08 19:23:29 meins 2 client_open: loglevel: 5 -Dec 08 19:23:29 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf -Dec 08 19:23:29 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan -Dec 08 19:23:29 meins 3 client_open: connecting localhost:2990 -Dec 08 19:23:29 meins 2 register_task: registering client (0x8067878) -Dec 08 19:23:29 meins 2 client_post_select: --> auth rc4 maan -Dec 08 19:23:29 meins 2 client_post_select: <-- [challenge] -Dec 08 19:23:29 meins 2 client_post_select: --> 1772412478 -Dec 08 19:23:29 meins 2 client_post_select: decrypting session key -Dec 08 19:23:29 meins 2 enable_crypt: rc4 encryption activated for fd 8 -Dec 08 19:23:29 meins 2 compute_time_diff: time diff (cur/avg): -1ms/+1ms -Dec 08 19:23:29 meins 2 unregister_task: unregistering client (0x8067878) -Dec 08 19:23:30 meins 2 status_pre_select: clock diff count: 2 -Dec 08 19:23:30 meins 2 client_open: loglevel: 5 -Dec 08 19:23:30 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf -Dec 08 19:23:30 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan -Dec 08 19:23:30 meins 3 client_open: connecting localhost:2990 -Dec 08 19:23:30 meins 2 register_task: registering client (0x8067878) -Dec 08 19:23:30 meins 2 client_post_select: --> auth rc4 maan -Dec 08 19:23:30 meins 2 client_post_select: <-- [challenge] -Dec 08 19:23:30 meins 2 client_post_select: --> 1882631692 -Dec 08 19:23:30 meins 2 client_post_select: decrypting session key -Dec 08 19:23:30 meins 2 enable_crypt: rc4 encryption activated for fd 8 -Dec 08 19:23:30 meins 2 compute_time_diff: time diff (cur/avg): -1ms/+1ms -Dec 08 19:23:30 meins 2 unregister_task: unregistering client (0x8067878) -Dec 08 19:23:31 meins 2 status_pre_select: clock diff count: 1 -Dec 08 19:23:31 meins 2 client_open: loglevel: 5 -Dec 08 19:23:31 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf -Dec 08 19:23:31 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan -Dec 08 19:23:31 meins 3 client_open: connecting localhost:2990 -Dec 08 19:23:31 meins 2 register_task: registering client (0x8067878) -Dec 08 19:23:31 meins 2 client_post_select: --> auth rc4 maan -Dec 08 19:23:31 meins 2 client_post_select: <-- [challenge] -Dec 08 19:23:31 meins 2 client_post_select: --> 1630455651 -Dec 08 19:23:31 meins 2 client_post_select: decrypting session key -Dec 08 19:23:31 meins 2 enable_crypt: rc4 encryption activated for fd 8 -Dec 08 19:23:31 meins 2 compute_time_diff: time diff (cur/avg): -1ms/+1ms -Dec 08 19:23:31 meins 2 unregister_task: unregistering client (0x8067878) -Dec 08 19:23:36 meins 2 client_open: loglevel: 5 -Dec 08 19:23:36 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf -Dec 08 19:23:36 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan -Dec 08 19:23:36 meins 3 client_open: connecting localhost:2990 -Dec 08 19:23:36 meins 2 register_task: registering client (0x8067878) -Dec 08 19:23:36 meins 2 client_post_select: --> auth rc4 maan -Dec 08 19:23:36 meins 2 client_post_select: <-- [challenge] -Dec 08 19:23:36 meins 2 client_post_select: --> 759215314 -Dec 08 19:23:36 meins 2 client_post_select: decrypting session key -Dec 08 19:23:36 meins 2 enable_crypt: rc4 encryption activated for fd 8 -Dec 08 19:23:36 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms -Dec 08 19:23:36 meins 2 clear_slot: clearing slot 0 -Dec 08 19:23:36 meins 3 open_receiver: started ogg: dccp receiver in slot 0 -Dec 08 19:23:36 meins 2 register_task: registering dccp receiver node (0x806a324) -Dec 08 19:23:37 meins 2 open_filters: opening ogg filters -Dec 08 19:23:37 meins 3 open_filters: ogg filter 1/2 (oggdec) started in slot 0 -Dec 08 19:23:37 meins 3 open_filters: ogg filter 2/2 (compress) started in slot 0 -Dec 08 19:23:37 meins 2 register_task: registering filter chain (0x807460c) -Dec 08 19:23:37 meins 3 ogg_convert: input buffer: 17032, opening ov callbacks -Dec 08 19:23:37 meins 3 ogg_convert: 2 channels, 44100 Hz -Dec 08 19:23:37 meins 2 open_writers: opening ogg writers -Dec 08 19:23:37 meins 2 open_writers: samplerate: 44100 -Dec 08 19:23:37 meins 3 wng_open: opening wng 0x80a5660 with 1 writer(s) -Dec 08 19:23:37 meins 2 register_task: registering (0x80a5684) -Dec 08 19:23:37 meins 2 alsa_open: 2 channel(s), 44100Hz -Dec 08 19:23:37 meins 2 alsa_open: buffer time: 170658 -Dec 08 19:23:37 meins 2 alsa_open: buffer size: 7526, period_size: 940 -Dec 08 19:23:43 meins 3 rn_event_handler: dccp_recv: end of file -Dec 08 19:23:43 meins 2 unregister_task: unregistering dccp receiver node (0x806a324) -Dec 08 19:23:43 meins 2 unregister_task: unregistering client (0x8067878) -Dec 08 19:23:43 meins 2 kill_all_decoders: unregistering writer node group in slot 0 -Dec 08 19:23:43 meins 2 unregister_task: unregistering writer node group (0x80a5684) -Dec 08 19:23:43 meins 2 kill_all_decoders: unregistering filter chain in slot 0 -Dec 08 19:23:43 meins 2 unregister_task: unregistering filter chain (0x807460c) -Dec 08 19:23:43 meins 2 try_to_close_slot: closing slot 0 -Dec 08 19:23:43 meins 3 wng_close: closing wng with 1 writer(s) -Dec 08 19:23:43 meins 2 alsa_close: closing writer node 0x80a57b0 -Dec 08 19:23:43 meins 3 close_filters: closing filter chain 0x80745e0 -Dec 08 19:23:43 meins 2 close_filters: closing oggdec filter -Dec 08 19:23:43 meins 2 close_filters: closing compress filter -Dec 08 19:23:43 meins 3 close_receiver: closing ogg receiver in slot 0 (eof = 1) -Dec 08 19:23:43 meins 2 clear_slot: clearing slot 0 -Dec 08 19:23:48 meins 2 client_open: loglevel: 5 -Dec 08 19:23:48 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf -Dec 08 19:23:48 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan -Dec 08 19:23:48 meins 3 client_open: connecting localhost:2990 -Dec 08 19:23:48 meins 2 register_task: registering client (0x8067878) -Dec 08 19:23:48 meins 2 client_post_select: --> auth rc4 maan -Dec 08 19:23:48 meins 2 client_post_select: <-- [challenge] -Dec 08 19:23:48 meins 2 client_post_select: --> 960122352 -Dec 08 19:23:48 meins 2 client_post_select: decrypting session key -Dec 08 19:23:48 meins 2 enable_crypt: rc4 encryption activated for fd 8 -Dec 08 19:23:48 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms -Dec 08 19:23:58 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms -Dec 08 19:23:58 meins 2 clear_slot: clearing slot 0 -Dec 08 19:23:58 meins 3 open_receiver: started mp3: http receiver in slot 0 -Dec 08 19:23:58 meins 2 register_task: registering http receiver node (0x806a204) -Dec 08 19:23:58 meins 2 http_recv_post_select: sending http request -Dec 08 19:23:58 meins 2 http_recv_post_select: received ok msg, streaming -Dec 08 19:23:59 meins 2 open_filters: opening mp3 filters -Dec 08 19:23:59 meins 3 open_filters: mp3 filter 1/2 (mp3dec) started in slot 0 -Dec 08 19:23:59 meins 3 open_filters: mp3 filter 2/2 (compress) started in slot 0 -Dec 08 19:23:59 meins 2 register_task: registering filter chain (0x807260c) -Dec 08 19:23:59 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms -Dec 08 19:23:59 meins 2 audiod_pre_select: initial delay: 173 ms left -Dec 08 19:23:59 meins 2 audiod_pre_select: initial delay: 147 ms left -Dec 08 19:23:59 meins 2 audiod_pre_select: initial delay: 121 ms left -Dec 08 19:23:59 meins 2 audiod_pre_select: initial delay: 95 ms left -Dec 08 19:23:59 meins 2 audiod_pre_select: initial delay: 69 ms left -Dec 08 19:23:59 meins 2 audiod_pre_select: initial delay: 43 ms left -Dec 08 19:23:59 meins 2 audiod_pre_select: initial delay: 17 ms left -Dec 08 19:23:59 meins 2 open_writers: opening mp3 writers -Dec 08 19:23:59 meins 2 open_writers: samplerate: 44100 -Dec 08 19:23:59 meins 3 wng_open: opening wng 0x80a3c18 with 1 writer(s) -Dec 08 19:23:59 meins 2 register_task: registering (0x80a3c3c) -Dec 08 19:23:59 meins 2 alsa_open: 2 channel(s), 44100Hz -Dec 08 19:23:59 meins 2 alsa_open: buffer time: 170658 -Dec 08 19:23:59 meins 2 alsa_open: buffer size: 7526, period_size: 940 -Dec 08 19:24:43 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms -Dec 08 19:24:57 meins 4 alsa_write_post_select: EAGAIN -Dec 08 19:24:57 meins 4 alsa_write_post_select: EAGAIN -Dec 08 19:25:33 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms -Dec 08 19:25:43 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms -Dec 08 19:25:51 meins 3 rn_event_handler: http_recv: end of file -Dec 08 19:25:51 meins 2 unregister_task: unregistering http receiver node (0x806a204) -Dec 08 19:25:51 meins 2 unregister_task: unregistering client (0x8067878) -Dec 08 19:25:51 meins 2 kill_all_decoders: unregistering writer node group in slot 0 -Dec 08 19:25:51 meins 2 unregister_task: unregistering writer node group (0x80a3c3c) -Dec 08 19:25:51 meins 2 kill_all_decoders: unregistering filter chain in slot 0 -Dec 08 19:25:51 meins 2 unregister_task: unregistering filter chain (0x807260c) -Dec 08 19:25:51 meins 2 try_to_close_slot: closing slot 0 -Dec 08 19:25:51 meins 3 wng_close: closing wng with 1 writer(s) -Dec 08 19:25:51 meins 2 alsa_close: closing writer node 0x806a440 -Dec 08 19:25:51 meins 3 close_filters: closing filter chain 0x80725e0 -Dec 08 19:25:51 meins 2 close_filters: closing mp3dec filter -Dec 08 19:25:51 meins 2 close_filters: closing compress filter -Dec 08 19:25:51 meins 3 close_receiver: closing mp3 receiver in slot 0 (eof = 1) -Dec 08 19:25:51 meins 2 clear_slot: clearing slot 0 -Dec 08 19:25:56 meins 2 client_open: loglevel: 5 -Dec 08 19:25:56 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf -Dec 08 19:25:56 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan -Dec 08 19:25:56 meins 3 client_open: connecting localhost:2990 -Dec 08 19:25:56 meins 2 register_task: registering client (0x8067878) -Dec 08 19:25:56 meins 2 client_post_select: --> auth rc4 maan -Dec 08 19:25:56 meins 2 client_post_select: <-- [challenge] -Dec 08 19:25:56 meins 2 client_post_select: --> 563263924 -Dec 08 19:25:56 meins 2 client_post_select: decrypting session key -Dec 08 19:25:56 meins 2 enable_crypt: rc4 encryption activated for fd 8 -Dec 08 19:25:56 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms -Dec 08 19:26:06 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms -Dec 08 19:26:06 meins 2 clear_slot: clearing slot 0 -Dec 08 19:26:06 meins 3 open_receiver: started mp3: http receiver in slot 0 -Dec 08 19:26:06 meins 2 register_task: registering http receiver node (0x806a29c) -Dec 08 19:26:06 meins 2 http_recv_post_select: sending http request -Dec 08 19:26:06 meins 2 http_recv_post_select: received ok msg, streaming -Dec 08 19:26:06 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms -Dec 08 19:26:06 meins 2 open_filters: opening mp3 filters -Dec 08 19:26:06 meins 3 open_filters: mp3 filter 1/2 (mp3dec) started in slot 0 -Dec 08 19:26:06 meins 3 open_filters: mp3 filter 2/2 (compress) started in slot 0 -Dec 08 19:26:06 meins 2 register_task: registering filter chain (0x80726dc) -Dec 08 19:26:06 meins 2 audiod_pre_select: initial delay: 173 ms left -Dec 08 19:26:06 meins 2 audiod_pre_select: initial delay: 147 ms left -Dec 08 19:26:06 meins 2 audiod_pre_select: initial delay: 121 ms left -Dec 08 19:26:06 meins 2 audiod_pre_select: initial delay: 95 ms left -Dec 08 19:26:06 meins 2 audiod_pre_select: initial delay: 69 ms left -Dec 08 19:26:06 meins 2 audiod_pre_select: initial delay: 43 ms left -Dec 08 19:26:07 meins 2 audiod_pre_select: initial delay: 17 ms left -Dec 08 19:26:07 meins 2 open_writers: opening mp3 writers -Dec 08 19:26:07 meins 2 open_writers: samplerate: 44100 -Dec 08 19:26:07 meins 3 wng_open: opening wng 0x80a3cb8 with 1 writer(s) -Dec 08 19:26:07 meins 2 register_task: registering (0x80a3cdc) -Dec 08 19:26:07 meins 2 alsa_open: 2 channel(s), 44100Hz -Dec 08 19:26:07 meins 2 alsa_open: buffer time: 170658 -Dec 08 19:26:07 meins 2 alsa_open: buffer size: 7526, period_size: 940 -Dec 08 19:26:51 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms -Dec 08 19:26:58 meins 2 unregister_task: unregistering client (0x8067878) -Dec 08 19:26:58 meins 2 kill_all_decoders: unregistering writer node group in slot 0 -Dec 08 19:26:58 meins 2 unregister_task: unregistering writer node group (0x80a3cdc) -Dec 08 19:26:58 meins 2 kill_all_decoders: unregistering filter chain in slot 0 -Dec 08 19:26:58 meins 2 unregister_task: unregistering filter chain (0x80726dc) -Dec 08 19:26:58 meins 2 kill_all_decoders: unregistering receiver_node in slot 0 -Dec 08 19:26:58 meins 2 unregister_task: unregistering http receiver node (0x806a29c) -Dec 08 19:26:58 meins 2 try_to_close_slot: closing slot 0 -Dec 08 19:26:58 meins 3 wng_close: closing wng with 1 writer(s) -Dec 08 19:26:58 meins 2 alsa_close: closing writer node 0x80a3e08 -Dec 08 19:26:58 meins 3 close_filters: closing filter chain 0x80726b0 -Dec 08 19:26:58 meins 2 close_filters: closing mp3dec filter -Dec 08 19:26:58 meins 2 close_filters: closing compress filter -Dec 08 19:26:58 meins 3 close_receiver: closing mp3 receiver in slot 0 (eof = 1) -Dec 08 19:26:58 meins 2 clear_slot: clearing slot 0 -Dec 08 19:27:04 meins 2 client_open: loglevel: 5 -Dec 08 19:27:04 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf -Dec 08 19:27:04 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan -Dec 08 19:27:04 meins 3 client_open: connecting localhost:2990 -Dec 08 19:27:04 meins 5 makesock: can not create TCP socket localhost#2990. -Dec 08 19:27:04 meins 5 client_open: Connection refused -Dec 08 19:27:09 meins 2 client_open: loglevel: 5 -Dec 08 19:27:09 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf -Dec 08 19:27:09 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan -Dec 08 19:27:09 meins 3 client_open: connecting localhost:2990 -Dec 08 19:27:09 meins 5 makesock: can not create TCP socket localhost#2990. -Dec 08 19:27:09 meins 5 client_open: Connection refused -Dec 08 19:27:14 meins 2 client_open: loglevel: 5 -Dec 08 19:27:14 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf -Dec 08 19:27:14 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan -Dec 08 19:27:14 meins 3 client_open: connecting localhost:2990 -Dec 08 19:27:14 meins 5 makesock: can not create TCP socket localhost#2990. -Dec 08 19:27:14 meins 5 client_open: Connection refused -Dec 08 19:27:19 meins 2 client_open: loglevel: 5 -Dec 08 19:27:19 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf -Dec 08 19:27:19 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan -Dec 08 19:27:19 meins 3 client_open: connecting localhost:2990 -Dec 08 19:27:19 meins 5 makesock: can not create TCP socket localhost#2990. -Dec 08 19:27:19 meins 5 client_open: Connection refused -Dec 08 19:27:24 meins 2 client_open: loglevel: 5 -Dec 08 19:27:24 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf -Dec 08 19:27:24 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan -Dec 08 19:27:24 meins 3 client_open: connecting localhost:2990 -Dec 08 19:27:24 meins 5 makesock: can not create TCP socket localhost#2990. -Dec 08 19:27:24 meins 5 client_open: Connection refused -Dec 08 19:27:29 meins 2 client_open: loglevel: 5 -Dec 08 19:27:29 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf -Dec 08 19:27:29 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan -Dec 08 19:27:29 meins 3 client_open: connecting localhost:2990 -Dec 08 19:27:29 meins 5 makesock: can not create TCP socket localhost#2990. -Dec 08 19:27:29 meins 5 client_open: Connection refused -Dec 08 19:27:34 meins 2 client_open: loglevel: 5 -Dec 08 19:27:34 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf -Dec 08 19:27:34 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan -Dec 08 19:27:34 meins 3 client_open: connecting localhost:2990 -Dec 08 19:27:34 meins 2 register_task: registering client (0x8067878) -Dec 08 19:27:34 meins 2 client_post_select: --> auth rc4 maan -Dec 08 19:27:34 meins 2 client_post_select: <-- [challenge] -Dec 08 19:27:34 meins 2 client_post_select: --> 1725057215 -Dec 08 19:27:34 meins 2 client_post_select: decrypting session key -Dec 08 19:27:34 meins 2 enable_crypt: rc4 encryption activated for fd 8 -Dec 08 19:27:34 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms -Dec 08 19:27:45 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms -Dec 08 19:27:45 meins 2 clear_slot: clearing slot 0 -Dec 08 19:27:45 meins 3 open_receiver: started mp3: http receiver in slot 0 -Dec 08 19:27:45 meins 2 register_task: registering http receiver node (0x806a7b4) -Dec 08 19:27:45 meins 2 http_recv_post_select: sending http request -Dec 08 19:27:45 meins 2 http_recv_post_select: received ok msg, streaming -Dec 08 19:27:46 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms -Dec 08 19:27:46 meins 2 open_filters: opening mp3 filters -Dec 08 19:27:46 meins 3 open_filters: mp3 filter 1/2 (mp3dec) started in slot 0 -Dec 08 19:27:46 meins 3 open_filters: mp3 filter 2/2 (compress) started in slot 0 -Dec 08 19:27:46 meins 2 register_task: registering filter chain (0x80729f4) -Dec 08 19:27:46 meins 2 audiod_pre_select: initial delay: 173 ms left -Dec 08 19:27:46 meins 2 audiod_pre_select: initial delay: 147 ms left -Dec 08 19:27:46 meins 2 audiod_pre_select: initial delay: 121 ms left -Dec 08 19:27:46 meins 2 audiod_pre_select: initial delay: 95 ms left -Dec 08 19:27:46 meins 2 audiod_pre_select: initial delay: 69 ms left -Dec 08 19:27:46 meins 2 audiod_pre_select: initial delay: 43 ms left -Dec 08 19:27:46 meins 2 audiod_pre_select: initial delay: 17 ms left -Dec 08 19:27:46 meins 2 open_writers: opening mp3 writers -Dec 08 19:27:46 meins 2 open_writers: samplerate: 44100 -Dec 08 19:27:46 meins 3 wng_open: opening wng 0x80a4000 with 1 writer(s) -Dec 08 19:27:46 meins 2 register_task: registering (0x80a4024) -Dec 08 19:27:46 meins 2 alsa_open: 2 channel(s), 44100Hz -Dec 08 19:27:46 meins 2 alsa_open: buffer time: 170658 -Dec 08 19:27:46 meins 2 alsa_open: buffer size: 7526, period_size: 940 -Dec 08 19:28:30 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms -Dec 08 19:28:58 meins 3 rn_event_handler: http_recv: end of file -Dec 08 19:28:58 meins 2 unregister_task: unregistering http receiver node (0x806a7b4) -Dec 08 19:28:58 meins 2 unregister_task: unregistering client (0x8067878) -Dec 08 19:28:58 meins 2 kill_all_decoders: unregistering writer node group in slot 0 -Dec 08 19:28:58 meins 2 unregister_task: unregistering writer node group (0x80a4024) -Dec 08 19:28:58 meins 2 kill_all_decoders: unregistering filter chain in slot 0 -Dec 08 19:28:58 meins 2 unregister_task: unregistering filter chain (0x80729f4) -Dec 08 19:28:58 meins 2 try_to_close_slot: closing slot 0 -Dec 08 19:28:58 meins 3 wng_close: closing wng with 1 writer(s) -Dec 08 19:28:58 meins 2 alsa_close: closing writer node 0x80a4150 -Dec 08 19:28:58 meins 3 close_filters: closing filter chain 0x80729c8 -Dec 08 19:28:58 meins 2 close_filters: closing mp3dec filter -Dec 08 19:28:58 meins 2 close_filters: closing compress filter -Dec 08 19:28:58 meins 3 close_receiver: closing mp3 receiver in slot 0 (eof = 1) -Dec 08 19:28:58 meins 2 clear_slot: clearing slot 0 -Dec 08 19:29:03 meins 2 client_open: loglevel: 5 -Dec 08 19:29:03 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf -Dec 08 19:29:03 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan -Dec 08 19:29:03 meins 3 client_open: connecting localhost:2990 -Dec 08 19:29:03 meins 2 register_task: registering client (0x8067878) -Dec 08 19:29:03 meins 2 client_post_select: --> auth rc4 maan -Dec 08 19:29:03 meins 2 client_post_select: <-- [challenge] -Dec 08 19:29:03 meins 2 client_post_select: --> 705434988 -Dec 08 19:29:03 meins 2 client_post_select: decrypting session key -Dec 08 19:29:03 meins 2 enable_crypt: rc4 encryption activated for fd 8 -Dec 08 19:29:03 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms -Dec 08 19:29:13 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms -Dec 08 19:29:13 meins 2 clear_slot: clearing slot 0 -Dec 08 19:29:13 meins 3 open_receiver: started ogg: dccp receiver in slot 0 -Dec 08 19:29:13 meins 2 register_task: registering dccp receiver node (0x806a804) -Dec 08 19:29:13 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms -Dec 08 19:29:13 meins 2 open_filters: opening ogg filters -Dec 08 19:29:13 meins 3 open_filters: ogg filter 1/2 (oggdec) started in slot 0 -Dec 08 19:29:13 meins 3 open_filters: ogg filter 2/2 (compress) started in slot 0 -Dec 08 19:29:13 meins 2 register_task: registering filter chain (0x8074a44) -Dec 08 19:29:14 meins 3 ogg_convert: input buffer: 17167, opening ov callbacks -Dec 08 19:29:14 meins 3 ogg_convert: 2 channels, 44100 Hz -Dec 08 19:29:14 meins 2 open_writers: opening ogg writers -Dec 08 19:29:14 meins 2 open_writers: samplerate: 44100 -Dec 08 19:29:14 meins 3 wng_open: opening wng 0x80c8d20 with 1 writer(s) -Dec 08 19:29:14 meins 2 register_task: registering (0x80c8d44) -Dec 08 19:29:14 meins 2 alsa_open: 2 channel(s), 44100Hz -Dec 08 19:29:14 meins 2 alsa_open: buffer time: 170658 -Dec 08 19:29:14 meins 2 alsa_open: buffer size: 7526, period_size: 940 -Dec 08 19:29:58 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms -Dec 08 19:30:48 meins 2 compute_time_diff: time diff (cur/avg): -3ms/+1ms -Dec 08 19:30:58 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms -Dec 08 19:31:48 meins 2 compute_time_diff: time diff (cur/avg): -2ms/+1ms -Dec 08 19:31:58 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms -Dec 08 19:32:48 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms -Dec 08 19:32:58 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms -Dec 08 19:33:48 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms -Dec 08 19:33:56 meins 3 rn_event_handler: dccp_recv: end of file -Dec 08 19:33:56 meins 2 unregister_task: unregistering dccp receiver node (0x806a804) -Dec 08 19:33:56 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms -Dec 08 19:33:56 meins 3 filter_event_handler: filter chain: eof -Dec 08 19:33:56 meins 2 unregister_task: unregistering filter chain (0x8074a44) -Dec 08 19:33:56 meins 2 wng_event_handler: wng: end of file -Dec 08 19:33:56 meins 2 unregister_task: unregistering writer node group (0x80c8d44) -Dec 08 19:33:56 meins 2 try_to_close_slot: closing slot 0 -Dec 08 19:33:56 meins 3 wng_close: closing wng with 1 writer(s) -Dec 08 19:33:56 meins 2 alsa_close: closing writer node 0x80c8e70 -Dec 08 19:33:56 meins 3 close_filters: closing filter chain 0x8074a18 -Dec 08 19:33:56 meins 2 close_filters: closing oggdec filter -Dec 08 19:33:56 meins 2 close_filters: closing compress filter -Dec 08 19:33:56 meins 3 close_receiver: closing ogg receiver in slot 0 (eof = 1) -Dec 08 19:33:56 meins 2 clear_slot: clearing slot 0 -Dec 08 19:33:58 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms -Dec 08 19:33:58 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms -Dec 08 19:33:58 meins 2 clear_slot: clearing slot 0 -Dec 08 19:33:58 meins 3 open_receiver: started ogg: dccp receiver in slot 0 -Dec 08 19:33:58 meins 2 register_task: registering dccp receiver node (0x80d0dc4) -Dec 08 19:33:58 meins 2 open_filters: opening ogg filters -Dec 08 19:33:58 meins 3 open_filters: ogg filter 1/2 (oggdec) started in slot 0 -Dec 08 19:33:58 meins 3 open_filters: ogg filter 2/2 (compress) started in slot 0 -Dec 08 19:33:58 meins 2 register_task: registering filter chain (0x807481c) -Dec 08 19:33:58 meins 2 compute_time_diff: time diff (cur/avg): -3ms/+1ms -Dec 08 19:33:58 meins 3 ogg_convert: input buffer: 16947, opening ov callbacks -Dec 08 19:33:58 meins 3 ogg_convert: 2 channels, 44100 Hz -Dec 08 19:33:58 meins 2 open_writers: opening ogg writers -Dec 08 19:33:58 meins 2 open_writers: samplerate: 44100 -Dec 08 19:33:58 meins 3 wng_open: opening wng 0x809d860 with 1 writer(s) -Dec 08 19:33:58 meins 2 register_task: registering (0x809d884) -Dec 08 19:33:58 meins 2 alsa_open: 2 channel(s), 44100Hz -Dec 08 19:33:58 meins 2 alsa_open: buffer time: 170658 -Dec 08 19:33:58 meins 2 alsa_open: buffer size: 7526, period_size: 940 -Dec 08 19:34:48 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms -Dec 08 19:34:58 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms -Dec 08 19:35:48 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms -Dec 08 19:35:58 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms -Dec 08 19:36:48 meins 2 compute_time_diff: time diff (cur/avg): -3ms/+1ms -Dec 08 19:36:54 meins 3 rn_event_handler: dccp_recv: end of file -Dec 08 19:36:54 meins 2 unregister_task: unregistering dccp receiver node (0x80d0dc4) -Dec 08 19:36:54 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms -Dec 08 19:36:54 meins 3 filter_event_handler: filter chain: eof -Dec 08 19:36:54 meins 2 unregister_task: unregistering filter chain (0x807481c) -Dec 08 19:36:54 meins 2 wng_event_handler: wng: end of file -Dec 08 19:36:54 meins 2 unregister_task: unregistering writer node group (0x809d884) -Dec 08 19:36:54 meins 2 try_to_close_slot: closing slot 0 -Dec 08 19:36:54 meins 3 wng_close: closing wng with 1 writer(s) -Dec 08 19:36:54 meins 2 alsa_close: closing writer node 0x809d9b0 -Dec 08 19:36:54 meins 3 close_filters: closing filter chain 0x80747f0 -Dec 08 19:36:54 meins 2 close_filters: closing oggdec filter -Dec 08 19:36:54 meins 2 close_filters: closing compress filter -Dec 08 19:36:54 meins 3 close_receiver: closing ogg receiver in slot 0 (eof = 1) -Dec 08 19:36:54 meins 2 clear_slot: clearing slot 0 -Dec 08 19:36:56 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms -Dec 08 19:36:56 meins 2 clear_slot: clearing slot 0 -Dec 08 19:36:56 meins 3 open_receiver: started mp3: http receiver in slot 0 -Dec 08 19:36:56 meins 2 register_task: registering http receiver node (0x80d14ec) -Dec 08 19:36:56 meins 2 http_recv_post_select: sending http request -Dec 08 19:36:56 meins 2 http_recv_post_select: received ok msg, streaming -Dec 08 19:36:56 meins 2 open_filters: opening mp3 filters -Dec 08 19:36:56 meins 3 open_filters: mp3 filter 1/2 (mp3dec) started in slot 0 -Dec 08 19:36:56 meins 3 open_filters: mp3 filter 2/2 (compress) started in slot 0 -Dec 08 19:36:56 meins 2 register_task: registering filter chain (0x80d16ac) -Dec 08 19:36:56 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms -Dec 08 19:36:56 meins 2 audiod_pre_select: initial delay: 174 ms left -Dec 08 19:36:56 meins 2 audiod_pre_select: initial delay: 148 ms left -Dec 08 19:36:56 meins 2 audiod_pre_select: initial delay: 122 ms left -Dec 08 19:36:56 meins 2 audiod_pre_select: initial delay: 96 ms left -Dec 08 19:36:56 meins 2 audiod_pre_select: initial delay: 70 ms left -Dec 08 19:36:56 meins 2 audiod_pre_select: initial delay: 44 ms left -Dec 08 19:36:56 meins 2 audiod_pre_select: initial delay: 18 ms left -Dec 08 19:36:56 meins 2 open_writers: opening mp3 writers -Dec 08 19:36:56 meins 2 open_writers: samplerate: 44100 -Dec 08 19:36:56 meins 3 wng_open: opening wng 0x80743a0 with 1 writer(s) -Dec 08 19:36:56 meins 2 register_task: registering (0x80743c4) -Dec 08 19:36:56 meins 2 alsa_open: 2 channel(s), 44100Hz -Dec 08 19:36:56 meins 2 alsa_open: buffer time: 170658 -Dec 08 19:36:56 meins 2 alsa_open: buffer size: 7526, period_size: 940 -Dec 08 19:36:58 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms -Dec 08 19:37:48 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms -Dec 08 19:37:58 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms -Dec 08 19:38:48 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms -Dec 08 19:38:58 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms -Dec 08 19:39:06 meins 7 signal_event_handler: terminating on signal 15 -Dec 08 19:39:06 meins 7 clean_exit: caught deadly signal -Dec 08 19:39:06 meins 2 log_welcome: welcome to para_audiod git (Sat Dec 8 13:42:52 MET 2007) -Dec 08 19:39:06 meins 1 log_welcome: using loglevel 1 -Dec 08 19:39:06 meins 2 init_writers: maximal number of writers: 3 -Dec 08 19:39:06 meins 2 check_writer_arg: checking alsa -d plug:swmix -Dec 08 19:39:06 meins 2 alsa_parse_config: options: -d plug:swmix, 2 -Dec 08 19:39:06 meins 2 alsa_parse_config: help given: 0 -Dec 08 19:39:06 meins 2 init_writers: mp3 writer #0: alsa -Dec 08 19:39:06 meins 2 check_writer_arg: checking alsa -d plug:swmix -Dec 08 19:39:06 meins 2 alsa_parse_config: options: -d plug:swmix, 2 -Dec 08 19:39:06 meins 2 alsa_parse_config: help given: 0 -Dec 08 19:39:06 meins 2 init_writers: ogg writer #0: alsa -Dec 08 19:39:06 meins 2 check_writer_arg: checking alsa -d plug:swmix -Dec 08 19:39:06 meins 2 alsa_parse_config: options: -d plug:swmix, 2 -Dec 08 19:39:06 meins 2 alsa_parse_config: help given: 0 -Dec 08 19:39:06 meins 2 init_writers: aac writer #0: alsa -Dec 08 19:39:06 meins 2 init_receivers: initializing http receiver -Dec 08 19:39:06 meins 2 init_receivers: initializing dccp receiver -Dec 08 19:39:06 meins 2 init_receivers: initializing ortp receiver -Dec 08 19:39:06 meins 1 check_receiver_arg: checking http -i 127.0.0.1 -Dec 08 19:39:06 meins 1 parse_receiver_args: options: -i 127.0.0.1 -Dec 08 19:39:06 meins 1 parse_receiver_args: argc = 3, argv[0]: http -Dec 08 19:39:06 meins 1 check_receiver_arg: checking dccp -i localhost -Dec 08 19:39:06 meins 1 parse_receiver_args: options: -i localhost -Dec 08 19:39:06 meins 1 parse_receiver_args: argc = 3, argv[0]: dccp -Dec 08 19:39:06 meins 1 check_receiver_arg: checking dccp -i localhost -Dec 08 19:39:06 meins 1 parse_receiver_args: options: -i localhost -Dec 08 19:39:06 meins 1 parse_receiver_args: argc = 3, argv[0]: dccp -Dec 08 19:39:06 meins 2 init_filters: maximal number of filters: 6 -Dec 08 19:39:06 meins 2 add_filter: mp3 filter 1: mp3dec -Dec 08 19:39:06 meins 2 add_filter: mp3 filter 2: compress -Dec 08 19:39:06 meins 2 add_filter: ogg filter 1: oggdec -Dec 08 19:39:06 meins 2 add_filter: ogg filter 2: compress -Dec 08 19:39:06 meins 2 add_filter: aac filter 1: aacdec -Dec 08 19:39:06 meins 2 add_filter: aac filter 2: compress -Dec 08 19:39:06 meins 2 clear_slot: clearing slot 0 -Dec 08 19:39:06 meins 2 clear_slot: clearing slot 1 -Dec 08 19:39:06 meins 2 clear_slot: clearing slot 2 -Dec 08 19:39:06 meins 2 clear_slot: clearing slot 3 -Dec 08 19:39:06 meins 2 clear_slot: clearing slot 4 -Dec 08 19:39:06 meins 2 init_grabbing: grab init -Dec 08 19:39:06 meins 2 setup_signal_handling: signal pipe: fd 4 -Dec 08 19:39:06 meins 1 para_install_sighandler: catching signal 2 -Dec 08 19:39:06 meins 1 para_install_sighandler: catching signal 15 -Dec 08 19:39:06 meins 1 para_install_sighandler: catching signal 1 -Dec 08 19:39:06 meins 3 audiod_get_socket: local socket: /var/paraslash/audiod_socket.meins -Dec 08 19:39:06 meins 2 daemon_init: daemonizing -Dec 08 19:39:06 meins 2 init_sched: initializing scheduler -Dec 08 19:39:06 meins 2 register_task: registering signal task (0x80631bc) -Dec 08 19:39:06 meins 1 register_task: pre_select: 0x80631c0 -Dec 08 19:39:06 meins 1 register_task: post_select: 0x80631c0 -Dec 08 19:39:06 meins 2 register_task: registering command task (0xbfa7e274) -Dec 08 19:39:06 meins 1 register_task: pre_select: 0xbfa7e278 -Dec 08 19:39:06 meins 1 register_task: post_select: 0xbfa7e278 -Dec 08 19:39:06 meins 2 register_task: registering status task (0x80632e8) -Dec 08 19:39:06 meins 1 register_task: pre_select: 0x80632ec -Dec 08 19:39:06 meins 1 register_task: post_select: 0x80632ec -Dec 08 19:39:06 meins 2 register_task: registering audiod task (0xbfa7e398) -Dec 08 19:39:06 meins 1 register_task: pre_select: 0xbfa7e39c -Dec 08 19:39:06 meins 1 register_task: post_select: 0xbfa7e39c -Dec 08 19:39:06 meins 2 status_pre_select: clock diff count: 5 -Dec 08 19:39:06 meins 2 client_open: loglevel: 5 -Dec 08 19:39:06 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf -Dec 08 19:39:06 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan -Dec 08 19:39:06 meins 3 client_open: connecting localhost:2990 -Dec 08 19:39:06 meins 2 register_task: registering client (0x8067878) -Dec 08 19:39:06 meins 1 register_task: pre_select: 0x806787c -Dec 08 19:39:06 meins 1 register_task: post_select: 0x806787c -Dec 08 19:39:06 meins 2 client_post_select: --> auth rc4 maan -Dec 08 19:39:06 meins 2 client_post_select: <-- [challenge] -Dec 08 19:39:06 meins 2 client_post_select: --> 153889019 -Dec 08 19:39:06 meins 1 client_post_select: ++++ server info ++++ - -Proceed. - -++++ end of server info ++++ -Dec 08 19:39:06 meins 2 client_post_select: decrypting session key -Dec 08 19:39:06 meins 2 enable_crypt: rc4 encryption activated for fd 7 -Dec 08 19:39:06 meins 1 client_post_select: --> -stat -1 -End of Command. diff --git a/pics/screenshots/gui.png b/pics/screenshots/gui.png deleted file mode 100644 index 2a6c7443..00000000 Binary files a/pics/screenshots/gui.png and /dev/null differ diff --git a/pics/screenshots/server.log b/pics/screenshots/server.log deleted file mode 100644 index f84ede81..00000000 --- a/pics/screenshots/server.log +++ /dev/null @@ -1,192 +0,0 @@ -Dec 08 19:28:58 2: (21285) log_welcome: welcome to para_server git (Sat Dec 8 13:42:52 MET 2007) -Dec 08 19:28:58 1: (21285) log_welcome: using loglevel 1 -Dec 08 19:28:58 1: (21285) populate_user_list: found entry for maan -Dec 08 19:28:58 1: (21285) populate_user_list: found 4 perm entries -Dec 08 19:28:58 1: (21285) populate_user_list: found entry for install -Dec 08 19:28:58 1: (21285) populate_user_list: found 4 perm entries -Dec 08 19:28:58 1: (21285) populate_user_list: found entry for www -Dec 08 19:28:58 1: (21285) populate_user_list: found 4 perm entries -Dec 08 19:28:58 2: (21285) daemon_init: daemonizing -Dec 08 19:28:58 3: (21286) server_init: initializing audio format handlers -Dec 08 19:28:58 2: (21286) afh_init: supported audio formats: mp3 ogg aac -Dec 08 19:28:58 3: (21286) afh_init: initializing mp3 handler -Dec 08 19:28:58 3: (21286) afh_init: initializing ogg handler -Dec 08 19:28:58 3: (21286) afh_init: initializing aac handler -Dec 08 19:28:58 3: (21286) server_init: initializing virtual streaming system -Dec 08 19:28:58 2: (21286) vss_init: announce timeval: 300ms -Dec 08 19:28:58 3: (21286) vss_init: initializing http sender -Dec 08 19:28:58 2: (21286) para_listen: listening on TCP port 8000, fd 4 -Dec 08 19:28:58 1: (21286) http_send_init: http sender init complete -Dec 08 19:28:58 3: (21286) vss_init: initializing dccp sender -Dec 08 19:28:58 2: (21286) para_listen: listening on DCCP port 5001, fd 5 -Dec 08 19:28:58 3: (21286) vss_init: initializing ortp sender -Dec 08 19:28:58 1: (21286) ortp_send_init: ortp sender init complete -Dec 08 19:28:58 3: (21286) setup_signal_handling: setting up signal handlers -Dec 08 19:28:58 1: (21286) para_install_sighandler: catching signal 2 -Dec 08 19:28:58 1: (21286) para_install_sighandler: catching signal 15 -Dec 08 19:28:58 1: (21286) para_install_sighandler: catching signal 1 -Dec 08 19:28:58 1: (21286) para_install_sighandler: catching signal 17 -Dec 08 19:28:58 1: (21286) para_install_sighandler: catching signal 10 -Dec 08 19:28:58 3: (21286) server_init: initializing the audio file selector -Dec 08 19:28:58 2: (21287) get_database_dir: afs_database dir /home/maan/.paraslash/afs_database -Dec 08 19:28:58 3: (21287) open_afs_tables: opening 7 osl tables in /home/maan/.paraslash/afs_database -Dec 08 19:28:58 2: (21287) osl_open_table: opening table audio_files -Dec 08 19:28:58 1: (21287) init_table_structure: creating table structure for 'audio_files' from table description -Dec 08 19:28:58 1: (21287) init_table_structure: OK. Index entry size: 32 -Dec 08 19:28:58 1: (21287) map_table: mapping table 'audio_files' (index: /home/maan/.paraslash/afs_database/audio_files/index) -Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/audio_files/index: size 218667 -Dec 08 19:28:58 1: (21287) read_table_desc: 5 columns -Dec 08 19:28:58 1: (21287) compare_table_descriptions: table description of 'audio_files' matches on-disk data, good -Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/audio_files/2346ad27d7568ba9896f1b7da6b5991251debdf2: size 143430 -Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/audio_files/3150ecd5e0294534a81ae047ddac559de481d774: size 436636 -Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/audio_files/94ea39e309f3f31357ab60b190b6b8c32f21620b: size 225390 -Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/audio_files/b063ad096f9d142a388ca2a10d46b56904e26cda: size 1174409 -Dec 08 19:28:58 1: (21287) osl_open_table: num rows: 6830 -Dec 08 19:28:58 2: (21286) init_afs: afs_socket: 8, afs_socket_cookie: 1363241225 -Dec 08 19:28:58 3: (21286) server_init: initializing tcp command socket -Dec 08 19:28:58 2: (21286) para_listen: listening on TCP port 2990, fd 9 -Dec 08 19:28:58 3: (21286) server_init: server init complete -Dec 08 19:28:58 1: (21286) chk_barrier: autoplay_delay barrier: 14981ms left -Dec 08 19:28:58 1: (21286) status_refresh: 0 events, forcing status update -Dec 08 19:28:58 1: (21286) para_next_signal: next signal: 10 -Dec 08 19:28:58 1: (21286) chk_barrier: autoplay_delay barrier: 14981ms left -Dec 08 19:28:58 1: (21286) para_next_signal: next signal: 10 -Dec 08 19:28:58 1: (21286) chk_barrier: autoplay_delay barrier: 14981ms left -Dec 08 19:28:58 2: (21287) aft_open: audio file table contains 6830 files -Dec 08 19:28:58 2: (21287) osl_open_table: opening table attributes -Dec 08 19:28:58 1: (21287) init_table_structure: creating table structure for 'attributes' from table description -Dec 08 19:28:58 1: (21287) init_table_structure: OK. Index entry size: 16 -Dec 08 19:28:58 1: (21287) map_table: mapping table 'attributes' (index: /home/maan/.paraslash/afs_database/attributes/index) -Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/attributes/index: size 396 -Dec 08 19:28:58 1: (21287) read_table_desc: 2 columns -Dec 08 19:28:58 1: (21287) compare_table_descriptions: table description of 'attributes' matches on-disk data, good -Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/attributes/fd1e48caeff7212c45fc08608b7187feb10a7a2d: size 42 -Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/attributes/6ae999552a0d2dca14d62e2bc8b764d377b1dd6c: size 141 -Dec 08 19:28:58 1: (21287) osl_open_table: num rows: 21 -Dec 08 19:28:58 2: (21287) osl_open_table: opening table score -Dec 08 19:28:58 1: (21287) init_table_structure: creating table structure for 'score' from table description -Dec 08 19:28:58 1: (21287) init_table_structure: OK. Index entry size: 0 -Dec 08 19:28:58 1: (21287) osl_open_table: num rows: 0 -Dec 08 19:28:58 2: (21287) osl_open_table: opening table moods -Dec 08 19:28:58 1: (21287) init_table_structure: creating table structure for 'moods' from table description -Dec 08 19:28:58 1: (21287) init_table_structure: OK. Index entry size: 16 -Dec 08 19:28:58 1: (21287) map_table: mapping table 'moods' (index: /home/maan/.paraslash/afs_database/moods/index) -Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/moods/index: size 235 -Dec 08 19:28:58 1: (21287) read_table_desc: 3 columns -Dec 08 19:28:58 1: (21287) compare_table_descriptions: table description of 'moods' matches on-disk data, good -Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/moods/87ea5dfc8b8e384d848979496e706390b497e547: size 50 -Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/moods/6ae999552a0d2dca14d62e2bc8b764d377b1dd6c: size 67 -Dec 08 19:28:58 1: (21287) osl_open_table: num rows: 10 -Dec 08 19:28:58 2: (21287) osl_open_table: opening table lyrics -Dec 08 19:28:58 1: (21287) init_table_structure: creating table structure for 'lyrics' from table description -Dec 08 19:28:58 1: (21287) init_table_structure: OK. Index entry size: 16 -Dec 08 19:28:58 1: (21287) map_table: mapping table 'lyrics' (index: /home/maan/.paraslash/afs_database/lyrics/index) -Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/lyrics/index: size 347 -Dec 08 19:28:58 1: (21287) read_table_desc: 3 columns -Dec 08 19:28:58 1: (21287) compare_table_descriptions: table description of 'lyrics' matches on-disk data, good -Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/lyrics/87ea5dfc8b8e384d848979496e706390b497e547: size 85 -Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/lyrics/6ae999552a0d2dca14d62e2bc8b764d377b1dd6c: size 478 -Dec 08 19:28:58 1: (21287) osl_open_table: num rows: 17 -Dec 08 19:28:58 2: (21287) osl_open_table: opening table images -Dec 08 19:28:58 1: (21287) init_table_structure: creating table structure for 'images' from table description -Dec 08 19:28:58 1: (21287) init_table_structure: OK. Index entry size: 16 -Dec 08 19:28:58 1: (21287) map_table: mapping table 'images' (index: /home/maan/.paraslash/afs_database/images/index) -Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/images/index: size 7227 -Dec 08 19:28:58 1: (21287) read_table_desc: 3 columns -Dec 08 19:28:58 1: (21287) compare_table_descriptions: table description of 'images' matches on-disk data, good -Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/images/87ea5dfc8b8e384d848979496e706390b497e547: size 2235 -Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/images/6ae999552a0d2dca14d62e2bc8b764d377b1dd6c: size 12950 -Dec 08 19:28:58 1: (21287) osl_open_table: num rows: 447 -Dec 08 19:28:58 2: (21287) osl_open_table: opening table playlists -Dec 08 19:28:58 1: (21287) init_table_structure: creating table structure for 'playlists' from table description -Dec 08 19:28:58 1: (21287) init_table_structure: OK. Index entry size: 16 -Dec 08 19:28:58 1: (21287) map_table: mapping table 'playlists' (index: /home/maan/.paraslash/afs_database/playlists/index) -Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/playlists/index: size 107 -Dec 08 19:28:58 1: (21287) read_table_desc: 3 columns -Dec 08 19:28:58 1: (21287) compare_table_descriptions: table description of 'playlists' matches on-disk data, good -Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/playlists/87ea5dfc8b8e384d848979496e706390b497e547: size 10 -Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/playlists/6ae999552a0d2dca14d62e2bc8b764d377b1dd6c: size 7 -Dec 08 19:28:58 1: (21287) osl_open_table: num rows: 2 -Dec 08 19:28:58 2: (21287) afs_init: server_socket: 9, afs_socket_cookie: 1363241225 -Dec 08 19:28:58 1: (21287) osl_open_disk_object: filename: /home/maan/.paraslash/afs_database/moods/f3f1dd33eb2a8b380b64a830e5fd90eab77d9ff3/9d/b063f3b5e0adfd0d29a03db0a1c207b3740a94 -Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/moods/f3f1dd33eb2a8b380b64a830e5fd90eab77d9ff3/9d/b063f3b5e0adfd0d29a03db0a1c207b3740a94: size 31 -Dec 08 19:28:58 1: (21287) parse_mood_line: accept entry added, method: 0x806c0f4 -Dec 08 19:28:58 3: (21287) change_current_mood: computing statistics of admissible files -Dec 08 19:28:58 2: (21287) log_statistics: last_played mean: 1193159581, last_played sigma: 4161524 -Dec 08 19:28:58 2: (21287) log_statistics: num_played mean: 32, num_played sigma: 21 -Dec 08 19:28:58 2: (21287) change_current_mood: 26 admissible files -Dec 08 19:28:58 3: (21287) change_current_mood: loaded mood gulp -Dec 08 19:28:58 2: (21287) register_signal_task: signal pipe: fd 8 -Dec 08 19:28:58 1: (21287) para_install_sighandler: catching signal 2 -Dec 08 19:28:58 1: (21287) para_install_sighandler: catching signal 15 -Dec 08 19:28:58 1: (21287) para_install_sighandler: catching signal 13 -Dec 08 19:28:58 1: (21287) para_install_sighandler: catching signal 1 -Dec 08 19:28:58 2: (21287) init_sched: initializing scheduler -Dec 08 19:28:58 2: (21287) register_task: registering signal task (0x806f244) -Dec 08 19:28:58 1: (21287) register_task: pre_select: 0x806f248 -Dec 08 19:28:58 1: (21287) register_task: post_select: 0x806f248 -Dec 08 19:28:58 2: (21287) setup_command_socket_or_die: listening on socket /var/paraslash/afs_command_socket (fd 1) -Dec 08 19:28:58 2: (21287) register_task: registering command task (0x806f118) -Dec 08 19:28:58 1: (21287) register_task: pre_select: 0x806f11c -Dec 08 19:28:58 1: (21287) register_task: post_select: 0x806f11c -Dec 08 19:28:59 2: (21286) main: got connection from ::ffff:127.0.0.1#42344, forking -Dec 08 19:28:59 1: (21356) close_listed_fds: closing fd 9 -Dec 08 19:28:59 1: (21356) close_listed_fds: closing fd 8 -Dec 08 19:28:59 1: (21356) close_listed_fds: closing fd 6 -Dec 08 19:28:59 1: (21356) close_listed_fds: closing fd 5 -Dec 08 19:28:59 1: (21356) close_listed_fds: closing fd 4 -Dec 08 19:28:59 1: (21286) chk_barrier: autoplay_delay barrier: 13507ms left -Dec 08 19:28:59 1: (21356) handle_connect: received rc4 request for user maan -Dec 08 19:28:59 1: (21356) handle_connect: sending 64 byte challenge -Dec 08 19:28:59 2: (21356) handle_connect: good auth for maan (1081806111) -Dec 08 19:28:59 1: (21356) init_rc4_keys: rc4 keys initialized (84:105) -Dec 08 19:28:59 2: (21356) enable_crypt: rc4 encryption activated for fd 10 -Dec 08 19:28:59 3: (21356) handle_connect: invalid command -Dec 08 19:28:59 1: (21286) para_next_signal: next signal: 17 -Dec 08 19:28:59 1: (21286) para_reap_child: child 21356 exited. Exit status: 1 -Dec 08 19:28:59 1: (21286) chk_barrier: autoplay_delay barrier: 13498ms left -Dec 08 19:29:03 2: (21286) main: got connection from ::ffff:127.0.0.1#42345, forking -Dec 08 19:29:03 1: (21362) close_listed_fds: closing fd 9 -Dec 08 19:29:03 1: (21362) close_listed_fds: closing fd 8 -Dec 08 19:29:03 1: (21362) close_listed_fds: closing fd 6 -Dec 08 19:29:03 1: (21362) close_listed_fds: closing fd 5 -Dec 08 19:29:03 1: (21362) close_listed_fds: closing fd 4 -Dec 08 19:29:03 1: (21286) chk_barrier: autoplay_delay barrier: 9951ms left -Dec 08 19:29:03 1: (21362) handle_connect: received rc4 request for user maan -Dec 08 19:29:03 1: (21362) handle_connect: sending 64 byte challenge -Dec 08 19:29:03 2: (21362) handle_connect: good auth for maan (705434988) -Dec 08 19:29:03 1: (21362) init_rc4_keys: rc4 keys initialized (18:65) -Dec 08 19:29:03 2: (21362) enable_crypt: rc4 encryption activated for fd 10 -Dec 08 19:29:03 1: (21362) check_perms: checking permissions -Dec 08 19:29:03 3: (21362) handle_connect: calling com_stat() for maan@::ffff:127.0.0.1#42345 -Dec 08 19:29:13 1: (21286) chk_barrier: autoplay_delay barrier: 3ms left -Dec 08 19:29:13 1: (21286) vss_preselect: ready and playing, but no audio file -Dec 08 19:29:13 3: (21286) vss_post_select: requesting new fd from afs -Dec 08 19:29:13 1: (21287) execute_server_command: received: new -Dec 08 19:29:13 3: (21287) open_next_audio_file: getting next audio file -Dec 08 19:29:13 1: (21287) osl_open_disk_object: filename: /home/maan/.paraslash/afs_database/audio_files/7e078c9876ccabef154017c770e05195c85b5e4d/55/b5e38d467105bd88133cf5ded70e551e582593 -Dec 08 19:29:13 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/audio_files/7e078c9876ccabef154017c770e05195c85b5e4d/55/b5e38d467105bd88133cf5ded70e551e582593: size 4532 -Dec 08 19:29:13 1: (21287) mmap_full_file: /home/mp3/checked/dvd_07/cd_46/The_G.U.L.P.__Scheiss_Krieg.ogg: size 5274482 -Dec 08 19:29:13 1: (21287) mood_update_audio_file: score: 6 -Dec 08 19:29:13 1: (21287) mood_update_audio_file: moving from rank 26 to 35% -Dec 08 19:29:13 1: (21287) score_update: new score: 0, rank 9/26 -Dec 08 19:29:13 1: (21287) osl_update_object: updating column 1 of score -Dec 08 19:29:13 1: (21287) score_update: new score: -65, rank 1/26 -Dec 08 19:29:13 1: (21287) osl_update_object: updating column 1 of score -Dec 08 19:29:13 1: (21287) save_afd: size: 8932 -Dec 08 19:29:13 1: (21287) pass_afd: passing 8 bytes and fd 12 -Dec 08 19:29:13 1: (21286) recv_afs_result: fd: 10, code: 0, shmid: 28442631 -Dec 08 19:29:13 1: (21286) chk_barrier: data send barrier: 300ms left -Dec 08 19:29:13 1: (21286) status_refresh: 1 events, forcing status update -Dec 08 19:29:13 1: (21286) chk_barrier: data send barrier: 300ms left -Dec 08 19:29:13 1: (21286) chk_barrier: data send barrier: 300ms left -Dec 08 19:29:13 1: (21286) para_next_signal: next signal: 10 -Dec 08 19:29:13 1: (21286) chk_barrier: data send barrier: 300ms left -Dec 08 19:29:13 3: (21286) dccp_post_select: connection from ::ffff:127.0.0.1#46539 -Dec 08 19:29:13 1: (21286) chk_barrier: data send barrier: 287ms left -Dec 08 19:29:13 1: (21286) chk_barrier: data send barrier: 287ms left -Dec 08 19:29:13 1: (21287) para_next_signal: next signal: 10 -Dec 08 19:29:13 1: (21286) status_refresh: 2 events, forcing status update -Dec 08 19:29:13 1: (21286) para_next_signal: next signal: 10 -Dec 08 19:29:13 1: (21287) para_next_signal: next signal: 10 -Dec 08 19:29:14 1: (21286) cq_enqueue: 4446 bytes queued for 0x8072eb0 diff --git a/pics/web/paraslash.ico b/pics/web/paraslash.ico deleted file mode 100644 index 85ce9be1..00000000 Binary files a/pics/web/paraslash.ico and /dev/null differ diff --git a/pics/web/paraslash.png b/pics/web/paraslash.png deleted file mode 100644 index 744ce0ac..00000000 Binary files a/pics/web/paraslash.png and /dev/null differ diff --git a/playlist.c b/playlist.c index a9925cd3..2d2f23b4 100644 --- a/playlist.c +++ b/playlist.c @@ -1,9 +1,12 @@ /* - * Copyright (C) 2007-2009 Andre Noll + * Copyright (C) 2007-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ +#include +#include + #include "para.h" #include "error.h" #include "string.h" @@ -174,7 +177,7 @@ int playlist_open(char *name) obj.data = name; obj.size = strlen(obj.data); - ret = osl_get_row(playlists_table, BLOBCOL_NAME, &obj, &row); + ret = osl(osl_get_row(playlists_table, BLOBCOL_NAME, &obj, &row)); if (ret < 0) { PARA_NOTICE_LOG("failed to load playlist %s\n", name); return ret; diff --git a/portable_io.h b/portable_io.h index c2ed8afc..e766d7bc 100644 --- a/portable_io.h +++ b/portable_io.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007-2009 Andre Noll + * Copyright (C) 2007-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ diff --git a/prebuffer_filter.c b/prebuffer_filter.c index 1ad5f446..e1b0390c 100644 --- a/prebuffer_filter.c +++ b/prebuffer_filter.c @@ -1,16 +1,20 @@ /* - * Copyright (C) 2009 Andre Noll + * Copyright (C) 2009-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file prebuffer_filter.c Paraslash's prebuffering filter. */ +#include +#include + #include "para.h" #include "prebuffer_filter.cmdline.h" #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "filter.h" #include "string.h" #include "error.h" @@ -25,56 +29,52 @@ struct private_prebuffer_data { struct timeval barrier; }; -static ssize_t prebuffer_convert(char *inbuf, size_t inbuf_len, - struct filter_node *fn) +static void prebuffer_pre_select(struct sched *s, struct task *t) { + struct filter_node *fn = container_of(t, struct filter_node, task); + struct btr_node *btrn = fn->btrn; + size_t iqs = btr_get_input_queue_size(btrn); struct private_prebuffer_data *ppd = fn->private_data; struct prebuffer_filter_args_info *conf = ppd->conf; + struct timeval diff; - if (inbuf_len == 0) { - if (*fn->fc->input_error < 0 && ppd->prebuffered >= 0) - goto prebuffer_end; - return 0; - } - if (ppd->prebuffered < 0) { - size_t copy = PARA_MIN(inbuf_len, fn->bufsize - fn->loaded); - memcpy(fn->buf + fn->loaded, inbuf, copy); - fn->loaded += copy; - return copy; - } - if (ppd->prebuffered + inbuf_len > fn->bufsize) { - fn->bufsize = PARA_MAX(2 * fn->bufsize, - ppd->prebuffered + inbuf_len); - fn->buf = para_realloc(fn->buf, fn->bufsize); - } - memcpy(fn->buf + ppd->prebuffered, inbuf, inbuf_len); - if (ppd->prebuffered == 0) { + t->error = 0; + if (iqs == 0) + return; + if (ppd->barrier.tv_sec == 0) { struct timeval tv; PARA_INFO_LOG("prebuffer period %dms\n", conf->duration_arg); ms2tv(conf->duration_arg, &tv); tv_add(&tv, now, &ppd->barrier); } - ppd->prebuffered += inbuf_len; - PARA_DEBUG_LOG("%d bytes prebuffered\n", ppd->prebuffered); - if (*fn->fc->input_error >= 0) { - struct timeval diff; - if (tv_diff(now, &ppd->barrier, &diff) < 0) - goto out; - if (ppd->prebuffered < conf->size_arg) - goto out; - } -prebuffer_end: - fn->loaded = ppd->prebuffered; - ppd->prebuffered = -1; -out: - return inbuf_len; + if (tv_diff(&ppd->barrier, now, &diff) < 0) + return sched_min_delay(s); + sched_request_timeout(&diff, s); } static void prebuffer_close(struct filter_node *fn) { free(fn->private_data); - free(fn->buf); +} + +static void prebuffer_post_select(__a_unused struct sched *s, struct task *t) +{ + struct filter_node *fn = container_of(t, struct filter_node, task); + struct btr_node *btrn = fn->btrn; + size_t iqs = btr_get_input_queue_size(btrn); + struct private_prebuffer_data *ppd = fn->private_data; + struct prebuffer_filter_args_info *conf = ppd->conf; + + t->error = 0; + if (ppd->barrier.tv_sec == 0) + return; + if (tv_diff(now, &ppd->barrier, NULL) < 0) + return; + if (iqs < conf->size_arg) + return; + btr_splice_out_node(btrn); + t->error = -E_PREBUFFER_SUCCESS; } static int prebuffer_parse_config(int argc, char **argv, void **config) @@ -105,8 +105,11 @@ static void prebuffer_open(struct filter_node *fn) ppd->conf = fn->conf; fn->private_data = ppd; - fn->bufsize = 8192; /* gets increased on demand */ - fn->buf = para_malloc(fn->bufsize); +} + +static void prebuffer_free_config(void *conf) +{ + prebuffer_cmdline_parser_free(conf); } /** @@ -121,8 +124,10 @@ void prebuffer_filter_init(struct filter *f) prebuffer_cmdline_parser_init(&dummy); f->open = prebuffer_open; f->close = prebuffer_close; - f->convert = prebuffer_convert; f->parse_config = prebuffer_parse_config; + f->free_config = prebuffer_free_config; + f->pre_select = prebuffer_pre_select; + f->post_select = prebuffer_post_select; f->help = (struct ggo_help) { .short_help = prebuffer_filter_args_info_help, .detailed_help = prebuffer_filter_args_info_detailed_help diff --git a/rbtree.c b/rbtree.c deleted file mode 100644 index 7f200d1d..00000000 --- a/rbtree.c +++ /dev/null @@ -1,457 +0,0 @@ -/* - Red Black Trees - (C) 1999 Andrea Arcangeli - (C) 2002 David Woodhouse - (C) 2007 Andre Noll - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - linux/lib/rbtree.c -*/ - -/** \file rbtree.c Red-black tree implementation. */ - -#include "stddef.h" -#include "rbtree.h" - -static void __rb_rotate_left(struct rb_node *node, struct rb_root *root) -{ - struct rb_node *right = node->rb_right; - struct rb_node *parent = rb_parent(node); - - if ((node->rb_right = right->rb_left)) - rb_set_parent(right->rb_left, node); - right->rb_left = node; - - rb_set_parent(right, parent); - - if (parent) - { - if (node == parent->rb_left) - parent->rb_left = right; - else - parent->rb_right = right; - } - else - root->rb_node = right; - rb_set_parent(node, right); - right->size = node->size; - node->size = 1; - if (node->rb_right) - node->size += node->rb_right->size; - if (node->rb_left) - node->size += node->rb_left->size; -} - -static void __rb_rotate_right(struct rb_node *node, struct rb_root *root) -{ - struct rb_node *left = node->rb_left; - struct rb_node *parent = rb_parent(node); - - if ((node->rb_left = left->rb_right)) - rb_set_parent(left->rb_right, node); - left->rb_right = node; - - rb_set_parent(left, parent); - - if (parent) - { - if (node == parent->rb_right) - parent->rb_right = left; - else - parent->rb_left = left; - } - else - root->rb_node = left; - rb_set_parent(node, left); - left->size = node->size; - node->size = 1; - if (node->rb_right) - node->size += node->rb_right->size; - if (node->rb_left) - node->size += node->rb_left->size; -} - -void rb_insert_color(struct rb_node *node, struct rb_root *root) -{ - struct rb_node *parent, *gparent; - - while ((parent = rb_parent(node)) && rb_is_red(parent)) - { - gparent = rb_parent(parent); - - if (parent == gparent->rb_left) - { - { - register struct rb_node *uncle = gparent->rb_right; - if (uncle && rb_is_red(uncle)) - { - rb_set_black(uncle); - rb_set_black(parent); - rb_set_red(gparent); - node = gparent; - continue; - } - } - - if (parent->rb_right == node) - { - register struct rb_node *tmp; - __rb_rotate_left(parent, root); - tmp = parent; - parent = node; - node = tmp; - } - - rb_set_black(parent); - rb_set_red(gparent); - __rb_rotate_right(gparent, root); - } else { - { - register struct rb_node *uncle = gparent->rb_left; - if (uncle && rb_is_red(uncle)) - { - rb_set_black(uncle); - rb_set_black(parent); - rb_set_red(gparent); - node = gparent; - continue; - } - } - - if (parent->rb_left == node) - { - register struct rb_node *tmp; - __rb_rotate_right(parent, root); - tmp = parent; - parent = node; - node = tmp; - } - - rb_set_black(parent); - rb_set_red(gparent); - __rb_rotate_left(gparent, root); - } - } - - rb_set_black(root->rb_node); -} - -static void __rb_erase_color(struct rb_node *node, struct rb_node *parent, - struct rb_root *root) -{ - struct rb_node *other; - - while ((!node || rb_is_black(node)) && node != root->rb_node) - { - if (parent->rb_left == node) - { - other = parent->rb_right; - if (rb_is_red(other)) - { - rb_set_black(other); - rb_set_red(parent); - __rb_rotate_left(parent, root); - other = parent->rb_right; - } - if ((!other->rb_left || rb_is_black(other->rb_left)) && - (!other->rb_right || rb_is_black(other->rb_right))) - { - rb_set_red(other); - node = parent; - parent = rb_parent(node); - } - else - { - if (!other->rb_right || rb_is_black(other->rb_right)) - { - struct rb_node *o_left; - if ((o_left = other->rb_left)) - rb_set_black(o_left); - rb_set_red(other); - __rb_rotate_right(other, root); - other = parent->rb_right; - } - rb_set_color(other, rb_color(parent)); - rb_set_black(parent); - if (other->rb_right) - rb_set_black(other->rb_right); - __rb_rotate_left(parent, root); - node = root->rb_node; - break; - } - } - else - { - other = parent->rb_left; - if (rb_is_red(other)) - { - rb_set_black(other); - rb_set_red(parent); - __rb_rotate_right(parent, root); - other = parent->rb_left; - } - if ((!other->rb_left || rb_is_black(other->rb_left)) && - (!other->rb_right || rb_is_black(other->rb_right))) - { - rb_set_red(other); - node = parent; - parent = rb_parent(node); - } - else - { - if (!other->rb_left || rb_is_black(other->rb_left)) - { - register struct rb_node *o_right; - if ((o_right = other->rb_right)) - rb_set_black(o_right); - rb_set_red(other); - __rb_rotate_left(other, root); - other = parent->rb_left; - } - rb_set_color(other, rb_color(parent)); - rb_set_black(parent); - if (other->rb_left) - rb_set_black(other->rb_left); - __rb_rotate_right(parent, root); - node = root->rb_node; - break; - } - } - } - if (node) - rb_set_black(node); -} - -void rb_erase(struct rb_node *node, struct rb_root *root) -{ - struct rb_node *child, *parent; - int color; - - if (!node->rb_left) - child = node->rb_right; - else if (!node->rb_right) - child = node->rb_left; - else - { - struct rb_node *old = node, *left; - - node = node->rb_right; - while ((left = node->rb_left) != NULL) - node = left; - child = node->rb_right; - parent = rb_parent(node); - color = rb_color(node); - - if (child) - rb_set_parent(child, parent); - if (parent == old) { - parent->rb_right = child; - parent = node; - } else - parent->rb_left = child; - - node->rb_parent_color = old->rb_parent_color; - node->rb_right = old->rb_right; - node->rb_left = old->rb_left; - node->size = old->size; - - if (rb_parent(old)) - { - if (rb_parent(old)->rb_left == old) - rb_parent(old)->rb_left = node; - else - rb_parent(old)->rb_right = node; - } else - root->rb_node = node; - - rb_set_parent(old->rb_left, node); - if (old->rb_right) - rb_set_parent(old->rb_right, node); - goto color; - } - - parent = rb_parent(node); - color = rb_color(node); - - if (child) - rb_set_parent(child, parent); - if (parent) - { - if (parent->rb_left == node) - parent->rb_left = child; - else - parent->rb_right = child; - } - else - root->rb_node = child; - - color: - if (color == RB_BLACK) - __rb_erase_color(child, parent, root); -} - -/* - * This function returns the first node (in sort order) of the tree. - */ -struct rb_node *rb_first(const struct rb_root *root) -{ - struct rb_node *n; - - n = root->rb_node; - if (!n) - return NULL; - while (n->rb_left) - n = n->rb_left; - return n; -} - -struct rb_node *rb_last(const struct rb_root *root) -{ - struct rb_node *n; - - n = root->rb_node; - if (!n) - return NULL; - while (n->rb_right) - n = n->rb_right; - return n; -} - -struct rb_node *rb_next(const struct rb_node *node) -{ - struct rb_node *parent; - - if (rb_parent(node) == node) - return NULL; - - /* If we have a right-hand child, go down and then left as far - as we can. */ - if (node->rb_right) { - node = node->rb_right; - while (node->rb_left) - node=node->rb_left; - return (struct rb_node *)node; - } - - /* No right-hand children. Everything down and left is - smaller than us, so any 'next' node must be in the general - direction of our parent. Go up the tree; any time the - ancestor is a right-hand child of its parent, keep going - up. First time it's a left-hand child of its parent, said - parent is our 'next' node. */ - while ((parent = rb_parent(node)) && node == parent->rb_right) - node = parent; - - return parent; -} - -struct rb_node *rb_prev(const struct rb_node *node) -{ - struct rb_node *parent; - - if (rb_parent(node) == node) - return NULL; - - /* If we have a left-hand child, go down and then right as far - as we can. */ - if (node->rb_left) { - node = node->rb_left; - while (node->rb_right) - node=node->rb_right; - return (struct rb_node *)node; - } - - /* No left-hand children. Go up till we find an ancestor which - is a right-hand child of its parent */ - while ((parent = rb_parent(node)) && node == parent->rb_left) - node = parent; - - return parent; -} - -void rb_replace_node(struct rb_node *victim, struct rb_node *new, - struct rb_root *root) -{ - struct rb_node *parent = rb_parent(victim); - - /* Set the surrounding nodes to point to the replacement */ - if (parent) { - if (victim == parent->rb_left) - parent->rb_left = new; - else - parent->rb_right = new; - } else { - root->rb_node = new; - } - if (victim->rb_left) - rb_set_parent(victim->rb_left, new); - if (victim->rb_right) - rb_set_parent(victim->rb_right, new); - - /* Copy the pointers/colour from the victim to the replacement */ - *new = *victim; -} - -/** - * Get the n-th node (in sort order) of the tree. - * - * \param node The root of the subtree to consider. - * \param n The order statistic to compute. - * - * \return Pointer to the \a n th greatest node on success, \p NULL on errors. - */ -struct rb_node *rb_nth(const struct rb_node *node, unsigned n) -{ - unsigned size = 1; - - if (!node) - return NULL; - if (node->rb_left) - size += node->rb_left->size; - if (n == size) - return (struct rb_node *)node; - if (n < size) - return rb_nth(node->rb_left, n); - return rb_nth(node->rb_right, n - size); -} - -/** - * Get the rank of a node in O(log n) time. - * - * \param node The node to get the rank of. - * \param rank Result pointer. - * - * \return Positive on success, -1 on errors. - */ -int rb_rank(const struct rb_node *node, unsigned *rank) -{ - struct rb_node *parent; - - *rank = 1; - if (!node) - return -1; - if (node->rb_left) - *rank += node->rb_left->size; - while ((parent = rb_parent(node))) { - if (node == parent->rb_right) { - (*rank)++; - if (parent->rb_left) - *rank += parent->rb_left->size; - } - node = parent; - } - return 1; -} diff --git a/rbtree.h b/rbtree.h deleted file mode 100644 index 8295d2ad..00000000 --- a/rbtree.h +++ /dev/null @@ -1,175 +0,0 @@ -/* - Red Black Trees - (C) 1999 Andrea Arcangeli - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - - linux/include/linux/rbtree.h - - To use rbtrees you'll have to implement your own insert and search cores. - This will avoid us to use callbacks and to drop drammatically performances. - I know it's not the cleaner way, but in C (not in C++) to get - performances and genericity... - - Some example of insert and search follows here. The search is a plain - normal search over an ordered tree. The insert instead must be implemented - int two steps: as first thing the code must insert the element in - order as a red leaf in the tree, then the support library function - rb_insert_color() must be called. Such function will do the - not trivial work to rebalance the rbtree if necessary. - ------------------------------------------------------------------------ -static inline struct page * rb_search_page_cache(struct inode * inode, - unsigned long offset) -{ - struct rb_node * n = inode->i_rb_page_cache.rb_node; - struct page * page; - - while (n) - { - page = rb_entry(n, struct page, rb_page_cache); - - if (offset < page->offset) - n = n->rb_left; - else if (offset > page->offset) - n = n->rb_right; - else - return page; - } - return NULL; -} - -static inline struct page * __rb_insert_page_cache(struct inode * inode, - unsigned long offset, - struct rb_node * node) -{ - struct rb_node ** p = &inode->i_rb_page_cache.rb_node; - struct rb_node * parent = NULL; - struct page * page; - - while (*p) - { - parent = *p; - page = rb_entry(parent, struct page, rb_page_cache); - - if (offset < page->offset) - p = &(*p)->rb_left; - else if (offset > page->offset) - p = &(*p)->rb_right; - else - return page; - } - - rb_link_node(node, parent, p); - - return NULL; -} - -static inline struct page * rb_insert_page_cache(struct inode * inode, - unsigned long offset, - struct rb_node * node) -{ - struct page * ret; - if ((ret = __rb_insert_page_cache(inode, offset, node))) - goto out; - rb_insert_color(node, &inode->i_rb_page_cache); - out: - return ret; -} ------------------------------------------------------------------------ -*/ - -/** \file rbtree.h Exported symbols from rbtree.h */ - -#ifndef _LINUX_RBTREE_H -#define _LINUX_RBTREE_H - - -/** get the struct this entry is embedded in */ -#define container_of(ptr, type, member) ({ \ - const typeof( ((type *)0)->member ) *__mptr = (ptr); \ - (type *)( (char *)__mptr - offsetof(type,member) );}) - - -struct rb_node -{ - unsigned long rb_parent_color; -#define RB_RED 0 -#define RB_BLACK 1 - struct rb_node *rb_right; - struct rb_node *rb_left; - unsigned size; -} __attribute__((aligned(sizeof(long)))); - /* The alignment might seem pointless, but allegedly CRIS needs it */ - -struct rb_root -{ - struct rb_node *rb_node; -}; - - -#define rb_parent(r) ((struct rb_node *)((r)->rb_parent_color & ~3)) -#define rb_color(r) ((r)->rb_parent_color & 1) -#define rb_is_red(r) (!rb_color(r)) -#define rb_is_black(r) rb_color(r) -#define rb_set_red(r) do { (r)->rb_parent_color &= ~1; } while (0) -#define rb_set_black(r) do { (r)->rb_parent_color |= 1; } while (0) - -static inline void rb_set_parent(struct rb_node *rb, struct rb_node *p) -{ - rb->rb_parent_color = (rb->rb_parent_color & 3) | (unsigned long)p; -} -static inline void rb_set_color(struct rb_node *rb, int color) -{ - rb->rb_parent_color = (rb->rb_parent_color & ~1) | color; -} - -#define RB_ROOT (struct rb_root) { NULL, } -#define rb_entry(ptr, type, member) container_of(ptr, type, member) - -#define RB_EMPTY_ROOT(root) ((root)->rb_node == NULL) -#define RB_EMPTY_NODE(node) (rb_parent(node) == node) -#define RB_CLEAR_NODE(node) (rb_set_parent(node, node)) - -extern void rb_insert_color(struct rb_node *, struct rb_root *); -extern void rb_erase(struct rb_node *, struct rb_root *); - -/* Find logical next and previous nodes in a tree */ -extern struct rb_node *rb_next(const struct rb_node *); -extern struct rb_node *rb_prev(const struct rb_node *); -extern struct rb_node *rb_first(const struct rb_root *); -extern struct rb_node *rb_last(const struct rb_root *); - -extern struct rb_node *rb_nth(const struct rb_node *node, unsigned n); -extern int rb_rank(const struct rb_node *node, unsigned *rank); - -/* Fast replacement of a single node without remove/rebalance/add/rebalance */ -extern void rb_replace_node(struct rb_node *victim, struct rb_node *new, - struct rb_root *root); - -static inline void rb_link_node(struct rb_node * node, struct rb_node * parent, - struct rb_node ** rb_link) -{ - node->size = 1; - node->rb_parent_color = (unsigned long )parent; - node->rb_left = node->rb_right = NULL; - - *rb_link = node; - /* Fixup the size fields in the tree */ - while ((node = rb_parent(node))) - node->size++; -} - -#endif /* _LINUX_RBTREE_H */ diff --git a/rc4.h b/rc4.h index 9ddd8224..1815e3b8 100644 --- a/rc4.h +++ b/rc4.h @@ -1,4 +1,4 @@ /** \file rc4.h common symbols of command.c and client_common.c */ -/** number of bytes of the rc4 session key */ -#define RC4_KEY_LEN 16 +/** Number of bytes of the rc4 session key. */ +#define RC4_KEY_LEN 32 diff --git a/recv.c b/recv.c index fc34279a..a737e4cb 100644 --- a/recv.c +++ b/recv.c @@ -1,13 +1,15 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file recv.c the stand-alone audio stream receiver */ +#include #include #include +#include #include "para.h" #include "list.h" @@ -19,9 +21,10 @@ #include "string.h" #include "error.h" #include "stdout.h" +#include "buffer_tree.h" -/** the gengetopt args info struct */ -struct recv_args_info conf; +/** The gengetopt args info struct. */ +static struct recv_args_info conf; static int loglevel; /** Always log to stderr. */ @@ -57,7 +60,7 @@ static void *parse_config(int argc, char *argv[], int *receiver_num) } /** - * the main function of para_recv + * The main function of para_recv. * * \param argc number of arguments * \param argv vector of arguments @@ -89,28 +92,31 @@ int main(int argc, char *argv[]) } r = &receivers[receiver_num]; rn.receiver = r; + rn.btrn = btr_new_node(&(struct btr_node_description) + EMBRACE(.name = r->name)); ret = r->open(&rn); if (ret < 0) goto out; r_opened = 1; + sot.btrn = btr_new_node(&(struct btr_node_description) + EMBRACE(.parent = rn.btrn, .name = "stdout")); stdout_set_defaults(&sot); - sot.bufp = &rn.buf; - sot.loaded = &rn.loaded; - sot.input_error = &rn.task.error; register_task(&sot.task); rn.task.pre_select = r->pre_select; rn.task.post_select = r->post_select; - sprintf(rn.task.status, "receiver node"); + sprintf(rn.task.status, "%s", r->name); register_task(&rn.task); ret = schedule(&s); out: if (r_opened) r->close(&rn); - if (r) - r->shutdown(); + btr_free_node(rn.btrn); + btr_free_node(sot.btrn); + if (rn.conf) + r->free_config(rn.conf); if (ret < 0) PARA_ERROR_LOG("%s\n", para_strerror(-ret)); return ret < 0? EXIT_FAILURE : EXIT_SUCCESS; diff --git a/recv.h b/recv.h index dc49ef95..05c5ed76 100644 --- a/recv.h +++ b/recv.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -12,18 +12,14 @@ struct receiver_node { /** Points to the corresponding receiver. */ struct receiver *receiver; - /** The output buffer. */ - char *buf; - /** The amount of bytes in \a buf. */ - size_t loaded; /** Receiver-specific data. */ void *private_data; - /** Pointer to the error member of the consumer. */ - int *output_error; /** Pointer to the configuration data for this instance. */ void *conf; /** The task associated with this instance. */ struct task task; + /** The receiver node is always the root of the buffer tree. */ + struct btr_node *btrn; }; /** @@ -54,6 +50,7 @@ struct receiver { * \a argc and \a argv. */ void *(*parse_config)(int argc, char **argv); + void (*free_config)(void *conf); /** * Open one instance of the receiver. * @@ -75,12 +72,6 @@ struct receiver { * \sa receiver_node. */ void (*close)(struct receiver_node *rn); - /** - * Deactivate the receiver. - * - * Clean up what init has allocated. - */ - void (*shutdown)(void); /** * Add file descriptors to fd_sets and compute timeout for select(2). * @@ -109,18 +100,6 @@ struct receiver { struct ggo_help help; }; - -/** \cond */ -extern void http_recv_init(struct receiver *r); -#define HTTP_RECEIVER {.name = "http", .init = http_recv_init}, -extern void dccp_recv_init(struct receiver *r); -#define DCCP_RECEIVER {.name = "dccp", .init = dccp_recv_init}, -extern void udp_recv_init(struct receiver *r); -#define UDP_RECEIVER {.name = "udp", .init = udp_recv_init}, - -extern struct receiver receivers[]; -/** \endcond */ - /** Define an array of all available receivers. */ #define DEFINE_RECEIVER_ARRAY struct receiver receivers[] = { \ HTTP_RECEIVER \ @@ -134,3 +113,16 @@ extern struct receiver receivers[]; void recv_init(void); void *check_receiver_arg(char *ra, int *receiver_num); void print_receiver_helps(int detailed); +int generic_recv_pre_select(struct sched *s, struct task *t); + +/** \cond */ +extern void http_recv_init(struct receiver *r); +#define HTTP_RECEIVER {.name = "http", .init = http_recv_init}, +extern void dccp_recv_init(struct receiver *r); +#define DCCP_RECEIVER {.name = "dccp", .init = dccp_recv_init}, +extern void udp_recv_init(struct receiver *r); +#define UDP_RECEIVER {.name = "udp", .init = udp_recv_init}, + +extern struct receiver receivers[]; +/** \endcond */ + diff --git a/recv_common.c b/recv_common.c index 00c1dd84..3fe2a139 100644 --- a/recv_common.c +++ b/recv_common.c @@ -1,18 +1,21 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file recv_common.c common functions of para_recv and para_audiod */ -#include "para.h" +#include +#include +#include "para.h" #include "list.h" #include "sched.h" #include "ggo.h" #include "recv.h" #include "string.h" +#include "buffer_tree.h" DEFINE_RECEIVER_ARRAY; @@ -35,14 +38,16 @@ static void *parse_receiver_args(int receiver_num, char *options) void *conf; -// PARA_DEBUG_LOG("%s, options: %s\n", r->name, -// options? options : "(none)"); + PARA_DEBUG_LOG("%s, options: %s\n", r->name, + options? options : "(none)"); if (options) { PARA_DEBUG_LOG("options: %s\n", options); - argc = split_args(options, &argv, " \t"); + argc = create_argv(options, " \t", &argv); + if (argc < 0) + return NULL; for (i = argc - 1; i >= 0; i--) argv[i + 1] = argv[i]; - argc += 1; + argc++; } else { argc = 1; argv = para_malloc(2 * sizeof(char*)); @@ -50,7 +55,8 @@ static void *parse_receiver_args(int receiver_num, char *options) } argv[0] = make_message("%s_recv", r->name); conf = r->parse_config(argc, argv); - free(argv[0]); + for (i = 0; i < argc; i++) + free(argv[i]); free(argv); return conf; } @@ -116,3 +122,14 @@ void print_receiver_helps(int detailed) ggo_print_help(&r->help, detailed); } } + +int generic_recv_pre_select(struct sched *s, struct task *t) +{ + struct receiver_node *rn = container_of(t, struct receiver_node, task); + int ret = btr_node_status(rn->btrn, 0, BTR_NT_ROOT); + + t->error = 0; + if (ret < 0) + sched_min_delay(s); + return ret; +} diff --git a/ringbuffer.c b/ringbuffer.c index b27fd8e8..bf990b06 100644 --- a/ringbuffer.c +++ b/ringbuffer.c @@ -1,11 +1,13 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file ringbuffer.c Simple ringbuffer implementation */ +#include + #include "para.h" #include "ringbuffer.h" #include "string.h" diff --git a/ringbuffer.h b/ringbuffer.h index a7488429..67f9f3a8 100644 --- a/ringbuffer.h +++ b/ringbuffer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ diff --git a/sched.c b/sched.c index 6f3f1faf..b0e9ce12 100644 --- a/sched.c +++ b/sched.c @@ -1,11 +1,12 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file sched.c Paraslash's scheduling functions. */ +#include #include /* readdir() */ #include #include @@ -16,6 +17,7 @@ #include "list.h" #include "sched.h" #include "string.h" +#include "time.h" #include "error.h" static struct list_head pre_select_list, post_select_list; @@ -24,7 +26,7 @@ static int initialized; static struct timeval now_struct; struct timeval *now = &now_struct; -/** +/* * Remove a task from the scheduler. * * \param t The task to remove. @@ -67,13 +69,33 @@ static void sched_preselect(struct sched *s) } } +//#define SCHED_DEBUG 1 +static inline void call_post_select(struct sched *s, struct task *t) +{ +#ifndef SCHED_DEBUG + t->post_select(s, t); +#else + struct timeval t1, t2, diff; + unsigned long pst; + + gettimeofday(&t1, NULL); + t->post_select(s, t); + gettimeofday(&t2, NULL); + tv_diff(&t1, &t2, &diff); + pst = tv2ms(&diff); + if (pst > 50) + PARA_WARNING_LOG("%s: post_select time: %lums\n", + t->status, pst); +#endif +} + static void sched_post_select(struct sched *s) { struct task *t, *tmp; list_for_each_entry_safe(t, tmp, &post_select_list, post_select_node) { if (t->error >= 0) - t->post_select(s, t); + call_post_select(s, t); // PARA_INFO_LOG("%s: %d\n", t->status, t->ret); if (t->error >= 0) continue; @@ -107,18 +129,29 @@ int schedule(struct sched *s) return -E_NOT_INITIALIZED; if (!s->select_function) s->select_function = para_select; - gettimeofday(now, NULL); again: FD_ZERO(&s->rfds); FD_ZERO(&s->wfds); - s->timeout = s->default_timeout; + s->select_timeout = s->default_timeout; s->max_fileno = -1; + gettimeofday(now, NULL); sched_preselect(s); if (list_empty(&pre_select_list) && list_empty(&post_select_list)) return 0; - ret = s->select_function(s->max_fileno + 1, &s->rfds, &s->wfds, &s->timeout); + ret = s->select_function(s->max_fileno + 1, &s->rfds, &s->wfds, + &s->select_timeout); if (ret < 0) return ret; + if (ret == 0) { + /* + * APUE: Be careful not to check the descriptor sets on return + * unless the return value is greater than zero. The return + * state of the descriptor sets is implementation dependent if + * either a signal is caught or the timer expires. + */ + FD_ZERO(&s->rfds); + FD_ZERO(&s->wfds); + } gettimeofday(now, NULL); sched_post_select(s); if (list_empty(&pre_select_list) && list_empty(&post_select_list)) @@ -126,7 +159,7 @@ again: goto again; } -/** +/* * Initialize the paraslash scheduler. */ static void init_sched(void) @@ -188,7 +221,7 @@ void sched_shutdown(void) * * Each entry of the list contains an identifier which is simply a hex number * that may be used in \a kill_task() to terminate the task. - * The result ist dynamically allocated and must be freed by the caller. + * The result is dynamically allocated and must be freed by the caller. */ char *get_task_list(void) { @@ -249,3 +282,88 @@ int kill_task(char *id) } return -E_NO_SUCH_TASK; } + +/** + * Set the select timeout to the minimal possible value. + * + * \param s Pointer to the scheduler struct. + * + * This causes the next select() call to return immediately. + */ +void sched_min_delay(struct sched *s) +{ + s->select_timeout.tv_sec = 0; + s->select_timeout.tv_usec = 1; +} + +/** + * Impose an upper bound for the timeout of the next select() call. + * + * \param to Maximal allowed timeout. + * \param s Pointer to the scheduler struct. + * + * If the current scheduler timeout is already smaller than \a to, this + * function does nothing. Otherwise the timeout for the next select() call is + * set to the given value. + * + * \sa sched_request_timeout_ms(). + */ +void sched_request_timeout(struct timeval *to, struct sched *s) +{ + if (tv_diff(&s->select_timeout, to, NULL) > 0) + s->select_timeout = *to; +} + +/** + * Force the next select() call to return before the given amount of milliseconds. + * + * \param ms The maximal allowed timeout in milliseconds. + * \param s Pointer to the scheduler struct. + * + * Like sched_request_timeout() this imposes an upper bound on the timeout + * value for the next select() call. + */ +void sched_request_timeout_ms(long unsigned ms, struct sched *s) +{ + struct timeval tv; + ms2tv(ms, &tv); + sched_request_timeout(&tv, s); +} + +/** + * Force the next select() call to return before the given future time. + * + * \param barrier Absolute time before select() should return. + * \param s Pointer to the scheduler struct. + * + * If \a barrier is in the past, this function does nothing. + * + * \sa sched_request_barrier_or_min_delay(). + */ +void sched_request_barrier(struct timeval *barrier, struct sched *s) +{ + struct timeval diff; + + if (tv_diff(now, barrier, &diff) > 0) + return; + sched_request_timeout(&diff, s); +} + +/** + * Force the next select() call to return before the given time. + * + * \param barrier Absolute time before select() should return. + * \param s Pointer to the scheduler struct. + * + * If \a barrier is in the past, this function requests a minimal timeout. + * + * \sa sched_min_delay(), sched_request_barrier(). + */ +void sched_request_barrier_or_min_delay(struct timeval *barrier, struct sched *s) +{ + struct timeval diff; + + if (tv_diff(now, barrier, &diff) > 0) + return sched_min_delay(s); + sched_request_timeout(&diff, s); +} diff --git a/sched.h b/sched.h index 26aaabe9..7158a873 100644 --- a/sched.h +++ b/sched.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -20,14 +20,14 @@ struct sched { /** Initial value before any pre_select call. */ struct timeval default_timeout; /** The current timeout for the upcoming select call. */ - struct timeval timeout; + struct timeval select_timeout; /** fds that should be watched for readability. */ fd_set rfds; /** fds that should be watched for writability. */ fd_set wfds; /** Highest numbered file descriptor in any of the above fd sets. */ int max_fileno; - /** In non-NULL, use this function instead of para_select. */ + /** If non-NULL, use this function instead of para_select. */ int (*select_function)(int, fd_set *, fd_set *, struct timeval *); }; @@ -63,7 +63,7 @@ struct task { /** Position of the task in the post_select list of the scheduler. */ struct list_head post_select_node; /** Descriptive text and current status of the task. */ - char status[MAXLINE]; + char status[255]; }; /** @@ -79,3 +79,8 @@ int schedule(struct sched *s); char *get_task_list(void); int kill_task(char *id); void sched_shutdown(void); +void sched_min_delay(struct sched *s); +void sched_request_timeout(struct timeval *to, struct sched *s); +void sched_request_timeout_ms(long unsigned ms, struct sched *s); +void sched_request_barrier(struct timeval *barrier, struct sched *s); +void sched_request_barrier_or_min_delay(struct timeval *barrier, struct sched *s); diff --git a/score.c b/score.c index 51ec1e4a..27fec711 100644 --- a/score.c +++ b/score.c @@ -1,10 +1,13 @@ /* - * Copyright (C) 2007-2009 Andre Noll + * Copyright (C) 2007-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file score.c Scoring functions to determine the audio file streaming order. */ +#include +#include + #include "para.h" #include "error.h" #include "string.h" @@ -93,7 +96,7 @@ static struct osl_table_description score_table_desc = { */ int get_num_admissible_files(unsigned *num) { - return osl_get_num_rows(score_table, num); + return osl(osl_get_num_rows(score_table, num)); } /** @@ -109,7 +112,7 @@ int get_num_admissible_files(unsigned *num) static int get_score_of_row(void *score_row, long *score) { struct osl_object obj; - int ret = osl_get_object(score_table, score_row, SCORECOL_SCORE, &obj); + int ret = osl(osl_get_object(score_table, score_row, SCORECOL_SCORE, &obj)); if (ret >= 0) *score = *(long *)obj.data; @@ -141,7 +144,7 @@ int score_add(const struct osl_row *aft_row, long score) *(int *)(score_objs[SCORECOL_SCORE].data) = score; // PARA_DEBUG_LOG("adding %p\n", *(void **) (score_objs[SCORECOL_AFT_ROW].data)); - ret = osl_add_row(score_table, score_objs); + ret = osl(osl_add_row(score_table, score_objs)); if (ret < 0) { PARA_ERROR_LOG("%s\n", para_strerror(-ret)); free(score_objs[SCORECOL_SCORE].data); @@ -152,7 +155,7 @@ int score_add(const struct osl_row *aft_row, long score) static int get_nth_score(unsigned n, long *score) { struct osl_row *row; - int ret = osl_get_nth_row(score_table, SCORECOL_SCORE, n, &row); + int ret = osl(osl_get_nth_row(score_table, SCORECOL_SCORE, n, &row)); if (ret < 0) return ret; @@ -180,9 +183,9 @@ int score_update(const struct osl_row *aft_row, long percent) unsigned n, new_pos; struct osl_object obj = {.data = (struct osl_row *)aft_row, .size = sizeof(aft_row)}; - int ret = osl_get_row(score_table, SCORECOL_AFT_ROW, &obj, &row); + int ret = osl(osl_get_row(score_table, SCORECOL_AFT_ROW, &obj, &row)); - if (ret == -E_RB_KEY_NOT_FOUND) /* not an error */ + if (ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND)) /* not an error */ return 1; if (ret < 0) return ret; @@ -198,7 +201,7 @@ int score_update(const struct osl_row *aft_row, long percent) obj.data = para_malloc(obj.size); *(long *)obj.data = new_score; PARA_DEBUG_LOG("new score: %ld, rank %u/%u\n", new_score, new_pos, n); - return osl_update_object(score_table, row, SCORECOL_SCORE, &obj); + return osl(osl_update_object(score_table, row, SCORECOL_SCORE, &obj)); } /** @@ -219,7 +222,7 @@ int get_score_and_aft_row(struct osl_row *score_row, long *score, if (ret < 0) return ret; - ret = osl_get_object(score_table, score_row, SCORECOL_AFT_ROW, &obj); + ret = osl(osl_get_object(score_table, score_row, SCORECOL_AFT_ROW, &obj)); if (ret < 0) return ret; *aft_row = obj.data; @@ -231,8 +234,7 @@ static int get_score_row_from_aft_row(const struct osl_row *aft_row, { struct osl_object obj = {.data = (struct osl_row *)aft_row, .size = sizeof(aft_row)}; - return osl_get_row(score_table, SCORECOL_AFT_ROW, &obj, score_row); - + return osl(osl_get_row(score_table, SCORECOL_AFT_ROW, &obj, score_row)); } /** @@ -250,7 +252,7 @@ static int get_score_row_from_aft_row(const struct osl_row *aft_row, */ int admissible_file_loop(void *data, osl_rbtree_loop_func *func) { - return osl_rbtree_loop(score_table, SCORECOL_SCORE, data, func); + return osl(osl_rbtree_loop(score_table, SCORECOL_SCORE, data, func)); } /** @@ -265,7 +267,7 @@ int admissible_file_loop(void *data, osl_rbtree_loop_func *func) */ int admissible_file_loop_reverse(void *data, osl_rbtree_loop_func *func) { - return osl_rbtree_loop_reverse(score_table, SCORECOL_SCORE, data, func); + return osl(osl_rbtree_loop_reverse(score_table, SCORECOL_SCORE, data, func)); } /** @@ -281,11 +283,11 @@ int score_get_best(struct osl_row **aft_row, long *score) { struct osl_row *row; struct osl_object obj; - int ret = osl_rbtree_last_row(score_table, SCORECOL_SCORE, &row); + int ret = osl(osl_rbtree_last_row(score_table, SCORECOL_SCORE, &row)); if (ret < 0) return ret; - ret = osl_get_object(score_table, row, SCORECOL_AFT_ROW, &obj); + ret = osl(osl_get_object(score_table, row, SCORECOL_AFT_ROW, &obj)); if (ret < 0) return ret; *aft_row = obj.data; @@ -309,7 +311,7 @@ int score_delete(const struct osl_row *aft_row) if (ret < 0) return ret; - return osl_del_row(score_table, score_row); + return osl(osl_del_row(score_table, score_row)); } /** @@ -327,13 +329,13 @@ int row_belongs_to_score_table(const struct osl_row *aft_row, unsigned *rank) struct osl_row *score_row; int ret = get_score_row_from_aft_row(aft_row, &score_row); - if (ret == -E_RB_KEY_NOT_FOUND) + if (ret == -OSL_ERRNO_TO_PARA_ERROR(E_OSL_RB_KEY_NOT_FOUND)) return 0; if (ret < 0) return ret; if (!rank) return 1; - ret = osl_get_rank(score_table, score_row, SCORECOL_SCORE, rank); + ret = osl(osl_get_rank(score_table, score_row, SCORECOL_SCORE, rank)); if (ret < 0) return ret; return 1; @@ -356,7 +358,7 @@ static void score_close(void) static int score_open(__a_unused const char *dir) { score_table_desc.dir = NULL; /* this table has only volatile columns */ - return osl_open_table(&score_table_desc, &score_table); + return osl(osl_open_table(&score_table_desc, &score_table)); } /** diff --git a/send.h b/send.h index 7087c266..836babd5 100644 --- a/send.h +++ b/send.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -47,7 +47,7 @@ struct sender { * only differ if the stream was repositioned by the \a ff or \a jmp * command. Of course, \a buf is a pointer to the chunk of data which * should be sent, and \a len is the length of this buffer. - */ + */ void (*send)(long unsigned current_chunk, long unsigned chunks_sent, const char *buf, size_t len, const char *header_buf, size_t header_len); @@ -87,6 +87,15 @@ struct sender { * pointer means this command is not implemented by this sender. */ int (*client_cmds[NUM_SENDER_CMDS])(struct sender_command_data*); + /** + * Resolve target-specific URL string + * + * This method must be defined if the sender supports the add/delete + * subcommands. It interprets a string specifying a target URL in a + * sender-specific fashion (e.g. embedded FEC string). It can also + * fill in sender-specific defaults if necessary. + */ + int (*resolve_target)(const char *, struct sender_command_data *); }; /** Describes one client, connected to a paraslash sender. */ @@ -105,6 +114,41 @@ struct sender_client { void *private_data; }; +/** + * Each paraslash sender may register arbitrary many clients to the virtual + * streaming system, possibly with varying fec parameters. In order to do so, + * it must allocate a \a fec_client_parms structure and pass it to \ref + * vss_add_fec_client. + * + * Clients are automatically removed from that list by the vss if an error + * occurs, or if the sender requests deletion of a client by calling \ref + * vss_del_fec_client(). + */ + +/** FEC parameters requested by FEC clients. */ +struct fec_client_parms { + /** Number of data slices plus redundant slices. */ + uint8_t slices_per_group; + /** Number of slices minus number of redundant slices. */ + uint8_t data_slices_per_group; + /** Whether the header must be sent periodically. */ + bool need_periodic_header; + /** + * Transport-layer initialisation for FEC support. + * + * This optional function serves (a) to make the transport layer + * ready to send FEC slices and (b) to determine the Maximum + * Packet Size (MPS) supported by the connection. The MPS value + * determines the largest payload size. This value is used to + * send FEC slices that are not larger than the path MTU, to avoid + * fragmentation and to maximize packet utilization. The user can + * alternatively specify a slice size of up to this value. + */ + int (*init_fec)(struct sender_client *sc); + /** Push out FEC-encoded packets */ + int (*send_fec)(struct sender_client *sc, char *buf, size_t len); +}; + /** Describes the current status of one paraslash sender. */ struct sender_status { /** The file descriptor of the socket this sender is listening on. */ @@ -125,13 +169,9 @@ struct sender_status { void shutdown_client(struct sender_client *sc, struct sender_status *ss); void shutdown_clients(struct sender_status *ss); -void send_chunk(struct sender_client *sc, struct sender_status *ss, - size_t max_bytes_per_write, long unsigned current_chunk, - const char *buf, size_t len, const char *header_buf, - size_t header_len); void init_sender_status(struct sender_status *ss, char **access_arg, int num_access_args, int port, int max_clients, int default_deny); -char *get_sender_info(struct sender_status *ss, char *name); +char *get_sender_info(struct sender_status *ss, const char *name); void generic_com_allow(struct sender_command_data *scd, struct sender_status *ss); @@ -140,7 +180,6 @@ void generic_com_deny(struct sender_command_data *scd, int generic_com_on(struct sender_status *ss, unsigned protocol); void generic_com_off(struct sender_status *ss); char *generic_sender_help(void); -struct sender_client *accept_sender_client(struct sender_status *ss); -int send_queued_chunks(int fd, struct chunk_queue *cq, - size_t max_bytes_per_write); +struct sender_client *accept_sender_client(struct sender_status *ss, fd_set *rfds); +int send_queued_chunks(int fd, struct chunk_queue *cq); int parse_fec_url(const char *arg, struct sender_command_data *scd); diff --git a/send_common.c b/send_common.c index 8653c330..6a85644d 100644 --- a/send_common.c +++ b/send_common.c @@ -1,12 +1,15 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file send_common.c Functions used by more than one paraslash sender. */ +#include #include +#include + #include "para.h" #include "error.h" #include "string.h" @@ -39,7 +42,7 @@ */ static int open_sender(unsigned l4type, int port) { - int fd, ret = para_listen(AF_UNSPEC, l4type, port); + int fd, ret = para_listen_simple(l4type, port); if (ret < 0) return ret; @@ -93,34 +96,24 @@ void shutdown_clients(struct sender_status *ss) shutdown_client(sc, ss); } -static int queue_chunk_or_shutdown(struct sender_client *sc, - struct sender_status *ss, const char *buf, size_t num_bytes) -{ - int ret = cq_enqueue(sc->cq, buf, num_bytes); - if (ret < 0) - shutdown_client(sc, ss); - return ret; -} - /** * Try to empty the chunk queue for this fd. * * \param fd The file descriptor. * \param cq The list of queued chunks. - * \param max_bytes_per_write Do not send more than this in one go. * * \return Negative on errors, zero if not everything was sent, one otherwise. */ -int send_queued_chunks(int fd, struct chunk_queue *cq, - size_t max_bytes_per_write) +int send_queued_chunks(int fd, struct chunk_queue *cq) { struct queued_chunk *qc; while ((qc = cq_peek(cq))) { const char *buf; size_t len; int ret; + cq_get(qc, &buf, &len); - ret = write_nonblock(fd, buf, len, max_bytes_per_write); + ret = write_nonblock(fd, buf, len); if (ret < 0) return ret; cq_update(cq, ret); @@ -131,59 +124,6 @@ int send_queued_chunks(int fd, struct chunk_queue *cq, return 1; } -/** - * Send one chunk of audio data to a connected client. - * - * \param sc The client. - * \param ss The sender. - * \param max_bytes_per_write Split writes to chunks of at most that many bytes. - * \param current_chunk The number of the chunk to write. - * \param buf The data to write. - * \param len The number of bytes of \a buf. - * \param header_buf The audio file header. - * \param header_len The number of bytes of \a header_buf. - * - * On errors, the client is shut down. If only a part of the buffer could be - * written, the remainder is put into the chunk queue for that client. - */ -void send_chunk(struct sender_client *sc, struct sender_status *ss, - size_t max_bytes_per_write, long unsigned current_chunk, - const char *buf, size_t len, const char *header_buf, - size_t header_len) -{ - int ret; - - if (!sc->header_sent && current_chunk) { - if (header_buf && header_len > 0) { - ret = queue_chunk_or_shutdown(sc, ss, header_buf, header_len); - if (ret < 0) - goto out; - } - } - sc->header_sent = 1; - ret = send_queued_chunks(sc->fd, sc->cq, max_bytes_per_write); - if (ret < 0) { - shutdown_client(sc, ss); - goto out; - } - if (!len) - goto out; - if (!ret) { /* still data left in the queue */ - ret = queue_chunk_or_shutdown(sc, ss, buf, len); - goto out; - } - ret = write_nonblock(sc->fd, buf, len, max_bytes_per_write); - if (ret < 0) { - shutdown_client(sc, ss); - goto out; - } - if (ret != len) - ret = queue_chunk_or_shutdown(sc, ss, buf + ret, len - ret); -out: - if (ret < 0) - PARA_NOTICE_LOG("%s\n", para_strerror(-ret)); -} - /** * Initialize a struct sender status. * @@ -214,7 +154,7 @@ void init_sender_status(struct sender_status *ss, char **access_arg, * * \return The string printed in the "si" command. */ -char *get_sender_info(struct sender_status *ss, char *name) +char *get_sender_info(struct sender_status *ss, const char *name) { char *clnts = NULL, *ret; struct sender_client *sc, *tmp_sc; @@ -228,14 +168,14 @@ char *get_sender_info(struct sender_status *ss, char *name) ret = make_message( "%s sender:\n" "\tstatus: %s\n" - "\tport: %d\n" + "\tport: %s\n" "\tnumber of connected clients: %d\n" "\tmaximal number of clients: %d%s\n" "\tconnected clients: %s\n" "\taccess %s list: %s\n", name, (ss->listen_fd >= 0)? "on" : "off", - ss->port, + stringify_port(ss->port, strcmp(name, "http") ? "dccp" : "tcp"), ss->num_clients, ss->max_clients, ss->max_clients > 0? "" : " (unlimited)", @@ -321,6 +261,7 @@ void generic_com_off(struct sender_status *ss) * Accept a connection on the socket this server is listening on. * * \param ss The sender whose listening fd is ready for reading. + * \param rfds Passed to para_accept(), * * This must be called only if the socket fd of \a ss is ready for reading. It * calls para_accept() to accept the connection and performs the following @@ -345,15 +286,18 @@ void generic_com_off(struct sender_status *ss) * \sa \ref para_accept(), \ref mark_fd_nonblocking(), \ref acl_check_access(), * \ref cq_new(), \ref add_close_on_fork_list(). */ -struct sender_client *accept_sender_client(struct sender_status *ss) +struct sender_client *accept_sender_client(struct sender_status *ss, fd_set *rfds) { struct sender_client *sc; - int fd, ret = para_accept(ss->listen_fd, NULL, 0); - if (ret < 0) { + int fd, ret; + + if (ss->listen_fd < 0) + return NULL; + ret = para_accept(ss->listen_fd, rfds, NULL, 0, &fd); + if (ret < 0) PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + if (ret <= 0) return NULL; - } - fd = ret; ret = -E_MAX_CLIENTS; if (ss->max_clients > 0 && ss->num_clients >= ss->max_clients) goto err_out; @@ -366,7 +310,7 @@ struct sender_client *accept_sender_client(struct sender_status *ss) ss->num_clients++; sc = para_calloc(sizeof(*sc)); sc->fd = fd; - sc->name = make_message("%s", remote_name(fd)); + sc->name = para_strdup(remote_name(fd)); sc->cq = cq_new(MAX_CQ_BYTES); para_list_add(&sc->node, &ss->client_list); add_close_on_fork_list(fd); @@ -398,47 +342,51 @@ char *generic_sender_help(void) static int parse_fec_parms(const char *arg, struct sender_command_data *scd) { int32_t val; - char *a = para_strdup(arg), *b = a, *e = strchr(b, ':'); + char *a = para_strdup(arg), + *b = strchr(a, ':'), + *c = strrchr(a, ':'); int ret = -E_COMMAND_SYNTAX; - /* parse max slice bytes */ - if (!e) - goto out; - *e = '\0'; - ret = para_atoi32(b, &val); - if (ret < 0) - goto out; - ret = -ERRNO_TO_PARA_ERROR(EINVAL); - if (val < 0 || val > 65535) + if (!b || !c) goto out; - scd->max_slice_bytes = val; - /* parse data_slices_per_group */ - b = e + 1; - e = strchr(b, ':'); - ret = -E_COMMAND_SYNTAX; - if (!e) - goto out; - *e = '\0'; - ret = para_atoi32(b, &val); + *b = *c = '\0'; + + ret = para_atoi32(a, &val); if (ret < 0) goto out; - ret = -ERRNO_TO_PARA_ERROR(EINVAL); + + /* optional max_slice_bytes (0 means "use MTU") */ + if (b == c) { + scd->max_slice_bytes = 0; + } else { + if (val < 0 || val > 65535) + goto fec_einval; + scd->max_slice_bytes = val; + + ret = para_atoi32(b + 1, &val); + if (ret < 0) + goto out; + } + + /* k = data_slices_per_group */ if (val < 0 || val > 255) - goto out; + goto fec_einval; scd->data_slices_per_group = val; - /* parse slices_per_group */ - b = e + 1; - ret = para_atoi32(b, &val); + + /* n = slices_per_group */ + ret = para_atoi32(c + 1, &val); if (ret < 0) goto out; - ret = -ERRNO_TO_PARA_ERROR(EINVAL); if (val < 0 || val < scd->data_slices_per_group) - goto out; + goto fec_einval; scd->slices_per_group = val; ret = 0; out: free(a); return ret; +fec_einval: + ret = -ERRNO_TO_PARA_ERROR(EINVAL); + goto out; } /** @@ -458,26 +406,22 @@ out: */ int parse_fec_url(const char *arg, struct sender_command_data *scd) { - int ret; - ssize_t len = sizeof(scd->host); char *a = para_strdup(arg), *p = strchr(a, '/'); + int ret = 0; + + /* default fec parameters */ + scd->max_slice_bytes = 0; + scd->data_slices_per_group = 14; + scd->slices_per_group = 16; if (p) { *p = '\0'; - len = strlen(a); - } - ret = -ERRNO_TO_PARA_ERROR(EINVAL); - if (!parse_url(a, scd->host, len, &scd->port)) - goto out; - if (p) { ret = parse_fec_parms(p + 1, scd); - goto out; + if (ret < 0) + goto out; } - /* use default fec parameters. */ - scd->max_slice_bytes = 1490; - scd->slices_per_group = 16; - scd->data_slices_per_group = 14; - ret = 0; + if (!parse_url(a, scd->host, sizeof(scd->host), &scd->port)) + ret = -ERRNO_TO_PARA_ERROR(EINVAL); out: free(a); return ret; diff --git a/server.c b/server.c index 95ff25cf..849b707a 100644 --- a/server.c +++ b/server.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 1997-2009 Andre Noll + * Copyright (C) 1997-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -14,18 +14,20 @@ * * * - The main programs: \ref server.c, \ref audiod.c, \ref client.c, - * \ref audioc.c, \ref fsck.c, \ref afh.c + * \ref audioc.c, \ref afh.c * - Server: \ref server_command, \ref sender, - * - Audio file selector: \ref audio_format_handler, \ref mood, \ref afs_table, + * - Audio file selector: \ref audio_format_handler, \ref afs_table, * - Client: \ref receiver, \ref receiver_node, \ref filter, \ref filter_node. * * * The gory details, listed by topic: * - * - Audio format handlers: \ref send_common.c \ref mp3_afh.c, \ref ogg_afh.c, \ref aac_afh.c, - * - Decoders: \ref mp3dec_filter.c, \ref oggdec_filter.c, \ref aacdec_filter.c, + * - Audio format handlers: \ref send_common.c \ref mp3_afh.c, + * \ref ogg_afh.c, \ref aac_afh.c, \ref wma_afh.c, \ref spx_afh.c + * - Decoders: \ref mp3dec_filter.c, \ref oggdec_filter.c, + * \ref aacdec_filter.c, \ref wmadec_filter.c, spxdec_filter.c, * - Volume normalizer: \ref compress_filter.c, - * - Output: \ref alsa_write.c, \ref osx_write.c, + * - Output: \ref alsa_write.c, \ref osx_write.c, \ref oss_write.c, * - http: \ref http_recv.c, \ref http_send.c, * - udp: \ref udp_recv.c, \ref udp_send.c, * - dccp: \ref dccp_recv.c, \ref dccp_send.c, @@ -47,7 +49,6 @@ * - Time: \ref time.c, * - Spawning processes: \ref exec.c, * - Inter process communication: \ref ipc.c, - * - The object storage layer: \ref osl.c, * - Blob tables: \ref blob.c, * - The error subssystem: \ref error.h. * - Access control for paraslash senders: \ref acl.c, \ref acl.h. @@ -55,29 +56,33 @@ * Low-level data structures: * * - Doubly linked lists: \ref list.h, - * - Red-black trees: \ref rbtree.h, \ref rbtree.c, * - Ring buffer: \ref ringbuffer.c, \ref ringbuffer.h, * - Hashing: \ref hash.h, \ref sha1.h, \ref sha1.c, * - Crypto: \ref crypt.c. - * - Forward error correction: \ref fec.c + * - Forward error correction: \ref fec.c. */ #include #include #include +#include +#include +#include +#include #include "para.h" #include "error.h" +#include "crypt.h" #include "server.cmdline.h" #include "afh.h" #include "string.h" #include "afs.h" #include "server.h" +#include "list.h" +#include "send.h" #include "vss.h" #include "config.h" #include "close_on_fork.h" -#include "list.h" -#include "send.h" #include "net.h" #include "daemon.h" #include "ipc.h" @@ -142,17 +147,14 @@ static int want_colors(void) static void init_colors_or_die(void) { - int ret, i; + int i; if (!want_colors()) return; daemon_set_flag(DF_COLOR_LOG); daemon_set_default_log_colors(); - for (i = 0; i < conf.log_color_given; i++) { - ret = daemon_set_log_color(conf.log_color_arg[i]); - if (ret < 0) - exit(EXIT_FAILURE); - } + for (i = 0; i < conf.log_color_given; i++) + daemon_set_log_color_or_die(conf.log_color_arg[i]); } /* @@ -241,6 +243,8 @@ void parse_config_or_die(int override) daemon_set_flag(DF_LOG_PID); daemon_set_flag(DF_LOG_LL); daemon_set_flag(DF_LOG_TIME); + if (conf.log_timing_given) + daemon_set_flag(DF_LOG_TIMING); ret = 1; out: free(cf); @@ -270,15 +274,13 @@ static void handle_sighup(void) kill(mmd->afs_pid, SIGHUP); } -static void signal_post_select(struct sched *s, struct task *t) +static void signal_post_select(struct sched *s, __a_unused struct task *t) { - struct signal_task *st = container_of(t, struct signal_task, task); + int signum = para_next_signal(&s->rfds); - if (!FD_ISSET(st->fd, &s->rfds)) + switch (signum) { + case 0: return; - - st->signum = para_next_signal(); - switch (st->signum) { case SIGHUP: handle_sighup(); break; @@ -298,7 +300,7 @@ static void signal_post_select(struct sched *s, struct task *t) /* die on sigint/sigterm. Kill all children too. */ case SIGINT: case SIGTERM: - PARA_EMERG_LOG("terminating on signal %d\n", st->signum); + PARA_EMERG_LOG("terminating on signal %d\n", signum); kill(0, SIGTERM); /* * We must wait for afs because afs catches SIGINT/SIGTERM. @@ -318,7 +320,6 @@ static void signal_post_select(struct sched *s, struct task *t) waitpid(mmd->afs_pid, NULL, 0); cleanup: free(mmd->afd.afhi.chunk_table); - free(mmd->afd.afhi.info_string); close_listed_fds(); mutex_destroy(mmd_mutex); shm_detach(mmd); @@ -360,28 +361,23 @@ static void command_post_select(struct sched *s, struct task *t) char *peer_name; pid_t child_pid; uint32_t *chunk_table; - char *info_string; - if (!FD_ISSET(sct->listen_fd, &s->rfds)) - return; - ret = para_accept(sct->listen_fd, NULL, 0); - if (ret < 0) + ret = para_accept(sct->listen_fd, &s->rfds, NULL, 0, &new_fd); + if (ret <= 0) goto out; - new_fd = ret; peer_name = remote_name(new_fd); PARA_INFO_LOG("got connection from %s, forking\n", peer_name); mmd->num_connects++; mmd->active_connections++; - random(); - /* The chunk table and the info_string are pointers located in the - * mmd struct that point to dynamically allocated memory that must be - * freed by the parent and the child. However, as the mmd struct is in - * a shared memory area, there's no guarantee that after the fork these - * pointers are still valid in child context. As these two pointers are - * not used in the child anyway, we save them to local variables and - * free the memory via that copy in the child. + /* + * The chunk table is a pointer located in the mmd struct that points + * to dynamically allocated memory, i.e. it must be freed by the parent + * and the child. However, as the mmd struct is in a shared memory + * area, there's no guarantee that after the fork this pointer is still + * valid in child context. As it is not used in the child anyway, we + * save it to a local variable before the fork and free the memory via + * that copy in the child directly after the fork. */ - info_string = mmd->afd.afhi.info_string; chunk_table = mmd->afd.afhi.chunk_table; child_pid = fork(); if (child_pid < 0) { @@ -394,7 +390,6 @@ static void command_post_select(struct sched *s, struct task *t) return; } /* mmd might already have changed at this point */ - free(info_string); free(chunk_table); alarm(ALARM_TIMEOUT); close_listed_fds(); @@ -423,7 +418,7 @@ static void init_server_command_task(int argc, char **argv) sct->task.post_select = command_post_select; sct->argc = argc; sct->argv = argv; - ret = para_listen(AF_UNSPEC, IPPROTO_TCP, conf.port_arg); + ret = para_listen_simple(IPPROTO_TCP, conf.port_arg); if (ret < 0) goto err; sct->listen_fd = ret; @@ -438,35 +433,6 @@ err: exit(EXIT_FAILURE); } -static void init_random_seed(void) -{ - unsigned int seed; - int fd, ret = para_open("/dev/urandom", O_RDONLY, 0); - - if (ret < 0) - goto err; - fd = ret; - ret = read(fd, &seed, sizeof(seed)); - if (ret < 0) { - ret = -ERRNO_TO_PARA_ERROR(errno); - goto out; - } - if (ret != sizeof(seed)) { - ret = -ERRNO_TO_PARA_ERROR(EIO); - goto out; - } - srandom(seed); - ret = 1; -out: - close(fd); - if (ret >= 0) - return; -err: - PARA_EMERG_LOG("can not seed pseudo random number generator: %s\n", - para_strerror(-ret)); - exit(EXIT_FAILURE); -} - static int init_afs(void) { int ret, afs_server_socket[2]; @@ -475,7 +441,8 @@ static int init_afs(void) ret = socketpair(PF_UNIX, SOCK_DGRAM, 0, afs_server_socket); if (ret < 0) exit(EXIT_FAILURE); - afs_socket_cookie = para_random((uint32_t)-1); + get_random_bytes_or_die((unsigned char *)&afs_socket_cookie, + sizeof(afs_socket_cookie)); afs_pid = fork(); if (afs_pid < 0) exit(EXIT_FAILURE); @@ -512,7 +479,7 @@ static void server_init(int argc, char **argv) int afs_socket; valid_fd_012(); - init_random_seed(); + init_random_seed_or_die(); /* parse command line options */ server_cmdline_parser_ext(argc, argv, &conf, ¶ms); HANDLE_VERSION_FLAG("server", conf); diff --git a/server.cmd b/server.cmd index a6b512fe..73ee3417 100644 --- a/server.cmd +++ b/server.cmd @@ -3,7 +3,8 @@ SF: command.c HC: prototypes for the server command handlers CC: array of server commands AT: server_command -IN: para error string afh afs server list user_list +SI: openssl/rc4 osl regex +IN: para error crypt command string afh afs server list user_list SN: list of server commands --- N: ff @@ -92,11 +93,14 @@ H: Print server uptime and other information. --- N: stat P: VSS_READ -D: Print status info for current audio file. -U: stat [n] -H: Without any arguments, stat continuously prints status messages -H: about the audio file being streamed. Use the optional number n -H: to let stat exit after having displayed status n times. +D: Print status info for the current audio file. +U: stat [-n num] [-p] +H: If -n is given, the command exits after having displayed the status n +H: times. Otherwise, the command runs in an endless loop. +H: +H: The -p option activates parser-friendly output: Each status item is +H: prefixed with its size in bytes and the status items identifiers are +H: printed as numerical values. --- N: stop P: VSS_READ | VSS_WRITE diff --git a/server.h b/server.h index cf17c0ef..3b2f381c 100644 --- a/server.h +++ b/server.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 1997-2009 Andre Noll + * Copyright (C) 1997-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -12,23 +12,6 @@ /** The maximum length of the host component in an URL */ #define MAX_HOSTLEN 256 -/** - * Defines one command of para_server. - */ -struct server_command { - /** The name of the command. */ - const char *name; - /** Pointer to the function that handles the command. */ - int (*handler)(int, int, char * const * const); - /** The privileges a user must have to execute this command. */ - unsigned int perms; - /** One-line description of the command. */ - const char *description; - /** Summary of the command line options. */ - const char *usage; - /** The long help text. */ - const char *help; -}; /** Holds the arguments for the para_server's sender command. */ struct sender_command_data{ diff --git a/sha1.c b/sha1.c index 65524bc5..b0e955ec 100644 --- a/sha1.c +++ b/sha1.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007-2009 Andre Noll + * Copyright (C) 2007-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ diff --git a/signal.c b/signal.c index 5d3e2c9d..4726571c 100644 --- a/signal.c +++ b/signal.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004-2009 Andre Noll + * Copyright (C) 2004-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -151,27 +151,24 @@ void para_install_sighandler(int sig) /** * Return the number of the next pending signal. * - * This should be called if the fd for the signal pipe is ready for reading. + * \param rfds Th fd_set containing the signal pipe. * - * \return On success, the number of the received signal is returned. If the - * read returned zero or was interrupted by another signal the function returns - * 0. Otherwise, a negative error value is returned. + * \return On success, the number of the received signal is returned. If there + * is no signal currently pending, the function returns zero. On read errors + * from the signal pipe, the process is terminated. */ -int para_next_signal(void) +int para_next_signal(fd_set *rfds) { - int s; - ssize_t r = read(signal_pipe[0], &s, sizeof(s)); + size_t n; + int s, ret = read_nonblock(signal_pipe[0], &s, sizeof(s), rfds, &n); - if (!r) { - PARA_CRIT_LOG("read from signal pipe returned zero\n"); - return 0; - } - if (r < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - return -ERRNO_TO_PARA_ERROR(errno); + if (ret < 0) { + PARA_EMERG_LOG("%s\n", para_strerror(-ret)); + exit(EXIT_FAILURE); } - assert(r == sizeof(s)); + if (n == 0) + return 0; + assert(n == sizeof(s)); PARA_DEBUG_LOG("next signal: %d\n", s); return s; } diff --git a/signal.h b/signal.h index f55409df..799c317f 100644 --- a/signal.h +++ b/signal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007-2009 Andre Noll + * Copyright (C) 2007-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -12,8 +12,6 @@ struct signal_task { /** The signal pipe. */ int fd; - /** The number of the most recent signal. */ - int signum; /** The associated task structure. */ struct task task; }; @@ -22,5 +20,5 @@ int para_signal_init(void); void para_sigaction(int sig, void (*handler)(int)); void para_install_sighandler(int); int para_reap_child(pid_t *pid); -int para_next_signal(void); +int para_next_signal(fd_set *rfds); void para_signal_shutdown(void); diff --git a/skencil/overview.sk b/skencil/overview.sk index d57c08f8..5ab648c8 100644 --- a/skencil/overview.sk +++ b/skencil/overview.sk @@ -79,17 +79,6 @@ Fn('Times-Roman') Fs(14) txt('cmd output',(-1.83691e-16,-1,1,-1.83691e-16,64.2028,142.553)) lw(0.992126) -la2(([(-4.0, 3.0), (2.0, 0.0), (-4.0, -3.0), (-4.0, 3.0)], 1)) -b() -bs(497.249,328.901,0) -bs(497.249,212.112,0) -fp((0,0,0)) -le() -lw(1) -Fn('Times-Roman') -Fs(14) -txt('pcm data',(-1.83691e-16,-1,1,-1.83691e-16,507.289,295.35)) -lw(0.992126) ld((0.10000000000000001, 1.0)) b() bs(24.7094,792.443,0) @@ -174,7 +163,7 @@ le() lw(1) Fn('Times-Bold') Fs(36) -txt('0.3.0',(437.217,712.433)) +txt('0.4.0',(437.217,712.433)) fp((0,0,0)) le() lw(1) @@ -290,17 +279,6 @@ lw(1) ld((4, 4)) r(156.432,0,0,-101.428,50.1221,646.762) G() -G() -lw(1) -r(41.883,0,0,-28.2587,55.6728,642.22) -fp((0,0,0)) -le() -lw(1) -Fn('Times-Roman') -Fs(24) -txt('osl',(61.2681,622.499)) -G_() -G() lw(1) r(41.883,0,0,-28.2587,160.634,643.23) fp((0,0,0)) @@ -317,7 +295,6 @@ Fn('Times-Italic') txt('afs process',(106.135,651.24)) lw(1) r(0,0,0,-0.504639,125.815,629.1) -G_() lw(1) ld((5, 5)) r(272.999,0,0,-67.6189,281.238,614.467) @@ -469,6 +446,29 @@ Fn('Times-Roman') Fs(24) txt('afs',(0.886791,0,0,1,115.93,576.074)) G_() +G() +lw(1) +r(75.1101,0,0,-28.2587,56.6797,643.23) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(24) +txt('libosl',(67.5587,620.179)) +G_() +G() +lw(0.992126) +la2(([(-4.0, 3.0), (2.0, 0.0), (-4.0, -3.0), (-4.0, 3.0)], 1)) +b() +bs(466.036,335.949,0) +bs(466.036,203.05,0) +fp((0,0,0)) +le() +lw(1) +Fn('Times-Roman') +Fs(14) +txt('pcm data',(-1.83691e-16,-1,1,-1.83691e-16,476.076,294.966)) +G_() guidelayer('Guide Lines',1,0,0,1,(0,0,1)) guide(-307.905,0) grid((0,0,20,20),0,(0,0,1),'Grid') diff --git a/spx.h b/spx.h new file mode 100644 index 00000000..124d6834 --- /dev/null +++ b/spx.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2010-2011 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** + * \file spx.h Structures and prototypes common to the speex audio format + * handler and the speex decoder. + */ + +/** + * Information extracted from the first ogg packet. + * + * It contains tech data but not the content of the attached comment tags. + */ +struct spx_header_info { + /** Holds the state of the decoder. */ + void *state; + /** Extracted from header. */ + int frame_size; + /** Current sample rate in Hz. */ + spx_int32_t sample_rate; + /** Current bitrate used by the decoder. */ + int bitrate; + /** Number of frames per packet, extracted from header. */ + int nframes; + /** The number of channels of the current stream. */ + int channels; + /** Only needed for stereo streams. */ + SpeexStereoState stereo; + /** Must be skipped during decode. */ + int extra_headers; + /** Narrow/wide/ultrawide band, bitstream version. */ + const SpeexMode *mode; +}; + +int spx_process_header(unsigned char *packet, long bytes, + struct spx_header_info *shi); +int spx_ctl(void *state, int request, void *ptr); diff --git a/spx_afh.c b/spx_afh.c new file mode 100644 index 00000000..6414f4c4 --- /dev/null +++ b/spx_afh.c @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2010-2011 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/* This file is based on speexdec.c, by Jean-Marc Valin, see below. */ + +/* Copyright (C) 2002-2006 Jean-Marc Valin + File: speexdec.c + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +/** \file spx_afh.c Audio format handler for ogg/speex files. */ + +#include +#include +#include +#include +#include +#include + +#include "para.h" +#include "afh.h" +#include "error.h" +#include "portable_io.h" +#include "string.h" +#include "spx.h" +#include "ogg_afh_common.h" + +struct private_spx_data { + struct spx_header_info shi; +}; + + +static char *copy_comment(const char *src, int len) +{ + char *p = para_malloc(len + 1); + + if (len > 0) + memcpy(p, src, len); + p[len] = '\0'; + PARA_DEBUG_LOG("%s\n", p); + return p; +} + +static bool copy_if_tag_type(const char *tag, int taglen, const char *type, + char **p) +{ + int len = strlen(type); + + if (taglen <= len) + return false; + if (strncasecmp(tag, type, len)) + return false; + if (tag[len] != '=') + return false; + free(*p); + *p = copy_comment(tag + len + 1, taglen - len - 1); + return true; +} + +static int spx_get_comments(unsigned char *comments, int length, + struct taginfo *tags) +{ + char *c = (char *)comments; + uint32_t len, nb_fields; + int i; + char *end; + + if (length < 8) + return -E_SPX_COMMENT; + end = c + length; + len = read_u32(c); + c += 4; + if (c + len > end) + return -E_SPX_COMMENT; + tags->comment = copy_comment(c, len); + + c += len; + if (c + 4 > end) + return -E_SPX_COMMENT; + nb_fields = read_u32(c); + PARA_DEBUG_LOG("%d comment(s)\n", nb_fields); + c += 4; + for (i = 0; i < nb_fields; i++, c += len) { + char *tag; + + if (c + 4 > end) + return -E_SPX_COMMENT; + len = read_u32(c); + c += 4; + if (c + len > end) + return -E_SPX_COMMENT; + if (copy_if_tag_type(c, len, "author", &tags->artist)) + continue; + if (copy_if_tag_type(c, len, "artist", &tags->artist)) + continue; + if (copy_if_tag_type(c, len, "title", &tags->title)) + continue; + if (copy_if_tag_type(c, len, "album", &tags->album)) + continue; + if (copy_if_tag_type(c, len, "year", &tags->year)) + continue; + if (copy_if_tag_type(c, len, "comment", &tags->comment)) + continue; + tag = copy_comment(c, len); + PARA_NOTICE_LOG("unrecognized comment: %s\n", tag); + free(tag); + } + return 1; +} + +static const char* speex_suffixes[] = {"spx", "speex", NULL}; + +static int spx_packet_callback(ogg_packet *packet, int packet_num, + struct afh_info *afhi, void *private_data) +{ + struct private_spx_data *psd = private_data; + int ret; + + if (packet_num == 0) { + ret = spx_process_header(packet->packet, packet->bytes, + &psd->shi); + if (ret < 0) + return ret; + afhi->channels = psd->shi.channels; + afhi->frequency = psd->shi.sample_rate; + afhi->bitrate = psd->shi.bitrate / 1000; + afhi->techinfo = make_message("%s, v%d", psd->shi.mode->modeName, + psd->shi.mode->bitstream_version); + return 1; + } + if (packet_num == 1) { + ret = spx_get_comments(packet->packet, packet->bytes, + &afhi->tags); + if (ret < 0) + return ret; + return 0; /* header complete */ + } + /* never reached */ + return 0; +} + +static int spx_get_file_info(char *map, size_t numbytes, __a_unused int fd, + struct afh_info *afhi) +{ + struct private_spx_data psd; + struct ogg_afh_callback_info spx_callback_info = { + .packet_callback = spx_packet_callback, + .private_data = &psd, + }; + + memset(&psd, 0, sizeof(psd)); + return ogg_get_file_info(map, numbytes, afhi, &spx_callback_info); +} + +/** + * The init function of the ogg/speex audio format handler. + * + * \param afh Pointer to the struct to initialize. + */ +void spx_afh_init(struct audio_format_handler *afh) +{ + afh->get_file_info = spx_get_file_info, + afh->suffixes = speex_suffixes; +} diff --git a/spx_common.c b/spx_common.c new file mode 100644 index 00000000..f9f1eb7e --- /dev/null +++ b/spx_common.c @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2002-2006 Jean-Marc Valin + * Copyright (C) 2010-2011 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** + * \file spx_common.c Functions used by the speex decoder and the speex audio + * format handler. + */ + +/* This file is based on speexdec.c, by Jean-Marc Valin, see below. */ + +/* Copyright (C) 2002-2006 Jean-Marc Valin + File: speexdec.c + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ +#include +#include +#include +#include + +#include "para.h" +#include "error.h" +#include "spx.h" + +/** + * Wrapper for speex_decoder_ctl(). + * + * \param state Decoder state. + * \param request ioctl-type request. + * \param ptr Value-result pointer. + * + * \return Standard. + */ +int spx_ctl(void *state, int request, void *ptr) +{ + int ret = speex_decoder_ctl(state, request, ptr); + + if (ret == 0) /* success */ + return 1; + if (ret == -1) + return -E_SPX_CTL_BAD_RQ; + return -E_SPX_CTL_INVAL; +} + +/** + * Obtain information about a speex file from an ogg packet. + * + * \param packet Start of the ogg packet. + * \param bytes Length of the ogg packet. + * \param shi Result pointer. + * + * \return Standard. + */ +int spx_process_header(unsigned char *packet, long bytes, + struct spx_header_info *shi) +{ + int ret; + spx_int32_t enh_enabled = 1; + SpeexHeader *h = speex_packet_to_header((char *)packet, bytes); + + if (!h) + return -E_SPX_HEADER; + ret = -E_SPX_HEADER_MODE; + if (h->mode >= SPEEX_NB_MODES || h->mode < 0) + goto out; + shi->mode = speex_lib_get_mode(h->mode); + + ret = -E_SPX_VERSION; + if (h->speex_version_id > 1) + goto out; + if (shi->mode->bitstream_version < h->mode_bitstream_version) + goto out; + if (shi->mode->bitstream_version > h->mode_bitstream_version) + goto out; + + ret = -E_SPX_DECODER_INIT; + shi->state = speex_decoder_init(shi->mode); + if (!shi->state) + goto out; + + ret = spx_ctl(shi->state, SPEEX_SET_ENH, &enh_enabled); + if (ret < 0) + goto out; + ret = spx_ctl(shi->state, SPEEX_GET_FRAME_SIZE, &shi->frame_size); + if (ret < 0) + goto out; + shi->sample_rate = h->rate; + ret = spx_ctl(shi->state, SPEEX_SET_SAMPLING_RATE, &shi->sample_rate); + if (ret < 0) + goto out; + shi->nframes = h->frames_per_packet; + shi->channels = h->nb_channels; + if (shi->channels != 1) { + shi->stereo = (SpeexStereoState)SPEEX_STEREO_STATE_INIT; + SpeexCallback callback = { + .callback_id = SPEEX_INBAND_STEREO, + .func = speex_std_stereo_request_handler, + .data = &shi->stereo, + }; + ret = spx_ctl(shi->state, SPEEX_SET_HANDLER, &callback); + if (ret < 0) + goto out; + shi->channels = 2; + } + ret = spx_ctl(shi->state, SPEEX_GET_BITRATE, &shi->bitrate); + if (ret < 0) + goto out; + PARA_NOTICE_LOG("%d Hz, %s, %s, %s, %d bits/s\n", + shi->sample_rate, shi->mode->modeName, + shi->channels == 1? "mono" : "stereo", + h->vbr? "vbr" : "cbr", + shi->bitrate + ); + shi->extra_headers = h->extra_headers; + ret = 1; +out: + free(h); + return ret; +} diff --git a/spxdec_filter.c b/spxdec_filter.c new file mode 100644 index 00000000..41b66c51 --- /dev/null +++ b/spxdec_filter.c @@ -0,0 +1,312 @@ +/* + * Copyright (C) 2002-2006 Jean-Marc Valin + * Copyright (C) 2010-2011 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file spxdec_filter.c Paraslash's ogg/speex decoder. */ + +/* This file is based on speexdec.c, by Jean-Marc Valin, see below. */ + +/* Copyright (C) 2002-2006 Jean-Marc Valin + File: speexdec.c + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#include "para.h" +#include "list.h" +#include "sched.h" +#include "ggo.h" +#include "buffer_tree.h" +#include "filter.h" +#include "error.h" +#include "string.h" +#include "spx.h" + +/** Data specific to the speexdec filter. */ +struct private_spxdec_data { + /** Header information obtained from the first ogg packet. */ + struct spx_header_info shi; + /** Read from and written to by the Speex routines. */ + SpeexBits bits; + /* Ogg pages are retrieved from this structure. */ + ogg_sync_state oy; + /** Extracted from header. */ + int speex_serialno; + /** Total number of ogg packets retrieved so far. */ + int packet_count; + /** Needed to find out how much to skip. */ + ogg_int64_t last_granule; + /** Also needed for skipping packets. */ + int lookahead; + /** The state information about the current stream. */ + ogg_stream_state os; + /** Whether \a os initialized. */ + bool stream_init; +}; + +static void spxdec_open(struct filter_node *fn) +{ + struct private_spxdec_data *psd = para_calloc(sizeof(*psd)); + + fn->private_data = psd; + fn->min_iqs = 200; + psd->speex_serialno = -1; +} + +static void speexdec_close(struct filter_node *fn) +{ + struct private_spxdec_data *psd = fn->private_data; + + if (psd->shi.state) { + /* Destroy the decoder state */ + speex_decoder_destroy(psd->shi.state); + /* Destroy the bit-stream struct */ + speex_bits_destroy(&psd->bits); + } + free(psd); + fn->private_data = NULL; +} + +static int speexdec_execute(struct btr_node *btrn, const char *cmd, + char **result) +{ + struct filter_node *fn = btr_context(btrn); + struct private_spxdec_data *psd = fn->private_data; + + return decoder_execute(cmd, psd->shi.sample_rate, psd->shi.channels, + result); +} + +static int speexdec_init(struct filter_node *fn) +{ + struct private_spxdec_data *psd = fn->private_data; + + PARA_INFO_LOG("init\n"); + ogg_sync_init(&psd->oy); + speex_bits_init(&psd->bits); + return 1; +} + +#if !defined(__LITTLE_ENDIAN__) && ( defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__) ) +#define le_short(s) ((short) ((unsigned short) (s) << 8) | ((unsigned short) (s) >> 8)) +#else +#define le_short(s) ((short) (s)) +#endif + +#define MAX_FRAME_SIZE 2000 +/* Copy Ogg packet to Speex bitstream */ +static int speexdec_write_frames(int packet_no, + struct private_spxdec_data *psd, int skip_samples, + int page_nb_packets, struct btr_node *btrn) +{ + int i, j; + + for (j = 0; j != psd->shi.nframes; j++) { + short output[MAX_FRAME_SIZE], *btr_output; + int skip = skip_samples + psd->lookahead, skip_idx = 0; + int samples, new_frame_size = psd->shi.frame_size; + + if (speex_decode_int(psd->shi.state, &psd->bits, output) < 0) + return -E_SPX_DECODE; + if (speex_bits_remaining(&psd->bits) < 0) + return -E_SPX_DECODE_OVERFLOW; + if (psd->shi.channels == 2) + speex_decode_stereo_int(output, psd->shi.frame_size, + &psd->shi.stereo); + if (packet_no == 1 && j == 0 && skip_samples > 0) { + new_frame_size -= skip; + skip_idx = skip * psd->shi.channels; + } + if (packet_no == page_nb_packets && skip_samples < 0) { + new_frame_size = psd->shi.nframes * psd->shi.frame_size + + skip - j * psd->shi.frame_size; + if (new_frame_size < 0) + new_frame_size = 0; + if (new_frame_size > psd->shi.frame_size) + new_frame_size = psd->shi.frame_size; + } + if (new_frame_size <= 0) + continue; + samples = new_frame_size * psd->shi.channels; + btr_output = para_malloc(2 * samples); + for (i = 0; i < samples; i++) + btr_output[i] = le_short(output[i + skip_idx]); + btr_add_output((char *)btr_output, samples * 2, btrn); + } + return 1; +} + +/* Extract all available packets */ +static int speexdec_extract_packets(struct private_spxdec_data *psd, + ogg_stream_state *os, int skip_samples, int page_nb_packets, + struct btr_node *btrn) +{ + int ret, packet_no; + bool eos = false; + + for (packet_no = 0;; psd->packet_count++) { + ogg_packet op; + + if (ogg_stream_packetout(os, &op) != 1) + return 0; + if (op.bytes >= 5 && !memcmp(op.packet, "Speex", 5)) + psd->speex_serialno = os->serialno; + if (psd->speex_serialno == -1) + return 0; + if (os->serialno != psd->speex_serialno) + return 0; + /* If first packet, process as Speex header */ + if (psd->packet_count == 0) { + ret = spx_process_header(op.packet, op.bytes, &psd->shi); + if (ret < 0) + return ret; + ret = speex_decoder_ctl(psd->shi.state, SPEEX_GET_LOOKAHEAD, + &psd->lookahead); + if (ret < 0) + return ret; + if (!psd->shi.nframes) + psd->shi.nframes = 1; + PARA_INFO_LOG("frame size: %d\n", psd->shi.frame_size); + continue; + } + if (psd->packet_count == 1) /* ignore comments */ + continue; + if (psd->packet_count <= 1 + psd->shi.extra_headers) + continue; /* Ignore extra headers */ + packet_no++; + /* check end of stream condition */ + if (op.e_o_s && os->serialno == psd->speex_serialno) + eos = true; + speex_bits_read_from(&psd->bits, (char *)op.packet, + op.bytes); + ret = speexdec_write_frames(packet_no, psd, + skip_samples, page_nb_packets, btrn); + if (ret < 0) + return ret; + if (eos == true) + return -E_SPX_EOS; + } +} + +static int compute_skip_samples(ogg_page *og, struct private_spxdec_data *psd) +{ + int ret, page_granule = ogg_page_granulepos(og); + + if (page_granule <= 0) + return 0; + if (psd->shi.frame_size == 0) + return 0; + ret = ogg_page_packets(og) * psd->shi.frame_size * psd->shi.nframes + - page_granule + psd->last_granule; + if (ogg_page_eos(og)) + ret = -ret; + return ret; +} + +static void speexdec_post_select(__a_unused struct sched *s, struct task *t) +{ + struct filter_node *fn = container_of(t, struct filter_node, task); + struct private_spxdec_data *psd = fn->private_data; + struct btr_node *btrn = fn->btrn; + int ret, ns; + ogg_page og; + char *btr_buf; + size_t nbytes; + +next_buffer: + t->error = 0; + ret = ns = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL); + btr_merge(btrn, fn->min_iqs); + if (!psd->shi.state) { + if (ret <= 0) + goto fail; + ret = speexdec_init(fn); + if (ret <= 0) + goto fail; + } + nbytes = btr_next_buffer(btrn, &btr_buf); + nbytes = PARA_MIN(nbytes, (size_t)200); + if (nbytes > 0) { + char *data = ogg_sync_buffer(&psd->oy, nbytes); + memcpy(data, btr_buf, nbytes); + btr_consume(btrn, nbytes); + ogg_sync_wrote(&psd->oy, nbytes); + } + /* Loop for all complete pages we got */ + while (ogg_sync_pageout(&psd->oy, &og) == 1) { + int skip_samples; + + if (psd->stream_init == false) { + ogg_stream_init(&psd->os, ogg_page_serialno(&og)); + psd->stream_init = true; + } + if (ogg_page_serialno(&og) != psd->os.serialno) + ogg_stream_reset_serialno(&psd->os, ogg_page_serialno(&og)); + /* Add page to the bitstream */ + ogg_stream_pagein(&psd->os, &og); + skip_samples = compute_skip_samples(&og, psd); + psd->last_granule = ogg_page_granulepos(&og); + ret = speexdec_extract_packets(psd, &psd->os, skip_samples, + ogg_page_packets(&og), btrn); + if (ret < 0) + goto fail; + } + if (ns > 0) + goto next_buffer; + ret = ns; +fail: + if (ret < 0) { + t->error = ret; + btr_remove_node(btrn); + } +} + +/** + * The init function of the ogg/speex decoder. + * + * \param f Its fields are filled in by the function. + */ +void spxdec_filter_init(struct filter *f) +{ + f->open = spxdec_open; + f->close = speexdec_close; + f->pre_select = generic_filter_pre_select; + f->post_select = speexdec_post_select; + f->execute = speexdec_execute; +} diff --git a/stat.c b/stat.c index 0306cba4..b125529a 100644 --- a/stat.c +++ b/stat.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -9,174 +9,73 @@ * and para_audiod. */ - -#include -#include +#include #include "para.h" -#include "close_on_fork.h" -#include "list.h" #include "error.h" #include "string.h" -#include "fd.h" - -/** The maximal number of simultaneous connections. */ -#define MAX_STAT_CLIENTS 50 -/** - * Describes a status client of para_audiod. - * - * There's one such structure per audiod client that sent the 'stat' command. - * - * A status client is identified by its file descriptor. para_audiod - * keeps a list of connected status clients. - */ -struct stat_client { - /** The stat client's file descriptor. */ - int fd; - /** Bitmask of those status items the client is interested in. */ - uint64_t item_mask; - /** Its entry in the list of stat clients. */ - struct list_head node; -}; -static struct list_head client_list; -static int initialized; -static int num_clients; +/** The minimal length of a status item buffer. */ +#define MIN_STAT_ITEM_LEN 9 /* 5 + 2 + 2, e.g. '0005 00:\n' */ -/** The list of all status items used by para_{server,audiod,gui}. */ -const char *status_item_list[] = {STATUS_ITEM_ARRAY}; - -static void dump_stat_client_list(void) -{ - struct stat_client *sc; - - if (!initialized) - return; - list_for_each_entry(sc, &client_list, node) - PARA_INFO_LOG("stat client on fd %d\n", sc->fd); -} /** - * Add a status client to the list. + * Call a function for each complete status item of a buffer. * - * \param fd The file descriptor of the client. - * \param mask Bitfield of status items for this client. + * \param item_buf The source buffer. + * \param num_bytes The length of \a buf. + * \param item_handler Function to call for each complete item. * - * Only those status items having the bit set in \a mask will be - * sent to the client. + * \return Negative on errors, the number of bytes _not_ passed to \a + * item_handler on success. * - * \return Positive value on success, or -E_TOO_MANY_CLIENTS if - * the number of connected clients exceeds #MAX_STAT_CLIENTS. + * Status items are expected in the format used by parser-friendly output mode + * of the stat command of para_client/para_audioc. */ -int stat_client_add(int fd, uint64_t mask) +int for_each_stat_item(char *item_buf, size_t num_bytes, + int (*item_handler)(int, char *)) { - struct stat_client *new_client; - - if (num_clients >= MAX_STAT_CLIENTS) { - PARA_ERROR_LOG("maximal number of stat clients (%d) exceeded\n", - MAX_STAT_CLIENTS); - return -E_TOO_MANY_CLIENTS; - } - if (!initialized) { - INIT_LIST_HEAD(&client_list); - initialized = 1; - } - PARA_INFO_LOG("adding client on fd %d\n", fd); - new_client = para_malloc(sizeof(struct stat_client)); - new_client->fd = fd; - new_client->item_mask = mask; - para_list_add(&new_client->node, &client_list); - dump_stat_client_list(); - num_clients++; - return 1; -} -/** - * Write a message to all connected status clients. - * - * \param msg A \p NULL terminated buffer. - * \param itemnum The number of the status item of \a msg. - * - * On write errors, remove the status client from the client list and close its - * file descriptor. - */ -void stat_client_write(const char *msg, int itemnum) -{ - struct stat_client *sc, *tmp; - size_t len = strlen(msg); - const uint64_t one = 1; - - if (!initialized || !len) - return; - list_for_each_entry_safe(sc, tmp, &client_list, node) { - int fd = sc->fd, ret; - - if (!((one << itemnum) & sc->item_mask)) - continue; - if (write_ok(fd) > 0) { - ret = write(fd, msg, len); - // PARA_DEBUG_LOG("dumped %s to fd %d, ret = %d\n", msg, fd, ret); - if (ret == len) + char *buf = item_buf; + int len = num_bytes; + + for (;;) { + int i, ret, item_len, item_num = 0; + if (len < MIN_STAT_ITEM_LEN) + break; + ret = read_size_header(buf); + if (ret < 0) + return ret; + item_len = ret; + if (item_len > len - 5) /* item not complete */ + break; + for (i = 0; i < 2; i++) { + unsigned char c = buf[5 + i]; + item_num <<= 4; + if (c >= '0' && c <= '9') { + item_num += c - '0'; continue; + } + if (c >= 'a' && c <= 'f') { + item_num += c - 'a' + 10; + continue; + } + return -E_STAT_ITEM_PARSE; } - /* write error or fd not ready for writing */ - close(fd); - num_clients--; - PARA_INFO_LOG("deleting client on fd %d\n", fd); - list_del(&sc->node); - free(sc); - dump_stat_client_list(); - } -// if (num_clients) -// PARA_DEBUG_LOG("%d client(s)\n", num_clients); -} - -/** - * Check if string is a known status item. - * - * \param item Buffer containing the text to check. - * - * \return If \a item is a valid status item, the number of that status item is - * returned. Otherwise, this function returns \p -E_UNKNOWN_STAT_ITEM. - */ -int stat_item_valid(const char *item) -{ - int i; - if (!item || !*item) { - PARA_ERROR_LOG("%s\n", "no item"); - return -E_UNKNOWN_STAT_ITEM; - } - FOR_EACH_STATUS_ITEM(i) - if (!strcmp(status_item_list[i], item)) - return i; - PARA_ERROR_LOG("invalid stat item: %s\n", item); - return -E_UNKNOWN_STAT_ITEM; -} - -/** - * Check if line starts with known status item. - * - * \param line Buffer containing the line. - * - * \return If the beginning of \a line matches any paraslash status item and is - * followed by a colon, the number of that status item is returned. Otherwise, - * this function returns \p -E_UNKNOWN_STAT_ITEM. - */ -int stat_line_valid(const char *line) -{ - int i; - size_t line_len; - - if (!line || !*line) - return -E_UNKNOWN_STAT_ITEM; - line_len = strlen(line); - FOR_EACH_STATUS_ITEM(i) { - const char *s = status_item_list[i]; - size_t item_len = strlen(s); - - if (line_len > item_len && line[item_len] == ':' && - !strncmp(line, s, item_len)) - return i; + if (buf[7] != ':' || buf[5 + item_len - 1] != '\n') + return -E_STAT_ITEM_PARSE; + buf[5 + item_len - 1] = '\0'; + if (item_num >= NUM_STAT_ITEMS) + PARA_WARNING_LOG("unknown status item %d: %s\n", + item_num, buf + 8); + else { + ret = item_handler(item_num, buf + 8); + if (ret < 0) + return ret; + } + buf += 5 + item_len; + len -= 5 + item_len; + assert(len >= 0 && buf <= item_buf + num_bytes); } - return -E_UNKNOWN_STAT_ITEM; + assert(len >= 0); + return len; } - diff --git a/stdin.c b/stdin.c index 14201a9a..e625f372 100644 --- a/stdin.c +++ b/stdin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -8,15 +8,17 @@ #include /* readdir() */ #include - +#include +#include #include "para.h" -#include "string.h" #include "list.h" #include "sched.h" #include "fd.h" #include "error.h" #include "stdin.h" +#include "buffer_tree.h" +#include "string.h" /** * The pre_select function of the stdin task. @@ -31,17 +33,17 @@ static void stdin_pre_select(struct sched *s, struct task *t) { struct stdin_task *sit = container_of(t, struct stdin_task, task); + int ret; - if (sit->output_error && *sit->output_error < 0) { - t->error = *sit->output_error; - return; - } t->error = 0; - sit->check_fd = 0; - if (sit->loaded >= sit->bufsize) + ret = btr_node_status(sit->btrn, 0, BTR_NT_ROOT); + if (ret < 0) + sched_min_delay(s); + if (ret <= 0) return; - sit->check_fd = 1; - para_fd_set(STDIN_FILENO, &s->rfds, &s->max_fileno); + if (btr_pool_unused(sit->btrp) > 0) + return para_fd_set(STDIN_FILENO, &s->rfds, &s->max_fileno); + sched_request_timeout_ms(100, s); } /** @@ -52,30 +54,40 @@ static void stdin_pre_select(struct sched *s, struct task *t) * * This function checks if \p STDIN_FILENO was included by in the read fd set * of \a s during the previous pre_select call. If yes, and \p STDIN_FILENO - * appears to be readable, data is read from stdin into the buffer of the - * stdin task. + * appears to be readable, data is read from stdin and fed into the buffer + * tree. */ static void stdin_post_select(struct sched *s, struct task *t) { struct stdin_task *sit = container_of(t, struct stdin_task, task); ssize_t ret; + size_t sz, n; + char *buf = NULL; - if (sit->output_error && *sit->output_error < 0) { - t->error = *sit->output_error; - return; - } t->error = 0; - if (!sit->check_fd) + ret = btr_node_status(sit->btrn, 0, BTR_NT_ROOT); + if (ret < 0) + goto err; + if (ret == 0) return; - if (!FD_ISSET(STDIN_FILENO, &s->rfds)) + sz = btr_pool_get_buffer(sit->btrp, &buf); + if (sz == 0) return; - ret = read(STDIN_FILENO, sit->buf + sit->loaded, sit->bufsize - sit->loaded); - if (ret < 0) - t->error = -ERRNO_TO_PARA_ERROR(errno); - else if (ret > 0) - sit->loaded += ret; - else - t->error = -E_STDIN_EOF; + /* + * Do not use the maximal size to avoid having only a single buffer + * reference for the whole pool. This is bad because if that single + * reference can not be freed, we're stuck. + */ + sz = PARA_MIN(sz, btr_pool_size(sit->btrp) / 2); + ret = read_nonblock(STDIN_FILENO, buf, sz, &s->rfds, &n); + if (n > 0) + btr_add_output_pool(sit->btrp, n, sit->btrn); + if (ret >= 0) + return; +err: + btr_remove_node(sit->btrn); + //btr_pool_free(sit->btrp); + t->error = ret; } /** @@ -85,20 +97,19 @@ static void stdin_post_select(struct sched *s, struct task *t) * * This fills in the pre/post select function pointers of the task structure * given by \a sit. Moreover, the stdin file desctiptor is set to nonblocking - * mode and \a bufsize is initialized to 16 KB (but no buffer is allocated). + * mode, and a buffer tree is created. */ void stdin_set_defaults(struct stdin_task *sit) { int ret; - sit->bufsize = 32 * 1024, sit->task.pre_select = stdin_pre_select; sit->task.post_select = stdin_post_select; + sit->btrp = btr_pool_new("stdin", 64 * 1024); sprintf(sit->task.status, "stdin reader"); ret = mark_fd_nonblocking(STDIN_FILENO); if (ret >= 0) return; - sit->output_error = NULL; PARA_EMERG_LOG("%s\n", para_strerror(-ret)); exit(EXIT_FAILURE); } diff --git a/stdin.h b/stdin.h index edb72cf8..2d279a6e 100644 --- a/stdin.h +++ b/stdin.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -8,18 +8,12 @@ /** The task structure used for reading from stdin. */ struct stdin_task { - /** Input buffer. */ - char *buf; - /** The size of \a buf. */ - size_t bufsize; - /** Number of bytes currently loaded in \a buf. */ - size_t loaded; - /** Pointer to the error member of the consumer. */ - int *output_error; - /** Whether \p STDIN_FILENO was included in the read fd set. */ - int check_fd; /** The task structure. */ struct task task; + /** Stdin is always the root of a buffer tree. */ + struct btr_node *btrn; + /* Use a buffer pool to minimize memcpy due to alignment problems. */ + struct btr_pool *btrp; }; void stdin_set_defaults(struct stdin_task *sit); diff --git a/stdout.c b/stdout.c index e178fe0e..5604fced 100644 --- a/stdout.c +++ b/stdout.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -8,14 +8,15 @@ #include /* readdir() */ #include +#include #include "para.h" -#include "string.h" #include "list.h" #include "sched.h" #include "fd.h" #include "error.h" #include "stdout.h" +#include "buffer_tree.h" /** * The pre_select function of the stdout task. @@ -23,25 +24,20 @@ * \param s The scheduler this task was registered to. * \param t The task structure of the stdout task. * - * This function is always successful. If there is data available in the input - * buffer, it adds \p STDOUT_FILENO to the write fd set of \a s. + * This function is always successful. If there is input data available, it + * adds \p STDOUT_FILENO to the write fd set of \a s. */ static void stdout_pre_select(struct sched *s, struct task *t) { struct stdout_task *sot = container_of(t, struct stdout_task, task); + int ret; t->error = 0; - sot->check_fd = 0; - if (!*sot->loaded) { - if (*sot->input_error < 0) { - t->error = *sot->input_error; - s->timeout.tv_sec = 0; - s->timeout.tv_usec = 1; - } - return; - } - sot->check_fd = 1; - para_fd_set(STDOUT_FILENO, &s->wfds, &s->max_fileno); + ret = btr_node_status(sot->btrn, 0, BTR_NT_LEAF); + if (ret > 0) + para_fd_set(STDOUT_FILENO, &s->wfds, &s->max_fileno); + else if (ret < 0) + sched_min_delay(s); } /** @@ -50,41 +46,47 @@ static void stdout_pre_select(struct sched *s, struct task *t) * \param s The scheduler this task was registered to. * \param t The task structure of the stdout task. * - * This function checks if \p STDOUT_FILENO was included by in the write fd set - * of \a s during the previous pre_select call. If yes, and \p STDOUT_FILENO - * appeears to be writable, the data loaded in the input buffer is written to - * stdout. + * This function writes input data from the buffer tree to stdout if \p + * STDOUT_FILENO is writable. */ static void stdout_post_select(struct sched *s, struct task *t) { struct stdout_task *sot = container_of(t, struct stdout_task, task); - ssize_t ret; + struct btr_node *btrn = sot->btrn; + int ret; + char *buf; + size_t sz; t->error = 0; - if (!sot->check_fd) { - if (!*sot->loaded && *sot->input_error < 0) - t->error = *sot->input_error; + ret = btr_node_status(btrn, 0, BTR_NT_LEAF); + if (ret < 0) + goto out; + if (ret == 0) return; - } if (!FD_ISSET(STDOUT_FILENO, &s->wfds)) return; - ret = write(STDOUT_FILENO, *sot->bufp, *sot->loaded); - if (ret < 0) { - t->error = -ERRNO_TO_PARA_ERROR(errno); - return; + + for (;;) { + sz = btr_next_buffer(btrn, &buf); + if (sz == 0) + break; + ret = write_nonblock(STDOUT_FILENO, buf, sz); + if (ret <= 0) + break; + btr_consume(btrn, ret); } - *sot->loaded -= ret; - if (*sot->loaded) - memmove(*sot->bufp, *sot->bufp + ret, *sot->loaded); +out: + if (ret < 0) + btr_remove_node(btrn); + t->error = ret; } - /** * Initialize a stdout task structure with default values. * * \param sot The stdout task structure. * - * This fills in the pre/post select function poinzters of the task structure - * given by \a sot. + * This fills in the pre/post select function pointers of the task structure + * given by \a sot and sets the stdout file descriptor to nonblocking mode. */ void stdout_set_defaults(struct stdout_task *sot) { @@ -92,7 +94,7 @@ void stdout_set_defaults(struct stdout_task *sot) sot->task.pre_select = stdout_pre_select; sot->task.post_select = stdout_post_select; - sprintf(sot->task.status, "stdout writer"); + sprintf(sot->task.status, "stdout"); ret = mark_fd_nonblocking(STDOUT_FILENO); if (ret >= 0) return; diff --git a/stdout.h b/stdout.h index cca12e5f..1e35d6ca 100644 --- a/stdout.h +++ b/stdout.h @@ -1,25 +1,21 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ -/** \file stdout.h The standard out task structure. */ +/** \file stdout.h Writing to stdout via buffer trees. */ /** * The task structure used for writing to stdout. + * + * This is used by para_recv, para_filter and para_client. */ struct stdout_task { - /** Pointer to the data buffer pointer. */ - char **bufp; - /** Number of bytes loaded in \a buf. */ - size_t *loaded; - /** Pointer to the error variable of the feeding task. */ - int *input_error; - /** The task structure. */ + /** The task structure used by the scheduler. */ struct task task; - /** Whether \p STDOUT_FILENO was included in the write fd set. */ - int check_fd; + /** Stdout is always a leaf node in the buffer tree. */ + struct btr_node *btrn; }; void stdout_set_defaults(struct stdout_task *sot); diff --git a/string.c b/string.c index 1d8f69b9..813999bb 100644 --- a/string.c +++ b/string.c @@ -1,19 +1,19 @@ /* - * Copyright (C) 2004-2009 Andre Noll + * Copyright (C) 2004-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file string.c Memory allocation and string handling functions. */ -#include "para.h" -#include "string.h" - #include /* gettimeofday */ #include #include /* uname() */ #include +#include +#include "para.h" +#include "string.h" #include "error.h" /** @@ -135,6 +135,20 @@ __must_check __printf_1_2 __malloc char *make_message(const char *fmt, ...) return msg; } +/** + * Free the content of a pointer and set it to \p NULL. + * + * This is equivalent to "free(*arg); *arg = NULL;". + * + * \param arg The pointer whose content should be freed. + */ +void freep(void *arg) +{ + void **ptr = (void **)arg; + free(*ptr); + *ptr = NULL; +} + /** * Paraslash's version of strcat(). * @@ -200,15 +214,15 @@ __must_check __malloc char *para_dirname(const char *name) * ends with a slash. Otherwise, a pointer within \a name is returned. Caller * must not free the result. */ -__must_check const char *para_basename(const char *name) +__must_check char *para_basename(const char *name) { - const char *ret; + char *ret; if (!name || !*name) return NULL; ret = strrchr(name, '/'); if (!ret) - return name; + return (char *)name; ret++; return ret; } @@ -231,27 +245,6 @@ void chop(char *buf) buf[n - 1] = '\0'; } -/** - * Get a random filename. - * - * This is by no means a secure way to create temporary files in a hostile - * directory like \p /tmp. However, it is OK to use for temp files, fifos, - * sockets that are created in ~/.paraslash. Result must be freed by the - * caller. - * - * \return A pointer to a random filename. - */ -__must_check __malloc char *para_tmpname(void) -{ - struct timeval now; - unsigned int seed; - - gettimeofday(&now, NULL); - seed = now.tv_usec; - srand(seed); - return make_message("%08i", rand()); -} - /** * Get the logname of the current user. * @@ -279,56 +272,6 @@ __must_check __malloc char *para_homedir(void) return para_strdup(pw? pw->pw_dir : "/tmp"); } -/** - * Split string and return pointers to its parts. - * - * \param args The string to be split. - * \param argv_ptr Pointer to the list of substrings. - * \param delim Delimiter. - * - * This function modifies \a args by replacing each occurrence of \a delim by - * zero. A \p NULL-terminated array of pointers to char* is allocated dynamically - * and these pointers are initialized to point to the broken-up substrings - * within \a args. A pointer to this array is returned via \a argv_ptr. - * - * \return The number of substrings found in \a args. - */ -unsigned split_args(char *args, char *** const argv_ptr, const char *delim) -{ - char *p; - char **argv; - size_t n = 0, i, j; - - p = args + strspn(args, delim); - for (;;) { - i = strcspn(p, delim); - if (!i) - break; - p += i; - n++; - p += strspn(p, delim); - } - *argv_ptr = para_malloc((n + 1) * sizeof(char *)); - argv = *argv_ptr; - i = 0; - p = args + strspn(args, delim); - while (p) { - argv[i] = p; - j = strcspn(p, delim); - if (!j) - break; - p += strcspn(p, delim); - if (*p) { - *p = '\0'; - p++; - p += strspn(p, delim); - } - i++; - } - argv[n] = NULL; - return n; -} - /** * Get the own hostname. * @@ -457,6 +400,53 @@ int for_each_line_ro(char *buf, size_t size, line_handler_t *line_handler, private_data); } +/** Return the hex characters of the lower 4 bits. */ +#define hex(a) (hexchar[(a) & 15]) + +static void write_size_header(char *buf, int n) +{ + static char hexchar[] = "0123456789abcdef"; + + buf[0] = hex(n >> 12); + buf[1] = hex(n >> 8); + buf[2] = hex(n >> 4); + buf[3] = hex(n); + buf[4] = ' '; +} + +/** + * Read a four-byte hex-number and return its value. + * + * Each status item sent by para_server is prefixed with such a hex number in + * ASCII which describes the size of the status item. + * + * \param buf The buffer which must be at least four bytes long. + * + * \return The value of the hex number on success, \p -E_SIZE_PREFIX if the + * buffer did not contain only hex digits. + */ +int read_size_header(const char *buf) +{ + int i, len = 0; + + for (i = 0; i < 4; i++) { + unsigned char c = buf[i]; + len <<= 4; + if (c >= '0' && c <= '9') { + len += c - '0'; + continue; + } + if (c >= 'a' && c <= 'f') { + len += c - 'a' + 10; + continue; + } + return -E_SIZE_PREFIX; + } + if (buf[4] != ' ') + return -E_SIZE_PREFIX; + return len; +} + /** * Safely print into a buffer at a given offset. * @@ -472,7 +462,8 @@ int for_each_line_ro(char *buf, size_t size, line_handler_t *line_handler, * private_data pointer of \a b are passed to the \a max_size_handler of \a b. * If this function succeeds, i.e. returns a non-negative value, the offset of * \a b is reset to zero and the given data is written to the beginning of the - * buffer. + * buffer. If \a max_size_handler() returns a negative value, this value is + * returned by \a para_printf(). * * Upon return, the offset of \a b is adjusted accordingly so that subsequent * calls to this function append data to what is already contained in the @@ -482,13 +473,15 @@ int for_each_line_ro(char *buf, size_t size, line_handler_t *line_handler, * initial buffer is allocated. * * \return The number of bytes printed into the buffer (not including the - * terminating \p NULL byte). + * terminating \p NULL byte) on success, negative on errors. If there is no + * size-bound on \a b, i.e. if \p b->max_size is zero, this function never + * fails. * * \sa make_message(), vsnprintf(3). */ __printf_2_3 int para_printf(struct para_buffer *b, const char *fmt, ...) { - int ret; + int ret, sz_off = (b->flags & PBF_SIZE_PREFIX)? 5 : 0; if (!b->buf) { b->buf = para_malloc(128); @@ -499,13 +492,16 @@ __printf_2_3 int para_printf(struct para_buffer *b, const char *fmt, ...) char *p = b->buf + b->offset; size_t size = b->size - b->offset; va_list ap; - if (size) { + + if (size > sz_off) { va_start(ap, fmt); - ret = vsnprintf(p, size, fmt, ap); + ret = vsnprintf(p + sz_off, size - sz_off, fmt, ap); va_end(ap); - if (ret > -1 && ret < size) { /* success */ - b->offset += ret; - return ret; + if (ret > -1 && ret < size - sz_off) { /* success */ + b->offset += ret + sz_off; + if (sz_off) + write_size_header(p, ret); + return ret + sz_off; } } /* check if we may grow the buffer */ @@ -618,3 +614,178 @@ int get_loglevel_by_name(const char *txt) return LL_EMERG; return -1; } + +static int get_next_word(const char *buf, const char *delim, char **word) +{ + enum line_state_flags {LSF_HAVE_WORD = 1, LSF_BACKSLASH = 2, + LSF_SINGLE_QUOTE = 4, LSF_DOUBLE_QUOTE = 8}; + const char *in; + char *out; + int ret, state = 0; + + out = para_malloc(strlen(buf) + 1); + *out = '\0'; + *word = out; + for (in = buf; *in; in++) { + const char *p; + + switch (*in) { + case '\\': + if (state & LSF_BACKSLASH) /* \\ */ + goto copy_char; + state |= LSF_BACKSLASH; + state |= LSF_HAVE_WORD; + continue; + case 'n': + case 't': + if (state & LSF_BACKSLASH) { /* \n or \t */ + *out++ = (*in == 'n')? '\n' : '\t'; + state &= ~LSF_BACKSLASH; + continue; + } + goto copy_char; + case '"': + if (state & LSF_BACKSLASH) /* \" */ + goto copy_char; + if (state & LSF_SINGLE_QUOTE) /* '" */ + goto copy_char; + if (state & LSF_DOUBLE_QUOTE) { + state &= ~LSF_DOUBLE_QUOTE; + continue; + } + state |= LSF_HAVE_WORD; + state |= LSF_DOUBLE_QUOTE; + continue; + case '\'': + if (state & LSF_BACKSLASH) /* \' */ + goto copy_char; + if (state & LSF_DOUBLE_QUOTE) /* "' */ + goto copy_char; + if (state & LSF_SINGLE_QUOTE) { + state &= ~LSF_SINGLE_QUOTE; + continue; + } + state |= LSF_HAVE_WORD; + state |= LSF_SINGLE_QUOTE; + continue; + } + for (p = delim; *p; p++) { + if (*in != *p) + continue; + if (state & LSF_BACKSLASH) + goto copy_char; + if (state & LSF_SINGLE_QUOTE) + goto copy_char; + if (state & LSF_DOUBLE_QUOTE) + goto copy_char; + if (state & LSF_HAVE_WORD) + goto success; + break; + } + if (*p) /* ignore delimiter at the beginning */ + continue; +copy_char: + state |= LSF_HAVE_WORD; + *out++ = *in; + state &= ~LSF_BACKSLASH; + } + ret = 0; + if (!(state & LSF_HAVE_WORD)) + goto out; + ret = -ERRNO_TO_PARA_ERROR(EINVAL); + if (state & LSF_BACKSLASH) { + PARA_ERROR_LOG("trailing backslash\n"); + goto out; + } + if ((state & LSF_SINGLE_QUOTE) || (state & LSF_DOUBLE_QUOTE)) { + PARA_ERROR_LOG("unmatched quote character\n"); + goto out; + } +success: + *out = '\0'; + return in - buf; +out: + free(*word); + *word = NULL; + return ret; +} + +/** + * Free an array of words created by create_argv(). + * + * \param argv A pointer previously obtained by \ref create_argv(). + */ +void free_argv(char **argv) +{ + int i; + + for (i = 0; argv[i]; i++) + free(argv[i]); + free(argv); +} + +/** + * Split a buffer into words. + * + * This parser honors single and double quotes, backslash-escaped characters + * and special characters like \p \\n. The result contains pointers to copies + * of the words contained in \a buf and has to be freed by using \ref + * free_argv(). + * + * \param buf The buffer to be split. + * \param delim Each character in this string is treated as a separator. + * \param result The array of words is returned here. + * + * \return Number of words in \a buf, negative on errors. + */ +int create_argv(const char *buf, const char *delim, char ***result) +{ + char *word, **argv = para_malloc(2 * sizeof(char *)); + const char *p; + int ret, num_words; + + for (p = buf, num_words = 0; ; p += ret, num_words++) { + ret = get_next_word(p, delim, &word); + if (ret < 0) + goto err; + if (!ret) + break; + argv = para_realloc(argv, (num_words + 2) * sizeof(char*)); + argv[num_words] = word; + } + argv[num_words] = NULL; + *result = argv; + return num_words; +err: + while (num_words > 0) + free(argv[--num_words]); + free(argv); + return ret; +} + +/** + * Compile a regular expression. + * + * This simple wrapper calls regcomp() and logs a message on errors. + * + * \param preg See regcomp(3). + * \param regex See regcomp(3). + * \param cflags See regcomp(3). + * + * \return Standard. + */ +int para_regcomp(regex_t *preg, const char *regex, int cflags) +{ + char *buf; + size_t size; + int ret = regcomp(preg, regex, cflags); + + if (ret == 0) + return 1; + size = regerror(ret, preg, NULL, 0); + buf = para_malloc(size); + regerror(ret, preg, buf, size); + PARA_ERROR_LOG("%s\n", buf); + free(buf); + return -E_REGEX; +} diff --git a/string.h b/string.h index 272f107d..06f4d3e6 100644 --- a/string.h +++ b/string.h @@ -1,11 +1,17 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file string.h exported sybmols from string.c */ +/** Flags that change how content is printed into the buffer. */ +enum para_buffer_flags { + /** Prefix each buffer with its length. */ + PBF_SIZE_PREFIX = 1, +}; + /** A string buffer used for para_printf(). */ struct para_buffer { /** The buffer. May be \p NULL. */ @@ -14,6 +20,8 @@ struct para_buffer { size_t size; /** The maximal size this buffer may grow. Zero means unlimited. */ size_t max_size; + /** \sa para_buffer_flags. */ + unsigned flags; /** The next para_printf() will write at this offset. */ size_t offset; /** @@ -26,6 +34,28 @@ struct para_buffer { void *private_data; }; +/** + * Write the contents of a status item to a para_buffer. + * + * \param b The para_buffer. + * \param n The number of the status item. + * \param f A format string. + * + * \return The return value of the underlying call to para_printf(). + */ +#define WRITE_STATUS_ITEM(b, n, f, ...) (\ +{ \ + int _ret; \ + if ((b)->flags & PBF_SIZE_PREFIX) { \ + _ret = para_printf((b), "%02x:" f, n, ## __VA_ARGS__); \ + } else { \ + _ret = para_printf((b), "%s: " f, status_item_list[(n)], \ + ## __VA_ARGS__); \ + } \ + _ret; \ +} \ +) + __must_check __malloc void *para_realloc(void *p, size_t size); __must_check __malloc void *para_malloc(size_t size); __must_check __malloc void *para_calloc(size_t size); @@ -33,12 +63,10 @@ __must_check __malloc char *para_strdup(const char *s); __must_check __malloc __printf_1_2 char *make_message(const char *fmt, ...); __must_check __malloc char *para_strcat(char *a, const char *b); __must_check __malloc char *para_dirname(const char *name); -__must_check const char *para_basename(const char *name); +__must_check char *para_basename(const char *name); void chop(char *buf); -__must_check __malloc char *para_tmpname(void); __must_check __malloc char *para_logname(void); __must_check __malloc char *para_homedir(void); -unsigned split_args(char *args, char *** const argv_ptr, const char *delim); __malloc char *para_hostname(void); __printf_2_3 int para_printf(struct para_buffer *b, const char *fmt, ...); /** Used for for_each_line() and for_each_line_ro(). */ @@ -50,3 +78,8 @@ int for_each_line_ro(char *buf, size_t size, line_handler_t *line_handler, int para_atoi64(const char *str, int64_t *result); int para_atoi32(const char *str, int32_t *value); int get_loglevel_by_name(const char *txt); +int read_size_header(const char *buf); +int create_argv(const char *buf, const char *delim, char ***result); +void free_argv(char **argv); +int para_regcomp(regex_t *preg, const char *regex, int cflags); +void freep(void *arg); diff --git a/time.c b/time.c index 0cb9babe..19bf469c 100644 --- a/time.c +++ b/time.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -131,43 +131,44 @@ void tv_divide(const unsigned long divisor, const struct timeval *tv, /** * Compute a convex combination of two time values. * - * \param a The first coefiicent. + * \param a The first coefficient. * \param tv1 The first time value. - * \param b The second coefiicent. + * \param b The second coefficient. * \param tv2 The second time value. * \param result Contains the convex combination upon return. * - * compute x := (a * tv1 + b * tv2) / (|a| + |b|) and store |x| in \a result. + * Compute x := (a * tv1 + b * tv2) / (|a| + |b|) and store |x| in \a result. * Both \a a and \a b may be negative. * - * \return One if \a x is positive, -1 otherwise. + * \return Zero, 1 or -1, if \a x is zero, positive or negative, respectively. */ int tv_convex_combination(const long a, const struct timeval *tv1, const long b, const struct timeval *tv2, struct timeval *result) { struct timeval tmp1, tmp2, tmp3; - int ret = 1, subtract = ((a > 0 && b < 0) || (a < 0 && b > 0)); - unsigned long a1 = PARA_ABS(a), b1 = PARA_ABS(b); + int ret = 1; + unsigned long a1, b1; + if (a == 0 && b == 0) { + result->tv_sec = 0; + result->tv_usec = 0; + return 0; + } + a1 = PARA_ABS(a); + b1 = PARA_ABS(b); tv_scale(a1, tv1, &tmp1); tv_scale(b1, tv2, &tmp2); - if (subtract) + if ((a > 0 && b < 0) || (a < 0 && b > 0)) /* subtract */ ret = tv_diff(&tmp1, &tmp2, &tmp3); else tv_add(&tmp1, &tmp2, &tmp3); - if (a1 + b1) - tv_divide(a1 + b1, &tmp3, result); - else { - result->tv_sec = 0; - result->tv_usec = 0; - } + tv_divide(a1 + b1, &tmp3, result); if (!a || !b) { if (a + b < 0) ret = -1; - } else - if (a < 0) - ret = -ret; + } else if (a < 0) + ret = -ret; return ret; } diff --git a/udp_recv.c b/udp_recv.c index e4d23404..e276343e 100644 --- a/udp_recv.c +++ b/udp_recv.c @@ -1,10 +1,11 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file udp_recv.c Paraslash's udp receiver */ +#include #include #include #include @@ -17,13 +18,11 @@ #include "ggo.h" #include "recv.h" #include "udp_recv.cmdline.h" -#include "audiod.h" #include "string.h" #include "net.h" #include "fd.h" +#include "buffer_tree.h" -/** The size of the receiver node buffer. */ -#define UDP_RECV_CHUNK_SIZE (128 * 1024) /** * Data specific to the udp receiver. * @@ -32,6 +31,7 @@ struct private_udp_recv_data { /** The socket file descriptor. */ int fd; + struct btr_pool *btrp; }; static void udp_recv_pre_select(struct sched *s, struct task *t) @@ -39,63 +39,68 @@ static void udp_recv_pre_select(struct sched *s, struct task *t) struct receiver_node *rn = container_of(t, struct receiver_node, task); struct private_udp_recv_data *purd = rn->private_data; + if (generic_recv_pre_select(s, t) <= 0) + return; para_fd_set(purd->fd, &s->rfds, &s->max_fileno); } -static int enough_space(size_t nbytes, size_t loaded) -{ - return nbytes + loaded < UDP_RECV_CHUNK_SIZE; -} - -static int add_rn_output(struct receiver_node *rn, char *buf, size_t len) +static int udp_check_eof(size_t sz, struct iovec iov[2]) { - if (!len) - return 1; - if (!enough_space(len, rn->loaded)) - return -E_UDP_OVERRUN; - memcpy(rn->buf + rn->loaded, buf, len); - rn->loaded += len; - return 1; + if (sz < FEC_EOF_PACKET_LEN) + return 0; + if (iov[0].iov_len >= FEC_EOF_PACKET_LEN) { + if (memcmp(iov[0].iov_base, FEC_EOF_PACKET, + FEC_EOF_PACKET_LEN) != 0) + return 0; + return -E_RECV_EOF; + } + if (memcmp(iov[0].iov_base, FEC_EOF_PACKET, iov[0].iov_len) != 0) + return 0; + if (memcmp(iov[1].iov_base, FEC_EOF_PACKET + iov[0].iov_len, + FEC_EOF_PACKET_LEN - iov[0].iov_len) != 0) + return 0; + return -E_RECV_EOF; } static void udp_recv_post_select(__a_unused struct sched *s, struct task *t) { struct receiver_node *rn = container_of(t, struct receiver_node, task); struct private_udp_recv_data *purd = rn->private_data; - int ret; - char tmpbuf[UDP_RECV_CHUNK_SIZE]; - size_t packet_size; - - if (rn->output_error && *rn->output_error < 0) { - t->error = *rn->output_error; - return; - } - if (!FD_ISSET(purd->fd, &s->rfds)) - return; - ret = recv_bin_buffer(purd->fd, tmpbuf, UDP_RECV_CHUNK_SIZE); - if (ret < 0) { - if (is_errno(ret, EINTR) || is_errno(ret, EAGAIN)) - goto success; - t->error = ret; - return; + struct btr_node *btrn = rn->btrn; + size_t num_bytes; + struct iovec iov[2]; + int ret, readv_ret, iovcnt; + + t->error = 0; + ret = btr_node_status(btrn, 0, BTR_NT_ROOT); + if (ret <= 0) + goto out; + iovcnt = btr_pool_get_buffers(purd->btrp, iov); + ret = -E_UDP_OVERRUN; + if (iovcnt == 0) + goto out; + ret = readv_nonblock(purd->fd, iov, iovcnt, &s->rfds, &num_bytes); + if (num_bytes == 0) + goto out; + readv_ret = ret; + ret = udp_check_eof(num_bytes, iov); + if (ret < 0) + goto out; + if (iov[0].iov_len >= num_bytes) + btr_add_output_pool(purd->btrp, num_bytes, btrn); + else { /* both buffers contain data */ + btr_add_output_pool(purd->btrp, iov[0].iov_len, btrn); + btr_add_output_pool(purd->btrp, num_bytes - iov[0].iov_len, + btrn); } - t->error = -E_RECV_EOF; - if (!ret) - return; - packet_size = ret; - if (packet_size >= FEC_EOF_PACKET_LEN) - if (!memcmp(tmpbuf, FEC_EOF_PACKET, FEC_EOF_PACKET_LEN)) - return; - t->error = add_rn_output(rn, tmpbuf, packet_size); - if (t->error < 0) + ret = readv_ret; +out: + if (ret >= 0) return; -success: - t->error = 1; -} - -static void udp_shutdown(void) -{ - return; + btr_remove_node(btrn); + t->error = ret; + close(purd->fd); + purd->fd = -1; } static void udp_recv_close(struct receiver_node *rn) @@ -104,8 +109,8 @@ static void udp_recv_close(struct receiver_node *rn) if (purd->fd >= 0) close(purd->fd); + btr_pool_free(purd->btrp); free(rn->private_data); - free(rn->buf); } static void *udp_recv_parse_config(int argc, char **argv) @@ -137,9 +142,11 @@ static int mcast_receiver_setup(int fd, const char *iface) if (getsockname(fd, (struct sockaddr *)&ss, &sslen) < 0) goto err; + assert(ss.ss_family == AF_INET || ss.ss_family == AF_INET6); if (iface != NULL && id == 0) - PARA_WARNING_LOG("could not resolve interface %s, using default", iface); + PARA_WARNING_LOG("could not resolve interface %s, using default\n", + iface); switch (ss.ss_family) { case AF_INET: @@ -154,12 +161,13 @@ static int mcast_receiver_setup(int fd, const char *iface) m4.imr_interface.s_addr = INADDR_ANY; if (id != 0) - PARA_ERROR_LOG("Setting IPv4 receiver mcast interface not supported."); + PARA_ERROR_LOG("Setting IPv4 receiver mcast interface not supported\n"); #endif - m4.imr_multiaddr = ((struct sockaddr_in *)&ss)->sin_addr; + m4.imr_multiaddr = ((struct sockaddr_in *)&ss)->sin_addr; - if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &m4, sizeof(m4)) < 0) + if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, + &m4, sizeof(m4)) < 0) break; } return 0; @@ -174,9 +182,6 @@ static int mcast_receiver_setup(int fd, const char *iface) break; } return 0; - default: - PARA_ERROR_LOG("address family %d not supported", ss.ss_family); - return -E_ADDRESS_LOOKUP; } err: return -ERRNO_TO_PARA_ERROR(errno); @@ -189,11 +194,10 @@ static int udp_recv_open(struct receiver_node *rn) char *iface = c->iface_given ? c->iface_arg : NULL; int ret; - rn->buf = para_calloc(UDP_RECV_CHUNK_SIZE); rn->private_data = para_calloc(sizeof(struct private_udp_recv_data)); purd = rn->private_data; - ret = makesock(AF_UNSPEC, IPPROTO_UDP, 1, c->host_arg, c->port_arg); + ret = makesock(IPPROTO_UDP, 1, c->host_arg, c->port_arg, NULL); if (ret < 0) goto err; purd->fd = ret; @@ -201,21 +205,29 @@ static int udp_recv_open(struct receiver_node *rn) ret = mcast_receiver_setup(purd->fd, iface); if (ret < 0) { close(purd->fd); - return ret; + goto err; } ret = mark_fd_nonblocking(purd->fd); - if (ret < 0) + if (ret < 0) { + close(purd->fd); goto err; - PARA_NOTICE_LOG("receiving from %s:%d, fd=%d\n", c->host_arg, + } + PARA_INFO_LOG("receiving from %s:%d, fd=%d\n", c->host_arg, c->port_arg, purd->fd); + purd->btrp = btr_pool_new("udp_recv", 320 * 1024); return purd->fd; err: free(rn->private_data); - free(rn->buf); return ret; } +static void udp_recv_free_config(void *conf) +{ + udp_recv_cmdline_parser_free(conf); + free(conf); +} + /** * The init function of the udp receiver. * @@ -228,12 +240,12 @@ void udp_recv_init(struct receiver *r) struct udp_recv_args_info dummy; udp_recv_cmdline_parser_init(&dummy); - r->shutdown = udp_shutdown; r->open = udp_recv_open; r->close = udp_recv_close; r->pre_select = udp_recv_pre_select; r->post_select = udp_recv_post_select; r->parse_config = udp_recv_parse_config; + r->free_config = udp_recv_free_config; r->help = (struct ggo_help) { .short_help = udp_recv_args_info_help, .detailed_help = udp_recv_args_info_detailed_help diff --git a/udp_send.c b/udp_send.c index 8677ec5e..4fb10b44 100644 --- a/udp_send.c +++ b/udp_send.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -7,10 +7,14 @@ /** \file udp_send.c Para_server's udp sender. */ +#include #include #include #include +#include #include +#include +#include #include "server.cmdline.h" #include "para.h" @@ -19,28 +23,26 @@ #include "afh.h" #include "afs.h" #include "server.h" -#include "vss.h" #include "list.h" #include "send.h" +#include "vss.h" #include "portable_io.h" #include "net.h" #include "fd.h" #include "sched.h" #include "close_on_fork.h" -#include "chunk_queue.h" + +/** + * Time window during which ICMP Destination/Port Unreachable messages are + * ignored, covering transient receiver problems such as restarting the + * client, rebooting, reconfiguration, or handover. + */ +#define UDP_MAX_UNREACHABLE_TIME 30 /** Describes one entry in the list of targets for the udp sender. */ struct udp_target { - /** The position of this target in the list of targets. */ - struct list_head node; - /** The hostname (DNS name or IPv4/v6 address string). */ - char host[MAX_HOSTLEN]; - /** The UDP port. */ - int port; - /** The socket fd. */ - int fd; - /** The list of queued chunks for this fd. */ - struct chunk_queue *cq; + /** Track time (seconds) of last ICMP Port Unreachable error */ + time_t last_unreachable; /** The opaque structure returned by vss_add_fec_client(). */ struct fec_client *fc; /** The FEC parameters for this target. */ @@ -50,50 +52,56 @@ struct udp_target { static struct list_head targets; static int sender_status; -static void udp_close_target(struct udp_target *ut) +static void udp_close_target(struct sender_client *sc) { - if (ut->fd < 0) - return; - close(ut->fd); - del_close_on_fork_list(ut->fd); - cq_destroy(ut->cq); - ut->cq = NULL; - ut->fd = -1; + const char *buf; + size_t len = vss_get_fec_eof_packet(&buf); + + /* ignore return value, closing the target anyway. */ + (void)write(sc->fd, buf, len); } -static void udp_delete_target(struct udp_target *ut, const char *msg) +static void udp_delete_target(struct sender_client *sc, const char *msg) { - PARA_NOTICE_LOG("deleting %s#%d (%s) from list\n", ut->host, - ut->port, msg); - udp_close_target(ut); + struct udp_target *ut = sc->private_data; + + PARA_NOTICE_LOG("deleting %s (%s) from list\n", sc->name, msg); + udp_close_target(sc); + close(sc->fd); + del_close_on_fork_list(sc->fd); vss_del_fec_client(ut->fc); - list_del(&ut->node); + list_del(&sc->node); + free(sc->name); + free(sc); free(ut); } /** * Perform AF-independent multicast sender setup. * - * \param fd The connected socket descriptor. - * \param ttl UDPv4 multicast TTL or UDPv6 multicast number of hops. - * Use -1 to mean default, 0..255 otherwise. - * \param iface The outgoing multicast interface, or NULL for the default. + * \param sc The connected sender client. * * \return Zero if okay, negative on error. */ -static int mcast_sender_setup(struct udp_target *ut, int ttl, char *iface) +static int mcast_sender_setup(struct sender_client *sc) { struct sockaddr_storage ss; socklen_t sslen = sizeof(ss); - + int ttl = conf.udp_ttl_arg, id = 0; const int on = 1; - int id = iface == NULL ? 0 : if_nametoindex(iface); - if (getpeername(ut->fd, (struct sockaddr *)&ss, &sslen) < 0) - goto err; + if (conf.udp_mcast_iface_given) { + char *iface = conf.udp_mcast_iface_arg; - if (iface != NULL && id == 0) - PARA_WARNING_LOG("could not resolve interface %s, using default", iface); + id = if_nametoindex(iface); + if (id == 0) + PARA_WARNING_LOG("could not resolve interface '%s', " + "using default", iface); + } + + if (getpeername(sc->fd, (struct sockaddr *)&ss, &sslen) < 0) + goto err; + assert(ss.ss_family == AF_INET || ss.ss_family == AF_INET6); /* RFC 3493, 5.2: -1 means 'use kernel default' */ if (ttl < 0 || ttl > 255) @@ -109,7 +117,7 @@ static int mcast_sender_setup(struct udp_target *ut, int ttl, char *iface) memset(&mn, 0, sizeof(mn)); mn.imr_ifindex = id; - if (setsockopt(ut->fd, IPPROTO_IP, IP_MULTICAST_IF, &mn, sizeof(mn)) < 0) + if (setsockopt(sc->fd, IPPROTO_IP, IP_MULTICAST_IF, &mn, sizeof(mn)) < 0) goto err; #else PARA_ERROR_LOG("No support for setting outgoing IPv4 mcast interface."); @@ -119,84 +127,63 @@ static int mcast_sender_setup(struct udp_target *ut, int ttl, char *iface) * Enable receiving multicast messages generated on the local host * At least on Linux, this is enabled by default. */ - if (setsockopt(ut->fd, IPPROTO_IP, IP_MULTICAST_LOOP, &on, sizeof(on)) < 0) + if (setsockopt(sc->fd, IPPROTO_IP, IP_MULTICAST_LOOP, &on, sizeof(on)) < 0) break; /* Default: use local subnet (do not flood out into the WAN) */ if (ttl == -1) ttl = 1; - if (setsockopt(ut->fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0) + if (setsockopt(sc->fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) < 0) break; return 0; case AF_INET6: if (!IN6_IS_ADDR_MULTICAST(&((struct sockaddr_in6 *)&ss)->sin6_addr)) return 0; if (id != 0 && - setsockopt(ut->fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &id, sizeof(id)) < 0) + setsockopt(sc->fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &id, sizeof(id)) < 0) break; - if (setsockopt(ut->fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &on, sizeof(on)) < 0) + if (setsockopt(sc->fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &on, sizeof(on)) < 0) break; - if (setsockopt(ut->fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)) < 0) + if (setsockopt(sc->fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)) < 0) break; return 0; - default: - PARA_ERROR_LOG("address family %d not supported", ss.ss_family); - return -E_ADDRESS_LOOKUP; } err: return -ERRNO_TO_PARA_ERROR(errno); } -/** The maximal size of the per-target chunk queue. */ -#define UDP_CQ_BYTES 40000 - -static int udp_init_session(struct udp_target *ut) +static void udp_init_session(struct sender_client *sc) { - int ret; - char *iface = NULL; - - if (ut->fd >= 0) /* nothing to do */ - return 0; + PARA_NOTICE_LOG("sending to udp %s\n", sc->name); +} - ret = makesock(AF_UNSPEC, IPPROTO_UDP, 0, ut->host, ut->port); - if (ret < 0) - return ret; - ut->fd = ret; +static void udp_shutdown_targets(void) +{ + struct sender_client *sc, *tmp; + list_for_each_entry_safe(sc, tmp, &targets, node) + udp_close_target(sc); +} - if (conf.udp_mcast_iface_given) - iface = conf.udp_mcast_iface_arg; +static int udp_resolve_target(const char *url, struct sender_command_data *scd) +{ + const char *result; + int ret, port; - ret = mcast_sender_setup(ut, conf.udp_ttl_arg, iface); - if (ret < 0) { - close(ut->fd); + ret = parse_fec_url(url, scd); + if (ret) return ret; - } + port = scd->port > 0 ? scd->port : conf.udp_default_port_arg; - ret = mark_fd_nonblocking(ut->fd); - if (ret < 0) { - close(ut->fd); + ret = para_connect_simple(IPPROTO_UDP, scd->host, port); + if (ret < 0) return ret; - } - add_close_on_fork_list(ut->fd); - ut->cq = cq_new(UDP_CQ_BYTES); - PARA_NOTICE_LOG("sending to udp %s#%d\n", ut->host, ut->port); - return 1; -} -static void udp_shutdown_targets(void) -{ - struct udp_target *ut, *tmp; - const char *buf = NULL; - size_t len = 0; /* STFU, gcc */ + result = remote_name(ret); + close(ret); - list_for_each_entry_safe(ut, tmp, &targets, node) { - if (ut->fd < 0) - continue; - if (!buf) - len = vss_get_fec_eof_packet(&buf); - write(ut->fd, buf, len); - udp_close_target(ut); - } + if (!parse_url(result, scd->host, sizeof(scd->host), &scd->port)) + return -E_ADDRESS_LOOKUP; + return 1; } static int udp_com_on(__a_unused struct sender_command_data *scd) @@ -212,89 +199,165 @@ static int udp_com_off(__a_unused struct sender_command_data *scd) return 1; } -static int udp_com_delete(struct sender_command_data *scd) +static struct sender_client *udp_lookup_target(struct sender_command_data *scd) { - struct udp_target *ut, *tmp; + struct sender_client *sc, *tmp; + char host[MAX_HOSTLEN]; + int32_t port; + + list_for_each_entry_safe(sc, tmp, &targets, node) { + parse_url(sc->name, host, sizeof(host), &port); - list_for_each_entry_safe(ut, tmp, &targets, node) { /* Unspecified port means wildcard port match */ - if (scd->port > 0 && scd->port != ut->port) + if (scd->port > 0 && scd->port != port) continue; - if (strcmp(ut->host, scd->host)) + if (strcmp(host, scd->host)) continue; - udp_delete_target(ut, "com_delete"); + return sc; } - return 1; + return NULL; +} + +static int udp_com_delete(struct sender_command_data *scd) +{ + struct sender_client *sc = udp_lookup_target(scd); + + if (sc) { + udp_delete_target(sc, "com_delete"); + return 1; + } + PARA_NOTICE_LOG("not deleting non-existing target '%s'\n", scd->host); + return -E_TARGET_NOT_FOUND; +} + +/** Initialize UDP session and set maximum payload size. */ +static int udp_init_fec(struct sender_client *sc) +{ + int mps; + + udp_init_session(sc); + mps = generic_max_transport_msg_size(sc->fd) - sizeof(struct udphdr); + PARA_INFO_LOG("current MPS = %d bytes\n", mps); + return mps; } -static int udp_send_fec(char *buf, size_t len, void *private_data) +/** Check and clear socket error if any. */ +static int udp_check_socket_state(struct sender_client *sc) +{ + struct udp_target *ut = sc->private_data; + int ret; + socklen_t errlen = sizeof(ret); + + if (getsockopt(sc->fd, SOL_SOCKET, SO_ERROR, &ret, &errlen) < 0) { + PARA_ERROR_LOG("SO_ERROR failed: %s\n", strerror(ret)); + return 0; + } else if (ret == 0) { + return 0; + } else if (ret == ECONNREFUSED) { + time_t dist = now->tv_sec - ut->last_unreachable; + + if (dist <= UDP_MAX_UNREACHABLE_TIME) { + return 0; + } else if (dist > 2 * UDP_MAX_UNREACHABLE_TIME) { + ut->last_unreachable = now->tv_sec; + return 0; + } else { + /* + * unreachable_time < dist <= 2 * unreachable_time + * No errors are allowed during this time window. + */ + PARA_NOTICE_LOG("Evicting %s after %d seconds " + "of connection errors.\n", + sc->name, (int)dist); + } + } + return -ERRNO_TO_PARA_ERROR(ret); +} + +static int udp_send_fec(struct sender_client *sc, char *buf, size_t len) { - struct udp_target *ut = private_data; int ret; if (sender_status == SENDER_OFF) return 0; - ret = udp_init_session(ut); - if (ret < 0) - goto fail; - ret = send_queued_chunks(ut->fd, ut->cq, 0); + if (len == 0) + return 0; + ret = udp_check_socket_state(sc); if (ret < 0) goto fail; - if (!len) - return 0; - if (!ret) { /* still data left in the queue */ - ret = cq_enqueue(ut->cq, buf, len); - if (ret < 0) - goto fail; + ret = write_nonblock(sc->fd, buf, len); + if (ret == -ERRNO_TO_PARA_ERROR(ECONNREFUSED)) { + /* + * Happens if meanwhile an ICMP Destination / Port Unreachable + * has arrived. Ignore, persistent errors will be caught above. + */ + ret = 0; } - ret = write_nonblock(ut->fd, buf, len, 0); if (ret < 0) goto fail; - if (ret != len) { - ret = cq_enqueue(ut->cq, buf + ret, len - ret); - if (ret < 0) - goto fail; - } return 1; fail: - udp_delete_target(ut, para_strerror(-ret)); + udp_delete_target(sc, para_strerror(-ret)); return ret; } -static void udp_add_target(struct sender_command_data *scd) +static int udp_com_add(struct sender_command_data *scd) { - struct udp_target *ut = para_calloc(sizeof(struct udp_target)); - - strncpy(ut->host, scd->host, sizeof(ut->host)); - ut->port = scd->port > 0 ? scd->port : conf.udp_default_port_arg; - ut->fd = -1; /* not yet connected */ - PARA_INFO_LOG("adding to target list (%s#%d)\n", ut->host, ut->port); - para_list_add(&ut->node, &targets); - ut->fcp.slices_per_group = scd->slices_per_group; + int ret; + struct udp_target *ut; + struct sender_client *sc = udp_lookup_target(scd); + + if (sc) { + PARA_NOTICE_LOG("target %s already exists - not adding it\n", + sc->name); + return -E_TARGET_EXISTS; + } + ut = para_calloc(sizeof(*ut)); + sc = para_calloc(sizeof(*sc)); + ut->fcp.slices_per_group = scd->slices_per_group; ut->fcp.data_slices_per_group = scd->data_slices_per_group; - ut->fcp.max_slice_bytes = scd->max_slice_bytes; - ut->fcp.send = udp_send_fec; - ut->fcp.private_data = ut; - vss_add_fec_client(&ut->fcp, &ut->fc); -} + ut->fcp.init_fec = udp_init_fec; + ut->fcp.send_fec = udp_send_fec; + ut->fcp.need_periodic_header = true; -static int udp_com_add(struct sender_command_data *scd) -{ - udp_add_target(scd); + sc->private_data = ut; + sc->fd = -1; + ret = para_connect_simple(IPPROTO_UDP, scd->host, scd->port); + if (ret < 0) + goto err; + sc->fd = ret; + + ret = mcast_sender_setup(sc); + if (ret < 0) + goto err; + ret = mark_fd_nonblocking(sc->fd); + if (ret < 0) + goto err; + sc->name = para_strdup(remote_name(sc->fd)); + PARA_INFO_LOG("adding to target list (%s)\n", sc->name); + ut->fc = vss_add_fec_client(sc, &ut->fcp); + para_list_add(&sc->node, &targets); + add_close_on_fork_list(sc->fd); return 1; +err: + if (sc->fd >= 0) + close(sc->fd); + PARA_NOTICE_LOG("failed to set up %s:%d (%s)- not adding it\n", + scd->host, scd->port, para_strerror(-ret)); + free(sc); + free(ut); + return ret; } static char *udp_info(void) { - struct udp_target *ut; + struct sender_client *sc; char *ret, *tgts = NULL; - list_for_each_entry(ut, &targets, node) { - bool is_v6 = strchr(ut->host, ':') != NULL; - char *tmp = make_message("%s%s%s%s:%d/%u:%u:%u ", tgts ? : "", - is_v6 ? "[" : "", ut->host, - is_v6 ? "]" : "", ut->port, - ut->fcp.max_slice_bytes, + list_for_each_entry(sc, &targets, node) { + struct udp_target *ut = sc->private_data; + char *tmp = make_message("%s%s/%u:%u ", + tgts ? : "", sc->name, ut->fcp.data_slices_per_group, ut->fcp.slices_per_group ); @@ -304,10 +367,10 @@ static char *udp_info(void) ret = make_message( "udp sender:\n" "\tstatus: %s\n" - "\tport: udp %d\n" + "\tport: %s\n" "\ttargets: %s\n", (sender_status == SENDER_ON)? "on" : "off", - conf.udp_default_port_arg, + stringify_port(conf.udp_default_port_arg, "udp"), tgts? tgts : "(none)" ); free(tgts); @@ -321,12 +384,11 @@ static void udp_init_target_list(void) INIT_LIST_HEAD(&targets); for (i = 0; i < conf.udp_target_given; i++) { - if (parse_fec_url(conf.udp_target_arg[i], &scd) < 0) { - PARA_CRIT_LOG("syntax error for udp target option " - "#%d, ignoring\n", i); - continue; - } - udp_add_target(&scd); + if (udp_resolve_target(conf.udp_target_arg[i], &scd) < 0) + PARA_CRIT_LOG("not adding requested target '%s'\n", + conf.udp_target_arg[i]); + else + udp_com_add(&scd); } } @@ -334,15 +396,17 @@ static char *udp_help(void) { return make_message( "usage: {on|off}\n" - "usage: {add|delete} host[:port][/packet_size:k:n]\n" + "usage: {add|delete} ip_address[:port][/[packet_size:]k:n]\n" + " - k is the number of data slices per FEC group\n" + " - n is the total number of slices in a FEC group\n" + " - packet_size reduces the slice size below path MTU\n\n" "examples: add 224.0.1.38:1500 (IPv4 multicast)\n" - " add 224.0.1.38:1500/1400:14:16\n" - " (likewise, using 1400 byte packets, with 14\n" - " data slices per 16 slice FEC group)\n" + " add 224.0.1.38:8080/14:16 (explicit FEC)\n" " add 10.10.1.42 (using default port)\n" " add [FF05::42]:1500 (IPv6 multicast)\n" " add [::1] (IPv6 localhost/default port)\n" " delete myhost.net (host with port wildcard)\n" + " delete 10.1.2.3:456 (IPv4 with explicit port)\n" " delete [badc0de::1] (IPv6 with port wildcard)\n" ); } @@ -363,6 +427,7 @@ void udp_send_init(struct sender *s) s->pre_select = NULL; s->post_select = NULL; s->shutdown_clients = udp_shutdown_targets; + s->resolve_target = udp_resolve_target; s->client_cmds[SENDER_ON] = udp_com_on; s->client_cmds[SENDER_OFF] = udp_com_off; s->client_cmds[SENDER_DENY] = NULL; diff --git a/user_list.c b/user_list.c index f5aabc09..fe1b946b 100644 --- a/user_list.c +++ b/user_list.c @@ -1,20 +1,24 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file user_list.c User handling for para_server. */ +#include #include #include +#include #include "para.h" #include "error.h" +#include "crypt.h" #include "fd.h" #include "string.h" #include "list.h" #include "user_list.h" +#include "rc4.h" static struct list_head user_list; @@ -53,6 +57,18 @@ static void populate_user_list(char *user_list_file) para_strerror(-ret)); continue; } + /* + * In order to encrypt len := CHALLENGE_SIZE + 2 * RC4_KEY_LEN + * bytes using RSA_public_encrypt() with EME-OAEP padding mode, + * RSA_size(rsa) must be greater than len + 41. So ignore keys + * which are too short. For details see RSA_public_encrypt(3). + */ + if (ret <= CHALLENGE_SIZE + 2 * RC4_KEY_LEN + 41) { + PARA_WARNING_LOG("rsa key %s too short (%d)\n", + k, ret); + rsa_free(rsa); + continue; + } u = para_malloc(sizeof(*u)); u->name = para_strdup(n); u->rsa = rsa; diff --git a/user_list.h b/user_list.h index 5baceacc..cce577a6 100644 --- a/user_list.h +++ b/user_list.h @@ -1,13 +1,11 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file user_list.h exported functions from user_list.c */ -#include "crypt.h" - /** * permission flags that can be set individually for any server command * diff --git a/versions/paraslash-0.4.0.tar.bz2 b/versions/paraslash-0.4.0.tar.bz2 new file mode 100644 index 00000000..b4bd7fd9 Binary files /dev/null and b/versions/paraslash-0.4.0.tar.bz2 differ diff --git a/versions/paraslash-0.4.0.tar.bz2.asc b/versions/paraslash-0.4.0.tar.bz2.asc new file mode 100644 index 00000000..c97337db --- /dev/null +++ b/versions/paraslash-0.4.0.tar.bz2.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.6 (GNU/Linux) + +iD8DBQBK+ZoEWto1QDEAkw8RAnNWAJ918+nDTCYHpYNyDiYiOg42QyfPIACZAWAI +a3yaR7ISQ9Fl5RGNkcnU3ms= +=1J9B +-----END PGP SIGNATURE----- diff --git a/versions/paraslash-0.4.1.tar.bz2 b/versions/paraslash-0.4.1.tar.bz2 new file mode 100644 index 00000000..a6c815ee Binary files /dev/null and b/versions/paraslash-0.4.1.tar.bz2 differ diff --git a/versions/paraslash-0.4.1.tar.bz2.asc b/versions/paraslash-0.4.1.tar.bz2.asc new file mode 100644 index 00000000..6a8296e9 --- /dev/null +++ b/versions/paraslash-0.4.1.tar.bz2.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.6 (GNU/Linux) + +iD8DBQBLMMIsWto1QDEAkw8RAobAAJ98LeeEymj+MuUsNe8fse8Ty3mzxgCgj+RP +uuiX7zu0xTzuuKchgesqxAU= +=lTsJ +-----END PGP SIGNATURE----- diff --git a/versions/paraslash-0.4.2.tar.bz2 b/versions/paraslash-0.4.2.tar.bz2 new file mode 100644 index 00000000..cdccdece Binary files /dev/null and b/versions/paraslash-0.4.2.tar.bz2 differ diff --git a/versions/paraslash-0.4.2.tar.bz2.asc b/versions/paraslash-0.4.2.tar.bz2.asc new file mode 100644 index 00000000..9c6c31b3 --- /dev/null +++ b/versions/paraslash-0.4.2.tar.bz2.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.6 (GNU/Linux) + +iD8DBQBL0XoLWto1QDEAkw8RAgQ0AJ9DIV4s1+7B8yQAICpybPLVJDb6HQCfeOfL +fyu8jl02vEy+RJQzVnfHlDA= +=XkAf +-----END PGP SIGNATURE----- diff --git a/versions/paraslash-0.4.3.tar.bz2 b/versions/paraslash-0.4.3.tar.bz2 new file mode 100644 index 00000000..aafb5bad Binary files /dev/null and b/versions/paraslash-0.4.3.tar.bz2 differ diff --git a/versions/paraslash-0.4.3.tar.bz2.asc b/versions/paraslash-0.4.3.tar.bz2.asc new file mode 100644 index 00000000..ada50cfa --- /dev/null +++ b/versions/paraslash-0.4.3.tar.bz2.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.6 (GNU/Linux) + +iD8DBQBMMfksWto1QDEAkw8RAlk7AJ4qx93caXEdtfuEb5pLqHYsvAIiRgCeK/Rb +B5z1ZZrbg8n0nF7F8myUb4U= +=iLIk +-----END PGP SIGNATURE----- diff --git a/versions/paraslash-0.4.4.tar.bz2 b/versions/paraslash-0.4.4.tar.bz2 new file mode 100644 index 00000000..8d4208a2 Binary files /dev/null and b/versions/paraslash-0.4.4.tar.bz2 differ diff --git a/versions/paraslash-0.4.4.tar.bz2.asc b/versions/paraslash-0.4.4.tar.bz2.asc new file mode 100644 index 00000000..7da18a53 --- /dev/null +++ b/versions/paraslash-0.4.4.tar.bz2.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.6 (GNU/Linux) + +iD8DBQBMW/uAWto1QDEAkw8RAqMaAKCAHjhoBMIkkBLRLeGCyFTB5KPUswCfZzmu +ECnZSGI47+W6fJszazBybew= +=isXJ +-----END PGP SIGNATURE----- diff --git a/versions/paraslash-0.4.5.tar.bz2 b/versions/paraslash-0.4.5.tar.bz2 new file mode 100644 index 00000000..6299d0c5 Binary files /dev/null and b/versions/paraslash-0.4.5.tar.bz2 differ diff --git a/versions/paraslash-0.4.5.tar.bz2.asc b/versions/paraslash-0.4.5.tar.bz2.asc new file mode 100644 index 00000000..c56b9b36 --- /dev/null +++ b/versions/paraslash-0.4.5.tar.bz2.asc @@ -0,0 +1,7 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v1.4.10 (GNU/Linux) + +iEYEABECAAYFAk0LimcACgkQWto1QDEAkw9cigCfQfJi6KJMC7HHGO7zrvlrz7f/ +4DoAn2hSNGm08WWIgqLYOjUGbQSCnNMc +=/3KE +-----END PGP SIGNATURE----- diff --git a/vss.c b/vss.c index b63a1fcb..916a71d5 100644 --- a/vss.c +++ b/vss.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 1997-2009 Andre Noll + * Copyright (C) 1997-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -11,7 +11,9 @@ * senders. */ +#include #include +#include #include "para.h" #include "error.h" @@ -24,8 +26,8 @@ #include "net.h" #include "server.cmdline.h" #include "list.h" -#include "vss.h" #include "send.h" +#include "vss.h" #include "ipc.h" #include "fd.h" #include "sched.h" @@ -127,14 +129,24 @@ struct fec_group { struct timeval slice_duration; /** Group contains the audio file header that occupies that many slices. */ uint8_t num_header_slices; + /** Number of bytes per slice for this group. */ + uint16_t slice_bytes; +}; + +enum fec_client_state { + FEC_STATE_NONE = 0, /**< not initialized and not enabled */ + FEC_STATE_DISABLED, /**< temporarily disabled */ + FEC_STATE_READY_TO_RUN /**< initialized and enabled */ }; /** * Describes one connected FEC client. */ struct fec_client { - /** If negative, this client is temporarily disabled. */ - int error; + /** Current state of the client */ + enum fec_client_state state; + /** The connected sender client (transport layer). */ + struct sender_client *sc; /** Parameters requested by the client. */ struct fec_client_parms *fcp; /** Used by the core FEC code. */ @@ -159,6 +171,8 @@ struct fec_client { int num_extra_slices; /** Contains the FEC-encoded data. */ unsigned char *enc_buf; + /** Maximal packet size. */ + int mps; }; /** @@ -197,93 +211,280 @@ static void write_fec_header(struct fec_client *fc, struct vss_task *vsst) write_u32(buf + 14, g->bytes); write_u8(buf + 18, fc->current_slice_num); - write_u16(buf + 20, p->max_slice_bytes - FEC_HEADER_SIZE); + write_u16(buf + 20, g->slice_bytes); write_u8(buf + 22, g->first_chunk? 0 : 1); write_u8(buf + 23, vsst->header_len? 1 : 0); memset(buf + 24, 0, 7); } -static int need_audio_header(struct fec_client *fc, struct vss_task *vsst) +static bool need_audio_header(struct fec_client *fc, struct vss_task *vsst) { if (!mmd->current_chunk) { tv_add(now, &vsst->header_interval, &fc->next_header_time); - return 0; + return false; } if (!vsst->header_buf) - return 0; - if (!vsst->header_len) - return 0; - if (fc->group.num && tv_diff(&fc->next_header_time, now, NULL) > 0) - return 0; + return false; + if (vsst->header_len == 0) + return false; + if (fc->group.num > 0) { + if (!fc->fcp->need_periodic_header) + return false; + if (tv_diff(&fc->next_header_time, now, NULL) > 0) + return false; + } tv_add(now, &vsst->header_interval, &fc->next_header_time); - return 1; + return true; +} + +static bool need_data_slices(struct fec_client *fc, struct vss_task *vsst) +{ + if (fc->group.num > 0) + return true; + if (!vsst->header_buf) + return true; + if (vsst->header_len == 0) + return true; + if (fc->fcp->need_periodic_header) + return true; + return false; } -static int num_slices(long unsigned bytes, struct fec_client *fc, uint8_t *result) +static int num_slices(long unsigned bytes, int max_payload, int rs) { - unsigned long m = fc->fcp->max_slice_bytes - FEC_HEADER_SIZE; - unsigned rv, redundant_slices = fc->fcp->slices_per_group - - fc->fcp->data_slices_per_group; + int ret; - if (!m) - return -E_BAD_CT; - rv = (bytes + m - 1) / m; - if (rv + redundant_slices > 255) + assert(max_payload > 0); + assert(rs > 0); + ret = DIV_ROUND_UP(bytes, max_payload); + if (ret + rs > 255) return -E_BAD_CT; - *result = rv; - return 1; + return ret; } /* set group start and group duration */ -static void set_group_timing(struct fec_client *fc, struct fec_group *g) +static void set_group_timing(struct fec_client *fc, struct vss_task *vsst) { + struct fec_group *g = &fc->group; struct timeval *chunk_tv = vss_chunk_time(); - tv_scale(g->num_chunks, chunk_tv, &g->duration); + if (!need_data_slices(fc, vsst)) + ms2tv(200, &g->duration); + else + tv_scale(g->num_chunks, chunk_tv, &g->duration); tv_divide(fc->fcp->slices_per_group + fc->num_extra_slices, &g->duration, &g->slice_duration); PARA_DEBUG_LOG("durations (group/chunk/slice): %lu/%lu/%lu\n", tv2ms(&g->duration), tv2ms(chunk_tv), tv2ms(&g->slice_duration)); } -static int setup_next_fec_group(struct fec_client *fc, struct vss_task *vsst) +static int initialize_fec_client(struct fec_client *fc, struct vss_task *vsst) { - int ret, i, k, data_slices; - size_t len; - const char *buf, *start_buf; - struct fec_group *g = &fc->group; - unsigned slice_bytes = fc->fcp->max_slice_bytes - FEC_HEADER_SIZE; - uint32_t max_data_size; + int k, n, ret; + int hs, ds, rs; /* header/data/redundant slices */ + struct fec_client_parms *fcp = fc->fcp; - if (fc->first_stream_chunk < 0) { - uint8_t hs, ds; /* needed header/data slices */ - uint8_t rs = fc->fcp->slices_per_group - - fc->fcp->data_slices_per_group; /* redundant slices */ - int n; - - ret = num_slices(vsst->header_len, fc, &hs); + /* set mps */ + if (fcp->init_fec) { + /* + * Set the maximum slice size to the Maximum Packet Size if the + * transport protocol allows to determine this value. The user + * can specify a slice size up to this value. + */ + ret = fcp->init_fec(fc->sc); if (ret < 0) return ret; - ret = num_slices(afh_get_largest_chunk_size(&mmd->afd.afhi), - fc, &ds); + fc->mps = ret; + } else + fc->mps = generic_max_transport_msg_size(fc->sc->fd); + if (fc->mps <= FEC_HEADER_SIZE) + return -ERRNO_TO_PARA_ERROR(EINVAL); + + rs = fc->fcp->slices_per_group - fc->fcp->data_slices_per_group; + ret = num_slices(vsst->header_len, fc->mps - FEC_HEADER_SIZE, rs); + if (ret < 0) + return ret; + hs = ret; + ret = num_slices(mmd->afd.max_chunk_size, fc->mps - FEC_HEADER_SIZE, rs); + if (ret < 0) + return ret; + ds = ret; + if (fc->fcp->need_periodic_header) + k = hs + ds; + else + k = PARA_MAX(hs, ds); + if (k < fc->fcp->data_slices_per_group) + k = fc->fcp->data_slices_per_group; + fc->num_extra_slices = k - fc->fcp->data_slices_per_group; + n = k + rs; + fec_free(fc->parms); + ret = fec_new(k, n, &fc->parms); + if (ret < 0) + return ret; + PARA_INFO_LOG("mps: %d, k: %d, n: %d, extra slices: %d\n", + fc->mps, k, n, fc->num_extra_slices); + fc->src_data = para_realloc(fc->src_data, k * sizeof(char *)); + fc->enc_buf = para_realloc(fc->enc_buf, fc->mps); + fc->extra_src_buf = para_realloc(fc->extra_src_buf, fc->mps); + + fc->state = FEC_STATE_READY_TO_RUN; + fc->next_header_time.tv_sec = 0; + fc->stream_start = *now; + fc->first_stream_chunk = mmd->current_chunk; + return 1; +} + +static void compute_group_size(struct vss_task *vsst, struct fec_group *g, + int max_bytes) +{ + int i, max_chunks = PARA_MAX(1LU, 150 / tv2ms(vss_chunk_time())); + + g->num_chunks = 0; + g->bytes = 0; + /* + * Include chunks into the group until the group duration is at least + * 150ms. For ogg and wma, a single chunk's duration (ogg page/wma + * super frame) is already larger than 150ms, so a FEC group consists + * of exactly one chunk for these audio formats. + */ + for (i = 0;; i++) { + const char *buf; + size_t len; + int chunk_num = g->first_chunk + i; + + if (g->bytes > 0 && i >= max_chunks) /* duration limit */ + break; + if (chunk_num >= mmd->afd.afhi.chunks_total) /* eof */ + break; + afh_get_chunk(chunk_num, &mmd->afd.afhi, vsst->map, &buf, &len); + if (g->bytes + len > max_bytes) + break; + /* Include this chunk */ + g->bytes += len; + g->num_chunks++; + } + assert(g->num_chunks); +} + +/* + * Compute the slice size of the next group. + * + * The FEC parameters n and k are fixed but the slice size varies per + * FEC group. We'd like to choose slices as small as possible to avoid + * unnecessary FEC calculations but large enough to guarantee that the + * k data slices suffice to encode the header (if needed) and the data + * chunk(s). + * + * Once we know the payload of the next group, we define the number s + * of bytes per slice for this group by + * + * s = ceil(payload / k) + * + * However, for header streams, computing s is more complicated since no + * overlapping of header and data slices is possible. Hence we have k >= + * 2 and s must satisfy + * + * (*) ceil(h / s) + ceil(d / s) <= k + * + * where h and d are payload of the header and the data chunk(s) + * respectively. In general there is no value for s such that (*) + * becomes an equality, for example if h = 4000, d = 5000 and k = 10. + * + * We use the following approach for computing a suitable value for s: + * + * Let + * k1 := ceil(k * min(h, d) / (h + d)), + * k2 := k - k1. + * + * Note that k >= 2 implies k1 > 0 and k2 > 0, so + * + * s := max(ceil(min(h, d) / k1), ceil(max(h, d) / k2)) + * + * is well-defined. Inequality (*) holds for this value of s since k1 + * slices suffice to store min(h, d) while k2 slices suffice to store + * max(h, d), i.e. the first addent of (*) is bounded by k1 and the + * second by k2. + * + * For the above example we obtain + * + * k1 = ceil(10 * 4000 / 9000) = 5, k2 = 5, + * s = max(4000 / 5, 5000 / 5) = 1000, + * + * which is optimal since a slice size of 999 bytes would already require + * 11 slices. + */ +static int compute_slice_size(struct fec_client *fc, struct vss_task *vsst) +{ + struct fec_group *g = &fc->group; + int k = fc->fcp->data_slices_per_group + fc->num_extra_slices; + int n = fc->fcp->slices_per_group + fc->num_extra_slices; + int ret, k1, k2, h, d, min, max, sum; + int max_slice_bytes = fc->mps - FEC_HEADER_SIZE; + int max_group_bytes; + + if (!need_audio_header(fc, vsst)) { + max_group_bytes = k * max_slice_bytes; + g->num_header_slices = 0; + compute_group_size(vsst, g, max_group_bytes); + g->slice_bytes = DIV_ROUND_UP(g->bytes, k); + if (g->slice_bytes == 0) + g->slice_bytes = 1; + return 1; + } + if (!need_data_slices(fc, vsst)) { + g->bytes = 0; + g->num_chunks = 0; + g->slice_bytes = DIV_ROUND_UP(vsst->header_len, k); + g->num_header_slices = k; + return 1; + } + h = vsst->header_len; + max_group_bytes = (k - num_slices(h, max_slice_bytes, n - k)) + * max_slice_bytes; + compute_group_size(vsst, g, max_group_bytes); + d = g->bytes; + if (d == 0) { + g->slice_bytes = DIV_ROUND_UP(h, k); + ret = num_slices(vsst->header_len, g->slice_bytes, n - k); if (ret < 0) return ret; - k = (int)hs + ds; - if (k > 255) - return -E_BAD_CT; - if (k < fc->fcp->data_slices_per_group) - k = fc->fcp->data_slices_per_group; - n = k + rs; - fc->num_extra_slices = k - fc->fcp->data_slices_per_group; - PARA_NOTICE_LOG("fec parms %d:%d:%d (%d extra slices)\n", - slice_bytes, k, n, fc->num_extra_slices); - fec_free(fc->parms); - fc->src_data = para_realloc(fc->src_data, k * sizeof(char *)); - ret = fec_new(k, n, &fc->parms); + g->num_header_slices = ret; + return 1; + } + min = PARA_MIN(h, d); + max = PARA_MAX(h, d); + sum = h + d; + k1 = DIV_ROUND_UP(k * min, sum); + k2 = k - k1; + assert(k1 > 0); + assert(k2 > 0); + + g->slice_bytes = PARA_MAX(DIV_ROUND_UP(min, k1), DIV_ROUND_UP(max, k2)); + /* + * This value of s := g->slice_bytes satisfies inequality (*) above, + * but it might be larger than max_slice_bytes. However, we know that + * max_slice_bytes are sufficient to store header and data, so: + */ + g->slice_bytes = PARA_MIN((int)g->slice_bytes, max_slice_bytes); + + ret = num_slices(vsst->header_len, g->slice_bytes, n - k); + if (ret < 0) + return ret; + g->num_header_slices = ret; + return 1; +} + +static int setup_next_fec_group(struct fec_client *fc, struct vss_task *vsst) +{ + int ret, i, k, n, data_slices; + size_t len; + const char *buf; + struct fec_group *g = &fc->group; + + if (fc->state == FEC_STATE_NONE) { + ret = initialize_fec_client(fc, vsst); if (ret < 0) return ret; - fc->stream_start = *now; - fc->first_stream_chunk = mmd->current_chunk; g->first_chunk = mmd->current_chunk; g->num = 0; g->start = *now; @@ -297,78 +498,71 @@ static int setup_next_fec_group(struct fec_client *fc, struct vss_task *vsst) */ tmp = g->start; tv_add(&tmp, &g->duration, &g->start); - k = fc->fcp->data_slices_per_group + fc->num_extra_slices; - set_group_timing(fc, g); + set_group_timing(fc, vsst); g->first_chunk += g->num_chunks; g->num++; } - if (need_audio_header(fc, vsst)) { - ret = num_slices(vsst->header_len, fc, &g->num_header_slices); - if (ret < 0) - return ret; - } else - g->num_header_slices = 0; - afh_get_chunk(g->first_chunk, &mmd->afd.afhi, vsst->map, &start_buf, - &len); - data_slices = k - g->num_header_slices; - assert(data_slices); - max_data_size = slice_bytes * data_slices; - g->bytes = 0; - for (i = g->first_chunk; i < mmd->afd.afhi.chunks_total; i++) { - afh_get_chunk(i, &mmd->afd.afhi, vsst->map, &buf, &len); - if (g->bytes + len > max_data_size) - break; - g->bytes += len; - } - g->num_chunks = i - g->first_chunk; - assert(g->num_chunks); + k = fc->fcp->data_slices_per_group + fc->num_extra_slices; + n = fc->fcp->slices_per_group + fc->num_extra_slices; + + compute_slice_size(fc, vsst); + assert(g->slice_bytes > 0); + ret = num_slices(g->bytes, g->slice_bytes, n - k); + if (ret < 0) + return ret; + data_slices = ret; + assert(g->num_header_slices + data_slices <= k); fc->current_slice_num = 0; if (g->num == 0) - set_group_timing(fc, g); + set_group_timing(fc, vsst); /* setup header slices */ buf = vsst->header_buf; for (i = 0; i < g->num_header_slices; i++) { fc->src_data[i] = (const unsigned char *)buf; - buf += slice_bytes; + buf += g->slice_bytes; } /* setup data slices */ - buf = start_buf; - for (i = g->num_header_slices; i < k; i++) { - if (buf + slice_bytes > vsst->map + mmd->size) + afh_get_chunk(g->first_chunk, &mmd->afd.afhi, vsst->map, &buf, &len); + for (; i < g->num_header_slices + data_slices; i++) { + if (buf + g->slice_bytes > vsst->map + mmd->size) { /* * Can not use the memory mapped audio file for this * slice as it goes beyond the map. This slice will not * be fully used. */ + uint32_t payload_size = vsst->map + mmd->size - buf; + memcpy(fc->extra_src_buf, buf, payload_size); + if (payload_size < g->slice_bytes) + memset(fc->extra_src_buf + payload_size, 0, + g->slice_bytes - payload_size); + fc->src_data[i] = fc->extra_src_buf; + i++; break; + } fc->src_data[i] = (const unsigned char *)buf; - buf += slice_bytes; + buf += g->slice_bytes; } if (i < k) { - uint32_t payload_size = vsst->map + mmd->size - buf; - memcpy(fc->extra_src_buf, buf, payload_size); - fc->src_data[i] = fc->extra_src_buf; - i++; /* use arbitrary data for all remaining slices */ buf = vsst->map; for (; i < k; i++) fc->src_data[i] = (const unsigned char *)buf; } - PARA_DEBUG_LOG("FEC group %d: %d chunks (%d - %d), " - "%d header slices, %d data slices\n", + PARA_DEBUG_LOG("FEC group %d: %d chunks (%d - %d), %d bytes\n", g->num, g->num_chunks, g->first_chunk, - g->first_chunk + g->num_chunks - 1, - g->num_header_slices, data_slices + g->first_chunk + g->num_chunks - 1, g->bytes + ); + PARA_DEBUG_LOG("slice_bytes: %d, %d header slices, %d data slices\n", + g->slice_bytes, g->num_header_slices, data_slices ); return 1; } static int compute_next_fec_slice(struct fec_client *fc, struct vss_task *vsst) { - assert(fc->error >= 0); - if (fc->first_stream_chunk < 0 || fc->current_slice_num + if (fc->state == FEC_STATE_NONE || fc->current_slice_num == fc->fcp->slices_per_group + fc->num_extra_slices) { int ret = setup_next_fec_group(fc, vsst); if (ret == 0) @@ -376,14 +570,13 @@ static int compute_next_fec_slice(struct fec_client *fc, struct vss_task *vsst) if (ret < 0) { PARA_ERROR_LOG("%s\n", para_strerror(-ret)); PARA_ERROR_LOG("FEC client temporarily disabled\n"); - fc->error = ret; - return fc->error; + fc->state = FEC_STATE_DISABLED; + return ret; } } write_fec_header(fc, vsst); fec_encode(fc->parms, fc->src_data, fc->enc_buf + FEC_HEADER_SIZE, - fc->current_slice_num, - fc->fcp->max_slice_bytes - FEC_HEADER_SIZE); + fc->current_slice_num, fc->group.slice_bytes); return 1; } @@ -406,38 +599,20 @@ size_t vss_get_fec_eof_packet(const char **buf) /** * Add one entry to the list of active fec clients. * - * \param fcp Describes the fec parameters to be used for this client. - * \param result An opaque pointer that must be used by remove the client later. + * \param sc Generic sender_client data of the transport layer. + * \param fcp FEC parameters as supplied by the transport layer. * - * \return Standard. + * \return Newly allocated fec_client struct. */ -int vss_add_fec_client(struct fec_client_parms *fcp, struct fec_client **result) +struct fec_client *vss_add_fec_client(struct sender_client *sc, + struct fec_client_parms *fcp) { - int ret; - struct fec_client *fc; + struct fec_client *fc = para_calloc(sizeof(*fc)); - if (fcp->max_slice_bytes < FEC_HEADER_SIZE + fcp->data_slices_per_group) - return -ERRNO_TO_PARA_ERROR(EINVAL); - fc = para_calloc(sizeof(*fc)); + fc->sc = sc; fc->fcp = fcp; - ret = fec_new(fcp->data_slices_per_group, fcp->slices_per_group, - &fc->parms); - if (ret < 0) - goto err; - fc->first_stream_chunk = -1; /* stream not yet started */ - fc->src_data = para_malloc(fc->fcp->slices_per_group * sizeof(char *)); - fc->enc_buf = para_calloc(fc->fcp->max_slice_bytes); - fc->num_extra_slices = 0; - fc->extra_src_buf = para_calloc(fc->fcp->max_slice_bytes); - fc->next_header_time.tv_sec = 0; para_list_add(&fc->node, &fec_client_list); - *result = fc; - return 1; -err: - fec_free(fc->parms); - free(fc); - *result = NULL; - return ret; + return fc; } /** @@ -467,7 +642,7 @@ static int next_slice_is_due(struct fec_client *fc, struct timeval *diff) struct timeval tmp, next; int ret; - if (fc->first_stream_chunk < 0) + if (fc->state == FEC_STATE_NONE) return 1; tv_scale(fc->current_slice_num, &fc->group.slice_duration, &tmp); tv_add(&tmp, &fc->group.start, &next); @@ -479,11 +654,10 @@ static void compute_slice_timeout(struct timeval *timeout) { struct fec_client *fc; - assert(vss_playing()); list_for_each_entry(fc, &fec_client_list, node) { struct timeval diff; - if (fc->error < 0) + if (fc->state != FEC_STATE_READY_TO_RUN) continue; if (next_slice_is_due(fc, &diff)) { timeout->tv_sec = 0; @@ -499,15 +673,14 @@ static void compute_slice_timeout(struct timeval *timeout) static void set_eof_barrier(struct vss_task *vsst) { struct fec_client *fc; - struct timeval timeout = mmd->afd.afhi.eof_tv, - *chunk_tv = vss_chunk_time(); + struct timeval timeout = {1, 0}, *chunk_tv = vss_chunk_time(); if (!chunk_tv) goto out; list_for_each_entry(fc, &fec_client_list, node) { struct timeval group_duration; - if (fc->error < 0) + if (fc->state != FEC_STATE_READY_TO_RUN) continue; tv_scale(fc->group.num_chunks, chunk_tv, &group_duration); if (tv_diff(&timeout, &group_duration, NULL) < 0) @@ -636,33 +809,17 @@ static void vss_eof(struct vss_task *vsst) para_munmap(vsst->map, mmd->size); vsst->map = NULL; mmd->chunks_sent = 0; - mmd->offset = 0; + //mmd->offset = 0; mmd->afd.afhi.seconds_total = 0; mmd->afd.afhi.chunk_tv.tv_sec = 0; mmd->afd.afhi.chunk_tv.tv_usec = 0; free(mmd->afd.afhi.chunk_table); mmd->afd.afhi.chunk_table = NULL; - free(mmd->afd.afhi.info_string); - mmd->afd.afhi.info_string = make_message("%s:\n%s:\n%s:\n", status_item_list[SI_AUDIO_FILE_INFO], - status_item_list[SI_TAGINFO1], status_item_list[SI_TAGINFO2]); - make_empty_status_items(mmd->afd.verbose_ls_output); mmd->mtime = 0; mmd->size = 0; mmd->events++; } -/** - * Get the list of all supported audio formats. - * - * \return Aa space separated list of all supported audio formats - * It is not allocated at runtime, i.e. there is no need to free - * the returned string in the caller. - */ -const char *supported_audio_formats(void) -{ - return SUPPORTED_AUDIO_FORMATS; -} - static int need_to_request_new_audio_file(struct vss_task *vsst) { struct timeval diff; @@ -681,6 +838,13 @@ static int need_to_request_new_audio_file(struct vss_task *vsst) return 1; } +static void set_mmd_offset(void) +{ + struct timeval offset; + tv_scale(mmd->current_chunk, &mmd->afd.afhi.chunk_tv, &offset); + mmd->offset = tv2ms(&offset); +} + /** * Compute the timeout for the main select-loop of the scheduler. * @@ -698,7 +862,7 @@ static int need_to_request_new_audio_file(struct vss_task *vsst) static void vss_pre_select(struct sched *s, struct task *t) { int i; - struct timeval *tv, diff; + struct timeval *tv; struct vss_task *vsst = container_of(t, struct vss_task, task); if (!vsst->map || vss_next() || vss_paused() || vss_repos()) { @@ -706,10 +870,8 @@ static void vss_pre_select(struct sched *s, struct task *t) for (i = 0; senders[i].name; i++) if (senders[i].shutdown_clients) senders[i].shutdown_clients(); - list_for_each_entry_safe(fc, tmp, &fec_client_list, node) { - fc->first_stream_chunk = -1; - fc->error = 0; - } + list_for_each_entry_safe(fc, tmp, &fec_client_list, node) + fc->state = FEC_STATE_NONE; mmd->stream_start.tv_sec = 0; mmd->stream_start.tv_usec = 0; } @@ -725,6 +887,7 @@ static void vss_pre_select(struct sched *s, struct task *t) mmd->chunks_sent = 0; mmd->current_chunk = mmd->repos_request; mmd->new_vss_status_flags &= ~VSS_REPOS; + set_mmd_offset(); } if (need_to_request_new_audio_file(vsst)) { PARA_DEBUG_LOG("ready and playing, but no audio file\n"); @@ -738,8 +901,8 @@ static void vss_pre_select(struct sched *s, struct task *t) senders[i].pre_select(&s->max_fileno, &s->rfds, &s->wfds); } tv = vss_compute_timeout(vsst); - if (tv && tv_diff(tv, &s->timeout, &diff) < 0) - s->timeout = *tv; + if (tv) + sched_request_timeout(tv, s); } static int recv_afs_msg(int afs_socket, int *fd, uint32_t *code, uint32_t *data) @@ -776,16 +939,20 @@ static int recv_afs_msg(int afs_socket, int *fd, uint32_t *code, uint32_t *data) return 1; } -static void recv_afs_result(struct vss_task *vsst) +static void recv_afs_result(struct vss_task *vsst, fd_set *rfds) { int ret, passed_fd, shmid; uint32_t afs_code = 0, afs_data = 0; struct stat statbuf; - vsst->afsss = AFS_SOCKET_READY; + if (!FD_ISSET(vsst->afs_socket, rfds)) + return; ret = recv_afs_msg(vsst->afs_socket, &passed_fd, &afs_code, &afs_data); + if (ret == -ERRNO_TO_PARA_ERROR(EAGAIN)) + return; if (ret < 0) goto err; + vsst->afsss = AFS_SOCKET_READY; PARA_DEBUG_LOG("fd: %d, code: %u, shmid: %u\n", passed_fd, afs_code, afs_data); ret = -E_NOFD; @@ -794,7 +961,6 @@ static void recv_afs_result(struct vss_task *vsst) if (passed_fd < 0) goto err; shmid = afs_data; - free(mmd->afd.afhi.info_string); ret = load_afd(shmid, &mmd->afd); if (ret < 0) goto err; @@ -852,7 +1018,7 @@ static void vss_send(struct vss_task *vsst) &due, 1) < 0) return; list_for_each_entry_safe(fc, tmp_fc, &fec_client_list, node) { - if (fc->error < 0) + if (fc->state == FEC_STATE_DISABLED) continue; if (!next_slice_is_due(fc, NULL)) { fec_active = 1; @@ -861,10 +1027,9 @@ static void vss_send(struct vss_task *vsst) if (compute_next_fec_slice(fc, vsst) <= 0) continue; PARA_DEBUG_LOG("sending %d:%d (%u bytes)\n", fc->group.num, - fc->current_slice_num, fc->fcp->max_slice_bytes); - fc->fcp->send((char *)fc->enc_buf, - fc->fcp->max_slice_bytes, - fc->fcp->private_data); + fc->current_slice_num, fc->group.slice_bytes); + fc->fcp->send_fec(fc->sc, (char *)fc->enc_buf, + fc->group.slice_bytes + FEC_HEADER_SIZE); fc->current_slice_num++; fec_active = 1; } @@ -880,11 +1045,9 @@ static void vss_send(struct vss_task *vsst) size_t len; if (!mmd->chunks_sent) { - struct timeval tmp; mmd->stream_start = *now; - tv_scale(mmd->current_chunk, &mmd->afd.afhi.chunk_tv, &tmp); - mmd->offset = tv2ms(&tmp); mmd->events++; + set_mmd_offset(); } /* * We call the send function also in case of empty chunks as @@ -914,14 +1077,17 @@ static void vss_post_select(struct sched *s, struct task *t) int num = mmd->sender_cmd_data.cmd_num, sender_num = mmd->sender_cmd_data.sender_num; - if (senders[sender_num].client_cmds[num]) - senders[sender_num].client_cmds[num](&mmd->sender_cmd_data); + if (senders[sender_num].client_cmds[num]) { + ret = senders[sender_num].client_cmds[num] + (&mmd->sender_cmd_data); + if (ret < 0) + PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + } mmd->sender_cmd_data.cmd_num = -1; } - if (vsst->afsss != AFS_SOCKET_CHECK_FOR_WRITE) { - if (FD_ISSET(vsst->afs_socket, &s->rfds)) - recv_afs_result(vsst); - } else if (FD_ISSET(vsst->afs_socket, &s->wfds)) { + if (vsst->afsss != AFS_SOCKET_CHECK_FOR_WRITE) + recv_afs_result(vsst, &s->rfds); + else if (FD_ISSET(vsst->afs_socket, &s->wfds)) { PARA_NOTICE_LOG("requesting new fd from afs\n"); ret = send_buffer(vsst->afs_socket, "new"); if (ret < 0) @@ -971,7 +1137,6 @@ void init_vss_task(int afs_socket) free(hn); free(home); mmd->sender_cmd_data.cmd_num = -1; - make_empty_status_items(mmd->afd.verbose_ls_output); if (conf.autoplay_given) { struct timeval tmp; mmd->vss_status_flags |= VSS_PLAYING; diff --git a/vss.h b/vss.h index 9bdc449c..bd798b0c 100644 --- a/vss.h +++ b/vss.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -13,7 +13,6 @@ unsigned int vss_repos(void); unsigned int vss_paused(void); unsigned int vss_stopped(void); struct timeval *vss_chunk_time(void); -const char *supported_audio_formats(void); /** Stop playing after current audio file. */ #define VSS_NOMORE 1 @@ -24,32 +23,7 @@ const char *supported_audio_formats(void); /** Currently playing. */ #define VSS_PLAYING 8 -/** - * Each paraslash sender may register arbitrary many clients to the virtual - * streaming system, possibly with varying fec parameters. In order to do so, - * it must allocate a \a fec_client_parms structure and pass it to \ref - * add_fec_client. - * - * Clients are automatically removed from that list by the vss if an error - * occurs, or if the sender requests deletion of a client by calling \ref - * vss_del_fec_client(). - */ -struct fec_client; - -/** FEC parameters requested by FEC clients. */ -struct fec_client_parms { - /** Number of data slices plus redundant slices. */ - uint8_t slices_per_group; - /** Number of slices minus number of redundant slices. */ - uint8_t data_slices_per_group; - /** Maximal number of bytes per slice. */ - uint16_t max_slice_bytes; - /** Called by vss.c when the next slice should be sent. */ - int (*send)(char *buf, size_t num_bytes, void *private_data); - /** Passed verbatim to \a send(). */ - void *private_data; -}; - -int vss_add_fec_client(struct fec_client_parms *fcp, struct fec_client **result); +struct fec_client *vss_add_fec_client(struct sender_client *sc, + struct fec_client_parms *fcp); void vss_del_fec_client(struct fec_client *fc); size_t vss_get_fec_eof_packet(const char **buf); diff --git a/wav_filter.c b/wav_filter.c index 1c0da181..8408034d 100644 --- a/wav_filter.c +++ b/wav_filter.c @@ -1,38 +1,38 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file wav_filter.c A filter that inserts a wave header. */ +#include +#include + #include "para.h" #include "error.h" - #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "filter.h" #include "string.h" #include "portable_io.h" -/** size of the output buffer */ -#define WAV_OUTBUF_SIZE 81920 -/** a wav header is always 44 bytes */ +/** A wav header is always 44 bytes. */ #define WAV_HEADER_LEN 44 -/** always write 16 bit header */ +/** Always write 16 bit header. */ #define BITS 16 -static void make_wav_header(unsigned int channels, unsigned int samplerate, - struct filter_node *fn) +static void make_wav_header(unsigned int channels, unsigned int sample_rate, + char *headbuf) { - char *headbuf = fn->buf; unsigned int size = 0x7fffffff; - int bytespersec = channels * samplerate * BITS / 8; + int bytespersec = channels * sample_rate * BITS / 8; int align = channels * BITS / 8; - PARA_DEBUG_LOG("writing wave header: %d channels, %d KHz\n", channels, samplerate); + PARA_DEBUG_LOG("writing wave header: %d channels, %d KHz\n", channels, sample_rate); memset(headbuf, 0, WAV_HEADER_LEN); memcpy(headbuf, "RIFF", 4); write_u32(headbuf + 4, size - 8); @@ -41,7 +41,7 @@ static void make_wav_header(unsigned int channels, unsigned int samplerate, write_u32(headbuf + 16, 16); /* 16 + extra format bytes (zero) */ write_u16(headbuf + 20, 1); /* format (1 == PCM/uncompressed) */ write_u16(headbuf + 22, channels); - write_u32(headbuf + 24, samplerate); + write_u32(headbuf + 24, sample_rate); write_u32(headbuf + 28, bytespersec); write_u16(headbuf + 32, align); /* number of bytes per sample slice */ write_u16(headbuf + 34, BITS); /* significant bits per sample */ @@ -49,34 +49,8 @@ static void make_wav_header(unsigned int channels, unsigned int samplerate, write_u32(headbuf + 40, size - 44); /* chunk size */ } -static ssize_t wav_convert(char *inbuf, size_t len, struct filter_node *fn) -{ - size_t copy; - int *bof = fn->private_data; - - if (*bof) { - if (!len) - return 0; - if (!fn->fc->channels || !fn->fc->samplerate) { - PARA_ERROR_LOG("%s\n", para_strerror(E_WAV_BAD_FC)); - return -E_WAV_BAD_FC; - } - make_wav_header(fn->fc->channels, fn->fc->samplerate, fn); - fn->loaded = WAV_HEADER_LEN; - *bof = 0; -// return 0; - } - copy = PARA_MIN(len, fn->bufsize - fn->loaded); - memmove(fn->buf + fn->loaded, inbuf, copy); - fn->loaded += copy; -// PARA_DEBUG_LOG("len = %d, copy = %d\n", len, copy); - return copy; -} - static void wav_close(struct filter_node *fn) { - free(fn->buf); - fn->buf = NULL; free(fn->private_data); fn->private_data = NULL; } @@ -85,24 +59,79 @@ static void wav_open(struct filter_node *fn) { int *bof; - fn->bufsize = WAV_OUTBUF_SIZE; - fn->buf = para_malloc(fn->bufsize); fn->private_data = para_malloc(sizeof(int)); bof = fn->private_data; - fn->loaded = 0; *bof = 1; - PARA_INFO_LOG("wav filter node: %p, output buffer: %p, loaded: %zd\n", - fn, fn->buf, fn->loaded); +} + +static void wav_pre_select(struct sched *s, struct task *t) +{ + struct filter_node *fn = container_of(t, struct filter_node, task); + size_t iqs = btr_get_input_queue_size(fn->btrn); + + t->error = 0; + if (iqs == 0) + return; + sched_min_delay(s); +} + +static void wav_post_select(__a_unused struct sched *s, struct task *t) +{ + struct filter_node *fn = container_of(t, struct filter_node, task); + struct btr_node *btrn = fn->btrn; + size_t iqs = btr_get_input_queue_size(btrn); + int ret; + char *header, *buf; + int32_t rate, ch; + + t->error = 0; + if (iqs == 0) { + ret = -E_WAV_EOF; + if (btr_no_parent(btrn)) + goto err; + return; + } + ret = btr_exec_up(btrn, "sample_rate", &buf); + if (ret < 0) { + ret = -E_WAV_BAD_FC; + goto err; + } + ret = para_atoi32(buf, &rate); + free(buf); + if (ret < 0) + goto err; + ret = btr_exec_up(btrn, "channels", &buf); + if (ret < 0) { + ret = -E_WAV_BAD_FC; + goto err; + } + ret = para_atoi32(buf, &ch); + free(buf); + if (ret < 0) + goto err; + header = para_malloc(WAV_HEADER_LEN); + make_wav_header(ch, rate, header); + btr_add_output(header, WAV_HEADER_LEN, btrn); + ret = -E_WAV_SUCCESS; +err: + t->error = ret; + if (ret == -E_WAV_SUCCESS) + btr_splice_out_node(btrn); + else { + btr_remove_node(btrn); + PARA_ERROR_LOG("%s\n", para_strerror(-ret)); + } } /** - * the init function of the wav filter + * The init function of the wav filter. * - * \param f struct to initialize + * \param f Structure to initialize. */ void wav_filter_init(struct filter *f) { - f->convert = wav_convert; f->close = wav_close; f->open = wav_open; + f->pre_select = wav_pre_select; + f->post_select = wav_post_select; } diff --git a/web/documentation.in.html b/web/documentation.in.html index a9d3e3a2..2789112a 100644 --- a/web/documentation.in.html +++ b/web/documentation.in.html @@ -11,20 +11,8 @@ the pieces of paraslash work together. -
  • REQUIREMENTS, - list of required and optional software. -
  • - -
  • README, - the paraslash executables, with brief descriptions. -
  • - -
  • INSTALL, - installation and configuration notes. -
  • - -
  • README.afs, - audio file selector documentation. +
  • user manual, + Installation, Configuration and Usage.
  • @@ -37,7 +25,6 @@ [para_client] [para_audioc] [para_afh] - [para_fsck] [para_recv] [para_filter] [para_write] diff --git a/web/download.in.html b/web/download.in.html index b5ea7505..d735d5e8 100644 --- a/web/download.in.html +++ b/web/download.in.html @@ -5,9 +5,12 @@ download directory -or grab the current +or grab a -snapshot. +tarball + +of the current master branch. This version is expected to be more +stable than any of the released versions. All regular releases are cryptographically signed. @@ -15,7 +18,7 @@ Anonymous (read-only) git -access is also available. Checkout a copy with

    +access is also available. Check out a copy with

    git clone git://paraslash.systemlinux.org/git paraslash diff --git a/web/footer.html b/web/footer.html index d9c9f058..38572c94 100644 --- a/web/footer.html +++ b/web/footer.html @@ -1,13 +1,4 @@


    - -

    - Valid HTML 4.01! -

    ---> diff --git a/web/images/paraslash.ico b/web/images/paraslash.ico new file mode 100644 index 00000000..860c5abd Binary files /dev/null and b/web/images/paraslash.ico differ diff --git a/web/images/paraslash.png b/web/images/paraslash.png new file mode 100644 index 00000000..d4a1bd9a Binary files /dev/null and b/web/images/paraslash.png differ diff --git a/web/index.in.html b/web/index.in.html index 65b7f0ed..48f5bb51 100644 --- a/web/index.in.html +++ b/web/index.in.html @@ -1,10 +1,35 @@

    Events


      +
    • 2010-12-17: paraslash-0.4.5 + (sig) + "symmetric randomization" +
    • +
    • 2010-08-06: paraslash-0.4.4 + (sig) + "persistent regularity" +
    • +
    • 2010-07-05: paraslash-0.4.3 + (sig) + "imaginary radiation" +
    • +
    • 2010-06-23: What's cooking page online
    • +
    • 2010-04-23: paraslash-0.4.2 + (sig) + "associative expansion" +
    • +
    • 2009-12-22: paraslash-0.4.1 + (sig) + "concurrent horizon" +
    • 2009-12-07: paraslash-0.3.6 (sig) "cubic continuity"
    • +
    • 2009-11-10: paraslash-0.4.0 + (sig) + "simultaneous independence" +
    • 2009-09-21: paraslash-0.3.5 (sig) "symplectic separability" diff --git a/web/manual.m4 b/web/manual.m4 new file mode 100644 index 00000000..946b9d09 --- /dev/null +++ b/web/manual.m4 @@ -0,0 +1,2139 @@ +dnl To generate the html version, execute +dnl m4 web/manual.m4 | grutatxt --toc + +define(`LOCAL_LINK_NAME', `translit(`$1', `A-Z +', `a-z__')') +define(`REMOVE_NEWLINE', `translit(`$1',` +', ` ')') + +define(`REFERENCE', ./``#''`LOCAL_LINK_NAME($1)' (`REMOVE_NEWLINE($2)')) +define(`XREFERENCE', `$1' (`REMOVE_NEWLINE($2)')) +define(`EMPH', ``_''`REMOVE_NEWLINE($1)'``_'') + +Paraslash user manual +===================== + +This document describes how to install, configure and use the paraslash +network audio streaming system. Most chapters start with a chapter +overview and conclude with an example section. We try to focus on +general concepts and on the interaction of the various pieces of the +paraslash package. Hence this user manual is not meant as a replacement +for the manual pages that describe all command line options of each +paraslash executable. + +------------ +Introduction +------------ + +In this chapter we give an REFERENCE(Overview, overview) of the +interactions of the two main programs contained in the paraslash +package, followed by REFERENCE(The paraslash executables, brief +descriptions) of all executables. + +Overview +~~~~~~~~ + +The core functionality of the para suite is provided by two main +executables, para_server and para_audiod. The former maintains a +database of audio files and streams these files to para_audiod which +receives and plays the stream. + +In a typical setting, both para_server and para_audiod act as +background daemons whose functionality is controlled by client +programs: the para_audioc client controls para_audiod over a local +socket while the para_client program connects to para_server over a +local or remote networking connection. + +Typically, these two daemons run on different hosts but a local setup +is also possible. + +A simplified picture of a typical setup is as follows +<< +
      + server_host                                  client_host
      + ~~~~~~~~~~~                                  ~~~~~~~~~~~
      + 
      + +-----------+         audio stream           +-----------+
      + |para_server| -----------------------------> |para_audiod|
      + +-----------+                                +-----------+
      +      ^                                            ^
      +      |                                            |
      +      |                                            | connect
      +      |                                            |
      +      |                                            |
      +      |                                       +-----------+
      +      |                                       |para_audioc|
      +      |                                       +-----------+
      +      |
      +      |
      +      |                  connect              +-----------+
      +      +-------------------------------------- |para_client|
      +                                              +-----------+
      +
      +>> + +The paraslash executables +~~~~~~~~~~~~~~~~~~~~~~~~~ + +*para_server* + +para_server streams binary audio data (MP3, OGG/Vorbis, OGG/Speex, +M4A, WMA files) over local and/or remote networks. It listens on a +TCP port and accepts commands such as play, stop, pause, next from +authenticated clients. There are many more commands though, see the +man page of para_server for a description of all commands. + +It supports three built-in network streaming protocols +(senders/receivers): HTTP, DCCP, or UDP. This is explained in more +detail in the section on REFERENCE(Networking, networking). + +The built-in audio file selector of paraslash is used to manage your +audio files. It maintains statistics on the usage of all available +audio files such as last-played time, and the number of times each +file was selected. + +Additional information may be added to the database to allow +fine-grained selection based on various properties of the audio file, +including information found in (ID3) tags. However, old-fashioned +playlists are also supported. + +It is also possible to store images (album covers) and lyrics in the +database and associate these to the corresponding audio files. + +The section on the REFERENCE(The audio file selector, audio file +selector) discusses this topic. + + +*para_client* + +The client program to connect to para_server. paraslash commands +are sent to para_server and the response is dumped to STDOUT. This +can be used by any scripting language to produce user interfaces with +little programming effort. + +All connections between para_server and para_client are encrypted +with a symmetric RC4 session key. For each user of paraslash you must +create a public/secret RSA key pair for authentication. + + +*para_audiod* + +The local daemon that collects information from para_server. + +It runs on the client side and connects to para_server. As soon as +para_server announces the availability of an audio stream, para_audiod +starts an appropriate receiver, any number of filters and a paraslash +writer to play the stream. + +Moreover, para_audiod listens on a local socket and sends status +information about para_server and para_audiod to local clients on +request. Access via this local socket may be restricted by using Unix +socket credentials, if available. + + +*para_audioc* + +The client program which talks to para_audiod. Used to control +para_audiod, to receive status info, or to grab the stream at any +point of the decoding process. + +*para_recv* + +A command line HTTP/DCCP/UDP stream grabber. The http mode is +compatible with arbitrary HTTP streaming sources (e.g. icecast). + +*para_filter* + +A filter program that reads from STDIN and writes to STDOUT. +Like para_recv, this is an atomic building block which can be used to +assemble higher-level audio receiving facilities. It combines several +different functionalities in one tool: decoders for multiple audio +formats and a number of processing filters, among these a normalizer +for audio volume. + +*para_afh* + +A small stand-alone program that prints tech info about the given +audio file to STDOUT. It can be instructed to print a "chunk table", +an array of offsets within the audio file or to write the content of +the audio file in complete chunks 'just in time'. + +This allows third-party streaming software that is unaware of the +particular audio format to send complete frames in real time. + +*para_write* + +A modular audio stream writer. It supports a simple file writer +output plug-in and optional WAV/raw players for ALSA (Linux) and for +coreaudio (Mac OS). para_write can also be used as a stand-alone WAV +or raw audio player. + + +*para_gui* + +Curses-based gui that presents status information obtained in a curses +window. Appearance can be customized via themes. para_gui provides +key-bindings for the most common server commands and new key-bindings +can be added easily. + + +*para_fade* + +An (OSS-only) alarm clock and volume-fader. + +----------- +Quick start +----------- + +This chapter lists the REFERENCE(Requirements, necessary software) +that must be installed to compile the paraslash package, describes +how to REFERENCE(Installation, compile and install) the paraslash +source code and the steps that have to be performed in order to +REFERENCE(Quick start, set up) a typical server and client. + +Requirements +~~~~~~~~~~~~ + +In any case you'll need + + - XREFERENCE(http://systemlinux.org/~maan/osl/, libosl). + The _object storage layer_ library is used by para_server. To + clone the source code repository, execute + + git clone git://git.tuebingen.mpg.de/osl + + - XREFERENCE(ftp://ftp.gnu.org/pub/gnu/gcc, gcc). The + EMPH(gnu compiler collection) is usually shipped with the + distro. gcc-3.3 or newer is required. + + - XREFERENCE(ftp://ftp.gnu.org/pub/gnu/make, gnu make) is + also shipped with the disto. On BSD systems the gnu make + executable is often called gmake. + + - XREFERENCE(ftp://ftp.gnu.org/pub/gnu/bash, bash). Some + scripts which run during compilation require the EMPH(Bourne + again shell). It is most likely already installed. + + - XREFERENCE(http://www.openssl.org/, openssl). The EMPH(Secure + Sockets Layer) library is needed for cryptographic routines + on both the server and the client side. It is usually shipped + with the distro, but you might have to install the "development + package" (called libssl-dev on debian systems) as well. + + - XREFERENCE(ftp://ftp.gnu.org/pub/gnu/help2man, help2man) + is used to create the man pages. + +Optional: + + - XREFERENCE(http://www.underbit.com/products/mad/, libmad). + To compile in MP3 support for paraslash, the development + package must be installed. It is called libmad0-dev on + debian-based systems. Note that libmad is not necessary on + the server side, i.e. for sending MP3 files. + + - XREFERENCE(http://www.underbit.com/products/mad/, + libid3tag). For version-2 ID3 tag support, you'll need + the libid3tag development package libid3tag0-dev. Without + libid3tag, only version one tags are recognized. + + - XREFERENCE(http://www.xiph.org/downloads/, ogg vorbis). + For ogg vorbis streams you'll need libogg, libvorbis, + libvorbisfile. The corresponding Debian packages are called + libogg-dev and libvorbis-dev. + + - XREFERENCE(http://www.audiocoding.com/, libfaad). For aac + files (m4a) you'll need libfaad (libfaad-dev). + + - XREFERENCE(http://www.speex.org/, speex). In order to stream + or decode speex files, libspeex (libspeex-dev) is required. + + - XREFERENCE(ftp://ftp.alsa-project.org/pub/lib/, alsa-lib). On + Linux, you'll need to have ALSA's development package + libasound2-dev installed. + +Installation +~~~~~~~~~~~~ + +First make sure all non-optional packages listed in the section on +REFERENCE(Requirements, required software) are installed on your +system. + +You don't need everything listed there. In particular, MP3, OGG/Vorbis, +OGG/Speex and AAC support are all optional. The configure script will +detect what is installed on your system and will only try to build +those executables that can be built with your setup. + +Note that no special decoder library (not even the MP3 decoding library +libmad) is needed for para_server if you only want to stream MP3 or WMA +files. Also, it's fine to use para_server on a box without sound card. + +Next, install the paraslash package on all machines, you'd like this +software to run on: + + (./configure && make) > /dev/null + +There should be no errors but probably some warnings about missing +packages which usually implies that not all audio formats will be +supported. If headers or libs are installed at unusual locations you +might need to tell the configure script where to find them. Try + + ./configure --help + +to see a list of options. If the paraslash package was compiled +successfully, execute as root, + + make install + +to install executables under /usr/local/bin and the man pages under +/usr/local/man. + +Configuration +~~~~~~~~~~~~~ + +*Step 1*: Create a paraslash user + +In order to control para_server at runtime you must create a paraslash +user. As authentication is based on the RSA crypto system you'll have +to create an RSA key pair. If you already have a user and an RSA key +pair, you may skip this step. + +In this section we'll assume a typical setup: You would like to run +para_server on some host called server_host as user foo, and you want +to connect to para_server from another machine called client_host as +user bar. + +As foo@server_host, create ~/.paraslash/server.users by typing the +following commands: + + user=bar + target=~/.paraslash/server.users + key=~/.paraslash/key.pub.$user + perms=AFS_READ,AFS_WRITE,VSS_READ,VSS_WRITE + mkdir -p ~/.paraslash + echo "user $user $key $perms" >> $target + +Next, change to the "bar" account on client_host and generate the +key pair with the commands + + key=~/.paraslash/key.$LOGNAME + mkdir -p ~/.paraslash + (umask 077 && openssl genrsa -out $key 2048) + +para_server only needs to know the public key of the key pair just +created. It can be extracted with + + pubkey=~/.paraslash/key.pub.$LOGNAME + openssl rsa -in $key -pubout -out $pubkey + +Copy the public key just created to server_host (you may skip this step +for a single-user setup, i.e. if foo=bar and server_host=client_host): + + scp $pubkey foo@server_host:.paraslash/ + +Finally, tell para_client to connect to server_host: + + conf=~/.paraslash/client.conf + echo 'hostname server_host' > $conf + + +*Step 2*: Start para_server + +Before starting the server make sure you have write permissions to +the directory /var/paraslash that has been created during installation: + + sudo chown $LOGNAME /var/paraslash + +Alternatively, use the --afs_socket Option to specify a different +location for the AFS command socket. + +For this first try, we'll use the info loglevel to make the output +of para_server more verbose. + + para_server -l info + +Now you can use para_client to connect to the server and issue +commands. Open a new shell as bar@client_host and try + + para_client help + para_client si + +to retrieve the list of available commands and some server info. +Don't proceed if this doesn't work. + +*Step 3*: Create and populate the database + +An empty database is created with + + para_client init + +This initializes a couple of empty tables under +~/.paraslash/afs_database-0.4. You normally don't need to look at these +tables, but it's good to know that you can start from scratch with + + rm -rf ~/.paraslash/afs_database-0.4 + +in case something went wrong. + +Next, you need to add some audio files to that database so that +para_server knows about them. Choose an absolute path to a directory +containing some audio files and add them to the audio file table: + + para_client add /my/mp3/dir + +This might take a while, so it is a good idea to start with a directory +containing not too many files. Note that the table only contains data +about the audio files found, not the files themselves. + +You may print the list of all known audio files with + + para_client ls + +*Step 4*: Configure para_audiod + +para_audiod needs to create a "well-known" socket for the clients to +connect to. The default path for this socket is + + /var/paraslash/audiod_socket.$HOSTNAME + +In order to make this directory writable for para_audiod, execute +as bar@client_host + + sudo chown $LOGNAME /var/paraslash + + +We will also have to tell para_audiod that it should receive the +audio stream from server_host: + + para_audiod -l info -r 'mp3:http -i server_host' + +You should now be able to listen to the audio stream once para_server +starts streaming. To activate streaming, execute + + para_client play + +Since no playlist has been specified yet, the "dummy" mode which +selects all known audio files is activated automatically. See the +section on the REFERENCE(The audio file selector, audio file selector) +for how to use playlists and moods to specify which files should be +streamed in which order. + +*Troubleshooting* + +It did not work? To find out why, try to receive, decode and play the +stream manually using para_recv, para_filter and para_write as follows. + +For simplicity we assume that you're running Linux/ALSA and that only +MP3 files have been added to the database. + + para_recv -r 'http -i server_host' > file.mp3 + # (interrupt with CTRL+C after a few seconds) + ls -l file.mp3 # should not be empty + para_filter -f mp3dec -f wav < file.mp3 > file.wav + ls -l file.wav # should be much bigger than file.mp3 + para_write -w alsa < file.wav + +Double check what is logged by para_server and use the --loglevel +option of para_recv, para_filter and para_write to increase verbosity. + +--------------- +User management +--------------- + +para_server uses a challenge-response mechanism to authenticate +requests from incoming connections, similar to ssh's public key +authentication method. Authenticated connections are encrypted using +the RC4 stream cipher. + +In this chapter we briefly describe RSA and RC4 and sketch the +REFERENCE(Client-server authentication, authentication handshake) +between para_client and para_server. User management is discussed +in the section on REFERENCE(The user_list file, the user_list file). +These sections are all about communication between the client and the +server. Connecting para_audiod is a different matter and is described +in a REFERENCE(Connecting para_audiod, separate section). + + + +RSA and RC4 +~~~~~~~~~~~ + +RSA is an asymmetric block cipher which is used in many applications, +including ssh and gpg. An RSA key consists in fact of two keys, +called the public key and the private key. A message can be encrypted +with either key and only the counterpart of that key can decrypt +the message. While RSA can be used for both signing and encrypting +a message, paraslash only uses RSA only for the latter purpose. The +RSA public key encryption and signatures algorithms are defined in +detail in RFC 2437. + +RC4 is a stream cipher, i.e. the input is XORed with a pseudo-random +key stream to produce the output. Decryption uses the same function +calls as encryption. While RC4 supports variable key lengths, +paraslash uses a fixed length of 256 bits, which is considered a +strong encryption by today's standards. Since the same key must never +be used twice, a different, randomly-generated key is used for every +new connection. + +Client-server authentication +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The authentication handshake between para_client and para_server goes +as follows: + + - para_client connects to para_server and sends an + authentication request for a user. It does so by connecting + to para_server, TCP 2990, the control port of para_server. + + - para_server accepts the connection and forks a child process + which is supposed to handle the connection. The parent process + keeps listening on the control port while the child process + (also called para_server below) continues as follows. + + - para_server loads the RSA public key of that user, fills a + fixed-length buffer with random bytes, encrypts that buffer + using the public key and sends the encrypted buffer to the + client. The first part of the buffer is the challenge which + is used for authentication while the second part is the RC4 + session key. + + - para_client receives the encrypted buffer and decrypts it + using the user's private key, thereby obtaining the challenge + buffer and the session key. It sends the SHA1 hash value of + the challenge back to para_server and stores the session key + for further use. + + - para_server also computes the SHA1 hash of the challenge + and compares it against what was sent back by the client. + + - If the two hashes do not match, the authentication has + failed and para_server closes the connection. + + - Otherwise the user is considered authenticated and the client + is allowed to proceed by sending a command to be executed. From + this point on the communication is encrypted using the RC4 + stream cipher with the session key known to both peers. + +paraslash relies on the quality of openssl's cryptographically strong +pseudo-random bytes, on the security of the implementation of the +openssl RSA and RC4 crypto routines and on the infeasibility to invert +the SHA1 function. + +Neither para_server or para_client create RSA keys on their own. This +has to be done once for each user as sketched in REFERENCE(Quick start, +Quick start) and discussed in more detail REFERENCE(The user_list +file, below). + +The user_list file +~~~~~~~~~~~~~~~~~~ + +At startup para_server reads the user list file which must contain +one line per user. The default location of the user list file may be +changed with the --user_list option. + +There should be at least one user in this file. Each user must have +an RSA key pair. The public part of the key is needed by para_server +while the private key is needed by para_client. Each line of the +user list file must be of the form + + user + +where _username_ is an arbitrary string (usually the user's login +name), _key_ is the full path to that user's public RSA key, and +_perms_ is a comma-separated list of zero or more of the following +permission bits: + + +---------------------------------------------------------+ + | AFS_READ | read the contents of the databases | + +-----------+---------------------------------------------+ + | AFS_WRITE | change database contents | + +-----------+---------------------------------------------+ + | VSS_READ | obtain information about the current stream | + +-----------+---------------------------------------------+ + | VSS_WRITE | change the current stream | + +---------------------------------------------------------+ + +The permission bits specify which commands the user is allowed to +execute. The output of + + para_client help + +contains in the third column the permissions needed to execute the +command. + +A new RSA key can be created with + + openssl genrsa -out 2048 + +and the public part may be extracted with + + openssl rsa -in -pubout -out + +Note that para_server refuses to use a key if it is shorter than 2048 +bits. In particular, the RSA keys of paraslash 0.3.x will not work +with version 0.4.x. Moreover, para_client refuses to use a (private) +key which is world-readable. + +It is possible to make para_server reread the user_list file by +executing the paraslash "hup" command or by sending SIGHUP to the +PID of para_server. + + +Connecting para_audiod +~~~~~~~~~~~~~~~~~~~~~~ + +para_audiod listens on a Unix domain socket. Those sockets are +for local communication only, so only local users can connect to +para_audiod. The default is to let any user connect but this can be +restricted on platforms that support UNIX socket credentials which +allow para_audiod to obtain the Unix credentials of the connecting +process. + +Use para_audiod's --user_allow option to allow connections only for +a limited set of users. + +----------------------- +The audio file selector +----------------------- + +paraslash comes with a sophisticated audio file selector (AFS), +whose main task is to determine which file to stream next, based on +information on the audio files stored in a database. It communicates +also with para_client whenever an AFS command is executed, for example +to answer a database query. + +Besides the traditional playlists, AFS supports audio file selection +based on _moods_ which act as a filter that limits the set of all +known audio files to those which satisfy certain criteria. It also +maintains tables containing images (e.g. album cover art) and lyrics +that can be associated with one or more audio files. + +AFS uses libosl, the object storage layer, as the backend library +for storing information on audio files, playlists, etc. This library +offers functionality similar to a relational database, but is much +more lightweight than a full database backend. + +In this chapter we sketch the setup of the REFERENCE(The AFS process, +AFS process) during server startup and proceed with the description +of the REFERENCE(Database layout, layout) of the various database +tables. The section on REFERENCE(Playlists and moods, playlists +and moods) explains these two audio file selection mechanisms +in detail and contains pratical examples. The way REFERENCE(File +renames and content changes, file renames and content changes) are +detected is discussed briefly before the REFERENCE(Troubleshooting, +Troubleshooting) section which concludes the chapter. + +The AFS process +~~~~~~~~~~~~~~~ + +On startup, para_server forks to create the AFS process which opens +the OSL database tables. The server process communicates with the +AFS process via pipes and shared memory. Usually, the AFS process +awakes only briefly whenever the current audio file changes. The AFS +process determines the next audio file, opens it, verifies it has +not been changed since it was added to the database and passes the +open file descriptor to the server process, along with audio file +meta-data such as file name, duration, audio format and so on. The +server process then starts to stream the audio file. + +The AFS process also accepts connections from local clients via +a well-known socket. However, only child processes of para_server +may connect through this socket. All server commands that have the +AFS_READ or AFS_WRITE permission bits use this mechanism to query or +change the database. + +Database layout +~~~~~~~~~~~~~~~ + +*The audio file table* + +This is the most important and usually also the largest table of the +AFS database. It contains the information needed to stream each audio +file. In particular the following data is stored for each audio file. + + - SHA1 hash value of the audio file contents. This is computed + once when the file is added to the database. Whenever AFS + selects this audio file for streaming the hash value is + recomputed and checked against the value stored in the + database to detect content changes. + + - The time when this audio file was last played. + + - The number of times the file has been played so far. + + - The attribute bitmask. + + - The image id which describes the image associated with this + audio file. + + - The lyrics id which describes the lyrics associated with + this audio file. + + - The audio format id (MP3, OGG, ...). + + - An amplification value that can be used by the amplification + filter to pre-amplify the decoded audio stream. + + - The chunk table. It describes the location and the timing + of the building blocks of the audio file. This is used by + para_server to send chunks of the file at appropriate times. + + - The duration of the audio file. + + - Tag information contained in the audio file (ID3 tags, + Vorbis comments, ...). + + - The number of channels + + - The encoding bitrate. + + - The sampling frequency. + +To add or refresh the data contained in the audio file table, the _add_ +command is used. It takes the full path of either an audio file or a +directory. In the latter case, the directory is traversed recursively +and all files which are recognized as valid audio files are added to +the database. + +*The attribute table* + +The attribute table contains two columns, _name_ and _bitnum_. An +attribute is simply a name for a certain bit number in the attribute +bitmask of the audio file table. + +Each of the 64 bits of the attribute bitmask can be set for each +audio file individually. Hence up to 64 different attributes may be +defined. For example, "pop", "rock", "blues", "jazz", "instrumental", +"german_lyrics", "speech", whatever. You are free to choose as +many attributes as you like and there are no naming restrictions +for attributes. + +A new attribute "test" is created by + + para_client addatt test +and + para_client lsatt + +lists all available attributes. You can set the "test" attribute for +an audio file by executing + + para_client setatt test+ /path/to/the/audio/file + +Similarly, the "test" bit can be removed from an audio file with + + para_client setatt test- /path/to/the/audio/file + +Instead of a path you may use a shell wildcard pattern. The attribute +is applied to all audio files matching that pattern: + + para_client setatt test+ '/test/directory/*' + +The command + + para_client -- ls -lv + +gives you a verbose listing of your audio files also showing which +attributes are set. + +In case you wonder why the double-dash in the above command is needed: +It tells para_client to not interpret the options after the dashes. If +you find this annoying, just say + + alias para='para_client --' + +and be happy. In what follows we shall use this alias. + +The "test" attribute can be dropped from the database with + + para rmatt test + +Read the output of + + para help ls + para help setatt + +for more information and a complete list of command line options to +these commands. + +*Blob tables* + +The image, lyrics, moods and playlists tables are all blob tables. +Blob tables consist of three columns each: The identifier which is +a positive non-negative number that is auto-incremented, the name +(an arbitrary string) and the content (the blob). + +All blob tables support the same set of actions: cat, ls, mv, rm +and add. Of course, _add_ is used for adding new blobs to the table +while the other actions have the same meaning as the corresponding +Unix commands. The paraslash commands to perform these actions are +constructed as the concatenation of the table name and the action. For +example addimg, catimg, lsimg, mvimg, rmimg are the commands that +manipulate or query the image table. + +The add variant of these commands is special as these commands read +the blob contents from stdin. To add an image to the image table the +command + + para addimg image_name < file.jpg + +can be used. + +Note that the images and lyrics are not interpreted at all, and also +the playlist and the mood blobs are only investigated when the mood +or playlist is activated by using the select command. + +*The score table* + +Unlike all other tables the contents of the score table remain in +memory and are never stored on disk. The score table contains two +columns: The SHA1 hash value (of an audio file) and its current +score. + +However, only those files which are admissible for the current mood +or playlist are contained in the score table. The audio file selector +always chooses the row with the highest score as the file to stream +next. While doing so, it computes the new score and updates the +last_played and the num_played fields in the audio file table. + +The score table is recomputed by the select command which loads a +new mood or playlist. + +Playlists and moods +~~~~~~~~~~~~~~~~~~~ + +Playlists and moods offer two different ways of specifying the set of +admissible files. A playlist in itself describes a set of admissible +files. A mood, in contrast, describes the set of admissible files in +terms of attributes and other type of information available in the +audio file table. As an example, a mood can define a filename pattern, +which is then matched against the names of audio files in the table. + +Selecting a mood or playlist means the generation of a ranking +(a score table) for the set of admissible files. Audio files are +then selected on a highest-score-first basis. The score table is +recomputed at the moment the mood or playlist is selected. + +*Playlists* + +Playlists are accommodated in the playlist table of the afs database, +using the aforementioned blob format for tables. A new filelist is +created using the addpl command, by specifying the full (absolute) +paths of all desired audio files, separated by newlines. For example + + find /my/mp3/dir -name "*.mp3" | para addpl my_playlist + +If _my_playlist_ already exists it is overwritten. To activate the +new playlist, execute + + para select p/my_playlist + +The audio file selector will assign scores to each entry of the list, +in descending order so that files will be selected in order. If a +file could not be opened for streaming, its entry is removed from +the score table (but not from the playlist). + +*Moods* + +A mood consists of a unique name and its *mood definition*, which is +a set of *mood lines* containing expressions in terms of attributes +and other data contained in the database. + +At any time, at most one mood can be *active* which means that +para_server is going to select only files from that subset of +admissible files. + +So in order to create a mood definition one has to write a set of +mood lines. Mood lines come in three flavours: Accept lines, deny +lines and score lines. + +The general syntax of the three types of mood lines is + + + accept [with score ] [if] [not] [options] + deny [with score ] [if] [not] [options] + score [if] [not] [options] + + +Here is either an integer or the string "random" which assigns +a random score to all matching files. The score value changes the +order in which admissible files are going to be selected, but is of +minor importance for this introduction. + +So we concentrate on the first two forms, i.e. accept and deny +lines. As usual, everything in square brackets is optional, i.e. +accept/deny lines take the following form when ignoring scores: + + accept [if] [not] [options] + +and analogously for the deny case. The "if" keyword is only syntactic +sugar and has no function. The "not" keyword just inverts the result, +so the essence of a mood line is the mood method part and the options +following thereafter. + +A *mood method* is realized as a function which takes an audio file +and computes a number from the data contained in the database. +If this number is non-negative, we say the file *matches* the mood +method. The file matches the full mood line if it either + + - matches the mood method and the "not" keyword is not given, +or + - does not match the mood method, but the "not" keyword is given. + +The set of admissible files for the whole mood is now defined as those +files which match at least one accept mood line, but no deny mood line. +More formally, an audio file F is admissible if and only if + + (F ~ AL1 or F ~ AL2...) and not (F ~ DL1 or F ~ DN2 ...) + +where AL1, AL2... are the accept lines, DL1, DL2... are the deny +lines and "~" means "matches". + +The cases where no mood lines of accept/deny type are defined need +special treatment: + + - Neither accept nor deny lines: This treats all files as + admissible (in fact, that is the definition of the dummy mood + which is activated automatically if no moods are available). + + - Only accept lines: A file is admissible iff it matches at + least one accept line: + + F ~ AL1 or F ~ AL2 or ... + + - Only deny lines: A file is admissible iff it matches no + deny line: + + not (F ~ DL1 or F ~ DN2 ...) + + + +*List of mood_methods* + + no_attributes_set + +Takes no arguments and matches an audio file if and only if no +attributes are set. + + is_set + +Takes the name of an attribute and matches iff that attribute is set. + + path_matches + +Takes a filename pattern and matches iff the path of the audio file +matches the pattern. + + artist_matches + album_matches + title_matches + comment_matches + +Takes an extended regular expression and matches iff the text of the +corresponding tag of the audio file matches the pattern. If the tag +is not set, the empty string is matched against the pattern. + + year ~ + bitrate ~ + frequency ~ + channels ~ + num_played ~ + +Takes a comparator ~ of the set {<, =, <=, >, >=, !=} and a number +. Matches an audio file iff the condition ~ is +satisfied where val is the corresponding value of the audio file +(value of the year tag, bitrate in kbit/s, frequency in Hz, channel +count, play count). + +The year tag is special as its value is undefined if the audio file +has no year tag or the content of the year tag is not a number. Such +audio files never match. Another difference is the special treatment +if the year tag is a two-digit number. In this case either 1900 or +2000 are added to the tag value depending on whether the number is +greater than 2000 plus the current year. + + +*Mood usage* + +To create a new mood called "my_mood", write its definition into +some temporary file, say "tmpfile", and add it to the mood table +by executing + + para addmood my_mood < tmpfile + +If the mood definition is really short, you may just pipe it to the +client instead of using temporary files. Like this: + + echo "$MOOD_DEFINITION" | para addmood my_mood + +There is no need to keep the temporary file since you can always use +the catmood command to get it back: + + para catmood my_mood + +A mood can be activated by executing + + para select m/my_mood + +Once active, the list of admissible files is shown by the ls command +if the "-a" switch is given: + + para ls -a + + +*Example mood definition* + +Suppose you have defined attributes "punk" and "rock" and want to define +a mood containing only Punk-Rock songs. That is, an audio file should be +admissible if and only if both attributes are set. Since + + punk and rock + +is obviously the same as + + not (not punk or not rock) + +(de Morgan's rule), a mood definition that selects only Punk-Rock +songs is + + deny if not is_set punk + deny if not is_set rock + + + +File renames and content changes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Since the audio file selector knows the SHA1 of each audio file that +has been added to the afs database, it recognizes if the content of +a file has changed, e.g. because an ID3 tag was added or modified. +Also, if a file has been renamed or moved to a different location, +afs will detect that an entry with the same hash value already exists +in the audio file table. + +In both cases it is enough to just re-add the new file. In the +first case (file content changed), the audio table is updated, while +metadata such as the num_played and last_played fields, as well as +the attributes, remain unchanged. In the other case, when the file +is moved or renamed, only the path information is updated, all other +data remains as before. + +It is possible to change the behaviour of the add command by using the +"-l" (lazy add) or the "-f" (force add) option. + +Troubleshooting +~~~~~~~~~~~~~~~ + +Use the debug loglevel (option -l debug for most commands) to show +debugging info. Almost all paraslash executables have a brief online +help which is displayed by using the -h switch. The --detailed-help +option prints the full help text. + +If para_server crashed or was killed by SIGKILL (signal 9), it +may refuse to start again because of "dirty osl tables". In this +case you'll have to run the oslfsck program of libosl to fix your +database. It might be necessary to use --force (even if your name +isn't Luke). However, make sure para_server isn't running before +executing oslfsck --force. + +If you don't mind to recreate your database you can start +from scratch by removing the entire database directory, i.e. + + rm -rf ~/.paraslash/afs_database-0.4 + +Be aware that this removes all attribute definitions, all playlists +and all mood definitions and requires to re-initialize the tables. + +Although oslfsck fixes inconsistencies in database tables it doesn't +care about the table contents. To check for invalid table contents, use + + para_client check + +This prints out references to missing audio files as well as invalid +playlists and mood definitions. + +--------------------------------------- +Audio formats and audio format handlers +--------------------------------------- + +Audio formats +~~~~~~~~~~~~~ + +The following audio formats are supported by paraslash: + +*MP3* + +Mp3, MPEG-1 Audio Layer 3, is a common audio format for audio storage, +designed as part of its MPEG-1 standard. An MP3 file is made up of +multiple MP3 frames, which consist of a header and a data block. The +size of an MP3 frame depends on the bit rate and on the number +of channels. For a typical CD-audio file (sample rate of 44.1 kHz +stereo), encoded with a bit rate of 128 kbit, an MP3 frame is about +400 bytes large. + +*OGG/Vorbis* + +OGG is a standardized audio container format, while Vorbis is an +open source codec for lossy audio compression. Since Vorbis is most +commonly made available via the OGG container format, it is often +referred to as OGG/Vorbis. The OGG container format divides data into +chunks called OGG pages. A typical OGG page is about 4KB large. The +Vorbis codec creates variable-bitrate (VBR) data, where the bitrate +may vary considerably. + +*OGG/Speex* + +Speex is an open-source speech codec that is based on CELP (Code +Excited Linear Prediction) coding. It is designed for voice +over IP applications, has modest complexity and a small memory +footprint. Wideband and narrowband (telephone quality) speech are +supported. As for Vorbis audio, Speex bit-streams are often stored +in OGG files. + +*AAC* + +Advanced Audio Coding (AAC) is a standardized, lossy compression +and encoding scheme for digital audio which is the default audio +format for Apple's iPhone, iPod, iTunes. Usually MPEG-4 is used as +the container format and audio files encoded with AAC have the .m4a +extension. A typical AAC frame is about 700 bytes large. + +*WMA* + +Windows Media Audio (WMA) is an audio data compression technology +developed by Microsoft. A WMA file is usually encapsulated in the +Advanced Systems Format (ASF) container format, which also specifies +how meta data about the file is to be encoded. The bit stream of WMA +is composed of superframes, each containing one or more frames of +2048 samples. For 16 bit stereo a WMA superframe is about 8K large. + +Meta data +~~~~~~~~~ + +Unfortunately, each audio format has its own conventions how meta +data is added as tags to the audio file. + +For MP3 files, ID3, version 1 and 2 are widely used. ID3 version 1 +is rather simple but also very limited as it supports only artist, +title, album, year and comment tags. Each of these can only be at most +32 characters long. ID3, version 2 is much more flexible but requires +a separate library being installed for paraslash to support it. + +Ogg vorbis files contain meta data as Vorbis comments, which are +typically implemented as strings of the form "[TAG]=[VALUE]". Unlike +ID3 version 1 tags, one may use whichever tags are appropriate for +the content. + +AAC files usually use the MPEG-4 container format for storing meta +data while WMA files wrap meta data as special objects within the +ASF container format. + +paraslash only tracks the most common tags that are supported by +all tag variants: artist, title, year, album, comment. When a file +is added to the AFS database, the meta data of the file is extracted +and stored in the audio file table. + +Chunks and chunk tables +~~~~~~~~~~~~~~~~~~~~~~~ + +paraslash uses the word "chunk" as common term for the building blocks +of an audio file. For MP3 files, a chunk is the same as an MP3 frame, +while for OGG files a chunk is an OGG page, etc. Therefore the chunk +size varies considerably between audio formats, from a few hundred +bytes (MP3) up to 8K (WMA). + +The chunk table contains the offsets within the audio file that +correspond to the chunk boundaries of the file. Like the meta data, +the chunk table is computed and stored in the database whenever an +audio file is added. + +The paraslash senders (see below) always send complete chunks. The +granularity for seeking is therefore determined by the chunk size. + +Audio format handlers +~~~~~~~~~~~~~~~~~~~~~ + +For each audio format paraslash contains an audio format handler whose +first task is to tell whether a given file is a valid audio file of +this type. If so, the audio file handler extracts some technical data +(duration, sampling rate, number of channels etc.), computes the +chunk table and reads the meta data. + +The audio format handler code is linked into para_server and executed +via the _add_ command. The same code is also available as a stand-alone +tool, para_afh, which can be used to print the technical data, the +chunk table and the meta data of a file. Furthermore, one can use +para_afh to cut an audio file, i.e. to select some of its chunks to +produce a new file containing only these chunks. + +---------- +Networking +---------- + +Paraslash uses different network connections for control and data. +para_client communicates with para_server over a dedicated TCP control +connection. To transport audio data, separate data connections are +used. For these data connections, a variety of transports (UDP, DCCP, +HTTP) can be chosen. + +The chapter starts with the REFERENCE(The paraslash control +service, control service), followed by a section on the various +REFERENCE(Streaming protocols, streaming protocols) in which the data +connections are described. The way audio file headers are embedded into +the stream is discussed REFERENCE(Streams with headers and headerless +streams, briefly) before the REFERENCE(Networking examples, example +section) which illustrates typical commands for real-life scenarios. + +Both IPv4 and IPv6 are supported. + +The paraslash control service +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +para_server is controlled at runtime via the paraslash control +connection. This connection is used for server commands (play, stop, +...) as well as for afs commands (ls, select, ...). + +The server listens on a TCP port and accepts connections from clients +that connect the open port. Each connection causes the server to fork +off a client process which inherits the connection and deals with that +client only. In this classical accept/fork approach the server process +is unaffected if the child dies or goes crazy for whatever reason. In +fact, the child process can not change address space of server process. + +The section on REFERENCE(Client-server authentication, client-server +authentication) above described the early connection establishment +from the crypto point of view. Here it is described what happens +after the connection (including crypto setup) has been established. +There are four processes involved during command dispatch as sketched +in the following diagram. + +<< +
      + server_host                                   client_host
      + ~~~~~~~~~~~                                   ~~~~~~~~~~~
      +
      + +-----------+             connect            +-----------+
      + |para_server|<------------------------------ |para_client|
      + +-----------+                                +-----------+
      +      |                                             ^
      +      |     fork   +---+                            |
      +      +----------> |AFS|                            |
      +      |            +---+                            |
      +      |              ^                              |
      +      |              |                              |
      +      |              | connect (cookie)             |
      +      |              |                              |
      +      |              |                              |
      +      |    fork   +-----+    inherited connection   |
      +      +---------->|child|<--------------------------+
      +                  +-----+
      +
      +>> + +Note that the child process is not a child of the afs process, +so communication of these two processes has to happen via local +sockets. In order to avoid abuse of the local socket by unrelated +processes, a magic cookie is created once at server startup time just +before the server process forks off the AFS process. This cookie is +known to the server, AFS and the child, but not to unrelated processes. + +There are two different kinds of commands: First there are commands +that cause the server to respond with some answer such as the list +of all audio files. All but the addblob commands (addimg, addlyr, +addpl, addmood) are of this kind. The addblob commands add contents +to the database, so they need to transfer data the other way round, +from the client to the server. + +There is no knowledge about the server commands built into para_client, +so it does not know about addblob commands. Instead, it inspects the +first data package sent by the server for a magic string. If this +string was found, it sends STDIN to the server, otherwise it dumps +data from the server to STDOUT. + +Streaming protocols +~~~~~~~~~~~~~~~~~~~ + +A network (audio) stream usually consists of one streaming source, +the _sender_, and one or more _receivers_ which read data over the +network from the streaming source. + +Senders are thus part of para_server while receivers are part of +para_audiod. Moreover, there is the stand-alone tool para_recv which +can be used to manually download a stream, either from para_server +or from a web-based audio streaming service. + +The following three streaming protocols are supported by paraslash: + + - HTTP. Recommended for public streams that can be played by + any player like mpg123, xmms, itunes, winamp, etc. The HTTP + sender is supported on all operating systems and all platforms. + + - DCCP. Recommended for LAN streaming. DCCP is currently + available only for Linux. + + - UDP. Recommended for multicast LAN streaming. + +See the Appendix on REFERENCE(Network protocols, network protocols) +for brief descriptions of the various protocols relevant for network +audio streaming with paraslash. + +It is possible to activate more than one sender simultaneously. +Senders can be controlled at run time and via config file and command +line options. + +Note that audio connections are _not_ encrypted. Transport or Internet +layer encryption should be used if encrypted data connections are +needed. + +Since DCCP and TCP are both connection-oriented protocols, connection +establishment/teardown and access control are very similar between +these two streaming protocols. UDP is the most lightweight option, +since in contrast to TCP/DCCP it is connectionless. It is also the +only protocol supporting IP multicast. + +The HTTP and the DCCP sender listen on a (TCP/DCCP) port waiting for +clients to connect and establish a connection via some protocol-defined +handshake mechanism. Both senders maintain two linked lists each: +The list of all clients which are currently connected, and the list +of access control entries which determines who is allowed to connect. +IP-based access control may be configured through config file and +command line options and via the "allow" and "deny" sender subcommands. + +Upon receiving a GET request from the client, the HTTP sender sends +back a status line and a message. The body of this message is the +audio stream. This is common practice and is supported by many popular +clients which can thus be used to play a stream offered by para_server. +For DCCP things are a bit simpler: No messages are exchanged between +the receiver and sender. The client simply connects and the sender +starts to stream. + +DCCP is an experimental protocol which offers a number of new features +not available for TCP. Both ends can negotiate these features using +a built-in negotiation mechanism. In contrast to TCP/HTTP, DCCP is +datagram-based (no retransmissions) and thus should not be used over +lossy media (e.g. WiFi networks). One useful feature offered by DCCP +is access to a variety of different congestion-control mechanisms +called CCIDs. Two different CCIDs are available per default on Linux: + + + - _CCID 2_. A Congestion Control mechanism similar to that + of TCP. The sender maintains a congestion window and halves + this window in response to congestion. + + + - _CCID-3_. Designed to be fair when competing for bandwidth. + It has lower variation of throughput over time compared with + TCP, which makes it suitable for streaming media. + +Unlike the HTTP and DCCP senders, the UDP sender maintains only a +single list, the _target list_. This list describes the set of clients +to which the stream is sent. There is no list for access control and +no "allow" and "deny" commands for the UDP sender. Instead, the "add" +and "delete" commands can be used to modify the target list. + +Since both UDP and DCCP offer an unreliable datagram-based transport, +additional measures are necessary to guard against disruptions over +networks that are lossy or which may be subject to interference (as +is for instance the case with WiFi). Paraslash uses FEC (Forward +Error Correction) to guard against packet losses and reordering. The +stream is FEC-encoded before it is sent through the UDP socket and +must be decoded accordingly on the receiver side. + +The packet size and the amount of redundancy introduced by FEC can +be configured via the FEC parameters which are dictated by server +and may also be configured through the "sender" command. The FEC +parameters are encoded in the header of each network packet, so no +configuration is necessary on the receiver side. See the section on +REFERENCE(Forward error correction, FEC) below. + +Streams with headers and headerless streams +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For OGG/Vorbis, OGG/Speex and wma streams, some of the information +needed to decode the stream is only contained in the audio file +header of the container format but not in each data chunk. Clients +must be able to obtain this information in case streaming starts in +the middle of the file or if para_audiod is started while para_server +is already sending a stream. + +This is accomplished in different ways, depending on the streaming +protocol. For connection-oriented streams (HTTP, DCCP) the audio file +header is sent prior to audio file data. This technique however does +not work for the connectionless UDP transport. Hence the audio file +header is periodically being embedded into the UDP audio data stream. +By default, the header is resent after five seconds. The receiver has +to wait until the next header arrives before it can start decoding +the stream. + +Examples +~~~~~~~~ + +The sender command of para_server allows to (de-)activate senders +and to change the access permissions senders at runtime. The "si" +(server info) command is used to list the streaming options of the +currently running server as well as the various sender access lists. + +-> Show client/target/access lists: + + para_client si + +-> Obtain general help for the sender command: + + para_client help sender + +-> Get help for a specific sender (contains further examples): + + s=http # or dccp or udp + para_client sender $s help + +By default para_server activates both the HTTP and th DCCP sender on +startup. This can be changed via command line options or para_server's +config file. + +-> List config file options for senders: + + para_server -h + +All senders share the "on" and "off" commands, so senders may be +activated and deactivated independently of each other. + +-> Switch off the http sender: + + para_client sender http off + +-> Receive a DCCP stream using CCID2 and write the output into a file: + + host=foo.org; ccid=2; filename=bar + para_recv --receiver "dccp --host $host --ccid $ccid" > $filename + +Note the quotes around the arguments for the dccp receiver. Each +receiver has its own set of command line options and its own command +line parser, so arguments for the dccp receiver must be protected +from being interpreted by para_recv. + +-> Start UDP multicast, using the default multicast address: + + para_client sender udp add 224.0.1.38 + +-> Receive FEC-encoded multicast stream and write the output into a file: + + filename=foo + para_recv -r udp > $filename + +-> Add an UDP unicast for a client to the target list of the UDP sender: + + t=client.foo.org + para_client sender udp add $t + +-> Receive this (FEC-encoded) unicast stream: + + filename=foo + para_recv -r 'udp -i 0.0.0.0' > $filename + +-> Create a minimal config for para_audiod for HTTP streams: + + c=$HOME/.paraslash/audiod.conf.min; s=server.foo.com + formats="mp3 ogg aac wma" # remove what you do not have + for f in $formats; do echo receiver \"$f:http -i $s\"; done > $c + para_audiod --config $c + +------- +Filters +------- + +A paraslash filter is a module which transforms an input stream into +an output stream. Filters are included in the para_audiod executable +and in the stand-alone tool para_filter which usually contains the +same modules. + +While para_filter reads its input stream from STDIN and writes +the output to STDOUT, the filter modules of para_audiod are always +connected to a receiver which produces the input stream and a writer +which absorbs the output stream. + +Some filters depend on a specific library being installed and are +not compiled in if this library was not found at compile time. To +see the list of supported filters, run para_filter and para_audiod +with the --help option. The output looks similar to the following: + + Available filters: + compress wav amp fecdec wmadec prebuffer oggdec aacdec mp3dec + +Out of these filter modules, a chain of filters can be constructed, +much in the way Unix pipes can be chained, and analogous to the use +of modules in gstreamer: The output of the first filter becomes the +input of the second filter. There is no limitation on the number of +filters and the same filter may occur more than once. + +Like receivers, each filter has its own command line options which +must be quoted to protect them from the command line options of +the driving application (para_audiod or para_filter). Example: + + para_filter -f 'mp3dec --ignore-crc' -f 'compress --damp 1' + +For para_audiod, each audio format has its own set of filters. The +name of the audio format for which the filter should be applied is +used as the prefix for the filter option. Example: + + para_audiod -f 'mp3:prebuffer --duration 300' + +Decoders +~~~~~~~~ + +For each supported audio format there is a corresponding filter +which decodes audio data in this format to 16 bit PCM data which +can be directly sent to the sound device or any other software that +operates on undecoded PCM data (visualizers, equalizers etc.). Such +filters are called _decoders_ in general, and xxxdec is the name of +the paraslash decoder for the audio format xxx. For example, the mp3 +decoder filter is called mp3dec. + +Note that the output of the decoder is about 10 times larger than +its input. This means that filters that operate on the decoded audio +stream have to deal with much more data than filters that transform +the audio stream before it is fed to the decoder. + +Paraslash relies on external libraries for most decoders, so these +libraries must be installed for the decoder to be included in the +para_filter and para_audiod executables. The oggdec filter depends +on the libogg and libvorbis libraries for example. + +Forward error correction +~~~~~~~~~~~~~~~~~~~~~~~~ + +As already mentioned REFERENCE(Streaming protocols, earlier), +paraslash uses forward error correction (FEC) for the unreliable +UDP transport. FEC is a technique which was invented already in +1960 by Reed and Solomon and which is widely used for the parity +calculations of storage devices (RAID arrays). It is based on the +algebraic concept of finite fields, today called Galois fields, in +honour of the mathematician Galois (1811-1832). The FEC implementation +of paraslash is based on code by Luigi Rizzo. + +Although the details require a sound knowledge of the underlying +mathematics, the basic idea is not hard to understand: For positive +integers k and n with k < n it is possible to compute for any k given +data bytes d_1, ..., d_k the corresponding r := n -k parity bytes p_1, +..., p_r such that all data bytes can be reconstructed from *any* +k bytes of the set + + {d_1, ..., d_k, p_1, ..., p_r}. + +FEC-encoding for unreliable network transports boils down to slicing +the audio stream into groups of k suitably sized pieces called _slices_ +and computing the r corresponding parity slices. This step is performed +in para_server which then sends both the data and the parity slices +over the unreliable network connection. If the client was able +to receive at least k of the n = k + r slices, it can reconstruct +(FEC-decode) the original audio stream. + +From these observations it is clear that there are three different +FEC parameters: The slice size, the number of data slices k, and the +total number of slices n. It is crucial to choose the slice size +such that no fragmentation of network packets takes place because +FEC only guards against losses and reodering but fails if slices are +received partially. + +FEC decoding in paralash is performed through the fecdec filter which +usually is the first filter (there can be other filters before fecdec +if these do not alter the audio stream). + + +Volume adjustment (amp and compress) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The amp and the compress filter both adjust the volume of the audio +stream. These filters operate on uncompressed audio samples. Hence +they are usually placed directly after the decoding filter. Each +sample is multiplied with a scaling factor (>= 1) which makes amp +and compress quite expensive in terms of computing power. + +*amp* + +The amp filter amplifies the audio stream by a fixed scaling factor +that must be known in advance. For para_audiod this factor is derived +from the amplification field of the audio file's entry in the audio +file table while para_filter uses the value given at the command line. + +The optimal scaling factor F for an audio file is the largest real +number F >= 1 such that after multiplication with F all samples still +fit into the sample interval [-32768, 32767]. One can use para_filter +in combination with the sox utility to compute F: + + para_filter -f mp3dec -f wav < file.mp3 | sox -t wav - -e stat -v + +The amplification value V which is stored in the audio file table, +however, is an integer between 0 and 255 which is connected to F +through the formula + + V = (F - 1) * 64. + +To store V in the audio file table, the command + + para_client -- touch -a=V file.mp3 + +is used. The reader is encouraged to write a script that performs +these computations :) + +*compress* + +Unlike the amplification filter, the compress filter adjusts the volume +of the audio stream dynamically without prior knowledge about the peak +value. It maintains the maximal volume of the last n samples of the +audio stream and computes a suitable amplification factor based on that +value and the various configuration options. It tries to chose this +factor such that the adjusted volume meets the desired target level. + +Note that it makes sense to combine amp and compress. + +Misc filters (wav and prebuffer) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These filters are rather simple and do not modify the audio stream at +all. The wav filter is only useful with para_filter and in connection +with a decoder. It asks the decoder for the number of channels and the +sample rate of the stream and adds a Microsoft wave header containing +this information at the beginning. This allows to write wav files +rather than raw PCM files (which do not contain any information about +the number of channels and the sample rate). + +The prebuffer filter simply delays the output until the given time has +passed (starting from the time the first byte was available in its +input queue) or until the given amount of data has accumulated. It +is mainly useful for para_audiod if the standard parameters result +in buffer underruns. + +Both filters require almost no additional computing time, even when +operating on uncompressed audio streams, since data buffers are simply +"pushed down" rather than copied. + +Examples +~~~~~~~~ + +-> Decode an mp3 file to wav format: + + para_filter -f mp3dec -f wav < file.mp3 > file.wav + +-> Amplify a raw audio file by a factor of 1.5: + + para_filter -f amp --amp 32 < foo.raw > bar.raw + +------ +Output +------ + +Once an audio stream has been received and decoded to PCM format, +it can be sent to a sound device for playback. This part is performed +by paraslash _writers_ which are described in this chapter. + +Writers +~~~~~~~ + +A paraslash writer acts as a data sink that consumes but does not +produce audio data. Paraslash writers operate on the client side and +are contained in para_audiod and in the stand-alone tool para_write. + +The para_write program reads uncompressed audio data from STDIN. If +this data starts with a wav header, sample rate, sample format and +channel count are read from the header. Otherwise CD audio (44.1KHz +16 bit little endian, stereo) is assumed but this can be overridden +by command line options. para_audiod, on the other hand, obtains +the sample rate and the number of channels from the decoder. + +Like receivers and filters, each writer has an individual set of +command line options, and for para_audiod writers can be configured +per audio format separately. It is possible to activate more than +one writer for the same stream simultaneously. + +OS-dependent APIs +~~~~~~~~~~~~~~~~~ + +Unfortunately, the various flavours of Unix on which paraslash +runs on have different APIs for opening a sound device and starting +playback. Hence for each such API there is a paraslash writer that +can play the audio stream via this API. + +*ALSA*. The _Advanced Linux Sound Architecture_ is only available on +Linux systems. Although there are several mid-layer APIs in use by +the various Linux distributions (ESD, Jack, PulseAudio), paraslash +currently supports only the low-level ALSA API which is not supposed +to be change. ALSA is very feature-rich, in particular it supports +software mixing via its DMIX plugin. ALSA is the default writer on +Linux systems. + +*OSS*. The _Open Sound System_ is the only API on *BSD Unixes and +is also available on Linux systems, usually provided by ALSA as an +emulation for backwards compatibility. This API is rather simple but +also limited. For example only one application can open the device +at any time. The OSS writer is activated by default on BSD Systems. + +*OSX*. Mac OS X has yet another API called CoreAudio. The OSX writer +for this API is only compiled in on such systems and is of course +the default there. + +*FILE*. The file writer allows to capture the audio stream and +write the PCM data to a file on the file system rather than playing +it through a sound device. It is supported on all platforms and is +always compiled in. + +Examples +~~~~~~~~ + +-> Use the OSS writer to play a wav file: + + para_write --writer oss < file.wav + +-> Enable ALSA software mixing for mp3 streams + + para_audiod --writer 'mp3:alsa -d plug:swmix' + + +--- +Gui +--- + +para_gui executes an arbitrary command which is supposed to print +status information to STDOUT. It then displays this information in +a curses window. By default the command + + para_audioc -- stat -p + +is executed, but this can be customized via the --stat_cmd option. In +particular it possible to use + + para_client -- stat -p + +to make para_gui work on systems on which para_audiod is not running. + +Key bindings +~~~~~~~~~~~~ + +It is possible to bind keys to arbitrary commands via custom +key-bindings. Besides the internal keys which can not be changed (help, +quit, loglevel, version...), the following flavours of key-bindings +are supported: + + - external: Shutdown curses before launching the given command. + Useful for starting other ncurses programs from within + para_gui, e.g. aumix or dialog scripts. Or, use the mbox + output format to write a mailbox containing one mail for each + (admissible) file the audio file selector knows about. Then + start mutt from within para_gui to browse your collection! + + - display: Launch the command and display its stdout in + para_gui's bottom window. + + - para: Like display, but start "para_client " instead of "". + +The general form of a key binding is + + key_map k:m:c + +which maps key k to command c using mode m. Mode may be x, d or p +for external, display and paraslash commands, respectively. + +Themes +~~~~~~ + +Currently there are only two themes for para_gui. It is easy, however, +to add more themes. To create a new theme one has to define the +position, color and geometry for for each status item that should be +shown by this theme. See gui_theme.c for examples. + +The "." and "," keys are used to switch between themes. + +Examples +~~~~~~~~ + +-> Show server info: + + key_map "i:p:si" + +-> Jump to the middle of the current audio file by pressing F5: + + key_map ":p:jmp 50" + +-> vi-like bindings for jumping around: + + key_map "l:p:ff 10" + key_map "h:p:ff 10-" + key_map "w:p:ff 60" + key_map "b:p:ff 60-" + +-> Print the current date and time: + + key_map "D:d:date" + +-> Call other curses programs: + + key_map "U:x:aumix" + key_map "!:x:/bin/bash" + key_map "^E:x:/bin/sh -c 'vi ~/.paraslash/gui.conf'" + +----------- +Development +----------- + +Tools +~~~~~ + +In order to compile the sources from the git repository (rather than +from tar balls) and for contributing non-trivial changes to the +paraslash project, some additional tools should be installed on a +developer machine. + +http://git.or.cz/ (git). As described in more detail REFERENCE(Git +branches, below), the git source code management tool is used for +paraslash development. It is necessary for cloning the git repository +and for getting updates. + +ftp://ftp.gnu.org/pub/gnu/gengetopt/ (gengetopt). The C code for +the command line parsers of all paraslash executables is generated +by gengetopt. The generated C files are shipped in the tarballs but +are not contained in the git repository. + +ftp://ftp.gnu.org/pub/gnu/m4/ (m4). Some input files for gengetopt +are generated from templates by the m4 macro processor. + +ftp://ftp.gnu.org/pub/gnu/autoconf/ (autoconf) GNU autoconf creates +the configure file which is shipped in the tarballs but has to be +generated when compiling from git. + +http://www.triptico.com/software/grutatxt.html (grutatxt). The +HTML version of this manual and some of the paraslash web pages are +generated by the grutatxt plain text to HTML converter. If changes +are made to these text files the grutatxt package must be installed +to regenerate the HTML files. + +http://www.stack.nl/~dimitri/doxygen/ (doxygen). The documentation +of paraslash's C sources uses the doxygen documentation system. The +conventions for documenting the source code is described in the +REFERENCE(Doxygen, Doxygen section). + +ftp://ftp.gnu.org/pub/gnu/global (global). This is used to generate +browsable HTML from the C sources. It is needed by doxygen. + +Git branches +~~~~~~~~~~~~ + +Paraslash has been developed using the git source code management +tool since 2006. Development is organized roughly in the same spirit +as the git development itself, as described below. + +The following text passage is based on "A note from the maintainer", +written by Junio C Hamano, the maintainer of git. + +There are four branches in the paraslash repository that track the +source tree: "master", "maint", "next", and "pu". + +The "master" branch is meant to contain what is well tested and +ready to be used in a production setting. There could occasionally be +minor breakages or brown paper bag bugs but they are not expected to +be anything major, and more importantly quickly and easily fixable. +Every now and then, a "feature release" is cut from the tip of this +branch, named with three dotted decimal digits, like 0.4.2. + +Whenever changes are about to be included that will eventually lead to +a new major release (e.g. 0.5.0), a "maint" branch is forked off from +"master" at that point. Obvious, safe and urgent fixes after the major +release are applied to this branch and maintenance releases are cut +from it. New features never go to this branch. This branch is also +merged into "master" to propagate the fixes forward. + +A trivial and safe enhancement goes directly on top of "master". +New development does not usually happen on "master", however. +Instead, a separate topic branch is forked from the tip of "master", +and it first is tested in isolation; Usually there are a handful such +topic branches that are running ahead of "master". The tip of these +branches is not published in the public repository to keep the number +of branches that downstream developers need to worry about low. + +The quality of topic branches varies widely. Some of them start out as +"good idea but obviously is broken in some areas" and then with some +more work become "more or less done and can now be tested by wider +audience". Luckily, most of them start out in the latter, better shape. + +The "next" branch is to merge and test topic branches in the latter +category. In general, this branch always contains the tip of "master". +It might not be quite rock-solid production ready, but is expected to +work more or less without major breakage. The maintainer usually uses +the "next" version of paraslash for his own pleasure, so it cannot +be _that_ broken. The "next" branch is where new and exciting things +take place. + +The two branches "master" and "maint" are never rewound, and "next" +usually will not be either (this automatically means the topics that +have been merged into "next" are usually not rebased, and you can find +the tip of topic branches you are interested in from the output of +"git log next"). You should be able to safely build on top of them. + +However, at times "next" will be rebuilt from the tip of "master" to +get rid of merge commits that will never be in "master. The commit +that replaces "next" will usually have the identical tree, but it +will have different ancestry from the tip of "master". + +The "pu" (proposed updates) branch bundles the remainder of the +topic branches. The "pu" branch, and topic branches that are only in +"pu", are subject to rebasing in general. By the above definition +of how "next" works, you can tell that this branch will contain quite +experimental and obviously broken stuff. + +When a topic that was in "pu" proves to be in testable shape, it +graduates to "next". This is done with + + git checkout next + git merge that-topic-branch + +Sometimes, an idea that looked promising turns out to be not so good +and the topic can be dropped from "pu" in such a case. + +A topic that is in "next" is expected to be polished to perfection +before it is merged to "master". Similar to the above, this is +done with + + git checkout master + git merge that-topic-branch + git branch -d that-topic-branch + +Note that being in "next" is not a guarantee to appear in the next +release (being in "master" is such a guarantee, unless it is later +found seriously broken and reverted), nor even in any future release. + +Coding Style +~~~~~~~~~~~~ + +The preferred coding style for paraslash coincides more or less +with the style of the Linux kernel. So rather than repeating what is +written XREFERENCE(http://www.kernel.org/doc/Documentation/CodingStyle, +there), here are the most important points. + + - Burn the GNU coding standards. + - Never use spaces for indentation. + - Tabs are 8 characters, and thus indentations are also 8 characters. + - Don't put multiple assignments on a single line. + - Avoid tricky expressions. + - Don't leave whitespace at the end of lines. + - The limit on the length of lines is 80 columns. + - Use K&R style for placing braces and spaces: + + if (x is true) { + we do y + } + + - Use a space after (most) keywords. + - Do not add spaces around (inside) parenthesized expressions. + - Use one space around (on each side of) most binary and ternary operators. + - Do not use cute names like ThisVariableIsATemporaryCounter, call it tmp. + - Mixed-case names are frowned upon. + - Descriptive names for global variables are a must. + - Avoid typedefs. + - Functions should be short and sweet, and do just one thing. + - The number of local variables shouldn't exceed 10. + - Gotos are fine if they improve readability and reduce nesting. + - Don't use C99-style "// ..." comments. + - Names of macros defining constants and labels in enums are capitalized. + - Enums are preferred when defining several related constants. + - Always use the paraslash wrappers for allocating memory. + - If the name of a function is an action or an imperative. + command, the function should return an error-code integer + (<0 means error, >=0 means success). If the name is a + predicate, the function should return a "succeeded" boolean. + + +Doxygen +~~~~~~~ + +Doxygen is a documentation system for various programming +languages. The paraslash project uses Doxygen for generating the API +reference on the web pages, but good source code documentation is +also beneficial to people trying to understand the code structure +and the interactions between the various source files. + +It is more illustrative to look at the source code for examples than +to describe the conventions for documenting the source in this manual, +so we only describe which parts of the code need doxygen comments, +but leave out details on documentation conventions. + +As a rule, only the public part of the C source is documented with +Doxygen. This includes structures, defines and enumerations in header +files as well as public (non-static) C functions. These should be +documented completely. For example each parameter and the return +value of a public function should get a descriptive comment. + +No doxygen comments are necessary for static functions and for +structures and enumerations in C files (which are used only within +this file). This does not mean, however, that those entities need +no documentation at all. Instead, common sense should be applied to +document what is not obvious from reading the code. + +-------- +Appendix +-------- + +Network protocols +~~~~~~~~~~~~~~~~~ + +*IP*. The _Internet Protocol_ is the primary networking protocol +used for the Internet. All protocols described below use IP as the +underlying layer. Both the prevalent IPv4 and the next-generation +IPv6 variant are being deployed actively worldwide. + +*Connection-oriented and connectionless protocols*. Connectionless +protocols differ from connection-oriented ones in that state +associated with the sending/receiving endpoints is treated +implicitly. Connectionless protocols maintain no internal knowledge +about the state of the connection. Hence they are not capable of +reacting to state changes, such as sudden loss or congestion on the +connection medium. Connection-oriented protocols, in contrast, make +this knowledge explicit. The connection is established only after +a bidirectional handshake which requires both endpoints to agree +on the state of the connection, and may also involve negotiating +specific parameters for the particular connection. Maintaining an +up-to-date internal state of the connection also in general means +that the sending endpoints perform congestion control, adapting to +qualitative changes of the connection medium. + +*Reliability*. In IP networking, packets can be lost, duplicated, +or delivered out of order, and different network protocols handle +these problems in different ways. We call a transport-layer protocol +_reliable_, if it turns the unreliable IP delivery into an ordered, +duplicate- and loss-free delivery of packets. Sequence numbers +are used to discard duplicates and re-arrange packets delivered +out-of-order. Retransmission is used to guarantee loss-free +delivery. Unreliable protocols, in contrast, do not guarantee ordering +or data integrity. + +*Classification*. With these definitions the protocols which are used +by paraslash for steaming audio data may be classified as follows. + + - HTTP/TCP: connection-oriented, reliable, + - UDP: connectionless, unreliable, + - DCCP: connection-oriented, unreliable. + +Below we give a short descriptions of these protocols. + +*TCP*. The _Transmission Control Protocol_ provides reliable, +ordered delivery of a stream and a classic window-based congestion +control. In contrast to UDP and DCCP (see below), TCP does not have +record-oriented or datagram-based syntax, i.e. it provides a stream +which is unaware and independent of any record (packet) boundaries. +TCP is used extensively by many application layers. Besides HTTP (the +Hypertext Transfer Protocol), also FTP (the File Transfer protocol), +SMTP (Simple Mail Transfer Protocol), SSH (Secure Shell) all sit on +top of TCP. + +*UDP*. The _User Datagram Protocol_ is the simplest transport-layer +protocol, built as a thin layer directly on top of IP. For this reason, +it offers the same best-effort service as IP itself, i.e. there is no +detection of duplicate or reordered packets. Being a connectionless +protocol, only minimal internal state about the connection is +maintained, which means that there is no protection against packet +loss or network congestion. Error checking and correction (if at all) +are performed in the application.' + +*DCCP*. The _Datagram Congestion Control Protocol_ combines the +connection-oriented state maintenance known from TCP with the +unreliable, datagram-based transport of UDP. This means that it +is capable of reacting to changes in the connection by performing +congestion control, offering multiple alternative approaches. But it +is bound to datagram boundaries (the maximum packet size supported +by a medium), and like UDP it lacks retransmission to protect +against loss. Due to the use of sequence numbers, it is however +able to react to loss (interpreted as a congestion indication) and +to ignore out-of-order and duplicate packets. Unlike TCP it allows +to negotiate specific, binding features for a connection, such as +the choice of congestion control: classic, window-based congestion +control known from TCP is available as CCID-2, rate-based, "smooth" +congestion control is offered as CCID-3. + +*HTTP*. _The Hypertext Transfer Protocol_ is an application layer +protocol on top of TCP. It is spoken by web servers and is most often +used for web services. However, as can be seen by the many Internet +radio stations and YouTube/Flash videos, http is by far not limited to +the delivery of web pages only. Being a simple request/response based +protocol, the semantics of the protocol also allow the delivery of +multimedia content, such as audio over http. + +*Multicast*. IP multicast is not really a protocol but a technique +for one-to-many communication over an IP network. The challenge is to +deliver information to a group of destinations simultaneously using +the most efficient strategy to send the messages over each link of +the network only once. This has benefits for streaming multimedia: +the standard one-to-one unicast offered by TCP/DCCP means that +n clients listening to the same stream also consume n-times the +resources, whereas multicast requires to send the stream just once, +irrespective of the number of receivers. Since it would be costly to +maintain state for each listening receiver, multicast often implies +connectionless transport, which is the reason that it is currently +only available via UDP. + +License +~~~~~~~ + +Paraslash is licensed under the GPL, version 2. Most of the code +base has been written from scratch, and those parts are GPL V2 +throughout. Notable exceptions are FEC and the WMA decoder. See the +corresponding source files for licencing details for these parts. Some +code sniplets of several other third party software packages have +been incorporated into the paraslash sources, for example log message +coloring was taken from the git sources. These third party software +packages are all published under the GPL or some other license +compatible to the GPL. + +Acknowledgements +~~~~~~~~~~~~~~~~ + +Many thanks to Gerrit Renker who read an early draft of this manual +and contributed significant improvements. + +---------- +References +---------- + +Articles +~~~~~~~~ + - Reed, Irving S.; Solomon, Gustave (1960), + XREFERENCE(http://kom.aau.dk/~heb/kurser/NOTER/KOFA01.PDF, + Polynomial Codes over Certain Finite Fields), Journal of the + Society for Industrial and Applied Mathematics (SIAM) 8 (2): + 300-304, doi:10.1137/0108018) + +RFCs +~~~~ + + - XREFERENCE(http://www.ietf.org/rfc/rfc768.txt, RFC 768) (1980): + User Datagram Protocol + - XREFERENCE(http://www.ietf.org/rfc/rfc791.txt, RFC 791) (1981): + Internet Protocol + - XREFERENCE(http://www.ietf.org/rfc/rfc2437.txt, RFC 2437) (1998): + RSA Cryptography Specifications + - XREFERENCE(http://www.ietf.org/rfc/rfc4340.txt, RFC 4340) + (2006): Datagram Congestion Control Protocol (DCCP) + - XREFERENCE(http://www.ietf.org/rfc/rfc4341.txt, RFC 4341) (2006): + Congestion Control ID 2: TCP-like Congestion Control + - XREFERENCE(http://www.ietf.org/rfc/rfc4342.txt, RFC 4342) (2006): + Congestion Control ID 3: TCP-Friendly Rate Control (TFRC) + +Application web pages +~~~~~~~~~~~~~~~~~~~~~ + + - XREFERENCE(http://paraslash.systemlinux.org/, paraslash) + - XREFERENCE(http://xmms2.org/wiki/Main_Page, xmms) + - XREFERENCE(http://www.mpg123.de/, mpg123) + - XREFERENCE(http://gstreamer.freedesktop.org/, gstreamer) + - XREFERENCE(http://www.icecast.org/, icecast) + - XREFERENCE(http://beesbuzz.biz/code/audiocompress.php, Audio Compress) + +External documentation +~~~~~~~~~~~~~~~~~~~~~~ + + - XREFERENCE(http://kernel.org/pub/linux/kernel/people/hpa/raid6.pdf, + H. Peter Anvin: The mathematics of Raid6) + - XREFERENCE(http://info.iet.unipi.it/~luigi/fec_ccr.ps.gz, + Luigi Rizzo: Effective Erasure Codes for reliable Computer + Communication Protocols) + +Code +~~~~ + - XREFERENCE(http://info.iet.unipi.it/~luigi/vdm.tar.gz, + Original FEC implementation by Luigi Rizzo) + diff --git a/web/para.css b/web/para.css index 157cce96..f103abd3 100644 --- a/web/para.css +++ b/web/para.css @@ -93,8 +93,8 @@ A.qindexHL:visited { A.el { text-decoration: none; font-weight: bold } A.elRef { font-weight: bold } -A.code:link { text-decoration: none; font-weight: normal; color: ##BA3708} -A.code:visited { text-decoration: none; font-weight: normal; color: ##BA3708} +A.code:link { text-decoration: none; font-weight: normal; color: #BA3708} +A.code:visited { text-decoration: none; font-weight: normal; color: #BA3708} A.codeRef:link { font-weight: normal; color: #BA3708} A.codeRef:visited { font-weight: normal; color: #BA3708} A:hover { text-decoration: none; background-color: #ffff00 } diff --git a/web/screenshots/audiod.log b/web/screenshots/audiod.log new file mode 100644 index 00000000..c9e43663 --- /dev/null +++ b/web/screenshots/audiod.log @@ -0,0 +1,549 @@ +Dec 08 19:23:27 meins 2 log_welcome: welcome to para_audiod git (Sat Dec 8 13:42:52 MET 2007) +Dec 08 19:23:27 meins 2 init_writers: maximal number of writers: 3 +Dec 08 19:23:27 meins 2 check_writer_arg: checking alsa -d plug:swmix +Dec 08 19:23:27 meins 2 alsa_parse_config: options: -d plug:swmix, 2 +Dec 08 19:23:27 meins 2 alsa_parse_config: help given: 0 +Dec 08 19:23:27 meins 2 init_writers: mp3 writer #0: alsa +Dec 08 19:23:27 meins 2 check_writer_arg: checking alsa -d plug:swmix +Dec 08 19:23:27 meins 2 alsa_parse_config: options: -d plug:swmix, 2 +Dec 08 19:23:27 meins 2 alsa_parse_config: help given: 0 +Dec 08 19:23:27 meins 2 init_writers: ogg writer #0: alsa +Dec 08 19:23:27 meins 2 check_writer_arg: checking alsa -d plug:swmix +Dec 08 19:23:27 meins 2 alsa_parse_config: options: -d plug:swmix, 2 +Dec 08 19:23:27 meins 2 alsa_parse_config: help given: 0 +Dec 08 19:23:27 meins 2 init_writers: aac writer #0: alsa +Dec 08 19:23:27 meins 2 init_receivers: initializing http receiver +Dec 08 19:23:27 meins 2 init_receivers: initializing dccp receiver +Dec 08 19:23:27 meins 2 init_receivers: initializing ortp receiver +Dec 08 19:23:27 meins 2 init_filters: maximal number of filters: 6 +Dec 08 19:23:27 meins 2 add_filter: mp3 filter 1: mp3dec +Dec 08 19:23:27 meins 2 add_filter: mp3 filter 2: compress +Dec 08 19:23:27 meins 2 add_filter: ogg filter 1: oggdec +Dec 08 19:23:27 meins 2 add_filter: ogg filter 2: compress +Dec 08 19:23:27 meins 2 add_filter: aac filter 1: aacdec +Dec 08 19:23:27 meins 2 add_filter: aac filter 2: compress +Dec 08 19:23:27 meins 2 clear_slot: clearing slot 0 +Dec 08 19:23:27 meins 2 clear_slot: clearing slot 1 +Dec 08 19:23:27 meins 2 clear_slot: clearing slot 2 +Dec 08 19:23:27 meins 2 clear_slot: clearing slot 3 +Dec 08 19:23:27 meins 2 clear_slot: clearing slot 4 +Dec 08 19:23:27 meins 2 init_grabbing: grab init +Dec 08 19:23:27 meins 2 setup_signal_handling: signal pipe: fd 4 +Dec 08 19:23:27 meins 3 audiod_get_socket: local socket: /var/paraslash/audiod_socket.meins +Dec 08 19:23:27 meins 2 daemon_init: daemonizing +Dec 08 19:23:27 meins 2 init_sched: initializing scheduler +Dec 08 19:23:27 meins 2 register_task: registering signal task (0x80631bc) +Dec 08 19:23:27 meins 2 register_task: registering command task (0xbfec3ec4) +Dec 08 19:23:27 meins 2 register_task: registering status task (0x80632e8) +Dec 08 19:23:27 meins 2 register_task: registering audiod task (0xbfec3fe8) +Dec 08 19:23:27 meins 2 status_pre_select: clock diff count: 5 +Dec 08 19:23:27 meins 2 client_open: loglevel: 5 +Dec 08 19:23:27 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf +Dec 08 19:23:27 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan +Dec 08 19:23:27 meins 3 client_open: connecting localhost:2990 +Dec 08 19:23:27 meins 2 register_task: registering client (0x8067878) +Dec 08 19:23:27 meins 2 client_post_select: --> auth rc4 maan +Dec 08 19:23:27 meins 2 client_post_select: <-- [challenge] +Dec 08 19:23:27 meins 2 client_post_select: --> 1499570993 +Dec 08 19:23:27 meins 2 client_post_select: decrypting session key +Dec 08 19:23:27 meins 2 enable_crypt: rc4 encryption activated for fd 7 +Dec 08 19:23:27 meins 2 unregister_task: unregistering client (0x8067878) +Dec 08 19:23:28 meins 2 handle_connect: connection from user 409, buf: stat +Dec 08 19:23:28 meins 2 handle_connect: argv[0]: stat, argc= 1 +Dec 08 19:23:28 meins 2 com_stat: mask: 0xffffffff +Dec 08 19:23:28 meins 2 stat_client_add: adding client on fd 7 +Dec 08 19:23:28 meins 2 dump_stat_client_list: stat client on fd 7 +Dec 08 19:23:28 meins 2 status_pre_select: clock diff count: 4 +Dec 08 19:23:28 meins 2 client_open: loglevel: 5 +Dec 08 19:23:28 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf +Dec 08 19:23:28 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan +Dec 08 19:23:28 meins 3 client_open: connecting localhost:2990 +Dec 08 19:23:28 meins 2 register_task: registering client (0x8067878) +Dec 08 19:23:28 meins 2 client_post_select: --> auth rc4 maan +Dec 08 19:23:28 meins 2 client_post_select: <-- [challenge] +Dec 08 19:23:28 meins 2 client_post_select: --> 1400991892 +Dec 08 19:23:28 meins 2 client_post_select: decrypting session key +Dec 08 19:23:28 meins 2 enable_crypt: rc4 encryption activated for fd 8 +Dec 08 19:23:28 meins 2 compute_time_diff: time diff (cur/avg): -1ms/+1ms +Dec 08 19:23:28 meins 2 unregister_task: unregistering client (0x8067878) +Dec 08 19:23:29 meins 2 status_pre_select: clock diff count: 3 +Dec 08 19:23:29 meins 2 client_open: loglevel: 5 +Dec 08 19:23:29 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf +Dec 08 19:23:29 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan +Dec 08 19:23:29 meins 3 client_open: connecting localhost:2990 +Dec 08 19:23:29 meins 2 register_task: registering client (0x8067878) +Dec 08 19:23:29 meins 2 client_post_select: --> auth rc4 maan +Dec 08 19:23:29 meins 2 client_post_select: <-- [challenge] +Dec 08 19:23:29 meins 2 client_post_select: --> 1772412478 +Dec 08 19:23:29 meins 2 client_post_select: decrypting session key +Dec 08 19:23:29 meins 2 enable_crypt: rc4 encryption activated for fd 8 +Dec 08 19:23:29 meins 2 compute_time_diff: time diff (cur/avg): -1ms/+1ms +Dec 08 19:23:29 meins 2 unregister_task: unregistering client (0x8067878) +Dec 08 19:23:30 meins 2 status_pre_select: clock diff count: 2 +Dec 08 19:23:30 meins 2 client_open: loglevel: 5 +Dec 08 19:23:30 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf +Dec 08 19:23:30 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan +Dec 08 19:23:30 meins 3 client_open: connecting localhost:2990 +Dec 08 19:23:30 meins 2 register_task: registering client (0x8067878) +Dec 08 19:23:30 meins 2 client_post_select: --> auth rc4 maan +Dec 08 19:23:30 meins 2 client_post_select: <-- [challenge] +Dec 08 19:23:30 meins 2 client_post_select: --> 1882631692 +Dec 08 19:23:30 meins 2 client_post_select: decrypting session key +Dec 08 19:23:30 meins 2 enable_crypt: rc4 encryption activated for fd 8 +Dec 08 19:23:30 meins 2 compute_time_diff: time diff (cur/avg): -1ms/+1ms +Dec 08 19:23:30 meins 2 unregister_task: unregistering client (0x8067878) +Dec 08 19:23:31 meins 2 status_pre_select: clock diff count: 1 +Dec 08 19:23:31 meins 2 client_open: loglevel: 5 +Dec 08 19:23:31 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf +Dec 08 19:23:31 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan +Dec 08 19:23:31 meins 3 client_open: connecting localhost:2990 +Dec 08 19:23:31 meins 2 register_task: registering client (0x8067878) +Dec 08 19:23:31 meins 2 client_post_select: --> auth rc4 maan +Dec 08 19:23:31 meins 2 client_post_select: <-- [challenge] +Dec 08 19:23:31 meins 2 client_post_select: --> 1630455651 +Dec 08 19:23:31 meins 2 client_post_select: decrypting session key +Dec 08 19:23:31 meins 2 enable_crypt: rc4 encryption activated for fd 8 +Dec 08 19:23:31 meins 2 compute_time_diff: time diff (cur/avg): -1ms/+1ms +Dec 08 19:23:31 meins 2 unregister_task: unregistering client (0x8067878) +Dec 08 19:23:36 meins 2 client_open: loglevel: 5 +Dec 08 19:23:36 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf +Dec 08 19:23:36 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan +Dec 08 19:23:36 meins 3 client_open: connecting localhost:2990 +Dec 08 19:23:36 meins 2 register_task: registering client (0x8067878) +Dec 08 19:23:36 meins 2 client_post_select: --> auth rc4 maan +Dec 08 19:23:36 meins 2 client_post_select: <-- [challenge] +Dec 08 19:23:36 meins 2 client_post_select: --> 759215314 +Dec 08 19:23:36 meins 2 client_post_select: decrypting session key +Dec 08 19:23:36 meins 2 enable_crypt: rc4 encryption activated for fd 8 +Dec 08 19:23:36 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms +Dec 08 19:23:36 meins 2 clear_slot: clearing slot 0 +Dec 08 19:23:36 meins 3 open_receiver: started ogg: dccp receiver in slot 0 +Dec 08 19:23:36 meins 2 register_task: registering dccp receiver node (0x806a324) +Dec 08 19:23:37 meins 2 open_filters: opening ogg filters +Dec 08 19:23:37 meins 3 open_filters: ogg filter 1/2 (oggdec) started in slot 0 +Dec 08 19:23:37 meins 3 open_filters: ogg filter 2/2 (compress) started in slot 0 +Dec 08 19:23:37 meins 2 register_task: registering filter chain (0x807460c) +Dec 08 19:23:37 meins 3 ogg_convert: input buffer: 17032, opening ov callbacks +Dec 08 19:23:37 meins 3 ogg_convert: 2 channels, 44100 Hz +Dec 08 19:23:37 meins 2 open_writers: opening ogg writers +Dec 08 19:23:37 meins 2 open_writers: samplerate: 44100 +Dec 08 19:23:37 meins 3 wng_open: opening wng 0x80a5660 with 1 writer(s) +Dec 08 19:23:37 meins 2 register_task: registering (0x80a5684) +Dec 08 19:23:37 meins 2 alsa_open: 2 channel(s), 44100Hz +Dec 08 19:23:37 meins 2 alsa_open: buffer time: 170658 +Dec 08 19:23:37 meins 2 alsa_open: buffer size: 7526, period_size: 940 +Dec 08 19:23:43 meins 3 rn_event_handler: dccp_recv: end of file +Dec 08 19:23:43 meins 2 unregister_task: unregistering dccp receiver node (0x806a324) +Dec 08 19:23:43 meins 2 unregister_task: unregistering client (0x8067878) +Dec 08 19:23:43 meins 2 kill_all_decoders: unregistering writer node group in slot 0 +Dec 08 19:23:43 meins 2 unregister_task: unregistering writer node group (0x80a5684) +Dec 08 19:23:43 meins 2 kill_all_decoders: unregistering filter chain in slot 0 +Dec 08 19:23:43 meins 2 unregister_task: unregistering filter chain (0x807460c) +Dec 08 19:23:43 meins 2 try_to_close_slot: closing slot 0 +Dec 08 19:23:43 meins 3 wng_close: closing wng with 1 writer(s) +Dec 08 19:23:43 meins 2 alsa_close: closing writer node 0x80a57b0 +Dec 08 19:23:43 meins 3 close_filters: closing filter chain 0x80745e0 +Dec 08 19:23:43 meins 2 close_filters: closing oggdec filter +Dec 08 19:23:43 meins 2 close_filters: closing compress filter +Dec 08 19:23:43 meins 3 close_receiver: closing ogg receiver in slot 0 (eof = 1) +Dec 08 19:23:43 meins 2 clear_slot: clearing slot 0 +Dec 08 19:23:48 meins 2 client_open: loglevel: 5 +Dec 08 19:23:48 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf +Dec 08 19:23:48 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan +Dec 08 19:23:48 meins 3 client_open: connecting localhost:2990 +Dec 08 19:23:48 meins 2 register_task: registering client (0x8067878) +Dec 08 19:23:48 meins 2 client_post_select: --> auth rc4 maan +Dec 08 19:23:48 meins 2 client_post_select: <-- [challenge] +Dec 08 19:23:48 meins 2 client_post_select: --> 960122352 +Dec 08 19:23:48 meins 2 client_post_select: decrypting session key +Dec 08 19:23:48 meins 2 enable_crypt: rc4 encryption activated for fd 8 +Dec 08 19:23:48 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms +Dec 08 19:23:58 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms +Dec 08 19:23:58 meins 2 clear_slot: clearing slot 0 +Dec 08 19:23:58 meins 3 open_receiver: started mp3: http receiver in slot 0 +Dec 08 19:23:58 meins 2 register_task: registering http receiver node (0x806a204) +Dec 08 19:23:58 meins 2 http_recv_post_select: sending http request +Dec 08 19:23:58 meins 2 http_recv_post_select: received ok msg, streaming +Dec 08 19:23:59 meins 2 open_filters: opening mp3 filters +Dec 08 19:23:59 meins 3 open_filters: mp3 filter 1/2 (mp3dec) started in slot 0 +Dec 08 19:23:59 meins 3 open_filters: mp3 filter 2/2 (compress) started in slot 0 +Dec 08 19:23:59 meins 2 register_task: registering filter chain (0x807260c) +Dec 08 19:23:59 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms +Dec 08 19:23:59 meins 2 audiod_pre_select: initial delay: 173 ms left +Dec 08 19:23:59 meins 2 audiod_pre_select: initial delay: 147 ms left +Dec 08 19:23:59 meins 2 audiod_pre_select: initial delay: 121 ms left +Dec 08 19:23:59 meins 2 audiod_pre_select: initial delay: 95 ms left +Dec 08 19:23:59 meins 2 audiod_pre_select: initial delay: 69 ms left +Dec 08 19:23:59 meins 2 audiod_pre_select: initial delay: 43 ms left +Dec 08 19:23:59 meins 2 audiod_pre_select: initial delay: 17 ms left +Dec 08 19:23:59 meins 2 open_writers: opening mp3 writers +Dec 08 19:23:59 meins 2 open_writers: samplerate: 44100 +Dec 08 19:23:59 meins 3 wng_open: opening wng 0x80a3c18 with 1 writer(s) +Dec 08 19:23:59 meins 2 register_task: registering (0x80a3c3c) +Dec 08 19:23:59 meins 2 alsa_open: 2 channel(s), 44100Hz +Dec 08 19:23:59 meins 2 alsa_open: buffer time: 170658 +Dec 08 19:23:59 meins 2 alsa_open: buffer size: 7526, period_size: 940 +Dec 08 19:24:43 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms +Dec 08 19:24:57 meins 4 alsa_write_post_select: EAGAIN +Dec 08 19:24:57 meins 4 alsa_write_post_select: EAGAIN +Dec 08 19:25:33 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms +Dec 08 19:25:43 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms +Dec 08 19:25:51 meins 3 rn_event_handler: http_recv: end of file +Dec 08 19:25:51 meins 2 unregister_task: unregistering http receiver node (0x806a204) +Dec 08 19:25:51 meins 2 unregister_task: unregistering client (0x8067878) +Dec 08 19:25:51 meins 2 kill_all_decoders: unregistering writer node group in slot 0 +Dec 08 19:25:51 meins 2 unregister_task: unregistering writer node group (0x80a3c3c) +Dec 08 19:25:51 meins 2 kill_all_decoders: unregistering filter chain in slot 0 +Dec 08 19:25:51 meins 2 unregister_task: unregistering filter chain (0x807260c) +Dec 08 19:25:51 meins 2 try_to_close_slot: closing slot 0 +Dec 08 19:25:51 meins 3 wng_close: closing wng with 1 writer(s) +Dec 08 19:25:51 meins 2 alsa_close: closing writer node 0x806a440 +Dec 08 19:25:51 meins 3 close_filters: closing filter chain 0x80725e0 +Dec 08 19:25:51 meins 2 close_filters: closing mp3dec filter +Dec 08 19:25:51 meins 2 close_filters: closing compress filter +Dec 08 19:25:51 meins 3 close_receiver: closing mp3 receiver in slot 0 (eof = 1) +Dec 08 19:25:51 meins 2 clear_slot: clearing slot 0 +Dec 08 19:25:56 meins 2 client_open: loglevel: 5 +Dec 08 19:25:56 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf +Dec 08 19:25:56 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan +Dec 08 19:25:56 meins 3 client_open: connecting localhost:2990 +Dec 08 19:25:56 meins 2 register_task: registering client (0x8067878) +Dec 08 19:25:56 meins 2 client_post_select: --> auth rc4 maan +Dec 08 19:25:56 meins 2 client_post_select: <-- [challenge] +Dec 08 19:25:56 meins 2 client_post_select: --> 563263924 +Dec 08 19:25:56 meins 2 client_post_select: decrypting session key +Dec 08 19:25:56 meins 2 enable_crypt: rc4 encryption activated for fd 8 +Dec 08 19:25:56 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms +Dec 08 19:26:06 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms +Dec 08 19:26:06 meins 2 clear_slot: clearing slot 0 +Dec 08 19:26:06 meins 3 open_receiver: started mp3: http receiver in slot 0 +Dec 08 19:26:06 meins 2 register_task: registering http receiver node (0x806a29c) +Dec 08 19:26:06 meins 2 http_recv_post_select: sending http request +Dec 08 19:26:06 meins 2 http_recv_post_select: received ok msg, streaming +Dec 08 19:26:06 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms +Dec 08 19:26:06 meins 2 open_filters: opening mp3 filters +Dec 08 19:26:06 meins 3 open_filters: mp3 filter 1/2 (mp3dec) started in slot 0 +Dec 08 19:26:06 meins 3 open_filters: mp3 filter 2/2 (compress) started in slot 0 +Dec 08 19:26:06 meins 2 register_task: registering filter chain (0x80726dc) +Dec 08 19:26:06 meins 2 audiod_pre_select: initial delay: 173 ms left +Dec 08 19:26:06 meins 2 audiod_pre_select: initial delay: 147 ms left +Dec 08 19:26:06 meins 2 audiod_pre_select: initial delay: 121 ms left +Dec 08 19:26:06 meins 2 audiod_pre_select: initial delay: 95 ms left +Dec 08 19:26:06 meins 2 audiod_pre_select: initial delay: 69 ms left +Dec 08 19:26:06 meins 2 audiod_pre_select: initial delay: 43 ms left +Dec 08 19:26:07 meins 2 audiod_pre_select: initial delay: 17 ms left +Dec 08 19:26:07 meins 2 open_writers: opening mp3 writers +Dec 08 19:26:07 meins 2 open_writers: samplerate: 44100 +Dec 08 19:26:07 meins 3 wng_open: opening wng 0x80a3cb8 with 1 writer(s) +Dec 08 19:26:07 meins 2 register_task: registering (0x80a3cdc) +Dec 08 19:26:07 meins 2 alsa_open: 2 channel(s), 44100Hz +Dec 08 19:26:07 meins 2 alsa_open: buffer time: 170658 +Dec 08 19:26:07 meins 2 alsa_open: buffer size: 7526, period_size: 940 +Dec 08 19:26:51 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms +Dec 08 19:26:58 meins 2 unregister_task: unregistering client (0x8067878) +Dec 08 19:26:58 meins 2 kill_all_decoders: unregistering writer node group in slot 0 +Dec 08 19:26:58 meins 2 unregister_task: unregistering writer node group (0x80a3cdc) +Dec 08 19:26:58 meins 2 kill_all_decoders: unregistering filter chain in slot 0 +Dec 08 19:26:58 meins 2 unregister_task: unregistering filter chain (0x80726dc) +Dec 08 19:26:58 meins 2 kill_all_decoders: unregistering receiver_node in slot 0 +Dec 08 19:26:58 meins 2 unregister_task: unregistering http receiver node (0x806a29c) +Dec 08 19:26:58 meins 2 try_to_close_slot: closing slot 0 +Dec 08 19:26:58 meins 3 wng_close: closing wng with 1 writer(s) +Dec 08 19:26:58 meins 2 alsa_close: closing writer node 0x80a3e08 +Dec 08 19:26:58 meins 3 close_filters: closing filter chain 0x80726b0 +Dec 08 19:26:58 meins 2 close_filters: closing mp3dec filter +Dec 08 19:26:58 meins 2 close_filters: closing compress filter +Dec 08 19:26:58 meins 3 close_receiver: closing mp3 receiver in slot 0 (eof = 1) +Dec 08 19:26:58 meins 2 clear_slot: clearing slot 0 +Dec 08 19:27:04 meins 2 client_open: loglevel: 5 +Dec 08 19:27:04 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf +Dec 08 19:27:04 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan +Dec 08 19:27:04 meins 3 client_open: connecting localhost:2990 +Dec 08 19:27:04 meins 5 makesock: can not create TCP socket localhost#2990. +Dec 08 19:27:04 meins 5 client_open: Connection refused +Dec 08 19:27:09 meins 2 client_open: loglevel: 5 +Dec 08 19:27:09 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf +Dec 08 19:27:09 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan +Dec 08 19:27:09 meins 3 client_open: connecting localhost:2990 +Dec 08 19:27:09 meins 5 makesock: can not create TCP socket localhost#2990. +Dec 08 19:27:09 meins 5 client_open: Connection refused +Dec 08 19:27:14 meins 2 client_open: loglevel: 5 +Dec 08 19:27:14 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf +Dec 08 19:27:14 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan +Dec 08 19:27:14 meins 3 client_open: connecting localhost:2990 +Dec 08 19:27:14 meins 5 makesock: can not create TCP socket localhost#2990. +Dec 08 19:27:14 meins 5 client_open: Connection refused +Dec 08 19:27:19 meins 2 client_open: loglevel: 5 +Dec 08 19:27:19 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf +Dec 08 19:27:19 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan +Dec 08 19:27:19 meins 3 client_open: connecting localhost:2990 +Dec 08 19:27:19 meins 5 makesock: can not create TCP socket localhost#2990. +Dec 08 19:27:19 meins 5 client_open: Connection refused +Dec 08 19:27:24 meins 2 client_open: loglevel: 5 +Dec 08 19:27:24 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf +Dec 08 19:27:24 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan +Dec 08 19:27:24 meins 3 client_open: connecting localhost:2990 +Dec 08 19:27:24 meins 5 makesock: can not create TCP socket localhost#2990. +Dec 08 19:27:24 meins 5 client_open: Connection refused +Dec 08 19:27:29 meins 2 client_open: loglevel: 5 +Dec 08 19:27:29 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf +Dec 08 19:27:29 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan +Dec 08 19:27:29 meins 3 client_open: connecting localhost:2990 +Dec 08 19:27:29 meins 5 makesock: can not create TCP socket localhost#2990. +Dec 08 19:27:29 meins 5 client_open: Connection refused +Dec 08 19:27:34 meins 2 client_open: loglevel: 5 +Dec 08 19:27:34 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf +Dec 08 19:27:34 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan +Dec 08 19:27:34 meins 3 client_open: connecting localhost:2990 +Dec 08 19:27:34 meins 2 register_task: registering client (0x8067878) +Dec 08 19:27:34 meins 2 client_post_select: --> auth rc4 maan +Dec 08 19:27:34 meins 2 client_post_select: <-- [challenge] +Dec 08 19:27:34 meins 2 client_post_select: --> 1725057215 +Dec 08 19:27:34 meins 2 client_post_select: decrypting session key +Dec 08 19:27:34 meins 2 enable_crypt: rc4 encryption activated for fd 8 +Dec 08 19:27:34 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms +Dec 08 19:27:45 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms +Dec 08 19:27:45 meins 2 clear_slot: clearing slot 0 +Dec 08 19:27:45 meins 3 open_receiver: started mp3: http receiver in slot 0 +Dec 08 19:27:45 meins 2 register_task: registering http receiver node (0x806a7b4) +Dec 08 19:27:45 meins 2 http_recv_post_select: sending http request +Dec 08 19:27:45 meins 2 http_recv_post_select: received ok msg, streaming +Dec 08 19:27:46 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms +Dec 08 19:27:46 meins 2 open_filters: opening mp3 filters +Dec 08 19:27:46 meins 3 open_filters: mp3 filter 1/2 (mp3dec) started in slot 0 +Dec 08 19:27:46 meins 3 open_filters: mp3 filter 2/2 (compress) started in slot 0 +Dec 08 19:27:46 meins 2 register_task: registering filter chain (0x80729f4) +Dec 08 19:27:46 meins 2 audiod_pre_select: initial delay: 173 ms left +Dec 08 19:27:46 meins 2 audiod_pre_select: initial delay: 147 ms left +Dec 08 19:27:46 meins 2 audiod_pre_select: initial delay: 121 ms left +Dec 08 19:27:46 meins 2 audiod_pre_select: initial delay: 95 ms left +Dec 08 19:27:46 meins 2 audiod_pre_select: initial delay: 69 ms left +Dec 08 19:27:46 meins 2 audiod_pre_select: initial delay: 43 ms left +Dec 08 19:27:46 meins 2 audiod_pre_select: initial delay: 17 ms left +Dec 08 19:27:46 meins 2 open_writers: opening mp3 writers +Dec 08 19:27:46 meins 2 open_writers: samplerate: 44100 +Dec 08 19:27:46 meins 3 wng_open: opening wng 0x80a4000 with 1 writer(s) +Dec 08 19:27:46 meins 2 register_task: registering (0x80a4024) +Dec 08 19:27:46 meins 2 alsa_open: 2 channel(s), 44100Hz +Dec 08 19:27:46 meins 2 alsa_open: buffer time: 170658 +Dec 08 19:27:46 meins 2 alsa_open: buffer size: 7526, period_size: 940 +Dec 08 19:28:30 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms +Dec 08 19:28:58 meins 3 rn_event_handler: http_recv: end of file +Dec 08 19:28:58 meins 2 unregister_task: unregistering http receiver node (0x806a7b4) +Dec 08 19:28:58 meins 2 unregister_task: unregistering client (0x8067878) +Dec 08 19:28:58 meins 2 kill_all_decoders: unregistering writer node group in slot 0 +Dec 08 19:28:58 meins 2 unregister_task: unregistering writer node group (0x80a4024) +Dec 08 19:28:58 meins 2 kill_all_decoders: unregistering filter chain in slot 0 +Dec 08 19:28:58 meins 2 unregister_task: unregistering filter chain (0x80729f4) +Dec 08 19:28:58 meins 2 try_to_close_slot: closing slot 0 +Dec 08 19:28:58 meins 3 wng_close: closing wng with 1 writer(s) +Dec 08 19:28:58 meins 2 alsa_close: closing writer node 0x80a4150 +Dec 08 19:28:58 meins 3 close_filters: closing filter chain 0x80729c8 +Dec 08 19:28:58 meins 2 close_filters: closing mp3dec filter +Dec 08 19:28:58 meins 2 close_filters: closing compress filter +Dec 08 19:28:58 meins 3 close_receiver: closing mp3 receiver in slot 0 (eof = 1) +Dec 08 19:28:58 meins 2 clear_slot: clearing slot 0 +Dec 08 19:29:03 meins 2 client_open: loglevel: 5 +Dec 08 19:29:03 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf +Dec 08 19:29:03 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan +Dec 08 19:29:03 meins 3 client_open: connecting localhost:2990 +Dec 08 19:29:03 meins 2 register_task: registering client (0x8067878) +Dec 08 19:29:03 meins 2 client_post_select: --> auth rc4 maan +Dec 08 19:29:03 meins 2 client_post_select: <-- [challenge] +Dec 08 19:29:03 meins 2 client_post_select: --> 705434988 +Dec 08 19:29:03 meins 2 client_post_select: decrypting session key +Dec 08 19:29:03 meins 2 enable_crypt: rc4 encryption activated for fd 8 +Dec 08 19:29:03 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms +Dec 08 19:29:13 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms +Dec 08 19:29:13 meins 2 clear_slot: clearing slot 0 +Dec 08 19:29:13 meins 3 open_receiver: started ogg: dccp receiver in slot 0 +Dec 08 19:29:13 meins 2 register_task: registering dccp receiver node (0x806a804) +Dec 08 19:29:13 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms +Dec 08 19:29:13 meins 2 open_filters: opening ogg filters +Dec 08 19:29:13 meins 3 open_filters: ogg filter 1/2 (oggdec) started in slot 0 +Dec 08 19:29:13 meins 3 open_filters: ogg filter 2/2 (compress) started in slot 0 +Dec 08 19:29:13 meins 2 register_task: registering filter chain (0x8074a44) +Dec 08 19:29:14 meins 3 ogg_convert: input buffer: 17167, opening ov callbacks +Dec 08 19:29:14 meins 3 ogg_convert: 2 channels, 44100 Hz +Dec 08 19:29:14 meins 2 open_writers: opening ogg writers +Dec 08 19:29:14 meins 2 open_writers: samplerate: 44100 +Dec 08 19:29:14 meins 3 wng_open: opening wng 0x80c8d20 with 1 writer(s) +Dec 08 19:29:14 meins 2 register_task: registering (0x80c8d44) +Dec 08 19:29:14 meins 2 alsa_open: 2 channel(s), 44100Hz +Dec 08 19:29:14 meins 2 alsa_open: buffer time: 170658 +Dec 08 19:29:14 meins 2 alsa_open: buffer size: 7526, period_size: 940 +Dec 08 19:29:58 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms +Dec 08 19:30:48 meins 2 compute_time_diff: time diff (cur/avg): -3ms/+1ms +Dec 08 19:30:58 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms +Dec 08 19:31:48 meins 2 compute_time_diff: time diff (cur/avg): -2ms/+1ms +Dec 08 19:31:58 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms +Dec 08 19:32:48 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms +Dec 08 19:32:58 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms +Dec 08 19:33:48 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms +Dec 08 19:33:56 meins 3 rn_event_handler: dccp_recv: end of file +Dec 08 19:33:56 meins 2 unregister_task: unregistering dccp receiver node (0x806a804) +Dec 08 19:33:56 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms +Dec 08 19:33:56 meins 3 filter_event_handler: filter chain: eof +Dec 08 19:33:56 meins 2 unregister_task: unregistering filter chain (0x8074a44) +Dec 08 19:33:56 meins 2 wng_event_handler: wng: end of file +Dec 08 19:33:56 meins 2 unregister_task: unregistering writer node group (0x80c8d44) +Dec 08 19:33:56 meins 2 try_to_close_slot: closing slot 0 +Dec 08 19:33:56 meins 3 wng_close: closing wng with 1 writer(s) +Dec 08 19:33:56 meins 2 alsa_close: closing writer node 0x80c8e70 +Dec 08 19:33:56 meins 3 close_filters: closing filter chain 0x8074a18 +Dec 08 19:33:56 meins 2 close_filters: closing oggdec filter +Dec 08 19:33:56 meins 2 close_filters: closing compress filter +Dec 08 19:33:56 meins 3 close_receiver: closing ogg receiver in slot 0 (eof = 1) +Dec 08 19:33:56 meins 2 clear_slot: clearing slot 0 +Dec 08 19:33:58 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms +Dec 08 19:33:58 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms +Dec 08 19:33:58 meins 2 clear_slot: clearing slot 0 +Dec 08 19:33:58 meins 3 open_receiver: started ogg: dccp receiver in slot 0 +Dec 08 19:33:58 meins 2 register_task: registering dccp receiver node (0x80d0dc4) +Dec 08 19:33:58 meins 2 open_filters: opening ogg filters +Dec 08 19:33:58 meins 3 open_filters: ogg filter 1/2 (oggdec) started in slot 0 +Dec 08 19:33:58 meins 3 open_filters: ogg filter 2/2 (compress) started in slot 0 +Dec 08 19:33:58 meins 2 register_task: registering filter chain (0x807481c) +Dec 08 19:33:58 meins 2 compute_time_diff: time diff (cur/avg): -3ms/+1ms +Dec 08 19:33:58 meins 3 ogg_convert: input buffer: 16947, opening ov callbacks +Dec 08 19:33:58 meins 3 ogg_convert: 2 channels, 44100 Hz +Dec 08 19:33:58 meins 2 open_writers: opening ogg writers +Dec 08 19:33:58 meins 2 open_writers: samplerate: 44100 +Dec 08 19:33:58 meins 3 wng_open: opening wng 0x809d860 with 1 writer(s) +Dec 08 19:33:58 meins 2 register_task: registering (0x809d884) +Dec 08 19:33:58 meins 2 alsa_open: 2 channel(s), 44100Hz +Dec 08 19:33:58 meins 2 alsa_open: buffer time: 170658 +Dec 08 19:33:58 meins 2 alsa_open: buffer size: 7526, period_size: 940 +Dec 08 19:34:48 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms +Dec 08 19:34:58 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms +Dec 08 19:35:48 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms +Dec 08 19:35:58 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms +Dec 08 19:36:48 meins 2 compute_time_diff: time diff (cur/avg): -3ms/+1ms +Dec 08 19:36:54 meins 3 rn_event_handler: dccp_recv: end of file +Dec 08 19:36:54 meins 2 unregister_task: unregistering dccp receiver node (0x80d0dc4) +Dec 08 19:36:54 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms +Dec 08 19:36:54 meins 3 filter_event_handler: filter chain: eof +Dec 08 19:36:54 meins 2 unregister_task: unregistering filter chain (0x807481c) +Dec 08 19:36:54 meins 2 wng_event_handler: wng: end of file +Dec 08 19:36:54 meins 2 unregister_task: unregistering writer node group (0x809d884) +Dec 08 19:36:54 meins 2 try_to_close_slot: closing slot 0 +Dec 08 19:36:54 meins 3 wng_close: closing wng with 1 writer(s) +Dec 08 19:36:54 meins 2 alsa_close: closing writer node 0x809d9b0 +Dec 08 19:36:54 meins 3 close_filters: closing filter chain 0x80747f0 +Dec 08 19:36:54 meins 2 close_filters: closing oggdec filter +Dec 08 19:36:54 meins 2 close_filters: closing compress filter +Dec 08 19:36:54 meins 3 close_receiver: closing ogg receiver in slot 0 (eof = 1) +Dec 08 19:36:54 meins 2 clear_slot: clearing slot 0 +Dec 08 19:36:56 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms +Dec 08 19:36:56 meins 2 clear_slot: clearing slot 0 +Dec 08 19:36:56 meins 3 open_receiver: started mp3: http receiver in slot 0 +Dec 08 19:36:56 meins 2 register_task: registering http receiver node (0x80d14ec) +Dec 08 19:36:56 meins 2 http_recv_post_select: sending http request +Dec 08 19:36:56 meins 2 http_recv_post_select: received ok msg, streaming +Dec 08 19:36:56 meins 2 open_filters: opening mp3 filters +Dec 08 19:36:56 meins 3 open_filters: mp3 filter 1/2 (mp3dec) started in slot 0 +Dec 08 19:36:56 meins 3 open_filters: mp3 filter 2/2 (compress) started in slot 0 +Dec 08 19:36:56 meins 2 register_task: registering filter chain (0x80d16ac) +Dec 08 19:36:56 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms +Dec 08 19:36:56 meins 2 audiod_pre_select: initial delay: 174 ms left +Dec 08 19:36:56 meins 2 audiod_pre_select: initial delay: 148 ms left +Dec 08 19:36:56 meins 2 audiod_pre_select: initial delay: 122 ms left +Dec 08 19:36:56 meins 2 audiod_pre_select: initial delay: 96 ms left +Dec 08 19:36:56 meins 2 audiod_pre_select: initial delay: 70 ms left +Dec 08 19:36:56 meins 2 audiod_pre_select: initial delay: 44 ms left +Dec 08 19:36:56 meins 2 audiod_pre_select: initial delay: 18 ms left +Dec 08 19:36:56 meins 2 open_writers: opening mp3 writers +Dec 08 19:36:56 meins 2 open_writers: samplerate: 44100 +Dec 08 19:36:56 meins 3 wng_open: opening wng 0x80743a0 with 1 writer(s) +Dec 08 19:36:56 meins 2 register_task: registering (0x80743c4) +Dec 08 19:36:56 meins 2 alsa_open: 2 channel(s), 44100Hz +Dec 08 19:36:56 meins 2 alsa_open: buffer time: 170658 +Dec 08 19:36:56 meins 2 alsa_open: buffer size: 7526, period_size: 940 +Dec 08 19:36:58 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms +Dec 08 19:37:48 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+1ms +Dec 08 19:37:58 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms +Dec 08 19:38:48 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms +Dec 08 19:38:58 meins 2 compute_time_diff: time diff (cur/avg): -0ms/+0ms +Dec 08 19:39:06 meins 7 signal_event_handler: terminating on signal 15 +Dec 08 19:39:06 meins 7 clean_exit: caught deadly signal +Dec 08 19:39:06 meins 2 log_welcome: welcome to para_audiod git (Sat Dec 8 13:42:52 MET 2007) +Dec 08 19:39:06 meins 1 log_welcome: using loglevel 1 +Dec 08 19:39:06 meins 2 init_writers: maximal number of writers: 3 +Dec 08 19:39:06 meins 2 check_writer_arg: checking alsa -d plug:swmix +Dec 08 19:39:06 meins 2 alsa_parse_config: options: -d plug:swmix, 2 +Dec 08 19:39:06 meins 2 alsa_parse_config: help given: 0 +Dec 08 19:39:06 meins 2 init_writers: mp3 writer #0: alsa +Dec 08 19:39:06 meins 2 check_writer_arg: checking alsa -d plug:swmix +Dec 08 19:39:06 meins 2 alsa_parse_config: options: -d plug:swmix, 2 +Dec 08 19:39:06 meins 2 alsa_parse_config: help given: 0 +Dec 08 19:39:06 meins 2 init_writers: ogg writer #0: alsa +Dec 08 19:39:06 meins 2 check_writer_arg: checking alsa -d plug:swmix +Dec 08 19:39:06 meins 2 alsa_parse_config: options: -d plug:swmix, 2 +Dec 08 19:39:06 meins 2 alsa_parse_config: help given: 0 +Dec 08 19:39:06 meins 2 init_writers: aac writer #0: alsa +Dec 08 19:39:06 meins 2 init_receivers: initializing http receiver +Dec 08 19:39:06 meins 2 init_receivers: initializing dccp receiver +Dec 08 19:39:06 meins 2 init_receivers: initializing ortp receiver +Dec 08 19:39:06 meins 1 check_receiver_arg: checking http -i 127.0.0.1 +Dec 08 19:39:06 meins 1 parse_receiver_args: options: -i 127.0.0.1 +Dec 08 19:39:06 meins 1 parse_receiver_args: argc = 3, argv[0]: http +Dec 08 19:39:06 meins 1 check_receiver_arg: checking dccp -i localhost +Dec 08 19:39:06 meins 1 parse_receiver_args: options: -i localhost +Dec 08 19:39:06 meins 1 parse_receiver_args: argc = 3, argv[0]: dccp +Dec 08 19:39:06 meins 1 check_receiver_arg: checking dccp -i localhost +Dec 08 19:39:06 meins 1 parse_receiver_args: options: -i localhost +Dec 08 19:39:06 meins 1 parse_receiver_args: argc = 3, argv[0]: dccp +Dec 08 19:39:06 meins 2 init_filters: maximal number of filters: 6 +Dec 08 19:39:06 meins 2 add_filter: mp3 filter 1: mp3dec +Dec 08 19:39:06 meins 2 add_filter: mp3 filter 2: compress +Dec 08 19:39:06 meins 2 add_filter: ogg filter 1: oggdec +Dec 08 19:39:06 meins 2 add_filter: ogg filter 2: compress +Dec 08 19:39:06 meins 2 add_filter: aac filter 1: aacdec +Dec 08 19:39:06 meins 2 add_filter: aac filter 2: compress +Dec 08 19:39:06 meins 2 clear_slot: clearing slot 0 +Dec 08 19:39:06 meins 2 clear_slot: clearing slot 1 +Dec 08 19:39:06 meins 2 clear_slot: clearing slot 2 +Dec 08 19:39:06 meins 2 clear_slot: clearing slot 3 +Dec 08 19:39:06 meins 2 clear_slot: clearing slot 4 +Dec 08 19:39:06 meins 2 init_grabbing: grab init +Dec 08 19:39:06 meins 2 setup_signal_handling: signal pipe: fd 4 +Dec 08 19:39:06 meins 1 para_install_sighandler: catching signal 2 +Dec 08 19:39:06 meins 1 para_install_sighandler: catching signal 15 +Dec 08 19:39:06 meins 1 para_install_sighandler: catching signal 1 +Dec 08 19:39:06 meins 3 audiod_get_socket: local socket: /var/paraslash/audiod_socket.meins +Dec 08 19:39:06 meins 2 daemon_init: daemonizing +Dec 08 19:39:06 meins 2 init_sched: initializing scheduler +Dec 08 19:39:06 meins 2 register_task: registering signal task (0x80631bc) +Dec 08 19:39:06 meins 1 register_task: pre_select: 0x80631c0 +Dec 08 19:39:06 meins 1 register_task: post_select: 0x80631c0 +Dec 08 19:39:06 meins 2 register_task: registering command task (0xbfa7e274) +Dec 08 19:39:06 meins 1 register_task: pre_select: 0xbfa7e278 +Dec 08 19:39:06 meins 1 register_task: post_select: 0xbfa7e278 +Dec 08 19:39:06 meins 2 register_task: registering status task (0x80632e8) +Dec 08 19:39:06 meins 1 register_task: pre_select: 0x80632ec +Dec 08 19:39:06 meins 1 register_task: post_select: 0x80632ec +Dec 08 19:39:06 meins 2 register_task: registering audiod task (0xbfa7e398) +Dec 08 19:39:06 meins 1 register_task: pre_select: 0xbfa7e39c +Dec 08 19:39:06 meins 1 register_task: post_select: 0xbfa7e39c +Dec 08 19:39:06 meins 2 status_pre_select: clock diff count: 5 +Dec 08 19:39:06 meins 2 client_open: loglevel: 5 +Dec 08 19:39:06 meins 2 client_open: config_file: /home/maan/.paraslash/client.conf +Dec 08 19:39:06 meins 2 client_open: key_file: /home/maan/.paraslash/key.maan +Dec 08 19:39:06 meins 3 client_open: connecting localhost:2990 +Dec 08 19:39:06 meins 2 register_task: registering client (0x8067878) +Dec 08 19:39:06 meins 1 register_task: pre_select: 0x806787c +Dec 08 19:39:06 meins 1 register_task: post_select: 0x806787c +Dec 08 19:39:06 meins 2 client_post_select: --> auth rc4 maan +Dec 08 19:39:06 meins 2 client_post_select: <-- [challenge] +Dec 08 19:39:06 meins 2 client_post_select: --> 153889019 +Dec 08 19:39:06 meins 1 client_post_select: ++++ server info ++++ + +Proceed. + +++++ end of server info ++++ +Dec 08 19:39:06 meins 2 client_post_select: decrypting session key +Dec 08 19:39:06 meins 2 enable_crypt: rc4 encryption activated for fd 7 +Dec 08 19:39:06 meins 1 client_post_select: --> +stat +1 +End of Command. diff --git a/web/screenshots/gui.png b/web/screenshots/gui.png new file mode 100644 index 00000000..2a6c7443 Binary files /dev/null and b/web/screenshots/gui.png differ diff --git a/web/screenshots/server.log b/web/screenshots/server.log new file mode 100644 index 00000000..f84ede81 --- /dev/null +++ b/web/screenshots/server.log @@ -0,0 +1,192 @@ +Dec 08 19:28:58 2: (21285) log_welcome: welcome to para_server git (Sat Dec 8 13:42:52 MET 2007) +Dec 08 19:28:58 1: (21285) log_welcome: using loglevel 1 +Dec 08 19:28:58 1: (21285) populate_user_list: found entry for maan +Dec 08 19:28:58 1: (21285) populate_user_list: found 4 perm entries +Dec 08 19:28:58 1: (21285) populate_user_list: found entry for install +Dec 08 19:28:58 1: (21285) populate_user_list: found 4 perm entries +Dec 08 19:28:58 1: (21285) populate_user_list: found entry for www +Dec 08 19:28:58 1: (21285) populate_user_list: found 4 perm entries +Dec 08 19:28:58 2: (21285) daemon_init: daemonizing +Dec 08 19:28:58 3: (21286) server_init: initializing audio format handlers +Dec 08 19:28:58 2: (21286) afh_init: supported audio formats: mp3 ogg aac +Dec 08 19:28:58 3: (21286) afh_init: initializing mp3 handler +Dec 08 19:28:58 3: (21286) afh_init: initializing ogg handler +Dec 08 19:28:58 3: (21286) afh_init: initializing aac handler +Dec 08 19:28:58 3: (21286) server_init: initializing virtual streaming system +Dec 08 19:28:58 2: (21286) vss_init: announce timeval: 300ms +Dec 08 19:28:58 3: (21286) vss_init: initializing http sender +Dec 08 19:28:58 2: (21286) para_listen: listening on TCP port 8000, fd 4 +Dec 08 19:28:58 1: (21286) http_send_init: http sender init complete +Dec 08 19:28:58 3: (21286) vss_init: initializing dccp sender +Dec 08 19:28:58 2: (21286) para_listen: listening on DCCP port 5001, fd 5 +Dec 08 19:28:58 3: (21286) vss_init: initializing ortp sender +Dec 08 19:28:58 1: (21286) ortp_send_init: ortp sender init complete +Dec 08 19:28:58 3: (21286) setup_signal_handling: setting up signal handlers +Dec 08 19:28:58 1: (21286) para_install_sighandler: catching signal 2 +Dec 08 19:28:58 1: (21286) para_install_sighandler: catching signal 15 +Dec 08 19:28:58 1: (21286) para_install_sighandler: catching signal 1 +Dec 08 19:28:58 1: (21286) para_install_sighandler: catching signal 17 +Dec 08 19:28:58 1: (21286) para_install_sighandler: catching signal 10 +Dec 08 19:28:58 3: (21286) server_init: initializing the audio file selector +Dec 08 19:28:58 2: (21287) get_database_dir: afs_database dir /home/maan/.paraslash/afs_database +Dec 08 19:28:58 3: (21287) open_afs_tables: opening 7 osl tables in /home/maan/.paraslash/afs_database +Dec 08 19:28:58 2: (21287) osl_open_table: opening table audio_files +Dec 08 19:28:58 1: (21287) init_table_structure: creating table structure for 'audio_files' from table description +Dec 08 19:28:58 1: (21287) init_table_structure: OK. Index entry size: 32 +Dec 08 19:28:58 1: (21287) map_table: mapping table 'audio_files' (index: /home/maan/.paraslash/afs_database/audio_files/index) +Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/audio_files/index: size 218667 +Dec 08 19:28:58 1: (21287) read_table_desc: 5 columns +Dec 08 19:28:58 1: (21287) compare_table_descriptions: table description of 'audio_files' matches on-disk data, good +Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/audio_files/2346ad27d7568ba9896f1b7da6b5991251debdf2: size 143430 +Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/audio_files/3150ecd5e0294534a81ae047ddac559de481d774: size 436636 +Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/audio_files/94ea39e309f3f31357ab60b190b6b8c32f21620b: size 225390 +Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/audio_files/b063ad096f9d142a388ca2a10d46b56904e26cda: size 1174409 +Dec 08 19:28:58 1: (21287) osl_open_table: num rows: 6830 +Dec 08 19:28:58 2: (21286) init_afs: afs_socket: 8, afs_socket_cookie: 1363241225 +Dec 08 19:28:58 3: (21286) server_init: initializing tcp command socket +Dec 08 19:28:58 2: (21286) para_listen: listening on TCP port 2990, fd 9 +Dec 08 19:28:58 3: (21286) server_init: server init complete +Dec 08 19:28:58 1: (21286) chk_barrier: autoplay_delay barrier: 14981ms left +Dec 08 19:28:58 1: (21286) status_refresh: 0 events, forcing status update +Dec 08 19:28:58 1: (21286) para_next_signal: next signal: 10 +Dec 08 19:28:58 1: (21286) chk_barrier: autoplay_delay barrier: 14981ms left +Dec 08 19:28:58 1: (21286) para_next_signal: next signal: 10 +Dec 08 19:28:58 1: (21286) chk_barrier: autoplay_delay barrier: 14981ms left +Dec 08 19:28:58 2: (21287) aft_open: audio file table contains 6830 files +Dec 08 19:28:58 2: (21287) osl_open_table: opening table attributes +Dec 08 19:28:58 1: (21287) init_table_structure: creating table structure for 'attributes' from table description +Dec 08 19:28:58 1: (21287) init_table_structure: OK. Index entry size: 16 +Dec 08 19:28:58 1: (21287) map_table: mapping table 'attributes' (index: /home/maan/.paraslash/afs_database/attributes/index) +Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/attributes/index: size 396 +Dec 08 19:28:58 1: (21287) read_table_desc: 2 columns +Dec 08 19:28:58 1: (21287) compare_table_descriptions: table description of 'attributes' matches on-disk data, good +Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/attributes/fd1e48caeff7212c45fc08608b7187feb10a7a2d: size 42 +Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/attributes/6ae999552a0d2dca14d62e2bc8b764d377b1dd6c: size 141 +Dec 08 19:28:58 1: (21287) osl_open_table: num rows: 21 +Dec 08 19:28:58 2: (21287) osl_open_table: opening table score +Dec 08 19:28:58 1: (21287) init_table_structure: creating table structure for 'score' from table description +Dec 08 19:28:58 1: (21287) init_table_structure: OK. Index entry size: 0 +Dec 08 19:28:58 1: (21287) osl_open_table: num rows: 0 +Dec 08 19:28:58 2: (21287) osl_open_table: opening table moods +Dec 08 19:28:58 1: (21287) init_table_structure: creating table structure for 'moods' from table description +Dec 08 19:28:58 1: (21287) init_table_structure: OK. Index entry size: 16 +Dec 08 19:28:58 1: (21287) map_table: mapping table 'moods' (index: /home/maan/.paraslash/afs_database/moods/index) +Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/moods/index: size 235 +Dec 08 19:28:58 1: (21287) read_table_desc: 3 columns +Dec 08 19:28:58 1: (21287) compare_table_descriptions: table description of 'moods' matches on-disk data, good +Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/moods/87ea5dfc8b8e384d848979496e706390b497e547: size 50 +Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/moods/6ae999552a0d2dca14d62e2bc8b764d377b1dd6c: size 67 +Dec 08 19:28:58 1: (21287) osl_open_table: num rows: 10 +Dec 08 19:28:58 2: (21287) osl_open_table: opening table lyrics +Dec 08 19:28:58 1: (21287) init_table_structure: creating table structure for 'lyrics' from table description +Dec 08 19:28:58 1: (21287) init_table_structure: OK. Index entry size: 16 +Dec 08 19:28:58 1: (21287) map_table: mapping table 'lyrics' (index: /home/maan/.paraslash/afs_database/lyrics/index) +Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/lyrics/index: size 347 +Dec 08 19:28:58 1: (21287) read_table_desc: 3 columns +Dec 08 19:28:58 1: (21287) compare_table_descriptions: table description of 'lyrics' matches on-disk data, good +Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/lyrics/87ea5dfc8b8e384d848979496e706390b497e547: size 85 +Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/lyrics/6ae999552a0d2dca14d62e2bc8b764d377b1dd6c: size 478 +Dec 08 19:28:58 1: (21287) osl_open_table: num rows: 17 +Dec 08 19:28:58 2: (21287) osl_open_table: opening table images +Dec 08 19:28:58 1: (21287) init_table_structure: creating table structure for 'images' from table description +Dec 08 19:28:58 1: (21287) init_table_structure: OK. Index entry size: 16 +Dec 08 19:28:58 1: (21287) map_table: mapping table 'images' (index: /home/maan/.paraslash/afs_database/images/index) +Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/images/index: size 7227 +Dec 08 19:28:58 1: (21287) read_table_desc: 3 columns +Dec 08 19:28:58 1: (21287) compare_table_descriptions: table description of 'images' matches on-disk data, good +Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/images/87ea5dfc8b8e384d848979496e706390b497e547: size 2235 +Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/images/6ae999552a0d2dca14d62e2bc8b764d377b1dd6c: size 12950 +Dec 08 19:28:58 1: (21287) osl_open_table: num rows: 447 +Dec 08 19:28:58 2: (21287) osl_open_table: opening table playlists +Dec 08 19:28:58 1: (21287) init_table_structure: creating table structure for 'playlists' from table description +Dec 08 19:28:58 1: (21287) init_table_structure: OK. Index entry size: 16 +Dec 08 19:28:58 1: (21287) map_table: mapping table 'playlists' (index: /home/maan/.paraslash/afs_database/playlists/index) +Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/playlists/index: size 107 +Dec 08 19:28:58 1: (21287) read_table_desc: 3 columns +Dec 08 19:28:58 1: (21287) compare_table_descriptions: table description of 'playlists' matches on-disk data, good +Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/playlists/87ea5dfc8b8e384d848979496e706390b497e547: size 10 +Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/playlists/6ae999552a0d2dca14d62e2bc8b764d377b1dd6c: size 7 +Dec 08 19:28:58 1: (21287) osl_open_table: num rows: 2 +Dec 08 19:28:58 2: (21287) afs_init: server_socket: 9, afs_socket_cookie: 1363241225 +Dec 08 19:28:58 1: (21287) osl_open_disk_object: filename: /home/maan/.paraslash/afs_database/moods/f3f1dd33eb2a8b380b64a830e5fd90eab77d9ff3/9d/b063f3b5e0adfd0d29a03db0a1c207b3740a94 +Dec 08 19:28:58 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/moods/f3f1dd33eb2a8b380b64a830e5fd90eab77d9ff3/9d/b063f3b5e0adfd0d29a03db0a1c207b3740a94: size 31 +Dec 08 19:28:58 1: (21287) parse_mood_line: accept entry added, method: 0x806c0f4 +Dec 08 19:28:58 3: (21287) change_current_mood: computing statistics of admissible files +Dec 08 19:28:58 2: (21287) log_statistics: last_played mean: 1193159581, last_played sigma: 4161524 +Dec 08 19:28:58 2: (21287) log_statistics: num_played mean: 32, num_played sigma: 21 +Dec 08 19:28:58 2: (21287) change_current_mood: 26 admissible files +Dec 08 19:28:58 3: (21287) change_current_mood: loaded mood gulp +Dec 08 19:28:58 2: (21287) register_signal_task: signal pipe: fd 8 +Dec 08 19:28:58 1: (21287) para_install_sighandler: catching signal 2 +Dec 08 19:28:58 1: (21287) para_install_sighandler: catching signal 15 +Dec 08 19:28:58 1: (21287) para_install_sighandler: catching signal 13 +Dec 08 19:28:58 1: (21287) para_install_sighandler: catching signal 1 +Dec 08 19:28:58 2: (21287) init_sched: initializing scheduler +Dec 08 19:28:58 2: (21287) register_task: registering signal task (0x806f244) +Dec 08 19:28:58 1: (21287) register_task: pre_select: 0x806f248 +Dec 08 19:28:58 1: (21287) register_task: post_select: 0x806f248 +Dec 08 19:28:58 2: (21287) setup_command_socket_or_die: listening on socket /var/paraslash/afs_command_socket (fd 1) +Dec 08 19:28:58 2: (21287) register_task: registering command task (0x806f118) +Dec 08 19:28:58 1: (21287) register_task: pre_select: 0x806f11c +Dec 08 19:28:58 1: (21287) register_task: post_select: 0x806f11c +Dec 08 19:28:59 2: (21286) main: got connection from ::ffff:127.0.0.1#42344, forking +Dec 08 19:28:59 1: (21356) close_listed_fds: closing fd 9 +Dec 08 19:28:59 1: (21356) close_listed_fds: closing fd 8 +Dec 08 19:28:59 1: (21356) close_listed_fds: closing fd 6 +Dec 08 19:28:59 1: (21356) close_listed_fds: closing fd 5 +Dec 08 19:28:59 1: (21356) close_listed_fds: closing fd 4 +Dec 08 19:28:59 1: (21286) chk_barrier: autoplay_delay barrier: 13507ms left +Dec 08 19:28:59 1: (21356) handle_connect: received rc4 request for user maan +Dec 08 19:28:59 1: (21356) handle_connect: sending 64 byte challenge +Dec 08 19:28:59 2: (21356) handle_connect: good auth for maan (1081806111) +Dec 08 19:28:59 1: (21356) init_rc4_keys: rc4 keys initialized (84:105) +Dec 08 19:28:59 2: (21356) enable_crypt: rc4 encryption activated for fd 10 +Dec 08 19:28:59 3: (21356) handle_connect: invalid command +Dec 08 19:28:59 1: (21286) para_next_signal: next signal: 17 +Dec 08 19:28:59 1: (21286) para_reap_child: child 21356 exited. Exit status: 1 +Dec 08 19:28:59 1: (21286) chk_barrier: autoplay_delay barrier: 13498ms left +Dec 08 19:29:03 2: (21286) main: got connection from ::ffff:127.0.0.1#42345, forking +Dec 08 19:29:03 1: (21362) close_listed_fds: closing fd 9 +Dec 08 19:29:03 1: (21362) close_listed_fds: closing fd 8 +Dec 08 19:29:03 1: (21362) close_listed_fds: closing fd 6 +Dec 08 19:29:03 1: (21362) close_listed_fds: closing fd 5 +Dec 08 19:29:03 1: (21362) close_listed_fds: closing fd 4 +Dec 08 19:29:03 1: (21286) chk_barrier: autoplay_delay barrier: 9951ms left +Dec 08 19:29:03 1: (21362) handle_connect: received rc4 request for user maan +Dec 08 19:29:03 1: (21362) handle_connect: sending 64 byte challenge +Dec 08 19:29:03 2: (21362) handle_connect: good auth for maan (705434988) +Dec 08 19:29:03 1: (21362) init_rc4_keys: rc4 keys initialized (18:65) +Dec 08 19:29:03 2: (21362) enable_crypt: rc4 encryption activated for fd 10 +Dec 08 19:29:03 1: (21362) check_perms: checking permissions +Dec 08 19:29:03 3: (21362) handle_connect: calling com_stat() for maan@::ffff:127.0.0.1#42345 +Dec 08 19:29:13 1: (21286) chk_barrier: autoplay_delay barrier: 3ms left +Dec 08 19:29:13 1: (21286) vss_preselect: ready and playing, but no audio file +Dec 08 19:29:13 3: (21286) vss_post_select: requesting new fd from afs +Dec 08 19:29:13 1: (21287) execute_server_command: received: new +Dec 08 19:29:13 3: (21287) open_next_audio_file: getting next audio file +Dec 08 19:29:13 1: (21287) osl_open_disk_object: filename: /home/maan/.paraslash/afs_database/audio_files/7e078c9876ccabef154017c770e05195c85b5e4d/55/b5e38d467105bd88133cf5ded70e551e582593 +Dec 08 19:29:13 1: (21287) mmap_full_file: /home/maan/.paraslash/afs_database/audio_files/7e078c9876ccabef154017c770e05195c85b5e4d/55/b5e38d467105bd88133cf5ded70e551e582593: size 4532 +Dec 08 19:29:13 1: (21287) mmap_full_file: /home/mp3/checked/dvd_07/cd_46/The_G.U.L.P.__Scheiss_Krieg.ogg: size 5274482 +Dec 08 19:29:13 1: (21287) mood_update_audio_file: score: 6 +Dec 08 19:29:13 1: (21287) mood_update_audio_file: moving from rank 26 to 35% +Dec 08 19:29:13 1: (21287) score_update: new score: 0, rank 9/26 +Dec 08 19:29:13 1: (21287) osl_update_object: updating column 1 of score +Dec 08 19:29:13 1: (21287) score_update: new score: -65, rank 1/26 +Dec 08 19:29:13 1: (21287) osl_update_object: updating column 1 of score +Dec 08 19:29:13 1: (21287) save_afd: size: 8932 +Dec 08 19:29:13 1: (21287) pass_afd: passing 8 bytes and fd 12 +Dec 08 19:29:13 1: (21286) recv_afs_result: fd: 10, code: 0, shmid: 28442631 +Dec 08 19:29:13 1: (21286) chk_barrier: data send barrier: 300ms left +Dec 08 19:29:13 1: (21286) status_refresh: 1 events, forcing status update +Dec 08 19:29:13 1: (21286) chk_barrier: data send barrier: 300ms left +Dec 08 19:29:13 1: (21286) chk_barrier: data send barrier: 300ms left +Dec 08 19:29:13 1: (21286) para_next_signal: next signal: 10 +Dec 08 19:29:13 1: (21286) chk_barrier: data send barrier: 300ms left +Dec 08 19:29:13 3: (21286) dccp_post_select: connection from ::ffff:127.0.0.1#46539 +Dec 08 19:29:13 1: (21286) chk_barrier: data send barrier: 287ms left +Dec 08 19:29:13 1: (21286) chk_barrier: data send barrier: 287ms left +Dec 08 19:29:13 1: (21287) para_next_signal: next signal: 10 +Dec 08 19:29:13 1: (21286) status_refresh: 2 events, forcing status update +Dec 08 19:29:13 1: (21286) para_next_signal: next signal: 10 +Dec 08 19:29:13 1: (21287) para_next_signal: next signal: 10 +Dec 08 19:29:14 1: (21286) cq_enqueue: 4446 bytes queued for 0x8072eb0 diff --git a/wma.h b/wma.h new file mode 100644 index 00000000..33e34a35 --- /dev/null +++ b/wma.h @@ -0,0 +1,33 @@ +/** \file wma.h The asf_header structure and public wma function declarations. */ + +/** + * Information contained in an asf audio file header. + * + * Both para_filter and para_afh need to read the header. + */ +struct asf_header_info { + /** The size of the audio file header. */ + int header_len; + /** Offset of the audio stream info header. */ + int audio_stream_info_start; + /** Number of channels. */ + uint8_t channels; + /** One of the 5 possible sample rates. */ + uint16_t sample_rate; + /** Size of one data block. */ + uint16_t block_align; + /** Bits per second. */ + uint32_t bit_rate; + /** Further decoding information (ignored). */ + uint32_t flags1; + /** Whether to use exp_vlc, bit reservoir, variable block len. */ + uint16_t flags2; +}; + +/* wma_common.c */ +int wma_log2(unsigned int v); +const char *search_pattern(const char *pattern, int pattern_len, + const char *buf, int buf_size); +int read_asf_header(const char *buf, int loaded, struct asf_header_info *ahi); + +#define WMA_FRAME_SKIP 31 diff --git a/wma_afh.c b/wma_afh.c new file mode 100644 index 00000000..9b7d85b3 --- /dev/null +++ b/wma_afh.c @@ -0,0 +1,278 @@ +/* + * Copyright (C) 2009-2011 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file wma_afh.c The audio format handler for WMA files. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "para.h" +#include "error.h" +#include "afh.h" +#include "portable_io.h" +#include "string.h" +#include "wma.h" + +#define FOR_EACH_FRAME(_f, _buf, _size, _ba) for (_f = (_buf); \ + _f + (_ba) + WMA_FRAME_SKIP < (_buf) + (_size); \ + _f += (_ba) + WMA_FRAME_SKIP) + +/* + * Must be called on a frame boundary, e.g. start + header_len. + * \return Frame count, superframe count via *num_superframes. + */ +static int count_frames(const char *buf, int buf_size, int block_align, + int *num_superframes) +{ + int fc = 0, sfc = 0; /* frame count, superframe count */ + const uint8_t *p; + + + FOR_EACH_FRAME(p, (uint8_t *)buf, buf_size, block_align) { + fc += p[WMA_FRAME_SKIP] & 0x0f; + sfc++; + } + PARA_INFO_LOG("%d frames, %d superframes\n", fc, sfc); + if (num_superframes) + *num_superframes = sfc; + return fc; +} + +/* + * put_utf8() and get_str16() below are based on macros in libavutil/common.h + * of the mplayer source code, copyright (c) 2006 Michael Niedermayer + * . + */ + +/* + * Convert a 32-bit Unicode character to its UTF-8 encoded form. + * + * Writes up to 4 bytes for values in the valid UTF-8 range and up to 7 bytes + * in the general case, depending on the length of the converted Unicode + * character. + * + * \param result Where the converted UTF-8 bytes are written. + */ +static int put_utf8(uint32_t val, char *result) +{ + char *out = result; + int bytes, shift; + uint32_t in = val; + + if (in < 0x80) { + *out++ = in; + return 1; + } + bytes = (wma_log2(in) + 4) / 5; + shift = (bytes - 1) * 6; + *out++ = (256 - (256 >> bytes)) | (in >> shift); + while (shift >= 6) { + shift -= 6; + *out++ = 0x80 | ((in >> shift) & 0x3f); + } + return out - result; +} + +static char *get_str16(const char *in, int len) +{ + const char *p = in; + int out_size = 0, out_len = 0; + char *out = NULL; + + len /= 2; + while (len--) { + uint32_t x; + if (out_len + 7 + 1 >= out_size) { + out_size = 2 * out_size + 50; + out = para_realloc(out, out_size); + } + x = read_u16(p); + p += 2; + out_len += put_utf8(x, out + out_len); + if (x == 0) + return out; + } + if (out) + out[out_len] = '\0'; + return out; +} + +static const char comment_header[] = { + 0x33, 0x26, 0xb2, 0x75, 0x8E, 0x66, 0xCF, 0x11, + 0xa6, 0xd9, 0x00, 0xaa, 0x00, 0x62, 0xce, 0x6c +}; + +static const char extended_content_header[] = { + 0x40, 0xA4, 0xD0, 0xD2, 0x07, 0xE3, 0xD2, 0x11, + 0x97, 0xF0, 0x00, 0xA0, 0xC9, 0x5E, 0xA8, 0x50 +}; + +static const char year_tag_header[] = { /* WM/Year */ + 0x57, 0x00, 0x4d, 0x00, 0x2f, 0x00, 0x59, 0x00, + 0x65, 0x00, 0x61, 0x00, 0x72, 0x00 +}; + +static const char album_tag_header[] = { /* WM/AlbumTitle */ + 0x57, 0x00, 0x4d, 0x00, 0x2f, 0x00, 0x41, 0x00, + 0x6c, 0x00, 0x62, 0x00, 0x75, 0x00, 0x6d, 0x00, + 0x54, 0x00, 0x69, 0x00, 0x74, 0x00, 0x6c, 0x00, + 0x65, 0x00 +}; + +static void read_asf_tags(const char *buf, int buf_size, struct taginfo *ti) +{ + const char *p, *end = buf + buf_size, *q; + uint16_t len1, len2, len3, len4, len5; + + p = search_pattern(comment_header, sizeof(comment_header), + buf, buf_size); + if (!p || p + 34 >= end) { + PARA_NOTICE_LOG("comment header not found\n"); + goto next; + } + p += 24; + len1 = read_u16(p); + p += 2; + len2 = read_u16(p); + p += 2; + len3 = read_u16(p); + p += 2; + len4 = read_u16(p); + p += 2; + len5 = read_u16(p); + p += 2; + if (p + len1 >= end) + goto next; + ti->title = get_str16(p, len1); + p += len1; + if (p + len2 >= end) + goto next; + ti->artist = get_str16(p, len2); + p += len2 + len3 + len4; + if (p + len5 >= end) + goto next; + ti->comment = get_str16(p, len5); +next: + p = search_pattern(extended_content_header, sizeof(extended_content_header), + buf, buf_size); + if (!p) { + PARA_NOTICE_LOG("extended content header not found\n"); + return; + } + q = search_pattern(year_tag_header, sizeof(year_tag_header), + p, end - p); + if (q) { + const char *r = q + sizeof(year_tag_header) + 6; + if (r < end) + ti->year = get_str16(r, end - r); + } + q = search_pattern(album_tag_header, sizeof(album_tag_header), + p, end - p); + if (q) { + const char *r = q + sizeof(album_tag_header) + 6; + if (r < end) + ti->album = get_str16(r, end - r); + } +} + +static void set_chunk_tv(int frames_per_chunk, int frequency, + struct timeval *result) +{ + uint64_t x = (uint64_t)frames_per_chunk * 2048 * 1000 * 1000 + / frequency; + + result->tv_sec = x / 1000 / 1000; + result->tv_usec = x % (1000 * 1000); + PARA_INFO_LOG("chunk time: %lums\n", tv2ms(result)); +} + +/* Must be called on a frame boundary. */ +static int wma_make_chunk_table(char *buf, size_t buf_size, int block_align, + struct afh_info *afhi) +{ + const uint8_t *f, *start = (uint8_t *)buf; + int j, frames_per_chunk; + size_t ct_size = 250; + int ret, count = 0, num_frames, num_superframes; + + afhi->chunk_table = para_malloc(ct_size * sizeof(uint32_t)); + afhi->chunk_table[0] = 0; + afhi->chunk_table[1] = afhi->header_len; + + num_frames = count_frames(buf, buf_size, block_align, + &num_superframes); + ret = -E_NO_WMA; + if (num_frames == 0 || num_superframes == 0) + goto fail; + afhi->seconds_total = num_frames * 2048 /* FIXME */ + / afhi->frequency; + frames_per_chunk = num_frames / num_superframes / 2; + PARA_INFO_LOG("%d frames per chunk\n", frames_per_chunk); + j = 1; + FOR_EACH_FRAME(f, start, buf_size, block_align) { + count += f[WMA_FRAME_SKIP] & 0x0f; + while (count > j * frames_per_chunk) { + j++; + if (j >= ct_size) { + ct_size *= 2; + afhi->chunk_table = para_realloc( + afhi->chunk_table, + ct_size * sizeof(uint32_t)); + } + afhi->chunk_table[j] = f - start + afhi->header_len + block_align + WMA_FRAME_SKIP; + } + } + afhi->chunks_total = j; + set_chunk_tv(frames_per_chunk, afhi->frequency, &afhi->chunk_tv); + return 1; +fail: + free(afhi->chunk_table); + return ret; +} + +static int wma_get_file_info(char *map, size_t numbytes, __a_unused int fd, + struct afh_info *afhi) +{ + int ret; + struct asf_header_info ahi; + + ret = read_asf_header(map, numbytes, &ahi); + if (ret < 0) + return ret; + if (ret == 0) + return -E_NO_WMA; + afhi->bitrate = ahi.bit_rate / 1000; + if (ahi.sample_rate == 0) + return -E_NO_WMA; + afhi->frequency = ahi.sample_rate; + afhi->channels = ahi.channels; + afhi->header_len = ahi.header_len; + afhi->header_offset = 0; + wma_make_chunk_table(map + ahi.header_len, numbytes - ahi.header_len, + ahi.block_align, afhi); + read_asf_tags(map, ahi.header_len, &afhi->tags); + return 0; +} + +static const char* wma_suffixes[] = {"wma", NULL}; + +/** + * The init function of the wma audio format handler. + * + * \param afh Pointer to the struct to initialize. + */ +void wma_afh_init(struct audio_format_handler *afh) +{ + afh->get_file_info = wma_get_file_info; + afh->suffixes = wma_suffixes; +} diff --git a/wma_common.c b/wma_common.c new file mode 100644 index 00000000..781db5f4 --- /dev/null +++ b/wma_common.c @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2009-2011 Andre Noll + * + * Licensed under the GPL v2. For licencing details see COPYING. + */ + +/** \file wma_common.c Functions used by both the WMA afh and decoder. */ + +#include +#include +#include +#include +#include +#include +#include + +#include "para.h" +#include "error.h" +#include "afh.h" +#include "portable_io.h" +#include "imdct.h" +#include "wma.h" + +/** + * Find the first occurrence of the given pattern. + * + * \param pattern The pattern to search for. + * \param pattern_len The length of the pattern in bytes. + * \param buf The buffer to search for the pattern. + * \param buf_size The number of bytes in \a buf. + */ +const char *search_pattern(const char *pattern, int pattern_len, + const char *buf, int buf_size) +{ + const char *p, *end = buf + buf_size; + + /* TODO: Use suffix arrays to speed up the search. */ + for (p = buf; p + pattern_len < end; p++) { + if (memcmp(p, pattern, pattern_len)) + continue; + PARA_DEBUG_LOG("found %d byte pattern@%d\n", + pattern_len, (int)(p - buf)); + return p; + } + PARA_NOTICE_LOG("%d byte pattern not found\n", pattern_len); + return NULL; +} + +/* + 40 9e 69 f8 4d 5b cf 11 a8 fd 00 80 5f 5c 44 2b + */ +static int find_audio_stream_info(const char *buf, int len) +{ + const char pattern[] = {0x40, 0x9e, 0x69, 0xf8}; + const char *p = search_pattern(pattern, sizeof(pattern), buf, len); + + if (!p) + return -E_WMA_NO_GUID; + PARA_DEBUG_LOG("found audio stream guid@%0x\n", (int)(p - buf)); + return p - buf + 16; +} + +static int read_header_len(const char *buf, int len) +{ + uint16_t header_len; + + if (len < 18) + return 0; + header_len = read_u16(buf + 16) + 46; + PARA_DEBUG_LOG("header_len: %d\n", header_len); + return header_len; +} + +/** + * Read an asf audio file header. + * + * \param buf The input buffer. + * \param loaded Number of bytes in \a buf. + * \param ahi Result pointer. + * + * \return Negative on errors, zero if more data is needed in order to read the + * full header, 1 on success. + */ +int read_asf_header(const char *buf, int loaded, struct asf_header_info *ahi) +{ + int ret; + const char *start; + + ahi->header_len = read_header_len(buf, loaded); + if (ahi->header_len == 0) /* too short to read header len */ + return 0; + if (ahi->header_len > loaded) /* too short to read header */ + return 0; + ret = find_audio_stream_info(buf, ahi->header_len); + if (ret < 0) + return ret; + if (ret + 62 > loaded) + return 0; + ahi->audio_stream_info_start = ret; + start = buf + ahi->audio_stream_info_start; + ahi->channels = ((uint8_t *)start)[40]; + ahi->sample_rate = read_u16(start + 42); + PARA_NOTICE_LOG("%d channels, sample rate: %d\n", ahi->channels, + ahi->sample_rate); + + ahi->bit_rate = 8 * read_u16(start + 46); + PARA_INFO_LOG("bit rate: %d\n", ahi->bit_rate); + + ahi->block_align = read_u16(start + 50); + PARA_INFO_LOG("block_align: %d\n", ahi->block_align); + + ahi->flags1 = read_u32(start + 56); + ahi->flags2 = read_u16(start + 60); + PARA_INFO_LOG("read_asf_header: flags1: %d, flag2: %d\n", + ahi->flags1, ahi->flags2); + return 1; +} + +const uint8_t log2_tab[256] = { + 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7 +}; + +/** + * Compute the base-2 logarithm. + * + * \param v The value to compute the logarithm of. + * + * \return An integer approximation of log2(v). + */ +int wma_log2(unsigned int v) +{ + int n = 0; + if (v & 0xffff0000) { + v >>= 16; + n += 16; + } + if (v & 0xff00) { + v >>= 8; + n += 8; + } + n += log2_tab[v]; + + return n; +} diff --git a/wmadata.h b/wmadata.h new file mode 100644 index 00000000..b5dad0f5 --- /dev/null +++ b/wmadata.h @@ -0,0 +1,1453 @@ +/* + * WMA compatible decoder + * copyright (c) 2002 The FFmpeg Project + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** \file wmadata.h Various WMA tables. */ + +/** Contains huffcodes, huffbits and run/level tables. */ +struct coef_vlc_table { + /** Total number of codes. */ + int n; + /** VLC bit values. */ + const uint32_t *huffcodes; + /** VLC bit size. */ + const uint8_t *huffbits; + /* Table to build run/level tables. */ + const uint16_t *levels; +}; + +static const uint16_t wma_critical_freqs[25] = { + 100, 200, 300, 400, 510, 630, 770, 920, + 1080, 1270, 1480, 1720, 2000, 2320, 2700, 3150, + 3700, 4400, 5300, 6400, 7700, 9500, 12000, 15500, + 24500, +}; + +/* first value is number of bands */ +static const uint8_t exponent_band_22050[3][25] = { + {10, 4, 8, 4, 8, 8, 12, 20, 24, 24, 16,}, + {14, 4, 8, 8, 4, 12, 12, 16, 24, 16, 20, 24, 32, 40, 36,}, + {23, 4, 4, 4, 8, 4, 4, 8, 8, 8, 8, 8, 12, 12, 16, 16, 24, 24, 32, 44, + 48, 60, 84, 72,}, +}; + +static const uint8_t exponent_band_32000[3][25] = { + {11, 4, 4, 8, 4, 4, 12, 16, 24, 20, 28, 4,}, + {15, 4, 8, 4, 4, 8, 8, 16, 20, 12, 20, 20, 28, 40, 56, 8,}, + {16, 8, 4, 8, 8, 12, 16, 20, 24, 40, 32, 32, 44, 56, 80, 112, 16,}, +}; + +static const uint8_t exponent_band_44100[3][25] = { + {12, 4, 4, 4, 4, 4, 8, 8, 8, 12, 16, 20, 36,}, + {15, 4, 8, 4, 8, 8, 4, 8, 8, 12, 12, 12, 24, 28, 40, 76,}, + {17, 4, 8, 8, 4, 12, 12, 8, 8, 24, 16, 20, 24, 32, 40, 60, 80, 152,}, +}; + +static const uint16_t wma_hgain_huffcodes[37] = { + 0x00003, 0x002e7, 0x00001, 0x005cd, 0x0005d, 0x005c9, 0x0005e, 0x00003, + 0x00016, 0x0000b, 0x00001, 0x00006, 0x00001, 0x00006, 0x00004, 0x00005, + 0x00004, 0x00007, 0x00003, 0x00007, 0x00004, 0x0000a, 0x0000a, 0x00002, + 0x00003, 0x00000, 0x00005, 0x00002, 0x0005f, 0x00004, 0x00003, 0x00002, + 0x005c8, 0x000b8, 0x005ca, 0x005cb, 0x005cc, +}; + +static const uint8_t wma_hgain_huffbits[37] = { + 10, 12, 10, 13, 9, 13, 9, 8, + 7, 5, 5, 4, 4, 3, 3, 3, + 4, 3, 4, 4, 5, 5, 6, 8, + 7, 10, 8, 10, 9, 8, 9, 9, + 13, 10, 13, 13, 13, +}; + +#define NB_LSP_COEFS 10 + +static const float wma_lsp_codebook[NB_LSP_COEFS][16] = { + {1.98732877, 1.97944528, 1.97179088, 1.96260549, 1.95038374, 1.93336114, + 1.90719232, 1.86191415,}, + {1.97260000, 1.96083160, 1.94982586, 1.93806164, 1.92516608, 1.91010199, + 1.89232331, 1.87149812, + 1.84564818, 1.81358067, 1.77620070, 1.73265264, 1.67907855, 1.60959081, + 1.50829650, 1.33120330,}, + {1.90109110, 1.86482426, 1.83419671, 1.80168452, 1.76650116, 1.72816320, + 1.68502700, 1.63738256, + 1.58501580, 1.51795181, 1.43679906, 1.33950585, 1.24176208, 1.12260729, + 0.96749668, 0.74048265,}, + {1.76943864, 1.67822463, 1.59946365, 1.53560582, 1.47470796, 1.41210167, + 1.34509536, 1.27339507, + 1.19303814, 1.09765169, 0.98818722, 0.87239446, 0.74369172, 0.59768184, + 0.43168630, 0.17977021,}, + {1.43428349, 1.32038354, 1.21074086, 1.10577988, 1.00561746, 0.90335924, + 0.80437489, 0.70709671, + 0.60427395, 0.49814048, 0.38509539, 0.27106800, 0.14407416, 0.00219910, + -0.16725141, -0.36936085,}, + {0.99895687, 0.84188166, 0.70753739, 0.57906595, 0.47055563, 0.36966965, + 0.26826648, 0.17163380, + 0.07208392, -0.03062936, -1.40037388, -0.25128968, -0.37213937, + -0.51075646, -0.64887512, -0.80308031,}, + {0.26515280, 0.06313551, -0.08872080, -0.21103548, -0.31069678, + -0.39680323, -0.47223474, -0.54167135, + -0.61444740, -0.68943343, -0.76580211, -0.85170082, -0.95289061, + -1.06514703, -1.20510707, -1.37617746,}, + {-0.53940301, -0.73770929, -0.88424876, -1.01117930, -1.13389091, + -1.26830073, -1.42041987, -1.62033919, + -1.10158808, -1.16512566, -1.23337128, -1.30414401, -1.37663312, + -1.46853845, -1.57625798, -1.66893638,}, + {-0.38601997, -0.56009350, -0.66978483, -0.76028471, -0.83846064, + -0.90868087, -0.97408881, -1.03694962,}, + {-1.56144989, -1.65944032, -1.72689685, -1.77857740, -1.82203011, + -1.86220079, -1.90283983, -1.94820479,}, +}; + +static const uint32_t wma_scale_huffcodes[121] = { + 0x3ffe8, 0x3ffe6, 0x3ffe7, 0x3ffe5, 0x7fff5, 0x7fff1, 0x7ffed, 0x7fff6, + 0x7ffee, 0x7ffef, 0x7fff0, 0x7fffc, 0x7fffd, 0x7ffff, 0x7fffe, 0x7fff7, + 0x7fff8, 0x7fffb, 0x7fff9, 0x3ffe4, 0x7fffa, 0x3ffe3, 0x1ffef, 0x1fff0, + 0x0fff5, 0x1ffee, 0x0fff2, 0x0fff3, 0x0fff4, 0x0fff1, 0x07ff6, 0x07ff7, + 0x03ff9, 0x03ff5, 0x03ff7, 0x03ff3, 0x03ff6, 0x03ff2, 0x01ff7, 0x01ff5, + 0x00ff9, 0x00ff7, 0x00ff6, 0x007f9, 0x00ff4, 0x007f8, 0x003f9, 0x003f7, + 0x003f5, 0x001f8, 0x001f7, 0x000fa, 0x000f8, 0x000f6, 0x00079, 0x0003a, + 0x00038, 0x0001a, 0x0000b, 0x00004, 0x00000, 0x0000a, 0x0000c, 0x0001b, + 0x00039, 0x0003b, 0x00078, 0x0007a, 0x000f7, 0x000f9, 0x001f6, 0x001f9, + 0x003f4, 0x003f6, 0x003f8, 0x007f5, 0x007f4, 0x007f6, 0x007f7, 0x00ff5, + 0x00ff8, 0x01ff4, 0x01ff6, 0x01ff8, 0x03ff8, 0x03ff4, 0x0fff0, 0x07ff4, + 0x0fff6, 0x07ff5, 0x3ffe2, 0x7ffd9, 0x7ffda, 0x7ffdb, 0x7ffdc, 0x7ffdd, + 0x7ffde, 0x7ffd8, 0x7ffd2, 0x7ffd3, 0x7ffd4, 0x7ffd5, 0x7ffd6, 0x7fff2, + 0x7ffdf, 0x7ffe7, 0x7ffe8, 0x7ffe9, 0x7ffea, 0x7ffeb, 0x7ffe6, 0x7ffe0, + 0x7ffe1, 0x7ffe2, 0x7ffe3, 0x7ffe4, 0x7ffe5, 0x7ffd7, 0x7ffec, 0x7fff4, + 0x7fff3, +}; + +static const uint8_t wma_scale_huffbits[121] = { + 18, 18, 18, 18, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 18, 19, 18, 17, 17, + 16, 17, 16, 16, 16, 16, 15, 15, + 14, 14, 14, 14, 14, 14, 13, 13, + 12, 12, 12, 11, 12, 11, 10, 10, + 10, 9, 9, 8, 8, 8, 7, 6, + 6, 5, 4, 3, 1, 4, 4, 5, + 6, 6, 7, 7, 8, 8, 9, 9, + 10, 10, 10, 11, 11, 11, 11, 12, + 12, 13, 13, 13, 14, 14, 16, 15, + 16, 15, 18, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 19, + 19, +}; + +static const uint32_t coef0_huffcodes[666] = { + 0x00258, 0x0003d, 0x00000, 0x00005, 0x00008, 0x00008, 0x0000c, 0x0001b, + 0x0001f, 0x00015, 0x00024, 0x00032, 0x0003a, 0x00026, 0x0002c, 0x0002f, + 0x0004a, 0x0004d, 0x00061, 0x00070, 0x00073, 0x00048, 0x00052, 0x0005a, + 0x0005d, 0x0006e, 0x00099, 0x0009e, 0x000c1, 0x000ce, 0x000e4, 0x000f0, + 0x00093, 0x0009e, 0x000a2, 0x000a1, 0x000b8, 0x000d2, 0x000d3, 0x0012e, + 0x00130, 0x000de, 0x0012d, 0x0019b, 0x001e4, 0x00139, 0x0013a, 0x0013f, + 0x0014f, 0x0016d, 0x001a2, 0x0027c, 0x0027e, 0x00332, 0x0033c, 0x0033f, + 0x0038b, 0x00396, 0x003c5, 0x00270, 0x0027c, 0x0025a, 0x00395, 0x00248, + 0x004bd, 0x004fb, 0x00662, 0x00661, 0x0071b, 0x004e6, 0x004ff, 0x00666, + 0x0071c, 0x0071a, 0x0071f, 0x00794, 0x00536, 0x004e2, 0x0078e, 0x004ee, + 0x00518, 0x00535, 0x004fb, 0x0078d, 0x00530, 0x00680, 0x0068f, 0x005cb, + 0x00965, 0x006a6, 0x00967, 0x0097f, 0x00682, 0x006ae, 0x00cd0, 0x00e28, + 0x00f13, 0x00f1f, 0x009f5, 0x00cd3, 0x00f11, 0x00926, 0x00964, 0x00f32, + 0x00f12, 0x00f30, 0x00966, 0x00d0b, 0x00a68, 0x00b91, 0x009c7, 0x00b73, + 0x012fa, 0x0131d, 0x013f9, 0x01ca0, 0x0199c, 0x01c7a, 0x0198c, 0x01248, + 0x01c74, 0x01c64, 0x0139e, 0x012fd, 0x00a77, 0x012fc, 0x01c7b, 0x012ca, + 0x014cc, 0x014d2, 0x014e3, 0x014dc, 0x012dc, 0x03344, 0x02598, 0x0263c, + 0x0333b, 0x025e6, 0x01a1c, 0x01e3c, 0x014e2, 0x033d4, 0x01a11, 0x03349, + 0x03cce, 0x014e1, 0x01a34, 0x0273e, 0x02627, 0x0273f, 0x038ee, 0x03971, + 0x03c67, 0x03c61, 0x0333d, 0x038c2, 0x0263f, 0x038cd, 0x02638, 0x02e41, + 0x0351f, 0x03348, 0x03c66, 0x03562, 0x02989, 0x027d5, 0x0333c, 0x02e4f, + 0x0343b, 0x02ddf, 0x04bc8, 0x029c0, 0x02e57, 0x04c72, 0x025b7, 0x03547, + 0x03540, 0x029d3, 0x04c45, 0x025bb, 0x06600, 0x04c73, 0x04bce, 0x0357b, + 0x029a6, 0x029d2, 0x0263e, 0x0298a, 0x07183, 0x06602, 0x07958, 0x04b66, + 0x0537d, 0x05375, 0x04fe9, 0x04b67, 0x0799f, 0x04bc9, 0x051fe, 0x06a3b, + 0x05bb6, 0x04fa8, 0x0728f, 0x05376, 0x0492c, 0x0537e, 0x0795a, 0x06a3c, + 0x0e515, 0x07887, 0x0683a, 0x051f9, 0x051fd, 0x0cc6a, 0x06a8a, 0x0cc6d, + 0x05bb3, 0x0683b, 0x051fc, 0x05378, 0x0728e, 0x07886, 0x05bb7, 0x0f2a4, + 0x0795b, 0x0683c, 0x09fc1, 0x0683d, 0x0b752, 0x09678, 0x0a3e8, 0x06ac7, + 0x051f0, 0x0b759, 0x06af3, 0x04b6b, 0x0f2a0, 0x0f2ad, 0x096c3, 0x0e518, + 0x0b75c, 0x0d458, 0x0cc6b, 0x0537c, 0x067aa, 0x04fea, 0x0343a, 0x0cc71, + 0x0967f, 0x09fc4, 0x096c2, 0x0e516, 0x0f2a1, 0x0d45c, 0x0d45d, 0x0d45e, + 0x12fb9, 0x0967e, 0x1982f, 0x09883, 0x096c4, 0x0b753, 0x12fb8, 0x0f2a8, + 0x1ca21, 0x096c5, 0x0e51a, 0x1ca27, 0x12f3c, 0x0d471, 0x0f2aa, 0x0b75b, + 0x12fbb, 0x0f2a9, 0x0f2ac, 0x0d45a, 0x0b74f, 0x096c8, 0x16e91, 0x096ca, + 0x12fbf, 0x0d0a7, 0x13103, 0x0d516, 0x16e99, 0x12cbd, 0x0a3ea, 0x19829, + 0x0b755, 0x29ba7, 0x1ca28, 0x29ba5, 0x16e93, 0x1982c, 0x19828, 0x25994, + 0x0a3eb, 0x1ca29, 0x16e90, 0x1ca25, 0x1982d, 0x1ca26, 0x16e9b, 0x0b756, + 0x0967c, 0x25997, 0x0b75f, 0x198d3, 0x0b757, 0x19a2a, 0x0d45b, 0x0e517, + 0x1ca24, 0x1ca23, 0x1ca22, 0x0b758, 0x16e97, 0x0cd14, 0x13100, 0x00007, + 0x0003b, 0x0006b, 0x00097, 0x00138, 0x00125, 0x00173, 0x00258, 0x00335, + 0x0028e, 0x004c6, 0x00715, 0x00729, 0x004ef, 0x00519, 0x004ed, 0x00532, + 0x0068c, 0x00686, 0x00978, 0x00e5d, 0x00e31, 0x009f4, 0x00b92, 0x012f8, + 0x00d06, 0x00a67, 0x00d44, 0x00a76, 0x00d59, 0x012cd, 0x01c78, 0x01c75, + 0x0199f, 0x0198f, 0x01c67, 0x014c6, 0x01c79, 0x01c76, 0x00b94, 0x00d1b, + 0x01e32, 0x01e31, 0x01ab0, 0x01a05, 0x01aa1, 0x0333a, 0x025e5, 0x02626, + 0x03541, 0x03544, 0x03421, 0x03546, 0x02e55, 0x02e56, 0x0492d, 0x02dde, + 0x0299b, 0x02ddc, 0x0357a, 0x0249c, 0x0668b, 0x1c77f, 0x1ca20, 0x0d45f, + 0x09886, 0x16e9a, 0x0f2a7, 0x0b751, 0x0a3ee, 0x0cf59, 0x0cf57, 0x0b754, + 0x0d0a6, 0x16e98, 0x0b760, 0x06ac6, 0x0a3f0, 0x12fbe, 0x13104, 0x0f2a5, + 0x0a3ef, 0x0d472, 0x12cba, 0x1982e, 0x16e9c, 0x1c77e, 0x198d0, 0x13105, + 0x16e92, 0x0b75d, 0x0d459, 0x0001a, 0x000c0, 0x0016c, 0x003cd, 0x00350, + 0x0067b, 0x0051e, 0x006a9, 0x009f4, 0x00b72, 0x00d09, 0x01249, 0x01e3d, + 0x01ca1, 0x01a1f, 0x01721, 0x01a8a, 0x016e8, 0x03347, 0x01a35, 0x0249d, + 0x0299a, 0x02596, 0x02e4e, 0x0298b, 0x07182, 0x04c46, 0x025ba, 0x02e40, + 0x027d6, 0x04fe8, 0x06607, 0x05310, 0x09884, 0x072e1, 0x06a3d, 0x04b6a, + 0x04c7a, 0x06603, 0x04c7b, 0x03428, 0x06605, 0x09664, 0x09fc0, 0x071de, + 0x06601, 0x05bb2, 0x09885, 0x0a3e2, 0x1c61f, 0x12cbb, 0x0b750, 0x0cf58, + 0x0967d, 0x25995, 0x668ad, 0x0b75a, 0x09fc2, 0x0537f, 0x0b75e, 0x13fae, + 0x12fbc, 0x00031, 0x001c4, 0x004c5, 0x005b8, 0x00cf4, 0x0096f, 0x00d46, + 0x01e57, 0x01a04, 0x02625, 0x03346, 0x028f9, 0x04c47, 0x072e0, 0x04b69, + 0x03420, 0x07957, 0x06639, 0x0799e, 0x07959, 0x07881, 0x04b68, 0x09fc3, + 0x09fd6, 0x0cc70, 0x0a3f1, 0x12cbe, 0x0e30e, 0x0e51b, 0x06af2, 0x12cbc, + 0x1c77d, 0x0f2ab, 0x12fbd, 0x1aa2f, 0x0a3ec, 0x0d473, 0x05377, 0x0a3e9, + 0x1982b, 0x0e300, 0x12f3f, 0x0cf5f, 0x096c0, 0x38c3c, 0x16e94, 0x16e95, + 0x12f3d, 0x29ba4, 0x29ba6, 0x1c77c, 0x6a8ba, 0x3545c, 0x33457, 0x668ac, + 0x6a8bb, 0x16e9d, 0x0e519, 0x25996, 0x12f3e, 0x00036, 0x0033e, 0x006ad, + 0x00d03, 0x012c8, 0x0124a, 0x03c42, 0x03ccd, 0x06606, 0x07880, 0x06852, + 0x06a3a, 0x05bb4, 0x0f2a2, 0x09fc7, 0x12cb9, 0x0cc6c, 0x0a6e8, 0x096c1, + 0x0004a, 0x00355, 0x012f9, 0x014e8, 0x01abe, 0x025b6, 0x0492e, 0x09fc6, + 0x051ff, 0x0cc6f, 0x096cb, 0x0d071, 0x198d1, 0x12cb8, 0x38c3d, 0x13faf, + 0x096c9, 0x0009d, 0x00539, 0x012ce, 0x0341f, 0x029c1, 0x04b33, 0x0a3e3, + 0x0d070, 0x16e96, 0x0b763, 0x000a0, 0x009ce, 0x038cc, 0x0343d, 0x051fa, + 0x09888, 0x12fba, 0x000df, 0x00a75, 0x029a7, 0x09fc5, 0x0e301, 0x0967b, + 0x001e7, 0x012c9, 0x051fb, 0x09889, 0x0f2a6, 0x0016f, 0x01cb9, 0x0cf5a, + 0x12cbf, 0x09679, 0x00272, 0x01a15, 0x0967a, 0x003cb, 0x025f6, 0x0b762, + 0x0028d, 0x03c60, 0x0cf5e, 0x00352, 0x03ccc, 0x0072f, 0x07186, 0x004ec, + 0x05379, 0x0068e, 0x09887, 0x006a7, 0x06af1, 0x00e29, 0x0cf5b, 0x00f31, + 0x0d470, 0x009c6, 0x013fb, 0x13102, 0x019a5, 0x13101, 0x01983, 0x01c65, + 0x0124f, 0x014c7, 0x01726, 0x01abf, 0x03304, 0x02624, 0x03c41, 0x027d7, + 0x02ddd, 0x02e54, 0x0343c, 0x06604, 0x07181, 0x0663a, 0x04fa9, 0x0663b, + 0x05311, 0x0537a, 0x06839, 0x05bb5, 0x0492f, 0x06af0, 0x096c7, 0x0cc6e, + 0x0537b, 0x0cf5c, 0x0cf56, 0x198d2, 0x0cf5d, 0x0a3ed, 0x0f2a3, 0x1982a, + 0x0b761, 0x096c6, +}; + +static const uint8_t coef0_huffbits[666] = { + 11, 6, 2, 3, 4, 5, 5, 5, + 5, 6, 6, 6, 6, 7, 7, 7, + 7, 7, 7, 7, 7, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 11, 11, 11, 10, 11, + 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 12, 12, 11, 12, + 12, 12, 12, 11, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 13, 13, 12, + 12, 12, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 14, + 13, 13, 13, 13, 13, 13, 13, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 13, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 15, + 15, 14, 14, 15, 15, 15, 14, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 14, 15, 15, 15, 15, 16, + 16, 16, 15, 16, 15, 15, 16, 16, + 16, 16, 15, 16, 16, 16, 15, 16, + 16, 15, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 15, 15, 16, 16, + 15, 16, 16, 16, 17, 17, 17, 16, + 16, 17, 16, 16, 16, 16, 17, 16, + 17, 17, 16, 16, 15, 15, 15, 16, + 17, 16, 17, 16, 16, 17, 17, 17, + 17, 17, 17, 16, 17, 17, 17, 16, + 17, 17, 16, 17, 17, 17, 16, 17, + 17, 16, 16, 17, 17, 17, 18, 17, + 17, 17, 17, 17, 18, 18, 17, 17, + 17, 19, 17, 19, 18, 17, 17, 18, + 17, 17, 18, 17, 17, 17, 18, 17, + 17, 18, 17, 17, 17, 17, 17, 16, + 17, 17, 17, 17, 18, 16, 17, 4, + 6, 8, 9, 9, 10, 10, 10, 10, + 11, 11, 11, 11, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 14, 13, 13, 13, 13, + 13, 13, 14, 14, 14, 14, 14, 14, + 15, 15, 15, 15, 15, 15, 16, 15, + 15, 15, 15, 15, 15, 17, 17, 17, + 16, 18, 16, 17, 17, 16, 16, 17, + 17, 18, 17, 16, 17, 17, 17, 16, + 17, 17, 18, 17, 18, 17, 17, 17, + 18, 17, 17, 5, 8, 10, 10, 11, + 11, 12, 12, 12, 13, 13, 14, 13, + 13, 14, 14, 14, 14, 14, 14, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 16, 16, 15, 16, 16, + 15, 15, 15, 15, 15, 16, 16, 15, + 15, 16, 16, 17, 17, 18, 17, 16, + 17, 18, 19, 17, 16, 16, 17, 17, + 17, 6, 9, 11, 12, 12, 13, 13, + 13, 14, 14, 14, 15, 15, 15, 16, + 15, 15, 15, 15, 15, 15, 16, 16, + 16, 16, 17, 18, 16, 16, 16, 18, + 17, 16, 17, 18, 17, 17, 16, 17, + 17, 16, 17, 16, 17, 18, 18, 18, + 17, 19, 19, 17, 20, 19, 18, 19, + 20, 18, 16, 18, 17, 7, 10, 12, + 13, 13, 14, 14, 14, 15, 15, 16, + 16, 16, 16, 16, 18, 16, 17, 17, + 8, 11, 13, 14, 14, 15, 16, 16, + 16, 16, 17, 17, 17, 18, 18, 17, + 17, 8, 12, 14, 15, 15, 15, 17, + 17, 18, 17, 9, 12, 14, 15, 16, + 16, 17, 9, 13, 15, 16, 16, 17, + 9, 13, 16, 16, 16, 10, 13, 16, + 18, 17, 10, 14, 17, 10, 14, 17, + 11, 14, 16, 11, 14, 11, 15, 12, + 16, 12, 16, 12, 16, 12, 16, 12, + 17, 13, 13, 17, 13, 17, 13, 13, + 14, 14, 14, 14, 14, 14, 14, 15, + 15, 15, 15, 15, 15, 15, 16, 15, + 16, 16, 16, 16, 16, 16, 17, 16, + 16, 16, 16, 17, 16, 17, 16, 17, + 17, 17, +}; + +static const uint32_t coef1_huffcodes[555] = { + 0x00115, 0x00002, 0x00001, 0x00000, 0x0000d, 0x00007, 0x00013, 0x0001d, + 0x00008, 0x0000c, 0x00023, 0x0002b, 0x0003f, 0x00017, 0x0001b, 0x00043, + 0x00049, 0x00050, 0x00055, 0x00054, 0x00067, 0x00064, 0x0007b, 0x0002d, + 0x00028, 0x0002a, 0x00085, 0x00089, 0x0002b, 0x00035, 0x00090, 0x00091, + 0x00094, 0x00088, 0x000c1, 0x000c6, 0x000f2, 0x000e3, 0x000c5, 0x000e2, + 0x00036, 0x000f0, 0x000a7, 0x000cd, 0x000fb, 0x00059, 0x00116, 0x00103, + 0x00108, 0x0012b, 0x0012d, 0x00188, 0x0012e, 0x0014c, 0x001c3, 0x00187, + 0x001e7, 0x0006f, 0x00094, 0x00069, 0x001e6, 0x001ca, 0x00147, 0x00195, + 0x000a7, 0x00213, 0x00209, 0x00303, 0x00295, 0x00289, 0x0028c, 0x0028d, + 0x00312, 0x00330, 0x0029b, 0x00308, 0x00328, 0x0029a, 0x0025e, 0x003c5, + 0x00384, 0x0039f, 0x00397, 0x00296, 0x0032e, 0x00332, 0x003c6, 0x003e6, + 0x0012d, 0x000d1, 0x00402, 0x000dd, 0x00161, 0x0012b, 0x00127, 0x0045d, + 0x00601, 0x004ab, 0x0045f, 0x00410, 0x004bf, 0x00528, 0x0045c, 0x00424, + 0x00400, 0x00511, 0x00618, 0x0073d, 0x0063a, 0x00614, 0x0073c, 0x007c0, + 0x007cf, 0x00802, 0x00966, 0x00964, 0x00951, 0x008a0, 0x00346, 0x00803, + 0x00a52, 0x0024a, 0x007c1, 0x0063f, 0x00126, 0x00406, 0x00789, 0x008a2, + 0x00960, 0x00967, 0x00c05, 0x00c70, 0x00c79, 0x00a5d, 0x00c26, 0x00c4d, + 0x00372, 0x008a5, 0x00c08, 0x002c5, 0x00f11, 0x00cc4, 0x00f8e, 0x00e16, + 0x00496, 0x00e77, 0x00f9c, 0x00c25, 0x00f1e, 0x00c27, 0x00f1f, 0x00e17, + 0x00ccd, 0x00355, 0x00c09, 0x00c78, 0x00f90, 0x00521, 0x00357, 0x00356, + 0x0068e, 0x00f9d, 0x00c04, 0x00e58, 0x00a20, 0x00a2c, 0x00c4c, 0x0052f, + 0x00f8d, 0x01178, 0x01053, 0x01097, 0x0180f, 0x0180d, 0x012fb, 0x012aa, + 0x0202a, 0x00a40, 0x018ed, 0x01ceb, 0x01455, 0x018e3, 0x012a1, 0x00354, + 0x00353, 0x00f1c, 0x00c7b, 0x00c37, 0x0101d, 0x012cb, 0x01142, 0x0197d, + 0x01095, 0x01e3b, 0x0186b, 0x00588, 0x01c2a, 0x014b8, 0x01e3a, 0x018ec, + 0x01f46, 0x012fa, 0x00a53, 0x01ce8, 0x00a55, 0x01c29, 0x0117b, 0x01052, + 0x012a0, 0x00589, 0x00950, 0x01c2b, 0x00a50, 0x0208b, 0x0180e, 0x02027, + 0x02556, 0x01e20, 0x006e7, 0x01c28, 0x0197a, 0x00684, 0x020a2, 0x01f22, + 0x03018, 0x039cf, 0x03e25, 0x02557, 0x0294c, 0x028a6, 0x00d11, 0x028a9, + 0x02979, 0x00d46, 0x00a56, 0x039ce, 0x030cc, 0x0329a, 0x0149d, 0x0510f, + 0x0451c, 0x02028, 0x03299, 0x01ced, 0x014b9, 0x00f85, 0x00c7a, 0x01800, + 0x00341, 0x012ca, 0x039c8, 0x0329d, 0x00d0d, 0x03e20, 0x05144, 0x00d45, + 0x030d0, 0x0186d, 0x030d5, 0x00d0f, 0x00d40, 0x04114, 0x020a1, 0x0297f, + 0x03e24, 0x032f1, 0x04047, 0x030d4, 0x028a8, 0x00d0e, 0x0451d, 0x04044, + 0x0297e, 0x04042, 0x030d2, 0x030cf, 0x03e21, 0x03e26, 0x028a5, 0x0451a, + 0x00d48, 0x01a16, 0x00d44, 0x04518, 0x0149b, 0x039ca, 0x01498, 0x0403d, + 0x0451b, 0x0149c, 0x032f3, 0x030cb, 0x08073, 0x03e22, 0x0529a, 0x020aa, + 0x039cc, 0x0738a, 0x06530, 0x07389, 0x06193, 0x08071, 0x04043, 0x030ce, + 0x05147, 0x07388, 0x05145, 0x08072, 0x04521, 0x00d47, 0x0297c, 0x030cd, + 0x030ca, 0x0000b, 0x0000c, 0x00083, 0x000e4, 0x00048, 0x00102, 0x001cc, + 0x001f5, 0x00097, 0x0020b, 0x00124, 0x00453, 0x00627, 0x00639, 0x00605, + 0x00517, 0x001b8, 0x00663, 0x00667, 0x007c3, 0x00823, 0x00961, 0x00963, + 0x00e5a, 0x00e59, 0x00a2b, 0x00cbf, 0x00292, 0x00a2d, 0x007d0, 0x00953, + 0x00cc5, 0x00f84, 0x004ab, 0x014a7, 0x0068a, 0x0117a, 0x0052e, 0x01442, + 0x0052c, 0x00c77, 0x00f8f, 0x004aa, 0x01094, 0x01801, 0x012c4, 0x0297b, + 0x00952, 0x01f19, 0x006a5, 0x01149, 0x012c5, 0x01803, 0x022f2, 0x0329b, + 0x04520, 0x0149e, 0x00d13, 0x01f16, 0x01ce9, 0x0101c, 0x006e6, 0x039c9, + 0x06191, 0x07c8e, 0x06192, 0x0ca63, 0x039cd, 0x06190, 0x06884, 0x06885, + 0x07382, 0x00d49, 0x00d41, 0x0450c, 0x0149a, 0x030d1, 0x08077, 0x03e23, + 0x01a15, 0x0e701, 0x0e702, 0x08079, 0x0822a, 0x0a218, 0x07887, 0x0403f, + 0x0520b, 0x0529b, 0x0e700, 0x04519, 0x00007, 0x000e0, 0x000d0, 0x0039b, + 0x003e5, 0x00163, 0x0063e, 0x007c9, 0x00806, 0x00954, 0x01044, 0x01f44, + 0x0197c, 0x01f45, 0x00a51, 0x01f47, 0x00951, 0x0052d, 0x02291, 0x0092f, + 0x00a54, 0x00d12, 0x0297d, 0x00d0c, 0x01499, 0x0329e, 0x032f0, 0x02025, + 0x039c6, 0x00a57, 0x03e46, 0x00d42, 0x0738b, 0x05146, 0x04046, 0x08078, + 0x0510e, 0x07886, 0x02904, 0x04156, 0x04157, 0x06032, 0x030d3, 0x08bce, + 0x04040, 0x0403e, 0x0a414, 0x10457, 0x08075, 0x06887, 0x07c8f, 0x039c7, + 0x07387, 0x08070, 0x08bcf, 0x1482a, 0x10456, 0x1482b, 0x01a17, 0x06886, + 0x0450d, 0x00013, 0x0006b, 0x00615, 0x0080b, 0x0082b, 0x00952, 0x00e5b, + 0x018e2, 0x0186c, 0x01f18, 0x0329f, 0x00d43, 0x03e29, 0x05140, 0x05141, + 0x0ca62, 0x06033, 0x03c42, 0x03e28, 0x0450f, 0x0a21a, 0x07384, 0x0a219, + 0x0e703, 0x0a21b, 0x01a14, 0x07383, 0x045e6, 0x0007a, 0x0012c, 0x00ccc, + 0x0068f, 0x01802, 0x00a52, 0x00953, 0x04045, 0x01a20, 0x0451f, 0x000a4, + 0x00735, 0x01cec, 0x02029, 0x020a3, 0x0451e, 0x00069, 0x00c24, 0x02024, + 0x032f2, 0x05142, 0x00196, 0x00523, 0x000a6, 0x0197b, 0x0030b, 0x0092e, + 0x003e9, 0x03e27, 0x00160, 0x05143, 0x00652, 0x04041, 0x00734, 0x028a7, + 0x0080f, 0x01483, 0x0097c, 0x00340, 0x0068b, 0x00522, 0x01054, 0x01096, + 0x01f17, 0x0202b, 0x01cea, 0x020a0, 0x02978, 0x02026, 0x0297a, 0x039cb, + 0x03e2b, 0x0149f, 0x0329c, 0x07385, 0x08074, 0x0450e, 0x03e2a, 0x05149, + 0x08076, 0x07386, 0x05148, +}; + +static const uint8_t coef1_huffbits[555] = { + 9, 5, 2, 4, 4, 5, 5, 5, + 6, 6, 6, 6, 6, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 8, + 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, + 9, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, + 9, 10, 10, 10, 9, 9, 9, 9, + 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, + 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, + 11, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 11, 11, 11, 11, 11, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 13, 12, 12, 12, 12, 12, 12, 12, + 13, 12, 12, 12, 12, 12, 12, 12, + 12, 13, 12, 12, 12, 13, 13, 13, + 13, 12, 12, 12, 12, 12, 12, 13, + 12, 13, 13, 13, 13, 13, 13, 13, + 14, 14, 13, 13, 13, 13, 13, 13, + 13, 12, 12, 12, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 14, 13, 14, 13, 13, 13, + 13, 13, 14, 13, 14, 14, 13, 14, + 14, 13, 14, 13, 13, 14, 14, 13, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 15, 14, 14, 14, 14, 15, 15, + 15, 14, 14, 13, 13, 12, 12, 13, + 13, 13, 14, 14, 15, 14, 15, 15, + 14, 13, 14, 15, 15, 15, 14, 14, + 14, 14, 15, 14, 14, 15, 15, 15, + 14, 15, 14, 14, 14, 14, 14, 15, + 15, 16, 15, 15, 15, 14, 15, 15, + 15, 15, 14, 14, 16, 14, 15, 14, + 14, 15, 15, 15, 15, 16, 15, 14, + 15, 15, 15, 16, 15, 15, 14, 14, + 14, 4, 7, 8, 8, 9, 9, 9, + 9, 10, 10, 11, 11, 11, 11, 11, + 11, 12, 11, 11, 11, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 11, 12, + 12, 12, 13, 13, 13, 13, 13, 13, + 13, 12, 12, 13, 13, 13, 13, 14, + 14, 13, 14, 13, 13, 13, 14, 14, + 15, 15, 14, 13, 13, 13, 14, 14, + 15, 15, 15, 16, 14, 15, 17, 17, + 15, 15, 15, 15, 15, 14, 16, 14, + 16, 16, 16, 16, 16, 16, 15, 15, + 17, 15, 16, 15, 6, 8, 10, 10, + 10, 11, 11, 11, 12, 12, 13, 13, + 13, 13, 14, 13, 14, 13, 14, 14, + 14, 14, 14, 15, 15, 14, 14, 14, + 14, 14, 14, 15, 15, 15, 15, 16, + 15, 15, 16, 15, 15, 15, 14, 16, + 15, 15, 18, 17, 16, 17, 15, 14, + 15, 16, 16, 19, 17, 19, 16, 17, + 15, 7, 10, 11, 12, 12, 12, 12, + 13, 13, 13, 14, 15, 14, 15, 15, + 16, 15, 14, 14, 15, 16, 15, 16, + 16, 16, 16, 15, 15, 7, 11, 12, + 13, 13, 14, 14, 15, 15, 15, 8, + 11, 13, 14, 14, 15, 9, 12, 14, + 14, 15, 9, 13, 10, 13, 10, 14, + 10, 14, 11, 15, 11, 15, 11, 14, + 12, 15, 12, 13, 13, 13, 13, 13, + 13, 14, 13, 14, 14, 14, 14, 14, + 14, 15, 14, 15, 16, 15, 14, 15, + 16, 15, 15, +}; + +static const uint32_t coef2_huffcodes[1336] = { + 0x003e6, 0x000f6, 0x00000, 0x00002, 0x00006, 0x0000f, 0x0001b, 0x00028, + 0x00039, 0x0003f, 0x0006b, 0x00076, 0x000b7, 0x000e8, 0x000ef, 0x00169, + 0x001a7, 0x001d4, 0x001dc, 0x002c4, 0x00349, 0x00355, 0x00391, 0x003dc, + 0x00581, 0x005b2, 0x00698, 0x0070c, 0x00755, 0x0073a, 0x00774, 0x007cf, + 0x00b0a, 0x00b66, 0x00d2e, 0x00d5e, 0x00e1b, 0x00eac, 0x00e5a, 0x00f7e, + 0x00fa1, 0x0163e, 0x01a37, 0x01a52, 0x01c39, 0x01ab3, 0x01d5f, 0x01cb6, + 0x01f52, 0x01dd9, 0x02c04, 0x02c2e, 0x02c2d, 0x02c23, 0x03467, 0x034a3, + 0x0351b, 0x03501, 0x03a5d, 0x0351c, 0x03875, 0x03dea, 0x0397b, 0x039db, + 0x03df1, 0x039d8, 0x03bb4, 0x0580a, 0x0584d, 0x05842, 0x05b13, 0x058ea, + 0x0697d, 0x06a06, 0x068cc, 0x06ac7, 0x06a96, 0x072f4, 0x07543, 0x072b4, + 0x07d20, 0x0b003, 0x073b5, 0x07be6, 0x0d180, 0x07bd1, 0x07cb8, 0x07d06, + 0x07d25, 0x0d2f2, 0x0d19a, 0x0d334, 0x0e1dc, 0x0d529, 0x0d584, 0x0e1d2, + 0x0e5e3, 0x0eec4, 0x0e564, 0x0fa49, 0x16001, 0x0eedc, 0x0f7fa, 0x1a32c, + 0x16131, 0x16003, 0x0f9c8, 0x1ef80, 0x1d2a0, 0x1aa4b, 0x0f7ce, 0x1abfe, + 0x1aa50, 0x1a458, 0x1a816, 0x1cae4, 0x1d2fe, 0x1d52e, 0x1aa4c, 0x2c245, + 0x1d2a1, 0x1a35d, 0x1ca1b, 0x1d5d8, 0x1f531, 0x1ca1c, 0x1f389, 0x1f4af, + 0x3a5e7, 0x351fb, 0x2c24b, 0x34bce, 0x2c24d, 0x2c249, 0x2c24a, 0x72dfc, + 0x357ef, 0x35002, 0x3a5e6, 0x39431, 0x5843b, 0x34a77, 0x58431, 0x3a5f3, + 0x3a5dd, 0x3e5e5, 0x356bd, 0x3976e, 0x6a3d2, 0x3500d, 0x694c4, 0x580bd, + 0x3e5e8, 0x74b95, 0x34a6e, 0x3977c, 0x39432, 0x5b0d2, 0x6a3d8, 0x580b8, + 0x5b0cb, 0x5b0d7, 0x72dee, 0x72ded, 0x72dec, 0x74b9c, 0x3977f, 0x72dea, + 0x74b9e, 0x7be7d, 0x580bf, 0x5b0d5, 0x7cba8, 0x74b91, 0x3e5dd, 0xb6171, + 0xd46b3, 0xd46b9, 0x7cba1, 0x74b9f, 0x72de1, 0xe59f5, 0x3e5eb, 0x00004, + 0x00015, 0x00038, 0x00075, 0x000e8, 0x001d3, 0x00347, 0x0039c, 0x00690, + 0x0074a, 0x00b60, 0x00e93, 0x00f74, 0x0163d, 0x01a5a, 0x01d24, 0x01cbe, + 0x01f4b, 0x03468, 0x03562, 0x03947, 0x03e82, 0x05804, 0x05b12, 0x05803, + 0x0696d, 0x06a9e, 0x0697c, 0x06978, 0x06afb, 0x074b2, 0x072f5, 0x073c0, + 0x07541, 0x06944, 0x074b7, 0x070d3, 0x07ba9, 0x0b0b1, 0x0d1af, 0x0e1dd, + 0x0e5e2, 0x0e1a3, 0x0eec3, 0x1612f, 0x0e961, 0x0eeda, 0x0e78e, 0x0fa48, + 0x1612c, 0x0e511, 0x0e565, 0x0e953, 0x1aa4a, 0x0e59d, 0x1d52c, 0x1a811, + 0x1cae7, 0x1abfc, 0x1d52d, 0x1cacf, 0x1cf05, 0x2c254, 0x34a72, 0x1f4ac, + 0x3976b, 0x34a71, 0x2c6d9, 0x2d873, 0x34a6a, 0x357e7, 0x3464c, 0x3e5f5, + 0x58433, 0x1f53a, 0x3500a, 0x357ea, 0x34a73, 0x3942f, 0x357e5, 0x39775, + 0x694cd, 0x39772, 0x7cba5, 0x6a3ef, 0x35483, 0x74b98, 0x5b0c1, 0x39770, + 0x3a5d7, 0x39433, 0x39434, 0x694ce, 0x580be, 0x3e5ff, 0x6a3ec, 0xb616f, + 0xd46b1, 0x6a3d1, 0x72de5, 0x74b6e, 0x72de9, 0x3e700, 0xd46b6, 0x6a3e9, + 0x74b69, 0xe5675, 0xd46b8, 0x7cbaa, 0x3a5d1, 0x0000c, 0x0003c, 0x000eb, + 0x001f1, 0x003a4, 0x006a8, 0x007d5, 0x00d43, 0x00e77, 0x016c5, 0x01cb1, + 0x02c5d, 0x03a55, 0x03a56, 0x03e51, 0x03bb5, 0x05b0a, 0x06a9f, 0x074b8, + 0x07d28, 0x0d187, 0x0d40e, 0x0d52e, 0x0d425, 0x0eae3, 0x0e1d3, 0x1612e, + 0x0e59e, 0x0eec2, 0x0e578, 0x0e51a, 0x0e579, 0x0e515, 0x0e960, 0x0d183, + 0x0d220, 0x0d2cb, 0x0e512, 0x16c3e, 0x16002, 0x16c42, 0x1cae9, 0x3461a, + 0x1d2fa, 0x1a308, 0x1a849, 0x1cf07, 0x1f38f, 0x34b65, 0x2c253, 0x1ef9e, + 0x1cbc3, 0x1cbc1, 0x2c255, 0x1f384, 0x58435, 0x2c5cd, 0x3a5f7, 0x2c252, + 0x3959c, 0x2c6d8, 0x3a5d3, 0x6ad78, 0x6a3f2, 0x7cba9, 0xb6176, 0x72deb, + 0x39764, 0x3e5f6, 0x3a5d8, 0x74a8c, 0x6a3e6, 0x694d1, 0x6ad79, 0x1a4592, + 0xe59fb, 0x7cbb3, 0x5b0cd, 0x00017, 0x000b5, 0x002c3, 0x005b7, 0x00b1c, + 0x00e5c, 0x0163f, 0x01ab2, 0x01efa, 0x0348a, 0x0396e, 0x058da, 0x06963, + 0x06a30, 0x072cd, 0x073cf, 0x07ce7, 0x0d2ca, 0x0d2d8, 0x0e764, 0x0e794, + 0x16008, 0x16167, 0x1617e, 0x1aa49, 0x1a30b, 0x1a813, 0x2c6da, 0x1a580, + 0x1cbc2, 0x0f9ca, 0x1617f, 0x1d2fe, 0x0f7fc, 0x16c40, 0x0e513, 0x0eec5, + 0x0f7c3, 0x1d508, 0x1a81e, 0x1d2fd, 0x39430, 0x35486, 0x3e5fd, 0x2c24c, + 0x2c75a, 0x34a74, 0x3a5f4, 0x3464d, 0x694ca, 0x3a5f1, 0x1d509, 0x1d5c0, + 0x34648, 0x3464e, 0x6a3d5, 0x6a3e8, 0x6a3e7, 0x5b0c3, 0x2c248, 0x1f38a, + 0x3a5f2, 0x6a3e5, 0x00029, 0x00168, 0x0058c, 0x00b67, 0x00f9d, 0x01c3d, + 0x01cbf, 0x02c20, 0x0351d, 0x03df6, 0x06af9, 0x072b5, 0x0b1d7, 0x0b0b2, + 0x0d40a, 0x0d52b, 0x0e952, 0x0e797, 0x163c3, 0x1c3a0, 0x1f386, 0x1ca21, + 0x34655, 0x2c247, 0x1f53b, 0x2c250, 0x2c24f, 0x1f385, 0x1ef5d, 0x1cf15, + 0x1caea, 0x1ab0a, 0x1cf19, 0x1f53d, 0x1d5c2, 0x1d2fb, 0x1ef58, 0x34a78, + 0x357ec, 0x1f533, 0x3a5e1, 0x694d2, 0x58482, 0x3a5ee, 0x2c6dc, 0x357eb, + 0x5b0c4, 0x39778, 0x6a3e1, 0x7cbb4, 0x3a5e1, 0x74b68, 0x3a5ef, 0x3a5d2, + 0x39424, 0x72de2, 0xe59f6, 0xe59f7, 0x3e702, 0x3e5ec, 0x1f38b, 0x0003b, + 0x001f0, 0x00777, 0x00fa8, 0x01cb2, 0x02d84, 0x03a57, 0x03dd6, 0x06917, + 0x06a11, 0x07d07, 0x0eae2, 0x0e796, 0x0f9c9, 0x0f7fb, 0x16166, 0x16160, + 0x1ab1b, 0x1abfa, 0x2d87b, 0x1d2f7, 0x39768, 0x1f38c, 0x34653, 0x34651, + 0x6a3d9, 0x35001, 0x3abbd, 0x38742, 0x39426, 0x34a76, 0x3a5ec, 0x34a75, + 0x35000, 0x35488, 0x1cf10, 0x2c6db, 0x357ed, 0x357e8, 0x357e9, 0x3a5f0, + 0x694c2, 0xb6178, 0x72df5, 0x39425, 0x3942b, 0x74b6d, 0x74b6f, 0xb6177, + 0xb6179, 0x74b6a, 0xb6172, 0x58487, 0x3e5ee, 0x3e5ed, 0x72df2, 0x72df4, + 0x7cbae, 0x6a3ca, 0x70e86, 0x34bcf, 0x6a3c8, 0x00059, 0x00384, 0x00d5b, + 0x01c38, 0x03560, 0x0395b, 0x0584e, 0x06964, 0x073cd, 0x0b1e7, 0x0e798, + 0x0e78d, 0x0fa43, 0x1a848, 0x1a32f, 0x1aa4e, 0x3464a, 0x1f4ab, 0x1f38d, + 0x3a5eb, 0x3a5d4, 0x3548a, 0x6a3c7, 0x5b0d0, 0x6a3c5, 0x7cbb0, 0x694cb, + 0x3a5e5, 0x3e5e2, 0x3942c, 0x2d872, 0x1f4ae, 0x3a5d5, 0x694d3, 0x58481, + 0x35009, 0x39774, 0x58432, 0xb616c, 0x5b0db, 0x3548b, 0xb6174, 0x1d5d95, + 0xb004c, 0x7cbb2, 0x3a5e5, 0x74a8f, 0xe59f9, 0x72df6, 0xe59fd, 0x7cbad, + 0xd427d, 0x72cff, 0x3977a, 0x5b0d9, 0xb616d, 0xb616b, 0x1a4593, 0x7cbaf, + 0x5b0da, 0x00071, 0x003eb, 0x01603, 0x02c6c, 0x03961, 0x068c8, 0x06a31, + 0x072bd, 0x0d2c2, 0x0e51b, 0x0e5e6, 0x1abfb, 0x1d2ff, 0x1cae5, 0x1ef5c, + 0x1ef5e, 0x1cf13, 0x34a6d, 0x3976d, 0xb616a, 0x3e5f2, 0x6a3c4, 0xb6169, + 0x3e5dc, 0x580b9, 0x74b99, 0x75764, 0x58434, 0x3a5d9, 0x6945a, 0x69459, + 0x3548c, 0x3a5e9, 0x69457, 0x72df1, 0x6945e, 0x6a35e, 0x3e701, 0xb6168, + 0x5b0dd, 0x3a5de, 0x6a3c2, 0xd4278, 0x6a3cc, 0x72dfd, 0xb6165, 0x16009a, + 0x7cbb1, 0xd427c, 0xb6162, 0xe765e, 0x1cecbe, 0x7cbb6, 0x69454, 0xb6160, + 0xd427a, 0x1d5d96, 0xb1d6d, 0xe59f4, 0x72de8, 0x3a5db, 0x0007a, 0x006ae, + 0x01c3c, 0x03aba, 0x058e9, 0x072cc, 0x0d2dd, 0x0d22d, 0x0eec1, 0x0eedb, + 0x1d2a2, 0x1ef5b, 0x357e2, 0x3abbf, 0x1d2f9, 0x35004, 0x3a5dc, 0x351fc, + 0x3976c, 0x6a3c6, 0x6a3cb, 0x3e5ea, 0xe59f3, 0x6a3ce, 0x69452, 0xe59f0, + 0x74b90, 0xd4279, 0xd427b, 0x7cbb5, 0x5b0c5, 0x3a5e3, 0x3a5e2, 0x000d0, + 0x00775, 0x01efe, 0x03dd5, 0x0728c, 0x07cb9, 0x0e1a2, 0x0ea85, 0x0eed8, + 0x1a30a, 0x1aa4f, 0x3a5df, 0x35008, 0x3a5e0, 0x3e5f4, 0x3e5f7, 0xb1d6c, + 0x5843e, 0x34a70, 0x72df8, 0x74b6b, 0xd427f, 0x72df0, 0x5b0bf, 0x5b0c0, + 0xd46b0, 0x72def, 0xe59f8, 0x162e64, 0xb1d6f, 0x3a5e0, 0x39427, 0x69166, + 0x6a3e2, 0x6a3e3, 0x74a8d, 0xd427e, 0x1d5d97, 0xd46b4, 0x5b0d8, 0x6a3d3, + 0x000e0, 0x00b63, 0x034cc, 0x06a33, 0x073c9, 0x0e1a0, 0x0f7fd, 0x0f9cc, + 0x1617d, 0x1caeb, 0x1f4a9, 0x3abb3, 0x69450, 0x39420, 0x39777, 0x3e5e0, + 0x6a3d4, 0x6a3ed, 0xb6166, 0xe59f1, 0xb1d6e, 0xe5676, 0x6a3ea, 0xe5674, + 0xb6163, 0xd46b7, 0x7cba6, 0xd46ba, 0x1d5d94, 0xb6164, 0x6a3f1, 0x7cba2, + 0x69451, 0x72dfa, 0xd46bb, 0x72df7, 0x74b94, 0x1cecbf, 0xe59fa, + 0x16009b, + 0x6a3e4, 0x000e6, 0x00e94, 0x03876, 0x070ef, 0x0d52a, 0x16015, 0x16014, + 0x1abf9, 0x1cf17, 0x34a79, 0x34650, 0x3e705, 0x6a3d0, 0x58430, 0x74b9d, + 0x7be7e, 0x5b0be, 0x39773, 0x6a3de, 0x000fb, 0x00f7b, 0x03dd7, 0x07bd0, + 0x0e59c, 0x0f9cd, 0x1cf18, 0x1d2ff, 0x34a7a, 0x39429, 0x3500c, 0x72de0, + 0x69456, 0x7be7c, 0xd46b5, 0xd46b2, 0x6a3dd, 0x001a2, 0x0163b, 0x06913, + 0x0b016, 0x0fa42, 0x1a32d, 0x1cf06, 0x34a7c, 0x34a7d, 0xb6161, 0x35481, + 0x3e5fa, 0x7cba0, 0x7be7f, 0x7cba3, 0x7cba7, 0x5b0d3, 0x72de6, 0x6a3dc, + 0x001a9, 0x01ab4, 0x06a34, 0x0d46a, 0x16130, 0x1ef5f, 0x1f532, 0x1f536, + 0x3942e, 0x58436, 0x6a3db, 0x6945b, 0x001c9, 0x01ca0, 0x0728b, 0x0eed9, + 0x1f539, 0x1ca1d, 0x39765, 0x39766, 0x58439, 0x6945d, 0x39767, 0x001d3, + 0x01f2c, 0x07bfc, 0x16161, 0x34652, 0x3a5ed, 0x3548d, 0x58438, 0x6a3da, + 0x002c1, 0x02c5e, 0x0d335, 0x1ab1a, 0x2d874, 0x35006, 0x35484, 0x5b0cc, + 0x74b9a, 0x72df3, 0x6a3d6, 0x002da, 0x034b3, 0x0d5ae, 0x1caee, 0x2d871, + 0x357e3, 0x74b97, 0x72df9, 0x580ba, 0x5b0d4, 0x0034d, 0x0354e, 0x0f750, + 0x1cbc0, 0x3a5e7, 0x3a5e4, 0x00385, 0x03a58, 0x16c41, 0x2c5cf, 0x3e5e1, + 0x74b6c, 0xe5677, 0x6a3df, 0x00390, 0x03e50, 0x163c2, 0x2d876, 0x35482, + 0x5b0d6, 0x5843a, 0x0039f, 0x0585e, 0x1a583, 0x3500f, 0x74b93, 0x39771, + 0x003e4, 0x06912, 0x16c43, 0x357e1, 0x0058a, 0x0696f, 0x1f538, 0x5b0c9, + 0x6a3cf, 0x005b6, 0x06af8, 0x1f534, 0x58483, 0x6a3e0, 0x00695, 0x07d02, + 0x1cae8, 0x58485, 0x006a2, 0x0754a, 0x357ee, 0x3977b, 0x00748, 0x074b2, + 0x34a7b, 0x00729, 0x0b1e0, 0x34649, 0x3e5e3, 0x0073d, 0x0d2c4, 0x3e5e6, + 0x007bb, 0x0b099, 0x39762, 0x5b0ce, 0x6945f, 0x007d1, 0x0d5ab, 0x39779, + 0x007d3, 0x0d52f, 0x39763, 0x6945c, 0x00b1a, 0x0d2c5, 0x35489, 0x00d23, + 0x0eaed, 0x3e5f8, 0x00d32, 0x16016, 0x3e5fb, 0x00d41, 0x0e768, 0x3a5ed, + 0x00e1f, 0x16017, 0x58027, 0x00ead, 0x0fa07, 0x69455, 0x00e54, 0x1612b, + 0x00e55, 0x1a581, 0x00f78, 0x1a32b, 0x580bc, 0x6a3ee, 0x00f79, 0x1abfd, + 0x00f95, 0x1ab18, 0x6a3f0, 0x01637, 0x1aa4d, 0x0162d, 0x1f53c, 0x6a3f3, + 0x01a31, 0x1a810, 0x39769, 0x01a50, 0x1caef, 0x01a36, 0x1a32e, 0x01a67, + 0x1f38e, 0x01a85, 0x1ef59, 0x01aa6, 0x1ef83, 0x01d51, 0x2c012, 0x01d53, + 0x2d879, 0x01d5e, 0x35005, 0x01cba, 0x1cf04, 0x69453, 0x01d2d, 0x351ff, + 0x01f2d, 0x2d86f, 0x01f29, 0x35007, 0x02c22, 0x351fa, 0x02c03, 0x3a5ec, + 0x02c5f, 0x3a5eb, 0x02c58, 0x34a6b, 0x03469, 0x356be, 0x02c59, 0x34a6c, + 0x0346a, 0x3a5ea, 0x034bd, 0x034bf, 0x356bf, 0x0386a, 0x03ab9, 0x5843f, + 0x0386b, 0x3a5f5, 0x03a4b, 0x39421, 0x03aa4, 0x3a5e9, 0x03a5a, 0x03960, + 0x3977e, 0x03de9, 0x03958, 0x03df7, 0x039e1, 0x3e5e4, 0x0395f, 0x69458, + 0x03e91, 0x03df2, 0x39428, 0x058f2, 0x03e80, 0x6a3c3, 0x03e93, 0x694c0, + 0x058b8, 0x5b0ca, 0x0584f, 0x694c1, 0x058f1, 0x068d6, 0x06a10, 0x06ac3, + 0x06a32, 0x070d2, 0x06911, 0x074b1, 0x07494, 0x06ad4, 0x06ad6, 0x072b8, + 0x06afa, 0x074b3, 0x07540, 0x073ce, 0x0b005, 0x074b3, 0x07495, 0x074b9, + 0x0d336, 0x07bff, 0x07763, 0x073c8, 0x07d29, 0x0b622, 0x0d221, 0x0d181, + 0x0b1d1, 0x074b8, 0x0b1d0, 0x0d19b, 0x0d2c3, 0x0b172, 0x0d2dc, 0x0b623, + 0x0d5aa, 0x0d426, 0x0d182, 0x0e795, 0x0e1d1, 0x0d337, 0x0e96c, 0x0e5e4, + 0x0e514, 0x0eaee, 0x16000, 0x0e767, 0x0e1a1, 0x0e78f, 0x16004, 0x0f7c2, + 0x0e799, 0x0e5e7, 0x0e566, 0x0e769, 0x0f751, 0x0eede, 0x0fa06, 0x16005, + 0x0fa9f, 0x1a5e6, 0x0e766, 0x1636f, 0x0eedd, 0x0eec0, 0x1a309, 0x1ceca, + 0x163cd, 0x0f9cb, 0x0eedf, 0x1a582, 0x1612d, 0x0e5e5, 0x1abf8, 0x1a30c, + 0x1ca1f, 0x163cc, 0x1a35c, 0x1ca1e, 0x1aa51, 0x163ac, 0x1a84e, 0x1a53f, + 0x1cf16, 0x1d2fc, 0x1a5b3, 0x1ab19, 0x1a81f, 0x1d5c3, 0x16c3f, 0x1d5c1, + 0x1d2fc, 0x1f4aa, 0x1a812, 0x1f535, 0x1cf12, 0x1a817, 0x1617c, 0x1ab0b, + 0x1d2f8, 0x1ef82, 0x2d87a, 0x1d52f, 0x1f530, 0x1aa48, 0x35487, 0x1d2fd, + 0x1f4ad, 0x1cf11, 0x3461b, 0x35485, 0x1ca20, 0x1caed, 0x1cae6, 0x1abff, + 0x3464f, 0x34a6f, 0x1ef81, 0x3464b, 0x39d96, 0x1f383, 0x1f537, 0x1cf14, + 0x2c5ce, 0x3500e, 0x2c251, 0x1caec, 0x1f387, 0x34654, 0x357e4, 0x2d878, + 0x3500b, 0x35480, 0x3a5e8, 0x3548e, 0x34b64, 0x1f4a8, 0x35003, 0x3e5df, + 0x2d870, 0x357e6, 0x3e5f0, 0x1ef5a, 0x3a5ea, 0x1f388, 0x3e703, 0x2c24e, + 0x3a5e2, 0x351fd, 0x2c6dd, 0x3e704, 0x351fe, 0x2d875, 0x5b0c7, 0x3976a, + 0x3a5e6, 0x39423, 0x58480, 0x2c246, 0x3a5e3, 0x2d877, 0x3e5f1, 0x3abbe, + 0x58489, 0x3e5f9, 0x357e0, 0x3abbc, 0x5b0c6, 0x69167, 0x69165, 0x3e5e9, + 0x39422, 0x3976f, 0x3977d, 0x3e5de, 0x6a3c9, 0x58b98, 0x3a5f6, 0x3a5d0, + 0x58486, 0x6a3c1, 0x3e5fc, 0x5b0dc, 0x3548f, 0x3942d, 0x694c9, 0x58484, + 0x3a5e8, 0x74b9b, 0x74b96, 0x694d0, 0x58488, 0x3a5e4, 0x3942a, 0x72ec2, + 0x39776, 0x5b0d1, 0x5b0cf, 0x3a5d6, 0xe59fc, 0x5b0c8, 0x3e5e7, 0x7cbb7, + 0x70e87, 0x7cbab, 0x5b0c2, 0x694c3, 0x74a8e, 0x3e5f3, 0x6a3cd, 0x72dfe, + 0x73b2e, 0x72ec0, 0x694c5, 0x58437, 0x694c8, 0x72dff, 0x39435, 0x5843d, + 0x6a3d7, 0x72ec1, 0xd22c8, 0x694cf, 0xb6173, 0x3e5fe, 0x580bb, 0xe59f2, + 0xb616e, 0xb6175, 0x3a5da, 0x5b0bd, 0x694cc, 0x5843c, 0x694c7, 0x74b92, + 0x72ec3, 0x694c6, 0xb6170, 0x7cbac, 0xb1733, 0x7cba4, 0xb6167, 0x72de7, + 0x72de4, 0x6a3c0, 0x3e5ef, 0x162e65, 0x72de3, 0x72dfb, 0x6a35f, 0x6a3eb, +}; + +static const uint8_t coef2_huffbits[1336] = { + 11, 9, 2, 3, 4, 4, 5, 6, + 6, 7, 7, 8, 8, 8, 9, 9, + 9, 9, 10, 10, 10, 10, 11, 11, + 11, 11, 11, 11, 11, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 16, 15, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 18, 17, 17, 17, 17, + 17, 17, 17, 18, 18, 17, 17, 18, + 17, 17, 18, 17, 18, 18, 18, 18, + 19, 18, 18, 18, 18, 18, 18, 20, + 18, 18, 18, 19, 19, 18, 19, 18, + 19, 19, 18, 19, 19, 18, 19, 19, + 19, 19, 18, 19, 19, 19, 19, 19, + 19, 19, 20, 20, 20, 19, 19, 20, + 19, 20, 19, 19, 20, 19, 19, 20, + 20, 20, 20, 19, 20, 21, 19, 3, + 5, 7, 8, 9, 9, 10, 11, 11, + 12, 12, 12, 13, 13, 13, 13, 14, + 14, 14, 14, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 16, 16, + 15, 15, 15, 15, 16, 16, 16, 16, + 17, 16, 17, 17, 16, 17, 17, 17, + 17, 17, 17, 16, 17, 17, 17, 17, + 18, 17, 17, 18, 18, 18, 18, 18, + 19, 18, 18, 18, 18, 18, 18, 19, + 19, 18, 18, 18, 18, 19, 18, 19, + 19, 19, 20, 19, 18, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 20, + 20, 19, 20, 19, 20, 19, 20, 19, + 19, 21, 20, 20, 19, 4, 7, 8, + 10, 11, 11, 12, 12, 13, 13, 14, + 14, 14, 14, 15, 15, 15, 15, 15, + 16, 16, 16, 16, 16, 16, 16, 17, + 17, 17, 17, 17, 17, 17, 16, 16, + 16, 16, 17, 17, 17, 17, 18, 18, + 18, 17, 17, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 19, 18, 18, 18, + 19, 18, 19, 19, 19, 20, 20, 20, + 19, 19, 19, 19, 19, 19, 19, 21, + 21, 20, 19, 5, 8, 10, 11, 12, + 13, 13, 13, 14, 14, 15, 15, 15, + 15, 16, 16, 16, 16, 16, 17, 17, + 17, 17, 17, 17, 17, 17, 18, 17, + 18, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 19, 18, 19, 18, + 18, 18, 18, 18, 19, 18, 17, 17, + 18, 18, 19, 19, 19, 19, 18, 18, + 18, 19, 6, 9, 11, 12, 13, 13, + 14, 14, 14, 15, 15, 16, 16, 16, + 16, 16, 16, 17, 17, 17, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, + 18, 17, 18, 18, 17, 18, 18, 18, + 18, 18, 18, 19, 19, 18, 18, 18, + 19, 19, 19, 20, 19, 19, 18, 19, + 19, 20, 21, 21, 19, 19, 18, 6, + 10, 12, 13, 14, 14, 14, 15, 15, + 15, 16, 16, 17, 17, 17, 17, 17, + 17, 17, 18, 18, 19, 18, 18, 18, + 19, 18, 18, 18, 19, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, + 19, 20, 20, 19, 19, 19, 19, 20, + 20, 19, 20, 19, 19, 19, 20, 20, + 20, 19, 19, 18, 19, 7, 10, 12, + 13, 14, 15, 15, 15, 16, 16, 17, + 17, 17, 17, 17, 17, 18, 18, 18, + 18, 19, 18, 19, 19, 19, 20, 19, + 18, 19, 19, 18, 18, 19, 19, 19, + 18, 19, 19, 20, 19, 18, 20, 21, + 20, 20, 19, 19, 21, 20, 21, 20, + 20, 20, 19, 19, 20, 20, 21, 20, + 19, 7, 11, 13, 14, 15, 15, 15, + 16, 16, 17, 17, 17, 17, 18, 18, + 18, 18, 18, 19, 20, 19, 19, 20, + 19, 19, 19, 19, 19, 19, 19, 19, + 18, 18, 19, 20, 19, 19, 19, 20, + 19, 19, 19, 20, 19, 20, 20, 21, + 20, 20, 20, 21, 22, 20, 19, 20, + 20, 21, 20, 21, 20, 19, 8, 11, + 13, 14, 15, 16, 16, 16, 17, 17, + 17, 18, 18, 18, 18, 18, 19, 18, + 19, 19, 19, 19, 21, 19, 19, 21, + 19, 20, 20, 20, 19, 18, 18, 8, + 12, 14, 15, 16, 16, 16, 16, 17, + 17, 17, 19, 18, 18, 19, 19, 20, + 19, 18, 20, 19, 20, 20, 19, 19, + 20, 20, 21, 21, 20, 19, 19, 19, + 19, 19, 19, 20, 21, 20, 19, 19, + 8, 12, 14, 15, 16, 16, 17, 17, + 17, 18, 18, 18, 19, 19, 19, 19, + 19, 19, 20, 21, 20, 21, 19, 21, + 20, 20, 20, 20, 21, 20, 19, 20, + 19, 20, 20, 20, 19, 22, 21, 21, + 19, 9, 12, 14, 15, 16, 17, 17, + 17, 18, 18, 18, 19, 19, 19, 19, + 20, 19, 19, 19, 9, 13, 15, 16, + 17, 17, 18, 18, 18, 19, 18, 20, + 19, 20, 20, 20, 19, 9, 13, 15, + 16, 17, 17, 18, 18, 18, 20, 18, + 19, 20, 20, 20, 20, 19, 20, 19, + 9, 13, 15, 16, 17, 18, 18, 18, + 19, 19, 19, 19, 10, 14, 16, 17, + 18, 18, 19, 19, 19, 19, 19, 10, + 14, 16, 17, 18, 18, 18, 19, 19, + 10, 14, 16, 17, 18, 18, 18, 19, + 19, 20, 19, 10, 14, 16, 18, 18, + 18, 19, 20, 19, 19, 10, 14, 17, + 18, 18, 18, 10, 15, 17, 18, 19, + 19, 21, 19, 11, 15, 17, 18, 18, + 19, 19, 11, 15, 17, 18, 19, 19, + 11, 15, 17, 18, 11, 15, 18, 19, + 19, 11, 15, 18, 19, 19, 11, 16, + 18, 19, 11, 15, 18, 19, 11, 16, + 18, 12, 16, 18, 19, 12, 16, 19, + 12, 16, 19, 19, 19, 12, 16, 19, + 12, 16, 19, 19, 12, 16, 18, 12, + 16, 19, 12, 17, 19, 12, 17, 19, + 12, 17, 19, 12, 17, 19, 13, 17, + 13, 17, 13, 17, 19, 19, 13, 17, + 13, 17, 19, 13, 17, 13, 18, 19, + 13, 17, 19, 13, 18, 13, 17, 13, + 18, 13, 18, 13, 18, 13, 18, 13, + 18, 13, 18, 14, 18, 19, 14, 18, + 14, 18, 14, 18, 14, 18, 14, 19, + 14, 19, 14, 18, 14, 18, 14, 18, + 14, 19, 14, 14, 18, 14, 14, 19, + 14, 18, 14, 19, 14, 19, 14, 15, + 19, 15, 15, 15, 15, 19, 15, 19, + 15, 15, 19, 15, 15, 19, 15, 19, + 15, 19, 15, 19, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 16, + 15, 15, 15, 16, 16, 16, 15, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 17, 16, 16, 16, 17, + 17, 16, 17, 17, 16, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 18, + 17, 17, 17, 17, 17, 17, 17, 17, + 18, 17, 17, 18, 17, 17, 17, 17, + 18, 18, 17, 17, 17, 17, 17, 17, + 17, 18, 17, 18, 18, 17, 17, 17, + 18, 18, 18, 17, 18, 17, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 17, + 18, 18, 18, 18, 19, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 18, 18, 18, 18, 18, 19, + 18, 18, 19, 18, 18, 18, 19, 18, + 19, 18, 18, 19, 18, 18, 19, 19, + 19, 19, 19, 18, 19, 18, 19, 18, + 19, 19, 18, 18, 19, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 18, 19, + 19, 19, 19, 19, 18, 19, 19, 19, + 19, 19, 19, 19, 19, 19, 19, 20, + 19, 19, 19, 19, 21, 19, 19, 20, + 19, 20, 19, 19, 19, 19, 19, 20, + 20, 20, 19, 19, 19, 20, 19, 19, + 19, 20, 20, 19, 20, 19, 19, 21, + 20, 20, 19, 19, 19, 19, 19, 19, + 20, 19, 20, 20, 20, 20, 20, 20, + 20, 19, 19, 21, 20, 20, 19, 19, +}; + +static const uint32_t coef3_huffcodes[1072] = { + 0x001b2, 0x00069, 0x00000, 0x00004, 0x00006, 0x0000e, 0x00014, 0x00019, + 0x00016, 0x0002b, 0x00030, 0x0003d, 0x0003c, 0x0005a, 0x0005f, 0x0006d, + 0x0007e, 0x0005f, 0x0007f, 0x000b6, 0x000bc, 0x000d8, 0x000f2, 0x000fe, + 0x000bc, 0x000fc, 0x00161, 0x0016e, 0x00174, 0x00176, 0x001a2, 0x001e3, + 0x001f3, 0x00174, 0x0017a, 0x001ea, 0x002a8, 0x002c4, 0x002e6, 0x00314, + 0x00346, 0x00367, 0x003e9, 0x002e5, 0x002ee, 0x003d6, 0x00555, 0x00554, + 0x00557, 0x005c3, 0x005d6, 0x006e0, 0x0062f, 0x006e2, 0x00799, 0x00789, + 0x007fa, 0x005ce, 0x007fe, 0x005ec, 0x007cc, 0x007af, 0x00aa7, 0x00b19, + 0x00b94, 0x00b85, 0x00b9f, 0x00c48, 0x00c45, 0x00dd8, 0x00c4c, 0x00c4b, + 0x00d99, 0x00d1f, 0x00dc2, 0x00f95, 0x00fa2, 0x00bb5, 0x00b9f, 0x00f5d, + 0x00bbf, 0x00f47, 0x0154a, 0x00fd5, 0x00f45, 0x00f7f, 0x0160d, 0x01889, + 0x01757, 0x01722, 0x018b3, 0x0172d, 0x01a39, 0x01a18, 0x01bb3, 0x01b30, + 0x01e63, 0x0173c, 0x01b35, 0x01723, 0x01e80, 0x01fee, 0x01761, 0x01ffc, + 0x01f7f, 0x02c7c, 0x01fa1, 0x0177b, 0x01755, 0x0175a, 0x01fa6, 0x02eab, + 0x0310a, 0x02c69, 0x03669, 0x03127, 0x03103, 0x02e43, 0x03662, 0x03165, + 0x03124, 0x0313b, 0x03111, 0x03668, 0x0343b, 0x03c52, 0x03efc, 0x02e6c, + 0x03fda, 0x03ef8, 0x02e7b, 0x03ee2, 0x03cc5, 0x03d72, 0x058c0, 0x03df8, + 0x02ea9, 0x03e7e, 0x0556d, 0x05c82, 0x03d71, 0x03e7b, 0x03c42, 0x058d7, + 0x03f4e, 0x06200, 0x03d70, 0x05cb2, 0x05c96, 0x05cb0, 0x03f45, 0x05cb1, + 0x02e6d, 0x03110, 0x02f68, 0x05c90, 0x07ca6, 0x07c88, 0x06204, 0x062c8, + 0x078a6, 0x07986, 0x079d5, 0x0b1ad, 0x07989, 0x0b079, 0x05cdd, 0x0aad4, + 0x05de8, 0x07dcd, 0x07987, 0x05d67, 0x05d99, 0x0b91d, 0x07cf1, 0x05d9b, + 0x079d7, 0x0b07b, 0x05c85, 0x05d9a, 0x07dcc, 0x07ebf, 0x07dce, 0x07dfb, + 0x07ec0, 0x07d1a, 0x07a07, 0x05c84, 0x0c471, 0x07cf2, 0x0baef, 0x0b9d2, + 0x05deb, 0x07bd6, 0x0b845, 0x05d98, 0x0b91a, 0x0bae8, 0x0c4e0, 0x0dc31, + 0x0f93d, 0x0bbce, 0x0d1d2, 0x0f7a9, 0x0d9b9, 0x0bbcb, 0x0b900, 0x0aad7, + 0x0babd, 0x0c4e1, 0x0f46f, 0x0c588, 0x0c58b, 0x160e6, 0x0bbcf, 0x0bac3, + 0x0f945, 0x0f7a3, 0x0d1c1, 0x0fb8e, 0x0f7a4, 0x0fb8c, 0x0f40c, 0x0c473, + 0x0fd72, 0x0bbcd, 0x0fffa, 0x0f940, 0x0bbc9, 0x0f7a8, 0x1a1ed, 0x0bbc5, + 0x1f26f, 0x163fd, 0x160c7, 0x1a1f5, 0x0f947, 0x163fc, 0x154b3, 0x0fff6, + 0x163f6, 0x160e9, 0x1a1f0, 0x0bab9, 0x0baba, 0x17086, 0x0b903, 0x0fd75, + 0x0f308, 0x176f3, 0x163ff, 0x0fd7d, 0x1bb78, 0x163fb, 0x188db, 0x1a1f7, + 0x154b2, 0x172fd, 0x163f4, 0x1bb73, 0x172ff, 0x0babc, 0x0f97d, 0x1a1f3, + 0x1bb6d, 0x1ffd5, 0x1a1f4, 0x1f272, 0x17380, 0x17382, 0x1ffe7, 0x0bac8, + 0x0bbc4, 0x188d3, 0x160e0, 0x0fd7b, 0x1725f, 0x172f5, 0x1bb79, 0x1fad9, + 0x1f269, 0x188d0, 0x0bac4, 0x0bac5, 0x31185, 0x188d2, 0x188cc, 0x31187, + 0x3e7fe, 0x188d1, 0x1bb6c, 0x1f268, 0x1fad2, 0x1ffd9, 0x1a1ea, 0x1bb68, + 0x1facb, 0x3fdb2, 0x1e81a, 0x188ce, 0x172fb, 0x1a1ef, 0x1face, 0x1bb70, + 0x0bac1, 0x1bb6b, 0x172f8, 0x1bb66, 0x1ffdf, 0x1bb6a, 0x1ffd7, 0x1f266, + 0x176f8, 0x37653, 0x1fa7e, 0x31182, 0x1fac8, 0x2c7e3, 0x370ee, 0x176ec, + 0x176e9, 0x2e4bc, 0x160c5, 0x3765a, 0x3ce9c, 0x17373, 0x176e8, 0x188d4, + 0x176f1, 0x176ef, 0x37659, 0x1bb7c, 0x1ffde, 0x176f2, 0x3118b, 0x2c7d4, + 0x37651, 0x5ce9f, 0x37650, 0x31191, 0x3f4f6, 0x3f4f5, 0x7a06c, 0x1fac1, + 0x5c97b, 0x2c7e0, 0x79d3a, 0x3e7fd, 0x2c7df, 0x3f4f0, 0x7a06d, 0x376c1, + 0x79d3b, 0x00004, 0x00014, 0x00059, 0x000ab, 0x000b8, 0x00177, 0x001f5, + 0x001f2, 0x00315, 0x003fc, 0x005bd, 0x0062d, 0x006e8, 0x007dd, 0x00b04, + 0x007cd, 0x00b1e, 0x00d1e, 0x00f15, 0x00f3b, 0x00f41, 0x01548, 0x018b0, + 0x0173b, 0x01884, 0x01a1c, 0x01bb4, 0x01f25, 0x017b5, 0x0176d, 0x01ef8, + 0x02e73, 0x03107, 0x03125, 0x03105, 0x02e49, 0x03ce8, 0x03ef9, 0x03e5e, + 0x02e72, 0x03471, 0x03fd9, 0x0623f, 0x078a0, 0x06867, 0x05cb3, 0x06272, + 0x068ec, 0x06e9a, 0x079d4, 0x06e98, 0x0b1aa, 0x06e1a, 0x07985, 0x068ee, + 0x06e9b, 0x05c88, 0x0b1ac, 0x07dfa, 0x05d65, 0x07cf0, 0x07cbf, 0x0c475, + 0x160eb, 0x1bb7e, 0x0f7a6, 0x1fedd, 0x160e3, 0x0fffb, 0x0fb8d, 0x0fff9, + 0x0d1c0, 0x0c58c, 0x1a1e9, 0x0bab8, 0x0f5cf, 0x0fff5, 0x376c5, 0x1a1ec, + 0x160ed, 0x1fede, 0x1fac9, 0x1a1eb, 0x1f224, 0x176ee, 0x0fd79, 0x17080, + 0x17387, 0x1bb7a, 0x1ffe9, 0x176f7, 0x17385, 0x17781, 0x2c7d5, 0x17785, + 0x1ffe3, 0x163f5, 0x1fac2, 0x3e7f9, 0x3118d, 0x3fdb1, 0x1ffe2, 0x1f226, + 0x3118a, 0x2c7d9, 0x31190, 0x3118c, 0x3f4f3, 0x1bb7f, 0x1bb72, 0x31184, + 0xb92f4, 0x3e7fb, 0x6e1d9, 0x1faca, 0x62300, 0x3fdb8, 0x3d037, 0x3e7fc, + 0x62301, 0x3f4f2, 0x1f26a, 0x0000e, 0x00063, 0x000f8, 0x001ee, 0x00377, + 0x003f7, 0x006e3, 0x005cc, 0x00b05, 0x00dd2, 0x00fd4, 0x0172e, 0x0172a, + 0x01e23, 0x01f2d, 0x01763, 0x01769, 0x0176c, 0x02e75, 0x03104, 0x02ec1, + 0x03e58, 0x0583f, 0x03f62, 0x03f44, 0x058c5, 0x0623c, 0x05cf4, 0x07bd7, + 0x05d9d, 0x0aad2, 0x05d66, 0x0b1a9, 0x0b078, 0x07cfe, 0x0b918, 0x0c46f, + 0x0b919, 0x0b847, 0x06e1b, 0x0b84b, 0x0aad8, 0x0fd74, 0x172f4, 0x17081, + 0x0f97c, 0x1f273, 0x0f7a0, 0x0fd7c, 0x172f7, 0x0fd7a, 0x1bb77, 0x172fe, + 0x1f270, 0x0fd73, 0x1bb7b, 0x1a1bc, 0x1bb7d, 0x0bbc3, 0x172f6, 0x0baeb, + 0x0fb8f, 0x3f4f4, 0x3fdb4, 0x376c8, 0x3e7fa, 0x1ffd0, 0x62303, 0xb92f5, + 0x1f261, 0x31189, 0x3fdb5, 0x2c7db, 0x376c9, 0x1fad6, 0x1fad1, 0x00015, + 0x000f0, 0x002e0, 0x0058e, 0x005d7, 0x00c4d, 0x00fa1, 0x00bdb, 0x01756, + 0x01f70, 0x02c19, 0x0313c, 0x0370f, 0x03cc0, 0x02ea8, 0x058c6, 0x058c7, + 0x02eb7, 0x058d0, 0x07d18, 0x0aa58, 0x0b848, 0x05d9e, 0x05d6c, 0x0b84c, + 0x0c589, 0x0b901, 0x163f8, 0x0bac9, 0x0b9c5, 0x0f93c, 0x188d8, 0x0bbc7, + 0x160ec, 0x0fd6f, 0x188d9, 0x160ea, 0x0f7a7, 0x0f944, 0x0baab, 0x0dc3a, + 0x188cf, 0x176fb, 0x2c7d8, 0x2c7d7, 0x1bb75, 0x5ce9e, 0x62302, 0x370ed, + 0x176f4, 0x1ffd1, 0x370ef, 0x3f4f8, 0x376c7, 0x1ffe1, 0x376c6, 0x176ff, + 0x6e1d8, 0x176f6, 0x17087, 0x0f5cd, 0x00035, 0x001a0, 0x0058b, 0x00aac, + 0x00b9a, 0x0175f, 0x01e22, 0x01e8c, 0x01fb2, 0x0310b, 0x058d1, 0x0552e, + 0x05c27, 0x0686e, 0x07ca7, 0x0c474, 0x0dc33, 0x07bf2, 0x05de9, 0x07a35, + 0x0baaa, 0x0b9eb, 0x0fb95, 0x0b9b8, 0x17381, 0x1f262, 0x188cd, 0x17088, + 0x172fa, 0x0f7a2, 0x1fad3, 0x0bac0, 0x3765c, 0x1fedf, 0x1f225, 0x1fad4, + 0x2c7da, 0x5ce9d, 0x3e7f8, 0x1e203, 0x188d7, 0x00054, 0x002c0, 0x007a1, + 0x00f78, 0x01b36, 0x01fa3, 0x0313a, 0x03436, 0x0343a, 0x07d1d, 0x07bd8, + 0x05cdf, 0x0b846, 0x0b189, 0x0d9b8, 0x0fff8, 0x0d9be, 0x0c58a, 0x05dea, + 0x0d1d3, 0x160e4, 0x1f26b, 0x188da, 0x1e202, 0x2c7d2, 0x163fe, 0x31193, + 0x17782, 0x376c2, 0x2c7d1, 0x3fdb0, 0x3765d, 0x2c7d0, 0x1fad0, 0x1e201, + 0x188dd, 0x2c7e2, 0x37657, 0x37655, 0x376c4, 0x376c0, 0x176ea, 0x0006f, + 0x003cf, 0x00dd5, 0x01f23, 0x02c61, 0x02ed0, 0x05d54, 0x0552d, 0x07883, + 0x0b1a8, 0x0b91c, 0x0babf, 0x0b902, 0x0f7aa, 0x0f7a5, 0x1a1e8, 0x1ffd6, + 0x0babe, 0x1a1bf, 0x163f3, 0x1ffd8, 0x1fad7, 0x1f275, 0x1ffdc, 0x0007d, + 0x005bc, 0x01549, 0x02a99, 0x03def, 0x06273, 0x079d6, 0x07d1b, 0x0aad3, + 0x0d0fc, 0x2c7dd, 0x188d6, 0x0bac2, 0x2c7e1, 0x1bb76, 0x1a1bd, 0x31186, + 0x0fd78, 0x1a1be, 0x31183, 0x3fdb6, 0x3f4f1, 0x37652, 0x1fad5, 0x3f4f9, + 0x3e7ff, 0x5ce9c, 0x3765b, 0x31188, 0x17372, 0x000bd, 0x0078b, 0x01f21, + 0x03c43, 0x03ded, 0x0aad6, 0x07ec1, 0x0f942, 0x05c86, 0x17089, 0x0babb, + 0x1ffe8, 0x2c7de, 0x1f26e, 0x1fac4, 0x3f4f7, 0x37656, 0x1fa7d, 0x376c3, + 0x3fdb3, 0x3118f, 0x1fac6, 0x000f8, 0x007ed, 0x01efd, 0x03e7a, 0x05c91, + 0x0aad9, 0x0baec, 0x0dc32, 0x0f46e, 0x1e200, 0x176fa, 0x3765e, 0x3fdb7, + 0x2c7d6, 0x3fdb9, 0x37654, 0x37658, 0x3118e, 0x1ffdb, 0x000f6, 0x00c43, + 0x03106, 0x068ef, 0x0b84d, 0x0b188, 0x0bbcc, 0x1f264, 0x1bb69, 0x17386, + 0x1fac0, 0x00171, 0x00f39, 0x03e41, 0x068ed, 0x0d9bc, 0x0f7a1, 0x1bb67, + 0x1ffdd, 0x176f9, 0x001b9, 0x00f7d, 0x03f63, 0x0d0fd, 0x0b9ea, 0x188dc, + 0x1fac3, 0x1a1f2, 0x31192, 0x1ffe4, 0x001f6, 0x01754, 0x06865, 0x0f309, + 0x160e5, 0x176f5, 0x3765f, 0x1facc, 0x001e9, 0x01a1a, 0x06201, 0x0f105, + 0x176f0, 0x002df, 0x01756, 0x05d6d, 0x163fa, 0x176ed, 0x00342, 0x02e40, + 0x0d0ff, 0x17082, 0x003cd, 0x02a98, 0x0fffc, 0x2c7dc, 0x1fa7f, 0x003fe, + 0x03764, 0x0fffd, 0x176fc, 0x1fac5, 0x002f7, 0x02ed1, 0x0fb97, 0x0058a, + 0x02edc, 0x0bbc8, 0x005d4, 0x0623d, 0x160e8, 0x0062e, 0x05830, 0x163f9, + 0x006eb, 0x06205, 0x1f274, 0x007de, 0x062c9, 0x1f265, 0x005c9, 0x05cde, + 0x1ffd3, 0x005d4, 0x07988, 0x007ce, 0x0b849, 0x00b1b, 0x05c89, 0x1fac7, + 0x00b93, 0x05c83, 0x00b9e, 0x0f14f, 0x00c4a, 0x0b9c7, 0x00dd4, 0x0c470, + 0x1f271, 0x00f38, 0x0fb96, 0x176eb, 0x00fa0, 0x163f7, 0x00bb2, 0x0b91b, + 0x00bbe, 0x0f102, 0x00f44, 0x0f946, 0x1facd, 0x00f79, 0x0d9bd, 0x0154d, + 0x0bbc6, 0x00fd2, 0x160e7, 0x0172b, 0x188cb, 0x0175e, 0x0fd76, 0x0175c, + 0x1bb71, 0x0189f, 0x1a1ee, 0x01f24, 0x1a1f6, 0x01ba7, 0x0bbca, 0x01f7d, + 0x0ffff, 0x01f2e, 0x1bb65, 0x01bb5, 0x172f9, 0x01fef, 0x1f26c, 0x01f3e, + 0x0fd77, 0x01762, 0x1bb6e, 0x01ef9, 0x172fc, 0x01fa0, 0x02ab7, 0x02e4a, + 0x1f267, 0x01fb3, 0x1ffda, 0x02e42, 0x03101, 0x17780, 0x0313d, 0x03475, + 0x17784, 0x03126, 0x1facf, 0x03c51, 0x17783, 0x03e40, 0x1ffe5, 0x03663, + 0x1ffe0, 0x03e8f, 0x1f26d, 0x0343c, 0x03cc1, 0x176fd, 0x03e45, 0x02ec0, + 0x03f61, 0x03dee, 0x03fd8, 0x0583e, 0x02e45, 0x03e59, 0x03d02, 0x05ce8, + 0x05568, 0x176fe, 0x02f69, 0x1fad8, 0x058c1, 0x05c83, 0x1ffe6, 0x06271, + 0x06e1c, 0x062c7, 0x068e1, 0x0552f, 0x06864, 0x06866, 0x06e99, 0x05cbc, + 0x07ca5, 0x078a1, 0x05c82, 0x07dcf, 0x0623b, 0x0623e, 0x068e8, 0x07a36, + 0x05d9c, 0x0b077, 0x07cf3, 0x07a34, 0x07ca4, 0x07d19, 0x079d2, 0x07d1c, + 0x07bd9, 0x0b84a, 0x0fb94, 0x0aad5, 0x0dc30, 0x07bf3, 0x0baee, 0x0b07a, + 0x0c472, 0x0b91e, 0x0d9ba, 0x05d9f, 0x0d0fe, 0x0b9c6, 0x05c87, 0x0f14e, + 0x0baed, 0x0b92e, 0x0f103, 0x0b9c4, 0x0fb91, 0x0d9bb, 0x0b1ab, 0x0c58d, + 0x0fffe, 0x0f93b, 0x0f941, 0x0baea, 0x0b91f, 0x0f5cc, 0x0d9bf, 0x0f943, + 0x0f104, 0x1f260, 0x0fb92, 0x0f93f, 0x0f3a6, 0x0bac7, 0x0f7ab, 0x0bac6, + 0x17383, 0x0fd6d, 0x0bae9, 0x0fd6e, 0x1e74f, 0x188ca, 0x1f227, 0x0fb93, + 0x0fb90, 0x0fff7, 0x17085, 0x17083, 0x160e1, 0x17084, 0x0f93e, 0x160e2, + 0x160c6, 0x1a1f1, 0x1bb6f, 0x17384, 0x0fd70, 0x1f263, 0x188d5, 0x173a6, + 0x0f5ce, 0x163f2, 0x0fd71, 0x1ffd2, 0x160c4, 0x1ffd4, 0x2c7d3, 0x1bb74, +}; + +static const uint8_t coef3_huffbits[1072] = { + 9, 7, 2, 3, 4, 4, 5, 5, + 6, 6, 6, 6, 7, 7, 7, 7, + 7, 8, 8, 8, 8, 8, 8, 8, + 9, 9, 9, 9, 9, 9, 9, 9, + 9, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, + 11, 12, 11, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 14, 13, 14, 14, 13, 14, 13, + 13, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 15, + 14, 14, 15, 14, 14, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 14, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 14, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 16, 15, 16, 16, 16, + 16, 15, 15, 16, 16, 16, 16, 16, + 15, 16, 16, 16, 15, 16, 15, 15, + 16, 15, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 17, 16, 17, 16, 17, 17, 16, + 17, 16, 17, 16, 16, 17, 17, 17, + 16, 17, 16, 16, 17, 16, 17, 16, + 17, 17, 16, 16, 17, 17, 17, 17, + 17, 17, 17, 17, 16, 17, 17, 16, + 17, 17, 17, 17, 17, 17, 17, 17, + 16, 18, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 16, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 18, + 17, 17, 17, 17, 18, 17, 17, 18, + 19, 17, 17, 17, 18, 17, 17, 17, + 18, 18, 18, 17, 17, 17, 18, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 18, 18, 18, 18, 18, 18, 18, 18, + 18, 18, 17, 18, 18, 18, 18, 17, + 18, 18, 18, 17, 17, 18, 18, 18, + 18, 19, 18, 18, 19, 19, 20, 18, + 19, 18, 19, 19, 18, 19, 20, 18, + 19, 4, 6, 7, 8, 9, 9, 9, + 10, 10, 10, 11, 11, 11, 11, 12, + 12, 12, 12, 12, 12, 13, 13, 13, + 13, 13, 13, 13, 13, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 16, 15, 15, 15, + 15, 16, 16, 15, 16, 16, 15, 16, + 17, 17, 17, 17, 17, 16, 16, 16, + 16, 16, 17, 17, 17, 16, 18, 17, + 17, 17, 18, 17, 17, 18, 17, 17, + 17, 17, 17, 18, 17, 18, 18, 18, + 17, 17, 18, 19, 18, 18, 17, 17, + 18, 18, 18, 18, 19, 17, 17, 18, + 20, 19, 19, 18, 19, 18, 19, 19, + 19, 19, 17, 5, 7, 9, 10, 10, + 11, 11, 12, 12, 12, 13, 13, 13, + 13, 13, 14, 14, 14, 14, 14, 15, + 14, 15, 15, 15, 15, 15, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 15, 16, 16, 17, 17, 17, + 16, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 16, + 16, 19, 18, 18, 19, 17, 19, 20, + 17, 18, 18, 18, 18, 18, 18, 6, + 8, 10, 11, 12, 12, 12, 13, 13, + 13, 14, 14, 14, 14, 15, 15, 15, + 15, 15, 15, 16, 16, 16, 16, 16, + 16, 17, 17, 17, 16, 16, 17, 17, + 17, 17, 17, 17, 17, 16, 16, 16, + 17, 18, 18, 18, 17, 19, 19, 18, + 18, 17, 18, 19, 18, 17, 18, 18, + 19, 18, 17, 17, 6, 9, 11, 12, + 13, 13, 13, 14, 14, 14, 15, 15, + 15, 15, 15, 16, 16, 16, 16, 16, + 16, 17, 16, 17, 17, 17, 17, 17, + 17, 17, 18, 17, 18, 17, 17, 18, + 18, 19, 19, 17, 17, 7, 10, 12, + 13, 13, 14, 14, 14, 14, 15, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 17, 17, 17, 17, 18, 17, 18, + 18, 18, 18, 18, 18, 18, 18, 17, + 17, 18, 18, 18, 18, 18, 18, 7, + 10, 12, 13, 14, 15, 15, 15, 15, + 16, 16, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 18, 17, 17, 8, + 11, 13, 14, 15, 15, 15, 15, 16, + 16, 18, 17, 17, 18, 17, 17, 18, + 17, 17, 18, 18, 19, 18, 18, 19, + 19, 19, 18, 18, 18, 8, 11, 13, + 14, 15, 16, 16, 16, 16, 17, 17, + 17, 18, 17, 18, 19, 18, 18, 18, + 18, 18, 18, 8, 12, 14, 15, 15, + 16, 16, 16, 17, 17, 18, 18, 18, + 18, 18, 18, 18, 18, 17, 9, 12, + 14, 15, 16, 16, 17, 17, 17, 17, + 18, 9, 12, 14, 15, 16, 17, 17, + 17, 18, 9, 13, 15, 16, 17, 17, + 18, 17, 18, 17, 9, 13, 15, 16, + 17, 18, 18, 18, 10, 13, 15, 16, + 18, 10, 14, 16, 17, 18, 10, 14, + 16, 17, 10, 14, 16, 18, 18, 10, + 14, 16, 18, 18, 11, 15, 16, 11, + 15, 17, 11, 15, 17, 11, 15, 17, + 11, 15, 17, 11, 15, 17, 12, 16, + 17, 12, 15, 12, 16, 12, 16, 18, + 12, 16, 12, 16, 12, 16, 12, 16, + 17, 12, 16, 18, 12, 17, 13, 16, + 13, 16, 13, 16, 18, 13, 16, 13, + 17, 13, 17, 13, 17, 13, 17, 13, + 17, 13, 17, 13, 17, 13, 17, 13, + 16, 13, 17, 13, 17, 13, 17, 14, + 17, 14, 17, 14, 17, 14, 14, 14, + 17, 14, 17, 14, 14, 18, 14, 14, + 18, 14, 18, 14, 18, 14, 17, 14, + 17, 14, 17, 14, 14, 18, 14, 15, + 15, 15, 14, 15, 15, 14, 15, 15, + 15, 18, 15, 18, 15, 15, 17, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 16, 15, 15, 15, 15, 16, + 16, 16, 16, 16, 15, 15, 15, 15, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 17, 16, 16, + 16, 17, 16, 16, 16, 17, 17, 17, + 17, 17, 16, 17, 17, 17, 17, 16, + 16, 16, 17, 17, 17, 17, 16, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 18, 17, +}; + +static const uint32_t coef4_huffcodes[476] = { + 0x00f01, 0x0001e, 0x00000, 0x00004, 0x00006, 0x0000d, 0x0000a, 0x00017, + 0x0001d, 0x00017, 0x0002c, 0x00031, 0x00039, 0x0003e, 0x00039, 0x0005a, + 0x00066, 0x00070, 0x0007b, 0x00070, 0x00077, 0x000af, 0x000c9, 0x000f2, + 0x000f4, 0x000b2, 0x000e3, 0x0015b, 0x0015d, 0x00181, 0x0019d, 0x001e3, + 0x001c5, 0x002b5, 0x002db, 0x00338, 0x003c3, 0x003cc, 0x003f0, 0x002cd, + 0x003fa, 0x003a1, 0x005b4, 0x00657, 0x007ab, 0x0074d, 0x0074c, 0x00ac1, + 0x00ac5, 0x0076b, 0x00ca8, 0x00f04, 0x00f00, 0x00fe3, 0x00f3c, 0x00f10, + 0x00f39, 0x00fe6, 0x00e26, 0x00e90, 0x016c5, 0x01827, 0x01954, 0x015c5, + 0x01958, 0x01f8a, 0x01c4a, 0x02b0f, 0x02b41, 0x02b0e, 0x033c6, 0x03050, + 0x01c4f, 0x02d88, 0x0305c, 0x03c18, 0x02b4f, 0x02cc2, 0x03a47, 0x05680, + 0x0569d, 0x06442, 0x06443, 0x06446, 0x0656e, 0x06444, 0x07120, 0x0748a, + 0x0c1ba, 0x07e22, 0x07aa6, 0x07f25, 0x07aa7, 0x07e20, 0x0c11b, 0x0c118, + 0x07aa5, 0x0ad0a, 0x0f389, 0x19ebb, 0x0caad, 0x0fe42, 0x0fe40, 0x16c34, + 0x2b4e5, 0x33d65, 0x16c30, 0x1e7ae, 0x1e25c, 0x18370, 0x1e703, 0x19eba, + 0x16c37, 0x0e234, 0x16c6e, 0x00004, 0x0002a, 0x00061, 0x00075, 0x000cb, + 0x000ff, 0x00190, 0x001eb, 0x001d1, 0x002b9, 0x00307, 0x00339, 0x0033f, + 0x003fb, 0x003b4, 0x0060c, 0x00679, 0x00645, 0x0067d, 0x0078a, 0x007e3, + 0x00749, 0x00ac4, 0x00ad2, 0x00ae3, 0x00c10, 0x00c16, 0x00ad1, 0x00cf4, + 0x00fe2, 0x01586, 0x00e9d, 0x019f1, 0x01664, 0x01e26, 0x01d38, 0x02b4d, + 0x033c5, 0x01fc2, 0x01fc3, 0x01d28, 0x03c1d, 0x0598e, 0x0f094, 0x07aa4, + 0x0ad38, 0x0ac0c, 0x0c11a, 0x079ea, 0x0c881, 0x0fe44, 0x0b635, 0x0ac0d, + 0x0b61e, 0x05987, 0x07121, 0x0f382, 0x0f387, 0x0e237, 0x0fe47, 0x0f383, + 0x0f091, 0x0f385, 0x0e233, 0x182ee, 0x19eb8, 0x1663e, 0x0f093, 0x00014, + 0x00058, 0x00159, 0x00167, 0x00300, 0x003d4, 0x005b5, 0x0079d, 0x0076a, + 0x00b67, 0x00b60, 0x00f05, 0x00cf0, 0x00f17, 0x00e95, 0x01822, 0x01913, + 0x016c2, 0x0182f, 0x01959, 0x01fcb, 0x01e27, 0x01c40, 0x033c7, 0x01e7b, + 0x01c49, 0x02d89, 0x01e23, 0x01660, 0x03f12, 0x02cc6, 0x033e1, 0x05b34, + 0x0609a, 0x06569, 0x07488, 0x07e21, 0x0cf5f, 0x0712c, 0x0389d, 0x067cf, + 0x07f28, 0x1663f, 0x33d67, 0x1663d, 0x1e25d, 0x3c1ab, 0x15c44, 0x16c36, + 0x0001f, 0x000ec, 0x00323, 0x005b2, 0x0079f, 0x00ac2, 0x00f16, 0x00e9e, + 0x01956, 0x01e0f, 0x019ea, 0x01666, 0x02b89, 0x02b02, 0x02d8c, 0x03c1b, + 0x03c19, 0x032b5, 0x03f9c, 0x02ccf, 0x03897, 0x05b35, 0x0ad02, 0x07f29, + 0x06441, 0x03884, 0x07888, 0x0784e, 0x06568, 0x0c1bb, 0x05986, 0x067cc, + 0x0fe49, 0x0fe48, 0x0c1bc, 0x0fe41, 0x18371, 0x1663c, 0x0e231, 0x0711e, + 0x0ad09, 0x0f092, 0x0002d, 0x001db, 0x00781, 0x00c1a, 0x00f55, 0x01580, + 0x01ea8, 0x02d9b, 0x032af, 0x03f16, 0x03c1c, 0x07834, 0x03c45, 0x0389c, + 0x067ce, 0x06445, 0x0c1b9, 0x07889, 0x07f3a, 0x0784f, 0x07f2b, 0x0ad0b, + 0x0f090, 0x0c11d, 0x0e94e, 0x0711f, 0x0e9f1, 0x0f38e, 0x079e9, 0x0ad03, + 0x0f09b, 0x0caae, 0x0fe46, 0x2b4e6, 0x0e9f0, 0x19eb6, 0x67ac1, 0x67ac0, + 0x33d66, 0x0f388, 0x00071, 0x003a0, 0x00ca9, 0x01829, 0x01d39, 0x02b43, + 0x02cc4, 0x06554, 0x0f09a, 0x0b61f, 0x067cd, 0x0711c, 0x0b636, 0x07f2a, + 0x0b634, 0x0c11f, 0x0cf5e, 0x0b61d, 0x0f06b, 0x0caab, 0x0c1be, 0x0e94c, + 0x0f099, 0x182ed, 0x0e94f, 0x0c119, 0x0e232, 0x2b4e4, 0x0f38a, 0x19eb4, + 0x1e25f, 0x0e94d, 0x000b7, 0x00785, 0x016cc, 0x03051, 0x033c4, 0x0656f, + 0x03891, 0x0711d, 0x0caaf, 0x0f097, 0x07489, 0x0f098, 0x0c880, 0x0caaa, + 0x0f386, 0x19eb7, 0x16c6f, 0x0f384, 0x182e8, 0x182e9, 0x0e230, 0x1e700, + 0x33d62, 0x33d63, 0x33d64, 0x16c33, 0x0e216, 0x000fd, 0x00c15, 0x01665, + 0x03c4a, 0x07f3b, 0x07896, 0x0c11c, 0x0e215, 0x16c32, 0x0f38b, 0x0f38d, + 0x182ea, 0x1e701, 0x712df, 0x15c46, 0x00194, 0x00fe0, 0x03f13, 0x0748b, + 0x0f096, 0x0cf80, 0x1e25e, 0xe25bd, 0x33d61, 0x16c31, 0x001f9, 0x01912, + 0x05710, 0x0f3d0, 0x0c1bf, 0x00301, 0x01e24, 0x0ad08, 0x003cd, 0x01c41, + 0x0c1bd, 0x00563, 0x03a52, 0x0f3d1, 0x00570, 0x02cce, 0x0e217, 0x0067b, + 0x0655d, 0x0074b, 0x06447, 0x00c12, 0x074fb, 0x00f08, 0x0b61c, 0x00e22, + 0x0fe43, 0x016c7, 0x01836, 0x019f2, 0x01c43, 0x01d3f, 0x01fcf, 0x02b4c, + 0x0304c, 0x032b6, 0x03a46, 0x05607, 0x03f17, 0x02cc5, 0x0609b, 0x0655c, + 0x07e23, 0x067c1, 0x07f26, 0x07f27, 0x0f095, 0x0e9f3, 0x0cf81, 0x0c11e, + 0x0caac, 0x0f38f, 0x0e9f2, 0x074fa, 0x0e236, 0x0fe45, 0x1c428, 0x0e235, + 0x182ef, 0x19eb5, 0x0f3d6, 0x182ec, 0x16c35, 0x0f38c, 0x2b4e7, 0x15c47, + 0xe25bc, 0x1e702, 0x1c4b6, 0x0e25a, 0x3c1aa, 0x15c45, 0x1c429, 0x19eb9, + 0x1e7af, 0x182eb, 0x1e0d4, 0x3896e, +}; + +static const uint8_t coef4_huffbits[476] = { + 12, 6, 2, 3, 4, 4, 5, 5, + 5, 6, 6, 6, 6, 6, 7, 7, + 7, 7, 7, 8, 8, 8, 8, 8, + 8, 9, 9, 9, 9, 9, 9, 9, + 10, 10, 10, 10, 10, 10, 10, 11, + 10, 11, 11, 11, 11, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 13, 13, 13, 13, 13, 13, + 13, 13, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 16, 16, + 16, 15, 15, 15, 15, 15, 16, 16, + 15, 16, 16, 17, 16, 16, 16, 17, + 18, 18, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 4, 6, 7, 8, 8, + 8, 9, 9, 10, 10, 10, 10, 10, + 10, 11, 11, 11, 11, 11, 11, 11, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 13, 13, 13, 14, 13, 14, 14, + 14, 13, 13, 14, 14, 16, 16, 15, + 16, 16, 16, 15, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 17, 16, 16, + 16, 16, 17, 17, 17, 18, 16, 5, + 8, 9, 10, 10, 10, 11, 11, 12, + 12, 12, 12, 12, 12, 13, 13, 13, + 13, 13, 13, 13, 13, 14, 14, 13, + 14, 14, 13, 14, 14, 15, 14, 15, + 15, 15, 16, 15, 16, 16, 15, 15, + 15, 18, 18, 18, 17, 18, 17, 17, + 6, 9, 10, 11, 11, 12, 12, 13, + 13, 13, 13, 14, 14, 14, 14, 14, + 14, 14, 14, 15, 15, 15, 16, 15, + 15, 15, 15, 15, 15, 16, 16, 15, + 16, 16, 16, 16, 17, 18, 17, 16, + 16, 16, 7, 10, 11, 12, 12, 13, + 13, 14, 14, 14, 14, 15, 14, 15, + 15, 15, 16, 15, 15, 15, 15, 16, + 16, 16, 17, 16, 17, 16, 15, 16, + 16, 16, 16, 18, 17, 17, 19, 19, + 18, 16, 7, 11, 12, 13, 14, 14, + 15, 15, 16, 16, 15, 16, 16, 15, + 16, 16, 16, 16, 16, 16, 16, 17, + 16, 17, 17, 16, 17, 18, 16, 17, + 17, 17, 8, 11, 13, 14, 14, 15, + 15, 16, 16, 16, 16, 16, 16, 16, + 16, 17, 17, 16, 17, 17, 17, 17, + 18, 18, 18, 17, 17, 8, 12, 14, + 14, 15, 15, 16, 17, 17, 16, 16, + 17, 17, 20, 17, 9, 12, 14, 16, + 16, 16, 17, 21, 18, 17, 9, 13, + 15, 16, 16, 10, 13, 16, 10, 14, + 16, 11, 15, 16, 11, 15, 17, 11, + 15, 12, 15, 12, 16, 12, 16, 13, + 16, 13, 13, 13, 14, 14, 13, 14, + 14, 14, 15, 15, 14, 15, 15, 15, + 15, 15, 15, 15, 16, 17, 16, 16, + 16, 16, 17, 16, 17, 16, 18, 17, + 17, 17, 16, 17, 17, 16, 18, 17, + 21, 17, 18, 17, 18, 17, 18, 17, + 17, 17, 17, 19, +}; + +static const uint32_t coef5_huffcodes[435] = { + 0x00347, 0x0000b, 0x00001, 0x00001, 0x0000c, 0x00004, 0x00010, 0x00015, + 0x0001f, 0x0000b, 0x00023, 0x00026, 0x00029, 0x00035, 0x00037, 0x00001, + 0x00015, 0x0001a, 0x0001d, 0x0001c, 0x0001e, 0x0004e, 0x00049, 0x00051, + 0x00078, 0x00004, 0x00000, 0x00008, 0x0000d, 0x0007b, 0x00005, 0x00032, + 0x00095, 0x00091, 0x00096, 0x000a1, 0x000d9, 0x00003, 0x00019, 0x00061, + 0x00066, 0x00060, 0x00017, 0x0000e, 0x00063, 0x001a0, 0x001b7, 0x001e6, + 0x001e7, 0x001b6, 0x00018, 0x001e8, 0x00038, 0x00031, 0x00005, 0x0003d, + 0x00027, 0x001ea, 0x0001a, 0x000c5, 0x000f9, 0x000ff, 0x000db, 0x00250, + 0x000fc, 0x0025c, 0x00008, 0x00075, 0x003d7, 0x003d3, 0x001b0, 0x0007c, + 0x003ca, 0x00036, 0x00189, 0x004a6, 0x004a2, 0x004fb, 0x000c0, 0x0007f, + 0x0009a, 0x00311, 0x0006e, 0x0009b, 0x0068c, 0x006c0, 0x00484, 0x00012, + 0x000c3, 0x0094f, 0x00979, 0x009f9, 0x00d09, 0x00da6, 0x00da8, 0x00901, + 0x000c1, 0x00373, 0x00d08, 0x009fa, 0x00d8b, 0x00d85, 0x00d86, 0x000df, + 0x006e2, 0x000ce, 0x00f24, 0x009fe, 0x001f7, 0x007c1, 0x000cf, 0x009fc, + 0x009ff, 0x00d89, 0x00da9, 0x009fd, 0x001f8, 0x01a36, 0x0128c, 0x0129d, + 0x01a37, 0x00196, 0x003ea, 0x00f8b, 0x00d93, 0x01e45, 0x01e58, 0x01e4b, + 0x01e59, 0x013f1, 0x00309, 0x00265, 0x00308, 0x0243a, 0x027e1, 0x00f89, + 0x00324, 0x03cbc, 0x03c86, 0x03695, 0x0243c, 0x0243b, 0x0243e, 0x01e4a, + 0x003a5, 0x03468, 0x03428, 0x03c84, 0x027e0, 0x025e2, 0x01880, 0x00197, + 0x00325, 0x03cb7, 0x0791e, 0x007ec, 0x06c75, 0x004c8, 0x04bc7, 0x004c6, + 0x00983, 0x0481e, 0x01b53, 0x0251b, 0x01b58, 0x00984, 0x04fa8, 0x03cbb, + 0x00f8a, 0x00322, 0x0346a, 0x0243d, 0x00326, 0x03469, 0x0481f, 0x0481d, + 0x00746, 0x09032, 0x01b50, 0x01d13, 0x0d8e4, 0x0481b, 0x06c74, 0x0796b, + 0x07969, 0x00985, 0x0d8e3, 0x00986, 0x00fa2, 0x01301, 0x06c7c, 0x00987, + 0x03cb8, 0x0f4af, 0x00e88, 0x1b1c0, 0x00fce, 0x033eb, 0x03f6a, 0x03f69, + 0x00fcf, 0x0791f, 0x004c9, 0x04871, 0x00fcd, 0x00982, 0x00fcc, 0x00fa3, + 0x01d12, 0x0796c, 0x01b47, 0x00321, 0x0796a, 0x0d8e2, 0x04872, 0x04873, + 0x0000e, 0x00014, 0x0000a, 0x000a0, 0x00012, 0x0007d, 0x001a2, 0x0003b, + 0x0025f, 0x000dd, 0x0027c, 0x00343, 0x00368, 0x0036b, 0x0003e, 0x001fa, + 0x00485, 0x001b3, 0x0007f, 0x001b1, 0x0019e, 0x004ba, 0x007ad, 0x00339, + 0x00066, 0x007a4, 0x00793, 0x006c6, 0x0007e, 0x000f1, 0x00372, 0x009fb, + 0x00d83, 0x00d8a, 0x00947, 0x009f4, 0x001d0, 0x01b09, 0x01b4b, 0x007ec, + 0x003e1, 0x000ca, 0x003ec, 0x02539, 0x04fa9, 0x01b57, 0x03429, 0x03d2a, + 0x00d97, 0x003a7, 0x00dc0, 0x00d96, 0x00dc1, 0x007eb, 0x03cba, 0x00c43, + 0x00c41, 0x01b52, 0x007ef, 0x00323, 0x03cb9, 0x03c83, 0x007d0, 0x007ed, + 0x06c7f, 0x09033, 0x03f6c, 0x36383, 0x1e95d, 0x06c78, 0x00747, 0x01b51, + 0x00022, 0x00016, 0x00039, 0x00252, 0x00079, 0x00486, 0x00338, 0x00369, + 0x00d88, 0x00026, 0x00d87, 0x00f4b, 0x00d82, 0x00027, 0x001e1, 0x01a15, + 0x007c7, 0x012f0, 0x001e0, 0x006d0, 0x01a16, 0x01e44, 0x01e5f, 0x03690, + 0x00d90, 0x00c42, 0x00daf, 0x00d92, 0x00f80, 0x00cfb, 0x0342f, 0x0487f, + 0x01b46, 0x07968, 0x00d95, 0x00d91, 0x01b55, 0x03f68, 0x04bc6, 0x03cbd, + 0x00f81, 0x00320, 0x00069, 0x000fe, 0x006d5, 0x0033f, 0x000de, 0x007c6, + 0x01e40, 0x00d94, 0x00f88, 0x03c8e, 0x03694, 0x00dae, 0x00dad, 0x00267, + 0x003a6, 0x00327, 0x0487e, 0x007ee, 0x00749, 0x004c7, 0x03692, 0x01b56, + 0x00fd1, 0x07a56, 0x06c77, 0x09031, 0x00748, 0x06c7a, 0x0796d, 0x033ea, + 0x06c76, 0x00fd0, 0x36382, 0x1e417, 0x00745, 0x04faf, 0x0d8e1, 0x03f6b, + 0x1e95c, 0x04fad, 0x0009e, 0x004bd, 0x0067c, 0x01b08, 0x003eb, 0x01b45, + 0x03691, 0x0d8e5, 0x07904, 0x00981, 0x007ea, 0x019f4, 0x06c7d, 0x04fab, + 0x04fac, 0x06c7e, 0x01300, 0x06c7b, 0x0006f, 0x003f7, 0x03c85, 0x004c4, + 0x0001e, 0x006e1, 0x03693, 0x01b44, 0x00241, 0x01e46, 0x0019d, 0x00266, + 0x004bb, 0x02538, 0x007ac, 0x01b54, 0x00902, 0x04870, 0x00da7, 0x00900, + 0x00185, 0x06c79, 0x006e3, 0x003e9, 0x01e94, 0x003ed, 0x003f2, 0x0342e, + 0x0346b, 0x0251a, 0x004c5, 0x01881, 0x0481c, 0x01b59, 0x03c87, 0x04fae, + 0x007e9, 0x03f6d, 0x0f20a, 0x09030, 0x04faa, 0x0d8e6, 0x03f6f, 0x0481a, + 0x03f6e, 0x1e416, 0x0d8e7, +}; + +static const uint8_t coef5_huffbits[435] = { + 10, 4, 2, 4, 4, 5, 5, 5, + 5, 6, 6, 6, 6, 6, 6, 7, + 7, 7, 7, 7, 7, 7, 7, 7, + 7, 8, 8, 8, 8, 7, 8, 8, + 8, 8, 8, 8, 8, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 10, 9, 10, 10, 10, 10, + 10, 9, 10, 10, 10, 10, 10, 10, + 10, 10, 11, 11, 10, 10, 11, 11, + 10, 11, 11, 11, 11, 11, 12, 12, + 12, 12, 12, 12, 11, 11, 11, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 13, + 13, 13, 12, 12, 13, 13, 13, 12, + 12, 12, 12, 12, 13, 13, 13, 13, + 13, 14, 14, 14, 14, 13, 13, 13, + 13, 13, 14, 14, 14, 14, 14, 14, + 15, 14, 14, 14, 14, 14, 14, 13, + 14, 14, 14, 14, 14, 14, 15, 14, + 15, 14, 15, 15, 15, 15, 15, 15, + 16, 15, 15, 14, 15, 16, 15, 14, + 14, 15, 14, 14, 15, 14, 15, 15, + 15, 16, 15, 17, 16, 15, 15, 15, + 15, 16, 16, 16, 16, 17, 15, 16, + 14, 16, 16, 17, 16, 16, 16, 16, + 16, 15, 15, 15, 16, 16, 16, 16, + 17, 15, 15, 15, 15, 16, 15, 15, + 4, 7, 8, 8, 9, 9, 9, 10, + 10, 10, 10, 10, 10, 10, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 12, + 12, 11, 11, 11, 12, 12, 12, 12, + 12, 12, 12, 12, 13, 13, 13, 13, + 12, 13, 14, 14, 15, 15, 14, 14, + 14, 14, 14, 14, 14, 15, 14, 14, + 14, 15, 15, 15, 14, 14, 15, 15, + 15, 16, 16, 18, 17, 15, 15, 15, + 6, 9, 10, 10, 11, 11, 12, 12, + 12, 13, 12, 12, 12, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 14, + 14, 14, 14, 14, 14, 14, 14, 15, + 15, 15, 14, 14, 15, 16, 15, 14, + 14, 15, 7, 10, 11, 12, 13, 13, + 13, 14, 14, 14, 14, 14, 14, 14, + 14, 15, 15, 15, 15, 15, 14, 15, + 16, 15, 15, 16, 15, 15, 15, 16, + 15, 16, 18, 17, 15, 15, 16, 16, + 17, 15, 8, 11, 13, 13, 14, 15, + 14, 16, 15, 16, 15, 15, 15, 15, + 15, 15, 17, 15, 9, 12, 14, 15, + 10, 13, 14, 15, 10, 13, 11, 14, + 11, 14, 11, 15, 12, 15, 12, 12, + 13, 15, 13, 14, 13, 14, 14, 14, + 14, 14, 15, 15, 15, 15, 14, 15, + 15, 16, 16, 16, 15, 16, 16, 15, + 16, 17, 16, +}; + +static const uint16_t levels0[60] = { + 317, 92, 62, 60, 19, 17, 10, 7, + 6, 5, 5, 3, 3, 3, 2, 2, + 2, 2, 2, 2, 2, 1, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, +}; + +static const uint16_t levels1[40] = { + 311, 91, 61, 28, 10, 6, 5, 2, + 2, 2, 2, 2, 2, 2, 2, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, +}; + +static const uint16_t levels2[340] = { + 181, 110, 78, 63, 61, 62, 60, 61, + 33, 41, 41, 19, 17, 19, 12, 11, + 9, 11, 10, 6, 8, 7, 6, 4, + 5, 5, 4, 4, 3, 4, 3, 5, + 3, 4, 3, 3, 3, 3, 3, 3, + 2, 2, 4, 2, 3, 2, 3, 3, + 2, 2, 2, 2, 2, 2, 2, 2, + 3, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 1, 2, 1, 2, 2, + 2, 2, 1, 2, 1, 1, 1, 2, + 2, 1, 2, 1, 2, 2, 2, 2, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, +}; + +static const uint16_t levels3[180] = { + 351, 122, 76, 61, 41, 42, 24, 30, + 22, 19, 11, 9, 10, 8, 5, 5, + 4, 5, 5, 3, 3, 3, 3, 3, + 3, 3, 2, 2, 3, 2, 2, 2, + 3, 3, 2, 2, 2, 3, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 1, 1, + 2, 2, 1, 2, 1, 2, 2, 2, + 2, 2, 2, 1, 2, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 2, + 2, 1, 2, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, +}; + +static const uint16_t levels4[70] = { + 113, 68, 49, 42, 40, 32, 27, 15, + 10, 5, 3, 3, 3, 3, 2, 2, + 2, 2, 2, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, +}; + +static const uint16_t levels5[40] = { + 214, 72, 42, 40, 18, 4, 4, 2, + 2, 2, 2, 2, 1, 1, 2, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, +}; + +#define DEF_COEF_TABLE(_x) { \ + .n = sizeof(coef ## _x ## _huffbits), \ + .huffcodes = coef ## _x ## _huffcodes, \ + .huffbits = coef ## _x ##_huffbits, \ + .levels = levels ## _x} + +static const struct coef_vlc_table coef_vlcs[6] = { + DEF_COEF_TABLE(0), DEF_COEF_TABLE(1), DEF_COEF_TABLE(2), + DEF_COEF_TABLE(3), DEF_COEF_TABLE(4), DEF_COEF_TABLE(5) +}; diff --git a/wmadec_filter.c b/wmadec_filter.c new file mode 100644 index 00000000..bb534df5 --- /dev/null +++ b/wmadec_filter.c @@ -0,0 +1,1296 @@ +/* + * WMA compatible decoder + * + * Extracted 2009 from the mplayer source code 2009-02-10. + * + * Copyright (c) 2002 The FFmpeg Project + * + * Licensed under the GNU Lesser General Public License. + * For licencing details see COPYING.LIB. + */ + +/** \file wmadec_filter.c paraslash's WMA decoder. */ + +/* + * This decoder handles Microsoft Windows Media Audio data version 2. + */ + +#define _XOPEN_SOURCE 600 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "para.h" +#include "error.h" +#include "list.h" +#include "ggo.h" +#include "string.h" +#include "sched.h" +#include "buffer_tree.h" +#include "filter.h" +#include "bitstream.h" +#include "imdct.h" +#include "wma.h" +#include "wmadata.h" + + +/* size of blocks */ +#define BLOCK_MIN_BITS 7 +#define BLOCK_MAX_BITS 11 +#define BLOCK_MAX_SIZE (1 << BLOCK_MAX_BITS) + +#define BLOCK_NB_SIZES (BLOCK_MAX_BITS - BLOCK_MIN_BITS + 1) + +/* XXX: find exact max size */ +#define HIGH_BAND_MAX_SIZE 16 + +/* XXX: is it a suitable value ? */ +#define MAX_CODED_SUPERFRAME_SIZE 16384 + +#define MAX_CHANNELS 2 + +#define NOISE_TAB_SIZE 8192 + +#define LSP_POW_BITS 7 + +struct private_wmadec_data { + /** Information contained in the audio file header. */ + struct asf_header_info ahi; + struct getbit_context gb; + /** Whether to use the bit reservoir. */ + int use_bit_reservoir; + /** Whether to use variable block length. */ + int use_variable_block_len; + /** Whether to use exponent coding. */ + int use_exp_vlc; + /** Whether perceptual noise is added. */ + int use_noise_coding; + int byte_offset_bits; + struct vlc exp_vlc; + int exponent_sizes[BLOCK_NB_SIZES]; + uint16_t exponent_bands[BLOCK_NB_SIZES][25]; + /** The index of the first coef in high band. */ + int high_band_start[BLOCK_NB_SIZES]; + /** Maximal number of coded coefficients. */ + int coefs_end[BLOCK_NB_SIZES]; + int exponent_high_sizes[BLOCK_NB_SIZES]; + int exponent_high_bands[BLOCK_NB_SIZES][HIGH_BAND_MAX_SIZE]; + struct vlc hgain_vlc; + + /* coded values in high bands */ + int high_band_coded[MAX_CHANNELS][HIGH_BAND_MAX_SIZE]; + int high_band_values[MAX_CHANNELS][HIGH_BAND_MAX_SIZE]; + + /* there are two possible tables for spectral coefficients */ + struct vlc coef_vlc[2]; + uint16_t *run_table[2]; + uint16_t *level_table[2]; + const struct coef_vlc_table *coef_vlcs[2]; + /** Frame length in samples. */ + int frame_len; + /** log2 of frame_len. */ + int frame_len_bits; + /** Number of block sizes. */ + int nb_block_sizes; + /* block info */ + int reset_block_lengths; + /** log2 of current block length. */ + int block_len_bits; + /** log2 of next block length. */ + int next_block_len_bits; + /** log2 of previous block length. */ + int prev_block_len_bits; + /** Block length in samples. */ + int block_len; + /** Current position in frame. */ + int block_pos; + /** True if mid/side stereo mode. */ + uint8_t ms_stereo; + /** True if channel is coded. */ + uint8_t channel_coded[MAX_CHANNELS]; + /** log2 ratio frame/exp. length. */ + int exponents_bsize[MAX_CHANNELS]; + + float exponents[MAX_CHANNELS][BLOCK_MAX_SIZE]; + float max_exponent[MAX_CHANNELS]; + int16_t coefs1[MAX_CHANNELS][BLOCK_MAX_SIZE]; + float coefs[MAX_CHANNELS][BLOCK_MAX_SIZE]; + float output[BLOCK_MAX_SIZE * 2]; + struct mdct_context *mdct_ctx[BLOCK_NB_SIZES]; + float *windows[BLOCK_NB_SIZES]; + /** Output buffer for one frame and the last for IMDCT windowing. */ + float frame_out[MAX_CHANNELS][BLOCK_MAX_SIZE * 2]; + /** Last frame info. */ + uint8_t last_superframe[MAX_CODED_SUPERFRAME_SIZE + 4]; /* padding added */ + int last_bitoffset; + int last_superframe_len; + float noise_table[NOISE_TAB_SIZE]; + int noise_index; + float noise_mult; /* XXX: suppress that and integrate it in the noise array */ + /* lsp_to_curve tables */ + float lsp_cos_table[BLOCK_MAX_SIZE]; + float lsp_pow_e_table[256]; + float lsp_pow_m_table1[(1 << LSP_POW_BITS)]; + float lsp_pow_m_table2[(1 << LSP_POW_BITS)]; +}; + +#define EXPVLCBITS 8 +#define EXPMAX DIV_ROUND_UP(19, EXPVLCBITS) + +#define HGAINVLCBITS 9 +#define HGAINMAX DIV_ROUND_UP(13, HGAINVLCBITS) + +#define VLCBITS 9 +#define VLCMAX DIV_ROUND_UP(22, VLCBITS) + +#define SINE_WINDOW(x) float sine_ ## x[x] __a_aligned(16) + +SINE_WINDOW(128); +SINE_WINDOW(256); +SINE_WINDOW(512); +SINE_WINDOW(1024); +SINE_WINDOW(2048); +SINE_WINDOW(4096); + +static float *sine_windows[6] = { + sine_128, sine_256, sine_512, sine_1024, sine_2048, sine_4096 +}; + +/* Generate a sine window. */ +static void sine_window_init(float *window, int n) +{ + int i; + + for (i = 0; i < n; i++) + window[i] = sinf((i + 0.5) * (M_PI / (2.0 * n))); +} + +static void wmadec_cleanup(struct private_wmadec_data *pwd) +{ + int i; + + for (i = 0; i < pwd->nb_block_sizes; i++) + imdct_end(pwd->mdct_ctx[i]); + if (pwd->use_exp_vlc) + free_vlc(&pwd->exp_vlc); + if (pwd->use_noise_coding) + free_vlc(&pwd->hgain_vlc); + for (i = 0; i < 2; i++) { + free_vlc(&pwd->coef_vlc[i]); + free(pwd->run_table[i]); + free(pwd->level_table[i]); + } +} + +static void init_coef_vlc(struct vlc *vlc, uint16_t **prun_table, + uint16_t **plevel_table, const struct coef_vlc_table *vlc_table) +{ + int n = vlc_table->n; + const uint8_t *table_bits = vlc_table->huffbits; + const uint32_t *table_codes = vlc_table->huffcodes; + const uint16_t *levels_table = vlc_table->levels; + uint16_t *run_table, *level_table; + int i, l, j, k, level; + + init_vlc(vlc, VLCBITS, n, table_bits, table_codes, 4); + + run_table = para_malloc(n * sizeof(uint16_t)); + level_table = para_malloc(n * sizeof(uint16_t)); + i = 2; + level = 1; + k = 0; + while (i < n) { + l = levels_table[k++]; + for (j = 0; j < l; j++) { + run_table[i] = j; + level_table[i] = level; + i++; + } + level++; + } + *prun_table = run_table; + *plevel_table = level_table; +} + +/* compute the scale factor band sizes for each MDCT block size */ +static void compute_scale_factor_band_sizes(struct private_wmadec_data *pwd, + float high_freq) +{ + struct asf_header_info *ahi = &pwd->ahi; + int a, b, pos, lpos, k, block_len, i, j, n; + const uint8_t *table; + + for (k = 0; k < pwd->nb_block_sizes; k++) { + block_len = pwd->frame_len >> k; + + table = NULL; + a = pwd->frame_len_bits - BLOCK_MIN_BITS - k; + if (a < 3) { + if (ahi->sample_rate >= 44100) + table = exponent_band_44100[a]; + else if (ahi->sample_rate >= 32000) + table = exponent_band_32000[a]; + else if (ahi->sample_rate >= 22050) + table = exponent_band_22050[a]; + } + if (table) { + n = *table++; + for (i = 0; i < n; i++) + pwd->exponent_bands[k][i] = table[i]; + pwd->exponent_sizes[k] = n; + } else { + j = 0; + lpos = 0; + for (i = 0; i < 25; i++) { + a = wma_critical_freqs[i]; + b = ahi->sample_rate; + pos = ((block_len * 2 * a) + (b << 1)) / (4 * b); + pos <<= 2; + if (pos > block_len) + pos = block_len; + if (pos > lpos) + pwd->exponent_bands[k][j++] = pos - lpos; + if (pos >= block_len) + break; + lpos = pos; + } + pwd->exponent_sizes[k] = j; + } + + /* max number of coefs */ + pwd->coefs_end[k] = (pwd->frame_len - ((pwd->frame_len * 9) / 100)) >> k; + /* high freq computation */ + pwd->high_band_start[k] = (int) ((block_len * 2 * high_freq) + / ahi->sample_rate + 0.5); + n = pwd->exponent_sizes[k]; + j = 0; + pos = 0; + for (i = 0; i < n; i++) { + int start, end; + start = pos; + pos += pwd->exponent_bands[k][i]; + end = pos; + if (start < pwd->high_band_start[k]) + start = pwd->high_band_start[k]; + if (end > pwd->coefs_end[k]) + end = pwd->coefs_end[k]; + if (end > start) + pwd->exponent_high_bands[k][j++] = end - start; + } + pwd->exponent_high_sizes[k] = j; + } +} + +static int wma_init(struct private_wmadec_data *pwd) +{ + int i; + float bps1, high_freq; + volatile float bps; + int sample_rate1; + int coef_vlc_table; + struct asf_header_info *ahi = &pwd->ahi; + int flags2 = ahi->flags2; + + if (ahi->sample_rate <= 0 || ahi->sample_rate > 50000 + || ahi->channels <= 0 || ahi->channels > 8 + || ahi->bit_rate <= 0) + return -E_WMA_BAD_PARAMS; + + /* compute MDCT block size */ + if (ahi->sample_rate <= 16000) + pwd->frame_len_bits = 9; + else if (ahi->sample_rate <= 22050) + pwd->frame_len_bits = 10; + else + pwd->frame_len_bits = 11; + pwd->frame_len = 1 << pwd->frame_len_bits; + if (pwd->use_variable_block_len) { + int nb_max, nb; + nb = ((flags2 >> 3) & 3) + 1; + if ((ahi->bit_rate / ahi->channels) >= 32000) + nb += 2; + nb_max = pwd->frame_len_bits - BLOCK_MIN_BITS; + if (nb > nb_max) + nb = nb_max; + pwd->nb_block_sizes = nb + 1; + } else + pwd->nb_block_sizes = 1; + + /* init rate dependent parameters */ + pwd->use_noise_coding = 1; + high_freq = ahi->sample_rate * 0.5; + + /* wma2 rates are normalized */ + sample_rate1 = ahi->sample_rate; + if (sample_rate1 >= 44100) + sample_rate1 = 44100; + else if (sample_rate1 >= 22050) + sample_rate1 = 22050; + else if (sample_rate1 >= 16000) + sample_rate1 = 16000; + else if (sample_rate1 >= 11025) + sample_rate1 = 11025; + else if (sample_rate1 >= 8000) + sample_rate1 = 8000; + + bps = (float) ahi->bit_rate / (float) (ahi->channels * ahi->sample_rate); + pwd->byte_offset_bits = wma_log2((int) (bps * pwd->frame_len / 8.0 + 0.5)) + 2; + /* + * Compute high frequency value and choose if noise coding should be + * activated. + */ + bps1 = bps; + if (ahi->channels == 2) + bps1 = bps * 1.6; + if (sample_rate1 == 44100) { + if (bps1 >= 0.61) + pwd->use_noise_coding = 0; + else + high_freq = high_freq * 0.4; + } else if (sample_rate1 == 22050) { + if (bps1 >= 1.16) + pwd->use_noise_coding = 0; + else if (bps1 >= 0.72) + high_freq = high_freq * 0.7; + else + high_freq = high_freq * 0.6; + } else if (sample_rate1 == 16000) { + if (bps > 0.5) + high_freq = high_freq * 0.5; + else + high_freq = high_freq * 0.3; + } else if (sample_rate1 == 11025) + high_freq = high_freq * 0.7; + else if (sample_rate1 == 8000) { + if (bps <= 0.625) + high_freq = high_freq * 0.5; + else if (bps > 0.75) + pwd->use_noise_coding = 0; + else + high_freq = high_freq * 0.65; + } else { + if (bps >= 0.8) + high_freq = high_freq * 0.75; + else if (bps >= 0.6) + high_freq = high_freq * 0.6; + else + high_freq = high_freq * 0.5; + } + PARA_INFO_LOG("channels=%d sample_rate=%d " + "bitrate=%d block_align=%d\n", + ahi->channels, ahi->sample_rate, + ahi->bit_rate, ahi->block_align); + PARA_INFO_LOG("frame_len=%d, bps=%f bps1=%f " + "high_freq=%f bitoffset=%d\n", + pwd->frame_len, bps, bps1, + high_freq, pwd->byte_offset_bits); + PARA_INFO_LOG("use_noise_coding=%d use_exp_vlc=%d nb_block_sizes=%d\n", + pwd->use_noise_coding, pwd->use_exp_vlc, pwd->nb_block_sizes); + + compute_scale_factor_band_sizes(pwd, high_freq); + /* init MDCT windows : simple sinus window */ + for (i = 0; i < pwd->nb_block_sizes; i++) { + int n; + n = 1 << (pwd->frame_len_bits - i); + sine_window_init(sine_windows[pwd->frame_len_bits - i - 7], n); + pwd->windows[i] = sine_windows[pwd->frame_len_bits - i - 7]; + } + + pwd->reset_block_lengths = 1; + + if (pwd->use_noise_coding) { + /* init the noise generator */ + if (pwd->use_exp_vlc) + pwd->noise_mult = 0.02; + else + pwd->noise_mult = 0.04; + + { + unsigned int seed; + float norm; + seed = 1; + norm = (1.0 / (float) (1LL << 31)) * sqrt(3) * pwd->noise_mult; + for (i = 0; i < NOISE_TAB_SIZE; i++) { + seed = seed * 314159 + 1; + pwd->noise_table[i] = (float) ((int) seed) * norm; + } + } + } + + /* choose the VLC tables for the coefficients */ + coef_vlc_table = 2; + if (ahi->sample_rate >= 32000) { + if (bps1 < 0.72) + coef_vlc_table = 0; + else if (bps1 < 1.16) + coef_vlc_table = 1; + } + pwd->coef_vlcs[0] = &coef_vlcs[coef_vlc_table * 2]; + pwd->coef_vlcs[1] = &coef_vlcs[coef_vlc_table * 2 + 1]; + init_coef_vlc(&pwd->coef_vlc[0], &pwd->run_table[0], &pwd->level_table[0], + pwd->coef_vlcs[0]); + init_coef_vlc(&pwd->coef_vlc[1], &pwd->run_table[1], &pwd->level_table[1], + pwd->coef_vlcs[1]); + return 0; +} + +static void wma_lsp_to_curve_init(struct private_wmadec_data *pwd, int frame_len) +{ + float wdel, a, b; + int i, e, m; + + wdel = M_PI / frame_len; + for (i = 0; i < frame_len; i++) + pwd->lsp_cos_table[i] = 2.0f * cos(wdel * i); + + /* tables for x^-0.25 computation */ + for (i = 0; i < 256; i++) { + e = i - 126; + pwd->lsp_pow_e_table[i] = pow(2.0, e * -0.25); + } + + /* These two tables are needed to avoid two operations in pow_m1_4. */ + b = 1.0; + for (i = (1 << LSP_POW_BITS) - 1; i >= 0; i--) { + m = (1 << LSP_POW_BITS) + i; + a = (float) m *(0.5 / (1 << LSP_POW_BITS)); + a = pow(a, -0.25); + pwd->lsp_pow_m_table1[i] = 2 * a - b; + pwd->lsp_pow_m_table2[i] = b - a; + b = a; + } +} + +static int wma_decode_init(char *initial_buf, int len, struct private_wmadec_data **result) +{ + struct private_wmadec_data *pwd; + int ret, i; + + PARA_NOTICE_LOG("initial buf: %d bytes\n", len); + pwd = para_calloc(sizeof(*pwd)); + ret = read_asf_header(initial_buf, len, &pwd->ahi); + if (ret <= 0) { + free(pwd); + return ret; + } + + pwd->use_exp_vlc = pwd->ahi.flags2 & 0x0001; + pwd->use_bit_reservoir = pwd->ahi.flags2 & 0x0002; + pwd->use_variable_block_len = pwd->ahi.flags2 & 0x0004; + + ret = wma_init(pwd); + if (ret < 0) + return ret; + /* init MDCT */ + for (i = 0; i < pwd->nb_block_sizes; i++) { + ret = imdct_init(pwd->frame_len_bits - i + 1, &pwd->mdct_ctx[i]); + if (ret < 0) + return ret; + } + if (pwd->use_noise_coding) { + PARA_INFO_LOG("using noise coding\n"); + init_vlc(&pwd->hgain_vlc, HGAINVLCBITS, + sizeof(wma_hgain_huffbits), wma_hgain_huffbits, + wma_hgain_huffcodes, 2); + } + + if (pwd->use_exp_vlc) { + PARA_INFO_LOG("using exp_vlc\n"); + init_vlc(&pwd->exp_vlc, EXPVLCBITS, + sizeof(wma_scale_huffbits), wma_scale_huffbits, + wma_scale_huffcodes, 4); + } else { + PARA_INFO_LOG("using curve\n"); + wma_lsp_to_curve_init(pwd, pwd->frame_len); + } + *result = pwd; + return pwd->ahi.header_len; +} + +/** + * compute x^-0.25 with an exponent and mantissa table. We use linear + * interpolation to reduce the mantissa table size at a small speed + * expense (linear interpolation approximately doubles the number of + * bits of precision). + */ +static inline float pow_m1_4(struct private_wmadec_data *pwd, float x) +{ + union { + float f; + unsigned int v; + } u, t; + unsigned int e, m; + float a, b; + + u.f = x; + e = u.v >> 23; + m = (u.v >> (23 - LSP_POW_BITS)) & ((1 << LSP_POW_BITS) - 1); + /* build interpolation scale: 1 <= t < 2. */ + t.v = ((u.v << LSP_POW_BITS) & ((1 << 23) - 1)) | (127 << 23); + a = pwd->lsp_pow_m_table1[m]; + b = pwd->lsp_pow_m_table2[m]; + return pwd->lsp_pow_e_table[e] * (a + b * t.f); +} + +static void wma_lsp_to_curve(struct private_wmadec_data *pwd, + float *out, float *val_max_ptr, int n, float *lsp) +{ + int i, j; + float p, q, w, v, val_max; + + val_max = 0; + for (i = 0; i < n; i++) { + p = 0.5f; + q = 0.5f; + w = pwd->lsp_cos_table[i]; + for (j = 1; j < NB_LSP_COEFS; j += 2) { + q *= w - lsp[j - 1]; + p *= w - lsp[j]; + } + p *= p * (2.0f - w); + q *= q * (2.0f + w); + v = p + q; + v = pow_m1_4(pwd, v); + if (v > val_max) + val_max = v; + out[i] = v; + } + *val_max_ptr = val_max; +} + +/* Decode exponents coded with LSP coefficients (same idea as Vorbis). */ +static void decode_exp_lsp(struct private_wmadec_data *pwd, int ch) +{ + float lsp_coefs[NB_LSP_COEFS]; + int val, i; + + for (i = 0; i < NB_LSP_COEFS; i++) { + if (i == 0 || i >= 8) + val = get_bits(&pwd->gb, 3); + else + val = get_bits(&pwd->gb, 4); + lsp_coefs[i] = wma_lsp_codebook[i][val]; + } + + wma_lsp_to_curve(pwd, pwd->exponents[ch], &pwd->max_exponent[ch], + pwd->block_len, lsp_coefs); +} + +/* Decode exponents coded with VLC codes. */ +static int decode_exp_vlc(struct private_wmadec_data *pwd, int ch) +{ + int last_exp, n, code; + const uint16_t *ptr, *band_ptr; + float v, *q, max_scale, *q_end; + + band_ptr = pwd->exponent_bands[pwd->frame_len_bits - pwd->block_len_bits]; + ptr = band_ptr; + q = pwd->exponents[ch]; + q_end = q + pwd->block_len; + max_scale = 0; + last_exp = 36; + + while (q < q_end) { + code = get_vlc(&pwd->gb, pwd->exp_vlc.table, EXPVLCBITS, EXPMAX); + if (code < 0) + return code; + /* NOTE: this offset is the same as MPEG4 AAC ! */ + last_exp += code - 60; + /* XXX: use a table */ + v = pow(10, last_exp * (1.0 / 16.0)); + if (v > max_scale) + max_scale = v; + n = *ptr++; + do { + *q++ = v; + } while (--n); + } + pwd->max_exponent[ch] = max_scale; + return 0; +} + +/* compute src0 * src1 + src2 */ +static inline void vector_mult_add(float *dst, const float *src0, const float *src1, + const float *src2, int len) +{ + int i; + + for (i = 0; i < len; i++) + dst[i] = src0[i] * src1[i] + src2[i]; +} + +static inline void vector_mult_reverse(float *dst, const float *src0, + const float *src1, int len) +{ + int i; + + src1 += len - 1; + for (i = 0; i < len; i++) + dst[i] = src0[i] * src1[-i]; +} + +/** + * Apply MDCT window and add into output. + * + * We ensure that when the windows overlap their squared sum + * is always 1 (MDCT reconstruction rule). + */ +static void wma_window(struct private_wmadec_data *pwd, float *out) +{ + float *in = pwd->output; + int block_len, bsize, n; + + /* left part */ + if (pwd->block_len_bits <= pwd->prev_block_len_bits) { + block_len = pwd->block_len; + bsize = pwd->frame_len_bits - pwd->block_len_bits; + vector_mult_add(out, in, pwd->windows[bsize], out, block_len); + } else { + block_len = 1 << pwd->prev_block_len_bits; + n = (pwd->block_len - block_len) / 2; + bsize = pwd->frame_len_bits - pwd->prev_block_len_bits; + vector_mult_add(out + n, in + n, pwd->windows[bsize], out + n, + block_len); + memcpy(out + n + block_len, in + n + block_len, + n * sizeof(float)); + } + out += pwd->block_len; + in += pwd->block_len; + /* right part */ + if (pwd->block_len_bits <= pwd->next_block_len_bits) { + block_len = pwd->block_len; + bsize = pwd->frame_len_bits - pwd->block_len_bits; + vector_mult_reverse(out, in, pwd->windows[bsize], block_len); + } else { + block_len = 1 << pwd->next_block_len_bits; + n = (pwd->block_len - block_len) / 2; + bsize = pwd->frame_len_bits - pwd->next_block_len_bits; + memcpy(out, in, n * sizeof(float)); + vector_mult_reverse(out + n, in + n, pwd->windows[bsize], + block_len); + memset(out + n + block_len, 0, n * sizeof(float)); + } +} + +static int wma_total_gain_to_bits(int total_gain) +{ + if (total_gain < 15) + return 13; + else if (total_gain < 32) + return 12; + else if (total_gain < 40) + return 11; + else if (total_gain < 45) + return 10; + else + return 9; +} + +static int compute_high_band_values(struct private_wmadec_data *pwd, + int bsize, int nb_coefs[MAX_CHANNELS]) +{ + int ch; + + if (!pwd->use_noise_coding) + return 0; + for (ch = 0; ch < pwd->ahi.channels; ch++) { + int i, m, a; + if (!pwd->channel_coded[ch]) + continue; + m = pwd->exponent_high_sizes[bsize]; + for (i = 0; i < m; i++) { + a = get_bit(&pwd->gb); + pwd->high_band_coded[ch][i] = a; + if (!a) + continue; + nb_coefs[ch] -= pwd->exponent_high_bands[bsize][i]; + } + } + for (ch = 0; ch < pwd->ahi.channels; ch++) { + int i, n, val; + if (!pwd->channel_coded[ch]) + continue; + n = pwd->exponent_high_sizes[bsize]; + val = (int)0x80000000; + for (i = 0; i < n; i++) { + if (!pwd->high_band_coded[ch][i]) + continue; + if (val == (int)0x80000000) + val = get_bits(&pwd->gb, 7) - 19; + else { + int code = get_vlc(&pwd->gb, + pwd->hgain_vlc.table, HGAINVLCBITS, + HGAINMAX); + if (code < 0) + return code; + val += code - 18; + } + pwd->high_band_values[ch][i] = val; + } + } + return 1; +} + +static void compute_mdct_coefficients(struct private_wmadec_data *pwd, + int bsize, int total_gain, int nb_coefs[MAX_CHANNELS]) +{ + int ch; + float mdct_norm = 1.0 / (pwd->block_len / 2); + + for (ch = 0; ch < pwd->ahi.channels; ch++) { + int16_t *coefs1; + float *coefs, *exponents, mult, mult1, noise; + int i, j, n, n1, last_high_band, esize; + float exp_power[HIGH_BAND_MAX_SIZE]; + + if (!pwd->channel_coded[ch]) + continue; + coefs1 = pwd->coefs1[ch]; + exponents = pwd->exponents[ch]; + esize = pwd->exponents_bsize[ch]; + mult = pow(10, total_gain * 0.05) / pwd->max_exponent[ch]; + mult *= mdct_norm; + coefs = pwd->coefs[ch]; + if (!pwd->use_noise_coding) { + /* XXX: optimize more */ + n = nb_coefs[ch]; + for (i = 0; i < n; i++) + *coefs++ = coefs1[i] * + exponents[i << bsize >> esize] * mult; + n = pwd->block_len - pwd->coefs_end[bsize]; + for (i = 0; i < n; i++) + *coefs++ = 0.0; + continue; + } + mult1 = mult; + n1 = pwd->exponent_high_sizes[bsize]; + /* compute power of high bands */ + exponents = pwd->exponents[ch] + + (pwd->high_band_start[bsize] << bsize); + last_high_band = 0; /* avoid warning */ + for (j = 0; j < n1; j++) { + n = pwd->exponent_high_bands[ + pwd->frame_len_bits - pwd->block_len_bits][j]; + if (pwd->high_band_coded[ch][j]) { + float e2, val; + e2 = 0; + for (i = 0; i < n; i++) { + val = exponents[i << bsize >> esize]; + e2 += val * val; + } + exp_power[j] = e2 / n; + last_high_band = j; + } + exponents += n << bsize; + } + /* main freqs and high freqs */ + exponents = pwd->exponents[ch]; + for (j = -1; j < n1; j++) { + if (j < 0) + n = pwd->high_band_start[bsize]; + else + n = pwd->exponent_high_bands[pwd->frame_len_bits + - pwd->block_len_bits][j]; + if (j >= 0 && pwd->high_band_coded[ch][j]) { + /* use noise with specified power */ + mult1 = sqrt(exp_power[j] + / exp_power[last_high_band]); + /* XXX: use a table */ + mult1 = mult1 * pow(10, + pwd->high_band_values[ch][j] * 0.05); + mult1 /= (pwd->max_exponent[ch] * pwd->noise_mult); + mult1 *= mdct_norm; + for (i = 0; i < n; i++) { + noise = pwd->noise_table[pwd->noise_index]; + pwd->noise_index = (pwd->noise_index + 1) + & (NOISE_TAB_SIZE - 1); + *coefs++ = noise * exponents[ + i << bsize >> esize] * mult1; + } + exponents += n << bsize; + } else { + /* coded values + small noise */ + for (i = 0; i < n; i++) { + noise = pwd->noise_table[pwd->noise_index]; + pwd->noise_index = (pwd->noise_index + 1) + & (NOISE_TAB_SIZE - 1); + *coefs++ = ((*coefs1++) + noise) * + exponents[i << bsize >> esize] + * mult; + } + exponents += n << bsize; + } + } + /* very high freqs: noise */ + n = pwd->block_len - pwd->coefs_end[bsize]; + mult1 = mult * exponents[((-1 << bsize)) >> esize]; + for (i = 0; i < n; i++) { + *coefs++ = pwd->noise_table[pwd->noise_index] * mult1; + pwd->noise_index = (pwd->noise_index + 1) + & (NOISE_TAB_SIZE - 1); + } + } +} + +/** + * Returns 0 if OK, 1 if last block of frame, negative on uncorrectable + * errors. + */ +static int wma_decode_block(struct private_wmadec_data *pwd) +{ + int ret, n, v, ch, code, bsize; + int coef_nb_bits, total_gain; + int nb_coefs[MAX_CHANNELS]; + + /* compute current block length */ + if (pwd->use_variable_block_len) { + n = wma_log2(pwd->nb_block_sizes - 1) + 1; + + if (pwd->reset_block_lengths) { + pwd->reset_block_lengths = 0; + v = get_bits(&pwd->gb, n); + if (v >= pwd->nb_block_sizes) + return -E_WMA_BLOCK_SIZE; + pwd->prev_block_len_bits = pwd->frame_len_bits - v; + v = get_bits(&pwd->gb, n); + if (v >= pwd->nb_block_sizes) + return -E_WMA_BLOCK_SIZE; + pwd->block_len_bits = pwd->frame_len_bits - v; + } else { + /* update block lengths */ + pwd->prev_block_len_bits = pwd->block_len_bits; + pwd->block_len_bits = pwd->next_block_len_bits; + } + v = get_bits(&pwd->gb, n); + if (v >= pwd->nb_block_sizes) + return -E_WMA_BLOCK_SIZE; + pwd->next_block_len_bits = pwd->frame_len_bits - v; + } else { + /* fixed block len */ + pwd->next_block_len_bits = pwd->frame_len_bits; + pwd->prev_block_len_bits = pwd->frame_len_bits; + pwd->block_len_bits = pwd->frame_len_bits; + } + + /* now check if the block length is coherent with the frame length */ + pwd->block_len = 1 << pwd->block_len_bits; + if ((pwd->block_pos + pwd->block_len) > pwd->frame_len) + return -E_INCOHERENT_BLOCK_LEN; + + if (pwd->ahi.channels == 2) + pwd->ms_stereo = get_bit(&pwd->gb); + v = 0; + for (ch = 0; ch < pwd->ahi.channels; ch++) { + int a = get_bit(&pwd->gb); + pwd->channel_coded[ch] = a; + v |= a; + } + + bsize = pwd->frame_len_bits - pwd->block_len_bits; + + /* if no channel coded, no need to go further */ + /* XXX: fix potential framing problems */ + if (!v) + goto next; + + /* + * Read total gain and extract corresponding number of bits for coef + * escape coding. + */ + total_gain = 1; + for (;;) { + int a = get_bits(&pwd->gb, 7); + total_gain += a; + if (a != 127) + break; + } + + coef_nb_bits = wma_total_gain_to_bits(total_gain); + + /* compute number of coefficients */ + n = pwd->coefs_end[bsize]; + for (ch = 0; ch < pwd->ahi.channels; ch++) + nb_coefs[ch] = n; + + ret = compute_high_band_values(pwd, bsize, nb_coefs); + if (ret < 0) + return ret; + + /* exponents can be reused in short blocks. */ + if ((pwd->block_len_bits == pwd->frame_len_bits) || get_bit(&pwd->gb)) { + for (ch = 0; ch < pwd->ahi.channels; ch++) { + if (pwd->channel_coded[ch]) { + if (pwd->use_exp_vlc) { + ret = decode_exp_vlc(pwd, ch); + if (ret < 0) + return ret; + } else + decode_exp_lsp(pwd, ch); + pwd->exponents_bsize[ch] = bsize; + } + } + } + + /* parse spectral coefficients : just RLE encoding */ + for (ch = 0; ch < pwd->ahi.channels; ch++) { + struct vlc *coef_vlc; + int level, run, tindex; + int16_t *ptr, *eptr; + const uint16_t *level_table, *run_table; + + if (!pwd->channel_coded[ch]) + continue; + /* + * special VLC tables are used for ms stereo because there is + * potentially less energy there + */ + tindex = (ch == 1 && pwd->ms_stereo); + coef_vlc = &pwd->coef_vlc[tindex]; + run_table = pwd->run_table[tindex]; + level_table = pwd->level_table[tindex]; + /* XXX: optimize */ + ptr = &pwd->coefs1[ch][0]; + eptr = ptr + nb_coefs[ch]; + memset(ptr, 0, pwd->block_len * sizeof(int16_t)); + for (;;) { + code = get_vlc(&pwd->gb, coef_vlc->table, + VLCBITS, VLCMAX); + if (code < 0) + return code; + if (code == 1) /* EOB */ + break; + if (code == 0) { /* escape */ + level = get_bits(&pwd->gb, coef_nb_bits); + /* reading block_len_bits would be better */ + run = get_bits(&pwd->gb, pwd->frame_len_bits); + } else { /* normal code */ + run = run_table[code]; + level = level_table[code]; + } + if (!get_bit(&pwd->gb)) + level = -level; + ptr += run; + if (ptr >= eptr) { + PARA_ERROR_LOG("overflow in spectral RLE, ignoring\n"); + break; + } + *ptr++ = level; + if (ptr >= eptr) /* EOB can be omitted */ + break; + } + } + compute_mdct_coefficients(pwd, bsize, total_gain, nb_coefs); + if (pwd->ms_stereo && pwd->channel_coded[1]) { + float a, b; + int i; + /* + * Nominal case for ms stereo: we do it before mdct. + * + * No need to optimize this case because it should almost never + * happen. + */ + if (!pwd->channel_coded[0]) { + PARA_NOTICE_LOG("rare ms-stereo\n"); + memset(pwd->coefs[0], 0, sizeof(float) * pwd->block_len); + pwd->channel_coded[0] = 1; + } + for (i = 0; i < pwd->block_len; i++) { + a = pwd->coefs[0][i]; + b = pwd->coefs[1][i]; + pwd->coefs[0][i] = a + b; + pwd->coefs[1][i] = a - b; + } + } +next: + for (ch = 0; ch < pwd->ahi.channels; ch++) { + int n4, idx; + + n = pwd->block_len; + n4 = pwd->block_len / 2; + if (pwd->channel_coded[ch]) + imdct(pwd->mdct_ctx[bsize], pwd->output, pwd->coefs[ch]); + else if (!(pwd->ms_stereo && ch == 1)) + memset(pwd->output, 0, sizeof(pwd->output)); + + /* multiply by the window and add in the frame */ + idx = (pwd->frame_len / 2) + pwd->block_pos - n4; + wma_window(pwd, &pwd->frame_out[ch][idx]); + } + + /* update block number */ + pwd->block_pos += pwd->block_len; + if (pwd->block_pos >= pwd->frame_len) + return 1; + else + return 0; +} + +/* + * Clip a signed integer value into the -32768,32767 range. + * + * \param a The value to clip. + * + * \return The clipped value. + */ +static inline int16_t av_clip_int16(int a) +{ + if ((a + 32768) & ~65535) + return (a >> 31) ^ 32767; + else + return a; +} + +/* Decode a frame of frame_len samples. */ +static int wma_decode_frame(struct private_wmadec_data *pwd, int16_t *samples) +{ + int ret, i, n, ch, incr; + int16_t *ptr; + float *iptr; + + /* read each block */ + pwd->block_pos = 0; + for (;;) { + ret = wma_decode_block(pwd); + if (ret < 0) + return ret; + if (ret) + break; + } + + /* convert frame to integer */ + n = pwd->frame_len; + incr = pwd->ahi.channels; + for (ch = 0; ch < pwd->ahi.channels; ch++) { + ptr = samples + ch; + iptr = pwd->frame_out[ch]; + + for (i = 0; i < n; i++) { + *ptr = av_clip_int16(lrintf(*iptr++)); + ptr += incr; + } + /* prepare for next block */ + memmove(&pwd->frame_out[ch][0], &pwd->frame_out[ch][pwd->frame_len], + pwd->frame_len * sizeof(float)); + } + return 0; +} + +static int wma_decode_superframe(struct private_wmadec_data *pwd, void *data, + int *data_size, const uint8_t *buf, int buf_size) +{ + int ret; + int16_t *samples; + + if (buf_size == 0) { + pwd->last_superframe_len = 0; + return 0; + } + if (buf_size < pwd->ahi.block_align) + return 0; + buf_size = pwd->ahi.block_align; + samples = data; + init_get_bits(&pwd->gb, buf, buf_size); + if (pwd->use_bit_reservoir) { + int i, nb_frames, bit_offset, pos, len; + uint8_t *q; + + /* read super frame header */ + skip_bits(&pwd->gb, 4); /* super frame index */ + nb_frames = get_bits(&pwd->gb, 4) - 1; + // PARA_DEBUG_LOG("have %d frames\n", nb_frames); + ret = -E_WMA_OUTPUT_SPACE; + if ((nb_frames + 1) * pwd->ahi.channels * pwd->frame_len + * sizeof(int16_t) > *data_size) + goto fail; + + bit_offset = get_bits(&pwd->gb, pwd->byte_offset_bits + 3); + + if (pwd->last_superframe_len > 0) { + /* add bit_offset bits to last frame */ + ret = -E_WMA_BAD_SUPERFRAME; + if ((pwd->last_superframe_len + ((bit_offset + 7) >> 3)) > + MAX_CODED_SUPERFRAME_SIZE) + goto fail; + q = pwd->last_superframe + pwd->last_superframe_len; + len = bit_offset; + while (len > 7) { + *q++ = get_bits(&pwd->gb, 8); + len -= 8; + } + if (len > 0) + *q++ = get_bits(&pwd->gb, len) << (8 - len); + + /* XXX: bit_offset bits into last frame */ + init_get_bits(&pwd->gb, pwd->last_superframe, + MAX_CODED_SUPERFRAME_SIZE); + /* skip unused bits */ + if (pwd->last_bitoffset > 0) + skip_bits(&pwd->gb, pwd->last_bitoffset); + /* + * This frame is stored in the last superframe and in + * the current one. + */ + ret = wma_decode_frame(pwd, samples); + if (ret < 0) + goto fail; + samples += pwd->ahi.channels * pwd->frame_len; + } + + /* read each frame starting from bit_offset */ + pos = bit_offset + 4 + 4 + pwd->byte_offset_bits + 3; + init_get_bits(&pwd->gb, buf + (pos >> 3), + (MAX_CODED_SUPERFRAME_SIZE - (pos >> 3))); + len = pos & 7; + if (len > 0) + skip_bits(&pwd->gb, len); + + pwd->reset_block_lengths = 1; + for (i = 0; i < nb_frames; i++) { + ret = wma_decode_frame(pwd, samples); + if (ret < 0) + goto fail; + samples += pwd->ahi.channels * pwd->frame_len; + } + + /* we copy the end of the frame in the last frame buffer */ + pos = get_bits_count(&pwd->gb) + + ((bit_offset + 4 + 4 + pwd->byte_offset_bits + 3) & ~7); + pwd->last_bitoffset = pos & 7; + pos >>= 3; + len = buf_size - pos; + ret = -E_WMA_BAD_SUPERFRAME; + if (len > MAX_CODED_SUPERFRAME_SIZE || len < 0) + goto fail; + pwd->last_superframe_len = len; + memcpy(pwd->last_superframe, buf + pos, len); + } else { + PARA_DEBUG_LOG("not using bit reservoir\n"); + ret = -E_WMA_OUTPUT_SPACE; + if (pwd->ahi.channels * pwd->frame_len * sizeof(int16_t) > *data_size) + goto fail; + /* single frame decode */ + ret = wma_decode_frame(pwd, samples); + if (ret < 0) + goto fail; + samples += pwd->ahi.channels * pwd->frame_len; + } + PARA_DEBUG_LOG("frame_len: %d, block_len: %d, outbytes: %d, eaten: %d\n", + pwd->frame_len, pwd->block_len, + (int)((int8_t *)samples - (int8_t *)data), pwd->ahi.block_align); + *data_size = (int8_t *)samples - (int8_t *)data; + return pwd->ahi.block_align; +fail: + /* reset the bit reservoir on errors */ + pwd->last_superframe_len = 0; + return ret; +} + +static void wmadec_close(struct filter_node *fn) +{ + struct private_wmadec_data *pwd = fn->private_data; + + if (!pwd) + return; + wmadec_cleanup(pwd); + free(fn->private_data); + fn->private_data = NULL; +} + +static int wmadec_execute(struct btr_node *btrn, const char *cmd, char **result) +{ + struct filter_node *fn = btr_context(btrn); + struct private_wmadec_data *pwd = fn->private_data; + + return decoder_execute(cmd, pwd->ahi.sample_rate, pwd->ahi.channels, + result); +} + +#define WMA_OUTPUT_BUFFER_SIZE (128 * 1024) + +static void wmadec_post_select(__a_unused struct sched *s, struct task *t) +{ + struct filter_node *fn = container_of(t, struct filter_node, task); + int ret, converted; + struct private_wmadec_data *pwd = fn->private_data; + struct btr_node *btrn = fn->btrn; + size_t len; + char *in; + +next_buffer: + converted = 0; + t->error = 0; + ret = btr_node_status(btrn, fn->min_iqs, BTR_NT_INTERNAL); + if (ret < 0) + goto err; + if (ret == 0) + return; + btr_merge(btrn, fn->min_iqs); + len = btr_next_buffer(btrn, (char **)&in); + ret = -E_WMADEC_EOF; + if (len < fn->min_iqs) + goto err; + if (!pwd) { + ret = wma_decode_init(in, len, &pwd); + if (ret < 0) + goto err; + if (ret == 0) { + fn->min_iqs += 4096; + goto next_buffer; + } + fn->min_iqs = 2 * (WMA_FRAME_SKIP + pwd->ahi.block_align); + fn->private_data = pwd; + converted = pwd->ahi.header_len; + goto success; + } + fn->min_iqs = WMA_FRAME_SKIP + pwd->ahi.block_align; + for (;;) { + char *out; + int out_size = WMA_OUTPUT_BUFFER_SIZE; + if (converted + fn->min_iqs > len) + break; + out = para_malloc(WMA_OUTPUT_BUFFER_SIZE); + ret = wma_decode_superframe(pwd, out, + &out_size, (uint8_t *)in + converted + WMA_FRAME_SKIP, + len - WMA_FRAME_SKIP); + if (ret < 0) { + free(out); + goto err; + } + btr_add_output(out, out_size, btrn); + converted += ret + WMA_FRAME_SKIP; + } +success: + btr_consume(btrn, converted); + return; +err: + assert(ret < 0); + t->error = ret; + btr_remove_node(btrn); +} + +static void wmadec_open(struct filter_node *fn) +{ + fn->private_data = NULL; + fn->min_iqs = 4096; +} + +/** + * The init function of the wma decoder. + * + * \param f Its fields are filled in by the function. + */ +void wmadec_filter_init(struct filter *f) +{ + f->open = wmadec_open; + f->close = wmadec_close; + f->execute = wmadec_execute; + f->pre_select = generic_filter_pre_select; + f->post_select = wmadec_post_select; +} diff --git a/write.c b/write.c index c12736a6..dda1b705 100644 --- a/write.c +++ b/write.c @@ -1,13 +1,15 @@ /* - * Copyright (C) 2005-2009 Andre Noll + * Copyright (C) 2005-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file write.c Paraslash's standalone wav/raw player. */ +#include #include #include +#include #include "para.h" #include "string.h" @@ -16,6 +18,7 @@ #include "sched.h" #include "ggo.h" #include "stdin.h" +#include "buffer_tree.h" #include "write.h" #include "write_common.h" #include "fd.h" @@ -23,146 +26,130 @@ INIT_WRITE_ERRLISTS; -/** Check if given buffer contains a valid wave header. */ +enum check_wav_state { + CWS_NEED_HEADER, + CWS_HAVE_HEADER, + CWS_NO_HEADER, +}; + +/* Information extracted from the wav header. */ struct check_wav_task { - /** The buffer to check. */ - char *buf; - /** Number of bytes loaded in \a buf. */ - size_t *loaded; - /** Non-zero if an error occurred or end of file was reached. */ - int *input_error; - /** Number of channels specified in wav header given by \a buf. */ + int state; + /** Number of channels. */ unsigned channels; + unsigned sample_format; /** Sample rate specified in wav header given by \a buf. */ - unsigned samplerate; + unsigned sample_rate; /** The task structure used by the scheduler. */ struct task task; -}; - -/** Delay writing until given time. */ -struct initial_delay_task { - /** The time the first data should be written out. */ - struct timeval start_time; - /** The task structure for this task. */ - struct task task; + struct btr_node *btrn; + size_t min_iqs; }; static struct write_args_info conf; static struct stdin_task sit; -static struct check_wav_task the_check_wav_task; -static struct initial_delay_task the_initial_delay_task; - -static struct writer_node_group *wng; - /** Length of a standard wav header. */ #define WAV_HEADER_LEN 44 -/** - * Test if audio buffer contains a valid wave header. - * - * \return If not, return -E_NO_WAV_HEADER, otherwise, return zero. If - * there is less than WAV_HEADER_LEN bytes available, return one. - */ -static void check_wav_pre_select(__a_unused struct sched *s, struct task *t) +static void check_wav_pre_select(struct sched *s, struct task *t) { struct check_wav_task *cwt = container_of(t, struct check_wav_task, task); - unsigned char *a; int ret; - if (*cwt->loaded < WAV_HEADER_LEN) { - if (*cwt->input_error < 0) - t->error = *cwt->input_error; - return; - } - cwt->channels = 2; - cwt->samplerate = 44100; - a = (unsigned char*)cwt->buf; - if (a[0] != 'R' || a[1] != 'I' || a[2] != 'F' || a[3] != 'F') { - PARA_NOTICE_LOG("wav header not found\n"); - t->error = -E_NO_WAV_HEADER; - goto out; - } - cwt->channels = (unsigned) a[22]; - cwt->samplerate = a[24] + (a[25] << 8) + (a[26] << 16) + (a[27] << 24); - *cwt->loaded -= WAV_HEADER_LEN; - memmove(cwt->buf, cwt->buf + WAV_HEADER_LEN, *cwt->loaded); - t->error = -E_WAV_HEADER_SUCCESS; - PARA_INFO_LOG("channels: %d, sample rate: %d\n", cwt->channels, cwt->samplerate); -out: - wng->channels = &cwt->channels; - wng->samplerate = &cwt->samplerate; - ret = wng_open(wng); - if (ret < 0) - t->error = ret; - s->timeout.tv_sec = 0; - s->timeout.tv_usec = 1; + ret = btr_node_status(cwt->btrn, cwt->min_iqs, BTR_NT_INTERNAL); + if (ret != 0) + sched_min_delay(s); } -static void initial_delay_pre_select(struct sched *s, struct task *t) +#define HANDLE_EXEC(_cmd) \ + if (!strcmp(cmd, #_cmd)) { \ + if (!conf._cmd ## _given && cwt->state == CWS_NEED_HEADER) \ + return -E_BTR_NAVAIL; \ + *result = make_message("%d", cwt->state == CWS_NO_HEADER || conf._cmd ## _given? \ + conf._cmd ## _arg : cwt->_cmd); \ + return 1; \ + } \ + + +static int check_wav_exec(struct btr_node *btrn, const char *cmd, char **result) { - struct initial_delay_task *idt = container_of(t, struct initial_delay_task, task); - struct timeval diff; + struct check_wav_task *cwt = btr_context(btrn); - if (!idt->start_time.tv_sec && !idt->start_time.tv_usec) { - t->error = -E_NO_DELAY; - goto register_check_wav; - } - if (tv_diff(now, &idt->start_time, &diff) > 0) { - t->error = -E_DELAY_TIMEOUT; - goto register_check_wav; - } - if (tv_diff(&s->timeout , &diff, NULL) > 0) - s->timeout = diff; - return; -register_check_wav: - register_task(&the_check_wav_task.task); - s->timeout.tv_sec = 0; - s->timeout.tv_usec = 1; + HANDLE_EXEC(sample_rate); + HANDLE_EXEC(channels); + HANDLE_EXEC(sample_format); + return -ERRNO_TO_PARA_ERROR(ENOTSUP); } -static int loglevel; -INIT_STDERR_LOGGING(loglevel) - -static struct writer_node_group *check_args(void) +static void check_wav_post_select(__a_unused struct sched *s, struct task *t) { - int i, ret = -E_WRITE_SYNTAX; - struct writer_node_group *g = NULL; - struct initial_delay_task *idt = &the_initial_delay_task; + struct check_wav_task *cwt = container_of(t, struct check_wav_task, task); + struct btr_node *btrn = cwt->btrn; + unsigned char *a; + size_t sz; + int ret; + uint16_t bps; /* bits per sample */ + const char *sample_formats[] = {SAMPLE_FORMATS}; - loglevel = get_loglevel_by_name(conf.loglevel_arg); - if (conf.start_time_given) { - long unsigned sec, usec; - if (sscanf(conf.start_time_arg, "%lu:%lu", - &sec, &usec) != 2) - goto out; - idt->start_time.tv_sec = sec; - idt->start_time.tv_usec = usec; - } - if (!conf.writer_given) { - g = setup_default_wng(); - ret = 1; + t->error = 0; + ret = btr_node_status(btrn, cwt->min_iqs, BTR_NT_INTERNAL); + if (ret <= 0) + goto out; + if (cwt->state != CWS_NEED_HEADER) + goto pushdown; + btr_merge(btrn, cwt->min_iqs); + sz = btr_next_buffer(btrn, (char **)&a); + if (sz < cwt->min_iqs) /* file size less than WAV_HEADER_SIZE */ + goto pushdown; + cwt->min_iqs = 0; + /* + * The default byte ordering assumed for WAVE data files is + * little-endian. Files written using the big-endian byte ordering + * scheme have the identifier RIFX instead of RIFF. + */ + if (a[0] != 'R' || a[1] != 'I' || a[2] != 'F' || + (a[3] != 'F' && a[3] != 'X')) { + PARA_NOTICE_LOG("wav header not found\n"); + cwt->state = CWS_NO_HEADER; + sprintf(t->status, "check wav: no header"); goto out; } - g = wng_new(conf.writer_given); - ret = -E_WRITE_SYNTAX; - for (i = 0; i < conf.writer_given; i++) { - int writer_num; - g->writer_nodes[i].conf = check_writer_arg( - conf.writer_arg[i], &writer_num); - if (!g->writer_nodes[i].conf) - goto out; - g->writer_nodes[i].writer_num = writer_num; + PARA_INFO_LOG("found wav header\n"); + cwt->state = CWS_HAVE_HEADER; + sprintf(t->status, "check wav: have header"); + cwt->channels = (unsigned) a[22]; + cwt->sample_rate = a[24] + (a[25] << 8) + (a[26] << 16) + (a[27] << 24); + bps = a[34] + ((unsigned)a[35] << 8); + if (bps != 8 && bps != 16) { + PARA_WARNING_LOG("%u bps not supported, assuming 16\n", bps); + bps = 16; } - ret = 1; + /* + * 8-bit samples are stored as unsigned bytes, ranging from 0 to 255. + * 16-bit samples are stored as 2's-complement signed integers, ranging + * from -32768 to 32767. + */ + if (bps == 8) + cwt->sample_format = SF_U8; + else + cwt->sample_format = (a[3] == 'F')? SF_S16_LE : SF_S16_BE; + PARA_NOTICE_LOG("%dHz, %s, %s\n", cwt->sample_rate, + cwt->channels == 1? "mono" : "stereo", + sample_formats[cwt->sample_format]); + btr_consume(btrn, WAV_HEADER_LEN); +pushdown: + btr_pushdown(btrn); out: - if (ret > 0) - return g; - free(g); - return NULL; + t->error = ret; + if (ret < 0) + btr_remove_node(btrn); } +static int loglevel; +INIT_STDERR_LOGGING(loglevel) + __noreturn static void print_help_and_die(void) { int d = conf.detailed_help_given; @@ -178,14 +165,72 @@ __noreturn static void print_help_and_die(void) exit(0); } +static int main_btr(struct sched *s) +{ + int i, ret; + struct check_wav_task _cwt, *cwt = &_cwt; + struct writer_node *wns; + + loglevel = get_loglevel_by_name(conf.loglevel_arg); + sit.btrn = btr_new_node(&(struct btr_node_description) + EMBRACE(.name = "stdin")); + stdin_set_defaults(&sit); + register_task(&sit.task); + + cwt->state = CWS_NEED_HEADER; + cwt->min_iqs = WAV_HEADER_LEN; + cwt->btrn = btr_new_node(&(struct btr_node_description) + EMBRACE(.name = "check_wav", .parent = sit.btrn, + .handler = check_wav_exec, .context = cwt)); + sprintf(cwt->task.status, "check_wav"); + cwt->task.pre_select = check_wav_pre_select; + cwt->task.post_select = check_wav_post_select; + cwt->task.error = 0; + register_task(&cwt->task); + + ret = -E_WRITE_SYNTAX; + if (!conf.writer_given) { + i = 0; + wns = para_calloc(sizeof(*wns)); + ret = setup_writer_node(NULL, cwt->btrn, wns); + if (ret < 0) + goto out; + i = 1; + } else { + wns = para_calloc(conf.writer_given * sizeof(*wns)); + for (i = 0; i < conf.writer_given; i++) { + ret = setup_writer_node(conf.writer_arg[i], + cwt->btrn, wns + i); + if (ret < 0) + goto out; + } + } + + s->default_timeout.tv_sec = 10; + s->default_timeout.tv_usec = 50000; + ret = schedule(s); +out: + for (i--; i >= 0; i--) { + struct writer_node *wn = wns + i; + struct writer *w = writers + wn->writer_num; + + w->close(wn); + btr_free_node(wn->btrn); + free(wn->conf); + } + free(wns); + btr_free_node(cwt->btrn); + return ret; +} + /** * Para_write's main function. * * \param argc The usual argument counter. * \param argv The usual argument vector. * - * It registers the stdin task, the check_wav_task, the task for initial delay - * and all tasks for actually writing out the stream. + * It sets up and starts the tasks and the buffer tree nodes determined by + * command line options. * * \return \p EXIT_SUCCESS or EXIT_FAILURE */ @@ -193,8 +238,6 @@ int main(int argc, char *argv[]) { int ret = -E_WRITE_SYNTAX; static struct sched s; - struct check_wav_task *cwt = &the_check_wav_task; - struct initial_delay_task *idt = &the_initial_delay_task; writer_init(); write_cmdline_parser(argc, argv, &conf); @@ -202,39 +245,7 @@ int main(int argc, char *argv[]) if (conf.help_given || conf.detailed_help_given) print_help_and_die(); - wng = check_args(); - if (!wng) - goto out; - stdin_set_defaults(&sit); - ret = -ERRNO_TO_PARA_ERROR(EINVAL); - if (conf.bufsize_arg < 0) - goto out; - if (conf.bufsize_arg >= INT_MAX / 1024) - goto out; - sit.bufsize = conf.bufsize_arg * 1024; - sit.buf = para_malloc(sit.bufsize); - - wng->bufp = &sit.buf; - wng->loaded = &sit.loaded; - wng->input_error = &sit.task.error; - - register_task(&sit.task); - - cwt->buf = sit.buf; - cwt->loaded = &sit.loaded; - cwt->input_error = &sit.task.error; - sprintf(cwt->task.status, "check wav"); - cwt->task.pre_select = check_wav_pre_select; - - idt->task.pre_select = initial_delay_pre_select; - sprintf(idt->task.status, "initial_delay"); - register_task(&idt->task); - - s.default_timeout.tv_sec = 10; - s.default_timeout.tv_usec = 0; - ret = schedule(&s); - wng_close(wng); -out: + ret = main_btr(&s); if (ret < 0) { PARA_ERROR_LOG("%s\n", para_strerror(-ret)); exit(EXIT_FAILURE); diff --git a/write.h b/write.h index 8816be73..0044ae4f 100644 --- a/write.h +++ b/write.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ @@ -17,12 +17,11 @@ struct writer_node { int writer_num; /** Writer-specific data. */ void *private_data; - /** Pointer to the group this node belongs to. */ - struct writer_node_group *wng; /** The writer-specific configuration of this node. */ void *conf; - /** How much of the wng's buffer is already written. */ - size_t written; + struct btr_node *btrn; + struct task task; + size_t min_iqs; }; /** Describes one supported writer. */ @@ -37,39 +36,33 @@ struct writer { /** * The command line parser of the writer. * - * It should check whether the command line options given by \a options are - * valid. On success, it should return a pointer to the writer-specific - * configuration data determined by \a options. Note that this might be called - * more than once with different values of \a options. + * It should check whether the command line options given by \a options + * are valid and return a pointer to the writer-specific configuration + * data determined by \a options. This function must either succeed or + * call exit(). Note that parse_config_or_die() might be called more + * than once with different values of \a options. \sa \ref + * free_config(). */ - void *(*parse_config)(const char *options); + void *(*parse_config_or_die)(const char *options); /** - * Open one instance of this writer. + * Dellocate all configuration resources. * - * This function should perform any work necessary to write the incoming - * stream. To this aim, it may allocate its private data structure and store - * a pointer to that structure via the given writer_node parameter. + * This should free whatever was allocated by \ref parse_config_or_die(). */ - int (*open)(struct writer_node *); + void (*free_config)(void *config); /** * Prepare the fd sets for select. * - * This is called from the writer node group task's pre_select(). It - * may use the sched pointer to add any file descriptors or to decrease - * the select timeout. It must return positive on success and negative - * on errors. + * This is called from scheduler. It may use the sched pointer to add + * any file descriptors or to decrease the select timeout. */ - int (*pre_select)(struct sched *s, struct writer_node *wn); + void (*pre_select)(struct sched *s, struct task *t); /** * Write audio data. * - * Called from the post_select function of the wng task. It must keep - * track of the number of bytes consumed from the wng's buffer via - * the \p wn->written variable (which may be modified by the wng handling - * functions). This function must return positive on success and - * negative on errors. + * Called from the post_select function of the writer node's task. */ - int (*post_select)(struct sched *s, struct writer_node *wn); + void (*post_select)(struct sched *s, struct task *t); /** * Close one instance of the writer. * @@ -83,36 +76,9 @@ struct writer { */ void (*shutdown)(struct writer_node *); struct ggo_help help; + btr_command_handler execute; }; -/** - * Describes a set of writer nodes that all write the same stream. - */ -struct writer_node_group { - /** Number of nodes belonging to this group. */ - unsigned num_writers; - /** Array of pointers to the corresponding writer nodes. */ - struct writer_node *writer_nodes; - /** Non-zero if an error or end of file was encountered by the feeding task. */ - int *input_error; - /** Current output buffer. */ - char **bufp; - /** Number of bytes loaded in the output buffer. */ - size_t *loaded; - /** Number of audio channels of the current stream. */ - unsigned int *channels; - /** Sample rate of the current stream. */ - unsigned int *samplerate; - /** The task associated to this group. */ - struct task task; - /** Whether the group is open, i.e. wng_open() was called. */ - int open; - /** Max number of bytes written in the previous post_select() call. */ - int last_written; -}; - -/** Loop over each writer node in a writer group. */ -#define FOR_EACH_WRITER_NODE(i, wng) for (i = 0; i < (wng)->num_writers; i++) /** Loop over each supported writer. */ #define FOR_EACH_WRITER(i) for (i = 0; i < NUM_SUPPORTED_WRITERS; i++) diff --git a/write_common.c b/write_common.c index 2dca309c..5988faf7 100644 --- a/write_common.c +++ b/write_common.c @@ -1,16 +1,20 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ /** \file write_common.c common functions of para_audiod and para_write */ +#include +#include + #include "para.h" #include "string.h" #include "list.h" #include "sched.h" #include "ggo.h" +#include "buffer_tree.h" #include "write.h" #include "error.h" @@ -20,150 +24,6 @@ const char *writer_names[] ={WRITER_NAMES}; /** the array of supported writers */ struct writer writers[NUM_SUPPORTED_WRITERS] = {WRITER_ARRAY}; -static void wng_pre_select(struct sched *s, struct task *t) -{ - struct writer_node_group *g = container_of(t, struct writer_node_group, task); - int i; - - FOR_EACH_WRITER_NODE(i, g) { - struct writer_node *wn = &g->writer_nodes[i]; - struct writer *w = writers + wn->writer_num; - if (!w->pre_select) - continue; - t->error = w->pre_select(s, wn); - if (t->error < 0) - return; - } - /* - * Force a minimal delay if something was written during the previous - * call to wng_post_select(). This is necessary because the filter - * chain might still have data for us which it couldn't convert during - * the previous run due to its buffer size constraints. In this case we - * do not want to wait until the next input data arrives as this could - * lead to buffer underruns. - */ - if (g->last_written == 0) - return; - s->timeout.tv_sec = 0; - s->timeout.tv_usec = 1; -} - -static void wng_post_select(struct sched *s, struct task *t) -{ - struct writer_node_group *g = container_of(t, struct writer_node_group, task); - int i; - size_t min_written = 0, max_written = 0; - - FOR_EACH_WRITER_NODE(i, g) { - struct writer_node *wn = &g->writer_nodes[i]; - struct writer *w = writers + wn->writer_num; - t->error = w->post_select(s, wn); - if (t->error < 0) - return; - if (!i) - min_written = wn->written; - else - min_written = PARA_MIN(min_written, wn->written); - max_written = PARA_MAX(max_written, wn->written); - } - g->last_written = max_written; - //PARA_INFO_LOG("loaded: %zd, min_written: %zd bytes\n", *g->loaded, min_written); - if (min_written) { - *g->loaded -= min_written; - FOR_EACH_WRITER_NODE(i, g) - g->writer_nodes[i].written -= min_written; - } - if (!*g->loaded && *g->input_error) { - t->error = *g->input_error; - return; - } - if (*g->loaded && min_written) { -// PARA_INFO_LOG("moving %zd bytes\n", *g->loaded); - memmove(*g->bufp, *g->bufp + min_written, *g->loaded); - } -} - -/** - * call the open function of each writer in the group - * - * \param g the writer node group - * - * \return If at least one open function returned an error, all successful - * writer notes get closed and this error value is returned. Upon success, a - * task associated with \a g is registered to the scheduler and the function - * returns a positive value. - * */ -int wng_open(struct writer_node_group *g) -{ - int i, ret = 1; - - PARA_NOTICE_LOG("opening wng %p with %d writer(s)\n", g, g->num_writers); - FOR_EACH_WRITER_NODE(i, g) { - struct writer_node *wn = &g->writer_nodes[i]; - struct writer *w = writers + wn->writer_num; - wn->wng = g; - ret = w->open(wn); - if (ret < 0) - goto err_out; - } - sprintf(g->task.status, "%s", "writer node group"); - register_task(&g->task); - g->open = 1; - return 1; -err_out: - PARA_ERROR_LOG("%s\n", para_strerror(-ret)); - while (i > 0) { - struct writer_node *wn = &g->writer_nodes[--i]; - struct writer *w = writers + wn->writer_num; - w->close(wn); - } - free(g->writer_nodes); - g->num_writers = 0; - g->task.error = -E_TASK_UNREGISTERED; - return ret; -} - -/** - * call the close function of each writer in the given group - * - * \param g the writer node group to close - * - * This function also frees all resources of the given group. - */ -void wng_close(struct writer_node_group *g) -{ - int i; - - if (!g || !g->open) - return; - PARA_NOTICE_LOG("closing wng with %d writer(s)\n", g->num_writers); - FOR_EACH_WRITER_NODE(i, g) { - struct writer_node *wn = &g->writer_nodes[i]; - struct writer *w = writers + wn->writer_num; - w->close(wn); - } - free(g->writer_nodes); - free(g); -} - -/** - * allocate and initialize a new writer_node_group struct - * - * \param num_writers the number of writer nodes for the new group - * - * \return Pointer to the new writer node group - */ -struct writer_node_group *wng_new(unsigned num_writers) -{ - struct writer_node_group *g = para_calloc(sizeof(struct writer_node_group)); - g->num_writers = num_writers; - g->writer_nodes = para_calloc(num_writers - * sizeof(struct writer_node)); - g->task.post_select = wng_post_select; - g->task.pre_select = wng_pre_select; - return g; -} - /** * Call the init function of each supported paraslash writer. */ @@ -206,32 +66,68 @@ void *check_writer_arg(const char *wa, int *writer_num) c = wa[len]; if (c && c != ' ') continue; - if (c && !writers[i].parse_config) - return NULL; *writer_num = i; - return writers[i].parse_config(c? wa + len + 1 : ""); + return writers[i].parse_config_or_die(c? wa + len + 1 : ""); } PARA_ERROR_LOG("writer not found\n"); return NULL; } /** - * setup a writer node group with only one writer, the default writer + * Open a writer node and register the corresponding task. * - * The writer which is set up depends on the OS. It defaults to alsa for Linux, - * osx_write for OS X, file writer if neither of these is supported. + * \param wn The writer node to open. + * \param parent The parent btr node (the source for the writer node). * - * \return pointer to the allocated writer node group + * The configuration of the writer node stored in \p wn->conf must be + * initialized before this function may be called. */ -struct writer_node_group *setup_default_wng(void) +void register_writer_node(struct writer_node *wn, struct btr_node *parent) { - struct writer_node_group *wng = wng_new(1); - wng->writer_nodes[0].writer_num = DEFAULT_WRITER; - PARA_INFO_LOG("using default writer: %s %p\n", - writer_names[DEFAULT_WRITER], writers[DEFAULT_WRITER].parse_config); - wng->writer_nodes[0].conf = writers[DEFAULT_WRITER].parse_config(""); - return wng; + struct writer *w = writers + wn->writer_num; + char *name = make_message("%s writer", writer_names[wn->writer_num]); + + wn->btrn = btr_new_node(&(struct btr_node_description) + EMBRACE(.name = name, .parent = parent, + .handler = w->execute, .context = wn)); + strcpy(wn->task.status, name); + free(name); + wn->task.post_select = w->post_select; + wn->task.pre_select = w->pre_select; + register_task(&wn->task); } + +/** + * Setup a writer node with the default writer. + * + * If arg is \p NULL, the OS-dependent default writer is used with an empty + * configuration string. It defaults to alsa for Linux, osx for OS X, oss for + * *BSD and the file writer if neither of these is supported. + * + * Once the writer configuration has been retrieved, a writer node is created, + * its buffer tree node is added to the buffer tree as a child of the given + * parent. + * + * Finally, the new writer node's taks structure is initialized and registered + * to the paraslash scheduler. + * + * \return A pointer to the allocated writer node group. + */ +int setup_writer_node(const char *arg, struct btr_node *parent, + struct writer_node *wn) +{ + if (arg) + wn->conf = check_writer_arg(arg, &wn->writer_num); + else { + wn->writer_num = DEFAULT_WRITER; + wn->conf = writers[DEFAULT_WRITER].parse_config_or_die(""); + } + if (!wn->conf) + return -E_WRITE_COMMON_SYNTAX; + register_writer_node(wn, parent); + return 1; +} + /** * Print the help text of all writers to stdout. * @@ -254,3 +150,58 @@ void print_writer_helps(int detailed) ggo_print_help(&w->help, detailed); } } + +static void get_btr_value(struct btr_node *btrn, const char *cmd, + int32_t *result) +{ + char *buf = NULL; + int ret = btr_exec_up(btrn, cmd, &buf); + + if (ret < 0) { + /* + * This really should not happen. It means one of our parent + * nodes died unexpectedly. Proceed with fingers crossed. + */ + PARA_CRIT_LOG("cmd %s: %s\n", cmd, para_strerror(-ret)); + *result = 0; + return; + } + ret = para_atoi32(buf, result); + assert(ret >= 0); + free(buf); +} + +/** + * Ask parent btr nodes for the sample rate of the current stream. + * + * \param btrn Where to start the search. + * \param result Filled in by this function. + * + * This function is assumed to succeed and terminates on errors. + */ +void get_btr_sample_rate(struct btr_node *btrn, int32_t *result) +{ + get_btr_value(btrn, "sample_rate", result); +} + +/** + * Ask parent btr nodes for the channel count of the current stream. + * + * \param btrn See \ref get_btr_sample_rate. + * \param result See \ref get_btr_sample_rate. + */ +void get_btr_channels(struct btr_node *btrn, int32_t *result) +{ + get_btr_value(btrn, "channels", result); +} + +/** + * Ask parent btr nodes for the number of bits per sample and the byte sex. + * + * \param btrn See \ref get_btr_sample_rate. + * \param result Contains the sample format as an enum sample_format type. + */ +void get_btr_sample_format(struct btr_node *btrn, int32_t *result) +{ + get_btr_value(btrn, "sample_format", result); +} diff --git a/write_common.h b/write_common.h index 5edebe11..5f55120b 100644 --- a/write_common.h +++ b/write_common.h @@ -1,15 +1,17 @@ /* - * Copyright (C) 2006-2009 Andre Noll + * Copyright (C) 2006-2011 Andre Noll * * Licensed under the GPL v2. For licencing details see COPYING. */ -/** \file write_common.h exported symbols from write_common.c */ +/** \file write_common.h Exported symbols from write_common.c. */ -int wng_open(struct writer_node_group *g); -void wng_close(struct writer_node_group *g); -struct writer_node_group *wng_new(unsigned num_writers); void writer_init(void); void *check_writer_arg(const char *wa, int *writer_num); -struct writer_node_group *setup_default_wng(void); void print_writer_helps(int detailed); +void register_writer_node(struct writer_node *wn, struct btr_node *parent); +int setup_writer_node(const char *arg, struct btr_node *parent, + struct writer_node *wn); +void get_btr_sample_rate(struct btr_node *btrn, int32_t *result); +void get_btr_channels(struct btr_node *btrn, int32_t *result); +void get_btr_sample_format(struct btr_node *btrn, int32_t *result);