diff options
author | J. Alexander Treuman <jat@spatialrift.net> | 2007-05-28 13:09:41 +0000 |
---|---|---|
committer | J. Alexander Treuman <jat@spatialrift.net> | 2007-05-28 13:09:41 +0000 |
commit | 6e5c90e098005b66f86a9fd99a26956cbaa0c392 (patch) | |
tree | d5699855fe945b0b02e511c87def301d119ae922 | |
parent | 28c7a91d2462128a7df9a417cbbd59cad89ba19b (diff) | |
download | mpd-6e5c90e098005b66f86a9fd99a26956cbaa0c392.tar.gz mpd-6e5c90e098005b66f86a9fd99a26956cbaa0c392.tar.xz mpd-6e5c90e098005b66f86a9fd99a26956cbaa0c392.zip |
Re-tagging 0.13.0 release to fix a couple of bugs with the tarball.
git-svn-id: https://svn.musicpd.org/mpd/tags/release-0.13.0@6325 09075e82-0dd4-0310-85a5-a0d7c8717e4f
161 files changed, 37854 insertions, 0 deletions
diff --git a/trunk/AUTHORS b/trunk/AUTHORS new file mode 100644 index 000000000..3a801f150 --- /dev/null +++ b/trunk/AUTHORS @@ -0,0 +1,44 @@ +Current Developers +------------------ +Warren Dukes <warren.dukes@gmail.com> + general + +J. Alexander Treuman <jat@spatialrift.net> + general, MP3, ID3, PulseAudio, format conversion, stored playlists + +Eric Wong <normalperson@yhbt.net> + general, FLAC, mpd-ke branch + +José Anarch <anarchsss@gmail.com> + JACK plugin + +Guus Sliepen <guus@sliepen.eu.org> + libsamplerate code + +Jim Ramsay <i.am@jimramsay.com> + Zerconf/avahi support + +Qball Cow <qballcow@gmail.com> + Playlist commands + +Patrik Weiskircher <pat@icore.at> + Stored playlist commands + +Former Developers +----------------- +tw-nym + +Nagilum + setuid + +Niklas Hofer + 'next' and 'previous' patch + +mackstann + command.c and signal handling cleanup + +AliasMrJones + replayGain + +mp4ff copyrighted by M. Bakker, Ahead Software AG, http://www.nero.com +compress.[ch] copyrighted by fluffy <fluffy@beesbuzz.biz> diff --git a/trunk/COPYING b/trunk/COPYING new file mode 100644 index 000000000..d60c31a97 --- /dev/null +++ b/trunk/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/trunk/ChangeLog b/trunk/ChangeLog new file mode 100644 index 000000000..3323d9de0 --- /dev/null +++ b/trunk/ChangeLog @@ -0,0 +1,337 @@ +ver 0.13.0 (2007/5/28) +* New JACK audio output +* Support for "file" as an alternative to "filename" in search, find, and list +* FLAC 1.1.3 API support +* New playlistadd command for adding to stored playlists +* New playlistclear command for clearing stored playlists +* Fix a bug where "find any" and "list <type> any" wouldn't return any results +* Make "list any" return an error instead of no results and an OK +* New gapless_mp3_playback option to disable gapless MP3 playback +* Support for seeking HTTP streams +* Zeroconf support using Avahi +* libsamplerate support for high quality audio resampling +* ID3v2 "Original Artist/Performer" tag support +* New playlistsearch command for searching the playlist (similar to "search") +* New playlistfind command for finding songs in the playlist (similar to "find") +* libmikmod 3.2.0 beta support +* New tagtypes command for retrieving a list of available tag types +* Fix a bug where no ACK was returned if loading a playlist failed +* Fix a bug where db_update in stats would be 0 after initial database creation +* New count command for getting stats on found songs (similar to "find") +* New playlistmove command for moving songs in stored playlists +* New playlistdelete command for deleting songs from stored playlists +* New rename command for renaming stored playlists +* Increased default buffer_before_play from 0% to 10% to prevent skipping +* Lots of bug fixes, cleaned up code, and performance improvements + +ver 0.12.2 (2007/3/20) +* Fix a bug where clients could cause MPD to segfault + +ver 0.12.1 (2006/10/10) +* Fix segfault when scanning an MP3 that has a Xing tag with 0 frames +* Fix segfault when there's no audio output specified and one can't be detected +* Fix handling of escaping in quotes +* Allow a quality of -1 to be specified for shout outputs +* A few minor cleanups + +ver 0.12.0 (2006/9/22) +* New audio output code which supports: + * A plugin-like architecture + * Non-libao ("native") outputs: + * ALSA + * OSS + * OS X + * Media MVP + * PulseAudio + * Shout (Icecast or Shoutcast) + * Playing through multiple outputs at once + * Enabling/disabling outputs while MPD is running + * Saving output state (enabled/disabled) to the state_file +* OggFLAC support +* Musepack support +* Gapless MP3 playback +* MP3 ReplayGain support (using ID3v2 tags only) +* Support for MP2 files if MP3 support is enabled +* Composer, Performer, Comment, and Disc metadata support +* New outputs command for listing available audio outputs +* New enableoutput and disableoutput commands for enabling/disabling outputs +* New plchangesposid command for a stripped down version of plchanges +* New addid command for adding to the playlist and returning a song ID +* New commands and notcommands commands for checking available commands +* Can now specify any supported metadata type or "any" in search, find, and list +* New volume_normalization parameter for enabling Audio Compress normalization +* New metadata_to_use parameter for choosing supported metadata types +* New pid_file parameter for saving the MPD process ID to the specified file +* The db_file parameter is now required +* The port parameter is now optional (defaults to 6600) +* Can specify bind_to_address multiple times +* New --kill argument for killing MPD if pid_file is specified +* Removed --update-db argument (use the update function in your client instead) +* New mpdconf.example +* New mpd.conf man page +* Removed bundled libmad and libid3tag +* Lots of bug fixes, cleaned up code, and performance improvements + +ver 0.11.5 (2004/11/1) +1) New id3v1_encoding config option to configure the id3v1 tag encoding (patch from dottedmag) +2) Strip '\r' from m3u playlists (thank you windows) +3) Use random() instead of rand() for playlist randomizing +4) Fix a bug trying skipping some commented lines in m3u playlist files +5) Fix a bug when fetching metadata from streams that may cause certain weirdnesses +6) Fix a bug where replaygain preamp was used on files w/o replaygain tags +7) Fix a busy loop when trying to prebuffer a nonexistant or missing stream +8) Fix a bug in forgetting to remove leading ' ' in content-type for http streams +9) Check for ice-name in http headers +10) Be sure the strip all '\n' chars in tags +11) Set $HOME env variable when setuid'ing, this should fix the /root/.mcop errors triggered by arts/libao + +ver 0.11.4 (2004/7/26) +1) Fixed a segfault when decoding mp3's with corrupt id3v2 tags +2) Fixed a memory leak when encountering id3v2 tags in mp3 decoder + +ver 0.11.3 (2004/7/21) +1) Add support for http authentication for streams +2) Added replaygain pre-amp support +3) Better error handling for fread() in inputStream_file +4) Fixed a bug so that when a freeAllInterfaces is called, it sets max_interface_connections to 0. This prevents potential segfaults and other nastiness for forked processes, like the player and update-er (do to interfacePrintWithFD()). +5) Allow blockingWrite() to handle errors more gracefully (for example, if the disc is full, and thus the write() fails or can't be completed, we just skip this write() and continue, instead of getting stuck in an infinite loop until the write() becomes successful) +6) Updated mpdconf.example from sbh/avuton +7) If "user" is specified, then convert ~ in paths to the user's home path specified by "user" config paramter (not the actual current user running mpd). + +ver 0.11.2 (2004/7/5) +1) Work around in computing total time for mp3's whose first valid mpeg frame is not layer III +2) Fix mp3 and mp4 decoders when seeking past the end of the file +3) Fix replaygain for flac and vorbis +4) Fix memory leaks in flac decoder (from normalperson) +5) Fix Several other bugs in playlist.c and directory.c (from normalperson) + +ver 0.11.1 (2004/6/24) +1) Fix a bug that caused "popping" at the beginning of mp3's +2) Fix playlistid command +3) Fix move commands so they don't mess up the song id's +4) Added support for HTTP Proxy +5) Detect and skip recursive links in the music directory +6) Fix addPathToDB() so updating on a specific path doesn't exist correctly adds the parent directories to the DB + +ver 0.11.0 (2004/6/18) +1) Support for playing mp3 and Ogg Vorbis streams +2) Non-blocking Update +3) Replaygain support for Ogg Vorbis and FLAC (by Eric Moore aka AliasMrJones) +4) audio_output_format option that allows for all audio output to be converted to a format compatible with any sound card +5) Own routines for to always support UTF-8 <-> ISO-8859-1 conversion +6) Added "Id" and "Pos" metadata for songs in playlist +7) Added commands: plchanges, currentsong, playid, seekid, playlistid, moveid, swapid, deleteid +8) UTF-8 validation of all tags +9) Update specific files/directories (for fast, incremental updating) +10) Added ACK error codes +11) Mod file support +12) Added command_list_ok_begin +13) Play after stop resumes from last position in the playlist +14) Play while pause resumes playback +15) Better signal handling by mackstann +16) Cleanup decoder interface (now called InputPlugins) +17) --create-db no long starts the daemon +18) --no-daemon outputs to log files +19) --stdout sends output to stdout/stderr +20) Default port is now 6600 +21) Lots of other cleanups and Bugfixes + +ver 0.10.4 (2004/5/26) +1) Fix configure problems on OpenBSD with langinfo and iconv +2) Fix an infinte loop when writing to an interface and it has expired +3) Fix a segfault in decoding flac's +4) Ingore CRC stuff in mp3's since some encoders did not compute the CRC correctly +5) Fix a segfault in processing faulty mp4 metadata + +ver 0.10.3 (2004/4/2) +1) Fix a segfault when a blanck line is sent from a client +2) Fix for loading playlists on platforms where char is unsigned +3) When pausing, release audio device after we say pause is successful (this makes pause appear to not lag) +4) When returning errors for unknown types by player, be sure to copy the filename +5) add --disable-alsa for disabling alsa mixer support +6) Use select() for a portable usleep() +7) For alsa mixer, default to "Master' element, not first element + +ver 0.10.2 (2004/3/25) +1) Add suport for AAC +2) Substitute '\n' with ' ' in tag info +3) Remove empty directories from db +4) Resume from current position in song when using state file +5) Pause now closes the music device, and reopens it on resuming +6) Fix unnecessary big endian byte swapping +7) If locale is "C" or "POSIX", then use ISO-8859-1 as the fs charset +8) Fix a bug where alsa mixer wasn't detecting volume changes +9) For alsa and software mixer, show volume to be the same as it was set (even if its not the exact volume) +10) Report bitrate for wave files +11) Compute song length of CBR mp3's more accurately + +ver 0.10.1 (2004/3/7) +1) Check to see if we need to add "-lm" when linking mpd +2) Fix issues with skipping bad frames in an mp3 (this way we get the correct samplerate and such) +3) Fix crossfading bug with ogg's +4) Updated libmad and libid3tag included w/ source to 0.15.1b + +ver 0.10.0 (2004/3/3) +1) Use UTF-8 for all client communications +2) Crossfading support +3) Password Authentication (all in plaintext) +4) Software mixer +5) Buffer Size is configurable +6) Reduced Memory consumption (use directory tree for search and find) +7) Bitrate support for Flac +8) setvol command (deprecates volume command) +9) add command takes directories +10) Path's in config file now work with ~ +11) Add samplerate,bits, and channels to status +12) Reenable playTime in stats display +13) Fix a segfault when doing: add "" +14) Fix a segfault with flac vorbis comments simply being "=" +15) Fix a segfault/bug in queueNextSong with repeat+random +16) Fix a bug, where one process may segfault, and cause more processes to spawn w/o killing ones that lost their parent. +17) Fix a bug when the OSS device was unable to fetch the current volume, +it would close the device (when it maybe previously closed by the exact same code) +18) command.c cleanup by mackstann +19) directory.c and command.c cleanup by tw-nym + +ver 0.9.4 (2004/1/21) +1) Fix a bug where updated tag info wasn't being detected +2) Set the default audio write size to 1024 bytes (should decrease cpu load a bit on some machines). +3) Make audio write size configurable via "audio_write_size" config option +4) Tweak output buffer size for connections by detecting the kernel output buffer size. + +ver 0.9.3 (2003/10/31) +1) Store total time/length of songs in db and display in *info commands +2) Display instantaneous bitrate in status command +3) Add Wave Support using libaudiofile (Patch from normalperson) +4) Command code cleanup (Patch from tw-nym) +5) Optimize listing of playlists (10-100x faster) +6) Optimize interface output (write in 4kB chunks instead of on every '\n') +7) Fix bug that prevented rm command from working +8) Fix bug where deleting current song skips the next song +9) Use iconv to convert vorbis comments from UTF-8 to Latin1 + +ver 0.9.2 (2003/10/6) +1) Fix FreeBSD Compilation Problems +2) Fix bug in move command +3) Add mixer_control options to configure which mixer control/device mpd controls +4) Randomize on play -1 +5) Fix a bug in toggling repeat off and at the end of the playlist + +ver 0.9.1 (2003/9/30) +1) Fix a statement in the middle of declarations in listen.c, causes error for +gcc 2.7 + +ver 0.9.0 (2003/9/30) +1) Random play mode +2) Alsa Mixer Support +3) Save and Restore "state" +4) Default config file locations (.mpdconf and /etc/mpd.conf) +5) Make db file locations configurable +6) Move songs around in the playlist +7) Gapless playback +8) Use Xing tags for mp3's +9) Remove stop_on_error +10) Seeking support +11) Playlists can be loaded and deleted from subdirectories +12) Complete rewrite of player layer (fork()'s only once, opens and closes +audio device as needed). +13) Eliminate use and dependence of SIGIO +14) IPv6 support +15) Solaris compilations fixes +16) Support for different log levels +17) Timestamps for log entries +18) "user" config parameter for setuid (patch from Nagilum) +19) Other misc features and bug fixes + +ver 0.8.7 (2003/9/3) +1) Fix a memory leak. When closing a interface, was called close() on the fd +instead of calling fclose() on the fp that was opened with fdopen(). + +ver 0.8.6 (2003/8/25) +1) Fix a memory leak when a buffered existed, and a connection was unexpectedly closed, and i wasn't free'ing the buffer apropriatly. + +ver 0.8.5 (2003/8/17) +1) Fix a bug where an extra end of line is returned when attempting to play a +non existing file. This causes parsing errors for clients. + +ver 0.8.4 (2003/8/13) +1) Fix a bug where garbage is returned with errors in "list" command + +ver 0.8.3 (2003/8/12) +1) Fix a compilation error on older linux systems +2) Fix a bug in searching by title +3) Add "list" command +4) Add config options for specifying libao driver/plugin and options +5) Add config option to specify which address to bind to +6) Add support for loading and saving absolute pathnames in saved playlists +7) Playlist no longer creates duplicate entries for song data (more me +efficient) +8) Songs deleted from the db are now removed for the playlist as well + +ver 0.8.2 (2003/7/22) +1) Increased the connection que for listen() from 0 to 5 +2) Cleanup configure makefiles so that mpd uses MPD_LIBS and MPD_CFLAGS +rather than LIBS and CFLAGS +3) Put a cap on the number of commands per command list +4) Put a cap on the maximum number of buffered output lines +5) Get rid of TIME_WAIT/EADDRINUSE socket problem +6) Use asynchronious IO (i.e. trigger SIGIO instead so we can sleep in +select() calls longer) + +ver 0.8.1 (2003/7/11) +1) FreeBSD fixes +2) Fix for rare segfault when updating +3) Fix bug where client was being hungup on when done playing current song +4) Fix bug when playing flac's where it incorrectly reports an error +5) Make stop playlist on error configurable +6) Configure checks for installed libmad and libid3tag and uses those if found +7) Use buffer->finished in *_decode's instead of depending on catching signals + +ver 0.8.0 (2003/7/6) +1) Flac support +2) Make playlist max length configurable +3) New backward compatible status (backward compatible for 0.8.0 on) +4) listall command now can take a directory as an argument +5) Buffer rewritten to use shared memory instead of sockets +6) Playlist adding done using db +7) Add sort to list, and use binary search for finding +8) New "stats" command +9) Command list (for faster adding of large batches of files) +10) Add buffered chunks before play +11) Useful error reporting to clients (part of status command) +12) Use libid3tag for reading id3 tags (more stable) +13) Non-blocking output to clients +14) Fix bug when removing items from directory +15) Fix bug when playing mono mp3's +16) Fix bug when attempting to delete files when using samba +17) Lots of other bug fixes I can't remember + +ver 0.7.0 (2003/6/20) +1) use mad instead of mpg123 for mp3 decoding +2) volume support +3) repeate playlist support +4) use autoconf/automake (i.e. "configure") +5) configurable max connections + +ver 0.6.2 (2003/6/11) +1) Buffer support for ogg +2) new config file options: "connection_timeout" and "mpg123_ignore_junk" +3) new commands: "next", "previous", and "listall" +Thanks to Niklas Hofer for "next" and "previous" patches! +4) Search by filename +5) bug fix for pause when playing mp3's + +ver 0.6.1 (2003/5/29) +1) Add conf file support +2) Fix a bug when doing mp3stop (do wait3(NULL,WNOHANG|WUNTRACED,NULL)) +3) Fix a bug when fork'ing, fflush file buffers before forking so the +child doesn't print the same stuff in the buffer. + +ver 0.6.0 (2003/5/25) +1) Add ogg vorbis support +2) Fix two bugs relating to tables, one for search by title, and one where we +freed the tables before directories, causing a segfault +3) The info command has been removed. + +ver 0.5.0-0.5.2 +Initial release(s). Support for MP3 via mpg123 diff --git a/trunk/INSTALL b/trunk/INSTALL new file mode 100644 index 000000000..e0bc9e551 --- /dev/null +++ b/trunk/INSTALL @@ -0,0 +1,131 @@ + Music Player Daemon (MPD) - INSTALL + +Optional Output Dependencies +---------------------------- + +You will need at least one of these to compile MPD. + +Most of these are available as packages on major distributions. Be sure to +install both the library package as well as the development package. + +AO - http://www.xiph.org/ao/ +A portable library that abstracts many audio output types as one API. Should +be used only if there is no native plugin available or if the native plugin +doesn't work. You will need libao. + +ALSA - http://www.alsa-project.org/ +The Advanced Linux Sound Architecture. Recommended audio output if you use +Linux. You will need libasound. + +PulseAudio - http://www.pulseaudio.org/ +An advanced sound daemon. You will need libpulse. + +JACK - http://www.jackaudio.org/ +A low-latency sound daemon. + +libshout - http://www.icecast.org/ +For streaming to an Icecast or Shoutcast server. + +Optional Input Dependencies +--------------------------- + +You will need at least one of these to compile MPD. + +Most of these are available as packages on major distributions. Be sure to +install both the library package as well as the development package. + +MAD - http://www.underbit.com/products/mad/ +For MP3 support. You will need libmad, and optionally libid3tag if you want +ID3 tag support. + +Ogg Vorbis - http://www.xiph.org/ogg/vorbis/ +For Ogg Vorbis support. You will need libogg and libvorbis. + +FLAC - http://flac.sourceforge.net/ +For FLAC support. You will need version 1.1.0 or higher of libflac. + +OggFLAC - http://www.xiph.org/ogg/vorbis/ and http://flac.sourceforge.net/ +For OggFLAC support. You will need liboggflac, which can be built from the +FLAC sources if libogg is already installed. Versions of flac 1.1.3 and +greater will automatically detect and use OggFLAC if it's available. + +Audio File - http://www.68k.org/~michael/audiofile/ +For WAVE, AIFF, and AU support. You will need libaudiofile. + +FAAD2 - http://www.audiocoding.com/ +For MP4/AAC support. You will need libmp4ff. + +libmpcdec - http://www.musepack.net/ +For Musepack support. + +MikMod - http://mikmod.raphnet.net/ +For MOD support. You will need libmikmod. + +Optional Miscellaneous Dependencies +----------------------------------- + +Avahi - http://www.avahi.org/ +For Zeroconf support. + +libsamplerate - http://www.mega-nerd.com/SRC/ +For advanced samplerate conversions. + +Download +-------- + +Get the latest release from of MPD from <http://www.musicpd.org/>. + +Compile +------- + +1) unzip and untar the archive + +$ tar zxvf mpd-x.x.x.tar.gz + +or + +$ tar jxvf mpd-x.x.x.tar.bz2 + +2) change to directory created + +$ cd mpd-x.x.x + +3) Run configure script (this will determine what dependencies you have) + +$ ./configure + +4) Compile + +$ make + +Install (Optional) +------- + +(as root) +$ make install + +Run +--- + +1) run mpd: + +$ mpd <config file> + +(if no config file is specified, mpd's looks for ~/.mpdconf then /etc/mpd.conf) + +an example would be: + +$ mpd playlists/.mpdconf + +A sample config file is included with the source of MPD, mpdconf.example . + +Note: The first time you run mpd, it will "explore" your mp3 directory for +mp3's. + +Using MPD +--------- + +You can download many different interfaces for MPD at + <http://mpd.wikia.com/wiki/Clients> + +MPD can be interfaced directly using telnet (see COMMANDS, if you are brave). diff --git a/trunk/Makefile.am b/trunk/Makefile.am new file mode 100644 index 000000000..952d42762 --- /dev/null +++ b/trunk/Makefile.am @@ -0,0 +1,9 @@ +AUTOMAKE_OPTIONS = foreign 1.6 + +SUBDIRS = src doc +docdir = $(prefix)/share/doc/$(PACKAGE) +doc_DATA = README UPGRADING +EXTRA_DIST = COPYING $(doc_DATA) + +sparse-check: + $(MAKE) -C src $@ diff --git a/trunk/README b/trunk/README new file mode 100644 index 000000000..b403dd3a9 --- /dev/null +++ b/trunk/README @@ -0,0 +1,22 @@ + + Music Player Daemon (MPD) + http://www.musicpd.org + +A daemon for playing music of various formats. Music is played through the +server's audio device. The daemon stores info about all available music, +and this info can be easily searched and retrieved. Player control, info +retrieval, and playlist management can all be managed remotely. + +To install MPD, see INSTALL. + +MPD includes mp4ff in the source, due to licensing issues of the newer +version and includes bugfixes with the properly licensed version. mp4ff is +released under the GPL and copyrighted by M. Bakker, Ahead Software AG +(http://www.nero.com) and is distributed as a part of the FAAD2 - Freeware +Advance Audio (AAC) Decoder. + +MPD is released under the GNU Public License. +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 +For the full license, see COPYING. diff --git a/trunk/TODO b/trunk/TODO new file mode 100644 index 000000000..f652d4ff4 --- /dev/null +++ b/trunk/TODO @@ -0,0 +1,83 @@ +0.14 +---- + +*) data structures + *) remove changes made to linked list for TagTracker + +*) input plugins + *) add support for playing aac streams + +*) mixer + *) add sun support + *) add OS X support + +*) Add support for 24-bit audio + +*) cleanup linked list code! + +*) implement listener socket protocol as documented here: + http://mpd.wikia.com/wiki/MusicPlayerDaemonListenerProtocol + +*) support for dynamically loading plugins + *) cleanup input plugins "API" + *) cleanup output plugins "API" + +*) add error codes for status->error + +*) Cleanup Config File Code + +*) audio output + *) write a esd native audioOutput + *) write a nas native audioOutput + *) allowing "pausing" of audio output devices + *) while pausing, play silence for the devices that don't support + "pausing" + *) write a sun native audioOutput + *) more accurate time reporting by determining how much of audio_device + buffer has been played + +*) state + *) abstract out state code from playlist.c + *) put MPD Version in statefile + +*) add playlistreplace command (replace current playlist with saved playlist + and keep playing) + +*) add command for inserting songs in a specific position + +1.0 +--- + +*) bug fixes + +post-1.0 +-------- + +*) rewrite audio pipe + *) use pthreads/clone + *) try to constrain the use of pthread mutex's and condition's + to specific output plugins + *) use pull model for audio_output + *) threads + 0) managing thread + *) receives commands + *) manages state + *) handles time/metadata sending + 1) decoding thread + 2) effects thread + *) crossfading + *) *command* resampling/conversions + 3) audio_output thread + *) thread for each audio_output device + +*) dynamic metadata + *) implement by recording the ftell positions of entries + *) buffer changes and flush them once every 60 seconds + *) buffer changes while doing an update + *) be sure to check that the metadata "header" is what we expect + before writing at the position + *) add support for: + *) last time played + *) times played + *) times skipped + *) ranking diff --git a/trunk/UPGRADING b/trunk/UPGRADING new file mode 100644 index 000000000..b25beab68 --- /dev/null +++ b/trunk/UPGRADING @@ -0,0 +1,84 @@ + Music Player Daemon (MPD) - UPGRADING + +Upgrading to 0.13.0 +------------------- + +JACK, Avahi, and libsamplerate have been added as optional dependencies. +FLAC/OggFLAC now supports the 1.1.3 API, and libmikmod 3.2.0 betas are +supported as well. + +New mpd.conf parameters include zeroconf_name, samplerate_converter, and +gapless_mp3_playback. See the mpd.conf man page or updated mpconf.example for +more information on these parameters. + +Support for the ID3v2 "Original Artist/Performer" tag has been added. Your +MP3s will need to be rescanned for these tags to be included in the database. +This can be done by running mpd --create-db. + +Upgrading to 0.12.0 +------------------- + +The ao_driver and ao_driver_options config parameters have been removed and +replaced with the audio_output config section. You will have to update your +config file to use this instead. See the mpd.conf man page or the new +mpdconf.example for details on specifying audio_output sections. + +The db_file parameter is no longer optional. If you did not specify it in your +old config file then you will have to add it in order to run 0.12.0. + +Support for OggFLAC and Musepack audio files has been added. Additionally, +scanning of MP3 files has been improved. To make use of these updates it is +highly recommended that you run mpd --create-db to recreate your entire +database. + +Upgrading to 0.11.0 +------------------- + +The database format has changed a little bit, but in a backward compatible way. +This means that if you upgrade to 0.11.0 from 0.10.x, you do not need to make +any changes. However, if you downgrade back to 0.10.x, then you will need +to recreate your db. + +The default port for MPD is now 6600, so update your mpd and client +configurations appropriately. + +Upgrading to 0.10.0 +------------------- + +All information is now stored in the db in UTF-8 format, and the character +set used for the filesystem is stored in the db. Thus, it is highly +recommended that you recreate the db. To do so, run mpd with the +"--create-db" command line option. Also, note that the filesystem +character set will be determined from your current locale settings. +If your locale settings are not the same as those used for the filesystem, +then use the config file parameter "filesystem_charset" to specify the +correct character set (this maybe necessary if you create the db with root). + +Upgrading to 0.9.3 +------------------ + +Wave support was added, so to have your wave files added, update the db (mpc +update). + +Also, song lengths are now stored in the db. To get this stuff +added to the db, you will need to recreate the db from scratch. To do this, +run mpd with the "--create-db" commandline option. + +Upgrading to 0.9.0 +------------------ + +The "stop_on_error" config parameter was removed, so be sure to remove this +parameter from your config file. + +Upgrading to 0.8.x +------------------ + +If you have FLACs, then to have them added to your list of available music, +just use "update". + +Upgrading from 0.5.x to 0.6.x +----------------------------- +If you have not compiled MPD with "make ogg", then nothing is needed. + +If you compiled with "make ogg", just use "update" (available via the phpMp +interface) to add your OGGs to MPD's list of available music. diff --git a/trunk/autogen.sh b/trunk/autogen.sh new file mode 100755 index 000000000..34dbf3408 --- /dev/null +++ b/trunk/autogen.sh @@ -0,0 +1,163 @@ +#!/bin/sh +# Run this to set up the build system: configure, makefiles, etc. +# (at one point this was based on the version in enlightenment's cvs) + +package="mpd" + +olddir="`pwd`" +srcdir="`dirname $0`" +test -z "$srcdir" && srcdir=. +cd "$srcdir" +DIE= +AM_VERSIONGREP="sed -e s/.*[^0-9\.]\([0-9]\.[0-9]\).*/\1/" +AC_VERSIONGREP="sed -e s/.*[^0-9\.]\([0-9]\.[0-9][0-9]\).*/\1/" +VERSIONMKINT="sed -e s/[^0-9]//" +if test -n "$AM_FORCE_VERSION" +then + AM_VERSIONS="$AM_FORCE_VERSION" +else + AM_VERSIONS='1.6 1.7 1.8 1.9' +fi +if test -n "$AC_FORCE_VERSION" +then + AC_VERSIONS="$AC_FORCE_VERSION" +else + AC_VERSIONS='2.58 2.59' +fi + +versioned_bins () +{ + bin="$1" + needed_int=`echo $VERNEEDED | $VERSIONMKINT` + for i in $VERSIONS + do + i_int=`echo $i | $VERSIONMKINT` + if test $i_int -ge $needed_int + then + echo $bin-$i $bin$i $bin-$i_int $bin$i_int + fi + done + echo $bin +} + +for c in autoconf autoheader automake aclocal +do + uc=`echo $c | tr a-z A-Z` + eval "val=`echo '$'$uc`" + if test -n "$val" + then + echo "$uc=$val in environment, will not attempt to auto-detect" + continue + fi + + case "$c" in + autoconf|autoheader) + VERNEEDED=`fgrep AC_PREREQ configure.ac | $AC_VERSIONGREP` + VERSIONS="$AC_VERSIONS" + pkg=autoconf + ;; + automake|aclocal) + VERNEEDED=`fgrep AUTOMAKE_OPTIONS Makefile.am | $AM_VERSIONGREP` + VERSIONS="$AM_VERSIONS" + pkg=automake + ;; + esac + printf "checking for $c ... " + for x in `versioned_bins $c`; do + ($x --version < /dev/null > /dev/null 2>&1) > /dev/null 2>&1 + if test $? -eq 0 + then + echo $x + eval $uc=$x + break + fi + done + eval "val=`echo '$'$uc`" + if test -z "$val" + then + if test $c = $pkg + then + DIE="$DIE $c=$VERNEEDED" + else + DIE="$DIE $c($pkg)=$VERNEEDED" + fi + fi +done + +if test -n "$LIBTOOLIZE" +then + echo "LIBTOOLIZE=$LIBTOOLIZE in environment," \ + "will not attempt to auto-detect" +else + printf "checking for libtoolize ... " + for x in libtoolize glibtoolize + do + ($x --version < /dev/null > /dev/null 2>&1) > /dev/null 2>&1 + if test $? -eq 0 + then + echo $x + LIBTOOLIZE=$x + break + fi + done +fi + +if test -z "$LIBTOOLIZE" +then + DIE="$DIE libtoolize(libtool)" +fi + +if test -n "$DIE" +then + echo "You must have the following installed to compile $package:" + for i in $DIE + do + printf ' ' + echo $i | sed -e 's/(/ (from /' -e 's/=\(.*\)/ (>= \1)/' + done + echo "Download the appropriate package(s) for your system," + echo "or get the source from one of the GNU ftp sites" + echo "listed in http://www.gnu.org/order/ftp.html" + exit 1 +fi + +echo "Generating configuration files for $package, please wait...." + +ACLOCAL_FLAGS="$ACLOCAL_FLAGS -I $PWD/m4" + +# /usr/share/aclocal is most likely included by default, already... +ac_local_paths=' +/usr/local/share/aclocal +/sw/share/aclocal +/usr/pkg/share/aclocal +/opt/share/aclocal +/usr/gnu/share/aclocal +' + +for i in $ac_local_paths; do + if test -d "$i"; then + ACLOCAL_FLAGS="$ACLOCAL_FLAGS -I $i" + # we probably only want one of these... + break + fi +done + +echo " $ACLOCAL $ACLOCAL_FLAGS" +$ACLOCAL $ACLOCAL_FLAGS || exit 1 + +echo " $AUTOHEADER" +$AUTOHEADER || exit 1 + +echo " $LIBTOOLIZE --automake" +$LIBTOOLIZE --automake || exit 1 + +echo " $AUTOMAKE --add-missing $AUTOMAKE_FLAGS" +$AUTOMAKE --add-missing $AUTOMAKE_FLAGS || exit 1 + +echo " $AUTOCONF" +$AUTOCONF || exit 1 + +cd "$olddir" +if test x$NOCONFIGURE = x; then + "$srcdir"/configure "$@" || exit 1 +fi diff --git a/trunk/configure.ac b/trunk/configure.ac new file mode 100644 index 000000000..5863bf2b1 --- /dev/null +++ b/trunk/configure.ac @@ -0,0 +1,776 @@ +dnl AC_INIT(src/main.c) +dnl AM_INIT_AUTOMAKE(mpd, 0.13.0) + +AC_PREREQ(2.58) +AC_INIT(mpd, 0.13.0, warren.dukes@gmail.com) +AM_INIT_AUTOMAKE($PACKAGE_NAME, $PACKAGE_VERSION) + +AC_SUBST(MPD_LIBS) +AC_SUBST(MPD_CFLAGS) +AC_SUBST(MP4FF_LIB) +AC_SUBST(MP4FF_SUBDIR) + +AC_PROG_CC +AM_PROG_CC_C_O +AC_PROG_INSTALL +AC_PROG_LIBTOOL +AC_PROG_MAKE_SET + +AM_CONFIG_HEADER(config.h) +AC_DEFINE(PROTOCOL_VERSION, "0.13.0", [The mpd protocol version]) + +MPD_LIBS="" +MPD_CFLAGS="" +if test x$GCC = xyes; then + MPD_CFLAGS="-Wall -Wmissing-prototypes" +fi + +if test -z "$prefix" || test "x$prefix" = xNONE; then + local_lib= + local_include= + + # aren't autotools supposed to be smart enough to figure this out? oh + # well, the git-core Makefile managed to do some of the work for us :) + case "$host_os" in + darwin*) + local_lib='/sw/lib /opt/local/lib' + local_include='/sw/include /opt/local/include' + ;; + freebsd* | openbsd*) + local_lib=/usr/local/lib + local_include=/usr/local/include + ;; + netbsd*) + local_lib=/usr/pkg/lib + local_include=/usr/pkg/include + LDFLAGS="$LDFLAGS -Wl,-rpath,/usr/pkg/lib" + ;; + esac + + for d in $local_lib; do + if test -d "$d"; then + LDFLAGS="$LDFLAGS -L$d" + break + fi + done + for d in $local_include; do + if test -d "$d"; then + CFLAGS="$CFLAGS -I$d" + break + fi + done +fi + +AC_ARG_ENABLE(ao,[ --enable-ao enable support for libao (default: disable)],[enable_ao=$enableval],[enable_ao=no]) +AC_ARG_ENABLE(shout,[ --disable-shout disable support for streaming through shout (default: enable)],[enable_shout=$enableval],[enable_shout=yes]) +AC_ARG_ENABLE(iconv,[ --disable-iconv disable iconv support (default: enable)],[enable_iconv=$enableval],[enable_iconv=yes]) +AC_ARG_ENABLE(ipv6,[ --disable-ipv6 disable IPv6 support (default: enable)],[enable_ipv6=$enableval],[enable_ipv6=yes]) +AC_ARG_ENABLE(oss,[ --disable-oss disable OSS support (default: enable)],[enable_oss=$enableval],[enable_oss=yes]) +AC_ARG_ENABLE(alsa,[ --disable-alsa disable ALSA support (default: enable)],[enable_alsa=$enableval],[enable_alsa=yes]) +AC_ARG_ENABLE(jack,[ --disable-jack disable jack support (default: enable)],[enable_jack=$enableval],[enable_jack=yes]) +AC_ARG_ENABLE(pulse,[ --disable-pulse disable support for the PulseAudio sound server (default: enable)],[enable_pulse=$enableval],[enable_pulse=yes]) +AC_ARG_ENABLE(mvp,[ --enable-mvp enable support for Hauppauge Media MVP (default: disable)],[enable_mvp=$enableval],[enable_mvp=no]) +AC_ARG_ENABLE(oggvorbis,[ --disable-oggvorbis disable Ogg Vorbis support (default: enable)],[enable_oggvorbis=$enableval],enable_oggvorbis=yes) +AC_ARG_ENABLE(oggflac,[ --disable-oggflac disable OggFLAC support (default: enable)],[enable_oggflac=$enableval],enable_oggflac=yes) +AC_ARG_ENABLE(flac,[ --disable-flac disable flac support (default: enable)],[enable_flac=$enableval],[enable_flac=yes]) +AC_ARG_ENABLE(mp3,[ --disable-mp3 disable mp3 support (default: enable)],[enable_mp3=$enableval],[enable_mp3=yes]) +AC_ARG_ENABLE(aac,[ --disable-aac disable AAC support (default: enable)],[enable_aac=$enableval],[enable_aac=yes]) +AC_ARG_ENABLE(audiofile,[ --disable-audiofile disable audiofile support, disables wave support (default: enable)],[enable_audiofile=$enableval],[enable_audiofile=yes]) +AC_ARG_ENABLE(mod,[ --enable-mod enable MOD support (default: disable],[enable_mod=$enableval],[enable_mod=yes]) +AC_ARG_ENABLE(mpc,[ --disable-mpc disable musepack (MPC) support (default: enable)],[enable_mpc=$enableval],[enable_mpc=yes]) +AC_ARG_ENABLE(id3,[ --disable-id3 disable id3 support (default: enable)],[enable_id3=$enableval],[enable_id3=yes]) +AC_ARG_ENABLE(lsr,[ --disable-lsr disable libsamplerate support (default: enable)],[enable_lsr=$enableval],[enable_lsr=yes]) + +AC_ARG_WITH(tremor,[[ --with-tremor[=PFX] Use Tremor(vorbisidec) integer Ogg-Vorbis decoder (with optional prefix)]], use_tremor=yes; test x$withval != xyes && tremor_prefix="$withval",) +AC_ARG_WITH(tremor-libraries,[ --with-tremor-libraries=DIR Directory where Tremor library is installed (optional)], tremor_libraries="$withval", tremor_libraries="") +AC_ARG_WITH(tremor-includes,[ --with-tremor-includes=DIR Directory where Tremor header files are installed (optional)], tremor_includes="$withval", tremor_includes="") + +AC_ARG_WITH(iconv,[ --with-iconv=PFX Prefix where iconv is installed (optional)], iconv_prefix="$withval", iconv_prefix="") +AC_ARG_WITH(iconv-libraries,[ --with-iconv-libraries=DIR Directory where libiconv library is installed (optional)], iconv_libraries="$withval", iconv_libraries="") +AC_ARG_WITH(iconv-includes,[ --with-iconv-includes=DIR Directory where libiconv header files are installed (optional)], iconv_includes="$withval", iconv_includes="") + +AC_ARG_WITH(id3tag,[ --with-id3tag=PFX Prefix where libid3tag is installed (optional)], id3tag_prefix="$withval", id3tag_prefix="") +AC_ARG_WITH(id3tag-libraries,[ --with-id3tag-libraries=DIR Directory where libid3tag library is installed (optional)], id3tag_libraries="$withval", id3tag_libraries="") +AC_ARG_WITH(id3tag-includes,[ --with-id3tag-includes=DIR Directory where libid3tag header files are installed (optional)], id3tag_includes="$withval", id3tag_includes="") + +AC_ARG_WITH(mad,[ --with-mad=PFX Prefix where libmad is installed (optional)], mad_prefix="$withval", mad_prefix="") +AC_ARG_WITH(mad-libraries,[ --with-mad-libraries=DIR Directory where libmad library is installed (optional)], mad_libraries="$withval", mad_libraries="") +AC_ARG_WITH(mad-includes,[ --with-mad-includes=DIR Directory where mad header files are installed (optional)], mad_includes="$withval", mad_includes="") + +AC_ARG_WITH(faad,[ --with-faad=PFX Prefix where faad2 is installed], faad_prefix="$withval", faad_prefix="") +AC_ARG_WITH(faad-libraries,[ --with-faad-libraries=DIR Directory where faad2 library is installed (optional)], faad_libraries="$withval", faad_libraries="") +AC_ARG_WITH(faad-includes,[ --with-faad-includes=DIR Directory where faad2 header files are installed (optional)], faad_includes="$withval", faad_includes="") +AC_ARG_WITH(zeroconf,[[ --with-zeroconf=[auto|avahi|bonjour|no] Enable zeroconf backend (default=auto)]], with_zeroconf="$withval", with_zeroconf="auto") + +AC_ARG_WITH(lsr,[ --with-src=PFX Prefix where libsamplerate is installed], src_prefix="$withval", src_prefix="") +AC_ARG_WITH(lsr-libraries,[ --with-lsr-libraries=DIR Directory where libsamplerate library is installed (optional)], lsr_libraries="$withval", lsr_libraries="") +AC_ARG_WITH(lsr-includes,[ --with-lsr-includes=DIR Directory where libsamplerate header files are installed (optional)], lsr_includes="$withval", lsr_includes="") + +AC_C_BIGENDIAN + +AC_CHECK_SIZEOF(short) +AC_CHECK_SIZEOF(int) +AC_CHECK_SIZEOF(long) +AC_CHECK_SIZEOF(long long) + +AC_CHECK_HEADER(sys/inttypes.h,AC_DEFINE(HAVE_SYS_INTTYPES_H,1,[Define if sys/inttypes.h present]),) + +AC_CHECK_LIB(socket,socket,MPD_LIBS="$MPD_LIBS -lsocket",) +AC_CHECK_LIB(nsl,gethostbyname,MPD_LIBS="$MPD_LIBS -lnsl",) + +AC_CHECK_LIB(m,exp,MPD_LIBS="$MPD_LIBS -lm",) +AC_CHECK_FUNCS(setenv) + + +dnl doesn't work for systems that don't have CODESET like OpenBSD +dnl AC_CHECK_HEADER(langinfo.h,[enable_langinfo=yes;AC_DEFINE(HAVE_LANGINFO,1,[Define if nl_langinfo.h is present])],enable_langinfo=no) +AM_LANGINFO_CODESET +AC_CHECK_HEADER(locale.h,[enable_locale=yes;AC_DEFINE(HAVE_LOCALE,1,[Define if locale.h is present])],enable_locale=no) + +if test x$enable_ipv6 = xyes; then + AC_MSG_CHECKING(for ipv6) + AC_EGREP_CPP([AP_maGiC_VALUE], + [ +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#ifdef PF_INET6 +#ifdef AF_INET6 +AP_maGiC_VALUE +#endif +#endif + ], + AC_DEFINE(HAVE_IPV6, 1, [Define if IPv6 support present]) + AC_MSG_RESULT([yes]), + AC_MSG_RESULT([no]) +) +fi + +enable_osx=no +case $host in + *-darwin*) + AC_DEFINE(HAVE_OSX, 1, [Define for compiling OS X support]) + MPD_LIBS="$MPD_LIBS -framework AudioUnit -framework CoreServices" + enable_osx=yes ;; +esac + +if test x$enable_shout = xyes; then + if test x$enable_oggvorbis = xno; then + AC_MSG_WARN([disabling shout streaming support because vorbis is not enabled]) + enable_shout=no + fi + if test x$use_tremor = xyes; then + AC_MSG_WARN([disabling shout streaming support because tremor does not support vorbis encoding]) + enable_shout=no + fi +fi + +if test x$enable_ao = xyes; then + XIPH_PATH_AO([AC_DEFINE(HAVE_AO, 1, [Define to play with ao]) MPD_LIBS="$MPD_LIBS $AO_LIBS" MPD_CFLAGS="$MPD_CFLAGS $AO_CFLAGS"], enable_ao=no) +fi + +if test x$enable_shout = xyes; then + XIPH_PATH_SHOUT([AC_DEFINE(HAVE_SHOUT, 1, [Define to enable libshout support]) MPD_LIBS="$MPD_LIBS $SHOUT_LIBS" MPD_CFLAGS="$MPD_CFLAGS $SHOUT_CFLAGS"], enable_shout=no) +fi + +if test x$enable_oss = xyes; then + AC_CHECK_HEADER(sys/soundcard.h,[enable_oss=yes;AC_DEFINE(HAVE_OSS,1,[Define to enable OSS])],[AC_MSG_WARN(Soundcard headers not found -- disabling OSS support);enable_oss=no]) +fi + +if test x$enable_pulse = xyes || test x$enable_jack = xyes || + test x$enable_lsr = xyes || test x$with_zeroconf != xno; then + PKG_PROG_PKG_CONFIG +fi + +if test x$enable_pulse = xyes; then + PKG_CHECK_MODULES([PULSE], [libpulse-simple], + [enable_pulse=yes;AC_DEFINE([HAVE_PULSE], 1, [Define to enable PulseAudio support])] MPD_LIBS="$MPD_LIBS $PULSE_LIBS" MPD_CFLAGS="$MPD_CFLAGS $PULSE_CFLAGS", + [enable_pulse=no;AC_MSG_WARN([PulseAudio not found -- disabling])]) +fi + +if test x$enable_lsr = xyes; then + PKG_CHECK_MODULES([SAMPLERATE], [samplerate >= 0.0.15], + [enable_lsr=yes;AC_DEFINE([HAVE_LIBSAMPLERATE], 1, [Define to enable libsamplerate])] MPD_LIBS="$MPD_LIBS $SAMPLERATE_LIBS" MPD_CFLAGS="$MPD_CFLAGS $SAMPLERATE_CFLAGS", + [enable_lsr=no;AC_MSG_WARN([libsamplerate not found -- disabling])]) +fi + +if test x$enable_mvp = xyes; then + AC_DEFINE(HAVE_MVP,1,[Define to enable Hauppauge Media MVP support]) +fi + +if test x$enable_alsa = xyes; then + AM_PATH_ALSA(0.9.0,[AC_DEFINE(HAVE_ALSA,1,[Define to enable ALSA support]) MPD_LIBS="$MPD_LIBS $ALSA_LIBS" MPD_CFLAGS="$MPD_CFLAGS $ALSA_CFLAGS"],enable_alsa=no) +fi + +if test x$enable_jack = xyes; then + PKG_CHECK_MODULES([JACK], [jack >= 0.4], + [enable_jack=yes;AC_DEFINE([HAVE_JACK], 1, [Define to enable JACK support])] MPD_LIBS="$MPD_LIBS $JACK_LIBS" MPD_CFLAGS="$MPD_CFLAGS $JACK_CFLAGS", + [enable_jack=no;AC_MSG_WARN([JACK not found -- disabling])]) +fi + +if test x$enable_iconv = xyes; then + if test "x$iconv_libraries" != "x" ; then + ICONV_LIBS="-L$iconv_libraries" + elif test "x$iconv_prefix" != "x" ; then + ICONV_LIBS="-L$iconv_prefix/lib" + fi + + ICONV_LIBS="$ICONV_LIBS -liconv" + + if test "x$iconv_includes" != "x" ; then + ICONV_CFLAGS="-I$iconv_includes" + elif test "x$iconv_prefix" != "x" ; then + ICONV_CFLAGS="-I$iconv_prefix/include" + fi + + oldcflags=$CFLAGS + oldlibs=$LIBS + oldcppflags=$CPPFLAGS + CFLAGS="$CFLAGS $MPD_CFLAGS $ICONV_CFLAGS" + LIBS="$LIBS $MPD_LIBS $ICONV_LIBS" + CPPFLAGS=$CFLAGS + AC_CHECK_HEADER(iconv.h,MPD_CFLAGS="$MPD_CFLAGS $ICONV_CFLAGS",enable_iconv=no) + if test x$enable_iconv = xyes; then + AC_CHECK_LIB(iconv,main,MPD_LIBS="$MPD_LIBS $ICONV_LIBS",) + AC_DEFINE(HAVE_ICONV,1,[Define to use iconv]) + fi + CFLAGS=$oldcflags + LIBS=$oldlibs + CPPFLAGS=$oldcppflags +fi + +ID3_SUBDIR="" + +if test x$enable_id3 = xyes; then + if test "x$id3tag_libraries" != "x" ; then + ID3TAG_LIBS="-L$id3tag_libraries" + elif test "x$id3tag_prefix" != "x" ; then + ID3TAG_LIBS="-L$id3tag_prefix/lib" + fi + + ID3TAG_LIBS="$ID3TAG_LIBS -lid3tag -lz" + + if test "x$id3tag_includes" != "x" ; then + ID3TAG_CFLAGS="-I$id3tag_includes" + elif test "x$id3tag_prefix" != "x" ; then + ID3TAG_CFLAGS="-I$id3tag_prefix/include" + fi + + ID3TAG_CFLAGS="$ID3TAG_CFLAGS" + + oldcflags=$CFLAGS + oldlibs=$LIBS + oldcppflags=$CPPFLAGS + CFLAGS="$CFLAGS $MPD_CFLAGS $ID3TAG_CFLAGS" + LIBS="$LIBS $MPD_LIBS $ID3TAG_LIBS" + CPPFLAGS=$CFLAGS + AC_CHECK_HEADERS(id3tag.h,use_libid3tag=yes, + [use_libid3tag=no]) + if test x$use_libid3tag = xyes; then + AC_CHECK_LIB(id3tag,id3_file_open, + [MPD_LIBS="$MPD_LIBS $ID3TAG_LIBS"; + MPD_CFLAGS="$MPD_CFLAGS $ID3TAG_CFLAGS"; + use_libid3tag=yes], + [use_libid3tag=no]) + fi + CFLAGS=$oldcflags + LIBS=$oldlibs + CPPFLAGS=$oldcppflags + if test x$use_libid3tag = xyes; then + AC_DEFINE(HAVE_ID3TAG,1,[Define to use id3tag]) + else + enable_id3=no + fi +fi + +MAD_SUBDIR="" + +if test x$enable_mp3 = xyes; then + if test "x$mad_libraries" != "x" ; then + MAD_LIBS="-L$mad_libraries" + elif test "x$mad_prefix" != "x" ; then + MAD_LIBS="-L$mad_prefix/lib" + fi + + MAD_LIBS="$MAD_LIBS -lmad" + + if test "x$mad_includes" != "x" ; then + MAD_CFLAGS="-I$mad_includes" + elif test "x$mad_prefix" != "x" ; then + MAD_CFLAGS="-I$mad_prefix/include" + fi + + oldcflags=$CFLAGS + oldlibs=$LIBS + oldcppflags=$CPPFLAGS + CFLAGS="$CFLAGS $MPD_CFLAGS $MAD_CFLAGS" + LIBS="$LIBS $MPD_LIBS $MAD_LIBS" + CPPFLAGS=$CFLAGS + AC_CHECK_HEADERS(mad.h,use_libmad=yes, + [use_libmad=no]) + if test x$use_libmad = xyes; then + AC_CHECK_LIB(mad,mad_stream_init,[MPD_LIBS="$MPD_LIBS $MAD_LIBS"; + MPD_CFLAGS="$MPD_CFLAGS $MAD_CFLAGS"; + use_libmad=yes],[use_libmad=no]) + fi + CFLAGS=$oldcflags + LIBS=$oldlibs + CPPFLAGS=$oldcppflags + if test x$use_libmad = xyes; then + AC_DEFINE(HAVE_MAD,1,[Define to use libmad]) + else + enable_mp3=no + fi +fi + +if test x$enable_mpc = xyes; then + if test "x$mpcdec_libraries" != "x" ; then + MPCDEC_LIBS="-L$mpcdec_libraries" + elif test "x$mpcdec_prefix" != "x" ; then + MPCDEC_LIBS="-L$mpcdec_prefix/lib" + fi + + MPCDEC_LIBS="$MPCDEC_LIBS -lmpcdec" + + if test "x$mpcdec_includes" != "x" ; then + MPCDEC_CFLAGS="-I$mpcdec_includes" + elif test "x$mpcdec_prefix" != "x" ; then + MPCDEC_CFLAGS="-I$mpcdec_prefix/include" + fi + + oldcflags=$CFLAGS + oldlibs=$LIBS + oldcppflags=$CPPFLAGS + CFLAGS="$CFLAGS $MPD_CFLAGS $MPCDEC_CFLAGS -I." + LIBS="$LIBS $MPD_LIBS $MPCDEC_LIBS" + CPPFLAGS=$CFLAGS + AC_CHECK_HEADER(mpcdec/mpcdec.h,,enable_mpc=no) + if test x$enable_mpc = xyes; then + AC_CHECK_LIB(mpcdec,main,[MPD_LIBS="$MPD_LIBS $MPCDEC_LIBS";MPD_CFLAGS="$MPD_CFLAGS $MPCDEC_CFLAGS";],enable_mpc=no) + fi + if test x$enable_mpc = xyes; then + AC_DEFINE(HAVE_MPCDEC,1,[Define to use libmpcdec for MPC decoding]) + else + AC_MSG_WARN([mpcdec lib needed for MPC support -- disabling MPC support]) + fi + CFLAGS=$oldcflags + LIBS=$oldlibs + CPPFLAGS=$oldcppflags +fi + +MP4FF_SUBDIR="" + +if test x$enable_aac = xyes; then + if test "x$faad_libraries" != "x" ; then + FAAD_LIBS="-L$faad_libraries" + elif test "x$faad_prefix" != "x" ; then + FAAD_LIBS="-L$faad_prefix/lib" + fi + + FAAD_LIBS="$FAAD_LIBS -lfaad" + + if test "x$faad_includes" != "x" ; then + FAAD_CFLAGS="-I$faad_includes" + elif test "x$faad_prefix" != "x" ; then + FAAD_CFLAGS="-I$faad_prefix/include" + fi + + oldcflags=$CFLAGS + oldlibs=$LIBS + oldcppflags=$CPPFLAGS + CFLAGS="$CFLAGS $MPD_CFLAGS $FAAD_CFLAGS -I." + LIBS="$LIBS $MPD_LIBS $FAAD_LIBS" + CPPFLAGS=$CFLAGS + AC_CHECK_HEADER(faad.h,,enable_aac=no) + if test x$enable_aac = xyes; then + AC_CHECK_DECL(FAAD2_VERSION,,enable_aac=no,[#include <faad.h>]) + fi + if test x$enable_aac = xyes; then + AC_CHECK_DECL(faacDecInit2,,enable_aac=no,[#include <faad.h>]) + fi + if test x$enable_aac = xyes; then + AC_CHECK_LIB(faad,faacDecInit2,[MPD_LIBS="$MPD_LIBS $FAAD_LIBS";MPD_CFLAGS="$MPD_CFLAGS $FAAD_CFLAGS";MP4FF_SUBDIR="mp4ff";MP4FF_LIB="mp4ff/libmp4ff.la"],enable_aac=no) + if test x$enable_aac = xno; then + enable_aac=yes + AC_CHECK_LIB(faad,NeAACDecInit2,[MPD_LIBS="$MPD_LIBS $FAAD_LIBS";MPD_CFLAGS="$MPD_CFLAGS $FAAD_CFLAGS";MP4FF_SUBDIR="mp4ff";MP4FF_LIB="mp4ff/libmp4ff.la"],enable_aac=no) + fi + fi + if test x$enable_aac = xyes; then + AC_MSG_CHECKING(that FAAD2 uses buffer and bufferlen) + AC_COMPILE_IFELSE([ +#include <faad.h> + +int main() { + char buffer; + long bufferlen = 0; + faacDecHandle decoder; + faacDecFrameInfo frameInfo; + faacDecConfigurationPtr config; + unsigned char channels; + long sampleRate; + mp4AudioSpecificConfig mp4ASC; + + decoder = faacDecOpen(); + config = faacDecGetCurrentConfiguration(decoder); + config->outputFormat = FAAD_FMT_16BIT; + faacDecSetConfiguration(decoder,config); + AudioSpecificConfig(&buffer, bufferlen, &mp4ASC); + faacDecInit(decoder,&buffer,bufferlen,&sampleRate,&channels); + faacDecInit2(decoder,&buffer,bufferlen,&sampleRate,&channels); + faacDecDecode(decoder,&frameInfo,&buffer,bufferlen); + + return 0; +} +],[AC_MSG_RESULT(yes);AC_DEFINE(HAVE_FAAD_BUFLEN_FUNCS,1,[Define if FAAD2 uses buflen in function calls])],[AC_MSG_RESULT(no); + AC_MSG_CHECKING(that FAAD2 can even be used) + AC_COMPILE_IFELSE([ +#include <faad.h> + +int main() { + char buffer; + faacDecHandle decoder; + faacDecFrameInfo frameInfo; + faacDecConfigurationPtr config; + unsigned char channels; + long sampleRate; + long bufferlen = 0; + unsigned long dummy1_32; + unsigned char dummy2_8, dummy3_8, dummy4_8, dummy5_8, dummy6_8, + dummy7_8, dummy8_8; + + decoder = faacDecOpen(); + config = faacDecGetCurrentConfiguration(decoder); + config->outputFormat = FAAD_FMT_16BIT; + faacDecSetConfiguration(decoder,config); + AudioSpecificConfig(&buffer,&dummy1_32,&dummy2_8, + &dummy3_8,&dummy4_8,&dummy5_8, + &dummy6_8,&dummy7_8,&dummy8_8); + faacDecInit(decoder,&buffer,&sampleRate,&channels); + faacDecInit2(decoder,&buffer,bufferlen,&sampleRate,&channels); + faacDecDecode(decoder,&frameInfo,&buffer); + faacDecClose(decoder); + + return 0; +} +],AC_MSG_RESULT(yes),[AC_MSG_RESULT(no);enable_aac=no]) + ]) + fi + if test x$enable_aac = xyes; then + AC_CHECK_TYPES(mp4AudioSpecificConfig,,,[#include <faad.h>]) + AC_CHECK_MEMBERS([faacDecConfiguration.downMatrix,faacDecConfiguration.dontUpSampleImplicitSBR,faacDecFrameInfo.samplerate],,,[#include <faad.h>]) + AC_DEFINE(HAVE_FAAD,1,[Define to use FAAD2 for AAC decoding]) + else + AC_MSG_WARN([faad2 lib needed for MP4/AAC support -- disabling MP4/AAC support]) + fi + CFLAGS=$oldcflags + LIBS=$oldlibs + CPPFLAGS=$oldcppflags +fi + +if test x$use_tremor = xyes; then + if test "x$tremor_libraries" != "x" ; then + TREMOR_LIBS="-L$tremor_libraries" + elif test "x$tremor_prefix" != "x" ; then + TREMOR_LIBS="-L$tremor_prefix/lib" + fi + TREMOR_LIBS="$TREMOR_LIBS -lvorbisidec" + if test "x$tremor_includes" != "x" ; then + TREMOR_CFLAGS="-I$tremor_includes" + elif test "x$tremor_prefix" != "x" ; then + TREMOR_CFLAGS="-I$tremor_prefix/include" + fi + ac_save_CFLAGS="$CFLAGS" + ac_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $TREMOR_CFLAGS" + LIBS="$LIBS $TREMOR_LIBS" + AC_CHECK_LIB(vorbisidec,ov_read,enable_oggvorbis=yes,enable_oggvorbis=no;AC_MSG_WARN([vorbisidec lib needed for ogg support with tremor -- disabling ogg support])) + if test x$enable_oggvorbis = xno; then + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + fi +elif test x$enable_oggvorbis = xyes; then + XIPH_PATH_OGG(,enable_oggvorbis=no) + XIPH_PATH_VORBIS(,enable_oggvorbis=no) + if test x$enable_oggvorbis = xyes; then + MPD_LIBS="$MPD_LIBS $OGG_LIBS $VORBIS_LIBS $VORBISFILE_LIBS" + MPD_CFLAGS="$MPD_CFLAGS $OGG_CFLAGS $VORBIS_CFLAGS" + + if test x$enable_shout = xyes; then + MPD_LIBS="$MPD_LIBS $VORBISENC_LIBS" + MPD_CFLAGS="$MPD_CFLAGS $VORBISFILE_CFLAGS $VORBISENC_CFLAGS" + fi + fi +fi + +if test x$enable_oggvorbis = xyes; then + AC_DEFINE(HAVE_OGGVORBIS,1,[Define for Ogg Vorbis support]) +fi + +if test x$use_tremor = xyes; then + AC_DEFINE(HAVE_TREMOR,1,[Define to use tremor (libvorbisidec) for ogg support]) + if test x$enable_oggflac = xyes; then + AC_MSG_WARN([disabling OggFLAC support because it is incompatible with tremor]) + enable_oggflac=no + fi +fi + +if test x$enable_flac = xyes; then + oldmpdcflags="$MPD_CFLAGS" + oldmpdlibs="$MPD_LIBS" + AM_PATH_LIBFLAC(MPD_LIBS="$MPD_LIBS $LIBFLAC_LIBS" MPD_CFLAGS="$MPD_CFLAGS $LIBFLAC_CFLAGS",enable_flac=no) +fi + + +if test x$enable_flac = xyes; then + oldcflags="$CFLAGS" + oldlibs="$LIBS" + CFLAGS="$CFLAGS $MPD_CFLAGS" + LIBS="$LIBS $MPD_LIBS" + AC_CHECK_LIB(FLAC, FLAC__metadata_object_vorbiscomment_find_entry_from, + ,[enable_flac=no;AC_MSG_WARN(You need FLAC 1.1 -- disabling flac support)]) + if test x$enable_flac = xno; then + MPD_CFLAGS="$oldmpdcflags" + MPD_LIBS="$oldmpdlibs" + else + AC_CHECK_DECL(FLAC_API_SUPPORTS_OGG_FLAC, + [enable_oggflac=flac], [], + [#include <FLAC/export.h>]) + fi + CFLAGS="$oldcflags" + LIBS="$oldlibs" +fi + + +if test x$enable_flac = xyes; then + AC_DEFINE(HAVE_FLAC,1,[Define for FLAC support]) +fi + + +if test x$enable_oggflac = xyes; then + oldmpdcflags="$MPD_CFLAGS" + oldmpdlibs="$MPD_LIBS" + AM_PATH_LIBOGGFLAC(MPD_LIBS="$MPD_LIBS $LIBOGGFLAC_LIBS" MPD_CFLAGS="$MPD_CFLAGS $LIBOGGFLAC_CFLAGS",enable_oggflac=no) +fi + +if test x$enable_oggflac = xyes; then + AC_DEFINE(HAVE_OGGFLAC,1,[Define for OggFLAC support]) +fi + + +if test x$enable_audiofile = xyes; then + AM_PATH_AUDIOFILE(0.1.7, MPD_LIBS="$MPD_LIBS $AUDIOFILE_LIBS" MPD_CFLAGS="$MPD_CFLAGS $AUDIOFILE_CFLAGS", + [enable_audiofile=no;AC_MSG_WARN(You need audiofile -- disabling audiofile support)]) +fi + +if test x$enable_audiofile = xyes; then + AC_DEFINE(HAVE_AUDIOFILE,1,[Define for audiofile support]) +fi + +if test x$enable_mod = xyes; then + AM_PATH_LIBMIKMOD(3.1.7, MPD_CFLAGS="$MPD_CFLAGS $LIBMIKMOD_CFLAGS" + MPD_LIBS="$MPD_LIBS $LIBMIKMOD_LIBS $LIBMIKMOD_LDADD", enable_mod=no) + if test x$enable_mod = xyes; then + AC_DEFINE(HAVE_MIKMOD, 1, [Define for mikmod support]) + fi +fi + +case $with_zeroconf in +no|avahi|bonjour) + ;; +*) + with_zeroconf=auto + ;; +esac + +if test x$with_zeroconf != xno; then + if test x$with_zeroconf = xauto; then + PKG_CHECK_MODULES([AVAHI], [avahi-client], + [with_zeroconf=avahi;AC_DEFINE([HAVE_AVAHI], 1, [Define to enable Avahi Zeroconf support])] MPD_LIBS="$MPD_LIBS $AVAHI_LIBS" MPD_CFLAGS="$MPD_CFLAGS $AVAHI_CFLAGS", + [with_zeroconf=auto]) + elif test x$with_zeroconf = xavahi; then + PKG_CHECK_MODULES([AVAHI], [avahi-client], + [with_zeroconf=avahi;AC_DEFINE([HAVE_AVAHI], 1, [Define to enable Avahi Zeroconf support])] MPD_LIBS="$MPD_LIBS $AVAHI_LIBS" MPD_CFLAGS="$MPD_CFLAGS $AVAHI_CFLAGS") + fi + + # In the future, should add bonjour support (for OSX) and check at autodetect + # time + #if test x$with_zeroconf = xbonjour -o x$with_zeroconf = xauto; then + if test x$with_zeroconf = xbonjour; then + AC_MSG_WARN([Bonjour support has not been implemented yet, disabling Zeroconf]) + with_zeroconf=no + fi + + if test x$with_zeroconf = xauto; then + AC_MSG_WARN([No supported Zeroconf backend found, disabling Zeroconf]) + with_zeroconf=no + fi +fi + +AC_OUTPUT(src/mp4ff/Makefile doc/Makefile src/Makefile Makefile ) + +echo "" +echo "########### MPD CONFIGURATION ############" +echo "" + +echo " Playback Support:" +if test x$enable_ao = xyes; then + echo " libao support .................enabled" +else + echo " libao support .................disabled" +fi + +if test x$enable_oss = xyes; then + echo " OSS support ...................enabled" +else + echo " OSS support ...................disabled" +fi + +if test x$enable_alsa = xyes; then + echo " ALSA support ..................enabled" +else + echo " ALSA support ..................disabled" +fi + +if test x$enable_jack = xyes; then + echo " JACK support ..................enabled" +else + echo " JACK support ..................disabled" +fi + +if test x$enable_osx = xyes; then + echo " OS X support ..................enabled" +else + echo " OS X support ..................disabled" +fi + +if test x$enable_pulse = xyes; then + echo " PulseAudio support ............enabled" +else + echo " PulseAudio support ............disabled" +fi + +if test x$enable_mvp = xyes; then + echo " Media MVP support .............enabled" +else + echo " Media MVP support .............disabled" +fi + +if test x$enable_shout = xyes; then + echo " Shout streaming support .......enabled" +else + echo " Shout streaming support .......disabled" +fi + +echo "" + +if test x$enable_ao = xno && + test x$enable_oss = xno && + test x$enable_shout = xno && + test x$enable_alsa = xno && + test x$enable_osx = xno && + test x$enable_pulse = xno && + test x$enable_jack = xno && + test x$enable_mvp = xno; then + AC_MSG_ERROR([No Audio Output types configured!]) +fi + +echo " File Format Support:" + +if test x$enable_id3 = xyes; then + echo " ID3 tag support ...............enabled" +else + echo " ID3 tag support ...............disabled" +fi + +if test x$enable_mp3 = xyes; then + echo " mp3 support ...................enabled" +else + echo " mp3 support ...................disabled" +fi + +if test x$enable_oggvorbis = xyes; then + echo " Ogg Vorbis support ............enabled" + if test x$use_tremor = xyes; then + echo " using tremor.................yes" + else + echo " using tremor.................no" + fi +else + echo " Ogg Vorbis support ............disabled" +fi + +if test x$enable_flac = xyes; then + echo " FLAC support ..................enabled" +else + echo " FLAC support ..................disabled" +fi + +case $enable_oggflac in +yes) + echo " OggFLAC support ...............enabled" + ;; +flac) + echo " OggFLAC support ...............enabled(FLAC 1.1.3)" + ;; +*) + echo " OggFLAC support ...............disabled" + ;; +esac + +if test x$enable_audiofile = xyes; then + echo " Wave file support .............enabled" +else + echo " Wave file support .............disabled" +fi + +if test x$enable_aac = xyes; then + echo " MP4/AAC support ...............enabled" +else + echo " MP4/AAC support ...............disabled" +fi + +if test x$enable_mpc = xyes; then + echo " Musepack (MPC) support ........enabled" +else + echo " Musepack (MPC) support ........disabled" +fi + +if test x$enable_mod = xyes; then + echo " MOD support ...................enabled" +else + echo " MOD support ...................disabled" +fi + +if + test x$enable_mp3 = xno && + test x$enable_oggvorbis = xno && + test x$enable_flac = xno && + test x$enable_oggflac = xno && + test x$enable_audiofile = xno && + test x$enable_aac = xno && + test x$enable_mpc = xno && + test x$enable_mod = xno; then + AC_MSG_ERROR([No input plugins supported!]) +fi + +echo "" +echo " Other features:" + +if test x$enable_lsr = xyes; then + echo " libsamplerate support .........enabled" +else + echo " libsamplerate support .........disabled" +fi + + +if test x$with_zeroconf != xno; then + echo " Zeroconf support ..............$with_zeroconf" +else + echo " Zeroconf support ..............disabled" +fi + +echo "" +echo "##########################################" +echo "" +echo "You are now ready to compile MPD" +echo "Type \"make\" to compile MPD" diff --git a/trunk/doc/COMMANDS b/trunk/doc/COMMANDS new file mode 100644 index 000000000..652e31511 --- /dev/null +++ b/trunk/doc/COMMANDS @@ -0,0 +1,275 @@ + Music Player Daemon - Commands + + WARNING + This document has not been updated to reflect recent changes in + the MPD protocol. It does not contain all supported commands, + and some commands may now take additional arguments. However, + clients conforming to this specification should still be + compatible with the latest release of MPD. For more up to date + documentation, please see the protocol reference on the wiki at + <http://mpd.wikia.com/wiki/Protocol_Reference>. + +This document is intended for client developers, not end users. + +Format: +------- + +If arguments contain spaces, they should be surrounded by double quotation +marks, ". + +command <type arg1> <type arg2> ... + explanation: w/ arg1 and arg2 + +All data between the client and server is encoded in UTF-8. (Note, +that in UTF-8 all standard ansi characters, 0-127, are the same as a standard +ansi encoding. Also, no ansi character appears in any multi-byte +characters. So, you can use standard C functions like strlen, and strcpy +just fine with UTF-8 encoded strings. For example: "OK\n" encoded in UTF-8 is +simply "OK\n". For more information on UTF=8: +http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 ) + +Command Completion: +------------------- + +A command returns "OK\n" on completion or "ACK some error\n" on failure. +These denote the end of command execution. + +NOTE: +----- + +For manipulating playlists and playing, there are two sets of commands. One +set uses the song id of a song in the playlist, while another set uses the +playlist position of the song. The commands using song id's should be used +instead of the commands that manipulate and control playback based on playlist +position. Using song id's is a safer method when multiple clients are +interacting with MPD. + +Commands: +--------- + +add <string path> + add the file _path_ to the playlist (directories add recursively) + _path_ can also be a single file + increments playlist version by for each song added + +clear + clears the current playlist + increments playlist version by 1 + +clearerror + clear the current error message in status + (this is also accomplished by any command that starts playback) + +close + close the connection with the MPD + +crossfade <int seconds> + sets crossfading between songs + +currentsong + displays the song info of current song (same song that is identified + in status) + +delete <int song> + delete _song_ from playlist + increments playlist version by 1 + +deleteid <int songid> + delete song with _songid_ from playlist + increments playlist version by 1 + +find <string type> <string what> + finds songs in the db that are exactly _what_ + _type_ should be "album", "artist", or "title" + _what_ is what to find + +kill + kill MPD + +list <string type> <string arg1> + list all tags of _type_ + _type_ should be "album" or "artist" + _arg1_ is an optional parameter when type is album, this specifies + to list albums by a artist, where artist is specified with + arg1 + +listall <string path> + lists all songs and directories in _path_ (recursively) + _path_ is optional and maybe a directory or path + +listallinfo <string path> + same as listall command, except it also returns metadata info + in the same format as lsinfo + +load <string name> + loads the playlist _name_.m3u from the playlist directory + increments playlist version by the number of songs added + +lsinfo <string directory> + list contents of _directory_, from the db. _directory_ is optional + +move <int from> <int to> + move song at _from_ to _to_ in the playlist + increments playlist version by 1 + +moveid <int songid> <int to> + move song with _songid_ to _to_ in the playlist + increments playlist version by 1 + +next + plays next song in playlist + +pause <bool pause> + toggle pause/resume playing + _pause_ is required and should be 0 or 1 + NOTE: use of pause command w/o the _pause_ argument is deprecated + +password <string password> + this is used for authentication with the server. + _password_ is simply the plaintext password + +ping + does nothing but return "OK" + +play <int song> + begin playing playlist at song number _song_, _song_ is optional + +playid <int songid> + begin playing playlist at song with _songid_, _songid_ is optional + +playlist + displays the current playlist + NOTE: do not use this, instead use 'playlistinfo' + +playlistinfo <int song> + displays list of songs in the playlist + _song_ is optional and specifies a single song to display info for + +playlistid <int songid> + displays list of songs in the playlist + _songid_ is optional and specifies a single song to display info for + +plchanges <playlist version> + displays changed songs currently in the playlist since + _playlist version_ + NOTE: to detect songs that were deleted at the end of the playlist, + use playlistlength returned by status command. + +plchangesposid <playlist version> + displays changed songs currently in the playlist since + _playlist version_ + This function only returns the position and the id of the changed song, not the complete metadata. This is more bandwidth efficient. + NOTE: to detect songs that were deleted at the end of the playlist, + use playlistlength returned by status command. + +previous + plays previous song in playlist + +random <int state> + set random state to _state_, _state_ should be 0 or 1 + +repeat <int state> + set repeat state to _state_, _state_ should be 0 or 1 + +rm <string name> + removes the playlist <name>.m3u from the playlist directory + +save <string name> + saves the current playlist to _name_.m3u in the playlist directory + +search <string type> <string what> + searches for any song that contain _what_ + _type_ can be "title","artist","album", or "filename" + search is not case sensitive + +seek <int song> <int time> + seeks to the position _time_ (in seconds) of entry _song_ in the + playlist + +seekid <int songid> <int time> + seeks to the position _time_ (in seconds) of song with _songid_ + +setvol <int vol> + set volume to _vol_ + _vol_ the range of volume is 0-100 + +shuffle + shuffles the current playlist + increments playlist version by 1 + +stats + display stats + artists: number of artists + albums: number of albums + songs: number of songs + uptime: daemon uptime in seconds + db_playtime: sum of all song times in db + db_update: last db update in UNIX time + playtime: time length of music played + +status + reports current status of player, and volume level. + volume: (0-100). + repeat: (0 or 1) + playlist: (31-bit unsigned integer, the playlist version number) + playlistlength: (integer, the length of the playlist) + state: ("play", "stop", or "pause") + song: (current song stopped on or playing, playlist song number) + songid: (current song stopped on or playing, playlist songid) + time: <int elapsed>:<time total> (of current playing/paused song) + bitrate: <int bitrate> (instantaneous bitrate in kbps) + xfade: <int seconds> (crossfade in seconds) + audio: <int sampleRate>:<int bits>:<int channels> + updatings_db: <int job id> + error: if there is an error, returns message here + +stop + stop playing + +swap <int song1> <int song2> + swap positions of _song1_ and _song2_ + increments playlist version by 1 + +swapid <int songid1> <int songid2> + swap positions of of songs with song id's of _songid1_ and _songid2_ + increments playlist version by 1 + +update <string path> + searches mp3 directory for new music and removes old music from the db + _path_ is an optional argument that maybe a particular directory or + song/file to update. + returned: + updating_db: <int job id> + where job id, is the job id requested for your update, and is displayed + in status, while the requested update is happening + increments playlist version by 1 + NOTE: To update a number of paths/songs at once, use command_list, + it will be much more faster/efficient. Also, if you use a + command_list for updating, only one update_db job id will be returned + per sequence of updates. + +volume <int change> + change volume by amount _change_ + NOTE: volume command is deprecated, use setvol instead + +COMMAND LIST +------------ + +To facilitate faster adding of files, etc, you can pass a list of commands all +at once using a command list. The command list beings with: + +command_list_begin + +or: + +command_list_ok_begin + +And ends with: + +command_list_end + +It does not execute any commands until the list has ended. The return +value is whatever the return for a list of commands is. On success +for all commands, OK is returned. If a command fails, no more commands +are executed and the appropriate ACK error is returned. If "command_list_ok_begin is used", "list_OK\n" is returned for each successful command executed +in the command list. diff --git a/trunk/doc/Makefile.am b/trunk/doc/Makefile.am new file mode 100644 index 000000000..4fe260180 --- /dev/null +++ b/trunk/doc/Makefile.am @@ -0,0 +1,4 @@ +man_MANS = mpd.1 mpd.conf.5 +docdir = $(prefix)/share/doc/$(PACKAGE) +doc_DATA = COMMANDS mpdconf.example +EXTRA_DIST = $(man_MANS) $(doc_DATA) diff --git a/trunk/doc/mpd.1 b/trunk/doc/mpd.1 new file mode 100644 index 000000000..c1ddf1762 --- /dev/null +++ b/trunk/doc/mpd.1 @@ -0,0 +1,61 @@ +.TH "Music Player Daemon" 1 +.SH NAME +MPD \- A daemon for playing music +.SH SYNOPSIS +.B mpd +.RI [ options ] +.RI [ CONF_FILE ] +.SH DESCRIPTION +MPD is a daemon for playing music. Music is played through the configured +audio output(s) (which are generally local, but can be remote). The daemon +stores info about all available music, and this info can be easily searched and +retrieved. Player control, info retrieval, and playlist management can all be +managed remotely. + +MPD searches for a config file in \fB~/.mpdconf\fP then \fB/etc/mpd.conf\fP or +uses CONF_FILE. + +Read more about MPD at <\fBhttp://www.musicpd.org/\fP>. +.SH OPTIONS +.TP +.BI --help +Output a brief help message. +.TP +.BI --kill +Kill the currently running mpd session. The pid_file parameter must be +specified in the config file for this to work. +.TP +.BI --create-db +Force (re)creation of database and exit. +.TP +.BI --no-create-db +Do not create database, even if it doesn't exist. +.TP +.BI --no-daemon +Don't detach from console. +.TP +.BI --stdout +Print messages to stdout and stderr. +.TP +.BI --verbose +Verbose logging. +.TP +.BI --version +Print version information. +.SH FILES +.TP +.BI ~/.mpdconf +User configuration file. +.TP +.BI /etc/mpd.conf +Global configuration file. +.SH SEE ALSO +mpd.conf(5), mpc(1) +.SH BUGS +If you find a bug, please report it at +.br +<\fBhttp://www.musicpd.org/mantis/bug_report_page.php\fP>. +.SH AUTHORS +Warren Dukes <warren.dukes@gmail.com> + +Special thanks to all the people that provided feedback and patches. diff --git a/trunk/doc/mpd.conf.5 b/trunk/doc/mpd.conf.5 new file mode 100644 index 000000000..efe95167d --- /dev/null +++ b/trunk/doc/mpd.conf.5 @@ -0,0 +1,387 @@ +.TH mpd.conf 5 +.SH NAME +mpd.conf \- Music Player Daemon configuration file +.SH DESCRIPTION +\fBmpd.conf\fP is the configuration file for mpd(1). If not specified on the +command line, MPD first searches for it at \fB~/.mpdconf\fP and then in +\fB/etc/mpd.conf\fP. + +Lines beginning with a "#" character are comments. All other non-empty lines +specify parameters and their values. These lines contain the parameter name +and parameter value (surrounded by double quotes) separated by whitespace +(either tabs or spaces). For example: + +parameter "value" + +The exception to this rule is the audio_output parameter, which is of the form: + +audio_output { +.br + parameter1 "value" + parameter2 "value" +.br +} + +Parameters that take a file or directory as an argument should use absolute +paths. + +See \fBdocs/mpdconf.example\fP in the source tarball for an example +configuration file. +.SH REQUIRED PARAMETERS +.TP +.B music_directory <directory> +This specifies the directory where music is located. +.TP +.B playlist_directory <directory> +This specifies the directory where saved playlists are stored. +.TP +.B db_file <file> +This specifies where the db file will be stored. +.TP +.B log_file <file> +This specifies where the log file should be located. +.TP +.B error_file <file> +This specifies where the error file should be located. +.SH OPTIONAL PARAMETERS +.TP +.B pid_file <file> +This specifies the file to save mpd's process ID in. +.TP +.B state_file <file> +This specifies if a state file is used and where it is located. The state of +mpd will be saved to this file when mpd is terminated by a TERM signal or by +the "kill" command. When mpd is restarted, it will read the state file and +restore the state of mpd (including the playlist). +.TP +.B user <username> +This specifies the user that mpd will run as, if set. +.TP +.B bind_to_address <ip address or hostname or any> +This specifies which address mpd binds to and listens on. Multiple +bind_to_address parameters may be specified. The default is "any", which binds +to all available addresses. +.TP +.B port <port> +This specifies the port that mpd listens on. The default is 6600. +.TP +.B log_level <default, secure, or verbose> +This specifies how verbose logs are. "default" is minimal logging, "secure" +reports from what address a connection is opened, and when it is closed, and +"verbose" records excessive amounts of information for debugging purposes. The +default is "default". +.TP +.B zeroconf_name <name> +If Zerconf is compiled into MPD, this is the service name to publish. This +should be unique to your local network, but name collisions will be properly +dealt with. +.TP +.B password <password@permissions> +This specifies a password for access to mpd. The format is +"password@permissions" where permissions is a comma delimited list composed +of "read", "add", "control", and/or "admin". "read" allows for reading of the +database, displaying the current playlist, and current status of mpd. "add" +allows for adding songs and loading playlists. "control" allows for all other +player and playlist manipulations. "admin" allows the db to be updated and for +the client to kill mpd. An example value is "somePassword@read,add". Multiple +password parameters may be specified. +.TP +.B default_permissions <permissions> +This specifies the permissions of a client that has not been authenticated +using a password. The format of permissions is specified in the description of +the "password" config parameter. If no passwords are specified, the default is +"read,add,control,admin", otherwise it is "" (no permissions). +.TP +.B audio_output +See \fBDESCRIPTION\fP and the various \fBAUDIO OUTPUT PARAMETERS\fP sections +for the format of this parameter. Multiple audio_output sections may be +specified. If no audio_output section is specified, then MPD will scan for a +usable audio output. +.TP +.B audio_output_format <sample_rate:bits:channels> +This specifies the sample rate, bits per sample, and number of channels of +audio that is sent to each audio output. Note that audio outputs may specify +their own audio format which will be used for actual output to the audio +device. An example is "44100:16:2" for 44100Hz, 16 bits, and 2 channels. The +default is to use the audio format of the input file. +.TP +.B samplerate_converter <integer or prefix> +This specifies the libsamplerate converter to use. The supplied value should +either be an integer or a prefix of the name of a converter. The default is +"Fastest Sinc Interpolator". + +At the time of this writing, the following converters are available: +.RS +.TP +Best Sinc Interpolator (0) + +Band limited sinc interpolation, best quality, 97dB SNR, 96% BW. +.TP +Medium Sinc Interpolator (1) + +Band limited sinc interpolation, medium quality, 97dB SNR, 90% BW. +.TP +Fastest Sinc Interpolator (2) + +Band limited sinc interpolation, fastest, 97dB SNR, 80% BW. +.TP +ZOH Interpolator (3) + +Zero order hold interpolator, very fast, very poor quality with audible +distortions. +.TP +Linear Interpolator (4) + +Linear interpolator, very fast, poor quality. +.RE +.IP +For an up-to-date list of available converters, please see the libsamplerate +documentation (available online at <\fBhttp://www.mega-nerd.com/SRC/\fP>). +.TP +.B mixer_type <oss, alsa or software> +This specifies which mixer to use. The default depends on what audio output +support mpd was built with. +.TP +.B mixer_device <mixer dev> +This specifies which mixer to use. The default for oss is "/dev/mixer"; the +default for alsa is "default". +.TP +.B mixer_control <mixer ctrl> +This specifies which mixer control to use (sometimes referred to as the +"device"). Examples of mixer controls are PCM, Line1, Master, etc. An example +for OSS is "Pcm", and an example for alsa is "PCM". +.TP +.B replaygain <album or track> +If specified, mpd will adjust the volume of songs played using ReplayGain tags +(see <\fBhttp://www.replaygain.org/\fP>). Setting this to "album" will adjust +volume using the album's ReplayGain tags, while setting it to "track" will +adjust it using the track ReplayGain tags. Currently only FLAC, Ogg Vorbis, +Musepack, and MP3 (through ID3v2 ReplayGain tags, not APEv2) are supported. +.TP +.B replaygain_preamp <-15 to 15> +This is the gain (in dB) applied to songs with ReplayGain tags. +.TP +.B volume_normalization <yes or no> +If yes, mpd will normalize the volume of songs as they play. The default is no. +.TP +.B audio_buffer_size <size in KiB> +This specifies the size of the audio buffer in kibibytes. The default is 2048, +large enough for nearly 12 seconds of CD-quality audio. +.TP +.B buffer_before_play <0-100%> +This specifies how much of the audio buffer should be filled before playing a +song. Try increasing this if you hear skipping when manually changing songs. +The default is 10%, a little over 1 second of CD-quality audio with the default +buffer size. +.TP +.B http_buffer_size <size in KiB> +This specifies the size of the buffer used for playing HTTP streams. The +default is 128. +.TP +.B http_prebuffer_size <size in KiB> +This specifies how much of an HTTP stream should be buffered before beginning +playback. The default is 32. +.TP +.B http_proxy_host <hostname> +Use to specify the proxy host used for HTTP connections. +.TP +.B http_proxy_port <port> +The port that the HTTP proxy host uses. +.TP +.B http_proxy_user <username> +If the HTTP proxy server requires authentication, this specifies the username. +.TP +.B http_proxy_password <password> +If the HTTP proxy server requires authentication, this specifies the password. +.TP +.B connection_timeout <seconds> +If a client does not send any new data in this time period, the connection is +closed. The default is 60. +.TP +.B max_connections <number> +This specifies the maximum number of clients that can be connected to mpd. The +default is 5. +.TP +.B max_playlist_length <number> +This specifies the maximum number of songs that can be in the playlist. The +default is 4096. +.TP +.B max_command_list_size <size in KiB> +This specifies the maximum size a command list can be. The default is 2048. +.TP +.B max_output_buffer_size <size in KiB> +This specifies the maximum size of the output buffer to a client. The default +is 8192. +.TP +.B filesystem_charset <charset> +This specifies the character set used for the filesystem. A list of supported +character sets can be obtained by running "iconv -l". The default is +determined from the locale when the db was originally created. +.TP +.B id3v1_encoding <charset> +This specifies the character set which ID3v1 tags are encoded in. A list of +supported character sets can be obtained by running "iconv -l". The default is +to let libid3tag convert them (from ISO-8859-1, as the standard specifies) and +do no additional conversion. +.TP +.B gapless_mp3_playback <yes or no> +This specifies whether to support gapless playback of MP3s which have the +necessary headers. Useful if your MP3s have headers with incorrect +information. If you have such MP3s, it is highly recommended that you fix them +using vbrfix (available from <http://www.willwap.co.uk/Programs/vbrfix.php>) +instead of disabling gapless MP3 playback. The default is to support gapless +MP3 playback. +.TP +.B save_absolute_paths_in_playlists <yes or no> +This specifies whether relative or absolute paths for song filenames are used +when saving playlists. The default is "no". +.TP +.B metadata_to_use <tags> +This specifies the tag types that will be scanned for and made available to +clients. Note that you must recreate (not update) your database for changes to +this parameter to take effect. Possible values are artist, album, title, +track, name, genre, date, composer, performer, comment, and disc. Multiple +tags may be specified as a comma separated list. An example value is +"artist,album,title,track". The special value "none" may be used alone to +disable all metadata. The default is to use all known tag types except for +comments. +.SH REQUIRED AUDIO OUTPUT PARAMETERS +.TP +.B type <type> +This specifies the audio output type. See the list of supported outputs in mpd +--version for possible values. +.TP +.B name <name> +This specifies a unique name for the audio output. +.SH OPTIONAL AUDIO OUTPUT PARAMETERS +.TP +.B format <sample_rate:bits:channels> +This specifies the sample rate, bits per sample, and number of channels of +audio that is sent to the audio output device. See documentation for the +\fBaudio_output_format\fP parameter for more details. The default is to use +whatever audio format is passed to the audio output. +.SH OPTIONAL ALSA OUTPUT PARAMETERS +.TP +.B device <dev> +This specifies the device to use for audio output. The default is "default". +.TP +.B use_mmap <yes or no> +Setting this allows you to use memory-mapped I/O. Certain hardware setups may +benefit from this, but most do not. Most users do not need to set this. The +default is to not use memory-mapped I/O. +.TP +.B buffer_time <time in microseconds> +This sets the length of the hardware sample buffer in microseconds. Increasing +it may help to reduce or eliminate skipping on certain setups. Most users do +not need to change this. The default is 500000 microseconds (0.5 seconds). +.TP +.B period_time <time in microseconds> +This sets the time between hardware sample transfers in microseconds. +Increasing this can reduce CPU usage while lowering it can reduce underrun +errors on bandwidth-limited devices. Some users have reported good results +with this set to 50000, but not all devices support values this high. Most +users do not need to change this. The default is 256000000 / sample_rate(kHz), +or 5804 microseconds for CD-quality audio. +.SH OPTIONAL OSS OUTPUT PARAMETERS +.TP +.B device <dev> +This specifies the device to use for audio output. The default is "/dev/dsp". +.SH OPTIONAL PULSE OUTPUT PARAMETERS +.TP +.B server <server list> +A space separated list of servers to try to connect to. See +<\fBhttp://www.pulseaudio.org/wiki/ServerStrings\fP> for more details. The +default is to let PulseAudio choose a server. +.TP +.B sink <sink> +The sink to output to. The default is to let PulseAudio choose a sink. +.SH REQUIRED JACK OUTPUT PARAMETERS +.TP +.B name <name> +The client name to use when connecting to JACK. The output ports <name>:left +and <name>:right will also be created for the left and right channels, +respectively. +.SH OPTIONAL JACK OUTPUT PARAMETERS +.TP +.B ports <left_port,right_port> +This specifies the left and right ports to connect to for the left and right +channels, respectively. The default is to let JACK choose a pair of ports. +.TP +.B ringbuffer_size <size in bytes> +This specifies the size of the ringbuffer in bytes. The default is 32768. +.SH OPTIONAL AO OUTPUT PARAMETERS +.TP +.B driver <driver> +This specifies the libao driver to use for audio output. Possible values +depend on what libao drivers are available. See +<\fBhttp://www.xiph.org/ao/doc/drivers.html\fP> for information on some +commonly used drivers. Typical values for Linux include "oss" and "alsa09". +The default is "default", which causes libao to select an appropriate plugin. +.TP +.B options <opts> +This specifies the options to use for the selected libao driver. For oss, the +only option available is "dsp". For alsa09, the available options are: "dev", +"buf_size", and "periods". See <\fBhttp://www.xiph.org/ao/doc/drivers.html\fP> +for available options for some commonly used drivers. Options are assigned +using "=", and ";" is used to separate options. An example for oss: +"dsp=/dev/dsp". An example for alsa09: "dev=hw:0,0;buf_size=4096". The +default is "". +.TP +.B write_size <size in bytes> +This specifies how many bytes to write to the audio device at once. This +parameter is to work around a bug in older versions of libao on sound cards +with very small buffers. The default is 1024. +.SH REQUIRED SHOUT OUTPUT PARAMETERS +.TP +.B name <name> +This specifies not only the unique audio output name, but also the stream +title. +.TP +.B host <hostname> +This specifies the hostname of the icecast server to connect to. +.TP +.B port <port> +This specifies the port of the icecast server to connect to. +.TP +.B mount <mountpoint> +This specifies the icecast mountpoint to use. +.TP +.B password <password> +This specifies the password to use when logging in to the icecast server. +.TP +.B quality <quality> +This specifies the ogg encoding quality to use. The value must be between 0 +and 10. Fractional values, such as 2.5, are permitted. Either the quality or +the bitrate parameter must be specified, but not both. +.TP +.B bitrate <kbps> +This specifies the bitrate to use for encoding. Either the quality or the +bitrate parameter must be specified, but not both. +.TP +.B format <sample_rate:bits:channels> +This specifies the sample rate, bits per sample, and number of channels to use +for encoding. +.SH OPTIONAL SHOUT OUTPUT PARAMETERS +.TP +.B user <username> +This specifies the username to use when logging in to the icecast server. The +default is "source". +.TP +.B public <yes or no> +This specifies whether to request that the stream be listed in all public +stream directories that the icecast server knows about. The default is no. +.TP +.B description <description> +This specifies a description of the stream. +.TP +.B genre <genre> +This specifies the genre(s) of the stream. +.SH FILES +.TP +.BI ~/.mpdconf +User configuration file. +.TP +.BI /etc/mpd.conf +Global configuration file. +.SH SEE ALSO +mpd(1), mpc(1) diff --git a/trunk/doc/mpdconf.example b/trunk/doc/mpdconf.example new file mode 100644 index 000000000..8cd367541 --- /dev/null +++ b/trunk/doc/mpdconf.example @@ -0,0 +1,268 @@ +# An example configuration file for MPD +# See the mpd.conf man page for a more detailed description of each parameter. + +######################## REQUIRED PATHS ######################## +music_directory "~/music" +playlist_directory "~/.mpd/playlists" +db_file "~/.mpd/mpd.db" +log_file "~/.mpd/mpd.log" +error_file "~/.mpd/mpd.error" +################################################################ + + +######################## OPTIONAL PATHS ######################## +# +# If you wish to use mpd --kill to stop MPD, then you must +# specify a file here in which to store MPD's process ID. +# +#pid_file "~/.mpd/mpd.pid" +# +# If specified, MPD will save its current state (playlist, +# current song, playing/paused, etc.) at exit. This will be +# used to restore the session the next time it is run. +# +#state_file "~/.mpd/mpdstate" +# +################################################################ + + +######################## DAEMON OPTIONS ######################## +# +# If started as root, MPD will drop root privileges and run as +# this user instead. Otherwise, MPD will run as the user it was +# started by. If left unspecified, MPD will not drop root +# privileges at all (not recommended). +# +#user "nobody" +# +# The address and port to listen on. +# +#bind_to_address "any" +#port "6600" +# +# Controls the amount of information that is logged. Can be +# "default", "secure", or "verbose". +# +#log_level "default" +# +# If Zeroconf is configured, the service name to publish. This +# should be unique on your local network, but name collisions +# will be taken care of for you. +# +#zeroconf_name "Music Player" +# +################################################################ + + +########################## PERMISSIONS ######################### +# +# MPD can require that users specify a password before using it. +# You may specify one ore more here, along with what users who +# log in with that password are allowed to do. +# +#password "password@read,add,control,admin" +# +# Specifies what permissions a user who has not logged in with a +# password has. By default, all users have full access to MPD +# if no password is specified above, or no access if one or +# more passwords are specified. +# +#default_permissions "read,add,control,admin" +# +################################################################ + + +########################## AUDIO OUTPUT ######################## +# +# MPD supports many audio output types, as well as playing +# through multiple audio outputs at the same time. You can +# specify one or more here. If you don't specify any, MPD will +# automatically scan for a usable audio output. +# +# See <http://mpd.wikia.com/wiki/Configuration#Audio_Outputs> +# for examples of other audio outputs. +# +# An example of an ALSA output: +# +#audio_output { +# type "alsa" +# name "My ALSA Device" +# device "hw:0,0" # optional +# format "44100:16:2" # optional +#} +# +# An example of an OSS output: +# +#audio_output { +# type "oss" +# name "My OSS Device" +# device "/dev/dsp" # optional +# format "44100:16:2" # optional +#} +# +# An example of a shout output (for streaming to Icecast): +# +#audio_output { +# type "shout" +# name "My Shout Stream" +# host "localhost" +# port "8000" +# mount "/mpd.ogg" +# password "hackme" +# quality "5.0" +# bitrate "128" +# format "44100:16:1" +# user "source" # optional +# description "My Stream Description" # optional +# genre "jazz" # optional +# public "no" # optional +#} +# +# Force all decoded audio to be converted to this format before +# being passed to the audio outputs. +# +#audio_output_format "44100:16:2" +# +# If MPD has been compiled with libsamplerate support, this +# specifies the sample rate converter to use. Possible +# values can be found in the mpd.conf man page or the +# libsamplerate documentation. +# +#samplerate_converter "Fastest Sinc Interpolator" +# +################################################################ + + +############################# MIXER ############################ +# +# MPD needs to know what mixer settings to change when you +# adjust the volume. If you don't specify one here, MPD will +# pick one based on which ones it was compiled with support for. +# +# An example for controlling an ALSA mixer: +# +#mixer_type "alsa" +#mixer_device "default" +#mixer_control "PCM" +# +# An example for controlling an OSS mixer: +# +#mixer_type "oss" +#mixer_device "/dev/mixer" +#mixer_control "PCM" +# +# If you want MPD to adjust the volume of audio sent to the +# audio outputs, you can tell it to use the software mixer: +# +#mixer_type "software" +# +################################################################ + + +######################### NORMALIZATION ######################## +# +# Specifies the type of ReplayGain to use. Can be "album" or +# "track". ReplayGain will not be used if not specified. See +# <http://www.replaygain.org> for more details. +# +#replaygain "album" +# +# Sets the pre-amp used for files that have ReplayGain tags. +# +#replaygain_preamp "0" +# +# Enable on the fly volume normalization. This will cause the +# volume of all songs played to be adjusted so that they sound +# as though they are of equal loudness. +# +#volume_normalization "no" +# +################################################################ + + +########################### BUFFERING ########################## +# +# The size of the buffer containing decoded audio. You probably +# shouldn't change this. +# +#audio_buffer_size "2048" +# +# How much of the buffer to fill before beginning to play. +# Increase this if you hear skipping when changing songs. +# +#buffer_before_play "10%" +# +# Similar options for the HTTP stream buffer. If you hear +# skipping while playing HTTP streams, you may wish to increase +# these. +# +#http_buffer_size "128" +#http_prebuffer_size "25%" +# +################################################################ + + +########################### HTTP PROXY ######################### +# +# Specifies the HTTP proxy to use for playing HTTP streams. +# +#http_proxy_host "proxy.isp.com" +#http_proxy_port "8080" +#http_proxy_user "user" +#http_proxy_password "password" +# +################################################################ + + +############################# LIMITS ########################### +# +# These are various limits to prevent MPD from using too many +# resources. You should only change them if they start +# restricting your usage of MPD. +# +#connection_timeout "60" +#max_connections "5" +#max_playlist_length "16384" +#max_command_list_size "2048" +#max_output_buffer_size "8192" +# +################################################################ + + +###################### CHARACTER ENCODINGS ##################### +# +# If file or directory names do not display correctly, then you +# may need to change this. In most cases it should be either +# "ISO-8859-1" or "UTF-8". You must recreate your database +# after changing this (use mpd --create-db). +# +#filesystem_charset "ISO-8859-1" +# +# The encoding that ID3v1 tags should be converted from. +# +#id3v1_encoding "ISO-8859-1" +# +################################################################ + + +######################### OTHER OPTIONS ######################## +# +# Try disabling this if you have MP3s which appear to end +# abruptly. If this solves the problem, it is highly +# recommended that you fix your MP3s with vbrfix (available from +# <http://www.willwap.co.uk/Programs/vbrfix.php>), at which +# point you can re-enable support for gapless MP3 playback. +# +#gapless_mp3_playback "yes" +# +# Enable this if you wish to use your MPD created playlists in +# other music players. +# +#save_absolute_paths_in_playlists "no" +# +# A list of tag types that MPD will scan for and make available +# to clients. +# +#metadata_to_use "artist,album,title,track,name,genre,date,composer,performer,disc" +# +################################################################ diff --git a/trunk/m4/alsa.m4 b/trunk/m4/alsa.m4 new file mode 100644 index 000000000..ea5730cf4 --- /dev/null +++ b/trunk/m4/alsa.m4 @@ -0,0 +1,142 @@ +dnl Configure Paths for Alsa +dnl Some modifications by Richard Boulton <richard-alsa@tartarus.org> +dnl Christopher Lansdown <lansdoct@cs.alfred.edu> +dnl Jaroslav Kysela <perex@suse.cz> +dnl Last modification: alsa.m4,v 1.23 2004/01/16 18:14:22 tiwai Exp +dnl AM_PATH_ALSA([MINIMUM-VERSION [, ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]]) +dnl Test for libasound, and define ALSA_CFLAGS and ALSA_LIBS as appropriate. +dnl enables arguments --with-alsa-prefix= +dnl --with-alsa-enc-prefix= +dnl --disable-alsatest +dnl +dnl For backwards compatibility, if ACTION_IF_NOT_FOUND is not specified, +dnl and the alsa libraries are not found, a fatal AC_MSG_ERROR() will result. +dnl +AC_DEFUN([AM_PATH_ALSA], +[dnl Save the original CFLAGS, LDFLAGS, and LIBS +alsa_save_CFLAGS="$CFLAGS" +alsa_save_LDFLAGS="$LDFLAGS" +alsa_save_LIBS="$LIBS" +alsa_found=yes + +dnl +dnl Get the cflags and libraries for alsa +dnl +AC_ARG_WITH(alsa-prefix, +[ --with-alsa-prefix=PFX Prefix where Alsa library is installed(optional)], +[alsa_prefix="$withval"], [alsa_prefix=""]) + +AC_ARG_WITH(alsa-inc-prefix, +[ --with-alsa-inc-prefix=PFX Prefix where include libraries are (optional)], +[alsa_inc_prefix="$withval"], [alsa_inc_prefix=""]) + +dnl FIXME: this is not yet implemented +AC_ARG_ENABLE(alsatest, +[ --disable-alsatest Do not try to compile and run a test Alsa program], +[enable_alsatest="$enableval"], +[enable_alsatest=yes]) + +dnl Add any special include directories +AC_MSG_CHECKING(for ALSA CFLAGS) +if test "$alsa_inc_prefix" != "" ; then + ALSA_CFLAGS="$ALSA_CFLAGS -I$alsa_inc_prefix" + CFLAGS="$CFLAGS -I$alsa_inc_prefix" +fi +AC_MSG_RESULT($ALSA_CFLAGS) +CFLAGS="$alsa_save_CFLAGS" + +dnl add any special lib dirs +AC_MSG_CHECKING(for ALSA LDFLAGS) +if test "$alsa_prefix" != "" ; then + ALSA_LIBS="$ALSA_LIBS -L$alsa_prefix" + LDFLAGS="$LDFLAGS $ALSA_LIBS" +fi + +dnl add the alsa library +ALSA_LIBS="$ALSA_LIBS -lasound -lm -ldl -lpthread" +LIBS=`echo $LIBS | sed 's/-lm//'` +LIBS=`echo $LIBS | sed 's/-ldl//'` +LIBS=`echo $LIBS | sed 's/-lpthread//'` +LIBS=`echo $LIBS | sed 's/ //'` +LIBS="$ALSA_LIBS $LIBS" +AC_MSG_RESULT($ALSA_LIBS) + +dnl Check for a working version of libasound that is of the right version. +min_alsa_version=ifelse([$1], ,0.1.1,$1) +AC_MSG_CHECKING(for libasound headers version >= $min_alsa_version) +no_alsa="" + alsa_min_major_version=`echo $min_alsa_version | \ + sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'` + alsa_min_minor_version=`echo $min_alsa_version | \ + sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'` + alsa_min_micro_version=`echo $min_alsa_version | \ + sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'` + +AC_LANG_SAVE +AC_LANG_C +AC_TRY_COMPILE([ +#include <alsa/asoundlib.h> +], [ +/* ensure backward compatibility */ +#if !defined(SND_LIB_MAJOR) && defined(SOUNDLIB_VERSION_MAJOR) +#define SND_LIB_MAJOR SOUNDLIB_VERSION_MAJOR +#endif +#if !defined(SND_LIB_MINOR) && defined(SOUNDLIB_VERSION_MINOR) +#define SND_LIB_MINOR SOUNDLIB_VERSION_MINOR +#endif +#if !defined(SND_LIB_SUBMINOR) && defined(SOUNDLIB_VERSION_SUBMINOR) +#define SND_LIB_SUBMINOR SOUNDLIB_VERSION_SUBMINOR +#endif + +# if(SND_LIB_MAJOR > $alsa_min_major_version) + exit(0); +# else +# if(SND_LIB_MAJOR < $alsa_min_major_version) +# error not present +# endif + +# if(SND_LIB_MINOR > $alsa_min_minor_version) + exit(0); +# else +# if(SND_LIB_MINOR < $alsa_min_minor_version) +# error not present +# endif + +# if(SND_LIB_SUBMINOR < $alsa_min_micro_version) +# error not present +# endif +# endif +# endif +exit(0); +], + [AC_MSG_RESULT(found.)], + [AC_MSG_RESULT(not present.) + ifelse([$3], , [AC_MSG_ERROR(Sufficiently new version of libasound not found.)]) + alsa_found=no] +) +AC_LANG_RESTORE + +dnl Now that we know that we have the right version, let's see if we have the library and not just the headers. +if test "x$enable_alsatest" = "xyes"; then +AC_CHECK_LIB([asound], [snd_ctl_open],, + [ifelse([$3], , [AC_MSG_ERROR(No linkable libasound was found.)]) + alsa_found=no] +) +fi + +LDFLAGS="$alsa_save_LDFLAGS" +LIBS="$alsa_save_LIBS" + +if test "x$alsa_found" = "xyes" ; then + ifelse([$2], , :, [$2]) +else + ALSA_CFLAGS="" + ALSA_LIBS="" + ifelse([$3], , :, [$3]) +fi + +dnl That should be it. Now just export out symbols: +AC_SUBST(ALSA_CFLAGS) +AC_SUBST(ALSA_LIBS) +]) + diff --git a/trunk/m4/ao.m4 b/trunk/m4/ao.m4 new file mode 100644 index 000000000..a852ad86c --- /dev/null +++ b/trunk/m4/ao.m4 @@ -0,0 +1,112 @@ +# ao.m4 +# Configure paths for libao +# Jack Moffitt <jack@icecast.org> 10-21-2000 +# Shamelessly stolen from Owen Taylor and Manish Singh + +dnl XIPH_PATH_AO([ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) +dnl Test for libao, and define AO_CFLAGS and AO_LIBS +dnl +AC_DEFUN([XIPH_PATH_AO], +[dnl +dnl Get the cflags and libraries +dnl +AC_ARG_WITH(ao,[ --with-ao=PFX Prefix where libao is installed (optional)], ao_prefix="$withval", ao_prefix="") +AC_ARG_WITH(ao-libraries,[ --with-ao-libraries=DIR Directory where libao library is installed (optional)], ao_libraries="$withval", ao_libraries="") +AC_ARG_WITH(ao-includes,[ --with-ao-includes=DIR Directory where libao header files are installed (optional)], ao_includes="$withval", ao_includes="") +AC_ARG_ENABLE(aotest, [ --disable-aotest Do not try to compile and run a test ao program],, enable_aotest=yes) + + + if test "x$ao_libraries" != "x" ; then + AO_LIBS="-L$ao_libraries" + elif test "x$ao_prefix" != "x"; then + AO_LIBS="-L$ao_prefix/lib" + elif test "x$prefix" != "xNONE"; then + AO_LIBS="-L$prefix/lib" + fi + + if test "x$ao_includes" != "x" ; then + AO_CFLAGS="-I$ao_includes" + elif test "x$ao_prefix" != "x"; then + AO_CFLAGS="-I$ao_prefix/include" + elif test "x$prefix" != "xNONE"; then + AO_CFLAGS="-I$prefix/include" + fi + + # see where dl* and friends live + AC_CHECK_FUNCS(dlopen, [AO_DL_LIBS=""], [ + AC_CHECK_LIB(dl, dlopen, [AO_DL_LIBS="-ldl"], [ + AC_MSG_WARN([could not find dlopen() needed by libao sound drivers + your system may not be supported.]) + ]) + ]) + + AO_LIBS="$AO_LIBS -lao $AO_DL_LIBS" + + AC_MSG_CHECKING(for ao) + no_ao="" + + + if test "x$enable_aotest" = "xyes" ; then + ac_save_CFLAGS="$CFLAGS" + ac_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $AO_CFLAGS" + LIBS="$LIBS $AO_LIBS" +dnl +dnl Now check if the installed ao is sufficiently new. +dnl + rm -f conf.aotest + AC_TRY_RUN([ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ao/ao.h> + +int main () +{ + system("touch conf.aotest"); + return 0; +} + +],, no_ao=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"]) + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + fi + + if test "x$no_ao" = "x" ; then + AC_MSG_RESULT(yes) + ifelse([$1], , :, [$1]) + else + AC_MSG_RESULT(no) + if test -f conf.aotest ; then + : + else + echo "*** Could not run ao test program, checking why..." + CFLAGS="$CFLAGS $AO_CFLAGS" + LIBS="$LIBS $AO_LIBS" + AC_TRY_LINK([ +#include <stdio.h> +#include <ao/ao.h> +], [ return 0; ], + [ echo "*** The test program compiled, but did not run. This usually means" + echo "*** that the run-time linker is not finding ao or finding the wrong" + echo "*** version of ao. If it is not finding ao, you'll need to set your" + echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point" + echo "*** to the installed location Also, make sure you have run ldconfig if that" + echo "*** is required on your system" + echo "***" + echo "*** If you have an old version installed, it is best to remove it, although" + echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"], + [ echo "*** The test program failed to compile or link. See the file config.log for the" + echo "*** exact error that occured. This usually means ao was incorrectly installed" + echo "*** or that you have moved ao since it was installed." ]) + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + fi + AO_CFLAGS="" + AO_LIBS="" + ifelse([$2], , :, [$2]) + fi + AC_SUBST(AO_CFLAGS) + AC_SUBST(AO_LIBS) + rm -f conf.aotest +]) diff --git a/trunk/m4/audiofile.m4 b/trunk/m4/audiofile.m4 new file mode 100644 index 000000000..86176c5d4 --- /dev/null +++ b/trunk/m4/audiofile.m4 @@ -0,0 +1,179 @@ +# Configure paths for the Audio File Library +# Bertrand Guiheneuf 98-10-21 +# stolen from esd.m4 in esound : +# Manish Singh 98-9-30 +# stolen back from Frank Belew +# stolen from Manish Singh +# Shamelessly stolen from Owen Taylor + +dnl AM_PATH_AUDIOFILE([MINIMUM-VERSION, [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]]) +dnl Test for Audio File Library, and define AUDIOFILE_CFLAGS and AUDIOFILE_LIBS. +dnl +AC_DEFUN([AM_PATH_AUDIOFILE], +[dnl +dnl Get compiler flags and libraries from the audiofile-config script. +dnl +AC_ARG_WITH(audiofile-prefix,[ --with-audiofile-prefix=PFX Prefix where Audio File Library is installed (optional)], + audiofile_prefix="$withval", audiofile_prefix="") +AC_ARG_WITH(audiofile-exec-prefix,[ --with-audiofile-exec-prefix=PFX Exec prefix where Audio File Library is installed (optional)], + audiofile_exec_prefix="$withval", audiofile_exec_prefix="") +AC_ARG_ENABLE(audiofiletest, [ --disable-audiofiletest Do not try to compile and run a test Audio File Library program], , enable_audiofiletest=yes) + + if test x$audiofile_exec_prefix != x ; then + audiofile_args="$audiofile_args --exec-prefix=$audiofile_exec_prefix" + if test x${AUDIOFILE_CONFIG+set} != xset ; then + AUDIOFILE_CONFIG=$audiofile_exec_prefix/bin/audiofile-config + fi + fi + if test x$audiofile_prefix != x ; then + audiofile_args="$audiofile_args --prefix=$audiofile_prefix" + if test x${AUDIOFILE_CONFIG+set} != xset ; then + AUDIOFILE_CONFIG=$audiofile_prefix/bin/audiofile-config + fi + fi + + AC_PATH_PROG(AUDIOFILE_CONFIG, audiofile-config, no) + min_audiofile_version=ifelse([$1], ,0.2.5,$1) + AC_MSG_CHECKING(for Audio File Library - version >= $min_audiofile_version) + no_audiofile="" + if test "$AUDIOFILE_CONFIG" = "no" ; then + no_audiofile=yes + else + AUDIOFILE_LIBS=`$AUDIOFILE_CONFIG $audiofileconf_args --libs` + AUDIOFILE_CFLAGS=`$AUDIOFILE_CONFIG $audiofileconf_args --cflags` + audiofile_major_version=`$AUDIOFILE_CONFIG $audiofile_args --version | \ + sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\1/'` + audiofile_minor_version=`$AUDIOFILE_CONFIG $audiofile_args --version | \ + sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\2/'` + audiofile_micro_version=`$AUDIOFILE_CONFIG $audiofile_config_args --version | \ + sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\)/\3/'` + if test "x$enable_audiofiletest" = "xyes" ; then + AC_LANG_SAVE + AC_LANG_C + ac_save_CFLAGS="$CFLAGS" + ac_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $AUDIOFILE_CFLAGS" + LIBS="$LIBS $AUDIOFILE_LIBS" +dnl +dnl Now check if the installed Audio File Library is sufficiently new. +dnl (Also checks the sanity of the results of audiofile-config to some extent.) +dnl + rm -f conf.audiofiletest + AC_TRY_RUN([ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <audiofile.h> + +char* +my_strdup (char *str) +{ + char *new_str; + + if (str) + { + new_str = malloc ((strlen (str) + 1) * sizeof(char)); + strcpy (new_str, str); + } + else + new_str = NULL; + + return new_str; +} + +int main () +{ + int major, minor, micro; + char *tmp_version; + + system ("touch conf.audiofiletest"); + + /* HP/UX 9 (%@#!) writes to sscanf strings */ + tmp_version = my_strdup("$min_audiofile_version"); + if (sscanf(tmp_version, "%d.%d.%d", &major, &minor, µ) != 3) { + printf("%s, bad version string\n", "$min_audiofile_version"); + exit(1); + } + + if (($audiofile_major_version > major) || + (($audiofile_major_version == major) && ($audiofile_minor_version > minor)) || + (($audiofile_major_version == major) && ($audiofile_minor_version == minor) && ($audiofile_micro_version >= micro))) + { + return 0; + } + else + { + printf("\n*** 'audiofile-config --version' returned %d.%d.%d, but the minimum version\n", $audiofile_major_version, $audiofile_minor_version, $audiofile_micro_version); + printf("*** of the Audio File Library required is %d.%d.%d. If audiofile-config is correct, then it is\n", major, minor, micro); + printf("*** best to upgrade to the required version.\n"); + printf("*** If audiofile-config was wrong, set the environment variable AUDIOFILE_CONFIG\n"); + printf("*** to point to the correct copy of audiofile-config, and remove the file\n"); + printf("*** config.cache before re-running configure\n"); + return 1; + } +} + +],, no_audiofile=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"]) + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + AC_LANG_RESTORE + fi + fi + if test "x$no_audiofile" = x ; then + AC_MSG_RESULT(yes) + ifelse([$2], , :, [$2]) + else + AC_MSG_RESULT(no) + if test "$AUDIOFILE_CONFIG" = "no" ; then + cat <<END +*** The audiofile-config script installed by the Audio File Library could +*** not be found. If the Audio File Library was installed in PREFIX, make +*** sure PREFIX/bin is in your path, or set the AUDIOFILE_CONFIG +*** environment variable to the full path to audiofile-config. +END + else + if test -f conf.audiofiletest ; then + : + else + echo "*** Could not run Audio File Library test program; checking why..." + AC_LANG_SAVE + AC_LANG_C + CFLAGS="$CFLAGS $AUDIOFILE_CFLAGS" + LIBS="$LIBS $AUDIOFILE_LIBS" + AC_TRY_LINK([ +#include <stdio.h> +#include <audiofile.h> +], [ return 0; ], + [ cat <<END +*** The test program compiled, but did not run. This usually means that +*** the run-time linker is not finding Audio File Library or finding the +*** wrong version of Audio File Library. +*** +*** If it is not finding Audio File Library, you'll need to set your +*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point +*** to the installed location. Also, make sure you have run ldconfig if +*** that is required on your system. +*** +*** If you have an old version installed, it is best to remove it, although +*** you may also be able to get things to work by modifying +*** LD_LIBRARY_PATH. +END + ], + [ echo "*** The test program failed to compile or link. See the file config.log" + echo "*** for the exact error that occured. This usually means the Audio File" + echo "*** Library was incorrectly installed or that you have moved the Audio" + echo "*** File Library since it was installed. In the latter case, you may want" + echo "*** to edit the audiofile-config script: $AUDIOFILE_CONFIG" ]) + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + AC_LANG_RESTORE + fi + fi + AUDIOFILE_CFLAGS="" + AUDIOFILE_LIBS="" + ifelse([$3], , :, [$3]) + fi + AC_SUBST(AUDIOFILE_CFLAGS) + AC_SUBST(AUDIOFILE_LIBS) + rm -f conf.audiofiletest +]) diff --git a/trunk/m4/codeset.m4 b/trunk/m4/codeset.m4 new file mode 100644 index 000000000..59535ebcf --- /dev/null +++ b/trunk/m4/codeset.m4 @@ -0,0 +1,23 @@ +# codeset.m4 serial AM1 (gettext-0.10.40) +dnl Copyright (C) 2000-2002 Free Software Foundation, Inc. +dnl This file is free software, distributed under the terms of the GNU +dnl General Public License. As a special exception to the GNU General +dnl Public License, this file may be distributed as part of a program +dnl that contains a configuration script generated by Autoconf, under +dnl the same distribution terms as the rest of that program. + +dnl From Bruno Haible. + +AC_DEFUN([AM_LANGINFO_CODESET], +[ + AC_CACHE_CHECK([for nl_langinfo and CODESET], am_cv_langinfo_codeset, + [AC_TRY_LINK([#include <langinfo.h>], + [char* cs = nl_langinfo(CODESET);], + am_cv_langinfo_codeset=yes, + am_cv_langinfo_codeset=no) + ]) + if test $am_cv_langinfo_codeset = yes; then + AC_DEFINE(HAVE_LANGINFO_CODESET, 1, + [Define if you have <langinfo.h> and nl_langinfo(CODESET).]) + fi +]) diff --git a/trunk/m4/libFLAC.m4 b/trunk/m4/libFLAC.m4 new file mode 100644 index 000000000..8f111b2fa --- /dev/null +++ b/trunk/m4/libFLAC.m4 @@ -0,0 +1,104 @@ +# Configure paths for libFLAC +# "Inspired" by ogg.m4 + +dnl AM_PATH_LIBFLAC([ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) +dnl Test for libFLAC, and define LIBFLAC_CFLAGS and LIBFLAC_LIBS +dnl +AC_DEFUN([AM_PATH_LIBFLAC], +[dnl +dnl Get the cflags and libraries +dnl +AC_ARG_WITH(libFLAC,[ --with-libFLAC=PFX Prefix where libFLAC is installed (optional)], libFLAC_prefix="$withval", libFLAC_prefix="") +AC_ARG_WITH(libFLAC-libraries,[ --with-libFLAC-libraries=DIR Directory where libFLAC library is installed (optional)], libFLAC_libraries="$withval", libFLAC_libraries="") +AC_ARG_WITH(libFLAC-includes,[ --with-libFLAC-includes=DIR Directory where libFLAC header files are installed (optional)], libFLAC_includes="$withval", libFLAC_includes="") +AC_ARG_ENABLE(libFLACtest, [ --disable-libFLACtest Do not try to compile and run a test libFLAC program],, enable_libFLACtest=yes) + + if test "x$libFLAC_libraries" != "x" ; then + LIBFLAC_LIBS="-L$libFLAC_libraries" + elif test "x$libFLAC_prefix" != "x" ; then + LIBFLAC_LIBS="-L$libFLAC_prefix/lib" + elif test "x$prefix" != "xNONE" ; then + LIBFLAC_LIBS="-L$libdir" + fi + + LIBFLAC_LIBS="$LIBFLAC_LIBS -lFLAC -lm" + + if test "x$libFLAC_includes" != "x" ; then + LIBFLAC_CFLAGS="-I$libFLAC_includes" + elif test "x$libFLAC_prefix" != "x" ; then + LIBFLAC_CFLAGS="-I$libFLAC_prefix/include" + elif test "$prefix" != "xNONE"; then + LIBFLAC_CFLAGS="-I$libdir" + fi + + AC_MSG_CHECKING(for libFLAC) + no_libFLAC="" + + + if test "x$enable_libFLACtest" = "xyes" ; then + ac_save_CFLAGS="$CFLAGS" + ac_save_CXXFLAGS="$CXXFLAGS" + ac_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $LIBFLAC_CFLAGS" + CXXFLAGS="$CXXFLAGS $LIBFLAC_CFLAGS" + LIBS="$LIBS $LIBFLAC_LIBS" +dnl +dnl Now check if the installed libFLAC is sufficiently new. +dnl + rm -f conf.libFLACtest + AC_TRY_RUN([ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <FLAC/format.h> + +int main () +{ + system("touch conf.libFLACtest"); + return 0; +} + +],, no_libFLAC=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"]) + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + fi + + if test "x$no_libFLAC" = "x" ; then + AC_MSG_RESULT(yes) + ifelse([$1], , :, [$1]) + else + AC_MSG_RESULT(no) + if test -f conf.libFLACtest ; then + : + else + echo "*** Could not run libFLAC test program, checking why..." + CFLAGS="$CFLAGS $LIBFLAC_CFLAGS" + LIBS="$LIBS $LIBFLAC_LIBS" + AC_TRY_LINK([ +#include <stdio.h> +#include <FLAC/format.h> +], [ return 0; ], + [ echo "*** The test program compiled, but did not run. This usually means" + echo "*** that the run-time linker is not finding libFLAC or finding the wrong" + echo "*** version of libFLAC. If it is not finding libFLAC, you'll need to set your" + echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point" + echo "*** to the installed location Also, make sure you have run ldconfig if that" + echo "*** is required on your system" + echo "***" + echo "*** If you have an old version installed, it is best to remove it, although" + echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"], + [ echo "*** The test program failed to compile or link. See the file config.log for the" + echo "*** exact error that occured. This usually means libFLAC was incorrectly installed" + echo "*** or that you have moved libFLAC since it was installed. In the latter case, you" + echo "*** may want to edit the libFLAC-config script: $LIBFLAC_CONFIG" ]) + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + fi + LIBFLAC_CFLAGS="" + LIBFLAC_LIBS="" + ifelse([$2], , :, [$2]) + fi + AC_SUBST(LIBFLAC_CFLAGS) + AC_SUBST(LIBFLAC_LIBS) + rm -f conf.libFLACtest +]) diff --git a/trunk/m4/libOggFLAC.m4 b/trunk/m4/libOggFLAC.m4 new file mode 100644 index 000000000..a90c69bb8 --- /dev/null +++ b/trunk/m4/libOggFLAC.m4 @@ -0,0 +1,104 @@ +# Configure paths for libOggFLAC +# "Inspired" by ogg.m4 + +dnl AM_PATH_LIBOGGFLAC([ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) +dnl Test for libOggFLAC, and define LIBOGGFLAC_CFLAGS and LIBOGGFLAC_LIBS +dnl +AC_DEFUN([AM_PATH_LIBOGGFLAC], +[dnl +dnl Get the cflags and libraries +dnl +AC_ARG_WITH(libOggFLAC,[ --with-libOggFLAC=PFX Prefix where libOggFLAC is installed (optional)], libOggFLAC_prefix="$withval", libOggFLAC_prefix="") +AC_ARG_WITH(libOggFLAC-libraries,[ --with-libOggFLAC-libraries=DIR Directory where libOggFLAC library is installed (optional)], libOggFLAC_libraries="$withval", libOggFLAC_libraries="") +AC_ARG_WITH(libOggFLAC-includes,[ --with-libOggFLAC-includes=DIR Directory where libOggFLAC header files are installed (optional)], libOggFLAC_includes="$withval", libOggFLAC_includes="") +AC_ARG_ENABLE(libOggFLACtest, [ --disable-libOggFLACtest Do not try to compile and run a test libOggFLAC program],, enable_libOggFLACtest=yes) + + if test "x$libOggFLAC_libraries" != "x" ; then + LIBOGGFLAC_LIBS="-L$libOggFLAC_libraries" + elif test "x$libOggFLAC_prefix" != "x" ; then + LIBOGGFLAC_LIBS="-L$libOggFLAC_prefix/lib" + elif test "x$prefix" != "xNONE" ; then + LIBOGGFLAC_LIBS="-L$libdir" + fi + + LIBOGGFLAC_LIBS="$LIBOGGFLAC_LIBS -lOggFLAC -lFLAC -lm" + + if test "x$libOggFLAC_includes" != "x" ; then + LIBOGGFLAC_CFLAGS="-I$libOggFLAC_includes" + elif test "x$libOggFLAC_prefix" != "x" ; then + LIBOGGFLAC_CFLAGS="-I$libOggFLAC_prefix/include" + elif test "x$prefix" != "xNONE"; then + LIBOGGFLAC_CFLAGS="-I$prefix/include" + fi + + AC_MSG_CHECKING(for libOggFLAC) + no_libOggFLAC="" + + + if test "x$enable_libOggFLACtest" = "xyes" ; then + ac_save_CFLAGS="$CFLAGS" + ac_save_CXXFLAGS="$CXXFLAGS" + ac_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $LIBOGGFLAC_CFLAGS" + CXXFLAGS="$CXXFLAGS $LIBOGGFLAC_CFLAGS" + LIBS="$LIBS $LIBOGGFLAC_LIBS" +dnl +dnl Now check if the installed libOggFLAC is sufficiently new. +dnl + rm -f conf.libOggFLACtest + AC_TRY_RUN([ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <OggFLAC/stream_decoder.h> + +int main () +{ + system("touch conf.libOggFLACtest"); + return 0; +} + +],, no_libOggFLAC=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"]) + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + fi + + if test "x$no_libOggFLAC" = "x" ; then + AC_MSG_RESULT(yes) + ifelse([$1], , :, [$1]) + else + AC_MSG_RESULT(no) + if test -f conf.libOggFLACtest ; then + : + else + echo "*** Could not run libOggFLAC test program, checking why..." + CFLAGS="$CFLAGS $LIBOGGFLAC_CFLAGS" + LIBS="$LIBS $LIBOGGFLAC_LIBS" + AC_TRY_LINK([ +#include <stdio.h> +#include <OggFLAC/stream_decoder.h> +], [ return 0; ], + [ echo "*** The test program compiled, but did not run. This usually means" + echo "*** that the run-time linker is not finding libOggFLAC or finding the wrong" + echo "*** version of libOggFLAC. If it is not finding libOggFLAC, you'll need to set your" + echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point" + echo "*** to the installed location Also, make sure you have run ldconfig if that" + echo "*** is required on your system" + echo "***" + echo "*** If you have an old version installed, it is best to remove it, although" + echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"], + [ echo "*** The test program failed to compile or link. See the file config.log for the" + echo "*** exact error that occured. This usually means libOggFLAC was incorrectly installed" + echo "*** or that you have moved libOggFLAC since it was installed. In the latter case, you" + echo "*** may want to edit the libOggFLAC-config script: $LIBOGGFLAC_CONFIG" ]) + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + fi + LIBOGGFLAC_CFLAGS="" + LIBOGGFLAC_LIBS="" + ifelse([$2], , :, [$2]) + fi + AC_SUBST(LIBOGGFLAC_CFLAGS) + AC_SUBST(LIBOGGFLAC_LIBS) + rm -f conf.libOggFLACtest +]) diff --git a/trunk/m4/libmikmod.m4 b/trunk/m4/libmikmod.m4 new file mode 100644 index 000000000..18feebec0 --- /dev/null +++ b/trunk/m4/libmikmod.m4 @@ -0,0 +1,207 @@ +# Configure paths for libmikmod +# +# Derived from glib.m4 (Owen Taylor 97-11-3) +# Improved by Chris Butler +# + +dnl AM_PATH_LIBMIKMOD([MINIMUM-VERSION, [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND ]]]) +dnl Test for libmikmod, and define LIBMIKMOD_CFLAGS, LIBMIKMOD_LIBS and +dnl LIBMIKMOD_LDADD +dnl +AC_DEFUN([AM_PATH_LIBMIKMOD], +[dnl +dnl Get the cflags and libraries from the libmikmod-config script +dnl +AC_ARG_WITH(libmikmod-prefix,[ --with-libmikmod-prefix=PFX Prefix where libmikmod is installed (optional)], + libmikmod_config_prefix="$withval", libmikmod_config_prefix="") +AC_ARG_WITH(libmikmod-exec-prefix,[ --with-libmikmod-exec-prefix=PFX Exec prefix where libmikmod is installed (optional)], + libmikmod_config_exec_prefix="$withval", libmikmod_config_exec_prefix="") +AC_ARG_ENABLE(libmikmodtest, [ --disable-libmikmodtest Do not try to compile and run a test libmikmod program], + , enable_libmikmodtest=yes) + + if test x$libmikmod_config_exec_prefix != x ; then + libmikmod_config_args="$libmikmod_config_args --exec-prefix=$libmikmod_config_exec_prefix" + if test x${LIBMIKMOD_CONFIG+set} != xset ; then + LIBMIKMOD_CONFIG=$libmikmod_config_exec_prefix/bin/libmikmod-config + fi + fi + if test x$libmikmod_config_prefix != x ; then + libmikmod_config_args="$libmikmod_config_args --prefix=$libmikmod_config_prefix" + if test x${LIBMIKMOD_CONFIG+set} != xset ; then + LIBMIKMOD_CONFIG=$libmikmod_config_prefix/bin/libmikmod-config + fi + fi + + AC_PATH_PROG(LIBMIKMOD_CONFIG, libmikmod-config, no) + min_libmikmod_version=ifelse([$1], ,3.1.5,$1) + AC_MSG_CHECKING(for libmikmod - version >= $min_libmikmod_version) + no_libmikmod="" + if test "$LIBMIKMOD_CONFIG" = "no" ; then + no_libmikmod=yes + else + LIBMIKMOD_CFLAGS=`$LIBMIKMOD_CONFIG $libmikmod_config_args --cflags` + LIBMIKMOD_LIBS=`$LIBMIKMOD_CONFIG $libmikmod_config_args --libs` + LIBMIKMOD_LDADD=`$LIBMIKMOD_CONFIG $libmikmod_config_args --ldadd` + libmikmod_config_major_version=`$LIBMIKMOD_CONFIG $libmikmod_config_args --version | \ + sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\).*/\1/'` + libmikmod_config_minor_version=`$LIBMIKMOD_CONFIG $libmikmod_config_args --version | \ + sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\).*/\2/'` + libmikmod_config_micro_version=`$LIBMIKMOD_CONFIG $libmikmod_config_args --version | \ + sed 's/\([[0-9]]*\).\([[0-9]]*\).\([[0-9]]*\).*/\3/'` + if test "x$enable_libmikmodtest" = "xyes" ; then + ac_save_CFLAGS="$CFLAGS" + ac_save_LIBS="$LIBS" + AC_LANG_SAVE + AC_LANG_C + CFLAGS="$CFLAGS $LIBMIKMOD_CFLAGS $LIBMIKMOD_LDADD" + LIBS="$LIBMIKMOD_LIBS $LIBS" +dnl +dnl Now check if the installed libmikmod is sufficiently new. (Also sanity +dnl checks the results of libmikmod-config to some extent +dnl + rm -f conf.mikmodtest + AC_TRY_RUN([ +#include <mikmod.h> +#include <stdio.h> +#include <stdlib.h> + +char* my_strdup (char *str) +{ + char *new_str; + + if (str) { + new_str = malloc ((strlen (str) + 1) * sizeof(char)); + strcpy (new_str, str); + } else + new_str = NULL; + + return new_str; +} + +int main() +{ + int major,minor,micro; + int libmikmod_major_version,libmikmod_minor_version,libmikmod_micro_version; + char *tmp_version; + + system("touch conf.mikmodtest"); + + /* HP/UX 9 (%@#!) writes to sscanf strings */ + tmp_version = my_strdup("$min_libmikmod_version"); + if (sscanf(tmp_version, "%d.%d.%d", &major, &minor, µ) != 3) { + printf("%s, bad version string\n", "$min_libmikmod_version"); + exit(1); + } + + libmikmod_major_version=(MikMod_GetVersion() >> 16) & 255; + libmikmod_minor_version=(MikMod_GetVersion() >> 8) & 255; + libmikmod_micro_version=(MikMod_GetVersion() ) & 255; + + if ((libmikmod_major_version != $libmikmod_config_major_version) || + (libmikmod_minor_version != $libmikmod_config_minor_version) || + (libmikmod_micro_version != $libmikmod_config_micro_version)) + { + printf("\n*** 'libmikmod-config --version' returned %d.%d.%d, but libmikmod (%d.%d.%d)\n", + $libmikmod_config_major_version, $libmikmod_config_minor_version, $libmikmod_config_micro_version, + libmikmod_major_version, libmikmod_minor_version, libmikmod_micro_version); + printf ("*** was found! If libmikmod-config was correct, then it is best\n"); + printf ("*** to remove the old version of libmikmod. You may also be able to fix the error\n"); + printf("*** by modifying your LD_LIBRARY_PATH enviroment variable, or by editing\n"); + printf("*** /etc/ld.so.conf. Make sure you have run ldconfig if that is\n"); + printf("*** required on your system.\n"); + printf("*** If libmikmod-config was wrong, set the environment variable LIBMIKMOD_CONFIG\n"); + printf("*** to point to the correct copy of libmikmod-config, and remove the file config.cache\n"); + printf("*** before re-running configure\n"); + } + else if ((libmikmod_major_version != LIBMIKMOD_VERSION_MAJOR) || + (libmikmod_minor_version != LIBMIKMOD_VERSION_MINOR) || + (libmikmod_micro_version != LIBMIKMOD_REVISION)) + { + printf("*** libmikmod header files (version %d.%d.%d) do not match\n", + LIBMIKMOD_VERSION_MAJOR, LIBMIKMOD_VERSION_MINOR, LIBMIKMOD_REVISION); + printf("*** library (version %d.%d.%d)\n", + libmikmod_major_version, libmikmod_minor_version, libmikmod_micro_version); + } + else + { + if ((libmikmod_major_version > major) || + ((libmikmod_major_version == major) && (libmikmod_minor_version > minor)) || + ((libmikmod_major_version == major) && (libmikmod_minor_version == minor) && (libmikmod_micro_version >= micro))) + { + return 0; + } + else + { + printf("\n*** An old version of libmikmod (%d.%d.%d) was found.\n", + libmikmod_major_version, libmikmod_minor_version, libmikmod_micro_version); + printf("*** You need a version of libmikmod newer than %d.%d.%d.\n", + major, minor, micro); + printf("***\n"); + printf("*** If you have already installed a sufficiently new version, this error\n"); + printf("*** probably means that the wrong copy of the libmikmod-config shell script is\n"); + printf("*** being found. The easiest way to fix this is to remove the old version\n"); + printf("*** of libmikmod, but you can also set the LIBMIKMOD_CONFIG environment to point to the\n"); + printf("*** correct copy of libmikmod-config. (In this case, you will have to\n"); + printf("*** modify your LD_LIBRARY_PATH enviroment variable, or edit /etc/ld.so.conf\n"); + printf("*** so that the correct libraries are found at run-time))\n"); + } + } + return 1; +} +],, no_libmikmod=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"]) + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + AC_LANG_RESTORE + fi + fi + if test "x$no_libmikmod" = x ; then + AC_MSG_RESULT([yes, `$LIBMIKMOD_CONFIG --version`]) + ifelse([$2], , :, [$2]) + else + AC_MSG_RESULT(no) + if test "$LIBMIKMOD_CONFIG" = "no" ; then + echo "*** The libmikmod-config script installed by libmikmod could not be found" + echo "*** If libmikmod was installed in PREFIX, make sure PREFIX/bin is in" + echo "*** your path, or set the LIBMIKMOD_CONFIG environment variable to the" + echo "*** full path to libmikmod-config." + else + if test -f conf.mikmodtest ; then + : + else + echo "*** Could not run libmikmod test program, checking why..." + CFLAGS="$CFLAGS $LIBMIKMOD_CFLAGS" + LIBS="$LIBS $LIBMIKMOD_LIBS" + AC_LANG_SAVE + AC_LANG_C + AC_TRY_LINK([ +#include <mikmod.h> +#include <stdio.h> +], [ return (MikMod_GetVersion()!=0); ], + [ echo "*** The test program compiled, but did not run. This usually means" + echo "*** that the run-time linker is not finding libmikmod or finding the wrong" + echo "*** version of libmikmod. If it is not finding libmikmod, you'll need to set your" + echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point" + echo "*** to the installed location. Also, make sure you have run ldconfig if that" + echo "*** is required on your system." + echo "***" + echo "*** If you have an old version installed, it is best to remove it, although" + echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"], + [ echo "*** The test program failed to compile or link. See the file config.log for the" + echo "*** exact error that occured. This usually means libmikmod was incorrectly installed" + echo "*** or that you have moved libmikmod since it was installed. In the latter case, you" + echo "*** may want to edit the libmikmod-config script: $LIBMIKMOD_CONFIG" ]) + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + AC_LANG_RESTORE + fi + fi + LIBMIKMOD_CFLAGS="" + LIBMIKMOD_LIBS="" + LIBMIKMOD_LDADD="" + ifelse([$3], , :, [$3]) + fi + AC_SUBST(LIBMIKMOD_CFLAGS) + AC_SUBST(LIBMIKMOD_LIBS) + AC_SUBST(LIBMIKMOD_LDADD) + rm -f conf.mikmodtest +]) diff --git a/trunk/m4/ogg.m4 b/trunk/m4/ogg.m4 new file mode 100644 index 000000000..0e1f1abf5 --- /dev/null +++ b/trunk/m4/ogg.m4 @@ -0,0 +1,102 @@ +# Configure paths for libogg +# Jack Moffitt <jack@icecast.org> 10-21-2000 +# Shamelessly stolen from Owen Taylor and Manish Singh + +dnl XIPH_PATH_OGG([ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) +dnl Test for libogg, and define OGG_CFLAGS and OGG_LIBS +dnl +AC_DEFUN([XIPH_PATH_OGG], +[dnl +dnl Get the cflags and libraries +dnl +AC_ARG_WITH(ogg,[ --with-ogg=PFX Prefix where libogg is installed (optional)], ogg_prefix="$withval", ogg_prefix="") +AC_ARG_WITH(ogg-libraries,[ --with-ogg-libraries=DIR Directory where libogg library is installed (optional)], ogg_libraries="$withval", ogg_libraries="") +AC_ARG_WITH(ogg-includes,[ --with-ogg-includes=DIR Directory where libogg header files are installed (optional)], ogg_includes="$withval", ogg_includes="") +AC_ARG_ENABLE(oggtest, [ --disable-oggtest Do not try to compile and run a test Ogg program],, enable_oggtest=yes) + + if test "x$ogg_libraries" != "x" ; then + OGG_LIBS="-L$ogg_libraries" + elif test "x$ogg_prefix" != "x" ; then + OGG_LIBS="-L$ogg_prefix/lib" + elif test "x$prefix" != "xNONE" ; then + OGG_LIBS="-L$prefix/lib" + fi + + OGG_LIBS="$OGG_LIBS -logg" + + if test "x$ogg_includes" != "x" ; then + OGG_CFLAGS="-I$ogg_includes" + elif test "x$ogg_prefix" != "x" ; then + OGG_CFLAGS="-I$ogg_prefix/include" + elif test "x$prefix" != "xNONE"; then + OGG_CFLAGS="-I$prefix/include" + fi + + AC_MSG_CHECKING(for Ogg) + no_ogg="" + + + if test "x$enable_oggtest" = "xyes" ; then + ac_save_CFLAGS="$CFLAGS" + ac_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $OGG_CFLAGS" + LIBS="$LIBS $OGG_LIBS" +dnl +dnl Now check if the installed Ogg is sufficiently new. +dnl + rm -f conf.oggtest + AC_TRY_RUN([ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ogg/ogg.h> + +int main () +{ + system("touch conf.oggtest"); + return 0; +} + +],, no_ogg=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"]) + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + fi + + if test "x$no_ogg" = "x" ; then + AC_MSG_RESULT(yes) + ifelse([$1], , :, [$1]) + else + AC_MSG_RESULT(no) + if test -f conf.oggtest ; then + : + else + echo "*** Could not run Ogg test program, checking why..." + CFLAGS="$CFLAGS $OGG_CFLAGS" + LIBS="$LIBS $OGG_LIBS" + AC_TRY_LINK([ +#include <stdio.h> +#include <ogg/ogg.h> +], [ return 0; ], + [ echo "*** The test program compiled, but did not run. This usually means" + echo "*** that the run-time linker is not finding Ogg or finding the wrong" + echo "*** version of Ogg. If it is not finding Ogg, you'll need to set your" + echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point" + echo "*** to the installed location Also, make sure you have run ldconfig if that" + echo "*** is required on your system" + echo "***" + echo "*** If you have an old version installed, it is best to remove it, although" + echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"], + [ echo "*** The test program failed to compile or link. See the file config.log for the" + echo "*** exact error that occured. This usually means Ogg was incorrectly installed" + echo "*** or that you have moved Ogg since it was installed." ]) + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + fi + OGG_CFLAGS="" + OGG_LIBS="" + ifelse([$2], , :, [$2]) + fi + AC_SUBST(OGG_CFLAGS) + AC_SUBST(OGG_LIBS) + rm -f conf.oggtest +]) diff --git a/trunk/m4/pkg.m4 b/trunk/m4/pkg.m4 new file mode 100644 index 000000000..c29b6c057 --- /dev/null +++ b/trunk/m4/pkg.m4 @@ -0,0 +1,157 @@ +# pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*- +# +# Copyright © 2004 Scott James Remnant <scott@netsplit.com>. +# +# 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. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# PKG_PROG_PKG_CONFIG([MIN-VERSION]) +# ---------------------------------- +AC_DEFUN([PKG_PROG_PKG_CONFIG], +[m4_pattern_forbid([^_?PKG_[A-Z_]+$]) +m4_pattern_allow([^PKG_CONFIG(_PATH)?$]) +AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])dnl +if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then + AC_PATH_TOOL([PKG_CONFIG], [pkg-config]) +fi +if test -n "$PKG_CONFIG"; then + _pkg_min_version=m4_default([$1], [0.9.0]) + AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version]) + if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + PKG_CONFIG="" + fi + +fi[]dnl +])# PKG_PROG_PKG_CONFIG + +# PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) +# +# Check to see whether a particular set of modules exists. Similar +# to PKG_CHECK_MODULES(), but does not set variables or print errors. +# +# +# Similar to PKG_CHECK_MODULES, make sure that the first instance of +# this or PKG_CHECK_MODULES is called, or make sure to call +# PKG_CHECK_EXISTS manually +# -------------------------------------------------------------- +AC_DEFUN([PKG_CHECK_EXISTS], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +if test -n "$PKG_CONFIG" && \ + AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then + m4_ifval([$2], [$2], [:]) +m4_ifvaln([$3], [else + $3])dnl +fi]) + + +# _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES]) +# --------------------------------------------- +m4_define([_PKG_CONFIG], +[if test -n "$PKG_CONFIG"; then + if test -n "$$1"; then + pkg_cv_[]$1="$$1" + else + PKG_CHECK_EXISTS([$3], + [pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`], + [pkg_failed=yes]) + fi +else + pkg_failed=untried +fi[]dnl +])# _PKG_CONFIG + +# _PKG_SHORT_ERRORS_SUPPORTED +# ----------------------------- +AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG]) +if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then + _pkg_short_errors_supported=yes +else + _pkg_short_errors_supported=no +fi[]dnl +])# _PKG_SHORT_ERRORS_SUPPORTED + + +# PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND], +# [ACTION-IF-NOT-FOUND]) +# +# +# Note that if there is a possibility the first call to +# PKG_CHECK_MODULES might not happen, you should be sure to include an +# explicit call to PKG_PROG_PKG_CONFIG in your configure.ac +# +# +# -------------------------------------------------------------- +AC_DEFUN([PKG_CHECK_MODULES], +[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl +AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl +AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl + +pkg_failed=no +AC_MSG_CHECKING([for $1]) + +_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2]) +_PKG_CONFIG([$1][_LIBS], [libs], [$2]) + +m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS +and $1[]_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details.]) + +if test $pkg_failed = yes; then + _PKG_SHORT_ERRORS_SUPPORTED + if test $_pkg_short_errors_supported = yes; then + $1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --errors-to-stdout --print-errors "$2"` + else + $1[]_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "$2"` + fi + # Put the nasty error message in config.log where it belongs + echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD + + ifelse([$4], , [AC_MSG_ERROR(dnl +[Package requirements ($2) were not met: + +$$1_PKG_ERRORS + +Consider adjusting the PKG_CONFIG_PATH environment variable if you +installed software in a non-standard prefix. + +_PKG_TEXT +])], + [AC_MSG_RESULT([no]) + $4]) +elif test $pkg_failed = untried; then + ifelse([$4], , [AC_MSG_FAILURE(dnl +[The pkg-config script could not be found or is too old. Make sure it +is in your PATH or set the PKG_CONFIG environment variable to the full +path to pkg-config. + +_PKG_TEXT + +To get pkg-config, see <http://www.freedesktop.org/software/pkgconfig>.])], + [$4]) +else + $1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS + $1[]_LIBS=$pkg_cv_[]$1[]_LIBS + AC_MSG_RESULT([yes]) + ifelse([$3], , :, [$3]) +fi[]dnl +])# PKG_CHECK_MODULES diff --git a/trunk/m4/shout.m4 b/trunk/m4/shout.m4 new file mode 100644 index 000000000..4eba5c09b --- /dev/null +++ b/trunk/m4/shout.m4 @@ -0,0 +1,90 @@ +dnl XIPH_PATH_SHOUT +dnl Jack Moffitt <jack@icecast.org> 08-06-2001 +dnl Rewritten for libshout 2 +dnl Brendan Cully <brendan@xiph.org> 20030612 +dnl +dnl $Id: shout.m4 7180 2004-07-20 02:50:54Z brendan $ + +# XIPH_PATH_SHOUT([ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) +# Test for libshout, and define SHOUT_CPPFLAGS SHOUT_CFLAGS SHOUT_LIBS, and +# SHOUT_THREADSAFE +AC_DEFUN([XIPH_PATH_SHOUT], +[dnl +xt_have_shout="no" +SHOUT_THREADSAFE="no" +SHOUT_CPPFLAGS="" +SHOUT_CFLAGS="" +SHOUT_LIBS="" + +# NB: PKG_CHECK_MODULES exits if pkg-config is unavailable on the target +# system, so we can't use it. + +# seed pkg-config with the default libshout location +PKG_CONFIG_PATH=${PKG_CONFIG_PATH:-/usr/local/lib/pkgconfig} +export PKG_CONFIG_PATH + +# Step 1: Use pkg-config if available +AC_PATH_PROG([PKGCONFIG], [pkg-config], [no]) +if test "$PKGCONFIG" != "no" && `$PKGCONFIG --exists shout` +then + SHOUT_CFLAGS=`$PKGCONFIG --variable=cflags_only shout` + SHOUT_CPPFLAGS=`$PKGCONFIG --variable=cppflags shout` + SHOUT_LIBS=`$PKGCONFIG --libs shout` + xt_have_shout="maybe" +else + if test "$PKGCONFIG" != "no" + then + AC_MSG_NOTICE([$PKGCONFIG couldn't find libshout. Try adjusting PKG_CONFIG_PATH.]) + fi + # pkg-config unavailable, try shout-config + AC_PATH_PROG([SHOUTCONFIG], [shout-config], [no]) + if test "$SHOUTCONFIG" != "no" && test `$SHOUTCONFIG --package` = "libshout" + then + SHOUT_CPPFLAGS=`$SHOUTCONFIG --cppflags` + SHOUT_CFLAGS=`$SHOUTCONFIG --cflags-only` + SHOUT_LIBS=`$SHOUTCONFIG --libs` + xt_have_shout="maybe" + fi +fi + +# Now try actually using libshout +if test "$xt_have_shout" != "no" +then + ac_save_CPPFLAGS="$CPPFLAGS" + ac_save_CFLAGS="$CFLAGS" + ac_save_LIBS="$LIBS" + + # freebsd 6.1 + shout 2.2 port seems to leave pthread out + case "$host_os" in + freebsd*) + case "$SHOUT_CFLAGS$SHOUT_CPPFLAGS" in + *-D_THREAD_SAFE*) + SHOUT_LIBS="$SHOUT_LIBS -lpthread" + ;; + esac + ;; + esac + CPPFLAGS="$CPPFLAGS $SHOUT_CPPFLAGS" + CFLAGS="$CFLAGS $SHOUT_CFLAGS" + LIBS="$SHOUT_LIBS $LIBS" + AC_CHECK_HEADERS([shout/shout.h], [ + AC_CHECK_FUNC([shout_new], [ + ifelse([$1], , :, [$1]) + xt_have_shout="yes" + ]) + AC_EGREP_CPP([yes], [#include <shout/shout.h> +#if SHOUT_THREADSAFE +yes +#endif +], [SHOUT_THREADSAFE="yes"]) + ]) + CPPFLAGS="$ac_save_CPPFLAGS" + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" +fi + +if test "$xt_have_shout" != "yes" +then + ifelse([$2], , :, [$2]) +fi +])dnl XIPH_PATH_SHOUT diff --git a/trunk/m4/vorbis.m4 b/trunk/m4/vorbis.m4 new file mode 100644 index 000000000..300cc6c7d --- /dev/null +++ b/trunk/m4/vorbis.m4 @@ -0,0 +1,122 @@ +# Configure paths for libvorbis +# Jack Moffitt <jack@icecast.org> 10-21-2000 +# Shamelessly stolen from Owen Taylor and Manish Singh +# thomasvs added check for vorbis_bitrate_addblock which is new in rc3 + +dnl XIPH_PATH_VORBIS([ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]]) +dnl Test for libvorbis, and define VORBIS_CFLAGS and VORBIS_LIBS +dnl +AC_DEFUN([XIPH_PATH_VORBIS], +[dnl +dnl Get the cflags and libraries +dnl +AC_ARG_WITH(vorbis,[ --with-vorbis=PFX Prefix where libvorbis is installed (optional)], vorbis_prefix="$withval", vorbis_prefix="") +AC_ARG_WITH(vorbis-libraries,[ --with-vorbis-libraries=DIR Directory where libvorbis library is installed (optional)], vorbis_libraries="$withval", vorbis_libraries="") +AC_ARG_WITH(vorbis-includes,[ --with-vorbis-includes=DIR Directory where libvorbis header files are installed (optional)], vorbis_includes="$withval", vorbis_includes="") +AC_ARG_ENABLE(vorbistest, [ --disable-vorbistest Do not try to compile and run a test Vorbis program],, enable_vorbistest=yes) + + if test "x$vorbis_libraries" != "x" ; then + VORBIS_LIBS="-L$vorbis_libraries" + elif test "x$vorbis_prefix" != "x" ; then + VORBIS_LIBS="-L$vorbis_prefix/lib" + elif test "x$prefix" != "xNONE"; then + VORBIS_LIBS="-L$prefix/lib" + fi + + VORBIS_LIBS="$VORBIS_LIBS -lvorbis -lm" + VORBISFILE_LIBS="-lvorbisfile" + VORBISENC_LIBS="-lvorbisenc" + + if test "x$vorbis_includes" != "x" ; then + VORBIS_CFLAGS="-I$vorbis_includes" + elif test "x$vorbis_prefix" != "x" ; then + VORBIS_CFLAGS="-I$vorbis_prefix/include" + elif test "x$prefix" != "xNONE"; then + VORBIS_CFLAGS="-I$prefix/include" + fi + + + AC_MSG_CHECKING(for Vorbis) + no_vorbis="" + + + if test "x$enable_vorbistest" = "xyes" ; then + ac_save_CFLAGS="$CFLAGS" + ac_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $VORBIS_CFLAGS $OGG_CFLAGS" + LIBS="$LIBS $VORBIS_LIBS $VORBISENC_LIBS $OGG_LIBS" +dnl +dnl Now check if the installed Vorbis is sufficiently new. +dnl + rm -f conf.vorbistest + AC_TRY_RUN([ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <vorbis/codec.h> +#include <vorbis/vorbisenc.h> + +int main () +{ + vorbis_block vb; + vorbis_dsp_state vd; + vorbis_info vi; + + vorbis_info_init (&vi); + vorbis_encode_init (&vi, 2, 44100, -1, 128000, -1); + vorbis_analysis_init (&vd, &vi); + vorbis_block_init (&vd, &vb); + /* this function was added in 1.0rc3, so this is what we're testing for */ + vorbis_bitrate_addblock (&vb); + + system("touch conf.vorbistest"); + return 0; +} + +],, no_vorbis=yes,[echo $ac_n "cross compiling; assumed OK... $ac_c"]) + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + fi + + if test "x$no_vorbis" = "x" ; then + AC_MSG_RESULT(yes) + ifelse([$1], , :, [$1]) + else + AC_MSG_RESULT(no) + if test -f conf.vorbistest ; then + : + else + echo "*** Could not run Vorbis test program, checking why..." + CFLAGS="$CFLAGS $VORBIS_CFLAGS" + LIBS="$LIBS $VORBIS_LIBS $OGG_LIBS" + AC_TRY_LINK([ +#include <stdio.h> +#include <vorbis/codec.h> +], [ return 0; ], + [ echo "*** The test program compiled, but did not run. This usually means" + echo "*** that the run-time linker is not finding Vorbis or finding the wrong" + echo "*** version of Vorbis. If it is not finding Vorbis, you'll need to set your" + echo "*** LD_LIBRARY_PATH environment variable, or edit /etc/ld.so.conf to point" + echo "*** to the installed location Also, make sure you have run ldconfig if that" + echo "*** is required on your system" + echo "***" + echo "*** If you have an old version installed, it is best to remove it, although" + echo "*** you may also be able to get things to work by modifying LD_LIBRARY_PATH"], + [ echo "*** The test program failed to compile or link. See the file config.log for the" + echo "*** exact error that occured. This usually means Vorbis was incorrectly installed" + echo "*** or that you have moved Vorbis since it was installed." ]) + CFLAGS="$ac_save_CFLAGS" + LIBS="$ac_save_LIBS" + fi + VORBIS_CFLAGS="" + VORBIS_LIBS="" + VORBISFILE_LIBS="" + VORBISENC_LIBS="" + ifelse([$2], , :, [$2]) + fi + AC_SUBST(VORBIS_CFLAGS) + AC_SUBST(VORBIS_LIBS) + AC_SUBST(VORBISFILE_LIBS) + AC_SUBST(VORBISENC_LIBS) + rm -f conf.vorbistest +]) diff --git a/trunk/scripts/makedist.sh b/trunk/scripts/makedist.sh new file mode 100755 index 000000000..d342cea3a --- /dev/null +++ b/trunk/scripts/makedist.sh @@ -0,0 +1,25 @@ +#!/bin/sh +PWD=`pwd` + +## If we're not in the scripts directory +## assume the base directory. +if test "`basename $PWD`" == "scripts"; then + cd ../ +else + MYOLDPWD=`pwd` + cd `dirname $0`/../ +fi + +if test -e Makefile +then + make distclean +fi +./autogen.sh +make +make dist + +if test "`basename $PWD`" == "scripts"; then + cd contrib/ +else + cd $MYOLDPWD +fi diff --git a/trunk/scripts/makerpm.sh b/trunk/scripts/makerpm.sh new file mode 100755 index 000000000..bfa2b47f0 --- /dev/null +++ b/trunk/scripts/makerpm.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +PWD=`pwd` + +## If we're not in the scripts directory +## assume the base directory. +if test "`basename $PWD`" != "scripts"; then + MYOLDPWD=`pwd` + cd `dirname $0` +fi + +./makedist.sh + +rpmbuild -bb mpd.spec + +if test $? -eq 0; then + echo 'Your RPM should be ready now' +else + echo 'Something went wrong when building your RPM' +fi + +if test -f ../mpd-?.??.?.tar.gz; +then + rm ../mpd-?.??.?.tar.gz +fi + +if test "`basename $PWD`" != "scripts"; then + cd $MYOLDPWD +fi diff --git a/trunk/scripts/mpd-indent.sh b/trunk/scripts/mpd-indent.sh new file mode 100755 index 000000000..992cf9dc5 --- /dev/null +++ b/trunk/scripts/mpd-indent.sh @@ -0,0 +1 @@ +indent -npro -kr -i8 -ts8 -sob -l80 -ss -ncs -cdw -cd0 -c0 -cp0 $@ diff --git a/trunk/scripts/mpd.spec b/trunk/scripts/mpd.spec new file mode 100644 index 000000000..74c8ebaa8 --- /dev/null +++ b/trunk/scripts/mpd.spec @@ -0,0 +1,68 @@ +# the Music Player Daemon (MPD) +# Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) +# This project's homepage is: http://www.musicpd.org +# +# 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 + +# please send bugfixes or comments to avuton@gmail.com + +%define _prefix /usr +%define _share %_prefix/share +%define _bindir %_prefix/bin +%define _docdir %_share/doc +%define _mandir %_share/man + +Summary: Music Player Daemon (MPD) +Name: mpd +Version: 0.13.0 +Release: 0 +License: GPL +Group: Productivity/Multimedia/Sound/Players +Source: ../%name-%version.tar.gz +#Patch: +BuildRoot: %{_tmppath}/%{name}-build + +%description +Music Player Daemon is simply a daemon for playing music +which can be easily controlled over TCP by different +clients. It has very low CPU usage and can be controlled +from different machines at the same time. + +%prep +%setup -q +#%patch -q1 -b .buildroot + +%build +CFLAGS="%{optflags}" \ +CXXFLAGS="%{optflags}" \ +LDFLAGS="%{optflags}" \ +./configure --prefix=%{_prefix} +make + +%install +make DESTDIR=$RPM_BUILD_ROOT install + +%clean +[ ${RPM_BUILD_ROOT} != "/" ] && rm -rf ${RPM_BUILD_ROOT} + +%files +%defattr(-,root,root) + +%{_bindir}/%{name} +%doc %{_docdir} +%doc %attr(0444,root,root) %{_mandir} + +%changelog +* Fri Jul 21 2006 Avuton Olrich <avuton@gmail.com> +- Initial revision diff --git a/trunk/src/Makefile.am b/trunk/src/Makefile.am new file mode 100644 index 000000000..b9c6fa0c7 --- /dev/null +++ b/trunk/src/Makefile.am @@ -0,0 +1,148 @@ +bin_PROGRAMS = mpd +SUBDIRS = $(MP4FF_SUBDIR) + +mpd_audioOutputs = \ + audioOutputs/audioOutput_alsa.c \ + audioOutputs/audioOutput_ao.c \ + audioOutputs/audioOutput_oss.c \ + audioOutputs/audioOutput_osx.c \ + audioOutputs/audioOutput_pulse.c \ + audioOutputs/audioOutput_mvp.c \ + audioOutputs/audioOutput_shout.c \ + audioOutputs/audioOutput_jack.c + +mpd_inputPlugins = \ + inputPlugins/_flac_common.c \ + inputPlugins/_ogg_common.c \ + inputPlugins/oggflac_plugin.c \ + inputPlugins/oggvorbis_plugin.c \ + inputPlugins/aac_plugin.c \ + inputPlugins/audiofile_plugin.c \ + inputPlugins/flac_plugin.c \ + inputPlugins/mod_plugin.c \ + inputPlugins/mp3_plugin.c \ + inputPlugins/mp4_plugin.c \ + inputPlugins/mpc_plugin.c + + +mpd_headers = \ + ack.h \ + audio.h \ + audioOutput.h \ + buffer2array.h \ + charConv.h \ + command.h \ + conf.h \ + dbUtils.h \ + decode.h \ + directory.h \ + gcc.h \ + inputPlugin.h \ + inputPlugins/_flac_common.h \ + inputPlugins/_ogg_common.h \ + inputStream.h \ + inputStream_file.h \ + inputStream_http.h \ + interface.h \ + list.h \ + listen.h \ + log.h \ + ls.h \ + metadataChunk.h \ + mpd_types.h \ + myfprintf.h \ + normalize.h \ + compress.h \ + outputBuffer.h \ + path.h \ + pcm_utils.h \ + permission.h \ + player.h \ + playerData.h \ + playlist.h \ + replayGain.h \ + signal_check.h \ + sig_handlers.h \ + sllist.h \ + song.h \ + state_file.h \ + stats.h \ + tag.h \ + tagTracker.h \ + tree.h \ + utf8.h \ + utils.h \ + volume.h \ + ioops.h \ + zeroconf.h \ + locate.h \ + storedPlaylist.h + + +mpd_SOURCES = \ + $(mpd_headers) \ + $(mpd_audioOutputs) \ + $(mpd_inputPlugins) \ + audio.c \ + audioOutput.c \ + buffer2array.c \ + charConv.c \ + command.c \ + conf.c \ + dbUtils.c \ + decode.c \ + directory.c \ + inputPlugin.c \ + inputStream.c \ + inputStream_file.c \ + inputStream_http.c \ + interface.c \ + list.c \ + listen.c \ + log.c \ + ls.c \ + main.c \ + metadataChunk.c \ + myfprintf.c \ + normalize.c \ + compress.c \ + outputBuffer.c \ + path.c \ + pcm_utils.c \ + permission.c \ + player.c \ + playerData.c \ + playlist.c \ + replayGain.c \ + sig_handlers.c \ + signal_check.c \ + sllist.c \ + song.c \ + state_file.c \ + stats.c \ + tag.c \ + tagTracker.c \ + tree.c \ + utils.c \ + volume.c \ + utf8.c \ + zeroconf.c \ + locate.c \ + storedPlaylist.c + + +mpd_CFLAGS = $(MPD_CFLAGS) +mpd_LDADD = $(MPD_LIBS) $(MP4FF_LIB) + +DIST_SUBDIRS = mp4ff + +# sparse is a semantic parser +# URL: git://www.kernel.org/pub/scm/devel/sparse/sparse.git +SPARSE = sparse +SPARSE_FLAGS = +sparse-check: + for i in $(mpd_SOURCES); \ + do \ + $(SPARSE) -I. $(mpd_CFLAGS) $(SPARSE_FLAGS) $(srcdir)/$$i || exit; \ + done + diff --git a/trunk/src/ack.h b/trunk/src/ack.h new file mode 100644 index 000000000..1b2950e20 --- /dev/null +++ b/trunk/src/ack.h @@ -0,0 +1,37 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef ACK_H +#define ACK_H + +/* Common Errors */ +#define ACK_ERROR_NOT_LIST 1 +#define ACK_ERROR_ARG 2 +#define ACK_ERROR_PASSWORD 3 +#define ACK_ERROR_PERMISSION 4 +#define ACK_ERROR_UNKNOWN 5 + +#define ACK_ERROR_NO_EXIST 50 +#define ACK_ERROR_PLAYLIST_MAX 51 +#define ACK_ERROR_SYSTEM 52 +#define ACK_ERROR_PLAYLIST_LOAD 53 +#define ACK_ERROR_UPDATE_ALREADY 54 +#define ACK_ERROR_PLAYER_SYNC 55 +#define ACK_ERROR_EXIST 56 + +#endif diff --git a/trunk/src/audio.c b/trunk/src/audio.c new file mode 100644 index 000000000..912e46ffa --- /dev/null +++ b/trunk/src/audio.c @@ -0,0 +1,518 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "audio.h" +#include "audioOutput.h" +#include "conf.h" +#include "log.h" +#include "sig_handlers.h" +#include "command.h" +#include "playerData.h" +#include "utils.h" +#include "state_file.h" + +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <signal.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <errno.h> +#include <unistd.h> + +#define AUDIO_DEVICE_STATE "audio_device_state:" +#define AUDIO_DEVICE_STATE_LEN 19 /* strlen(AUDIO_DEVICE_STATE) */ +#define AUDIO_BUFFER_SIZE 2*MAXPATHLEN + +static AudioFormat audio_format; + +static AudioFormat *audio_configFormat; + +static AudioOutput *audioOutputArray; +static mpd_uint8 audioOutputArraySize; + +#define DEVICE_OFF 0x00 +#define DEVICE_ENABLE 0x01 /* currently off, but to be turned on */ +#define DEVICE_ON 0x03 +#define DEVICE_DISABLE 0x04 /* currently on, but to be turned off */ + +/* the audioEnabledArray should be stuck into shared memory, and then disable + and enable in playAudio() routine */ +static mpd_uint8 *audioDeviceStates; + +static mpd_uint8 audioOpened; + +static mpd_sint32 audioBufferSize; +static char *audioBuffer; +static mpd_sint32 audioBufferPos; + +size_t audio_device_count(void) +{ + size_t nr = 0; + ConfigParam *param = NULL; + + while ((param = getNextConfigParam(CONF_AUDIO_OUTPUT, param))) + nr++; + if (!nr) + nr = 1; /* we'll always have at least one device */ + return nr; +} + +void copyAudioFormat(AudioFormat * dest, AudioFormat * src) +{ + if (!src) + return; + + memcpy(dest, src, sizeof(AudioFormat)); +} + +int cmpAudioFormat(AudioFormat * f1, AudioFormat * f2) +{ + if (f1 && f2 && (f1->sampleRate == f2->sampleRate) && + (f1->bits == f2->bits) && (f1->channels == f2->channels)) + return 0; + return 1; +} + +void loadAudioDrivers(void) +{ + initAudioOutputPlugins(); + loadAudioOutputPlugin(&alsaPlugin); + loadAudioOutputPlugin(&aoPlugin); + loadAudioOutputPlugin(&ossPlugin); + loadAudioOutputPlugin(&osxPlugin); + loadAudioOutputPlugin(&pulsePlugin); + loadAudioOutputPlugin(&mvpPlugin); + loadAudioOutputPlugin(&shoutPlugin); + loadAudioOutputPlugin(&jackPlugin); +} + +/* make sure initPlayerData is called before this function!! */ +void initAudioDriver(void) +{ + ConfigParam *param = NULL; + int i; + + loadAudioDrivers(); + + audioOutputArraySize = audio_device_count(); + audioDeviceStates = (getPlayerData())->audioDeviceStates; + audioOutputArray = xmalloc(sizeof(AudioOutput) * audioOutputArraySize); + + for (i = 0; i < audioOutputArraySize; i++) + { + AudioOutput *output = &audioOutputArray[i]; + int j; + + param = getNextConfigParam(CONF_AUDIO_OUTPUT, param); + + /* only allow param to be NULL if there just one audioOutput */ + assert(param || (audioOutputArraySize == 1)); + + if (!initAudioOutput(output, param)) { + if (param) + { + FATAL("problems configuring output device " + "defined at line %i\n", param->line); + } + else + { + FATAL("No audio_output specified and unable to " + "detect a default audio output device\n"); + } + } + + /* require output names to be unique: */ + for (j = 0; j < i; j++) { + if (!strcmp(output->name, audioOutputArray[j].name)) { + FATAL("output devices with identical " + "names: %s\n", output->name); + } + } + audioDeviceStates[i] = DEVICE_ENABLE; + } +} + +void getOutputAudioFormat(AudioFormat * inAudioFormat, + AudioFormat * outAudioFormat) +{ + if (audio_configFormat) { + copyAudioFormat(outAudioFormat, audio_configFormat); + } else + copyAudioFormat(outAudioFormat, inAudioFormat); +} + +void initAudioConfig(void) +{ + ConfigParam *param = getConfigParam(CONF_AUDIO_OUTPUT_FORMAT); + + if (NULL == param || NULL == param->value) + return; + + audio_configFormat = xmalloc(sizeof(AudioFormat)); + + if (0 != parseAudioConfig(audio_configFormat, param->value)) { + FATAL("error parsing \"%s\" at line %i\n", + CONF_AUDIO_OUTPUT_FORMAT, param->line); + } +} + +int parseAudioConfig(AudioFormat * audioFormat, char *conf) +{ + char *test; + + memset(audioFormat, 0, sizeof(AudioFormat)); + + audioFormat->sampleRate = strtol(conf, &test, 10); + + if (*test != ':') { + ERROR("error parsing audio output format: %s\n", conf); + return -1; + } + + /*switch(audioFormat->sampleRate) { + case 48000: + case 44100: + case 32000: + case 16000: + break; + default: + ERROR("sample rate %i can not be used for audio output\n", + (int)audioFormat->sampleRate); + return -1 + } */ + + if (audioFormat->sampleRate <= 0) { + ERROR("sample rate %i is not >= 0\n", + (int)audioFormat->sampleRate); + return -1; + } + + audioFormat->bits = strtol(test + 1, &test, 10); + + if (*test != ':') { + ERROR("error parsing audio output format: %s\n", conf); + return -1; + } + + switch (audioFormat->bits) { + case 16: + break; + default: + ERROR("bits %i can not be used for audio output\n", + (int)audioFormat->bits); + return -1; + } + + audioFormat->channels = strtol(test + 1, &test, 10); + + if (*test != '\0') { + ERROR("error parsing audio output format: %s\n", conf); + return -1; + } + + switch (audioFormat->channels) { + case 1: + case 2: + break; + default: + ERROR("channels %i can not be used for audio output\n", + (int)audioFormat->channels); + return -1; + } + + return 0; +} + +void finishAudioConfig(void) +{ + if (audio_configFormat) + free(audio_configFormat); +} + +void finishAudioDriver(void) +{ + int i; + + for (i = 0; i < audioOutputArraySize; i++) { + finishAudioOutput(&audioOutputArray[i]); + } + + free(audioOutputArray); + audioOutputArray = NULL; + audioOutputArraySize = 0; +} + +int isCurrentAudioFormat(AudioFormat * audioFormat) +{ + if (!audioFormat) + return 1; + + if (cmpAudioFormat(audioFormat, &audio_format) != 0) + return 0; + + return 1; +} + +static void syncAudioDeviceStates(void) +{ + int i; + + if (!audio_format.channels) + return; + for (i = 0; i < audioOutputArraySize; ++i ) { + switch (audioDeviceStates[i]) { + case DEVICE_ON: + /* This will reopen only if the audio format changed */ + openAudioOutput(&audioOutputArray[i], &audio_format); + break; + case DEVICE_ENABLE: + openAudioOutput(&audioOutputArray[i], &audio_format); + audioDeviceStates[i] = DEVICE_ON; + break; + case DEVICE_DISABLE: + dropBufferedAudioOutput(&audioOutputArray[i]); + closeAudioOutput(&audioOutputArray[i]); + audioDeviceStates[i] = DEVICE_OFF; + break; + } + } +} + +static int flushAudioBuffer(void) +{ + int ret = -1; + int i, err; + + if (audioBufferPos == 0) + return 0; + + syncAudioDeviceStates(); + + for (i = 0; i < audioOutputArraySize; ++i) { + if (audioDeviceStates[i] != DEVICE_ON) + continue; + err = playAudioOutput(&audioOutputArray[i], audioBuffer, + audioBufferPos); + if (!err) + ret = 0; + else if (err < 0) + /* device should already be closed if the play + * func returned an error */ + audioDeviceStates[i] = DEVICE_ENABLE; + } + + audioBufferPos = 0; + + return ret; +} + +int openAudioDevice(AudioFormat * audioFormat) +{ + int ret = -1; + int i; + + if (!audioOutputArray) + return -1; + + if (!audioOpened || !isCurrentAudioFormat(audioFormat)) { + flushAudioBuffer(); + copyAudioFormat(&audio_format, audioFormat); + audioBufferSize = (audio_format.bits >> 3) * + audio_format.channels; + audioBufferSize *= audio_format.sampleRate >> 5; + audioBuffer = xrealloc(audioBuffer, audioBufferSize); + } + + syncAudioDeviceStates(); + + for (i = 0; i < audioOutputArraySize; ++i) { + if (audioOutputArray[i].open) + ret = 0; + } + + if (ret == 0) + audioOpened = 1; + else { + /* close all devices if there was an error */ + for (i = 0; i < audioOutputArraySize; ++i) { + closeAudioOutput(&audioOutputArray[i]); + } + + audioOpened = 0; + } + + return ret; +} + +int playAudio(char *playChunk, int size) +{ + int send; + + while (size > 0) { + send = audioBufferSize - audioBufferPos; + send = send < size ? send : size; + + memcpy(audioBuffer + audioBufferPos, playChunk, send); + audioBufferPos += send; + size -= send; + playChunk += send; + + if (audioBufferPos == audioBufferSize) { + if (flushAudioBuffer() < 0) + return -1; + } + } + + return 0; +} + +int isAudioDeviceOpen(void) +{ + return audioOpened; +} + +void dropBufferedAudio(void) +{ + int i; + + syncAudioDeviceStates(); + audioBufferPos = 0; + + for (i = 0; i < audioOutputArraySize; ++i) { + if (audioDeviceStates[i] == DEVICE_ON) + dropBufferedAudioOutput(&audioOutputArray[i]); + } +} + +void closeAudioDevice(void) +{ + int i; + + flushAudioBuffer(); + + free(audioBuffer); + audioBuffer = NULL; + audioBufferSize = 0; + + for (i = 0; i < audioOutputArraySize; ++i) { + if (audioDeviceStates[i] == DEVICE_ON) + audioDeviceStates[i] = DEVICE_ENABLE; + closeAudioOutput(&audioOutputArray[i]); + } + + audioOpened = 0; +} + +void sendMetadataToAudioDevice(MpdTag * tag) +{ + int i; + + for (i = 0; i < audioOutputArraySize; ++i) { + sendMetadataToAudioOutput(&audioOutputArray[i], tag); + } +} + +int enableAudioDevice(int fd, int device) +{ + if (device < 0 || device >= audioOutputArraySize) { + commandError(fd, ACK_ERROR_ARG, "audio output device id %i " + "doesn't exist\n", device); + return -1; + } + + if (!(audioDeviceStates[device] & 0x01)) + audioDeviceStates[device] = DEVICE_ENABLE; + + return 0; +} + +int disableAudioDevice(int fd, int device) +{ + if (device < 0 || device >= audioOutputArraySize) { + commandError(fd, ACK_ERROR_ARG, "audio output device id %i " + "doesn't exist\n", device); + return -1; + } + if (audioDeviceStates[device] & 0x01) + audioDeviceStates[device] = DEVICE_DISABLE; + + return 0; +} + +void printAudioDevices(int fd) +{ + int i; + + for (i = 0; i < audioOutputArraySize; i++) { + fdprintf(fd, + "outputid: %i\noutputname: %s\noutputenabled: %i\n", + i, + audioOutputArray[i].name, + audioDeviceStates[i] & 0x01); + } +} + +void saveAudioDevicesState(FILE *fp) +{ + int i; + + assert(audioOutputArraySize != 0); + for (i = 0; i < audioOutputArraySize; i++) { + fprintf(fp, AUDIO_DEVICE_STATE "%d:%s\n", + audioDeviceStates[i] & 0x01, + audioOutputArray[i].name); + } +} + +void readAudioDevicesState(FILE *fp) +{ + char buffer[AUDIO_BUFFER_SIZE]; + int i; + + assert(audioOutputArraySize != 0); + + while (myFgets(buffer, AUDIO_BUFFER_SIZE, fp)) { + char *c, *name; + + if (strncmp(buffer, AUDIO_DEVICE_STATE, AUDIO_DEVICE_STATE_LEN)) + continue; + + c = strchr(buffer, ':'); + if (!c || !(++c)) + goto errline; + + name = strchr(c, ':'); + if (!name || !(++name)) + goto errline; + + for (i = 0; i < audioOutputArraySize; ++i) { + if (!strcmp(name, audioOutputArray[i].name)) { + /* devices default to on */ + if (!atoi(c)) + audioDeviceStates[i] = DEVICE_DISABLE; + break; + } + } + continue; +errline: + /* nonfatal */ + ERROR("invalid line in state_file: %s\n", buffer); + } +} + diff --git a/trunk/src/audio.h b/trunk/src/audio.h new file mode 100644 index 000000000..cc4ac0752 --- /dev/null +++ b/trunk/src/audio.h @@ -0,0 +1,83 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef AUDIO_H +#define AUDIO_H + +#include "../config.h" + +#include "mpd_types.h" +#include "tag.h" + +#include <stdio.h> + +#define AUDIO_AO_DRIVER_DEFAULT "default" + +typedef struct _AudioFormat { + volatile mpd_sint8 channels; + volatile mpd_uint32 sampleRate; + volatile mpd_sint8 bits; +} AudioFormat; + +size_t audio_device_count(void); + +void copyAudioFormat(AudioFormat * dest, AudioFormat * src); + +int cmpAudioFormat(AudioFormat * dest, AudioFormat * src); + +void getOutputAudioFormat(AudioFormat * inFormat, AudioFormat * outFormat); + +int parseAudioConfig(AudioFormat * audioFormat, char *conf); + +/* make sure initPlayerData is called before this function!! */ +void initAudioConfig(void); + +void finishAudioConfig(void); + +void initAudioDriver(void); + +void finishAudioDriver(void); + +int openAudioDevice(AudioFormat * audioFormat); + +int playAudio(char *playChunk, int size); + +void dropBufferedAudio(void); + +void closeAudioDevice(void); + +int isAudioDeviceOpen(void); + +int isCurrentAudioFormat(AudioFormat * audioFormat); + +void sendMetadataToAudioDevice(MpdTag * tag); + +/* these functions are called in the main parent process while the child + process is busy playing to the audio */ +int enableAudioDevice(int fd, int device); + +int disableAudioDevice(int fd, int device); + +void printAudioDevices(int fd); + +void readAudioDevicesState(FILE *fp); + +void saveAudioDevicesState(FILE *fp); + +void loadAudioDrivers(void); +#endif diff --git a/trunk/src/audioOutput.c b/trunk/src/audioOutput.c new file mode 100644 index 000000000..49a7ce258 --- /dev/null +++ b/trunk/src/audioOutput.c @@ -0,0 +1,269 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "audioOutput.h" + +#include "list.h" +#include "log.h" +#include "pcm_utils.h" + +#include <string.h> + +#define AUDIO_OUTPUT_TYPE "type" +#define AUDIO_OUTPUT_NAME "name" +#define AUDIO_OUTPUT_FORMAT "format" + +static List *audioOutputPluginList; + +void loadAudioOutputPlugin(AudioOutputPlugin * audioOutputPlugin) +{ + if (!audioOutputPlugin->name) + return; + insertInList(audioOutputPluginList, audioOutputPlugin->name, + audioOutputPlugin); +} + +void unloadAudioOutputPlugin(AudioOutputPlugin * audioOutputPlugin) +{ + if (!audioOutputPlugin->name) + return; + deleteFromList(audioOutputPluginList, audioOutputPlugin->name); +} + +void initAudioOutputPlugins(void) +{ + audioOutputPluginList = makeList(NULL, 0); +} + +void finishAudioOutputPlugins(void) +{ + freeList(audioOutputPluginList); +} + +#define getBlockParam(name, str, force) { \ + bp = getBlockParam(param, name); \ + if(force && bp == NULL) { \ + FATAL("couldn't find parameter \"%s\" in audio output " \ + "definition beginning at %i\n", \ + name, param->line); \ + } \ + if(bp) str = bp->value; \ +} + +int initAudioOutput(AudioOutput *ao, ConfigParam * param) +{ + void *data = NULL; + char *name = NULL; + char *format = NULL; + char *type = NULL; + BlockParam *bp = NULL; + AudioOutputPlugin *plugin = NULL; + + if (param) { + getBlockParam(AUDIO_OUTPUT_NAME, name, 1); + getBlockParam(AUDIO_OUTPUT_TYPE, type, 1); + getBlockParam(AUDIO_OUTPUT_FORMAT, format, 0); + + if (!findInList(audioOutputPluginList, type, &data)) { + FATAL("couldn't find audio output plugin for type " + "\"%s\" at line %i\n", type, param->line); + } + + plugin = (AudioOutputPlugin *) data; + } else { + ListNode *node = audioOutputPluginList->firstNode; + + WARNING("No \"%s\" defined in config file\n", + CONF_AUDIO_OUTPUT); + WARNING("Attempt to detect audio output device\n"); + + while (node) { + plugin = (AudioOutputPlugin *) node->data; + if (plugin->testDefaultDeviceFunc) { + WARNING("Attempting to detect a %s audio " + "device\n", plugin->name); + if (plugin->testDefaultDeviceFunc() == 0) { + WARNING("Successfully detected a %s " + "audio device\n", plugin->name); + break; + } + } + node = node->nextNode; + } + + if (!node) { + WARNING("Unable to detect an audio device\n"); + return 0; + } + + name = "default detected output"; + type = plugin->name; + } + + ao->name = name; + ao->type = type; + ao->finishDriverFunc = plugin->finishDriverFunc; + ao->openDeviceFunc = plugin->openDeviceFunc; + ao->playFunc = plugin->playFunc; + ao->dropBufferedAudioFunc = plugin->dropBufferedAudioFunc; + ao->closeDeviceFunc = plugin->closeDeviceFunc; + ao->sendMetdataFunc = plugin->sendMetdataFunc; + ao->open = 0; + + ao->convertAudioFormat = 0; + ao->sameInAndOutFormats = 0; + ao->convBuffer = NULL; + ao->convBufferLen = 0; + + memset(&ao->inAudioFormat, 0, sizeof(AudioFormat)); + memset(&ao->outAudioFormat, 0, sizeof(AudioFormat)); + memset(&ao->reqAudioFormat, 0, sizeof(AudioFormat)); + memset(&ao->convState, 0, sizeof(ConvState)); + + if (format) { + ao->convertAudioFormat = 1; + + if (0 != parseAudioConfig(&ao->reqAudioFormat, format)) { + FATAL("error parsing format at line %i\n", bp->line); + } + + copyAudioFormat(&ao->outAudioFormat, &ao->reqAudioFormat); + } + + if (plugin->initDriverFunc(ao, param) != 0) + return 0; + + return 1; +} + +int openAudioOutput(AudioOutput * audioOutput, AudioFormat * audioFormat) +{ + int ret = 0; + + if (audioOutput->open) + { + if (0==cmpAudioFormat(audioFormat, &audioOutput->inAudioFormat)) + { + return 0; + } + } + + copyAudioFormat(&audioOutput->inAudioFormat, audioFormat); + + if (audioOutput->convertAudioFormat) + { + copyAudioFormat(&audioOutput->outAudioFormat, + &audioOutput->reqAudioFormat); + } + else + { + copyAudioFormat(&audioOutput->outAudioFormat, + &audioOutput->inAudioFormat); + if (audioOutput->open) closeAudioOutput(audioOutput); + } + + if (!audioOutput->open) + { + ret = audioOutput->openDeviceFunc(audioOutput); + } + + audioOutput->sameInAndOutFormats = + !cmpAudioFormat(&audioOutput->inAudioFormat, + &audioOutput->outAudioFormat); + + return ret; +} + +static void convertAudioFormat(AudioOutput * audioOutput, char **chunkArgPtr, + int *sizeArgPtr) +{ + int size = pcm_sizeOfConvBuffer(&(audioOutput->inAudioFormat), + *sizeArgPtr, + &(audioOutput->outAudioFormat)); + + if (size > audioOutput->convBufferLen) { + audioOutput->convBuffer = + xrealloc(audioOutput->convBuffer, size); + audioOutput->convBufferLen = size; + } + + *sizeArgPtr = pcm_convertAudioFormat(&(audioOutput->inAudioFormat), + *chunkArgPtr, *sizeArgPtr, + &(audioOutput->outAudioFormat), + audioOutput->convBuffer, + &audioOutput->convState); + + *chunkArgPtr = audioOutput->convBuffer; +} + +int playAudioOutput(AudioOutput * audioOutput, char *playChunk, int size) +{ + int ret; + + if (!audioOutput->open) + return -1; + + if (!audioOutput->sameInAndOutFormats) { + convertAudioFormat(audioOutput, &playChunk, &size); + } + + ret = audioOutput->playFunc(audioOutput, playChunk, size); + + return ret; +} + +void dropBufferedAudioOutput(AudioOutput * audioOutput) +{ + if (audioOutput->open) + audioOutput->dropBufferedAudioFunc(audioOutput); +} + +void closeAudioOutput(AudioOutput * audioOutput) +{ + if (audioOutput->open) + audioOutput->closeDeviceFunc(audioOutput); +} + +void finishAudioOutput(AudioOutput * audioOutput) +{ + closeAudioOutput(audioOutput); + audioOutput->finishDriverFunc(audioOutput); + if (audioOutput->convBuffer) + free(audioOutput->convBuffer); +} + +void sendMetadataToAudioOutput(AudioOutput * audioOutput, MpdTag * tag) +{ + if (!audioOutput->sendMetdataFunc) + return; + audioOutput->sendMetdataFunc(audioOutput, tag); +} + +void printAllOutputPluginTypes(FILE * fp) +{ + ListNode *node = audioOutputPluginList->firstNode; + AudioOutputPlugin *plugin; + + while (node) { + plugin = (AudioOutputPlugin *) node->data; + fprintf(fp, "%s ", plugin->name); + node = node->nextNode; + } + fprintf(fp, "\n"); + fflush(fp); +} diff --git a/trunk/src/audioOutput.h b/trunk/src/audioOutput.h new file mode 100644 index 000000000..bcbe7997d --- /dev/null +++ b/trunk/src/audioOutput.h @@ -0,0 +1,117 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef AUDIO_OUTPUT_H +#define AUDIO_OUTPUT_H + +#include "../config.h" + +#include "pcm_utils.h" +#include "mpd_types.h" +#include "audio.h" +#include "tag.h" +#include "conf.h" +#include "utils.h" + +#define DISABLED_AUDIO_OUTPUT_PLUGIN(plugin) AudioOutputPlugin plugin; + +typedef struct _AudioOutput AudioOutput; + +typedef int (*AudioOutputTestDefaultDeviceFunc) (); + +typedef int (*AudioOutputInitDriverFunc) (AudioOutput * audioOutput, + ConfigParam * param); + +typedef void (*AudioOutputFinishDriverFunc) (AudioOutput * audioOutput); + +typedef int (*AudioOutputOpenDeviceFunc) (AudioOutput * audioOutput); + +typedef int (*AudioOutputPlayFunc) (AudioOutput * audioOutput, + char *playChunk, int size); + +typedef void (*AudioOutputDropBufferedAudioFunc) (AudioOutput * audioOutput); + +typedef void (*AudioOutputCloseDeviceFunc) (AudioOutput * audioOutput); + +typedef void (*AudioOutputSendMetadataFunc) (AudioOutput * audioOutput, + MpdTag * tag); + +struct _AudioOutput { + int open; + char *name; + char *type; + + AudioOutputFinishDriverFunc finishDriverFunc; + AudioOutputOpenDeviceFunc openDeviceFunc; + AudioOutputPlayFunc playFunc; + AudioOutputDropBufferedAudioFunc dropBufferedAudioFunc; + AudioOutputCloseDeviceFunc closeDeviceFunc; + AudioOutputSendMetadataFunc sendMetdataFunc; + + int convertAudioFormat; + AudioFormat inAudioFormat; + AudioFormat outAudioFormat; + AudioFormat reqAudioFormat; + ConvState convState; + char *convBuffer; + int convBufferLen; + int sameInAndOutFormats; + + void *data; +}; + +typedef struct _AudioOutputPlugin { + char *name; + + AudioOutputTestDefaultDeviceFunc testDefaultDeviceFunc; + AudioOutputInitDriverFunc initDriverFunc; + AudioOutputFinishDriverFunc finishDriverFunc; + AudioOutputOpenDeviceFunc openDeviceFunc; + AudioOutputPlayFunc playFunc; + AudioOutputDropBufferedAudioFunc dropBufferedAudioFunc; + AudioOutputCloseDeviceFunc closeDeviceFunc; + AudioOutputSendMetadataFunc sendMetdataFunc; +} AudioOutputPlugin; + +void initAudioOutputPlugins(void); +void finishAudioOutputPlugins(void); + +void loadAudioOutputPlugin(AudioOutputPlugin * audioOutputPlugin); +void unloadAudioOutputPlugin(AudioOutputPlugin * audioOutputPlugin); + +int initAudioOutput(AudioOutput *, ConfigParam * param); +int openAudioOutput(AudioOutput * audioOutput, AudioFormat * audioFormat); +int playAudioOutput(AudioOutput * audioOutput, char *playChunk, int size); +void dropBufferedAudioOutput(AudioOutput * audioOutput); +void closeAudioOutput(AudioOutput * audioOutput); +void finishAudioOutput(AudioOutput * audioOutput); +int keepAudioOutputAlive(AudioOutput * audioOutput, int ms); +void sendMetadataToAudioOutput(AudioOutput * audioOutput, MpdTag * tag); + +void printAllOutputPluginTypes(FILE * fp); + +extern AudioOutputPlugin alsaPlugin; +extern AudioOutputPlugin aoPlugin; +extern AudioOutputPlugin ossPlugin; +extern AudioOutputPlugin osxPlugin; +extern AudioOutputPlugin pulsePlugin; +extern AudioOutputPlugin mvpPlugin; +extern AudioOutputPlugin shoutPlugin; +extern AudioOutputPlugin jackPlugin; + +#endif diff --git a/trunk/src/audioOutputs/audioOutput_alsa.c b/trunk/src/audioOutputs/audioOutput_alsa.c new file mode 100644 index 000000000..3ade3df46 --- /dev/null +++ b/trunk/src/audioOutputs/audioOutput_alsa.c @@ -0,0 +1,427 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "../audioOutput.h" + +#include <stdlib.h> + +#ifdef HAVE_ALSA + +#define ALSA_PCM_NEW_HW_PARAMS_API +#define ALSA_PCM_NEW_SW_PARAMS_API + +#define MPD_ALSA_BUFFER_TIME_US 500000 +/* the default period time of xmms is 50 ms, so let's use that as well. + * a user can tweak this parameter via the "period_time" config parameter. + */ +#define MPD_ALSA_PERIOD_TIME_US 50000 +#define MPD_ALSA_RETRY_NR 5 + +#include "../conf.h" +#include "../log.h" + +#include <string.h> + +#include <alsa/asoundlib.h> + +typedef snd_pcm_sframes_t alsa_writei_t(snd_pcm_t * pcm, const void *buffer, + snd_pcm_uframes_t size); + +typedef struct _AlsaData { + char *device; + snd_pcm_t *pcmHandle; + alsa_writei_t *writei; + unsigned int buffer_time; + unsigned int period_time; + int sampleSize; + int useMmap; + int canPause; + int canResume; +} AlsaData; + +static AlsaData *newAlsaData(void) +{ + AlsaData *ret = xmalloc(sizeof(AlsaData)); + + ret->device = NULL; + ret->pcmHandle = NULL; + ret->writei = snd_pcm_writei; + ret->useMmap = 0; + ret->buffer_time = MPD_ALSA_BUFFER_TIME_US; + ret->period_time = MPD_ALSA_PERIOD_TIME_US; + + return ret; +} + +static void freeAlsaData(AlsaData * ad) +{ + if (ad->device) + free(ad->device); + + free(ad); +} + +static int alsa_initDriver(AudioOutput * audioOutput, ConfigParam * param) +{ + AlsaData *ad = newAlsaData(); + + if (param) { + BlockParam *bp = getBlockParam(param, "device"); + ad->device = bp ? xstrdup(bp->value) : xstrdup("default"); + + if ((bp = getBlockParam(param, "use_mmap")) && + !strcasecmp(bp->value, "yes")) + ad->useMmap = 1; + if ((bp = getBlockParam(param, "buffer_time"))) + ad->buffer_time = atoi(bp->value); + if ((bp = getBlockParam(param, "period_time"))) + ad->period_time = atoi(bp->value); + } else + ad->device = xstrdup("default"); + audioOutput->data = ad; + + return 0; +} + +static void alsa_finishDriver(AudioOutput * audioOutput) +{ + AlsaData *ad = audioOutput->data; + + freeAlsaData(ad); +} + +static int alsa_testDefault(void) +{ + snd_pcm_t *handle; + + int ret = snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, + SND_PCM_NONBLOCK); + snd_config_update_free_global(); + + if (ret) { + WARNING("Error opening default alsa device: %s\n", + snd_strerror(-ret)); + return -1; + } else + snd_pcm_close(handle); + + return 0; +} + +static int alsa_openDevice(AudioOutput * audioOutput) +{ + AlsaData *ad = audioOutput->data; + AudioFormat *audioFormat = &audioOutput->outAudioFormat; + snd_pcm_format_t bitformat; + snd_pcm_hw_params_t *hwparams; + snd_pcm_sw_params_t *swparams; + unsigned int sampleRate = audioFormat->sampleRate; + unsigned int channels = audioFormat->channels; + snd_pcm_uframes_t alsa_buffer_size; + snd_pcm_uframes_t alsa_period_size; + int err; + const char *cmd = NULL; + int retry = MPD_ALSA_RETRY_NR; + unsigned int period_time, period_time_ro; + unsigned int buffer_time; + + switch (audioFormat->bits) { + case 8: + bitformat = SND_PCM_FORMAT_S8; + break; + case 16: + bitformat = SND_PCM_FORMAT_S16; + break; + case 24: + bitformat = SND_PCM_FORMAT_S24; + break; + case 32: + bitformat = SND_PCM_FORMAT_S32; + break; + default: + ERROR("ALSA device \"%s\" doesn't support %i bit audio\n", + ad->device, audioFormat->bits); + return -1; + } + + err = snd_pcm_open(&ad->pcmHandle, ad->device, + SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK); + snd_config_update_free_global(); + if (err < 0) { + ad->pcmHandle = NULL; + goto error; + } + + cmd = "snd_pcm_nonblock"; + err = snd_pcm_nonblock(ad->pcmHandle, 0); + if (err < 0) + goto error; + + period_time_ro = period_time = ad->period_time; +configure_hw: + /* configure HW params */ + snd_pcm_hw_params_alloca(&hwparams); + + cmd = "snd_pcm_hw_params_any"; + err = snd_pcm_hw_params_any(ad->pcmHandle, hwparams); + if (err < 0) + goto error; + + if (ad->useMmap) { + err = snd_pcm_hw_params_set_access(ad->pcmHandle, hwparams, + SND_PCM_ACCESS_MMAP_INTERLEAVED); + if (err < 0) { + ERROR("Cannot set mmap'ed mode on alsa device \"%s\": " + " %s\n", ad->device, snd_strerror(-err)); + ERROR("Falling back to direct write mode\n"); + ad->useMmap = 0; + } else + ad->writei = snd_pcm_mmap_writei; + } + + if (!ad->useMmap) { + cmd = "snd_pcm_hw_params_set_access"; + err = snd_pcm_hw_params_set_access(ad->pcmHandle, hwparams, + SND_PCM_ACCESS_RW_INTERLEAVED); + if (err < 0) + goto error; + ad->writei = snd_pcm_writei; + } + + err = snd_pcm_hw_params_set_format(ad->pcmHandle, hwparams, bitformat); + if (err < 0) { + ERROR("ALSA device \"%s\" does not support %i bit audio: " + "%s\n", ad->device, audioFormat->bits, snd_strerror(-err)); + goto fail; + } + + err = snd_pcm_hw_params_set_channels_near(ad->pcmHandle, hwparams, + &channels); + if (err < 0) { + ERROR("ALSA device \"%s\" does not support %i channels: " + "%s\n", ad->device, (int)audioFormat->channels, + snd_strerror(-err)); + goto fail; + } + audioFormat->channels = channels; + + err = snd_pcm_hw_params_set_rate_near(ad->pcmHandle, hwparams, + &sampleRate, NULL); + if (err < 0 || sampleRate == 0) { + ERROR("ALSA device \"%s\" does not support %i Hz audio\n", + ad->device, (int)audioFormat->sampleRate); + goto fail; + } + audioFormat->sampleRate = sampleRate; + + buffer_time = ad->buffer_time; + cmd = "snd_pcm_hw_params_set_buffer_time_near"; + err = snd_pcm_hw_params_set_buffer_time_near(ad->pcmHandle, hwparams, + &buffer_time, NULL); + if (err < 0) + goto error; + + period_time = period_time_ro; + cmd = "snd_pcm_hw_params_set_period_time_near"; + err = snd_pcm_hw_params_set_period_time_near(ad->pcmHandle, hwparams, + &period_time, NULL); + if (err < 0) + goto error; + + cmd = "snd_pcm_hw_params"; + err = snd_pcm_hw_params(ad->pcmHandle, hwparams); + if (err == -EPIPE && --retry > 0) { + period_time_ro = period_time_ro >> 1; + goto configure_hw; + } else if (err < 0) + goto error; + if (retry != MPD_ALSA_RETRY_NR) + DEBUG("ALSA period_time set to %d\n", period_time); + + cmd = "snd_pcm_hw_params_get_buffer_size"; + err = snd_pcm_hw_params_get_buffer_size(hwparams, &alsa_buffer_size); + if (err < 0) + goto error; + + cmd = "snd_pcm_hw_params_get_period_size"; + err = snd_pcm_hw_params_get_period_size(hwparams, &alsa_period_size, + NULL); + if (err < 0) + goto error; + + ad->canPause = snd_pcm_hw_params_can_pause(hwparams); + ad->canResume = snd_pcm_hw_params_can_resume(hwparams); + + /* configure SW params */ + snd_pcm_sw_params_alloca(&swparams); + + cmd = "snd_pcm_sw_params_current"; + err = snd_pcm_sw_params_current(ad->pcmHandle, swparams); + if (err < 0) + goto error; + + cmd = "snd_pcm_sw_params_set_start_threshold"; + err = snd_pcm_sw_params_set_start_threshold(ad->pcmHandle, swparams, + alsa_buffer_size - + alsa_period_size); + if (err < 0) + goto error; + + cmd = "snd_pcm_sw_params_set_avail_min"; + err = snd_pcm_sw_params_set_avail_min(ad->pcmHandle, swparams, + alsa_period_size); + if (err < 0) + goto error; + + cmd = "snd_pcm_sw_params_set_xfer_align"; + err = snd_pcm_sw_params_set_xfer_align(ad->pcmHandle, swparams, 1); + if (err < 0) + goto error; + + cmd = "snd_pcm_sw_params"; + err = snd_pcm_sw_params(ad->pcmHandle, swparams); + if (err < 0) + goto error; + + ad->sampleSize = (audioFormat->bits / 8) * audioFormat->channels; + + audioOutput->open = 1; + + DEBUG("alsa device \"%s\" will be playing %i bit, %i channel audio at " + "%i Hz\n", ad->device, (int)audioFormat->bits, + channels, sampleRate); + + return 0; + +error: + if (cmd) { + ERROR("Error opening alsa device \"%s\" (%s): %s\n", + ad->device, cmd, snd_strerror(-err)); + } else { + ERROR("Error opening alsa device \"%s\": %s\n", ad->device, + snd_strerror(-err)); + } +fail: + if (ad->pcmHandle) + snd_pcm_close(ad->pcmHandle); + ad->pcmHandle = NULL; + audioOutput->open = 0; + return -1; +} + +static int alsa_errorRecovery(AlsaData * ad, int err) +{ + if (err == -EPIPE) { + DEBUG("Underrun on alsa device \"%s\"\n", ad->device); + } else if (err == -ESTRPIPE) { + DEBUG("alsa device \"%s\" was suspended\n", ad->device); + } + + switch (snd_pcm_state(ad->pcmHandle)) { + case SND_PCM_STATE_PAUSED: + err = snd_pcm_pause(ad->pcmHandle, /* disable */ 0); + break; + case SND_PCM_STATE_SUSPENDED: + err = ad->canResume ? + snd_pcm_resume(ad->pcmHandle) : + snd_pcm_prepare(ad->pcmHandle); + break; + case SND_PCM_STATE_SETUP: + case SND_PCM_STATE_XRUN: + err = snd_pcm_prepare(ad->pcmHandle); + break; + case SND_PCM_STATE_DISCONNECTED: + /* so alsa_closeDevice won't try to drain: */ + snd_pcm_close(ad->pcmHandle); + ad->pcmHandle = NULL; + break; + default: + /* unknown state, do nothing */ + break; + } + + return err; +} + +static void alsa_dropBufferedAudio(AudioOutput * audioOutput) +{ + AlsaData *ad = audioOutput->data; + + alsa_errorRecovery(ad, snd_pcm_drop(ad->pcmHandle)); +} + +static void alsa_closeDevice(AudioOutput * audioOutput) +{ + AlsaData *ad = audioOutput->data; + + if (ad->pcmHandle) { + snd_pcm_drain(ad->pcmHandle); + snd_pcm_close(ad->pcmHandle); + ad->pcmHandle = NULL; + } + + audioOutput->open = 0; +} + +static int alsa_playAudio(AudioOutput * audioOutput, char *playChunk, int size) +{ + AlsaData *ad = audioOutput->data; + int ret; + + size /= ad->sampleSize; + + while (size > 0) { + ret = ad->writei(ad->pcmHandle, playChunk, size); + + if (ret == -EAGAIN || ret == -EINTR) + continue; + + if (ret < 0) { + if (alsa_errorRecovery(ad, ret) < 0) { + ERROR("closing alsa device \"%s\" due to write " + "error: %s\n", ad->device, + snd_strerror(-errno)); + alsa_closeDevice(audioOutput); + return -1; + } + continue; + } + + playChunk += ret * ad->sampleSize; + size -= ret; + } + + return 0; +} + +AudioOutputPlugin alsaPlugin = { + "alsa", + alsa_testDefault, + alsa_initDriver, + alsa_finishDriver, + alsa_openDevice, + alsa_playAudio, + alsa_dropBufferedAudio, + alsa_closeDevice, + NULL, /* sendMetadataFunc */ +}; + +#else /* HAVE ALSA */ + +DISABLED_AUDIO_OUTPUT_PLUGIN(alsaPlugin) +#endif /* HAVE_ALSA */ diff --git a/trunk/src/audioOutputs/audioOutput_ao.c b/trunk/src/audioOutputs/audioOutput_ao.c new file mode 100644 index 000000000..a7f437ef4 --- /dev/null +++ b/trunk/src/audioOutputs/audioOutput_ao.c @@ -0,0 +1,246 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "../audioOutput.h" + +#ifdef HAVE_AO + +#include "../conf.h" +#include "../log.h" + +#include <string.h> + +#include <ao/ao.h> + +static int driverInitCount; + +typedef struct _AoData { + int writeSize; + int driverId; + ao_option *options; + ao_device *device; +} AoData; + +static AoData *newAoData(void) +{ + AoData *ret = xmalloc(sizeof(AoData)); + ret->device = NULL; + ret->options = NULL; + + return ret; +} + +static void audioOutputAo_error(void) +{ + if (errno == AO_ENOTLIVE) { + ERROR("not a live ao device\n"); + } else if (errno == AO_EOPENDEVICE) { + ERROR("not able to open audio device\n"); + } else if (errno == AO_EBADOPTION) { + ERROR("bad driver option\n"); + } +} + +static int audioOutputAo_initDriver(AudioOutput * audioOutput, + ConfigParam * param) +{ + ao_info *ai; + char *dup; + char *stk1; + char *stk2; + char *n1; + char *key; + char *value; + char *test; + AoData *ad = newAoData(); + BlockParam *blockParam; + + audioOutput->data = ad; + + if ((blockParam = getBlockParam(param, "write_size"))) { + ad->writeSize = strtol(blockParam->value, &test, 10); + if (*test != '\0') { + FATAL("\"%s\" is not a valid write size at line %i\n", + blockParam->value, blockParam->line); + } + } else + ad->writeSize = 1024; + + if (driverInitCount == 0) { + ao_initialize(); + } + driverInitCount++; + + blockParam = getBlockParam(param, "driver"); + + if (!blockParam || 0 == strcmp(blockParam->value, "default")) { + ad->driverId = ao_default_driver_id(); + } else if ((ad->driverId = ao_driver_id(blockParam->value)) < 0) { + FATAL("\"%s\" is not a valid ao driver at line %i\n", + blockParam->value, blockParam->line); + } + + if ((ai = ao_driver_info(ad->driverId)) == NULL) { + FATAL("problems getting driver info for device defined at line %i\n" + "you may not have permission to the audio device\n", param->line); + } + + DEBUG("using ao driver \"%s\" for \"%s\"\n", ai->short_name, + audioOutput->name); + + blockParam = getBlockParam(param, "options"); + + if (blockParam) { + dup = xstrdup(blockParam->value); + } else + dup = xstrdup(""); + + if (strlen(dup)) { + stk1 = NULL; + n1 = strtok_r(dup, ";", &stk1); + while (n1) { + stk2 = NULL; + key = strtok_r(n1, "=", &stk2); + if (!key) + FATAL("problems parsing options \"%s\"\n", n1); + /*found = 0; + for(i=0;i<ai->option_count;i++) { + if(strcmp(ai->options[i],key)==0) { + found = 1; + break; + } + } + if(!found) { + FATAL("\"%s\" is not an option for " + "\"%s\" ao driver\n",key, + ai->short_name); + } */ + value = strtok_r(NULL, "", &stk2); + if (!value) + FATAL("problems parsing options \"%s\"\n", n1); + ao_append_option(&ad->options, key, value); + n1 = strtok_r(NULL, ";", &stk1); + } + } + free(dup); + + return 0; +} + +static void freeAoData(AoData * ad) +{ + ao_free_options(ad->options); + free(ad); +} + +static void audioOutputAo_finishDriver(AudioOutput * audioOutput) +{ + AoData *ad = (AoData *) audioOutput->data; + freeAoData(ad); + + driverInitCount--; + + if (driverInitCount == 0) + ao_shutdown(); +} + +static void audioOutputAo_dropBufferedAudio(AudioOutput * audioOutput) +{ + /* not supported by libao */ +} + +static void audioOutputAo_closeDevice(AudioOutput * audioOutput) +{ + AoData *ad = (AoData *) audioOutput->data; + + if (ad->device) { + ao_close(ad->device); + ad->device = NULL; + } + + audioOutput->open = 0; +} + +static int audioOutputAo_openDevice(AudioOutput * audioOutput) +{ + ao_sample_format format; + AoData *ad = (AoData *) audioOutput->data; + + if (ad->device) { + audioOutputAo_closeDevice(audioOutput); + } + + format.bits = audioOutput->outAudioFormat.bits; + format.rate = audioOutput->outAudioFormat.sampleRate; + format.byte_format = AO_FMT_NATIVE; + format.channels = audioOutput->outAudioFormat.channels; + + ad->device = ao_open_live(ad->driverId, &format, ad->options); + + if (ad->device == NULL) + return -1; + + audioOutput->open = 1; + + return 0; +} + +static int audioOutputAo_play(AudioOutput * audioOutput, char *playChunk, + int size) +{ + int send; + AoData *ad = (AoData *) audioOutput->data; + + if (ad->device == NULL) + return -1; + + while (size > 0) { + send = ad->writeSize > size ? size : ad->writeSize; + + if (ao_play(ad->device, playChunk, send) == 0) { + audioOutputAo_error(); + ERROR("closing audio device due to write error\n"); + audioOutputAo_closeDevice(audioOutput); + return -1; + } + + playChunk += send; + size -= send; + } + + return 0; +} + +AudioOutputPlugin aoPlugin = { + "ao", + NULL, + audioOutputAo_initDriver, + audioOutputAo_finishDriver, + audioOutputAo_openDevice, + audioOutputAo_play, + audioOutputAo_dropBufferedAudio, + audioOutputAo_closeDevice, + NULL, /* sendMetadataFunc */ +}; + +#else + +#include <stdio.h> + +DISABLED_AUDIO_OUTPUT_PLUGIN(aoPlugin) +#endif diff --git a/trunk/src/audioOutputs/audioOutput_jack.c b/trunk/src/audioOutputs/audioOutput_jack.c new file mode 100644 index 000000000..1fdfaf4bb --- /dev/null +++ b/trunk/src/audioOutputs/audioOutput_jack.c @@ -0,0 +1,440 @@ +/* jack plug in for the Music Player Daemon (MPD) + * (c)2006 by anarch(anarchsss@gmail.com) + * + * 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 + */ + +#include "../audioOutput.h" + +#ifdef HAVE_JACK + +#include <stdlib.h> +#include <errno.h> + +#include "../conf.h" +#include "../log.h" + +#include <string.h> +#include <pthread.h> + +#include <jack/jack.h> +#include <jack/types.h> +#include <jack/ringbuffer.h> + +pthread_mutex_t play_audio_lock = PTHREAD_MUTEX_INITIALIZER; +pthread_cond_t play_audio = PTHREAD_COND_INITIALIZER; + +/*#include "dmalloc.h"*/ + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +/*#define SAMPLE_SIZE sizeof(jack_default_audio_sample_t);*/ + + +static char *name = "mpd"; +static char *output_ports[2]; +static int ringbuf_sz = 32768; +size_t sample_size = sizeof(jack_default_audio_sample_t); + +typedef struct _JackData { + jack_port_t *ports[2]; + jack_client_t *client; + jack_ringbuffer_t *ringbuffer[2]; + int bps; + int shutdown; +} JackData; + +/*JackData *jd = NULL;*/ + +static JackData *newJackData(void) +{ + JackData *ret; + ret = xcalloc(sizeof(JackData), 1); + + return ret; +} + +static void freeJackData(AudioOutput *audioOutput) +{ + JackData *jd = audioOutput->data; + if (jd) { + if (jd->ringbuffer[0]) + jack_ringbuffer_free(jd->ringbuffer[0]); + if (jd->ringbuffer[1]) + jack_ringbuffer_free(jd->ringbuffer[1]); + free(jd); + audioOutput->data = NULL; + } +} + +static void jack_finishDriver(AudioOutput *audioOutput) +{ + JackData *jd = audioOutput->data; + int i; + + if ( jd && jd->client ) { + jack_deactivate(jd->client); + jack_client_close(jd->client); + } + DEBUG("disconnect_jack (pid=%d)\n", getpid ()); + + if ( strcmp(name, "mpd") ) { + free(name); + name = "mpd"; + } + + for ( i = ARRAY_SIZE(output_ports); --i >= 0; ) { + if (!output_ports[i]) + continue; + free(output_ports[i]); + output_ports[i] = NULL; + } + + freeJackData(audioOutput); +} + +static int srate(jack_nframes_t rate, void *data) +{ + JackData *jd = (JackData *) ((AudioOutput*) data)->data; + AudioFormat *audioFormat = &(((AudioOutput*) data)->outAudioFormat); + + audioFormat->sampleRate = (int)jack_get_sample_rate(jd->client); + + return 0; +} + +static int process(jack_nframes_t nframes, void *arg) +{ + size_t i; + JackData *jd = (JackData *) arg; + jack_default_audio_sample_t *out[2]; + size_t avail_data, avail_frames; + + if ( nframes <= 0 ) + return 0; + + out[0] = jack_port_get_buffer(jd->ports[0], nframes); + out[1] = jack_port_get_buffer(jd->ports[1], nframes); + + while ( nframes ) { + avail_data = jack_ringbuffer_read_space(jd->ringbuffer[1]); + + if ( avail_data > 0 ) { + avail_frames = avail_data / sample_size; + + if (avail_frames > nframes) { + avail_frames = nframes; + avail_data = nframes*sample_size; + } + + jack_ringbuffer_read(jd->ringbuffer[0], (char *)out[0], + avail_data); + jack_ringbuffer_read(jd->ringbuffer[1], (char *)out[1], + avail_data); + + nframes -= avail_frames; + out[0] += avail_data; + out[1] += avail_data; + } else { + for (i = 0; i < nframes; i++) + out[0][i] = out[1][i] = 0.0; + nframes = 0; + } + + if (pthread_mutex_trylock (&play_audio_lock) == 0) { + pthread_cond_signal (&play_audio); + pthread_mutex_unlock (&play_audio_lock); + } + } + + + /*DEBUG("process (pid=%d)\n", getpid());*/ + return 0; +} + +static void shutdown_callback(void *arg) +{ + JackData *jd = (JackData *) arg; + jd->shutdown = 1; +} + +static void set_audioformat(AudioOutput *audioOutput) +{ + JackData *jd = audioOutput->data; + AudioFormat *audioFormat = &audioOutput->outAudioFormat; + + audioFormat->sampleRate = (int) jack_get_sample_rate(jd->client); + DEBUG("samplerate = %d\n", audioFormat->sampleRate); + audioFormat->channels = 2; + audioFormat->bits = 16; + jd->bps = audioFormat->channels + * sizeof(jack_default_audio_sample_t) + * audioFormat->sampleRate; +} + +static void error_callback(const char *msg) +{ + ERROR("jack: %s\n", msg); +} + +static int jack_initDriver(AudioOutput *audioOutput, ConfigParam *param) +{ + BlockParam *bp; + char *endptr; + int val; + char *cp = NULL; + + DEBUG("jack_initDriver (pid=%d)\n", getpid()); + if ( ! param ) return 0; + + if ( (bp = getBlockParam(param, "ports")) ) { + DEBUG("output_ports=%s\n", bp->value); + + if (!(cp = strchr(bp->value, ','))) + FATAL("expected comma and a second value for '%s' " + "at line %d: %s\n", + bp->name, bp->line, bp->value); + + *cp = '\0'; + output_ports[0] = xstrdup(bp->value); + *cp++ = ','; + + if (!*cp) + FATAL("expected a second value for '%s' at line %d: " + "%s\n", bp->name, bp->line, bp->value); + + output_ports[1] = xstrdup(cp); + + if (strchr(cp,',')) + FATAL("Only %d values are supported for '%s' " + "at line %d\n", (int)ARRAY_SIZE(output_ports), + bp->name, bp->line); + } + + if ( (bp = getBlockParam(param, "ringbuffer_size")) ) { + errno = 0; + val = strtol(bp->value, &endptr, 10); + + if ( errno == 0 && endptr != bp->value) { + ringbuf_sz = val < 32768 ? 32768 : val; + DEBUG("ringbuffer_size=%d\n", ringbuf_sz); + } else { + FATAL("%s is not a number; ringbuf_size=%d\n", + bp->value, ringbuf_sz); + } + } + + if ( (bp = getBlockParam(param, "name")) + && (strcmp(bp->value, "mpd") != 0) ) { + name = xstrdup(bp->value); + DEBUG("name=%s\n", name); + } + + return 0; +} + +static int jack_testDefault(void) +{ + return 0; +} + +static int connect_jack(AudioOutput *audioOutput) +{ + JackData *jd = audioOutput->data; + char **jports; + char *port_name; + + if ( (jd->client = jack_client_new(name)) == NULL ) { + ERROR("jack server not running?\n"); + freeJackData(audioOutput); + return -1; + } + + jack_set_error_function(error_callback); + jack_set_process_callback(jd->client, process, (void *)jd); + jack_set_sample_rate_callback(jd->client, (JackProcessCallback)srate, + (void *)audioOutput); + jack_on_shutdown(jd->client, shutdown_callback, (void *)jd); + + if ( jack_activate(jd->client) ) { + ERROR("cannot activate client"); + freeJackData(audioOutput); + return -1; + } + + jd->ports[0] = jack_port_register(jd->client, "left", + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + if ( !jd->ports[0] ) { + ERROR("Cannot register left output port.\n"); + freeJackData(audioOutput); + return -1; + } + + jd->ports[1] = jack_port_register(jd->client, "right", + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + if ( !jd->ports[1] ) { + ERROR("Cannot register right output port.\n"); + freeJackData(audioOutput); + return -1; + } + + /* hay que buscar que hay */ + if ( !output_ports[1] + && (jports = (char **)jack_get_ports(jd->client, NULL, NULL, + JackPortIsPhysical| + JackPortIsInput)) ) { + output_ports[0] = jports[0]; + output_ports[1] = jports[1] ? jports[1] : jports[0]; + DEBUG("output_ports: %s %s\n", output_ports[0], output_ports[1]); + free(jports); + } + + if ( output_ports[1] ) { + jd->ringbuffer[0] = jack_ringbuffer_create(ringbuf_sz); + jd->ringbuffer[1] = jack_ringbuffer_create(ringbuf_sz); + memset(jd->ringbuffer[0]->buf, 0, jd->ringbuffer[0]->size); + memset(jd->ringbuffer[1]->buf, 0, jd->ringbuffer[1]->size); + + port_name = xmalloc(sizeof(char)*(7+strlen(name))); + + sprintf(port_name, "%s:left", name); + if ( (jack_connect(jd->client, port_name, + output_ports[0])) != 0 ) { + ERROR("%s is not a valid Jack Client / Port ", + output_ports[0]); + freeJackData(audioOutput); + free(port_name); + return -1; + } + sprintf(port_name, "%s:right", name); + if ( (jack_connect(jd->client, port_name, + output_ports[1])) != 0 ) { + ERROR("%s is not a valid Jack Client / Port ", + output_ports[1]); + freeJackData(audioOutput); + free(port_name); + return -1; + } + free(port_name); + } + + DEBUG("connect_jack (pid=%d)\n", getpid()); + return 1; +} + +static int jack_openDevice(AudioOutput *audioOutput) +{ + JackData *jd = audioOutput->data; + + if ( !jd ) { + DEBUG("connect!\n"); + jd = newJackData(); + audioOutput->data = jd; + + if (connect_jack(audioOutput) < 0) { + freeJackData(audioOutput); + audioOutput->open = 0; + return -1; + } + } + + set_audioformat(audioOutput); + audioOutput->open = 1; + + DEBUG("jack_openDevice (pid=%d)!\n", getpid ()); + return 0; +} + + +static void jack_closeDevice(AudioOutput * audioOutput) +{ + /*jack_finishDriver(audioOutput);*/ + audioOutput->open = 0; + DEBUG("jack_closeDevice (pid=%d)\n", getpid()); +} + +static void jack_dropBufferedAudio (AudioOutput * audioOutput) +{ +} + +static int jack_playAudio(AudioOutput * audioOutput, char *buff, int size) +{ + JackData *jd = audioOutput->data; + size_t space; + int i; + short *buffer = (short *) buff; + jack_default_audio_sample_t sample; + size_t samples = size/4; + + /*DEBUG("jack_playAudio: (pid=%d)!\n", getpid());*/ + + if ( jd->shutdown ) { + ERROR("Refusing to play, because there is no client thread.\n"); + freeJackData(audioOutput); + audioOutput->open = 0; + return 0; + } + + while ( samples && !jd->shutdown ) { + + if ( (space = jack_ringbuffer_write_space(jd->ringbuffer[0])) + >= samples*sample_size ) { + + /*space = MIN(space, samples*sample_size);*/ + /*space = samples*sample_size;*/ + + /*for(i=0; i<space/sample_size; i++) {*/ + for(i=0; i<samples; i++) { + sample = (jack_default_audio_sample_t) *(buffer++)/32768.0; + + jack_ringbuffer_write(jd->ringbuffer[0], (void*)&sample, + sample_size); + + sample = (jack_default_audio_sample_t) *(buffer++)/32768.0; + + jack_ringbuffer_write(jd->ringbuffer[1], (void*)&sample, + sample_size); + + /*samples--;*/ + } + samples=0; + + } else { + pthread_mutex_lock(&play_audio_lock); + pthread_cond_wait(&play_audio, &play_audio_lock); + pthread_mutex_unlock(&play_audio_lock); + } + + } + return 0; +} + +AudioOutputPlugin jackPlugin = { + "jack", + jack_testDefault, + jack_initDriver, + jack_finishDriver, + jack_openDevice, + jack_playAudio, + jack_dropBufferedAudio, + jack_closeDevice, + NULL, /* sendMetadataFunc */ +}; + +#else /* HAVE JACK */ + +DISABLED_AUDIO_OUTPUT_PLUGIN(jackPlugin) + +#endif /* HAVE_JACK */ diff --git a/trunk/src/audioOutputs/audioOutput_mvp.c b/trunk/src/audioOutputs/audioOutput_mvp.c new file mode 100644 index 000000000..ea365c657 --- /dev/null +++ b/trunk/src/audioOutputs/audioOutput_mvp.c @@ -0,0 +1,284 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * Media MVP audio output based on code from MVPMC project: + * http://mvpmc.sourceforge.net/ + * + * 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 + */ + +#include "../audioOutput.h" + +#include <stdlib.h> + +#ifdef HAVE_MVP + +#include "../conf.h" +#include "../log.h" + +#include <string.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +typedef struct { + unsigned long dsp_status; + unsigned long stream_decode_type; + unsigned long sample_rate; + unsigned long bit_rate; + unsigned long raw[64 / sizeof(unsigned long)]; +} aud_status_t; + +#define MVP_SET_AUD_STOP _IOW('a',1,int) +#define MVP_SET_AUD_PLAY _IOW('a',2,int) +#define MVP_SET_AUD_PAUSE _IOW('a',3,int) +#define MVP_SET_AUD_UNPAUSE _IOW('a',4,int) +#define MVP_SET_AUD_SRC _IOW('a',5,int) +#define MVP_SET_AUD_MUTE _IOW('a',6,int) +#define MVP_SET_AUD_BYPASS _IOW('a',8,int) +#define MVP_SET_AUD_CHANNEL _IOW('a',9,int) +#define MVP_GET_AUD_STATUS _IOR('a',10,aud_status_t) +#define MVP_SET_AUD_VOLUME _IOW('a',13,int) +#define MVP_GET_AUD_VOLUME _IOR('a',14,int) +#define MVP_SET_AUD_STREAMTYPE _IOW('a',15,int) +#define MVP_SET_AUD_FORMAT _IOW('a',16,int) +#define MVP_GET_AUD_SYNC _IOR('a',21,pts_sync_data_t*) +#define MVP_SET_AUD_STC _IOW('a',22,long long int *) +#define MVP_SET_AUD_SYNC _IOW('a',23,int) +#define MVP_SET_AUD_END_STREAM _IOW('a',25,int) +#define MVP_SET_AUD_RESET _IOW('a',26,int) +#define MVP_SET_AUD_DAC_CLK _IOW('a',27,int) +#define MVP_GET_AUD_REGS _IOW('a',28,aud_ctl_regs_t*) + +typedef struct _MvpData { + int fd; +} MvpData; + +static int pcmfrequencies[][3] = { + {9, 8000, 32000}, + {10, 11025, 44100}, + {11, 12000, 48000}, + {1, 16000, 32000}, + {2, 22050, 44100}, + {3, 24000, 48000}, + {5, 32000, 32000}, + {0, 44100, 44100}, + {7, 48000, 48000}, + {13, 64000, 32000}, + {14, 88200, 44100}, + {15, 96000, 48000} +}; + +static int numfrequencies = sizeof(pcmfrequencies) / 12; + +static int mvp_testDefault(void) +{ + int fd; + + fd = open("/dev/adec_pcm", O_WRONLY); + + if (fd) { + close(fd); + return 0; + } + + WARNING("Error opening PCM device \"/dev/adec_pcm\": %s\n", + strerror(errno)); + + return -1; +} + +static int mvp_initDriver(AudioOutput * audioOutput, ConfigParam * param) +{ + MvpData *md = xmalloc(sizeof(MvpData)); + md->fd = -1; + audioOutput->data = md; + + return 0; +} + +static void mvp_finishDriver(AudioOutput * audioOutput) +{ + MvpData *md = audioOutput->data; + free(md); +} + +static int mvp_setPcmParams(MvpData * md, unsigned long rate, int channels, + int big_endian, int bits) +{ + int iloop; + int mix[5]; + + if (channels == 1) + mix[0] = 1; + else if (channels == 2) + mix[0] = 0; + else + return -1; + + /* 0,1=24bit(24) , 2,3=16bit */ + if (bits == 16) + mix[1] = 2; + else if (bits == 24) + mix[1] = 0; + else + return -1; + + mix[3] = 0; /* stream type? */ + + if (big_endian == 1) + mix[4] = 1; + else if (big_endian == 0) + mix[4] = 0; + else + return -1; + + /* + * if there is an exact match for the frequency, use it. + */ + for (iloop = 0; iloop < numfrequencies; iloop++) { + if (rate == pcmfrequencies[iloop][1]) { + mix[2] = pcmfrequencies[iloop][0]; + break; + } + } + + if (iloop >= numfrequencies) { + ERROR("Can not find suitable output frequency for %ld\n", rate); + return -1; + } + + if (ioctl(md->fd, MVP_SET_AUD_FORMAT, &mix) < 0) { + ERROR("Can not set audio format\n"); + return -1; + } + + if (ioctl(md->fd, MVP_SET_AUD_SYNC, 2) != 0) { + ERROR("Can not set audio sync\n"); + return -1; + } + + if (ioctl(md->fd, MVP_SET_AUD_PLAY, 0) < 0) { + ERROR("Can not set audio play mode\n"); + return -1; + } + + return 0; +} + +static int mvp_openDevice(AudioOutput * audioOutput) +{ + long long int stc = 0; + MvpData *md = audioOutput->data; + AudioFormat *audioFormat = &audioOutput->outAudioFormat; + int mix[5] = { 0, 2, 7, 1, 0 }; + + if ((md->fd = open("/dev/adec_pcm", O_RDWR | O_NONBLOCK)) < 0) { + ERROR("Error opening /dev/adec_pcm: %s\n", strerror(errno)); + return -1; + } + if (ioctl(md->fd, MVP_SET_AUD_SRC, 1) < 0) { + ERROR("Error setting audio source: %s\n", strerror(errno)); + return -1; + } + if (ioctl(md->fd, MVP_SET_AUD_STREAMTYPE, 0) < 0) { + ERROR("Error setting audio streamtype: %s\n", strerror(errno)); + return -1; + } + if (ioctl(md->fd, MVP_SET_AUD_FORMAT, &mix) < 0) { + ERROR("Error setting audio format: %s\n", strerror(errno)); + return -1; + } + ioctl(md->fd, MVP_SET_AUD_STC, &stc); + if (ioctl(md->fd, MVP_SET_AUD_BYPASS, 1) < 0) { + ERROR("Error setting audio streamtype: %s\n", strerror(errno)); + return -1; + } +#ifdef WORDS_BIGENDIAN + mvp_setPcmParams(md, audioFormat->sampleRate, audioFormat->channels, 0, + audioFormat->bits); +#else + mvp_setPcmParams(md, audioFormat->sampleRate, audioFormat->channels, 1, + audioFormat->bits); +#endif + audioOutput->open = 1; + return 0; +} + +static void mvp_closeDevice(AudioOutput * audioOutput) +{ + MvpData *md = audioOutput->data; + if (md->fd >= 0) + close(md->fd); + md->fd = -1; + audioOutput->open = 0; +} + +static void mvp_dropBufferedAudio(AudioOutput * audioOutput) +{ + MvpData *md = audioOutput->data; + if (md->fd >= 0) { + ioctl(md->fd, MVP_SET_AUD_RESET, 0x11); + close(md->fd); + md->fd = -1; + audioOutput->open = 0; + } +} + +static int mvp_playAudio(AudioOutput * audioOutput, char *playChunk, int size) +{ + MvpData *md = audioOutput->data; + int ret; + + /* reopen the device since it was closed by dropBufferedAudio */ + if (md->fd < 0) + mvp_openDevice(audioOutput); + + while (size > 0) { + ret = write(md->fd, playChunk, size); + if (ret < 0) { + if (errno == EINTR) + continue; + ERROR("closing mvp PCM device due to write error: " + "%s\n", strerror(errno)); + mvp_closeDevice(audioOutput); + return -1; + } + playChunk += ret; + size -= ret; + } + return 0; +} + +AudioOutputPlugin mvpPlugin = { + "mvp", + mvp_testDefault, + mvp_initDriver, + mvp_finishDriver, + mvp_openDevice, + mvp_playAudio, + mvp_dropBufferedAudio, + mvp_closeDevice, + NULL, /* sendMetadataFunc */ +}; + +#else /* HAVE_MVP */ + +DISABLED_AUDIO_OUTPUT_PLUGIN(mvpPlugin) +#endif /* HAVE_MVP */ diff --git a/trunk/src/audioOutputs/audioOutput_oss.c b/trunk/src/audioOutputs/audioOutput_oss.c new file mode 100644 index 000000000..01293cbd1 --- /dev/null +++ b/trunk/src/audioOutputs/audioOutput_oss.c @@ -0,0 +1,575 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * OSS audio output (c) 2004, 2005, 2006, 2007 by Eric Wong <eric@petta-tech.com> + * and Warren Dukes <warren.dukes@gmail.com> + * + * 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 + */ + +#include "../audioOutput.h" + +#include <stdlib.h> + +#ifdef HAVE_OSS + +#include "../conf.h" +#include "../log.h" + +#include <string.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +#if defined(__OpenBSD__) || defined(__NetBSD__) +# include <soundcard.h> +#else /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ +# include <sys/soundcard.h> +#endif /* !(defined(__OpenBSD__) || defined(__NetBSD__) */ + +#ifdef WORDS_BIGENDIAN +# define AFMT_S16_MPD AFMT_S16_BE +#else +# define AFMT_S16_MPD AFMT_S16_LE +#endif /* WORDS_BIGENDIAN */ + +typedef struct _OssData { + int fd; + const char *device; + int channels; + int sampleRate; + int bitFormat; + int bits; + int *supported[3]; + int numSupported[3]; + int *unsupported[3]; + int numUnsupported[3]; +} OssData; + +#define OSS_SUPPORTED 1 +#define OSS_UNSUPPORTED 0 +#define OSS_UNKNOWN -1 + +#define OSS_RATE 0 +#define OSS_CHANNELS 1 +#define OSS_BITS 2 + +static int getIndexForParam(int param) +{ + int index = 0; + + switch (param) { + case SNDCTL_DSP_SPEED: + index = OSS_RATE; + break; + case SNDCTL_DSP_CHANNELS: + index = OSS_CHANNELS; + break; + case SNDCTL_DSP_SAMPLESIZE: + index = OSS_BITS; + break; + } + + return index; +} + +static int findSupportedParam(OssData * od, int param, int val) +{ + int i; + int index = getIndexForParam(param); + + for (i = 0; i < od->numSupported[index]; i++) { + if (od->supported[index][i] == val) + return 1; + } + + return 0; +} + +static int canConvert(int index, int val) +{ + switch (index) { + case OSS_BITS: + if (val != 16) + return 0; + break; + case OSS_CHANNELS: + if (val != 2) + return 0; + break; + } + + return 1; +} + +static int getSupportedParam(OssData * od, int param, int val) +{ + int i; + int index = getIndexForParam(param); + int ret = -1; + int least = val; + int diff; + + for (i = 0; i < od->numSupported[index]; i++) { + diff = od->supported[index][i] - val; + if (diff < 0) + diff = -diff; + if (diff < least) { + if (!canConvert(index, od->supported[index][i])) { + continue; + } + least = diff; + ret = od->supported[index][i]; + } + } + + return ret; +} + +static int findUnsupportedParam(OssData * od, int param, int val) +{ + int i; + int index = getIndexForParam(param); + + for (i = 0; i < od->numUnsupported[index]; i++) { + if (od->unsupported[index][i] == val) + return 1; + } + + return 0; +} + +static void addSupportedParam(OssData * od, int param, int val) +{ + int index = getIndexForParam(param); + + od->numSupported[index]++; + od->supported[index] = xrealloc(od->supported[index], + od->numSupported[index] * sizeof(int)); + od->supported[index][od->numSupported[index] - 1] = val; +} + +static void addUnsupportedParam(OssData * od, int param, int val) +{ + int index = getIndexForParam(param); + + od->numUnsupported[index]++; + od->unsupported[index] = xrealloc(od->unsupported[index], + od->numUnsupported[index] * + sizeof(int)); + od->unsupported[index][od->numUnsupported[index] - 1] = val; +} + +static void removeSupportedParam(OssData * od, int param, int val) +{ + int i = 0; + int j = 0; + int index = getIndexForParam(param); + + for (i = 0; i < od->numSupported[index] - 1; i++) { + if (od->supported[index][i] == val) + j = 1; + od->supported[index][i] = od->supported[index][i + j]; + } + + od->numSupported[index]--; + od->supported[index] = xrealloc(od->supported[index], + od->numSupported[index] * sizeof(int)); +} + +static void removeUnsupportedParam(OssData * od, int param, int val) +{ + int i = 0; + int j = 0; + int index = getIndexForParam(param); + + for (i = 0; i < od->numUnsupported[index] - 1; i++) { + if (od->unsupported[index][i] == val) + j = 1; + od->unsupported[index][i] = od->unsupported[index][i + j]; + } + + od->numUnsupported[index]--; + od->unsupported[index] = xrealloc(od->unsupported[index], + od->numUnsupported[index] * + sizeof(int)); +} + +static int isSupportedParam(OssData * od, int param, int val) +{ + if (findSupportedParam(od, param, val)) + return OSS_SUPPORTED; + if (findUnsupportedParam(od, param, val)) + return OSS_UNSUPPORTED; + return OSS_UNKNOWN; +} + +static void supportParam(OssData * od, int param, int val) +{ + int supported = isSupportedParam(od, param, val); + + if (supported == OSS_SUPPORTED) + return; + + if (supported == OSS_UNSUPPORTED) { + removeUnsupportedParam(od, param, val); + } + + addSupportedParam(od, param, val); +} + +static void unsupportParam(OssData * od, int param, int val) +{ + int supported = isSupportedParam(od, param, val); + + if (supported == OSS_UNSUPPORTED) + return; + + if (supported == OSS_SUPPORTED) { + removeSupportedParam(od, param, val); + } + + addUnsupportedParam(od, param, val); +} + +static OssData *newOssData(void) +{ + OssData *ret = xmalloc(sizeof(OssData)); + + ret->device = NULL; + ret->fd = -1; + + ret->supported[OSS_RATE] = NULL; + ret->supported[OSS_CHANNELS] = NULL; + ret->supported[OSS_BITS] = NULL; + ret->unsupported[OSS_RATE] = NULL; + ret->unsupported[OSS_CHANNELS] = NULL; + ret->unsupported[OSS_BITS] = NULL; + + ret->numSupported[OSS_RATE] = 0; + ret->numSupported[OSS_CHANNELS] = 0; + ret->numSupported[OSS_BITS] = 0; + ret->numUnsupported[OSS_RATE] = 0; + ret->numUnsupported[OSS_CHANNELS] = 0; + ret->numUnsupported[OSS_BITS] = 0; + + supportParam(ret, SNDCTL_DSP_SPEED, 48000); + supportParam(ret, SNDCTL_DSP_SPEED, 44100); + supportParam(ret, SNDCTL_DSP_CHANNELS, 2); + supportParam(ret, SNDCTL_DSP_SAMPLESIZE, 16); + + return ret; +} + +static void freeOssData(OssData * od) +{ + if (od->supported[OSS_RATE]) + free(od->supported[OSS_RATE]); + if (od->supported[OSS_CHANNELS]) + free(od->supported[OSS_CHANNELS]); + if (od->supported[OSS_BITS]) + free(od->supported[OSS_BITS]); + if (od->unsupported[OSS_RATE]) + free(od->unsupported[OSS_RATE]); + if (od->unsupported[OSS_CHANNELS]) + free(od->unsupported[OSS_CHANNELS]); + if (od->unsupported[OSS_BITS]) + free(od->unsupported[OSS_BITS]); + + free(od); +} + +#define OSS_STAT_NO_ERROR 0 +#define OSS_STAT_NOT_CHAR_DEV -1 +#define OSS_STAT_NO_PERMS -2 +#define OSS_STAT_DOESN_T_EXIST -3 +#define OSS_STAT_OTHER -4 + +static int oss_statDevice(const char *device, int *stErrno) +{ + struct stat st; + + if (0 == stat(device, &st)) { + if (!S_ISCHR(st.st_mode)) { + return OSS_STAT_NOT_CHAR_DEV; + } + } else { + *stErrno = errno; + + switch (errno) { + case ENOENT: + case ENOTDIR: + return OSS_STAT_DOESN_T_EXIST; + case EACCES: + return OSS_STAT_NO_PERMS; + default: + return OSS_STAT_OTHER; + } + } + + return 0; +} + +static const char *default_devices[] = { "/dev/sound/dsp", "/dev/dsp" }; + +static int oss_testDefault(void) +{ + int fd, i; + + for (i = ARRAY_SIZE(default_devices); --i >= 0; ) { + if ((fd = open(default_devices[i], O_WRONLY)) >= 0) { + xclose(fd); + return 0; + } + WARNING("Error opening OSS device \"%s\": %s\n", + default_devices[i], strerror(errno)); + } + + return -1; +} + +static int oss_open_default(AudioOutput *ao, ConfigParam *param, OssData *od) +{ + int i; + int err[ARRAY_SIZE(default_devices)]; + int ret[ARRAY_SIZE(default_devices)]; + + for (i = ARRAY_SIZE(default_devices); --i >= 0; ) { + ret[i] = oss_statDevice(default_devices[i], &err[i]); + if (ret[i] == 0) { + od->device = default_devices[i]; + return 0; + } + } + + if (param) + ERROR("error trying to open specified OSS device" + " at line %i\n", param->line); + else + ERROR("error trying to open default OSS device\n"); + + for (i = ARRAY_SIZE(default_devices); --i >= 0; ) { + const char *dev = default_devices[i]; + switch(ret[i]) { + case OSS_STAT_DOESN_T_EXIST: + ERROR("%s not found\n", dev); + break; + case OSS_STAT_NOT_CHAR_DEV: + ERROR("%s is not a character device\n", dev); + break; + case OSS_STAT_NO_PERMS: + ERROR("%s: permission denied\n", dev); + break; + default: + ERROR("Error accessing %s: %s", dev, strerror(err[i])); + } + } + exit(EXIT_FAILURE); + return 0; /* some compilers can be dumb... */ +} + +static int oss_initDriver(AudioOutput * audioOutput, ConfigParam * param) +{ + OssData *od = newOssData(); + audioOutput->data = od; + if (param) { + BlockParam *bp = getBlockParam(param, "device"); + if (bp) { + od->device = bp->value; + return 0; + } + } + return oss_open_default(audioOutput, param, od); +} + +static void oss_finishDriver(AudioOutput * audioOutput) +{ + OssData *od = audioOutput->data; + + freeOssData(od); +} + +static int setParam(OssData * od, int param, int *value) +{ + int val = *value; + int copy; + int supported = isSupportedParam(od, param, val); + + do { + if (supported == OSS_UNSUPPORTED) { + val = getSupportedParam(od, param, val); + if (copy < 0) + return -1; + } + copy = val; + if (ioctl(od->fd, param, ©)) { + unsupportParam(od, param, val); + supported = OSS_UNSUPPORTED; + } else { + if (supported == OSS_UNKNOWN) { + supportParam(od, param, val); + supported = OSS_SUPPORTED; + } + val = copy; + } + } while (supported == OSS_UNSUPPORTED); + + *value = val; + + return 0; +} + +static void oss_close(OssData * od) +{ + if (od->fd >= 0) + while (close(od->fd) && errno == EINTR) ; + od->fd = -1; +} + +static int oss_open(AudioOutput * audioOutput) +{ + int tmp; + OssData *od = audioOutput->data; + + if ((od->fd = open(od->device, O_WRONLY)) < 0) { + ERROR("Error opening OSS device \"%s\": %s\n", od->device, + strerror(errno)); + goto fail; + } + + if (setParam(od, SNDCTL_DSP_CHANNELS, &od->channels)) { + ERROR("OSS device \"%s\" does not support %i channels: %s\n", + od->device, od->channels, strerror(errno)); + goto fail; + } + + if (setParam(od, SNDCTL_DSP_SPEED, &od->sampleRate)) { + ERROR("OSS device \"%s\" does not support %i Hz audio: %s\n", + od->device, od->sampleRate, strerror(errno)); + goto fail; + } + + switch (od->bits) { + case 8: + tmp = AFMT_S8; + break; + case 16: + tmp = AFMT_S16_MPD; + } + + if (setParam(od, SNDCTL_DSP_SAMPLESIZE, &tmp)) { + ERROR("OSS device \"%s\" does not support %i bit audio: %s\n", + od->device, tmp, strerror(errno)); + goto fail; + } + + audioOutput->open = 1; + + return 0; + +fail: + oss_close(od); + audioOutput->open = 0; + return -1; +} + +static int oss_openDevice(AudioOutput * audioOutput) +{ + int ret = -1; + OssData *od = audioOutput->data; + AudioFormat *audioFormat = &audioOutput->outAudioFormat; + + od->channels = audioFormat->channels; + od->sampleRate = audioFormat->sampleRate; + od->bits = audioFormat->bits; + + if ((ret = oss_open(audioOutput)) < 0) + return ret; + + audioFormat->channels = od->channels; + audioFormat->sampleRate = od->sampleRate; + audioFormat->bits = od->bits; + + DEBUG("oss device \"%s\" will be playing %i bit %i channel audio at " + "%i Hz\n", od->device, od->bits, od->channels, od->sampleRate); + + return ret; +} + +static void oss_closeDevice(AudioOutput * audioOutput) +{ + OssData *od = audioOutput->data; + + oss_close(od); + + audioOutput->open = 0; +} + +static void oss_dropBufferedAudio(AudioOutput * audioOutput) +{ + OssData *od = audioOutput->data; + + if (od->fd >= 0) { + ioctl(od->fd, SNDCTL_DSP_RESET, 0); + oss_close(od); + } +} + +static int oss_playAudio(AudioOutput * audioOutput, char *playChunk, int size) +{ + OssData *od = audioOutput->data; + int ret; + + /* reopen the device since it was closed by dropBufferedAudio */ + if (od->fd < 0 && oss_open(audioOutput) < 0) + return -1; + + while (size > 0) { + ret = write(od->fd, playChunk, size); + if (ret < 0) { + if (errno == EINTR) + continue; + ERROR("closing oss device \"%s\" due to write error: " + "%s\n", od->device, strerror(errno)); + oss_closeDevice(audioOutput); + return -1; + } + playChunk += ret; + size -= ret; + } + + return 0; +} + +AudioOutputPlugin ossPlugin = { + "oss", + oss_testDefault, + oss_initDriver, + oss_finishDriver, + oss_openDevice, + oss_playAudio, + oss_dropBufferedAudio, + oss_closeDevice, + NULL, /* sendMetadataFunc */ +}; + +#else /* HAVE OSS */ + +DISABLED_AUDIO_OUTPUT_PLUGIN(ossPlugin) +#endif /* HAVE_OSS */ diff --git a/trunk/src/audioOutputs/audioOutput_osx.c b/trunk/src/audioOutputs/audioOutput_osx.c new file mode 100644 index 000000000..1caebade5 --- /dev/null +++ b/trunk/src/audioOutputs/audioOutput_osx.c @@ -0,0 +1,374 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "../audioOutput.h" + +#ifdef HAVE_OSX + +#include <AudioUnit/AudioUnit.h> +#include <stdlib.h> +#include <pthread.h> + +#include "../log.h" + +typedef struct _OsxData { + AudioUnit au; + pthread_mutex_t mutex; + pthread_cond_t condition; + char *buffer; + int bufferSize; + int pos; + int len; + int started; +} OsxData; + +static OsxData *newOsxData() +{ + OsxData *ret = xmalloc(sizeof(OsxData)); + + pthread_mutex_init(&ret->mutex, NULL); + pthread_cond_init(&ret->condition, NULL); + + ret->pos = 0; + ret->len = 0; + ret->started = 0; + ret->buffer = NULL; + ret->bufferSize = 0; + + return ret; +} + +static int osx_testDefault() +{ + /*AudioUnit au; + ComponentDescription desc; + Component comp; + + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = kAudioUnitSubType_Output; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + comp = FindNextComponent(NULL, &desc); + if(!comp) { + ERROR("Unable to open default OS X defice\n"); + return -1; + } + + if(OpenAComponent(comp, &au) != noErr) { + ERROR("Unable to open default OS X defice\n"); + return -1; + } + + CloseComponent(au); */ + + return 0; +} + +static int osx_initDriver(AudioOutput * audioOutput, ConfigParam * param) +{ + OsxData *od = newOsxData(); + + audioOutput->data = od; + + return 0; +} + +static void freeOsxData(OsxData * od) +{ + if (od->buffer) + free(od->buffer); + pthread_mutex_destroy(&od->mutex); + pthread_cond_destroy(&od->condition); + free(od); +} + +static void osx_finishDriver(AudioOutput * audioOutput) +{ + OsxData *od = (OsxData *) audioOutput->data; + freeOsxData(od); +} + +static void osx_dropBufferedAudio(AudioOutput * audioOutput) +{ + OsxData *od = (OsxData *) audioOutput->data; + + pthread_mutex_lock(&od->mutex); + od->len = 0; + pthread_mutex_unlock(&od->mutex); +} + +static void osx_closeDevice(AudioOutput * audioOutput) +{ + OsxData *od = (OsxData *) audioOutput->data; + + pthread_mutex_lock(&od->mutex); + while (od->len) { + pthread_cond_wait(&od->condition, &od->mutex); + } + pthread_mutex_unlock(&od->mutex); + + if (od->started) { + AudioOutputUnitStop(od->au); + od->started = 0; + } + + CloseComponent(od->au); + AudioUnitUninitialize(od->au); + + audioOutput->open = 0; +} + +static OSStatus osx_render(void *vdata, + AudioUnitRenderActionFlags * ioActionFlags, + const AudioTimeStamp * inTimeStamp, + UInt32 inBusNumber, UInt32 inNumberFrames, + AudioBufferList * bufferList) +{ + OsxData *od = (OsxData *) vdata; + AudioBuffer *buffer = &bufferList->mBuffers[0]; + int bufferSize = buffer->mDataByteSize; + int bytesToCopy; + int curpos = 0; + + /*DEBUG("osx_render: enter : %i\n", (int)bufferList->mNumberBuffers); + DEBUG("osx_render: ioActionFlags: %p\n", ioActionFlags); + if(ioActionFlags) { + if(*ioActionFlags & kAudioUnitRenderAction_PreRender) { + DEBUG("prerender\n"); + } + if(*ioActionFlags & kAudioUnitRenderAction_PostRender) { + DEBUG("post render\n"); + } + if(*ioActionFlags & kAudioUnitRenderAction_OutputIsSilence) { + DEBUG("post render\n"); + } + if(*ioActionFlags & kAudioOfflineUnitRenderAction_Preflight) { + DEBUG("prefilight\n"); + } + if(*ioActionFlags & kAudioOfflineUnitRenderAction_Render) { + DEBUG("render\n"); + } + if(*ioActionFlags & kAudioOfflineUnitRenderAction_Complete) { + DEBUG("complete\n"); + } + } */ + + /* while(bufferSize) { + DEBUG("osx_render: lock\n"); */ + pthread_mutex_lock(&od->mutex); + /* + DEBUG("%i:%i\n", bufferSize, od->len); + while(od->go && od->len < bufferSize && + od->len < od->bufferSize) + { + DEBUG("osx_render: wait\n"); + pthread_cond_wait(&od->condition, &od->mutex); + } + */ + + bytesToCopy = od->len < bufferSize ? od->len : bufferSize; + bufferSize = bytesToCopy; + od->len -= bytesToCopy; + + if (od->pos + bytesToCopy > od->bufferSize) { + int bytes = od->bufferSize - od->pos; + memcpy(buffer->mData + curpos, od->buffer + od->pos, bytes); + od->pos = 0; + curpos += bytes; + bytesToCopy -= bytes; + } + + memcpy(buffer->mData + curpos, od->buffer + od->pos, bytesToCopy); + od->pos += bytesToCopy; + curpos += bytesToCopy; + + if (od->pos >= od->bufferSize) + od->pos = 0; + /* DEBUG("osx_render: unlock\n"); */ + pthread_mutex_unlock(&od->mutex); + pthread_cond_signal(&od->condition); + /* } */ + + buffer->mDataByteSize = bufferSize; + + if (!bufferSize) { + my_usleep(1000); + } + + /* DEBUG("osx_render: leave\n"); */ + return 0; +} + +static int osx_openDevice(AudioOutput * audioOutput) +{ + OsxData *od = (OsxData *) audioOutput->data; + ComponentDescription desc; + Component comp; + AURenderCallbackStruct callback; + AudioFormat *audioFormat = &audioOutput->outAudioFormat; + AudioStreamBasicDescription streamDesc; + + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = kAudioUnitSubType_DefaultOutput; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + desc.componentFlags = 0; + desc.componentFlagsMask = 0; + + comp = FindNextComponent(NULL, &desc); + if (comp == 0) { + ERROR("Error finding OS X component\n"); + return -1; + } + + if (OpenAComponent(comp, &od->au) != noErr) { + ERROR("Unable to open OS X component\n"); + return -1; + } + + if (AudioUnitInitialize(od->au) != 0) { + CloseComponent(od->au); + ERROR("Unable to initialize OS X audio unit\n"); + return -1; + } + + callback.inputProc = osx_render; + callback.inputProcRefCon = od; + + if (AudioUnitSetProperty(od->au, kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, 0, + &callback, sizeof(callback)) != 0) { + AudioUnitUninitialize(od->au); + CloseComponent(od->au); + ERROR("unable to set callback for OS X audio unit\n"); + return -1; + } + + streamDesc.mSampleRate = audioFormat->sampleRate; + streamDesc.mFormatID = kAudioFormatLinearPCM; + streamDesc.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger; +#ifdef WORDS_BIGENDIAN + streamDesc.mFormatFlags |= kLinearPCMFormatFlagIsBigEndian; +#endif + + streamDesc.mBytesPerPacket = + audioFormat->channels * audioFormat->bits / 8; + streamDesc.mFramesPerPacket = 1; + streamDesc.mBytesPerFrame = streamDesc.mBytesPerPacket; + streamDesc.mChannelsPerFrame = audioFormat->channels; + streamDesc.mBitsPerChannel = audioFormat->bits; + + if (AudioUnitSetProperty(od->au, kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, 0, + &streamDesc, sizeof(streamDesc)) != 0) { + AudioUnitUninitialize(od->au); + CloseComponent(od->au); + ERROR("Unable to set format on OS X device\n"); + return -1; + } + + /* create a buffer of 1s */ + od->bufferSize = (audioFormat->sampleRate) * + (audioFormat->bits >> 3) * (audioFormat->channels); + od->buffer = xrealloc(od->buffer, od->bufferSize); + + od->pos = 0; + od->len = 0; + + audioOutput->open = 1; + + return 0; +} + +static int osx_play(AudioOutput * audioOutput, char *playChunk, int size) +{ + OsxData *od = (OsxData *) audioOutput->data; + int bytesToCopy; + int curpos; + + /* DEBUG("osx_play: enter\n"); */ + + if (!od->started) { + int err; + od->started = 1; + err = AudioOutputUnitStart(od->au); + if (err) { + ERROR("unable to start audio output: %i\n", err); + return -1; + } + } + + pthread_mutex_lock(&od->mutex); + + while (size) { + /* DEBUG("osx_play: lock\n"); */ + curpos = od->pos + od->len; + if (curpos >= od->bufferSize) + curpos -= od->bufferSize; + + bytesToCopy = od->bufferSize < size ? od->bufferSize : size; + + while (od->len > od->bufferSize - bytesToCopy) { + /* DEBUG("osx_play: wait\n"); */ + pthread_cond_wait(&od->condition, &od->mutex); + } + + bytesToCopy = od->bufferSize - od->len; + bytesToCopy = bytesToCopy < size ? bytesToCopy : size; + size -= bytesToCopy; + od->len += bytesToCopy; + + if (curpos + bytesToCopy > od->bufferSize) { + int bytes = od->bufferSize - curpos; + memcpy(od->buffer + curpos, playChunk, bytes); + curpos = 0; + playChunk += bytes; + bytesToCopy -= bytes; + } + + memcpy(od->buffer + curpos, playChunk, bytesToCopy); + curpos += bytesToCopy; + playChunk += bytesToCopy; + + } + /* DEBUG("osx_play: unlock\n"); */ + pthread_mutex_unlock(&od->mutex); + + /* DEBUG("osx_play: leave\n"); */ + return 0; +} + +AudioOutputPlugin osxPlugin = { + "osx", + osx_testDefault, + osx_initDriver, + osx_finishDriver, + osx_openDevice, + osx_play, + osx_dropBufferedAudio, + osx_closeDevice, + NULL, /* sendMetadataFunc */ +}; + +#else + +#include <stdio.h> + +DISABLED_AUDIO_OUTPUT_PLUGIN(osxPlugin) +#endif diff --git a/trunk/src/audioOutputs/audioOutput_pulse.c b/trunk/src/audioOutputs/audioOutput_pulse.c new file mode 100644 index 000000000..8948e0263 --- /dev/null +++ b/trunk/src/audioOutputs/audioOutput_pulse.c @@ -0,0 +1,221 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "../audioOutput.h" + +#include <stdlib.h> + +#ifdef HAVE_PULSE + +#include "../conf.h" +#include "../log.h" + +#include <string.h> +#include <time.h> + +#include <pulse/simple.h> +#include <pulse/error.h> + +#define MPD_PULSE_NAME "mpd" +#define CONN_ATTEMPT_INTERVAL 60 + +typedef struct _PulseData { + pa_simple *s; + char *server; + char *sink; + int connAttempts; + time_t lastAttempt; +} PulseData; + +static PulseData *newPulseData(void) +{ + PulseData *ret; + + ret = xmalloc(sizeof(PulseData)); + + ret->s = NULL; + ret->server = NULL; + ret->sink = NULL; + ret->connAttempts = 0; + ret->lastAttempt = 0; + + return ret; +} + +static void freePulseData(PulseData * pd) +{ + if (pd->server) + free(pd->server); + if (pd->sink) + free(pd->sink); + free(pd); +} + +static int pulse_initDriver(AudioOutput * audioOutput, ConfigParam * param) +{ + BlockParam *server = NULL; + BlockParam *sink = NULL; + PulseData *pd; + + if (param) { + server = getBlockParam(param, "server"); + sink = getBlockParam(param, "sink"); + } + + pd = newPulseData(); + pd->server = server ? xstrdup(server->value) : NULL; + pd->sink = sink ? xstrdup(sink->value) : NULL; + audioOutput->data = pd; + + return 0; +} + +static void pulse_finishDriver(AudioOutput * audioOutput) +{ + freePulseData((PulseData *) audioOutput->data); +} + +static int pulse_testDefault(void) +{ + pa_simple *s; + pa_sample_spec ss; + int error; + + ss.format = PA_SAMPLE_S16NE; + ss.rate = 44100; + ss.channels = 2; + + s = pa_simple_new(NULL, MPD_PULSE_NAME, PA_STREAM_PLAYBACK, NULL, + MPD_PULSE_NAME, &ss, NULL, NULL, &error); + if (!s) { + WARNING("Cannot connect to default PulseAudio server: %s\n", + pa_strerror(error)); + return -1; + } + + pa_simple_free(s); + + return 0; +} + +static int pulse_openDevice(AudioOutput * audioOutput) +{ + PulseData *pd; + AudioFormat *audioFormat; + pa_sample_spec ss; + time_t t; + int error; + + t = time(NULL); + pd = audioOutput->data; + audioFormat = &audioOutput->outAudioFormat; + + if (pd->connAttempts != 0 && + (t - pd->lastAttempt) < CONN_ATTEMPT_INTERVAL) + return -1; + + pd->connAttempts++; + pd->lastAttempt = t; + + if (audioFormat->bits != 16) { + ERROR("PulseAudio doesn't support %i bit audio\n", + audioFormat->bits); + return -1; + } + + ss.format = PA_SAMPLE_S16NE; + ss.rate = audioFormat->sampleRate; + ss.channels = audioFormat->channels; + + pd->s = pa_simple_new(pd->server, MPD_PULSE_NAME, PA_STREAM_PLAYBACK, + pd->sink, audioOutput->name, &ss, NULL, NULL, + &error); + if (!pd->s) { + ERROR("Cannot connect to server in PulseAudio output " + "\"%s\" (attempt %i): %s\n", audioOutput->name, + pd->connAttempts, pa_strerror(error)); + return -1; + } + + pd->connAttempts = 0; + audioOutput->open = 1; + + DEBUG("PulseAudio output \"%s\" connected and playing %i bit, %i " + "channel audio at %i Hz\n", audioOutput->name, audioFormat->bits, + audioFormat->channels, audioFormat->sampleRate); + + return 0; +} + +static void pulse_dropBufferedAudio(AudioOutput * audioOutput) +{ + PulseData *pd; + int error; + + pd = audioOutput->data; + if (pa_simple_flush(pd->s, &error) < 0) + WARNING("Flush failed in PulseAudio output \"%s\": %s\n", + audioOutput->name, pa_strerror(error)); +} + +static void pulse_closeDevice(AudioOutput * audioOutput) +{ + PulseData *pd; + + pd = audioOutput->data; + if (pd->s) { + pa_simple_drain(pd->s, NULL); + pa_simple_free(pd->s); + } + + audioOutput->open = 0; +} + +static int pulse_playAudio(AudioOutput * audioOutput, char *playChunk, int size) +{ + PulseData *pd; + int error; + + pd = audioOutput->data; + + if (pa_simple_write(pd->s, playChunk, size, &error) < 0) { + ERROR("PulseAudio output \"%s\" disconnecting due to write " + "error: %s\n", audioOutput->name, pa_strerror(error)); + pulse_closeDevice(audioOutput); + return -1; + } + + return 0; +} + +AudioOutputPlugin pulsePlugin = { + "pulse", + pulse_testDefault, + pulse_initDriver, + pulse_finishDriver, + pulse_openDevice, + pulse_playAudio, + pulse_dropBufferedAudio, + pulse_closeDevice, + NULL, /* sendMetadataFunc */ +}; + +#else /* HAVE_PULSE */ + +DISABLED_AUDIO_OUTPUT_PLUGIN(pulsePlugin) +#endif /* HAVE_PULSE */ diff --git a/trunk/src/audioOutputs/audioOutput_shout.c b/trunk/src/audioOutputs/audioOutput_shout.c new file mode 100644 index 000000000..7d93f8f85 --- /dev/null +++ b/trunk/src/audioOutputs/audioOutput_shout.c @@ -0,0 +1,636 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "../audioOutput.h" + +#include <stdlib.h> + +#ifdef HAVE_SHOUT + +#include "../conf.h" +#include "../log.h" +#include "../pcm_utils.h" + +#include <string.h> +#include <time.h> + +#include <shout/shout.h> +#include <vorbis/vorbisenc.h> + +#define CONN_ATTEMPT_INTERVAL 60 + +static int shoutInitCount; + +/* lots of this code blatantly stolent from bossogg/bossao2 */ + +typedef struct _ShoutData { + shout_t *shoutConn; + int shoutError; + + ogg_stream_state os; + ogg_page og; + ogg_packet op; + ogg_packet header_main; + ogg_packet header_comments; + ogg_packet header_codebooks; + + vorbis_dsp_state vd; + vorbis_block vb; + vorbis_info vi; + vorbis_comment vc; + + float quality; + int bitrate; + + int opened; + + MpdTag *tag; + int tagToSend; + + int connAttempts; + time_t lastAttempt; + int last_err; + + /* just a pointer to audioOutput->outAudioFormat */ + AudioFormat *audioFormat; +} ShoutData; + +static ShoutData *newShoutData(void) +{ + ShoutData *ret = xmalloc(sizeof(ShoutData)); + + ret->shoutConn = shout_new(); + ret->opened = 0; + ret->tag = NULL; + ret->tagToSend = 0; + ret->bitrate = -1; + ret->quality = -2.0; + ret->connAttempts = 0; + ret->lastAttempt = 0; + ret->audioFormat = NULL; + ret->last_err = SHOUTERR_UNCONNECTED; + + return ret; +} + +static void freeShoutData(ShoutData * sd) +{ + if (sd->shoutConn) + shout_free(sd->shoutConn); + if (sd->tag) + freeMpdTag(sd->tag); + + free(sd); +} + +#define checkBlockParam(name) { \ + blockParam = getBlockParam(param, name); \ + if (!blockParam) { \ + FATAL("no \"%s\" defined for shout device defined at line " \ + "%i\n", name, param->line); \ + } \ +} + +static int myShout_initDriver(AudioOutput * audioOutput, ConfigParam * param) +{ + ShoutData *sd; + char *test; + int port; + char *host; + char *mount; + char *passwd; + char *user; + char *name; + BlockParam *blockParam; + unsigned int public = 0; + + sd = newShoutData(); + + if (shoutInitCount == 0) + shout_init(); + + shoutInitCount++; + + checkBlockParam("host"); + host = blockParam->value; + + checkBlockParam("mount"); + mount = blockParam->value; + + checkBlockParam("port"); + + port = strtol(blockParam->value, &test, 10); + + if (*test != '\0' || port <= 0) { + FATAL("shout port \"%s\" is not a positive integer, line %i\n", + blockParam->value, blockParam->line); + } + + checkBlockParam("password"); + passwd = blockParam->value; + + checkBlockParam("name"); + name = blockParam->value; + + blockParam = getBlockParam(param, "public"); + if (blockParam) { + if (0 == strcmp(blockParam->value, "yes")) { + public = 1; + } else if (0 == strcmp(blockParam->value, "no")) { + public = 0; + } else { + FATAL("public \"%s\" is not \"yes\" or \"no\" at line " + "%i\n", param->value, param->line); + } + } + + blockParam = getBlockParam(param, "user"); + if (blockParam) + user = blockParam->value; + else + user = "source"; + + blockParam = getBlockParam(param, "quality"); + + if (blockParam) { + int line = blockParam->line; + + sd->quality = strtod(blockParam->value, &test); + + if (*test != '\0' || sd->quality < -1.0 || sd->quality > 10.0) { + FATAL("shout quality \"%s\" is not a number in the " + "range -1 to 10, line %i\n", blockParam->value, + blockParam->line); + } + + blockParam = getBlockParam(param, "bitrate"); + + if (blockParam) { + FATAL("quality (line %i) and bitrate (line %i) are " + "both defined for shout output\n", line, + blockParam->line); + } + } else { + blockParam = getBlockParam(param, "bitrate"); + + if (!blockParam) { + FATAL("neither bitrate nor quality defined for shout " + "output at line %i\n", param->line); + } + + sd->bitrate = strtol(blockParam->value, &test, 10); + + if (*test != '\0' || sd->bitrate <= 0) { + FATAL("bitrate at line %i should be a positive integer " + "\n", blockParam->line); + } + } + + checkBlockParam("format"); + sd->audioFormat = &audioOutput->outAudioFormat; + + if (shout_set_host(sd->shoutConn, host) != SHOUTERR_SUCCESS || + shout_set_port(sd->shoutConn, port) != SHOUTERR_SUCCESS || + shout_set_password(sd->shoutConn, passwd) != SHOUTERR_SUCCESS || + shout_set_mount(sd->shoutConn, mount) != SHOUTERR_SUCCESS || + shout_set_name(sd->shoutConn, name) != SHOUTERR_SUCCESS || + shout_set_user(sd->shoutConn, user) != SHOUTERR_SUCCESS || + shout_set_public(sd->shoutConn, public) != SHOUTERR_SUCCESS || + shout_set_nonblocking(sd->shoutConn, 1) != SHOUTERR_SUCCESS || + shout_set_format(sd->shoutConn, SHOUT_FORMAT_VORBIS) + != SHOUTERR_SUCCESS || + shout_set_protocol(sd->shoutConn, SHOUT_PROTOCOL_HTTP) + != SHOUTERR_SUCCESS || + shout_set_agent(sd->shoutConn, "MPD") != SHOUTERR_SUCCESS) { + FATAL("error configuring shout defined at line %i: %s\n", + param->line, shout_get_error(sd->shoutConn)); + } + + /* optional paramters */ + blockParam = getBlockParam(param, "genre"); + if (blockParam && shout_set_genre(sd->shoutConn, blockParam->value)) { + FATAL("error configuring shout defined at line %i: %s\n", + param->line, shout_get_error(sd->shoutConn)); + } + + blockParam = getBlockParam(param, "description"); + if (blockParam && shout_set_description(sd->shoutConn, + blockParam->value)) { + FATAL("error configuring shout defined at line %i: %s\n", + param->line, shout_get_error(sd->shoutConn)); + } + + { + char temp[11]; + memset(temp, 0, sizeof(temp)); + + snprintf(temp, sizeof(temp), "%d", sd->audioFormat->channels); + shout_set_audio_info(sd->shoutConn, SHOUT_AI_CHANNELS, temp); + + snprintf(temp, sizeof(temp), "%d", sd->audioFormat->sampleRate); + + shout_set_audio_info(sd->shoutConn, SHOUT_AI_SAMPLERATE, temp); + + if (sd->quality >= -1.0) { + snprintf(temp, sizeof(temp), "%2.2f", sd->quality); + shout_set_audio_info(sd->shoutConn, SHOUT_AI_QUALITY, + temp); + } else { + snprintf(temp, sizeof(temp), "%d", sd->bitrate); + shout_set_audio_info(sd->shoutConn, SHOUT_AI_BITRATE, + temp); + } + } + + audioOutput->data = sd; + + return 0; +} + +static int myShout_handleError(ShoutData * sd, int err) +{ + switch (err) { + case SHOUTERR_SUCCESS: + break; + case SHOUTERR_UNCONNECTED: + case SHOUTERR_SOCKET: + ERROR("Lost shout connection to %s:%i : %s\n", + shout_get_host(sd->shoutConn), + shout_get_port(sd->shoutConn), + shout_get_error(sd->shoutConn)); + sd->shoutError = 1; + return -1; + default: + ERROR("shout: connection to %s:%i error : %s\n", + shout_get_host(sd->shoutConn), + shout_get_port(sd->shoutConn), + shout_get_error(sd->shoutConn)); + sd->shoutError = 1; + return -1; + } + + return 0; +} + +static int write_page(ShoutData * sd) +{ + int err = 0; + + /*DEBUG("shout_delay: %i\n", shout_delay(sd->shoutConn)); */ + shout_sync(sd->shoutConn); + err = shout_send(sd->shoutConn, sd->og.header, sd->og.header_len); + if (myShout_handleError(sd, err) < 0) + return -1; + err = shout_send(sd->shoutConn, sd->og.body, sd->og.body_len); + if (myShout_handleError(sd, err) < 0) + return -1; + + return 0; +} + +static void finishEncoder(ShoutData * sd) +{ + vorbis_analysis_wrote(&sd->vd, 0); + + while (vorbis_analysis_blockout(&sd->vd, &sd->vb) == 1) { + vorbis_analysis(&sd->vb, NULL); + vorbis_bitrate_addblock(&sd->vb); + while (vorbis_bitrate_flushpacket(&sd->vd, &sd->op)) { + ogg_stream_packetin(&sd->os, &sd->op); + } + } +} + +static int flushEncoder(ShoutData * sd) +{ + return (ogg_stream_pageout(&sd->os, &sd->og) > 0); +} + +static void clearEncoder(ShoutData * sd) +{ + finishEncoder(sd); + while (1 == flushEncoder(sd)) { + if (!sd->shoutError) + write_page(sd); + } + + vorbis_comment_clear(&sd->vc); + ogg_stream_clear(&sd->os); + vorbis_block_clear(&sd->vb); + vorbis_dsp_clear(&sd->vd); + vorbis_info_clear(&sd->vi); +} + +static void myShout_closeShoutConn(ShoutData * sd) +{ + if (sd->opened) { + clearEncoder(sd); + + if (shout_close(sd->shoutConn) != SHOUTERR_SUCCESS) { + ERROR("problem closing connection to shout server: " + "%s\n", shout_get_error(sd->shoutConn)); + } + } + + sd->last_err = SHOUTERR_UNCONNECTED; + sd->opened = 0; +} + +static void myShout_finishDriver(AudioOutput * audioOutput) +{ + ShoutData *sd = (ShoutData *) audioOutput->data; + + myShout_closeShoutConn(sd); + + freeShoutData(sd); + + shoutInitCount--; + + if (shoutInitCount == 0) + shout_shutdown(); +} + +static void myShout_dropBufferedAudio(AudioOutput * audioOutput) +{ + /* needs to be implemented */ +} + +static void myShout_closeDevice(AudioOutput * audioOutput) +{ + ShoutData *sd = (ShoutData *) audioOutput->data; + + myShout_closeShoutConn(sd); + + audioOutput->open = 0; +} + +#define addTag(name, value) { \ + if(value) vorbis_comment_add_tag(&(sd->vc), name, value); \ +} + +static void copyTagToVorbisComment(ShoutData * sd) +{ + if (sd->tag) { + int i; + + for (i = 0; i < sd->tag->numOfItems; i++) { + switch (sd->tag->items[i].type) { + case TAG_ITEM_ARTIST: + addTag("ARTIST", sd->tag->items[i].value); + break; + case TAG_ITEM_ALBUM: + addTag("ALBUM", sd->tag->items[i].value); + break; + case TAG_ITEM_TITLE: + addTag("TITLE", sd->tag->items[i].value); + break; + } + } + } +} + +static int initEncoder(ShoutData * sd) +{ + vorbis_info_init(&(sd->vi)); + + if (sd->quality >= -1.0) { + if (0 != vorbis_encode_init_vbr(&(sd->vi), + sd->audioFormat->channels, + sd->audioFormat->sampleRate, + sd->quality * 0.1)) { + ERROR("problem setting up vorbis encoder for shout\n"); + vorbis_info_clear(&(sd->vi)); + return -1; + } + } else { + if (0 != vorbis_encode_init(&(sd->vi), + sd->audioFormat->channels, + sd->audioFormat->sampleRate, -1.0, + sd->bitrate * 1000, -1.0)) { + ERROR("problem setting up vorbis encoder for shout\n"); + vorbis_info_clear(&(sd->vi)); + return -1; + } + } + + vorbis_analysis_init(&(sd->vd), &(sd->vi)); + vorbis_block_init(&(sd->vd), &(sd->vb)); + + ogg_stream_init(&(sd->os), rand()); + + vorbis_comment_init(&(sd->vc)); + + return 0; +} + +static int myShout_openShoutConn(AudioOutput * audioOutput) +{ + ShoutData *sd = (ShoutData *) audioOutput->data; + time_t t = time(NULL); + + if (sd->connAttempts != 0 && + (t - sd->lastAttempt) < CONN_ATTEMPT_INTERVAL) { + return -1; + } + + sd->connAttempts++; + + if (sd->last_err == SHOUTERR_UNCONNECTED) + sd->last_err = shout_open(sd->shoutConn); + switch (sd->last_err) { + case SHOUTERR_SUCCESS: + case SHOUTERR_CONNECTED: + break; + case SHOUTERR_BUSY: + sd->last_err = shout_get_connected(sd->shoutConn); + if (sd->last_err == SHOUTERR_CONNECTED) + break; + return -1; + default: + sd->lastAttempt = t; + ERROR("problem opening connection to shout server %s:%i " + "(attempt %i): %s\n", + shout_get_host(sd->shoutConn), + shout_get_port(sd->shoutConn), + sd->connAttempts, shout_get_error(sd->shoutConn)); + return -1; + } + + if (initEncoder(sd) < 0) { + shout_close(sd->shoutConn); + return -1; + } + + sd->shoutError = 0; + + copyTagToVorbisComment(sd); + + vorbis_analysis_headerout(&(sd->vd), &(sd->vc), &(sd->header_main), + &(sd->header_comments), + &(sd->header_codebooks)); + + ogg_stream_packetin(&(sd->os), &(sd->header_main)); + ogg_stream_packetin(&(sd->os), &(sd->header_comments)); + ogg_stream_packetin(&(sd->os), &(sd->header_codebooks)); + + sd->opened = 1; + sd->tagToSend = 0; + + while (ogg_stream_flush(&(sd->os), &(sd->og))) { + if (write_page(sd) < 0) { + myShout_closeShoutConn(sd); + return -1; + } + } + + sd->connAttempts = 0; + + return 0; +} + +static int myShout_openDevice(AudioOutput * audioOutput) +{ + ShoutData *sd = (ShoutData *) audioOutput->data; + + audioOutput->open = 1; + + if (sd->opened) + return 0; + + if (myShout_openShoutConn(audioOutput) < 0) { + audioOutput->open = 0; + return -1; + } + + return 0; +} + +static void myShout_sendMetadata(ShoutData * sd) +{ + if (!sd->opened || !sd->tag) + return; + + clearEncoder(sd); + if (initEncoder(sd) < 0) + return; + + copyTagToVorbisComment(sd); + + vorbis_analysis_headerout(&(sd->vd), &(sd->vc), &(sd->header_main), + &(sd->header_comments), + &(sd->header_codebooks)); + + ogg_stream_packetin(&(sd->os), &(sd->header_main)); + ogg_stream_packetin(&(sd->os), &(sd->header_comments)); + ogg_stream_packetin(&(sd->os), &(sd->header_codebooks)); + + while (ogg_stream_flush(&(sd->os), &(sd->og))) { + if (write_page(sd) < 0) { + myShout_closeShoutConn(sd); + return; + } + } + + /*if(sd->tag) freeMpdTag(sd->tag); + sd->tag = NULL; */ + sd->tagToSend = 0; +} + +static int myShout_play(AudioOutput * audioOutput, char *playChunk, int size) +{ + int i, j; + ShoutData *sd = (ShoutData *) audioOutput->data; + float **vorbbuf; + int samples; + int bytes = sd->audioFormat->bits / 8; + + if (sd->opened && sd->tagToSend) + myShout_sendMetadata(sd); + + if (!sd->opened) { + if (myShout_openShoutConn(audioOutput) < 0) { + return -1; + } + } + + samples = size / (bytes * sd->audioFormat->channels); + + /* this is for only 16-bit audio */ + + vorbbuf = vorbis_analysis_buffer(&(sd->vd), samples); + + for (i = 0; i < samples; i++) { + for (j = 0; j < sd->audioFormat->channels; j++) { + vorbbuf[j][i] = (*((mpd_sint16 *) playChunk)) / 32768.0; + playChunk += bytes; + } + } + + vorbis_analysis_wrote(&(sd->vd), samples); + + while (1 == vorbis_analysis_blockout(&(sd->vd), &(sd->vb))) { + vorbis_analysis(&(sd->vb), NULL); + vorbis_bitrate_addblock(&(sd->vb)); + + while (vorbis_bitrate_flushpacket(&(sd->vd), &(sd->op))) { + ogg_stream_packetin(&(sd->os), &(sd->op)); + } + } + + while (ogg_stream_pageout(&(sd->os), &(sd->og)) != 0) { + if (write_page(sd) < 0) { + myShout_closeShoutConn(sd); + return -1; + } + } + + return 0; +} + +static void myShout_setTag(AudioOutput * audioOutput, MpdTag * tag) +{ + ShoutData *sd = (ShoutData *) audioOutput->data; + + if (sd->tag) + freeMpdTag(sd->tag); + sd->tag = NULL; + sd->tagToSend = 0; + + if (!tag) + return; + + sd->tag = mpdTagDup(tag); + sd->tagToSend = 1; +} + +AudioOutputPlugin shoutPlugin = { + "shout", + NULL, + myShout_initDriver, + myShout_finishDriver, + myShout_openDevice, + myShout_play, + myShout_dropBufferedAudio, + myShout_closeDevice, + myShout_setTag, +}; + +#else + +DISABLED_AUDIO_OUTPUT_PLUGIN(shoutPlugin) +#endif diff --git a/trunk/src/buffer2array.c b/trunk/src/buffer2array.c new file mode 100644 index 000000000..d7bfc4561 --- /dev/null +++ b/trunk/src/buffer2array.c @@ -0,0 +1,132 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "buffer2array.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> + + +static inline +int +isWhiteSpace(char c) +{ + return (c == ' ' || c == '\t'); +} + +int buffer2array(char *buffer, char *array[], const int max) +{ + int i = 0; + char *c = buffer; + + while (*c != '\0' && i < max) { + if (*c == '\"') { + array[i++] = ++c; + while (*c != '\0') { + if (*c == '\"') { + *(c++) = '\0'; + break; + } + else if (*(c++) == '\\' && *c != '\0') { + memmove(c - 1, c, strlen(c) + 1); + } + } + } else { + while (isWhiteSpace(*c)) + ++c; + array[i++] = c++; + if (*c == '\0') + return i; + while (!isWhiteSpace(*c) && *c != '\0') + ++c; + } + if (*c == '\0') + return i; + *(c++) = '\0'; + while (isWhiteSpace(*c)) + ++c; + } + return i; +} + +#ifdef UNIT_TEST + +#include <stdio.h> +#include <string.h> +#include <assert.h> + +int main() +{ + char *a[4] = { NULL }; + char *b; + int max; + + b = strdup("lsinfo \"/some/dir/name \\\"test\\\"\""); + max = buffer2array(b, a, 4); + assert( !strcmp("lsinfo", a[0]) ); + assert( !strcmp("/some/dir/name \"test\"", a[1]) ); + assert( !a[2] ); + + b = strdup("lsinfo \"/some/dir/name \\\"test\\\" something else\""); + max = buffer2array(b, a, 4); + assert( !strcmp("lsinfo", a[0]) ); + assert( !strcmp("/some/dir/name \"test\" something else", a[1]) ); + assert( !a[2] ); + + b = strdup("lsinfo \"/some/dir\\\\name\""); + max = buffer2array(b, a, 4); + assert( !strcmp("lsinfo", a[0]) ); + assert( !strcmp("/some/dir\\name", a[1]) ); + assert( !a[2] ); + + b = strdup("lsinfo \"/some/dir name\""); + max = buffer2array(b, a, 4); + assert( !strcmp("lsinfo", a[0]) ); + assert( !strcmp("/some/dir name", a[1]) ); + assert( !a[2] ); + + b = strdup("lsinfo \"\\\"/some/dir\\\"\""); + max = buffer2array(b, a, 4); + assert( !strcmp("lsinfo", a[0]) ); + assert( !strcmp("\"/some/dir\"", a[1]) ); + assert( !a[2] ); + + b = strdup("lsinfo \"\\\"/some/dir\\\" x\""); + max = buffer2array(b, a, 4); + assert( !strcmp("lsinfo", a[0]) ); + assert( !strcmp("\"/some/dir\" x", a[1]) ); + assert( !a[2] ); + + b = strdup("lsinfo \"single quote\\'d from php magicquotes\""); + max = buffer2array(b, a, 4); + assert( !strcmp("lsinfo", a[0]) ); + assert( !strcmp("single quote\'d from php magicquotes", a[1]) ); + assert( !a[2] ); + + b = strdup("lsinfo \"double quote\\\"d from php magicquotes\""); + max = buffer2array(b, a, 4); + assert( !strcmp("lsinfo", a[0]) ); + assert( !strcmp("double quote\"d from php magicquotes", a[1]) ); + assert( !a[2] ); + + return 0; +} + +#endif diff --git a/trunk/src/buffer2array.h b/trunk/src/buffer2array.h new file mode 100644 index 000000000..ece663994 --- /dev/null +++ b/trunk/src/buffer2array.h @@ -0,0 +1,32 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef BUFFER_2_ARRAY_H +#define BUFFER_2_ARRAY_H + +#include "../config.h" + +/* tokenizes up to max elements in buffer (a null-terminated string) and + * stores the result in array (which must be capable of holding up to + * max elements). Tokenization is based on C string quoting rules. + * The arguments buffer and array are modified. + * Returns the number of elements tokenized. + */ +int buffer2array(char *buffer, char *array[], const int max); + +#endif diff --git a/trunk/src/charConv.c b/trunk/src/charConv.c new file mode 100644 index 000000000..69777c47a --- /dev/null +++ b/trunk/src/charConv.c @@ -0,0 +1,168 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "charConv.h" +#include "mpd_types.h" +#include "utf8.h" +#include "utils.h" + +#include <stdlib.h> +#include <errno.h> +#include <string.h> + +#ifdef HAVE_ICONV +#include <iconv.h> +static iconv_t char_conv_iconv; +#endif + +static char *char_conv_to; +static char *char_conv_from; +static mpd_sint8 char_conv_same; +static mpd_sint8 char_conv_use_iconv; + +/* 1 is to use latin1ToUtf8 + 0 is not to use latin1/utf8 converter + -1 is to use utf8ToLatin1*/ +static mpd_sint8 char_conv_latin1ToUtf8; + +#define BUFFER_SIZE 1024 + +static void closeCharSetConversion(void); + +int setCharSetConversion(char *to, char *from) +{ + if (char_conv_to && char_conv_from) { + if (char_conv_latin1ToUtf8 && + !strcmp(from, char_conv_to) && + !strcmp(to, char_conv_from)) { + char *swap = char_conv_from; + char_conv_from = char_conv_to; + char_conv_to = swap; + char_conv_latin1ToUtf8 *= -1; + return 0; + } else if (!strcmp(to, char_conv_to) && + !strcmp(from,char_conv_from)) { + return 0; + } + } + + closeCharSetConversion(); + + if (0 == strcmp(to, from)) { + char_conv_same = 1; + char_conv_to = xstrdup(to); + char_conv_from = xstrdup(from); + return 0; + } + + if (strcmp(to, "UTF-8") == 0 && strcmp(from, "ISO-8859-1") == 0) { + char_conv_latin1ToUtf8 = 1; + } else if (strcmp(to, "ISO-8859-1") == 0 && strcmp(from, "UTF-8") == 0) { + char_conv_latin1ToUtf8 = -1; + } + + if (char_conv_latin1ToUtf8 != 0) { + char_conv_to = xstrdup(to); + char_conv_from = xstrdup(from); + return 0; + } +#ifdef HAVE_ICONV + if ((char_conv_iconv = iconv_open(to, from)) == (iconv_t) (-1)) + return -1; + + char_conv_to = xstrdup(to); + char_conv_from = xstrdup(from); + char_conv_use_iconv = 1; + + return 0; +#endif + + return -1; +} + +char *convStrDup(char *string) +{ + if (!char_conv_to) + return NULL; + + if (char_conv_same) + return xstrdup(string); + +#ifdef HAVE_ICONV + if (char_conv_use_iconv) { + char buffer[BUFFER_SIZE]; + size_t inleft = strlen(string); + char *ret; + size_t outleft; + size_t retlen = 0; + size_t err; + char *bufferPtr; + + ret = xmalloc(1); + ret[0] = '\0'; + + while (inleft) { + bufferPtr = buffer; + outleft = BUFFER_SIZE; + err = + iconv(char_conv_iconv, &string, &inleft, &bufferPtr, + &outleft); + if (outleft == BUFFER_SIZE + || (err == -1L && errno != E2BIG)) { + free(ret); + return NULL; + } + + ret = xrealloc(ret, retlen + BUFFER_SIZE - outleft + 1); + memcpy(ret + retlen, buffer, BUFFER_SIZE - outleft); + retlen += BUFFER_SIZE - outleft; + ret[retlen] = '\0'; + } + + return ret; + } +#endif + + switch (char_conv_latin1ToUtf8) { + case 1: + return latin1StrToUtf8Dup(string); + break; + case -1: + return utf8StrToLatin1Dup(string); + break; + } + + return NULL; +} + +static void closeCharSetConversion(void) +{ + if (char_conv_to) { +#ifdef HAVE_ICONV + if (char_conv_use_iconv) + iconv_close(char_conv_iconv); +#endif + free(char_conv_to); + free(char_conv_from); + char_conv_to = NULL; + char_conv_from = NULL; + char_conv_same = 0; + char_conv_latin1ToUtf8 = 0; + char_conv_use_iconv = 0; + } +} diff --git a/trunk/src/charConv.h b/trunk/src/charConv.h new file mode 100644 index 000000000..4b1ed4237 --- /dev/null +++ b/trunk/src/charConv.h @@ -0,0 +1,28 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef CHAR_CONV_H +#define CHAR_CONV_H + +#include "../config.h" + +int setCharSetConversion(char *to, char *from); + +char *convStrDup(char *string); + +#endif diff --git a/trunk/src/command.c b/trunk/src/command.c new file mode 100644 index 000000000..84a30db2b --- /dev/null +++ b/trunk/src/command.c @@ -0,0 +1,1299 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "command.h" +#include "player.h" +#include "playlist.h" +#include "ls.h" +#include "directory.h" +#include "volume.h" +#include "stats.h" +#include "myfprintf.h" +#include "list.h" +#include "permission.h" +#include "buffer2array.h" +#include "log.h" +#include "tag.h" +#include "utils.h" +#include "storedPlaylist.h" + +#include <assert.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define COMMAND_PLAY "play" +#define COMMAND_PLAYID "playid" +#define COMMAND_STOP "stop" +#define COMMAND_PAUSE "pause" +#define COMMAND_STATUS "status" +#define COMMAND_KILL "kill" +#define COMMAND_CLOSE "close" +#define COMMAND_ADD "add" +#define COMMAND_ADDID "addid" +#define COMMAND_DELETE "delete" +#define COMMAND_DELETEID "deleteid" +#define COMMAND_PLAYLIST "playlist" +#define COMMAND_SHUFFLE "shuffle" +#define COMMAND_CLEAR "clear" +#define COMMAND_SAVE "save" +#define COMMAND_LOAD "load" +#define COMMAND_LISTPLAYLIST "listplaylist" +#define COMMAND_LISTPLAYLISTINFO "listplaylistinfo" +#define COMMAND_LSINFO "lsinfo" +#define COMMAND_RM "rm" +#define COMMAND_PLAYLISTINFO "playlistinfo" +#define COMMAND_PLAYLISTID "playlistid" +#define COMMAND_FIND "find" +#define COMMAND_SEARCH "search" +#define COMMAND_UPDATE "update" +#define COMMAND_NEXT "next" +#define COMMAND_PREVIOUS "previous" +#define COMMAND_LISTALL "listall" +#define COMMAND_VOLUME "volume" +#define COMMAND_REPEAT "repeat" +#define COMMAND_RANDOM "random" +#define COMMAND_STATS "stats" +#define COMMAND_CLEAR_ERROR "clearerror" +#define COMMAND_LIST "list" +#define COMMAND_MOVE "move" +#define COMMAND_MOVEID "moveid" +#define COMMAND_SWAP "swap" +#define COMMAND_SWAPID "swapid" +#define COMMAND_SEEK "seek" +#define COMMAND_SEEKID "seekid" +#define COMMAND_LISTALLINFO "listallinfo" +#define COMMAND_PING "ping" +#define COMMAND_SETVOL "setvol" +#define COMMAND_PASSWORD "password" +#define COMMAND_CROSSFADE "crossfade" +#define COMMAND_URL_HANDLERS "urlhandlers" +#define COMMAND_PLCHANGES "plchanges" +#define COMMAND_PLCHANGESPOSID "plchangesposid" +#define COMMAND_CURRENTSONG "currentsong" +#define COMMAND_ENABLE_DEV "enableoutput" +#define COMMAND_DISABLE_DEV "disableoutput" +#define COMMAND_DEVICES "outputs" +#define COMMAND_COMMANDS "commands" +#define COMMAND_NOTCOMMANDS "notcommands" +#define COMMAND_PLAYLISTCLEAR "playlistclear" +#define COMMAND_PLAYLISTADD "playlistadd" +#define COMMAND_PLAYLISTFIND "playlistfind" +#define COMMAND_PLAYLISTSEARCH "playlistsearch" +#define COMMAND_PLAYLISTMOVE "playlistmove" +#define COMMAND_PLAYLISTDELETE "playlistdelete" +#define COMMAND_TAGTYPES "tagtypes" +#define COMMAND_COUNT "count" +#define COMMAND_RENAME "rename" + +#define COMMAND_STATUS_VOLUME "volume" +#define COMMAND_STATUS_STATE "state" +#define COMMAND_STATUS_REPEAT "repeat" +#define COMMAND_STATUS_RANDOM "random" +#define COMMAND_STATUS_PLAYLIST "playlist" +#define COMMAND_STATUS_PLAYLIST_LENGTH "playlistlength" +#define COMMAND_STATUS_SONG "song" +#define COMMAND_STATUS_SONGID "songid" +#define COMMAND_STATUS_TIME "time" +#define COMMAND_STATUS_BITRATE "bitrate" +#define COMMAND_STATUS_ERROR "error" +#define COMMAND_STATUS_CROSSFADE "xfade" +#define COMMAND_STATUS_AUDIO "audio" +#define COMMAND_STATUS_UPDATING_DB "updating_db" + +/* + * The most we ever use is for search/find, and that limits it to the + * number of tags we can have. Add one for the command, and one extra + * to catch errors clients may send us + */ +#define COMMAND_ARGV_MAX (2+(TAG_NUM_OF_ITEM_TYPES*2)) + +typedef struct _CommandEntry CommandEntry; + +typedef int (*CommandHandlerFunction) (int, int *, int, char **); +typedef int (*CommandListHandlerFunction) + (int, int *, int, char **, struct strnode *, CommandEntry *); + +/* if min: -1 don't check args * + * if max: -1 no max args */ +struct _CommandEntry { + char *cmd; + int min; + int max; + int reqPermission; + CommandHandlerFunction handler; + CommandListHandlerFunction listHandler; +}; + +static char *current_command; +static int command_listNum; + +static CommandEntry *getCommandEntryFromString(char *string, int *permission); + +static List *commandList; + +static CommandEntry *newCommandEntry(void) +{ + CommandEntry *cmd = xmalloc(sizeof(CommandEntry)); + cmd->cmd = NULL; + cmd->min = 0; + cmd->max = 0; + cmd->handler = NULL; + cmd->listHandler = NULL; + cmd->reqPermission = 0; + return cmd; +} + +static void addCommand(char *name, + int reqPermission, + int minargs, + int maxargs, + CommandHandlerFunction handler_func, + CommandListHandlerFunction listHandler_func) +{ + CommandEntry *cmd = newCommandEntry(); + cmd->cmd = name; + cmd->min = minargs; + cmd->max = maxargs; + cmd->handler = handler_func; + cmd->listHandler = listHandler_func; + cmd->reqPermission = reqPermission; + + insertInList(commandList, cmd->cmd, cmd); +} + +static int handleUrlHandlers(int fd, int *permission, int argc, char *argv[]) +{ + return printRemoteUrlHandlers(fd); +} + +static int handleTagTypes(int fd, int *permission, int argc, char *argv[]) +{ + printTagTypes(fd); + return 0; +} + +static int handlePlay(int fd, int *permission, int argc, char *argv[]) +{ + int song = -1; + char *test; + + if (argc == 2) { + song = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "need a positive integer"); + return -1; + } + } + return playPlaylist(fd, song, 0); +} + +static int handlePlayId(int fd, int *permission, int argc, char *argv[]) +{ + int id = -1; + char *test; + + if (argc == 2) { + id = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "need a positive integer"); + return -1; + } + } + return playPlaylistById(fd, id, 0); +} + +static int handleStop(int fd, int *permission, int argc, char *argv[]) +{ + return stopPlaylist(fd); +} + +static int handleCurrentSong(int fd, int *permission, int argc, char *argv[]) +{ + int song = getPlaylistCurrentSong(); + + if (song >= 0) { + return playlistInfo(fd, song); + } else + return 0; +} + +static int handlePause(int fd, int *permission, int argc, char *argv[]) +{ + if (argc == 2) { + char *test; + int pause = strtol(argv[1], &test, 10); + if (*test != '\0' || (pause != 0 && pause != 1)) { + commandError(fd, ACK_ERROR_ARG, "\"%s\" is not 0 or 1", + argv[1]); + return -1; + } + return playerSetPause(fd, pause); + } + return playerPause(fd); +} + +static int commandStatus(int fd, int *permission, int argc, char *argv[]) +{ + char *state = NULL; + int updateJobId; + int song; + + /*syncPlayerAndPlaylist(); */ + playPlaylistIfPlayerStopped(); + switch (getPlayerState()) { + case PLAYER_STATE_STOP: + state = COMMAND_STOP; + break; + case PLAYER_STATE_PAUSE: + state = COMMAND_PAUSE; + break; + case PLAYER_STATE_PLAY: + state = COMMAND_PLAY; + break; + } + + fdprintf(fd, "%s: %i\n", COMMAND_STATUS_VOLUME, getVolumeLevel()); + fdprintf(fd, "%s: %i\n", COMMAND_STATUS_REPEAT, + getPlaylistRepeatStatus()); + fdprintf(fd, "%s: %i\n", COMMAND_STATUS_RANDOM, + getPlaylistRandomStatus()); + fdprintf(fd, "%s: %li\n", COMMAND_STATUS_PLAYLIST, + getPlaylistVersion()); + fdprintf(fd, "%s: %i\n", COMMAND_STATUS_PLAYLIST_LENGTH, + getPlaylistLength()); + fdprintf(fd, "%s: %i\n", COMMAND_STATUS_CROSSFADE, + (int)(getPlayerCrossFade() + 0.5)); + + fdprintf(fd, "%s: %s\n", COMMAND_STATUS_STATE, state); + + song = getPlaylistCurrentSong(); + if (song >= 0) { + fdprintf(fd, "%s: %i\n", COMMAND_STATUS_SONG, song); + fdprintf(fd, "%s: %i\n", COMMAND_STATUS_SONGID, + getPlaylistSongId(song)); + } + if (getPlayerState() != PLAYER_STATE_STOP) { + fdprintf(fd, "%s: %i:%i\n", COMMAND_STATUS_TIME, + getPlayerElapsedTime(), getPlayerTotalTime()); + fdprintf(fd, "%s: %li\n", COMMAND_STATUS_BITRATE, + getPlayerBitRate()); + fdprintf(fd, "%s: %u:%i:%i\n", COMMAND_STATUS_AUDIO, + getPlayerSampleRate(), getPlayerBits(), + getPlayerChannels()); + } + + if ((updateJobId = isUpdatingDB())) { + fdprintf(fd, "%s: %i\n", COMMAND_STATUS_UPDATING_DB, + updateJobId); + } + + if (getPlayerError() != PLAYER_ERROR_NOERROR) { + fdprintf(fd, "%s: %s\n", COMMAND_STATUS_ERROR, + getPlayerErrorStr()); + } + + return 0; +} + +static int handleKill(int fd, int *permission, int argc, char *argv[]) +{ + return COMMAND_RETURN_KILL; +} + +static int handleClose(int fd, int *permission, int argc, char *argv[]) +{ + return COMMAND_RETURN_CLOSE; +} + +static int handleAdd(int fd, int *permission, int argc, char *argv[]) +{ + char *path = argv[1]; + + if (isRemoteUrl(path)) + return addToPlaylist(fd, path, 0); + + return addAllIn(fd, path); +} + +static int handleAddId(int fd, int *permission, int argc, char *argv[]) +{ + return addToPlaylist(fd, argv[1], 1); +} + +static int handleDelete(int fd, int *permission, int argc, char *argv[]) +{ + int song; + char *test; + + song = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, "need a positive integer"); + return -1; + } + return deleteFromPlaylist(fd, song); +} + +static int handleDeleteId(int fd, int *permission, int argc, char *argv[]) +{ + int id; + char *test; + + id = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, "need a positive integer"); + return -1; + } + return deleteFromPlaylistById(fd, id); +} + +static int handlePlaylist(int fd, int *permission, int argc, char *argv[]) +{ + return showPlaylist(fd); +} + +static int handleShuffle(int fd, int *permission, int argc, char *argv[]) +{ + return shufflePlaylist(fd); +} + +static int handleClear(int fd, int *permission, int argc, char *argv[]) +{ + return clearPlaylist(fd); +} + +static int handleSave(int fd, int *permission, int argc, char *argv[]) +{ + return savePlaylist(fd, argv[1]); +} + +static int handleLoad(int fd, int *permission, int argc, char *argv[]) +{ + return loadPlaylist(fd, argv[1]); +} + +static int handleListPlaylist(int fd, int *permission, int argc, char *argv[]) +{ + return PlaylistInfo(fd, argv[1], 0); +} + +static int handleListPlaylistInfo(int fd, int *permission, + int argc, char *argv[]) +{ + return PlaylistInfo(fd, argv[1], 1); +} + +static int handleLsInfo(int fd, int *permission, int argc, char *argv[]) +{ + char *path = ""; + + if (argc == 2) + path = argv[1]; + + if (printDirectoryInfo(fd, path) < 0) + return -1; + + if (isRootDirectory(path)) + return lsPlaylists(fd, path); + + return 0; +} + +static int handleRm(int fd, int *permission, int argc, char *argv[]) +{ + return deletePlaylist(fd, argv[1]); +} + +static int handleRename(int fd, int *permission, int argc, char *argv[]) +{ + return renameStoredPlaylist(fd, argv[1], argv[2]); +} + +static int handlePlaylistChanges(int fd, int *permission, + int argc, char *argv[]) +{ + unsigned long version; + char *test; + + version = strtoul(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, "need a positive integer"); + return -1; + } + return playlistChanges(fd, version); +} + +static int handlePlaylistChangesPosId(int fd, int *permission, + int argc, char *argv[]) +{ + unsigned long version; + char *test; + + version = strtoul(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, "need a positive integer"); + return -1; + } + return playlistChangesPosId(fd, version); +} + +static int handlePlaylistInfo(int fd, int *permission, int argc, char *argv[]) +{ + int song = -1; + char *test; + + if (argc == 2) { + song = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "need a positive integer"); + return -1; + } + } + return playlistInfo(fd, song); +} + +static int handlePlaylistId(int fd, int *permission, int argc, char *argv[]) +{ + int id = -1; + char *test; + + if (argc == 2) { + id = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "need a positive integer"); + return -1; + } + } + return playlistId(fd, id); +} + +static int handleFind(int fd, int *permission, int argc, char *argv[]) +{ + int ret; + + LocateTagItem *items; + int numItems = newLocateTagItemArrayFromArgArray(argv + 1, + argc - 1, + &items); + + if (numItems <= 0) { + commandError(fd, ACK_ERROR_ARG, "incorrect arguments"); + return -1; + } + + ret = findSongsIn(fd, NULL, numItems, items); + + freeLocateTagItemArray(numItems, items); + + return ret; +} + +static int handleSearch(int fd, int *permission, int argc, char *argv[]) +{ + int ret; + + LocateTagItem *items; + int numItems = newLocateTagItemArrayFromArgArray(argv + 1, + argc - 1, + &items); + + if (numItems <= 0) { + commandError(fd, ACK_ERROR_ARG, "incorrect arguments"); + return -1; + } + + ret = searchForSongsIn(fd, NULL, numItems, items); + + freeLocateTagItemArray(numItems, items); + + return ret; +} + +static int handleCount(int fd, int *permission, int argc, char *argv[]) +{ + int ret; + + LocateTagItem *items; + int numItems = newLocateTagItemArrayFromArgArray(argv + 1, + argc - 1, + &items); + + if (numItems <= 0) { + commandError(fd, ACK_ERROR_ARG, "incorrect arguments"); + return -1; + } + + ret = searchStatsForSongsIn(fd, NULL, numItems, items); + + freeLocateTagItemArray(numItems, items); + + return ret; +} + +static int handlePlaylistFind(int fd, int *permission, int argc, char *argv[]) +{ + LocateTagItem *items; + int numItems = newLocateTagItemArrayFromArgArray(argv + 1, + argc - 1, + &items); + + if (numItems <= 0) { + commandError(fd, ACK_ERROR_ARG, "incorrect arguments"); + return -1; + } + + findSongsInPlaylist(fd, numItems, items); + + freeLocateTagItemArray(numItems, items); + + return 0; +} + +static int handlePlaylistSearch(int fd, int *permission, int argc, char *argv[]) +{ + LocateTagItem *items; + int numItems = newLocateTagItemArrayFromArgArray(argv + 1, + argc - 1, + &items); + + if (numItems <= 0) { + commandError(fd, ACK_ERROR_ARG, "incorrect arguments"); + return -1; + } + + searchForSongsInPlaylist(fd, numItems, items); + + freeLocateTagItemArray(numItems, items); + + return 0; +} + +static int handlePlaylistDelete(int fd, int *permission, int argc, char *argv[]) { + char *playlist = argv[1]; + int from; + char *test; + + from = strtol(argv[2], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[2]); + return -1; + } + + return removeOneSongFromStoredPlaylistByPath(fd, playlist, from); +} + +static int handlePlaylistMove(int fd, int *permission, int argc, char *argv[]) +{ + char *playlist = argv[1]; + int from, to; + char *test; + + from = strtol(argv[2], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[2]); + return -1; + } + to = strtol(argv[3], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[3]); + return -1; + } + + return moveSongInStoredPlaylistByPath(fd, playlist, from, to); +} + +static int listHandleUpdate(int fd, + int *permission, + int argc, + char *argv[], + struct strnode *cmdnode, CommandEntry * cmd) +{ + static List *pathList; + CommandEntry *nextCmd = NULL; + struct strnode *next = cmdnode->next; + + if (!pathList) + pathList = makeList(NULL, 1); + + if (argc == 2) + insertInList(pathList, argv[1], NULL); + else + insertInList(pathList, "", NULL); + + if (next) + nextCmd = getCommandEntryFromString(next->data, permission); + + if (cmd != nextCmd) { + int ret = updateInit(fd, pathList); + freeList(pathList); + pathList = NULL; + return ret; + } + + return 0; +} + +static int handleUpdate(int fd, int *permission, int argc, char *argv[]) +{ + if (argc == 2) { + int ret; + List *pathList = makeList(NULL, 1); + insertInList(pathList, argv[1], NULL); + ret = updateInit(fd, pathList); + freeList(pathList); + return ret; + } + return updateInit(fd, NULL); +} + +static int handleNext(int fd, int *permission, int argc, char *argv[]) +{ + return nextSongInPlaylist(fd); +} + +static int handlePrevious(int fd, int *permission, int argc, char *argv[]) +{ + return previousSongInPlaylist(fd); +} + +static int handleListAll(int fd, int *permission, int argc, char *argv[]) +{ + char *directory = NULL; + + if (argc == 2) + directory = argv[1]; + return printAllIn(fd, directory); +} + +static int handleVolume(int fd, int *permission, int argc, char *argv[]) +{ + int change; + char *test; + + change = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, "need an integer"); + return -1; + } + return changeVolumeLevel(fd, change, 1); +} + +static int handleSetVol(int fd, int *permission, int argc, char *argv[]) +{ + int level; + char *test; + + level = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, "need an integer"); + return -1; + } + return changeVolumeLevel(fd, level, 0); +} + +static int handleRepeat(int fd, int *permission, int argc, char *argv[]) +{ + int status; + char *test; + + status = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, "need an integer"); + return -1; + } + return setPlaylistRepeatStatus(fd, status); +} + +static int handleRandom(int fd, int *permission, int argc, char *argv[]) +{ + int status; + char *test; + + status = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, "need an integer"); + return -1; + } + return setPlaylistRandomStatus(fd, status); +} + +static int handleStats(int fd, int *permission, int argc, char *argv[]) +{ + return printStats(fd); +} + +static int handleClearError(int fd, int *permission, int argc, char *argv[]) +{ + clearPlayerError(); + return 0; +} + +static int handleList(int fd, int *permission, int argc, char *argv[]) +{ + int numConditionals = 0; + LocateTagItem *conditionals = NULL; + int tagType = getLocateTagItemType(argv[1]); + int ret; + + if (tagType < 0) { + commandError(fd, ACK_ERROR_ARG, "\"%s\" is not known", argv[1]); + return -1; + } + + if (tagType == LOCATE_TAG_ANY_TYPE) { + commandError(fd, ACK_ERROR_ARG, + "\"any\" is not a valid return tag type"); + return -1; + } + + /* for compatibility with < 0.12.0 */ + if (argc == 3) { + if (tagType != TAG_ITEM_ALBUM) { + commandError(fd, ACK_ERROR_ARG, + "should be \"%s\" for 3 arguments", + mpdTagItemKeys[TAG_ITEM_ALBUM]); + return -1; + } + conditionals = newLocateTagItem(mpdTagItemKeys[TAG_ITEM_ARTIST], + argv[2]); + numConditionals = 1; + } else { + numConditionals = + newLocateTagItemArrayFromArgArray(argv + 2, + argc - 2, &conditionals); + + if (numConditionals < 0) { + commandError(fd, ACK_ERROR_ARG, + "not able to parse args"); + return -1; + } + } + + ret = listAllUniqueTags(fd, tagType, numConditionals, conditionals); + + if (conditionals) + freeLocateTagItemArray(numConditionals, conditionals); + + return ret; +} + +static int handleMove(int fd, int *permission, int argc, char *argv[]) +{ + int from; + int to; + char *test; + + from = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[1]); + return -1; + } + to = strtol(argv[2], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[2]); + return -1; + } + return moveSongInPlaylist(fd, from, to); +} + +static int handleMoveId(int fd, int *permission, int argc, char *argv[]) +{ + int id; + int to; + char *test; + + id = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[1]); + return -1; + } + to = strtol(argv[2], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[2]); + return -1; + } + return moveSongInPlaylistById(fd, id, to); +} + +static int handleSwap(int fd, int *permission, int argc, char *argv[]) +{ + int song1; + int song2; + char *test; + + song1 = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[1]); + return -1; + } + song2 = strtol(argv[2], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, "\"%s\" is not a integer", + argv[2]); + return -1; + } + return swapSongsInPlaylist(fd, song1, song2); +} + +static int handleSwapId(int fd, int *permission, int argc, char *argv[]) +{ + int id1; + int id2; + char *test; + + id1 = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[1]); + return -1; + } + id2 = strtol(argv[2], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, "\"%s\" is not a integer", + argv[2]); + return -1; + } + return swapSongsInPlaylistById(fd, id1, id2); +} + +static int handleSeek(int fd, int *permission, int argc, char *argv[]) +{ + int song; + int time; + char *test; + + song = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[1]); + return -1; + } + time = strtol(argv[2], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[2]); + return -1; + } + return seekSongInPlaylist(fd, song, time); +} + +static int handleSeekId(int fd, int *permission, int argc, char *argv[]) +{ + int id; + int time; + char *test; + + id = strtol(argv[1], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[1]); + return -1; + } + time = strtol(argv[2], &test, 10); + if (*test != '\0') { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer", argv[2]); + return -1; + } + return seekSongInPlaylistById(fd, id, time); +} + +static int handleListAllInfo(int fd, int *permission, int argc, char *argv[]) +{ + char *directory = NULL; + + if (argc == 2) + directory = argv[1]; + return printInfoForAllIn(fd, directory); +} + +static int handlePing(int fd, int *permission, int argc, char *argv[]) +{ + return 0; +} + +static int handlePassword(int fd, int *permission, int argc, char *argv[]) +{ + if (getPermissionFromPassword(argv[1], permission) < 0) { + commandError(fd, ACK_ERROR_PASSWORD, "incorrect password"); + return -1; + } + + return 0; +} + +static int handleCrossfade(int fd, int *permission, int argc, char *argv[]) +{ + int time; + char *test; + + time = strtol(argv[1], &test, 10); + if (*test != '\0' || time < 0) { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer >= 0", argv[1]); + return -1; + } + + setPlayerCrossFade(time); + + return 0; +} + +static int handleEnableDevice(int fd, int *permission, int argc, char *argv[]) +{ + int device; + char *test; + + device = strtol(argv[1], &test, 10); + if (*test != '\0' || device < 0) { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer >= 0", argv[1]); + return -1; + } + + return enableAudioDevice(fd, device); +} + +static int handleDisableDevice(int fd, int *permission, int argc, char *argv[]) +{ + int device; + char *test; + + device = strtol(argv[1], &test, 10); + if (*test != '\0' || device < 0) { + commandError(fd, ACK_ERROR_ARG, + "\"%s\" is not a integer >= 0", argv[1]); + return -1; + } + + return disableAudioDevice(fd, device); +} + +static int handleDevices(int fd, int *permission, int argc, char *argv[]) +{ + printAudioDevices(fd); + + return 0; +} + +/* don't be fooled, this is the command handler for "commands" command */ +static int handleCommands(int fd, int *permission, int argc, char *argv[]) +{ + ListNode *node = commandList->firstNode; + CommandEntry *cmd; + + while (node != NULL) { + cmd = (CommandEntry *) node->data; + if (cmd->reqPermission == (*permission & cmd->reqPermission)) { + fdprintf(fd, "command: %s\n", cmd->cmd); + } + + node = node->nextNode; + } + + return 0; +} + +static int handleNotcommands(int fd, int *permission, int argc, char *argv[]) +{ + ListNode *node = commandList->firstNode; + CommandEntry *cmd; + + while (node != NULL) { + cmd = (CommandEntry *) node->data; + + if (cmd->reqPermission != (*permission & cmd->reqPermission)) { + fdprintf(fd, "command: %s\n", cmd->cmd); + } + + node = node->nextNode; + } + + return 0; +} + +static int handlePlaylistClear(int fd, int *permission, int argc, char *argv[]) +{ + return clearStoredPlaylist(fd, argv[1]); +} + +static int handlePlaylistAdd(int fd, int *permission, int argc, char *argv[]) +{ + char *playlist = argv[1]; + char *path = argv[2]; + + if (isRemoteUrl(path)) + return addToStoredPlaylist(fd, path, playlist); + + return addAllInToStoredPlaylist(fd, path, playlist); +} + +void initCommands(void) +{ + commandList = makeList(free, 1); + + /* addCommand(name, permission, min, max, handler, list handler); */ + addCommand(COMMAND_PLAY, PERMISSION_CONTROL, 0, 1, handlePlay, NULL); + addCommand(COMMAND_PLAYID, PERMISSION_CONTROL, 0, 1, handlePlayId, NULL); + addCommand(COMMAND_STOP, PERMISSION_CONTROL, 0, 0, handleStop, NULL); + addCommand(COMMAND_CURRENTSONG, PERMISSION_READ, 0, 0, handleCurrentSong, NULL); + addCommand(COMMAND_PAUSE, PERMISSION_CONTROL, 0, 1, handlePause, NULL); + addCommand(COMMAND_STATUS, PERMISSION_READ, 0, 0, commandStatus, NULL); + addCommand(COMMAND_KILL, PERMISSION_ADMIN, -1, -1, handleKill, NULL); + addCommand(COMMAND_CLOSE, PERMISSION_NONE, -1, -1, handleClose, NULL); + addCommand(COMMAND_ADD, PERMISSION_ADD, 1, 1, handleAdd, NULL); + addCommand(COMMAND_ADDID, PERMISSION_ADD, 1, 1, handleAddId, NULL); + addCommand(COMMAND_DELETE, PERMISSION_CONTROL, 1, 1, handleDelete, NULL); + addCommand(COMMAND_DELETEID, PERMISSION_CONTROL, 1, 1, handleDeleteId, NULL); + addCommand(COMMAND_PLAYLIST, PERMISSION_READ, 0, 0, handlePlaylist, NULL); + addCommand(COMMAND_PLAYLISTID, PERMISSION_READ, 0, 1, handlePlaylistId, NULL); + addCommand(COMMAND_SHUFFLE, PERMISSION_CONTROL, 0, 0, handleShuffle, NULL); + addCommand(COMMAND_CLEAR, PERMISSION_CONTROL, 0, 0, handleClear, NULL); + addCommand(COMMAND_SAVE, PERMISSION_CONTROL, 1, 1, handleSave, NULL); + addCommand(COMMAND_LOAD, PERMISSION_ADD, 1, 1, handleLoad, NULL); + addCommand(COMMAND_LISTPLAYLIST, PERMISSION_READ, 1, 1, handleListPlaylist, NULL); + addCommand(COMMAND_LISTPLAYLISTINFO, PERMISSION_READ, 1, 1, handleListPlaylistInfo, NULL); + addCommand(COMMAND_LSINFO, PERMISSION_READ, 0, 1, handleLsInfo, NULL); + addCommand(COMMAND_RM, PERMISSION_CONTROL, 1, 1, handleRm, NULL); + addCommand(COMMAND_PLAYLISTINFO, PERMISSION_READ, 0, 1, handlePlaylistInfo, NULL); + addCommand(COMMAND_FIND, PERMISSION_READ, 2, -1, handleFind, NULL); + addCommand(COMMAND_SEARCH, PERMISSION_READ, 2, -1, handleSearch, NULL); + addCommand(COMMAND_UPDATE, PERMISSION_ADMIN, 0, 1, handleUpdate, listHandleUpdate); + addCommand(COMMAND_NEXT, PERMISSION_CONTROL, 0, 0, handleNext, NULL); + addCommand(COMMAND_PREVIOUS, PERMISSION_CONTROL, 0, 0, handlePrevious, NULL); + addCommand(COMMAND_LISTALL, PERMISSION_READ, 0, 1, handleListAll, NULL); + addCommand(COMMAND_VOLUME, PERMISSION_CONTROL, 1, 1, handleVolume, NULL); + addCommand(COMMAND_REPEAT, PERMISSION_CONTROL, 1, 1, handleRepeat, NULL); + addCommand(COMMAND_RANDOM, PERMISSION_CONTROL, 1, 1, handleRandom, NULL); + addCommand(COMMAND_STATS, PERMISSION_READ, 0, 0, handleStats, NULL); + addCommand(COMMAND_CLEAR_ERROR, PERMISSION_CONTROL, 0, 0, handleClearError, NULL); + addCommand(COMMAND_LIST, PERMISSION_READ, 1, -1, handleList, NULL); + addCommand(COMMAND_MOVE, PERMISSION_CONTROL, 2, 2, handleMove, NULL); + addCommand(COMMAND_MOVEID, PERMISSION_CONTROL, 2, 2, handleMoveId, NULL); + addCommand(COMMAND_SWAP, PERMISSION_CONTROL, 2, 2, handleSwap, NULL); + addCommand(COMMAND_SWAPID, PERMISSION_CONTROL, 2, 2, handleSwapId, NULL); + addCommand(COMMAND_SEEK, PERMISSION_CONTROL, 2, 2, handleSeek, NULL); + addCommand(COMMAND_SEEKID, PERMISSION_CONTROL, 2, 2, handleSeekId, NULL); + addCommand(COMMAND_LISTALLINFO, PERMISSION_READ, 0, 1, handleListAllInfo, NULL); + addCommand(COMMAND_PING, PERMISSION_NONE, 0, 0, handlePing, NULL); + addCommand(COMMAND_SETVOL, PERMISSION_CONTROL, 1, 1, handleSetVol, NULL); + addCommand(COMMAND_PASSWORD, PERMISSION_NONE, 1, 1, handlePassword, NULL); + addCommand(COMMAND_CROSSFADE, PERMISSION_CONTROL, 1, 1, handleCrossfade, NULL); + addCommand(COMMAND_URL_HANDLERS, PERMISSION_READ, 0, 0, handleUrlHandlers, NULL); + addCommand(COMMAND_PLCHANGES, PERMISSION_READ, 1, 1, handlePlaylistChanges, NULL); + addCommand(COMMAND_PLCHANGESPOSID, PERMISSION_READ, 1, 1, handlePlaylistChangesPosId, NULL); + addCommand(COMMAND_ENABLE_DEV, PERMISSION_ADMIN, 1, 1, handleEnableDevice, NULL); + addCommand(COMMAND_DISABLE_DEV, PERMISSION_ADMIN, 1, 1, handleDisableDevice, NULL); + addCommand(COMMAND_DEVICES, PERMISSION_READ, 0, 0, handleDevices, NULL); + addCommand(COMMAND_COMMANDS, PERMISSION_NONE, 0, 0, handleCommands, NULL); + addCommand(COMMAND_NOTCOMMANDS, PERMISSION_NONE, 0, 0, handleNotcommands, NULL); + addCommand(COMMAND_PLAYLISTCLEAR, PERMISSION_CONTROL, 1, 1, handlePlaylistClear, NULL); + addCommand(COMMAND_PLAYLISTADD, PERMISSION_CONTROL, 2, 2, handlePlaylistAdd, NULL); + addCommand(COMMAND_PLAYLISTFIND, PERMISSION_READ, 2, -1, handlePlaylistFind, NULL); + addCommand(COMMAND_PLAYLISTSEARCH, PERMISSION_READ, 2, -1, handlePlaylistSearch, NULL); + addCommand(COMMAND_PLAYLISTMOVE, PERMISSION_CONTROL, 3, 3, handlePlaylistMove, NULL); + addCommand(COMMAND_PLAYLISTDELETE, PERMISSION_CONTROL, 2, 2, handlePlaylistDelete, NULL); + addCommand(COMMAND_TAGTYPES, PERMISSION_READ, 0, 0, handleTagTypes, NULL); + addCommand(COMMAND_COUNT, PERMISSION_READ, 2, -1, handleCount, NULL); + addCommand(COMMAND_RENAME, PERMISSION_CONTROL, 2, 2, handleRename, NULL); + + sortList(commandList); +} + +void finishCommands(void) +{ + freeList(commandList); +} + +static int checkArgcAndPermission(CommandEntry * cmd, int fd, + int permission, int argc, char *argv[]) +{ + int min = cmd->min + 1; + int max = cmd->max + 1; + + if (cmd->reqPermission != (permission & cmd->reqPermission)) { + if (fd) { + commandError(fd, ACK_ERROR_PERMISSION, + "you don't have permission for \"%s\"", + cmd->cmd); + } + return -1; + } + + if (min == 0) + return 0; + + if (min == max && max != argc) { + if (fd) { + commandError(fd, ACK_ERROR_ARG, + "wrong number of arguments for \"%s\"", + argv[0]); + } + return -1; + } else if (argc < min) { + if (fd) { + commandError(fd, ACK_ERROR_ARG, + "too few arguments for \"%s\"", argv[0]); + } + return -1; + } else if (argc > max && max /* != 0 */ ) { + if (fd) { + commandError(fd, ACK_ERROR_ARG, + "too many arguments for \"%s\"", argv[0]); + } + return -1; + } else + return 0; +} + +static CommandEntry *getCommandEntryAndCheckArgcAndPermission(int fd, + int *permission, + int argc, + char *argv[]) +{ + static char unknown[] = ""; + CommandEntry *cmd; + + current_command = unknown; + + if (argc == 0) + return NULL; + + if (!findInList(commandList, argv[0], (void *)&cmd)) { + if (fd) { + commandError(fd, ACK_ERROR_UNKNOWN, + "unknown command \"%s\"", argv[0]); + } + return NULL; + } + + current_command = cmd->cmd; + + if (checkArgcAndPermission(cmd, fd, *permission, argc, argv) < 0) { + return NULL; + } + + return cmd; +} + +static CommandEntry *getCommandEntryFromString(char *string, int *permission) +{ + CommandEntry *cmd = NULL; + char *argv[COMMAND_ARGV_MAX] = { NULL }; + int argc = buffer2array(string, argv, COMMAND_ARGV_MAX); + + if (0 == argc) + return NULL; + + cmd = getCommandEntryAndCheckArgcAndPermission(0, permission, + argc, argv); + + return cmd; +} + +static int processCommandInternal(int fd, int *permission, + char *commandString, struct strnode *cmdnode) +{ + int argc; + char *argv[COMMAND_ARGV_MAX] = { NULL }; + CommandEntry *cmd; + int ret = -1; + + argc = buffer2array(commandString, argv, COMMAND_ARGV_MAX); + + if (argc == 0) + return 0; + + if ((cmd = getCommandEntryAndCheckArgcAndPermission(fd, permission, + argc, argv))) { + if (!cmdnode || !cmd->listHandler) { + ret = cmd->handler(fd, permission, argc, argv); + } else { + ret = cmd->listHandler(fd, permission, argc, argv, + cmdnode, cmd); + } + } + + current_command = NULL; + + return ret; +} + +int processListOfCommands(int fd, int *permission, int *expired, + int listOK, struct strnode *list) +{ + struct strnode *cur = list; + int ret = 0; + + command_listNum = 0; + + while (cur) { + DEBUG("processListOfCommands: process command \"%s\"\n", + cur->data); + ret = processCommandInternal(fd, permission, cur->data, cur); + DEBUG("processListOfCommands: command returned %i\n", ret); + if (ret != 0 || (*expired) != 0) + goto out; + else if (listOK) + fdprintf(fd, "list_OK\n"); + command_listNum++; + cur = cur->next; + } +out: + command_listNum = 0; + return ret; +} + +int processCommand(int fd, int *permission, char *commandString) +{ + return processCommandInternal(fd, permission, commandString, NULL); +} + +mpd_fprintf_ void commandError(int fd, int error, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + + if (current_command && fd != STDERR_FILENO) { + fdprintf(fd, "ACK [%i@%i] {%s} ", + (int)error, command_listNum, current_command); + vfdprintf(fd, fmt, args); + fdprintf(fd, "\n"); + current_command = NULL; + } else { + fdprintf(STDERR_FILENO, "ACK [%i@%i] ", + (int)error, command_listNum); + vfdprintf(STDERR_FILENO, fmt, args); + fdprintf(STDERR_FILENO, "\n"); + } + + va_end(args); +} diff --git a/trunk/src/command.h b/trunk/src/command.h new file mode 100644 index 000000000..a560b9484 --- /dev/null +++ b/trunk/src/command.h @@ -0,0 +1,50 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef COMMAND_H +#define COMMAND_H + +#include "../config.h" + +#include "list.h" +#include "myfprintf.h" +#include "log.h" +#include "ack.h" +#include "sllist.h" + +#include <unistd.h> +#include <stdio.h> + +#define COMMAND_RETURN_KILL 10 +#define COMMAND_RETURN_CLOSE 20 +#define COMMAND_MASTER_READY 30 + +int processListOfCommands(int fd, int *permission, int *expired, + int listOK, struct strnode *list); + +int processCommand(int fd, int *permission, char *commandString); + +void initCommands(void); + +void finishCommands(void); + +#define commandSuccess(fd) fdprintf(fd, "OK\n") + +mpd_fprintf_ void commandError(int fd, int error, const char *fmt, ...); + +#endif diff --git a/trunk/src/compress.c b/trunk/src/compress.c new file mode 100644 index 000000000..d8db7ab64 --- /dev/null +++ b/trunk/src/compress.c @@ -0,0 +1,411 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * Compressor logic by + * (c)2003-6 fluffy@beesbuzz.biz + * + * 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 + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> + +#include "compress.h" +#include "utils.h" + +#ifdef USE_X +#include <X11/Xlib.h> +#include <X11/Xutil.h> + +static Display *display; +static Window window; +static Visual *visual; +static int screen; +static GC blackGC, whiteGC, blueGC, yellowGC, dkyellowGC, redGC; +#endif + +static int *peaks; +static int gainCurrent, gainTarget; + +static struct { + int show_mon; + int anticlip; + int target; + int gainmax; + int gainsmooth; + int buckets; +} prefs; + +#ifdef USE_X +static int mon_init; +#endif + +void CompressCfg(int show_mon, int anticlip, int target, int gainmax, + int gainsmooth, int buckets) +{ + static int lastsize; + + prefs.show_mon = show_mon; + prefs.anticlip = anticlip; + prefs.target = target; + prefs.gainmax = gainmax; + prefs.gainsmooth = gainsmooth; + prefs.buckets = buckets; + + /* Allocate the peak structure */ + peaks = xrealloc(peaks, sizeof(int)*prefs.buckets); + + if (prefs.buckets > lastsize) + memset(peaks + lastsize, 0, sizeof(int)*(prefs.buckets + - lastsize)); + lastsize = prefs.buckets; + +#ifdef USE_X + /* Configure the monitor window if needed */ + if (show_mon && !mon_init) + { + display = XOpenDisplay(getenv("DISPLAY")); + + /* We really shouldn't try to init X if there's no X */ + if (!display) + { + fprintf(stderr, + "X not detected; disabling monitor window\n"); + show_mon = prefs.show_mon = 0; + } + } + + if (show_mon && !mon_init) + { + XGCValues gcv; + XColor col; + + gainCurrent = gainTarget = (1 << GAINSHIFT); + + + + screen = DefaultScreen(display); + visual = DefaultVisual(display, screen); + window = XCreateSimpleWindow(display, + RootWindow(display, screen), + 0, 0, prefs.buckets, 128 + 8, 0, + BlackPixel(display, screen), + WhitePixel(display, screen)); + XStoreName(display, window, "AudioCompress monitor"); + + gcv.foreground = BlackPixel(display, screen); + blackGC = XCreateGC(display, window, GCForeground, &gcv); + gcv.foreground = WhitePixel(display, screen); + whiteGC = XCreateGC(display, window, GCForeground, &gcv); + col.red = 0; + col.green = 0; + col.blue = 65535; + XAllocColor(display, DefaultColormap(display, screen), &col); + gcv.foreground = col.pixel; + blueGC = XCreateGC(display, window, GCForeground, &gcv); + col.red = 65535; + col.green = 65535; + col.blue = 0; + XAllocColor(display, DefaultColormap(display, screen), &col); + gcv.foreground = col.pixel; + yellowGC = XCreateGC(display, window, GCForeground, &gcv); + col.red = 32767; + col.green = 32767; + col.blue = 0; + XAllocColor(display, DefaultColormap(display, screen), &col); + gcv.foreground = col.pixel; + dkyellowGC = XCreateGC(display, window, GCForeground, &gcv); + col.red = 65535; + col.green = 0; + col.blue = 0; + XAllocColor(display, DefaultColormap(display, screen), &col); + gcv.foreground = col.pixel; + redGC = XCreateGC(display, window, GCForeground, &gcv); + mon_init = 1; + } + + if (mon_init) + { + if (show_mon) + XMapWindow(display, window); + else + XUnmapWindow(display, window); + XResizeWindow(display, window, prefs.buckets, 128 + 8); + XFlush(display); + } +#endif +} + +void CompressFree(void) +{ +#ifdef USE_X + if (mon_init) + { + XFreeGC(display, blackGC); + XFreeGC(display, whiteGC); + XFreeGC(display, blueGC); + XFreeGC(display, yellowGC); + XFreeGC(display, dkyellowGC); + XFreeGC(display, redGC); + XDestroyWindow(display, window); + XCloseDisplay(display); + } +#endif + + if (peaks) + free(peaks); +} + +void CompressDo(void *data, unsigned int length) +{ + int16_t *audio = (int16_t *)data, *ap; + int peak, pos; + int i; + int gr, gf, gn; + static int pn = -1; +#ifdef STATS + static int clip; +#endif + static int clipped; + + if (!peaks) + return; + + if (pn == -1) + { + for (i = 0; i < prefs.buckets; i++) + peaks[i] = 0; + } + pn = (pn + 1)%prefs.buckets; + +#ifdef DEBUG + fprintf(stderr, "modifyNative16(0x%08x, %d)\n",(unsigned int)data, + length); +#endif + + /* Determine peak's value and position */ + peak = 1; + pos = 0; + +#ifdef DEBUG + fprintf(stderr, "finding peak(b=%d)\n", pn); +#endif + + ap = audio; + for (i = 0; i < length/2; i++) + { + int val = *ap; + if (val > peak) + { + peak = val; + pos = i; + } else if (-val > peak) + { + peak = -val; + pos = i; + } + ap++; + } + peaks[pn] = peak; + + /* Only draw if needed, of course */ +#ifdef USE_X + if (prefs.show_mon) + { + /* current amplitude */ + XDrawLine(display, window, whiteGC, + pn, 0, + pn, + 127 - + (peaks[pn]*gainCurrent >> (GAINSHIFT + 8))); + + /* amplification */ + XDrawLine(display, window, yellowGC, + pn, + 127 - (peaks[pn]*gainCurrent + >> (GAINSHIFT + 8)), + pn, 127); + + /* peak */ + XDrawLine(display, window, blackGC, + pn, 127 - (peaks[pn] >> 8), pn, 127); + + /* clip indicator */ + if (clipped) + XDrawLine(display, window, redGC, + (pn + prefs.buckets - 1)%prefs.buckets, + 126 - clipped/(length*512), + (pn + prefs.buckets - 1)%prefs.buckets, + 127); + clipped = 0; + + /* target line */ + /* XDrawPoint(display, window, redGC, */ + /* pn, 127 - TARGET/256); */ + /* amplification edge */ + XDrawLine(display, window, dkyellowGC, + pn, + 127 - (peaks[pn]*gainCurrent + >> (GAINSHIFT + 8)), + pn - 1, + 127 - + (peaks[(pn + prefs.buckets + - 1)%prefs.buckets]*gainCurrent + >> (GAINSHIFT + 8))); + } +#endif + + for (i = 0; i < prefs.buckets; i++) + { + if (peaks[i] > peak) + { + peak = peaks[i]; + pos = 0; + } + } + + /* Determine target gain */ + gn = (1 << GAINSHIFT)*prefs.target/peak; + + if (gn <(1 << GAINSHIFT)) + gn = 1 << GAINSHIFT; + + gainTarget = (gainTarget *((1 << prefs.gainsmooth) - 1) + gn) + >> prefs.gainsmooth; + + /* Give it an extra insignifigant nudge to counteract possible + ** rounding error + */ + + if (gn < gainTarget) + gainTarget--; + else if (gn > gainTarget) + gainTarget++; + + if (gainTarget > prefs.gainmax << GAINSHIFT) + gainTarget = prefs.gainmax << GAINSHIFT; + + +#ifdef USE_X + if (prefs.show_mon) + { + int x; + + /* peak*gain */ + XDrawPoint(display, window, redGC, + pn, + 127 - (peak*gainCurrent + >> (GAINSHIFT + 8))); + + /* gain indicator */ + XFillRectangle(display, window, whiteGC, 0, 128, + prefs.buckets, 8); + x = (gainTarget - (1 << GAINSHIFT))*prefs.buckets + / ((prefs.gainmax - 1) << GAINSHIFT); + XDrawLine(display, window, redGC, x, + 128, x, 128 + 8); + + x = (gn - (1 << GAINSHIFT))*prefs.buckets + / ((prefs.gainmax - 1) << GAINSHIFT); + + XDrawLine(display, window, blackGC, + x, 132 - 1, + x, 132 + 1); + + /* blue peak line */ + XDrawLine(display, window, blueGC, + 0, 127 - (peak >> 8), prefs.buckets, + 127 - (peak >> 8)); + XFlush(display); + XDrawLine(display, window, whiteGC, + 0, 127 - (peak >> 8), prefs.buckets, + 127 - (peak >> 8)); + } +#endif + + /* See if a peak is going to clip */ + gn = (1 << GAINSHIFT)*32768/peak; + + if (gn < gainTarget) + { + gainTarget = gn; + + if (prefs.anticlip) + pos = 0; + + } else + { + /* We're ramping up, so draw it out over the whole frame */ + pos = length; + } + + /* Determine gain rate necessary to make target */ + if (!pos) + pos = 1; + + gr = ((gainTarget - gainCurrent) << 16)/pos; + + /* Do the shiznit */ + gf = gainCurrent << 16; + +#ifdef STATS + fprintf(stderr, "\rgain = %2.2f%+.2e ", + gainCurrent*1.0/(1 << GAINSHIFT), + (gainTarget - gainCurrent)*1.0/(1 << GAINSHIFT)); +#endif + + ap = audio; + for (i = 0; i < length/2; i++) + { + int sample; + + /* Interpolate the gain */ + gainCurrent = gf >> 16; + if (i < pos) + gf += gr; + else if (i == pos) + gf = gainTarget << 16; + + /* Amplify */ + sample = (*ap)*gainCurrent >> GAINSHIFT; + if (sample < -32768) + { +#ifdef STATS + clip++; +#endif + clipped += -32768 - sample; + sample = -32768; + } else if (sample > 32767) + { +#ifdef STATS + clip++; +#endif + clipped += sample - 32767; + sample = 32767; + } + *ap++ = sample; + } +#ifdef STATS + fprintf(stderr, "clip %d b%-3d ", clip, pn); +#endif + +#ifdef DEBUG + fprintf(stderr, "\ndone\n"); +#endif +} + diff --git a/trunk/src/compress.h b/trunk/src/compress.h new file mode 100644 index 000000000..42638f788 --- /dev/null +++ b/trunk/src/compress.h @@ -0,0 +1,47 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * interface to audio compression + * (c)2003-6 fluffy@beesbuzz.biz + * + * 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 + * + */ + +#ifndef COMPRESS_H +#define COMPRESS_H + +/* These are copied from the AudioCompress config.h, mainly because CompressDo + * needs GAINSHIFT defined. The rest are here so they can be used as defaults + * to pass to CompressCfg(). -- jat */ +#define ANTICLIP 0 /* Strict clipping protection */ +#define TARGET 25000 /* Target level */ +#define GAINMAX 32 /* The maximum amount to amplify by */ +#define GAINSHIFT 10 /* How fine-grained the gain is */ +#define GAINSMOOTH 8 /* How much inertia ramping has*/ +#define BUCKETS 400 /* How long of a history to store */ + +void CompressCfg(int monitor, + int anticlip, + int target, + int maxgain, + int smooth, + int buckets); + +void CompressDo(void *data, unsigned int numSamples); + +void CompressFree(void); + +#endif diff --git a/trunk/src/conf.c b/trunk/src/conf.c new file mode 100644 index 000000000..8ab59a505 --- /dev/null +++ b/trunk/src/conf.c @@ -0,0 +1,450 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "conf.h" + +#include "log.h" + +#include "utils.h" +#include "buffer2array.h" +#include "list.h" + +#include <sys/param.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <pwd.h> +#include <errno.h> + +#define MAX_STRING_SIZE MAXPATHLEN+80 + +#define CONF_COMMENT '#' +#define CONF_BLOCK_BEGIN "{" +#define CONF_BLOCK_END "}" + +#define CONF_REPEATABLE_MASK 0x01 +#define CONF_BLOCK_MASK 0x02 +#define CONF_LINE_TOKEN_MAX 3 + +typedef struct _configEntry { + unsigned char mask; + List *configParamList; +} ConfigEntry; + +static List *configEntriesList; + +static ConfigParam *newConfigParam(char *value, int line) +{ + ConfigParam *ret = xmalloc(sizeof(ConfigParam)); + + if (!value) + ret->value = NULL; + else + ret->value = xstrdup(value); + + ret->line = line; + + ret->numberOfBlockParams = 0; + ret->blockParams = NULL; + + return ret; +} + +static void freeConfigParam(ConfigParam * param) +{ + int i; + + if (param->value) + free(param->value); + + for (i = 0; i < param->numberOfBlockParams; i++) { + if (param->blockParams[i].name) { + free(param->blockParams[i].name); + } + if (param->blockParams[i].value) { + free(param->blockParams[i].value); + } + } + + if (param->numberOfBlockParams) + free(param->blockParams); + + free(param); +} + +static ConfigEntry *newConfigEntry(int repeatable, int block) +{ + ConfigEntry *ret = xmalloc(sizeof(ConfigEntry)); + + ret->mask = 0; + ret->configParamList = + makeList((ListFreeDataFunc *) freeConfigParam, 1); + + if (repeatable) + ret->mask |= CONF_REPEATABLE_MASK; + if (block) + ret->mask |= CONF_BLOCK_MASK; + + return ret; +} + +static void freeConfigEntry(ConfigEntry * entry) +{ + freeList(entry->configParamList); + free(entry); +} + +static void registerConfigParam(char *name, int repeatable, int block) +{ + ConfigEntry *entry; + + if (findInList(configEntriesList, name, NULL)) + FATAL("config parameter \"%s\" already registered\n", name); + + entry = newConfigEntry(repeatable, block); + + insertInList(configEntriesList, name, entry); +} + +void finishConf(void) +{ + freeList(configEntriesList); +} + +void initConf(void) +{ + configEntriesList = makeList((ListFreeDataFunc *) freeConfigEntry, 1); + + /* registerConfigParam(name, repeatable, block); */ + registerConfigParam(CONF_MUSIC_DIR, 0, 0); + registerConfigParam(CONF_PLAYLIST_DIR, 0, 0); + registerConfigParam(CONF_DB_FILE, 0, 0); + registerConfigParam(CONF_LOG_FILE, 0, 0); + registerConfigParam(CONF_ERROR_FILE, 0, 0); + registerConfigParam(CONF_PID_FILE, 0, 0); + registerConfigParam(CONF_STATE_FILE, 0, 0); + registerConfigParam(CONF_USER, 0, 0); + registerConfigParam(CONF_BIND_TO_ADDRESS, 1, 0); + registerConfigParam(CONF_PORT, 0, 0); + registerConfigParam(CONF_LOG_LEVEL, 0, 0); + registerConfigParam(CONF_ZEROCONF_NAME, 0, 0); + registerConfigParam(CONF_PASSWORD, 1, 0); + registerConfigParam(CONF_DEFAULT_PERMS, 0, 0); + registerConfigParam(CONF_AUDIO_OUTPUT, 1, 1); + registerConfigParam(CONF_AUDIO_OUTPUT_FORMAT, 0, 0); + registerConfigParam(CONF_MIXER_TYPE, 0, 0); + registerConfigParam(CONF_MIXER_DEVICE, 0, 0); + registerConfigParam(CONF_MIXER_CONTROL, 0, 0); + registerConfigParam(CONF_REPLAYGAIN, 0, 0); + registerConfigParam(CONF_REPLAYGAIN_PREAMP, 0, 0); + registerConfigParam(CONF_VOLUME_NORMALIZATION, 0, 0); + registerConfigParam(CONF_SAMPLERATE_CONVERTER, 0, 0); + registerConfigParam(CONF_AUDIO_BUFFER_SIZE, 0, 0); + registerConfigParam(CONF_BUFFER_BEFORE_PLAY, 0, 0); + registerConfigParam(CONF_HTTP_BUFFER_SIZE, 0, 0); + registerConfigParam(CONF_HTTP_PREBUFFER_SIZE, 0, 0); + registerConfigParam(CONF_HTTP_PROXY_HOST, 0, 0); + registerConfigParam(CONF_HTTP_PROXY_PORT, 0, 0); + registerConfigParam(CONF_HTTP_PROXY_USER, 0, 0); + registerConfigParam(CONF_HTTP_PROXY_PASSWORD, 0, 0); + registerConfigParam(CONF_CONN_TIMEOUT, 0, 0); + registerConfigParam(CONF_MAX_CONN, 0, 0); + registerConfigParam(CONF_MAX_PLAYLIST_LENGTH, 0, 0); + registerConfigParam(CONF_MAX_COMMAND_LIST_SIZE, 0, 0); + registerConfigParam(CONF_MAX_OUTPUT_BUFFER_SIZE, 0, 0); + registerConfigParam(CONF_FS_CHARSET, 0, 0); + registerConfigParam(CONF_ID3V1_ENCODING, 0, 0); + registerConfigParam(CONF_METADATA_TO_USE, 0, 0); + registerConfigParam(CONF_SAVE_ABSOLUTE_PATHS, 0, 0); + registerConfigParam(CONF_GAPLESS_MP3_PLAYBACK, 0, 0); +} + +static void addBlockParam(ConfigParam * param, char *name, char *value, + int line) +{ + param->numberOfBlockParams++; + + param->blockParams = xrealloc(param->blockParams, + param->numberOfBlockParams * + sizeof(BlockParam)); + + param->blockParams[param->numberOfBlockParams - 1].name = xstrdup(name); + param->blockParams[param->numberOfBlockParams - 1].value = + xstrdup(value); + param->blockParams[param->numberOfBlockParams - 1].line = line; +} + +static ConfigParam *readConfigBlock(FILE * fp, int *count, char *string) +{ + ConfigParam *ret = newConfigParam(NULL, *count); + + int i; + int numberOfArgs; + int argsMinusComment; + + while (myFgets(string, MAX_STRING_SIZE, fp)) { + char *array[CONF_LINE_TOKEN_MAX] = { NULL }; + + (*count)++; + + numberOfArgs = buffer2array(string, array, CONF_LINE_TOKEN_MAX); + + for (i = 0; i < numberOfArgs; i++) { + if (array[i][0] == CONF_COMMENT) + break; + } + + argsMinusComment = i; + + if (0 == argsMinusComment) { + continue; + } + + if (1 == argsMinusComment && + 0 == strcmp(array[0], CONF_BLOCK_END)) { + break; + } + + if (2 != argsMinusComment) { + FATAL("improperly formatted config file at line %i:" + " %s\n", *count, string); + } + + if (0 == strcmp(array[0], CONF_BLOCK_BEGIN) || + 0 == strcmp(array[1], CONF_BLOCK_BEGIN) || + 0 == strcmp(array[0], CONF_BLOCK_END) || + 0 == strcmp(array[1], CONF_BLOCK_END)) { + FATAL("improperly formatted config file at line %i: %s\n" + "in block beginning at line %i\n", + *count, string, ret->line);; + } + + addBlockParam(ret, array[0], array[1], *count); + } + + return ret; +} + +void readConf(char *file) +{ + FILE *fp; + char string[MAX_STRING_SIZE + 1]; + int i; + int numberOfArgs; + int argsMinusComment; + int count = 0; + ConfigEntry *entry; + void *voidPtr; + ConfigParam *param; + + if (!(fp = fopen(file, "r"))) { + FATAL("problems opening file %s for reading: %s\n", file, + strerror(errno)); + } + + while (myFgets(string, MAX_STRING_SIZE, fp)) { + char *array[CONF_LINE_TOKEN_MAX] = { NULL }; + count++; + + numberOfArgs = buffer2array(string, array, CONF_LINE_TOKEN_MAX); + + for (i = 0; i < numberOfArgs; i++) { + if (array[i][0] == CONF_COMMENT) + break; + } + + argsMinusComment = i; + + if (0 == argsMinusComment) { + continue; + } + + if (2 != argsMinusComment) { + FATAL("improperly formatted config file at line %i:" + " %s\n", count, string); + } + + if (!findInList(configEntriesList, array[0], &voidPtr)) { + FATAL("unrecognized parameter in config file at line " + "%i: %s\n", count, string); + } + + entry = (ConfigEntry *) voidPtr; + + if (!(entry->mask & CONF_REPEATABLE_MASK) && + entry->configParamList->numberOfNodes) { + param = entry->configParamList->firstNode->data; + FATAL("config parameter \"%s\" is first defined on line " + "%i and redefined on line %i\n", array[0], + param->line, count); + } + + if (entry->mask & CONF_BLOCK_MASK) { + if (0 != strcmp(array[1], CONF_BLOCK_BEGIN)) { + FATAL("improperly formatted config file at " + "line %i: %s\n", count, string); + } + param = readConfigBlock(fp, &count, string); + } else + param = newConfigParam(array[1], count); + + insertInListWithoutKey(entry->configParamList, param); + } + fclose(fp); +} + +ConfigParam *getNextConfigParam(char *name, ConfigParam * last) +{ + void *voidPtr; + ConfigEntry *entry; + ListNode *node; + ConfigParam *param; + + if (!findInList(configEntriesList, name, &voidPtr)) + return NULL; + + entry = voidPtr; + + node = entry->configParamList->firstNode; + + if (last) { + while (node != NULL) { + param = node->data; + node = node->nextNode; + if (param == last) + break; + } + } + + if (node == NULL) + return NULL; + + param = node->data; + + return param; +} + +char *getConfigParamValue(char *name) +{ + ConfigParam *param = getConfigParam(name); + + if (!param) + return NULL; + + return param->value; +} + +int getBoolConfigParam(char *name) +{ + ConfigParam *param; + + param = getConfigParam(name); + if (!param) return -1; + + if (strcmp("yes", param->value) == 0) return 1; + else if (strcmp("no", param->value) == 0) return 0; + + ERROR("%s is not \"yes\" or \"no\" on line %i\n", name, param->line); + + return -2; +} + +BlockParam *getBlockParam(ConfigParam * param, char *name) +{ + BlockParam *ret = NULL; + int i; + + for (i = 0; i < param->numberOfBlockParams; i++) { + if (0 == strcmp(name, param->blockParams[i].name)) { + if (ret) { + ERROR("\"%s\" first defined on line %i, and " + "redefined on line %i\n", name, + ret->line, param->blockParams[i].line); + } + ret = param->blockParams + i; + } + } + + return ret; +} + +ConfigParam *parseConfigFilePath(char *name, int force) +{ + ConfigParam *param = getConfigParam(name); + char *path; + + if (!param && force) + FATAL("config parameter \"%s\" not found\n", name); + + if (!param) + return NULL; + + path = param->value; + + if (path[0] != '/' && path[0] != '~') { + FATAL("\"%s\" is not an absolute path at line %i\n", + param->value, param->line); + } + /* Parse ~ in path */ + else if (path[0] == '~') { + struct passwd *pwd = NULL; + char *newPath; + int pos = 1; + if (path[1] == '/' || path[1] == '\0') { + ConfigParam *userParam = getConfigParam(CONF_USER); + + if (userParam) { + pwd = getpwnam(userParam->value); + if (!pwd) { + FATAL("no such user %s at line %i\n", + userParam->value, + userParam->line); + } + } else { + uid_t uid = geteuid(); + if ((pwd = getpwuid(uid)) == NULL) { + FATAL("problems getting passwd entry " + "for current user\n"); + } + } + } else { + int foundSlash = 0; + char *ch = path + 1; + for (; *ch != '\0' && *ch != '/'; ch++) ; + if (*ch == '/') + foundSlash = 1; + *ch = '\0'; + pos += ch - path - 1; + if ((pwd = getpwnam(path + 1)) == NULL) { + FATAL("user \"%s\" not found at line %i\n", + path + 1, param->line); + } + if (foundSlash) + *ch = '/'; + } + newPath = xmalloc(strlen(pwd->pw_dir) + strlen(path + pos) + 1); + strcpy(newPath, pwd->pw_dir); + strcat(newPath, path + pos); + free(param->value); + param->value = newPath; + } + + return param; +} diff --git a/trunk/src/conf.h b/trunk/src/conf.h new file mode 100644 index 000000000..7059eaa90 --- /dev/null +++ b/trunk/src/conf.h @@ -0,0 +1,98 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef CONF_H +#define CONF_H + +#include "../config.h" + +#define CONF_MUSIC_DIR "music_directory" +#define CONF_PLAYLIST_DIR "playlist_directory" +#define CONF_DB_FILE "db_file" +#define CONF_LOG_FILE "log_file" +#define CONF_ERROR_FILE "error_file" +#define CONF_PID_FILE "pid_file" +#define CONF_STATE_FILE "state_file" +#define CONF_USER "user" +#define CONF_BIND_TO_ADDRESS "bind_to_address" +#define CONF_PORT "port" +#define CONF_LOG_LEVEL "log_level" +#define CONF_ZEROCONF_NAME "zeroconf_name" +#define CONF_PASSWORD "password" +#define CONF_DEFAULT_PERMS "default_permissions" +#define CONF_AUDIO_OUTPUT "audio_output" +#define CONF_AUDIO_OUTPUT_FORMAT "audio_output_format" +#define CONF_MIXER_TYPE "mixer_type" +#define CONF_MIXER_DEVICE "mixer_device" +#define CONF_MIXER_CONTROL "mixer_control" +#define CONF_REPLAYGAIN "replaygain" +#define CONF_REPLAYGAIN_PREAMP "replaygain_preamp" +#define CONF_VOLUME_NORMALIZATION "volume_normalization" +#define CONF_SAMPLERATE_CONVERTER "samplerate_converter" +#define CONF_AUDIO_BUFFER_SIZE "audio_buffer_size" +#define CONF_BUFFER_BEFORE_PLAY "buffer_before_play" +#define CONF_HTTP_BUFFER_SIZE "http_buffer_size" +#define CONF_HTTP_PREBUFFER_SIZE "http_prebuffer_size" +#define CONF_HTTP_PROXY_HOST "http_proxy_host" +#define CONF_HTTP_PROXY_PORT "http_proxy_port" +#define CONF_HTTP_PROXY_USER "http_proxy_user" +#define CONF_HTTP_PROXY_PASSWORD "http_proxy_password" +#define CONF_CONN_TIMEOUT "connection_timeout" +#define CONF_MAX_CONN "max_connections" +#define CONF_MAX_PLAYLIST_LENGTH "max_playlist_length" +#define CONF_MAX_COMMAND_LIST_SIZE "max_command_list_size" +#define CONF_MAX_OUTPUT_BUFFER_SIZE "max_output_buffer_size" +#define CONF_FS_CHARSET "filesystem_charset" +#define CONF_ID3V1_ENCODING "id3v1_encoding" +#define CONF_METADATA_TO_USE "metadata_to_use" +#define CONF_SAVE_ABSOLUTE_PATHS "save_absolute_paths_in_playlists" +#define CONF_GAPLESS_MP3_PLAYBACK "gapless_mp3_playback" + +typedef struct _BlockParam { + char *name; + char *value; + int line; +} BlockParam; + +typedef struct _ConfigParam { + char *value; + unsigned int line; + BlockParam *blockParams; + int numberOfBlockParams; +} ConfigParam; + +void initConf(void); +void finishConf(void); + +void readConf(char *file); + +/* don't free the returned value + set _last_ to NULL to get first entry */ +ConfigParam *getNextConfigParam(char *name, ConfigParam * last); + +#define getConfigParam(name) getNextConfigParam(name, NULL) + +char *getConfigParamValue(char *name); + +int getBoolConfigParam(char *name); + +BlockParam *getBlockParam(ConfigParam * param, char *name); + +ConfigParam *parseConfigFilePath(char *name, int force); + +#endif diff --git a/trunk/src/dbUtils.c b/trunk/src/dbUtils.c new file mode 100644 index 000000000..f83ae4c21 --- /dev/null +++ b/trunk/src/dbUtils.c @@ -0,0 +1,341 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "dbUtils.h" + +#include "directory.h" +#include "myfprintf.h" +#include "utils.h" +#include "playlist.h" +#include "song.h" +#include "tag.h" +#include "tagTracker.h" +#include "log.h" +#include "storedPlaylist.h" + +typedef struct _ListCommandItem { + mpd_sint8 tagType; + int numConditionals; + LocateTagItem *conditionals; +} ListCommandItem; + +typedef struct _LocateTagItemArray { + int numItems; + LocateTagItem *items; +} LocateTagItemArray; + +typedef struct _SearchStats { + LocateTagItemArray locateArray; + int numberOfSongs; + unsigned long playTime; +} SearchStats; + +static int countSongsInDirectory(int fd, Directory * directory, void *data) +{ + int *count = (int *)data; + + *count += directory->songs->numberOfNodes; + + return 0; +} + +static int printDirectoryInDirectory(int fd, Directory * directory, + void *data) +{ + if (directory->path) { + fdprintf(fd, "directory: %s\n", getDirectoryPath(directory)); + } + return 0; +} + +static int printSongInDirectory(int fd, Song * song, void *data) +{ + printSongUrl(fd, song); + return 0; +} + +static int searchInDirectory(int fd, Song * song, void *data) +{ + LocateTagItemArray *array = data; + + if (strstrSearchTags(song, array->numItems, array->items)) + printSongInfo(fd, song); + + return 0; +} + +int searchForSongsIn(int fd, char *name, int numItems, LocateTagItem * items) +{ + int ret = -1; + int i; + + char **originalNeedles = xmalloc(numItems * sizeof(char *)); + LocateTagItemArray array; + + for (i = 0; i < numItems; i++) { + originalNeedles[i] = items[i].needle; + items[i].needle = strDupToUpper(originalNeedles[i]); + } + + array.numItems = numItems; + array.items = items; + + ret = traverseAllIn(fd, name, searchInDirectory, NULL, &array); + + for (i = 0; i < numItems; i++) { + free(items[i].needle); + items[i].needle = originalNeedles[i]; + } + + free(originalNeedles); + + return ret; +} + +static int findInDirectory(int fd, Song * song, void *data) +{ + LocateTagItemArray *array = data; + + if (tagItemsFoundAndMatches(song, array->numItems, array->items)) + printSongInfo(fd, song); + + return 0; +} + +int findSongsIn(int fd, char *name, int numItems, LocateTagItem * items) +{ + LocateTagItemArray array; + + array.numItems = numItems; + array.items = items; + + return traverseAllIn(fd, name, findInDirectory, NULL, (void *)&array); +} + +static void printSearchStats(int fd, SearchStats *stats) +{ + fdprintf(fd, "songs: %i\n", stats->numberOfSongs); + fdprintf(fd, "playtime: %li\n", stats->playTime); +} + +static int searchStatsInDirectory(int fd, Song * song, void *data) +{ + SearchStats *stats = data; + + if (tagItemsFoundAndMatches(song, stats->locateArray.numItems, + stats->locateArray.items)) { + stats->numberOfSongs++; + if (song->tag->time > 0) + stats->playTime += song->tag->time; + } + + return 0; +} + +int searchStatsForSongsIn(int fd, char *name, int numItems, + LocateTagItem * items) +{ + SearchStats stats; + int ret; + + stats.locateArray.numItems = numItems; + stats.locateArray.items = items; + stats.numberOfSongs = 0; + stats.playTime = 0; + + ret = traverseAllIn(fd, name, searchStatsInDirectory, NULL, &stats); + if (ret == 0) + printSearchStats(fd, &stats); + + return ret; +} + +int printAllIn(int fd, char *name) +{ + return traverseAllIn(fd, name, printSongInDirectory, + printDirectoryInDirectory, NULL); +} + +static int directoryAddSongToPlaylist(int fd, Song * song, void *data) +{ + return addSongToPlaylist(fd, song, 0); +} + +static int directoryAddSongToStoredPlaylist(int fd, Song *song, void *data) +{ + if (appendSongToStoredPlaylistByPath(fd, (char *)data, song) != 0) + return -1; + return 0; +} + +int addAllIn(int fd, char *name) +{ + return traverseAllIn(fd, name, directoryAddSongToPlaylist, NULL, NULL); +} + +int addAllInToStoredPlaylist(int fd, char *name, char *utf8file) +{ + return traverseAllIn(fd, name, directoryAddSongToStoredPlaylist, NULL, + (void *)utf8file); +} + +static int directoryPrintSongInfo(int fd, Song * song, void *data) +{ + return printSongInfo(fd, song); +} + +static int sumSongTime(int fd, Song * song, void *data) +{ + unsigned long *time = (unsigned long *)data; + + if (song->tag && song->tag->time >= 0) + *time += song->tag->time; + + return 0; +} + +int printInfoForAllIn(int fd, char *name) +{ + return traverseAllIn(fd, name, directoryPrintSongInfo, + printDirectoryInDirectory, NULL); +} + +int countSongsIn(int fd, char *name) +{ + int count = 0; + void *ptr = (void *)&count; + + traverseAllIn(fd, name, NULL, countSongsInDirectory, ptr); + + return count; +} + +unsigned long sumSongTimesIn(int fd, char *name) +{ + unsigned long dbPlayTime = 0; + void *ptr = (void *)&dbPlayTime; + + traverseAllIn(fd, name, sumSongTime, NULL, ptr); + + return dbPlayTime; +} + +static ListCommandItem *newListCommandItem(int tagType, int numConditionals, + LocateTagItem * conditionals) +{ + ListCommandItem *item = xmalloc(sizeof(ListCommandItem)); + + item->tagType = tagType; + item->numConditionals = numConditionals; + item->conditionals = conditionals; + + return item; +} + +static void freeListCommandItem(ListCommandItem * item) +{ + free(item); +} + +static void visitTag(int fd, Song * song, int tagType) +{ + int i; + MpdTag *tag = song->tag; + + if (tagType == LOCATE_TAG_FILE_TYPE) { + printSongUrl(fd, song); + return; + } + + if (!tag) + return; + + for (i = 0; i < tag->numOfItems; i++) { + if (tag->items[i].type == tagType) { + visitInTagTracker(tagType, tag->items[i].value); + } + } +} + +static int listUniqueTagsInDirectory(int fd, Song * song, void *data) +{ + ListCommandItem *item = data; + + if (tagItemsFoundAndMatches(song, item->numConditionals, + item->conditionals)) { + visitTag(fd, song, item->tagType); + } + + return 0; +} + +int listAllUniqueTags(int fd, int type, int numConditionals, + LocateTagItem * conditionals) +{ + int ret; + ListCommandItem *item = newListCommandItem(type, numConditionals, + conditionals); + + if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) { + resetVisitedFlagsInTagTracker(type); + } + + ret = traverseAllIn(fd, NULL, listUniqueTagsInDirectory, NULL, + (void *)item); + + if (type >= 0 && type <= TAG_NUM_OF_ITEM_TYPES) { + printVisitedInTagTracker(fd, type); + } + + freeListCommandItem(item); + + return ret; +} + +static int sumSavedFilenameMemoryInDirectory(int fd, Directory * dir, + void *data) +{ + int *sum = data; + + if (!dir->path) + return 0; + + *sum += (strlen(getDirectoryPath(dir)) + 1 - sizeof(Directory *)) * + dir->songs->numberOfNodes; + + return 0; +} + +static int sumSavedFilenameMemoryInSong(int fd, Song * song, void *data) +{ + int *sum = data; + + *sum += strlen(song->url) + 1; + + return 0; +} + +void printSavedMemoryFromFilenames(void) +{ + int sum = 0; + + traverseAllIn(STDERR_FILENO, NULL, sumSavedFilenameMemoryInSong, + sumSavedFilenameMemoryInDirectory, (void *)&sum); + + DEBUG("saved memory from filenames: %i\n", sum); +} diff --git a/trunk/src/dbUtils.h b/trunk/src/dbUtils.h new file mode 100644 index 000000000..0607bc3b5 --- /dev/null +++ b/trunk/src/dbUtils.h @@ -0,0 +1,51 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef DB_UTILS_H +#define DB_UTILS_H + +#include <stdio.h> + +#include "locate.h" + +int printAllIn(int fd, char *name); + +int addAllIn(int fd, char *name); + +int addAllInToStoredPlaylist(int fd, char *name, char *utf8file); + +int printInfoForAllIn(int fd, char *name); + +int searchForSongsIn(int fd, char *name, int numItems, + LocateTagItem * items); + +int findSongsIn(int fd, char *name, int numItems, LocateTagItem * items); + +int searchStatsForSongsIn(int fd, char *name, int numItems, + LocateTagItem * items); + +int countSongsIn(int fd, char *name); + +unsigned long sumSongTimesIn(int fd, char *name); + +int listAllUniqueTags(int fd, int type, int numConditiionals, + LocateTagItem * conditionals); + +void printSavedMemoryFromFilenames(void); + +#endif diff --git a/trunk/src/decode.c b/trunk/src/decode.c new file mode 100644 index 000000000..82eba19b9 --- /dev/null +++ b/trunk/src/decode.c @@ -0,0 +1,706 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "decode.h" + +#include "player.h" +#include "playerData.h" +#include "utils.h" +#include "pcm_utils.h" +#include "audio.h" +#include "path.h" +#include "log.h" +#include "sig_handlers.h" +#include "ls.h" +#include "utf8.h" + +#include <signal.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/wait.h> +#include <unistd.h> +#include <string.h> + +static int decode_pid; + +void decodeSigHandler(int sig, siginfo_t * si, void *v) +{ + if (sig == SIGCHLD) { + int status; + if (decode_pid == wait3(&status, WNOHANG, NULL)) { + if (WIFSIGNALED(status)) { + if (WTERMSIG(status) != SIGTERM) { + ERROR("decode process died from " + "signal: %i\n", WTERMSIG(status)); + } + } + decode_pid = 0; + getPlayerData()->playerControl.decode_pid = 0; + } + } else if (sig == SIGTERM) { + int pid = decode_pid; + if (pid > 0) { + DEBUG("player (or child) got SIGTERM\n"); + kill(pid, SIGTERM); + } else + DEBUG("decoder (or child) got SIGTERM\n"); + exit(EXIT_SUCCESS); + } +} + +static void stopDecode(DecoderControl * dc) +{ + if (decode_pid > 0 && (dc->start || dc->state != DECODE_STATE_STOP)) { + dc->stop = 1; + while (decode_pid > 0 && dc->stop) + my_usleep(10000); + } +} + +static void quitDecode(PlayerControl * pc, DecoderControl * dc) +{ + stopDecode(dc); + pc->state = PLAYER_STATE_STOP; + dc->seek = 0; + pc->play = 0; + pc->stop = 0; + pc->pause = 0; + kill(getppid(), SIGUSR1); +} + +static int calculateCrossFadeChunks(PlayerControl * pc, AudioFormat * af) +{ + long chunks; + + if (pc->crossFade <= 0) + return 0; + + chunks = (af->sampleRate * af->bits * af->channels / 8.0 / CHUNK_SIZE); + chunks = (chunks * pc->crossFade + 0.5); + + if (chunks > (buffered_chunks - buffered_before_play)) { + chunks = buffered_chunks - buffered_before_play; + } + + if (chunks < 0) + chunks = 0; + + return (int)chunks; +} + +#define handleDecodeStart() \ + if(decodeWaitedOn) { \ + if(dc->state!=DECODE_STATE_START && decode_pid > 0 && \ + dc->error==DECODE_ERROR_NOERROR) \ + { \ + decodeWaitedOn = 0; \ + if(openAudioDevice(&(cb->audioFormat))<0) { \ + pathcpy_trunc(pc->erroredUrl, pc->utf8url); \ + pc->error = PLAYER_ERROR_AUDIO; \ + ERROR("problems opening audio device while playing \"%s\"\n", pc->utf8url); \ + quitDecode(pc,dc); \ + return; \ + } \ + pc->totalTime = dc->totalTime; \ + pc->sampleRate = dc->audioFormat.sampleRate; \ + pc->bits = dc->audioFormat.bits; \ + pc->channels = dc->audioFormat.channels; \ + sizeToTime = 8.0/cb->audioFormat.bits/ \ + cb->audioFormat.channels/ \ + cb->audioFormat.sampleRate; \ + } \ + else if(dc->state!=DECODE_STATE_START || decode_pid <= 0) { \ + pathcpy_trunc(pc->erroredUrl, pc->utf8url); \ + pc->error = PLAYER_ERROR_FILE; \ + quitDecode(pc,dc); \ + return; \ + } \ + else { \ + my_usleep(10000); \ + continue; \ + } \ + } + +static int waitOnDecode(PlayerControl * pc, DecoderControl * dc, + OutputBuffer * cb, int *decodeWaitedOn) +{ + MpdTag *tag = NULL; + pathcpy_trunc(pc->currentUrl, pc->utf8url); + + while (decode_pid > 0 && dc->start) + my_usleep(10000); + + if (dc->start || dc->error != DECODE_ERROR_NOERROR) { + pathcpy_trunc(pc->erroredUrl, pc->utf8url); + pc->error = PLAYER_ERROR_FILE; + quitDecode(pc, dc); + return -1; + } + + if ((tag = metadataChunkToMpdTagDup(&(pc->fileMetadataChunk)))) { + sendMetadataToAudioDevice(tag); + freeMpdTag(tag); + } + + pc->totalTime = pc->fileTime; + pc->bitRate = 0; + pc->sampleRate = 0; + pc->bits = 0; + pc->channels = 0; + *decodeWaitedOn = 1; + + return 0; +} + +static int decodeSeek(PlayerControl * pc, DecoderControl * dc, + OutputBuffer * cb, int *decodeWaitedOn, int *next) +{ + int ret = -1; + + if (decode_pid > 0) { + if (dc->state == DECODE_STATE_STOP || dc->error || + strcmp(dc->utf8url, pc->utf8url) != 0) { + stopDecode(dc); + *next = -1; + cb->begin = 0; + cb->end = 0; + dc->error = 0; + dc->start = 1; + waitOnDecode(pc, dc, cb, decodeWaitedOn); + } + if (decode_pid > 0 && dc->state != DECODE_STATE_STOP && + dc->seekable) { + *next = -1; + dc->seekWhere = pc->seekWhere > pc->totalTime - 0.1 ? + pc->totalTime - 0.1 : pc->seekWhere; + dc->seekWhere = 0 > dc->seekWhere ? 0 : dc->seekWhere; + dc->seekError = 0; + dc->seek = 1; + while (decode_pid > 0 && dc->seek) + my_usleep(10000); + if (!dc->seekError) { + pc->elapsedTime = dc->seekWhere; + ret = 0; + } + } + } + pc->seek = 0; + + return ret; +} + +#define processDecodeInput() \ + if(pc->cycleLogFiles) { \ + cycle_log_files(); \ + pc->cycleLogFiles = 0; \ + } \ + if(pc->lockQueue) { \ + pc->queueLockState = PLAYER_QUEUE_LOCKED; \ + pc->lockQueue = 0; \ + } \ + if(pc->unlockQueue) { \ + pc->queueLockState = PLAYER_QUEUE_UNLOCKED; \ + pc->unlockQueue = 0; \ + } \ + if(pc->pause) { \ + pause = !pause; \ + if (pause) pc->state = PLAYER_STATE_PAUSE; \ + else { \ + if (openAudioDevice(NULL) >= 0) pc->state = PLAYER_STATE_PLAY; \ + else { \ + pathcpy_trunc(pc->erroredUrl, pc->utf8url); \ + pc->error = PLAYER_ERROR_AUDIO; \ + ERROR("problems opening audio device while playing \"%s\"\n", pc->utf8url); \ + pause = -1; \ + } \ + } \ + pc->pause = 0; \ + kill(getppid(), SIGUSR1); \ + if (pause == -1) pause = 1; \ + else if (pause) { \ + dropBufferedAudio(); \ + closeAudioDevice(); \ + } \ + } \ + if(pc->seek) { \ + dropBufferedAudio(); \ + if(decodeSeek(pc,dc,cb,&decodeWaitedOn,&next) == 0) { \ + doCrossFade = 0; \ + nextChunk = -1; \ + bbp = 0; \ + } \ + } \ + if(pc->stop) { \ + dropBufferedAudio(); \ + quitDecode(pc,dc); \ + return; \ + } + +static void decodeStart(PlayerControl * pc, OutputBuffer * cb, + DecoderControl * dc) +{ + int ret; + InputStream inStream; + InputPlugin *plugin = NULL; + char *path; + + if (isRemoteUrl(pc->utf8url)) + path = utf8StrToLatin1Dup(pc->utf8url); + else + path = xstrdup(rmp2amp(utf8ToFsCharset(pc->utf8url))); + + if (!path) { + dc->error = DECODE_ERROR_FILE; + dc->state = DECODE_STATE_STOP; + dc->start = 0; + return; + } + + copyMpdTagToOutputBuffer(cb, NULL); + + pathcpy_trunc(dc->utf8url, pc->utf8url); + + if (openInputStream(&inStream, path) < 0) { + dc->error = DECODE_ERROR_FILE; + dc->state = DECODE_STATE_STOP; + dc->start = 0; + free(path); + return; + } + + dc->state = DECODE_STATE_START; + dc->start = 0; + + while (!inputStreamAtEOF(&inStream) && bufferInputStream(&inStream) < 0 + && !dc->stop) { + /* sleep so we don't consume 100% of the cpu */ + my_usleep(1000); + } + + /* for http streams, seekable is determined in bufferInputStream */ + dc->seekable = inStream.seekable; + + if (dc->stop) { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + free(path); + return; + } + + /*if(inStream.metaName) { + MpdTag * tag = newMpdTag(); + tag->name = xstrdup(inStream.metaName); + copyMpdTagToOutputBuffer(cb, tag); + freeMpdTag(tag); + } */ + + /* reset Metadata in OutputBuffer */ + + ret = DECODE_ERROR_UNKTYPE; + if (isRemoteUrl(dc->utf8url)) { + unsigned int next = 0; + cb->acceptMetadata = 1; + + /* first we try mime types: */ + while (ret + && (plugin = + getInputPluginFromMimeType(inStream.mime, next++))) { + if (!plugin->streamDecodeFunc) + continue; + if (!(plugin->streamTypes & INPUT_PLUGIN_STREAM_URL)) + continue; + if (plugin->tryDecodeFunc + && !plugin->tryDecodeFunc(&inStream)) + continue; + ret = plugin->streamDecodeFunc(cb, dc, &inStream); + break; + } + + /* if that fails, try suffix matching the URL: */ + if (plugin == NULL) { + char *s = getSuffix(dc->utf8url); + next = 0; + while (ret + && (plugin = + getInputPluginFromSuffix(s, next++))) { + if (!plugin->streamDecodeFunc) + continue; + if (!(plugin->streamTypes & + INPUT_PLUGIN_STREAM_URL)) + continue; + if (plugin->tryDecodeFunc && + !plugin->tryDecodeFunc(&inStream)) + continue; + ret = + plugin->streamDecodeFunc(cb, dc, &inStream); + break; + } + } + /* fallback to mp3: */ + /* this is needed for bastard streams that don't have a suffix + or set the mimeType */ + if (plugin == NULL) { + /* we already know our mp3Plugin supports streams, no + * need to check for stream{Types,DecodeFunc} */ + if ((plugin = getInputPluginFromName("mp3"))) + ret = plugin->streamDecodeFunc(cb, dc, + &inStream); + } + } else { + unsigned int next = 0; + char *s = getSuffix(dc->utf8url); + cb->acceptMetadata = 0; + while (ret && (plugin = getInputPluginFromSuffix(s, next++))) { + if (!plugin->streamTypes & INPUT_PLUGIN_STREAM_FILE) + continue; + if (plugin->tryDecodeFunc + && !plugin->tryDecodeFunc(&inStream)) + continue; + + if (plugin->streamDecodeFunc) { + ret = + plugin->streamDecodeFunc(cb, dc, &inStream); + break; + } else if (plugin->fileDecodeFunc) { + closeInputStream(&inStream); + ret = plugin->fileDecodeFunc(cb, dc, path); + } + } + } + + if (ret < 0 || ret == DECODE_ERROR_UNKTYPE) { + pathcpy_trunc(pc->erroredUrl, dc->utf8url); + if (ret != DECODE_ERROR_UNKTYPE) + dc->error = DECODE_ERROR_FILE; + else { + dc->error = DECODE_ERROR_UNKTYPE; + closeInputStream(&inStream); + } + dc->stop = 0; + dc->state = DECODE_STATE_STOP; + } + + free(path); +} + +static int decoderInit(PlayerControl * pc, OutputBuffer * cb, + DecoderControl * dc) +{ + blockSignals(); + getPlayerData()->playerControl.decode_pid = 0; + decode_pid = fork(); + + if (decode_pid == 0) { + /* CHILD */ + unblockSignals(); + + while (1) { + if (dc->cycleLogFiles) { + cycle_log_files(); + dc->cycleLogFiles = 0; + } else if (dc->start || dc->seek) + decodeStart(pc, cb, dc); + else if (dc->stop) { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } else + my_usleep(10000); + } + + exit(EXIT_SUCCESS); + /* END OF CHILD */ + } else if (decode_pid < 0) { + unblockSignals(); + pathcpy_trunc(pc->erroredUrl, pc->utf8url); + pc->error = PLAYER_ERROR_SYSTEM; + return -1; + } + DEBUG("decoder PID: %d\n", decode_pid); + getPlayerData()->playerControl.decode_pid = decode_pid; + unblockSignals(); + + return 0; +} + +static void handleMetadata(OutputBuffer * cb, PlayerControl * pc, int *previous, + int *currentChunkSent, MetadataChunk * currentChunk) +{ + if (cb->begin != cb->end) { + int meta = cb->metaChunk[cb->begin]; + if (meta != *previous) { + DEBUG("player: metadata change\n"); + if (meta >= 0 && cb->metaChunkSet[meta]) { + DEBUG("player: new metadata from decoder!\n"); + memcpy(currentChunk, + cb->metadataChunks + meta, + sizeof(MetadataChunk)); + *currentChunkSent = 0; + cb->metaChunkSet[meta] = 0; + } + } + *previous = meta; + } + if (!(*currentChunkSent) && pc->metadataState == + PLAYER_METADATA_STATE_WRITE) { + MpdTag *tag = NULL; + + *currentChunkSent = 1; + + if ((tag = metadataChunkToMpdTagDup(currentChunk))) { + sendMetadataToAudioDevice(tag); + freeMpdTag(tag); + } + + memcpy(&(pc->metadataChunk), currentChunk, + sizeof(MetadataChunk)); + pc->metadataState = PLAYER_METADATA_STATE_READ; + kill(getppid(), SIGUSR1); + } +} + +static void advanceOutputBufferTo(OutputBuffer * cb, PlayerControl * pc, + int *previous, int *currentChunkSent, + MetadataChunk * currentChunk, int to) +{ + while (cb->begin != to) { + handleMetadata(cb, pc, previous, currentChunkSent, + currentChunk); + if (cb->begin + 1 >= buffered_chunks) { + cb->begin = 0; + } + else cb->begin++; + } +} + +static void decodeParent(PlayerControl * pc, DecoderControl * dc, OutputBuffer * cb) +{ + int pause = 0; + int quit = 0; + int bbp = buffered_before_play; + int doCrossFade = 0; + int crossFadeChunks = 0; + int fadePosition; + int nextChunk = -1; + int test; + int decodeWaitedOn = 0; + char silence[CHUNK_SIZE]; + double sizeToTime = 0.0; + int previousMetadataChunk = -1; + MetadataChunk currentMetadataChunk; + int currentChunkSent = 1; + int end; + int next = -1; + + memset(silence, 0, CHUNK_SIZE); + + if (waitOnDecode(pc, dc, cb, &decodeWaitedOn) < 0) + return; + + pc->elapsedTime = 0; + pc->state = PLAYER_STATE_PLAY; + pc->play = 0; + kill(getppid(), SIGUSR1); + + while (decode_pid > 0 && + cb->end - cb->begin < bbp && + cb->end != buffered_chunks - 1 && + dc->state != DECODE_STATE_STOP) { + processDecodeInput(); + my_usleep(1000); + } + + while (!quit) { + processDecodeInput(); + handleDecodeStart(); + handleMetadata(cb, pc, &previousMetadataChunk, + ¤tChunkSent, ¤tMetadataChunk); + if (dc->state == DECODE_STATE_STOP && + pc->queueState == PLAYER_QUEUE_FULL && + pc->queueLockState == PLAYER_QUEUE_UNLOCKED) { + next = cb->end; + dc->start = 1; + pc->queueState = PLAYER_QUEUE_DECODE; + kill(getppid(), SIGUSR1); + } + if (next >= 0 && doCrossFade == 0 && !dc->start && + dc->state != DECODE_STATE_START) { + nextChunk = -1; + if (isCurrentAudioFormat(&(cb->audioFormat))) { + doCrossFade = 1; + crossFadeChunks = + calculateCrossFadeChunks(pc, + &(cb-> + audioFormat)); + if (!crossFadeChunks + || pc->crossFade >= dc->totalTime) { + doCrossFade = -1; + } + } else + doCrossFade = -1; + } + + /* copy these to local variables to prevent any potential + race conditions and weirdness */ + end = cb->end; + + if (pause) + my_usleep(10000); + else if (cb->begin != end && cb->begin != next) { + if (doCrossFade == 1 && next >= 0 && + ((next > cb->begin && + (fadePosition = next - cb->begin) + <= crossFadeChunks) || + (cb->begin > next && + (fadePosition = next - cb->begin + + buffered_chunks) <= crossFadeChunks))) { + if (nextChunk < 0) { + crossFadeChunks = fadePosition; + } + test = end; + if (end < cb->begin) + test += buffered_chunks; + nextChunk = cb->begin + crossFadeChunks; + if (nextChunk < test) { + if (nextChunk >= buffered_chunks) { + nextChunk -= buffered_chunks; + } + pcm_mix(cb->chunks + + cb->begin * CHUNK_SIZE, + cb->chunks + + nextChunk * CHUNK_SIZE, + cb->chunkSize[cb->begin], + cb->chunkSize[nextChunk], + &(cb->audioFormat), + ((float)fadePosition) / + crossFadeChunks); + if (cb->chunkSize[nextChunk] > + cb->chunkSize[cb->begin] + ) { + cb->chunkSize[cb->begin] + = cb->chunkSize[nextChunk]; + } + } else { + if (dc->state == DECODE_STATE_STOP) { + doCrossFade = -1; + } else + continue; + } + } + pc->elapsedTime = cb->times[cb->begin]; + pc->bitRate = cb->bitRate[cb->begin]; + pcm_volumeChange(cb->chunks + cb->begin * + CHUNK_SIZE, + cb->chunkSize[cb->begin], + &(cb->audioFormat), + pc->softwareVolume); + if (playAudio(cb->chunks + cb->begin * CHUNK_SIZE, + cb->chunkSize[cb->begin]) < 0) { + quit = 1; + } + pc->totalPlayTime += + sizeToTime * cb->chunkSize[cb->begin]; + if (cb->begin + 1 >= buffered_chunks) { + cb->begin = 0; + } else + cb->begin++; + } else if (cb->begin != end && cb->begin == next) { + if (doCrossFade == 1 && nextChunk >= 0) { + nextChunk = cb->begin + crossFadeChunks; + test = end; + if (end < cb->begin) + test += buffered_chunks; + if (nextChunk < test) { + if (nextChunk >= buffered_chunks) { + nextChunk -= buffered_chunks; + } + advanceOutputBufferTo(cb, pc, + &previousMetadataChunk, + ¤tChunkSent, + ¤tMetadataChunk, + nextChunk); + } + } + while (pc->queueState == PLAYER_QUEUE_DECODE || + pc->queueLockState == PLAYER_QUEUE_LOCKED) { + processDecodeInput(); + if (quit) { + quitDecode(pc, dc); + return; + } + my_usleep(10000); + } + if (pc->queueState != PLAYER_QUEUE_PLAY) { + quit = 1; + break; + } else { + next = -1; + if (waitOnDecode(pc, dc, cb, &decodeWaitedOn) < + 0) { + return; + } + nextChunk = -1; + doCrossFade = 0; + crossFadeChunks = 0; + pc->queueState = PLAYER_QUEUE_EMPTY; + kill(getppid(), SIGUSR1); + } + } else if (decode_pid <= 0 || + (dc->state == DECODE_STATE_STOP && !dc->start)) { + quit = 1; + break; + } else { + /*DEBUG("waiting for decoded audio, play silence\n");*/ + if (playAudio(silence, CHUNK_SIZE) < 0) + quit = 1; + } + } + + quitDecode(pc, dc); +} + +/* decode w/ buffering + * this will fork another process + * child process does decoding + * parent process does playing audio + */ +void decode(void) +{ + OutputBuffer *cb; + PlayerControl *pc; + DecoderControl *dc; + + cb = &(getPlayerData()->buffer); + + clearAllMetaChunkSets(cb); + cb->begin = 0; + cb->end = 0; + pc = &(getPlayerData()->playerControl); + dc = &(getPlayerData()->decoderControl); + dc->error = 0; + dc->seek = 0; + dc->stop = 0; + dc->start = 1; + + if (decode_pid <= 0) { + if (decoderInit(pc, cb, dc) < 0) + return; + } + + decodeParent(pc, dc, cb); +} diff --git a/trunk/src/decode.h b/trunk/src/decode.h new file mode 100644 index 000000000..f073c0d55 --- /dev/null +++ b/trunk/src/decode.h @@ -0,0 +1,69 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef DECODE_H +#define DECODE_H + +#include "../config.h" +#include "tag.h" + +#include "mpd_types.h" +#include "audio.h" + +#include <stdio.h> +#include <sys/param.h> +#include <signal.h> + +#define DECODE_TYPE_FILE 0 +#define DECODE_TYPE_URL 1 + +#define DECODE_STATE_STOP 0 +#define DECODE_STATE_START 1 +#define DECODE_STATE_DECODE 2 + +#define DECODE_ERROR_NOERROR 0 +#define DECODE_ERROR_UNKTYPE 10 +#define DECODE_ERROR_FILE 20 + +#define DECODE_SUFFIX_MP3 1 +#define DECODE_SUFFIX_OGG 2 +#define DECODE_SUFFIX_FLAC 3 +#define DECODE_SUFFIX_AAC 4 +#define DECODE_SUFFIX_MP4 5 +#define DECODE_SUFFIX_WAVE 6 + +typedef struct _DecoderControl { + volatile mpd_sint8 state; + volatile mpd_sint8 stop; + volatile mpd_sint8 start; + volatile mpd_uint16 error; + volatile mpd_sint8 seek; + volatile mpd_sint8 seekError; + volatile mpd_sint8 seekable; + volatile mpd_sint8 cycleLogFiles; + volatile double seekWhere; + AudioFormat audioFormat; + char utf8url[MAXPATHLEN + 1]; + volatile float totalTime; +} DecoderControl; + +void decodeSigHandler(int sig, siginfo_t * siginfo, void *v); + +void decode(void); + +#endif diff --git a/trunk/src/directory.c b/trunk/src/directory.c new file mode 100644 index 000000000..560c04b7b --- /dev/null +++ b/trunk/src/directory.c @@ -0,0 +1,1362 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "directory.h" + +#include "command.h" +#include "conf.h" +#include "dbUtils.h" +#include "interface.h" +#include "list.h" +#include "listen.h" +#include "log.h" +#include "ls.h" +#include "mpd_types.h" +#include "path.h" +#include "player.h" +#include "playlist.h" +#include "sig_handlers.h" +#include "stats.h" +#include "tagTracker.h" +#include "utils.h" +#include "volume.h" + +#include <sys/wait.h> +#include <dirent.h> +#include <errno.h> +#include <assert.h> +#include <libgen.h> + +#define DIRECTORY_DIR "directory: " +#define DIRECTORY_MTIME "mtime: " +#define DIRECTORY_BEGIN "begin: " +#define DIRECTORY_END "end: " +#define DIRECTORY_INFO_BEGIN "info_begin" +#define DIRECTORY_INFO_END "info_end" +#define DIRECTORY_MPD_VERSION "mpd_version: " +#define DIRECTORY_FS_CHARSET "fs_charset: " + +#define DIRECTORY_UPDATE_EXIT_NOUPDATE 0 +#define DIRECTORY_UPDATE_EXIT_UPDATE 1 +#define DIRECTORY_UPDATE_EXIT_ERROR 2 + +#define DIRECTORY_RETURN_NOUPDATE 0 +#define DIRECTORY_RETURN_UPDATE 1 +#define DIRECTORY_RETURN_ERROR -1 + +static Directory *mp3rootDirectory; + +static time_t directory_dbModTime; + +static volatile int directory_updatePid; + +static volatile int directory_reReadDB; + +static volatile mpd_uint16 directory_updateJobId; + +static DirectoryList *newDirectoryList(); + +static int addToDirectory(Directory * directory, char *shortname, char *name); + +static void freeDirectoryList(DirectoryList * list); + +static void freeDirectory(Directory * directory); + +static int exploreDirectory(Directory * directory); + +static int updateDirectory(Directory * directory); + +static void deleteEmptyDirectoriesInDirectory(Directory * directory); + +static void removeSongFromDirectory(Directory * directory, char *shortname); + +static int addSubDirectoryToDirectory(Directory * directory, char *shortname, + char *name, struct stat *st); + +static Directory *getDirectoryDetails(char *name, char **shortname); + +static Directory *getDirectory(char *name); + +static Song *getSongDetails(char *file, char **shortnameRet, + Directory ** directoryRet); + +static int updatePath(char *utf8path); + +static void sortDirectory(Directory * directory); + +static int inodeFoundInParent(Directory * parent, ino_t inode, dev_t device); + +static int statDirectory(Directory * dir); + +static char *getDbFile(void) +{ + ConfigParam *param = parseConfigFilePath(CONF_DB_FILE, 1); + + assert(param); + assert(param->value); + + return param->value; +} + +static void clearUpdatePid(void) +{ + directory_updatePid = 0; +} + +int isUpdatingDB(void) +{ + if (directory_updatePid > 0 || directory_reReadDB) { + return directory_updateJobId; + } + return 0; +} + +void directory_sigChldHandler(int pid, int status) +{ + if (directory_updatePid == pid) { + if (WIFSIGNALED(status) && WTERMSIG(status) != SIGTERM) { + ERROR("update process died from a " + "non-TERM signal: %i\n", WTERMSIG(status)); + } else if (!WIFSIGNALED(status)) { + switch (WEXITSTATUS(status)) { + case DIRECTORY_UPDATE_EXIT_UPDATE: + directory_reReadDB = 1; + DEBUG("directory_sigChldHandler: " + "updated db\n"); + case DIRECTORY_UPDATE_EXIT_NOUPDATE: + DEBUG("directory_sigChldHandler: " + "update exited succesffully\n"); + break; + default: + ERROR("error updating db\n"); + } + } + clearUpdatePid(); + } +} + +void readDirectoryDBIfUpdateIsFinished(void) +{ + if (directory_reReadDB && 0 == directory_updatePid) { + DEBUG("readDirectoryDB since update finished successfully\n"); + readDirectoryDB(); + playlistVersionChange(); + directory_reReadDB = 0; + } +} + +int updateInit(int fd, List * pathList) +{ + if (directory_updatePid > 0) { + commandError(fd, ACK_ERROR_UPDATE_ALREADY, "already updating"); + return -1; + } + + /* need to block CHLD signal, cause it can exit before we + even get a chance to assign directory_updatePID */ + blockSignals(); + directory_updatePid = fork(); + if (directory_updatePid == 0) { + /* child */ + int dbUpdated = 0; + clearPlayerPid(); + + unblockSignals(); + + finishSigHandlers(); + closeAllListenSockets(); + freeAllInterfaces(); + finishPlaylist(); + finishVolume(); + + if (pathList) { + ListNode *node = pathList->firstNode; + + while (node) { + switch (updatePath(node->key)) { + case 1: + dbUpdated = 1; + break; + case 0: + break; + default: + exit(DIRECTORY_UPDATE_EXIT_ERROR); + } + node = node->nextNode; + } + } else { + if ((dbUpdated = updateDirectory(mp3rootDirectory)) < 0) { + exit(DIRECTORY_UPDATE_EXIT_ERROR); + } + } + + if (!dbUpdated) + exit(DIRECTORY_UPDATE_EXIT_NOUPDATE); + + /* ignore signals since we don't want them to corrupt the db */ + ignoreSignals(); + if (writeDirectoryDB() < 0) { + exit(DIRECTORY_UPDATE_EXIT_ERROR); + } + exit(DIRECTORY_UPDATE_EXIT_UPDATE); + } else if (directory_updatePid < 0) { + unblockSignals(); + ERROR("updateInit: Problems forking()'ing\n"); + commandError(fd, ACK_ERROR_SYSTEM, + "problems trying to update"); + directory_updatePid = 0; + return -1; + } + unblockSignals(); + + directory_updateJobId++; + if (directory_updateJobId > 1 << 15) + directory_updateJobId = 1; + DEBUG("updateInit: fork()'d update child for update job id %i\n", + (int)directory_updateJobId); + fdprintf(fd, "updating_db: %i\n", (int)directory_updateJobId); + + return 0; +} + +static DirectoryStat *newDirectoryStat(struct stat *st) +{ + DirectoryStat *ret = xmalloc(sizeof(DirectoryStat)); + ret->inode = st->st_ino; + ret->device = st->st_dev; + return ret; +} + +static void freeDirectoryStatFromDirectory(Directory * dir) +{ + if (dir->stat) + free(dir->stat); + dir->stat = NULL; +} + +static DirectoryList *newDirectoryList(void) +{ + return makeList((ListFreeDataFunc *) freeDirectory, 1); +} + +static Directory *newDirectory(char *dirname, Directory * parent) +{ + Directory *directory; + + directory = xmalloc(sizeof(Directory)); + + if (dirname && strlen(dirname)) + directory->path = xstrdup(dirname); + else + directory->path = NULL; + directory->subDirectories = newDirectoryList(); + directory->songs = newSongList(); + directory->stat = NULL; + directory->parent = parent; + + return directory; +} + +static void freeDirectory(Directory * directory) +{ + freeDirectoryList(directory->subDirectories); + freeSongList(directory->songs); + if (directory->path) + free(directory->path); + freeDirectoryStatFromDirectory(directory); + free(directory); + /* this resets last dir returned */ + /*getDirectoryPath(NULL); */ +} + +static void freeDirectoryList(DirectoryList * directoryList) +{ + freeList(directoryList); +} + +static void removeSongFromDirectory(Directory * directory, char *shortname) +{ + void *song; + + if (findInList(directory->songs, shortname, &song)) { + LOG("removing: %s\n", getSongUrl((Song *) song)); + deleteFromList(directory->songs, shortname); + } +} + +static void deleteEmptyDirectoriesInDirectory(Directory * directory) +{ + ListNode *node = directory->subDirectories->firstNode; + ListNode *nextNode; + Directory *subDir; + + while (node) { + subDir = (Directory *) node->data; + deleteEmptyDirectoriesInDirectory(subDir); + nextNode = node->nextNode; + if (subDir->subDirectories->numberOfNodes == 0 && + subDir->songs->numberOfNodes == 0) { + deleteNodeFromList(directory->subDirectories, node); + } + node = nextNode; + } +} + +/* return values: + -1 -> error + 0 -> no error, but nothing updated + 1 -> no error, and stuff updated + */ +static int updateInDirectory(Directory * directory, char *shortname, char *name) +{ + void *song; + void *subDir; + struct stat st; + + if (myStat(name, &st)) + return -1; + + if (S_ISREG(st.st_mode) && hasMusicSuffix(name, 0)) { + if (0 == findInList(directory->songs, shortname, &song)) { + addToDirectory(directory, shortname, name); + return DIRECTORY_RETURN_UPDATE; + } else if (st.st_mtime != ((Song *) song)->mtime) { + LOG("updating %s\n", name); + if (updateSongInfo((Song *) song) < 0) { + removeSongFromDirectory(directory, shortname); + } + return 1; + } + } else if (S_ISDIR(st.st_mode)) { + if (findInList + (directory->subDirectories, shortname, (void **)&subDir)) { + freeDirectoryStatFromDirectory(subDir); + ((Directory *) subDir)->stat = newDirectoryStat(&st); + return updateDirectory((Directory *) subDir); + } else { + return addSubDirectoryToDirectory(directory, shortname, + name, &st); + } + } + + return 0; +} + +/* return values: + -1 -> error + 0 -> no error, but nothing removed + 1 -> no error, and stuff removed + */ +static int removeDeletedFromDirectory(Directory * directory, DIR * dir) +{ + char cwd[2]; + struct dirent *ent; + char *dirname = getDirectoryPath(directory); + List *entList = makeList(free, 1); + void *name; + char *s; + char *utf8; + ListNode *node; + ListNode *tmpNode; + int ret = 0; + + cwd[0] = '.'; + cwd[1] = '\0'; + if (dirname == NULL) + dirname = cwd; + + while ((ent = readdir(dir))) { + if (ent->d_name[0] == '.') + continue; /* hide hidden stuff */ + if (strchr(ent->d_name, '\n')) + continue; + + utf8 = fsCharsetToUtf8(ent->d_name); + + if (!utf8) + continue; + + if (directory->path) { + s = xmalloc(strlen(getDirectoryPath(directory)) + + strlen(utf8) + 2); + sprintf(s, "%s/%s", getDirectoryPath(directory), utf8); + } else + s = xstrdup(utf8); + insertInList(entList, utf8, s); + } + + node = directory->subDirectories->firstNode; + while (node) { + tmpNode = node->nextNode; + if (findInList(entList, node->key, &name)) { + if (!isDir((char *)name)) { + LOG("removing directory: %s\n", (char *)name); + deleteFromList(directory->subDirectories, + node->key); + ret = 1; + } + } else { + LOG("removing directory: %s/%s\n", + getDirectoryPath(directory), node->key); + deleteFromList(directory->subDirectories, node->key); + ret = 1; + } + node = tmpNode; + } + + node = directory->songs->firstNode; + while (node) { + tmpNode = node->nextNode; + if (findInList(entList, node->key, (void **)&name)) { + if (!isMusic(name, NULL, 0)) { + removeSongFromDirectory(directory, node->key); + ret = 1; + } + } else { + removeSongFromDirectory(directory, node->key); + ret = 1; + } + node = tmpNode; + } + + freeList(entList); + + return ret; +} + +static Directory *addDirectoryPathToDB(char *utf8path, char **shortname) +{ + char *parent; + Directory *parentDirectory; + void *directory; + + parent = xstrdup(parentPath(utf8path)); + + if (strlen(parent) == 0) + parentDirectory = (void *)mp3rootDirectory; + else + parentDirectory = addDirectoryPathToDB(parent, shortname); + + if (!parentDirectory) { + free(parent); + return NULL; + } + + *shortname = utf8path + strlen(parent); + while (*(*shortname) && *(*shortname) == '/') + (*shortname)++; + + if (!findInList + (parentDirectory->subDirectories, *shortname, &directory)) { + struct stat st; + if (myStat(utf8path, &st) < 0 || + inodeFoundInParent(parentDirectory, st.st_ino, st.st_dev)) { + free(parent); + return NULL; + } else { + directory = newDirectory(utf8path, parentDirectory); + insertInList(parentDirectory->subDirectories, + *shortname, directory); + } + } + + /* if we're adding directory paths, make sure to delete filenames + with potentially the same name */ + removeSongFromDirectory(parentDirectory, *shortname); + + free(parent); + + return (Directory *) directory; +} + +static Directory *addParentPathToDB(char *utf8path, char **shortname) +{ + char *parent; + Directory *parentDirectory; + + parent = xstrdup(parentPath(utf8path)); + + if (strlen(parent) == 0) + parentDirectory = (void *)mp3rootDirectory; + else + parentDirectory = addDirectoryPathToDB(parent, shortname); + + if (!parentDirectory) { + free(parent); + return NULL; + } + + *shortname = utf8path + strlen(parent); + while (*(*shortname) && *(*shortname) == '/') + (*shortname)++; + + free(parent); + + return (Directory *) parentDirectory; +} + +/* return values: + -1 -> error + 0 -> no error, but nothing updated + 1 -> no error, and stuff updated + */ +static int updatePath(char *utf8path) +{ + Directory *directory; + Directory *parentDirectory; + Song *song; + char *shortname; + char *path = sanitizePathDup(utf8path); + time_t mtime; + int ret = 0; + + if (NULL == path) + return -1; + + /* if path is in the DB try to update it, or else delete it */ + if ((directory = getDirectoryDetails(path, &shortname))) { + parentDirectory = directory->parent; + + /* if this update directory is successfull, we are done */ + if ((ret = updateDirectory(directory)) >= 0) { + free(path); + sortDirectory(directory); + return ret; + } + /* we don't want to delete the root directory */ + else if (directory == mp3rootDirectory) { + free(path); + return 0; + } + /* if updateDirectory fails, means we should delete it */ + else { + LOG("removing directory: %s\n", path); + deleteFromList(parentDirectory->subDirectories, + shortname); + ret = 1; + /* don't return, path maybe a song now */ + } + } else if ((song = getSongDetails(path, &shortname, &parentDirectory))) { + if (!parentDirectory->stat + && statDirectory(parentDirectory) < 0) { + free(path); + return 0; + } + /* if this song update is successfull, we are done */ + else if (0 == inodeFoundInParent(parentDirectory->parent, + parentDirectory->stat->inode, + parentDirectory->stat->device) + && song && isMusic(getSongUrl(song), &mtime, 0)) { + free(path); + if (song->mtime == mtime) + return 0; + else if (updateSongInfo(song) == 0) + return 1; + else { + removeSongFromDirectory(parentDirectory, + shortname); + return 1; + } + } + /* if updateDirectory fails, means we should delete it */ + else { + removeSongFromDirectory(parentDirectory, shortname); + ret = 1; + /* don't return, path maybe a directory now */ + } + } + + /* path not found in the db, see if it actually exists on the fs. + * Also, if by chance a directory was replaced by a file of the same + * name or vice versa, we need to add it to the db + */ + if (isDir(path) || isMusic(path, NULL, 0)) { + parentDirectory = addParentPathToDB(path, &shortname); + if (!parentDirectory || (!parentDirectory->stat && + statDirectory(parentDirectory) < 0)) { + } else if (0 == inodeFoundInParent(parentDirectory->parent, + parentDirectory->stat->inode, + parentDirectory->stat-> + device) + && addToDirectory(parentDirectory, shortname, path) + > 0) { + ret = 1; + } + } + + free(path); + + return ret; +} + +/* return values: + -1 -> error + 0 -> no error, but nothing updated + 1 -> no error, and stuff updated + */ +static int updateDirectory(Directory * directory) +{ + DIR *dir; + char cwd[2]; + struct dirent *ent; + char *s; + char *utf8; + char *dirname = getDirectoryPath(directory); + int ret = 0; + + { + if (!directory->stat && statDirectory(directory) < 0) { + return -1; + } else if (inodeFoundInParent(directory->parent, + directory->stat->inode, + directory->stat->device)) { + return -1; + } + } + + cwd[0] = '.'; + cwd[1] = '\0'; + if (dirname == NULL) + dirname = cwd; + + if ((dir = opendir(rmp2amp(utf8ToFsCharset(dirname)))) == NULL) + return -1; + + if (removeDeletedFromDirectory(directory, dir) > 0) + ret = 1; + + rewinddir(dir); + + while ((ent = readdir(dir))) { + if (ent->d_name[0] == '.') + continue; /* hide hidden stuff */ + if (strchr(ent->d_name, '\n')) + continue; + + utf8 = fsCharsetToUtf8(ent->d_name); + + if (!utf8) + continue; + + utf8 = xstrdup(utf8); + + if (directory->path) { + s = xmalloc(strlen(getDirectoryPath(directory)) + + strlen(utf8) + 2); + sprintf(s, "%s/%s", getDirectoryPath(directory), utf8); + } else + s = xstrdup(utf8); + if (updateInDirectory(directory, utf8, s) > 0) + ret = 1; + free(utf8); + free(s); + } + + closedir(dir); + + return ret; +} + +/* return values: + -1 -> error + 0 -> no error, but nothing found + 1 -> no error, and stuff found + */ +static int exploreDirectory(Directory * directory) +{ + DIR *dir; + char cwd[2]; + struct dirent *ent; + char *s; + char *utf8; + char *dirname = getDirectoryPath(directory); + int ret = 0; + + cwd[0] = '.'; + cwd[1] = '\0'; + if (dirname == NULL) + dirname = cwd; + + DEBUG("explore: attempting to opendir: %s\n", dirname); + if ((dir = opendir(rmp2amp(utf8ToFsCharset(dirname)))) == NULL) + return -1; + + DEBUG("explore: %s\n", dirname); + while ((ent = readdir(dir))) { + if (ent->d_name[0] == '.') + continue; /* hide hidden stuff */ + if (strchr(ent->d_name, '\n')) + continue; + + utf8 = fsCharsetToUtf8(ent->d_name); + + if (!utf8) + continue; + + utf8 = xstrdup(utf8); + + DEBUG("explore: found: %s (%s)\n", ent->d_name, utf8); + + if (directory->path) { + s = xmalloc(strlen(getDirectoryPath(directory)) + + strlen(utf8) + 2); + sprintf(s, "%s/%s", getDirectoryPath(directory), utf8); + } else + s = xstrdup(utf8); + if (addToDirectory(directory, utf8, s) > 0) + ret = 1; + free(utf8); + free(s); + } + + closedir(dir); + + return ret; +} + +static int statDirectory(Directory * dir) +{ + struct stat st; + + if (myStat(getDirectoryPath(dir) ? getDirectoryPath(dir) : "", &st) < 0) + { + return -1; + } + + dir->stat = newDirectoryStat(&st); + + return 0; +} + +static int inodeFoundInParent(Directory * parent, ino_t inode, dev_t device) +{ + while (parent) { + if (!parent->stat) { + if (statDirectory(parent) < 0) + return -1; + } + if (parent->stat->inode == inode && + parent->stat->device == device) { + DEBUG("recursive directory found\n"); + return 1; + } + parent = parent->parent; + } + + return 0; +} + +static int addSubDirectoryToDirectory(Directory * directory, char *shortname, + char *name, struct stat *st) +{ + Directory *subDirectory; + + if (inodeFoundInParent(directory, st->st_ino, st->st_dev)) + return 0; + + subDirectory = newDirectory(name, directory); + subDirectory->stat = newDirectoryStat(st); + + if (exploreDirectory(subDirectory) < 1) { + freeDirectory(subDirectory); + return 0; + } + + insertInList(directory->subDirectories, shortname, subDirectory); + + return 1; +} + +static int addToDirectory(Directory * directory, char *shortname, char *name) +{ + struct stat st; + + if (myStat(name, &st)) { + DEBUG("failed to stat %s: %s\n", name, strerror(errno)); + return -1; + } + + if (S_ISREG(st.st_mode) && hasMusicSuffix(name, 0)) { + Song *song; + song = addSongToList(directory->songs, shortname, name, + SONG_TYPE_FILE, directory); + if (!song) + return -1; + LOG("added %s\n", name); + return 1; + } else if (S_ISDIR(st.st_mode)) { + return addSubDirectoryToDirectory(directory, shortname, name, + &st); + } + + DEBUG("addToDirectory: %s is not a directory or music\n", name); + + return -1; +} + +void closeMp3Directory(void) +{ + freeDirectory(mp3rootDirectory); +} + +static Directory *findSubDirectory(Directory * directory, char *name) +{ + void *subDirectory; + char *dup = xstrdup(name); + char *key; + + key = strtok(dup, "/"); + if (!key) { + free(dup); + return NULL; + } + + if (findInList(directory->subDirectories, key, &subDirectory)) { + free(dup); + return (Directory *) subDirectory; + } + + free(dup); + return NULL; +} + +int isRootDirectory(char *name) +{ + if (name == NULL || name[0] == '\0' || strcmp(name, "/") == 0) { + return 1; + } + return 0; +} + +static Directory *getSubDirectory(Directory * directory, char *name, + char **shortname) +{ + Directory *subDirectory; + int len; + + if (isRootDirectory(name)) { + return directory; + } + + if ((subDirectory = findSubDirectory(directory, name)) == NULL) + return NULL; + + *shortname = name; + + len = 0; + while (name[len] != '/' && name[len] != '\0') + len++; + while (name[len] == '/') + len++; + + return getSubDirectory(subDirectory, &(name[len]), shortname); +} + +static Directory *getDirectoryDetails(char *name, char **shortname) +{ + *shortname = NULL; + + return getSubDirectory(mp3rootDirectory, name, shortname); +} + +static Directory *getDirectory(char *name) +{ + char *shortname; + + return getSubDirectory(mp3rootDirectory, name, &shortname); +} + +static int printDirectoryList(int fd, DirectoryList * directoryList) +{ + ListNode *node = directoryList->firstNode; + Directory *directory; + + while (node != NULL) { + directory = (Directory *) node->data; + fdprintf(fd, "%s%s\n", DIRECTORY_DIR, + getDirectoryPath(directory)); + node = node->nextNode; + } + + return 0; +} + +int printDirectoryInfo(int fd, char *name) +{ + Directory *directory; + + if ((directory = getDirectory(name)) == NULL) { + commandError(fd, ACK_ERROR_NO_EXIST, "directory not found"); + return -1; + } + + printDirectoryList(fd, directory->subDirectories); + printSongInfoFromList(fd, directory->songs); + + return 0; +} + +static void writeDirectoryInfo(FILE * fp, Directory * directory) +{ + ListNode *node = (directory->subDirectories)->firstNode; + Directory *subDirectory; + + if (directory->path) { + fprintf(fp, "%s%s\n", DIRECTORY_BEGIN, + getDirectoryPath(directory)); + } + + while (node != NULL) { + subDirectory = (Directory *) node->data; + fprintf(fp, "%s%s\n", DIRECTORY_DIR, node->key); + writeDirectoryInfo(fp, subDirectory); + node = node->nextNode; + } + + writeSongInfoFromList(fp, directory->songs); + + if (directory->path) { + fprintf(fp, "%s%s\n", DIRECTORY_END, + getDirectoryPath(directory)); + } +} + +static void readDirectoryInfo(FILE * fp, Directory * directory) +{ + char buffer[MAXPATHLEN * 2]; + int bufferSize = MAXPATHLEN * 2; + char *key; + Directory *subDirectory; + int strcmpRet; + char *name; + ListNode *nextDirNode = directory->subDirectories->firstNode; + ListNode *nodeTemp; + + while (myFgets(buffer, bufferSize, fp) + && 0 != strncmp(DIRECTORY_END, buffer, strlen(DIRECTORY_END))) { + if (0 == strncmp(DIRECTORY_DIR, buffer, strlen(DIRECTORY_DIR))) { + key = xstrdup(&(buffer[strlen(DIRECTORY_DIR)])); + if (!myFgets(buffer, bufferSize, fp)) + FATAL("Error reading db, fgets\n"); + /* for compatibility with db's prior to 0.11 */ + if (0 == strncmp(DIRECTORY_MTIME, buffer, + strlen(DIRECTORY_MTIME))) { + if (!myFgets(buffer, bufferSize, fp)) + FATAL("Error reading db, fgets\n"); + } + if (strncmp + (DIRECTORY_BEGIN, buffer, + strlen(DIRECTORY_BEGIN))) { + FATAL("Error reading db at line: %s\n", buffer); + } + name = xstrdup(&(buffer[strlen(DIRECTORY_BEGIN)])); + + while (nextDirNode && (strcmpRet = + strcmp(key, + nextDirNode->key)) > 0) { + nodeTemp = nextDirNode->nextNode; + deleteNodeFromList(directory->subDirectories, + nextDirNode); + nextDirNode = nodeTemp; + } + + if (NULL == nextDirNode) { + subDirectory = newDirectory(name, directory); + insertInList(directory->subDirectories, + key, (void *)subDirectory); + } else if (strcmpRet == 0) { + subDirectory = (Directory *) nextDirNode->data; + nextDirNode = nextDirNode->nextNode; + } else { + subDirectory = newDirectory(name, directory); + insertInListBeforeNode(directory-> + subDirectories, + nextDirNode, -1, key, + (void *)subDirectory); + } + + free(name); + free(key); + readDirectoryInfo(fp, subDirectory); + } else if (0 == strncmp(SONG_BEGIN, buffer, strlen(SONG_BEGIN))) { + readSongInfoIntoList(fp, directory->songs, directory); + } else { + FATAL("Unknown line in db: %s\n", buffer); + } + } + + while (nextDirNode) { + nodeTemp = nextDirNode->nextNode; + deleteNodeFromList(directory->subDirectories, nextDirNode); + nextDirNode = nodeTemp; + } +} + +static void sortDirectory(Directory * directory) +{ + ListNode *node = directory->subDirectories->firstNode; + Directory *subDir; + + sortList(directory->subDirectories); + sortList(directory->songs); + + while (node != NULL) { + subDir = (Directory *) node->data; + sortDirectory(subDir); + node = node->nextNode; + } +} + +int checkDirectoryDB(void) +{ + struct stat st; + char *dbFile; + char *dirPath; + char *dbPath; + + dbFile = getDbFile(); + + /* Check if the file exists */ + if (access(dbFile, F_OK)) { + /* If the file doesn't exist, we can't check if we can write + * it, so we are going to try to get the directory path, and + * see if we can write a file in that */ + dbPath = xstrdup(dbFile); + dirPath = dirname(dbPath); + + /* Check that the parent part of the path is a directory */ + if (stat(dirPath, &st) < 0) { + ERROR("Couldn't stat parent directory of db file " + "\"%s\": %s\n", dbFile, strerror(errno)); + free(dbPath); + return -1; + } + + if (!S_ISDIR(st.st_mode)) { + ERROR("Couldn't create db file \"%s\" because the " + "parent path is not a directory\n", dbFile); + free(dbPath); + return -1; + } + + /* Check if we can write to the directory */ + if (access(dirPath, R_OK | W_OK)) { + ERROR("Can't create db file in \"%s\": %s\n", dirPath, + strerror(errno)); + free(dbPath); + return -1; + + } + + free(dbPath); + return 0; + } + + /* Path exists, now check if it's a regular file */ + if (stat(dbFile, &st) < 0) { + ERROR("Couldn't stat db file \"%s\": %s\n", dbFile, + strerror(errno)); + return -1; + } + + if (!S_ISREG(st.st_mode)) { + ERROR("db file \"%s\" is not a regular file\n", dbFile); + return -1; + } + + /* And check that we can write to it */ + if (access(dbFile, R_OK | W_OK)) { + ERROR("Can't open db file \"%s\" for reading/writing: %s\n", + dbFile, strerror(errno)); + return -1; + } + + return 0; +} + +int writeDirectoryDB(void) +{ + FILE *fp; + char *dbFile = getDbFile(); + struct stat st; + + DEBUG("removing empty directories from DB\n"); + deleteEmptyDirectoriesInDirectory(mp3rootDirectory); + + DEBUG("sorting DB\n"); + + sortDirectory(mp3rootDirectory); + + DEBUG("writing DB\n"); + + while (!(fp = fopen(dbFile, "w")) && errno == EINTR); + if (!fp) { + ERROR("unable to write to db file \"%s\": %s\n", + dbFile, strerror(errno)); + return -1; + } + + /* block signals when writing the db so we don't get a corrupted db */ + fprintf(fp, "%s\n", DIRECTORY_INFO_BEGIN); + fprintf(fp, "%s%s\n", DIRECTORY_MPD_VERSION, VERSION); + fprintf(fp, "%s%s\n", DIRECTORY_FS_CHARSET, getFsCharset()); + fprintf(fp, "%s\n", DIRECTORY_INFO_END); + + writeDirectoryInfo(fp, mp3rootDirectory); + + while (fclose(fp) && errno == EINTR); + + if (stat(dbFile, &st) == 0) + directory_dbModTime = st.st_mtime; + + return 0; +} + +int readDirectoryDB(void) +{ + FILE *fp = NULL; + char *dbFile = getDbFile(); + struct stat st; + + if (!mp3rootDirectory) + mp3rootDirectory = newDirectory(NULL, NULL); + while (!(fp = fopen(dbFile, "r")) && errno == EINTR) ; + if (fp == NULL) { + ERROR("unable to open db file \"%s\": %s\n", + dbFile, strerror(errno)); + return -1; + } + + /* get initial info */ + { + char buffer[100]; + int bufferSize = 100; + int foundFsCharset = 0; + int foundVersion = 0; + + if (!myFgets(buffer, bufferSize, fp)) + FATAL("Error reading db, fgets\n"); + if (0 == strcmp(DIRECTORY_INFO_BEGIN, buffer)) { + while (myFgets(buffer, bufferSize, fp) && + 0 != strcmp(DIRECTORY_INFO_END, buffer)) { + if (0 == strncmp(DIRECTORY_MPD_VERSION, buffer, + strlen(DIRECTORY_MPD_VERSION))) + { + if (foundVersion) + FATAL("already found version in db\n"); + foundVersion = 1; + } else if (0 == + strncmp(DIRECTORY_FS_CHARSET, buffer, + strlen + (DIRECTORY_FS_CHARSET))) { + char *fsCharset; + char *tempCharset; + + if (foundFsCharset) + FATAL("already found fs charset in db\n"); + + foundFsCharset = 1; + + fsCharset = &(buffer[strlen(DIRECTORY_FS_CHARSET)]); + if ((tempCharset = getConfigParamValue(CONF_FS_CHARSET)) + && strcmp(fsCharset, tempCharset)) { + WARNING("Using \"%s\" for the " + "filesystem charset " + "instead of \"%s\"\n", + fsCharset, tempCharset); + WARNING("maybe you need to " + "recreate the db?\n"); + setFsCharset(fsCharset); + } + } else { + FATAL("directory: unknown line in db info: %s\n", + buffer); + } + } + } else { + ERROR("db info not found in db file\n"); + ERROR("you should recreate the db using --create-db\n"); + fseek(fp, 0, SEEK_SET); + return -1; + } + } + + DEBUG("reading DB\n"); + + readDirectoryInfo(fp, mp3rootDirectory); + while (fclose(fp) && errno == EINTR) ; + + stats.numberOfSongs = countSongsIn(STDERR_FILENO, NULL); + stats.dbPlayTime = sumSongTimesIn(STDERR_FILENO, NULL); + + if (stat(dbFile, &st) == 0) + directory_dbModTime = st.st_mtime; + + return 0; +} + +void updateMp3Directory(void) +{ + switch (updateDirectory(mp3rootDirectory)) { + case 0: + /* nothing updated */ + return; + case 1: + if (writeDirectoryDB() < 0) + exit(EXIT_FAILURE); + break; + default: + /* something was updated and db should be written */ + FATAL("problems updating music db\n"); + } + + return; +} + +static int traverseAllInSubDirectory(int fd, Directory * directory, + int (*forEachSong) (int, Song *, + void *), + int (*forEachDir) (int, Directory *, + void *), void *data) +{ + ListNode *node = directory->songs->firstNode; + Song *song; + Directory *dir; + int errFlag = 0; + + if (forEachDir) { + errFlag = forEachDir(fd, directory, data); + if (errFlag) + return errFlag; + } + + if (forEachSong) { + while (node != NULL && !errFlag) { + song = (Song *) node->data; + errFlag = forEachSong(fd, song, data); + node = node->nextNode; + } + if (errFlag) + return errFlag; + } + + node = directory->subDirectories->firstNode; + + while (node != NULL && !errFlag) { + dir = (Directory *) node->data; + errFlag = traverseAllInSubDirectory(fd, dir, forEachSong, + forEachDir, data); + node = node->nextNode; + } + + return errFlag; +} + +int traverseAllIn(int fd, char *name, + int (*forEachSong) (int, Song *, void *), + int (*forEachDir) (int, Directory *, void *), void *data) +{ + Directory *directory; + + if ((directory = getDirectory(name)) == NULL) { + Song *song; + if ((song = getSongFromDB(name)) && forEachSong) { + return forEachSong(fd, song, data); + } + commandError(fd, ACK_ERROR_NO_EXIST, + "directory or file not found"); + return -1; + } + + return traverseAllInSubDirectory(fd, directory, forEachSong, forEachDir, + data); +} + +static void freeAllDirectoryStats(Directory * directory) +{ + ListNode *node = directory->subDirectories->firstNode; + + while (node != NULL) { + freeAllDirectoryStats((Directory *) node->data); + node = node->nextNode; + } + + freeDirectoryStatFromDirectory(directory); +} + +void initMp3Directory(void) +{ + mp3rootDirectory = newDirectory(NULL, NULL); + exploreDirectory(mp3rootDirectory); + freeAllDirectoryStats(mp3rootDirectory); + stats.numberOfSongs = countSongsIn(STDERR_FILENO, NULL); + stats.dbPlayTime = sumSongTimesIn(STDERR_FILENO, NULL); +} + +static Song *getSongDetails(char *file, char **shortnameRet, + Directory ** directoryRet) +{ + void *song = NULL; + Directory *directory; + char *dir = NULL; + char *dup = xstrdup(file); + char *shortname = dup; + char *c = strtok(dup, "/"); + + DEBUG("get song: %s\n", file); + + while (c) { + shortname = c; + c = strtok(NULL, "/"); + } + + if (shortname != dup) { + for (c = dup; c < shortname - 1; c++) { + if (*c == '\0') + *c = '/'; + } + dir = dup; + } + + if (!(directory = getDirectory(dir))) { + free(dup); + return NULL; + } + + if (!findInList(directory->songs, shortname, &song)) { + free(dup); + return NULL; + } + + free(dup); + if (shortnameRet) + *shortnameRet = shortname; + if (directoryRet) + *directoryRet = directory; + return (Song *) song; +} + +Song *getSongFromDB(char *file) +{ + return getSongDetails(file, NULL, NULL); +} + +time_t getDbModTime(void) +{ + return directory_dbModTime; +} diff --git a/trunk/src/directory.h b/trunk/src/directory.h new file mode 100644 index 000000000..b1482988f --- /dev/null +++ b/trunk/src/directory.h @@ -0,0 +1,76 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef DIRECTORY_H +#define DIRECTORY_H + +#include "../config.h" + +#include "song.h" +#include "list.h" + +typedef List DirectoryList; + +typedef struct _DirectoryStat { + ino_t inode; + dev_t device; +} DirectoryStat; + +typedef struct _Directory { + char *path; + DirectoryList *subDirectories; + SongList *songs; + struct _Directory *parent; + DirectoryStat *stat; +} Directory; + +void readDirectoryDBIfUpdateIsFinished(void); + +int isUpdatingDB(void); + +void directory_sigChldHandler(int pid, int status); + +int updateInit(int fd, List * pathList); + +void initMp3Directory(void); + +void closeMp3Directory(void); + +int isRootDirectory(char *name); + +int printDirectoryInfo(int fd, char *dirname); + +int checkDirectoryDB(void); + +int writeDirectoryDB(void); + +int readDirectoryDB(void); + +void updateMp3Directory(void); + +Song *getSongFromDB(char *file); + +time_t getDbModTime(void); + +int traverseAllIn(int fd, char *name, + int (*forEachSong) (int, Song *, void *), + int (*forEachDir) (int, Directory *, void *), void *data); + +#define getDirectoryPath(dir) ((dir && dir->path) ? dir->path : "") + +#endif diff --git a/trunk/src/gcc.h b/trunk/src/gcc.h new file mode 100644 index 000000000..4ae18c46b --- /dev/null +++ b/trunk/src/gcc.h @@ -0,0 +1,67 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef MPD_GCC_H +#define MPD_GCC_H + +/* this allows us to take advantage of special gcc features while still + * allowing other compilers to compile: + * + * example taken from: http://rlove.org/log/2005102601 + */ + +#if defined(__GNUC__) && (__GNUC__ >= 3) +# define mpd_const __attribute__ ((const)) +# define mpd_deprecated __attribute__ ((deprecated)) +# define mpd_malloc __attribute__ ((malloc)) +# define mpd_must_check __attribute__ ((warn_unused_result)) +# define mpd_noreturn __attribute__ ((noreturn)) +# define mpd_packed __attribute__ ((packed)) +/* these are very useful for type checking */ +# define mpd_printf __attribute__ ((format(printf,1,2))) +# define mpd_fprintf __attribute__ ((format(printf,2,3))) +# define mpd_fprintf_ __attribute__ ((format(printf,3,4))) +# define mpd_pure __attribute__ ((pure)) +# define mpd_scanf __attribute__ ((format(scanf,1,2))) +# define mpd_unused __attribute__ ((unused)) +# define mpd_used __attribute__ ((used)) +/* # define inline inline __attribute__ ((always_inline)) */ +# define mpd_noinline __attribute__ ((noinline)) +# define mpd_likely(x) __builtin_expect (!!(x), 1) +# define mpd_unlikely(x) __builtin_expect (!!(x), 0) +#else +# define mpd_const +# define mpd_deprecated +# define mpd_malloc +# define mpd_must_check +# define mpd_noreturn +# define mpd_packed +# define mpd_printf +# define mpd_fprintf +# define mpd_fprintf_ +# define mpd_pure +# define mpd_scanf +# define mpd_unused +# define mpd_used +/* # define inline */ +# define mpd_noinline +# define mpd_likely(x) (x) +# define mpd_unlikely(x) (x) +#endif + +#endif /* MPD_GCC_H */ diff --git a/trunk/src/inputPlugin.c b/trunk/src/inputPlugin.c new file mode 100644 index 000000000..60e60947b --- /dev/null +++ b/trunk/src/inputPlugin.c @@ -0,0 +1,158 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "inputPlugin.h" + +#include "list.h" +#include "myfprintf.h" + +#include <stdlib.h> +#include <string.h> + +static List *inputPlugin_list; + +void loadInputPlugin(InputPlugin * inputPlugin) +{ + if (!inputPlugin) + return; + if (!inputPlugin->name) + return; + + if (inputPlugin->initFunc && inputPlugin->initFunc() < 0) + return; + + insertInList(inputPlugin_list, inputPlugin->name, (void *)inputPlugin); +} + +void unloadInputPlugin(InputPlugin * inputPlugin) +{ + if (inputPlugin->finishFunc) + inputPlugin->finishFunc(); + deleteFromList(inputPlugin_list, inputPlugin->name); +} + +static int stringFoundInStringArray(char **array, char *suffix) +{ + while (array && *array) { + if (strcasecmp(*array, suffix) == 0) + return 1; + array++; + } + + return 0; +} + +InputPlugin *getInputPluginFromSuffix(char *suffix, unsigned int next) +{ + static ListNode *pos; + ListNode *node; + InputPlugin *plugin; + + if (suffix == NULL) + return NULL; + + if (next) { + if (pos) + node = pos; + else + return NULL; + } else + node = inputPlugin_list->firstNode; + + while (node != NULL) { + plugin = node->data; + if (stringFoundInStringArray(plugin->suffixes, suffix)) { + pos = node->nextNode; + return plugin; + } + node = node->nextNode; + } + + return NULL; +} + +InputPlugin *getInputPluginFromMimeType(char *mimeType, unsigned int next) +{ + static ListNode *pos; + ListNode *node; + InputPlugin *plugin; + + if (mimeType == NULL) + return NULL; + + node = (next && pos) ? pos : inputPlugin_list->firstNode; + + while (node != NULL) { + plugin = node->data; + if (stringFoundInStringArray(plugin->mimeTypes, mimeType)) { + pos = node->nextNode; + return plugin; + } + node = node->nextNode; + } + + return NULL; +} + +InputPlugin *getInputPluginFromName(char *name) +{ + void *plugin = NULL; + + findInList(inputPlugin_list, name, &plugin); + + return (InputPlugin *) plugin; +} + +void printAllInputPluginSuffixes(FILE * fp) +{ + ListNode *node = inputPlugin_list->firstNode; + InputPlugin *plugin; + char **suffixes; + + while (node) { + plugin = (InputPlugin *) node->data; + suffixes = plugin->suffixes; + while (suffixes && *suffixes) { + fprintf(fp, "%s ", *suffixes); + suffixes++; + } + node = node->nextNode; + } + fprintf(fp, "\n"); + fflush(fp); +} + +void initInputPlugins(void) +{ + inputPlugin_list = makeList(NULL, 1); + + /* load plugins here */ + loadInputPlugin(&mp3Plugin); + loadInputPlugin(&oggvorbisPlugin); + loadInputPlugin(&oggflacPlugin); + loadInputPlugin(&flacPlugin); + loadInputPlugin(&audiofilePlugin); + loadInputPlugin(&mp4Plugin); + loadInputPlugin(&mpcPlugin); + loadInputPlugin(&modPlugin); +} + +void finishInputPlugins(void) +{ + freeList(inputPlugin_list); +} diff --git a/trunk/src/inputPlugin.h b/trunk/src/inputPlugin.h new file mode 100644 index 000000000..398ddc1cb --- /dev/null +++ b/trunk/src/inputPlugin.h @@ -0,0 +1,109 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef INPUT_PLUGIN_H +#define INPUT_PLUGIN_H + +#include "../config.h" +#include "inputStream.h" +#include "decode.h" +#include "outputBuffer.h" +#include "tag.h" + +/* valid values for streamTypes in the InputPlugin struct: */ +#define INPUT_PLUGIN_STREAM_FILE 0x01 +#define INPUT_PLUGIN_STREAM_URL 0x02 + +/* optional, set this to NULL if the InputPlugin doesn't have/need one + * this must return < 0 if there is an error and >= 0 otherwise */ +typedef int (*InputPlugin_initFunc) (); + +/* optional, set this to NULL if the InputPlugin doesn't have/need one */ +typedef void (*InputPlugin_finishFunc) (); + +/* boolean return value, returns 1 if the InputStream is decodable by + * the InputPlugin, 0 if not */ +typedef unsigned int (*InputPlugin_tryDecodeFunc) (InputStream *); + +/* this will be used to decode InputStreams, and is recommended for files + * and networked (HTTP) connections. + * + * returns -1 on error, 0 on success */ +typedef int (*InputPlugin_streamDecodeFunc) (OutputBuffer *, DecoderControl *, + InputStream *); + +/* use this if and only if your InputPlugin can only be passed a filename or + * handle as input, and will not allow callbacks to be set (like Ogg-Vorbis + * and FLAC libraries allow) + * + * returns -1 on error, 0 on success */ +typedef int (*InputPlugin_fileDecodeFunc) (OutputBuffer *, DecoderControl *, + char *path); + +/* file should be the full path! Returns NULL if a tag cannot be found + * or read */ +typedef MpdTag *(*InputPlugin_tagDupFunc) (char *file); + +typedef struct _InputPlugin { + char *name; + InputPlugin_initFunc initFunc; + InputPlugin_finishFunc finishFunc; + InputPlugin_tryDecodeFunc tryDecodeFunc; + InputPlugin_streamDecodeFunc streamDecodeFunc; + InputPlugin_fileDecodeFunc fileDecodeFunc; + InputPlugin_tagDupFunc tagDupFunc; + + /* one or more of the INPUT_PLUGIN_STREAM_* values OR'd together */ + unsigned char streamTypes; + + /* last element in these arrays must always be a NULL: */ + char **suffixes; + char **mimeTypes; +} InputPlugin; + +/* individual functions to load/unload plugins */ +void loadInputPlugin(InputPlugin * inputPlugin); +void unloadInputPlugin(InputPlugin * inputPlugin); + +/* interface for using plugins */ + +InputPlugin *getInputPluginFromSuffix(char *suffix, unsigned int next); + +InputPlugin *getInputPluginFromMimeType(char *mimeType, unsigned int next); + +InputPlugin *getInputPluginFromName(char *name); + +void printAllInputPluginSuffixes(FILE * fp); + +/* this is where we "load" all the "plugins" ;-) */ +void initInputPlugins(void); + +/* this is where we "unload" all the "plugins" */ +void finishInputPlugins(void); + +extern InputPlugin mp3Plugin; +extern InputPlugin oggvorbisPlugin; +extern InputPlugin flacPlugin; +extern InputPlugin oggflacPlugin; +extern InputPlugin audiofilePlugin; +extern InputPlugin mp4Plugin; +extern InputPlugin mpcPlugin; +extern InputPlugin aacPlugin; +extern InputPlugin modPlugin; + +#endif diff --git a/trunk/src/inputPlugins/_flac_common.c b/trunk/src/inputPlugins/_flac_common.c new file mode 100644 index 000000000..11126cd1b --- /dev/null +++ b/trunk/src/inputPlugins/_flac_common.c @@ -0,0 +1,211 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * Common data structures and functions used by FLAC and OggFLAC + * (c) 2005 by Eric Wong <normalperson@yhbt.net> + * + * 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 + */ + +#include "../inputPlugin.h" + +#if defined(HAVE_FLAC) || defined(HAVE_OGGFLAC) + +#include "_flac_common.h" + +#include "../log.h" +#include "../tag.h" +#include "../inputStream.h" +#include "../outputBuffer.h" +#include "../decode.h" +#include "../replayGain.h" + +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <FLAC/format.h> +#include <FLAC/metadata.h> + +void init_FlacData(FlacData * data, OutputBuffer * cb, + DecoderControl * dc, InputStream * inStream) +{ + data->chunk_length = 0; + data->time = 0; + data->position = 0; + data->bitRate = 0; + data->cb = cb; + data->dc = dc; + data->inStream = inStream; + data->replayGainInfo = NULL; + data->tag = NULL; +} + +static int flacFindVorbisCommentFloat(const FLAC__StreamMetadata * block, + const char *cmnt, float *fl) +{ + int offset = + FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0, cmnt); + + if (offset >= 0) { + size_t pos = strlen(cmnt) + 1; /* 1 is for '=' */ + int len = block->data.vorbis_comment.comments[offset].length + - pos; + if (len > 0) { + unsigned char tmp; + unsigned char *dup = &(block->data.vorbis_comment. + comments[offset].entry[pos]); + tmp = dup[len]; + dup[len] = '\0'; + *fl = atof((char *)dup); + dup[len] = tmp; + + return 1; + } + } + + return 0; +} + +/* replaygain stuff by AliasMrJones */ +static void flacParseReplayGain(const FLAC__StreamMetadata * block, + FlacData * data) +{ + int found = 0; + + if (data->replayGainInfo) + freeReplayGainInfo(data->replayGainInfo); + + data->replayGainInfo = newReplayGainInfo(); + + found |= flacFindVorbisCommentFloat(block, "replaygain_album_gain", + &data->replayGainInfo->albumGain); + found |= flacFindVorbisCommentFloat(block, "replaygain_album_peak", + &data->replayGainInfo->albumPeak); + found |= flacFindVorbisCommentFloat(block, "replaygain_track_gain", + &data->replayGainInfo->trackGain); + found |= flacFindVorbisCommentFloat(block, "replaygain_track_peak", + &data->replayGainInfo->trackPeak); + + if (!found) { + freeReplayGainInfo(data->replayGainInfo); + data->replayGainInfo = NULL; + } +} + +/* tracknumber is used in VCs, MPD uses "track" ..., all the other + * tag names match */ +static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber"; +static const char *VORBIS_COMMENT_DISC_KEY = "discnumber"; + +static unsigned int commentMatchesAddToTag(const + FLAC__StreamMetadata_VorbisComment_Entry + * entry, unsigned int itemType, + MpdTag ** tag) +{ + const char *str; + size_t slen; + int vlen; + + switch (itemType) { + case TAG_ITEM_TRACK: + str = VORBIS_COMMENT_TRACK_KEY; + break; + case TAG_ITEM_DISC: + str = VORBIS_COMMENT_DISC_KEY; + break; + default: + str = mpdTagItemKeys[itemType]; + } + slen = strlen(str); + vlen = entry->length - slen - 1; + + if ((vlen > 0) && (0 == strncasecmp(str, (char *)entry->entry, slen)) + && (*(entry->entry + slen) == '=')) { + if (!*tag) + *tag = newMpdTag(); + + addItemToMpdTagWithLen(*tag, itemType, + (char *)(entry->entry + slen + 1), vlen); + + return 1; + } + + return 0; +} + +MpdTag *copyVorbisCommentBlockToMpdTag(const FLAC__StreamMetadata * block, + MpdTag * tag) +{ + unsigned int i, j; + FLAC__StreamMetadata_VorbisComment_Entry *comments; + + comments = block->data.vorbis_comment.comments; + + for (i = block->data.vorbis_comment.num_comments; i != 0; --i) { + for (j = TAG_NUM_OF_ITEM_TYPES; j--;) { + if (commentMatchesAddToTag(comments, j, &tag)) + break; + } + comments++; + } + + return tag; +} + +void flac_metadata_common_cb(const FLAC__StreamMetadata * block, + FlacData * data) +{ + DecoderControl *dc = data->dc; + const FLAC__StreamMetadata_StreamInfo *si = &(block->data.stream_info); + + switch (block->type) { + case FLAC__METADATA_TYPE_STREAMINFO: + dc->audioFormat.bits = si->bits_per_sample; + dc->audioFormat.sampleRate = si->sample_rate; + dc->audioFormat.channels = si->channels; + dc->totalTime = ((float)si->total_samples) / (si->sample_rate); + getOutputAudioFormat(&(dc->audioFormat), + &(data->cb->audioFormat)); + break; + case FLAC__METADATA_TYPE_VORBIS_COMMENT: + flacParseReplayGain(block, data); + default: + break; + } +} + +void flac_error_common_cb(const char *plugin, + const FLAC__StreamDecoderErrorStatus status, + FlacData * data) +{ + if (data->dc->stop) + return; + + switch (status) { + case FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC: + ERROR("%s lost sync\n", plugin); + break; + case FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER: + ERROR("bad %s header\n", plugin); + break; + case FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH: + ERROR("%s crc mismatch\n", plugin); + break; + default: + ERROR("unknown %s error\n", plugin); + } +} + +#endif /* HAVE_FLAC || HAVE_OGGFLAC */ diff --git a/trunk/src/inputPlugins/_flac_common.h b/trunk/src/inputPlugins/_flac_common.h new file mode 100644 index 000000000..e04e70693 --- /dev/null +++ b/trunk/src/inputPlugins/_flac_common.h @@ -0,0 +1,187 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * Common data structures and functions used by FLAC and OggFLAC + * (c) 2005 by Eric Wong <normalperson@yhbt.net> + * + * 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 + */ + +#ifndef _FLAC_COMMON_H +#define _FLAC_COMMON_H + +#include "../inputPlugin.h" + +#if defined(HAVE_FLAC) || defined(HAVE_OGGFLAC) + +#include "../tag.h" +#include "../inputStream.h" +#include "../outputBuffer.h" +#include "../decode.h" +#include <FLAC/export.h> +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 +# include <FLAC/seekable_stream_decoder.h> +# define flac_decoder FLAC__SeekableStreamDecoder +# define flac_new() FLAC__seekable_stream_decoder_new() + +# define flac_ogg_init(a,b,c,d,e,f,g,h,i,j) (0) + +# define flac_get_decode_position(x,y) \ + FLAC__seekable_stream_decoder_get_decode_position(x,y) +# define flac_get_state(x) FLAC__seekable_stream_decoder_get_state(x) +# define flac_process_single(x) FLAC__seekable_stream_decoder_process_single(x) +# define flac_process_metadata(x) \ + FLAC__seekable_stream_decoder_process_until_end_of_metadata(x) +# define flac_seek_absolute(x,y) \ + FLAC__seekable_stream_decoder_seek_absolute(x,y) +# define flac_finish(x) FLAC__seekable_stream_decoder_finish(x) +# define flac_delete(x) FLAC__seekable_stream_decoder_delete(x) + +# define flac_decoder_eof FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM + +typedef unsigned flac_read_status_size_t; +# define flac_read_status FLAC__SeekableStreamDecoderReadStatus +# define flac_read_status_continue \ + FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK +# define flac_read_status_eof FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK +# define flac_read_status_abort \ + FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR + +# define flac_seek_status FLAC__SeekableStreamDecoderSeekStatus +# define flac_seek_status_ok FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK +# define flac_seek_status_error FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR + +# define flac_tell_status FLAC__SeekableStreamDecoderTellStatus +# define flac_tell_status_ok FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK +# define flac_tell_status_error \ + FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR +# define flac_tell_status_unsupported \ + FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_ERROR + +# define flac_length_status FLAC__SeekableStreamDecoderLengthStatus +# define flac_length_status_ok FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK +# define flac_length_status_error \ + FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR +# define flac_length_status_unsupported \ + FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_ERROR + +# ifdef HAVE_OGGFLAC +# include <OggFLAC/seekable_stream_decoder.h> +# endif +#else /* FLAC_API_VERSION_CURRENT >= 7 */ + + /* OggFLAC support is handled by our flac_plugin already, and + * thus we *can* always have it if libFLAC was compiled with it */ +# ifndef HAVE_OGGFLAC +# define HAVE_OGGFLAC 1 +# endif +# include "_ogg_common.h" +# undef HAVE_OGGFLAC /* we don't need this defined anymore */ + +# include <FLAC/stream_decoder.h> +# define flac_decoder FLAC__StreamDecoder +# define flac_new() FLAC__stream_decoder_new() + +# define flac_init(a,b,c,d,e,f,g,h,i,j) \ + (FLAC__stream_decoder_init_stream(a,b,c,d,e,f,g,h,i,j) \ + == FLAC__STREAM_DECODER_INIT_STATUS_OK) +# define flac_ogg_init(a,b,c,d,e,f,g,h,i,j) \ + (FLAC__stream_decoder_init_ogg_stream(a,b,c,d,e,f,g,h,i,j) \ + == FLAC__STREAM_DECODER_INIT_STATUS_OK) + +# define flac_get_decode_position(x,y) \ + FLAC__stream_decoder_get_decode_position(x,y) +# define flac_get_state(x) FLAC__stream_decoder_get_state(x) +# define flac_process_single(x) FLAC__stream_decoder_process_single(x) +# define flac_process_metadata(x) \ + FLAC__stream_decoder_process_until_end_of_metadata(x) +# define flac_seek_absolute(x,y) FLAC__stream_decoder_seek_absolute(x,y) +# define flac_finish(x) FLAC__stream_decoder_finish(x) +# define flac_delete(x) FLAC__stream_decoder_delete(x) + +# define flac_decoder_eof FLAC__STREAM_DECODER_END_OF_STREAM + +typedef size_t flac_read_status_size_t; +# define flac_read_status FLAC__StreamDecoderReadStatus +# define flac_read_status_continue \ + FLAC__STREAM_DECODER_READ_STATUS_CONTINUE +# define flac_read_status_eof FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM +# define flac_read_status_abort FLAC__STREAM_DECODER_READ_STATUS_ABORT + +# define flac_seek_status FLAC__StreamDecoderSeekStatus +# define flac_seek_status_ok FLAC__STREAM_DECODER_SEEK_STATUS_OK +# define flac_seek_status_error FLAC__STREAM_DECODER_SEEK_STATUS_ERROR +# define flac_seek_status_unsupported \ + FLAC__STREAM_DECODER_SEEK_STATUS_UNSUPPORTED + +# define flac_tell_status FLAC__StreamDecoderTellStatus +# define flac_tell_status_ok FLAC__STREAM_DECODER_TELL_STATUS_OK +# define flac_tell_status_error FLAC__STREAM_DECODER_TELL_STATUS_ERROR +# define flac_tell_status_unsupported \ + FLAC__STREAM_DECODER_TELL_STATUS_UNSUPPORTED + +# define flac_length_status FLAC__StreamDecoderLengthStatus +# define flac_length_status_ok FLAC__STREAM_DECODER_LENGTH_STATUS_OK +# define flac_length_status_error FLAC__STREAM_DECODER_LENGTH_STATUS_ERROR +# define flac_length_status_unsupported \ + FLAC__STREAM_DECODER_LENGTH_STATUS_UNSUPPORTED + +#endif /* FLAC_API_VERSION_CURRENT >= 7 */ + +#include <FLAC/metadata.h> + +#define FLAC_CHUNK_SIZE 4080 + +typedef struct { + unsigned char chunk[FLAC_CHUNK_SIZE]; + int chunk_length; + float time; + int bitRate; + FLAC__uint64 position; + OutputBuffer *cb; + DecoderControl *dc; + InputStream *inStream; + ReplayGainInfo *replayGainInfo; + MpdTag *tag; +} FlacData; + +/* initializes a given FlacData struct */ +void init_FlacData(FlacData * data, OutputBuffer * cb, + DecoderControl * dc, InputStream * inStream); +void flac_metadata_common_cb(const FLAC__StreamMetadata * block, + FlacData * data); +void flac_error_common_cb(const char *plugin, + FLAC__StreamDecoderErrorStatus status, + FlacData * data); + +MpdTag *copyVorbisCommentBlockToMpdTag(const FLAC__StreamMetadata * block, + MpdTag * tag); + +/* keep this inlined, this is just macro but prettier :) */ +static inline int flacSendChunk(FlacData * data) +{ + if (sendDataToOutputBuffer(data->cb, NULL, data->dc, 1, data->chunk, + data->chunk_length, data->time, + data->bitRate, + data->replayGainInfo) == + OUTPUT_BUFFER_DC_STOP) + return -1; + + return 0; +} + +#endif /* HAVE_FLAC || HAVE_OGGFLAC */ + +#endif /* _FLAC_COMMON_H */ diff --git a/trunk/src/inputPlugins/_ogg_common.c b/trunk/src/inputPlugins/_ogg_common.c new file mode 100644 index 000000000..c83e46103 --- /dev/null +++ b/trunk/src/inputPlugins/_ogg_common.c @@ -0,0 +1,73 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) + * (c) 2005 by Eric Wong <normalperson@yhbt.net> + * + * 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 + */ + +#include "../inputPlugin.h" + +#if defined(HAVE_OGGFLAC) || defined(HAVE_OGGVORBIS) + +#include "../utils.h" +#include "_ogg_common.h" + +#include <string.h> + +ogg_stream_type ogg_stream_type_detect(InputStream * inStream) +{ + /* oggflac detection based on code in ogg123 and this post + * http://lists.xiph.org/pipermail/flac/2004-December/000393.html + * ogg123 trunk still doesn't have this patch as of June 2005 */ + unsigned char buf[41]; + size_t r, to_read = 41; + + seekInputStream(inStream, 0, SEEK_SET); + + while (to_read) { + r = readFromInputStream(inStream, buf, 1, to_read); + if (inStream->error) + break; + to_read -= r; + if (!r && !inputStreamAtEOF(inStream)) + my_usleep(10000); + else + break; + } + + seekInputStream(inStream, 0, SEEK_SET); + + if (r >= 32 && memcmp(buf, "OggS", 4) == 0 && ((memcmp + (buf + 29, "FLAC", + 4) == 0 + && memcmp(buf + 37, + "fLaC", + 4) == 0) + || + (memcmp + (buf + 28, "FLAC", + 4) == 0) + || + (memcmp + (buf + 28, "fLaC", + 4) == 0))) { + return FLAC; + } + return VORBIS; +} + +#endif /* defined(HAVE_OGGFLAC || defined(HAVE_OGGVORBIS) */ diff --git a/trunk/src/inputPlugins/_ogg_common.h b/trunk/src/inputPlugins/_ogg_common.h new file mode 100644 index 000000000..5821e6641 --- /dev/null +++ b/trunk/src/inputPlugins/_ogg_common.h @@ -0,0 +1,35 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC) + * (c) 2005 by Eric Wong <normalperson@yhbt.net> + * + * 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 + */ + +#ifndef _OGG_COMMON_H +#define _OGG_COMMON_H + +#include "../inputPlugin.h" + +#if defined(HAVE_OGGFLAC) || defined(HAVE_OGGVORBIS) + +typedef enum _ogg_stream_type { VORBIS, FLAC } ogg_stream_type; + +ogg_stream_type ogg_stream_type_detect(InputStream * inStream); + +#endif /* defined(HAVE_OGGFLAC || defined(HAVE_OGGVORBIS) */ + +#endif /* _OGG_COMMON_H */ diff --git a/trunk/src/inputPlugins/aac_plugin.c b/trunk/src/inputPlugins/aac_plugin.c new file mode 100644 index 000000000..529689706 --- /dev/null +++ b/trunk/src/inputPlugins/aac_plugin.c @@ -0,0 +1,475 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "../inputPlugin.h" + +#ifdef HAVE_FAAD + +#define AAC_MAX_CHANNELS 6 + +#include "../utils.h" +#include "../audio.h" +#include "../log.h" +#include "../inputStream.h" +#include "../outputBuffer.h" + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <faad.h> + +/* all code here is either based on or copied from FAAD2's frontend code */ +typedef struct { + InputStream *inStream; + long bytesIntoBuffer; + long bytesConsumed; + long fileOffset; + unsigned char *buffer; + int atEof; +} AacBuffer; + +static void fillAacBuffer(AacBuffer * b) +{ + if (b->bytesConsumed > 0) { + int bread; + + if (b->bytesIntoBuffer) { + memmove((void *)b->buffer, (void *)(b->buffer + + b->bytesConsumed), + b->bytesIntoBuffer); + } + + if (!b->atEof) { + bread = readFromInputStream(b->inStream, + (void *)(b->buffer + + b-> + bytesIntoBuffer), + 1, b->bytesConsumed); + if (bread != b->bytesConsumed) + b->atEof = 1; + b->bytesIntoBuffer += bread; + } + + b->bytesConsumed = 0; + + if (b->bytesIntoBuffer > 3) { + if (memcmp(b->buffer, "TAG", 3) == 0) + b->bytesIntoBuffer = 0; + } + if (b->bytesIntoBuffer > 11) { + if (memcmp(b->buffer, "LYRICSBEGIN", 11) == 0) { + b->bytesIntoBuffer = 0; + } + } + if (b->bytesIntoBuffer > 8) { + if (memcmp(b->buffer, "APETAGEX", 8) == 0) { + b->bytesIntoBuffer = 0; + } + } + } +} + +static void advanceAacBuffer(AacBuffer * b, int bytes) +{ + b->fileOffset += bytes; + b->bytesConsumed = bytes; + b->bytesIntoBuffer -= bytes; +} + +static int adtsSampleRates[] = + { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, + 16000, 12000, 11025, 8000, 7350, 0, 0, 0 +}; + +static int adtsParse(AacBuffer * b, float *length) +{ + int frames, frameLength; + int tFrameLength = 0; + int sampleRate = 0; + float framesPerSec, bytesPerFrame; + + /* Read all frames to ensure correct time and bitrate */ + for (frames = 0;; frames++) { + fillAacBuffer(b); + + if (b->bytesIntoBuffer > 7) { + /* check syncword */ + if (!((b->buffer[0] == 0xFF) && + ((b->buffer[1] & 0xF6) == 0xF0))) { + break; + } + + if (frames == 0) { + sampleRate = adtsSampleRates[(b-> + buffer[2] & 0x3c) + >> 2]; + } + + frameLength = ((((unsigned int)b->buffer[3] & 0x3)) + << 11) | (((unsigned int)b->buffer[4]) + << 3) | (b->buffer[5] >> 5); + + tFrameLength += frameLength; + + if (frameLength > b->bytesIntoBuffer) + break; + + advanceAacBuffer(b, frameLength); + } else + break; + } + + framesPerSec = (float)sampleRate / 1024.0; + if (frames != 0) { + bytesPerFrame = (float)tFrameLength / (float)(frames * 1000); + } else + bytesPerFrame = 0; + if (framesPerSec != 0) + *length = (float)frames / framesPerSec; + + return 1; +} + +static void initAacBuffer(InputStream * inStream, AacBuffer * b, float *length, + size_t * retFileread, size_t * retTagsize) +{ + size_t fileread; + size_t bread; + size_t tagsize; + + if (length) + *length = -1; + + memset(b, 0, sizeof(AacBuffer)); + + b->inStream = inStream; + + fileread = inStream->size; + + b->buffer = xmalloc(FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); + memset(b->buffer, 0, FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); + + bread = readFromInputStream(inStream, b->buffer, 1, + FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS); + b->bytesIntoBuffer = bread; + b->bytesConsumed = 0; + b->fileOffset = 0; + + if (bread != FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS) + b->atEof = 1; + + tagsize = 0; + if (!memcmp(b->buffer, "ID3", 3)) { + tagsize = (b->buffer[6] << 21) | (b->buffer[7] << 14) | + (b->buffer[8] << 7) | (b->buffer[9] << 0); + + tagsize += 10; + advanceAacBuffer(b, tagsize); + fillAacBuffer(b); + } + + if (retFileread) + *retFileread = fileread; + if (retTagsize) + *retTagsize = tagsize; + + if (length == NULL) + return; + + if ((b->buffer[0] == 0xFF) && ((b->buffer[1] & 0xF6) == 0xF0)) { + adtsParse(b, length); + seekInputStream(b->inStream, tagsize, SEEK_SET); + + bread = readFromInputStream(b->inStream, b->buffer, 1, + FAAD_MIN_STREAMSIZE * + AAC_MAX_CHANNELS); + if (bread != FAAD_MIN_STREAMSIZE * AAC_MAX_CHANNELS) + b->atEof = 1; + else + b->atEof = 0; + b->bytesIntoBuffer = bread; + b->bytesConsumed = 0; + b->fileOffset = tagsize; + } else if (memcmp(b->buffer, "ADIF", 4) == 0) { + int bitRate; + int skipSize = (b->buffer[4] & 0x80) ? 9 : 0; + bitRate = + ((unsigned int)(b-> + buffer[4 + + skipSize] & 0x0F) << 19) | ((unsigned + int)b-> + buffer[5 + + + skipSize] + << 11) | + ((unsigned int)b-> + buffer[6 + skipSize] << 3) | ((unsigned int)b->buffer[7 + + skipSize] + & 0xE0); + + if (fileread != 0 && bitRate != 0) + *length = fileread * 8.0 / bitRate; + else + *length = fileread; + } +} + +static float getAacFloatTotalTime(char *file) +{ + AacBuffer b; + float length; + size_t fileread, tagsize; + faacDecHandle decoder; + faacDecConfigurationPtr config; + unsigned long sampleRate; + unsigned char channels; + InputStream inStream; + long bread; + + if (openInputStream(&inStream, file) < 0) + return -1; + + initAacBuffer(&inStream, &b, &length, &fileread, &tagsize); + + if (length < 0) { + decoder = faacDecOpen(); + + config = faacDecGetCurrentConfiguration(decoder); + config->outputFormat = FAAD_FMT_16BIT; + faacDecSetConfiguration(decoder, config); + + fillAacBuffer(&b); +#ifdef HAVE_FAAD_BUFLEN_FUNCS + bread = faacDecInit(decoder, b.buffer, b.bytesIntoBuffer, + &sampleRate, &channels); +#else + bread = faacDecInit(decoder, b.buffer, &sampleRate, &channels); +#endif + if (bread >= 0 && sampleRate > 0 && channels > 0) + length = 0; + + faacDecClose(decoder); + } + + if (b.buffer) + free(b.buffer); + closeInputStream(&inStream); + + return length; +} + +static int getAacTotalTime(char *file) +{ + int time = -1; + float length; + + if ((length = getAacFloatTotalTime(file)) >= 0) + time = length + 0.5; + + return time; +} + +static int aac_decode(OutputBuffer * cb, DecoderControl * dc, char *path) +{ + float time; + float totalTime; + faacDecHandle decoder; + faacDecFrameInfo frameInfo; + faacDecConfigurationPtr config; + long bread; + unsigned long sampleRate; + unsigned char channels; + int eof = 0; + unsigned int sampleCount; + char *sampleBuffer; + size_t sampleBufferLen; + /*float * seekTable; + long seekTableEnd = -1; + int seekPositionFound = 0; */ + mpd_uint16 bitRate = 0; + AacBuffer b; + InputStream inStream; + + if ((totalTime = getAacFloatTotalTime(path)) < 0) + return -1; + + if (openInputStream(&inStream, path) < 0) + return -1; + + initAacBuffer(&inStream, &b, NULL, NULL, NULL); + + decoder = faacDecOpen(); + + config = faacDecGetCurrentConfiguration(decoder); + config->outputFormat = FAAD_FMT_16BIT; +#ifdef HAVE_FAACDECCONFIGURATION_DOWNMATRIX + config->downMatrix = 1; +#endif +#ifdef HAVE_FAACDECCONFIGURATION_DONTUPSAMPLEIMPLICITSBR + config->dontUpSampleImplicitSBR = 0; +#endif + faacDecSetConfiguration(decoder, config); + + fillAacBuffer(&b); + +#ifdef HAVE_FAAD_BUFLEN_FUNCS + bread = faacDecInit(decoder, b.buffer, b.bytesIntoBuffer, + &sampleRate, &channels); +#else + bread = faacDecInit(decoder, b.buffer, &sampleRate, &channels); +#endif + if (bread < 0) { + ERROR("Error not a AAC stream.\n"); + faacDecClose(decoder); + closeInputStream(b.inStream); + if (b.buffer) + free(b.buffer); + return -1; + } + + dc->audioFormat.bits = 16; + + dc->totalTime = totalTime; + + time = 0.0; + + advanceAacBuffer(&b, bread); + + while (!eof) { + fillAacBuffer(&b); + + if (b.bytesIntoBuffer == 0) { + eof = 1; + break; + } +#ifdef HAVE_FAAD_BUFLEN_FUNCS + sampleBuffer = faacDecDecode(decoder, &frameInfo, b.buffer, + b.bytesIntoBuffer); +#else + sampleBuffer = faacDecDecode(decoder, &frameInfo, b.buffer); +#endif + + if (frameInfo.error > 0) { + ERROR("error decoding AAC file: %s\n", path); + ERROR("faad2 error: %s\n", + faacDecGetErrorMessage(frameInfo.error)); + eof = 1; + break; + } +#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE + sampleRate = frameInfo.samplerate; +#endif + + if (dc->state != DECODE_STATE_DECODE) { + dc->audioFormat.channels = frameInfo.channels; + dc->audioFormat.sampleRate = sampleRate; + getOutputAudioFormat(&(dc->audioFormat), + &(cb->audioFormat)); + dc->state = DECODE_STATE_DECODE; + } + + advanceAacBuffer(&b, frameInfo.bytesconsumed); + + sampleCount = (unsigned long)(frameInfo.samples); + + if (sampleCount > 0) { + bitRate = frameInfo.bytesconsumed * 8.0 * + frameInfo.channels * sampleRate / + frameInfo.samples / 1000 + 0.5; + time += + (float)(frameInfo.samples) / frameInfo.channels / + sampleRate; + } + + sampleBufferLen = sampleCount * 2; + + sendDataToOutputBuffer(cb, NULL, dc, 0, sampleBuffer, + sampleBufferLen, time, bitRate, NULL); + if (dc->seek) { + dc->seekError = 1; + dc->seek = 0; + } else if (dc->stop) { + eof = 1; + break; + } + } + + flushOutputBuffer(cb); + + faacDecClose(decoder); + closeInputStream(b.inStream); + if (b.buffer) + free(b.buffer); + + if (dc->state != DECODE_STATE_DECODE) + return -1; + + if (dc->seek) { + dc->seekError = 1; + dc->seek = 0; + } + + if (dc->stop) { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } else + dc->state = DECODE_STATE_STOP; + + return 0; +} + +static MpdTag *aacTagDup(char *file) +{ + MpdTag *ret = NULL; + int time; + + time = getAacTotalTime(file); + + if (time >= 0) { + if ((ret = id3Dup(file)) == NULL) + ret = newMpdTag(); + ret->time = time; + } else { + DEBUG("aacTagDup: Failed to get total song time from: %s\n", + file); + } + + return ret; +} + +static char *aacSuffixes[] = { "aac", NULL }; + +InputPlugin aacPlugin = { + "aac", + NULL, + NULL, + NULL, + NULL, + aac_decode, + aacTagDup, + INPUT_PLUGIN_STREAM_FILE, + aacSuffixes, + NULL +}; + +#else + +InputPlugin aacPlugin; + +#endif /* HAVE_FAAD */ diff --git a/trunk/src/inputPlugins/audiofile_plugin.c b/trunk/src/inputPlugins/audiofile_plugin.c new file mode 100644 index 000000000..35fb48b8a --- /dev/null +++ b/trunk/src/inputPlugins/audiofile_plugin.c @@ -0,0 +1,188 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * libaudiofile (wave) support added by Eric Wong <normalperson@yhbt.net> + * + * 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 + */ + +#include "../inputPlugin.h" + +#ifdef HAVE_AUDIOFILE + +#include "../utils.h" +#include "../audio.h" +#include "../log.h" +#include "../pcm_utils.h" +#include "../playerData.h" + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <audiofile.h> + +static int getAudiofileTotalTime(char *file) +{ + int time; + AFfilehandle af_fp = afOpenFile(file, "r", NULL); + if (af_fp == AF_NULL_FILEHANDLE) { + return -1; + } + time = (int) + ((double)afGetFrameCount(af_fp, AF_DEFAULT_TRACK) + / afGetRate(af_fp, AF_DEFAULT_TRACK)); + afCloseFile(af_fp); + return time; +} + +static int audiofile_decode(OutputBuffer * cb, DecoderControl * dc, char *path) +{ + int fs, frame_count; + AFfilehandle af_fp; + int bits; + mpd_uint16 bitRate; + struct stat st; + + if (stat(path, &st) < 0) { + ERROR("failed to stat: %s\n", path); + return -1; + } + + af_fp = afOpenFile(path, "r", NULL); + if (af_fp == AF_NULL_FILEHANDLE) { + ERROR("failed to open: %s\n", path); + return -1; + } + + afSetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, + AF_SAMPFMT_TWOSCOMP, 16); + afGetVirtualSampleFormat(af_fp, AF_DEFAULT_TRACK, &fs, &bits); + dc->audioFormat.bits = bits; + dc->audioFormat.sampleRate = afGetRate(af_fp, AF_DEFAULT_TRACK); + dc->audioFormat.channels = afGetVirtualChannels(af_fp, AF_DEFAULT_TRACK); + getOutputAudioFormat(&(dc->audioFormat), &(cb->audioFormat)); + + frame_count = afGetFrameCount(af_fp, AF_DEFAULT_TRACK); + + dc->totalTime = + ((float)frame_count / (float)dc->audioFormat.sampleRate); + + bitRate = st.st_size * 8.0 / dc->totalTime / 1000.0 + 0.5; + + if (dc->audioFormat.bits != 8 && dc->audioFormat.bits != 16) { + ERROR("Only 8 and 16-bit files are supported. %s is %i-bit\n", + path, dc->audioFormat.bits); + afCloseFile(af_fp); + return -1; + } + + fs = (int)afGetVirtualFrameSize(af_fp, AF_DEFAULT_TRACK, 1); + + dc->state = DECODE_STATE_DECODE; + { + int ret, eof = 0, current = 0; + char chunk[CHUNK_SIZE]; + + while (!eof) { + if (dc->seek) { + clearOutputBuffer(cb); + current = dc->seekWhere * + dc->audioFormat.sampleRate; + afSeekFrame(af_fp, AF_DEFAULT_TRACK, current); + dc->seek = 0; + } + + ret = + afReadFrames(af_fp, AF_DEFAULT_TRACK, chunk, + CHUNK_SIZE / fs); + if (ret <= 0) + eof = 1; + else { + current += ret; + sendDataToOutputBuffer(cb, + NULL, + dc, + 1, + chunk, + ret * fs, + (float)current / + (float)dc->audioFormat. + sampleRate, bitRate, + NULL); + if (dc->stop) + break; + } + } + + flushOutputBuffer(cb); + + /*if(dc->seek) { + dc->seekError = 1; + dc->seek = 0; + } */ + + if (dc->stop) { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } else + dc->state = DECODE_STATE_STOP; + } + afCloseFile(af_fp); + + return 0; +} + +static MpdTag *audiofileTagDup(char *file) +{ + MpdTag *ret = NULL; + int time = getAudiofileTotalTime(file); + + if (time >= 0) { + if (!ret) + ret = newMpdTag(); + ret->time = time; + } else { + DEBUG + ("audiofileTagDup: Failed to get total song time from: %s\n", + file); + } + + return ret; +} + +static char *audiofileSuffixes[] = { "wav", "au", "aiff", "aif", NULL }; + +InputPlugin audiofilePlugin = { + "audiofile", + NULL, + NULL, + NULL, + NULL, + audiofile_decode, + audiofileTagDup, + INPUT_PLUGIN_STREAM_FILE, + audiofileSuffixes, + NULL +}; + +#else + +InputPlugin audiofilePlugin; + +#endif /* HAVE_AUDIOFILE */ diff --git a/trunk/src/inputPlugins/flac_plugin.c b/trunk/src/inputPlugins/flac_plugin.c new file mode 100644 index 000000000..3f3a4b4f1 --- /dev/null +++ b/trunk/src/inputPlugins/flac_plugin.c @@ -0,0 +1,530 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "_flac_common.h" + +#ifdef HAVE_FLAC + +#include "../utils.h" +#include "../log.h" +#include "../pcm_utils.h" +#include "../inputStream.h" +#include "../outputBuffer.h" +#include "../replayGain.h" +#include "../audio.h" + +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <assert.h> + +/* this code was based on flac123, from flac-tools */ + +static flac_read_status flacRead(const flac_decoder * flacDec, + FLAC__byte buf[], + flac_read_status_size_t *bytes, + void *fdata) +{ + FlacData *data = (FlacData *) fdata; + size_t r; + + while (1) { + r = readFromInputStream(data->inStream, (void *)buf, 1, *bytes); + if (r == 0 && !inputStreamAtEOF(data->inStream) && + !data->dc->stop) + my_usleep(10000); + else + break; + } + *bytes = r; + + if (r == 0 && !data->dc->stop) { + if (inputStreamAtEOF(data->inStream)) + return flac_read_status_eof; + else + return flac_read_status_abort; + } + return flac_read_status_continue; +} + +static flac_seek_status flacSeek(const flac_decoder * flacDec, + FLAC__uint64 offset, + void *fdata) +{ + FlacData *data = (FlacData *) fdata; + + if (seekInputStream(data->inStream, offset, SEEK_SET) < 0) { + return flac_seek_status_error; + } + + return flac_seek_status_ok; +} + +static flac_tell_status flacTell(const flac_decoder * flacDec, + FLAC__uint64 * offset, + void *fdata) +{ + FlacData *data = (FlacData *) fdata; + + *offset = (long)(data->inStream->offset); + + return flac_tell_status_ok; +} + +static flac_length_status flacLength(const flac_decoder * flacDec, + FLAC__uint64 * length, + void *fdata) +{ + FlacData *data = (FlacData *) fdata; + + *length = (size_t) (data->inStream->size); + + return flac_length_status_ok; +} + +static FLAC__bool flacEOF(const flac_decoder * flacDec, void *fdata) +{ + FlacData *data = (FlacData *) fdata; + + if (inputStreamAtEOF(data->inStream) == 1) + return true; + return false; +} + +static void flacError(const flac_decoder *dec, + FLAC__StreamDecoderErrorStatus status, void *fdata) +{ + flac_error_common_cb("flac", status, (FlacData *) fdata); +} + +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 +static void flacPrintErroredState(FLAC__SeekableStreamDecoderState state) +{ + const char *str = ""; /* "" to silence compiler warning */ + switch (state) { + case FLAC__SEEKABLE_STREAM_DECODER_OK: + case FLAC__SEEKABLE_STREAM_DECODER_SEEKING: + case FLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM: + return; + case FLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR: + str = "allocation error"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_READ_ERROR: + str = "read error"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR: + str = "seek error"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR: + str = "seekable stream error"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED: + str = "decoder already initialized"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK: + str = "invalid callback"; + break; + case FLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED: + str = "decoder uninitialized"; + } + ERROR("flac %s\n", str); +} + +static int flac_init(FLAC__SeekableStreamDecoder *dec, + FLAC__SeekableStreamDecoderReadCallback read_cb, + FLAC__SeekableStreamDecoderSeekCallback seek_cb, + FLAC__SeekableStreamDecoderTellCallback tell_cb, + FLAC__SeekableStreamDecoderLengthCallback length_cb, + FLAC__SeekableStreamDecoderEofCallback eof_cb, + FLAC__SeekableStreamDecoderWriteCallback write_cb, + FLAC__SeekableStreamDecoderMetadataCallback metadata_cb, + FLAC__SeekableStreamDecoderErrorCallback error_cb, + void *data) +{ + int s = 1; + s &= FLAC__seekable_stream_decoder_set_read_callback(dec, read_cb); + s &= FLAC__seekable_stream_decoder_set_seek_callback(dec, seek_cb); + s &= FLAC__seekable_stream_decoder_set_tell_callback(dec, tell_cb); + s &= FLAC__seekable_stream_decoder_set_length_callback(dec, length_cb); + s &= FLAC__seekable_stream_decoder_set_eof_callback(dec, eof_cb); + s &= FLAC__seekable_stream_decoder_set_write_callback(dec, write_cb); + s &= FLAC__seekable_stream_decoder_set_metadata_callback(dec, + metadata_cb); + s &= FLAC__seekable_stream_decoder_set_metadata_respond(dec, + FLAC__METADATA_TYPE_VORBIS_COMMENT); + s &= FLAC__seekable_stream_decoder_set_error_callback(dec, error_cb); + s &= FLAC__seekable_stream_decoder_set_client_data(dec, data); + if (!s || (FLAC__seekable_stream_decoder_init(dec) != + FLAC__SEEKABLE_STREAM_DECODER_OK)) + return 0; + return 1; +} +#else /* FLAC_API_VERSION_CURRENT >= 7 */ +static void flacPrintErroredState(FLAC__StreamDecoderState state) +{ + const char *str = ""; /* "" to silence compiler warning */ + switch (state) { + case FLAC__STREAM_DECODER_SEARCH_FOR_METADATA: + case FLAC__STREAM_DECODER_READ_METADATA: + case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC: + case FLAC__STREAM_DECODER_READ_FRAME: + case FLAC__STREAM_DECODER_END_OF_STREAM: + return; + case FLAC__STREAM_DECODER_OGG_ERROR: + str = "error in the Ogg layer"; + break; + case FLAC__STREAM_DECODER_SEEK_ERROR: + str = "seek error"; + break; + case FLAC__STREAM_DECODER_ABORTED: + str = "decoder aborted by read"; + break; + case FLAC__STREAM_DECODER_MEMORY_ALLOCATION_ERROR: + str = "allocation error"; + break; + case FLAC__STREAM_DECODER_UNINITIALIZED: + str = "decoder uninitialized"; + } + ERROR("flac %s\n", str); +} +#endif /* FLAC_API_VERSION_CURRENT >= 7 */ + +static void flacMetadata(const flac_decoder * dec, + const FLAC__StreamMetadata * block, void *vdata) +{ + flac_metadata_common_cb(block, (FlacData *) vdata); +} + +static FLAC__StreamDecoderWriteStatus flacWrite(const flac_decoder *dec, + const FLAC__Frame * frame, + const FLAC__int32 * const buf[], + void *vdata) +{ + FlacData *data = (FlacData *) vdata; + FLAC__uint32 samples = frame->header.blocksize; + FLAC__uint16 u16; + unsigned char *uc; + int c_samp, c_chan, d_samp; + int i; + float timeChange; + FLAC__uint64 newPosition = 0; + + timeChange = ((float)samples) / frame->header.sample_rate; + data->time += timeChange; + + flac_get_decode_position(dec, &newPosition); + if (data->position) { + data->bitRate = + ((newPosition - data->position) * 8.0 / timeChange) + / 1000 + 0.5; + } + data->position = newPosition; + + for (c_samp = d_samp = 0; c_samp < frame->header.blocksize; c_samp++) { + for (c_chan = 0; c_chan < frame->header.channels; + c_chan++, d_samp++) { + u16 = buf[c_chan][c_samp]; + uc = (unsigned char *)&u16; + for (i = 0; i < (data->dc->audioFormat.bits / 8); i++) { + if (data->chunk_length >= FLAC_CHUNK_SIZE) { + if (flacSendChunk(data) < 0) { + return + FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } + data->chunk_length = 0; + if (data->dc->seek) { + return + FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; + } + } + data->chunk[data->chunk_length++] = *(uc++); + } + } + } + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +static MpdTag *flacMetadataDup(char *file, int *vorbisCommentFound) +{ + MpdTag *ret = NULL; + FLAC__Metadata_SimpleIterator *it; + FLAC__StreamMetadata *block = NULL; + + *vorbisCommentFound = 0; + + it = FLAC__metadata_simple_iterator_new(); + if (!FLAC__metadata_simple_iterator_init(it, file, 1, 0)) { + switch (FLAC__metadata_simple_iterator_status(it)) { + case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ILLEGAL_INPUT: + DEBUG + ("flacMetadataDup: Reading '%s' metadata gave the following error: Illegal Input\n", + file); + break; + case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_ERROR_OPENING_FILE: + DEBUG + ("flacMetadataDup: Reading '%s' metadata gave the following error: Error Opening File\n", + file); + break; + case FLAC__METADATA_SIMPLE_ITERATOR_STATUS_NOT_A_FLAC_FILE: + DEBUG + ("flacMetadataDup: Reading '%s' metadata gave the following error: Not A Flac File\n", + file); + break; + default: + DEBUG("flacMetadataDup: Reading '%s' metadata failed\n", + file); + } + FLAC__metadata_simple_iterator_delete(it); + return ret; + } + + do { + block = FLAC__metadata_simple_iterator_get_block(it); + if (!block) + break; + if (block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) { + ret = copyVorbisCommentBlockToMpdTag(block, ret); + + if (ret) + *vorbisCommentFound = 1; + } else if (block->type == FLAC__METADATA_TYPE_STREAMINFO) { + if (!ret) + ret = newMpdTag(); + ret->time = ((float)block->data.stream_info. + total_samples) / + block->data.stream_info.sample_rate + 0.5; + } + FLAC__metadata_object_delete(block); + } while (FLAC__metadata_simple_iterator_next(it)); + + FLAC__metadata_simple_iterator_delete(it); + return ret; +} + +static MpdTag *flacTagDup(char *file) +{ + MpdTag *ret = NULL; + int foundVorbisComment = 0; + + ret = flacMetadataDup(file, &foundVorbisComment); + if (!ret) { + DEBUG("flacTagDup: Failed to grab information from: %s\n", + file); + return NULL; + } + if (!foundVorbisComment) { + MpdTag *temp = id3Dup(file); + if (temp) { + temp->time = ret->time; + freeMpdTag(ret); + ret = temp; + } + } + + return ret; +} + +static int flac_decode_internal(OutputBuffer * cb, DecoderControl * dc, + InputStream * inStream, int is_ogg) +{ + flac_decoder *flacDec; + FlacData data; + const char *err = NULL; + + if (!(flacDec = flac_new())) + return -1; + init_FlacData(&data, cb, dc, inStream); + if (is_ogg) { + if (!flac_ogg_init(flacDec, flacRead, flacSeek, flacTell, + flacLength, flacEOF, flacWrite, flacMetadata, + flacError, (void *)&data)) { + err = "doing Ogg init()"; + goto fail; + } + } else { + if (!flac_init(flacDec, flacRead, flacSeek, flacTell, + flacLength, flacEOF, flacWrite, flacMetadata, + flacError, (void *)&data)) { + err = "doing init()"; + goto fail; + } + if (!flac_process_metadata(flacDec)) { + err = "problem reading metadata"; + goto fail; + } + } + + dc->state = DECODE_STATE_DECODE; + + while (1) { + if (!flac_process_single(flacDec)) + break; + if (flac_get_state(flacDec) == flac_decoder_eof) + break; + if (dc->seek) { + FLAC__uint64 sampleToSeek = dc->seekWhere * + dc->audioFormat.sampleRate + 0.5; + if (flac_seek_absolute(flacDec, sampleToSeek)) { + clearOutputBuffer(cb); + data.time = ((float)sampleToSeek) / + dc->audioFormat.sampleRate; + data.position = 0; + } else + dc->seekError = 1; + dc->seek = 0; + } + } + if (!dc->stop) { + flacPrintErroredState(flac_get_state(flacDec)); + flac_finish(flacDec); + } + /* send last little bit */ + if (data.chunk_length > 0 && !dc->stop) { + flacSendChunk(&data); + flushOutputBuffer(data.cb); + } + + /*if(dc->seek) { + dc->seekError = 1; + dc->seek = 0; + } */ + + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + +fail: + if (data.replayGainInfo) + freeReplayGainInfo(data.replayGainInfo); + + if (flacDec) + flac_delete(flacDec); + + closeInputStream(inStream); + + if (err) { + ERROR("flac %s\n", err); + return -1; + } + return 0; +} + +static int flac_decode(OutputBuffer * cb, DecoderControl * dc, + InputStream * inStream) +{ + return flac_decode_internal(cb, dc, inStream, 0); +} + +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT <= 7 +# define flac_plugin_init NULL +#else /* FLAC_API_VERSION_CURRENT >= 7 */ +/* some of this stuff is duplicated from oggflac_plugin.c */ +extern InputPlugin oggflacPlugin; + +static MpdTag *oggflac_tag_dup(char *file) +{ + MpdTag *ret = NULL; + FLAC__Metadata_Iterator *it; + FLAC__StreamMetadata *block; + FLAC__Metadata_Chain *chain = FLAC__metadata_chain_new(); + + if (!(FLAC__metadata_chain_read_ogg(chain, file))) + goto out; + it = FLAC__metadata_iterator_new(); + FLAC__metadata_iterator_init(it, chain); + do { + if (!(block = FLAC__metadata_iterator_get_block(it))) + break; + if (block->type == FLAC__METADATA_TYPE_VORBIS_COMMENT) { + ret = copyVorbisCommentBlockToMpdTag(block, ret); + } else if (block->type == FLAC__METADATA_TYPE_STREAMINFO) { + if (!ret) + ret = newMpdTag(); + ret->time = ((float)block->data.stream_info. + total_samples) / + block->data.stream_info.sample_rate + 0.5; + } + } while (FLAC__metadata_iterator_next(it)); + FLAC__metadata_iterator_delete(it); +out: + FLAC__metadata_chain_delete(chain); + return ret; +} + +static int oggflac_decode(OutputBuffer * cb, DecoderControl * dc, + InputStream * inStream) +{ + return flac_decode_internal(cb, dc, inStream, 1); +} + +static unsigned int oggflac_try_decode(InputStream * inStream) +{ + return (ogg_stream_type_detect(inStream) == FLAC) ? 1 : 0; +} + +static char *oggflac_suffixes[] = { "ogg", NULL }; +static char *oggflac_mime_types[] = { "audio/x-flac+ogg", + "application/ogg", + "application/x-ogg", + NULL }; + +static int flac_plugin_init(void) +{ + if (!FLAC_API_SUPPORTS_OGG_FLAC) { + DEBUG("libFLAC does not support OggFLAC\n"); + return 1; + } + DEBUG("libFLAC supports OggFLAC, initializing OggFLAC support\n"); + assert(oggflacPlugin.name == NULL); + oggflacPlugin.name = "oggflac"; + oggflacPlugin.tryDecodeFunc = oggflac_try_decode; + oggflacPlugin.streamDecodeFunc = oggflac_decode; + oggflacPlugin.tagDupFunc = oggflac_tag_dup; + oggflacPlugin.streamTypes = INPUT_PLUGIN_STREAM_URL | + INPUT_PLUGIN_STREAM_FILE; + oggflacPlugin.suffixes = oggflac_suffixes; + oggflacPlugin.mimeTypes = oggflac_mime_types; + loadInputPlugin(&oggflacPlugin); + return 1; +} + +#endif /* FLAC_API_VERSION_CURRENT >= 7 */ + +static char *flacSuffixes[] = { "flac", NULL }; +static char *flac_mime_types[] = { "audio/x-flac", + "application/x-flac", + NULL }; + +InputPlugin flacPlugin = { + "flac", + flac_plugin_init, + NULL, + NULL, + flac_decode, + NULL, + flacTagDup, + INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, + flacSuffixes, + flac_mime_types +}; + +#else /* !HAVE_FLAC */ + +InputPlugin flacPlugin; + +#endif /* HAVE_FLAC */ diff --git a/trunk/src/inputPlugins/mod_plugin.c b/trunk/src/inputPlugins/mod_plugin.c new file mode 100644 index 000000000..800abc95f --- /dev/null +++ b/trunk/src/inputPlugins/mod_plugin.c @@ -0,0 +1,299 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "../inputPlugin.h" + +#ifdef HAVE_MIKMOD + +#include "../utils.h" +#include "../audio.h" +#include "../log.h" +#include "../pcm_utils.h" +#include "../playerData.h" + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <mikmod.h> + +/* this is largely copied from alsaplayer */ + +#define MIKMOD_FRAME_SIZE 4096 + +static BOOL mod_mpd_Init(void) +{ + return VC_Init(); +} + +static void mod_mpd_Exit(void) +{ + VC_Exit(); +} + +static void mod_mpd_Update(void) +{ +} + +static BOOL mod_mpd_IsThere(void) +{ + return 1; +} + +static MDRIVER drv_mpd = { + NULL, + "MPD", + "MPD Output Driver v0.1", + 0, + 255, +#if (LIBMIKMOD_VERSION > 0x030106) + "mpd", /* Alias */ +#if (LIBMIKMOD_VERSION > 0x030200) + NULL, /* CmdLineHelp */ +#endif + NULL, /* CommandLine */ +#endif + mod_mpd_IsThere, + VC_SampleLoad, + VC_SampleUnload, + VC_SampleSpace, + VC_SampleLength, + mod_mpd_Init, + mod_mpd_Exit, + NULL, + VC_SetNumVoices, + VC_PlayStart, + VC_PlayStop, + mod_mpd_Update, + NULL, + VC_VoiceSetVolume, + VC_VoiceGetVolume, + VC_VoiceSetFrequency, + VC_VoiceGetFrequency, + VC_VoiceSetPanning, + VC_VoiceGetPanning, + VC_VoicePlay, + VC_VoiceStop, + VC_VoiceStopped, + VC_VoiceGetPosition, + VC_VoiceRealVolume +}; + +static int mod_mikModInitiated; +static int mod_mikModInitError; + +static int mod_initMikMod(void) +{ + if (mod_mikModInitError) + return -1; + + if (!mod_mikModInitiated) { + mod_mikModInitiated = 1; + + md_device = 0; + md_reverb = 0; + + MikMod_RegisterDriver(&drv_mpd); + MikMod_RegisterAllLoaders(); + } + + md_pansep = 64; + md_mixfreq = 44100; + md_mode = (DMODE_SOFT_MUSIC | DMODE_INTERP | DMODE_STEREO | + DMODE_16BITS); + + if (MikMod_Init("")) { + ERROR("Could not init MikMod: %s\n", + MikMod_strerror(MikMod_errno)); + mod_mikModInitError = 1; + return -1; + } + + return 0; +} + +static void mod_finishMikMod(void) +{ + MikMod_Exit(); +} + +typedef struct _mod_Data { + MODULE *moduleHandle; + SBYTE *audio_buffer; +} mod_Data; + +static mod_Data *mod_open(char *path) +{ + MODULE *moduleHandle; + mod_Data *data; + + if (!(moduleHandle = Player_Load(path, 128, 0))) + return NULL; + + /* Prevent module from looping forever */ + moduleHandle->loop = 0; + + data = xmalloc(sizeof(mod_Data)); + + data->audio_buffer = xmalloc(MIKMOD_FRAME_SIZE); + data->moduleHandle = moduleHandle; + + Player_Start(data->moduleHandle); + + return data; +} + +static void mod_close(mod_Data * data) +{ + Player_Stop(); + Player_Free(data->moduleHandle); + free(data->audio_buffer); + free(data); +} + +static int mod_decode(OutputBuffer * cb, DecoderControl * dc, char *path) +{ + mod_Data *data; + float time = 0.0; + int ret; + float secPerByte; + + if (mod_initMikMod() < 0) + return -1; + + if (!(data = mod_open(path))) { + ERROR("failed to open mod: %s\n", path); + MikMod_Exit(); + return -1; + } + + dc->totalTime = 0; + dc->audioFormat.bits = 16; + dc->audioFormat.sampleRate = 44100; + dc->audioFormat.channels = 2; + getOutputAudioFormat(&(dc->audioFormat), &(cb->audioFormat)); + + secPerByte = + 1.0 / ((dc->audioFormat.bits * dc->audioFormat.channels / 8.0) * + (float)dc->audioFormat.sampleRate); + + dc->state = DECODE_STATE_DECODE; + while (1) { + if (dc->seek) { + dc->seekError = 1; + dc->seek = 0; + } + + if (dc->stop) + break; + + if (!Player_Active()) + break; + + ret = VC_WriteBytes(data->audio_buffer, MIKMOD_FRAME_SIZE); + time += ret * secPerByte; + sendDataToOutputBuffer(cb, NULL, dc, 0, + (char *)data->audio_buffer, ret, time, + 0, NULL); + } + + flushOutputBuffer(cb); + + mod_close(data); + + MikMod_Exit(); + + if (dc->stop) { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } else + dc->state = DECODE_STATE_STOP; + + return 0; +} + +static MpdTag *modTagDup(char *file) +{ + MpdTag *ret = NULL; + MODULE *moduleHandle; + char *title; + + if (mod_initMikMod() < 0) { + DEBUG("modTagDup: Failed to initialize MikMod\n"); + return NULL; + } + + if (!(moduleHandle = Player_Load(file, 128, 0))) { + DEBUG("modTagDup: Failed to open file: %s\n", file); + MikMod_Exit(); + return NULL; + + } + Player_Free(moduleHandle); + + ret = newMpdTag(); + + ret->time = 0; + title = xstrdup(Player_LoadTitle(file)); + if (title) + addItemToMpdTag(ret, TAG_ITEM_TITLE, title); + + MikMod_Exit(); + + return ret; +} + +static char *modSuffixes[] = { "amf", + "dsm", + "far", + "gdm", + "imf", + "it", + "med", + "mod", + "mtm", + "s3m", + "stm", + "stx", + "ult", + "uni", + "xm", + NULL +}; + +InputPlugin modPlugin = { + "mod", + NULL, + mod_finishMikMod, + NULL, + NULL, + mod_decode, + modTagDup, + INPUT_PLUGIN_STREAM_FILE, + modSuffixes, + NULL +}; + +#else + +InputPlugin modPlugin; + +#endif /* HAVE_MIKMOD */ diff --git a/trunk/src/inputPlugins/mp3_plugin.c b/trunk/src/inputPlugins/mp3_plugin.c new file mode 100644 index 000000000..a920b98a1 --- /dev/null +++ b/trunk/src/inputPlugins/mp3_plugin.c @@ -0,0 +1,1092 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "../inputPlugin.h" + +#ifdef HAVE_MAD + +#include "../pcm_utils.h" +#include <mad.h> + +#ifdef HAVE_ID3TAG +#include <id3tag.h> +#endif + +#include "../log.h" +#include "../utils.h" +#include "../replayGain.h" +#include "../tag.h" +#include "../conf.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> + +#define FRAMES_CUSHION 2000 + +#define READ_BUFFER_SIZE 40960 + +#define DECODE_SKIP -3 +#define DECODE_BREAK -2 +#define DECODE_CONT -1 +#define DECODE_OK 0 + +#define MUTEFRAME_SKIP 1 +#define MUTEFRAME_SEEK 2 + +/* the number of samples of silence the decoder inserts at start */ +#define DECODERDELAY 529 + +#define DEFAULT_GAPLESS_MP3_PLAYBACK 1 + +static int gaplessPlayback; + +/* this is stolen from mpg321! */ +struct audio_dither { + mad_fixed_t error[3]; + mad_fixed_t random; +}; + +static unsigned long prng(unsigned long state) +{ + return (state * 0x0019660dL + 0x3c6ef35fL) & 0xffffffffL; +} + +static signed long audio_linear_dither(unsigned int bits, mad_fixed_t sample, + struct audio_dither *dither) +{ + unsigned int scalebits; + mad_fixed_t output, mask, random; + + enum { + MIN = -MAD_F_ONE, + MAX = MAD_F_ONE - 1 + }; + + sample += dither->error[0] - dither->error[1] + dither->error[2]; + + dither->error[2] = dither->error[1]; + dither->error[1] = dither->error[0] / 2; + + output = sample + (1L << (MAD_F_FRACBITS + 1 - bits - 1)); + + scalebits = MAD_F_FRACBITS + 1 - bits; + mask = (1L << scalebits) - 1; + + random = prng(dither->random); + output += (random & mask) - (dither->random & mask); + + dither->random = random; + + if (output > MAX) { + output = MAX; + + if (sample > MAX) + sample = MAX; + } else if (output < MIN) { + output = MIN; + + if (sample < MIN) + sample = MIN; + } + + output &= ~mask; + + dither->error[0] = sample - output; + + return output >> scalebits; +} + +/* end of stolen stuff from mpg321 */ + +static int mp3_plugin_init(void) +{ + gaplessPlayback = getBoolConfigParam(CONF_GAPLESS_MP3_PLAYBACK); + if (gaplessPlayback == -1) gaplessPlayback = DEFAULT_GAPLESS_MP3_PLAYBACK; + else if (gaplessPlayback < 0) exit(EXIT_FAILURE); + return 1; +} + +/* decoder stuff is based on madlld */ + +#define MP3_DATA_OUTPUT_BUFFER_SIZE 4096 + +typedef struct _mp3DecodeData { + struct mad_stream stream; + struct mad_frame frame; + struct mad_synth synth; + mad_timer_t timer; + unsigned char readBuffer[READ_BUFFER_SIZE]; + char outputBuffer[MP3_DATA_OUTPUT_BUFFER_SIZE]; + char *outputPtr; + char *outputBufferEnd; + float totalTime; + float elapsedTime; + int muteFrame; + long *frameOffset; + mad_timer_t *times; + long highestFrame; + long maxFrames; + long currentFrame; + int dropFramesAtStart; + int dropFramesAtEnd; + int dropSamplesAtStart; + int dropSamplesAtEnd; + int foundXing; + int foundFirstFrame; + int decodedFirstFrame; + int flush; + unsigned long bitRate; + InputStream *inStream; + struct audio_dither dither; + enum mad_layer layer; +} mp3DecodeData; + +static void initMp3DecodeData(mp3DecodeData * data, InputStream * inStream) +{ + data->outputPtr = data->outputBuffer; + data->outputBufferEnd = + data->outputBuffer + MP3_DATA_OUTPUT_BUFFER_SIZE; + data->muteFrame = 0; + data->highestFrame = 0; + data->maxFrames = 0; + data->frameOffset = NULL; + data->times = NULL; + data->currentFrame = 0; + data->dropFramesAtStart = 0; + data->dropFramesAtEnd = 0; + data->dropSamplesAtStart = 0; + data->dropSamplesAtEnd = 0; + data->foundXing = 0; + data->foundFirstFrame = 0; + data->decodedFirstFrame = 0; + data->flush = 1; + data->inStream = inStream; + data->layer = 0; + memset(&(data->dither), 0, sizeof(struct audio_dither)); + + mad_stream_init(&data->stream); + mad_stream_options(&data->stream, MAD_OPTION_IGNORECRC); + mad_frame_init(&data->frame); + mad_synth_init(&data->synth); + mad_timer_reset(&data->timer); +} + +static int seekMp3InputBuffer(mp3DecodeData * data, long offset) +{ + if (seekInputStream(data->inStream, offset, SEEK_SET) < 0) { + return -1; + } + + mad_stream_buffer(&data->stream, data->readBuffer, 0); + (data->stream).error = 0; + + return 0; +} + +static int fillMp3InputBuffer(mp3DecodeData * data) +{ + size_t readSize; + size_t remaining; + size_t readed; + unsigned char *readStart; + + if ((data->stream).next_frame != NULL) { + remaining = (data->stream).bufend - (data->stream).next_frame; + memmove(data->readBuffer, (data->stream).next_frame, remaining); + readStart = (data->readBuffer) + remaining; + readSize = READ_BUFFER_SIZE - remaining; + } else { + readSize = READ_BUFFER_SIZE; + readStart = data->readBuffer, remaining = 0; + } + + /* we've exhausted the read buffer, so give up!, these potential + * mp3 frames are way too big, and thus unlikely to be mp3 frames */ + if (readSize == 0) + return -1; + + readed = readFromInputStream(data->inStream, readStart, (size_t) 1, + readSize); + if (readed <= 0 && inputStreamAtEOF(data->inStream)) + return -1; + /* sleep for a fraction of a second! */ + else if (readed <= 0) { + readed = 0; + my_usleep(10000); + } + + mad_stream_buffer(&data->stream, data->readBuffer, readed + remaining); + (data->stream).error = 0; + + return 0; +} + +#ifdef HAVE_ID3TAG +static ReplayGainInfo *parseId3ReplayGainInfo(struct id3_tag *tag) +{ + int i; + char *key; + char *value; + struct id3_frame *frame; + int found = 0; + ReplayGainInfo *replayGainInfo; + + replayGainInfo = newReplayGainInfo(); + + for (i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) { + if (frame->nfields < 3) + continue; + + key = (char *) + id3_ucs4_latin1duplicate(id3_field_getstring + (&frame->fields[1])); + value = (char *) + id3_ucs4_latin1duplicate(id3_field_getstring + (&frame->fields[2])); + + if (strcasecmp(key, "replaygain_track_gain") == 0) { + replayGainInfo->trackGain = atof(value); + found = 1; + } else if (strcasecmp(key, "replaygain_album_gain") == 0) { + replayGainInfo->albumGain = atof(value); + found = 1; + } else if (strcasecmp(key, "replaygain_track_peak") == 0) { + replayGainInfo->trackPeak = atof(value); + found = 1; + } else if (strcasecmp(key, "replaygain_album_peak") == 0) { + replayGainInfo->albumPeak = atof(value); + found = 1; + } + + free(key); + free(value); + } + + if (found) + return replayGainInfo; + freeReplayGainInfo(replayGainInfo); + return NULL; +} +#endif + +#ifdef HAVE_ID3TAG +static void mp3_parseId3Tag(mp3DecodeData * data, signed long tagsize, + MpdTag ** mpdTag, ReplayGainInfo ** replayGainInfo) +{ + struct id3_tag *id3Tag = NULL; + id3_length_t count; + id3_byte_t const *id3_data; + id3_byte_t *allocated = NULL; + MpdTag *tmpMpdTag; + ReplayGainInfo *tmpReplayGainInfo; + + count = data->stream.bufend - data->stream.this_frame; + + if (tagsize <= count) { + id3_data = data->stream.this_frame; + mad_stream_skip(&(data->stream), tagsize); + } else { + allocated = xmalloc(tagsize); + if (!allocated) + goto fail; + + memcpy(allocated, data->stream.this_frame, count); + mad_stream_skip(&(data->stream), count); + + while (count < tagsize) { + int len; + + len = readFromInputStream(data->inStream, + allocated + count, (size_t) 1, + tagsize - count); + if (len <= 0 && inputStreamAtEOF(data->inStream)) { + break; + } else if (len <= 0) + my_usleep(10000); + else + count += len; + } + + if (count != tagsize) { + DEBUG("mp3_decode: error parsing ID3 tag\n"); + goto fail; + } + + id3_data = allocated; + } + + id3Tag = id3_tag_parse(id3_data, tagsize); + if (!id3Tag) + goto fail; + + if (mpdTag) { + tmpMpdTag = parseId3Tag(id3Tag); + if (tmpMpdTag) { + if (*mpdTag) + freeMpdTag(*mpdTag); + *mpdTag = tmpMpdTag; + } + } + + if (replayGainInfo) { + tmpReplayGainInfo = parseId3ReplayGainInfo(id3Tag); + if (tmpReplayGainInfo) { + if (*replayGainInfo) + freeReplayGainInfo(*replayGainInfo); + *replayGainInfo = tmpReplayGainInfo; + } + } + + id3_tag_delete(id3Tag); +fail: + if (allocated) + free(allocated); +} +#endif + +static int decodeNextFrameHeader(mp3DecodeData * data, MpdTag ** tag, + ReplayGainInfo ** replayGainInfo) +{ + enum mad_layer layer; + + if ((data->stream).buffer == NULL + || (data->stream).error == MAD_ERROR_BUFLEN) { + if (fillMp3InputBuffer(data) < 0) { + return DECODE_BREAK; + } + } + if (mad_header_decode(&data->frame.header, &data->stream)) { +#ifdef HAVE_ID3TAG + if ((data->stream).error == MAD_ERROR_LOSTSYNC && + (data->stream).this_frame) { + signed long tagsize = id3_tag_query((data->stream). + this_frame, + (data->stream). + bufend - + (data->stream). + this_frame); + + if (tagsize > 0) { + if (tag && !(*tag)) { + mp3_parseId3Tag(data, tagsize, tag, + replayGainInfo); + } else { + mad_stream_skip(&(data->stream), + tagsize); + } + return DECODE_CONT; + } + } +#endif + if (MAD_RECOVERABLE((data->stream).error)) { + return DECODE_SKIP; + } else { + if ((data->stream).error == MAD_ERROR_BUFLEN) + return DECODE_CONT; + else { + ERROR("unrecoverable frame level error " + "(%s).\n", + mad_stream_errorstr(&data->stream)); + data->flush = 0; + return DECODE_BREAK; + } + } + } + + layer = data->frame.header.layer; + if (!data->layer) { + if (layer != MAD_LAYER_II && layer != MAD_LAYER_III) { + /* Only layer 2 and 3 have been tested to work */ + return DECODE_SKIP; + } + data->layer = layer; + } else if (layer != data->layer) { + /* Don't decode frames with a different layer than the first */ + return DECODE_SKIP; + } + + return DECODE_OK; +} + +static int decodeNextFrame(mp3DecodeData * data) +{ + if ((data->stream).buffer == NULL + || (data->stream).error == MAD_ERROR_BUFLEN) { + if (fillMp3InputBuffer(data) < 0) { + return DECODE_BREAK; + } + } + if (mad_frame_decode(&data->frame, &data->stream)) { +#ifdef HAVE_ID3TAG + if ((data->stream).error == MAD_ERROR_LOSTSYNC) { + signed long tagsize = id3_tag_query((data->stream). + this_frame, + (data->stream). + bufend - + (data->stream). + this_frame); + if (tagsize > 0) { + mad_stream_skip(&(data->stream), tagsize); + return DECODE_CONT; + } + } +#endif + if (MAD_RECOVERABLE((data->stream).error)) { + return DECODE_SKIP; + } else { + if ((data->stream).error == MAD_ERROR_BUFLEN) + return DECODE_CONT; + else { + ERROR("unrecoverable frame level error " + "(%s).\n", + mad_stream_errorstr(&data->stream)); + data->flush = 0; + return DECODE_BREAK; + } + } + } + + return DECODE_OK; +} + +/* xing stuff stolen from alsaplayer, and heavily modified by jat */ +#define XI_MAGIC (('X' << 8) | 'i') +#define NG_MAGIC (('n' << 8) | 'g') +#define IN_MAGIC (('I' << 8) | 'n') +#define FO_MAGIC (('f' << 8) | 'o') + +enum xing_magic { + XING_MAGIC_XING, /* VBR */ + XING_MAGIC_INFO /* CBR */ +}; + +struct xing { + long flags; /* valid fields (see below) */ + unsigned long frames; /* total number of frames */ + unsigned long bytes; /* total number of bytes */ + unsigned char toc[100]; /* 100-point seek table */ + long scale; /* VBR quality */ + enum xing_magic magic; /* header magic */ +}; + +enum { + XING_FRAMES = 0x00000001L, + XING_BYTES = 0x00000002L, + XING_TOC = 0x00000004L, + XING_SCALE = 0x00000008L +}; + +struct lame { + char encoder[10]; /* 9 byte encoder name/version ("LAME3.97b") */ +#if 0 + /* See related comment in parse_lame() */ + float peak; /* replaygain peak */ + float trackGain; /* replaygain track gain */ + float albumGain; /* replaygain album gain */ +#endif + int encoderDelay; /* # of added samples at start of mp3 */ + int encoderPadding; /* # of added samples at end of mp3 */ +}; + +static int parse_xing(struct xing *xing, struct mad_bitptr *ptr, int *oldbitlen) +{ + unsigned long bits; + int bitlen; + int bitsleft; + int i; + + bitlen = *oldbitlen; + + if (bitlen < 16) goto fail; + bits = mad_bit_read(ptr, 16); + bitlen -= 16; + + if (bits == XI_MAGIC) { + if (bitlen < 16) goto fail; + if (mad_bit_read(ptr, 16) != NG_MAGIC) goto fail; + bitlen -= 16; + xing->magic = XING_MAGIC_XING; + } else if (bits == IN_MAGIC) { + if (bitlen < 16) goto fail; + if (mad_bit_read(ptr, 16) != FO_MAGIC) goto fail; + bitlen -= 16; + xing->magic = XING_MAGIC_INFO; + } + else if (bits == NG_MAGIC) xing->magic = XING_MAGIC_XING; + else if (bits == FO_MAGIC) xing->magic = XING_MAGIC_INFO; + else goto fail; + + if (bitlen < 32) goto fail; + xing->flags = mad_bit_read(ptr, 32); + bitlen -= 32; + + if (xing->flags & XING_FRAMES) { + if (bitlen < 32) goto fail; + xing->frames = mad_bit_read(ptr, 32); + bitlen -= 32; + } + + if (xing->flags & XING_BYTES) { + if (bitlen < 32) goto fail; + xing->bytes = mad_bit_read(ptr, 32); + bitlen -= 32; + } + + if (xing->flags & XING_TOC) { + if (bitlen < 800) goto fail; + for (i = 0; i < 100; ++i) xing->toc[i] = mad_bit_read(ptr, 8); + bitlen -= 800; + } + + if (xing->flags & XING_SCALE) { + if (bitlen < 32) goto fail; + xing->scale = mad_bit_read(ptr, 32); + bitlen -= 32; + } + + /* Make sure we consume no less than 120 bytes (960 bits) in hopes that + * the LAME tag is found there, and not right after the Xing header */ + bitsleft = 960 - ((*oldbitlen) - bitlen); + if (bitsleft < 0) goto fail; + else if (bitsleft > 0) { + mad_bit_read(ptr, bitsleft); + bitlen -= bitsleft; + } + + *oldbitlen = bitlen; + + return 1; +fail: + xing->flags = 0; + return 0; +} + +static int parse_lame(struct lame *lame, struct mad_bitptr *ptr, int *bitlen) +{ + int i; + + /* Unlike the xing header, the lame tag has a fixed length. Fail if + * not all 36 bytes (288 bits) are there. */ + if (*bitlen < 288) return 0; + + for (i = 0; i < 9; i++) lame->encoder[i] = (char)mad_bit_read(ptr, 8); + lame->encoder[9] = '\0'; + + /* This is technically incorrect, since the encoder might not be lame. + * But there's no other way to determine if this is a lame tag, and we + * wouldn't want to go reading a tag that's not there. */ + if (strncmp(lame->encoder, "LAME", 4) != 0) return 0; + +#if 0 + /* Apparently lame versions <3.97b1 do not calculate replaygain. I'm + * using lame 3.97b2, and while it does calculate replaygain, it's + * setting the values to 0. Using --replaygain-(fast|accurate) doesn't + * make any difference. Leaving this code unused until we have a way + * of testing it. -- jat */ + + mad_bit_read(ptr, 16); + + mad_bit_read(ptr, 32); /* peak */ + + mad_bit_read(ptr, 6); /* header */ + bits = mad_bit_read(ptr, 1); /* sign bit */ + lame->trackGain = mad_bit_read(ptr, 9); /* gain*10 */ + lame->trackGain = (bits ? -lame->trackGain : lame->trackGain) / 10; + + mad_bit_read(ptr, 6); /* header */ + bits = mad_bit_read(ptr, 1); /* sign bit */ + lame->albumGain = mad_bit_read(ptr, 9); /* gain*10 */ + lame->albumGain = (bits ? -lame->albumGain : lame->albumGain) / 10; + + mad_bit_read(ptr, 16); +#else + mad_bit_read(ptr, 96); +#endif + + lame->encoderDelay = mad_bit_read(ptr, 12); + lame->encoderPadding = mad_bit_read(ptr, 12); + + mad_bit_read(ptr, 96); + + *bitlen -= 288; + + return 1; +} + +static int decodeFirstFrame(mp3DecodeData * data, DecoderControl * dc, + MpdTag ** tag, ReplayGainInfo ** replayGainInfo) +{ + struct xing xing; + struct lame lame; + struct mad_bitptr ptr; + int bitlen; + int ret; + + /* stfu gcc */ + memset(&xing, 0, sizeof(struct xing)); + xing.flags = 0; + + while (1) { + while ((ret = decodeNextFrameHeader(data, tag, replayGainInfo)) == DECODE_CONT && + (!dc || !dc->stop)); + if (ret == DECODE_BREAK || (dc && dc->stop)) return -1; + if (ret == DECODE_SKIP) continue; + + while ((ret = decodeNextFrame(data)) == DECODE_CONT && + (!dc || !dc->stop)); + if (ret == DECODE_BREAK || (dc && dc->stop)) return -1; + if (ret == DECODE_OK) break; + } + + ptr = data->stream.anc_ptr; + bitlen = data->stream.anc_bitlen; + + /* + * Attempt to calulcate the length of the song from filesize + */ + { + size_t offset = data->inStream->offset; + mad_timer_t duration = data->frame.header.duration; + float frameTime = ((float)mad_timer_count(duration, + MAD_UNITS_MILLISECONDS)) / 1000; + + if (data->stream.this_frame != NULL) + offset -= data->stream.bufend - data->stream.this_frame; + else + offset -= data->stream.bufend - data->stream.buffer; + + if (data->inStream->size >= offset) { + data->totalTime = ((data->inStream->size - offset) * + 8.0) / (data->frame).header.bitrate; + data->maxFrames = data->totalTime / frameTime + + FRAMES_CUSHION; + } else { + data->maxFrames = FRAMES_CUSHION; + data->totalTime = 0; + } + } + /* + * if an xing tag exists, use that! + */ + if (parse_xing(&xing, &ptr, &bitlen)) { + data->foundXing = 1; + data->muteFrame = MUTEFRAME_SKIP; + + if (gaplessPlayback && data->inStream->seekable && + parse_lame(&lame, &ptr, &bitlen)) { + data->dropSamplesAtStart = lame.encoderDelay + DECODERDELAY; + data->dropSamplesAtEnd = lame.encoderPadding; + } + + if ((xing.flags & XING_FRAMES) && xing.frames) { + mad_timer_t duration = data->frame.header.duration; + mad_timer_multiply(&duration, xing.frames); + data->totalTime = ((float)mad_timer_count(duration, MAD_UNITS_MILLISECONDS)) / 1000; + data->maxFrames = xing.frames; + } + } + + if (!data->maxFrames) return -1; + + data->frameOffset = xmalloc(sizeof(long) * data->maxFrames); + data->times = xmalloc(sizeof(mad_timer_t) * data->maxFrames); + + return 0; +} + +static void mp3DecodeDataFinalize(mp3DecodeData * data) +{ + mad_synth_finish(&data->synth); + mad_frame_finish(&data->frame); + mad_stream_finish(&data->stream); + + if (data->frameOffset) free(data->frameOffset); + if (data->times) free(data->times); +} + +/* this is primarily used for getting total time for tags */ +static int getMp3TotalTime(char *file) +{ + InputStream inStream; + mp3DecodeData data; + int ret; + + if (openInputStream(&inStream, file) < 0) + return -1; + initMp3DecodeData(&data, &inStream); + if (decodeFirstFrame(&data, NULL, NULL, NULL) < 0) + ret = -1; + else + ret = data.totalTime + 0.5; + mp3DecodeDataFinalize(&data); + closeInputStream(&inStream); + + return ret; +} + +static int openMp3FromInputStream(InputStream * inStream, mp3DecodeData * data, + DecoderControl * dc, MpdTag ** tag, + ReplayGainInfo ** replayGainInfo) +{ + initMp3DecodeData(data, inStream); + *tag = NULL; + if (decodeFirstFrame(data, dc, tag, replayGainInfo) < 0) { + mp3DecodeDataFinalize(data); + if (tag && *tag) + freeMpdTag(*tag); + return -1; + } + + return 0; +} + +static int mp3Read(mp3DecodeData * data, OutputBuffer * cb, DecoderControl * dc, + ReplayGainInfo ** replayGainInfo) +{ + int samplesPerFrame; + int samplesLeft; + int i; + int ret; + int skip; + + if (data->currentFrame >= data->highestFrame) { + mad_timer_add(&data->timer, (data->frame).header.duration); + data->bitRate = (data->frame).header.bitrate; + if (data->currentFrame >= data->maxFrames) { + data->currentFrame = data->maxFrames - 1; + } else { + data->highestFrame++; + } + data->frameOffset[data->currentFrame] = data->inStream->offset; + if (data->stream.this_frame != NULL) { + data->frameOffset[data->currentFrame] -= + data->stream.bufend - data->stream.this_frame; + } else { + data->frameOffset[data->currentFrame] -= + data->stream.bufend - data->stream.buffer; + } + data->times[data->currentFrame] = data->timer; + } else { + data->timer = data->times[data->currentFrame]; + } + data->currentFrame++; + data->elapsedTime = + ((float)mad_timer_count(data->timer, MAD_UNITS_MILLISECONDS)) / + 1000; + + switch (data->muteFrame) { + case MUTEFRAME_SKIP: + data->muteFrame = 0; + break; + case MUTEFRAME_SEEK: + if (dc->seekWhere <= data->elapsedTime) { + data->outputPtr = data->outputBuffer; + clearOutputBuffer(cb); + data->muteFrame = 0; + dc->seek = 0; + } + break; + default: + mad_synth_frame(&data->synth, &data->frame); + + if (!data->foundFirstFrame) { + samplesPerFrame = (data->synth).pcm.length; + data->dropFramesAtStart = data->dropSamplesAtStart / samplesPerFrame; + data->dropFramesAtEnd = data->dropSamplesAtEnd / samplesPerFrame; + data->dropSamplesAtStart = data->dropSamplesAtStart % samplesPerFrame; + data->dropSamplesAtEnd = data->dropSamplesAtEnd % samplesPerFrame; + data->foundFirstFrame = 1; + } + + if (data->dropFramesAtStart > 0) { + data->dropFramesAtStart--; + break; + } else if ((data->dropFramesAtEnd > 0) && + (data->currentFrame == (data->maxFrames + 1 - data->dropFramesAtEnd))) { + /* stop decoding, effectively dropping all remaining + * frames */ + return DECODE_BREAK; + } + + if (data->inStream->metaTitle) { + MpdTag *tag = newMpdTag(); + if (data->inStream->metaName) { + addItemToMpdTag(tag, + TAG_ITEM_NAME, + data->inStream->metaName); + } + addItemToMpdTag(tag, TAG_ITEM_TITLE, + data->inStream->metaTitle); + free(data->inStream->metaTitle); + data->inStream->metaTitle = NULL; + copyMpdTagToOutputBuffer(cb, tag); + freeMpdTag(tag); + } + + samplesLeft = (data->synth).pcm.length; + + for (i = 0; i < (data->synth).pcm.length; i++) { + mpd_sint16 *sample; + + samplesLeft--; + + if (!data->decodedFirstFrame && + (i < data->dropSamplesAtStart)) { + continue; + } else if (data->dropSamplesAtEnd && + (data->currentFrame == (data->maxFrames - data->dropFramesAtEnd)) && + (samplesLeft < data->dropSamplesAtEnd)) { + /* stop decoding, effectively dropping + * all remaining samples */ + return DECODE_BREAK; + } + + sample = (mpd_sint16 *) data->outputPtr; + *sample = (mpd_sint16) audio_linear_dither(16, + (data->synth).pcm.samples[0][i], + &(data->dither)); + data->outputPtr += 2; + + if (MAD_NCHANNELS(&(data->frame).header) == 2) { + sample = (mpd_sint16 *) data->outputPtr; + *sample = (mpd_sint16) audio_linear_dither(16, + (data->synth).pcm.samples[1][i], + &(data->dither)); + data->outputPtr += 2; + } + + if (data->outputPtr >= data->outputBufferEnd) { + ret = sendDataToOutputBuffer(cb, + data->inStream, + dc, + data->inStream->seekable, + data->outputBuffer, + data->outputPtr - data->outputBuffer, + data->elapsedTime, + data->bitRate / 1000, + (replayGainInfo != NULL) ? *replayGainInfo : NULL); + if (ret == OUTPUT_BUFFER_DC_STOP) { + data->flush = 0; + return DECODE_BREAK; + } + + data->outputPtr = data->outputBuffer; + + if (ret == OUTPUT_BUFFER_DC_SEEK) + break; + } + } + + data->decodedFirstFrame = 1; + + if (dc->seek && data->inStream->seekable) { + long j = 0; + data->muteFrame = MUTEFRAME_SEEK; + while (j < data->highestFrame && dc->seekWhere > + ((float)mad_timer_count(data->times[j], + MAD_UNITS_MILLISECONDS)) + / 1000) { + j++; + } + if (j < data->highestFrame) { + if (seekMp3InputBuffer(data, + data->frameOffset[j]) == + 0) { + data->outputPtr = data->outputBuffer; + clearOutputBuffer(cb); + data->currentFrame = j; + } else + dc->seekError = 1; + data->muteFrame = 0; + dc->seek = 0; + } + } else if (dc->seek && !data->inStream->seekable) { + dc->seek = 0; + dc->seekError = 1; + } + } + + while (1) { + skip = 0; + while ((ret = + decodeNextFrameHeader(data, NULL, + replayGainInfo)) == DECODE_CONT + && !dc->stop) ; + if (ret == DECODE_BREAK || dc->stop || dc->seek) + break; + else if (ret == DECODE_SKIP) + skip = 1; + if (!data->muteFrame) { + while ((ret = decodeNextFrame(data)) == DECODE_CONT && + !dc->stop && !dc->seek) ; + if (ret == DECODE_BREAK || dc->stop || dc->seek) + break; + } + if (!skip && ret == DECODE_OK) + break; + } + + if (dc->stop) + return DECODE_BREAK; + + return ret; +} + +static void initAudioFormatFromMp3DecodeData(mp3DecodeData * data, + AudioFormat * af) +{ + af->bits = 16; + af->sampleRate = (data->frame).header.samplerate; + af->channels = MAD_NCHANNELS(&(data->frame).header); +} + +static int mp3_decode(OutputBuffer * cb, DecoderControl * dc, + InputStream * inStream) +{ + mp3DecodeData data; + MpdTag *tag = NULL; + ReplayGainInfo *replayGainInfo = NULL; + + if (openMp3FromInputStream(inStream, &data, dc, &tag, &replayGainInfo) < + 0) { + closeInputStream(inStream); + if (!dc->stop) { + ERROR + ("Input does not appear to be a mp3 bit stream.\n"); + return -1; + } else { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } + return 0; + } + + initAudioFormatFromMp3DecodeData(&data, &(dc->audioFormat)); + getOutputAudioFormat(&(dc->audioFormat), &(cb->audioFormat)); + + dc->totalTime = data.totalTime; + + if (inStream->metaTitle) { + if (tag) + freeMpdTag(tag); + tag = newMpdTag(); + addItemToMpdTag(tag, TAG_ITEM_TITLE, inStream->metaTitle); + free(inStream->metaTitle); + inStream->metaTitle = NULL; + if (inStream->metaName) { + addItemToMpdTag(tag, TAG_ITEM_NAME, inStream->metaName); + } + copyMpdTagToOutputBuffer(cb, tag); + freeMpdTag(tag); + } else if (tag) { + if (inStream->metaName) { + clearItemsFromMpdTag(tag, TAG_ITEM_NAME); + addItemToMpdTag(tag, TAG_ITEM_NAME, inStream->metaName); + } + copyMpdTagToOutputBuffer(cb, tag); + freeMpdTag(tag); + } else if (inStream->metaName) { + tag = newMpdTag(); + if (inStream->metaName) { + addItemToMpdTag(tag, TAG_ITEM_NAME, inStream->metaName); + } + copyMpdTagToOutputBuffer(cb, tag); + freeMpdTag(tag); + } + + dc->state = DECODE_STATE_DECODE; + + while (mp3Read(&data, cb, dc, &replayGainInfo) != DECODE_BREAK) ; + /* send last little bit if not dc->stop */ + if (!dc->stop && data.outputPtr != data.outputBuffer && data.flush) { + sendDataToOutputBuffer(cb, NULL, dc, + data.inStream->seekable, + data.outputBuffer, + data.outputPtr - data.outputBuffer, + data.elapsedTime, data.bitRate / 1000, + replayGainInfo); + } + + if (replayGainInfo) + freeReplayGainInfo(replayGainInfo); + + closeInputStream(inStream); + + if (dc->seek && data.muteFrame == MUTEFRAME_SEEK) { + clearOutputBuffer(cb); + dc->seek = 0; + } + + flushOutputBuffer(cb); + mp3DecodeDataFinalize(&data); + + if (dc->stop) { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } else + dc->state = DECODE_STATE_STOP; + + return 0; +} + +static MpdTag *mp3_tagDup(char *file) +{ + MpdTag *ret = NULL; + int time; + + ret = id3Dup(file); + + time = getMp3TotalTime(file); + + if (time >= 0) { + if (!ret) + ret = newMpdTag(); + ret->time = time; + } else { + DEBUG("mp3_tagDup: Failed to get total song time from: %s\n", + file); + } + + return ret; +} + +static char *mp3_suffixes[] = { "mp3", "mp2", NULL }; +static char *mp3_mimeTypes[] = { "audio/mpeg", NULL }; + +InputPlugin mp3Plugin = { + "mp3", + mp3_plugin_init, + NULL, + NULL, + mp3_decode, + NULL, + mp3_tagDup, + INPUT_PLUGIN_STREAM_FILE | INPUT_PLUGIN_STREAM_URL, + mp3_suffixes, + mp3_mimeTypes +}; +#else + +InputPlugin mp3Plugin; + +#endif /* HAVE_MAD */ diff --git a/trunk/src/inputPlugins/mp4_plugin.c b/trunk/src/inputPlugins/mp4_plugin.c new file mode 100644 index 000000000..1ebf556c6 --- /dev/null +++ b/trunk/src/inputPlugins/mp4_plugin.c @@ -0,0 +1,455 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "../inputPlugin.h" + +#ifdef HAVE_FAAD + +#include "../utils.h" +#include "../audio.h" +#include "../log.h" +#include "../pcm_utils.h" +#include "../inputStream.h" +#include "../outputBuffer.h" +#include "../decode.h" + +#include "../mp4ff/mp4ff.h" + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <faad.h> + +/* all code here is either based on or copied from FAAD2's frontend code */ + +static int mp4_getAACTrack(mp4ff_t * infile) +{ + /* find AAC track */ + int i, rc; + int numTracks = mp4ff_total_tracks(infile); + + for (i = 0; i < numTracks; i++) { + unsigned char *buff = NULL; + unsigned int buff_size = 0; +#ifdef HAVE_MP4AUDIOSPECIFICCONFIG + mp4AudioSpecificConfig mp4ASC; +#else + unsigned long dummy1_32; + unsigned char dummy2_8, dummy3_8, dummy4_8, dummy5_8, dummy6_8, + dummy7_8, dummy8_8; +#endif + + mp4ff_get_decoder_config(infile, i, &buff, &buff_size); + + if (buff) { +#ifdef HAVE_MP4AUDIOSPECIFICCONFIG + rc = AudioSpecificConfig(buff, buff_size, &mp4ASC); +#else + rc = AudioSpecificConfig(buff, &dummy1_32, &dummy2_8, + &dummy3_8, &dummy4_8, + &dummy5_8, &dummy6_8, + &dummy7_8, &dummy8_8); +#endif + free(buff); + if (rc < 0) + continue; + return i; + } + } + + /* can't decode this */ + return -1; +} + +static uint32_t mp4_inputStreamReadCallback(void *inStream, void *buffer, + uint32_t length) +{ + return readFromInputStream((InputStream *) inStream, buffer, 1, length); +} + +static uint32_t mp4_inputStreamSeekCallback(void *inStream, uint64_t position) +{ + return seekInputStream((InputStream *) inStream, position, SEEK_SET); +} + +static int mp4_decode(OutputBuffer * cb, DecoderControl * dc, char *path) +{ + mp4ff_t *mp4fh; + mp4ff_callback_t *mp4cb; + int32_t track; + float time; + int32_t scale; + faacDecHandle decoder; + faacDecFrameInfo frameInfo; + faacDecConfigurationPtr config; + unsigned char *mp4Buffer; + unsigned int mp4BufferSize; + unsigned long sampleRate; + unsigned char channels; + long sampleId; + long numSamples; + int eof = 0; + long dur; + unsigned int sampleCount; + char *sampleBuffer; + size_t sampleBufferLen; + unsigned int initial = 1; + float *seekTable; + long seekTableEnd = -1; + int seekPositionFound = 0; + long offset; + mpd_uint16 bitRate = 0; + InputStream inStream; + int seeking = 0; + + if (openInputStream(&inStream, path) < 0) { + ERROR("failed to open %s\n", path); + return -1; + } + + mp4cb = xmalloc(sizeof(mp4ff_callback_t)); + mp4cb->read = mp4_inputStreamReadCallback; + mp4cb->seek = mp4_inputStreamSeekCallback; + mp4cb->user_data = &inStream; + + mp4fh = mp4ff_open_read(mp4cb); + if (!mp4fh) { + ERROR("Input does not appear to be a mp4 stream.\n"); + free(mp4cb); + closeInputStream(&inStream); + return -1; + } + + track = mp4_getAACTrack(mp4fh); + if (track < 0) { + ERROR("No AAC track found in mp4 stream.\n"); + mp4ff_close(mp4fh); + closeInputStream(&inStream); + free(mp4cb); + return -1; + } + + decoder = faacDecOpen(); + + config = faacDecGetCurrentConfiguration(decoder); + config->outputFormat = FAAD_FMT_16BIT; +#ifdef HAVE_FAACDECCONFIGURATION_DOWNMATRIX + config->downMatrix = 1; +#endif +#ifdef HAVE_FAACDECCONFIGURATION_DONTUPSAMPLEIMPLICITSBR + config->dontUpSampleImplicitSBR = 0; +#endif + faacDecSetConfiguration(decoder, config); + + dc->audioFormat.bits = 16; + + mp4Buffer = NULL; + mp4BufferSize = 0; + mp4ff_get_decoder_config(mp4fh, track, &mp4Buffer, &mp4BufferSize); + + if (faacDecInit2 + (decoder, mp4Buffer, mp4BufferSize, &sampleRate, &channels) < 0) { + ERROR("Error not a AAC stream.\n"); + faacDecClose(decoder); + mp4ff_close(mp4fh); + free(mp4cb); + closeInputStream(&inStream); + return -1; + } + + dc->audioFormat.sampleRate = sampleRate; + dc->audioFormat.channels = channels; + time = mp4ff_get_track_duration_use_offsets(mp4fh, track); + scale = mp4ff_time_scale(mp4fh, track); + + if (mp4Buffer) + free(mp4Buffer); + + if (scale < 0) { + ERROR("Error getting audio format of mp4 AAC track.\n"); + faacDecClose(decoder); + mp4ff_close(mp4fh); + closeInputStream(&inStream); + free(mp4cb); + return -1; + } + dc->totalTime = ((float)time) / scale; + + numSamples = mp4ff_num_samples(mp4fh, track); + + time = 0.0; + + seekTable = xmalloc(sizeof(float) * numSamples); + + for (sampleId = 0; sampleId < numSamples && !eof; sampleId++) { + if (dc->seek) + seeking = 1; + + if (seeking && seekTableEnd > 1 && + seekTable[seekTableEnd] >= dc->seekWhere) { + int i = 2; + while (seekTable[i] < dc->seekWhere) + i++; + sampleId = i - 1; + time = seekTable[sampleId]; + } + + dur = mp4ff_get_sample_duration(mp4fh, track, sampleId); + offset = mp4ff_get_sample_offset(mp4fh, track, sampleId); + + if (sampleId > seekTableEnd) { + seekTable[sampleId] = time; + seekTableEnd = sampleId; + } + + if (sampleId == 0) + dur = 0; + if (offset > dur) + dur = 0; + else + dur -= offset; + time += ((float)dur) / scale; + + if (seeking && time > dc->seekWhere) + seekPositionFound = 1; + + if (seeking && seekPositionFound) { + seekPositionFound = 0; + clearOutputBuffer(cb); + seeking = 0; + dc->seek = 0; + } + + if (seeking) + continue; + + if (mp4ff_read_sample(mp4fh, track, sampleId, &mp4Buffer, + &mp4BufferSize) == 0) { + eof = 1; + continue; + } +#ifdef HAVE_FAAD_BUFLEN_FUNCS + sampleBuffer = faacDecDecode(decoder, &frameInfo, mp4Buffer, + mp4BufferSize); +#else + sampleBuffer = faacDecDecode(decoder, &frameInfo, mp4Buffer); +#endif + + if (mp4Buffer) + free(mp4Buffer); + if (frameInfo.error > 0) { + ERROR("error decoding MP4 file: %s\n", path); + ERROR("faad2 error: %s\n", + faacDecGetErrorMessage(frameInfo.error)); + eof = 1; + break; + } + + if (dc->state != DECODE_STATE_DECODE) { + channels = frameInfo.channels; +#ifdef HAVE_FAACDECFRAMEINFO_SAMPLERATE + scale = frameInfo.samplerate; +#endif + dc->audioFormat.sampleRate = scale; + dc->audioFormat.channels = frameInfo.channels; + getOutputAudioFormat(&(dc->audioFormat), + &(cb->audioFormat)); + dc->state = DECODE_STATE_DECODE; + } + + if (channels * (dur + offset) > frameInfo.samples) { + dur = frameInfo.samples / channels; + offset = 0; + } + + sampleCount = (unsigned long)(dur * channels); + + if (sampleCount > 0) { + initial = 0; + bitRate = frameInfo.bytesconsumed * 8.0 * + frameInfo.channels * scale / + frameInfo.samples / 1000 + 0.5; + } + + sampleBufferLen = sampleCount * 2; + + sampleBuffer += offset * channels * 2; + + sendDataToOutputBuffer(cb, NULL, dc, 1, sampleBuffer, + sampleBufferLen, time, bitRate, NULL); + if (dc->stop) { + eof = 1; + break; + } + } + + free(seekTable); + faacDecClose(decoder); + mp4ff_close(mp4fh); + closeInputStream(&inStream); + free(mp4cb); + + if (dc->state != DECODE_STATE_DECODE) + return -1; + + if (dc->seek && seeking) { + clearOutputBuffer(cb); + dc->seek = 0; + } + flushOutputBuffer(cb); + + if (dc->stop) { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } else + dc->state = DECODE_STATE_STOP; + + return 0; +} + +static MpdTag *mp4DataDup(char *file, int *mp4MetadataFound) +{ + MpdTag *ret = NULL; + InputStream inStream; + mp4ff_t *mp4fh; + mp4ff_callback_t *cb; + int32_t track; + int32_t time; + int32_t scale; + int i; + + *mp4MetadataFound = 0; + + if (openInputStream(&inStream, file) < 0) { + DEBUG("mp4DataDup: Failed to open file: %s\n", file); + return NULL; + } + + cb = xmalloc(sizeof(mp4ff_callback_t)); + cb->read = mp4_inputStreamReadCallback; + cb->seek = mp4_inputStreamSeekCallback; + cb->user_data = &inStream; + + mp4fh = mp4ff_open_read(cb); + if (!mp4fh) { + free(cb); + closeInputStream(&inStream); + return NULL; + } + + track = mp4_getAACTrack(mp4fh); + if (track < 0) { + mp4ff_close(mp4fh); + closeInputStream(&inStream); + free(cb); + return NULL; + } + + ret = newMpdTag(); + time = mp4ff_get_track_duration_use_offsets(mp4fh, track); + scale = mp4ff_time_scale(mp4fh, track); + if (scale < 0) { + mp4ff_close(mp4fh); + closeInputStream(&inStream); + free(cb); + freeMpdTag(ret); + return NULL; + } + ret->time = ((float)time) / scale + 0.5; + + for (i = 0; i < mp4ff_meta_get_num_items(mp4fh); i++) { + char *item; + char *value; + + mp4ff_meta_get_by_index(mp4fh, i, &item, &value); + + if (0 == strcasecmp("artist", item)) { + addItemToMpdTag(ret, TAG_ITEM_ARTIST, value); + *mp4MetadataFound = 1; + } else if (0 == strcasecmp("title", item)) { + addItemToMpdTag(ret, TAG_ITEM_TITLE, value); + *mp4MetadataFound = 1; + } else if (0 == strcasecmp("album", item)) { + addItemToMpdTag(ret, TAG_ITEM_ALBUM, value); + *mp4MetadataFound = 1; + } else if (0 == strcasecmp("track", item)) { + addItemToMpdTag(ret, TAG_ITEM_TRACK, value); + *mp4MetadataFound = 1; + } else if (0 == strcasecmp("disc", item)) { /* Is that the correct id? */ + addItemToMpdTag(ret, TAG_ITEM_DISC, value); + *mp4MetadataFound = 1; + } else if (0 == strcasecmp("genre", item)) { + addItemToMpdTag(ret, TAG_ITEM_GENRE, value); + *mp4MetadataFound = 1; + } else if (0 == strcasecmp("date", item)) { + addItemToMpdTag(ret, TAG_ITEM_DATE, value); + *mp4MetadataFound = 1; + } + + free(item); + free(value); + } + + mp4ff_close(mp4fh); + closeInputStream(&inStream); + free(cb); + + return ret; +} + +static MpdTag *mp4TagDup(char *file) +{ + MpdTag *ret = NULL; + int mp4MetadataFound = 0; + + ret = mp4DataDup(file, &mp4MetadataFound); + if (!ret) + return NULL; + if (!mp4MetadataFound) { + MpdTag *temp = id3Dup(file); + if (temp) { + temp->time = ret->time; + freeMpdTag(ret); + ret = temp; + } + } + + return ret; +} + +static char *mp4Suffixes[] = { "m4a", "mp4", NULL }; + +InputPlugin mp4Plugin = { + "mp4", + NULL, + NULL, + NULL, + NULL, + mp4_decode, + mp4TagDup, + INPUT_PLUGIN_STREAM_FILE, + mp4Suffixes, + NULL +}; + +#else + +InputPlugin mp4Plugin; + +#endif /* HAVE_FAAD */ diff --git a/trunk/src/inputPlugins/mpc_plugin.c b/trunk/src/inputPlugins/mpc_plugin.c new file mode 100644 index 000000000..885f6cfc9 --- /dev/null +++ b/trunk/src/inputPlugins/mpc_plugin.c @@ -0,0 +1,359 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "../inputPlugin.h" + +#ifdef HAVE_MPCDEC + +#include "../utils.h" +#include "../audio.h" +#include "../log.h" +#include "../pcm_utils.h" +#include "../inputStream.h" +#include "../outputBuffer.h" +#include "../replayGain.h" + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <mpcdec/mpcdec.h> +#include <errno.h> +#include <math.h> + +typedef struct _MpcCallbackData { + InputStream *inStream; + DecoderControl *dc; +} MpcCallbackData; + +static mpc_int32_t mpc_read_cb(void *vdata, void *ptr, mpc_int32_t size) +{ + mpc_int32_t ret = 0; + MpcCallbackData *data = (MpcCallbackData *) vdata; + + while (1) { + ret = readFromInputStream(data->inStream, ptr, 1, size); + if (ret == 0 && !inputStreamAtEOF(data->inStream) && + (data->dc && !data->dc->stop)) { + my_usleep(10000); + } else + break; + } + + return ret; +} + +static mpc_bool_t mpc_seek_cb(void *vdata, mpc_int32_t offset) +{ + MpcCallbackData *data = (MpcCallbackData *) vdata; + + return seekInputStream(data->inStream, offset, SEEK_SET) < 0 ? 0 : 1; +} + +static mpc_int32_t mpc_tell_cb(void *vdata) +{ + MpcCallbackData *data = (MpcCallbackData *) vdata; + + return (long)(data->inStream->offset); +} + +static mpc_bool_t mpc_canseek_cb(void *vdata) +{ + MpcCallbackData *data = (MpcCallbackData *) vdata; + + return data->inStream->seekable; +} + +static mpc_int32_t mpc_getsize_cb(void *vdata) +{ + MpcCallbackData *data = (MpcCallbackData *) vdata; + + return data->inStream->size; +} + +/* this _looks_ performance-critical, don't de-inline -- eric */ +static inline mpd_sint16 convertSample(MPC_SAMPLE_FORMAT sample) +{ + /* only doing 16-bit audio for now */ + mpd_sint32 val; + + const int clip_min = -1 << (16 - 1); + const int clip_max = (1 << (16 - 1)) - 1; + +#ifdef MPC_FIXED_POINT + const int shift = 16 - MPC_FIXED_POINT_SCALE_SHIFT; + + if (sample > 0) { + sample <<= shift; + } else if (shift < 0) { + sample >>= -shift; + } + val = sample; +#else + const int float_scale = 1 << (16 - 1); + + val = sample * float_scale; +#endif + + if (val < clip_min) + val = clip_min; + else if (val > clip_max) + val = clip_max; + + return val; +} + +static int mpc_decode(OutputBuffer * cb, DecoderControl * dc, + InputStream * inStream) +{ + mpc_decoder decoder; + mpc_reader reader; + mpc_streaminfo info; + + MpcCallbackData data; + + MPC_SAMPLE_FORMAT sample_buffer[MPC_DECODER_BUFFER_LENGTH]; + + int eof = 0; + long ret; +#define MPC_CHUNK_SIZE 4096 + char chunk[MPC_CHUNK_SIZE]; + int chunkpos = 0; + long bitRate = 0; + mpd_sint16 *s16 = (mpd_sint16 *) chunk; + unsigned long samplePos = 0; + mpc_uint32_t vbrUpdateAcc; + mpc_uint32_t vbrUpdateBits; + float time; + int i; + ReplayGainInfo *replayGainInfo = NULL; + + data.inStream = inStream; + data.dc = dc; + + reader.read = mpc_read_cb; + reader.seek = mpc_seek_cb; + reader.tell = mpc_tell_cb; + reader.get_size = mpc_getsize_cb; + reader.canseek = mpc_canseek_cb; + reader.data = &data; + + mpc_streaminfo_init(&info); + + if ((ret = mpc_streaminfo_read(&info, &reader)) != ERROR_CODE_OK) { + closeInputStream(inStream); + if (!dc->stop) { + ERROR("Not a valid musepack stream"); + return -1; + } else { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } + return 0; + } + + mpc_decoder_setup(&decoder, &reader); + + if (!mpc_decoder_initialize(&decoder, &info)) { + closeInputStream(inStream); + if (!dc->stop) { + ERROR("Not a valid musepack stream"); + } else { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } + } + + dc->totalTime = mpc_streaminfo_get_length(&info); + + dc->audioFormat.bits = 16; + dc->audioFormat.channels = info.channels; + dc->audioFormat.sampleRate = info.sample_freq; + + getOutputAudioFormat(&(dc->audioFormat), &(cb->audioFormat)); + + replayGainInfo = newReplayGainInfo(); + replayGainInfo->albumGain = info.gain_album * 0.01; + replayGainInfo->albumPeak = info.peak_album / 32767.0; + replayGainInfo->trackGain = info.gain_title * 0.01; + replayGainInfo->trackPeak = info.peak_title / 32767.0; + + dc->state = DECODE_STATE_DECODE; + + while (!eof) { + if (dc->seek) { + samplePos = dc->seekWhere * dc->audioFormat.sampleRate; + if (mpc_decoder_seek_sample(&decoder, samplePos)) { + clearOutputBuffer(cb); + s16 = (mpd_sint16 *) chunk; + chunkpos = 0; + } else + dc->seekError = 1; + dc->seek = 0; + } + + vbrUpdateAcc = 0; + vbrUpdateBits = 0; + ret = mpc_decoder_decode(&decoder, sample_buffer, + &vbrUpdateAcc, &vbrUpdateBits); + + if (ret <= 0 || dc->stop) { + eof = 1; + break; + } + + samplePos += ret; + + /* ret is in samples, and we have stereo */ + ret *= 2; + + for (i = 0; i < ret; i++) { + /* 16 bit audio again */ + *s16 = convertSample(sample_buffer[i]); + chunkpos += 2; + s16++; + + if (chunkpos >= MPC_CHUNK_SIZE) { + time = ((float)samplePos) / + dc->audioFormat.sampleRate; + + bitRate = vbrUpdateBits * + dc->audioFormat.sampleRate / 1152 / 1000; + + sendDataToOutputBuffer(cb, inStream, dc, + inStream->seekable, + chunk, chunkpos, + time, + bitRate, replayGainInfo); + + chunkpos = 0; + s16 = (mpd_sint16 *) chunk; + if (dc->stop) { + eof = 1; + break; + } + } + } + } + + if (!dc->stop && chunkpos > 0) { + time = ((float)samplePos) / dc->audioFormat.sampleRate; + + bitRate = + vbrUpdateBits * dc->audioFormat.sampleRate / 1152 / 1000; + + sendDataToOutputBuffer(cb, NULL, dc, inStream->seekable, + chunk, chunkpos, time, bitRate, + replayGainInfo); + } + + closeInputStream(inStream); + + flushOutputBuffer(cb); + + freeReplayGainInfo(replayGainInfo); + + if (dc->stop) { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } else { + dc->state = DECODE_STATE_STOP; + } + + return 0; +} + +static float mpcGetTime(char *file) +{ + InputStream inStream; + float time = -1; + + mpc_reader reader; + mpc_streaminfo info; + MpcCallbackData data; + + data.inStream = &inStream; + data.dc = NULL; + + reader.read = mpc_read_cb; + reader.seek = mpc_seek_cb; + reader.tell = mpc_tell_cb; + reader.get_size = mpc_getsize_cb; + reader.canseek = mpc_canseek_cb; + reader.data = &data; + + mpc_streaminfo_init(&info); + + if (openInputStream(&inStream, file) < 0) { + DEBUG("mpcGetTime: Failed to open file: %s\n", file); + return -1; + } + + if (mpc_streaminfo_read(&info, &reader) != ERROR_CODE_OK) { + closeInputStream(&inStream); + return -1; + } + + time = mpc_streaminfo_get_length(&info); + + closeInputStream(&inStream); + + return time; +} + +static MpdTag *mpcTagDup(char *file) +{ + MpdTag *ret = NULL; + float time = mpcGetTime(file); + + if (time < 0) { + DEBUG("mpcTagDup: Failed to get Songlength of file: %s\n", + file); + return NULL; + } + + ret = apeDup(file); + if (!ret) + ret = id3Dup(file); + if (!ret) + ret = newMpdTag(); + ret->time = time; + + return ret; +} + +static char *mpcSuffixes[] = { "mpc", NULL }; + +InputPlugin mpcPlugin = { + "mpc", + NULL, + NULL, + NULL, + mpc_decode, + NULL, + mpcTagDup, + INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, + mpcSuffixes, + NULL +}; + +#else + +InputPlugin mpcPlugin; + +#endif /* HAVE_MPCDEC */ diff --git a/trunk/src/inputPlugins/oggflac_plugin.c b/trunk/src/inputPlugins/oggflac_plugin.c new file mode 100644 index 000000000..58eb0a5f7 --- /dev/null +++ b/trunk/src/inputPlugins/oggflac_plugin.c @@ -0,0 +1,423 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * OggFLAC support (half-stolen from flac_plugin.c :)) + * (c) 2005 by Eric Wong <normalperson@yhbt.net> + * + * 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 + */ + +#include "_flac_common.h" + +#ifdef HAVE_OGGFLAC + +#include "_ogg_common.h" + +#include "../utils.h" +#include "../log.h" +#include "../pcm_utils.h" +#include "../inputStream.h" +#include "../outputBuffer.h" +#include "../replayGain.h" +#include "../audio.h" + +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +static void oggflac_cleanup(InputStream * inStream, + FlacData * data, + OggFLAC__SeekableStreamDecoder * decoder) +{ + if (data->replayGainInfo) + freeReplayGainInfo(data->replayGainInfo); + if (decoder) + OggFLAC__seekable_stream_decoder_delete(decoder); + closeInputStream(inStream); +} + +static OggFLAC__SeekableStreamDecoderReadStatus of_read_cb(const + OggFLAC__SeekableStreamDecoder + * decoder, + FLAC__byte buf[], + unsigned *bytes, + void *fdata) +{ + FlacData *data = (FlacData *) fdata; + size_t r; + + while (1) { + r = readFromInputStream(data->inStream, (void *)buf, 1, *bytes); + if (r == 0 && !inputStreamAtEOF(data->inStream) && + !data->dc->stop) + my_usleep(10000); + else + break; + } + *bytes = r; + + if (r == 0 && !inputStreamAtEOF(data->inStream) && !data->dc->stop) + return OggFLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR; + + return OggFLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK; +} + +static OggFLAC__SeekableStreamDecoderSeekStatus of_seek_cb(const + OggFLAC__SeekableStreamDecoder + * decoder, + FLAC__uint64 offset, + void *fdata) +{ + FlacData *data = (FlacData *) fdata; + + if (seekInputStream(data->inStream, offset, SEEK_SET) < 0) { + return OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR; + } + + return OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK; +} + +static OggFLAC__SeekableStreamDecoderTellStatus of_tell_cb(const + OggFLAC__SeekableStreamDecoder + * decoder, + FLAC__uint64 * + offset, void *fdata) +{ + FlacData *data = (FlacData *) fdata; + + *offset = (long)(data->inStream->offset); + + return OggFLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK; +} + +static OggFLAC__SeekableStreamDecoderLengthStatus of_length_cb(const + OggFLAC__SeekableStreamDecoder + * decoder, + FLAC__uint64 * + length, + void *fdata) +{ + FlacData *data = (FlacData *) fdata; + + *length = (size_t) (data->inStream->size); + + return OggFLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK; +} + +static FLAC__bool of_EOF_cb(const OggFLAC__SeekableStreamDecoder * decoder, + void *fdata) +{ + FlacData *data = (FlacData *) fdata; + + if (inputStreamAtEOF(data->inStream) == 1) + return true; + return false; +} + +static void of_error_cb(const OggFLAC__SeekableStreamDecoder * decoder, + FLAC__StreamDecoderErrorStatus status, void *fdata) +{ + flac_error_common_cb("oggflac", status, (FlacData *) fdata); +} + +static void oggflacPrintErroredState(OggFLAC__SeekableStreamDecoderState state) +{ + switch (state) { + case OggFLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR: + ERROR("oggflac allocation error\n"); + break; + case OggFLAC__SEEKABLE_STREAM_DECODER_READ_ERROR: + ERROR("oggflac read error\n"); + break; + case OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR: + ERROR("oggflac seek error\n"); + break; + case OggFLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR: + ERROR("oggflac seekable stream error\n"); + break; + case OggFLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED: + ERROR("oggflac decoder already initialized\n"); + break; + case OggFLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK: + ERROR("invalid oggflac callback\n"); + break; + case OggFLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED: + ERROR("oggflac decoder uninitialized\n"); + break; + case OggFLAC__SEEKABLE_STREAM_DECODER_OK: + case OggFLAC__SEEKABLE_STREAM_DECODER_SEEKING: + case OggFLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM: + break; + } +} + +static FLAC__StreamDecoderWriteStatus oggflacWrite(const + OggFLAC__SeekableStreamDecoder + * decoder, + const FLAC__Frame * frame, + const FLAC__int32 * + const buf[], void *vdata) +{ + FlacData *data = (FlacData *) vdata; + FLAC__uint32 samples = frame->header.blocksize; + FLAC__uint16 u16; + unsigned char *uc; + int c_samp, c_chan, d_samp; + int i; + float timeChange; + + timeChange = ((float)samples) / frame->header.sample_rate; + data->time += timeChange; + + /* ogg123 uses a complicated method of calculating bitrate + * with averaging which I'm not too fond of. + * (waste of memory/CPU cycles, especially given this is _lossless_) + * a get_decode_position() is not available in OggFLAC, either + * + * this does not give an accurate bitrate: + * (bytes_last_read was set in the read callback) + data->bitRate = ((8.0 * data->bytes_last_read * + frame->header.sample_rate) + /((float)samples * 1000)) + 0.5; + */ + + for (c_samp = d_samp = 0; c_samp < frame->header.blocksize; c_samp++) { + for (c_chan = 0; c_chan < frame->header.channels; + c_chan++, d_samp++) { + u16 = buf[c_chan][c_samp]; + uc = (unsigned char *)&u16; + for (i = 0; i < (data->dc->audioFormat.bits / 8); i++) { + if (data->chunk_length >= FLAC_CHUNK_SIZE) { + if (flacSendChunk(data) < 0) { + return + FLAC__STREAM_DECODER_WRITE_STATUS_ABORT; + } + data->chunk_length = 0; + if (data->dc->seek) { + return + FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; + } + } + data->chunk[data->chunk_length++] = *(uc++); + } + } + } + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +/* used by TagDup */ +static void of_metadata_dup_cb(const OggFLAC__SeekableStreamDecoder * decoder, + const FLAC__StreamMetadata * block, void *vdata) +{ + FlacData *data = (FlacData *) vdata; + + switch (block->type) { + case FLAC__METADATA_TYPE_STREAMINFO: + if (!data->tag) + data->tag = newMpdTag(); + data->tag->time = ((float)block->data.stream_info. + total_samples) / + block->data.stream_info.sample_rate + 0.5; + return; + case FLAC__METADATA_TYPE_VORBIS_COMMENT: + copyVorbisCommentBlockToMpdTag(block, data->tag); + default: + break; + } +} + +/* used by decode */ +static void of_metadata_decode_cb(const OggFLAC__SeekableStreamDecoder * dec, + const FLAC__StreamMetadata * block, + void *vdata) +{ + flac_metadata_common_cb(block, (FlacData *) vdata); +} + +static OggFLAC__SeekableStreamDecoder + * full_decoder_init_and_read_metadata(FlacData * data, + unsigned int metadata_only) +{ + OggFLAC__SeekableStreamDecoder *decoder = NULL; + unsigned int s = 1; + + if (!(decoder = OggFLAC__seekable_stream_decoder_new())) + return NULL; + + if (metadata_only) { + s &= OggFLAC__seekable_stream_decoder_set_metadata_callback + (decoder, of_metadata_dup_cb); + s &= OggFLAC__seekable_stream_decoder_set_metadata_respond + (decoder, FLAC__METADATA_TYPE_STREAMINFO); + } else { + s &= OggFLAC__seekable_stream_decoder_set_metadata_callback + (decoder, of_metadata_decode_cb); + } + + s &= OggFLAC__seekable_stream_decoder_set_read_callback(decoder, + of_read_cb); + s &= OggFLAC__seekable_stream_decoder_set_seek_callback(decoder, + of_seek_cb); + s &= OggFLAC__seekable_stream_decoder_set_tell_callback(decoder, + of_tell_cb); + s &= OggFLAC__seekable_stream_decoder_set_length_callback(decoder, + of_length_cb); + s &= OggFLAC__seekable_stream_decoder_set_eof_callback(decoder, + of_EOF_cb); + s &= OggFLAC__seekable_stream_decoder_set_write_callback(decoder, + oggflacWrite); + s &= OggFLAC__seekable_stream_decoder_set_metadata_respond(decoder, + FLAC__METADATA_TYPE_VORBIS_COMMENT); + s &= OggFLAC__seekable_stream_decoder_set_error_callback(decoder, + of_error_cb); + s &= OggFLAC__seekable_stream_decoder_set_client_data(decoder, + (void *)data); + + if (!s) { + ERROR("oggflac problem before init()\n"); + goto fail; + } + if (OggFLAC__seekable_stream_decoder_init(decoder) != + OggFLAC__SEEKABLE_STREAM_DECODER_OK) { + ERROR("oggflac problem doing init()\n"); + goto fail; + } + if (!OggFLAC__seekable_stream_decoder_process_until_end_of_metadata + (decoder)) { + ERROR("oggflac problem reading metadata\n"); + goto fail; + } + + return decoder; + +fail: + oggflacPrintErroredState(OggFLAC__seekable_stream_decoder_get_state + (decoder)); + OggFLAC__seekable_stream_decoder_delete(decoder); + return NULL; +} + +/* public functions: */ +static MpdTag *oggflac_TagDup(char *file) +{ + InputStream inStream; + OggFLAC__SeekableStreamDecoder *decoder; + FlacData data; + + if (openInputStream(&inStream, file) < 0) + return NULL; + if (ogg_stream_type_detect(&inStream) != FLAC) { + closeInputStream(&inStream); + return NULL; + } + + init_FlacData(&data, NULL, NULL, &inStream); + + /* errors here won't matter, + * data.tag will be set or unset, that's all we care about */ + decoder = full_decoder_init_and_read_metadata(&data, 1); + + oggflac_cleanup(&inStream, &data, decoder); + + return data.tag; +} + +static unsigned int oggflac_try_decode(InputStream * inStream) +{ + return (ogg_stream_type_detect(inStream) == FLAC) ? 1 : 0; +} + +static int oggflac_decode(OutputBuffer * cb, DecoderControl * dc, + InputStream * inStream) +{ + OggFLAC__SeekableStreamDecoder *decoder = NULL; + FlacData data; + int ret = 0; + + init_FlacData(&data, cb, dc, inStream); + + if (!(decoder = full_decoder_init_and_read_metadata(&data, 0))) { + ret = -1; + goto fail; + } + + dc->state = DECODE_STATE_DECODE; + + while (1) { + OggFLAC__seekable_stream_decoder_process_single(decoder); + if (OggFLAC__seekable_stream_decoder_get_state(decoder) != + OggFLAC__SEEKABLE_STREAM_DECODER_OK) { + break; + } + if (dc->seek) { + FLAC__uint64 sampleToSeek = dc->seekWhere * + dc->audioFormat.sampleRate + 0.5; + if (OggFLAC__seekable_stream_decoder_seek_absolute + (decoder, sampleToSeek)) { + clearOutputBuffer(cb); + data.time = ((float)sampleToSeek) / + dc->audioFormat.sampleRate; + data.position = 0; + } else + dc->seekError = 1; + dc->seek = 0; + } + } + + if (!dc->stop) { + oggflacPrintErroredState + (OggFLAC__seekable_stream_decoder_get_state(decoder)); + OggFLAC__seekable_stream_decoder_finish(decoder); + } + /* send last little bit */ + if (data.chunk_length > 0 && !dc->stop) { + flacSendChunk(&data); + flushOutputBuffer(data.cb); + } + + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + +fail: + oggflac_cleanup(inStream, &data, decoder); + + return ret; +} + +static char *oggflac_Suffixes[] = { "ogg", NULL }; +static char *oggflac_mime_types[] = { "audio/x-flac+ogg", + "application/ogg", + "application/x-ogg", + NULL }; + +InputPlugin oggflacPlugin = { + "oggflac", + NULL, + NULL, + oggflac_try_decode, + oggflac_decode, + NULL, + oggflac_TagDup, + INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, + oggflac_Suffixes, + oggflac_mime_types +}; + +#else /* !HAVE_FLAC */ + +InputPlugin oggflacPlugin; + +#endif /* HAVE_OGGFLAC */ diff --git a/trunk/src/inputPlugins/oggvorbis_plugin.c b/trunk/src/inputPlugins/oggvorbis_plugin.c new file mode 100644 index 000000000..4b4b87c8a --- /dev/null +++ b/trunk/src/inputPlugins/oggvorbis_plugin.c @@ -0,0 +1,434 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +/* TODO 'ogg' should probably be replaced with 'oggvorbis' in all instances */ + +#include "../inputPlugin.h" + +#ifdef HAVE_OGGVORBIS + +#include "_ogg_common.h" + +#include "../utils.h" +#include "../audio.h" +#include "../log.h" +#include "../pcm_utils.h" +#include "../inputStream.h" +#include "../outputBuffer.h" +#include "../replayGain.h" + +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> + +#ifndef HAVE_TREMOR +#include <vorbis/vorbisfile.h> +#else +#include <tremor/ivorbisfile.h> +/* Macros to make Tremor's API look like libogg. Tremor always + returns host-byte-order 16-bit signed data, and uses integer + milliseconds where libogg uses double seconds. +*/ +#define ov_read(VF, BUFFER, LENGTH, BIGENDIANP, WORD, SGNED, BITSTREAM) \ + ov_read(VF, BUFFER, LENGTH, BITSTREAM) +#define ov_time_total(VF, I) ((double)ov_time_total(VF, I)/1000) +#define ov_time_tell(VF) ((double)ov_time_tell(VF)/1000) +#define ov_time_seek_page(VF, S) (ov_time_seek_page(VF, (S)*1000)) +#endif /* HAVE_TREMOR */ + +#include <errno.h> + +#ifdef WORDS_BIGENDIAN +#define OGG_DECODE_USE_BIGENDIAN 1 +#else +#define OGG_DECODE_USE_BIGENDIAN 0 +#endif + +typedef struct _OggCallbackData { + InputStream *inStream; + DecoderControl *dc; +} OggCallbackData; + +static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *vdata) +{ + size_t ret = 0; + OggCallbackData *data = (OggCallbackData *) vdata; + + while (1) { + ret = readFromInputStream(data->inStream, ptr, size, nmemb); + if (ret == 0 && !inputStreamAtEOF(data->inStream) && + !data->dc->stop) { + my_usleep(10000); + } else + break; + } + errno = 0; + /*if(ret<0) errno = ((InputStream *)inStream)->error; */ + + return ret; +} + +static int ogg_seek_cb(void *vdata, ogg_int64_t offset, int whence) +{ + OggCallbackData *data = (OggCallbackData *) vdata; + + return seekInputStream(data->inStream, offset, whence); +} + +static int ogg_close_cb(void *vdata) +{ + OggCallbackData *data = (OggCallbackData *) vdata; + + return closeInputStream(data->inStream); +} + +static long ogg_tell_cb(void *vdata) +{ + OggCallbackData *data = (OggCallbackData *) vdata; + + return (long)(data->inStream->offset); +} + +static char *ogg_parseComment(char *comment, char *needle) +{ + int len = strlen(needle); + + if (strncasecmp(comment, needle, len) == 0 && *(comment + len) == '=') { + return comment + len + 1; + } + + return NULL; +} + +static void ogg_getReplayGainInfo(char **comments, ReplayGainInfo ** infoPtr) +{ + char *temp; + int found = 0; + + if (*infoPtr) + freeReplayGainInfo(*infoPtr); + *infoPtr = newReplayGainInfo(); + + while (*comments) { + if ((temp = + ogg_parseComment(*comments, "replaygain_track_gain"))) { + (*infoPtr)->trackGain = atof(temp); + found = 1; + } else if ((temp = ogg_parseComment(*comments, + "replaygain_album_gain"))) { + (*infoPtr)->albumGain = atof(temp); + found = 1; + } else if ((temp = ogg_parseComment(*comments, + "replaygain_track_peak"))) { + (*infoPtr)->trackPeak = atof(temp); + found = 1; + } else if ((temp = ogg_parseComment(*comments, + "replaygain_album_peak"))) { + (*infoPtr)->albumPeak = atof(temp); + found = 1; + } + + comments++; + } + + if (!found) { + freeReplayGainInfo(*infoPtr); + *infoPtr = NULL; + } +} + +static const char *VORBIS_COMMENT_TRACK_KEY = "tracknumber"; +static const char *VORBIS_COMMENT_DISC_KEY = "discnumber"; + +static unsigned int ogg_parseCommentAddToTag(char *comment, + unsigned int itemType, + MpdTag ** tag) +{ + const char *needle; + unsigned int len; + switch (itemType) { + case TAG_ITEM_TRACK: + needle = VORBIS_COMMENT_TRACK_KEY; + break; + case TAG_ITEM_DISC: + needle = VORBIS_COMMENT_DISC_KEY; + break; + default: + needle = mpdTagItemKeys[itemType]; + } + len = strlen(needle); + + if (strncasecmp(comment, needle, len) == 0 && *(comment + len) == '=') { + if (!*tag) + *tag = newMpdTag(); + + addItemToMpdTag(*tag, itemType, comment + len + 1); + + return 1; + } + + return 0; +} + +static MpdTag *oggCommentsParse(char **comments) +{ + MpdTag *tag = NULL; + + while (*comments) { + int j; + for (j = TAG_NUM_OF_ITEM_TYPES; --j >= 0;) { + if (ogg_parseCommentAddToTag(*comments, j, &tag)) + break; + } + comments++; + } + + return tag; +} + +static void putOggCommentsIntoOutputBuffer(OutputBuffer * cb, char *streamName, + char **comments) +{ + MpdTag *tag; + + tag = oggCommentsParse(comments); + if (!tag && streamName) { + tag = newMpdTag(); + } + if (!tag) + return; + + /*if(tag->artist) printf("Artist: %s\n", tag->artist); + if(tag->album) printf("Album: %s\n", tag->album); + if(tag->track) printf("Track: %s\n", tag->track); + if(tag->title) printf("Title: %s\n", tag->title); */ + + if (streamName) { + clearItemsFromMpdTag(tag, TAG_ITEM_NAME); + addItemToMpdTag(tag, TAG_ITEM_NAME, streamName); + } + + copyMpdTagToOutputBuffer(cb, tag); + + freeMpdTag(tag); +} + +/* public */ +static int oggvorbis_decode(OutputBuffer * cb, DecoderControl * dc, + InputStream * inStream) +{ + OggVorbis_File vf; + ov_callbacks callbacks; + OggCallbackData data; + int current_section; + int prev_section = -1; + int eof = 0; + long ret; +#define OGG_CHUNK_SIZE 4096 + char chunk[OGG_CHUNK_SIZE]; + int chunkpos = 0; + long bitRate = 0; + long test; + ReplayGainInfo *replayGainInfo = NULL; + char **comments; + char *errorStr; + + data.inStream = inStream; + data.dc = dc; + + callbacks.read_func = ogg_read_cb; + callbacks.seek_func = ogg_seek_cb; + callbacks.close_func = ogg_close_cb; + callbacks.tell_func = ogg_tell_cb; + + if ((ret = ov_open_callbacks(&data, &vf, NULL, 0, callbacks)) < 0) { + closeInputStream(inStream); + if (!dc->stop) { + switch (ret) { + case OV_EREAD: + errorStr = "read error"; + break; + case OV_ENOTVORBIS: + errorStr = "not vorbis stream"; + break; + case OV_EVERSION: + errorStr = "vorbis version mismatch"; + break; + case OV_EBADHEADER: + errorStr = "invalid vorbis header"; + break; + case OV_EFAULT: + errorStr = "internal logic error"; + break; + default: + errorStr = "unknown error"; + break; + } + ERROR("Error decoding Ogg Vorbis stream: %s\n", + errorStr); + return -1; + } else { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } + return 0; + } + + dc->totalTime = ov_time_total(&vf, -1); + if (dc->totalTime < 0) + dc->totalTime = 0; + + dc->audioFormat.bits = 16; + + while (!eof) { + if (dc->seek) { + if (0 == ov_time_seek_page(&vf, dc->seekWhere)) { + clearOutputBuffer(cb); + chunkpos = 0; + } else + dc->seekError = 1; + dc->seek = 0; + } + ret = ov_read(&vf, chunk + chunkpos, + OGG_CHUNK_SIZE - chunkpos, + OGG_DECODE_USE_BIGENDIAN, 2, 1, ¤t_section); + + if (current_section != prev_section) { + /*printf("new song!\n"); */ + vorbis_info *vi = ov_info(&vf, -1); + dc->audioFormat.channels = vi->channels; + dc->audioFormat.sampleRate = vi->rate; + if (dc->state == DECODE_STATE_START) { + getOutputAudioFormat(&(dc->audioFormat), + &(cb->audioFormat)); + dc->state = DECODE_STATE_DECODE; + } + comments = ov_comment(&vf, -1)->user_comments; + putOggCommentsIntoOutputBuffer(cb, inStream->metaName, + comments); + ogg_getReplayGainInfo(comments, &replayGainInfo); + } + + prev_section = current_section; + + if (ret <= 0 && ret != OV_HOLE) { + eof = 1; + break; + } + if (ret == OV_HOLE) + ret = 0; + + chunkpos += ret; + + if (chunkpos >= OGG_CHUNK_SIZE) { + if ((test = ov_bitrate_instant(&vf)) > 0) { + bitRate = test / 1000; + } + sendDataToOutputBuffer(cb, inStream, dc, + inStream->seekable, + chunk, chunkpos, + ov_pcm_tell(&vf) / + dc->audioFormat.sampleRate, + bitRate, replayGainInfo); + chunkpos = 0; + if (dc->stop) + break; + } + } + + if (!dc->stop && chunkpos > 0) { + sendDataToOutputBuffer(cb, NULL, dc, inStream->seekable, + chunk, chunkpos, + ov_time_tell(&vf), bitRate, + replayGainInfo); + } + + if (replayGainInfo) + freeReplayGainInfo(replayGainInfo); + + ov_clear(&vf); + + flushOutputBuffer(cb); + + if (dc->stop) { + dc->state = DECODE_STATE_STOP; + dc->stop = 0; + } else + dc->state = DECODE_STATE_STOP; + + return 0; +} + +static MpdTag *oggvorbis_TagDup(char *file) +{ + MpdTag *ret = NULL; + FILE *fp; + OggVorbis_File vf; + + fp = fopen(file, "r"); + if (!fp) { + DEBUG("oggvorbis_TagDup: Failed to open file: '%s', %s\n", + file, strerror(errno)); + return NULL; + } + if (ov_open(fp, &vf, NULL, 0) < 0) { + fclose(fp); + return NULL; + } + + ret = oggCommentsParse(ov_comment(&vf, -1)->user_comments); + + if (!ret) + ret = newMpdTag(); + ret->time = (int)(ov_time_total(&vf, -1) + 0.5); + + ov_clear(&vf); + + return ret; +} + +static unsigned int oggvorbis_try_decode(InputStream * inStream) +{ + return (ogg_stream_type_detect(inStream) == VORBIS) ? 1 : 0; +} + +static char *oggvorbis_Suffixes[] = { "ogg", NULL }; +static char *oggvorbis_MimeTypes[] = { "application/ogg", + "audio/x-vorbis+ogg", + "application/x-ogg", + NULL }; + +InputPlugin oggvorbisPlugin = { + "oggvorbis", + NULL, + NULL, + oggvorbis_try_decode, + oggvorbis_decode, + NULL, + oggvorbis_TagDup, + INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE, + oggvorbis_Suffixes, + oggvorbis_MimeTypes +}; + +#else /* !HAVE_OGGVORBIS */ + +InputPlugin oggvorbisPlugin; + +#endif /* HAVE_OGGVORBIS */ diff --git a/trunk/src/inputStream.c b/trunk/src/inputStream.c new file mode 100644 index 000000000..013d75f17 --- /dev/null +++ b/trunk/src/inputStream.c @@ -0,0 +1,83 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "inputStream.h" + +#include "inputStream_file.h" +#include "inputStream_http.h" + +#include <stdio.h> +#include <sys/types.h> +#include <unistd.h> + +void initInputStream(void) +{ + inputStream_initFile(); + inputStream_initHttp(); +} + +int openInputStream(InputStream * inStream, char *url) +{ + inStream->offset = 0; + inStream->size = 0; + inStream->error = 0; + inStream->mime = NULL; + inStream->seekable = 0; + inStream->metaName = NULL; + inStream->metaTitle = NULL; + + if (inputStream_fileOpen(inStream, url) == 0) + return 0; + if (inputStream_httpOpen(inStream, url) == 0) + return 0; + + return -1; +} + +int seekInputStream(InputStream * inStream, long offset, int whence) +{ + return inStream->seekFunc(inStream, offset, whence); +} + +size_t readFromInputStream(InputStream * inStream, void *ptr, size_t size, + size_t nmemb) +{ + return inStream->readFunc(inStream, ptr, size, nmemb); +} + +int closeInputStream(InputStream * inStream) +{ + if (inStream->mime) + free(inStream->mime); + if (inStream->metaName) + free(inStream->metaName); + if (inStream->metaTitle) + free(inStream->metaTitle); + + return inStream->closeFunc(inStream); +} + +int inputStreamAtEOF(InputStream * inStream) +{ + return inStream->atEOFFunc(inStream); +} + +int bufferInputStream(InputStream * inStream) +{ + return inStream->bufferFunc(inStream); +} diff --git a/trunk/src/inputStream.h b/trunk/src/inputStream.h new file mode 100644 index 000000000..74397f07f --- /dev/null +++ b/trunk/src/inputStream.h @@ -0,0 +1,70 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef INPUT_STREAM_H +#define INPUT_STREAM_H + +#include <stdlib.h> + +typedef struct _InputStream InputStream; + +typedef int (*InputStreamSeekFunc) (InputStream * inStream, long offset, + int whence); +typedef size_t(*InputStreamReadFunc) (InputStream * inStream, void *ptr, + size_t size, size_t nmemb); +typedef int (*InputStreamCloseFunc) (InputStream * inStream); +typedef int (*InputStreamAtEOFFunc) (InputStream * inStream); +typedef int (*InputStreamBufferFunc) (InputStream * inStream); + +struct _InputStream { + int error; + long offset; + size_t size; + char *mime; + int seekable; + + /* don't touc this stuff */ + InputStreamSeekFunc seekFunc; + InputStreamReadFunc readFunc; + InputStreamCloseFunc closeFunc; + InputStreamAtEOFFunc atEOFFunc; + InputStreamBufferFunc bufferFunc; + void *data; + char *metaName; + char *metaTitle; +}; + +void initInputStream(void); + +int isUrlSaneForInputStream(char *url); + +/* if an error occurs for these 3 functions, then -1 is returned and errno + for the input stream is set */ +int openInputStream(InputStream * inStream, char *url); +int seekInputStream(InputStream * inStream, long offset, int whence); +int closeInputStream(InputStream * inStream); +int inputStreamAtEOF(InputStream * inStream); + +/* return value: -1 is error, 1 inidicates stuff was buffered, 0 means nothing + was buffered */ +int bufferInputStream(InputStream * inStream); + +size_t readFromInputStream(InputStream * inStream, void *ptr, size_t size, + size_t nmemb); + +#endif diff --git a/trunk/src/inputStream_file.c b/trunk/src/inputStream_file.c new file mode 100644 index 000000000..389aaad01 --- /dev/null +++ b/trunk/src/inputStream_file.c @@ -0,0 +1,119 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "inputStream_file.h" + +#include "log.h" + +#include <stdio.h> +#include <string.h> +#include <sys/types.h> +#include <unistd.h> +#include <errno.h> +#define _XOPEN_SOURCE 600 +#include <fcntl.h> + +void inputStream_initFile(void) +{ +} + +int inputStream_fileOpen(InputStream * inStream, char *filename) +{ + FILE *fp; + + fp = fopen(filename, "r"); + if (!fp) { + inStream->error = errno; + return -1; + } + + inStream->seekable = 1; + + fseek(fp, 0, SEEK_END); + inStream->size = ftell(fp); + fseek(fp, 0, SEEK_SET); + +#ifdef POSIX_FADV_SEQUENTIAL + posix_fadvise(fileno(fp), (off_t)0, inStream->size, POSIX_FADV_SEQUENTIAL); +#endif + + inStream->data = fp; + inStream->seekFunc = inputStream_fileSeek; + inStream->closeFunc = inputStream_fileClose; + inStream->readFunc = inputStream_fileRead; + inStream->atEOFFunc = inputStream_fileAtEOF; + inStream->bufferFunc = inputStream_fileBuffer; + + return 0; +} + +int inputStream_fileSeek(InputStream * inStream, long offset, int whence) +{ + if (fseek((FILE *) inStream->data, offset, whence) == 0) { + inStream->offset = ftell((FILE *) inStream->data); + } else { + inStream->error = errno; + return -1; + } + + return 0; +} + +size_t inputStream_fileRead(InputStream * inStream, void *ptr, size_t size, + size_t nmemb) +{ + size_t readSize; + + readSize = fread(ptr, size, nmemb, (FILE *) inStream->data); + if (readSize <= 0 && ferror((FILE *) inStream->data)) { + inStream->error = errno; + DEBUG("inputStream_fileRead: error reading: %s\n", + strerror(inStream->error)); + } + + inStream->offset = ftell((FILE *) inStream->data); + + return readSize; +} + +int inputStream_fileClose(InputStream * inStream) +{ + if (fclose((FILE *) inStream->data) < 0) { + inStream->error = errno; + return -1; + } + + return 0; +} + +int inputStream_fileAtEOF(InputStream * inStream) +{ + if (feof((FILE *) inStream->data)) + return 1; + + if (ferror((FILE *) inStream->data) && inStream->error != EINTR) { + return 1; + } + + return 0; +} + +int inputStream_fileBuffer(InputStream * inStream) +{ + return 0; +} diff --git a/trunk/src/inputStream_file.h b/trunk/src/inputStream_file.h new file mode 100644 index 000000000..fad7ac26e --- /dev/null +++ b/trunk/src/inputStream_file.h @@ -0,0 +1,39 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef INPUT_STREAM_FILE_H +#define INPUT_STREAM_FILE_H + +#include "inputStream.h" + +void inputStream_initFile(void); + +int inputStream_fileOpen(InputStream * inStream, char *filename); + +int inputStream_fileSeek(InputStream * inStream, long offset, int whence); + +size_t inputStream_fileRead(InputStream * inStream, void *ptr, size_t size, + size_t nmemb); + +int inputStream_fileClose(InputStream * inStream); + +int inputStream_fileAtEOF(InputStream * inStream); + +int inputStream_fileBuffer(InputStream * inStream); + +#endif diff --git a/trunk/src/inputStream_http.c b/trunk/src/inputStream_http.c new file mode 100644 index 000000000..3f18575dd --- /dev/null +++ b/trunk/src/inputStream_http.c @@ -0,0 +1,912 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "inputStream_http.h" + +#include "utils.h" +#include "log.h" +#include "conf.h" + +#include <stdio.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <sys/param.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> + +#define HTTP_CONN_STATE_CLOSED 0 +#define HTTP_CONN_STATE_INIT 1 +#define HTTP_CONN_STATE_HELLO 2 +#define HTTP_CONN_STATE_OPEN 3 +#define HTTP_CONN_STATE_REOPEN 4 + +#define HTTP_BUFFER_SIZE_DEFAULT 131072 +#define HTTP_PREBUFFER_SIZE_DEFAULT (HTTP_BUFFER_SIZE_DEFAULT >> 2) + +#define HTTP_REDIRECT_MAX 10 + +static char *proxyHost; +static char *proxyPort; +static char *proxyUser; +static char *proxyPassword; +static int bufferSize = HTTP_BUFFER_SIZE_DEFAULT; +static int prebufferSize = HTTP_PREBUFFER_SIZE_DEFAULT; + +typedef struct _InputStreemHTTPData { + char *host; + char *path; + char *port; + int sock; + int connState; + char *buffer; + size_t buflen; + int timesRedirected; + int icyMetaint; + int prebuffer; + int icyOffset; + char *proxyAuth; + char *httpAuth; +} InputStreamHTTPData; + +void inputStream_initHttp(void) +{ + ConfigParam *param = getConfigParam(CONF_HTTP_PROXY_HOST); + char *test; + + if (param) { + proxyHost = param->value; + + param = getConfigParam(CONF_HTTP_PROXY_PORT); + + if (!param) { + FATAL("%s specified but not %s", CONF_HTTP_PROXY_HOST, + CONF_HTTP_PROXY_PORT); + } + proxyPort = param->value; + + param = getConfigParam(CONF_HTTP_PROXY_USER); + + if (param) { + proxyUser = param->value; + + param = getConfigParam(CONF_HTTP_PROXY_PASSWORD); + + if (!param) { + FATAL("%s specified but not %s\n", + CONF_HTTP_PROXY_USER, + CONF_HTTP_PROXY_PASSWORD); + } + + proxyPassword = param->value; + } else { + param = getConfigParam(CONF_HTTP_PROXY_PASSWORD); + + if (param) { + FATAL("%s specified but not %s\n", + CONF_HTTP_PROXY_PASSWORD, CONF_HTTP_PROXY_USER); + } + } + } else if ((param = getConfigParam(CONF_HTTP_PROXY_PORT))) { + FATAL("%s specified but not %s, line %i\n", + CONF_HTTP_PROXY_PORT, CONF_HTTP_PROXY_HOST, param->line); + } else if ((param = getConfigParam(CONF_HTTP_PROXY_USER))) { + FATAL("%s specified but not %s, line %i\n", + CONF_HTTP_PROXY_USER, CONF_HTTP_PROXY_HOST, param->line); + } else if ((param = getConfigParam(CONF_HTTP_PROXY_PASSWORD))) { + FATAL("%s specified but not %s, line %i\n", + CONF_HTTP_PROXY_PASSWORD, CONF_HTTP_PROXY_HOST, + param->line); + } + + param = getConfigParam(CONF_HTTP_BUFFER_SIZE); + + if (param) { + bufferSize = strtol(param->value, &test, 10); + + if (bufferSize <= 0 || *test != '\0') { + FATAL("\"%s\" specified for %s at line %i is not a " + "positive integer\n", + param->value, CONF_HTTP_BUFFER_SIZE, param->line); + } + + bufferSize *= 1024; + + if (prebufferSize > bufferSize) + prebufferSize = bufferSize; + } + + param = getConfigParam(CONF_HTTP_PREBUFFER_SIZE); + + if (param) { + prebufferSize = strtol(param->value, &test, 10); + + if (prebufferSize <= 0 || *test != '\0') { + FATAL("\"%s\" specified for %s at line %i is not a " + "positive integer\n", + param->value, CONF_HTTP_PREBUFFER_SIZE, + param->line); + } + + prebufferSize *= 1024; + } + + if (prebufferSize > bufferSize) + prebufferSize = bufferSize; +} + +/* base64 code taken from xmms */ + +#define BASE64_LENGTH(len) (4 * (((len) + 2) / 3)) + +static char *base64Dup(char *s) +{ + int i; + int len = strlen(s); + char *ret = xcalloc(BASE64_LENGTH(len) + 1, 1); + unsigned char *p = (unsigned char *)ret; + + char tbl[64] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/' + }; + + /* Transform the 3x8 bits to 4x6 bits, as required by base64. */ + for (i = 0; i < len; i += 3) { + *p++ = tbl[s[0] >> 2]; + *p++ = tbl[((s[0] & 3) << 4) + (s[1] >> 4)]; + *p++ = tbl[((s[1] & 0xf) << 2) + (s[2] >> 6)]; + *p++ = tbl[s[2] & 0x3f]; + s += 3; + } + /* Pad the result if necessary... */ + if (i == len + 1) + *(p - 1) = '='; + else if (i == len + 2) + *(p - 1) = *(p - 2) = '='; + /* ...and zero-terminate it. */ + *p = '\0'; + + return ret; +} + +static char *authString(char *header, char *user, char *password) +{ + char *ret = NULL; + int templen; + char *temp; + char *temp64; + + if (!user || !password) + return NULL; + + templen = strlen(user) + strlen(password) + 2; + temp = xmalloc(templen); + strcpy(temp, user); + strcat(temp, ":"); + strcat(temp, password); + temp64 = base64Dup(temp); + free(temp); + + ret = xmalloc(strlen(temp64) + strlen(header) + 3); + strcpy(ret, header); + strcat(ret, temp64); + strcat(ret, "\r\n"); + free(temp64); + + return ret; +} + +#define PROXY_AUTH_HEADER "Proxy-Authorization: Basic " +#define HTTP_AUTH_HEADER "Authorization: Basic " + +#define proxyAuthString(x, y) authString(PROXY_AUTH_HEADER, x, y) +#define httpAuthString(x, y) authString(HTTP_AUTH_HEADER, x, y) + +static InputStreamHTTPData *newInputStreamHTTPData(void) +{ + InputStreamHTTPData *ret = xmalloc(sizeof(InputStreamHTTPData)); + + if (proxyHost) { + ret->proxyAuth = proxyAuthString(proxyUser, proxyPassword); + } else + ret->proxyAuth = NULL; + + ret->httpAuth = NULL; + ret->host = NULL; + ret->path = NULL; + ret->port = NULL; + ret->connState = HTTP_CONN_STATE_CLOSED; + ret->timesRedirected = 0; + ret->icyMetaint = 0; + ret->prebuffer = 0; + ret->icyOffset = 0; + ret->buffer = xmalloc(bufferSize); + + return ret; +} + +static void freeInputStreamHTTPData(InputStreamHTTPData * data) +{ + if (data->host) + free(data->host); + if (data->path) + free(data->path); + if (data->port) + free(data->port); + if (data->proxyAuth) + free(data->proxyAuth); + if (data->httpAuth) + free(data->httpAuth); + + free(data->buffer); + + free(data); +} + +static int parseUrl(InputStreamHTTPData * data, char *url) +{ + char *temp; + char *colon; + char *slash; + char *at; + int len; + + if (strncmp("http://", url, strlen("http://")) != 0) + return -1; + + temp = url + strlen("http://"); + + colon = strchr(temp, ':'); + at = strchr(temp, '@'); + + if (data->httpAuth) { + free(data->httpAuth); + data->httpAuth = NULL; + } + + if (at) { + char *user; + char *passwd; + + if (colon && colon < at) { + user = xmalloc(colon - temp + 1); + memcpy(user, temp, colon - temp); + user[colon - temp] = '\0'; + + passwd = xmalloc(at - colon); + memcpy(passwd, colon + 1, at - colon - 1); + passwd[at - colon - 1] = '\0'; + } else { + user = xmalloc(at - temp + 1); + memcpy(user, temp, at - temp); + user[at - temp] = '\0'; + + passwd = xstrdup(""); + } + + data->httpAuth = httpAuthString(user, passwd); + + free(user); + free(passwd); + + temp = at + 1; + colon = strchr(temp, ':'); + } + + slash = strchr(temp, '/'); + + if (slash && colon && slash <= colon) + return -1; + + /* fetch the host portion */ + if (colon) + len = colon - temp + 1; + else if (slash) + len = slash - temp + 1; + else + len = strlen(temp) + 1; + + if (len <= 1) + return -1; + + data->host = xmalloc(len); + memcpy(data->host, temp, len - 1); + data->host[len - 1] = '\0'; + /* fetch the port */ + if (colon && (!slash || slash != colon + 1)) { + len = strlen(colon) - 1; + if (slash) + len -= strlen(slash); + data->port = xmalloc(len + 1); + memcpy(data->port, colon + 1, len); + data->port[len] = '\0'; + DEBUG(__FILE__ ": Port: %s\n", data->port); + } else { + data->port = xstrdup("80"); + } + + /* fetch the path */ + if (proxyHost) + data->path = xstrdup(url); + else + data->path = xstrdup(slash ? slash : "/"); + + return 0; +} + +static int initHTTPConnection(InputStream * inStream) +{ + char *connHost; + char *connPort; + struct addrinfo *ans = NULL; + struct addrinfo *ap = NULL; + struct addrinfo hints; + int error, flags; + InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data; + /** + * Setup hints + */ + hints.ai_flags = 0; + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + hints.ai_addrlen = 0; + hints.ai_addr = NULL; + hints.ai_canonname = NULL; + hints.ai_next = NULL; + + if (proxyHost) { + connHost = proxyHost; + connPort = proxyPort; + } else { + connHost = data->host; + connPort = data->port; + } + + error = getaddrinfo(connHost, connPort, &hints, &ans); + if (error) { + DEBUG(__FILE__ ": Error getting address info: %s\n", + gai_strerror(error)); + return -1; + } + + /* loop through possible addresses */ + for (ap = ans; ap != NULL; ap = ap->ai_next) { + if ((data->sock = socket(ap->ai_family, ap->ai_socktype, + ap->ai_protocol)) < 0) { + DEBUG(__FILE__ ": unable to connect: %s\n", + strerror(errno)); + freeaddrinfo(ans); + return -1; + } + + flags = fcntl(data->sock, F_GETFL, 0); + fcntl(data->sock, F_SETFL, flags | O_NONBLOCK); + + if (connect(data->sock, ap->ai_addr, ap->ai_addrlen) >= 0 + || errno == EINPROGRESS) { + data->connState = HTTP_CONN_STATE_INIT; + data->buflen = 0; + freeaddrinfo(ans); + return 0; /* success */ + } + + /* failed, get the next one */ + + DEBUG(__FILE__ ": unable to connect: %s\n", strerror(errno)); + close(data->sock); + } + + freeaddrinfo(ans); + return -1; /* failed */ +} + +static int finishHTTPInit(InputStream * inStream) +{ + InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data; + struct timeval tv; + fd_set writeSet; + fd_set errorSet; + int error; + socklen_t error_len = sizeof(int); + int ret; + int length; + char request[2048]; + + tv.tv_sec = 0; + tv.tv_usec = 0; + + FD_ZERO(&writeSet); + FD_ZERO(&errorSet); + FD_SET(data->sock, &writeSet); + FD_SET(data->sock, &errorSet); + + ret = select(data->sock + 1, NULL, &writeSet, &errorSet, &tv); + + if (ret == 0 || (ret < 0 && errno == EINTR)) + return 0; + + if (ret < 0) { + DEBUG(__FILE__ ": problem select'ing: %s\n", strerror(errno)); + goto close_err; + } + + getsockopt(data->sock, SOL_SOCKET, SO_ERROR, &error, &error_len); + if (error) + goto close_err; + + /* deal with ICY metadata later, for now its fucking up stuff! */ + length = snprintf(request, sizeof(request), + "GET %s HTTP/1.1\r\n" "Host: %s\r\n" + /*"Connection: close\r\n" */ + "User-Agent: %s/%s\r\n" + "Range: bytes=%ld-\r\n" + "%s" /* authorization */ + "Icy-Metadata:1\r\n" + "\r\n", + data->path, data->host, + PACKAGE_NAME, PACKAGE_VERSION, + inStream->offset, + data->proxyAuth ? data->proxyAuth : + (data->httpAuth ? data->httpAuth : "")); + + if (length >= sizeof(request)) + goto close_err; + ret = write(data->sock, request, length); + if (ret != length) + goto close_err; + + data->connState = HTTP_CONN_STATE_HELLO; + return 0; + +close_err: + close(data->sock); + data->connState = HTTP_CONN_STATE_CLOSED; + return -1; +} + +static int getHTTPHello(InputStream * inStream) +{ + InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data; + fd_set readSet; + struct timeval tv; + int ret; + char *needle; + char *cur = data->buffer; + int rc; + long readed; + + FD_ZERO(&readSet); + FD_SET(data->sock, &readSet); + + tv.tv_sec = 0; + tv.tv_usec = 0; + + ret = select(data->sock + 1, &readSet, NULL, NULL, &tv); + + if (ret == 0 || (ret < 0 && errno == EINTR)) + return 0; + + if (ret < 0) { + data->connState = HTTP_CONN_STATE_CLOSED; + close(data->sock); + data->buflen = 0; + return -1; + } + + if (data->buflen >= bufferSize - 1) { + data->connState = HTTP_CONN_STATE_CLOSED; + close(data->sock); + return -1; + } + + readed = recv(data->sock, data->buffer + data->buflen, + bufferSize - 1 - data->buflen, 0); + + if (readed < 0 && (errno == EAGAIN || errno == EINTR)) + return 0; + + if (readed <= 0) { + data->connState = HTTP_CONN_STATE_CLOSED; + close(data->sock); + data->buflen = 0; + return -1; + } + + data->buffer[data->buflen + readed] = '\0'; + data->buflen += readed; + + needle = strstr(data->buffer, "\r\n\r\n"); + + if (!needle) + return 0; + + if (0 == strncmp(cur, "HTTP/1.0 ", 9)) { + inStream->seekable = 0; + rc = atoi(cur + 9); + } else if (0 == strncmp(cur, "HTTP/1.1 ", 9)) { + inStream->seekable = 1; + rc = atoi(cur + 9); + } else if (0 == strncmp(cur, "ICY 200 OK", 10)) { + inStream->seekable = 0; + rc = 200; + } else if (0 == strncmp(cur, "ICY 400 Server Full", 19)) + rc = 400; + else if (0 == strncmp(cur, "ICY 404", 7)) + rc = 404; + else { + close(data->sock); + data->connState = HTTP_CONN_STATE_CLOSED; + return -1; + } + + switch (rc) { + case 200: + case 206: + break; + case 301: + case 302: + cur = strstr(cur, "Location: "); + if (cur) { + char *url; + int curlen = 0; + cur += strlen("Location: "); + while (*(cur + curlen) != '\0' + && *(cur + curlen) != '\r') { + curlen++; + } + url = xmalloc(curlen + 1); + memcpy(url, cur, curlen); + url[curlen] = '\0'; + ret = parseUrl(data, url); + free(url); + if (ret == 0 && data->timesRedirected < + HTTP_REDIRECT_MAX) { + data->timesRedirected++; + close(data->sock); + data->connState = HTTP_CONN_STATE_REOPEN; + data->buflen = 0; + return 0; + } + } + case 400: + case 401: + case 403: + case 404: + default: + close(data->sock); + data->connState = HTTP_CONN_STATE_CLOSED; + data->buflen = 0; + return -1; + } + + cur = strstr(data->buffer, "\r\n"); + while (cur && cur != needle) { + if (0 == strncmp(cur, "\r\nContent-Length: ", 18)) { + if (!inStream->size) + inStream->size = atol(cur + 18); + } else if (0 == strncmp(cur, "\r\nicy-metaint:", 14)) { + data->icyMetaint = atoi(cur + 14); + } else if (0 == strncmp(cur, "\r\nicy-name:", 11) || + 0 == strncmp(cur, "\r\nice-name:", 11)) { + int incr = 11; + char *temp = strstr(cur + incr, "\r\n"); + if (!temp) + break; + *temp = '\0'; + if (inStream->metaName) + free(inStream->metaName); + while (*(incr + cur) == ' ') + incr++; + inStream->metaName = xstrdup(cur + incr); + *temp = '\r'; + DEBUG("inputStream_http: metaName: %s\n", + inStream->metaName); + } else if (0 == strncmp(cur, "\r\nx-audiocast-name:", 19)) { + int incr = 19; + char *temp = strstr(cur + incr, "\r\n"); + if (!temp) + break; + *temp = '\0'; + if (inStream->metaName) + free(inStream->metaName); + while (*(incr + cur) == ' ') + incr++; + inStream->metaName = xstrdup(cur + incr); + *temp = '\r'; + DEBUG("inputStream_http: metaName: %s\n", + inStream->metaName); + } else if (0 == strncmp(cur, "\r\nContent-Type:", 15)) { + int incr = 15; + char *temp = strstr(cur + incr, "\r\n"); + if (!temp) + break; + *temp = '\0'; + if (inStream->mime) + free(inStream->mime); + while (*(incr + cur) == ' ') + incr++; + inStream->mime = xstrdup(cur + incr); + *temp = '\r'; + } + + cur = strstr(cur + 2, "\r\n"); + } + + if (inStream->size <= 0) + inStream->seekable = 0; + + needle += 4; /* 4 == strlen("\r\n\r\n") */ + data->buflen -= (needle - data->buffer); + /*fwrite(data->buffer, 1, data->buflen, stdout); */ + memmove(data->buffer, needle, data->buflen); + + data->connState = HTTP_CONN_STATE_OPEN; + + data->prebuffer = 1; + + return 0; +} + +int inputStream_httpOpen(InputStream * inStream, char *url) +{ + InputStreamHTTPData *data = newInputStreamHTTPData(); + + inStream->data = data; + + if (parseUrl(data, url) < 0) { + freeInputStreamHTTPData(data); + return -1; + } + + if (initHTTPConnection(inStream) < 0) { + freeInputStreamHTTPData(data); + return -1; + } + + inStream->seekFunc = inputStream_httpSeek; + inStream->closeFunc = inputStream_httpClose; + inStream->readFunc = inputStream_httpRead; + inStream->atEOFFunc = inputStream_httpAtEOF; + inStream->bufferFunc = inputStream_httpBuffer; + + return 0; +} + +int inputStream_httpSeek(InputStream * inStream, long offset, int whence) +{ + InputStreamHTTPData *data; + + if (!inStream->seekable) + return -1; + + switch (whence) { + case SEEK_SET: + inStream->offset = offset; + break; + case SEEK_CUR: + inStream->offset += offset; + break; + case SEEK_END: + inStream->offset = inStream->size + offset; + break; + default: + return -1; + } + + data = (InputStreamHTTPData *)inStream->data; + close(data->sock); + data->connState = HTTP_CONN_STATE_REOPEN; + data->buflen = 0; + + inputStream_httpBuffer(inStream); + + return 0; +} + +static void parseIcyMetadata(InputStream * inStream, char *metadata, int size) +{ + char *r; + char *s; + char *temp = xmalloc(size + 1); + memcpy(temp, metadata, size); + temp[size] = '\0'; + s = strtok_r(temp, ";", &r); + while (s) { + if (0 == strncmp(s, "StreamTitle=", 12)) { + int cur = 12; + if (inStream->metaTitle) + free(inStream->metaTitle); + if (*(s + cur) == '\'') + cur++; + if (s[strlen(s) - 1] == '\'') { + s[strlen(s) - 1] = '\0'; + } + inStream->metaTitle = xstrdup(s + cur); + DEBUG("inputStream_http: metaTitle: %s\n", + inStream->metaTitle); + } + s = strtok_r(NULL, ";", &r); + } + free(temp); +} + +size_t inputStream_httpRead(InputStream * inStream, void *ptr, size_t size, + size_t nmemb) +{ + InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data; + long tosend = 0; + long inlen = size * nmemb; + long maxToSend = data->buflen; + + inputStream_httpBuffer(inStream); + + switch (data->connState) { + case HTTP_CONN_STATE_OPEN: + if (data->prebuffer || data->buflen < data->icyMetaint) + return 0; + + break; + case HTTP_CONN_STATE_CLOSED: + if (data->buflen) + break; + default: + return 0; + } + + if (data->icyMetaint > 0) { + if (data->icyOffset >= data->icyMetaint) { + int metalen = *(data->buffer); + metalen <<= 4; + if (metalen < 0) + metalen = 0; + if (metalen + 1 > data->buflen) { + /* damn that's some fucking big metadata! */ + if (bufferSize < metalen + 1) { + data->connState = + HTTP_CONN_STATE_CLOSED; + close(data->sock); + data->buflen = 0; + } + return 0; + } + if (metalen > 0) { + parseIcyMetadata(inStream, data->buffer + 1, + metalen); + } + data->buflen -= metalen + 1; + memmove(data->buffer, data->buffer + metalen + 1, + data->buflen); + data->icyOffset = 0; + } + maxToSend = data->icyMetaint - data->icyOffset; + maxToSend = maxToSend > data->buflen ? data->buflen : maxToSend; + } + + if (data->buflen > 0) { + tosend = inlen > maxToSend ? maxToSend : inlen; + tosend = (tosend / size) * size; + + memcpy(ptr, data->buffer, tosend); + /*fwrite(ptr,1,readed,stdout); */ + data->buflen -= tosend; + data->icyOffset += tosend; + /*fwrite(data->buffer,1,readed,stdout); */ + memmove(data->buffer, data->buffer + tosend, data->buflen); + + inStream->offset += tosend; + } + + return tosend / size; +} + +int inputStream_httpClose(InputStream * inStream) +{ + InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data; + + switch (data->connState) { + case HTTP_CONN_STATE_CLOSED: + break; + default: + close(data->sock); + } + + freeInputStreamHTTPData(data); + + return 0; +} + +int inputStream_httpAtEOF(InputStream * inStream) +{ + InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data; + switch (data->connState) { + case HTTP_CONN_STATE_CLOSED: + if (data->buflen == 0) + return 1; + default: + return 0; + } +} + +int inputStream_httpBuffer(InputStream * inStream) +{ + InputStreamHTTPData *data = (InputStreamHTTPData *) inStream->data; + ssize_t readed = 0; + + if (data->connState == HTTP_CONN_STATE_REOPEN) { + if (initHTTPConnection(inStream) < 0) + return -1; + } + + if (data->connState == HTTP_CONN_STATE_INIT) { + if (finishHTTPInit(inStream) < 0) + return -1; + } + + if (data->connState == HTTP_CONN_STATE_HELLO) { + if (getHTTPHello(inStream) < 0) + return -1; + } + + switch (data->connState) { + case HTTP_CONN_STATE_OPEN: + case HTTP_CONN_STATE_CLOSED: + break; + default: + return -1; + } + + if (data->buflen == 0 || data->buflen < data->icyMetaint) { + data->prebuffer = 1; + } else if (data->buflen > prebufferSize) + data->prebuffer = 0; + + if (data->connState == HTTP_CONN_STATE_OPEN && + data->buflen < bufferSize - 1) { + readed = read(data->sock, data->buffer + data->buflen, + (size_t) (bufferSize - 1 - data->buflen)); + + if (readed < 0 && (errno == EAGAIN || errno == EINTR)) { + readed = 0; + } else if (readed <= 0) { + close(data->sock); + data->connState = HTTP_CONN_STATE_CLOSED; + readed = 0; + } + /*fwrite(data->buffer+data->buflen,1,readed,stdout); */ + data->buflen += readed; + } + + if (data->buflen > prebufferSize) + data->prebuffer = 0; + + return (readed ? 1 : 0); +} diff --git a/trunk/src/inputStream_http.h b/trunk/src/inputStream_http.h new file mode 100644 index 000000000..7ab23a5de --- /dev/null +++ b/trunk/src/inputStream_http.h @@ -0,0 +1,39 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef INPUT_STREAM_HTTP_H +#define INPUT_STREAM_HTTP_H + +#include "inputStream.h" + +void inputStream_initHttp(void); + +int inputStream_httpOpen(InputStream * inStream, char *filename); + +int inputStream_httpSeek(InputStream * inStream, long offset, int whence); + +size_t inputStream_httpRead(InputStream * inStream, void *ptr, size_t size, + size_t nmemb); + +int inputStream_httpClose(InputStream * inStream); + +int inputStream_httpAtEOF(InputStream * inStream); + +int inputStream_httpBuffer(InputStream * inStream); + +#endif diff --git a/trunk/src/interface.c b/trunk/src/interface.c new file mode 100644 index 000000000..22660432f --- /dev/null +++ b/trunk/src/interface.c @@ -0,0 +1,851 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "interface.h" +#include "command.h" +#include "conf.h" +#include "list.h" +#include "log.h" +#include "listen.h" +#include "playlist.h" +#include "permission.h" +#include "sllist.h" +#include "utils.h" +#include "ioops.h" + +#include <stdio.h> +#include <stdlib.h> +#include <assert.h> +#include <sys/select.h> +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> +#include <sys/param.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> +#include <signal.h> + +#define GREETING "OK MPD " PROTOCOL_VERSION "\n" + +#define INTERFACE_MAX_BUFFER_LENGTH (40960) +#define INTERFACE_LIST_MODE_BEGIN "command_list_begin" +#define INTERFACE_LIST_OK_MODE_BEGIN "command_list_ok_begin" +#define INTERFACE_LIST_MODE_END "command_list_end" +#define INTERFACE_DEFAULT_OUT_BUFFER_SIZE (4096) +#define INTERFACE_TIMEOUT_DEFAULT (60) +#define INTERFACE_MAX_CONNECTIONS_DEFAULT (10) +#define INTERFACE_MAX_COMMAND_LIST_DEFAULT (2048*1024) +#define INTERFACE_MAX_OUTPUT_BUFFER_SIZE_DEFAULT (8192*1024) + +/* set this to zero to indicate we have no possible interfaces */ +static int interface_max_connections; /*INTERFACE_MAX_CONNECTIONS_DEFAULT; */ +static int interface_timeout = INTERFACE_TIMEOUT_DEFAULT; +static size_t interface_max_command_list_size = + INTERFACE_MAX_COMMAND_LIST_DEFAULT; +static size_t interface_max_output_buffer_size = + INTERFACE_MAX_OUTPUT_BUFFER_SIZE_DEFAULT; + +/* List of registered external IO handlers */ +static struct ioOps *ioList; + +/* maybe make conf option for this, or... 32 might be good enough */ +static long int interface_list_cache_size = 32; + +/* shared globally between all interfaces: */ +static struct strnode *list_cache; +static struct strnode *list_cache_head; +static struct strnode *list_cache_tail; + +typedef struct _Interface { + char buffer[INTERFACE_MAX_BUFFER_LENGTH]; + int bufferLength; + int bufferPos; + int fd; /* file descriptor */ + int permission; + time_t lastTime; + struct strnode *cmd_list; /* for when in list mode */ + struct strnode *cmd_list_tail; /* for when in list mode */ + int cmd_list_OK; /* print OK after each command execution */ + int cmd_list_size; /* mem cmd_list consumes */ + int cmd_list_dup; /* has the cmd_list been copied to private space? */ + struct sllnode *deferred_send; /* for output if client is slow */ + int deferred_bytes; /* mem deferred_send consumes */ + int expired; /* set whether this interface should be closed on next + check of old interfaces */ + int num; /* interface number */ + + char *send_buf; + int send_buf_used; /* bytes used this instance */ + int send_buf_size; /* bytes usable this instance */ + int send_buf_alloc; /* bytes actually allocated */ +} Interface; + +static Interface *interfaces; + +static void flushInterfaceBuffer(Interface * interface); + +static void printInterfaceOutBuffer(Interface * interface); + +#ifdef SO_SNDBUF +static int get_default_snd_buf_size(Interface * interface) +{ + int new_size; + socklen_t sockOptLen = sizeof(int); + + if (getsockopt(interface->fd, SOL_SOCKET, SO_SNDBUF, + (char *)&new_size, &sockOptLen) < 0) { + DEBUG("problem getting sockets send buffer size\n"); + return INTERFACE_DEFAULT_OUT_BUFFER_SIZE; + } + if (new_size > 0) + return new_size; + DEBUG("sockets send buffer size is not positive\n"); + return INTERFACE_DEFAULT_OUT_BUFFER_SIZE; +} +#else /* !SO_SNDBUF */ +static int get_default_snd_buf_size(Interface * interface) +{ + return INTERFACE_DEFAULT_OUT_BUFFER_SIZE; +} +#endif /* !SO_SNDBUF */ + +static void set_send_buf_size(Interface * interface) +{ + int new_size = get_default_snd_buf_size(interface); + if (interface->send_buf_size != new_size) { + interface->send_buf_size = new_size; + /* don't resize to get smaller, only bigger */ + if (interface->send_buf_alloc < new_size) { + if (interface->send_buf) + free(interface->send_buf); + interface->send_buf = xmalloc(new_size); + interface->send_buf_alloc = new_size; + } + } +} + +static void openInterface(Interface * interface, int fd) +{ + int flags; + + assert(interface->fd < 0); + + interface->cmd_list_size = 0; + interface->cmd_list_dup = 0; + interface->cmd_list_OK = -1; + interface->bufferLength = 0; + interface->bufferPos = 0; + interface->fd = fd; + while ((flags = fcntl(fd, F_GETFL)) < 0 && errno == EINTR) ; + while (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0 && errno == EINTR) ; + interface->lastTime = time(NULL); + interface->cmd_list = NULL; + interface->cmd_list_tail = NULL; + interface->deferred_send = NULL; + interface->expired = 0; + interface->deferred_bytes = 0; + interface->send_buf_used = 0; + + interface->permission = getDefaultPermissions(); + set_send_buf_size(interface); + + xwrite(fd, GREETING, strlen(GREETING)); +} + +static void free_cmd_list(struct strnode *list) +{ + struct strnode *tmp = list; + + while (tmp) { + struct strnode *next = tmp->next; + if (tmp >= list_cache_head && tmp <= list_cache_tail) { + /* inside list_cache[] array */ + tmp->data = NULL; + tmp->next = NULL; + } else + free(tmp); + tmp = next; + } +} + +static void cmd_list_clone(Interface * interface) +{ + struct strnode *new = dup_strlist(interface->cmd_list); + free_cmd_list(interface->cmd_list); + interface->cmd_list = new; + interface->cmd_list_dup = 1; + + /* new tail */ + while (new && new->next) + new = new->next; + interface->cmd_list_tail = new; +} + +static void new_cmd_list_ptr(Interface * interface, char *s, const int size) +{ + int i; + struct strnode *new; + + if (!interface->cmd_list_dup) { + for (i = interface_list_cache_size - 1; i >= 0; --i) { + if (list_cache[i].data) + continue; + new = &(list_cache[i]); + new->data = s; + /* implied in free_cmd_list() and init: */ + /* last->next->next = NULL; */ + goto out; + } + } + + /* allocate from the heap */ + new = interface->cmd_list_dup ? new_strnode_dup(s, size) + : new_strnode(s); +out: + if (interface->cmd_list) { + interface->cmd_list_tail->next = new; + interface->cmd_list_tail = new; + } else + interface->cmd_list = interface->cmd_list_tail = new; +} + +static void closeInterface(Interface * interface) +{ + struct sllnode *buf; + if (interface->fd < 0) + return; + xclose(interface->fd); + interface->fd = -1; + + if (interface->cmd_list) { + free_cmd_list(interface->cmd_list); + interface->cmd_list = NULL; + } + + if ((buf = interface->deferred_send)) { + do { + struct sllnode *prev = buf; + buf = buf->next; + free(prev); + } while (buf); + interface->deferred_send = NULL; + } + + SECURE("interface %i: closed\n", interface->num); +} + +void openAInterface(int fd, struct sockaddr *addr) +{ + int i; + + for (i = 0; i < interface_max_connections + && interfaces[i].fd >= 0; i++) /* nothing */ ; + + if (i == interface_max_connections) { + ERROR("Max Connections Reached!\n"); + xclose(fd); + } else { + SECURE("interface %i: opened from ", i); + switch (addr->sa_family) { + case AF_INET: + { + char *host = inet_ntoa(((struct sockaddr_in *) + addr)->sin_addr); + if (host) { + SECURE("%s\n", host); + } else { + SECURE("error getting ipv4 address\n"); + } + } + break; +#ifdef HAVE_IPV6 + case AF_INET6: + { + char host[INET6_ADDRSTRLEN + 1]; + memset(host, 0, INET6_ADDRSTRLEN + 1); + if (inet_ntop(AF_INET6, (void *) + &(((struct sockaddr_in6 *)addr)-> + sin6_addr), host, + INET6_ADDRSTRLEN)) { + SECURE("%s\n", host); + } else { + SECURE("error getting ipv6 address\n"); + } + } + break; +#endif + case AF_UNIX: + SECURE("local connection\n"); + break; + default: + SECURE("unknown\n"); + } + openInterface(&(interfaces[i]), fd); + } +} + +static int processLineOfInput(Interface * interface) +{ + int ret = 1; + char *line = interface->buffer + interface->bufferPos; + + if (interface->cmd_list_OK >= 0) { + if (strcmp(line, INTERFACE_LIST_MODE_END) == 0) { + DEBUG("interface %i: process command " + "list\n", interface->num); + ret = processListOfCommands(interface->fd, + &(interface->permission), + &(interface->expired), + interface->cmd_list_OK, + interface->cmd_list); + DEBUG("interface %i: process command " + "list returned %i\n", interface->num, ret); + if (ret == 0) + commandSuccess(interface->fd); + else if (ret == COMMAND_RETURN_CLOSE + || interface->expired) + closeInterface(interface); + + printInterfaceOutBuffer(interface); + free_cmd_list(interface->cmd_list); + interface->cmd_list = NULL; + interface->cmd_list_OK = -1; + } else { + size_t len = strlen(line) + 1; + interface->cmd_list_size += len; + if (interface->cmd_list_size > + interface_max_command_list_size) { + ERROR("interface %i: command " + "list size (%i) is " + "larger than the max " + "(%li)\n", + interface->num, + interface->cmd_list_size, + (long)interface_max_command_list_size); + closeInterface(interface); + ret = COMMAND_RETURN_CLOSE; + } else + new_cmd_list_ptr(interface, line, len); + } + } else { + if (strcmp(line, INTERFACE_LIST_MODE_BEGIN) == 0) { + interface->cmd_list_OK = 0; + ret = 1; + } else if (strcmp(line, INTERFACE_LIST_OK_MODE_BEGIN) == 0) { + interface->cmd_list_OK = 1; + ret = 1; + } else { + DEBUG("interface %i: process command \"%s\"\n", + interface->num, line); + ret = processCommand(interface->fd, + &(interface->permission), line); + DEBUG("interface %i: command returned %i\n", + interface->num, ret); + if (ret == 0) + commandSuccess(interface->fd); + else if (ret == COMMAND_RETURN_CLOSE + || interface->expired) { + closeInterface(interface); + } + printInterfaceOutBuffer(interface); + } + } + + return ret; +} + +static int processBytesRead(Interface * interface, int bytesRead) +{ + int ret = 0; + char *buf_tail = &(interface->buffer[interface->bufferLength - 1]); + + while (bytesRead > 0) { + interface->bufferLength++; + bytesRead--; + buf_tail++; + if (*buf_tail == '\n') { + *buf_tail = '\0'; + if (interface->bufferLength - interface->bufferPos > 1) { + if (*(buf_tail - 1) == '\r') + *(buf_tail - 1) = '\0'; + } + ret = processLineOfInput(interface); + interface->bufferPos = interface->bufferLength; + } + if (interface->bufferLength == INTERFACE_MAX_BUFFER_LENGTH) { + if (interface->bufferPos == 0) { + ERROR("interface %i: buffer overflow\n", + interface->num); + closeInterface(interface); + return 1; + } + if (interface->cmd_list_OK >= 0 && + interface->cmd_list && + !interface->cmd_list_dup) + cmd_list_clone(interface); + interface->bufferLength -= interface->bufferPos; + memmove(interface->buffer, + interface->buffer + interface->bufferPos, + interface->bufferLength); + interface->bufferPos = 0; + } + if (ret == COMMAND_RETURN_KILL || ret == COMMAND_RETURN_CLOSE) { + return ret; + } + + } + + return ret; +} + +static int interfaceReadInput(Interface * interface) +{ + int bytesRead; + + bytesRead = read(interface->fd, + interface->buffer + interface->bufferLength, + INTERFACE_MAX_BUFFER_LENGTH - interface->bufferLength); + + if (bytesRead > 0) + return processBytesRead(interface, bytesRead); + else if (bytesRead == 0 || (bytesRead < 0 && errno != EINTR)) { + closeInterface(interface); + } else + return 0; + + return 1; +} + +static void addInterfacesReadyToReadAndListenSocketToFdSet(fd_set * fds, + int *fdmax) +{ + int i; + + FD_ZERO(fds); + addListenSocketsToFdSet(fds, fdmax); + + for (i = 0; i < interface_max_connections; i++) { + if (interfaces[i].fd >= 0 && !interfaces[i].expired + && !interfaces[i].deferred_send) { + FD_SET(interfaces[i].fd, fds); + if (*fdmax < interfaces[i].fd) + *fdmax = interfaces[i].fd; + } + } +} + +static void addInterfacesForBufferFlushToFdSet(fd_set * fds, int *fdmax) +{ + int i; + + FD_ZERO(fds); + + for (i = 0; i < interface_max_connections; i++) { + if (interfaces[i].fd >= 0 && !interfaces[i].expired + && interfaces[i].deferred_send) { + FD_SET(interfaces[i].fd, fds); + if (*fdmax < interfaces[i].fd) + *fdmax = interfaces[i].fd; + } + } +} + +static void closeNextErroredInterface(void) +{ + fd_set fds; + struct timeval tv; + int i; + + tv.tv_sec = 0; + tv.tv_usec = 0; + + for (i = 0; i < interface_max_connections; i++) { + if (interfaces[i].fd >= 0) { + FD_ZERO(&fds); + FD_SET(interfaces[i].fd, &fds); + if (select(FD_SETSIZE, &fds, NULL, NULL, &tv) < 0) { + closeInterface(&interfaces[i]); + return; + } + } + } +} + +int doIOForInterfaces(void) +{ + fd_set rfds; + fd_set wfds; + fd_set efds; + struct timeval tv; + int i; + int selret; + int fdmax; + + tv.tv_sec = 1; + tv.tv_usec = 0; + + while (1) { + fdmax = 0; + + FD_ZERO( &rfds ); + FD_ZERO( &wfds ); + FD_ZERO( &efds ); + addInterfacesReadyToReadAndListenSocketToFdSet(&rfds, &fdmax); + addInterfacesForBufferFlushToFdSet(&wfds, &fdmax); + + /* Add fds for all registered IO handlers */ + if( ioList ) { + struct ioOps *o = ioList; + while( o ) { + struct ioOps *current = o; + int fdnum; + assert( current->fdset ); + fdnum = current->fdset( &rfds, &wfds, &efds ); + if( fdmax < fdnum ) + fdmax = fdnum; + o = o->next; + } + } + + selret = select(fdmax + 1, &rfds, &wfds, &efds, &tv); + + if (selret < 0 && errno == EINTR) + break; + + /* Consume fds for all registered IO handlers */ + if( ioList ) { + struct ioOps *o = ioList; + while( o ) { + struct ioOps *current = o; + assert( current->consume ); + selret = current->consume( selret, &rfds, &wfds, &efds ); + o = o->next; + } + } + + if (selret == 0) + break; + + if (selret < 0) { + closeNextErroredInterface(); + continue; + } + + getConnections(&rfds); + + for (i = 0; i < interface_max_connections; i++) { + if (interfaces[i].fd >= 0 + && FD_ISSET(interfaces[i].fd, &rfds)) { + if (COMMAND_RETURN_KILL == + interfaceReadInput(&(interfaces[i]))) { + return COMMAND_RETURN_KILL; + } + interfaces[i].lastTime = time(NULL); + } + if (interfaces[i].fd >= 0 + && FD_ISSET(interfaces[i].fd, &wfds)) { + flushInterfaceBuffer(&interfaces[i]); + interfaces[i].lastTime = time(NULL); + } + } + + tv.tv_sec = 0; + tv.tv_usec = 0; + } + + return 1; +} + +void initInterfaces(void) +{ + int i; + char *test; + ConfigParam *param; + + param = getConfigParam(CONF_CONN_TIMEOUT); + + if (param) { + interface_timeout = strtol(param->value, &test, 10); + if (*test != '\0' || interface_timeout <= 0) { + FATAL("connection timeout \"%s\" is not a positive " + "integer, line %i\n", CONF_CONN_TIMEOUT, + param->line); + } + } + + param = getConfigParam(CONF_MAX_CONN); + + if (param) { + interface_max_connections = strtol(param->value, &test, 10); + if (*test != '\0' || interface_max_connections <= 0) { + FATAL("max connections \"%s\" is not a positive integer" + ", line %i\n", param->value, param->line); + } + } else + interface_max_connections = INTERFACE_MAX_CONNECTIONS_DEFAULT; + + param = getConfigParam(CONF_MAX_COMMAND_LIST_SIZE); + + if (param) { + interface_max_command_list_size = strtol(param->value, + &test, 10); + if (*test != '\0' || interface_max_command_list_size <= 0) { + FATAL("max command list size \"%s\" is not a positive " + "integer, line %i\n", param->value, param->line); + } + interface_max_command_list_size *= 1024; + } + + param = getConfigParam(CONF_MAX_OUTPUT_BUFFER_SIZE); + + if (param) { + interface_max_output_buffer_size = strtol(param->value, + &test, 10); + if (*test != '\0' || interface_max_output_buffer_size <= 0) { + FATAL("max output buffer size \"%s\" is not a positive " + "integer, line %i\n", param->value, param->line); + } + interface_max_output_buffer_size *= 1024; + } + + interfaces = xmalloc(sizeof(Interface) * interface_max_connections); + + list_cache = xcalloc(interface_list_cache_size, sizeof(struct strnode)); + list_cache_head = &(list_cache[0]); + list_cache_tail = &(list_cache[interface_list_cache_size - 1]); + + for (i = 0; i < interface_max_connections; i++) { + interfaces[i].fd = -1; + interfaces[i].send_buf = NULL; + interfaces[i].send_buf_size = 0; + interfaces[i].send_buf_alloc = 0; + interfaces[i].num = i; + } +} + +static void closeAllInterfaces(void) +{ + int i; + + for (i = 0; i < interface_max_connections; i++) { + if (interfaces[i].fd > 0) + closeInterface(&(interfaces[i])); + if (interfaces[i].send_buf) + free(interfaces[i].send_buf); + } + free(list_cache); +} + +void freeAllInterfaces(void) +{ + closeAllInterfaces(); + + free(interfaces); + + interface_max_connections = 0; +} + +void closeOldInterfaces(void) +{ + int i; + + for (i = 0; i < interface_max_connections; i++) { + if (interfaces[i].fd > 0) { + if (interfaces[i].expired) { + DEBUG("interface %i: expired\n", i); + closeInterface(&(interfaces[i])); + } else if (time(NULL) - interfaces[i].lastTime > + interface_timeout) { + DEBUG("interface %i: timeout\n", i); + closeInterface(&(interfaces[i])); + } + } + } +} + +static void flushInterfaceBuffer(Interface * interface) +{ + struct sllnode *buf; + int ret = 0; + + buf = interface->deferred_send; + while (buf) { + ret = write(interface->fd, buf->data, buf->size); + if (ret < 0) + break; + else if (ret < buf->size) { + interface->deferred_bytes -= ret; + buf->data = (char *)buf->data + ret; + buf->size -= ret; + } else { + struct sllnode *tmp = buf; + interface->deferred_bytes -= (buf->size + + sizeof(struct sllnode)); + buf = buf->next; + free(tmp); + interface->deferred_send = buf; + } + interface->lastTime = time(NULL); + } + + if (!interface->deferred_send) { + DEBUG("interface %i: buffer empty %i\n", interface->num, + interface->deferred_bytes); + assert(interface->deferred_bytes == 0); + } else if (ret < 0 && errno != EAGAIN && errno != EINTR) { + /* cause interface to close */ + DEBUG("interface %i: problems flushing buffer\n", + interface->num); + buf = interface->deferred_send; + do { + struct sllnode *prev = buf; + buf = buf->next; + free(prev); + } while (buf); + interface->deferred_send = NULL; + interface->deferred_bytes = 0; + interface->expired = 1; + } +} + +int interfacePrintWithFD(int fd, char *buffer, int buflen) +{ + static int i; + int copylen; + Interface *interface; + + assert(fd > 0); + + if (i >= interface_max_connections || + interfaces[i].fd < 0 || interfaces[i].fd != fd) { + for (i = 0; i < interface_max_connections; i++) { + if (interfaces[i].fd == fd) + break; + } + if (i == interface_max_connections) + return -1; + } + + /* if fd isn't found or interfaces is going to be closed, do nothing */ + if (interfaces[i].expired) + return 0; + + interface = interfaces + i; + + while (buflen > 0 && !interface->expired) { + int left = interface->send_buf_size - interface->send_buf_used; + copylen = buflen > left ? left : buflen; + memcpy(interface->send_buf + interface->send_buf_used, buffer, + copylen); + buflen -= copylen; + interface->send_buf_used += copylen; + buffer += copylen; + if (interface->send_buf_used >= interface->send_buf_size) + printInterfaceOutBuffer(interface); + } + + return 0; +} + +static void printInterfaceOutBuffer(Interface * interface) +{ + int ret; + struct sllnode *buf; + + if (interface->fd < 0 || interface->expired || + !interface->send_buf_used) + return; + + if ((buf = interface->deferred_send)) { + interface->deferred_bytes += sizeof(struct sllnode) + + interface->send_buf_used; + if (interface->deferred_bytes > + interface_max_output_buffer_size) { + ERROR("interface %i: output buffer size (%li) is " + "larger than the max (%li)\n", + interface->num, + (long)interface->deferred_bytes, + (long)interface_max_output_buffer_size); + /* cause interface to close */ + interface->expired = 1; + do { + struct sllnode *prev = buf; + buf = buf->next; + free(prev); + } while (buf); + interface->deferred_send = NULL; + interface->deferred_bytes = 0; + } else { + while (buf->next) + buf = buf->next; + buf->next = new_sllnode(interface->send_buf, + interface->send_buf_used); + } + } else { + if ((ret = write(interface->fd, interface->send_buf, + interface->send_buf_used)) < 0) { + if (errno == EAGAIN || errno == EINTR) { + interface->deferred_send = + new_sllnode(interface->send_buf, + interface->send_buf_used); + } else { + DEBUG("interface %i: problems writing\n", + interface->num); + interface->expired = 1; + return; + } + } else if (ret < interface->send_buf_used) { + interface->deferred_send = + new_sllnode(interface->send_buf + ret, + interface->send_buf_used - ret); + } + if (interface->deferred_send) { + DEBUG("interface %i: buffer created\n", interface->num); + interface->deferred_bytes = + interface->deferred_send->size + + sizeof(struct sllnode); + } + } + + interface->send_buf_used = 0; +} + +/* From ioops.h: */ +void registerIO( struct ioOps *ops ) +{ + assert( ops != NULL ); + + ops->next = ioList; + ioList = ops; + ops->prev = NULL; + if( ops->next ) + ops->next->prev = ops; +} + +void deregisterIO( struct ioOps *ops ) +{ + assert( ops != NULL ); + + if( ioList == ops ) + ioList = ops->next; + else if( ops->prev != NULL ) + ops->prev->next = ops->next; +} diff --git a/trunk/src/interface.h b/trunk/src/interface.h new file mode 100644 index 000000000..6f3c2c070 --- /dev/null +++ b/trunk/src/interface.h @@ -0,0 +1,37 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef INTERFACE_H +#define INTERFACE_H + +#include "../config.h" + +#include <stdio.h> +#include <time.h> +#include <sys/types.h> +#include <sys/socket.h> + +void initInterfaces(void); +void openAInterface(int fd, struct sockaddr *addr); +void freeAllInterfaces(void); +void closeOldInterfaces(void); +int interfacePrintWithFD(int fd, char *buffer, int len); + +int doIOForInterfaces(void); + +#endif diff --git a/trunk/src/ioops.h b/trunk/src/ioops.h new file mode 100644 index 000000000..e797a7153 --- /dev/null +++ b/trunk/src/ioops.h @@ -0,0 +1,51 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef IOOPS_H +#define IOOPS_H + +#include <sys/select.h> + +struct ioOps { + struct ioOps *prev, *next; + + /* + * Called before each 'select' statement. + * To register for IO, call FD_SET for each required queue + * Return the highest fd number you registered + */ + int (*fdset) ( fd_set *rfds, fd_set *wfds, fd_set *efds ); + + /* + * Called after each 'select' statement. + * fdCount is the number of fds total in all sets. It may be 0. + * For each fd you registered for in (fdset), you should FD_CLR it from the + * appropriate queue(s). + * Return the total number of fds left in all sets (Ie, return fdCount + * minus the number of times you called FD_CLR). + */ + int (*consume) ( int fdCount, fd_set *rfds, fd_set *wfds, fd_set *efds ); +}; + +/* Call this to register your io operation handler struct */ +void registerIO( struct ioOps *ops ); + +/* Call this to deregister your io operation handler struct */ +void deregisterIO( struct ioOps *ops ); + +#endif diff --git a/trunk/src/list.c b/trunk/src/list.c new file mode 100644 index 000000000..71c30f7b6 --- /dev/null +++ b/trunk/src/list.c @@ -0,0 +1,519 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "list.h" +#include "utils.h" + +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <time.h> +#include <stdio.h> + +static void makeListNodesArray(List * list) +{ + ListNode *node = list->firstNode; + long i; + + if (!list->numberOfNodes) + return; + + list->nodesArray = xrealloc(list->nodesArray, + sizeof(ListNode *) * list->numberOfNodes); + + for (i = 0; i < list->numberOfNodes; i++) { + list->nodesArray[i] = node; + node = node->nextNode; + } +} + +static void freeListNodesArray(List * list) +{ + if (!list->nodesArray) + return; + free(list->nodesArray); + list->nodesArray = NULL; +} + +List *makeList(ListFreeDataFunc * freeDataFunc, int strdupKeys) +{ + List *list = xmalloc(sizeof(List)); + + assert(list != NULL); + + list->sorted = 0; + list->firstNode = NULL; + list->lastNode = NULL; + list->freeDataFunc = freeDataFunc; + list->numberOfNodes = 0; + list->nodesArray = NULL; + list->strdupKeys = strdupKeys; + + return list; +} + +ListNode *insertInListBeforeNode(List * list, ListNode * beforeNode, int pos, + char *key, void *data) +{ + ListNode *node; + + assert(list != NULL); + assert(key != NULL); + /*assert(data!=NULL); */ + + node = xmalloc(sizeof(ListNode)); + assert(node != NULL); + + node->nextNode = beforeNode; + if (beforeNode == list->firstNode) { + if (list->firstNode == NULL) { + assert(list->lastNode == NULL); + list->lastNode = node; + } else { + assert(list->lastNode != NULL); + assert(list->lastNode->nextNode == NULL); + list->firstNode->prevNode = node; + } + node->prevNode = NULL; + list->firstNode = node; + } else { + if (beforeNode) { + node->prevNode = beforeNode->prevNode; + beforeNode->prevNode = node; + } else { + node->prevNode = list->lastNode; + list->lastNode = node; + } + node->prevNode->nextNode = node; + } + + if (list->strdupKeys) + node->key = xstrdup(key); + else + node->key = key; + + node->data = data; + + list->numberOfNodes++; + + if (list->sorted) { + list->nodesArray = xrealloc(list->nodesArray, + list->numberOfNodes * + sizeof(ListNode *)); + if (node == list->lastNode) { + list->nodesArray[list->numberOfNodes - 1] = node; + } else if (pos < 0) + makeListNodesArray(list); + else { + memmove(list->nodesArray + pos + 1, + list->nodesArray + pos, + sizeof(ListNode *) * (list->numberOfNodes - + pos - 1)); + list->nodesArray[pos] = node; + } + } + + return node; +} + +ListNode *insertInList(List * list, char *key, void *data) +{ + ListNode *node; + + assert(list != NULL); + assert(key != NULL); + /*assert(data!=NULL); */ + + node = xmalloc(sizeof(ListNode)); + assert(node != NULL); + + if (list->nodesArray) + freeListNodesArray(list); + + if (list->firstNode == NULL) { + assert(list->lastNode == NULL); + list->firstNode = node; + } else { + assert(list->lastNode != NULL); + assert(list->lastNode->nextNode == NULL); + list->lastNode->nextNode = node; + } + + if (list->strdupKeys) + node->key = xstrdup(key); + else + node->key = key; + + node->data = data; + node->nextNode = NULL; + node->prevNode = list->lastNode; + + list->lastNode = node; + + list->numberOfNodes++; + + return node; +} + +int insertInListWithoutKey(List * list, void *data) +{ + ListNode *node; + + assert(list != NULL); + assert(data != NULL); + + node = xmalloc(sizeof(ListNode)); + assert(node != NULL); + + if (list->nodesArray) + freeListNodesArray(list); + + if (list->firstNode == NULL) { + assert(list->lastNode == NULL); + list->firstNode = node; + } else { + assert(list->lastNode != NULL); + assert(list->lastNode->nextNode == NULL); + list->lastNode->nextNode = node; + } + + node->key = NULL; + node->data = data; + node->nextNode = NULL; + node->prevNode = list->lastNode; + + list->lastNode = node; + + list->numberOfNodes++; + + return 1; +} + +/* if _key_ is not found, *_node_ is assigned to the node before which + the info would be found */ +int findNodeInList(List * list, char *key, ListNode ** node, int *pos) +{ + long high; + long low; + long cur; + ListNode *tmpNode; + int cmp; + + assert(list != NULL); + + if (list->sorted && list->nodesArray) { + high = list->numberOfNodes - 1; + low = 0; + cur = high; + + while (high > low) { + cur = (high + low) / 2; + tmpNode = list->nodesArray[cur]; + cmp = strcmp(tmpNode->key, key); + if (cmp == 0) { + *node = tmpNode; + *pos = cur; + return 1; + } else if (cmp > 0) + high = cur; + else { + if (low == cur) + break; + low = cur; + } + } + + cur = high; + if (cur >= 0) { + tmpNode = list->nodesArray[cur]; + *node = tmpNode; + *pos = high; + cmp = tmpNode ? strcmp(tmpNode->key, key) : -1; + if (0 == cmp) + return 1; + else if (cmp > 0) + return 0; + else { + *pos = -1; + *node = NULL; + return 0; + } + } else { + *pos = 0; + *node = list->firstNode; + return 0; + } + } else { + tmpNode = list->firstNode; + + while (tmpNode != NULL && strcmp(tmpNode->key, key) != 0) { + tmpNode = tmpNode->nextNode; + } + + *node = tmpNode; + if (tmpNode) + return 1; + } + + return 0; +} + +int findInList(List * list, char *key, void **data) +{ + ListNode *node; + int pos; + + if (findNodeInList(list, key, &node, &pos)) { + if (data) + *data = node->data; + return 1; + } + + return 0; +} + +int deleteFromList(List * list, char *key) +{ + ListNode *tmpNode; + + assert(list != NULL); + + tmpNode = list->firstNode; + + while (tmpNode != NULL && strcmp(tmpNode->key, key) != 0) { + tmpNode = tmpNode->nextNode; + } + + if (tmpNode != NULL) + deleteNodeFromList(list, tmpNode); + else + return 0; + + return 1; +} + +void deleteNodeFromList(List * list, ListNode * node) +{ + assert(list != NULL); + assert(node != NULL); + + if (node->prevNode == NULL) { + list->firstNode = node->nextNode; + } else { + node->prevNode->nextNode = node->nextNode; + } + if (node->nextNode == NULL) { + list->lastNode = node->prevNode; + } else { + node->nextNode->prevNode = node->prevNode; + } + if (list->freeDataFunc) { + list->freeDataFunc(node->data); + } + + if (list->strdupKeys) + free(node->key); + free(node); + list->numberOfNodes--; + + if (list->nodesArray) { + freeListNodesArray(list); + if (list->sorted) + makeListNodesArray(list); + } + +} + +void freeList(void *list) +{ + ListNode *tmpNode; + ListNode *tmpNode2; + + assert(list != NULL); + + tmpNode = ((List *) list)->firstNode; + + if (((List *) list)->nodesArray) + free(((List *) list)->nodesArray); + + while (tmpNode != NULL) { + tmpNode2 = tmpNode->nextNode; + if (((List *) list)->strdupKeys) + free(tmpNode->key); + if (((List *) list)->freeDataFunc) { + ((List *) list)->freeDataFunc(tmpNode->data); + } + free(tmpNode); + tmpNode = tmpNode2; + } + + free(list); +} + +static void swapNodes(ListNode * nodeA, ListNode * nodeB) +{ + char *key; + void *data; + + assert(nodeA != NULL); + assert(nodeB != NULL); + + key = nodeB->key; + data = nodeB->data; + + nodeB->key = nodeA->key; + nodeB->data = nodeA->data; + + nodeA->key = key; + nodeA->data = data; +} + +static void bubbleSort(ListNode ** nodesArray, long start, long end) +{ + long i; + long j; + ListNode *node; + + if (start >= end) + return; + + for (j = start; j < end; j++) { + for (i = end - 1; i >= start; i--) { + node = nodesArray[i]; + if (strcmp(node->key, node->nextNode->key) > 0) { + swapNodes(node, node->nextNode); + } + } + } +} + +static void quickSort(ListNode ** nodesArray, long start, long end) +{ + if (start >= end) + return; + else if (end - start < 5) + bubbleSort(nodesArray, start, end); + else { + long i; + ListNode *node; + long pivot; + ListNode *pivotNode; + char *pivotKey; + + List *startList = makeList(free, 0); + List *endList = makeList(free, 0); + long *startPtr = xmalloc(sizeof(long)); + long *endPtr = xmalloc(sizeof(long)); + *startPtr = start; + *endPtr = end; + insertInListWithoutKey(startList, (void *)startPtr); + insertInListWithoutKey(endList, (void *)endPtr); + + while (startList->numberOfNodes) { + start = *((long *)startList->lastNode->data); + end = *((long *)endList->lastNode->data); + + if (end - start < 5) { + bubbleSort(nodesArray, start, end); + deleteNodeFromList(startList, + startList->lastNode); + deleteNodeFromList(endList, endList->lastNode); + } else { + pivot = (start + end) / 2; + pivotNode = nodesArray[pivot]; + pivotKey = pivotNode->key; + + for (i = pivot - 1; i >= start; i--) { + node = nodesArray[i]; + if (strcmp(node->key, pivotKey) > 0) { + pivot--; + if (pivot > i) { + swapNodes(node, + nodesArray + [pivot]); + } + swapNodes(pivotNode, + nodesArray[pivot]); + pivotNode = nodesArray[pivot]; + } + } + for (i = pivot + 1; i <= end; i++) { + node = nodesArray[i]; + if (strcmp(pivotKey, node->key) > 0) { + pivot++; + if (pivot < i) { + swapNodes(node, + nodesArray + [pivot]); + } + swapNodes(pivotNode, + nodesArray[pivot]); + pivotNode = nodesArray[pivot]; + } + } + + deleteNodeFromList(startList, + startList->lastNode); + deleteNodeFromList(endList, endList->lastNode); + + if (pivot - 1 - start > 0) { + startPtr = xmalloc(sizeof(long)); + endPtr = xmalloc(sizeof(long)); + *startPtr = start; + *endPtr = pivot - 1; + insertInListWithoutKey(startList, + (void *) + startPtr); + insertInListWithoutKey(endList, + (void *)endPtr); + } + + if (end - pivot - 1 > 0) { + startPtr = xmalloc(sizeof(long)); + endPtr = xmalloc(sizeof(long)); + *startPtr = pivot + 1; + *endPtr = end; + insertInListWithoutKey(startList, + (void *) + startPtr); + insertInListWithoutKey(endList, + (void *)endPtr); + } + } + } + + freeList(startList); + freeList(endList); + } +} + +void sortList(List * list) +{ + assert(list != NULL); + + list->sorted = 1; + + if (list->numberOfNodes < 2) + return; + + if (list->nodesArray) + freeListNodesArray(list); + makeListNodesArray(list); + + quickSort(list->nodesArray, 0, list->numberOfNodes - 1); +} diff --git a/trunk/src/list.h b/trunk/src/list.h new file mode 100644 index 000000000..5938934ff --- /dev/null +++ b/trunk/src/list.h @@ -0,0 +1,110 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef LIST_H +#define LIST_H + +#include "../config.h" + +#include <stdlib.h> + +/* used to make a list where free() will be used to free data in list */ +#define DEFAULT_FREE_DATA_FUNC free + +/* typedef for function to free data stored in the list nodes */ +typedef void ListFreeDataFunc(void *); + +typedef struct _ListNode { + /* used to identify node (ie. when using findInList) */ + char *key; + /* data store in node */ + void *data; + /* next node in list */ + struct _ListNode *nextNode; + /* previous node in list */ + struct _ListNode *prevNode; +} ListNode; + +typedef struct _List { + /* first node in list */ + ListNode *firstNode; + /* last node in list */ + ListNode *lastNode; + /* function used to free data stored in nodes of the list */ + ListFreeDataFunc *freeDataFunc; + /* number of nodes */ + long numberOfNodes; + /* array for searching when list is sorted */ + ListNode **nodesArray; + /* sorted */ + int sorted; + /* whether to strdup() key's on insertion */ + int strdupKeys; +} List; + +/* allocates memory for a new list and initializes it + * _freeDataFunc_ -> pointer to function used to free data, use + * DEFAULT_FREE_DATAFUNC to use free() + * returns pointer to new list if successful, NULL otherwise + */ +List *makeList(ListFreeDataFunc * freeDataFunc, int strdupKeys); + +/* inserts a node into _list_ with _key_ and _data_ + * _list_ -> list the data will be inserted in + * _key_ -> identifier for node/data to be inserted into list + * _data_ -> data to be inserted in list + * returns 1 if successful, 0 otherwise + */ +ListNode *insertInList(List * list, char *key, void *data); + +ListNode *insertInListBeforeNode(List * list, ListNode * beforeNode, + int pos, char *key, void *data); + +int insertInListWithoutKey(List * list, void *data); + +/* deletes the first node in the list with the key _key_ + * _list_ -> list the node will be deleted from + * _key_ -> key used to identify node to delete + * returns 1 if node is found and deleted, 0 otherwise + */ +int deleteFromList(List * list, char *key); + +void deleteNodeFromList(List * list, ListNode * node); + +/* finds data in a list based on key + * _list_ -> list to search for _key_ in + * _key_ -> which node is being searched for + * _data_ -> a pointer to where data will be placed, + * _data_ memory should not by allocated or freed + * _data_ can be NULL + * returns 1 if successful, 0 otherwise + */ +int findInList(List * list, char *key, void **data); + +/* if _key_ is not found, *_node_ is assigned to the node before which + the info would be found */ +int findNodeInList(List * list, char *key, ListNode ** node, int *pos); + +/* frees memory malloc'd for list and its nodes + * _list_ -> List to be free'd + */ +void freeList(void *list); + +void sortList(List * list); + +#endif diff --git a/trunk/src/listen.c b/trunk/src/listen.c new file mode 100644 index 000000000..323bf430f --- /dev/null +++ b/trunk/src/listen.c @@ -0,0 +1,258 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "listen.h" +#include "interface.h" +#include "conf.h" +#include "log.h" +#include "utils.h" + +#include <stdio.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <netinet/in.h> +#include <netdb.h> +#include <string.h> +#include <errno.h> +#include <resolv.h> +#include <fcntl.h> + +#define MAXHOSTNAME 1024 + +#define ALLOW_REUSE 1 + +#define DEFAULT_PORT 6600 + +#define BINDERROR() do { \ + FATAL("unable to bind port %u: %s\n" \ + "maybe MPD is still running?\n", \ + port, strerror(errno)); \ +} while (0); + +static int *listenSockets; +static int numberOfListenSockets; +static int boundPort; + +static int establishListen(unsigned int port, + struct sockaddr *addrp, socklen_t addrlen) +{ + int pf = 0; + int sock; + int allowReuse = ALLOW_REUSE; + + switch (addrp->sa_family) { + case AF_INET: + pf = PF_INET; + break; +#ifdef HAVE_IPV6 + case AF_INET6: + pf = PF_INET6; + break; +#endif + case AF_UNIX: + pf = PF_UNIX; + break; + default: + FATAL("unknown address family: %i\n", addrp->sa_family); + } + + if ((sock = socket(pf, SOCK_STREAM, 0)) < 0) + FATAL("socket < 0\n"); + + if (fcntl(sock, F_SETFL, fcntl(sock, F_GETFL) | O_NONBLOCK) < 0) { + FATAL("problems setting nonblocking on listen socket: %s\n", + strerror(errno)); + } + + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&allowReuse, + sizeof(allowReuse)) < 0) { + FATAL("problems setsockopt'ing: %s\n", strerror(errno)); + } + + if (bind(sock, addrp, addrlen) < 0) { + close(sock); + return -1; + } + + if (listen(sock, 5) < 0) + FATAL("problems listen'ing: %s\n", strerror(errno)); + + numberOfListenSockets++; + listenSockets = + xrealloc(listenSockets, sizeof(int) * numberOfListenSockets); + + listenSockets[numberOfListenSockets - 1] = sock; + + return 0; +} + +static void parseListenConfigParam(unsigned int port, ConfigParam * param) +{ + struct sockaddr *addrp = NULL; + socklen_t addrlen = 0; + struct sockaddr_in sin; +#ifdef HAVE_IPV6 + struct sockaddr_in6 sin6; + int useIpv6 = ipv6Supported(); + + memset(&sin6, 0, sizeof(struct sockaddr_in6)); + sin6.sin6_port = htons(port); + sin6.sin6_family = AF_INET6; +#endif + memset(&sin, 0, sizeof(struct sockaddr_in)); + sin.sin_port = htons(port); + sin.sin_family = AF_INET; + + if (!param || 0 == strcmp(param->value, "any")) { + DEBUG("binding to any address\n"); +#ifdef HAVE_IPV6 + if (useIpv6) { + sin6.sin6_addr = in6addr_any; + addrp = (struct sockaddr *)&sin6; + addrlen = sizeof(struct sockaddr_in6); + if (establishListen(port, addrp, addrlen) < 0) + BINDERROR(); + } +#endif + sin.sin_addr.s_addr = INADDR_ANY; + addrp = (struct sockaddr *)&sin; + addrlen = sizeof(struct sockaddr_in); +#ifdef HAVE_IPV6 + if ((establishListen(port, addrp, addrlen) < 0) && !useIpv6) { +#else + if (establishListen(port, addrp, addrlen) < 0) { +#endif + BINDERROR(); + } + } else { + struct hostent *he; + DEBUG("binding to address for %s\n", param->value); + if (!(he = gethostbyname(param->value))) { + FATAL("can't lookup host \"%s\" at line %i\n", + param->value, param->line); + } + switch (he->h_addrtype) { +#ifdef HAVE_IPV6 + case AF_INET6: + if (!useIpv6) { + FATAL("no IPv6 support, but a IPv6 address " + "found for \"%s\" at line %i\n", + param->value, param->line); + } + memcpy((char *)&sin6.sin6_addr.s6_addr, + (char *)he->h_addr, he->h_length); + addrp = (struct sockaddr *)&sin6; + addrlen = sizeof(struct sockaddr_in6); + break; +#endif + case AF_INET: + memcpy((char *)&sin.sin_addr.s_addr, + (char *)he->h_addr, he->h_length); + addrp = (struct sockaddr *)&sin; + addrlen = sizeof(struct sockaddr_in); + break; + default: + FATAL("address type for \"%s\" is not IPv4 or IPv6 " + "at line %i\n", param->value, param->line); + } + + if (establishListen(port, addrp, addrlen) < 0) + BINDERROR(); + } +} + +void listenOnPort(void) +{ + int port = DEFAULT_PORT; + ConfigParam *param = getNextConfigParam(CONF_BIND_TO_ADDRESS, NULL); + ConfigParam *portParam = getConfigParam(CONF_PORT); + + if (portParam) { + char *test; + port = strtol(portParam->value, &test, 10); + if (port <= 0 || *test != '\0') { + FATAL("%s \"%s\" specified at line %i is not a " + "positive integer", CONF_PORT, + portParam->value, portParam->line); + } + } + + boundPort = port; + + do { + parseListenConfigParam(port, param); + } while ((param = getNextConfigParam(CONF_BIND_TO_ADDRESS, param))); +} + +void addListenSocketsToFdSet(fd_set * fds, int *fdmax) +{ + int i; + + for (i = 0; i < numberOfListenSockets; i++) { + FD_SET(listenSockets[i], fds); + if (listenSockets[i] > *fdmax) + *fdmax = listenSockets[i]; + } +} + +void closeAllListenSockets(void) +{ + int i; + + DEBUG("closeAllListenSockets called\n"); + + for (i = 0; i < numberOfListenSockets; i++) { + DEBUG("closing listen socket %i\n", i); + while (close(listenSockets[i]) < 0 && errno == EINTR) ; + } + freeAllListenSockets(); +} + +void freeAllListenSockets(void) +{ + numberOfListenSockets = 0; + free(listenSockets); + listenSockets = NULL; +} + +void getConnections(fd_set * fds) +{ + int i; + int fd = 0; + struct sockaddr sockAddr; + socklen_t socklen = sizeof(sockAddr); + + for (i = 0; i < numberOfListenSockets; i++) { + if (FD_ISSET(listenSockets[i], fds)) { + if ((fd = accept(listenSockets[i], &sockAddr, &socklen)) + >= 0) { + openAInterface(fd, &sockAddr); + } else if (fd < 0 + && (errno != EAGAIN && errno != EINTR)) { + ERROR("Problems accept()'ing\n"); + } + } + } +} + +int getBoundPort(void) +{ + return boundPort; +} diff --git a/trunk/src/listen.h b/trunk/src/listen.h new file mode 100644 index 000000000..638214003 --- /dev/null +++ b/trunk/src/listen.h @@ -0,0 +1,41 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef LISTEN_H +#define LISTEN_H + +#include "../config.h" + +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> +#include <sys/select.h> + +void listenOnPort(void); + +void getConnections(fd_set * fds); + +void closeAllListenSockets(void); +void freeAllListenSockets(void); + +/* fdmax should be initialized to something */ +void addListenSocketsToFdSet(fd_set * fds, int *fdmax); + +int getBoundPort(void); + +#endif diff --git a/trunk/src/locate.c b/trunk/src/locate.c new file mode 100644 index 000000000..7c3bab899 --- /dev/null +++ b/trunk/src/locate.c @@ -0,0 +1,211 @@ +/* the Music Player Daemon (MPD) + * (c)2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "locate.h" + +#include "utils.h" + +#define LOCATE_TAG_FILE_KEY "file" +#define LOCATE_TAG_FILE_KEY_OLD "filename" +#define LOCATE_TAG_ANY_KEY "any" + +int getLocateTagItemType(char *str) +{ + int i; + + if (0 == strcasecmp(str, LOCATE_TAG_FILE_KEY) || + 0 == strcasecmp(str, LOCATE_TAG_FILE_KEY_OLD)) + { + return LOCATE_TAG_FILE_TYPE; + } + + if (0 == strcasecmp(str, LOCATE_TAG_ANY_KEY)) + { + return LOCATE_TAG_ANY_TYPE; + } + + for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) + { + if (0 == strcasecmp(str, mpdTagItemKeys[i])) + return i; + } + + return -1; +} + +static int initLocateTagItem(LocateTagItem * item, char *typeStr, char *needle) +{ + item->tagType = getLocateTagItemType(typeStr); + + if (item->tagType < 0) + return -1; + + item->needle = xstrdup(needle); + + return 0; +} + +LocateTagItem *newLocateTagItem(char *typeStr, char *needle) +{ + LocateTagItem *ret = xmalloc(sizeof(LocateTagItem)); + + if (initLocateTagItem(ret, typeStr, needle) < 0) { + free(ret); + ret = NULL; + } + + return ret; +} + +void freeLocateTagItemArray(int count, LocateTagItem * array) +{ + int i; + + for (i = 0; i < count; i++) + free(array[i].needle); + + free(array); +} + +int newLocateTagItemArrayFromArgArray(char *argArray[], + int numArgs, LocateTagItem ** arrayRet) +{ + int i, j; + LocateTagItem *item; + + if (numArgs == 0) + return 0; + + if (numArgs % 2 != 0) + return -1; + + *arrayRet = xmalloc(sizeof(LocateTagItem) * numArgs / 2); + + for (i = 0, item = *arrayRet; i < numArgs / 2; i++, item++) { + if (initLocateTagItem + (item, argArray[i * 2], argArray[i * 2 + 1]) < 0) + goto fail; + } + + return numArgs / 2; + +fail: + for (j = 0; j < i; j++) { + free((*arrayRet)[j].needle); + } + + free(*arrayRet); + *arrayRet = NULL; + return -1; +} + +void freeLocateTagItem(LocateTagItem * item) +{ + free(item->needle); + free(item); +} + +static int strstrSearchTag(Song * song, int type, char *str) +{ + int i; + char *dup; + int ret = 0; + + if (type == LOCATE_TAG_FILE_TYPE || type == LOCATE_TAG_ANY_TYPE) { + dup = strDupToUpper(getSongUrl(song)); + if (strstr(dup, str)) + ret = 1; + free(dup); + if (ret == 1 || type == LOCATE_TAG_FILE_TYPE) { + return ret; + } + } + + if (!song->tag) + return 0; + + for (i = 0; i < song->tag->numOfItems && !ret; i++) { + if (type != LOCATE_TAG_ANY_TYPE && + song->tag->items[i].type != type) { + continue; + } + + dup = strDupToUpper(song->tag->items[i].value); + if (strstr(dup, str)) + ret = 1; + free(dup); + } + + return ret; +} + +int strstrSearchTags(Song * song, int numItems, LocateTagItem * items) +{ + int i; + + for (i = 0; i < numItems; i++) { + if (!strstrSearchTag(song, items[i].tagType, + items[i].needle)) { + return 0; + } + } + + return 1; +} + +static int tagItemFoundAndMatches(Song * song, int type, char *str) +{ + int i; + + if (type == LOCATE_TAG_FILE_TYPE || type == LOCATE_TAG_ANY_TYPE) { + if (0 == strcmp(str, getSongUrl(song))) + return 1; + if (type == LOCATE_TAG_FILE_TYPE) + return 0; + } + + if (!song->tag) + return 0; + + for (i = 0; i < song->tag->numOfItems; i++) { + if (type != LOCATE_TAG_ANY_TYPE && + song->tag->items[i].type != type) { + continue; + } + + if (0 == strcmp(str, song->tag->items[i].value)) + return 1; + } + + return 0; +} + + +int tagItemsFoundAndMatches(Song * song, int numItems, LocateTagItem * items) +{ + int i; + + for (i = 0; i < numItems; i++) { + if (!tagItemFoundAndMatches(song, items[i].tagType, + items[i].needle)) { + return 0; + } + } + + return 1; +} diff --git a/trunk/src/locate.h b/trunk/src/locate.h new file mode 100644 index 000000000..c165a310a --- /dev/null +++ b/trunk/src/locate.h @@ -0,0 +1,46 @@ +/* the Music Player Daemon (MPD) + * (c)2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "song.h" + +#define LOCATE_TAG_FILE_TYPE TAG_NUM_OF_ITEM_TYPES+10 +#define LOCATE_TAG_ANY_TYPE TAG_NUM_OF_ITEM_TYPES+20 + +/* struct used for search, find, list queries */ +typedef struct _LocateTagItem { + mpd_sint8 tagType; + /* what we are looking for */ + char *needle; +} LocateTagItem; + +int getLocateTagItemType(char *str); + +/* returns NULL if not a known type */ +LocateTagItem *newLocateTagItem(char *typeString, char *needle); + +/* return number of items or -1 on error */ +int newLocateTagItemArrayFromArgArray(char *argArray[], int numArgs, + LocateTagItem ** arrayRet); + +void freeLocateTagItemArray(int count, LocateTagItem * array); + +void freeLocateTagItem(LocateTagItem * item); + +int strstrSearchTags(Song * song, int numItems, LocateTagItem * items); + +int tagItemsFoundAndMatches(Song * song, int numItems, LocateTagItem * items); diff --git a/trunk/src/log.c b/trunk/src/log.c new file mode 100644 index 000000000..fa4ae64f0 --- /dev/null +++ b/trunk/src/log.c @@ -0,0 +1,262 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "log.h" + +#include "conf.h" +#include "myfprintf.h" +#include "utils.h" + +#include <assert.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <time.h> + +static unsigned int logLevel = LOG_LEVEL_LOW; +static int warningFlushed; +static int stdout_mode = 1; +static char *warningBuffer; +static int out_fd = -1; +static int err_fd = -1; +static const char *out_filename; +static const char *err_filename; + +/* redirect stdin to /dev/null to work around a libao bug */ +static void redirect_stdin(void) +{ + int fd; + if ((fd = open("/dev/null", O_RDONLY)) < 0) + FATAL("failed to open /dev/null %s\n", strerror(errno)); + if (dup2(fd, STDIN_FILENO) < 0) + FATAL("dup2 stdin: %s\n", strerror(errno)); +} + +static void redirect_logs(void) +{ + assert(out_fd > 0); + assert(err_fd > 0); + if (dup2(out_fd, STDOUT_FILENO) < 0) + FATAL("problems dup2 stdout : %s\n", strerror(errno)); + if (dup2(err_fd, STDERR_FILENO) < 0) + FATAL("problems dup2 stderr : %s\n", strerror(errno)); +} + +static const char *log_date(void) +{ + static char buf[16]; + time_t t = time(NULL); + strftime(buf, 16, "%b %d %H:%M : ", localtime(&t)); + return buf; +} + +#define BUFFER_LENGTH 4096 +static void buffer_warning(const char *fmt, va_list args) +{ + char buffer[BUFFER_LENGTH]; + char *tmp = buffer; + size_t len = BUFFER_LENGTH; + + if (!stdout_mode) { + memcpy(buffer, log_date(), 15); + tmp += 15; + len -= 15; + } + + vsnprintf(tmp, len, fmt, args); + warningBuffer = appendToString(warningBuffer, buffer); + + va_end(args); +} + +static void do_log(FILE *fp, const char *fmt, va_list args) +{ + if (!stdout_mode) + fwrite(log_date(), 15, 1, fp); + vfprintf(fp, fmt, args); +} + +void flushWarningLog(void) +{ + char *s = warningBuffer; + + DEBUG("flushing warning messages\n"); + + if (warningBuffer != NULL) + { + while (s != NULL) { + char *next = strchr(s, '\n'); + if (next == NULL) break; + *next = '\0'; + next++; + fprintf(stderr, "%s\n", s); + s = next; + } + + warningBuffer = NULL; + } + + warningFlushed = 1; + + DEBUG("done flushing warning messages\n"); +} + +void initLog(const int verbose) +{ + ConfigParam *param; + + /* unbuffer stdout, stderr is unbuffered by default, leave it */ + setvbuf(stdout, (char *)NULL, _IONBF, 0); + + if (verbose) { + logLevel = LOG_LEVEL_DEBUG; + return; + } + if (!(param = getConfigParam(CONF_LOG_LEVEL))) + return; + if (0 == strcmp(param->value, "default")) { + logLevel = LOG_LEVEL_LOW; + } else if (0 == strcmp(param->value, "secure")) { + logLevel = LOG_LEVEL_SECURE; + } else if (0 == strcmp(param->value, "verbose")) { + logLevel = LOG_LEVEL_DEBUG; + } else { + FATAL("unknown log level \"%s\" at line %i\n", + param->value, param->line); + } +} + +void open_log_files(const int use_stdout) +{ + mode_t prev; + ConfigParam *param; + + if (use_stdout) { + flushWarningLog(); + return; + } + + prev = umask(0066); + param = parseConfigFilePath(CONF_LOG_FILE, 1); + out_filename = param->value; + out_fd = open(out_filename, O_CREAT | O_WRONLY | O_APPEND, 0666); + if (out_fd < 0) + FATAL("problem opening log file \"%s\" (config line %i) for " + "writing\n", param->value, param->line); + + param = parseConfigFilePath(CONF_ERROR_FILE, 1); + err_filename = param->value; + err_fd = open(err_filename, O_CREAT | O_WRONLY | O_APPEND, 0666); + if (err_fd < 0) + FATAL("problem opening error file \"%s\" (config line %i) for " + "writing\n", param->value, param->line); + + umask(prev); +} + +void setup_log_output(const int use_stdout) +{ + fflush(NULL); + if (!use_stdout) { + redirect_logs(); + stdout_mode = 0; + } + redirect_stdin(); +} + +#define log_func(func,level,fp) \ +mpd_printf void func(const char *fmt, ...) \ +{ \ + if (logLevel >= level) { \ + va_list args; \ + va_start(args, fmt); \ + do_log(fp, fmt, args); \ + va_end(args); \ + } \ +} + +log_func(ERROR, 0, stderr) +log_func(LOG, 0, stdout) +log_func(SECURE, LOG_LEVEL_SECURE, stdout) +log_func(DEBUG, LOG_LEVEL_DEBUG, stdout) + +#undef log_func + +void WARNING(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + if (warningFlushed) { + do_log(stderr, fmt, args); + } else + buffer_warning(fmt, args); + va_end(args); +} + +mpd_printf mpd_noreturn void FATAL(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + do_log(stderr, fmt, args); + va_end(args); + exit(EXIT_FAILURE); +} + +int cycle_log_files(void) +{ + mode_t prev; + + if (stdout_mode) + return 0; + assert(out_filename); + assert(err_filename); + + DEBUG("Cycling log files...\n"); + close_log_files(); + + prev = umask(0066); + + out_fd = open(out_filename, O_CREAT | O_WRONLY | O_APPEND, 0666); + if (out_fd < 0) { + ERROR("error re-opening log file: %s\n", out_filename); + return -1; + } + + err_fd = open(err_filename, O_CREAT | O_WRONLY | O_APPEND, 0666); + if (err_fd < 0) { + ERROR("error re-opening error file: %s\n", err_filename); + return -1; + } + + umask(prev); + + redirect_logs(); + DEBUG("Done cycling log files\n"); + return 0; +} + +void close_log_files(void) +{ + if (stdout_mode) + return; + assert(out_fd > 0); + assert(err_fd > 0); + xclose(out_fd); + xclose(err_fd); +} + diff --git a/trunk/src/log.h b/trunk/src/log.h new file mode 100644 index 000000000..34f6ac00e --- /dev/null +++ b/trunk/src/log.h @@ -0,0 +1,50 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef LOG_H +#define LOG_H + +#include "../config.h" +#include "gcc.h" + +#include <unistd.h> + +#define LOG_LEVEL_LOW 0 +#define LOG_LEVEL_SECURE 1 +#define LOG_LEVEL_DEBUG 2 + +mpd_printf void ERROR(const char *fmt, ...); +mpd_printf void LOG(const char *fmt, ...); +mpd_printf void SECURE(const char *fmt, ...); +mpd_printf void DEBUG(const char *fmt, ...); +mpd_printf void WARNING(const char *fmt, ...); +mpd_printf void FATAL(const char *fmt, ...); + +void initLog(const int verbose); + +void setup_log_output(const int use_stdout); + +void open_log_files(const int use_stdout); + +int cycle_log_files(void); + +void close_log_files(void); + +void flushWarningLog(void); + +#endif /* LOG_H */ diff --git a/trunk/src/ls.c b/trunk/src/ls.c new file mode 100644 index 000000000..0b3f7f354 --- /dev/null +++ b/trunk/src/ls.c @@ -0,0 +1,281 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "ls.h" +#include "playlist.h" +#include "path.h" +#include "myfprintf.h" +#include "log.h" +#include "utf8.h" +#include "utils.h" + +#include <dirent.h> +#include <stdio.h> +#include <errno.h> + +static char *remoteUrlPrefixes[] = { + "http://", + NULL +}; + +int printRemoteUrlHandlers(int fd) +{ + char **prefixes = remoteUrlPrefixes; + + while (*prefixes) { + fdprintf(fd, "handler: %s\n", *prefixes); + prefixes++; + } + + return 0; +} + +int isValidRemoteUtf8Url(char *utf8url) +{ + int ret = 0; + char *temp; + + switch (isRemoteUrl(utf8url)) { + case 1: + ret = 1; + temp = utf8url; + while (*temp) { + if ((*temp >= 'a' && *temp <= 'z') || + (*temp >= 'A' && *temp <= 'Z') || + (*temp >= '0' && *temp <= '9') || + *temp == '$' || + *temp == '-' || + *temp == '.' || + *temp == '+' || + *temp == '!' || + *temp == '*' || + *temp == '\'' || + *temp == '(' || + *temp == ')' || + *temp == ',' || + *temp == '%' || + *temp == '/' || + *temp == ':' || + *temp == '?' || + *temp == ';' || *temp == '&' || *temp == '=') { + } else { + ret = 1; + break; + } + temp++; + } + break; + } + + return ret; +} + +int isRemoteUrl(char *url) +{ + int count = 0; + char **urlPrefixes = remoteUrlPrefixes; + + while (*urlPrefixes) { + count++; + if (strncmp(*urlPrefixes, url, strlen(*urlPrefixes)) == 0) { + return count; + } + urlPrefixes++; + } + + return 0; +} + +int lsPlaylists(int fd, char *utf8path) +{ + DIR *dir; + struct stat st; + struct dirent *ent; + char *dup; + char *utf8; + char s[MAXPATHLEN + 1]; + List *list = NULL; + ListNode *node = NULL; + char *path = utf8ToFsCharset(utf8path); + char *actualPath = rpp2app(path); + int actlen = strlen(actualPath) + 1; + int maxlen = MAXPATHLEN - actlen; + int suflen = strlen(PLAYLIST_FILE_SUFFIX) + 1; + int suff; + + if (actlen > MAXPATHLEN - 1 || (dir = opendir(actualPath)) == NULL) { + return 0; + } + + s[MAXPATHLEN] = '\0'; + /* this is safe, notice actlen > MAXPATHLEN-1 above */ + strcpy(s, actualPath); + strcat(s, "/"); + + while ((ent = readdir(dir))) { + size_t len = strlen(ent->d_name) + 1; + dup = ent->d_name; + if (mpd_likely(len <= maxlen) && + dup[0] != '.' && + (suff = strlen(dup) - suflen) > 0 && + dup[suff] == '.' && + strcmp(dup + suff + 1, PLAYLIST_FILE_SUFFIX) == 0) { + memcpy(s + actlen, ent->d_name, len); + if (stat(s, &st) == 0) { + if (S_ISREG(st.st_mode)) { + if (list == NULL) + list = makeList(NULL, 1); + dup[suff] = '\0'; + if ((utf8 = fsCharsetToUtf8(dup))) { + insertInList(list, utf8, NULL); + } + } + } + } + } + + closedir(dir); + + if (list) { + int i; + sortList(list); + + dup = xmalloc(strlen(utf8path) + 2); + strcpy(dup, utf8path); + for (i = strlen(dup) - 1; i >= 0 && dup[i] == '/'; i--) { + dup[i] = '\0'; + } + if (strlen(dup)) + strcat(dup, "/"); + + node = list->firstNode; + while (node != NULL) { + if (!strchr(node->key, '\n')) { + fdprintf(fd, "playlist: %s%s\n", dup, + node->key); + } + node = node->nextNode; + } + + freeList(list); + free(dup); + } + + return 0; +} + +int myStat(char *utf8file, struct stat *st) +{ + char *file = utf8ToFsCharset(utf8file); + char *actualFile = file; + + if (actualFile[0] != '/') + actualFile = rmp2amp(file); + + return stat(actualFile, st); +} + +static int isFile(char *utf8file, time_t * mtime) +{ + struct stat st; + + if (myStat(utf8file, &st) == 0) { + if (S_ISREG(st.st_mode)) { + if (mtime) + *mtime = st.st_mtime; + return 1; + } else { + DEBUG("isFile: %s is not a regular file\n", utf8file); + return 0; + } + } else { + DEBUG("isFile: failed to stat: %s: %s\n", utf8file, + strerror(errno)); + } + + return 0; +} + +/* suffixes should be ascii only characters */ +char *getSuffix(char *utf8file) +{ + char *ret = NULL; + + while (*utf8file) { + if (*utf8file == '.') + ret = utf8file + 1; + utf8file++; + } + + return ret; +} + +static int hasSuffix(char *utf8file, char *suffix) +{ + char *s = getSuffix(utf8file); + if (s && 0 == strcmp(s, suffix)) + return 1; + return 0; +} + +int isPlaylist(char *utf8file) +{ + if (isFile(utf8file, NULL)) { + return hasSuffix(utf8file, PLAYLIST_FILE_SUFFIX); + } + return 0; +} + +int isDir(char *utf8name) +{ + struct stat st; + + if (myStat(utf8name, &st) == 0) { + if (S_ISDIR(st.st_mode)) { + return 1; + } + } + + return 0; +} + +InputPlugin *hasMusicSuffix(char *utf8file, unsigned int next) +{ + InputPlugin *ret = NULL; + + char *s = getSuffix(utf8file); + if (s) { + ret = getInputPluginFromSuffix(s, next); + } else { + DEBUG("hasMusicSuffix: The file: %s has no valid suffix\n", + utf8file); + } + + return ret; +} + +InputPlugin *isMusic(char *utf8file, time_t * mtime, unsigned int next) +{ + if (isFile(utf8file, mtime)) { + InputPlugin *plugin = hasMusicSuffix(utf8file, next); + if (plugin != NULL) + return plugin; + } + DEBUG("isMusic: %s is not a valid file\n", utf8file); + return NULL; +} diff --git a/trunk/src/ls.h b/trunk/src/ls.h new file mode 100644 index 000000000..20f668bd9 --- /dev/null +++ b/trunk/src/ls.h @@ -0,0 +1,52 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef LS_H +#define LS_H + +#include "../config.h" + +#include "inputPlugin.h" + +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <time.h> + +int lsPlaylists(int fd, char *utf8path); + +char *getSuffix(char *utf8file); + +int isValidRemoteUtf8Url(char *utf8url); + +int isRemoteUrl(char *url); + +int myStat(char *utf8file, struct stat *st); + +int isDir(char *utf8name); + +int isPlaylist(char *utf8file); + +InputPlugin *hasMusicSuffix(char *utf8file, unsigned int next); + +InputPlugin *isMusic(char *utf8file, time_t * mtime, unsigned int next); + +int printRemoteUrlHandlers(int fd); + +#endif diff --git a/trunk/src/main.c b/trunk/src/main.c new file mode 100644 index 000000000..4c537eb1d --- /dev/null +++ b/trunk/src/main.c @@ -0,0 +1,482 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "interface.h" +#include "command.h" +#include "playlist.h" +#include "directory.h" +#include "player.h" +#include "listen.h" +#include "conf.h" +#include "path.h" +#include "playerData.h" +#include "stats.h" +#include "sig_handlers.h" +#include "audio.h" +#include "volume.h" +#include "log.h" +#include "permission.h" +#include "replayGain.h" +#include "inputPlugin.h" +#include "audioOutput.h" +#include "inputStream.h" +#include "state_file.h" +#include "tag.h" +#include "tagTracker.h" +#include "dbUtils.h" +#include "../config.h" +#include "utils.h" +#include "normalize.h" +#include "zeroconf.h" + +#include <stdio.h> +#include <sys/select.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <pwd.h> +#include <grp.h> +#include <time.h> +#include <unistd.h> + +#define SYSTEM_CONFIG_FILE_LOCATION "/etc/mpd.conf" +#define USER_CONFIG_FILE_LOCATION "/.mpdconf" + +typedef struct _Options { + int kill; + int daemon; + int stdOutput; + int createDB; + int verbose; +} Options; + +/* + * from git-1.3.0, needed for solaris + */ +#ifndef HAVE_SETENV +static int setenv(const char *name, const char *value, int replace) +{ + int out; + size_t namelen, valuelen; + char *envstr; + + if (!name || !value) + return -1; + if (!replace) { + char *oldval = NULL; + oldval = getenv(name); + if (oldval) + return 0; + } + + namelen = strlen(name); + valuelen = strlen(value); + envstr = xmalloc((namelen + valuelen + 2)); + if (!envstr) + return -1; + + memcpy(envstr, name, namelen); + envstr[namelen] = '='; + memcpy(envstr + namelen + 1, value, valuelen); + envstr[namelen + valuelen + 1] = 0; + + out = putenv(envstr); + /* putenv(3) makes the argument string part of the environment, + * and changing that string modifies the environment --- which + * means we do not own that storage anymore. Do not free + * envstr. + */ + + return out; +} +#endif /* HAVE_SETENV */ + +static void usage(char *argv[]) +{ + ERROR("usage:\n"); + ERROR(" %s [options] <conf file>\n", argv[0]); + ERROR(" %s [options] (searches for ~%s then %s)\n", + argv[0], USER_CONFIG_FILE_LOCATION, SYSTEM_CONFIG_FILE_LOCATION); + ERROR("\n"); + ERROR("options:\n"); + ERROR(" --help this usage statement\n"); + ERROR(" --kill kill the currently running mpd session\n"); + ERROR + (" --create-db force (re)creation of database and exit\n"); + ERROR + (" --no-create-db don't create database, even if it doesn't exist\n"); + ERROR(" --no-daemon don't detach from console\n"); + ERROR(" --stdout print messages to stdout and stderr\n"); + ERROR(" --verbose verbose logging\n"); + ERROR(" --version prints version information\n"); +} + +static void version(void) +{ + LOG("mpd (MPD: Music Player Daemon) %s\n", VERSION); + LOG("\n"); + LOG("Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n"); + LOG("This is free software; see the source for copying conditions. There is NO\n"); + LOG("warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"); + LOG("\n"); + LOG("Supported formats:\n"); + + initInputPlugins(); + printAllInputPluginSuffixes(stdout); + + LOG("\n"); + LOG("Supported outputs:\n"); + loadAudioDrivers(); + printAllOutputPluginTypes(stdout); +} + +static void parseOptions(int argc, char **argv, Options * options) +{ + int argcLeft = argc; + + options->verbose = 0; + options->daemon = 1; + options->stdOutput = 0; + options->createDB = 0; + options->kill = 0; + + if (argc > 1) { + int i = 1; + while (i < argc) { + if (strncmp(argv[i], "--", 2) == 0) { + if (strcmp(argv[i], "--help") == 0) { + usage(argv); + exit(EXIT_SUCCESS); + } else if (strcmp(argv[i], "--kill") == 0) { + options->kill++; + argcLeft--; + } else if (strcmp(argv[i], "--no-daemon") == 0) { + options->daemon = 0; + argcLeft--; + } else if (strcmp(argv[i], "--stdout") == 0) { + options->stdOutput = 1; + argcLeft--; + } else if (strcmp(argv[i], "--create-db") == 0) { + options->stdOutput = 1; + options->createDB = 1; + argcLeft--; + } else if (strcmp(argv[i], "--no-create-db") == + 0) { + options->createDB = -1; + argcLeft--; + } else if (strcmp(argv[i], "--verbose") == 0) { + options->verbose = 1; + argcLeft--; + } else if (strcmp(argv[i], "--version") == 0) { + version(); + exit(EXIT_SUCCESS); + } else { + fprintf(stderr, + "unknown command line option: %s\n", + argv[i]); + exit(EXIT_FAILURE); + } + } else + break; + i++; + } + } + + if (argcLeft <= 2) { + if (argcLeft == 2) { + readConf(argv[argc - 1]); + return; + } else if (argcLeft == 1) { + struct stat st; + char *homedir = getenv("HOME"); + char userfile[MAXPATHLEN + 1] = ""; + if (homedir && (strlen(homedir) + + strlen(USER_CONFIG_FILE_LOCATION)) < + MAXPATHLEN) { + strcpy(userfile, homedir); + strcat(userfile, USER_CONFIG_FILE_LOCATION); + } + if (strlen(userfile) && (0 == stat(userfile, &st))) { + readConf(userfile); + return; + } else if (0 == stat(SYSTEM_CONFIG_FILE_LOCATION, &st)) { + readConf(SYSTEM_CONFIG_FILE_LOCATION); + return; + } + } + } + + usage(argv); + exit(EXIT_FAILURE); +} + +static void closeAllFDs(void) +{ + int i; + int fds = getdtablesize(); + + for (i = 3; i < fds; i++) + close(i); +} + +static void changeToUser(void) +{ + ConfigParam *param = getConfigParam(CONF_USER); + + if (param && strlen(param->value)) { + /* get uid */ + struct passwd *userpwd; + if ((userpwd = getpwnam(param->value)) == NULL) { + FATAL("no such user \"%s\" at line %i\n", param->value, + param->line); + } + + if (setgid(userpwd->pw_gid) == -1) { + FATAL("cannot setgid for user \"%s\" at line %i: %s\n", + param->value, param->line, strerror(errno)); + } +#ifdef _BSD_SOURCE + /* init suplementary groups + * (must be done before we change our uid) + */ + if (initgroups(param->value, userpwd->pw_gid) == -1) { + WARNING("cannot init supplementary groups " + "of user \"%s\" at line %i: %s\n", + param->value, param->line, strerror(errno)); + } +#endif + + /* set uid */ + if (setuid(userpwd->pw_uid) == -1) { + FATAL("cannot change to uid of user " + "\"%s\" at line %i: %s\n", + param->value, param->line, strerror(errno)); + } + + /* this is needed by libs such as arts */ + if (userpwd->pw_dir) { + setenv("HOME", userpwd->pw_dir, 1); + } + } +} + +static void openDB(Options * options, char *argv0) +{ + if (options->createDB > 0 || readDirectoryDB() < 0) { + if (options->createDB < 0) { + FATAL("can't open db file and using " + "\"--no-create-db\" command line option\n" + "try running \"%s --create-db\"\n", argv0); + } + flushWarningLog(); + if (checkDirectoryDB() < 0) + exit(EXIT_FAILURE); + initMp3Directory(); + if (writeDirectoryDB() < 0) + exit(EXIT_FAILURE); + if (options->createDB) + exit(EXIT_SUCCESS); + } +} + +static void daemonize(Options * options) +{ + FILE *fp = NULL; + ConfigParam *pidFileParam = parseConfigFilePath(CONF_PID_FILE, 0); + + if (pidFileParam) { + /* do this before daemon'izing so we can fail gracefully if we can't + * write to the pid file */ + DEBUG("opening pid file\n"); + fp = fopen(pidFileParam->value, "w+"); + if (!fp) { + FATAL("could not open %s \"%s\" (at line %i) for writing: %s\n", + CONF_PID_FILE, pidFileParam->value, + pidFileParam->line, strerror(errno)); + } + } + + if (options->daemon) { + int pid; + + fflush(NULL); + pid = fork(); + if (pid > 0) + _exit(EXIT_SUCCESS); + else if (pid < 0) { + FATAL("problems fork'ing for daemon!\n"); + } + + if (chdir("/") < 0) { + FATAL("problems changing to root directory\n"); + } + + if (setsid() < 0) { + FATAL("problems setsid'ing\n"); + } + + fflush(NULL); + pid = fork(); + if (pid > 0) + _exit(EXIT_SUCCESS); + else if (pid < 0) { + FATAL("problems fork'ing for daemon!\n"); + } + + DEBUG("daemonized!\n"); + } + + if (pidFileParam) { + DEBUG("writing pid file\n"); + fprintf(fp, "%lu\n", (unsigned long)getpid()); + fclose(fp); + } +} + +static void cleanUpPidFile(void) +{ + ConfigParam *pidFileParam = parseConfigFilePath(CONF_PID_FILE, 0); + + if (!pidFileParam) + return; + + DEBUG("cleaning up pid file\n"); + + unlink(pidFileParam->value); +} + +static void killFromPidFile(char *cmd, int killOption) +{ + FILE *fp; + ConfigParam *pidFileParam = parseConfigFilePath(CONF_PID_FILE, 0); + int pid; + + if (!pidFileParam) { + FATAL("no pid_file specified in the config file\n"); + } + + fp = fopen(pidFileParam->value, "r"); + if (!fp) { + FATAL("unable to open %s \"%s\": %s\n", + CONF_PID_FILE, pidFileParam->value, strerror(errno)); + } + if (fscanf(fp, "%i", &pid) != 1) { + FATAL("unable to read the pid from file \"%s\"\n", + pidFileParam->value); + } + fclose(fp); + + if (kill(pid, SIGTERM)) { + FATAL("unable to kill proccess %i: %s\n", pid, strerror(errno)); + } + exit(EXIT_SUCCESS); +} + +int main(int argc, char *argv[]) +{ + Options options; + clock_t start; + + closeAllFDs(); + + initConf(); + + parseOptions(argc, argv, &options); + + if (options.kill) + killFromPidFile(argv[0], options.kill); + + initStats(); + initTagConfig(); + initLog(options.verbose); + + if (options.createDB <= 0) + listenOnPort(); + + changeToUser(); + + open_log_files(options.stdOutput); + + initPaths(); + initPermissions(); + initPlaylist(); + initInputPlugins(); + + openDB(&options, argv[0]); + + initCommands(); + initPlayerData(); + initAudioConfig(); + initAudioDriver(); + initVolume(); + initInterfaces(); + initZeroconf(); + initReplayGainState(); + initNormalization(); + initInputStream(); + + daemonize(&options); + + setup_log_output(options.stdOutput); + + + + initSigHandlers(); + + openVolumeDevice(); + read_state_file(); + + while (COMMAND_RETURN_KILL != doIOForInterfaces()) { + if (COMMAND_RETURN_KILL == handlePendingSignals()) + break; + syncPlayerAndPlaylist(); + closeOldInterfaces(); + readDirectoryDBIfUpdateIsFinished(); + } + + write_state_file(); + playerKill(); + finishZeroconf(); + freeAllInterfaces(); + closeAllListenSockets(); + finishPlaylist(); + + start = clock(); + closeMp3Directory(); + DEBUG("closeMp3Directory took %f seconds\n", + ((float)(clock()-start))/CLOCKS_PER_SEC); + + finishNormalization(); + finishAudioDriver(); + finishAudioConfig(); + finishVolume(); + finishPaths(); + finishPermissions(); + finishCommands(); + finishInputPlugins(); + cleanUpPidFile(); + finishConf(); + freePlayerData(); + + close_log_files(); + return EXIT_SUCCESS; +} diff --git a/trunk/src/metadataChunk.c b/trunk/src/metadataChunk.c new file mode 100644 index 000000000..bc5118fd0 --- /dev/null +++ b/trunk/src/metadataChunk.c @@ -0,0 +1,94 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "metadataChunk.h" +#include "gcc.h" + +#include <string.h> + +static void initMetadataChunk(MetadataChunk * chunk) +{ + chunk->name = -1; + chunk->artist = -1; + chunk->album = -1; + chunk->title = -1; +} + +#define dupElementToTag(item, element) { \ + if(element >= 0 && element < METADATA_BUFFER_LENGTH) { \ + addItemToMpdTag(ret, item, chunk->buffer+element); \ + } \ +} + +MpdTag *metadataChunkToMpdTagDup(MetadataChunk * chunk) +{ + MpdTag *ret = newMpdTag(); + + chunk->buffer[METADATA_BUFFER_LENGTH - 1] = '\0'; + + dupElementToTag(TAG_ITEM_NAME, chunk->name); + dupElementToTag(TAG_ITEM_TITLE, chunk->title); + dupElementToTag(TAG_ITEM_ARTIST, chunk->artist); + dupElementToTag(TAG_ITEM_ALBUM, chunk->album); + + return ret; +} + +#define copyStringToChunk(string, element) { \ + if(element < 0 && string && (slen = strlen(string)) && \ + pos < METADATA_BUFFER_LENGTH-1) \ + { \ + size_t len = slen; \ + size_t max = METADATA_BUFFER_LENGTH - 1 - pos; \ + if (mpd_unlikely(len > max)) \ + len = max; \ + memcpy(chunk->buffer+pos, string, len); \ + *(chunk->buffer+pos+len) = '\0'; \ + element = pos; \ + pos += slen+1; \ + } \ +} + +void copyMpdTagToMetadataChunk(MpdTag * tag, MetadataChunk * chunk) +{ + int pos = 0; + int slen; + int i; + + initMetadataChunk(chunk); + + if (!tag) + return; + + for (i = 0; i < tag->numOfItems; i++) { + switch (tag->items[i].type) { + case TAG_ITEM_NAME: + copyStringToChunk(tag->items[i].value, chunk->name); + break; + case TAG_ITEM_TITLE: + copyStringToChunk(tag->items[i].value, chunk->title); + break; + case TAG_ITEM_ARTIST: + copyStringToChunk(tag->items[i].value, chunk->artist); + break; + case TAG_ITEM_ALBUM: + copyStringToChunk(tag->items[i].value, chunk->album); + break; + } + } +} diff --git a/trunk/src/metadataChunk.h b/trunk/src/metadataChunk.h new file mode 100644 index 000000000..c1da8b320 --- /dev/null +++ b/trunk/src/metadataChunk.h @@ -0,0 +1,38 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef METADATA_CHUNK_H +#define METADATA_CHUNK_H + +#define METADATA_BUFFER_LENGTH 1024 + +#include "tag.h" + +typedef struct _MetadataChunk { + int name; + int title; + int artist; + int album; + char buffer[METADATA_BUFFER_LENGTH]; +} MetadataChunk; + +MpdTag *metadataChunkToMpdTagDup(MetadataChunk * chunk); + +void copyMpdTagToMetadataChunk(MpdTag * tag, MetadataChunk * chunk); + +#endif diff --git a/trunk/src/mp4ff/Makefile.am b/trunk/src/mp4ff/Makefile.am new file mode 100644 index 000000000..d1258e7b8 --- /dev/null +++ b/trunk/src/mp4ff/Makefile.am @@ -0,0 +1,9 @@ +noinst_LTLIBRARIES = libmp4ff.la + +noinst_HEADERS = mp4ff.h + +libmp4ff_la_SOURCES = mp4ff.c mp4atom.c mp4meta.c mp4sample.c mp4util.c \ + mp4tagupdate.c mp4ff.h mp4ffint.h mp4ff_int_types.h \ + drms.h drms.c drmstables.h + +AM_CFLAGS = -DUSE_TAGGING=1 diff --git a/trunk/src/mp4ff/drms.c b/trunk/src/mp4ff/drms.c new file mode 100644 index 000000000..368b88110 --- /dev/null +++ b/trunk/src/mp4ff/drms.c @@ -0,0 +1,1043 @@ +/***************************************************************************** + * drms.c : DRMS + ***************************************************************************** + * Copyright (C) 2004 VideoLAN + * $Id: drms.c,v 1.3 2004/01/11 15:52:18 menno Exp $ + * + * Author: Jon Lech Johansen <jon-vl@nanocrew.net> + * + * 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, USA. + *****************************************************************************/ + +#include <stdlib.h> /* malloc(), free() */ + +#include "mp4ffint.h" + +#ifdef ITUNES_DRM + +#ifdef _WIN32 +#include <tchar.h> +#include <shlobj.h> +#include <windows.h> +#endif + +#include "drms.h" +#include "drmstables.h" + +static __inline uint32_t U32_AT( void * _p ) +{ + uint8_t * p = (uint8_t *)_p; + return ( ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) + | ((uint32_t)p[2] << 8) | p[3] ); +} + +#define TAOS_INIT( tmp, i ) \ + memset( tmp, 0, sizeof(tmp) ); \ + tmp[ i + 0 ] = 0x67452301; \ + tmp[ i + 1 ] = 0xEFCDAB89; \ + tmp[ i + 2 ] = 0x98BADCFE; \ + tmp[ i + 3 ] = 0x10325476; + +#define ROR( x, n ) (((x) << (32-(n))) | ((x) >> (n))) + +static void init_ctx( uint32_t *p_ctx, uint32_t *p_input ) +{ + uint32_t i; + uint32_t p_tmp[ 6 ]; + + p_ctx[ 0 ] = sizeof(*p_input); + + memset( &p_ctx[ 1 + 4 ], 0, sizeof(*p_input) * 4 ); + memcpy( &p_ctx[ 1 + 0 ], p_input, sizeof(*p_input) * 4 ); + + p_tmp[ 0 ] = p_ctx[ 1 + 3 ]; + + for( i = 0; i < sizeof(p_drms_tab1)/sizeof(p_drms_tab1[ 0 ]); i++ ) + { + p_tmp[ 0 ] = ROR( p_tmp[ 0 ], 8 ); + + p_tmp[ 5 ] = p_drms_tab2[ (p_tmp[ 0 ] >> 24) & 0xFF ] + ^ ROR( p_drms_tab2[ (p_tmp[ 0 ] >> 16) & 0xFF ], 8 ) + ^ ROR( p_drms_tab2[ (p_tmp[ 0 ] >> 8) & 0xFF ], 16 ) + ^ ROR( p_drms_tab2[ p_tmp[ 0 ] & 0xFF ], 24 ) + ^ p_drms_tab1[ i ] + ^ p_ctx[ 1 + ((i + 1) * 4) - 4 ]; + + p_ctx[ 1 + ((i + 1) * 4) + 0 ] = p_tmp[ 5 ]; + p_tmp[ 5 ] ^= p_ctx[ 1 + ((i + 1) * 4) - 3 ]; + p_ctx[ 1 + ((i + 1) * 4) + 1 ] = p_tmp[ 5 ]; + p_tmp[ 5 ] ^= p_ctx[ 1 + ((i + 1) * 4) - 2 ]; + p_ctx[ 1 + ((i + 1) * 4) + 2 ] = p_tmp[ 5 ]; + p_tmp[ 5 ] ^= p_ctx[ 1 + ((i + 1) * 4) - 1 ]; + p_ctx[ 1 + ((i + 1) * 4) + 3 ] = p_tmp[ 5 ]; + + p_tmp[ 0 ] = p_tmp[ 5 ]; + } + + memcpy( &p_ctx[ 1 + 64 ], &p_ctx[ 1 ], sizeof(*p_ctx) * 4 ); + + for( i = 4; i < sizeof(p_drms_tab1); i++ ) + { + p_tmp[ 2 ] = p_ctx[ 1 + 4 + (i - 4) ]; + + p_tmp[ 0 ] = (((p_tmp[ 2 ] >> 7) & 0x01010101) * 27) + ^ ((p_tmp[ 2 ] & 0xFF7F7F7F) << 1); + p_tmp[ 1 ] = (((p_tmp[ 0 ] >> 7) & 0x01010101) * 27) + ^ ((p_tmp[ 0 ] & 0xFF7F7F7F) << 1); + p_tmp[ 4 ] = (((p_tmp[ 1 ] >> 7) & 0x01010101) * 27) + ^ ((p_tmp[ 1 ] & 0xFF7F7F7F) << 1); + + p_tmp[ 2 ] ^= p_tmp[ 4 ]; + + p_tmp[ 3 ] = ROR( p_tmp[ 1 ] ^ p_tmp[ 2 ], 16 ) + ^ ROR( p_tmp[ 0 ] ^ p_tmp[ 2 ], 8 ) + ^ ROR( p_tmp[ 2 ], 24 ); + + p_ctx[ 1 + 4 + 64 + (i - 4) ] = p_tmp[ 3 ] ^ p_tmp[ 4 ] + ^ p_tmp[ 1 ] ^ p_tmp[ 0 ]; + } +} + +static void ctx_xor( uint32_t *p_ctx, uint32_t *p_in, uint32_t *p_out, + uint32_t p_table1[ 256 ], uint32_t p_table2[ 256 ] ) +{ + uint32_t i, x, y; + uint32_t p_tmp1[ 4 ]; + uint32_t p_tmp2[ 4 ]; + + i = p_ctx[ 0 ] * 4; + + p_tmp1[ 0 ] = p_ctx[ 1 + i + 24 ] ^ p_in[ 0 ]; + p_tmp1[ 1 ] = p_ctx[ 1 + i + 25 ] ^ p_in[ 1 ]; + p_tmp1[ 2 ] = p_ctx[ 1 + i + 26 ] ^ p_in[ 2 ]; + p_tmp1[ 3 ] = p_ctx[ 1 + i + 27 ] ^ p_in[ 3 ]; + + i += 84; + +#define XOR_ROR( p_table, p_tmp, i_ctx ) \ + p_table[ (p_tmp[ y > 2 ? y - 3 : y + 1 ] >> 24) & 0xFF ] \ + ^ ROR( p_table[ (p_tmp[ y > 1 ? y - 2 : y + 2 ] >> 16) & 0xFF ], 8 ) \ + ^ ROR( p_table[ (p_tmp[ y > 0 ? y - 1 : y + 3 ] >> 8) & 0xFF ], 16 ) \ + ^ ROR( p_table[ p_tmp[ y ] & 0xFF ], 24 ) \ + ^ p_ctx[ i_ctx ] + + for( x = 0; x < 1; x++ ) + { + memcpy( p_tmp2, p_tmp1, sizeof(p_tmp1) ); + + for( y = 0; y < 4; y++ ) + { + p_tmp1[ y ] = XOR_ROR( p_table1, p_tmp2, 1 + i - x + y ); + } + } + + for( ; x < 9; x++ ) + { + memcpy( p_tmp2, p_tmp1, sizeof(p_tmp1) ); + + for( y = 0; y < 4; y++ ) + { + p_tmp1[ y ] = XOR_ROR( p_table1, p_tmp2, + 1 + i - x - ((x * 3) - y) ); + } + } + + for( y = 0; y < 4; y++ ) + { + p_out[ y ] = XOR_ROR( p_table2, p_tmp1, + 1 + i - x - ((x * 3) - y) ); + } + +#undef XOR_ROR +} + +static void taos( uint32_t *p_buffer, uint32_t *p_input ) +{ + uint32_t i; + uint32_t x = 0; + uint32_t p_tmp1[ 4 ]; + uint32_t p_tmp2[ 4 ]; + + memcpy( p_tmp1, p_buffer, sizeof(p_tmp1) ); + + p_tmp2[ 0 ] = ((~p_tmp1[ 1 ] & p_tmp1[ 3 ]) + | (p_tmp1[ 2 ] & p_tmp1[ 1 ])) + p_input[ x ]; + p_tmp1[ 0 ] = p_tmp2[ 0 ] + p_tmp1[ 0 ] + p_drms_tab_taos[ x++ ]; + + for( i = 0; i < 4; i++ ) + { + p_tmp2[ 0 ] = ((p_tmp1[ 0 ] >> 0x19) + | (p_tmp1[ 0 ] << 0x7)) + p_tmp1[ 1 ]; + p_tmp2[ 1 ] = ((~p_tmp2[ 0 ] & p_tmp1[ 2 ]) + | (p_tmp1[ 1 ] & p_tmp2[ 0 ])) + p_input[ x ]; + p_tmp2[ 1 ] += p_tmp1[ 3 ] + p_drms_tab_taos[ x++ ]; + + p_tmp1[ 3 ] = ((p_tmp2[ 1 ] >> 0x14) + | (p_tmp2[ 1 ] << 0xC)) + p_tmp2[ 0 ]; + p_tmp2[ 1 ] = ((~p_tmp1[ 3 ] & p_tmp1[ 1 ]) + | (p_tmp1[ 3 ] & p_tmp2[ 0 ])) + p_input[ x ]; + p_tmp2[ 1 ] += p_tmp1[ 2 ] + p_drms_tab_taos[ x++ ]; + + p_tmp1[ 2 ] = ((p_tmp2[ 1 ] >> 0xF) + | (p_tmp2[ 1 ] << 0x11)) + p_tmp1[ 3 ]; + p_tmp2[ 1 ] = ((~p_tmp1[ 2 ] & p_tmp2[ 0 ]) + | (p_tmp1[ 3 ] & p_tmp1[ 2 ])) + p_input[ x ]; + p_tmp2[ 2 ] = p_tmp2[ 1 ] + p_tmp1[ 1 ] + p_drms_tab_taos[ x++ ]; + + p_tmp1[ 1 ] = ((p_tmp2[ 2 ] << 0x16) + | (p_tmp2[ 2 ] >> 0xA)) + p_tmp1[ 2 ]; + if( i == 3 ) + { + p_tmp2[ 1 ] = ((~p_tmp1[ 3 ] & p_tmp1[ 2 ]) + | (p_tmp1[ 3 ] & p_tmp1[ 1 ])) + p_input[ 1 ]; + } + else + { + p_tmp2[ 1 ] = ((~p_tmp1[ 1 ] & p_tmp1[ 3 ]) + | (p_tmp1[ 2 ] & p_tmp1[ 1 ])) + p_input[ x ]; + } + p_tmp1[ 0 ] = p_tmp2[ 0 ] + p_tmp2[ 1 ] + p_drms_tab_taos[ x++ ]; + } + + for( i = 0; i < 4; i++ ) + { + uint8_t p_table[ 4 ][ 4 ] = + { + { 6, 11, 0, 5 }, + { 10, 15, 4, 9 }, + { 14, 3, 8, 13 }, + { 2, 7, 12, 5 } + }; + + p_tmp2[ 0 ] = ((p_tmp1[ 0 ] >> 0x1B) + | (p_tmp1[ 0 ] << 0x5)) + p_tmp1[ 1 ]; + p_tmp2[ 1 ] = ((~p_tmp1[ 2 ] & p_tmp1[ 1 ]) + | (p_tmp1[ 2 ] & p_tmp2[ 0 ])) + + p_input[ p_table[ i ][ 0 ] ]; + p_tmp2[ 1 ] += p_tmp1[ 3 ] + p_drms_tab_taos[ x++ ]; + + p_tmp1[ 3 ] = ((p_tmp2[ 1 ] >> 0x17) + | (p_tmp2[ 1 ] << 0x9)) + p_tmp2[ 0 ]; + p_tmp2[ 1 ] = ((~p_tmp1[ 1 ] & p_tmp2[ 0 ]) + | (p_tmp1[ 3 ] & p_tmp1[ 1 ])) + + p_input[ p_table[ i ][ 1 ] ]; + p_tmp2[ 1 ] += p_tmp1[ 2 ] + p_drms_tab_taos[ x++ ]; + + p_tmp1[ 2 ] = ((p_tmp2[ 1 ] >> 0x12) + | (p_tmp2[ 1 ] << 0xE)) + p_tmp1[ 3 ]; + p_tmp2[ 1 ] = ((~p_tmp2[ 0 ] & p_tmp1[ 3 ]) + | (p_tmp1[ 2 ] & p_tmp2[ 0 ])) + + p_input[ p_table[ i ][ 2 ] ]; + p_tmp2[ 1 ] += p_tmp1[ 1 ] + p_drms_tab_taos[ x++ ]; + + p_tmp1[ 1 ] = ((p_tmp2[ 1 ] << 0x14) + | (p_tmp2[ 1 ] >> 0xC)) + p_tmp1[ 2 ]; + if( i == 3 ) + { + p_tmp2[ 1 ] = (p_tmp1[ 3 ] ^ p_tmp1[ 2 ] ^ p_tmp1[ 1 ]) + + p_input[ p_table[ i ][ 3 ] ]; + } + else + { + p_tmp2[ 1 ] = ((~p_tmp1[ 3 ] & p_tmp1[ 2 ]) + | (p_tmp1[ 3 ] & p_tmp1[ 1 ])) + + p_input[ p_table[ i ][ 3 ] ]; + } + p_tmp1[ 0 ] = p_tmp2[ 0 ] + p_tmp2[ 1 ] + p_drms_tab_taos[ x++ ]; + } + + for( i = 0; i < 4; i++ ) + { + uint8_t p_table[ 4 ][ 4 ] = + { + { 8, 11, 14, 1 }, + { 4, 7, 10, 13 }, + { 0, 3, 6, 9 }, + { 12, 15, 2, 0 } + }; + + p_tmp2[ 0 ] = ((p_tmp1[ 0 ] >> 0x1C) + | (p_tmp1[ 0 ] << 0x4)) + p_tmp1[ 1 ]; + p_tmp2[ 1 ] = (p_tmp1[ 2 ] ^ p_tmp1[ 1 ] ^ p_tmp2[ 0 ]) + + p_input[ p_table[ i ][ 0 ] ]; + p_tmp2[ 1 ] += p_tmp1[ 3 ] + p_drms_tab_taos[ x++ ]; + + p_tmp1[ 3 ] = ((p_tmp2[ 1 ] >> 0x15) + | (p_tmp2[ 1 ] << 0xB)) + p_tmp2[ 0 ]; + p_tmp2[ 1 ] = (p_tmp1[ 3 ] ^ p_tmp1[ 1 ] ^ p_tmp2[ 0 ]) + + p_input[ p_table[ i ][ 1 ] ]; + p_tmp2[ 1 ] += p_tmp1[ 2 ] + p_drms_tab_taos[ x++ ]; + + p_tmp1[ 2 ] = ((p_tmp2[ 1 ] >> 0x10) + | (p_tmp2[ 1 ] << 0x10)) + p_tmp1[ 3 ]; + p_tmp2[ 1 ] = (p_tmp1[ 3 ] ^ p_tmp1[ 2 ] ^ p_tmp2[ 0 ]) + + p_input[ p_table[ i ][ 2 ] ]; + p_tmp2[ 1 ] += p_tmp1[ 1 ] + p_drms_tab_taos[ x++ ]; + + p_tmp1[ 1 ] = ((p_tmp2[ 1 ] << 0x17) + | (p_tmp2[ 1 ] >> 0x9)) + p_tmp1[ 2 ]; + if( i == 3 ) + { + p_tmp2[ 1 ] = ((~p_tmp1[ 3 ] | p_tmp1[ 1 ]) ^ p_tmp1[ 2 ]) + + p_input[ p_table[ i ][ 3 ] ]; + } + else + { + p_tmp2[ 1 ] = (p_tmp1[ 3 ] ^ p_tmp1[ 2 ] ^ p_tmp1[ 1 ]) + + p_input[ p_table[ i ][ 3 ] ]; + } + p_tmp1[ 0 ] = p_tmp2[ 0 ] + p_tmp2[ 1 ] + p_drms_tab_taos[ x++ ]; + } + + for( i = 0; i < 4; i++ ) + { + uint8_t p_table[ 4 ][ 4 ] = + { + { 7, 14, 5, 12 }, + { 3, 10, 1, 8 }, + { 15, 6, 13, 4 }, + { 11, 2, 9, 0 } + }; + + p_tmp2[ 0 ] = ((p_tmp1[ 0 ] >> 0x1A) + | (p_tmp1[ 0 ] << 0x6)) + p_tmp1[ 1 ]; + p_tmp2[ 1 ] = ((~p_tmp1[ 2 ] | p_tmp2[ 0 ]) ^ p_tmp1[ 1 ]) + + p_input[ p_table[ i ][ 0 ] ]; + p_tmp2[ 1 ] += p_tmp1[ 3 ] + p_drms_tab_taos[ x++ ]; + + p_tmp1[ 3 ] = ((p_tmp2[ 1 ] >> 0x16) + | (p_tmp2[ 1 ] << 0xA)) + p_tmp2[ 0 ]; + p_tmp2[ 1 ] = ((~p_tmp1[ 1 ] | p_tmp1[ 3 ]) ^ p_tmp2[ 0 ]) + + p_input[ p_table[ i ][ 1 ] ]; + p_tmp2[ 1 ] += p_tmp1[ 2 ] + p_drms_tab_taos[ x++ ]; + + p_tmp1[ 2 ] = ((p_tmp2[ 1 ] >> 0x11) + | (p_tmp2[ 1 ] << 0xF)) + p_tmp1[ 3 ]; + p_tmp2[ 1 ] = ((~p_tmp2[ 0 ] | p_tmp1[ 2 ]) ^ p_tmp1[ 3 ]) + + p_input[ p_table[ i ][ 2 ] ]; + p_tmp2[ 1 ] += p_tmp1[ 1 ] + p_drms_tab_taos[ x++ ]; + + p_tmp1[ 1 ] = ((p_tmp2[ 1 ] << 0x15) + | (p_tmp2[ 1 ] >> 0xB)) + p_tmp1[ 2 ]; + + if( i < 3 ) + { + p_tmp2[ 1 ] = ((~p_tmp1[ 3 ] | p_tmp1[ 1 ]) ^ p_tmp1[ 2 ]) + + p_input[ p_table[ i ][ 3 ] ]; + p_tmp1[ 0 ] = p_tmp2[ 0 ] + p_tmp2[ 1 ] + p_drms_tab_taos[ x++ ]; + } + } + + p_buffer[ 0 ] += p_tmp2[ 0 ]; + p_buffer[ 1 ] += p_tmp1[ 1 ]; + p_buffer[ 2 ] += p_tmp1[ 2 ]; + p_buffer[ 3 ] += p_tmp1[ 3 ]; +} + +static void taos_add1( uint32_t *p_buffer, + uint8_t *p_in, uint32_t i_len ) +{ + uint32_t i; + uint32_t x, y; + uint32_t p_tmp[ 16 ]; + uint32_t i_offset = 0; + + x = p_buffer[ 6 ] & 63; + y = 64 - x; + + p_buffer[ 6 ] += i_len; + + if( i_len < y ) + { + memcpy( &((uint8_t *)p_buffer)[ 48 + x ], p_in, i_len ); + } + else + { + if( x ) + { + memcpy( &((uint8_t *)p_buffer)[ 48 + x ], p_in, y ); + taos( &p_buffer[ 8 ], &p_buffer[ 12 ] ); + i_offset = y; + i_len -= y; + } + + if( i_len >= 64 ) + { + for( i = 0; i < i_len / 64; i++ ) + { + memcpy( p_tmp, &p_in[ i_offset ], sizeof(p_tmp) ); + taos( &p_buffer[ 8 ], p_tmp ); + i_offset += 64; + i_len -= 64; + } + } + + if( i_len ) + { + memcpy( &p_buffer[ 12 ], &p_in[ i_offset ], i_len ); + } + } +} + +static void taos_end1( uint32_t *p_buffer, uint32_t *p_out ) +{ + uint32_t x, y; + + x = p_buffer[ 6 ] & 63; + y = 63 - x; + + ((uint8_t *)p_buffer)[ 48 + x++ ] = 128; + + if( y < 8 ) + { + memset( &((uint8_t *)p_buffer)[ 48 + x ], 0, y ); + taos( &p_buffer[ 8 ], &p_buffer[ 12 ] ); + y = 64; + x = 0; + } + + memset( &((uint8_t *)p_buffer)[ 48 + x ], 0, y ); + + p_buffer[ 26 ] = p_buffer[ 6 ] * 8; + p_buffer[ 27 ] = p_buffer[ 6 ] >> 29; + taos( &p_buffer[ 8 ], &p_buffer[ 12 ] ); + + memcpy( p_out, &p_buffer[ 8 ], sizeof(*p_out) * 4 ); +} + +static void taos_add2( uint32_t *p_buffer, uint8_t *p_in, uint32_t i_len ) +{ + uint32_t i, x; + uint32_t p_tmp[ 16 ]; + + x = (p_buffer[ 0 ] / 8) & 63; + i = p_buffer[ 0 ] + i_len * 8; + + if( i < p_buffer[ 0 ] ) + { + p_buffer[ 1 ] += 1; + } + + p_buffer[ 0 ] = i; + p_buffer[ 1 ] += i_len >> 29; + + for( i = 0; i < i_len; i++ ) + { + ((uint8_t *)p_buffer)[ 24 + x++ ] = p_in[ i ]; + + if( x != 64 ) + continue; + + memcpy( p_tmp, &p_buffer[ 6 ], sizeof(p_tmp) ); + taos( &p_buffer[ 2 ], p_tmp ); + } +} + +static void taos_add2e( uint32_t *p_buffer, uint32_t *p_in, uint32_t i_len ) +{ + uint32_t i, x, y; + uint32_t p_tmp[ 32 ]; + + if( i_len ) + { + for( x = i_len; x; x -= y ) + { + y = x > 32 ? 32 : x; + + for( i = 0; i < y; i++ ) + { + p_tmp[ i ] = U32_AT(&p_in[ i ]); + } + } + } + + taos_add2( p_buffer, (uint8_t *)p_tmp, i_len * sizeof(p_tmp[ 0 ]) ); +} + +static void taos_end2( uint32_t *p_buffer ) +{ + uint32_t x; + uint32_t p_tmp[ 16 ]; + + p_tmp[ 14 ] = p_buffer[ 0 ]; + p_tmp[ 15 ] = p_buffer[ 1 ]; + + x = (p_buffer[ 0 ] / 8) & 63; + + taos_add2( p_buffer, p_drms_tab_tend, 56 - x ); + memcpy( p_tmp, &p_buffer[ 6 ], 56 ); + taos( &p_buffer[ 2 ], p_tmp ); + memcpy( &p_buffer[ 22 ], &p_buffer[ 2 ], sizeof(*p_buffer) * 4 ); +} + +static void taos_add3( uint32_t *p_buffer, uint8_t *p_key, uint32_t i_len ) +{ + uint32_t x, y; + uint32_t i = 0; + + x = (p_buffer[ 4 ] / 8) & 63; + p_buffer[ 4 ] += i_len * 8; + + if( p_buffer[ 4 ] < i_len * 8 ) + p_buffer[ 5 ] += 1; + + p_buffer[ 5 ] += i_len >> 29; + + y = 64 - x; + + if( i_len >= y ) + { + memcpy( &((uint8_t *)p_buffer)[ 24 + x ], p_key, y ); + taos( p_buffer, &p_buffer[ 6 ] ); + + i = y; + y += 63; + + if( y < i_len ) + { + for( ; y < i_len; y += 64, i += 64 ) + { + taos( p_buffer, (uint32_t *)&p_key[y - 63] ); + } + } + else + { + x = 0; + } + } + + memcpy( &((uint8_t *)p_buffer)[ 24 + x ], &p_key[ i ], i_len - i ); +} + +static int taos_osi( uint32_t *p_buffer ) +{ + int i_ret = 0; + +#ifdef _WIN32 + HKEY i_key; + uint32_t i; + DWORD i_size; + DWORD i_serial; + LPBYTE p_reg_buf; + + static LPCTSTR p_reg_keys[ 3 ][ 2 ] = + { + { + _T("HARDWARE\\DESCRIPTION\\System"), + _T("SystemBiosVersion") + }, + + { + _T("HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"), + _T("ProcessorNameString") + }, + + { + _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion"), + _T("ProductId") + } + }; + + taos_add1( p_buffer, "cache-control", 13 ); + taos_add1( p_buffer, "Ethernet", 8 ); + + GetVolumeInformation( _T("C:\\"), NULL, 0, &i_serial, + NULL, NULL, NULL, 0 ); + taos_add1( p_buffer, (uint8_t *)&i_serial, 4 ); + + for( i = 0; i < sizeof(p_reg_keys)/sizeof(p_reg_keys[ 0 ]); i++ ) + { + if( RegOpenKeyEx( HKEY_LOCAL_MACHINE, p_reg_keys[ i ][ 0 ], + 0, KEY_READ, &i_key ) == ERROR_SUCCESS ) + { + if( RegQueryValueEx( i_key, p_reg_keys[ i ][ 1 ], + NULL, NULL, NULL, + &i_size ) == ERROR_SUCCESS ) + { + p_reg_buf = malloc( i_size ); + + if( p_reg_buf != NULL ) + { + if( RegQueryValueEx( i_key, p_reg_keys[ i ][ 1 ], + NULL, NULL, p_reg_buf, + &i_size ) == ERROR_SUCCESS ) + { + taos_add1( p_buffer, (uint8_t *)p_reg_buf, + i_size ); + } + + free( p_reg_buf ); + } + } + + RegCloseKey( i_key ); + } + } + +#else + i_ret = -1; +#endif + + return( i_ret ); +} + +static int get_sci_data( uint32_t p_sci[ 11 ][ 4 ] ) +{ + int i_ret = -1; + +#ifdef _WIN32 + HANDLE i_file; + DWORD i_size, i_read; + TCHAR p_path[ MAX_PATH ]; + TCHAR *p_filename = _T("\\Apple Computer\\iTunes\\SC Info\\SC Info.sidb"); + + typedef HRESULT (WINAPI *SHGETFOLDERPATH)( HWND, int, HANDLE, DWORD, + LPTSTR ); + + HINSTANCE shfolder_dll = NULL; + SHGETFOLDERPATH dSHGetFolderPath = NULL; + + if( ( shfolder_dll = LoadLibrary( _T("SHFolder.dll") ) ) != NULL ) + { + dSHGetFolderPath = + (SHGETFOLDERPATH)GetProcAddress( shfolder_dll, +#ifdef _UNICODE + _T("SHGetFolderPathW") ); +#else + _T("SHGetFolderPathA") ); +#endif + } + + if( dSHGetFolderPath != NULL && + SUCCEEDED( dSHGetFolderPath( NULL, /*CSIDL_COMMON_APPDATA*/ 0x0023, + NULL, 0, p_path ) ) ) + { + _tcsncat( p_path, p_filename, min( _tcslen( p_filename ), + (MAX_PATH-1) - _tcslen( p_path ) ) ); + + i_file = CreateFile( p_path, GENERIC_READ, 0, NULL, + OPEN_EXISTING, 0, NULL ); + if( i_file != INVALID_HANDLE_VALUE ) + { + i_read = sizeof(p_sci[ 0 ]) * 11; + i_size = GetFileSize( i_file, NULL ); + if( i_size != INVALID_FILE_SIZE && i_size >= i_read ) + { + i_size = SetFilePointer( i_file, 4, NULL, FILE_BEGIN ); + if( i_size != /*INVALID_SET_FILE_POINTER*/ ((DWORD)-1)) + { + if( ReadFile( i_file, p_sci, i_read, &i_size, NULL ) && + i_size == i_read ) + { + i_ret = 0; + } + } + } + + CloseHandle( i_file ); + } + } +#endif + + return( i_ret ); +} + +static void acei_taxs( uint32_t *p_acei, uint32_t i_val ) +{ + uint32_t i, x; + + i = (i_val / 16) & 15; + x = (~(i_val & 15)) & 15; + + if( (i_val & 768) == 768 ) + { + x = (~i) & 15; + i = i_val & 15; + + p_acei[ 25 + i ] = p_acei[ 25 + ((16 - x) & 15) ] + + p_acei[ 25 + (15 - x) ]; + } + else if( (i_val & 512) == 512 ) + { + p_acei[ 25 + i ] ^= p_drms_tab_xor[ 15 - i ][ x ]; + } + else if( (i_val & 256) == 256 ) + { + p_acei[ 25 + i ] -= p_drms_tab_sub[ 15 - i ][ x ]; + } + else + { + p_acei[ 25 + i ] += p_drms_tab_add[ 15 - i ][ x ]; + } +} + +static void acei( uint32_t *p_acei, uint8_t *p_buffer, uint32_t i_len ) +{ + uint32_t i, x; + uint32_t p_tmp[ 26 ]; + + for( i = 5; i < 25; i++ ) + { + if( p_acei[ i ] ) + { + acei_taxs( p_acei, p_acei[ i ] ); + } + } + + TAOS_INIT( p_tmp, 2 ); + taos_add2e( p_tmp, &p_acei[ 25 ], sizeof(*p_acei) * 4 ); + taos_end2( p_tmp ); + + x = i_len < 16 ? i_len : 16; + + if( x > 0 ) + { + for( i = 0; i < x; i++ ) + { + p_buffer[ i ] ^= ((uint8_t *)&p_tmp)[ 88 + i ]; + } + } +} + +static uint32_t ttov_calc( uint32_t *p_acei ) +{ + int32_t i_val; + uint32_t p_tmp[ 26 ]; + + TAOS_INIT( p_tmp, 2 ); + taos_add2e( p_tmp, &p_acei[ 0 ], 4 ); + taos_add2e( p_tmp, &p_acei[ 4 ], 1 ); + taos_end2( p_tmp ); + + p_acei[ 4 ]++; + + i_val = ((int32_t)U32_AT(&p_tmp[ 22 ])) % 1024; + + return( i_val < 0 ? i_val * -1 : i_val ); +} + +static void acei_init( uint32_t *p_acei, uint32_t *p_sys_key ) +{ + uint32_t i; + + for( i = 0; i < 4; i++ ) + { + p_acei[ i ] = U32_AT(&p_sys_key[ i ]); + } + + p_acei[ 4 ] = 0x5476212A; + + for( i = 5; i < 25; i++ ) + { + p_acei[ i ] = ttov_calc( p_acei ); + } + + p_acei[ 25 + 0 ] = p_acei[ 0 ]; + p_acei[ 25 + 1 ] = 0x68723876; + p_acei[ 25 + 2 ] = 0x41617376; + p_acei[ 25 + 3 ] = 0x4D4B4F76; + + p_acei[ 25 + 4 ] = p_acei[ 1 ]; + p_acei[ 25 + 5 ] = 0x48556646; + p_acei[ 25 + 6 ] = 0x38393725; + p_acei[ 25 + 7 ] = 0x2E3B5B3D; + + p_acei[ 25 + 8 ] = p_acei[ 2 ]; + p_acei[ 25 + 9 ] = 0x37363866; + p_acei[ 25 + 10 ] = 0x30383637; + p_acei[ 25 + 11 ] = 0x34333661; + + p_acei[ 25 + 12 ] = p_acei[ 3 ]; + p_acei[ 25 + 13 ] = 0x37386162; + p_acei[ 25 + 14 ] = 0x494F6E66; + p_acei[ 25 + 15 ] = 0x2A282966; +} + +static __inline void block_xor( uint32_t *p_in, uint32_t *p_key, + uint32_t *p_out ) +{ + uint32_t i; + + for( i = 0; i < 4; i++ ) + { + p_out[ i ] = p_key[ i ] ^ p_in[ i ]; + } +} + +int drms_get_sys_key( uint32_t *p_sys_key ) +{ + uint32_t p_tmp[ 128 ]; + uint32_t p_tmp_key[ 4 ]; + + TAOS_INIT( p_tmp, 8 ); + if( taos_osi( p_tmp ) ) + { + return( -1 ); + } + taos_end1( p_tmp, p_tmp_key ); + + TAOS_INIT( p_tmp, 2 ); + taos_add2( p_tmp, "YuaFlafu", 8 ); + taos_add2( p_tmp, (uint8_t *)p_tmp_key, 6 ); + taos_add2( p_tmp, (uint8_t *)p_tmp_key, 6 ); + taos_add2( p_tmp, (uint8_t *)p_tmp_key, 6 ); + taos_add2( p_tmp, "zPif98ga", 8 ); + taos_end2( p_tmp ); + + memcpy( p_sys_key, &p_tmp[ 2 ], sizeof(*p_sys_key) * 4 ); + + return( 0 ); +} + +int drms_get_user_key( uint32_t *p_sys_key, uint32_t *p_user_key ) +{ + uint32_t i; + uint32_t p_tmp[ 4 ]; + uint32_t *p_cur_key; + uint32_t p_acei[ 41 ]; + uint32_t p_ctx[ 128 ]; + uint32_t p_sci[ 2 ][ 11 ][ 4 ]; + + uint32_t p_sci_key[ 4 ] = + { + 0x6E66556D, 0x6E676F70, 0x67666461, 0x33373866 + }; + + if( p_sys_key == NULL ) + { + if( drms_get_sys_key( p_tmp ) ) + { + return( -1 ); + } + + p_sys_key = p_tmp; + } + + if( get_sci_data( p_sci[ 0 ] ) ) + { + return( -1 ); + } + + init_ctx( p_ctx, p_sys_key ); + + for( i = 0, p_cur_key = p_sci_key; + i < sizeof(p_sci[ 0 ])/sizeof(p_sci[ 0 ][ 0 ]); i++ ) + { + ctx_xor( p_ctx, &p_sci[ 0 ][ i ][ 0 ], &p_sci[ 1 ][ i ][ 0 ], + p_drms_tab3, p_drms_tab4 ); + block_xor( &p_sci[ 1 ][ i ][ 0 ], p_cur_key, &p_sci[ 1 ][ i ][ 0 ] ); + + p_cur_key = &p_sci[ 0 ][ i ][ 0 ]; + } + + acei_init( p_acei, p_sys_key ); + + for( i = 0; i < sizeof(p_sci[ 1 ])/sizeof(p_sci[ 1 ][ 0 ]); i++ ) + { + acei( p_acei, (uint8_t *)&p_sci[ 1 ][ i ][ 0 ], + sizeof(p_sci[ 1 ][ i ]) ); + } + + memcpy( p_user_key, &p_sci[ 1 ][ 10 ][ 0 ], sizeof(p_sci[ 1 ][ i ]) ); + + return( 0 ); +} + +struct drms_s +{ + uint8_t *p_iviv; + uint32_t i_iviv_len; + uint8_t *p_name; + uint32_t i_name_len; + + uint32_t *p_tmp; + uint32_t i_tmp_len; + + uint32_t p_key[ 4 ]; + uint32_t p_ctx[ 128 ]; +}; + +#define P_DRMS ((struct drms_s *)p_drms) + +void *drms_alloc() +{ + struct drms_s *p_drms; + + p_drms = malloc( sizeof(struct drms_s) ); + + if( p_drms != NULL ) + { + memset( p_drms, 0, sizeof(struct drms_s) ); + + p_drms->i_tmp_len = 1024; + p_drms->p_tmp = malloc( p_drms->i_tmp_len ); + if( p_drms->p_tmp == NULL ) + { + free( (void *)p_drms ); + p_drms = NULL; + } + } + + return( (void *)p_drms ); +} + +void drms_free( void *p_drms ) +{ + if( P_DRMS->p_name != NULL ) + { + free( (void *)P_DRMS->p_name ); + } + + if( P_DRMS->p_iviv != NULL ) + { + free( (void *)P_DRMS->p_iviv ); + } + + if( P_DRMS->p_tmp != NULL ) + { + free( (void *)P_DRMS->p_tmp ); + } + + free( p_drms ); +} + +void drms_decrypt( void *p_drms, uint32_t *p_buffer, uint32_t i_len ) +{ + uint32_t i, x, y; + uint32_t *p_cur_key = P_DRMS->p_key; + + x = (i_len / sizeof(P_DRMS->p_key)) * sizeof(P_DRMS->p_key); + + if( P_DRMS->i_tmp_len < x ) + { + free( (void *)P_DRMS->p_tmp ); + + P_DRMS->i_tmp_len = x; + P_DRMS->p_tmp = malloc( P_DRMS->i_tmp_len ); + } + + if( P_DRMS->p_tmp != NULL ) + { + memcpy( P_DRMS->p_tmp, p_buffer, x ); + + for( i = 0, x /= sizeof(P_DRMS->p_key); i < x; i++ ) + { + y = i * sizeof(*p_buffer); + + ctx_xor( P_DRMS->p_ctx, P_DRMS->p_tmp + y, p_buffer + y, + p_drms_tab3, p_drms_tab4 ); + block_xor( p_buffer + y, p_cur_key, p_buffer + y ); + + p_cur_key = P_DRMS->p_tmp + y; + } + } +} + +int drms_init( void *p_drms, uint32_t i_type, + uint8_t *p_info, uint32_t i_len ) +{ + int i_ret = 0; + + switch( i_type ) + { + case DRMS_INIT_UKEY: + { + if( i_len != sizeof(P_DRMS->p_key) ) + { + i_ret = -1; + break; + } + + init_ctx( P_DRMS->p_ctx, (uint32_t *)p_info ); + } + break; + + case DRMS_INIT_IVIV: + { + if( i_len != sizeof(P_DRMS->p_key) ) + { + i_ret = -1; + break; + } + + P_DRMS->p_iviv = malloc( i_len ); + if( P_DRMS->p_iviv == NULL ) + { + i_ret = -1; + break; + } + + memcpy( P_DRMS->p_iviv, p_info, i_len ); + P_DRMS->i_iviv_len = i_len; + } + break; + + case DRMS_INIT_NAME: + { + P_DRMS->p_name = malloc( i_len ); + if( P_DRMS->p_name == NULL ) + { + i_ret = -1; + break; + } + + memcpy( P_DRMS->p_name, p_info, i_len ); + P_DRMS->i_name_len = i_len; + } + break; + + case DRMS_INIT_PRIV: + { + uint32_t i; + uint32_t p_priv[ 64 ]; + uint32_t p_tmp[ 128 ]; + + if( i_len < 64 ) + { + i_ret = -1; + break; + } + + TAOS_INIT( p_tmp, 0 ); + taos_add3( p_tmp, P_DRMS->p_name, P_DRMS->i_name_len ); + taos_add3( p_tmp, P_DRMS->p_iviv, P_DRMS->i_iviv_len ); + memcpy( p_priv, &p_tmp[ 4 ], sizeof(p_priv[ 0 ]) * 2 ); + i = (p_tmp[ 4 ] / 8) & 63; + i = i >= 56 ? 120 - i : 56 - i; + taos_add3( p_tmp, p_drms_tab_tend, i ); + taos_add3( p_tmp, (uint8_t *)p_priv, sizeof(p_priv[ 0 ]) * 2 ); + + memcpy( p_priv, p_info, 64 ); + memcpy( P_DRMS->p_key, p_tmp, sizeof(P_DRMS->p_key) ); + drms_decrypt( p_drms, p_priv, sizeof(p_priv) ); + + init_ctx( P_DRMS->p_ctx, &p_priv[ 6 ] ); + memcpy( P_DRMS->p_key, &p_priv[ 12 ], sizeof(P_DRMS->p_key) ); + + free( (void *)P_DRMS->p_name ); + P_DRMS->p_name = NULL; + free( (void *)P_DRMS->p_iviv ); + P_DRMS->p_iviv = NULL; + } + break; + } + + return( i_ret ); +} + +#undef P_DRMS + +#endif + diff --git a/trunk/src/mp4ff/drms.h b/trunk/src/mp4ff/drms.h new file mode 100644 index 000000000..42d957400 --- /dev/null +++ b/trunk/src/mp4ff/drms.h @@ -0,0 +1,40 @@ +/***************************************************************************** + * drms.h : DRMS + ***************************************************************************** + * Copyright (C) 2004 VideoLAN + * $Id: drms.h,v 1.3 2004/01/11 15:52:18 menno Exp $ + * + * Author: Jon Lech Johansen <jon-vl@nanocrew.net> + * + * 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, USA. + *****************************************************************************/ + + +#define DRMS_INIT_UKEY 0 +#define DRMS_INIT_IVIV 1 +#define DRMS_INIT_NAME 2 +#define DRMS_INIT_PRIV 3 + +extern int drms_get_sys_key( uint32_t *p_sys_key ); +extern int drms_get_user_key( uint32_t *p_sys_key, + uint32_t *p_user_key ); + +extern void *drms_alloc(); +extern void drms_free( void *p_drms ); +extern int drms_init( void *p_drms, uint32_t i_type, + uint8_t *p_info, uint32_t i_len ); +extern void drms_decrypt( void *p_drms, uint32_t *p_buffer, + uint32_t i_len ); + diff --git a/trunk/src/mp4ff/drmstables.h b/trunk/src/mp4ff/drmstables.h new file mode 100644 index 000000000..e38c1f762 --- /dev/null +++ b/trunk/src/mp4ff/drmstables.h @@ -0,0 +1,449 @@ +/***************************************************************************** + * drmstables.h : DRMS tables + ***************************************************************************** + * Copyright (C) 2004 VideoLAN + * $Id: drmstables.h,v 1.2 2004/01/11 15:52:18 menno Exp $ + * + * Author: Jon Lech Johansen <jon-vl@nanocrew.net> + * + * 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, USA. + *****************************************************************************/ + + +static uint32_t p_drms_tab1[ 10 ] = +{ + 0x00000001, 0x00000002, 0x00000004, 0x00000008, 0x00000010, 0x00000020, + 0x00000040, 0x00000080, 0x0000001B, 0x00000036 +}; + +static uint32_t p_drms_tab2[ 256 ] = +{ + 0x63000000, 0x7C000000, 0x77000000, 0x7B000000, 0xF2000000, 0x6B000000, + 0x6F000000, 0xC5000000, 0x30000000, 0x01000000, 0x67000000, 0x2B000000, + 0xFE000000, 0xD7000000, 0xAB000000, 0x76000000, 0xCA000000, 0x82000000, + 0xC9000000, 0x7D000000, 0xFA000000, 0x59000000, 0x47000000, 0xF0000000, + 0xAD000000, 0xD4000000, 0xA2000000, 0xAF000000, 0x9C000000, 0xA4000000, + 0x72000000, 0xC0000000, 0xB7000000, 0xFD000000, 0x93000000, 0x26000000, + 0x36000000, 0x3F000000, 0xF7000000, 0xCC000000, 0x34000000, 0xA5000000, + 0xE5000000, 0xF1000000, 0x71000000, 0xD8000000, 0x31000000, 0x15000000, + 0x04000000, 0xC7000000, 0x23000000, 0xC3000000, 0x18000000, 0x96000000, + 0x05000000, 0x9A000000, 0x07000000, 0x12000000, 0x80000000, 0xE2000000, + 0xEB000000, 0x27000000, 0xB2000000, 0x75000000, 0x09000000, 0x83000000, + 0x2C000000, 0x1A000000, 0x1B000000, 0x6E000000, 0x5A000000, 0xA0000000, + 0x52000000, 0x3B000000, 0xD6000000, 0xB3000000, 0x29000000, 0xE3000000, + 0x2F000000, 0x84000000, 0x53000000, 0xD1000000, 0x00000000, 0xED000000, + 0x20000000, 0xFC000000, 0xB1000000, 0x5B000000, 0x6A000000, 0xCB000000, + 0xBE000000, 0x39000000, 0x4A000000, 0x4C000000, 0x58000000, 0xCF000000, + 0xD0000000, 0xEF000000, 0xAA000000, 0xFB000000, 0x43000000, 0x4D000000, + 0x33000000, 0x85000000, 0x45000000, 0xF9000000, 0x02000000, 0x7F000000, + 0x50000000, 0x3C000000, 0x9F000000, 0xA8000000, 0x51000000, 0xA3000000, + 0x40000000, 0x8F000000, 0x92000000, 0x9D000000, 0x38000000, 0xF5000000, + 0xBC000000, 0xB6000000, 0xDA000000, 0x21000000, 0x10000000, 0xFF000000, + 0xF3000000, 0xD2000000, 0xCD000000, 0x0C000000, 0x13000000, 0xEC000000, + 0x5F000000, 0x97000000, 0x44000000, 0x17000000, 0xC4000000, 0xA7000000, + 0x7E000000, 0x3D000000, 0x64000000, 0x5D000000, 0x19000000, 0x73000000, + 0x60000000, 0x81000000, 0x4F000000, 0xDC000000, 0x22000000, 0x2A000000, + 0x90000000, 0x88000000, 0x46000000, 0xEE000000, 0xB8000000, 0x14000000, + 0xDE000000, 0x5E000000, 0x0B000000, 0xDB000000, 0xE0000000, 0x32000000, + 0x3A000000, 0x0A000000, 0x49000000, 0x06000000, 0x24000000, 0x5C000000, + 0xC2000000, 0xD3000000, 0xAC000000, 0x62000000, 0x91000000, 0x95000000, + 0xE4000000, 0x79000000, 0xE7000000, 0xC8000000, 0x37000000, 0x6D000000, + 0x8D000000, 0xD5000000, 0x4E000000, 0xA9000000, 0x6C000000, 0x56000000, + 0xF4000000, 0xEA000000, 0x65000000, 0x7A000000, 0xAE000000, 0x08000000, + 0xBA000000, 0x78000000, 0x25000000, 0x2E000000, 0x1C000000, 0xA6000000, + 0xB4000000, 0xC6000000, 0xE8000000, 0xDD000000, 0x74000000, 0x1F000000, + 0x4B000000, 0xBD000000, 0x8B000000, 0x8A000000, 0x70000000, 0x3E000000, + 0xB5000000, 0x66000000, 0x48000000, 0x03000000, 0xF6000000, 0x0E000000, + 0x61000000, 0x35000000, 0x57000000, 0xB9000000, 0x86000000, 0xC1000000, + 0x1D000000, 0x9E000000, 0xE1000000, 0xF8000000, 0x98000000, 0x11000000, + 0x69000000, 0xD9000000, 0x8E000000, 0x94000000, 0x9B000000, 0x1E000000, + 0x87000000, 0xE9000000, 0xCE000000, 0x55000000, 0x28000000, 0xDF000000, + 0x8C000000, 0xA1000000, 0x89000000, 0x0D000000, 0xBF000000, 0xE6000000, + 0x42000000, 0x68000000, 0x41000000, 0x99000000, 0x2D000000, 0x0F000000, + 0xB0000000, 0x54000000, 0xBB000000, 0x16000000 +}; + +static uint32_t p_drms_tab3[ 256 ] = +{ + 0x5150A7F4, 0x7E536541, 0x1AC3A417, 0x3A965E27, 0x3BCB6BAB, 0x1FF1459D, + 0xACAB58FA, 0x4B9303E3, 0x2055FA30, 0xADF66D76, 0x889176CC, 0xF5254C02, + 0x4FFCD7E5, 0xC5D7CB2A, 0x26804435, 0xB58FA362, 0xDE495AB1, 0x25671BBA, + 0x45980EEA, 0x5DE1C0FE, 0xC302752F, 0x8112F04C, 0x8DA39746, 0x6BC6F9D3, + 0x03E75F8F, 0x15959C92, 0xBFEB7A6D, 0x95DA5952, 0xD42D83BE, 0x58D32174, + 0x492969E0, 0x8E44C8C9, 0x756A89C2, 0xF478798E, 0x996B3E58, 0x27DD71B9, + 0xBEB64FE1, 0xF017AD88, 0xC966AC20, 0x7DB43ACE, 0x63184ADF, 0xE582311A, + 0x97603351, 0x62457F53, 0xB1E07764, 0xBB84AE6B, 0xFE1CA081, 0xF9942B08, + 0x70586848, 0x8F19FD45, 0x94876CDE, 0x52B7F87B, 0xAB23D373, 0x72E2024B, + 0xE3578F1F, 0x662AAB55, 0xB20728EB, 0x2F03C2B5, 0x869A7BC5, 0xD3A50837, + 0x30F28728, 0x23B2A5BF, 0x02BA6A03, 0xED5C8216, 0x8A2B1CCF, 0xA792B479, + 0xF3F0F207, 0x4EA1E269, 0x65CDF4DA, 0x06D5BE05, 0xD11F6234, 0xC48AFEA6, + 0x349D532E, 0xA2A055F3, 0x0532E18A, 0xA475EBF6, 0x0B39EC83, 0x40AAEF60, + 0x5E069F71, 0xBD51106E, 0x3EF98A21, 0x963D06DD, 0xDDAE053E, 0x4D46BDE6, + 0x91B58D54, 0x71055DC4, 0x046FD406, 0x60FF1550, 0x1924FB98, 0xD697E9BD, + 0x89CC4340, 0x67779ED9, 0xB0BD42E8, 0x07888B89, 0xE7385B19, 0x79DBEEC8, + 0xA1470A7C, 0x7CE90F42, 0xF8C91E84, 0x00000000, 0x09838680, 0x3248ED2B, + 0x1EAC7011, 0x6C4E725A, 0xFDFBFF0E, 0x0F563885, 0x3D1ED5AE, 0x3627392D, + 0x0A64D90F, 0x6821A65C, 0x9BD1545B, 0x243A2E36, 0x0CB1670A, 0x930FE757, + 0xB4D296EE, 0x1B9E919B, 0x804FC5C0, 0x61A220DC, 0x5A694B77, 0x1C161A12, + 0xE20ABA93, 0xC0E52AA0, 0x3C43E022, 0x121D171B, 0x0E0B0D09, 0xF2ADC78B, + 0x2DB9A8B6, 0x14C8A91E, 0x578519F1, 0xAF4C0775, 0xEEBBDD99, 0xA3FD607F, + 0xF79F2601, 0x5CBCF572, 0x44C53B66, 0x5B347EFB, 0x8B762943, 0xCBDCC623, + 0xB668FCED, 0xB863F1E4, 0xD7CADC31, 0x42108563, 0x13402297, 0x842011C6, + 0x857D244A, 0xD2F83DBB, 0xAE1132F9, 0xC76DA129, 0x1D4B2F9E, 0xDCF330B2, + 0x0DEC5286, 0x77D0E3C1, 0x2B6C16B3, 0xA999B970, 0x11FA4894, 0x472264E9, + 0xA8C48CFC, 0xA01A3FF0, 0x56D82C7D, 0x22EF9033, 0x87C74E49, 0xD9C1D138, + 0x8CFEA2CA, 0x98360BD4, 0xA6CF81F5, 0xA528DE7A, 0xDA268EB7, 0x3FA4BFAD, + 0x2CE49D3A, 0x500D9278, 0x6A9BCC5F, 0x5462467E, 0xF6C2138D, 0x90E8B8D8, + 0x2E5EF739, 0x82F5AFC3, 0x9FBE805D, 0x697C93D0, 0x6FA92DD5, 0xCFB31225, + 0xC83B99AC, 0x10A77D18, 0xE86E639C, 0xDB7BBB3B, 0xCD097826, 0x6EF41859, + 0xEC01B79A, 0x83A89A4F, 0xE6656E95, 0xAA7EE6FF, 0x2108CFBC, 0xEFE6E815, + 0xBAD99BE7, 0x4ACE366F, 0xEAD4099F, 0x29D67CB0, 0x31AFB2A4, 0x2A31233F, + 0xC63094A5, 0x35C066A2, 0x7437BC4E, 0xFCA6CA82, 0xE0B0D090, 0x3315D8A7, + 0xF14A9804, 0x41F7DAEC, 0x7F0E50CD, 0x172FF691, 0x768DD64D, 0x434DB0EF, + 0xCC544DAA, 0xE4DF0496, 0x9EE3B5D1, 0x4C1B886A, 0xC1B81F2C, 0x467F5165, + 0x9D04EA5E, 0x015D358C, 0xFA737487, 0xFB2E410B, 0xB35A1D67, 0x9252D2DB, + 0xE9335610, 0x6D1347D6, 0x9A8C61D7, 0x377A0CA1, 0x598E14F8, 0xEB893C13, + 0xCEEE27A9, 0xB735C961, 0xE1EDE51C, 0x7A3CB147, 0x9C59DFD2, 0x553F73F2, + 0x1879CE14, 0x73BF37C7, 0x53EACDF7, 0x5F5BAAFD, 0xDF146F3D, 0x7886DB44, + 0xCA81F3AF, 0xB93EC468, 0x382C3424, 0xC25F40A3, 0x1672C31D, 0xBC0C25E2, + 0x288B493C, 0xFF41950D, 0x397101A8, 0x08DEB30C, 0xD89CE4B4, 0x6490C156, + 0x7B6184CB, 0xD570B632, 0x48745C6C, 0xD04257B8 +}; + +static uint32_t p_drms_tab4[ 256 ] = +{ + 0x52000000, 0x09000000, 0x6A000000, 0xD5000000, 0x30000000, 0x36000000, + 0xA5000000, 0x38000000, 0xBF000000, 0x40000000, 0xA3000000, 0x9E000000, + 0x81000000, 0xF3000000, 0xD7000000, 0xFB000000, 0x7C000000, 0xE3000000, + 0x39000000, 0x82000000, 0x9B000000, 0x2F000000, 0xFF000000, 0x87000000, + 0x34000000, 0x8E000000, 0x43000000, 0x44000000, 0xC4000000, 0xDE000000, + 0xE9000000, 0xCB000000, 0x54000000, 0x7B000000, 0x94000000, 0x32000000, + 0xA6000000, 0xC2000000, 0x23000000, 0x3D000000, 0xEE000000, 0x4C000000, + 0x95000000, 0x0B000000, 0x42000000, 0xFA000000, 0xC3000000, 0x4E000000, + 0x08000000, 0x2E000000, 0xA1000000, 0x66000000, 0x28000000, 0xD9000000, + 0x24000000, 0xB2000000, 0x76000000, 0x5B000000, 0xA2000000, 0x49000000, + 0x6D000000, 0x8B000000, 0xD1000000, 0x25000000, 0x72000000, 0xF8000000, + 0xF6000000, 0x64000000, 0x86000000, 0x68000000, 0x98000000, 0x16000000, + 0xD4000000, 0xA4000000, 0x5C000000, 0xCC000000, 0x5D000000, 0x65000000, + 0xB6000000, 0x92000000, 0x6C000000, 0x70000000, 0x48000000, 0x50000000, + 0xFD000000, 0xED000000, 0xB9000000, 0xDA000000, 0x5E000000, 0x15000000, + 0x46000000, 0x57000000, 0xA7000000, 0x8D000000, 0x9D000000, 0x84000000, + 0x90000000, 0xD8000000, 0xAB000000, 0x00000000, 0x8C000000, 0xBC000000, + 0xD3000000, 0x0A000000, 0xF7000000, 0xE4000000, 0x58000000, 0x05000000, + 0xB8000000, 0xB3000000, 0x45000000, 0x06000000, 0xD0000000, 0x2C000000, + 0x1E000000, 0x8F000000, 0xCA000000, 0x3F000000, 0x0F000000, 0x02000000, + 0xC1000000, 0xAF000000, 0xBD000000, 0x03000000, 0x01000000, 0x13000000, + 0x8A000000, 0x6B000000, 0x3A000000, 0x91000000, 0x11000000, 0x41000000, + 0x4F000000, 0x67000000, 0xDC000000, 0xEA000000, 0x97000000, 0xF2000000, + 0xCF000000, 0xCE000000, 0xF0000000, 0xB4000000, 0xE6000000, 0x73000000, + 0x96000000, 0xAC000000, 0x74000000, 0x22000000, 0xE7000000, 0xAD000000, + 0x35000000, 0x85000000, 0xE2000000, 0xF9000000, 0x37000000, 0xE8000000, + 0x1C000000, 0x75000000, 0xDF000000, 0x6E000000, 0x47000000, 0xF1000000, + 0x1A000000, 0x71000000, 0x1D000000, 0x29000000, 0xC5000000, 0x89000000, + 0x6F000000, 0xB7000000, 0x62000000, 0x0E000000, 0xAA000000, 0x18000000, + 0xBE000000, 0x1B000000, 0xFC000000, 0x56000000, 0x3E000000, 0x4B000000, + 0xC6000000, 0xD2000000, 0x79000000, 0x20000000, 0x9A000000, 0xDB000000, + 0xC0000000, 0xFE000000, 0x78000000, 0xCD000000, 0x5A000000, 0xF4000000, + 0x1F000000, 0xDD000000, 0xA8000000, 0x33000000, 0x88000000, 0x07000000, + 0xC7000000, 0x31000000, 0xB1000000, 0x12000000, 0x10000000, 0x59000000, + 0x27000000, 0x80000000, 0xEC000000, 0x5F000000, 0x60000000, 0x51000000, + 0x7F000000, 0xA9000000, 0x19000000, 0xB5000000, 0x4A000000, 0x0D000000, + 0x2D000000, 0xE5000000, 0x7A000000, 0x9F000000, 0x93000000, 0xC9000000, + 0x9C000000, 0xEF000000, 0xA0000000, 0xE0000000, 0x3B000000, 0x4D000000, + 0xAE000000, 0x2A000000, 0xF5000000, 0xB0000000, 0xC8000000, 0xEB000000, + 0xBB000000, 0x3C000000, 0x83000000, 0x53000000, 0x99000000, 0x61000000, + 0x17000000, 0x2B000000, 0x04000000, 0x7E000000, 0xBA000000, 0x77000000, + 0xD6000000, 0x26000000, 0xE1000000, 0x69000000, 0x14000000, 0x63000000, + 0x55000000, 0x21000000, 0x0C000000, 0x7D000000 +}; + +static int32_t p_drms_tab_taos[ 64 ] = +{ + -0x28955B88, -0x173848AA, +0x242070DB, -0x3E423112, -0x0A83F051, + +0x4787C62A, -0x57CFB9ED, -0x02B96AFF, +0x698098D8, -0x74BB0851, + -0x0000A44F, -0x76A32842, +0x6B901122, -0x02678E6D, -0x5986BC72, + +0x49B40821, -0x09E1DA9E, -0x3FBF4CC0, +0x265E5A51, -0x16493856, + -0x29D0EFA3, +0x02441453, -0x275E197F, -0x182C0438, +0x21E1CDE6, + -0x3CC8F82A, -0x0B2AF279, +0x455A14ED, -0x561C16FB, -0x03105C08, + +0x676F02D9, -0x72D5B376, -0x0005C6BE, -0x788E097F, +0x6D9D6122, + -0x021AC7F4, -0x5B4115BC, +0x4BDECFA9, -0x0944B4A0, -0x41404390, + +0x289B7EC6, -0x155ED806, -0x2B10CF7B, +0x04881D05, -0x262B2FC7, + -0x1924661B, +0x1FA27CF8, -0x3B53A99B, -0x0BD6DDBC, +0x432AFF97, + -0x546BDC59, -0x036C5FC7, +0x655B59C3, -0x70F3336E, -0x00100B83, + -0x7A7BA22F, +0x6FA87E4F, -0x01D31920, -0x5CFEBCEC, +0x4E0811A1, + -0x08AC817E, -0x42C50DCB, +0x2AD7D2BB, -0x14792C6F +}; + +static uint8_t p_drms_tab_tend[ 64 ] = +{ + 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static uint16_t p_drms_tab_xor[ 16 ][ 16 ] = +{ + { + 0x00D1, 0x0315, 0x1A32, 0x19EC, 0x1BBB, 0x1D6F, 0x14FE, 0x0E9E, + 0x029E, 0x1B8F, 0x0B70, 0x033A, 0x188E, 0x1D18, 0x0BD8, 0x0EDB + }, + + { + 0x0C64, 0x1C2B, 0x149C, 0x047B, 0x1064, 0x1C7C, 0x118D, 0x1355, + 0x0AE5, 0x0F18, 0x016F, 0x17D6, 0x1595, 0x0084, 0x0616, 0x1CCD + }, + + { + 0x1D94, 0x0618, 0x182C, 0x195B, 0x196D, 0x0394, 0x07DB, 0x0287, + 0x1636, 0x0B81, 0x1519, 0x0DF9, 0x1BA3, 0x1CC3, 0x0EE2, 0x1434 + }, + + { + 0x1457, 0x0CED, 0x0F7D, 0x0D7B, 0x0B9E, 0x0D13, 0x13D7, 0x18D0, + 0x1259, 0x1977, 0x0606, 0x1E80, 0x05F2, 0x06B8, 0x1F07, 0x1365 + }, + + { + 0x0334, 0x0E30, 0x195F, 0x15F1, 0x058E, 0x0AA8, 0x045A, 0x0465, + 0x0B3E, 0x071E, 0x0A36, 0x105C, 0x01AC, 0x1A1E, 0x04E4, 0x056B + }, + + { + 0x12BF, 0x0DA2, 0x0B41, 0x0EAF, 0x034F, 0x0181, 0x04E2, 0x002B, + 0x12E6, 0x01BE, 0x10E8, 0x128F, 0x0EB2, 0x1369, 0x05BE, 0x1A59 + }, + + { + 0x117E, 0x047C, 0x1E86, 0x056A, 0x0DA7, 0x0D61, 0x03FC, 0x1E6E, + 0x1D0C, 0x1E6D, 0x14BF, 0x0C50, 0x063A, 0x1B47, 0x17AE, 0x1321 + }, + + { + 0x041B, 0x0A24, 0x0D4D, 0x1F2B, 0x1CB6, 0x1BED, 0x1549, 0x03A7, + 0x0254, 0x006C, 0x0C9E, 0x0F73, 0x006C, 0x0008, 0x11F9, 0x0DD5 + }, + + { + 0x0BCF, 0x0AF9, 0x1DFE, 0x0341, 0x0E49, 0x0D38, 0x17CB, 0x1513, + 0x0E96, 0x00ED, 0x0556, 0x1B28, 0x100C, 0x19D8, 0x14FA, 0x028C + }, + + { + 0x1C60, 0x1232, 0x13D3, 0x0D00, 0x1534, 0x192C, 0x14B5, 0x1CF2, + 0x0504, 0x0B5B, 0x1ECF, 0x0423, 0x183B, 0x06B0, 0x169E, 0x1066 + }, + + { + 0x04CB, 0x08A2, 0x1B4A, 0x1254, 0x198D, 0x1044, 0x0236, 0x1BD8, + 0x18A1, 0x03FF, 0x1A0D, 0x0277, 0x0C2D, 0x17C9, 0x007C, 0x116E + }, + + { + 0x048A, 0x1EAF, 0x0922, 0x0C45, 0x0766, 0x1E5F, 0x1A28, 0x0120, + 0x1C15, 0x034C, 0x0508, 0x0E73, 0x0879, 0x0441, 0x09AE, 0x132F + }, + + { + 0x14FE, 0x0413, 0x0A9D, 0x1727, 0x01D7, 0x1A2B, 0x0474, 0x18F0, + 0x1F3B, 0x14F5, 0x1071, 0x0895, 0x1071, 0x18FF, 0x18E3, 0x0EB9 + }, + + { + 0x0BA9, 0x0961, 0x1599, 0x019E, 0x1D12, 0x1BAA, 0x1E94, 0x1921, + 0x14DC, 0x124E, 0x0A25, 0x03AB, 0x1CC0, 0x1EBB, 0x0B4B, 0x16E5 + }, + + { + 0x11EA, 0x0D78, 0x1BB3, 0x1BA7, 0x1510, 0x1B7B, 0x0C64, 0x1995, + 0x1A58, 0x1651, 0x1964, 0x147A, 0x15F2, 0x11BB, 0x1654, 0x166E + }, + + { + 0x0EA9, 0x1DE1, 0x1443, 0x13C5, 0x00E1, 0x0B2F, 0x0B6F, 0x0A37, + 0x18AC, 0x08E6, 0x06F0, 0x136E, 0x0853, 0x0B2E, 0x0813, 0x10D6 + } +}; + +static uint16_t p_drms_tab_sub[ 16 ][ 16 ] = +{ + { + 0x067A, 0x0C7D, 0x0B4F, 0x127D, 0x0BD6, 0x04AC, 0x16E0, 0x1730, + 0x0587, 0x0AFB, 0x1AC3, 0x0120, 0x14B5, 0x0F67, 0x11DE, 0x0961 + }, + + { + 0x1127, 0x1A68, 0x07F0, 0x17D0, 0x1A6F, 0x1F3B, 0x01EF, 0x0919, + 0x131E, 0x0F90, 0x19E9, 0x18A8, 0x0CB2, 0x1AD0, 0x0C66, 0x0378 + }, + + { + 0x03B0, 0x01BE, 0x1866, 0x1159, 0x197C, 0x1105, 0x010B, 0x0353, + 0x1ABB, 0x09A6, 0x028A, 0x1BAD, 0x1B20, 0x0455, 0x0F57, 0x0588 + }, + + { + 0x1491, 0x0A1D, 0x0F04, 0x0650, 0x191E, 0x1E0E, 0x174B, 0x016B, + 0x051F, 0x0532, 0x00DF, 0x1AEA, 0x0005, 0x0E1B, 0x0FF6, 0x08D8 + }, + + { + 0x14B4, 0x086A, 0x0C20, 0x0149, 0x1971, 0x0F26, 0x1852, 0x017D, + 0x1228, 0x0352, 0x0A44, 0x1330, 0x18DF, 0x1E38, 0x01BC, 0x0BAC + }, + + { + 0x1A48, 0x021F, 0x02F7, 0x0C31, 0x0BC4, 0x1E75, 0x105C, 0x13E3, + 0x0B20, 0x03A1, 0x1AF3, 0x1A36, 0x0E34, 0x181F, 0x09BD, 0x122B + }, + + { + 0x0EE0, 0x163B, 0x0BE7, 0x103D, 0x1075, 0x1E9D, 0x02AF, 0x0BA2, + 0x1DAA, 0x0CF1, 0x04B6, 0x0598, 0x06A1, 0x0D33, 0x1CFE, 0x04EE + }, + + { + 0x1BAD, 0x07C8, 0x1A48, 0x05E6, 0x031F, 0x0E0A, 0x0326, 0x1650, + 0x0526, 0x0B4E, 0x08FC, 0x0E4D, 0x0832, 0x06EA, 0x09BF, 0x0993 + }, + + { + 0x09EB, 0x0F31, 0x071B, 0x14D5, 0x11CA, 0x0722, 0x120D, 0x014C, + 0x1993, 0x0AE4, 0x1CCB, 0x04E9, 0x0AEE, 0x1708, 0x0C3D, 0x12F2 + }, + + { + 0x1A19, 0x07C1, 0x05A7, 0x0744, 0x1606, 0x1A9B, 0x042D, 0x1BFC, + 0x1841, 0x0C3C, 0x0FFE, 0x1AB1, 0x1416, 0x18A9, 0x0320, 0x1EC2 + }, + + { + 0x0AE7, 0x11C6, 0x124A, 0x11DF, 0x0F81, 0x06CF, 0x0ED9, 0x0253, + 0x1D2B, 0x0349, 0x0805, 0x08B3, 0x1052, 0x12CF, 0x0A44, 0x0EA6 + }, + + { + 0x03BF, 0x1D90, 0x0EF8, 0x0657, 0x156D, 0x0405, 0x10BE, 0x091F, + 0x1C82, 0x1725, 0x19EF, 0x0B8C, 0x04D9, 0x02C7, 0x025A, 0x1B89 + }, + + { + 0x0F5C, 0x013D, 0x02F7, 0x12E3, 0x0BC5, 0x1B56, 0x0848, 0x0239, + 0x0FCF, 0x03A4, 0x092D, 0x1354, 0x1D83, 0x01BD, 0x071A, 0x0AF1 + }, + + { + 0x0875, 0x0793, 0x1B41, 0x1782, 0x0DEF, 0x1D20, 0x13BE, 0x0095, + 0x1650, 0x19D4, 0x0DE3, 0x0980, 0x18F2, 0x0CA3, 0x0098, 0x149A + }, + + { + 0x0B81, 0x0AD2, 0x1BBA, 0x1A02, 0x027B, 0x1906, 0x07F5, 0x1CAE, + 0x0C3F, 0x02F6, 0x1298, 0x175E, 0x15B2, 0x13D8, 0x14CC, 0x161A + }, + + { + 0x0A42, 0x15F3, 0x0870, 0x1C1D, 0x1203, 0x18B1, 0x1738, 0x1954, + 0x1143, 0x1AE8, 0x1D9D, 0x155B, 0x11E8, 0x0ED9, 0x06F7, 0x04CA + } +}; + +static uint16_t p_drms_tab_add[ 16 ][ 16 ] = +{ + { + 0x0706, 0x175A, 0x0DEF, 0x1E72, 0x0297, 0x1B0E, 0x1D5A, 0x15B8, + 0x13E2, 0x1347, 0x10C6, 0x0B4F, 0x0629, 0x0A75, 0x0A9B, 0x0F55 + }, + + { + 0x1A69, 0x09BF, 0x0BA6, 0x1582, 0x1086, 0x1921, 0x01CB, 0x1C6A, + 0x0FF5, 0x00F7, 0x0A67, 0x0A1E, 0x1838, 0x0196, 0x10D6, 0x0C7A + }, + + { + 0x180E, 0x038D, 0x1ADD, 0x0684, 0x154A, 0x0AB0, 0x18A4, 0x0D73, + 0x1641, 0x0EC6, 0x09F1, 0x1A62, 0x0414, 0x162A, 0x194E, 0x1EC9 + }, + + { + 0x022F, 0x0296, 0x1104, 0x14FC, 0x096C, 0x1D02, 0x09BD, 0x027C, + 0x080E, 0x1324, 0x128C, 0x0DC1, 0x00B9, 0x17F2, 0x0CBC, 0x0F97 + }, + + { + 0x1B93, 0x1C3C, 0x0415, 0x0395, 0x0C7A, 0x06CC, 0x0D4B, 0x16E2, + 0x04A2, 0x0DAB, 0x1228, 0x012B, 0x0896, 0x0012, 0x1CD6, 0x1DAC + }, + + { + 0x080D, 0x0446, 0x047A, 0x00AD, 0x029E, 0x0686, 0x17C3, 0x1466, + 0x0D16, 0x1896, 0x076E, 0x00CD, 0x17DC, 0x1E9F, 0x1A7C, 0x02BB + }, + + { + 0x0D06, 0x112B, 0x14CB, 0x0A03, 0x1541, 0x1290, 0x0F6D, 0x1503, + 0x084B, 0x0382, 0x1A3F, 0x0371, 0x1977, 0x0B67, 0x0CAD, 0x1DF8 + }, + + { + 0x1CE3, 0x1306, 0x13F8, 0x1163, 0x1B0B, 0x00BD, 0x0BF0, 0x1A4F, + 0x16F7, 0x0B4F, 0x0CF8, 0x1254, 0x0541, 0x100D, 0x0296, 0x0410 + }, + + { + 0x1A2B, 0x1169, 0x17D9, 0x0819, 0x03D6, 0x0D03, 0x194D, 0x184A, + 0x07CA, 0x1989, 0x0FAD, 0x011C, 0x1C71, 0x0EF6, 0x0DC8, 0x0F2F + }, + + { + 0x0FA5, 0x11BE, 0x0F3B, 0x1D52, 0x0DE2, 0x016E, 0x1AD1, 0x0C4A, + 0x1BC2, 0x0AC9, 0x1485, 0x1BEE, 0x0949, 0x1A79, 0x1894, 0x12BB + }, + + { + 0x17B6, 0x14F5, 0x16B1, 0x142C, 0x1301, 0x03EF, 0x16FF, 0x0D37, + 0x0D78, 0x01FF, 0x00D6, 0x1053, 0x1A2A, 0x0F61, 0x1352, 0x0C7F + }, + + { + 0x137F, 0x09C4, 0x1D96, 0x021D, 0x1037, 0x1B19, 0x10EF, 0x14E4, + 0x02A0, 0x0236, 0x0A5D, 0x1519, 0x141C, 0x1399, 0x007E, 0x1E74 + }, + + { + 0x0941, 0x1B3C, 0x0062, 0x0371, 0x09AD, 0x08E8, 0x0A24, 0x0B97, + 0x1ED2, 0x0889, 0x136B, 0x0006, 0x1C4C, 0x0444, 0x06F8, 0x0DFB + }, + + { + 0x1D0F, 0x198D, 0x0700, 0x0AFC, 0x1781, 0x12F3, 0x10DA, 0x1F19, + 0x1055, 0x0DC9, 0x1860, 0x012B, 0x05BF, 0x082D, 0x0C17, 0x1941 + }, + + { + 0x0359, 0x1232, 0x104C, 0x0762, 0x0897, 0x1D6C, 0x030F, 0x1A36, + 0x16B0, 0x094D, 0x1782, 0x036F, 0x0EEA, 0x06E6, 0x0D00, 0x0187 + }, + + { + 0x17E2, 0x05E5, 0x19FA, 0x1950, 0x146A, 0x0B2A, 0x0512, 0x0EE0, + 0x1E27, 0x112D, 0x1DF0, 0x0B13, 0x0378, 0x1DD0, 0x00C1, 0x01E6 + } +}; + diff --git a/trunk/src/mp4ff/mp4atom.c b/trunk/src/mp4ff/mp4atom.c new file mode 100644 index 000000000..b75a3da7f --- /dev/null +++ b/trunk/src/mp4ff/mp4atom.c @@ -0,0 +1,781 @@ +/* +** FAAD2 - Freeware Advanced Audio (AAC) Decoder including SBR decoding +** Copyright (C) 2003-2004 M. Bakker, Ahead Software AG, http://www.nero.com +** +** 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. +** +** Any non-GPL usage of this software or parts of this software is strictly +** forbidden. +** +** Commercial non-GPL licensing of this software is possible. +** For more info contact Ahead Software through Mpeg4AAClicense@nero.com. +** +** $Id: mp4atom.c,v 1.17 2004/01/11 15:52:18 menno Exp $ +**/ + +#include <stdlib.h> +#include "mp4ffint.h" + +#include "drms.h" + +/* parse atom header size */ +static int32_t mp4ff_atom_get_size(const int8_t *data) +{ + uint32_t result; + uint32_t a, b, c, d; + + a = (uint8_t)data[0]; + b = (uint8_t)data[1]; + c = (uint8_t)data[2]; + d = (uint8_t)data[3]; + + result = (a<<24) | (b<<16) | (c<<8) | d; + //if (result > 0 && result < 8) result = 8; + + return (int32_t)result; +} + +/* comnapre 2 atom names, returns 1 for equal, 0 for unequal */ +static int32_t mp4ff_atom_compare(const int8_t a1, const int8_t b1, const int8_t c1, const int8_t d1, + const int8_t a2, const int8_t b2, const int8_t c2, const int8_t d2) +{ + if (a1 == a2 && b1 == b2 && c1 == c2 && d1 == d2) + return 1; + else + return 0; +} + +static uint8_t mp4ff_atom_name_to_type(const int8_t a, const int8_t b, + const int8_t c, const int8_t d) +{ + if (a == 'm') + { + if (mp4ff_atom_compare(a,b,c,d, 'm','o','o','v')) + return ATOM_MOOV; + else if (mp4ff_atom_compare(a,b,c,d, 'm','i','n','f')) + return ATOM_MINF; + else if (mp4ff_atom_compare(a,b,c,d, 'm','d','i','a')) + return ATOM_MDIA; + else if (mp4ff_atom_compare(a,b,c,d, 'm','d','a','t')) + return ATOM_MDAT; + else if (mp4ff_atom_compare(a,b,c,d, 'm','d','h','d')) + return ATOM_MDHD; + else if (mp4ff_atom_compare(a,b,c,d, 'm','v','h','d')) + return ATOM_MVHD; + else if (mp4ff_atom_compare(a,b,c,d, 'm','p','4','a')) + return ATOM_MP4A; + else if (mp4ff_atom_compare(a,b,c,d, 'm','p','4','v')) + return ATOM_MP4V; + else if (mp4ff_atom_compare(a,b,c,d, 'm','p','4','s')) + return ATOM_MP4S; + else if (mp4ff_atom_compare(a,b,c,d, 'm','e','t','a')) + return ATOM_META; + } else if (a == 't') { + if (mp4ff_atom_compare(a,b,c,d, 't','r','a','k')) + return ATOM_TRAK; + else if (mp4ff_atom_compare(a,b,c,d, 't','k','h','d')) + return ATOM_TKHD; + else if (mp4ff_atom_compare(a,b,c,d, 't','r','e','f')) + return ATOM_TREF; + else if (mp4ff_atom_compare(a,b,c,d, 't','r','k','n')) + return ATOM_TRACK; + else if (mp4ff_atom_compare(a,b,c,d, 't','m','p','o')) + return ATOM_TEMPO; + } else if (a == 's') { + if (mp4ff_atom_compare(a,b,c,d, 's','t','b','l')) + return ATOM_STBL; + else if (mp4ff_atom_compare(a,b,c,d, 's','m','h','d')) + return ATOM_SMHD; + else if (mp4ff_atom_compare(a,b,c,d, 's','t','s','d')) + return ATOM_STSD; + else if (mp4ff_atom_compare(a,b,c,d, 's','t','t','s')) + return ATOM_STTS; + else if (mp4ff_atom_compare(a,b,c,d, 's','t','c','o')) + return ATOM_STCO; + else if (mp4ff_atom_compare(a,b,c,d, 's','t','s','c')) + return ATOM_STSC; + else if (mp4ff_atom_compare(a,b,c,d, 's','t','s','z')) + return ATOM_STSZ; + else if (mp4ff_atom_compare(a,b,c,d, 's','t','z','2')) + return ATOM_STZ2; + else if (mp4ff_atom_compare(a,b,c,d, 's','k','i','p')) + return ATOM_SKIP; + else if (mp4ff_atom_compare(a,b,c,d, 's','i','n','f')) + return ATOM_SINF; + else if (mp4ff_atom_compare(a,b,c,d, 's','c','h','i')) + return ATOM_SCHI; + } else if (a == '©') { + if (mp4ff_atom_compare(a,b,c,d, '©','n','a','m')) + return ATOM_TITLE; + else if (mp4ff_atom_compare(a,b,c,d, '©','A','R','T')) + return ATOM_ARTIST; + else if (mp4ff_atom_compare(a,b,c,d, '©','w','r','t')) + return ATOM_WRITER; + else if (mp4ff_atom_compare(a,b,c,d, '©','a','l','b')) + return ATOM_ALBUM; + else if (mp4ff_atom_compare(a,b,c,d, '©','d','a','y')) + return ATOM_DATE; + else if (mp4ff_atom_compare(a,b,c,d, '©','t','o','o')) + return ATOM_TOOL; + else if (mp4ff_atom_compare(a,b,c,d, '©','c','m','t')) + return ATOM_COMMENT; + else if (mp4ff_atom_compare(a,b,c,d, '©','g','e','n')) + return ATOM_GENRE1; + } + + if (mp4ff_atom_compare(a,b,c,d, 'e','d','t','s')) + return ATOM_EDTS; + else if (mp4ff_atom_compare(a,b,c,d, 'e','s','d','s')) + return ATOM_ESDS; + else if (mp4ff_atom_compare(a,b,c,d, 'f','t','y','p')) + return ATOM_FTYP; + else if (mp4ff_atom_compare(a,b,c,d, 'f','r','e','e')) + return ATOM_FREE; + else if (mp4ff_atom_compare(a,b,c,d, 'h','m','h','d')) + return ATOM_HMHD; + else if (mp4ff_atom_compare(a,b,c,d, 'v','m','h','d')) + return ATOM_VMHD; + else if (mp4ff_atom_compare(a,b,c,d, 'u','d','t','a')) + return ATOM_UDTA; + else if (mp4ff_atom_compare(a,b,c,d, 'i','l','s','t')) + return ATOM_ILST; + else if (mp4ff_atom_compare(a,b,c,d, 'n','a','m','e')) + return ATOM_NAME; + else if (mp4ff_atom_compare(a,b,c,d, 'd','a','t','a')) + return ATOM_DATA; + else if (mp4ff_atom_compare(a,b,c,d, 'd','i','s','k')) + return ATOM_DISC; + else if (mp4ff_atom_compare(a,b,c,d, 'g','n','r','e')) + return ATOM_GENRE2; + else if (mp4ff_atom_compare(a,b,c,d, 'c','o','v','r')) + return ATOM_COVER; + else if (mp4ff_atom_compare(a,b,c,d, 'c','p','i','l')) + return ATOM_COMPILATION; + else if (mp4ff_atom_compare(a,b,c,d, 'c','t','t','s')) + return ATOM_CTTS; + else if (mp4ff_atom_compare(a,b,c,d, 'd','r','m','s')) + return ATOM_DRMS; + else if (mp4ff_atom_compare(a,b,c,d, 'f','r','m','a')) + return ATOM_FRMA; + else if (mp4ff_atom_compare(a,b,c,d, 'p','r','i','v')) + return ATOM_PRIV; + else if (mp4ff_atom_compare(a,b,c,d, 'i','v','i','v')) + return ATOM_IVIV; + else + return ATOM_UNKNOWN; +} + +/* read atom header, return atom size, atom size is with header included */ +uint64_t mp4ff_atom_read_header(mp4ff_t *f, uint8_t *atom_type, uint8_t *header_size) +{ + uint64_t size; + int32_t ret; + int8_t atom_header[8]; + + ret = mp4ff_read_data(f, atom_header, 8); + if (ret != 8) + return 0; + + size = mp4ff_atom_get_size(atom_header); + *header_size = 8; + + /* check for 64 bit atom size */ + if (size == 1) + { + *header_size = 16; + size = mp4ff_read_int64(f); + } + + //printf("%c%c%c%c\n", atom_header[4], atom_header[5], atom_header[6], atom_header[7]); + + *atom_type = mp4ff_atom_name_to_type(atom_header[4], atom_header[5], atom_header[6], atom_header[7]); + + return size; +} + +static int32_t mp4ff_read_stsz(mp4ff_t *f) +{ + mp4ff_read_char(f); /* version */ + mp4ff_read_int24(f); /* flags */ + f->track[f->total_tracks - 1]->stsz_sample_size = mp4ff_read_int32(f); + f->track[f->total_tracks - 1]->stsz_sample_count = mp4ff_read_int32(f); + + if (f->track[f->total_tracks - 1]->stsz_sample_size == 0) + { + int32_t i; + f->track[f->total_tracks - 1]->stsz_table = + (int32_t*)malloc(f->track[f->total_tracks - 1]->stsz_sample_count*sizeof(int32_t)); + + for (i = 0; i < f->track[f->total_tracks - 1]->stsz_sample_count; i++) + { + f->track[f->total_tracks - 1]->stsz_table[i] = mp4ff_read_int32(f); + } + } + + return 0; +} + +static int32_t mp4ff_read_esds(mp4ff_t *f) +{ + uint8_t tag; + uint32_t temp; + + mp4ff_read_char(f); /* version */ + mp4ff_read_int24(f); /* flags */ + + /* get and verify ES_DescrTag */ + tag = mp4ff_read_char(f); + if (tag == 0x03) + { + /* read length */ + if (mp4ff_read_mp4_descr_length(f) < 5 + 15) + { + return 1; + } + /* skip 3 bytes */ + mp4ff_read_int24(f); + } else { + /* skip 2 bytes */ + mp4ff_read_int16(f); + } + + /* get and verify DecoderConfigDescrTab */ + if (mp4ff_read_char(f) != 0x04) + { + return 1; + } + + /* read length */ + temp = mp4ff_read_mp4_descr_length(f); + if (temp < 13) return 1; + + f->track[f->total_tracks - 1]->audioType = mp4ff_read_char(f); + mp4ff_read_int32(f);//0x15000414 ???? + f->track[f->total_tracks - 1]->maxBitrate = mp4ff_read_int32(f); + f->track[f->total_tracks - 1]->avgBitrate = mp4ff_read_int32(f); + + /* get and verify DecSpecificInfoTag */ + if (mp4ff_read_char(f) != 0x05) + { + return 1; + } + + /* read length */ + f->track[f->total_tracks - 1]->decoderConfigLen = mp4ff_read_mp4_descr_length(f); + + if (f->track[f->total_tracks - 1]->decoderConfig) + free(f->track[f->total_tracks - 1]->decoderConfig); + f->track[f->total_tracks - 1]->decoderConfig = malloc(f->track[f->total_tracks - 1]->decoderConfigLen); + if (f->track[f->total_tracks - 1]->decoderConfig) + { + mp4ff_read_data(f, f->track[f->total_tracks - 1]->decoderConfig, f->track[f->total_tracks - 1]->decoderConfigLen); + } else { + f->track[f->total_tracks - 1]->decoderConfigLen = 0; + } + + /* will skip the remainder of the atom */ + return 0; +} + +static int32_t mp4ff_read_mp4a(mp4ff_t *f) +{ + uint64_t size; + int32_t i; + uint8_t atom_type = 0; + uint8_t header_size = 0; + + for (i = 0; i < 6; i++) + { + mp4ff_read_char(f); /* reserved */ + } + /* data_reference_index */ mp4ff_read_int16(f); + + mp4ff_read_int32(f); /* reserved */ + mp4ff_read_int32(f); /* reserved */ + + f->track[f->total_tracks - 1]->channelCount = mp4ff_read_int16(f); + f->track[f->total_tracks - 1]->sampleSize = mp4ff_read_int16(f); + + mp4ff_read_int16(f); + mp4ff_read_int16(f); + + f->track[f->total_tracks - 1]->sampleRate = mp4ff_read_int16(f); + + mp4ff_read_int16(f); + + size = mp4ff_atom_read_header(f, &atom_type, &header_size); + if (atom_type == ATOM_ESDS) + { + mp4ff_read_esds(f); + } + + return 0; +} + +#ifdef ITUNES_DRM +static int32_t mp4ff_read_drms(mp4ff_t *f, uint64_t skip) +{ + uint64_t size; + int32_t i; + uint8_t atom_type = 0; + uint8_t header_size = 0; + uint32_t drms_user_key[4]; + + if (drms_get_user_key(NULL, drms_user_key) == 0) + { + f->track[f->total_tracks - 1]->p_drms = drms_alloc(); + + drms_init( f->track[f->total_tracks - 1]->p_drms, + DRMS_INIT_UKEY, (uint8_t *)drms_user_key, + sizeof(drms_user_key) ); + } + + for (i = 0; i < 6; i++) + { + mp4ff_read_char(f); /* reserved */ + } + /* data_reference_index */ mp4ff_read_int16(f); + + mp4ff_read_int32(f); /* reserved */ + mp4ff_read_int32(f); /* reserved */ + + f->track[f->total_tracks - 1]->channelCount = mp4ff_read_int16(f); + f->track[f->total_tracks - 1]->sampleSize = mp4ff_read_int16(f); + + mp4ff_read_int16(f); + mp4ff_read_int16(f); + + f->track[f->total_tracks - 1]->sampleRate = mp4ff_read_int16(f); + + mp4ff_read_int16(f); + + size = mp4ff_atom_read_header(f, &atom_type, &header_size); + if (atom_type == ATOM_ESDS) + { + mp4ff_read_esds(f); + } + mp4ff_set_position(f, skip+size+28); + + size = mp4ff_atom_read_header(f, &atom_type, &header_size); + if (atom_type == ATOM_SINF) + { + parse_sub_atoms(f, size-header_size); + } + + return 0; +} + +static int32_t mp4ff_read_frma(mp4ff_t *f) +{ + uint8_t atom_type; + int8_t type[4]; + + mp4ff_read_data(f, type, 4); + + atom_type = mp4ff_atom_name_to_type(type[0], type[1], type[2], type[3]); + + if (atom_type == ATOM_MP4A) + { + f->track[f->total_tracks - 1]->type = TRACK_AUDIO; + } else if (atom_type == ATOM_MP4V) { + f->track[f->total_tracks - 1]->type = TRACK_VIDEO; + } else if (atom_type == ATOM_MP4S) { + f->track[f->total_tracks - 1]->type = TRACK_SYSTEM; + } else { + f->track[f->total_tracks - 1]->type = TRACK_UNKNOWN; + } + + return 0; +} + +static int32_t mp4ff_read_name(mp4ff_t *f, uint64_t size) +{ + uint8_t *data = malloc(size); + mp4ff_read_data(f, data, size); + + if (f->track[f->total_tracks - 1]->p_drms != NULL) + { + drms_init(f->track[f->total_tracks - 1]->p_drms, + DRMS_INIT_NAME, data, strlen(data) ); + } + + if (data) + free(data); + + return 0; +} + +static int32_t mp4ff_read_priv(mp4ff_t *f, uint64_t size) +{ + uint8_t *data = malloc(size); + mp4ff_read_data(f, data, size); + + if (f->track[f->total_tracks - 1]->p_drms != 0) + { + drms_init(f->track[f->total_tracks - 1]->p_drms, + DRMS_INIT_PRIV, data, size ); + } + + if (data) + free(data); + + return 0; +} + +static int32_t mp4ff_read_iviv(mp4ff_t *f, uint64_t size) +{ + uint8_t *data = malloc(size); + mp4ff_read_data(f, data, size); + + if (f->track[f->total_tracks - 1]->p_drms != 0) + { + drms_init(f->track[f->total_tracks - 1]->p_drms, + DRMS_INIT_IVIV, data, sizeof(uint32_t) * 4 ); + } + + if (data) + free(data); + + return 0; +} +#endif + +static int32_t mp4ff_read_stsd(mp4ff_t *f) +{ + int32_t i; + uint8_t header_size = 0; + + mp4ff_read_char(f); /* version */ + mp4ff_read_int24(f); /* flags */ + + f->track[f->total_tracks - 1]->stsd_entry_count = mp4ff_read_int32(f); + + for (i = 0; i < f->track[f->total_tracks - 1]->stsd_entry_count; i++) + { + uint64_t skip = mp4ff_position(f); + uint64_t size; + uint8_t atom_type = 0; + size = mp4ff_atom_read_header(f, &atom_type, &header_size); + skip += size; + + if (atom_type == ATOM_MP4A) + { + f->track[f->total_tracks - 1]->type = TRACK_AUDIO; + mp4ff_read_mp4a(f); + } else if (atom_type == ATOM_MP4V) { + f->track[f->total_tracks - 1]->type = TRACK_VIDEO; + } else if (atom_type == ATOM_MP4S) { + f->track[f->total_tracks - 1]->type = TRACK_SYSTEM; +#ifdef ITUNES_DRM + } else if (atom_type == ATOM_DRMS) { + // track type is read from the "frma" atom + f->track[f->total_tracks - 1]->type = TRACK_UNKNOWN; + mp4ff_read_drms(f, skip-size+header_size); +#endif + } else { + f->track[f->total_tracks - 1]->type = TRACK_UNKNOWN; + } + + mp4ff_set_position(f, skip); + } + + return 0; +} + +static int32_t mp4ff_read_stsc(mp4ff_t *f) +{ + int32_t i; + + mp4ff_read_char(f); /* version */ + mp4ff_read_int24(f); /* flags */ + f->track[f->total_tracks - 1]->stsc_entry_count = mp4ff_read_int32(f); + + f->track[f->total_tracks - 1]->stsc_first_chunk = + (int32_t*)malloc(f->track[f->total_tracks - 1]->stsc_entry_count*sizeof(int32_t)); + f->track[f->total_tracks - 1]->stsc_samples_per_chunk = + (int32_t*)malloc(f->track[f->total_tracks - 1]->stsc_entry_count*sizeof(int32_t)); + f->track[f->total_tracks - 1]->stsc_sample_desc_index = + (int32_t*)malloc(f->track[f->total_tracks - 1]->stsc_entry_count*sizeof(int32_t)); + + for (i = 0; i < f->track[f->total_tracks - 1]->stsc_entry_count; i++) + { + f->track[f->total_tracks - 1]->stsc_first_chunk[i] = mp4ff_read_int32(f); + f->track[f->total_tracks - 1]->stsc_samples_per_chunk[i] = mp4ff_read_int32(f); + f->track[f->total_tracks - 1]->stsc_sample_desc_index[i] = mp4ff_read_int32(f); + } + + return 0; +} + +static int32_t mp4ff_read_stco(mp4ff_t *f) +{ + int32_t i; + + mp4ff_read_char(f); /* version */ + mp4ff_read_int24(f); /* flags */ + f->track[f->total_tracks - 1]->stco_entry_count = mp4ff_read_int32(f); + + f->track[f->total_tracks - 1]->stco_chunk_offset = + (int32_t*)malloc(f->track[f->total_tracks - 1]->stco_entry_count*sizeof(int32_t)); + + for (i = 0; i < f->track[f->total_tracks - 1]->stco_entry_count; i++) + { + f->track[f->total_tracks - 1]->stco_chunk_offset[i] = mp4ff_read_int32(f); + } + + return 0; +} + +static int32_t mp4ff_read_ctts(mp4ff_t *f) +{ + int32_t i; + mp4ff_track_t * p_track = f->track[f->total_tracks - 1]; + + if (p_track->ctts_entry_count) return 0; + + mp4ff_read_char(f); /* version */ + mp4ff_read_int24(f); /* flags */ + p_track->ctts_entry_count = mp4ff_read_int32(f); + + p_track->ctts_sample_count = (int32_t*)malloc(p_track->ctts_entry_count * sizeof(int32_t)); + p_track->ctts_sample_offset = (int32_t*)malloc(p_track->ctts_entry_count * sizeof(int32_t)); + + if (p_track->ctts_sample_count == 0 || p_track->ctts_sample_offset == 0) + { + if (p_track->ctts_sample_count) {free(p_track->ctts_sample_count);p_track->ctts_sample_count=0;} + if (p_track->ctts_sample_offset) {free(p_track->ctts_sample_offset);p_track->ctts_sample_offset=0;} + p_track->ctts_entry_count = 0; + return 0; + } + else + { + for (i = 0; i < f->track[f->total_tracks - 1]->ctts_entry_count; i++) + { + p_track->ctts_sample_count[i] = mp4ff_read_int32(f); + p_track->ctts_sample_offset[i] = mp4ff_read_int32(f); + } + return 1; + } +} + +static int32_t mp4ff_read_stts(mp4ff_t *f) +{ + int32_t i; + mp4ff_track_t * p_track = f->track[f->total_tracks - 1]; + + if (p_track->stts_entry_count) return 0; + + mp4ff_read_char(f); /* version */ + mp4ff_read_int24(f); /* flags */ + p_track->stts_entry_count = mp4ff_read_int32(f); + + p_track->stts_sample_count = (int32_t*)malloc(p_track->stts_entry_count * sizeof(int32_t)); + p_track->stts_sample_delta = (int32_t*)malloc(p_track->stts_entry_count * sizeof(int32_t)); + + if (p_track->stts_sample_count == 0 || p_track->stts_sample_delta == 0) + { + if (p_track->stts_sample_count) {free(p_track->stts_sample_count);p_track->stts_sample_count=0;} + if (p_track->stts_sample_delta) {free(p_track->stts_sample_delta);p_track->stts_sample_delta=0;} + p_track->stts_entry_count = 0; + return 0; + } + else + { + for (i = 0; i < f->track[f->total_tracks - 1]->stts_entry_count; i++) + { + p_track->stts_sample_count[i] = mp4ff_read_int32(f); + p_track->stts_sample_delta[i] = mp4ff_read_int32(f); + } + return 1; + } +} + +static int32_t mp4ff_read_mvhd(mp4ff_t *f) +{ + int32_t i; + + mp4ff_read_char(f); /* version */ + mp4ff_read_int24(f); /* flags */ + /* creation_time */ mp4ff_read_int32(f); + /* modification_time */ mp4ff_read_int32(f); + f->time_scale = mp4ff_read_int32(f); + f->duration = mp4ff_read_int32(f); + /* preferred_rate */ mp4ff_read_int32(f); /*mp4ff_read_fixed32(f);*/ + /* preferred_volume */ mp4ff_read_int16(f); /*mp4ff_read_fixed16(f);*/ + for (i = 0; i < 10; i++) + { + /* reserved */ mp4ff_read_char(f); + } + for (i = 0; i < 9; i++) + { + mp4ff_read_int32(f); /* matrix */ + } + /* preview_time */ mp4ff_read_int32(f); + /* preview_duration */ mp4ff_read_int32(f); + /* poster_time */ mp4ff_read_int32(f); + /* selection_time */ mp4ff_read_int32(f); + /* selection_duration */ mp4ff_read_int32(f); + /* current_time */ mp4ff_read_int32(f); + /* next_track_id */ mp4ff_read_int32(f); + + return 0; +} + +#if 0 +static int32_t mp4ff_read_tkhd(mp4ff_t *f) +{ + uint8_t version; + uint32_t flags; + version = mp4ff_read_char(f); /* version */ + flags = mp4ff_read_int24(f); /* flags */ + if (version==1) + { + mp4ff_read_int64(f);//creation-time + mp4ff_read_int64(f);//modification-time + mp4ff_read_int32(f);//track-id + mp4ff_read_int32(f);//reserved + f->track[f->total_tracks - 1]->duration = mp4ff_read_int64(f);//duration + } + else //version == 0 + { + mp4ff_read_int32(f);//creation-time + mp4ff_read_int32(f);//modification-time + mp4ff_read_int32(f);//track-id + mp4ff_read_int32(f);//reserved + f->track[f->total_tracks - 1]->duration = mp4ff_read_int32(f);//duration + if (f->track[f->total_tracks - 1]->duration == 0xFFFFFFFF) + f->track[f->total_tracks - 1]->duration = 0xFFFFFFFFFFFFFFFF; + + } + mp4ff_read_int32(f);//reserved + mp4ff_read_int32(f);//reserved + mp4ff_read_int16(f);//layer + mp4ff_read_int16(f);//pre-defined + mp4ff_read_int16(f);//volume + mp4ff_read_int16(f);//reserved + + //matrix + mp4ff_read_int32(f); mp4ff_read_int32(f); mp4ff_read_int32(f); + mp4ff_read_int32(f); mp4ff_read_int32(f); mp4ff_read_int32(f); + mp4ff_read_int32(f); mp4ff_read_int32(f); mp4ff_read_int32(f); + mp4ff_read_int32(f);//width + mp4ff_read_int32(f);//height + return 1; +} +#endif + +static int32_t mp4ff_read_mdhd(mp4ff_t *f) +{ + uint32_t version; + + version = mp4ff_read_int32(f); + if (version==1) + { + mp4ff_read_int64(f);//creation-time + mp4ff_read_int64(f);//modification-time + f->track[f->total_tracks - 1]->timeScale = mp4ff_read_int32(f);//timescale + f->track[f->total_tracks - 1]->duration = mp4ff_read_int64(f);//duration + } + else //version == 0 + { + uint32_t temp; + + mp4ff_read_int32(f);//creation-time + mp4ff_read_int32(f);//modification-time + f->track[f->total_tracks - 1]->timeScale = mp4ff_read_int32(f);//timescale + temp = mp4ff_read_int32(f); + f->track[f->total_tracks - 1]->duration = (temp == (uint32_t)(-1)) ? (uint64_t)(-1) : (uint64_t)(temp); + } + mp4ff_read_int16(f); + mp4ff_read_int16(f); + return 1; +} +#ifdef USE_TAGGING +static int32_t mp4ff_read_meta(mp4ff_t *f, const uint64_t size) +{ + uint64_t subsize, sumsize = 0; + uint8_t atom_type; + uint8_t header_size = 0; + + mp4ff_read_char(f); /* version */ + mp4ff_read_int24(f); /* flags */ + + while (sumsize < (size-12)) + { + subsize = mp4ff_atom_read_header(f, &atom_type, &header_size); + if (atom_type == ATOM_ILST) + { + mp4ff_parse_metadata(f, (uint32_t)(subsize-(header_size+4))); + } else { + mp4ff_set_position(f, mp4ff_position(f)+subsize-header_size); + } + sumsize += subsize; + } + + return 0; +} +#endif + +int32_t mp4ff_atom_read(mp4ff_t *f, const int32_t size, const uint8_t atom_type) +{ + uint64_t dest_position = mp4ff_position(f)+size-8; + if (atom_type == ATOM_STSZ) + { + /* sample size box */ + mp4ff_read_stsz(f); + } else if (atom_type == ATOM_STTS) { + /* time to sample box */ + mp4ff_read_stts(f); + } else if (atom_type == ATOM_CTTS) { + /* composition offset box */ + mp4ff_read_ctts(f); + } else if (atom_type == ATOM_STSC) { + /* sample to chunk box */ + mp4ff_read_stsc(f); + } else if (atom_type == ATOM_STCO) { + /* chunk offset box */ + mp4ff_read_stco(f); + } else if (atom_type == ATOM_STSD) { + /* sample description box */ + mp4ff_read_stsd(f); + } else if (atom_type == ATOM_MVHD) { + /* movie header box */ + mp4ff_read_mvhd(f); + } else if (atom_type == ATOM_MDHD) { + /* track header */ + mp4ff_read_mdhd(f); +#ifdef ITUNES_DRM + } else if (atom_type == ATOM_FRMA) { + /* DRM track format */ + mp4ff_read_frma(f); + } else if (atom_type == ATOM_IVIV) { + mp4ff_read_iviv(f, size-8); + } else if (atom_type == ATOM_NAME) { + mp4ff_read_name(f, size-8); + } else if (atom_type == ATOM_PRIV) { + mp4ff_read_priv(f, size-8); +#endif +#ifdef USE_TAGGING + } else if (atom_type == ATOM_META) { + /* iTunes Metadata box */ + mp4ff_read_meta(f, size); +#endif + } + + mp4ff_set_position(f, dest_position); + + + return 0; +} diff --git a/trunk/src/mp4ff/mp4ff.c b/trunk/src/mp4ff/mp4ff.c new file mode 100644 index 000000000..e0bb781e8 --- /dev/null +++ b/trunk/src/mp4ff/mp4ff.c @@ -0,0 +1,430 @@ +/* +** FAAD2 - Freeware Advanced Audio (AAC) Decoder including SBR decoding +** Copyright (C) 2003-2004 M. Bakker, Ahead Software AG, http://www.nero.com +** +** 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. +** +** Any non-GPL usage of this software or parts of this software is strictly +** forbidden. +** +** Commercial non-GPL licensing of this software is possible. +** For more info contact Ahead Software through Mpeg4AAClicense@nero.com. +** +** $Id: mp4ff.c,v 1.15 2004/01/11 15:52:18 menno Exp $ +**/ + +#include <stdlib.h> +#include <string.h> +#include "mp4ffint.h" + +#include "drms.h" + +int64_t mp4ff_get_track_duration_use_offsets(const mp4ff_t *f, const int32_t track); + +mp4ff_t *mp4ff_open_read(mp4ff_callback_t *f) +{ + mp4ff_t *ff = malloc(sizeof(mp4ff_t)); + + memset(ff, 0, sizeof(mp4ff_t)); + + ff->stream = f; + + parse_atoms(ff); + + return ff; +} + +void mp4ff_close(mp4ff_t *ff) +{ + int32_t i; + + for (i = 0; i < ff->total_tracks; i++) + { + if (ff->track[i]) + { + if (ff->track[i]->stsz_table) + free(ff->track[i]->stsz_table); + if (ff->track[i]->stts_sample_count) + free(ff->track[i]->stts_sample_count); + if (ff->track[i]->stts_sample_delta) + free(ff->track[i]->stts_sample_delta); + if (ff->track[i]->stsc_first_chunk) + free(ff->track[i]->stsc_first_chunk); + if (ff->track[i]->stsc_samples_per_chunk) + free(ff->track[i]->stsc_samples_per_chunk); + if (ff->track[i]->stsc_sample_desc_index) + free(ff->track[i]->stsc_sample_desc_index); + if (ff->track[i]->stco_chunk_offset) + free(ff->track[i]->stco_chunk_offset); + if (ff->track[i]->decoderConfig) + free(ff->track[i]->decoderConfig); + if (ff->track[i]->ctts_sample_count) + free(ff->track[i]->ctts_sample_count); + if (ff->track[i]->ctts_sample_offset) + free(ff->track[i]->ctts_sample_offset); +#ifdef ITUNES_DRM + if (ff->track[i]->p_drms) + drms_free(ff->track[i]->p_drms); +#endif + free(ff->track[i]); + } + } + +#ifdef USE_TAGGING + mp4ff_tag_delete(&(ff->tags)); +#endif + + if (ff) free(ff); +} + +static void mp4ff_track_add(mp4ff_t *f) +{ + f->total_tracks++; + + f->track[f->total_tracks - 1] = malloc(sizeof(mp4ff_track_t)); + + memset(f->track[f->total_tracks - 1], 0, sizeof(mp4ff_track_t)); +} + +/* parse atoms that are sub atoms of other atoms */ +int32_t parse_sub_atoms(mp4ff_t *f, const uint64_t total_size) +{ + uint64_t size; + uint8_t atom_type = 0; + uint64_t counted_size = 0; + uint8_t header_size = 0; + + while (counted_size < total_size) + { + size = mp4ff_atom_read_header(f, &atom_type, &header_size); + counted_size += size; + + /* check for end of file */ + if (size == 0) + break; + + /* we're starting to read a new track, update index, + * so that all data and tables get written in the right place + */ + if (atom_type == ATOM_TRAK) + { + mp4ff_track_add(f); + } + + /* parse subatoms */ + if (atom_type < SUBATOMIC) + { + parse_sub_atoms(f, size-header_size); + } else { + mp4ff_atom_read(f, (uint32_t)size, atom_type); + } + } + + return 0; +} + +/* parse root atoms */ +int32_t parse_atoms(mp4ff_t *f) +{ + uint64_t size; + uint8_t atom_type = 0; + uint8_t header_size = 0; + + f->file_size = 0; + + while ((size = mp4ff_atom_read_header(f, &atom_type, &header_size)) != 0) + { + f->file_size += size; + f->last_atom = atom_type; + + if (atom_type == ATOM_MDAT && f->moov_read) + { + /* moov atom is before mdat, we can stop reading when mdat is encountered */ + /* file position will stay at beginning of mdat data */ +// break; + } + + if (atom_type == ATOM_MOOV && size > header_size) + { + f->moov_read = 1; + f->moov_offset = mp4ff_position(f)-header_size; + f->moov_size = size; + } + + /* parse subatoms */ + if (atom_type < SUBATOMIC) + { + parse_sub_atoms(f, size-header_size); + } else { + /* skip this atom */ + mp4ff_set_position(f, mp4ff_position(f)+size-header_size); + } + } + + return 0; +} + +int32_t mp4ff_get_decoder_config(const mp4ff_t *f, const int32_t track, + uint8_t** ppBuf, uint32_t* pBufSize) +{ + if (track >= f->total_tracks) + { + *ppBuf = NULL; + *pBufSize = 0; + return 1; + } + + if (f->track[track]->decoderConfig == NULL || f->track[track]->decoderConfigLen == 0) + { + *ppBuf = NULL; + *pBufSize = 0; + } else { + *ppBuf = malloc(f->track[track]->decoderConfigLen); + if (*ppBuf == NULL) + { + *pBufSize = 0; + return 1; + } + memcpy(*ppBuf, f->track[track]->decoderConfig, f->track[track]->decoderConfigLen); + *pBufSize = f->track[track]->decoderConfigLen; + } + + return 0; +} + +static int32_t mp4ff_get_track_type(const mp4ff_t *f, const int track) +{ + return f->track[track]->type; +} + +int32_t mp4ff_total_tracks(const mp4ff_t *f) +{ + return f->total_tracks; +} + +int32_t mp4ff_time_scale(const mp4ff_t *f, const int32_t track) +{ + return f->track[track]->timeScale; +} + +static uint32_t mp4ff_get_avg_bitrate(const mp4ff_t *f, const int32_t track) +{ + return f->track[track]->avgBitrate; +} + +static uint32_t mp4ff_get_max_bitrate(const mp4ff_t *f, const int32_t track) +{ + return f->track[track]->maxBitrate; +} + +static int64_t mp4ff_get_track_duration(const mp4ff_t *f, const int32_t track) +{ + return f->track[track]->duration; +} + +int64_t mp4ff_get_track_duration_use_offsets(const mp4ff_t *f, const int32_t track) +{ + int64_t duration = mp4ff_get_track_duration(f,track); + if (duration!=-1) + { + int64_t offset = mp4ff_get_sample_offset(f,track,0); + if (offset > duration) duration = 0; + else duration -= offset; + } + return duration; +} + + +int32_t mp4ff_num_samples(const mp4ff_t *f, const int32_t track) +{ + int32_t i; + int32_t total = 0; + + for (i = 0; i < f->track[track]->stts_entry_count; i++) + { + total += f->track[track]->stts_sample_count[i]; + } + return total; +} + + + + +static uint32_t mp4ff_get_sample_rate(const mp4ff_t *f, const int32_t track) +{ + return f->track[track]->sampleRate; +} + +static uint32_t mp4ff_get_channel_count(const mp4ff_t * f,const int32_t track) +{ + return f->track[track]->channelCount; +} + +static uint32_t mp4ff_get_audio_type(const mp4ff_t * f,const int32_t track) +{ + return f->track[track]->audioType; +} + +static int32_t mp4ff_get_sample_duration_use_offsets(const mp4ff_t *f, const int32_t track, const int32_t sample) +{ + int32_t d,o; + d = mp4ff_get_sample_duration(f,track,sample); + if (d!=-1) + { + o = mp4ff_get_sample_offset(f,track,sample); + if (o>d) d = 0; + else d -= o; + } + return d; +} + +int32_t mp4ff_get_sample_duration(const mp4ff_t *f, const int32_t track, const int32_t sample) +{ + int32_t i, co = 0; + + for (i = 0; i < f->track[track]->stts_entry_count; i++) + { + int32_t delta = f->track[track]->stts_sample_count[i]; + if (sample < co + delta) + return f->track[track]->stts_sample_delta[i]; + co += delta; + } + return (int32_t)(-1); +} + +int64_t mp4ff_get_sample_position(const mp4ff_t *f, const int32_t track, const int32_t sample) +{ + int32_t i, co = 0; + int64_t acc = 0; + + for (i = 0; i < f->track[track]->stts_entry_count; i++) + { + int32_t delta = f->track[track]->stts_sample_count[i]; + if (sample < co + delta) + { + acc += f->track[track]->stts_sample_delta[i] * (sample - co); + return acc; + } + else + { + acc += f->track[track]->stts_sample_delta[i] * delta; + } + co += delta; + } + return (int64_t)(-1); +} + +int32_t mp4ff_get_sample_offset(const mp4ff_t *f, const int32_t track, const int32_t sample) +{ + int32_t i, co = 0; + + for (i = 0; i < f->track[track]->ctts_entry_count; i++) + { + int32_t delta = f->track[track]->ctts_sample_count[i]; + if (sample < co + delta) + return f->track[track]->ctts_sample_offset[i]; + co += delta; + } + return 0; +} + +int32_t mp4ff_find_sample(const mp4ff_t *f, const int32_t track, const int64_t offset,int32_t * toskip) +{ + int32_t i, co = 0; + int64_t offset_total = 0; + mp4ff_track_t * p_track = f->track[track]; + + for (i = 0; i < p_track->stts_entry_count; i++) + { + int32_t sample_count = p_track->stts_sample_count[i]; + int32_t sample_delta = p_track->stts_sample_delta[i]; + int64_t offset_delta = (int64_t)sample_delta * (int64_t)sample_count; + if (offset < offset_total + offset_delta) + { + int64_t offset_fromstts = offset - offset_total; + if (toskip) *toskip = (int32_t)(offset_fromstts % sample_delta); + return co + (int32_t)(offset_fromstts / sample_delta); + } + else + { + offset_total += offset_delta; + } + co += sample_count; + } + return (int32_t)(-1); +} + +static int32_t mp4ff_find_sample_use_offsets(const mp4ff_t *f, const int32_t track, const int64_t offset,int32_t * toskip) +{ + return mp4ff_find_sample(f,track,offset + mp4ff_get_sample_offset(f,track,0),toskip); +} + +int32_t mp4ff_read_sample(mp4ff_t *f, const int32_t track, const int32_t sample, + uint8_t **audio_buffer, uint32_t *bytes) +{ + int32_t result = 0; + + *bytes = mp4ff_audio_frame_size(f, track, sample); + + if (*bytes==0) return 0; + + *audio_buffer = (uint8_t*)malloc(*bytes); + + mp4ff_set_sample_position(f, track, sample); + + result = mp4ff_read_data(f, *audio_buffer, *bytes); + + if (!result) + { + free(*audio_buffer); + *audio_buffer = 0; + return 0; + } + +#ifdef ITUNES_DRM + if (f->track[track]->p_drms != NULL) + { + drms_decrypt(f->track[track]->p_drms, (uint32_t*)*audio_buffer, *bytes); + } +#endif + + return *bytes; +} + + +static int32_t mp4ff_read_sample_v2(mp4ff_t *f, const int track, const int sample,unsigned char *buffer) +{ + int32_t result = 0; + int32_t size = mp4ff_audio_frame_size(f,track,sample); + if (size<=0) return 0; + mp4ff_set_sample_position(f, track, sample); + result = mp4ff_read_data(f,buffer,size); + +#ifdef ITUNES_DRM + if (f->track[track]->p_drms != NULL) + { + drms_decrypt(f->track[track]->p_drms, (uint32_t*)buffer, size); + } +#endif + + return result; +} + +static int32_t mp4ff_read_sample_getsize(mp4ff_t *f, const int track, const int sample) +{ + int32_t temp = mp4ff_audio_frame_size(f, track, sample); + if (temp<0) temp = 0; + return temp; +} diff --git a/trunk/src/mp4ff/mp4ff.dsp b/trunk/src/mp4ff/mp4ff.dsp new file mode 100644 index 000000000..e3a2d0899 --- /dev/null +++ b/trunk/src/mp4ff/mp4ff.dsp @@ -0,0 +1,144 @@ +# Microsoft Developer Studio Project File - Name="mp4ff" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=mp4ff - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "mp4ff.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "mp4ff.mak" CFG="mp4ff - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "mp4ff - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "mp4ff - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=xicl6.exe +RSC=rc.exe + +!IF "$(CFG)" == "mp4ff - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +F90=df.exe +MTL=midl.exe +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /FD /c +# ADD CPP /nologo /MD /W3 /O1 /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "USE_TAGGING" /YX /FD /c +# ADD BASE RSC /l 0x413 /d "NDEBUG" +# ADD RSC /l 0x413 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=xilink6.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "mp4ff - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +F90=df.exe +MTL=midl.exe +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /ZI /Od /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "USE_TAGGING" /YX /FD /GZ /c +# ADD BASE RSC /l 0x413 /d "_DEBUG" +# ADD RSC /l 0x413 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=xilink6.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ENDIF + +# Begin Target + +# Name "mp4ff - Win32 Release" +# Name "mp4ff - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\drms.c +# End Source File +# Begin Source File + +SOURCE=.\mp4atom.c +# End Source File +# Begin Source File + +SOURCE=.\mp4ff.c +# End Source File +# Begin Source File + +SOURCE=.\mp4meta.c +# End Source File +# Begin Source File + +SOURCE=.\mp4sample.c +# End Source File +# Begin Source File + +SOURCE=.\mp4tagupdate.c +# End Source File +# Begin Source File + +SOURCE=.\mp4util.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\drms.h +# End Source File +# Begin Source File + +SOURCE=.\drmstables.h +# End Source File +# Begin Source File + +SOURCE=.\mp4ff.h +# End Source File +# Begin Source File + +SOURCE=.\mp4ff_int_types.h +# End Source File +# Begin Source File + +SOURCE=.\mp4ffint.h +# End Source File +# End Group +# End Target +# End Project diff --git a/trunk/src/mp4ff/mp4ff.h b/trunk/src/mp4ff/mp4ff.h new file mode 100644 index 000000000..5fb485d77 --- /dev/null +++ b/trunk/src/mp4ff/mp4ff.h @@ -0,0 +1,128 @@ +/* +** FAAD2 - Freeware Advanced Audio (AAC) Decoder including SBR decoding +** Copyright (C) 2003-2004 M. Bakker, Ahead Software AG, http://www.nero.com +** +** 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. +** +** Any non-GPL usage of this software or parts of this software is strictly +** forbidden. +** +** Commercial non-GPL licensing of this software is possible. +** For more info contact Ahead Software through Mpeg4AAClicense@nero.com. +** +** $Id: mp4ff.h,v 1.19 2004/01/11 15:52:18 menno Exp $ +**/ + +#ifndef MP4FF_H +#define MP4FF_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include "mp4ff_int_types.h" + +/* file callback structure */ +typedef struct +{ + uint32_t (*read)(void *user_data, void *buffer, uint32_t length); + uint32_t (*write)(void *udata, void *buffer, uint32_t length); + uint32_t (*seek)(void *user_data, uint64_t position); + uint32_t (*truncate)(void *user_data); + void *user_data; +} mp4ff_callback_t; + +/* mp4 main file structure */ +typedef void* mp4ff_t; + + +/* API */ + +mp4ff_t *mp4ff_open_read(mp4ff_callback_t *f); +void mp4ff_close(mp4ff_t *f); +int32_t mp4ff_get_sample_duration(const mp4ff_t *f, const int32_t track, const int32_t sample); +int32_t mp4ff_get_sample_duration_use_offsets(const mp4ff_t *f, const int32_t track, const int32_t sample); +int64_t mp4ff_get_sample_position(const mp4ff_t *f, const int32_t track, const int32_t sample); +int32_t mp4ff_get_sample_offset(const mp4ff_t *f, const int32_t track, const int32_t sample); +int32_t mp4ff_find_sample(const mp4ff_t *f, const int32_t track, const int64_t offset,int32_t * toskip); +int32_t mp4ff_find_sample_use_offsets(const mp4ff_t *f, const int32_t track, const int64_t offset,int32_t * toskip); + +int32_t mp4ff_read_sample(mp4ff_t *f, const int track, const int sample, + unsigned char **audio_buffer, unsigned int *bytes); + +int32_t mp4ff_read_sample_v2(mp4ff_t *f, const int track, const int sample,unsigned char *buffer);//returns 0 on error, number of bytes read on success, use mp4ff_read_sample_getsize() to check buffer size needed +int32_t mp4ff_read_sample_getsize(mp4ff_t *f, const int track, const int sample);//returns 0 on error, buffer size needed for mp4ff_read_sample_v2() on success + + + +int32_t mp4ff_get_decoder_config(const mp4ff_t *f, const int track, + unsigned char** ppBuf, unsigned int* pBufSize); +int32_t mp4ff_get_track_type(const mp4ff_t *f, const int track); +int32_t mp4ff_total_tracks(const mp4ff_t *f); +int32_t mp4ff_num_samples(const mp4ff_t *f, const int track); +int32_t mp4ff_time_scale(const mp4ff_t *f, const int track); + +uint32_t mp4ff_get_avg_bitrate(const mp4ff_t *f, const int32_t track); +uint32_t mp4ff_get_max_bitrate(const mp4ff_t *f, const int32_t track); +int64_t mp4ff_get_track_duration(const mp4ff_t *f, const int32_t track); //returns (-1) if unknown +int64_t mp4ff_get_track_duration_use_offsets(const mp4ff_t *f, const int32_t track); //returns (-1) if unknown +uint32_t mp4ff_get_sample_rate(const mp4ff_t *f, const int32_t track); +uint32_t mp4ff_get_channel_count(const mp4ff_t * f,const int32_t track); +uint32_t mp4ff_get_audio_type(const mp4ff_t * f,const int32_t track); + + +/* metadata */ +int mp4ff_meta_get_num_items(const mp4ff_t *f); +int mp4ff_meta_get_by_index(const mp4ff_t *f, unsigned int index, + char **item, char **value); +int mp4ff_meta_get_title(const mp4ff_t *f, char **value); +int mp4ff_meta_get_artist(const mp4ff_t *f, char **value); +int mp4ff_meta_get_writer(const mp4ff_t *f, char **value); +int mp4ff_meta_get_album(const mp4ff_t *f, char **value); +int mp4ff_meta_get_date(const mp4ff_t *f, char **value); +int mp4ff_meta_get_tool(const mp4ff_t *f, char **value); +int mp4ff_meta_get_comment(const mp4ff_t *f, char **value); +int mp4ff_meta_get_genre(const mp4ff_t *f, char **value); +int mp4ff_meta_get_track(const mp4ff_t *f, char **value); +int mp4ff_meta_get_disc(const mp4ff_t *f, char **value); +int mp4ff_meta_get_compilation(const mp4ff_t *f, char **value); +int mp4ff_meta_get_tempo(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_coverart(const mp4ff_t *f, char **value); +#ifdef USE_TAGGING + +/* metadata tag structure */ +typedef struct +{ + char *item; + char *value; +} mp4ff_tag_t; + +/* metadata list structure */ +typedef struct +{ + mp4ff_tag_t *tags; + uint32_t count; +} mp4ff_metadata_t; + +int32_t mp4ff_meta_update(mp4ff_callback_t *f,const mp4ff_metadata_t * data); + +#endif + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/trunk/src/mp4ff/mp4ff_int_types.h b/trunk/src/mp4ff/mp4ff_int_types.h new file mode 100644 index 000000000..2da8fee6e --- /dev/null +++ b/trunk/src/mp4ff/mp4ff_int_types.h @@ -0,0 +1,32 @@ +#ifndef _MP4FF_INT_TYPES_H_ +#define _MP4FF_INT_TYPES_H_ + +#ifdef _WIN32 + +typedef char int8_t; +typedef unsigned char uint8_t; +typedef short int16_t; +typedef unsigned short uint16_t; +typedef long int32_t; +typedef unsigned long uint32_t; + +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; + +#else + +#include "config.h" + +#if defined(HAVE_STDINT_H) +#include <stdint.h> +#elif defined(HAVE_INTTYPES_H) +#include <inttypes.h> +#elif defined(HAVE_SYS_INTTYPES_H) +#include <sys/inttypes.h> +#elif defined(HAVE_SYS_TYPES_H) +#include <sys/types.h> +#endif + +#endif + +#endif diff --git a/trunk/src/mp4ff/mp4ffint.h b/trunk/src/mp4ff/mp4ffint.h new file mode 100644 index 000000000..fc13f469d --- /dev/null +++ b/trunk/src/mp4ff/mp4ffint.h @@ -0,0 +1,329 @@ +/* +** FAAD2 - Freeware Advanced Audio (AAC) Decoder including SBR decoding +** Copyright (C) 2003-2004 M. Bakker, Ahead Software AG, http://www.nero.com +** +** 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. +** +** Any non-GPL usage of this software or parts of this software is strictly +** forbidden. +** +** Commercial non-GPL licensing of this software is possible. +** For more info contact Ahead Software through Mpeg4AAClicense@nero.com. +** +** $Id: mp4ffint.h,v 1.15 2004/01/14 20:50:22 menno Exp $ +**/ + +#ifndef MP4FF_INTERNAL_H +#define MP4FF_INTERNAL_H + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include "mp4ff_int_types.h" + + +#ifdef _WIN32 +#define ITUNES_DRM +#endif + + +#define MAX_TRACKS 1024 +#define TRACK_UNKNOWN 0 +#define TRACK_AUDIO 1 +#define TRACK_VIDEO 2 +#define TRACK_SYSTEM 3 + + +#define SUBATOMIC 128 + +/* atoms without subatoms */ +#define ATOM_FTYP 129 +#define ATOM_MDAT 130 +#define ATOM_MVHD 131 +#define ATOM_TKHD 132 +#define ATOM_TREF 133 +#define ATOM_MDHD 134 +#define ATOM_VMHD 135 +#define ATOM_SMHD 136 +#define ATOM_HMHD 137 +#define ATOM_STSD 138 +#define ATOM_STTS 139 +#define ATOM_STSZ 140 +#define ATOM_STZ2 141 +#define ATOM_STCO 142 +#define ATOM_STSC 143 +#define ATOM_MP4A 144 +#define ATOM_MP4V 145 +#define ATOM_MP4S 146 +#define ATOM_ESDS 147 +#define ATOM_META 148 /* iTunes Metadata box */ +#define ATOM_NAME 149 /* iTunes Metadata name box */ +#define ATOM_DATA 150 /* iTunes Metadata data box */ +#define ATOM_CTTS 151 +#define ATOM_FRMA 152 +#define ATOM_IVIV 153 +#define ATOM_PRIV 154 + +#define ATOM_UNKNOWN 255 +#define ATOM_FREE ATOM_UNKNOWN +#define ATOM_SKIP ATOM_UNKNOWN + +/* atoms with subatoms */ +#define ATOM_MOOV 1 +#define ATOM_TRAK 2 +#define ATOM_EDTS 3 +#define ATOM_MDIA 4 +#define ATOM_MINF 5 +#define ATOM_STBL 6 +#define ATOM_UDTA 7 +#define ATOM_ILST 8 /* iTunes Metadata list */ +#define ATOM_TITLE 9 +#define ATOM_ARTIST 10 +#define ATOM_WRITER 11 +#define ATOM_ALBUM 12 +#define ATOM_DATE 13 +#define ATOM_TOOL 14 +#define ATOM_COMMENT 15 +#define ATOM_GENRE1 16 +#define ATOM_TRACK 17 +#define ATOM_DISC 18 +#define ATOM_COMPILATION 19 +#define ATOM_GENRE2 20 +#define ATOM_TEMPO 21 +#define ATOM_COVER 22 +#define ATOM_DRMS 23 +#define ATOM_SINF 24 +#define ATOM_SCHI 25 + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifndef _WIN32 +#define stricmp strcasecmp +#endif + +/* file callback structure */ +typedef struct +{ + uint32_t (*read)(void *user_data, void *buffer, uint32_t length); + uint32_t (*write)(void *udata, void *buffer, uint32_t length); + uint32_t (*seek)(void *user_data, uint64_t position); + uint32_t (*truncate)(void *user_data); + void *user_data; +} mp4ff_callback_t; + + +/* metadata tag structure */ +typedef struct +{ + char *item; + char *value; +} mp4ff_tag_t; + +/* metadata list structure */ +typedef struct +{ + mp4ff_tag_t *tags; + uint32_t count; +} mp4ff_metadata_t; + + +typedef struct +{ + int32_t type; + int32_t channelCount; + int32_t sampleSize; + uint16_t sampleRate; + int32_t audioType; + + /* stsd */ + int32_t stsd_entry_count; + + /* stsz */ + int32_t stsz_sample_size; + int32_t stsz_sample_count; + int32_t *stsz_table; + + /* stts */ + int32_t stts_entry_count; + int32_t *stts_sample_count; + int32_t *stts_sample_delta; + + /* stsc */ + int32_t stsc_entry_count; + int32_t *stsc_first_chunk; + int32_t *stsc_samples_per_chunk; + int32_t *stsc_sample_desc_index; + + /* stsc */ + int32_t stco_entry_count; + int32_t *stco_chunk_offset; + + /* ctts */ + int32_t ctts_entry_count; + int32_t *ctts_sample_count; + int32_t *ctts_sample_offset; + + /* esde */ + uint8_t *decoderConfig; + int32_t decoderConfigLen; + + uint32_t maxBitrate; + uint32_t avgBitrate; + + uint32_t timeScale; + uint64_t duration; + +#ifdef ITUNES_DRM + /* drms */ + void *p_drms; +#endif + +} mp4ff_track_t; + +/* mp4 main file structure */ +typedef struct +{ + /* stream to read from */ + mp4ff_callback_t *stream; + int64_t current_position; + + int32_t moov_read; + uint64_t moov_offset; + uint64_t moov_size; + uint8_t last_atom; + uint64_t file_size; + + /* mvhd */ + int32_t time_scale; + int32_t duration; + + /* incremental track index while reading the file */ + int32_t total_tracks; + + /* track data */ + mp4ff_track_t *track[MAX_TRACKS]; + + /* metadata */ + mp4ff_metadata_t tags; +} mp4ff_t; + + + + +/* mp4util.c */ +int32_t mp4ff_read_data(mp4ff_t *f, int8_t *data, uint32_t size); +int32_t mp4ff_write_data(mp4ff_t *f, int8_t *data, uint32_t size); +uint64_t mp4ff_read_int64(mp4ff_t *f); +uint32_t mp4ff_read_int32(mp4ff_t *f); +uint32_t mp4ff_read_int24(mp4ff_t *f); +uint16_t mp4ff_read_int16(mp4ff_t *f); +uint8_t mp4ff_read_char(mp4ff_t *f); +int32_t mp4ff_write_int32(mp4ff_t *f,const uint32_t data); +uint32_t mp4ff_read_mp4_descr_length(mp4ff_t *f); +int64_t mp4ff_position(const mp4ff_t *f); +int32_t mp4ff_set_position(mp4ff_t *f, const int64_t position); +int32_t mp4ff_truncate(mp4ff_t * f); +char * mp4ff_read_string(mp4ff_t * f,uint32_t length); + +/* mp4atom.c */ +static int32_t mp4ff_atom_get_size(const int8_t *data); +static int32_t mp4ff_atom_compare(const int8_t a1, const int8_t b1, const int8_t c1, const int8_t d1, + const int8_t a2, const int8_t b2, const int8_t c2, const int8_t d2); +static uint8_t mp4ff_atom_name_to_type(const int8_t a, const int8_t b, const int8_t c, const int8_t d); +uint64_t mp4ff_atom_read_header(mp4ff_t *f, uint8_t *atom_type, uint8_t *header_size); +static int32_t mp4ff_read_stsz(mp4ff_t *f); +static int32_t mp4ff_read_esds(mp4ff_t *f); +static int32_t mp4ff_read_mp4a(mp4ff_t *f); +static int32_t mp4ff_read_stsd(mp4ff_t *f); +static int32_t mp4ff_read_stsc(mp4ff_t *f); +static int32_t mp4ff_read_stco(mp4ff_t *f); +static int32_t mp4ff_read_stts(mp4ff_t *f); +#ifdef USE_TAGGING +static int32_t mp4ff_read_meta(mp4ff_t *f, const uint64_t size); +#endif +int32_t mp4ff_atom_read(mp4ff_t *f, const int32_t size, const uint8_t atom_type); + +/* mp4sample.c */ +static int32_t mp4ff_chunk_of_sample(const mp4ff_t *f, const int32_t track, const int32_t sample, + int32_t *chunk_sample, int32_t *chunk); +static int32_t mp4ff_chunk_to_offset(const mp4ff_t *f, const int32_t track, const int32_t chunk); +static int32_t mp4ff_sample_range_size(const mp4ff_t *f, const int32_t track, + const int32_t chunk_sample, const int32_t sample); +static int32_t mp4ff_sample_to_offset(const mp4ff_t *f, const int32_t track, const int32_t sample); +int32_t mp4ff_audio_frame_size(const mp4ff_t *f, const int32_t track, const int32_t sample); +int32_t mp4ff_set_sample_position(mp4ff_t *f, const int32_t track, const int32_t sample); + +#ifdef USE_TAGGING +/* mp4meta.c */ +static int32_t mp4ff_tag_add_field(mp4ff_metadata_t *tags, const char *item, const char *value); +static int32_t mp4ff_tag_set_field(mp4ff_metadata_t *tags, const char *item, const char *value); +static int32_t mp4ff_set_metadata_name(mp4ff_t *f, const uint8_t atom_type, char **name); +static int32_t mp4ff_parse_tag(mp4ff_t *f, const uint8_t parent_atom_type, const int32_t size); +static int32_t mp4ff_meta_find_by_name(const mp4ff_t *f, const char *item, char **value); +int32_t mp4ff_parse_metadata(mp4ff_t *f, const int32_t size); +int32_t mp4ff_tag_delete(mp4ff_metadata_t *tags); +int32_t mp4ff_meta_get_num_items(const mp4ff_t *f); +int32_t mp4ff_meta_get_by_index(const mp4ff_t *f, uint32_t index, + char **item, char **value); +int32_t mp4ff_meta_get_title(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_artist(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_writer(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_album(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_date(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_tool(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_comment(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_genre(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_track(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_disc(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_compilation(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_tempo(const mp4ff_t *f, char **value); +int32_t mp4ff_meta_get_coverart(const mp4ff_t *f, char **value); +#endif + +/* mp4ff.c */ +mp4ff_t *mp4ff_open_read(mp4ff_callback_t *f); +#ifdef USE_TAGGING +mp4ff_t *mp4ff_open_edit(mp4ff_callback_t *f); +#endif +void mp4ff_close(mp4ff_t *ff); +/*void mp4ff_track_add(mp4ff_t *f);*/ +int32_t parse_sub_atoms(mp4ff_t *f, const uint64_t total_size); +int32_t parse_atoms(mp4ff_t *f); + +int32_t mp4ff_get_sample_duration(const mp4ff_t *f, const int32_t track, const int32_t sample); +int64_t mp4ff_get_sample_position(const mp4ff_t *f, const int32_t track, const int32_t sample); +int32_t mp4ff_get_sample_offset(const mp4ff_t *f, const int32_t track, const int32_t sample); +int32_t mp4ff_find_sample(const mp4ff_t *f, const int32_t track, const int64_t offset,int32_t * toskip); + +int32_t mp4ff_read_sample(mp4ff_t *f, const int32_t track, const int32_t sample, + uint8_t **audio_buffer, uint32_t *bytes); +int32_t mp4ff_get_decoder_config(const mp4ff_t *f, const int32_t track, + uint8_t** ppBuf, uint32_t* pBufSize); +int32_t mp4ff_total_tracks(const mp4ff_t *f); +int32_t mp4ff_time_scale(const mp4ff_t *f, const int32_t track); +int32_t mp4ff_num_samples(const mp4ff_t *f, const int32_t track); + +uint32_t mp4ff_meta_genre_to_index(const char * genrestr);//returns 1-based index, 0 if not found +const char * mp4ff_meta_index_to_genre(uint32_t idx);//returns pointer to static string + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif diff --git a/trunk/src/mp4ff/mp4meta.c b/trunk/src/mp4ff/mp4meta.c new file mode 100644 index 000000000..762f5dee7 --- /dev/null +++ b/trunk/src/mp4ff/mp4meta.c @@ -0,0 +1,414 @@ +/* +** FAAD2 - Freeware Advanced Audio (AAC) Decoder including SBR decoding +** Copyright (C) 2003-2004 M. Bakker, Ahead Software AG, http://www.nero.com +** +** 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. +** +** Any non-GPL usage of this software or parts of this software is strictly +** forbidden. +** +** Commercial non-GPL licensing of this software is possible. +** For more info contact Ahead Software through Mpeg4AAClicense@nero.com. +** +** $Id: mp4meta.c,v 1.13 2004/01/11 15:52:18 menno Exp $ +**/ + +#ifdef USE_TAGGING + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include "mp4ffint.h" + +static int32_t mp4ff_tag_add_field(mp4ff_metadata_t *tags, const char *item, const char *value) +{ + void *backup = (void *)tags->tags; + + if (!item || (item && !*item) || !value) return 0; + + tags->tags = (mp4ff_tag_t*)realloc(tags->tags, (tags->count+1) * sizeof(mp4ff_tag_t)); + if (!tags->tags) + { + if (backup) free(backup); + return 0; + } else { + tags->tags[tags->count].item = strdup(item); + tags->tags[tags->count].value = strdup(value); + + if (!tags->tags[tags->count].item || !tags->tags[tags->count].value) + { + if (!tags->tags[tags->count].item) free (tags->tags[tags->count].item); + if (!tags->tags[tags->count].value) free (tags->tags[tags->count].value); + tags->tags[tags->count].item = NULL; + tags->tags[tags->count].value = NULL; + return 0; + } + + tags->count++; + return 1; + } +} + +static int32_t mp4ff_tag_set_field(mp4ff_metadata_t *tags, const char *item, const char *value) +{ + unsigned int i; + + if (!item || (item && !*item) || !value) return 0; + + for (i = 0; i < tags->count; i++) + { + if (!stricmp(tags->tags[i].item, item)) + { + free(tags->tags[i].value); + tags->tags[i].value = strdup(value); + return 1; + } + } + + return mp4ff_tag_add_field(tags, item, value); +} + +int32_t mp4ff_tag_delete(mp4ff_metadata_t *tags) +{ + uint32_t i; + + for (i = 0; i < tags->count; i++) + { + if (tags->tags[i].item) free(tags->tags[i].item); + if (tags->tags[i].value) free(tags->tags[i].value); + } + + if (tags->tags) free(tags->tags); + + tags->tags = NULL; + tags->count = 0; + + return 0; +} + +static const char* ID3v1GenreList[] = { + "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", + "Grunge", "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", + "Other", "Pop", "R&B", "Rap", "Reggae", "Rock", + "Techno", "Industrial", "Alternative", "Ska", "Death Metal", "Pranks", + "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk", + "Fusion", "Trance", "Classical", "Instrumental", "Acid", "House", + "Game", "Sound Clip", "Gospel", "Noise", "AlternRock", "Bass", + "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", "Instrumental Rock", + "Ethnic", "Gothic", "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", + "Eurodance", "Dream", "Southern Rock", "Comedy", "Cult", "Gangsta", + "Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American", "Cabaret", + "New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer", "Lo-Fi", + "Tribal", "Acid Punk", "Acid Jazz", "Polka", "Retro", "Musical", + "Rock & Roll", "Hard Rock", "Folk", "Folk/Rock", "National Folk", "Swing", + "Fast-Fusion", "Bebob", "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", + "Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock", "Big Band", + "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson", + "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus", + "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba", + "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle", "Duet", + "Punk Rock", "Drum Solo", "A capella", "Euro-House", "Dance Hall", + "Goa", "Drum & Bass", "Club House", "Hardcore", "Terror", + "Indie", "BritPop", "NegerPunk", "Polsk Punk", "Beat", + "Christian Gangsta", "Heavy Metal", "Black Metal", "Crossover", "Contemporary C", + "Christian Rock", "Merengue", "Salsa", "Thrash Metal", "Anime", "JPop", + "SynthPop", +}; + +uint32_t mp4ff_meta_genre_to_index(const char * genrestr) +{ + unsigned n; + for(n=0;n<sizeof(ID3v1GenreList)/sizeof(ID3v1GenreList[0]);n++) + { + if (!stricmp(genrestr,ID3v1GenreList[n])) return n+1; + } + return 0; +} + +const char * mp4ff_meta_index_to_genre(uint32_t idx) +{ + if (idx>0 && idx<=sizeof(ID3v1GenreList)/sizeof(ID3v1GenreList[0])) + { + return ID3v1GenreList[idx-1]; + } + else + { + return 0; + } +} + + +static int32_t TrackToString(char** str, const uint16_t track, const uint16_t totalTracks) +{ + char temp[32]; + sprintf(temp, "%.5u of %.5u", track, totalTracks); + *str = strdup(temp); + return 0; +} + +static int32_t mp4ff_set_metadata_name(mp4ff_t *f, const uint8_t atom_type, char **name) +{ + static char *tag_names[] = { + "unknown", "title", "artist", "writer", "album", + "date", "tool", "comment", "genre", "track", + "disc", "compilation", "genre", "tempo", "cover" + }; + uint8_t tag_idx = 0; + + switch (atom_type) + { + case ATOM_TITLE: tag_idx = 1; break; + case ATOM_ARTIST: tag_idx = 2; break; + case ATOM_WRITER: tag_idx = 3; break; + case ATOM_ALBUM: tag_idx = 4; break; + case ATOM_DATE: tag_idx = 5; break; + case ATOM_TOOL: tag_idx = 6; break; + case ATOM_COMMENT: tag_idx = 7; break; + case ATOM_GENRE1: tag_idx = 8; break; + case ATOM_TRACK: tag_idx = 9; break; + case ATOM_DISC: tag_idx = 10; break; + case ATOM_COMPILATION: tag_idx = 11; break; + case ATOM_GENRE2: tag_idx = 12; break; + case ATOM_TEMPO: tag_idx = 13; break; + case ATOM_COVER: tag_idx = 14; break; + default: tag_idx = 0; break; + } + + *name = strdup(tag_names[tag_idx]); + + return 0; +} + +static int32_t mp4ff_parse_tag(mp4ff_t *f, const uint8_t parent_atom_type, const int32_t size) +{ + uint8_t atom_type; + uint8_t header_size = 0; + uint64_t subsize, sumsize = 0; + char * name = NULL; + char * data = NULL; + uint32_t done = 0; + + + while (sumsize < size) + { + uint64_t destpos; + subsize = mp4ff_atom_read_header(f, &atom_type, &header_size); + destpos = mp4ff_position(f)+subsize-header_size; + if (!done) + { + if (atom_type == ATOM_DATA) + { + mp4ff_read_char(f); /* version */ + mp4ff_read_int24(f); /* flags */ + mp4ff_read_int32(f); /* reserved */ + + /* some need special attention */ + if (parent_atom_type == ATOM_GENRE2 || parent_atom_type == ATOM_TEMPO) + { + if (subsize - header_size >= 8 + 2) + { + uint16_t val = mp4ff_read_int16(f); + + if (parent_atom_type == ATOM_TEMPO) + { + char temp[16]; + sprintf(temp, "%.5u BPM", val); + mp4ff_tag_add_field(&(f->tags), "tempo", temp); + } + else + { + const char * temp = mp4ff_meta_index_to_genre(val); + if (temp) + { + mp4ff_tag_add_field(&(f->tags), "genre", temp); + } + } + done = 1; + } + } else if (parent_atom_type == ATOM_TRACK || parent_atom_type == ATOM_DISC) { + if (!done && subsize - header_size >= 8 + 8) + { + uint16_t index,total; + char temp[32]; + mp4ff_read_int16(f); + index = mp4ff_read_int16(f); + total = mp4ff_read_int16(f); + mp4ff_read_int16(f); + + sprintf(temp,"%d",index); + mp4ff_tag_add_field(&(f->tags), parent_atom_type == ATOM_TRACK ? "track" : "disc", temp); + if (total>0) + { + sprintf(temp,"%d",total); + mp4ff_tag_add_field(&(f->tags), parent_atom_type == ATOM_TRACK ? "totaltracks" : "totaldiscs", temp); + } + done = 1; + } + } else + { + if (data) {free(data);data = NULL;} + data = mp4ff_read_string(f,(uint32_t)(subsize-(header_size+8))); + } + } else if (atom_type == ATOM_NAME) { + if (!done) + { + mp4ff_read_char(f); /* version */ + mp4ff_read_int24(f); /* flags */ + if (name) free(name); + name = mp4ff_read_string(f,(uint32_t)(subsize-(header_size+4))); + } + } + mp4ff_set_position(f, destpos); + sumsize += subsize; + } + } + + if (data) + { + if (!done) + { + if (name == NULL) mp4ff_set_metadata_name(f, parent_atom_type, &name); + if (name) mp4ff_tag_add_field(&(f->tags), name, data); + } + + free(data); + } + if (name) free(name); + return 1; +} + +int32_t mp4ff_parse_metadata(mp4ff_t *f, const int32_t size) +{ + uint64_t subsize, sumsize = 0; + uint8_t atom_type; + uint8_t header_size = 0; + + while (sumsize < size) + { + subsize = mp4ff_atom_read_header(f, &atom_type, &header_size); + mp4ff_parse_tag(f, atom_type, (uint32_t)(subsize-header_size)); + sumsize += subsize; + } + + return 0; +} + +/* find a metadata item by name */ +/* returns 0 if item found, 1 if no such item */ +static int32_t mp4ff_meta_find_by_name(const mp4ff_t *f, const char *item, char **value) +{ + uint32_t i; + + for (i = 0; i < f->tags.count; i++) + { + if (!stricmp(f->tags.tags[i].item, item)) + { + *value = strdup(f->tags.tags[i].value); + return 1; + } + } + + *value = NULL; + + /* not found */ + return 0; +} + +int32_t mp4ff_meta_get_num_items(const mp4ff_t *f) +{ + return f->tags.count; +} + +int32_t mp4ff_meta_get_by_index(const mp4ff_t *f, uint32_t index, + char **item, char **value) +{ + if (index >= f->tags.count) + { + *item = NULL; + *value = NULL; + return 0; + } else { + *item = strdup(f->tags.tags[index].item); + *value = strdup(f->tags.tags[index].value); + return 1; + } +} + +int32_t mp4ff_meta_get_title(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "title", value); +} + +int32_t mp4ff_meta_get_artist(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "artist", value); +} + +int32_t mp4ff_meta_get_writer(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "writer", value); +} + +int32_t mp4ff_meta_get_album(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "album", value); +} + +int32_t mp4ff_meta_get_date(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "date", value); +} + +int32_t mp4ff_meta_get_tool(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "tool", value); +} + +int32_t mp4ff_meta_get_comment(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "comment", value); +} + +int32_t mp4ff_meta_get_genre(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "genre", value); +} + +int32_t mp4ff_meta_get_track(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "track", value); +} + +int32_t mp4ff_meta_get_disc(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "disc", value); +} + +int32_t mp4ff_meta_get_compilation(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "compilation", value); +} + +int32_t mp4ff_meta_get_tempo(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "tempo", value); +} + +int32_t mp4ff_meta_get_coverart(const mp4ff_t *f, char **value) +{ + return mp4ff_meta_find_by_name(f, "cover", value); +} + +#endif diff --git a/trunk/src/mp4ff/mp4sample.c b/trunk/src/mp4ff/mp4sample.c new file mode 100644 index 000000000..5688a3a8f --- /dev/null +++ b/trunk/src/mp4ff/mp4sample.c @@ -0,0 +1,152 @@ +/* +** FAAD2 - Freeware Advanced Audio (AAC) Decoder including SBR decoding +** Copyright (C) 2003-2004 M. Bakker, Ahead Software AG, http://www.nero.com +** +** 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. +** +** Any non-GPL usage of this software or parts of this software is strictly +** forbidden. +** +** Commercial non-GPL licensing of this software is possible. +** For more info contact Ahead Software through Mpeg4AAClicense@nero.com. +** +** $Id: mp4sample.c,v 1.15 2004/01/11 15:52:19 menno Exp $ +**/ + +#include <stdlib.h> +#include "mp4ffint.h" + + +static int32_t mp4ff_chunk_of_sample(const mp4ff_t *f, const int32_t track, const int32_t sample, + int32_t *chunk_sample, int32_t *chunk) +{ + int32_t total_entries = 0; + int32_t chunk2entry; + int32_t chunk1, chunk2, chunk1samples, range_samples, total = 0; + + if (f->track[track] == NULL) + { + return -1; + } + + total_entries = f->track[track]->stsc_entry_count; + + chunk1 = 1; + chunk1samples = 0; + chunk2entry = 0; + + do + { + chunk2 = f->track[track]->stsc_first_chunk[chunk2entry]; + *chunk = chunk2 - chunk1; + range_samples = *chunk * chunk1samples; + + if (sample < total + range_samples) break; + + chunk1samples = f->track[track]->stsc_samples_per_chunk[chunk2entry]; + chunk1 = chunk2; + + if(chunk2entry < total_entries) + { + chunk2entry++; + total += range_samples; + } + } while (chunk2entry < total_entries); + + if (chunk1samples) + *chunk = (sample - total) / chunk1samples + chunk1; + else + *chunk = 1; + + *chunk_sample = total + (*chunk - chunk1) * chunk1samples; + + return 0; +} + +static int32_t mp4ff_chunk_to_offset(const mp4ff_t *f, const int32_t track, const int32_t chunk) +{ + const mp4ff_track_t * p_track = f->track[track]; + + if (p_track->stco_entry_count && (chunk > p_track->stco_entry_count)) + { + return p_track->stco_chunk_offset[p_track->stco_entry_count - 1]; + } else if (p_track->stco_entry_count) { + return p_track->stco_chunk_offset[chunk - 1]; + } else { + return 8; + } + + return 0; +} + +static int32_t mp4ff_sample_range_size(const mp4ff_t *f, const int32_t track, + const int32_t chunk_sample, const int32_t sample) +{ + int32_t i, total; + const mp4ff_track_t * p_track = f->track[track]; + + if (p_track->stsz_sample_size) + { + return (sample - chunk_sample) * p_track->stsz_sample_size; + } + else + { + if (sample>=p_track->stsz_sample_count) return 0;//error + + for(i = chunk_sample, total = 0; i < sample; i++) + { + total += p_track->stsz_table[i]; + } + } + + return total; +} + +static int32_t mp4ff_sample_to_offset(const mp4ff_t *f, const int32_t track, const int32_t sample) +{ + int32_t chunk, chunk_sample, chunk_offset1, chunk_offset2; + + mp4ff_chunk_of_sample(f, track, sample, &chunk_sample, &chunk); + + chunk_offset1 = mp4ff_chunk_to_offset(f, track, chunk); + chunk_offset2 = chunk_offset1 + mp4ff_sample_range_size(f, track, chunk_sample, sample); + + return chunk_offset2; +} + +int32_t mp4ff_audio_frame_size(const mp4ff_t *f, const int32_t track, const int32_t sample) +{ + int32_t bytes; + const mp4ff_track_t * p_track = f->track[track]; + + if (p_track->stsz_sample_size) + { + bytes = p_track->stsz_sample_size; + } else { + bytes = p_track->stsz_table[sample]; + } + + return bytes; +} + +int32_t mp4ff_set_sample_position(mp4ff_t *f, const int32_t track, const int32_t sample) +{ + int32_t offset; + + offset = mp4ff_sample_to_offset(f, track, sample); + mp4ff_set_position(f, offset); + + return 0; +} diff --git a/trunk/src/mp4ff/mp4tagupdate.c b/trunk/src/mp4ff/mp4tagupdate.c new file mode 100644 index 000000000..c999fa5ee --- /dev/null +++ b/trunk/src/mp4ff/mp4tagupdate.c @@ -0,0 +1,645 @@ +#include <stdlib.h> +#include <string.h> +#include "mp4ffint.h" + +#ifdef USE_TAGGING + +static uint32_t fix_byte_order_32(uint32_t src) +{ + uint32_t result; + uint32_t a, b, c, d; + int8_t data[4]; + + memcpy(data,&src,sizeof(src)); + a = (uint8_t)data[0]; + b = (uint8_t)data[1]; + c = (uint8_t)data[2]; + d = (uint8_t)data[3]; + + result = (a<<24) | (b<<16) | (c<<8) | d; + return (uint32_t)result; +} + +static uint16_t fix_byte_order_16(uint16_t src) +{ + uint16_t result; + uint16_t a, b; + int8_t data[2]; + + memcpy(data,&src,sizeof(src)); + a = (uint8_t)data[0]; + b = (uint8_t)data[1]; + + result = (a<<8) | b; + return (uint16_t)result; +} + + +typedef struct +{ + void * data; + unsigned written; + unsigned allocated; + unsigned error; +} membuffer; + +static unsigned membuffer_write(membuffer * buf,const void * ptr,unsigned bytes) +{ + unsigned dest_size = buf->written + bytes; + + if (buf->error) return 0; + if (dest_size > buf->allocated) + { + do + { + buf->allocated <<= 1; + } while(dest_size > buf->allocated); + + { + void * newptr = realloc(buf->data,buf->allocated); + if (newptr==0) + { + free(buf->data); + buf->data = 0; + buf->error = 1; + return 0; + } + buf->data = newptr; + } + } + + if (ptr) memcpy((char*)buf->data + buf->written,ptr,bytes); + buf->written += bytes; + return bytes; +} + +#define membuffer_write_data membuffer_write + +static unsigned membuffer_write_int32(membuffer * buf,uint32_t data) +{ + uint8_t temp[4] = {(uint8_t)(data>>24),(uint8_t)(data>>16),(uint8_t)(data>>8),(uint8_t)data}; + return membuffer_write_data(buf,temp,4); +} + +static unsigned membuffer_write_int24(membuffer * buf,uint32_t data) +{ + uint8_t temp[3] = {(uint8_t)(data>>16),(uint8_t)(data>>8),(uint8_t)data}; + return membuffer_write_data(buf,temp,3); +} + +static unsigned membuffer_write_int16(membuffer * buf,uint16_t data) +{ + uint8_t temp[2] = {(uint8_t)(data>>8),(uint8_t)data}; + return membuffer_write_data(buf,temp,2); +} + +static unsigned membuffer_write_atom_name(membuffer * buf,const char * data) +{ + return membuffer_write_data(buf,data,4)==4 ? 1 : 0; +} + +static void membuffer_write_atom(membuffer * buf,const char * name,unsigned size,const void * data) +{ + membuffer_write_int32(buf,size + 8); + membuffer_write_atom_name(buf,name); + membuffer_write_data(buf,data,size); +} + +static unsigned membuffer_write_string(membuffer * buf,const char * data) +{ + return membuffer_write_data(buf,data,strlen(data)); +} + +static unsigned membuffer_write_int8(membuffer * buf,uint8_t data) +{ + return membuffer_write_data(buf,&data,1); +} + +static void * membuffer_get_ptr(const membuffer * buf) +{ + return buf->data; +} + +static unsigned membuffer_get_size(const membuffer * buf) +{ + return buf->written; +} + +static unsigned membuffer_error(const membuffer * buf) +{ + return buf->error; +} + +static void membuffer_set_error(membuffer * buf) {buf->error = 1;} + +static unsigned membuffer_transfer_from_file(membuffer * buf,mp4ff_t * src,unsigned bytes) +{ + unsigned oldsize; + void * bufptr; + + oldsize = membuffer_get_size(buf); + if (membuffer_write_data(buf,0,bytes) != bytes) return 0; + + bufptr = membuffer_get_ptr(buf); + if (bufptr==0) return 0; + + if ((unsigned)mp4ff_read_data(src,(char*)bufptr + oldsize,bytes)!=bytes) + { + membuffer_set_error(buf); + return 0; + } + + return bytes; +} + + +static membuffer * membuffer_create() +{ + const unsigned initial_size = 256; + + membuffer * buf = (membuffer *) malloc(sizeof(membuffer)); + buf->data = malloc(initial_size); + buf->written = 0; + buf->allocated = initial_size; + buf->error = buf->data == 0 ? 1 : 0; + + return buf; +} + +static void membuffer_free(membuffer * buf) +{ + if (buf->data) free(buf->data); + free(buf); +} + +static void * membuffer_detach(membuffer * buf) +{ + void * ret; + + if (buf->error) return 0; + + ret = realloc(buf->data,buf->written); + + if (ret == 0) free(buf->data); + + buf->data = 0; + buf->error = 1; + + return ret; +} + +#if 0 +/* metadata tag structure */ +typedef struct +{ + char *item; + char *value; +} mp4ff_tag_t; + +/* metadata list structure */ +typedef struct +{ + mp4ff_tag_t *tags; + uint32_t count; +} mp4ff_metadata_t; +#endif + +typedef struct +{ + const char * atom; + const char * name; +} stdmeta_entry; + +static stdmeta_entry stdmetas[] = +{ + {"©nam","title"}, + {"©ART","artist"}, + {"©wrt","writer"}, + {"©alb","album"}, + {"©day","date"}, + {"©too","tool"}, + {"©cmt","comment"}, +// {"©gen","genre"}, + {"cpil","compilation"}, +// {"trkn","track"}, +// {"disk","disc"}, +// {"gnre","genre"}, + {"covr","cover"}, +}; + + +static const char* find_standard_meta(const char * name) //returns atom name if found, 0 if not +{ + unsigned n; + for(n=0;n<sizeof(stdmetas)/sizeof(stdmetas[0]);n++) + { + if (!stricmp(name,stdmetas[n].name)) return stdmetas[n].atom; + } + return 0; +} + +static void membuffer_write_track_tag(membuffer * buf,const char * name,uint32_t index,uint32_t total) +{ + membuffer_write_int32(buf,8 /*atom header*/ + 8 /*data atom header*/ + 8 /*flags + reserved*/ + 8 /*actual data*/ ); + membuffer_write_atom_name(buf,name); + membuffer_write_int32(buf,8 /*data atom header*/ + 8 /*flags + reserved*/ + 8 /*actual data*/ ); + membuffer_write_atom_name(buf,"data"); + membuffer_write_int32(buf,0);//flags + membuffer_write_int32(buf,0);//reserved + membuffer_write_int16(buf,0); + membuffer_write_int16(buf,(uint16_t)index);//track number + membuffer_write_int16(buf,(uint16_t)total);//total tracks + membuffer_write_int16(buf,0); +} + +static void membuffer_write_int16_tag(membuffer * buf,const char * name,uint16_t value) +{ + membuffer_write_int32(buf,8 /*atom header*/ + 8 /*data atom header*/ + 8 /*flags + reserved*/ + 2 /*actual data*/ ); + membuffer_write_atom_name(buf,name); + membuffer_write_int32(buf,8 /*data atom header*/ + 8 /*flags + reserved*/ + 2 /*actual data*/ ); + membuffer_write_atom_name(buf,"data"); + membuffer_write_int32(buf,0);//flags + membuffer_write_int32(buf,0);//reserved + membuffer_write_int16(buf,value);//value +} + +static void membuffer_write_std_tag(membuffer * buf,const char * name,const char * value) +{ + membuffer_write_int32(buf,8 /*atom header*/ + 8 /*data atom header*/ + 8 /*flags + reserved*/ + strlen(value) ); + membuffer_write_atom_name(buf,name); + membuffer_write_int32(buf,8 /*data atom header*/ + 8 /*flags + reserved*/ + strlen(value)); + membuffer_write_atom_name(buf,"data"); + membuffer_write_int32(buf,1);//flags + membuffer_write_int32(buf,0);//reserved + membuffer_write_data(buf,value,strlen(value)); +} + +static void membuffer_write_custom_tag(membuffer * buf,const char * name,const char * value) +{ + membuffer_write_int32(buf,8 /*atom header*/ + 0x1C /*weirdo itunes atom*/ + 12 /*name atom header*/ + strlen(name) + 16 /*data atom header + flags*/ + strlen(value) ); + membuffer_write_atom_name(buf,"----"); + membuffer_write_int32(buf,0x1C);//weirdo itunes atom + membuffer_write_atom_name(buf,"mean"); + membuffer_write_int32(buf,0); + membuffer_write_data(buf,"com.apple.iTunes",16); + membuffer_write_int32(buf,12 + strlen(name)); + membuffer_write_atom_name(buf,"name"); + membuffer_write_int32(buf,0); + membuffer_write_data(buf,name,strlen(name)); + membuffer_write_int32(buf,8 /*data atom header*/ + 8 /*flags + reserved*/ + strlen(value)); + membuffer_write_atom_name(buf,"data"); + membuffer_write_int32(buf,1);//flags + membuffer_write_int32(buf,0);//reserved + membuffer_write_data(buf,value,strlen(value)); + +} + +static uint32_t myatoi(const char * param) +{ + return param ? atoi(param) : 0; +} + +static uint32_t create_ilst(const mp4ff_metadata_t * data,void ** out_buffer,uint32_t * out_size) +{ + membuffer * buf = membuffer_create(); + unsigned metaptr; + char * mask = (char*)malloc(data->count); + memset(mask,0,data->count); + + { + const char * tracknumber_ptr = 0, * totaltracks_ptr = 0; + const char * discnumber_ptr = 0, * totaldiscs_ptr = 0; + const char * genre_ptr = 0, * tempo_ptr = 0; + for(metaptr = 0; metaptr < data->count; metaptr++) + { + mp4ff_tag_t * tag = &data->tags[metaptr]; + if (!stricmp(tag->item,"tracknumber") || !stricmp(tag->item,"track")) + { + if (tracknumber_ptr==0) tracknumber_ptr = tag->value; + mask[metaptr] = 1; + } + else if (!stricmp(tag->item,"totaltracks")) + { + if (totaltracks_ptr==0) totaltracks_ptr = tag->value; + mask[metaptr] = 1; + } + else if (!stricmp(tag->item,"discnumber") || !stricmp(tag->item,"disc")) + { + if (discnumber_ptr==0) discnumber_ptr = tag->value; + mask[metaptr] = 1; + } + else if (!stricmp(tag->item,"totaldiscs")) + { + if (totaldiscs_ptr==0) totaldiscs_ptr = tag->value; + mask[metaptr] = 1; + } + else if (!stricmp(tag->item,"genre")) + { + if (genre_ptr==0) genre_ptr = tag->value; + mask[metaptr] = 1; + } + else if (!stricmp(tag->item,"tempo")) + { + if (tempo_ptr==0) tempo_ptr = tag->value; + mask[metaptr] = 1; + } + + } + + if (tracknumber_ptr) membuffer_write_track_tag(buf,"trkn",myatoi(tracknumber_ptr),myatoi(totaltracks_ptr)); + if (discnumber_ptr) membuffer_write_track_tag(buf,"disk",myatoi(discnumber_ptr),myatoi(totaldiscs_ptr)); + if (tempo_ptr) membuffer_write_int16_tag(buf,"tmpo",(uint16_t)myatoi(tempo_ptr)); + + if (genre_ptr) + { + uint32_t index = mp4ff_meta_genre_to_index(genre_ptr); + if (index==0) + membuffer_write_std_tag(buf,"©gen",genre_ptr); + else + membuffer_write_int16_tag(buf,"gnre",(uint16_t)index); + } + } + + for(metaptr = 0; metaptr < data->count; metaptr++) + { + if (!mask[metaptr]) + { + mp4ff_tag_t * tag = &data->tags[metaptr]; + const char * std_meta_atom = find_standard_meta(tag->item); + if (std_meta_atom) + { + membuffer_write_std_tag(buf,std_meta_atom,tag->value); + } + else + { + membuffer_write_custom_tag(buf,tag->item,tag->value); + } + } + } + + free(mask); + + if (membuffer_error(buf)) + { + membuffer_free(buf); + return 0; + } + + *out_size = membuffer_get_size(buf); + *out_buffer = membuffer_detach(buf); + membuffer_free(buf); + + return 1; +} + +static uint32_t find_atom(mp4ff_t * f,uint64_t base,uint32_t size,const char * name) +{ + uint32_t remaining = size; + uint64_t atom_offset = base; + for(;;) + { + char atom_name[4]; + uint32_t atom_size; + + mp4ff_set_position(f,atom_offset); + + if (remaining < 8) break; + atom_size = mp4ff_read_int32(f); + if (atom_size > remaining || atom_size < 8) break; + mp4ff_read_data(f,atom_name,4); + + if (!memcmp(atom_name,name,4)) + { + mp4ff_set_position(f,atom_offset); + return 1; + } + + remaining -= atom_size; + atom_offset += atom_size; + } + return 0; +} + +static uint32_t find_atom_v2(mp4ff_t * f,uint64_t base,uint32_t size,const char * name,uint32_t extraheaders,const char * name_inside) +{ + uint64_t first_base = (uint64_t)(-1); + while(find_atom(f,base,size,name))//try to find atom <name> with atom <name_inside> in it + { + uint64_t mybase = mp4ff_position(f); + uint32_t mysize = mp4ff_read_int32(f); + + if (first_base == (uint64_t)(-1)) first_base = mybase; + + if (mysize < 8 + extraheaders) break; + + if (find_atom(f,mybase+(8+extraheaders),mysize-(8+extraheaders),name_inside)) + { + mp4ff_set_position(f,mybase); + return 2; + } + base += mysize; + if (size<=mysize) {size=0;break;} + size -= mysize; + } + + if (first_base != (uint64_t)(-1))//wanted atom inside not found + { + mp4ff_set_position(f,first_base); + return 1; + } + else return 0; +} + +static uint32_t create_meta(const mp4ff_metadata_t * data,void ** out_buffer,uint32_t * out_size) +{ + membuffer * buf; + uint32_t ilst_size; + void * ilst_buffer; + + if (!create_ilst(data,&ilst_buffer,&ilst_size)) return 0; + + buf = membuffer_create(); + + membuffer_write_int32(buf,0); + membuffer_write_atom(buf,"ilst",ilst_size,ilst_buffer); + free(ilst_buffer); + + *out_size = membuffer_get_size(buf); + *out_buffer = membuffer_detach(buf); + membuffer_free(buf); + return 1; +} + +static uint32_t create_udta(const mp4ff_metadata_t * data,void ** out_buffer,uint32_t * out_size) +{ + membuffer * buf; + uint32_t meta_size; + void * meta_buffer; + + if (!create_meta(data,&meta_buffer,&meta_size)) return 0; + + buf = membuffer_create(); + + membuffer_write_atom(buf,"meta",meta_size,meta_buffer); + + free(meta_buffer); + + *out_size = membuffer_get_size(buf); + *out_buffer = membuffer_detach(buf); + membuffer_free(buf); + return 1; +} + +static uint32_t modify_moov(mp4ff_t * f,const mp4ff_metadata_t * data,void ** out_buffer,uint32_t * out_size) +{ + uint64_t total_base = f->moov_offset + 8; + uint32_t total_size = (uint32_t)(f->moov_size - 8); + + uint64_t udta_offset,meta_offset,ilst_offset; + uint32_t udta_size, meta_size, ilst_size; + + uint32_t new_ilst_size; + void * new_ilst_buffer; + + uint8_t * p_out; + int32_t size_delta; + + + if (!find_atom_v2(f,total_base,total_size,"udta",0,"meta")) + { + membuffer * buf; + void * new_udta_buffer; + uint32_t new_udta_size; + if (!create_udta(data,&new_udta_buffer,&new_udta_size)) return 0; + + buf = membuffer_create(); + mp4ff_set_position(f,total_base); + membuffer_transfer_from_file(buf,f,total_size); + + membuffer_write_atom(buf,"udta",new_udta_size,new_udta_buffer); + + free(new_udta_buffer); + + *out_size = membuffer_get_size(buf); + *out_buffer = membuffer_detach(buf); + membuffer_free(buf); + return 1; + } + else + { + udta_offset = mp4ff_position(f); + udta_size = mp4ff_read_int32(f); + if (find_atom_v2(f,udta_offset+8,udta_size-8,"meta",4,"ilst")<2) + { + membuffer * buf; + void * new_meta_buffer; + uint32_t new_meta_size; + if (!create_meta(data,&new_meta_buffer,&new_meta_size)) return 0; + + buf = membuffer_create(); + mp4ff_set_position(f,total_base); + membuffer_transfer_from_file(buf,f,(uint32_t)(udta_offset - total_base)); + + membuffer_write_int32(buf,udta_size + 8 + new_meta_size); + membuffer_write_atom_name(buf,"udta"); + membuffer_transfer_from_file(buf,f,udta_size); + + membuffer_write_atom(buf,"meta",new_meta_size,new_meta_buffer); + free(new_meta_buffer); + + *out_size = membuffer_get_size(buf); + *out_buffer = membuffer_detach(buf); + membuffer_free(buf); + return 1; + } + meta_offset = mp4ff_position(f); + meta_size = mp4ff_read_int32(f); + if (!find_atom(f,meta_offset+12,meta_size-12,"ilst")) return 0;//shouldn't happen, find_atom_v2 above takes care of it + ilst_offset = mp4ff_position(f); + ilst_size = mp4ff_read_int32(f); + + if (!create_ilst(data,&new_ilst_buffer,&new_ilst_size)) return 0; + + size_delta = new_ilst_size - (ilst_size - 8); + + *out_size = total_size + size_delta; + *out_buffer = malloc(*out_size); + if (*out_buffer == 0) + { + free(new_ilst_buffer); + return 0; + } + + p_out = (uint8_t*)*out_buffer; + + mp4ff_set_position(f,total_base); + mp4ff_read_data(f,p_out,(uint32_t)(udta_offset - total_base )); p_out += (uint32_t)(udta_offset - total_base ); + *(uint32_t*)p_out = fix_byte_order_32(mp4ff_read_int32(f) + size_delta); p_out += 4; + mp4ff_read_data(f,p_out,4); p_out += 4; + mp4ff_read_data(f,p_out,(uint32_t)(meta_offset - udta_offset - 8)); p_out += (uint32_t)(meta_offset - udta_offset - 8); + *(uint32_t*)p_out = fix_byte_order_32(mp4ff_read_int32(f) + size_delta); p_out += 4; + mp4ff_read_data(f,p_out,4); p_out += 4; + mp4ff_read_data(f,p_out,(uint32_t)(ilst_offset - meta_offset - 8)); p_out += (uint32_t)(ilst_offset - meta_offset - 8); + *(uint32_t*)p_out = fix_byte_order_32(mp4ff_read_int32(f) + size_delta); p_out += 4; + mp4ff_read_data(f,p_out,4); p_out += 4; + + memcpy(p_out,new_ilst_buffer,new_ilst_size); + p_out += new_ilst_size; + + mp4ff_set_position(f,ilst_offset + ilst_size); + mp4ff_read_data(f,p_out,(uint32_t)(total_size - (ilst_offset - total_base) - ilst_size)); + + free(new_ilst_buffer); + } + return 1; + +} + +static int32_t mp4ff_meta_update(mp4ff_callback_t *f,const mp4ff_metadata_t * data) +{ + void * new_moov_data; + uint32_t new_moov_size; + + mp4ff_t *ff = malloc(sizeof(mp4ff_t)); + + memset(ff, 0, sizeof(mp4ff_t)); + ff->stream = f; + mp4ff_set_position(ff,0); + + parse_atoms(ff); + + + if (!modify_moov(ff,data,&new_moov_data,&new_moov_size)) + { + mp4ff_close(ff); + return 0; + } + + /* copy moov atom to end of the file */ + if (ff->last_atom != ATOM_MOOV) + { + char *free_data = "free"; + + /* rename old moov to free */ + mp4ff_set_position(ff, ff->moov_offset + 4); + mp4ff_write_data(ff, free_data, 4); + + mp4ff_set_position(ff, ff->file_size); + mp4ff_write_int32(ff,new_moov_size + 8); + mp4ff_write_data(ff,"moov",4); + mp4ff_write_data(ff, new_moov_data, new_moov_size); + } + else + { + mp4ff_set_position(ff, ff->moov_offset); + mp4ff_write_int32(ff,new_moov_size + 8); + mp4ff_write_data(ff,"moov",4); + mp4ff_write_data(ff, new_moov_data, new_moov_size); + } + + mp4ff_truncate(ff); + + mp4ff_close(ff); + return 1; +} +#endif diff --git a/trunk/src/mp4ff/mp4util.c b/trunk/src/mp4ff/mp4util.c new file mode 100644 index 000000000..1a77c97ae --- /dev/null +++ b/trunk/src/mp4ff/mp4util.c @@ -0,0 +1,188 @@ +/* +** FAAD2 - Freeware Advanced Audio (AAC) Decoder including SBR decoding +** Copyright (C) 2003-2004 M. Bakker, Ahead Software AG, http://www.nero.com +** +** 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. +** +** Any non-GPL usage of this software or parts of this software is strictly +** forbidden. +** +** Commercial non-GPL licensing of this software is possible. +** For more info contact Ahead Software through Mpeg4AAClicense@nero.com. +** +** $Id: mp4util.c,v 1.15 2004/01/11 15:52:19 menno Exp $ +**/ + +#include "mp4ffint.h" +#include <stdlib.h> + +int32_t mp4ff_read_data(mp4ff_t *f, int8_t *data, uint32_t size) +{ + int32_t result = 1; + + result = f->stream->read(f->stream->user_data, data, size); + + f->current_position += size; + + return result; +} + +int32_t mp4ff_truncate(mp4ff_t * f) +{ + return f->stream->truncate(f->stream->user_data); +} + +int32_t mp4ff_write_data(mp4ff_t *f, int8_t *data, uint32_t size) +{ + int32_t result = 1; + + result = f->stream->write(f->stream->user_data, data, size); + + f->current_position += size; + + return result; +} + +int32_t mp4ff_write_int32(mp4ff_t *f,const uint32_t data) +{ + uint32_t result; + uint32_t a, b, c, d; + int8_t temp[4]; + + *(uint32_t*)temp = data; + a = (uint8_t)temp[0]; + b = (uint8_t)temp[1]; + c = (uint8_t)temp[2]; + d = (uint8_t)temp[3]; + + result = (a<<24) | (b<<16) | (c<<8) | d; + + return mp4ff_write_data(f,(uint8_t*)&result,sizeof(result)); +} + +int32_t mp4ff_set_position(mp4ff_t *f, const int64_t position) +{ + f->stream->seek(f->stream->user_data, position); + f->current_position = position; + + return 0; +} + +int64_t mp4ff_position(const mp4ff_t *f) +{ + return f->current_position; +} + +uint64_t mp4ff_read_int64(mp4ff_t *f) +{ + uint8_t data[8]; + uint64_t result = 0; + int8_t i; + + mp4ff_read_data(f, data, 8); + + for (i = 0; i < 8; i++) + { + result |= ((uint64_t)data[i]) << ((7 - i) * 8); + } + + return result; +} + +uint32_t mp4ff_read_int32(mp4ff_t *f) +{ + uint32_t result; + uint32_t a, b, c, d; + int8_t data[4]; + + mp4ff_read_data(f, data, 4); + a = (uint8_t)data[0]; + b = (uint8_t)data[1]; + c = (uint8_t)data[2]; + d = (uint8_t)data[3]; + + result = (a<<24) | (b<<16) | (c<<8) | d; + return (uint32_t)result; +} + +uint32_t mp4ff_read_int24(mp4ff_t *f) +{ + uint32_t result; + uint32_t a, b, c; + int8_t data[4]; + + mp4ff_read_data(f, data, 3); + a = (uint8_t)data[0]; + b = (uint8_t)data[1]; + c = (uint8_t)data[2]; + + result = (a<<16) | (b<<8) | c; + return (uint32_t)result; +} + +uint16_t mp4ff_read_int16(mp4ff_t *f) +{ + uint32_t result; + uint32_t a, b; + int8_t data[2]; + + mp4ff_read_data(f, data, 2); + a = (uint8_t)data[0]; + b = (uint8_t)data[1]; + + result = (a<<8) | b; + return (uint16_t)result; +} + +char * mp4ff_read_string(mp4ff_t * f,uint32_t length) +{ + char * str = (char*)malloc(length + 1); + if (str!=0) + { + if ((uint32_t)mp4ff_read_data(f,str,length)!=length) + { + free(str); + str = 0; + } + else + { + str[length] = 0; + } + } + return str; +} + +uint8_t mp4ff_read_char(mp4ff_t *f) +{ + uint8_t output; + mp4ff_read_data(f, &output, 1); + return output; +} + +uint32_t mp4ff_read_mp4_descr_length(mp4ff_t *f) +{ + uint8_t b; + uint8_t numBytes = 0; + uint32_t length = 0; + + do + { + b = mp4ff_read_char(f); + numBytes++; + length = (length << 7) | (b & 0x7F); + } while ((b & 0x80) && numBytes < 4); + + return length; +} diff --git a/trunk/src/mpd_types.h b/trunk/src/mpd_types.h new file mode 100644 index 000000000..dbdfc6865 --- /dev/null +++ b/trunk/src/mpd_types.h @@ -0,0 +1,43 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef MPD_TYPES_H +#define MPD_TYPES_H + +#include "../config.h" + +typedef unsigned char mpd_uint8; +typedef signed char mpd_sint8; + +#if SIZEOF_SHORT == 2 +typedef unsigned short mpd_uint16; +typedef signed short mpd_sint16; +#elif SIZEOF_INT == 2 +typedef unsigned int mpd_uint16; +typedef signed int mpd_sint16; +#endif + +#if SIZEOF_INT == 4 +typedef unsigned int mpd_uint32; +typedef signed int mpd_sint32; +#elif SIZEOF_LONG == 4 +typedef unsigned long mpd_uint32; +typedef signed long mpd_sint32; +#endif + +#endif diff --git a/trunk/src/myfprintf.c b/trunk/src/myfprintf.c new file mode 100644 index 000000000..a09ae4324 --- /dev/null +++ b/trunk/src/myfprintf.c @@ -0,0 +1,72 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "myfprintf.h" +#include "interface.h" +#include "path.h" +#include "log.h" +#include "conf.h" +#include "utils.h" + +#include <stdarg.h> +#include <sys/param.h> +#include <unistd.h> +#include <sys/select.h> +#include <fcntl.h> +#include <string.h> +#include <errno.h> + +#define BUFFER_LENGTH MAXPATHLEN+1024 + +static void blockingWrite(const int fd, const char *string, size_t len) +{ + while (len) { + ssize_t ret = xwrite(fd, string, len); + if (ret == len) + return; + if (ret >= 0) { + len -= ret; + string += ret; + continue; + } + return; /* error */ + } +} + +void vfdprintf(const int fd, const char *fmt, va_list args) +{ + static char buffer[BUFFER_LENGTH]; + char *buf = buffer; + size_t len; + + vsnprintf(buf, BUFFER_LENGTH, fmt, args); + len = strlen(buf); + if (fd == STDERR_FILENO || + fd == STDOUT_FILENO || + interfacePrintWithFD(fd, buf, len) < 0) + blockingWrite(fd, buf, len); +} + +mpd_fprintf void fdprintf(const int fd, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vfdprintf(fd, fmt, args); + va_end(args); +} + diff --git a/trunk/src/myfprintf.h b/trunk/src/myfprintf.h new file mode 100644 index 000000000..287902f9b --- /dev/null +++ b/trunk/src/myfprintf.h @@ -0,0 +1,31 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef MYFPRINTF_H +#define MYFPRINTF_H + +#include "../config.h" +#include "gcc.h" + +#include <stdarg.h> +#include <stdio.h> + +mpd_fprintf void fdprintf(const int fd, const char *fmt, ...); +void vfdprintf(const int fd, const char *fmt, va_list arglist); + +#endif diff --git a/trunk/src/normalize.c b/trunk/src/normalize.c new file mode 100644 index 000000000..fb62e7a4e --- /dev/null +++ b/trunk/src/normalize.c @@ -0,0 +1,47 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "compress.h" +#include "conf.h" +#include "normalize.h" + +#include <stdlib.h> + +int normalizationEnabled; + +void initNormalization(void) +{ + normalizationEnabled = getBoolConfigParam(CONF_VOLUME_NORMALIZATION); + if (normalizationEnabled == -1) normalizationEnabled = 0; + else if (normalizationEnabled < 0) exit(EXIT_FAILURE); + + if (normalizationEnabled) + CompressCfg(0, ANTICLIP, TARGET, GAINMAX, GAINSMOOTH, BUCKETS); +} + +void finishNormalization(void) +{ + if (normalizationEnabled) CompressFree(); +} + +void normalizeData(char *buffer, int bufferSize, AudioFormat *format) +{ + if ((format->bits != 16) || (format->channels != 2)) return; + + CompressDo(buffer, bufferSize); +} diff --git a/trunk/src/normalize.h b/trunk/src/normalize.h new file mode 100644 index 000000000..ddbefab08 --- /dev/null +++ b/trunk/src/normalize.h @@ -0,0 +1,32 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef NORMALIZE_H +#define NORMALIZE_H + +#include "audio.h" + +extern int normalizationEnabled; + +void initNormalization(void); + +void finishNormalization(void); + +void normalizeData(char *buffer, int bufferSize, AudioFormat *format); + +#endif /* !NORMALIZE_H */ diff --git a/trunk/src/outputBuffer.c b/trunk/src/outputBuffer.c new file mode 100644 index 000000000..c7ff8b479 --- /dev/null +++ b/trunk/src/outputBuffer.c @@ -0,0 +1,198 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "outputBuffer.h" + +#include "pcm_utils.h" +#include "playerData.h" +#include "utils.h" +#include "log.h" +#include "normalize.h" +#include "conf.h" + +#include <string.h> + +static mpd_sint16 currentChunk = -1; + +static mpd_sint8 currentMetaChunk = -1; +static mpd_sint8 sendMetaChunk; + +void clearAllMetaChunkSets(OutputBuffer * cb) +{ + memset(cb->metaChunkSet, 0, BUFFERED_METACHUNKS); +} + +void clearOutputBuffer(OutputBuffer * cb) +{ + int currentSet = 1; + + cb->end = cb->begin; + + /* be sure to reset metaChunkSets cause we are skipping over audio + * audio chunks, and thus skipping over metadata */ + if (currentChunk >= 0 && sendMetaChunk == 0 && currentMetaChunk >= 0) { + currentSet = cb->metaChunkSet[currentChunk]; + } + clearAllMetaChunkSets(cb); + if (currentChunk >= 0 && sendMetaChunk == 0 && currentMetaChunk >= 0) { + cb->metaChunkSet[currentChunk] = currentSet; + } + currentChunk = -1; +} + +void flushOutputBuffer(OutputBuffer * cb) +{ + if (currentChunk == cb->end) { + if ((cb->end + 1) >= buffered_chunks) { + cb->end = 0; + } + else cb->end++; + currentChunk = -1; + } +} + +int sendDataToOutputBuffer(OutputBuffer * cb, InputStream * inStream, + DecoderControl * dc, int seekable, void *dataIn, + long dataInLen, float time, mpd_uint16 bitRate, + ReplayGainInfo * replayGainInfo) +{ + mpd_uint16 dataToSend; + mpd_uint16 chunkLeft; + char *data; + size_t datalen; + static char *convBuffer; + static long convBufferLen; + + if (cmpAudioFormat(&(cb->audioFormat), &(dc->audioFormat)) == 0) { + data = dataIn; + datalen = dataInLen; + } else { + datalen = pcm_sizeOfConvBuffer(&(dc->audioFormat), dataInLen, + &(cb->audioFormat)); + if (datalen > convBufferLen) { + convBuffer = xrealloc(convBuffer, datalen); + convBufferLen = datalen; + } + data = convBuffer; + datalen = pcm_convertAudioFormat(&(dc->audioFormat), dataIn, + dataInLen, &(cb->audioFormat), + data, &(cb->convState)); + } + + if (replayGainInfo && (replayGainState != REPLAYGAIN_OFF)) + doReplayGain(replayGainInfo, data, datalen, &cb->audioFormat); + else if (normalizationEnabled) + normalizeData(data, datalen, &cb->audioFormat); + + while (datalen) { + if (currentChunk != cb->end) { + int next = cb->end + 1; + if (next >= buffered_chunks) { + next = 0; + } + while (cb->begin == next && !dc->stop) { + if (dc->seek) { + if (seekable) { + return OUTPUT_BUFFER_DC_SEEK; + } else { + dc->seekError = 1; + dc->seek = 0; + } + } + if (!inStream || + bufferInputStream(inStream) <= 0) { + my_usleep(10000); + } + } + if (dc->stop) + return OUTPUT_BUFFER_DC_STOP; + + currentChunk = cb->end; + cb->chunkSize[currentChunk] = 0; + + if (sendMetaChunk) { + cb->metaChunk[currentChunk] = currentMetaChunk; + } else + cb->metaChunk[currentChunk] = -1; + cb->bitRate[currentChunk] = bitRate; + cb->times[currentChunk] = time; + } + + chunkLeft = CHUNK_SIZE - cb->chunkSize[currentChunk]; + dataToSend = datalen > chunkLeft ? chunkLeft : datalen; + + memcpy(cb->chunks + currentChunk * CHUNK_SIZE + + cb->chunkSize[currentChunk], data, dataToSend); + cb->chunkSize[currentChunk] += dataToSend; + datalen -= dataToSend; + data += dataToSend; + + if (cb->chunkSize[currentChunk] == CHUNK_SIZE) { + flushOutputBuffer(cb); + } + } + + return 0; +} + +int copyMpdTagToOutputBuffer(OutputBuffer * cb, MpdTag * tag) +{ + int nextChunk; + static MpdTag *last; + + if (!cb->acceptMetadata || !tag) { + sendMetaChunk = 0; + if (last) + freeMpdTag(last); + last = NULL; + DEBUG("copyMpdTagToOB: !acceptMetadata || !tag\n"); + return 0; + } + + if (last && mpdTagsAreEqual(last, tag)) { + DEBUG("copyMpdTagToOB: same as last\n"); + return 0; + } + + if (last) + freeMpdTag(last); + last = NULL; + + nextChunk = currentMetaChunk + 1; + if (nextChunk >= BUFFERED_METACHUNKS) + nextChunk = 0; + + if (cb->metaChunkSet[nextChunk]) { + sendMetaChunk = 0; + DEBUG("copyMpdTagToOB: metachunk in use!\n"); + return -1; + } + + sendMetaChunk = 1; + currentMetaChunk = nextChunk; + + last = mpdTagDup(tag); + + copyMpdTagToMetadataChunk(tag, &(cb->metadataChunks[currentMetaChunk])); + + cb->metaChunkSet[nextChunk] = 1; + + DEBUG("copyMpdTagToOB: copiedTag\n"); + + return 0; +} diff --git a/trunk/src/outputBuffer.h b/trunk/src/outputBuffer.h new file mode 100644 index 000000000..f690941d4 --- /dev/null +++ b/trunk/src/outputBuffer.h @@ -0,0 +1,69 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef OUTPUT_BUFFER_H +#define OUTPUT_BUFFER_H + +#include "pcm_utils.h" +#include "mpd_types.h" +#include "decode.h" +#include "audio.h" +#include "inputStream.h" +#include "metadataChunk.h" +#include "replayGain.h" + +#define OUTPUT_BUFFER_DC_STOP -1 +#define OUTPUT_BUFFER_DC_SEEK -2 + +#define BUFFERED_METACHUNKS 25 + +typedef struct _OutputBuffer { + char *volatile chunks; + mpd_uint16 *volatile chunkSize; + mpd_uint16 *volatile bitRate; + float *volatile times; + mpd_sint16 volatile begin; + mpd_sint16 volatile end; + AudioFormat audioFormat; + ConvState convState; + MetadataChunk metadataChunks[BUFFERED_METACHUNKS]; + mpd_sint8 metaChunkSet[BUFFERED_METACHUNKS]; + mpd_sint8 *volatile metaChunk; + volatile mpd_sint8 acceptMetadata; +} OutputBuffer; + +void clearOutputBuffer(OutputBuffer * cb); + +void flushOutputBuffer(OutputBuffer * cb); + +/* we send inStream for buffering the inputStream while waiting to + send the next chunk */ +int sendDataToOutputBuffer(OutputBuffer * cb, + InputStream * inStream, + DecoderControl * dc, + int seekable, + void *data, + long datalen, + float time, + mpd_uint16 bitRate, ReplayGainInfo * replayGainInfo); + +int copyMpdTagToOutputBuffer(OutputBuffer * cb, MpdTag * tag); + +void clearAllMetaChunkSets(OutputBuffer * cb); + +#endif diff --git a/trunk/src/path.c b/trunk/src/path.c new file mode 100644 index 000000000..f30eb0793 --- /dev/null +++ b/trunk/src/path.c @@ -0,0 +1,310 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "path.h" +#include "log.h" +#include "charConv.h" +#include "conf.h" +#include "utf8.h" +#include "utils.h" + +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#include <dirent.h> + +#ifdef HAVE_LOCALE +#ifdef HAVE_LANGINFO_CODESET +#include <locale.h> +#include <langinfo.h> +#endif +#endif + +const char *musicDir; +static const char *playlistDir; +static char *fsCharset; + +static char *pathConvCharset(char *to, char *from, char *str, char *ret) +{ + if (ret) + free(ret); + return setCharSetConversion(to, from) ? NULL : convStrDup(str); +} + +char *fsCharsetToUtf8(char *str) +{ + static char *ret; + + ret = pathConvCharset("UTF-8", fsCharset, str, ret); + + if (ret && !validUtf8String(ret)) { + free(ret); + ret = NULL; + } + + return ret; +} + +char *utf8ToFsCharset(char *str) +{ + static char *ret; + + ret = pathConvCharset(fsCharset, "UTF-8", str, ret); + + if (!ret) + ret = xstrdup(str); + + return ret; +} + +void setFsCharset(char *charset) +{ + int error = 0; + + if (fsCharset) + free(fsCharset); + + fsCharset = xstrdup(charset); + + DEBUG("setFsCharset: fs charset is: %s\n", fsCharset); + + if (setCharSetConversion("UTF-8", fsCharset) != 0) { + WARNING("fs charset conversion problem: " + "not able to convert from \"%s\" to \"%s\"\n", + fsCharset, "UTF-8"); + error = 1; + } + if (setCharSetConversion(fsCharset, "UTF-8") != 0) { + WARNING("fs charset conversion problem: " + "not able to convert from \"%s\" to \"%s\"\n", + "UTF-8", fsCharset); + error = 1; + } + + if (error) { + free(fsCharset); + WARNING("setting fs charset to ISO-8859-1!\n"); + fsCharset = xstrdup("ISO-8859-1"); + } +} + +char *getFsCharset(void) +{ + return fsCharset; +} + +static char *appendSlash(char **path) +{ + char *temp = *path; + int len = strlen(temp); + + if (temp[len - 1] != '/') { + temp = xmalloc(len + 2); + memset(temp, 0, len + 2); + memcpy(temp, *path, len); + temp[len] = '/'; + free(*path); + *path = temp; + } + + return temp; +} + +void initPaths(void) +{ + ConfigParam *musicParam = parseConfigFilePath(CONF_MUSIC_DIR, 1); + ConfigParam *playlistParam = parseConfigFilePath(CONF_PLAYLIST_DIR, 1); + ConfigParam *fsCharsetParam = getConfigParam(CONF_FS_CHARSET); + + char *charset = NULL; + char *originalLocale; + DIR *dir; + + musicDir = appendSlash(&(musicParam->value)); + playlistDir = appendSlash(&(playlistParam->value)); + + if ((dir = opendir(playlistDir)) == NULL) { + FATAL("cannot open %s \"%s\" (config line %i): %s\n", + CONF_PLAYLIST_DIR, playlistParam->value, + playlistParam->line, strerror(errno)); + } + closedir(dir); + + if ((dir = opendir(musicDir)) == NULL) { + FATAL("cannot open %s \"%s\" (config line %i): %s\n", + CONF_MUSIC_DIR, musicParam->value, + musicParam->line, strerror(errno)); + } + closedir(dir); + + if (fsCharsetParam) { + charset = xstrdup(fsCharsetParam->value); + } +#ifdef HAVE_LOCALE +#ifdef HAVE_LANGINFO_CODESET + else if ((originalLocale = setlocale(LC_CTYPE, NULL))) { + char *temp; + char *currentLocale; + originalLocale = xstrdup(originalLocale); + + if (!(currentLocale = setlocale(LC_CTYPE, ""))) { + WARNING("problems setting current locale with " + "setlocale()\n"); + } else { + if (strcmp(currentLocale, "C") == 0 || + strcmp(currentLocale, "POSIX") == 0) { + WARNING("current locale is \"%s\"\n", + currentLocale); + } else if ((temp = nl_langinfo(CODESET))) { + charset = xstrdup(temp); + } else + WARNING + ("problems getting charset for locale\n"); + if (!setlocale(LC_CTYPE, originalLocale)) { + WARNING + ("problems resetting locale with setlocale()\n"); + } + } + + free(originalLocale); + } else + WARNING("problems getting locale with setlocale()\n"); +#endif +#endif + + if (charset) { + setFsCharset(charset); + free(charset); + } else { + WARNING("setting filesystem charset to ISO-8859-1\n"); + setFsCharset("ISO-8859-1"); + } +} + +void finishPaths(void) +{ + free(fsCharset); + fsCharset = NULL; +} + +static char *pfx_path(const char *path, const char *pfx, const size_t pfx_len) +{ + static char ret[MAXPATHLEN+1]; + size_t rp_len = strlen(path); + + /* check for the likely condition first: */ + if (mpd_likely((pfx_len + rp_len) < MAXPATHLEN)) { + memcpy(ret, pfx, pfx_len); + memcpy(ret + pfx_len, path, rp_len + 1); + return ret; + } + + /* unlikely, return an empty string because truncating would + * also be wrong... break early and break loudly (the system + * headers are likely screwed, not mpd) */ + ERROR("Cannot prefix '%s' to '%s', max: %d", pfx, path, MAXPATHLEN); + ret[0] = '\0'; + return ret; +} + +char *rmp2amp(char *relativePath) +{ + size_t pfx_len = strlen(musicDir); + return pfx_path(relativePath, musicDir, pfx_len); +} + +char *rpp2app(char *relativePath) +{ + size_t pfx_len = strlen(playlistDir); + return pfx_path(relativePath, playlistDir, pfx_len); +} + +/* this is actually like strlcpy (OpenBSD), but we don't actually want to + * blindly use it everywhere, only for paths that are OK to truncate (for + * error reporting and such */ +void pathcpy_trunc(char *dest, const char *src) +{ + size_t len = strlen(src); + + if (mpd_unlikely(len > MAXPATHLEN)) + len = MAXPATHLEN; + memcpy(dest, src, len); + dest[len] = '\0'; +} + +char *parentPath(char *path) +{ + static char parentPath[MAXPATHLEN+1]; + char *c; + + pathcpy_trunc(parentPath, path); + c = strrchr(parentPath,'/'); + + if (c == NULL) + parentPath[0] = '\0'; + else { + while ((parentPath <= c) && *(--c) == '/') /* nothing */ + ; + c[1] = '\0'; + } + + return parentPath; +} + +char *sanitizePathDup(char *path) +{ + int len = strlen(path) + 1; + char *ret = xmalloc(len); + char *cp = ret; + + memset(ret, 0, len); + + len = 0; + + /* eliminate more than one '/' in a row, like "///" */ + while (*path) { + while (*path == '/') + path++; + if (*path == '.') { + /* we don't want to have hidden directories, or '.' or + ".." in our path */ + free(ret); + return NULL; + } + while (*path && *path != '/') { + *(cp++) = *(path++); + len++; + } + if (*path == '/') { + *(cp++) = *(path++); + len++; + } + } + + if (len && ret[len - 1] == '/') { + len--; + ret[len] = '\0'; + } + + DEBUG("sanitized: %s\n", ret); + + return xrealloc(ret, len + 1); +} diff --git a/trunk/src/path.h b/trunk/src/path.h new file mode 100644 index 000000000..2357aa25d --- /dev/null +++ b/trunk/src/path.h @@ -0,0 +1,69 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef PATH_H +#define PATH_H + +#include "../config.h" + +#include <sys/param.h> + +extern const char *musicDir; + +void initPaths(void); + +void finishPaths(void); + +/* utf8ToFsCharset() and fsCharsetToUtf8() + * Each returns a static pointer to a dynamically allocated buffer + * which means: + * - Do not manually free the return value of these functions, it'll be + * automatically freed the next time it is called. + * - They are not reentrant, xstrdup the return value immediately if + * you expect to call one of these functions again, but still need the + * previous result. + * - The static pointer is unique to each function. + */ +char *utf8ToFsCharset(char *str); +char *fsCharsetToUtf8(char *str); + +void setFsCharset(char *charset); + +char *getFsCharset(void); + +/* relative music path to absolute music path + * char * passed is a static variable, so don't free it + */ +char *rmp2amp(char *file); + +/* static char * returned */ +char *rpp2app(char *file); + +/* static char * returned */ +char *parentPath(char *path); + +/* strips extra "///" and leading "/" and trailing "/" */ +char *sanitizePathDup(char *path); + +/* this is actually like strlcpy (OpenBSD), but we don't actually want to + * blindly use it everywhere, only for paths that are OK to truncate (for + * error reporting and such. + * dest must be MAXPATHLEN+1 bytes large (which is standard in mpd) */ +void pathcpy_trunc(char *dest, const char *src); + +#endif diff --git a/trunk/src/pcm_utils.c b/trunk/src/pcm_utils.c new file mode 100644 index 000000000..534095620 --- /dev/null +++ b/trunk/src/pcm_utils.c @@ -0,0 +1,470 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "pcm_utils.h" + +#include "mpd_types.h" +#include "log.h" +#include "utils.h" +#include "conf.h" + +#include <string.h> +#include <math.h> +#include <assert.h> + +void pcm_volumeChange(char *buffer, int bufferSize, AudioFormat * format, + int volume) +{ + mpd_sint32 temp32; + mpd_sint8 *buffer8 = (mpd_sint8 *) buffer; + mpd_sint16 *buffer16 = (mpd_sint16 *) buffer; + + if (volume >= 1000) + return; + + if (volume <= 0) { + memset(buffer, 0, bufferSize); + return; + } + + switch (format->bits) { + case 16: + while (bufferSize > 0) { + temp32 = *buffer16; + temp32 *= volume; + temp32 += rand() & 511; + temp32 -= rand() & 511; + temp32 += 500; + temp32 /= 1000; + *buffer16 = temp32 > 32767 ? 32767 : + (temp32 < -32768 ? -32768 : temp32); + buffer16++; + bufferSize -= 2; + } + break; + case 8: + while (bufferSize > 0) { + temp32 = *buffer8; + temp32 *= volume; + temp32 += rand() & 511; + temp32 -= rand() & 511; + temp32 += 500; + temp32 /= 1000; + *buffer8 = temp32 > 127 ? 127 : + (temp32 < -128 ? -128 : temp32); + buffer8++; + bufferSize--; + } + break; + default: + FATAL("%i bits not supported by pcm_volumeChange!\n", + format->bits); + } +} + +static void pcm_add(char *buffer1, char *buffer2, size_t bufferSize1, + size_t bufferSize2, int vol1, int vol2, + AudioFormat * format) +{ + mpd_sint32 temp32; + mpd_sint8 *buffer8_1 = (mpd_sint8 *) buffer1; + mpd_sint8 *buffer8_2 = (mpd_sint8 *) buffer2; + mpd_sint16 *buffer16_1 = (mpd_sint16 *) buffer1; + mpd_sint16 *buffer16_2 = (mpd_sint16 *) buffer2; + + switch (format->bits) { + case 16: + while (bufferSize1 > 0 && bufferSize2 > 0) { + temp32 = + (vol1 * (*buffer16_1) + + vol2 * (*buffer16_2)); + temp32 += rand() & 511; + temp32 -= rand() & 511; + temp32 += 500; + temp32 /= 1000; + *buffer16_1 = + temp32 > 32767 ? 32767 : (temp32 < + -32768 ? -32768 : temp32); + buffer16_1++; + buffer16_2++; + bufferSize1 -= 2; + bufferSize2 -= 2; + } + if (bufferSize2 > 0) + memcpy(buffer16_1, buffer16_2, bufferSize2); + break; + case 8: + while (bufferSize1 > 0 && bufferSize2 > 0) { + temp32 = + (vol1 * (*buffer8_1) + vol2 * (*buffer8_2)); + temp32 += rand() & 511; + temp32 -= rand() & 511; + temp32 += 500; + temp32 /= 1000; + *buffer8_1 = + temp32 > 127 ? 127 : (temp32 < + -128 ? -128 : temp32); + buffer8_1++; + buffer8_2++; + bufferSize1--; + bufferSize2--; + } + if (bufferSize2 > 0) + memcpy(buffer8_1, buffer8_2, bufferSize2); + break; + default: + FATAL("%i bits not supported by pcm_add!\n", format->bits); + } +} + +void pcm_mix(char *buffer1, char *buffer2, size_t bufferSize1, + size_t bufferSize2, AudioFormat * format, float portion1) +{ + int vol1; + float s = sin(M_PI_2 * portion1); + s *= s; + + vol1 = s * 1000 + 0.5; + vol1 = vol1 > 1000 ? 1000 : (vol1 < 0 ? 0 : vol1); + + pcm_add(buffer1, buffer2, bufferSize1, bufferSize2, vol1, 1000 - vol1, + format); +} + +#ifdef HAVE_LIBSAMPLERATE +static int pcm_getSampleRateConverter(void) +{ + const char *conf = getConfigParamValue(CONF_SAMPLERATE_CONVERTER); + long convalgo; + char *test; + size_t len; + + if (!conf) { + convalgo = SRC_SINC_FASTEST; + goto out; + } + + convalgo = strtol(conf, &test, 10); + if (*test == '\0' && src_get_name(convalgo)) + goto out; + + len = strlen(conf); + for (convalgo = 0 ; ; convalgo++) { + test = (char *)src_get_name(convalgo); + if (!test) { + convalgo = SRC_SINC_FASTEST; + break; + } + if (strncasecmp(test, conf, len) == 0) + goto out; + } + + ERROR("unknown samplerate converter \"%s\"\n", conf); +out: + DEBUG("selecting samplerate converter \"%s\"\n", + src_get_name(convalgo)); + + return convalgo; +} +#endif + +#ifdef HAVE_LIBSAMPLERATE +static size_t pcm_convertSampleRate(mpd_sint8 channels, mpd_uint32 inSampleRate, + char *inBuffer, size_t inSize, + mpd_uint32 outSampleRate, char *outBuffer, + size_t outSize, ConvState *convState) +{ + static int convalgo = -1; + SRC_DATA *data = &convState->data; + size_t dataInSize; + size_t dataOutSize; + int error; + + if (convalgo < 0) + convalgo = pcm_getSampleRateConverter(); + + /* (re)set the state/ratio if the in or out format changed */ + if ((channels != convState->lastChannels) || + (inSampleRate != convState->lastInSampleRate) || + (outSampleRate != convState->lastOutSampleRate)) { + convState->error = 0; + convState->lastChannels = channels; + convState->lastInSampleRate = inSampleRate; + convState->lastOutSampleRate = outSampleRate; + + if (convState->state) + convState->state = src_delete(convState->state); + + convState->state = src_new(convalgo, channels, &error); + if (!convState->state) { + ERROR("cannot create new libsamplerate state: %s\n", + src_strerror(error)); + convState->error = 1; + return 0; + } + + data->src_ratio = (double)outSampleRate / (double)inSampleRate; + DEBUG("setting samplerate conversion ratio to %.2lf\n", + data->src_ratio); + src_set_ratio(convState->state, data->src_ratio); + } + + /* there was an error previously, and nothing has changed */ + if (convState->error) + return 0; + + data->input_frames = inSize / 2 / channels; + dataInSize = data->input_frames * sizeof(float) * channels; + if (dataInSize > convState->dataInSize) { + convState->dataInSize = dataInSize; + data->data_in = xrealloc(data->data_in, dataInSize); + } + + data->output_frames = outSize / 2 / channels; + dataOutSize = data->output_frames * sizeof(float) * channels; + if (dataOutSize > convState->dataOutSize) { + convState->dataOutSize = dataOutSize; + data->data_out = xrealloc(data->data_out, dataOutSize); + } + + src_short_to_float_array((short *)inBuffer, data->data_in, + data->input_frames * channels); + + error = src_process(convState->state, data); + if (error) { + ERROR("error processing samples with libsamplerate: %s\n", + src_strerror(error)); + convState->error = 1; + return 0; + } + + src_float_to_short_array(data->data_out, (short *)outBuffer, + data->output_frames_gen * channels); + + return data->output_frames_gen * 2 * channels; +} +#else /* !HAVE_LIBSAMPLERATE */ +/* resampling code blatantly ripped from ESD */ +static size_t pcm_convertSampleRate(mpd_sint8 channels, mpd_uint32 inSampleRate, + char *inBuffer, size_t inSize, + mpd_uint32 outSampleRate, char *outBuffer, + size_t outSize, ConvState *convState) +{ + mpd_uint32 rd_dat = 0; + mpd_uint32 wr_dat = 0; + mpd_sint16 *in = (mpd_sint16 *)inBuffer; + mpd_sint16 *out = (mpd_sint16 *)outBuffer; + mpd_uint32 nlen = outSize / 2; + mpd_sint16 lsample, rsample; + + switch (channels) { + case 1: + while (wr_dat < nlen) { + rd_dat = wr_dat * inSampleRate / outSampleRate; + + lsample = in[rd_dat++]; + + out[wr_dat++] = lsample; + } + break; + case 2: + while (wr_dat < nlen) { + rd_dat = wr_dat * inSampleRate / outSampleRate; + rd_dat &= ~1; + + lsample = in[rd_dat++]; + rsample = in[rd_dat++]; + + out[wr_dat++] = lsample; + out[wr_dat++] = rsample; + } + break; + } + + return outSize; +} +#endif /* !HAVE_LIBSAMPLERATE */ + +static char *pcm_convertChannels(mpd_sint8 channels, char *inBuffer, + size_t inSize, size_t *outSize) +{ + static char *buf; + static size_t len; + char *outBuffer = NULL;; + mpd_sint16 *in; + mpd_sint16 *out; + int inSamples, i; + + switch (channels) { + /* convert from 1 -> 2 channels */ + case 1: + *outSize = (inSize >> 1) << 2; + if (*outSize > len) { + len = *outSize; + buf = xrealloc(buf, len); + } + outBuffer = buf; + + inSamples = inSize >> 1; + in = (mpd_sint16 *)inBuffer; + out = (mpd_sint16 *)outBuffer; + for (i = 0; i < inSamples; i++) { + *out++ = *in; + *out++ = *in++; + } + + break; + /* convert from 2 -> 1 channels */ + case 2: + *outSize = inSize >> 1; + if (*outSize > len) { + len = *outSize; + buf = xrealloc(buf, len); + } + outBuffer = buf; + + inSamples = inSize >> 2; + in = (mpd_sint16 *)inBuffer; + out = (mpd_sint16 *)outBuffer; + for (i = 0; i < inSamples; i++) { + *out = (*in++) / 2; + *out++ += (*in++) / 2; + } + + break; + default: + ERROR("only 1 or 2 channels are supported for conversion!\n"); + } + + return outBuffer; +} + +static char *pcm_convertTo16bit(mpd_sint8 bits, char *inBuffer, size_t inSize, + size_t *outSize) +{ + static char *buf; + static size_t len; + char *outBuffer = NULL; + mpd_sint8 *in; + mpd_sint16 *out; + int i; + + switch (bits) { + case 8: + *outSize = inSize << 1; + if (*outSize > len) { + len = *outSize; + buf = xrealloc(buf, len); + } + outBuffer = buf; + + in = (mpd_sint8 *)inBuffer; + out = (mpd_sint16 *)outBuffer; + for (i = 0; i < inSize; i++) + *out++ = (*in++) << 8; + + break; + case 16: + *outSize = inSize; + outBuffer = inBuffer; + break; + case 24: + /* put dithering code from mp3_decode here */ + default: + ERROR("only 8 or 16 bits are supported for conversion!\n"); + } + + return outBuffer; +} + +/* outFormat bits must be 16 and channels must be 1 or 2! */ +size_t pcm_convertAudioFormat(AudioFormat * inFormat, char *inBuffer, + size_t inSize, AudioFormat * outFormat, + char *outBuffer, ConvState *convState) +{ + char *buf; + size_t len; + size_t outSize = pcm_sizeOfConvBuffer(inFormat, inSize, outFormat); + + assert(outFormat->bits == 16); + assert(outFormat->channels == 2 || outFormat->channels == 1); + + /* everything else supports 16 bit only, so convert to that first */ + buf = pcm_convertTo16bit(inFormat->bits, inBuffer, inSize, &len); + if (!buf) + exit(EXIT_FAILURE); + + if (inFormat->channels != outFormat->channels) { + buf = pcm_convertChannels(inFormat->channels, buf, len, &len); + if (!buf) + exit(EXIT_FAILURE); + } + + if (inFormat->sampleRate == outFormat->sampleRate) { + assert(outSize >= len); + memcpy(outBuffer, buf, len); + } else { + len = pcm_convertSampleRate(outFormat->channels, + inFormat->sampleRate, buf, len, + outFormat->sampleRate, outBuffer, + outSize, convState); + if (len == 0) + exit(EXIT_FAILURE); + } + + return len; +} + +size_t pcm_sizeOfConvBuffer(AudioFormat * inFormat, size_t inSize, + AudioFormat * outFormat) +{ + const double ratio = (double)outFormat->sampleRate / + (double)inFormat->sampleRate; + const int shift = 2 * outFormat->channels; + size_t outSize = inSize; + + switch (inFormat->bits) { + case 8: + outSize <<= 1; + break; + case 16: + break; + default: + FATAL("only 8 or 16 bits are supported for conversion!\n"); + } + + if (inFormat->channels != outFormat->channels) { + switch (inFormat->channels) { + case 1: + outSize = (outSize >> 1) << 2; + break; + case 2: + outSize >>= 1; + break; + default: + FATAL("only 1 or 2 channels are supported " + "for conversion!\n"); + } + } + + outSize /= shift; + outSize = floor(0.5 + (double)outSize * ratio); + outSize *= shift; + + return outSize; +} diff --git a/trunk/src/pcm_utils.h b/trunk/src/pcm_utils.h new file mode 100644 index 000000000..2c6610a75 --- /dev/null +++ b/trunk/src/pcm_utils.h @@ -0,0 +1,57 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef PCM_UTILS_H +#define PCM_UTILS_H + +#include "../config.h" + +#include "audio.h" + +#include <stdlib.h> + +#ifdef HAVE_LIBSAMPLERATE +#include <samplerate.h> +#endif + +typedef struct _ConvState { +#ifdef HAVE_LIBSAMPLERATE + SRC_STATE *state; + SRC_DATA data; + size_t dataInSize; + size_t dataOutSize; + mpd_sint8 lastChannels; + mpd_sint32 lastInSampleRate; + mpd_sint32 lastOutSampleRate; + int error; +#endif +} ConvState; + +void pcm_volumeChange(char *buffer, int bufferSize, AudioFormat * format, + int volume); + +void pcm_mix(char *buffer1, char *buffer2, size_t bufferSize1, + size_t bufferSize2, AudioFormat * format, float portion1); + +size_t pcm_convertAudioFormat(AudioFormat * inFormat, char *inBuffer, + size_t inSize, AudioFormat * outFormat, + char *outBuffer, ConvState *convState); + +size_t pcm_sizeOfConvBuffer(AudioFormat * inFormat, size_t inSize, + AudioFormat * outFormat); +#endif diff --git a/trunk/src/permission.c b/trunk/src/permission.c new file mode 100644 index 000000000..3d597052c --- /dev/null +++ b/trunk/src/permission.c @@ -0,0 +1,140 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "permission.h" + +#include "conf.h" +#include "list.h" +#include "log.h" +#include "utils.h" + +#include <string.h> + +#define PERMISSION_PASSWORD_CHAR "@" +#define PERMISSION_SEPERATOR "," + +#define PERMISSION_READ_STRING "read" +#define PERMISSION_ADD_STRING "add" +#define PERMISSION_CONTROL_STRING "control" +#define PERMISSION_ADMIN_STRING "admin" + +static List *permission_passwords; + +static int permission_default; + +static int parsePermissions(char *string) +{ + int permission = 0; + char *temp; + char *tok; + + if (!string) + return 0; + + temp = strtok_r(string, PERMISSION_SEPERATOR, &tok); + while (temp) { + if (strcmp(temp, PERMISSION_READ_STRING) == 0) { + permission |= PERMISSION_READ; + } else if (strcmp(temp, PERMISSION_ADD_STRING) == 0) { + permission |= PERMISSION_ADD; + } else if (strcmp(temp, PERMISSION_CONTROL_STRING) == 0) { + permission |= PERMISSION_CONTROL; + } else if (strcmp(temp, PERMISSION_ADMIN_STRING) == 0) { + permission |= PERMISSION_ADMIN; + } else { + FATAL("unknown permission \"%s\"\n", temp); + } + + temp = strtok_r(NULL, PERMISSION_SEPERATOR, &tok); + } + + return permission; +} + +void initPermissions(void) +{ + char *temp; + char *cp2; + char *password; + int *permission; + ConfigParam *param; + + permission_passwords = makeList(free, 1); + + permission_default = PERMISSION_READ | PERMISSION_ADD | + PERMISSION_CONTROL | PERMISSION_ADMIN; + + param = getNextConfigParam(CONF_PASSWORD, NULL); + + if (param) { + permission_default = 0; + + do { + if (!strstr(param->value, PERMISSION_PASSWORD_CHAR)) { + FATAL("\"%s\" not found in password string " + "\"%s\", line %i\n", + PERMISSION_PASSWORD_CHAR, + param->value, param->line); + } + + if (!(temp = strtok_r(param->value, + PERMISSION_PASSWORD_CHAR, + &cp2))) { + FATAL("something weird just happened in permission.c\n"); + } + + password = temp; + + permission = xmalloc(sizeof(int)); + *permission = + parsePermissions(strtok_r(NULL, "", &cp2)); + + insertInList(permission_passwords, password, + permission); + } while ((param = getNextConfigParam(CONF_PASSWORD, param))); + } + + param = getConfigParam(CONF_DEFAULT_PERMS); + + if (param) + permission_default = parsePermissions(param->value); + + sortList(permission_passwords); +} + +int getPermissionFromPassword(char *password, int *permission) +{ + void *foundPermission; + + if (findInList(permission_passwords, password, &foundPermission)) { + *permission = *((int *)foundPermission); + return 0; + } + + return -1; +} + +void finishPermissions(void) +{ + freeList(permission_passwords); +} + +int getDefaultPermissions(void) +{ + return permission_default; +} diff --git a/trunk/src/permission.h b/trunk/src/permission.h new file mode 100644 index 000000000..bd4257080 --- /dev/null +++ b/trunk/src/permission.h @@ -0,0 +1,39 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef PERMISSION_H +#define PERMISSION_H + +#include "../config.h" + +#define PERMISSION_NONE 0 +#define PERMISSION_READ 1 +#define PERMISSION_ADD 2 +#define PERMISSION_CONTROL 4 +#define PERMISSION_ADMIN 8 + + +int getPermissionFromPassword(char *password, int *permission); + +void finishPermissions(void); + +int getDefaultPermissions(void); + +void initPermissions(void); + +#endif diff --git a/trunk/src/player.c b/trunk/src/player.c new file mode 100644 index 000000000..7c92d088e --- /dev/null +++ b/trunk/src/player.c @@ -0,0 +1,530 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "player.h" +#include "path.h" +#include "decode.h" +#include "command.h" +#include "interface.h" +#include "playlist.h" +#include "ls.h" +#include "listen.h" +#include "log.h" +#include "utils.h" +#include "directory.h" +#include "volume.h" +#include "playerData.h" +#include "permission.h" +#include "sig_handlers.h" + +#include <stdio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <sys/time.h> +#include <sys/wait.h> +#include <stdlib.h> +#include <signal.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> + +volatile int player_pid = 0; + +void clearPlayerPid(void) +{ + player_pid = 0; +} + +static void resetPlayerMetadata(void) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + if (pc->metadataState == PLAYER_METADATA_STATE_READ) { + pc->metadataState = PLAYER_METADATA_STATE_WRITE; + } +} + +static void resetPlayer(void) +{ + int pid; + + clearPlayerPid(); + getPlayerData()->playerControl.stop = 0; + getPlayerData()->playerControl.play = 0; + getPlayerData()->playerControl.pause = 0; + getPlayerData()->playerControl.lockQueue = 0; + getPlayerData()->playerControl.unlockQueue = 0; + getPlayerData()->playerControl.state = PLAYER_STATE_STOP; + getPlayerData()->playerControl.queueState = PLAYER_QUEUE_UNLOCKED; + getPlayerData()->playerControl.seek = 0; + getPlayerData()->playerControl.metadataState = + PLAYER_METADATA_STATE_WRITE; + pid = getPlayerData()->playerControl.decode_pid; + if (pid > 0) + kill(pid, SIGTERM); + getPlayerData()->playerControl.decode_pid = 0; +} + +void player_sigChldHandler(int pid, int status) +{ + if (player_pid == pid) + { + DEBUG("SIGCHLD caused by player process\n"); + if (WIFSIGNALED(status) && + WTERMSIG(status) != SIGTERM && + WTERMSIG(status) != SIGINT) + { + ERROR("player process died from signal: %i\n", + WTERMSIG(status)); + } + resetPlayer(); + } + else if (pid == getPlayerData()->playerControl.decode_pid && + player_pid <= 0) + { + if (WIFSIGNALED(status) && WTERMSIG(status) != SIGTERM) + { + ERROR("(caught by master parent) " + "decode process died from a " + "non-TERM signal: %i\n", WTERMSIG(status)); + } + getPlayerData()->playerControl.decode_pid = 0; + } +} + +int playerInit(void) +{ + blockSignals(); + player_pid = fork(); + if (player_pid==0) + { + clock_t start = clock(); + + PlayerControl *pc = &(getPlayerData()->playerControl); + + unblockSignals(); + + setSigHandlersForDecoder(); + + closeAllListenSockets(); + freeAllInterfaces(); + finishPlaylist(); + closeMp3Directory(); + finishPermissions(); + finishCommands(); + finishVolume(); + + DEBUG("took %f to init player\n", + (float)(clock()-start)/CLOCKS_PER_SEC); + + while (1) { + if (pc->play) + decode(); + else if (pc->stop) + pc->stop = 0; + else if (pc->seek) + pc->seek = 0; + else if (pc->pause) + pc->pause = 0; + else if (pc->closeAudio) { + closeAudioDevice(); + pc->closeAudio = 0; + kill(getppid(), SIGUSR1); + } else if (pc->lockQueue) { + pc->queueLockState = PLAYER_QUEUE_LOCKED; + pc->lockQueue = 0; + } else if (pc->unlockQueue) { + pc->queueLockState = PLAYER_QUEUE_UNLOCKED; + pc->unlockQueue = 0; + } else if (pc->cycleLogFiles) { + cycle_log_files(); + pc->cycleLogFiles = 0; + } else + my_usleep(10000); + } + + exit(EXIT_SUCCESS); + } + else if (player_pid < 0) + { + unblockSignals(); + ERROR("player Problems fork()'ing\n"); + player_pid = 0; + return -1; + } + + unblockSignals(); + + return 0; +} + +int playerPlay(int fd, Song * song) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + if (playerStop(fd) < 0) + return -1; + + if (song->tag) + pc->fileTime = song->tag->time; + else + pc->fileTime = 0; + + copyMpdTagToMetadataChunk(song->tag, &(pc->fileMetadataChunk)); + + pathcpy_trunc(pc->utf8url, getSongUrl(song)); + + pc->play = 1; + if (player_pid == 0 && playerInit() < 0) { + pc->play = 0; + return -1; + } + + resetPlayerMetadata(); + while (player_pid > 0 && pc->play) + my_usleep(1000); + + return 0; +} + +int playerStop(int fd) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + if (player_pid > 0 && pc->state != PLAYER_STATE_STOP) { + pc->stop = 1; + while (player_pid > 0 && pc->stop) + my_usleep(1000); + } + + pc->queueState = PLAYER_QUEUE_BLANK; + playerQueueUnlock(); + + return 0; +} + +void playerKill(void) +{ + int pid; + /*PlayerControl * pc = &(getPlayerData()->playerControl); + + playerStop(stderr); + playerCloseAudio(stderr); + if(player_pid>0 && pc->closeAudio) sleep(1); */ + + pid = player_pid; + if (pid > 0) + kill(pid, SIGTERM); +} + +int playerPause(int fd) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + if (player_pid > 0 && pc->state != PLAYER_STATE_STOP) { + pc->pause = 1; + while (player_pid > 0 && pc->pause) + my_usleep(1000); + } + + return 0; +} + +int playerSetPause(int fd, int pause) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + if (player_pid <= 0) + return 0; + + switch (pc->state) { + case PLAYER_STATE_PLAY: + if (pause) + playerPause(fd); + break; + case PLAYER_STATE_PAUSE: + if (!pause) + playerPause(fd); + break; + } + + return 0; +} + +int getPlayerElapsedTime(void) +{ + return (int)(getPlayerData()->playerControl.elapsedTime + 0.5); +} + +unsigned long getPlayerBitRate(void) +{ + return getPlayerData()->playerControl.bitRate; +} + +int getPlayerTotalTime(void) +{ + return (int)(getPlayerData()->playerControl.totalTime + 0.5); +} + +int getPlayerState(void) +{ + return getPlayerData()->playerControl.state; +} + +void clearPlayerError(void) +{ + getPlayerData()->playerControl.error = 0; +} + +int getPlayerError(void) +{ + return getPlayerData()->playerControl.error; +} + +char *getPlayerErrorStr(void) +{ + static char *error; + int errorlen = MAXPATHLEN + 1024; + PlayerControl *pc = &(getPlayerData()->playerControl); + + error = xrealloc(error, errorlen); + error[0] = '\0'; + + switch (pc->error) { + case PLAYER_ERROR_FILENOTFOUND: + snprintf(error, errorlen, + "file \"%s\" does not exist or is inaccessible", + pc->erroredUrl); + break; + case PLAYER_ERROR_FILE: + snprintf(error, errorlen, "problems decoding \"%s\"", + pc->erroredUrl); + break; + case PLAYER_ERROR_AUDIO: + snprintf(error, errorlen, "problems opening audio device"); + break; + case PLAYER_ERROR_SYSTEM: + snprintf(error, errorlen, "system error occured"); + break; + case PLAYER_ERROR_UNKTYPE: + snprintf(error, errorlen, "file type of \"%s\" is unknown", + pc->erroredUrl); + default: + break; + } + + errorlen = strlen(error); + error = xrealloc(error, errorlen + 1); + + if (errorlen) + return error; + + return NULL; +} + +void playerCloseAudio(void) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + if (player_pid > 0) { + if (playerStop(STDERR_FILENO) < 0) + return; + pc->closeAudio = 1; + } +} + +int queueSong(Song * song) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + if (pc->queueState == PLAYER_QUEUE_BLANK) { + pathcpy_trunc(pc->utf8url, getSongUrl(song)); + + if (song->tag) + pc->fileTime = song->tag->time; + else + pc->fileTime = 0; + + copyMpdTagToMetadataChunk(song->tag, &(pc->fileMetadataChunk)); + + pc->queueState = PLAYER_QUEUE_FULL; + return 0; + } + + return -1; +} + +int getPlayerQueueState(void) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + return pc->queueState; +} + +void setQueueState(int queueState) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + pc->queueState = queueState; +} + +void playerQueueLock(void) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + if (player_pid > 0 && pc->queueLockState == PLAYER_QUEUE_UNLOCKED) { + pc->lockQueue = 1; + while (player_pid > 0 && pc->lockQueue) + my_usleep(1000); + } +} + +void playerQueueUnlock(void) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + if (player_pid > 0 && pc->queueLockState == PLAYER_QUEUE_LOCKED) { + pc->unlockQueue = 1; + while (player_pid > 0 && pc->unlockQueue) + my_usleep(1000); + } +} + +int playerSeek(int fd, Song * song, float time) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + if (pc->state == PLAYER_STATE_STOP) { + commandError(fd, ACK_ERROR_PLAYER_SYNC, + "player not currently playing"); + return -1; + } + + if (strcmp(pc->utf8url, getSongUrl(song)) != 0) { + if (song->tag) + pc->fileTime = song->tag->time; + else + pc->fileTime = 0; + + copyMpdTagToMetadataChunk(song->tag, &(pc->fileMetadataChunk)); + + pathcpy_trunc(pc->utf8url, getSongUrl(song)); + } + + if (pc->error == PLAYER_ERROR_NOERROR) { + resetPlayerMetadata(); + pc->seekWhere = time; + pc->seek = 1; + while (player_pid > 0 && pc->seek) + my_usleep(1000); + } + + return 0; +} + +float getPlayerCrossFade(void) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + return pc->crossFade; +} + +void setPlayerCrossFade(float crossFadeInSeconds) +{ + PlayerControl *pc; + if (crossFadeInSeconds < 0) + crossFadeInSeconds = 0; + + pc = &(getPlayerData()->playerControl); + + pc->crossFade = crossFadeInSeconds; +} + +void setPlayerSoftwareVolume(int volume) +{ + PlayerControl *pc; + volume = (volume > 1000) ? 1000 : (volume < 0 ? 0 : volume); + + pc = &(getPlayerData()->playerControl); + + pc->softwareVolume = volume; +} + +double getPlayerTotalPlayTime(void) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + return pc->totalPlayTime; +} + +unsigned int getPlayerSampleRate(void) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + return pc->sampleRate; +} + +int getPlayerBits(void) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + return pc->bits; +} + +int getPlayerChannels(void) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + + return pc->channels; +} + +void playerCycleLogFiles(void) +{ + PlayerControl *pc = &(getPlayerData()->playerControl); + DecoderControl *dc = &(getPlayerData()->decoderControl); + + pc->cycleLogFiles = 1; + dc->cycleLogFiles = 1; +} + +/* this actually creates a dupe of the current metadata */ +Song *playerCurrentDecodeSong(void) +{ + static Song *song; + static MetadataChunk *prev; + Song *ret = NULL; + PlayerControl *pc = &(getPlayerData()->playerControl); + + if (pc->metadataState == PLAYER_METADATA_STATE_READ) { + DEBUG("playerCurrentDecodeSong: caught new metadata!\n"); + if (prev) + free(prev); + prev = xmalloc(sizeof(MetadataChunk)); + memcpy(prev, &(pc->metadataChunk), sizeof(MetadataChunk)); + if (song) + freeJustSong(song); + song = newNullSong(); + song->url = xstrdup(pc->currentUrl); + song->tag = metadataChunkToMpdTagDup(prev); + ret = song; + resetPlayerMetadata(); + } + + return ret; +} diff --git a/trunk/src/player.h b/trunk/src/player.h new file mode 100644 index 000000000..b62fab2ea --- /dev/null +++ b/trunk/src/player.h @@ -0,0 +1,154 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef PLAYER_H +#define PLAYER_H + +#include "../config.h" + +#include "decode.h" +#include "mpd_types.h" +#include "song.h" +#include "metadataChunk.h" + +#include <stdio.h> +#include <sys/param.h> + +#define PLAYER_STATE_STOP 0 +#define PLAYER_STATE_PAUSE 1 +#define PLAYER_STATE_PLAY 2 + +#define PLAYER_ERROR_NOERROR 0 +#define PLAYER_ERROR_FILE 1 +#define PLAYER_ERROR_AUDIO 2 +#define PLAYER_ERROR_SYSTEM 3 +#define PLAYER_ERROR_UNKTYPE 4 +#define PLAYER_ERROR_FILENOTFOUND 5 + +/* 0->1->2->3->5 regular playback + * ->4->0 don't play queued song + */ +#define PLAYER_QUEUE_BLANK 0 +#define PLAYER_QUEUE_FULL 1 +#define PLAYER_QUEUE_DECODE 2 +#define PLAYER_QUEUE_PLAY 3 +#define PLAYER_QUEUE_STOP 4 +#define PLAYER_QUEUE_EMPTY 5 + +#define PLAYER_QUEUE_UNLOCKED 0 +#define PLAYER_QUEUE_LOCKED 1 + +#define PLAYER_METADATA_STATE_READ 1 +#define PLAYER_METADATA_STATE_WRITE 2 + +typedef struct _PlayerControl { + volatile mpd_sint8 stop; + volatile mpd_sint8 play; + volatile mpd_sint8 pause; + volatile mpd_sint8 state; + volatile mpd_sint8 closeAudio; + volatile mpd_sint8 error; + volatile mpd_uint16 bitRate; + volatile mpd_sint8 bits; + volatile mpd_sint8 channels; + volatile mpd_uint32 sampleRate; + volatile float totalTime; + volatile float elapsedTime; + volatile float fileTime; + char utf8url[MAXPATHLEN + 1]; + char currentUrl[MAXPATHLEN + 1]; + char erroredUrl[MAXPATHLEN + 1]; + volatile mpd_sint8 queueState; + volatile mpd_sint8 queueLockState; + volatile mpd_sint8 lockQueue; + volatile mpd_sint8 unlockQueue; + volatile mpd_sint8 seek; + volatile double seekWhere; + volatile float crossFade; + volatile mpd_uint16 softwareVolume; + volatile double totalPlayTime; + volatile int decode_pid; + volatile mpd_sint8 cycleLogFiles; + volatile mpd_sint8 metadataState; + MetadataChunk metadataChunk; + MetadataChunk fileMetadataChunk; +} PlayerControl; + +void clearPlayerPid(void); + +void player_sigChldHandler(int pid, int status); + +int playerPlay(int fd, Song * song); + +int playerSetPause(int fd, int pause); + +int playerPause(int fd); + +int playerStop(int fd); + +void playerCloseAudio(void); + +void playerKill(void); + +int getPlayerTotalTime(void); + +int getPlayerElapsedTime(void); + +unsigned long getPlayerBitRate(void); + +int getPlayerState(void); + +void clearPlayerError(void); + +char *getPlayerErrorStr(void); + +int getPlayerError(void); + +int playerInit(void); + +int queueSong(Song * song); + +int getPlayerQueueState(void); + +void setQueueState(int queueState); + +void playerQueueLock(void); + +void playerQueueUnlock(void); + +int playerSeek(int fd, Song * song, float time); + +void setPlayerCrossFade(float crossFadeInSeconds); + +float getPlayerCrossFade(void); + +void setPlayerSoftwareVolume(int volume); + +double getPlayerTotalPlayTime(void); + +unsigned int getPlayerSampleRate(void); + +int getPlayerBits(void); + +int getPlayerChannels(void); + +void playerCycleLogFiles(void); + +Song *playerCurrentDecodeSong(void); + +#endif diff --git a/trunk/src/playerData.c b/trunk/src/playerData.c new file mode 100644 index 000000000..30ff6d6d6 --- /dev/null +++ b/trunk/src/playerData.c @@ -0,0 +1,162 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "playerData.h" +#include "conf.h" +#include "log.h" + +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/ipc.h> +#include <sys/shm.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +int buffered_before_play; +int buffered_chunks; + +#define DEFAULT_BUFFER_SIZE 2048 +#define DEFAULT_BUFFER_BEFORE_PLAY 10 + +static PlayerData *playerData_pd; + +void initPlayerData(void) +{ + float perc = DEFAULT_BUFFER_BEFORE_PLAY; + char *test; + int shmid; + int crossfade = 0; + size_t bufferSize = DEFAULT_BUFFER_SIZE; + size_t allocationSize; + OutputBuffer *buffer; + ConfigParam *param; + size_t device_array_size = audio_device_count() * sizeof(mpd_sint8); + + param = getConfigParam(CONF_AUDIO_BUFFER_SIZE); + + if (param) { + bufferSize = strtol(param->value, &test, 10); + if (*test != '\0' || bufferSize <= 0) { + FATAL("buffer size \"%s\" is not a positive integer, " + "line %i\n", param->value, param->line); + } + } + + bufferSize *= 1024; + + buffered_chunks = bufferSize / CHUNK_SIZE; + + if (buffered_chunks >= 1 << 15) { + FATAL("buffer size \"%li\" is too big\n", (long)bufferSize); + } + + param = getConfigParam(CONF_BUFFER_BEFORE_PLAY); + + if (param) { + perc = strtod(param->value, &test); + if (*test != '%' || perc < 0 || perc > 100) { + FATAL("buffered before play \"%s\" is not a positive " + "percentage and less than 100 percent, line %i" + "\n", param->value, param->line); + } + } + + buffered_before_play = (perc / 100) * buffered_chunks; + if (buffered_before_play > buffered_chunks) { + buffered_before_play = buffered_chunks; + } else if (buffered_before_play < 0) + buffered_before_play = 0; + + allocationSize = buffered_chunks * CHUNK_SIZE; /*actual buffer */ + allocationSize += buffered_chunks * sizeof(float); /*for times */ + allocationSize += buffered_chunks * sizeof(mpd_sint16); /*for chunkSize */ + allocationSize += buffered_chunks * sizeof(mpd_sint16); /*for bitRate */ + allocationSize += buffered_chunks * sizeof(mpd_sint8); /*for metaChunk */ + allocationSize += sizeof(PlayerData); /*for playerData struct */ + + /* for audioDeviceStates[] */ + allocationSize += device_array_size; + + if ((shmid = shmget(IPC_PRIVATE, allocationSize, IPC_CREAT | 0600)) < 0) + FATAL("problems shmget'ing\n"); + if (!(playerData_pd = shmat(shmid, NULL, 0))) + FATAL("problems shmat'ing\n"); + if (shmctl(shmid, IPC_RMID, NULL) < 0) + FATAL("problems shmctl'ing\n"); + + playerData_pd->audioDeviceStates = (mpd_uint8 *)playerData_pd + + allocationSize - device_array_size; + buffer = &(playerData_pd->buffer); + + memset(&buffer->convState, 0, sizeof(ConvState)); + buffer->chunks = ((char *)playerData_pd) + sizeof(PlayerData); + buffer->chunkSize = (mpd_uint16 *) (((char *)buffer->chunks) + + buffered_chunks * CHUNK_SIZE); + buffer->bitRate = (mpd_uint16 *) (((char *)buffer->chunkSize) + + buffered_chunks * sizeof(mpd_sint16)); + buffer->metaChunk = (mpd_sint8 *) (((char *)buffer->bitRate) + + buffered_chunks * + sizeof(mpd_sint16)); + buffer->times = + (float *)(((char *)buffer->metaChunk) + + buffered_chunks * sizeof(mpd_sint8)); + buffer->acceptMetadata = 0; + + playerData_pd->playerControl.stop = 0; + playerData_pd->playerControl.pause = 0; + playerData_pd->playerControl.play = 0; + playerData_pd->playerControl.error = PLAYER_ERROR_NOERROR; + playerData_pd->playerControl.lockQueue = 0; + playerData_pd->playerControl.unlockQueue = 0; + playerData_pd->playerControl.state = PLAYER_STATE_STOP; + playerData_pd->playerControl.queueState = PLAYER_QUEUE_BLANK; + playerData_pd->playerControl.queueLockState = PLAYER_QUEUE_UNLOCKED; + playerData_pd->playerControl.seek = 0; + playerData_pd->playerControl.closeAudio = 0; + memset(playerData_pd->playerControl.utf8url, 0, MAXPATHLEN + 1); + memset(playerData_pd->playerControl.erroredUrl, 0, MAXPATHLEN + 1); + memset(playerData_pd->playerControl.currentUrl, 0, MAXPATHLEN + 1); + playerData_pd->playerControl.crossFade = crossfade; + playerData_pd->playerControl.softwareVolume = 1000; + playerData_pd->playerControl.totalPlayTime = 0; + playerData_pd->playerControl.decode_pid = 0; + playerData_pd->playerControl.metadataState = + PLAYER_METADATA_STATE_WRITE; + + playerData_pd->decoderControl.stop = 0; + playerData_pd->decoderControl.start = 0; + playerData_pd->decoderControl.state = DECODE_STATE_STOP; + playerData_pd->decoderControl.seek = 0; + playerData_pd->decoderControl.error = DECODE_ERROR_NOERROR; + memset(playerData_pd->decoderControl.utf8url, 0, MAXPATHLEN + 1); +} + +PlayerData *getPlayerData(void) +{ + return playerData_pd; +} + +void freePlayerData(void) +{ + /* We don't want to release this memory until we know our player and + * decoder have exited. Otherwise, their signal handlers will want to + * access playerData_pd and we need to keep it available for them */ + waitpid(-1, NULL, 0); + shmdt(playerData_pd); +} diff --git a/trunk/src/playerData.h b/trunk/src/playerData.h new file mode 100644 index 000000000..00e4040be --- /dev/null +++ b/trunk/src/playerData.h @@ -0,0 +1,49 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef PLAYER_DATA_H +#define PLAYER_DATA_H + +#include "../config.h" + +#include "audio.h" +#include "player.h" +#include "decode.h" +#include "mpd_types.h" +#include "outputBuffer.h" + +/* pick 1020 since its devisible for 8,16,24, and 32-bit audio */ +#define CHUNK_SIZE 1020 + +extern int buffered_before_play; +extern int buffered_chunks; + +typedef struct _PlayerData { + OutputBuffer buffer; + PlayerControl playerControl; + DecoderControl decoderControl; + mpd_uint8 *audioDeviceStates; +} PlayerData; + +void initPlayerData(void); + +PlayerData *getPlayerData(void); + +void freePlayerData(void); + +#endif diff --git a/trunk/src/playlist.c b/trunk/src/playlist.c new file mode 100644 index 000000000..d8f2c6b65 --- /dev/null +++ b/trunk/src/playlist.c @@ -0,0 +1,1499 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "playlist.h" +#include "player.h" +#include "command.h" +#include "ls.h" +#include "tag.h" +#include "conf.h" +#include "directory.h" +#include "log.h" +#include "path.h" +#include "utils.h" +#include "sig_handlers.h" +#include "state_file.h" +#include "storedPlaylist.h" + +#include <string.h> +#include <stdlib.h> +#include <sys/stat.h> +#include <sys/param.h> +#include <errno.h> +#include <unistd.h> +#include <time.h> + +#define PLAYLIST_STATE_STOP 0 +#define PLAYLIST_STATE_PLAY 1 + +#define PLAYLIST_PREV_UNLESS_ELAPSED 10 + +#define PLAYLIST_STATE_FILE_STATE "state: " +#define PLAYLIST_STATE_FILE_RANDOM "random: " +#define PLAYLIST_STATE_FILE_REPEAT "repeat: " +#define PLAYLIST_STATE_FILE_CURRENT "current: " +#define PLAYLIST_STATE_FILE_TIME "time: " +#define PLAYLIST_STATE_FILE_CROSSFADE "crossfade: " +#define PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "playlist_begin" +#define PLAYLIST_STATE_FILE_PLAYLIST_END "playlist_end" + +#define PLAYLIST_STATE_FILE_STATE_PLAY "play" +#define PLAYLIST_STATE_FILE_STATE_PAUSE "pause" +#define PLAYLIST_STATE_FILE_STATE_STOP "stop" + +#define PLAYLIST_BUFFER_SIZE 2*MAXPATHLEN + +#define PLAYLIST_HASH_MULT 4 + +#define DEFAULT_PLAYLIST_MAX_LENGTH (1024*16) +#define DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS 0 + +static Playlist playlist; +static int playlist_state = PLAYLIST_STATE_STOP; +static int playlist_max_length = DEFAULT_PLAYLIST_MAX_LENGTH; +static int playlist_stopOnError; +static int playlist_errorCount; +static int playlist_queueError; +static int playlist_noGoToNext; + +int playlist_saveAbsolutePaths = DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS; + +static void swapOrder(int a, int b); +static int playPlaylistOrderNumber(int fd, int orderNum); +static void randomizeOrder(int start, int end); + +static void incrPlaylistVersion(void) +{ + static unsigned long max = ((mpd_uint32) 1 << 31) - 1; + playlist.version++; + if (playlist.version >= max) { + int i; + + for (i = 0; i < playlist.length; i++) { + playlist.songMod[i] = 0; + } + + playlist.version = 1; + } +} + +void playlistVersionChange(void) +{ + int i = 0; + + for (i = 0; i < playlist.length; i++) { + playlist.songMod[i] = playlist.version; + } + + incrPlaylistVersion(); +} + +static void incrPlaylistCurrent(void) +{ + if (playlist.current < 0) + return; + + if (playlist.current >= playlist.length - 1) { + if (playlist.repeat) + playlist.current = 0; + else + playlist.current = -1; + } else + playlist.current++; +} + +void initPlaylist(void) +{ + char *test; + int i; + ConfigParam *param; + + playlist.length = 0; + playlist.repeat = 0; + playlist.version = 1; + playlist.random = 0; + playlist.queued = -1; + playlist.current = -1; + + param = getConfigParam(CONF_MAX_PLAYLIST_LENGTH); + + if (param) { + playlist_max_length = strtol(param->value, &test, 10); + if (*test != '\0') { + FATAL("max playlist length \"%s\" is not an integer, " + "line %i\n", param->value, param->line); + } + } + + playlist_saveAbsolutePaths = getBoolConfigParam(CONF_SAVE_ABSOLUTE_PATHS); + if (playlist_saveAbsolutePaths == -1) playlist_saveAbsolutePaths = DEFAULT_PLAYLIST_SAVE_ABSOLUTE_PATHS; + else if (playlist_saveAbsolutePaths < 0) exit(EXIT_FAILURE); + + playlist.songs = xmalloc(sizeof(Song *) * playlist_max_length); + playlist.songMod = xmalloc(sizeof(mpd_uint32) * playlist_max_length); + playlist.order = xmalloc(sizeof(int) * playlist_max_length); + playlist.idToPosition = xmalloc(sizeof(int) * playlist_max_length * + PLAYLIST_HASH_MULT); + playlist.positionToId = xmalloc(sizeof(int) * playlist_max_length); + + memset(playlist.songs, 0, sizeof(char *) * playlist_max_length); + + srandom(time(NULL)); + + for (i = 0; i < playlist_max_length * PLAYLIST_HASH_MULT; i++) { + playlist.idToPosition[i] = -1; + } +} + +static int getNextId(void) +{ + static int cur = -1; + + do { + cur++; + if (cur >= playlist_max_length * PLAYLIST_HASH_MULT) { + cur = 0; + } + } while (playlist.idToPosition[cur] != -1); + + return cur; +} + +void finishPlaylist(void) +{ + int i; + for (i = 0; i < playlist.length; i++) { + if (playlist.songs[i]->type == SONG_TYPE_URL) { + freeJustSong(playlist.songs[i]); + } + } + + playlist.length = 0; + + free(playlist.songs); + playlist.songs = NULL; + free(playlist.songMod); + playlist.songMod = NULL; + free(playlist.order); + playlist.order = NULL; + free(playlist.idToPosition); + playlist.idToPosition = NULL; + free(playlist.positionToId); + playlist.positionToId = NULL; +} + +int clearPlaylist(int fd) +{ + int i; + + if (stopPlaylist(fd) < 0) + return -1; + + for (i = 0; i < playlist.length; i++) { + if (playlist.songs[i]->type == SONG_TYPE_URL) { + freeJustSong(playlist.songs[i]); + } + playlist.idToPosition[playlist.positionToId[i]] = -1; + playlist.songs[i] = NULL; + } + playlist.length = 0; + playlist.current = -1; + + incrPlaylistVersion(); + + return 0; +} + +int clearStoredPlaylist(int fd, char *utf8file) +{ + removeAllFromStoredPlaylistByPath(fd, utf8file); + return 0; +} + +int showPlaylist(int fd) +{ + int i; + + for (i = 0; i < playlist.length; i++) { + fdprintf(fd, "%i:%s\n", i, getSongUrl(playlist.songs[i])); + } + + return 0; +} + +void savePlaylistState(FILE *fp) +{ + fprintf(fp, "%s", PLAYLIST_STATE_FILE_STATE); + switch (playlist_state) { + case PLAYLIST_STATE_PLAY: + switch (getPlayerState()) { + case PLAYER_STATE_PAUSE: + fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_PAUSE); + break; + default: + fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_PLAY); + } + fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CURRENT, + playlist.order[playlist.current]); + fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_TIME, + getPlayerElapsedTime()); + break; + default: + fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_STATE_STOP); + break; + } + fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_RANDOM, playlist.random); + fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_REPEAT, playlist.repeat); + fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CROSSFADE, + (int)(getPlayerCrossFade())); + fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_BEGIN); + fflush(fp); + showPlaylist(fileno(fp)); + fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_END); +} + +static void loadPlaylistFromStateFile(FILE *fp, char *buffer, + int state, int current, int time) +{ + char *temp; + int song; + + if (!myFgets(buffer, PLAYLIST_BUFFER_SIZE, fp)) + state_file_fatal(); + while (strcmp(buffer, PLAYLIST_STATE_FILE_PLAYLIST_END)) { + song = atoi(strtok(buffer, ":")); + if (!(temp = strtok(NULL, ""))) + state_file_fatal(); + if (!addToPlaylist(STDERR_FILENO, temp, 0) && current == song) { + if (state != PLAYER_STATE_STOP) { + playPlaylist(STDERR_FILENO, + playlist.length - 1, 0); + } + if (state == PLAYER_STATE_PAUSE) { + playerPause(STDERR_FILENO); + } + if (state != PLAYER_STATE_STOP) { + seekSongInPlaylist(STDERR_FILENO, + playlist.length - 1, time); + } + } + if (!myFgets(buffer, PLAYLIST_BUFFER_SIZE, fp)) + state_file_fatal(); + } +} + +void readPlaylistState(FILE *fp) +{ + int current = -1; + int time = 0; + int state = PLAYER_STATE_STOP; + char buffer[PLAYLIST_BUFFER_SIZE]; + + while (myFgets(buffer, PLAYLIST_BUFFER_SIZE, fp)) { + if (strncmp(buffer, PLAYLIST_STATE_FILE_STATE, + strlen(PLAYLIST_STATE_FILE_STATE)) == 0) { + if (strcmp(&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]), + PLAYLIST_STATE_FILE_STATE_PLAY) == 0) { + state = PLAYER_STATE_PLAY; + } else + if (strcmp + (&(buffer[strlen(PLAYLIST_STATE_FILE_STATE)]), + PLAYLIST_STATE_FILE_STATE_PAUSE) + == 0) { + state = PLAYER_STATE_PAUSE; + } + } else if (strncmp(buffer, PLAYLIST_STATE_FILE_TIME, + strlen(PLAYLIST_STATE_FILE_TIME)) == 0) { + time = + atoi(&(buffer[strlen(PLAYLIST_STATE_FILE_TIME)])); + } else + if (strncmp + (buffer, PLAYLIST_STATE_FILE_REPEAT, + strlen(PLAYLIST_STATE_FILE_REPEAT)) == 0) { + if (strcmp + (&(buffer[strlen(PLAYLIST_STATE_FILE_REPEAT)]), + "1") == 0) { + setPlaylistRepeatStatus(STDERR_FILENO, 1); + } else + setPlaylistRepeatStatus(STDERR_FILENO, 0); + } else + if (strncmp + (buffer, PLAYLIST_STATE_FILE_CROSSFADE, + strlen(PLAYLIST_STATE_FILE_CROSSFADE)) == 0) { + setPlayerCrossFade(atoi + (& + (buffer + [strlen + (PLAYLIST_STATE_FILE_CROSSFADE)]))); + } else + if (strncmp + (buffer, PLAYLIST_STATE_FILE_RANDOM, + strlen(PLAYLIST_STATE_FILE_RANDOM)) == 0) { + if (strcmp + (& + (buffer + [strlen(PLAYLIST_STATE_FILE_RANDOM)]), + "1") == 0) { + setPlaylistRandomStatus(STDERR_FILENO, 1); + } else + setPlaylistRandomStatus(STDERR_FILENO, 0); + } else if (strncmp(buffer, PLAYLIST_STATE_FILE_CURRENT, + strlen(PLAYLIST_STATE_FILE_CURRENT)) + == 0) { + if (strlen(buffer) == + strlen(PLAYLIST_STATE_FILE_CURRENT)) + state_file_fatal(); + current = atoi(&(buffer + [strlen + (PLAYLIST_STATE_FILE_CURRENT)])); + } else + if (strncmp + (buffer, PLAYLIST_STATE_FILE_PLAYLIST_BEGIN, + strlen(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN) + ) == 0) { + if (state == PLAYER_STATE_STOP) + current = -1; + loadPlaylistFromStateFile(fp, buffer, state, + current, time); + } + } +} + +static void printPlaylistSongInfo(int fd, int song) +{ + printSongInfo(fd, playlist.songs[song]); + fdprintf(fd, "Pos: %i\nId: %i\n", song, playlist.positionToId[song]); +} + +int playlistChanges(int fd, mpd_uint32 version) +{ + int i; + + for (i = 0; i < playlist.length; i++) { + if (version > playlist.version || + playlist.songMod[i] >= version || + playlist.songMod[i] == 0) { + printPlaylistSongInfo(fd, i); + } + } + + return 0; +} + +int playlistChangesPosId(int fd, mpd_uint32 version) +{ + int i; + + for (i = 0; i < playlist.length; i++) { + if (version > playlist.version || + playlist.songMod[i] >= version || + playlist.songMod[i] == 0) { + fdprintf(fd, "cpos: %i\nId: %i\n", + i, playlist.positionToId[i]); + } + } + + return 0; +} + +int playlistInfo(int fd, int song) +{ + int i; + int begin = 0; + int end = playlist.length; + + if (song >= 0) { + begin = song; + end = song + 1; + } + if (song >= playlist.length) { + commandError(fd, ACK_ERROR_NO_EXIST, + "song doesn't exist: \"%i\"", song); + return -1; + } + + for (i = begin; i < end; i++) + printPlaylistSongInfo(fd, i); + + return 0; +} + +# define checkSongId(id) { \ + if(id < 0 || id >= PLAYLIST_HASH_MULT*playlist_max_length || \ + playlist.idToPosition[id] == -1 ) \ + { \ + commandError(fd, ACK_ERROR_NO_EXIST, \ + "song id doesn't exist: \"%i\"", id); \ + return -1; \ + } \ +} + +int playlistId(int fd, int id) +{ + int i; + int begin = 0; + int end = playlist.length; + + if (id >= 0) { + checkSongId(id); + begin = playlist.idToPosition[id]; + end = begin + 1; + } + + for (i = begin; i < end; i++) + printPlaylistSongInfo(fd, i); + + return 0; +} + +static void swapSongs(int song1, int song2) +{ + Song *sTemp; + int iTemp; + + sTemp = playlist.songs[song1]; + playlist.songs[song1] = playlist.songs[song2]; + playlist.songs[song2] = sTemp; + + playlist.songMod[song1] = playlist.version; + playlist.songMod[song2] = playlist.version; + + playlist.idToPosition[playlist.positionToId[song1]] = song2; + playlist.idToPosition[playlist.positionToId[song2]] = song1; + + iTemp = playlist.positionToId[song1]; + playlist.positionToId[song1] = playlist.positionToId[song2]; + playlist.positionToId[song2] = iTemp; +} + +static void queueNextSongInPlaylist(void) +{ + if (playlist.current < playlist.length - 1) { + playlist.queued = playlist.current + 1; + DEBUG("playlist: queue song %i:\"%s\"\n", + playlist.queued, + getSongUrl(playlist. + songs[playlist.order[playlist.queued]])); + if (queueSong(playlist.songs[playlist.order[playlist.queued]]) < + 0) { + playlist.queued = -1; + playlist_queueError = 1; + } + } else if (playlist.length && playlist.repeat) { + if (playlist.length > 1 && playlist.random) { + randomizeOrder(0, playlist.length - 1); + } + playlist.queued = 0; + DEBUG("playlist: queue song %i:\"%s\"\n", + playlist.queued, + getSongUrl(playlist. + songs[playlist.order[playlist.queued]])); + if (queueSong(playlist.songs[playlist.order[playlist.queued]]) < + 0) { + playlist.queued = -1; + playlist_queueError = 1; + } + } +} + +static void syncPlaylistWithQueue(int queue) +{ + if (queue && getPlayerQueueState() == PLAYER_QUEUE_BLANK) { + queueNextSongInPlaylist(); + } else if (getPlayerQueueState() == PLAYER_QUEUE_DECODE) { + if (playlist.queued != -1) + setQueueState(PLAYER_QUEUE_PLAY); + else + setQueueState(PLAYER_QUEUE_STOP); + } else if (getPlayerQueueState() == PLAYER_QUEUE_EMPTY) { + setQueueState(PLAYER_QUEUE_BLANK); + if (playlist.queued >= 0) { + DEBUG("playlist: now playing queued song\n"); + playlist.current = playlist.queued; + } + playlist.queued = -1; + if (queue) + queueNextSongInPlaylist(); + } +} + +static void lockPlaylistInteraction(void) +{ + if (getPlayerQueueState() == PLAYER_QUEUE_PLAY || + getPlayerQueueState() == PLAYER_QUEUE_FULL) { + playerQueueLock(); + syncPlaylistWithQueue(0); + } +} + +static void unlockPlaylistInteraction(void) +{ + playerQueueUnlock(); +} + +static void clearPlayerQueue(void) +{ + playlist.queued = -1; + switch (getPlayerQueueState()) { + case PLAYER_QUEUE_FULL: + DEBUG("playlist: dequeue song\n"); + setQueueState(PLAYER_QUEUE_BLANK); + break; + case PLAYER_QUEUE_PLAY: + DEBUG("playlist: stop decoding queued song\n"); + setQueueState(PLAYER_QUEUE_STOP); + break; + } +} + +int addToPlaylist(int fd, char *url, int printId) +{ + Song *song; + + DEBUG("add to playlist: %s\n", url); + + if ((song = getSongFromDB(url))) { + } else if (!(isValidRemoteUtf8Url(url) && + (song = newSong(url, SONG_TYPE_URL, NULL)))) { + commandError(fd, ACK_ERROR_NO_EXIST, + "\"%s\" is not in the music db or is " + "not a valid url", url); + return -1; + } + + return addSongToPlaylist(fd, song, printId); +} + +int addToStoredPlaylist(int fd, char *url, char *utf8file) +{ + Song *song; + + DEBUG("add to stored playlist: %s\n", url); + + song = getSongFromDB(url); + if (song) { + appendSongToStoredPlaylistByPath(fd, utf8file, song); + return 0; + } + + if (!isValidRemoteUtf8Url(url)) + goto fail; + + song = newSong(url, SONG_TYPE_URL, NULL); + if (song) { + appendSongToStoredPlaylistByPath(fd, utf8file, song); + freeJustSong(song); + return 0; + } + +fail: + commandError(fd, ACK_ERROR_NO_EXIST, "\"%s\" is not in the music db" + "or is not a valid url", url); + return -1; +} + +int addSongToPlaylist(int fd, Song * song, int printId) +{ + int id; + + if (playlist.length == playlist_max_length) { + commandError(fd, ACK_ERROR_PLAYLIST_MAX, + "playlist is at the max size"); + return -1; + } + + if (playlist_state == PLAYLIST_STATE_PLAY) { + if (playlist.queued >= 0 + && playlist.current == playlist.length - 1) { + lockPlaylistInteraction(); + clearPlayerQueue(); + unlockPlaylistInteraction(); + } + } + + id = getNextId(); + + playlist.songs[playlist.length] = song; + playlist.songMod[playlist.length] = playlist.version; + playlist.order[playlist.length] = playlist.length; + playlist.positionToId[playlist.length] = id; + playlist.idToPosition[playlist.positionToId[playlist.length]] = + playlist.length; + playlist.length++; + + if (playlist.random) { + int swap; + int start; + /*if(playlist_state==PLAYLIST_STATE_STOP) start = 0; + else */ if (playlist.queued >= 0) + start = playlist.queued + 1; + else + start = playlist.current + 1; + if (start < playlist.length) { + swap = random() % (playlist.length - start); + swap += start; + swapOrder(playlist.length - 1, swap); + } + } + + incrPlaylistVersion(); + + if (printId) + fdprintf(fd, "Id: %i\n", id); + + return 0; +} + +int swapSongsInPlaylist(int fd, int song1, int song2) +{ + int queuedSong = -1; + int currentSong = -1; + + if (song1 < 0 || song1 >= playlist.length) { + commandError(fd, ACK_ERROR_NO_EXIST, + "song doesn't exist: \"%i\"", song1); + return -1; + } + if (song2 < 0 || song2 >= playlist.length) { + commandError(fd, ACK_ERROR_NO_EXIST, + "song doesn't exist: \"%i\"", song2); + return -1; + } + + if (playlist_state == PLAYLIST_STATE_PLAY) { + if (playlist.queued >= 0) { + queuedSong = playlist.order[playlist.queued]; + } + currentSong = playlist.order[playlist.current]; + + if (queuedSong == song1 || queuedSong == song2 + || currentSong == song1 || currentSong == song2) { + lockPlaylistInteraction(); + clearPlayerQueue(); + unlockPlaylistInteraction(); + } + } + + swapSongs(song1, song2); + if (playlist.random) { + int i; + int k; + int j = -1; + for (i = 0; playlist.order[i] != song1; i++) { + if (playlist.order[i] == song2) + j = i; + } + k = i; + for (; j == -1; i++) + if (playlist.order[i] == song2) + j = i; + swapOrder(k, j); + } else { + if (playlist.current == song1) + playlist.current = song2; + else if (playlist.current == song2) + playlist.current = song1; + } + + incrPlaylistVersion(); + + return 0; +} + +int swapSongsInPlaylistById(int fd, int id1, int id2) +{ + checkSongId(id1); + checkSongId(id2); + + return swapSongsInPlaylist(fd, playlist.idToPosition[id1], + playlist.idToPosition[id2]); +} + +#define moveSongFromTo(from, to) { \ + playlist.idToPosition[playlist.positionToId[from]] = to; \ + playlist.positionToId[to] = playlist.positionToId[from]; \ + playlist.songs[to] = playlist.songs[from]; \ + playlist.songMod[to] = playlist.version; \ +} + +int deleteFromPlaylist(int fd, int song) +{ + int i; + int songOrder; + + if (song < 0 || song >= playlist.length) { + commandError(fd, ACK_ERROR_NO_EXIST, + "song doesn't exist: \"%i\"", song); + return -1; + } + + if (playlist_state == PLAYLIST_STATE_PLAY) { + if (playlist.queued >= 0 + && (playlist.order[playlist.queued] == song + || playlist.order[playlist.current] == song)) { + lockPlaylistInteraction(); + clearPlayerQueue(); + unlockPlaylistInteraction(); + } + } + + if (playlist.songs[song]->type == SONG_TYPE_URL) { + freeJustSong(playlist.songs[song]); + } + + playlist.idToPosition[playlist.positionToId[song]] = -1; + + /* delete song from songs array */ + for (i = song; i < playlist.length - 1; i++) { + moveSongFromTo(i + 1, i); + } + /* now find it in the order array */ + for (i = 0; i < playlist.length - 1; i++) { + if (playlist.order[i] == song) + break; + } + songOrder = i; + /* delete the entry from the order array */ + for (; i < playlist.length - 1; i++) + playlist.order[i] = playlist.order[i + 1]; + /* readjust values in the order array */ + for (i = 0; i < playlist.length - 1; i++) { + if (playlist.order[i] > song) + playlist.order[i]--; + } + /* now take care of other misc stuff */ + playlist.songs[playlist.length - 1] = NULL; + playlist.length--; + + incrPlaylistVersion(); + + if (playlist_state != PLAYLIST_STATE_STOP + && playlist.current == songOrder) { + /*if(playlist.current>=playlist.length) return playerStop(fd); + else return playPlaylistOrderNumber(fd,playlist.current); */ + playerStop(STDERR_FILENO); + playlist_noGoToNext = 1; + } + + if (playlist.current > songOrder) { + playlist.current--; + } else if (playlist.current >= playlist.length) { + incrPlaylistCurrent(); + } + + if (playlist.queued > songOrder) { + playlist.queued--; + } + + return 0; +} + +int deleteFromPlaylistById(int fd, int id) +{ + checkSongId(id); + + return deleteFromPlaylist(fd, playlist.idToPosition[id]); +} + +void deleteASongFromPlaylist(Song * song) +{ + int i; + + if (NULL == playlist.songs) + return; + + for (i = 0; i < playlist.length; i++) { + if (song == playlist.songs[i]) { + deleteFromPlaylist(STDERR_FILENO, i); + } + } +} + +int stopPlaylist(int fd) +{ + DEBUG("playlist: stop\n"); + if (playerStop(fd) < 0) + return -1; + playerCloseAudio(); + playlist.queued = -1; + playlist_state = PLAYLIST_STATE_STOP; + playlist_noGoToNext = 0; + if (playlist.random) + randomizeOrder(0, playlist.length - 1); + return 0; +} + +static int playPlaylistOrderNumber(int fd, int orderNum) +{ + + if (playerStop(fd) < 0) + return -1; + + playlist_state = PLAYLIST_STATE_PLAY; + playlist_noGoToNext = 0; + playlist.queued = -1; + playlist_queueError = 0; + + DEBUG("playlist: play %i:\"%s\"\n", orderNum, + getSongUrl(playlist.songs[playlist.order[orderNum]])); + + if (playerPlay(fd, (playlist.songs[playlist.order[orderNum]])) < 0) { + stopPlaylist(fd); + return -1; + } + + playlist.current = orderNum; + + return 0; +} + +int playPlaylist(int fd, int song, int stopOnError) +{ + int i = song; + + clearPlayerError(); + + if (song == -1) { + if (playlist.length == 0) + return 0; + + if (playlist_state == PLAYLIST_STATE_PLAY) { + return playerSetPause(fd, 0); + } + if (playlist.current >= 0 && playlist.current < playlist.length) { + i = playlist.current; + } else { + i = 0; + } + } else if (song < 0 || song >= playlist.length) { + commandError(fd, ACK_ERROR_NO_EXIST, + "song doesn't exist: \"%i\"", song); + return -1; + } + + if (playlist.random) { + if (song == -1 && playlist_state == PLAYLIST_STATE_PLAY) { + randomizeOrder(0, playlist.length - 1); + } else { + if (song >= 0) + for (i = 0; song != playlist.order[i]; i++) ; + if (playlist_state == PLAYLIST_STATE_STOP) { + playlist.current = 0; + } + swapOrder(i, playlist.current); + i = playlist.current; + } + } + + playlist_stopOnError = stopOnError; + playlist_errorCount = 0; + + return playPlaylistOrderNumber(fd, i); +} + +int playPlaylistById(int fd, int id, int stopOnError) +{ + if (id == -1) { + return playPlaylist(fd, id, stopOnError); + } + + checkSongId(id); + + return playPlaylist(fd, playlist.idToPosition[id], stopOnError); +} + +static void syncCurrentPlayerDecodeMetadata(void) +{ + Song *songPlayer = playerCurrentDecodeSong(); + Song *song; + int songNum; + + if (!songPlayer) + return; + + if (playlist_state != PLAYLIST_STATE_PLAY) + return; + + songNum = playlist.order[playlist.current]; + song = playlist.songs[songNum]; + + if (song->type == SONG_TYPE_URL && + 0 == strcmp(getSongUrl(song), songPlayer->url) && + !mpdTagsAreEqual(song->tag, songPlayer->tag)) { + if (song->tag) + freeMpdTag(song->tag); + song->tag = mpdTagDup(songPlayer->tag); + playlist.songMod[songNum] = playlist.version; + incrPlaylistVersion(); + } +} + +void syncPlayerAndPlaylist(void) +{ + if (playlist_state != PLAYLIST_STATE_PLAY) + return; + + if (getPlayerState() == PLAYER_STATE_STOP) + playPlaylistIfPlayerStopped(); + else + syncPlaylistWithQueue(!playlist_queueError); + + syncCurrentPlayerDecodeMetadata(); +} + +static int currentSongInPlaylist(int fd) +{ + if (playlist_state != PLAYLIST_STATE_PLAY) + return 0; + + playlist_stopOnError = 0; + + syncPlaylistWithQueue(0); + + if (playlist.current >= 0 && playlist.current < playlist.length) { + return playPlaylistOrderNumber(fd, playlist.current); + } else + return stopPlaylist(fd); + + return 0; +} + +int nextSongInPlaylist(int fd) +{ + if (playlist_state != PLAYLIST_STATE_PLAY) + return 0; + + syncPlaylistWithQueue(0); + + playlist_stopOnError = 0; + + if (playlist.current < playlist.length - 1) { + return playPlaylistOrderNumber(fd, playlist.current + 1); + } else if (playlist.length && playlist.repeat) { + if (playlist.random) + randomizeOrder(0, playlist.length - 1); + return playPlaylistOrderNumber(fd, 0); + } else { + incrPlaylistCurrent(); + return stopPlaylist(fd); + } + + return 0; +} + +void playPlaylistIfPlayerStopped(void) +{ + if (getPlayerState() == PLAYER_STATE_STOP) { + int error = getPlayerError(); + + if (error == PLAYER_ERROR_NOERROR) + playlist_errorCount = 0; + else + playlist_errorCount++; + + if (playlist_state == PLAYLIST_STATE_PLAY + && ((playlist_stopOnError && error != PLAYER_ERROR_NOERROR) + || error == PLAYER_ERROR_AUDIO + || error == PLAYER_ERROR_SYSTEM + || playlist_errorCount >= playlist.length)) { + stopPlaylist(STDERR_FILENO); + } else if (playlist_noGoToNext) + currentSongInPlaylist(STDERR_FILENO); + else + nextSongInPlaylist(STDERR_FILENO); + } +} + +int getPlaylistRepeatStatus(void) +{ + return playlist.repeat; +} + +int getPlaylistRandomStatus(void) +{ + return playlist.random; +} + +int setPlaylistRepeatStatus(int fd, int status) +{ + if (status != 0 && status != 1) { + commandError(fd, ACK_ERROR_ARG, "\"%i\" is not 0 or 1", status); + return -1; + } + + if (playlist_state == PLAYLIST_STATE_PLAY) { + if (playlist.repeat && !status && playlist.queued == 0) { + lockPlaylistInteraction(); + clearPlayerQueue(); + unlockPlaylistInteraction(); + } + } + + playlist.repeat = status; + + return 0; +} + +int moveSongInPlaylist(int fd, int from, int to) +{ + int i; + Song *tmpSong; + int tmpId; + int queuedSong = -1; + int currentSong = -1; + + if (from < 0 || from >= playlist.length) { + commandError(fd, ACK_ERROR_NO_EXIST, + "song doesn't exist: \"%i\"", from); + return -1; + } + + if (to < 0 || to >= playlist.length) { + commandError(fd, ACK_ERROR_NO_EXIST, + "song doesn't exist: \"%i\"", to); + return -1; + } + + if (playlist_state == PLAYLIST_STATE_PLAY) { + if (playlist.queued >= 0) { + queuedSong = playlist.order[playlist.queued]; + } + currentSong = playlist.order[playlist.current]; + if (queuedSong == from || queuedSong == to + || currentSong == from || currentSong == to) { + lockPlaylistInteraction(); + clearPlayerQueue(); + unlockPlaylistInteraction(); + } + } + + tmpSong = playlist.songs[from]; + tmpId = playlist.positionToId[from]; + /* move songs to one less in from->to */ + for (i = from; i < to; i++) { + moveSongFromTo(i + 1, i); + } + /* move songs to one more in to->from */ + for (i = from; i > to; i--) { + moveSongFromTo(i - 1, i); + } + /* put song at _to_ */ + playlist.idToPosition[tmpId] = to; + playlist.positionToId[to] = tmpId; + playlist.songs[to] = tmpSong; + playlist.songMod[to] = playlist.version; + /* now deal with order */ + if (playlist.random) { + for (i = 0; i < playlist.length; i++) { + if (playlist.order[i] > from && playlist.order[i] <= to) { + playlist.order[i]--; + } else if (playlist.order[i] < from && + playlist.order[i] >= to) { + playlist.order[i]++; + } else if (from == playlist.order[i]) { + playlist.order[i] = to; + } + } + } + else + { + if (playlist.current == from) + playlist.current = to; + else if (playlist.current > from && playlist.current <= to) { + playlist.current--; + } else if (playlist.current >= to && playlist.current < from) { + playlist.current++; + } + + /* this first if statement isn't necessary since the queue + * would have been cleared out if queued == from */ + if (playlist.queued == from) + playlist.queued = to; + else if (playlist.queued > from && playlist.queued <= to) { + playlist.queued--; + } else if (playlist.queued>= to && playlist.queued < from) { + playlist.queued++; + } + } + + incrPlaylistVersion(); + + return 0; +} + +int moveSongInPlaylistById(int fd, int id1, int to) +{ + checkSongId(id1); + + return moveSongInPlaylist(fd, playlist.idToPosition[id1], to); +} + +static void orderPlaylist(void) +{ + int i; + + if (playlist.current >= 0 && playlist.current < playlist.length) { + playlist.current = playlist.order[playlist.current]; + } + + if (playlist_state == PLAYLIST_STATE_PLAY) { + if (playlist.queued >= 0) { + lockPlaylistInteraction(); + clearPlayerQueue(); + unlockPlaylistInteraction(); + } + } + + for (i = 0; i < playlist.length; i++) { + playlist.order[i] = i; + } + +} + +static void swapOrder(int a, int b) +{ + int bak = playlist.order[a]; + playlist.order[a] = playlist.order[b]; + playlist.order[b] = bak; +} + +static void randomizeOrder(int start, int end) +{ + int i; + int ri; + + DEBUG("playlist: randomize from %i to %i\n", start, end); + + if (playlist_state == PLAYLIST_STATE_PLAY) { + if (playlist.queued >= start && playlist.queued <= end) { + lockPlaylistInteraction(); + clearPlayerQueue(); + unlockPlaylistInteraction(); + } + } + + for (i = start; i <= end; i++) { + ri = random() % (end - start + 1) + start; + if (ri == playlist.current) + playlist.current = i; + else if (i == playlist.current) + playlist.current = ri; + swapOrder(i, ri); + } + +} + +int setPlaylistRandomStatus(int fd, int status) +{ + int statusWas = playlist.random; + + if (status != 0 && status != 1) { + commandError(fd, ACK_ERROR_ARG, "\"%i\" is not 0 or 1", status); + return -1; + } + + playlist.random = status; + + if (status != statusWas) { + if (playlist.random) { + /*if(playlist_state==PLAYLIST_STATE_PLAY) { + randomizeOrder(playlist.current+1, + playlist.length-1); + } + else */ randomizeOrder(0, playlist.length - 1); + if (playlist.current >= 0 && + playlist.current < playlist.length) { + swapOrder(playlist.current, 0); + playlist.current = 0; + } + } else + orderPlaylist(); + } + + return 0; +} + +int previousSongInPlaylist(int fd) +{ + static time_t lastTime; + time_t diff = time(NULL) - lastTime; + + lastTime += diff; + + if (playlist_state != PLAYLIST_STATE_PLAY) + return 0; + + syncPlaylistWithQueue(0); + + if (diff && getPlayerElapsedTime() > PLAYLIST_PREV_UNLESS_ELAPSED) { + return playPlaylistOrderNumber(fd, playlist.current); + } else { + if (playlist.current > 0) { + return playPlaylistOrderNumber(fd, + playlist.current - 1); + } else if (playlist.repeat) { + return playPlaylistOrderNumber(fd, playlist.length - 1); + } else { + return playPlaylistOrderNumber(fd, playlist.current); + } + } + + return 0; +} + +int shufflePlaylist(int fd) +{ + int i; + int ri; + + if (playlist.length > 1) { + if (playlist_state == PLAYLIST_STATE_PLAY) { + lockPlaylistInteraction(); + clearPlayerQueue(); + unlockPlaylistInteraction(); + /* put current playing song first */ + swapSongs(0, playlist.order[playlist.current]); + if (playlist.random) { + int j; + for (j = 0; 0 != playlist.order[j]; j++) ; + playlist.current = j; + } else + playlist.current = 0; + i = 1; + } else { + i = 0; + playlist.current = -1; + } + /* shuffle the rest of the list */ + for (; i < playlist.length; i++) { + ri = random() % (playlist.length - 1) + 1; + swapSongs(i, ri); + } + + incrPlaylistVersion(); + } + + return 0; +} + +int deletePlaylist(int fd, char *utf8file) +{ + char *file = utf8ToFsCharset(utf8file); + char *rfile = xmalloc(strlen(file) + strlen(".") + + strlen(PLAYLIST_FILE_SUFFIX) + 1); + char *actualFile; + + strcpy(rfile, file); + strcat(rfile, "."); + strcat(rfile, PLAYLIST_FILE_SUFFIX); + + if ((actualFile = rpp2app(rfile)) && isPlaylist(actualFile)) + free(rfile); + else { + free(rfile); + commandError(fd, ACK_ERROR_NO_EXIST, + "playlist \"%s\" not found", utf8file); + return -1; + } + + if (unlink(actualFile) < 0) { + commandError(fd, ACK_ERROR_SYSTEM, + "problems deleting file"); + return -1; + } + + return 0; +} + +int savePlaylist(int fd, char *utf8file) +{ + StoredPlaylist *sp = newStoredPlaylist(utf8file, fd, 0); + if (!sp) + return -1; + + appendPlaylistToStoredPlaylist(sp, &playlist); + if (writeStoredPlaylist(sp) != 0) { + freeStoredPlaylist(sp); + return -1; + } + + freeStoredPlaylist(sp); + return 0; +} + +int getPlaylistCurrentSong(void) +{ + if (playlist.current >= 0 && playlist.current < playlist.length) { + return playlist.order[playlist.current]; + } + + return -1; +} + +unsigned long getPlaylistVersion(void) +{ + return playlist.version; +} + +int getPlaylistLength(void) +{ + return playlist.length; +} + +int seekSongInPlaylist(int fd, int song, float time) +{ + int i = song; + + if (song < 0 || song >= playlist.length) { + commandError(fd, ACK_ERROR_NO_EXIST, + "song doesn't exist: \"%i\"", song); + return -1; + } + + if (playlist.random) + for (i = 0; song != playlist.order[i]; i++) ; + + clearPlayerError(); + playlist_stopOnError = 1; + playlist_errorCount = 0; + + if (playlist_state == PLAYLIST_STATE_PLAY) { + if (playlist.queued >= 0) { + lockPlaylistInteraction(); + clearPlayerQueue(); + unlockPlaylistInteraction(); + } + } else if (playPlaylistOrderNumber(fd, i) < 0) + return -1; + + if (playlist.current != i) { + if (playPlaylistOrderNumber(fd, i) < 0) + return -1; + } + + return playerSeek(fd, playlist.songs[playlist.order[i]], time); +} + +int seekSongInPlaylistById(int fd, int id, float time) +{ + checkSongId(id); + + return seekSongInPlaylist(fd, playlist.idToPosition[id], time); +} + +int getPlaylistSongId(int song) +{ + return playlist.positionToId[song]; +} + +int PlaylistInfo(int fd, char *utf8file, int detail) +{ + ListNode *node; + StoredPlaylist *sp = loadStoredPlaylist(utf8file, fd); + if (sp == NULL) + return -1; + + node = sp->list->firstNode; + while (node != NULL) { + char *temp = node->data; + int wrote = 0; + + if (detail) { + Song *song = getSongFromDB(temp); + if (song) { + printSongInfo(fd, song); + wrote = 1; + } + } + + if (!wrote) { + fdprintf(fd, SONG_FILE "%s\n", temp); + } + + node = node->nextNode; + } + + freeStoredPlaylist(sp); + return 0; +} + +int loadPlaylist(int fd, char *utf8file) +{ + ListNode *node; + StoredPlaylist *sp = loadStoredPlaylist(utf8file, fd); + if (sp == NULL) + return -1; + + node = sp->list->firstNode; + while (node != NULL) { + char *temp = node->data; + if ((addToPlaylist(STDERR_FILENO, temp, 0)) < 0) { + /* for windows compatibility, convert slashes */ + char *temp2 = xstrdup(temp); + char *p = temp2; + while (*p) { + if (*p == '\\') + *p = '/'; + p++; + } + if ((addToPlaylist(STDERR_FILENO, temp2, 0)) < 0) { + commandError(fd, ACK_ERROR_PLAYLIST_LOAD, + "can't add file \"%s\"", temp2); + } + free(temp2); + } + + node = node->nextNode; + } + + freeStoredPlaylist(sp); + return 0; +} + +void searchForSongsInPlaylist(int fd, int numItems, LocateTagItem * items) +{ + int i; + char **originalNeedles = xmalloc(numItems * sizeof(char *)); + + for (i = 0; i < numItems; i++) { + originalNeedles[i] = items[i].needle; + items[i].needle = strDupToUpper(originalNeedles[i]); + } + + for (i = 0; i < playlist.length; i++) { + if (strstrSearchTags(playlist.songs[i], numItems, items)) + printPlaylistSongInfo(fd, i); + } + + for (i = 0; i < numItems; i++) { + free(items[i].needle); + items[i].needle = originalNeedles[i]; + } + + free(originalNeedles); +} + +void findSongsInPlaylist(int fd, int numItems, LocateTagItem * items) +{ + int i; + + for (i = 0; i < playlist.length; i++) { + if (tagItemsFoundAndMatches(playlist.songs[i], numItems, items)) + printPlaylistSongInfo(fd, i); + } +} diff --git a/trunk/src/playlist.h b/trunk/src/playlist.h new file mode 100644 index 000000000..0ae3a677f --- /dev/null +++ b/trunk/src/playlist.h @@ -0,0 +1,144 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef PLAYLIST_H +#define PLAYLIST_H + +#include "../config.h" + +#include "dbUtils.h" + +#include <stdio.h> +#include <sys/param.h> +#include <time.h> + +#define PLAYLIST_FILE_SUFFIX "m3u" +#define PLAYLIST_COMMENT '#' + +typedef struct _Playlist { + Song **songs; + /* holds version a song was modified on */ + mpd_uint32 *songMod; + int *order; + int *positionToId; + int *idToPosition; + int length; + int current; + int queued; + int repeat; + int random; + mpd_uint32 version; +} Playlist; + +extern int playlist_saveAbsolutePaths; + +void initPlaylist(void); + +void finishPlaylist(void); + +void readPlaylistState(FILE *); + +void savePlaylistState(FILE *); + +int clearPlaylist(int fd); + +int clearStoredPlaylist(int fd, char *utf8file); + +int addToPlaylist(int fd, char *file, int printId); + +int addToStoredPlaylist(int fd, char *file, char *utf8file); + +int addSongToPlaylist(int fd, Song * song, int printId); + +int showPlaylist(int fd); + +int deleteFromPlaylist(int fd, int song); + +int deleteFromPlaylistById(int fd, int song); + +int playlistInfo(int fd, int song); + +int playlistId(int fd, int song); + +int stopPlaylist(int fd); + +int playPlaylist(int fd, int song, int stopOnError); + +int playPlaylistById(int fd, int song, int stopOnError); + +int nextSongInPlaylist(int fd); + +void syncPlayerAndPlaylist(void); + +int previousSongInPlaylist(int fd); + +int shufflePlaylist(int fd); + +int savePlaylist(int fd, char *utf8file); + +int deletePlaylist(int fd, char *utf8file); + +int deletePlaylistById(int fd, char *utf8file); + +void deleteASongFromPlaylist(Song * song); + +int moveSongInPlaylist(int fd, int from, int to); + +int moveSongInPlaylistById(int fd, int id, int to); + +int swapSongsInPlaylist(int fd, int song1, int song2); + +int swapSongsInPlaylistById(int fd, int id1, int id2); + +int loadPlaylist(int fd, char *utf8file); + +int getPlaylistRepeatStatus(void); + +int setPlaylistRepeatStatus(int fd, int status); + +int getPlaylistRandomStatus(void); + +int setPlaylistRandomStatus(int fd, int status); + +int getPlaylistCurrentSong(void); + +int getPlaylistSongId(int song); + +int getPlaylistLength(void); + +unsigned long getPlaylistVersion(void); + +void playPlaylistIfPlayerStopped(void); + +int seekSongInPlaylist(int fd, int song, float time); + +int seekSongInPlaylistById(int fd, int id, float time); + +void playlistVersionChange(void); + +int playlistChanges(int fd, mpd_uint32 version); + +int playlistChangesPosId(int fd, mpd_uint32 version); + +int PlaylistInfo(int fd, char *utf8file, int detail); + +void searchForSongsInPlaylist(int fd, int numItems, LocateTagItem * items); + +void findSongsInPlaylist(int fd, int numItems, LocateTagItem * items); + +#endif diff --git a/trunk/src/replayGain.c b/trunk/src/replayGain.c new file mode 100644 index 000000000..7c20919b8 --- /dev/null +++ b/trunk/src/replayGain.c @@ -0,0 +1,165 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * (c)2004 replayGain code by AliasMrJones + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "replayGain.h" +#include "utils.h" + +#include "log.h" +#include "conf.h" + +#include <string.h> +#include <math.h> +#include <stdlib.h> + +/* Added 4/14/2004 by AliasMrJones */ +int replayGainState = REPLAYGAIN_OFF; + +static float replayGainPreamp = 1.0; + +void initReplayGainState(void) +{ + ConfigParam *param = getConfigParam(CONF_REPLAYGAIN); + + if (!param) + return; + + if (strcmp(param->value, "track") == 0) { + replayGainState = REPLAYGAIN_TRACK; + } else if (strcmp(param->value, "album") == 0) { + replayGainState = REPLAYGAIN_ALBUM; + } else { + FATAL("replaygain value \"%s\" at line %i is invalid\n", + param->value, param->line); + } + + param = getConfigParam(CONF_REPLAYGAIN_PREAMP); + + if (param) { + char *test; + float f = strtod(param->value, &test); + + if (*test != '\0') { + FATAL("Replaygain preamp \"%s\" is not a number at " + "line %i\n", param->value, param->line); + } + + if (f < -15 || f > 15) { + FATAL("Replaygain preamp \"%s\" is not between -15 and" + "15 at line %i\n", param->value, param->line); + } + + replayGainPreamp = pow(10, f / 20.0); + } +} + +static float computeReplayGainScale(float gain, float peak) +{ + float scale; + + if (gain == 0.0) + return (1); + scale = pow(10.0, gain / 20.0); + scale *= replayGainPreamp; + if (scale > 15.0) + scale = 15.0; + + if (scale * peak > 1.0) { + scale = 1.0 / peak; + } + return (scale); +} + +ReplayGainInfo *newReplayGainInfo(void) +{ + ReplayGainInfo *ret = xmalloc(sizeof(ReplayGainInfo)); + + ret->albumGain = 0.0; + ret->albumPeak = 0.0; + + ret->trackGain = 0.0; + ret->trackPeak = 0.0; + + /* set to -1 so that we know in doReplayGain to compute the scale */ + ret->scale = -1.0; + + return ret; +} + +void freeReplayGainInfo(ReplayGainInfo * info) +{ + free(info); +} + +void doReplayGain(ReplayGainInfo * info, char *buffer, int bufferSize, + AudioFormat * format) +{ + mpd_sint16 *buffer16; + mpd_sint8 *buffer8; + mpd_sint32 temp32; + float scale; + + if (replayGainState == REPLAYGAIN_OFF || !info) + return; + + if (info->scale < 0) { + switch (replayGainState) { + case REPLAYGAIN_TRACK: + info->scale = computeReplayGainScale(info->trackGain, + info->trackPeak); + break; + default: + info->scale = computeReplayGainScale(info->albumGain, + info->albumPeak); + break; + } + } + + if (info->scale <= 1.01 && info->scale >= 0.99) + return; + + buffer16 = (mpd_sint16 *) buffer; + buffer8 = (mpd_sint8 *) buffer; + + scale = info->scale; + + switch (format->bits) { + case 16: + while (bufferSize > 0) { + temp32 = *buffer16; + temp32 *= scale; + *buffer16 = temp32 > 32767 ? 32767 : + (temp32 < -32768 ? -32768 : temp32); + buffer16++; + bufferSize -= 2; + } + break; + case 8: + while (bufferSize > 0) { + temp32 = *buffer8; + temp32 *= scale; + *buffer8 = temp32 > 127 ? 127 : + (temp32 < -128 ? -128 : temp32); + buffer8++; + bufferSize--; + } + break; + default: + ERROR("%i bits not supported by doReplaygain!\n", format->bits); + } +} diff --git a/trunk/src/replayGain.h b/trunk/src/replayGain.h new file mode 100644 index 000000000..c2d471464 --- /dev/null +++ b/trunk/src/replayGain.h @@ -0,0 +1,50 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * (c)2004 replayGain code by AliasMrJones + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef REPLAYGAIN_H +#define REPLAYGAIN_H + +#include "audio.h" + +#define REPLAYGAIN_OFF 0 +#define REPLAYGAIN_TRACK 1 +#define REPLAYGAIN_ALBUM 2 + +extern int replayGainState; + +typedef struct _ReplayGainInfo { + float albumGain; + float albumPeak; + float trackGain; + float trackPeak; + + /* used internally by mpd, to mess with it */ + float scale; +} ReplayGainInfo; + +ReplayGainInfo *newReplayGainInfo(void); + +void freeReplayGainInfo(ReplayGainInfo * info); + +void initReplayGainState(void); + +void doReplayGain(ReplayGainInfo * info, char *buffer, int bufferSize, + AudioFormat * format); + +#endif diff --git a/trunk/src/sig_handlers.c b/trunk/src/sig_handlers.c new file mode 100644 index 000000000..fc29d2522 --- /dev/null +++ b/trunk/src/sig_handlers.c @@ -0,0 +1,159 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * (c) 2004 Nick Welch (mack@incise.org) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "sig_handlers.h" +#include "player.h" +#include "playerData.h" +#include "playlist.h" +#include "directory.h" +#include "command.h" +#include "signal_check.h" +#include "log.h" +#include "player.h" +#include "decode.h" + +#include <signal.h> +#include <sys/types.h> +#include <sys/time.h> +#include <sys/resource.h> +#include <sys/wait.h> +#include <errno.h> +#include <unistd.h> + +int handlePendingSignals(void) +{ + if (signal_is_pending(SIGINT) || signal_is_pending(SIGTERM)) { + DEBUG("main process got SIGINT or SIGTERM, exiting\n"); + return COMMAND_RETURN_KILL; + } + + if (signal_is_pending(SIGHUP)) { + DEBUG("got SIGHUP, rereading DB\n"); + signal_clear(SIGHUP); + if (!isUpdatingDB()) { + readDirectoryDB(); + playlistVersionChange(); + } + if (cycle_log_files() < 0) + return COMMAND_RETURN_KILL; + playerCycleLogFiles(); + } + + return 0; +} + +static void chldSigHandler(int signal) +{ + int status; + int pid; + DEBUG("main process got SIGCHLD\n"); + while (0 != (pid = wait3(&status, WNOHANG, NULL))) { + if (pid < 0) { + if (errno == EINTR) + continue; + else + break; + } + player_sigChldHandler(pid, status); + directory_sigChldHandler(pid, status); + } +} + +void initSigHandlers(void) +{ + struct sigaction sa; + + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_IGN; + while (sigaction(SIGPIPE, &sa, NULL) < 0 && errno == EINTR) ; + sa.sa_handler = chldSigHandler; + while (sigaction(SIGCHLD, &sa, NULL) < 0 && errno == EINTR) ; + signal_handle(SIGUSR1); + signal_handle(SIGINT); + signal_handle(SIGTERM); + signal_handle(SIGHUP); +} + +void finishSigHandlers(void) +{ + signal_unhandle(SIGINT); + signal_unhandle(SIGUSR1); + signal_unhandle(SIGTERM); + signal_unhandle(SIGHUP); +} + +void setSigHandlersForDecoder(void) +{ + struct sigaction sa; + + finishSigHandlers(); + + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_IGN; + while (sigaction(SIGHUP, &sa, NULL) < 0 && errno == EINTR) ; + while (sigaction(SIGINT, &sa, NULL) < 0 && errno == EINTR) ; + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = decodeSigHandler; + while (sigaction(SIGCHLD, &sa, NULL) < 0 && errno == EINTR) ; + while (sigaction(SIGTERM, &sa, NULL) < 0 && errno == EINTR) ; +} + +void ignoreSignals(void) +{ + struct sigaction sa; + + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sa.sa_handler = SIG_IGN; + sa.sa_sigaction = NULL; + while (sigaction(SIGPIPE, &sa, NULL) < 0 && errno == EINTR) ; + while (sigaction(SIGCHLD, &sa, NULL) < 0 && errno == EINTR) ; + while (sigaction(SIGUSR1, &sa, NULL) < 0 && errno == EINTR) ; + while (sigaction(SIGINT, &sa, NULL) < 0 && errno == EINTR) ; + while (sigaction(SIGTERM, &sa, NULL) < 0 && errno == EINTR) ; + while (sigaction(SIGHUP, &sa, NULL) < 0 && errno == EINTR) ; +} + +void blockSignals(void) +{ + sigset_t sset; + + sigemptyset(&sset); + sigaddset(&sset, SIGCHLD); + sigaddset(&sset, SIGUSR1); + sigaddset(&sset, SIGHUP); + sigaddset(&sset, SIGINT); + sigaddset(&sset, SIGTERM); + while (sigprocmask(SIG_BLOCK, &sset, NULL) < 0 && errno == EINTR) ; +} + +void unblockSignals(void) +{ + sigset_t sset; + + sigemptyset(&sset); + sigaddset(&sset, SIGCHLD); + sigaddset(&sset, SIGUSR1); + sigaddset(&sset, SIGHUP); + sigaddset(&sset, SIGINT); + sigaddset(&sset, SIGTERM); + while (sigprocmask(SIG_UNBLOCK, &sset, NULL) < 0 && errno == EINTR) ; +} diff --git a/trunk/src/sig_handlers.h b/trunk/src/sig_handlers.h new file mode 100644 index 000000000..15fa181ee --- /dev/null +++ b/trunk/src/sig_handlers.h @@ -0,0 +1,42 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef SIG_HANDLERS_H +#define SIG_HANDLERS_H + +#include "../config.h" + +int handlePendingSignals(void); + +void initSigHandlers(void); + +void finishSigHandlers(void); + +void setSigHandlersForDecoder(void); + +void ignoreSignals(void); + +void blockSignals(void); + +void unblockSignals(void); + +void blockTermSignal(void); + +void unblockTermSignal(void); + +#endif diff --git a/trunk/src/signal_check.c b/trunk/src/signal_check.c new file mode 100644 index 000000000..77a2b1251 --- /dev/null +++ b/trunk/src/signal_check.c @@ -0,0 +1,60 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * (c)2004 by mackstann + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "signal_check.h" + +#include <errno.h> +#include <stddef.h> + +static volatile sig_atomic_t __caught_signals[NSIG]; + +static void __signal_handler(int sig) +{ + __caught_signals[sig] = 1; +} + +static void __set_signal_handler(int sig, void (*handler) (int)) +{ + struct sigaction act; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + act.sa_handler = handler; + while (sigaction(sig, &act, NULL) && errno == EINTR) ; +} + +void signal_handle(int sig) +{ + __set_signal_handler(sig, __signal_handler); +} + +void signal_unhandle(int sig) +{ + signal_clear(sig); + __set_signal_handler(sig, SIG_DFL); +} + +int signal_is_pending(int sig) +{ + return __caught_signals[sig]; +} + +void signal_clear(int sig) +{ + __caught_signals[sig] = 0; +} diff --git a/trunk/src/signal_check.h b/trunk/src/signal_check.h new file mode 100644 index 000000000..58c9f3c3e --- /dev/null +++ b/trunk/src/signal_check.h @@ -0,0 +1,30 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * (c)2004 by mackstann + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef SIGNAL_CHECK_H +#define SIGNAL_CHECK_H + +#include <signal.h> + +void signal_handle(int sig); +void signal_unhandle(int sig); +int signal_is_pending(int sig); +void signal_clear(int sig); + +#endif /* SIGNAL_CHECK_H */ diff --git a/trunk/src/sllist.c b/trunk/src/sllist.c new file mode 100644 index 000000000..00408a3cd --- /dev/null +++ b/trunk/src/sllist.c @@ -0,0 +1,72 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +/* a very simple singly-linked-list structure for queues/buffers */ + +#include <string.h> +#include "sllist.h" +#include "utils.h" + +static void init_strnode(struct strnode *x, char *s) +{ + x->data = s; + x->next = NULL; +} + +struct strnode *new_strnode(char *s) +{ + struct strnode *x = xmalloc(sizeof(struct strnode)); + init_strnode(x, s); + return x; +} + +struct strnode *new_strnode_dup(char *s, const size_t size) +{ + struct strnode *x = xmalloc(sizeof(struct strnode) + size); + x->next = NULL; + x->data = ((char *)x + sizeof(struct strnode)); + memcpy((void *)x->data, (void*)s, size); + return x; +} + +struct sllnode *new_sllnode(void *s, const size_t size) +{ + struct sllnode *x = xmalloc(sizeof(struct sllnode) + size); + x->next = NULL; + x->size = size; + x->data = ((char *)x + sizeof(struct sllnode)); + memcpy(x->data, (void *)s, size); + return x; +} + +struct strnode *dup_strlist(struct strnode *old) +{ + struct strnode *tmp, *new, *cur; + + tmp = old; + cur = new = new_strnode_dup(tmp->data, strlen(tmp->data) + 1); + tmp = tmp->next; + while (tmp) { + cur->next = new_strnode_dup(tmp->data, strlen(tmp->data) + 1); + cur = cur->next; + tmp = tmp->next; + } + return new; +} + + diff --git a/trunk/src/sllist.h b/trunk/src/sllist.h new file mode 100644 index 000000000..1e81abef3 --- /dev/null +++ b/trunk/src/sllist.h @@ -0,0 +1,52 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +/* a very simple singly-linked-list structure for queues/buffers */ + +#ifndef SLLIST_H +#define SLLIST_H + +#include <stddef.h> + +/* just free the entire structure if it's free-able, the 'data' member + * should _NEVER_ be explicitly freed + * + * there's no free command, iterate through them yourself and just + * call free() on it iff you xmalloc'd them */ + +struct strnode { + struct strnode *next; + char *data; +}; + +struct sllnode { + struct sllnode *next; + void *data; + size_t size; +}; + +struct strnode *new_strnode(char *s); + +struct strnode *new_strnode_dup(char *s, const size_t size); + +struct strnode *dup_strlist(struct strnode *old); + +struct sllnode *new_sllnode(void *s, const size_t size); + + +#endif /* SLLIST_H */ diff --git a/trunk/src/song.c b/trunk/src/song.c new file mode 100644 index 000000000..9bcb1a0b4 --- /dev/null +++ b/trunk/src/song.c @@ -0,0 +1,353 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "song.h" +#include "ls.h" +#include "directory.h" +#include "utils.h" +#include "tag.h" +#include "log.h" +#include "path.h" +#include "playlist.h" +#include "inputPlugin.h" +#include "myfprintf.h" + +#define SONG_KEY "key: " +#define SONG_MTIME "mtime: " + +#include <stdlib.h> +#include <string.h> +#include <assert.h> + +Song *newNullSong(void) +{ + Song *song = xmalloc(sizeof(Song)); + + song->tag = NULL; + song->url = NULL; + song->type = SONG_TYPE_FILE; + song->parentDir = NULL; + + return song; +} + +Song *newSong(char *url, int type, Directory * parentDir) +{ + Song *song = NULL; + + if (strchr(url, '\n')) { + DEBUG("newSong: '%s' is not a valid uri\n", url); + return NULL; + } + + song = newNullSong(); + + song->url = xstrdup(url); + song->type = type; + song->parentDir = parentDir; + + assert(type == SONG_TYPE_URL || parentDir); + + if (song->type == SONG_TYPE_FILE) { + InputPlugin *plugin; + unsigned int next = 0; + char *song_url = getSongUrl(song); + char *abs_path = rmp2amp(utf8ToFsCharset(song_url)); + while (!song->tag && (plugin = isMusic(song_url, + &(song->mtime), + next++))) { + song->tag = plugin->tagDupFunc(abs_path); + } + if (!song->tag || song->tag->time < 0) { + freeSong(song); + song = NULL; + } + } + + return song; +} + +void freeSong(Song * song) +{ + deleteASongFromPlaylist(song); + freeJustSong(song); +} + +void freeJustSong(Song * song) +{ + free(song->url); + if (song->tag) + freeMpdTag(song->tag); + free(song); + getSongUrl(NULL); +} + +SongList *newSongList(void) +{ + return makeList((ListFreeDataFunc *) freeSong, 0); +} + +Song *addSongToList(SongList * list, char *url, char *utf8path, + int songType, Directory * parentDirectory) +{ + Song *song = NULL; + + switch (songType) { + case SONG_TYPE_FILE: + if (isMusic(utf8path, NULL, 0)) { + song = newSong(url, songType, parentDirectory); + } + break; + case SONG_TYPE_URL: + song = newSong(url, songType, parentDirectory); + break; + default: + DEBUG("addSongToList: Trying to add an invalid song type\n"); + } + + if (song == NULL) + return NULL; + + insertInList(list, song->url, (void *)song); + + return song; +} + +void freeSongList(SongList * list) +{ + freeList(list); +} + +void printSongUrl(int fd, Song * song) +{ + if (song->parentDir && song->parentDir->path) { + fdprintf(fd, "%s%s/%s\n", SONG_FILE, + getDirectoryPath(song->parentDir), song->url); + } else { + fdprintf(fd, "%s%s\n", SONG_FILE, song->url); + } +} + +int printSongInfo(int fd, Song * song) +{ + printSongUrl(fd, song); + + if (song->tag) + printMpdTag(fd, song->tag); + + return 0; +} + +int printSongInfoFromList(int fd, SongList * list) +{ + ListNode *tempNode = list->firstNode; + + while (tempNode != NULL) { + printSongInfo(fd, (Song *) tempNode->data); + tempNode = tempNode->nextNode; + } + + return 0; +} + +void writeSongInfoFromList(FILE * fp, SongList * list) +{ + ListNode *tempNode = list->firstNode; + + fprintf(fp, "%s\n", SONG_BEGIN); + + while (tempNode != NULL) { + fprintf(fp, "%s%s\n", SONG_KEY, tempNode->key); + fflush(fp); + printSongInfo(fileno(fp), (Song *) tempNode->data); + fprintf(fp, "%s%li\n", SONG_MTIME, + (long)((Song *) tempNode->data)->mtime); + tempNode = tempNode->nextNode; + } + + fprintf(fp, "%s\n", SONG_END); +} + +static void insertSongIntoList(SongList * list, ListNode ** nextSongNode, + char *key, Song * song) +{ + ListNode *nodeTemp; + int cmpRet = 0; + + while (*nextSongNode + && (cmpRet = strcmp(key, (*nextSongNode)->key)) > 0) { + nodeTemp = (*nextSongNode)->nextNode; + deleteNodeFromList(list, *nextSongNode); + *nextSongNode = nodeTemp; + } + + if (!(*nextSongNode)) { + insertInList(list, song->url, (void *)song); + } else if (cmpRet == 0) { + Song *tempSong = (Song *) ((*nextSongNode)->data); + if (tempSong->mtime != song->mtime) { + freeMpdTag(tempSong->tag); + tempSong->tag = song->tag; + tempSong->mtime = song->mtime; + song->tag = NULL; + } + freeJustSong(song); + *nextSongNode = (*nextSongNode)->nextNode; + } else { + insertInListBeforeNode(list, *nextSongNode, -1, song->url, + (void *)song); + } +} + +static int matchesAnMpdTagItemKey(char *buffer, int *itemType) +{ + int i; + + for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) { + if (0 == strncmp(mpdTagItemKeys[i], buffer, + strlen(mpdTagItemKeys[i]))) { + *itemType = i; + return 1; + } + } + + return 0; +} + +void readSongInfoIntoList(FILE * fp, SongList * list, Directory * parentDir) +{ + char buffer[MAXPATHLEN + 1024]; + int bufferSize = MAXPATHLEN + 1024; + Song *song = NULL; + ListNode *nextSongNode = list->firstNode; + ListNode *nodeTemp; + int itemType; + + while (myFgets(buffer, bufferSize, fp) && 0 != strcmp(SONG_END, buffer)) { + if (0 == strncmp(SONG_KEY, buffer, strlen(SONG_KEY))) { + if (song) { + insertSongIntoList(list, &nextSongNode, + song->url, song); + song = NULL; + } + + song = newNullSong(); + song->url = xstrdup(buffer + strlen(SONG_KEY)); + song->type = SONG_TYPE_FILE; + song->parentDir = parentDir; + } else if (0 == strncmp(SONG_FILE, buffer, strlen(SONG_FILE))) { + if (!song) + FATAL("Problems reading song info\n"); + /* we don't need this info anymore + song->url = xstrdup(&(buffer[strlen(SONG_FILE)])); + */ + } else if (matchesAnMpdTagItemKey(buffer, &itemType)) { + if (!song->tag) + song->tag = newMpdTag(); + addItemToMpdTag(song->tag, itemType, + &(buffer + [strlen(mpdTagItemKeys[itemType]) + + 2])); + } else if (0 == strncmp(SONG_TIME, buffer, strlen(SONG_TIME))) { + if (!song->tag) + song->tag = newMpdTag(); + song->tag->time = atoi(&(buffer[strlen(SONG_TIME)])); + } else if (0 == strncmp(SONG_MTIME, buffer, strlen(SONG_MTIME))) { + song->mtime = atoi(&(buffer[strlen(SONG_MTIME)])); + } + /* ignore empty lines (starting with '\0') */ + else if (*buffer) + FATAL("songinfo: unknown line in db: %s\n", buffer); + } + + if (song) { + insertSongIntoList(list, &nextSongNode, song->url, song); + song = NULL; + } + + while (nextSongNode) { + nodeTemp = nextSongNode->nextNode; + deleteNodeFromList(list, nextSongNode); + nextSongNode = nodeTemp; + } +} + +int updateSongInfo(Song * song) +{ + if (song->type == SONG_TYPE_FILE) { + InputPlugin *plugin; + unsigned int next = 0; + char *song_url = getSongUrl(song); + char *abs_path = rmp2amp(song_url); + + if (song->tag) + freeMpdTag(song->tag); + + song->tag = NULL; + + while (!song->tag && (plugin = isMusic(song_url, + &(song->mtime), + next++))) { + song->tag = plugin->tagDupFunc(abs_path); + } + if (!song->tag || song->tag->time < 0) + return -1; + } + + return 0; +} + +/* pass song = NULL to reset, we do this freeJustSong(), so that if + * we free and recreate this memory we make sure to print it correctly*/ +char *getSongUrl(Song * song) +{ + static char *buffer; + static int bufferSize; + static Song *lastSong; + int slen; + int dlen; + int size; + + if (!song) { + lastSong = song; + return NULL; + } + + if (!song->parentDir || !song->parentDir->path) + return song->url; + + /* be careful with this! */ + if (song == lastSong) + return buffer; + + slen = strlen(song->url); + dlen = strlen(getDirectoryPath(song->parentDir)); + + size = slen + dlen + 2; + + if (size > bufferSize) { + buffer = xrealloc(buffer, size); + bufferSize = size; + } + + strcpy(buffer, getDirectoryPath(song->parentDir)); + buffer[dlen] = '/'; + strcpy(buffer + dlen + 1, song->url); + + return buffer; +} diff --git a/trunk/src/song.h b/trunk/src/song.h new file mode 100644 index 000000000..c4100d2a2 --- /dev/null +++ b/trunk/src/song.h @@ -0,0 +1,79 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef SONG_H +#define SONG_H + +#include "../config.h" + +#include <sys/param.h> +#include <time.h> + +#include "tag.h" +#include "list.h" + +#define SONG_BEGIN "songList begin" +#define SONG_END "songList end" + +#define SONG_TYPE_FILE 1 +#define SONG_TYPE_URL 2 + +#define SONG_FILE "file: " +#define SONG_TIME "Time: " + +typedef struct _Song { + char *url; + mpd_sint8 type; + MpdTag *tag; + struct _Directory *parentDir; + time_t mtime; +} Song; + +typedef List SongList; + +Song *newNullSong(void); + +Song *newSong(char *url, int songType, struct _Directory *parentDir); + +void freeSong(Song *); + +void freeJustSong(Song *); + +SongList *newSongList(void); + +void freeSongList(SongList * list); + +Song *addSongToList(SongList * list, char *url, char *utf8path, + int songType, struct _Directory *parentDir); + +int printSongInfo(int fd, Song * song); + +int printSongInfoFromList(int fd, SongList * list); + +void writeSongInfoFromList(FILE * fp, SongList * list); + +void readSongInfoIntoList(FILE * fp, SongList * list, + struct _Directory *parent); + +int updateSongInfo(Song * song); + +void printSongUrl(int fd, Song * song); + +char *getSongUrl(Song * song); + +#endif diff --git a/trunk/src/state_file.c b/trunk/src/state_file.c new file mode 100644 index 000000000..ac2fcde7a --- /dev/null +++ b/trunk/src/state_file.c @@ -0,0 +1,111 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "../config.h" +#include "state_file.h" +#include "conf.h" +#include "gcc.h" +#include "log.h" +#include "audio.h" +#include "playlist.h" +#include "utils.h" +#include "volume.h" + +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> + +static struct _sf_cb { + void (*reader)(FILE *); + void (*writer)(FILE *); +} sf_callbacks [] = { + { read_sw_volume_state, save_sw_volume_state }, + { readAudioDevicesState, saveAudioDevicesState }, + { readPlaylistState, savePlaylistState }, +}; + +static const char *sfpath; + +static void get_state_file_path(void) +{ + ConfigParam *param; + if (sfpath) + return; + param = parseConfigFilePath(CONF_STATE_FILE, 0); + if (param) + sfpath = (const char *)param->value; +} + +void write_state_file(void) +{ + int i; + FILE *fp; + + if (!sfpath) + return; + while (!(fp = fopen(sfpath, "w")) && errno == EINTR); + + if (mpd_unlikely(!fp)) { + ERROR("problems opening state file \"%s\" for writing: %s\n", + sfpath, strerror(errno)); + return; + } + + for (i = 0; i < ARRAY_SIZE(sf_callbacks); i++) + sf_callbacks[i].writer(fp); + + while(fclose(fp) && errno == EINTR) /* nothing */; +} + +void read_state_file(void) +{ + struct stat st; + int i; + FILE *fp; + + get_state_file_path(); + if (!sfpath) + return; + if (stat(sfpath, &st) < 0) { + DEBUG("failed to stat state file: %s\n", sfpath); + return; + } + if (!S_ISREG(st.st_mode)) + FATAL("state file \"%s\" is not a regular file\n", sfpath); + + while (!(fp = fopen(sfpath, "r")) && errno == EINTR); + if (mpd_unlikely(!fp)) { + FATAL("problems opening state file \"%s\" for reading: %s\n", + sfpath, strerror(errno)); + } + for (i = 0; i < ARRAY_SIZE(sf_callbacks); i++) { + sf_callbacks[i].reader(fp); + rewind(fp); + } + + while(fclose(fp) && errno == EINTR) /* nothing */; +} + +void mpd_noreturn state_file_fatal(void) +{ + FATAL("error parsing state file \"%s\"\n", sfpath); + exit(EXIT_FAILURE); +} + diff --git a/trunk/src/state_file.h b/trunk/src/state_file.h new file mode 100644 index 000000000..4a7d012ec --- /dev/null +++ b/trunk/src/state_file.h @@ -0,0 +1,30 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef STATE_FILE_H +#define STATE_FILE_H + +#include "gcc.h" + +#include <stdio.h> + +void write_state_file(void); +void read_state_file(void); +void mpd_noreturn state_file_fatal(void); + +#endif /* STATE_FILE_H */ diff --git a/trunk/src/stats.c b/trunk/src/stats.c new file mode 100644 index 000000000..5045077c0 --- /dev/null +++ b/trunk/src/stats.c @@ -0,0 +1,48 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "stats.h" + +#include "directory.h" +#include "myfprintf.h" +#include "player.h" +#include "tag.h" +#include "tagTracker.h" + +#include <time.h> + +Stats stats; + +void initStats(void) +{ + stats.daemonStart = time(NULL); + stats.numberOfSongs = 0; +} + +int printStats(int fd) +{ + fdprintf(fd, "artists: %i\n", getNumberOfTagItems(TAG_ITEM_ARTIST)); + fdprintf(fd, "albums: %i\n", getNumberOfTagItems(TAG_ITEM_ALBUM)); + fdprintf(fd, "songs: %i\n", stats.numberOfSongs); + fdprintf(fd, "uptime: %li\n", time(NULL) - stats.daemonStart); + fdprintf(fd, "playtime: %li\n", + (long)(getPlayerTotalPlayTime() + 0.5)); + fdprintf(fd, "db_playtime: %li\n", stats.dbPlayTime); + fdprintf(fd, "db_update: %li\n", getDbModTime()); + return 0; +} diff --git a/trunk/src/stats.h b/trunk/src/stats.h new file mode 100644 index 000000000..cd7d0122c --- /dev/null +++ b/trunk/src/stats.h @@ -0,0 +1,40 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef STATS_H +#define STATS_H + +#include "../config.h" + +#include <stdio.h> + +typedef struct _Stats { + unsigned long daemonStart; + int numberOfSongs; + unsigned long dbPlayTime; + /*unsigned long playTime; + unsigned long songsPlayed; */ +} Stats; + +extern Stats stats; + +void initStats(void); + +int printStats(int fd); + +#endif diff --git a/trunk/src/storedPlaylist.c b/trunk/src/storedPlaylist.c new file mode 100644 index 000000000..322cb1b5b --- /dev/null +++ b/trunk/src/storedPlaylist.c @@ -0,0 +1,501 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "storedPlaylist.h" +#include "log.h" +#include "path.h" +#include "utils.h" +#include "playlist.h" +#include "ack.h" +#include "command.h" +#include "ls.h" +#include "directory.h" + +#include <string.h> +#include <errno.h> + +static char *utf8pathToFsPathInStoredPlaylist(const char *utf8path, int fd) +{ + char *file; + char *rfile; + char *actualFile; + + if (strstr(utf8path, "/")) { + commandError(fd, ACK_ERROR_ARG, "playlist name \"%s\" is " + "invalid: playlist names may not contain slashes", + utf8path); + return NULL; + } + + file = utf8ToFsCharset((char *)utf8path); + + rfile = xmalloc(strlen(file) + strlen(".") + + strlen(PLAYLIST_FILE_SUFFIX) + 1); + + strcpy(rfile, file); + strcat(rfile, "."); + strcat(rfile, PLAYLIST_FILE_SUFFIX); + + actualFile = rpp2app(rfile); + + free(rfile); + + return actualFile; +} + +static unsigned int lengthOfStoredPlaylist(StoredPlaylist *sp) +{ + return sp->list->numberOfNodes; +} + +static ListNode *nodeOfStoredPlaylist(StoredPlaylist *sp, int index) +{ + int forward; + ListNode *node; + int i; + + if (index >= lengthOfStoredPlaylist(sp) || index < 0) + return NULL; + + if (index > lengthOfStoredPlaylist(sp)/2) { + forward = 0; + node = sp->list->lastNode; + i = lengthOfStoredPlaylist(sp) - 1; + } else { + forward = 1; + node = sp->list->firstNode; + i = 0; + } + + while (node != NULL) { + if (i == index) + return node; + + if (forward) { + i++; + node = node->nextNode; + } else { + i--; + node = node->prevNode; + } + } + + return NULL; +} + +static void appendSongToStoredPlaylist(StoredPlaylist *sp, Song *song) +{ + insertInListWithoutKey(sp->list, xstrdup(getSongUrl(song))); +} + +StoredPlaylist *newStoredPlaylist(const char *utf8name, int fd, int ignoreExisting) +{ + struct stat buf; + char *filename = NULL; + StoredPlaylist *sp = calloc(1, sizeof(*sp)); + if (!sp) + return NULL; + + if (utf8name) { + filename = utf8pathToFsPathInStoredPlaylist(utf8name, fd); + + if (filename && stat(filename, &buf) == 0 && + ignoreExisting == 0) { + commandError(fd, ACK_ERROR_EXIST, + "a file or directory already exists with " + "the name \"%s\"", utf8name); + free(sp); + return NULL; + } + } + + sp->list = makeList(DEFAULT_FREE_DATA_FUNC, 0); + sp->fd = fd; + + if (filename) + sp->fspath = xstrdup(filename); + + return sp; +} + +StoredPlaylist *loadStoredPlaylist(const char *utf8path, int fd) +{ + char *filename; + StoredPlaylist *sp; + FILE *file; + char s[MAXPATHLEN + 1]; + int slength = 0; + char *temp = utf8ToFsCharset((char *)utf8path); + char *parent = parentPath(temp); + int parentlen = strlen(parent); + int tempInt; + int commentCharFound = 0; + Song *song; + + filename = utf8pathToFsPathInStoredPlaylist(utf8path, fd); + if (!filename) + return NULL; + + while (!(file = fopen(filename, "r")) && errno == EINTR); + if (file == NULL) { + commandError(fd, ACK_ERROR_NO_EXIST, "could not open file " + "\"%s\": %s", filename, strerror(errno)); + return NULL; + } + + sp = newStoredPlaylist(utf8path, fd, 1); + if (!sp) + goto out; + + while ((tempInt = fgetc(file)) != EOF) { + s[slength] = tempInt; + if (s[slength] == '\n' || s[slength] == '\0') { + commentCharFound = 0; + s[slength] = '\0'; + if (s[0] == PLAYLIST_COMMENT) + commentCharFound = 1; + if (strncmp(s, musicDir, strlen(musicDir)) == 0) { + strcpy(s, &(s[strlen(musicDir)])); + } else if (parentlen) { + temp = xstrdup(s); + memset(s, 0, MAXPATHLEN + 1); + strcpy(s, parent); + strncat(s, "/", MAXPATHLEN - parentlen); + strncat(s, temp, MAXPATHLEN - parentlen - 1); + if (strlen(s) >= MAXPATHLEN) { + commandError(sp->fd, + ACK_ERROR_PLAYLIST_LOAD, + "\"%s\" is too long", temp); + free(temp); + freeStoredPlaylist(sp); + sp = NULL; + goto out; + } + free(temp); + } + slength = 0; + temp = fsCharsetToUtf8(s); + if (temp && !commentCharFound) { + song = getSongFromDB(temp); + if (song) { + appendSongToStoredPlaylist(sp, song); + continue; + } + + if (!isValidRemoteUtf8Url(temp)) + continue; + + song = newSong(temp, SONG_TYPE_URL, NULL); + if (song) { + appendSongToStoredPlaylist(sp, song); + freeJustSong(song); + } + } + } else if (slength == MAXPATHLEN) { + s[slength] = '\0'; + commandError(sp->fd, ACK_ERROR_PLAYLIST_LOAD, + "line \"%s\" in playlist \"%s\" " + "is too long", s, utf8path); + freeStoredPlaylist(sp); + sp = NULL; + goto out; + } else if (s[slength] != '\r') { + slength++; + } + } + +out: + while (fclose(file) && errno == EINTR); + return sp; +} + +void freeStoredPlaylist(StoredPlaylist *sp) +{ + if (sp->list) + freeList(sp->list); + if (sp->fspath) + free(sp->fspath); + + free(sp); +} + +static int moveSongInStoredPlaylist(int fd, StoredPlaylist *sp, int src, int dest) +{ + ListNode *srcNode, *destNode; + + if (src >= lengthOfStoredPlaylist(sp) || dest >= lengthOfStoredPlaylist(sp) || src < 0 || dest < 0 || src == dest) { + commandError(fd, ACK_ERROR_ARG, "argument out of range"); + return -1; + } + + srcNode = nodeOfStoredPlaylist(sp, src); + if (!srcNode) + return -1; + + destNode = nodeOfStoredPlaylist(sp, dest); + + /* remove src */ + if (srcNode->prevNode) + srcNode->prevNode->nextNode = srcNode->nextNode; + else + sp->list->firstNode = srcNode->nextNode; + + if (srcNode->nextNode) + srcNode->nextNode->prevNode = srcNode->prevNode; + else + sp->list->lastNode = srcNode->prevNode; + + /* this is all a bit complicated - but I tried to + * maintain the same order stuff is moved as in the + * real playlist */ + if (dest == 0) { + sp->list->firstNode->prevNode = srcNode; + srcNode->nextNode = sp->list->firstNode; + srcNode->prevNode = NULL; + sp->list->firstNode = srcNode; + } else if ((dest + 1) == lengthOfStoredPlaylist(sp)) { + sp->list->lastNode->nextNode = srcNode; + srcNode->nextNode = NULL; + srcNode->prevNode = sp->list->lastNode; + sp->list->lastNode = srcNode; + } else { + if (destNode == NULL) { + /* this shouldn't be happening. */ + return -1; + } + + if (src > dest) { + destNode->prevNode->nextNode = srcNode; + srcNode->prevNode = destNode->prevNode; + srcNode->nextNode = destNode; + destNode->prevNode = srcNode; + } else { + destNode->nextNode->prevNode = srcNode; + srcNode->prevNode = destNode; + srcNode->nextNode = destNode->nextNode; + destNode->nextNode = srcNode; + } + } + + return 0; +} + +int moveSongInStoredPlaylistByPath(int fd, const char *utf8path, int src, int dest) +{ + StoredPlaylist *sp = loadStoredPlaylist(utf8path, fd); + if (!sp) { + commandError(fd, ACK_ERROR_UNKNOWN, "could not open playlist"); + return -1; + } + + if (moveSongInStoredPlaylist(fd, sp, src, dest) != 0) { + freeStoredPlaylist(sp); + return -1; + } + + if (writeStoredPlaylist(sp) != 0) { + commandError(fd, ACK_ERROR_UNKNOWN, "failed to save playlist"); + freeStoredPlaylist(sp); + return -1; + } + + freeStoredPlaylist(sp); + return 0; +} + +/* Not used currently +static void removeAllFromStoredPlaylist(StoredPlaylist *sp) +{ + freeList(sp->list); + sp->list = makeList(DEFAULT_FREE_DATA_FUNC, 0); +} +*/ + +int removeAllFromStoredPlaylistByPath(int fd, const char *utf8path) +{ + char *filename; + FILE *file; + + filename = utf8pathToFsPathInStoredPlaylist(utf8path, fd); + if (!filename) + return -1; + + while (!(file = fopen(filename, "w")) && errno == EINTR); + if (file == NULL) { + commandError(fd, ACK_ERROR_NO_EXIST, "could not open file " + "\"%s\": %s", filename, strerror(errno)); + return -1; + } + + while (fclose(file) != 0 && errno == EINTR); + return 0; +} + +static int removeOneSongFromStoredPlaylist(int fd, StoredPlaylist *sp, int pos) +{ + ListNode *node = nodeOfStoredPlaylist(sp, pos); + if (!node) { + commandError(fd, ACK_ERROR_ARG, + "could not find song at position"); + return -1; + } + + deleteNodeFromList(sp->list, node); + + return 0; +} + +int removeOneSongFromStoredPlaylistByPath(int fd, const char *utf8path, int pos) +{ + StoredPlaylist *sp = loadStoredPlaylist(utf8path, fd); + if (!sp) { + commandError(fd, ACK_ERROR_UNKNOWN, "could not open playlist"); + return -1; + } + + if (removeOneSongFromStoredPlaylist(fd, sp, pos) != 0) { + freeStoredPlaylist(sp); + return -1; + } + + if (writeStoredPlaylist(sp) != 0) { + commandError(fd, ACK_ERROR_UNKNOWN, "failed to save playlist"); + freeStoredPlaylist(sp); + return -1; + } + + freeStoredPlaylist(sp); + return 0; +} + +static int writeStoredPlaylistToPath(StoredPlaylist *sp, const char *fspath) +{ + ListNode *node; + FILE *file; + char *s; + + if (fspath == NULL) + return -1; + + while (!(file = fopen(fspath, "w")) && errno == EINTR); + if (file == NULL) { + commandError(sp->fd, ACK_ERROR_NO_EXIST, "could not open file " + "\"%s\": %s", fspath, strerror(errno)); + return -1; + } + + node = sp->list->firstNode; + while (node != NULL) { + s = (char *)node->data; + if (isValidRemoteUtf8Url(s) || !playlist_saveAbsolutePaths) + s = utf8ToFsCharset(s); + else + s = rmp2amp(utf8ToFsCharset(s)); + fprintf(file, "%s\n", s); + node = node->nextNode; + } + + while (fclose(file) != 0 && errno == EINTR); + return 0; +} + +int writeStoredPlaylist(StoredPlaylist *sp) +{ + return writeStoredPlaylistToPath(sp, sp->fspath); +} + +int appendSongToStoredPlaylistByPath(int fd, const char *utf8path, Song *song) +{ + char *filename; + FILE *file; + char *s; + + filename = utf8pathToFsPathInStoredPlaylist(utf8path, fd); + if (!filename) + return -1; + + while (!(file = fopen(filename, "a")) && errno == EINTR); + if (file == NULL) { + commandError(fd, ACK_ERROR_NO_EXIST, "could not open file " + "\"%s\": %s", filename, strerror(errno)); + return -1; + } + + if (playlist_saveAbsolutePaths && song->type == SONG_TYPE_FILE) + s = rmp2amp(utf8ToFsCharset(getSongUrl(song))); + else + s = utf8ToFsCharset(getSongUrl(song)); + + fprintf(file, "%s\n", s); + + while (fclose(file) != 0 && errno == EINTR); + return 0; +} + +void appendPlaylistToStoredPlaylist(StoredPlaylist *sp, Playlist *playlist) +{ + int i; + for (i = 0; i < playlist->length; i++) + appendSongToStoredPlaylist(sp, playlist->songs[i]); +} + +int renameStoredPlaylist(int fd, const char *utf8from, const char *utf8to) +{ + struct stat st; + char *from; + char *to; + int ret = 0; + + from = xstrdup(utf8pathToFsPathInStoredPlaylist(utf8from, fd)); + if (!from) + return -1; + + to = xstrdup(utf8pathToFsPathInStoredPlaylist(utf8to, fd)); + if (!to) { + free(from); + return -1; + } + + if (stat(from, &st) != 0) { + commandError(fd, ACK_ERROR_NO_EXIST, + "no playlist named \"%s\"", utf8from); + ret = -1; + goto out; + } + + if (stat(to, &st) == 0) { + commandError(fd, ACK_ERROR_EXIST, "a file or directory " + "already exists with the name \"%s\"", utf8to); + ret = -1; + goto out; + } + + if (rename(from, to) < 0) { + commandError(fd, ACK_ERROR_UNKNOWN, + "could not rename playlist \"%s\" to \"%s\": %s", + utf8from, utf8to, strerror(errno)); + ret = -1; + goto out; + } + +out: + free(from); + free(to); + + return ret; +} diff --git a/trunk/src/storedPlaylist.h b/trunk/src/storedPlaylist.h new file mode 100644 index 000000000..1c30e814a --- /dev/null +++ b/trunk/src/storedPlaylist.h @@ -0,0 +1,48 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef STORED_PLAYLIST_H +#define STORED_PLAYLIST_H + +#include "song.h" +#include "list.h" +#include "playlist.h" + +typedef struct _storedPlaylist { + List *list; + unsigned int length; + char *fspath; + int fd; +} StoredPlaylist; + +StoredPlaylist *newStoredPlaylist(const char *filename, int fd, int ignoreExisting); +StoredPlaylist *loadStoredPlaylist(const char *utf8path, int fd); +void freeStoredPlaylist(StoredPlaylist *sp); + +int moveSongInStoredPlaylistByPath(int fd, const char *utf8path, int src, int dest); +int removeAllFromStoredPlaylistByPath(int fd, const char *utf8path); +int removeOneSongFromStoredPlaylistByPath(int fd, const char *utf8path, int pos); + +int writeStoredPlaylist(StoredPlaylist *sp); + +int appendSongToStoredPlaylistByPath(int fd, const char *utf8path, Song *song); +void appendPlaylistToStoredPlaylist(StoredPlaylist *sp, Playlist *playlist); + +int renameStoredPlaylist(int fd, const char *utf8from, const char *utf8to); + +#endif diff --git a/trunk/src/tag.c b/trunk/src/tag.c new file mode 100644 index 000000000..92a597d0e --- /dev/null +++ b/trunk/src/tag.c @@ -0,0 +1,646 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "tag.h" +#include "path.h" +#include "myfprintf.h" +#include "utils.h" +#include "utf8.h" +#include "log.h" +#include "inputStream.h" +#include "conf.h" +#include "charConv.h" +#include "tagTracker.h" +#include "mpd_types.h" +#include "gcc.h" +#include "song.h" + +#include <sys/stat.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <assert.h> +#include <errno.h> + +#ifdef HAVE_ID3TAG +# define isId3v1(tag) (id3_tag_options(tag, 0, 0) & ID3_TAG_OPTION_ID3V1) +# ifndef ID3_FRAME_COMPOSER +# define ID3_FRAME_COMPOSER "TCOM" +# endif +# ifndef ID3_FRAME_PERFORMER +# define ID3_FRAME_PERFORMER "TOPE" +# endif +# ifndef ID3_FRAME_DISC +# define ID3_FRAME_DISC "TPOS" +# endif +#endif + +char *mpdTagItemKeys[TAG_NUM_OF_ITEM_TYPES] = { + "Artist", + "Album", + "Title", + "Track", + "Name", + "Genre", + "Date", + "Composer", + "Performer", + "Comment", + "Disc" +}; + +static mpd_sint8 ignoreTagItems[TAG_NUM_OF_ITEM_TYPES]; + +void initTagConfig(void) +{ + int quit = 0; + char *temp; + char *s; + char *c; + ConfigParam *param; + int i; + + /* parse the "metadata_to_use" config parameter below */ + + memset(ignoreTagItems, 0, TAG_NUM_OF_ITEM_TYPES); + ignoreTagItems[TAG_ITEM_COMMENT] = 1; /* ignore comments by default */ + + param = getConfigParam(CONF_METADATA_TO_USE); + + if (!param) + return; + + memset(ignoreTagItems, 1, TAG_NUM_OF_ITEM_TYPES); + + if (0 == strcasecmp(param->value, "none")) + return; + + temp = c = s = xstrdup(param->value); + while (!quit) { + if (*s == ',' || *s == '\0') { + if (*s == '\0') + quit = 1; + *s = '\0'; + for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) { + if (strcasecmp(c, mpdTagItemKeys[i]) == 0) { + ignoreTagItems[i] = 0; + break; + } + } + if (strlen(c) && i == TAG_NUM_OF_ITEM_TYPES) { + FATAL("error parsing metadata item \"%s\" at " + "line %i\n", c, param->line); + } + s++; + c = s; + } + s++; + } + + free(temp); +} + +void printTagTypes(int fd) +{ + int i; + + for (i = 0; i < TAG_NUM_OF_ITEM_TYPES; i++) { + if (ignoreTagItems[i] == 0) + fdprintf(fd, "tagtype: %s\n", mpdTagItemKeys[i]); + } +} + +void printMpdTag(int fd, MpdTag * tag) +{ + int i; + + if (tag->time >= 0) + fdprintf(fd, SONG_TIME "%i\n", tag->time); + + for (i = 0; i < tag->numOfItems; i++) { + fdprintf(fd, "%s: %s\n", mpdTagItemKeys[tag->items[i].type], + tag->items[i].value); + } +} + +#ifdef HAVE_ID3TAG +static MpdTag *getID3Info(struct id3_tag *tag, char *id, int type, MpdTag * mpdTag) +{ + struct id3_frame const *frame; + id3_ucs4_t const *ucs4; + id3_utf8_t *utf8; + id3_latin1_t *isostr; + union id3_field const *field; + unsigned int nstrings; + int i; + char *encoding; + + frame = id3_tag_findframe(tag, id, 0); + if (!frame || frame->nfields < 2) + return mpdTag; + + field = &frame->fields[1]; + nstrings = id3_field_getnstrings(field); + + for (i = 0; i < nstrings; i++) { + ucs4 = id3_field_getstrings(field, i); + if (!ucs4) + continue; + + if (type == TAG_ITEM_GENRE) + ucs4 = id3_genre_name(ucs4); + + if (isId3v1(tag) && + (encoding = getConfigParamValue(CONF_ID3V1_ENCODING))) { + isostr = id3_ucs4_latin1duplicate(ucs4); + if (mpd_unlikely(!isostr)) + continue; + setCharSetConversion("UTF-8", encoding); + utf8 = (id3_utf8_t *)convStrDup((char *)isostr); + if (!utf8) { + DEBUG("Unable to convert %s string to UTF-8: " + "'%s'\n", encoding, isostr); + free(isostr); + continue; + } + free(isostr); + } else { + utf8 = id3_ucs4_utf8duplicate(ucs4); + if (mpd_unlikely(!utf8)) + continue; + } + + if (mpdTag == NULL) + mpdTag = newMpdTag(); + addItemToMpdTag(mpdTag, type, (char *)utf8); + + free(utf8); + } + + return mpdTag; +} +#endif + +#ifdef HAVE_ID3TAG +MpdTag *parseId3Tag(struct id3_tag * tag) +{ + MpdTag *ret = NULL; + + ret = getID3Info(tag, ID3_FRAME_ARTIST, TAG_ITEM_ARTIST, ret); + ret = getID3Info(tag, ID3_FRAME_TITLE, TAG_ITEM_TITLE, ret); + ret = getID3Info(tag, ID3_FRAME_ALBUM, TAG_ITEM_ALBUM, ret); + ret = getID3Info(tag, ID3_FRAME_TRACK, TAG_ITEM_TRACK, ret); + ret = getID3Info(tag, ID3_FRAME_YEAR, TAG_ITEM_DATE, ret); + ret = getID3Info(tag, ID3_FRAME_GENRE, TAG_ITEM_GENRE, ret); + ret = getID3Info(tag, ID3_FRAME_COMPOSER, TAG_ITEM_COMPOSER, ret); + ret = getID3Info(tag, ID3_FRAME_PERFORMER, TAG_ITEM_PERFORMER, ret); + ret = getID3Info(tag, ID3_FRAME_COMMENT, TAG_ITEM_COMMENT, ret); + ret = getID3Info(tag, ID3_FRAME_DISC, TAG_ITEM_DISC, ret); + + return ret; +} +#endif + +#ifdef HAVE_ID3TAG +static int fillBuffer(void *buf, size_t size, FILE * stream, + long offset, int whence) +{ + if (fseek(stream, offset, whence) != 0) return 0; + return fread(buf, 1, size, stream); +} +#endif + +#ifdef HAVE_ID3TAG +static int getId3v2FooterSize(FILE * stream, long offset, int whence) +{ + id3_byte_t buf[ID3_TAG_QUERYSIZE]; + int bufsize; + + bufsize = fillBuffer(buf, ID3_TAG_QUERYSIZE, stream, offset, whence); + if (bufsize <= 0) return 0; + return id3_tag_query(buf, bufsize); +} +#endif + +#ifdef HAVE_ID3TAG +static struct id3_tag *getId3Tag(FILE * stream, long offset, int whence) +{ + struct id3_tag *tag; + id3_byte_t queryBuf[ID3_TAG_QUERYSIZE]; + id3_byte_t *tagBuf; + int tagSize; + int queryBufSize; + int tagBufSize; + + /* It's ok if we get less than we asked for */ + queryBufSize = fillBuffer(queryBuf, ID3_TAG_QUERYSIZE, + stream, offset, whence); + if (queryBufSize <= 0) return NULL; + + /* Look for a tag header */ + tagSize = id3_tag_query(queryBuf, queryBufSize); + if (tagSize <= 0) return NULL; + + /* Found a tag. Allocate a buffer and read it in. */ + tagBuf = xmalloc(tagSize); + if (!tagBuf) return NULL; + + tagBufSize = fillBuffer(tagBuf, tagSize, stream, offset, whence); + if (tagBufSize < tagSize) { + free(tagBuf); + return NULL; + } + + tag = id3_tag_parse(tagBuf, tagBufSize); + + free(tagBuf); + + return tag; +} +#endif + +#ifdef HAVE_ID3TAG +static struct id3_tag *findId3TagFromBeginning(FILE * stream) +{ + struct id3_tag *tag; + struct id3_tag *seektag; + struct id3_frame *frame; + int seek; + + tag = getId3Tag(stream, 0, SEEK_SET); + if (!tag) { + return NULL; + } else if (isId3v1(tag)) { + /* id3v1 tags don't belong here */ + id3_tag_delete(tag); + return NULL; + } + + /* We have an id3v2 tag, so let's look for SEEK frames */ + while ((frame = id3_tag_findframe(tag, "SEEK", 0))) { + /* Found a SEEK frame, get it's value */ + seek = id3_field_getint(id3_frame_field(frame, 0)); + if (seek < 0) + break; + + /* Get the tag specified by the SEEK frame */ + seektag = getId3Tag(stream, seek, SEEK_CUR); + if (!seektag || isId3v1(seektag)) + break; + + /* Replace the old tag with the new one */ + id3_tag_delete(tag); + tag = seektag; + } + + return tag; +} +#endif + +#ifdef HAVE_ID3TAG +static struct id3_tag *findId3TagFromEnd(FILE * stream) +{ + struct id3_tag *tag; + struct id3_tag *v1tag; + int tagsize; + + /* Get an id3v1 tag from the end of file for later use */ + v1tag = getId3Tag(stream, -128, SEEK_END); + + /* Get the id3v2 tag size from the footer (located before v1tag) */ + tagsize = getId3v2FooterSize(stream, (v1tag ? -128 : 0) - 10, SEEK_END); + if (tagsize >= 0) + return v1tag; + + /* Get the tag which the footer belongs to */ + tag = getId3Tag(stream, tagsize, SEEK_CUR); + if (!tag) + return v1tag; + + /* We have an id3v2 tag, so ditch v1tag */ + id3_tag_delete(v1tag); + + return tag; +} +#endif + +MpdTag *id3Dup(char *file) +{ + MpdTag *ret = NULL; +#ifdef HAVE_ID3TAG + struct id3_tag *tag; + FILE *stream; + + stream = fopen(file, "r"); + if (!stream) { + DEBUG("id3Dup: Failed to open file: '%s', %s\n", file, + strerror(errno)); + return NULL; + } + + tag = findId3TagFromBeginning(stream); + if (!tag) + tag = findId3TagFromEnd(stream); + + fclose(stream); + + if (!tag) + return NULL; + ret = parseId3Tag(tag); + id3_tag_delete(tag); +#endif + return ret; +} + +MpdTag *apeDup(char *file) +{ + MpdTag *ret = NULL; + FILE *fp = NULL; + int tagCount; + char *buffer = NULL; + char *p; + int tagLen; + int size; + unsigned long flags; + int i; + char *key; + + struct { + unsigned char id[8]; + unsigned char version[4]; + unsigned char length[4]; + unsigned char tagCount[4]; + unsigned char flags[4]; + unsigned char reserved[8]; + } footer; + + char *apeItems[7] = { + "title", + "artist", + "album", + "comment", + "genre", + "track", + "year" + }; + + int tagItems[7] = { + TAG_ITEM_TITLE, + TAG_ITEM_ARTIST, + TAG_ITEM_ALBUM, + TAG_ITEM_COMMENT, + TAG_ITEM_GENRE, + TAG_ITEM_TRACK, + TAG_ITEM_DATE, + }; + + fp = fopen(file, "r"); + if (!fp) + return NULL; + + /* determine if file has an apeV2 tag */ + if (fseek(fp, 0, SEEK_END)) + goto fail; + size = ftell(fp); + if (fseek(fp, size - sizeof(footer), SEEK_SET)) + goto fail; + if (fread(&footer, 1, sizeof(footer), fp) != sizeof(footer)) + goto fail; + if (memcmp(footer.id, "APETAGEX", sizeof(footer.id)) != 0) + goto fail; + if (readLEuint32(footer.version) != 2000) + goto fail; + + /* find beginning of ape tag */ + tagLen = readLEuint32(footer.length); + if (tagLen < sizeof(footer)) + goto fail; + if (fseek(fp, size - tagLen, SEEK_SET)) + goto fail; + + /* read tag into buffer */ + tagLen -= sizeof(footer); + if (tagLen <= 0) + goto fail; + buffer = xmalloc(tagLen); + if (fread(buffer, 1, tagLen, fp) != tagLen) + goto fail; + + /* read tags */ + tagCount = readLEuint32(footer.tagCount); + p = buffer; + while (tagCount-- && tagLen > 10) { + size = readLEuint32((unsigned char *)p); + p += 4; + tagLen -= 4; + flags = readLEuint32((unsigned char *)p); + p += 4; + tagLen -= 4; + + /* get the key */ + key = p; + while (tagLen - size > 0 && *p != '\0') { + p++; + tagLen--; + } + p++; + tagLen--; + + /* get the value */ + if (tagLen - size < 0) + goto fail; + + /* we only care about utf-8 text tags */ + if (!(flags & (0x3 << 1))) { + for (i = 0; i < 7; i++) { + if (strcasecmp(key, apeItems[i]) == 0) { + if (!ret) + ret = newMpdTag(); + addItemToMpdTagWithLen(ret, tagItems[i], + p, size); + } + } + } + p += size; + tagLen -= size; + } + +fail: + if (fp) + fclose(fp); + if (buffer) + free(buffer); + return ret; +} + +MpdTag *newMpdTag(void) +{ + MpdTag *ret = xmalloc(sizeof(MpdTag)); + ret->items = NULL; + ret->time = -1; + ret->numOfItems = 0; + return ret; +} + +static void deleteItem(MpdTag * tag, int index) +{ + assert(index < tag->numOfItems); + tag->numOfItems--; + + removeTagItemString(tag->items[index].type, tag->items[index].value); + /* free(tag->items[index].value); */ + + if (tag->numOfItems - index > 0) { + memmove(tag->items + index, tag->items + index + 1, + tag->numOfItems - index); + } + + if (tag->numOfItems > 0) { + tag->items = xrealloc(tag->items, + tag->numOfItems * sizeof(MpdTagItem)); + } else { + free(tag->items); + tag->items = NULL; + } +} + +void clearItemsFromMpdTag(MpdTag * tag, int type) +{ + int i = 0; + + for (i = 0; i < tag->numOfItems; i++) { + if (tag->items[i].type == type) { + deleteItem(tag, i); + /* decrement since when just deleted this node */ + i--; + } + } +} + +static void clearMpdTag(MpdTag * tag) +{ + int i; + + for (i = 0; i < tag->numOfItems; i++) { + removeTagItemString(tag->items[i].type, tag->items[i].value); + /* free(tag->items[i].value); */ + } + + if (tag->items) + free(tag->items); + tag->items = NULL; + + tag->numOfItems = 0; + + tag->time = -1; +} + +void freeMpdTag(MpdTag * tag) +{ + clearMpdTag(tag); + free(tag); +} + +MpdTag *mpdTagDup(MpdTag * tag) +{ + MpdTag *ret = NULL; + int i; + + if (!tag) + return NULL; + + ret = newMpdTag(); + ret->time = tag->time; + + for (i = 0; i < tag->numOfItems; i++) { + addItemToMpdTag(ret, tag->items[i].type, tag->items[i].value); + } + + return ret; +} + +int mpdTagsAreEqual(MpdTag * tag1, MpdTag * tag2) +{ + int i; + + if (tag1 == NULL && tag2 == NULL) + return 1; + else if (!tag1 || !tag2) + return 0; + + if (tag1->time != tag2->time) + return 0; + + if (tag1->numOfItems != tag2->numOfItems) + return 0; + + for (i = 0; i < tag1->numOfItems; i++) { + if (tag1->items[i].type != tag2->items[i].type) + return 0; + if (strcmp(tag1->items[i].value, tag2->items[i].value)) { + return 0; + } + } + + return 1; +} + +#define fixUtf8(str) { \ + if(str && !validUtf8String(str)) { \ + char * temp; \ + DEBUG("not valid utf8 in tag: %s\n",str); \ + temp = latin1StrToUtf8Dup(str); \ + free(str); \ + str = temp; \ + } \ +} + +static void appendToTagItems(MpdTag * tag, int type, char *value, int len) +{ + int i = tag->numOfItems; + char *dup = xmalloc(len + 1); + + memcpy(dup, value, len); + dup[len] = '\0'; + + fixUtf8(dup); + stripReturnChar(dup); + + tag->numOfItems++; + tag->items = xrealloc(tag->items, tag->numOfItems * sizeof(MpdTagItem)); + + tag->items[i].type = type; + tag->items[i].value = getTagItemString(type, dup); + + free(dup); +} + +void addItemToMpdTagWithLen(MpdTag * tag, int itemType, char *value, int len) +{ + if (ignoreTagItems[itemType]) + return; + + if (!value || !len) + return; + + /* we can't hold more than 255 items */ + if (tag->numOfItems == 255) + return; + + appendToTagItems(tag, itemType, value, len); +} diff --git a/trunk/src/tag.h b/trunk/src/tag.h new file mode 100644 index 000000000..9723facdd --- /dev/null +++ b/trunk/src/tag.h @@ -0,0 +1,89 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef TAG_H +#define TAG_H + +#include "../config.h" + +#include "mpd_types.h" + +#include <string.h> + +#include <stdio.h> +#ifdef HAVE_ID3TAG +#include <id3tag.h> +#endif + +#define TAG_ITEM_ARTIST 0 +#define TAG_ITEM_ALBUM 1 +#define TAG_ITEM_TITLE 2 +#define TAG_ITEM_TRACK 3 +#define TAG_ITEM_NAME 4 +#define TAG_ITEM_GENRE 5 +#define TAG_ITEM_DATE 6 +#define TAG_ITEM_COMPOSER 7 +#define TAG_ITEM_PERFORMER 8 +#define TAG_ITEM_COMMENT 9 +#define TAG_ITEM_DISC 10 + +#define TAG_NUM_OF_ITEM_TYPES 11 + +extern char *mpdTagItemKeys[]; + +typedef struct _MpdTagItem { + mpd_sint8 type; + char *value; +} MpdTagItem; + +typedef struct _MpdTag { + int time; + MpdTagItem *items; + mpd_uint8 numOfItems; +} MpdTag; + +#ifdef HAVE_ID3TAG +MpdTag *parseId3Tag(struct id3_tag *); +#endif + +MpdTag *apeDup(char *file); + +MpdTag *id3Dup(char *file); + +MpdTag *newMpdTag(void); + +void initTagConfig(void); + +void clearItemsFromMpdTag(MpdTag * tag, int itemType); + +void freeMpdTag(MpdTag * tag); + +void addItemToMpdTagWithLen(MpdTag * tag, int itemType, char *value, int len); + +#define addItemToMpdTag(tag, itemType, value) \ + addItemToMpdTagWithLen(tag, itemType, value, strlen(value)) + +void printTagTypes(int fd); + +void printMpdTag(int fd, MpdTag * tag); + +MpdTag *mpdTagDup(MpdTag * tag); + +int mpdTagsAreEqual(MpdTag * tag1, MpdTag * tag2); + +#endif diff --git a/trunk/src/tagTracker.c b/trunk/src/tagTracker.c new file mode 100644 index 000000000..ab356e500 --- /dev/null +++ b/trunk/src/tagTracker.c @@ -0,0 +1,147 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "tagTracker.h" + +#include "tree.h" +#include "log.h" +#include "utils.h" +#include "myfprintf.h" + +#include <assert.h> +#include <stdlib.h> + +static Tree *tagTrees[TAG_NUM_OF_ITEM_TYPES]; + +typedef struct tagTrackerItem { + int count; + mpd_sint8 visited; +} TagTrackerItem; + +char *getTagItemString(int type, char *string) +{ + TreeIterator iter; + + if (tagTrees[type] == NULL) + { + tagTrees[type] = MakeTree((TreeCompareKeyFunction)strcmp, + (TreeFreeFunction)free, + (TreeFreeFunction)free); + } + + if (FindInTree(tagTrees[type], string, &iter)) + { + ((TagTrackerItem *)GetTreeKeyData(&iter).data)->count++; + return (char *)GetTreeKeyData(&iter).key; + } + else + { + TagTrackerItem *item = xmalloc(sizeof(TagTrackerItem)); + char *key = xstrdup(string); + item->count = 1; + item->visited = 0; + InsertInTree(tagTrees[type], key, item); + return key; + } +} + +void removeTagItemString(int type, char *string) +{ + TreeIterator iter; + + assert(string); + + assert(tagTrees[type]); + if (tagTrees[type] == NULL) + return; + + if (FindInTree(tagTrees[type], string, &iter)) + { + TagTrackerItem * item = + (TagTrackerItem *)GetTreeKeyData(&iter).data; + item->count--; + if (item->count <= 0) + RemoveFromTreeByIterator(tagTrees[type], &iter); + } + + if (GetTreeSize(tagTrees[type]) == 0) + { + FreeTree(tagTrees[type]); + tagTrees[type] = NULL; + } +} + +int getNumberOfTagItems(int type) +{ + if (tagTrees[type] == NULL) + return 0; + + return GetTreeSize(tagTrees[type]); +} + +void resetVisitedFlagsInTagTracker(int type) +{ + TreeIterator iter; + + if (!tagTrees[type]) + return; + + for (SetTreeIteratorToBegin(tagTrees[type], &iter); + !IsTreeIteratorAtEnd(&iter); + IncrementTreeIterator(&iter)) + { + ((TagTrackerItem *)GetTreeKeyData(&iter).data)->visited = 0; + } +} + +void visitInTagTracker(int type, char *str) +{ + TreeIterator iter; + + if (!tagTrees[type]) + return; + + if (!FindInTree(tagTrees[type], str, &iter)) + return; + + ((TagTrackerItem *)GetTreeKeyData(&iter).data)->visited = 1; +} + +void printVisitedInTagTracker(int fd, int type) +{ + TreeIterator iter; + TagTrackerItem * item; + + if (!tagTrees[type]) + return; + + for (SetTreeIteratorToBegin(tagTrees[type], &iter); + !IsTreeIteratorAtEnd(&iter); + IncrementTreeIterator(&iter)) + { + item = ((TagTrackerItem *)GetTreeKeyData(&iter).data); + + if (item->visited) + { + fdprintf(fd, + "%s: %s\n", + mpdTagItemKeys[type], + (char *)GetTreeKeyData(&iter).key); + } + } +} diff --git a/trunk/src/tagTracker.h b/trunk/src/tagTracker.h new file mode 100644 index 000000000..09d07f1dc --- /dev/null +++ b/trunk/src/tagTracker.h @@ -0,0 +1,38 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef TAG_TRACKER_H +#define TAG_TRACKER_H + +#include "tag.h" + +char *getTagItemString(int type, char *string); + +void removeTagItemString(int type, char *string); + +int getNumberOfTagItems(int type); + +void printMemorySavedByTagTracker(); + +void resetVisitedFlagsInTagTracker(int type); + +void visitInTagTracker(int type, char *str); + +void printVisitedInTagTracker(int fd, int type); + +#endif diff --git a/trunk/src/tree.c b/trunk/src/tree.c new file mode 100644 index 000000000..87028d744 --- /dev/null +++ b/trunk/src/tree.c @@ -0,0 +1,706 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2006-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "tree.h" +#include "utils.h" + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#ifndef CHILDREN_PER_NODE +#define CHILDREN_PER_NODE 25 +#endif + +#define DATA_PER_NODE (CHILDREN_PER_NODE-1) + +#if CHILDREN_PER_NODE > 7 +#define USE_BINARY_SEARCH 1 +#endif + + +/************************* DATA STRUCTURES **********************************/ + +struct _TreeNode +{ + TreeKeyData keyData[DATA_PER_NODE]; + struct _TreeNode * parent; + short parentPos; + struct _TreeNode * children[CHILDREN_PER_NODE]; + short count; +}; + +struct _Tree +{ + TreeCompareKeyFunction compareKey; + TreeFreeFunction freeKey; + TreeFreeFunction freeData; + TreeNode * rootNode; + int size; +}; + +/************************* STATIC METHODS ***********************************/ + +static +TreeNode * +_MakeNode(void) +{ + TreeNode * ret = xmalloc(sizeof(TreeNode)); + memset(ret, 0, sizeof(TreeNode)); + return ret; +} + +static +void +_ClearKeyData(TreeKeyData * keyData) +{ + memset(keyData, 0, sizeof(TreeKeyData)); +} + +static +int +_FindPosition(Tree * tree, TreeNode * node, void * key, int * pos) +{ +#ifdef USE_BINARY_SEARCH + int low = 0; + int high = node->count; + int cmp = -1; + + while (high > low) + { + int cur = (high + low) >> 1; + cmp = tree->compareKey(key, node->keyData[cur].key); + if (cmp > 0) + { + low = cur+1; + } + else if (cmp < 0) + { + high = cur; + } + else + { + low = cur; + break; + } + } + + *pos = low; + return (cmp == 0); +#else + int i = 0; + int cmp = -1; + for (; + i < node->count && + (cmp = tree->compareKey(key, node->keyData[i].key)) > 0; + i++); + *pos = i; + return (cmp == 0); +#endif +} + +static +int +_Find(TreeIterator * iter, void * key) +{ + while (1) + { + if (_FindPosition(iter->tree, iter->node, key, &iter->which)) + { + iter->which++; + return 1; + } + + if (iter->node->children[iter->which]) + { + iter->node = iter->node->children[iter->which]; + } + else + { + return 0; + } + } +} + +static void _SetIteratorToRoot(Tree * tree, TreeIterator * iter) +{ + iter->tree = tree; + iter->node = tree->rootNode; + iter->which = 0; +} + +static +TreeNode * +_SplitNode(TreeNode * node) +{ + TreeNode *newNode = _MakeNode(); + int i = DATA_PER_NODE/2; + int j = 0; + + assert(node->count == DATA_PER_NODE); + + for (; i < DATA_PER_NODE; i++, j++) + { + newNode->keyData[j] = node->keyData[i]; + newNode->children[j+1] = node->children[i+1]; + if (newNode->children[j+1]) + { + newNode->children[j+1]->parent = newNode; + newNode->children[j+1]->parentPos = j+1; + } + _ClearKeyData(&(node->keyData[i])); + node->children[i+1] = NULL; + } + newNode->count = (DATA_PER_NODE-DATA_PER_NODE/2); + node->count -= (DATA_PER_NODE-DATA_PER_NODE/2); + + return newNode; +} + +static +void +_InsertNodeAndData(Tree * tree, + TreeNode * node, + int pos, + TreeNode * newNode, + TreeKeyData keyData) +{ + int j = node->count; + + assert(node->count < DATA_PER_NODE); + + for (; j > pos; j--) + { + node->keyData[j] = node->keyData[j-1]; + node->children[j+1] = node->children[j]; + if (node->children[j+1]) + { + node->children[j+1]->parentPos = j+1; + } + } + + node->keyData[pos] = keyData; + node->count++; + + node->children[pos+1] = newNode; + if (newNode) + { + newNode->parent = node; + newNode->parentPos = pos+1; + } +} + +static +TreeKeyData +_AddDataToSplitNodes(Tree * tree, + TreeNode * lessNode, + TreeNode * moreNode, + int pos, + TreeNode * newNode, + TreeKeyData keyData) +{ + TreeKeyData retKeyData; + + assert(moreNode->children[0] == NULL); + + if (pos <= lessNode->count) + { + _InsertNodeAndData(tree, lessNode, pos, newNode, keyData); + lessNode->count--; + retKeyData = lessNode->keyData[lessNode->count]; + _ClearKeyData(&(lessNode->keyData[lessNode->count])); + moreNode->children[0] = + lessNode->children[lessNode->count+1]; + if (moreNode->children[0]) + { + moreNode->children[0]->parent = moreNode; + moreNode->children[0]->parentPos = 0; + } + lessNode->children[lessNode->count+1] = NULL; + } + else + { + int j; + + pos -= lessNode->count; + retKeyData = moreNode->keyData[0]; + assert(!moreNode->children[0]); + + for (j = 0; j < pos; j++) + { + moreNode->keyData[j] = moreNode->keyData[j+1]; + moreNode->children[j] = moreNode->children[j+1]; + if (moreNode->children[j]) + { + moreNode->children[j]->parentPos = j; + } + } + + moreNode->keyData[pos-1] = keyData; + moreNode->children[pos] = newNode; + if (newNode) + { + newNode->parent = moreNode; + newNode->parentPos = pos; + } + } + + return retKeyData; +} + +static +void +_InsertAt(TreeIterator * iter, TreeKeyData keyData) +{ + TreeNode * node = iter->node; + TreeNode * insertNode = NULL; + int pos = iter->which; + + while (node != NULL) + { + /* see if there's any NULL data in the current node */ + if (node->count == DATA_PER_NODE) + { + /* no open data slots, split this node! */ + TreeNode * newNode = _SplitNode(node); + + /* insert data in split nodes */ + keyData = _AddDataToSplitNodes(iter->tree, + node, + newNode, + pos, + insertNode, + keyData); + + if (node->parent == NULL) + { + assert(node == iter->tree->rootNode); + iter->tree->rootNode = _MakeNode(); + iter->tree->rootNode->children[0] = node; + node->parent = iter->tree->rootNode; + node->parentPos = 0; + iter->tree->rootNode->children[1] = newNode; + newNode->parent = iter->tree->rootNode; + newNode->parentPos = 1; + iter->tree->rootNode->keyData[0] = keyData; + iter->tree->rootNode->count = 1; + return; + } + + pos = node->parentPos; + node = node->parent; + insertNode = newNode; + } + else + { + /* insert the data and newNode */ + _InsertNodeAndData(iter->tree, + node, + pos, + insertNode, + keyData); + return; + } + } +} + +static +void +_MergeNodes(TreeNode * lessNode, TreeNode * moreNode) +{ + int i = 0; + int j = lessNode->count; + + assert((lessNode->count + moreNode->count) <= DATA_PER_NODE); + assert(lessNode->children[j] == NULL); + + for(; i < moreNode->count; i++,j++) + { + assert(!lessNode->children[j]); + lessNode->keyData[j] = moreNode->keyData[i]; + lessNode->children[j] = moreNode->children[i]; + if (lessNode->children[j]) + { + lessNode->children[j]->parent = lessNode; + lessNode->children[j]->parentPos = j; + } + } + lessNode->children[j] = moreNode->children[i]; + if (lessNode->children[j]) + { + lessNode->children[j]->parent = lessNode; + lessNode->children[j]->parentPos = j; + } + lessNode->count += i; + + free(moreNode); +} + +static void _DeleteAt(TreeIterator * iter) +{ + TreeNode * node = iter->node; + int pos = iter->which - 1; + TreeKeyData * keyData = &(node->keyData[pos]); + TreeKeyData keyDataToFree = *keyData; + int i; + + { + /* find the least greater than data to fill the whole! */ + if (node->children[pos+1]) + { + TreeNode * child = node->children[++pos]; + while (child->children[0]) + { + pos = 0; + child = child->children[0]; + } + + *keyData = child->keyData[0]; + keyData = &(child->keyData[0]); + node = child; + } + /* or the greatest lesser than data to fill the whole! */ + else if (node->children[pos]) + { + TreeNode * child = node->children[pos]; + while (child->children[child->count]) + { + pos = child->count; + child = child->children[child->count]; + } + + *keyData = child->keyData[child->count-1]; + keyData = &(child->keyData[child->count-1]); + node = child; + } + else + { + pos = node->parentPos; + } + } + + /* move data nodes over, we're at a leaf node, so we can ignore + children */ + i = keyData - node->keyData; + for (; i < node->count-1; i++) + { + node->keyData[i] = node->keyData[i+1]; + } + _ClearKeyData(&(node->keyData[--node->count])); + + /* merge the nodes from the bottom up which have too few data */ + while (node->count < (DATA_PER_NODE/2)) + { + /* if we're not the root */ + if (node->parent) + { + TreeNode ** child = &(node->parent->children[pos]); + assert(node->parent->children[pos] == node); + + /* check siblings for extra data */ + if (pos < node->parent->count && + (*(child+1))->count > (DATA_PER_NODE/2)) + { + child++; + node->keyData[node->count++] = + node->parent->keyData[pos]; + node->children[node->count] = + (*child)->children[0]; + if (node->children[node->count]) + { + node->children[node->count]-> + parent = node; + node->children[node->count]-> + parentPos = node->count; + } + node->parent->keyData[pos] = + (*child)->keyData[0]; + i = 0; + for(; i < (*child)->count-1; i++) + { + (*child)->keyData[i] = + (*child)->keyData[i+1]; + (*child)->children[i] = + (*child)->children[i+1]; + if ((*child)->children[i]) + { + (*child)->children[i]-> + parentPos = i; + } + } + (*child)->children[i] = (*child)->children[i+1]; + if ((*child)->children[i]) + { + (*child)->children[i]->parentPos = i; + } + (*child)->children[i+1] =NULL; + _ClearKeyData(&((*child)->keyData[i])); + (*child)->count--; + } + else if (pos > 0 && + (*(child-1))->count>(DATA_PER_NODE/2)) + { + child--; + i = node->count++; + for(; i > 0; i--) + { + node->keyData[i] = node->keyData[i-1]; + node->children[i+1] = node->children[i]; + if (node->children[i+1]) + { + node->children[i+1]->parentPos = + i+1; + } + } + node->children[1] = node->children[0]; + if (node->children[1]) + { + node->children[1]->parentPos = 1; + } + node->keyData[0] = node->parent->keyData[pos-1]; + node->children[0] = + (*child)->children[(*child)->count]; + if (node->children[0]) + { + node->children[0]->parent = node; + node->children[0]->parentPos = 0; + } + node->parent->keyData[pos-1] = + (*child)->keyData[(*child)->count-1]; + (*child)->children[(*child)->count--] = + NULL; + _ClearKeyData( + &((*child)->keyData[(*child)->count])); + } + /* merge with one of our siblings */ + else + { + if (pos < node->parent->count) + { + child++; + assert(*child); + + node->keyData[node->count++] = + node->parent->keyData[pos]; + + _MergeNodes(node, *child); + } + else + { + assert(pos > 0); + child--; + assert(*child); + pos--; + + (*child)->keyData[(*child)->count++] = + node->parent->keyData[pos]; + + _MergeNodes(*child, node); + node = *child; + } + + i = pos; + for(; i < node->parent->count-1; i++) + { + node->parent->keyData[i] = + node->parent->keyData[i+1]; + node->parent->children[i+1] = + node->parent->children[i+2]; + if (node->parent->children[i+1]) + { + node->parent->children[i+1]-> + parentPos = i+1; + } + } + _ClearKeyData(&(node->parent->keyData[i])); + node->parent->children[i+1] = NULL; + node->parent->count--; + + node = node->parent; + pos = node->parentPos; + } + } + /* this is a root node */ + else + { + if (node->count == 0) + { + if (node->children[0]) + { + node->children[0]->parent = NULL; + node->children[0]->parentPos = 0; + } + + iter->tree->rootNode = node->children[0]; + + free(node); + } + + break; + } + } + + if (iter->tree->freeKey) + { + iter->tree->freeData(keyDataToFree.key); + } + if (iter->tree->freeData) + { + iter->tree->freeData(keyDataToFree.data); + } +} + +/************************* PUBLIC METHODS ***********************************/ + +Tree * +MakeTree(TreeCompareKeyFunction compareKey, + TreeFreeFunction freeKey, + TreeFreeFunction freeData) +{ + Tree * ret = xmalloc(sizeof(Tree)); + ret->compareKey = compareKey; + ret->freeKey = freeKey; + ret->freeData = freeData; + ret->rootNode = _MakeNode(); + ret->size = 0; + return ret; +} + +void +FreeTree(Tree * tree) +{ + assert(tree->rootNode == NULL); + free(tree); +} + +int +GetTreeSize(Tree * tree) +{ + return tree->size; +} + +void SetTreeIteratorToBegin(Tree * tree, TreeIterator * iter) +{ + _SetIteratorToRoot(tree, iter); + IncrementTreeIterator(iter); +} + +int IsTreeIteratorAtEnd(const TreeIterator * iter) +{ + return (iter->node == NULL); +} + +void IncrementTreeIterator(TreeIterator * iter) +{ + while(iter->node) + { + if (iter->node->children[iter->which]) + { + iter->node = iter->node->children[iter->which]; + iter->which = 0; + } + else + { + iter->which++; + } + + while (iter->node && iter->which > iter->node->count) + { + iter->which = iter->node->parentPos + 1; + iter->node = iter->node->parent; + } + + if (iter->node && + iter->which > 0 && iter->which <= iter->node->count) + { + return; + } + } +} + +TreeKeyData +GetTreeKeyData(TreeIterator * iter) +{ + assert(iter->node && + iter->which > 0 && + iter->which <= iter->node->count); + return iter->node->keyData[iter->which-1]; +} + +int +InsertInTree(Tree * tree, void * key, void * data) +{ + TreeKeyData keyData; + TreeIterator iter; + + _SetIteratorToRoot(tree, &iter); + + if (_Find(&iter, key)) + { + return 0; + } + + keyData.key = key; + keyData.data = data; + _InsertAt(&iter, keyData); + tree->size++; + + return 1; +} + +int +RemoveFromTreeByKey(Tree * tree, void * key) +{ + TreeIterator iter; + _SetIteratorToRoot(tree, &iter); + + if (_Find(&iter, key)) + { + _DeleteAt(&iter); + tree->size--; + return 1; + } + + return 0; +} + +void +RemoveFromTreeByIterator(Tree * tree, TreeIterator * iter) +{ + _DeleteAt(iter); + tree->size--; +} + +int +FindInTree(Tree * tree, void * key, TreeIterator * iter) +{ + TreeIterator i; + + if (iter == NULL) + { + iter = &i; + } + + _SetIteratorToRoot(tree, iter); + if (_Find(iter, key)) + { + return 1; + } + + return 0; +} diff --git a/trunk/src/tree.h b/trunk/src/tree.h new file mode 100644 index 000000000..76a980cd2 --- /dev/null +++ b/trunk/src/tree.h @@ -0,0 +1,62 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2006-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef TREE_H +#define TREE_H + +typedef struct _Tree Tree; +typedef struct _TreeNode TreeNode; +typedef struct _TreeIterator TreeIterator; +typedef struct _TreeKeyData TreeKeyData; + +struct _TreeIterator +{ + Tree * tree; + TreeNode * node; + int which; +}; + +struct _TreeKeyData +{ + void * key; + void * data; +}; + +typedef int (*TreeCompareKeyFunction)(const void * key1, const void * key2); +typedef void (*TreeFreeFunction)(void * data); + +Tree * MakeTree(TreeCompareKeyFunction compareFunc, + TreeFreeFunction freeKey, + TreeFreeFunction freeData); +void FreeTree(Tree * tree); + +int GetTreeSize(Tree * tree); + +void SetTreeIteratorToBegin(Tree * tree, TreeIterator * iter); +int IsTreeIteratorAtEnd(const TreeIterator * iter); +void IncrementTreeIterator(TreeIterator * iter); + +TreeKeyData GetTreeKeyData(TreeIterator * iter); + +int InsertInTree(Tree * tree, void * key, void * data); +int RemoveFromTreeByKey(Tree * tree, void * key); +void RemoveFromTreeByIterator(Tree * tree, TreeIterator * iter); + +int FindInTree(Tree * tree, void * key, TreeIterator * iter /* can be NULL */); + +#endif diff --git a/trunk/src/utf8.c b/trunk/src/utf8.c new file mode 100644 index 000000000..2061a78de --- /dev/null +++ b/trunk/src/utf8.c @@ -0,0 +1,148 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "utf8.h" +#include "utils.h" + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +static char *latin1ToUtf8(char c) +{ + static unsigned char utf8[3]; + unsigned char uc = c; + + memset(utf8, 0, 3); + + if (uc < 128) + utf8[0] = uc; + else if (uc < 192) { + utf8[0] = 194; + utf8[1] = uc; + } else { + utf8[0] = 195; + utf8[1] = uc - 64; + } + + return (char *)utf8; +} + +char *latin1StrToUtf8Dup(char *latin1) +{ + /* utf8 should have at most two char's per latin1 char */ + int len = strlen(latin1) * 2 + 1; + char *ret = xmalloc(len); + char *cp = ret; + char *utf8; + + memset(ret, 0, len); + + len = 0; + + while (*latin1) { + utf8 = latin1ToUtf8(*latin1); + while (*utf8) { + *(cp++) = *(utf8++); + len++; + } + latin1++; + } + + return xrealloc(ret, len + 1); +} + +static char utf8ToLatin1(char *inUtf8) +{ + unsigned char c = 0; + unsigned char *utf8 = (unsigned char *)inUtf8; + + if (utf8[0] < 128) + return utf8[0]; + else if (utf8[0] == 195) + c += 64; + else if (utf8[0] != 194) + return '?'; + return (char)(c + utf8[1]); +} + +static int validateUtf8Char(char *inUtf8Char) +{ + unsigned char *utf8Char = (unsigned char *)inUtf8Char; + + if (utf8Char[0] < 0x80) + return 1; + + if (utf8Char[0] >= 0xC0 && utf8Char[0] <= 0xFD) { + int count = 1; + char t = 1 << 5; + int i; + while (count < 6 && (t & utf8Char[0])) { + t = (t >> 1); + count++; + } + if (count > 5) + return 0; + for (i = 1; i <= count; i++) { + if (utf8Char[i] < 0x80 || utf8Char[i] > 0xBF) + return 0; + } + return count + 1; + } else + return 0; +} + +int validUtf8String(char *string) +{ + int ret; + + while (*string) { + ret = validateUtf8Char(string); + if (0 == ret) + return 0; + string += ret; + } + + return 1; +} + +char *utf8StrToLatin1Dup(char *utf8) +{ + /* utf8 should have at most two char's per latin1 char */ + int len = strlen(utf8) + 1; + char *ret = xmalloc(len); + char *cp = ret; + int count; + + memset(ret, 0, len); + + len = 0; + + while (*utf8) { + count = validateUtf8Char(utf8); + if (!count) { + free(ret); + return NULL; + } + *(cp++) = utf8ToLatin1(utf8); + utf8 += count; + len++; + } + + return xrealloc(ret, len + 1); +} diff --git a/trunk/src/utf8.h b/trunk/src/utf8.h new file mode 100644 index 000000000..0eb60d82c --- /dev/null +++ b/trunk/src/utf8.h @@ -0,0 +1,28 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef UTF_8_H +#define UTF_8_H + +char *latin1StrToUtf8Dup(char *latin1); + +char *utf8StrToLatin1Dup(char *utf8); + +int validUtf8String(char *string); + +#endif diff --git a/trunk/src/utils.c b/trunk/src/utils.c new file mode 100644 index 000000000..a6dd9d8ae --- /dev/null +++ b/trunk/src/utils.c @@ -0,0 +1,161 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include "utils.h" +#include "log.h" + +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/select.h> +#include <sys/time.h> +#include <unistd.h> +#include <assert.h> + +char *myFgets(char *buffer, int bufferSize, FILE * fp) +{ + char *ret = fgets(buffer, bufferSize, fp); + if (ret && strlen(buffer) > 0 && buffer[strlen(buffer) - 1] == '\n') { + buffer[strlen(buffer) - 1] = '\0'; + } + if (ret && strlen(buffer) > 0 && buffer[strlen(buffer) - 1] == '\r') { + buffer[strlen(buffer) - 1] = '\0'; + } + return ret; +} + +char *strDupToUpper(char *str) +{ + char *ret = xstrdup(str); + int i; + + for (i = 0; i < strlen(str); i++) + ret[i] = toupper((int)ret[i]); + + return ret; +} + +void stripReturnChar(char *string) +{ + while (string && (string = strchr(string, '\n'))) { + *string = ' '; + } +} + +void my_usleep(long usec) +{ + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = usec; + + select(0, NULL, NULL, NULL, &tv); +} + +int ipv6Supported(void) +{ +#ifdef HAVE_IPV6 + int s; + s = socket(AF_INET6, SOCK_STREAM, 0); + if (s == -1) + return 0; + close(s); + return 1; +#endif + return 0; +} + +char *appendToString(char *dest, const char *src) +{ + int destlen; + int srclen = strlen(src); + + if (dest == NULL) { + dest = xmalloc(srclen + 1); + memset(dest, 0, srclen + 1); + destlen = 0; + } else { + destlen = strlen(dest); + dest = xrealloc(dest, destlen + srclen + 1); + } + + memcpy(dest + destlen, src, srclen); + dest[destlen + srclen] = '\0'; + + return dest; +} + +unsigned long readLEuint32(const unsigned char *p) +{ + return ((unsigned long)p[0] << 0) | + ((unsigned long)p[1] << 8) | + ((unsigned long)p[2] << 16) | ((unsigned long)p[3] << 24); +} + +mpd_malloc char *xstrdup(const char *s) +{ + char *ret = strdup(s); + if (mpd_unlikely(!ret)) + FATAL("OOM: strdup\n"); + return ret; +} + +/* borrowed from git :) */ + +mpd_malloc void *xmalloc(size_t size) +{ + void *ret; + + assert(mpd_likely(size)); + + ret = malloc(size); + if (mpd_unlikely(!ret)) + FATAL("OOM: malloc\n"); + return ret; +} + +mpd_malloc void *xrealloc(void *ptr, size_t size) +{ + void *ret = realloc(ptr, size); + + /* some C libraries return NULL when size == 0, + * make sure we get a free()-able pointer (free(NULL) + * doesn't work with all C libraries, either) */ + if (mpd_unlikely(!ret && !size)) + ret = realloc(ptr, 1); + + if (mpd_unlikely(!ret)) + FATAL("OOM: realloc\n"); + return ret; +} + +mpd_malloc void *xcalloc(size_t nmemb, size_t size) +{ + void *ret; + + assert(mpd_likely(nmemb && size)); + + ret = calloc(nmemb, size); + if (mpd_unlikely(!ret)) + FATAL("OOM: calloc\n"); + return ret; +} + + diff --git a/trunk/src/utils.h b/trunk/src/utils.h new file mode 100644 index 000000000..2f911499b --- /dev/null +++ b/trunk/src/utils.h @@ -0,0 +1,85 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef UTILS_H +#define UTILS_H + +#include "../config.h" +#include "gcc.h" + +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) + +char *myFgets(char *buffer, int bufferSize, FILE * fp); + +char *strDupToUpper(char *str); + +void stripReturnChar(char *string); + +void my_usleep(long usec); + +int ipv6Supported(void); + +char *appendToString(char *dest, const char *src); + +unsigned long readLEuint32(const unsigned char *p); + +/* trivial functions, keep them inlined */ +static inline void xclose(int fd) +{ + while (close(fd) && errno == EINTR); +} + +static inline ssize_t xread(int fd, void *buf, size_t len) +{ + ssize_t nr; + while (1) { + nr = read(fd, buf, len); + if ((nr < 0) && (errno == EAGAIN || errno == EINTR)) + continue; + return nr; + } +} + +static inline ssize_t xwrite(int fd, const void *buf, size_t len) +{ + ssize_t nr; + while (1) { + nr = write(fd, buf, len); + if ((nr < 0) && (errno == EAGAIN || errno == EINTR)) + continue; + return nr; + } +} + +mpd_malloc char *xstrdup(const char *s); + +mpd_malloc void *xmalloc(size_t size); + +mpd_malloc void *xrealloc(void *ptr, size_t size); + +mpd_malloc void *xcalloc(size_t nmemb, size_t size); + +#endif diff --git a/trunk/src/volume.c b/trunk/src/volume.c new file mode 100644 index 000000000..59e8b550c --- /dev/null +++ b/trunk/src/volume.c @@ -0,0 +1,552 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ +#include "volume.h" + +#include "command.h" +#include "conf.h" +#include "log.h" +#include "player.h" +#include "state_file.h" +#include "gcc.h" +#include "utils.h" + +#include <math.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <fcntl.h> +#include <errno.h> +#ifdef HAVE_OSS +#include <sys/soundcard.h> +#endif +#ifdef HAVE_ALSA +#include <alsa/asoundlib.h> +#endif + +#define VOLUME_MIXER_TYPE_SOFTWARE 0 +#define VOLUME_MIXER_TYPE_OSS 1 +#define VOLUME_MIXER_TYPE_ALSA 2 + +#define VOLUME_MIXER_SOFTWARE_DEFAULT "" +#define VOLUME_MIXER_OSS_DEFAULT "/dev/mixer" +#define VOLUME_MIXER_ALSA_DEFAULT "default" +#define VOLUME_MIXER_ALSA_CONTROL_DEFAULT "PCM" +#define SW_VOLUME_STATE "sw_volume: " + +#ifdef HAVE_OSS +#define VOLUME_MIXER_TYPE_DEFAULT VOLUME_MIXER_TYPE_OSS +#define VOLUME_MIXER_DEVICE_DEFAULT VOLUME_MIXER_OSS_DEFAULT +#else +#ifdef HAVE_ALSA +#define VOLUME_MIXER_TYPE_DEFAULT VOLUME_MIXER_TYPE_ALSA +#define VOLUME_MIXER_DEVICE_DEFAULT VOLUME_MIXER_ALSA_DEFAULT +#else +#define VOLUME_MIXER_TYPE_DEFAULT VOLUME_MIXER_TYPE_SOFTWARE +#define VOLUME_MIXER_DEVICE_DEFAULT VOLUME_MIXER_SOFTWARE_DEFAULT +#endif +#endif + +static int volume_mixerType = VOLUME_MIXER_TYPE_DEFAULT; +static char *volume_mixerDevice = VOLUME_MIXER_DEVICE_DEFAULT; + +static int volume_softwareSet = 100; + +#ifdef HAVE_OSS +static int volume_ossFd = -1; +static int volume_ossControl = SOUND_MIXER_PCM; +#endif + +#ifdef HAVE_ALSA +static snd_mixer_t *volume_alsaMixerHandle; +static snd_mixer_elem_t *volume_alsaElem; +static long volume_alsaMin; +static long volume_alsaMax; +static int volume_alsaSet = -1; +#endif + +#ifdef HAVE_OSS + +static void closeOssMixer(void) +{ + while (close(volume_ossFd) && errno == EINTR) ; + volume_ossFd = -1; +} + +static int prepOssMixer(char *device) +{ + ConfigParam *param; + + if ((volume_ossFd = open(device, O_RDONLY)) < 0) { + WARNING("unable to open oss mixer \"%s\"\n", device); + return -1; + } + + param = getConfigParam(CONF_MIXER_CONTROL); + + if (param) { + char *labels[SOUND_MIXER_NRDEVICES] = SOUND_DEVICE_LABELS; + char *dup; + int i, j; + int devmask = 0; + + if (ioctl(volume_ossFd, SOUND_MIXER_READ_DEVMASK, &devmask) < 0) { + WARNING("errors getting read_devmask for oss mixer\n"); + closeOssMixer(); + return -1; + } + + for (i = 0; i < SOUND_MIXER_NRDEVICES; i++) { + dup = xstrdup(labels[i]); + /* eliminate spaces at the end */ + j = strlen(dup) - 1; + while (j >= 0 && dup[j] == ' ') + dup[j--] = '\0'; + if (strcasecmp(dup, param->value) == 0) { + free(dup); + break; + } + free(dup); + } + + if (i >= SOUND_MIXER_NRDEVICES) { + WARNING("mixer control \"%s\" not found at line %i\n", + param->value, param->line); + closeOssMixer(); + return -1; + } else if (!((1 << i) & devmask)) { + WARNING("mixer control \"%s\" not usable at line %i\n", + param->value, param->line); + closeOssMixer(); + return -1; + } + + volume_ossControl = i; + } + + return 0; +} + +static int ensure_oss_open(void) +{ + if ((volume_ossFd < 0 && prepOssMixer(volume_mixerDevice) < 0)) + return -1; + return 0; +} + +static int getOssVolumeLevel(void) +{ + int left, right, level; + + if (ensure_oss_open() < 0) + return -1; + + if (ioctl(volume_ossFd, MIXER_READ(volume_ossControl), &level) < 0) { + closeOssMixer(); + WARNING("unable to read volume\n"); + return -1; + } + + left = level & 0xff; + right = (level & 0xff00) >> 8; + + if (left != right) { + WARNING("volume for left and right is not the same, \"%i\" and " + "\"%i\"\n", left, right); + } + + return left; +} + +static int changeOssVolumeLevel(int fd, int change, int rel) +{ + int current; + int new; + int level; + + if (rel) { + if ((current = getOssVolumeLevel()) < 0) { + commandError(fd, ACK_ERROR_SYSTEM, + "problem getting current volume"); + return -1; + } + + new = current + change; + } else { + if (ensure_oss_open() < 0) + return -1; + new = change; + } + + if (new < 0) + new = 0; + else if (new > 100) + new = 100; + + level = (new << 8) + new; + + if (ioctl(volume_ossFd, MIXER_WRITE(volume_ossControl), &level) < 0) { + closeOssMixer(); + commandError(fd, ACK_ERROR_SYSTEM, "problems setting volume"); + return -1; + } + + return 0; +} +#endif + +#ifdef HAVE_ALSA +static void closeAlsaMixer(void) +{ + snd_mixer_close(volume_alsaMixerHandle); + volume_alsaMixerHandle = NULL; +} + +static int prepAlsaMixer(char *card) +{ + int err; + snd_mixer_elem_t *elem; + char *controlName = VOLUME_MIXER_ALSA_CONTROL_DEFAULT; + ConfigParam *param; + + err = snd_mixer_open(&volume_alsaMixerHandle, 0); + snd_config_update_free_global(); + if (err < 0) { + WARNING("problems opening alsa mixer: %s\n", snd_strerror(err)); + return -1; + } + + if ((err = snd_mixer_attach(volume_alsaMixerHandle, card)) < 0) { + closeAlsaMixer(); + WARNING("problems attaching alsa mixer: %s\n", + snd_strerror(err)); + return -1; + } + + if ((err = + snd_mixer_selem_register(volume_alsaMixerHandle, NULL, + NULL)) < 0) { + closeAlsaMixer(); + WARNING("problems snd_mixer_selem_register'ing: %s\n", + snd_strerror(err)); + return -1; + } + + if ((err = snd_mixer_load(volume_alsaMixerHandle)) < 0) { + closeAlsaMixer(); + WARNING("problems snd_mixer_selem_register'ing: %s\n", + snd_strerror(err)); + return -1; + } + + elem = snd_mixer_first_elem(volume_alsaMixerHandle); + + param = getConfigParam(CONF_MIXER_CONTROL); + + if (param) { + controlName = param->value; + } + + while (elem) { + if (snd_mixer_elem_get_type(elem) == SND_MIXER_ELEM_SIMPLE) { + if (strcasecmp(controlName, + snd_mixer_selem_get_name(elem)) == 0) { + break; + } + } + elem = snd_mixer_elem_next(elem); + } + + if (elem) { + volume_alsaElem = elem; + snd_mixer_selem_get_playback_volume_range(volume_alsaElem, + &volume_alsaMin, + &volume_alsaMax); + return 0; + } + + WARNING("can't find alsa mixer_control \"%s\"\n", controlName); + + closeAlsaMixer(); + return -1; +} + +static int prep_alsa_get_level(long *level) +{ + const char *cmd; + int err; + + if (!volume_alsaMixerHandle && prepAlsaMixer(volume_mixerDevice) < 0) + return -1; + + if ((err = snd_mixer_handle_events(volume_alsaMixerHandle)) < 0) { + cmd = "handle_events"; + goto error; + } + if ((err = snd_mixer_selem_get_playback_volume(volume_alsaElem, + SND_MIXER_SCHN_FRONT_LEFT, + level)) < 0) { + cmd = "selem_get_playback_volume"; + goto error; + } + return 0; + +error: + WARNING("problems getting alsa volume: %s (snd_mixer_%s)\n", + snd_strerror(err), cmd); + closeAlsaMixer(); + return -1; +} + +static int getAlsaVolumeLevel(void) +{ + int ret; + long level; + long max = volume_alsaMax; + long min = volume_alsaMin; + + if (prep_alsa_get_level(&level) < 0) + return -1; + + ret = ((volume_alsaSet / 100.0) * (max - min) + min) + 0.5; + if (volume_alsaSet > 0 && ret == level) { + ret = volume_alsaSet; + } else + ret = (int)(100 * (((float)(level - min)) / (max - min)) + 0.5); + + return ret; +} + +static int changeAlsaVolumeLevel(int fd, int change, int rel) +{ + float vol; + long level; + long test; + long max = volume_alsaMax; + long min = volume_alsaMin; + int err; + + if (prep_alsa_get_level(&level) < 0) + return -1; + + if (rel) { + test = ((volume_alsaSet / 100.0) * (max - min) + min) + 0.5; + if (volume_alsaSet >= 0 && level == test) { + vol = volume_alsaSet; + } else + vol = 100.0 * (((float)(level - min)) / (max - min)); + vol += change; + } else + vol = change; + + volume_alsaSet = vol + 0.5; + volume_alsaSet = volume_alsaSet > 100 ? 100 : + (volume_alsaSet < 0 ? 0 : volume_alsaSet); + + level = (long)(((vol / 100.0) * (max - min) + min) + 0.5); + level = level > max ? max : level; + level = level < min ? min : level; + + if ((err = + snd_mixer_selem_set_playback_volume_all(volume_alsaElem, + level)) < 0) { + commandError(fd, ACK_ERROR_SYSTEM, "problems setting volume"); + WARNING("problems setting alsa volume: %s\n", + snd_strerror(err)); + closeAlsaMixer(); + return -1; + } + + return 0; +} +#endif + +static int prepMixer(char *device) +{ + switch (volume_mixerType) { +#ifdef HAVE_ALSA + case VOLUME_MIXER_TYPE_ALSA: + return prepAlsaMixer(device); +#endif +#ifdef HAVE_OSS + case VOLUME_MIXER_TYPE_OSS: + return prepOssMixer(device); +#endif + } + + return 0; +} + +void finishVolume(void) +{ + switch (volume_mixerType) { +#ifdef HAVE_ALSA + case VOLUME_MIXER_TYPE_ALSA: + closeAlsaMixer(); + break; +#endif +#ifdef HAVE_OSS + case VOLUME_MIXER_TYPE_OSS: + closeOssMixer(); + break; +#endif + } +} + +void initVolume(void) +{ + ConfigParam *param = getConfigParam(CONF_MIXER_TYPE); + + if (param) { + if (0) ; +#ifdef HAVE_ALSA + else if (strcmp(param->value, VOLUME_MIXER_ALSA) == 0) { + volume_mixerType = VOLUME_MIXER_TYPE_ALSA; + volume_mixerDevice = VOLUME_MIXER_ALSA_DEFAULT; + } +#endif +#ifdef HAVE_OSS + else if (strcmp(param->value, VOLUME_MIXER_OSS) == 0) { + volume_mixerType = VOLUME_MIXER_TYPE_OSS; + volume_mixerDevice = VOLUME_MIXER_OSS_DEFAULT; + } +#endif + else if (strcmp(param->value, VOLUME_MIXER_SOFTWARE) == 0) { + volume_mixerType = VOLUME_MIXER_TYPE_SOFTWARE; + volume_mixerDevice = VOLUME_MIXER_SOFTWARE_DEFAULT; + } else { + FATAL("unknown mixer type %s at line %i\n", + param->value, param->line); + } + } + + param = getConfigParam(CONF_MIXER_DEVICE); + + if (param) { + volume_mixerDevice = param->value; + } +} + +void openVolumeDevice(void) +{ + if (prepMixer(volume_mixerDevice) < 0) { + WARNING("using software volume\n"); + volume_mixerType = VOLUME_MIXER_TYPE_SOFTWARE; + } +} + +static int getSoftwareVolume(void) +{ + return volume_softwareSet; +} + +int getVolumeLevel(void) +{ + switch (volume_mixerType) { +#ifdef HAVE_ALSA + case VOLUME_MIXER_TYPE_ALSA: + return getAlsaVolumeLevel(); +#endif +#ifdef HAVE_OSS + case VOLUME_MIXER_TYPE_OSS: + return getOssVolumeLevel(); +#endif + case VOLUME_MIXER_TYPE_SOFTWARE: + return getSoftwareVolume(); + default: + return -1; + } +} + +static int changeSoftwareVolume(int fd, int change, int rel) +{ + int new = change; + + if (rel) + new += volume_softwareSet; + + if (new > 100) + new = 100; + else if (new < 0) + new = 0; + + volume_softwareSet = new; + + /*new = 100.0*(exp(new/50.0)-1)/(M_E*M_E-1)+0.5; */ + if (new >= 100) + new = 1000; + else if (new <= 0) + new = 0; + else + new = + 1000.0 * (exp(new / 25.0) - 1) / (54.5981500331F - 1) + 0.5; + + setPlayerSoftwareVolume(new); + + return 0; +} + +int changeVolumeLevel(int fd, int change, int rel) +{ + switch (volume_mixerType) { +#ifdef HAVE_ALSA + case VOLUME_MIXER_TYPE_ALSA: + return changeAlsaVolumeLevel(fd, change, rel); +#endif +#ifdef HAVE_OSS + case VOLUME_MIXER_TYPE_OSS: + return changeOssVolumeLevel(fd, change, rel); +#endif + case VOLUME_MIXER_TYPE_SOFTWARE: + return changeSoftwareVolume(fd, change, rel); + default: + return 0; + break; + } +} + +void read_sw_volume_state(FILE *fp) +{ + /* strlen(SW_VOLUME_STATE) + strlen('100') + '\0' */ + #define bufsize 16 + char buf[bufsize]; + const size_t len = strlen(SW_VOLUME_STATE); + char *end = NULL; + long int sv; + + if (volume_mixerType != VOLUME_MIXER_TYPE_SOFTWARE) + return; + while (myFgets(buf, bufsize, fp)) { + if (strncmp(buf, SW_VOLUME_STATE, len)) + continue; + sv = strtol(buf + len, &end, 10); + if (mpd_likely(!*end)) + changeSoftwareVolume(STDERR_FILENO, sv, 0); + else + ERROR("Can't parse software volume: %s\n", buf); + return; + } + #undef bufsize +} + +void save_sw_volume_state(FILE *fp) +{ + if (volume_mixerType == VOLUME_MIXER_TYPE_SOFTWARE) + fprintf(fp, SW_VOLUME_STATE "%d\n", volume_softwareSet); +} + diff --git a/trunk/src/volume.h b/trunk/src/volume.h new file mode 100644 index 000000000..fcaefc64d --- /dev/null +++ b/trunk/src/volume.h @@ -0,0 +1,44 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef VOLUME_H +#define VOLUME_H + +#include "../config.h" + +#include <stdio.h> + +#define VOLUME_MIXER_OSS "oss" +#define VOLUME_MIXER_ALSA "alsa" +#define VOLUME_MIXER_SOFTWARE "software" + +void initVolume(void); + +void openVolumeDevice(void); + +void finishVolume(void); + +int getVolumeLevel(void); + +int changeVolumeLevel(int fd, int change, int rel); + +void read_sw_volume_state(FILE *fp); + +void save_sw_volume_state(FILE *fp); + +#endif diff --git a/trunk/src/zeroconf.c b/trunk/src/zeroconf.c new file mode 100644 index 000000000..eeec794cd --- /dev/null +++ b/trunk/src/zeroconf.c @@ -0,0 +1,498 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#include <stdlib.h> +#include <assert.h> +#include <string.h> + +#include "zeroconf.h" +#include "conf.h" +#include "log.h" +#include "listen.h" +#include "ioops.h" +#include "utils.h" + +/* The dns-sd service type qualifier to publish */ +#define SERVICE_TYPE "_mpd._tcp" + +/* The default service name to publish + * (overridden by 'zeroconf_name' config parameter) + */ +#define SERVICE_NAME "Music Player" + +/* Here is the implementation for Avahi (http://avahi.org) Zeroconf support */ +#ifdef HAVE_AVAHI + +#include <avahi-client/client.h> +#include <avahi-client/publish.h> + +#include <avahi-common/alternative.h> +#include <avahi-common/domain.h> +#include <avahi-common/malloc.h> +#include <avahi-common/error.h> + +/* Static avahi data */ +static AvahiEntryGroup *avahiGroup; +static char *avahiName; +static AvahiClient* avahiClient; +static AvahiPoll avahiPoll; +static int avahiRunning; + +static int avahiFdset( fd_set* rfds, fd_set* wfds, fd_set* efds ); +static int avahiFdconsume( int fdCount, fd_set* rfds, fd_set* wfds, fd_set* efds ); +static struct ioOps avahiIo = { + .fdset = avahiFdset, + .consume = avahiFdconsume, +}; + +/* Forward Declaration */ +static void avahiRegisterService(AvahiClient *c); + +struct AvahiWatch { + struct AvahiWatch* prev; + struct AvahiWatch* next; + int fd; + AvahiWatchEvent requestedEvent; + AvahiWatchEvent observedEvent; + AvahiWatchCallback callback; + void* userdata; +}; + +struct AvahiTimeout { + struct AvahiTimeout* prev; + struct AvahiTimeout* next; + struct timeval expiry; + int enabled; + AvahiTimeoutCallback callback; + void* userdata; +}; + +static AvahiWatch* avahiWatchList; +static AvahiTimeout* avahiTimeoutList; + +static AvahiWatch* avahiWatchNew( const AvahiPoll *api, int fd, AvahiWatchEvent event, AvahiWatchCallback callback, void *userdata ) +{ + struct AvahiWatch* newWatch = xmalloc( sizeof(struct AvahiWatch) ); + + newWatch->fd = fd; + newWatch->requestedEvent = event; + newWatch->observedEvent = 0; + newWatch->callback = callback; + newWatch->userdata = userdata; + + /* Insert at front of list */ + newWatch->next = avahiWatchList; + avahiWatchList = newWatch; + newWatch->prev = NULL; + if( newWatch->next ) + newWatch->next->prev = newWatch; + + return newWatch; +} + +static void avahiWatchUpdate( AvahiWatch *w, AvahiWatchEvent event ) +{ + assert( w != NULL ); + w->requestedEvent = event; +} + +static AvahiWatchEvent avahiWatchGetEvents( AvahiWatch *w ) +{ + assert( w != NULL ); + return w->observedEvent; +} + +static void avahiWatchFree( AvahiWatch *w ) +{ + assert( w != NULL ); + + if( avahiWatchList == w ) + avahiWatchList = w->next; + else if( w->prev != NULL ) + w->prev->next = w->next; + + free( w ); +} + +static void avahiCheckExpiry( AvahiTimeout *t ) +{ + assert( t != NULL ); + if( t->enabled ) { + struct timeval now; + gettimeofday( &now, NULL ); + if( timercmp( &now, &(t->expiry), > ) ) { + t->enabled = 0; + t->callback( t, t->userdata ); + } + } +} + +static void avahiTimeoutUpdate( AvahiTimeout *t, const struct timeval *tv ) +{ + assert( t != NULL ); + if( tv ) { + t->enabled = 1; + t->expiry.tv_sec = tv->tv_sec; + t->expiry.tv_usec = tv->tv_usec; + } else { + t->enabled = 0; + } +} + +static void avahiTimeoutFree( AvahiTimeout *t ) +{ + assert( t != NULL ); + + if( avahiTimeoutList == t ) + avahiTimeoutList = t->next; + else if( t->prev != NULL ) + t->prev->next = t->next; + + free( t ); +} + +static AvahiTimeout* avahiTimeoutNew( const AvahiPoll *api, const struct timeval *tv, AvahiTimeoutCallback callback, void *userdata ) +{ + struct AvahiTimeout* newTimeout = xmalloc( sizeof(struct AvahiTimeout) ); + + newTimeout->callback = callback; + newTimeout->userdata = userdata; + + avahiTimeoutUpdate( newTimeout, tv ); + + /* Insert at front of list */ + newTimeout->next = avahiTimeoutList; + avahiTimeoutList = newTimeout; + newTimeout->prev = NULL; + if( newTimeout->next ) + newTimeout->next->prev = newTimeout; + + return newTimeout; +} + +/* Callback when the EntryGroup changes state */ +static void avahiGroupCallback( + AvahiEntryGroup *g, + AvahiEntryGroupState state, + void *userdata) +{ + assert(g); + + DEBUG( "Avahi: Service group changed to state %d\n", state ); + + switch (state) { + case AVAHI_ENTRY_GROUP_ESTABLISHED : + /* The entry group has been established successfully */ + LOG( "Avahi: Service '%s' successfully established.\n", avahiName ); + break; + + case AVAHI_ENTRY_GROUP_COLLISION : { + char *n; + + /* A service name collision happened. Let's pick a new name */ + n = avahi_alternative_service_name(avahiName); + avahi_free(avahiName); + avahiName = n; + + LOG( "Avahi: Service name collision, renaming service to '%s'\n", avahiName ); + + /* And recreate the services */ + avahiRegisterService(avahi_entry_group_get_client(g)); + break; + } + + case AVAHI_ENTRY_GROUP_FAILURE : + ERROR( "Avahi: Entry group failure: %s\n", + avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g))) ); + /* Some kind of failure happened while we were registering our services */ + avahiRunning = 0; + break; + + case AVAHI_ENTRY_GROUP_UNCOMMITED: + DEBUG( "Avahi: Service group is UNCOMMITED\n" ); + break; + case AVAHI_ENTRY_GROUP_REGISTERING: + DEBUG( "Avahi: Service group is REGISTERING\n" ); + ; + } +} + +/* Registers a new service with avahi */ +static void avahiRegisterService(AvahiClient *c) +{ + int ret; + assert(c); + DEBUG( "Avahi: Registering service %s/%s\n", SERVICE_TYPE, avahiName ); + + /* If this is the first time we're called, let's create a new entry group */ + if (!avahiGroup) { + avahiGroup = avahi_entry_group_new(c, avahiGroupCallback, NULL); + if( !avahiGroup ) { + ERROR( "Avahi: Failed to create avahi EntryGroup: %s\n", avahi_strerror(avahi_client_errno(c)) ); + goto fail; + } + } + + /* Add the service */ + /* TODO: This currently binds to ALL interfaces. + * We could maybe add a service per actual bound interface, if that's better. */ + ret = avahi_entry_group_add_service(avahiGroup, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, + avahiName, SERVICE_TYPE, + NULL, NULL, + getBoundPort(), + NULL); + if( ret < 0 ) { + ERROR( "Avahi: Failed to add service %s: %s\n", SERVICE_TYPE, avahi_strerror(ret) ); + goto fail; + } + + /* Tell the server to register the service group */ + ret = avahi_entry_group_commit(avahiGroup); + if( ret < 0 ) { + ERROR( "Avahi: Failed to commit service group: %s\n", avahi_strerror(ret) ); + goto fail; + } + return; + +fail: + avahiRunning = 0; +} + +/* Callback when avahi changes state */ +static void avahiClientCallback(AvahiClient *c, AvahiClientState state, void *userdata) +{ + assert(c); + + /* Called whenever the client or server state changes */ + DEBUG( "Avahi: Client changed to state %d\n", state ); + + switch (state) { + case AVAHI_CLIENT_S_RUNNING: + DEBUG( "Avahi: Client is RUNNING\n" ); + + /* The server has startup successfully and registered its host + * name on the network, so it's time to create our services */ + if (!avahiGroup) + avahiRegisterService(c); + break; + + case AVAHI_CLIENT_FAILURE: + { + int reason = avahi_client_errno(c); + if( reason == AVAHI_ERR_DISCONNECTED ) { + LOG( "Avahi: Client Disconnected, will reconnect shortly\n"); + avahi_entry_group_free( avahiGroup ); + avahiGroup = NULL; + avahi_client_free( avahiClient ); + avahiClient = NULL; + avahiClient = avahi_client_new( &avahiPoll, AVAHI_CLIENT_NO_FAIL, + avahiClientCallback, NULL, &reason ); + if( !avahiClient ) { + ERROR( "Avahi: Could not reconnect: %s\n", avahi_strerror(reason) ); + avahiRunning = 0; + } + } else { + ERROR( "Avahi: Client failure: %s (terminal)\n", avahi_strerror(reason)); + avahiRunning = 0; + } + } + break; + + case AVAHI_CLIENT_S_COLLISION: + DEBUG( "Avahi: Client is COLLISION\n" ); + /* Let's drop our registered services. When the server is back + * in AVAHI_SERVER_RUNNING state we will register them + * again with the new host name. */ + if (avahiGroup) { + DEBUG( "Avahi: Resetting group\n" ); + avahi_entry_group_reset(avahiGroup); + } + + case AVAHI_CLIENT_S_REGISTERING: + DEBUG( "Avahi: Client is REGISTERING\n" ); + /* The server records are now being established. This + * might be caused by a host name change. We need to wait + * for our own records to register until the host name is + * properly esatblished. */ + + if (avahiGroup) { + DEBUG( "Avahi: Resetting group\n" ); + avahi_entry_group_reset(avahiGroup); + } + + break; + + case AVAHI_CLIENT_CONNECTING: + DEBUG( "Avahi: Client is CONNECTING\n" ); + ; + } +} + +static int avahiFdset( fd_set* rfds, fd_set* wfds, fd_set* efds ) +{ + AvahiWatch* w; + int maxfd = -1; + if( !avahiRunning ) + return maxfd; + for( w = avahiWatchList; w != NULL; w = w->next ) { + if( w->requestedEvent & AVAHI_WATCH_IN ) { + FD_SET( w->fd, rfds ); + } + if( w->requestedEvent & AVAHI_WATCH_OUT ) { + FD_SET( w->fd, wfds ); + } + if( w->requestedEvent & AVAHI_WATCH_ERR ) { + FD_SET( w->fd, efds ); + } + if( w->requestedEvent & AVAHI_WATCH_HUP ) { + ERROR( "Avahi: No support for HUP events! (ignoring)\n" ); + } + + if( w->fd > maxfd ) + maxfd = w->fd; + } + return maxfd; +} + +static int avahiFdconsume( int fdCount, fd_set* rfds, fd_set* wfds, fd_set* efds ) +{ + int retval = fdCount; + AvahiTimeout* t; + AvahiWatch* w = avahiWatchList; + + while( w != NULL && retval > 0 ) { + AvahiWatch* current = w; + current->observedEvent = 0; + if( FD_ISSET( current->fd, rfds ) ) { + current->observedEvent |= AVAHI_WATCH_IN; + FD_CLR( current->fd, rfds ); + retval--; + } + if( FD_ISSET( current->fd, wfds ) ) { + current->observedEvent |= AVAHI_WATCH_OUT; + FD_CLR( current->fd, wfds ); + retval--; + } + if( FD_ISSET( current->fd, efds ) ) { + current->observedEvent |= AVAHI_WATCH_ERR; + FD_CLR( current->fd, efds ); + retval--; + } + + /* Advance to the next one right now, in case the callback + * removes itself + */ + w = w->next; + + if( current->observedEvent && avahiRunning ) { + current->callback( current, current->fd, + current->observedEvent, current->userdata ); + } + } + + t = avahiTimeoutList; + while( t != NULL && avahiRunning ) { + AvahiTimeout* current = t; + + /* Advance to the next one right now, in case the callback + * removes itself + */ + t = t->next; + avahiCheckExpiry( current ); + } + + return retval; +} + +static void init_avahi(const char *serviceName) +{ + int error; + DEBUG( "Avahi: Initializing interface\n" ); + + if( avahi_is_valid_service_name( serviceName ) ) { + avahiName = avahi_strdup( serviceName ); + } else { + ERROR( "Invalid zeroconf_name \"%s\", defaulting to \"%s\" instead.\n", serviceName, SERVICE_NAME ); + avahiName = avahi_strdup( SERVICE_NAME ); + } + + avahiRunning = 1; + + avahiPoll.userdata = NULL; + avahiPoll.watch_new = avahiWatchNew; + avahiPoll.watch_update = avahiWatchUpdate; + avahiPoll.watch_get_events = avahiWatchGetEvents; + avahiPoll.watch_free = avahiWatchFree; + avahiPoll.timeout_new = avahiTimeoutNew; + avahiPoll.timeout_update = avahiTimeoutUpdate; + avahiPoll.timeout_free = avahiTimeoutFree; + + avahiClient = avahi_client_new( &avahiPoll, AVAHI_CLIENT_NO_FAIL, + avahiClientCallback, NULL, &error ); + + if( !avahiClient ) { + ERROR( "Avahi: Failed to create client: %s\n", avahi_strerror(error) ); + goto fail; + } + + avahiIo.fdset = avahiFdset; + avahiIo.consume = avahiFdconsume; + registerIO( &avahiIo ); + + return; + +fail: + finishZeroconf(); +} +#else /* !HAVE_AVAHI */ +static void init_avahi(const char *serviceName) { } +#endif /* HAVE_AVAHI */ + +void initZeroconf(void) +{ + const char* serviceName = SERVICE_NAME; + ConfigParam *param; + + param = getConfigParam(CONF_ZEROCONF_NAME); + + if (param && strlen(param->value) > 0) + serviceName = param->value; + init_avahi(serviceName); +} + +void finishZeroconf(void) +{ +#ifdef HAVE_AVAHI + DEBUG( "Avahi: Shutting down interface\n" ); + deregisterIO( &avahiIo ); + + if( avahiGroup ) { + avahi_entry_group_free( avahiGroup ); + avahiGroup = NULL; + } + + if( avahiClient ) { + avahi_client_free( avahiClient ); + avahiClient = NULL; + } + + avahi_free( avahiName ); + avahiName = NULL; +#endif /* HAVE_AVAHI */ +} diff --git a/trunk/src/zeroconf.h b/trunk/src/zeroconf.h new file mode 100644 index 000000000..ef7167d53 --- /dev/null +++ b/trunk/src/zeroconf.h @@ -0,0 +1,27 @@ +/* the Music Player Daemon (MPD) + * Copyright (C) 2003-2007 by Warren Dukes (warren.dukes@gmail.com) + * This project's homepage is: http://www.musicpd.org + * + * 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 + */ + +#ifndef ZEROCONF_H +#define ZEROCONF_H + +#include "../config.h" + +void initZeroconf(void); +void finishZeroconf(void); + +#endif |